Medical Imaging Interaction Toolkit  2016.11.0
Medical Imaging Interaction Toolkit
TumorProgressionMapping.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,
6 Division of Medical and Biological Informatics.
7 All rights reserved.
8 
9 This software is distributed WITHOUT ANY WARRANTY; without
10 even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 A PARTICULAR PURPOSE.
12 
13 See LICENSE.txt or http://www.mitk.org for details.
14 
15 ===================================================================*/
16 // MITK
17 #include <mitkIOUtil.h>
18 #include <mitkImageAccessByItk.h>
19 #include <mitkImageCast.h>
22 #include <mitkImageToItk.h>
23 #include <mitkImageWriter.h>
24 #include <mitkPixelType.h>
25 #include <mitkPlaneGeometry.h>
26 
27 // ITK
28 #include "itkImageDuplicator.h"
29 #include "itkPermuteAxesImageFilter.h"
30 #include "itkTileImageFilter.h"
31 #include <itkExtractImageFilter.h>
32 #include <itkImageFileWriter.h>
33 #include <itkRGBPixel.h>
34 #include <itkResampleImageFilter.h>
35 #include <itkVTKImageImport.h>
36 
37 #include <itkDirectory.h>
38 #include <itksys/SystemTools.hxx>
39 // VTK
40 #include <vtkCellArray.h>
41 #include <vtkFreeTypeStringToImage.h>
42 #include <vtkImageData.h>
43 #include <vtkTextProperty.h>
44 #include <vtkUnicodeString.h>
45 
46 // MITK
47 #include <mitkCommandLineParser.h>
48 
49 #include "itkRescaleIntensityImageFilter.h"
50 
51 #include <typeExtension.h>
52 
53 using namespace std;
54 
55 typedef unsigned short PixelType;
56 typedef double InputPixelType;
57 typedef itk::Image<itk::RGBPixel<PixelType>, 2> RGB2DImage;
58 typedef itk::RGBPixel<PixelType> RGBPixelType;
59 
60 typedef std::vector<std::string> FileList;
61 typedef std::vector<mitk::Image::Pointer> ImageList;
62 
64 static FileList CreateFileList(std::string folder, std::string postfix)
65 {
67  FileList fileList;
68 
69  if (dir->Load(folder.c_str()))
70  {
71  int n = dir->GetNumberOfFiles();
72  for (int r = 0; r < n; r++)
73  {
74  std::string filename = dir->GetFile(r);
75  if (filename == "." || filename == "..")
76  continue;
77  filename = folder + filename;
78  if (!itksys::SystemTools::FileExists(filename.c_str()))
79  continue;
80 
81  if (postfix.compare(filename.substr(filename.length() - postfix.length())) == 0)
82  fileList.push_back(filename);
83  }
84  }
85  std::sort(fileList.begin(), fileList.end());
86  return fileList;
87 }
88 
89 class mitkProgressionVisualization
90 {
91 public:
92  mitkProgressionVisualization() {}
98  mitk::Image::Pointer ConvertToRGBImage(mitk::Image::Pointer grayImage)
99  {
101  unsigned int *dim = grayImage->GetDimensions();
102  rgbImage->Initialize(mitk::MakePixelType<PixelType, RGBPixelType, 3>(), 3, dim);
103  rgbImage->SetGeometry(grayImage->GetGeometry());
104 
106  mitk::CastToItkImage(grayImage, itkGrayImage);
107 
109 
110  typedef itk::RescaleIntensityImageFilter<itk::Image<InputPixelType, 3>, itk::Image<PixelType, 3>> RescaleFilterType;
112  rescaleFilter->SetInput(itkGrayImage);
113  rescaleFilter->SetOutputMinimum(0);
114  rescaleFilter->SetOutputMaximum(255 * 255);
115  rescaleFilter->Update();
116 
117  itk::Index<3> idx;
118  RGBPixelType value;
119 
120  // Fill rgb image with gray values
121  for (idx[2] = 0; (unsigned int)idx[2] < dim[2]; idx[2]++)
122  {
123  for (idx[1] = 0; (unsigned int)idx[1] < dim[1]; idx[1]++)
124  {
125  for (idx[0] = 0; (unsigned int)idx[0] < dim[0]; idx[0]++)
126  {
127  value.Fill(rescaleFilter->GetOutput()->GetPixel(idx));
128  writeAcc.SetPixelByIndex(idx, value);
129  }
130  }
131  }
132  return rgbImage;
133  }
134 
135  RGB2DImage::Pointer TextAsImage(std::string text)
136  {
137  vtkSmartPointer<vtkImageData> textImage = vtkSmartPointer<vtkImageData>::New();
138  vtkSmartPointer<vtkFreeTypeStringToImage> freetype = vtkSmartPointer<vtkFreeTypeStringToImage>::New();
139  vtkSmartPointer<vtkTextProperty> prop = vtkSmartPointer<vtkTextProperty>::New();
140  float color[3] = {1, 1, 1};
141  float opacity = 1.0;
142  prop->SetColor(color[0], color[1], color[2]);
143  prop->SetFontSize(40);
144  prop->SetOpacity(opacity);
145  textImage->AllocateScalars(VTK_UNSIGNED_SHORT, 3);
146  freetype->RenderString(prop, vtkUnicodeString::from_utf8(text.c_str()), 72, textImage);
147  textImage->Modified();
148 
149  int *extent = textImage->GetExtent();
150 
152 
153  RGB2DImage::IndexType start;
154  start.Fill(0);
155  RGB2DImage::SizeType size;
156  size[0] = extent[1];
157  size[1] = extent[3];
158  size[2] = 0;
159 
160  RGB2DImage::RegionType region(start, size);
161  itkImage->SetRegions(region);
162  itkImage->Allocate();
163  RGB2DImage::IndexType idx;
164 
165  for (unsigned int y = 0; y < size[1]; y++)
166  {
167  for (unsigned int x = 0; x < size[0]; x++)
168  {
169  PixelType *pixel = static_cast<PixelType *>(textImage->GetScalarPointer(x, y, 0));
170  RGBPixelType values;
171  values.Fill(0);
172  values[0] = pixel[1];
173  values[1] = pixel[1];
174  values[2] = pixel[1];
175  idx.Fill(0);
176  idx[0] = x;
177  idx[1] = y;
178 
179  itkImage->SetPixel(idx, values);
180  }
181  }
182 
183  typedef itk::PermuteAxesImageFilter<RGB2DImage> PermuteAxesImageFilterType;
184 
185  itk::FixedArray<unsigned int, 2> order;
186  order[0] = 1;
187  order[1] = 0;
188 
190  permuteAxesFilter->SetInput(itkImage);
191  permuteAxesFilter->SetOrder(order);
192  permuteAxesFilter->Update();
193 
194  RGB2DImage::Pointer itkResultImage = RGB2DImage::New();
195  itkResultImage->SetRegions(region);
196  itkResultImage->Allocate();
197 
198  itkResultImage->Graft(permuteAxesFilter->GetOutput());
199 
200  return itkResultImage;
201  }
202 
213  void AddColouredOverlay(mitk::Image::Pointer rgbImage, mitk::Image::Pointer overlayImage, mitk::Color color)
214  {
215  unsigned int *dim = rgbImage->GetDimensions();
217  mitk::CastToItkImage(overlayImage.GetPointer(), itkOverlayImage);
219 
220  itk::Index<3> idx;
221  itk::RGBPixel<PixelType> value;
222  unsigned short overlayVal = 0;
223  // Fill rgb image with gray values
224  for (idx[2] = 0; (unsigned int)idx[2] < dim[2]; idx[2]++)
225  {
226  for (idx[1] = 0; (unsigned int)idx[1] < dim[1]; idx[1]++)
227  {
228  for (idx[0] = 0; (unsigned int)idx[0] < dim[0]; idx[0]++)
229  {
230  overlayVal = 255 * itkOverlayImage->GetPixel(idx);
231  value = writeAcc.GetPixelByIndex(idx);
232  value[0] = std::min((int)(value[0] + overlayVal * color[0]), 254 * 255);
233  value[1] = std::min((int)(value[1] + overlayVal * color[1]), 254 * 255);
234  value[2] = std::min((int)(value[2] + overlayVal * color[2]), 254 * 255);
235  writeAcc.SetPixelByIndex(idx, value);
236  }
237  }
238  }
239  }
240 
241  itk::Image<itk::RGBPixel<PixelType>, 2>::Pointer ExtractSlice(
242  itk::Image<itk::RGBPixel<PixelType>, 3>::Pointer itkImage, unsigned int sliceNo)
243  {
244  typedef itk::Image<RGBPixelType, 3> InputImageType;
245  typedef itk::Image<RGBPixelType, 2> OutputImageType;
246 
247  int dim[3];
248  dim[0] = itkImage->GetLargestPossibleRegion().GetSize()[0];
249  dim[1] = itkImage->GetLargestPossibleRegion().GetSize()[1];
250  dim[2] = itkImage->GetLargestPossibleRegion().GetSize()[2];
251 
252  itk::Index<3> desiredStart;
253  itk::Size<3> desiredSize;
254 
255  // case AXIAL: // 3rd dimension fixed
256  desiredStart.Fill(0);
257  desiredStart[2] = sliceNo;
258  desiredSize.Fill(0);
259  desiredSize[0] = dim[0];
260  desiredSize[1] = dim[1];
261  desiredSize[2] = 0;
262 
263  itk::ImageRegion<3> desiredRegion(desiredStart, desiredSize);
264 
265  // Extract slice
268  extractSlice->SetInput(itkImage);
269  extractSlice->SetExtractionRegion(desiredRegion);
270  extractSlice->SetDirectionCollapseToIdentity();
271  extractSlice->Update();
272 
273  return extractSlice->GetOutput();
274  }
275 };
276 
277 static std::string GetName(std::string fileName, std::string suffix = "_T2.nrrd")
278 {
279  fileName = itksys::SystemTools::GetFilenameName(fileName);
280  return fileName.substr(0, fileName.length() - suffix.length() - 11); // 10 = date length
281 }
282 
283 static std::string GetDate(std::string fileName, std::string suffix = "_T2.nrrd")
284 {
285  fileName = itksys::SystemTools::GetFilenameName(fileName);
286  fileName = fileName.substr(fileName.length() - suffix.length() - 10, 10); // 10 = date length
287  return fileName;
288 }
289 
291 {
293  for (unsigned int i = 0; i < files.size(); ++i)
294  {
295  images.push_back(mitk::IOUtil::LoadImage(files.at(i)));
296  }
297  return images;
298 }
299 
300 int main(int argc, char *argv[])
301 {
302  // Parse Command-Line Arguments
303  mitkCommandLineParser parser;
304  parser.setArgumentPrefix("--", "-");
305 
306  parser.setTitle("Tumor Progression Mapping");
307  parser.setCategory("Preprocessing Tools");
308  parser.setContributor("MBI");
309  parser.setDescription("Convert a set of co-registered and resampled follow-up images into a 2D png overview (and "
310  "optionally in a 4D NRRD Volume).\nNaming convecntion of files is "
311  "IDENTIFIER_YYYY-MM-DD_MODALITY.nrrd");
312 
313  parser.setArgumentPrefix("--", "-");
314  parser.addArgument("input", "i", mitkCommandLineParser::InputDirectory, "Input folder containing all follow ups");
315  parser.addArgument("output", "o", mitkCommandLineParser::OutputFile, "Output file (PNG)");
316  parser.addArgument("blanked", "b", mitkCommandLineParser::Bool, "Only Display Morphology");
317  parser.addArgument("morphology", "m", mitkCommandLineParser::String, "Morphology postfix.", "_T2.nrrd");
318  parser.addArgument(
319  "segmentation", "s", mitkCommandLineParser::String, "Segmentation postfix. Default: _GTV.nrrd", "_GTV.nrrd");
320  parser.addArgument("4dVolume", "v", mitkCommandLineParser::OutputFile, "Filepath to merged 4d NRRD Volume.");
321  parser.addArgument(
322  "skip", "k", mitkCommandLineParser::Int, "Number of slices to be skipped from top and from button (Default 0)");
323  parser.addArgument(
324  "interval", "n", mitkCommandLineParser::Int, "1 - for all slices, 2 - every second, 3 - every third ...");
325  parser.addArgument("opacity", "c", mitkCommandLineParser::Float, "Opacity of overlay [0,1] invisible -> visible");
326 
327  map<string, us::Any> parsedArgs = parser.parseArguments(argc, argv);
328 
329  if (parsedArgs.size() == 0)
330  return EXIT_SUCCESS;
331 
332  // Show a help message
333  if (parsedArgs.count("help") || parsedArgs.count("h"))
334  {
335  std::cout << parser.helpText();
336  return EXIT_SUCCESS;
337  }
338 
339  std::string outputFile;
340  std::string inputFolder;
341 
342  if (parsedArgs.count("input") || parsedArgs.count("i"))
343  {
344  inputFolder = us::any_cast<string>(parsedArgs["input"]) + "/";
345  }
346 
347  if (parsedArgs.count("output") || parsedArgs.count("o"))
348  {
349  outputFile = us::any_cast<string>(parsedArgs["output"]);
350  }
351 
352  int skip = 0;
353  int interval = 1;
354  float opacity = .3;
355 
356  if (parsedArgs.count("skip") || parsedArgs.count("k"))
357  {
358  skip = us::any_cast<int>(parsedArgs["skip"]);
359  }
360 
361  if (parsedArgs.count("interval") || parsedArgs.count("n"))
362  {
363  interval = us::any_cast<int>(parsedArgs["interval"]);
364  }
365 
366  if (parsedArgs.count("opacity") || parsedArgs.count("c"))
367  {
368  opacity = us::any_cast<float>(parsedArgs["opacity"]);
369  }
370 
371  FileList morphFiles;
372  FileList segFiles;
373 
374  std::string refPattern;
375  std::string segPattern;
376 
377  if (parsedArgs.count("morphology") || parsedArgs.count("m"))
378  {
379  refPattern = us::any_cast<std::string>(parsedArgs["morphology"]);
380  }
381  else
382  return EXIT_FAILURE;
383 
384  if (parsedArgs.count("segmentation") || parsedArgs.count("s"))
385  {
386  segPattern = us::any_cast<std::string>(parsedArgs["segmentation"]);
387  }
388 
389  bool blank = false;
390  if (parsedArgs.count("blanked") || parsedArgs.count("b"))
391  {
392  blank = true;
393  }
395  typedef itk::Image<RGBPixelType, 2> OutputImageType;
396  typedef itk::Image<RGBPixelType, 3> InputImageType;
397 
398  mitkProgressionVisualization progressVis;
399 
400  morphFiles = CreateFileList(inputFolder, refPattern);
401  segFiles = CreateFileList(inputFolder, segPattern);
402 
403  ImageList morphImages = LoadPreprocessedFiles(morphFiles);
404  ImageList segImages;
405  if (segFiles.size() > 0 && blank == false)
406  segImages = LoadPreprocessedFiles(segFiles);
407 
408  mitk::Image::Pointer rgbImage;
409 
410  // define color for overlay image
411  mitk::Color color;
412  color.Fill(0);
413  color[0] = 200 * opacity;
414  color[1] = 0;
415 
416  // Set up itk time image filter to contain 0..N-1 images
419  itk::FixedArray<unsigned int, 2> layout;
420  unsigned int noOfTimeSteps = morphImages.size();
421  layout[0] = noOfTimeSteps;
422  layout[1] = 0; // automatic number of neccessary rows
423  tileFilter->SetLayout(layout);
424 
425  // Get Reference image (all images are expected to have exact same dimensions!)
426 
427  std::string fileName = morphFiles.at(0);
428  mitk::Image *referenceImg = morphImages.at(0);
429 
430  mitk::Image::Pointer merged4D;
431  std::string volumeFile;
432 
433  if (parsedArgs.count("4dVolume") || parsedArgs.count("v"))
434  {
435  volumeFile = us::any_cast<string>(parsedArgs["4dVolume"]);
436  if (volumeFile != "")
437  {
438  unsigned int *dims = new unsigned int[4];
439  dims[0] = referenceImg->GetDimensions()[0];
440  dims[1] = referenceImg->GetDimensions()[1];
441  dims[2] = referenceImg->GetDimensions()[2];
442  dims[3] = morphImages.size();
443  merged4D = mitk::Image::New();
444  merged4D->Initialize(referenceImg->GetPixelType(), 4, dims);
445  merged4D->GetTimeGeometry()->Expand(noOfTimeSteps);
446  }
447  }
448 
449  unsigned int *dim = referenceImg->GetDimensions();
450  unsigned int sliceMaxAxial = dim[2];
451 
452  // Now iterate over all data sets, extract overlay and add it to reference image
453  mitk::Image *morphImage;
454  mitk::Image *segmentationImage = NULL;
455 
456  for (unsigned int i = 0; i < noOfTimeSteps; i++)
457  {
458  MITK_INFO << "File : " << i << " of /" << noOfTimeSteps;
459  int currentSliceNo = 0;
460 
461  // Retrieve images of current time step
462  fileName = morphFiles.at(i);
463  morphImage = morphImages.at(i);
464 
465  // Create 4D Volume image
466  if (volumeFile != "")
467  {
469 
470  merged4D->GetGeometry(i)->SetSpacing(referenceImg->GetGeometry()->GetSpacing());
471  merged4D->GetGeometry(i)->SetOrigin(referenceImg->GetGeometry()->GetOrigin());
472  merged4D->GetGeometry(i)->SetIndexToWorldTransform(referenceImg->GetGeometry()->GetIndexToWorldTransform());
473  merged4D->SetVolume(readAc.GetData(), i);
474  }
475 
476  MITK_INFO << "-- Convert to RGB";
477  rgbImage = progressVis.ConvertToRGBImage(morphImage);
478 
479  // Add current seg in red
480  color.Fill(0);
481  color[0] = 200 * opacity;
482 
483  if (!blank)
484  {
485  segmentationImage = segImages.at(i);
486  if (segmentationImage != NULL)
487  {
488  MITK_INFO << "-- Add Overlay";
489  progressVis.AddColouredOverlay(rgbImage, segmentationImage, color);
490  }
491  }
492  // Add Segmentation of next time step in red
493  if (i == 0)
494  {
495  MITK_INFO << "Skipping retro view in first time step";
496  }
497  else
498  {
499  color.Fill(0);
500  // Add previous in green
501  color[1] = 200 * opacity;
502  if (!blank)
503  {
504  mitk::Image *nextSeg = segImages.at(i - 1);
505  MITK_INFO << "-- Add Overlay of next Step";
506  progressVis.AddColouredOverlay(rgbImage, nextSeg, color);
507  }
508  }
509 
510  // Now save all slices from overlay in output folder
511  MITK_INFO << "-- Extract Slices";
512  for (int slice = sliceMaxAxial - skip - 1; slice > skip; slice -= interval) // sliceMaxAxial/40.0f))
513  {
515  InputImageType::Pointer itkImage2;
516 
517  mitk::CastToItkImage(rgbImage, itkImage);
518  typedef itk::ImageDuplicator<InputImageType> DuplicatorType;
520  duplicator->SetInputImage(itkImage);
521  duplicator->Update();
522  itkImage2 = duplicator->GetOutput();
523 
525  extractedSlice->Graft(progressVis.ExtractSlice(itkImage2, slice));
526  tileFilter->SetInput(((currentSliceNo + 1) * (noOfTimeSteps) + i), extractedSlice);
527 
528  tileFilter->SetInput(i, progressVis.TextAsImage(GetDate(fileName)));
529 
530  currentSliceNo++;
531  }
532  }
533 
534  MITK_INFO << "Tile Filter Update";
535  tileFilter->Update();
536 
537  // Write the output image
538  typedef itk::ImageFileWriter<OutputImageType> WriterType;
540  writer->SetInput(tileFilter->GetOutput());
541  std::string patientName;
542 
543  patientName = GetName(fileName);
544 
545  if (blank)
546  writer->SetFileName(outputFile);
547  else
548  writer->SetFileName(outputFile);
549 
550  writer->Update();
551  if (volumeFile != "")
552  mitk::IOUtil::Save(merged4D, volumeFile);
553 
554  return EXIT_SUCCESS;
555 }
const Point3D GetOrigin() const
Get the origin, e.g. the upper-left corner of the plane.
Gives locked and index-based write access for a particular image part. The class provides several set...
static void Save(const mitk::BaseData *data, const std::string &path)
Save a mitk::BaseData instance.
Definition: mitkIOUtil.cpp:824
itk::SmartPointer< Self > Pointer
Gives locked and index-based read access for a particular image part. The class provides several set-...
#define MITK_INFO
Definition: mitkLogMacros.h:22
static std::string GetName(std::string fileName, std::string suffix="_T2.nrrd")
void setContributor(std::string contributor)
std::vector< mitk::Image::Pointer > ImageList
int main(int argc, char *argv[])
STL namespace.
ValueType * any_cast(Any *operand)
Definition: usAny.h:377
const mitk::Vector3D GetSpacing() const
Get the spacing (size of a pixel).
std::vector< std::string > FileList
std::map< std::string, us::Any > parseArguments(const StringContainerType &arguments, bool *ok=nullptr)
static std::string GetDate(std::string fileName, std::string suffix="_T2.nrrd")
class ITK_EXPORT Image
unsigned int * GetDimensions() const
Get the sizes of all dimensions as an integer-array.
Definition: mitkImage.cpp:1308
itk::Image< double, 3 > InputImageType
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)
static const std::string filename
Image class for storing images.
Definition: mitkImage.h:76
static ImageList LoadPreprocessedFiles(FileList files)
const mitk::PixelType GetPixelType(int n=0) const
Returns the PixelType of channel n.
Definition: mitkImage.cpp:105
void setCategory(std::string category)
const TPixel * GetData() const
Gives const access to the data.
static FileList CreateFileList(std::string folder, std::string postfix)
Create list of all files in provided folder ending with same postfix.
static Pointer New()
itk::RGBPixel< float > Color
Color Standard RGB color typedef (float)
void setArgumentPrefix(const std::string &longPrefix, const std::string &shortPrefix)
static T min(T x, T y)
Definition: svm.cpp:67
itk::Image< itk::RGBPixel< PixelType >, 2 > RGB2DImage
void MITKCORE_EXPORT CastToItkImage(const mitk::Image *mitkImage, itk::SmartPointer< ItkOutputImageType > &itkOutputImage)
Cast an mitk::Image to an itk::Image with a specific type.
itk::RGBPixel< PixelType > RGBPixelType
std::string helpText() const
unsigned short PixelType
void setTitle(std::string title)
double InputPixelType
mitk::BaseGeometry * GetGeometry(int t=0) const
Return the geometry, which is a TimeGeometry, of the data as non-const pointer.
Definition: mitkBaseData.h:129
void setDescription(std::string description)
static mitk::Image::Pointer LoadImage(const std::string &path)
LoadImage Convenience method to load an arbitrary mitkImage.
Definition: mitkIOUtil.cpp:597
section MAP_FRAME_Mapper_Settings Mapper settings For the mapping of corrected images
mitk::AffineTransform3D * GetIndexToWorldTransform()
Get the transformation used to convert from index to world coordinates.
static itkEventMacro(BoundingShapeInteractionEvent, itk::AnyEvent) class MITKBOUNDINGSHAPE_EXPORT BoundingShapeInteractor Pointer New()
Basic interaction methods for mitk::GeometryData.