Skip to content

sentinel

This module contains Sentinel-2 classes (sources and scenes).

Overview

Right not, the following sentinel products are implemented: - Level 2A from Theia (from local files) - Level 3A from Theia (from local files) - Level 2A from Microsoft Planetary Computer (from STAC)

Scene classes:

classDiagram Scene <|-- Sentinel2SceneBase Sentinel2SceneBase <|-- Sentinel2MPCScene Sentinel2SceneBase <|-- Sentinel2TheiaScene Sentinel2TheiaScene <|-- Sentinel22AScene Sentinel2TheiaScene <|-- Sentinel23AScene

Source classes:

classDiagram Source <|-- CommonImagerySource CommonImagerySource <|-- Sentinel2TheiaGenericSource Sentinel2TheiaGenericSource <|-- Sentinel2Theia2ASource Sentinel2TheiaGenericSource <|-- Sentinel2Theia3ASource

The Sentinel2MPCScene class delivers sources which are of type CommonImagerySource.

Sources

The Sentinel2SceneBase class contains generic methods to access all Sentinel-2 products Sources members. - Single band getters (e.g. b3, b4) - Concatenated bands getters (e.g. 10m bands)

You can do the following to list available sources:

print(sc)  # show everything, including the sources names
print(sc.sources.keys())  # show only the sources keys

Single image getters

Any Source corresponding to a specific raster asset can be retrieved using the get_<name>() function call. For instance this enables to retrieve the band 4:

b4 = sc.get_b4()

Concatenated bands

The sources carry the following images for:

  • The 10m spacing channels, in the following order: 4, 3, 2, 8
  • The 20m spacing channels, in the following order: 5, 6, 7, 8a, 11, 12
bands_10m = sc.get_10m_bands()
bands_20m = sc.get_20m_bands()

Subset of bands can also be requested:

bands_10m_2a = sc_2a.get_10m_bands("b4")
bands_10m_2a = sc_2a.get_10m_bands("b4", "b8")
bands_10m_2a = sc_2a.get_10m_bands(["b4", "b8"])

Theia

The Sentinel22AScene and Sentinel23AScene classes carry metadata and sources respectively for Sentinel-2 Level 2A and Level 3A products. They both inherit from the generic abstract Sentinel2TheiaScene class.

Instantiation

Sentinel22AScene and Sentinel23AScene are instantiated from the archive (.zip file) or the product folder.

import scenes
sc_2a = scenes.sentinel.Sentinel22AScene("SENTINEL2B_..._T31TEJ_D_V1-8.zip")
sc_3a = scenes.sentinel.Sentinel23AScene("SENTINEL2X_...L3A_T31TEJ_D_V1-0.zip")

Metadata

The scene metadata can be accessed with the metadata attribute, like any scenes.core.Scene instance.

md_dict = sc_2a.metadata
scenes.utils.pprint(md_dict)

Sources

The Sentinel2Theia2ASource and Sentinel2Theia3ASource classes carry imagery sources respectively for Sentinel-2 Level 2A and Level 3A products. They both inherit from the generic Sentinel2TheiaGenericSource class, which itself inherits from CommonImagerySource.

classDiagram Sentinel2TheiaGenericSource <|-- Sentinel2Theia2ASource Sentinel2TheiaGenericSource <|-- Sentinel2Theia3ASource class Sentinel2TheiaGenericSource{ +R1_SIZE +R2_SIZE +msk_drilled(msk_dict, exp, nodata=0) } class Sentinel2Theia2ASource{ +cld_msk_drilled(nodata=0) } class Sentinel2Theia3ASource{ +flg_msk_drilled(keep_flags_values=(3, 4), nodata=0) }

The Sentinel2Theia2ASource implements the Sentinel2Theia2ASource.cld_msk_drilled method, that enable to mask the cloud masks over the root source, with the specified no-data value (default is 0). The following example show how to derive a child source replacing the pixels that are in the clouds with pixels at -10000 (which is the no-data value in Theia products):

drilled = bands_10m_2a.cld_msk_drilled()

The Sentinel2Theia3ASource implements the Sentinel2Theia3ASource.flg_msk_drilled method, that enable to mask the pixels on a selection of labels of the quality mask. The following example shows how to mask pixels of anything other that land with -10000:

