diff --git a/Modules/Core/Metadata/include/otbGeometryMetadata.h b/Modules/Core/Metadata/include/otbGeometryMetadata.h index ccd902ecce298e49760467685d104a208386760a..c37062502653c76187b584936be77ab58f972f9a 100644 --- a/Modules/Core/Metadata/include/otbGeometryMetadata.h +++ b/Modules/Core/Metadata/include/otbGeometryMetadata.h @@ -164,6 +164,9 @@ struct OTBMetadata_EXPORT RPCParam }; +OTBMetadata_EXPORT bool operator==(const RPCParam & lhs, const RPCParam & rhs); + + } // end namespace Projection } // end namespace otb diff --git a/Modules/Core/Metadata/include/otbMetaDataKey.h b/Modules/Core/Metadata/include/otbMetaDataKey.h index 88656b521e166ff8b9ac3ed6eb82c91956f36a69..2fc00307893480115c73c02c1257fed72323035d 100644 --- a/Modules/Core/Metadata/include/otbMetaDataKey.h +++ b/Modules/Core/Metadata/include/otbMetaDataKey.h @@ -240,6 +240,8 @@ struct OTBMetadata_EXPORT Time : tm }; +OTBMetadata_EXPORT bool operator==(const Time & lhs, const Time & rhs); + struct LUTAxis { /** number of measurements on this axis */ @@ -254,6 +256,9 @@ struct LUTAxis std::string ToJSON(bool multiline=false) const; }; +OTBMetadata_EXPORT bool operator==(const LUTAxis & lhs, const LUTAxis & rhs); + + template <unsigned int VDim> class LUT { public: @@ -268,6 +273,26 @@ public: void OTBMetadata_EXPORT FromString(std::string); }; +template <unsigned int VDim> +OTBMetadata_EXPORT bool operator==(const LUT<VDim> & lhs, const LUT<VDim> & rhs) +{ + bool axisComparison = true; + for (unsigned int i = 0; i < VDim; i++) + { + axisComparison = axisComparison && lhs.Axis[i] == rhs.Axis[i]; + } + return axisComparison && lhs.Array == rhs.Array; +} + + +template <unsigned int VDim> +std::ostream& operator<<(std::ostream& os, const LUT<VDim>& val) +{ + os << val.ToString(); + return os; +} + + typedef LUT<1> LUT1D; typedef LUT<2> LUT2D; @@ -300,6 +325,32 @@ extern OTBMetadata_EXPORT MDL1DBmType MDL1DNames; typedef boost::bimap<MDL2D, std::string> MDL2DBmType; extern OTBMetadata_EXPORT MDL2DBmType MDL2DNames; +template<class T> +std::string EnumToString(T t); + +template<> +std::string EnumToString(MDGeom value); + +template<> +std::string EnumToString(MDNum value); + +template<> +std::string EnumToString(MDStr value); + +template<> +std::string EnumToString(MDL1D value); + +template<> +std::string EnumToString(MDL2D value); + +template<> +std::string EnumToString(MDTime value); + +// Specialization for extra keys +template<> +std::string EnumToString(std::string value); + + } // end namespace MetaData namespace Utils diff --git a/Modules/Core/Metadata/src/otbGeometryMetadata.cxx b/Modules/Core/Metadata/src/otbGeometryMetadata.cxx index 0dadc355c40c57bf0575a36db318f76348ca54b9..fea281966736308de49b24df42e9b25909d82479 100644 --- a/Modules/Core/Metadata/src/otbGeometryMetadata.cxx +++ b/Modules/Core/Metadata/src/otbGeometryMetadata.cxx @@ -107,5 +107,23 @@ std::string RPCParam::ToJSON(bool multiline) const return oss.str(); } +bool operator==(const RPCParam & lhs, const RPCParam & rhs) +{ + return lhs.LineOffset == rhs.LineOffset + && lhs.SampleOffset == rhs.SampleOffset + && lhs.LatOffset == rhs.LatOffset + && lhs.LonOffset == rhs.LonOffset + && lhs.HeightOffset == rhs.HeightOffset + && lhs.LineScale == rhs.LineScale + && lhs.SampleScale == rhs.SampleScale + && lhs.LatScale == rhs.LatScale + && lhs.LonScale == rhs.LonScale + && lhs.HeightScale == rhs.HeightScale + && std::equal(lhs.LineNum, lhs.LineNum+20, rhs.LineNum ) + && std::equal(lhs.LineDen, lhs.LineDen+20, rhs.LineDen ) + && std::equal(lhs.SampleNum, lhs.SampleNum+20, rhs.SampleNum ) + && std::equal(lhs.SampleDen, lhs.SampleDen+20, rhs.SampleDen ); +} + } // end namespace Projection } // end namespace otb diff --git a/Modules/Core/Metadata/src/otbMetaDataKey.cxx b/Modules/Core/Metadata/src/otbMetaDataKey.cxx index db6ec2ac9111cf24fd9783d42e1a445862c8d590..9808d0fec7bd1877841592bd5be13ce00a1aad85 100644 --- a/Modules/Core/Metadata/src/otbMetaDataKey.cxx +++ b/Modules/Core/Metadata/src/otbMetaDataKey.cxx @@ -187,6 +187,16 @@ std::istream& operator>>(std::istream& is, Time& val) #undef _OTB_ISTREAM_EXPECT + + +bool operator==(const Time & lhs, const Time & rhs) +{ + tm tmLhs = lhs; + tm tmRhs = rhs; + return mktime(&tmLhs) + lhs.frac_sec == mktime(&tmRhs) + rhs.frac_sec; +} + + std::string LUTAxis::ToJSON(bool multiline) const { std::ostringstream oss; @@ -394,6 +404,63 @@ MDGeomBmType MDGeomNames = bimapGenerator<MDGeom>(std::map<MDGeom, std::string> {MDGeom::Adjustment, "Adjustment"} }); + +OTBMetadata_EXPORT bool operator==(const LUTAxis & lhs, const LUTAxis & rhs) +{ + return lhs.Size == rhs.Size + && lhs.Origin == rhs.Origin + && lhs.Spacing == rhs.Spacing + && lhs.Values == rhs.Values; +} + +template<> +std::string EnumToString(MDGeom value) +{ + return MetaData::MDGeomNames.left.at(value); +} + +template<> +std::string EnumToString(MDNum value) +{ + return MetaData::MDNumNames.left.at(value); +} + +template<> +std::string EnumToString(MDStr value) +{ + return MetaData::MDStrNames.left.at(value); +} + +template<> +std::string EnumToString(MDL1D value) +{ + return MetaData::MDL1DNames.left.at(value); +} + +template<> +std::string EnumToString(MDL2D value) +{ + return MetaData::MDL2DNames.left.at(value); +} + +template<> +std::string EnumToString(MDTime value) +{ + return MetaData::MDTimeNames.left.at(value); +} + +// Specialization for extra keys +template<> +std::string EnumToString(std::string value) +{ + return value; +} + + + + + + } // end namespace MetaData } // end namespace otb diff --git a/Modules/IO/ImageIO/test/CMakeLists.txt b/Modules/IO/ImageIO/test/CMakeLists.txt index 3a8be81881a79c08c480e3608760e1c8a6b859e1..3bc5cbfe531fdd4e02ffcfba6405a64fae9f052a 100644 --- a/Modules/IO/ImageIO/test/CMakeLists.txt +++ b/Modules/IO/ImageIO/test/CMakeLists.txt @@ -1306,3 +1306,14 @@ otb_add_test(NAME ioTvCompoundMetadataReaderTest otbWriteGeomFile ${INPUTDATA}/QB_Toulouse_combo.vrt ${TEMP}/ioTvCompoundMetadataReaderTest.tif) + + + + +otb_add_test(NAME ioTvCompareMetadataTest COMMAND otbImageIOTestDriver + --compare-metadata ${EPSILON_9} + LARGEINPUT{SENTINEL1/S1A_S6_SLC__1SSV_20150619T195043} + LARGEINPUT{SENTINEL1/S1A_S6_SLC__1SSV_20150619T195043} + otbImageFileReaderRGBTest + ${INPUTDATA}/couleurs_extrait.png + ${TEMP}/ioImageFileReaderRGB_PNG2PNG.png ) diff --git a/Modules/IO/TestKernel/src/otbTestHelper.cxx b/Modules/IO/TestKernel/src/otbTestHelper.cxx index 426db29ec55f9132f696067765210cddf108d657..b6875c0156223a40c13a26b0c7fadfc85f3fd32d 100644 --- a/Modules/IO/TestKernel/src/otbTestHelper.cxx +++ b/Modules/IO/TestKernel/src/otbTestHelper.cxx @@ -1418,7 +1418,78 @@ int TestHelper::RegressionTestImage(int cpt, const char* testImageFilename, cons return ret; } -int TestHelper::RegressionTestMetaData(const char* testImageFilename, const char* baselineImageFilename, const double /*toleranceDiffPixelImage*/) const +namespace +{ + +template <class InputIt1, class InputIt2, class BinaryPredicate > +int CompareMetadataDict( InputIt1 first1, InputIt1 last1, + InputIt2 first2, InputIt2 last2, + bool reportErrors, + const BinaryPredicate & p) +{ + if (std::distance(first1, last1) != std::distance(first2, last2)) + { + if (reportErrors) + { + std::cerr << "Input metadata dictionaries have different sizes" << std::endl; + } + + return 1; + } + + int errorCount = 0; + + while (first1 != last1) + { + if (first1->first != first2->first) + { + errorCount++; + if (reportErrors) + { + std::cerr << "Metadata key " << otb::MetaData::EnumToString(first1->first) + << " does not match between test and baseline images: " + << std::endl; + } + return errorCount; + } + + + if (!p(first1->second, first2->second)) + { + errorCount++; + if (reportErrors) + std::cerr << "Metadata " << otb::MetaData::EnumToString(first1->first) + << " does not match between test and baseline images: " + << std::endl + << "Baseline image: " + << first1->second + << std::endl + << "Test image: " + << first2->second + << std::endl; + } + + ++first1; + ++first2; + } + + return errorCount; +} + + +template <class InputIt1, class InputIt2 > +int CompareMetadataDict( InputIt1 first1, InputIt1 last1, + InputIt2 first2, InputIt2 last2, + bool reportErrors) +{ + auto p = []( const decltype(first1->second) & rhs, + const decltype(first2->second) & lhs) + {return rhs == lhs;}; + return CompareMetadataDict(first1, last1, first2, last2, reportErrors, p); +} +} + +int TestHelper::RegressionTestMetaData(const char* testImageFilename, const char* baselineImageFilename, const double tolerance) const { // 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; @@ -1449,6 +1520,7 @@ int TestHelper::RegressionTestMetaData(const char* testImageFilename, const char } unsigned int errcount = 0; + // The sizes of the baseline and test image must match ImageType::SizeType baselineSize; baselineSize = baselineReader->GetOutput()->GetLargestPossibleRegion().GetSize(); @@ -1583,12 +1655,88 @@ int TestHelper::RegressionTestMetaData(const char* testImageFilename, const char } } } + + const auto & baselineImageMetadata = blImPtr->GetImageMetadata(); + const auto & testImageMetadata = testImPtr->GetImageMetadata(); + + // Compare string keys (strict equality) + errcount += CompareMetadataDict(baselineImageMetadata.StringKeys.begin(), + baselineImageMetadata.StringKeys.end(), + testImageMetadata.StringKeys.begin(), + testImageMetadata.StringKeys.end(), + m_ReportErrors); + + // Compare numeric keys + auto compareDouble = [tolerance](double lhs, double rhs) + {return fabs(lhs - rhs) + <= ( (fabs(lhs) < fabs(rhs) ? fabs(rhs) : fabs(lhs)) * tolerance);}; + + errcount += CompareMetadataDict(baselineImageMetadata.NumericKeys.begin(), + baselineImageMetadata.NumericKeys.end(), + testImageMetadata.NumericKeys.begin(), + testImageMetadata.NumericKeys.end(), + m_ReportErrors, + compareDouble); + + // Compare time keys (strict equality) + errcount += CompareMetadataDict(baselineImageMetadata.TimeKeys.begin(), + baselineImageMetadata.TimeKeys.end(), + testImageMetadata.TimeKeys.begin(), + testImageMetadata.TimeKeys.end(), + m_ReportErrors); + + + // Compare LUTs (strict equality) + errcount += CompareMetadataDict(baselineImageMetadata.LUT1DKeys.begin(), + baselineImageMetadata.LUT1DKeys.end(), + testImageMetadata.LUT1DKeys.begin(), + testImageMetadata.LUT1DKeys.end(), + m_ReportErrors); + + errcount += CompareMetadataDict(baselineImageMetadata.LUT2DKeys.begin(), + baselineImageMetadata.LUT2DKeys.end(), + testImageMetadata.LUT2DKeys.begin(), + testImageMetadata.LUT2DKeys.end(), + m_ReportErrors); + + + // Compare extra keys (strict equality) + errcount += CompareMetadataDict(baselineImageMetadata.ExtraKeys.begin(), + baselineImageMetadata.ExtraKeys.end(), + testImageMetadata.ExtraKeys.begin(), + testImageMetadata.ExtraKeys.end(), + m_ReportErrors); + + + if (baselineImageMetadata.Has(MDGeom::RPC)) + { + if (!testImageMetadata.Has(MDGeom::RPC)) + { + errcount++; + if (m_ReportErrors) + { + std::cerr << "Test image does not have RPC coefficients" << std::endl; + } + + } + if (!(boost::any_cast<Projection::RPCParam>(baselineImageMetadata[MDGeom::RPC]) + == boost::any_cast<Projection::RPCParam>(testImageMetadata[MDGeom::RPC]))) + { + errcount++; + if (m_ReportErrors) + { + std::cerr << "RPC parameters mismatch between baseline and test images" << std::endl; + } + } + } + if (errcount > 0) { std::cout << "<DartMeasurement name=\"MetadataError\" type=\"numeric/int\">"; std::cout << errcount; std::cout << "</DartMeasurement>" << std::endl; } + return errcount; } diff --git a/Modules/IO/TestKernel/test/CMakeLists.txt b/Modules/IO/TestKernel/test/CMakeLists.txt index 2800ab8599d0d9c342c8a18c22f203f9701612f9..2320848aa4efa212b87fb85ddd3f478522a62f6e 100644 --- a/Modules/IO/TestKernel/test/CMakeLists.txt +++ b/Modules/IO/TestKernel/test/CMakeLists.txt @@ -29,7 +29,8 @@ set(OTBTestKernelTests otbCompareAsciiTests.cxx otbCopyTest.cxx otbCompareAsciiTestsEpsilon3_WhiteSpace.cxx - otbTestKernelTestDriver.cxx ) + otbTestKernelTestDriver.cxx + otbDummyTest.cxx) add_executable(otbTestKernelTestDriver ${OTBTestKernelTests}) target_link_libraries(otbTestKernelTestDriver ${OTBTestKernel-Test_LIBRARIES}) @@ -205,3 +206,18 @@ otb_add_test(NAME tsTvCompareImages_DifferentSizes COMMAND otbTestKernelTestDriv ${TEMP}/tsTvCompareImages_DifferentSizes.tif ) set_property(TEST tsTvCompareImages_DifferentSizes PROPERTY WILL_FAIL true) + +otb_add_test(NAME tsTvCompareMetadata1 COMMAND otbTestKernelTestDriver + --compare-metadata 0 + LARGEINPUT{SENTINEL1/S1A_S6_SLC__1SSV_20150619T195043} + LARGEINPUT{SENTINEL1/S1A_S6_SLC__1SSV_20150619T195043} + otbDummyTest + ) + +otb_add_test(NAME tsTvCompareMetadata2 COMMAND otbTestKernelTestDriver + --compare-metadata 0 + ${OTB_DATA_LARGEINPUT_ROOT}/PLEIADES-PRE/TLSE_JP2_ORTHO_DIMAPv2_PMS-N_lossy_12bits/IMG_PHR1Z_PMS_N_001/IMG_PHR1A_PMS-N_201006181052297_ORT_IPU_20111011_0619-001_R1C1.JP2 + ${OTB_DATA_LARGEINPUT_ROOT}/PLEIADES-PRE/TLSE_TIFF_ORTHO_DIMAPv2_MS_lossless_8bits/IMG_PHR1A_MS_002/IMG_PHR1A_MS_201006181052297_ORT_IPU_20111109_7807-004_R1C1.TIF + otbDummyTest + ) +set_property(TEST tsTvCompareMetadata2 PROPERTY WILL_FAIL true) \ No newline at end of file diff --git a/Modules/IO/TestKernel/test/otbDummyTest.cxx b/Modules/IO/TestKernel/test/otbDummyTest.cxx new file mode 100644 index 0000000000000000000000000000000000000000..19a8d604cd8eb917f35a3a79c9ff64148eda2939 --- /dev/null +++ b/Modules/IO/TestKernel/test/otbDummyTest.cxx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2005-2020 Centre National d'Etudes Spatiales (CNES) + * + * This file is part of Orfeo Toolbox + * + * https://www.orfeo-toolbox.org/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <iostream> + +// This test does nothing. It is used to test the TestKernel module +int otbDummyTest(int argc, char* argv[]) +{ + if (argc != 1) + { + std::cerr << argv[0] << "does not take any additional parameter" << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/Modules/IO/TestKernel/test/otbTestKernelTestDriver.cxx b/Modules/IO/TestKernel/test/otbTestKernelTestDriver.cxx index 62863ae2488982a1525099afd6d604a1378572f3..356e3c73d1ba065d4e15d69446fb1b79b201c12e 100644 --- a/Modules/IO/TestKernel/test/otbTestKernelTestDriver.cxx +++ b/Modules/IO/TestKernel/test/otbTestKernelTestDriver.cxx @@ -31,4 +31,5 @@ void RegisterTests() REGISTER_TEST(otbCompareAsciiTests); REGISTER_TEST(otbCompareAsciiTestsEpsilon3_WhiteSpace); REGISTER_TEST(otbCopyTest); + REGISTER_TEST(otbDummyTest); }