1 /*
2 * Medical Image Registration ToolKit (MIRTK)
3 *
4 * Copyright 2013-2015 Imperial College London
5 * Copyright 2013-2015 Andreas Schuh
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19
20 #include "mirtk/GenericImage.h"
21 #include "mirtk/Transformation.h"
22 #include "mirtk/Noise.h"
23 #include "mirtk/GaussianBlurring.h"
24 #include "mirtk/GaussianBlurring2D.h"
25 #include "mirtk/ConvolutionWithGaussianDerivative.h"
26 #include "mirtk/ConvolutionWithGaussianDerivative2.h"
27 #include "mirtk/DisplacementToVelocityField.h"
28 #include "mirtk/VelocityToDisplacementFieldEuler.h"
29
30 #include <cmath>
31 #include <cstring>
32 #include <cstdlib>
33 #include <iostream>
34
35 using namespace std;
36 using namespace mirtk;
37
38
39 // ===========================================================================
40 // Initialization
41 // ===========================================================================
42
43 // ---------------------------------------------------------------------------
init(char * name_in,ImageAttributes & grid,double sigma,double max,double * & x,double * & y,double * & z,double * & dx,double * & dy,double * & dz)44 void init(char *name_in, ImageAttributes &grid,
45 double sigma, double max,
46 double *&x, double *&y, double *&z,
47 double *&dx, double *&dy, double *&dz)
48 {
49 GenericImage<RealPixel> disp;
50
51 if (name_in == NULL) {
52 // create zero displacement field
53 ImageAttributes attr = grid;
54 attr._t = 3;
55 disp.Initialize(attr);
56
57 // add random noise to displacement field
58 GaussianNoise<RealPixel> noise(0.0, sigma, -fabs(max), +fabs(max));
59 noise.SetInput (&disp);
60 noise.SetOutput(&disp);
61 noise.Run();
62
63 // smooth displacements
64 if (grid._z > 1) {
65 GaussianBlurring<RealPixel> blur(1.25);
66 blur.SetInput (&disp);
67 blur.SetOutput(&disp);
68 blur.Run();
69 } else {
70 GaussianBlurring2D<RealPixel> blur(1.25);
71 blur.SetInput (&disp);
72 blur.SetOutput(&disp);
73 blur.Run();
74 }
75 } else if (strstr(name_in, ".dof") != NULL) {
76 // read transformation from file
77 Transformation *t = Transformation::New(name_in);
78 MultiLevelTransformation *mffd = dynamic_cast<MultiLevelTransformation *>(t);
79 FreeFormTransformation *ffd = dynamic_cast<FreeFormTransformation *>(t);
80 if (mffd != NULL && mffd->NumberOfLevels() > 0) {
81 ffd = mffd->GetLocalTransformation(mffd->NumberOfLevels() - 1);
82 }
83 if (ffd != NULL) grid = ffd->Attributes();
84 ImageAttributes attr = grid;
85 attr._t = 3;
86 disp.Initialize(attr);
87 t->Displacement(disp);
88 } else {
89 // read displacements from file
90 disp.Read(name_in);
91 if (disp.GetT() != 3) {
92 cerr << "Invalid input displacement field: " << name_in << endl;
93 exit(1);
94 }
95 grid = disp.GetImageAttributes();
96 grid._t = 1;
97 }
98
99 // copy to output arrays
100 const int n = grid._x * grid._y * grid._z;
101
102 x = new double[n];
103 y = new double[n];
104 z = new double[n];
105 dx = new double[n];
106 dy = new double[n];
107 dz = new double[n];
108
109 int p = 0;
110 for (int k = 0; k < grid._z; k++) {
111 for (int j = 0; j < grid._y; j++) {
112 for (int i = 0; i < grid._x; i++) {
113 x[p] = i;
114 y[p] = j;
115 z[p] = k;
116 disp.ImageToWorld(x[p], y[p], z[p]);
117 dx[p] = disp(i, j, k, 0);
118 dy[p] = disp(i, j, k, 1);
119 dz[p] = disp(i, j, k, 2);
120 p++;
121 }
122 }
123 }
124 }
125
126 // ===========================================================================
127 // Auxiliary functions
128 // ===========================================================================
129
130 // ---------------------------------------------------------------------------
jac(Matrix & J,GenericImage<double> & img,int i,int j,int k)131 inline void jac(Matrix &J, GenericImage<double> &img, int i, int j, int k)
132 {
133 J.Initialize(3, 3);
134
135 double sx = 1.0 / img.GetXSize();
136 double sy = 1.0 / img.GetYSize();
137 double sz = 1.0 / img.GetZSize();
138
139 if (i <= 0 || i >= img.GetX() - 1) {
140 J(0, 0) = 0;
141 J(0, 1) = 0;
142 J(0, 2) = 0;
143 } else {
144 // central difference
145 J(0, 0) = 0.5 * sx * (img(i + 1, j, k, 0) - img(i - 1, j, k, 0));
146 J(0, 1) = 0.5 * sx * (img(i + 1, j, k, 1) - img(i - 1, j, k, 1));
147 J(0, 2) = 0.5 * sx * (img(i + 1, j, k, 2) - img(i - 1, j, k, 2));
148 }
149
150 if (j <= 0 || j >= img.GetY() - 1) {
151 J(1, 0) = 0;
152 J(1, 1) = 0;
153 J(1, 2) = 0;
154 } else {
155 // central difference
156 J(1, 0) = 0.5 * sy * (img(i, j + 1, k, 0) - img(i, j - 1, k, 0));
157 J(1, 1) = 0.5 * sy * (img(i, j + 1, k, 1) - img(i, j - 1, k, 1));
158 J(1, 2) = 0.5 * sy * (img(i, j + 1, k, 2) - img(i, j - 1, k, 2));
159 }
160
161 if (k <= 0 || k >= img.GetZ() - 1) {
162 J(2, 0) = 0;
163 J(2, 1) = 0;
164 J(2, 2) = 0;
165 } else {
166 // central difference
167 J(2, 0) = 0.5 * sz * (img(i, j, k + 1, 0) - img(i, j, k - 1, 0));
168 J(2, 1) = 0.5 * sz * (img(i, j, k + 1, 1) - img(i, j, k - 1, 1));
169 J(2, 2) = 0.5 * sz * (img(i, j, k + 1, 2) - img(i, j, k - 1, 2));
170 }
171 }
172
173 // ===========================================================================
174 // Exponential map
175 // ===========================================================================
176
177 // ---------------------------------------------------------------------------
expEuler(GenericImage<double> & v,double & x,double & y,double & z,bool inv)178 inline void expEuler(GenericImage<double> &v, double &x, double &y, double &z, bool inv)
179 {
180 const int NumberOfSteps = 100; // Vercauteren et al. use 500 Euler steps for log()
181 const double dt = (inv ? -1.0 : 1.0) / static_cast<double>(NumberOfSteps);
182
183 LinearInterpolateImageFunction vf;
184 vf.SetInput(&v);
185 vf.Initialize();
186
187 for (int s = 0; s < NumberOfSteps; s++) {
188 double i = x;
189 double j = y;
190 double k = z;
191 v.WorldToImage(i, j, k);
192 x += vf.Evaluate(i, j, k, 0) * dt;
193 y += vf.Evaluate(i, j, k, 1) * dt;
194 z += vf.Evaluate(i, j, k, 2) * dt;
195 }
196 }
197
198 // ===========================================================================
199 // Logarithmic map
200 // ===========================================================================
201
202 // ---------------------------------------------------------------------------
testLogWithExpEuler(const ImageAttributes & grid,const double * x,const double * y,const double * z,const double * dx,const double * dy,const double * dz)203 void testLogWithExpEuler(const ImageAttributes &grid,
204 const double *x, const double *y, const double *z,
205 const double *dx, const double *dy, const double *dz)
206 {
207 const int nsteps = 5;
208 const int n = grid._x * grid._y * grid._z;
209
210 double x1, y1, z1;
211 double x2, y2, z2;
212 double lx, ly, lz;
213 double vx, vy, vz;
214 double dvx, dvy, dvz;
215 Matrix Jv, Jdv;
216 double error, avg_error, max_error;
217 int p;
218
219 ImageAttributes attr = grid;
220 attr._t = 3;
221
222 GenericImage<double> phi(attr); // displacement field
223 GenericImage<double> v (attr); // current velocity field
224 GenericImage<double> vn (attr); // next velocity field
225 GenericImage<double> dv (attr); // dv = exp(-vp) ° Phi(x) - x
226
227 // Initialize:
228 // v_0 = 0
229 // v_1 = exp(-v_0) ° Phi(x) - x = Phi(x) - x
230 p = 0;
231 for (int k = 0; k < grid._z; k++) {
232 for (int j = 0; j < grid._y; j++) {
233 for (int i = 0; i < grid._x; i++) {
234 phi(i, j, k, 0) = dx[p];
235 phi(i, j, k, 1) = dy[p];
236 phi(i, j, k, 2) = dz[p];
237 v (i, j, k, 0) = dx[p];
238 v (i, j, k, 1) = dy[p];
239 v (i, j, k, 2) = dz[p];
240 p++;
241 }
242 }
243 }
244
245 for (int s = 0; s < nsteps; s++) {
246 // Compute dv = exp(-v) ° Phi(x) - x
247 for (int k = 0; k < grid._z; k++) {
248 for (int j = 0; j < grid._y; j++) {
249 for (int i = 0; i < grid._x; i++) {
250 // Convert to world coordinates
251 x1 = i;
252 y1 = j;
253 z1 = k;
254 phi.ImageToWorld(x1, y1, z1);
255 // Transform by Phi
256 x2 = x1 + phi(i, j, k, 0);
257 y2 = y1 + phi(i, j, k, 1);
258 z2 = z1 + phi(i, j, k, 2);
259 // Transform by inverse of current transformation [= exp(-v)]
260 expEuler(v, x2, y2, z2, true);
261 // Compute delta values
262 dv(i, j, k, 0) = x2 - x1;
263 dv(i, j, k, 1) = y2 - y1;
264 dv(i, j, k, 2) = z2 - z1;
265 }
266 }
267 }
268 // Smooth dv to stabilize computation (see Vercauteren et al. ITK filter)
269 GaussianBlurring2D<double> blur(2.0 * (grid._dx > grid._dy ? grid._dx : grid._dy));
270 blur.SetInput (&dv);
271 blur.SetOutput(&dv);
272 blur.Run();
273 // Update velocities using the Baker-Campbell-Hausdorff (BCH) formula
274 for (int k = 0; k < grid._z; k++) {
275 for (int j = 0; j < grid._y; j++) {
276 for (int i = 0; i < grid._x; i++) {
277 vx = v (i, j, k, 0);
278 vy = v (i, j, k, 1);
279 vz = v (i, j, k, 2);
280 dvx = dv(i, j, k, 0);
281 dvy = dv(i, j, k, 1);
282 dvz = dv(i, j, k, 2);
283 // Lie bracket [v, dv]
284 #if 1
285 jac(Jv, v, i, j, k);
286 jac(Jdv, dv, i, j, k);
287 lx = dvx * Jv(0, 0) - vx * Jdv(0, 0) +
288 dvy * Jv(0, 1) - vy * Jdv(0, 1) +
289 dvz * Jv(0, 2) - vz * Jdv(0, 2);
290 ly = dvx * Jv(1, 0) - vx * Jdv(1, 0) +
291 dvy * Jv(1, 1) - vy * Jdv(1, 1) +
292 dvz * Jv(1, 2) - vz * Jdv(1, 2);
293 lz = dvx * Jv(2, 0) - vx * Jdv(2, 0) +
294 dvy * Jv(2, 1) - vy * Jdv(2, 1) +
295 dvz * Jv(2, 2) - vz * Jdv(2, 2);
296 dvx += 0.5 * lx;
297 dvy += 0.5 * ly;
298 dvz += 0.5 * lz;
299 #endif
300 // BCH update step
301 vn(i, j, k, 0) = vx + dvx;
302 vn(i, j, k, 1) = vy + dvy;
303 vn(i, j, k, 2) = vz + dvz;
304 }
305 }
306 }
307 // Set velocities to updated ones
308 v = vn;
309 }
310
311 // The stationary velocity field v = log(Phi) is now computed.
312 // In the following, the dense vector field is approximated/interpolated
313 // by a 3D B-spline FFD with a control point at each grid point (voxel).
314
315 // Store integrated displacements in [rx, ry, rz] while also calculating
316 // the RMS error of the resulting (dense) displacement field.
317
318 double *rx = new double[n];
319 double *ry = new double[n];
320 double *rz = new double[n];
321
322 avg_error = 0;
323 max_error = 0;
324 p = 0;
325 for (int k = 0; k < grid._z; k++) {
326 for (int j = 0; j < grid._y; j++) {
327 for (int i = 0; i < grid._x; i++) {
328 x1 = i;
329 y1 = j;
330 z1 = k;
331 v.ImageToWorld(x1, y1, z1);
332 x2 = x1;
333 y2 = y1;
334 z2 = z1;
335 expEuler(v, x2, y2, z2, false);
336 rx[p] = x2 - x1;
337 ry[p] = y2 - y1;
338 rz[p] = z2 - z1;
339 error = sqrt(pow(rx[p] - phi(i, j, k, 0), 2) +
340 pow(ry[p] - phi(i, j, k, 1), 2) +
341 pow(rz[p] - phi(i, j, k, 2), 2));
342 if (max_error < error) max_error = error;
343 avg_error += error;
344 p++;
345 }
346 }
347 }
348 avg_error /= static_cast<double>(n);
349
350 cout << "Average RMS error SV (grid, expEuler): " << avg_error << endl;
351 cout << "Maximum RMS error SV (grid, expEuler): " << max_error << endl;
352 cout.flush();
353
354 // Now approximate a B-spline FFD from the given displacements
355 BSplineFreeFormTransformation3D ffd(const_cast<ImageAttributes&>(grid), grid._dx, grid._dy, grid._dz);
356
357 double *tmpx = new double[n];
358 double *tmpy = new double[n];
359 double *tmpz = new double[n];
360
361 memcpy(tmpx, rx, sizeof(double) * n);
362 memcpy(tmpy, ry, sizeof(double) * n);
363 memcpy(tmpz, rz, sizeof(double) * n);
364
365 ffd.ApproximateAsNew(x, y, z, tmpx, tmpy, tmpz, n);
366 ffd.Write("test1-vffd1.dof.gz");
367
368 // Error of the approximation, thus compare FFD displacement to
369 // displacement [rx, ry, rz] which was used as input to ApproximateAsNew()
370 avg_error = 0;
371 max_error = 0;
372 for (int k = 2; k < grid._z - 2; k++) {
373 for (int j = 2; j < grid._y - 2; j++) {
374 for (int i = 2; i < grid._x - 2; i++) {
375 x1 = i;
376 y1 = j;
377 z1 = k;
378 v.ImageToWorld(x1, y1, z1);
379 x2 = x1;
380 y2 = y1;
381 z2 = z1;
382 ffd.Transform(x2, y2, z2);
383 p = k * grid._x * grid._y + j * grid._x + i;
384 error = sqrt(pow((x2 - x1) - rx[p], 2) +
385 pow((y2 - y1) - ry[p], 2) +
386 pow((z2 - z1) - rz[p], 2));
387 if (max_error < error) max_error = error;
388 avg_error += error;
389 p++;
390 }
391 }
392 }
393 avg_error /= static_cast<double>(n);
394
395 cout << "Average RMS error SV (grid, approx. FFD): " << avg_error << endl;
396 cout << "Maximum RMS error SV (grid, approx. FFD): " << max_error << endl;
397 cout.flush();
398
399 // Error of the approximated FFD relative to the initial displacement Phi
400 avg_error = 0;
401 max_error = 0;
402 for (int k = 2; k < grid._z - 2; k++) {
403 for (int j = 2; j < grid._y - 2; j++) {
404 for (int i = 2; i < grid._x - 2; i++) {
405 x1 = i;
406 y1 = j;
407 z1 = k;
408 v.ImageToWorld(x1, y1, z1);
409 x2 = x1;
410 y2 = y1;
411 z2 = z1;
412 ffd.Transform(x2, y2, z2);
413 error = sqrt(pow((x2 - x1) - phi(i, j, k, 0), 2) +
414 pow((y2 - y1) - phi(i, j, k, 1), 2) +
415 pow((z2 - z1) - phi(i, j, k, 2), 2));
416 if (max_error < error) max_error = error;
417 avg_error += error;
418 p++;
419 }
420 }
421 }
422 avg_error /= static_cast<double>(n);
423
424 cout << "Average RMS error SV (grid, expEuler, approx. FFD): " << avg_error << endl;
425 cout << "Maximum RMS error SV (grid, expEuler, approx. FFD): " << max_error << endl;
426 cout.flush();
427
428 memcpy(tmpx, rx, sizeof(double) * n);
429 memcpy(tmpy, ry, sizeof(double) * n);
430 memcpy(tmpz, rz, sizeof(double) * n);
431
432 // Now do the same using the B-spline interpolation instead
433 ffd.Interpolate(tmpx, tmpy, tmpz);
434 ffd.Write("test1-vffd2.dof.gz");
435
436 avg_error = 0;
437 max_error = 0;
438 for (int k = 2; k < grid._z - 2; k++) {
439 for (int j = 2; j < grid._y - 2; j++) {
440 for (int i = 2; i < grid._x - 2; i++) {
441 x1 = i;
442 y1 = j;
443 z1 = k;
444 v.ImageToWorld(x1, y1, z1);
445 x2 = x1;
446 y2 = y1;
447 z2 = z1;
448 ffd.Transform(x2, y2, z2);
449 p = k * grid._x * grid._y + j * grid._x + i;
450 error = sqrt(pow((x2 - x1) - rx[p], 2) +
451 pow((y2 - y1) - ry[p], 2) +
452 pow((z2 - z1) - rz[p], 2));
453 if (max_error < error) max_error = error;
454 avg_error += error;
455 p++;
456 }
457 }
458 }
459 avg_error /= static_cast<double>(n);
460
461 cout << "Average RMS error SV (grid, interp. FFD): " << avg_error << endl;
462 cout << "Maximum RMS error SV (grid, interp. FFD): " << max_error << endl;
463 cout.flush();
464
465 avg_error = 0;
466 max_error = 0;
467 for (int k = 2; k < grid._z - 2; k++) {
468 for (int j = 2; j < grid._y - 2; j++) {
469 for (int i = 2; i < grid._x - 2; i++) {
470 x1 = i;
471 y1 = j;
472 z1 = k;
473 v.ImageToWorld(x1, y1, z1);
474 x2 = x1;
475 y2 = y1;
476 z2 = z1;
477 ffd.Transform(x2, y2, z2);
478 error = sqrt(pow((x2 - x1) - phi(i, j, k, 0), 2) +
479 pow((y2 - y1) - phi(i, j, k, 1), 2) +
480 pow((z2 - z1) - phi(i, j, k, 2), 2));
481 if (max_error < error) max_error = error;
482 avg_error += error;
483 p++;
484 }
485 }
486 }
487 avg_error /= static_cast<double>(n);
488
489 cout << "Average RMS error SV (grid, expEuler, interp. FFD): " << avg_error << endl;
490 cout << "Maximum RMS error SV (grid, expEuler, interp. FFD): " << max_error << endl;
491 cout.flush();
492
493 delete[] tmpx;
494 delete[] tmpy;
495 delete[] tmpz;
496
497 delete[] rx;
498 delete[] ry;
499 delete[] rz;
500 }
501
502 // ===========================================================================
503 // Main
504 // ===========================================================================
505
DisplacementToVelocityFieldTest(int ac,char * av[])506 int DisplacementToVelocityFieldTest(int ac, char *av[])
507 {
508 // extract/discard program name
509 ac--;
510 av++;
511
512 // parse arguments
513 char *dofin = NULL;
514 char *dofout = NULL;
515 double std = 2.0;
516 double max = 5.0;
517
518 ImageAttributes grid;
519 grid._x = 64;
520 grid._y = 64;
521 grid._z = 1;
522 grid._dx = 1.0;
523 grid._dy = 1.0;
524 grid._dz = 1.0;
525
526 for (int i = 0; i < ac; i++) {
527 char *opt = av[i]; // option/flag name
528 char *arg = av[i+1]; // argument for option
529 bool flag = false; // whether option has no argument
530
531 if (strcmp(opt, "-dofin") == 0) {
532 dofin = arg;
533 } else if (strcmp(opt, "-dofout") == 0) {
534 dofout = arg;
535 } else if (strcmp(opt, "-std") == 0) {
536 std = atof(arg);
537 } else if (strcmp(opt, "-max") == 0) {
538 max = atof(arg);
539 } else if (strcmp(opt, "-x") == 0) {
540 grid._x = atoi(arg);
541 } else if (strcmp(opt, "-y") == 0) {
542 grid._y = atoi(arg);
543 } else if (strcmp(opt, "-z") == 0) {
544 grid._z = atoi(arg);
545 } else if (strcmp(opt, "-dx") == 0) {
546 grid._dx = atoi(arg);
547 } else if (strcmp(opt, "-dy") == 0) {
548 grid._dy = atoi(arg);
549 } else if (strcmp(opt, "-dz") == 0) {
550 grid._dz = atoi(arg);
551 } else {
552 cerr << "Unknown option: " << opt << endl;
553 exit(1);
554 }
555
556 if (!flag) i++; // skip option argument
557 }
558
559 // initial displacement field
560 double *x, *y, *z, *dx, *dy, *dz;
561 init(dofin, grid, std, max, x, y, z, dx, dy, dz);
562
563 ImageAttributes attr = grid; attr._t = 3;
564 GenericImage<double> din(attr);
565
566 int p = 0;
567 for (int k = 0; k < grid._z; k++) {
568 for (int j = 0; j < grid._y; j++) {
569 for (int i = 0; i < grid._x; i++) {
570 din(i, j, k, 0) = dx[p];
571 din(i, j, k, 1) = dy[p];
572 din(i, j, k, 2) = dz[p];
573 p++;
574 }
575 }
576 }
577
578 DisplacementToVelocityFieldBCH<double> dtov;
579 GenericImage<double> v;
580
581 dtov.SetInput (&din);
582 dtov.SetOutput(&v);
583
584 dtov.Run();
585
586 VelocityToDisplacementFieldEuler<double> vtod;
587 GenericImage<double> dout;
588
589 vtod.SetInput (&v);
590 vtod.SetOutput(&dout);
591
592 vtod.Run();
593
594 if (dofout != NULL) {
595 if (strstr(dofout, ".dof") != NULL) {
596 MultiLevelFreeFormTransformation mffd;
597 FreeFormTransformation3D *ffd = new LinearFreeFormTransformation(dout, grid._dx, grid._dy, grid._dz);
598 ffd->Interpolate(dout.GetPointerToVoxels(0, 0, 0, 0),
599 dout.GetPointerToVoxels(0, 0, 0, 1),
600 dout.GetPointerToVoxels(0, 0, 0, 2));
601 mffd.PushLocalTransformation(ffd);
602 mffd.Write(dofout);
603 } else {
604 dout.Write(dofout);
605 }
606 }
607
608 // perform tests
609 //testLogWithExpEuler(grid, x, y, z, dx, dy, dz);
610
611 // clean up
612 delete[] x;
613 delete[] y;
614 delete[] z;
615 delete[] dx;
616 delete[] dy;
617 delete[] dz;
618
619 exit(0);
620 }
621