ANALYZETM 7.5 Coordinate System

The coordinate system employed by the ANALYZETM programs is left-handed, with the coordinate origin in the lower left corner. Thus, with the subject lying supine, the coordinate origin is on the right side of the body (x), at the back (y), and at the feet (z).

A major advantage of this convention is that the coordinate origin of each orthogonal orientation (transverse, coronal, and sagittal) lies in the lower left corner of the slice as it is displayed.

Orthogonal slices are numbered from one to the number of slices in that orientation. For example, a volume (x, y, z) dimensioned 128, 256, 48 has:

Pixel coordinates are made with reference to the slice numbers from which the pixels come. Thus, the first pixel in the volume is referenced p(1,1,1) and not at p(0,0,0).

The names of the orthogonal planes can be changed in the Configure program.

This system is illustrated in the following diagrams.

Volume

Origin/Order Consistency

We refer to the ANALYZETM coordinate system as an origin/order system because it is based on an unambiguous single 3-D origin in the image data, and on the idea that the order of pixels in one line of a section, the order of lines in a section, and the order of sections in a volume all proceed from the origin. As shown in Figure 2, the projection of the origin is always displayed at the lower left of the screen, and the order of slices is always from the origin outward. Image data in any of these three orientations (if properly identified) will be correctly rendered as a 3D image (i.e. left-sided defects will appear on the left side of the 3-D rendering). Flipping data in any of the three orientations about two axes will also result in a correctly rendered 3-D image, but the two axis flip will move the effective location of the origin, and the orthogonal sections may appear upside down or left/right reversed when displayed. Flipping the data about one or three axes will ruin the integrity of the data and cause 3-D renderings to appear mirror-reversed (i.e. right-sided lesions will appear to be on the left). Mislabeling transverse data sets as sagittal or vice-versa will not destroy the coherence of the data, and renderings will be correct although the labelling of axes will be in error. However, mislabeling transverse or sagittal images as coronal (or vice-versa) will cause renderings to be mirror reversed.

It should be noted that the choice of a particular origin/order system is largely arbitrary, and that there are 16 possible origin/order systems based on the transverse orientation, and another 16 possible systems based on each of the other two orientations.

Other Consistent Systems

Origin/order systems provide many conveniences to the programmer, because the complete 3-D orientation of a volume image is coded into the order of pixels in the image file. Origin/order is not, however, the only logically consistent way of thinking about 3-D images made up of 2-D sections. Those who work with sectional images which are not inherently registered by the imaging modality (such as serial microscopic sections or photographs of gross tissue specimens) may naturally think of the sections in a view/order system.

View/order consistency is not concerned with the maintenance of a single unambiguous 3-D origin, it simply posits a relationship which should hold between the orientation of the sectional images and their order in the data file. A view/order based system would accept sectional images in any of six orientations, and would assign the x, y, and z axis to the row, column, and slice directions of the image data regardless of the physical section orientation. The main advantage of the view/order concept is that it is highly intuitive and is completely insensitive to orientation. Its major drawback is that the location of the origin and orientations of the axes will vary depending upon the section orientation. Figure 3 illustrates the view order system which most closely matches the origin/order system of ANALYZETM, i.e., it is a left-handed coordinate system based on the transverse ANALYZETM specification. Note carefully the nconsistencies between this system and the one used in ANALYZETM. For example, the first possible transverse orientation of Figure 3 is completely compatible with the ANALYZETM format, while the second transverse format is not. If images in this second transverse format are loaded into ANALYZETM, the 3-D coherence of the image will remain intact, but the initial 3-D rendering will be upside down. On the other hand, neither of the Coronal orientations of Figure 3 are compatible with ANALYZETM,and loading images in this order will cause a mirror-reversed 3-D rendering.

Other Systems

Finally, other systems of data order may be completely arbitrary. Figure 4 illustrates the conventions currently in use at the Mayo Foundation for 3-D MR images. The transverse standard is also the current convention for CT. Note that the conventions describe a consistently right-handed system, but it is not an origin/order system because that the location of the 3-D origin is ambiguous. The order of coronal images implies that the origin is at the back of the head, while the orientation of the transverse and sagittal images clearly indicates it is at the front. Note also that it is not a consistent view/order system, and to add further confusion, the sagittal orientation is standard only for GE and Siemens and is not followed by other manufacturers.

The ANALYZETM functions for reading GE and Siemens MR tapes correctly convert the above orientations into the ANALYZETM system, but this means the orientation and order of sections in ANALYZE TM differs from that seen on the MR console.

