From 38bb32bd96e659a78cb26540368061a513141ea5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9mi?= <remi.cresson@inrae.fr>
Date: Tue, 8 Oct 2024 20:19:13 +0200
Subject: [PATCH 01/15] wip: fix #130

---
 pyotb/__init__.py |   1 -
 pyotb/core.py     | 118 +---------------------------------------------
 2 files changed, 1 insertion(+), 118 deletions(-)

diff --git a/pyotb/__init__.py b/pyotb/__init__.py
index 594272b..d112031 100644
--- a/pyotb/__init__.py
+++ b/pyotb/__init__.py
@@ -8,7 +8,6 @@ from .core import (
     OTBObject,
     App,
     Input,
-    Output,
     get_nbchannels,
     get_pixel_type,
     summarize,
diff --git a/pyotb/core.py b/pyotb/core.py
index b78efd4..3bd4c3c 100644
--- a/pyotb/core.py
+++ b/pyotb/core.py
@@ -601,13 +601,6 @@ class App(OTBObject):
         # Init, execute and write (auto flush only when output param was provided)
         if args or kwargs:
             self.set_parameters(*args, **kwargs)
-        # Create Output image objects
-        for key in (
-            key
-            for key, param in self._out_param_types.items()
-            if param == otb.ParameterType_OutputImage
-        ):
-            self.outputs[key] = Output(self, key, self._settings.get(key))
 
         if not self.frozen:
             self.execute()
@@ -1516,113 +1509,6 @@ class Input(App):
         return f"<pyotb.Input object, from {self.filepath}>"
 
 
-class Output(OTBObject):
-    """Object that behave like a pointer to a specific application in-memory output or file.
-
-    Args:
-        pyotb_app: The pyotb App to store reference from
-        param_key: Output parameter key of the target app
-        filepath: path of the output file (if not memory)
-        mkdir: create missing parent directories
-
-    """
-
-    _filepath: str | Path = None
-
-    @deprecated_alias(app="pyotb_app", output_parameter_key="param_key")
-    def __init__(
-        self,
-        pyotb_app: App,
-        param_key: str = None,
-        filepath: str = None,
-        mkdir: bool = True,
-    ):
-        """Constructor for an Output object, initialized during App.__init__."""
-        self.parent_pyotb_app = pyotb_app  # keep trace of parent app
-        self.param_key = param_key
-        self.filepath = filepath
-        if mkdir and filepath is not None:
-            self.make_parent_dirs()
-
-    @property
-    def name(self) -> str:
-        """Return Output name containing filepath."""
-        return f"Output {self.param_key} from {self.parent_pyotb_app.name}"
-
-    @property
-    def app(self) -> otb.Application:
-        """Reference to the parent pyotb otb.Application instance."""
-        return self.parent_pyotb_app.app
-
-    @property
-    @deprecated_attr(replacement="parent_pyotb_app")
-    def pyotb_app(self) -> App:
-        """Reference to the parent pyotb App (deprecated)."""
-
-    @property
-    def exports_dic(self) -> dict[str, dict]:
-        """Reference to parent _exports_dic object that contains np array exports."""
-        return self.parent_pyotb_app.exports_dic
-
-    @property
-    def output_image_key(self) -> str:
-        """Force the right key to be used when accessing the OTBObject."""
-        return self.param_key
-
-    @property
-    def filepath(self) -> str | Path:
-        """Property to manage output URL."""
-        if self._filepath is None:
-            raise ValueError("Filepath is not set")
-        return self._filepath
-
-    @filepath.setter
-    def filepath(self, path: str):
-        if isinstance(path, str):
-            if path and not path.startswith(("/vsi", "http://", "https://", "ftp://")):
-                path = Path(path.split("?")[0])
-            self._filepath = path
-
-    def exists(self) -> bool:
-        """Check if the output file exist on disk.
-
-        Raises:
-            ValueError: if filepath is not set or is remote URL
-
-        """
-        if not isinstance(self.filepath, Path):
-            raise ValueError("Filepath is not set or points to a remote URL")
-        return self.filepath.exists()
-
-    def make_parent_dirs(self):
-        """Create missing parent directories.
-
-        Raises:
-            ValueError: if filepath is not set or is remote URL
-
-        """
-        if not isinstance(self.filepath, Path):
-            raise ValueError("Filepath is not set or points to a remote URL")
-        self.filepath.parent.mkdir(parents=True, exist_ok=True)
-
-    def write(self, filepath: None | str | Path = None, **kwargs) -> bool:
-        """Write output to disk, filepath is not required if it was provided to parent App during init.
-
-        Args:
-            filepath: path of the output file, can be None if a value was passed during app init
-
-        """
-        if filepath is None:
-            return self.parent_pyotb_app.write(
-                {self.output_image_key: self.filepath}, **kwargs
-            )
-        return self.parent_pyotb_app.write({self.output_image_key: filepath}, **kwargs)
-
-    def __str__(self) -> str:
-        """Return string representation of Output filepath."""
-        return str(self.filepath)
-
-
 def get_nbchannels(inp: str | Path | OTBObject) -> int:
     """Get the nb of bands of input image.
 
@@ -1733,7 +1619,7 @@ def get_out_images_param_keys(otb_app: otb.Application) -> list[str]:
 
 
 def summarize(
-    obj: App | Output | str | float | list,
+    obj: App | str | float | list,
     strip_inpath: bool = False,
     strip_outpath: bool = False,
 ) -> dict[str, dict | Any] | str | float | list:
@@ -1756,8 +1642,6 @@ def summarize(
     """
     if isinstance(obj, list):
         return [summarize(o) for o in obj]
-    if isinstance(obj, Output):
-        return summarize(obj.parent_pyotb_app)
     # => This is the deepest recursion level
     if not isinstance(obj, App):
         return obj
-- 
GitLab


From d6266957f455b561df9fcf8bda4ff7de95157032 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9mi?= <remi.cresson@inrae.fr>
Date: Tue, 8 Oct 2024 20:27:37 +0200
Subject: [PATCH 02/15] wip: fix #130

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

diff --git a/pyotb/__init__.py b/pyotb/__init__.py
index d112031..99c4555 100644
--- a/pyotb/__init__.py
+++ b/pyotb/__init__.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 """This module provides convenient python wrapping of otbApplications."""
-__version__ = "2.0.3.dev2"
+__version__ = "2.1.0"
 
 from .install import install_otb
 from .helpers import logger
-- 
GitLab


From b83f5278a551c844e58f8105693a6a6c302b29dd Mon Sep 17 00:00:00 2001
From: Vincent Delbar <vincent.delbar@latelescop.fr>
Date: Wed, 9 Oct 2024 14:09:25 +0200
Subject: [PATCH 03/15] Revert "wip: fix #130"

This reverts commit d6266957f455b561df9fcf8bda4ff7de95157032.
---
 pyotb/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyotb/__init__.py b/pyotb/__init__.py
index 99c4555..d112031 100644
--- a/pyotb/__init__.py
+++ b/pyotb/__init__.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 """This module provides convenient python wrapping of otbApplications."""
-__version__ = "2.1.0"
+__version__ = "2.0.3.dev2"
 
 from .install import install_otb
 from .helpers import logger
-- 
GitLab


From ed45b39ee3d530b82d48aae098c503bed8e6f5c7 Mon Sep 17 00:00:00 2001
From: Vincent Delbar <vincent.delbar@latelescop.fr>
Date: Wed, 9 Oct 2024 14:09:36 +0200
Subject: [PATCH 04/15] Revert "wip: fix #130"

This reverts commit 38bb32bd96e659a78cb26540368061a513141ea5.
---
 pyotb/__init__.py |   1 +
 pyotb/core.py     | 118 +++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 118 insertions(+), 1 deletion(-)

diff --git a/pyotb/__init__.py b/pyotb/__init__.py
index d112031..594272b 100644
--- a/pyotb/__init__.py
+++ b/pyotb/__init__.py
@@ -8,6 +8,7 @@ from .core import (
     OTBObject,
     App,
     Input,
+    Output,
     get_nbchannels,
     get_pixel_type,
     summarize,
diff --git a/pyotb/core.py b/pyotb/core.py
index 3bd4c3c..b78efd4 100644
--- a/pyotb/core.py
+++ b/pyotb/core.py
@@ -601,6 +601,13 @@ class App(OTBObject):
         # Init, execute and write (auto flush only when output param was provided)
         if args or kwargs:
             self.set_parameters(*args, **kwargs)
+        # Create Output image objects
+        for key in (
+            key
+            for key, param in self._out_param_types.items()
+            if param == otb.ParameterType_OutputImage
+        ):
+            self.outputs[key] = Output(self, key, self._settings.get(key))
 
         if not self.frozen:
             self.execute()
@@ -1509,6 +1516,113 @@ class Input(App):
         return f"<pyotb.Input object, from {self.filepath}>"
 
 
+class Output(OTBObject):
+    """Object that behave like a pointer to a specific application in-memory output or file.
+
+    Args:
+        pyotb_app: The pyotb App to store reference from
+        param_key: Output parameter key of the target app
+        filepath: path of the output file (if not memory)
+        mkdir: create missing parent directories
+
+    """
+
+    _filepath: str | Path = None
+
+    @deprecated_alias(app="pyotb_app", output_parameter_key="param_key")
+    def __init__(
+        self,
+        pyotb_app: App,
+        param_key: str = None,
+        filepath: str = None,
+        mkdir: bool = True,
+    ):
+        """Constructor for an Output object, initialized during App.__init__."""
+        self.parent_pyotb_app = pyotb_app  # keep trace of parent app
+        self.param_key = param_key
+        self.filepath = filepath
+        if mkdir and filepath is not None:
+            self.make_parent_dirs()
+
+    @property
+    def name(self) -> str:
+        """Return Output name containing filepath."""
+        return f"Output {self.param_key} from {self.parent_pyotb_app.name}"
+
+    @property
+    def app(self) -> otb.Application:
+        """Reference to the parent pyotb otb.Application instance."""
+        return self.parent_pyotb_app.app
+
+    @property
+    @deprecated_attr(replacement="parent_pyotb_app")
+    def pyotb_app(self) -> App:
+        """Reference to the parent pyotb App (deprecated)."""
+
+    @property
+    def exports_dic(self) -> dict[str, dict]:
+        """Reference to parent _exports_dic object that contains np array exports."""
+        return self.parent_pyotb_app.exports_dic
+
+    @property
+    def output_image_key(self) -> str:
+        """Force the right key to be used when accessing the OTBObject."""
+        return self.param_key
+
+    @property
+    def filepath(self) -> str | Path:
+        """Property to manage output URL."""
+        if self._filepath is None:
+            raise ValueError("Filepath is not set")
+        return self._filepath
+
+    @filepath.setter
+    def filepath(self, path: str):
+        if isinstance(path, str):
+            if path and not path.startswith(("/vsi", "http://", "https://", "ftp://")):
+                path = Path(path.split("?")[0])
+            self._filepath = path
+
+    def exists(self) -> bool:
+        """Check if the output file exist on disk.
+
+        Raises:
+            ValueError: if filepath is not set or is remote URL
+
+        """
+        if not isinstance(self.filepath, Path):
+            raise ValueError("Filepath is not set or points to a remote URL")
+        return self.filepath.exists()
+
+    def make_parent_dirs(self):
+        """Create missing parent directories.
+
+        Raises:
+            ValueError: if filepath is not set or is remote URL
+
+        """
+        if not isinstance(self.filepath, Path):
+            raise ValueError("Filepath is not set or points to a remote URL")
+        self.filepath.parent.mkdir(parents=True, exist_ok=True)
+
+    def write(self, filepath: None | str | Path = None, **kwargs) -> bool:
+        """Write output to disk, filepath is not required if it was provided to parent App during init.
+
+        Args:
+            filepath: path of the output file, can be None if a value was passed during app init
+
+        """
+        if filepath is None:
+            return self.parent_pyotb_app.write(
+                {self.output_image_key: self.filepath}, **kwargs
+            )
+        return self.parent_pyotb_app.write({self.output_image_key: filepath}, **kwargs)
+
+    def __str__(self) -> str:
+        """Return string representation of Output filepath."""
+        return str(self.filepath)
+
+
 def get_nbchannels(inp: str | Path | OTBObject) -> int:
     """Get the nb of bands of input image.
 
@@ -1619,7 +1733,7 @@ def get_out_images_param_keys(otb_app: otb.Application) -> list[str]:
 
 
 def summarize(
-    obj: App | str | float | list,
+    obj: App | Output | str | float | list,
     strip_inpath: bool = False,
     strip_outpath: bool = False,
 ) -> dict[str, dict | Any] | str | float | list:
@@ -1642,6 +1756,8 @@ def summarize(
     """
     if isinstance(obj, list):
         return [summarize(o) for o in obj]
+    if isinstance(obj, Output):
+        return summarize(obj.parent_pyotb_app)
     # => This is the deepest recursion level
     if not isinstance(obj, App):
         return obj
-- 
GitLab


From 98d03b9ec7aca7d48c542cfa1ceed26668b088bc Mon Sep 17 00:00:00 2001
From: Vincent Delbar <vincent.delbar@latelescop.fr>
Date: Wed, 9 Oct 2024 14:27:26 +0200
Subject: [PATCH 05/15] Fixes #130 using WeakValueDictionary

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

diff --git a/pyotb/core.py b/pyotb/core.py
index b78efd4..bb589a9 100644
--- a/pyotb/core.py
+++ b/pyotb/core.py
@@ -7,6 +7,7 @@ from ast import literal_eval
 from pathlib import Path
 from time import perf_counter
 from typing import Any
+from weakref import WeakValueDictionary
 
 import numpy as np
 import otbApplication as otb  # pylint: disable=import-error
@@ -579,7 +580,8 @@ class App(OTBObject):
         self._exports_dic = {}
         self._settings, self._auto_parameters = {}, {}
         self._time_start, self._time_end = 0.0, 0.0
-        self.data, self.outputs = {}, {}
+        self.data = {}
+        self.outputs = WeakValueDictionary()
         self.quiet, self.frozen = quiet, frozen
 
         # Param keys and types
-- 
GitLab


From d09e94ca8f90c17e9ca6adfcfa965f257baa8aef Mon Sep 17 00:00:00 2001
From: Vincent Delbar <vincent.delbar@latelescop.fr>
Date: Wed, 9 Oct 2024 14:48:09 +0200
Subject: [PATCH 06/15] Revert "Fixes #130 using WeakValueDictionary"

This reverts commit 98d03b9ec7aca7d48c542cfa1ceed26668b088bc.
---
 pyotb/core.py | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/pyotb/core.py b/pyotb/core.py
index bb589a9..b78efd4 100644
--- a/pyotb/core.py
+++ b/pyotb/core.py
@@ -7,7 +7,6 @@ from ast import literal_eval
 from pathlib import Path
 from time import perf_counter
 from typing import Any
-from weakref import WeakValueDictionary
 
 import numpy as np
 import otbApplication as otb  # pylint: disable=import-error
@@ -580,8 +579,7 @@ class App(OTBObject):
         self._exports_dic = {}
         self._settings, self._auto_parameters = {}, {}
         self._time_start, self._time_end = 0.0, 0.0
-        self.data = {}
-        self.outputs = WeakValueDictionary()
+        self.data, self.outputs = {}, {}
         self.quiet, self.frozen = quiet, frozen
 
         # Param keys and types
-- 
GitLab


From f44c6738ae2713339003a13b88bf9e44751e78d5 Mon Sep 17 00:00:00 2001
From: Vincent Delbar <vincent.delbar@latelescop.fr>
Date: Wed, 9 Oct 2024 14:52:29 +0200
Subject: [PATCH 07/15] Try with weakref.ref

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

diff --git a/pyotb/core.py b/pyotb/core.py
index b78efd4..83c9dc7 100644
--- a/pyotb/core.py
+++ b/pyotb/core.py
@@ -2,6 +2,7 @@
 from __future__ import annotations
 
 import re
+import weakref
 from abc import ABC, abstractmethod
 from ast import literal_eval
 from pathlib import Path
@@ -1538,7 +1539,7 @@ class Output(OTBObject):
         mkdir: bool = True,
     ):
         """Constructor for an Output object, initialized during App.__init__."""
-        self.parent_pyotb_app = pyotb_app  # keep trace of parent app
+        self.parent_pyotb_app = weakref.ref(pyotb_app)  # keep a weak reference to parent app
         self.param_key = param_key
         self.filepath = filepath
         if mkdir and filepath is not None:
-- 
GitLab


From 12774a23d6bd9a37f10b2b3a78bee3b5035af4e6 Mon Sep 17 00:00:00 2001
From: Vincent Delbar <vincent.delbar@latelescop.fr>
Date: Wed, 9 Oct 2024 15:00:14 +0200
Subject: [PATCH 08/15] Use weakref.proxy

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

diff --git a/pyotb/core.py b/pyotb/core.py
index 83c9dc7..c7262d8 100644
--- a/pyotb/core.py
+++ b/pyotb/core.py
@@ -1539,7 +1539,7 @@ class Output(OTBObject):
         mkdir: bool = True,
     ):
         """Constructor for an Output object, initialized during App.__init__."""
