diff --git a/CHANGES.md b/CHANGES.md index 90d2503f..21abaf2f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,6 +16,7 @@ - FIX: Use the sun elevation angle rather than the sun zenith angle for STAC [#158](https://github.com/sertit/eoreader/issues/158) - FIX: Create comparison operators for `BandNames`, removing the `xarray RuntimeWarning` about `sort order is undefined for incomparable objects`. - FIX: Add some missing `@cache` around time-consuming functions +- FIX: Set correctly the SAR product type, with adding two types (`ORTHO` and `GEOCODED`) - OPTIM: Cache the access to any archived file list, as this operation is expensive when done with large archives stored on the cloud (and thus better done only once). - CI: Remove useless verbosity in CI - DOC: Update `conf.py` (remove useless hunks and set Sphinx 7 as base) diff --git a/eoreader/products/sar/capella_product.py b/eoreader/products/sar/capella_product.py index 2f4d2314..3a63f667 100644 --- a/eoreader/products/sar/capella_product.py +++ b/eoreader/products/sar/capella_product.py @@ -320,8 +320,12 @@ def _set_product_type(self) -> None: prod_type = self.split_name[3] self.product_type = getattr(CapellaProductType, prod_type) - if self.product_type in [CapellaProductType.GEO, CapellaProductType.GEC]: - self.sar_prod_type = SarProductType.GDRG + # WARNING: Capella don't seem to have Ground Range products... + + if self.product_type == CapellaProductType.GEO: + self.sar_prod_type = SarProductType.ORTHO + elif self.product_type == CapellaProductType.GEC: + self.sar_prod_type = SarProductType.GEOCODED elif self.product_type == CapellaProductType.SLC: self.sar_prod_type = SarProductType.CPLX else: diff --git a/eoreader/products/sar/cosmo_product.py b/eoreader/products/sar/cosmo_product.py index d5712255..a060a314 100644 --- a/eoreader/products/sar/cosmo_product.py +++ b/eoreader/products/sar/cosmo_product.py @@ -236,8 +236,12 @@ def _set_product_type(self) -> None: self.product_type = CosmoProductType.from_value(prod_type) - if self.product_type == CosmoProductType.DGM: - self.sar_prod_type = SarProductType.GDRG + if self.product_type == CosmoProductType.GTC: + self.sar_prod_type = SarProductType.ORTHO + elif self.product_type == CosmoProductType.GEC: + self.sar_prod_type = SarProductType.GEOCODED + elif self.product_type == CosmoProductType.DGM: + self.sar_prod_type = SarProductType.GRD elif self.product_type == CosmoProductType.SCS: self.sar_prod_type = SarProductType.CPLX else: diff --git a/eoreader/products/sar/iceye_product.py b/eoreader/products/sar/iceye_product.py index ee2b1145..40cf5f65 100644 --- a/eoreader/products/sar/iceye_product.py +++ b/eoreader/products/sar/iceye_product.py @@ -54,10 +54,6 @@ class IceyeProductType(ListEnum): SLC = "SLC" """ Level-1 Single Look Complex (SLC) """ - # Not released yet - # ORTHO = "ORTHO" - # """ ORTHO """ - @unique class IceyeSensorMode(ListEnum): @@ -201,7 +197,7 @@ def _set_product_type(self) -> None: self.product_type = IceyeProductType.from_value(prod_type) if self.product_type == IceyeProductType.GRD: - self.sar_prod_type = SarProductType.GDRG + self.sar_prod_type = SarProductType.GRD elif self.product_type == IceyeProductType.SLC: self.sar_prod_type = SarProductType.CPLX else: diff --git a/eoreader/products/sar/rcm_product.py b/eoreader/products/sar/rcm_product.py index a758e45e..dc61fa30 100644 --- a/eoreader/products/sar/rcm_product.py +++ b/eoreader/products/sar/rcm_product.py @@ -42,32 +42,43 @@ class RcmProductType(ListEnum): """ RADARSAT-Constellation projection identifier. - Take a look `here `_. + Take a look `here `_. """ SLC = "SLC" - """ Georeferenced. Single look complex data in slant range. """ + """ + SLC represents a SLant range, georeferenced Complex product. + It is equivalent to a single-look complex product for RADARSAT-1 or RADARSAT-2). + """ GRC = "GRC" - """ Georeferenced. Multilook complex data in ground range. """ + """ + GRC represents GRound range, georeferenced, Complex products. + """ GRD = "GRD" - """ Georeferenced. Multilook detected data in ground range. """ + """ + GRD represents GRound range, georeferenced, Detected. + GRD is equivalent to an SGX, SCN, or SCW product for RADARSAT-1 or RADARSAT-2. + """ GCC = "GCC" """ - Geocoded to a map projection. - Complex data. - Data commonly projected to Universal Transverse Mercator Projection (UTM) - or Universal Polar Stereographic (UPS) north of 84N or south of 80S. + GCC represents GeoCoded Complex products. """ GCD = "GCD" """ - Geocoded to a map projection. - Detected data. - Data commonly projected to Universal Transverse Mercator (UTM) - or Universal Polar Stereographic (UPS) north of 84N or south of 80S. + GCD represents GeoCoded Detected products. + GCD is equivalent to an SSG or SPG product for RADARSAT-1 or RADARSAT-2. + """ + + MLC = "MLC" + """ + MLC represent Multi-Look slant range Complex products. + It is for dual Co-/Cross-Polarization1 or Compact Polarization which is a spatially averaged version of the SLC product condensed to a single multi-burst image of the covariance matrix elements. + The covariance matrix elements consist of two real diagonal elements |xH|2 and |xV|2, and a complex off-diagonal element xH*conj(xV) where x is either Circular, + H or V depending on the SLC product polarization setting and conj denotes the complex conjugate. """ @@ -230,13 +241,11 @@ def _set_product_type(self) -> None: self.product_type = RcmProductType.from_value(prod_type) - if self.product_type in [RcmProductType.GRD, RcmProductType.GCD]: - self.sar_prod_type = SarProductType.GDRG - elif self.product_type in [ - RcmProductType.SLC, - RcmProductType.GRC, - RcmProductType.GCC, - ]: + if self.product_type in [RcmProductType.GRD, RcmProductType.GRC]: + self.sar_prod_type = SarProductType.GRD + elif self.product_type in [RcmProductType.GCD, RcmProductType.GCC]: + self.sar_prod_type = SarProductType.GEOCODED + elif self.product_type in [RcmProductType.SLC, RcmProductType.MLC]: self.sar_prod_type = SarProductType.CPLX else: raise NotImplementedError( diff --git a/eoreader/products/sar/rs2_product.py b/eoreader/products/sar/rs2_product.py index 2f452f96..b439ebbc 100644 --- a/eoreader/products/sar/rs2_product.py +++ b/eoreader/products/sar/rs2_product.py @@ -445,8 +445,12 @@ def _set_product_type(self) -> None: """Set products type""" if self.product_type == Rs2ProductType.SLC: self.sar_prod_type = SarProductType.CPLX + elif self.product_type in [Rs2ProductType.SGX, Rs2ProductType.SGF]: + self.sar_prod_type = SarProductType.GEOCODED + elif self.product_type in [Rs2ProductType.SSG, Rs2ProductType.SPG]: + self.sar_prod_type = SarProductType.ORTHO else: - self.sar_prod_type = SarProductType.GDRG + self.sar_prod_type = SarProductType.GRD if self.product_type not in [ Rs2ProductType.SGF, diff --git a/eoreader/products/sar/s1_product.py b/eoreader/products/sar/s1_product.py index 76ce438c..3211ba7e 100644 --- a/eoreader/products/sar/s1_product.py +++ b/eoreader/products/sar/s1_product.py @@ -203,12 +203,8 @@ def _pre_init(self, **kwargs) -> None: # Pre init done by the super class super()._pre_init(**kwargs) - # Check if COG in name - if ( - "_COG" in self.filename - and self._need_snap - and not self._has_snap_10_or_higher() - ): + # Check if COG in name (S1 needs always SNAP) + if "_COG" in self.filename and not self._has_snap_10_or_higher(): raise NotImplementedError( "S1 COG products are only handled by SNAP 10.0 or higher. " "Please upgrade your software to process this product." @@ -277,7 +273,7 @@ def _set_product_type(self) -> None: self.product_type = S1ProductType.from_value(prod_type) if self.product_type == S1ProductType.GRD: - self.sar_prod_type = SarProductType.GDRG + self.sar_prod_type = SarProductType.GRD elif self.product_type == S1ProductType.SLC: self.sar_prod_type = SarProductType.CPLX else: diff --git a/eoreader/products/sar/saocom_product.py b/eoreader/products/sar/saocom_product.py index d33b51f9..71e87917 100644 --- a/eoreader/products/sar/saocom_product.py +++ b/eoreader/products/sar/saocom_product.py @@ -321,12 +321,12 @@ def _set_product_type(self) -> None: self.product_type = SaocomProductType.from_value(prod_type) - if self.product_type in [ - SaocomProductType.DI, - SaocomProductType.GEC, - SaocomProductType.GTC, - ]: - self.sar_prod_type = SarProductType.GDRG + if self.product_type == SaocomProductType.DI: + self.sar_prod_type = SarProductType.GRD + elif self.product_type == SaocomProductType.GEC: + self.sar_prod_type = SarProductType.GEOCODED + elif self.product_type == SaocomProductType.GTC: + self.sar_prod_type = SarProductType.ORTHO elif self.product_type == SaocomProductType.SLC: self.sar_prod_type = SarProductType.CPLX else: diff --git a/eoreader/products/sar/sar_product.py b/eoreader/products/sar/sar_product.py index ccdf72c8..36b55489 100644 --- a/eoreader/products/sar/sar_product.py +++ b/eoreader/products/sar/sar_product.py @@ -115,14 +115,19 @@ class SarProductType(ListEnum): """ CPLX = "COMPLEX" - """Single Look Complex""" + """Complex image (most likely Single Look Complex)""" - GDRG = "GROUND" - """Ground Range""" + GRD = "GROUND RANGE" + """Ground Range image""" + + GEOCODED = "GEOCODED" + """Already geocoded image: don't need SNAP. To orthorectify it, you may need to take GCP by hand...""" + + ORTHO = "ORTHO" + """Already orthorecified image: don't need SNAP""" OTHER = "OTHER" - """Other products types, no used in EOReader""" - # Add ortho products ? + """Other products types, not used in EOReader""" class _ExtendedFormatter(Formatter): @@ -193,6 +198,9 @@ def __init__( # Initialization from the super class super().__init__(product_path, archive_path, output_path, remove_tmp, **kwargs) + self._need_snap = self._need_snap_to_pre_process() + self.is_ortho = self.sar_prod_type == SarProductType.ORTHO + def _map_bands(self) -> None: """ Map bands @@ -218,8 +226,6 @@ def _pre_init(self, **kwargs) -> None: self.tile_name = None self.sensor_type = SensorType.SAR self.bands = SarBandMap() - self.is_ortho = False - self._need_snap = self._need_snap_to_pre_process() def _post_init(self, **kwargs) -> None: """ @@ -258,14 +264,7 @@ def _get_predictor(self) -> int: def _need_snap_to_pre_process(self): """This product needs SNAP for pre-process.""" - raw_band_path = self.get_raw_band_paths() - - # Cannot use get_default_band here, because of recursion - def_key = list(raw_band_path.keys())[0] - with rasterio.open(raw_band_path[def_key]) as ds: - raw_crs = ds.crs - - need_snap = not (raw_crs is not None and raw_crs.is_projected) + need_snap = self.sar_prod_type in [SarProductType.CPLX, SarProductType.GRD] return need_snap @cache @@ -323,6 +322,7 @@ def get_default_band(self) -> BandNames: return default_band + @cache def get_default_band_path(self, **kwargs) -> AnyPathType: """ Get default band path (the first existing one between :code:`VV` and :code:`HH` for SAR data), ready to use (orthorectified) @@ -504,6 +504,7 @@ def get_raw_band_paths(self, **kwargs) -> dict: extended_fmt = _ExtendedFormatter() band_paths = {} for band in sab.speckle_list(): + LOGGER.debug(f"get RAW band path for {band.name}") band_regex = extended_fmt.format(self._raw_band_regex, band.value) if self.is_archived: @@ -530,6 +531,7 @@ def get_raw_band_paths(self, **kwargs) -> dict: return band_paths + @cache def _get_raw_bands(self) -> list: """ Return the existing band paths (as they come with th archived products). @@ -742,7 +744,7 @@ def _pre_process_sar(self, band: sab, pixel_size: float = None, **kwargs) -> str sat = "no_calib" else: sat = "sar" - spt = "grd" if self.sar_prod_type == SarProductType.GDRG else "cplx" + spt = "grd" if self.sar_prod_type == SarProductType.GRD else "cplx" pp_graph = utils.get_data_dir().joinpath( f"{spt}_{sat}_preprocess_default.xml" ) diff --git a/eoreader/products/sar/tsx_product.py b/eoreader/products/sar/tsx_product.py index b00b0c07..f6fe188c 100644 --- a/eoreader/products/sar/tsx_product.py +++ b/eoreader/products/sar/tsx_product.py @@ -400,12 +400,12 @@ def _set_product_type(self) -> None: self.product_type = TsxProductType.from_value(prod_type) - if self.product_type in [ - TsxProductType.MGD, - TsxProductType.GEC, - TsxProductType.EEC, - ]: - self.sar_prod_type = SarProductType.GDRG + if self.product_type == TsxProductType.MGD: + self.sar_prod_type = SarProductType.GRD + elif self.product_type == TsxProductType.GEC: + self.sar_prod_type = SarProductType.GEOCODED + elif self.product_type == TsxProductType.EEC: + self.sar_prod_type = SarProductType.ORTHO elif self.product_type == TsxProductType.SSC: self.sar_prod_type = SarProductType.CPLX else: