From d5e3521a8ba932db33473a2c489deae3d7ace238 Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Wed, 5 Apr 2023 23:13:21 +0200
Subject: [PATCH 01/22] WIP: input nodata

---
 include/otbTensorflowMultisourceModelBase.h   | 33 +++++++++++----
 include/otbTensorflowMultisourceModelBase.hxx | 42 ++++++++++++++++---
 .../otbTensorflowMultisourceModelFilter.hxx   | 38 +++++++++--------
 3 files changed, 83 insertions(+), 30 deletions(-)

diff --git a/include/otbTensorflowMultisourceModelBase.h b/include/otbTensorflowMultisourceModelBase.h
index 6c943d1f..d2c43cb4 100644
--- a/include/otbTensorflowMultisourceModelBase.h
+++ b/include/otbTensorflowMultisourceModelBase.h
@@ -96,6 +96,8 @@ public:
   typedef std::pair<std::string, tensorflow::Tensor> DictElementType;
   typedef std::vector<std::string>                   StringList;
   typedef std::vector<SizeType>                      SizeListType;
+  typedef std::vector<bool>                          BoolListType;
+  typedef std::vector<InternalPixelType>             ValueListType;
   typedef std::vector<DictElementType>               DictType;
   typedef std::vector<tensorflow::DataType>          DataTypeListType;
   typedef std::vector<tensorflow::TensorShapeProto>  TensorShapeProtoList;
@@ -119,7 +121,13 @@ public:
 
   /** Model parameters */
   void
-  PushBackInputTensorBundle(std::string name, SizeType receptiveField, ImagePointerType image);
+  PushBackInputTensorBundle(
+    std::string name, 
+    SizeType 
+    receptiveField, 
+    ImagePointerType image,
+    bool useNodata,
+    InternalPixelType nodataValue);
   void
   PushBackOuputTensorBundle(std::string name, SizeType expressionField);
 
@@ -131,6 +139,14 @@ public:
   itkSetMacro(InputReceptiveFields, SizeListType);
   itkGetMacro(InputReceptiveFields, SizeListType);
 
+  /** Use no-data */
+  itkSetMacro(InputUseNodata, BoolListType);
+  itkGetMacro(InputUseNodata, BoolListType);
+
+  /** No-data value */
+  itkSetMacro(InputNodataValue, ValueListType);
+  itkGetMacro(InputNodataValue, ValueListType);
+
   /** Output tensors names */
   itkSetMacro(OutputTensors, StringList);
   itkGetMacro(OutputTensors, StringList);
@@ -173,6 +189,7 @@ protected:
 
   virtual void
   RunSession(DictType & inputs, TensorListType & outputs);
+  RunSession(DictType & inputs, TensorListType & outputs, bool & nodata);
 
 private:
   TensorflowMultisourceModelBase(const Self &); // purposely not implemented
@@ -183,12 +200,14 @@ private:
   tensorflow::SavedModelBundle * m_SavedModel; // The TensorFlow model
 
   // Model parameters
-  StringList   m_InputPlaceholders;      // Input placeholders names
-  SizeListType m_InputReceptiveFields;   // Input receptive fields
-  StringList   m_OutputTensors;          // Output tensors names
-  SizeListType m_OutputExpressionFields; // Output expression fields
-  DictType     m_UserPlaceholders;       // User placeholders
-  StringList   m_TargetNodesNames;       // User nodes target
+  StringList    m_InputPlaceholders;      // Input placeholders names
+  SizeListType  m_InputReceptiveFields;   // Input receptive fields
+  ValueListType m_InputNodataValues;      // Input no-data values
+  BoolListType  m_InputUseNodata;         // Input no-data used
+  StringList    m_OutputTensors;          // Output tensors names
+  SizeListType  m_OutputExpressionFields; // Output expression fields
+  DictType      m_UserPlaceholders;       // User placeholders
+  StringList    m_TargetNodesNames;       // User nodes target
 
   // Internal, read-only
   DataTypeListType     m_InputConstantsDataTypes; // Input constants datatype
diff --git a/include/otbTensorflowMultisourceModelBase.hxx b/include/otbTensorflowMultisourceModelBase.hxx
index ba461262..2c182ba3 100644
--- a/include/otbTensorflowMultisourceModelBase.hxx
+++ b/include/otbTensorflowMultisourceModelBase.hxx
@@ -55,13 +55,18 @@ TensorflowMultisourceModelBase<TInputImage, TOutputImage>::GetSignatureDef()
 
 template <class TInputImage, class TOutputImage>
 void
-TensorflowMultisourceModelBase<TInputImage, TOutputImage>::PushBackInputTensorBundle(std::string      placeholder,
-                                                                                     SizeType         receptiveField,
-                                                                                     ImagePointerType image)
+TensorflowMultisourceModelBase<TInputImage, TOutputImage>::PushBackInputTensorBundle(
+  std::string       placeholder,
+  SizeType          receptiveField,
+  ImagePointerType  image,
+  bool              useNodata = false,
+  InternalPixelType nodataValue = 0)
 {
   Superclass::PushBackInput(image);
   m_InputReceptiveFields.push_back(receptiveField);
   m_InputPlaceholders.push_back(placeholder);
+  m_InputUseNodata.push_back(useNoData);
+  m_InputNodataValues.push_back(nodataValue);
 }
 
 template <class TInputImage, class TOutputImage>
@@ -96,10 +101,17 @@ TensorflowMultisourceModelBase<TInputImage, TOutputImage>::GenerateDebugReport(D
   return debugReport;
 }
 
-
 template <class TInputImage, class TOutputImage>
 void
 TensorflowMultisourceModelBase<TInputImage, TOutputImage>::RunSession(DictType & inputs, TensorListType & outputs)
+{
+  bool nodata;
+  this->RunSession(inputs, outputs, nodata);
+}
+
+template <class TInputImage, class TOutputImage>
+void
+TensorflowMultisourceModelBase<TInputImage, TOutputImage>::RunSession(DictType & inputs, TensorListType & outputs, bool & nodata)
 {
 
   // Run the TF session here
@@ -119,10 +131,28 @@ TensorflowMultisourceModelBase<TInputImage, TOutputImage>::RunSession(DictType &
   }
 
   // Add input tensors
+  // During this step we also check for nodata values
+  struct IsNodata
+    {
+        const InternalPixelType ndval;
+        IsNodata(InternalPixelType val) : ndval(val) {}
+        bool operator()(InternalPixelType val) const { return val == ndval; }
+    };
+  nodata = False;
   k = 0;
   for (auto & dict : inputs)
   {
-    inputs_new.emplace_back(m_InputLayers[k], dict.second);
+    auto inputTensor = dict.second;
+    inputs_new.emplace_back(m_InputLayers[k], inputTensor);
+    if (m_InputUseNodata[k] == true)
+    {
+      auto array = inputTensor.flat<InternalPixelType>().data();
+      if (std::all_of(array.cbegin(), array.cend(), IsNodata(m_InputNodataValue[k])))
+      {
+        nodata = true;
+        return;
+      }
+    }
     k += 1;
   }
 
@@ -140,7 +170,7 @@ TensorflowMultisourceModelBase<TInputImage, TOutputImage>::RunSession(DictType &
                       << "Tensorflow error message:\n"
                       << status.ToString()
                       << "\n"
-                         "OTB Filter debug message:\n"
+                        "OTB Filter debug message:\n"
                       << debugReport.str());
   }
 }
