From 4c3c72961a4f3cc53acba9fde70779c1b6b8ab00 Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Thu, 2 Feb 2023 21:14:51 +0100
Subject: [PATCH 01/11] FIX: summarize() for RasterInterface

---
 pyotb/core.py | 51 ++++++++++++++++++++++++++-------------------------
 1 file changed, 26 insertions(+), 25 deletions(-)

diff --git a/pyotb/core.py b/pyotb/core.py
index 3705961..bdf4eb5 100644
--- a/pyotb/core.py
+++ b/pyotb/core.py
@@ -338,6 +338,22 @@ class ImageObject(ABC):
             return pyotb_app
         return NotImplemented
 
+    def summarize(self) -> dict[str, str | dict[str, Any]]:
+        """Serialize an object and its pipeline into a dictionary.
+
+        Returns:
+            nested dictionary summarizing the pipeline
+
+        """
+        parameters = self.parameters.copy()
+        for key, param in parameters.items():
+            # In the following, we replace each parameter which is an ImageObject, with its summary.
+            if isinstance(param, ImageObject):  # single parameter
+                parameters[key] = param.summarize()
+            elif isinstance(param, list):  # parameter list
+                parameters[key] = [p.summarize() if isinstance(p, ImageObject) else p for p in param]
+        return {"name": self.app.GetName(), "parameters": parameters}
+
 
 class App(ImageObject):
     """Base class that gathers common operations for any OTB application."""
@@ -465,7 +481,7 @@ class App(ImageObject):
             except (RuntimeError, TypeError, ValueError, KeyError) as e:
                 raise RuntimeError(
                     f"{self.name}: something went wrong before execution "
-                    f"(while setting parameter '{key}' to '{obj}')"
+                    f"(while setting parameter '{key}' to '{obj}': {e})"
                 ) from e
         # Update _parameters using values from OtbApplication object
         otb_params = self.app.GetParameters().items()
@@ -536,7 +552,7 @@ class App(ImageObject):
         try:
             self.app.Execute()
         except (RuntimeError, FileNotFoundError) as e:
-            raise RuntimeError(f"{self.name}: error during during app execution") from e
+            raise RuntimeError(f"{self.name}: error during during app execution ({e}") from e
         self.frozen = False
         self._time_end = perf_counter()
         logger.debug("%s: execution ended", self.name)
@@ -628,22 +644,6 @@ class App(ImageObject):
             logger.error("%s: execution seems to have failed, %s does not exist", self.name, filename)
         return tuple(files)
 
-    def summarize(self) -> dict[str, str | dict[str, Any]]:
-        """Serialize an object and its pipeline into a dictionary.
-
-        Returns:
-            nested dictionary summarizing the pipeline
-
-        """
-        parameters = self.parameters.copy()
-        for key, param in parameters.items():
-            # In the following, we replace each parameter which is an ImageObject, with its summary.
-            if isinstance(param, ImageObject):  # single parameter
-                parameters[key] = param.summarize()
-            elif isinstance(param, list):  # parameter list
-                parameters[key] = [p.summarize() if isinstance(p, ImageObject) else p for p in param]
-        return {"name": self.app.GetName(), "parameters": parameters}
-
     # Private functions
     def __parse_args(self, args: list[str | ImageObject | dict | list]) -> dict[str, Any]:
         """Gather all input arguments in kwargs dict.
@@ -1079,16 +1079,16 @@ class LogicalOperation(Operation):
 class Input(App):
     """Class for transforming a filepath to pyOTB object."""
 
-    def __init__(self, path: str):
+    def __init__(self, filepath: str):
         """Default constructor.
 
         Args:
-            path: Anything supported by GDAL (local file on the filesystem, remote resource e.g. /vsicurl/.., etc.)
+            filepath: Anything supported by GDAL (local file on the filesystem, remote resource e.g. /vsicurl/.., etc.)
 
         """
-        super().__init__("ExtractROI", {"in": path}, frozen=True)
-        self.name = f"Input from {path}"
-        self.filepath = Path(path)
+        super().__init__("ExtractROI", {"in": filepath}, frozen=True)
+        self.name = f"Input from {filepath}"
+        self.filepath = Path(filepath)
         self.propagate_dtype()
         self.execute()
 
@@ -1112,6 +1112,7 @@ class Output(ImageObject):
         """
         self.name = f"Output {param_key} from {pyotb_app.name}"
         self.parent_pyotb_app = pyotb_app  # keep trace of parent app
