From a34b53f645f59a60e8094608430202c8bfe5c027 Mon Sep 17 00:00:00 2001 From: Otmane Lahlou <otmane.lahlou@c-s.fr> Date: Fri, 11 Dec 2009 11:44:45 +0100 Subject: [PATCH] ENH : otbTestDriver bin to use in external projects --- Code/IO/CMakeLists.txt | 18 ++ Code/IO/otbTestDriver.cxx | 522 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 540 insertions(+) create mode 100644 Code/IO/otbTestDriver.cxx diff --git a/Code/IO/CMakeLists.txt b/Code/IO/CMakeLists.txt index 9dfd24ded5..5d296cf6f1 100644 --- a/Code/IO/CMakeLists.txt +++ b/Code/IO/CMakeLists.txt @@ -2,6 +2,9 @@ FILE(GLOB OTBIO_SRCS "*.cxx" ) +# Remove the otbTestDriver cause only an executable is nedded + LIST(REMOVE_ITEM OTBIO_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/otbTestDriver.cxx" ) + IF(NOT OTB_COMPILE_JPEG2000) LIST(REMOVE_ITEM OTBIO_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/otbJPEG2000ImageIO.cxx" ) LIST(REMOVE_ITEM OTBIO_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/otbJPEG2000ImageIOFactory.cxx" ) @@ -64,6 +67,17 @@ IF(NOT OTB_COMPILE_JPEG2000) LIST(REMOVE_ITEM __files1 "${CMAKE_CURRENT_SOURCE_DIR}/otbJpeg2000ImageIO.h" ) ENDIF(NOT OTB_COMPILE_JPEG2000) +# Compile otbTestDriver +# Nedded in the OTB-Wrapping project. +# Has to be compiled even if the BUILD_TEST are set to OFF +IF(CMAKE_COMPILER_IS_GNUCXX) + SET_SOURCE_FILES_PROPERTIES(itkTestDriver.cxx PROPERTIES COMPILE_FLAGS -w) +ENDIF(CMAKE_COMPILER_IS_GNUCXX) + +ADD_EXECUTABLE(otbTestDriver otbTestDriver.cxx) +TARGET_LINK_LIBRARIES(otbTestDriver OTBIO) +SET(ITK_TEST_DRIVER "${EXECUTABLE_OUTPUT_PATH}/otbTestDriver" + CACHE INTERNAL "otbTestDriver path to be used by subprojects") IF(NOT OTB_INSTALL_NO_DEVELOPMENT) FILE(GLOB __files1 "${CMAKE_CURRENT_SOURCE_DIR}/*.h") @@ -71,5 +85,9 @@ IF(NOT OTB_INSTALL_NO_DEVELOPMENT) INSTALL(FILES ${__files1} ${__files2} DESTINATION ${OTB_INSTALL_INCLUDE_DIR_CM24}/IO COMPONENT Development) + INSTALL(TARGETS otbTestDriver RUNTIME DESTINATION ${OTB_INSTALL_BIN_DIR_CM24} COMPONENT Development) ENDIF(NOT OTB_INSTALL_NO_DEVELOPMENT) + + + diff --git a/Code/IO/otbTestDriver.cxx b/Code/IO/otbTestDriver.cxx new file mode 100644 index 0000000000..8309f34cf5 --- /dev/null +++ b/Code/IO/otbTestDriver.cxx @@ -0,0 +1,522 @@ +/*========================================================================= + + Program: ORFEO Toolbox + Language: C++ + Date: $Date$ + Version: $Revision$ + + + Copyright (c) Centre National d'Etudes Spatiales. All rights reserved. + See OTBCopyright.txt for details. + + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notices for more information. + +=========================================================================*/ + +// define some itksys* things to make ShareForward.h happy +#define itksys_SHARED_FORWARD_DIR_BUILD "" +#define itksys_SHARED_FORWARD_PATH_BUILD "" +#define itksys_SHARED_FORWARD_PATH_INSTALL "" +#define itksys_SHARED_FORWARD_EXE_BUILD "" +#define itksys_SHARED_FORWARD_EXE_INSTALL "" + +#include "itkWin32Header.h" +#include <map> +#include <string> +#include <iostream> +#include <fstream> +#include "itkNumericTraits.h" +#include "itkMultiThreader.h" +#include "otbImage.h" +#include "otbImageFileReader.h" +#include "otbImageFileWriter.h" +#include "itkImageRegionConstIterator.h" +#include "itkSubtractImageFilter.h" +#include "itkRescaleIntensityImageFilter.h" +#include "itkExtractImageFilter.h" +#include "itkDifferenceImageFilter.h" +#include "itkImageRegion.h" +#include "itksys/SystemTools.hxx" +// include SharedForward to avoid duplicating the code which find the library path variable +// name and the path separator +#include "itksys/SharedForward.h" +#include "itksys/Process.h" + +#define ITK_TEST_DIMENSION_MAX 6 + +void usage() +{ + std::cerr << "usage: otbTestDriver [options] prg [args]" << std::endl; + std::cerr << std::endl; + std::cerr << "otbTestDriver alter the environment, run a test program and compare the images" << std::endl; + std::cerr << "produced." << std::endl; + std::cerr << std::endl; + std::cerr << "Options:" << std::endl; + std::cerr << " --add-before-libpath PATH" << std::endl; + std::cerr << " Add a path to the library path environment. This option take care of" << std::endl; + std::cerr << " choosing the right environment variable for your system." << std::endl; + std::cerr << " This option can be used several times." << std::endl; + std::cerr << std::endl; + std::cerr << " --add-before-env NAME VALUE" << std::endl; + std::cerr << " Add a VALUE to the variable name in the environment." << std::endl; + std::cerr << " This option can be used several times." << std::endl; + std::cerr << std::endl; + std::cerr << " --compare TEST BASELINE" << std::endl; + std::cerr << " Compare the TEST image to the BASELINE one." << std::endl; + std::cerr << " This option can be used several times." << std::endl; + std::cerr << std::endl; + std::cerr << " --" << std::endl; + std::cerr << " The options after -- are not interpreted by this program and passed" << std::endl; + std::cerr << " directly to the test program." << std::endl; + std::cerr << std::endl; + std::cerr << " --help" << std::endl; + std::cerr << " Display this message and exit." << std::endl; + std::cerr << std::endl; + +} + +// Regression Testing Code + +int RegressionTestImage (const char *testImageFilename, const char *baselineImageFilename, int reportErrors) +{ + // Use the factory mechanism to read the test and baseline files and convert them to double + typedef otb::Image<double,ITK_TEST_DIMENSION_MAX> ImageType; + typedef otb::Image<unsigned char,ITK_TEST_DIMENSION_MAX> OutputType; + typedef otb::Image<unsigned char,2> DiffOutputType; + typedef otb::ImageFileReader<ImageType> ReaderType; + + // Read the baseline file + ReaderType::Pointer baselineReader = ReaderType::New(); + baselineReader->SetFileName(baselineImageFilename); + try + { + baselineReader->UpdateLargestPossibleRegion(); + } + catch (itk::ExceptionObject& e) + { + std::cerr << "Exception detected while reading " << baselineImageFilename << " : " << e.GetDescription(); + return 1000; + } + + // Read the file generated by the test + ReaderType::Pointer testReader = ReaderType::New(); + testReader->SetFileName(testImageFilename); + try + { + testReader->UpdateLargestPossibleRegion(); + } + catch (itk::ExceptionObject& e) + { + std::cerr << "Exception detected while reading " << testImageFilename << " : " << e.GetDescription() << std::endl; + return 1000; + } + + // The sizes of the baseline and test image must match + ImageType::SizeType baselineSize; + baselineSize = baselineReader->GetOutput()->GetLargestPossibleRegion().GetSize(); + ImageType::SizeType testSize; + testSize = testReader->GetOutput()->GetLargestPossibleRegion().GetSize(); + + if (baselineSize != testSize) + { + std::cerr << "The size of the Baseline image and Test image do not match!" << std::endl; + std::cerr << "Baseline image: " << baselineImageFilename + << " has size " << baselineSize << std::endl; + std::cerr << "Test image: " << testImageFilename + << " has size " << testSize << std::endl; + return 1; + } + + // Now compare the two images + typedef itk::DifferenceImageFilter<ImageType,ImageType> DiffType; + DiffType::Pointer diff = DiffType::New(); + diff->SetValidInput(baselineReader->GetOutput()); + diff->SetTestInput(testReader->GetOutput()); + diff->SetDifferenceThreshold(2.0); + diff->UpdateLargestPossibleRegion(); + + double status = diff->GetTotalDifference(); + + // if there are discrepencies, create an diff image + if (status && reportErrors) + { + typedef itk::RescaleIntensityImageFilter<ImageType,OutputType> RescaleType; + typedef itk::ExtractImageFilter<OutputType,DiffOutputType> ExtractType; + typedef otb::ImageFileWriter<DiffOutputType> WriterType; + typedef itk::ImageRegion<ITK_TEST_DIMENSION_MAX> RegionType; + + OutputType::IndexType index; index.Fill(0); + OutputType::SizeType size; size.Fill(0); + + RescaleType::Pointer rescale = RescaleType::New(); + rescale->SetOutputMinimum(itk::NumericTraits<unsigned char>::NonpositiveMin()); + rescale->SetOutputMaximum(itk::NumericTraits<unsigned char>::max()); + rescale->SetInput(diff->GetOutput()); + rescale->UpdateLargestPossibleRegion(); + + RegionType region; + region.SetIndex(index); + + size = rescale->GetOutput()->GetLargestPossibleRegion().GetSize(); + for (unsigned int i = 2; i < ITK_TEST_DIMENSION_MAX; i++) + { + size[i] = 0; + } + region.SetSize(size); + + ExtractType::Pointer extract = ExtractType::New(); + extract->SetInput(rescale->GetOutput()); + extract->SetExtractionRegion(region); + + WriterType::Pointer writer = WriterType::New(); + writer->SetInput(extract->GetOutput()); + + std::cout << "<DartMeasurement name=\"ImageError\" type=\"numeric/double\">"; + std::cout << status; + std::cout << "</DartMeasurement>" << std::endl; + + ::itk::OStringStream diffName; + diffName << testImageFilename << ".diff.png"; + try + { + rescale->SetInput(diff->GetOutput()); + rescale->Update(); + } + catch(const std::exception& e) + { + std::cerr << "Error during rescale of " << diffName.str() << std::endl; + std::cerr << e.what() << "\n"; + } + catch (...) + { + std::cerr << "Error during rescale of " << diffName.str() << std::endl; + } + writer->SetFileName(diffName.str().c_str()); + try + { + writer->Update(); + } + catch(const std::exception& e) + { + std::cerr << "Error during write of " << diffName.str() << std::endl; + std::cerr << e.what() << "\n"; + } + catch (...) + { + std::cerr << "Error during write of " << diffName.str() << std::endl; + } + + std::cout << "<DartMeasurementFile name=\"DifferenceImage\" type=\"image/png\">"; + std::cout << diffName.str(); + std::cout << "</DartMeasurementFile>" << std::endl; + + ::itk::OStringStream baseName; + baseName << testImageFilename << ".base.png"; + try + { + rescale->SetInput(baselineReader->GetOutput()); + rescale->Update(); + } + catch(const std::exception& e) + { + std::cerr << "Error during rescale of " << baseName.str() << std::endl; + std::cerr << e.what() << "\n"; + } + catch (...) + { + std::cerr << "Error during rescale of " << baseName.str() << std::endl; + } + try + { + writer->SetFileName(baseName.str().c_str()); + writer->Update(); + } + catch(const std::exception& e) + { + std::cerr << "Error during write of " << baseName.str() << std::endl; + std::cerr << e.what() << "\n"; + } + catch (...) + { + std::cerr << "Error during write of " << baseName.str() << std::endl; + } + + std::cout << "<DartMeasurementFile name=\"BaselineImage\" type=\"image/png\">"; + std::cout << baseName.str(); + std::cout << "</DartMeasurementFile>" << std::endl; + + ::itk::OStringStream testName; + testName << testImageFilename << ".test.png"; + try + { + rescale->SetInput(testReader->GetOutput()); + rescale->Update(); + } + catch(const std::exception& e) + { + std::cerr << "Error during rescale of " << testName.str() << std::endl; + std::cerr << e.what() << "\n"; + } + catch (...) + { + std::cerr << "Error during rescale of " << testName.str() << std::endl; + } + try + { + writer->SetFileName(testName.str().c_str()); + writer->Update(); + } + catch(const std::exception& e) + { + std::cerr << "Error during write of " << testName.str() << std::endl; + std::cerr << e.what() << "\n"; + } + catch (...) + { + std::cerr << "Error during write of " << testName.str() << std::endl; + } + + std::cout << "<DartMeasurementFile name=\"TestImage\" type=\"image/png\">"; + std::cout << testName.str(); + std::cout << "</DartMeasurementFile>" << std::endl; + + + } + return (status != 0) ? 1 : 0; +} + +// +// Generate all of the possible baselines +// The possible baselines are generated fromn the baselineFilename using the following algorithm: +// 1) strip the suffix +// 2) append a digit .x +// 3) append the original suffix. +// It the file exists, increment x and continue +// +std::map<std::string,int> RegressionTestBaselines (char *baselineFilename) +{ + std::map<std::string,int> baselines; + baselines[std::string(baselineFilename)] = 0; + + std::string originalBaseline(baselineFilename); + + int x = 0; + std::string::size_type suffixPos = originalBaseline.rfind("."); + std::string suffix; + if (suffixPos != std::string::npos) + { + suffix = originalBaseline.substr(suffixPos,originalBaseline.length()); + originalBaseline.erase(suffixPos,originalBaseline.length()); + } + while (++x) + { + ::itk::OStringStream filename; + filename << originalBaseline << "." << x << suffix; + std::ifstream filestream(filename.str().c_str()); + if (!filestream) + { + break; + } + baselines[filename.str()] = 0; + filestream.close(); + } + return baselines; +} + +int main(int ac, char* av[] ) +{ + std::vector< char* > args; + typedef std::pair< char *, char *> ComparePairType; + std::vector< ComparePairType > compareList; + + // parse the command line + int i = 1; + bool skip = false; + while( i < ac ) + { + if( !skip && strcmp(av[i], "--add-before-libpath") == 0 ) + { + if( i+1 >= ac ) + { + usage(); + return 1; + } + std::string libpath = KWSYS_SHARED_FORWARD_LDPATH; + libpath += "="; + libpath += av[i+1]; + char * oldenv = getenv(KWSYS_SHARED_FORWARD_LDPATH); + if( oldenv ) + { + libpath += KWSYS_SHARED_FORWARD_PATH_SEP; + libpath += oldenv; + } + itksys::SystemTools::PutEnv( libpath.c_str() ); + // on some 64 bit systems, LD_LIBRARY_PATH_64 is used before + // LD_LIBRARY_PATH if it is set. It can lead the test to load + // the system library instead of the expected one, so this + // var must also be set + if( std::string(KWSYS_SHARED_FORWARD_LDPATH) == "LD_LIBRARY_PATH" ) + { + std::string libpath = "LD_LIBRARY_PATH_64"; + libpath += "="; + libpath += av[i+1]; + char * oldenv = getenv("LD_LIBRARY_PATH_64"); + if( oldenv ) + { + libpath += KWSYS_SHARED_FORWARD_PATH_SEP; + libpath += oldenv; + } + itksys::SystemTools::PutEnv( libpath.c_str() ); + } + i += 2; + } + else if( !skip && strcmp(av[i], "--add-before-env") == 0 ) + { + if( i+2 >= ac ) + { + usage(); + return 1; + } + std::string env = av[i+1]; + env += "="; + env += av[i+2]; + char * oldenv = getenv(av[i+1]); + if( oldenv ) + { + env += KWSYS_SHARED_FORWARD_PATH_SEP; + env += oldenv; + } + itksys::SystemTools::PutEnv( env.c_str() ); + i += 3; + } + else if( !skip && strcmp(av[i], "--compare") == 0 ) + { + if( i+2 >= ac ) + { + usage(); + return 1; + } + compareList.push_back( ComparePairType( av[i+1], av[i+2] ) ); + i += 3; + } + else if( !skip && strcmp(av[i], "--") == 0 ) + { + skip = true; + i += 1; + } + else if( !skip && strcmp(av[i], "--help") == 0 ) + { + usage(); + return 0; + } + else + { + args.push_back( av[i] ); + i += 1; + } + } + + if( args.empty() ) + { + usage(); + return 1; + } + + // a NULL is required at the end of the table + char** argv = new char*[ args.size() + 1 ]; + for( i=0; i<static_cast<int>(args.size()); i++ ) + { + argv[ i ] = args[ i ]; + } + argv[ args.size() ] = NULL; + + itksysProcess * process = itksysProcess_New(); + itksysProcess_SetCommand( process, argv ); + itksysProcess_SetPipeShared( process, itksysProcess_Pipe_STDOUT, true); + itksysProcess_SetPipeShared( process, itksysProcess_Pipe_STDERR, true); + itksysProcess_Execute( process ); + itksysProcess_WaitForExit( process, NULL ); + + delete []argv; + + int retCode = itksysProcess_GetExitValue( process ); + if( retCode != 0 ) + { + // no need to compare the images: the test has failed + return retCode; + } + + // now compare the images + try + { + for( i=0; i<static_cast<int>(compareList.size()); i++) + { + char * testFilename = compareList[i].first; + char * baselineFilename = compareList[i].second; + std::cout << "testFilename: " << testFilename << " baselineFilename: " << baselineFilename << std::endl; + + // Make a list of possible baselines + std::map<std::string,int> baselines = RegressionTestBaselines(baselineFilename); + std::map<std::string,int>::iterator baseline = baselines.begin(); + std::string bestBaseline; + int bestBaselineStatus = itk::NumericTraits<int>::max(); + while (baseline != baselines.end()) + { + baseline->second = RegressionTestImage(testFilename, + (baseline->first).c_str(), + 0); + if (baseline->second < bestBaselineStatus) + { + bestBaseline = baseline->first; + bestBaselineStatus = baseline->second; + } + if (baseline->second == 0) + { + break; + } + ++baseline; + } + // if the best we can do still has errors, generate the error images + if (bestBaselineStatus) + { + baseline->second = RegressionTestImage(testFilename, + bestBaseline.c_str(), + 1); + } + + // output the matching baseline + std::cout << "<DartMeasurement name=\"BaselineImageName\" type=\"text/string\">"; + std::cout << itksys::SystemTools::GetFilenameName(bestBaseline); + std::cout << "</DartMeasurement>" << std::endl; + + if( bestBaselineStatus != 0 ) + { + return bestBaselineStatus; + } + } + + } + catch(const itk::ExceptionObject& e) + { + std::cerr << "ITK test driver caught an ITK exception:\n"; + std::cerr << e.GetFile() << ":" << e.GetLine() << ":\n" + << e.GetDescription() << "\n"; + return -1; + } + catch(const std::exception& e) + { + std::cerr << "ITK test driver caught an exception:\n"; + std::cerr << e.what() << "\n"; + return -1; + } + catch(...) + { + std::cerr << "ITK test driver caught an unknown exception!!!\n"; + return -1; + } + + return 0; +} -- GitLab