Get Teem at SourceForge.net. Fast, secure and Free Open Source software downloads
teem / nrrd

  Introduction

What Nrrd Is

Nrrd is a tool for representing and processing N-dimensional raster data. It offers an efficient and general way to manipulate scientific and medical images and volumes. Nrrd facilitates the pre-processing tasks that often precede visualization or segmentation (cropping, quantizing, resampling), as well as the basic analysis tasks which help to understand the structure of a dataset (histograms, maximum intensity projections, connected components). Nrrd has a persistent habit of replacing the throw-away one-time code that people often write to transform datasets, while also offering a convenient, flexible, and portable file format for storing the data.

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.

Data Dimension

Nrrd is intended to represent raster data of any dimension greater than zero. Ideally there would be no run-time limit on the representable dimension. The current implementation (the nrrd library) has put a #define limit on the dimension; NRRD_DIM_MAX is currently set to 16. One subtle aspect of dimension involves the handling of non-scalar data. A simplifying design decision in nrrd is that "everything is a scalar". Thus, in order to represent a 3-dimensional field of vectors, a 4-dimensional nrrd is used, usually with the (X,Y,Z) components of the vector interleaved between sample points. In this case, the first axis has 3 samples, while the second, third, and fourth axes correspond to the spatial dimensions of the grid. "Everything is a scalar" because the vector field is represented as a 4-D array of scalars instead of a 3-D array of vectors. In contrast, for scalar 3-D data, the spatial dimensions are represented by the first, second, and third axes of a 3-dimensional nrrd. While it be seem strange to be using nrrds of different dimensions for what is conceptually the same grid, experience has shown that this is not a hinderance to using nrrds to represent and operate on scalar, vector, and tensor data, especially given the low-level flavor of the nrrd functions.

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.

Raster Order

The ordering of axes is fundamental. In nrrd, whenever the axes are represented or identified in sequence, the ordering is always from "fastest" to "slowest". If we assign coordinates to each axis, and traverse the physical layout of the raster data from the first sample (with all zero coordinates) to the last sample, the fastest axis corresponds to that coordinate which increments fastest as we traverse the samples. Thus, nrrd's axis ordering for typical interleaved color image data is color, horizontal, and vertical.

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.

Data Axes

There are many aspects of an axis that nrrd can represent. The number of samples is the only required information. There is a dynamically allocated char* string which can be used to give a name to each axis. The spacing between samples is also represented, and if there is a known extent to the axis in some "world" space, the min and max position of the axis can also be represented. Univariate histograms and joint histograms naturally have a min and max along their axes; many volume datasets have only a notion of spacing. Whether the samples along an axis are cell or node centered can also be indicated. This plays an important role in representing histograms (necessarily cell-centered) and image data (probably cell-centered if you're talking about mipmaps, but node-centered in many other signal-processing contexts).

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.

Data Type

Nrrd can handle ten different types of scalar data. There are 8 integral types (1-, 2-, 4-, and 8-byte integers, signed and unsigned), and two floating point types (4-byte floats, 8-byte doubles). These are currently implemented with the C types "char", "short", "int", and "long long" (prefixed by either "signed" or "unsigned"), and "float" and "double". Although "long double" is available on many systems, it has no standard size (8 bytes on Mac OSX, 12 bytes on Intel, 16 bytes elsewhere), so it is not included in 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.