Skip to content

Fix findings: - #150

Merged
Hendrik-code merged 6 commits into
mainfrom
fix/reviewer-findings
Sep 2, 2026
Merged

Fix findings: #150
Hendrik-code merged 6 commits into
mainfrom
fix/reviewer-findings

Conversation

@Hendrik-code

@Hendrik-code Hendrik-code commented Sep 2, 2026

Copy link
Copy Markdown
Owner

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.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

…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
@Hendrik-code Hendrik-code self-assigned this Sep 2, 2026
@Hendrik-code Hendrik-code added the bug Something isn't working label Sep 2, 2026
Comment thread TPTBox/core/nii_wrapper.py Outdated
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

Benchmark comparison

Speed (wall time per call)

baseline 2cafc2c vs head 2cafc2c · python 3.11.16 · 5 repeats + 1 warmup

Showing the 5 most-changed measurements per case; the rest are collapsed. Rows with a baseline below 3 ms are tagged (noise) — runner jitter on those swamps any real change.

ct_3d — shape (73, 47, 73)

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.

Comment thread TPTBox/stitching/README.md Outdated
Comment thread pyproject.toml Outdated
Comment thread pyproject.toml
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
Comment thread docs/getting-started.md
Hendrik-code and others added 3 commits September 2, 2026 12:01
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
@Hendrik-code
Hendrik-code merged commit 5b4b10b into main Sep 2, 2026
6 checks passed
@Hendrik-code
Hendrik-code deleted the fix/reviewer-findings branch September 2, 2026 13:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants