Skip to content

Commit

Permalink
Add Frequency range
Browse files Browse the repository at this point in the history
  • Loading branch information
mraspaud committed Dec 1, 2021
1 parent 85656a8 commit d183d87
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 2 deletions.
101 changes: 100 additions & 1 deletion satpy/dataset/dataid.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from contextlib import suppress
from copy import copy, deepcopy
from enum import Enum, IntEnum
from typing import NoReturn
from typing import NamedTuple, NoReturn

import numpy as np

Expand Down Expand Up @@ -72,6 +72,105 @@ def __repr__(self):
return '<' + str(self) + '>'


class FrequencyRangeBase(NamedTuple):
"""Base class for frequency ranges.
This is needed because of this bug: https://bugs.python.org/issue41629
"""

central: float
bandwidth: float
unit: str = "GHz"


class FrequencyRange(FrequencyRangeBase):
"""A named tuple for frequency ranges.
The elements of the range are central and bandwidth values, and optionally
a unit (defaults to GHz). No clever unit conversion is done here, it's just
used for checking that two ranges are comparable.
This type is used for passive microwave sensors.
"""

def __eq__(self, other):
"""Return if two channel frequencies are equal.
Args:
other (tuple or scalar): (central frq, band width frq) or scalar frq
Return:
True if other is a scalar and min <= other <= max, or if other is
a tuple equal to self, False otherwise.
"""
if other is None:
return False
elif isinstance(other, numbers.Number):
return other in self
elif isinstance(other, (tuple, list)) and len(other) == 2:
return self[:2] == other
return super().__eq__(other)

def __ne__(self, other):
"""Return the opposite of `__eq__`."""
return not self == other

def __lt__(self, other):
"""Compare to another frequency."""
if other is None:
return False
return super().__lt__(other)

def __gt__(self, other):
"""Compare to another frequency."""
if other is None:
return True
return super().__gt__(other)

def __hash__(self):
"""Hash this tuple."""
return tuple.__hash__(self)

def __str__(self):
"""Format for print out."""
return "{0.central} {0.unit} ({0.bandwidth} {0.unit})".format(self)

def __contains__(self, other):
"""Check if this range contains *other*."""
if other is None:
return False
elif isinstance(other, numbers.Number):
return self.central - self.bandwidth/2. <= other <= self.central + self.bandwidth/2.

with suppress(AttributeError):
if self.unit != other.unit:
raise NotImplementedError("Can't compare frequency ranges with different units.")
return (self.central - self.bandwidth/2. <= other.central - other.bandwidth/2. and
self.central + self.bandwidth/2. >= other.central + other.bandwidth/2.)
return False

def distance(self, value):
"""Get the distance from value."""
if self == value:
try:
return abs(value.central - self.central)
except AttributeError:
if isinstance(value, (tuple, list)):
return abs(value[0] - self.central)
return abs(value - self.central)
else:
return np.inf

@classmethod
def convert(cls, frq):
"""Convert `frq` to this type if possible."""
if isinstance(frq, dict):
return cls(**frq)
return frq


wlklass = namedtuple("WavelengthRange", "min central max unit", defaults=('µm',)) # type: ignore


Expand Down
6 changes: 5 additions & 1 deletion satpy/readers/yaml_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import warnings
from abc import ABCMeta, abstractmethod
from collections import OrderedDict, deque
from contextlib import suppress
from fnmatch import fnmatch
from weakref import WeakValueDictionary

Expand Down Expand Up @@ -301,7 +302,10 @@ def load_ds_ids_from_config(self):
ds_info = dataset.copy()
for key in dsid.keys():
if isinstance(ds_info.get(key), dict):
ds_info.update(ds_info[key][dsid.get(key)])
with suppress(KeyError):
# KeyError is suppressed in case the key does not represent interesting metadata,
# eg a custom type
ds_info.update(ds_info[key][dsid.get(key)])
# this is important for wavelength which was converted
# to a tuple
ds_info[key] = dsid.get(key)
Expand Down
52 changes: 52 additions & 0 deletions satpy/tests/test_yaml_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,58 @@ def test_create_filehandlers(self):
self.assertEqual(len(self.reader.file_handlers['ftype1']), 3)


class TestFileYAMLReaderWithCustomIDKey(unittest.TestCase):
"""Test units from FileYAMLReader with custo id_keys."""

def setUp(self):
"""Set up the test case."""
from satpy.dataset.dataid import FrequencyRange, ModifierTuple
res_dict = {'reader': {'name': 'mhs_l1c_aapp',
'description': 'AAPP l1c Reader for AMSU-B/MHS data',
'sensors': ['mhs'],
'data_identification_keys': {'name': {'required': 'true'},
'frequency_range': {'type': FrequencyRange},
'resolution': None,
'polarization': {'enum': ['H', 'V']},
'calibration': {'enum': ['brightness_temperature'],
'transitive': 'true'},
'modifiers': {'required': 'true', 'default': [],
'type': ModifierTuple}}},
'datasets': {'1': {'name': '1',
'frequency_range': {'central': 89., 'bandwidth': 2.8, 'unit': 'GHz'},
'polarization': 'V',
'resolution': 16000,
'calibration': {
'brightness_temperature': {'standard_name': 'toa_brightness_temperature'}},
'coordinates': ['longitude', 'latitude'],
'file_type': 'mhs_aapp_l1c'},
'latitude': {'name': 'latitude',
'resolution': 16000,
'file_type': 'mhs_aapp_l1c',
'standard_name': 'latitude',
'units': 'degrees_north'},
'longitude': {'name': 'longitude',
'resolution': 16000,
'file_type': 'mhs_aapp_l1c',
'standard_name': 'longitude',
'units': 'degrees_east'}},
'file_types': {'mhs_aapp_l1c': {'file_reader': BaseFileHandler,
'file_patterns': [
'mhsl1c_{platform_shortname}_{start_time:%Y%m%d_%H%M}_{orbit_number:05d}.l1c']}}} # noqa
self.config = res_dict
self.reader = yr.FileYAMLReader(res_dict,
filter_parameters={
'start_time': datetime(2000, 1, 1),
'end_time': datetime(2000, 1, 2),
})

def test_custom_type_with_dict_contents_gets_parsed_correctly(self):
"""Test custom type with dictionary contents gets parsed correctly."""
from satpy.dataset.dataid import FrequencyRange
ds_ids = list(self.reader.all_dataset_ids)
assert ds_ids[0]["frequency_range"] == FrequencyRange(89., 2.8, "GHz")


class TestFileFileYAMLReader(unittest.TestCase):
"""Test units from FileYAMLReader."""

Expand Down

0 comments on commit d183d87

Please sign in to comment.