diff --git a/include/otbTensorflowMultisourceModelFilter.hxx b/include/otbTensorflowMultisourceModelFilter.hxx
index 3cbb53d9..59105dd0 100644
--- a/include/otbTensorflowMultisourceModelFilter.hxx
+++ b/include/otbTensorflowMultisourceModelFilter.hxx
@@ -470,31 +470,35 @@ TensorflowMultisourceModelFilter<TInputImage, TOutputImage>::GenerateData()
   // Run session
   // TODO: see if we print some info about inputs/outputs of the model e.g. m_OutputTensors
   TensorListType outputs;
-  this->RunSession(inputs, outputs);
+  bool nodata;
+  this->RunSession(inputs, outputs, nodata);
 
   // Fill the output buffer with zero value
   outputPtr->SetBufferedRegion(outputReqRegion);
   outputPtr->Allocate();
   outputPtr->FillBuffer(m_NullPixel);
 
-  // Get output tensors
-  int bandOffset = 0;
-  for (unsigned int i = 0; i < outputs.size(); i++)
+  if (nodata == false)
   {
-    // The offset (i.e. the starting index of the channel for the output tensor) is updated
-    // during this call
-    // TODO: implement a generic strategy enabling expression field copy in patch-based mode (see
-    // tf::CopyTensorToImageRegion)
-    try
+    // Get output tensors
+    int bandOffset = 0;
+    for (unsigned int i = 0; i < outputs.size(); i++)
     {
-      tf::CopyTensorToImageRegion<TOutputImage>(
-        outputs[i], outputAlignedReqRegion, outputPtr, outputReqRegion, bandOffset);
-    }
-    catch (itk::ExceptionObject & err)
-    {
-      std::stringstream debugMsg = this->GenerateDebugReport(inputs);
-      itkExceptionMacro("Error occurred during tensor to image conversion.\n"
-                        << "Context: " << debugMsg.str() << "Error:" << err);
+      // The offset (i.e. the starting index of the channel for the output tensor) is updated
+      // during this call
+      // TODO: implement a generic strategy enabling expression field copy in patch-based mode (see
+      // tf::CopyTensorToImageRegion)
+      try
+      {
+        tf::CopyTensorToImageRegion<TOutputImage>(
+          outputs[i], outputAlignedReqRegion, outputPtr, outputReqRegion, bandOffset);
+      }
+      catch (itk::ExceptionObject & err)
+      {
+        std::stringstream debugMsg = this->GenerateDebugReport(inputs);
+        itkExceptionMacro("Error occurred during tensor to image conversion.\n"
+                          << "Context: " << debugMsg.str() << "Error:" << err);
+      }
     }
   }
 }
-- 
GitLab


From 3467313782ae1421d503c22cd791d05ad820c613 Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Wed, 5 Apr 2023 23:50:18 +0200
Subject: [PATCH 02/22] WIP: input nodata

---
 include/otbTensorflowMultisourceModelBase.h   | 12 +++---
 include/otbTensorflowMultisourceModelBase.hxx | 39 +++++++++----------
 2 files changed, 26 insertions(+), 25 deletions(-)

diff --git a/include/otbTensorflowMultisourceModelBase.h b/include/otbTensorflowMultisourceModelBase.h
index d2c43cb4..f452cc22 100644
--- a/include/otbTensorflowMultisourceModelBase.h
+++ b/include/otbTensorflowMultisourceModelBase.h
@@ -126,8 +126,8 @@ public:
     SizeType 
     receptiveField, 
     ImagePointerType image,
-    bool useNodata,
-    InternalPixelType nodataValue);
+    bool useNodata = false,
+    InternalPixelType nodataValue = 0);
   void
   PushBackOuputTensorBundle(std::string name, SizeType expressionField);
 
@@ -144,8 +144,8 @@ public:
   itkGetMacro(InputUseNodata, BoolListType);
 
   /** No-data value */
-  itkSetMacro(InputNodataValue, ValueListType);
-  itkGetMacro(InputNodataValue, ValueListType);
+  itkSetMacro(InputNodataValues, ValueListType);
+  itkGetMacro(InputNodataValues, ValueListType);
 
   /** Output tensors names */
   itkSetMacro(OutputTensors, StringList);
@@ -188,9 +188,11 @@ protected:
   GenerateDebugReport(DictType & inputs);
 
   virtual void
-  RunSession(DictType & inputs, TensorListType & outputs);
   RunSession(DictType & inputs, TensorListType & outputs, bool & nodata);
 
+  virtual void
+  RunSession(DictType & inputs, TensorListType & outputs);
+  
 private:
   TensorflowMultisourceModelBase(const Self &); // purposely not implemented
   void
diff --git a/include/otbTensorflowMultisourceModelBase.hxx b/include/otbTensorflowMultisourceModelBase.hxx
index 2c182ba3..15e8dbef 100644
--- a/include/otbTensorflowMultisourceModelBase.hxx
+++ b/include/otbTensorflowMultisourceModelBase.hxx
@@ -59,13 +59,13 @@ TensorflowMultisourceModelBase<TInputImage, TOutputImage>::PushBackInputTensorBu
   std::string       placeholder,
   SizeType          receptiveField,
   ImagePointerType  image,
-  bool              useNodata = false,
-  InternalPixelType nodataValue = 0)
+  bool              useNodata,
+  InternalPixelType nodataValue)
 {
   Superclass::PushBackInput(image);
   m_InputReceptiveFields.push_back(receptiveField);
   m_InputPlaceholders.push_back(placeholder);
-  m_InputUseNodata.push_back(useNoData);
+  m_InputUseNodata.push_back(useNodata);
   m_InputNodataValues.push_back(nodataValue);
 }
 
@@ -101,14 +101,6 @@ TensorflowMultisourceModelBase<TInputImage, TOutputImage>::GenerateDebugReport(D
   return debugReport;
 }
 
