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