Skip to content

Commit

Permalink
Merge pull request #57 from daberer/master
Browse files Browse the repository at this point in the history
Add error maps
  • Loading branch information
wpreimes authored Jan 24, 2023
2 parents 9abb5db + 4098f40 commit 583a084
Show file tree
Hide file tree
Showing 10 changed files with 440 additions and 39 deletions.
44 changes: 42 additions & 2 deletions src/qa4sm_reader/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import warnings

import cartopy.crs as ccrs
import matplotlib.colors as cl
import matplotlib.pyplot as plt

# PLOT DEFAULT SETTINGS
Expand Down Expand Up @@ -62,11 +63,44 @@
"frm_class": ['frm_class'],
}

# === calculation errors (pytesmo) === #TODO: import from pytesmo
status = {
-1: 'Other error',
0: 'Success',
1: 'Not enough data',
2: 'Metric calculation failed',
3: 'Temporal matching failed',
4: 'No overlap for temporal match',
5: 'Scaling failed',
6: 'Unexpected validation error',
7: 'Missing GPI data',
8: 'Data reading failed'
}

# helper dict to replace some error codes and have merged categories
# (e.g.: No overlap for temporal match -> Temporal matching failed)
status_replace = {
4: 3,
7: 1,
}

# === colormaps used for plotting metrics ===
# Colormaps can be set for classes of similar metrics or individually for metrics.
# Any colormap name can be used, that works with matplotlib.pyplot.cm.get_cmap('colormap')
# more on colormaps: https://matplotlib.org/users/colormaps.html | https://morphocode.com/the-use-of-color-in-maps/


def get_status_colors():
# function to get custom cmap for calculation errors
# limited to 14 different error entries to produce distinct colors
cmap = plt.cm.get_cmap('Set3', len(status) - 2)
colors = [cmap(i) for i in range(cmap.N)]
colors.insert(0, (0, 0.66666667, 0.89019608, 1.0))
colors.insert(0, (0.45882353, 0.08235294, 0.11764706, 1.0))
cmap = cl.ListedColormap(colors=colors)
return cmap


_cclasses = {
'div_better': plt.cm.get_cmap(
'RdYlBu'
Expand All @@ -81,6 +115,8 @@
), # sequential: increasing value bad (p_R, p_rho, rmsd, ubRMSD, RSS)
'seq_better': plt.cm.get_cmap(
'YlGn'), # sequential: increasing value good (n_obs, STDerr)
'qua_neutr':
get_status_colors(), # qualitative category with 2 forced colors
}

_colormaps = { # from /qa4sm/validator/validation/graphics.py
Expand All @@ -102,6 +138,7 @@
'snr': _cclasses['div_better'],
'err_std': _cclasses['seq_worse'],
'beta': _cclasses['div_neutr'],
'status': _cclasses['qua_neutr'],
}

# Colorbars for difference plots
Expand Down Expand Up @@ -132,9 +169,9 @@
0: ['n_obs'],
2: [
'R', 'p_R', 'rho', 'p_rho', 'RMSD', 'BIAS', 'urmsd', 'mse', 'mse_corr',
'mse_bias', 'mse_var', 'RSS', 'tau', 'p_tau'
'mse_bias', 'mse_var', 'RSS', 'tau', 'p_tau', 'status'
],
3: ['snr', 'err_std', 'beta']
3: ['snr', 'err_std', 'beta', 'status']
}

# === variable template ===
Expand Down Expand Up @@ -196,6 +233,7 @@
'snr': [None, None],
'err_std': [None, None],
'beta': [None, None],
'status': [-1, len(status)-2],
}
# mask values out of range
_metric_mask_range = {
Expand Down Expand Up @@ -226,6 +264,7 @@
'snr': ' in dB',
'err_std': ' in {}',
'beta': ' in {}',
'status': '',
}


Expand Down Expand Up @@ -291,6 +330,7 @@ def get_metric_units(dataset, raise_error=False):
'snr': 'Signal-to-noise ratio',
'err_std': 'Error standard deviation',
'beta': 'TC scaling coefficient',
'status': 'Validation errors'
}

# BACKUPS
Expand Down
39 changes: 36 additions & 3 deletions src/qa4sm_reader/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

class MixinVarmeta:
"""Mixin class to provide functions that are common to the MetricVariable and ConfidenceInterval subclasses"""

@property
def pretty_name(self):
"""Create a nice name for the variable"""
Expand Down Expand Up @@ -63,8 +64,13 @@ def get_varmeta(self) -> (tuple, tuple, tuple):
ref_ds = self.Datasets.dataset_metadata(self.parts['ref_id'])
mds = self.Datasets.dataset_metadata(self.parts['sat_id0'])
dss = None

