sidescanpy.processing.range_correction#
Empirical range correction for the residual TVG range-dependence.
processing/radiometric.py’s apply_tvg() implements the physically-
grounded spreading+absorption TVG model. Even after that model is applied
real data can show a
remaining, altitude-invariant, non-monotonic range-dependent residual.
This module does not attempt to model the physical cause (it is not a
mistuned absorption coefficient or a missing grazing-angle/beam-pattern
correction). Instead it fits and removes whatever
range-dependent shape is actually observed in TVG-corrected data, per
frequency.
The library ships no calibration table: a table is fit from a specific
survey and instrument, so it belongs with the project that fitted it. Fit
one with fit_range_correction(), store it with
RangeCorrectionTable.save(), and pass it (or its path) as the
table argument of apply_empirical_range_correction() /
the "range_correction" step.
This is a separate, optional correction step ("range_correction")
from apply_tvg() — not in sidescanpy.processing.DEFAULT_STEPS; opt in
by listing it in the steps passed to sidescanpy.processing.correct.
2026 rshomberg@uri.edu
Contents
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str |
|
Per-frequency empirical range-correction lookup. |
|
Fit an empirical range-correction curve from one TVG-corrected channel. |
|
Apply a previously-fit empirical range correction to TVG-corrected backscatter. |
|
Correction (dB) of a piecewise-linear curve evaluated at |
- sidescanpy.processing.range_correction.STEP_NAME = 'range_correction'#
str(object=’’) -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to ‘strict’.
- class sidescanpy.processing.range_correction.RangeCorrectionTable#
Bases:
objectPer-frequency empirical range-correction lookup.
Each calibrated frequency stores a piecewise-linear correction curve (range-bin centers -> correction_db) fit by
fit_range_correction(). This is the “non-parametric smooth interpolation over the observed bins” approach from the design doc — deliberately not a fitted polynomial, since the two frequencies calibrated so far have qualitatively different residual shapes (a smooth bowl vs. a sharp near-field rise-then-plateau) that a low-order polynomial risks fitting poorly or overfitting.- add_curve(freq_hz, range_m, correction_db, fit_metadata=None)#
Add or replace the calibrated curve for one frequency.
- matched_frequency(freq_hz, tolerance_hz=1000.0)#
The calibrated frequency key matching
freq_hz, or None.
- correction_db_at(freq_hz, range_m, tolerance_hz=1000.0)#
Evaluate the fitted correction curve for
freq_hzatrange_m.Piecewise-linear interpolation between calibrated range-bin centers.
range_mvalues outside the calibration data’s observed span are clamped to the nearest edge’s correction value (np.interp’s default constant-extrapolation behavior) — a deliberate, documented policy, not an accident: the calibration data doesn’t cover ranges never seen in the fitting survey, and clamping is the safest default versus extrapolating an unverified shape.- Returns:
- np.ndarray, same shape as
range_m, or None if no calibrated - curve matches
freq_hzwithintolerance_hz— caller decides - what to do (see
apply_empirical_range_correction, which skips - the correction with a warning rather than guessing).
- np.ndarray, same shape as
- save(path)#
Write the table to a JSON file (see
to_dict).
- classmethod load(path)#
Read a table written by
save.
- sidescanpy.processing.range_correction.fit_range_correction(ds, bin_width_m=5.0, exclude_below_db=-110.0)#
Fit an empirical range-correction curve from one TVG-corrected channel.
- Parameters:
- dsxr.Dataset
Must be TVG-corrected and have a
ground_range_mvariable — i.e. already run through the"tvg"and"geometric"steps.- bin_width_mfloat, default 5.0
Passed through to
processing.profiles.backscatter_vs_range.- exclude_below_dbfloat, optional, default -110.0
Passed through as
exclude_below_dbto exclude the floor-clipped samples (~-120 dB exact-zero-amplitude floor) from biasing the fit.Nonedisables filtering.
- Returns:
- (range_m, correction_db, fit_metadata)
range_m/correction_dbare bin-center arrays ready forRangeCorrectionTable.add_curve().correction_db[i]is defined astarget_level_db - mean_backscatter[i], wheretarget_level_dbis the count-weighted mean across all bins — i.e. the fitted correction has ~zero net effect on the survey’s average absolute level, it only flattens the range-dependent shape.
- Raises:
- ValueError
If
dsisn’t TVG+geometric corrected, or no samples survive filtering.
- sidescanpy.processing.range_correction.apply_empirical_range_correction(ds, table=None)#
Apply a previously-fit empirical range correction to TVG-corrected backscatter.
- Parameters:
- dsxr.Dataset or SideScanSurvey
TVG-corrected (
"tvg"in processing_history) with aground_range_mvariable — i.e. already run through the"tvg"and"geometric"steps. ASideScanSurveyis corrected frequency by frequency with the same arguments; a new survey is returned and the input is not modified.- tableRangeCorrectionTable or path
Required. The calibration to apply, or the path of a JSON file written by
RangeCorrectionTable.save(). The library has no built-in default table.
- Returns:
- xr.Dataset or SideScanSurvey
Same type as
ds: a new dataset withbackscatter_intensityshifted by the fitted curve and a"range_correction"history record holding the curve actually applied (so it can be inverted without the table).Samples with non-finite
ground_range_m(masked water-column samples) receive a zero correction rather than propagating NaN into previously-finite backscatter values. Floor-clipped samples (exactlyFLOOR_DB) are left at the floor, as byapply_tvg().If no calibrated curve matches this dataset’s
freq_hz_start, logs a warning and returnsdsunchanged (no history record) — never guesses at an uncalibrated frequency.
- Raises:
- TypeError
If
tableis missing or not a table / path.- ValueError
If
dsisn’t TVG+geometric corrected, or the range correction is already recorded in processing_history (refuses to double-apply).
- sidescanpy.processing.range_correction.range_correction_db(ds, range_m, correction_db)#
Correction (dB) of a piecewise-linear curve evaluated at
ds["ground_range_m"].Clamped outside the curve’s span; non-finite ground range gets 0 dB. Shared by the forward step and its inversion.