We strongly suggest a carefully review of data order conventions whenever integrating a new source of images into the ANALYZETM system. Only properly labelled images in the correct orientation can be accurately rendered by ANALYZETM, and mirror-reversed images are often almost impossible to detect.


ANALYZETM 7.5 File Format

The image database is the system of files that the ANALYZETM package uses to organize and access image data on the disk. Facilities are provided for converting data from a number of sources for use with the package. A description of the database format is provided to aid developers in porting images from other sources for use with the ANALYZE TM system. An ANALYZETM image database consists of at least two files:

The files have the same name being distinguished by the extensions .img for the image file and .hdr for the header file. Thus, for the image database heart, there are the UNIX files heart.img and heart.hdr. The ANALYZETM programs all refer to this pair of files as a single entity named heart.

Image File

The format of the image file is very simple containing usually uncompressed pixel data for the images in one of several possible pixel formats:

Header File

The header file is represented here as a `C' structure which describes the dimensions and history of the pixel data. The header structure consists of three substructures:

header_key               describes the header
image_dimension    describes image sizes
data_history             optional

ANALYZETM Header File Format (analyze_db.h)

struct header_key       /* header key   */ 
       {                                /* off + size      */
       int sizeof_hdr                   /* 0 + 4           */
       char data_type[10];              /* 4 + 10          */
       char db_name[18];                /* 14 + 18         */
       int extents;                     /* 32 + 4          */
       short int session_error;         /* 36 + 2          */
       char regular;                    /* 38 + 1          */
       char hkey_un0;                   /* 39 + 1          */
       };                               /* total=40 bytes  */
       struct image_dimension 
       {                                /* off + size      */
       short int dim[8];                /* 0 + 16          */
       short int unused8;               /* 16 + 2          */
       short int unused9;               /* 18 + 2          */
       short int unused10;              /* 20 + 2          */
       short int unused11;              /* 22 + 2          */
       short int unused12;              /* 24 + 2          */
       short int unused13;              /* 26 + 2          */
       short int unused14;              /* 28 + 2          */
       short int datatype;              /* 30 + 2          */
       short int bitpix;                /* 32 + 2          */
       short int dim_un0;               /* 34 + 2          */
       float pixdim[8];                 /* 36 + 32         */
       float funused8;                  /* 68 + 4          */
       float funused9;                  /* 72 + 4          */
       float funused10;                 /* 76 + 4          */
       float funused11;                 /* 80 + 4          */
       float funused12;                 /* 84 + 4          */
       float funused13;                 /* 88 + 4          */
       float compressed;                /* 92 + 4          */
       float verified;                  /* 96 + 4          */
       int glmax,glmin;                 /* 100 + 8         */
       };                               /* total=108 bytes */
struct data_history       
       {                                /* off + size      */
       char descrip[80];                /* 0 + 80          */
       char aux_file[24];               /* 80 + 24         */
       char orient;                     /* 104 + 1         */
       char originator[10];             /* 105 + 10        */
       char generated[10];              /* 115 + 10        */
       char scannum[10];                /* 125 + 10        */
       char patient_id[10];             /* 135 + 10        */
       char exp_date[10];               /* 145 + 10        */
       char exp_time[10];               /* 155 + 10        */
       char hist_un0[3];                /* 165 + 3         */
       int views                        /* 168 + 4         */
       int vols_added;                  /* 172 + 4         */
       int start_field;                 /* 176 + 4         */
       int field_skip;                  /* 180 + 4         */
       int omax, omin;                  /* 184 + 8         */
       int smax, smin;                  /* 192 + 8         */
       };
struct dsr
       { 
       struct header_key hk;            /* 0 + 40          */
       struct image_dimension dime;     /* 40 + 108        */
       struct data_history hist;        /* 148 + 200       */
       };                               /* total= 348 bytes*/   


Comments

The header format is flexible and can be extended for new user-defined data types. The essential structures of the header are the header_key and the image_dimension.

The required elements in the header_key substructure are:

int sizeof_header      Must indicate the byte size of the header file.
int extents              Should be 16384, the image file is created as contiguous with a minimum extent size.
char regular              Must be `r' to indicate that all images and volumes are the same size.

The image_dimension substructure describes the organization and size of the images. These elements enable the database to reference images by volume and slice number. Explanation of each element follows:

