diff --git a/scenes/core.py b/scenes/core.py
index 007a8dbaba365c0b2deb316fc2792e0efe0d317f..4630e047a68096b181ebeda1e48486e27e96144a 100644
--- a/scenes/core.py
+++ b/scenes/core.py
@@ -9,34 +9,43 @@ from scenes import utils
 
 
 def save_scenes(scenes_list, pickle_file):
-    """
-    Use pickle to save scenes
-    :param scenes_list: a list of Scene instances
-    :param pickle_file: pickle file
+    """Use pickle to save scenes
+
+    Args:
+        scenes_list: a list of Scene instances
+        pickle_file: pickle file
+
     """
     pickle.dump(scenes_list, open(pickle_file, "wb"))
 
 
 def load_scenes(pickle_file):
-    """
-    Use pickle to save Spot-6/7 scenes
-    :param pickle_file: pickle file
-    :return: list of Scene instances
+    """Use pickle to save Spot-6/7 scenes
+
+    Args:
+        pickle_file: pickle file
+
+    Returns:
+        list of Scene instances
+
     """
     return pickle.load(open(pickle_file, "rb"))
 
 
 class Source(pyotb.Output):
-    """
-    Source class.
+    """Source class.
+
     Holds common operations on image sources (e.g. drill, resample, extract an ROI, etc.)
+
     """
 
     def __init__(self, root_imagery, out, parent=None):
         """
-        :param root_imagery: root Imagery instance
-        :param out: image to deliver (can be an image filename (str), a pyotb.App, etc.)
-        :param parent: parent Source instance
+        Args:
+            root_imagery: root Imagery instance
+            out: image to deliver (can be an image filename (str), a pyotb.App, etc.)
+            parent: parent Source instance
+
         """
         assert isinstance(root_imagery, Imagery), "root_imagery type is {}".format(type(root_imagery))
         self.root_imagery = root_imagery  # root imagery
@@ -50,24 +59,33 @@ class Source(pyotb.Output):
         self._app_stack = []  # list of otb applications or output to keep trace
 
     def new_source(self, *args):
-        """
-        Return a new Source instance with new apps added at the end of the pipeline.
-        :param *args: list of pyotb.app instances to append to the existing pipeline
-        :return: new source
+        """Return a new Source instance with new apps added at the end of the pipeline.
+
+        Args:
+            *args: list of pyotb.app instances to append to the existing pipeline
+
+        Returns:
+            new source
+
         """
         for new_app in args:
             self._app_stack.append(new_app)
         return Source(root_imagery=self.root_imagery, out=self._app_stack[-1], parent=self)
 
     def drilled(self, msk_vec_file, inside=True, nodata=0):
-        """
-        Return the source drilled from the input vector data.
+        """Return the source drilled from the input vector data.
+
         The default behavior is that the hole is made inside the polygon.
         This can be changed setting the "inside" parameter to False.
