Skip to content

Commit 8087880

Browse files
Add migration guide
1 parent 82d05a6 commit 8087880

3 files changed

Lines changed: 300 additions & 0 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,5 @@ venv/
2727

2828
# Data
2929
*.h5
30+
31+
CLAUDE.md

AGENTS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,11 @@ Public functions should have concise docstrings documenting:
435435
- background-label behavior, if relevant
436436
- axis and coordinate conventions
437437

438+
When adding or changing public functionality, update `MIGRATION_GUIDE.md` so
439+
users migrating from `nifty`, `affogato`, or related packages know the
440+
corresponding `bioimage-cpp` API, important behavioral differences, and any
441+
intentional improvements.
442+
438443
## What an agent should do when modifying this repository
439444

440445
When adding or changing code:

MIGRATION_GUIDE.md

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
# Migration Guide
2+
3+
This guide explains how to migrate code that used selected `nifty` or
4+
`affogato` functionality to `bioimage-cpp`.
5+
6+
`bioimage-cpp` is not a drop-in compatibility layer. The package keeps a
7+
smaller, NumPy-first API with Python-style method names, explicit dtype support,
8+
and no I/O dependencies in the C++ core.
9+
10+
## Imports
11+
12+
Use:
13+
14+
```python
15+
import bioimage_cpp as bic
16+
```
17+
18+
Graph functionality is under `bic.graph`, segmentation functionality is under
19+
`bic.segmentation`, and small utility functions are under `bic.utils`.
20+
21+
## Blocking
22+
23+
Nifty:
24+
25+
```python
26+
import nifty.tools as nt
27+
28+
blocking = nt.blocking([0, 0], [100, 80], [32, 32])
29+
block = blocking.getBlock(0)
30+
block_with_halo = blocking.getBlockWithHalo(0, [8, 8])
31+
```
32+
33+
bioimage-cpp:
34+
35+
```python
36+
import bioimage_cpp as bic
37+
38+
blocking = bic.Blocking([0, 0], [100, 80], [32, 32])
39+
block = blocking.get_block(0)
40+
block_with_halo = blocking.get_block_with_halo(0, [8, 8])
41+
```
42+
43+
Name changes:
44+
45+
| nifty-style name | bioimage-cpp name |
46+
| --- | --- |
47+
| `roiBegin` | `roi_begin` |
48+
| `roiEnd` | `roi_end` |
49+
| `blockShape` | `block_shape` |
50+
| `blockShift` | `block_shift` |
51+
| `blocksPerAxis` | `blocks_per_axis` |
52+
| `numberOfBlocks` | `number_of_blocks` |
53+
| `blockGridPosition` | `block_grid_position` |
54+
| `getNeighborId` | `get_neighbor_id` |
55+
| `getBlock` | `get_block` |
56+
| `getBlockWithHalo` | `get_block_with_halo` |
57+
| `addHalo` | `add_halo` |
58+
| `coordinatesToBlockId` | `coordinates_to_block_id` |
59+
| `getBlockIdsInBoundingBox` | `get_block_ids_in_bounding_box` |
60+
| `getBlockIdsOverlappingBoundingBox` | `get_block_ids_overlapping_bounding_box` |
61+
| `getLocalOverlaps` | `get_local_overlaps` |
62+
| `getBlockIdsInSlice` | `get_block_ids_in_slice` |
63+
64+
Intentional improvements over nifty:
65+
66+
- `coordinates_to_block_id` accounts for both `roi_begin` and `block_shift`.
67+
- `get_block_ids_overlapping_bounding_box` works for any dimensionality, not
68+
only 3D.
69+
- Bounding boxes use NumPy-style half-open intervals: `[begin, end)`.
70+
- `get_local_overlaps` returns `None` if blocks do not overlap; otherwise it
71+
returns `(begin_a, end_a, begin_b, end_b)` in local coordinates.
72+
73+
## Undirected Graphs
74+
75+
Nifty:
76+
77+
```python
78+
import nifty.graph as ng
79+
80+
graph = ng.undirectedGraph(4)
81+
edge_id = graph.insertEdge(0, 1)
82+
uvs = graph.uvIds()
83+
```
84+
85+
bioimage-cpp:
86+
87+
```python
88+
import bioimage_cpp as bic
89+
90+
graph = bic.graph.UndirectedGraph(4)
91+
edge_id = graph.insert_edge(0, 1)
92+
uvs = graph.uv_ids()
93+
```
94+
95+
The convenience constructor is:
96+
97+
```python
98+
graph = bic.graph.undirected_graph(4)
99+
graph = bic.graph.UndirectedGraph.from_edges(4, [[0, 1], [1, 2]])
100+
```
101+
102+
Important differences:
103+
104+
- Nodes are fixed at construction and have ids `0 .. number_of_nodes - 1`.
105+
- Re-inserting an existing undirected edge returns the existing edge id.
106+
- Bulk methods accept array-like inputs and return NumPy arrays.
107+
- Python-style names are preferred. A few nifty-style aliases are still present
108+
on `UndirectedGraph` for convenience, but new code should use snake_case.
109+
110+
Common method/property mapping:
111+
112+
| nifty-style name | bioimage-cpp name |
113+
| --- | --- |
114+
| `numberOfNodes` | `number_of_nodes` |
115+
| `numberOfEdges` | `number_of_edges` |
116+
| `nodeIdUpperBound` | `node_id_upper_bound` |
117+
| `edgeIdUpperBound` | `edge_id_upper_bound` |
118+
| `insertEdge` | `insert_edge` |
119+
| `insertEdges` | `insert_edges` |
120+
| `findEdge` | `find_edge` |
121+
| `findEdges` | `find_edges` |
122+
| `uvIds` | `uv_ids` |
123+
| `nodeAdjacency` | `node_adjacency` |
124+
| `serializationSize` | `serialization_size` |
125+
| `extractSubgraphFromNodes` | `extract_subgraph_from_nodes` |
126+
| `edgesFromNodeList` | `edges_from_node_list` |
127+
128+
## Region Adjacency Graphs
129+
130+
Nifty:
131+
132+
```python
133+
import nifty.graph.rag as nrag
134+
135+
rag = nrag.gridRag(labels)
136+
uvs = rag.uvIds()
137+
```
138+
139+
bioimage-cpp:
140+
141+
```python
142+
import bioimage_cpp as bic
143+
144+
rag = bic.graph.region_adjacency_graph(labels)
145+
uvs = rag.uv_ids()
146+
```
147+
148+
Notes:
149+
150+
- Supported label dtypes are `uint32`, `uint64`, `int32`, and `int64`.
151+
- Labels must be 2D or 3D.
152+
- Negative signed labels are rejected.
153+
- Nodes correspond to label ids from `0` to `labels.max()`.
154+
- Edge ids are deterministic; RAG edges are sorted lexicographically by
155+
endpoint ids.
156+
- Non-contiguous labels are copied to contiguous memory before entering C++.
157+
158+
## RAG Boundary and Affinity Features
159+
160+
Nifty has RAG feature helpers such as `accumulateEdgeMeanAndLength`,
161+
`accumulateEdgeStandartFeatures`, and affinity feature accumulation helpers.
162+
In `bioimage-cpp`, these are exposed as explicit NumPy-returning functions.
163+
164+
Simple edge-map features:
165+
166+
```python
167+
rag = bic.graph.region_adjacency_graph(labels)
168+
features = bic.graph.edge_map_features(rag, labels, edge_map)
169+
```
170+
171+
The columns are:
172+
173+
```python
174+
bic.graph.SIMPLE_EDGE_FEATURE_NAMES
175+
# ("mean", "size")
176+
```
177+
178+
Complex edge-map features:
179+
180+
```python
181+
features = bic.graph.edge_map_features_complex(rag, labels, edge_map)
182+
```
183+
184+
The columns are:
185+
186+
```python
187+
bic.graph.COMPLEX_EDGE_FEATURE_NAMES
188+
# ("mean", "median", "std", "min", "max", "p5", "p10",
189+
# "p25", "p75", "p90", "p95", "size")
190+
```
191+
192+
Affinity features:
193+
194+
```python
195+
features = bic.graph.affinity_features(
196+
rag,
197+
labels,
198+
affinities,
199+
offsets=[[0, 1], [1, 0]],
200+
)
201+
```
202+
203+
Complex affinity features:
204+
205+
```python
206+
features = bic.graph.affinity_features_complex(
207+
rag,
208+
labels,
209+
affinities,
210+
offsets=[[0, 1], [1, 0]],
211+
)
212+
```
213+
214+
Notes:
215+
216+
- `edge_map` must have the same shape as `labels`.
217+
- `affinities` must have shape `(channels, *labels.shape)`.
218+
- `offsets` must have one offset per channel in NumPy axis order.
219+
- Feature arrays use `float64` output.
220+
- `number_of_threads=0` uses the library default; pass a positive integer for a
221+
fixed thread count.
222+
223+
## Mutex Watershed
224+
225+
Affogato:
226+
227+
```python
228+
from affogato.segmentation import compute_mws_segmentation
229+
230+
seg = compute_mws_segmentation(
231+
weights,
232+
offsets,
233+
number_of_attractive_channels=3,
234+
strides=[1, 1, 1],
235+
)
236+
```
237+
238+
bioimage-cpp:
239+
240+
```python
241+
import bioimage_cpp as bic
242+
243+
seg = bic.segmentation.mutex_watershed(
244+
weights,
245+
offsets,
246+
number_of_attractive_channels=3,
247+
strides=[1, 1, 1],
248+
)
249+
```
250+
251+
Important migration notes:
252+
253+
- `bioimage-cpp` expects the first `number_of_attractive_channels` channels to
254+
be attractive merge-edge weights and the remaining channels to be mutex-edge
255+
weights.
256+
- Supported affinity dtypes are `float32` and `float64`.
257+
- Inputs must represent 2D or 3D grids with shapes `(channels, y, x)` or
258+
`(channels, z, y, x)`.
259+
- Non-contiguous affinity arrays are copied to contiguous memory.
260+
- `strides` sub-sample mutex edges only; attractive edges are always kept.
261+
- `randomized_strides=True` uses NumPy's global random state, so existing
262+
`np.random.seed(...)` workflows remain deterministic.
263+
- A boolean `mask` may be passed. Edges touching `False` pixels are ignored and
264+
masked pixels are set to label `0`.
265+
- Output labels are `uint64`, consecutive, and 1-based for foreground pixels.
266+
267+
## Dictionary-Based Relabeling
268+
269+
If you used a small helper to apply a dictionary to an integer label array, use
270+
`take_dict`:
271+
272+
```python
273+
labels = np.array([1, 3, 2, 1], dtype=np.uint64)
274+
relabeling = {1: 10, 2: 20, 3: 30}
275+
276+
out = bic.utils.take_dict(relabeling, labels)
277+
```
278+
279+
Notes:
280+
281+
- Supported input dtypes are `uint32`, `uint64`, `int32`, and `int64`.
282+
- Output has the same shape and dtype as the input array.
283+
- Every value in the input must be present in the mapping.
284+
- Non-contiguous inputs are copied before entering C++.
285+
286+
## I/O and Build Dependencies
287+
288+
`bioimage-cpp` intentionally does not replace nifty or affogato I/O helpers.
289+
Load TIFF, HDF5, zarr, N5, OME-NGFF, and related formats with existing Python
290+
libraries, then pass NumPy arrays to `bioimage-cpp`.
291+
292+
The package is designed for small PyPI wheels and does not depend on nifty,
293+
vigra, HDF5, z5, xtensor, pybind11, or other large C++ libraries.

0 commit comments

Comments
 (0)