ITK  4.13.0
Insight Segmentation and Registration Toolkit
itkTestDriverInclude.h
Go to the documentation of this file.
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  *
20  * Portions of this file are subject to the VTK Toolkit Version 3 copyright.
21  *
22  * Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
23  *
24  * For complete copyright, license and disclaimer of warranty information
25  * please refer to the NOTICE file at the top of the ITK source tree.
26  *
27  *=========================================================================*/
28 #ifndef itkTestDriverInclude_h
29 #define itkTestDriverInclude_h
30 //
31 // This file is used by the TestDriver executables generated by CMake's
32 // create_test_sourcelist. It defines a function, ProcessArguments
33 // that processes the command line for the test driver prior to
34 // invoking the test. It also defines the RegressionTestImage function
35 // that is called after a test has been run by the driver.
36 // command line options prior to invoking the test.
37 //
38 
39 #include "itksys/Process.h"
40 
41 #include "itkWin32Header.h"
42 #include <map>
43 #include <algorithm>
44 #include <string>
45 #include <iostream>
46 #include <fstream>
47 #include "itkMultiThreader.h"
48 #include "itkImage.h"
49 #include "itkImageFileReader.h"
50 #include "itkImageFileWriter.h"
56 #include "itksys/SystemTools.hxx"
57 #include "itkIntTypes.h"
58 #if defined(LINUX) && !defined(__MINGW32__) && defined(ITK_HAS_FEENABLEEXCEPT)
60 #endif
61 #include "vnl/vnl_sample.h"
62 
63 #define ITK_TEST_DIMENSION_MAX 6
64 
65 int RegressionTestImage(const char *testImageFilename,
66  const char *baselineImageFilename,
67  int reportErrors,
68  double intensityTolerance,
69  ::itk::SizeValueType numberOfPixelsTolerance = 0,
70  unsigned int radiusTolerance = 0,
71  bool verifyInputInformation = true,
72  double coordinateTolerance = 1.0e-6,
73  double directionTolerance = 1.0e-6);
74 
75 int HashTestImage( const char *testImageFilename,
76  const std::string md5hash );
77 
78 
79 std::map< std::string, int > RegressionTestBaselines(char *);
80 
81 typedef std::pair< char *, char * > ComparePairType;
82 
83 // A structure to hold regression test parameters
84 typedef struct
85 {
86  std::vector< ComparePairType > compareList;
89  unsigned int radiusTolerance;
94 
96 
97 
98 typedef std::pair< const char *, std::vector<std::string> > HashPairType;
99 
100 std::vector< HashPairType > hashTestList;
101 
102 typedef char ** ArgumentStringType;
103 
104 
105 // Types to hold parameters that should be processed later
106 typedef std::vector< char * > ArgumentsList;
107 
109 {
111 
116 };
117 
118 // A structure to hold redirect output parameters
119 typedef struct
120 {
121  bool redirect;
122  std::string fileName;
124 
126 
127 void usage()
128 {
129  std::cerr << "usage: itkTestDriver [options] prg [args]" << std::endl;
130  std::cerr << " itkTestDriver --no-process [options]" << std::endl;
131  std::cerr << std::endl;
132  std::cerr << "itkTestDriver alter the environment, run a test program and compare the images" << std::endl;
133  std::cerr << "produced." << std::endl;
134  std::cerr << std::endl;
135  std::cerr << "Options:" << std::endl;
136  std::cerr << " --add-before-libpath PATH" << std::endl;
137  std::cerr << " Add a path to the library path environment. This option take care of" << std::endl;
138  std::cerr << " choosing the right environment variable for your system." << std::endl;
139  std::cerr << " This option can be used several times." << std::endl;
140  std::cerr << std::endl;
141  std::cerr << " --add-before-env NAME VALUE" << std::endl;
142  std::cerr << " Add a VALUE to the variable name in the environment." << std::endl;
143  std::cerr << " The seperator used is the default one on the system." << std::endl;
144  std::cerr << " This option can be used several times." << std::endl;
145  std::cerr << std::endl;
146  std::cerr << " --add-before-env-with-sep NAME VALUE SEP" << std::endl;
147  std::cerr << " Add a VALUE to the variable name in the environment using the provided separator." << std::endl;
148  std::cerr << " This option can be used several times." << std::endl;
149  std::cerr << std::endl;
150  std::cerr << " --remove-env NAME" << std::endl;
151  std::cerr << " Remove the variable name from the environment." << std::endl;
152  std::cerr << " This option can be used several times." << std::endl;
153  std::cerr << std::endl;
154  std::cerr << " --compare TEST BASELINE" << std::endl;
155  std::cerr << " Compare the TEST image to the BASELINE one." << std::endl;
156  std::cerr << " This option can be used several times." << std::endl;
157  std::cerr << std::endl;
158  std::cerr << " --compare-MD5 TEST md5hash0 [ md5hash1 ... ]" << std::endl;
159  std::cerr << " Compare the TEST image file's md5 hash to the provided hash." << std::endl;
160  std::cerr << " md5hash0 is required and assumed to be a hash." << std::endl;
161  std::cerr << " Additional arguments are considered hashes when the string is 32 hexi-decimal characters. " << std::endl;
162  std::cerr << " This option can be used several times for multiple comparisons." << std::endl;
163  std::cerr << std::endl;
164  std::cerr << " --with-threads THREADS" << std::endl;
165  std::cerr << " Use at most THREADS threads." << std::endl;
166  std::cerr << std::endl;
167  std::cerr << " --without-threads" << std::endl;
168  std::cerr << " Use at most one thread." << std::endl;
169  std::cerr << std::endl;
170  std::cerr << " --compareNumberOfPixelsTolerance TOLERANCE" << std::endl;
171  std::cerr << " When comparing images with --compare, allow TOLERANCE pixels to differ." << std::endl;
172  std::cerr << " Default is 0." << std::endl;
173  std::cerr << std::endl;
174  std::cerr << " --compareRadiusTolerance TOLERANCE" << std::endl;
175  std::cerr << " Default is 0." << std::endl;
176  std::cerr << std::endl;
177  std::cerr << " --compareIntensityTolerance TOLERANCE" << std::endl;
178  std::cerr << " Default is 2.0." << std::endl;
179  std::cerr << std::endl;
180  std::cerr << " --compareCoordinateTolerance TOLERANCE" << std::endl;
181  std::cerr << " Default is 1.0e-6. Relative to the first spacing element of the input image." << std::endl;
182  std::cerr << std::endl;
183  std::cerr << " --compareDirectionTolerance TOLERANCE" << std::endl;
184  std::cerr << " Default is 1.0e-6." << std::endl;
185  std::cerr << std::endl; std::cerr << " --ignoreInputInformation" << std::endl;
186  std::cerr << " Skip verification of matching origins, spacings, and directions of input images." << std::endl;
187  std::cerr << std::endl;
188  std::cerr << " --no-process" << std::endl;
189  std::cerr << " The test driver will not invoke any process." << std::endl;
190  std::cerr << std::endl;
191  std::cerr << " --full-output" << std::endl;
192  std::cerr << " Causes the full output of the test to be passed to cdash." << std::endl;
193  std::cerr << " --redirect-output TEST_OUTPUT" << std::endl;
194  std::cerr << " Redirects the test output to the file TEST_OUTPUT." << std::endl;
195  std::cerr << std::endl;
196  std::cerr << " --" << std::endl;
197  std::cerr << " The options after -- are not interpreted by this program and passed" << std::endl;
198  std::cerr << " directly to the test program." << std::endl;
199  std::cerr << std::endl;
200  std::cerr << " --help" << std::endl;
201  std::cerr << " Display this message and exit." << std::endl;
202  std::cerr << std::endl;
203 }
204 
205 static char my_to_lower(const char c)
206 {
207  return static_cast<char>( ::tolower(c));
208 }
209 
210 int ProcessArguments(int *ac, ArgumentStringType *av, ProcessedOutputType * processedOutput = ITK_NULLPTR )
211 {
212 #if defined(LINUX) && !defined(__MINGW32__) && defined(ITK_HAS_FEENABLEEXCEPT)
214 #endif
215  regressionTestParameters.intensityTolerance = 2.0;
216  regressionTestParameters.numberOfPixelsTolerance = 0;
217  regressionTestParameters.radiusTolerance = 0;
218  regressionTestParameters.verifyInputInformation = true;
219  regressionTestParameters.coordinateTolerance = 1.0e-6;
220  regressionTestParameters.directionTolerance = 1.0e-6;
221  redirectOutputParameters.redirect = false;
222 
223  if( processedOutput )
224  {
225  processedOutput->externalProcessMustBeCalled = true;
226  }
227 
228  // parse the command line
229  int i = 1;
230  bool skip = false;
231  while ( i < *ac )
232  {
233  if ( !skip && strcmp((*av)[i], "--compare") == 0 )
234  {
235  if ( i + 2 >= *ac )
236  {
237  usage();
238  return 1;
239  }
240  regressionTestParameters.compareList.push_back( ComparePairType((*av)[i + 1], (*av)[i + 2]) );
241  (*av) += 3;
242  *ac -= 3;
243  }
244  else if ( !skip && strcmp((*av)[i], "--compare-MD5") == 0 )
245  {
246  if ( i + 2 >= *ac )
247  {
248  usage();
249  return 1;
250  }
251  const char *filename = (*av)[i + 1];
252  std::string md5hash0 = (*av)[i + 2];
253 
254  // convert hash to all lowercase letters
255  std::transform(md5hash0.begin(), md5hash0.end(), md5hash0.begin(), my_to_lower );
256 
257  // chech that the hash is of expected format
258  if ( md5hash0.size() != 32 ||
259  md5hash0.find_first_not_of( "0123456789abcdef" ) != std::string::npos )
260  {
261  std::cerr << "Warning: argument does not appear to be a valid md5 hash \"" << md5hash0 << "\"." << std::endl;
262  }
263 
264  std::vector< std::string > hashVector;
265  hashVector.push_back( md5hash0 );
266 
267  (*av) += 3;
268  (*ac) -= 3;
269 
270  // continue eating hash values
271  while ( *ac - i > 0 )
272  {
273  std::string md5hashN = (*av)[i];
274 
275  // convert hash to all lowercase letters
276  std::transform(md5hashN.begin(), md5hashN.end(), md5hashN.begin(), my_to_lower );
277 
278  // check if the next argument is a hash
279  if ( md5hashN.size() != 32 ||
280  md5hashN.find_first_not_of( "0123456789abcdef" ) != std::string::npos )
281  {
282  break;
283  }
284 
285  // add the hash
286  hashVector.push_back( md5hashN );
287 
288  // successful hash,
289  // move the arguments along
290  ++(*av);
291  --(*ac);
292  }
293 
294 
295  hashTestList.push_back( HashPairType( filename, hashVector ) );
296 
297  }
298  else if ( !skip && strcmp((*av)[i], "--") == 0 )
299  {
300  skip = true;
301  i += 1;
302  }
303  else if ( !skip && strcmp((*av)[i], "--help") == 0 )
304  {
305  usage();
306  return 1;
307  }
308  else if ( !skip && strcmp((*av)[i], "--with-threads") == 0 )
309  {
310  if ( i + 2 >= *ac )
311  {
312  usage();
313  return 1;
314  }
315  // set the environment which will be read by the subprocess
316  std::string threadEnv = "ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=";
317  threadEnv += (*av)[i + 1];
318  itksys::SystemTools::PutEnv( threadEnv.c_str() );
319  // and set the number of threads locally for the comparison
320  itk::MultiThreader::SetGlobalDefaultNumberOfThreads(atoi((*av)[i + 1]));
321  *av += 2;
322  *ac -= 2;
323  }
324  else if ( !skip && strcmp((*av)[i], "--without-threads") == 0 )
325  {
326  itksys::SystemTools::PutEnv( "ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=1" );
327  itk::MultiThreader::SetGlobalDefaultNumberOfThreads(1);
328  *av += 1;
329  *ac -= 1;
330  }
331  else if ( !skip && strcmp((*av)[i], "--compareNumberOfPixelsTolerance") == 0 )
332  {
333  if ( i + 2 >= *ac )
334  {
335  usage();
336  return 1;
337  }
338  regressionTestParameters.numberOfPixelsTolerance = atoi((*av)[i + 1]);
339  *av += 2;
340  *ac -= 2;
341  }
342  else if ( !skip && strcmp((*av)[i], "--compareRadiusTolerance") == 0 )
343  {
344  if ( i + 1 >= *ac )
345  {
346  usage();
347  return 1;
348  }
349  regressionTestParameters.radiusTolerance = atoi((*av)[i + 1]);
350  (*av) += 2;
351  *ac -= 2;
352  }
353  else if ( !skip && strcmp((*av)[i], "--compareIntensityTolerance") == 0 )
354  {
355  if ( i + 2 >= *ac )
356  {
357  usage();
358  return 1;
359  }
360  regressionTestParameters.intensityTolerance = atof((*av)[i + 1]);
361  (*av) += 2;
362  *ac -= 2;
363  }
364  else if ( !skip && strcmp((*av)[i], "--compareCoordinateTolerance") == 0 )
365  {
366  if ( i + 2 >= *ac )
367  {
368  usage();
369  return 1;
370  }
371  regressionTestParameters.coordinateTolerance = atof((*av)[i + 1]);
372  (*av) += 2;
373  *ac -= 2;
374  }
375  else if ( !skip && strcmp((*av)[i], "--compareDirectionTolerance") == 0 )
376  {
377  if ( i + 2 >= *ac )
378  {
379  usage();
380  return 1;
381  }
382  regressionTestParameters.directionTolerance = atof((*av)[i + 1]);
383  (*av) += 2;
384  *ac -= 2;
385  }
386 
387  else if ( !skip && strcmp((*av)[i], "--ignoreInputInformation") == 0 )
388  {
389  regressionTestParameters.verifyInputInformation = false;
390  (*av) += 1;
391  *ac -= 1;
392  }
393  else if ( !skip && strcmp((*av)[i], "--add-before-libpath") == 0 )
394  {
395  if ( i + 2 >= *ac )
396  {
397  usage();
398  return 1;
399  }
400  if( processedOutput )
401  {
402  processedOutput->add_before_libpath.push_back( (*av)[i+1] );
403  }
404  (*av) += 2;
405  *ac -= 2;
406  }
407  else if ( !skip && strcmp((*av)[i], "--add-before-env") == 0 )
408  {
409  if ( i + 3 >= *ac )
410  {
411  usage();
412  return 1;
413  }
414  if( processedOutput )
415  {
416  processedOutput->add_before_env.push_back( (*av)[i+1] );
417  processedOutput->add_before_env.push_back( (*av)[i+2] );
418  }
419  (*av) += 3;
420  *ac -= 3;
421  }
422  else if ( !skip && strcmp((*av)[i], "--add-before-env-with-sep") == 0 )
423  {
424  if ( i + 4 >= *ac )
425  {
426  usage();
427  return 1;
428  }
429  if( processedOutput )
430  {
431  processedOutput->add_before_env_with_sep.push_back( (*av)[i+1] );
432  processedOutput->add_before_env_with_sep.push_back( (*av)[i+2] );
433  processedOutput->add_before_env_with_sep.push_back( (*av)[i+3] );
434  }
435  (*av) += 4;
436  *ac -= 4;
437  }
438  else if ( !skip && strcmp((*av)[i], "--remove-env") == 0 )
439  {
440  if ( i + 2 >= *ac )
441  {
442  usage();
443  return 1;
444  }
445 
446  itksys::SystemTools::UnPutEnv( (*av)[i+1] );
447 
448  (*av) += 2;
449  *ac -= 2;
450  }
451  else if ( !skip && strcmp((*av)[i], "--full-output") == 0 )
452  {
453  // emit the string to tell ctest that the full output should be
454  // passed to cdash.
455  std::cout << "CTEST_FULL_OUTPUT" << std::endl;
456  (*av) += 1;
457  *ac -= 1;
458  }
459  else if ( !skip && strcmp((*av)[i], "--no-process") == 0 )
460  {
461  // The test driver needs to invoke another executable
462  // For example, the python interpreter to run Wrapping tests.
463  if( processedOutput )
464  {
465  processedOutput->externalProcessMustBeCalled = false;
466  }
467  (*av) += 1;
468  *ac -= 1;
469  }
470  else if ( !skip && strcmp((*av)[i], "--redirectOutput") == 0 )
471  {
472  if ( i + 1 >= *ac )
473  {
474  usage();
475  return 1;
476  }
477  redirectOutputParameters.redirect = true;
478  redirectOutputParameters.fileName = (*av)[i + 1];
479  *av += 2;
480  *ac -= 2;
481  }
482  else
483  {
484  if( processedOutput )
485  {
486  processedOutput->args.push_back((*av)[i]);
487  }
488  i += 1;
489  }
490  }
491 
492  return 0;
493 }
494 
495 
496 // Regression Testing Code
497 //
498 // This method returns:
499 // max int , if there is an error reading baselineImageFilename
500 // max int - 2, if there is an error reading testImageFilename
501 // max int - 1, if the size of the images don't match
502 // the number of pixel beyond the tolerance
503 // otherwise zero is returned if the difference is with in tolerances
504 template <typename PixelType>
505 int RegressionTestHelper(const char *testImageFilename,
506  const char *baselineImageFilename,
507  int reportErrors,
508  double intensityTolerance,
509  ::itk::SizeValueType numberOfPixelsTolerance,
510  unsigned int radiusTolerance,
511  bool verifyInputInformation,
512  double coordinateTolerance,
513  double directionTolerance)
514 {
515  // Use the factory mechanism to read the test and baseline files and convert
516  // them to double
519  typedef itk::Image< unsigned char, 2 > DiffOutputType;
520  typedef itk::ImageFileReader< ImageType > ReaderType;
521 
522  // Read the baseline file
523  typename ReaderType::Pointer baselineReader = ReaderType::New();
524  baselineReader->SetFileName(baselineImageFilename);
525  try
526  {
527  baselineReader->UpdateLargestPossibleRegion();
528  }
529  catch ( itk::ExceptionObject & e )
530  {
531  std::cerr << "Exception detected while reading " << baselineImageFilename << " : " << e.GetDescription();
533  }
534 
535  // Read the file generated by the test
536  typename ReaderType::Pointer testReader = ReaderType::New();
537  testReader->SetFileName(testImageFilename);
538  try
539  {
540  testReader->UpdateLargestPossibleRegion();
541  }
542  catch ( itk::ExceptionObject & e )
543  {
544  std::cerr << "Exception detected while reading " << testImageFilename << " : " << e.GetDescription() << std::endl;
546  }
547 
548  // The sizes of the baseline and test image must match
549  typename ImageType::SizeType baselineSize;
550  baselineSize = baselineReader->GetOutput()->GetLargestPossibleRegion().GetSize();
551  typename ImageType::SizeType testSize;
552  testSize = testReader->GetOutput()->GetLargestPossibleRegion().GetSize();
553 
554  if ( baselineSize != testSize )
555  {
556  std::cerr << "The size of the Baseline image and Test image do not match!" << std::endl;
557  std::cerr << "Baseline image: " << baselineImageFilename
558  << " has size " << baselineSize << std::endl;
559  std::cerr << "Test image: " << testImageFilename
560  << " has size " << testSize << std::endl;
562  }
563 
564  // Now compare the two images
566  typename DiffType::Pointer diff = DiffType::New();
567  diff->SetValidInput( baselineReader->GetOutput() );
568  diff->SetTestInput( testReader->GetOutput() );
569  diff->SetDifferenceThreshold(intensityTolerance);
570  diff->SetToleranceRadius(radiusTolerance);
571  diff->SetVerifyInputInformation(verifyInputInformation);
572  diff->SetCoordinateTolerance(coordinateTolerance);
573  diff->SetDirectionTolerance(directionTolerance);
574  diff->UpdateLargestPossibleRegion();
575 
576  itk::SizeValueType status = diff->GetNumberOfPixelsWithDifferences();
577 
578  if ( ! reportErrors )
579  {
580  //The measurement errors should be reported for both success and errors
581  //to facilitate setting tight tolerances of tests.
582  std::string shortFilename = itksys::SystemTools::GetFilenameName( baselineImageFilename );
583 
584  std::cout << "<DartMeasurement name=\"ImageError " << shortFilename
585  << "\" type=\"numeric/double\">";
586  std::cout << status;
587  std::cout << "</DartMeasurement>" << std::endl;
588  }
589 
590  // if there are discrepencies, create an diff image
591  if ( ( status > numberOfPixelsTolerance ) && reportErrors )
592  {
593 
594  // Report actuall image error to best baseline
595  std::cout << "<DartMeasurement name=\"ImageError\" type=\"numeric/double\">";
596  std::cout << status;
597  std::cout << "</DartMeasurement>" << std::endl;
598 
599 
600  // Report statistics for pixels which exceed tolerances
601  std::cout << "<DartMeasurement name=\"ImageError Minimum\" type=\"numeric/double\">";
602  std::cout << diff->GetMinimumDifference() << "</DartMeasurement>" << std::endl;
603 
604  std::cout << "<DartMeasurement name=\"ImageError Maximum\" type=\"numeric/double\">";
605  std::cout << diff->GetMaximumDifference() << "</DartMeasurement>" << std::endl;
606 
607  std::cout << "<DartMeasurement name=\"ImageError Mean\" type=\"numeric/double\">";
608  std::cout << diff->GetMeanDifference() << "</DartMeasurement>" << std::endl;
609 
610 
613  typedef itk::ImageFileWriter< DiffOutputType > WriterType;
615  OutputType::SizeType size; size.Fill(0);
616 
617  typename RescaleType::Pointer rescale = RescaleType::New();
618  rescale->SetOutputMinimum( itk::NumericTraits< unsigned char >::NonpositiveMin() );
619  rescale->SetOutputMaximum( itk::NumericTraits< unsigned char >::max() );
620  rescale->SetInput( diff->GetOutput() );
621  rescale->UpdateLargestPossibleRegion();
622  size = rescale->GetOutput()->GetLargestPossibleRegion().GetSize();
623 
624  //Get the center slice of the image, In 3D, the first slice
625  //is often a black slice with little debugging information.
626  OutputType::IndexType index; index.Fill(0);
627  for ( unsigned int i = 2; i < ITK_TEST_DIMENSION_MAX; i++ )
628  {
629  index[i] = size[i] / 2; //NOTE: Integer Divide used to get approximately
630  // the center slice
631  size[i] = 0;
632  }
633 
634  RegionType region;
635  region.SetIndex(index);
636 
637  region.SetSize(size);
638 
639  ExtractType::Pointer extract = ExtractType::New();
640  extract->SetDirectionCollapseToIdentity();
641  extract->SetInput( rescale->GetOutput() );
642  extract->SetExtractionRegion(region);
643 
644  WriterType::Pointer writer = WriterType::New();
645  writer->SetInput( extract->GetOutput() );
646 
647  std::ostringstream diffName;
648  diffName << testImageFilename << ".diff.png";
649  try
650  {
651  rescale->SetInput( diff->GetOutput() );
652  rescale->Update();
653  }
654  catch ( const std::exception & e )
655  {
656  std::cerr << "Error during rescale of " << diffName.str() << std::endl;
657  std::cerr << e.what() << "\n";
658  }
659  catch ( ... )
660  {
661  std::cerr << "Error during rescale of " << diffName.str() << std::endl;
662  }
663  writer->SetFileName( diffName.str().c_str() );
664  try
665  {
666  writer->Update();
667  }
668  catch ( const std::exception & e )
669  {
670  std::cerr << "Error during write of " << diffName.str() << std::endl;
671  std::cerr << e.what() << "\n";
672  }
673  catch ( ... )
674  {
675  std::cerr << "Error during write of " << diffName.str() << std::endl;
676  }
677 
678  std::cout << "<DartMeasurementFile name=\"DifferenceImage\" type=\"image/png\">";
679  std::cout << diffName.str();
680  std::cout << "</DartMeasurementFile>" << std::endl;
681 
682  std::ostringstream baseName;
683  baseName << testImageFilename << ".base.png";
684  try
685  {
686  rescale->SetInput( baselineReader->GetOutput() );
687  rescale->Update();
688  }
689  catch ( const std::exception & e )
690  {
691  std::cerr << "Error during rescale of " << baseName.str() << std::endl;
692  std::cerr << e.what() << "\n";
693  }
694  catch ( ... )
695  {
696  std::cerr << "Error during rescale of " << baseName.str() << std::endl;
697  }
698  try
699  {
700  writer->SetFileName( baseName.str().c_str() );
701  writer->Update();
702  }
703  catch ( const std::exception & e )
704  {
705  std::cerr << "Error during write of " << baseName.str() << std::endl;
706  std::cerr << e.what() << "\n";
707  }
708  catch ( ... )
709  {
710  std::cerr << "Error during write of " << baseName.str() << std::endl;
711  }
712 
713  std::cout << "<DartMeasurementFile name=\"BaselineImage\" type=\"image/png\">";
714  std::cout << baseName.str();
715  std::cout << "</DartMeasurementFile>" << std::endl;
716 
717  std::ostringstream testName;
718  testName << testImageFilename << ".test.png";
719  try
720  {
721  rescale->SetInput( testReader->GetOutput() );
722  rescale->Update();
723  }
724  catch ( const std::exception & e )
725  {
726  std::cerr << "Error during rescale of " << testName.str() << std::endl;
727  std::cerr << e.what() << "\n";
728  }
729  catch ( ... )
730  {
731  std::cerr << "Error during rescale of " << testName.str() << std::endl;
732  }
733  try
734  {
735  writer->SetFileName( testName.str().c_str() );
736  writer->Update();
737  }
738  catch ( const std::exception & e )
739  {
740  std::cerr << "Error during write of " << testName.str() << std::endl;
741  std::cerr << e.what() << "\n";
742  }
743  catch ( ... )
744  {
745  std::cerr << "Error during write of " << testName.str() << std::endl;
746  }
747 
748  std::cout << "<DartMeasurementFile name=\"TestImage\" type=\"image/png\">";
749  std::cout << testName.str();
750  std::cout << "</DartMeasurementFile>" << std::endl;
751  }
752  return ( status > numberOfPixelsTolerance ) ? static_cast<int>(status) : 0;
753 }
754 
755 
757 void GetImageType( const char * fileName,
759  itk::ImageIOBase::IOComponentType &componentType )
760 {
761  typedef itk::Image<unsigned char, 3> ImageType;
763  imageReader->SetFileName(fileName);
764  imageReader->UpdateOutputInformation();
765 
766  pixelType = imageReader->GetImageIO()->GetPixelType();
767  componentType = imageReader->GetImageIO()->GetComponentType();
768 }
769 
770 int RegressionTestImage(const char *testImageFilename,
771  const char *baselineImageFilename,
772  int reportErrors,
773  double intensityTolerance,
774  ::itk::SizeValueType numberOfPixelsTolerance,
775  unsigned int radiusTolerance,
776  bool verifyInputInformation,
777  double coordinateTolerance,
778  double directionTolerance)
779 {
781  itk::ImageIOBase::IOComponentType componentType;
782  try
783  {
784  GetImageType(testImageFilename, pixelType, componentType);
785 
786  switch (componentType)
787  {
793  return RegressionTestHelper<unsigned long long>(testImageFilename,
794  baselineImageFilename,
795  reportErrors,
796  intensityTolerance,
797  numberOfPixelsTolerance,
798  radiusTolerance,
799  verifyInputInformation,
800  coordinateTolerance,
801  directionTolerance);
807  return RegressionTestHelper<long long>(testImageFilename,
808  baselineImageFilename,
809  reportErrors,
810  intensityTolerance,
811  numberOfPixelsTolerance,
812  radiusTolerance,
813  verifyInputInformation,
814  coordinateTolerance,
815  directionTolerance);
818  return RegressionTestHelper<double>(testImageFilename,
819  baselineImageFilename,
820  reportErrors,
821  intensityTolerance,
822  numberOfPixelsTolerance,
823  radiusTolerance,
824  verifyInputInformation,
825  coordinateTolerance,
826  directionTolerance);
828  default:
829  std::cerr << "Exception detected while reading " << baselineImageFilename << " : " << "Unknown component type";
831  }
832  }
833  catch ( itk::ExceptionObject & e )
834  {
835  std::cerr << "Exception detected while reading " << baselineImageFilename << " : " << e.GetDescription();
837  }
838 }
839 
840 template< typename TImageType >
841 std::string ComputeHash( const char *testImageFilename )
842 {
843  typedef TImageType ImageType;
844  typedef itk::ImageFileReader< ImageType > ReaderType;
845 
846 
847  // Read the file generated by the test
848  typename ReaderType::Pointer testReader = ReaderType::New();
849  testReader->SetFileName(testImageFilename);
850  try
851  {
852  testReader->UpdateLargestPossibleRegion();
853  }
854  catch ( itk::ExceptionObject & e )
855  {
856  std::cerr << "Exception detected while reading " << testImageFilename << " : " << e.GetDescription() << std::endl;
857  throw; // re-throw
858  }
859 
860  typedef itk::Testing::HashImageFilter<ImageType> HashFilterType;
861 
862  typename HashFilterType::Pointer hasher = HashFilterType::New();
863  hasher->SetInput( testReader->GetOutput() );
864  hasher->Update();
865 
866  return hasher->GetHash();
867 }
868 
869 
870 int HashTestImage( const char *testImageFilename,
871  const std::vector<std::string> &baselineMD5Vector )
872 {
875 
876  if ( iobase.IsNull() )
877  {
878  itkGenericExceptionMacro( "Unable to determine ImageIO reader for \"" << testImageFilename << "\"" );
879  }
880 
881  // Read the image information
882  iobase->SetFileName( testImageFilename );
883  iobase->ReadImageInformation();
884 
885  // get output information about input image
886  itk::ImageIOBase::IOComponentType componentType = iobase->GetComponentType();
887 
888  std::string testMD5 = "";
889  switch(componentType)
890  {
892  testMD5 = ComputeHash< itk::VectorImage<char, ITK_TEST_DIMENSION_MAX> >( testImageFilename );
893  break;
895  testMD5 = ComputeHash< itk::VectorImage<unsigned char, ITK_TEST_DIMENSION_MAX> >( testImageFilename );
896  break;
898  testMD5 = ComputeHash< itk::VectorImage<short, ITK_TEST_DIMENSION_MAX> >( testImageFilename );
899  break;
901  testMD5 = ComputeHash< itk::VectorImage<unsigned short, ITK_TEST_DIMENSION_MAX> >( testImageFilename );
902  break;
904  testMD5 = ComputeHash< itk::VectorImage<int, ITK_TEST_DIMENSION_MAX> >( testImageFilename );
905  break;
907  testMD5 = ComputeHash< itk::VectorImage<unsigned int, ITK_TEST_DIMENSION_MAX> >( testImageFilename );
908  break;
910  testMD5 = ComputeHash< itk::VectorImage<long, ITK_TEST_DIMENSION_MAX> >( testImageFilename );
911  break;
913  testMD5 = ComputeHash< itk::VectorImage<unsigned long, ITK_TEST_DIMENSION_MAX> >( testImageFilename );
914  break;
916  testMD5 = ComputeHash< itk::VectorImage<long long, ITK_TEST_DIMENSION_MAX> >( testImageFilename );
917  break;
919  testMD5 = ComputeHash< itk::VectorImage<unsigned long long, ITK_TEST_DIMENSION_MAX> >( testImageFilename );
920  break;
923  std::cerr << "Hashing is not supporting for float and double images." << std::endl;
924  itkGenericExceptionMacro( "Hashing is not supported for images of float or doubles." );
925  break;
927  default:
928  assert( false ); // should never get here unless we forgot a type
929  itkGenericExceptionMacro( "Logic error!" );
930  }
931 
932  std::vector<std::string>::const_iterator iter = baselineMD5Vector.begin();
933  assert( baselineMD5Vector.size() );
934  do
935  {
936  if ( *iter == testMD5 )
937  {
938  // success, let's get out of here
939  return 0;
940  }
941  }
942  while (++iter != baselineMD5Vector.end() );
943 
944  // failed to match print the different md5s
945  std::cout << "<DartMeasurement name=\"TestMD5\" type=\"text/string\">";
946  std::cout << testMD5;
947  std::cout << "</DartMeasurement>" << std::endl;
948 
949 
950  // print out all md5 baselines
951  for ( iter = baselineMD5Vector.begin(); iter != baselineMD5Vector.end(); ++iter )
952  {
953  std::cout << "<DartMeasurement name=\"BaselineMD5\" type=\"text/string\">";
954  std::cout << *iter;
955  std::cout << "</DartMeasurement>" << std::endl;
956  }
957 
959  typedef itk::Image< double, 2 > SliceImageType;
960  typedef itk::Image< unsigned char, 2 > OutputType;
961  typedef itk::ImageFileReader< ImageType > ReaderType;
964  typedef itk::ImageFileWriter< OutputType > WriterType;
965 
966  // setup reader
967  ReaderType::Pointer reader = ReaderType::New();
968  reader->SetFileName( testImageFilename );
969  reader->UpdateLargestPossibleRegion();
970 
971  ImageType::SizeType size;
972  size = reader->GetOutput()->GetLargestPossibleRegion().GetSize();
973 
974  //Get the center slice of the image, In 3D, the first slice
975  //is often a black slice with little debugging information.
976  ImageType::IndexType index; index.Fill(0);
977  for ( unsigned int i = 2; i < ITK_TEST_DIMENSION_MAX; i++ )
978  {
979  index[i] = size[i] / 2; //NOTE: Integer Divide used to get approximately
980  // the center slice
981  size[i] = 0;
982  }
983 
984 
985  ImageType::RegionType region;
986  region.SetIndex(index);
987 
988  region.SetSize(size);
989 
990  ExtractType::Pointer extract = ExtractType::New();
991  extract->SetDirectionCollapseToIdentity();
992  extract->SetInput( reader->GetOutput() );
993  extract->SetExtractionRegion(region);
994 
995  RescaleType::Pointer rescale = RescaleType::New();
996  rescale->SetOutputMinimum( itk::NumericTraits< unsigned char >::NonpositiveMin() );
997  rescale->SetOutputMaximum( itk::NumericTraits< unsigned char >::max() );
998  rescale->SetInput( extract->GetOutput() );
999 
1000  WriterType::Pointer writer = WriterType::New();
1001  writer->SetInput( rescale->GetOutput() );
1002 
1003 
1004  std::ostringstream testName;
1005  testName << testImageFilename << ".test.png";
1006 
1007  writer->SetFileName( testName.str().c_str() );
1008 
1009  try
1010  {
1011  rescale->UpdateLargestPossibleRegion();
1012  writer->Update();
1013  }
1014  catch ( const std::exception & e )
1015  {
1016  std::cerr << "Error during rescale and writing of " << testName.str()<< std::endl;
1017  std::cerr << e.what() << "\n";
1018  }
1019  catch ( ... )
1020  {
1021  std::cerr << "Unknow error during rescale and writing of " << testName.str() << std::endl;
1022  }
1023 
1024  std::cout << "<DartMeasurementFile name=\"TestImage\" type=\"image/png\">";
1025  std::cout << testName.str();
1026  std::cout << "</DartMeasurementFile>" << std::endl;
1027 
1028  return 1;
1029 }
1030 
1031 //
1032 // Generate all of the possible baselines
1033 // The possible baselines are generated fromn the baselineFilename using the
1034 // following algorithm:
1035 // 1) strip the suffix
1036 // 2) append a digit .x
1037 // 3) append the original suffix.
1038 // It the file exists, increment x and continue
1039 //
1040 std::map< std::string, int > RegressionTestBaselines(char *baselineFilename)
1041 {
1042  std::map< std::string, int > baselines;
1043  baselines[std::string(baselineFilename)] = 0;
1044 
1045  std::string originalBaseline(baselineFilename);
1046 
1047  int x = 0;
1048  std::string::size_type suffixPos = originalBaseline.rfind(".");
1049  std::string suffix;
1050  if ( suffixPos != std::string::npos )
1051  {
1052  suffix = originalBaseline.substr( suffixPos, originalBaseline.length() );
1053  originalBaseline.erase( suffixPos, originalBaseline.length() );
1054  }
1055  while ( ++x )
1056  {
1057  std::ostringstream filename;
1058  filename << originalBaseline << "." << x << suffix;
1059  std::ifstream filestream( filename.str().c_str() );
1060  if ( !filestream )
1061  {
1062  break;
1063  }
1064  baselines[filename.str()] = 0;
1065  filestream.close();
1066  }
1067  return baselines;
1068 }
1069 
1070 
1071 // Needed for explicit instantiation
1072 #include "itkTestingComparisonImageFilter.hxx"
1073 
1074 #endif
std::string ComputeHash(const char *testImageFilename)
ArgumentsList add_before_libpath
void GetImageType(const char *fileName, itk::ImageIOBase::IOPixelType &pixelType, itk::ImageIOBase::IOComponentType &componentType)
Get the PixelType and ComponentType from fileName.
static char my_to_lower(const char c)
ArgumentsList add_before_env_with_sep
An image region represents a structured region of data.
int HashTestImage(const char *testImageFilename, const std::string md5hash)
std::vector< char * > ArgumentsList
unsigned long SizeValueType
Definition: itkIntTypes.h:143
Applies a linear transformation to the intensity levels of the input Image.
static Pointer New()
int RegressionTestImage(const char *testImageFilename, const char *baselineImageFilename, int reportErrors, double intensityTolerance,::itk::SizeValueType numberOfPixelsTolerance=0, unsigned int radiusTolerance=0, bool verifyInputInformation=true, double coordinateTolerance=1.0e-6, double directionTolerance=1.0e-6)
char ** ArgumentStringType
std::map< std::string, int > RegressionTestBaselines(char *)
static ITK_CONSTEXPR_FUNC T max(const T &)
RegressionTestParameters regressionTestParameters
std::pair< char *, char * > ComparePairType
int RegressionTestHelper(const char *testImageFilename, const char *baselineImageFilename, int reportErrors, double intensityTolerance,::itk::SizeValueType numberOfPixelsTolerance, unsigned int radiusTolerance, bool verifyInputInformation, double coordinateTolerance, double directionTolerance)
Generates a md5 hash string from an image.
void Fill(IndexValueType value)
Definition: itkIndex.h:293
Standard exception handling object.
#define ITK_TEST_DIMENSION_MAX
bool IsNull() const
Writes image data to a single file.
Implements comparison between two images.
Data source that reads image data from a single file.
static ImageIOBasePointer CreateImageIO(const char *path, FileModeType mode)
std::vector< ComparePairType > compareList
int ProcessArguments(int *ac, ArgumentStringType *av, ProcessedOutputType *processedOutput=nullptr)
void usage()
Define additional traits for native types such as int or float.
RedirectOutputParameters redirectOutputParameters
std::pair< const char *, std::vector< std::string > > HashPairType
static ITK_CONSTEXPR_VAR double e
The base of the natural logarithm or Euler&#39;s number
Definition: itkMath.h:56
std::vector< HashPairType > hashTestList
void Fill(SizeValueType value)
Definition: itkSize.h:198
const SizeValueType * GetSize() const
Definition: itkSize.h:166
Templated n-dimensional image class.
Definition: itkImage.h:75
Decrease the image size by cropping the image to the selected region bounds.
virtual const char * GetDescription() const