raddb package#
Subpackages#
Submodules#
raddb.aoi module#
raddb/aoi.py#
Area-of-Interest (AOI) selection internals.
These are the private building blocks behind the public RadDB.crop_*
methods. The design is LUT-first: an AOI geometry is intersected once with the
static per-radar LUT centroids (in Swiss LV95 / EPSG:2056) to resolve a set
of gate_id values, and that set then filters any number of dynamic volume
DataFrames — cheaply and identically across timesteps.
Because the intersection is done on gate centroids over all sweeps, a single horizontal footprint selects the whole vertical column above it (every elevation).
Coordinate convention: AOI geometries are handled in EPSG:2056 throughout; input
in another CRS is reprojected via _reproject_to_2056() before intersection.
No pyart / geocube dependency — only numpy, polars, pandas, shapely, pyproj.
The centroid tables are polars frames (the package-wide default); the spatial predicate itself runs on plain numpy arrays via shapely, so no geometry objects are ever materialised for the ~1.7M gates of a full LUT.
raddb.discovery module#
raddb/discovery.py#
File discovery for RadDB — both sides of the archive:
DataTree file discovery (input side): locate DataTree files (NetCDF / Zarr) on disk, optionally filtered by a filename timestamp, ready to load with
raddb.io_core.open_any_datatree()and archive withraddb.RadDB.archive_from_datatrees().Archived POL search (output side): locate
*_POL.parquetfiles in a time range inside an existing RadDB archive.
Everything here is pure filesystem + pandas — no pyart / radar_api
dependency — so discovery works in any environment. (Raw METRANET
scanning lives in the private raddb.mch.discovery module.)
- raddb.discovery.find_datatree_files(directory: str | Path, recursive: bool = True, extensions: tuple[str, ...] = ('.nc', '.nc4', '.cdf', '.zarr'), start_time: str | Timestamp | None = None, end_time: str | Timestamp | None = None, strict_time: bool = False) list[Path][source][source]#
Find DataTree files (NetCDF files / Zarr stores) under directory.
Zarr stores are directories — they are matched as leaves and never descended into. Results are sorted by (filename timestamp, path); files whose stem has no parseable timestamp sort last.
- Parameters:
directory (str or Path) – Directory to scan.
recursive (bool) – Recurse into subdirectories (default
True).extensions (tuple of str) – File/store suffixes to match (case-insensitive).
start_time (str or Timestamp, optional) – Keep only files whose filename timestamp (see
_parse_datatree_file_time()) falls in this range.end_time (str or Timestamp, optional) – Keep only files whose filename timestamp (see
_parse_datatree_file_time()) falls in this range.strict_time (bool) – When a time range is given, drop files whose timestamp cannot be parsed from the filename (default
False— they are kept).
- Returns:
Matching files/stores, time-sorted.
- Return type:
list of Path
raddb.hc_mapping module#
raddb/hc_mapping.py#
Canonical hydrometeor class constants for the MCH operational encoding.
- Both HC_MCH and HC_PYART are stored in parquet on the same 1-based scale:
parquet integer k → HC_MAP_DICT[k - 1]
HC_MCH raw (HYM file, after /25) is already 0-based (0-8). HC_PYART (PyART native 1-9) is remapped to 0-based operational via PYART_TO_OPE before archiving, then shifted +1 to match HC_MCH.
Import these constants wherever HC label/color information is needed.
raddb.helper module#
raddb/helper.py#
Shared utilities and configuration for RadDB.
- class raddb.helper.StageTimer[source][source]#
Bases:
objectAccumulates per-stage timing records for pipeline profiling.
Example
>>> timer = StageTimer() >>> with timer.time_stage("my_stage", volume="vol_001", sweep=2): ... do_work() >>> timer.print_summary() >>> plot_profiling_dashboard(timer)
- record(stage: str, duration: float, volume: str | None = None, sweep: int | None = None, t_start: float | None = None)[source][source]#
Manually append a pre-measured timing entry.
- summary() DataFrame[source][source]#
Aggregate by stage — returns (sum, mean, min, max, count) sorted by total time.
- raddb.helper.ensure_utc(dt_input)[source][source]#
Ensure a datetime-like input is timezone-aware and in UTC.
- raddb.helper.filter_df(df: DataFrame, feature: str = 'DBZH', threshold: float = 0.0, logic: str = '>') DataFrame[source][source]#
Filter a DataFrame, keeping rows where
feature [logic] threshold.- Parameters:
- Returns:
Filtered DataFrame with non-matching rows dropped (index reset).
- Return type:
pd.DataFrame
- Raises:
KeyError – If
featureis not a column ofdf.ValueError – If
logicis not one of the supported operators.
- raddb.helper.filter_dt(dt: DataTree, feature: str = 'DBZH', threshold: float = 0.0, logic: str = '>') DataTree[source][source]#
Filter a DataTree, masking gates where
feature [logic] thresholdis False.Gates that do not satisfy the condition are set to NaN across all data variables in each sweep. Gates that do satisfy the condition keep their original values unchanged — including legitimate zero values.
Note
This operates on the multidimensional DataTree structure via
xr.Dataset.where(). For tabular (row-level) filtering usefilter_df()instead, which drops non-matching rows entirely.- Parameters:
- Returns:
New DataTree where non-matching gates have NaN for all variables. Matching gates are left entirely unchanged (zeros remain zeros).
- Return type:
xr.DataTree
- Raises:
ValueError – If
logicis not a supported operator.
- raddb.helper.list_sweep_names(dt: DataTree) list[str][source][source]#
Return sorted sweep group names from a DataTree.
- raddb.helper.normalize_radar_name(radar: str) str[source][source]#
Normalize radar name to use only the single letter identifier.
Handles both formats: - “MLA” -> “A” - “A” -> “A” - “MLW” -> “W”
- Parameters:
radar (str) – Radar name (can be “ML*” format or just the letter)
- Returns:
Normalized single-letter radar name (uppercase)
- Return type:
Examples
>>> normalize_radar_name("MLA") 'A' >>> normalize_radar_name("A") 'A' >>> normalize_radar_name("MLW") 'W'
- raddb.helper.read_parquet_files(base_path: str | Path, pattern: str = '**/*POL.parquet', columns: list[str] | None = None, verbose: bool = True) DataFrame[source][source]#
- raddb.helper.resolve_filter_logic(logic: str)[source][source]#
Return the comparison function for logic.
- Raises:
ValueError – If
logicis not one of the supported operators.
raddb.io_core module#
raddb/io_core.py#
Core I/O conversion functions for radar data.
This module provides generic conversions between xarray DataTree,
pandas DataFrame, and Parquet files. It does not depend on pyart
or radar_api — all MCH-specific I/O lives in the private raddb.mch
subpackage.
- raddb.io_core.add_feature_to_df(df: DataFrame, feature_name: str, compute_fn: callable) DataFrame[source][source]#
Add a new column to a DataFrame computed from existing columns.
- Parameters:
df (pd.DataFrame) – Input DataFrame (e.g. from
parquet_to_dataframe()).feature_name (str) – Name of the new column to add.
compute_fn (callable) –
Function that takes
dfand returns a Series or array of the same length. Example:def my_feature(df): return df["ZDR"] + df["DBZH"] * 0.1
- Returns:
Copy of
dfwith the new column appended.- Return type:
pd.DataFrame
- raddb.io_core.add_feature_to_dt(dt: DataTree, feature_name: str, compute_fn: callable) DataTree[source][source]#
Add a new variable to every sweep in a DataTree.
- Parameters:
dt (xr.DataTree) – Input DataTree with
sweep_Ngroups.feature_name (str) – Name of the new variable to add to each sweep Dataset.
compute_fn (callable) –
Function that takes an
xr.Dataset(one sweep) and returns anxr.DataArraywith matching dimensions. Example:def kdp_proxy(ds): return (ds["PHIDP"].diff("range") / 0.250).clip(0)
- Returns:
New DataTree with the computed variable added to every sweep.
- Return type:
xr.DataTree
- raddb.io_core.archive_multiple_volumes(volumes: list[DataTree] | dict[str, DataTree], radar: str, base_output_path: str, filter_feature: str = 'DBZH', filter_threshold: float = 0.0, filter_logic: str = '>', verbose: bool = True, timer: StageTimer | None = None) list[dict][source][source]#
Archive multiple DataTree volumes sequentially.
- Parameters:
volumes (list or dict) – If a list, each element is a DataTree. If a dict, keys are volume labels and values are DataTrees.
radar (str) – Radar identifier.
base_output_path (str) – Base output directory.
filter_feature (str) – Column to use for gate filtering (default
"DBZH").filter_threshold (float) – Threshold value (default
0.0).filter_logic (str) – Comparison operator (default
">").verbose (bool) – Print progress.
timer (StageTimer, optional) – Profiling timer.
- Returns:
Results with keys: label, success, error, polar_path, n_gates.
- Return type:
- raddb.io_core.archive_volume(dt: DataTree, radar: str, base_output_path: str, filter_feature: str = 'DBZH', filter_threshold: float = 0.0, filter_logic: str = '>', timer=None, volume: str | None = None) str[source][source]#
Archive a single DataTree volume to Parquet format.
Converts the DataTree to a DataFrame, generates a
gate_idfor each gate (linking back to the LUT), drops gates that do not satisfyfilter_feature [filter_logic] filter_threshold, and saves the result as a POL parquet file.Non-matching gates are dropped (row removal), so zero values in surviving gates are never converted to NaN. NaN values in the reconstruction come only from gates absent in the parquet (i.e. gates that were filtered out or had no data in the original DataTree).
HZT availability check: if
"HZT"is not present in the DataTree,"HC_PYART"is also skipped because it requires HZT to be meaningful.- Parameters:
dt (xr.DataTree) – Processed volume (any radar network).
radar (str) – Radar identifier (single letter, e.g.
"A").base_output_path (str) – Base output directory for parquet files.
filter_feature (str) – Column to use for gate filtering (default
"DBZH").filter_threshold (float) – Threshold value for the filter (default
0.0).filter_logic (str) – Comparison operator (default
">").timer (StageTimer, optional) – Profiling timer.
volume (str, optional) – Volume label for timer records.
- Returns:
Path to the saved POL parquet file.
- Return type:
- raddb.io_core.archive_volumes_multi_radar(volumes_by_radar: dict[str, list[DataTree] | dict[str, DataTree]], base_output_path: str, filter_feature: str = 'DBZH', filter_threshold: float = 0.0, filter_logic: str = '>', verbose: bool = True, timer: StageTimer | None = None) dict[str, list[dict]][source][source]#
Archive volumes for multiple radars sequentially.
- Parameters:
volumes_by_radar (dict) – Keys are radar names, values are lists or dicts of DataTrees. Example:
{"A": [dt1, dt2], "D": [dt3, dt4]}base_output_path (str) – Base output directory.
filter_feature (str) – Column to use for gate filtering (default
"DBZH").filter_threshold (float) – Threshold value (default
0.0).filter_logic (str) – Comparison operator (default
">").verbose (bool) – Print progress.
timer (StageTimer, optional) – Profiling timer.
- Returns:
Keys are radar names, values are lists of result dicts.
- Return type:
- raddb.io_core.dataframe_to_datatree(df: DataFrame, radar: str, base_path: str | Path, label_column: str = 'DBZH', max_workers: int = 1) DataTree[source][source]#
Reconstruct a DataTree from an in-memory per-gate DataFrame.
The df→DataTree core shared by
parquet_to_datatree()and by DataFrame plotting: it joinsdfwith the radar LUT ongate_idto recover geometry (sweep/azimuth/range + lat/lon/alt/x/y/z and any projection cols), fills the full(azimuth × range)grid, and NaN-fills gates absent fromdf— so a cropped/filtered DataFrame reconstructs to a DataTree that carries the correct geometry but only the rows present indf, with the DataFrame’s own values (honouring crops or added feature columns).dfmust be a single radar and a single volume already (seeraddb.RadDB.datatree_from_df()for the radar/volume selection helper). Any of the LUT’s geometry columns already ondfare dropped and taken from the LUT instead, so acrop_bboxframe (which carriessweep/x_2056/y_2056/z/altitude) reconstructs cleanly.- Parameters:
df (pd.DataFrame) – Per-gate rows with a
gate_idcolumn (+ measurement columns).radar (str) – Radar identifier whose LUT to join against.
base_path (str or Path) – RadDB archive base directory.
label_column (str) – Feature used for reconstruction (default
"DBZH").max_workers (int) – Parallel workers for sweep reconstruction.
- Return type:
xr.DataTree
- Raises:
FileNotFoundError – If the LUT or radar info files are missing.
ValueError – If
dfis empty or no gates match the LUT.
- raddb.io_core.datatree_to_dataframe(dt: DataTree, max_workers: int = 1) DataFrame[source][source]#
Flatten a DataTree into a single pandas DataFrame.
Each sweep is converted independently and concatenated, with a
sweepcolumn indicating the source sweep number.
- raddb.io_core.datatree_to_dataset(dt: DataTree, sweep: str | int) Dataset[source][source]#
Extract a single sweep Dataset from a DataTree.
- raddb.io_core.datatree_to_parquet(dt: DataTree, radar: str, base_output_path: str, filter_feature: str = 'DBZH', filter_threshold: float = 0.0, filter_logic: str = '>', max_workers: int = 1) str[source][source]#
Convert a DataTree to a filtered POL parquet file.
Gates that do not satisfy
filter_feature [filter_logic] filter_thresholdare dropped (row removal) before saving. Zero values in surviving gates are preserved as-is.If
"HZT"is absent from the DataTree,"HC_PYART"is also skipped because it requires HZT to be meaningful.
- raddb.io_core.join_labels_with_lut(df_labels: DataFrame, lut_path: str | Path) DataFrame[source][source]#
Join label data with the LUT to recover spatial coordinates.
- raddb.io_core.labels_to_dataframe(labels: ndarray, gate_ids, extra_columns: dict | None = None) DataFrame[source][source]#
Create a DataFrame from prediction labels and gate IDs.
- raddb.io_core.open_any_datatree(path: str | Path, engine: str | None = None, **open_kwargs) DataTree[source][source]#
Open a DataTree from disk — NetCDF file or Zarr store.
Engine resolution: an explicit
enginewins; a*.zarrsuffix or a directory containing.zgroup/zarr.jsonselects"zarr"; otherwise xarray auto-detects the NetCDF backend (netCDF4 / h5netcdf).- Parameters:
path (str or Path) – Path to a NetCDF file or Zarr store.
engine (str, optional) – xarray backend override (e.g.
"h5netcdf","zarr").**open_kwargs – Forwarded to
xarray.open_datatree().
- Return type:
xr.DataTree
- raddb.io_core.parquet_to_dataframe(radar: str, base_path: str | Path, start_time: str | Timestamp | None = None, end_time: str | Timestamp | None = None, columns: list[str] | None = None, merge_lut: bool = False) DataFrame[source][source]#
Load archived POLAR parquet files as a single DataFrame.
- Parameters:
radar (str) – Single-letter radar identifier.
base_path (str or Path) – RadDB base directory.
start_time (optional) – Filter by volume timestamp.
end_time (optional) – Filter by volume timestamp.
columns (list of str, optional) – Columns to load from parquet files.
merge_lut (bool) – If True, merge with the LUT to add spatial coordinates (azimuth, range, latitude, longitude, altitude, x, y, z).
- Returns:
Includes a
volume_timecolumn (the volume timestamp parsed from each source filename) so a multi-volume frame can be split back into single volumes — used bydataframe_to_datatree()/ PPI plotting.- Return type:
pd.DataFrame
- raddb.io_core.parquet_to_datatree(radar: str, base_path: str | Path, start_time: str | Timestamp | None = None, end_time: str | Timestamp | None = None, label_column: str = 'DBZH', max_workers: int = 1) DataTree[source][source]#
Load archived POLAR parquet files and reconstruct a DataTree.
Loads all volumes in the given time range, joins with the LUT to recover azimuth/range coordinates, and reconstructs an xarray DataTree.
- Parameters:
radar (str) – Single-letter radar identifier.
base_path (str or Path) – RadDB base directory.
start_time (optional) – Filter by volume timestamp.
end_time (optional) – Filter by volume timestamp.
label_column (str) – Column to use for reconstruction (default
"DBZH").max_workers (int) – Parallel workers for sweep reconstruction.
- Return type:
xr.DataTree
- Raises:
FileNotFoundError – If the LUT or radar info files are missing.
ValueError – If no data is found.
- raddb.io_core.reconstruct_datatree(df_joined: DataFrame, lut_path: str | Path, radar_info_path: str | Path, label_column: str = 'hydrometeor_class', max_workers: int = 1) DataTree[source][source]#
Reconstruct a full DataTree from joined data + LUT + radar info.
- raddb.io_core.reconstruct_sweep_dataset(df_joined: DataFrame, sweep: int, lut_df: DataFrame, radar_info: dict, label_column: str = 'hydrometeor_class', sweep_corners: dict | None = None) Dataset[source][source]#
Reconstruct a single sweep Dataset from joined data.
Data variables come from the filtered POL parquet (NaN where gates were dropped). Per-gate spatial coords (lat/lon/alt/x/y/z and any x_<epsg>/ y_<epsg>) come from the LUT directly so every gate has a valid geometry regardless of filtering.
If
sweep_cornersis provided, per-gate edge arrays (x_edges,y_edges,z_edges,lon_edges,lat_edges) of shape(n_az+1, n_range+1)are attached as data_vars for pcolormesh rendering withshading="flat".
- raddb.io_core.scan_polar_parquet(radar: str, base_path: str | Path, start_time: str | pd.Timestamp | None = None, end_time: str | pd.Timestamp | None = None, columns: list[str] | None = None) pl.LazyFrame | None[source][source]#
Scan archived POLAR parquet files as a single polars LazyFrame.
The polars counterpart of
parquet_to_dataframe(), used byraddb.RadDB.open(). Scanning (rather than reading) lets polars push the column projection into the parquet reader, and avoids the pandas intermediate frames and thepd.concatcopy of the full result.Unlike
parquet_to_dataframe()this never merges the LUT: the static geometry stays in its own table and is joined only by theto_*converters.- Parameters:
radar (str) – Single-letter radar identifier.
base_path (str or Path) – RadDB base directory.
start_time (optional) – Filter by volume timestamp.
end_time (optional) – Filter by volume timestamp.
columns (list of str, optional) – Columns to project.
volume_time/radarare added here rather than read, so they are dropped from the parquet projection.
- Returns:
Nonewhen no volume matches — callers decide what an empty result means. The frame carries avolume_timeand aradarcolumn.- Return type:
pl.LazyFrame or None
raddb.lut module#
raddb/lut.py#
Look-Up Table (LUT) generation and loading utilities.
The LUT stores one record per radar gate (azimuth x range x sweep) with static spatial information (Cartesian coordinates, lat/lon, elevation). It is generated once per radar and reused for every subsequent volume.
This module is generic — it works with any xarray DataTree that
has azimuth, range, and elevation coordinates per sweep.
No pyart or radar_api dependency.
- raddb.lut.add_lut_projection(lut_df: DataFrame | DataFrame, epsg: int | None = None, crs=None) DataFrame | DataFrame[source][source]#
Add projected coordinates to a LUT DataFrame.
Converts the
latitude/longitudecolumns to the target CRS and appendsx_{suffix}/y_{suffix}columns, wheresuffixis the EPSG code (if available) or"custom".Accepts either a polars or a pandas frame and returns the same kind — LUT loading is polars, but LUT generation still builds pandas frames.
Requires pyproj (
pip install pyproj).- Parameters:
lut_df (pl.DataFrame or pd.DataFrame) – LUT DataFrame with
latitudeandlongitudecolumns (degrees, WGS-84 / EPSG:4326).epsg (int, optional) – EPSG code for the target CRS. Example:
2056for CH1903+ / LV95 (Swiss national grid).crs (pyproj.CRS or any CRS-coercible object, optional) – Alternative to
epsg. Used whenepsgisNone. Can be a pyprojCRSobject, a WKT string, or any value accepted bypyproj.CRS().
- Returns:
Copy of
lut_df(same kind) with two new columns:x_{suffix}(easting / metres) andy_{suffix}(northing / metres).- Return type:
pl.DataFrame or pd.DataFrame
- Raises:
ValueError – If neither
epsgnorcrsis provided.ImportError – If pyproj is not installed.
Examples
Add Swiss LV95 (CH1903+) coordinates to a LUT:
>>> lut_ch = add_lut_projection(lut_df, epsg=2056) >>> lut_ch[["x_2056", "y_2056"]].head()
Use a custom pyproj CRS:
>>> import pyproj >>> my_crs = pyproj.CRS.from_epsg(32632) # UTM zone 32N >>> lut_utm = add_lut_projection(lut_df, crs=my_crs)
- raddb.lut.antenna_vectors_to_cartesian(ranges: ndarray, azimuths: ndarray, elevations: ndarray, ke: float = 1.3333333333333333, edges: bool = False) tuple[ndarray, ndarray, ndarray][source][source]#
Convert radar antenna coordinates to Cartesian (x, y, z).
Replicates the standard radar beam propagation formula used by PyART (
pyart.core.transforms.antenna_vectors_to_cartesian) using only numpy. The 4/3 effective-Earth-radius model is used by default.- Parameters:
ranges (1-D array, shape (n_gates,)) – Range to each gate in meters.
azimuths (1-D array, shape (n_rays,)) – Azimuth angle of each ray in degrees (0 = North, clockwise).
elevations (1-D array, shape (n_rays,)) – Elevation angle of each ray in degrees.
ke (float) – Effective Earth radius scale factor (default 4/3).
edges (bool) – If True, interpolate ranges/azimuths/elevations to gate edges (size N+1) before computing Cartesian coords. Used for
pcolormesh(shading="flat")rendering.
- Returns:
x, y, z – Cartesian coordinates in meters relative to the radar.
- Return type:
arrays, shape (n_rays, n_gates) — or (n_rays+1, n_gates+1) if
edges.
- raddb.lut.cartesian_to_geographic(x: ndarray, y: ndarray, z: ndarray, radar_lat: float, radar_lon: float, radar_alt: float) tuple[ndarray, ndarray, ndarray][source][source]#
Convert gate Cartesian offsets to geographic coordinates.
Uses an equirectangular approximation which is accurate for typical radar ranges (< 250 km).
- Parameters:
x (arrays) – Cartesian offsets from the radar in meters (x = east, y = north, z = height above radar).
y (arrays) – Cartesian offsets from the radar in meters (x = east, y = north, z = height above radar).
z (arrays) – Cartesian offsets from the radar in meters (x = east, y = north, z = height above radar).
radar_lat (float) – Radar site latitude and longitude in degrees.
radar_lon (float) – Radar site latitude and longitude in degrees.
radar_alt (float) – Radar site altitude in meters above sea level.
- Returns:
lat, lon, alt – Geographic coordinates of each gate.
- Return type:
arrays (same shape as input)
- raddb.lut.compute_corners_from_lut(radar: str, lut_base_path: str | Path, ke: float = 1.3333333333333333) str[source][source]#
Rebuild
{radar}_corners.npzfrom an existing LUT parquet + info YAML.Useful when the corners file is missing (e.g. for LUTs generated before corners were introduced). Reads the per-sweep unique (azimuth, range, elevation) triples from the LUT, computes corner arrays via
compute_sweep_corners(), and saves them.- Returns:
str
- Return type:
path to the written
.npzfile.
- raddb.lut.compute_gate_xyz(ranges: ndarray, azimuths: ndarray, elevations: ndarray, ke: float = 1.25) tuple[ndarray, ndarray, ndarray][source][source]#
Compute gate Cartesian coordinates from polar coordinates.
Thin wrapper around
antenna_vectors_to_cartesian()for backwards compatibility.
- raddb.lut.compute_sweep_corners(ranges: ndarray, azimuths: ndarray, elevations: ndarray, radar_lat: float, radar_lon: float, radar_alt: float, ke: float = 1.3333333333333333) dict[source][source]#
Compute per-sweep gate corner arrays for pcolormesh rendering.
Uses PyART’s edge-interpolation (complex-plane for azimuth wrap-around) plus the standard 4/3 Earth beam propagation.
- Returns:
dict with keys
x_edges,y_edges,z_edges,lon_edges,lat_edges, each shape(n_az+1, n_range+1).
- raddb.lut.decode_gate_ids(gate_ids) tuple[ndarray, ndarray, ndarray][source][source]#
Inverse of
encode_gate_ids()(radar index excluded).- Returns:
(sweeps, azimuths, ranges) – Sweep number (int64), azimuth in degrees (float64, 1 decimal) and range in metres (float64), one entry per gate_id.
- Return type:
np.ndarray
- raddb.lut.encode_gate_ids(radar: str, sweeps: int | ndarray, azimuths: ndarray, ranges: ndarray) ndarray[source][source]#
Vectorised 64-bit gate identifier encoding.
Encoding:
radar_idx * 10^12 + sweep * 10^10 + az_int * 10^6 + range_intwhere
az_int = round(azimuth * 10)(1 decimal place precision) andrange_int = int(range_m)(integer metres). This is the single canonical implementation, used by LUT generation and volume archiving.- Parameters:
radar (str) – Radar identifier (single letter, e.g.
"A").sweeps (int or array) – Sweep number(s) — a scalar (applied to all gates) or an array aligned with
azimuths/ranges.azimuths (arrays (aligned per gate)) – Azimuth [deg] and range [m] of each gate.
ranges (arrays (aligned per gate)) – Azimuth [deg] and range [m] of each gate.
- Return type:
np.ndarray of int64
- raddb.lut.gate_polygons_geoarrow(radar: str, lut_base_path: str | Path, gate_ids, frame: str = 'geographic')[source][source]#
Build the gate wedge polygons for
gate_idsas a GeoArrow array.Each gate becomes a 4-corner ring (closed, 5 vertices) taken from the per-sweep edge arrays produced by
compute_sweep_corners()— the exact wedge, not a rectangle. The corners file is rebuilt viacompute_corners_from_lut()if it does not exist yet.- Parameters:
radar (str) – Single-letter radar identifier.
lut_base_path (str or Path) – RadDB archive base directory.
gate_ids (array-like of int64) – Gates to build polygons for, in the order they should appear.
frame ({"geographic", "cartesian"}) –
"geographic"useslon_edges/lat_edges(EPSG:4326, the frame web maps expect);"cartesian"usesx_edges/y_edges(metres from the radar).
- Returns:
A
geoarrow.polygonextension array, one polygon per gate_id. Gates whose (azimuth, range) is absent from the LUT grid yield null.- Return type:
pyarrow.Array
- raddb.lut.generate_gate_id(radar: str, sweep: int, azimuth: float, range_m: float) int[source][source]#
Create a unique gate identifier as a 64-bit integer.
Scalar convenience wrapper around
encode_gate_ids()— see there for the encoding definition.
- raddb.lut.generate_lut_from_datatree(dt: DataTree, radar: str, output_base_path: str, ke: float = 1.25, network: str = '', projection_epsg: int | None = None, projection_crs=None) str[source][source]#
Generate a LUT from an xarray DataTree.
This is the generic LUT generator — it works with any DataTree that has the standard xradar coordinate layout (azimuth, range, elevation per sweep).
For MCH-specific LUT generation from raw METRANET files, use
raddb.mch.generate_mch_lut()instead.- Parameters:
dt (xr.DataTree) – DataTree with
sweep_Ngroups, each containingazimuth,range, andelevationcoordinates.radar (str) – Radar identifier (single letter, e.g.
"A").output_base_path (str) – Base directory for LUT storage.
ke (float) – Effective Earth radius scale factor (default 1.25 for Switzerland).
network (str, optional) – Network identifier stored in radar info YAML.
projection_epsg (int, optional) – EPSG code for an additional projected coordinate system to include in the LUT (e.g.
2056for CH1903+ / LV95). Addsx_{epsg}/y_{epsg}columns viaadd_lut_projection().projection_crs (pyproj.CRS or CRS-coercible, optional) – Alternative to
projection_epsg.
- Returns:
Path to the saved LUT parquet file.
- Return type:
- raddb.lut.geoarrow_field(name: str, dtype, kind: str, crs: str | None = None)[source][source]#
Build a
pyarrow.Fieldtagged as a GeoArrow extension type.GeoArrow identifies geometry through field metadata, so the tag can only be attached where the array is placed into a table/schema.
- raddb.lut.get_full_sweep_index(lut_df: DataFrame, sweep: int) MultiIndex[source][source]#
Get the full (azimuth, range) MultiIndex for a sweep from the LUT.
- raddb.lut.load_radar_info(radar: str, lut_base_path: str | Path) dict[source][source]#
Load the radar info YAML for a radar.
- raddb.lut.load_radar_lut(radar: str, lut_base_path: str | Path) DataFrame[source][source]#
Load the LUT parquet for a radar as a polars DataFrame.
Call
polars.DataFrame.to_pandas()on the result if you need pandas.
raddb.main module#
raddb/main.py#
High-level interface for RadDB — a generic radar data archiving library.
RadDB is a single, dual-role class:
Archive-bound —
db = RadDB(archive_dir, crs=2056). Use it toarchive()DataTree volumes and toopen()archived data.archive_dir/crsare the shared defaults for every task.Data-carrying — the object returned by
open()(and byfilter/crop_* `` / ``extract_cross_section). It holds the loaded data as a polars DataFrame (rdf.data) and exposes the query, conversion, area-of-interest, cross-section and plotting methods. These return a newRadDB(fluentopen → filter → crop → plot).
Note: network-specific constants such as a list of radar identifiers
(e.g. Swiss radars A, D, L, P, W) belong in the user script or in the
network-specific pipeline (e.g. the private raddb.mch subpackage),
not here.
- class raddb.main.RadDB(archive_dir: str | None = None, crs: int | str | None = None, network: str = '', *, _data: DataFrame | None = None, _meta: dict | None = None)[source][source]#
Bases:
objectGeneric radar-data archiving and analysis interface (see module docstring).
Examples
>>> db = RadDB(archive_dir="/data/raddb", crs=2056) >>> db.archive(datatree_dir="/data/MCH_datatree") # or datatree=dt >>> rdf = db.open(time_period=("2024-08-26", "2024-08-27")) >>> rdf.filter({"var": "DBZH", "logic": ">", "threshold": 20}) ... .crop_by_bbox(extent=rdf.extent()) ... .plot_ppi(variable="DBZH", save="ppi.png")
Create an archive-bound RadDB.
- Parameters:
archive_dir (str, optional) – Base directory of the RadDB archive (LUT + POL parquet under
{archive_dir}/{radar}/). Shared default for every task.crs (int, str or CRS-coercible, optional) – Projection for the LUT (e.g.
2056for CH1903+/LV95). Used both when generating LUTs duringarchive()and to resolve projected coordinates (extent(),to_geopandas()).network (str, optional) – Network label stored in generated LUT metadata.
of (The private _data / _meta arguments carry the polars backend) –
use (a data-carrying instance and are not meant to be passed directly;) –
:param
open().:- add_feature(name: str, compute_fn) RadDB[source][source]#
Add a computed column
nameand return a new RadDB.compute_fnreceives the polars DataFrame and returns a polarsExpror a column-length array/Series, e.g.rdf.add_feature("ZDR_lin", lambda df: 10 ** (df["ZDR"] / 10)).
- add_lut_projection(radar: str, epsg: int | None = None, crs=None) DataFrame[source][source]#
Return the radar LUT enriched with projected
x_{epsg}/y_{epsg}columns.
- archive(datatree_dir: str | None = None, datatree=None, archive_dir: str | None = None, crs: int | str | None = None, radar: str | list[str] | None = None, filter: dict | None = None, time_period=None) dict[source][source]#
Archive DataTree volumes to the RadDB Parquet store.
Exactly one input source must be given:
datatree— an in-memoryxr.DataTree, a list of them, a{label: DataTree}dict (one radar), or a{radar: [DataTree, ...]}dict (several radars).datatree_dir— a directory of saved DataTree files (.zarr/.nc); volumes are grouped by radar from the filename.
Passing both raises
ValueError.- Parameters:
datatree_dir (str, optional) – Directory of saved DataTree files to archive.
datatree (xr.DataTree, list, or dict, optional) – In-memory volume(s) to archive.
archive_dir (optional) – Override the instance defaults for this call.
crs (optional) – Override the instance defaults for this call.
radar (str or list of str, optional) – Restrict / assign the radar(s). For
datatree_dirwithradar=None(default) the radar is inferred per file from the filename and all radars are archived. For a single in-memoryDataTree/ list,radaris required.filter (dict, optional) – Gate filter
{"var", "logic", "threshold"}defining which gates to keep. Default:{"var": "DBZH", "logic": ">", "threshold": 0}(drops no-echo gates).time_period (str, datetime or (start, end), optional) – For
datatree_dir: keep only files whose filename timestamp falls in the period.
- Returns:
{"n_archived": int, "n_failed": int, "radars": [...]}.- Return type:
- crop_around_point(point, distance: float, crs: int | str | None = None, quicklook: bool = False) RadDB[source][source]#
Crop to a circle of radius
distance(metres) aroundpoint; returns a new RadDB.pointis(x, y)or a shapely Point incrs.
- crop_by_bbox(bounds=None, extent=None, crs: int | str = 2056, quicklook: bool = False) RadDB[source][source]#
Crop to a rectangle; returns a new RadDB.
Give exactly one of
bounds=(xmin, ymin, xmax, ymax)orextent=[xmin, xmax, ymin, ymax](the latter matchesextent()), incrs(default EPSG:2056). A single horizontal footprint selects the whole vertical column above it.
- crop_by_polygone(polygon, crs: int | str | None = None, quicklook: bool = False) RadDB[source][source]#
Crop to an arbitrary polygon (shapely, GeoDataFrame/GeoSeries, or a
.shp/.geojsonpath); returns a new RadDB.crs=Noneauto-detects.
- extract_cross_section(p1, p2, crs: int | str | None = None, beamwidth_deg: float = 1.0, quicklook: bool = False) RadDB[source][source]#
Extract a vertical cross-section along the line
p1 -> p2; returns a new RadDB.The line need not pass through a radar. Each selected gate gets a polygon in the (distance-along-line, altitude) plane (
cs_polygon) plusd_near/d_far/z_near/z_farandd_center/z_center; visualize withplot_cross_section().p1/p2are(x, y)or shapely Points incrs; distance is measured fromp1.
- filter(filters) RadDB[source][source]#
Keep only gates satisfying
filters(row removal); returns a new RadDB.filtersis a dict{"var", "logic", "threshold"}or a list of such dicts (combined with AND). Vertical subsetting works the same way on analtitudecolumn, e.g.rdf.filter([{"var": "altitude", "logic": ">", "threshold": 2000}, {"var": "altitude", "logic": "<", "threshold": 4000}]).Filtering on a static column (
altitude,sweep,latitude…) joins it from the LUT just long enough to evaluate the predicate and drops it again, so the returned frame still carries dynamic values only.
- geographic_extent() list[float][source][source]#
Geographic bounding box
[lon_min, lon_max, lat_min, lat_max].
- get_lut(radar: str) DataFrame[source][source]#
Load the LUT (static gate geometry) for a radar, as polars.
- interactive_crop(**kwargs)[source][source]#
Draw an AOI on an interactive ipyleaflet map and crop (Jupyter only).
Returns an
AOISelector; after drawing a shape and clicking Apply crop, the cropped result (a newRadDB) is onselector.result.
- inventory(datatree_dir: str | None = None, detailed: bool = False, archive_dir: str | None = None) None[source][source]#
Print what data is available on disk — which radars, which time periods.
Answers “what can I analyse?” before
open()(archive side) orarchive()(input side). Prints only; nothing is loaded into memory.- Parameters:
datatree_dir (str, optional) – Scan a directory of saved DataTree files (
.zarr/.nc) — i.e. data not archived yet. Radar and time come from the filename (<RADAR>_<YYYYMMDD>_<HHMMSS>). When omitted, the RadDB archive is scanned instead.detailed (bool) – Add a per-radar, per-day breakdown; for the archive also the LUT size, the sweep count and the moment columns stored per volume.
archive_dir (str, optional) – Override the archive location for this call.
Examples
>>> db.inventory() # what is archived >>> db.inventory(detailed=True) # ... day by day >>> db.inventory(datatree_dir="/data/MCH_datatree") # what could be archived
- open(time_period=None, radars: str | list[str] | None = None, columns: list[str] | None = None, filters=None, archive_dir: str | None = None) RadDB[source][source]#
Load archived data into a data-carrying
RadDB.- Parameters:
time_period (str, datetime or (start, end), optional) – Time range to load.
Noneloads everything available.radars (str or list of str, optional) – Radar(s) to load.
None(default) loads all radars in the archive.columns (list of str, optional) – Subset of moment columns to load (
gate_idis always included).filters (dict or list of dict, optional) – Gate filter(s)
{"var", "logic", "threshold"}applied after load (a list is combined with AND).archive_dir (str, optional) – Override the archive location for this call.
- Returns:
A data-carrying instance (
rdf).- Return type:
- plot_cross_section(variable: str = 'DBZH', radar: str | None = None, timestep=None, save: str | None = None, **kwargs)[source][source]#
Plot a vertical cross-section from an
extract_cross_section()result.
- plot_ppi(sweep: int | str = 1, variable: str = 'DBZH', radar: str | None = None, timestep=None, save: str | None = None, **kwargs)[source][source]#
Plot a PPI of one sweep from the loaded data. Returns the matplotlib artist.
- plot_rhi(azimuth: float, variable: str = 'DBZH', radar: str | None = None, timestep=None, save: str | None = None, **kwargs)[source][source]#
Plot a pseudo-RHI through a radar site at
azimuth. Returns the matplotlib artist.
- to_datatree(radar: str | None = None, timestep=None, label_column: str = 'DBZH') DataTree[source][source]#
Reconstruct a single-volume
xr.DataTreefrom the loaded data.Recovers geometry from the LUT and NaN-fills gates absent from the data. Selects one radar and one volume:
radar— inferred when the data covers exactly one radar, else required.timestep— nearestvolume_time; required only when several volumes.
- to_geoarrow(geometry: str = 'point', columns: list[str] | None = None, max_rows: int | None = 5000000)[source][source]#
Return the data as a GeoArrow-tagged
pyarrow.Table.This is the export path for GPU map renderers (deck.gl / lonboard) and for GeoParquet. The dynamic columns come straight from the polars frame with no copy; the LUT geometry is joined here and only here.
- Parameters:
geometry ({"point", "polygon"}) –
"point"gives one gate centroid per row (~24 B/gate)."polygon"gives the exact wedge — the gate’s four corners taken from the LUT’s per-sweep edge arrays (~100 B/gate).columns (list of str, optional) – Dynamic columns to export.
None(default) exports all of them.gate_idis always included.max_rows (int, optional) – Guardrail: raise if the frame is larger, since renderers top out around a few million features. Pass
Noneto disable.
- Returns:
The dynamic columns plus a
geometrycolumn carrying the GeoArrow extension metadata.- Return type:
pyarrow.Table
Examples
>>> import lonboard >>> lonboard.viz(rdf.crop_by_bbox(bounds).to_geoarrow(geometry="polygon"))
Module contents#
RadDB Package — Generic radar data archiving and reconstruction.
RadDB archives xarray DataTree volumes as Parquet files with an efficient LUT-based layout. It is network-agnostic: any DataTree with the standard xradar coordinate layout can be archived and reconstructed.
MCH/METRANET-specific ingestion code lives in the private raddb.mch
subpackage (gitignored in the public repository; never imported here).
- class raddb.RadDB(archive_dir: str | None = None, crs: int | str | None = None, network: str = '', *, _data: DataFrame | None = None, _meta: dict | None = None)[source][source]#
Bases:
objectGeneric radar-data archiving and analysis interface (see module docstring).
Examples
>>> db = RadDB(archive_dir="/data/raddb", crs=2056) >>> db.archive(datatree_dir="/data/MCH_datatree") # or datatree=dt >>> rdf = db.open(time_period=("2024-08-26", "2024-08-27")) >>> rdf.filter({"var": "DBZH", "logic": ">", "threshold": 20}) ... .crop_by_bbox(extent=rdf.extent()) ... .plot_ppi(variable="DBZH", save="ppi.png")
Create an archive-bound RadDB.
- Parameters:
archive_dir (str, optional) – Base directory of the RadDB archive (LUT + POL parquet under
{archive_dir}/{radar}/). Shared default for every task.crs (int, str or CRS-coercible, optional) – Projection for the LUT (e.g.
2056for CH1903+/LV95). Used both when generating LUTs duringarchive()and to resolve projected coordinates (extent(),to_geopandas()).network (str, optional) – Network label stored in generated LUT metadata.
of (The private _data / _meta arguments carry the polars backend) –
use (a data-carrying instance and are not meant to be passed directly;) –
:param
open().:- add_feature(name: str, compute_fn) RadDB[source][source]#
Add a computed column
nameand return a new RadDB.compute_fnreceives the polars DataFrame and returns a polarsExpror a column-length array/Series, e.g.rdf.add_feature("ZDR_lin", lambda df: 10 ** (df["ZDR"] / 10)).
- add_lut_projection(radar: str, epsg: int | None = None, crs=None) DataFrame[source][source]#
Return the radar LUT enriched with projected
x_{epsg}/y_{epsg}columns.
- archive(datatree_dir: str | None = None, datatree=None, archive_dir: str | None = None, crs: int | str | None = None, radar: str | list[str] | None = None, filter: dict | None = None, time_period=None) dict[source][source]#
Archive DataTree volumes to the RadDB Parquet store.
Exactly one input source must be given:
datatree— an in-memoryxr.DataTree, a list of them, a{label: DataTree}dict (one radar), or a{radar: [DataTree, ...]}dict (several radars).datatree_dir— a directory of saved DataTree files (.zarr/.nc); volumes are grouped by radar from the filename.
Passing both raises
ValueError.- Parameters:
datatree_dir (str, optional) – Directory of saved DataTree files to archive.
datatree (xr.DataTree, list, or dict, optional) – In-memory volume(s) to archive.
archive_dir (optional) – Override the instance defaults for this call.
crs (optional) – Override the instance defaults for this call.
radar (str or list of str, optional) – Restrict / assign the radar(s). For
datatree_dirwithradar=None(default) the radar is inferred per file from the filename and all radars are archived. For a single in-memoryDataTree/ list,radaris required.filter (dict, optional) – Gate filter
{"var", "logic", "threshold"}defining which gates to keep. Default:{"var": "DBZH", "logic": ">", "threshold": 0}(drops no-echo gates).time_period (str, datetime or (start, end), optional) – For
datatree_dir: keep only files whose filename timestamp falls in the period.
- Returns:
{"n_archived": int, "n_failed": int, "radars": [...]}.- Return type:
- crop_around_point(point, distance: float, crs: int | str | None = None, quicklook: bool = False) RadDB[source][source]#
Crop to a circle of radius
distance(metres) aroundpoint; returns a new RadDB.pointis(x, y)or a shapely Point incrs.
- crop_by_bbox(bounds=None, extent=None, crs: int | str = 2056, quicklook: bool = False) RadDB[source][source]#
Crop to a rectangle; returns a new RadDB.
Give exactly one of
bounds=(xmin, ymin, xmax, ymax)orextent=[xmin, xmax, ymin, ymax](the latter matchesextent()), incrs(default EPSG:2056). A single horizontal footprint selects the whole vertical column above it.
- crop_by_polygone(polygon, crs: int | str | None = None, quicklook: bool = False) RadDB[source][source]#
Crop to an arbitrary polygon (shapely, GeoDataFrame/GeoSeries, or a
.shp/.geojsonpath); returns a new RadDB.crs=Noneauto-detects.
- extract_cross_section(p1, p2, crs: int | str | None = None, beamwidth_deg: float = 1.0, quicklook: bool = False) RadDB[source][source]#
Extract a vertical cross-section along the line
p1 -> p2; returns a new RadDB.The line need not pass through a radar. Each selected gate gets a polygon in the (distance-along-line, altitude) plane (
cs_polygon) plusd_near/d_far/z_near/z_farandd_center/z_center; visualize withplot_cross_section().p1/p2are(x, y)or shapely Points incrs; distance is measured fromp1.
- filter(filters) RadDB[source][source]#
Keep only gates satisfying
filters(row removal); returns a new RadDB.filtersis a dict{"var", "logic", "threshold"}or a list of such dicts (combined with AND). Vertical subsetting works the same way on analtitudecolumn, e.g.rdf.filter([{"var": "altitude", "logic": ">", "threshold": 2000}, {"var": "altitude", "logic": "<", "threshold": 4000}]).Filtering on a static column (
altitude,sweep,latitude…) joins it from the LUT just long enough to evaluate the predicate and drops it again, so the returned frame still carries dynamic values only.
- geographic_extent() list[float][source][source]#
Geographic bounding box
[lon_min, lon_max, lat_min, lat_max].
- get_lut(radar: str) DataFrame[source][source]#
Load the LUT (static gate geometry) for a radar, as polars.
- interactive_crop(**kwargs)[source][source]#
Draw an AOI on an interactive ipyleaflet map and crop (Jupyter only).
Returns an
AOISelector; after drawing a shape and clicking Apply crop, the cropped result (a newRadDB) is onselector.result.
- inventory(datatree_dir: str | None = None, detailed: bool = False, archive_dir: str | None = None) None[source][source]#
Print what data is available on disk — which radars, which time periods.
Answers “what can I analyse?” before
open()(archive side) orarchive()(input side). Prints only; nothing is loaded into memory.- Parameters:
datatree_dir (str, optional) – Scan a directory of saved DataTree files (
.zarr/.nc) — i.e. data not archived yet. Radar and time come from the filename (<RADAR>_<YYYYMMDD>_<HHMMSS>). When omitted, the RadDB archive is scanned instead.detailed (bool) – Add a per-radar, per-day breakdown; for the archive also the LUT size, the sweep count and the moment columns stored per volume.
archive_dir (str, optional) – Override the archive location for this call.
Examples
>>> db.inventory() # what is archived >>> db.inventory(detailed=True) # ... day by day >>> db.inventory(datatree_dir="/data/MCH_datatree") # what could be archived
- open(time_period=None, radars: str | list[str] | None = None, columns: list[str] | None = None, filters=None, archive_dir: str | None = None) RadDB[source][source]#
Load archived data into a data-carrying
RadDB.- Parameters:
time_period (str, datetime or (start, end), optional) – Time range to load.
Noneloads everything available.radars (str or list of str, optional) – Radar(s) to load.
None(default) loads all radars in the archive.columns (list of str, optional) – Subset of moment columns to load (
gate_idis always included).filters (dict or list of dict, optional) – Gate filter(s)
{"var", "logic", "threshold"}applied after load (a list is combined with AND).archive_dir (str, optional) – Override the archive location for this call.
- Returns:
A data-carrying instance (
rdf).- Return type:
- plot_cross_section(variable: str = 'DBZH', radar: str | None = None, timestep=None, save: str | None = None, **kwargs)[source][source]#
Plot a vertical cross-section from an
extract_cross_section()result.
- plot_ppi(sweep: int | str = 1, variable: str = 'DBZH', radar: str | None = None, timestep=None, save: str | None = None, **kwargs)[source][source]#
Plot a PPI of one sweep from the loaded data. Returns the matplotlib artist.
- plot_rhi(azimuth: float, variable: str = 'DBZH', radar: str | None = None, timestep=None, save: str | None = None, **kwargs)[source][source]#
Plot a pseudo-RHI through a radar site at
azimuth. Returns the matplotlib artist.
- to_datatree(radar: str | None = None, timestep=None, label_column: str = 'DBZH') DataTree[source][source]#
Reconstruct a single-volume
xr.DataTreefrom the loaded data.Recovers geometry from the LUT and NaN-fills gates absent from the data. Selects one radar and one volume:
radar— inferred when the data covers exactly one radar, else required.timestep— nearestvolume_time; required only when several volumes.
- to_geoarrow(geometry: str = 'point', columns: list[str] | None = None, max_rows: int | None = 5000000)[source][source]#
Return the data as a GeoArrow-tagged
pyarrow.Table.This is the export path for GPU map renderers (deck.gl / lonboard) and for GeoParquet. The dynamic columns come straight from the polars frame with no copy; the LUT geometry is joined here and only here.
- Parameters:
geometry ({"point", "polygon"}) –
"point"gives one gate centroid per row (~24 B/gate)."polygon"gives the exact wedge — the gate’s four corners taken from the LUT’s per-sweep edge arrays (~100 B/gate).columns (list of str, optional) – Dynamic columns to export.
None(default) exports all of them.gate_idis always included.max_rows (int, optional) – Guardrail: raise if the frame is larger, since renderers top out around a few million features. Pass
Noneto disable.
- Returns:
The dynamic columns plus a
geometrycolumn carrying the GeoArrow extension metadata.- Return type:
pyarrow.Table
Examples
>>> import lonboard >>> lonboard.viz(rdf.crop_by_bbox(bounds).to_geoarrow(geometry="polygon"))
- class raddb.StageTimer[source][source]#
Bases:
objectAccumulates per-stage timing records for pipeline profiling.
Example
>>> timer = StageTimer() >>> with timer.time_stage("my_stage", volume="vol_001", sweep=2): ... do_work() >>> timer.print_summary() >>> plot_profiling_dashboard(timer)
- record(stage: str, duration: float, volume: str | None = None, sweep: int | None = None, t_start: float | None = None)[source][source]#
Manually append a pre-measured timing entry.
- summary() DataFrame[source][source]#
Aggregate by stage — returns (sum, mean, min, max, count) sorted by total time.
- raddb.add_feature_to_df(df: DataFrame, feature_name: str, compute_fn: callable) DataFrame[source][source]#
Add a new column to a DataFrame computed from existing columns.
- Parameters:
df (pd.DataFrame) – Input DataFrame (e.g. from
parquet_to_dataframe()).feature_name (str) – Name of the new column to add.
compute_fn (callable) –
Function that takes
dfand returns a Series or array of the same length. Example:def my_feature(df): return df["ZDR"] + df["DBZH"] * 0.1
- Returns:
Copy of
dfwith the new column appended.- Return type:
pd.DataFrame
- raddb.add_feature_to_dt(dt: DataTree, feature_name: str, compute_fn: callable) DataTree[source][source]#
Add a new variable to every sweep in a DataTree.
- Parameters:
dt (xr.DataTree) – Input DataTree with
sweep_Ngroups.feature_name (str) – Name of the new variable to add to each sweep Dataset.
compute_fn (callable) –
Function that takes an
xr.Dataset(one sweep) and returns anxr.DataArraywith matching dimensions. Example:def kdp_proxy(ds): return (ds["PHIDP"].diff("range") / 0.250).clip(0)
- Returns:
New DataTree with the computed variable added to every sweep.
- Return type:
xr.DataTree
- raddb.add_lut_projection(lut_df: DataFrame | DataFrame, epsg: int | None = None, crs=None) DataFrame | DataFrame[source][source]#
Add projected coordinates to a LUT DataFrame.
Converts the
latitude/longitudecolumns to the target CRS and appendsx_{suffix}/y_{suffix}columns, wheresuffixis the EPSG code (if available) or"custom".Accepts either a polars or a pandas frame and returns the same kind — LUT loading is polars, but LUT generation still builds pandas frames.
Requires pyproj (
pip install pyproj).- Parameters:
lut_df (pl.DataFrame or pd.DataFrame) – LUT DataFrame with
latitudeandlongitudecolumns (degrees, WGS-84 / EPSG:4326).epsg (int, optional) – EPSG code for the target CRS. Example:
2056for CH1903+ / LV95 (Swiss national grid).crs (pyproj.CRS or any CRS-coercible object, optional) – Alternative to
epsg. Used whenepsgisNone. Can be a pyprojCRSobject, a WKT string, or any value accepted bypyproj.CRS().
- Returns:
Copy of
lut_df(same kind) with two new columns:x_{suffix}(easting / metres) andy_{suffix}(northing / metres).- Return type:
pl.DataFrame or pd.DataFrame
- Raises:
ValueError – If neither
epsgnorcrsis provided.ImportError – If pyproj is not installed.
Examples
Add Swiss LV95 (CH1903+) coordinates to a LUT:
>>> lut_ch = add_lut_projection(lut_df, epsg=2056) >>> lut_ch[["x_2056", "y_2056"]].head()
Use a custom pyproj CRS:
>>> import pyproj >>> my_crs = pyproj.CRS.from_epsg(32632) # UTM zone 32N >>> lut_utm = add_lut_projection(lut_df, crs=my_crs)
- raddb.antenna_vectors_to_cartesian(ranges: ndarray, azimuths: ndarray, elevations: ndarray, ke: float = 1.3333333333333333, edges: bool = False) tuple[ndarray, ndarray, ndarray][source][source]#
Convert radar antenna coordinates to Cartesian (x, y, z).
Replicates the standard radar beam propagation formula used by PyART (
pyart.core.transforms.antenna_vectors_to_cartesian) using only numpy. The 4/3 effective-Earth-radius model is used by default.- Parameters:
ranges (1-D array, shape (n_gates,)) – Range to each gate in meters.
azimuths (1-D array, shape (n_rays,)) – Azimuth angle of each ray in degrees (0 = North, clockwise).
elevations (1-D array, shape (n_rays,)) – Elevation angle of each ray in degrees.
ke (float) – Effective Earth radius scale factor (default 4/3).
edges (bool) – If True, interpolate ranges/azimuths/elevations to gate edges (size N+1) before computing Cartesian coords. Used for
pcolormesh(shading="flat")rendering.
- Returns:
x, y, z – Cartesian coordinates in meters relative to the radar.
- Return type:
arrays, shape (n_rays, n_gates) — or (n_rays+1, n_gates+1) if
edges.
- raddb.archive_multiple_volumes(volumes: list[DataTree] | dict[str, DataTree], radar: str, base_output_path: str, filter_feature: str = 'DBZH', filter_threshold: float = 0.0, filter_logic: str = '>', verbose: bool = True, timer: StageTimer | None = None) list[dict][source][source]#
Archive multiple DataTree volumes sequentially.
- Parameters:
volumes (list or dict) – If a list, each element is a DataTree. If a dict, keys are volume labels and values are DataTrees.
radar (str) – Radar identifier.
base_output_path (str) – Base output directory.
filter_feature (str) – Column to use for gate filtering (default
"DBZH").filter_threshold (float) – Threshold value (default
0.0).filter_logic (str) – Comparison operator (default
">").verbose (bool) – Print progress.
timer (StageTimer, optional) – Profiling timer.
- Returns:
Results with keys: label, success, error, polar_path, n_gates.
- Return type:
- raddb.archive_volume(dt: DataTree, radar: str, base_output_path: str, filter_feature: str = 'DBZH', filter_threshold: float = 0.0, filter_logic: str = '>', timer=None, volume: str | None = None) str[source][source]#
Archive a single DataTree volume to Parquet format.
Converts the DataTree to a DataFrame, generates a
gate_idfor each gate (linking back to the LUT), drops gates that do not satisfyfilter_feature [filter_logic] filter_threshold, and saves the result as a POL parquet file.Non-matching gates are dropped (row removal), so zero values in surviving gates are never converted to NaN. NaN values in the reconstruction come only from gates absent in the parquet (i.e. gates that were filtered out or had no data in the original DataTree).
HZT availability check: if
"HZT"is not present in the DataTree,"HC_PYART"is also skipped because it requires HZT to be meaningful.- Parameters:
dt (xr.DataTree) – Processed volume (any radar network).
radar (str) – Radar identifier (single letter, e.g.
"A").base_output_path (str) – Base output directory for parquet files.
filter_feature (str) – Column to use for gate filtering (default
"DBZH").filter_threshold (float) – Threshold value for the filter (default
0.0).filter_logic (str) – Comparison operator (default
">").timer (StageTimer, optional) – Profiling timer.
volume (str, optional) – Volume label for timer records.
- Returns:
Path to the saved POL parquet file.
- Return type:
- raddb.archive_volumes_multi_radar(volumes_by_radar: dict[str, list[DataTree] | dict[str, DataTree]], base_output_path: str, filter_feature: str = 'DBZH', filter_threshold: float = 0.0, filter_logic: str = '>', verbose: bool = True, timer: StageTimer | None = None) dict[str, list[dict]][source][source]#
Archive volumes for multiple radars sequentially.
- Parameters:
volumes_by_radar (dict) – Keys are radar names, values are lists or dicts of DataTrees. Example:
{"A": [dt1, dt2], "D": [dt3, dt4]}base_output_path (str) – Base output directory.
filter_feature (str) – Column to use for gate filtering (default
"DBZH").filter_threshold (float) – Threshold value (default
0.0).filter_logic (str) – Comparison operator (default
">").verbose (bool) – Print progress.
timer (StageTimer, optional) – Profiling timer.
- Returns:
Keys are radar names, values are lists of result dicts.
- Return type:
- raddb.cartesian_to_geographic(x: ndarray, y: ndarray, z: ndarray, radar_lat: float, radar_lon: float, radar_alt: float) tuple[ndarray, ndarray, ndarray][source][source]#
Convert gate Cartesian offsets to geographic coordinates.
Uses an equirectangular approximation which is accurate for typical radar ranges (< 250 km).
- Parameters:
x (arrays) – Cartesian offsets from the radar in meters (x = east, y = north, z = height above radar).
y (arrays) – Cartesian offsets from the radar in meters (x = east, y = north, z = height above radar).
z (arrays) – Cartesian offsets from the radar in meters (x = east, y = north, z = height above radar).
radar_lat (float) – Radar site latitude and longitude in degrees.
radar_lon (float) – Radar site latitude and longitude in degrees.
radar_alt (float) – Radar site altitude in meters above sea level.
- Returns:
lat, lon, alt – Geographic coordinates of each gate.
- Return type:
arrays (same shape as input)
- raddb.compute_gate_xyz(ranges: ndarray, azimuths: ndarray, elevations: ndarray, ke: float = 1.25) tuple[ndarray, ndarray, ndarray][source][source]#
Compute gate Cartesian coordinates from polar coordinates.
Thin wrapper around
antenna_vectors_to_cartesian()for backwards compatibility.
- raddb.dataframe_to_datatree(df: DataFrame, radar: str, base_path: str | Path, label_column: str = 'DBZH', max_workers: int = 1) DataTree[source][source]#
Reconstruct a DataTree from an in-memory per-gate DataFrame.
The df→DataTree core shared by
parquet_to_datatree()and by DataFrame plotting: it joinsdfwith the radar LUT ongate_idto recover geometry (sweep/azimuth/range + lat/lon/alt/x/y/z and any projection cols), fills the full(azimuth × range)grid, and NaN-fills gates absent fromdf— so a cropped/filtered DataFrame reconstructs to a DataTree that carries the correct geometry but only the rows present indf, with the DataFrame’s own values (honouring crops or added feature columns).dfmust be a single radar and a single volume already (seeraddb.RadDB.datatree_from_df()for the radar/volume selection helper). Any of the LUT’s geometry columns already ondfare dropped and taken from the LUT instead, so acrop_bboxframe (which carriessweep/x_2056/y_2056/z/altitude) reconstructs cleanly.- Parameters:
df (pd.DataFrame) – Per-gate rows with a
gate_idcolumn (+ measurement columns).radar (str) – Radar identifier whose LUT to join against.
base_path (str or Path) – RadDB archive base directory.
label_column (str) – Feature used for reconstruction (default
"DBZH").max_workers (int) – Parallel workers for sweep reconstruction.
- Return type:
xr.DataTree
- Raises:
FileNotFoundError – If the LUT or radar info files are missing.
ValueError – If
dfis empty or no gates match the LUT.
- raddb.datatree_to_dataframe(dt: DataTree, max_workers: int = 1) DataFrame[source][source]#
Flatten a DataTree into a single pandas DataFrame.
Each sweep is converted independently and concatenated, with a
sweepcolumn indicating the source sweep number.
- raddb.datatree_to_dataset(dt: DataTree, sweep: str | int) Dataset[source][source]#
Extract a single sweep Dataset from a DataTree.
- raddb.datatree_to_parquet(dt: DataTree, radar: str, base_output_path: str, filter_feature: str = 'DBZH', filter_threshold: float = 0.0, filter_logic: str = '>', max_workers: int = 1) str[source][source]#
Convert a DataTree to a filtered POL parquet file.
Gates that do not satisfy
filter_feature [filter_logic] filter_thresholdare dropped (row removal) before saving. Zero values in surviving gates are preserved as-is.If
"HZT"is absent from the DataTree,"HC_PYART"is also skipped because it requires HZT to be meaningful.
- raddb.filter_df(df: DataFrame, feature: str = 'DBZH', threshold: float = 0.0, logic: str = '>') DataFrame[source][source]#
Filter a DataFrame, keeping rows where
feature [logic] threshold.- Parameters:
- Returns:
Filtered DataFrame with non-matching rows dropped (index reset).
- Return type:
pd.DataFrame
- Raises:
KeyError – If
featureis not a column ofdf.ValueError – If
logicis not one of the supported operators.
- raddb.filter_dt(dt: DataTree, feature: str = 'DBZH', threshold: float = 0.0, logic: str = '>') DataTree[source][source]#
Filter a DataTree, masking gates where
feature [logic] thresholdis False.Gates that do not satisfy the condition are set to NaN across all data variables in each sweep. Gates that do satisfy the condition keep their original values unchanged — including legitimate zero values.
Note
This operates on the multidimensional DataTree structure via
xr.Dataset.where(). For tabular (row-level) filtering usefilter_df()instead, which drops non-matching rows entirely.- Parameters:
- Returns:
New DataTree where non-matching gates have NaN for all variables. Matching gates are left entirely unchanged (zeros remain zeros).
- Return type:
xr.DataTree
- Raises:
ValueError – If
logicis not a supported operator.
- raddb.find_datatree_files(directory: str | Path, recursive: bool = True, extensions: tuple[str, ...] = ('.nc', '.nc4', '.cdf', '.zarr'), start_time: str | Timestamp | None = None, end_time: str | Timestamp | None = None, strict_time: bool = False) list[Path][source][source]#
Find DataTree files (NetCDF files / Zarr stores) under directory.
Zarr stores are directories — they are matched as leaves and never descended into. Results are sorted by (filename timestamp, path); files whose stem has no parseable timestamp sort last.
- Parameters:
directory (str or Path) – Directory to scan.
recursive (bool) – Recurse into subdirectories (default
True).extensions (tuple of str) – File/store suffixes to match (case-insensitive).
start_time (str or Timestamp, optional) – Keep only files whose filename timestamp (see
_parse_datatree_file_time()) falls in this range.end_time (str or Timestamp, optional) – Keep only files whose filename timestamp (see
_parse_datatree_file_time()) falls in this range.strict_time (bool) – When a time range is given, drop files whose timestamp cannot be parsed from the filename (default
False— they are kept).
- Returns:
Matching files/stores, time-sorted.
- Return type:
list of Path
- raddb.generate_gate_id(radar: str, sweep: int, azimuth: float, range_m: float) int[source][source]#
Create a unique gate identifier as a 64-bit integer.
Scalar convenience wrapper around
encode_gate_ids()— see there for the encoding definition.
- raddb.generate_lut_from_datatree(dt: DataTree, radar: str, output_base_path: str, ke: float = 1.25, network: str = '', projection_epsg: int | None = None, projection_crs=None) str[source][source]#
Generate a LUT from an xarray DataTree.
This is the generic LUT generator — it works with any DataTree that has the standard xradar coordinate layout (azimuth, range, elevation per sweep).
For MCH-specific LUT generation from raw METRANET files, use
raddb.mch.generate_mch_lut()instead.- Parameters:
dt (xr.DataTree) – DataTree with
sweep_Ngroups, each containingazimuth,range, andelevationcoordinates.radar (str) – Radar identifier (single letter, e.g.
"A").output_base_path (str) – Base directory for LUT storage.
ke (float) – Effective Earth radius scale factor (default 1.25 for Switzerland).
network (str, optional) – Network identifier stored in radar info YAML.
projection_epsg (int, optional) – EPSG code for an additional projected coordinate system to include in the LUT (e.g.
2056for CH1903+ / LV95). Addsx_{epsg}/y_{epsg}columns viaadd_lut_projection().projection_crs (pyproj.CRS or CRS-coercible, optional) – Alternative to
projection_epsg.
- Returns:
Path to the saved LUT parquet file.
- Return type:
- raddb.get_full_sweep_index(lut_df: DataFrame, sweep: int) MultiIndex[source][source]#
Get the full (azimuth, range) MultiIndex for a sweep from the LUT.
- raddb.join_labels_with_lut(df_labels: DataFrame, lut_path: str | Path) DataFrame[source][source]#
Join label data with the LUT to recover spatial coordinates.
- raddb.labels_to_dataframe(labels: ndarray, gate_ids, extra_columns: dict | None = None) DataFrame[source][source]#
Create a DataFrame from prediction labels and gate IDs.
- raddb.list_sweep_names(dt: DataTree) list[str][source][source]#
Return sorted sweep group names from a DataTree.
- raddb.load_radar_info(radar: str, lut_base_path: str | Path) dict[source][source]#
Load the radar info YAML for a radar.
- raddb.load_radar_lut(radar: str, lut_base_path: str | Path) DataFrame[source][source]#
Load the LUT parquet for a radar as a polars DataFrame.
Call
polars.DataFrame.to_pandas()on the result if you need pandas.
- raddb.normalize_radar_name(radar: str) str[source][source]#
Normalize radar name to use only the single letter identifier.
Handles both formats: - “MLA” -> “A” - “A” -> “A” - “MLW” -> “W”
- Parameters:
radar (str) – Radar name (can be “ML*” format or just the letter)
- Returns:
Normalized single-letter radar name (uppercase)
- Return type:
Examples
>>> normalize_radar_name("MLA") 'A' >>> normalize_radar_name("A") 'A' >>> normalize_radar_name("MLW") 'W'
- raddb.open_any_datatree(path: str | Path, engine: str | None = None, **open_kwargs) DataTree[source][source]#
Open a DataTree from disk — NetCDF file or Zarr store.
Engine resolution: an explicit
enginewins; a*.zarrsuffix or a directory containing.zgroup/zarr.jsonselects"zarr"; otherwise xarray auto-detects the NetCDF backend (netCDF4 / h5netcdf).- Parameters:
path (str or Path) – Path to a NetCDF file or Zarr store.
engine (str, optional) – xarray backend override (e.g.
"h5netcdf","zarr").**open_kwargs – Forwarded to
xarray.open_datatree().
- Return type:
xr.DataTree
- raddb.parquet_to_dataframe(radar: str, base_path: str | Path, start_time: str | Timestamp | None = None, end_time: str | Timestamp | None = None, columns: list[str] | None = None, merge_lut: bool = False) DataFrame[source][source]#
Load archived POLAR parquet files as a single DataFrame.
- Parameters:
radar (str) – Single-letter radar identifier.
base_path (str or Path) – RadDB base directory.
start_time (optional) – Filter by volume timestamp.
end_time (optional) – Filter by volume timestamp.
columns (list of str, optional) – Columns to load from parquet files.
merge_lut (bool) – If True, merge with the LUT to add spatial coordinates (azimuth, range, latitude, longitude, altitude, x, y, z).
- Returns:
Includes a
volume_timecolumn (the volume timestamp parsed from each source filename) so a multi-volume frame can be split back into single volumes — used bydataframe_to_datatree()/ PPI plotting.- Return type:
pd.DataFrame
- raddb.parquet_to_datatree(radar: str, base_path: str | Path, start_time: str | Timestamp | None = None, end_time: str | Timestamp | None = None, label_column: str = 'DBZH', max_workers: int = 1) DataTree[source][source]#
Load archived POLAR parquet files and reconstruct a DataTree.
Loads all volumes in the given time range, joins with the LUT to recover azimuth/range coordinates, and reconstructs an xarray DataTree.
- Parameters:
radar (str) – Single-letter radar identifier.
base_path (str or Path) – RadDB base directory.
start_time (optional) – Filter by volume timestamp.
end_time (optional) – Filter by volume timestamp.
label_column (str) – Column to use for reconstruction (default
"DBZH").max_workers (int) – Parallel workers for sweep reconstruction.
- Return type:
xr.DataTree
- Raises:
FileNotFoundError – If the LUT or radar info files are missing.
ValueError – If no data is found.
- raddb.plot_latent_scatter(df: DataFrame, config: list[dict], figsize: tuple[float, float] | None = None, fig_height: float = 4.6, **scatter_kwargs)[source][source]#
Publication-ready 2×3 AMT latent-space scatter figure.
Creates a 2-row × 3-column figure with width=6.9 inches (AMT full-column width). Each subplot shows a scatter of
df["L1"]vsdf["L2"]coloured by one radar variable. A compact inset colorbar with a white semi-transparent background is placed inside each subplot.x-tick labels and x-axis labels are shown only on the bottom row. y-tick labels and y-axis labels are shown only on the first column.
- Parameters:
df (pd.DataFrame) – Must contain columns
"L1","L2", and the variable column named in each panel’s"var"key.Exactly 6 panel descriptors, one per subplot (row-major: panels 0-2 fill row 0, panels 3-5 fill row 1). Each dict must have:
"var": str — column indfto use for colouring."label": str — colorbar label text."cmap": str or Colormap — colormap passed toscatter."norm": matplotlib Normalize, optional — normalisation."cbar_kwargs": dict — extra kwargs forwarded tofig.colorbar()(e.g.{"ticks": [...]},{"extend": "both"})."scatter_kwargs": dict, optional — extra kwargs forax.scatter()(e.g.{"s": 1, "alpha": 0.5}).
figsize (tuple, optional) – Override figure size. Default
(6.9, fig_height).fig_height (float) – Figure height in inches. Default
4.6.**scatter_kwargs – Global fallback kwargs for
ax.scatter()(overridden per-panel byconfig[i]["scatter_kwargs"]).
- Returns:
fig (matplotlib.figure.Figure)
axes (ndarray of shape (2, 3))
Example config
————–
config = [ –
- {“var”: “DBZH”, “label”: “DBZH [dBZ]”, “cmap”: “turbo”,
”norm”: Normalize(-10, 60), “cbar_kwargs”: {}, “scatter_kwargs”: {“s”: 0.5}},
- {“var”: “ZDR”, “label”: “ZDR [dB]”, “cmap”: “RdBu_r”,
”norm”: Normalize(-2, 5), “cbar_kwargs”: {}, “scatter_kwargs”: {“s”: 0.5}},
…
]
- raddb.plot_ppi(dt, sweep, variable: str, ax=None, use_cartopy: bool | None = None, coords: str = 'geo', add_range_rings: bool = True, add_colorbar: bool = True, title: str | None = None, figsize: tuple[float, float] = (6, 6), xlim: tuple[float, float] | None = None, ylim: tuple[float, float] | None = None, **plot_kwargs)[source][source]#
Plan Position Indicator from a single sweep of a reconstructed DataTree.
- Parameters:
dt (xr.DataTree or xr.Dataset) – Reconstructed DataTree (from
parquet_to_datatree) or single sweep Dataset.sweep (int or str) – Sweep index (
3) or group name ("sweep_3"). Ignored ifdtis already a Dataset.variable (str) – Variable to plot:
"DBZH","ZDR","RHOHV","PHIDP","HZT","TEMP","HC_MCH","HC_PYART".ax (matplotlib.axes.Axes, optional) –
use_cartopy (bool, optional) – If None (default), auto-detect: use cartopy when installed. Pass
Falseto force plain matplotlib. PassTrueto require cartopy (raises ImportError if missing).coords ({"geo", "cartesian", "swiss"}) –
Coordinate frame for the axes:
"geo"—(longitude, latitude)degrees."cartesian"—(x, y)km relative to the radar (origin at the site). With cartopy this is an AEQD map centred on the radar."swiss"(aka"lv95"/"2056") — absolute Swiss LV95(x_2056, y_2056), axis ticks in km (E from the 2 000 km false-easting, N from 1 000 km). This is the same frame as the AOI quicklook, so a cropped-df PPI lines up with crop_* overlays. With cartopy (use_cartopy) it overlays country borders reprojected to LV95 as a basemap; otherwise the same plot without borders.
add_range_rings (bool) – For
cartesian/swissmodes, draw 50/100/150 km range rings.add_colorbar (bool) –
title (str, optional) –
figsize (tuple) – Figure size in inches. Default
(6, 6).xlim (tuple, optional) – x-axis limits in the natural units of the chosen
coords(degrees for"geo", km for"cartesian").ylim (tuple, optional) – y-axis limits (same units as
xlim).**plot_kwargs – Forwarded to
pcolormesh(overrides defaults forcmap,vmin,vmax,norm, etc.).
- Return type:
- raddb.plot_profiling_dashboard(timer: StageTimer, title_prefix: str = '', save_path: str | None = None)[source][source]#
3x2 profiling dashboard: stage totals, distribution pie, per-volume bars, stage breakdown per volume, and mean time per sweep line chart.
- raddb.plot_rhi(dt, azimuth: float, variable: str, radar: str = '', az_tol: float = 1.0, max_range_km: float | None = None, max_height_km: float | None = 20.0, ke: float = 1.3333333333333333, ax=None, add_colorbar: bool = True, title: str | None = None, figsize: tuple[float, float] = (10, 4), **plot_kwargs)[source][source]#
Pseudo-RHI from a volume PPI scan — PyART’s
cross_section_ppi.For every sweep in the DataTree, selects the ray whose azimuth is closest to
azimuth(withinaz_tol, wrap-around safe), regrids all rays to a common range axis, and renders a 2-D(ground_range, height_ASL)pcolormesh using gate edges computed with the 4/3 Earth-radius model — so curved-trapezoid gates and Earth curvature are both physically correct.- Parameters:
dt (xr.DataTree) – Reconstructed DataTree (from
parquet_to_datatree).azimuth (float) – Target azimuth in degrees (0..360; 0 = North, clockwise).
variable (str) –
radar (str, optional) – Radar identifier shown in the plot title (e.g.
"A").az_tol (float) – Maximum allowed angular distance between the requested azimuth and the nearest available ray.
max_range_km (float, optional) – Clip the ground-range axis.
max_height_km (float, optional) – Clip the height axis.
ke (float) – Effective Earth radius factor (default 4/3, pyart standard).
ax (see
plot_ppi.) –add_colorbar (see
plot_ppi.) –title (see
plot_ppi.) –figsize (see
plot_ppi.) –**plot_kwargs (see
plot_ppi.) –
- Return type:
- raddb.plot_stage_totals(timer: StageTimer, title: str = 'Pipeline — Total Time per Stage', save_path: str | None = None)[source][source]#
Horizontal bar chart: total wall-clock time per pipeline stage.
- raddb.plot_sweep_timing(timer: StageTimer, title: str = 'Processing Time per Sweep', save_path: str | None = None)[source][source]#
Box-plot: distribution of total sweep processing time across volumes.
- raddb.plot_volume_timing(timer: StageTimer, title: str = 'Processing Time per Volume', save_path: str | None = None)[source][source]#
Stacked bar chart: per-volume time breakdown by pipeline stage.
- raddb.read_parquet_files(base_path: str | Path, pattern: str = '**/*POL.parquet', columns: list[str] | None = None, verbose: bool = True) DataFrame[source][source]#
- raddb.reconstruct_datatree(df_joined: DataFrame, lut_path: str | Path, radar_info_path: str | Path, label_column: str = 'hydrometeor_class', max_workers: int = 1) DataTree[source][source]#
Reconstruct a full DataTree from joined data + LUT + radar info.
- raddb.reconstruct_sweep_dataset(df_joined: DataFrame, sweep: int, lut_df: DataFrame, radar_info: dict, label_column: str = 'hydrometeor_class', sweep_corners: dict | None = None) Dataset[source][source]#
Reconstruct a single sweep Dataset from joined data.
Data variables come from the filtered POL parquet (NaN where gates were dropped). Per-gate spatial coords (lat/lon/alt/x/y/z and any x_<epsg>/ y_<epsg>) come from the LUT directly so every gate has a valid geometry regardless of filtering.
If
sweep_cornersis provided, per-gate edge arrays (x_edges,y_edges,z_edges,lon_edges,lat_edges) of shape(n_az+1, n_range+1)are attached as data_vars for pcolormesh rendering withshading="flat".
- raddb.scan_polar_parquet(radar: str, base_path: str | Path, start_time: str | pd.Timestamp | None = None, end_time: str | pd.Timestamp | None = None, columns: list[str] | None = None) pl.LazyFrame | None[source][source]#
Scan archived POLAR parquet files as a single polars LazyFrame.
The polars counterpart of
parquet_to_dataframe(), used byraddb.RadDB.open(). Scanning (rather than reading) lets polars push the column projection into the parquet reader, and avoids the pandas intermediate frames and thepd.concatcopy of the full result.Unlike
parquet_to_dataframe()this never merges the LUT: the static geometry stays in its own table and is joined only by theto_*converters.- Parameters:
radar (str) – Single-letter radar identifier.
base_path (str or Path) – RadDB base directory.
start_time (optional) – Filter by volume timestamp.
end_time (optional) – Filter by volume timestamp.
columns (list of str, optional) – Columns to project.
volume_time/radarare added here rather than read, so they are dropped from the parquet projection.
- Returns:
Nonewhen no volume matches — callers decide what an empty result means. The frame carries avolume_timeand aradarcolumn.- Return type:
pl.LazyFrame or None