@@ -490,6 +490,54 @@ Notes:
490490 endpoint ids.
491491- Non-contiguous labels are copied to contiguous memory before entering C++.
492492
493+ ### Distributed Region Adjacency Graphs and Features
494+
495+ For volumes too large to hold in memory, ` nifty.distributed ` builds a RAG and
496+ its edge features block by block and merges the results. ` bioimage-cpp ` provides
497+ the equivalent ** low-level primitives** under ` bic.graph.distributed ` ; the
498+ orchestration nifty bundles (iterating blocks, sizing halos, and serializing the
499+ per-block subgraphs/features to zarr/N5/HDF5) is intentionally left to the
500+ caller, since I/O and block scheduling belong in Python.
501+
502+ The primitives assume ** globally consistent labels** (a segment has the same id
503+ in every block — as after a stitched distributed watershed). A block owns the
504+ pixel-pairs whose reference pixel lies in its inner (non-halo) box, so the
505+ caller reads each block with a halo (≥1 on the forward faces for the region
506+ graph / an edge map; ≥ ` max |offset| ` per side for affinities) and passes the
507+ owned box as ` own_begin ` / ` own_shape ` (e.g. from ` bic.utils.Blocking ` 's
508+ ` get_block_with_halo(...).inner_block_local ` ).
509+
510+ ``` python
511+ d = bic.graph.distributed
512+
513+ # per block (labels read with a halo):
514+ edges = d.block_region_adjacency_edges(labels_block, own_begin, own_shape)
515+ block_edges, block_stats = d.block_edge_map_stats(labels_block, edge_map_block, own_begin, own_shape)
516+ block_edges, block_stats = d.block_affinity_stats(labels_block, aff_block, offsets, own_begin, own_shape)
517+
518+ # merge the graph, then build the global graph:
519+ global_edges = d.merge_edges([edges_block_0, edges_block_1, ... ])
520+ graph = bic.graph.UndirectedGraph.from_unique_edges(number_of_nodes, global_edges)
521+
522+ # fold per-block features onto the global edges, then finalize:
523+ acc = d.empty_edge_stats(graph.number_of_edges)
524+ for be, bs in per_block_stats:
525+ acc = d.merge_block_edge_stats(graph, acc, be, bs)
526+ features = d.finalize_edge_features(acc, compute_complex_features = True )
527+ ```
528+
529+ Notes:
530+
531+ - Blocked results reproduce the whole-volume ` region_adjacency_graph ` /
532+ ` features.*_features ` exactly for ` size ` , ` min ` and ` max ` , and to
533+ floating-point tolerance for ` mean ` / ` std ` (the running sums depend on thread
534+ count and merge order).
535+ - ** Median and percentiles are not distributable.** The distributed complex
536+ output is the moment subset ` [mean, std, min, max, size] ` — the corresponding
537+ columns of the in-core 12-column complex features.
538+ - Making per-block-local ids globally consistent (label stitching) is a separate
539+ step and is not part of these primitives.
540+
493541### Breadth-First Search
494542
495543Nifty has an internal ` BreadthFirstSearch ` template used during lifted-edge
0 commit comments