-        :param msk_vec_file: input vector data filename
-        :param inside: whether the drill is happening inside the polygon or outside
-        :param nodata: nodata value inside holes
-        :return: drilled source
+
+        Args:
+            msk_vec_file: input vector data filename
+            inside: whether the drill is happening inside the polygon or outside (Default value = True)
+            nodata: nodata value inside holes (Default value = 0)
+
+        Returns:
+            drilled source
+
         """
         if utils.open_vector_layer(msk_vec_file):
             # Vector data not empty
@@ -80,12 +98,17 @@ class Source(pyotb.Output):
         return self  # Nothing but a soft copy of the source
 
     def masked(self, binary_mask, nodata=0):
-        """
-        Return the source masked from an uint8 binary raster (0 or 1..255).
+        """Return the source masked from an uint8 binary raster (0 or 1..255).
+
         Pixels are set to "nodata" where the mask values are 0.
-        :param binary_mask: input mono-band binary raster filename
-        :param nodata: nodata value for rejected values
-        :return: masked source
+
+        Args:
+            binary_mask: input mono-band binary raster filename
+            nodata: nodata value for rejected values (Default value = 0)
+
+        Returns:
+            masked source
+
         """
         manage_nodata = pyotb.ManageNoData({"in": self,
                                             "mode": "apply",
@@ -94,12 +117,16 @@ class Source(pyotb.Output):
         return self.new_source(binary_mask, manage_nodata)
 
     def resample_over(self, ref_img, interpolator="bco", nodata=0):
-        """
-        Return the source superimposed over the input image
-        :param ref_img: reference image
-        :param interpolator: interpolator
-        :param nodata: no data value
-        :return: resampled image source
+        """Return the source superimposed over the input image
+
+        Args:
+            ref_img: reference image
+            interpolator: interpolator (Default value = "bco")
+            nodata: no data value (Default value = 0)
+
+        Returns:
+            resampled image source
+
         """
         superimpose = pyotb.Superimpose({"inm": self,
                                          "inr": ref_img,
@@ -108,10 +135,14 @@ class Source(pyotb.Output):
         return self.new_source(ref_img, superimpose)
 
     def clip_over_img(self, ref_img):
-        """
-        Return the source clipped over the ROI specified by the input image extent
-        :param ref_img: reference image
-        :return: ROI clipped source
+        """Return the source clipped over the ROI specified by the input image extent
+
+        Args:
+            ref_img: reference image
+
+        Returns:
+            ROI clipped source
+
         """
         extract_roi = pyotb.ExtractROI({"in": self,
                                         "mode": "fit",
@@ -119,21 +150,29 @@ class Source(pyotb.Output):
         return self.new_source(ref_img, extract_roi)
 
     def clip_over_vec(self, ref_vec):
-        """
-        Return the source clipped over the ROI specified by the input vector extent
-        :param ref_vec: reference vector data
-        :return: ROI clipped source
+        """Return the source clipped over the ROI specified by the input vector extent
+
+        Args:
+            ref_vec: reference vector data
+
+        Returns:
+            ROI clipped source
+
         """
         return self.new_source(pyotb.ExtractROI({"in": self,
                                                  "mode": "fit",
                                                  "mode.fit.vec": ref_vec}))
 
     def reproject(self, epsg, interpolator="bco"):
-        """
-        Reproject the source into the specified EPSG
-        :param epsg: EPSG (int)
-        :param interpolator: interpolator
-        :return: reprojected source
+        """Reproject the source into the specified EPSG
+
+        Args:
+            epsg: EPSG (int)
+            interpolator: interpolator (Default value = "bco")
+
+        Returns:
+            reprojected source
+
         """
         if self.root_imagery.root_scene.epsg != epsg:
             return self.new_source(pyotb.Orthorectification({"io.in": self,
@@ -144,58 +183,69 @@ class Source(pyotb.Output):
 
 
 class Imagery(ABC):
-    """
-    Imagery class.
+    """Imagery class.
+
     This class carry the base image source, and additional generic stuff common to all sensors imagery.
+
     """
 
     def __init__(self, root_scene):
         """
-        :param root_scene: The Scene of which the Imagery instance is attached
+        Args:
+            root_scene: The Scene of which the Imagery instance is attached
+
         """
         self.root_scene = root_scene
 
 
 class Scene(ABC):
-    """
-    Scene class.
+    """Scene class.
+
     The class carries all the metadata from the scene, and can be used to retrieve its imagery.
     The get_imagery() function is abstract and must be implemented in child classes.
+
     """
 
     def __init__(self, acquisition_date, bbox_wgs84, epsg):
-        """
-        Constructor
-        :param acquisition_date: Acquisition date
-        :param bbox_wgs84: Bounding box, in WGS84 coordinates reference system
-        :param epsg: EPSG code
-        """
+        """Constructor
+
+        Args:
+            acquisition_date: Acquisition date
+            bbox_wgs84: Bounding box, in WGS84 coordinates reference system
+            epsg: EPSG code
 
+        """
         assert isinstance(acquisition_date, datetime.datetime), "acquisition_date must be a datetime.datetime instance"
         self.acquisition_date = acquisition_date
         self.bbox_wgs84 = bbox_wgs84
         assert isinstance(epsg, int), "epsg must be an int"
         self.epsg = epsg
+        self.metadata = self.get_metadata()
 
     @abstractmethod
     def get_imagery(self, **kwargs):
-        """
-        Must be implemented in child classes.
-        Return the imagery.
-        :param **kwargs: Imagery options
-        :return: Imagery instance
+        """Must be implemented in child classes.
+
+        Args:
+            **kwargs: Imagery options
+
+        Returns:
+            Imagery instance
+
         """
 
     def get_metadata(self):
-        """
-        Enable one instance to be used with print()
-        """
+        """Enable one instance to be used with print()"""
         return {
             "Acquisition date": self.acquisition_date,
             "Bounding box (WGS84)": self.bbox_wgs84,
             "EPSG": self.epsg,
         }
 
+    def get_serializable_metadata(self):
+        """Enable one instance to be used with print()"""
+        return {k: str(v) for k, v in self.metadata.items()}
+
     def __repr__(self):
         """
         Enable one instance to be used with print()
diff --git a/scenes/download.py b/scenes/download.py
index 6fb3a047145446d608310377e1f0a4f5cb8f4b7c..eb55009a5f1ed502bea55cf77715cac157fbb81f 100644
--- a/scenes/download.py
+++ b/scenes/download.py
@@ -13,9 +13,11 @@ import tqdm
 
 
 def compute_md5(fname):
-    """
-    Compute md5sum of a file
-    :param fname: file name
+    """Compute md5sum of a file
+
+    Args:
+        fname: file name
+
     """
     hash_md5 = hashlib.md5()
     with open(fname, "rb") as f:
@@ -25,8 +27,12 @@ def compute_md5(fname):
 
 
 def is_file_complete(filename, md5sum):
-    """
-    Tell if a file is complete
+    """Tell if a file is complete
+
+    Args:
+        filename: path of the file
+        md5sum: reference md5
+
     """
     # Does the file exist?
     if not os.path.isfile(filename):
@@ -37,14 +43,18 @@ def is_file_complete(filename, md5sum):
 
 
 def curl_url(url, postdata, verbose=False, fp=None, header=None):
-    """
-    Use PyCurl to make some requests
-    :param url: url
-    :param postdata: POST data
-    :param verbose: verbose (True or False)
-    :param fp: file handle
-    :param header: header. If None is kept, ['Accept:application/json'] is used
-    :return: decoded contents
+    """Use PyCurl to make some requests
+
+    Args:
+        url: url
+        postdata: POST data
+        verbose: boolean (Default value = False)
+        fp: file handle (Default value = None)
+        header: header. If None is kept, ['Accept:application/json'] is used (Default value = None)
+
+    Returns:
+        decoded contents
+
     """
     if not header:
         header = ['Accept:application/json']
@@ -67,12 +77,14 @@ def curl_url(url, postdata, verbose=False, fp=None, header=None):
         print("Downloading", flush=True)
 
         def _status(download_t, download_d, *_):
-            """
-            callback function for c.XFERINFOFUNCTION
+            """Callback function for c.XFERINFOFUNCTION
             https://stackoverflow.com/questions/19724222/pycurl-attachments-and-progress-functions
-            :param download_t: total
-            :param download_d: already downloaded
-            :return:
+
+            Args:
+                download_t: total
+                download_d: already downloaded
+                *_: any additional param (won't be used)
+
             """
             if download_d > 0:
                 nonlocal progress_bar, last_download_d
@@ -93,13 +105,12 @@ def curl_url(url, postdata, verbose=False, fp=None, header=None):
 
 
 class TheiaDownloader:
-    """
-    THEIA downloader
-    """
+    """THEIA downloader"""
     def __init__(self, config_file, max_records=500):
         """
-        :param config_file: Theia configuration file
-        :param max_records: Maximum number of records
+        Args:
+            config_file: Theia configuration file
+            max_records: Maximum number of records
         """
 
         # Read the Theia config file
@@ -129,9 +140,7 @@ class TheiaDownloader:
         self.max_records = max_records
 
     def _get_token(self):
-        """
-        Get the THEIA token
-        """
+        """Get the THEIA token"""
         postdata_token = {"ident": self.config["login_theia"], "pass": self.config["password_theia"]}
         url = "{}/services/authenticate/".format(self.config["serveur"])
         token = curl_url(url, postdata_token)
@@ -140,8 +149,8 @@ class TheiaDownloader:
         return token
 
     def _query(self, dict_query):
-        """
-        Search products
+        """Search products
+
         Return a dict with the following structure
             TILE_NAME
                +---- DATE
@@ -149,8 +158,13 @@ class TheiaDownloader:
                 +------ url
                 +------ checksum
                 +------ product_name
-        :param dict_query: query
-        :return tile dictionary
+
+        Args:
+            dict_query: query
+
+        Returns:
+            tile dictionary
+
         """
         url = "{}/{}/api/collections/SENTINEL2/search.json?{}".format(self.config["serveur"],
                                                                       self.config["resto"],
@@ -175,18 +189,24 @@ class TheiaDownloader:
         return tiles_dict
 
     def _download_tiles_and_dates(self, tiles_dict, download_dir):
-        """
-        Download a product.
+        """Download a product.
+
         Updates the "tiles_dict" with the filename of the downloaded files
-        :param tiles_dict: tiles dictionary. Must have the following structure:
-            TILE_NAME
-               +---- DATE
-                +------ id
-                +------ url
-                +------ checksum
-                +------ product_name
-        :return: tiles_dict updated with local_file:
-                +------ local_file
+
+        Args:
+            tiles_dict: tiles dictionary. Must have the following structure:
+                       TILE_NAME
+                         +---- DATE
+                         +------ id
+                         +------ url
+                         +------ checksum
+                         +------ product_name
+          download_dir:
+
+        Returns:
+            tiles_dict updated with local_file:
+                         +------ local_file
+
         """
         print("Get token...")
         token = self._get_token()
@@ -212,12 +232,16 @@ class TheiaDownloader:
         return tiles_dict
 
     def download_in_range(self, bbox_wgs84, dates_range, download_dir=None, level="LEVEL3A"):
-        """
-        Download all images within spatial and temporal ranges
-        :param bbox_wgs84: bounding box (WGS84)
-        :param dates_range: a tuple of datetime.datetime instances (start_date, end_date)
-        :param download_dir: download directory
-        :param level: LEVEL2A, LEVEL3A, ...
+        """Download all images within spatial and temporal ranges
+
+        Args:
+            bbox_wgs84: bounding box (WGS84)
+            dates_range: a tuple of datetime.datetime instances (start_date, end_date)
+            download_dir: download directory (Default value = None)
+            level: LEVEL2A, LEVEL3A, ... (Default value = "LEVEL3A")
+
+        Returns:
+            # TODO
         """
         start_date, end_date = dates_range
         # lonmin, latmin, lonmax, latmax
@@ -240,13 +264,17 @@ class TheiaDownloader:
         return search_results
 
     def download_closest(self, bbox_wgs84, acq_date, download_dir=None, level="LEVEL3A"):
-        """
-        query theia catalog, download_closest the files
-        :param bbox_wgs84: bounding box (WGS84)
-        :param acq_date: acquisition date to look around
-        :param download_dir: download directory
-        :param level: LEVEL2A, LEVEL3A, ...
-        :return: downloaded files
+        """Query Theia catalog, download_closest the files
+
+        Args:
+            bbox_wgs84: bounding box (WGS84)
+            acq_date: acquisition date to look around
+            download_dir: download directory (Default value = None)
+            level: LEVEL2A, LEVEL3A, ... (Default value = "LEVEL3A")
+
+        Returns:
+            downloaded files
+
         """
 
         # Important parameters
diff --git a/scenes/indexation.py b/scenes/indexation.py
index 19689022da4c7e37bb88a21873e7a71d9b3aaf50..6843a177cfa63a727bd45dd199fe61c483ce8f87 100644
--- a/scenes/indexation.py
+++ b/scenes/indexation.py
@@ -6,20 +6,28 @@ import rtree
 
 
 def get_timestamp(dt):
-    """
-    Converts datetime.datetime into a timestamp (in seconds)
-    :param dt: datetime.datetime instance
-    :return: timestamp (in seconds)
+    """Converts datetime.datetime into a timestamp (in seconds)
+
+    Args:
+        dt: datetime.datetime instance
+
+    Returns:
+        timestamp (in seconds)
+
     """
     return dt.replace(tzinfo=datetime.timezone.utc).timestamp()
 
 
 def new_bbox(bbox_wgs84, dt):
-    """
-    Return a bounding box in the domain (lat, lon, time)
-    :param bbox_wgs84: Bounding box (in WGS84)
-    :param dt: date datetime.datetime
-    :return: item for rtree
+    """Return a bounding box in the domain (lat, lon, time)
+
+    Args:
+        bbox_wgs84: Bounding box (in WGS84)
+        dt: date datetime.datetime
+
+    Returns:
+        item for rtree
+
     """
     dt_min = dt - datetime.timedelta(days=1)
     dt_max = dt + datetime.timedelta(days=1)
@@ -28,24 +36,28 @@ def new_bbox(bbox_wgs84, dt):
 
 
 def bbox(bbox_wgs84, dt_min, dt_max):
-    """
-    Return a bounding box in the domain (lat, lon, time)
-    :param bbox_wgs84: Bounding box (in WGS84)
-    :param dt_min: date min (datetime.datetime)
-    :param dt_max: date max (datetime.datetime)
-    :return: item for rtree
+    """Return a bounding box in the domain (lat, lon, time)
+
+    Args:
+        bbox_wgs84: Bounding box (in WGS84)
+        dt_min: date min (datetime.datetime)
+        dt_max: date max (datetime.datetime)
+
+    Returns:
+        item for rtree
+
     """
     (xmin, xmax, ymin, ymax) = bbox_wgs84
     return xmin, ymin, get_timestamp(dt_min), xmax, ymax, get_timestamp(dt_max)
 
 
 class Index:
-    """
-    Stores an indexation structures for a list of Scenes
-    """
+    """Stores an indexation structures for a list of Scenes"""
     def __init__(self, scenes_list):
         """
-        :param scenes_list: list of scenes
+        Args:
+            scenes_list: list of scenes
+
         """
         self.scenes_list = scenes_list
 
@@ -57,12 +69,16 @@ class Index:
             self.index.insert(scene_idx, new_bbox(bbox_wgs84=scene.bbox_wgs84, dt=scene.acquisition_date))
 
     def find_indices(self, bbox_wgs84, dt_min=None, dt_max=None):
-        """
-        Search the intersecting elements, and return their indices
-        :param bbox_wgs84: bounding box (WGS84)
-        :param dt_min: date min (datetime.datetime)
-        :param dt_max: date max (datetime.datetime)
-        :return: list of indices
+        """Search the intersecting elements, and return their indices
+
+        Args:
+            bbox_wgs84: bounding box (WGS84)
+            dt_min: date min (datetime.datetime) (Default value = None)
+            dt_max: date max (datetime.datetime) (Default value = None)
+
+        Returns:
+            list of indices
+
         """
         if not dt_min:
             dt_min = datetime.datetime.strptime("2000-01-01", "%Y-%m-%d")
@@ -72,12 +88,16 @@ class Index:
         return self.index.intersection(bbox_search)
 
     def find(self, bbox_wgs84, dt_min=None, dt_max=None):
-        """
-        Search the intersecting elements, and return them
-        :param bbox_wgs84: bounding box (WGS84)
-        :param dt_min: date min (datetime.datetime)
-        :param dt_max: date max (datetime.datetime)
-        :return: list of indices
+        """Search the intersecting elements, and return them
+
+        Args:
+            bbox_wgs84: bounding box (WGS84)
+            dt_min: date min (datetime.datetime) (Default value = None)
+            dt_max: date max (datetime.datetime) (Default value = None)
+
+        Returns:
+            list of indices
+
         """
         indices = self.find_indices(bbox_wgs84=bbox_wgs84, dt_min=dt_min, dt_max=dt_max)
         return [self.scenes_list[i] for i in indices]
diff --git a/scenes/raster_manips.py b/scenes/raster_manips.py
index 7218037423e3fee692eda5f14abd20170f58a66b..c97199ab5c87fc95ec645a100decf7cd23e52e13 100644
--- a/scenes/raster_manips.py
+++ b/scenes/raster_manips.py
@@ -7,14 +7,18 @@ import pyotb
 
 
 def align_on_raster(in_image, ref_image, spacing_ratio, interpolator="nn", nodata=0):
-    """
-    Return a raster which is aligned on the reference, with the given spacing ratio
-    :param in_image: input image
-    :param ref_image: reference image
-    :param spacing_ratio: spacing ratio. When > 1, the output has larger pixel. When < 1, the output has smaller pixels.
-    :param interpolator: interpolator (nn/bco/linear)
-    :param nodata: no data value outside image
-    :return: output aligned image
+    """Return a raster which is aligned on the reference, with the given spacing ratio
+
+    Args:
+        in_image: input image
+        ref_image: reference image
+        spacing_ratio: spacing ratio. When > 1, the output has larger pixel. When < 1, the output has smaller pixels.
+        interpolator: interpolator (nn/bco/linear) (Default value = "nn")
+        nodata: no data value outside image (Default value = 0)
+
+    Returns:
+        output aligned image
+
     """
 
     def _get_gt(image):
diff --git a/scenes/sentinel.py b/scenes/sentinel.py
index ae909378955c787a97a0d828fbaa0c0d43e35b16..1774733f9315dadaa824cf1cb6e5791db63ed8e7 100644
--- a/scenes/sentinel.py
+++ b/scenes/sentinel.py
@@ -9,18 +9,20 @@ from scenes.core import Source, Imagery, Scene
 
 
 class Sentinel2Source(Source):
-    """
-    Class for generic Sentinel-2 sources
-    """
+    """Class for generic Sentinel-2 sources"""
     R1_SIZE = 10980
     R2_SIZE = 5490
 
     def msk_drilled(self, msk_dict, exp, nodata=0):
         """
-        :param msk_dict: dict of masks
-        :param exp: bandmath expression to form the 0-255 binary mask
-        :param nodata: no-data value in masked output
-        :return: new masked source
+        Args:
+            msk_dict: dict of masks
+            exp: bandmath expression to form the 0-255 binary mask
+            nodata: no-data value in masked output (Default value = 0)
+
+        Returns:
+            new masked source
+
         """
         img_size = pyotb.ReadImageInfo(self).GetParameterInt('sizex')
         bm = pyotb.BandMath({"il": msk_dict[img_size], "exp": exp})
@@ -28,15 +30,17 @@ class Sentinel2Source(Source):
 
 
 class Sentinel22ASource(Sentinel2Source):
-    """
-    Sentinel-2 level 2A source class
-    """
+    """Sentinel-2 level 2A source class"""
 
     def cld_msk_drilled(self, nodata=-10000):
-        """
-        Return the source drilled from the cloud mask
-        :param nodata: nodata value inside holes
-        :return: drilled source
+        """Return the source drilled from the cloud mask
+
+        Args:
+            nodata: nodata value inside holes (Default value = -10000)
+
+        Returns:
+            drilled source
+
         """
         return self.msk_drilled(msk_dict={self.R1_SIZE: self.root_imagery.root_scene.cld_r1_msk_file,
                                           self.R2_SIZE: self.root_imagery.root_scene.cld_r2_msk_file},
@@ -45,22 +49,24 @@ class Sentinel22ASource(Sentinel2Source):
 
 
 class Sentinel23ASource(Sentinel2Source):
-    """
-    Sentinel-2 level 3A source class
-    """
+    """Sentinel-2 level 3A source class"""
 
     def flg_msk_drilled(self, keep_flags_values=(3, 4), nodata=-10000):
-        """
-        Return the source drilled from the FLG mask
-        :param keep_flags_values: flags values to keep. Can be:
-            0 = No data
-            1 = Cloud
-            2 = Snow
-            3 = Water
-            4 = Land
-            (source: https://labo.obs-mip.fr/multitemp/theias-l3a-product-format/)
-        :param nodata: nodata value inside holes
-        :return: drilled source
+        """Return the source drilled from the FLG mask
+
+        Args:
+            keep_flags_values: flags values to keep (Default value = (3, 4)). Can be:
+                               0 = No data
+                               1 = Cloud
+                               2 = Snow
+                               3 = Water
+                               4 = Land
+                               (source: https://labo.obs-mip.fr/multitemp/theias-l3a-product-format/)
+            nodata: nodata value inside holes (Default value = -10000)
+
+        Returns:
+            drilled source
+
         """
         exp = "||".join(["im1b1=={}".format(val) for val in keep_flags_values]) + "?255:0"
         return self.msk_drilled(msk_dict={self.R1_SIZE: self.root_imagery.root_scene.flg_r1_msk_file,
@@ -70,23 +76,17 @@ class Sentinel23ASource(Sentinel2Source):
 
 
 class Sentinel2ImageryBase(Imagery):
-    """
-    Base class for Sentinel-2 level 2A imagery classes.
-    """
+    """Base class for Sentinel-2 level 2A imagery classes."""
 
     def _concatenate_10m_bands(self):
-        """
-        :return: 10m spacing bands stacking pipeline
-        """
+        """Returns 10m spacing bands stacking pipeline"""
         return pyotb.ConcatenateImages([self.root_scene.band4_file,
                                         self.root_scene.band3_file,
                                         self.root_scene.band2_file,
                                         self.root_scene.band8_file])
 
     def _concatenate_20m_bands(self):
-        """
-        :return: 20m spacing bands stacking pipeline
-        """
+        """Returns 20m spacing bands stacking pipeline"""
         return pyotb.ConcatenateImages([self.root_scene.band5_file,
                                         self.root_scene.band6_file,
                                         self.root_scene.band7_file,
@@ -96,50 +96,38 @@ class Sentinel2ImageryBase(Imagery):
 
 
 class Sentinel22AImagery(Sentinel2ImageryBase):
-    """
-    Sentinel-2 level 2A class.
-    """
+    """Sentinel-2 level 2A class."""
 
     def get_10m_bands(self):
-        """
-        :return: 10m spacing bands
-        """
+        """Returns 10m spacing bands"""
         return Sentinel22ASource(self, self._concatenate_10m_bands())
 
     def get_20m_bands(self):
-        """
-        :return: 20m spacing bands
-        """
+        """Returns 20m spacing bands"""
         return Sentinel22ASource(self, self._concatenate_20m_bands())
 
 
 class Sentinel23AImagery(Sentinel2ImageryBase):
-    """
-    Sentinel-2 level 2A class.
-    """
+    """Sentinel-2 level 2A class."""
 
     def get_10m_bands(self):
-        """
-        :return: 10m spacing bands
-        """
+        """Returns 10m spacing bands"""
         return Sentinel23ASource(self, self._concatenate_10m_bands())
 
     def get_20m_bands(self):
-        """
-        :return: 20m spacing bands
-        """
+        """Returns 20m spacing bands"""
         return Sentinel23ASource(self, self._concatenate_20m_bands())
 
 
 class Sentinel2SceneBase(Scene):
-    """
-    Base class for Sentinel-2 images
-    """
+    """Base class for Sentinel-2 images"""
 
     @abstractmethod
     def __init__(self, archive, tag):
         """
-        :param archive: product .zip or directory
+        Args:
+            archive: product .zip or directory
+            tag: pattern to match in filenames, e.g. "FRE"
         """
         self.archive = archive
 
@@ -177,11 +165,17 @@ class Sentinel2SceneBase(Scene):
         super().__init__(acquisition_date=acquisition_date, bbox_wgs84=bbox_wgs84, epsg=epsg)
 
     def get_file(self, endswith):
-        """
-        Return the specified file.
-        Throw an exception if none or multiple candidates are found.
-        :param endswith: filtered extension
-        :return: the file
+        """Return the specified file.
+
+        Args:
+            endswith: filtered extension
+
+        Returns:
+            the file
+
+        Raises:
+            ValueError: If none or multiple candidates are found.
+
         """
         filtered_files_list = [f for f in self.files if f.endswith(endswith)]
         nb_matches = len(filtered_files_list)
@@ -191,19 +185,27 @@ class Sentinel2SceneBase(Scene):
         return filtered_files_list[0]
 
     def get_band(self, suffix1, suffix2):
-        """
-        Return the file path for the specified band.
-        Throw an exception if none or multiple candidates are found.
-        :param suffix1: suffix 1, e.g. "FRE"
-        :param suffix2: suffix 2, e.g. "B3"
-        :return: file path for the band
+        """Return the file path for the specified band.
+
+        Args:
+            suffix1: suffix 1, e.g. "FRE"
+            suffix2: suffix 2, e.g. "B3"
+
+        Returns:
+            file path for the band
+
+        Raises:
+            ValueError: If none or multiple candidates are found.
+
         """
         return self.get_file(endswith="_{}_{}.tif".format(suffix1, suffix2))
 
     def get_metadata(self):
-        """
-        Return the metadata
-        :return: metadata (dict)
+        """Get metadata
+
+        Returns:
+            metadata: dict
+
         """
         metadata = super().get_metadata()
         metadata.update({
@@ -223,14 +225,16 @@ class Sentinel2SceneBase(Scene):
 
 
 class Sentinel22AScene(Sentinel2SceneBase):
-    """
-    Sentinel-2 level 2A scene class.
+    """Sentinel-2 level 2A scene class.
+
     The class carries all the metadata from the root_scene, and can be used to retrieve its imagery.
+
     """
 
     def __init__(self, archive):
         """
-        :param archive: .zip file or folder. Must be a product from MAJA.
+        Args:
+            archive: .zip file or folder. Must be a product from MAJA.
         """
         # Call parent constructor
         super().__init__(archive=archive, tag="FRE")
@@ -242,16 +246,20 @@ class Sentinel22AScene(Sentinel2SceneBase):
         self.edg_r2_msk_file = self.get_file("_EDG_R2.tif")
 
     def get_imagery(self):  # pylint: disable=arguments-differ
-        """
-        Return the Sentinel-2 level 2A imagery
-        :return: Imagery instance
+        """ Get imagery
+
+        Returns:
+            Imagery instance
+
         """
         return Sentinel22AImagery(self)
 
     def get_metadata(self):
-        """
-        Return the metadata
-        :return: metadata (dict)
+        """ Get metadatas
+
+        Returns:
+            metadata: dict
+
         """
         metadata = super().get_metadata()
         metadata.update({
@@ -264,14 +272,16 @@ class Sentinel22AScene(Sentinel2SceneBase):
 
 
 class Sentinel23AScene(Sentinel2SceneBase):
-    """
-    Sentinel-2 level 3A scene class.
+    """Sentinel-2 level 3A scene class.
+
     The class carries all the metadata from the root_scene, and can be used to retrieve its imagery.
+
     """
 
     def __init__(self, archive):
         """
-        :param archive: .zip file or folder. Must be a product from WASP.
+        Args:
+            archive: .zip file or folder. Must be a product from WASP.
         """
         super().__init__(archive=archive, tag="FRC")
 
@@ -280,16 +290,20 @@ class Sentinel23AScene(Sentinel2SceneBase):
         self.flg_r2_msk_file = self.get_file("_FLG_R2.tif")
 
     def get_imagery(self):  # pylint: disable=arguments-differ
-        """
-        Return the Sentinel-2 level 3A imagery
-        :return: Imagery instance
+        """ Get imagery
+
+        Returns:
+            Imagery instance
+
         """
         return Sentinel23AImagery(self)
 
     def get_metadata(self):
-        """
-        Return the metadata
-        :return: metadata (dict)
+        """ Get metadatas
+
+        Returns:
+            metadata: dict
+
         """
         metadata = super().get_metadata()
         metadata.update({
diff --git a/scenes/spot.py b/scenes/spot.py
index 6faa498420caeda066a29e32b96c7dd175e5dc2a..0a573c5d8c0bd36519ea1f74b570e6b1502e0809 100644
--- a/scenes/spot.py
+++ b/scenes/spot.py
@@ -10,19 +10,27 @@ from scenes.core import Source, Imagery, Scene
 
 
 def find_all_dimaps(pth):
-    """
-    Return the list of DIMAPS that are inside all subdirectories of the root directory
-    :param pth: root directory
-    :return: list of DIMAPS
+    """Return the list of DIMAPS that are inside all subdirectories of the root directory
+
+    Args:
+        pth: root directory
+
+    Returns:
+        list of DIMAPS
+
     """
     return utils.find_files_in_all_subdirs(pth=pth, pattern="DIM_*.XML")
 
 
 def get_spot67_scenes(root_dir):
-    """
-    Return the list of pairs of PAN/XS DIMAPS
-    :param root_dir: directory containing "MS" and "PAN" subdirectories
-    :return: list of Spot67Scenes instances
+    """Return the list of pairs of PAN/XS DIMAPS
+
+    Args:
+        root_dir: directory containing "MS" and "PAN" subdirectories
+
+    Returns:
+        list of Spot67Scenes instances
+
     """
     # List files
     look_dir = root_dir + "/MS"
@@ -66,29 +74,33 @@ def get_spot67_scenes(root_dir):
 
 
 class Spot67Source(Source):
-    """
-    Spot 6/7 source class
-    """
+    """Spot 6/7 source class"""
 
     def cld_msk_drilled(self, nodata=0):
-        """
-        Return the source drilled from the cloud mask
-        :param nodata: nodata value inside holes
-        :return: drilled source
+        """Return the source drilled from the cloud mask
+
+        Args:
+            nodata: nodata value inside holes (Default value = 0)
+
+        Returns:
+            drilled source
+
         """
         return self.drilled(msk_vec_file=self.root_imagery.root_scene.cld_msk_file, nodata=nodata)
 
 
 class Spot67Imagery(Imagery):
-    """
-    Spot 6/7 imagery class.
+    """Spot 6/7 imagery class.
+
     This class carry the base image source, which can be radiometrically or geometrically corrected.
+
     """
 
     def __init__(self, root_scene, reflectance):
         """
-        :param root_scene: The Scene of which the Imagery instance is attached
-        :param reflectance: optional level of reflectance (can be "dn", "toa")
+        Args:
+            root_scene: The Scene of which the Imagery instance is attached
+            reflectance: optional level of reflectance (can be "dn", "toa")
         """
         super().__init__(root_scene=root_scene)
 
@@ -105,20 +117,21 @@ class Spot67Imagery(Imagery):
             self.pan = _toa(self.pan)
 
     def get_xs(self):
-        """
-        :return: XS source
-        """
+        """Returns XS source"""
         return Spot67Source(self, self.xs)
 
     def get_pan(self):
-        """
-        :return: PAN source
-        """
+        """Returns PAN source"""
         return Spot67Source(self, self.pan)
 
     def get_pxs(self, method="bayes"):
         """
-        :return: Pansharpened XS source
+        Args:
+            method: one of rcs, lmvm, bayes (Default value = "bayes")
+
+        Returns:
+            Pansharpened XS source
+
         """
         xs = self.get_xs()
         new_app = pyotb.BundleToPerfectSensor({"inp": self.pan, "inxs": xs, "method": method})
@@ -128,16 +141,18 @@ class Spot67Imagery(Imagery):
 
 
 class Spot67Scene(Scene):
-    """
-    Spot 6/7 root_scene class.
+    """Spot 6/7 root_scene class.
+
     The class carries all the metadata from the root_scene, and can be used to retrieve its imagery.
+
     """
     PXS_OVERLAP_THRESH = 0.995
 
     def __init__(self, dimap_file_xs, dimap_file_pan):
         """
-        :param dimap_file_xs: DIMAP file for XS
-        :param dimap_file_pan: DIMAP file for PAN
+        Args:
+            dimap_file_xs: DIMAP file for XS
+            dimap_file_pan: DIMAP file for PAN
         """
 
         # DIMAP files
@@ -151,11 +166,18 @@ class Spot67Scene(Scene):
 
         # Cloud masks
         def _get_mask(dimap_file, pattern):
-            """
-            Retrieve GML file.
-            An error is thrown when multiple files are found.
-            :param dimap_file: DIMAP file
-            :param pattern: vector data file pattern
+            """Retrieve GML file.
+
+            Args:
+                dimap_file: DIMAP file
+                pattern: vector data file pattern
+
+            Returns:
+                path of the file
+
+            Raises:
+                FileNotFoundError: when multiple files are found.
+
             """
             cld_path = utils.get_parent_directory(dimap_file) + "/MASKS"
             plist = utils.find_file_in_dir(cld_path, pattern=pattern)
@@ -247,22 +269,30 @@ class Spot67Scene(Scene):
 
     def has_partial_pxs_overlap(self):
         """
-        :return: True if at least PAN or XS imagery lies completely inside the other one. False else.
+        Returns:
+            True if at least PAN or XS imagery lies completely inside the other one. False else.
+
         """
         return self.pxs_overlap < self.PXS_OVERLAP_THRESH
 
     def get_imagery(self, reflectance="dn"):  # pylint: disable=arguments-differ
-        """
-        Return the Spot 6/7 imagery
-        :param reflectance: optional level of reflectance
-        :return: Imagery instance
+        """Return the Spot 6/7 imagery
+
+        Args:
+            reflectance: optional level of reflectance (Default value = "dn")
+
+        Returns:
+            Imagery instance
+
         """
         return Spot67Imagery(self, reflectance=reflectance)
 
     def get_metadata(self):
-        """
-        Return the metadata
-        :return: metadata (dict)
+        """ Get metadatas
+
+        Returns:
+            metadata: dict
+
         """
         metadata = super().get_metadata()
         metadata.update({
diff --git a/scenes/utils.py b/scenes/utils.py
index 198ddc45e18cf980d8e58afa6bf6d73658b2ce98..d15d88b52c75a16428c141726890ca08c64f0b06 100644
--- a/scenes/utils.py
+++ b/scenes/utils.py
@@ -12,10 +12,14 @@ from osgeo import osr, ogr, gdal
 
 
 def epsg2srs(epsg):
-    """
-    Return a Spatial Reference System corresponding to an EPSG
-    :param epsg: EPSG (int)
-    :return: OSR spatial reference
+    """Return a Spatial Reference System corresponding to an EPSG
+
+    Args:
+        epsg: EPSG (int)
+
+    Returns:
+        OSR spatial reference
+
     """
     srs = osr.SpatialReference()
     srs.ImportFromEPSG(epsg)
@@ -23,10 +27,14 @@ def epsg2srs(epsg):
 
 
 def get_epsg(gdal_ds):
-    """
-    Get the EPSG code of a GDAL dataset
-    :param gdal_ds: GDAL dataset
-    :return: EPSG code (int)
+    """Get the EPSG code of a GDAL dataset
+
+    Args:
+        gdal_ds: GDAL dataset
+
+    Returns:
+        EPSG code (int)
+
     """
     proj = osr.SpatialReference(wkt=gdal_ds.GetProjection())
     epsg = proj.GetAttrValue('AUTHORITY', 1)
@@ -35,10 +43,14 @@ def get_epsg(gdal_ds):
 
 
 def get_extent(gdal_ds):
-    """
-    Return list of corner coordinates from a GDAL dataset
-    :param gdal_ds: GDAL dataset
-    :return: list of coordinates
+    """Return list of corner coordinates from a GDAL dataset
+
+    Args:
+        gdal_ds: GDAL dataset
+
+    Returns:
+        list of coordinates
+
     """
     xmin, xpixel, _, ymax, _, ypixel = gdal_ds.GetGeoTransform()
     width, height = gdal_ds.RasterXSize, gdal_ds.RasterYSize
@@ -49,11 +61,16 @@ def get_extent(gdal_ds):
 
 
 def reproject_coords(coords, src_srs, tgt_srs):
-    """
-    Reproject a list of x,y coordinates.
-    :param coords: list of (x, y) tuples
-    :param src_srs: source CRS
-    :param tgt_srs: target CRS
+    """Reproject a list of x,y coordinates.
+
+    Args:
+        coords: list of (x, y) tuples
+        src_srs: source CRS
+        tgt_srs: target CRS
+
+    Returns:
+        trans_coords: coordinates in target CRS
+
     """
     trans_coords = []
     transform = osr.CoordinateTransformation(src_srs, tgt_srs)
@@ -65,10 +82,14 @@ def reproject_coords(coords, src_srs, tgt_srs):
 
 
 def get_extent_wgs84(gdal_ds):
-    """
-    Returns the extent in WGS84 CRS from a GDAL dataset
-    :param gdal_ds: GDAL dataset
-    :return: extent coordinates in WGS84 CRS
+    """Returns the extent in WGS84 CRS from a GDAL dataset
+
+    Args:
+        gdal_ds: GDAL dataset
+
+    Returns:
+        extent: coordinates in WGS84 CRS
+
     """
     coords = get_extent(gdal_ds)
     src_srs = osr.SpatialReference()
@@ -79,10 +100,14 @@ def get_extent_wgs84(gdal_ds):
 
 
 def extent_to_bbox(extent):
-    """
-    Converts an extent into a bounding box
-    :param extent: extent
-    :return: bounding box (xmin, xmax, ymin, ymax)
+    """Converts an extent into a bounding box
+
+    Args:
+        extent: extent
+
+    Returns:
+        bounding box (xmin, xmax, ymin, ymax)
+
     """
     xmin, xmax = math.inf, -math.inf
     ymin, ymax = math.inf, -math.inf
@@ -96,10 +121,14 @@ def extent_to_bbox(extent):
 
 
 def get_bbox_wgs84_from_gdal_ds(gdal_ds):
-    """
-    Returns the bounding box in WGS84 CRS from a GDAL dataset
-    :param gdal_ds: GDAL dataset
-    :return: bounding box (xmin, xmax, ymin, ymax)
+    """Returns the bounding box in WGS84 CRS from a GDAL dataset
+
+    Args:
+        gdal_ds: GDAL dataset
+
+    Returns:
+        bounding box (xmin, xmax, ymin, ymax)
+
     """
     extend_wgs84 = get_extent_wgs84(gdal_ds)
 
@@ -107,10 +136,14 @@ def get_bbox_wgs84_from_gdal_ds(gdal_ds):
 
 
 def get_epsg_extent_bbox(filename):
-    """
-    Returns (epsg, extent_wgs84) from a raster file that GDAL can open.
-    :param filename: file name
-    :return: (epsg, extent_wgs84)
+    """Returns (epsg, extent_wgs84) from a raster file that GDAL can open.
+
+    Args:
+        filename: file name
+
+    Returns:
+        (epsg, extent_wgs84, bbox_wgs84)
+
     """
     gdal_ds = gdal.Open(filename)
     epsg = get_epsg(gdal_ds)
@@ -121,10 +154,14 @@ def get_epsg_extent_bbox(filename):
 
 
 def coords2poly(coords):
-    """
-    Converts a list of coordinates into a polygon
-    :param coords: list of (x, y) coordinates
-    :return: a polygon
+    """Converts a list of coordinates into a polygon
+
+    Args:
+        coords: list of (x, y) coordinates
+
+    Returns:
+        a polygon
+
     """
     ring = ogr.Geometry(ogr.wkbLinearRing)
     for coord in coords + [coords[0]]:
@@ -137,10 +174,14 @@ def coords2poly(coords):
 
 
 def poly_union(layer):
-    """
-    Compute the union all the geometrical features of layer.
-    :param layer: The layer
-    :return The union of the layer's polygons (as a geometry)
+    """Compute the union all the geometrical features of layer.
+
+    Args:
+        layer: The layer
+
+    Returns:
+        the union of the layer's polygons (as a geometry)
+
     """
     union1 = None
     for feat in layer:
@@ -154,11 +195,15 @@ def poly_union(layer):
 
 
 def poly_overlap(poly, other_poly):
-    """
-    Returns the ratio of polygons overlap.
-    :param poly: polygon
-    :param other_poly: other polygon
-    :return: overlap (in the [0, 1] range). 0 -> no overlap with other_poly, 1 -> poly is completely inside other_poly
+    """Returns the ratio of polygons overlap.
+
+    Args:
+        poly: polygon
+        other_poly: other polygon
+
+    Returns:
+        overlap (in the [0, 1] range). 0 -> no overlap with other_poly, 1 -> poly is completely inside other_poly
+
     """
     inter = poly.Intersection(other_poly)
 
@@ -166,11 +211,15 @@ def poly_overlap(poly, other_poly):
 
 
 def extent_overlap(extent, other_extent):
-    """
-    Returns the ratio of extents overlap.
-    :param extent: extent
-    :param other_extent: other extent
-    :return: overlap (in the [0, 1] range). 0 -> no overlap with other_extent, 1 -> extent lies inside other_extent
+    """Returns the ratio of extents overlap.
+
+    Args:
+        extent: extent
+        other_extent: other extent
+
+    Returns:
+        overlap (in the [0, 1] range). 0 -> no overlap with other_extent, 1 -> extent lies inside other_extent
+
     """
     poly = coords2poly(extent)
     other_poly = coords2poly(other_extent)
@@ -178,10 +227,14 @@ def extent_overlap(extent, other_extent):
 
 
 def open_vector_layer(vector_file):
-    """
-    Return the vector dataset from a vector file. If the vector is empty, None is returned.
-    :param vector_file: input vector file
-    :return: ogr ds, or None (if error)
+    """Return the vector dataset from a vector file. If the vector is empty, None is returned.
+
+    Args:
+        vector_file: input vector file
+
+    Returns:
+        ogr ds, or None (if error)
+
     """
     poly_ds = ogr.Open(vector_file)
     if poly_ds is None:
@@ -190,10 +243,14 @@ def open_vector_layer(vector_file):
 
 
 def get_bbox_wgs84_from_vector(vector_file):
-    """
-    Returns the bounding box in WGS84 CRS from a vector data
-    :param vector_file: vector data filename
-    :return: bounding box in WGS84 CRS
+    """Returns the bounding box in WGS84 CRS from a vector data
+
+    Args:
+        vector_file: vector data filename
+
+    Returns:
+        bounding box in WGS84 CRS
+
     """
     poly_ds = open_vector_layer(vector_file=vector_file)
     poly_layer = poly_ds.GetLayer()
@@ -208,12 +265,16 @@ def get_bbox_wgs84_from_vector(vector_file):
 
 
 def find_files_in_all_subdirs(pth, pattern, case_sensitive=True):
-    """
-    Returns the list of files matching the pattern in all subdirectories of pth
-    :param pth: path
-    :param pattern: pattern
-    :param case_sensitive: case sensitive True or False
-    :return: list of str
+    """Returns the list of files matching the pattern in all subdirectories of pth
+
+    Args:
+        pth: path
+        pattern: pattern
+        case_sensitive: boolean (Default value = True)
+
+    Returns:
+        list of str
+
     """
     result = []
     reg_expr = re.compile(fnmatch.translate(pattern), 0 if case_sensitive else re.IGNORECASE)
@@ -223,20 +284,28 @@ def find_files_in_all_subdirs(pth, pattern, case_sensitive=True):
 
 
 def find_file_in_dir(pth, pattern):
-    """
-    Returns the list of files matching the pattern in the input directory
-    :param pth: path
-    :param pattern: pattern
-    :return: list of str
+    """Returns the list of files matching the pattern in the input directory
+
+    Args:
+        pth: path
+        pattern: pattern
+
+    Returns:
+        list of str
+
     """
     return glob.glob(os.path.join(pth, pattern))
 
 
 def get_parent_directory(pth):
-    """
-    Return the parent directory of the input directory or file
-    :param pth: input directory or file
-    :return: parent directory
+    """Return the parent directory of the input directory or file
+
+    Args:
+        pth: input directory or file
+
+    Returns:
+        parent directory
+
     """
     path = pathlib.Path(pth)
     if not path:
@@ -245,11 +314,15 @@ def get_parent_directory(pth):
 
 
 def list_files_in_zip(filename, endswith=None):
-    """
-    List files in zip archive
-    :param filename: path of the zip
-    :param endswith: optional, end of filename to be matched
-    :return: list of the filepaths
+    """List files in zip archive
+
+    Args:
+        filename: path of the zip
+        endswith: optional, end of filename to be matched (Default value = None)
+
+    Returns:
+        list of filepaths
+
     """
     with zipfile.ZipFile(filename) as zip_file:
         filelist = zip_file.namelist()
@@ -260,19 +333,27 @@ def list_files_in_zip(filename, endswith=None):
 
 
 def to_vsizip(zipfn, relpth):
-    """
-    Create path from zip file
-    :param zipfn: zip archive
-    :param relpth: relative path (inside archive)
-    :return: vsizip path
+    """Create path from zip file
+
+    Args:
+        zipfn: zip archive
+        relpth: relative path (inside archive)
+
+    Returns:
+        vsizip path
+
     """
     return "/vsizip/{}/{}".format(zipfn, relpth)
 
 
 def basename(pth):
-    """
-    Returns the basename. Works with files and paths
-    :param pth: path
-    :return: basename of the path
+    """Returns the basename. Works with files and paths
+
+    Args:
+        pth: path
+
+    Returns:
+        basename of the path
+
     """
     return str(pathlib.Path(pth).name)
diff --git a/test/scenes_test_base.py b/test/scenes_test_base.py
index bad32a80f9d1ab7c8557c8d8ca3e857899fd0da4..1a46f3383e90bf4f81a4ca9aa52cdf669c90f7a6 100644
--- a/test/scenes_test_base.py
+++ b/test/scenes_test_base.py
@@ -40,8 +40,12 @@ class ScenesTestBase(ABC, unittest.TestCase):
     def compare_file(self, file, reference):
         """
         Compare two files
-        :param file: file to compare
-        :param reference: baseline
-        :return: boolean
+
+        Args:
+            file: file to compare
+            reference: baseline
+
+        Return:
+             a boolean
         """
         self.assertTrue(filecmp.cmp(file, reference))