drilled = bands_10m_3a.flg_msk_drilled(keep_flags_values=(4,))

Sentinel2Theia2ASource and Sentinel2Theia3ASource inherit from scenes.core.CommonImagerySource, hence implemented sources transformations (e.g. scenes.core.CommonImagerySource.masked, scenes.core.CommonImagerySource.clip_over_img, scenes.core.CommonImagerySource.resample_over, scenes.core.CommonImagerySource.reproject, etc.)

clipped = drilled.clip_over_img(roi)
reprojected = clipped.reproject(epsg=4328)

Note that the resulting transformed Sentinel2Theia2ASource and Sentinel2Theia3ASource are still instances of Sentinel2Theia2ASource and Sentinel2Theia3ASource after generic operations implemented in scenes.core.CommonImagerySource.

Usage with pyotb

As scenes.core.Source, it also can be used like any pyotb.core.OTBObject. The following example show how to use an OTB application with a source at input.

rgb_nice = pyotb.DynamicConvert(reprojected)
rgb_nice.write("image.tif", pixel_type="uint8")

Sentinel22AScene

Bases: Sentinel2TheiaScene

Sentinel-2 level 2A scene class.

The class carries all the metadata from the root_scene, and can be used to retrieve its sources.

Source code in scenes/sentinel.py
class Sentinel22AScene(Sentinel2TheiaScene):
    """
    Sentinel-2 level 2A scene class.

    The class carries all the metadata from the root_scene, and can be used to
    retrieve its sources.

    """

    def __init__(self, archive: str, tag: str = "FRE"):
        """
        Args:
            archive: .zip file or folder. Must be a product from MAJA.
            tag: product tag (SRE/FRE)

        """
        masks = ["clm_r1", "edg_r1", "clm_r2", "edg_r2"]
        src_classes = {
            **{key: Sentinel2Theia2ASource for key in self.bands_keys},
            **{key: CommonImagerySource for key in masks}
        }
        super().__init__(archive=archive, tag=tag, src_classes=src_classes)

__init__(archive, tag='FRE')

Parameters:

Name Type Description Default
archive str

.zip file or folder. Must be a product from MAJA.

required
tag str

product tag (SRE/FRE)

'FRE'
Source code in scenes/sentinel.py
def __init__(self, archive: str, tag: str = "FRE"):
    """
    Args:
        archive: .zip file or folder. Must be a product from MAJA.
        tag: product tag (SRE/FRE)

    """
    masks = ["clm_r1", "edg_r1", "clm_r2", "edg_r2"]
    src_classes = {
        **{key: Sentinel2Theia2ASource for key in self.bands_keys},
        **{key: CommonImagerySource for key in masks}
    }
    super().__init__(archive=archive, tag=tag, src_classes=src_classes)

Sentinel23AScene

Bases: Sentinel2TheiaScene

Sentinel-2 level 3A scene class.

The class carries all the metadata from the root_scene, and can be used to retrieve its sources.

Source code in scenes/sentinel.py
class Sentinel23AScene(Sentinel2TheiaScene):
    """Sentinel-2 level 3A scene class.

    The class carries all the metadata from the root_scene, and can be used to
    retrieve its sources.

    """

    def __init__(self, archive: str, tag: str = "FRC"):
        """
        Args:
            archive: .zip file or folder. Must be a product from WASP.
            tag: product tag (FRC)

        """
        masks = ["flg_r1", "flg_r2"]
        src_classes = {
            **{key: Sentinel2Theia3ASource for key in self.bands_keys},
            **{key: CommonImagerySource for key in masks}
        }
        super().__init__(archive=archive, tag=tag, src_classes=src_classes)

__init__(archive, tag='FRC')

Parameters:

Name Type Description Default
archive str

.zip file or folder. Must be a product from WASP.

required
tag str

product tag (FRC)

'FRC'
Source code in scenes/sentinel.py
def __init__(self, archive: str, tag: str = "FRC"):
    """
    Args:
        archive: .zip file or folder. Must be a product from WASP.
        tag: product tag (FRC)

    """
    masks = ["flg_r1", "flg_r2"]
    src_classes = {
        **{key: Sentinel2Theia3ASource for key in self.bands_keys},
        **{key: CommonImagerySource for key in masks}
    }
    super().__init__(archive=archive, tag=tag, src_classes=src_classes)

Sentinel2MPCScene