short int dim[];      /* array of the image dimensions */
dim[0]      Number of dimensions in database; usually 4
dim[1]      Image X dimension; number of pixels in an image row
dim[2]      Image Y dimension; number of pixel rows in slice
dim[3]      Volume Z dimension; number of slices in a volume
cdim[4]      Time points, number of volumes in database.
char vox_units[4]     specifies the spatial units of measure for a voxel
char cal_units[4]      specifies the name of the calibration unit
short int datatype      /* datatype for this image set */
/*Acceptable values for datatype are*/
#define DT_NONE                        0
#define DT_UNKNOWN               0      /*Unknown data type*/
#define DT_BINARY                    1      /*Binary (1 bit per voxel)*/
#define DT_UNSIGNED_CHAR     2      /*Unsigned character (8 bits per voxel)*/
#define DT_SIGNED_SHORT        4      /*Signed short (16 bits per voxel)*/
#define DT_SIGNED_INT             8      /*Signed integer (32 bits per voxel)*/
#define DT_FLOAT                     16     /*Floating point (32 bits per voxel)*/
#define DT_COMPLEX                32     /*Complex (64 bits per voxel; 2 floating point numbers)
#define DT_DOUBLE                   64     /*Double precision (64 bits per voxel)*/
#define DT_RGB                         128    /* */
#define DT_ALL                         255    /* */
short int bitpix;        /* number of bits per pixel; 1, 8, 16, 32, or 64. */
short int dim_un0;   /* unused */
float pixdim[];       Parallel array to dim[], giving real world measurements in mm. and ms.
pixdim[1];      voxel width in mm.
pixdim[2];      voxel height in mm.
pixdim[3];      slice thickness in mm.
float vox_offset;      byte offset in the .img file at which voxels start. This value can be
                                   negative to specify that the absolute value is applied for every image
                                   in the file.
float calibrated Max, Min    specify the range of calibration values
int glmax, glmin;    The maximum and minimum pixel values for the entire database.

The data_history substructure is not required, but the orient field is used to indicate individual slice orientation and determines whether the Movie program will attempt to flip the images before displaying a movie sequence.

orient:       slice orientation for this dataset.
0      transverse unflipped
1      coronal unflipped
2      sagittal unflipped
3      transverse flipped
4      coronal flipped
5      sagittal flipped

Sample Program

Any image data can be ported to the ANALYZETM system by creating the appropriate image and header files. Although header files can be created with the Header Edit program, the following C program is provided to illustrate how to make an ANALYZETM image database header file given the critical image dimensions as parameters. For example;

make_header heart.hdr 128 128 97 3 CHAR 255 0

Makes the header file heart.hdr with the following dimensions:


/* This program creates an ANALYZETM database header  */
/*
 * (c) Copyright, 1986-1995
 * Biomedical Imaging Resource
 * Mayo Foundation
 *
 * to compile:
 *
 *    cc -o make_hdr make_hdr.c
 *
 */
#include <stdio.h>
#include "dbh.h"

main(argc,argv) /* file x y z t datatype max min */
int argc;
char **argv;
{
    int i;
    struct dsr hdr;
    FILE *fp;
    static char DataTypes[9][12] = {"UNKNOWN", "BINARY",
          "CHAR", "SHORT", "INT","FLOAT", "COMPLEX", 
          "DOUBLE", "RGB"};
                                                           
    static int DataTypeSizes[9] = {0,1,8,16,32,32,64,64,24};
    
    if(argc != 9)
    {
                       usage();
        exit(0);
    }
    memset(&hdr,0, sizeof(struct dsr));
    for(i=0;i<8;i++)
               hdr.dime.pixdim[i] = 0.0;
   
    hdr.dime.vox_offset  = 0.0;
    hdr.dime.funused1    = 0.0;
    hdr.dime.funused2    = 0.0;
    hdr.dime.funused3    = 0.0;
    hdr.dime.cal_max     = 0.0;
    hdr.dime.cal_min     = 0.0;
  
    
    hdr.dime.datatype = -1;

    for(i=1;i<=8;i++)
               if(!strcmp(argv[6],DataTypes[i]))
               {
                       hdr.dime.datatype = (1<<(i-1));
                       hdr.dime.bitpix = DataTypeSizes[i];
                       break;
               }
               
    if(hdr.dime.datatype <= 0)
    {
               printf("<%s> is an unacceptable datatype \n\n", argv[6]);
               usage();
        exit(0);
    }
 
    if((fp=fopen(argv[1],"w"))==0)
    {
        printf("unable to create: %s\n",argv[1]);
        exit(0);
    }

    hdr.dime.dim[0] = 4;  /* all Analyze images are taken as 4 dimensional */
    hdr.hk.regular = 'r';
    hdr.hk.sizeof_hdr = sizeof(struct dsr);

    hdr.dime.dim[1] = atoi(argv[2]);  /* slice width  in pixels */
    hdr.dime.dim[2] = atoi(argv[3]);  /* slice height in pixels */
    hdr.dime.dim[3] = atoi(argv[4]);  /* volume depth in slices */
    hdr.dime.dim[4] = atoi(argv[5]);  /* number of volumes per file */

    hdr.dime.glmax  = atoi(argv[7]);  /* maximum voxel value  */
    hdr.dime.glmin  = atoi(argv[8]);  /* minimum voxel value */
    



/*     Set the voxel dimension fields: 
       A value of 0.0 for these fields implies that the value is unknown.
         Change these values to what is appropriate for your data
         or pass additional command line arguments     */      
         
    hdr.dime.pixdim[1] = 0.0; /* voxel x dimension */
    hdr.dime.pixdim[2] = 0.0; /* voxel y dimension */
    hdr.dime.pixdim[3] = 0.0; /* pixel z dimension, slice thickness */
    
/*   Assume zero offset in .img file, byte at which pixel
       data starts in the image file */

    hdr.dime.vox_offset = 0.0; 
    
/*   Planar Orientation;    */
/*   Movie flag OFF: 0 = transverse, 1 = coronal, 2 = sagittal
     Movie flag ON:  3 = transverse, 4 = coronal, 5 = sagittal  */  

    hdr.hist.orient     = 0;  
    
/*   up to 3 characters for the voxels units label; i.e. mm., um., cm. */               */

    strcpy(hdr.dime.vox_units," ");
   
/*   up to 7 characters for the calibration units label; i.e. HU */

    strcpy(hdr.dime.cal_units," ");  
    
/*     Calibration maximum and minimum values;  
       values of 0.0 for both fields imply that no 
       calibration max and min values are used    */

    hdr.dime.cal_max = 0.0; 
    hdr.dime.cal_min = 0.0;

    fwrite(&hdr,sizeof(struct dsr),1,fp);
    fclose(fp);
}

