The MRI_IMAGE and MRI_IMARR structs
Data Value Types
The type of numerical values stored in an MRI_IMAGE is encoded by one of these values:
typedef enum MRI_TYPE {
MRI_byte , MRI_short , MRI_int ,
MRI_float , MRI_double , MRI_complex , MRI_rgb , MRI_rgba } MRI_TYPE ;
AFNI itself only uses the byte, short, float, complex, and rgb types; the others are not well-supported. Typedefs for complex and rgbyte can be found in the mrilib.h file.
Pointer to Image Data:
The data in the MRI_IMAGE struct is not referred to by a void * pointer; at the time I created MRI_IMAGE, I didn't understand pointers so well. Instead, the union type below is defined to hold the pointer for the actual image data array:
typedef union MRI_DATA {
byte *byte_data ;
short *short_data ;
int *int_data ;
float *float_data ;
double *double_data ;
complex *complex_data ;
byte *rgb_data ; /* Apr 1996: not well supported yet */
rgba *rgba_data ; /* Mar 2002 */
} MRI_DATA ;
I regret this choice, but that's the way it is. Functions and macros to facilitate access to the image data will be described below.
Contents of the MRI_IMAGE struct:
Here is the typedef for the MRI_IMAGE struct:
typedef struct MRI_IMAGE {
int nx ; /*!< 1st dimension of image */
int ny ; /*!< 2nd dimension of image (1 for 1D image) */
int nz ; /*!< 3rd dimension of image (1 for 2D image) */
int nt ; /*!< 4th dimension of image (1 for 3D image) */
int nu ; /*!< 5th dimension of image (1 for 4D image) */
int nv ; /*!< 6th dimension of image (1 for 5D image) */
int nw ; /*!< 7th dimension of image (1 for 6D image) */
int nxy ; /*!< nx*ny */
int nxyz ; /*!< nx*ny*nz */
int nxyzt ; /*!< nx*ny*nz*nt */
int nvox ; /*!< number of voxels total */
int pixel_size ; /*!< bytes per pixel */
MRI_TYPE kind ; /*!< one of the MRI_TYPE codes above */
MRI_DATA im ; /*!< pointer to actual pixel data */
char *name ; /*!< string attached; may be NULL; might be filename */
float dx ; /*!< physical pixel size, if != 0 */
float dy ; /*!< physical pixel size, if != 0 */
float dz ; /*!< physical pixel size, if != 0 */
float dt ; /*!< physical pixel size, if != 0 */
float du ; /*!< physical pixel size, if != 0 */
float dv ; /*!< physical pixel size, if != 0 */
float dw ; /*!< physical pixel size, if != 0 */
float xo ; /*!< spatial origin of axis */
float yo ; /*!< spatial origin of axis */
float zo ; /*!< spatial origin of axis */
float to ; /*!< spatial origin of axis */
float uo ; /*!< spatial origin of axis */
float vo ; /*!< spatial origin of axis */
float wo ; /*!< spatial origin of axis */
int was_swapped ; /* 07 Mar 2002 */
} MRI_IMAGE ;
Not all of these components need be set for a basic image, and some components are set automatically by the MRI_IMAGE creation functions.
You'll notice the oddity that the MRI_IMAGE struct allow for up to 7D images. Originally, it only allowed for 2D images (nx × ny). I extended this to 3D (nz) for the 1994-6 edition of AFNI. In 1996, when I extended AFNI to allow for 3D+time and bucket datasets, I originally thought that I would store a 4D dataset in a 4D MRI_IMAGE. I decided to extend the MRI_IMAGE once and for all to 7D (nx × ny × nz × nt × nu × nv × nw) so I wouldn't have to deal with this issue again. As it happened, I later decided to store a 4D dataset as an array of 3D MRI_IMAGEs, so the extension to 4-7 dimensionality was useless. But it still is there.
The convenience fields nxy, etc. are useful for indexing purposes. The nvox field is the product nx*ny*...*nw, and is the total number of voxels in the image array. The pixel_size field is the number of bytes per image number (e.g., 2 for kind==MRI_short).
The kind field is the type of data stored in the image array; the im field is union that contains the actual pointer to the image array. This field may be NULL (an "empty" image).
The name field is a char * that contains an optional name describing the image. The mri_read() function, for example, sets this field to the filename from which the image data was read.
The dx (etc.) fields don't need to be set for many image purposes. If nonzero, they indicate the pixel dimensions. Similarly, the xo ("o"="offset") field indicate the spatial offset of the image, so that pixel index #i is at x=i*dx+xo for i=0,1,..,nx-1. I don't think these "offset" fields are used anywhere very important in the AFNI code. The grid size fields are used in various places, such as the image viewer (imseq.c).
The was_swapped field is set in mri_read() if the image data was byte-swapped on input. This field is used in to3d.c to avoid double-swapping the file.
The Image Data Array:
Given an MRI_IMAGE, how do you get at the pixel values? The entire image array is stored as a big 1D array, so you have to access it by doing the multi-dimensional indexing yourself. For example, in dealing with a 2D image of shorts, one might have the code fragment
MRI_IMAGE *im ;
short *sar ;
int i,j , nx,ny ;
sar = mri_data_pointer(im) ;
nx = im->nx ; ny = im->ny ;
for( j=0 ; j < ny ; j++ )
for( i=0 ; i < nx ; i++ )
sar[i+j*nx] += 1 ; /* some trivial operation */
Images are stored with the x-direction varying most rapidly, the y-direction next, and so forth. For a 3D image, the (i,j,k) index is [i+j*nx+k*nx*ny].
Creating an MRI_IMAGE:
There are several functions used to create MRI_IMAGEs. The most venerable is the 2D image creation function; for example:
MRI_IMAGE *im = mri_new(nx,ny,MRI_float) ;which creates an image of the specified dimensions and data type. All image pixel values are set to zero (via calloc).
The function call
im = mri_new_vol(nx,ny,nz,MRI_float) ;create a 3D image. To create an "empty" image, with the image array pointer set to NULL, the call would be
im = mri_new_vol_empty(nx,ny,nz,MRI_float);Later, when the image array is created (with malloc() please!), it can be set with a call like
float *far = (float *)malloc(sizeof(float)*nx*ny*nz) ; mri_fix_data_pointer( far , im ) ;Note that if you use mri_fix_data_pointer() on an image with an existing image array, the existing array will not be free-d automatically; if you want that to happen, you must do it yourself. You can call mri_fix_data_pointer() with a NULL pointer to set an image to be "empty".
Function mri_read() tries to read an image from a file, as in
MRI_IMAGE *im = mri_read( "fred.jpg" ) ;If NULL is returned, then the read failed. Most images read this way are 2D, but not all (e.g., an ANALYZE .hdr file might return a 3D image).
You can create a copy of an image via
MRI_IMAGE *newim = mri_copy(oldim) ;Often more useful is to create a new image that "conforms" to an old one -- that is, has the same physical dimensions. This can be done via
MRI_IMAGE *newim = mri_new_conforming(oldim,MRI_float) ;where the second argument indicates that the data type stored in the new image (if you want the same as oldim, use oldim->kind as the second argument). This new image will have its pixel array set to be all zero.
Another useful operation is to make a new image that is a copy of the old image with the data array converted to a new type; for example
MRI_IMAGE *newim = mri_to_mri(MRI_short,oldim) ;Various similar functions, such as mri_to_float() exist for this type of conversion, as well.
Deleting an image from memory is done with the mri_free(im) function.
There are many other utility functions for manipulating MRI_IMAGE structs. Too many to discuss now. Perhaps in a later posting.
MRI_IMARR = Array of MRI_IMAGEs:
The MRI_IMARR struct is used to hold a collection of MRI_IMAGEs.
These image need not all be the same dimensions or data kinds. The typedef
for this struct is:
typedef struct MRI_IMARR {
int num ; /*!< Number of actual MRI_IMAGE here */
int nall ; /*!< Size of imarr array currently allocated */
MRI_IMAGE **imarr ; /*!< Array of MRI_IMAGE pointers */
} MRI_IMARR ;
In many cases, you don't deal with this struct directly, but use some macros
to access and manipulate it. In the examples below, suppose that we have
declared MRI_IMARR *imar;:
- IMARR_COUNT(imar) = number of images stored
- IMARR_SUBIMAGE(imar,n) = the nth image
in the array (for n=0..IMARR_COUNT(imar)-1)
- INIT_IMARR(imar) = initializes imar to point
to an "empty" image array
- ADDTO_IMARR(imar,im) = add MRI_IMAGE *im to
the end of the image array
- DESTROY_IMARR(imar) = calls mri_free() on each
component image, then deletes the imar struct itself
- FREE_IMARR(imar) = deletes the imar struct itself,
but not the component images; this would be used when the images
have been saved in some other place (e.g., put into a different MRI_IMARR)
- TRUNCATE_IMARR(imar,n) = deletes all the component images from #n to the end