Bases: Sentinel2SceneBase

class for Sentinel-2 images from mMicrosoft Planetary Computer

Source code in scenes/sentinel.py
class Sentinel2MPCScene(Sentinel2SceneBase):
    """class for Sentinel-2 images from mMicrosoft Planetary Computer"""

    def __init__(
            self,
            assets_paths: Dict[str, str],
    ):
        """
        Args:
            assets_paths: assets paths

        """
        # Assets (spectral bands)
        # We just use the same key for all Sentinel2SceneBase products
        bands_names_mapping = {
            "b4": "B04",
            "b3": "B03",
            "b2": "B02",
            "b8": "B08",
            "b5": "B05",
            "b6": "B06",
            "b7": "B07",
            "b8a": "B8A",
            "b11": "B11",
            "b12": "B12"
        }
        updated_assets_paths = {
            key: assets_paths[asset_key] for key, asset_key
            in bands_names_mapping.items()
        }

        # Sources classes
        src_classes = {
            key: CommonImagerySource for key in updated_assets_paths
        }

        # Date, extent
        b2_path = updated_assets_paths["b2"]
        # Here, b2_path is something like ...T45WXU_20221019T062901_B03_10m.tif
        epsg, extent = get_epsg_extent_wgs84(b2_path)
        datestr = b2_path.split("_")[-3]  # 20180630T105440
        acquisition_date = datetime.strptime(datestr, '%Y%m%dT%H%M%S')

        # Call parent constructor
        super().__init__(
            acquisition_date=acquisition_date,
            epsg=epsg,
            extent_wgs84=extent,
            assets_paths=updated_assets_paths,
            src_classes=src_classes
        )

__init__(assets_paths)

Parameters:

Name Type Description Default
assets_paths Dict[str, str]

assets paths

required
Source code in scenes/sentinel.py
def __init__(
        self,
        assets_paths: Dict[str, str],
):
    """
    Args:
        assets_paths: assets paths

    """
    # Assets (spectral bands)
    # We just use the same key for all Sentinel2SceneBase products
    bands_names_mapping = {
        "b4": "B04",
        "b3": "B03",
        "b2": "B02",
        "b8": "B08",
        "b5": "B05",
        "b6": "B06",
        "b7": "B07",
        "b8a": "B8A",
        "b11": "B11",
        "b12": "B12"
    }
    updated_assets_paths = {
        key: assets_paths[asset_key] for key, asset_key
        in bands_names_mapping.items()
    }

    # Sources classes
    src_classes = {
        key: CommonImagerySource for key in updated_assets_paths
    }

    # Date, extent
    b2_path = updated_assets_paths["b2"]
    # Here, b2_path is something like ...T45WXU_20221019T062901_B03_10m.tif
    epsg, extent = get_epsg_extent_wgs84(b2_path)
    datestr = b2_path.split("_")[-3]  # 20180630T105440
    acquisition_date = datetime.strptime(datestr, '%Y%m%dT%H%M%S')

    # Call parent constructor
    super().__init__(
        acquisition_date=acquisition_date,
        epsg=epsg,
        extent_wgs84=extent,
        assets_paths=updated_assets_paths,
        src_classes=src_classes
    )

Sentinel2SceneBase

Bases: Scene

Base class for Sentinel-2 images