-        self.parent_pyotb_app = weakref.ref(pyotb_app)  # keep a weak reference to parent app
+        self.parent_pyotb_app = weakref.proxy(pyotb_app)  # keep a weak reference to parent app
         self.param_key = param_key
         self.filepath = filepath
         if mkdir and filepath is not None:
-- 
GitLab


From 6a8d18b0d1b662ce2715104a0b359efb80bc477c Mon Sep 17 00:00:00 2001
From: Vincent Delbar <vincent.delbar@latelescop.fr>
Date: Wed, 9 Oct 2024 16:21:56 +0200
Subject: [PATCH 09/15] Do not instantiate Output objects during App init

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

diff --git a/pyotb/core.py b/pyotb/core.py
index c7262d8..d4b7e24 100644
--- a/pyotb/core.py
+++ b/pyotb/core.py
@@ -2,7 +2,6 @@
 from __future__ import annotations
 
 import re
-import weakref
 from abc import ABC, abstractmethod
 from ast import literal_eval
 from pathlib import Path
@@ -580,7 +579,7 @@ class App(OTBObject):
         self._exports_dic = {}
         self._settings, self._auto_parameters = {}, {}
         self._time_start, self._time_end = 0.0, 0.0
-        self.data, self.outputs = {}, {}
+        self.data = {}
         self.quiet, self.frozen = quiet, frozen
 
         # Param keys and types
@@ -598,17 +597,15 @@ class App(OTBObject):
             for key in self.parameters_keys
             if self.app.GetParameterType(key) == otb.ParameterType_Choice
         }
+        self._out_image_keys = tuple(
+            key
+            for key, param in self._out_param_types.items()
+            if param == otb.ParameterType_OutputImage
+        )
 
         # Init, execute and write (auto flush only when output param was provided)
         if args or kwargs:
             self.set_parameters(*args, **kwargs)
-        # Create Output image objects
-        for key in (
-            key
-            for key, param in self._out_param_types.items()
-            if param == otb.ParameterType_OutputImage
-        ):
-            self.outputs[key] = Output(self, key, self._settings.get(key))
 
         if not self.frozen:
             self.execute()
@@ -644,8 +641,8 @@ class App(OTBObject):
         return self._all_param_types[key] in param_types
 
     def __is_multi_output(self):
-        """Check if app has multiple outputs to ensure re-execution during write()."""
-        return len(self.outputs) > 1
+        """Check if app has multiple image outputs to ensure re-execution in write()."""
+        return len(self._out_image_keys) > 1
 
     def is_input(self, key: str) -> bool:
         """Returns True if the parameter key is an input."""
@@ -746,10 +743,8 @@ class App(OTBObject):
                     f"{self.name}: error before execution,"
                     f" while setting '{key}' to '{obj}': {e})"
                 ) from e
