ITK
4.1.0
Insight Segmentation and Registration Toolkit
|
00001 /*========================================================================= 00002 * 00003 * Copyright Insight Software Consortium 00004 * 00005 * Licensed under the Apache License, Version 2.0 (the "License"); 00006 * you may not use this file except in compliance with the License. 00007 * You may obtain a copy of the License at 00008 * 00009 * http://www.apache.org/licenses/LICENSE-2.0.txt 00010 * 00011 * Unless required by applicable law or agreed to in writing, software 00012 * distributed under the License is distributed on an "AS IS" BASIS, 00013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 00014 * See the License for the specific language governing permissions and 00015 * limitations under the License. 00016 * 00017 *=========================================================================*/ 00018 /*========================================================================= 00019 * 00020 * Portions of this file are subject to the VTK Toolkit Version 3 copyright. 00021 * 00022 * Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen 00023 * 00024 * For complete copyright, license and disclaimer of warranty information 00025 * please refer to the NOTICE file at the top of the ITK source tree. 00026 * 00027 *=========================================================================*/ 00028 #ifndef __itkTestDriverInclude_h 00029 #define __itkTestDriverInclude_h 00030 // 00031 // This file is used by the TestDriver executables generated by CMake's 00032 // create_test_sourcelist. It defines a function, ProcessArguments 00033 // that processes the command line for the test driver prior to 00034 // invoking the test. It also defines the RegressiontestImage function 00035 // that is called after a test has been run by the driver. 00036 // command line options prior to invoking the test. 00037 // 00038 00039 #include "itksys/Process.h" 00040 00041 #include "itkWin32Header.h" 00042 #include <map> 00043 #include <string> 00044 #include <iostream> 00045 #include <fstream> 00046 #include "itkMultiThreader.h" 00047 #include "itkImage.h" 00048 #include "itkImageFileReader.h" 00049 #include "itkImageFileWriter.h" 00050 #include "itkImageRegionConstIterator.h" 00051 #include "itkTestingStretchIntensityImageFilter.h" 00052 #include "itkTestingExtractSliceImageFilter.h" 00053 #include "itkTestingComparisonImageFilter.h" 00054 #include "itkTestingHashImageFilter.h" 00055 #include "itksys/SystemTools.hxx" 00056 #include "itkIntTypes.h" 00057 #ifdef LINUX 00058 #include "itkFloatingPointExceptions.h" 00059 #endif 00060 #include "vnl/vnl_sample.h" 00061 00062 #define ITK_TEST_DIMENSION_MAX 6 00063 00064 int RegressionTestImage(const char *testImageFilename, 00065 const char *baselineImageFilename, 00066 int reportErrors, 00067 double intensityTolerance, 00068 ::itk::SizeValueType numberOfPixelsTolerance = 0, 00069 unsigned int radiusTolerance = 0); 00070 00071 int HashTestImage( const char *testImageFilename, 00072 const std::string md5hash ); 00073 00074 00075 std::map< std::string, int > RegressionTestBaselines(char *); 00076 00077 typedef std::pair< char *, char * > ComparePairType; 00078 00079 // A structure to hold regression test parameters 00080 typedef struct 00081 { 00082 std::vector< ComparePairType > compareList; 00083 double intensityTolerance; 00084 unsigned int numberOfPixelsTolerance; 00085 unsigned int radiusTolerance; 00086 } RegressionTestParameters; 00087 00088 RegressionTestParameters regressionTestParameters; 00089 00090 00091 typedef std::pair< const char *, std::vector<std::string> > HashPairType; 00092 00093 std::vector< HashPairType > hashTestList; 00094 00095 typedef char ** ArgumentStringType; 00096 00097 00098 // Types to hold parameters that should be processed later 00099 typedef std::vector< char * > ArgumentsList; 00100 00101 struct ProcessedOutputType 00102 { 00103 bool externalProcessMustBeCalled; 00104 00105 ArgumentsList args; 00106 ArgumentsList add_before_libpath; 00107 ArgumentsList add_before_env; 00108 ArgumentsList add_before_env_with_sep; 00109 }; 00110 00111 // A structure to hold redirect output parameters 00112 typedef struct 00113 { 00114 bool redirect; 00115 std::string fileName; 00116 } RedirectOutputParameters; 00117 00118 RedirectOutputParameters redirectOutputParameters; 00119 00120 void usage() 00121 { 00122 std::cerr << "usage: itkTestDriver [options] prg [args]" << std::endl; 00123 std::cerr << " itkTestDriver --no-process [options]" << std::endl; 00124 std::cerr << std::endl; 00125 std::cerr << "itkTestDriver alter the environment, run a test program and compare the images" << std::endl; 00126 std::cerr << "produced." << std::endl; 00127 std::cerr << std::endl; 00128 std::cerr << "Options:" << std::endl; 00129 std::cerr << " --add-before-libpath PATH" << std::endl; 00130 std::cerr << " Add a path to the library path environment. This option take care of" << std::endl; 00131 std::cerr << " choosing the right environment variable for your system." << std::endl; 00132 std::cerr << " This option can be used several times." << std::endl; 00133 std::cerr << std::endl; 00134 std::cerr << " --add-before-env NAME VALUE" << std::endl; 00135 std::cerr << " Add a VALUE to the variable name in the environment." << std::endl; 00136 std::cerr << " The seperator used is the default one on the system." << std::endl; 00137 std::cerr << " This option can be used several times." << std::endl; 00138 std::cerr << std::endl; 00139 std::cerr << " --add-before-env-with-sep NAME VALUE SEP" << std::endl; 00140 std::cerr << " Add a VALUE to the variable name in the environment using the provided separator." << std::endl; 00141 std::cerr << " This option can be used several times." << std::endl; 00142 std::cerr << std::endl; 00143 std::cerr << " --compare TEST BASELINE" << std::endl; 00144 std::cerr << " Compare the TEST image to the BASELINE one." << std::endl; 00145 std::cerr << " This option can be used several times." << std::endl; 00146 std::cerr << std::endl; 00147 std::cerr << " --compare-MD5 TEST md5hash0 [ md5hash1 ... ]" << std::endl; 00148 std::cerr << " Compare the TEST image file's md5 hash to the provided hash." << std::endl; 00149 std::cerr << " md5hash0 is required and assumed to be a hash." << std::endl; 00150 std::cerr << " Additional arguments are considerd hashes when the string is 32 hexi-decimal characters. " << std::endl; 00151 std::cerr << " This option can be used several times for multiple comparisons." << std::endl; 00152 std::cerr << std::endl; 00153 std::cerr << " --with-threads THREADS" << std::endl; 00154 std::cerr << " Use at most THREADS threads." << std::endl; 00155 std::cerr << std::endl; 00156 std::cerr << " --without-threads" << std::endl; 00157 std::cerr << " Use at most one thread." << std::endl; 00158 std::cerr << std::endl; 00159 std::cerr << " --compareNumberOfPixelsTolerance TOLERANCE" << std::endl; 00160 std::cerr << " When comparing images with --compare, allow TOLERANCE pixels to differ." << std::endl; 00161 std::cerr << " Default is 0." << std::endl; 00162 std::cerr << std::endl; 00163 std::cerr << " --compareRadiusTolerance TOLERANCE" << std::endl; 00164 std::cerr << " Default is 0." << std::endl; 00165 std::cerr << std::endl; 00166 std::cerr << " --compareIntensityTolerance TOLERANCE" << std::endl; 00167 std::cerr << " Default is 2.0." << std::endl; 00168 std::cerr << std::endl; 00169 std::cerr << " --no-process" << std::endl; 00170 std::cerr << " The test driver will not invoke any process." << std::endl; 00171 std::cerr << std::endl; 00172 std::cerr << " --full-output" << std::endl; 00173 std::cerr << " Causes the full output of the test to be passed to cdash." << std::endl; 00174 std::cerr << " --redirect-output TEST_OUTPUT" << std::endl; 00175 std::cerr << " Redirects the test output to the file TEST_OUTPUT." << std::endl; 00176 std::cerr << std::endl; 00177 std::cerr << " --" << std::endl; 00178 std::cerr << " The options after -- are not interpreted by this program and passed" << std::endl; 00179 std::cerr << " directly to the test program." << std::endl; 00180 std::cerr << std::endl; 00181 std::cerr << " --help" << std::endl; 00182 std::cerr << " Display this message and exit." << std::endl; 00183 std::cerr << std::endl; 00184 } 00185 00186 00187 int ProcessArguments(int *ac, ArgumentStringType *av, ProcessedOutputType * processedOutput = NULL ) 00188 { 00189 #ifdef LINUX 00190 itk::FloatingPointExceptions::Enable(); 00191 #endif 00192 regressionTestParameters.intensityTolerance = 2.0; 00193 regressionTestParameters.numberOfPixelsTolerance = 0; 00194 regressionTestParameters.radiusTolerance = 0; 00195 00196 redirectOutputParameters.redirect = false; 00197 00198 if( processedOutput ) 00199 { 00200 processedOutput->externalProcessMustBeCalled = true; 00201 } 00202 00203 // parse the command line 00204 int i = 1; 00205 bool skip = false; 00206 while ( i < *ac ) 00207 { 00208 if ( !skip && strcmp((*av)[i], "--compare") == 0 ) 00209 { 00210 if ( i + 2 >= *ac ) 00211 { 00212 usage(); 00213 return 1; 00214 } 00215 regressionTestParameters.compareList.push_back( ComparePairType((*av)[i + 1], (*av)[i + 2]) ); 00216 (*av) += 3; 00217 *ac -= 3; 00218 } 00219 else if ( !skip && strcmp((*av)[i], "--compare-MD5") == 0 ) 00220 { 00221 if ( i + 2 >= *ac ) 00222 { 00223 usage(); 00224 return 1; 00225 } 00226 const char *filename = (*av)[i + 1]; 00227 std::string md5hash0 = (*av)[i + 2]; 00228 00229 // convert hash to all lowercase letters 00230 for( std::string::iterator iter = md5hash0.begin(); iter != md5hash0.end(); ++iter ) 00231 { 00232 *iter = tolower(*iter); 00233 } 00234 00235 // chech that the hash is of expected format 00236 if ( md5hash0.size() != 32 || 00237 md5hash0.find_first_not_of( "0123456789abcdef" ) != std::string::npos ) 00238 { 00239 std::cerr << "Warning: argument does not appear to be a valid md5 hash \"" << md5hash0 << "\"." << std::endl; 00240 } 00241 00242 std::vector< std::string > hashVector; 00243 hashVector.push_back( md5hash0 ); 00244 00245 (*av) += 3; 00246 (*ac) -= 3; 00247 00248 // continue eating hash values 00249 while ( *ac - i > 0 ) 00250 { 00251 std::string md5hashN = (*av)[i]; 00252 00253 // convert hash to all lowercase letters 00254 for( std::string::iterator iter = md5hashN.begin(); iter != md5hashN.end(); ++iter ) 00255 { 00256 *iter = tolower(*iter); 00257 } 00258 00259 // check if the next argument is a hash 00260 if ( md5hashN.size() != 32 || 00261 md5hashN.find_first_not_of( "0123456789abcdef" ) != std::string::npos ) 00262 { 00263 break; 00264 } 00265 00266 // add the hash 00267 hashVector.push_back( md5hashN ); 00268 00269 // successful hash, 00270 // move the arguments along 00271 ++(*av); 00272 --(*ac); 00273 } 00274 00275 00276 hashTestList.push_back( HashPairType( filename, hashVector ) ); 00277 00278 } 00279 else if ( !skip && strcmp((*av)[i], "--") == 0 ) 00280 { 00281 skip = true; 00282 i += 1; 00283 } 00284 else if ( !skip && strcmp((*av)[i], "--help") == 0 ) 00285 { 00286 usage(); 00287 return 1; 00288 } 00289 else if ( !skip && strcmp((*av)[i], "--with-threads") == 0 ) 00290 { 00291 if ( i + 1 >= *ac ) 00292 { 00293 usage(); 00294 return 1; 00295 } 00296 // set the environment which will be read by the subprocess 00297 std::string threadEnv = "ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS="; 00298 threadEnv += (*av)[i + 1]; 00299 itksys::SystemTools::PutEnv( threadEnv.c_str() ); 00300 // and set the number of threads locally for the comparison 00301 itk::MultiThreader::SetGlobalDefaultNumberOfThreads(atoi((*av)[i + 1])); 00302 *av += 2; 00303 *ac -= 2; 00304 } 00305 else if ( !skip && strcmp((*av)[i], "--without-threads") == 0 ) 00306 { 00307 itksys::SystemTools::PutEnv( "ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS=1" ); 00308 itk::MultiThreader::SetGlobalDefaultNumberOfThreads(1); 00309 *av += 1; 00310 *ac -= 1; 00311 } 00312 else if ( !skip && strcmp((*av)[i], "--compareNumberOfPixelsTolerance") == 0 ) 00313 { 00314 if ( i + 1 >= *ac ) 00315 { 00316 usage(); 00317 return 1; 00318 } 00319 regressionTestParameters.numberOfPixelsTolerance = atoi((*av)[i + 1]); 00320 *av += 2; 00321 *ac -= 2; 00322 } 00323 else if ( !skip && strcmp((*av)[i], "--compareRadiusTolerance") == 0 ) 00324 { 00325 if ( i + 1 >= *ac ) 00326 { 00327 usage(); 00328 return 1; 00329 } 00330 regressionTestParameters.radiusTolerance = atoi((*av)[i + 1]); 00331 (*av) += 2; 00332 *ac -= 2; 00333 } 00334 else if ( !skip && strcmp((*av)[i], "--compareIntensityTolerance") == 0 ) 00335 { 00336 if ( i + 1 >= *ac ) 00337 { 00338 usage(); 00339 return 1; 00340 } 00341 regressionTestParameters.intensityTolerance = atof((*av)[i + 1]); 00342 (*av) += 2; 00343 *ac -= 2; 00344 } 00345 else if ( !skip && strcmp((*av)[i], "--add-before-libpath") == 0 ) 00346 { 00347 if ( i + 1 >= *ac ) 00348 { 00349 usage(); 00350 return 1; 00351 } 00352 if( processedOutput ) 00353 { 00354 processedOutput->add_before_libpath.push_back( (*av)[i+1] ); 00355 } 00356 (*av) += 2; 00357 *ac -= 2; 00358 } 00359 else if ( !skip && strcmp((*av)[i], "--add-before-env") == 0 ) 00360 { 00361 if ( i + 1 >= *ac ) 00362 { 00363 usage(); 00364 return 1; 00365 } 00366 if( processedOutput ) 00367 { 00368 processedOutput->add_before_env.push_back( (*av)[i+1] ); 00369 processedOutput->add_before_env.push_back( (*av)[i+2] ); 00370 } 00371 (*av) += 3; 00372 *ac -= 3; 00373 } 00374 else if ( !skip && strcmp((*av)[i], "--add-before-env-with-sep") == 0 ) 00375 { 00376 if ( i + 1 >= *ac ) 00377 { 00378 usage(); 00379 return 1; 00380 } 00381 if( processedOutput ) 00382 { 00383 processedOutput->add_before_env_with_sep.push_back( (*av)[i+1] ); 00384 processedOutput->add_before_env_with_sep.push_back( (*av)[i+2] ); 00385 processedOutput->add_before_env_with_sep.push_back( (*av)[i+3] ); 00386 } 00387 (*av) += 4; 00388 *ac -= 4; 00389 } 00390 else if ( !skip && strcmp((*av)[i], "--full-output") == 0 ) 00391 { 00392 // emit the string to tell ctest that the full output should be 00393 // passed to cdash. 00394 std::cout << "CTEST_FULL_OUTPUT" << std::endl; 00395 (*av) += 1; 00396 *ac -= 1; 00397 } 00398 else if ( !skip && strcmp((*av)[i], "--no-process") == 0 ) 00399 { 00400 // The test driver needs to invoke another executable 00401 // For example, the python interpreter to run Wrapping tests. 00402 if( processedOutput ) 00403 { 00404 processedOutput->externalProcessMustBeCalled = false; 00405 } 00406 (*av) += 1; 00407 *ac -= 1; 00408 } 00409 else if ( !skip && strcmp((*av)[i], "--redirectOutput") == 0 ) 00410 { 00411 if ( i + 1 >= *ac ) 00412 { 00413 usage(); 00414 return 1; 00415 } 00416 redirectOutputParameters.redirect = true; 00417 redirectOutputParameters.fileName = (*av)[i + 1]; 00418 *av += 2; 00419 *ac -= 2; 00420 } 00421 else 00422 { 00423 if( processedOutput ) 00424 { 00425 processedOutput->args.push_back((*av)[i]); 00426 } 00427 i += 1; 00428 } 00429 } 00430 00431 return 0; 00432 } 00433 00434 00435 // Regression Testing Code 00436 // 00437 // This method returns: 00438 // max int, if there is an error reading either file 00439 // max int - 1, if the sized of the images don't match 00440 // the number of pixel beyond the tolerance 00441 // otherwise zero is returned if the difference is with in tolerances 00442 00443 int RegressionTestImage(const char *testImageFilename, 00444 const char *baselineImageFilename, 00445 int reportErrors, 00446 double intensityTolerance, 00447 ::itk::SizeValueType numberOfPixelsTolerance, 00448 unsigned int radiusTolerance) 00449 { 00450 // Use the factory mechanism to read the test and baseline files and convert 00451 // them to double 00452 typedef itk::Image< double, ITK_TEST_DIMENSION_MAX > ImageType; 00453 typedef itk::Image< unsigned char, ITK_TEST_DIMENSION_MAX > OutputType; 00454 typedef itk::Image< unsigned char, 2 > DiffOutputType; 00455 typedef itk::ImageFileReader< ImageType > ReaderType; 00456 00457 // Read the baseline file 00458 ReaderType::Pointer baselineReader = ReaderType::New(); 00459 baselineReader->SetFileName(baselineImageFilename); 00460 try 00461 { 00462 baselineReader->UpdateLargestPossibleRegion(); 00463 } 00464 catch ( itk::ExceptionObject & e ) 00465 { 00466 std::cerr << "Exception detected while reading " << baselineImageFilename << " : " << e.GetDescription(); 00467 return itk::NumericTraits< int >::max(); 00468 } 00469 00470 // Read the file generated by the test 00471 ReaderType::Pointer testReader = ReaderType::New(); 00472 testReader->SetFileName(testImageFilename); 00473 try 00474 { 00475 testReader->UpdateLargestPossibleRegion(); 00476 } 00477 catch ( itk::ExceptionObject & e ) 00478 { 00479 std::cerr << "Exception detected while reading " << testImageFilename << " : " << e.GetDescription() << std::endl; 00480 return itk::NumericTraits< int >::max(); 00481 } 00482 00483 // The sizes of the baseline and test image must match 00484 ImageType::SizeType baselineSize; 00485 baselineSize = baselineReader->GetOutput()->GetLargestPossibleRegion().GetSize(); 00486 ImageType::SizeType testSize; 00487 testSize = testReader->GetOutput()->GetLargestPossibleRegion().GetSize(); 00488 00489 if ( baselineSize != testSize ) 00490 { 00491 std::cerr << "The size of the Baseline image and Test image do not match!" << std::endl; 00492 std::cerr << "Baseline image: " << baselineImageFilename 00493 << " has size " << baselineSize << std::endl; 00494 std::cerr << "Test image: " << testImageFilename 00495 << " has size " << testSize << std::endl; 00496 return itk::NumericTraits< int >::max()-1; 00497 } 00498 00499 // Now compare the two images 00500 typedef itk::Testing::ComparisonImageFilter< ImageType, ImageType > DiffType; 00501 DiffType::Pointer diff = DiffType::New(); 00502 diff->SetValidInput( baselineReader->GetOutput() ); 00503 diff->SetTestInput( testReader->GetOutput() ); 00504 diff->SetDifferenceThreshold(intensityTolerance); 00505 diff->SetToleranceRadius(radiusTolerance); 00506 diff->UpdateLargestPossibleRegion(); 00507 00508 itk::SizeValueType status = itk::NumericTraits< itk::SizeValueType >::Zero; 00509 status = diff->GetNumberOfPixelsWithDifferences(); 00510 00511 if ( ! reportErrors ) 00512 { 00513 //The measurement errors should be reported for both success and errors 00514 //to facilitate setting tight tolerances of tests. 00515 std::string shortFilename = itksys::SystemTools::GetFilenameName( baselineImageFilename ); 00516 00517 std::cout << "<DartMeasurement name=\"ImageError " << shortFilename 00518 << "\" type=\"numeric/double\">"; 00519 std::cout << status; 00520 std::cout << "</DartMeasurement>" << std::endl; 00521 } 00522 00523 // if there are discrepencies, create an diff image 00524 if ( ( status > numberOfPixelsTolerance ) && reportErrors ) 00525 { 00526 00527 // Report actuall image error to best baseline 00528 std::cout << "<DartMeasurement name=\"ImageError\" type=\"numeric/double\">"; 00529 std::cout << status; 00530 std::cout << "</DartMeasurement>" << std::endl; 00531 00532 00533 // Report statistics for pixels which exceed tolerances 00534 std::cout << "<DartMeasurement name=\"ImageError Minimum\" type=\"numeric/double\">"; 00535 std::cout << diff->GetMinimumDifference() << "</DartMeasurement>" << std::endl; 00536 00537 std::cout << "<DartMeasurement name=\"ImageError Maximum\" type=\"numeric/double\">"; 00538 std::cout << diff->GetMaximumDifference() << "</DartMeasurement>" << std::endl; 00539 00540 std::cout << "<DartMeasurement name=\"ImageError Mean\" type=\"numeric/double\">"; 00541 std::cout << diff->GetMeanDifference() << "</DartMeasurement>" << std::endl; 00542 00543 00544 typedef itk::Testing::StretchIntensityImageFilter< ImageType, OutputType > RescaleType; 00545 typedef itk::Testing::ExtractSliceImageFilter< OutputType, DiffOutputType > ExtractType; 00546 typedef itk::ImageFileWriter< DiffOutputType > WriterType; 00547 typedef itk::ImageRegion< ITK_TEST_DIMENSION_MAX > RegionType; 00548 OutputType::SizeType size; size.Fill(0); 00549 00550 RescaleType::Pointer rescale = RescaleType::New(); 00551 rescale->SetOutputMinimum( itk::NumericTraits< unsigned char >::NonpositiveMin() ); 00552 rescale->SetOutputMaximum( itk::NumericTraits< unsigned char >::max() ); 00553 rescale->SetInput( diff->GetOutput() ); 00554 rescale->UpdateLargestPossibleRegion(); 00555 size = rescale->GetOutput()->GetLargestPossibleRegion().GetSize(); 00556 00557 //Get the center slice of the image, In 3D, the first slice 00558 //is often a black slice with little debugging information. 00559 OutputType::IndexType index; index.Fill(0); 00560 for ( unsigned int i = 2; i < ITK_TEST_DIMENSION_MAX; i++ ) 00561 { 00562 index[i] = size[i] / 2; //NOTE: Integer Divide used to get approximately 00563 // the center slice 00564 size[i] = 0; 00565 } 00566 00567 RegionType region; 00568 region.SetIndex(index); 00569 00570 region.SetSize(size); 00571 00572 ExtractType::Pointer extract = ExtractType::New(); 00573 extract->SetDirectionCollapseToIdentity(); 00574 extract->SetInput( rescale->GetOutput() ); 00575 extract->SetExtractionRegion(region); 00576 00577 WriterType::Pointer writer = WriterType::New(); 00578 writer->SetInput( extract->GetOutput() ); 00579 00580 std::ostringstream diffName; 00581 diffName << testImageFilename << ".diff.png"; 00582 try 00583 { 00584 rescale->SetInput( diff->GetOutput() ); 00585 rescale->Update(); 00586 } 00587 catch ( const std::exception & e ) 00588 { 00589 std::cerr << "Error during rescale of " << diffName.str() << std::endl; 00590 std::cerr << e.what() << "\n"; 00591 } 00592 catch ( ... ) 00593 { 00594 std::cerr << "Error during rescale of " << diffName.str() << std::endl; 00595 } 00596 writer->SetFileName( diffName.str().c_str() ); 00597 try 00598 { 00599 writer->Update(); 00600 } 00601 catch ( const std::exception & e ) 00602 { 00603 std::cerr << "Error during write of " << diffName.str() << std::endl; 00604 std::cerr << e.what() << "\n"; 00605 } 00606 catch ( ... ) 00607 { 00608 std::cerr << "Error during write of " << diffName.str() << std::endl; 00609 } 00610 00611 std::cout << "<DartMeasurementFile name=\"DifferenceImage\" type=\"image/png\">"; 00612 std::cout << diffName.str(); 00613 std::cout << "</DartMeasurementFile>" << std::endl; 00614 00615 std::ostringstream baseName; 00616 baseName << testImageFilename << ".base.png"; 00617 try 00618 { 00619 rescale->SetInput( baselineReader->GetOutput() ); 00620 rescale->Update(); 00621 } 00622 catch ( const std::exception & e ) 00623 { 00624 std::cerr << "Error during rescale of " << baseName.str() << std::endl; 00625 std::cerr << e.what() << "\n"; 00626 } 00627 catch ( ... ) 00628 { 00629 std::cerr << "Error during rescale of " << baseName.str() << std::endl; 00630 } 00631 try 00632 { 00633 writer->SetFileName( baseName.str().c_str() ); 00634 writer->Update(); 00635 } 00636 catch ( const std::exception & e ) 00637 { 00638 std::cerr << "Error during write of " << baseName.str() << std::endl; 00639 std::cerr << e.what() << "\n"; 00640 } 00641 catch ( ... ) 00642 { 00643 std::cerr << "Error during write of " << baseName.str() << std::endl; 00644 } 00645 00646 std::cout << "<DartMeasurementFile name=\"BaselineImage\" type=\"image/png\">"; 00647 std::cout << baseName.str(); 00648 std::cout << "</DartMeasurementFile>" << std::endl; 00649 00650 std::ostringstream testName; 00651 testName << testImageFilename << ".test.png"; 00652 try 00653 { 00654 rescale->SetInput( testReader->GetOutput() ); 00655 rescale->Update(); 00656 } 00657 catch ( const std::exception & e ) 00658 { 00659 std::cerr << "Error during rescale of " << testName.str() << std::endl; 00660 std::cerr << e.what() << "\n"; 00661 } 00662 catch ( ... ) 00663 { 00664 std::cerr << "Error during rescale of " << testName.str() << std::endl; 00665 } 00666 try 00667 { 00668 writer->SetFileName( testName.str().c_str() ); 00669 writer->Update(); 00670 } 00671 catch ( const std::exception & e ) 00672 { 00673 std::cerr << "Error during write of " << testName.str() << std::endl; 00674 std::cerr << e.what() << "\n"; 00675 } 00676 catch ( ... ) 00677 { 00678 std::cerr << "Error during write of " << testName.str() << std::endl; 00679 } 00680 00681 std::cout << "<DartMeasurementFile name=\"TestImage\" type=\"image/png\">"; 00682 std::cout << testName.str(); 00683 std::cout << "</DartMeasurementFile>" << std::endl; 00684 } 00685 return ( status > numberOfPixelsTolerance ) ? status : 0; 00686 } 00687 00688 template< typename TImageType > 00689 std::string ComputeHash( const char *testImageFilename ) 00690 { 00691 typedef TImageType ImageType; 00692 typedef itk::ImageFileReader< ImageType > ReaderType; 00693 00694 00695 // Read the file generated by the test 00696 typename ReaderType::Pointer testReader = ReaderType::New(); 00697 testReader->SetFileName(testImageFilename); 00698 try 00699 { 00700 testReader->UpdateLargestPossibleRegion(); 00701 } 00702 catch ( itk::ExceptionObject & e ) 00703 { 00704 std::cerr << "Exception detected while reading " << testImageFilename << " : " << e.GetDescription() << std::endl; 00705 throw; // re-throw 00706 } 00707 00708 typedef itk::Testing::HashImageFilter<ImageType> HashFilterType; 00709 00710 typename HashFilterType::Pointer hasher = HashFilterType::New(); 00711 hasher->SetInput( testReader->GetOutput() ); 00712 hasher->Update(); 00713 00714 return hasher->GetHash(); 00715 } 00716 00717 00718 int HashTestImage( const char *testImageFilename, 00719 const std::vector<std::string> &baselineMD5Vector ) 00720 { 00721 itk::ImageIOBase::Pointer iobase = 00722 itk::ImageIOFactory::CreateImageIO( testImageFilename, itk::ImageIOFactory::ReadMode); 00723 00724 if ( iobase.IsNull() ) 00725 { 00726 itkGenericExceptionMacro( "Unable to determine ImageIO reader for \"" << testImageFilename << "\"" ); 00727 } 00728 00729 // Read the image information 00730 iobase->SetFileName( testImageFilename ); 00731 iobase->ReadImageInformation(); 00732 00733 // get output information about input image 00734 itk::ImageIOBase::IOComponentType componentType = iobase->GetComponentType(); 00735 00736 std::string testMD5 = ""; 00737 switch(componentType) 00738 { 00739 case itk::ImageIOBase::CHAR: 00740 testMD5 = ComputeHash< itk::VectorImage<char, ITK_TEST_DIMENSION_MAX> >( testImageFilename ); 00741 break; 00742 case itk::ImageIOBase::UCHAR: 00743 testMD5 = ComputeHash< itk::VectorImage<unsigned char, ITK_TEST_DIMENSION_MAX> >( testImageFilename ); 00744 break; 00745 case itk::ImageIOBase::SHORT: 00746 testMD5 = ComputeHash< itk::VectorImage<short, ITK_TEST_DIMENSION_MAX> >( testImageFilename ); 00747 break; 00748 case itk::ImageIOBase::USHORT: 00749 testMD5 = ComputeHash< itk::VectorImage<unsigned short, ITK_TEST_DIMENSION_MAX> >( testImageFilename ); 00750 break; 00751 case itk::ImageIOBase::INT: 00752 testMD5 = ComputeHash< itk::VectorImage<int, ITK_TEST_DIMENSION_MAX> >( testImageFilename ); 00753 break; 00754 case itk::ImageIOBase::UINT: 00755 testMD5 = ComputeHash< itk::VectorImage<unsigned int, ITK_TEST_DIMENSION_MAX> >( testImageFilename ); 00756 break; 00757 case itk::ImageIOBase::LONG: 00758 testMD5 = ComputeHash< itk::VectorImage<long, ITK_TEST_DIMENSION_MAX> >( testImageFilename ); 00759 break; 00760 case itk::ImageIOBase::ULONG: 00761 testMD5 = ComputeHash< itk::VectorImage<unsigned long, ITK_TEST_DIMENSION_MAX> >( testImageFilename ); 00762 break; 00763 case itk::ImageIOBase::FLOAT: 00764 case itk::ImageIOBase::DOUBLE: 00765 std::cerr << "Hashing is not supporting for float and double images." << std::endl; 00766 itkGenericExceptionMacro( "Hashing is not supported for images of float or doubles." ); 00767 break; 00768 case itk::ImageIOBase::UNKNOWNCOMPONENTTYPE: 00769 default: 00770 assert( false ); // should never get here unless we forgot a type 00771 itkGenericExceptionMacro( "Logic error!" ); 00772 } 00773 00774 std::vector<std::string>::const_iterator iter = baselineMD5Vector.begin(); 00775 assert( baselineMD5Vector.size() ); 00776 do 00777 { 00778 if ( *iter == testMD5 ) 00779 { 00780 // success, let's get out of here 00781 return 0; 00782 } 00783 } 00784 while (++iter != baselineMD5Vector.end() ); 00785 00786 // failed to match print the different md5s 00787 std::cout << "<DartMeasurement name=\"TestMD5\" type=\"text/string\">"; 00788 std::cout << testMD5; 00789 std::cout << "</DartMeasurement>" << std::endl; 00790 00791 00792 // print out all md5 baselines 00793 for ( iter = baselineMD5Vector.begin(); iter != baselineMD5Vector.end(); ++iter ) 00794 { 00795 std::cout << "<DartMeasurement name=\"BaselineMD5\" type=\"text/string\">"; 00796 std::cout << *iter; 00797 std::cout << "</DartMeasurement>" << std::endl; 00798 } 00799 00800 typedef itk::Image< double, ITK_TEST_DIMENSION_MAX > ImageType; 00801 typedef itk::Image< double, 2 > SliceImageType; 00802 typedef itk::Image< unsigned char, 2 > OutputType; 00803 typedef itk::ImageFileReader< ImageType > ReaderType; 00804 typedef itk::Testing::StretchIntensityImageFilter< SliceImageType, OutputType > RescaleType; 00805 typedef itk::Testing::ExtractSliceImageFilter< ImageType, SliceImageType > ExtractType; 00806 typedef itk::ImageFileWriter< OutputType > WriterType; 00807 00808 // setup reader 00809 ReaderType::Pointer reader = ReaderType::New(); 00810 reader->SetFileName( testImageFilename ); 00811 reader->UpdateLargestPossibleRegion(); 00812 00813 ImageType::SizeType size; 00814 size = reader->GetOutput()->GetLargestPossibleRegion().GetSize(); 00815 00816 //Get the center slice of the image, In 3D, the first slice 00817 //is often a black slice with little debugging information. 00818 ImageType::IndexType index; index.Fill(0); 00819 for ( unsigned int i = 2; i < ITK_TEST_DIMENSION_MAX; i++ ) 00820 { 00821 index[i] = size[i] / 2; //NOTE: Integer Divide used to get approximately 00822 // the center slice 00823 size[i] = 0; 00824 } 00825 00826 00827 ImageType::RegionType region; 00828 region.SetIndex(index); 00829 00830 region.SetSize(size); 00831 00832 ExtractType::Pointer extract = ExtractType::New(); 00833 extract->SetDirectionCollapseToIdentity(); 00834 extract->SetInput( reader->GetOutput() ); 00835 extract->SetExtractionRegion(region); 00836 00837 RescaleType::Pointer rescale = RescaleType::New(); 00838 rescale->SetOutputMinimum( itk::NumericTraits< unsigned char >::NonpositiveMin() ); 00839 rescale->SetOutputMaximum( itk::NumericTraits< unsigned char >::max() ); 00840 rescale->SetInput( extract->GetOutput() ); 00841 00842 WriterType::Pointer writer = WriterType::New(); 00843 writer->SetInput( rescale->GetOutput() ); 00844 00845 00846 std::ostringstream testName; 00847 testName << testImageFilename << ".test.png"; 00848 00849 writer->SetFileName( testName.str().c_str() ); 00850 00851 try 00852 { 00853 rescale->UpdateLargestPossibleRegion(); 00854 writer->Update(); 00855 } 00856 catch ( const std::exception & e ) 00857 { 00858 std::cerr << "Error during rescale and writing of " << testName.str()<< std::endl; 00859 std::cerr << e.what() << "\n"; 00860 } 00861 catch ( ... ) 00862 { 00863 std::cerr << "Unknow error during rescale and writing of " << testName.str() << std::endl; 00864 } 00865 00866 std::cout << "<DartMeasurementFile name=\"TestImage\" type=\"image/png\">"; 00867 std::cout << testName.str(); 00868 std::cout << "</DartMeasurementFile>" << std::endl; 00869 00870 return 1; 00871 } 00872 00873 // 00874 // Generate all of the possible baselines 00875 // The possible baselines are generated fromn the baselineFilename using the 00876 // following algorithm: 00877 // 1) strip the suffix 00878 // 2) append a digit .x 00879 // 3) append the original suffix. 00880 // It the file exists, increment x and continue 00881 // 00882 std::map< std::string, int > RegressionTestBaselines(char *baselineFilename) 00883 { 00884 std::map< std::string, int > baselines; 00885 baselines[std::string(baselineFilename)] = 0; 00886 00887 std::string originalBaseline(baselineFilename); 00888 00889 int x = 0; 00890 std::string::size_type suffixPos = originalBaseline.rfind("."); 00891 std::string suffix; 00892 if ( suffixPos != std::string::npos ) 00893 { 00894 suffix = originalBaseline.substr( suffixPos, originalBaseline.length() ); 00895 originalBaseline.erase( suffixPos, originalBaseline.length() ); 00896 } 00897 while ( ++x ) 00898 { 00899 std::ostringstream filename; 00900 filename << originalBaseline << "." << x << suffix; 00901 std::ifstream filestream( filename.str().c_str() ); 00902 if ( !filestream ) 00903 { 00904 break; 00905 } 00906 baselines[filename.str()] = 0; 00907 filestream.close(); 00908 } 00909 return baselines; 00910 } 00911 00912 00913 // Needed for explicit instantiation 00914 #include "itkTestingComparisonImageFilter.hxx" 00915 00916 #endif 00917