sidescanpy.export.image#
Invertible 8-bit display transform.
Percentile-stretch + gamma-correct a backscatter array (any dtype) into
an 8-bit grayscale image suitable for training-dataset export, while
retaining enough metadata to reconstruct the original array later
(lossy — 8-bit quantization is not exactly reversible, but the scale is
recoverable). This is a display/export transform, not a physical
correction — it has no relationship to processing/radiometric.py’s
TVG correction. Run TVG (and geometric/georef, if wanted) first; this
module only turns whatever array it’s handed into 8-bit.
Normalization scope (per-image vs. survey-wide vs. dataset-wide) is
deliberately a caller decision, not baked in here: pass
reference_stats=None (default) for per-image scope, where each
array is stretched against its own percentiles, or precompute one
ReferenceStats via compute_reference_stats() over every
window a wider scope should share and pass it to every to_uint8()
call in that scope.
2026 rshomberg@uri.edu
Contents
Enough to invert |
|
Percentile bounds computed once, for reuse across many |
|
Compute shared percentile bounds over one or more arrays. |
|
Percentile-stretch + gamma-correct |
|
16-bit counterpart of |
|
Reverse |
|
|
|
Shared stretch for |
|
Write a single-channel grayscale PNG: 8-bit ( |
- class sidescanpy.export.image.ReversalMetadata(kind, p_min, p_max, gamma, original_max)#
Bases:
objectEnough to invert
to_uint8()back to the original array’s dtype and approximate scale.
- class sidescanpy.export.image.ReferenceStats(p_min, p_max)#
Bases:
objectPercentile bounds computed once, for reuse across many
to_uint8()calls at survey- or dataset-wide normalization scope, instead of each image normalizing against only its own window.
- sidescanpy.export.image.compute_reference_stats(*arrays, pct_low=1.0, pct_high=99.0)#
Compute shared percentile bounds over one or more arrays.
Call once over every window a survey-wide or dataset-wide normalization scope should share, then pass the result to every
to_uint8()call in that scope viareference_stats=. Omit entirely for the default per-image scope.Non-finite values are excluded before the percentile is computed. Mixing arrays of different kinds (e.g. raw counts and TVG-corrected dB) is the caller’s decision, not validated here — only combine arrays it actually makes sense to normalize together.
- sidescanpy.export.image.to_uint8(array, *, gamma=1.0, pct_low=1.0, pct_high=99.0, reference_stats=None)#
Percentile-stretch + gamma-correct
arrayinto an 8-bit grayscale image.- Parameters:
- arraynp.ndarray
uint16,uint32, orfloat32/float64.uint32is stretched over non-zero values only (zero treated as background/no-return, matching raw sensor-counts convention); the other two kinds are stretched over all finite values.- gammafloat, default 1.0
Power-law display contrast (
normalized ** gamma) — matches this library’s existing display-gamma convention (viz.waterfall.plot_waterfall/viz.gridded, both of which handgammastraight tomatplotlib.colors.PowerNorm, whose own convention isnormalized ** gamma), where 1.0 is a no-op. Sincegamma > 1onnormalizedin[0, 1]pushes values down (darkens midtones/background, compresses toward black), a caller reusing a gamma value tuned against aPowerNorm-based render (e.g. 2.5) gets the same visual direction here — the inverse convention (normalized ** (1/gamma), which brightens instead) was this function’s original, undocumented-as-different implementation and produced washed-out/over-bright output at the same nominal gamma values that darkened contrast correctly elsewhere in this library; see CHANGELOG.md.- pct_low, pct_highfloat, default (1.0, 99.0)
Percentile bounds for the stretch, matching this library’s existing default elsewhere. Ignored when
reference_statsis given.- reference_statsReferenceStats, optional
Precomputed bounds (survey-/dataset-wide normalization scope, see
compute_reference_stats()). When given,pct_low/pct_highare ignored — the array is stretched againstreference_stats’s bounds instead of its own. Omit for the default per-image scope.
- Returns:
- (uint8_array, ReversalMetadata)
- sidescanpy.export.image.to_uint16(array, *, gamma=1.0, pct_low=1.0, pct_high=99.0, reference_stats=None)#
16-bit counterpart of
to_uint8()— identical percentile stretch and gamma (same bounds, sameReversalMetadata), just quantized to 65536 levels instead of 256. For datasets where a model or analyst should see finer backscatter gradations than 8-bit keeps;to_uint16(a)[0] >> 8equalsto_uint8(a)[0]to within one level of rounding.
- sidescanpy.export.image.from_uint8(img_8bit, metadata)#
Reverse
to_uint8()(or, given auint16image,to_uint16()) back to (an approximation of) the original array’s dtype and scale. Lossy — quantization is not exactly reversible.
- sidescanpy.export.image.NORMALIZATION_SCOPES = ('image', 'shared')#
"image": each image stretched on its own percentiles;"shared": one stretch for a whole set.
- sidescanpy.export.image.reference_stats_for_scope(scope, arrays, pct_low, pct_high)#
Shared stretch for
arraysunder"shared"scope;Noneunder"image".- Raises:
- ValueError
Unknown
scope.
- sidescanpy.export.image.save_grayscale_png(image, path)#
Write a single-channel grayscale PNG: 8-bit (
"L") or, foruint16input, 16-bit.Unlike
matplotlib.pyplot.imsave(which always writes RGB(A)), the file holds exactly one channel.