-            # Save / update setting value and update the Output object initialized in __init__ without a filepath
+            # Save / update setting value
             self._settings[key] = obj
-            if key in self.outputs:
-                self.outputs[key].filepath = obj
             if key in self._auto_parameters:
                 del self._auto_parameters[key]
 
@@ -1105,8 +1100,8 @@ class App(OTBObject):
         if isinstance(key, str):
             if key in self.data:
                 return self.data[key]
-            if key in self.outputs:
-                return self.outputs[key]
+            if key in self._out_image_keys:
+                return Output(self, key, self._settings.get(key))
             if key in self.parameters:
                 return self.parameters[key]
             raise KeyError(f"{self.name}: unknown or undefined parameter '{key}'")
@@ -1539,7 +1534,7 @@ class Output(OTBObject):
         mkdir: bool = True,
     ):
         """Constructor for an Output object, initialized during App.__init__."""
-        self.parent_pyotb_app = weakref.proxy(pyotb_app)  # keep a weak reference to parent app
+        self.parent_pyotb_app = pyotb_app  # keep a weak reference to parent app
         self.param_key = param_key
         self.filepath = filepath
         if mkdir and filepath is not None:
-- 
GitLab


From 97811e16ad3a8c0945cb769e9afbd5540f7cdc0a Mon Sep 17 00:00:00 2001
From: Vincent Delbar <vincent.delbar@latelescop.fr>
Date: Wed, 9 Oct 2024 16:23:58 +0200
Subject: [PATCH 10/15] Update comment

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

