From 9a255e7f786d69b329af6cb961e1a7b5d8d62075 Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper Date: Wed, 24 Jul 2024 07:29:53 +0000 Subject: [PATCH 1/7] WIP SEVIRI channel specific calib --- satpy/readers/seviri_base.py | 155 ++++++++++++++++--------------- satpy/readers/seviri_l1b_hrit.py | 27 ++++-- 2 files changed, 98 insertions(+), 84 deletions(-) diff --git a/satpy/readers/seviri_base.py b/satpy/readers/seviri_base.py index ace63e3f12..88c7429cb6 100644 --- a/satpy/readers/seviri_base.py +++ b/satpy/readers/seviri_base.py @@ -189,14 +189,15 @@ import datetime as dt import warnings +from collections import defaultdict import dask.array as da import numpy as np import pyproj from numpy.polynomial.chebyshev import Chebyshev +import satpy.readers.utils as utils from satpy.readers.eum_base import issue_revision, time_cds_short -from satpy.readers.utils import apply_earthsun_distance_correction from satpy.utils import get_legacy_chunk_size CHUNK_SIZE = get_legacy_chunk_size() @@ -444,19 +445,6 @@ def should_apply_meirink(calib_mode, channel_name): return "MEIRINK" in calib_mode and channel_name in ["VIS006", "VIS008", "IR_016"] -class MeirinkCalibrationHandler: - """Re-calibration of the SEVIRI visible channels slope (see Meirink 2013).""" - - def __init__(self, calib_mode): - """Initialize the calibration handler.""" - self.coefs = MEIRINK_COEFS[calib_mode.split("-")[1]] - - def get_slope(self, platform, channel, time): - """Return the slope using the provided calibration coefficients.""" - coefs = self.coefs[platform][channel] - return get_meirink_slope(coefs, time) - - def get_cds_time(days, msecs): """Compute timestamp given the days since epoch and milliseconds of the day. @@ -649,7 +637,7 @@ def vis_calibrate(self, data, solar_irradiance): reflectances for SEVIRI warm channels: https://www-cdn.eumetsat.int/files/2020-04/pdf_msg_seviri_rad2refl.pdf """ reflectance = np.pi * data * 100.0 / solar_irradiance - return apply_earthsun_distance_correction(reflectance, self._scan_time) + return utils.apply_earthsun_distance_correction(reflectance, self._scan_time) class SEVIRICalibrationHandler: @@ -659,34 +647,34 @@ class SEVIRICalibrationHandler: calibration algorithm. """ - def __init__(self, platform_id, channel_name, coefs, calib_mode, scan_time): + def __init__(self, calib_params, scan_params): """Initialize the calibration handler.""" - self._platform_id = platform_id - self._channel_name = channel_name - self._coefs = coefs - self._calib_mode = calib_mode.upper() - self._scan_time = scan_time + self._platform_id = scan_params["platform_id"] + self._channel_name = scan_params["channel_name"] + self._scan_time = scan_params["scan_time"] + self._coefs = calib_params["coefs"] + try: + self._calib_mode = calib_params["mode"].upper() + except AttributeError: + self._calib_mode = calib_params["mode"] + self._legacy_ext_calib_coefs = calib_params["ext_calib_coefs"] self._algo = SEVIRICalibrationAlgorithm( platform_id=self._platform_id, scan_time=self._scan_time ) - valid_modes = ("NOMINAL", "GSICS", "MEIRINK-2023") - if self._calib_mode not in valid_modes: - raise ValueError( - "Invalid calibration mode: {}. Choose one of {}".format( - self._calib_mode, valid_modes) - ) - def calibrate(self, data, calibration): """Calibrate the given data.""" + coefs = None if calibration == "counts": res = data elif calibration in ["radiance", "reflectance", "brightness_temperature"]: - gain, offset = self.get_gain_offset() + coefs = self.get_coefs() res = self._algo.convert_to_radiance( - data.astype(np.float32), gain, offset + data.astype(np.float32), + coefs["coefs"]["gain"], + coefs["coefs"]["offset"] ) else: raise ValueError( @@ -702,39 +690,46 @@ def calibrate(self, data, calibration): res = self._algo.ir_calibrate( res, self._channel_name, self._coefs["radiance_type"] ) - + if coefs: + self._update_attrs(res, coefs) return res - def get_gain_offset(self): - """Get gain & offset for calibration from counts to radiance. + def _update_attrs(self, data, coefs): + data.attrs["calibration_parameters"] = { + "mode": coefs["mode"], + "gain": coefs["coefs"]["gain"], + "offset": coefs["coefs"]["offset"] + } + + def get_coefs(self): + """Get calibration coefficients.""" + self._warn_fallback() + coefs = self._include_legacy_ext_coefs() + picker = utils.CalibrationCoefficientPicker(coefs, + self._calib_mode, + default="NOMINAL", + fallback="NOMINAL") + return picker.get_coefs(self._channel_name) + + def _warn_fallback(self): + if self._should_warn_fallback(): + msg = f""" +{self._calib_mode} calibration coefficients are not available for all channels +and Satpy is falling back to nominal coefficients in that case. In the future +this will raise an error. Users are encouraged to specify a fallback +via reader_kwargs['calib_fallback']. +""" + warnings.warn(msg, FutureWarning) - Choices for internal coefficients are nominal or GSICS. If no - GSICS coefficients are available for a certain channel, fall back to - nominal coefficients. External coefficients take precedence over - internal coefficients. - """ - coefs = self._coefs["coefs"] - - # Select internal coefficients for the given calibration mode - internal_gain = coefs["NOMINAL"]["gain"] - internal_offset = coefs["NOMINAL"]["offset"] - if self._calib_mode == "GSICS": - gsics_gain = coefs["GSICS"]["gain"] - gsics_offset = coefs["GSICS"]["offset"] * gsics_gain - if gsics_gain != 0 and gsics_offset != 0: - # If no GSICS coefficients are available for a certain channel, - # they are set to zero in the file. - internal_gain = gsics_gain - internal_offset = gsics_offset - - if should_apply_meirink(self._calib_mode, self._channel_name): - meirink = MeirinkCalibrationHandler(calib_mode=self._calib_mode) - internal_gain = meirink.get_slope(self._platform_id, self._channel_name, self._scan_time) - - # Override with external coefficients, if any. - gain = coefs["EXTERNAL"].get("gain", internal_gain) - offset = coefs["EXTERNAL"].get("offset", internal_offset) - return gain, offset + def _should_warn_fallback(self): + return self._calib_mode == "GSICS" or "MEIRINK" in self._calib_mode + + def _include_legacy_ext_coefs(self): + if self._legacy_ext_calib_coefs: + coefs = self._legacy_ext_calib_coefs + if isinstance(self._calib_mode, str): + # Fill in other channels + return coefs def chebyshev(coefs, time, domain): @@ -992,22 +987,34 @@ def calculate_area_extent(area_dict): return (ll_c, ll_l, ur_c, ur_l) -def create_coef_dict(coefs_nominal, coefs_gsics, radiance_type, ext_coefs): +def create_coef_dict(coefs_nominal, coefs_gsics, platform_id, channel, scan_time, radiance_type): """Create coefficient dictionary expected by calibration class.""" - return { - "coefs": { - "NOMINAL": { - "gain": coefs_nominal[0], - "offset": coefs_nominal[1], - }, - "GSICS": { - "gain": coefs_gsics[0], - "offset": coefs_gsics[1] - }, - "EXTERNAL": ext_coefs - }, - "radiance_type": radiance_type + coefs = defaultdict(dict) + channel_name = channel["name"] + coefs["NOMINAL"][channel_name] = { + "gain": coefs_nominal[0], + "offset": coefs_nominal[1] } + if coefs_gsics[0] != 0 and coefs_gsics[1] != 0: + # If no GSICS coefficients are available they are set to zero in + # the file. + coefs["GSICS"][channel_name] = { + "gain": coefs_gsics[0], + "offset": coefs_gsics[1] + } + + for meirink_version, meirink_coefs in MEIRINK_COEFS.items(): + meirink_version = f"MEIRINK-{meirink_version}" + try: + coefs_this_channel = meirink_coefs[platform_id][channel_name] + except KeyError: + # Meirink only available for solar channels + continue + coefs[meirink_version][channel_name] = { + "gain": get_meirink_slope(coefs_this_channel, scan_time), + "offset": coefs_nominal[0] + } + return {"coefs": coefs, "radiance_type": radiance_type} def get_padding_area(shape, dtype): diff --git a/satpy/readers/seviri_l1b_hrit.py b/satpy/readers/seviri_l1b_hrit.py index f65faa8ecc..f5a6a537ff 100644 --- a/satpy/readers/seviri_l1b_hrit.py +++ b/satpy/readers/seviri_l1b_hrit.py @@ -455,7 +455,7 @@ class HRITMSGFileHandler(HRITFileHandler): """ def __init__(self, filename, filename_info, filetype_info, - prologue, epilogue, calib_mode="nominal", + prologue, epilogue, calib_mode="nominal", calib_fallback=None, ext_calib_coefs=None, include_raw_metadata=False, mda_max_array_size=100, fill_hrv=True, mask_bad_quality_scan_lines=True): @@ -475,6 +475,7 @@ def __init__(self, filename, filename_info, filetype_info, self.mda_max_array_size = mda_max_array_size self.fill_hrv = fill_hrv self.calib_mode = calib_mode + self.calib_fallback = calib_fallback self.ext_calib_coefs = ext_calib_coefs or {} self.mask_bad_quality_scan_lines = mask_bad_quality_scan_lines self._get_header() @@ -722,13 +723,17 @@ def pad_hrv_data(self, res): def calibrate(self, data, calibration): """Calibrate the data.""" - calib = SEVIRICalibrationHandler( - platform_id=self.platform_id, - channel_name=self.channel_name, - coefs=self._get_calib_coefs(self.channel_name), - calib_mode=self.calib_mode, - scan_time=self.observation_start_time - ) + calib_params = { + "mode": self.calib_mode, + "coefs": self._get_calib_coefs(), + "ext_calib_coefs": self.ext_calib_coefs + } + scan_params = { + "platform_id": self.platform_id, + "channel_name": self.channel_name, + "scan_time": self.observation_start_time + } + calib = SEVIRICalibrationHandler(calib_params, scan_params) res = calib.calibrate(data, calibration) return res @@ -783,7 +788,7 @@ def _update_attrs(self, res, info): if self.include_raw_metadata: res.attrs["raw_metadata"] = self._get_raw_mda() - def _get_calib_coefs(self, channel_name): + def _get_calib_coefs(self): """Get coefficients for calibration from counts to radiance.""" band_idx = self.mda["spectral_channel_id"] - 1 coefs_nominal = self.prologue["RadiometricProcessing"][ @@ -800,7 +805,9 @@ def _get_calib_coefs(self, channel_name): coefs_gsics["GSICSCalCoeff"][band_idx], coefs_gsics["GSICSOffsetCount"][band_idx] ), - ext_coefs=self.ext_calib_coefs.get(channel_name, {}), + platform_id=self.platform_id, + channel={"name": self.channel_name, "index": band_idx}, + scan_time=self.observation_start_time, radiance_type=radiance_types[band_idx] ) From 43f618a02171c9dea8959b2e0b5e2380c5375f6c Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper Date: Fri, 22 Nov 2024 08:27:12 +0000 Subject: [PATCH 2/7] Refactor SEVIRI calibration --- satpy/readers/seviri_base.py | 215 +++++++++++------- satpy/readers/seviri_l1b_hrit.py | 50 ++-- satpy/readers/utils.py | 2 +- satpy/tests/reader_tests/test_seviri_base.py | 18 +- .../test_seviri_l1b_calibration.py | 95 +++++--- 5 files changed, 241 insertions(+), 139 deletions(-) diff --git a/satpy/readers/seviri_base.py b/satpy/readers/seviri_base.py index 156f5e3ba9..304de2e962 100644 --- a/satpy/readers/seviri_base.py +++ b/satpy/readers/seviri_base.py @@ -201,7 +201,7 @@ import datetime as dt import warnings -from collections import defaultdict +from collections import namedtuple import dask.array as da import numpy as np @@ -437,28 +437,6 @@ } -def get_meirink_slope(meirink_coefs, acquisition_time): - """Compute the slope for the visible channel calibration according to Meirink 2013. - - S = A + B * 1.e-3* Day - - S is here in µW m-2 sr-1 (cm-1)-1 - - EUMETSAT calibration is given in mW m-2 sr-1 (cm-1)-1, so an extra factor of 1/1000 must - be applied. - """ - A = meirink_coefs[0] - B = meirink_coefs[1] - delta_t = (acquisition_time - MEIRINK_EPOCH).total_seconds() - S = A + B * delta_t / (3600*24) / 1000. - return S/1000 - - -def should_apply_meirink(calib_mode, channel_name): - """Decide whether to use the Meirink calibration coefficients.""" - return "MEIRINK" in calib_mode and channel_name in ["VIS006", "VIS008", "IR_016"] - - def get_cds_time(days, msecs): """Compute timestamp given the days since epoch and milliseconds of the day. @@ -654,6 +632,10 @@ def vis_calibrate(self, data, solar_irradiance): return utils.apply_earthsun_distance_correction(reflectance, self._scan_time) +CalibParams = namedtuple("CalibParams", ["mode", "internal_coefs", "external_coefs", "radiance_type"]) +ScanParams = namedtuple("ScanParams", ["platform_id", "channel_name", "scan_time"]) + + class SEVIRICalibrationHandler: """Calibration handler for SEVIRI HRIT-, native- and netCDF-formats. @@ -663,19 +645,21 @@ class SEVIRICalibrationHandler: def __init__(self, calib_params, scan_params): """Initialize the calibration handler.""" - self._platform_id = scan_params["platform_id"] - self._channel_name = scan_params["channel_name"] - self._scan_time = scan_params["scan_time"] - self._coefs = calib_params["coefs"] - try: - self._calib_mode = calib_params["mode"].upper() - except AttributeError: - self._calib_mode = calib_params["mode"] - self._legacy_ext_calib_coefs = calib_params["ext_calib_coefs"] + self._calib_params = calib_params + self._scan_params = scan_params self._algo = SEVIRICalibrationAlgorithm( - platform_id=self._platform_id, - scan_time=self._scan_time + platform_id=scan_params.platform_id, + scan_time=scan_params.scan_time ) + self._check_calib_mode(calib_params.mode) + + def _check_calib_mode(self, calib_mode): + valid_modes = ("NOMINAL", "GSICS", "MEIRINK-2023") + if calib_mode not in valid_modes: + raise ValueError( + "Invalid calibration mode: {}. Choose one of {}".format( + calib_mode, valid_modes) + ) def calibrate(self, data, calibration): """Calibrate the given data.""" @@ -693,16 +677,16 @@ def calibrate(self, data, calibration): else: raise ValueError( "Invalid calibration {} for channel {}".format( - calibration, self._channel_name + calibration, self._scan_params.channel_name ) ) if calibration == "reflectance": - solar_irradiance = CALIB[self._platform_id][self._channel_name]["F"] + solar_irradiance = CALIB[self._scan_params.platform_id][self._scan_params.channel_name]["F"] res = self._algo.vis_calibrate(res, solar_irradiance) elif calibration == "brightness_temperature": res = self._algo.ir_calibrate( - res, self._channel_name, self._coefs["radiance_type"] + res, self._scan_params.channel_name, self._calib_params.radiance_type ) if coefs: self._update_attrs(res, coefs) @@ -718,33 +702,32 @@ def _update_attrs(self, data, coefs): def get_coefs(self): """Get calibration coefficients.""" self._warn_fallback() - coefs = self._include_legacy_ext_coefs() - picker = utils.CalibrationCoefficientPicker(coefs, - self._calib_mode, + picker = utils.CalibrationCoefficientPicker(self._calib_params.internal_coefs, + self._get_calib_wishlist(), default="NOMINAL", fallback="NOMINAL") - return picker.get_coefs(self._channel_name) + return picker.get_coefs(self._scan_params.channel_name) def _warn_fallback(self): if self._should_warn_fallback(): msg = f""" -{self._calib_mode} calibration coefficients are not available for all channels -and Satpy is falling back to nominal coefficients in that case. In the future -this will raise an error. Users are encouraged to specify a fallback -via reader_kwargs['calib_fallback']. +{self._calib_params.mode} calibration coefficients are not available for all +channels and Satpy is falling back to nominal coefficients for these channels. +In the future this will raise an error. """ warnings.warn(msg, FutureWarning) def _should_warn_fallback(self): - return self._calib_mode == "GSICS" or "MEIRINK" in self._calib_mode - - def _include_legacy_ext_coefs(self): - if self._legacy_ext_calib_coefs: - coefs = self._legacy_ext_calib_coefs - if isinstance(self._calib_mode, str): - # TODO: Fill in other channels - pass - return coefs + is_gsics = self._calib_params.mode == "GSICS" + is_meirink = "MEIRINK" in self._calib_params.mode + return is_gsics or is_meirink + + def _get_calib_wishlist(self): + ext_coefs = self._calib_params.external_coefs or {} + wishlist = { + ch: self._calib_params.mode for ch in CHANNEL_NAMES.values() + } + return wishlist | ext_coefs def chebyshev(coefs, time, domain): @@ -1002,35 +985,115 @@ def calculate_area_extent(area_dict): return (ll_c, ll_l, ur_c, ur_l) -def create_coef_dict(coefs_nominal, coefs_gsics, platform_id, channel, scan_time, radiance_type): +def create_coef_dict(nominal_coefs, gsics_coefs, meirink_coefs): """Create coefficient dictionary expected by calibration class.""" - coefs = defaultdict(dict) - channel_name = channel["name"] - coefs["NOMINAL"][channel_name] = { - "gain": coefs_nominal[0], - "offset": coefs_nominal[1] - } - if coefs_gsics[0] != 0 and coefs_gsics[1] != 0: + coefs = nominal_coefs.get_coefs() + coefs.update(gsics_coefs.get_coefs()) + coefs.update(meirink_coefs.get_coefs(nominal_coefs.offset)) + return coefs + + +class NominalCoefficients: + """Nominal calibration coefficients.""" + def __init__(self, channel_name, gain, offset): + """Initialize coefficients.""" + self.channel_name = channel_name + self.gain = gain + self.offset = offset + + def get_coefs(self): + """Get coefficient dictionary.""" + return { + "NOMINAL": { + self.channel_name: { + "gain": self.gain, + "offset": self.offset + } + } + } + + +class GsicsCoefficients: + """GSICS calibration coefficients.""" + def __init__(self, channel_name, gain, offset): + """Initialize coefficients.""" + self.channel_name = channel_name + self.gain = gain + self.offset = offset + + def get_coefs(self): + """Get coefficient dictionary.""" + coefs = {"GSICS": {}} + if self._is_available(): + coefs["GSICS"][self.channel_name] = { + "gain": self.gain, + "offset": self.offset * self.gain + } + return coefs + + def _is_available(self): # If no GSICS coefficients are available they are set to zero in # the file. - coefs["GSICS"][channel_name] = { - "gain": coefs_gsics[0], - "offset": coefs_gsics[1] - } + return self.gain != 0 and self.offset != 0 + + +class MeirinkCoefficients: + """Re-calibration of the SEVIRI visible channels slope (see Meirink 2013).""" + + def __init__(self, platform_id, channel_name, scan_time): + """Initialize coefficients.""" + self.platform_id = platform_id + self.channel_name = channel_name + self.scan_time = scan_time + + def get_coefs(self, offset): + """Get coefficient dictionary. - for meirink_version, meirink_coefs in MEIRINK_COEFS.items(): - meirink_version = f"MEIRINK-{meirink_version}" + Args: + offset: Nominal calibration offset. + """ + gain = self._get_gain() + return self._combine_gain_and_offset(gain, offset) + + def _get_gain(self): + res = {} + for version, coefs in MEIRINK_COEFS.items(): + gain = self._get_gain_single_channel(coefs) + if gain: + res[f"MEIRINK-{version}"] = gain + return res + + def _get_gain_single_channel(self, coefs): try: - coefs_this_channel = meirink_coefs[platform_id][channel_name] + coefs_ch = coefs[self.platform_id][self.channel_name] + return self.get_slope(coefs_ch, self.scan_time) except KeyError: - # Meirink only available for solar channels - continue - coefs[meirink_version][channel_name] = { - "gain": get_meirink_slope(coefs_this_channel, scan_time), - "offset": coefs_nominal[0] - } - return {"coefs": coefs, "radiance_type": radiance_type} + return None + @staticmethod + def get_slope(coefs_single_channel, acquisition_time): + """Compute the slope for the visible channel calibration according to Meirink 2013. + + S = A + B * 1.e-3* Day + + S is here in µW m-2 sr-1 (cm-1)-1 + + EUMETSAT calibration is given in mW m-2 sr-1 (cm-1)-1, so an extra factor of 1/1000 must + be applied. + """ + A = coefs_single_channel[0] + B = coefs_single_channel[1] + delta_t = (acquisition_time - MEIRINK_EPOCH).total_seconds() + S = A + B * delta_t / (3600*24) / 1000. + return S/1000 + + def _combine_gain_and_offset(self, gain, offset): + return { + calib_mode: { + self.channel_name: {"gain": gain_, "offset": offset} + } + for calib_mode, gain_ in gain.items() + } def get_padding_area(shape, dtype): """Create a padding area filled with no data.""" diff --git a/satpy/readers/seviri_l1b_hrit.py b/satpy/readers/seviri_l1b_hrit.py index f5a6a537ff..d2db66bba0 100644 --- a/satpy/readers/seviri_l1b_hrit.py +++ b/satpy/readers/seviri_l1b_hrit.py @@ -238,8 +238,13 @@ HRV_NUM_COLUMNS, REPEAT_CYCLE_DURATION, SATNUM, + CalibParams, + GsicsCoefficients, + MeirinkCoefficients, + NominalCoefficients, NoValidOrbitParams, OrbitPolynomialFinder, + ScanParams, SEVIRICalibrationHandler, add_scanline_acq_time, create_coef_dict, @@ -723,16 +728,17 @@ def pad_hrv_data(self, res): def calibrate(self, data, calibration): """Calibrate the data.""" - calib_params = { - "mode": self.calib_mode, - "coefs": self._get_calib_coefs(), - "ext_calib_coefs": self.ext_calib_coefs - } - scan_params = { - "platform_id": self.platform_id, - "channel_name": self.channel_name, - "scan_time": self.observation_start_time - } + calib_params = CalibParams( + mode=self.calib_mode, + internal_coefs=self._get_calib_coefs(), + external_coefs=self.ext_calib_coefs, + radiance_type=self._get_radiance_type() + ) + scan_params = ScanParams( + platform_id=self.platform_id, + channel_name=self.channel_name, + scan_time=self.observation_start_time + ) calib = SEVIRICalibrationHandler(calib_params, scan_params) res = calib.calibrate(data, calibration) return res @@ -790,27 +796,33 @@ def _update_attrs(self, res, info): def _get_calib_coefs(self): """Get coefficients for calibration from counts to radiance.""" - band_idx = self.mda["spectral_channel_id"] - 1 + band_idx = self._get_band_index() coefs_nominal = self.prologue["RadiometricProcessing"][ "Level15ImageCalibration"] coefs_gsics = self.prologue["RadiometricProcessing"]["MPEFCalFeedback"] - radiance_types = self.prologue["ImageDescription"][ - "Level15ImageProduction"]["PlannedChanProcessing"] return create_coef_dict( - coefs_nominal=( + nominal_coefs=NominalCoefficients( + self.channel_name, coefs_nominal["CalSlope"][band_idx], coefs_nominal["CalOffset"][band_idx] ), - coefs_gsics=( + gsics_coefs=GsicsCoefficients( + self.channel_name, coefs_gsics["GSICSCalCoeff"][band_idx], coefs_gsics["GSICSOffsetCount"][band_idx] ), - platform_id=self.platform_id, - channel={"name": self.channel_name, "index": band_idx}, - scan_time=self.observation_start_time, - radiance_type=radiance_types[band_idx] + meirink_coefs=MeirinkCoefficients(self.platform_id, self.channel_name, self.observation_start_time) ) + def _get_radiance_type(self): + band_idx = self._get_band_index() + radiance_types = self.prologue["ImageDescription"][ + "Level15ImageProduction"]["PlannedChanProcessing"] + return radiance_types[band_idx] + + def _get_band_index(self): + return self.mda["spectral_channel_id"] - 1 + def pad_data(data, final_size, east_bound, west_bound): """Pad the data given east and west bounds and the desired size.""" diff --git a/satpy/readers/utils.py b/satpy/readers/utils.py index 983225acd5..37e3e9366f 100644 --- a/satpy/readers/utils.py +++ b/satpy/readers/utils.py @@ -593,7 +593,7 @@ class CalibrationCoefficientPicker: calib_wishlist = { "ch1": "meirink", - ("ch2", "ch3"): "gsics" + ("ch2", "ch3"): "gsics", "ch4": {"mygain": 123}, } # Also possible: Same mode for all channels via diff --git a/satpy/tests/reader_tests/test_seviri_base.py b/satpy/tests/reader_tests/test_seviri_base.py index 1b81aa1599..83b0b24b3d 100644 --- a/satpy/tests/reader_tests/test_seviri_base.py +++ b/satpy/tests/reader_tests/test_seviri_base.py @@ -28,10 +28,10 @@ from satpy.readers.seviri_base import ( MEIRINK_COEFS, MEIRINK_EPOCH, + MeirinkCoefficients, NoValidOrbitParams, OrbitPolynomial, OrbitPolynomialFinder, - SEVIRICalibrationHandler, chebyshev, dec10216, get_cds_time, @@ -371,11 +371,9 @@ class TestMeirinkSlope: @pytest.mark.parametrize("channel_name", ["VIS006", "VIS008", "IR_016"]) def test_get_meirink_slope_epoch(self, platform_id, channel_name): """Test the value of the slope of the Meirink calibration on 2000-01-01.""" - coefs = {"coefs": {}} - coefs["coefs"]["NOMINAL"] = {"gain": -1, "offset": -1} - coefs["coefs"]["EXTERNAL"] = {} - calibration_handler = SEVIRICalibrationHandler(platform_id, channel_name, coefs, "MEIRINK-2023", MEIRINK_EPOCH) - assert calibration_handler.get_gain_offset()[0] == MEIRINK_COEFS["2023"][platform_id][channel_name][0]/1000. + comp = MeirinkCoefficients(platform_id, channel_name, MEIRINK_EPOCH) + coefs = comp.get_coefs("dummy_offset") + assert coefs["MEIRINK-2023"][channel_name]["gain"] == MEIRINK_COEFS["2023"][platform_id][channel_name][0]/1000. @pytest.mark.parametrize(("platform_id", "time", "expected"), [ (321, dt.datetime(2005, 1, 18, 0, 0), [0.0250354716, 0.0315626684, 0.022880986]), @@ -389,9 +387,7 @@ def test_get_meirink_slope_epoch(self, platform_id, channel_name): ]) def test_get_meirink_slope_2020(self, platform_id, time, expected): """Test the value of the slope of the Meirink calibration.""" - coefs = {"coefs": {}} - coefs["coefs"]["NOMINAL"] = {"gain": -1, "offset": -1} - coefs["coefs"]["EXTERNAL"] = {} for i, channel_name in enumerate(["VIS006", "VIS008", "IR_016"]): - calibration_handler = SEVIRICalibrationHandler(platform_id, channel_name, coefs, "MEIRINK-2023", time) - assert abs(calibration_handler.get_gain_offset()[0] - expected[i]) < 1e-6 + comp = MeirinkCoefficients(platform_id, channel_name, time) + coefs = comp.get_coefs("dummy_offset") + assert abs(coefs["MEIRINK-2023"][channel_name]["gain"] - expected[i]) < 1e-6 diff --git a/satpy/tests/reader_tests/test_seviri_l1b_calibration.py b/satpy/tests/reader_tests/test_seviri_l1b_calibration.py index 8eaf2b83da..ad35d6494f 100644 --- a/satpy/tests/reader_tests/test_seviri_l1b_calibration.py +++ b/satpy/tests/reader_tests/test_seviri_l1b_calibration.py @@ -23,8 +23,9 @@ import numpy as np import pytest import xarray as xr +from pytest_lazy_fixtures.lazy_fixture import lf -from satpy.readers.seviri_base import SEVIRICalibrationAlgorithm, SEVIRICalibrationHandler +import satpy.readers.seviri_base as sev COUNTS_INPUT = xr.DataArray( np.array([[377., 377., 377., 376., 375.], @@ -108,7 +109,7 @@ class TestSEVIRICalibrationAlgorithm(unittest.TestCase): def setUp(self): """Set up the SEVIRI Calibration algorithm for testing.""" - self.algo = SEVIRICalibrationAlgorithm( + self.algo = sev.SEVIRICalibrationAlgorithm( platform_id=PLATFORM_ID, scan_time=dt.datetime(2020, 8, 15, 13, 0, 40) ) @@ -148,57 +149,87 @@ class TestSeviriCalibrationHandler: def test_init(self): """Test initialization of the calibration handler.""" with pytest.raises(ValueError, match="Invalid calibration mode: INVALID. Choose one of (.*)"): - SEVIRICalibrationHandler( - platform_id=None, - channel_name=None, - coefs=None, - calib_mode="invalid", - scan_time=None - ) - - def _get_calibration_handler(self, calib_mode="NOMINAL", ext_coefs=None): + self._get_calibration_handler("IR_108", "INVALID") + + def _get_calibration_handler(self, channel, calib_mode="NOMINAL", ext_coefs=None): """Provide a calibration handler.""" - return SEVIRICalibrationHandler( - platform_id=324, - channel_name="IR_108", - coefs={ - "coefs": { - "NOMINAL": { + int_coefs = { + "NOMINAL": { + "IR_108": { "gain": 10, "offset": -1 }, - "GSICS": { + "VIS006": { "gain": 20, "offset": -2 }, - "EXTERNAL": ext_coefs or {} }, - "radiance_type": 1 - }, - calib_mode=calib_mode, + "GSICS": { + "IR_108": { + "gain": 30, + "offset": -3 + }, + }, + } + calib_params = sev.CalibParams( + mode=calib_mode, + internal_coefs=int_coefs, + external_coefs=ext_coefs, + radiance_type=1) + scan_params = sev.ScanParams( + platform_id=324, + channel_name=channel, scan_time=None ) + return sev.SEVIRICalibrationHandler(calib_params, scan_params) def test_calibrate_exceptions(self): """Test exceptions raised by the calibration handler.""" - calib = self._get_calibration_handler() + calib = self._get_calibration_handler("IR_108") with pytest.raises(ValueError, match="Invalid calibration invalid for channel IR_108"): calib.calibrate(None, "invalid") + @pytest.fixture + def external_coefs(self): + """Get external coefficients.""" + return {"IR_108": {"gain": 40, "offset": -4}} + + @pytest.fixture + def coefs_ir108_nominal_exp(self): + """Get expected IR coefficients in nominal calib mode.""" + return {"coefs": {"gain": 10, "offset": -1}, "mode": "NOMINAL"} + + @pytest.fixture + def coefs_vis006_exp(self): + """Get expected VIS coefficients.""" + return {"coefs": {"gain": 20, "offset": -2}, "mode": "NOMINAL"} + + @pytest.fixture + def coefs_ir108_gsics_exp(self): + """Get expected IR coefficients in GSICS calib mode.""" + return {"coefs": {"gain": 30, "offset": -3}, "mode": "GSICS"} + + @pytest.fixture + def coefs_ir108_external_exp(self): + """Get expected IR coefficients in the presence of external coefficients.""" + return {"coefs": {"gain": 40, "offset": -4}, "mode": "external"} + @pytest.mark.parametrize( - ("calib_mode", "ext_coefs", "expected"), + ("channel", "calib_mode", "ext_coefs", "expected"), [ - ("NOMINAL", {}, (10, -1)), - ("GSICS", {}, (20, -40)), - ("GSICS", {"gain": 30, "offset": -3}, (30, -3)), - ("NOMINAL", {"gain": 30, "offset": -3}, (30, -3)) + ("IR_108", "NOMINAL", None, lf("coefs_ir108_nominal_exp")), + ("IR_108", "GSICS", None, lf("coefs_ir108_gsics_exp")), + ("IR_108", "NOMINAL", lf("external_coefs"), lf("coefs_ir108_external_exp")), + # For VIS006 there's only nominal coefficients in this example + ("VIS006", "NOMINAL", None, lf("coefs_vis006_exp")), + ("VIS006", "GSICS", None, lf("coefs_vis006_exp")), + ("VIS006", "NOMINAL", lf("external_coefs"), lf("coefs_vis006_exp")) ] ) - def test_get_gain_offset(self, calib_mode, ext_coefs, expected): + def test_get_coefs(self, channel, calib_mode, ext_coefs, expected): """Test selection of gain and offset.""" - calib = self._get_calibration_handler(calib_mode=calib_mode, - ext_coefs=ext_coefs) - coefs = calib.get_gain_offset() + calib = self._get_calibration_handler(channel, calib_mode, ext_coefs) + coefs = calib.get_coefs() assert coefs == expected From 9c8c43b6805ebc9b0a16b8fed7ed679dca8068d4 Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper Date: Fri, 22 Nov 2024 08:50:57 +0000 Subject: [PATCH 3/7] Adapt SEVIRI Native reader --- satpy/readers/seviri_l1b_hrit.py | 2 +- satpy/readers/seviri_l1b_native.py | 60 +++++++++++++++++++----------- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/satpy/readers/seviri_l1b_hrit.py b/satpy/readers/seviri_l1b_hrit.py index d2db66bba0..b37feef46e 100644 --- a/satpy/readers/seviri_l1b_hrit.py +++ b/satpy/readers/seviri_l1b_hrit.py @@ -729,7 +729,7 @@ def pad_hrv_data(self, res): def calibrate(self, data, calibration): """Calibrate the data.""" calib_params = CalibParams( - mode=self.calib_mode, + mode=self.calib_mode.upper(), internal_coefs=self._get_calib_coefs(), external_coefs=self.ext_calib_coefs, radiance_type=self._get_radiance_type() diff --git a/satpy/readers/seviri_l1b_native.py b/satpy/readers/seviri_l1b_native.py index 3eaa9b4dfd..26ec141f61 100644 --- a/satpy/readers/seviri_l1b_native.py +++ b/satpy/readers/seviri_l1b_native.py @@ -119,8 +119,13 @@ SATNUM, VISIR_NUM_COLUMNS, VISIR_NUM_LINES, + CalibParams, + GsicsCoefficients, + MeirinkCoefficients, + NominalCoefficients, NoValidOrbitParams, OrbitPolynomialFinder, + ScanParams, SEVIRICalibrationHandler, add_scanline_acq_time, calculate_area_extent, @@ -619,12 +624,21 @@ def calibrate(self, data, dataset_id): """Calibrate the data.""" tic = dt.datetime.now() channel_name = dataset_id["name"] + band_idx = self._get_band_index(channel_name) + radiance_types = self.header["15_DATA_HEADER"]["ImageDescription"][ + "Level15ImageProduction"]["PlannedChanProcessing"] + calib_params = CalibParams( + mode=self.calib_mode.upper(), + internal_coefs=self._get_calib_coefs(channel_name), + external_coefs=self.ext_calib_coefs, + radiance_type=radiance_types[band_idx] + ) + scan_params = ScanParams( + self.platform_id, channel_name, self.observation_start_time + ) calib = SEVIRICalibrationHandler( - platform_id=self.platform_id, - channel_name=channel_name, - coefs=self._get_calib_coefs(channel_name), - calib_mode=self.calib_mode, - scan_time=self.observation_start_time + calib_params, + scan_params ) res = calib.calibrate(data, dataset_id["calibration"]) logger.debug("Calibration time " + str(dt.datetime.now() - tic)) @@ -632,30 +646,32 @@ def calibrate(self, data, dataset_id): def _get_calib_coefs(self, channel_name): """Get coefficients for calibration from counts to radiance.""" - # even though all the channels may not be present in the file, - # the header does have calibration coefficients for all the channels - # hence, this channel index needs to refer to full channel list - band_idx = list(CHANNEL_NAMES.values()).index(channel_name) - + band_idx = self._get_band_index(channel_name) coefs_nominal = self.header["15_DATA_HEADER"][ "RadiometricProcessing"]["Level15ImageCalibration"] coefs_gsics = self.header["15_DATA_HEADER"][ "RadiometricProcessing"]["MPEFCalFeedback"] - radiance_types = self.header["15_DATA_HEADER"]["ImageDescription"][ - "Level15ImageProduction"]["PlannedChanProcessing"] + nominal_coefs = NominalCoefficients( + channel_name, coefs_nominal["CalSlope"][band_idx], coefs_nominal["CalOffset"][band_idx] + ) + gsics_coefs = GsicsCoefficients( + channel_name, coefs_gsics["GSICSCalCoeff"][band_idx], coefs_gsics["GSICSOffsetCount"][band_idx] + ) + meirink_coefs = MeirinkCoefficients( + self.platform_id, channel_name, self.observation_start_time + ) return create_coef_dict( - coefs_nominal=( - coefs_nominal["CalSlope"][band_idx], - coefs_nominal["CalOffset"][band_idx] - ), - coefs_gsics=( - coefs_gsics["GSICSCalCoeff"][band_idx], - coefs_gsics["GSICSOffsetCount"][band_idx] - ), - ext_coefs=self.ext_calib_coefs.get(channel_name, {}), - radiance_type=radiance_types[band_idx] + nominal_coefs, + gsics_coefs, + meirink_coefs ) + def _get_band_index(self, channel_name): + # even though all the channels may not be present in the file, + # the header does have calibration coefficients for all the channels + # hence, this channel index needs to refer to full channel list + return list(CHANNEL_NAMES.values()).index(channel_name) + def _add_scanline_acq_time(self, dataset, dataset_id): """Add scanline acquisition time to the given dataset.""" if dataset_id["name"] == "HRV": From f0497a0580937bc36dd768d1f0df7e86ddb4aa32 Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper Date: Fri, 22 Nov 2024 09:13:12 +0000 Subject: [PATCH 4/7] Adapt SEVIRI netCDF reader --- satpy/readers/seviri_base.py | 18 ++++--------- satpy/readers/seviri_l1b_hrit.py | 7 ++--- satpy/readers/seviri_l1b_native.py | 10 +++---- satpy/readers/seviri_l1b_nc.py | 43 ++++++++++++++++-------------- 4 files changed, 33 insertions(+), 45 deletions(-) diff --git a/satpy/readers/seviri_base.py b/satpy/readers/seviri_base.py index 304de2e962..dd46f04e3b 100644 --- a/satpy/readers/seviri_base.py +++ b/satpy/readers/seviri_base.py @@ -663,7 +663,6 @@ def _check_calib_mode(self, calib_mode): def calibrate(self, data, calibration): """Calibrate the given data.""" - coefs = None if calibration == "counts": res = data elif calibration in ["radiance", "reflectance", @@ -688,17 +687,8 @@ def calibrate(self, data, calibration): res = self._algo.ir_calibrate( res, self._scan_params.channel_name, self._calib_params.radiance_type ) - if coefs: - self._update_attrs(res, coefs) return res - def _update_attrs(self, data, coefs): - data.attrs["calibration_parameters"] = { - "mode": coefs["mode"], - "gain": coefs["coefs"]["gain"], - "offset": coefs["coefs"]["offset"] - } - def get_coefs(self): """Get calibration coefficients.""" self._warn_fallback() @@ -985,11 +975,13 @@ def calculate_area_extent(area_dict): return (ll_c, ll_l, ur_c, ur_l) -def create_coef_dict(nominal_coefs, gsics_coefs, meirink_coefs): +def create_coef_dict(nominal_coefs, gsics_coefs=None, meirink_coefs=None): """Create coefficient dictionary expected by calibration class.""" coefs = nominal_coefs.get_coefs() - coefs.update(gsics_coefs.get_coefs()) - coefs.update(meirink_coefs.get_coefs(nominal_coefs.offset)) + if gsics_coefs: + coefs.update(gsics_coefs.get_coefs()) + if meirink_coefs: + coefs.update(meirink_coefs.get_coefs(nominal_coefs.offset)) return coefs diff --git a/satpy/readers/seviri_l1b_hrit.py b/satpy/readers/seviri_l1b_hrit.py index b37feef46e..15c382a54a 100644 --- a/satpy/readers/seviri_l1b_hrit.py +++ b/satpy/readers/seviri_l1b_hrit.py @@ -734,11 +734,8 @@ def calibrate(self, data, calibration): external_coefs=self.ext_calib_coefs, radiance_type=self._get_radiance_type() ) - scan_params = ScanParams( - platform_id=self.platform_id, - channel_name=self.channel_name, - scan_time=self.observation_start_time - ) + scan_params = ScanParams(self.platform_id, self.channel_name, + self.observation_start_time) calib = SEVIRICalibrationHandler(calib_params, scan_params) res = calib.calibrate(data, calibration) return res diff --git a/satpy/readers/seviri_l1b_native.py b/satpy/readers/seviri_l1b_native.py index 26ec141f61..8ef97b3a78 100644 --- a/satpy/readers/seviri_l1b_native.py +++ b/satpy/readers/seviri_l1b_native.py @@ -633,13 +633,9 @@ def calibrate(self, data, dataset_id): external_coefs=self.ext_calib_coefs, radiance_type=radiance_types[band_idx] ) - scan_params = ScanParams( - self.platform_id, channel_name, self.observation_start_time - ) - calib = SEVIRICalibrationHandler( - calib_params, - scan_params - ) + scan_params = ScanParams(self.platform_id, channel_name, + self.observation_start_time) + calib = SEVIRICalibrationHandler(calib_params, scan_params) res = calib.calibrate(data, dataset_id["calibration"]) logger.debug("Calibration time " + str(dt.datetime.now() - tic)) return res diff --git a/satpy/readers/seviri_l1b_nc.py b/satpy/readers/seviri_l1b_nc.py index fd19634fda..b75b2cc09d 100644 --- a/satpy/readers/seviri_l1b_nc.py +++ b/satpy/readers/seviri_l1b_nc.py @@ -29,10 +29,14 @@ from satpy.readers.seviri_base import ( CHANNEL_NAMES, SATNUM, + CalibParams, + NominalCoefficients, NoValidOrbitParams, OrbitPolynomialFinder, + ScanParams, SEVIRICalibrationHandler, add_scanline_acq_time, + create_coef_dict, get_cds_time, get_satpos, mask_bad_quality, @@ -185,32 +189,31 @@ def calibrate(self, dataset, dataset_id): if dataset_id["calibration"] == "counts": dataset.attrs["_FillValue"] = 0 - calib = SEVIRICalibrationHandler( - platform_id=int(self.platform_id), - channel_name=channel, - coefs=self._get_calib_coefs(dataset, channel), - calib_mode="NOMINAL", - scan_time=self.observation_start_time + band_idx = list(CHANNEL_NAMES.values()).index(channel) + radiance_type = self.nc["planned_chan_processing"].values[band_idx] + calib_params = CalibParams( + mode="NOMINAL", + internal_coefs=self._get_calib_coefs(dataset, channel), + external_coefs=self.ext_calib_coefs, + radiance_type=radiance_type ) - + scan_params = ScanParams( + int(self.platform_id), + channel, + self.observation_start_time + ) + calib = SEVIRICalibrationHandler(calib_params, scan_params) return calib.calibrate(dataset, calibration) def _get_calib_coefs(self, dataset, channel): - """Get coefficients for calibration from counts to radiance.""" - band_idx = list(CHANNEL_NAMES.values()).index(channel) + """Get coefficients for calibration from counts to radiance. + + Only nominal calibration coefficients are available in netCDF files. + """ offset = dataset.attrs["add_offset"].astype("float32") gain = dataset.attrs["scale_factor"].astype("float32") - # Only one calibration available here - return { - "coefs": { - "NOMINAL": { - "gain": gain, - "offset": offset - }, - "EXTERNAL": self.ext_calib_coefs.get(channel, {}) - }, - "radiance_type": self.nc["planned_chan_processing"].values[band_idx] - } + nominal_coefs = NominalCoefficients(channel, gain, offset) + return create_coef_dict(nominal_coefs) def _mask_bad_quality(self, dataset, dataset_info): """Mask scanlines with bad quality.""" From de5e12941ddbf19641a3b2b3a0bf6520ac1b12f5 Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper Date: Fri, 22 Nov 2024 09:18:50 +0000 Subject: [PATCH 5/7] Make methods smaller --- satpy/readers/seviri_l1b_hrit.py | 9 ++++++--- satpy/readers/seviri_l1b_native.py | 22 ++++++++++++++-------- satpy/readers/seviri_l1b_nc.py | 15 ++++++++++----- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/satpy/readers/seviri_l1b_hrit.py b/satpy/readers/seviri_l1b_hrit.py index 15c382a54a..f08650f331 100644 --- a/satpy/readers/seviri_l1b_hrit.py +++ b/satpy/readers/seviri_l1b_hrit.py @@ -728,6 +728,11 @@ def pad_hrv_data(self, res): def calibrate(self, data, calibration): """Calibrate the data.""" + calib = self._get_calibration_handler() + res = calib.calibrate(data, calibration) + return res + + def _get_calibration_handler(self): calib_params = CalibParams( mode=self.calib_mode.upper(), internal_coefs=self._get_calib_coefs(), @@ -736,9 +741,7 @@ def calibrate(self, data, calibration): ) scan_params = ScanParams(self.platform_id, self.channel_name, self.observation_start_time) - calib = SEVIRICalibrationHandler(calib_params, scan_params) - res = calib.calibrate(data, calibration) - return res + return SEVIRICalibrationHandler(calib_params, scan_params) def _mask_bad_quality(self, data): """Mask scanlines with bad quality.""" diff --git a/satpy/readers/seviri_l1b_native.py b/satpy/readers/seviri_l1b_native.py index 8ef97b3a78..18c6d9f711 100644 --- a/satpy/readers/seviri_l1b_native.py +++ b/satpy/readers/seviri_l1b_native.py @@ -623,22 +623,22 @@ def _get_hrv_channel(self): def calibrate(self, data, dataset_id): """Calibrate the data.""" tic = dt.datetime.now() + calib = self._get_calibration_handler(dataset_id) + res = calib.calibrate(data, dataset_id["calibration"]) + logger.debug("Calibration time " + str(dt.datetime.now() - tic)) + return res + + def _get_calibration_handler(self, dataset_id): channel_name = dataset_id["name"] - band_idx = self._get_band_index(channel_name) - radiance_types = self.header["15_DATA_HEADER"]["ImageDescription"][ - "Level15ImageProduction"]["PlannedChanProcessing"] calib_params = CalibParams( mode=self.calib_mode.upper(), internal_coefs=self._get_calib_coefs(channel_name), external_coefs=self.ext_calib_coefs, - radiance_type=radiance_types[band_idx] + radiance_type=self._get_radiance_type(channel_name) ) scan_params = ScanParams(self.platform_id, channel_name, self.observation_start_time) - calib = SEVIRICalibrationHandler(calib_params, scan_params) - res = calib.calibrate(data, dataset_id["calibration"]) - logger.debug("Calibration time " + str(dt.datetime.now() - tic)) - return res + return SEVIRICalibrationHandler(calib_params, scan_params) def _get_calib_coefs(self, channel_name): """Get coefficients for calibration from counts to radiance.""" @@ -668,6 +668,12 @@ def _get_band_index(self, channel_name): # hence, this channel index needs to refer to full channel list return list(CHANNEL_NAMES.values()).index(channel_name) + def _get_radiance_type(self, channel_name): + band_idx = self._get_band_index(channel_name) + radiance_types = self.header["15_DATA_HEADER"]["ImageDescription"][ + "Level15ImageProduction"]["PlannedChanProcessing"] + return radiance_types[band_idx] + def _add_scanline_acq_time(self, dataset, dataset_id): """Add scanline acquisition time to the given dataset.""" if dataset_id["name"] == "HRV": diff --git a/satpy/readers/seviri_l1b_nc.py b/satpy/readers/seviri_l1b_nc.py index b75b2cc09d..726caf83a1 100644 --- a/satpy/readers/seviri_l1b_nc.py +++ b/satpy/readers/seviri_l1b_nc.py @@ -189,21 +189,26 @@ def calibrate(self, dataset, dataset_id): if dataset_id["calibration"] == "counts": dataset.attrs["_FillValue"] = 0 - band_idx = list(CHANNEL_NAMES.values()).index(channel) - radiance_type = self.nc["planned_chan_processing"].values[band_idx] + calib = self._get_calibration_handler(dataset, channel) + return calib.calibrate(dataset, calibration) + + def _get_calibration_handler(self, dataset, channel): calib_params = CalibParams( mode="NOMINAL", internal_coefs=self._get_calib_coefs(dataset, channel), external_coefs=self.ext_calib_coefs, - radiance_type=radiance_type + radiance_type=self._get_radiance_type(channel) ) scan_params = ScanParams( int(self.platform_id), channel, self.observation_start_time ) - calib = SEVIRICalibrationHandler(calib_params, scan_params) - return calib.calibrate(dataset, calibration) + return SEVIRICalibrationHandler(calib_params, scan_params) + + def _get_radiance_type(self, channel): + band_idx = list(CHANNEL_NAMES.values()).index(channel) + return self.nc["planned_chan_processing"].values[band_idx] def _get_calib_coefs(self, dataset, channel): """Get coefficients for calibration from counts to radiance. From d73e44aed295ec588f8efb576473473475fb0f3c Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper Date: Fri, 22 Nov 2024 09:32:20 +0000 Subject: [PATCH 6/7] Remove calib fallback --- satpy/readers/seviri_l1b_hrit.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/satpy/readers/seviri_l1b_hrit.py b/satpy/readers/seviri_l1b_hrit.py index f08650f331..7cb4997782 100644 --- a/satpy/readers/seviri_l1b_hrit.py +++ b/satpy/readers/seviri_l1b_hrit.py @@ -460,7 +460,7 @@ class HRITMSGFileHandler(HRITFileHandler): """ def __init__(self, filename, filename_info, filetype_info, - prologue, epilogue, calib_mode="nominal", calib_fallback=None, + prologue, epilogue, calib_mode="nominal", ext_calib_coefs=None, include_raw_metadata=False, mda_max_array_size=100, fill_hrv=True, mask_bad_quality_scan_lines=True): @@ -480,7 +480,6 @@ def __init__(self, filename, filename_info, filetype_info, self.mda_max_array_size = mda_max_array_size self.fill_hrv = fill_hrv self.calib_mode = calib_mode - self.calib_fallback = calib_fallback self.ext_calib_coefs = ext_calib_coefs or {} self.mask_bad_quality_scan_lines = mask_bad_quality_scan_lines self._get_header() From 5ab316e91f4097d7f65bb8a8e12096c8c69cd11b Mon Sep 17 00:00:00 2001 From: Stephan Finkensieper Date: Fri, 22 Nov 2024 10:04:16 +0000 Subject: [PATCH 7/7] Remove duplicate warning --- satpy/readers/seviri_base.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/satpy/readers/seviri_base.py b/satpy/readers/seviri_base.py index dd46f04e3b..a262b2ff58 100644 --- a/satpy/readers/seviri_base.py +++ b/satpy/readers/seviri_base.py @@ -691,27 +691,12 @@ def calibrate(self, data, calibration): def get_coefs(self): """Get calibration coefficients.""" - self._warn_fallback() picker = utils.CalibrationCoefficientPicker(self._calib_params.internal_coefs, self._get_calib_wishlist(), default="NOMINAL", fallback="NOMINAL") return picker.get_coefs(self._scan_params.channel_name) - def _warn_fallback(self): - if self._should_warn_fallback(): - msg = f""" -{self._calib_params.mode} calibration coefficients are not available for all -channels and Satpy is falling back to nominal coefficients for these channels. -In the future this will raise an error. -""" - warnings.warn(msg, FutureWarning) - - def _should_warn_fallback(self): - is_gsics = self._calib_params.mode == "GSICS" - is_meirink = "MEIRINK" in self._calib_params.mode - return is_gsics or is_meirink - def _get_calib_wishlist(self): ext_coefs = self._calib_params.external_coefs or {} wishlist = {