usage()
{
   printf("usage:  make_hdr name.hdr x y z t datatype max min \n\n");
   printf("  name.hdr = the name of the header file\n");
   printf("  x = width, y = height,  z = depth,  t = number of volumes\n");
   printf("  acceptable datatype values are: BINARY, CHAR, SHORT,\n");
   printf("                 INT, FLOAT, COMPLEX, DOUBLE, and RGB\n");
   printf("  max = maximum voxel value,  min = minimum voxel value\n");
}

The following program displays information in an Analyze header file.

#include <stdio.h>
#include "dbh.h"

void ShowHdr(char *, struct dsr *);
void swap_long(unsigned char *);
void swap_short(unsigned char *);

main(argc,argv) 
int argc;
char **argv;
    {
    struct dsr hdr;
    int size;
    double cmax, cmin;
    FILE *fp;
    
       if((fp=fopen(argv[1],"r"))==NULL)
    {
        fprintf(stderr,"Can't open:<%s>\n", argv[1]);
        exit(0);
    }
    fread(&hdr,1,sizeof(struct dsr),fp);

       if(hdr.dime.dim[0] < 0 || hdr.dime.dim[0] > 15)
               swap_hdr(&hdr);
    
     ShowHdr(argv[1], &hdr);
    

     }
     
        