diff --git a/pyotb/core.py b/pyotb/core.py
index d4b7e24..ee73fcb 100644
--- a/pyotb/core.py
+++ b/pyotb/core.py
@@ -1534,7 +1534,7 @@ class Output(OTBObject):
         mkdir: bool = True,
     ):
         """Constructor for an Output object, initialized during App.__init__."""
-        self.parent_pyotb_app = pyotb_app  # keep a weak reference to parent app
+        self.parent_pyotb_app = pyotb_app  # keep a reference to parent app
         self.param_key = param_key
         self.filepath = filepath
         if mkdir and filepath is not None:
-- 
GitLab


From 0c5d55dea3da07adbef9b95da5709a2363a036ae Mon Sep 17 00:00:00 2001
From: Vincent Delbar <vincent.delbar@latelescop.fr>
Date: Wed, 9 Oct 2024 16:28:29 +0200
Subject: [PATCH 11/15] Add version bump and release notes

---
 RELEASE_NOTES.txt | 6 ++++++
 pyotb/__init__.py | 2 +-
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt
index 7a36b43..02ad82d 100644
--- a/RELEASE_NOTES.txt
+++ b/RELEASE_NOTES.txt
@@ -1,3 +1,9 @@
+---------------------------------------------------------------------
+2.1.0 (Oct 9, 2024) - Changes since version 2.0.2
+
+- Fix memory leak due to circular class reference to Output objects in App
+- Braking change : replaced App.outputs by a list of out image keys (App._out_image_keys)
+
 ---------------------------------------------------------------------
 2.0.2 (Apr 5, 2024) - Changes since version 2.0.1
 
