sidescanpy.analysis.tvg#

TVG analysis: backscatter-vs-range profiles and hardware-TVG detection.

The TVG fit is sidescanpy.analysis.fit_tvg_models() (in sidescanpy.analysis.tvg_fit). The candidate classifier below, fit_range_decay() and fit_tvg_curve() with TVGCandidateFit and TVGCandidateFitResult, is deprecated since 0.10 and will be removed in 0.12: it anchored every model at one reference bin, compared one spreading coefficient at a time and took its window from the nadir and usable-range analyses, which often left it fitting the wrong stretch of range.

Profiles and comparisons:

Detecting what TVG the data already carries:

Contents

compare_before_after

Raw and TVG-corrected backscatter side by side, with ground range.

bin_by_altitude

Split pings into n_bins equal-count (quantile) altitude bins.

TVGCandidateFit

One candidate loss model fit against an observed loss-vs-range curve.

TVGCandidateFitResult

All four candidate fits for one curve, plus the best by fixed-scale RMSE.

fit_tvg_curve

Fit four candidate hardware-TVG models to a binned raw dB-vs-range curve.

RangeDecayResult

TVG-status classification from a range-decay fit.

fit_range_decay

Classify which TVG terms value_var already carries.

sidescanpy.analysis.tvg.compare_before_after(ds_raw, params=None)#

Raw and TVG-corrected backscatter side by side, with ground range.

Parameters:
ds_rawxr.Dataset

Uncorrected channel dataset (see sidescanpy.processing.invert()).

paramsTVGParams, optional

Passed to apply_tvg().

Returns:
xr.Dataset

ds_raw with geometric ranges plus backscatter_db_raw (linear → dB, no gain), backscatter_db_corrected (apply_tvg output) and tvg_gain_db (their difference).

sidescanpy.analysis.tvg.bin_by_altitude(ds, n_bins=3, altitude_var='altitude_m')#

Split pings into n_bins equal-count (quantile) altitude bins.

Comparing a range profile across altitude bins separates geometry-dependent effects (e.g. grazing angle) from range-only ones.

Returns:
dict[str, xr.Dataset]

Keys "altitude_{lo:.2f}_{hi:.2f}m" in increasing altitude. Pings with non-finite altitude are in no bin.

class sidescanpy.analysis.tvg.TVGCandidateFit(name, params, scale, rmse_db, fixed_scale_rmse_db, r_ref_m, r_fit_max_m, n_bins_used, r_fit_min_m=None)#

Bases: object

One candidate loss model fit against an observed loss-vs-range curve.

scale is the least-squares multiplier on the candidate’s model (near 1: the data decays like that model as written; near 0: that term is essentially absent) and rmse_db its residual. fixed_scale_rmse_db is the residual with the scale pinned to 1 — the literal physics model. Candidates are ranked by the fixed-scale value, because a free scale on one term can absorb unrelated range-dependent effects and win on RMSE alone. For flat (model identically zero) both values are equal.

Deprecated since version 0.10: Removed in 0.12; see TVGModelFit.

class sidescanpy.analysis.tvg.TVGCandidateFitResult(candidates, best, observed_range_m, observed_loss_db)#

Bases: object

All four candidate fits for one curve, plus the best by fixed-scale RMSE.

Deprecated since version 0.10: Removed in 0.12; see TVGFitResult.

sidescanpy.analysis.tvg.fit_tvg_curve(binned, *, spreading_loss_db=30.0, absorption_db_per_m, r_ref_m=None, r_fit_min_m=None, r_fit_max_m=None, min_count_for_r_ref=30, fit_fraction=0.6, r_ref_search_fraction=0.5, value_col='mean')#

Fit four candidate hardware-TVG models to a binned raw dB-vs-range curve.

Deprecated since version 0.10: Removed in 0.12. Use fit_tvg_models(), which fits a free constant, compares 20/30/40 log R with and without absorption, and ends its window at the noise knee.

