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