1 /*
2 * Medical Image Registration ToolKit (MIRTK)
3 *
4 * Copyright 2017 Imperial College London
5 * Copyright 2017 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/Common.h"
21 #include "mirtk/Options.h"
22
23 #include "mirtk/IOConfig.h"
24
25 #include "mirtk/GenericImage.h"
26 #include "mirtk/DataStatistics.h"
27 #include "mirtk/Histogram1D.h"
28
29 #include <functional>
30
31 using namespace mirtk;
32 using namespace mirtk::data::statistic;
33
34
35 // =============================================================================
36 // Help
37 // =============================================================================
38
39 /// Print program usage information
PrintHelp(const char * name)40 void PrintHelp(const char* name)
41 {
42 cout << "\n";
43 cout << "Usage: " << name << " <mode> <image> <image>... -output <file> [options]\n";
44 cout << "\n";
45 cout << "Description:\n";
46 cout << " Aggregates multiple (co-registered) input images into a single output image\n";
47 cout << " or report statistics thereof within a specified region of interest.\n";
48 cout << " The input images have to be defined in the same discrete finite image space.\n";
49 cout << "\n";
50 cout << "Required arguments:\n";
51 cout << " <mode>\n";
52 cout << " Name of function used to aggregate input values:\n";
53 cout << " - ``mu``, ``mean``, ``average``, ``avg``: Mean intensity.\n";
54 cout << " - ``sd``, ``stdev``, ``stddev``, ``sigma``: Standard deviation.\n";
55 cout << " - ``gini``, ``gini-coefficient``: Gini coefficient in [0, 1].\n";
56 cout << " - ``theil``, ``theil-index``: Theil index, equivalent to GE(1).\n";
57 cout << " - ``entropy-index``, ``ge``: Generalized entropy index (GE), see also :option:`-alpha`.\"\n";
58 cout << " - ``entropy``: Shannon entropy, see also :option:`-bins` and :option:`-parzen`.\n";
59 cout << " - ``mode``, ``majority``: Smallest modal value, can also be used for majority voting of labels.\n";
60 cout << " - ``label-consistency``, ``overlap``: Mean Dice overlap of all pairs of labels.\n";
61 cout << " <image>\n";
62 cout << " File names of at least two input images.\n";
63 cout << " -output <file>\n";
64 cout << " Voxel-wise aggregate image.\n";
65 cout << "\n";
66 cout << "Optional arguments:\n";
67 cout << " -dtype char|uchar|short|ushort|int|uint|float|double\n";
68 cout << " Data type of output image. (default: float)\n";
69 cout << " -padding <value>\n";
70 cout << " Background value of voxels to be ignored during :option:`-normalization` (default: NaN).\n";
71 cout << " -normalization, -normalize <mode>\n";
72 cout << " Input intensity normalization:\n";
73 cout << " - ``none``: Use input intensities unmodified. (default)\n";
74 cout << " - ``mean``: Divide by mean foreground value.\n";
75 cout << " - ``median``: Divide by median foreground value.\n";
76 cout << " - ``z-score``: Subtract mean and divide by standard deviation.\n";
77 cout << " - ``unit``: Rescale input intensities to [0, 1].\n";
78 cout << " -rescale [<min>] <max>\n";
79 cout << " Rescale normalized intensities to specified range. When <min> not specified,\n";
80 cout << " it is set to zero, i.e., the range is [0, <max>]. Intensities are only rescaled\n";
81 cout << " when <min> is less than <max>. (default: off)\n";
82 cout << " -threshold <0-1>\n";
83 cout << " Percentage in [0, 1] of input images that must have a value not equal to the\n";
84 cout << " specified :option:`-padding` value. Otherwise, the output value is background. (default: 0)\n";
85 cout << " -alpha <value>\n";
86 cout << " Alpha value of the generalized entropy index, where alpha=0 is the mean log deviation, alpha=1\n";
87 cout << " is the Theil coefficient, and alpha=2 is half the squared coefficient of variation. (default: 0)\n";
88 cout << " -bins\n";
89 cout << " No. of bins used for histogram-based aggregation functions. (default: 64)\n";
90 cout << " -parzen [yes|no|on|off]\n";
91 cout << " Use Parzen window based histogram estimation. (default: off)\n";
92 cout << " -intersection [yes|no|on|off]\n";
93 cout << " Calculate aggregation function for every voxel for which no input value is\n";
94 cout << " equal the specified :option:`-padding` value. By default, only voxels for which\n";
95 cout << " all input values are equal to the :option:`-padding` value are excluded. (default: off)\n";
96 PrintCommonOptions(cout);
97 cout << endl;
98 }
99
100 // =============================================================================
101 // Types
102 // =============================================================================
103
104 // Type of input images
105 typedef float InputType;
106 typedef Array<InputType> InputArray;
107 typedef GenericImage<InputType> InputImage;
108 typedef Array<InputImage> InputImages;
109
110 // Type of output image
111 typedef float OutputType;
112 typedef GenericImage<OutputType> OutputImage;
113
114 // Type of intensity image normalization
115 enum NormalizationMode
116 {
117 Normalization_None, ///< Use input intensity values unmodified
118 Normalization_Mean, ///< Divide input image values by mean intensity
119 Normalization_Median, ///< Divide input image values by median intensity
120 Normalization_ZScore, ///< Subtract mean intensity and divide by standard deviation
121 Normalization_UnitRange ///< Rescale input intensities to [0, 1]
122 };
123
124 // Enumeration of implemented aggregation functions
125 enum AggregationMode
126 {
127 AM_Mean, ///< Mean value
128 AM_Median, ///< Median value
129 AM_StDev, ///< Standard deviation
130 AM_Variance, ///< Variance
131 AM_Gini, ///< Gini coefficient
132 AM_Theil, ///< Theil coefficient, i.e., GE(1)
133 AM_EntropyIndex, ///< Generalized entropy index (GE)
134 AM_Entropy, ///< Shannon entropy
135 AM_Mode, ///< Modal value (can also be used for segmentation labels)
136 AM_LabelConsistency ///< Label consistency / mean of all pairwise "overlaps"
137 };
138
139 // Type of functions used to aggregate set of values
140 typedef std::function<OutputType(InputArray &)> AggregationFunction;
141
142 // =============================================================================
143 // Auxiliary functions
144 // =============================================================================
145
146 // -----------------------------------------------------------------------------
147 /// Convert string to aggregation mode enumeration value
FromString(const char * str,AggregationMode & value)148 bool FromString(const char *str, AggregationMode &value)
149 {
150 const string lstr = ToLower(str);
151 if (lstr == "mean") {
152 value = AM_Mean;
153 } else if (lstr == "median") {
154 value = AM_Median;
155 } else if (lstr == "stddev" || lstr == "stdev" || lstr == "sdev" || lstr == "sd" || lstr == "sigma") {
156 value = AM_StDev;
157 } else if (lstr == "var" || lstr == "variance") {
158 value = AM_Variance;
159 } else if (lstr == "gini" || lstr == "gini-coefficient") {
160 value = AM_Gini;
161 } else if (lstr == "theil" || lstr == "theil-index") {
162 value = AM_Theil;
163 } else if (lstr == "entropy-index" || lstr == "ge" || lstr == "generalized-entropy-index") {
164 value = AM_EntropyIndex;
165 } else if (lstr == "entropy" || lstr == "shannon-entropy") {
166 value = AM_Entropy;
167 } else if (lstr == "mode" || lstr == "majority") {
168 value = AM_Mode;
169 } else if (lstr == "label-consistency" || lstr == "overlap") {
170 value = AM_LabelConsistency;
171 } else {
172 return false;
173 }
174 return true;
175 }
176
177 // -----------------------------------------------------------------------------
178 /// Get foreground mask for use with DataStatistic functions
ForegroundMaskArray(const InputImage & image)179 UniquePtr<bool[]> ForegroundMaskArray(const InputImage &image)
180 {
181 const int n = image.NumberOfVoxels();
182 UniquePtr<bool[]> mask(new bool[n]);
183 for (int i = 0; i < n; ++i) {
184 mask[i] = (!IsNaN(image.GetAsDouble(i)) && image.IsForeground(i));
185 }
186 return mask;
187 }
188
189 // -----------------------------------------------------------------------------
190 /// Normalize image intensities
Normalize(InputImage & image,NormalizationMode mode=Normalization_ZScore)191 void Normalize(InputImage &image, NormalizationMode mode = Normalization_ZScore)
192 {
193 if (mode == Normalization_None) return;
194
195 const auto mask = ForegroundMaskArray(image);
196 const int n = image.NumberOfVoxels();
197 auto * const data = image.Data();
198
199 double s = 1.;
200 double t = 0.;
201
202 switch (mode) {
203 case Normalization_Mean: {
204 double mean = Mean::Calculate(n, data, mask.get());
205 if (!fequal(mean, 0.)) s = 1. / mean;
206 } break;
207
208 case Normalization_Median: {
209 double median = Median::Calculate(n, data, mask.get());
210 if (!fequal(median, 0.)) s = 1. / median;
211 } break;
212
213 case Normalization_ZScore: {
214 double mean, sigma;
215 NormalDistribution::Calculate(mean, sigma, n, data, mask.get());
216 if (fequal(sigma, 0.)) {
217 t = - mean;
218 } else {
219 s = 1. / sigma;
220 t = - mean / sigma;
221 }
222 } break;
223
224 case Normalization_UnitRange: {
225 double min_value, max_value;
226 Extrema::Calculate(min_value, max_value, n, data, mask.get());
227 double range = max_value - min_value;
228 if (fequal(range, 0.)) {
229 t = - min_value;
230 } else {
231 s = 1. / range;
232 t = - min_value / range;
233 }
234 } break;
235
236 default: break;
237 };
238
239 if (!fequal(s, 1.) || !fequal(t, 0.)) {
240 auto p = data;
241 auto m = mask.get();
242 for (int i = 0; i < n; ++i, ++p, ++m) {
243 if (*m) (*p) = static_cast<InputType>(s * static_cast<double>(*p) + t);
244 }
245 }
246 }
247
248 // -----------------------------------------------------------------------------
249 /// Normalize image intensities
Normalize(InputImages & images,NormalizationMode mode=Normalization_ZScore)250 void Normalize(InputImages &images, NormalizationMode mode = Normalization_ZScore)
251 {
252 if (mode != Normalization_None) {
253 for (auto &&image : images) {
254 Normalize(image, mode);
255 }
256 }
257 }
258
259 // -----------------------------------------------------------------------------
260 /// Rescale intensities
Rescale(InputImage & image,double vmin,double vmax)261 void Rescale(InputImage &image, double vmin, double vmax)
262 {
263 double scale = 1., shift = 0.;
264 double min_value, max_value;
265
266 const auto mask = ForegroundMaskArray(image);
267 const int n = image.NumberOfVoxels();
268 auto *data = image.Data();
269
270 Extrema::Calculate(min_value, max_value, n, data, mask.get());
271 if (!fequal(max_value, min_value)) {
272 scale = (vmax - vmin) / (max_value - min_value);
273 }
274 shift = vmin - scale * min_value;
275 if (!fequal(scale, 1.) || !fequal(shift, 0.)) {
276 for (int idx = 0; idx < n; ++idx) {
277 if (mask[idx]) {
278 data[idx] = voxel_cast<InputType>(clamp(scale * static_cast<double>(data[idx]) + shift, vmin, vmax));
279 }
280 }
281 }
282 }
283
284 // -----------------------------------------------------------------------------
285 /// Rescale intensities
Rescale(InputImages & images,double vmin,double vmax)286 void Rescale(InputImages &images, double vmin, double vmax)
287 {
288 for (auto &&image : images) {
289 Rescale(image, vmin, vmax);
290 }
291 }
292
293 // -----------------------------------------------------------------------------
294 /// Determine intensity range
GetMinMax(const InputImages & images,InputType & min_value,InputType & max_value)295 void GetMinMax(const InputImages &images, InputType &min_value, InputType &max_value)
296 {
297 min_value = +numeric_limits<InputType>::infinity();
298 max_value = -numeric_limits<InputType>::infinity();
299 for (auto image : images) {
300 InputType min_val, max_val;
301 image.GetMinMax(min_val, max_val);
302 min_value = min(min_value, min_val);
303 max_value = max(max_value, max_val);
304 }
305 if (min_value > max_value) {
306 Throw(ERR_InvalidArgument, __FUNCTION__, "Input images are empty");
307 }
308 }
309
310 // -----------------------------------------------------------------------------
311 /// Aggregate values at each voxel
312 struct AggregateValuesAtEachVoxel
313 {
314 Array<InputImage> *_Images;
315 OutputImage *_Output;
316 AggregationFunction _Function;
317 InputType _Padding;
318 int _MinValues;
319
operator ()AggregateValuesAtEachVoxel320 void operator ()(const blocked_range<int> &voxels) const
321 {
322 int m;
323 InputType v;
324 InputArray values(_Images->size());
325 const int n = static_cast<int>(_Images->size());
326 for (int vox = voxels.begin(); vox < voxels.end(); ++vox) {
327 if (_Output->IsForeground(vox)) {
328 m = n;
329 for (int i = 0; i < n; ++i) {
330 v = (*_Images)[i].Get(vox);
331 if (IsNaN(v)) {
332 v = _Padding;
333 --m;
334 }
335 values[i] = v;
336 }
337 if (m >= _MinValues) {
338 _Output->Put(vox, _Function(values));
339 } else {
340 _Output->Put(vox, numeric_limits<InputType>::quiet_NaN());
341 }
342 }
343 }
344 }
345 };
346
347 // =============================================================================
348 // Measures of dispersion
349 // =============================================================================
350
351 // -----------------------------------------------------------------------------
352 /// Evaluate Gini coefficient of data samples
353 ///
354 /// \param[in,out] samples Sampled values of distribution.
355 /// \note Modifies the input sample values to be non-positive and sorts them ascending.
356 ///
357 /// \returns Gini coefficient in [0, 1], where the Gini coefficient is 0 when all sample
358 /// values are equal and close to 1 when a single value differs.
359 ///
360 /// \see http://neuroplausible.com/gini
361 /// \see http://www.ellipsix.net/blog/2012/11/the-gini-coefficient-for-distribution-inequality.html
362 template <class T>
GiniCoefficient(Array<T> & samples)363 double GiniCoefficient(Array<T> &samples)
364 {
365 double sum1 = 0., sum2 = 0.;
366 const int n = static_cast<int>(samples.size());
367 // Shift values to be all positive
368 T shift = MinElement(samples);
369 if (shift <= static_cast<T>(0)) {
370 if (numeric_limits<T>::is_integer) shift -= static_cast<T>(1);
371 else shift -= static_cast<T>(1e-6);
372 Transform(samples, bind(minus<T>(), std::placeholders::_1, shift));
373 }
374 Sort(samples);
375 for (int i = 0; i < n; ++i) {
376 // Note: rank i is zero-based, hence 2 * (i + 1) - n - 1 = 2 * i + 2 - n - 1
377 sum1 += static_cast<double>(2 * i - n + 1) * static_cast<double>(samples[i]);
378 sum2 += samples[i];
379 }
380 return sum1 / (n * sum2);
381 }
382
383 // -----------------------------------------------------------------------------
384 /// Evaluate general entropy index
385 ///
386 /// \param[in] samples Sampled values of distribution.
387 /// \param[in] alpha Weight given to distances between values at different
388 /// parts of the distribution. For alpha = 0, the entropy
389 /// index is equal the mean log deviation. For alpha = 1,
390 /// it is equal the Theil index. For alpha = 2, it is
391 /// half the squared coefficient of variation (i.e, the
392 /// standard deviation divided by the mean value).
393 ///
394 /// \note Modifies the input sample values to be non-positive.
395 ///
396 /// \see https://en.wikipedia.org/wiki/Generalized_entropy_index
397 /// \see https://en.wikipedia.org/wiki/Theil_index
398 template <class T>
EntropyIndex(Array<T> & samples,int alpha=1)399 double EntropyIndex(Array<T> &samples, int alpha = 1)
400 {
401 if (alpha < 0) {
402 Throw(ERR_InvalidArgument, __FUNCTION__, "alpha must be non-negative");
403 }
404 const int n = static_cast<int>(samples.size());
405 if (n == 0) return 0.;
406 // Shift values to be all positive
407 T shift = MinElement(samples);
408 if (shift <= static_cast<T>(0)) {
409 if (numeric_limits<T>::is_integer) shift -= static_cast<T>(1);
410 else shift -= static_cast<T>(1e-6);
411 Transform(samples, bind(minus<T>(), std::placeholders::_1, shift));
412 }
413 // Compute mean value
414 double mean = 0.;
415 for (int i = 0; i < n; ++i) {
416 mean += samples[i];
417 }
418 mean /= n;
419 // Evaluate sum of generalized entropy index
420 double sum = 0., p;
421 switch (alpha) {
422 // Mean log deviation
423 case 0: {
424 for (int i = 0; i < n; ++i) {
425 p = samples[i] / mean;
426 sum -= log(p);
427 }
428 } break;
429 // Theil index
430 case 1: {
431 for (int i = 0; i < n; ++i) {
432 p = samples[i] / mean;
433 sum += p * log(p);
434 }
435 } break;
436 // Coefficient of variation (squared, half)
437 case 2: {
438 for (int i = 0; i < n; ++i) {
439 sum += samples[i] * samples[i];
440 }
441 sum /= mean * mean;
442 sum -= n;
443 sum /= 2;
444 } break;
445 // Generalized entropy index
446 default: {
447 for (int i = 0; i < n; ++i) {
448 p = samples[i] / mean;
449 sum += pow(p, alpha);
450 }
451 sum -= n;
452 sum /= alpha * (alpha - 1);
453 } break;
454 }
455 return sum / n;
456 }
457
458 // =============================================================================
459 // Main
460 // =============================================================================
461
462 // -----------------------------------------------------------------------------
main(int argc,char ** argv)463 int main(int argc, char **argv)
464 {
465 // Parse command arguments
466 REQUIRES_POSARGS(3);
467
468 AggregationMode mode;
469 if (!FromString(POSARG(1), mode)) {
470 FatalError("Invalid aggregation mode: " << POSARG(1));
471 }
472
473 const char *mask_name = nullptr;
474 const char *output_name = nullptr;
475 ImageDataType dtype = MIRTK_VOXEL_UNKNOWN;
476 NormalizationMode normalization = Normalization_None;
477 int alpha = 0;
478 int bins = 64;
479 bool parzen = false;
480 double padding = NaN;
481 double threshold = 0.;
482 bool intersection = false;
483 double rescale_min = NaN;
484 double rescale_max = NaN;
485
486 for (ALL_OPTIONS) {
487 if (OPTION("-output")) output_name = ARGUMENT;
488 else if (OPTION("-mask")) mask_name = ARGUMENT;
489 else if (OPTION("-dtype") || OPTION("-datatype")) {
490 PARSE_ARGUMENT(dtype);
491 }
492 else if (OPTION("-normalization") || OPTION("-normalize")) {
493 if (HAS_ARGUMENT) {
494 const string arg = ToLower(ARGUMENT);
495 bool bval;
496 if (FromString(arg, bval)) {
497 normalization = (bval ? Normalization_ZScore : Normalization_None);
498 } else if (arg == "none") {
499 normalization = Normalization_None;
500 } else if (arg == "mean") {
501 normalization = Normalization_Mean;
502 } else if (arg == "median") {
503 normalization = Normalization_Median;
504 } else if (arg == "zscore" || arg == "z-score") {
505 normalization = Normalization_ZScore;
506 } else if (arg == "unit") {
507 normalization = Normalization_UnitRange;
508 } else {
509 FatalError("Invalid -normalization mode: " << arg);
510 }
511 } else {
512 normalization = Normalization_ZScore;
513 }
514 }
515 else if (OPTION("-rescale")) {
516 PARSE_ARGUMENT(rescale_min);
517 if (HAS_ARGUMENT) {
518 PARSE_ARGUMENT(rescale_max);
519 } else {
520 rescale_max = rescale_min;
521 rescale_min = 0.;
522 }
523 }
524 else if (OPTION("-threshold")) {
525 PARSE_ARGUMENT(threshold);
526 }
527 else if (OPTION("-padding")) PARSE_ARGUMENT(padding);
528 else if (OPTION("-alpha")) PARSE_ARGUMENT(alpha);
529 else if (OPTION("-bins")) PARSE_ARGUMENT(bins);
530 else HANDLE_BOOL_OPTION(parzen);
531 else HANDLE_BOOL_OPTION(intersection);
532 else HANDLE_COMMON_OR_UNKNOWN_OPTION();
533 }
534 if (!output_name) {
535 FatalError("Option -output is required!");
536 }
537
538 // Initialize I/O factories
539 InitializeIOLibrary();
540
541 // Read input images
542 Array<InputImage> images(NUM_POSARGS - 1);
543 if (verbose) {
544 cout << "Reading " << images.size() << " images...";
545 cout.flush();
546 }
547 images[0].Read(POSARG(2));
548 const int nvox = images[0].NumberOfVoxels();
549 for (int i = 3; i <= NUM_POSARGS; ++i) {
550 images[i - 2].Read(POSARG(i));
551 if (images[i - 2].Attributes() != images[0].Attributes()) {
552 if (verbose) cout << " failed" << endl;
553 FatalError("Input image " << POSARG(i) << " has different attributes than previous input images!");
554 }
555 }
556 if (verbose) cout << " done" << endl;
557
558 // Replace background values by NaN to be able to identify background after normalization
559 for (auto &image : images) {
560 if (!IsNaN(padding)) {
561 for (int vox = 0; vox < nvox; ++vox) {
562 if (fequal(image(vox), padding)) {
563 image(vox) = numeric_limits<InputType>::quiet_NaN();
564 }
565 }
566 }
567 image.PutBackgroundValueAsDouble(NaN);
568 }
569
570 // Normalize images
571 if (normalization != Normalization_None) {
572 if (verbose) {
573 cout << "Normalizing images...";
574 cout.flush();
575 }
576 Normalize(images, normalization);
577 if (verbose) cout << " done" << endl;
578 }
579
580 // Rescale images
581 if (rescale_min < rescale_max) {
582 if (verbose) {
583 cout << "Rescaling images...";
584 cout.flush();
585 }
586 Rescale(images, rescale_min, rescale_max);
587 if (verbose) cout << " done" << endl;
588 }
589
590 // Ensure all (normalized) intensities are positive
591 if (mode == AM_Gini || mode == AM_Theil || mode == AM_EntropyIndex) {
592 double min_value = inf;
593 for (const auto &image : images) {
594 double vmin, vmax;
595 image.GetMinMaxAsDouble(vmin, vmax);
596 if (vmin < min_value) min_value = vmin;
597 }
598 if (IsInf(min_value)) {
599 FatalError("Neither input image seems to have any foreground given -padding value of " << padding);
600 }
601 min_value -= 1.;
602 for (auto &image : images) {
603 for (int vox = 0; vox < nvox; ++vox) {
604 if (image.IsForeground(vox)) {
605 image(vox) -= static_cast<InputType>(min_value);
606 } else {
607 image(vox) = 0.;
608 }
609 }
610 image.PutBackgroundValueAsDouble(0.);
611 }
612 }
613
614 // Initialize output image
615 double bg = NaN;
616 OutputImage output(images[0].Attributes());
617 if (!IsNaN(padding)) {
618 if (intersection) {
619 output = 0.;
620 for (int vox = 0; vox < nvox; ++vox) {
621 for (size_t i = 0; i < images.size(); ++i) {
622 if (images[i].IsBackground(vox)) {
623 output(vox) = static_cast<OutputType>(bg);
624 break;
625 }
626 }
627 }
628 } else {
629 output = static_cast<OutputType>(bg);
630 for (int vox = 0; vox < nvox; ++vox) {
631 for (size_t i = 0; i < images.size(); ++i) {
632 if (images[i].IsForeground(vox)) {
633 output(vox) = 0.;
634 break;
635 }
636 }
637 }
638 }
639 }
640 if (mask_name) {
641 BinaryImage mask(mask_name);
642 if (mask.Attributes() != output.Attributes()) {
643 FatalError("Mask has different attributes!");
644 }
645 for (int vox = 0; vox < nvox; ++vox) {
646 if (mask(vox) == BinaryPixel(0)) output(vox) = static_cast<OutputType>(bg);
647 }
648 }
649 output.PutBackgroundValueAsDouble(bg);
650 if (verbose > 1) {
651 int nbg = 0;
652 for (int vox = 0; vox < nvox; ++vox) {
653 if (output.IsBackground(vox)) ++nbg;
654 }
655 cout << "No. of foreground voxels = " << nvox - nbg << endl;
656 cout << "No. of background voxels = " << nbg << endl;
657 }
658
659 // Parameters of histogram based measures
660 InputType min_value, max_value;
661 if (mode == AM_Entropy || mode == AM_Mode) {
662 GetMinMax(images, min_value, max_value);
663 if (bins == 0) {
664 bins = iceil(max_value) - ifloor(min_value);
665 if (bins == 0) bins = 1;
666 }
667 }
668
669 // Evaluate aggregation function for samples given at each voxel
670 if (verbose) {
671 cout << "Performing voxel-wise aggregation...";
672 cout.flush();
673 }
674 AggregateValuesAtEachVoxel eval;
675 eval._Images = &images;
676 eval._Output = &output;
677 eval._Padding = voxel_cast<InputType>(padding);
678 eval._MinValues = iround(threshold * double(images.size()));
679 switch (mode) {
680 case AM_Mean: {
681 eval._Function = [](const InputArray &values) -> OutputType {
682 const auto n = static_cast<int>(values.size());
683 return static_cast<OutputType>(Mean::Calculate(n, values.data()));
684 };
685 } break;
686
687 case AM_Median: {
688 eval._Function = [](const InputArray &values) -> OutputType {
689 const auto n = static_cast<int>(values.size());
690 return static_cast<OutputType>(Median::Calculate(n, values.data()));
691 };
692 } break;
693
694 case AM_StDev: {
695 eval._Function = [](const InputArray &values) -> OutputType {
696 const auto n = static_cast<int>(values.size());
697 return static_cast<OutputType>(StDev::Calculate(n, values.data()));
698 };
699 } break;
700
701 case AM_Variance: {
702 eval._Function = [](const InputArray &values) -> OutputType {
703 const auto n = static_cast<int>(values.size());
704 return static_cast<OutputType>(Var::Calculate(n, values.data()));
705 };
706 } break;
707
708 case AM_Gini: {
709 eval._Function = [](InputArray &values) -> OutputType {
710 return static_cast<OutputType>(GiniCoefficient(values));
711 };
712 } break;
713
714 case AM_Theil: {
715 eval._Function = [](InputArray &values) -> OutputType {
716 return static_cast<OutputType>(EntropyIndex(values, 1));
717 };
718 } break;
719
720 case AM_EntropyIndex: {
721 eval._Function = [alpha](InputArray &values) -> OutputType {
722 return static_cast<OutputType>(EntropyIndex(values, alpha));
723 };
724 } break;
725
726 case AM_Entropy: {
727 eval._Function = [min_value, max_value, bins, parzen](const InputArray &values) -> OutputType {
728 Histogram1D<int> hist(bins);
729 hist.Min(static_cast<double>(min_value));
730 hist.Max(static_cast<double>(max_value));
731 for (auto value : values) {
732 hist.AddSample(static_cast<double>(value));
733 }
734 if (parzen) hist.Smooth();
735 return static_cast<OutputType>(hist.Entropy());
736 };
737 } break;
738
739 case AM_Mode: {
740 eval._Function = [min_value, max_value, bins, parzen](const InputArray &values) -> OutputType {
741 Histogram1D<int> hist(bins);
742 hist.Min(static_cast<double>(min_value));
743 hist.Max(static_cast<double>(max_value));
744 for (auto value : values) {
745 hist.AddSample(static_cast<double>(value));
746 }
747 if (parzen) hist.Smooth();
748 double mode = hist.Mode();
749 return static_cast<OutputType>(IsNaN(mode) ? 0. : mode);
750 };
751 } break;
752
753 case AM_LabelConsistency: {
754 eval._Function = [](const InputArray &values) -> OutputType {
755 const auto n = static_cast<int>(values.size());
756 int m = 0;
757 for (int i = 0; i < n; ++i)
758 for (int j = i + 1; j < n; ++j) {
759 if (static_cast<int>(values[i]) == static_cast<int>(values[j])) {
760 ++m;
761 }
762 }
763 return static_cast<OutputType>(2. * m / double(n * (n - 1)));
764 };
765
766 } break;
767
768 default:
769 if (verbose) cout << " failed" << endl;
770 FatalError("Invalid aggregation mode: " << mode);
771 };
772 parallel_for(blocked_range<int>(0, nvox), eval);
773 if (verbose) cout << " done" << endl;
774
775 // Set suitable background value
776 if (mode == AM_Mean || mode == AM_Median) {
777 OutputType omin, omax;
778 output.GetMinMax(omin, omax);
779 if (!IsNaN(padding) && padding < omin) {
780 bg = padding;
781 } else {
782 bg = omin - 1e-3;
783 }
784 } else if (mode == AM_LabelConsistency) {
785 bg = 1.;
786 } else {
787 bg = 0.;
788 }
789 for (int vox = 0; vox < nvox; ++vox) {
790 if (output.IsBackground(vox)) {
791 output(vox) = static_cast<OutputType>(bg);
792 }
793 }
794 output.ClearBackgroundValue();
795
796 // Write output image
797 if (verbose) {
798 cout << "Writing result to " << output_name << "...";
799 cout.flush();
800 }
801 if (dtype != MIRTK_VOXEL_UNKNOWN && dtype != output.GetDataType()) {
802 UniquePtr<BaseImage> image(BaseImage::New(dtype));
803 *image = output;
804 image->Write(output_name);
805 } else {
806 output.Write(output_name);
807 }
808 if (verbose) cout << " done" << endl;
809
810 return 0;
811 }
812