Skip to content

Commit 518f300

Browse files
Optimize label implementation
1 parent b52e44a commit 518f300

5 files changed

Lines changed: 626 additions & 84 deletions

File tree

MIGRATION_GUIDE.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,81 @@ unspecified.
14781478
labels = bic.segmentation.watershed(image, markers, mask=optional_mask)
14791479
```
14801480

1481+
### Connected-Components Labeling
1482+
1483+
`bioimage-cpp` provides pixel-grid connected-components labeling for 2D and
1484+
3D arrays, mirroring `skimage.measure.label`. Two non-background pixels share
1485+
a component iff there is a path of `connectivity`-neighbour steps between
1486+
them along which the input value is constant.
1487+
1488+
Skimage:
1489+
1490+
```python
1491+
from skimage.measure import label
1492+
1493+
labels = label(image, background=0, connectivity=None)
1494+
```
1495+
1496+
bioimage-cpp:
1497+
1498+
```python
1499+
import bioimage_cpp as bic
1500+
1501+
labels = bic.segmentation.label(image, background=0, connectivity=None)
1502+
```
1503+
1504+
Vigra has a closely related entry point on binary / labeled inputs:
1505+
1506+
```python
1507+
import vigra.analysis as va
1508+
1509+
labels = va.labelMultiArrayWithBackground(
1510+
image, neighborhood="direct", background_value=0,
1511+
)
1512+
```
1513+
1514+
`bic.segmentation.label` covers both cases — it labels equal-value runs (as
1515+
`skimage.measure.label` does), and for binary masks it agrees with vigra's
1516+
`labelMultiArrayWithBackground` partition. `neighborhood="direct"` maps to
1517+
`connectivity=1`, `neighborhood="indirect"` maps to `connectivity=image.ndim`.
1518+
1519+
Important migration notes:
1520+
1521+
- Supported input dtypes are `bool`, `uint8`, `uint16`, `uint32`, `uint64`,
1522+
`int32`, `int64`. Floating-point inputs are rejected. Non-contiguous
1523+
arrays are copied to contiguous memory.
1524+
- `connectivity` is an integer in `[1, image.ndim]`. `1` is orthogonal
1525+
neighbours only (4-connectivity in 2D, 6-connectivity in 3D);
1526+
`image.ndim` enables full diagonal connectivity (8-connectivity in 2D,
1527+
26-connectivity in 3D); `2` in 3D is 18-connectivity. `connectivity=None`
1528+
defaults to `image.ndim`, matching `skimage.measure.label`.
1529+
- `background` is the pixel value treated as background. Background pixels
1530+
stay `0` in the output; other equal-valued pixels start at label `1`.
1531+
- The output dtype is always `uint64`. `skimage.measure.label` returns
1532+
`intp`; cast if you need bit-for-bit dtype parity.
1533+
- Output labels are dense, start at `1`, and are assigned in row-major
1534+
first-occurrence order — same convention as skimage.
1535+
- Passing a `bool` array enables an internal fast path that skips
1536+
per-pixel value-equality compares. Convert `uint8` masks to `bool` first
1537+
if your data is binary.
1538+
- Only 2D and 3D inputs are supported in v1. `skimage.measure.label`
1539+
accepts arbitrary ndim; loop over slices externally if you need 4D+.
1540+
- `return_num=True` from `skimage.measure.label` is not provided. Use
1541+
`int(labels.max())` to get the component count.
1542+
1543+
Performance characteristics (single-threaded, against `skimage 0.25` and
1544+
`vigra 1.11`):
1545+
1546+
- On integer inputs (`uint8`/`uint16`/…), bioimage-cpp clearly beats both
1547+
skimage and vigra across the tested grid (2D 512²–2048², 3D 64³–128³, all
1548+
connectivities, binary and multi-value). Typical margin is **1.5×–3×**
1549+
faster than skimage and **2×–8×** faster than vigra.
1550+
- On `bool` inputs, skimage ships a separately tuned 2D kernel that is very
1551+
fast at large sizes. bioimage-cpp matches it at small/medium sizes and on
1552+
all 3D cases; at 2D 2048² the skimage-bool path is currently ahead by
1553+
roughly 1.7×. Convert to `uint8` to fall back onto the general path if
1554+
you need to win at every 2D size.
1555+
14811556
## Vigra / fastfilters
14821557

14831558
### Image Filters

0 commit comments

Comments
 (0)