otbDecloudTimeSeriesPreProcessor.cxx 23.39 KiB
/*=========================================================================
     Copyright (c) INRAE 2020-2022. All rights reserved.
     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.
=========================================================================*/
#include "itkFixedArray.h"
#include "itkObjectFactory.h"
#include "otbWrapperApplicationFactory.h"
// Application engine
#include "otbStandardFilterWatcher.h"
#include "itkFixedArray.h"
// Stack
#include "otbTensorflowSource.h"
#include "otbTensorflowCommon.h"
#include <vector>
#include <algorithm>
#include <numeric>
// Functor
#include "otbFunctorImageFilter.h"
// Channels slices
#include "otbMultiChannelExtractROI.h"
namespace otb
namespace Wrapper
// Name of the environment variable for the number of outputs
const std::string ENV_VAR_NOUTPUTS = "DECLOUD_PREPROCESSING_NOUTPUTS";
// Structure to store one timestamp and one index
template <class TimestampType>
struct TimestampWithIndex
  TimestampType timestamp;
  unsigned int  index;
// Sorting modes
enum SortMode
  ASC, // Ascending order
  DES, // Descending order
  ABS  // Ascending order computed from the abs(t-tref)
 * \class PixelFunction
 * \brief Computes the output pixel from
 * - SAR pixels (stacked in channels)
 * - Optical pixels (stacked in channels)
 * - pairs (list of pair of SAR and Optical input images indices)
 * - Nodata (of SAR, and Optical)
 * - Number of channels (in SAR, and Optical)
 * The function that computes the output pixel, outputs the stacked [SAR, Optical] pixels in channels
 * \ingroup OTBDecloud
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
template <class TPixel, class TPairList> class PixelFunction { public: PixelFunction() {} ~PixelFunction() {} bool operator!=(const PixelFunction &) const { return false; } bool operator==(const PixelFunction & other) const { return !(*this != other); } // Returns the output pixel size std::size_t OutputSize(std::array<size_t, 2ul> Inputs) const { return m_NbOutputImages * (m_SARNbBands + m_OptNbBands); } // Set parameters void SetParameters(TPairList pairs, unsigned int sarNbBands, unsigned int optNbBands, float sarNdVal, float optNdVal, unsigned int nbOutputImages) { m_Pairs = pairs; m_SARNbBands = sarNbBands; m_OptNbBands = optNbBands; m_SARNoDataValue = sarNdVal; m_OptNoDataValue = optNdVal; m_NbOutputImages = nbOutputImages; } /* * Get the pixel of n input image */ TPixel GetPixel(const TPixel & inPix, unsigned int idx, unsigned int nbBands) const { TPixel pix; pix.SetSize(nbBands); unsigned int outBand = 0; unsigned int start = idx * nbBands; for (unsigned int i = start; i < start + nbBands; i++) { pix[outBand] = inPix[i]; outBand++; } return pix; } /* * Tell if the pixel is full on no-data values */ bool IsNoData(const TPixel & pix, const float noDataValue) const { for (unsigned int i = 0; i < pix.GetSize(); i++) if (pix[i] != noDataValue) return false;
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
return true; } /* * Compute output pixel. * inSARPix: pixel of the stacked SAR images (N x m_SARNbBands) * inOptPix: pixel of the stacker optical images (M x m_OptNbBands) */ inline TPixel operator()(const TPixel & inSARPix, const TPixel & inOptPix) const { // Prepare output pixel TPixel outPix; outPix.SetSize(m_NbOutputImages * (m_SARNbBands + m_OptNbBands)); unsigned int outBand = 0, i = 0; for (unsigned int imgIdx = 0; imgIdx < m_NbOutputImages; imgIdx++) { for (i = 0; i < m_SARNbBands; i++) { outPix[outBand] = m_SARNoDataValue; outBand++; } for (i = 0; i < m_OptNbBands; i++) { outPix[outBand] = m_OptNoDataValue; outBand++; } } // Index of the current pair unsigned int currentNbOutput = 0; // Iterate through pairs outBand = 0; for (auto pair = m_Pairs.begin(); pair != m_Pairs.end() && currentNbOutput < m_NbOutputImages; ++pair) { unsigned int sarIdx = pair->first; // Index of the SAR image unsigned int optIdx = pair->second; // Index of the optical image // Read pixels of both SAR and optical images TPixel sarPix = GetPixel(inSARPix, sarIdx, m_SARNbBands); TPixel optPix = GetPixel(inOptPix, optIdx, m_OptNbBands); // Return the output pixel (concatenate SAR and optical pixel) if both pixels are not no-data if (!IsNoData(sarPix, m_SARNoDataValue) && !IsNoData(optPix, m_OptNoDataValue)) { for (i = 0; i < m_SARNbBands; i++) { outPix[outBand] = sarPix[i]; outBand++; } for (i = 0; i < m_OptNbBands; i++) { outPix[outBand] = optPix[i]; outBand++; } currentNbOutput++; } } // next pair // Return the output pixel return outPix; } private: unsigned int m_NbOutputImages; TPairList m_Pairs;
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
unsigned int m_SARNbBands; unsigned int m_OptNbBands; float m_SARNoDataValue; float m_OptNoDataValue; }; // PixelFunction /** * The OTB Application, that does the work with the functor. */ class DecloudTimeSeriesPreProcessor : public Application { public: // Here is a few declaration of types /** Standard class typedefs. */ typedef DecloudTimeSeriesPreProcessor Self; typedef Application Superclass; typedef itk::SmartPointer<Self> Pointer; typedef itk::SmartPointer<const Self> ConstPointer; /** Standard macro */ itkNewMacro(Self); itkTypeMacro(CRGAPreProcessor, Application); /** Typedefs for image concatenation */ typedef TensorflowSource<FloatVectorImageType> TFSourceType; /** Typedefs for various stuff */ typedef float DeltaTimestampType; typedef float TimestampType; typedef std::vector<TimestampType> TimestampList; typedef std::pair<unsigned int, unsigned int> IndicesPair; typedef std::vector<IndicesPair> IndicesPairList; typedef TimestampWithIndex<TimestampType> TimestampWithIndexType; typedef std::vector<TimestampWithIndexType> TimestampWithIndexList; typedef std::pair<TimestampWithIndexType, TimestampWithIndexType> CandidatePairType; typedef std::vector<CandidatePairType> CandidatePairListType; /** functor */ typedef FloatVectorImageType::PixelType PixelType; typedef PixelFunction<PixelType, IndicesPairList> FunctorType; typedef otb::FunctorImageFilter<FunctorType> FilterType; /** slicing */ typedef otb::MultiChannelExtractROI<PixelType::ValueType, PixelType::ValueType> ExtractorType; void DoUpdateParameters() {} void DoInit() { // Documentation SetName("DecloudTimeSeriesPreProcessor"); SetDescription("This application prepares input time series of SAR/Optical sync pairs."); SetDocLongDescription("This application takes as inputs : 1 optical time series, 1 SAR time series, and their " "respective timestamps lists. Change the " + ENV_VAR_NOUTPUTS + " environment variable to select the number of output images."); SetDocLimitations("None"); SetDocAuthors("Remi Cresson, Nicolas Narcon"); // Input time series AddParameter(ParameterType_InputImageList, "ilsar", "Input SAR images list"); AddParameter(ParameterType_InputImageList, "ilopt", "Input optical images list");
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
// Input timestamps AddParameter(ParameterType_StringList, "timestampssar", "Input SAR images timestamps list"); AddParameter(ParameterType_StringList, "timestampsopt", "Input optical images timestamps list"); // Sorting behavior AddParameter(ParameterType_Choice, "sorting", "The way images pairs are sorted"); AddChoice("sorting.asc", "Sort pairs in ascending chronological order"); AddChoice("sorting.des", "Sort pairs in descending chronological order"); AddChoice("sorting.abs", "Sort pairs in descending absolute gap wrt. reference timestamp"); AddParameter(ParameterType_String, "sorting.abs.reftimestamp", "Reference timestamp"); // SAR-optical gap AddParameter(ParameterType_Float, "maxgap", "maximum gap between SAR and optical images in seconds (!!!"); SetDefaultParameterFloat("maxgap", 144.0 * 3600.0); // No data parameters AddParameter(ParameterType_Float, "nodatasar", "No data value for SAR images"); SetDefaultParameterFloat("nodatasar", 0.0); AddParameter(ParameterType_Float, "nodataopt", "No data value for optical images"); SetDefaultParameterFloat("nodataopt", -10000.0); // Output images m_Outputs = std::max(otb::tf::GetEnvironmentVariableAsInt(ENV_VAR_NOUTPUTS), 1); for (int i = 1; i <= m_Outputs; i++) { std::stringstream sarKey, optKey; sarKey << "outsar" << i; optKey << "outopt" << i; AddParameter(ParameterType_OutputImage, sarKey.str(), "output SAR image"); AddParameter(ParameterType_OutputImage, optKey.str(), "output optical image"); } SetMultiWriting(true); } // Converts a std::string to a TimestampType TimestampType Str2Timestamp(std::string str) { return std::stof(str); } // Return a vector of timestamps with indices TimestampWithIndexList GetTimestampsWithIndices(const std::string key) { // Get timestamp from ParameterStringList otbAppLogINFO("Get timestamps of key " << key); TimestampWithIndexList ts; unsigned int count = 0; for (auto & str : GetParameterStringList(key)) { TimestampWithIndexType newTimestampWithIndex; newTimestampWithIndex.index = count; newTimestampWithIndex.timestamp = Str2Timestamp(str); ts.push_back(newTimestampWithIndex); count++; } return ts; } // Sort the elements from the timestamp // The function modifies the "ts" vector. void SortTimestampsWithIndices(TimestampWithIndexList & ts) { // Sort timestamp (3 different strategies) SortMode sortMode = static_cast<SortMode>(GetParameterInt("sorting")); if (sortMode == ASC)
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
{ otbAppLogINFO("Sorting timestamps in ascending order"); std::sort(ts.begin(), ts.end(), [](const TimestampWithIndexType & a, const TimestampWithIndexType & b) { return a.timestamp < b.timestamp; }); } else if (sortMode == DES) { otbAppLogINFO("Sorting timestamps in descending order"); std::sort(ts.begin(), ts.end(), [](const TimestampWithIndexType & a, const TimestampWithIndexType & b) { return a.timestamp > b.timestamp; }); } else if (sortMode == ABS) { const TimestampType refTimestamp = Str2Timestamp(GetParameterAsString("sorting.abs.reftimestamp")); otbAppLogINFO("Sorting timestamps in ascending order from the gap with reference timestamp " << refTimestamp); std::sort( ts.begin(), ts.end(), [refTimestamp](const TimestampWithIndexType & a, const TimestampWithIndexType & b) { return std::abs(a.timestamp - refTimestamp) < std::abs(b.timestamp - refTimestamp); }); } else otbAppLogCRITICAL("Wrong sorting mode"); } // This function return a std::vector of pairs of SAR and Optical images indices. // The function uses the images timestamps to form the pairs. // After this function, the timestamps are not used anymore. IndicesPairList GetCandidatesPairs() { // Get images timestamps and indices TimestampWithIndexList sarTsWithIdxList = GetTimestampsWithIndices("timestampssar"); TimestampWithIndexList optTsWithIdxList = GetTimestampsWithIndices("timestampsopt"); // Get maxgap const DeltaTimestampType maxgap = GetParameterFloat("maxgap"); if (maxgap < 3600.0) otbAppLogWARNING("maxgap is small (" << maxgap << " seconds). Did you miss to convert maxgap in seconds?"); // Sort the optical images timestamps using ASC, DES or ABS strategy, depending on // the application parameter choice "sorting". SortTimestampsWithIndices(optTsWithIdxList); // Iterate over optical images, since they are freshly re-ordered IndicesPairList indicesPairs; for (auto optTsWithIdx : optTsWithIdxList) { // Timestamp of the optical image TimestampType optTs = optTsWithIdx.timestamp; // Index of the optical image, in the application parameter input image list unsigned int optIdx = optTsWithIdx.index; // For each optical image, we sort the available SAR images from the closest to the farthest // Indices are sorted such as the first index is the closest to the optical image timestamp std::sort(sarTsWithIdxList.begin(), sarTsWithIdxList.end(), [&](const TimestampWithIndexType i, const TimestampWithIndexType j) { return std::abs(i.timestamp - optTs) < std::abs(j.timestamp - optTs); }); // Now we pick all SAR images satisfying the maxgap, in the sorted order. // And we add them into the pairs list. for (auto sarTsWithIdx : sarTsWithIdxList) if (std::abs(sarTsWithIdx.timestamp - optTs) <= maxgap) indicesPairs.push_back({ sarTsWithIdx.index, optIdx }); }
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
otbAppLogINFO("Candidate pairs of indices:"); for (auto & indicesPair : indicesPairs) otbAppLogINFO(<< "\t" << "SAR: " << indicesPair.first << " (" << std::to_string(sarTsWithIdxList[indicesPair.first].timestamp) << ") " << "OPT: " << indicesPair.second << " (" << std::to_string(optTsWithIdxList[indicesPair.second].timestamp) << ")"); if (indicesPairs.size() == 0) { otbAppLogFATAL(<< "No S1/S2 pairs found. You could try to increase the maxgap and/or double check the dates of your timeseries"); } return indicesPairs; } // This function prepares the input layerstack for each source, and populates the factorised list of pairs (outPairs) // sarSrc: SAR source (modified in the function) // optSrc: Optical source (modified in the function) // inIndicesPairs: a std::vector of std::pairs of indices. It describes the original paired images with their // indices. outIndicesPairs: a std::vector of std::pairs of indices (modified in the function). It describes the // paired images with their indices, after we have removed all unused ones. inSARList: input SAR FloatVectorImageList // inOptList: input optical FloatVectorImageList void InstantiateSources(TFSourceType & sarSrc, TFSourceType & optSrc, const IndicesPairList & inIndicesPairs, IndicesPairList & outIndicesPairs, FloatVectorImageListType::Pointer inSARList, FloatVectorImageListType::Pointer inOptList) { otbAppLogINFO("Preparing input images stacks"); // New images list, that will contain only the images that are in "inPairs" FloatVectorImageListType::Pointer sarList = FloatVectorImageListType::New(); FloatVectorImageListType::Pointer optList = FloatVectorImageListType::New(); // Here we build SAR and optical stacks, and update pairs with the indices of the actual images used outIndicesPairs.clear(); std::vector<unsigned int> sarOldIdx, optOldIdx; unsigned int sarNewIdxCounter = 0, optNewIdxCounter = 0; unsigned int sarNewIdx, optNewIdx; for (const auto pair : inIndicesPairs) { // Original indices const unsigned int sarIdx = pair.first; const unsigned int optIdx = pair.second; // Add the new index if its not already in the list of used images auto sarSearch = std::find(sarOldIdx.begin(), sarOldIdx.end(), sarIdx); if (sarSearch == sarOldIdx.end()) { otbAppLogINFO("\tAdd SAR image #" << sarIdx); sarOldIdx.push_back(sarIdx); sarList->PushBack(inSARList->GetNthElement(sarIdx)); sarNewIdx = sarNewIdxCounter; sarNewIdxCounter++; } // Retrieve position in new index if its already in the list of used images else { sarNewIdx = sarSearch - sarOldIdx.begin(); } // Add the new index if its not already in the list of used images auto optSearch = std::find(optOldIdx.begin(), optOldIdx.end(), optIdx); if (optSearch == optOldIdx.end()) { otbAppLogINFO("\tAdd optical image #" << optIdx); optOldIdx.push_back(optIdx);
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
optList->PushBack(inOptList->GetNthElement(optIdx)); optNewIdx = optNewIdxCounter; optNewIdxCounter++; } // Retrieve position in new index if its already in the list of used images else { optNewIdx = optSearch - optOldIdx.begin(); } // Update pairs with new indices otbAppLogINFO("\tNew indices: SAR image #" << sarIdx << " --> " << sarNewIdx << ", Optical image #" << optIdx << " --> " << optNewIdx); outIndicesPairs.push_back({ sarNewIdx, optNewIdx }); } // Set source sarSrc.Set(sarList); optSrc.Set(optList); } /** * Simple check on size of images lists, and timestamps lists. * Also prints stuff. */ void CheckNumbers(const std::string imgsKey, const std::string timestampKey) { // Check that images and timestamps sizes match unsigned int nImgs = GetParameterImageList(imgsKey)->Size(); unsigned int nTimestamps = GetParameterStringList(timestampKey).size(); if (nTimestamps != nImgs) otbAppLogFATAL("There is " << nImgs << " input images at input " << imgsKey << " but " << nTimestamps << " timestamps for " << timestampKey); // Summarize timestamps otbAppLogINFO("Timestamps for key " << timestampKey << ":"); for (auto timestamp : GetParameterStringList(timestampKey)) otbAppLogINFO("\t" << timestamp); } /** * Set-up the filter, and the slicers (because SAR and Optical images are stacked together) * which is the last part of the pipeline */ void InitFilter(FilterType::Pointer & filter, std::vector<ExtractorType::Pointer> & sarSlicers, std::vector<ExtractorType::Pointer> & optSlicers, IndicesPairList & indicesPairs, TFSourceType & sarSrc, TFSourceType & optSrc) { // Clear slicers lists sarSlicers.clear(); optSlicers.clear(); // Get the number of bands in images GetParameterImageList("ilsar")->GetNthElement(0)->UpdateOutputInformation(); GetParameterImageList("ilopt")->GetNthElement(0)->UpdateOutputInformation(); unsigned int sarNbBands = GetParameterImageList("ilsar")->GetNthElement(0)->GetNumberOfComponentsPerPixel(); unsigned int optNbBands = GetParameterImageList("ilopt")->GetNthElement(0)->GetNumberOfComponentsPerPixel(); otbAppLogINFO("Number of bands found in SAR images: " << sarNbBands); otbAppLogINFO("Number of bands found in Optical images: " << optNbBands); // No-data values float sarNoData = GetParameterFloat("nodatasar"); float optNoData = GetParameterFloat("nodataopt");
561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
// Initialize filter filter = FilterType::New(); filter->GetModifiableFunctor().SetParameters(indicesPairs, sarNbBands, optNbBands, sarNoData, optNoData, m_Outputs); filter->SetInputs(sarSrc.Get(), optSrc.Get()); // Initialize slicers FloatVectorImageListType::Pointer sarList = FloatVectorImageListType::New(); FloatVectorImageListType::Pointer optList = FloatVectorImageListType::New(); unsigned int start = 1; for (int i = 1; i <= m_Outputs; i++) { // SAR image ExtractorType::Pointer sarSlicer = ExtractorType::New(); sarSlicer->SetFirstChannel(start); sarSlicer->SetLastChannel(start + sarNbBands - 1); sarSlicer->SetInput(filter->GetOutput()); sarSlicer->UpdateOutputInformation(); sarSlicers.push_back(sarSlicer); // Optical image ExtractorType::Pointer optSlicer = ExtractorType::New(); optSlicer->SetFirstChannel(sarNbBands + start); optSlicer->SetLastChannel(start + sarNbBands + optNbBands - 1); optSlicer->SetInput(filter->GetOutput()); optSlicer->UpdateOutputInformation(); optSlicers.push_back(optSlicer); start += sarNbBands + optNbBands; } } void DoExecute() { // Check that timestamps lists have the same length as images lists CheckNumbers("ilsar", "timestampssar"); CheckNumbers("ilopt", "timestampsopt"); // Form the pairs of images indices. // In this step, optical images are sorted using the ASC, DES, or ABS strategy. // The optical images are first sorted regarding their timestamps. // Then, for each optical image, available SAR images satisfying the // "maxgap" criterion are kept and pairs are formed (Pairs are formed // using the order of SAR images: they are sorted using ABS strategy // relatively to the current optical image). IndicesPairList indicesPairs = GetCandidatesPairs(); // Prepare images stacks of input images that will be used use, and find // the indices of corresponding (SAR, Optical) pairs InstantiateSources(m_SARStack, // Selected SAR images stack (modified) m_OptStack, // Selected optical images stack (modified) indicesPairs, // (SAR, Optical) indices pairs list m_PairsIndices, // List of pairs of indices for selected images (modified) GetParameterImageList("ilsar"), // Input SAR images list GetParameterImageList("ilopt")); // Input optical images list // Initialize the filter that computes the output SAR and optical time series InitFilter(m_Filter, // The filter that "drills" the input time series (modified) m_OutSAR, // The output list of SAR images (modified) m_OutOpt, // The output list of optical images (modified) m_PairsIndices, // List of pairs of indices for selected images m_SARStack, // Selected SAR images stack m_OptStack); // Selected optical images stack // Set outputs for (int i = 1; i <= m_Outputs; i++) { std::stringstream sarKey, optKey; sarKey << "outsar" << i;
631632633634635636637638639640641642643644645646647648649650
optKey << "outopt" << i; SetParameterOutputImage(sarKey.str(), m_OutSAR[i - 1]->GetOutput()); SetParameterOutputImage(optKey.str(), m_OutOpt[i - 1]->GetOutput()); } } private: int m_Outputs; // Number of outputs TFSourceType m_SARStack, m_OptStack; // Layerstacks for inputs FilterType::Pointer m_Filter; // Time series "drilling" filter IndicesPairList m_PairsIndices; // List of pairs of indices for inputs std::vector<ExtractorType::Pointer> m_OutSAR, m_OutOpt; // Channels slicers for outputs }; // end of class } // namespace Wrapper } // end namespace otb OTB_APPLICATION_EXPORT(otb::Wrapper::DecloudTimeSeriesPreProcessor)