# -*- coding: utf-8 -*-
# Copyright 2022, SERTIT-ICube - France, https://sertit.unistra.fr/
# This file is part of eoreader project
# https://github.com/sertit/eoreader
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
""" Optical Bands """
# Lines too long
# pylint: disable=C0301
from collections.abc import MutableMapping
from enum import unique
from typing import Union
from sertit.misc import ListEnum
from eoreader.exceptions import InvalidTypeError
class _Bands(MutableMapping):
"""Super bands class, used as a dict"""
def __init__(self, *args, **kwargs):
self._band_map = dict()
self.update(dict(*args, **kwargs)) # use the free update to set keys
def __getitem__(self, key):
return self._band_map[key]
def __setitem__(self, key, value):
self._band_map[key] = value
def __delitem__(self, key):
del self._band_map[key]
def __iter__(self):
return iter(self._band_map)
def __len__(self):
return len(self._band_map)
def __str__(self):
return str(self._band_map)
[docs]class BandNames(ListEnum):
"""Super class for band names, **do not use it**."""
[docs] @classmethod
def from_list(cls, name_list: Union[list, str]) -> list:
"""
Get the band enums from list of band names
.. code-block:: python
>>> SarBandNames.from_list("VV")
[<SarBandNames.VV: 'VV'>]
Args:
name_list (Union[list, str]): List of names
Returns:
list: List of enums
"""
if not isinstance(name_list, list):
name_list = [name_list]
try:
band_names = [cls(name) for name in name_list]
except ValueError as ex:
raise InvalidTypeError(
f"Band names ({name_list}) should be chosen among: {cls.list_names()}"
) from ex
return band_names
[docs] @classmethod
def to_value_list(cls, name_list: list = None) -> list:
"""
Get a list from the values of the bands
.. code-block:: python
>>> SarBandNames.to_name_list([SarBandNames.HV_DSPK, SarBandNames.VV])
['HV_DSPK', 'VV']
>>> SarBandNames.to_name_list()
['VV', 'VV_DSPK', 'HH', 'HH_DSPK', 'VH', 'VH_DSPK', 'HV', 'HV_DSPK']
Args:
name_list (list): List of band names
Returns:
list: List of band values
"""
if name_list:
out_list = []
for key in name_list:
if isinstance(key, str):
out_list.append(getattr(cls, key).value)
elif isinstance(key, cls):
out_list.append(key.value)
else:
raise InvalidTypeError(
"The list should either contain strings or SarBandNames"
)
else:
out_list = cls.list_values()
return out_list
# ---------------------- SAR ----------------------
[docs]class SarBandNames(BandNames):
"""SAR Band names"""
VV = "VV"
""" Vertical Transmit-Vertical Receive Polarisation """
VV_DSPK = "VV_DSPK"
""" Vertical Transmit-Vertical Receive Polarisation (Despeckled) """
HH = "HH"
""" Horizontal Transmit-Horizontal Receive Polarisation """
HH_DSPK = "HH_DSPK"
""" Horizontal Transmit-Horizontal Receive Polarisation (Despeckled) """
VH = "VH"
""" Vertical Transmit-Horizontal Receive Polarisation """
VH_DSPK = "VH_DSPK"
""" Vertical Transmit-Horizontal Receive Polarisation (Despeckled) """
HV = "HV"
""" Horizontal Transmit-Vertical Receive Polarisation """
HV_DSPK = "HV_DSPK"
""" Horizontal Transmit-Vertical Receive Polarisation (Despeckled) """
RH = "RH"
""" Compact polarization: right circular transmit, horizontal receive """
RH_DSPK = "RH_DSPK"
""" Compact polarization: right circular transmit, horizontal receive """
RV = "RV"
""" Compact polarization: right circular transmit, vertical receive (Despeckled) """
RV_DSPK = "RV_DSPK"
""" Compact polarization: right circular transmit, horizontal receive (Despeckled) """
[docs] @classmethod
def corresponding_despeckle(cls, band: "SarBandNames"):
"""
Corresponding despeckled band.
.. code-block:: python
>>> SarBandNames.corresponding_despeckle(SarBandNames.VV)
<SarBandNames.VV_DSPK: 'VV_DSPK'>
>>> SarBandNames.corresponding_despeckle(SarBandNames.VV_DSPK)
<SarBandNames.VV_DSPK: 'VV_DSPK'>
Args:
band (SarBandNames): Noisy (speckle) band
Returns:
SarBandNames: Despeckled band
"""
if cls.is_despeckle(band):
dspk = band
else:
dspk = cls.from_value(f"{band.name}_DSPK")
return dspk
[docs] @classmethod
def corresponding_speckle(cls, band: "SarBandNames"):
"""
Corresponding speckle (noisy) band.
.. code-block:: python
>>> SarBandNames.corresponding_speckle(SarBandNames.VV)
<SarBandNames.VV: 'VV'>
>>> SarBandNames.corresponding_speckle(SarBandNames.VV_DSPK)
<SarBandNames.VV: 'VV'>
Args:
band (SarBandNames): Noisy (speckle) band
Returns:
SarBandNames: Despeckled band
"""
return cls.from_value(f"{band.name[:2]}")
[docs] @classmethod
def is_despeckle(cls, band: "SarBandNames"):
"""
Returns True if the band corresponds to a despeckled one.
.. code-block:: python
>>> SarBandNames.is_despeckle(SarBandNames.VV)
False
>>> SarBandNames.is_despeckle(SarBandNames.VV_DSPK)
True
Args:
band (SarBandNames): Band to test
Returns:
SarBandNames: Despeckled band
"""
return "DSPK" in band.name
# too many ancestors
# pylint: disable=R0901
[docs]class SarBands(_Bands):
"""SAR bands class"""
[docs] def __init__(self) -> None:
super().__init__({band_name: band_name.value for band_name in SarBandNames})
[docs] def map_bands(self, band_map: dict) -> None:
"""
Mapping band names to specific satellite band numbers, as strings.
.. code-block:: python
>>> # Example for Sentinel-2 L1C data
>>> sb = SarBands()
>>> sb.map_bands({
VV: 1,
})
Args:
band_map (dict): Band mapping as {SarBandNames: Band number for loading band}
"""
for band_name, band_nb in band_map.items():
if band_name not in self._band_map or not isinstance(
band_name, SarBandNames
):
raise InvalidTypeError(f"{band_name} should be an SarBandNames object")
# Set number
self._band_map[band_name] = band_nb
# ---------------------- OPTICAL ----------------------
[docs]class OpticalBandNames(BandNames):
"""
This class aims to regroup equivalent bands under the same nomenclature.
Each products will set their band number in regard to their corresponding name.
**Note**: The mapping is based on Sentinel-2 bands.
Satellites can have not mapped bands (such as Sentinel-3)
More information can be retrieved here:
- `Overall comparison <http://blog.imagico.de/wp-content/uploads/2016/11/sat_spectra_full4a.png>`_
- L8/S2:
- `Resource 1 <https://reader.elsevier.com/reader/sd/pii/S0034425718301883>`_
- `Resource 2 <https://landsat.gsfc.nasa.gov/wp-content/uploads/2015/06/Landsat.v.Sentinel-2.png>`_
- `L4/L5, MSS-TM <https://landsat.gsfc.nasa.gov/the-multispectral-scanner-system/>`_
- `All Landsats <https://landsat.gsfc.nasa.gov/wp-content/uploads/2016/10/all_Landsat_bands.png>`_
- `S2 <https://discovery.creodias.eu/dataset/72181b08-a577-4d55-8ece-d8485167beb7/resource/d8f5dd92-b35c-46ee-98a2-0879dad03fce/download/res_band_s2_1.png>`_
- `S3 OLCI <https://discovery.creodias.eu/dataset/a0960a9b-c9c4-46db-bca5-ec79d0dda32b/resource/de8300a4-08cd-41aa-96ec-d9813115cc08/download/s3_res_band_ol.png>`_
- `S3 SLSTR <https://discovery.creodias.eu/dataset/ea8f247e-d193-4368-8cf6-8687a03a5306/resource/8e5c485a-d832-42be-ad9c-af500b468f29/download/s3_slcs.png>`_
- `Index consistency <https://www.indexdatabase.de/>`_
This classification allows index computation and algorithms to run without knowing the band nb of every satellite.
If None, then the band does not exist for the satellite.
"""
CA = "COASTAL_AEROSOL"
"""Coastal aerosol"""
BLUE = "BLUE"
"""Blue"""
GREEN = "GREEN"
"""Green"""
YELLOW = "YELLOW"
"""Yellow"""
RED = "RED"
"""Red"""
VRE_1 = "VEGETATION_RED_EDGE_1"
"""Vegetation red edge, Band 1"""
VRE_2 = "VEGETATION_RED_EDGE_2"
"""Vegetation red edge, Band 2"""
VRE_3 = "VEGETATION_RED_EDGE_3"
"""Vegetation red edge, Band 3"""
NIR = "NIR"
"""NIR"""
NARROW_NIR = "NARROW_NIR"
"""Narrow NIR"""
WV = "WATER_VAPOUR"
"""Water vapour"""
SWIR_CIRRUS = "CIRRUS"
"""Cirrus"""
SWIR_1 = "SWIR_1"
"""SWIR, Band 1"""
SWIR_2 = "SWIR_2"
"""SWIR, Band 2"""
TIR_1 = "THERMAL_IR_1"
"""Thermal IR, Band 1"""
TIR_2 = "THERMAL_IR_2"
"""Thermal IR, Band 2"""
PAN = "PANCHROMATIC"
"""Panchromatic"""
# SLSTR additional band names
S7 = "S7"
"""
S7
"""
F1 = "F1"
"""
F1
"""
F2 = "F2"
"""
F2
"""
# OLCI additional band names
Oa01 = "Oa01"
"""
Oa01
"""
Oa02 = "Oa02"
"""
Oa02
"""
Oa05 = "Oa05"
"""
Oa05
"""
Oa09 = "Oa09"
"""
Oa09
"""
Oa10 = "Oa10"
"""
Oa10
"""
Oa13 = "Oa13"
"""
Oa13
"""
Oa14 = "Oa14"
"""
Oa14
"""
Oa15 = "Oa15"
"""
Oa15
"""
Oa18 = "Oa18"
"""
Oa18
"""
Oa19 = "Oa19"
"""
Oa01
"""
Oa21 = "Oa21"
"""
Oa01
"""
# too many ancestors
# pylint: disable=R0901
[docs]class OpticalBands(_Bands):
"""Optical bands class"""
[docs] def __init__(self) -> None:
super().__init__({band_name: None for band_name in OpticalBandNames})
[docs] def map_bands(self, band_map: dict) -> None:
"""
Mapping band names to specific satellite band numbers, as strings.
.. code-block:: python
>>> # Example for Sentinel-2 L1C data
>>> ob = OpticalBands()
>>> ob.map_bands({
CA: '01',
BLUE: '02',
GREEN: '03',
RED: '04',
VRE_1: '05',
VRE_2: '06',
VRE_3: '07',
NIR: '08',
NNIR: '8A',
WV: '09',
SWIR_1: '11',
SWIR_2: '12'
})
Args:
band_map (dict): Band mapping as {OpticalBandNames: Band number for loading band}
"""
for band_name, band_nb in band_map.items():
if band_name not in self._band_map or not isinstance(
band_name, OpticalBandNames
):
raise InvalidTypeError(
f"{band_name} should be an OpticalBandNames object"
)
# Set number
self._band_map[band_name] = band_nb
# ---------------------- DEM ----------------------
[docs]@unique
class DemBandNames(BandNames):
"""DEM Band names"""
DEM = "DEM"
""" DEM """
SLOPE = "SLOPE"
""" Slope """
HILLSHADE = "HILLSHADE"
""" Hillshade """
# too many ancestors
# pylint: disable=R0901
[docs]class DemBands(_Bands):
"""DEM bands class"""
[docs] def __init__(self) -> None:
super().__init__({band_name: band_name.value for band_name in DemBandNames})
# ---------------------- DEM ----------------------
[docs]@unique
class CloudsBandNames(BandNames):
"""Clouds Band names"""
RAW_CLOUDS = "RAW CLOUDS"
""" Raw cloud raster (can be either QA raster, rasterized cloud vectors...) """
CLOUDS = "CLOUDS"
""" Binary mask of clouds (High confidence) """
SHADOWS = "SHADOWS"
""" Binary mask of shadows (High confidence) """
CIRRUS = "CIRRUS"
""" Binary mask of cirrus (High confidence) """
ALL_CLOUDS = "ALL CLOUDS"
""" All clouds (Including all high confidence clouds, shadows and cirrus) """
# too many ancestors
# pylint: disable=R0901
[docs]class CloudsBands(_Bands):
"""Clouds bands class"""
[docs] def __init__(self) -> None:
super().__init__({band_name: band_name.value for band_name in CloudsBandNames})