Skip to content

Commit 3174610

Browse files
Expose UFD to python (#24)
1 parent ba4a708 commit 3174610

23 files changed

Lines changed: 393 additions & 30 deletions

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ nanobind_add_module(_core
1818
src/bindings/graph.cxx
1919
src/bindings/ground_truth.cxx
2020
src/bindings/segmentation.cxx
21+
src/bindings/util.cxx
2122
src/bindings/utils.cxx
2223
src/cpp/segmentation/mutex_watershed.cxx
2324
src/cpp/segmentation/semantic_mutex_watershed.cxx

MIGRATION_GUIDE.md

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import bioimage_cpp as bic
1717

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

2223
## Blocking
2324

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

1098+
## Union-Find
1099+
1100+
Nifty exposes a disjoint-set / union-find structure as `nifty.ufd.ufd`.
1101+
`bioimage-cpp` provides the same primitive under `bic.util`.
1102+
1103+
Nifty:
1104+
1105+
```python
1106+
import nifty.ufd as nufd
1107+
import numpy as np
1108+
1109+
uf = nufd.ufd(5)
1110+
uf.merge(0, 1)
1111+
uf.merge(np.array([[2, 3], [3, 4]], dtype="uint64"))
1112+
roots = uf.find(np.array([0, 1, 2, 3, 4], dtype="uint64"))
1113+
labels = uf.elementLabeling()
1114+
```
1115+
1116+
bioimage-cpp:
1117+
1118+
```python
1119+
import bioimage_cpp as bic
1120+
import numpy as np
1121+
1122+
uf = bic.util.UnionFind(5)
1123+
uf.merge(0, 1)
1124+
uf.merge(np.array([[2, 3], [3, 4]], dtype=np.uint64))
1125+
roots = uf.find(np.array([0, 1, 2, 3, 4], dtype=np.uint64))
1126+
labels = uf.element_labeling()
1127+
```
1128+
1129+
Method mapping:
1130+
1131+
| nifty-style name | bioimage-cpp name |
1132+
| --- | --- |
1133+
| `find(node)` / `find(array)` | `find(node)` / `find(array)` |
1134+
| `merge(u, v)` / `merge(array)` | `merge(u, v)` / `merge(array)` |
1135+
| `elementLabeling` | `element_labeling` |
1136+
| `numberOfElements` | `size` (property) |
1137+
1138+
Notes:
1139+
1140+
- The constructor takes a single `size` argument; all elements start as
1141+
singletons.
1142+
- Scalar `find`/`merge` accept Python integers and return Python integers.
1143+
- Bulk `find(nodes)` accepts a 1D `uint64` array and returns a 1D `uint64`
1144+
array of roots of the same length.
1145+
- Bulk `merge(edges)` accepts an `(N, 2)` `uint64` array of node-pair edges
1146+
and applies the merges in row order.
1147+
- `element_labeling()` returns a `uint64` array of length `size`, each entry
1148+
the (path-compressed) root of that element. Use this when you want the
1149+
final labeling as one array rather than via repeated `find` calls.
1150+
- `merge_to(stable, removed)` is also available: it forces `stable`'s root
1151+
to survive the union regardless of rank.
1152+
- `reset(n)` reinitialises the structure to `n` singletons, reusing
1153+
capacity where possible.
1154+
- The GIL is released around bulk operations, so multiple threads can run
1155+
bulk merges on independent `UnionFind` instances in parallel.
1156+
10971157
## Dictionary-Based Relabeling
10981158

10991159
If you used a small helper to apply a dictionary to an integer label array, use

include/bioimage_cpp/graph/connected_components.hxx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#pragma once
22

3-
#include "bioimage_cpp/detail/union_find.hxx"
3+
#include "bioimage_cpp/util/union_find.hxx"
44
#include "bioimage_cpp/graph/undirected_graph.hxx"
55

66
#include <cstddef>
@@ -11,7 +11,7 @@
1111
namespace bioimage_cpp::graph {
1212

1313
inline std::vector<std::uint64_t> dense_labels_from_union_find(
14-
detail::UnionFind &sets,
14+
bioimage_cpp::util::UnionFind &sets,
1515
const std::uint64_t number_of_nodes
1616
) {
1717
std::unordered_map<std::uint64_t, std::uint64_t> relabeling;
@@ -31,7 +31,7 @@ inline std::vector<std::uint64_t> connected_components(
3131
const UndirectedGraph &graph,
3232
const std::uint8_t *edge_mask = nullptr
3333
) {
34-
detail::UnionFind sets(static_cast<std::size_t>(graph.number_of_nodes()));
34+
bioimage_cpp::util::UnionFind sets(static_cast<std::size_t>(graph.number_of_nodes()));
3535
for (std::uint64_t edge = 0; edge < graph.number_of_edges(); ++edge) {
3636
if (edge_mask != nullptr && edge_mask[static_cast<std::size_t>(edge)] == 0) {
3737
continue;

include/bioimage_cpp/graph/detail/fusion_contract.hxx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#pragma once
22

3-
#include "bioimage_cpp/detail/union_find.hxx"
3+
#include "bioimage_cpp/util/union_find.hxx"
44
#include "bioimage_cpp/graph/undirected_graph.hxx"
55

66
#include <algorithm>
@@ -62,7 +62,7 @@ inline AgreementContraction contract_by_agreement(
6262
);
6363
}
6464

65-
bioimage_cpp::detail::UnionFind sets(static_cast<std::size_t>(number_of_nodes));
65+
bioimage_cpp::util::UnionFind sets(static_cast<std::size_t>(number_of_nodes));
6666
for (std::uint64_t edge = 0; edge < number_of_edges; ++edge) {
6767
const auto uv = graph.uv(edge);
6868
const auto u = static_cast<std::size_t>(uv.first);

include/bioimage_cpp/graph/edge_weighted_watershed.hxx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#pragma once
22

3-
#include "bioimage_cpp/detail/union_find.hxx"
3+
#include "bioimage_cpp/util/union_find.hxx"
44
#include "bioimage_cpp/graph/undirected_graph.hxx"
55

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

52-
bioimage_cpp::detail::UnionFind sets(static_cast<std::size_t>(number_of_nodes));
52+
bioimage_cpp::util::UnionFind sets(static_cast<std::size_t>(number_of_nodes));
5353

5454
constexpr SeedT zero{0};
5555
for (const auto &item : scratch.sort_buffer) {

include/bioimage_cpp/graph/lifted_multicut/detail.hxx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#pragma once
22

33
#include "bioimage_cpp/detail/indexed_heap.hxx"
4-
#include "bioimage_cpp/detail/union_find.hxx"
4+
#include "bioimage_cpp/util/union_find.hxx"
55
#include "bioimage_cpp/graph/connected_components.hxx"
66
#include "bioimage_cpp/graph/undirected_graph.hxx"
77

@@ -161,7 +161,7 @@ inline void rename_neighbor(
161161
// pops base edges off the heap).
162162
inline std::size_t merge_dynamic_nodes(
163163
DynamicGraph &dynamic_graph,
164-
bioimage_cpp::detail::UnionFind &sets,
164+
bioimage_cpp::util::UnionFind &sets,
165165
EdgeHeap &heap,
166166
std::size_t u,
167167
std::size_t v
@@ -258,7 +258,7 @@ inline std::size_t merge_dynamic_nodes(
258258
}
259259

260260
inline std::vector<std::uint64_t> labels_from_sets(
261-
bioimage_cpp::detail::UnionFind &sets,
261+
bioimage_cpp::util::UnionFind &sets,
262262
const UndirectedGraph &graph
263263
) {
264264
return dense_labels_from_union_find(sets, graph.number_of_nodes());

include/bioimage_cpp/graph/lifted_multicut/greedy_additive.hxx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace bioimage_cpp::graph::lifted_multicut {
1313
// Reusable scratch state for `lifted_greedy_additive`.
1414
struct GreedyAdditiveWorkspace {
1515
detail::DynamicGraph dynamic_graph;
16-
bioimage_cpp::detail::UnionFind union_find{0};
16+
bioimage_cpp::util::UnionFind union_find{0};
1717
detail::EdgeHeap heap;
1818

1919
void reset(const UndirectedGraph &lifted_graph) {

include/bioimage_cpp/graph/lifted_multicut/kernighan_lin.hxx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#include "bioimage_cpp/detail/edge_hash.hxx"
44
#include "bioimage_cpp/detail/indexed_heap.hxx"
55
#include "bioimage_cpp/detail/profile.hxx"
6-
#include "bioimage_cpp/detail/union_find.hxx"
6+
#include "bioimage_cpp/util/union_find.hxx"
77
#include "bioimage_cpp/graph/connected_components.hxx"
88
#include "bioimage_cpp/graph/lifted_multicut/objective.hxx"
99

include/bioimage_cpp/graph/multicut/detail.hxx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#pragma once
22

33
#include "bioimage_cpp/detail/indexed_heap.hxx"
4-
#include "bioimage_cpp/detail/union_find.hxx"
4+
#include "bioimage_cpp/util/union_find.hxx"
55
#include "bioimage_cpp/graph/connected_components.hxx"
66
#include "bioimage_cpp/graph/multicut/objective.hxx"
77
#include "bioimage_cpp/graph/undirected_graph.hxx"
@@ -150,7 +150,7 @@ inline void initialize_dynamic_graph(
150150
}
151151

152152
inline std::vector<std::uint64_t> labels_from_sets(
153-
bioimage_cpp::detail::UnionFind &sets,
153+
bioimage_cpp::util::UnionFind &sets,
154154
const UndirectedGraph &graph
155155
) {
156156
return dense_labels_from_union_find(sets, graph.number_of_nodes());
@@ -199,7 +199,7 @@ inline void rename_neighbor(
199199
// edge id appears at most once.
200200
inline std::size_t merge_dynamic_nodes(
201201
DynamicGraph &dynamic_graph,
202-
bioimage_cpp::detail::UnionFind &sets,
202+
bioimage_cpp::util::UnionFind &sets,
203203
EdgeHeap &heap,
204204
std::size_t u,
205205
std::size_t v,

include/bioimage_cpp/graph/multicut/greedy_additive.hxx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace bioimage_cpp::graph::multicut {
1515
// internal vectors are reset (not freed) between calls.
1616
struct GreedyAdditiveWorkspace {
1717
detail::DynamicGraph dynamic_graph;
18-
bioimage_cpp::detail::UnionFind union_find{0};
18+
bioimage_cpp::util::UnionFind union_find{0};
1919
detail::EdgeHeap heap;
2020

2121
void reset(const UndirectedGraph &graph) {

0 commit comments

Comments
 (0)