Skip to content

Commit

Permalink
Use explicit channel names
Browse files Browse the repository at this point in the history
  • Loading branch information
sfinkens committed Jun 12, 2024
1 parent c4153ba commit dca1a18
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 65 deletions.
114 changes: 64 additions & 50 deletions satpy/readers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,53 +477,79 @@ def remove_earthsun_distance_correction(reflectance, utc_date=None):


class CalibrationCoefficientSelector:
"""Helper for choosing coefficients out of multiple options."""
"""Helper for choosing coefficients out of multiple options.
Example: Three sets of coefficients are available (nominal, meirink, gsics).
Calibrate channel 1 with "meirink" and channels 2/3 with "gsics".
1. Setup
.. code-block:: python
from satpy.readers.utils import CalibrationCoefficientSelector
from satpy.tests.utils import make_dataid
coefs = {
"nominal": {
"ch1": "nominal_ch1",
"ch2": "nominal_ch2",
"ch3": "nominal_ch3"
},
"meirink": {
"ch1": "meirink_ch1",
},
"gsics": {
"ch2": "gsics_ch2",
# ch3 coefficients are missing
}
}
modes = {
"meirink": ["ch1"],
"gsics": ["ch2", "ch3"]
}
ch1 = make_dataid(name="ch1")
ch2 = make_dataid(name="ch2")
ch3 = make_dataid(name="ch3")
2. Query:
.. code-block:: python
>>> s = CalibrationCoefficientSelector(coefs, modes)
>>> s.get_coefs(ch1)
"meirink_ch1"
>>> s.get_coefs(ch2)
"gsics_ch2"
>>> s.get_coefs(ch3)
KeyError: 'No gsics calibration coefficients for ch3'
3. Fallback to nominal for ch3:
.. code-block:: python
>>> s = CalibrationCoefficientSelector(coefs, modes, fallback="nominal")
>>> s.get_coefs(ch3)
"nominal_ch3"
"""

def __init__(self, coefs, modes=None, default="nominal", fallback=None, refl_threshold=1):
def __init__(self, coefs, modes=None, default="nominal", fallback=None):
"""Initialize the coefficient selector.
Args:
coefs (dict): One set of calibration coefficients for each calibration
mode, for example ::
{
"nominal": {
"ch1": nominal_coefs_ch1,
"ch2": nominal_coefs_ch2
},
"gsics": {
"ch2": gsics_coefs_ch2
}
}
The actual coefficients can be of any type (reader-specific).
modes (dict): Desired calibration modes per channel type ::
{
"reflective": "nominal",
"emissive": "gsics"
}
or per channel ::
{
"VIS006": "nominal",
"IR_108": "gsics"
}
coefs (dict): One set of calibration coefficients for each
calibration mode. The actual coefficients can be of any type
(reader-specific).
modes (dict): Desired calibration modes
default (str): Default coefficients to be used if no mode has been
specified. Default: "nominal".
fallback (str): Fallback coefficients if the desired coefficients
are not available for some channel.
refl_threshold: Central wavelengths below/above this threshold are
considered reflective/emissive. Default is 1um.
"""
self.coefs = coefs
self.modes = modes or {}
self.default = default
self.fallback = fallback
self.refl_threshold = refl_threshold
if self.default not in self.coefs:
raise KeyError("Need at least default coefficients")
if self.fallback and self.fallback not in self.coefs:
Expand All @@ -548,19 +574,7 @@ def _get_coefs(self, dataset_id, mode):
raise KeyError(f"No {mode} calibration coefficients for {ds_name}")

def _get_mode(self, dataset_id):
try:
return self._get_mode_for_channel(dataset_id)
except KeyError:
return self._get_mode_for_channel_type(dataset_id)

def _get_mode_for_channel(self, dataset_id):
return self.modes[dataset_id["name"]]

def _get_mode_for_channel_type(self, dataset_id):
ch_type = self._get_channel_type(dataset_id)
return self.modes.get(ch_type, self.default)

def _get_channel_type(self, dataset_id):
if dataset_id["wavelength"].central < self.refl_threshold:
return "reflective"
return "emissive"
for mode, channels in self.modes.items():
if dataset_id["name"] in channels:
return mode
return self.default
26 changes: 11 additions & 15 deletions satpy/tests/reader_tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,16 +538,12 @@ def fixture_coefs(self):
@pytest.fixture(name="ch1")
def fixture_ch1(self):
"""Make fake data ID."""
return make_dataid(name="ch1", wavelength=(0.6, 0.7, 0.8))

@pytest.fixture(name="ch2")
def fixture_ch2(self):
"""Make fake data ID."""
return make_dataid(name="ch2", wavelength=(10, 11, 12))
return make_dataid(name="ch1")

@pytest.fixture(name="dataset_ids")
def fixture_dataset_ids(self, ch1, ch2):
def fixture_dataset_ids(self, ch1):
"""Make fake data IDs."""
ch2 = make_dataid(name="ch2")
return [ch1, ch2]

@pytest.mark.parametrize(
Expand All @@ -558,16 +554,16 @@ def fixture_dataset_ids(self, ch1, ch2):
{"ch1": "nominal_ch1", "ch2": "nominal_ch2"}
),
(
{"reflective": "mode1"},
{"ch1": "mode1_ch1", "ch2": "nominal_ch2"}
{"nominal": ["ch1", "ch2"]},
{"ch1": "nominal_ch1", "ch2": "nominal_ch2"}
),
(
{"reflective": "mode1", "emissive": "mode2"},
{"ch1": "mode1_ch1", "ch2": "mode2_ch2"}
{"mode1": ["ch1"]},
{"ch1": "mode1_ch1", "ch2": "nominal_ch2"}
),
(
{"ch1": "mode1"},
{"ch1": "mode1_ch1", "ch2": "nominal_ch2"}
{"mode1": ["ch1"], "mode2": ["ch2"]},
{"ch1": "mode1_ch1", "ch2": "mode2_ch2"}
),
]
)
Expand All @@ -582,14 +578,14 @@ def test_get_coefs(self, dataset_ids, coefs, calib_modes, expected):

def test_missing_coefs(self, coefs, ch1):
"""Test handling of missing coefficients."""
calib_modes = {"reflective": "mode2"}
calib_modes = {"mode2": ["ch1"]}
s = CalibrationCoefficientSelector(coefs, calib_modes)
with pytest.raises(KeyError, match="No mode2 calibration *"):
s.get_coefs(ch1)

def test_fallback_to_nominal(self, coefs, ch1):
"""Test falling back to nominal coefficients."""
calib_modes = {"reflective": "mode2"}
calib_modes = {"mode2": ["ch1"]}
s = CalibrationCoefficientSelector(coefs, calib_modes, fallback="nominal")
assert s.get_coefs(ch1) == "nominal_ch1"

Expand Down

0 comments on commit dca1a18

Please sign in to comment.