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.
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./* ******** 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;
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.
These functions operate with NrrdKernelSpects:/* ******** 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;
NrrdKernelSpec *nrrdKernelSpecNew(); NrrdKernelSpec *nrrdKernelSpecNix(NrrdKernelSpec *ksp); NrrdKernelSpec *nrrdKernelSpecCopy(NrrdKernelSpec *ksp);Create, destroy, and copy (which also creates).
void nrrdKernelSpecSet(NrrdKernelSpec *ksp, const NrrdKernel *k, const double kparm[NRRD_KERNEL_PARMS_NUM]);Set a NrrdKernelSpec from a NrrdKernel pointer and a parameter vector.
void nrrdKernelParmSet(const NrrdKernel **kP, double kparm[NRRD_KERNEL_PARMS_NUM], NrrdKernelSpec *ksp);Set a NrrdKernel pointer and a parameter vector from a NrrdKernelSpec.
int nrrdKernelSpecParse(NrrdKernelSpec *ksp, const char *str);Like nrrdKernelParse, but sets info in a given NrrdKernelSpec.
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() |
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