void ShowHdr(fileName,hdr)
struct dsr *hdr;
char *fileName;
{
int i;
char string[128];
printf("Analyze Header Dump of: <%s> \n", fileName);
/* Header Key */
printf("sizeof_hdr: <%d> \n", hdr->hk.sizeof_hdr);
printf("data_type:  <%s> \n", hdr->hk.data_type);
printf("db_name:    <%s> \n", hdr->hk.db_name);
printf("extents:    <%d> \n", hdr->hk.extents);
printf("session_error: <%d> \n", hdr->hk.session_error);
printf("regular:  <%c> \n", hdr->hk.regular);
printf("hkey_un0: <%c> \n", hdr->hk.hkey_un0);

/* Image Dimension */
for(i=0;i<8;i++)
       printf("dim[%d]: <%d> \n", i, hdr->dime.dim[i]);
       
       strncpy(string,hdr->dime.vox_units,4);
       printf("vox_units:  <%s> \n", string);
       
       strncpy(string,hdr->dime.cal_units,8);
       printf("cal_units: <%s> \n", string);
       printf("unused1:   <%d> \n", hdr->dime.unused1);
       printf("datatype:  <%d> \n", hdr->dime.datatype);
       printf("bitpix:    <%d> \n", hdr->dime.bitpix);
       
for(i=0;i<8;i++)
       printf("pixdim[%d]: <%6.4f> \n",i, hdr->dime.pixdim[i]);
       
printf("vox_offset: <%6.4> \n",  hdr->dime.vox_offset);
printf("funused1:   <%6.4f> \n", hdr->dime.funused1);
printf("funused2:   <%6.4f> \n", hdr->dime.funused2);
printf("funused3:   <%6.4f> \n", hdr->dime.funused3);
printf("cal_max:    <%6.4f> \n", hdr->dime.cal_max);
printf("cal_min:    <%6.4f> \n", hdr->dime.cal_min);
printf("compressed: <%d> \n", hdr->dime.compressed);
printf("verified:   <%d> \n", hdr->dime.verified);
printf("glmax:      <%d> \n", hdr->dime.glmax);
printf("glmin:      <%d> \n", hdr->dime.glmin);

/* Data History */
strncpy(string,hdr->hist.descrip,80);
printf("descrip:  <%s> \n", string);
strncpy(string,hdr->hist.aux_file,24);
printf("aux_file: <%s> \n", string);
printf("orient:   <%d> \n", hdr->hist.orient);

strncpy(string,hdr->hist.originator,10);
printf("originator: <%s> \n", string);

strncpy(string,hdr->hist.generated,10);
printf("generated: <%s> \n", string);


strncpy(string,hdr->hist.scannum,10);
printf("scannum: <%s> \n", string);

strncpy(string,hdr->hist.patient_id,10);
printf("patient_id: <%s> \n", string);

strncpy(string,hdr->hist.exp_date,10);
printf("exp_date: <%s> \n", string);

strncpy(string,hdr->hist.exp_time,10);
printf("exp_time: <%s> \n", string);

strncpy(string,hdr->hist.hist_un0,10);
printf("hist_un0: <%s> \n", string);

printf("views:      <%d> \n", hdr->hist.views);
printf("vols_added: <%d> \n", hdr->hist.vols_added);
printf("start_field:<%d> \n", hdr->hist.start_field);
printf("field_skip: <%d> \n", hdr->hist.field_skip);
printf("omax: <%d> \n", hdr->hist.omax);
printf("omin: <%d> \n", hdr->hist.omin);
printf("smin: <%d> \n", hdr->hist.smax);
printf("smin: <%d> \n", hdr->hist.smin);

}


swap_hdr(pntr)
struct dsr *pntr;
       {
       swap_long(&pntr->hk.sizeof_hdr) ;
       swap_long(&pntr->hk.extents) ;
       swap_short(&pntr->hk.session_error) ;
       swap_short(&pntr->dime.dim[0]) ;
       swap_short(&pntr->dime.dim[1]) ;
       swap_short(&pntr->dime.dim[2]) ;
       swap_short(&pntr->dime.dim[3]) ;
       swap_short(&pntr->dime.dim[4]) ;
       swap_short(&pntr->dime.dim[5]) ;
       swap_short(&pntr->dime.dim[6]) ;
       swap_short(&pntr->dime.dim[7]) ;
       swap_short(&pntr->dime.unused1) ;
       swap_short(&pntr->dime.datatype) ;
       swap_short(&pntr->dime.bitpix) ;
       swap_long(&pntr->dime.pixdim[0]) ;
       swap_long(&pntr->dime.pixdim[1]) ;
       swap_long(&pntr->dime.pixdim[2]) ;
       swap_long(&pntr->dime.pixdim[3]) ;
       swap_long(&pntr->dime.pixdim[4]) ;
       swap_long(&pntr->dime.pixdim[5]) ;
       swap_long(&pntr->dime.pixdim[6]) ;
       swap_long(&pntr->dime.pixdim[7]) ;
       swap_long(&pntr->dime.vox_offset) ;
       swap_long(&pntr->dime.funused1) ;
       swap_long(&pntr->dime.funused2) ;
       swap_long(&pntr->dime.cal_max) ;
       swap_long(&pntr->dime.cal_min) ;
       swap_long(&pntr->dime.compressed) ;
       swap_long(&pntr->dime.verified) ;
       swap_short(&pntr->dime.dim_un0) ;
       swap_long(&pntr->dime.glmax) ;
       swap_long(&pntr->dime.glmin) ;
       }
       
swap_long(pntr)
unsigned char *pntr;
        {
        unsigned char b0, b1, b2, b3;

        b0 = *pntr;
        b1 = *(pntr+1);
        b2 = *(pntr+2);
        b3 = *(pntr+3);

        *pntr = b3;
        *(pntr+1) = b2;
        *(pntr+2) = b1;
        *(pntr+3) = b0;
        }
        
swap_short(pntr)
unsigned char *pntr;
        {
        unsigned char b0, b1;

        b0 = *pntr;
        b1 = *(pntr+1);

        *pntr = b1;
        *(pntr+1) = b0;
        }