Source code for satrain.target
"""
satrain.target
==============
The ``satrain.target`` module provides the :class:`TargetConfig` class to configure
the loading of the retrieval reference data.
Usage
-----
``TargetConfig`` objects can be passed to the :py:class:`satrain.evaluation.Evaluator` to configure
the MRMS pixels that are used in the evaluation of the retrieval. They can also be passed to
the dataset classes defined in :py:mod:`satrain.pytorch.datasets` to exclude low-quality pixels
from the training.
Members
-------
"""
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Any, Dict, Optional
import numpy as np
import xarray as xr
from satrain.utils import open_if_required
[docs]
@dataclass
class TargetConfig:
"""
The TargetConfig class is used to specify quality criteria for the precipitation target
data loaded for training and evaluating precipitation retrievals.
The loaded precipitation values that don't satisfy the quality requirements, will
be set to NAN. This will cause them to be ignored by the :class:`satrain.evaluation.Evaluator`.
"""
target: str = "surface_precip"
min_rqi: float = 0.5
min_valid_fraction: float = 1.0
no_snow: bool = False
no_hail: bool = False
min_gcf: Optional[float] = None
max_gcf: Optional[float] = None
def __init__(
self,
target: str = "surface_precip",
min_rqi: float = 0.5,
min_valid_fraction: float = 0.5,
no_snow: bool = False,
no_hail: bool = False,
min_gcf: Optional[float] = None,
max_gcf: Optional[float] = None,
precip_threshold: float = 1e-1,
heavy_precip_threshold: float = 1e1
):
"""
Args:
target: The name of the target variable. Should be 'surface_precip' for
gauge-corrected MRMS surface precipitation downsampled to 0.036-degree
resolution or 'surface_precip_fpavg' for footprint-average
preciptiation.
min_rqi: Pixels with radar-quality index (RQI) below this value will be masked.
min_valid_fraction: The ``valid_fraction`` represents the fraction of valid
native-MRMS pixels withing the downsampled 0.036-degree resolution pixels.
Pixels with ``valid_fractions`` below this value will be masked.
no_snow: If ``True``, pixels with non-zero snow fraction will be masked.
no_snow: If ``True``, pixels with non-zero hail fraction will be masked.
min_gcf: Pixels with a gauge-correction factor less than this value will be
masked.
max_gcf: Pixels with a gauge-correction factor greater than this will be
masked.
precip_threshold: The threshold to use to distinguish raining from
non-raining pixels.
heavy_precip_threshold: The threshold to use to identify heavy
precipitation.
"""
self.target = target
self.min_rqi = min_rqi
self.min_valid_fraction = min_valid_fraction
self.no_snow = no_snow
self.no_hail = no_hail
self.min_gcf = min_gcf
self.max_gcf = max_gcf
self.precip_threshold = precip_threshold
self.heavy_precip_threshold = heavy_precip_threshold
[docs]
def get_mask(self, target_data: Path | str | xr.Dataset) -> np.ndarray:
"""
Get mask identifying invalid reference samples according to the
target config's settings.
Args:
target_data: A Path or str pointing to a target data file or an xarray.Dataset containing
the data from a loaded retrieval target file.
Return:
A field of bool values identifying the target samples that
should be ignored.
"""
with open_if_required(target_data) as data:
target = data[self.target].data
valid = np.isfinite(target)
# Allow for numerical inaccuracies to avoid noisy masks for min_rqi = 1.0.
if "radar_quality_index" in data:
rqi = data["radar_quality_index"].data
valid *= (rqi - self.min_rqi) > -1e-3
# Allow for numerical inaccuracies to avoid noisy masks for min_valid_fraction = 1.0.
if "valid_fraction" in data:
valid_frac = data["valid_fraction"].data
valid *= valid_frac - self.min_valid_fraction > -1e-3
if self.no_snow:
snow_frac = data["snow_fraction"].data
valid *= snow_frac == 0.0
if self.no_hail:
hail_frac = data["hail_fraction"].data
valid *= hail_frac == 0.0
if self.min_gcf is not None:
gcf = data["gauge_correction_factor"].data
valid *= self.min_gcf <= gcf
if self.max_gcf is not None:
gcf = data["gauge_correction_factor"].data
valid *= gcf <= self.min_gcf
return ~valid
[docs]
def to_dict(self) -> Dict[str, Any]:
"""
.toml compatible dictionary representation of input config.
"""
dct = asdict(self)
return {
name: val for name, val in dct.items() if val is not None
}
[docs]
def load_reference_precip(self, target_data: Path | str | xr.Dataset) -> np.ndarray:
"""
Loads reference precip field data from a target file. The method ensure that the correct
target variable is selected and masks samples not satisfying the quality requirements
by setting them to NAN.
Args:
target_data: A Path or str pointing to a target data file or an xarray.Dataset containing
the data from a loaded retrieval target file.
Return:
A numpy.ndarray containing the loaded target data.
"""
with open_if_required(target_data) as data:
target = data[self.target].data.copy()
invalid = self.get_mask(data)
target[invalid] = np.nan
del data
return target.copy()
[docs]
def load_precip_mask(self, target_data: Path | str | xr.Dataset) -> np.ndarray:
"""
Load mask identifying precipitation identified according to the
target config object's heavy precipitation threshold.
Args:
target_data: A Path or str pointing to a target data file or an xarray.Dataset containing
the data from a loaded retrieval target file.
Return:
A boolean numpy.ndarray containing the heavy precipitation mask.
"""
with open_if_required(target_data) as data:
target = data[self.target].data
mask = (self.precip_threshold <= target).astype(np.float32)
invalid = self.get_mask(data)
if isinstance(mask, np.ndarray):
mask[invalid] = np.nan
return mask.copy()
[docs]
def load_heavy_precip_mask(self, target_data: Path | str | xr.Dataset) -> np.ndarray:
"""
Load mask identifying heavy precipitation identified according to the
target config object's heavy precipitation threshold.
Args:
target_data: A Path or str pointing to a target data file or an xarray.Dataset containing
the data from a loaded retrieval target file.
Return:
A boolean numpy.ndarray containing the heavy precipitation mask.
"""
with open_if_required(target_data) as data:
target = data[self.target].data
mask = (self.heavy_precip_threshold <= target).astype(np.float32)
invalid = self.get_mask(data)
if isinstance(mask, np.ndarray):
mask[invalid] = np.nan
return mask.copy()