+        self.parameters = pyotb_app.parameters
         self.app = pyotb_app.app
         self.exports_dic = pyotb_app.exports_dic
         self.param_key = param_key
@@ -1169,7 +1170,7 @@ def get_nbchannels(inp: str | ImageObject) -> int:
             info = App("ReadImageInfo", inp, quiet=True)
             nb_channels = info.app.GetParameterInt("numberbands")
         except Exception as e:  # this happens when we pass a str that is not a filepath
-            raise TypeError(f"Could not get the number of channels of '{inp}'. Not a filepath or wrong filepath") from e
+            raise TypeError(f"Could not get the number of channels of '{inp}' ({e})") from e
     return nb_channels
 
 
@@ -1188,7 +1189,7 @@ def get_pixel_type(inp: str | ImageObject) -> str:
         try:
             info = App("ReadImageInfo", inp, quiet=True)
         except Exception as info_err:  # this happens when we pass a str that is not a filepath
-            raise TypeError(f"Could not get the pixel type of `{inp}`. Not a filepath or wrong filepath") from info_err
+            raise TypeError(f"Could not get the pixel type of `{inp}` ({info_err})") from info_err
         datatype = info.app.GetParameterString("datatype")  # which is such as short, float...
         if not datatype:
             raise TypeError(f"Unable to read pixel type of image {inp}")
-- 
GitLab


From d6de016596440d1facb081b2fa9b8059199456cd Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Thu, 2 Feb 2023 22:05:16 +0100
Subject: [PATCH 02/11] ADD: parameters property to RasterInterface

---
 pyotb/core.py | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/pyotb/core.py b/pyotb/core.py
index bdf4eb5..37a56bc 100644
--- a/pyotb/core.py
+++ b/pyotb/core.py
@@ -354,6 +354,10 @@ class ImageObject(ABC):
                 parameters[key] = [p.summarize() if isinstance(p, ImageObject) else p for p in param]
         return {"name": self.app.GetName(), "parameters": parameters}
 
+    @abstractmethod
+    def parameters(self):
+        """Parameters"""
+
 
 class App(ImageObject):
     """Base class that gathers common operations for any OTB application."""
@@ -385,7 +389,7 @@ class App(ImageObject):
         self.image_dic = image_dic
         self._time_start, self._time_end = 0, 0
         self.exports_dic = {}
-        self.parameters = {}
+        self._parameters = {}
         # Initialize app, set parameters and execute if not frozen
         create = otb.Registry.CreateApplicationWithoutLogger if quiet else otb.Registry.CreateApplication
         self.app = create(name)
@@ -446,6 +450,10 @@ class App(ImageObject):
                 data_dict[str(key)] = value
         return data_dict
 
+    @property
+    def parameters(self):
+        return self._parameters
+
     def set_parameters(self, *args, **kwargs):
         """Set some parameters of the app.
 
@@ -487,7 +495,7 @@ class App(ImageObject):
         otb_params = self.app.GetParameters().items()
         otb_params = {k: str(v) if isinstance(v, otb.ApplicationProxy) else v for k, v in otb_params}
         # Update param dict and save values as object attributes
-        self.parameters.update({**parameters, **otb_params})
+        self._parameters.update({**parameters, **otb_params})
         self.save_objects()
 
     def propagate_dtype(self, target_key: str = None, dtype: int = None):
@@ -1112,7 +1120,6 @@ class Output(ImageObject):
         """
         self.name = f"Output {param_key} from {pyotb_app.name}"
         self.parent_pyotb_app = pyotb_app  # keep trace of parent app
-        self.parameters = pyotb_app.parameters
         self.app = pyotb_app.app
         self.exports_dic = pyotb_app.exports_dic
         self.param_key = param_key
