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