Skip to content

Commit 58ce8fb

Browse files
Add semantic MWS comparison scripts and update migration guide
1 parent 3f9e62c commit 58ce8fb

7 files changed

Lines changed: 1119 additions & 0 deletions

File tree

MIGRATION_GUIDE.md

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -958,6 +958,142 @@ Notes:
958958
partitions. See `development/graph/check_mutex_clustering.py` for a
959959
comparison harness.
960960

961+
### Semantic mutex watershed
962+
963+
`bioimage-cpp` mirrors affogato's two semantic-mutex-watershed entry points
964+
`compute_semantic_mws_segmentation` for affinity volumes and
965+
`compute_semantic_mws_clustering` for an arbitrary graph. Both extend the
966+
regular mutex watershed with a third edge type: per-pixel (or per-node)
967+
"semantic edges" that tag each cluster with a class id. Two clusters that
968+
have been tagged with different class ids cannot subsequently merge.
969+
970+
#### Grid-based semantic mutex watershed (affinity volumes)
971+
972+
Affogato:
973+
974+
```python
975+
from affogato.segmentation import compute_semantic_mws_segmentation
976+
977+
labels, semantic_labels = compute_semantic_mws_segmentation(
978+
weights,
979+
offsets,
980+
number_of_attractive_channels=3,
981+
strides=[1, 1, 1],
982+
)
983+
```
984+
985+
bioimage-cpp:
986+
987+
```python
988+
import bioimage_cpp as bic
989+
990+
labels, semantic_labels = bic.segmentation.semantic_mutex_watershed(
991+
weights,
992+
offsets,
993+
number_of_attractive_channels=3,
994+
strides=[1, 1, 1],
995+
)
996+
```
997+
998+
Channel layout (identical to affogato):
999+
1000+
- Channels `[0, number_of_attractive_channels)` are attractive grid edges.
1001+
- Channels `[number_of_attractive_channels, len(offsets))` are mutex grid
1002+
edges.
1003+
- Channels `[len(offsets), affinities.shape[0])` are per-semantic-class
1004+
affinities; channel `len(offsets) + c` scores how strongly each pixel
1005+
belongs to class `c`.
1006+
1007+
Important migration notes:
1008+
1009+
- Inputs must represent 2D or 3D grids with shapes `(channels, y, x)` or
1010+
`(channels, z, y, x)` and `channels > len(offsets)` (use
1011+
`bic.segmentation.mutex_watershed` if there are no semantic channels).
1012+
- Supported affinity dtypes are `float32` and `float64`.
1013+
- Returned `labels` are `uint64`, consecutive, and 1-based for foreground
1014+
pixels (matching the regular grid-based mutex watershed). `semantic_labels`
1015+
is `int64` with `-1` reserved for clusters that received no class
1016+
assignment.
1017+
- A boolean `mask` may be passed. Edges touching `False` pixels are
1018+
ignored. Masked pixels are set to label `0` in `labels` and to the
1019+
`mask_label` parameter (default `0`) in `semantic_labels`.
1020+
- `strides` and `randomized_strides` follow the same convention as the
1021+
regular grid mutex watershed (mutex channels only; attractive channels
1022+
are always kept).
1023+
1024+
#### Graph-based semantic mutex watershed
1025+
1026+
Affogato:
1027+
1028+
```python
1029+
from affogato.segmentation import compute_semantic_mws_clustering
1030+
1031+
labels, semantic_labels = compute_semantic_mws_clustering(
1032+
number_of_nodes,
1033+
uvs.astype(np.uint64),
1034+
mutex_uvs.astype(np.uint64),
1035+
semantic_node_classes.astype(np.uint64),
1036+
weights.astype(np.float32),
1037+
mutex_weights.astype(np.float32),
1038+
semantic_weights.astype(np.float32),
1039+
)
1040+
```
1041+
1042+
bioimage-cpp:
1043+
1044+
```python
1045+
import bioimage_cpp as bic
1046+
1047+
graph = bic.graph.UndirectedGraph.from_edges(number_of_nodes, uvs)
1048+
labels, semantic_labels = bic.graph.semantic_mutex_watershed_clustering(
1049+
graph,
1050+
weights,
1051+
mutex_uvs,
1052+
mutex_weights,
1053+
semantic_node_classes,
1054+
semantic_weights,
1055+
)
1056+
```
1057+
1058+
Input format mirrors `mutex_watershed_clustering` plus two extra arrays:
1059+
1060+
- `semantic_node_classes` is an `(n_semantic, 2)` `uint64` table. Column 0
1061+
is a node id and column 1 is the semantic class id (a non-negative
1062+
integer interpreted as `int64` internally).
1063+
- `semantic_weights` is a 1D `float32`/`float64` array of length
1064+
`n_semantic` giving one weight per `(node, class)` candidate.
1065+
1066+
Notes:
1067+
1068+
- All three weight arrays (`weights`, `mutex_weights`, `semantic_weights`)
1069+
must have the same floating dtype, or all three are promoted to
1070+
`float64`.
1071+
- Output `labels` are dense `uint64` ids in `0 .. number_of_clusters - 1`
1072+
(first-occurrence order, matching the regular graph mutex watershed —
1073+
*not* the 1-based foreground labels produced by the grid variant).
1074+
`semantic_labels` is `int64` with `-1` for unassigned clusters.
1075+
- Accepts both `UndirectedGraph` and `RegionAdjacencyGraph`.
1076+
1077+
#### Divergence from affogato
1078+
1079+
The bioimage-cpp port fixes a missing `merge_semantic_labels` call on
1080+
attractive merges in affogato's graph kernel (`compute_semantic_mws_clustering`):
1081+
without that call, a node that has been tagged with a class can have its
1082+
tag dropped when it later becomes the non-root of a merge. Affogato's
1083+
array kernel additionally invokes `boost::disjoint_sets::link(u, v)` on
1084+
raw node ids rather than their roots, which corrupts the union-find tree
1085+
on multi-class inputs and over-fragments the result. The unit-test
1086+
problems shipped with affogato do not exercise these paths heavily, so
1087+
this only shows up on realistic multi-class data.
1088+
1089+
For most inputs the two implementations agree. On dense multi-class
1090+
inputs they may not; the development scripts under
1091+
`development/segmentation/check_semantic_mutex_watershed_{2d,3d}.py` and
1092+
`development/graph/check_semantic_mutex_clustering.py` print VI/ARI
1093+
partition metrics and a semantic-label match fraction so the deviation is
1094+
measurable. The bioimage-cpp partitions match an independent Python
1095+
reference implementation of the algorithm.
1096+
9611097
## Dictionary-Based Relabeling
9621098

9631099
If you used a small helper to apply a dictionary to an integer label array, use
File renamed without changes.

0 commit comments

Comments
 (0)