sidescanpy.processing.track#

Survey track: per-ping nadir positions plus strided port/starboard swath-edge positions, for a multi-file survey.

build_survey_track() returns a SurveyTrack of WGS84 lat/lon arrays; sidescanpy.viz.track.plot_track_and_scanlines and sidescanpy.viz.targets.plot_track_with_targets draw it.

2026 rshomberg@uri.edu

Contents

SurveyTrack

Nadir track and scan-line endpoints for one frequency (WGS84 degrees).

build_survey_track

Nadir track + strided port/starboard scan-line endpoints for one frequency, across every file that has it, in time order.

class sidescanpy.processing.track.SurveyTrack(track_lat, track_lon, scan_port_lat, scan_port_lon, scan_stbd_lat, scan_stbd_lon, scan_nadir_lat=None, scan_nadir_lon=None)#

Bases: object

Nadir track and scan-line endpoints for one frequency (WGS84 degrees).

track_* arrays have one entry per ping across every file, in time order. scan_* arrays are index-aligned port/starboard swath-edge positions at every scanline_stride-th ping of each file. scan_nadir_* is the nadir position at those same pings, where each rung splits into its port and starboard halves (None when not known).

sidescanpy.processing.track.build_survey_track(surveys, freq_key, *, scanline_stride, heading='compass')#

Nadir track + strided port/starboard scan-line endpoints for one frequency, across every file that has it, in time order.

Cheap by design, but not by slicing the sample axis down to one sample before georeferencing — that’s tempting (we only want the farthest-range sample per ping) but wrong: compute_geometric_ranges computes each sample’s position from the absolute sample coordinate when present (processing.sample_axis. sample_index), so slicing samples is safe for datasets that carry it. For legacy datasets without that coordinate it falls back to a local index (arange(ds.sizes["sample"])): pre-slicing to sample=[-1] then makes the function see “sample 0” (point-blank range) instead of the true far-range edge — silently producing NaN via the water-column mask instead of a real position.

The safe optimization instead pre-selects the strided pings (slicing the ping axis doesn’t touch ds.sizes["sample"], so it doesn’t affect the sample-index math) while keeping each selected ping’s full sample axis intact through georeferencing, and only slices to the last sample afterward. This still avoids georeferencing every ping — just the ones actually needed for a rung — while staying correct.

Parameters:
surveysdict[Path, SideScanSurvey]
freq_keystr

e.g. "hz_1495000".

scanline_strideint

Draw a scan-line rung every Nth ping.

heading{“compass”, “course”}, default “compass”

Heading used to place the swath edges: the recorded heading_deg, or the GPS course over ground (sidescanpy.processing.navigation.course_over_ground) for a towfish whose compass is not trusted.

Returns:
SurveyTrack or None

None if no file has freq_key. Track arrays cover every ping; scan arrays cover every scanline_stride-th ping.