Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,46 @@ if(BIOIMAGE_FLOW_FMA_DISPATCH)
endif()
endif()

option(
BIOIMAGE_FILTERS_AVX2_DISPATCH
"Build x86 AVX2/FMA filter kernels with runtime CPU dispatch"
ON
)
if(BIOIMAGE_FILTERS_AVX2_DISPATCH)
string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" _bioimage_filters_processor)
if(_bioimage_filters_processor MATCHES "^(x86_64|amd64|i[3-6]86)$")
if(MSVC)
target_sources(
_core PRIVATE
src/cpp/filters/dispatch.cxx
src/cpp/filters/convolve_avx2.cxx
src/cpp/filters/eigenvalues_avx2.cxx
)
set_property(
SOURCE
src/cpp/filters/convolve_avx2.cxx
src/cpp/filters/eigenvalues_avx2.cxx
APPEND PROPERTY COMPILE_OPTIONS /arch:AVX2
)
target_compile_definitions(_core PRIVATE BIOIMAGE_FILTERS_AVX2_DISPATCH=1)
elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_sources(
_core PRIVATE
src/cpp/filters/dispatch.cxx
src/cpp/filters/convolve_avx2.cxx
src/cpp/filters/eigenvalues_avx2.cxx
)
set_property(
SOURCE
src/cpp/filters/convolve_avx2.cxx
src/cpp/filters/eigenvalues_avx2.cxx
APPEND PROPERTY COMPILE_OPTIONS -mavx2 -mfma
)
target_compile_definitions(_core PRIVATE BIOIMAGE_FILTERS_AVX2_DISPATCH=1)
endif()
endif()
endif()

