Files
image-filter/docs/semantics.md
T
mohamad bcaae5ec22 feat: initialize standalone image-filter
Rename the distribution to image-filter and imports to image_filter.
Keep medical fixtures local and document their setup for fresh clones.
Include composite pipelines, consistent examples, and class diagrams.
2026-09-10 01:58:07 +03:30

9.2 KiB

Numerical semantics and scope

This package is a greenfield implementation. Its numerical contracts are the documented behavior of SciPy, scikit-image, PyWavelets, and NumPy—not any application-specific filter implementation.

Shared contract

  • Inputs are finite, real numeric NumPy arrays with two or three inferred non-singleton spatial axes.
  • Filters preserve the complete input shape, including singleton and channel axes.
  • Numeric output is explicitly float32 or float64 (default float64). Canny always returns a boolean mask.
  • SciPy-backed filters accept reflect, constant, nearest, mirror, and wrap. cval is used only by constant mode.
  • Each filter exposes a Capabilities object. Unsupported dimensions and recognized-but-unimplemented options use domain-specific exceptions.

MeanFilter

MeanFilter calls scipy.ndimage.uniform_filter exactly, with a scalar kernel size applied only to spatial axes. Channel and retained singleton axes use size one.

SciPy's origin=0 alignment is the default. Odd kernels are centered on a sample. Even kernels necessarily have one extra sample on one side of the origin; the exact placement is SciPy's documented convention. Set origin explicitly to shift the footprint. Valid origins are checked at construction. This policy makes even-kernel behavior deliberate and testable.

LoGFilter

LoGFilter sums second-order scipy.ndimage.gaussian_filter derivatives over the inferred spatial axes. This is numerically equivalent to scipy.ndimage.gaussian_laplace when every array axis is spatial, while also preventing smoothing or differentiation across channels. Sigma is expressed in pixels and may be scalar or one value per effective spatial axis.

LawsFilter

Laws responses use normalized standard L3/E3/S3 and L5/E5/S5/W5/R5 vectors with scipy.ndimage.convolve1d. A configuration contains one two-character kernel name per spatial axis, such as L5E5 or L5E5S5. Optional texture energy is the local uniform mean of the absolute response with window width 2 * energy_radius + 1.

Rotation-invariant pooling is intentionally unsupported. Construct explicit orientation filters and combine their outputs according to the domain's chosen statistic instead of relying on an undocumented convention.

GaborFilter

Gabor responses call skimage.filters.gabor for each orientation. Frequency is in cycles per pixel, angles are radians, and sigma_y = sigma / gamma. Available complex response projections are magnitude, real, and imaginary; multiple orientations use max, min, mean, or sum pooling.

The filter supports 2D data, including 2D multichannel arrays with an explicit channel axis. A 3D volume requires SliceWise, whose axis is part of the public configuration and provenance.

WaveletFilter

Wavelet coefficients use PyWavelets' shift-invariant pywt.swtn. Configuration letters map directly to PyWavelets keys: L to approximation (a) and H to detail (d) in the order of inferred spatial axes. The selected array is the requested coefficient at the final decomposition level.

SWT requires transformed lengths divisible by 2**level. Each spatial axis is padded only at its high edge to the next valid length using numpy.pad, then the coefficient is cropped to the original shape. pad_mode is therefore a NumPy padding mode rather than a PyWavelets signal-extension mode. The optional normalize=True is passed directly to pywt.swtn.

Rotation-invariant coefficient pooling and Riesz steering are not implemented; requesting rotation invariance raises UnsupportedOptionError. Decimated DWT reconstruction is a distinct operation and is outside this first API.

GaussianFilter

2D/3D Gaussian smoothing calls scipy.ndimage.gaussian_filter. sigma is a finite positive scalar or immutable nonempty tuple, one value per inferred spatial axis in array order. Tuple length is validated on application. truncate is finite and positive (default 4 standard deviations). Input and intermediate values use the selected floating output dtype; no intensity normalization occurs. Channels and singleton axes are indexed out before the backend call, preserving SciPy 1.10 compatibility.