-template <class TInputImage, class TOutputImage>
-void
-TensorflowMultisourceModelBase<TInputImage, TOutputImage>::RunSession(DictType & inputs, TensorListType & outputs)
-{
-  bool nodata;
-  this->RunSession(inputs, outputs, nodata);
-}
-
 template <class TInputImage, class TOutputImage>
 void
 TensorflowMultisourceModelBase<TInputImage, TOutputImage>::RunSession(DictType & inputs, TensorListType & outputs, bool & nodata)
@@ -132,13 +124,7 @@ TensorflowMultisourceModelBase<TInputImage, TOutputImage>::RunSession(DictType &
 
   // Add input tensors
   // During this step we also check for nodata values
-  struct IsNodata
-    {
-        const InternalPixelType ndval;
-        IsNodata(InternalPixelType val) : ndval(val) {}
-        bool operator()(InternalPixelType val) const { return val == ndval; }
-    };
-  nodata = False;
+  nodata = false;
   k = 0;
   for (auto & dict : inputs)
   {
@@ -146,8 +132,13 @@ TensorflowMultisourceModelBase<TInputImage, TOutputImage>::RunSession(DictType &
     inputs_new.emplace_back(m_InputLayers[k], inputTensor);
     if (m_InputUseNodata[k] == true)
     {
-      auto array = inputTensor.flat<InternalPixelType>().data();
-      if (std::all_of(array.cbegin(), array.cend(), IsNodata(m_InputNodataValue[k])))
+      tensorflow::int64 ndCount = 0;
+      const tensorflow::int64 nElmT = inputTensor.NumElements();
+      auto array = inputTensor.flat<InternalPixelType>();
+      for (tensorflow::int64 i = 0 ; i < nElmT ; i++)
+        if (array(i) == m_InputNodataValues[k])
+          ndCount++;
+      if (ndCount == nElmT)
       {
         nodata = true;
         return;
@@ -175,6 +166,14 @@ TensorflowMultisourceModelBase<TInputImage, TOutputImage>::RunSession(DictType &
   }
 }
 
+template <class TInputImage, class TOutputImage>
+void
+TensorflowMultisourceModelBase<TInputImage, TOutputImage>::RunSession(DictType & inputs, TensorListType & outputs)
+{
+  bool nodata;
+  this->RunSession(inputs, outputs, nodata);
+}
+
 template <class TInputImage, class TOutputImage>
 void
 TensorflowMultisourceModelBase<TInputImage, TOutputImage>::GenerateOutputInformation()
-- 
GitLab


From 9b83bdac08331866d76ef4aba76ee9c066a56577 Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Thu, 6 Apr 2023 00:06:40 +0200
Subject: [PATCH 03/22] ADD: no-data value for input sources

---
 app/otbTensorflowModelServe.cxx | 22 ++++++++++++++++++----
 1 file changed, 18 insertions(+), 4 deletions(-)

diff --git a/app/otbTensorflowModelServe.cxx b/app/otbTensorflowModelServe.cxx
index 47a8c957..1a91cd86 100644
--- a/app/otbTensorflowModelServe.cxx
+++ b/app/otbTensorflowModelServe.cxx
@@ -70,11 +70,14 @@ public:
     InputImageSource m_ImageSource;
     SizeType         m_PatchSize;
     std::string      m_Placeholder;
+    float            m_NodataValue;
+    bool             m_HasNodata;
 
     // Parameters keys
     std::string m_KeyIn;     // Key of input image list
-    std::string m_KeyPszX;   // Key for samples sizes X
-    std::string m_KeyPszY;   // Key for samples sizes Y
+    std::string m_KeyPszX;   // Key for receptive field size in X
+    std::string m_KeyPszY;   // Key for receptive field size in Y
+    std::string m_KeyND;     // Key for no-data value
     std::string m_KeyPHName; // Key for placeholder name in the tensorflow model
   };
 
@@ -93,7 +96,8 @@ public:
     ss_key_in, ss_desc_in,
     ss_key_dims_x, ss_desc_dims_x,
     ss_key_dims_y, ss_desc_dims_y,
-    ss_key_ph, ss_desc_ph;
+    ss_key_ph, ss_desc_ph,
+    ss_key_nd, ss_desc_nd;
 
     // Parameter group key/description
     ss_key_group  << "source"                  << inputNumber;
@@ -104,12 +108,14 @@ public:
     ss_key_dims_x  << ss_key_group.str() << ".rfieldx";
     ss_key_dims_y  << ss_key_group.str() << ".rfieldy";
     ss_key_ph      << ss_key_group.str() << ".placeholder";
+    ss_key_nd      << ss_key_group.str() << ".nodata";
 
     // Parameter group descriptions
     ss_desc_in     << "Input image (or list to stack) for source #" << inputNumber;
     ss_desc_dims_x << "Input receptive field (width) for source #"  << inputNumber;
     ss_desc_dims_y << "Input receptive field (height) for source #" << inputNumber;
     ss_desc_ph     << "Name of the input placeholder for source #"  << inputNumber;
+    ss_desc_nd     << "No-data value for pixels of source #"        << inputNumber;
 
     // Populate group
     AddParameter(ParameterType_Group,          ss_key_group.str(),  ss_desc_group.str());
@@ -122,6 +128,8 @@ public:
     SetDefaultParameterInt                    (ss_key_dims_y.str(), 1);
     AddParameter(ParameterType_String,         ss_key_ph.str(),     ss_desc_ph.str());
     MandatoryOff                              (ss_key_ph.str());
+    AddParameter(ParameterType_Float,          ss_key_nd.str(), ss_desc_nd.str());
+    MandatoryOff                              (ss_key_nd.str());
 
     // Add a new bundle
     ProcessObjectsBundle bundle;
@@ -129,6 +137,7 @@ public:
     bundle.m_KeyPszX   = ss_key_dims_x.str();
     bundle.m_KeyPszY   = ss_key_dims_y.str();
     bundle.m_KeyPHName = ss_key_ph.str();
+    bundle.m_KeyND     = ss_key_nd.str();
 
     m_Bundles.push_back(bundle);
 
@@ -236,10 +245,14 @@ public:
       bundle.m_Placeholder = GetParameterAsString(bundle.m_KeyPHName);
       bundle.m_PatchSize[0] = GetParameterInt(bundle.m_KeyPszX);
       bundle.m_PatchSize[1] = GetParameterInt(bundle.m_KeyPszY);
+      bundle.m_HasNodata = HasValue(bundle.m_KeyND);
+      bundle.m_NodataValue = GetParameterFloat(bundle.m_KeyND);
 
       otbAppLogINFO("Source info :");
       otbAppLogINFO("Receptive field  : " << bundle.m_PatchSize  );
       otbAppLogINFO("Placeholder name : " << bundle.m_Placeholder);
+      if (bundle.m_HasNodata == true)
+        otbAppLogINFO("No-data value    : " << bundle.m_NodataValue);
     }
   }
 
@@ -274,7 +287,8 @@ public:
     // Input sources
     for (auto& bundle: m_Bundles)
     {
-      m_TFFilter->PushBackInputTensorBundle(bundle.m_Placeholder, bundle.m_PatchSize, bundle.m_ImageSource.Get());
+      float nodata = (bundle.m_HasNodata == true) ? bundle.m_NodataValue : 0;
+      m_TFFilter->PushBackInputTensorBundle(bundle.m_Placeholder, bundle.m_PatchSize, bundle.m_ImageSource.Get(), bundle.m_HasNodata, nodata);
     }
 
     // Fully convolutional mode on/off
-- 
GitLab


From e9a443fd3b2bf15d856b49fcc68e06252fed1eff Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Thu, 6 Apr 2023 00:06:59 +0200
Subject: [PATCH 04/22] ADD: no-data support

---
 include/otbTensorflowMultisourceModelBase.hxx | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/include/otbTensorflowMultisourceModelBase.hxx b/include/otbTensorflowMultisourceModelBase.hxx
index 15e8dbef..f0d414b6 100644
--- a/include/otbTensorflowMultisourceModelBase.hxx
+++ b/include/otbTensorflowMultisourceModelBase.hxx
@@ -132,11 +132,12 @@ TensorflowMultisourceModelBase<TInputImage, TOutputImage>::RunSession(DictType &
     inputs_new.emplace_back(m_InputLayers[k], inputTensor);
     if (m_InputUseNodata[k] == true)
     {
-      tensorflow::int64 ndCount = 0;
+      const auto nodataValue = m_InputNodataValues[k];
       const tensorflow::int64 nElmT = inputTensor.NumElements();
+      tensorflow::int64 ndCount = 0;
       auto array = inputTensor.flat<InternalPixelType>();
       for (tensorflow::int64 i = 0 ; i < nElmT ; i++)
-        if (array(i) == m_InputNodataValues[k])
+        if (array(i) == nodataValue)
           ndCount++;
       if (ndCount == nElmT)
       {
-- 
GitLab


From 00f5e4fbee0da3c6705a08f9777a172b2ddba58a Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Thu, 6 Apr 2023 08:31:57 +0200
Subject: [PATCH 05/22] ADD: no-data value support

---
 app/otbTensorflowModelServe.cxx | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/app/otbTensorflowModelServe.cxx b/app/otbTensorflowModelServe.cxx
index 1a91cd86..6189e8fa 100644
--- a/app/otbTensorflowModelServe.cxx
+++ b/app/otbTensorflowModelServe.cxx
@@ -246,7 +246,7 @@ public:
       bundle.m_PatchSize[0] = GetParameterInt(bundle.m_KeyPszX);
       bundle.m_PatchSize[1] = GetParameterInt(bundle.m_KeyPszY);
       bundle.m_HasNodata = HasValue(bundle.m_KeyND);
-      bundle.m_NodataValue = GetParameterFloat(bundle.m_KeyND);
+      bundle.m_NodataValue = (bundle.m_HasNodata == true) ? GetParameterFloat(bundle.m_KeyND) : 0;
 
       otbAppLogINFO("Source info :");
       otbAppLogINFO("Receptive field  : " << bundle.m_PatchSize  );
@@ -287,8 +287,7 @@ public:
     // Input sources
     for (auto& bundle: m_Bundles)
     {
-      float nodata = (bundle.m_HasNodata == true) ? bundle.m_NodataValue : 0;
-      m_TFFilter->PushBackInputTensorBundle(bundle.m_Placeholder, bundle.m_PatchSize, bundle.m_ImageSource.Get(), bundle.m_HasNodata, nodata);
+      m_TFFilter->PushBackInputTensorBundle(bundle.m_Placeholder, bundle.m_PatchSize, bundle.m_ImageSource.Get(), bundle.m_HasNodata, bundle.m_NodataValue);
     }
 
     // Fully convolutional mode on/off
-- 
GitLab


From ff879a252004109f29f9fc843845e0cf338386ac Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Thu, 6 Apr 2023 08:32:11 +0200
Subject: [PATCH 06/22] ADD: no-data values check

---
 include/otbTensorflowMultisourceModelBase.hxx | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/include/otbTensorflowMultisourceModelBase.hxx b/include/otbTensorflowMultisourceModelBase.hxx
index f0d414b6..b67d9223 100644
--- a/include/otbTensorflowMultisourceModelBase.hxx
+++ b/include/otbTensorflowMultisourceModelBase.hxx
@@ -192,6 +192,18 @@ TensorflowMultisourceModelBase<TInputImage, TOutputImage>::GenerateOutputInforma
                       << " and the number of input tensors names is " << m_InputPlaceholders.size());
   }
 
+  // Check that no-data values size is consistent with the inputs
+  // If no value is specified, set a vector of the same size as the inputs
+  if (m_InputNodataValues.size() == 0 && m_InputHasNodata.size() == 0)
+  {
+    m_InputHasNodata = BoolListType(nbInputs, false);
+    m_InputNodataValues = ValueListType(nbInputs, 0.0);
+  }
+  if (nbInputs != m_InputNodataValues.size() || nbInputs != m_InputNodataValues.size())
+  {
+    itkExceptionMacro("Number of input images is " << nbInputs << " but the number of no-data values is not consistent");
+  }
+
   //////////////////////////////////////////////////////////////////////////////////////////
   //                               Get tensors information
   //////////////////////////////////////////////////////////////////////////////////////////
-- 
GitLab


From 7fb41950fedcfab836d0f5df90fdbe309e42887b Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Thu, 6 Apr 2023 09:02:28 +0200
Subject: [PATCH 07/22] ADD: no-data values check

---
 include/otbTensorflowMultisourceModelBase.hxx | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/include/otbTensorflowMultisourceModelBase.hxx b/include/otbTensorflowMultisourceModelBase.hxx
index b67d9223..d9d6d7c6 100644
--- a/include/otbTensorflowMultisourceModelBase.hxx
+++ b/include/otbTensorflowMultisourceModelBase.hxx
@@ -194,12 +194,12 @@ TensorflowMultisourceModelBase<TInputImage, TOutputImage>::GenerateOutputInforma
 
   // Check that no-data values size is consistent with the inputs
   // If no value is specified, set a vector of the same size as the inputs
-  if (m_InputNodataValues.size() == 0 && m_InputHasNodata.size() == 0)
+  if (m_InputNodataValues.size() == 0 && m_InputUseNodata.size() == 0)
   {
-    m_InputHasNodata = BoolListType(nbInputs, false);
+    m_InputUseNodata = BoolListType(nbInputs, false);
     m_InputNodataValues = ValueListType(nbInputs, 0.0);
   }
-  if (nbInputs != m_InputNodataValues.size() || nbInputs != m_InputNodataValues.size())
+  if (nbInputs != m_InputNodataValues.size() || nbInputs != m_InputUseNodata.size())
   {
     itkExceptionMacro("Number of input images is " << nbInputs << " but the number of no-data values is not consistent");
   }
-- 
GitLab


From 034be86068d3c63292b5f50252d04c74bb5b9619 Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Thu, 6 Apr 2023 10:19:48 +0200
Subject: [PATCH 08/22] TEST: add inference no-data test (wip)

---
 test/nodata_test.py | 41 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)
 create mode 100644 test/nodata_test.py

diff --git a/test/nodata_test.py b/test/nodata_test.py
new file mode 100644
index 00000000..0f106af6
--- /dev/null
+++ b/test/nodata_test.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+import pytest
+import unittest
+import otbtf
+import otbApplication
+import tensorflow as tf
+from test_utils import resolve_paths
+
+class NodataInferenceTest(unittest.TestCase):
+
+    def test_infersimple(self):
+        sm_dir = resolve_paths("$TMPDIR/l2_norm_savedmodel")
+
+        # Create model
+        x = tf.keras.Input(shape=[None, None, None], name="x")
+        y = tf.norm(x, axis=-1)
+        model = tf.keras.Model(inputs={"x": x}, outputs={"y": y})
+        model.save(sm_dir)
+
+        # OTB pipeline
+        bmx = otbApplication.Registry.CreateApplication("BandMathX")
+        bmx.SetParameterString("exp", "{idxX>idxY?1:0}")
+        bmx.SetParameterStringList(
+            "il", [resolve_paths("$DATADIR/fake_spot6.jp2")]
+        )
+        bmx.Execute()
+
+        infer = otbApplication.Registry.CreateApplication(
+            "TensorflowModelServe"
+        )
+        infer.SetParameterString("model.dir", sm_dir)
+        infer.AddImageToParameterInputImageList(
+            "source1.il", bmx.GetParameterOutputImage("out")
+        )
+        infer.SetParameterString("out", resolve_paths("$TMPDIR/nd_out.tif"))
+        infer.ExecuteAndWriteOutput()
+
+
+if __name__ == '__main__':
+    unittest.main()
-- 
GitLab


From 5f31067af40030d9e9816987ddd9fb9df6a14b97 Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Thu, 6 Apr 2023 11:29:08 +0200
Subject: [PATCH 09/22] CI: refactor variables

---
 .gitlab-ci.yml | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2cc93144..3cf79c4b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,5 +1,3 @@
-image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
-
 variables:
   OTBTF_VERSION: 4.0.0
   OTB_BUILD: /src/otb/build/OTB/build  # Local OTB build directory
@@ -20,6 +18,9 @@ variables:
   DOCKERHUB_IMAGE_BASE: ${DOCKERHUB_BASE}:${OTBTF_VERSION}
   CPU_BASE_IMG: ubuntu:22.04
   GPU_BASE_IMG: nvidia/cuda:12.0.1-cudnn8-devel-ubuntu22.04
+
+image: $BRANCH_IMAGE
+
 workflow:
   rules:
     - if: $CI_MERGE_REQUEST_ID || $CI_COMMIT_REF_NAME =~ /master/ # Execute jobs in merge request context, or commit in master branch
-- 
GitLab


From 36a2d1a6b299d7a4658b5ce526f9f1ae96c79e4c Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Thu, 6 Apr 2023 11:29:29 +0200
Subject: [PATCH 10/22] TEST: inference no-data test

---
 test/nodata_test.py | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/test/nodata_test.py b/test/nodata_test.py
index 0f106af6..93e165b2 100644
--- a/test/nodata_test.py
+++ b/test/nodata_test.py
@@ -20,7 +20,7 @@ class NodataInferenceTest(unittest.TestCase):
 
         # OTB pipeline
         bmx = otbApplication.Registry.CreateApplication("BandMathX")
-        bmx.SetParameterString("exp", "{idxX>idxY?1:0}")
+        bmx.SetParameterString("exp", "{idxX>idxY?idxX*idxY:0}")
         bmx.SetParameterStringList(
             "il", [resolve_paths("$DATADIR/fake_spot6.jp2")]
         )
@@ -30,12 +30,19 @@ class NodataInferenceTest(unittest.TestCase):
             "TensorflowModelServe"
         )
         infer.SetParameterString("model.dir", sm_dir)
