# 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](https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.gaussian_filter.html). `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](https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.median_filter.html). `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](https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.sobel.html) 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](https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.laplace.html) 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](https://scikit-image.org/docs/stable/api/skimage.feature.html#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.