From 4a0b74628ffa5a897413173c038e06480478627f Mon Sep 17 00:00:00 2001 From: Narcon Nicolas <nicolas.narcon@inrae.fr> Date: Wed, 23 Feb 2022 11:19:51 +0100 Subject: [PATCH 1/6] FIX: use GetParameterOutputImagePixelType instead of GetImageBasePixelType --- pyotb/core.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/pyotb/core.py b/pyotb/core.py index 8465840..ca38e60 100644 --- a/pyotb/core.py +++ b/pyotb/core.py @@ -897,19 +897,17 @@ def get_pixel_type(inp): # Executing the app, without printing its log info = App("ReadImageInfo", inp, otb_stdout=False) datatype = info.GetParameterInt("datatype") # which is such as short, float... - dataype_to_pixeltype = { - 'unsigned_char': 'uint8', 'short': 'int16', 'unsigned_short': 'uint16', - 'int': 'int32', 'unsigned_int': 'uint32', 'long': 'int32', 'ulong': 'uint32', - 'float': 'float','double': 'double' - } + dataype_to_pixeltype = {'unsigned_char': 'uint8', 'short': 'int16', 'unsigned_short': 'uint16', + 'int': 'int32', 'unsigned_int': 'uint32', 'long': 'int32', 'ulong': 'uint32', + 'float': 'float', 'double': 'double'} pixel_type = dataype_to_pixeltype[datatype] pixel_type = getattr(otb, f'ImagePixelType_{pixel_type}') - elif isinstance(inp, (Input, Output, Operation)): - pixel_type = inp.GetImageBasePixelType(inp.output_parameter_key) + elif isinstance(inp, (Input, Output, Operation, Slicer)): + pixel_type = inp.GetParameterOutputImagePixelType(inp.output_parameter_key) elif isinstance(inp, App): - if len(inp.output_parameters_keys) > 1: - pixel_type = inp.GetImageBasePixelType(inp.output_parameters_keys[0]) + if len(inp.output_parameters_keys) == 1: + pixel_type = inp.GetParameterOutputImagePixelType(inp.output_parameters_keys[0]) else: - pixel_type = {key: inp.GetImageBasePixelType(key) for key in inp.output_parameters_keys} + pixel_type = {key: inp.GetParameterOutputImagePixelType(key) for key in inp.output_parameters_keys} return pixel_type -- GitLab From f3988f40e2faca0b281550109af316a3cbccbdbe Mon Sep 17 00:00:00 2001 From: Narcon Nicolas <nicolas.narcon@inrae.fr> Date: Wed, 23 Feb 2022 12:09:09 +0100 Subject: [PATCH 2/6] ENH: add the ability to propagate pixel types for App. Add propagation as default behavior for Slicer and Input --- pyotb/core.py | 45 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/pyotb/core.py b/pyotb/core.py index ca38e60..d1594ed 100644 --- a/pyotb/core.py +++ b/pyotb/core.py @@ -303,7 +303,7 @@ class Slicer(otbObject): :param channels: """ # Initialize the app that will be used for writing the slicer - app = App('ExtractROI', {"in": input, 'mode': 'extent'}) + app = App('ExtractROI', {"in": input, 'mode': 'extent'}, propagate_pixel_type=True) self.output_parameter_key = 'out' self.name = 'Slicer' @@ -363,7 +363,7 @@ class Input(otbObject): Class for transforming a filepath to pyOTB object """ def __init__(self, filepath): - self.app = App('ExtractROI', filepath).app + self.app = App('ExtractROI', filepath, propagate_pixel_type=True).app self.output_parameter_key = 'out' self.filepath = filepath self.name = f'Input from {filepath}' @@ -412,7 +412,7 @@ class App(otbObject): # This will only store if app has been excuted, then find_output() is called when accessing the property self._finished = val - def __init__(self, appname, *args, execute=True, image_dic=None, otb_stdout=True, **kwargs): + def __init__(self, appname, *args, execute=True, image_dic=None, otb_stdout=True, propagate_pixel_type=False, **kwargs): """ Enables to run an otb app as a oneliner. Handles in-memory connection between apps :param appname: name of the app, e.g. 'Smoothing' @@ -426,6 +426,9 @@ class App(otbObject): the result of app.ExportImage(). Use it when the app takes a numpy array as input. See this related issue for why it is necessary to keep reference of object: https://gitlab.orfeo-toolbox.org/orfeotoolbox/otb/-/issues/1824 + :param otb_stdout: whether to print logs of the app + :param propagate_pixel_type: Propagate the pixel type from inputs to output. If several inputs, the type of an + arbitrary input is considered. If several outputs, all will have the same type. :param kwargs: keyword arguments e.g. il=['input1.tif', App_object2, App_object3.out], out='output.tif' """ self.appname = appname @@ -446,6 +449,8 @@ class App(otbObject): # Run app, write output if needed, update `finished` property if execute: self.execute() + if propagate_pixel_type: + self.__propagate_pixel_type() # 'Saving' outputs as attributes, i.e. so that they can be accessed like that: App.out # Also, thanks to __getitem__ method, the outputs can be accessed as App["out"]. This is useful when the key # contains reserved characters such as a point eg "io.out" @@ -587,6 +592,21 @@ class App(otbObject): else: self.app.SetParameterValue(param, obj) + def __propagate_pixel_type(self): + """Propagate the pixel type from inputs to output. If several inputs, the type of an arbitrary input + is considered. If several outputs, all outputs will have the same type.""" + pixel_type = None + for param in self.parameters.values(): + try: + pixel_type = get_pixel_type(param) + except TypeError: + pass + if not pixel_type: + logger.warning(f"{self.name}: Could not propagate pixel type from inputs to output, " + + f"no valid input found") + else: + for out_key in self.output_parameters_keys: + self.app.SetParameterOutputImagePixelType(out_key, pixel_type) def __with_output(self): """Check if App has any output parameter key""" @@ -890,13 +910,17 @@ def get_pixel_type(inp): """ Get the encoding of input image pixels :param inp: a filepath, or any pyotb object - :return pixel_type: either a dict of pixel types (in case of an App where there are several outputs) - format is like `otbApplication.ImagePixelType_uint8' + :return pixel_type: format is like `otbApplication.ImagePixelType_uint8'. For an App with several outputs, only the + pixel type of the first output is returned + """ if isinstance(inp, str): # Executing the app, without printing its log - info = App("ReadImageInfo", inp, otb_stdout=False) - datatype = info.GetParameterInt("datatype") # which is such as short, float... + try: + info = App("ReadImageInfo", inp, otb_stdout=False) + except Exception: # 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') + datatype = info.GetParameterString("datatype") # which is such as short, float... dataype_to_pixeltype = {'unsigned_char': 'uint8', 'short': 'int16', 'unsigned_short': 'uint16', 'int': 'int32', 'unsigned_int': 'uint32', 'long': 'int32', 'ulong': 'uint32', 'float': 'float', 'double': 'double'} @@ -905,9 +929,8 @@ def get_pixel_type(inp): elif isinstance(inp, (Input, Output, Operation, Slicer)): pixel_type = inp.GetParameterOutputImagePixelType(inp.output_parameter_key) elif isinstance(inp, App): - if len(inp.output_parameters_keys) == 1: - pixel_type = inp.GetParameterOutputImagePixelType(inp.output_parameters_keys[0]) - else: - pixel_type = {key: inp.GetParameterOutputImagePixelType(key) for key in inp.output_parameters_keys} + pixel_type = inp.GetParameterOutputImagePixelType(inp.output_parameters_keys[0]) + else: + raise TypeError(f'Could not get the pixel type. Not supported type: {inp}') return pixel_type -- GitLab From b0893edb68e2e85d991bd59c141bd39ba15ddcef Mon Sep 17 00:00:00 2001 From: Narcon Nicolas <nicolas.narcon@inrae.fr> Date: Wed, 23 Feb 2022 12:11:26 +0100 Subject: [PATCH 3/6] ENH: invert rows and columns in Slicer to follow the convention obj[x, y] --- pyotb/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyotb/core.py b/pyotb/core.py index d1594ed..d36f02a 100644 --- a/pyotb/core.py +++ b/pyotb/core.py @@ -114,7 +114,7 @@ class otbObject(ABC): elif isinstance(key, tuple) and len(key) == 2: # adding a 3rd dimension key = key + (slice(None, None, None),) - (rows, cols, channels) = key + (cols, rows, channels) = key return Slicer(self, rows, cols, channels) def __getattr__(self, name): -- GitLab From 877f169555f6e603c3d7417131bcc591b154ff00 Mon Sep 17 00:00:00 2001 From: Narcon Nicolas <nicolas.narcon@inrae.fr> Date: Wed, 23 Feb 2022 14:29:10 +0100 Subject: [PATCH 4/6] ENH: enable propagate pixel type for numpy export FIX: return a copy of the array when running np.asarray to avoid some segfault --- pyotb/core.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/pyotb/core.py b/pyotb/core.py index d36f02a..ebe60e7 100644 --- a/pyotb/core.py +++ b/pyotb/core.py @@ -231,16 +231,32 @@ class otbObject(ABC): def __hash__(self): return id(self) - def __array__(self): + def to_numpy(self, propagate_pixel_type=False): """ - This is called when running np.asarray(pyotb_object) + Export a pyotb object to numpy array :return: a numpy array """ if hasattr(self, 'output_parameter_key'): # this is for Input, Output, Operation, Slicer output_parameter_key = self.output_parameter_key else: # this is for App output_parameter_key = self.output_parameters_keys[0] - return self.app.ExportImage(output_parameter_key)['array'] + + array = self.app.ExportImage(output_parameter_key)['array'] + if propagate_pixel_type: + otb_pixeltype = get_pixel_type(self) + otb_pixeltype_to_np_pixeltype = {0: np.uint8, 1: np.int16, 2: np.uint16, 3: np.int32, 4: np.uint32, + 5: np.float32, 6: np.float64} + np_pixeltype = otb_pixeltype_to_np_pixeltype[otb_pixeltype] + array = array.astype(np_pixeltype) + return array.copy() + + def __array__(self): + """ + This is called when running np.asarray(pyotb_object) + :return: a numpy array + """ + + return self.to_numpy() def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): """ -- GitLab From 645f327c176ae188d2eafa6fbde01126b9dfa2f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20Nar=C3=A7on?= <nicolas.narcon@gmail.com> Date: Thu, 24 Feb 2022 08:34:52 +0000 Subject: [PATCH 5/6] REFAC: comment & move code --- pyotb/core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyotb/core.py b/pyotb/core.py index ebe60e7..cb74243 100644 --- a/pyotb/core.py +++ b/pyotb/core.py @@ -240,15 +240,15 @@ class otbObject(ABC): output_parameter_key = self.output_parameter_key else: # this is for App output_parameter_key = self.output_parameters_keys[0] - - array = self.app.ExportImage(output_parameter_key)['array'] + # we make a copy to avoid some segfault if the reference to app is lost + array = self.app.ExportImage(output_parameter_key)['array'].copy() if propagate_pixel_type: otb_pixeltype = get_pixel_type(self) otb_pixeltype_to_np_pixeltype = {0: np.uint8, 1: np.int16, 2: np.uint16, 3: np.int32, 4: np.uint32, 5: np.float32, 6: np.float64} np_pixeltype = otb_pixeltype_to_np_pixeltype[otb_pixeltype] array = array.astype(np_pixeltype) - return array.copy() + return array def __array__(self): """ -- GitLab From 68a19b5a6198552f20d29ba4869f49107deadceb Mon Sep 17 00:00:00 2001 From: Narcon Nicolas <nicolas.narcon@inrae.fr> Date: Thu, 24 Feb 2022 09:44:39 +0100 Subject: [PATCH 6/6] bump version --- README.md | 2 +- pyotb/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 60aeaba..8f94e4e 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Requirements: pip install pyotb ``` -For Python>=3.6, latest version available is pyotb 1.2.3. For Python 3.5, latest version available is pyotb 1.2.2 +For Python>=3.6, latest version available is pyotb 1.3. For Python 3.5, latest version available is pyotb 1.2.2 ## Quickstart: running an OTB app as a oneliner pyotb has been written so that it is more convenient to run an application in Python. diff --git a/pyotb/__init__.py b/pyotb/__init__.py index 91fbcf5..8a29946 100644 --- a/pyotb/__init__.py +++ b/pyotb/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "1.2.3" +__version__ = "1.3" from .apps import * from .core import App, Output, Input, get_nbchannels, get_pixel_type diff --git a/setup.py b/setup.py index b0762e5..5c78ce9 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ with open("README.md", "r", encoding="utf-8") as fh: setuptools.setup( name="pyotb", - version="1.2.3", + version="1.3", author="Nicolas Narçon", author_email="nicolas.narcon@gmail.com", description="Library to enable easy use of the Orfeo Tool Box (OTB) in Python", -- GitLab