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

  Kernels

Besides representing arrays, the Nrrd library is responsible in Teem for representing continuous finite-support functions called kernels (or filters), which are the basis of axis-aligned resampling in Nrrd, and of the convolution-based measurements in Gage. Kernels are used to reconstruct continuous values from discretely sampled data, as well as measuring derivatives from sampled data.

The NrrdKernel struct

Everything Nrrd knows about a kernel is contained in the NrrdKernel struct:
/*
******** NrrdKernel struct
**
** these are essentially the methods of the various kernels implemented.
**
** Nrrd's use of this sort of kernel always assumes support symmetric
** around zero, but does not assume anything about even- or oddness
**
** It is a strong but very simplifying assumption that the parameter
** array ("parm") is always type double.  There is essentially no
** value in allowing flexibility between float and double, and much
** Teem code assumes that it will always be type double.
*/
typedef struct {
  /* terse string representation of kernel function, irrespective of
     the parameter vector */
  char name[AIR_STRLEN_SMALL];

  /* number of parameters needed (# elements in parm[] used) */
  unsigned int numParm;

  /* smallest x (x > 0) such that k(y) = 0 for all y > x, y < -x */
  double (*support)(const double *parm);

  /* integral of kernel from -support to +support */
  double (*integral)(const double *parm);

  /* evaluate once, single precision */
  float (*eval1_f)(float x, const double *parm);

  /* evaluate N times, single precision */
  void (*evalN_f)(float *f, const float *x, size_t N, const double *parm);

  /* evaluate once, double precision */
  double (*eval1_d)(double x, const double *parm);

  /* eval. N times, double precision */
  void (*evalN_d)(double *f, const double *x, size_t N, const double *parm);
} NrrdKernel;
Note that the NrrdKernel is basically a bag of callback functions, for learning things about the kernel (its support, or its integral), and for evaluating the kernel in various ways.

All the kernels use a double* to communicate parameters which may change the shape or size of the kernel. The first parameter (parm[0]) usually determines the kernel scaling, as measured in units of samples. For example, nrrdKernelBox represents the box function (1.0 inside [-0.5,0.5], and 0.0 outside) when parm[0] == 1.0, but you can change the size of the box function by setting a different value for parm[0]: if parm[0] == 4.0, the support of the box functions extends to [-2,2]. To be mathematically well-behaved, when kernels support this kind of scaling, the amplitude of the kernel is scaled proportionally so that the integral of the kernel is constant.

Unlike other structs in Nrrd (and Teem), NrrdKernels are usually not user-allocated (there is no nrrdKernelNew() or nrrdKernelNix()). Rather, the kernels in Nrrd are created at compile-time, and pointers to these are used to communicate kernel information to the convolution functions that need them.

There is one function which supplies a standard way of mapping from a string to a NrrdKernel and a parameter vector; this is used for command-line specification of kernels:

int nrrdKernelParse(const NrrdKernel **kernelP, double *parm,
                    const char *str);
Examples of strings and the kernels they parse to are given below.

The NrrdKernelSpec struct

Because nearly all kernels require some parameters to tune their behavior, sometimes it is helpful to pair together the NrrdKernel and its parameter vector:
/*
******** NrrdKernelSpec struct
** 
** for those times when it makes most sense to directly associate a
** NrrdKernel with its parameter vector (that is, a full kernel
** "spec"ification), basically: using hest.
*/
typedef struct {
  const NrrdKernel *kernel;
  double parm[NRRD_KERNEL_PARMS_NUM];
} NrrdKernelSpec;
These functions operate with NrrdKernelSpects:

Kernels supplied by Nrrd

The "string" information in the table below are the sort of thing that can be parsed by nrrdKernelParse and nrrdKernelSpecParse. When there are two versions of the string, it means that when the scale ("scl") parameter is not explicitly given, parm[0] set to 1.0. The "parameter" information gives the number of parameters required by the kernel (numParm in the NrrdKernel), and what they mean.

