sidescanpy.processing.navigation#

Navigation-derived ping geometry: along-track distance and nav-stall repair.

  • along_track_distance_m() – cumulative along-track distance (m) from per-ping latitude/longitude, optionally with short navigation stalls filled.

  • fill_nav_stalls() – replace short runs of stalled position steps with the local median ping step.

  • course_over_ground() – per-ping course (degrees true) from the positions a few seconds before and after each ping, for use as the heading when a towfish compass is unreliable.

  • smooth_navigation() – running mean of the per-ping positions over a time window, for recorders whose positions carry a periodic error between fixes.

Pure array/dataset functions; no plotting.

2026 rshomberg@uri.edu

Contents

along_track_distance_m

Cumulative along-track distance (meters) from per-ping lat/lon.

fill_nav_stalls

Replace short navigation stalls with the local median ping step.

course_over_ground

Per-ping course over ground, degrees true (compass bearing, [0, 360)).

STEP_SMOOTH_NAVIGATION

History step name written by smooth_navigation().

smooth_navigation

Replace per-ping positions with their running mean over window_s.

sidescanpy.processing.navigation.along_track_distance_m(ds, *, fill_nav_stalls=False)#

Cumulative along-track distance (meters) from per-ping lat/lon.

Public wrapper around the same computation used internally for y_axis="along_track" — exposed for callers (e.g. distance-based segmentation) that need the coordinate array without going through plot_waterfall.

fill_nav_stalls: navigation can stall – several consecutive pings keep (nearly) the same position while the vessel keeps moving, and it resumes without catching up. The raw distance is then flat across those pings, and geometric resampling stacks their seafloor into a single along-track line (seen on one real survey: ~7 pings at each checked target, 42ms apart with distinct data, advanced only ~0.07m in total instead of ~0.5m – a hard seam through the target at fine resolution). With True, each stalled step is replaced by the local median step (see fill_nav_stalls). Off by default so gap/stationarity detection still sees the raw navigation.

sidescanpy.processing.navigation.fill_nav_stalls(along, *, stall_fraction=0.5, max_stall_pings=20, window_pings=25)#

Replace short navigation stalls with the local median ping step.

A step is stalled when it is below stall_fraction of the median step within window_pings either side. Only runs of at most max_stall_pings stalled steps with moving steps on both sides are filled – a longer run, or one at the window edge, is treated as the vessel really stopping and left as is.

sidescanpy.processing.navigation.course_over_ground(ds, *, half_window_s=4.0)#

Per-ping course over ground, degrees true (compass bearing, [0, 360)).

The bearing from the ping’s position half_window_s earlier to its position half_window_s later (WGS84 geodesic), taken from the per-ping latitude/longitude and time. Near the ends of the dataset the window is cut short. Pings without a finite position, or whose window has no movement, get the course interpolated from their neighbours (on the unwrapped angle, so no jump at north).

Use it in place of heading_deg when the towfish compass is not trusted, e.g. a heading-dependent compass deviation that shears the georeferenced swath. It assumes the towfish follows the vessel track (no crab angle), and it lags in tight turns by about half_window_s.

Parameters:
dsxr.Dataset

Frequency-channel dataset with per-ping time, latitude and longitude, pings in time order.

half_window_sfloat, default 4.0

Half the time window over which the course is measured. Longer smooths GPS noise; shorter follows turns more closely.

Returns:
np.ndarray

Course in degrees, one per ping; all NaN when fewer than two pings have distinct finite positions.

sidescanpy.processing.navigation.STEP_SMOOTH_NAVIGATION = 'smooth_navigation'#

History step name written by smooth_navigation().

sidescanpy.processing.navigation.smooth_navigation(ds, *, window_s=5.0)#

Replace per-ping positions with their running mean over window_s.

Each ping’s latitude and longitude come from a straight-line fit (least squares, against time) to the finite positions within window_s / 2 of its time; with evenly spaced pings that is their mean. The fit rather than the mean keeps pings next to missing positions unbiased. Near the ends of the dataset the window shrinks symmetrically, so the first and last pings keep their positions. A straight track at constant speed is unchanged (no lag).

For positions that change at every entry but carry an error between GPS fixes, which interpolating between fixes cannot remove. Some recorders run the position ahead of each fix and jump back when the next one arrives: a sawtooth along track with the fix period. A window spanning a whole number of fix periods removes it (and leaves a constant along-track offset of about half its amplitude). Check the track first (aux_update_events(), and the residual from a straight line); on sample-and-hold navigation, interpolate between fixes instead (interpolate_aux_onto_pings()).

The mean is taken in degrees, which is accurate for windows of seconds to minutes; turns tighter than the distance covered in window_s are cut short. Heading is not changed: derive it from the smoothed track with course_over_ground() if the compass is not used.

Parameters:
dsxr.Dataset or SideScanSurvey

Frequency channel dataset with per-ping time, latitude and longitude, pings in time order. A SideScanSurvey is smoothed frequency by frequency; a new survey is returned.

window_sfloat, default 5.0

Width of the time window (s), centred on each ping.

Returns:
xr.Dataset or SideScanSurvey

Same type as ds: a new dataset with float64 latitude and longitude and a smooth_navigation history record. Pings without a finite position keep NaN.

Raises:
ValueError

If window_s is not positive, or georef was already applied (its per-sample positions would go stale; invert it first).