feat(np_utils): add np_split_connected_component with erosion and mincut backends - #144
Conversation
…cut backends Splits a mask that is one connected component but should be two - two merged vertebral bodies, for instance - into two spatially separate parts. Unifies two independent implementations behind one API: - "erosion" (from spineps get_separating_components): erode until the component breaks apart, then re-grow both halves. Fast and dependency-free. - "mincut" (from nako-segmentation split_cc): erode only until two seeds appear, then find the minimal separating surface by min-cut/max-flow over the voxel adjacency graph, with edge capacities from the voxel spacing. Needs networkx, slower, but the cut is minimal-area rather than incidental. Also adds np_connected_component_contact_map, which dilates two disjoint parts until they touch and returns the 0/1/2/3 contact map - the reusable second half of the erosion algorithm. The two backends originally disagreed on what they returned: the erosion one recovered only the eroded cores while the mincut one partitioned the whole volume. A full_partition flag (default True) now grows the erosion cores back over the removed voxels so both agree; pass False for the raw seeds, which is what you want when fitting a separating plane to them. Two latent bugs in the mincut source are fixed, both now covered by tests: - add_2d_edges raised NameError on an undefined `geom.norm` - max_ignore=None raised NameError, since `max_errors` was only assigned inside the branch that the same argument disables networkx is imported lazily with a clear error message and declared only as a dev dependency, so the core package does not gain a runtime requirement. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Benchmark comparisonSpeed (wall time per call)baseline Showing the 5 most-changed measurements per case; the rest are collapsed. Rows with a baseline below 3 ms are tagged
|
| Measurement | baseline ms (median ±½·range) | head ms (median ±½·range) | Δ % | p |
|---|---|---|---|---|
nii_load_seg |
3.28 ±0.09 | 2.98 ±0.07 | -9.2% | 0.000 |
poi_calc_centroids_nocrop |
4.94 ±0.05 | 4.56 ±0.04 | -7.7% | 0.000 |
nii_save |
12.40 ±0.08 | 11.78 ±0.13 | -5.1% | 0.000 |
nii_erode_msk |
6.41 ±0.07 | 6.13 ±0.02 | -4.3% | 0.000 |
nii_filter_connected_components |
4.48 ±0.07 | 4.35 ±0.04 | -2.9% | 0.034 |
… 31 more measurements
| Measurement | baseline ms (median ±½·range) | head ms (median ±½·range) | Δ % | p |
|---|---|---|---|---|
poi_calc_poi_from_subreg_vert |
12.17 ±0.26 | 11.83 ±0.17 | -2.7% | 0.147 |
nii_load_img |
12.26 ±0.07 | 11.98 ±0.11 | -2.3% | 0.001 |
nii_fill_holes |
4.88 ±0.05 | 4.79 ±0.03 | -1.9% | 0.009 |
nii_resample_from_to |
42.39 ±0.24 | 41.90 ±0.13 | -1.2% | 0.002 |
nii_dilate_msk |
121.32 ±0.27 | 120.25 ±0.35 | -0.9% | 0.000 |
nii_rescale_seg |
3.30 ±0.09 | 3.29 ±0.02 | -0.3% | 0.363 |
nii_rescale |
40.18 ±0.17 | 40.21 ±0.18 | +0.1% | 0.399 |
metric_voxels |
250463.00 ±0.00 | 250463.00 ±0.00 | +0.0% | — |
metric_labels |
3.00 ±0.00 | 3.00 ±0.00 | +0.0% | — |
metric_foreground_pct |
14.51 ±0.00 | 14.51 ±0.00 | +0.0% | — |
poi_rescale |
0.31 ±0.01 | 0.23 ±0.02 | -24.2% (noise) | 0.000 |
nii_apply_crop |
0.87 ±0.02 | 0.71 ±0.04 | -18.0% (noise) | 0.000 |
poi_reorient |
0.52 ±0.04 | 0.43 ±0.02 | -16.5% (noise) | 0.002 |
nii_set_dtype |
0.58 ±0.03 | 0.49 ±0.03 | -15.6% (noise) | 0.000 |
nii_extract_label |
0.55 ±0.02 | 0.48 ±0.02 | -12.8% (noise) | 0.000 |
nii_compute_crop |
0.40 ±0.02 | 0.35 ±0.02 | -12.4% (noise) | 0.038 |
nii_pad_to |
0.86 ±0.05 | 0.76 ±0.02 | -11.4% (noise) | 0.008 |
nii_map_labels |
1.09 ±0.02 | 0.98 ±0.04 | -10.6% (noise) | 0.002 |
poi_to_global |
0.25 ±0.01 | 0.23 ±0.02 | -9.5% (noise) | 0.081 |
nii_get_array |
0.05 ±0.00 | 0.06 ±0.00 | +8.2% (noise) | 0.093 |
poi_load |
1.07 ±0.02 | 0.98 ±0.08 | -7.7% (noise) | 0.187 |
poi_calc_centroids |
2.79 ±0.03 | 2.58 ±0.06 | -7.6% (noise) | 0.000 |
nii_reorient |
0.91 ±0.09 | 0.87 ±0.08 | -4.6% (noise) | 0.538 |
poi_resample_from_to |
0.77 ±0.02 | 0.74 ±0.08 | -4.1% (noise) | 0.553 |
poi_local_to_global_arr |
0.26 ±0.01 | 0.27 ±0.01 | +2.6% (noise) | 0.430 |
nii_center_of_masses |
1.89 ±0.06 | 1.87 ±0.02 | -1.3% (noise) | 0.238 |
nii_get_connected_components |
2.14 ±0.05 | 2.17 ±0.02 | +1.3% (noise) | 0.896 |
poi_save |
0.52 ±0.05 | 0.51 ±0.01 | -1.2% (noise) | 0.472 |
nii_unique |
0.67 ±0.01 | 0.66 ±0.01 | -1.1% (noise) | 0.449 |
nii_volumes |
1.98 ±0.02 | 1.96 ±0.02 | -1.1% (noise) | 0.716 |
poi_map_labels |
0.21 ±0.01 | 0.21 ±0.01 | +0.6% (noise) | 0.835 |
ct_2d — shape (73, 47, 1)
| Measurement | baseline ms (median ±½·range) | head ms (median ±½·range) | Δ % | p |
|---|---|---|---|---|
nii_resample_from_to |
8.00 ±0.35 | 7.52 ±0.19 | -6.0% | 0.010 |
nii_rescale |
6.07 ±0.11 | 5.82 ±0.13 | -4.1% | 0.003 |
nii_dilate_msk |
14.21 ±0.31 | 13.94 ±0.08 | -1.9% | 0.073 |
metric_voxels |
3431.00 ±0.00 | 3431.00 ±0.00 | +0.0% | — |
metric_labels |
3.00 ±0.00 | 3.00 ±0.00 | +0.0% | — |
… 30 more measurements
| Measurement | baseline ms (median ±½·range) | head ms (median ±½·range) | Δ % | p |
|---|---|---|---|---|
metric_foreground_pct |
37.60 ±0.00 | 37.60 ±0.00 | +0.0% | — |
nii_map_labels |
0.61 ±0.02 | 0.55 ±0.01 | -8.8% (noise) | 0.003 |
poi_save |
0.60 ±0.02 | 0.55 ±0.02 | -8.5% (noise) | 0.018 |
poi_calc_centroids |
1.08 ±0.05 | 0.99 ±0.03 | -8.4% (noise) | 0.008 |
nii_get_array |
0.02 ±0.00 | 0.02 ±0.00 | -7.1% (noise) | 0.782 |
poi_load |
1.12 ±0.02 | 1.04 ±0.01 | -6.9% (noise) | 0.000 |
nii_filter_connected_components |
0.98 ±0.02 | 0.92 ±0.01 | -6.2% (noise) | 0.000 |
poi_rescale |
0.32 ±0.01 | 0.30 ±0.01 | -6.0% (noise) | 0.063 |
poi_to_global |
0.31 ±0.01 | 0.29 ±0.00 | -6.0% (noise) | 0.001 |
poi_map_labels |
0.25 ±0.01 | 0.24 ±0.01 | -5.7% (noise) | 0.050 |
poi_reorient |
0.54 ±0.01 | 0.51 ±0.01 | -5.5% (noise) | 0.002 |
poi_calc_centroids_nocrop |
1.26 ±0.04 | 1.20 ±0.02 | -5.3% (noise) | 0.007 |
nii_rescale_seg |
0.93 ±0.01 | 0.88 ±0.04 | -5.2% (noise) | 0.286 |
nii_save |
1.58 ±0.05 | 1.51 ±0.02 | -4.2% (noise) | 0.014 |
nii_load_seg |
1.85 ±0.06 | 1.77 ±0.03 | -3.9% (noise) | 0.018 |
nii_center_of_masses |
0.26 ±0.01 | 0.25 ±0.00 | -3.7% (noise) | 0.108 |
nii_fill_holes |
0.95 ±0.02 | 0.92 ±0.01 | -3.4% (noise) | 0.003 |
nii_load_img |
1.67 ±0.09 | 1.62 ±0.03 | -2.9% (noise) | 1.000 |
nii_apply_crop |
0.81 ±0.03 | 0.78 ±0.02 | -2.9% (noise) | 0.389 |
nii_unique |
0.16 ±0.01 | 0.16 ±0.01 | -2.5% (noise) | 0.321 |
nii_volumes |
0.33 ±0.01 | 0.34 ±0.01 | +2.5% (noise) | 0.123 |
nii_get_connected_components |
0.75 ±0.02 | 0.73 ±0.03 | -2.3% (noise) | 0.055 |
nii_set_dtype |
0.49 ±0.04 | 0.48 ±0.01 | -2.2% (noise) | 0.291 |
nii_pad_to |
0.68 ±0.04 | 0.66 ±0.02 | -2.1% (noise) | 0.147 |
poi_local_to_global_arr |
0.32 ±0.01 | 0.31 ±0.01 | -1.6% (noise) | 0.292 |
nii_reorient |
0.92 ±0.04 | 0.91 ±0.04 | -1.1% (noise) | 0.939 |
nii_extract_label |
0.50 ±0.03 | 0.50 ±0.01 | -0.7% (noise) | 0.859 |
nii_compute_crop |
0.19 ±0.01 | 0.19 ±0.01 | +0.2% (noise) | 0.699 |
poi_resample_from_to |
0.79 ±0.01 | 0.79 ±0.03 | +0.2% (noise) | 0.935 |
nii_erode_msk |
1.06 ±0.03 | 1.06 ±0.02 | -0.1% (noise) | 0.907 |
mri_3d — shape (68, 52, 67)
| Measurement | baseline ms (median ±½·range) | head ms (median ±½·range) | Δ % | p |
|---|---|---|---|---|
nii_load_seg |
3.18 ±0.03 | 3.06 ±0.03 | -3.8% | 0.001 |
poi_calc_poi_from_subreg_vert |
11.44 ±0.25 | 11.18 ±0.16 | -2.2% | 0.070 |
nii_load_img |
9.93 ±0.05 | 9.74 ±0.05 | -2.0% | 0.000 |
nii_resample_from_to |
40.38 ±0.29 | 39.77 ±0.21 | -1.5% | 0.054 |
nii_dilate_msk |
156.76 ±0.17 | 154.56 ±0.28 | -1.4% | 0.000 |
… 31 more measurements
| Measurement | baseline ms (median ±½·range) | head ms (median ±½·range) | Δ % | p |
|---|---|---|---|---|
nii_save |
9.90 ±0.03 | 9.77 ±0.06 | -1.3% | 0.002 |
nii_rescale |
38.62 ±0.14 | 38.16 ±0.20 | -1.2% | 0.003 |
nii_rescale_seg |
3.22 ±0.06 | 3.19 ±0.04 | -1.1% | 0.909 |
nii_filter_connected_components |
4.71 ±0.06 | 4.68 ±0.05 | -0.6% | 0.744 |
nii_fill_holes |
5.73 ±0.08 | 5.70 ±0.04 | -0.5% | 0.370 |
poi_calc_centroids_nocrop |
9.28 ±0.05 | 9.32 ±0.09 | +0.3% | 0.703 |
nii_erode_msk |
7.82 ±0.02 | 7.82 ±1.08 | +0.0% | 0.262 |
metric_voxels |
236912.00 ±0.00 | 236912.00 ±0.00 | +0.0% | — |
metric_labels |
8.00 ±0.00 | 8.00 ±0.00 | +0.0% | — |
metric_foreground_pct |
18.21 ±0.00 | 18.21 ±0.00 | +0.0% | — |
nii_get_array |
0.05 ±0.00 | 0.06 ±0.00 | +19.8% (noise) | 0.000 |
nii_set_dtype |
0.64 ±0.01 | 0.59 ±0.02 | -7.3% (noise) | 0.003 |
poi_map_labels |
0.30 ±0.02 | 0.28 ±0.01 | -7.0% (noise) | 0.045 |
nii_map_labels |
1.13 ±0.03 | 1.07 ±0.01 | -5.4% (noise) | 0.003 |
poi_local_to_global_arr |
0.30 ±0.01 | 0.31 ±0.01 | +4.4% (noise) | 0.042 |
nii_extract_label |
0.55 ±0.02 | 0.52 ±0.01 | -4.2% (noise) | 0.018 |
poi_resample_from_to |
0.81 ±0.03 | 0.85 ±0.03 | +4.1% (noise) | 0.214 |
nii_pad_to |
0.88 ±0.02 | 0.85 ±0.01 | -4.0% (noise) | 0.017 |
nii_reorient |
1.03 ±0.03 | 0.99 ±0.03 | -4.0% (noise) | 0.331 |
poi_reorient |
0.58 ±0.02 | 0.56 ±0.02 | -3.6% (noise) | 0.026 |
poi_to_global |
0.34 ±0.01 | 0.33 ±0.02 | -3.5% (noise) | 0.170 |
poi_save |
0.66 ±0.01 | 0.63 ±0.02 | -3.4% (noise) | 0.024 |
nii_get_connected_components |
2.25 ±0.03 | 2.18 ±0.02 | -3.3% (noise) | 0.002 |
nii_unique |
0.68 ±0.01 | 0.66 ±0.01 | -3.1% (noise) | 0.058 |
nii_apply_crop |
0.85 ±0.02 | 0.82 ±0.03 | -3.0% (noise) | 0.322 |
nii_compute_crop |
0.39 ±0.01 | 0.38 ±0.01 | -2.7% (noise) | 0.088 |
nii_center_of_masses |
2.15 ±0.02 | 2.13 ±0.02 | -1.1% (noise) | 0.044 |
poi_load |
2.14 ±0.01 | 2.16 ±0.03 | +1.1% (noise) | 0.236 |
poi_rescale |
0.35 ±0.02 | 0.35 ±0.03 | -0.8% (noise) | 0.767 |
nii_volumes |
2.11 ±0.02 | 2.10 ±0.02 | -0.6% (noise) | 0.129 |
poi_calc_centroids |
2.95 ±0.06 | 2.97 ±0.06 | +0.6% (noise) | 0.988 |
mri_2d — shape (68, 52, 1)
| Measurement | baseline ms (median ±½·range) | head ms (median ±½·range) | Δ % | p |
|---|---|---|---|---|
nii_rescale |
5.90 ±0.10 | 5.65 ±0.14 | -4.3% | 0.009 |
nii_dilate_msk |
18.71 ±0.18 | 18.39 ±0.08 | -1.7% | 0.012 |
nii_resample_from_to |
7.58 ±0.18 | 7.55 ±0.17 | -0.5% | 0.622 |
metric_voxels |
3536.00 ±0.00 | 3536.00 ±0.00 | +0.0% | — |
metric_labels |
7.00 ±0.00 | 7.00 ±0.00 | +0.0% | — |
… 30 more measurements
| Measurement | baseline ms (median ±½·range) | head ms (median ±½·range) | Δ % | p |
|---|---|---|---|---|
metric_foreground_pct |
50.17 ±0.00 | 50.17 ±0.00 | +0.0% | — |
nii_reorient |
0.98 ±0.02 | 0.89 ±0.02 | -8.8% (noise) | 0.000 |
nii_map_labels |
0.62 ±0.05 | 0.57 ±0.02 | -8.6% (noise) | 0.032 |
nii_load_seg |
1.86 ±0.02 | 1.72 ±0.04 | -7.7% (noise) | 0.001 |
nii_set_dtype |
0.49 ±0.02 | 0.45 ±0.01 | -7.5% (noise) | 0.086 |
nii_apply_crop |
0.82 ±0.02 | 0.76 ±0.02 | -7.2% (noise) | 0.000 |
nii_unique |
0.16 ±0.01 | 0.15 ±0.00 | -7.0% (noise) | 0.082 |
nii_pad_to |
0.68 ±0.02 | 0.65 ±0.02 | -5.7% (noise) | 0.003 |
poi_reorient |
0.56 ±0.02 | 0.53 ±0.03 | -5.6% (noise) | 0.086 |
poi_calc_centroids_nocrop |
1.50 ±0.05 | 1.42 ±0.02 | -5.5% (noise) | 0.012 |
nii_load_img |
1.65 ±0.02 | 1.56 ±0.04 | -5.5% (noise) | 0.008 |
nii_save |
1.57 ±0.02 | 1.48 ±0.02 | -5.3% (noise) | 0.000 |
nii_filter_connected_components |
1.00 ±0.01 | 0.94 ±0.02 | -5.2% (noise) | 0.000 |
nii_get_connected_components |
0.75 ±0.03 | 0.71 ±0.02 | -5.2% (noise) | 0.181 |
poi_calc_centroids |
1.27 ±0.02 | 1.20 ±0.01 | -5.1% (noise) | 0.002 |
poi_local_to_global_arr |
0.30 ±0.01 | 0.31 ±0.01 | +4.7% (noise) | 0.016 |
nii_center_of_masses |
0.44 ±0.01 | 0.42 ±0.00 | -4.5% (noise) | 0.017 |
nii_erode_msk |
1.30 ±0.06 | 1.25 ±0.02 | -4.1% (noise) | 0.074 |
poi_load |
2.01 ±0.03 | 1.94 ±0.02 | -3.5% (noise) | 0.006 |
poi_to_global |
0.31 ±0.01 | 0.32 ±0.01 | +3.4% (noise) | 0.083 |
nii_get_array |
0.02 ±0.01 | 0.02 ±0.00 | -3.3% (noise) | 0.735 |
nii_volumes |
0.48 ±0.01 | 0.50 ±0.03 | +3.0% (noise) | 0.326 |
nii_rescale_seg |
0.88 ±0.04 | 0.86 ±0.01 | -2.7% (noise) | 0.239 |
poi_map_labels |
0.29 ±0.01 | 0.29 ±0.01 | -2.5% (noise) | 0.219 |
poi_resample_from_to |
0.85 ±0.01 | 0.83 ±0.01 | -2.5% (noise) | 0.058 |
nii_fill_holes |
1.10 ±0.01 | 1.08 ±0.02 | -1.9% (noise) | 0.027 |
poi_rescale |
0.33 ±0.02 | 0.32 ±0.01 | -1.9% (noise) | 0.697 |
nii_compute_crop |
0.18 ±0.02 | 0.18 ±0.02 | -1.7% (noise) | 0.580 |
poi_save |
0.62 ±0.02 | 0.62 ±0.03 | -0.8% (noise) | 0.441 |
nii_extract_label |
0.48 ±0.04 | 0.48 ±0.01 | -0.6% (noise) | 0.714 |
Gate: a measurement fails when the baseline is ≥ 1 ms, the median grows by ≥ 50%, and Welch's t-test gives p < 0.05. metric_* rows are context only.
Memory (peak RSS growth per call)
baseline 7e54bda vs head 7e54bda · python 3.11.16 · 5 repeats + 1 warmup · sampler proc-status, isolation fork · measurement floor ≈ 0.90 MiB
Showing the 5 most-changed measurements per case; the rest are collapsed. Rows with a baseline below 3 MiB are tagged (noise) — runner jitter on those swamps any real change.
ct_3d — shape (73, 47, 73)
| Measurement | baseline MiB (median ±½·range) | head MiB (median ±½·range) | Δ % | p |
|---|---|---|---|---|
poi_resample_from_to |
5.28 ±0.00 | 5.19 ±0.00 | -1.6% | — |
nii_reorient |
5.91 ±0.00 | 5.82 ±0.00 | -1.5% | — |
nii_dilate_msk |
5.93 ±0.00 | 5.87 ±0.00 | -1.1% | — |
nii_erode_msk |
5.95 ±0.00 | 5.88 ±0.00 | -1.1% | — |
nii_save |
3.68 ±0.00 | 3.71 ±0.00 | +1.0% | — |
… 31 more measurements
| Measurement | baseline MiB (median ±½·range) | head MiB (median ±½·range) | Δ % | p |
|---|---|---|---|---|
nii_set_dtype |
5.24 ±0.00 | 5.20 ±0.00 | -0.9% | — |
nii_extract_label |
5.37 ±0.00 | 5.32 ±0.00 | -0.9% | — |
nii_pad_to |
5.43 ±0.00 | 5.38 ±0.00 | -0.9% | — |
nii_map_labels |
5.49 ±0.00 | 5.45 ±0.00 | -0.9% | — |
nii_load_seg |
5.98 ±0.00 | 5.93 ±0.00 | -0.8% | — |
poi_calc_centroids_nocrop |
5.93 ±0.00 | 5.88 ±0.00 | -0.8% | — |
poi_calc_centroids |
6.05 ±0.00 | 6.01 ±0.00 | -0.8% | — |
nii_fill_holes |
6.28 ±0.00 | 6.23 ±0.00 | -0.7% | — |
nii_rescale_seg |
5.91 ±0.00 | 5.95 ±0.00 | +0.7% | — |
nii_apply_crop |
5.83 ±0.00 | 5.82 ±0.00 | -0.2% | — |
nii_get_connected_components |
6.58 ±0.00 | 6.57 ±0.00 | -0.2% | — |
nii_filter_connected_components |
6.83 ±0.00 | 6.82 ±0.00 | -0.2% | — |
poi_calc_poi_from_subreg_vert |
7.08 ±0.00 | 7.07 ±0.00 | -0.2% | — |
nii_load_img |
3.53 ±0.00 | 3.53 ±0.00 | -0.1% | — |
nii_resample_from_to |
6.26 ±0.00 | 6.27 ±0.00 | +0.1% | — |
nii_rescale |
6.14 ±0.00 | 6.14 ±0.00 | +0.0% | — |
nii_volumes |
3.52 ±0.00 | 3.52 ±0.00 | +0.0% | — |
nii_center_of_masses |
3.27 ±0.00 | 3.27 ±0.00 | +0.0% | — |
poi_reorient |
3.83 ±0.00 | 3.83 ±0.00 | +0.0% | — |
poi_to_global |
3.08 ±0.00 | 3.08 ±0.00 | +0.0% | — |
metric_voxels |
250463.00 ±0.00 | 250463.00 ±0.00 | +0.0% | — |
metric_labels |
3.00 ±0.00 | 3.00 ±0.00 | +0.0% | — |
metric_foreground_pct |
14.51 ±0.00 | 14.51 ±0.00 | +0.0% | — |
nii_get_array |
1.65 ±0.00 | 1.65 ±0.00 | +0.0% (noise) | — |
nii_compute_crop |
2.90 ±0.00 | 2.90 ±0.00 | +0.0% (noise) | — |
nii_unique |
2.96 ±0.00 | 2.96 ±0.00 | +0.0% (noise) | — |
poi_rescale |
2.61 ±0.00 | 2.61 ±0.00 | +0.0% (noise) | — |
poi_local_to_global_arr |
2.96 ±0.00 | 2.96 ±0.00 | +0.0% (noise) | — |
poi_save |
1.40 ±0.00 | 1.40 ±0.00 | +0.0% (noise) | — |
poi_map_labels |
1.77 ±0.00 | 1.77 ±0.00 | +0.0% (noise) | — |
poi_load |
1.61 ±0.00 | 1.61 ±0.00 | +0.0% (noise) | — |
ct_2d — shape (73, 47, 1)
| Measurement | baseline MiB (median ±½·range) | head MiB (median ±½·range) | Δ % | p |
|---|---|---|---|---|
poi_resample_from_to |
5.21 ±0.00 | 5.13 ±0.00 | -1.6% | — |
nii_reorient |
5.84 ±0.00 | 5.76 ±0.00 | -1.5% | 0.000 |
nii_dilate_msk |
5.87 ±0.00 | 5.80 ±0.00 | -1.1% | — |
nii_erode_msk |
5.88 ±0.00 | 5.82 ±0.00 | -1.1% | — |
nii_save |
3.61 ±0.00 | 3.65 ±0.00 | +1.0% | — |
… 30 more measurements
| Measurement | baseline MiB (median ±½·range) | head MiB (median ±½·range) | Δ % | p |
|---|---|---|---|---|
nii_set_dtype |
5.18 ±0.00 | 5.13 ±0.00 | -0.9% | — |
nii_extract_label |
5.30 ±0.00 | 5.26 ±0.00 | -0.9% | — |
nii_pad_to |
5.36 ±0.00 | 5.32 ±0.00 | -0.9% | — |
nii_map_labels |
5.43 ±0.00 | 5.38 ±0.00 | -0.9% | — |
nii_load_seg |
5.91 ±0.00 | 5.86 ±0.00 | -0.8% | — |
poi_calc_centroids_nocrop |
5.93 ±0.00 | 5.88 ±0.00 | -0.8% | — |
poi_calc_centroids |
5.99 ±0.00 | 5.95 ±0.00 | -0.8% | 0.000 |
nii_fill_holes |
6.22 ±0.00 | 6.17 ±0.00 | -0.8% | — |
nii_rescale_seg |
5.85 ±0.00 | 5.89 ±0.00 | +0.7% | — |
nii_apply_crop |
5.77 ±0.00 | 5.76 ±0.00 | -0.2% | — |
nii_get_connected_components |
6.52 ±0.00 | 6.51 ±0.00 | -0.2% | — |
nii_filter_connected_components |
6.77 ±0.00 | 6.76 ±0.00 | -0.2% | — |
nii_resample_from_to |
6.20 ±0.00 | 6.20 ±0.00 | +0.1% | — |
nii_load_img |
3.46 ±0.00 | 3.46 ±0.00 | +0.0% | — |
nii_rescale |
6.07 ±0.00 | 6.07 ±0.00 | +0.0% | — |
nii_volumes |
3.46 ±0.00 | 3.46 ±0.00 | +0.0% | — |
nii_center_of_masses |
3.21 ±0.00 | 3.21 ±0.00 | +0.0% | — |
poi_reorient |
3.77 ±0.00 | 3.77 ±0.00 | +0.0% | — |
poi_to_global |
3.02 ±0.00 | 3.02 ±0.00 | +0.0% | — |
metric_voxels |
3431.00 ±0.00 | 3431.00 ±0.00 | +0.0% | — |
metric_labels |
3.00 ±0.00 | 3.00 ±0.00 | +0.0% | — |
metric_foreground_pct |
37.60 ±0.00 | 37.60 ±0.00 | +0.0% | — |
nii_get_array |
1.59 ±0.00 | 1.59 ±0.00 | +0.0% (noise) | — |
nii_compute_crop |
2.84 ±0.00 | 2.84 ±0.00 | +0.0% (noise) | — |
nii_unique |
2.90 ±0.00 | 2.90 ±0.00 | +0.0% (noise) | — |
poi_rescale |
2.54 ±0.00 | 2.54 ±0.00 | +0.0% (noise) | — |
poi_local_to_global_arr |
2.89 ±0.00 | 2.89 ±0.00 | +0.0% (noise) | — |
poi_save |
1.34 ±0.00 | 1.34 ±0.00 | +0.0% (noise) | — |
poi_map_labels |
1.71 ±0.00 | 1.71 ±0.00 | +0.0% (noise) | — |
poi_load |
1.55 ±0.00 | 1.55 ±0.00 | +0.0% (noise) | — |
mri_3d — shape (68, 52, 67)
| Measurement | baseline MiB (median ±½·range) | head MiB (median ±½·range) | Δ % | p |
|---|---|---|---|---|
poi_resample_from_to |
5.21 ±0.00 | 5.13 ±0.00 | -1.6% | — |
nii_reorient |
5.84 ±0.00 | 5.76 ±0.00 | -1.5% | — |
nii_dilate_msk |
5.87 ±0.00 | 5.80 ±0.00 | -1.1% | — |
nii_erode_msk |
5.88 ±0.00 | 5.82 ±0.00 | -1.1% | — |
nii_save |
3.61 ±0.00 | 3.65 ±0.00 | +1.0% | — |
… 31 more measurements
| Measurement | baseline MiB (median ±½·range) | head MiB (median ±½·range) | Δ % | p |
|---|---|---|---|---|
nii_set_dtype |
5.24 ±0.00 | 5.20 ±0.00 | -0.9% | — |
nii_extract_label |
5.30 ±0.00 | 5.26 ±0.00 | -0.9% | — |
nii_pad_to |
5.36 ±0.00 | 5.32 ±0.00 | -0.9% | — |
nii_map_labels |
5.43 ±0.00 | 5.38 ±0.00 | -0.9% | — |
poi_calc_centroids_nocrop |
5.86 ±0.00 | 5.82 ±0.00 | -0.8% | — |
nii_load_seg |
5.91 ±0.00 | 5.86 ±0.00 | -0.8% | — |
poi_calc_centroids |
5.99 ±0.00 | 5.95 ±0.00 | -0.8% | — |
nii_fill_holes |
6.22 ±0.00 | 6.17 ±0.00 | -0.8% | — |
nii_rescale_seg |
5.85 ±0.00 | 5.89 ±0.00 | +0.7% | — |
nii_apply_crop |
5.77 ±0.00 | 5.76 ±0.00 | -0.2% | — |
nii_get_connected_components |
6.52 ±0.00 | 6.51 ±0.00 | -0.2% | — |
nii_filter_connected_components |
6.77 ±0.00 | 6.76 ±0.00 | -0.2% | — |
poi_calc_poi_from_subreg_vert |
7.08 ±0.00 | 7.07 ±0.00 | -0.2% | — |
nii_resample_from_to |
6.20 ±0.00 | 6.20 ±0.00 | +0.1% | — |
nii_load_img |
3.53 ±0.00 | 3.53 ±0.00 | +0.0% | — |
nii_rescale |
6.07 ±0.00 | 6.07 ±0.00 | +0.0% | — |
nii_volumes |
3.46 ±0.00 | 3.46 ±0.00 | +0.0% | — |
nii_center_of_masses |
3.21 ±0.00 | 3.21 ±0.00 | +0.0% | — |
poi_reorient |
3.77 ±0.00 | 3.77 ±0.00 | +0.0% | — |
poi_to_global |
3.02 ±0.00 | 3.02 ±0.00 | +0.0% | — |
metric_voxels |
236912.00 ±0.00 | 236912.00 ±0.00 | +0.0% | — |
metric_labels |
8.00 ±0.00 | 8.00 ±0.00 | +0.0% | — |
metric_foreground_pct |
18.21 ±0.00 | 18.21 ±0.00 | +0.0% | — |
nii_get_array |
1.59 ±0.00 | 1.59 ±0.00 | +0.0% (noise) | — |
nii_compute_crop |
2.84 ±0.00 | 2.84 ±0.00 | +0.0% (noise) | — |
nii_unique |
2.90 ±0.00 | 2.90 ±0.00 | +0.0% (noise) | — |
poi_rescale |
2.54 ±0.00 | 2.54 ±0.00 | +0.0% (noise) | — |
poi_local_to_global_arr |
2.89 ±0.00 | 2.89 ±0.00 | +0.0% (noise) | — |
poi_save |
1.40 ±0.00 | 1.40 ±0.00 | +0.0% (noise) | — |
poi_map_labels |
1.71 ±0.00 | 1.71 ±0.00 | +0.0% (noise) | — |
poi_load |
1.55 ±0.00 | 1.55 ±0.00 | +0.0% (noise) | — |
mri_2d — shape (68, 52, 1)
| Measurement | baseline MiB (median ±½·range) | head MiB (median ±½·range) | Δ % | p |
|---|---|---|---|---|
poi_resample_from_to |
5.21 ±0.00 | 5.13 ±0.00 | -1.6% | — |
nii_reorient |
5.84 ±0.00 | 5.76 ±0.00 | -1.5% | — |
nii_dilate_msk |
5.87 ±0.00 | 5.80 ±0.00 | -1.1% | — |
nii_erode_msk |
5.88 ±0.00 | 5.82 ±0.00 | -1.1% | — |
nii_save |
3.61 ±0.00 | 3.65 ±0.00 | +1.0% | — |
… 30 more measurements
| Measurement | baseline MiB (median ±½·range) | head MiB (median ±½·range) | Δ % | p |
|---|---|---|---|---|
nii_set_dtype |
5.24 ±0.00 | 5.20 ±0.00 | -0.9% | — |
nii_extract_label |
5.30 ±0.00 | 5.26 ±0.00 | -0.9% | — |
nii_pad_to |
5.36 ±0.00 | 5.32 ±0.00 | -0.9% | — |
nii_map_labels |
5.43 ±0.00 | 5.38 ±0.00 | -0.9% | — |
nii_load_seg |
5.91 ±0.00 | 5.86 ±0.00 | -0.8% | — |
poi_calc_centroids_nocrop |
5.93 ±0.00 | 5.88 ±0.00 | -0.8% | — |
poi_calc_centroids |
5.99 ±0.00 | 5.95 ±0.00 | -0.8% | — |
nii_fill_holes |
6.22 ±0.00 | 6.17 ±0.00 | -0.8% | — |
nii_rescale_seg |
5.85 ±0.00 | 5.89 ±0.00 | +0.7% | — |
nii_apply_crop |
5.77 ±0.00 | 5.76 ±0.00 | -0.2% | — |
nii_get_connected_components |
6.52 ±0.00 | 6.51 ±0.00 | -0.2% | — |
nii_filter_connected_components |
6.77 ±0.00 | 6.76 ±0.00 | -0.2% | — |
nii_resample_from_to |
6.20 ±0.00 | 6.20 ±0.00 | +0.1% | — |
nii_load_img |
3.53 ±0.00 | 3.53 ±0.00 | +0.0% | — |
nii_rescale |
6.07 ±0.00 | 6.07 ±0.00 | +0.0% | — |
nii_volumes |
3.46 ±0.00 | 3.46 ±0.00 | +0.0% | — |
nii_center_of_masses |
3.21 ±0.00 | 3.21 ±0.00 | +0.0% | — |
poi_reorient |
3.77 ±0.00 | 3.77 ±0.00 | +0.0% | — |
poi_to_global |
3.02 ±0.00 | 3.02 ±0.00 | +0.0% | — |
metric_voxels |
3536.00 ±0.00 | 3536.00 ±0.00 | +0.0% | — |
metric_labels |
7.00 ±0.00 | 7.00 ±0.00 | +0.0% | — |
metric_foreground_pct |
50.17 ±0.00 | 50.17 ±0.00 | +0.0% | — |
nii_get_array |
1.59 ±0.00 | 1.59 ±0.00 | +0.0% (noise) | — |
nii_compute_crop |
2.84 ±0.00 | 2.84 ±0.00 | +0.0% (noise) | — |
nii_unique |
2.90 ±0.00 | 2.90 ±0.00 | +0.0% (noise) | — |
poi_rescale |
2.54 ±0.00 | 2.54 ±0.00 | +0.0% (noise) | — |
poi_local_to_global_arr |
2.89 ±0.00 | 2.89 ±0.00 | +0.0% (noise) | — |
poi_save |
1.40 ±0.00 | 1.40 ±0.00 | +0.0% (noise) | — |
poi_map_labels |
1.71 ±0.00 | 1.71 ±0.00 | +0.0% (noise) | — |
poi_load |
1.55 ±0.00 | 1.55 ±0.00 | +0.0% (noise) | — |
Gate: a measurement fails when the baseline is ≥ 1 MiB, the median grows by ≥ 50%, and Welch's t-test gives p < 0.05. metric_* rows are context only.
Upstreams B10 from the cross-repo survey.
What
np_split_connected_component(arr, method="erosion" | "mincut", …)— splits a mask that is one connected component but should be two (two merged vertebral bodies, say) into two spatially separate parts. Returns0= background,1/2= the two parts.np_connected_component_contact_map(part_a, part_b)— dilates two disjoint parts until they touch and returns the0/1/2/3contact map. This is the reusable second half of the erosion algorithm, useful on its own.Two backends, one API
"erosion""mincut"spinepsget_separating_componentsnako-segmentationsplit_ccnetworkxThe min-cut backend takes
zoomand weights graph edges by voxel spacing, so anisotropic data is handled correctly.Reconciling the two
The originals disagreed on what they returned, which would have made the shared API misleading: the erosion version recovered only the eroded cores (discarding the shell the erosion ate), while the min-cut version partitioned the whole volume. On a test dumbbell the erosion path lost 2560 of 4096 voxels.
A
full_partition=Trueflag (default) now grows the erosion cores back over the removed voxels by nearest-seed assignment, so both backends partition the input. Passfull_partition=Falsefor the raw seeds — which is whatspinepsactually wants, since it fits a separating plane to them.Two latent bugs fixed
Both are in the min-cut source and both are now covered by tests that would have failed before:
add_2d_edges=TrueraisedNameError— the capacity expression referenced an undefinedgeom.norm. Replaced withnp.linalg.norm.max_ignore=NoneraisedNameError—max_errorswas only assigned inside theif max_ignore is not Nonebranch, then read unconditionally at the final voxel-loss check. Now initialised to 0.Dependency
networkxis imported lazily inside the min-cut backend with a clear message ("the 'mincut' method needs networkx; install it with pip install networkx"), and declared only as a dev dependency. The core package gains no runtime requirement, and the min-cut tests skip if it is absent.Verification
unit_tests/test_nputils.py: input really is one component; both backends split into two non-empty parts;full_partitionkeeps every voxel and never paints outside the input; the two parts land on opposite cubes; unknown method rejected; an unsplittable solid block raises; and for min-cut — already-separate input rejected,min_volumeandmax_cutguards, anisotropic zoom, plus the two previously-crashing branches. Contact map: label set and non-empty contact zone, and that it does not mutate its inputs (the upstream dilation helpers work in place).ruff checkclean.🤖 Generated with Claude Code