sidescanpy.viz.tvg#

Plots for TVG and range-decay analysis (sidescanpy.analysis.tvg, sidescanpy.analysis.tvg_fit).

Each function draws into an optional ax, returns (fig, ax) and never saves a file. plot_tvg_fit() draws a fit_tvg_models() result and, below it, a check_tvg() result. The range-decay plots (plot_candidate_fit(), plot_range_decay_fit(), plot_range_decay_residuals(), set_fit_window_limits()) are deprecated with fit_range_decay() and will be removed in 0.12.

2026 rshomberg@uri.edu

Contents

plot_mean_vs_range

Line plot of mean backscatter vs. range, with an optional shaded percentile band.

plot_tvg_gain_curve

Plot tvg_gain_db vs. range from compare_before_after's output.

plot_per_ping_profiles

"Spaghetti plot": overlay n_pings individual ping profiles (value_var vs. range_var, one channel) at low alpha.

plot_multi_frequency_comparison

Overlay backscatter_vs_range curves for multiple frequencies, color-coded by frequency.

plot_altitude_bin_comparison

Overlay backscatter_vs_range curves for multiple altitude bins (from bin_by_altitude), color-coded by bin.

plot_candidate_fit

Overlay one analysis.tvg.fit_tvg_curve() candidate's fitted model against the observed raw-loss curve it was fit against.

plot_range_decay_fit

Observed loss-vs-range curve with every candidate model overlaid (one plot_candidate_fit() call per candidate on a shared axis).

set_fit_window_limits

Set robust axis limits on a decay-fit axis from the fit window.

plot_range_decay_residuals

Residual (model - observed) vs. range within each candidate's fit window.

plot_tvg_fit

One column: the TVG model fit and, below it, the check after TVG.

sidescanpy.viz.tvg.plot_mean_vs_range(binned, ax=None, *, show_band=True, band=('p10', 'p90'), label=None, color=None, linewidth=1.5, zorder=3.0, title=None)#

Line plot of mean backscatter vs. range, with an optional shaded percentile band.

Call twice on the same ax (raw vs. corrected) for a direct before/after overlay.

Parameters:
binnedpd.DataFrame

Output of backscatter_vs_range (or backscatter_vs_sample — any DataFrame with a “mean” column and matching min/max range columns).

show_bandbool, default True

Shade the region between band’s two percentile columns.

band(str, str), default (“p10”, “p90”)
linewidthfloat, default 1.5

Matplotlib’s own default is 1.5, so this default is a no-op for existing callers – pass a larger value (e.g. when overlaying on top of plot_per_ping_profiles’ dense low-alpha spaghetti, which can otherwise visually bury a thin mean line even with a distinct color) to make the mean line read clearly on top.

zorderfloat, default 3.0

Matplotlib draws in call order by default (zorder 1, 2, 3, … as each ax.plot/fill_between call happens) – 3.0 is chosen to sit above a typical single prior plot_per_ping_profiles call (which doesn’t set its own zorder, so defaults to 1) even if draw order alone wouldn’t guarantee it. Raise further if overlaying on top of more layers.

sidescanpy.viz.tvg.plot_tvg_gain_curve(ds, ax=None, *, range_var='ground_range_m', title='TVG gain vs. range')#

Plot tvg_gain_db vs. range from compare_before_after’s output.

Shows how much gain TVG actually added at each range — useful alongside plot_mean_vs_range to compare against the observed residual’s shape.

sidescanpy.viz.tvg.plot_per_ping_profiles(ds, value_var, range_var='ground_range_m', n_pings=50, ax=None, alpha=0.08, *, channel_idx=0, seed=0, title=None, x_sign=1.0)#

“Spaghetti plot”: overlay n_pings individual ping profiles (value_var vs. range_var, one channel) at low alpha.

Visual density shows ping-to-ping consistency/variance directly — complementary to plot_mean_vs_range’s percentile band, which summarizes spread but averages away periodic per-ping artifacts.

Parameters:
channel_idxint, default 0

Which channel to plot (0 = port, 1 = starboard per this schema’s convention) — call twice with channel_idx=0 and channel_idx=1 on separate axes for a side-by-side port/ starboard comparison. Previously hardcoded to 0; the default preserves that exact behavior for existing callers.

x_signfloat, default 1.0

Multiplies every plotted range_var value before drawing — pass -1.0 (typically for channel_idx=0/port) to mirror one channel onto the negative side of a shared axis, so calling this twice (once per channel, opposite signs) on the same ax draws one continuous full-swath profile instead of two independent one-sided ones. range_var itself (e.g. slant_range_m) is always unsigned magnitude — this is a display-only sign flip, not a different range computation. 1.0 is a no-op, preserving every existing caller’s output exactly.

sidescanpy.viz.tvg.plot_multi_frequency_comparison(binned_by_freq, ax=None, *, title='Mean backscatter vs. range, by frequency')#

Overlay backscatter_vs_range curves for multiple frequencies, color-coded by frequency.

If multiple frequencies (different absorption_for_frequency() coefficients) show a similarly-shaped residual, that’s evidence against “just retune absorption” (candidate 1).

