1 /*
2 * Medical Image Registration ToolKit (MIRTK)
3 *
4 * Copyright 2008-2017 Imperial College London
5 * Copyright 2008-2013 Daniel Rueckert, Julia Schnabel
6 * Copyright 2013-2017 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 #include "mirtk/FreeFormTransformation.h"
22
23 #include "mirtk/Math.h"
24 #include "mirtk/Memory.h"
25 #include "mirtk/Parallel.h"
26 #include "mirtk/Profiling.h"
27
28
29 namespace mirtk {
30
31
32 // =============================================================================
33 // Construction/Destruction
34 // =============================================================================
35
36 // Auxiliary macro used to initialize references to often needed
37 // control point image attributes in initialization list of constructor
38 #define _References() \
39 _attr (_CPImage.Attributes()), \
40 _x (_CPImage.Attributes()._x), \
41 _y (_CPImage.Attributes()._y), \
42 _z (_CPImage.Attributes()._z), \
43 _t (_CPImage.Attributes()._t), \
44 _dx (_CPImage.Attributes()._dx), \
45 _dy (_CPImage.Attributes()._dy), \
46 _dz (_CPImage.Attributes()._dz), \
47 _dt (_CPImage.Attributes()._dt), \
48 _matW2L(_CPImage.GetWorldToImageMatrix()), \
49 _matL2W(_CPImage.GetImageToWorldMatrix())
50
51 // -----------------------------------------------------------------------------
52 FreeFormTransformation
FreeFormTransformation(CPInterpolator & func,CPInterpolator * func2D)53 ::FreeFormTransformation(CPInterpolator &func, CPInterpolator *func2D)
54 :
55 _ExtrapolationMode(Extrapolation_Const),
56 _SpeedupFactor(1.0),
57 _CPValue (NULL),
58 _CPFunc (&func),
59 _CPFunc2D(func2D),
60 _CPStatus(NULL),
61 _References()
62 {
63 }
64
65 // -----------------------------------------------------------------------------
66 FreeFormTransformation
FreeFormTransformation(const FreeFormTransformation & ffd,CPInterpolator & func,CPInterpolator * func2D)67 ::FreeFormTransformation(const FreeFormTransformation &ffd,
68 CPInterpolator &func, CPInterpolator *func2D)
69 :
70 Transformation(ffd),
71 _ExtrapolationMode(ffd._ExtrapolationMode),
72 _SpeedupFactor(ffd._SpeedupFactor),
73 _CPValue (NULL),
74 _CPFunc (&func),
75 _CPFunc2D(func2D),
76 _CPStatus(NULL),
77 _References()
78 {
79 if (_NumberOfDOFs > 0) {
80 _CPImage.Initialize(ffd.Attributes(), reinterpret_cast<CPValue *>(_Param));
81 Allocate(_CPStatus, _x, _y, _z, _t, reinterpret_cast<CPStatus *>(_Status));
82 }
83 }
84
85 // -----------------------------------------------------------------------------
~FreeFormTransformation()86 FreeFormTransformation::~FreeFormTransformation()
87 {
88 Deallocate(_CPStatus, _Status);
89 Delete(_CPValue);
90 }
91
92
93 #undef _References // only used/needed for constructors
94
95 // -----------------------------------------------------------------------------
96 ImageAttributes
97 FreeFormTransformation
DefaultAttributes(const ImageAttributes & attr,double dx,double dy,double dz,double dt)98 ::DefaultAttributes(const ImageAttributes &attr,
99 double dx, double dy, double dz, double dt)
100 {
101 // Set spacing equal to voxel size if not specified (i.e., negative)
102 if (dx < .0) dx = attr._dx;
103 if (dy < .0) dy = attr._dy;
104 if (dz < .0) dz = attr._dz;
105 if (dt < .0) dt = attr._dt;
106
107 // Orthogonalize image grid if necessary
108 const ImageAttributes grid = OrthogonalFieldOfView(attr);
109
110 // Set lattice attributes to image attributes
111 ImageAttributes lattice = grid;
112
113 // Adjust lattice dimensions
114 lattice._x = ((dx > .0 && grid._x > 1) ? iround((grid._x - 1) * grid._dx / dx) + 1 : 1);
115 lattice._y = ((dy > .0 && grid._y > 1) ? iround((grid._y - 1) * grid._dy / dy) + 1 : 1);
116 lattice._z = ((dz > .0 && grid._z > 1) ? iround((grid._z - 1) * grid._dz / dz) + 1 : 1);
117 lattice._t = ((dt > .0 && grid._t > 1) ? iround((grid._t - 1) * grid._dt / dt) + 1 : 1);
118
119 // Update lattice spacing
120 lattice._dx = ((lattice._x > 1) ? dx : .0);
121 lattice._dy = ((lattice._y > 1) ? dy : .0);
122 lattice._dz = ((lattice._z > 1) ? dz : .0);
123 lattice._dt = ((lattice._t > 1) ? dt : .0);
124
125 return lattice;
126 }
127
128 // -----------------------------------------------------------------------------
129 ImageAttributes
130 FreeFormTransformation
DefaultAttributes(double x1,double y1,double z1,double t1,double x2,double y2,double z2,double t2,double dx,double dy,double dz,double dt,const double * xaxis,const double * yaxis,const double * zaxis)131 ::DefaultAttributes(double x1, double y1, double z1, double t1,
132 double x2, double y2, double z2, double t2,
133 double dx, double dy, double dz, double dt,
134 const double *xaxis,
135 const double *yaxis,
136 const double *zaxis)
137 {
138 ImageAttributes attr;
139
140 // Lattice origin
141 attr._xorigin = (x2 + x1) / 2.0;
142 attr._yorigin = (y2 + y1) / 2.0;
143 attr._zorigin = (z2 + z1) / 2.0;
144 attr._torigin = t1;
145
146 // Lattice orientation
147 memcpy(attr._xaxis, xaxis, 3 * sizeof(double));
148 memcpy(attr._yaxis, yaxis, 3 * sizeof(double));
149 memcpy(attr._zaxis, zaxis, 3 * sizeof(double));
150
151 // FOV in lattice orientation
152 double a, b, c;
153 a = x1 * xaxis[0] + y1 * xaxis[1] + z1 * xaxis[2];
154 b = x1 * yaxis[0] + y1 * yaxis[1] + z1 * yaxis[2];
155 c = x1 * zaxis[0] + y1 * zaxis[1] + z1 * zaxis[2];
156 x1 = a, y1 = b, z1 = c;
157 a = x2 * xaxis[0] + y2 * xaxis[1] + z2 * xaxis[2];
158 b = x2 * yaxis[0] + y2 * yaxis[1] + z2 * yaxis[2];
159 c = x2 * zaxis[0] + y2 * zaxis[1] + z2 * zaxis[2];
160 x2 = a, y2 = b, z2 = c;
161
162 // Lattice dimensions
163 attr._x = ((dx > .0) ? iround((x2 - x1) / dx) + 1 : 1);
164 attr._y = ((dy > .0) ? iround((y2 - y1) / dy) + 1 : 1);
165 attr._z = ((dz > .0) ? iround((z2 - z1) / dz) + 1 : 1);
166 attr._t = ((dt > .0) ? iround((t2 - t1) / dt) + 1 : 1);
167
168 // Lattice spacing
169 attr._dx = ((attr._x > 1) ? dx : .0);
170 attr._dy = ((attr._y > 1) ? dy : .0);
171 attr._dz = ((attr._z > 1) ? dz : .0);
172 attr._dt = ((attr._t > 1) ? dt : .0);
173
174 return attr;
175 }
176
177 // -----------------------------------------------------------------------------
InitializeInterpolator()178 void FreeFormTransformation::InitializeInterpolator()
179 {
180 // Attention: Returns NULL for _ExtrapolationMode == Extrapolation_None!
181 Delete(_CPValue);
182 _CPValue = CPExtrapolator::New(_ExtrapolationMode, &_CPImage);
183 _CPFunc->Input(&_CPImage);
184 _CPFunc->Extrapolator(_CPValue);
185 _CPFunc->Initialize(true); // also initializes _CPValue
186 if (_CPFunc2D) {
187 _CPFunc2D->Input(&_CPImage);
188 _CPFunc2D->Extrapolator(_CPValue);
189 _CPFunc2D->Initialize(true);
190 }
191 }
192
193 // -----------------------------------------------------------------------------
InitializeCPs(const ImageAttributes & attr,bool dofs)194 void FreeFormTransformation::InitializeCPs(const ImageAttributes &attr, bool dofs)
195 {
196 if (attr._x > 1000 || attr._y > 1000 || attr._z > 1000) {
197 // Otherwise we may not only run out of memory, but also encounter an
198 // integer overflow when computing the number of control points...
199 cerr << "FreeFormTransformation::InitializeCPs: Number of control points may not exceed 1000 in each dimension!" << endl;
200 exit(1);
201 }
202 Deallocate(_CPStatus, _Status);
203 const int ncps = attr.NumberOfPoints();
204 if (ncps > 0) {
205 // If NULL, allocate memory for control points which differs from
206 // the memory referenced by _Param and _Status of the base class
207 CPValue *param = NULL;
208 CPStatus *status = NULL;
209 // If control points are directly the parameters of the transformation...
210 if (dofs) {
211 // Assert size of types
212 if (sizeof(CPValue) != 3 * sizeof(DOFValue)) {
213 cerr << "FreeFormTransformation::InitializeCPs: Assertion \"sizeof(CPValue) == 3 * sizeof(DOFValue)\" failed!" << endl;
214 cerr << " Make sure that the vector type used fulfills this assertion such that FFDs can wrap the linear" << endl;
215 cerr << " memory used to store the transformation parameters in an image instance with 3D vector voxel type." << endl;
216 exit(1);
217 }
218 // ...allocate memory for parameters and their respective status
219 InitializeDOFs(3 * ncps);
220 // ...and reinterpret this memory as 3D(+t) images
221 param = reinterpret_cast<CPValue *>(_Param);
222 status = reinterpret_cast<CPStatus *>(_Status);
223 }
224 // Allocate 4D array for convenient access to parameters of each control point
225 _CPImage.Initialize(attr, param);
226 Allocate(_CPStatus, attr._x, attr._y, attr._z, attr._t, status);
227 InitializeInterpolator();
228 InitializeStatus();
229 } else {
230 Delete(_CPValue);
231 _CPImage.Clear();
232 InitializeDOFs(0);
233 }
234 }
235
236 // -----------------------------------------------------------------------------
InitializeCPs(const FreeFormTransformation & ffd,bool dofs)237 void FreeFormTransformation::InitializeCPs(const FreeFormTransformation &ffd, bool dofs)
238 {
239 if (ffd.NumberOfCPs() > 0) {
240 // If NULL, allocate memory for control points which differs from
241 // the memory referenced by _Param and _Status of the base class
242 CPValue *param = NULL;
243 CPStatus *status = NULL;
244 // If control points are directly the parameters of the transformation...
245 if (dofs) {
246 // ...copy transformation parameters and their respective status
247 InitializeDOFs(ffd);
248 // ...and reinterpret this memory as 3D(+t) images
249 param = reinterpret_cast<CPValue *>(_Param);
250 status = reinterpret_cast<CPStatus *>(_Status);
251 }
252 // Allocate 4D array for convenient access to parameters of each control point
253 _CPImage.Initialize(ffd.Attributes(), param);
254 Allocate(_CPStatus, _x, _y, _z, _t, status);
255 InitializeInterpolator();
256 InitializeStatus();
257 } else {
258 Delete(_CPValue);
259 _CPImage.Clear();
260 Deallocate(_CPStatus, _Status);
261 InitializeDOFs(0);
262 }
263 }
264
265 // -----------------------------------------------------------------------------
InitializeStatus()266 void FreeFormTransformation::InitializeStatus()
267 {
268 // Set status of unused control point dimensions to Passive
269 // TODO: Consider removing this commented code or uncomment it again once
270 // the control point coefficients are in lattice units/orientation.
271 // A 2D FFD in a plane not parallel to the xy plane has non-zero
272 // (i.e., active) z coefficients in world space!
273 // const CPStatus status(((_x > 1) ? Active : Passive),
274 // ((_y > 1) ? Active : Passive),
275 // ((_z > 1) ? Active : Passive));
276 const CPStatus status = Active;
277 for (int cp = 0; cp < this->NumberOfCPs(); ++cp) PutStatus(cp, status);
278 }
279
280 // -----------------------------------------------------------------------------
Initialize(const ImageAttributes & attr)281 void FreeFormTransformation::Initialize(const ImageAttributes &attr)
282 {
283 InitializeCPs(attr);
284 this->Changed(true);
285 }
286
287 // -----------------------------------------------------------------------------
Initialize(const ImageAttributes & attr,double dx,double dy,double dz,double dt)288 void FreeFormTransformation::Initialize(const ImageAttributes &attr,
289 double dx, double dy, double dz, double dt)
290 {
291 this->Initialize(DefaultAttributes(attr, dx, dy, dz, dt));
292 }
293
294 // -----------------------------------------------------------------------------
Initialize(const CPImage & image,bool displacement)295 void FreeFormTransformation::Initialize(const CPImage &image, bool displacement)
296 {
297 // Initialize free-form transformation
298 this->Initialize(image.Attributes());
299 // Copy control point coefficients
300 _CPImage.CopyFrom(image);
301 // Convert control point deformation to displacement
302 if (!displacement) {
303 double x, y, z;
304 CPValue *data = _CPImage.Data();
305 for (int l = 0; l < _t; ++l)
306 for (int k = 0; k < _z; ++k)
307 for (int j = 0; j < _y; ++j)
308 for (int i = 0; i < _x; ++i) {
309 x = i, y = j, z = k;
310 this->LatticeToWorld(x, y, z);
311 data->_x -= x;
312 data->_y -= y;
313 data->_z -= z;
314 ++data;
315 }
316 }
317 }
318
319 // -----------------------------------------------------------------------------
320 void FreeFormTransformation
Initialize(const GenericImage<double> & image,bool displacement)321 ::Initialize(const GenericImage<double> &image, bool displacement)
322 {
323 if (image.T() < 2 || image.T() > 3) {
324 cerr << "FreeFormTransformation::Initialize:"
325 " Input image must be 2D/3D vector field with _t == 2 or 3)" << endl;
326 exit(1);
327 }
328 // Initialize control points
329 ImageAttributes attr = image.Attributes();
330 attr._t = 1;
331 attr._dt = .0;
332 this->Initialize(attr);
333 // Copy vector field
334 CPValue *data = _CPImage.Data();
335 for (int k = 0; k < image.GetZ(); ++k)
336 for (int j = 0; j < image.GetY(); ++j)
337 for (int i = 0; i < image.GetX(); ++i) {
338 data->_x = image(i, j, k, 0);
339 data->_y = image(i, j, k, 1);
340 if (image.GetT() >= 3) data->_z = image(i, j, k, 2);
341 else data->_z = .0;
342 ++data;
343 }
344 // Convert control point deformation to displacement
345 if (!displacement) {
346 double x, y, z;
347 CPValue *data = _CPImage.Data();
348 for (int l = 0; l < _t; ++l)
349 for (int k = 0; k < _z; ++k)
350 for (int j = 0; j < _y; ++j)
351 for (int i = 0; i < _x; ++i) {
352 x = i, y = j, z = k;
353 this->LatticeToWorld(x, y, z);
354 data->_x -= x;
355 data->_y -= y;
356 data->_z -= z;
357 ++data;
358 }
359 }
360 }
361
362 // =============================================================================
363 // Parameters (non-DoFs)
364 // =============================================================================
365
366 // -----------------------------------------------------------------------------
ExtrapolationMode(enum ExtrapolationMode mode)367 void FreeFormTransformation::ExtrapolationMode(enum ExtrapolationMode mode)
368 {
369 // Set extrapolation mode
370 _ExtrapolationMode = mode;
371 // Update control point function, if _CPValue == NULL the FFD is uninitialized yet
372 if (_CPValue && _CPValue->ExtrapolationMode() != _ExtrapolationMode) {
373 Delete(_CPValue);
374 _CPValue = CPExtrapolator::New(_ExtrapolationMode, &_CPImage);
375 if (_CPValue) {
376 _CPValue->Input(&_CPImage);
377 _CPValue->Initialize();
378 }
379 _CPFunc->Extrapolator(_CPValue);
380 if (_CPFunc2D) _CPFunc2D->Extrapolator(_CPValue);
381 // Note: No need to call _CPFunc(2D)->Initialize() again
382 }
383 }
384
385 // -----------------------------------------------------------------------------
Set(const char * name,const char * value)386 bool FreeFormTransformation::Set(const char *name, const char *value)
387 {
388 // Extrapolation mode
389 if (strcmp(name, "Transformation extrapolation") == 0) {
390 enum ExtrapolationMode mode;
391 if (!FromString(value, mode)) return false;
392 this->ExtrapolationMode(mode);
393 this->Changed(true);
394 return true;
395 }
396 // Speedup factor for gradient computation
397 if (strcmp(name, "Speedup factor") == 0) {
398 double d;
399 if (!FromString(value, d) || d < 1.0) return false;
400 _SpeedupFactor = d;
401 this->Changed(true);
402 return true;
403 }
404 // Control point spacing
405 if (strncmp(name, "Control point spacing", 21) == 0) {
406 double dx, dy, dz, dt, ds;
407 if (!FromString(value, ds) || ds <= .0) return false;
408 if (strstr(name, "[m]") != NULL) ds *= 1.0e+3;
409 else if (strstr(name, "[mu]") != NULL) ds *= 1.0e-3;
410 _CPImage.GetPixelSize(dx, dy, dz, dt);
411 if (strncmp(name + 21, " in ", 4) == 0) {
412 if (name[25] == 'X') dx = ds;
413 else if (name[25] == 'Y') dy = ds;
414 else if (name[25] == 'Z') dz = ds;
415 else if (name[25] == 'T') dt = ds;
416 } else {
417 dx = dy = dz = dt = ds;
418 }
419 if (_x == 1) dx = .0;
420 if (_y == 1) dy = .0;
421 if (_z == 1) dz = .0;
422 if (_t == 1) dt = .0;
423 _CPImage.PutPixelSize(dx, dy, dz, dt);
424 this->Changed(true);
425 return true;
426 }
427 if (strcmp(name, "Control point xyz units") == 0) {
428 double s;
429 if (strcmp(value, "Meter") == 0 || strcmp(value, "m") == 0) s = 1.0e+3;
430 else if (strcmp(value, "Millimeter") == 0 || strcmp(value, "mm") == 0) s = 1.0;
431 else if (strcmp(value, "Micrometer") == 0 || strcmp(value, "mu") == 0) s = 1.0e-3;
432 else return false;
433 if (s != 1.0) {
434 double ox, oy, oz, dx, dy, dz;
435 _CPImage.GetOrigin (ox, oy, oz);
436 _CPImage.PutOrigin (ox * s, oy * s, oz * s);
437 _CPImage.GetPixelSize(dx, dy, dz);
438 _CPImage.PutPixelSize(dx * s, dy * s, dz * s);
439 _CPImage *= s;
440 }
441 this->Changed(true);
442 return true;
443 }
444 return Transformation::Set(name, value);
445 }
446
447 // -----------------------------------------------------------------------------
Parameter() const448 ParameterList FreeFormTransformation::Parameter() const
449 {
450 ParameterList params = Transformation::Parameter();
451 Insert(params, "Transformation extrapolation", ToString(_ExtrapolationMode));
452 Insert(params, "Speedup factor", ToString(_SpeedupFactor));
453 return params;
454 }
455
456 // =============================================================================
457 // Approximation/Interpolation
458 // =============================================================================
459
460 // -----------------------------------------------------------------------------
461 ImageAttributes FreeFormTransformation
ApproximationDomain(const ImageAttributes & domain,const Transformation *)462 ::ApproximationDomain(const ImageAttributes &domain, const Transformation *)
463 {
464 return domain;
465 }
466
467 // -----------------------------------------------------------------------------
EvaluateRMSError(const Transformation * dof) const468 double FreeFormTransformation::EvaluateRMSError(const Transformation *dof) const
469 {
470 return EvaluateRMSError(_attr, dof);
471 }
472
473 // -----------------------------------------------------------------------------
Approximate(const Transformation * t,int niter,double max_error)474 double FreeFormTransformation::Approximate(const Transformation *t, int niter, double max_error)
475 {
476 return this->Approximate(_attr, t, niter, max_error);
477 }
478
479 // -----------------------------------------------------------------------------
480 double FreeFormTransformation
Approximate(const ImageAttributes & domain,double * dx,double * dy,double * dz,int niter,double max_error)481 ::Approximate(const ImageAttributes &domain, double *dx, double *dy, double *dz,
482 int niter, double max_error)
483 {
484 const int no = domain.NumberOfPoints();
485 if (no <= 0) return .0;
486 double error = numeric_limits<double>::infinity();
487 if (niter < 1) return error;
488
489 // Compute world coordinates of lattice points
490 double *x = Allocate<double>(no);
491 double *y = Allocate<double>(no);
492 double *z = Allocate<double>(no);
493 double *t = Allocate<double>(no);
494 domain.LatticeToWorld(x, y, z, t);
495
496 // Copy original displacements
497 double *tx = CAllocate<double>(no);
498 double *ty = CAllocate<double>(no);
499 double *tz = CAllocate<double>(no);
500 memcpy(tx, dx, no * sizeof(double));
501 memcpy(ty, dy, no * sizeof(double));
502 memcpy(tz, dz, no * sizeof(double));
503
504 // Evaluate error of approximation and residual displacements
505 if (this->RequiresCachingOfDisplacements()) {
506 error = EvaluateRMSError(domain, dx, dy, dz);
507 } else {
508 error = EvaluateRMSError(x, y, z, t, dx, dy, dz, no);
509 }
510
511 // Repeat approximation n times or until error drops below a threshold
512 DOFValue *param = Allocate<DOFValue>(_NumberOfDOFs);
513 for (int iter = 0; iter < niter && error > max_error; ++iter) {
514
515 // Copy current parameters
516 this->Get(param);
517
518 // Approximate residual displacements by new parameters
519 this->ApproximateDOFs(x, y, z, t, dx, dy, dz, no);
520
521 // Add previous parameters
522 this->Add(param);
523
524 // Evaluate error of approximation and residual displacements
525 memcpy(dx, tx, no * sizeof(double));
526 memcpy(dy, ty, no * sizeof(double));
527 memcpy(dz, tz, no * sizeof(double));
528 if (this->RequiresCachingOfDisplacements()) {
529 error = EvaluateRMSError(domain, dx, dy, dz);
530 } else {
531 error = EvaluateRMSError(x, y, z, t, dx, dy, dz, no);
532 }
533 }
534
535 // Free memory
536 Deallocate(param);
537 Deallocate(tx);
538 Deallocate(ty);
539 Deallocate(tz);
540 Deallocate(x);
541 Deallocate(y);
542 Deallocate(z);
543 Deallocate(t);
544
545 return error;
546 }
547
548 // -----------------------------------------------------------------------------
549 double FreeFormTransformation
Approximate(const double * x,const double * y,const double * z,double * dx,double * dy,double * dz,int no,int niter,double max_error)550 ::Approximate(const double *x, const double *y, const double *z,
551 double *dx, double *dy, double *dz, int no,
552 int niter, double max_error)
553 {
554 if (no <= 0) return .0;
555 double error = numeric_limits<double>::infinity();
556 if (niter < 1) return error;
557
558 // Compute (fixed) time coordinates
559 const double t0 = .0;
560 double *t = CAllocate<double>(no, &t0);
561
562 // Copy original displacements
563 double *tx = Allocate<double>(no);
564 double *ty = Allocate<double>(no);
565 double *tz = Allocate<double>(no);
566 memcpy(tx, dx, no * sizeof(double));
567 memcpy(ty, dy, no * sizeof(double));
568 memcpy(tz, dz, no * sizeof(double));
569
570 // Evaluate error of approximation and residual displacements
571 error = EvaluateRMSError(x, y, z, t0, dx, dy, dz, no);
572
573 // Repeat approximation n times or until error drops below a threshold
574 DOFValue *param = Allocate<DOFValue>(_NumberOfDOFs);
575 for (int iter = 0; iter < niter && error > max_error; ++iter) {
576
577 // Copy current parameters
578 this->Get(param);
579
580 // Approximate residual displacements by new parameters
581 this->ApproximateDOFs(x, y, z, t, dx, dy, dz, no);
582
583 // Add previous parameters
584 this->Add(param);
585
586 // Evaluate error of approximation and residual displacements
587 memcpy(dx, tx, no * sizeof(double));
588 memcpy(dy, ty, no * sizeof(double));
589 memcpy(dz, tz, no * sizeof(double));
590 error = EvaluateRMSError(x, y, z, t0, dx, dy, dz, no);
591 }
592
593 // Free memory
594 Deallocate(param);
595 Deallocate(tx);
596 Deallocate(ty);
597 Deallocate(tz);
598 Deallocate(t);
599
600 return error;
601 }
602
603 // -----------------------------------------------------------------------------
604 double FreeFormTransformation
Approximate(const double * x,const double * y,const double * z,const double * t,double * dx,double * dy,double * dz,int no,int niter,double max_error)605 ::Approximate(const double *x, const double *y, const double *z, const double *t,
606 double *dx, double *dy, double *dz, int no,
607 int niter, double max_error)
608 {
609 if (no <= 0) return .0;
610 double error = numeric_limits<double>::infinity();
611 if (niter < 1) return error;
612
613 // Copy original displacements
614 double *tx = Allocate<double>(no);
615 double *ty = Allocate<double>(no);
616 double *tz = Allocate<double>(no);
617 memcpy(tx, dx, no * sizeof(double));
618 memcpy(ty, dy, no * sizeof(double));
619 memcpy(tz, dz, no * sizeof(double));
620
621 // Evaluate error of approximation and residual displacements
622 error = EvaluateRMSError(x, y, z, t, dx, dy, dz, no);
623
624 // Repeat approximation n times or until error drops below a threshold
625 DOFValue *param = Allocate<DOFValue>(_NumberOfDOFs);
626 for (int iter = 0; iter < niter && error > max_error; ++iter) {
627
628 // Copy current parameters
629 this->Get(param);
630
631 // Approximate residual displacements by new parameters
632 this->ApproximateDOFs(x, y, z, t, dx, dy, dz, no);
633
634 // Add previous parameters
635 this->Add(param);
636
637 // Evaluate error of approximation and residual displacements
638 memcpy(dx, tx, no * sizeof(double));
639 memcpy(dy, ty, no * sizeof(double));
640 memcpy(dz, tz, no * sizeof(double));
641 error = EvaluateRMSError(x, y, z, t, dx, dy, dz, no);
642 }
643
644 // Free memory
645 Deallocate(param);
646 Deallocate(tx);
647 Deallocate(ty);
648 Deallocate(tz);
649
650 return error;
651 }
652
653 // -----------------------------------------------------------------------------
ApproximateAsNew(const Transformation * t,int niter,double max_error)654 double FreeFormTransformation::ApproximateAsNew(const Transformation *t, int niter, double max_error)
655 {
656 return this->ApproximateAsNew(_attr, t, niter, max_error);
657 }
658
659 // -----------------------------------------------------------------------------
660 double FreeFormTransformation
ApproximateAsNew(const ImageAttributes & domain,double * dx,double * dy,double * dz,int niter,double max_error)661 ::ApproximateAsNew(const ImageAttributes &domain, double *dx, double *dy, double *dz, int niter, double max_error)
662 {
663 bool ignore_time = (AreEqual(domain._dt, 0.) || domain._t == 1) && (AreEqual(_dt, 0.) || _t == 1);
664 if ((!ignore_time && domain == _attr) || (ignore_time && domain.EqualInSpace(_attr))) {
665 this->Interpolate(dx, dy, dz);
666 return .0; // No approximation error at lattice/control points
667 }
668 return Transformation::ApproximateAsNew(domain, dx, dy, dz, niter, max_error);
669 }
670
671 // =============================================================================
672 // Lattice
673 // =============================================================================
674
675 // -----------------------------------------------------------------------------
CropPadPassiveCPs(int margin,bool reset)676 bool FreeFormTransformation::CropPadPassiveCPs(int margin, bool reset)
677 {
678 return this->CropPadPassiveCPs(margin, margin, margin, margin, reset);
679 }
680
681 // -----------------------------------------------------------------------------
CropPadPassiveCPs(int mx,int my,int mz,int mt,bool reset)682 bool FreeFormTransformation::CropPadPassiveCPs(int mx, int my, int mz, int mt, bool reset)
683 {
684 ImageAttributes attr = _attr;
685 // Determine lower bound along x axis: i1
686 int i1 = _x;
687 for (int l = 0; l < _t; ++l)
688 for (int k = 0; k < _z; ++k)
689 for (int j = 0; j < _y; ++j)
690 for (int i = 0; i < _x; ++i) {
691 if (IsActive(i, j, k, l)) {
692 if (i < i1) i1 = i;
693 break;
694 }
695 }
696 // Determine upper bound along x axis: i2
697 int i2 = -1;
698 for (int l = 0; l < _t; ++l)
699 for (int k = 0; k < _z; ++k)
700 for (int j = 0; j < _y; ++j)
701 for (int i = _x - 1; i >= i1; --i) {
702 if (IsActive(i, j, k, l)) {
703 if (i > i2) i2 = i;
704 break;
705 }
706 }
707 // Determine lower bound along y axis: j1
708 int j1 = _y;
709 for (int l = 0; l < _t; ++l)
710 for (int k = 0; k < _z; ++k)
711 for (int i = i1; i <= i2; ++i)
712 for (int j = 0; j < _y; ++j) {
713 if (IsActive(i, j, k, l)) {
714 if (j < j1) j1 = j;
715 break;
716 }
717 }
718 // Determine upper bound along y axis: j2
719 int j2 = -1;
720 for (int l = 0; l < _t; ++l)
721 for (int k = 0; k < _z; ++k)
722 for (int i = i1; i <= i2; ++i)
723 for (int j = _y - 1; j >= j1; --j) {
724 if (IsActive(i, j, k, l)) {
725 if (j > j2) j2 = j;
726 break;
727 }
728 }
729 // Determine lower bound along z axis: k1
730 int k1 = _z;
731 for (int l = 0; l < _t; ++l)
732 for (int j = j1; j <= j2; ++j)
733 for (int i = i1; i <= i2; ++i)
734 for (int k = 0; k < _z; ++k) {
735 if (IsActive(i, j, k, l)) {
736 if (k < k1) k1 = k;
737 break;
738 }
739 }
740 // Determine upper bound along z axis: k2
741 int k2 = -1;
742 for (int l = 0; l < _t; ++l)
743 for (int j = j1; j <= j2; ++j)
744 for (int i = i1; i <= i2; ++i)
745 for (int k = _z - 1; k >= k1; --k) {
746 if (IsActive(i, j, k, l)) {
747 if (k > k2) k2 = k;
748 break;
749 }
750 }
751 // Determine lower bound along t axis: l1
752 int l1 = _t;
753 for (int k = k1; k <= k2; ++k)
754 for (int j = j1; j <= j2; ++j)
755 for (int i = i1; i <= i2; ++i)
756 for (int l = 0; l < _t; ++l) {
757 if (IsActive(i, j, k, l)) {
758 if (l < l1) l1 = l;
759 break;
760 }
761 }
762 // Determine upper bound along t axis: l2
763 int l2 = -1;
764 for (int k = k1; k <= k2; ++k)
765 for (int j = j1; j <= j2; ++j)
766 for (int i = i1; i <= i2; ++i)
767 for (int l = _t - 1; l >= l1; --l) {
768 if (IsActive(i, j, k, l)) {
769 if (l > l2) l2 = l;
770 break;
771 }
772 }
773 // Do nothing if all control points are passive, but report it
774 if (i1 > i2 || j1 > j2 || k1 > k2 || l1 > l2) return false;
775 // Convert upper index bounds to margin widths
776 i2 = (_x - 1) - i2;
777 j2 = (_y - 1) - j2;
778 k2 = (_z - 1) - k2;
779 l2 = (_t - 1) - l2;
780 // Leave a margin of passive control points with specified width
781 // Note: Negative value gives the number of control points to add.
782 if (_x > 1) i1 -= mx, i2 -= mx;
783 if (_y > 1) j1 -= my, j2 -= my;
784 if (_z > 1) k1 -= mz, k2 -= mz;
785 if (_t > 1) l1 -= mt, l2 -= mt;
786 // Do nothing, if nothing to be done
787 if (i1 == 0 && i2 == 0 && j1 == 0 && j2 == 0 &&
788 k1 == 0 && k2 == 0 && l1 == 0 && l2 == 0) return true;
789 // Adjust control point lattice
790 attr._x -= i1 + i2;
791 attr._y -= j1 + j2;
792 attr._z -= k1 + k2;
793 attr._t -= l1 + l2;
794 attr._xorigin = 0.5 * ((_x - 1) + (i1 - i2));
795 attr._yorigin = 0.5 * ((_y - 1) + (j1 - j2));
796 attr._zorigin = 0.5 * ((_z - 1) + (k1 - k2));
797 this->LatticeToWorld(attr._xorigin, attr._yorigin, attr._zorigin);
798 attr._torigin = this->LatticeToTime(l1);
799 // Convert upper margin widths to index bounds
800 i2 = (_x - 1) - i2;
801 j2 = (_y - 1) - j2;
802 k2 = (_z - 1) - k2;
803 l2 = (_t - 1) - l2;
804 // Copy remaining control points and pad lattice where needed
805 const int ncps = attr.NumberOfPoints();
806 CPValue *param = Allocate<CPValue >(ncps);
807 CPStatus *status = Allocate<CPStatus>(ncps);
808 CPValue *param_iter = param;
809 CPStatus *status_iter = status;
810 for (int l = l1; l <= l2; ++l)
811 for (int k = k1; k <= k2; ++k)
812 for (int j = j1; j <= j2; ++j)
813 for (int i = i1; i <= i2; ++i, ++param_iter, ++status_iter) {
814 if (0 <= i && i < _x &&
815 0 <= j && j < _y &&
816 0 <= k && k < _z &&
817 0 <= l && l < _t) {
818 (*status_iter) = _CPStatus[l][k][j][i];
819 (*param_iter) = _CPImage(i, j, k, l);
820 } else {
821 // Padded control point to extend margin
822 (*status_iter) = CPStatus(Passive);
823 (*param_iter) = CPValue (.0);
824 }
825 }
826 // Initialize new control point lattice
827 this->Initialize(attr);
828 param_iter = param;
829 status_iter = status;
830 for (int l = 0; l < _t; ++l)
831 for (int k = 0; k < _z; ++k)
832 for (int j = 0; j < _y; ++j)
833 for (int i = 0; i < _x; ++i, ++param_iter, ++status_iter) {
834 _CPStatus[l][k][j][i] = (*status_iter);
835 if (// Copy all control point values by default
836 !reset ||
837 // Always if control point status is not passive...
838 (status_iter->_x != Passive) ||
839 (status_iter->_y != Passive) ||
840 (status_iter->_z != Passive) ||
841 // ...or control point is not within the boundary margin
842 i >= mx || i < _x - mx ||
843 j >= my || j < _y - my ||
844 k >= mz || k < _z - mz ||
845 l >= mt || l < _t - mt) {
846 _CPImage(i, j, k, l) = (*param_iter);
847 }
848 }
849 // Free temporary allocated memory
850 Deallocate(param);
851 Deallocate(status);
852 return true;
853 }
854
855 // =============================================================================
856 // Bounding box
857 // =============================================================================
858
859 // -----------------------------------------------------------------------------
PutBoundingBox(double x1,double y1,double z1,double x2,double y2,double z2)860 void FreeFormTransformation::PutBoundingBox(double x1, double y1, double z1,
861 double x2, double y2, double z2)
862 {
863 const ImageAttributes &attr = Attributes();
864 double a, b, c;
865
866 // Update lattice origin
867 _CPImage.PutOrigin((x2 + x1) / 2.0, (y2 + y1) / 2.0, (z2 + z1) / 2.0);
868
869 // FOV in lattice orientation
870 a = x1 * attr._xaxis[0] + y1 * attr._xaxis[1] + z1 * attr._xaxis[2];
871 b = x1 * attr._yaxis[0] + y1 * attr._yaxis[1] + z1 * attr._yaxis[2];
872 c = x1 * attr._zaxis[0] + y1 * attr._zaxis[1] + z1 * attr._zaxis[2];
873 x1 = a, y1 = b, z1 = c;
874
875 a = x2 * attr._xaxis[0] + y2 * attr._xaxis[1] + z2 * attr._xaxis[2];
876 b = x2 * attr._yaxis[0] + y2 * attr._yaxis[1] + z2 * attr._yaxis[2];
877 c = x2 * attr._zaxis[0] + y2 * attr._zaxis[1] + z2 * attr._zaxis[2];
878 x2 = a, y2 = b, z2 = c;
879
880 // Update lattice spacing
881 _CPImage.PutPixelSize(((x2 > x1) ? ((x2 - x1) / (attr._x - 1)) : 1),
882 ((y2 > y1) ? ((y2 - y1) / (attr._y - 1)) : 1),
883 ((z2 > z1) ? ((z2 - z1) / (attr._z - 1)) : 1));
884
885 this->Changed(true);
886 }
887
888 // -----------------------------------------------------------------------------
PutBoundingBox(const Point & p1,const Point & p2)889 void FreeFormTransformation::PutBoundingBox(const Point &p1, const Point &p2)
890 {
891 PutBoundingBox(p1._x, p1._y, p1._z, p2._x, p2._y, p2._z);
892 }
893
894 // ---------------------------------------------------------------------------
PutBoundingBox(double t1,double t2)895 void FreeFormTransformation::PutBoundingBox(double t1, double t2)
896 {
897 _CPImage.PutTOrigin((t2 + t1) / 2.0);
898 _CPImage.PutTSize ((t2 > t2) ? ((t2 - t1) / (_t - 1)) : 1);
899 this->Changed(true);
900 }
901
902 // ---------------------------------------------------------------------------
PutBoundingBox(double x1,double y1,double z1,double t1,double x2,double y2,double z2,double t2)903 void FreeFormTransformation::PutBoundingBox(double x1, double y1, double z1, double t1,
904 double x2, double y2, double z2, double t2)
905 {
906 PutBoundingBox(x1, y1, z1, x2, y2, z2);
907 PutBoundingBox(t1, t2);
908 }
909
910 // =============================================================================
911 // Derivatives
912 // =============================================================================
913
914 // -----------------------------------------------------------------------------
915 /// (Multi-threaded) body of FreeFormTransformation::ParametricGradient
916 ///
917 /// This default implementation uses the FreeFormTransformation::JacobianDOFs
918 /// overload which computes the derivatives of the transformation w.r.t. all
919 /// transformation parameters at once for a given spatial location (target voxel).
920 /// It provides better performance in particular for transformations which are
921 /// parameterized by non-stationary velocity fields (3D+t) in which case the
922 /// derivatives along each temporal trajectory are computed at once and therefore
923 /// this trajectory does not have to be re-computed for each and every control
924 /// point. For classic FFDs, which are parameterized by displacements, less
925 /// general but more efficient implementations of the ParametricGradient function
926 /// are provided by the FreeFormTransformation3D and
927 /// FreeFormTransformation4D subclasses, which therefore override this
928 /// FreeFormTransformation::ParametricGradient implementation. If a subclass
929 /// of either of these subclasses is parameterized by velocities, it must therefore
930 /// override the ParametericGradient function again, for example as follows,
931 /// unless a more specialized gradient computation is provided by this subclass.
932 ///
933 /// \code
934 /// void BSplineFreeFormTransformationTD::ParametricGradient(...)
935 /// {
936 /// FreeFormTransformation::ParametricGradient(...);
937 /// }
938 /// \endcode
939 class FreeFormTransformationParametricGradientBody
940 {
941 public:
942 const FreeFormTransformation *_FFD;
943 const GenericImage<double> *_Input;
944 const WorldCoordsImage *_WorldCoords;
945 double *_Output;
946 double _Weight;
947
948 double _t; ///< Time corrresponding to input gradient image (in ms)
949 double _t0; ///< Second time argument for velocity-based transformations
950
951 private:
952
953 int _X; ///< Number of voxels along x axis
954 int _Y; ///< Number of voxels along y axis
955
956 public:
957
958 // ---------------------------------------------------------------------------
959 /// Default constructor
FreeFormTransformationParametricGradientBody()960 FreeFormTransformationParametricGradientBody()
961 :
962 _FFD (NULL),
963 _Input (NULL),
964 _WorldCoords(NULL),
965 _Output (NULL),
966 _Weight ( 1.0),
967 _t ( 0.0),
968 _t0 (-1.0),
969 _X (0),
970 _Y (0)
971 {}
972
973 // ---------------------------------------------------------------------------
974 /// Split constructor
FreeFormTransformationParametricGradientBody(const FreeFormTransformationParametricGradientBody & other,split)975 FreeFormTransformationParametricGradientBody(const FreeFormTransformationParametricGradientBody &other, split)
976 :
977 _FFD (other._FFD),
978 _Input (other._Input),
979 _WorldCoords(other._WorldCoords),
980 _Output (NULL),
981 _Weight (other._Weight),
982 _t (other._t),
983 _t0 (other._t0),
984 _X (other._X),
985 _Y (other._Y)
986 {
987 const int ndofs = _FFD->NumberOfDOFs();
988 _Output = new double[ndofs];
989 memset(_Output, 0, ndofs * sizeof(double));
990 }
991
992 // ---------------------------------------------------------------------------
993 /// Destructor
~FreeFormTransformationParametricGradientBody()994 ~FreeFormTransformationParametricGradientBody() {}
995
996 // ---------------------------------------------------------------------------
997 /// Join results of right-hand body with this body
join(FreeFormTransformationParametricGradientBody & rhs)998 void join(FreeFormTransformationParametricGradientBody &rhs)
999 {
1000 const int ndofs = _FFD->NumberOfDOFs();
1001 for (int dof = 0; dof < ndofs; dof++) _Output[dof] += rhs._Output[dof];
1002 delete[] rhs._Output;
1003 rhs._Output = NULL;
1004 }
1005
1006 // ---------------------------------------------------------------------------
1007 /// Calculates the gradient of the similarity term w.r.t. the transformation
1008 /// parameters for each voxel in the specified image region
operator ()(const blocked_range3d<int> & re)1009 void operator ()(const blocked_range3d<int> &re)
1010 {
1011 const int i1 = re.cols ().begin();
1012 const int j1 = re.rows ().begin();
1013 const int k1 = re.pages().begin();
1014 const int i2 = re.cols ().end();
1015 const int j2 = re.rows ().end();
1016 const int k2 = re.pages().end();
1017
1018 TransformationJacobian jac;
1019 TransformationJacobian::ConstColumnIterator it;
1020
1021 //s1=1
1022 const int s2 = _X - (i2 - i1);
1023 const int s3 = (_Y - (j2 - j1)) * _X;
1024
1025 // Non-parametric/voxelwise gradient
1026 const double *gx = _Input->Data(i1, j1, k1, 0);
1027 const double *gy = _Input->Data(i1, j1, k1, 1);
1028 const double *gz = _Input->Data(i1, j1, k1, 2);
1029
1030 // With (transformed) pre-computed world coordinates
1031 if (_WorldCoords) {
1032 const double *wx = _WorldCoords->Data(i1, j1, k1, 0);
1033 const double *wy = _WorldCoords->Data(i1, j1, k1, 1);
1034 const double *wz = _WorldCoords->Data(i1, j1, k1, 2);
1035 for (int k = k1; k < k2; ++k, wx += s3, wy += s3, wz += s3, gx += s3, gy += s3, gz += s3)
1036 for (int j = j1; j < j2; ++j, wx += s2, wy += s2, wz += s2, gx += s2, gy += s2, gz += s2)
1037 for (int i = i1; i < i2; ++i, wx += 1, wy += 1, wz += 1, gx += 1, gy += 1, gz += 1) {
1038 if (*gx || *gy || *gz) {
1039 // Calculate derivatives of transformation w.r.t. the parameters
1040 _FFD->JacobianDOFs(jac, *wx, *wy, *wz, _t, _t0);
1041 // Apply chain rule to obtain similarity gradient w.r.t. the transformation parameters
1042 for (it = jac.Begin(); it != jac.End(); ++it) {
1043 _Output[it->first] += _Weight * (it->second._x * (*gx) +
1044 it->second._y * (*gy) +
1045 it->second._z * (*gz));
1046 }
1047 }
1048 }
1049 // Without pre-computed world coordinates
1050 } else {
1051 double x, y, z;
1052 for (int k = k1; k < k2; ++k, gx += s3, gy += s3, gz += s3)
1053 for (int j = j1; j < j2; ++j, gx += s2, gy += s2, gz += s2)
1054 for (int i = i1; i < i2; ++i, gx += 1, gy += 1, gz += 1) {
1055 if (*gx || *gy || *gz) {
1056 // Convert voxel to world coordinates
1057 x = i, y = j, z = k;
1058 _Input->ImageToWorld(x, y, z);
1059 // Calculate derivatives of transformation w.r.t. the parameters
1060 _FFD->JacobianDOFs(jac, x, y, z, _t, _t0);
1061 // Apply chain rule to obtain similarity gradient w.r.t. the transformation parameters
1062 for (it = jac.Begin(); it != jac.End(); ++it) {
1063 _Output[it->first] += _Weight * (it->second._x * (*gx) +
1064 it->second._y * (*gy) +
1065 it->second._z * (*gz));
1066 }
1067 }
1068 }
1069 }
1070 }
1071
1072 // ---------------------------------------------------------------------------
operator ()()1073 void operator ()()
1074 {
1075 // Initialize members to often accessed data
1076 _X = _Input->GetX();
1077 _Y = _Input->GetY();
1078 const int _Z = _Input->GetZ();
1079
1080 // Check input
1081 if (_WorldCoords && (_WorldCoords->X() != _X ||
1082 _WorldCoords->Y() != _Y ||
1083 _WorldCoords->Z() != _Z ||
1084 _WorldCoords->T() != 3)) {
1085 cerr << "FreeFormTransformation::ParametricGradient: Invalid world coordinates map" << endl;
1086 exit(1);
1087 }
1088
1089 if (_Input->GetXSize() > _FFD->GetXSpacing() ||
1090 _Input->GetYSize() > _FFD->GetYSpacing() ||
1091 (_FFD->Z() > 1 && _Input->GetZSize() > _FFD->GetZSpacing())) {
1092 // FIXME: In this case, the non-parametric input gradient should be
1093 // resampled using linear interpolation to obtain a value at
1094 // each control point.
1095 cerr << "Warning: FFD spacing smaller than image resolution!" << endl;
1096 cerr << " This may lead to artifacts in the transformation because" << endl;
1097 cerr << " not all control points are within the vicinity of a voxel center." << endl;
1098 }
1099
1100 // Calculate parametric gradient
1101 blocked_range3d<int> voxels(0, _Z, 0, _Y, 0, _X);
1102 parallel_reduce(voxels, *this);
1103 }
1104
1105 }; // FreeFormTransformationParametricGradientBody
1106
1107 // -----------------------------------------------------------------------------
1108 class FreeFormTransformationPointWiseParametricGradientBody
1109 {
1110 public:
1111
1112 const FreeFormTransformation *_FFD;
1113 const PointSet *_PointSet;
1114 const Vector3D<double> *_Input;
1115 double *_Output;
1116 double _Weight;
1117
1118 double _t; ///< Time corrresponding to input gradient image (in ms)
1119 double _t0; ///< Second time argument for velocity-based transformations
1120
1121 // ---------------------------------------------------------------------------
1122 /// Default constructor
FreeFormTransformationPointWiseParametricGradientBody()1123 FreeFormTransformationPointWiseParametricGradientBody()
1124 :
1125 _FFD (NULL),
1126 _PointSet(NULL),
1127 _Input (NULL),
1128 _Output (NULL),
1129 _Weight ( 1.0),
1130 _t ( 0.0),
1131 _t0 (-1.0)
1132 {}
1133
1134 // ---------------------------------------------------------------------------
1135 /// Split constructor
FreeFormTransformationPointWiseParametricGradientBody(const FreeFormTransformationPointWiseParametricGradientBody & other,split)1136 FreeFormTransformationPointWiseParametricGradientBody(const FreeFormTransformationPointWiseParametricGradientBody &other, split)
1137 :
1138 _FFD (other._FFD),
1139 _PointSet(other._PointSet),
1140 _Input (other._Input),
1141 _Output (NULL),
1142 _Weight (other._Weight),
1143 _t (other._t),
1144 _t0 (other._t0)
1145 {
1146 const int ndofs = _FFD->NumberOfDOFs();
1147 _Output = new double[ndofs];
1148 memset(_Output, 0, ndofs * sizeof(double));
1149 }
1150
1151 // ---------------------------------------------------------------------------
1152 /// Destructor
~FreeFormTransformationPointWiseParametricGradientBody()1153 ~FreeFormTransformationPointWiseParametricGradientBody() {}
1154
1155 // ---------------------------------------------------------------------------
1156 /// Join results of right-hand body with this body
join(FreeFormTransformationPointWiseParametricGradientBody & rhs)1157 void join(FreeFormTransformationPointWiseParametricGradientBody &rhs)
1158 {
1159 const int ndofs = _FFD->NumberOfDOFs();
1160 for (int dof = 0; dof < ndofs; ++dof) _Output[dof] += rhs._Output[dof];
1161 delete[] rhs._Output;
1162 rhs._Output = NULL;
1163 }
1164
1165 // ---------------------------------------------------------------------------
1166 /// Calculates the gradient of the similarity term w.r.t. the transformation
1167 /// parameters for the specified points in 3D.
operator ()(const blocked_range<int> & re)1168 void operator ()(const blocked_range<int> &re)
1169 {
1170 TransformationJacobian jac;
1171 TransformationJacobian::ConstColumnIterator it;
1172 Point p;
1173
1174 for (int i = re.begin(); i != re.end(); ++i) {
1175 const Vector3D<double> &g = _Input[i];
1176 // Check whether reference point is valid
1177 if (g._x != .0 || g._y != .0 || g._z != .0) {
1178 // Calculate derivatives of transformation w.r.t. the parameters
1179 _PointSet->GetPoint(i, p);
1180 _FFD->JacobianDOFs(jac, p._x, p._y, p._z, _t, _t0);
1181 // Apply chain rule to obtain gradient w.r.t. the transformation parameters
1182 for (it = jac.Begin(); it != jac.End(); ++it) {
1183 _Output[it->first] += _Weight * (it->second._x * g._x +
1184 it->second._y * g._y +
1185 it->second._z * g._z);
1186 }
1187 }
1188 }
1189 }
1190
1191 // ---------------------------------------------------------------------------
operator ()()1192 void operator ()()
1193 {
1194 blocked_range<int> pts(0, _PointSet->Size());
1195 parallel_reduce(pts, *this);
1196 }
1197
1198 }; // FreeFormTransformationPointWiseParametricGradientBody
1199
1200 // -----------------------------------------------------------------------------
1201 void FreeFormTransformation
ParametricGradient(const GenericImage<double> * in,double * out,const WorldCoordsImage * i2w,const WorldCoordsImage * wc,double t0,double w) const1202 ::ParametricGradient(const GenericImage<double> *in, double *out,
1203 const WorldCoordsImage *i2w, const WorldCoordsImage *wc,
1204 double t0, double w) const
1205 {
1206 MIRTK_START_TIMING();
1207 FreeFormTransformationParametricGradientBody body;
1208 body._FFD = this;
1209 body._Input = in;
1210 body._Output = out;
1211 body._Weight = w;
1212 body._WorldCoords = (wc ? wc : i2w);
1213 body._t = in->GetTOrigin();
1214 body._t0 = t0;
1215 body();
1216 MIRTK_DEBUG_TIMING(2, "parametric gradient computation (FFD)");
1217 }
1218
1219 // -----------------------------------------------------------------------------
1220 void FreeFormTransformation
ParametricGradient(const PointSet & pos,const Vector3D<double> * in,double * out,double t,double t0,double w) const1221 ::ParametricGradient(const PointSet &pos, const Vector3D<double> *in,
1222 double *out, double t, double t0, double w) const
1223 {
1224 MIRTK_START_TIMING();
1225 FreeFormTransformationPointWiseParametricGradientBody body;
1226 body._FFD = this;
1227 body._PointSet = &pos;
1228 body._Input = in;
1229 body._Output = out;
1230 body._Weight = w;
1231 body._t = t;
1232 body._t0 = t0;
1233 body();
1234 MIRTK_DEBUG_TIMING(2, "point-wise parametric gradient computation (FFD)");
1235 }
1236
1237 // -----------------------------------------------------------------------------
1238 void FreeFormTransformation
FFDJacobianDetDerivative(double[3],const Matrix &,int,double,double,double,double,double,bool,bool) const1239 ::FFDJacobianDetDerivative(double [3], const Matrix &, int, double, double, double, double, double, bool, bool) const
1240 {
1241 Throw(ERR_NotImplemented, __FUNCTION__, "Not implemented");
1242 }
1243
1244 // =============================================================================
1245 // Properties
1246 // =============================================================================
1247
1248 // -----------------------------------------------------------------------------
1249 double FreeFormTransformation
BendingEnergy(double x,double y,double z,double t,double t0,bool wrt_world) const1250 ::BendingEnergy(double x, double y, double z, double t, double t0, bool wrt_world) const
1251 {
1252 if (!wrt_world) {
1253 cerr << "FreeFormTransformation::BendingEnergy: Always uses derivatives w.r.t. world coordinates" << endl;
1254 exit(1);
1255 }
1256 // Calculate 2nd order derivatives
1257 Matrix hessian[3];
1258 this->LocalHessian(hessian, x, y, z, t, t0);
1259 // Calculate bending energy
1260 return Bending3D(hessian);
1261 }
1262
1263 // -----------------------------------------------------------------------------
1264 double FreeFormTransformation
BendingEnergy(bool incl_passive,bool wrt_world) const1265 ::BendingEnergy(bool incl_passive, bool wrt_world) const
1266 {
1267 int nactive = 0;
1268 double bending = .0;
1269 double x, y, z, t;
1270
1271 for (int l = 0; l < _t; ++l)
1272 for (int k = 0; k < _z; ++k)
1273 for (int j = 0; j < _y; ++j)
1274 for (int i = 0; i < _x; ++i) {
1275 if (incl_passive || this->IsActive(i, j, k, l)) {
1276 x = i, y = j, z = k;
1277 this->LatticeToWorld(x, y, z);
1278 t = this->LatticeToTime(l);
1279 bending += this->BendingEnergy(x, y, z, t, 1.0, wrt_world);
1280 ++nactive;
1281 }
1282 }
1283
1284 if (nactive > 0) bending /= nactive;
1285 return bending;
1286 }
1287
1288 // -----------------------------------------------------------------------------
1289 double FreeFormTransformation
BendingEnergy(const ImageAttributes & attr,double t0,bool wrt_world) const1290 ::BendingEnergy(const ImageAttributes &attr, double t0, bool wrt_world) const
1291 {
1292 const int N = attr.NumberOfPoints();
1293 const int L = attr._dt ? attr._t : 1;
1294 if (N == 0) return .0;
1295
1296 double x, y, z, t, bending = .0;
1297 for (int l = 0; l < L; ++l) {
1298 t = attr.LatticeToTime(l);
1299 for (int k = 0; k < attr._z; ++k)
1300 for (int j = 0; j < attr._y; ++j)
1301 for (int i = 0; i < attr._x; ++i) {
1302 x = i, y = j, z = k;
1303 attr.LatticeToWorld(x, y, z);
1304 bending += this->BendingEnergy(x, y, z, t, t0, wrt_world);
1305 }
1306 }
1307
1308 return bending / N;
1309 }
1310
1311 // -----------------------------------------------------------------------------
BendingEnergyGradient(double *,double,bool,bool,bool) const1312 void FreeFormTransformation::BendingEnergyGradient(double *, double, bool, bool, bool) const
1313 {
1314 cerr << this->NameOfClass() << "::BendingEnergyGradient: Not implemented" << endl;
1315 exit(1);
1316 }
1317
1318 // =============================================================================
1319 // I/O
1320 // =============================================================================
1321
1322 // -----------------------------------------------------------------------------
Print(ostream & os,Indent indent) const1323 void FreeFormTransformation::Print(ostream &os, Indent indent) const
1324 {
1325 // Print no. of transformation parameters
1326 os << indent << "Number of DOFs: " << this->NumberOfDOFs() << endl;
1327 os << indent << "Number of CPs (active): " << this->NumberOfActiveCPs()<< endl;
1328 os << indent << "Number of CPs (passive): " << this->NumberOfPassiveCPs()<< endl;
1329 os << indent << "Extrapolation mode: " << ToString(_ExtrapolationMode) << endl;
1330 // Print lattice attributes
1331 os << indent << "Control point lattice:" << endl;
1332 _attr.Print(os, indent + 1);
1333 }
1334
1335 // -----------------------------------------------------------------------------
WriteCPs(Cofstream & to) const1336 Cofstream &FreeFormTransformation::WriteCPs(Cofstream &to) const
1337 {
1338 // Note: this->NumberOfDOFs() may differ for specialized subclasses!
1339 const int num = 3 * this->NumberOfCPs();
1340 to.WriteAsDouble(reinterpret_cast<const double *>(_CPImage.Data()), num);
1341 to.WriteAsInt (reinterpret_cast<const int *>(_CPStatus[0][0][0]), num);
1342 return to;
1343 }
1344
1345
1346 } // namespace mirtk
1347