sidescanpy.processing.geometric#

Geometric range computation for SideScan sonar data.

Computes per-sample geometric variables as data variables attached to the dataset. Does NOT resample backscatter — original sample positions and values are preserved. Downstream tools (xarray.interp, rioxarray, etc.) handle resampling onto regular grids on demand.

Outputs (all shape (ping, channel, sample)):

slant_range_m       attitude-invariant time-of-flight range
ground_range_m      sqrt(slant^2 - altitude^2), unsigned: horizontal
                    distance from the nadir point (flat bottom; the
                    same with or without pitch)
across_track_m      signed Y component in the platform frame
                    (negative = port, positive = starboard)
along_track_m       signed X component in the platform frame, the
                    pitch-induced offset (positive = forward of nadir)
vertical_offset_m   Z component from platform = altitude_m for flat
                    bottom (broadcast). Future: extends to bathymetry.
depth_per_sample_m  absolute water depth at sample location
                    = depth_m + altitude_m
Sign conventions:
  • across_track_m: port channel negative, starboard positive

  • along_track_m: positive forward of nadir (nose up puts samples forward)

Pitch model (flat bottom, fan beam perpendicular to the body x-axis):

With the vehicle pitched by theta (positive nose up) at vertical altitude h, the fan plane tilts forward by theta. A sample at slant range R hits the seafloor at

along_track_m = h * tan(theta) (every sample of the ping) across_track_m = ±sqrt(R^2 - (h / cos(theta))^2)

and is on the seafloor only when R > h / cos(theta); nearer samples are water column and NaN in across_track_m and along_track_m. The horizontal distance from nadir, sqrt(along^2 + across^2), equals sqrt(R^2 - h^2) = ground_range_m. Pitch therefore shifts the whole ping forward or aft by a constant and shortens the across-track distance slightly; it does not rotate the swath.

NaN pitch_deg is treated as 0 (no pitch correction) with a warning, as NaN sound velocity falls back to 1500 m/s.

Roll’s effect on altitude is intentionally not modeled here. Future work: when DVL altitude is available it’s pre-corrected; when only fathometer_reading_m is available, attitude correction is applied to derive altitude_m.

processing_history is appended with a "geometric" record.

2026 rshomberg@uri.edu

Contents

PITCH_MODEL

Recorded in the geometric history record.

compute_geometric_ranges

Attach per-sample geometric variables to the dataset.

sidescanpy.processing.geometric.PITCH_MODEL = 'fan_beam_flat_bottom'#

Recorded in the geometric history record. Records without this key (sidescanpy 0.9.0 and earlier) were made by a model that rotated the ground vector by pitch (along = ground·sin θ), wrong for pitched pings.

sidescanpy.processing.geometric.compute_geometric_ranges(ds, *, expose_unmasked_z=False)#

Attach per-sample geometric variables to the dataset.

Parameters:
dsxr.Dataset or SideScanSurvey

Validated sidescan dataset. A SideScanSurvey is corrected frequency by frequency with the same arguments; a new survey is returned and the input is not modified.

expose_unmasked_zbool, default False

If True, vertical_offset_m and depth_per_sample_m are written as per-ping values broadcast to (ping, channel, sample) without masking the water column. The Z-axis variables become well-defined per-ping constants for every sample. The default (False) masks them with the same NaN mask as ground_range_m for consistency.

Returns:
xr.Dataset or SideScanSurvey

Same type as ds: a new dataset (input not modified) with six added per-sample data variables and a "geometric" processing_history record.

Notes

NaN handling:
  • NaN altitude_m for a ping → all per-sample geometry NaN for that ping (slant_range_m is unaffected; it’s altitude-independent).

  • NaN sound_vel_ms for a ping → falls back to 1500 m/s with warning.

  • NaN pitch_deg for a ping → taken as 0 with warning.

  • Water-column samples (slant <= altitude) → NaN in ground_range_m and (by default) vertical_offset_m and depth_per_sample_m. across_track_m and along_track_m use the pitched-fan limit slant <= altitude / cos(pitch), so on pitched pings a few samples just past the altitude have a ground range but no across/along position.