sidescanpy.processing.interpolate#

Interpolate auxiliary records onto per-ping timestamps.

The converter populates per-ping fields (latitude, heading_deg, etc.) on each SideScanFrequencyChannel from the embedded values in SonarDataMessage. For many quantities those embedded values are stale — they’re the most-recent auxiliary reading at the moment the ping was recorded, not interpolated to the ping’s exact timestamp.

This module provides higher-precision per-ping values by interpolating each frequency channel’s ping times against the survey’s auxiliary records.

Aux records are themselves often sample-and-hold (each entry repeats the latest sensor value until the sensor updates). By default (held_values="fixes") each record is first reduced to its real fixes with held_value_fixes(), so the interpolated track is smooth instead of stair-stepped. When a field has no usable aux source, the channel’s own per-ping values (also typically held) are reduced the same way and interpolated (per_ping_fallback).

Two layers:

  • interpolate_aux_onto_pings(survey, ...) — top-level entrypoint. Iterates the default field-to-aux mapping (or a user override) and applies interpolation per channel.

  • interpolate_onto_channel(channel, source, field_map, ...) — lower level. Takes any xr.Dataset with a time coordinate as the source, enabling external data (e.g. a separately recorded GPS log) to drive per-ping field updates.

2026 rshomberg@uri.edu

Contents

DEFAULT_FIELD_SOURCES

CIRCULAR_FIELDS

frozenset() -> empty frozenset object frozenset(iterable) -> frozenset object

interpolate_aux_onto_pings

Update per-ping fields on every frequency channel by interpolating the survey's auxiliary records onto each ping's timestamp.

interpolate_aux_onto_dataset

One frequency channel dataset's per-ping fields, interpolated from aux's auxiliary records (the "interpolate_aux" step).

interpolate_onto_channel

Interpolate variables from an arbitrary source dataset onto one frequency channel's ping times.

sidescanpy.processing.interpolate.DEFAULT_FIELD_SOURCES#
sidescanpy.processing.interpolate.CIRCULAR_FIELDS = frozenset({'course_deg', 'heading_deg'})#

frozenset() -> empty frozenset object frozenset(iterable) -> frozenset object

Build an immutable unordered collection of unique elements.

sidescanpy.processing.interpolate.interpolate_aux_onto_pings(survey, *, fields=None, method='linear', extrapolation='clamp', drop_invalid=True, force=False, in_place=False, held_values='fixes', max_hold_s=2.0, per_ping_fallback=True, groups=None, merge_window_s=None)#

Update per-ping fields on every frequency channel by interpolating the survey’s auxiliary records onto each ping’s timestamp.

Parameters:
surveySideScanSurvey

Survey to operate on.

fieldsdict, optional

Mapping {per_ping_field: (aux_name, aux_var)}. Overrides the default in DEFAULT_FIELD_SOURCES. Use this to interpolate only a subset of fields, or to point a field at a non-default source.

methodstr, default “linear”

Interpolation method passed to xarray.Dataset.interp — "linear", "nearest", "cubic", etc.

extrapolation{“clamp”, “linear”, “nan”}, default “clamp”

Behavior when a ping’s timestamp falls outside the aux record’s time range. "clamp" holds the boundary value; "linear" extrapolates the slope; "nan" writes NaN.

drop_invalidbool, default True

If True, drop rows where the aux source variable is NaN before interpolating. Avoids NaN propagation through the interpolated result. Set False to require the source be clean. With held_values="fixes" NaN entries are never fixes and are always dropped.

forcebool, default False

If True, attempt interpolation even when the aux record is empty or absent and no per-ping fallback applies: the per-ping field receives NaN. If False (default), the field is left unchanged and a warning is logged.

in_placebool, default False

If True, mutate survey in place and return it. If False, return a new SideScanSurvey leaving the input untouched.

held_values{“fixes”, “raw”}, default “fixes”