@@ -1129,6 +1136,10 @@ class Output(ImageObject):
         """Force the right key to be used when accessing the ImageObject."""
         return self.param_key
 
+    @property
+    def parameters(self):
+        return self.parent_pyotb_app.parameters
+
     def exists(self) -> bool:
         """Check file exist."""
         if self.filepath is None:
-- 
GitLab


From ff71b801656f56db1f7976f34a98f066c7c0f71d Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Thu, 2 Feb 2023 22:15:09 +0100
Subject: [PATCH 03/11] ADD: new test to prevent bug

---
 tests/test_core.py | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/tests/test_core.py b/tests/test_core.py
index 58ddeac..7bed905 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -166,3 +166,14 @@ def test_ndvi_comparison():
 
     thresholded_bandmath = pyotb.where(ndvi_bandmath >= 0.3, 1, 0)
     assert thresholded_bandmath.exp == "((((im1b4 - im1b1) / (im1b4 + im1b1)) >= 0.3) ? 1 : 0)"
+
+
+def test_output_in_arg():
+    o = pyotb.Output(INPUT, "out")
+    t = pyotb.ReadImageInfo(o)
+    assert t
+
+
+def test_output_summary():
+    o = pyotb.Output(INPUT, "out")
+    assert o.summarize()
-- 
GitLab


From c329fc1bed3a92f25132ada7202e7d07d3d45e81 Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Thu, 2 Feb 2023 22:15:23 +0100
Subject: [PATCH 04/11] STYLE: linter

---
 pyotb/core.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/pyotb/core.py b/pyotb/core.py
index 37a56bc..b6e6228 100644
--- a/pyotb/core.py
+++ b/pyotb/core.py
@@ -356,7 +356,7 @@ class ImageObject(ABC):
 
     @abstractmethod
     def parameters(self):
-        """Parameters"""
+        """Parameters."""
 
 
 class App(ImageObject):
@@ -452,6 +452,7 @@ class App(ImageObject):
 
     @property
     def parameters(self):
+        """Parameters."""
         return self._parameters
 
     def set_parameters(self, *args, **kwargs):
@@ -1138,6 +1139,7 @@ class Output(ImageObject):
 
     @property
     def parameters(self):
+        """Parameters."""
         return self.parent_pyotb_app.parameters
 
     def exists(self) -> bool:
-- 
GitLab


From 83c1d7a0db7b6aafbafe6421e5c389bf058f08bb Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Thu, 2 Feb 2023 22:16:53 +0100
Subject: [PATCH 05/11] REFAC: parameters abstract property in RasterInterface

---
 pyotb/core.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/pyotb/core.py b/pyotb/core.py
index b6e6228..3a969f1 100644
--- a/pyotb/core.py
+++ b/pyotb/core.py
@@ -355,6 +355,7 @@ class ImageObject(ABC):
         return {"name": self.app.GetName(), "parameters": parameters}
 
     @abstractmethod
+    @property
     def parameters(self):
         """Parameters."""
 
-- 
GitLab


From 3ab4fe2c0dc09e8b35fd7cb7e45394f4e4c71a63 Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Thu, 2 Feb 2023 22:26:09 +0100
Subject: [PATCH 06/11] REFAC: parameters abstract property in RasterInterface

---
 pyotb/core.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyotb/core.py b/pyotb/core.py
index 3a969f1..e363caf 100644
--- a/pyotb/core.py
+++ b/pyotb/core.py
@@ -354,8 +354,8 @@ class ImageObject(ABC):
                 parameters[key] = [p.summarize() if isinstance(p, ImageObject) else p for p in param]
         return {"name": self.app.GetName(), "parameters": parameters}
 
-    @abstractmethod
     @property
+    @abstractmethod
     def parameters(self):
         """Parameters."""
 
-- 
GitLab


From 5083e62f2a89f00b6e8b015916db1b3b6cb6e64f Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Fri, 3 Feb 2023 13:11:10 +0100
Subject: [PATCH 07/11] ADD: name in RasterInterface

---
 pyotb/core.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/pyotb/core.py b/pyotb/core.py
