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
A window's placement in its source array's native (row, col) space. |
|
Fixed |
|
Full-sample-axis window spanning a fixed ping span, at native resolution — not resized here. |
|
Resize an already-photometrically-transformed 2D image (e.g. the uint8 output of |
|
Pick a |
|
Every target from |
|
Translate a |
|
One rendered export window, before the 8/16-bit display transform. |
|
Port (sample axis reversed) and starboard side by side, nadir in the middle. |
|
Column of a single-channel |
|
Move every target's |
|
Record |
|
File-coordinate target lists for |
|
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:
objectA 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_pxcrop 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
arrayis smaller thansize_pxin 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-imagewidth/heightexplicitly, 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=Falseusesfixed_ping_countpings instead.center_pingdefaults 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_pxbehaves as inextract_chip().Known, deliberately deferred edge case: a survey shorter than the native ping span raises
ValueErrorrather 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 squareoutput_size_px x output_size_px— the deliberately-last step afterextract_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 leastmin_distance_pxfrom every position intarget_positions— for no-target negative samples.target_positionsis a plain list of(row, col)tuples, notTargetobjects, 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 ton_rows/n_colsandmin_distance_pxcan 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
targetswhose position falls insideextent, 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 afterextract_full_channel(), whose returned window is already downsampled whiletargets_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:
objectOne 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
sampleincombine_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; seerescale_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_yconvert 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) overresolution_mfor a geometric/georef window. The result goes inextra_attributes["bbox_size_px"], which COCO/YOLO writers use instead of the shared placeholder box size.Returns
targetunchanged when it has nobbox(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 incombine_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.
chipcropssize_pxsquare around(local_ping, center_sample)onchannel_idx;single-swathtakes that channel’s full sample axis over a square native ping span;dual-swathdoes the same on both channels combined (combine_channels_mirrored()). The window is oriented (orient_pixel_window()) and resized tosize_pxsquare 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
- 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
Nonewhen no target falls in the window.Falsefor background windows.
- Returns:
- RenderedWindow or None
Nonewhenlevel’s variable is absent frommasked, the neighborhood is too short for a full-swath style’s square native window, or (require_targets) no target is inside.