+        infer.SetParameterString("model.fullyconv", "on")
         infer.AddImageToParameterInputImageList(
             "source1.il", bmx.GetParameterOutputImage("out")
         )
+        infer.SetParameterFloat("source1.nodata", 0.0)
+        for param in [
+            "source1.rfieldx", "source1.rfieldy", "output.efieldx", "output.efieldy"
+        ]:
+            infer.SetParameterInt(param, 16)
         infer.SetParameterString("out", resolve_paths("$TMPDIR/nd_out.tif"))
         infer.ExecuteAndWriteOutput()
 
 
 if __name__ == '__main__':
-    unittest.main()
+    NodataInferenceTest().test_infersimple()
+    #unittest.main()
-- 
GitLab


From 448a826a0da01c06b46e63685031dc81414f8145 Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Fri, 21 Apr 2023 16:43:40 +0200
Subject: [PATCH 11/22] ADD: output background value

---
 app/otbTensorflowModelServe.cxx               | 12 ++++++++++-
 include/otbTensorflowMultisourceModelFilter.h | 21 +++++++++++--------
 .../otbTensorflowMultisourceModelFilter.hxx   |  2 +-
 3 files changed, 24 insertions(+), 11 deletions(-)

diff --git a/app/otbTensorflowModelServe.cxx b/app/otbTensorflowModelServe.cxx
index 6189e8fa..59969e36 100644
--- a/app/otbTensorflowModelServe.cxx
+++ b/app/otbTensorflowModelServe.cxx
@@ -192,7 +192,12 @@ public:
     SetDefaultParameterFloat                 ("output.spcscale", 1.0);
     SetParameterDescription                  ("output.spcscale", "The output image size/scale and spacing*scale where size and spacing corresponds to the first input");
     AddParameter(ParameterType_StringList,    "output.names",    "Names of the output tensors");
-    MandatoryOff                            ("output.names");
+    MandatoryOff                             ("output.names");
+
+    // Output background value
+    AddParameter(ParameterType_Float,         "output.bv", "Output background value");
+    SetDefaultParameterFloat                 ("output.bv", 0.0);
+    SetParameterDescription                  ("output.bv", "The value used when one input has only no-data values in its receptive field");
 
     // Output Field of Expression
     AddParameter(ParameterType_Int,           "output.efieldx", "The output expression field (width)");
@@ -297,6 +302,11 @@ public:
       m_TFFilter->SetFullyConvolutional(true);
     }
 
+    // Output background value
+    const float outBV = GetParameterFloat("output.bv");
+    otbAppLogINFO("Setting background value to " << outBV);
+    m_TFFilter->SetOutputBackgroundValue(outBV);
+
     // Output field of expression
     FloatVectorImageType::SizeType foe;
     foe[0] = GetParameterInt("output.efieldx");
diff --git a/include/otbTensorflowMultisourceModelFilter.h b/include/otbTensorflowMultisourceModelFilter.h
index bdf9a02d..ce855b28 100644
--- a/include/otbTensorflowMultisourceModelFilter.h
+++ b/include/otbTensorflowMultisourceModelFilter.h
@@ -132,6 +132,8 @@ public:
   itkGetMacro(FullyConvolutional, bool);
   itkSetMacro(OutputSpacingScale, float);
   itkGetMacro(OutputSpacingScale, float);
