SMILX  1.01
milxLabelVisualisation.cpp
1 /*=========================================================================
2  The Software is copyright (c) Commonwealth Scientific and Industrial Research Organisation (CSIRO)
3  ABN 41 687 119 230.
4  All rights reserved.
5 
6  Licensed under the CSIRO BSD 3-Clause License
7  You may not use this file except in compliance with the License.
8  You may obtain a copy of the License in the file LICENSE.md or at
9 
10  https://stash.csiro.au/projects/SMILI/repos/smili/browse/license.txt
11 
12  Unless required by applicable law or agreed to in writing, software
13  distributed under the License is distributed on an "AS IS" BASIS,
14  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  See the License for the specific language governing permissions and
16  limitations under the License.
17 =========================================================================*/
29 //Qt
30 #include <QApplication>
31 
32 #include "milxQtImage.h"
33 #include "milxQtModel.h"
34 #include "milxQtPlot.h"
35 #include "milxQtFile.h"
36 
37 #include <vtkWindowToImageFilter.h>
38 
39 #include <tclap/CmdLine.h> //Command line parser library
40 
41 using namespace TCLAP;
42 
43 int main(int argc, char* argv[])
44 {
45  QApplication app(argc,argv);
46  QMainWindow mainWindow;//app takes ownership so need to be on stack, not heap
47 
49  CmdLine cmd("A visualisation tool for labelled images", ' ', milx::NumberToString(milx::Version));
50 
52  ValueArg<std::string> labelledImageArg("l", "labels", "Labelled image to visualise", true, "seg.nii.gz", "Labels");
54  ValueArg<std::string> outputArg("o", "output", "Output Screenshot name", false, "screenshot.png", "Output");
55  ValueArg<std::string> saveArg("", "save", "Save the mesh generated with name. Only for non-volume output.", false, "surface.vtk", "Save");
56  ValueArg<std::string> surfaceArg("s", "surface", "Surface to overlay with labelled image", false, "surface.vtk", "Surface");
57  ValueArg<std::string> transformArg("t", "transform", "Transform (ITK Format) to apply to objects being rendered", false, "rigid.txt", "Transform");
58  ValueArg<std::string> loadViewFileArg("", "loadviewfile", "Load saved view from file (use onscreen mode to save view files)", false, "camera.cam", "Load View File");
59  ValueArg<float> aboveArg("", "above", "Add above value to thresholding of the labels. Controls max of scalar range of colourmap also.", false, 255, "Above");
60  ValueArg<float> belowArg("", "below", "Add below value to thresholding of the labels. Controls min of scalar range of colourmap also.", false, 0, "Below");
61  ValueArg<float> opacityArg("", "opacity", "Set the opacity of the surface", false, 0.5, "Opacity");
62  ValueArg<float> zoomArg("", "zoom", "Zoom in on the current view by given factor.", false, 2.0, "Zoom");
63  ValueArg<int> heightArg("y", "height", "Set the height of the window.", false, 600, "Height");
64  ValueArg<int> widthArg("x", "width", "Set the width of the window.", false, 800, "Width");
65  ValueArg<int> linearDivisionArg("", "lineardivision", "Evenly space the label values across N to get full use of colourmap.", false, 16, "Linear Division");
66  ValueArg<int> linearOffsetArg("", "linearoffset", "Offset the even spacing of the label values across N to get full use of colourmap.", false, 1, "Linear Offset");
68  SwitchArg onscreenArg("", "onscreen", "Enable on screen rendering, i.e. display the rendering as an interactive window.", false);
69  SwitchArg inverseArg("", "inverse", "Use the inverse transform when transform file is provided.", false);
70  SwitchArg whiteArg("", "white", "Make background white rather than default gradient colour.", false);
71  SwitchArg loadViewArg("", "loadview", "Load saved view (use smilx or onscreen render mode to view and save with Right Click->View->Save View", false);
72  SwitchArg humanArg("", "nohuman", "Disable human orientation glyph.", false);
73  SwitchArg processArg("", "postprocess", "Process the resulting surfaces from labels to reduce vertices and smooth etc.", false);
74  SwitchArg oversmoothArg("", "oversmooth", "Process the resulting surfaces from labels with oversmoothing. Option is for Postprocess argument.", false);
75  SwitchArg volumeArg("", "volume", "Display using volume rendering instead of marching cude iso-surfaces.", false);
76  SwitchArg lineariseArg("", "linearise", "Evenly space the label values to get full use of colourmap.", false);
77  SwitchArg wireframeArg("", "wireframe", "Display surface as a wireframe model.", false);
78  SwitchArg axialArg("", "axial", "Show axial view based on position of first surface.", false);
79  SwitchArg coronalArg("", "coronal", "Show coronal view based on position of first surface.", false);
80  SwitchArg saggitalArg("", "saggital", "Show saggital view based on position of first surface.", false);
82  SwitchArg jetArg("", "jet", "Change colourmap of the scalars to the Jet map (default)", false);
83  SwitchArg vtkArg("", "vtk", "Change colourmap of the scalars to blue-red (rainbow VTK) map", false);
84  SwitchArg hsvArg("", "hsv", "Change colourmap of the scalars to blue-red (rainbow HSV) map", false);
85  SwitchArg rainbowArg("", "rainbow", "Change colourmap of the scalars to the rainbow map", false);
86  SwitchArg spectralArg("", "spectral", "Change colourmap of the scalars to the spectral map", false);
87  SwitchArg nihArg("", "NIH", "Change colourmap of the scalars to NIH", false);
88  SwitchArg fireArg("", "NIH_FIRE", "Change colourmap of the scalars to NIH Fire", false);
89  SwitchArg aalArg("", "AAL", "Change colourmap of the scalars to AAL", false);
90  SwitchArg hotArg("", "HOT", "Change colourmap of the scalars to HOT", false);
91 
93  cmd.add( labelledImageArg );
94  cmd.add( outputArg );
95  cmd.add( saveArg );
96  cmd.add( surfaceArg );
97  cmd.add( transformArg );
98  cmd.add( loadViewFileArg );
99  cmd.add( aboveArg );
100  cmd.add( belowArg );
101  cmd.add( opacityArg );
102  cmd.add( zoomArg );
103  cmd.add( heightArg );
104  cmd.add( widthArg );
105  cmd.add( linearDivisionArg );
106  cmd.add( linearOffsetArg );
107 
108  cmd.add( onscreenArg );
109  cmd.add( inverseArg );
110  cmd.add( whiteArg );
111  cmd.add( loadViewArg );
112  cmd.add( humanArg );
113  cmd.add( processArg );
114  cmd.add( oversmoothArg );
115  cmd.add( volumeArg );
116  cmd.add( lineariseArg );
117  cmd.add( wireframeArg );
118  cmd.add( axialArg );
119  cmd.add( coronalArg );
120  cmd.add( saggitalArg );
121 
122  cmd.add( jetArg );
123  cmd.add( vtkArg );
124  cmd.add( hsvArg );
125  cmd.add( rainbowArg );
126  cmd.add( spectralArg );
127  cmd.add( nihArg );
128  cmd.add( fireArg );
129  cmd.add( aalArg );
130  cmd.add( hotArg );
131 
133  cmd.parse( argc, argv );
134 
136  const std::string labelsName = labelledImageArg.getValue();
137  const std::string screenName = outputArg.getValue();
138  const std::string fileName = saveArg.getValue();
139  const std::string surfaceName = surfaceArg.getValue();
140  const std::string transformName = transformArg.getValue();
141  const std::string loadViewName = loadViewFileArg.getValue();
142  const float aboveValue = aboveArg.getValue();
143  const float belowValue = belowArg.getValue();
144  const float opacity = opacityArg.getValue();
145  const float zoomFactor = zoomArg.getValue();
146  const int windowHeight = heightArg.getValue();
147  const int windowWidth = widthArg.getValue();
148  int N = linearDivisionArg.getValue();
149  int offset = linearOffsetArg.getValue();
150 
151  //Check arguments
152  //Most of the checking is done by TCLAP
153  if(inverseArg.isSet())
154  {
155  if(!transformArg.isSet())
156  {
157  cerr << "Error in arguments! Inverse argument needs to be used with the transform argument." << endl;
158  exit(EXIT_FAILURE);
159  }
160  }
161 
162  //Read transform
163  vtkSmartPointer<vtkTransform> transform = vtkSmartPointer<vtkTransform>::New(); //transform matrix
164  transform->Identity();
165  transform->PostMultiply();
166  if(transformArg.isSet())
167  {
168  vtkSmartPointer<vtkMatrix4x4> matrix = milx::File::OpenITKTransform(transformName);
169  transform->Concatenate(matrix);
170  if(inverseArg.isSet())
171  transform->Inverse();
172  }
173 
174  std::cerr << "Reading Header for type info etc." << std::endl;
175  std::string pixelType, componentType;
176  size_t dimensions = 3;
177  if( !milx::File::ReadImageInformation(labelsName, pixelType, componentType, dimensions) )
178  {
179  milx::PrintError("Failed Reading Image. Check the image type/file. Exiting.");
180  exit(EXIT_FAILURE);
181  }
182  milx::PrintInfo("Pixel Type: " + pixelType);
183  milx::PrintInfo("Component Type: " + componentType);
184  milx::PrintInfo("Dimensions: " + milx::NumberToString(dimensions));
185 
186  //Reader object
187  QScopedPointer<milxQtFile> reader(new milxQtFile);
188  QScopedPointer<milxQtImage> labelledImage(new milxQtImage); //smart deletion
189  bool success = reader->openImage(labelsName.c_str(), labelledImage.data());
190  labelledImage->setName(labelsName.c_str());
191  if(aboveArg.isSet() || belowArg.isSet())
192  labelledImage->threshold(0, belowValue, aboveValue);
193  labelledImage->generateImage();
194 
196  QPointer<milxQtImage> eightBitImage; //smart deletion
197  if(componentType == "unsigned_char" || componentType == "unsigned char")
198  {
199  eightBitImage = labelledImage.data();
200  milx::PrintInfo("Detected labelled images.");
201  }
202  else
203  {
204  milx::PrintWarning("8-bit image not found and will be cast to 8-bit image for visualisation.");
205  eightBitImage = new milxQtImage(labelledImage.data());
206  eightBitImage->setData( milx::Image<floatImageType>::CastImage<charImageType>(labelledImage->GetFloatImage()) );
207  }
208  eightBitImage->setName(labelsName.c_str());
209  eightBitImage->generateImage();
210 
211  std::vector<unsigned char> values = milx::Image<charImageType>::LabelValues(eightBitImage->GetCharImage());
212  std::cout << ">> " << values.size() << " Labels present: " << std::endl;
213  for(size_t j = 0; j < values.size(); j ++)
214  std::cout << static_cast<unsigned>(values[j]) << ", ";
215  std::cout << std::endl;
216 
217  if(!linearDivisionArg.isSet())
218  N = values.size();
219 
221  vtkSmartPointer<vtkLookupTable> lookupTable = vtkSmartPointer<vtkLookupTable>::New();
222  lookupTable->SetTableRange(0.0, values.size()+1);
223  lookupTable->Build();
224 
225  std::vector< QSharedPointer<milxQtModel> > isoSurfaces;
226  QSharedPointer<milxQtRenderWindow> model(new milxQtRenderWindow);
227  model->setName("Label Surface Rendering");
228  model->SetSize(windowHeight, windowWidth);
229  model->generateRender();
230 
231 if(volumeArg.isSet())
232 {
233  cout << ">> Overlay: Volume Rendering" << endl;
234 // float minVolumeValue = numeric_limits<float>::max();
235 // float maxVolumeValue = numeric_limits<float>::min();
236  std::vector<float> colourValuesAsFloat, opacityValuesAsFloat;
237  opacityValuesAsFloat.push_back(0.0); //background transparent
238  colourValuesAsFloat.push_back(0.0); //background
239  for(size_t j = 0; j < values.size(); j ++)
240  {
241  opacityValuesAsFloat.push_back(1.0);
242  colourValuesAsFloat.push_back( static_cast<float>(values[j]) );
243  }
244 
245  vtkSmartPointer<vtkImageFlip> imageReorient = vtkSmartPointer<vtkImageFlip>::New();
246  #if VTK_MAJOR_VERSION <= 5
247  imageReorient->SetInput(eightBitImage->GetOutput());
248  #else
249  imageReorient->SetInputData(eightBitImage->GetOutput());
250  #endif
251  imageReorient->SetFilteredAxis(1);
252  imageReorient->FlipAboutOriginOn();
253  imageReorient->Update(); //ITK image would have been flipped
254 
255  QScopedPointer<milxQtPlot> label(new milxQtPlot); //smart deletion
256  label->SetSource(imageReorient->GetOutput(), true);
257  label->setOpacityTransferValues(opacityValuesAsFloat);
258  label->setColourTransferValues(colourValuesAsFloat);
259  //Colour maps
260  label->colourMapToJet(belowValue, aboveValue); //default
261  if(vtkArg.isSet())
262  label->colourMapToVTK(belowValue, aboveValue);
263  if(hsvArg.isSet())
264  label->colourMapToHSV(belowValue, aboveValue);
265  if(rainbowArg.isSet())
266  label->colourMapToRainbow(belowValue, aboveValue);
267  if(spectralArg.isSet())
268  label->colourMapToSpectral(belowValue, aboveValue);
269  if(nihArg.isSet())
270  label->colourMapToNIH(belowValue, aboveValue);
271  if(fireArg.isSet())
272  label->colourMapToNIH_Fire(belowValue, aboveValue);
273  if(aalArg.isSet())
274  label->colourMapToAAL(belowValue, aboveValue);
275  if(hotArg.isSet())
276  label->colourMapToHOT(belowValue, aboveValue);
277  label->volumePlot(imageReorient->GetOutput(), true, true);
278 
279  vtkVolumeCollection *volumeCollection = label->GetVolumes();
280 
281  volumeCollection->InitTraversal();
282  vtkVolume * volume = volumeCollection->GetNextVolume();
283  if(transformArg.isSet())
284  volume->SetUserTransform(transform);
285  model->AddVolume(volume);
286 }
287 else
288 {
289  cout << ">> Overlay: Isosurfacing" << endl;
290  const float linearScale = (aboveValue-belowValue)/N;
291  for(size_t j = 0; j < values.size(); j ++)
292  {
293  if(lineariseArg.isSet())
294  std::cout << ">> Iso surface label " << static_cast<unsigned>(values[j]) << " -> " << (j+offset)*linearScale << std::endl;
295  else
296  std::cout << ">> Iso surface label " << static_cast<unsigned>(values[j]) << std::endl;
297 
298  QScopedPointer<milxQtImage> label(new milxQtImage); //smart deletion
299  label->SetInput(eightBitImage->GetCharImage());
300  label->binaryThreshold(1.0, values[j], values[j]);
301  label->generateImage();
302 
303  QSharedPointer<milxQtModel> mdl(new milxQtModel);
304  mdl->generateIsoSurface(label->GetOutput(), 0, 0.5);
305  if(processArg.isSet())
306  {
307  mdl->quadricDecimate(0.25);
308  if(oversmoothArg.isSet())
309  mdl->smooth(500);
310  else
311  mdl->smoothSinc(15);
312  }
313  if(transformArg.isSet())
314  mdl->SetTransform(transform);
315  mdl->generateModel();
316 
317  //Colour maps
318  model->colourMapToJet(belowValue, aboveValue); //default
319  if(vtkArg.isSet())
320  model->colourMapToVTK(belowValue, aboveValue);
321  if(hsvArg.isSet())
322  model->colourMapToHSV(belowValue, aboveValue);
323  if(rainbowArg.isSet())
324  model->colourMapToRainbow(belowValue, aboveValue);
325  if(spectralArg.isSet())
326  model->colourMapToSpectral(belowValue, aboveValue);
327  if(nihArg.isSet())
328  model->colourMapToNIH(belowValue, aboveValue);
329  if(fireArg.isSet())
330  model->colourMapToNIH_Fire(belowValue, aboveValue);
331  if(aalArg.isSet())
332  model->colourMapToAAL(belowValue, aboveValue);
333  if(hotArg.isSet())
334  model->colourMapToHOT(belowValue, aboveValue);
335 
337  double colour[3];
338  if(lineariseArg.isSet())
339  model->GetLookupTable()->GetColor((j+offset)*linearScale, colour);
340  else
341  model->GetLookupTable()->GetColor(values[j], colour);
342  mdl->changeColour(colour[0], colour[1], colour[2]);
343 
344  model->AddActor(mdl->GetActor());
345  isoSurfaces.push_back(mdl); //prevent deletion
346  }
347 
348  if(saveArg.isSet())
349  {
350  QSharedPointer<milxQtModel> labelMesh(new milxQtModel);
351  for(size_t j = 0; j < isoSurfaces.size(); j ++)
352  {
353  vtkSmartPointer<vtkPolyData> labelPolyData = vtkSmartPointer<vtkPolyData>::New();
354  labelPolyData->DeepCopy(isoSurfaces[j].data()->GetOutput());
355  vtkSmartPointer<vtkFloatArray> labelValueArray = vtkSmartPointer<vtkFloatArray>::New();
356  labelValueArray->SetNumberOfComponents(1);
357  labelValueArray->SetNumberOfTuples(labelPolyData->GetNumberOfPoints());
358  labelValueArray->FillComponent(0, values[j]);
359  labelPolyData->GetPointData()->SetScalars(labelValueArray);
360  labelMesh->AddInput(labelPolyData);
361  }
362  labelMesh->generateModel();
363  milx::File::SaveModel(fileName, labelMesh.data()->GetOutput());
364  }
365 }
366  cout << ">> Overlay: Generating Scene" << endl;
367  model->generateRender();
368 
370  QScopedPointer<milxQtModel> surface(new milxQtModel); //smart deletion
371  if(surfaceArg.isSet())
372  {
373  success = reader->openModel(surfaceName.c_str(), surface.data());
374  if(!success)
375  {
376  milx::PrintError("Failed Reading surface. Check the surface type/file. Exiting.");
377  exit(EXIT_FAILURE);
378  }
379 
380  cout << ">> Overlaying surface" << endl;
381  surface->setName(surfaceName.c_str());
382  surface->generateModel();
383  if(wireframeArg.isSet())
384  surface->generateWireframe();
385  if(opacityArg.isSet()) //needs to be after generate Model
386  surface->SetOpacity(opacity);
387 
388  model->AddActor(surface->GetActor());
389  }
390  model->generateRender();
391 
392  mainWindow.setCentralWidget(model.data());
393  mainWindow.resize(windowWidth, windowHeight);
394  if(humanArg.isSet())
395  model->disableOrient();
396  if(whiteArg.isSet())
397  model->background(true);
398  if(axialArg.isSet())
399  model->viewToAxial();
400  if(coronalArg.isSet())
401  model->viewToCoronal();
402  if(saggitalArg.isSet())
403  model->viewToSagittal();
404  if(loadViewArg.isSet())
405  model->loadView();
406  if(loadViewFileArg.isSet())
407  model->loadView(loadViewName.c_str());
408  //Zoom
409  if(zoomArg.isSet())
410  {
411  vtkCamera *camera = model->GetRenderer()->GetActiveCamera();
412  camera->Zoom(zoomFactor);
413  }
414  cout << ">> Overlay: Rendering" << endl;
415  if(!onscreenArg.isSet())
416  model->OffScreenRenderingOn();
417  else
418  mainWindow.show();
419 
420  //Update view
421  model->GetRenderWindow()->Render();
422  qApp->processEvents();
423  cout << ">> Complete" << endl;
424 
425  if(!onscreenArg.isSet())
426  {
428  vtkSmartPointer<vtkWindowToImageFilter> windowToImage = vtkSmartPointer<vtkWindowToImageFilter>::New();
429  windowToImage->SetInput(model->GetRenderWindow());
430  windowToImage->Update();
431 
432  QScopedPointer<milxQtFile> writer(new milxQtFile); //Smart deletion
433  model->GetRenderWindow()->Render();
434  writer->saveImage(screenName.c_str(), windowToImage->GetOutput());
435 
436  model->OffScreenRenderingOff(); //Required to prevent double-free
437  return EXIT_SUCCESS;
438  }
439  else
440  return app.exec();
441 }
This class represents the MILX Qt Render Window Display object using QVTK.
This class represents the MILX Qt Image Display object using VTK.
Definition: milxQtImage.h:118
void PrintError(const std::string msg)
Displays a generic msg to standard error with carriage return.
Definition: milxGlobal.h:202
This class represents the MILX Qt File I/O object using VTK/ITK/Qt.
Definition: milxQtFile.h:60
This program Animates image slices and animates surfaces in the model view for creating movies...
static vtkMatrix4x4 * OpenITKTransform(std::string filename)
Opens an ITK transform file and converts it to a VTK transform matrix.
Definition: milxFile.cxx:190
This class represents the MILX Qt Model/Mesh Display object using VTK.
Definition: milxQtModel.h:115
The class provides 1D/2D and 3D plotting capability. This includes scatter and surface plots...
Definition: milxQtPlot.h:66
Represents an image (i.e. an regular rectangular array with scalar values) and their common operation...
Definition: milxImage.h:174
void PrintWarning(const std::string msg)
Displays a generic msg to standard output with carriage return.
Definition: milxGlobal.h:183
static bool SaveModel(const std::string filename, vtkSmartPointer< vtkPolyData > data, const bool binary=false)
Saves a model as a file, which can be a VTK XML or Legacy VTK PolyData File (i.e. either a *...
Definition: milxFile.cxx:628
void PrintInfo(const std::string msg)
Displays a generic msg to standard output with carriage return.
Definition: milxGlobal.h:174
std::string NumberToString(double num, unsigned zeroPad=0)
Number to string converter.
Definition: milxGlobal.h:112
static bool ReadImageInformation(const std::string filename, std::string &pixeltype, std::string &componentType, size_t &dimensions)
Reads just the header of an image file (without reading the image data), and writes the info into the...
Definition: milxFile.cxx:69
void setData(QPointer< milxQtImage > newImg, const bool forceDeepCopy=false)
Assigns the milxQtImage data to current image. You will need to call generate image after this...
int main(int argc, char *argv[])