Skip to content

Commit 7d5c937

Browse files
First version of filters (#21)
* First version of filters * Add filter benchmarks * Finalize filter implementation
1 parent 63b1aed commit 7d5c937

18 files changed

Lines changed: 3778 additions & 1 deletion

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ nanobind_add_module(_core
1313
NB_STATIC
1414
src/bindings/blocking.cxx
1515
src/bindings/module.cxx
16+
src/bindings/filters.cxx
1617
src/bindings/graph.cxx
1718
src/bindings/ground_truth.cxx
1819
src/bindings/segmentation.cxx

MIGRATION_GUIDE.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,88 @@ Notes:
977977
- Every value in the input must be present in the mapping.
978978
- Non-contiguous inputs are copied before entering C++.
979979

980+
## Image Filters
981+
982+
`bioimage-cpp` ships a small Gaussian-derivative filter set under
983+
`bic.filters`. The scope is the "ilastik filter set" exposed by
984+
`fastfilters`, which is also the most-used subset of `vigra.filters`.
985+
986+
Vigra / fastfilters:
987+
988+
```python
989+
import vigra.filters as vf
990+
out = vf.gaussianSmoothing(img, sigma=1.5)
991+
ev = vf.hessianOfGaussianEigenvalues(img, scale=1.5)
992+
993+
import fastfilters as ff
994+
out = ff.gaussianSmoothing(img, sigma=1.5)
995+
ev = ff.hessianOfGaussianEigenvalues(img, scale=1.5)
996+
```
997+
998+
bioimage-cpp:
999+
1000+
```python
1001+
import bioimage_cpp as bic
1002+
1003+
out = bic.filters.gaussian_smoothing(img, sigma=1.5)
1004+
ev = bic.filters.hessian_of_gaussian_eigenvalues(img, sigma=1.5)
1005+
```
1006+
1007+
Name mapping:
1008+
1009+
| vigra / fastfilters name | bioimage-cpp name |
1010+
| --- | --- |
1011+
| `gaussianSmoothing` | `gaussian_smoothing` |
1012+
| `gaussianDerivative` | `gaussian_derivative` |
1013+
| `gaussianGradientMagnitude` | `gaussian_gradient_magnitude` |
1014+
| `laplacianOfGaussian` | `laplacian_of_gaussian` |
1015+
| `hessianOfGaussianEigenvalues` | `hessian_of_gaussian_eigenvalues` |
1016+
| `structureTensorEigenvalues` | `structure_tensor_eigenvalues` |
1017+
1018+
Common parameters:
1019+
1020+
- `sigma` is a positive scalar or a per-axis sequence of length
1021+
`image.ndim`. Anisotropic sigma is supported on every filter.
1022+
- `gaussian_derivative` takes an `order` argument that is a scalar or a
1023+
per-axis sequence of ints in `{0, 1, 2}`.
1024+
- `structure_tensor_eigenvalues` takes positional `inner_sigma` and
1025+
`outer_sigma` (vigra calls them `innerScale` / `outerScale`).
1026+
- `window_size` controls the kernel radius:
1027+
`radius = ceil(window_size * sigma)`. `0.0` (the default) selects the
1028+
vigra-style default `3 + 0.5 * order`. Matches the same-named parameter
1029+
in vigra/fastfilters.
1030+
1031+
Important differences from vigra and fastfilters:
1032+
1033+
- Only 2D and 3D scalar (single-channel) inputs are supported in v1.
1034+
Channels and leading batch axes should be looped externally — matches
1035+
fastfilters' convention. Vigra's `taggedView`/`AxisInfo` machinery is
1036+
not reproduced.
1037+
- C++ kernels operate on `float32`. `float64` inputs are accepted and the
1038+
output is cast back to `float64`. `uint8` and `uint16` are accepted with
1039+
a `float32` output (the typical ML-feature use case).
1040+
- Boundary handling is `mirror` (matches scipy `mode="mirror"`
1041+
reflection without edge-pixel repeat). Other boundary modes are not
1042+
exposed yet; the C++ layer carries an enum for future tiled processing.
1043+
- Eigenvalue outputs have a trailing axis of size `image.ndim`, sorted
1044+
largest → smallest. This matches `fastfilters`. To get vigra's
1045+
ascending order, reverse with `result[..., ::-1]`.
1046+
- No IIR / recursive Gaussian, no `convolve` / `recursiveFilter2D`, no
1047+
morphology, no distance transforms, no nonlinear diffusion, and no
1048+
non-local means in v1. Use `scipy.ndimage`, `skimage`, or the original
1049+
vigra/fastfilters bindings if you need those.
1050+
1051+
Implementation notes:
1052+
1053+
- All six filters are written as portable C++20 scalar code that the
1054+
compiler auto-vectorizes. No SIMD intrinsics, no per-file ISA flags, no
1055+
runtime CPU dispatch, no vendored SIMD library. This keeps the build
1056+
light enough to ship as portable PyPI wheels across Linux/macOS/Windows
1057+
and x86_64/arm64.
1058+
- Single-threaded for now. Threading can be added later via
1059+
`detail/threading.hxx::parallel_for_chunks` without changing the
1060+
public API.
1061+
9801062
## I/O and Build Dependencies
9811063

9821064
`bioimage-cpp` intentionally does not replace nifty or affogato I/O helpers.

0 commit comments

Comments
 (0)