From bddff400ced80af7950f438d3d5d32a035537da8 Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper Date: Fri, 8 May 2026 09:08:48 +0000 Subject: [PATCH 01/15] Deprecate sensor attribute in enhancements --- doc/source/enhancements.rst | 8 +++++--- satpy/enhancements/enhancer.py | 18 +++++++++++++++++- satpy/etc/enhancements/abi.yaml | 24 ++++++++++++------------ satpy/etc/enhancements/ahi.yaml | 4 ++-- satpy/etc/enhancements/amsr2.yaml | 24 ++++++++++++------------ 5 files changed, 48 insertions(+), 30 deletions(-) diff --git a/doc/source/enhancements.rst b/doc/source/enhancements.rst index 87cdecb23f..cb9b7e8f60 100644 --- a/doc/source/enhancements.rst +++ b/doc/source/enhancements.rst @@ -20,8 +20,10 @@ as well. See :ref:`component_configuration` for more information. Enhancements can be defined in a ``generic.yaml`` file that is always loaded for all data or in an instrument-specific file (e.g. ``seviri.yaml``) -corresponding to the ``.attrs["sensor"]`` metadata of the ``DataArray`` being -processed. Generic enhancements are loaded first followed by sensor-specific +corresponding to the ``.attrs["instruments"]`` metadata of the ``DataArray`` +being processed. For the filename, instruments are normalized using +:meth:`satpy._instruments.normalize_instrument_name`. +Generic enhancements are loaded first followed by instrument-specific enhancement files. Enhancement YAML Format @@ -95,7 +97,7 @@ implementation depends on the following keys: 1. ``name`` 2. ``reader`` 3. ``platform_name`` -4. ``sensor`` +4. ``instruments`` 5. ``standard_name`` 6. ``units`` diff --git a/satpy/enhancements/enhancer.py b/satpy/enhancements/enhancer.py index a26085c289..f43778ddb1 100644 --- a/satpy/enhancements/enhancer.py +++ b/satpy/enhancements/enhancer.py @@ -17,6 +17,7 @@ from __future__ import annotations import os +import warnings from pathlib import Path import yaml @@ -43,14 +44,29 @@ def __init__(self, *decision_dicts, **kwargs): "reader", "platform_name", "sensor", + "instruments", "standard_name", "units", )) self.prefix = kwargs.pop("config_section", "enhancements") - multival_keys = kwargs.pop("multival_keys", ["sensor"]) + multival_keys = kwargs.pop("multival_keys", ["sensor", "instruments"]) + self._check_deprecated_keys(match_keys, multival_keys) super(EnhancementDecisionTree, self).__init__( decision_dicts, match_keys, multival_keys) + def _check_deprecated_keys(self, match_keys, multival_keys): + user_provided_sensor = any( + "sensor" in keys + for keys in [match_keys, multival_keys] + ) + if user_provided_sensor: + warnings.warn( + "The 'sensor' attribute will be removed in v1.1. " + "Use 'instrument' instead.", + DeprecationWarning, + stacklevel=2 + ) + def add_config_to_tree(self, *decision_dict: str | Path | dict) -> None: """Add configuration to tree.""" conf: dict = {} diff --git a/satpy/etc/enhancements/abi.yaml b/satpy/etc/enhancements/abi.yaml index 9920f842a3..1986d8ddeb 100644 --- a/satpy/etc/enhancements/abi.yaml +++ b/satpy/etc/enhancements/abi.yaml @@ -1,7 +1,7 @@ enhancements: cimss_true_color: standard_name: cimss_true_color - sensor: abi + instrument: abi operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -35,7 +35,7 @@ enhancements: true_color_with_night_fires: standard_name: true_color_with_night_fires - sensor: abi + instrument: abi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -77,7 +77,7 @@ enhancements: ash_abi: ## RGB Ash recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/GOES_Ash_RGB.pdf standard_name: ash - sensor: abi + instrument: abi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -89,7 +89,7 @@ enhancements: dust_abi: ## RGB Dust recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/Dust_RGB_Quick_Guide.pdf standard_name: dust - sensor: abi + instrument: abi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -105,7 +105,7 @@ enhancements: convection_abi: ## RGB Convection recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/QuickGuide_GOESR_DayConvectionRGB_final.pdf standard_name: convection - sensor: abi + instrument: abi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -142,7 +142,7 @@ enhancements: night_microphysics_abi: ## RGB Nighttime Microphysics recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/QuickGuide_GOESR_NtMicroRGB_final.pdf standard_name: night_microphysics - sensor: abi + instrument: abi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -154,7 +154,7 @@ enhancements: night_microphysics_tropical_abi: ## RGB Nighttime Microphysics recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/QuickGuide_GOESR_NtMicroRGB_final.pdf standard_name: night_microphysics_tropical - sensor: abi + instrument: abi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -170,7 +170,7 @@ enhancements: land_cloud_fire: ## RGB Day Land Cloud Fire recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/QuickGuide_GOESR_DayLandCloudFireRGB_final.pdf standard_name: land_cloud_fire - sensor: abi + instrument: abi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -179,7 +179,7 @@ enhancements: land_cloud: ## RGB Day Land Cloud Fire recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/QuickGuide_GOESR_daylandcloudRGB_final.pdf standard_name: land_cloud - sensor: abi + instrument: abi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -191,7 +191,7 @@ enhancements: # IR with white clouds highlighted_brightness_temperature: standard_name: highlighted_toa_brightness_temperature - sensor: abi + instrument: abi operations: - name: btemp_threshold method: !!python/name:satpy.enhancements.contrast.btemp_threshold @@ -308,7 +308,7 @@ enhancements: ## RGB Recipe: https://rammb2.cira.colostate.edu/wp-content/uploads/2024/11/GOES-BlowingSnowRGB1_QuickGuide_24April2024.pdf ## Modified to match recommendations from RGB Workshop 2025 standard_name: day_blowing_snow - sensor: abi + instrument: abi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -323,7 +323,7 @@ enhancements: day_cloud_type: # Recipe PDF: http://cimss.ssec.wisc.edu/goes/OCLOFactSheetPDFs/ABIQuickGuide_Day_Cloud_Type_RGB.pdf standard_name: day_cloud_type - sensor: abi + instrument: abi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch diff --git a/satpy/etc/enhancements/ahi.yaml b/satpy/etc/enhancements/ahi.yaml index 5496be3cd8..cadd3ba863 100644 --- a/satpy/etc/enhancements/ahi.yaml +++ b/satpy/etc/enhancements/ahi.yaml @@ -12,7 +12,7 @@ enhancements: day_severe_storms: standard_name: day_severe_storms - sensor: ahi + instrument: ahi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -27,7 +27,7 @@ enhancements: night_microphysics_tropical: standard_name: night_microphysics_tropical - sensor: ahi + instrument: ahi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch diff --git a/satpy/etc/enhancements/amsr2.yaml b/satpy/etc/enhancements/amsr2.yaml index eb56f944ec..a1c860a4b3 100644 --- a/satpy/etc/enhancements/amsr2.yaml +++ b/satpy/etc/enhancements/amsr2.yaml @@ -3,28 +3,28 @@ enhancements: # https://www.ospo.noaa.gov/Products/atmosphere/gpds/maps.html?GPRR#gpdsMaps gaasp_clw: name: CLW - sensor: amsr2 + instrument: amsr2 operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 0.5} gaasp_sst: name: SST - sensor: amsr2 + instrument: amsr2 operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: -5.0, max_stretch: 35} gaasp_tpw: name: TPW - sensor: amsr2 + instrument: amsr2 operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 75.0} gaasp_wspd: name: WSPD - sensor: amsr2 + instrument: amsr2 operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -32,56 +32,56 @@ enhancements: # Snow_Cover unscaled (category product) gaasp_snow_depth: name: Snow_Depth - sensor: amsr2 + instrument: amsr2 operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 150.0} gaasp_swe: name: SWE - sensor: amsr2 + instrument: amsr2 operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 16.0} gaasp_soil_moisture: name: Soil_Moisture - sensor: amsr2 + instrument: amsr2 operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 100.0} gaasp_ice_concentration_nh: name: NASA_Team_2_Ice_Concentration_NH - sensor: amsr2 + instrument: amsr2 operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 100.0} gaasp_ice_concentration_sh: name: NASA_Team_2_Ice_Concentration_SH - sensor: amsr2 + instrument: amsr2 operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 100.0} # gaasp_latency_nh: # name: Latency_NH -# sensor: amsr2 +# instrument: amsr2 # operations: # - name: linear_stretch # method: !!python/name:satpy.enhancements.contrast.stretch # kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 100.0} # gaasp_latency_sh: # name: Latency_SH -# sensor: amsr2 +# instrument: amsr2 # operations: # - name: linear_stretch # method: !!python/name:satpy.enhancements.contrast.stretch # kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 100.0} gaasp_rain_rate: name: Rain_Rate - sensor: amsr2 + instrument: amsr2 operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch From 1ffe5b0a1c7699bba84f69cd8c8433199bc8009d Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper Date: Mon, 11 May 2026 15:08:50 +0000 Subject: [PATCH 02/15] Ensure compatibility in enhancement decision tree --- satpy/enhancements/enhancer.py | 31 +++++++++++++++---------------- satpy/etc/enhancements/abi.yaml | 24 ++++++++++++------------ satpy/etc/enhancements/ahi.yaml | 4 ++-- satpy/etc/enhancements/amsr2.yaml | 24 ++++++++++++------------ 4 files changed, 41 insertions(+), 42 deletions(-) diff --git a/satpy/enhancements/enhancer.py b/satpy/enhancements/enhancer.py index f43778ddb1..7a76f70a19 100644 --- a/satpy/enhancements/enhancer.py +++ b/satpy/enhancements/enhancer.py @@ -43,36 +43,22 @@ def __init__(self, *decision_dicts, **kwargs): ("name", "reader", "platform_name", - "sensor", "instruments", "standard_name", "units", )) self.prefix = kwargs.pop("config_section", "enhancements") - multival_keys = kwargs.pop("multival_keys", ["sensor", "instruments"]) - self._check_deprecated_keys(match_keys, multival_keys) + multival_keys = kwargs.pop("multival_keys", ["instruments"]) super(EnhancementDecisionTree, self).__init__( decision_dicts, match_keys, multival_keys) - def _check_deprecated_keys(self, match_keys, multival_keys): - user_provided_sensor = any( - "sensor" in keys - for keys in [match_keys, multival_keys] - ) - if user_provided_sensor: - warnings.warn( - "The 'sensor' attribute will be removed in v1.1. " - "Use 'instrument' instead.", - DeprecationWarning, - stacklevel=2 - ) - def add_config_to_tree(self, *decision_dict: str | Path | dict) -> None: """Add configuration to tree.""" conf: dict = {} for config_file in decision_dict: config_dict = self._get_config_dict_from_user(config_file) recursive_dict_update(conf, config_dict) + self._ensure_compat(conf) self._build_tree(conf) def _get_config_dict_from_user(self, config_file: str | Path | dict) -> dict: @@ -103,6 +89,18 @@ def _get_yaml_enhancement_dict(self, config_file: str | Path) -> dict: LOG.debug(f"Adding enhancement configuration from file: {config_file}") return enhancement_section + def _ensure_compat(self, config_dict: dict) -> None: + for enh_name, enh_config in config_dict.items(): + if "sensor" in enh_config: + warnings.warn( + "Renaming the 'sensor' attribute from enhancement YAML file " + "to 'instruments'. This will raise an exception in Satpy " + "v1.1 when the 'sensor' attribute will be removed.", + DeprecationWarning, + stacklevel=3 + ) + instru.set_instruments_attr(config_dict[enh_name], enh_config["sensor"]) + def find_match(self, **query_dict): """Find a match.""" try: @@ -113,6 +111,7 @@ def find_match(self, **query_dict): (query_dict.get("uid", None),)) + class Enhancer: """Helper class to get enhancement information for images.""" diff --git a/satpy/etc/enhancements/abi.yaml b/satpy/etc/enhancements/abi.yaml index 1986d8ddeb..cc7d2af42e 100644 --- a/satpy/etc/enhancements/abi.yaml +++ b/satpy/etc/enhancements/abi.yaml @@ -1,7 +1,7 @@ enhancements: cimss_true_color: standard_name: cimss_true_color - instrument: abi + instruments: [abi] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -35,7 +35,7 @@ enhancements: true_color_with_night_fires: standard_name: true_color_with_night_fires - instrument: abi + sensor: [abi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -77,7 +77,7 @@ enhancements: ash_abi: ## RGB Ash recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/GOES_Ash_RGB.pdf standard_name: ash - instrument: abi + instruments: [abi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -89,7 +89,7 @@ enhancements: dust_abi: ## RGB Dust recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/Dust_RGB_Quick_Guide.pdf standard_name: dust - instrument: abi + instruments: [abi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -105,7 +105,7 @@ enhancements: convection_abi: ## RGB Convection recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/QuickGuide_GOESR_DayConvectionRGB_final.pdf standard_name: convection - instrument: abi + instruments: [abi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -142,7 +142,7 @@ enhancements: night_microphysics_abi: ## RGB Nighttime Microphysics recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/QuickGuide_GOESR_NtMicroRGB_final.pdf standard_name: night_microphysics - instrument: abi + instruments: [abi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -154,7 +154,7 @@ enhancements: night_microphysics_tropical_abi: ## RGB Nighttime Microphysics recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/QuickGuide_GOESR_NtMicroRGB_final.pdf standard_name: night_microphysics_tropical - instrument: abi + instruments: [abi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -170,7 +170,7 @@ enhancements: land_cloud_fire: ## RGB Day Land Cloud Fire recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/QuickGuide_GOESR_DayLandCloudFireRGB_final.pdf standard_name: land_cloud_fire - instrument: abi + instruments: [abi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -179,7 +179,7 @@ enhancements: land_cloud: ## RGB Day Land Cloud Fire recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/QuickGuide_GOESR_daylandcloudRGB_final.pdf standard_name: land_cloud - instrument: abi + instruments: [abi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -191,7 +191,7 @@ enhancements: # IR with white clouds highlighted_brightness_temperature: standard_name: highlighted_toa_brightness_temperature - instrument: abi + instruments: [abi] operations: - name: btemp_threshold method: !!python/name:satpy.enhancements.contrast.btemp_threshold @@ -308,7 +308,7 @@ enhancements: ## RGB Recipe: https://rammb2.cira.colostate.edu/wp-content/uploads/2024/11/GOES-BlowingSnowRGB1_QuickGuide_24April2024.pdf ## Modified to match recommendations from RGB Workshop 2025 standard_name: day_blowing_snow - instrument: abi + instruments: [abi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -323,7 +323,7 @@ enhancements: day_cloud_type: # Recipe PDF: http://cimss.ssec.wisc.edu/goes/OCLOFactSheetPDFs/ABIQuickGuide_Day_Cloud_Type_RGB.pdf standard_name: day_cloud_type - instrument: abi + instruments: [abi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch diff --git a/satpy/etc/enhancements/ahi.yaml b/satpy/etc/enhancements/ahi.yaml index cadd3ba863..3f73b9c1a4 100644 --- a/satpy/etc/enhancements/ahi.yaml +++ b/satpy/etc/enhancements/ahi.yaml @@ -12,7 +12,7 @@ enhancements: day_severe_storms: standard_name: day_severe_storms - instrument: ahi + instruments: [ahi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -27,7 +27,7 @@ enhancements: night_microphysics_tropical: standard_name: night_microphysics_tropical - instrument: ahi + instruments: [ahi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch diff --git a/satpy/etc/enhancements/amsr2.yaml b/satpy/etc/enhancements/amsr2.yaml index a1c860a4b3..788b52cc2c 100644 --- a/satpy/etc/enhancements/amsr2.yaml +++ b/satpy/etc/enhancements/amsr2.yaml @@ -3,28 +3,28 @@ enhancements: # https://www.ospo.noaa.gov/Products/atmosphere/gpds/maps.html?GPRR#gpdsMaps gaasp_clw: name: CLW - instrument: amsr2 + instruments: [amsr2] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 0.5} gaasp_sst: name: SST - instrument: amsr2 + instruments: [amsr2] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: -5.0, max_stretch: 35} gaasp_tpw: name: TPW - instrument: amsr2 + instruments: [amsr2] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 75.0} gaasp_wspd: name: WSPD - instrument: amsr2 + instruments: [amsr2] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -32,56 +32,56 @@ enhancements: # Snow_Cover unscaled (category product) gaasp_snow_depth: name: Snow_Depth - instrument: amsr2 + instruments: [amsr2] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 150.0} gaasp_swe: name: SWE - instrument: amsr2 + instruments: [amsr2] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 16.0} gaasp_soil_moisture: name: Soil_Moisture - instrument: amsr2 + instruments: [amsr2] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 100.0} gaasp_ice_concentration_nh: name: NASA_Team_2_Ice_Concentration_NH - instrument: amsr2 + instruments: [amsr2] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 100.0} gaasp_ice_concentration_sh: name: NASA_Team_2_Ice_Concentration_SH - instrument: amsr2 + instruments: [amsr2] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 100.0} # gaasp_latency_nh: # name: Latency_NH -# instrument: amsr2 +# instruments: [amsr2] # operations: # - name: linear_stretch # method: !!python/name:satpy.enhancements.contrast.stretch # kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 100.0} # gaasp_latency_sh: # name: Latency_SH -# instrument: amsr2 +# instruments: [amsr2] # operations: # - name: linear_stretch # method: !!python/name:satpy.enhancements.contrast.stretch # kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 100.0} gaasp_rain_rate: name: Rain_Rate - instrument: amsr2 + instruments: [amsr2] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch From 184d538ef388c52137119844090be1becfa783b1 Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper <1991007+sfinkens@users.noreply.github.com> Date: Mon, 11 May 2026 17:11:26 +0200 Subject: [PATCH 03/15] Update doc/source/enhancements.rst Co-authored-by: David Hoese --- doc/source/enhancements.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/enhancements.rst b/doc/source/enhancements.rst index cb9b7e8f60..2892b0ef73 100644 --- a/doc/source/enhancements.rst +++ b/doc/source/enhancements.rst @@ -97,7 +97,7 @@ implementation depends on the following keys: 1. ``name`` 2. ``reader`` 3. ``platform_name`` -4. ``instruments`` +4. ``instruments`` (previously ``sensor`` in Satpy <1.0) 5. ``standard_name`` 6. ``units`` From f4bcd2fa55f679962b0726395e872f58277f0ce8 Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper Date: Mon, 11 May 2026 15:18:03 +0000 Subject: [PATCH 04/15] Improve deprecation warning message --- satpy/enhancements/enhancer.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/satpy/enhancements/enhancer.py b/satpy/enhancements/enhancer.py index 7a76f70a19..5ff97445c0 100644 --- a/satpy/enhancements/enhancer.py +++ b/satpy/enhancements/enhancer.py @@ -93,13 +93,16 @@ def _ensure_compat(self, config_dict: dict) -> None: for enh_name, enh_config in config_dict.items(): if "sensor" in enh_config: warnings.warn( - "Renaming the 'sensor' attribute from enhancement YAML file " - "to 'instruments'. This will raise an exception in Satpy " - "v1.1 when the 'sensor' attribute will be removed.", + "Renaming the 'sensor' enhancement attribute to 'instruments'. " + "This will raise an exception in Satpy v1.1 when the 'sensor' " + "attribute will be removed. To silence this warning, change " + "'sensor' to 'instruments' (type list) in your enhancement " + "YAML file.", DeprecationWarning, stacklevel=3 ) - instru.set_instruments_attr(config_dict[enh_name], enh_config["sensor"]) + instruments = instru.get_instruments_from_attrs(enh_config) + instru.set_instruments_attr(config_dict[enh_name], instruments) def find_match(self, **query_dict): """Find a match.""" From 91b753bece2f767fe4efc8c2503a92cc833b8a1b Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper Date: Mon, 11 May 2026 15:57:24 +0000 Subject: [PATCH 05/15] Keep single instruments as string instead of set --- satpy/enhancements/enhancer.py | 10 +++---- satpy/etc/enhancements/abi.yaml | 24 +++++++-------- satpy/etc/enhancements/ahi.yaml | 4 +-- satpy/etc/enhancements/amsr2.yaml | 26 ++++++++-------- .../tests/enhancement_tests/test_enhancer.py | 30 +++++++++---------- 5 files changed, 47 insertions(+), 47 deletions(-) diff --git a/satpy/enhancements/enhancer.py b/satpy/enhancements/enhancer.py index 5ff97445c0..52c6a7eb9e 100644 --- a/satpy/enhancements/enhancer.py +++ b/satpy/enhancements/enhancer.py @@ -95,14 +95,14 @@ def _ensure_compat(self, config_dict: dict) -> None: warnings.warn( "Renaming the 'sensor' enhancement attribute to 'instruments'. " "This will raise an exception in Satpy v1.1 when the 'sensor' " - "attribute will be removed. To silence this warning, change " - "'sensor' to 'instruments' (type list) in your enhancement " - "YAML file.", + "attribute will be removed. To silence this warning, rename " + "'sensor' to 'instruments' in your enhancement YAML file.", DeprecationWarning, stacklevel=3 ) - instruments = instru.get_instruments_from_attrs(enh_config) - instru.set_instruments_attr(config_dict[enh_name], instruments) + # Not using set_instruments_attr() here to keep the order. + instr_key = instru.get_instruments_key() + config_dict[enh_name][instr_key] = enh_config["sensor"] def find_match(self, **query_dict): """Find a match.""" diff --git a/satpy/etc/enhancements/abi.yaml b/satpy/etc/enhancements/abi.yaml index cc7d2af42e..7952c30a83 100644 --- a/satpy/etc/enhancements/abi.yaml +++ b/satpy/etc/enhancements/abi.yaml @@ -1,7 +1,7 @@ enhancements: cimss_true_color: standard_name: cimss_true_color - instruments: [abi] + instruments: abi operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -35,7 +35,7 @@ enhancements: true_color_with_night_fires: standard_name: true_color_with_night_fires - sensor: [abi] + instruments: abi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -77,7 +77,7 @@ enhancements: ash_abi: ## RGB Ash recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/GOES_Ash_RGB.pdf standard_name: ash - instruments: [abi] + instruments: abi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -89,7 +89,7 @@ enhancements: dust_abi: ## RGB Dust recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/Dust_RGB_Quick_Guide.pdf standard_name: dust - instruments: [abi] + instruments: abi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -105,7 +105,7 @@ enhancements: convection_abi: ## RGB Convection recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/QuickGuide_GOESR_DayConvectionRGB_final.pdf standard_name: convection - instruments: [abi] + instruments: abi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -142,7 +142,7 @@ enhancements: night_microphysics_abi: ## RGB Nighttime Microphysics recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/QuickGuide_GOESR_NtMicroRGB_final.pdf standard_name: night_microphysics - instruments: [abi] + instruments: abi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -154,7 +154,7 @@ enhancements: night_microphysics_tropical_abi: ## RGB Nighttime Microphysics recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/QuickGuide_GOESR_NtMicroRGB_final.pdf standard_name: night_microphysics_tropical - instruments: [abi] + instruments: abi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -170,7 +170,7 @@ enhancements: land_cloud_fire: ## RGB Day Land Cloud Fire recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/QuickGuide_GOESR_DayLandCloudFireRGB_final.pdf standard_name: land_cloud_fire - instruments: [abi] + instruments: abi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -179,7 +179,7 @@ enhancements: land_cloud: ## RGB Day Land Cloud Fire recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/QuickGuide_GOESR_daylandcloudRGB_final.pdf standard_name: land_cloud - instruments: [abi] + instruments: abi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -191,7 +191,7 @@ enhancements: # IR with white clouds highlighted_brightness_temperature: standard_name: highlighted_toa_brightness_temperature - instruments: [abi] + instruments: abi operations: - name: btemp_threshold method: !!python/name:satpy.enhancements.contrast.btemp_threshold @@ -308,7 +308,7 @@ enhancements: ## RGB Recipe: https://rammb2.cira.colostate.edu/wp-content/uploads/2024/11/GOES-BlowingSnowRGB1_QuickGuide_24April2024.pdf ## Modified to match recommendations from RGB Workshop 2025 standard_name: day_blowing_snow - instruments: [abi] + instruments: abi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -323,7 +323,7 @@ enhancements: day_cloud_type: # Recipe PDF: http://cimss.ssec.wisc.edu/goes/OCLOFactSheetPDFs/ABIQuickGuide_Day_Cloud_Type_RGB.pdf standard_name: day_cloud_type - instruments: [abi] + instruments: abi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch diff --git a/satpy/etc/enhancements/ahi.yaml b/satpy/etc/enhancements/ahi.yaml index 3f73b9c1a4..ce97bca324 100644 --- a/satpy/etc/enhancements/ahi.yaml +++ b/satpy/etc/enhancements/ahi.yaml @@ -12,7 +12,7 @@ enhancements: day_severe_storms: standard_name: day_severe_storms - instruments: [ahi] + instruments: ahi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -27,7 +27,7 @@ enhancements: night_microphysics_tropical: standard_name: night_microphysics_tropical - instruments: [ahi] + instruments: ahi operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch diff --git a/satpy/etc/enhancements/amsr2.yaml b/satpy/etc/enhancements/amsr2.yaml index 788b52cc2c..023b71420e 100644 --- a/satpy/etc/enhancements/amsr2.yaml +++ b/satpy/etc/enhancements/amsr2.yaml @@ -1,30 +1,30 @@ -enhancements: +abiamsr2enhancements: # GAASP enhancements based on PNGs at: # https://www.ospo.noaa.gov/Products/atmosphere/gpds/maps.html?GPRR#gpdsMaps gaasp_clw: name: CLW - instruments: [amsr2] + instruments: amsr2 operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 0.5} gaasp_sst: name: SST - instruments: [amsr2] + instruments: amsr2 operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: -5.0, max_stretch: 35} gaasp_tpw: name: TPW - instruments: [amsr2] + instruments: amsr2 operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 75.0} gaasp_wspd: name: WSPD - instruments: [amsr2] + instruments: amsr2 operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -32,56 +32,56 @@ enhancements: # Snow_Cover unscaled (category product) gaasp_snow_depth: name: Snow_Depth - instruments: [amsr2] + instruments: amsr2 operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 150.0} gaasp_swe: name: SWE - instruments: [amsr2] + instruments: amsr2 operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 16.0} gaasp_soil_moisture: name: Soil_Moisture - instruments: [amsr2] + instruments: amsr2 operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 100.0} gaasp_ice_concentration_nh: name: NASA_Team_2_Ice_Concentration_NH - instruments: [amsr2] + instruments: amsr2 operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 100.0} gaasp_ice_concentration_sh: name: NASA_Team_2_Ice_Concentration_SH - instruments: [amsr2] + instruments: amsr2 operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 100.0} # gaasp_latency_nh: # name: Latency_NH -# instruments: [amsr2] +# instruments: amsr2 # operations: # - name: linear_stretch # method: !!python/name:satpy.enhancements.contrast.stretch # kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 100.0} # gaasp_latency_sh: # name: Latency_SH -# instruments: [amsr2] +# instruments: amsr2 # operations: # - name: linear_stretch # method: !!python/name:satpy.enhancements.contrast.stretch # kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 100.0} gaasp_rain_rate: name: Rain_Rate - instruments: [amsr2] + instruments: amsr2 operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch diff --git a/satpy/tests/enhancement_tests/test_enhancer.py b/satpy/tests/enhancement_tests/test_enhancer.py index c175f11a9b..bcb1504058 100644 --- a/satpy/tests/enhancement_tests/test_enhancer.py +++ b/satpy/tests/enhancement_tests/test_enhancer.py @@ -144,7 +144,7 @@ class TestComplexSensorEnhancerConfigs(_BaseCustomEnhancementConfigTests): enhancements: test1_sensor1_specific: name: test1 - sensor: test_sensor1 + instruments: test_sensor1 operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -160,14 +160,14 @@ class TestComplexSensorEnhancerConfigs(_BaseCustomEnhancementConfigTests): kwargs: {stretch: crude, min_stretch: 0, max_stretch: 100} test1_sensor2_specific: name: test1 - sensor: test_sensor2 + instruments: test_sensor2 operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: crude, min_stretch: 0, max_stretch: 50} exact_multisensor_comp: name: my_comp - sensor: [test_sensor1, test_sensor2] + instruments: [test_sensor1, test_sensor2] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -183,7 +183,7 @@ def test_multisensor_choice(self, test_configs_path): ds = DataArray(np.arange(1, 11.).reshape((2, 5)), attrs={ "name": "test1", - "sensor": {"test_sensor2", "test_sensor1"}, + "instruments": {"test_sensor2", "test_sensor1"}, "mode": "L" }, dims=["y", "x"]) @@ -206,7 +206,7 @@ def test_multisensor_exact(self, test_configs_path): ds = DataArray(np.arange(1, 11.).reshape((2, 5)), attrs={ "name": "my_comp", - "sensor": {"test_sensor2", "test_sensor1"}, + "instruments": {"test_sensor2", "test_sensor1"}, "mode": "L" }, dims=["y", "x"]) @@ -227,7 +227,7 @@ def test_enhance_bad_query_value(self): from satpy.enhancements.enhancer import Enhancer, get_enhanced_image ds = DataArray(np.arange(1, 11.).reshape((2, 5)), - attrs=dict(name=["I", "am", "invalid"], sensor="test_sensor2", mode="L"), + attrs=dict(name=["I", "am", "invalid"], instruments={"test_sensor2"}, mode="L"), dims=["y", "x"]) e = Enhancer() assert e.enhancement_tree is not None @@ -282,7 +282,7 @@ def test_enhance_empty_config(self, test_configs_path): from satpy.enhancements.enhancer import Enhancer, get_enhanced_image ds = DataArray(np.arange(1, 11.).reshape((2, 5)), - attrs=dict(sensor="test_empty", mode="L"), + attrs=dict(instruments={"test_empty"}, mode="L"), dims=["y", "x"]) e = Enhancer() assert e.enhancement_tree is not None @@ -296,7 +296,7 @@ def test_enhance_with_sensor_no_entry(self, test_configs_path): from satpy.enhancements.enhancer import Enhancer, get_enhanced_image ds = DataArray(np.arange(1, 11.).reshape((2, 5)), - attrs=dict(sensor="test_sensor2", mode="L"), + attrs=dict(instruments={"test_sensor2"}, mode="L"), dims=["y", "x"]) e = Enhancer() assert e.enhancement_tree is not None @@ -311,7 +311,7 @@ def test_no_enhance(self): from satpy.enhancements.enhancer import get_enhanced_image ds = DataArray(np.arange(1, 11.).reshape((2, 5)), - attrs=dict(name="test1", sensor="test_sensor", mode="L"), + attrs=dict(name="test1", instruments={"test_sensor"}, mode="L"), dims=["y", "x"]) img = get_enhanced_image(ds, enhance=False) np.testing.assert_allclose(img.data.data.compute().squeeze(), ds.data) @@ -320,7 +320,7 @@ def test_writer_no_enhance(self): """Test turning off enhancements with writer.""" from xarray import DataArray ds = DataArray(np.arange(1, 11.).reshape((2, 5)), - attrs=dict(name="test1", sensor="test_sensor", mode="L"), + attrs=dict(name="test1", instruments={"test_sensor"}, mode="L"), dims=["y", "x"]) writer = _CustomImageWriter(enhance=False) writer.save_datasets((ds,), compute=False) @@ -333,7 +333,7 @@ def test_writer_custom_enhance(self): from satpy.enhancements.enhancer import Enhancer ds = DataArray(np.arange(1, 11.).reshape((2, 5)), - attrs=dict(name="test1", sensor="test_sensor", mode="L"), + attrs=dict(name="test1", instruments={"test_sensor"}, mode="L"), dims=["y", "x"]) enhance = Enhancer() writer = _CustomImageWriter(enhance=enhance) @@ -347,7 +347,7 @@ def test_enhance_with_sensor_entry(self, test_configs_path): from satpy.enhancements.enhancer import Enhancer, get_enhanced_image ds = DataArray(np.arange(1, 11.).reshape((2, 5)), - attrs=dict(name="test1", sensor="test_sensor", mode="L"), + attrs=dict(name="test1", instruments={"test_sensor"}, mode="L"), dims=["y", "x"]) e = Enhancer() assert e.enhancement_tree is not None @@ -359,7 +359,7 @@ def test_enhance_with_sensor_entry(self, test_configs_path): 1.) ds = DataArray(da.arange(1, 11., chunks=5).reshape((2, 5)), - attrs=dict(name="test1", sensor="test_sensor", mode="L"), + attrs=dict(name="test1", instruments={"test_sensor"}, mode="L"), dims=["y", "x"]) e = Enhancer() assert e.enhancement_tree is not None @@ -376,7 +376,7 @@ def test_enhance_with_sensor_entry2(self, test_configs_path): from satpy.enhancements.enhancer import Enhancer, get_enhanced_image ds = DataArray(np.arange(1, 11.).reshape((2, 5)), attrs=dict(name="test1", units="kelvin", - sensor="test_sensor", mode="L"), + instruments={"test_sensor"}, mode="L"), dims=["y", "x"]) e = Enhancer() assert e.enhancement_tree is not None @@ -432,7 +432,7 @@ def _get_test_data_array(self): ds = DataArray(np.arange(1, 11.).reshape((2, 5)), attrs={ "name": "test1", - "sensor": "test_sensor1", + "instruments": {"test_sensor1"}, "mode": "L", }, dims=["y", "x"]) From 2f69a1e82345b452e9565af437d7db689f1e1d89 Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper Date: Mon, 11 May 2026 16:37:27 +0000 Subject: [PATCH 06/15] Use instrument attribute setter --- satpy/enhancements/enhancer.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/satpy/enhancements/enhancer.py b/satpy/enhancements/enhancer.py index 52c6a7eb9e..5f46c9ba84 100644 --- a/satpy/enhancements/enhancer.py +++ b/satpy/enhancements/enhancer.py @@ -100,9 +100,7 @@ def _ensure_compat(self, config_dict: dict) -> None: DeprecationWarning, stacklevel=3 ) - # Not using set_instruments_attr() here to keep the order. - instr_key = instru.get_instruments_key() - config_dict[enh_name][instr_key] = enh_config["sensor"] + instru.set_instruments_attr(config_dict[enh_name], enh_config["sensor"]) def find_match(self, **query_dict): """Find a match.""" From 287a8ab95f32c67ecdedec3537c2fa7ea77b4a34 Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper Date: Mon, 11 May 2026 16:45:11 +0000 Subject: [PATCH 07/15] Fix typo --- satpy/etc/enhancements/amsr2.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/satpy/etc/enhancements/amsr2.yaml b/satpy/etc/enhancements/amsr2.yaml index 023b71420e..4a0ac05412 100644 --- a/satpy/etc/enhancements/amsr2.yaml +++ b/satpy/etc/enhancements/amsr2.yaml @@ -1,4 +1,4 @@ -abiamsr2enhancements: +enhancements: # GAASP enhancements based on PNGs at: # https://www.ospo.noaa.gov/Products/atmosphere/gpds/maps.html?GPRR#gpdsMaps gaasp_clw: From 806169814b176c5cd34d061ac1da658b2d8f4863 Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper Date: Mon, 11 May 2026 16:46:02 +0000 Subject: [PATCH 08/15] Fix whitespace --- satpy/enhancements/enhancer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/satpy/enhancements/enhancer.py b/satpy/enhancements/enhancer.py index 5f46c9ba84..ac6f1cde13 100644 --- a/satpy/enhancements/enhancer.py +++ b/satpy/enhancements/enhancer.py @@ -112,7 +112,6 @@ def find_match(self, **query_dict): (query_dict.get("uid", None),)) - class Enhancer: """Helper class to get enhancement information for images.""" From c859cd8e03eb48187ed5bd3983725823c2870f61 Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper Date: Tue, 19 May 2026 09:01:38 +0000 Subject: [PATCH 09/15] Use lists for instruments in enhancements --- satpy/decision_tree.py | 19 ++++++++++++--- satpy/etc/enhancements/abi.yaml | 24 +++++++++---------- satpy/etc/enhancements/ahi.yaml | 4 ++-- satpy/etc/enhancements/amsr2.yaml | 24 +++++++++---------- .../tests/enhancement_tests/test_enhancer.py | 4 ++-- 5 files changed, 44 insertions(+), 31 deletions(-) diff --git a/satpy/decision_tree.py b/satpy/decision_tree.py index c1f5e25162..c9adc26ac4 100644 --- a/satpy/decision_tree.py +++ b/satpy/decision_tree.py @@ -163,8 +163,8 @@ def _build_tree(self, conf): for match_level, match_key in enumerate(self._match_keys): # or None is necessary if they have empty strings this_attr_val = sect_attrs.get(match_key, self.any_key) or None - if match_key in self._multival_keys and isinstance(this_attr_val, list): - this_attr_val = tuple(sorted(this_attr_val)) + if match_key in self._multival_keys: + this_attr_val = self._normalize_multival_attr(this_attr_val) is_last_key = match_key == self._match_keys[-1] level_needs_init = this_attr_val not in curr_level if is_last_key: @@ -176,10 +176,23 @@ def _build_tree(self, conf): curr_level[this_attr_val] = _DecisionDict(self._match_keys[match_level + 1], match_level + 1) curr_level = curr_level[this_attr_val] + def _normalize_multival_attr(self, attr): + """Convert multival attributes from list/str to sorted tuple.""" + if isinstance(attr, list): + return tuple(sorted(attr)) + elif isinstance(attr, str): + return tuple([attr]) + return attr + @staticmethod def _convert_query_val_to_hashable(query_val): _sorted_query_val = sorted(query_val) - query_vals = [tuple(_sorted_query_val)] + _sorted_query_val + # Full match: All elements in the tuple + query_vals = [tuple(_sorted_query_val)] + # Partial match: One element of the tuple, as tuple + query_vals += [tuple([v]) for v in _sorted_query_val] + # Partial match: One element of the tuple, as string + query_vals += _sorted_query_val query_vals += query_val return query_vals diff --git a/satpy/etc/enhancements/abi.yaml b/satpy/etc/enhancements/abi.yaml index 7952c30a83..e72e7a7f63 100644 --- a/satpy/etc/enhancements/abi.yaml +++ b/satpy/etc/enhancements/abi.yaml @@ -1,7 +1,7 @@ enhancements: cimss_true_color: standard_name: cimss_true_color - instruments: abi + instruments: [abi] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -35,7 +35,7 @@ enhancements: true_color_with_night_fires: standard_name: true_color_with_night_fires - instruments: abi + instruments: [abi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -77,7 +77,7 @@ enhancements: ash_abi: ## RGB Ash recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/GOES_Ash_RGB.pdf standard_name: ash - instruments: abi + instruments: [abi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -89,7 +89,7 @@ enhancements: dust_abi: ## RGB Dust recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/Dust_RGB_Quick_Guide.pdf standard_name: dust - instruments: abi + instruments: [abi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -105,7 +105,7 @@ enhancements: convection_abi: ## RGB Convection recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/QuickGuide_GOESR_DayConvectionRGB_final.pdf standard_name: convection - instruments: abi + instruments: [abi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -142,7 +142,7 @@ enhancements: night_microphysics_abi: ## RGB Nighttime Microphysics recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/QuickGuide_GOESR_NtMicroRGB_final.pdf standard_name: night_microphysics - instruments: abi + instruments: [abi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -154,7 +154,7 @@ enhancements: night_microphysics_tropical_abi: ## RGB Nighttime Microphysics recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/QuickGuide_GOESR_NtMicroRGB_final.pdf standard_name: night_microphysics_tropical - instruments: abi + instruments: [abi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -170,7 +170,7 @@ enhancements: land_cloud_fire: ## RGB Day Land Cloud Fire recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/QuickGuide_GOESR_DayLandCloudFireRGB_final.pdf standard_name: land_cloud_fire - instruments: abi + instruments: [abi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -179,7 +179,7 @@ enhancements: land_cloud: ## RGB Day Land Cloud Fire recipe source: http://rammb.cira.colostate.edu/training/visit/quick_guides/QuickGuide_GOESR_daylandcloudRGB_final.pdf standard_name: land_cloud - instruments: abi + instruments: [abi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -191,7 +191,7 @@ enhancements: # IR with white clouds highlighted_brightness_temperature: standard_name: highlighted_toa_brightness_temperature - instruments: abi + instruments: [abi] operations: - name: btemp_threshold method: !!python/name:satpy.enhancements.contrast.btemp_threshold @@ -308,7 +308,7 @@ enhancements: ## RGB Recipe: https://rammb2.cira.colostate.edu/wp-content/uploads/2024/11/GOES-BlowingSnowRGB1_QuickGuide_24April2024.pdf ## Modified to match recommendations from RGB Workshop 2025 standard_name: day_blowing_snow - instruments: abi + instruments: [abi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -323,7 +323,7 @@ enhancements: day_cloud_type: # Recipe PDF: http://cimss.ssec.wisc.edu/goes/OCLOFactSheetPDFs/ABIQuickGuide_Day_Cloud_Type_RGB.pdf standard_name: day_cloud_type - instruments: abi + instruments: [abi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch diff --git a/satpy/etc/enhancements/ahi.yaml b/satpy/etc/enhancements/ahi.yaml index ce97bca324..3f73b9c1a4 100644 --- a/satpy/etc/enhancements/ahi.yaml +++ b/satpy/etc/enhancements/ahi.yaml @@ -12,7 +12,7 @@ enhancements: day_severe_storms: standard_name: day_severe_storms - instruments: ahi + instruments: [ahi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -27,7 +27,7 @@ enhancements: night_microphysics_tropical: standard_name: night_microphysics_tropical - instruments: ahi + instruments: [ahi] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch diff --git a/satpy/etc/enhancements/amsr2.yaml b/satpy/etc/enhancements/amsr2.yaml index 4a0ac05412..788b52cc2c 100644 --- a/satpy/etc/enhancements/amsr2.yaml +++ b/satpy/etc/enhancements/amsr2.yaml @@ -3,28 +3,28 @@ enhancements: # https://www.ospo.noaa.gov/Products/atmosphere/gpds/maps.html?GPRR#gpdsMaps gaasp_clw: name: CLW - instruments: amsr2 + instruments: [amsr2] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 0.5} gaasp_sst: name: SST - instruments: amsr2 + instruments: [amsr2] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: -5.0, max_stretch: 35} gaasp_tpw: name: TPW - instruments: amsr2 + instruments: [amsr2] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 75.0} gaasp_wspd: name: WSPD - instruments: amsr2 + instruments: [amsr2] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -32,56 +32,56 @@ enhancements: # Snow_Cover unscaled (category product) gaasp_snow_depth: name: Snow_Depth - instruments: amsr2 + instruments: [amsr2] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 150.0} gaasp_swe: name: SWE - instruments: amsr2 + instruments: [amsr2] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 16.0} gaasp_soil_moisture: name: Soil_Moisture - instruments: amsr2 + instruments: [amsr2] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 100.0} gaasp_ice_concentration_nh: name: NASA_Team_2_Ice_Concentration_NH - instruments: amsr2 + instruments: [amsr2] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 100.0} gaasp_ice_concentration_sh: name: NASA_Team_2_Ice_Concentration_SH - instruments: amsr2 + instruments: [amsr2] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 100.0} # gaasp_latency_nh: # name: Latency_NH -# instruments: amsr2 +# instruments: [amsr2] # operations: # - name: linear_stretch # method: !!python/name:satpy.enhancements.contrast.stretch # kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 100.0} # gaasp_latency_sh: # name: Latency_SH -# instruments: amsr2 +# instruments: [amsr2] # operations: # - name: linear_stretch # method: !!python/name:satpy.enhancements.contrast.stretch # kwargs: {stretch: 'crude', min_stretch: 0.0, max_stretch: 100.0} gaasp_rain_rate: name: Rain_Rate - instruments: amsr2 + instruments: [amsr2] operations: - name: linear_stretch method: !!python/name:satpy.enhancements.contrast.stretch diff --git a/satpy/tests/enhancement_tests/test_enhancer.py b/satpy/tests/enhancement_tests/test_enhancer.py index bcb1504058..06e03da02a 100644 --- a/satpy/tests/enhancement_tests/test_enhancer.py +++ b/satpy/tests/enhancement_tests/test_enhancer.py @@ -144,7 +144,7 @@ class TestComplexSensorEnhancerConfigs(_BaseCustomEnhancementConfigTests): enhancements: test1_sensor1_specific: name: test1 - instruments: test_sensor1 + instruments: [test_sensor1] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch @@ -160,7 +160,7 @@ class TestComplexSensorEnhancerConfigs(_BaseCustomEnhancementConfigTests): kwargs: {stretch: crude, min_stretch: 0, max_stretch: 100} test1_sensor2_specific: name: test1 - instruments: test_sensor2 + instruments: [test_sensor2] operations: - name: stretch method: !!python/name:satpy.enhancements.contrast.stretch From fd259b3b7c79eb762b66920a916f1fd7684e7f2f Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper Date: Tue, 19 May 2026 09:04:05 +0000 Subject: [PATCH 10/15] Normalize instrument names from dataset attributes --- satpy/enhancements/enhancer.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/satpy/enhancements/enhancer.py b/satpy/enhancements/enhancer.py index ac6f1cde13..d2dbcc40c5 100644 --- a/satpy/enhancements/enhancer.py +++ b/satpy/enhancements/enhancer.py @@ -228,7 +228,18 @@ def get_enhanced_image(dataset, enhance=None, overlay=None, decorate=None, sensors = instru.get_instruments_from_attrs(dataset.attrs) if sensors: enhancer.add_sensor_enhancements(sensors) - enhancer.apply(img, **dataset.attrs) + + # As long as enhancement YAMLs don't contain WMO instrument + # names yet, normalize instrument names from dataset + # attributes. This can be removed as soon as enhancement + # YAMLs have been updated. + attrs = dataset.attrs.copy() + normalized = { + instru.normalize_instrument_name(sensor) + for sensor in sensors + } + instru.set_instruments_attr(attrs, normalized) + enhancer.apply(img, **attrs) if overlay is not None: from satpy.enhancements.overlays import add_overlay From 51c8abf6ab30c5163bd61c3a749e69dca2366ce3 Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper Date: Fri, 22 May 2026 07:56:55 +0000 Subject: [PATCH 11/15] Improve docstring --- satpy/decision_tree.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/satpy/decision_tree.py b/satpy/decision_tree.py index c9adc26ac4..18e5572a25 100644 --- a/satpy/decision_tree.py +++ b/satpy/decision_tree.py @@ -186,12 +186,21 @@ def _normalize_multival_attr(self, attr): @staticmethod def _convert_query_val_to_hashable(query_val): + """Prepare multival query for matching. + + First priority is exact matches with the sorted values. + If not found, search each of the values individually in + alphabetical order - both as tuple with a single element + and string. + """ _sorted_query_val = sorted(query_val) - # Full match: All elements in the tuple + # Exact match query_vals = [tuple(_sorted_query_val)] - # Partial match: One element of the tuple, as tuple + # Each of the values individually, in alphabetical order, + # as tuple with a single element. query_vals += [tuple([v]) for v in _sorted_query_val] - # Partial match: One element of the tuple, as string + # Each of the values individually, in alphabetical order, + # as string. query_vals += _sorted_query_val query_vals += query_val return query_vals From 8a54b769a0394ea0d36f1eb7fd4d201c1eac16a3 Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper <1991007+sfinkens@users.noreply.github.com> Date: Fri, 22 May 2026 09:58:36 +0200 Subject: [PATCH 12/15] Simplify tuple creation Co-authored-by: David Hoese --- satpy/decision_tree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/satpy/decision_tree.py b/satpy/decision_tree.py index c9adc26ac4..2a89fe676e 100644 --- a/satpy/decision_tree.py +++ b/satpy/decision_tree.py @@ -181,7 +181,7 @@ def _normalize_multival_attr(self, attr): if isinstance(attr, list): return tuple(sorted(attr)) elif isinstance(attr, str): - return tuple([attr]) + return (attr,) return attr @staticmethod From 830678e5aa4a6ed15eb8850394bcc9420cf05068 Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper Date: Fri, 22 May 2026 08:05:22 +0000 Subject: [PATCH 13/15] Add v1.0 scissors --- satpy/enhancements/enhancer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/satpy/enhancements/enhancer.py b/satpy/enhancements/enhancer.py index e0c1d9b4f7..fb29c04a16 100644 --- a/satpy/enhancements/enhancer.py +++ b/satpy/enhancements/enhancer.py @@ -58,7 +58,9 @@ def add_config_to_tree(self, *decision_dict: str | Path | dict) -> None: for config_file in decision_dict: config_dict = self._get_config_dict_from_user(config_file) recursive_dict_update(conf, config_dict) + # 8< v1.0 self._ensure_compat(conf) + # >8 v1.0 self._build_tree(conf) def _get_config_dict_from_user(self, config_file: str | Path | dict) -> dict: @@ -89,6 +91,7 @@ def _get_yaml_enhancement_dict(self, config_file: str | Path) -> dict: LOG.debug(f"Adding enhancement configuration from file: {config_file}") return enhancement_section + # 8< v1.0 def _ensure_compat(self, config_dict: dict) -> None: for enh_name, enh_config in config_dict.items(): if "sensor" in enh_config: @@ -101,6 +104,7 @@ def _ensure_compat(self, config_dict: dict) -> None: stacklevel=3 ) inst_utils.set_instruments_attr(config_dict[enh_name], enh_config["sensor"]) + # >8 v1.0 def find_match(self, **query_dict): """Find a match.""" From 47bfb3d611996e4329ff36d96bdafbe4695762c9 Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper Date: Fri, 22 May 2026 08:32:25 +0000 Subject: [PATCH 14/15] Change scissor version to 1.1 --- satpy/enhancements/enhancer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/satpy/enhancements/enhancer.py b/satpy/enhancements/enhancer.py index fb29c04a16..bab850157f 100644 --- a/satpy/enhancements/enhancer.py +++ b/satpy/enhancements/enhancer.py @@ -58,9 +58,9 @@ def add_config_to_tree(self, *decision_dict: str | Path | dict) -> None: for config_file in decision_dict: config_dict = self._get_config_dict_from_user(config_file) recursive_dict_update(conf, config_dict) - # 8< v1.0 + # 8< v1.1 self._ensure_compat(conf) - # >8 v1.0 + # >8 v1.1 self._build_tree(conf) def _get_config_dict_from_user(self, config_file: str | Path | dict) -> dict: @@ -91,7 +91,7 @@ def _get_yaml_enhancement_dict(self, config_file: str | Path) -> dict: LOG.debug(f"Adding enhancement configuration from file: {config_file}") return enhancement_section - # 8< v1.0 + # 8< v1.1 def _ensure_compat(self, config_dict: dict) -> None: for enh_name, enh_config in config_dict.items(): if "sensor" in enh_config: @@ -104,7 +104,7 @@ def _ensure_compat(self, config_dict: dict) -> None: stacklevel=3 ) inst_utils.set_instruments_attr(config_dict[enh_name], enh_config["sensor"]) - # >8 v1.0 + # >8 v1.1 def find_match(self, **query_dict): """Find a match.""" From 5f836eae574177eec0ce90e56bbc963579e4932a Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper Date: Fri, 22 May 2026 12:28:21 +0000 Subject: [PATCH 15/15] Change scissors again --- satpy/enhancements/enhancer.py | 10 +++++----- satpy/tests/test_instruments.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/satpy/enhancements/enhancer.py b/satpy/enhancements/enhancer.py index bab850157f..0b3c09515b 100644 --- a/satpy/enhancements/enhancer.py +++ b/satpy/enhancements/enhancer.py @@ -58,9 +58,9 @@ def add_config_to_tree(self, *decision_dict: str | Path | dict) -> None: for config_file in decision_dict: config_dict = self._get_config_dict_from_user(config_file) recursive_dict_update(conf, config_dict) - # 8< v1.1 + # 8< v1.0 self._ensure_compat(conf) - # >8 v1.1 + # >8 v1.0 self._build_tree(conf) def _get_config_dict_from_user(self, config_file: str | Path | dict) -> dict: @@ -91,20 +91,20 @@ def _get_yaml_enhancement_dict(self, config_file: str | Path) -> dict: LOG.debug(f"Adding enhancement configuration from file: {config_file}") return enhancement_section - # 8< v1.1 + # 8< v1.0 def _ensure_compat(self, config_dict: dict) -> None: for enh_name, enh_config in config_dict.items(): if "sensor" in enh_config: warnings.warn( "Renaming the 'sensor' enhancement attribute to 'instruments'. " - "This will raise an exception in Satpy v1.1 when the 'sensor' " + "This will raise an exception in Satpy v1.0 when the 'sensor' " "attribute will be removed. To silence this warning, rename " "'sensor' to 'instruments' in your enhancement YAML file.", DeprecationWarning, stacklevel=3 ) inst_utils.set_instruments_attr(config_dict[enh_name], enh_config["sensor"]) - # >8 v1.1 + # >8 v1.0 def find_match(self, **query_dict): """Find a match.""" diff --git a/satpy/tests/test_instruments.py b/satpy/tests/test_instruments.py index abed85dbf2..a4d28f1141 100644 --- a/satpy/tests/test_instruments.py +++ b/satpy/tests/test_instruments.py @@ -43,7 +43,7 @@ def test_get_instruments_from_attrs(attrs, to_internal, expected): ) def test_get_instruments_from_attrs_with_warning(attrs, expected): """Test deprecation warnings when getting instruments.""" - with pytest.warns(DeprecationWarning, match="v1.1"): + with pytest.warns(DeprecationWarning, match="v1.0"): assert inst_utils.get_instruments_from_attrs(attrs) == expected