+  itkSetMacro(OutputBackgroundValue, OutputInternalPixelType);
+  itkGetMacro(OutputBackgroundValue, OutputInternalPixelType);
 
 protected:
   TensorflowMultisourceModelFilter();
@@ -162,17 +164,18 @@ private:
   void
   operator=(const Self &); // purposely not implemented
 
-  SizeType m_OutputGridSize;      // Output grid size
-  bool     m_ForceOutputGridSize; // Force output grid size
-  bool     m_FullyConvolutional;  // Convolution mode
-  float    m_OutputSpacingScale;  // scaling of the output spacings
+  SizeType                m_OutputGridSize;         // Output grid size
+  bool                    m_ForceOutputGridSize;    // Force output grid size
+  bool                    m_FullyConvolutional;     // Convolution mode
+  float                   m_OutputSpacingScale;     // scaling of the output spacings
+  OutputInternalPixelType m_OutputBackgroundValue;  // Output background value
 
   // Internal
-  SpacingType m_OutputSpacing; // Output image spacing
-  PointType   m_OutputOrigin;  // Output image origin
-  SizeType    m_OutputSize;    // Output image size
-  PixelType   m_NullPixel;     // Pixel filled with zeros
-
+  SpacingType             m_OutputSpacing;          // Output image spacing
+  PointType               m_OutputOrigin;           // Output image origin
+  SizeType                m_OutputSize;             // Output image size
+  PixelType               m_NullPixel;              // Pixel filled with zeros
+  
 }; // end class
 
 
diff --git a/include/otbTensorflowMultisourceModelFilter.hxx b/include/otbTensorflowMultisourceModelFilter.hxx
index 59105dd0..0f32334c 100644
--- a/include/otbTensorflowMultisourceModelFilter.hxx
+++ b/include/otbTensorflowMultisourceModelFilter.hxx
@@ -302,7 +302,7 @@ TensorflowMultisourceModelFilter<TInputImage, TOutputImage>::GenerateOutputInfor
 
   // Set null pixel
   m_NullPixel.SetSize(outputPtr->GetNumberOfComponentsPerPixel());
-  m_NullPixel.Fill(0);
+  m_NullPixel.Fill(m_OutputBackgroundValue);
 
   //////////////////////////////////////////////////////////////////////////////////////////
   //                        Set the tiling layout hint in metadata
-- 
GitLab


From 3e5c70cb696e34e392f4306a66aa198b40a1e572 Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Tue, 16 May 2023 10:01:22 +0200
Subject: [PATCH 12/22] COMP: version 4.0.0 -> 4.1.0 in ci

---
 .gitlab-ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3cf79c4b..2e944067 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,5 +1,5 @@
 variables:
-  OTBTF_VERSION: 4.0.0
+  OTBTF_VERSION: 4.1.0
   OTB_BUILD: /src/otb/build/OTB/build  # Local OTB build directory
   OTBTF_SRC: /src/otbtf  # Local OTBTF source directory
   OTB_TEST_DIR: $OTB_BUILD/Testing/Temporary  # OTB testing directory
-- 
GitLab


From 444f302393262dba237e890d3454eec37efb627a Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Tue, 16 May 2023 10:01:41 +0200
Subject: [PATCH 13/22] TEST: add baseline for no-data test

---
 test/data/nd_out.tif | 3 +++
 1 file changed, 3 insertions(+)
 create mode 100644 test/data/nd_out.tif

diff --git a/test/data/nd_out.tif b/test/data/nd_out.tif
new file mode 100644
index 00000000..4997afd6
--- /dev/null
+++ b/test/data/nd_out.tif
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ec7c74cf9da7d187390078e554098350b33307b06ce6738b7b635fb068d78b84
+size 1411
-- 
GitLab


From 07c5d714013d4083b541bf2a62ac1bd58cedcb23 Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Tue, 16 May 2023 10:06:21 +0200
Subject: [PATCH 14/22] TEST: compare no-data inference with baseline

---
 test/nodata_test.py | 46 ++++++++++++++++++++++++++++++++++++---------
 1 file changed, 37 insertions(+), 9 deletions(-)

diff --git a/test/nodata_test.py b/test/nodata_test.py
index 93e165b2..c3892153 100644
--- a/test/nodata_test.py
+++ b/test/nodata_test.py
@@ -1,15 +1,30 @@
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
+import otbApplication
 import pytest
+import tensorflow as tf
 import unittest
+
 import otbtf
-import otbApplication
-import tensorflow as tf
-from test_utils import resolve_paths
+from test_utils import resolve_paths, compare
+
 
 class NodataInferenceTest(unittest.TestCase):
 
     def test_infersimple(self):
+        """
+        In this test, we create a synthetic image:
+            f(x, y) = x * y if x > y else 0
+
+        Then we use an input no-data value (`source1.nodata 0`) and a
+        background value for the output (`output.bv 1024`).
+
+        We use the l2_norm SavedModel, forcing otbtf to use a tiling scheme
+        of 4x4. If the test succeeds, the output pixels in 4x4 areas where
+        there is at least one no-data pixel (i.e. 0), should be filled with
+        the `bv` value (i.e. 1024).
+
+        """
         sm_dir = resolve_paths("$TMPDIR/l2_norm_savedmodel")
 
         # Create model
@@ -18,11 +33,11 @@ class NodataInferenceTest(unittest.TestCase):
         model = tf.keras.Model(inputs={"x": x}, outputs={"y": y})
         model.save(sm_dir)
 
-        # OTB pipeline
+        # Input image: f(x, y) = x * y if x > y else 0
         bmx = otbApplication.Registry.CreateApplication("BandMathX")
         bmx.SetParameterString("exp", "{idxX>idxY?idxX*idxY:0}")
         bmx.SetParameterStringList(
-            "il", [resolve_paths("$DATADIR/fake_spot6.jp2")]
+            "il", [resolve_paths("$DATADIR/xs_subset.tif")]
         )
         bmx.Execute()
 
