This page is intended to be a basic introduction to programming with
the Nrrd API. The stand-alone NrrdIO library includes
all the functions described in Section 1 through 7; it lacks the
functions (listed in Section 8) which perform non-trivial
manipulations of the array values. This page will make the most sense
if you already know what sort of data and meta-data can be represented
in a nrrd. The Introduction to Nrrd is
useful in this respect, as is the General
Description of the NRRD Format. The Definition of NRRD File Format is a
detailed and "bottom-up" view of what can go into a nrrd, from the
standpoint of the written file. This will do the same thing, from the
standpoint of what is available as data structures in memory.
1. Data Structures
We start with the Nrrd data structure. This comes in two
parts: the struct for per-axis information (NrrdAxis), and the main
Nrrd struct which includes an array of NrrdAxiss. These
are extracted from teem/src/nrrd/nrrd.h
(or NrrdIO.h).
/*
******** NrrdAxisInfo struct
**
** all the information which can sensibly be associated with
** one axis of a nrrd. The only member which MUST be explicitly
** set to something meaningful is "size".
**
** If an axis lies conceptually along some direction in an enclosing
** space of dimension nrrd->spaceDim, then the first nrrd->spaceDim
** entries of spaceDirection[] must be non-NaN, and min, max, spacing,
** and units must NOT be set; thickness, center, and label can still
** be used. The mutual exclusion between axis-aligned and general
** direction information is enforced per-axis, not per-array.
**
** The min and max values give the range of positions "represented"
** by the samples along this axis. In node-centering, "min" IS the
** position at the lowest index. In cell-centering, the position at
** the lowest index is between min and max (a touch bigger than min,
** assuming min < max).
**
** There needs to be a one-to-one correspondence between these variables
** and the nrrdAxisInfo* enum (nrrdEnums.h), the per-axis header fields
** (see nrrdField* enum in nrrdEnums.h), and the various methods in axis.c
*/
typedef struct {
size_t size; /* number of elements along each axis */
double spacing; /* if non-NaN, distance between samples */
double thickness; /* if non-NaN, nominal thickness of region
represented by one sample along the axis. No
semantics relative to spacing are assumed or
imposed, and unlike spacing, there is no
sensible way to alter thickness- it is either
copied (as with cropping and slicing) or set to
NaN (when resampled). */
double min, max; /* if non-NaN, range of positions spanned by the
samples on this axis. Obviously, one can set
"spacing" to something incompatible with min
and max: the idea is that only one (min and
max, or spacing) should be taken to be
significant at any time. */
double spaceDirection[NRRD_SPACE_DIM_MAX];
/* the vector, in "space" (as described by
nrrd->space and/or nrrd->spaceDim), from one
sample to the next sample along this axis. It
is the column vector of the transform from
index space to "space" space */
int center; /* cell vs. node centering (value should be one of
nrrdCenter{Unknown,Node,Cell} */
int kind; /* what kind of information is along this axis
(from the nrrdKind* enum) */
char *label, /* short info string for each axis */
*units; /* string identifying the unit */
} NrrdAxisInfo;
/*
******** Nrrd struct
**
** The struct used to wrap around the raw data array
*/
typedef struct {
/*
** NECESSARY information describing the main array. This is
** generally set at the same time that either the nrrd is created,
** or at the time that the nrrd is wrapped around an existing array
*/
void *data; /* the data in memory */
int type; /* a value from the nrrdType enum */
unsigned int dim; /* the dimension (rank) of the array */
/*
** All per-axis specific information
*/
NrrdAxisInfo axis[NRRD_DIM_MAX]; /* axis[0] is the fastest axis in the scan-
line ordering, the one who's coordinates
change the fastest as the elements are
accessed in the order in which they
appear in memory */
/*
** Optional information descriptive of whole array, some of which is
** meaningfuly for only some uses of a nrrd
*/
char *content; /* brief account of what this data is */
char *sampleUnits; /* units of measurement of the values
stored in the array itself (not the
array axes and not space coordinates).
The logical name might be "dataUnits",
but that's perhaps ambiguous. Note that
these units may apply to non-scalar
kinds (e.g. coefficients of a vector
have the same units) */
int space; /* from nrrdSpace* enum, and often
implies the value of spaceDim */
unsigned int spaceDim; /* if non-zero, the dimension of the space
in which the regular sampling grid
conceptually lies. This is a separate
variable because this dimension can be
different than the array dimension.
The non-zero-ness of this value is in
fact the primary indicator that space
and orientation information is set.
This identifies the number of entries in
"origin" and the per-axis "direction"
vectors that are taken as meaningful */
char *spaceUnits[NRRD_SPACE_DIM_MAX];
/* units for coordinates of space */
double spaceOrigin[NRRD_SPACE_DIM_MAX];
/* the location of the center the first
(lowest memory address) array sample,
regardless of node-vs-cell centering */
double measurementFrame[NRRD_SPACE_DIM_MAX][NRRD_SPACE_DIM_MAX];
/* if spaceDim is non-zero, this may store
a spaceDim-by-spaceDim matrix which
transforms vector/matrix coefficients
in the "measurement frame" to those in
the world space described by spaceDim
(and hopefully space). Coeff [i][j] is
*column* i & *row* j, which is probably
the *transpose* of what you expect.
There are no semantics linking this to
the "kind" of any axis, for a variety
of reasons */
size_t blockSize; /* for nrrdTypeBlock, block byte size */
double oldMin, oldMax; /* if non-NaN, and if nrrd is of integral
type, extremal values for the array
BEFORE it was quantized */
void *ptr; /* never read or set by nrrd; use/abuse
as you see fit */
/*
** Comments. Read from, and written to, header.
** The comment array "cmt" is NOT NULL-terminated.
** The number of comments is cmtArr->len.
*/
char **cmt;
airArray *cmtArr;
/*
** Key-value pairs.
*/
char **kvp;
airArray *kvpArr;
} Nrrd;
Nrrd uses C enums for representing discrete information with more
semantic content than a normal integer. The three most important are:
- nrrd->type takes on values from the nrrdType enum:
/*
******** nrrdType enum
**
** all the different types, identified by integer
*/
enum {
nrrdTypeUnknown=0, /* 0: signifies "type is unset/unknown" */
nrrdTypeDefault=0, /* 0: signifies "determine output type for me" */
nrrdTypeChar, /* 1: signed 1-byte integer */
nrrdTypeUChar, /* 2: unsigned 1-byte integer */
nrrdTypeShort, /* 3: signed 2-byte integer */
nrrdTypeUShort, /* 4: unsigned 2-byte integer */
nrrdTypeInt, /* 5: signed 4-byte integer */
nrrdTypeUInt, /* 6: unsigned 4-byte integer */
nrrdTypeLLong, /* 7: signed 8-byte integer */
nrrdTypeULLong, /* 8: unsigned 8-byte integer */
nrrdTypeFloat, /* 9: 4-byte floating point */
nrrdTypeDouble, /* 10: 8-byte floating point */
nrrdTypeBlock, /* 11: size user defined at run time; MUST BE LAST */
nrrdTypeLast
};
- nrrd->axis[i].center takes on values from the nrrdCenter enum:
/*
******** nrrdCenter enum
**
** node-centered vs. cell-centered
*/
enum {
nrrdCenterUnknown, /* 0: no centering known for this axis */
nrrdCenterNode, /* 1: samples at corners of things
(how "voxels" are usually imagined)
|\______/|\______/|\______/|
X X X X */
nrrdCenterCell, /* 2: samples at middles of things
(characteristic of histogram bins)
\___|___/\___|___/\___|___/
X X X */
nrrdCenterLast
};
- nrrd->axis[i].kind takes on values from the nrrdKind enum:
/*
******** nrrdKind enum
**
** For describing the information along one axis of an array. This is
** most important for clarifying the representation of non-scalar
** data, in order to distinguish between axes that are genuine image
** domain axes, and axes that exist just to store the multiple
** attributes per sample. One could argue that this information
** should be per-array and not per-axis, but you still have to
** indicate which one of the axes is the attribute axis. And, if you
** have, say, the gradient of RGB colors, you want the per-pixel 3x3
** array to have those two attribute axes tagged accordingly.
**
** More of these may be added in the future, such as when nrrd
** supports bricking. Since nrrd is never going to be in the business
** of manipulating the kind information or supporting kind-specific
** semantics, there can be proliferation of nrrdKinds, provided
** pointless redundancy is avoided.
**
** There is a relationship between some of these (nrrdKindSpace is a
** specific nrrdKindDomain), but currently there is no effort to
** record this meta-kind information.
**
** Keep in sync:
** enumsNrrd.c: nrrdKind airEnum
** axis.c: nrrdKindSize()
** axis.c: _nrrdKindAltered()
**
** NOTE: The nrrdKindSize() function returns the valid size for these.
**
*/
enum {
nrrdKindUnknown,
nrrdKindDomain, /* 1: any image domain */
nrrdKindSpace, /* 2: a spatial domain */
nrrdKindTime, /* 3: a temporal domain */
/* -------------------------- end domain kinds */
/* -------------------------- begin range kinds */
nrrdKindList, /* 4: any list of values, non-resample-able */
nrrdKindPoint, /* 5: coords of a point */
nrrdKindVector, /* 6: coeffs of (contravariant) vector */
nrrdKindCovariantVector, /* 7: coeffs of covariant vector (eg gradient) */
nrrdKindNormal, /* 8: coeffs of unit-length covariant vector */
/* -------------------------- end arbitrary size kinds */
/* -------------------------- begin size-specific kinds */
nrrdKindStub, /* 9: axis with one sample (a placeholder) */
nrrdKindScalar, /* 10: effectively, same as a stub */
nrrdKindComplex, /* 11: real and imaginary components */
nrrdKind2Vector, /* 12: 2 component vector */
nrrdKind3Color, /* 13: ANY 3-component color value */
nrrdKindRGBColor, /* 14: RGB, no colorimetry */
nrrdKindHSVColor, /* 15: HSV, no colorimetry */
nrrdKindXYZColor, /* 16: perceptual primary colors */
nrrdKind4Color, /* 17: ANY 4-component color value */
nrrdKindRGBAColor, /* 18: RGBA, no colorimetry */
nrrdKind3Vector, /* 19: 3-component vector */
nrrdKind3Gradient, /* 20: 3-component covariant vector */
nrrdKind3Normal, /* 21: 3-component covector, assumed normalized */
nrrdKind4Vector, /* 22: 4-component vector */
nrrdKindQuaternion, /* 23: (x,y,z,w), not necessarily normalized */
nrrdKind2DSymMatrix, /* 24: Mxx Mxy Myy */
nrrdKind2DMaskedSymMatrix, /* 25: mask Mxx Mxy Myy */
nrrdKind2DMatrix, /* 26: Mxx Mxy Myx Myy */
nrrdKind2DMaskedMatrix, /* 27: mask Mxx Mxy Myx Myy */
nrrdKind3DSymMatrix, /* 28: Mxx Mxy Mxz Myy Myz Mzz */
nrrdKind3DMaskedSymMatrix, /* 29: mask Mxx Mxy Mxz Myy Myz Mzz */
nrrdKind3DMatrix, /* 30: Mxx Mxy Mxz Myx Myy Myz Mzx Mzy Mzz */
nrrdKind3DMaskedMatrix, /* 31: mask Mxx Mxy Mxz Myx Myy Myz Mzx Mzy Mzz */
nrrdKindLast
};
In general, you do not directly set the struct members- you set this
information via functions in the Nrrd API. As a matter of
convenience, however, it is common and okay to read value directly
from the Nrrd struct, although many accessor functions are
available as well. The value of looking at the struct definitions above is
to understand what information can be represented. There is one
NrrdAxis struct for each axis (or dimension) in the dataset,
and the only necessary piece of information is the size;
everything else is optional meta-information. spacing,
thickness, min, and max are all doubles
which follow the convention that if no useful value is known, then the
value is set to AIR_NAN, which represents
Not-A-Number. label and units are standard C char*
strings. The center field takes on values from the
nrrdCenter enum (above).
In the Nrrd struct, the basic information describing the
array are given in data, type, and dim.
type takes values from the nrrdType enum (above).
Any and all information about the array that is
specific to each axis is in the axis array of
NrrdAxis structs (note that this is an array of structs, not
an array of pointers to structs). The rest of the Nrrd
struct is for optional meta-information about the array. Of
particular note is the kvp key/value pair array; this becomes
the catch-all for other meta-information that you wish to associated
with an array in a way that will be saved out to a NRRD file, and
recovered upon reading in the file.
2. Input/Output functions, with error reporting
There are four main I/O functions; two functions for reading, and two
for writing, and you choose depending on whether you giving a
char* filename or a FILE* file pointer:
way to specify I/O
| Input
| Output
|
char* filename
| nrrdLoad()
| nrrdSave()
|
FILE* file pointer
| nrrdRead()
| nrrdWrite()
|
nrrdLoad() and nrrdSave() are more commonly used.
As with most functions in Teem libraries, these functions
return an int to signify an error condition or successful
completion. Teem uses verbose, detailed, textual error messages
in favor of cryptic, integer error codes. The error messages
accumulate in a seperate library devoted to error handling, called
biff. Thus, handling errors in Nrrd (or any other
Teem library) involves making biff calls to get the
error messages. Here is a fairly complete/pedantic example of using
nrrdLoad and nrrdSave with error handling:
#include <teem/nrrd.h>
void
demoIO(char *filename) {
char me[]="demoIO", newname[]="foo.nrrd", *err;
Nrrd *nin;
/* create a nrrd; at this point this is just an empty container */
nin = nrrdNew();
/* read in the nrrd from file */
if (nrrdLoad(nin, filename, NULL)) {
err = biffGetDone(NRRD);
fprintf(stderr, "%s: trouble reading \"%s\":\n%s", me, filename, err);
free(err);
return;
}
/* say something about the array */
printf("%s: \"%s\" is a %d-dimensional nrrd of type %d (%s)\n",
me, filename, nin->dim, nin->type,
airEnumStr(nrrdType, nin->type));
printf("%s: the array contains %d elements, each %d bytes in size\n",
me, (int)nrrdElementNumber(nin), (int)nrrdElementSize(nin));
/* write out the nrrd to a different file */
if (nrrdSave(newname, nin, NULL)) {
err = biffGetDone(NRRD);
fprintf(stderr, "%s: trouble writing \"%s\":\n%s", me, newname, err);
free(err);
return;
}
/* blow away both the Nrrd struct *and* the memory at nin->data
(nrrdNix() frees the struct but not the data,
nrrdEmpty() frees the data but not the struct) */
nrrdNuke(nin);
return;
}
Things to note here:
- With nrrdLoad() and nrrdSave(), a non-zero return
means "there were problems"; a return of zero means "everything is okay".
- The NULL argument to nrrdLoad() and nrrdSave()
is a place-holder for a NrrdIO struct, which is used whenever
you want to over-ride the default behavior of the I/O functions.
Experience has shown that the defaults probably do what you want.
- You pass "NRRD" to biffGetDone() because you know
that the function that just died is in the Nrrd library.
To be good, you do have to free the char* that is returned
from biff.
- nrrdType is a thing called an airEnum, which
facilitates mapping between strings and integral enum values.
airEnumStr() maps from the enum value to a string
(airEnumVal() goes the other way).
- nrrdElementNumber() and nrrdElementSize() both
return type size_t, thus the cast to int for printing.
This code snippet is available as a stand-alone program demoIO.c. When invoked on the hi.png file created on the Other Formats page:
>> demoIO hi.png
demoIO: "hi.png" is a 2-dimensional nrrd of type 2 (unsigned char)
demoIO: the array contains 35 elements, each 1 bytes in size
>> demoIO foo.nrrd
demoIO: "foo.nrrd" is a 2-dimensional nrrd of type 2 (unsigned char)
demoIO: the array contains 35 elements, each 1 bytes in size
>> unu head foo.nrrd
NRRD0001
content: (unsigned char)(???)
type: unsigned char
dimension: 2
sizes: 7 5
encoding: raw
>> rm -f foo.nrrd
>> demoIO foo.nrrd
demoIO: trouble reading "foo.nrrd":
[nrrd] nrrdLoad: fopen("foo.nrrd","rb") failed: No such file or directory
- unu head is being demonstrated as an easy way to inspect
the header of a NRRD file (much easier than using more or
less!)
3. Reading a NRRD with a different memory allocator
In some cases you want to read a NRRD in, but you want to do the
memory allocation, instead of the nrrd library doing it for you. This
is supported in nrrd, but you have to read the nrrd in two steps: the
first time reads the nrrd header so that you can figure out how much
memory to allocate, and the second time reads in the data (as well
as re-reading the header). This function demonstrates this:
Nrrd *
customLoad(char *filename) {
char me[]="demoIO", *err;
NrrdIoState *nio;
Nrrd *nin;
void *data;
/* create a new nrrd */
nin = nrrdNew();
/* tell nrrdLoad to only read the header, not the data */
nio = nrrdIoStateNew();
nrrdIoStateSet(nio, nrrdIoStateSkipData, AIR_TRUE);
/* read in the nrrd header from file */
if (nrrdLoad(nin, filename, nio)) {
err = biffGetDone(NRRD);
fprintf(stderr, "%s: trouble reading \"%s\" header:\n%s",
me, filename, err);
free(err); nio = nrrdIoStateNix(nio);
return NULL;
}
/* we're done with the nrrdIoState, this sets it to NULL */
nio = nrrdIoStateNix(nio);
/* 1) YOU, the nrrd user, look at things like nin->dim, nin->type
nin->axis[0].size, nin->axis[1].size, etc in order allocate the data
for the nrrd. There are two convenience functions you should use:
1) nrrdElementNumber(nin) returns the total number of sample values.
2) nrrdElementSize(nin) returns the number of bytes in each value.
Make sure that the data is allocated as one single contiguous address
range, or else nrrd will not operate correctly on the values. */
/* 2) YOU now set nin->data to the beginning of your allocated memory */
nin->data = YOUR POINTER HERE;
/* 3) Now load the nrrd again, this time actually reading the data.
The existing nin->data memory will be used, because nrrdRead
(called by nrrdLoad) remembers the address and size of incoming
allocated data, and eventually it will get used, instead of
nrrd allocating new memory */
if (nrrdLoad(nin, filename, NULL)) {
err = biffGetDone(NRRD);
fprintf(stderr, "%s: trouble reading \"%s\" data:\n%s",
me, filename, err);
free(err);
return NULL;
}
/* return the nrrd. When your program is done with the nrrd, the right
way to get rid of it is probably nrrdNix(): this will destroy the
Nrrd struct and its storage for meta information, but not the underlying
nrrd->data. nrrdNuke(), on the other hand, will free(nin->data) */
return nin;
}
4. Getting data into and out of nrrds
Suppose that you have a 128-by-128-by-64 3-D array of doubles
in memory (pointed to by the variable val) that you've
generated as part of some computation, and you want to save this array
out to disk in NRRD format. Forgetting about error checking, this can
be done in four steps:
Nrrd *nval;
...
nval = nrrdNew();
nrrdWrap(nval, val, nrrdTypeDouble, 3, 128, 128, 64);
nrrdSave("val.nrrd", nval, NULL);
nrrdNix(nval);
nrrdWrap() uses var-args based on the dimension of the array
(3 in the example above) to know how many integer axis size arguments
to expect. Calling nrrdNix() and not nrrdNuke() here
is important: we just want to get rid of the struct around the data,
not the data itself. One thing to keep in mind is that currently,
nrrd has no notion of data ownership. The Nrrd struct
doesn't know anything about whether or not its okay to delete the
array inside when you called nrrdNuke() on it.
The raster ordering of the data determines whether the last arguments
to nrrdWrap() should be (128, 128, 64) or (64, 128, 128).
In nrrd, axis-related things are always listed from
fast to slow. See the Introduction
for an explanation of fast and slow axes.
Adding error checking is not too hard:
Nrrd *nval;
char *err;
...
nval = nrrdNew();
if (nrrdWrap(nval, val, nrrdTypeDouble, 3, 128, 128, 64)
|| nrrdSave("val.nrrd", nval, NULL)) {
err = biffGetDone(NRRD);
...
}
nrrdNix(nval);
The idiom here is that a long sequence of nrrd calls, all
of which return ints, can be chained together as a single
boolean expression, so that as soon as one fails, none of the following
ones will be called.
If you want nrrd to allocate the memory for you, then
you would use nrrdAlloc() and nrrdNuke():
Nrrd *nval;
double *val;
int sx=128, sy=128, sz=64;
...
nval = nrrdNew();
nrrdAlloc(nval, nrrdTypeDouble, 3, sx, sy, sz);
val = (double*)nval->data;
val[10 + sx*(20 + sy*30)] = 42; /* val[x=10,y=20,z=30] = 42 */
...
nrrdNuke(nval);
Calling nrrdNuke() is appropriate here because nrrd
allocated the memory for you in nrrdAlloc(), and
nrrdNuke() will free that memory. The "val[10 + sx*(20 +
sy*30)] = 42" is just one example of how to access values in a
multi-dimensional array, but many others are possible and preferable.
Nrrd is so agnostic about multi-dimensional data access that it
provides no direct facilities for setting and getting values based on
a coordinate vector; there are only minimal facilities for
setting/getting values based on a one-dimensional index.
Remember this pairing: nrrdWrap() with nrrdNix(),
and nrrdAlloc() with nrrdNuke().
5. Setting/Getting per-axis information
The ability to store meta-information descriptive of each axis of a
multi-dimensional array is one of the main differences between "raw"
data, and the "nearly raw" data that nrrd supports. The API
has a few functions, all starting with nrrdAxisInfo..., which
are used to set and get axis information, but there are only two which
are commonly used: nrrdAxisInfoGet() and
nrrdAxisInfoSet(). The way to communicate which field
of the NrrdAxis struct that you wish to set/get is via
a C enum, which includes these self-descriptive values:
- nrrdAxisInfoSize
- nrrdAxisInfoSpacing
- nrrdAxisInfoThickness
- nrrdAxisInfoMin
- nrrdAxisInfoMax
- nrrdAxisInfoSpaceDirection
- nrrdAxisInfoCenter
- nrrdAxisInfoKind
- nrrdAxisInfoLabel
- nrrdAxisInfoUnits
Note the one-to-one correspondence between these enum values and the
individual fields in the NrrdAxis struct defined above. For
example, if you want to set labels ("x", "y", "z") for each of the
three axes in nval:
nrrdAxisInfoSet(nval, nrrdAxisInfoLabel, "x", "y", "z");
Because nrrd has the mentality that "everything is a scalar",
you'll need a 4-D array to represent a volume in which each voxel
has multiple values associated with it, such as with a 3-D vector
field. The "kind" per-axis information is how nrrd provides
guidance on representing this situation:
nrrdAxisInfoSet(nvec, nrrdAxisInfoKind,
nrrdKind3Vector, nrrdKindSpace, nrrdKindSpace, nrrdKindSpace);
nrrdAxisInfoSet(nvec, nrrdAxisInfoLabel, "Vx;Vy;Vz", "x", "y", "z");
Recovering these values from a given nrrd can be done this way
if you know exactly what the dimension of the nrrd is:
char *lab0, *lab1, *lab2, *lab3;
int *kind0, *kind1, *kind2, *kind3;
...
nrrdAxisInfoGet(nvec, nrrdAxisInfoLabel, &lab0, &lab1, &lab2, &lab3);
nrrdAxisInfoGet(nvec, nrrdAxisInfoKind, &kind0, &kind1, &kind2, &kind3);
printf("labels were: \"%s\", \"%s\", \"%s\", \"%s\"\n",
lab0, lab1, lab2, lab3);
printf("kinds were: %s (%d), %s (%d), %s (%d), %s (%d)\n",
airEnumStr(nrrdKind, kind0), kind0,
airEnumStr(nrrdKind, kind1), kind1,
airEnumStr(nrrdKind, kind2), kind2,
airEnumStr(nrrdKind, kind3), kind3);
It may be easier to use the dimensionally general version of the
function:
char *lab[NRRD_DIM_MAX];
int kind[NRRD_DIM_MAX];
int di;
...
nrrdAxisInfoGet_nva(nvec, nrrdAxisInfoLabel, lab);
nrrdAxisInfoGet_nva(nvec, nrrdAxisInfoKind, kind);
for (di=0; didim; di++) {
printf("axis %d: label = \"%s\", kind = %s (%d)\n",
di, lab[di], airEnumStr(nrrdKind, kind[di]), kind[di]);
}
- NRRD_DIM_MAX is a compile-time limit on the dimension
of the nrrd that can be handled. This is currently set to 16.
Having a compile-time max greatly simplifies many things that would
otherwise have to be handled dynamically.
- Note that the function name ends with "_nva" to signify
"no variable arguments"; all var-arg nrrd functions have an
analagous non-var-arg version that ends with _nva.
- Rule of thumb for string allocation and the nrrd API:
every string is strdup()ed. That means that internally,
nrrdAxisInfoSet() copies the strings passed to it (so it is
not reliant on the long-term existance of the strings passed to it),
and nrrdAxisInfoGet() gives you copies of the strings stored
inside (so that you can free these strings without worrying about
invalidating the internal pointers,
e.g. nval->axis[0].label).
Here is a more complete example of setting axis labels, centerings, and
spacings:
nrrdAxisInfoSet(nvec, nrrdAxisInfoLabel, "Vx;Vy;Vz", "x", "y", "z");
nrrdAxisInfoSet(nvec, nrrdAxisInfoKind,
nrrdKind3Vector, nrrdKindSpace, nrrdKindSpace, nrrdKindSpace);
nrrdAxisInfoSet(nvec, nrrdAxisInfoCenter,
nrrdCenterUnknown,
nrrdCenterNode, nrrdCenterNode, nrrdCenterNode);
nrrdAxisInfoSet(nvec, nrrdAxisInfoSpacing, AIR_NAN, 1.0, 1.0, 2.0);
nrrdCenterUnknown means "I don't know the centering" or
"Centering is moot for this axis". Using AIR_NAN for any
double-type per-axis field (spacing, min, or max) similiarly
means "don't know", "don't care".
6. Key/Value Pairs
This is the simple but flexible way to store extra fields of
information in a Nrrd. These will be preserved exactly when
saved to, and read from, a NRRD file. For example, if you want to
save some rendering parameters with a floating point rendered image,
you might save pairs like ("camera", "4, 5, 0"), ("look-at", "0, 0,
0"), ("U range", "-1, 1"), and so forth. Both the key and the value
are just char* strings, but hopefully packing and unpacking
between strings and useful values is probably the least of your
problems. Here are the calls to add the key/value pairs listed above:
nrrdKeyValueAdd(nrend, "camera", "4, 5, 0");
nrrdKeyValueAdd(nrend, "look-at", "0, 0, 0");
nrrdKeyValueAdd(nrend, "U range", "-1, 1");
To retrieve values:
char *cam, *at;
...
cam = nrrdKeyValueGet(nrend, "camera");
at = nrrdKeyValueGet(nrend, "look-at");
printf("camera = %s, look-at = %s\n", cam, at);
free(cam); free(at);
cam = NULL; at = NULL;
You can also iterate over all key/value pairs:
int ki, nk;
char *key, *val;
...
nk = nrrdKeyValueSize(nrend);
for (ki=0; ki<nk; ki++) {
nrrdKeyValueIndex(nrend, &key, &val, ki);
printf("%s = %s\n", key, val);
free(key); free(val);
key = NULL; val = NULL;
}
Following the "all strings are strdup()ed" rule of thumb for strings in NRRD,
the return from nrrdKeyValueGet() and
nrrdKeyValueIndex should be free()d.
Individual key/value pairs are erased with nrrdKeyValueErase(),
all pairs are erased with nrrdKeyValueClear().
7. Space and orientation
Starting with version 4 of the NRRD format (with magic "NRRD0004"),
the Nrrd struct can describe how the array is oriented and
positioned relative to some surrounding space. See the relevant section of the NRRD file format spec to see what
space and orientation information can be represented. The values of
nrrd->space come from an enum (in nrrdEnums.h):
/*
******** nrrdSpace* enum
**
** Identifies the space in which which the origin and direction
** vectors have their coordinates measured. When a direction is named
** here (like "Left" or "Anterior"), that implies a basis vector that
** points in that direction, along which that coordinate becomes *larger*
** (this is the opposite of MetaIO, for example).
**
** All of these spaces have a well-defined expected dimension, as
** determined by nrrdSpaceDimension(), and setting a nrrd to be in
** such a space, by nrrdSpaceSet(), will automatically set nrrd->spaceDim.
**
** The first six spaces here are PATIENT-ORIENTED spaces, which are
** properly speaking aligned with the patient, and not the scanner
** itself. But nrrdSpaceScannerXYZ and nrrdSpaceScannerXYZTime are
** DEVICE-ORIENTED spaces, irrespective of the patient, used in a
** previous version of the DICOM standard. When the two spaces are
** lined up with normal patient orientation in the scanner,
** nrrdSpaceScannerXYZ is the same as nrrdSpaceLeftPosteriorSuperior.
** To quote Part 3 (Information Object Definitions) of the DICOM spec
** (page 275): "If a patient lies parallel to the ground, face-up on
** the table, with his feet-to-head direction same as the
** front-to-back direction of the imaging equipment, the direction of
** the axes of this patient based coordinate system and the equipment
** based coordinate system in previous versions of this Standard will
** coincide."
*/
enum {
nrrdSpaceUnknown,
nrrdSpaceRightAnteriorSuperior, /* 1: NIFTI-1 (right-handed) */
nrrdSpaceLeftAnteriorSuperior, /* 2: standard Analyze (left-handed) */
nrrdSpaceLeftPosteriorSuperior, /* 3: DICOM 3.0 (right-handed) */
nrrdSpaceRightAnteriorSuperiorTime, /* 4: */
nrrdSpaceLeftAnteriorSuperiorTime, /* 5: */
nrrdSpaceLeftPosteriorSuperiorTime, /* 6: */
nrrdSpaceScannerXYZ, /* 7: ACR/NEMA 2.0 (pre-DICOM 3.0) */
nrrdSpaceScannerXYZTime, /* 8: */
nrrdSpace3DRightHanded, /* 9: */
nrrdSpace3DLeftHanded, /* 10: */
nrrdSpace3DRightHandedTime, /* 11: */
nrrdSpace3DLeftHandedTime, /* 12: */
nrrdSpaceLast
};
There are some
API functions which facilitate setting and getting this information.
- int nrrdSpaceDimension(int space)
returns the dimension
associated with the given space (from the nrrdSpace enum), or 0 if
the space value is unrecognized.
- int nrrdSpaceSet(Nrrd *nrrd, int space)
set the space.
- int nrrdSpaceDimensionSet(Nrrd *nrrd, int spaceDim)
set the space dimension, for when the space used is not a recognized and
named space (from the nrrdSpace enum).
- int nrrdSpaceKnown(const Nrrd *nrrd)
non-zero return means that
some kind of space or orientiation info is known. If zero, means that the
only information known is (possibly) the per-axis min, max, spacing.
- void nrrdSpaceGet(const Nrrd *nrrd, int *space, int *spaceDim)
Sets *space to the space and *spaceDim to
the space dimension of the surrounding space. *space will be set to
nrrdSpaceUnknown if the space dimension is known, but no named spaced is
being used.
- void nrrdSpaceOriginGet(const Nrrd *nrrd, double vector[NRRD_SPACE_DIM_MAX])
Gets the spaceOrigin from the nrrd (vector[0] through
vector[spaceDim-1] are set).
- int nrrdOriginCalculate3D(const Nrrd *nrrd, int ax0, int ax1, int ax2, int defaultCenter, double origin[3])
For those cases where you want
something like an "origin" even though you don't have space information,
pick the three axes (ax0, ax1, ax2)
that you think represent the spatial
axes of the volume, and pick a default centering
(nrrdCenterNode or nrrdCenterCell) and this will try
to figure out an origin from the per-axis min, max, and spacing. Return values are from the nrrdOriginStatus enum:
/*
******** nrrdOriginStatus* enum
**
** how origin information was or was not computed by nrrdOriginCalculate
*/
enum {
nrrdOriginStatusUnknown, /* 0: nobody knows, or invalid parms */
nrrdOriginStatusDirection, /* 1: chosen axes have spaceDirections */
nrrdOriginStatusNoMin, /* 2: axis->min doesn't exist */
nrrdOriginStatusNoMaxOrSpacing, /* 3: axis->max or ->spacing doesn't exist */
nrrdOriginStatusOkay, /* 4: all is well */
nrrdOriginStatusLast
};
- int nrrdSpacingCalculate(const Nrrd *nrrd, int ax, double *spacing, double vector[NRRD_SPACE_DIM_MAX]): determine spacing information for
the given axis. This is useful for nrrd with and without space information,
since the information can come either from the per-axis spacing or the
per-axis space direction. Return value indicates status of information:
/*
******** nrrdSpacingStatus* enum
**
** a way of describing how spacing information is known or not known for a
** given axis, as determined by nrrdSpacingCalculate
*/
enum {
nrrdSpacingStatusUnknown, /* 0: nobody knows,
or invalid axis choice */
nrrdSpacingStatusNone, /* 1: neither axis->spacing nor
axis->spaceDirection is set */
nrrdSpacingStatusScalarNoSpace, /* 2: axis->spacing set,
w/out space info */
nrrdSpacingStatusScalarWithSpace, /* 3: axis->spacing set, but there *is*
space info, which means the spacing
does *not* live in the surrounding
space */
nrrdSpacingStatusDirection, /* 4: axis->spaceDirection set, and
measured according to surrounding
space */
nrrdSpacingStatusLast
};
8. Overview of rest of API
The following aims to give a brief description of the important
nrrd library calls. See teem/src/nrrd/nrrd.h for
a complete list.
Programmers interested in seeing example code that uses the
Nrrd API are encouraged to look at the Unrrdu
commands that call into Nrrd functions. For example,
teem/src/unrrdu/slice.c demonstrates how to call
nrrdSlice().
Basic "methods"
These handle the basic operations for creating and destroying
nrrds and the data inside
nrrdNew
| -
| Create an emtpy nrrd (a container)
|
nrrdNix
| -
| Delete the nrrd struct, but not the data
|
nrrdEmpty
| -
| Free the data, keep the struct
|
nrrdNuke
| -
| Free both the data and the struct
|
nrrdCopy
| -
| Replicate a struct and the data inside
|
nrrdAlloc
| -
| allocate the data segment of a nrrd
|
nrrdMaybeAlloc
| -
| allocate the data segment if the requested size
is different than the current one
|
Manipulation of per-axis meta-information
nrrdAxisInfoCopy
| -
| Copy per-axis info from one nrrd to another
|
nrrdAxisInfoSet{_nva}
| -
| Set one field of axis, for all axes
|
nrrdAxisInfoGet{_nva}
| -
| Get one field of axis, for all axes
|
nrrdAxisInfoPos
| -
| Map from axis "index space" to "world space"
|
nrrdAxisInfoIdx
| -
| Map from axis "world space" to "index space"
|
nrrdAxisInfoSpacingSet
| -
| Set spacing, based on current min/max
|
nrrdAxisInfoMinMaxSet
| -
| Set min and max, based on current spacing
|
Utility functions
nrrdSanity() should be called on a new Teem build
to make sure things are all okay. There is in fact a Teem
stand-alone binary called nrrdSanity which is really
just a wrapper around nrrdSanity().
nrrdSanity
| -
| checks that all compile-time assumptions are true
|
nrrdContentSet
| -
| printf-style setting of the nrrd->content field
|
nrrdCheck
| -
| make sure a given nrrd's fields are valid and consistent
|
nrrdElementNumber
| -
| number of samples in a nrrd
|
nrrdElementSize
| -
| number of bytes in each sample
|
Comments in nrrd
Comments used to be the only way to extend the nrrd header with
other kinds of meta-information, but this is strongly discouraged
now that (as of version 1.6.0) we have key/value pairs.
nrrdCommentAdd
| -
| add a comment to a nrrd
|
nrrdCommentClear
| -
| clear all comments in a nrrd
|
nrrdCommentCopy
| -
| copy all comments from one nrrd to another
|
Key/value pairs
Described above.
nrrdKeyValueAdd
| -
| add a key/value pair to a nrrd
|
nrrdKeyValueGet
| -
| look up a value for a given key
|
nrrdKeyValueErase
| -
| erase a key/value pair, given the key
|
nrrdKeyValueClear
| -
| clear all key/value pairs in a nrrd
|
nrrdKeyValueSize
| -
| number of key/value pairs in a nrrd
|
nrrdKeyValueIndex
| -
| lookup a key/value pair, indexed by integer
|
Endianness (byte ordering)
Nrrd is designed so that you don't have to worry about
endianness. Whenever the byte ordering of an array matters
(when the element size is larger than one byte), and the array
is written out with an encoding which exposes endianness (anything
besides ASCII), then the endianness is explicitly recorded,
in the header. On read, the endianness if flipped automatically
(by nrrdRead()) if it is deemed necessary. If, for some
twisted reason, you want to change endianness again, this function
allows you to do so.
nrrdSwapEndian
| -
| reverses the byte-ordering of the data in memory
|
Getting/Setting values (crude!)
These functions provide a primitive means of setting/getting values
in an array of type known only at run-time. Here's an example of
their use, to add incr to all the values in a nrrd:
void
demoIncr(Nrrd *nrrd, double incr) {
double (*lup)(const void *, size_t I);
double (*ins)(const void *, size_t I, double v);
double val;
size_t I, N;
lup = nrrdDLookup[nrrd->type];
ins = nrrdDInsert[nrrd->type];
N = nrrdElementNumber(nrrd);
for (I=0; I<N; I++) {
val = lup(nrrd->data, I);
val += incr;
ins(nrrd->data, I, val);
}
return;
}
nrrd{I,F,D}Load[]
| -
| dereference a pointer (of indexed type), and cast to
int, float, or double
|
nrrd{I,F,D}Store[]
| -
| save a given int, float, or double
to the given pointer (which is of indexed type)
|
nrrd{I,F,D}Lookup[]
| -
| lookup a value in a given array (of indexed type), and
cast to int, float, or double
|
nrrd{I,F,D}Insert[]
| -
| insert the given int, float, or
double of a particular element of a given array
(which is of indexed type)
|
Input from, Output to files
Described above.
nrrdLoad
| -
| load a nrrd from a given char* filename
|
nrrdRead
| -
| read a nrrd from a given FILE* file pointer
|
nrrdSave
| -
| save a nrrd to a given char* filename
|
nrrdWrite
| -
| write a nrrd to a given FILE* file pointer
|
Representing ranges of values
The NrrdRange is a very simple struct who's only job
is to represent (you guessed!) a range of scalar values, either as
a description of what was seen in a given nrrd, or as input to
a function like nrrdQuantize which uses a value range as
part of its operation. The introduction of the NrrdRange was
the chosen means of making nrrd const-correct.
nrrdRangeNew
| -
| create a new NrrdRange based on the given
min and max
|
nrrdRangeNix
| -
| delete a NrrdRange
|
nrrdRangeCopy
| -
| allocate and produce a replicate of the given nrrd
|
nrrdRangeReset
| -
| reset a NrrdRange to base state
|
nrrdRangeSet
| -
| Go through the given nrrd and find its min and max.
|
nrrdRangeSafeSet
| -
| Like nrrdRangeSet, but only over-write non-NaN values
in the given NrrdRange
|
nrrdRangeNewSet
| -
| combines nrrdRangeNew() and nrrdRangeSet
|
Simple transforms and warpings of values
nrrdConvert
| -
| make a new nrrd which is the result of doing a
per-value cast on the old values
|
nrrdQuantize
| -
| reduce the values in a nrrd down to unsigned 8, 16, or 32 bits.a
|
nrrdUnquantize
| -
| try to recover original floating-point values from
a quantized nrrd based on the oldMin and oldMax fields.
|
nrrdHistoEq
| -
| perform histogram equalization on the values in a nrrd
|
Functional mapping of values
In all of the following, the values produced at the output of
the mapping can either be scalar (output is same dimension as
in input) or multi-scalar, e.g. color (output is one greater
dimension than input).
nrrdApply1DLut
| -
| send values in a nrrd through a univariate lookup-table
|
nrrdApply2DLut
| -
| send values in a nrrd through a bivariate lookup-table
|
nrrdApply1DRegMap
| -
| send values in a nrrd through a map of linear ramps
with uniformly spaced control points
|
nrrdApply1DIrregMap
| -
| send values in a nrrd through a map of linear ramps
with irregularly located control points
|
nrrdApply1DSubstitution
| -
| send values through a substitution table
|
Subset/Superset operations
nrrdSlice
| -
| slice an array along some axis at some position
(decreases dimension by one)
|
nrrdSplice
| -
| put a given slice into a gived nrrd (logical opposite of
nrrdSlice)
|
nrrdCrop
| -
| extract a sub-array of the same dimension
|
nrrdPad
| -
| pad an array out to be bigger (same dimension)
|
nrrdInset
| -
| drop some sub-volume into a larger one (logical
opposite of nrrdCrop)
|
Axis-based value re-ordering
Unlike the functions about axis meta-information (starting with
nrrdAxisInfo...), the functions relating to transforms
on the scan-line ordering of the samples themselves all start
with nrrdAxes.... This may not have been the wisest choice,
but its here now.
nrrdAxesPermute
| -
| re-order axes in the scan-line ordering of samples
|
nrrdAxesSwap
| -
| re-order two specific axes
(special case of nrrdAxesPermute)
|
nrrdShuffle
| -
| re-order slices along some axis
|
nrrdFlip
| -
| changes ordering of slices along some axis
(special case of nrrdShuffle)
|
nrrdJoin
| -
| join multiple input nrrds along some existing
axis or a new one
|
nrrdReshape
| -
| (like Matlab's command) impose some new
raster dimensions on an existing array
|
nrrdAxesInsert
| -
| insert a new stub (size=1) axis amongst some existing ones
|
nrrdAxesDelete
| -
| remove a stub axis from a nrrd
|
nrrdAxesSplit
| -
| split one axis into two adjacent axes (a fast and a slow)
|
nrrdAxesMerge
| -
| Join two adjacent axes into a single axis
|
nrrdTile2D
| -
| Tile the slices along one axis into two other axes
(makes a mosaic image)
|
nrrdUntile2D
| -
| Undoes the action of nrrdTile2D
|
"Measuring" an array
This is one of the more powerful function calls in nrrd: it
allows you to do axis-aligned maximum intensity projections, find the
L2-norm of each of a volume of vectors, find the median/mean/mode
value of each scanline of a multi-dimensional scatterplot.
nrrdProject
| -
| reduce all samples along scanlines (along the given axis)
to a single scalar, according to the given measure.
|
All things histogram-related
Because nrrd grew out of ideas on array representation that I
had when writing my Master's
thesis, histograms have always been a key part.
nrrdHisto
| -
| generate a histogram of the values in a nrrd
|
nrrdHistoDraw
| -
| draws a uni-variate histogram in a helpful way.
|
nrrdHistoAxis
| -
| replace every scanline with its histogram
(takes a while to wrap head around this)
|
nrrdHistoJoint
| -
| create multi-dimensional joint histogram of a
list of nrrds.
|
Arithmetic operations on one or more nrrds
nrrdArithGamma
| -
| "gamma correct" all values in a nrrd
|
nrrdArithUnaryOp
| -
| Send all values in a nrrd through some unary
function, e.g. sin(), log(), abs(), etc.
|
nrrdArithBinaryOp
| -
| do some binary function (takes two arguments) on
two nrrds, or on a nrrd and a constant
|
nrrdArithTernaryOp
| -
| so some ternary function (takes three arguments) on
nrrds and constants (any possibility barring three constants)
|
Filtering and Resampling
The median filtering is cheap and slow. The spatial resampling, on
the other hand, is a ridiculously complex and multi-purpose function,
but with an aim towards fast computation. It allows cropping and
padding as part of resampling, using different kernels on different
axes, resampling some axes while leaving others untouched, drawing
from the very large vocabulary of kernels that nrrd is
compiled with. See nrrd.h and resampleNrrd.c
for details, and look at teem/src/unrrdu/resample.c for
an example of a simplified interface to the resampler.
nrrdCheapMedian
| -
| perform simple histogram-based median filtering
in 1-D, 2-D or 3-D
|
nrrdSpatialResample
| -
| perform filtered up-sampling or down-sampling
with arbitrary (seperable) kernels
|
nrrdResampleExecute
| -
| re-implementation of nrrdSpatialResample
to allow fast resampling of multiple images
|
Connected components
These are pretty slow and stupid for the time being. They will
be optimized later, but the API should stay put.
nrrdCCFind
| -
| find connected components in 1-D, 2-D or 3-D
|
nrrdCCMerge
| -
| merge some components into others, based on
a variety of criteria
|
nrrdCCSettle
| -
| assign set of lowest-valued possible component IDs
|