diff --git a/pyotb/__init__.py b/pyotb/__init__.py
index 594272b..5e58316 100644
--- a/pyotb/__init__.py
+++ b/pyotb/__init__.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 """This module provides convenient python wrapping of otbApplications."""
-__version__ = "2.0.3.dev2"
+__version__ = "2.1.0"
 
 from .install import install_otb
 from .helpers import logger
-- 
GitLab


From a879d756d0a59488a1a617867a81da5b413e54a0 Mon Sep 17 00:00:00 2001
From: Vincent Delbar <vincent.delbar@latelescop.fr>
Date: Wed, 9 Oct 2024 16:28:57 +0200
Subject: [PATCH 12/15] Enhance release notes

---
 RELEASE_NOTES.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt
index 02ad82d..f43d456 100644
--- a/RELEASE_NOTES.txt
+++ b/RELEASE_NOTES.txt
@@ -1,7 +1,7 @@
 ---------------------------------------------------------------------
 2.1.0 (Oct 9, 2024) - Changes since version 2.0.2
 
-- Fix memory leak due to circular class reference to Output objects in App
+- Fix memory leak due to circular class reference to Output objects in App.outputs
 - Braking change : replaced App.outputs by a list of out image keys (App._out_image_keys)
 
 ---------------------------------------------------------------------
