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