option(BIOIMAGE_PROFILE "Enable per-phase profiling instrumentation (development only)" OFF)
if(BIOIMAGE_PROFILE)
target_compile_definitions(_core PRIVATE BIOIMAGE_PROFILE)
Expand Down
49 changes: 40 additions & 9 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ Notes:
wrapper dispatches to a templated C++ instantiation per dtype; other
floating dtypes are cast to `float32`. If the two arrays' dtypes do not
match, both are promoted to `float64` rather than silently downcast.
- Both weight arrays must contain only finite values.
- Higher weights are processed first (in descending order) — the same
convention affogato uses.
- The implementation reuses the union-find and per-root mutex-set helpers
Expand Down Expand Up @@ -331,6 +332,7 @@ Notes:
- All three weight arrays (`weights`, `mutex_weights`, `semantic_weights`)
must have the same floating dtype, or all three are promoted to
`float64`.
- All three weight arrays must contain only finite values.
- Output `labels` are dense `uint64` ids in `0 .. number_of_clusters - 1`
(first-occurrence order, matching the regular graph mutex watershed —
*not* the 1-based foreground labels produced by the grid variant).
Expand Down Expand Up @@ -946,7 +948,7 @@ Notes:
- `edge_weights` must be 1D with length `graph.number_of_edges`. Supported
dtypes are `float32` and `float64`; other floating dtypes are promoted to
`float32` (matching nifty, whose Python binding is `float32`-only). Non-float
dtypes raise `TypeError`.
dtypes raise `TypeError`. All weights must be finite.
- `seeds` must be 1D with length `graph.number_of_nodes`. Supported dtypes are
`uint32`, `uint64`, `int32`, `int64`. The value `0` marks unlabeled nodes;
non-zero ids are propagated along low-weight paths. Signed seed arrays must
Expand Down Expand Up @@ -1153,6 +1155,12 @@ Notes:
`downsampleMultiset` returns only four of them and leaves the caller to
reconstruct `entry_offsets` / `entry_sizes`; `bioimage-cpp` returns them
directly so multi-level downsample chains compose without bookkeeping.
- `offsets` and `unique_offsets` are element offsets into `ids` and `counts`.
They are not byte offsets.
- Construction checks integer ranges and the complete flat-storage structure.
Native operations repeat the checks because `LabelMultiset` is mutable.
- Stored entries must contain at least one `(id, count)` pair. A completely
empty top-level `LabelMultiset` is valid.
- `multiset_from_labels(labels, block_shape)` builds the level-0 multiset
from a `uint32` or `uint64` label volume in one call. There is no nifty
equivalent.
Expand Down Expand Up @@ -1210,10 +1218,10 @@ energy = objective.energy(labels)
- `graph` — an `UndirectedGraph` or `RegionAdjacencyGraph`. The constructor
copies the topology, so further mutations on the input graph do not affect
the objective.
- `edge_costs` — 1D `float64` array of length `graph.number_of_edges`.
- `edge_costs` — 1D array of finite values with length
`graph.number_of_edges`; compatible inputs are converted to `float64`.
- `lifted_uvs` / `lifted_costs` — optional `(n_lifted, 2)` uint64 array and 1D
float64 array of equal length, listing the additional lifted edges and
their weights.
float64 array of equal length. Lifted costs must be finite.
- `bfs_distance` — optional positive integer. Adds a zero-weight lifted edge
for every pair of nodes within this many base-graph hops of each other
(excluding nodes already connected by a base edge). Pairs with both
Expand Down Expand Up @@ -1394,10 +1402,11 @@ labels = bic.graph.multicut.GreedyAdditiveMulticut().optimize(objective)
energy = objective.energy(labels)
```

`MulticutObjective` accepts an `UndirectedGraph` or a `RegionAdjacencyGraph` and
a 1D `edge_costs` array of length `graph.number_of_edges`. The objective owns
the current best `labels`; `optimize` updates them in place and also returns
the new array.
`MulticutObjective` accepts an `UndirectedGraph` or a `RegionAdjacencyGraph`.
It converts a 1D `edge_costs` array to `float64`. The array length must equal
`graph.number_of_edges`, and all costs must be finite. The objective owns the
current best `labels`; `optimize` updates them in place and also returns the
new array.

Available solvers:

Expand Down Expand Up @@ -1459,12 +1468,24 @@ labels = solver.optimize(objective)

Notes:

- `edge_costs` must be `float64` and 1D with length `graph.number_of_edges`.
- `edge_costs` must be finite and 1D with length `graph.number_of_edges`.
- Output labels are dense `uint64` ids in `0 .. number_of_clusters - 1`.
- `MulticutObjective.energy(labels)` is the multicut energy used internally; it
matches `nmc.multicutObjective(...).evalNodeLabels(labels)`.
- `objective.reset_labels()` restores the per-node initial labeling, useful when
re-running solvers from a clean state.
- `number_of_threads=0` on `MulticutDecomposer` uses available hardware
concurrency. A positive value sets the maximum worker count.
- `MulticutDecomposer` runs decomposition and component solvers in C++. It
accepts `GreedyAdditiveMulticut`, `GreedyFixationMulticut`,
`KernighanLinMulticut`, and chains composed only of these solvers.
- `MulticutDecomposer` does not accept custom Python solvers,
`FusionMoveMulticut`, or another `MulticutDecomposer` as a component or
fallthrough solver. Use one of the supported native solvers instead.
- Multicut solvers no longer expose `clone()`. Native decomposition creates
independent C++ solver instances for its workers.
- Components can finish in any order. The decomposer applies labels in
component order, so scheduling does not change output labels.

Intentional differences vs. nifty:

Expand All @@ -1476,6 +1497,9 @@ Intentional differences vs. nifty:
- `MulticutDecomposer` short-circuits the trivial case where the sub-solver is
`GreedyAdditiveMulticut` and no fallthrough is given — the greedy solver
already operates on each connected component internally.
- Unlike nifty's factory-based decomposer, the native decomposer supports only
the solver types listed above. This keeps worker execution outside the GIL
and avoids Python callbacks from native threads.

#### Fusion Moves

Expand Down Expand Up @@ -1615,6 +1639,13 @@ Differences from nifty:
underlying loop is the same.
- Both `float32` and `float64` inputs are accepted; computation runs in
`float64` internally.
- All indicators, weights, sizes, and node features that affect priorities
must contain finite values.
- `MalaClusterPolicy.num_bins` must be an integer greater than or equal to `2`.
- `MalaClusterPolicy.num_edges_stop` counts all active edges after each
contraction. This includes the contracted edge and folded parallel edges.
Earlier bioimage-cpp versions counted only folds and could stop too late on
sparse graphs. The new behavior matches nifty's contraction graph.
- Tie-breaks follow the deterministic order of edge ids returned by
`UndirectedGraph`, which may differ from nifty's. On inputs where many
edges share the same indicator value, this combines with the
Expand Down
Loading
Loading