-- 
GitLab


From 81daa2e62066360a2b89c8f583012ca90bd8b542 Mon Sep 17 00:00:00 2001
From: Vincent Delbar <vincent.delbar@latelescop.fr>
Date: Wed, 9 Oct 2024 16:34:26 +0200
Subject: [PATCH 13/15] Typo

---
 RELEASE_NOTES.txt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt
index f43d456..90a9076 100644
--- a/RELEASE_NOTES.txt
+++ b/RELEASE_NOTES.txt
@@ -1,8 +1,8 @@
 ---------------------------------------------------------------------
 2.1.0 (Oct 9, 2024) - Changes since version 2.0.2
 
-- Fix memory leak due to circular class reference to Output objects in App.outputs
-- Braking change : replaced App.outputs by a list of out image keys (App._out_image_keys)
+- Fix memory leak due to circular references to Output objects in list App.outputs
+- Breaking change : replaced App.outputs by a list of out image keys (App._out_image_keys)
 
 ---------------------------------------------------------------------
 2.0.2 (Apr 5, 2024) - Changes since version 2.0.1
-- 
GitLab


From dba41eacc3ee8e323462298d142c6c884902b5f9 Mon Sep 17 00:00:00 2001
From: Vincent Delbar <vincent.delbar@latelescop.fr>
Date: Wed, 9 Oct 2024 16:34:57 +0200
Subject: [PATCH 14/15] Remove useless newlines

---
 RELEASE_NOTES.txt | 2 --
 1 file changed, 2 deletions(-)

diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt
index 90a9076..a3116c1 100644
--- a/RELEASE_NOTES.txt
+++ b/RELEASE_NOTES.txt
@@ -11,13 +11,11 @@
 - Fix a bug with parameters of type "field" for vector files
 - Fix wrong output parameter key in ImageClassifier and ImageClassifierFromDeepFeatures
 
-
 ---------------------------------------------------------------------
 2.0.1 (Dec 18, 2023) - Changes since version 2.0.0
 
 - Fix a bug when writing outputs in uint8
 
-
 ---------------------------------------------------------------------
 2.0.0 (Nov 23, 2023) - Changes since version 1.5.4
 
-- 
GitLab


From a7c46538e15049ce282832e33b8e9a20b37418dd Mon Sep 17 00:00:00 2001
From: Vincent Delbar <vincent.delbar@latelescop.fr>
Date: Wed, 9 Oct 2024 16:35:32 +0200
Subject: [PATCH 15/15] Typo

---
 RELEASE_NOTES.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt
index a3116c1..ff64cbe 100644
--- a/RELEASE_NOTES.txt
+++ b/RELEASE_NOTES.txt
@@ -2,7 +2,7 @@
 2.1.0 (Oct 9, 2024) - Changes since version 2.0.2
 
 - Fix memory leak due to circular references to Output objects in list App.outputs
-- Breaking change : replaced App.outputs by a list of out image keys (App._out_image_keys)
+- Breaking change : replaced App.outputs by a tuple of out image keys (App._out_image_keys)
 
 ---------------------------------------------------------------------
 2.0.2 (Apr 5, 2024) - Changes since version 2.0.1
-- 
GitLab