MedianFilter

2D/3D rank filtering calls scipy.ndimage.median_filter. size is a positive integer applied along each spatial axis. origin must be an integer in [-size//2, (size-1)//2], interpreting the lower bound as -(size//2). Even footprints select sorted element n//2, rather than averaging the middle pair. Input is converted to the selected floating dtype before filtering, including fractional cval padding for integer images and support for float16 input. Large 3D windows can require substantial backend memory.

SobelFilter

2D/3D gradients call scipy.ndimage.sobel on each spatial block. The default axis=None returns Euclidean magnitude sqrt(sum(sobel(image, axis=i)**2)) over spatial axes, accumulated with hypot to avoid intermediate squared overflow. There is no division by dimensionality or kernel gain. Optional integer axis selects a signed component, indexing the inferred spatial axes (negative indices supported). Channels are neither smoothed nor differentiated. Computation uses the selected floating dtype.

LaplacianFilter

2D/3D scipy.ndimage.laplace sums discrete second differences over spatial axes. Input is cast to the selected floating dtype, retaining signed responses. This operation adds no Gaussian smoothing; use LoGFilter when that is desired.

CannyFilter

skimage.feature.canny produces a boolean edge mask from each 2D plane/channel. Singleton axes are restored afterward. A 3D image is rejected; use SliceWise(CannyFilter(), axis=0) with an explicitly chosen array axis. There is no implicit projection or slicing.

Canny rejects int64/uint64 with InvalidImageError, matching the backend restriction declared in its capabilities. Callers may explicitly convert to float and choose thresholds in the resulting intensity units.

The original supported input dtype reaches Canny. Its image conversion scales unsigned integers by their dtype maximum and signed integers to the signed unit range; floating input keeps its values, with no min/max normalization or clipping. Absolute thresholds are expressed in original dtype intensity units; the backend divides integer thresholds by the dtype maximum. For floating input, thresholds are already in the unchanged intensity units. Missing low/high thresholds become 0.1/0.2 in the backend's normalized scale. use_quantiles=True instead interprets thresholds as gradient-magnitude quantiles in [0, 1], with defaults 0.1/0.2. Thresholds must be finite, nonnegative, and ordered; ordering against missing defaults for absolute thresholds is checked by the backend at application time and converted to InvalidConfigurationError.

sigma must be finite and positive. cval is in the converted image's units. mode defaults to constant; it governs Gaussian padding, and Canny excludes the outermost edge pixels during mask erosion. There is no public mask option.

Boundary and precision details for the new filters

All five accept reflect (half-sample reflection), mirror (whole-sample reflection), nearest (edge replication), wrap (periodic), and constant (finite cval, default 0). SciPy aliases are intentionally excluded. Numeric filters default to reflect. Boolean configuration values are not numbers. Like their backends, floating filters can lose precision or overflow for extreme finite inputs; finite outputs are verified on normal image ranges, not promised for arbitrary floating-point magnitudes. Provenance records parameters, effective spatial axes, shapes and dtypes; SliceWise records the nested filter and axis.

Composite contract

ImageFilter defines apply and shared apply_batch behavior. Numerical leaves reuse the internal NumericFilter implementation for validation and history. Pipeline is an ImageFilter containing a nonempty immutable tuple of components. Nested pipelines are supported at arbitrary depth and execute depth-first in child order. History contains each executed leaf once; composite boundaries do not add history entries. Errors propagate from the failing child.

Pipeline dimensions are the intersection of child dimensions. This declaration does not guarantee that intermediate ranges or dtypes satisfy later children; each child validates its actual input. The last child determines output dtype. Batch processing uses the same component API for both leaves and pipelines.

SliceWise accepts any component with 2D capability, including a pipeline, and calls its public API on each plane with remapped channel metadata. The actual first plane result determines output dtype (including boolean Canny results). Its outer history records one adapter entry containing the nested configuration, rather than repeating history for every slice.