"fixes" reduces each aux record to its real fixes first (held_value_fixes()): many recorders log the latest GPS or attitude value at every ping or packet, and interpolating between those repeated entries keeps the stair-steps (consecutive pings stack on one position, then jump). "raw" interpolates the entries as logged (the only behavior up to 0.7.2).

max_hold_sfloat, default 2.0

With held_values="fixes": a value unchanged for longer than this is kept flat across the hold (a stop, or a sensor dropout) rather than ramped through it. See held_value_fixes().

per_ping_fallbackbool, default True

With held_values="fixes": when a field’s aux record is absent or empty, or its variable is missing or all NaN, reduce the channel’s own per-ping values of that field (against the ping times) to fixes and interpolate those instead. Per-ping values that never repeat are returned unchanged. The fields filled this way are recorded in the history entry. No effect with held_values="raw".

groupssequence of sequence of str, optional

With held_values="fixes": variables that update together, passed to held_value_fixes() for each record (by aux variable name; for the per-ping fallback, by per-ping field name). A group applies to a record only when all its variables are in it, so groups=[POSITION_GROUP] joins latitude and longitude in the navigation record and leaves attitude alone. Default: every variable is its own group.

merge_window_sfloat or mapping of str to float, optional

With held_values="fixes": merge changes at most this many seconds apart into one update event, for recorders that log one sensor update over consecutive entries. A float applies to every group of every record; a mapping by aux variable name (for the per-ping fallback, by per-ping field name), such as {"latitude": 0.15, "longitude": 0.15}, only to those variables’ groups. See held_value_fixes(). Default None: no merging.

Returns:
SideScanSurvey

Survey with updated per-ping fields. Each touched frequency channel gets a new processing_history entry.

Raises:
ValueError

If fields references an unknown aux record name. If extrapolation or held_values is not one of the supported values, or max_hold_s or merge_window_s is out of range.

sidescanpy.processing.interpolate.interpolate_aux_onto_dataset(ds, aux, *, fields=None, method='linear', extrapolation='clamp', drop_invalid=True, force=False, held_values='fixes', max_hold_s=2.0, per_ping_fallback=True, groups=None, merge_window_s=None)#

One frequency channel dataset’s per-ping fields, interpolated from aux’s auxiliary records (the "interpolate_aux" step).

Same parameters as interpolate_aux_onto_pings(), but for a single dataset (or a ping slice of one) with the survey supplying the aux records. Always appends an "interpolate_aux" history record (with the fields actually updated, possibly none, held_values, max_hold_s, per_ping_fallback, groups, merge_window_s and the fields filled from per-ping values as per_ping_fallback_fields) so the step is not re-run.

For the per-ping fallback on a ping slice, the per-ping values come from the matching whole channel in aux (same freq_hz_start, same times at the slice’s ping coordinates) when there is one, so a chunked run gives the same result as an unchunked one.

sidescanpy.processing.interpolate.interpolate_onto_channel(channel, source, *, field_map, method='linear', extrapolation='clamp', drop_invalid=True, record_history=True)#

Interpolate variables from an arbitrary source dataset onto one frequency channel’s ping times.

The source entries are used as given. For a sample-and-hold source (the latest value repeated until the sensor updates), pass held_value_fixes(source) to interpolate between real fixes.

Parameters:
channelSideScanFrequencyChannel

Target channel.

sourcexr.Dataset

Source dataset. Must have a time coordinate (datetime64-typed) and a single non-time dimension over which the variables are defined. Variables named in field_map values must exist.

field_mapdict

{per_ping_field: source_var_name}.

methodstr, default “linear”

Interpolation method passed to xarray.Dataset.interp.

extrapolation{“clamp”, “linear”, “nan”}, default “clamp”
drop_invalidbool, default True

Drop NaN rows from each source variable before interpolating.

record_historybool, default True

If True, append a processing_history entry to the resulting dataset. Set False when called as part of a higher-level pipeline that records history once.

Returns:
SideScanFrequencyChannel

New channel with updated per-ping fields. Original is not modified.