NrrdKernel* string parameters description
nrrdKernelZero "zero"
"zero:scl"
1:
parm[0]=scale
Zero everywhere
nrrdKernelBox "box"
"box:scl"
1:
parm[0]=scale
The box function, equals 1.0 inside [-0.5,0.5], and 0.0 outside. Used for implementing nearest-neighbor interpolation.
nrrdKernelTent "tent"
"tent:scl"
1:
parm[0]=scale
The tent function: f(-1)=0, f(0)=1, f(1)=0, with linear ramps in between, and zero elsewhere. Used for linear (and bilinear and trilinear) interpolation.
nrrdKernelForwDiff "forwdiff"
"forwdiff:scl"
1:
parm[0]=scale
Piecewise-linear ramps that implement forward-difference differentiation
nrrdKernelCentDiff "centdiff"
"centdiff:scl"
1:
parm[0]=scale
Piecewise-linear ramps that implement central-difference differentiation
nrrdKernelBCCubic "cubic:B,C"
"cubic:scl,B,C"
3:
parm[0]=scale
parm[1]=B
parm[2]=C
The two-parameter family of first-order continuous, 4-sample support, piece-wise cubic splines, described by "D. P. Mitchell and A. N. Netravali. Reconstruction filters in computer graphics. In Computer Graphics (SIGGRAPH '88 Proceedings), volume 22, pages 221--228, August 1988." Includes the Catmull-Rom spline at (B,C)=(0,0.5) and the uniform cubic B-spline at (B,C)=(1,0).
nrrdKernelBCCubicD "cubicd:B,C"
"cubicd:scl,B,C"
3:
parm[0]=scale
parm[1]=B
parm[2]=C
The first derivative of the BC cubics
nrrdKernelBCCubicDD "cubicdd:B,C"
"cubicdd:scl,B,C"
3:
parm[0]=scale
parm[1]=B
parm[2]=C
The second derivative of the BC cubics. Note: The uniform cubic B-spline at (B,C)=(1,0) is the only one which is second-order continuous.
nrrdKernelGaussian "gauss:sig,cut" 2:
parm[0]=sigma
parm[1]=cut-off
The Gaussian. Sigma is measured in units of samples, and the cut-off is measured in units of sigma. "gauss:3,5" describes a Gaussian with inflection points at -3 and +3, and which is zero outside [-15,15].
nrrdKernelGaussianD "gaussd:sig,cut" 2:
parm[0]=sigma
parm[1]=cut-off
First derivative of the Gaussian
nrrdKernelGaussianDD "gaussdd:sig,cut" 2:
parm[0]=sigma
parm[1]=cut-off
Second derivative of the Gaussian
nrrdKernelHann "hann:cut" 2:
parm[0]=scaling
parm[1]=cut-off
Hann-windowed sinc() function. The scaling parameter allows you to stretch the kernel so that its zero-crossings are either closer (parm[0]<1) or further apart (parm[0]>1) than the integers. parm[0]=1 is how most people would expect the kernel to be. The cut-off is measured in units of zero-crossings.
nrrdKernelHannD "hannd:cut" 2:
parm[0]=scaling
parm[1]=cut-off
First derivative of Hann-windowed sinc()
nrrdKernelHannDD "hanndd:cut" 2:
parm[0]=scaling
parm[1]=cut-off
Second derivative of Hann-windowed sinc()
nrrdKernelBlackman "hann:cut" 2:
parm[0]=scaling
parm[1]=cut-off
Blackman-windowed sinc() function. Scaling and cut-off parameters have the same semantics as with nrrdKernelHann (above).
nrrdKernelBlackmanD "hannd:cut" 2:
parm[0]=scaling
parm[1]=cut-off
First derivative of Blackman-windowed sinc()
nrrdKernelBlackmanDD "hanndd:cut" 2:
parm[0]=scaling
parm[1]=cut-off
Second derivative of Blackman-windowed sinc()

Plotting kernels

The formulae for the kernels are available for inspection in teem/src/nrrd/kernel.c, but it is sometimes nice to be able to quickly see what a given kernel looks like. Some unu can do this:
echo 0 0 1 0 0 \
 | unu axdelete -a -1 \
 | unu resample -s x100 -c node -k cubic:0,0.5 \
 | unu 2op + - 0.2 \
 | unu dhisto -h 200 -nolog \
 | unu pad -min -1 -1 -max M+1 M+1 -b pad -v 0 -o cmr.png
echo 0 0 1 0 0 \
 | unu axdelete -a -1 \
 | unu resample -s x100 -c node -k cubicd:0,0.5 \
 | unu 2op + - 1.4 \
 | unu dhisto -h 200 -nolog \
 | unu pad -min -1 -1 -max M+1 M+1 -b pad -v 0 -o cmrd.png

The "-k" argument to unu resample specifies the kernel to use to upsample the input vector, in this case "0 0 1 0 0", which ends up plotting from -2 to +2. Using "0 0 0 1 0 0 0" would plot from -3 to +3. The "unu 2op + - 0.2" adds a little bit to all the values so that nothing negative is given to unu dhisto, which was never really meant for this.

You can also save the sampled values to text files for plotting with other programs:

echo 0 0 1 0 0 \
 | unu axdelete -a -1 \
 | unu resample -s x100 -c node -k cubic:0,0.5 -o range.txt
echo -2 -1 0 1 2 \
 | unu axdelete -a -1 \
 | unu resample -s x100 -c node -k tent -o domain.txt