Source code in scenes/sentinel.py
class Sentinel2SceneBase(Scene):
    """Base class for Sentinel-2 images"""

    bands_keys = BANDS_1 + BANDS_2
    concatenated_bands_dict = {
        "10m_bands": BANDS_1,
        "20m_bands": BANDS_2
    }

    def __init__(
            self,
            acquisition_date: datetime,
            epsg: int,
            extent_wgs84: List[Tuple(float, float)],
            assets_paths: List[str, str],
            src_classes: Dict[str, Type[Source]] = None,
            additional_metadata: Dict[str, Any] = None,
    ):
        """
        Initialize the Sentinel-2 Scene

        Args:
            acquisition_date: Acquisition date
            epsg: EPSG code
            extent_wgs84: extent in WGS84 coordinates reference system
            assets_paths: assets dictionary
                {source_name, source_path/url}, e.g.:
                {'b4': '/vsicurl/https://.../...B4.tif'}
            src_classes: imagery sources class
            additional_metadata: additional metadata

        """

        assert all(key in src_classes for key in self.bands_keys), \
            "Some keys in concatenated_bands_dict are not in src_classes"

        # Sources
        sources = {
            key: partial(src_class, root_scene=self, out=assets_paths[key])
            for key, src_class in src_classes.items()
        }

        # Sources for concatenated bands: get_b10m_bands, get_20m_bands
        for key, default_bands_names in self.concatenated_bands_dict.items():
            sources.update({
                key: partial(
                    self._get_bands,
                    src_classes[default_bands_names[0]],
                    default_bands_names
                )
            })

        super().__init__(
            acquisition_date=acquisition_date,
            epsg=epsg,
            extent_wgs84=extent_wgs84,
            assets_paths=assets_paths,
            sources=sources,
            additional_metadata=additional_metadata
        )

    def _get_bands(
            self,
            imagery_src_class,
            default_bands_names: tuple(str),
            *args
    ) -> Source:
        """
        Returns a Source for the concatenated bands

        Args:
            imagery_src_class: source class
            default_bands_names: names of available bands
            *args: can be:
             - names of the bands
             - a name
             - a list or a tuple of names

        Returns:
            Selected spectral bands

        """
        bands = default_bands_names
        if args:
            bands = []
            for arg in args:
                bands += arg if isinstance(arg, (list, tuple)) else [arg]
        for band in bands:
            assert isinstance(band, str), f"{band} is not a string"
        # user can use upper or lower cases
        bands = [band.lower() for band in bands]
        assert all(band in default_bands_names for band in bands), \
            f"Some bands in {bands} are not in the available bands " \
            f"{default_bands_names}"
        concat = pyotb.ConcatenateImages(
            [self.get_asset_path(band_name) for band_name in bands]
        )
        return imagery_src_class(self, concat)

__init__(acquisition_date, epsg, extent_wgs84, assets_paths, src_classes=None, additional_metadata=None)

Initialize the Sentinel-2 Scene

Parameters:

Name Type Description Default
acquisition_date datetime

Acquisition date

required
epsg int

EPSG code

required
extent_wgs84 List[Tuple(float, float)]

extent in WGS84 coordinates reference system

required
assets_paths List[str, str]

assets dictionary {source_name, source_path/url}, e.g.: {'b4': '/vsicurl/https://.../...B4.tif'}

required
src_classes Dict[str, Type[Source]]

imagery sources class

None
additional_metadata Dict[str, Any]

additional metadata

None
Source code in scenes/sentinel.py
def __init__(
        self,
        acquisition_date: datetime,
        epsg: int,
        extent_wgs84: List[Tuple(float, float)],
        assets_paths: List[str, str],
        src_classes: Dict[str, Type[Source]] = None,
        additional_metadata: Dict[str, Any] = None,
):
    """
    Initialize the Sentinel-2 Scene

    Args:
        acquisition_date: Acquisition date
        epsg: EPSG code
        extent_wgs84: extent in WGS84 coordinates reference system
        assets_paths: assets dictionary
            {source_name, source_path/url}, e.g.:
            {'b4': '/vsicurl/https://.../...B4.tif'}
        src_classes: imagery sources class
        additional_metadata: additional metadata

    """

    assert all(key in src_classes for key in self.bands_keys), \
        "Some keys in concatenated_bands_dict are not in src_classes"

    # Sources
    sources = {
        key: partial(src_class, root_scene=self, out=assets_paths[key])
        for key, src_class in src_classes.items()
    }

    # Sources for concatenated bands: get_b10m_bands, get_20m_bands
    for key, default_bands_names in self.concatenated_bands_dict.items():
        sources.update({
            key: partial(
                self._get_bands,
                src_classes[default_bands_names[0]],
                default_bands_names
            )
        })

    super().__init__(
        acquisition_date=acquisition_date,
        epsg=epsg,
        extent_wgs84=extent_wgs84,
        assets_paths=assets_paths,
        sources=sources,
        additional_metadata=additional_metadata
    )

Sentinel2Theia2ASource

Bases: Sentinel2TheiaGenericSource

Sentinel-2 level 2A source class

Source code in scenes/sentinel.py
class Sentinel2Theia2ASource(Sentinel2TheiaGenericSource):
    """Sentinel-2 level 2A source class"""

    def cld_msk_drilled(
            self,
            nodata: Union[float, int] = -10000
    ) -> Sentinel2Theia2ASource:
        """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_SPC: self.root_scene.get_asset_path("clm_r1"),
                self.R2_SPC: self.root_scene.get_asset_path("clm_r2")
            },
            exp="im1b1==0?255:0",
            nodata=nodata
        )

cld_msk_drilled(nodata=-10000)

Return the source drilled from the cloud mask

Parameters:

Name Type Description Default
nodata Union[float, int]

nodata value inside holes (Default value = -10000)

-10000

Returns:

Type Description
Sentinel2Theia2ASource

drilled source

Source code in scenes/sentinel.py
def cld_msk_drilled(
        self,
        nodata: Union[float, int] = -10000
) -> Sentinel2Theia2ASource:
    """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_SPC: self.root_scene.get_asset_path("clm_r1"),
            self.R2_SPC: self.root_scene.get_asset_path("clm_r2")
        },
        exp="im1b1==0?255:0",
        nodata=nodata
    )

Sentinel2Theia3ASource

Bases: Sentinel2TheiaGenericSource

Sentinel-2 level 3A source class

Source code in scenes/sentinel.py
class Sentinel2Theia3ASource(Sentinel2TheiaGenericSource):
    """Sentinel-2 level 3A source class"""

    def flg_msk_drilled(
            self,
            keep_flags_values: Tuple[int] = (3, 4),
            nodata: Union[float, int] = -10000
    ) -> Sentinel2Theia3ASource:
        """
        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

        """
        cond = "||".join([f"im1b1=={val}" for val in keep_flags_values])
        exp = cond + "?255:0"
        return self.msk_drilled(
            msk_dict={
                self.R1_SPC: self.root_scene.get_asset_path("flg_r1"),
                self.R2_SPC: self.root_scene.get_asset_path("flg_r2")
            },
            exp=exp,
            nodata=nodata
        )

flg_msk_drilled(keep_flags_values=(3, 4), nodata=-10000)

Return the source drilled from the FLG mask

Parameters:

Name Type Description Default
keep_flags_values Tuple[int]

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/)

(3, 4)
nodata Union[float, int]

nodata value inside holes (Default value = -10000)

-10000

Returns:

Type Description
Sentinel2Theia3ASource

drilled source

Source code in scenes/sentinel.py
def flg_msk_drilled(
        self,
        keep_flags_values: Tuple[int] = (3, 4),
        nodata: Union[float, int] = -10000
) -> Sentinel2Theia3ASource:
    """
    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

    """
    cond = "||".join([f"im1b1=={val}" for val in keep_flags_values])
    exp = cond + "?255:0"
    return self.msk_drilled(
        msk_dict={
            self.R1_SPC: self.root_scene.get_asset_path("flg_r1"),
            self.R2_SPC: self.root_scene.get_asset_path("flg_r2")
        },
        exp=exp,
        nodata=nodata
    )

Sentinel2TheiaGenericSource

Bases: CommonImagerySource

Class for generic Sentinel-2 sources

Source code in scenes/sentinel.py
class Sentinel2TheiaGenericSource(CommonImagerySource):
    """Class for generic Sentinel-2 sources"""
    R1_SPC = 10.0
    R2_SPC = 20.0

    def msk_drilled(
            self,
            msk_dict: Dict[float, str],
            exp: str,
            nodata: Union[float, int] = 0
    ) -> Sentinel2TheiaGenericSource:
        """
        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_spc = pyotb.ReadImageInfo(self)['spacingx']
        if img_spc not in msk_dict:
            raise KeyError(f"No mask for image spacing {img_spc}")
        binary_mask = pyotb.BandMath({"il": msk_dict[img_spc], "exp": exp})
        return self.masked(binary_mask=binary_mask, nodata=nodata)

msk_drilled(msk_dict, exp, nodata=0)

Parameters:

Name Type Description Default
msk_dict Dict[float, str]

dict of masks

required
exp str

bandmath expression to form the 0-255 binary mask

required
nodata Union[float, int]

no-data value in masked output (Default value = 0)

0

Returns:

Type Description
Sentinel2TheiaGenericSource

new masked source

Source code in scenes/sentinel.py
def msk_drilled(
        self,
        msk_dict: Dict[float, str],
        exp: str,
        nodata: Union[float, int] = 0
) -> Sentinel2TheiaGenericSource:
    """
    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_spc = pyotb.ReadImageInfo(self)['spacingx']
    if img_spc not in msk_dict:
        raise KeyError(f"No mask for image spacing {img_spc}")
    binary_mask = pyotb.BandMath({"il": msk_dict[img_spc], "exp": exp})
    return self.masked(binary_mask=binary_mask, nodata=nodata)

