Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions cmac/cmac_ppi_quicklooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
generate_radar_name, generate_radar_time_begin)

from .config import get_plot_values, get_field_names
from .gate_id import get_gate_id_categories, gate_id_has_category

plt.switch_backend('agg')

Expand Down Expand Up @@ -190,13 +191,12 @@ def _range(key, default):

# Four panel plot of gate_id, velocity_texture, reflectivity, and
# cross_correlation_ratio.
cat_dict = {}
gate_id_field = radar.fields['gate_id']
cat_dict = get_gate_id_categories(gate_id_field)
print('##')
print('## Keys for each gate id are as follows:')
for i, pair_str in enumerate(radar.fields['gate_id']['notes'].split(',')):
pair_str = pair_str.split(':')[1].strip()
print('## ', str(pair_str))
cat_dict.update({pair_str: i})
for label in sorted(cat_dict, key=cat_dict.get):
print('## ', str(label))
sorted_cats = sorted(cat_dict.items(), key=operator.itemgetter(1))
cat_colors = dict(cat_colors_cfg)
lab_colors = [cat_colors[kitty[0]] for kitty in sorted_cats]
Expand All @@ -218,7 +218,8 @@ def _range(key, default):
colors='k')

cbax = ax[0, 0]
if 'ground_clutter' in radar.fields.keys() or 'terrain_blockage' in radar.fields['gate_id']['notes']:
if ('ground_clutter' in radar.fields.keys()
or gate_id_has_category(gate_id_field, 'terrain_blockage')):
tick_locs = np.linspace(
0, len(sorted_cats) - 1, len(sorted_cats)) + 0.5
else:
Expand Down
13 changes: 7 additions & 6 deletions cmac/cmac_rhi_quicklooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
generate_radar_name, generate_radar_time_begin)

from .config import get_plot_values, get_field_names
from .gate_id import get_gate_id_categories, gate_id_has_category

plt.switch_backend('agg')

Expand Down Expand Up @@ -106,13 +107,12 @@ def _range(key, default):

# Four panel plot of gate_id, velocity_texture, reflectivity, and
# cross_correlation_ratio.
cat_dict = {}
gate_id_field = radar.fields['gate_id']
cat_dict = get_gate_id_categories(gate_id_field)
print('##')
print('## Keys for each gate id are as follows:')
for i, pair_str in enumerate(radar.fields['gate_id']['notes'].split(',')):
pair_str = pair_str.split(':')[1].strip()
print('## ', str(pair_str))
cat_dict.update({pair_str: i})
for label in sorted(cat_dict, key=cat_dict.get):
print('## ', str(label))
sorted_cats = sorted(cat_dict.items(), key=operator.itemgetter(1))

cat_colors = dict(cat_colors_cfg)
Expand All @@ -126,7 +126,8 @@ def _range(key, default):
cmap=cmap, vmin=0, vmax=6)

cbax = ax[0, 0]
if 'ground_clutter' in radar.fields.keys() or 'terrain_blockage' in radar.fields['gate_id']['notes']:
if ('ground_clutter' in radar.fields.keys()
or gate_id_has_category(gate_id_field, 'terrain_blockage')):
tick_locs = np.linspace(
0, len(sorted_cats) - 1, len(sorted_cats)) + 0.5
else:
Expand Down
91 changes: 91 additions & 0 deletions cmac/gate_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""
Helpers for interpreting the category metadata attached to a ``gate_id``
(hydrometeor ID) radar field.

CMAC's own gate id fields document their categories with a ``notes``
attribute, which in practice shows up in a few different shapes:

- ``"0: multi_trip, 1: rain, 2: snow"`` -- comma separated ``"index: label"``
pairs, colon separated.
- ``"0 multi_trip, 1 rain, 2 snow"`` -- comma separated ``"index label"``
pairs, whitespace separated.
- ``"multi_trip rain snow melting no_scatter clutter terrain_blockage"`` --
a plain, unindexed list of labels in order, with no indices or commas at
all.

Fields that follow the CF conventions instead (or radar objects re-read
from a file that converted ``notes`` on save) may document the same
information with a ``flag_meanings`` attribute and a parallel
``flag_values`` attribute (the matching integer codes). ``flag_meanings``
is generally comma separated, though a plain whitespace separated string
is also accepted.
"""

import re

_PAIR_SEP_RE = re.compile(r'[:\s]+')


def _label_from_pair(pair_str):
"""Extract the category label from a single ``"index: label"`` pair,
where the index/label separator is a colon, whitespace, or both."""
parts = _PAIR_SEP_RE.split(pair_str.strip(), maxsplit=1)
return parts[-1].strip()


def _split_list(text):
"""Split a comma or whitespace separated list of labels into its
individual, stripped entries."""
if ',' in text:
parts = text.split(',')
else:
parts = text.split()
return [part.strip() for part in parts if part.strip()]


def _labels_from_notes(notes):
"""Return the ordered list of category labels encoded in a ``notes``
attribute, handling both indexed ``"index: label"``/``"index label"``
pairs and a plain, unindexed list of labels."""
pieces = [p.strip() for p in notes.split(',') if p.strip()]
if len(pieces) > 1 or (pieces and ':' in pieces[0]):
return [_label_from_pair(piece) for piece in pieces]
return _split_list(notes)


def get_gate_id_categories(gate_id_field):
"""
Return a dict mapping each gate id category label to its integer code.

Parameters
----------
gate_id_field : dict
A Py-ART field dictionary, e.g. ``radar.fields['gate_id']``.

"""
if 'notes' in gate_id_field:
labels = _labels_from_notes(gate_id_field['notes'])
return {label: i for i, label in enumerate(labels)}

if 'flag_meanings' in gate_id_field and 'flag_values' in gate_id_field:
labels = _split_list(gate_id_field['flag_meanings'])
values = gate_id_field['flag_values']
return {label: int(value) for label, value in zip(labels, values)}

raise KeyError(
"The 'gate_id' field must have either a 'notes' attribute or "
"'flag_values'/'flag_meanings' attributes describing its "
"categories.")


def gate_id_has_category(gate_id_field, category):
"""
Return True if ``category`` is one of the documented categories of a
``gate_id`` field, whether documented via ``notes`` or via
``flag_meanings``.
"""
if 'notes' in gate_id_field:
return category in _labels_from_notes(gate_id_field['notes'])
if 'flag_meanings' in gate_id_field:
return category in _split_list(gate_id_field['flag_meanings'])
return False
Loading