sidescanpy.processing.segments#

Survey segmentation: find clean, homogeneous ping ranges in one channel.

Aggregate analysis over a ping range that silently mixes a gap, a setting change, a dropout or a turn transient gives misleading numbers. These detectors find such stretches so they can be cut out first.

find_segments() combines them into Segment objects labelled "clean" or "excluded" with the reasons that fired. split_by_segments() slices a survey into one sub-survey per clean segment; write_segments() writes each to its own .ss.h5.

Contents

Segment

One [start, stop) ping range with its status and the reasons for it.

detect_gaps

Per-ping boundary flags: True at i marks a break between pings i-1 and i.

detect_setting_changes

Per-ping boundary flags for a likely acquisition-setting change.

detect_garbage_runs

[(start, stop), ...] runs of at least min_run_pings degenerate pings.

score_stability

Stability score of [start, start + span); lower is calmer.

detect_unstable_spans

[(start, stop), ...] unstable stretches from altitude and pitch.

find_segments

Split [0, n_pings) into contiguous Segment objects.

split_by_segments

One sub-survey per "clean" segment, keyed by segment label.

write_segments

Write <label>.ss.h5 into output_dir for every clean segment.

class sidescanpy.processing.segments.Segment(label, start, stop, status, reasons=<factory>)#

Bases: object

One [start, stop) ping range with its status and the reasons for it.

to_dict()#

JSON-serializable dict.

sidescanpy.processing.segments.detect_gaps(ds, *, gap_factor=8.0, time_gap_factor=8.0)#

Per-ping boundary flags: True at i marks a break between pings i-1 and i.

Fires when the along-track step exceeds gap_factor times the median nonzero step, or the ping-time step exceeds time_gap_factor times the median nonzero interval. Either signal alone can show a real discontinuity. Index 0 is never flagged.

sidescanpy.processing.segments.detect_setting_changes(ds, *, sound_vel_rel_tol=0.05)#

Per-ping boundary flags for a likely acquisition-setting change.

Fires on any change in valid_samples (when present) or a relative step in sound_vel_ms larger than sound_vel_rel_tol. A sample-count change that the converter already padded away without recording valid_samples can’t be detected here.

sidescanpy.processing.segments.detect_garbage_runs(ds, value_var='backscatter_intensity', *, min_run_pings=20, degenerate_frac=0.999)#

[(start, stop), ...] runs of at least min_run_pings degenerate pings.

A ping is degenerate when at least degenerate_frac of its values (all channels and samples) are NaN or exactly zero — a mid-survey dropout, not the per-ping leading nadir blanking.

sidescanpy.processing.segments.score_stability(altitude, pitch, start, span)#

Stability score of [start, start + span); lower is calmer.

Altitude coefficient of variation plus pitch standard deviation (degrees), weighted 1:1. A calm stretch scores a few hundredths to tenths; a turn or deployment transient scores one to two orders of magnitude higher. inf with fewer than two finite values.

sidescanpy.processing.segments.detect_unstable_spans(ds, *, window_pings=100, stability_threshold=1.0, min_run_pings=50)#

[(start, stop), ...] unstable stretches from altitude and pitch.

Non-overlapping window_pings windows scoring above stability_threshold (score_stability()) are unstable; adjacent ones merge, and a merged span shorter than min_run_pings is dropped as noise. The default threshold is a coarse starting point, not a validated value. Empty without altitude_m and pitch_deg.

sidescanpy.processing.segments.find_segments(ds, *, gap_factor=8.0, time_gap_factor=8.0, sound_vel_rel_tol=0.05, garbage_value_var='backscatter_intensity', garbage_min_run_pings=20, garbage_degenerate_frac=0.999, stability_window_pings=100, stability_threshold=1.0, stability_min_run_pings=50, min_segment_pings=200)#

Split [0, n_pings) into contiguous Segment objects.

Gap and setting-change flags become boundaries; garbage and unstable spans become "excluded" segments. Each segment’s reasons names every signal that fired inside it (["clean"] if none).

Status is resolved per raw interval before merging. Only a clean interval shorter than min_segment_pings is merged, and only into a preceding clean neighbour, so a short excluded span never taints clean pings. A short clean interval with no clean neighbour is kept as is.

sidescanpy.processing.segments.split_by_segments(survey, segments)#

One sub-survey per "clean" segment, keyed by segment label.

Every frequency channel is sliced to the segment’s ping range with slice_by_pings(); all channels are assumed to share one ping axis. Excluded segments are skipped.

sidescanpy.processing.segments.write_segments(survey, segments, output_dir)#

Write <label>.ss.h5 into output_dir for every clean segment.

Uses split_by_segments(); existing files are overwritten. Returns the paths written, in segment order.