index e363caf..f035979 100644
--- a/pyotb/core.py
+++ b/pyotb/core.py
@@ -17,6 +17,7 @@ from .helpers import logger
 class ImageObject(ABC):
     """Abstraction of an image object."""
 
+    name: str
     app: otb.Application
     exports_dic: dict
 
-- 
GitLab


From 733e032c4b3a2ba31c1a9ee0c338c219fc82a5ae Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Fri, 3 Feb 2023 13:35:29 +0100
Subject: [PATCH 08/11] REFAC: add parameters as attribute rather than property

---
 pyotb/core.py | 24 ++++++------------------
 1 file changed, 6 insertions(+), 18 deletions(-)

diff --git a/pyotb/core.py b/pyotb/core.py
index f035979..01f1a91 100644
--- a/pyotb/core.py
+++ b/pyotb/core.py
@@ -19,6 +19,7 @@ class ImageObject(ABC):
 
     name: str
     app: otb.Application
+    parameters: dict
     exports_dic: dict
 
     @property
@@ -355,10 +356,6 @@ class ImageObject(ABC):
                 parameters[key] = [p.summarize() if isinstance(p, ImageObject) else p for p in param]
         return {"name": self.app.GetName(), "parameters": parameters}
 
-    @property
-    @abstractmethod
-    def parameters(self):
-        """Parameters."""
 
 
 class App(ImageObject):
@@ -391,7 +388,7 @@ class App(ImageObject):
         self.image_dic = image_dic
         self._time_start, self._time_end = 0, 0
         self.exports_dic = {}
-        self._parameters = {}
+        self.parameters = {}
         # Initialize app, set parameters and execute if not frozen
         create = otb.Registry.CreateApplicationWithoutLogger if quiet else otb.Registry.CreateApplication
         self.app = create(name)
@@ -422,7 +419,7 @@ class App(ImageObject):
 
     @property
     def key_input_image(self) -> str:
-        """Get the name of first input image parameter."""
+        """Name of the first input image parameter."""
         return self.get_first_key(param_types=[otb.ParameterType_InputImage, otb.ParameterType_InputImageList])
 
     @property
@@ -452,11 +449,6 @@ class App(ImageObject):
                 data_dict[str(key)] = value
         return data_dict
 
-    @property
-    def parameters(self):
-        """Parameters."""
-        return self._parameters
-
     def set_parameters(self, *args, **kwargs):
         """Set some parameters of the app.
 
@@ -498,7 +490,7 @@ class App(ImageObject):
         otb_params = self.app.GetParameters().items()
         otb_params = {k: str(v) if isinstance(v, otb.ApplicationProxy) else v for k, v in otb_params}
         # Update param dict and save values as object attributes
-        self._parameters.update({**parameters, **otb_params})
+        self.parameters.update({**parameters, **otb_params})
         self.save_objects()
 
     def propagate_dtype(self, target_key: str = None, dtype: int = None):
@@ -844,7 +836,7 @@ class Operation(App):
 
     """
 
-    def __init__(self, operator: str, *inputs, nb_bands: int = None, name: str = None):
+    def __init__(self, operator: str, *inputs, nb_bands: int = None):
         """Given some inputs and an operator, this function enables to transform this into an OTB application.
 
         Operations generally involve 2 inputs (+, -...). It can have only 1 input for `abs` operator.
@@ -1126,6 +1118,7 @@ class Output(ImageObject):
         self.app = pyotb_app.app
         self.exports_dic = pyotb_app.exports_dic
         self.param_key = param_key
+        self.parameters = self.parent_pyotb_app.parameters
         self.filepath = None
         if filepath:
             if "?" in filepath:
@@ -1139,11 +1132,6 @@ class Output(ImageObject):
         """Force the right key to be used when accessing the ImageObject."""
         return self.param_key
 
-    @property
-    def parameters(self):
-        """Parameters."""
-        return self.parent_pyotb_app.parameters
-
     def exists(self) -> bool:
         """Check file exist."""
         if self.filepath is None:
-- 
GitLab


From 683b75cfa40b48ed6ef5fd5af68f9008102a68ee Mon Sep 17 00:00:00 2001
From: Remi Cresson <remi.cresson@inrae.fr>
Date: Fri, 3 Feb 2023 13:46:33 +0100
Subject: [PATCH 09/11] FIX: sync with renamed attrs/cls

---
 pyotb/core.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pyotb/core.py b/pyotb/core.py
index 01f1a91..a174e9c 100644
--- a/pyotb/core.py
+++ b/pyotb/core.py
@@ -836,7 +836,7 @@ class Operation(App):
 
     """
 