Sentinel2TheiaScene

Bases: Sentinel2SceneBase

Base class for Sentinel-2 images from Theia

Source code in scenes/sentinel.py
class Sentinel2TheiaScene(Sentinel2SceneBase):
    """Base class for Sentinel-2 images from Theia"""

    @abstractmethod
    def __init__(
            self,
            archive: str,
            tag: str,
            src_classes: Dict[str, Type[Source]]
    ):
        """
        Args:
            archive: product .zip or directory
            tag: pattern to match in filenames, e.g. "FRE"
            src_classes: imagery sources classes dict

        """
        self.archive = archive

        # Retrieve the list of .tif files
        is_zip = self.archive.lower().endswith(".zip")
        if is_zip:
            # print("Input type is a .zip archive")
            files = utils.list_files_in_zip(self.archive)
            self.files = [utils.to_vsizip(self.archive, f) for f in files]
        else:
            # print("Input type is a directory")
            self.files = utils.find_files_in_all_subdirs(
                self.archive, "*.tif", case_sensitive=False
            )

        # Assets
        assets_paths = {}

        # In Theia products, the suffixes are always uppercase (B4, ..., B8A)
        # so we need to retrieve the files from the uppercase key.

        # 1. Spectral bands
        for key in self.bands_keys:
            suffix = key.upper()  # e.g. b4 --> B4
            assets_paths[key] = self.get_file(endswith=f"_{tag}_{suffix}.tif")

        # 2. Other assets (quality masks, etc)
        for key in [key for key in src_classes if key not in assets_paths]:
            suffix = key.upper()  # e.g. cld_r1 --> CLD_R1
            assets_paths[key] = self.get_file(endswith=f"_{suffix}.tif")

        # Date, extent
        b2_path = assets_paths["b2"]
        epsg, extent = get_epsg_extent_wgs84(b2_path)
        # Basename, e.g. SENTINEL2A_20180630-105440-000_L2A_T31TEJ_D_V1-8
        b2_basepath = utils.basename(b2_path)
        datestr = b2_basepath.split("_")[1]  # e.g. 20180630-105440
        acquisition_date = datetime.strptime(datestr, '%Y%m%d-%H%M%S-%f')

        # Call parent constructor
        super().__init__(
            acquisition_date=acquisition_date,
            epsg=epsg,
            extent_wgs84=extent,
            assets_paths=assets_paths,
            src_classes=src_classes,
            additional_metadata={"archive": archive}
        )

    def get_file(self, endswith: str) -> str:
        """
        Return the specified file.

        Args:
            endswith: filtered extension

        Returns:
            the file

        """
        filtered_files_list = [f for f in self.files if f.endswith(endswith)]
        nb_matches = len(filtered_files_list)
        if nb_matches == 1:
            return filtered_files_list[0]
        print(
            "Warning: "
            f"{self.archive}: found {nb_matches} occurrence(s) of file "
            f"with suffix \"{endswith}\""
        )

__init__(archive, tag, src_classes) abstractmethod

Parameters:

Name Type Description Default
archive str

product .zip or directory

required
tag str

pattern to match in filenames, e.g. "FRE"

required
src_classes Dict[str, Type[Source]]

imagery sources classes dict

