Skip to content

Commit c524c99

Browse files
Improve implementation, tests, and benchmark
1 parent b1b5728 commit c524c99

8 files changed

Lines changed: 745 additions & 220 deletions

File tree

MIGRATION_GUIDE.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1905,6 +1905,8 @@ Important details:
19051905
numeric or boolean input dtype is accepted; complex inputs are rejected.
19061906
- `method="lewiner"` resolves ambiguous cases and is the default;
19071907
`method="lorensen"` selects the original 256-case algorithm.
1908+
- `spacing` accepts either one positive finite scalar for isotropic data or a
1909+
length-three `(z, y, x)` sequence.
19081910
- Normals and local-range values follow scikit-image semantics. As in
19091911
scikit-image, `gradient_direction` reverses face winding without changing
19101912
normals, and anisotropic spacing scales vertices without transforming
@@ -1914,8 +1916,12 @@ Important details:
19141916
foreground-positive segmentation masks. The iso-level is determined from
19151917
the original unpadded volume.
19161918
- Spacing entries must be positive and finite, and faces remain `int32` when
1917-
degenerate faces are removed. These validations/dtype choices are
1918-
intentional differences from scikit-image edge cases.
1919+
degenerate faces are removed. Duplicate vertices in a collapsed face are
1920+
merged transitively with the first vertex as representative, and faces that
1921+
still collapse after remapping are discarded. This guarantees in-range face
1922+
indices and intentionally avoids a rare scikit-image negative-index
1923+
remapping quirk. These validation and cleanup choices are intentional
1924+
differences from scikit-image edge cases.
19191925

19201926
See `development/mesh/check_marching_cubes.py` for reference comparisons and
19211927
`development/mesh/benchmark_marching_cubes.py` for reproducible timings.

development/mesh/PERFORMANCE_NOTES.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,65 @@
11
# Marching cubes performance
22

3+
## Codex-Sol / Claude consolidation (2026-07-11)
4+
5+
The consolidation pass kept the Codex-Sol float32 MC33 kernel, exact
6+
scikit-image normal/value semantics, and zero-copy NumPy handoff. It moved
7+
axis conversion and winding into triangle emission, added robust transitive
8+
degenerate-vertex merging with the shared `UnionFind`, and broadened the
9+
correctness and benchmark suites. Scalar spacing is now accepted as an
10+
isotropic convenience.
11+
12+
The benchmark now has a backward-compatible single-size mode and a full
13+
scaling suite matching the independent implementation comparison:
14+
15+
```bash
16+
python development/mesh/benchmark_marching_cubes.py --size medium
17+
python development/mesh/benchmark_marching_cubes.py \
18+
--suite scaling --repeats 5 --batches 1 --warmup 1 --memory \
19+
--json /tmp/marching-cubes.json
20+
```
21+
22+
The scaling suite alternates paired bioimage-cpp/scikit-image calls and covers
23+
Lewiner spheres/scalar fields through 512³, dense 10%-foreground masks through
24+
256³, and all three Lorensen workloads at 128³. Fresh subprocesses measure
25+
peak RSS before the timing process allocates any large volumes.
26+
27+
Same-harness before/after results on the machine described below:
28+
29+
| case | before ms | after ms | change | scikit-image / after |
30+
|---|---:|---:|---:|---:|
31+
| sphere, Lewiner, 512³ | 1,898.68 | 1,786.24 | -5.9% | 1.06× |
32+
| dense mask, Lewiner, 256³ | 2,119.26 | 2,063.38 | -2.6% | 1.98× |
33+
| scalar field, Lewiner, 512³ | 2,268.58 | 2,295.50 | +1.2% | 1.30× |
34+
| sphere, Lorensen, 128³ | 30.31 | 28.39 | -6.3% | 1.31× |
35+
| dense mask, Lorensen, 128³ | 192.19 | 212.75 | +10.7% | 2.16× |
36+
| scalar field, Lorensen, 128³ | 48.72 | 49.80 | +2.2% | 1.84× |
37+
38+
Absolute timings moved with CPU state: across all 15 cases the geometric-mean
39+
wall time improved 2.4%, while normalization by each paired scikit-image time
40+
showed a 1.1% improvement. Targeted same-session A/B repeats against the exact
41+
pre-change commit resolved the apparent small-scalar and dense-Lorensen
42+
regressions: scalar 64³ retained a ~2.04–2.06× paired speedup, while dense
43+
Lorensen improved from ~2.01–2.02× to ~2.08×. No reproducible regression above
44+
3% remained.
45+
46+
Peak RSS was unchanged within measurement noise:
47+
48+
| case | before MiB | after MiB | scikit-image MiB |
49+
|---|---:|---:|---:|
50+
| sphere 512³ | 725.8 | 725.7 | 734.6 |
51+
| dense mask 256³ | 643.5 | 643.5 | 1,108.6 |
52+
| scalar field 512³ | 703.3 | 703.4 | 843.3 |
53+
54+
Correctness validation finished with 29 mesh tests, 1,024 full-suite tests,
55+
all 254 nontrivial binary cube configurations for both methods, 1,024 random
56+
scalar cubes for both methods, and 1,000 randomized multi-cube mask/stride/
57+
degeneracy cases. Twelve `allow_degenerate=False` cases intentionally differed
58+
from scikit-image's negative-index remapping quirk; all returned faces were
59+
valid and contained no collapsed-coordinate triangles.
60+
61+
## Two-slice face-cache optimization
62+
363
`bic.mesh.marching_cubes` remains deterministic and single-threaded. The
464
optimization pass replaced the volume-growing edge hash map with Lewiner's
565
two-slice face cache. Several smaller candidates were measured first and

0 commit comments

Comments
 (0)