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