Add radar wavelength reader for NISAR GSLC products - #225
Open
s-sasaki-earthsea-wizard wants to merge 3 commits into
Open
Add radar wavelength reader for NISAR GSLC products#225s-sasaki-earthsea-wizard wants to merge 3 commits into
s-sasaki-earthsea-wizard wants to merge 3 commits into
Conversation
Add `opera_utils.nisar.get_nisar_wavelength(filename, subdataset)`, which resolves the frequency group from the subdataset path (so the wavelength always matches the radar band and sub-band of the data being processed), reads `centerFrequency`, and converts it via SPEED_OF_LIGHT. Also add a thin `GslcProduct.get_wavelength(frequency=...)` delegating method. Both work on local files and remote URLs through `open_h5`. Missing or invalid (non-scalar, non-real, non-finite, <= 0) `centerFrequency` values raise a contextual ValueError instead of falling through silently. Design discussion: isce-framework/dolphin#704 🤖 Assisted by [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
Cover `get_nisar_wavelength` and `GslcProduct.get_wavelength` with synthetic HDF5 files built by a factory fixture: - subdataset path resolution: frequencyA/B datasets, the frequency group itself, SSAR paths (band-agnostic), and reading from the subdataset's parent group rather than the first frequency group in the file - path validation: missing/multiple frequency segments, frequencyC, and longer segment names like frequencyAB are rejected - centerFrequency validation: positive integer dtypes accepted; missing, group-instead-of-dataset, non-scalar, complex/bool/string dtypes, zero, negative, NaN, and +/-inf rejected with the filename and dataset path included in the error message - known value: NISAR L-band 20 MHz mode resolves to ~23.84 cm - product method: default frequency, explicit "B", invalid frequency, and a frequencyB-only product 🤖 Assisted by [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
for more information, see https://pre-commit.ci
Author
|
Gentle ping on this — no rush if the review queue is deep. If you'd like any changes (API naming, scope, test layout), let me know and I'll turn them around quickly. Happy to rebase onto current |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
NISAR GSLC products state their radar center frequency per frequency sub-band, in
centerFrequency, butopera_utilshas no reader for it.This adds one:
opera_utils.nisar.get_nisar_wavelength(filename, subdataset)— resolves the frequency group from thesubdatasetpath and returnsSPEED_OF_LIGHT / centerFrequency.GslcProduct.get_wavelength(frequency="A")— a thin convenience wrapper for when you have a product object rather than a subdataset path.Both work on local files and remote (HTTPS/S3) URLs through
open_h5. This is a pure addition: no existing function or signature changes.Motivation
Downstream, dolphin cannot auto-detect the radar wavelength for NISAR GSLC inputs (isce-framework/dolphin#704); its wavelength stays unset and
timeseries/outputs silently remain in radians. In that issue, the suggestion was to put the metadata reader inopera_utilsrather than in dolphin, since this is exactly the kind of product-metadata accessopera_utils.nisaralready owns.This PR is that reader.
A tempting shortcut is a hardcoded L-band constant (or a mode→frequency lookup table keyed on the filename). Real products rule that out. Across the
NISAR_L2_GSLC_BETA_V1collection we measured six distinct center frequencies — 1221.5, 1229.0, 1236.5, 1239.0, 1257.5, 1293.5 MHz — a wavelength spread of ~13.7 mm (5.9% of a wavelength). No single constant is correct, and many granules carry bothfrequencyAandfrequencyBwith two different center frequencies in one file, so the filename alone cannot say which applies.The product states the value exactly; the right move is to read it.
Design
subdatasetpath, not guessed.Reading the subdataset
.../frequencyA/HHreads.../frequencyA/centerFrequencyfrom the same group. This makes the wavelength always match the radar band and sub-band of the data actually being processed, and it removes any LSAR/SSARambiguity structurally (the band is part of the path).
subdatasetpath (exactly onefrequencyA/frequencyBsegment required). The convenience method follows the existingGslcProductconvention of defaulting its
frequencyargument to"A", but it never falls back to another available sub-band — on a frequencyB-only product, omitting the argument raises rather than silently returning B. This matters because A and B can differ by ~2.8% in wavelength, and that error would propagate directly into displacement.centerFrequency(non-scalar, non-real dtype, non-finite, ≤ 0) raises aValueErrornaming the dataset path and the file, rather than returning a plausible-but-wrong number.Testing
tests/test_nisar_wavelength.py, 27 tests): synthetic GSLC HDF5 built withh5py, covering subdataset path resolution (frequencyA/B, the group itself, SSAR paths, reading the subdataset's own group rather than thefirst frequency group), path validation (missing/multiple frequency segments,
frequencyC,frequencyAB),centerFrequencyvalidation (positive integer dtype accepted; missing, group-instead-of-dataset, non-scalar, complex/bool/ string dtypes, zero, negative, NaN, ±inf rejected with filename + path in the message), a known-value check (a 1,257.5 MHz center frequency ≈ 23.84 cm), and theGslcProductdelegation including a frequencyB-only product where the default"A"correctly raises.(mode, polarization) configuration) via remote byte-range reads — no bulk download. All 22 present sub-bands returned exactly
c / centerFrequency(bit-identical), all 4 absent sub-bands raised as designed, and the product method matched. Reproducible withmake validate-wavelength; the full report (pinned) is here, and the harness lives in the same repository.API policy question
Currently the reader raises
ValueErrorin two distinct situations: (a) the requested sub-band is genuinely absent from the product, and (b) the sub-band is present but itscenterFrequencymetadata is invalid. Case (b) — present butinvalid — should always raise, and I am not proposing to change it.
The one open choice is case (a): would maintainers prefer
get_nisar_wavelength/get_wavelengthto returnNonefor a genuinely absent requested sub-band, so the caller decides what to do?The current raise-on-absent keeps a
float-only return type and surfaces product/request mismatches loudly; returningNonewould change the public contract tofloat | None.Happy to go either way — flagging it here since it is the one user-visible policy decision in the PR.
Disclosure
These features, investigations, and tests were developed with AI assistance — Claude Opus 4.8 and Fable 5 (Anthropic), and GPT-5.6 Terra (OpenAI). All code was reviewed by the author and validated against an actual NISAR sample product.*