![]() |
![]() | ||
teem | / | nrrd |
Introduction |
In memory and on disk, the scanline ordering of the data on a regular lattice imposes a strict linear ordering of all the samples. A basic capability of nrrd is managing the relationship between the multi-dimensional logical structure of raster data, and its one-dimensional physical layout. There are four fundamental aspects of this sort of data: the dimension of the grid, the raster order of the samples, additional information about each axis, and the type of data associated with each sample point. These four aspects are now considered in more detail.
Having complete dimensional generality complicates the terms "dimension" and "dimensions". While "dimension" (singular) could be used strictly to refer to the dimensionality of the grid itself (1 for histograms, 2 for grayscale images, 3 for color images, etc.), and "dimensions" (plural) could refer to the number of samples along the axes, this seems risky. In the context of nrrd, "dimension" is used solely in in the former sense (how many dimensions are there), and "size" is always used in the latter sense (how many samples are there). Another possibility would have to use "dimensions" and "rank" for the two terms. Thus, an NTSC color image represented in nrrd would have dimension 3, with sizes 3, 640, and 480.
What we call these axes is an entirely seperate issue of convention: "X" could refer to the fastest or slowest spatial dimension. Different schemes for creating multidimensional arrays in C/C++ have different associated axes ordering. Indexing data by val[x][y][z], for instance, implies that the X axis is slowest. On the other hand, if sx and sy are the sizes of the X and Y axes, respectively, then indexing data by *(val + x + sx*(y + sy*z)) implies that the X axis is fastest. Nrrd is wholly agnostic on these matters; it imposes or implies no policy on axis names or on indexing methods. Axes are identified in nrrd only by their integral index into the axis ordering. For the color data example mentioned above, the color axis is 0, horizontal is 1, and vertical is 2.
All this peripheral information describing each axis is saved to the NRRD file with nrrdSave() and nrrdWrite(), and read in with nrrdLoad() and nrrdRead(). Also, the axis information follows each axis through all the basic nrrd operations. For example, if you have a volume with axis labels "x", "y", and "z", and you use nrrdSlice() to cut the volume along axis 0, the resulting 2-dimensional nrrd will have axis labels "y" and "z". If you use nrrdSpatialResample() to downsample a 640x480 cell-centered grayscale image, with spacings 1.0 and 1.0, to a 256x256 image, the resulting spacings will be 2.5 and 1.875 (but 2.50588 and 1.87843 if it was node-centered). The proper and intelligent handling of peripheral information, such as cell-vs-node centering, and axis-specific sample spacing, is the central difference between "nearly raw" and "raw" raster data. The absence of flexible and powerful tools for operating on nearly raw raster data with complete dimensional generality was the primary motivation for starting nrrd development in 1998. The rest of the teem libraries grew out of nrrd.
The cautious and principled C/C++ programmer will quickly point out, however, that no type can be counted on to have a specific bit-size, according to the definition of the C language. All we know is that sizeof(char) is 1, but a char need not be 8 bits. Only the C99 language offers guaranteed bit-length representation of types. In spite of this, and to its pleasant surprise, nrrd development actually has yet to encounter a machine on which the types sizes listed above did not hold, or on which a byte was not 8 bits. Thus, the C types listed above are common in the nrrd implementation across all platforms: GNU/Linux, Solaris (32 and 64 bit), and Irix (32 and 64 bit), Mac OSX, and Windows (which has different names for the 8-byte integers). As soon as one presents itself, a machine with different underlying type sizes will provide the opportunity and context for implementing and testing a system of type abstraction which has, to date, not been required.
There is one final nrrd type, called "block". This is actually not a scalar, but an opaque chunk of memory of some specified length. This allows nrrd to represent and operate on, for example, a volume of C structs or C++ objects, as long as they are all the same size. Nrrd can't determine a scalar value from a a block, nor can it create blocks from scalar values, so it can't do operations like histogramming or filtered resampling on arrays of blocks. But many other operations are supported, such as slicing, cropping, axis permuting, and some kinds of padding.