sidescanpy.viz.tvg.plot_altitude_bin_comparison(binned_by_altitude, ax=None, *, title='Mean backscatter vs. range, by altitude bin')#

Overlay backscatter_vs_range curves for multiple altitude bins (from bin_by_altitude), color-coded by bin.

If the residual’s shape changes with altitude (different near-nadir geometry), that’s evidence for a missing angular correction (candidate 2); roughly altitude-invariant leans toward candidate 1.

sidescanpy.viz.tvg.plot_candidate_fit(fit, observed_range_m, observed_loss_db, ax=None, *, color=None, show_observed=True, title=None, use_fixed_scale=True, model_n_points=200)#

Overlay one analysis.tvg.fit_tvg_curve() candidate’s fitted model against the observed raw-loss curve it was fit against.

Call once per candidate on the same ax (result.candidates from TVGCandidateFitResult) — show_observed=True on the first call only, False on the rest, so the observed curve is drawn once and each candidate’s fitted line overlays it. Mirrors plot_mean_vs_range’s existing “call twice for before/after” idiom, generalized to “call once per candidate.”

Parameters:
fitTVGCandidateFit

One candidate from TVGCandidateFitResult.candidates (or .best).

observed_range_m, observed_loss_dbnp.ndarray

TVGCandidateFitResult.observed_range_m/.observed_loss_db — the same arrays for every candidate fit from one fit_tvg_curve() call, so passing a different candidate on repeated calls only changes the overlaid line.

show_observedbool, default True

Plot the observed curve itself. Set False on repeat calls against the same ax to avoid redrawing it once per candidate.

use_fixed_scalebool, default True

Draw the literal, uncalibrated model (scale pinned to 1.0) – matches what fit_range_decay actually ranks candidates by (2026-08-24 change; see TVGCandidateFit’s docstring). False draws the old free-fit-scaled line instead, for diagnostic comparison against the fixed-scale one – e.g. to see by how much a candidate needed to cheat to look competitive.

model_n_pointsint, default 200

The model line is drawn over its own dense, evenly-spaced range array from 0 to observed_range_m’s max — not just at the bin centers observed_range_m happens to have — so the curve’s shape reads smoothly all the way back to near-zero range for reference, including where no observed data exists (e.g. inside the excluded nadir gap). Purely a display domain; the fit itself was never evaluated there and this draws no new conclusion about that span.

.. deprecated:: 0.10

Removed in 0.12. Use plot_tvg_fit() with fit_tvg_models().

sidescanpy.viz.tvg.plot_range_decay_fit(result, ax=None)#

Observed loss-vs-range curve with every candidate model overlaid (one plot_candidate_fit() call per candidate on a shared axis).

Deprecated since version 0.10: Removed in 0.12. Use plot_tvg_fit() with fit_tvg_models().

sidescanpy.viz.tvg.set_fit_window_limits(ax, result, *, x_pad_fraction=0.12, y_pad_fraction=0.25, y_pad_min_db=1.0, x_pad_min_m=1.0)#

Set robust axis limits on a decay-fit axis from the fit window.

y-limits come from the observed values inside the fit window (result.r_fit_min_m..``r_fit_max_m``) only, so an out-of-window outlier bin cannot compress the fit curves. x-limits deliberately span 0 to the far edge (padded), not the fit window, so a misplaced reference or window stays visible. With a shared x-axis, call this on one panel only. No-op if there is no fit or no recorded window.

Deprecated since version 0.10: Removed in 0.12. Use plot_tvg_fit() with fit_tvg_models().

sidescanpy.viz.tvg.plot_range_decay_residuals(result, *, use_fixed_scale=True, ax=None, relative_x=True)#

Residual (model - observed) vs. range within each candidate’s fit window.

use_fixed_scale=True (default) draws the scale-1.0 residual and labels it with fixed_scale_rmse_db, the number candidates are ranked by; False draws the free-fit-scaled residual for comparison. The reference uses the nearest bin to r_ref_m, matching fit_tvg_curve’s own RMSE computation exactly. relative_x=True plots distance from each candidate’s r_ref_m; pass False for absolute range (to stack under plot_candidate_fit() on a shared x-axis).

Deprecated since version 0.10: Removed in 0.12. Use plot_tvg_fit() with fit_tvg_models().

sidescanpy.viz.tvg.plot_tvg_fit(fit, check=None, ax=None, ax_check=None, *, title=None)#

One column: the TVG model fit and, below it, the check after TVG.

Fit panel: the median level against range (black), the fit window (shaded), the 20, 30 and 40 log R models with absorption (thin), the best model (thick; dashed when it has no absorption), each labelled with its RMS in the window. Check panel (when check is given): the median level after the TVG, relative to its mean in the window, with a zero line, limits of ±5 dB, and the spread and trend written in the corner. Flat means the model holds.

Parameters:
fitTVGFitResult

From fit_tvg_models().

checkTVGCheckResult, optional

From check_tvg(), normally on fit.tvg_params and fit.window_m.

ax, ax_checkAxes, optional

Where to draw. Without ax a new figure is made, with a second row for the check when check is given. With ax and a check, pass ax_check too.

titlestr, optional

Title of the fit panel.

Returns:
(Figure, (Axes, Axes or None))

The figure, the fit axes and the check axes (None without a check).