sidescanpy.processing.radiometric#

Radiometric correction for sidescan sonar data.

This module implements Time-Varying Gain (TVG) correction to compensate for transmission losses in the water column. The raw backscatter_intensity values represent echo levels received by the transducer but do not account for:

  1. Geometric spreading: sound spreads in 3D, so intensity falls as 1/R² each way. Over the two-way path that is 40*log10(R) dB (samples are amplitude, converted with 20*log10, so dB values are comparable to intensity dB). Some authors use 30*log10(R) for sidescan, because the insonified seafloor patch grows in proportion to R and adds back 10*log10(R) (see the note on TVGParams); the coefficient is a parameter, not fixed.

  2. Absorption: water absorbs acoustic energy proportional to distance traveled (in dB: 2*alpha*R dB loss for two-way path)

TVG correction adds these losses back, so equal backscatter at different ranges produces equal corrected values. This is essential for radiometric calibration and mosaic generation.

The default absorption coefficient comes from the Francois-Garrison seawater absorption model (francois_garrison_absorption_db_per_m()):

      1. Francois and G. R. Garrison, “Sound absorption based on ocean measurements: Part I: Pure water and magnesium sulfate contributions,” J. Acoust. Soc. Am. 72(3), 896-907 (1982).

      1. Francois and G. R. Garrison, “Sound absorption based on ocean measurements: Part II: Boric acid contribution and equation for total absorption,” J. Acoust. Soc. Am. 72(6), 1879-1890 (1982).

Input backscatter is assumed to be linear amplitude; this module converts to dB, applies TVG, and outputs dB. The step is recorded in processing_history with its resolved parameters so it can be inverted exactly.

2026 rshomberg@uri.edu

Contents

DEFAULT_TEMPERATURE_C

Convert a string or number to a floating-point number, if possible.

DEFAULT_SALINITY_PSU

Convert a string or number to a floating-point number, if possible.

DEFAULT_DEPTH_M

Convert a string or number to a floating-point number, if possible.

DEFAULT_PH

Convert a string or number to a floating-point number, if possible.

DEFAULT_SPREADING_LOSS_DB

Default TVG spreading coefficient (dB per decade of range): 30 log R.

TVGParams

Parameters for time-varying gain correction.

francois_garrison_absorption_db_per_m

Seawater sound absorption (dB/m, one-way) after Francois & Garrison (1982).

center_frequency_hz

Acoustic center frequency of a channel (Hz), for frequency-dependent physics.

absorption_for_frequency

Default absorption coefficient (dB/m, one-way) for a sonar frequency.

tvg_gain_db

Per-range TVG gain (dB): spreading loss + absorption loss.

apply_tvg_to_amplitude

Apply TVG correction to a plain linear-amplitude array — no xarray Dataset required.

apply_tvg

Apply time-varying gain (TVG) correction to backscatter_intensity.

resolve_tvg_params

Fill a TVGParams' unset fields from ds (new object; input untouched).

tvg_gain_for_dataset

Per-sample TVG gain (dB, float64, shape (n_samples,)) for ds.

tvg_params_record

History-record params for a resolved TVGParams.

tvg_params_from_record

TVGParams from a "tvg" history record (current or legacy key names).

sidescanpy.processing.radiometric.DEFAULT_TEMPERATURE_C = 10.0#

Convert a string or number to a floating-point number, if possible.

sidescanpy.processing.radiometric.DEFAULT_SALINITY_PSU = 35.0#

Convert a string or number to a floating-point number, if possible.

sidescanpy.processing.radiometric.DEFAULT_DEPTH_M = 10.0#

Convert a string or number to a floating-point number, if possible.

sidescanpy.processing.radiometric.DEFAULT_PH = 8.0#

Convert a string or number to a floating-point number, if possible.

sidescanpy.processing.radiometric.DEFAULT_SPREADING_LOSS_DB = 30.0#

Default TVG spreading coefficient (dB per decade of range): 30 log R.

Used by TVGParams and the TVG curve fits. 30 log R models the insonified seafloor area growing with range; 40 log R (two-way spherical spreading) and 20 log R (one-way) remain available by passing spreading_loss_db.

class sidescanpy.processing.radiometric.TVGParams(spreading_loss_db=30.0, absorption_db_per_m=None, min_range_m=0.5, sound_velocity_ms=None, temperature_c=None, salinity_psu=None, depth_m=None, ph=None)#

Bases: object

Parameters for time-varying gain correction.

Attributes:
spreading_loss_dbfloat, default=30.0

