You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
open_nexradlevel2_datatree(..., incomplete_sweep="pad") (added in #332) auto-detects the azimuth grid for an incomplete sweep with util.extract_angle_parameters, which infers angle_res from the median ray-to-ray azimuth difference of the observed rays. WSR-88D azimuths carry oversampling jitter — a truncated super-res cut's measured median spacing can be ~0.486° instead of the nominal 0.5° — and the rounding step (np.round(median_diff, decimals=2)) turns that into angle_res = 0.49. reindex_angle then builds np.arange(0, 360, 0.49) → a 735-ray grid, a rotation no WSR-88D produces (super-res cuts are 720 rays, legacy cuts 360).
Consequences:
the padded sweep's azimuth dimension is physically impossible and unstable across polls of the same live volume (the inferred median shifts as more rays arrive) — while shape stability is a main reason to use "pad" in streaming pipelines;
grid centers are misaligned relative to the nominal 0.25°+k·0.5° slots, so rays land in wrong bins near the arc edges.
Reproducer (real data)
Uses a real truncated volume captured from the live feed: the first 18 chunk objects (1 S + 17 I) of KLOT volume 2026-07-27 19:01:44 UTC, packaged as an open-radar-data fixture (nexrad_level2_chunks_KLOT_998.tar.gz, PR to openradar/open-radar-data in flight — I can link it here once opened). The truncated sweep_2 has 600 observed rays with median measured spacing 0.4861°.
Note: the existing full-volume chunks fixture (nexrad_level2_chunks_KLOT.tar.gz) does not reproduce this at any truncation point — its truncated sweeps happen to infer the nominal 0.5° — which is why the new jittery capture is needed to pin the bug with real data.
Mechanism in isolation (no data download)
The same failure through the public util functions with a synthetic sweep at the real-world measured spacing:
xradar/util.py, extract_angle_parameters step 8: angle_res = np.round(median_diff, decimals=2) — the std-filtered median of observed diffs, rounded to 2 decimals, with no snap to the radar's nominal resolutions.
For the NEXRAD pad path, the exact resolution is available on the wire and already parsed: the MSG 31 data header's azimuth_resolution field (1 → 0.5°, 2 → 1.0°, ICD 2620002 Table XVII). Using it instead of inference makes the grid deterministic (720/360) regardless of jitter. A more general belt-and-braces option: in extract_angle_parameters, snap the inferred angle_res to the nearest member of a small set of plausible resolutions when sweep_mode == "azimuth_surveillance" (0.25/0.5/1.0), keeping free inference for RHI and exotic radars. Happy to submit a PR for either approach.
Summary
open_nexradlevel2_datatree(..., incomplete_sweep="pad")(added in #332) auto-detects the azimuth grid for an incomplete sweep withutil.extract_angle_parameters, which infersangle_resfrom the median ray-to-ray azimuth difference of the observed rays. WSR-88D azimuths carry oversampling jitter — a truncated super-res cut's measured median spacing can be ~0.486° instead of the nominal 0.5° — and the rounding step (np.round(median_diff, decimals=2)) turns that intoangle_res = 0.49.reindex_anglethen buildsnp.arange(0, 360, 0.49)→ a 735-ray grid, a rotation no WSR-88D produces (super-res cuts are 720 rays, legacy cuts 360).Consequences:
"pad"in streaming pipelines;Reproducer (real data)
Uses a real truncated volume captured from the live feed: the first 18 chunk objects (1
S+ 17I) of KLOT volume 2026-07-27 19:01:44 UTC, packaged as an open-radar-data fixture (nexrad_level2_chunks_KLOT_998.tar.gz, PR to openradar/open-radar-data in flight — I can link it here once opened). The truncatedsweep_2has 600 observed rays with median measured spacing 0.4861°.Output:
Until the fixture lands, the same 18 objects can be fetched directly (volume directories in the live bucket persist for a few days):
Note: the existing full-volume chunks fixture (
nexrad_level2_chunks_KLOT.tar.gz) does not reproduce this at any truncation point — its truncated sweeps happen to infer the nominal 0.5° — which is why the new jittery capture is needed to pin the bug with real data.Mechanism in isolation (no data download)
The same failure through the public
utilfunctions with a synthetic sweep at the real-world measured spacing:Root cause
xradar/util.py,extract_angle_parametersstep 8:angle_res = np.round(median_diff, decimals=2)— the std-filtered median of observed diffs, rounded to 2 decimals, with no snap to the radar's nominal resolutions.xradar/io/backends/nexrad_level2.py(pad path from ADD: Support reading NEXRAD Level 2 chunks from real-time S3 bucket #332): feeds that inferred value intoreindex_anglefor incomplete sweeps.Suggested fix
For the NEXRAD pad path, the exact resolution is available on the wire and already parsed: the MSG 31 data header's
azimuth_resolutionfield (1 → 0.5°, 2 → 1.0°, ICD 2620002 Table XVII). Using it instead of inference makes the grid deterministic (720/360) regardless of jitter. A more general belt-and-braces option: inextract_angle_parameters, snap the inferredangle_resto the nearest member of a small set of plausible resolutions whensweep_mode == "azimuth_surveillance"(0.25/0.5/1.0), keeping free inference for RHI and exotic radars. Happy to submit a PR for either approach.Environment
main(0.12.1.dev10+g79ba4956b, includes ADD: Support reading NEXRAD Level 2 chunks from real-time S3 bucket #332), xarray 2026.7.0, numpy 2.4.4, Python 3.12