sidescanpy.io.jsf.targets#

EdgeTech Discover SQLite .target catalog reader.

Discover writes one SQLite database per target pick (Target-NNNN.target), holding a raw JSF ping-sample window plus position metadata. Schema (Target, PingChannel, MutableTarget, MutableTag tables) was reverse-engineered by hand and verified against real Discover target files.

Known gotchas this reader accounts for:

  • Target.TargetId is a per-file primary key that always resets to 1 — never a cross-file identifier. The real identity is the filename’s own number (Target-NNNN.target), used as Target.target_id here.

  • PingChannel’s ~200-row window is usually a clean contiguous run of PingSequenceNumber values, but not always. One real file seen so far (Target-0002.target) has two things wrong at once: (1) a uint32 rollover artifact — 104 of its rows, and the target’s own ``MutableTarget.PingSequenceNumber``, are stored as true_value + 2**32 (e.g. raw 4294971779 for a true 4483, confirmed by the corrected value’s neighbors forming a clean contiguous run); (2) an unrelated, disjoint cluster of 94 rows (4846-4939) mixed into the same file’s PingChannel table, nowhere near the target’s real window. Correcting the rollover and then walking outward from the target’s own (corrected) ping to find its contiguous run handles both at once — no fixed distance threshold from the target’s raw ping is reliable here, since the target’s own raw value is exactly what’s corrupted. position_confidence downgrades to "approximate" (recording the exclusion in extra_attributes) whenever this filtering actually removes rows.

  • MutableTarget.Description/MutableTag.Tag are commonly empty — this is normal (no classification was ever recorded), not a read failure; label stays None rather than a placeholder string.

Position formula (validated visually against 6+ real targets): within the ping window sorted by sequence number, the target’s row is the index of its own PingSequenceNumber; its column is round(SlantRangeDistanceMeters / PingMetersPerSample) using the target’s own ping’s PingMetersPerSample (not a window-wide constant, though it typically doesn’t vary within one window).

Decode (decode_target_window(); raw values, native orientation – TVG and display orientation live in export.target_window): SampleBytes -> np.frombuffer(dtype="<i2").astype(float) * 2 ** (-JsfWeightingFactor) — real JSF sample amplitudes, not a rendered image. Expect a leading run of exact-zero samples (blanking/ring-down); not a bug.

2026 rshomberg@uri.edu

Contents

read_discover_targets

Read every Target-NNNN.target file in directory.

decode_target_window

Re-decode the raw ping-sample window for target from its source SQLite catalog.

sidescanpy.io.jsf.targets.read_discover_targets(directory)#

Read every Target-NNNN.target file in directory.

Returns one Target per file, sorted by filename. Files that fail to parse are skipped with a warning rather than aborting the whole batch (matches the family’s “clip/skip rather than error on one bad row” convention elsewhere in this library).

sidescanpy.io.jsf.targets.decode_target_window(target)#

Re-decode the raw ping-sample window for target from its source SQLite catalog.

Returns a (n_pings, n_samples) float array of real JSF sample amplitudes (not a rendered/processed image) — samples per ping are truncated to the shortest row in the window, in the rare case they differ. Row target.extra_attributes["window_row"] is the target’s own ping within this array; column target.sample is its computed position within each ping.