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