Spreading loss coefficient (dB per decade of range): the gain is spreading_loss_db * log10(R). 30.0 (the default, DEFAULT_SPREADING_LOSS_DB) models the insonified seafloor area growing with range (e.g. J. Mar. Sci. Eng. 2017, 5(4), 54, https://www.mdpi.com/2077-1312/5/4/54). 40.0 is two-way spherical spreading (the default in 0.7.0-0.7.5); 20.0 is one-way spherical spreading (the default before 0.7.0). Which fits real data varies, so measure with sidescanpy.analysis.fit_range_decay() per survey. 0.0 disables the term (hardware already compensates spreading).

absorption_db_per_mfloat, optional

Absorption coefficient (dB per meter). If None, derived from the channel frequency with francois_garrison_absorption_db_per_m() (Francois & Garrison, J. Acoust. Soc. Am. 72(3) 896-907 and 72(6) 1879-1890, 1982) and the four water parameters below (typical-coastal defaults where None).

min_range_mfloat, default=0.5

Minimum range threshold (meters). Ranges below this are clipped to avoid log(0) singularity. Typical value 0.5 m accounts for transducer blanking.

sound_velocity_msfloat, optional

Sound velocity (m/s) used to turn sample index into slant range. If None, the mean of the dataset’s per-ping sound_vel_ms.

temperature_c, salinity_psu, depth_m, phfloat, optional

Water conditions for the Francois-Garrison absorption; used only when absorption_db_per_m is None (defaults 10 C, 35 psu, 10 m, pH 8 – pass real values when known).

sidescanpy.processing.radiometric.francois_garrison_absorption_db_per_m(frequency_hz, *, temperature_c=10.0, salinity_psu=35.0, depth_m=10.0, ph=8.0, sound_velocity_ms=None)#

Seawater sound absorption (dB/m, one-way) after Francois & Garrison (1982).

Sum of three relaxation / viscosity terms (f in kHz, result in dB/km before conversion):

alpha = A1 P1 f1 f^2 / (f1^2 + f^2)      boric acid
      + A2 P2 f2 f^2 / (f2^2 + f^2)      magnesium sulfate
      + A3 P3 f^2                        pure water (viscosity)

with the paper’s coefficients: f1 = 2.8 sqrt(S/35) 10^(4 - 1245/(T+273)), f2 = 8.17 10^(8 - 1990/(T+273)) / (1 + 0.0018 (S - 35)), A3 having separate polynomial branches for T <= 20 C and T > 20 C, and depth factors P2, P3. Valid roughly 0.4 - 1000 kHz, -1.8 to 30 C, S 30-35 psu, depth 0 - 6 km.

Parameters:
frequency_hzfloat or array_like

Frequency (Hz). Vectorised.

temperature_c, salinity_psu, depth_m, phfloat

Water conditions (degrees C, practical salinity units, metres, pH).

sound_velocity_msfloat, optional

Sound speed (m/s) in the relaxation-amplitude terms. Default: the paper’s own c = 1412 + 3.21 T + 1.19 S + 0.0167 D.

Returns:
float or np.ndarray

Absorption coefficient in dB per metre (the paper’s dB/km / 1000), a float for scalar input.

References

[1]

R. E. Francois and G. R. Garrison, “Sound absorption based on ocean measurements: Part I: Pure water and magnesium sulfate contributions,” J. Acoust. Soc. Am. 72(3), 896-907 (1982) (pure water and magnesium sulfate terms).

[2]

R. E. Francois and G. R. Garrison, “Sound absorption based on ocean measurements: Part II: Boric acid contribution and equation for total absorption,” J. Acoust. Soc. Am. 72(6), 1879-1890 (1982) (boric acid term and the equation for total absorption).

The defaults are “typical coastal” values (10 C, 35 psu, 10 m, pH 8), not a measurement: callers with real conditions should pass their own.

sidescanpy.processing.radiometric.center_frequency_hz(ds)#

Acoustic center frequency of a channel (Hz), for frequency-dependent physics.

The midpoint of freq_hz_start and freq_hz_end for a chirp (a 1495-1605 kHz sweep is centered at 1550 kHz); freq_hz_start when the end is missing, non-positive or equal to the start. The channel label (hz_<freq_hz_start>) is the start frequency and is not affected.

sidescanpy.processing.radiometric.absorption_for_frequency(frequency_hz, **env)#

Default absorption coefficient (dB/m, one-way) for a sonar frequency.

Thin wrapper over francois_garrison_absorption_db_per_m(); env (temperature_c, salinity_psu, depth_m, ph, sound_velocity_ms) is passed through, defaulting to its typical-coastal-water values.

sidescanpy.processing.radiometric.tvg_gain_db(range_m, params)#

Per-range TVG gain (dB): spreading loss + absorption loss.

spreading_loss (dB) = spreading_loss_db * log10(range_m) absorption_loss (dB) = 2 * absorption_db_per_m * range_m (factor of 2 for the two-way path)

Shared by apply_tvg() (xarray survey pipeline) and apply_tvg_to_amplitude() (plain-array path, e.g. target crop windows) so both apply the identical formula rather than maintaining it twice. Caller is responsible for clipping range_m to params.min_range_m first (as both callers here do) — this function doesn’t clip on its own since it has no opinion on what a caller’s minimum range should be.

sidescanpy.processing.radiometric.apply_tvg_to_amplitude(amplitude, meters_per_sample, *, params=None, freq_hz=None, sample_idx=None)#

Apply TVG correction to a plain linear-amplitude array — no xarray Dataset required.

For use on data that isn’t a full survey channel (e.g. a target’s raw ping-sample window decoded straight from its catalog SQLite via sidescanpy.io.jsf.targets.decode_target_window), where constructing a full SideScanSurvey/Dataset just to TVG-correct a small crop isn’t warranted. Same formula and defaults as apply_tvg() — see that function for the physical justification.

Parameters:
amplitudenp.ndarray

Linear amplitude, sample axis last (e.g. (n_pings, n_samples) or (n_samples,)).

meters_per_samplefloat

Range covered by one sample (two-way), e.g. a target catalog’s own PingMetersPerSample — the per-sample analog of apply_tvg()’s sound_velocity / (2 * sample_rate).

paramsTVGParams, optional

If None, built from defaults with absorption_db_per_m derived from freq_hz (required in that case, matching apply_tvg()’s own default-resolution pattern).

freq_hzfloat, optional

Used only when params is None (or has no absorption_db_per_m set), to derive a default via absorption_for_frequency().

sample_idxnp.ndarray, optional

Absolute sample index of each position on the last axis. Defaults to arange(n_samples) (the array starts at the ping’s first recorded sample); pass it when leading samples were cropped off.

Returns:
np.ndarray

TVG-corrected values in dB, same shape as amplitude.

sidescanpy.processing.radiometric.apply_tvg(ds, params=None, *, spreading_loss_db=None, absorption_db_per_m=None, min_range_m=None, sound_velocity_ms=None, temperature_c=None, salinity_psu=None, depth_m=None, ph=None)#

Apply time-varying gain (TVG) correction to backscatter_intensity.

Compensates for geometric spreading and acoustic absorption losses. Converts linear amplitude to dB (20*log10(clip(x, 1e-6)), see sidescanpy.core.units), adds the TVG gain for each sample’s slant range, and returns corrected dB values in place of the input.

Input backscatter_intensity is assumed to be linear amplitude; the library does not check this (what units the input data is in is the caller’s knowledge). Exact-zero amplitude maps to the -120 dB floor and gets no gain, so it stays exactly at FLOOR_DB after TVG and mask_floor_clip() still recognizes it (uncorrected_backscatter() maps it back to 0).

The step is recorded in processing_history as "tvg" with the parameters actually used (defaults resolved), which is what sidescanpy.processing.inversion.invert() reads to undo it.

Parameters:
dsxr.Dataset or SideScanSurvey

Frequency channel dataset; needs backscatter_intensity, sound_vel_ms and the freq_hz_start/sample_rate_hz attrs. A SideScanSurvey is corrected frequency by frequency with the same arguments; a new survey is returned and the input is not modified.

paramsTVGParams, optional

If None, defaults with absorption_db_per_m derived from freq_hz_start and sound_velocity_ms from the mean per-ping sound_vel_ms. The caller’s object is never modified.

spreading_loss_db, absorption_db_per_m, min_range_m, sound_velocity_msfloat, optional

Override the matching TVGParams field (of params, or of the defaults), so apply_tvg(ds, spreading_loss_db=40) needs no TVGParams. See TVGParams for their meaning.

temperature_c, salinity_psu, depth_m, phfloat, optional

Francois-Garrison water conditions, used only when absorption_db_per_m is not set; override the matching fields like the arguments above.

Returns:
xr.Dataset or SideScanSurvey

Same type as ds: a new dataset with TVG-corrected dB backscatter_intensity and the "tvg" history record appended.

Raises:
ValueError

If TVG has already been applied (prevents double application).

sidescanpy.processing.radiometric.resolve_tvg_params(ds, params)#

Fill a TVGParams’ unset fields from ds (new object; input untouched).

absorption_db_per_m=None -> Francois-Garrison absorption at the channel’s center frequency (center_frequency_hz()); sound_velocity_ms=None -> mean of the per-ping sound_vel_ms (NaN or non-positive mean falls back to 1500 m/s).

sidescanpy.processing.radiometric.tvg_gain_for_dataset(ds, params)#

Per-sample TVG gain (dB, float64, shape (n_samples,)) for ds.

Slant range of each sample is sample_index * c / (2 * fs) (absolute sample index, so cropped datasets get the same gain per sample), clipped to params.min_range_m. params must be fully resolved (see resolve_tvg_params()).

sidescanpy.processing.radiometric.tvg_params_record(params)#

History-record params for a resolved TVGParams.

sidescanpy.processing.radiometric.tvg_params_from_record(record_params)#

TVGParams from a "tvg" history record (current or legacy key names).