Commit b8006172 authored by Florian's avatar Florian
Browse files

add support for Pleiade NEO

missing default spectral sensitivity value
missing testing data
parent 6a79318a
Pipeline #10835 failed with stages
in 104 minutes and 4 seconds
......@@ -105,6 +105,9 @@ public:
/** Parse Dimap data from a Dimap v2 product */
void ParseDimapV2(const MetadataSupplierInterface & mds, const std::string & prefix = "Dimap_Document.");
/** Parse Dimap data from a Dimap v3 product */
void ParseDimapV3(const MetadataSupplierInterface & mds, const std::string & prefix = "Dimap_Document.");
protected:
private:
......
......@@ -137,6 +137,7 @@ enum class MDNum
RedDisplayChannel,
GreenDisplayChannel,
BlueDisplayChannel,
DeepBlueDisplayChannel,
// optical section
PhysicalGain,
PhysicalBias,
......
/*
* Copyright (C) 2005-2022 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.
*/
#ifndef otbPleiadesNeoImageMetadataInterface_h
#define otbPleiadesNeoImageMetadataInterface_h
#include "otbOpticalImageMetadataInterface.h"
#include <string>
#include "otbDateTime.h"
namespace otb
{
/** \class PleiadesNeoImageMetadataInterface
*
* \brief Creation of an "otb" PleiadesNeoImageMetadataInterface that gets metadata.
*
*
* \ingroup OTBMetadata
*/
class OTBMetadata_EXPORT PleiadesNeoImageMetadataInterface : public OpticalImageMetadataInterface
{
public:
typedef PleiadesNeoImageMetadataInterface Self;
typedef OpticalImageMetadataInterface Superclass;
typedef itk::SmartPointer<Self> Pointer;
typedef itk::SmartPointer<const Self> ConstPointer;
itkNewMacro(Self);
/** Run-time type information (and related methods). */
itkTypeMacro(PleiadesNeoImageMetadataInterface, OpticalImageMetadataInterface);
typedef Superclass::ImageType ImageType;
typedef Superclass::MetaDataDictionaryType MetaDataDictionaryType;
typedef Superclass::VectorType VectorType;
typedef Superclass::VariableLengthVectorType VariableLengthVectorType;
void Parse(ImageMetadata &) override;
protected:
PleiadesNeoImageMetadataInterface();
~PleiadesNeoImageMetadataInterface() = default;
private:
PleiadesNeoImageMetadataInterface(const Self&) = delete;
void operator=(const Self&) = delete;
void FetchTabulatedPhysicalGain(ImageMetadata& imd);
void FetchSolarIrradiance(const std::vector<double> & dimapSolarIrradiance, ImageMetadata& imd);
void FetchSatAngles(const std::vector<double> & incidenceAngles,
const std::vector<double> & alongTrackIncidenceAngles,
const std::vector<double> & axrossTrackIncidenceAngles,
const std::vector<double> & sceneOrientation,
ImageMetadata & imd);
/** Vector that contains the filter function value in 6S format (step of 0.0025 micro m).
* There values a computed by 6S. */
void FetchSpectralSensitivity(const std::string & sensorId, ImageMetadata &imd);
};
namespace MetaData
{
namespace PleiadesNeoUtils
{
const std::string IMAGE_ID_KEY = "ImageID";
const std::string TIME_RANGE_START_KEY = "TimeRangeStart";
const std::string TIME_RANGE_END_KEY = "TimeRangeEnd";
const std::string LINE_PERIOD_KEY = "LinePeriod";
const std::string SWATH_FIRST_COL_KEY = "SwathFirstCol";
const std::string SWATH_LAST_COL_KEY = "SwathLastCol";
/** This struct defines additional metadata used to enhance pansharpening of phr products */
struct SensorModelCharacteristics
{
std::string imageID;
MetaData::TimePoint timeRangeStart;
MetaData::TimePoint timeRangeEnd;
double linePeriod;
int swathFirstCol;
int swathLastCol;
};
bool HasSensorModelCharacteristics(const ImageMetadata &);
/** extract sensor model characteristics from ImageMetadata */
SensorModelCharacteristics GetSensorModelCharacteristics(const ImageMetadata &);
} // end namespace PleiadesNeoUtils
} // end namespace MetaData
} // end namespace otb
#endif
/*
* Copyright (C) 2005-2022 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.
*/
#ifndef otbPleiadesNeoImageMetadataInterfaceFactory_h
#define otbPleiadesNeoImageMetadataInterfaceFactory_h
#include "itkObjectFactoryBase.h"
#include "OTBMetadataExport.h"
namespace otb
{
/** \class PleiadesNeoImageMetadataInterfaceFactory
* \brief Creating an instance of a ImageMetadataInterface object using object factory.
*
* \ingroup OTBMetadata
*/
class OTBMetadata_EXPORT PleiadesNeoImageMetadataInterfaceFactory : public itk::ObjectFactoryBase
{
public:
/** Standard class typedefs. */
typedef PleiadesNeoImageMetadataInterfaceFactory Self;
typedef itk::ObjectFactoryBase Superclass;
typedef itk::SmartPointer<Self> Pointer;
typedef itk::SmartPointer<const Self> ConstPointer;
/** Class methods used to interface with the registered factories. */
const char* GetITKSourceVersion(void) const override;
const char* GetDescription(void) const override;
/** Method for class instantiation. */
itkFactorylessNewMacro(Self);
/** Run-time type information (and related methods). */
itkTypeMacro(PleiadesNeoImageMetadataInterfaceFactory, itk::ObjectFactoryBase);
/** Register one factory of this type */
static void RegisterOneFactory(void)
{
PleiadesNeoImageMetadataInterfaceFactory::Pointer pleiadesNeoIMIFactory = PleiadesNeoImageMetadataInterfaceFactory::New();
itk::ObjectFactoryBase::RegisterFactory(pleiadesNeoIMIFactory);
}
protected:
PleiadesNeoImageMetadataInterfaceFactory();
~PleiadesNeoImageMetadataInterfaceFactory() override;
private:
PleiadesNeoImageMetadataInterfaceFactory(const Self&) = delete;
void operator=(const Self&) = delete;
};
} // end namespace otb
#endif
......@@ -20,7 +20,7 @@
set(DOCUMENTATION "This module contains a set of classes that allow parsing
metadata files from different types of sensor (both optical and radar sensor types
are supported. for instance: Pleiades, SPOT6, TerraSar, and so on).")
are supported. for instance: Pleiades, Pleiades Neo, SPOT6, TerraSar, and so on).")
otb_module(OTBMetadata
ENABLE_SHARED
......
......@@ -47,6 +47,9 @@ set(OTBMetadata_SRC
otbIkonosImageMetadataInterfaceFactory.cxx
otbIkonosImageMetadataInterface.cxx
otbPleiadesNeoImageMetadataInterface.cxx
otbPleiadesNeoImageMetadataInterfaceFactory.cxx
otbPleiadesImageMetadataInterfaceFactory.cxx
otbPleiadesImageMetadataInterface.cxx
......
......@@ -333,4 +333,83 @@ void DimapMetadataHelper::ParseDimapV2(const MetadataSupplierInterface & mds, co
}
}
void DimapMetadataHelper::ParseDimapV3(const MetadataSupplierInterface & mds, const std::string & prefix)
{
std::vector<std::string> missionVec;
ParseVector(mds, prefix + "Dataset_Sources.Source_Identification"
,"Strip_Source.MISSION", missionVec);
m_Data.mission = missionVec[0];
std::vector<std::string> missionIndexVec;
ParseVector(mds, prefix + "Dataset_Sources.Source_Identification"
,"Strip_Source.MISSION_INDEX", missionIndexVec);
m_Data.missionIndex = missionIndexVec[0];
ParseVector(mds, prefix + "Radiometric_Data.Radiometric_Calibration.Instrument_Calibration.Band_Measurement_List.Band_Radiance",
"BAND_ID", m_Data.BandIDs);
ParseVector(mds, prefix + "Geometric_Data.Use_Area.Located_Geometric_Values",
"Solar_Incidences.SUN_ELEVATION", m_Data.SunElevation);
ParseVector(mds, prefix + "Geometric_Data.Use_Area.Located_Geometric_Values",
"Solar_Incidences.SUN_AZIMUTH", m_Data.SunAzimuth);
ParseVector(mds, prefix + "Geometric_Data.Use_Area.Located_Geometric_Values",
"Acquisition_Angles.INCIDENCE_ANGLE", m_Data.IncidenceAngle);
ParseVector(mds, prefix + "Geometric_Data.Use_Area.Located_Geometric_Values",
"Acquisition_Angles.INCIDENCE_ANGLE_ALONG_TRACK", m_Data.AlongTrackIncidenceAngle);
ParseVector(mds, prefix + "Geometric_Data.Use_Area.Located_Geometric_Values",
"Acquisition_Angles.INCIDENCE_ANGLE_ACROSS_TRACK", m_Data.AcrossTrackIncidenceAngle);
ParseVector(mds, prefix + "Geometric_Data.Use_Area.Located_Geometric_Values",
"Acquisition_Angles.VIEWING_ANGLE", m_Data.ViewingAngle);
ParseVector(mds, prefix + "Geometric_Data.Use_Area.Located_Geometric_Values",
"Acquisition_Angles.AZIMUTH_ANGLE", m_Data.AzimuthAngle);
std::vector<double> gainbiasUnavail={};
ParseVector(mds, prefix + "Radiometric_Data.Radiometric_Calibration.Instrument_Calibration.Band_Measurement_List.Band_Radiance",
"BIAS", m_Data.PhysicalBias,gainbiasUnavail);
ParseVector(mds, prefix + "Radiometric_Data.Radiometric_Calibration.Instrument_Calibration.Band_Measurement_List.Band_Radiance",
"GAIN", m_Data.PhysicalGain,gainbiasUnavail);
ParseVector(mds, prefix + "Radiometric_Data.Radiometric_Calibration.Instrument_Calibration.Band_Measurement_List.Band_Solar_Irradiance",
"VALUE" , m_Data.SolarIrradiance);
ParseVector(mds, prefix + "Geometric_Data.Use_Area.Located_Geometric_Values",
"Acquisition_Angles.AZIMUTH_ANGLE" , m_Data.SceneOrientation);
std::string path = prefix + "Product_Information.Delivery_Identification.JOB_ID";
m_Data.ImageID =mds.GetAs<std::string>(path);
path = prefix + "Product_Information.Delivery_Identification.PRODUCTION_DATE";
m_Data.ProductionDate = mds.GetAs<std::string>(path);
auto imagingDate = GetSingleValueFromList<std::string>(mds, prefix + "Dataset_Sources.Source_Identification", "Strip_Source.IMAGING_DATE" );
auto imagingTime = GetSingleValueFromList<std::string>(mds, prefix + "Dataset_Sources.Source_Identification", "Strip_Source.IMAGING_TIME" );
m_Data.AcquisitionDate = imagingDate + "T" + imagingTime;
m_Data.Instrument = GetSingleValueFromList<std::string>(mds, prefix + "Dataset_Sources.Source_Identification", "Strip_Source.INSTRUMENT" );
m_Data.InstrumentIndex = GetSingleValueFromList<std::string>(mds, prefix + "Dataset_Sources.Source_Identification", "Strip_Source.INSTRUMENT_INDEX" );
m_Data.ProcessingLevel = mds.GetAs<std::string>
(prefix + "Processing_Information.Product_Settings.PROCESSING_LEVEL");
m_Data.SpectralProcessing = mds.GetAs<std::string>
(prefix + "Processing_Information.Product_Settings.SPECTRAL_PROCESSING");
// These metadata are specific to PHR sensor products
if (m_Data.mission == "PHRNEO" && m_Data.ProcessingLevel == "SENSOR")
{
m_Data.TimeRangeStart = mds.GetAs<std::string>(prefix + "Geometric_Data.Refined_Model.Time.Time_Range.START");
m_Data.TimeRangeEnd = mds.GetAs<std::string>(prefix + "Geometric_Data.Refined_Model.Time.Time_Range.END");
m_Data.LinePeriod = mds.GetAs<std::string>(prefix +"Geometric_Data.Refined_Model.Time.Time_Stamp.LINE_PERIOD");
m_Data.SwathFirstCol = mds.GetAs<std::string>(prefix + "Geometric_Data.Refined_Model.Geometric_Calibration.Instrument_Calibration.Swath_Range.FIRST_COL");
m_Data.SwathLastCol = mds.GetAs<std::string>(prefix + "Geometric_Data.Refined_Model.Geometric_Calibration.Instrument_Calibration.Swath_Range.LAST_COL");
}
}
} // end namespace otb
\ No newline at end of file
......@@ -27,6 +27,7 @@
#include "otbIkonosImageMetadataInterfaceFactory.h"
#include "otbSpotImageMetadataInterfaceFactory.h"
#include "otbPleiadesImageMetadataInterfaceFactory.h"
#include "otbPleiadesNeoImageMetadataInterfaceFactory.h"
#include "otbSpot6ImageMetadataInterfaceFactory.h"
#include "otbFormosatImageMetadataInterfaceFactory.h"
#include "otbQuickBirdImageMetadataInterfaceFactory.h"
......@@ -102,6 +103,7 @@ void ImageMetadataInterfaceFactory::RegisterBuiltInFactories()
{
itk::ObjectFactoryBase::RegisterFactory(IkonosImageMetadataInterfaceFactory::New());
itk::ObjectFactoryBase::RegisterFactory(SpotImageMetadataInterfaceFactory::New());
itk::ObjectFactoryBase::RegisterFactory(PleiadesNeoImageMetadataInterfaceFactory::New());
itk::ObjectFactoryBase::RegisterFactory(PleiadesImageMetadataInterfaceFactory::New());
itk::ObjectFactoryBase::RegisterFactory(Spot6ImageMetadataInterfaceFactory::New());
itk::ObjectFactoryBase::RegisterFactory(FormosatImageMetadataInterfaceFactory::New());
......
......@@ -29,6 +29,7 @@
#include "otbQuickBirdImageMetadataInterfaceFactory.h"
#include "otbWorldView2ImageMetadataInterfaceFactory.h"
#include "otbPleiadesImageMetadataInterfaceFactory.h"
#include "otbPleiadesNeoImageMetadataInterfaceFactory.h"
#include "otbSpot6ImageMetadataInterfaceFactory.h"
#include "itkMutexLock.h"
......@@ -54,6 +55,7 @@ void OpticalImageMetadataInterfaceFactory::RegisterBuiltInFactories()
itk::ObjectFactoryBase::RegisterFactory(FormosatImageMetadataInterfaceFactory::New());
itk::ObjectFactoryBase::RegisterFactory(QuickBirdImageMetadataInterfaceFactory::New());
itk::ObjectFactoryBase::RegisterFactory(WorldView2ImageMetadataInterfaceFactory::New());
itk::ObjectFactoryBase::RegisterFactory(PleiadesNeoImageMetadataInterfaceFactory::New());
itk::ObjectFactoryBase::RegisterFactory(PleiadesImageMetadataInterfaceFactory::New());
itk::ObjectFactoryBase::RegisterFactory(Spot6ImageMetadataInterfaceFactory::New());
firstTime = false;
......
/*
* Copyright (C) 2005-2022 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 "otbPleiadesNeoImageMetadataInterface.h"
#include "otbMacro.h"
#include "itkMetaDataObject.h"
#include "otbGeometryMetadata.h"
#include "otbStringUtils.h"
#include "otbDimapMetadataHelper.h"
// useful constants
#include <otbMath.h>
#include "itksys/SystemTools.hxx"
#include "otbXMLMetadataSupplier.h"
namespace otb
{
using boost::lexical_cast;
using boost::bad_lexical_cast;
PleiadesNeoImageMetadataInterface::PleiadesNeoImageMetadataInterface()
{
}
void PleiadesNeoImageMetadataInterface::FetchSatAngles(
const std::vector<double> & incidenceAngles,
const std::vector<double> & alongTrackIncidenceAngles,
const std::vector<double> & acrossTrackIncidenceAngles,
const std::vector<double> & sceneOrientation,
ImageMetadata& imd)
{
if(incidenceAngles.size() != 3 || sceneOrientation.size() != 3)
{
otbGenericExceptionMacro(MissingMetadataException,<<"Missing satellite angles in Dimap")
}
// Convention use in input of atmospheric correction parameters computation is
//"90 - satOrientation". PleiadesNeo does not seem to follow this convention so
// inverse the formula here to be able to take the angle read in the metadata
// as input for 6S. The second value is used (center value)
imd.Add(MDNum::SatElevation, 90. - incidenceAngles[1]);
if (alongTrackIncidenceAngles.size() != 3 ||
acrossTrackIncidenceAngles.size() != 3)
{
// Use only orientation if across/along track incidence are not available
imd.Add(MDNum::SatAzimuth, sceneOrientation[1]);
}
else
{
// Use center values
auto cap = sceneOrientation[1];
auto along = alongTrackIncidenceAngles[1];
auto ortho = acrossTrackIncidenceAngles[1];
auto satAzimuth = (cap - std::atan2(std::tan(ortho * CONST_PI_180),
std::tan(along * CONST_PI_180))
* CONST_180_PI);
imd.Add(MDNum::SatAzimuth, fmod(satAzimuth, 360));
}
}
void PleiadesNeoImageMetadataInterface::FetchTabulatedPhysicalGain(ImageMetadata& imd)
{
std::unordered_map<std::string, double> bandNameToPhysicalGain;
// TODO check band order here.
const auto & sensorId = imd[MDStr::SensorID];
if (sensorId == "PHRNEO" || sensorId == "PNEO3" || sensorId == "PNEO4" || sensorId == "PNEO5" || sensorId == "PNEO6")
{
bandNameToPhysicalGain = { {"P", 7.996},
{"B5", 8.039}, {"B1", 6.600}, {"B2", 7.338}, {"B3", 8.132}, {"B6",9.955}, {"B4", 12.089}};
}else
{
otbGenericExceptionMacro(MissingMetadataException, << "Invalid metadata, bad sensor id");
}
for (auto & band: imd.Bands)
{
auto gain = bandNameToPhysicalGain.find(band[MDStr::BandName]);
if (gain == bandNameToPhysicalGain.end())
{
otbGenericExceptionMacro(MissingMetadataException, << "Cannot find the physical gain associated with " << band[MDStr::BandName]);
}
else
{
band.Add(MDNum::PhysicalGain, gain->second);
}
}
}
void PleiadesNeoImageMetadataInterface::FetchSolarIrradiance(const std::vector<double> & dimapSolarIrradiance, ImageMetadata& imd)
{
std::unordered_map<std::string, double> defaultSolarIrradiance;
const auto & sensorID = imd[MDStr::SensorID];
otbGenericExceptionMacro(MissingMetadataException,<< "Invalid metadata, bad sensor id")
// tolerance threshold
double tolerance = 0.05;
auto solarIrradianceIt = dimapSolarIrradiance.begin();
for (auto & band : imd.Bands)
{
auto defaultValue = defaultSolarIrradiance.find(band[MDStr::BandName]);
if (defaultValue != defaultSolarIrradiance.end() &&
std::abs(*solarIrradianceIt - defaultValue->second) > (tolerance * defaultValue->second))
{
band.Add(MDNum::SolarIrradiance, defaultValue->second);
}
else
{
band.Add(MDNum::SolarIrradiance, *solarIrradianceIt);
}
solarIrradianceIt++;
}
}
void PleiadesNeoImageMetadataInterface::FetchSpectralSensitivity(const std::string & sensorId, ImageMetadata& imd)
{
otbGenericExceptionMacro(MissingMetadataException, "No Spectral sensitivity data for the PNEO sensor")
}
void PleiadesNeoImageMetadataInterface::Parse(ImageMetadata &imd)
{
DimapMetadataHelper helper;
// Satellite ID is either PHR 1A or PHR 1B
// Product read by the TIFF/JP2 GDAL driver
if (m_MetadataSupplierInterface->GetAs<std::string>("", "IMAGERY/SATELLITEID").find("NEO") != std::string::npos)
{
// The driver stored the content of the Dimap XML file as metadatas in the IMD domain.
helper.ParseDimapV3(*m_MetadataSupplierInterface, "IMD/");
imd.Add(MDStr::GeometricLevel, helper.GetDimapData().ProcessingLevel);
// fill RPC model
if (imd[MDStr::GeometricLevel] == "SENSOR")
{
FetchRPC(imd, -0.5, -0.5);
}
}
// Product read by the DIMAP GDAL driver
else if (m_MetadataSupplierInterface->GetAs<std::string> ("","MISSION") == "PHRNEO")
{
// The DIMAP driver does not read the same metadata as the TIFF/JP2 one, and
// some required metadata are missing.
// The XML Dimap file is read again and provided to the DimapMetadataHelper
// using a XMLMetadataSupplier
XMLMetadataSupplier xmlMds(m_MetadataSupplierInterface->GetResourceFile());
helper.ParseDimapV3(xmlMds, "Dimap_Document.");
imd.Add(MDStr::GeometricLevel, helper.GetDimapData().ProcessingLevel);
// fill RPC model
if (imd[MDStr::GeometricLevel] == "SENSOR")
{
FetchRPC(imd, -0.5, -0.5);
}
}
// Geom case
else if (m_MetadataSupplierInterface->GetAs<std::string>("", "support_data.sensorID").find("NEO") != std::string::npos)
{
helper.ParseGeom(*m_MetadataSupplierInterface);
imd.Add(MDStr::GeometricLevel, helper.GetDimapData().ProcessingLevel);
}
else
{
otbGenericExceptionMacro(MissingMetadataException,<<"Sensor ID doesn't start with NEO")
}
const auto & dimapData = helper.GetDimapData();
imd.Add(MDStr::SensorID, dimapData.mission + " " +dimapData.missionIndex);
imd.Add(MDStr::Mission, "Pléiades NEO");
imd.Add(MDStr::Instrument, dimapData.Instrument);
imd.Add(MDStr::InstrumentIndex, dimapData.InstrumentIndex);
if (dimapData.BandIDs.size() == imd.Bands.size())
{
const std::unordered_map<std::string, std::string> bandNameToEnhancedBandName =
{{"P", "PAN"}, {"B5", "Deep Blue"}, {"B1", "Blue"}, {"B2", "Green"}, {"B3", "Red"}, {"B6", "Red edge"}, {"B4", "NIR"} };
auto bandId = dimapData.BandIDs.begin();
for (auto & band: imd.Bands)
{
band.Add(MDStr::BandName, *bandId);
auto it = bandNameToEnhancedBandName.find(*bandId);
if (it != bandNameToEnhancedBandName.end())
{
band.Add(MDStr::EnhancedBandName, it->second);
}
else
{
band.Add(MDStr::EnhancedBandName, "Unknown");
}
bandId++;
}
}
else
{
otbGenericExceptionMacro(MissingMetadataException,
<< "The number of bands in image metadatas is incoherent with the DIMAP product")
}
//Sun elevation and azimuth should be taken from the center of the image , [0] is Top Center, [1] is Center, [2] is Bottom Center
//This is the same for Viewing angle, it is taken from the center of the image (see FetchSatAngles)
imd.