MINC Programmer's Guide | ||
---|---|---|
<<< Previous | The MINC format |
One of the requirements for file formats mentioned earlier was a software interface to make the interface easy to use. The biggest difficulty in using a flexible format is that the application must handle many possibilities. Where images are concerned, this means various data types and scale factors, and images of differing sizes. The image conversion variable functions of MINC attempt to remove this complication for the programmer.
An image conversion variable (icv) is essentially a specification of what the program wants images to look like, in type, scale and dimension. When an MINC image is read through an icv, it is converted for the calling program to a standard format regardless of how data is stored in the file.
There are two categories of conversion: Type and range conversions change the datatype (and sign) of image values and optionally scale them for proper normalization. Dimension conversions allow programs to specify image dimension size and image axis orientation (should MIxspace coordinates be increasing or decreasing? should the patient's left side appear on the left or right of the image?).
Accessing a file through an icv is a straight-forward process. Create the icv with miicv_create, set properties (like desired datatype) with the miicv_set routines, attach the icv to a NetCDF variable with miicv_attach and access the data with miicv_get or miicv_put. The icv can be detached from a NetCDF variable with miicv_detach and can be freed with miicv_free.
Icv properties are strings, integers, long integers or doubles. For example, MI_ICV_SIGN (the sign of variable values) is a string, while MI_ICV_IMAGE_MAX (image maximum) is a double precision value. Four functions --- miicv_setint, miicv_setlong, miicv_setdbl and miicv_setstr --- are provided to simplify the setting of property values. Programs can inquire about property values with miicv_inqint, miicv_inqlong, miicv_inqdbl and miicv_inqstr.
Pixel values are converted for type and sign by specifying values for the properties MI_ICV_TYPE and MI_ICV_SIGN (they default to NC_SHORT and MI_SIGNED). Values can also be converted for valid range and for normalization. These conversions are enabled by setting MI_ICV_DO_RANGE to TRUE (the default).
If MI_ICV_DO_NORM is FALSE (the default) then only conversions for valid range are made. This means that if the input file has shorts in the range 0 to 4095, then they can be converted to bytes in the range 64 to 248 (for example). The real image maximum and minimum (MIimagemax and MIimagemin) are ignored. The valid range is specified by the properties MI_ICV_VALID_MAX and MI_ICV_VALID_MIN, which default to the legal range for the type and sign.
We may want to scale values so that they are normalized either to all values in the MIimage variable or to some user-defined range. To do normalization, set MI_ICV_DO_NORM to TRUE. Setting MI_ICV_USER_NORM to FALSE (the default) causes normalization to the real maximum and minimum of the variable (the maximum of MIimagemax and the minimum of MIimagemin). If MI_ICV_USER_NORM is true then the values of MI_ICV_IMAGE_MAX and MI_ICV_IMAGE_MIN are used (defaulting to 1.0 and 0.0).
When either MI_ICV_TYPE or the file type is floating-point, then the conversion to and from real values is always done using the real image maximum and minimum information. If the internal type is integer and MI_ICV_DO_NORM is FALSE, then the rescaling is done so that the slice maximum maps to the valid range of the internal values.
Note that when converting to integer types, values are rounded to the nearest integer and limited to be within the legal range for the data type.
The above transformations are simple enough, but the use of floating-point values adds to the complexity, since in general we do not want to rescale these values to get the real values. The various possibilities are described in greater detail below.
The easiest way to think about the rescaling is through four ranges (maximum-minimum pairs). In the file variable, values have a valid range var_vrange and these correspond to real values var_imgrange. The user/application wants to convert real values usr_imgrange to a useful valid range usr_vrange. From var_vrange, var_imgrange, usr_imgrange and usr_vrange, we can determine a scale and offset for converting pixel values: Input values are scaled to real values by var_vrange to var_imgrange and then scaled again to user values by usr_imgrange to usr_vrange.
If either of the vrange variables are not specified, they default to maximum possible range for integer types. For floating point types, usr_vrange is set equal to usr_imgrange so that no conversion of real values is done.
If normalization is not being done, then for integer types var_imgrange and usr_imgrange are set to [0,1] (scale down to [0,1] and scale up again). When normalizibng, usr_imgrange is set to either the full range of the variable ([0,1] if not found) or the user's requested range. If the variable values are floating point, then var_imgrange is set to var_vrange (no scaling to real values), otherwise var_imgrange is read for each image (again, [0,1] if not found). What this means for reading and writing images is discussed below.
When reading into internal floating point values, normalization has no effect. When reading integers without normalization, each image is scaled to full range. With normalization they are scaled to the specified range and slices can be compared.
When the input file is missing either MIimagemax/MIimagemin (var_imgrange information) or MIvalid_range, the routines try to provide sensible defaults, but funny things can still happen. The biggest problem is the absence of MIvalid_range if the defaults are not correct (full range for integer values and [0,1] for floating point). When converting floating point values to an integer type, there will be overflows if values are outside the range [0,1].
The conversion routines can be used for writing values. This can be useful for data compression --- e.g. converting internal floats to byte values in the file, or converting internal shorts to bytes. When doing this with normalization (to rescale bytes to the slice maximum, for example) it is important to write the slice maximum and minimum in MIimagemax and MIimagemin before writing the slice.
The other concern is that MIvalid_range or MIvalid_max and MIvalid_min be written properly (especially if the defaults are not correct). When writing floating point values, MIvalid_range should be set to the full range of values in the variable. In this case, the attribute does not have to be set correctly before writing the variable, but if it exists, the values should be reasonable (maximum greater than minimum and values not likely to cause overflow). These will be set automatically if the routine micreate_std_variable is used with NC_FILL mode on (the default).
Example 1. Read an image without normalization:
/* Create the icv */ icv=miicv_create(); (void) miicv_setint(icv, MI_ICV_TYPE, NC_SHORT); (void) miicv_setstr(icv, MI_ICV_SIGN, MI_UNSIGNED); (void) miicv_setint(icv, MI_ICV_VALID_MAX, 32000); (void) miicv_setint(icv, MI_ICV_VALID_MIN, 0); /* Open the file, attach the image variable */ cdfid=ncopen(filename, NC_NOWRITE); /* Attach image variable */ img=ncvarid(cdfid, MIimage); (void) miicv_attach(icv, cdfid, img); /* Get the data - we assume that coord and count are set properly */ (void) miicv_get(icv, coord, count, image); /* Close the file and free the icv */ (void) ncclose(cdfid); (void) miicv_free(icv); |
Example 2. Read an integer image with normalization:
/* Create the icv */ icv=miicv_create(); (void) miicv_setint(icv, MI_ICV_TYPE, NC_SHORT); (void) miicv_setstr(icv, MI_ICV_SIGN, MI_UNSIGNED); (void) miicv_setint(icv, MI_ICV_VALID_MAX, 32000); (void) miicv_setint(icv, MI_ICV_VALID_MIN, 0); (void) miicv_setint(icv, MI_ICV_DO_NORM, TRUE); (void) miicv_setint(icv, MI_ICV_USER_NORM, TRUE); (void) miicv_setdbl(icv, MI_ICV_IMAGE_MAX, 1.83); (void) miicv_setdbl(icv, MI_ICV_IMAGE_MIN, -0.57); ... |
Example 3. Read a floating point image:
/* Create the icv. We don't have to set MI_ICV_USER_NORM to TRUE, but doing so ensures that the conversion is done properly without looking at file values (the defaults for MI_ICV_IMAGE_MAX and MI_ICV_IMAGE_MIN are 1 and 0) */ icv=miicv_create(); (void) miicv_setint(icv, MI_ICV_TYPE, NC_FLOAT); (void) miicv_setint(icv, MI_ICV_DO_NORM, TRUE); (void) miicv_setint(icv, MI_ICV_USER_NORM, TRUE); ... |
Example 4. Writing from floating point to byte values:
/* Create the icv */ icv=miicv_create(); (void) miicv_setint(icv, MI_ICV_TYPE, NC_FLOAT); (void) miicv_setint(icv, MI_ICV_DO_NORM, TRUE); /* Create the file */ cdf=nccreate(filename, NC_CLOBBER); /* Define the dimensions */ dim[0]=ncdimdef(cdf, MIyspace, ysize); dim[1]=ncdimdef(cdf, MIxspace, xsize); /* Define the variables */ img=micreate_std_variable(cdf, MIimage, NC_BYTE, 2, dim); (void) miattputstr(cdf, img, MIsigntype, MI_UNSIGNED); vrange[0]=0; vrange[1]=200; (void) ncattput(cdf, img, MIvalid_range, NC_DOUBLE, 2, vrange); max=micreate_std_variable(cdf, MIimagemax, NC_DOUBLE, 0, NULL); min=micreate_std_variable(cdf, MIimagemin, NC_DOUBLE, 0, NULL); /* End definition mode */ ncendef(cdf); /* Attach image variable */ (void) miicv_attach(icv, cdf, img); /* Write the image max and min */ ncvarput1(cdf, max, NULL, &image_maximum); ncvarput1(cdf, min, NULL, &image_minimum); /* Write the image */ start[0]=start[1]=0; count[0]=ysize; count[1]=xsize; miicv_put(icv, start, count, vals); /* Close the file and free the icv */ (void) ncclose(cdf); (void) miicv_free(icv); |
If we were writing a floating point image, the only difference (apart from changing NC_BYTE to NC_FLOAT) would be that we would rewrite MIvalid_range at the end of the file with the full range of floating point values.
One of the problems of arbitrary dimensioned images is that it becomes necessary for software to handle the general case. It is easier to write application software if it is known in advance that all images will have a specific size (e.g. 256 x 256) and a specific orientation (e.g. the first pixel is at the patient's anterior, right side).
By setting the icv property MI_ICV_DO_DIM_CONV to TRUE these conversions can be done automatically. The orientation of spatial axes is determined by the properties MI_ICV_XDIM_DIR, MI_ICV_YDIM_DIR and MI_ICV_ZDIM_DIR. These affect any image dimensions that are MI?space or MI?frequency where ? corresponds to x, y or z. These properties can have values MI_ICV_POSITIVE, MI_ICV_NEGATIVE or MI_ICV_ANYDIR. The last of these will prevent flipping of the dimension. The first two will flip the dimension if necessary so that the attribute MIstep of the dimension variable will have the correct sign.
The two image dimensions are referred to as dimensions A and B. Dimension A is the fastest varying dimension of the two. Setting properties MI_ICV_ADIM_SIZE and MI_ICV_BDIM_SIZE specify the desired size for the image dimension. Dimensions are resized so that the file image will fit entirely in the calling program's image, and is centred in the image. The size MI_ICV_ANYSIZE allows one of the dimensions to have a variable size. If property MI_ICV_KEEP_ASPECT is set to TRUE, then the two dimensions are rescaled by the same amount. It is possible to inquire about the new step and start, corresponding to attributes MIstep and MIstart (where pixel position = ipixel*step+start, with ipixel counting from zero). The properties MI_ICV_?DIM_STEP and MI_ICV_?DIM_START (? = A or B) are set automatically and can be inquired but not set.
Although vector images are allowed, many applications would rather only deal with scalar images (one intensity value at each point). Setting MI_ICV_DO_SCALAR to TRUE (the default) will cause vector images to be converted to scalar images by averaging the components. (Thus, RGB images are automatically converted to gray-scale images in this simple way).
It can sometimes be useful for a program to perform dimension conversions on three (or perhaps more) dimensions, not just the two standard image dimensions. To perform dimension flipping and/or resizing on dimensions beyond the usual two, the property MI_ICV_NUM_IMGDIMS can be set to an integer value between one and MI_MAX_IMGDIMS. To set the size of a dimension, set the property MI_ICV_DIM_SIZE (analogous to MI_ICV_ADIM_SIZE). To specify the dimension to be set, add the dimension to the property (adding zero corresponds to the fastest varying dimension --- add zero for the ``A'' dimension, one for the ``B'' dimension, etc.). Voxel separation and location can be inquired about through the properties MI_ICV_DIM_STEP and MI_ICV_DIM_START (analogous to MI_ICV_ADIM_STEP and MI_ICV_ADIM_START), again adding the dimension number to the property.
Reading a 256 x 256 image with the first pixel at the patient's inferior, posterior, left side as short values between 0 and 32000:
/* Create the icv */ icv=miicv_create(); (void) miicv_setint(icv, MI_ICV_TYPE, NC_SHORT); (void) miicv_setstr(icv, MI_ICV_SIGN, MI_UNSIGNED); (void) miicv_setint(icv, MI_ICV_VALID_MAX, 32000); (void) miicv_setint(icv, MI_ICV_VALID_MIN, 0); (void) miicv_setint(icv, MI_ICV_DO_DIM_CONV, TRUE); (void) miicv_setint(icv, MI_ICV_ADIM_SIZE, 256); (void) miicv_setint(icv, MI_ICV_BDIM_SIZE, 256); (void) miicv_setint(icv, MI_ICV_KEEP_ASPECT, TRUE); (void) miicv_setint(icv, MI_ICV_XDIM_DIR, MI_POSITIVE); (void) miicv_setint(icv, MI_ICV_YDIM_DIR, MI_POSITIVE); (void) miicv_setint(icv, MI_ICV_ZDIM_DIR, MI_POSITIVE); /* Open the file, attach the image variable */ cdfid=ncopen(filename, NC_NOWRITE); /* Attach image variable */ img=ncvarid(cdfid, MIimage); (void) miicv_attach(icv, cdfid, img); /* Get the data - we assume that coord and count are set properly */ (void) miicv_get(icv, coord, count, image); /* Close the file and free the icv */ (void) ncclose(cdfid); (void) miicv_free(icv); |
<<< Previous | Home | |
MINC specific convenience functions | Up |