1 /*
2  * Medical Image Registration ToolKit (MIRTK)
3  *
4  * Copyright 2008-2015 Imperial College London
5  * Copyright 2008-2013 Daniel Rueckert, Julia Schnabel
6  * Copyright 2013-2015 Andreas Schuh
7  *
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *     http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */
20 
21 #include "mirtk/Common.h"
22 #include "mirtk/Options.h"
23 
24 #include "mirtk/PointSet.h"
25 #include "mirtk/BaseImage.h"
26 #include "mirtk/IOConfig.h"
27 
28 using namespace mirtk;
29 
30 
31 // =============================================================================
32 // Help
33 // =============================================================================
34 
35 // -----------------------------------------------------------------------------
PrintHelp(const char * name)36 void PrintHelp(const char *name)
37 {
38   cout << "\n";
39   cout << "Usage: " << name << " <input> <output> [options]\n";
40   cout << "\n";
41   cout << "Description:" << endl;
42   cout << "  Crop/pad image by extracting a region of interest and optionally split\n";
43   cout << "  the extracted region into separate image files, e.g., individual slices\n";
44   cout << "  of a volume saved as individual image files. The output image region\n";
45   cout << "  is chosen such that it contains the union of all specified axis-aligned\n";
46   cout << "  rectangular input image regions. In case of :option:`-pad`, the output\n";
47   cout << "  region does not have to be fully contained within the input image.\n";
48   cout << "  Values outside are then set to the specified padding value.\n";
49   cout << "\n";
50   cout << "Positional arguments:\n";
51   cout << "  input    Input image file path.\n";
52   cout << "  output   Output file path.\n";
53   cout << "\n";
54   cout << "Optional arguments:\n";
55   cout << "  -Rx1 <int>\n";
56   cout << "      Leftmost input voxel index along x axis.\n";
57   cout << "  -Rx2 <int>\n";
58   cout << "      Rightmost input voxel index along x axis.\n";
59   cout << "  -Ry1 <int>\n";
60   cout << "      Leftmost input voxel index along y axis.\n";
61   cout << "  -Ry2 <int>\n";
62   cout << "      Rightmost input voxel index along y axis.\n";
63   cout << "  -Rz1 <int>\n";
64   cout << "      Leftmost input voxel index along z axis.\n";
65   cout << "  -Rz2 <int>\n";
66   cout << "      Rightmost input voxel index along z axis.\n";
67   cout << "  -Rt1 <int>\n";
68   cout << "      Leftmost input voxel index along t axis.\n";
69   cout << "  -Rt2 <int>\n";
70   cout << "      Rightmost input voxel index along t axis.\n";
71   cout << "  -patch <i> <j> <k> <nx> [<ny> [<nz>]]\n";
72   cout << "      Extract image patch of given size centered at the specified voxel.\n";
73   cout << "  -closest-patch <x> <y> <z> <nx> [<ny> [<nz>]]\n";
74   cout << "      Extract image patch of given size centered at nearest voxel to specified point [mm].\n";
75   cout << "  -landmarks <file>...\n";
76   cout << "      Extract minimum bounding box containing the landmark points.\n";
77   cout << "  -ref <file>\n";
78   cout << "      Extract region specified by discrete reference image domain.\n";
79   cout << "  -margin <int>...\n";
80   cout << "      Add fixed-width margin to union of image regions. (default: 0)\n";
81   cout << "  -margin-mm <float>...\n";
82   cout << "      Add fixed-width margin in mm to union of image regions. (default: 0)\n";
83   cout << "  -scale <float>...\n";
84   cout << "      Scale resulting region by specified factor. (default: 1)\n";
85   cout << "  -crop [value]\n";
86   cout << "      Crop background with intensity below or equal specified value. (default: 0)\n";
87   cout << "  -pad [value]\n";
88   cout << "      Pad output image by the specified value. (default: 0)\n";
89   cout << "  -split <dim>|x|y|z|t\n";
90   cout << "      Split extracted region along specified dimension into separate images.\n";
91   cout << "      For example, use '-split z' to save individual slices of a 3D volume.\n";
92   cout << "      The <output> file path is appended with a format string '_%03d' before\n";
93   cout << "      the file name extension to create unique output file paths, unless such\n";
94   cout << "      format specification is part of the given file path already.\n";
95   cout << "  -swap [on|off]\n";
96   cout << "      When :option:`-split` is used, first swap the dimension along which the\n";
97   cout << "      extracted region is split with the last dimension such that when splitting\n";
98   cout << "      a volume along the first dimension, the output image files have z=1. (default: off)\n";
99   cout << "  -noswap\n";
100   cout << "      Set :option:`-swap` to off.\n";
101   PrintStandardOptions(cout);
102   cout << endl;
103 }
104 
105 // =============================================================================
106 // Auxiliaries
107 // =============================================================================
108 
109 const int MIN_INDEX = numeric_limits<int>::min();
110 const int MAX_INDEX = numeric_limits<int>::max();
111 
112 // -----------------------------------------------------------------------------
ToVoxelIndex(const char * arg)113 int ToVoxelIndex(const char *arg)
114 {
115   int i;
116   if (!FromString(arg, i)) {
117     cerr << "Invalid voxel index argument: " << arg << endl;
118     exit(1);
119   }
120   return i;
121 }
122 
123 // -----------------------------------------------------------------------------
ToRegionMargin(const char * arg)124 int ToRegionMargin(const char *arg)
125 {
126   int i;
127   if (!FromString(arg, i)) {
128     cerr << "Invalid region margin argument: " << arg << endl;
129     exit(1);
130   }
131   return i;
132 }
133 
134 // -----------------------------------------------------------------------------
ToScaleFactor(const char * arg)135 double ToScaleFactor(const char *arg)
136 {
137   double s;
138   if (!FromString(arg, s)) {
139     cerr << "Invalid scale factor argument: " << arg << endl;
140     exit(1);
141   }
142   return s;
143 }
144 
145 // -----------------------------------------------------------------------------
MinIndex(int i,int j)146 int MinIndex(int i, int j)
147 {
148   return (i <= MIN_INDEX ? j : min(i, j));
149 }
150 
151 // -----------------------------------------------------------------------------
MaxIndex(int i,int j)152 int MaxIndex(int i, int j)
153 {
154   return (i >= MAX_INDEX ? j : max(i, j));
155 }
156 
157 // -----------------------------------------------------------------------------
ScaleInterval(int & i,int & j,double s)158 void ScaleInterval(int &i, int &j, double s)
159 {
160   if (s == 1.0) return;
161   double m = (i + j) / 2.0;
162   double r = s * abs(j - i) / 2.0;
163   i = ifloor(m - r);
164   j = iceil (m + r);
165 }
166 
167 // =============================================================================
168 // Main
169 // =============================================================================
170 
171 // -----------------------------------------------------------------------------
main(int argc,char ** argv)172 int main(int argc, char **argv)
173 {
174   EXPECTS_POSARGS(2);
175 
176   const char *input_name  = POSARG(1);
177   const char *output_name = POSARG(2);
178 
179   // Read input image
180   InitializeIOLibrary();
181   UniquePtr<BaseImage> in(BaseImage::New(input_name));
182 
183   // Parse optional arguments and adjust image region accordingly
184   int    xmargin    =  0, ymargin    =  0, zmargin    =  0, tmargin    =  0;
185   double xmargin_mm = .0, ymargin_mm = .0, zmargin_mm = .0, tmargin_mm = .0;
186   double xscale     =  1, yscale     =  1, zscale     =  1, tscale     =  1;
187   bool   pad           = false;
188   double padding_value = .0;
189   int    split         = -1;
190   bool   swap_dims     = false;
191 
192   int x1 = MIN_INDEX, x2 = MAX_INDEX;
193   int y1 = MIN_INDEX, y2 = MAX_INDEX;
194   int z1 = MIN_INDEX, z2 = MAX_INDEX;
195   int t1 = MIN_INDEX, t2 = MAX_INDEX;
196 
197   for (ALL_OPTIONS) {
198     if      (OPTION("-Rx1")) x1 = MinIndex(x1, ToVoxelIndex(ARGUMENT));
199     else if (OPTION("-Rx2")) x2 = MaxIndex(x2, ToVoxelIndex(ARGUMENT));
200     else if (OPTION("-Ry1")) y1 = MinIndex(y1, ToVoxelIndex(ARGUMENT));
201     else if (OPTION("-Ry2")) y2 = MaxIndex(y2, ToVoxelIndex(ARGUMENT));
202     else if (OPTION("-Rz1")) z1 = MinIndex(z1, ToVoxelIndex(ARGUMENT));
203     else if (OPTION("-Rz2")) z2 = MaxIndex(z2, ToVoxelIndex(ARGUMENT));
204     else if (OPTION("-Rt1")) t1 = MinIndex(t1, ToVoxelIndex(ARGUMENT));
205     else if (OPTION("-Rt2")) t2 = MaxIndex(t2, ToVoxelIndex(ARGUMENT));
206     else if (OPTION("-patch")) {
207       int i, j, k, nx, ny, nz;
208       PARSE_ARGUMENT(i);
209       PARSE_ARGUMENT(j);
210       PARSE_ARGUMENT(k);
211       PARSE_ARGUMENT(nx);
212       if (HAS_ARGUMENT) {
213         PARSE_ARGUMENT(ny);
214         if (HAS_ARGUMENT) PARSE_ARGUMENT(nz);
215         else              nz = 1;
216       } else ny = nz = nx;
217       int rx = (nx - 1) / 2;
218       int ry = (ny - 1) / 2;
219       int rz = (nz - 1) / 2;
220       x1 = i - rx, x2 = i + rx;
221       y1 = j - ry, y2 = j + ry;
222       z1 = k - rz, z2 = k + rz;
223     }
224     else if (OPTION("-closest-patch")) {
225       double x, y, z;
226       PARSE_ARGUMENT(x);
227       PARSE_ARGUMENT(y);
228       PARSE_ARGUMENT(z);
229       int nx, ny, nz;
230       PARSE_ARGUMENT(nx);
231       if (HAS_ARGUMENT) {
232         PARSE_ARGUMENT(ny);
233         if (HAS_ARGUMENT) PARSE_ARGUMENT(nz);
234         else              nz = 1;
235       } else ny = nz = nx;
236       in->WorldToImage(x, y, z);
237       int i  = iround(x);
238       int j  = iround(y);
239       int k  = iround(z);
240       int rx = (nx - 1) / 2;
241       int ry = (ny - 1) / 2;
242       int rz = (nz - 1) / 2;
243       x1 = i - rx, x2 = i + rx;
244       y1 = j - ry, y2 = j + ry;
245       z1 = k - rz, z2 = k + rz;
246     }
247     else if (OPTION("-landmarks") || OPTION("-points") || OPTION("-poly")) {
248       PointSet landmarks;
249       do {
250         landmarks.Read(ARGUMENT);
251         for (int i = 0; i < landmarks.Size(); ++i) {
252           Point &p = landmarks(i);
253           in->WorldToImage(p);
254           x1 = MinIndex(x1, ifloor(p._x));
255           x2 = MaxIndex(x2, iceil (p._x));
256           y1 = MinIndex(y1, ifloor(p._y));
257           y2 = MaxIndex(y2, iceil (p._y));
258           z1 = MinIndex(z1, ifloor(p._z));
259           z2 = MaxIndex(z2, iceil (p._z));
260         }
261       } while (HAS_ARGUMENT);
262     }
263     else if (OPTION("-ref")) {
264       do {
265         BinaryImage ref(ARGUMENT);
266         double y, z;
267         Point p;
268         for (int k = 0; k < 2; ++k) {
269           z = k * ref.Z();
270           for (int j = 0; j < 2; ++j) {
271             y = j * ref.Y();
272             for (int i = 0; i < 2; ++i) {
273               p._x = i * ref.X(), p._y = y, p._z = z;
274               ref.ImageToWorld(p);
275               in->WorldToImage(p);
276               x1 = MinIndex(x1, ifloor(p._x));
277               x2 = MaxIndex(x2, iceil (p._x));
278               y1 = MinIndex(y1, ifloor(p._y));
279               y2 = MaxIndex(y2, iceil (p._y));
280               z1 = MinIndex(z1, ifloor(p._z));
281               z2 = MaxIndex(z2, iceil (p._z));
282             }
283           }
284         }
285       } while (HAS_ARGUMENT);
286     }
287     else if (OPTION("-margin")) {
288       xmargin = ToRegionMargin(ARGUMENT);
289       if (HAS_ARGUMENT) {
290         ymargin = ToRegionMargin(ARGUMENT);
291         zmargin = tmargin = 0;
292         if (HAS_ARGUMENT) zmargin = ToRegionMargin(ARGUMENT);
293         if (HAS_ARGUMENT) tmargin = ToRegionMargin(ARGUMENT);
294       } else {
295         ymargin = zmargin = tmargin = xmargin;
296       }
297     }
298     else if (OPTION("-margin-mm")) {
299       PARSE_ARGUMENT(xmargin_mm);
300       if (HAS_ARGUMENT) {
301         PARSE_ARGUMENT(ymargin_mm);
302         zmargin_mm = tmargin_mm = .0;
303         if (HAS_ARGUMENT) PARSE_ARGUMENT(zmargin_mm);
304         if (HAS_ARGUMENT) PARSE_ARGUMENT(tmargin_mm);
305       } else {
306         ymargin_mm = zmargin_mm = tmargin_mm = xmargin_mm;
307       }
308     }
309     else if (OPTION("-scale")) {
310       xscale = ToScaleFactor(ARGUMENT);
311       if (HAS_ARGUMENT) {
312         yscale = ToScaleFactor(ARGUMENT);
313         zscale = tscale = 1.0;
314         if (HAS_ARGUMENT) zscale = ToScaleFactor(ARGUMENT);
315         if (HAS_ARGUMENT) tscale = ToScaleFactor(ARGUMENT);
316       } else {
317         yscale = zscale = tscale = xscale;
318       }
319     }
320     else if (OPTION("-crop")) {
321       double bg = padding_value;
322       if (HAS_ARGUMENT) PARSE_ARGUMENT(bg);
323       auto bbox = in->ForegroundDomain(bg, false);
324       Point p(bbox._xorigin, bbox._yorigin, bbox._zorigin);
325       in->WorldToImage(p);
326       Point p1 = p - Point(bbox._x / 2, bbox._y / 2, bbox._z / 2);
327       Point p2 = p + Point(bbox._x / 2, bbox._y / 2, bbox._z / 2);
328       x1 = MinIndex(x1, ifloor(p1._x));
329       x2 = MaxIndex(x2, iceil (p2._x));
330       y1 = MinIndex(y1, ifloor(p1._y));
331       y2 = MaxIndex(y2, iceil (p2._y));
332       z1 = MinIndex(z1, ifloor(p1._z));
333       z2 = MaxIndex(z2, iceil (p2._z));
334     }
335     else if (OPTION("-pad")) {
336       pad = true;
337       if (HAS_ARGUMENT) PARSE_ARGUMENT(padding_value);
338     }
339     else if (OPTION("-nopad")) {
340       pad = false;
341     }
342     else if (OPTION("-split")) {
343       const string arg = ToLower(ARGUMENT);
344       if      (arg == "x") split = 0;
345       else if (arg == "y") split = 1;
346       else if (arg == "z") split = 2;
347       else if (arg == "t") split = 3;
348       else if (!FromString(arg, split) || split < 0 || split > 3) {
349         FatalError("Invalid -split option argument: " << arg);
350       }
351     }
352     else HANDLE_BOOLEAN_OPTION("swap", swap_dims);
353     else HANDLE_COMMON_OR_UNKNOWN_OPTION();
354   }
355 
356   // Full input image region by default
357   if (x1 <= MIN_INDEX) x1 = 0;
358   if (x2 >= MAX_INDEX) x2 = in->X() - 1;
359   if (y1 <= MIN_INDEX) y1 = 0;
360   if (y2 >= MAX_INDEX) y2 = in->Y() - 1;
361   if (z1 <= MIN_INDEX) z1 = 0;
362   if (z2 >= MAX_INDEX) z2 = in->Z() - 1;
363   if (t1 <= MIN_INDEX) t1 = 0;
364   if (t2 >= MAX_INDEX) t2 = in->T() - 1;
365 
366   // Add fixed-width margin
367   xmargin = max(xmargin, iceil(xmargin_mm / in->GetXSize()));
368   ymargin = max(ymargin, iceil(ymargin_mm / in->GetYSize()));
369   zmargin = max(zmargin, iceil(zmargin_mm / in->GetZSize()));
370   tmargin = max(tmargin, iceil(tmargin_mm / in->GetTSize()));
371 
372   x1 -= xmargin, x2 += xmargin;
373   y1 -= ymargin, y2 += ymargin;
374   z1 -= zmargin, z2 += zmargin;
375   t1 -= tmargin, t2 += tmargin;
376 
377   // Swap indices if necessary
378   if (x1 > x2) swap(x1, x2);
379   if (y1 > y2) swap(y1, y2);
380   if (z1 > z2) swap(z1, z2);
381   if (t1 > t2) swap(t1, t2);
382 
383   // Scale region
384   ScaleInterval(x1, x2, xscale);
385   ScaleInterval(y1, y2, yscale);
386   ScaleInterval(z1, z2, zscale);
387   ScaleInterval(t1, t2, tscale);
388 
389   // Limit to extend of input region if padding not requested
390   if (!pad) {
391     x1 = max(x1, 0);
392     y1 = max(y1, 0);
393     z1 = max(z1, 0);
394     t1 = max(t1, 0);
395     x2 = min(x2, in->X() - 1);
396     y2 = min(y2, in->Y() - 1);
397     z2 = min(z2, in->Z() - 1);
398     t2 = min(t2, in->T() - 1);
399   }
400 
401   // Verbose reporting/logging of resulting region
402   if (verbose) {
403     cout <<  "-Rx1 " << x1 << " -Rx2 " << x2 << " -Ry1 " << y1 << " -Ry2 " << y2;
404     cout << " -Rz1 " << z1 << " -Rz2 " << z2 << " -Rt1 " << t1 << " -Rt2 " << t2;
405     cout << endl;
406   }
407 
408   // Allocate output image
409   ImageAttributes attr = in->Attributes();
410   attr._x = abs(x2 - x1 + 1);
411   attr._y = abs(y2 - y1 + 1);
412   attr._z = abs(z2 - z1 + 1);
413   attr._t = abs(t2 - t1 + 1);
414   attr._xorigin = .0;
415   attr._yorigin = .0;
416   attr._zorigin = .0;
417   attr._torigin = in->ImageToTime(t1);
418   UniquePtr<BaseImage> out(BaseImage::New(in->GetDataType()));
419   out->Initialize(attr);
420 
421   // Adjust spatial origin (i.e., image center)
422   double o1[3] = {.0};
423   double o2[3] = {static_cast<double>(x1), static_cast<double>(y1), static_cast<double>(z1)};
424   out->ImageToWorld(o1[0], o1[1], o1[2]);
425   in ->ImageToWorld(o2[0], o2[1], o2[2]);
426   out->PutOrigin(o2[0] - o1[0], o2[1] - o1[1], o2[2] - o1[2]);
427 
428   // Copy image region
429   int idx = 0;
430   for (int l = t1; l <= t2; ++l)
431   for (int k = z1; k <= z2; ++k)
432   for (int j = y1; j <= y2; ++j)
433   for (int i = x1; i <= x2; ++i, ++idx) {
434     if (in->IsInside(i, j, k, l)) {
435       out->PutAsDouble(idx, in->Get(i, j, k, l));
436     } else {
437       out->PutAsDouble(idx, padding_value);
438     }
439   }
440 
441   // Write output region
442   if (split == -1) {
443     out->Write(output_name);
444   } else {
445     // Format string for output file paths
446     string output_path(output_name);
447     auto pos = output_path.find('%');
448     auto end = pos;
449     if (pos != string::npos) {
450       end = output_path.find('d', pos);
451     }
452     if (pos == end) {
453       const int dims[] = {out->X(), out->Y(), out->Z(), out->T()};
454       auto name = FilePrefix(output_path);
455       auto ext  = Extension(output_path);
456       string fmt = "%03d";
457       if      (dims[split] <  10) fmt = "%d";
458       else if (dims[split] < 100) fmt = "%02d";
459       else if (dims[split] > 999) fmt = "%04d";
460       output_path = name + string("_") + fmt + ext;
461     }
462     // Buffer for output file paths
463     size_t max_image_path_length = output_path.length() + 10;
464     UniquePtr<char> image_path(new char[max_image_path_length + 1]);
465     // Swap image dimensions
466     if (swap_dims && split < 2) {
467       if (split == 0) out->SwapXY();
468       if (split <= 1) out->SwapYZ();
469       split = 2;
470     }
471     // Allocate memory for sub-regions
472     double origin[3];
473     int dims[] = {out->X(), out->Y(), out->Z(), out->T()};
474     auto attr = out->Attributes();
475     if      (split == 0) attr._x = 1;
476     else if (split == 1) attr._y = 1;
477     else if (split == 2) attr._z = 1;
478     else if (split == 3) attr._t = 1;
479     UniquePtr<BaseImage> sub(BaseImage::New(out->GetDataType()));
480     sub->Initialize(attr);
481     // Save individual sub-regions
482     int outidx[4], subidx[4];
483     int perm[] = {0, 1, 2, 3};  // permutation of for loops
484     if (split == 0) swap(perm[0], perm[1]);
485     if (split <= 1) swap(perm[1], perm[2]);
486     if (split <= 2) swap(perm[2], perm[3]);
487     for (outidx[perm[3]] = 0; outidx[perm[3]] < dims[perm[3]]; ++outidx[perm[3]]) {
488       // Set origin of sub-region image
489       if (split < 3) {
490         origin[0] = .5 * static_cast<double>(dims[0] - 1);
491         origin[1] = .5 * static_cast<double>(dims[1] - 1);
492         origin[2] = .5 * static_cast<double>(dims[2] - 1);
493         origin[split] = static_cast<double>(outidx[perm[3]]);
494         out->ImageToWorld(origin[0], origin[1], origin[2]);
495         sub->PutOrigin(origin[0], origin[1], origin[2]);
496       } else {
497         sub->PutTOrigin(out->ImageToTime(outidx[perm[3]]));
498       }
499       // Copy sub-region to output image
500       double value;
501       for (outidx[perm[2]] = 0; outidx[perm[2]] < dims[perm[2]]; ++outidx[perm[2]])
502       for (outidx[perm[1]] = 0; outidx[perm[1]] < dims[perm[1]]; ++outidx[perm[1]])
503       for (outidx[perm[0]] = 0; outidx[perm[0]] < dims[perm[0]]; ++outidx[perm[0]]) {
504         value = out->GetAsDouble(outidx[0], outidx[1], outidx[2], outidx[3]);
505         memcpy(subidx, outidx, 4 * sizeof(int));
506         subidx[split] = 0;
507         sub->PutAsDouble(subidx[0], subidx[1], subidx[2], subidx[3], value);
508       }
509       // Write sub-region to output image
510       snprintf(image_path.get(), max_image_path_length, output_path.c_str(), outidx[perm[3]]);
511       sub->Write(image_path.get());
512 
513     }
514   }
515 
516   return 0;
517 }
518