sidescanpy.export.windows.pixel#

Sample/ping pixel-space windows: chips, full-channel views, co-located targets, and the per-style window used by the uncorrected/db/ radiometric render levels.

The low-level functions operate on plain 2D (n_pings, n_samples) arrays – a caller has already selected the channel and backscatter variable (e.g. ds["backscatter_intensity"].isel(channel=0).values). Row = ping index, column = sample index throughout. extract_pixel_style_window() works on a loaded, nadir-masked ping neighborhood and handles all three styles.

2026 rshomberg@uri.edu

Contents

WindowExtent

A window's placement in its source array's native (row, col) space.

extract_chip

Fixed size_px x size_px crop around (center_row, center_col).

extract_full_channel

Full-sample-axis window spanning a fixed ping span, at native resolution — not resized here.

resize_to

Resize an already-photometrically-transformed 2D image (e.g. the uint8 output of sidescanpy.export.image.to_uint8()) to a square output_size_px x output_size_px — the deliberately-last step after extract_full_channel() + a percentile/gamma stretch, never before it (see that function's docstring for why the order matters).

sample_negative_window

Pick a (row, col) window center at least min_distance_px from every position in target_positions — for no-target negative samples.

targets_within_window

Every target from targets whose position falls inside extent, translated to window-local coordinates.

rescale_target_position

Translate a (row, col) position from a window's native (pre- downsample) space into its resized output space — needed after extract_full_channel(), whose returned window is already downsampled while targets_within_window() reports positions in the native extent.

RenderedWindow

One rendered export window, before the 8/16-bit display transform.

combine_channels_mirrored

Port (sample axis reversed) and starboard side by side, nadir in the middle.

target_combined_col

Column of a single-channel sample in combine_channels_mirrored()'s output.

rescale_targets

Move every target's (row, col) from a native window into its resized output.

with_bbox_size_px

Record target.bbox's measured (w, h) in this window's output pixels.

place_targets_in_file

File-coordinate target lists for extract_pixel_style_window()'s co-target lookup.

extract_pixel_style_window

Sample/ping-space window for one style, with the targets inside it.

class sidescanpy.export.windows.pixel.WindowExtent(row_start, col_start, n_rows, n_cols)#

Bases: object

A window’s placement in its source array’s native (row, col) space.

sidescanpy.export.windows.pixel.extract_chip(array, center_row, center_col, size_px, *, jitter_px=0, rng=None)#

Fixed size_px x size_px crop around (center_row, center_col).

Non-padding placement: the window shifts inward near an array edge instead of zero-padding, trading perfect centering for “always full real data.” jitter_px (default 0, exact centering) randomizes which point the window centers on, so a detector doesn’t learn “the target is always dead-center” — the true target position is unaffected, only where the window lands moves.

If array is smaller than size_px in a given axis, the full available extent on that axis is returned instead (non-square output) rather than padding — confirmed against real per-target survey data where a source catalog’s own decoded window can have a hard ping-count ceiling well under a requested chip size. Every caller downstream (CocoImageEntry, write_coco/write_yolo) already carries per-image width/height explicitly, so a dataset with mixed image sizes from this cause is handled correctly, not silently mis-sized.

Returns:
(window, extent)
sidescanpy.export.windows.pixel.extract_full_channel(array, *, center_ping=None, square=True, fixed_ping_count=200, jitter_px=0, rng=None)#

Full-sample-axis window spanning a fixed ping span, at native resolution — not resized here. Resize (via resize_to()) belongs after a photometric transform (e.g. sidescanpy.export.image.to_uint8()) has run on the full native-resolution values, not before it: computing a percentile stretch on an already-downsampled array measures the stretch against a blurred, artificially low-variance distribution and washes the result out (found the hard way — see this function’s own history). Every other statistics-computing function in this library (waterfall/ gridded percentile scaling, resample_channel_to_grid’s cell aggregation) works the same way: aggregate/stretch against real per-sample values first, resize the result for display second.

square=True (default): the native ping span equals the channel’s own sample count, not a fixed ping count against a differing sample count — avoids the aspect-ratio stretch a fixed ping count would otherwise introduce once the eventual square output is produced. square=False uses fixed_ping_count pings instead.

center_ping defaults to the array’s own midpoint (a full-channel negative sample has no target to center on); pass a target’s ping position for a positive sample. jitter_px behaves as in extract_chip().

Known, deliberately deferred edge case: a survey shorter than the native ping span raises ValueError rather than guessing a workaround (e.g. padding or falling back to non-square) — revisit if a real survey actually hits this.

Returns:
(window, extent)

Native resolution — pass through a photometric transform, then resize_to(), in that order.

sidescanpy.export.windows.pixel.resize_to(image, output_size_px)#

Resize an already-photometrically-transformed 2D image (e.g. the uint8 output of sidescanpy.export.image.to_uint8()) to a square output_size_px x output_size_px — the deliberately-last step after extract_full_channel() + a percentile/gamma stretch, never before it (see that function’s docstring for why the order matters). Bilinear resampling, matching this project’s own established resize-after-transform precedent.

sidescanpy.export.windows.pixel.sample_negative_window(n_rows, n_cols, target_positions, size_px, min_distance_px, rng, *, max_attempts=200)#

Pick a (row, col) window center at least min_distance_px from every position in target_positions — for no-target negative samples.

target_positions is a plain list of (row, col) tuples, not Target objects, so callers don’t need to build one just to check distances.

Raises:
RuntimeError

If no valid location is found within max_attempts — a dense target catalog relative to n_rows/n_cols and min_distance_px can make this genuinely infeasible; fail loudly rather than silently returning a too-close location.

sidescanpy.export.windows.pixel.targets_within_window(targets, extent)#

Every target from targets whose position falls inside extent, translated to window-local coordinates.

Reuses the existing Target.sample (column) / extra_attributes["window_row"] (row) convention used by the chip exporters. Targets missing either field are skipped (nothing to place). For multi-target images: every included target becomes its own annotation on the same exported image, not just the one a chip happened to be centered on.

sidescanpy.export.windows.pixel.rescale_target_position(row, col, native_shape, output_shape)#

Translate a (row, col) position from a window’s native (pre- downsample) space into its resized output space — needed after extract_full_channel(), whose returned window is already downsampled while targets_within_window() reports positions in the native extent.

class sidescanpy.export.windows.pixel.RenderedWindow(image, targets=<factory>, extent=None, meta=None, grid_shape=None, resolution_m=None)#

Bases: object

One rendered export window, before the 8/16-bit display transform.

Attributes:
imagenp.ndarray

Display-oriented float window at its output size.

targetslist of Target

Targets inside the window, in image’s pixel coordinates (sample = column, extra_attributes["window_row"] = row).

extentWindowExtent or None

Pixel levels: the window’s placement in the file’s ping axis and the style’s column space (single-channel samples, or the combined columns of combine_channels_mirrored()), before orientation.

metadict or None

Geometric/georef levels: the extractor’s meta (grid origin and resolution) for locating further points in the window.

grid_shapetuple of int or None

Geometric/georef levels: (n_rows, n_cols) of the resampled grid.

resolution_mfloat or None

Geometric/georef levels: meters per output pixel.

sidescanpy.export.windows.pixel.combine_channels_mirrored(port_2d, starboard_2d)#

Port (sample axis reversed) and starboard side by side, nadir in the middle.

A plain concatenation of the two pixel arrays, not a resample into a shared real-world frame (that is what the geometric/georef levels do).

sidescanpy.export.windows.pixel.target_combined_col(sample, n_samples_single, channel_idx)#

Column of a single-channel sample in combine_channels_mirrored()’s output.

sidescanpy.export.windows.pixel.rescale_targets(targets, native_shape, output_shape)#

Move every target’s (row, col) from a native window into its resized output.

Matches resize_to()’s transform; see rescale_target_position().

sidescanpy.export.windows.pixel.with_bbox_size_px(target, scale_x, scale_y)#

Record target.bbox’s measured (w, h) in this window’s output pixels.

scale_x/scale_y convert from the bbox’s native units (source sample/ping pixels) into output pixels – 1:1 for an unresized pixel window; meters-per-sample (or per ping) over resolution_m for a geometric/georef window. The result goes in extra_attributes["bbox_size_px"], which COCO/YOLO writers use instead of the shared placeholder box size.

Returns target unchanged when it has no bbox (a center-only pick keeps the placeholder).

sidescanpy.export.windows.pixel.place_targets_in_file(items_by_channel, n_samples_single)#

File-coordinate target lists for extract_pixel_style_window()’s co-target lookup.

Parameters:
items_by_channeldict

{channel_idx: [(target, file_ping_idx), ...]}.

n_samples_singleint

Samples per channel.

Returns:
(placed_by_channel, placed_combined)

Per channel, each target with extra_attributes["window_row"] = its file ping (column = its own sample); and one list over both channels in combine_channels_mirrored()’s column space.

sidescanpy.export.windows.pixel.extract_pixel_style_window(masked, *, center_sample, channel_idx, local_ping, ping_offset, style, level, size_px, jitter_px, rng, placed_by_channel, placed_combined, require_targets=True)#

Sample/ping-space window for one style, with the targets inside it.

chip crops size_px square around (local_ping, center_sample) on channel_idx; single-swath takes that channel’s full sample axis over a square native ping span; dual-swath does the same on both channels combined (combine_channels_mirrored()). The window is oriented (orient_pixel_window()) and resized to size_px square if needed, with every co-located target moved to match and its measured bbox recorded (with_bbox_size_px()).

Parameters:
maskedxr.Dataset

Nadir-masked ping neighborhood (see mask_nadir_gap_with_uncorrected()).

center_sampleint

Window center on channel_idx’s own sample axis.

channel_idxint

Channel of the window (and of center_sample).

local_pingint

Center ping, indexed into masked.

ping_offsetint

File ping index of masked’s first ping (placed targets carry file ping indices).

style, levelstr

See sidescanpy.export.windows.levels.

size_pxint

Output size (both axes).

jitter_pxint

Uniform center jitter, drawn from rng.

rngnp.random.Generator
placed_by_channel, placed_combined

From place_targets_in_file().

require_targetsbool, default True

Return None when no target falls in the window. False for background windows.

Returns:
RenderedWindow or None

None when level’s variable is absent from masked, the neighborhood is too short for a full-swath style’s square native window, or (require_targets) no target is inside.