@@ -36,13 +51,26 @@ class NodataInferenceTest(unittest.TestCase):
         )
         infer.SetParameterFloat("source1.nodata", 0.0)
         for param in [
-            "source1.rfieldx", "source1.rfieldy", "output.efieldx", "output.efieldy"
+            "source1.rfieldx",
+            "source1.rfieldy",
+            "output.efieldx",
+            "output.efieldy",
+            "optim.tilesizex",
+            "optim.tilesizey",
         ]:
-            infer.SetParameterInt(param, 16)
+            infer.SetParameterInt(param, 4)
+
+        infer.SetParameterFloat("output.bv", 1024)
         infer.SetParameterString("out", resolve_paths("$TMPDIR/nd_out.tif"))
         infer.ExecuteAndWriteOutput()
 
+        self.assertTrue(
+            compare(
+                raster1=resolve_paths("$TMPDIR/nd_out.tif"),
+                raster2=resolve_paths("$DATADIR/nd_out.tif"),
+            )
+        )
+
 
 if __name__ == '__main__':
-    NodataInferenceTest().test_infersimple()
-    #unittest.main()
+    unittest.main()
-- 
GitLab


From 88da9da5acb83a433321fdb657cc852c66279c5a Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Tue, 16 May 2023 10:02:03 +0200
Subject: [PATCH 15/22] DOC: add no-data options for inference in documentation

---
 doc/app_inference.md | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/doc/app_inference.md b/doc/app_inference.md
index d50a7c03..63557d99 100644
--- a/doc/app_inference.md
+++ b/doc/app_inference.md
@@ -23,14 +23,14 @@ known:
 ![Schema](images/schema.png)
 
 The **scale factor** describes the physical change of spacing of the outputs,
-typically introduced in the model by non unitary strides in pooling or
+typically introduced in the model by non-unitary strides in pooling or
 convolution operators.
 For each output, it is expressed relatively to one single input of the model
 called the *reference input source*.
 Additionally, the names of the *target nodes* must be known (e.g. optimizers
 for Tensorflow API v1).
 Also, the names of *user placeholders*, typically scalars inputs that are
-used to control some parameters of the model, must be know.
+used to control some parameters of the model, must be known.
 The **receptive field** corresponds to the input volume that "sees" the deep
 net.
 The **expression field** corresponds to the output volume that the deep net
@@ -58,15 +58,20 @@ computation of one single tile of pixels.
 So, this application takes in input one or multiple _input sources_ (the number
 of _input sources_ can be changed by setting the `OTB_TF_NSOURCES` to the
 desired number) and produce one output of the specified tensors.
-The user is responsible of giving the **receptive field** and **name** of
+The user is responsible for giving the **receptive field** and **name** of
 _input placeholders_, as well as the **expression field**, **scale factor** and
 **name** of _output tensors_.
 The first _input source_ (`source1.il`) corresponds to the _reference input
 source_.
 As explained, the **scale factor** provided for the
 _output tensors_ is related to this _reference input source_.
-The user can ask for multiple _output tensors_, that will be stack along the
+The user can ask for multiple _output tensors_, that will be stacked along the
 channel dimension of the output raster.
+Since OTBTF 4.1, a no-data value can be provided for each input source (e.g. 
+`source1.nodata`). When all elements of an input are equals to the no-data 
+value in the processed chunk of image, the local inference process is skipped, 
+and the output pixel is filled with the value provided by the `output.bv` 
+parameter.
 
 !!! Warning
 
-- 
GitLab


From 7f3e8863726a4677d106d4869de0e260d3c39a42 Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Tue, 16 May 2023 10:02:09 +0200
Subject: [PATCH 16/22] COMP: version 4.0.0 -> 4.1.0 in setup.py

---
 setup.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/setup.py b/setup.py
