Shapeworks Studio  2.1
Shape analysis software suite
itkPSMCommandLineClass2DTest.cxx
1 /*=========================================================================
2  *
3  * Copyright Insight Software Consortium
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0.txt
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  *=========================================================================*/
18 
19 #include <iostream>
20 #include <vector>
21 #include <math.h>
22 #include <numeric>
23 #include "itkPSMCommandLineClass.h"
24 #include "itkImageFileReader.h"
25 #include "itkImageFileWriter.h"
26 //#include "itkPSMCommandLineClass.cxx"
27 #include "itkPSMImplicitSurfaceImageFilter.h"
28 #include "itkExceptionObject.h"
29 #include "itkDOMNode.h"
30 #include "itkDOMNodeXMLWriter.h"
31 #include "itkTranslationTransform.h"
32 #include "itkImageRegionIteratorWithIndex.h"
33 #include "itkNearestNeighborInterpolateImageFunction.h"
34 #include "itkResampleImageFilter.h"
35 #include "itkChangeInformationImageFilter.h"
36 #include "itkNumericTraits.h"
37 #include "itkArray.h"
38 
39 namespace itk{
40 // Function for generating random Gaussians using polar Box-Muller method
41 static void RandomGaussian(double n, double mean, double sigma, std::vector<double> & r)
42 {
43  double r2, x = 0, y = 0;
44  for(int i = 0; i < n; i++)
45  {
46  r2 = 0;
47  // Generate random Gaussians
48  while(r2 >= 1.0 || r2 == 0)
49  {
50  x = 2.0 * static_cast<double>(rand()) /
51  static_cast<double>(RAND_MAX) - 1.0;
52  y = 2.0 * static_cast<double>(rand()) /
53  static_cast<double>(RAND_MAX) - 1.0;
54  r2 = x * x + y * y;
55  }
56  r[i] = mean + sigma * y * sqrt(-2.0 * log(r2) / r2);
57  // Check that random radius value does not exceed half the set image size.
58  // This avoids an 'out of bounds' error during optimization.
59  if (r[i] > 100.0)
60  r[i] = 99.0;
61  }
62 }
63 
64 // Function for calculating slope of regression line
65 static double Slope(const std::vector<double>& x, const std::vector<double>& y)
66 {
67  if(x.size() != y.size())
68  {
69  std::cerr << "Error! Vectors are not the same length." << std::endl;
70  return EXIT_FAILURE;
71  }
72  double n = x.size();
73 
74  double avgX = std::accumulate(x.begin(), x.end(), 0.0) / n;
75  double avgY = std::accumulate(y.begin(), y.end(), 0.0) / n;
76 
77  double numerator = 0.0;
78  double denominator = 0.0;
79 
80  for(int i=0; i<n; ++i)
81  {
82  numerator += (x[i] - avgX) * (y[i] - avgY);
83  denominator += (x[i] - avgX) * (x[i] - avgX);
84  }
85 
86  if(denominator == 0)
87  {
88  std::cerr << "Error! Divide by zero." << std::endl;
89  return EXIT_FAILURE;
90  }
91  // Calculate slope
92  double b1 = numerator / denominator;
93  // Calculate Y intercept
94  //double b0 = avgY - (b1*avgX);
95 
96  double sumXY = 0.0, sum_squareX = 0.0, sum_squareY = 0.0;
97  for(int i=0; i<n; i++)
98  {
99  sumXY += x[i] * y[i];
100  sum_squareX += x[i] * x[i];
101  sum_squareY += y[i] * y[i];
102  }
103  // Calculate correlation coeff and coeff of determination
104  double r_xy = sumXY / sqrt(sum_squareX * sum_squareY);
105  std::cout << "\nCorrelation coefficient = " << r_xy << std::endl;
106  std::cout << "Coefficient of determination = " << (r_xy * r_xy) << std::endl;
107 
108  return b1;
109 }
110 
111 // Function for centering the image in index space
112 static void Center(itk::Image<float, 2> *input)
113 {
114  typedef itk::Image<float, 2> ImageType;
115  ImageType::PixelType m_BackgroundValue = itk::NumericTraits< ImageType::PixelType >::Zero;
116  // Reset the physical coordinates at the center of the image to (0,0,0)
117  ImageType::PointType lower;
118  ImageType::PointType upper;
119  ImageType::PointType ul_physical_coords;
120  input->TransformIndexToPhysicalPoint(input->GetBufferedRegion().GetIndex(), lower);
121  input->TransformIndexToPhysicalPoint(input->GetBufferedRegion().GetIndex()
122  + input->GetBufferedRegion().GetSize(), upper);
123  for (unsigned int i = 0; i < 2; i++)
124  {
125  ul_physical_coords[i] = -(lower[i] + upper[i]) / 2.0;
126  }
127 
128  // The following command sets the physical coordinates
129  // of pixel number 0,0,0... (i.e. the upper-left corner of the volume).
130  input->SetOrigin(ul_physical_coords);
131 
132  // Find the center of mass.
133  itk::ImageRegionIteratorWithIndex<ImageType> oit(input, input->GetBufferedRegion());
134  oit.GoToBegin();
135 
136  itk::Array<double> params(2);
137  params.Fill(0.0);
138  double count = 0.0;
139  itk::Point<double, 2> point;
140  for (; ! oit.IsAtEnd(); ++oit)
141  {
142  if (oit.Get() != m_BackgroundValue)
143  {
144  // Get the physical index from the image index.
145  input->TransformIndexToPhysicalPoint(oit.GetIndex(), point);
146  for (unsigned int i = 0; i < 2; i++) { params[i] += point[i]; }
147  count += 1.0;
148  }
149  }
150 
151  // Compute center of mass.
152  for (unsigned int i = 0; i < 2; i++)
153  {
154  params[i] = params[i] / count;
155  }
156 
157  // Translate the segmentation into the output
158  itk::TranslationTransform<double,2>::Pointer trans
159  = itk::TranslationTransform<double,2>::New();
160  trans->SetParameters(params);
161 
162  itk::NearestNeighborInterpolateImageFunction<ImageType,double>::Pointer
163  interp = itk::NearestNeighborInterpolateImageFunction<ImageType,double>::New();
164 
165  itk::ResampleImageFilter<ImageType, ImageType>::Pointer resampler
166  = itk::ResampleImageFilter<ImageType, ImageType>::New();
167  resampler->SetOutputParametersFromImage(input);
168  resampler->SetTransform(trans);
169  resampler->SetInterpolator(interp);
170  resampler->SetInput(input);
171  resampler->Update();
172 }
173 } // end namespace itk
174 
175 // Function to return the greater absolute of two values with different signs
176 static bool abs_compare(double a, double b)
177 {
178  return (std::abs(a) < std::abs(b));
179 }
180 
182 int itkPSMCommandLineClass2DTest( int argc, char* argv[] )
183 {
184  std::string output_path = "";
185  std::string input_path_prefix = "";
186  std::string errstring = "";
187  // Check for proper arguments
188  if (argc < 1)
189  {
190  std::cout << "Wrong number of arguments. \nUse: "
191  << "itkPSMCommandLineClass2DTest [output_path] [input_path]\n"
192  << "Note that input_path will be prefixed to any file names and paths in the xml parameter file.\n"
193  << std::endl;
194  return EXIT_FAILURE;
195  }
196 
197  if (argc > 1)
198  {
199  output_path = std::string(argv[1]);
200  }
201 
202  if (argc > 2)
203  {
204  input_path_prefix = std::string(argv[2]);
205  }
206 
207  try
208  {
209  // Standard typedefs
210  typedef itk::Image< unsigned char, 2 > ImageType;
211  typedef itk::Image<float, 2> DTImageType;
212  typedef itk::PSMImplicitSurfaceImageFilter<ImageType, DTImageType> ImplicitSurfaceImageFilterType;
213  typedef itk::PSMCommandLineClass<2> CommandLineClassType;
214  ImplicitSurfaceImageFilterType::Pointer implicitSurfaceFilter = ImplicitSurfaceImageFilterType::New();
215 
216  // Generate 10 ellipses that will be used as inputs to the optimization.
217  // Variables for generating random radius values.
218  int number_of_ellipses = 10;
219  int r1 = 30;
220  int r2 = 80;
221 
222  srand(time(NULL));
223  // Randomly generate a value for sigma greater than 1
224  double sigma_intercept = ((double)rand()) / ((double)RAND_MAX) * 9.9 + 2.0;
225  std::cout << "sigma_intercept: " << sigma_intercept << std::endl;
226 
227  std::vector<double> radius_minor, radius_major;
228  radius_minor.resize(number_of_ellipses);
229  radius_major.resize(number_of_ellipses);
230 
231  // Generate the radius values
232  itk::RandomGaussian(number_of_ellipses, r1, sigma_intercept, radius_minor);
233  itk::RandomGaussian(number_of_ellipses, r2, sigma_intercept, radius_major);
234 
235  for(int i=0; i<radius_major.size(); i++)
236  {
237  std::cout << "Input_R: " << radius_major[i] << " Input_r: " << radius_minor[i] << std::endl;
238  }
239 
240  // Create DOMNode elements for output project file
241  itk::DOMNode::Pointer inputDOMObject = itk::DOMNode::New();
242  inputDOMObject->SetName( "psm_project" );
243 
244  // Create nodes and add them to the DOM object
245  itk::DOMNode::Pointer data_node = itk::DOMNode::New();
246  data_node->SetName( "data" );
247  inputDOMObject->AddChildAtEnd( data_node );
248 
249  // Create the distance transforms node
250  itk::DOMNode::Pointer distance_trans_node = itk::DOMNode::New();
251  distance_trans_node->SetName( "distance_transforms" );
252  data_node->AddChildAtEnd( distance_trans_node );
253 
254  // Create the model node
255  itk::DOMNode::Pointer model_node = itk::DOMNode::New();
256  model_node->SetName( "model" );
257  data_node->AddChildAtEnd( model_node );
258 
259  // Add an attribute to the model node
260  std::string att = "optimized";
261  itk::FancyString fs;
262  fs << att;
263  model_node->SetAttribute( "name", att );
264 
265  // Create the optimization node
266  itk::DOMNode::Pointer opt_node = itk::DOMNode::New();
267  opt_node->SetName( "optimization" );
268  inputDOMObject->AddChildAtEnd( opt_node );
269 
270  // Default optimization parameters
271  int num_of_scales = 8, reg_ds = 5000, max_iter = 1000;
272  double reg_init = 10.0, reg_final = 2.0, tol = 0.01;
273  fs << itk::ClearContent << num_of_scales;
274  opt_node->SetAttribute( "number_of_scales", fs );
275 
276  for(int i=0; i<num_of_scales; i++)
277  {
278  // Create the scale node with default paramter values
279  itk::DOMNode::Pointer scale_node = itk::DOMNode::New();
280  scale_node->SetName( "scale" );
281  fs << itk::ClearContent << i;
282  scale_node->SetAttribute( "number", fs );
283  fs << itk::ClearContent << reg_init;
284  scale_node->SetAttribute( "regularization_initial", fs );
285  fs << itk::ClearContent << reg_final;
286  scale_node->SetAttribute( "regularization_final", fs );
287  fs << itk::ClearContent << reg_ds;
288  scale_node->SetAttribute( "regularization_decayspan", fs );
289  fs << itk::ClearContent << tol;
290  scale_node->SetAttribute( "tolerance", fs );
291  fs << itk::ClearContent << max_iter;
292  scale_node->SetAttribute( "maximum_iterations", fs );
293  // Add the scale nodes to the optimization node
294  opt_node->AddChildAtEnd( scale_node );
295  }
296  // Distance transform parameters
297  double sigma = 0.5;
298  double isosurface_value = 0.5;
299 
300  // Iterator for ellipse image
301  typedef itk::ImageRegionIteratorWithIndex<ImageType> OutputIteratorType;
302 
303  std::cout << "Writing ellipse distance transforms..." << std::endl;
304  // Now generate the ellipses and their distance transforms
305  for(int i=0; i<number_of_ellipses; i++)
306  {
307  // Create an ellipse image with specified size and spacing.
308  ImageType::Pointer ellipseImage = ImageType::New();
309  ImageType::SizeType regionSize;
310  regionSize[0] = 200;
311  regionSize[1] = 200;
312 
313  ImageType::IndexType regionIndex;
314  regionIndex[0] = 0;
315  regionIndex[1] = 0;
316 
317  ImageType::RegionType region;
318  region.SetSize(regionSize);
319  region.SetIndex(regionIndex);
320 
321  ImageType::SpacingType spacing;
322  spacing[0] = 1;
323  spacing[1] = 1;
324  ellipseImage->SetSpacing( spacing );
325  ellipseImage->SetRegions(region);
326  ellipseImage->Allocate();
327 
328  ellipseImage->FillBuffer(0);
329 
330  double r = radius_minor[i];
331  double R = radius_major[i];
332 
333  OutputIteratorType it( ellipseImage, ellipseImage->GetRequestedRegion() );
334  it.GoToBegin();
335 
336  ImageType::IndexType index;
337  // Set pixels which are within ellipse area to white. Center of ellipse is (100,100)
338  for(int y=-r; y<=r; y++)
339  {
340  for(int x=-R; x<=R; x++)
341  {
342  if(x*x*r*r + y*y*R*R <= r*r*R*R)
343  {
344  index[0] = 100+x;
345  index[1] = 100+y;
346  it.SetIndex( index );
347  it.Set( itk::NumericTraits<ImageType::PixelType>::max() );
348  }
349  }
350  }
351 
352  // Generate the distance transforms
353  implicitSurfaceFilter->SetSmoothingSigma(sigma);
354  implicitSurfaceFilter->SetIsosurfaceValue(isosurface_value);
355  implicitSurfaceFilter->SetInput(ellipseImage);
356  implicitSurfaceFilter->Update();
357 
358  // Center the image in index space
359  itk::Center(implicitSurfaceFilter->GetOutput());
360 
361  std::string str = "ellipse";
362  typedef itk::ImageFileWriter< DTImageType > WriterType;
363  WriterType::Pointer writer = WriterType::New();
364  std::ostringstream ss;
365  ss << i;
366  std::string iname = output_path + str + "_DT_Test" + ss.str() + ".nrrd";
367  writer->SetFileName( iname.c_str() );
368  writer->SetInput( implicitSurfaceFilter->GetOutput() );
369  std::cout << "....";
370  writer->Update();
371 
372  // Add the image file names to the node
373  distance_trans_node->AddTextChildAtEnd( iname );
374 
375  // Add the output model names to the node
376  std::string opt_name = str + "_" + ss.str() + "_optimized.lpts";
377  model_node->AddTextChildAtEnd( opt_name );
378  }
379 
380  // Write the DOM object to an XML file
381  //const char*
382  std::string outputXMLFileName = "PSMCommandLineClass2DTest.xml";
383  std::string projectFileName = output_path + outputXMLFileName;
384  itk::DOMNodeXMLWriter::Pointer writer_xml = itk::DOMNodeXMLWriter::New();
385  writer_xml->SetInput( inputDOMObject );
386  writer_xml->SetFileName( projectFileName.c_str() );
387  writer_xml->Update();
388 
389  // Run the command line tool with the newly generated XML project
390  // file as input.
391  itk::PSMCommandLineClass<2>::Pointer psmClass = itk::PSMCommandLineClass<2>::New();
392  psmClass->Run( "PSMCommandLineClass2DTest.xml", output_path, output_path );
393 
394  CommandLineClassType::EntropyModelFilterType::Pointer filter = psmClass->GetPSMFilter();
395 
396  std::vector<double> output_x_coord;
397  std::vector<double> output_y_coord;
398  std::vector<double> output_radius_minor;
399  std::vector<double> output_radius_major;
400  // Find the radii of the output ellipse shapes
401  for (unsigned int d=0; d<filter->GetParticleSystem()->GetNumberOfDomains(); d++)
402  {
403  for (unsigned int ind=0; ind<filter->GetParticleSystem()->GetNumberOfParticles(d); ind++)
404  {
405  output_x_coord.push_back( filter->GetParticleSystem()->GetPosition(ind,d)[0] ); // Get x co-ord
406  output_y_coord.push_back( filter->GetParticleSystem()->GetPosition(ind,d)[1] ); // Get y co-ord
407  }
408  // Find the maxiumum absolute x and y co-ord values for each output ellipse
409  output_radius_minor.push_back( std::abs(*std::max_element(output_y_coord.begin(), output_y_coord.end(), abs_compare)) );
410  output_radius_major.push_back( std::abs(*std::max_element(output_x_coord.begin(), output_x_coord.end(), abs_compare)) );
411  output_x_coord.clear();
412  output_y_coord.clear();
413  }
414  // Print out the input radii along with mean and standard deviation
415  std::cout << "\nR: major ellipse radius r: minor ellipse radius" << std::endl;
416  for(int i=0; i<radius_major.size(); i++)
417  {
418  std::cout << "Input_R: " << radius_major[i] << " Input_r: " << radius_minor[i] << std::endl;
419  }
420 
421  std::cout << "\nInput R_mean = " << r2 << std::endl;
422  std::cout << "Input R_std_dev = " << sigma_intercept << std::endl;
423  std::cout << "Input r_mean = " << r1 << std::endl;
424  std::cout << "Input r_std_dev = " << sigma_intercept << std::endl;
425 
426  // Linear regression slope calculations
427  double sl = itk::Slope(radius_major, radius_minor);
428  std::cout << "Regression line slope at input = " << sl << "\n" << std::endl;
429 
430  for(int i=0; i<output_radius_minor.size(); i++)
431  {
432  std::cout << "Output_R: " << output_radius_major[i] << " Output_r: " << output_radius_minor[i] << std::endl;
433  }
434 
435  // Calculate the mean and standard deviation of the output radii
436  double mean_R_output = std::accumulate(output_radius_major.begin(), output_radius_major.end(), 0.0) / number_of_ellipses;
437  double mean_r_output = std::accumulate(output_radius_minor.begin(), output_radius_minor.end(), 0.0) / number_of_ellipses;
438 
439  double sq_sum_R = std::inner_product(output_radius_major.begin(), output_radius_major.end(), output_radius_major.begin(), 0.0);
440  double stdev_R = std::sqrt((sq_sum_R / output_radius_major.size()) - (mean_R_output * mean_R_output) );
441 
442  std::cout << "\nOutput R_mean = " << mean_R_output << std::endl;
443  std::cout << "Output R_std_dev = " << stdev_R << std::endl;
444 
445  double sq_sum_r = std::inner_product(output_radius_minor.begin(), output_radius_minor.end(), output_radius_minor.begin(), 0.0);
446  double stdev_r = std::sqrt((sq_sum_r / output_radius_minor.size()) - (mean_r_output * mean_r_output) );
447 
448  std::cout << "Output r_mean = " << mean_r_output << std::endl;
449  std::cout << "Output r_std_dev = " << stdev_r << std::endl;
450 
451  sl = itk::Slope(output_radius_major, output_radius_minor);
452  std::cout << "Regression line slope for output = " << sl << "\n" << std::endl;
453  }
454 
455  catch(itk::ExceptionObject &e)
456  {
457  std::cerr << "ITK exception with description: " << e.GetDescription()
458  << "\n at location:" << e.GetLocation()
459  << "\n in file:" << e.GetFile() << std::endl;
460  return EXIT_FAILURE;
461  }
462 
463  catch(...)
464  {
465  errstring = "Unknown exception thrown";
466  return EXIT_FAILURE;
467  }
468 
469  return EXIT_SUCCESS;
470 }
This filter generates an antialiased and smoothed signed distance transform from the input label map ...
void Run(const char *fname, std::string input_path_prefix, std::string output_path)
This class provides a command line tool to run the Particle Shape Modeling. It runs the optimization ...