Medical Imaging Interaction Toolkit  2018.4.99-3e3f1a6e
Medical Imaging Interaction Toolkit
PixelDumpMiniApp.cpp
Go to the documentation of this file.
1 /*============================================================================
2 
3 The Medical Imaging Interaction Toolkit (MITK)
4 
5 Copyright (c) German Cancer Research Center (DKFZ)
6 All rights reserved.
7 
8 Use of this source code is governed by a 3-clause BSD license that can be
9 found in the LICENSE file.
10 
11 ============================================================================*/
12 
13 // std includes
14 #include <string>
15 
16 // itk includes
17 #include "itksys/SystemTools.hxx"
18 #include "itkImageRegionConstIteratorWithIndex.h"
19 #include "itkCastImageFilter.h"
20 #include "itkExtractImageFilter.h"
21 
22 // CTK includes
23 #include "mitkCommandLineParser.h"
24 
25 // MITK includes
26 #include <mitkIOUtil.h>
28 
29 #include <mitkImageTimeSelector.h>
30 #include "mitkImageAccessByItk.h"
31 #include "mitkImageCast.h"
32 
34 std::string outFileName;
35 std::string maskFileName;
37 
38 using ImageVectorType = std::vector<mitk::Image::Pointer>;
40 
42 
43 bool verbose(false);
44 
45 typedef itk::Image<mitk::ScalarType, 3> InternalImageType;
46 typedef std::map<std::string, InternalImageType::Pointer> InternalImageMapType;
48 
49 itk::ImageRegion<3> relevantRegion;
50 InternalImageType::PointType relevantOrigin;
51 InternalImageType::SpacingType relevantSpacing;
52 InternalImageType::DirectionType relevantDirection;
53 
55 typedef std::vector<mitk::ScalarType> DumpedValuesType;
56 
57 struct DumpIndexCompare
58 {
59  bool operator() (const DumpIndexType& lhs, const DumpIndexType& rhs) const
60  {
61  if (lhs[0] < rhs[0])
62  {
63  return true;
64  }
65  else if (lhs[0] > rhs[0])
66  {
67  return false;
68  }
69 
70  if (lhs[1] < rhs[1])
71  {
72  return true;
73  }
74  else if (lhs[1] > rhs[1])
75  {
76  return false;
77  }
78 
79  return lhs[2] < rhs[2];
80  }
81 };
82 
83 typedef std::map<DumpIndexType, DumpedValuesType, DumpIndexCompare> DumpPixelMapType;
85 
87 {
88  // set general information about your MiniApp
89  parser.setCategory("Generic Analysis Tools");
90  parser.setTitle("Pixel Dumper");
91  parser.setDescription("MiniApp that allows to dump the pixel values of all passed files into a csv. The region of dumping can defined by a mask. All images (and mask) must have the same geometrie.");
92  parser.setContributor("DKFZ MIC");
94 
96  // how should arguments be prefixed
97  parser.setArgumentPrefix("--", "-");
98  // add each argument, unless specified otherwise each argument is optional
99  // see mitkCommandLineParser::addArgument for more information
100  parser.beginGroup("Required I/O parameters");
101  parser.addArgument(
102  "inputs", "i", mitkCommandLineParser::StringList, "Input files", "list of the images that should be dumped.", us::Any(), false);
103  parser.addArgument("output",
104  "o",
106  "Output file",
107  "where to save the csv.",
108  us::Any(),
109  false, false, false, mitkCommandLineParser::Output);
110  parser.endGroup();
111 
112  parser.beginGroup("Optional parameters");
113  parser.addArgument(
114  "mask", "m", mitkCommandLineParser::File, "Mask file", "Mask that defines the spatial image region that should be dumped. Must have the same geometry as the input images!", us::Any(), true, false, false, mitkCommandLineParser::Input);
115  parser.addArgument(
116  "captions", "c", mitkCommandLineParser::StringList, "Captions of image columns", "If provided the pixel columns of the csv will be named according to the passed values instead of using the image pathes. Number of images and names must be equal.", us::Any(), false);
117  parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text");
118  parser.endGroup();
120 }
121 
122 bool configureApplicationSettings(std::map<std::string, us::Any> parsedArgs)
123 {
124  if (parsedArgs.size() == 0)
125  return false;
126 
127  // parse, cast and set required arguments
128 
130  outFileName = us::any_cast<std::string>(parsedArgs["output"]);
131 
132  if (parsedArgs.count("mask"))
133  {
134  maskFileName = us::any_cast<std::string>(parsedArgs["mask"]);
135  }
136 
138 
139  if (parsedArgs.count("captions"))
140  {
141  captions = us::any_cast<mitkCommandLineParser::StringContainerType>(parsedArgs["captions"]);
142  }
143 
144  return true;
145 }
146 
147 template < typename TPixel, unsigned int VImageDimension >
149  const itk::Image< TPixel, VImageDimension > *image)
150 {
151  relevantRegion = image->GetLargestPossibleRegion();
152  relevantOrigin = image->GetOrigin();
153  relevantSpacing = image->GetSpacing();
154  relevantDirection = image->GetDirection();
155 }
156 
157 template < typename TPixel, unsigned int VImageDimension >
159  const itk::Image< TPixel, VImageDimension > *image,
160  InternalImageType::Pointer& internalImage)
161 {
162  typedef itk::Image< TPixel, VImageDimension > ImageType;
163 
164  //check if image fit to geometry
165 
166  // Make sure that spacing are the same
167  typename ImageType::SpacingType imageSpacing = image->GetSpacing();
168  typename ImageType::PointType zeroPoint; zeroPoint.Fill(0.0);
169  if ((zeroPoint + imageSpacing).SquaredEuclideanDistanceTo((zeroPoint + relevantSpacing)) >
170  1e-6) // for the dumper we are not as strict as mitk normally would be (mitk::eps)
171  {
172  mitkThrow() << "Images need to have same spacing! (Image spacing: " << imageSpacing
173  << "; relevant spacing: " << relevantSpacing << ")";
174  }
175 
176  // Make sure that orientation of mask and image are the same
177  typename ImageType::DirectionType imageDirection = image->GetDirection();
178  for (unsigned int i = 0; i < imageDirection.RowDimensions; ++i)
179  {
180  for (unsigned int j = 0; j < imageDirection.ColumnDimensions; ++j)
181  {
182  double differenceDirection = imageDirection[i][j] - relevantDirection[i][j];
183  if (fabs(differenceDirection) > 1e-6) // SD: 1e6 wird hier zum zweiten mal als Magic Number benutzt -> Konstante
184  {
185  // for the dumper we are not as strict as mitk normally would be (mitk::eps)
186  mitkThrow() << "Images need to have same direction! (Image direction: "
187  << imageDirection << "; relevant direction: " << relevantDirection << ")";
188  }
189  }
190  }
191 
192  // Make sure that origin of mask and image are the same
193  typename ImageType::PointType imageOrigin = image->GetOrigin();
194  if (imageOrigin.SquaredEuclideanDistanceTo(relevantOrigin) > 1e-6)
195  {
196  // for the dumper we are not as strict as mitk normally would be (mitk::eps)
197  mitkThrow() << "Image need to have same spacing! (Image spacing: "
198  << imageSpacing << "; relevant spacing: " << relevantOrigin << ")";
199  }
200 
201  typename ImageType::RegionType imageRegion = image->GetLargestPossibleRegion();
202 
203  if (!imageRegion.IsInside(relevantRegion) && imageRegion != relevantRegion)
204  {
205  mitkThrow() << "Images need to have same region! (Image region: "
206  << imageRegion << "; relevant region: " << relevantRegion << ")";
207  }
208 
209  //convert to internal image
210  typedef itk::ExtractImageFilter<ImageType, ImageType> ExtractFilterType;
211  typename ExtractFilterType::Pointer extractFilter = ExtractFilterType::New();
212  typedef itk::CastImageFilter<ImageType, InternalImageType> CastFilterType;
213  typename CastFilterType::Pointer castFilter = CastFilterType::New();
214 
215  extractFilter->SetInput(image);
216  extractFilter->SetExtractionRegion(relevantRegion);
217  castFilter->SetInput(extractFilter->GetOutput());
218  castFilter->Update();
219  internalImage = castFilter->GetOutput();
220 }
221 
222 template < typename TPixel, unsigned int VImageDimension >
224  const itk::Image< TPixel, VImageDimension > *image)
225 {
226  typedef itk::Image< TPixel, VImageDimension > ImageType;
227 
228  itk::ImageRegionConstIteratorWithIndex<ImageType> it(image, relevantRegion);
229 
230  it.GoToBegin();
231 
232  while (!it.IsAtEnd())
233  {
234  if (mask.IsNull() || it.Get() > 0)
235  {
236  DumpedValuesType values;
237 
238  const auto index = it.GetIndex();
239 
240  for (auto& imagePos : internalImages)
241  {
242  double value = imagePos.second->GetPixel(index);
243  values.push_back(value);
244  }
245 
246  dumpedPixels.insert(std::make_pair(index, values));
247  }
248  ++it;
249  }
250 }
251 
253 {
255 
256  InternalImageType::Pointer internalImage;
257 
258  for (unsigned int i = 0; i < image->GetTimeSteps(); ++i)
259  {
261  imageTimeSelector->SetInput(image);
262  imageTimeSelector->SetTimeNr(i);
263  imageTimeSelector->UpdateLargestPossibleRegion();
264 
265  mitk::Image::Pointer imageTimePoint = imageTimeSelector->GetOutput();
266 
267  AccessFixedDimensionByItk_1(imageTimePoint,
269  3,
270  internalImage);
271 
272  std::stringstream stream;
273  stream << "[" << i << "]";
274  map.insert(std::make_pair(stream.str(), internalImage));
275  }
276 
277  return map;
278 }
279 
280 void doDumping()
281 {
282  if (mask.IsNotNull() && mask->GetTimeSteps() > 1)
283  {
284  std::cout <<
285  "Pixel Dumper: Selected mask has multiple timesteps. Only use first timestep to mask the pixel dumping." << std::endl;
286 
288  maskTimeSelector->SetInput(mask);
289  maskTimeSelector->SetTimeNr(0);
290  maskTimeSelector->UpdateLargestPossibleRegion();
291  mask = maskTimeSelector->GetOutput();
292  }
293 
294  try
295  {
296  if (mask.IsNotNull())
297  { // if mask exist, we use the mask because it could be a sub region.
299  }
300  else
301  {
303  }
304  }
305  catch (const std::exception& e)
306  {
307  std::cerr << "Error extracting image geometry. Error text: " << e.what();
308  throw;
309  }
310 
311 
312  for (unsigned int index = 0; index < images.size(); ++index)
313  {
314  try
315  {
316  InternalImageMapType conversionMap = ConvertImageTimeSteps(images[index]);
317 
318  if (conversionMap.size() == 1)
319  {
320  internalImages.insert(std::make_pair(captions[index], conversionMap.begin()->second));
321  }
322  else if (conversionMap.size() > 1)
323  {
324  for (auto& pos : conversionMap)
325  {
326  std::stringstream namestream;
327  namestream << captions[index] << " " << pos.first;
328  internalImages.insert(std::make_pair(namestream.str(), pos.second));
329  }
330  }
331  }
332  catch (const std::exception& e)
333  {
334  std::stringstream errorStr;
335  errorStr << "Inconsistent image \"" << captions[index] << "\" will be excluded from the collection. Error: " << e.what();
336  std::cerr << errorStr.str() << std::endl;
337  }
338 
339  }
340 
341  if (mask.IsNotNull())
342  { // if mask exist, we use the mask because it could be a sub region.
344  }
345  else
346  {
348  }
349 
350 }
351 
352 void storeCSV()
353 {
354  std::ofstream resultfile;
355  resultfile.open(outFileName.c_str());
356  resultfile << "x,y,z";
357  for (auto aImage : internalImages)
358  {
359  resultfile << "," << aImage.first;
360  }
361 
362  resultfile << std::endl;
363 
364  for (auto dumpPos : dumpedPixels)
365  {
366  resultfile << dumpPos.first[0] << "," << dumpPos.first[1] << "," << dumpPos.first[2];
367 
368  for(auto d : dumpPos.second)
369  {
370  resultfile << "," << d;
371  }
372 
373  resultfile << std::endl;
374  }
375 }
376 
377 int main(int argc, char* argv[])
378 {
379  mitkCommandLineParser parser;
380  setupParser(parser);
381  const std::map<std::string, us::Any>& parsedArgs = parser.parseArguments(argc, argv);
382 
383  mitk::PreferenceListReaderOptionsFunctor readerFilterFunctor = mitk::PreferenceListReaderOptionsFunctor({ "MITK DICOM Reader v2 (classic config)" }, { "MITK DICOM Reader" });
384 
385  if (!configureApplicationSettings(parsedArgs))
386  {
387  return EXIT_FAILURE;
388  };
389 
390  // Show a help message
391  if (parsedArgs.count("help") || parsedArgs.count("h"))
392  {
393  std::cout << parser.helpText();
394  return EXIT_SUCCESS;
395  }
396 
397  if (!captions.empty() && inFilenames.size() != captions.size())
398  {
399  std::cerr << "Cannot dump images. Number of given captions does not equal number of given images.";
400  return EXIT_FAILURE;
401  };
402 
404  try
405  {
406  std::cout << "Load images:" << std::endl;
407  for (auto path : inFilenames)
408  {
409  std::cout << "Input: " << path << std::endl;
410  auto image = mitk::IOUtil::Load<mitk::Image>(path, &readerFilterFunctor);
411  images.push_back(image);
412 
413  }
414 
415  if (!maskFileName.empty())
416  {
417  mask = mitk::IOUtil::Load<mitk::Image>(maskFileName, &readerFilterFunctor);
418  std::cout << "Mask: " << maskFileName << std::endl;
419  }
420  else
421  {
422  std::cout << "Mask: none" << std::endl;
423  }
424 
425  doDumping();
426  storeCSV();
427 
428  std::cout << "Processing finished." << std::endl;
429 
430  return EXIT_SUCCESS;
431  }
432  catch (const itk::ExceptionObject& e)
433  {
434  MITK_ERROR << e.what();
435  return EXIT_FAILURE;
436  }
437  catch (const std::exception& e)
438  {
439  MITK_ERROR << e.what();
440  return EXIT_FAILURE;
441  }
442  catch (...)
443  {
444  MITK_ERROR << "Unexpected error encountered.";
445  return EXIT_FAILURE;
446  }
447 }
Option callback functor with a preference list/ black list option selection strategy.
void DoMaskedCollecting(const itk::Image< TPixel, VImageDimension > *image)
std::map< DumpIndexType, DumpedValuesType, DumpIndexCompare > DumpPixelMapType
std::string outFileName
std::map< std::string, InternalImageType::Pointer > InternalImageMapType
ImageVectorType images
#define AccessFixedDimensionByItk(mitkImage, itkImageTypeFunction, dimension)
Access a mitk-image with known dimension by an itk-image.
itk::Image< unsigned char, 3 > ImageType
#define MITK_ERROR
Definition: mitkLogMacros.h:20
void setContributor(std::string contributor)
std::string maskFileName
ValueType * any_cast(Any *operand)
Definition: usAny.h:377
mitkCommandLineParser::StringContainerType captions
void addArgument(const std::string &longarg, const std::string &shortarg, Type type, const std::string &argLabel, const std::string &argHelp=std::string(), const us::Any &defaultValue=us::Any(), bool optional=true, bool ignoreRest=false, bool deprecated=false, mitkCommandLineParser::Channel channel=mitkCommandLineParser::Channel::None)
InternalImageType::PointType relevantOrigin
std::map< std::string, us::Any > parseArguments(const StringContainerType &arguments, bool *ok=nullptr)
mitk::Image::Pointer mask
InternalImageType::SpacingType relevantSpacing
void DoInternalImageConversion(const itk::Image< TPixel, VImageDimension > *image, InternalImageType::Pointer &internalImage)
void storeCSV()
itk::ImageRegion< 3 > relevantRegion
InternalImageType::DirectionType relevantDirection
void doDumping()
void setupParser(mitkCommandLineParser &parser)
InternalImageMapType internalImages
void ExtractRelevantInformation(const itk::Image< TPixel, VImageDimension > *image)
DumpPixelMapType dumpedPixels
#define mitkThrow()
Image class for storing images.
Definition: mitkImage.h:72
Definition: usAny.h:163
std::string helpText() const
itk::Index< 3 > DumpIndexType
void setCategory(std::string category)
mitk::Image::Pointer image
MITKMATCHPOINTREGISTRATION_EXPORT ResultImageType::Pointer map(const InputImageType *input, const RegistrationType *registration, bool throwOnOutOfInputAreaError=false, const double &paddingValue=0, const ResultImageGeometryType *resultGeometry=nullptr, bool throwOnMappingError=true, const double &errorValue=0, mitk::ImageMappingInterpolator::Type interpolatorType=mitk::ImageMappingInterpolator::Linear)
void setArgumentPrefix(const std::string &longPrefix, const std::string &shortPrefix)
#define AccessFixedDimensionByItk_1(mitkImage, itkImageTypeFunction, dimension, arg1)
mitkCommandLineParser::StringContainerType inFilenames
std::vector< mitk::ScalarType > DumpedValuesType
std::vector< std::string > StringContainerType
unsigned int GetTimeSteps() const
Get the number of time steps from the TimeGeometry As the base data has not a data vector given by it...
Definition: mitkBaseData.h:355
InternalImageMapType ConvertImageTimeSteps(mitk::Image *image)
void setTitle(std::string title)
itk::Image< mitk::ScalarType, 3 > InternalImageType
bool configureApplicationSettings(std::map< std::string, us::Any > parsedArgs)
std::vector< mitk::Image::Pointer > ImageVectorType
void setDescription(std::string description)
int main(int argc, char *argv[])
void beginGroup(const std::string &description)
static Pointer New()
bool verbose(false)