index 958be96a..1feeff9c 100644
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
 
 setuptools.setup(
     name="otbtf",
-    version="4.0.0",
+    version="4.1.0",
     author="Remi Cresson",
     author_email="remi.cresson@inrae.fr",
     description="OTBTF: Orfeo ToolBox meets TensorFlow",
-- 
GitLab


From 6f87b4ae1b34111404019851d78ae086a6f5ccbf Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Tue, 16 May 2023 11:09:29 +0200
Subject: [PATCH 17/22] CI: enable nodata test

---
 .gitlab-ci.yml | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2e944067..b1dff867 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -218,6 +218,11 @@ rio:
     - sudo pip install rasterio
     - python -m pytest --junitxml=$ARTIFACT_TEST_DIR/report_rio.xml $OTBTF_SRC/test/rio_test.py
 
+nodata:
+  extends: .applications_test_base
+  script:
+    - python -m pytest --junitxml=$ARTIFACT_TEST_DIR/report_nodata.xml $OTBTF_SRC/test/nodata_test.py
+
 deploy_cpu-dev-testing:
   stage: Update dev image
   extends: .docker_build_base
-- 
GitLab


From 52f0faed3013be7bb7336657f4af967c6be17941 Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Wed, 17 May 2023 14:13:39 +0200
Subject: [PATCH 18/22] CI: update CUDA version for GPU images, add TensorRT
 image build

---
 .gitlab-ci.yml | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b1dff867..5f86f2dc 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -17,7 +17,7 @@ variables:
   DOCKERHUB_BASE: mdl4eo/otbtf
   DOCKERHUB_IMAGE_BASE: ${DOCKERHUB_BASE}:${OTBTF_VERSION}
   CPU_BASE_IMG: ubuntu:22.04
-  GPU_BASE_IMG: nvidia/cuda:12.0.1-cudnn8-devel-ubuntu22.04
+  GPU_BASE_IMG: nvidia/cuda:12.1.1-cudnn8-devel-ubuntu22.04
 
 image: $BRANCH_IMAGE
 
@@ -271,6 +271,7 @@ deploy_gpu:
     IMAGE_GPUDEV: $CI_REGISTRY_PUBIMG-gpu-dev
     IMAGE_GPUOPT: $CI_REGISTRY_PUBIMG-gpu-opt
     IMAGE_GPUOPTDEV: $CI_REGISTRY_PUBIMG-gpu-opt-dev
+    IMAGE_GPUOPTDEVTRT: $CI_REGISTRY_PUBIMG-gpu-opt-dev-trt
     DOCKERHUB_GPU: $DOCKERHUB_IMAGE_BASE-gpu
     DOCKERHUB_GPUDEV: $DOCKERHUB_IMAGE_BASE-gpu-dev
   script:
@@ -280,6 +281,9 @@ deploy_gpu:
     # gpu-opt-dev
     - docker build --build-arg BZL_OPTIONS="--remote_cache=$BAZELCACHE" --tag $IMAGE_GPUOPTDEV --build-arg BASE_IMG=$GPU_BASE_IMG --build-arg KEEP_SRC_OTB=true .
     - docker push $IMAGE_GPUOPTDEV
+    # gpu-opt-dev-trt
+    - docker build --build-arg BZL_OPTIONS="--remote_cache=$BAZELCACHE" --tag $IMAGE_GPUOPTDEV --build-arg BASE_IMG="cuda:11.8.0-cudnn8-devel-ubuntu22.04" --build-arg KEEP_SRC_OTB=true --build-arg TENSORRT="8.5.1-1+cuda11.8" .
+    - docker push $IMAGE_GPUOPTDEVTRT
     # gpu-basic
     - docker build --build-arg BZL_OPTIONS="--remote_cache=$BAZELCACHE" --tag $IMAGE_GPU --build-arg BASE_IMG=$GPU_BASE_IMG --build-arg BZL_CONFIGS="" .
     - docker push $IMAGE_GPU
-- 
GitLab


From 6ce1125e03234d9de20ba5b8926684d407cb7750 Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Wed, 17 May 2023 14:16:18 +0200
Subject: [PATCH 19/22] COMP: add TensorRT version ARG in Dockerfile

---
 Dockerfile | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Dockerfile b/Dockerfile
index a612da25..711dc4ba 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -44,6 +44,7 @@ RUN git config --global advice.detachedHead false
 ### TF
 
 ARG TF=v2.12.0
+ARG TENSORRT
 
 # Install bazelisk (will read .bazelversion and download the right bazel binary - latest by default)
 RUN wget -qO /opt/otbtf/bin/bazelisk https://github.com/bazelbuild/bazelisk/releases/latest/download/bazelisk-linux-amd64 \
@@ -202,4 +203,5 @@ ENV PATH="/home/otbuser/.local/bin:$PATH"
 RUN python -c "import tensorflow"
 RUN python -c "import otbtf, tricks"
 RUN python -c "import otbApplication as otb; otb.Registry.CreateApplication('ImageClassifierFromDeepFeatures')"
-RUN python -c "from osgeo import gdal"
\ No newline at end of file
+RUN python -c "from osgeo import gdal"
+
-- 
GitLab


From da1a8a0e100c9c98765178825dfb0d8a45ad5a23 Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Wed, 17 May 2023 14:16:29 +0200
Subject: [PATCH 20/22] ADD: update release notes

---
 RELEASE_NOTES.txt | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt
index fd30467c..785dc31d 100644
--- a/RELEASE_NOTES.txt
+++ b/RELEASE_NOTES.txt
@@ -1,3 +1,10 @@
+Version 4.1.0 (17 may 2023)
+----------------------------------------------------------------
+* Add no-data values support for inference in TensorflowModelServe application
+* Update base docker image for NVIDIA GPU builds (CUDA 12.1.1)
+* Fix CuDNN version detection in `build-env-tf.sh`
+* Add TensorRT support (tested: CUDA 11.8.0 and TensorRT 8.5)
+
 Version 4.0.0 (5 apr 2023)
 ----------------------------------------------------------------
 * Big improvement of the documentation:
-- 
GitLab


From 93e0d0dac8b1915633d6ab5637b00a8680382721 Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Wed, 17 May 2023 14:16:49 +0200
Subject: [PATCH 21/22] ADD: TensorRT support in build-env-tf.sh

---
 tools/docker/build-env-tf.sh | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/tools/docker/build-env-tf.sh b/tools/docker/build-env-tf.sh
index ff5c5692..4eb7c7ff 100644
--- a/tools/docker/build-env-tf.sh
+++ b/tools/docker/build-env-tf.sh
@@ -34,6 +34,16 @@ export TF_NEED_ROCM=0
 export TF_NEED_CUDA=0
 export CUDA_TOOLKIT_PATH=$(find /usr/local -maxdepth 1 -type d -name 'cuda-*')
 if  [ ! -z $CUDA_TOOLKIT_PATH ] ; then
+    if [ ! -z $TENSORRT ]; then
+        echo "Building tensorflow with TensorRT support"
+        apt install \
+            libnvinfer8=$TENSORRT \
+            libnvinfer-dev=$TENSORRT \
+            libnvinfer-plugin8=$TENSORRT \
+            libnvinfer-plugin-dev=$TENSORRT
+        export TF_TENSORRT_VERSION=$(cat $(find /usr/ -type f -name NvInferVersion.h) | grep '#define NV_TENSORRT_MAJOR' | cut -f3 -d' ')
+        export TF_NEED_TENSORRT=1
+    fi
     export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$CUDA_TOOLKIT_PATH/lib64:$CUDA_TOOLKIT_PATH/lib64/stubs"
     export TF_CUDA_VERSION=$(echo $CUDA_TOOLKIT_PATH | sed -r 's/.*\/cuda-(.*)/\1/')
     export TF_CUDA_COMPUTE_CAPABILITIES="5.2,6.1,7.0,7.5,8.6"
@@ -41,6 +51,6 @@ if  [ ! -z $CUDA_TOOLKIT_PATH ] ; then
     export TF_CUDA_CLANG=0
     export TF_NEED_TENSORRT=0
     export CUDNN_INSTALL_PATH="/usr/"
-    export TF_CUDNN_VERSION=$(sed -n 's/^#define CUDNN_MAJOR\s*\(.*\).*/\1/p' $CUDNN_INSTALL_PATH/include/cudnn.h)
+    export TF_CUDNN_VERSION=$(sed -n 's/^#define CUDNN_MAJOR\s*\(.*\).*/\1/p' $CUDNN_INSTALL_PATH/include/cudnn_version.h)
     export TF_NCCL_VERSION=2
 fi
-- 
GitLab


From fefcc3bc9c3c45d839a05e137e3fd51e1b64a5f4 Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Tue, 23 May 2023 21:38:24 +0200
Subject: [PATCH 22/22] DOC: update release notes

---
 RELEASE_NOTES.txt | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt
index 785dc31d..24d3f247 100644
--- a/RELEASE_NOTES.txt
+++ b/RELEASE_NOTES.txt
@@ -1,9 +1,8 @@
-Version 4.1.0 (17 may 2023)
+Version 4.1.0 (23 may 2023)
 ----------------------------------------------------------------
 * Add no-data values support for inference in TensorflowModelServe application
 * Update base docker image for NVIDIA GPU builds (CUDA 12.1.1)
 * Fix CuDNN version detection in `build-env-tf.sh`
-* Add TensorRT support (tested: CUDA 11.8.0 and TensorRT 8.5)
 
 Version 4.0.0 (5 apr 2023)
 ----------------------------------------------------------------
-- 
GitLab