Fix findings: - #150
Conversation
…ps, doc drift
Addresses an external review of the public tutorials. Both reported implementation
defects were real, and each turned out to also affect a sibling function.
Bug 1 - CUDA-only call on the CPU inference path
get_gpu_memory_MB/get_gpu_util called torch.cuda.mem_get_info() unconditionally.
That API rejects its *argument* rather than probing the environment, so
run_vibeseg(ddevice="cpu") died with "ValueError: Expected a cuda device, but
got: cpu" before predicting a single tile - even on a machine with a GPU. Both
helpers now dispatch on device.type like the neighbouring empty_cache(), and
export_prediction's fallback guard tests the requested device instead of merely
torch.cuda.is_available().
Bug 2 - compounding mm->voxel conversion in the curved-planar projections
curve_projected_mip converted thick_t from mm to voxels inside its per-slice
loop and assigned the result back to the same name, so slice n divided what
slice n-1 had already divided. At 0.5 mm spacing the slab grew geometrically
until int() raised "OverflowError: int too big to convert"; at 3 mm it shrank to
the 1-voxel floor within four slices, silently projecting a far thinner slab
than requested with no error at all. curve_projected_mean had the identical
defect, unguarded. Both now convert from the mm source via a shared helper, and
the try/except that was printing "thick infinity bug" is gone.
Optional dependencies
pydicom/dicom2nifti and the nnU-Net stack were imported at module top level but
declared nowhere, so `import TPTBox.segmentation` failed outright on a clean
install - the actual root cause of what looked like a SPINEPS/nnU-Net conflict.
Added [tool.poetry.extras] dicom and seg (nnunetv2 pinned >=2.4,<2.5) plus
import guards, so sub-packages import and only entry points raise, naming the
extra. _add_grid_info_to_json moved to core/internal/nii_help.py: it uses no
DICOM library, yet its old home made the public BIDS_FILE.get_grid_info() -
which the stitching tutorial calls - require pydicom.
Documentation and examples
Fixed 14 confirmed-broken snippets, including the homepage's first code sample
(.first() on a filter() that returns None), the stitching tutorial's
ProcessPoolExecutor cell (unpicklable under Windows/Jupyter spawn, and its
lazy map() swallowed every worker exception - now joblib), the obsolete
TPTBox.registration.deformable import, and seg_all.ipynb's four dead imports.
docs/modules/{spine,stitching}.md were hand-copies that had already diverged
from the in-package READMEs; they are now --8<-- includes.
Regression coverage
unit_tests/test_snapshot_mip.py pins both projection bugs (verified failing
before the fix in both spacing directions). unit_tests/test_docs_examples.py
statically parses every notebook and Markdown code block and asserts each
TPTBox module and symbol still exists - it flags all of the above when pointed
at the previous tree, and passes with or without the optional stack installed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GrWjhK9FCgmRGkHhhVFd2V
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_filter_connected_components |
4.23 ±0.03 | 4.33 ±0.03 | +2.2% | 0.002 |
nii_rescale_seg |
3.23 ±0.06 | 3.29 ±0.04 | +1.6% | 0.096 |
nii_load_img |
11.75 ±0.04 | 11.92 ±0.05 | +1.4% | 0.001 |
nii_rescale |
39.60 ±0.19 | 40.01 ±0.16 | +1.0% | 0.046 |
poi_calc_poi_from_subreg_vert |
11.76 ±0.18 | 11.67 ±0.15 | -0.8% | 0.263 |
… 31 more measurements
| Measurement | baseline ms (median ±½·range) | head ms (median ±½·range) | Δ % | p |
|---|---|---|---|---|
nii_resample_from_to |
41.70 ±0.17 | 41.93 ±0.10 | +0.6% | 0.288 |
poi_calc_centroids_nocrop |
4.49 ±0.04 | 4.51 ±0.08 | +0.6% | 0.139 |
nii_dilate_msk |
119.61 ±0.46 | 119.18 ±0.27 | -0.4% | 0.223 |
nii_erode_msk |
6.24 ±0.13 | 6.23 ±0.04 | -0.2% | 0.448 |
nii_fill_holes |
4.81 ±0.13 | 4.81 ±0.05 | +0.1% | 0.846 |
nii_save |
11.86 ±0.14 | 11.85 ±0.18 | -0.1% | 0.247 |
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 |
0.03 ±0.00 | 0.05 ±0.01 | +73.4% (noise) | 0.000 |
nii_set_dtype |
0.36 ±0.02 | 0.49 ±0.06 | +36.6% (noise) | 0.006 |
nii_extract_label |
0.35 ±0.02 | 0.44 ±0.02 | +27.1% (noise) | 0.000 |
poi_reorient |
0.37 ±0.03 | 0.47 ±0.06 | +26.8% (noise) | 0.019 |
poi_to_global |
0.21 ±0.02 | 0.25 ±0.02 | +21.3% (noise) | 0.028 |
nii_pad_to |
0.64 ±0.06 | 0.77 ±0.05 | +21.0% (noise) | 0.032 |
nii_map_labels |
0.82 ±0.02 | 0.96 ±0.02 | +16.1% (noise) | 0.000 |
nii_reorient |
0.68 ±0.03 | 0.77 ±0.02 | +14.3% (noise) | 0.004 |
poi_rescale |
0.21 ±0.03 | 0.24 ±0.02 | +13.8% (noise) | 0.062 |
poi_map_labels |
0.18 ±0.02 | 0.19 ±0.02 | +8.4% (noise) | 0.398 |
nii_apply_crop |
0.64 ±0.06 | 0.69 ±0.02 | +6.6% (noise) | 0.198 |
poi_save |
0.46 ±0.01 | 0.49 ±0.03 | +5.7% (noise) | 0.515 |
nii_get_connected_components |
2.06 ±0.03 | 2.16 ±0.04 | +5.0% (noise) | 0.001 |
poi_calc_centroids |
2.43 ±0.04 | 2.54 ±0.06 | +4.7% (noise) | 0.007 |
poi_resample_from_to |
0.70 ±0.03 | 0.67 ±0.02 | -4.5% (noise) | 0.238 |
nii_load_seg |
2.77 ±0.04 | 2.89 ±0.07 | +4.1% (noise) | 0.007 |
poi_local_to_global_arr |
0.27 ±0.01 | 0.28 ±0.01 | +4.1% (noise) | 0.041 |
nii_center_of_masses |
1.81 ±0.04 | 1.87 ±0.02 | +3.3% (noise) | 0.085 |
nii_unique |
0.62 ±0.02 | 0.63 ±0.01 | +2.7% (noise) | 0.520 |
poi_load |
0.96 ±0.04 | 0.95 ±0.02 | -2.0% (noise) | 0.573 |
nii_compute_crop |
0.34 ±0.02 | 0.33 ±0.01 | -0.8% (noise) | 0.797 |
nii_volumes |
1.97 ±0.04 | 1.98 ±0.03 | +0.5% (noise) | 0.334 |
ct_2d — shape (73, 47, 1)
| Measurement | baseline ms (median ±½·range) | head ms (median ±½·range) | Δ % | p |
|---|---|---|---|---|
nii_resample_from_to |
7.40 ±0.15 | 7.49 ±0.15 | +1.2% | 0.158 |
nii_dilate_msk |
13.74 ±0.12 | 13.83 ±0.08 | +0.6% | 0.334 |
nii_rescale |
5.75 ±0.05 | 5.75 ±0.13 | -0.0% | 0.963 |
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_compute_crop |
0.15 ±0.01 | 0.17 ±0.01 | +10.5% (noise) | 0.032 |
nii_set_dtype |
0.43 ±0.02 | 0.47 ±0.02 | +10.3% (noise) | 0.013 |
poi_save |
0.54 ±0.04 | 0.58 ±0.07 | +7.6% (noise) | 0.296 |
nii_unique |
0.14 ±0.01 | 0.15 ±0.01 | +6.8% (noise) | 0.157 |
nii_get_connected_components |
0.72 ±0.01 | 0.77 ±0.06 | +6.7% (noise) | 0.068 |
poi_local_to_global_arr |
0.29 ±0.01 | 0.30 ±0.01 | +5.8% (noise) | 0.073 |
poi_to_global |
0.28 ±0.02 | 0.26 ±0.01 | -5.6% (noise) | 0.141 |
poi_calc_centroids_nocrop |
1.11 ±0.01 | 1.17 ±0.04 | +5.0% (noise) | 0.033 |
poi_map_labels |
0.23 ±0.01 | 0.24 ±0.14 | +4.7% (noise) | 0.325 |
nii_reorient |
0.86 ±0.04 | 0.89 ±0.03 | +4.1% (noise) | 0.183 |
nii_erode_msk |
0.95 ±0.01 | 0.99 ±0.03 | +3.9% (noise) | 0.012 |
poi_rescale |
0.29 ±0.02 | 0.28 ±0.01 | -3.8% (noise) | 0.168 |
nii_load_img |
1.53 ±0.02 | 1.47 ±0.01 | -3.7% (noise) | 0.001 |
nii_load_seg |
1.72 ±0.05 | 1.78 ±0.02 | +3.6% (noise) | 0.028 |
nii_fill_holes |
0.84 ±0.03 | 0.87 ±0.01 | +3.2% (noise) | 0.190 |
poi_reorient |
0.47 ±0.01 | 0.48 ±0.02 | +2.7% (noise) | 0.132 |
nii_filter_connected_components |
0.90 ±0.01 | 0.93 ±0.02 | +2.6% (noise) | 0.078 |
nii_extract_label |
0.47 ±0.01 | 0.48 ±0.03 | +2.3% (noise) | 0.208 |
poi_calc_centroids |
0.98 ±0.03 | 0.99 ±0.02 | +1.8% (noise) | 0.127 |
poi_load |
1.02 ±0.03 | 1.04 ±0.01 | +1.7% (noise) | 0.897 |
poi_resample_from_to |
0.76 ±0.01 | 0.77 ±0.02 | +1.6% (noise) | 0.162 |
nii_volumes |
0.30 ±0.01 | 0.31 ±0.01 | +1.1% (noise) | 0.115 |
nii_rescale_seg |
0.87 ±0.02 | 0.86 ±0.02 | -0.8% (noise) | 0.551 |
nii_center_of_masses |
0.23 ±0.01 | 0.23 ±0.00 | -0.3% (noise) | 0.678 |
nii_pad_to |
0.62 ±0.02 | 0.62 ±0.02 | -0.2% (noise) | 0.327 |
nii_map_labels |
0.54 ±0.01 | 0.54 ±0.05 | +0.2% (noise) | 0.643 |
nii_get_array |
0.02 ±0.00 | 0.02 ±0.00 | +0.1% (noise) | 0.773 |
nii_save |
1.47 ±0.05 | 1.47 ±0.03 | -0.0% (noise) | 0.586 |
nii_apply_crop |
0.75 ±0.01 | 0.75 ±0.02 | -0.0% (noise) | 0.756 |
mri_3d — shape (68, 52, 67)
| Measurement | baseline ms (median ±½·range) | head ms (median ±½·range) | Δ % | p |
|---|---|---|---|---|
nii_load_seg |
3.02 ±0.09 | 2.84 ±0.10 | -5.8% | 0.135 |
poi_calc_centroids_nocrop |
9.28 ±0.07 | 9.06 ±0.09 | -2.4% | 0.005 |
nii_save |
9.79 ±0.14 | 9.58 ±0.08 | -2.1% | 0.099 |
nii_erode_msk |
7.68 ±0.08 | 7.52 ±0.05 | -2.1% | 0.005 |
nii_resample_from_to |
39.92 ±0.51 | 39.22 ±0.21 | -1.7% | 0.026 |
… 31 more measurements
| Measurement | baseline ms (median ±½·range) | head ms (median ±½·range) | Δ % | p |
|---|---|---|---|---|
nii_rescale |
37.55 ±0.24 | 38.20 ±1.18 | +1.7% | 0.147 |
poi_calc_poi_from_subreg_vert |
11.16 ±0.20 | 10.96 ±0.09 | -1.7% | 0.088 |
nii_filter_connected_components |
4.59 ±0.06 | 4.66 ±0.05 | +1.3% | 0.541 |
nii_rescale_seg |
3.14 ±0.20 | 3.18 ±0.04 | +1.2% | 0.596 |
nii_load_img |
9.75 ±0.05 | 9.66 ±0.11 | -1.0% | 0.025 |
nii_fill_holes |
5.64 ±0.04 | 5.59 ±0.07 | -0.8% | 0.122 |
nii_dilate_msk |
154.70 ±0.29 | 154.72 ±1.42 | +0.0% | 0.847 |
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.01 | 0.06 ±0.00 | +20.1% (noise) | 0.072 |
poi_save |
0.64 ±0.03 | 0.59 ±0.03 | -7.7% (noise) | 0.061 |
poi_load |
2.12 ±0.04 | 2.24 ±0.11 | +5.6% (noise) | 0.053 |
nii_set_dtype |
0.58 ±0.01 | 0.54 ±0.02 | -5.3% (noise) | 0.123 |
poi_reorient |
0.52 ±0.01 | 0.50 ±0.03 | -4.3% (noise) | 0.119 |
nii_apply_crop |
0.71 ±0.01 | 0.73 ±0.01 | +3.6% (noise) | 0.008 |
poi_local_to_global_arr |
0.30 ±0.01 | 0.31 ±0.01 | +3.4% (noise) | 0.077 |
poi_to_global |
0.31 ±0.01 | 0.32 ±0.01 | +3.3% (noise) | 0.020 |
nii_unique |
0.63 ±0.01 | 0.61 ±0.01 | -3.2% (noise) | 0.000 |
nii_reorient |
0.87 ±0.03 | 0.89 ±0.02 | +3.0% (noise) | 0.171 |
nii_map_labels |
1.04 ±0.02 | 1.01 ±0.02 | -2.6% (noise) | 0.223 |
nii_pad_to |
0.73 ±0.02 | 0.74 ±0.01 | +2.1% (noise) | 0.162 |
poi_map_labels |
0.28 ±0.01 | 0.27 ±0.00 | -2.1% (noise) | 0.073 |
poi_resample_from_to |
0.84 ±0.02 | 0.82 ±0.02 | -1.7% (noise) | 0.231 |
poi_calc_centroids |
2.82 ±0.03 | 2.87 ±0.02 | +1.5% (noise) | 0.009 |
poi_rescale |
0.31 ±0.01 | 0.31 ±0.01 | -1.5% (noise) | 0.107 |
nii_get_connected_components |
2.14 ±0.07 | 2.17 ±0.08 | +1.2% (noise) | 0.251 |
nii_extract_label |
0.50 ±0.02 | 0.51 ±0.01 | +1.0% (noise) | 0.392 |
nii_volumes |
2.06 ±0.02 | 2.05 ±0.01 | -0.7% (noise) | 0.104 |
nii_compute_crop |
0.36 ±0.01 | 0.36 ±0.01 | +0.6% (noise) | 0.271 |
nii_center_of_masses |
2.09 ±0.01 | 2.10 ±0.01 | +0.2% (noise) | 0.325 |
mri_2d — shape (68, 52, 1)
| Measurement | baseline ms (median ±½·range) | head ms (median ±½·range) | Δ % | p |
|---|---|---|---|---|
nii_rescale |
5.71 ±0.04 | 5.46 ±0.14 | -4.4% | 0.022 |
nii_dilate_msk |
18.26 ±0.30 | 18.49 ±0.19 | +1.3% | 0.702 |
nii_resample_from_to |
7.50 ±0.20 | 7.49 ±0.15 | -0.1% | 0.310 |
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_get_array |
0.02 ±0.00 | 0.02 ±0.00 | +10.9% (noise) | 0.334 |
poi_load |
1.85 ±0.01 | 1.99 ±0.03 | +8.0% (noise) | 0.000 |
poi_save |
0.61 ±0.04 | 0.57 ±0.01 | -6.3% (noise) | 0.068 |
poi_calc_centroids_nocrop |
1.36 ±0.02 | 1.44 ±0.05 | +5.7% (noise) | 0.031 |
nii_set_dtype |
0.43 ±0.01 | 0.46 ±0.04 | +5.1% (noise) | 0.151 |
poi_map_labels |
0.27 ±0.02 | 0.26 ±0.01 | -4.4% (noise) | 0.376 |
poi_local_to_global_arr |
0.29 ±0.01 | 0.30 ±0.02 | +4.3% (noise) | 0.071 |
poi_resample_from_to |
0.78 ±0.03 | 0.81 ±0.01 | +4.2% (noise) | 0.010 |
nii_center_of_masses |
0.41 ±0.01 | 0.42 ±0.02 | +4.0% (noise) | 0.073 |
nii_map_labels |
0.56 ±0.01 | 0.54 ±0.02 | -3.7% (noise) | 0.085 |
nii_fill_holes |
1.07 ±0.02 | 1.04 ±0.03 | -3.3% (noise) | 0.084 |
nii_filter_connected_components |
0.88 ±0.04 | 0.91 ±0.02 | +3.3% (noise) | 0.159 |
nii_load_img |
1.52 ±0.05 | 1.56 ±0.02 | +3.0% (noise) | 0.036 |
nii_compute_crop |
0.17 ±0.02 | 0.16 ±0.00 | -2.8% (noise) | 0.481 |
nii_rescale_seg |
0.82 ±0.02 | 0.84 ±0.02 | +2.8% (noise) | 0.255 |
poi_calc_centroids |
1.16 ±0.04 | 1.19 ±0.03 | +2.5% (noise) | 0.140 |
nii_save |
1.45 ±0.03 | 1.42 ±0.06 | -2.3% (noise) | 0.603 |
nii_pad_to |
0.58 ±0.06 | 0.59 ±0.03 | +2.2% (noise) | 0.701 |
nii_apply_crop |
0.73 ±0.06 | 0.71 ±0.02 | -2.0% (noise) | 0.283 |
nii_load_seg |
1.74 ±0.03 | 1.71 ±0.03 | -1.9% (noise) | 0.355 |
nii_erode_msk |
1.22 ±0.01 | 1.21 ±0.04 | -1.3% (noise) | 0.544 |
nii_get_connected_components |
0.67 ±0.02 | 0.66 ±0.02 | -1.2% (noise) | 0.226 |
poi_reorient |
0.51 ±0.01 | 0.51 ±0.02 | -1.0% (noise) | 0.972 |
poi_to_global |
0.30 ±0.02 | 0.30 ±0.01 | +0.9% (noise) | 0.403 |
nii_reorient |
0.87 ±0.22 | 0.86 ±0.03 | -0.7% (noise) | 0.303 |
nii_extract_label |
0.45 ±0.01 | 0.44 ±0.02 | -0.6% (noise) | 0.255 |
poi_rescale |
0.32 ±0.01 | 0.32 ±0.01 | +0.2% (noise) | 0.653 |
nii_unique |
0.15 ±0.01 | 0.15 ±0.01 | -0.2% (noise) | 0.266 |
nii_volumes |
0.48 ±0.01 | 0.48 ±0.01 | +0.1% (noise) | 0.317 |
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 2cafc2c vs head 2cafc2c · 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 |
|---|---|---|---|---|
nii_apply_crop |
5.77 ±0.00 | 5.84 ±0.00 | +1.2% | — |
nii_rescale_seg |
5.85 ±0.00 | 5.92 ±0.00 | +1.2% | — |
nii_load_seg |
5.88 ±0.00 | 5.95 ±0.00 | +1.2% | — |
nii_rescale |
6.04 ±0.00 | 6.11 ±0.00 | +1.2% | — |
nii_resample_from_to |
6.16 ±0.00 | 6.23 ±0.00 | +1.1% | — |
… 31 more measurements
| Measurement | baseline MiB (median ±½·range) | head MiB (median ±½·range) | Δ % | p |
|---|---|---|---|---|
nii_get_connected_components |
6.52 ±0.00 | 6.59 ±0.00 | +1.1% | — |
nii_filter_connected_components |
6.77 ±0.00 | 6.84 ±0.00 | +1.0% | — |
nii_load_img |
3.49 ±0.00 | 3.53 ±0.00 | +1.0% | — |
nii_save |
3.68 ±0.00 | 3.71 ±0.00 | +1.0% | — |
poi_calc_poi_from_subreg_vert |
7.02 ±0.00 | 7.09 ±0.00 | +0.9% | — |
nii_dilate_msk |
5.92 ±0.00 | 5.87 ±0.00 | -0.9% | — |
nii_erode_msk |
5.94 ±0.00 | 5.88 ±0.00 | -0.9% | — |
poi_resample_from_to |
5.17 ±0.00 | 5.21 ±0.00 | +0.8% | — |
nii_set_dtype |
5.17 ±0.00 | 5.21 ±0.00 | +0.8% | — |
nii_extract_label |
5.30 ±0.00 | 5.34 ±0.00 | +0.8% | — |
nii_pad_to |
5.36 ±0.00 | 5.40 ±0.00 | +0.8% | — |
nii_map_labels |
5.42 ±0.00 | 5.46 ±0.00 | +0.8% | — |
nii_reorient |
5.80 ±0.00 | 5.84 ±0.00 | +0.7% | — |
poi_calc_centroids_nocrop |
5.86 ±0.00 | 5.90 ±0.00 | +0.7% | — |
poi_calc_centroids |
5.98 ±0.00 | 6.03 ±0.00 | +0.7% | — |
nii_fill_holes |
6.21 ±0.00 | 6.26 ±0.00 | +0.7% | — |
poi_to_global |
3.07 ±0.00 | 3.08 ±0.00 | +0.3% | — |
nii_center_of_masses |
3.27 ±0.00 | 3.27 ±0.00 | +0.2% | — |
nii_volumes |
3.52 ±0.00 | 3.52 ±0.00 | +0.2% | — |
poi_reorient |
3.82 ±0.00 | 3.83 ±0.00 | +0.2% | — |
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 |
2.60 ±0.00 | 2.61 ±0.00 | +0.3% (noise) | — |
nii_compute_crop |
2.89 ±0.00 | 2.90 ±0.00 | +0.3% (noise) | — |
poi_local_to_global_arr |
2.95 ±0.00 | 2.96 ±0.00 | +0.3% (noise) | — |
nii_unique |
2.95 ±0.00 | 2.96 ±0.00 | +0.3% (noise) | — |
nii_get_array |
1.65 ±0.00 | 1.65 ±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 |
|---|---|---|---|---|
nii_apply_crop |
5.71 ±0.00 | 5.78 ±0.00 | +1.2% | — |
nii_rescale_seg |
5.79 ±0.00 | 5.86 ±0.00 | +1.2% | — |
nii_load_seg |
5.81 ±0.00 | 5.88 ±0.00 | +1.2% | — |
nii_rescale |
5.97 ±0.00 | 6.04 ±0.00 | +1.2% | — |
nii_resample_from_to |
6.10 ±0.00 | 6.17 ±0.00 | +1.2% | — |
… 30 more measurements
| Measurement | baseline MiB (median ±½·range) | head MiB (median ±½·range) | Δ % | p |
|---|---|---|---|---|
nii_get_connected_components |
6.46 ±0.00 | 6.53 ±0.00 | +1.1% | — |
nii_filter_connected_components |
6.71 ±0.00 | 6.78 ±0.00 | +1.0% | — |
nii_load_img |
3.43 ±0.00 | 3.46 ±0.00 | +1.0% | — |
nii_save |
3.61 ±0.00 | 3.65 ±0.00 | +1.0% | — |
nii_dilate_msk |
5.86 ±0.00 | 5.80 ±0.00 | -0.9% | — |
nii_erode_msk |
5.88 ±0.00 | 5.82 ±0.00 | -0.9% | — |
poi_resample_from_to |
5.11 ±0.00 | 5.15 ±0.00 | +0.8% | — |
nii_set_dtype |
5.11 ±0.00 | 5.15 ±0.00 | +0.8% | — |
nii_extract_label |
5.23 ±0.00 | 5.28 ±0.00 | +0.8% | — |
nii_pad_to |
5.29 ±0.00 | 5.34 ±0.00 | +0.8% | — |
nii_map_labels |
5.36 ±0.00 | 5.40 ±0.00 | +0.8% | — |
nii_reorient |
5.73 ±0.00 | 5.78 ±0.00 | +0.7% | — |
poi_calc_centroids_nocrop |
5.86 ±0.00 | 5.90 ±0.00 | +0.7% | — |
poi_calc_centroids |
5.92 ±0.00 | 5.96 ±0.00 | +0.7% | — |
nii_fill_holes |
6.15 ±0.00 | 6.20 ±0.00 | +0.7% | — |
poi_to_global |
3.01 ±0.00 | 3.02 ±0.00 | +0.3% | — |
nii_center_of_masses |
3.20 ±0.00 | 3.21 ±0.00 | +0.2% | — |
nii_volumes |
3.45 ±0.00 | 3.46 ±0.00 | +0.2% | — |
poi_reorient |
3.76 ±0.00 | 3.77 ±0.00 | +0.2% | — |
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% | — |
poi_rescale |
2.54 ±0.00 | 2.54 ±0.00 | +0.3% (noise) | — |
nii_compute_crop |
2.83 ±0.00 | 2.84 ±0.00 | +0.3% (noise) | — |
poi_local_to_global_arr |
2.89 ±0.00 | 2.89 ±0.00 | +0.3% (noise) | — |
nii_unique |
2.89 ±0.00 | 2.90 ±0.00 | +0.3% (noise) | — |
nii_get_array |
1.59 ±0.00 | 1.59 ±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 |
|---|---|---|---|---|
nii_apply_crop |
5.71 ±0.00 | 5.78 ±0.00 | +1.2% | — |
nii_rescale_seg |
5.79 ±0.00 | 5.86 ±0.00 | +1.2% | — |
nii_load_seg |
5.81 ±0.00 | 5.88 ±0.00 | +1.2% | — |
nii_rescale |
5.97 ±0.00 | 6.04 ±0.00 | +1.2% | — |
nii_resample_from_to |
6.10 ±0.00 | 6.17 ±0.00 | +1.2% | — |
… 31 more measurements
| Measurement | baseline MiB (median ±½·range) | head MiB (median ±½·range) | Δ % | p |
|---|---|---|---|---|
nii_get_connected_components |
6.46 ±0.00 | 6.53 ±0.00 | +1.1% | — |
nii_filter_connected_components |
6.71 ±0.00 | 6.78 ±0.00 | +1.0% | — |
nii_load_img |
3.49 ±0.00 | 3.53 ±0.00 | +1.0% | — |
poi_calc_poi_from_subreg_vert |
7.02 ±0.00 | 7.09 ±0.00 | +1.0% | — |
nii_save |
3.61 ±0.00 | 3.65 ±0.00 | +1.0% | — |
nii_dilate_msk |
5.86 ±0.00 | 5.80 ±0.00 | -0.9% | — |
nii_erode_msk |
5.88 ±0.00 | 5.82 ±0.00 | -0.9% | — |
poi_resample_from_to |
5.11 ±0.00 | 5.15 ±0.00 | +0.8% | — |
nii_set_dtype |
5.17 ±0.00 | 5.21 ±0.00 | +0.8% | — |
nii_extract_label |
5.23 ±0.00 | 5.28 ±0.00 | +0.8% | — |
nii_pad_to |
5.29 ±0.00 | 5.34 ±0.00 | +0.8% | — |
nii_map_labels |
5.36 ±0.00 | 5.40 ±0.00 | +0.8% | — |
nii_reorient |
5.73 ±0.00 | 5.78 ±0.00 | +0.7% | — |
poi_calc_centroids_nocrop |
5.79 ±0.00 | 5.84 ±0.00 | +0.7% | — |
nii_fill_holes |
6.15 ±0.00 | 6.20 ±0.00 | +0.7% | — |
poi_calc_centroids |
5.93 ±0.00 | 5.96 ±0.00 | +0.6% | — |
poi_to_global |
3.01 ±0.00 | 3.02 ±0.00 | +0.3% | — |
nii_center_of_masses |
3.20 ±0.00 | 3.21 ±0.00 | +0.2% | — |
nii_volumes |
3.46 ±0.00 | 3.47 ±0.00 | +0.2% | — |
poi_reorient |
3.76 ±0.00 | 3.77 ±0.00 | +0.2% | — |
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% | — |
poi_rescale |
2.54 ±0.00 | 2.54 ±0.00 | +0.3% (noise) | — |
nii_compute_crop |
2.83 ±0.00 | 2.84 ±0.00 | +0.3% (noise) | — |
poi_local_to_global_arr |
2.89 ±0.00 | 2.89 ±0.00 | +0.3% (noise) | — |
nii_unique |
2.89 ±0.00 | 2.90 ±0.00 | +0.3% (noise) | — |
nii_get_array |
1.59 ±0.00 | 1.59 ±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 |
|---|---|---|---|---|
nii_apply_crop |
5.71 ±0.00 | 5.78 ±0.00 | +1.2% | — |
nii_rescale_seg |
5.79 ±0.00 | 5.86 ±0.00 | +1.2% | — |
nii_load_seg |
5.81 ±0.00 | 5.88 ±0.00 | +1.2% | — |
nii_rescale |
5.97 ±0.00 | 6.04 ±0.00 | +1.2% | — |
nii_resample_from_to |
6.10 ±0.00 | 6.17 ±0.00 | +1.2% | — |
… 30 more measurements
| Measurement | baseline MiB (median ±½·range) | head MiB (median ±½·range) | Δ % | p |
|---|---|---|---|---|
nii_get_connected_components |
6.46 ±0.00 | 6.53 ±0.00 | +1.1% | — |
nii_filter_connected_components |
6.71 ±0.00 | 6.78 ±0.00 | +1.0% | — |
nii_load_img |
3.49 ±0.00 | 3.53 ±0.00 | +1.0% | — |
nii_save |
3.61 ±0.00 | 3.65 ±0.00 | +1.0% | — |
nii_dilate_msk |
5.86 ±0.00 | 5.80 ±0.00 | -0.9% | — |
nii_erode_msk |
5.88 ±0.00 | 5.82 ±0.00 | -0.9% | — |
poi_resample_from_to |
5.11 ±0.00 | 5.15 ±0.00 | +0.8% | — |
nii_set_dtype |
5.17 ±0.00 | 5.21 ±0.00 | +0.8% | — |
nii_extract_label |
5.23 ±0.00 | 5.28 ±0.00 | +0.8% | — |
nii_pad_to |
5.29 ±0.00 | 5.34 ±0.00 | +0.8% | — |
nii_map_labels |
5.36 ±0.00 | 5.40 ±0.00 | +0.8% | — |
nii_reorient |
5.73 ±0.00 | 5.78 ±0.00 | +0.7% | — |
poi_calc_centroids_nocrop |
5.86 ±0.00 | 5.90 ±0.00 | +0.7% | — |
poi_calc_centroids |
5.92 ±0.00 | 5.96 ±0.00 | +0.7% | — |
nii_fill_holes |
6.15 ±0.00 | 6.20 ±0.00 | +0.7% | — |
poi_to_global |
3.01 ±0.00 | 3.02 ±0.00 | +0.3% | — |
nii_center_of_masses |
3.20 ±0.00 | 3.21 ±0.00 | +0.2% | — |
nii_volumes |
3.45 ±0.00 | 3.46 ±0.00 | +0.2% | — |
poi_reorient |
3.76 ±0.00 | 3.77 ±0.00 | +0.2% | — |
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% | — |
poi_rescale |
2.54 ±0.00 | 2.54 ±0.00 | +0.3% (noise) | — |
nii_compute_crop |
2.83 ±0.00 | 2.84 ±0.00 | +0.3% (noise) | — |
poi_local_to_global_arr |
2.89 ±0.00 | 2.89 ±0.00 | +0.3% (noise) | — |
nii_unique |
2.89 ±0.00 | 2.90 ±0.00 | +0.3% (noise) | — |
nii_get_array |
1.59 ±0.00 | 1.59 ±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.
compute_crop_slice, apply_crop_slice and apply_crop_slice_ were thin deprecated wrappers around compute_crop / apply_crop / apply_crop_, carrying a "remove in version 1.0" marker. Nothing in the repository called them: the tutorials were migrated to the current names in 876980c, and the only remaining mention was a commented-out line in stitching_tools.py, updated here for consistency. Note that TPTBox.stitching.stitching.compute_crop_slice is unrelated and stays - it is a module-level helper taking a raw Nifti1Image, not an NII method. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GrWjhK9FCgmRGkHhhVFd2V
Three corrections to the packaging/naming choices in 6178510. Stitching function name Renamed GNC_stitch_T2w -> NAKO_stitch_T2w in stitching_tools.py, the TPTBox.stitching re-export, examples/nako/stitching_T2w.py and the README. Clean rename with no deprecated alias: the symbol was only ever public at the TPTBox.stitching level. (For the record, 6178510 did not rename anything - it corrected the README, which b214cc2 had changed to a name the code never had.) nnU-Net version constraint The previous >=2.4,<2.5 pin was asserted on an unverified premise and was actively harmful. nnUnet_utils is a self-contained fork: it reads the checkpoint's own plans.json and builds its own PlansManager, so it is not tied to the installed nnU-Net's plans layout. Checked against the 2.8.1 wheel, every symbol TPTBox imports still exists; the one signature change (recursive_find_python_class) only added defaulted parameters, and all three call sites pass three arguments. Meanwhile SPINEPS requires nnunetv2>=2.8.0 on Python 3.10+, so the old pin made TPTBox[seg] and spineps mutually uninstallable - it created the conflict the note blamed on the user. Now mirrors SPINEPS' own split: ==2.4.2 below Python 3.10 (the last release supporting it) and >=2.8,<3.0 from 3.10 on. Verified the lock carries both markers. The three places repeating the old claim are corrected, and the tutorial note is marked as Python-3.9-only advice. Not proven end to end: this is static evidence (import surface, signatures, wheel diff), not an inference run on 2.8.x. reg extra and correct install hints Added a reg extra (torch + hf-deepali), closing the gap where four docs told users to install hf-deepali while it was declared nowhere. spineps stays undeclared on purpose - it depends on TPTBox itself, so an extra would be circular; that is now recorded in pyproject and in the code. Fixed a bug from 6178510: the SPINEPS stubs pointed at pip install 'TPTBox[seg]', an extra that does not and cannot contain spineps, so following the message could not fix the error. optional_deps now accepts extra=None for backends with no extra. Migrated registration/__init__.py onto the shared optional_deps factories (-43 lines of duplication) and pointed its message at the new extra; test_registration_deepali's stub assertions updated to the unified message and strengthened to also check the extra and the original error. Also: nii_wrapper's antspyx failure message named hf-deepali, and getting-started claimed every optional backend is guarded when antspyx is not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GrWjhK9FCgmRGkHhhVFd2V
test_poi_landmark_registration_converges loaded the sample CT from /media/data/robert/code/TPTBox/... , an absolute path on one developer's machine, so it failed with FileNotFoundError for everyone else and in CI. The files it wants are packaged in the repo at TPTBox/tests/sample_ct/, and every other test in this file already reaches them via get_test_ct(). Switched to get_test_ct(), which returns exactly the (ct, subreg, vert) trio the test built by hand. The full suite is now green: 594 passed, 5 skipped, 0 failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GrWjhK9FCgmRGkHhhVFd2V
The tests workflow failed on all four matrix jobs with the same error:
test_all_subpackages_import
AssertionError: ["TPTBox.core.internal.train_nnUnet.fastProcessor:
No module named 'blosc2'"]
fastProcessor imports blosc2, a transitive dependency of nnunetv2 >= 2.6. It is
installed on the dev box but not in CI, so the gate passed locally and failed
remotely.
The underlying flaw was the design: the test allowlisted third-party package
names, which is unbounded and varies per machine and per CI job. Inverted it to
enumerate TPTBox's own surface instead, which is bounded and is what the test
actually cares about:
- test_required_entry_points_import asserts the public entry points
(TPTBox, .segmentation, .core.dicom, .registration, ...) import with no
optional backend present. This is the real regression guard - `import
TPTBox.segmentation` failing on a clean install was the original bug.
- test_no_subpackage_has_broken_internal_imports walks every module but fails
only when the missing module is positively identified as a TPTBox one. A
leaf module needing blosc2/torch/xvfbwrapper is an environment fact, not
drift. Both real drift modes still trip it: a stale path sets
name="TPTBox.gone", and a removed symbol sets name="TPTBox.mod" with
"cannot import name" - verified by injecting both.
The wider walk (the old one skipped underscore-prefixed packages) immediately
found a genuine pre-existing bug: registration/_deformable/grid_search.py
imported _load_config from deformable_reg, where it does not exist - it lives in
_deepali/deepali_model.py, which deformable_reg itself already imports from. The
module was un-importable on main too. Repointed at the real location.
Also renamed the tests_mr workflow to "tests (pull request)". Both test
workflows were named "tests" with a job called "build", so they reported
indistinguishable checks; `gh run list --workflow tests` fails outright with
"could not resolve to a unique workflow". Verified main has no branch protection,
so no required status check depends on the old name.
Local suite: 595 passed, 5 skipped, 0 failed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GrWjhK9FCgmRGkHhhVFd2V
CPU inference, MIP slab thickness, optional deps, doc drift
Addresses an external review of the public tutorials. Both reported implementation defects were real, and each turned out to also affect a sibling function.
Bug 1 - CUDA-only call on the CPU inference path
get_gpu_memory_MB/get_gpu_util called torch.cuda.mem_get_info() unconditionally.
That API rejects its argument rather than probing the environment, so
run_vibeseg(ddevice="cpu") died with "ValueError: Expected a cuda device, but
got: cpu" before predicting a single tile - even on a machine with a GPU. Both
helpers now dispatch on device.type like the neighbouring empty_cache(), and
export_prediction's fallback guard tests the requested device instead of merely
torch.cuda.is_available().
Bug 2 - compounding mm->voxel conversion in the curved-planar projections
curve_projected_mip converted thick_t from mm to voxels inside its per-slice
loop and assigned the result back to the same name, so slice n divided what
slice n-1 had already divided. At 0.5 mm spacing the slab grew geometrically
until int() raised "OverflowError: int too big to convert"; at 3 mm it shrank to
the 1-voxel floor within four slices, silently projecting a far thinner slab
than requested with no error at all. curve_projected_mean had the identical
defect, unguarded. Both now convert from the mm source via a shared helper, and
the try/except that was printing "thick infinity bug" is gone.
Optional dependencies
pydicom/dicom2nifti and the nnU-Net stack were imported at module top level but
declared nowhere, so
import TPTBox.segmentationfailed outright on a cleaninstall - the actual root cause of what looked like a SPINEPS/nnU-Net conflict.
Added [tool.poetry.extras] dicom and seg (nnunetv2 pinned >=2.4,<2.5) plus
import guards, so sub-packages import and only entry points raise, naming the
extra. _add_grid_info_to_json moved to core/internal/nii_help.py: it uses no
DICOM library, yet its old home made the public BIDS_FILE.get_grid_info() -
which the stitching tutorial calls - require pydicom.
Documentation and examples
Fixed 14 confirmed-broken snippets, including the homepage's first code sample
(.first() on a filter() that returns None), the stitching tutorial's
ProcessPoolExecutor cell (unpicklable under Windows/Jupyter spawn, and its
lazy map() swallowed every worker exception - now joblib), the obsolete
TPTBox.registration.deformable import, and seg_all.ipynb's four dead imports.
docs/modules/{spine,stitching}.md were hand-copies that had already diverged
from the in-package READMEs; they are now --8<-- includes.
Regression coverage
unit_tests/test_snapshot_mip.py pins both projection bugs (verified failing
before the fix in both spacing directions). unit_tests/test_docs_examples.py
statically parses every notebook and Markdown code block and asserts each
TPTBox module and symbol still exists - it flags all of the above when pointed
at the previous tree, and passes with or without the optional stack installed