diff --git a/satpy/_config.py b/satpy/_config.py index fbfcb0c0d5..6fb1a62451 100644 --- a/satpy/_config.py +++ b/satpy/_config.py @@ -54,6 +54,9 @@ "readers": { "clip_negative_radiances": False, }, + # 8< v1.0 + "oscar_compliant_attributes": False, + # >8 v1.0 } # Satpy main configuration object diff --git a/satpy/composites/config_loader.py b/satpy/composites/config_loader.py index e5165ada01..916b94c835 100644 --- a/satpy/composites/config_loader.py +++ b/satpy/composites/config_loader.py @@ -192,7 +192,7 @@ def _load_config(composite_configs): sensor_modifiers = {} dep_id_keys = None - sensor_deps = sensor_name.split("/")[:-1] + sensor_deps = conf.get("dependencies", sensor_name.split("/")[:-1]) if sensor_deps: # get dependent for sensor_dep in sensor_deps: @@ -246,6 +246,10 @@ def _update_cached_wrapper(wrapper, cached_func): return wrapper +def sensor_to_filename(sensor_name: str) -> str: + """Get filename tag for the given sensor.""" + return sensor_name.lower().replace("-", "").replace(" ", "_").replace("/", "-") + @_lru_cache_with_config_path def load_compositor_configs_for_sensor(sensor_name: str) -> tuple[dict[str, dict], dict[str, dict], dict]: """Load compositor, modifier, and DataID key information from configuration files for the specified sensor. @@ -268,7 +272,7 @@ def load_compositor_configs_for_sensor(sensor_name: str) -> tuple[dict[str, dict DataID key -> key properties """ - config_filename = sensor_name + ".yaml" + config_filename = sensor_to_filename(sensor_name) + ".yaml" logger.debug("Looking for composites config file %s", config_filename) paths = get_entry_points_config_dirs("satpy.composites") composite_configs = config_search_paths( diff --git a/satpy/etc/composites/abi.yaml b/satpy/etc/composites/abi.yaml index 59b4fe28f0..982766a43b 100644 --- a/satpy/etc/composites/abi.yaml +++ b/satpy/etc/composites/abi.yaml @@ -1,5 +1,4 @@ sensor_name: visir/abi - modifiers: rayleigh_corrected_crefl: modifier: !!python/name:satpy.modifiers.atmosphere.ReflectanceCorrector diff --git a/satpy/modifiers/atmosphere.py b/satpy/modifiers/atmosphere.py index c7144c27ca..655a489632 100644 --- a/satpy/modifiers/atmosphere.py +++ b/satpy/modifiers/atmosphere.py @@ -26,6 +26,7 @@ from satpy.modifiers import ModifierBase from satpy.modifiers._crefl import ReflectanceCorrector # noqa from satpy.modifiers.angles import compute_relative_azimuth, get_angles, get_satellite_zenith_angle +from satpy.modifiers.base import _convert_to_pyspectral logger = logging.getLogger(__name__) @@ -104,7 +105,8 @@ def __call__(self, projectables, optional_datasets=None, **info): logger.info("Removing Rayleigh scattering with atmosphere '%s' and " "aerosol type '%s' for '%s'", atmosphere, aerosol_type, vis.attrs["name"]) - corrector = Rayleigh(vis.attrs["platform_name"], vis.attrs["sensor"], + sensor = _convert_to_pyspectral(vis.attrs["sensor"]) + corrector = Rayleigh(vis.attrs["platform_name"], sensor, atmosphere=atmosphere, aerosol_type=aerosol_type) @@ -158,8 +160,9 @@ def __call__(self, projectables, optional_datasets=None, **info): satz = satz.data # get dask array underneath logger.info("Correction for limb cooling") + sensor = _convert_to_pyspectral(band.attrs["sensor"]) corrector = AtmosphericalCorrection(band.attrs["platform_name"], - band.attrs["sensor"]) + sensor) atm_corr = da.map_blocks(_call_mapped_correction, satz, band.data, corrector=corrector, diff --git a/satpy/modifiers/base.py b/satpy/modifiers/base.py index 06c561db7e..8170d73460 100644 --- a/satpy/modifiers/base.py +++ b/satpy/modifiers/base.py @@ -38,3 +38,8 @@ class ModifierBase(CompositeBase): def __call__(self, datasets, optional_datasets=None, **info): """Generate a modified copy of the first provided dataset.""" raise NotImplementedError() + + +def _convert_to_pyspectral(sensor: str) -> str: + """Convert sensor name to format expected by pyspectral.""" + return sensor.lower() diff --git a/satpy/modifiers/spectral.py b/satpy/modifiers/spectral.py index 402b5606d4..bf8925461d 100644 --- a/satpy/modifiers/spectral.py +++ b/satpy/modifiers/spectral.py @@ -22,6 +22,7 @@ import xarray as xr from satpy.modifiers import ModifierBase +from satpy.modifiers.base import _convert_to_pyspectral try: from pyspectral.near_infrared_reflectance import Calculator @@ -132,7 +133,8 @@ def _init_reflectance_calculator(self, metadata): logger.info("Couldn't load pyspectral") raise ImportError("No module named pyspectral.near_infrared_reflectance") - reflectance_3x_calculator = Calculator(metadata["platform_name"], metadata["sensor"], metadata["name"], + sensor = _convert_to_pyspectral(metadata["sensor"]) + reflectance_3x_calculator = Calculator(metadata["platform_name"], sensor, metadata["name"], sunz_threshold=self.sun_zenith_threshold, masking_limit=self.masking_limit) return reflectance_3x_calculator diff --git a/satpy/readers/core/yaml_reader.py b/satpy/readers/core/yaml_reader.py index 1982aac346..533f1f2210 100644 --- a/satpy/readers/core/yaml_reader.py +++ b/satpy/readers/core/yaml_reader.py @@ -47,6 +47,7 @@ from satpy.coords import add_crs_xy_coords from satpy.dataset import DataID, DataQuery, get_key from satpy.dataset.dataid import default_co_keys_config, default_id_keys_config, get_keys_from_config +from satpy.readers.core.file_handlers import BaseFileHandler from satpy.utils import recursive_dict_update logger = logging.getLogger(__name__) @@ -483,6 +484,7 @@ def __init__(self, self.file_handlers = {} self.available_ids = {} self.register_data_files() + self.attribute_composer = AttributeComposer(self.name) @property def sensor_names(self): @@ -737,10 +739,8 @@ def _load_dataset_data(self, file_handlers, dsid, **kwargs): ds_info = self.all_ids[dsid] proj = self._load_dataset(dsid, ds_info, file_handlers, **kwargs) # FIXME: areas could be concatenated here - # Update the metadata - proj.attrs["start_time"] = file_handlers[0].start_time - proj.attrs["end_time"] = file_handlers[-1].end_time - proj.attrs["reader"] = self.name + attrs = self.attribute_composer.get_attrs(proj, file_handlers) + proj.attrs.update(attrs) return proj def _preferred_filetype(self, filetypes): @@ -985,6 +985,78 @@ def _get_coordinates_for_dataset_key(self, dsid): return cids +# 8< v1.0 +LEGACY_SENSORS = { + "ABI": "abi" +} +LEGACY_PLATFORMS: dict[str, str] = {} + +from functools import wraps # noqa: E402 + +import satpy # noqa: E402 + + +def with_legacy_attributes(func): + """Adds option to get legacy attributes back.""" + @wraps(func) + def translate_attrs(*args, **kwargs): + attrs = func(*args, **kwargs) + if not satpy.config.get("oscar_compliant_attributes", False): + _make_legacy_attrs(attrs) + return attrs + return translate_attrs + +def _make_legacy_attrs(attrs: dict) -> None: + sensor = attrs.get("sensor") + if sensor: + attrs["sensor"] = _get_legacy_sensor(sensor) + platform = attrs.get("platform") + if platform: + attrs["platform_name"] = _get_legacy_platform(platform) + + +def _get_legacy_sensor(sensor: str|set[str]) -> str|set[str]: + if isinstance(sensor, set): + return { + LEGACY_SENSORS.get(s, s) + for s in sensor + } + return LEGACY_SENSORS.get(sensor, sensor) + + +def _get_legacy_platform(platform: str) -> str: + return LEGACY_PLATFORMS.get(platform, platform) +# >8 v1.0 + + +class AttributeComposer: + """Compose dataset attributes.""" + + def __init__(self, reader_name: str) -> None: + """Initialize the composer.""" + self.reader_name = reader_name + + def get_attrs(self, data: xr.DataArray, file_handlers: list[BaseFileHandler]) -> dict: + """Get dataset attributes.""" + attrs = { + "start_time": file_handlers[0].start_time, + "end_time": file_handlers[-1].end_time, + "reader": self.reader_name, + } + return attrs | self._get_platform_sensor(data) + + # 8< v1.0 + @with_legacy_attributes + # >8 v1.0 + def _get_platform_sensor(self, data: xr.DataArray) -> dict: + keys = ["platform_name", "sensor"] + return { + key: value + for key in keys + if (value := data.attrs.get(key)) + } + + def _load_area_def(dsid, file_handlers): """Load the area definition of *dsid*.""" area_defs = [fh.get_area_def(dsid) for fh in file_handlers] diff --git a/satpy/tests/reader_tests/conftest.py b/satpy/tests/reader_tests/conftest.py index 4a551cadc8..49dd495d46 100644 --- a/satpy/tests/reader_tests/conftest.py +++ b/satpy/tests/reader_tests/conftest.py @@ -232,3 +232,11 @@ def aws_mwr_l1c_handler(aws_mwr_l1c_file): filetype_info["file_type"] = "aws1_mwr_l1c" filetype_info["feed_horn_group_name"] = None return AWS_MWR_L1CFile(aws_mwr_l1c_file, filename_info, filetype_info) + +# 8< v1.0 +@pytest.fixture(scope="module", autouse=True) +def oscar_compliant_attrs(): + """Request oscar compliant dataset attributes.""" + import satpy + satpy.config.set(oscar_compliant_attributes=False) +# >8 v1.0