Each candidate is a term (or both) of tvg_gain_db() still present in the data:

  • flat — neither term (hardware compensated both)

  • spreading_only — only spreading_loss_db * log10(R)

  • absorption_only — only 2 * absorption_db_per_m * R

  • full_physics — both (no hardware compensation)

The fitted quantity is observed_loss(r) = value[r_ref] - value[r] (absolute level is unknown). Each candidate gets one free scale, fitted independently: a joint two-term regression is poorly conditioned because the terms are nearly collinear over realistic range spans.

Parameters:
binnedpd.DataFrame

backscatter_vs_range() output computed on uncorrected dB backscatter.

spreading_loss_dbfloat, default 30.0

Spreading coefficient of the spreading_only and full_physics candidates (DEFAULT_SPREADING_LOSS_DB).

absorption_db_per_mfloat

Required; pass the survey’s own frequency-appropriate value.

r_ref_mfloat, optional

Reference range. Default: the bin with the highest value among bins with count >= min_count_for_r_ref in the near r_ref_search_fraction of the range span (falls back to all eligible bins). Not simply the nearest bin, which often reads low from ring-down/blanking; not the global max, which on flat data can land on a far-range noise bump.

r_fit_min_mfloat, optional

Near edge of the fit window. Default r_ref_m (one-sided). Set it lower to fit both sides of the reference.

r_fit_max_mfloat, optional

Far edge of the fit window. Default r_ref_m + fit_fraction * (max_range - r_ref_m).

min_count_for_r_refint, default 30
fit_fractionfloat, default 0.6
r_ref_search_fractionfloat, default 0.5
value_col{“mean”, “median”}, default “mean”

Per-bin statistic to fit.

Returns:
TVGCandidateFitResult
Raises:
ValueError

If binned is empty, or no bin has count >= min_count_for_r_ref.

class sidescanpy.analysis.tvg.RangeDecayResult(tvg_status=None, freq_hz=None, db_reference=None, absorption_db_per_m=None, bin_width_m=None, bin_width_m_requested=None, exclude_first_n_bins=0, n_bins_excluded_after_bottom=0, r_ref_m=None, r_ref_m_override=None, r_fit_min_m=None, r_fit_min_m_override=None, r_fit_min_auto_margin_m=None, r_fit_min_auto_search_max_m=None, r_fit_min_auto_peak_edge_m=None, r_fit_max_m=None, n_bins_used=None, best_candidate=None, best_scale=None, best_rmse_db=None, best_free_fit_scale=None, best_free_fit_rmse_db=None, ambiguous_rel_tol=None, candidates=<factory>, range_var=None, channel=None, water_column_n_excluded=0, nadir_gap_n_excluded=0, reject_outliers=False, outlier_mad_k=None, outlier_n_rejected=0, fit_value_col=None, notes=<factory>, binned=None, fit=None)#

Bases:

TVG-status classification from a range-decay fit.

Deprecated since version 0.10: Removed in 0.12, with fit_range_decay(); see TVGFitResult.

tvg_status names what hardware has already removed: "none", "spreading_only", "absorption_only", "full", or "ambiguous" (winner not clearly ahead, or too little data). best_rmse_db is the winner’s fixed-scale RMSE (the ranking criterion); best_free_fit_* are its free-scale values. r_ref_m_override / r_fit_min_m_override record what was requested (e.g. "auto"); r_ref_m / r_fit_min_m what was used. With r_fit_min_m="auto" resolved, r_fit_min_auto_search_max_m is the bound of the near-field peak search (bin centers up to it) and r_fit_min_auto_peak_edge_m the peak bin’s far edge, so the resolved value is r_fit_min_auto_peak_edge_m + r_fit_min_auto_margin_m before any clamp to r_ref_m; it can lie past the search bound. fit (the full TVGCandidateFitResult) is left out of to_dict(); candidates summarizes it.

