Skip to content

Commit f976ae3

Browse files
committed
moved to new dvue functionality
1 parent cf1d7c6 commit f976ae3

2 files changed

Lines changed: 142 additions & 17 deletions

File tree

schismviz/schismcalibplotui.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,32 @@
1111
from holoviews import opts
1212

1313
from schimpy import batch_metrics
14+
from dvue.catalog import DataReferenceReader, DataReference, DataCatalog
1415
from dvue.dataui import DataUI, DataUIManager
1516
from . import datastore, schismstudy
1617
from vtools.functions.filter import cosine_lanczos
1718
from dvue.utils import interpret_file_relative_to
1819

1920
# from .calibplot import tsplot, scatterplot, calculate_metrics, regression_line_plots
2021

22+
23+
class SchismCalibNullReader(DataReferenceReader):
24+
"""Placeholder reader for SchismCalibPlotUIManager catalog entries.
25+
26+
All data retrieval happens inside
27+
:meth:`SchismCalibPlotUIManager.create_panel` → ``plot_metrics()``;
28+
``getData()`` is never called on these references.
29+
"""
30+
31+
def load(self, **attributes) -> pd.DataFrame:
32+
raise NotImplementedError(
33+
"SchismCalibPlotUIManager entries are rendered via create_panel(), not getData()."
34+
)
35+
36+
def __repr__(self) -> str:
37+
return "SchismCalibNullReader()"
38+
39+
2140
VAR_to_PARAM = {
2241
"flow": "flow",
2342
"elev": "elev",
@@ -222,6 +241,7 @@ def _ensure_list(v):
222241
self.dcat["full_id"] = (
223242
self.dcat["station_id"].astype(str) + "_" + self.dcat["subloc"].astype(str)
224243
)
244+
self._dvue_catalog = self._build_dvue_catalog()
225245

226246
def get_widgets(self):
227247
return pn.Column(pn.pane.Markdown("UI Controls Placeholder"))
@@ -251,6 +271,35 @@ def get_data_catalog(self):
251271
scat = scat[scat["id"].isin(self.selected_stations)].reset_index(drop=True)
252272
return scat
253273

274+
@property
275+
def data_catalog(self) -> DataCatalog:
276+
return self._dvue_catalog
277+
278+
def _build_dvue_catalog(self) -> DataCatalog:
279+
_reader = SchismCalibNullReader()
280+
dfcat = self.get_data_catalog()
281+
geo_crs = (
282+
str(dfcat.crs)
283+
if isinstance(dfcat, gpd.GeoDataFrame) and dfcat.crs is not None
284+
else None
285+
)
286+
catalog = DataCatalog(crs=geo_crs)
287+
for _, row in dfcat.iterrows():
288+
ref_name = f"{row['id']}_{row['variable']}"
289+
attrs = {k: v for k, v in row.items() if k != "geometry"}
290+
if "geometry" in row.index and row["geometry"] is not None:
291+
attrs["geometry"] = row["geometry"]
292+
try:
293+
catalog.add(DataReference(
294+
_reader,
295+
name=ref_name,
296+
cache=False,
297+
**attrs,
298+
))
299+
except ValueError:
300+
pass # duplicate id+variable; skip
301+
return catalog
302+
254303
def get_table_column_width_map(self):
255304
"""only columns to be displayed in the table should be included in the map"""
256305
column_width_map = {
@@ -386,7 +435,10 @@ def schism_plot_template(
386435
)
387436
df = df.loc[ex_avg_time_window]
388437
df = df.resample(dfobs.index.freq).mean()
389-
dff = cosine_lanczos(df.loc[ex_avg_time_window], "40H")
438+
# Interpolate internal NaN gaps before filtering so the cosine-Lanczos
439+
# kernel does not propagate NaN across gappy observations.
440+
df_for_filter = df.loc[ex_avg_time_window].interpolate(method="time")
441+
dff = cosine_lanczos(df_for_filter, "40H")
390442
df = df.loc[slice(*inst_time_window), :]
391443
dff = dff.loc[slice(*avg_time_window), :]
392444
#

schismviz/schismui.py

Lines changed: 89 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import holoviews as hv
55

66
hv.extension("bokeh")
7+
from dvue.catalog import DataReferenceReader, DataReference, DataCatalog
78
from dvue.dataui import DataUI
89
from dvue.tsdataui import TimeSeriesDataUIManager
910
from dvue import utils
@@ -15,6 +16,56 @@
1516
logger = logging.getLogger(__name__)
1617

1718

19+
class SchismDataReferenceReader(DataReferenceReader):
20+
"""Reads a single SCHISM time series from a study output file or the
21+
observation datastore.
22+
23+
``source == "datastore"`` rows are fetched via the observation datastore;
24+
all other rows are fetched from the matching :class:`SchismStudy` output
25+
files. ``convert_units`` is checked on the owning manager at every
26+
:meth:`load` call. Caching is intentionally disabled on all
27+
:class:`~dvue.catalog.DataReference` objects using this reader so that
28+
toggling ``convert_units`` is always reflected immediately.
29+
30+
Parameters
31+
----------
32+
study_dir_map : dict
33+
Mapping of ``str(output_dir)`` → :class:`SchismStudy`.
34+
obs_datastore : StationDatastore or None
35+
Observation datastore for ``source == "datastore"`` entries.
36+
manager : SchismOutputUIDataManager
37+
Owning manager; provides the reactive ``convert_units`` flag.
38+
"""
39+
40+
def __init__(self, study_dir_map, obs_datastore, manager):
41+
self._study_dir_map = study_dir_map
42+
self._datastore = obs_datastore
43+
self._manager = manager
44+
45+
def load(self, **attributes) -> pd.DataFrame:
46+
source = attributes.get("source", "")
47+
unit = attributes.get("unit", "")
48+
if source == "datastore":
49+
df = self._datastore.get_data(attributes)
50+
if self._manager.convert_units:
51+
df, unit = schismstudy.convert_to_SI(df, unit)
52+
else:
53+
base_dir = str(pathlib.Path(attributes["filename"]).parent)
54+
study = self._study_dir_map[base_dir]
55+
try:
56+
df = study.get_data(attributes)
57+
except KeyError as e:
58+
logger.warning(str(e).strip("'\""))
59+
raise
60+
df = df[slice(df.first_valid_index(), df.last_valid_index())]
61+
df.attrs["unit"] = unit
62+
df.attrs["ptype"] = "INST-VAL"
63+
return df
64+
65+
def __repr__(self) -> str:
66+
return f"SchismDataReferenceReader(sources={list(self._study_dir_map.keys())!r})"
67+
68+
1869
class SchismOutputUIDataManager(TimeSeriesDataUIManager):
1970

2071
convert_units = param.Boolean(default=True, doc="Convert units to SI")
@@ -43,7 +94,16 @@ def __init__(self, *studies, datastore=None, time_range=None, **kwargs):
4394
self.color_cycle_column = "id"
4495
self.dashed_line_cycle_column = "source"
4596
self.marker_cycle_column = "variable"
46-
97+
# Build dvue catalog after param is fully initialized
98+
self._schism_reader = SchismDataReferenceReader(
99+
self.study_dir_map, self.datastore, self
100+
)
101+
geo_crs = (
102+
str(self.catalog.crs)
103+
if hasattr(self.catalog, "crs") and self.catalog.crs is not None
104+
else None
105+
)
106+
self._dvue_catalog = self._build_dvue_catalog(geo_crs)
47107

48108
def get_widgets(self):
49109
control_widgets = super().get_widgets()
@@ -73,6 +133,29 @@ def _convert_to_study_format(self, df):
73133
def get_data_catalog(self):
74134
return self.catalog
75135

136+
@property
137+
def data_catalog(self) -> DataCatalog:
138+
return self._dvue_catalog
139+
140+
def _build_dvue_catalog(self, crs=None) -> DataCatalog:
141+
catalog = DataCatalog(crs=crs)
142+
for _, row in self.catalog.iterrows():
143+
ref_name = f"{row['source']}::{row['id']}/{row['variable']}"
144+
attrs = {k: v for k, v in row.items() if k != "geometry"}
145+
attrs.pop("name", None) # avoid clash with the explicit name= kwarg below
146+
if "geometry" in row.index and row["geometry"] is not None:
147+
attrs["geometry"] = row["geometry"]
148+
try:
149+
catalog.add(DataReference(
150+
self._schism_reader,
151+
name=ref_name,
152+
cache=False, # convert_units is reactive; always re-run
153+
**attrs,
154+
))
155+
except ValueError:
156+
pass # duplicate name; skip
157+
return catalog
158+
76159
def get_time_range(self, dfcat):
77160
return self.time_range
78161

@@ -141,21 +224,11 @@ def is_irregular(self, r):
141224
return False
142225

143226
def get_data_for_time_range(self, r, time_range):
144-
unit = r["unit"]
145-
if r["source"] == "datastore":
146-
df = self.datastore.get_data(r)
147-
if self.convert_units:
148-
df, unit = schismstudy.convert_to_SI(df, r["unit"])
149-
else:
150-
base_dir = str(pathlib.Path(r["filename"]).parent)
151-
study = self.study_dir_map[base_dir]
152-
try:
153-
df = study.get_data(r)
154-
except KeyError as e:
155-
logger.warning(str(e).strip('"\''))
156-
raise
157-
ptype = "INST-VAL"
158-
df = df[slice(df.first_valid_index(), df.last_valid_index())]
227+
ref_name = f"{r['source']}::{r['id']}/{r['variable']}"
228+
ref = self._dvue_catalog.get(ref_name)
229+
df = ref.getData(time_range=time_range)
230+
unit = df.attrs.get("unit", r["unit"])
231+
ptype = df.attrs.get("ptype", "INST-VAL")
159232
return df, unit, ptype
160233

161234
# methods below if geolocation data is available

0 commit comments

Comments
 (0)