# if metric is status and globals.metric_groups is 3, add third dataset
if self.g == 3 and self.metric == 'status':
dss = self.Datasets.dataset_metadata(self.parts['sat_id1'])

# if metric is TC, add third dataset
if self.g == 3:
elif self.g == 3:
mds = self.Datasets.dataset_metadata(self.parts['mds_id'])
dss = self.Datasets.dataset_metadata(self.parts['sat_id1'])
if dss == mds:
Expand All @@ -84,6 +90,7 @@ class QA4SMDatasets():
1-based and 0-based index number of the datasets, respectively. For newer validations, these are always
the same
"""

def __init__(self, global_attrs):
"""
Parameters
Expand Down Expand Up @@ -310,6 +317,7 @@ def dataset_metadata(self, id: int, element: str or list = None) -> tuple:

class QA4SMVariable():
"""Super class for all variable types in the validations (MetricVariable, CI and Metadata)"""

def __init__(self, varname, global_attrs, values=None):
"""
Validation results for a validation metric and a combination of datasets.
Expand Down Expand Up @@ -372,6 +380,25 @@ def is_CI(self):
def ismetric(self) -> bool:
return self.g is not None

def _parse_wrap(self, pattern, g):
"""Wrapper function that handles case of metric 'status' that occurs
in two globals.metric_groups (2,3). This is because a status array
can be the result of a validation between two or three datasets (tc)
"""
# ignore this case - (status is also in globals.metric_groups 2 but
# should be treated as group 3)
if self.varname.startswith('status') and (self.varname.count('_and_')
== 2) and g == 2:
return None
# parse self.varname when three datasets
elif self.varname.startswith('status') and (self.varname.count('_and_')
== 2) and g == 3:
template = globals.var_name_ds_sep[3]
return parse(
'{}{}'.format(globals.var_name_metric_sep[2], template),
self.varname)
return parse(pattern, self.varname)

def _parse_varname(self) -> (str, int, dict):
"""
Parse the name to get the metric, group and variable data
Expand All @@ -393,7 +420,8 @@ def _parse_varname(self) -> (str, int, dict):
template = ''
pattern = '{}{}'.format(globals.var_name_metric_sep[g], template)
# parse infromation from pattern and name
parts = parse(pattern, self.varname)

parts = self._parse_wrap(pattern, g)

if parts is not None and parts['metric'] in globals.metric_groups[
g]:
Expand All @@ -411,6 +439,7 @@ def _parse_varname(self) -> (str, int, dict):

class MetricVariable(QA4SMVariable, MixinVarmeta):
"""Class that describes a metric variable, i.e. the metric for a specific set of Datasets"""

def __init__(self, varname, global_attrs, values=None):
super().__init__(varname, global_attrs, values)

Expand All @@ -420,6 +449,7 @@ def __init__(self, varname, global_attrs, values=None):

class ConfidenceInterval(QA4SMVariable, MixinVarmeta):
"""Class for a MetricVariable representing confidence intervals"""

def __init__(self, varname, global_attrs, values=None):
super().__init__(varname, global_attrs, values)

Expand All @@ -431,6 +461,7 @@ def __init__(self, varname, global_attrs, values=None):

class Metadata(QA4SMVariable):
"""Class for a MetricVariable representing metadata (only with ISMN as reference)"""

def __init__(self, varname, global_attrs, values=None):
super().__init__(varname, global_attrs, values)

Expand All @@ -451,6 +482,7 @@ def pretty_name(self) -> str:

class QA4SMMetric():
"""Class for validation metric"""

def __init__(self, name, variables_list=None):

self.name = name
Expand All @@ -476,7 +508,8 @@ def _get_attribute(self, attr: str):
"""
for n, Var in enumerate(self.variables):
value = getattr(Var, attr)
if n != 0:
# special case for "status" attribute (self.g can be 2 or 3)
if n != 0 and not Var.varname.startswith('status'):
assert value == previous, "The attribute {} is not equal in all variables".format(
attr)
previous = value
Expand Down
4 changes: 4 additions & 0 deletions src/qa4sm_reader/plot_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ def plot_all(filepath: str,
warnings.warn(
"Too few points are available to generate metadata-based plots"
)
except AttributeError as e:
warnings.warn(
f"Error when trying to plot_all for triple collocation nc files. \nIssue: #59 {e}"
)

if save_csv:
out_csv = plotter.save_stats()
Expand Down
Loading

0 comments on commit 583a084

Please sign in to comment.