-    def __init__(self, operator: str, *inputs, nb_bands: int = None):
+    def __init__(self, operator: str, *inputs, nb_bands: int = None, name: str = None):
         """Given some inputs and an operator, this function enables to transform this into an OTB application.
 
         Operations generally involve 2 inputs (+, -...). It can have only 1 input for `abs` operator.
@@ -874,7 +874,7 @@ class Operation(App):
         appname = "BandMath" if len(self.exp_bands) == 1 else "BandMathX"
         # Execute app
         super().__init__(appname, il=self.unique_inputs, exp=self.exp, quiet=True)
-        self.name = f'Operation exp="{self.exp}"'
+        self.name = name or f'Operation exp="{self.exp}"'
 
     def build_fake_expressions(self, operator: str, inputs: list[ImageObject | str | int | float],
                                nb_bands: int = None):
-- 
GitLab


From bb9ce48bf66c7476c4d6d78081803396c462d49a Mon Sep 17 00:00:00 2001
From: Vincent Delbar <vincent.delbar@latelescop.fr>
Date: Fri, 3 Feb 2023 17:32:55 +0000
Subject: [PATCH 10/11] BUG: App init error will raise RuntimeError

---
 pyotb/core.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pyotb/core.py b/pyotb/core.py
index a174e9c..319dc64 100644
--- a/pyotb/core.py
+++ b/pyotb/core.py
@@ -1172,7 +1172,7 @@ def get_nbchannels(inp: str | ImageObject) -> int:
         try:
             info = App("ReadImageInfo", inp, quiet=True)
             nb_channels = info.app.GetParameterInt("numberbands")
-        except Exception as e:  # this happens when we pass a str that is not a filepath
+        except RuntimeError as e:  # this happens when we pass a str that is not a filepath
             raise TypeError(f"Could not get the number of channels of '{inp}' ({e})") from e
     return nb_channels
 
@@ -1191,7 +1191,7 @@ def get_pixel_type(inp: str | ImageObject) -> str:
     if isinstance(inp, str):
         try:
             info = App("ReadImageInfo", inp, quiet=True)
-        except Exception as info_err:  # this happens when we pass a str that is not a filepath
+        except RuntimeError as info_err:  # this happens when we pass a str that is not a filepath
             raise TypeError(f"Could not get the pixel type of `{inp}` ({info_err})") from info_err
         datatype = info.app.GetParameterString("datatype")  # which is such as short, float...
         if not datatype:
-- 
GitLab


From 819c3bf681abd096872f16f7803c9c5a92a4a5cb Mon Sep 17 00:00:00 2001
From: Vincent Delbar <vincent.delbar@latelescop.fr>
Date: Fri, 3 Feb 2023 17:34:53 +0000
Subject: [PATCH 11/11] ENH: rename error e to info_err

---
 pyotb/core.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pyotb/core.py b/pyotb/core.py
index 319dc64..b9fa376 100644
--- a/pyotb/core.py
+++ b/pyotb/core.py
@@ -1172,8 +1172,8 @@ def get_nbchannels(inp: str | ImageObject) -> int:
         try:
             info = App("ReadImageInfo", inp, quiet=True)
             nb_channels = info.app.GetParameterInt("numberbands")
-        except RuntimeError as e:  # this happens when we pass a str that is not a filepath
-            raise TypeError(f"Could not get the number of channels of '{inp}' ({e})") from e
+        except RuntimeError as info_err:  # this happens when we pass a str that is not a filepath
+            raise TypeError(f"Could not get the number of channels of '{inp}' ({info_err})") from info_err
     return nb_channels
 
 
-- 
GitLab