BSplineFreeFormTransformation4D.h
1 /*
2  * Medical Image Registration ToolKit (MIRTK)
3  *
4  * Copyright 2008-2015 Imperial College London
5  * Copyright 2008-2013 Daniel Rueckert, Julia Schnabel
6  * Copyright 2013-2015 Andreas Schuh
7  *
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */
20 
21 #ifndef MIRTK_BSplineFreeFormTransformation4D_H
22 #define MIRTK_BSplineFreeFormTransformation4D_H
23 
24 #include "mirtk/FreeFormTransformation4D.h"
25 
26 #include "mirtk/FastCubicBSplineInterpolateImageFunction4D.h"
27 #include "mirtk/TransformationJacobian.h"
28 
29 
30 namespace mirtk {
31 
32 
33 /**
34  * Class for free-form transformations based on tensor product B-splines.
35  *
36  * This class implements 4D free-form transformation using B-splines.
37  *
38  * For more details about the implementation see Lee, Wolberg and Shin, IEEE
39  * Transactions on Visualization and Computer Graphics, Vol. 3, No. 3, 1997.
40  */
42 {
43  mirtkTransformationMacro(BSplineFreeFormTransformation4D);
44 
45  // ---------------------------------------------------------------------------
46  // Types
47 
48 public:
49 
52 
53  // -------------------------------------------------------------------------
54  // Data members
55 
56 protected:
57 
58  /// Interpolates control point values at arbitrary lattice locations
59  Interpolator _FFD;
60 
61  // ---------------------------------------------------------------------------
62  // Construction/Destruction
63 
64 public:
65 
66  /// Default constructor
68 
69  /// Construct free-form transformation for given image domain and lattice spacing
70  BSplineFreeFormTransformation4D(double, double, double, double,
71  double, double, double, double,
72  double, double, double, double,
73  double *, double *, double *);
74 
75  /// Construct free-form transformation for given image domain and lattice spacing
77  double = -1, double = -1, double = -1, double = -1);
78 
79  /// Construct free-form transformation for given target image and lattice spacing
81  double, double, double, double);
82 
83  /// Copy Constructor
85 
86  /// Destructor
88 
89  // ---------------------------------------------------------------------------
90  // Approximation/Interpolation
91 
92  /// Approximate displacements: This function takes a set of points and a set
93  /// of displacements and finds !new! parameters such that the resulting
94  /// transformation approximates the displacements as good as possible.
95  virtual void ApproximateDOFs(const double *, const double *, const double *, const double *,
96  const double *, const double *, const double *, int);
97 
98  /// Finds gradient of approximation error: This function takes a set of points
99  /// and a set of errors. It finds a gradient w.r.t. the transformation parameters
100  /// which minimizes the L2 norm of the approximation error and adds it to the
101  /// input gradient with the given weight.
102  virtual void ApproximateDOFsGradient(const double *, const double *, const double *, const double *,
103  const double *, const double *, const double *, int,
104  double *, double = 1.0) const;
105 
106  /// Interpolates displacements: This function takes a set of displacements defined
107  /// at the control points and finds a FFD which interpolates these displacements.
108  virtual void Interpolate(const double *, const double *, const double *);
109 
110  // ---------------------------------------------------------------------------
111  // Lattice
112 
114 
115  /// Number of control points in x after subdivision
116  virtual int GetXAfterSubdivision() const;
117 
118  /// Number of control points in y after subdivision
119  virtual int GetYAfterSubdivision() const;
120 
121  /// Number of control points in z after subdivision
122  virtual int GetZAfterSubdivision() const;
123 
124  /// Number of control points in t after subdivision
125  virtual int GetTAfterSubdivision() const;
126 
127  /// Returns the control point spacing in x after the subdivision
128  virtual double GetXSpacingAfterSubdivision() const;
129 
130  /// Returns the control point spacing in y after the subdivision
131  virtual double GetYSpacingAfterSubdivision() const;
132 
133  /// Returns the control point spacing in z after the subdivision
134  virtual double GetZSpacingAfterSubdivision() const;
135 
136  /// Returns the control point spacing in t after the subdivision
137  virtual double GetTSpacingAfterSubdivision() const;
138 
139  /// Subdivide FFD lattice in the specified dimensions
140  virtual void Subdivide(bool = true, bool = true, bool = true, bool = true);
141 
142  /// Gets the temporal bounding box for a control point. The last parameter
143  /// specifies what fraction of the bounding box to return. The default
144  /// is 1 which equals 100% of the bounding box.
145  virtual void BoundingBox(int, double &, double &, double = 1) const;
146 
147  /// Gets the spatial bounding box for a control point. The last parameter
148  /// specifies what fraction of the bounding box to return. The default
149  /// is 1 which equals 100% of the bounding box.
150  virtual void BoundingBox(int, double &, double &, double &,
151  double &, double &, double &, double = 1) const;
152 
153  /// Gets the spatio-temporal bounding box for a control point. The last parameter
154  /// specifies what fraction of the bounding box to return. The default
155  /// is 1 which equals 100% of the bounding box.
156  virtual void BoundingBox(int, double &, double &, double &, double &,
157  double &, double &, double &, double &, double = 1) const;
158 
159  // ---------------------------------------------------------------------------
160  // Evaluation
161 
162  /// Calculates the FFD (for a point in FFD coordinates)
163  void Evaluate(double &, double &, double &, double) const;
164 
165  /// Evaluates the FFD at a point in lattice coordinates inside the FFD domain
166  void EvaluateInside(double &, double &, double &, double) const;
167 
168  /// Calculates the spatial Jacobian of the FFD at a point in lattice coordinates
169  void EvaluateJacobian(Matrix &, int, int, int, int) const;
170 
171  /// Calculates the spatial Jacobian of the FFD at a point in lattice coordinates
172  void EvaluateJacobian(Matrix &, double, double, double, double) const;
173 
174  /// Calculates the spatial Jacobian of the FFD at a point in lattice coordinates
175  /// and converts the resulting Jacobian to derivatives w.r.t world coordinates
176  void EvaluateJacobianWorld(Matrix &, double, double, double, double) const;
177 
178  /// Calculates the Jacobian of the FFD at a point in lattice coordinates
179  /// w.r.t the control point with lattice coordinates (i, j, k, l)
180  void EvaluateJacobianDOFs(double [3], int, int, int, int,
181  double, double, double, double) const;
182 
183  /// Calculates the Jacobian of the FFD at a point in lattice coordinates
184  /// w.r.t the parameters. The resulting Jacobian matrix contains one column
185  /// for each parameter, but only non-zero columns, i.e., of control points
186  /// for whom the point is in the local support region, are actually stored.
187  void EvaluateJacobianDOFs(TransformationJacobian &jac, double, double, double, double) const;
188 
189  /// Calculates the Hessian of the FFD at a point in lattice coordinates
190  void EvaluateHessian(Matrix [3], int, int, int, int) const;
191 
192  /// Calculates the Hessian of the FFD at a point in lattice coordinates
193  void EvaluateHessian(Matrix [3], double, double, double, double) const;
194 
195  /// Calculates the Hessian of the FFD at a point in lattice coordinates
196  /// and converts the resulting Hessian to derivatives w.r.t world coordinates
197  void EvaluateHessianWorld(Matrix [3], double, double, double, double) const;
198 
199  /// Calculates the Laplacian of the FFD at a point in lattice coordinates
200  void EvaluateLaplacian(double [3], int, int, int, int) const;
201 
202  /// Calculates the Laplacian of the FFD at a point in lattice coordinates
203  void EvaluateLaplacian(double [3], int, int, int, double) const;
204 
205  /// Calculates the Laplacian of the FFD at a point in lattice coordinates
206  void EvaluateLaplacian(double [3], double, double, double, double) const;
207 
208  /// Calculates the Laplacian of the FFD at a point in lattice coordinates
209  void EvaluateLaplacian(double &, double &, double &, double) const;
210 
211  // ---------------------------------------------------------------------------
212  // Point transformation
213 
214  /// Transforms a single point using the local transformation component only
215  virtual void LocalTransform(double &, double &, double &, double, double = NaN) const;
216 
217  // ---------------------------------------------------------------------------
218  // Derivatives
219 
223 
224  /// Calculates the Jacobian of the local transformation w.r.t world coordinates
225  virtual void LocalJacobian(Matrix &, double, double, double, double, double = NaN) const;
226 
227  /// Calculates the Hessian for each component of the local transformation w.r.t world coordinates
228  virtual void LocalHessian(Matrix [3], double, double, double, double, double = NaN) const;
229 
230  /// Calculates the Jacobian of the transformation w.r.t the transformation parameters
231  virtual void JacobianDOFs(Matrix &, int, int, int, int, double, double, double, double, double = NaN) const;
232 
233  /// Calculates the Jacobian of the transformation w.r.t the transformation parameters
234  virtual void JacobianDOFs(double [3], int, int, int, int, double, double, double, double, double = NaN) const;
235 
236  /// Calculates the Jacobian of the transformation w.r.t the transformation parameters
237  virtual void JacobianDOFs(TransformationJacobian &, double, double, double, double, double = NaN) const;
238 
239  /// Applies the chain rule to convert spatial non-parametric gradient
240  /// to a gradient w.r.t the parameters of this transformation
241  virtual void ParametricGradient(const GenericImage<double> *, double *,
242  const WorldCoordsImage *,
243  const WorldCoordsImage *,
244  double = NaN, double = 1.0) const;
245 
246  // ---------------------------------------------------------------------------
247  // Properties
249 
250  /// Size of support region of the used kernel
251  virtual int KernelSize() const;
252 
253  /// Calculates the bending of the transformation
254  virtual double BendingEnergy(double, double, double, double = 0, double = NaN, bool = true) const;
255 
256  /// Approximates the bending energy on the control point lattice
257  virtual double BendingEnergy(bool = false, bool = true) const;
258 
259  /// Approximates the bending energy on the specified discrete domain
260  virtual double BendingEnergy(const ImageAttributes &, double = NaN, bool = true) const;
261 
262  /// Approximates the gradient of the bending energy on the control point
263  /// lattice w.r.t the transformation parameters and adds it with the given weight
264  virtual void BendingEnergyGradient(double *, double = 1.0, bool = false, bool = true, bool = true) const;
265 
266  // ---------------------------------------------------------------------------
267  // I/O
268 
269  // Do not hide methods of base class
271 
272  /// Prints the parameters of the transformation
273  virtual void Print(ostream &, Indent = 0) const;
274 
275  /// Whether this transformation can read a file of specified type (i.e. format)
276  virtual bool CanRead(TransformationType) const;
277 
278 };
279 
280 ////////////////////////////////////////////////////////////////////////////////
281 // Inline definitions
282 ////////////////////////////////////////////////////////////////////////////////
283 
284 // =============================================================================
285 // Evaluation
286 // =============================================================================
287 
288 // -----------------------------------------------------------------------------
290 ::Evaluate(double &x, double &y, double &z, double t) const
291 {
292  Vector d = _FFD(x, y, z, t);
293  x = d._x, y = d._y, z = d._z;
294 }
295 
296 // -----------------------------------------------------------------------------
298 ::EvaluateInside(double &x, double &y, double &z, double t) const
299 {
300  Vector d = _FFD.GetInside(x, y, z, t);
301  x = d._x, y = d._y, z = d._z;
302 }
303 
304 // -----------------------------------------------------------------------------
306 ::EvaluateJacobianWorld(Matrix &jac, double x, double y, double z, double t) const
307 {
308  this->EvaluateJacobian(jac, x, y, z, t);
309  JacobianToWorld(jac);
310 }
311 
312 // -----------------------------------------------------------------------------
314 ::EvaluateJacobianDOFs(double jac[3], int i, int j, int k, int l,
315  double x, double y, double z, double t) const
316 {
317  jac[0] = jac[1] = jac[2] = Kernel::Weight(x - i) *
318  Kernel::Weight(y - j) *
319  Kernel::Weight(z - k) *
320  Kernel::Weight(t - l);
321 }
322 
323 // -----------------------------------------------------------------------------
325 ::EvaluateJacobianDOFs(TransformationJacobian &jac, double x, double y, double z, double t) const
326 {
327  int i = static_cast<int>(floor(x));
328  int j = static_cast<int>(floor(y));
329  int k = static_cast<int>(floor(z));
330  int l = static_cast<int>(floor(t));
331 
332  const int A = Kernel::VariableToIndex(x - i);
333  const int B = Kernel::VariableToIndex(y - j);
334  const int C = Kernel::VariableToIndex(z - k);
335  const int D = Kernel::VariableToIndex(t - l);
336 
337  --i, --j, --k, --l;
338 
339  double basis[4];
340  int ci, cj, ck, cl, xdof, ydof, zdof;
341 
342  for (int d = 0; d <= 3; ++d) {
343  cl = l + d;
344  if (0 <= cl && cl < _t) {
345  basis[3] = Kernel::LookupTable[D][d];
346  for (int c = 0; c <= 3; ++c) {
347  ck = k + c;
348  if (0 <= ck && ck < _z) {
349  basis[2] = Kernel::LookupTable[C][c] * basis[3];
350  for (int b = 0; b <= 3; ++b) {
351  cj = j + b;
352  if (0 <= cj && cj < _y) {
353  basis[1] = Kernel::LookupTable[B][b] * basis[2];
354  for (int a = 0; a <= 3; ++a) {
355  ci = i + a;
356  if (0 <= ci && ci < _x) {
357  basis[0] = Kernel::LookupTable[A][a] * basis[1];
358  IndexToDOFs(LatticeToIndex(ci, cj, ck, cl), xdof, ydof, zdof);
359  jac(xdof)._x = basis[0];
360  jac(ydof)._y = basis[0];
361  jac(zdof)._z = basis[0];
362  }
363  }
364  }
365  }
366  }
367  }
368  }
369  }
370 }
371 
372 // -----------------------------------------------------------------------------
374 ::EvaluateHessianWorld(Matrix hessian[3], double x, double y, double z, double t) const
375 {
376  this->EvaluateHessian(hessian, x, y, z, t);
377  HessianToWorld(hessian);
378 }
379 
380 // =============================================================================
381 // Point transformation
382 // =============================================================================
383 
384 // -----------------------------------------------------------------------------
386 ::LocalTransform(double &x, double &y, double &z, double t, double) const
387 {
388  // Convert to lattice coordinates
389  double dx = x, dy = y, dz = z;
390  this->WorldToLattice(dx, dy, dz);
391  t = this->TimeToLattice(t);
392  // Evaluate displacement
393  this->Evaluate(dx, dy, dz, t);
394  // Transform point
395  x += dx, y += dy, z += dz;
396 }
397 
398 // =============================================================================
399 // Derivatives
400 // =============================================================================
401 
402 // -----------------------------------------------------------------------------
404 ::LocalJacobian(Matrix &jac, double x, double y, double z, double t, double) const
405 {
406  // Convert to lattice coordinates
407  this->WorldToLattice(x, y, z);
408  t = this->TimeToLattice(t);
409  // Compute 1st order derivatives
410  this->EvaluateJacobianWorld(jac, x, y, z, t);
411  // Add derivatives of "x" term in T(x) = x + FFD(x)
412  jac(0, 0) += 1.0;
413  jac(1, 1) += 1.0;
414  jac(2, 2) += 1.0;
415 }
416 
417 // -----------------------------------------------------------------------------
419 ::LocalHessian(Matrix hessian[3], double x, double y, double z, double t, double) const
420 {
421  // Convert to lattice coordinates
422  this->WorldToLattice(x, y, z);
423  t = this->TimeToLattice(t);
424  // Compute 2nd order derivatives
425  this->EvaluateHessianWorld(hessian, x, y, z, t);
426 }
427 
428 // -----------------------------------------------------------------------------
430 ::JacobianDOFs(double jac[3], int i, int j, int k, int l,
431  double x, double y, double z, double t, double) const
432 {
433  // Convert to lattice coordinates
434  this->WorldToLattice(x, y, z);
435  t = this->TimeToLattice(t);
436  // Calculate derivatives w.r.t. control point coordinates
437  this->EvaluateJacobianDOFs(jac, i, j, k, l, x, y, z, t);
438 }
439 
440 // -----------------------------------------------------------------------------
442 ::JacobianDOFs(Matrix &jac, int i, int j, int k, int l,
443  double x, double y, double z, double t, double t0) const
444 {
445  double tmp[3];
446  this->JacobianDOFs(tmp, i, j, k, l, x, y, z, t, t0);
447  jac.Initialize(3, 3);
448  jac(0, 0) = tmp[0];
449  jac(1, 1) = tmp[1];
450  jac(2, 2) = tmp[2];
451 }
452 
453 // -----------------------------------------------------------------------------
455 ::JacobianDOFs(TransformationJacobian &jac, double x, double y, double z, double t, double) const
456 {
457  // Convert to lattice coordinates
458  this->WorldToLattice(x, y, z);
459  t = this->TimeToLattice(t);
460  // Calculate derivatives w.r.t. control point coordinates
461  this->EvaluateJacobianDOFs(jac, x, y, z, t);
462 }
463 
464 
465 } // namespace mirtk
466 
467 #endif // MIRTK_BSplineFreeFormTransformation4D_H
void EvaluateJacobianDOFs(double [3], int, int, int, int, double, double, double, double) const
static int VariableToIndex(TReal)
Returns the lookup table index for a given value in [0,1].
Definition: BSpline.h:625
virtual void LocalHessian(Matrix [3], double, double, double, double, double=NaN) const
Calculates the Hessian for each component of the local transformation w.r.t world coordinates...
static MIRTKCU_API TReal Weight(TReal)
Returns the value of the B-spline function.
Definition: BSpline.h:291
virtual void Subdivide(bool=true, bool=true, bool=true, bool=true)
Subdivide FFD lattice in the specified dimensions.
void EvaluateHessianWorld(Matrix [3], double, double, double, double) const
virtual void ParametricGradient(const GenericImage< double > *, double *, const WorldCoordsImage *, const WorldCoordsImage *, double=NaN, double=1) const
virtual void Print(ostream &, Indent=0) const
Prints the parameters of the transformation.
virtual void Interpolate(const double *, const double *, const double *)
virtual void BoundingBox(int, double &, double &, double=1) const
virtual double TimeToLattice(double) const
Transforms time (in ms) to temporal lattice coordinate.
void IndexToDOFs(int, int &, int &) const
Get indices of transformation parameters (DoFs)
void JacobianToWorld(double &, double &) const
void Evaluate(double &, double &, double &, double) const
Calculates the FFD (for a point in FFD coordinates)
void EvaluateInside(double &, double &, double &, double) const
Evaluates the FFD at a point in lattice coordinates inside the FFD domain.
void EvaluateJacobian(Matrix &, int, int, int, int) const
Calculates the spatial Jacobian of the FFD at a point in lattice coordinates.
virtual double GetYSpacingAfterSubdivision() const
Returns the control point spacing in y after the subdivision.
virtual double BendingEnergy(double, double, double, double=0, double=NaN, bool=true) const
Calculates the bending of the transformation.
const int & _x
Read-only reference to _x attribute of _CPImage.
virtual int GetYAfterSubdivision() const
Number of control points in y after subdivision.
virtual void Print(ostream &, Indent=0) const
Prints the parameters of the transformation.
Interpolator _FFD
Interpolates control point values at arbitrary lattice locations.
virtual int KernelSize() const
Size of support region of the used kernel.
virtual double BendingEnergy(double, double, double, double=0, double=NaN, bool=true) const
Calculates the bending of the transformation.
static MIRTK_Numerics_EXPORT TReal LookupTable[LookupTableSize][4]
Lookup table of B-spline basis function values.
Definition: BSpline.h:127
void Initialize(int, int=-1, double *=NULL)
Initialize matrix with number of rows and columns.
virtual bool CanRead(TransformationType) const
Whether this transformation can read a file of specified type (i.e. format)
Definition: IOConfig.h:41
virtual int GetZAfterSubdivision() const
Number of control points in z after subdivision.
virtual int GetTAfterSubdivision() const
Number of control points in t after subdivision.
virtual double GetTSpacingAfterSubdivision() const
Returns the control point spacing in t after the subdivision.
void EvaluateLaplacian(double [3], int, int, int, int) const
Calculates the Laplacian of the FFD at a point in lattice coordinates.
virtual void LocalTransform(double &, double &, double &, double, double=NaN) const
Transforms a single point using the local transformation component only.
virtual double GetZSpacingAfterSubdivision() const
Returns the control point spacing in z after the subdivision.
virtual void ApproximateDOFs(const double *, const double *, const double *, const double *, const double *, const double *, const double *, int)
virtual void JacobianDOFs(Matrix &, int, int, int, int, double, double, double, double, double=NaN) const
Calculates the Jacobian of the transformation w.r.t the transformation parameters.
int LatticeToIndex(int, int, int=0, int=0) const
Get control point index from lattice coordinates.
void BoundingBox(double &, double &) const
Gets the temporal bounding box of the free-form deformation (in ms)
const int & _t
Read-only reference to _t attribute of _CPImage.
virtual double GetXSpacingAfterSubdivision() const
Returns the control point spacing in x after the subdivision.
const int & _z
Read-only reference to _z attribute of _CPImage.
virtual ~BSplineFreeFormTransformation4D()
Destructor.
virtual void BendingEnergyGradient(double *, double=1.0, bool=false, bool=true, bool=true) const
void EvaluateJacobianWorld(Matrix &, double, double, double, double) const
virtual void JacobianDOFs(Matrix &, int, int, int, int, double, double, double, double, double=NaN) const
Calculates the Jacobian of the transformation w.r.t a control point.
T _z
The z component.
Definition: Vector3D.h:60
T _y
The y component.
Definition: Vector3D.h:59
virtual void ParametricGradient(const GenericImage< double > *, double *, const WorldCoordsImage *, const WorldCoordsImage *, double=NaN, double=1.0) const
virtual void LocalJacobian(Matrix &, double, double, double, double, double=NaN) const
Calculates the Jacobian of the local transformation w.r.t world coordinates.
T _x
The x component.
Definition: Vector3D.h:58
virtual void LocalJacobian(Matrix &, double, double, double, double=0, double=NaN) const
Calculates the Jacobian of the local transformation w.r.t world coordinates.
void HessianToWorld(double &, double &, double &) const
void EvaluateHessian(Matrix [3], int, int, int, int) const
Calculates the Hessian of the FFD at a point in lattice coordinates.
BSplineFreeFormTransformation4D()
Default constructor.
const int & _y
Read-only reference to _y attribute of _CPImage.
virtual void ApproximateDOFsGradient(const double *, const double *, const double *, const double *, const double *, const double *, const double *, int, double *, double=1.0) const
virtual int GetXAfterSubdivision() const
Number of control points in x after subdivision.
virtual void WorldToLattice(double &, double &) const
Transforms world coordinates (in mm) to lattice coordinates.