SMILX  1.01
milxHausdorffDistance.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 =========================================================================*/
25 //ITK
26 #include <itkImage.h>
27 //VTK
28 #include <vtkSmartPointer.h>
29 #include <vtkFloatArray.h>
30 //SMILI
31 #include "milxDeformableModel.h"
32 #include "milxImage.h"
33 #include "milxFile.h"
34 
35 #include <tclap/CmdLine.h> //Command line parser library
36 
37 using namespace TCLAP;
38 
39 typedef float FloatPixelType;
40 typedef itk::Image<FloatPixelType, 3> FloatImageType;
41 typedef unsigned char InputPixelType;
42 typedef itk::Image<InputPixelType, 3> LabelImageType;
43 typedef LabelImageType::SizeValueType SizeValueType;
44 
45 int main(int argc, char* argv[])
46 {
47  //---------------------------
49  milx::PrintInfo("--------------------------------------------------------");
50  milx::PrintInfo("MILX-SMILI Hausdorff Distance Tool for Medical Imaging");
51  milx::PrintInfo("(c) Copyright CSIRO, 2012.");
52  milx::PrintInfo("Australian e-Health Research Centre, CSIRO.");
53  milx::PrintInfo("SMILI Version: " + milx::NumberToString(milx::Version));
54  milx::PrintInfo("Application Version: 1.00");
55  milx::PrintInfo("Processors to be used: " + milx::NumberToString(milx::NumberOfProcessors()/2));
56  milx::PrintInfo("--------------------------------------------------------\n");
57 
58  //---------------------------
60  CmdLine cmd("A Hausdorff Distance tool for models", ' ', milx::NumberToString(milx::Version));
61 
63  ValueArg<std::string> outputArg("o", "output", "Output model name", false, "hausdorff.vtk", "Output");
64  ValueArg<std::string> prefixArg("p", "prefix", "Output prefix for multiple output", false, "hausdorff_", "Output Prefix");
65  ValueArg<std::string> labelArg("l", "label", "Compute the distances from the labelled image to the surface(s) provided.", true, "labelling.nii.gz", "Label");
66  ValueArg<float> labelValueArg("", "labelvalue", "Set the label value for option --label.", true, 255, "Label Value");
67  ValueArg<size_t> caseArg("c", "case", "Set the case ID being done. Used to name extra output.", false, 0, "Case");
69  SwitchArg symmetricArg("s", "symmetric", "Compute forward and backward distances. This is required to get Hausdorff distance.", false);
70 
72  UnlabeledMultiArg<std::string> multinames("surfaces", "Surfaces to compute the distances with.", true, "Surfaces");
73 
75  cmd.add( multinames );
76  cmd.add( outputArg );
77  cmd.add( prefixArg );
78  cmd.add( labelArg );
79  cmd.add( labelValueArg );
80  cmd.add( symmetricArg );
81  cmd.add( caseArg );
82 
84  cmd.parse( argc, argv );
85 
87  //Filenames of surfaces
88  std::vector<std::string> filenames = multinames.getValue();
89  const std::string outputName = outputArg.getValue();
90  const std::string prefixName = prefixArg.getValue();
91  const std::string labelName = labelArg.getValue();
92  const float labelValue = labelValueArg.getValue();
93  const size_t caseID = caseArg.getValue();
94 
96  itk::MultiThreader::SetGlobalDefaultNumberOfThreads(milx::NumberOfProcessors()/2);
97 
98  //Check arguments
99  //Most of the checking is done by TCLAP
100  if(labelValueArg.isSet())
101  {
102  if(!labelArg.isSet())
103  {
104  cerr << "Error in arguments! Label argument needs to be used with the label value argument." << endl;
105  exit(EXIT_FAILURE);
106  }
107  }
108 
110  bool success = false;
111 
112  //Load Models
113  cout << ">> Hausdorff Distance: Reading Models" << endl;
114  vtkSmartPointer<vtkPolyDataCollection> collection;
115  success = milx::File::OpenModelCollection(filenames, collection);
116  if(!success) //Error printed inside
117  {
118  cerr << "Error reading models!" << endl;
119  exit(EXIT_FAILURE);
120  }
121  const size_t n = collection->GetNumberOfItems();
122  if(n < 1)
123  {
124  cerr << "At least one model must be provided!" << endl;
125  exit(EXIT_FAILURE);
126  }
127  collection->InitTraversal();
128  vtkSmartPointer<vtkPolyData> surface = collection->GetNextItem();
129  double bounds[6];
130  surface->GetBounds(bounds);
131  std::cerr << "Done" << std::endl;
132 
133  //Read labels
134  itk::SmartPointer<LabelImageType> labelledImage, resizedLabelledImage, thresholdedImage; //smart deletion
135  itk::SmartPointer<FloatImageType> distanceMap;
136  const bool binary = false, signedDistance = true, insideDistance = false, squaredDistance = false;
137  const unsigned char objectValue = 255;
138  if(labelArg.isSet())
139  {
140  cout << ">> Hausdorff Distance: Using Labelling" << endl;
141  cout << "Loading... " << endl;
142  success = milx::File::OpenImage<LabelImageType>(labelName, labelledImage);
143 
144  cout << "Thresholding... " << endl;
145  //~ thresholdedImage = milx::Image<LabelImageType>::BinaryThresholdImage<LabelImageType>(resizedLabelledImage, 0, objectValue, labelValue, labelValue);
146  thresholdedImage = milx::Image<LabelImageType>::BinaryThresholdImage<LabelImageType>(labelledImage, 0, objectValue, labelValue, labelValue);
147 
148  cout << "Computing Distance Map of Label... " << endl;
149  distanceMap = milx::Image<LabelImageType>::DistanceMap<FloatImageType>(thresholdedImage, binary, signedDistance, insideDistance, squaredDistance);
150 
151 // success = milx::File::SaveImage<FloatImageType>(prefixName + "_dmap.nii.gz", distanceMap);
152  }
153  if(!success)
154  {
155  cerr << "Error Reading one or more of the input files. Exiting." << endl;
156  exit(EXIT_FAILURE);
157  }
158 
159  //Read labels and generate iso surface
160  //Clip mesh to ensure same FoV
161  cout << ">> Hausdorff Distance: Computing Absolute Surface Distances... " << endl;
162  vtkSmartPointer<vtkFloatArray> weights = vtkSmartPointer<vtkFloatArray>::New();
163  milx::DeformableModel model(surface);
164  model.RemoveScalars(); //remove because mark will not set as 1.0, causing problems with meshes having scalars already
165  model.MarkSurfaceInsideImage<FloatImageType>(model.GetOutput(), distanceMap, weights); //sets weights as scalars in here
166  model.Clip(1.0, 1.0);
167 
168  //Save distances as scalars on mesh
169  const bool absValues = true;
170  vtkSmartPointer<vtkFloatArray> scalars = model.SurfaceScalarsFromImage<FloatImageType>(model.GetOutput(), distanceMap, absValues);
171  scalars->SetName("Surface Distance Errors");
172  model.SetScalars(scalars);
173 
174  //Restore correspondence to copy scalars over to full (unclipped) mesh, since the mesh is clipped to ensure same FoV
175  cout << "Regions of Surface Outside the Image are marked with -1." << endl;
176  weights->FillComponent(0, -1.0); //resized within MarkSurfaceInsideImage function
177  for(int j = 0; j < model.GetOutput()->GetNumberOfPoints(); j ++)
178  {
179  vtkIdType index = surface->FindPoint(model.GetOutput()->GetPoint(j));
180  weights->SetTuple1(index, scalars->GetTuple1(j));
181  }
182 
183  milx::DeformableModel symmetricModel;
184  if(symmetricArg.isSet())
185  {
186  if(labelArg.isSet())
187  {
188  //Voxelise surface
189  LabelImageType::SpacingType labelSpacing = labelledImage->GetSpacing();
190  double bounds[6];
191  model.GetOutput()->GetBounds(bounds);
192  vtkSmartPointer<vtkImageData> voxelisedModel = model.Voxelise(objectValue, labelSpacing.GetDataPointer(), bounds);
193  LabelImageType::Pointer modelLabel = milx::Image<LabelImageType>::ConvertVTKImageToITKImage(voxelisedModel);
194  cout << "Computing Distance Map of Surface... " << endl;
195  itk::SmartPointer<FloatImageType> modelDistanceMap = milx::Image<LabelImageType>::DistanceMap<FloatImageType>(modelLabel, binary, signedDistance, insideDistance, squaredDistance);
196 
197  //~ //Debug, write distance map
198  //~ milx::File::SaveImage(prefixName + "_model_distance_map.nii.gz", modelDistanceMap);
199 
200  //Isosurface label
201  cout << "Generating Iso Surface... " << endl;
202  vtkSmartPointer<vtkImageData> labelledImageVTK = vtkSmartPointer<vtkImageData>::New();
203  labelledImageVTK->DeepCopy( milx::Image<LabelImageType>::ConvertITKImageToVTKImage(thresholdedImage) ); //no orientation kept here
204  milx::PrintDebug("Apply orientation to VTK Image form of Labelled Image");
205  vtkSmartPointer<vtkMatrix4x4> matrix = vtkSmartPointer<vtkMatrix4x4>::New(); //not used but required by ApplyOrientationToVTKImage
206  vtkSmartPointer<vtkImageData> restoredLabelledImageVTK = vtkSmartPointer<vtkImageData>::New();
207  restoredLabelledImageVTK->DeepCopy( milx::Image<LabelImageType>::ApplyOrientationToVTKImage(labelledImageVTK, labelledImage, matrix, true) );
208 
209  //Write result
210  //~ cout << "Saving VTK Image Result... " << endl;
211  //~ milx::File::SaveImage<LabelImageType>(prefixName + "_vtkimage.vti", restoredLabelledImageVTK);
212 
213  //Iso surface
214  milx::DeformableModel isoSurface;
215  milx::PrintDebug("VTK Marching cubes");
216  isoSurface.IsoSurface(restoredLabelledImageVTK, objectValue-1);
217 
218  //Write result
219 // cout << "Saving Iso Surface Result... " << endl;
220 // milx::File::SaveModel(prefixName + milx::NumberToString(caseID) + "_isosurface.vtp", isoSurface.GetOutput());
221 
222  cout << ">> Hausdorff Distance: Computing Backward Absolute Surface Distances... " << endl;
223  vtkSmartPointer<vtkFloatArray> weights2 = vtkSmartPointer<vtkFloatArray>::New();
224  symmetricModel.SetInput(isoSurface.GetOutput());
225  symmetricModel.RemoveScalars(); //remove because mark will not set as 1.0, causing problems with meshes having scalars already
226  symmetricModel.MarkSurfaceInsideImage<FloatImageType>(symmetricModel.GetOutput(), modelDistanceMap, weights2); //sets weights2 as scalars in here
227  isoSurface.SetScalars(weights2);
228  //~ cout << "Saving Clipped Iso Surface Result... " << endl;
229  //~ milx::File::SaveModel(prefixName + "_isosurface_clipped.vtp", symmetricModel.GetOutput());
230  symmetricModel.Clip(1.0, 1.0);
231 
232  //Save distances as scalars on mesh
233  const bool absValues = true;
234  vtkSmartPointer<vtkFloatArray> scalars2 = model.SurfaceScalarsFromImage<FloatImageType>(symmetricModel.GetOutput(), modelDistanceMap, absValues);
235  scalars2->SetName("Surface Distance Errors");
236  symmetricModel.SetScalars(scalars2);
237 
238  //Restore correspondence to copy scalars over to full (unclipped) mesh, since the mesh is clipped to ensure same FoV
239  weights2->FillComponent(0, -1.0);
240  for(int j = 0; j < symmetricModel.GetOutput()->GetNumberOfPoints(); j ++)
241  {
242  vtkIdType index = isoSurface.GetOutput()->FindPoint(symmetricModel.GetOutput()->GetPoint(j));
243  weights2->SetTuple1(index, scalars2->GetTuple1(j));
244  }
245 
247  double range1[2], range2[2];
248  surface->GetScalarRange(range1);
249  isoSurface.GetOutput()->GetScalarRange(range2);
250  std::cout << "Forward Max Distance: " << range1[1] << std::endl;
251  std::cout << "Backward Max Distance: " << range2[1] << std::endl;
252  std::cout << "Hausdorff Distance: " << milx::Maximum<double>(range1[1], range2[1]) << std::endl;
253 
254  //Write result
255  cout << "Saving Backward Result... " << endl;
256  milx::File::SaveModel(prefixName + milx::NumberToString(caseID) + "_backward.vtp", isoSurface.GetOutput());
257  }
258  }
259 
260  //Copy result over as scalars
261  surface->GetPointData()->SetScalars(weights);
262 
263  //Write result
264  cout << "Saving Result... " << endl;
265  milx::File::SaveModel(outputName, surface);
266 
267  cout << ">> Hausdorff Distance: Operation Complete" << endl;
268  return EXIT_SUCCESS;
269 }
void PrintDebug(const std::string msg)
Displays a generic msg to standard error with carriage return if in Debug mode.
Definition: milxGlobal.h:192
void RemoveScalars()
Removes all the scalars from the current model. Same as ClearScalars()
Definition: milxModel.cxx:265
static itk::SmartPointer< TImage > ConvertVTKImageToITKImage(vtkSmartPointer< vtkImageData > img)
Converts a VTK image object to an ITK image object.
Definition: milxImage.h:1093
This program Animates image slices and animates surfaces in the model view for creating movies...
static bool OpenModelCollection(std::vector< std::string > filenames, vtkSmartPointer< vtkPolyDataCollection > &collection)
Opens model files, which can be a VTK XML, Legacy VTK PolyData File (i.e. either a *...
Definition: milxFile.cxx:611
void SetInput(vtkSmartPointer< vtkPolyData > model)
Assigns the input model to class.
Definition: milxModel.cxx:130
Represents an image (i.e. an regular rectangular array with scalar values) and their common operation...
Definition: milxImage.h:174
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 void MarkSurfaceInsideImage(vtkSmartPointer< vtkPolyData > &surface, itk::SmartPointer< TImage > img, vtkFloatArray *weights, const float outsideValue=0.0)
Mark existing surface with all parts outside the image as &#39;outsideValue&#39; value. Inside will remain th...
Represents a deformable model (i.e. a model with cells and scalar values that has special members for...
void Clip(const coordinateType belowValue, const coordinateType aboveValue)
Clip the mesh based on a scalar threshold of the mesh scalars.
Definition: milxModel.cxx:675
vtkSmartPointer< vtkPolyData > & GetOutput()
Returns the current model, i.e. the result of the latest operation ITK/VTK style. ...
Definition: milxModel.h:224
int main(int argc, char *argv[])
void SetScalars(vtkSmartPointer< vtkDataArray > modelScalars)
Sets the scalar field of the mesh (values per vertex of the surface)
Definition: milxModel.cxx:218
void IsoSurface(vtkSmartPointer< vtkImageData > img, double minValue)
Definition: milxModel.cxx:744
unsigned NumberOfProcessors()
Number of processors or cores on the machine.
Definition: milxGlobal.h:124