Source code for geoips.interfaces.class_based.interpolators
# # # This source code is subject to the license referenced at
# # # https://github.com/NRLMMD-GEOIPS.
"""Interpolators interface class."""
import xarray as xr
from geoips.interfaces.class_based_plugin import BaseClassPlugin
from geoips.interfaces.base import BaseClassInterface
[docs]class BaseInterpolatorPlugin(BaseClassPlugin, abstract=True):
"""Base class for GeoIPS interpolator plugins."""
data_tree = False
def _pre_call(self, data=None, *args, _obp_initiated=False, **kwargs):
"""Prepare OBP interpolator inputs before invoking ``call``.
Legacy interpolators expect ``area_def``, ``input_xarray``,
``output_xarray``, and ``varlist`` as call arguments. Under OBP, those
values may arrive as DataTree dependencies and conduit-injected kwargs.
Normalize them here so ``BaseClassPlugin._invoke`` can remain generic and
still be the only method that calls ``call``.
"""
# Workflow mode usually arrives here with a multi-input tree. Script
# mode keeps the accumulated script tree in ``data``, then _invoke
# extracts upstream reader/interpolator and sector nodes into conduit
# kwargs such as ``xarray_obj`` and ``area_def``. Either shape needs
# normalization to the legacy interpolator call signature.
has_extracted_conduit_inputs = "xarray_obj" in kwargs or "area_def" in kwargs
if _obp_initiated and (
self._use_positional_unpacking(data, _obp_initiated)
or has_extracted_conduit_inputs
):
kwargs = self._prepare_obp_interpolator_kwargs(data, kwargs)
data = super()._pre_call(
data, *args, _obp_initiated=_obp_initiated, **kwargs
)
return data, kwargs
return super()._pre_call(data, *args, _obp_initiated=_obp_initiated, **kwargs)
def _prepare_obp_interpolator_kwargs(self, data, kwargs):
"""Populate legacy interpolator call kwargs from OBP inputs."""
input_xarray = kwargs.pop("xarray_obj", None)
area_def_found = kwargs.get("area_def") is not None
if input_xarray is None or not area_def_found:
input_xarray, area_def_found = self._collect_interpolator_inputs(
data, input_xarray, area_def_found
)
if input_xarray is None:
raise RuntimeError(
"Error: Could not find an input dataset to interpolate for "
f"interpolator plugin '{self.name}'."
)
if not area_def_found:
raise RuntimeError(
"Error: Could not determine an area definition for interpolator "
f"plugin '{self.name}'. Provide an area_def directly, or include "
"a sector step or sector input that can supply an area definition."
)
if kwargs.get("area_def") is None:
raise RuntimeError(
"Error: Could not determine an appropriate area definition to "
"interpolate to. Please ensure your interpolator step depends on a "
"sector before continuing."
)
kwargs["input_xarray"] = input_xarray
kwargs.setdefault("output_xarray", xr.Dataset())
if kwargs.get("varlist") is None:
kwargs["varlist"] = list(input_xarray.variables.keys())
return kwargs
@staticmethod
def _collect_interpolator_inputs(data, input_xarray=None, area_def_found=False):
"""Find interpolation input data and sector-derived area definitions."""
if not isinstance(data, xr.DataTree):
return input_xarray, area_def_found
for group in data.groups:
ds = data[group]
if not hasattr(ds, "attrs") or not ds.attrs:
continue
if ds.attrs.get("plugin_kind") == "sector":
area_def_found = True
elif input_xarray is None:
input_xarray = ds.to_dataset()
return input_xarray, area_def_found
[docs]class InterpolatorsInterface(BaseClassInterface):
"""Interpolation routine to apply when reprojecting data."""
name = "interpolators"
plugin_class = BaseInterpolatorPlugin
required_args = {
"2d": ["area_def", "input_xarray", "output_xarray", "varlist"],
"grid": ["area_def", "input_xarray", "output_xarray", "varlist"],
}
required_kwargs = {"2d": ["array_num"], "grid": ["array_num"]}
interpolators = InterpolatorsInterface()