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.
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
float32orfloat64(defaultfloat64). Canny always returns a boolean mask. - SciPy-backed filters accept
reflect,constant,nearest,mirror, andwrap.cvalis used only byconstantmode. - Each filter exposes a
Capabilitiesobject. 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.