vol_label_shape_stats — VOLCOLOR measure op

• Data kinds: labels → table

• Call: import fullseye as fs; fs.ledger.vol_label_shape_stats(labels, spacing=None, shape: 'bool' = True) (to call the implementation directly, import volcolor; volcolor.vol_label_shape_stats(labels, spacing=None, shape: 'bool' = True); from the registry, opsvolcolor.get("vol_label_shape_stats"))

Usage

Per-component quantities that can be had in linear time (volume, centroid, box, PCA shape indices).

A sister of volops.vol_region_props, but with a different purpose:

• That one produces surface_area and Wadell sphericity. To do so it runs marching cubes per component, so a Python loop runs in proportion to the number of components (it gets heavy fast on the pathological input of 1 label per 1 voxel).

• This one is limited to quantities obtainable from weighted sums of np.bincount alone. Per 1 label array, 9 calls of bincount = O(N + n). Measured (connectivity=26, a checkerboard of 1 label per 1 voxel = pathological input):

16 ** 3 = 4096 voxels / 512 components = 0.0037 s, 32 ** 3 = 32768 / 4096 =

0.0350 s, 64 ** 3 = 262144 / 32768 = 0.2556 s, 128 ** 3 = 2097152 / 262144 =

2.3122 s. For every 8x voxels, 9.5 / 7.3 / 9.0x (quadratic would be 64x),

about 1.1 microseconds/voxel throughout. Colouring the same 128 ** 3 takes 0.0357 s.

(For reference: volops.vol_region_props(surface="faces") takes on the same 16 ** 3

0.0121 s -- 3.5x, because of the per-component Python loop. The default surface="auto", which uses

marching cubes, is heavier still.)

The 5 of label voxel_count volume centroid bbox have the same definition and the same values as volops.vol_region_props (pinned with exact equality by tests/test_volcolor.py::test_stats_agree_with_vol_region_props_exactly ). bbox is (z0, z1, y0, y1, x0, x1) with exclusive upper bounds.

Returned in addition:

centroid_mm (z, y, x) physical coordinates (same as voxel without spacing) ·

extent physical size of the bbox (dz, dy, dx) · equivalent_diameter

(6V/pi)**(1/3) · touches_border whether the bbox touches the volume edge ·

principal_extent square roots of the covariance eigenvalues (s1 >= s2 >= s3) (physical units) ·

linearity (l1-l2)/l1 · planarity (l2-l3)/l1 · isotropy

l3/l1 · elongation sqrt(l1/l2).

elongation can be infinite (contract): for l1 > 0 and l2 == 0, i.e. a component that is a perfectly straight line 1 voxel thick, it returns inf. This is not an accident of dividing by 0 but the fact that "there is no extent along the 2nd axis"; rounding it would silently swap the ranking by slenderness. A single voxel (l1 == 0) is an isotropic point, so 1.0.

Forgetting to pass spacing changes both the numbers and the conclusion. Measured -- a sphere of radius 6 mm sampled on an anisotropic grid of spacing = (3.0, 1.0, 1.0) mm (only z is 3x coarser) gives 293 voxels. The closed-form ground truth is 4/3 pi 6**3 = 904.78 mm**3:

• with spacing = 879.0 mm ** 3 (error -2.85 %), isotropy = 0.7349 (nearly isotropic = correct).

• without spacing = 293.0 (voxel units; read as mm ** 3 the error is -67.62 %), isotropy = 0.0817 -- the sphere looks like a "plate". Ignoring anisotropy breaks the shape indices, and they come back as finite, plausible-looking values.

• The equivalent diameter is also off by 1.44x: 11.8849 mm vs 8.2406.

Numerical note: the 2nd moments are computed as E[x**2] - E[x]**2, so for components with large coordinates and small extent, loss of significant digits can occur. At the limit MAX_COLOR_VOXELS (up to ~2000 per 1 side), against E[x**2] ~ 4e6 the float64 relative precision 1e-16 = absolute 1e-10, leaving a margin of 10 digits relative to variances in voxel units. Volumes larger than that should be cut to an ROI before being passed (that is also one reason the limit exists).

Setting *shape* to False skips the covariance (6 of the 9 bincounts) and does not return the 5 items from principal_extent on -- when only a volume filter is needed, this reduces the O(N) temporary arrays to 1.

Returns list[dict] in ascending label order (only labels that exist -- even with gaps in the numbering, it does not return empty dicts up to labels.max()).

References (sample data, literature)

• Sample-data catalog (download URLs / licences) — 2-D uses skimage.data (BSD/public domain) plus synthetic images; 3-D lists download URLs for real data sources (Stanford, PDS, …).

• Operator provenance and references — the sources of the research/methods this op family came from.

• The canonical algorithm (author, year) and its uses are named in the family usage guide above.

Runnable examples (verified samples that actually call this op)

• poc_cold_chain_excursion — py -3.11 examples/poc_cold_chain_excursion.py

• voxel_labels_color — py -3.11 examples/voxel_labels_color.py

Ops the type connects to (they accept table as input)

—

Same category (measure)

vol_label_legend


*Provenance: volcolor.py — VOLCOLOR operator registry. This per-op note is generated by tools/opdocs.py md (do not hand-edit).*

© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.