sidescanpy.analysis.tvg.fit_range_decay(ds, value_var='backscatter_intensity', range_var='slant_range_m', *, already_db=False, db_reference_percentile=99.0, db_reference_value=None, freq_hz=None, bin_width_m=5.0, usable_range_m=None, min_bins=6, ambiguous_rel_tol=0.2, min_total_count=30, exclude_before_sample=None, exclude_first_n_bins=0, reject_outliers=False, outlier_mad_k=3.5, outlier_min_bin_count=5, fit_value_col='mean', r_ref_m=None, r_fit_min_m=None, r_fit_min_auto_margin_m=5.0, r_ref_search_fraction=0.5, r_ref_window_fraction=0.3333333333333333, channel=None, spreading_loss_db=30.0)#

Classify which TVG terms value_var already carries.

Deprecated since version 0.10: Removed in 0.12. Use fit_tvg_models() (and check_tvg()); its best model is the TVG to apply and its tvg_status replaces this one. This function anchors every model at one reference bin, compares one spreading coefficient at a time and needs its window from the caller, so its answers differ.

Converts to dB, bins by range_var, fits the four fit_tvg_curve() candidates and maps the winner (lowest fixed-scale RMSE) to a TVG status. If the runner-up’s RMSE is within ambiguous_rel_tol (relative) of the winner’s, the status is "ambiguous".

Water-column samples are always excluded via ground_range_m’s NaN pattern (when present), independent of range_var.

Parameters:
range_varstr, default “slant_range_m”

Range axis; slant range matches apply_tvg’s gain model.

already_dbbool, default False

value_var is already dB; skip the conversion.

db_reference_percentilefloat, default 99.0
db_reference_valuefloat, optional

Exact linear reference instead of the percentile (e.g. 1.0 for “dB re 1”).

freq_hzfloat, optional

Default: the channel’s center frequency (center_frequency_hz()); sets the absorption term.

bin_width_mfloat, default 5.0

Requested bin width; shrunk (never widened) so that usable_range_m spans at least min_bins bins.

usable_range_mfloat, optional

Far edge of the fit window.

min_binsint, default 6
min_total_countint, default 30

Fewer binned samples than this → "ambiguous" without a fit.

exclude_before_samplenp.ndarray, optional

(ping, channel) sample index; samples before it are dropped (in dB space) before binning. For aggregate fits pass a uniform boundary (uniform_exclusion_boundary()): a per-ping one biases the near-range bins and the reference pick.

exclude_first_n_binsint, default 0

Drop this many nearest bins after binning, before the reference pick and fit — for a bright leading-edge artifact at the bottom return.

reject_outliersbool, default False

Run reject_range_outliers() on the dB values first (outlier_mad_k, outlier_min_bin_count).

fit_value_col{“mean”, “median”}, default “mean”

Passed to fit_tvg_curve() as value_col.

r_ref_mfloat, “auto”, “mid_swath” or None

Reference range. None: fit_tvg_curve()’s near-field auto-pick. "auto" (recommended with r_fit_min_m="auto"): placed r_ref_window_fraction of the way from the resolved fit window’s near edge to usable_range_m, so it always lies inside the window. "mid_swath" (legacy): usable_range_m / 2. Both string modes fall back to the auto-pick, with a note, when usable_range_m is unavailable.

r_fit_min_mfloat, “auto” or None

Near edge of the fit window (resolved before r_ref_m). None: one-sided from r_ref_m. "auto": the raw near-field peak bin’s far edge plus r_fit_min_auto_margin_m, searched up to a numeric r_ref_m or r_ref_search_fraction * usable_range_m; falls back to None with a note when it can’t be resolved. Only the peak is searched for within that bound: the margin, added afterwards, can carry the value past it, and the result reports both (r_fit_min_auto_search_max_m, r_fit_min_auto_peak_edge_m). If it ends up beyond a numeric/”mid_swath” r_ref_m it is clamped to it.

r_fit_min_auto_margin_mfloat, default 5.0
r_ref_search_fractionfloat, default 0.5
r_ref_window_fractionfloat, default 1/3
channelint or str, optional

Restrict to one physical side (index or channel_id value). None pools both. Applied by masking.

spreading_loss_dbfloat, default 30.0

Spreading coefficient of the spreading_only and full_physics candidates; pass the value the survey’s correction uses.