From 38b8e87183ef216ae50ef26b7e742a390184445e Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@inrae.fr> Date: Thu, 29 Sep 2022 15:19:15 +0200 Subject: [PATCH 1/7] REFAC: typing (wip) --- pyotb/core.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pyotb/core.py b/pyotb/core.py index d309d85..d68cd1d 100644 --- a/pyotb/core.py +++ b/pyotb/core.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """This module is the core of pyotb.""" +from __future__ import annotations from pathlib import Path import numpy as np @@ -15,7 +16,7 @@ class otbObject: output_param = "" @property - def name(self): + def name(self) -> str: """Application name that will be printed in logs. Returns: @@ -25,7 +26,7 @@ class otbObject: return self._name or self.app.GetName() @name.setter - def name(self, val): + def name(self, val: str): """Set custom name. Args: @@ -35,7 +36,7 @@ class otbObject: self._name = val @property - def dtype(self): + def dtype(self) -> int: """Expose the pixel type of an output image using numpy convention. Returns: @@ -49,7 +50,7 @@ class otbObject: return None @property - def shape(self): + def shape(self) -> tuple[int, int, int]: """Enables to retrieve the shape of a pyotb object using numpy convention. Returns: @@ -60,7 +61,7 @@ class otbObject: bands = self.app.GetImageNbBands(self.output_param) return (height, width, bands) - def write(self, *args, filename_extension="", pixel_type=None, **kwargs): + def write(self, *args: dict | str, filename_extension: str="", pixel_type: dict | str=None, **kwargs): """Trigger execution, set output pixel type and write the output. Args: @@ -136,7 +137,7 @@ class otbObject: logger.debug('%s: failed to simply write output, executing once again then writing', self.name) self.app.ExecuteAndWriteOutput() - def to_numpy(self, preserve_dtype=True, copy=False): + def to_numpy(self, preserve_dtype: bool=True, copy: bool=False): """Export a pyotb object to numpy array. Args: @@ -156,7 +157,7 @@ class otbObject: return array.copy() return array - def to_rasterio(self): + def to_rasterio(self) -> tuple(np.ndarray, dict): """Export image as a numpy array and its metadata compatible with rasterio. Returns: -- GitLab From f7a262b13e09f912a4acdfdaece09d242097a564 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@inrae.fr> Date: Wed, 11 Jan 2023 22:56:20 +0100 Subject: [PATCH 2/7] DOC: typing hint --- pyotb/core.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pyotb/core.py b/pyotb/core.py index bc6860f..7ea38dc 100644 --- a/pyotb/core.py +++ b/pyotb/core.py @@ -297,7 +297,7 @@ class OTBObject: for key in keys: self.app.SetParameterOutputImagePixelType(key, dtype) - def read_values_at_coords(self, row: int, col: int, bands: int = None): + def read_values_at_coords(self, row: int, col: int, bands: int = None) -> list[int | float] | int | float: """Get pixel value(s) at a given YX coordinates. Args: @@ -329,7 +329,7 @@ class OTBObject: return data[0] return data - def channels_list_from_slice(self, bands: int): + def channels_list_from_slice(self, bands: int) -> list[int]: """Get list of channels to read values at, from a slice.""" nb_channels = self.shape[2] start, stop, step = bands.start, bands.stop, bands.step @@ -806,7 +806,7 @@ class OTBObject: """ return self.to_numpy() - def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): + def __array_ufunc__(self, ufunc, method, *inputs, **kwargs) -> OTBObject: """This is called whenever a numpy function is called on a pyotb object. Operation is performed in numpy, then imported back to pyotb with the same georeference as input. @@ -1049,7 +1049,7 @@ class Operation(OTBObject): fake_exp = f"({fake_exps[0]} ? {fake_exps[1]} : {fake_exps[2]})" self.fake_exp_bands.append(fake_exp) - def get_real_exp(self, fake_exp_bands: str): + def get_real_exp(self, fake_exp_bands: str) -> tuple(list[str], str): """Generates the BandMathX expression. Args: @@ -1073,7 +1073,8 @@ class Operation(OTBObject): return exp_bands, exp @staticmethod - def create_one_input_fake_exp(x: OTBObject | Output | str, band: int, keep_logical: bool = False): + def create_one_input_fake_exp(x: OTBObject | Output | str, + band: int, keep_logical: bool = False) -> tuple(str, list[OTBObject], int): """This an internal function, only to be used by `create_fake_exp`. Enable to create a fake expression just for one input and one band. @@ -1130,7 +1131,7 @@ class Operation(OTBObject): fake_exp = f"{x}b{band}" return fake_exp, inputs, nb_channels - def __str__(self): + def __str__(self) -> str: """Return a nice string representation with operator and object id.""" return f"<pyotb.Operation `{self.operator}` object, id {id(self)}>" @@ -1215,7 +1216,7 @@ class Input(OTBObject): self.propagate_dtype() self.execute() - def __str__(self): + def __str__(self) -> str: """Return a nice string representation with file path.""" return f"<pyotb.Input object from {self.path}>" @@ -1253,7 +1254,7 @@ class Output: if not self.filepath.parent.exists(): self.filepath.parent.mkdir(parents=True) - def __str__(self): + def __str__(self) -> str: """Return a nice string representation with source app name and object id.""" return f"<pyotb.Output {self.source_app.name} object, id {id(self)}>" -- GitLab From 7a36939d7910d3eb096bd63bdb4db0ca765e0f17 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@inrae.fr> Date: Wed, 11 Jan 2023 22:58:19 +0100 Subject: [PATCH 3/7] DOC: typing hint --- pyotb/apps.py | 15 ++++++++------- pyotb/functions.py | 19 ++++++++++++------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/pyotb/apps.py b/pyotb/apps.py index 5e30d0e..a2af7ed 100644 --- a/pyotb/apps.py +++ b/pyotb/apps.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """Search for OTB (set env if necessary), subclass core.App for each available application.""" +from __future__ import annotations import os import sys from pathlib import Path @@ -9,7 +10,7 @@ from .core import OTBObject from .helpers import logger -def get_available_applications(as_subprocess=False): +def get_available_applications(as_subprocess: bool = False) -> list[str]: """Find available OTB applications. Args: @@ -68,11 +69,11 @@ class App(OTBObject): self.description = self.app.GetDocLongDescription() @property - def used_outputs(self): + def used_outputs(self) -> list[str]: """List of used application outputs.""" return [getattr(self, key) for key in self.out_param_types if key in self.parameters] - def find_outputs(self): + def find_outputs(self) -> tuple[str]: """Find output files on disk using path found in parameters. Returns: @@ -91,7 +92,7 @@ class App(OTBObject): class OTBTFApp(App): """Helper for OTBTF.""" @staticmethod - def set_nb_sources(*args, n_sources=None): + def set_nb_sources(*args, n_sources: int = None): """Set the number of sources of TensorflowModelServe. Can be either user-defined or deduced from the args. Args: @@ -109,11 +110,11 @@ class OTBTFApp(App): if n_sources >= 1: os.environ['OTB_TF_NSOURCES'] = str(n_sources) - def __init__(self, app_name, *args, n_sources=None, **kwargs): + def __init__(self, name: str, *args, n_sources: int = None, **kwargs): """Constructor for an OTBTFApp object. Args: - app_name: name of the OTBTF app + name: name of the OTBTF app *args: arguments (dict). NB: we don't need kwargs because it cannot contain source#.il n_sources: number of sources. Default is None (resolves the number of sources based on the content of the dict passed in args, where some 'source' str is found) @@ -121,7 +122,7 @@ class OTBTFApp(App): """ self.set_nb_sources(*args, n_sources=n_sources) - super().__init__(app_name, *args, **kwargs) + super().__init__(name, *args, **kwargs) AVAILABLE_APPLICATIONS = get_available_applications(as_subprocess=True) diff --git a/pyotb/functions.py b/pyotb/functions.py index 669039a..906547f 100644 --- a/pyotb/functions.py +++ b/pyotb/functions.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """This module provides a set of functions for pyotb.""" +from __future__ import annotations import inspect import os import sys @@ -7,18 +8,19 @@ import textwrap import uuid from collections import Counter -from .core import (OTBObject, Input, Operation, LogicalOperation, get_nbchannels) +from .core import OTBObject, Input, Operation, LogicalOperation, get_nbchannels, Output from .helpers import logger -def where(cond, x, y): +def where(cond: OTBObject | Output | str, x: OTBObject | Output | str | int | float, + y: OTBObject | Output | str | int | float) -> Operation: """Functionally similar to numpy.where. Where cond is True (!=0), returns x. Else returns y. Args: cond: condition, must be a raster (filepath, App, Operation...). If cond is monoband whereas x or y are multiband, cond channels are expanded to match x & y ones. - x: value if cond is True. Can be float, int, App, filepath, Operation... - y: value if cond is False. Can be float, int, App, filepath, Operation... + x: value if cond is True. Can be: float, int, App, filepath, Operation... + y: value if cond is False. Can be: float, int, App, filepath, Operation... Returns: an output where pixels are x if cond is True, else y @@ -61,7 +63,8 @@ def where(cond, x, y): return operation -def clip(a, a_min, a_max): +def clip(a: OTBObject | Output | str, a_min: OTBObject | Output | str | int | float, + a_max: OTBObject | Output | str | int | float): """Clip values of image in a range of values. Args: @@ -321,8 +324,10 @@ def run_tf_function(func): return wrapper -def define_processing_area(*args, window_rule='intersection', pixel_size_rule='minimal', interpolator='nn', - reference_window_input=None, reference_pixel_size_input=None): +def define_processing_area(*args, window_rule: str = 'intersection', pixel_size_rule: str = 'minimal', + interpolator: str = 'nn', + reference_window_input: dict = None, reference_pixel_size_input: str = None) -> list[ + OTBObject]: """Given several inputs, this function handles the potential resampling and cropping to same extent. WARNING: Not fully implemented / tested -- GitLab From d9ca23ae55b32b828d52c0beb8ebd617687ecd8e Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@inrae.fr> Date: Wed, 11 Jan 2023 23:00:37 +0100 Subject: [PATCH 4/7] DOC: typing hint --- pyotb/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyotb/core.py b/pyotb/core.py index 7ea38dc..1208ac9 100644 --- a/pyotb/core.py +++ b/pyotb/core.py @@ -673,7 +673,7 @@ class OTBObject: return NotImplemented # this enables to fallback on numpy emulation thanks to __array_ufunc__ return Operation("/", other, self) - def __abs__(self, other: OTBObject | Output | str | int | float) -> Operation: + def __abs__(self) -> Operation: """Overrides the default abs operator and flavours it with BandMathX. Returns: -- GitLab From efd8f6ebd682987b2db4573ce762f96276076236 Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@inrae.fr> Date: Wed, 11 Jan 2023 23:03:24 +0100 Subject: [PATCH 5/7] DOC: typing hint --- pyotb/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyotb/core.py b/pyotb/core.py index 1208ac9..88496b6 100644 --- a/pyotb/core.py +++ b/pyotb/core.py @@ -449,7 +449,7 @@ class OTBObject: for arg in args: if isinstance(arg, dict): kwargs.update(arg) - elif isinstance(arg, (str, OTBObject, list)) and is_key_list(self, self.key_input): + elif isinstance(arg, (str, OTBObject)) or isinstance(arg, list) and is_key_list(self, self.key_input): kwargs.update({self.key_input: arg}) return kwargs -- GitLab From de049834402058a055d9ab1ba675f86516ea518c Mon Sep 17 00:00:00 2001 From: Remi Cresson <remi.cresson@inrae.fr> Date: Wed, 11 Jan 2023 23:08:06 +0100 Subject: [PATCH 6/7] DOC: typing hint --- pyotb/functions.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyotb/functions.py b/pyotb/functions.py index 906547f..aa161e6 100644 --- a/pyotb/functions.py +++ b/pyotb/functions.py @@ -325,9 +325,8 @@ def run_tf_function(func): def define_processing_area(*args, window_rule: str = 'intersection', pixel_size_rule: str = 'minimal', - interpolator: str = 'nn', - reference_window_input: dict = None, reference_pixel_size_input: str = None) -> list[ - OTBObject]: + interpolator: str = 'nn', reference_window_input: dict = None, + reference_pixel_size_input: str = None) -> list[OTBObject]: """Given several inputs, this function handles the potential resampling and cropping to same extent. WARNING: Not fully implemented / tested -- GitLab From d75905f86847a514acb2884374734d38f63bb6f5 Mon Sep 17 00:00:00 2001 From: Vincent Delbar <vincent.delbar@latelescop.fr> Date: Fri, 13 Jan 2023 11:35:06 +0100 Subject: [PATCH 7/7] ENH: type hints for helpers.py --- pyotb/helpers.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyotb/helpers.py b/pyotb/helpers.py index 742fee1..d38dfb3 100644 --- a/pyotb/helpers.py +++ b/pyotb/helpers.py @@ -26,7 +26,7 @@ logger_handler.setLevel(getattr(logging, LOG_LEVEL)) logger.addHandler(logger_handler) -def set_logger_level(level): +def set_logger_level(level: str): """Allow user to change the current logging level. Args: @@ -36,7 +36,7 @@ def set_logger_level(level): logger_handler.setLevel(getattr(logging, level)) -def find_otb(prefix=OTB_ROOT, scan=True, scan_userdir=True): +def find_otb(prefix: str = OTB_ROOT, scan: bool = True, scan_userdir: bool = True): """Try to load OTB bindings or scan system, help user in case of failure, set env variables. Path precedence : OTB_ROOT > python bindings directory @@ -98,7 +98,7 @@ def find_otb(prefix=OTB_ROOT, scan=True, scan_userdir=True): raise SystemExit("Failed to import OTB. Exiting.") from e -def set_environment(prefix): +def set_environment(prefix: str): """Set environment variables (before OTB import), raise error if anything is wrong. Args: @@ -156,7 +156,7 @@ def set_environment(prefix): os.environ["PROJ_LIB"] = proj_lib -def __find_lib(prefix=None, otb_module=None): +def __find_lib(prefix: str = None, otb_module=None): """Try to find OTB external libraries directory. Args: @@ -187,7 +187,7 @@ def __find_lib(prefix=None, otb_module=None): return None -def __find_python_api(lib_dir): +def __find_python_api(lib_dir: Path): """Try to find the python path. Args: @@ -206,7 +206,7 @@ def __find_python_api(lib_dir): return None -def __find_apps_path(lib_dir): +def __find_apps_path(lib_dir: Path): """Try to find the OTB applications path. Args: @@ -225,7 +225,7 @@ def __find_apps_path(lib_dir): return "" -def __find_otb_root(scan_userdir=False): +def __find_otb_root(scan_userdir: bool = False): """Search for OTB root directory in well known locations. Args: @@ -271,7 +271,7 @@ def __find_otb_root(scan_userdir=False): return prefix -def __suggest_fix_import(error_message, prefix): +def __suggest_fix_import(error_message: str, prefix: str): """Help user to fix the OTB installation with appropriate log messages.""" logger.critical("An error occurred while importing OTB Python API") logger.critical("OTB error message was '%s'", error_message) -- GitLab