required
Source code in scenes/sentinel.py
@abstractmethod
def __init__(
        self,
        archive: str,
        tag: str,
        src_classes: Dict[str, Type[Source]]
):
    """
    Args:
        archive: product .zip or directory
        tag: pattern to match in filenames, e.g. "FRE"
        src_classes: imagery sources classes dict

    """
    self.archive = archive

    # Retrieve the list of .tif files
    is_zip = self.archive.lower().endswith(".zip")
    if is_zip:
        # print("Input type is a .zip archive")
        files = utils.list_files_in_zip(self.archive)
        self.files = [utils.to_vsizip(self.archive, f) for f in files]
    else:
        # print("Input type is a directory")
        self.files = utils.find_files_in_all_subdirs(
            self.archive, "*.tif", case_sensitive=False
        )

    # Assets
    assets_paths = {}

    # In Theia products, the suffixes are always uppercase (B4, ..., B8A)
    # so we need to retrieve the files from the uppercase key.

    # 1. Spectral bands
    for key in self.bands_keys:
        suffix = key.upper()  # e.g. b4 --> B4
        assets_paths[key] = self.get_file(endswith=f"_{tag}_{suffix}.tif")

    # 2. Other assets (quality masks, etc)
    for key in [key for key in src_classes if key not in assets_paths]:
        suffix = key.upper()  # e.g. cld_r1 --> CLD_R1
        assets_paths[key] = self.get_file(endswith=f"_{suffix}.tif")

    # Date, extent
    b2_path = assets_paths["b2"]
    epsg, extent = get_epsg_extent_wgs84(b2_path)
    # Basename, e.g. SENTINEL2A_20180630-105440-000_L2A_T31TEJ_D_V1-8
    b2_basepath = utils.basename(b2_path)
    datestr = b2_basepath.split("_")[1]  # e.g. 20180630-105440
    acquisition_date = datetime.strptime(datestr, '%Y%m%d-%H%M%S-%f')

    # Call parent constructor
    super().__init__(
        acquisition_date=acquisition_date,
        epsg=epsg,
        extent_wgs84=extent,
        assets_paths=assets_paths,
        src_classes=src_classes,
        additional_metadata={"archive": archive}
    )

get_file(endswith)

Return the specified file.

Parameters:

Name Type Description Default
endswith str

filtered extension

required

Returns:

Type Description
str

the file

Source code in scenes/sentinel.py
def get_file(self, endswith: str) -> str:
    """
    Return the specified file.

    Args:
        endswith: filtered extension

    Returns:
        the file

    """
    filtered_files_list = [f for f in self.files if f.endswith(endswith)]
    nb_matches = len(filtered_files_list)
    if nb_matches == 1:
        return filtered_files_list[0]
    print(
        "Warning: "
        f"{self.archive}: found {nb_matches} occurrence(s) of file "
        f"with suffix \"{endswith}\""
    )

get_local_scenes(root_dir, tile=None)

Retrieve the sentinel scenes in the directory

Parameters:

Name Type Description Default
root_dir str

directory

required
tile str

tile name (optional) e.g. 31TEJ

None

Returns:

Type Description
List[Union[Sentinel22AScene, Sentinel23AScene]]

a list of sentinel scenes instances

Source code in scenes/sentinel.py
def get_local_scenes(
        root_dir: str,
        tile: str = None
) -> List[Union[Sentinel22AScene, Sentinel23AScene]]:
    """
    Retrieve the sentinel scenes in the directory

    Args:
        root_dir: directory
        tile: tile name (optional) e.g. 31TEJ

    Returns:
        a list of sentinel scenes instances

    """
    scenes_list = []
    archives = utils.find_files_in_all_subdirs(
        pth=root_dir, pattern="*.zip", case_sensitive=False
    )
    for archive in tqdm(archives):
        candidate = get_scene(archive)
        if candidate:
            tile_name = archive.split("_")[3]
            if not tile or tile_name == tile:
                scenes_list.append(candidate)
    return scenes_list

get_scene(archive)

Return the right Scene instance from the given archive (L2A or L3A)

Parameters:

Name Type Description Default
archive str

L3A or L3A archive

required

Returns:

Type Description
Union[Sentinel22AScene, Sentinel23AScene]

a Sentinel23AScene or Sentinel22AScene instance

Source code in scenes/sentinel.py
def get_scene(archive: str) -> Union[Sentinel22AScene, Sentinel23AScene]:
    """
    Return the right `Scene` instance from the given archive (L2A or L3A)

    Args:
        archive: L3A or L3A archive

    Returns:
        a Sentinel23AScene or Sentinel22AScene instance

    """
    # todo: raise filenotfound if file/path not exist
    splits = utils.basename(archive).split("_")
    if len(splits) > 4:
        level = splits[2]
        if level == "L3A":
            return Sentinel23AScene(archive)
        if level == "L2A":
            return Sentinel22AScene(archive)
    print(f"Warning: file {archive} is not a valid Sentinel-2 product")
    return None