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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ nanobind_add_module(_core
src/bindings/graph.cxx
src/bindings/ground_truth.cxx
src/bindings/segmentation.cxx
src/bindings/util.cxx
src/bindings/utils.cxx
src/cpp/segmentation/mutex_watershed.cxx
src/cpp/segmentation/semantic_mutex_watershed.cxx
Expand Down
62 changes: 61 additions & 1 deletion MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import bioimage_cpp as bic

Graph functionality is under `bic.graph`, segmentation functionality is under
`bic.segmentation`, ground-truth comparison functionality is under
`bic.ground_truth`, and small utility functions are under `bic.utils`.
`bic.ground_truth`, small utility functions are under `bic.utils`, and
reusable data structures (e.g. union-find) are under `bic.util`.

## Blocking

Expand Down Expand Up @@ -1094,6 +1095,65 @@ partition metrics and a semantic-label match fraction so the deviation is
measurable. The bioimage-cpp partitions match an independent Python
reference implementation of the algorithm.

## Union-Find

Nifty exposes a disjoint-set / union-find structure as `nifty.ufd.ufd`.
`bioimage-cpp` provides the same primitive under `bic.util`.

Nifty:

```python
import nifty.ufd as nufd
import numpy as np

uf = nufd.ufd(5)
uf.merge(0, 1)
uf.merge(np.array([[2, 3], [3, 4]], dtype="uint64"))
roots = uf.find(np.array([0, 1, 2, 3, 4], dtype="uint64"))
labels = uf.elementLabeling()
```

bioimage-cpp:

```python
import bioimage_cpp as bic
import numpy as np

uf = bic.util.UnionFind(5)
uf.merge(0, 1)
uf.merge(np.array([[2, 3], [3, 4]], dtype=np.uint64))
roots = uf.find(np.array([0, 1, 2, 3, 4], dtype=np.uint64))
labels = uf.element_labeling()
```

Method mapping:

| nifty-style name | bioimage-cpp name |
| --- | --- |
| `find(node)` / `find(array)` | `find(node)` / `find(array)` |
| `merge(u, v)` / `merge(array)` | `merge(u, v)` / `merge(array)` |
| `elementLabeling` | `element_labeling` |
| `numberOfElements` | `size` (property) |

Notes:

- The constructor takes a single `size` argument; all elements start as
singletons.
- Scalar `find`/`merge` accept Python integers and return Python integers.
- Bulk `find(nodes)` accepts a 1D `uint64` array and returns a 1D `uint64`
array of roots of the same length.
- Bulk `merge(edges)` accepts an `(N, 2)` `uint64` array of node-pair edges
and applies the merges in row order.
- `element_labeling()` returns a `uint64` array of length `size`, each entry
the (path-compressed) root of that element. Use this when you want the
final labeling as one array rather than via repeated `find` calls.
- `merge_to(stable, removed)` is also available: it forces `stable`'s root
to survive the union regardless of rank.
- `reset(n)` reinitialises the structure to `n` singletons, reusing
capacity where possible.
- The GIL is released around bulk operations, so multiple threads can run
bulk merges on independent `UnionFind` instances in parallel.

## Dictionary-Based Relabeling

If you used a small helper to apply a dictionary to an integer label array, use
Expand Down
6 changes: 3 additions & 3 deletions include/bioimage_cpp/graph/connected_components.hxx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once

#include "bioimage_cpp/detail/union_find.hxx"
#include "bioimage_cpp/util/union_find.hxx"
#include "bioimage_cpp/graph/undirected_graph.hxx"

#include <cstddef>
Expand All @@ -11,7 +11,7 @@
namespace bioimage_cpp::graph {

inline std::vector<std::uint64_t> dense_labels_from_union_find(
detail::UnionFind &sets,
bioimage_cpp::util::UnionFind &sets,
const std::uint64_t number_of_nodes
) {
std::unordered_map<std::uint64_t, std::uint64_t> relabeling;
Expand All @@ -31,7 +31,7 @@ inline std::vector<std::uint64_t> connected_components(
const UndirectedGraph &graph,
const std::uint8_t *edge_mask = nullptr
) {
detail::UnionFind sets(static_cast<std::size_t>(graph.number_of_nodes()));
bioimage_cpp::util::UnionFind sets(static_cast<std::size_t>(graph.number_of_nodes()));
for (std::uint64_t edge = 0; edge < graph.number_of_edges(); ++edge) {
if (edge_mask != nullptr && edge_mask[static_cast<std::size_t>(edge)] == 0) {
continue;
Expand Down
4 changes: 2 additions & 2 deletions include/bioimage_cpp/graph/detail/fusion_contract.hxx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once

#include "bioimage_cpp/detail/union_find.hxx"
#include "bioimage_cpp/util/union_find.hxx"
#include "bioimage_cpp/graph/undirected_graph.hxx"

#include <algorithm>
Expand Down Expand Up @@ -62,7 +62,7 @@ inline AgreementContraction contract_by_agreement(
);
}

bioimage_cpp::detail::UnionFind sets(static_cast<std::size_t>(number_of_nodes));
bioimage_cpp::util::UnionFind sets(static_cast<std::size_t>(number_of_nodes));
for (std::uint64_t edge = 0; edge < number_of_edges; ++edge) {
const auto uv = graph.uv(edge);
const auto u = static_cast<std::size_t>(uv.first);
Expand Down
4 changes: 2 additions & 2 deletions include/bioimage_cpp/graph/edge_weighted_watershed.hxx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#pragma once

#include "bioimage_cpp/detail/union_find.hxx"
#include "bioimage_cpp/util/union_find.hxx"
#include "bioimage_cpp/graph/undirected_graph.hxx"

#include <algorithm>
Expand Down Expand Up @@ -49,7 +49,7 @@ inline void edge_weighted_watershed_kernel(
[](const auto &a, const auto &b) { return a.first < b.first; }
);

bioimage_cpp::detail::UnionFind sets(static_cast<std::size_t>(number_of_nodes));
bioimage_cpp::util::UnionFind sets(static_cast<std::size_t>(number_of_nodes));

constexpr SeedT zero{0};
for (const auto &item : scratch.sort_buffer) {
Expand Down
6 changes: 3 additions & 3 deletions include/bioimage_cpp/graph/lifted_multicut/detail.hxx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#include "bioimage_cpp/detail/indexed_heap.hxx"
#include "bioimage_cpp/detail/union_find.hxx"
#include "bioimage_cpp/util/union_find.hxx"
#include "bioimage_cpp/graph/connected_components.hxx"
#include "bioimage_cpp/graph/undirected_graph.hxx"

Expand Down Expand Up @@ -161,7 +161,7 @@ inline void rename_neighbor(
// pops base edges off the heap).
inline std::size_t merge_dynamic_nodes(
DynamicGraph &dynamic_graph,
bioimage_cpp::detail::UnionFind &sets,
bioimage_cpp::util::UnionFind &sets,
EdgeHeap &heap,
std::size_t u,
std::size_t v
Expand Down Expand Up @@ -258,7 +258,7 @@ inline std::size_t merge_dynamic_nodes(
}

inline std::vector<std::uint64_t> labels_from_sets(
bioimage_cpp::detail::UnionFind &sets,
bioimage_cpp::util::UnionFind &sets,
const UndirectedGraph &graph
) {
return dense_labels_from_union_find(sets, graph.number_of_nodes());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace bioimage_cpp::graph::lifted_multicut {
// Reusable scratch state for `lifted_greedy_additive`.
struct GreedyAdditiveWorkspace {
detail::DynamicGraph dynamic_graph;
bioimage_cpp::detail::UnionFind union_find{0};
bioimage_cpp::util::UnionFind union_find{0};
detail::EdgeHeap heap;

void reset(const UndirectedGraph &lifted_graph) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include "bioimage_cpp/detail/edge_hash.hxx"
#include "bioimage_cpp/detail/indexed_heap.hxx"
#include "bioimage_cpp/detail/profile.hxx"
#include "bioimage_cpp/detail/union_find.hxx"
#include "bioimage_cpp/util/union_find.hxx"
#include "bioimage_cpp/graph/connected_components.hxx"
#include "bioimage_cpp/graph/lifted_multicut/objective.hxx"

Expand Down
6 changes: 3 additions & 3 deletions include/bioimage_cpp/graph/multicut/detail.hxx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#include "bioimage_cpp/detail/indexed_heap.hxx"
#include "bioimage_cpp/detail/union_find.hxx"
#include "bioimage_cpp/util/union_find.hxx"
#include "bioimage_cpp/graph/connected_components.hxx"
#include "bioimage_cpp/graph/multicut/objective.hxx"
#include "bioimage_cpp/graph/undirected_graph.hxx"
Expand Down Expand Up @@ -150,7 +150,7 @@ inline void initialize_dynamic_graph(
}

inline std::vector<std::uint64_t> labels_from_sets(
bioimage_cpp::detail::UnionFind &sets,
bioimage_cpp::util::UnionFind &sets,
const UndirectedGraph &graph
) {
return dense_labels_from_union_find(sets, graph.number_of_nodes());
Expand Down Expand Up @@ -199,7 +199,7 @@ inline void rename_neighbor(
// edge id appears at most once.
inline std::size_t merge_dynamic_nodes(
DynamicGraph &dynamic_graph,
bioimage_cpp::detail::UnionFind &sets,
bioimage_cpp::util::UnionFind &sets,
EdgeHeap &heap,
std::size_t u,
std::size_t v,
Expand Down
2 changes: 1 addition & 1 deletion include/bioimage_cpp/graph/multicut/greedy_additive.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace bioimage_cpp::graph::multicut {
// internal vectors are reset (not freed) between calls.
struct GreedyAdditiveWorkspace {
detail::DynamicGraph dynamic_graph;
bioimage_cpp::detail::UnionFind union_find{0};
bioimage_cpp::util::UnionFind union_find{0};
detail::EdgeHeap heap;

void reset(const UndirectedGraph &graph) {
Expand Down
2 changes: 1 addition & 1 deletion include/bioimage_cpp/graph/multicut/greedy_fixation.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ inline std::vector<std::uint64_t> greedy_fixation(
) {
validate_costs(graph, costs);
detail::DynamicGraph dynamic_graph(graph);
bioimage_cpp::detail::UnionFind sets(static_cast<std::size_t>(graph.number_of_nodes()));
bioimage_cpp::util::UnionFind sets(static_cast<std::size_t>(graph.number_of_nodes()));
detail::EdgeHeap heap;
detail::initialize_dynamic_graph(graph, costs, dynamic_graph, heap, true);

Expand Down
4 changes: 2 additions & 2 deletions include/bioimage_cpp/graph/multicut/kernighan_lin.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#include "bioimage_cpp/detail/edge_hash.hxx"
#include "bioimage_cpp/detail/indexed_heap.hxx"
#include "bioimage_cpp/detail/union_find.hxx"
#include "bioimage_cpp/util/union_find.hxx"
#include "bioimage_cpp/graph/multicut/objective.hxx"

#include <algorithm>
Expand Down Expand Up @@ -343,7 +343,7 @@ inline bool apply_joins(
if (number_of_clusters == 0) {
return false;
}
bioimage_cpp::detail::UnionFind sets(static_cast<std::size_t>(number_of_clusters));
bioimage_cpp::util::UnionFind sets(static_cast<std::size_t>(number_of_clusters));
bool any_join = false;
for (const auto &pair : pairs) {
if (pair.cut_weight <= epsilon) {
Expand Down
4 changes: 2 additions & 2 deletions include/bioimage_cpp/graph/mutex_watershed/clustering.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#include "bioimage_cpp/detail/mutex_storage.hxx"
#include "bioimage_cpp/detail/relabel.hxx"
#include "bioimage_cpp/detail/union_find.hxx"
#include "bioimage_cpp/util/union_find.hxx"
#include "bioimage_cpp/graph/undirected_graph.hxx"

#include <algorithm>
Expand Down Expand Up @@ -97,7 +97,7 @@ std::vector<std::uint64_t> mutex_watershed_clustering(
return first.index < second.index;
});

bioimage_cpp::detail::UnionFind sets(number_of_nodes);
bioimage_cpp::util::UnionFind sets(number_of_nodes);
MutexStorage mutexes(number_of_nodes);

for (const auto &edge : edge_order) {
Expand Down
4 changes: 2 additions & 2 deletions include/bioimage_cpp/graph/mutex_watershed/semantic.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include "bioimage_cpp/detail/mutex_storage.hxx"
#include "bioimage_cpp/detail/relabel.hxx"
#include "bioimage_cpp/detail/semantic_labels.hxx"
#include "bioimage_cpp/detail/union_find.hxx"
#include "bioimage_cpp/util/union_find.hxx"
#include "bioimage_cpp/graph/undirected_graph.hxx"

#include <algorithm>
Expand Down Expand Up @@ -144,7 +144,7 @@ SemanticMutexWatershedResult semantic_mutex_watershed_clustering(
return first.index < second.index;
});

bioimage_cpp::detail::UnionFind sets(number_of_nodes);
bioimage_cpp::util::UnionFind sets(number_of_nodes);
MutexStorage mutexes(number_of_nodes);
SemanticLabeling semantic_labels(number_of_nodes, -1);

Expand Down
4 changes: 2 additions & 2 deletions include/bioimage_cpp/segmentation/mutex_watershed.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include "bioimage_cpp/array_view.hxx"
#include "bioimage_cpp/detail/grid.hxx"
#include "bioimage_cpp/detail/mutex_storage.hxx"
#include "bioimage_cpp/detail/union_find.hxx"
#include "bioimage_cpp/util/union_find.hxx"

#include <algorithm>
#include <cstddef>
Expand Down Expand Up @@ -100,7 +100,7 @@ void mutex_watershed_grid(
return first.weight > second.weight;
});

detail::UnionFind sets(static_cast<std::size_t>(number_of_nodes));
bioimage_cpp::util::UnionFind sets(static_cast<std::size_t>(number_of_nodes));
MutexStorage mutexes(static_cast<std::size_t>(number_of_nodes));

for (const auto &edge : edge_order) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include "bioimage_cpp/detail/grid.hxx"
#include "bioimage_cpp/detail/mutex_storage.hxx"
#include "bioimage_cpp/detail/semantic_labels.hxx"
#include "bioimage_cpp/detail/union_find.hxx"
#include "bioimage_cpp/util/union_find.hxx"

#include <algorithm>
#include <cstddef>
Expand Down Expand Up @@ -132,7 +132,7 @@ void semantic_mutex_watershed_grid(
return first.weight > second.weight;
});

detail::UnionFind sets(static_cast<std::size_t>(number_of_nodes));
bioimage_cpp::util::UnionFind sets(static_cast<std::size_t>(number_of_nodes));
MutexStorage mutexes(static_cast<std::size_t>(number_of_nodes));
SemanticLabeling semantic_labels(static_cast<std::size_t>(number_of_nodes), -1);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include <utility>
#include <vector>

namespace bioimage_cpp::detail {
namespace bioimage_cpp::util {

// Disjoint-set / union-find with path compression and union-by-rank.
//
Expand Down Expand Up @@ -85,4 +85,4 @@ private:
std::vector<std::uint64_t> ranks_;
};

} // namespace bioimage_cpp::detail
} // namespace bioimage_cpp::util
2 changes: 2 additions & 0 deletions src/bindings/module.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "graph.hxx"
#include "ground_truth.hxx"
#include "segmentation.hxx"
#include "util.hxx"
#include "utils.hxx"

#include <nanobind/nanobind.h>
Expand All @@ -18,5 +19,6 @@ NB_MODULE(_core, m) {
bioimage_cpp::bindings::bind_graph(m);
bioimage_cpp::bindings::bind_ground_truth(m);
bioimage_cpp::bindings::bind_segmentation(m);
bioimage_cpp::bindings::bind_util(m);
bioimage_cpp::bindings::bind_utils(m);
}
Loading
Loading