Skip to content

spinestats: version 3 — endplate-plane Cobb + apex + tightened multi-Cobb - #154

Open
robert-graf wants to merge 35 commits into
mainfrom
cleanup/cobb-endplate-v3
Open

robert-graf wants to merge 35 commits into
mainfrom
cleanup/cobb-endplate-v3

Conversation

@robert-graf

Copy link
Copy Markdown
Collaborator

Summary

Extends the endplate-plane direction machinery (used for v2 lordosis/kyphosis) to the coronal Cobb angle, bumps CURRENT_VERSION to 3 so cached stat files recompute the angle keys, and adds per-curve apex vertebra keys.

Stacks on top of these already-(mostly-)merged branches:

  • dicom-extract-cleanup
  • nako-follow-up-export
  • poi-label-names

Changes

TPTBox/spine/spinestats/angles.py

  • _get_norm routes Vertebra_Direction_Right through a new _endplate_r_direction helper (analogue of _endplate_ap_direction), so Cobb lines are true endplate lines averaged across the shared disc.
  • plot_cobb_angle always uses the endplate right direction — the old use_ivd_direction branch used Vertebra_Disc_Inferior from each vertebra separately, giving two different lines at one anatomic disc.
  • compute_angel_between_two_points_ orders id1/id2 by Vertebra_Instance.order_dict() rather than by numeric label, so T13 (label 28, anatomically between T12 and L1) pairs to the right endplates.
  • compute_max_cobb_angle_multi uses an exclusive split (+ 1 on the below slice) so sibling curves cannot share an endpoint vertebra.
  • VERT_START_COBB is C3 (C2 excluded from Cobb search).
  • compute_lordosis_and_kyphosis emits {curve}_apex keys via a new _find_curve_apex helper; plot_compute_lordosis_and_kyphosis renders a *apex marker.
  • plot_cobb_angle anchors the label at the apex disc and widens the coronal pad from 50 to 80 mm.

TPTBox/spine/spinestats/_run_all.py

  • New POI_INVALIDATING_VERSION = 2 constant replaces a bare literal 2 in _poi_is_stale_wrt_stat; its docstring now references a symbol that actually exists (previously pointed at a nonexistent ANGLES_VERSION).
  • Documents the v3 bump in the CURRENT_VERSION history comment.

TPTBox/spine/spinestats/veridah_angles.py

  • _compute_k_variant also returns lumbar_lordosis_apex so the per-k variants carry the new apex key.

Test plan

  • pytest unit_tests/
  • Run _run_all.py in test=True mode on a NAKO subject and confirm cobb/curv keys, {curve}_apex values, and that a v2 stat file is recomputed to v3 without invalidating POIs.
  • Eyeball a plot_cobb_angle snapshot and a plot_compute_lordosis_and_kyphosis snapshot to verify the apex marker and label placement.

🤖 Generated with Claude Code

robert-graf and others added 25 commits August 26, 2026 14:42
Roll the internal-scripts changes together with the new
precompute_grid_info_parallel helper in _load_nako_wh, which fans
per-file _add_grid_info_to_json calls out to a ProcessPoolExecutor so
the sidecar grid cache is populated up front.
Adds a small registration mechanism so producers can attach
per-vertebra direction vectors (endplate PCA normals, orientation
vectors, ...) or label-keyed scalars (endplate_internal_angle,
curvature_*) to poi.info and have them stay aligned with the POI
points automatically:

- New TPTBox/core/poi_fun/vector_fields.py with the constants
  POI_INFO_VECTOR_FIELDS_KEY / POI_INFO_LABEL_KEYED_FIELDS_KEY and
  three helpers (_transform_direction_vectors_inplace,
  _rotate_direction_vectors_inplace, _remap_vector_field_keys_inplace).
- POI.reorient signed-permutes vectors; POI.resample_from_to applies
  the real R_ref.T @ R_self rotation; POI.rescale is a no-op.
- POI_Global.to_cord_system flips vector components on RAS<->LPS.
- POI.map_labels remaps top-level keys of both registries.
- endplates.py registers angle_superior/inferior_endplate,
  endplate_internal_angle, and curvature_* when they're produced.
- Fix: vertebra_pois_non_centroids.py now (re)runs
  calc_endplate_points_ when S1 is present but its Sacrum_Endplate-
  derived superior-endplate landmark is missing, so the sacrum
  endplate direction actually lands in the buffer.
- Angles: _endplate_ap_direction preferred over Vertebra_Direction_
  Posterior for MoveTo.TOP/BOTTOM in _get_norm, yielding endplate-
  aligned Cobb-style lordosis/kyphosis lines.

Docs: new section in docs/api/poi_fun.md.
Tests: unit_tests/test_poi_vector_fields.py (13 tests).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Merges the poi-label-names branch (LABEL_NAME + nested
{region: {subregion: name, "name": group}} format in info["label_name"],
new label_name / set_label_name / level_one_name / set_level_one_name
accessors, migration helpers, and level_*_info propagation in copy /
_set_inplace / calc_poi_average / POI_Global.to_other).

Then wires label_name into the auto-transform mapping scheme:

- New vector_fields._remap_label_name_inplace handles the nested
  format: remaps top-level region keys via label_map_region and
  inner subregion keys via label_map_subregion, preserving the
  "name" group entry. Colliding regions merge with last-write-wins
  on inner keys.
- map_labels calls both _remap_vector_field_keys_inplace (for the
  registered flat vector / label-keyed fields) and the new
  _remap_label_name_inplace on all three return paths.
- Also triggers the legacy flat-format migration on remap.

Docs updated with a "Special-cased field" section for label_name.
Tests: 5 new cases in Test_LabelName_MapLabels covering region-only,
subregion-only, combined, region collision, and legacy-format
migration paths (18/18 passing).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…Cobb

Extends the endplate-plane direction machinery used for v2
lordosis/kyphosis to the coronal Cobb angle, and bumps
``CURRENT_VERSION`` to 3 so cached stat files recompute the angle keys
(POI buffers stay valid — no new landmarks).

Angles (``TPTBox/spine/spinestats/angles.py``):
- ``_get_norm`` now routes ``Vertebra_Direction_Right`` through a new
  ``_endplate_r_direction`` helper (analogue of ``_endplate_ap_direction``),
  so Cobb lines are true endplate lines averaged across the shared disc
  instead of the two flanking vertebrae disagreeing on the same boundary.
- ``plot_cobb_angle`` always uses that endplate right direction — the
  ``use_ivd_direction`` branch used ``Vertebra_Disc_Inferior`` from each
  vertebra separately, giving two different lines at one anatomic disc.
- ``compute_angel_between_two_points_`` orders ``id1``/``id2`` by
  ``Vertebra_Instance.order_dict()`` rather than by numeric label, so T13
  (label 28, anatomically between T12 and L1) pairs to the right endplates
  and ``MoveTo.TOP``/``BOTTOM`` are not swapped.
- ``compute_max_cobb_angle_multi`` uses an exclusive split (``+ 1`` on the
  ``below`` slice) so sibling curves cannot share an endpoint vertebra.
- ``VERT_START_COBB`` is C3 (C2 excluded from Cobb search).
- ``compute_lordosis_and_kyphosis`` emits ``{curve}_apex`` keys, computed
  by the new ``_find_curve_apex`` helper (same bisector heuristic as
  ``compute_max_cobb_angle``). ``plot_compute_lordosis_and_kyphosis``
  renders a ``*apex`` marker at that vertebra.
- ``plot_cobb_angle`` anchors the label at the apex disc and widens the
  coronal pad from 50 to 80 mm so labels don't get clipped.

Runner (``TPTBox/spine/spinestats/_run_all.py``):
- Introduces ``POI_INVALIDATING_VERSION = 2`` so the ``_poi_is_stale_wrt_stat``
  threshold is a named constant rather than a magic ``2``, and its docstring
  points at a symbol that actually exists (previously referenced a
  nonexistent ``ANGLES_VERSION``).
- Documents the v3 bump in the ``CURRENT_VERSION`` history comment.

Veridah (``TPTBox/spine/spinestats/veridah_angles.py``):
- ``_compute_k_variant`` also returns ``lumbar_lordosis_apex`` so the
  per-k variants carry the new apex key.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Benchmark comparison

Speed (wall time per call)

baseline 7d95bc9 vs head 7d95bc9 · python 3.11.17 · 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
poi_calc_centroids_nocrop 4.61 ±0.26 4.21 ±0.10 -8.7% 0.042
nii_rescale_seg 3.41 ±0.18 3.19 ±0.08 -6.6% 0.037
nii_fill_holes 4.46 ±0.07 4.58 ±0.06 +2.8% 0.083
poi_calc_poi_from_subreg_vert 12.91 ±0.78 13.18 ±0.63 +2.1% 0.910
nii_filter_connected_components 4.42 ±0.12 4.51 ±0.15 +1.9% 0.115
… 31 more measurements
Measurement baseline ms (median ±½·range) head ms (median ±½·range) Δ % p
nii_erode_msk 6.21 ±0.03 6.32 ±0.03 +1.8% 0.000
nii_save 11.85 ±0.16 12.02 ±0.13 +1.5% 0.069
nii_resample_from_to 42.06 ±0.22 42.60 ±0.19 +1.3% 0.002
nii_rescale 39.89 ±0.20 40.36 ±0.29 +1.2% 0.013
nii_dilate_msk 126.19 ±0.45 126.70 ±0.62 +0.4% 0.071
nii_load_img 10.93 ±0.77 10.93 ±0.06 +0.1% 0.390
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_save 0.53 ±0.02 0.35 ±0.01 -34.6% (noise) 0.000
poi_to_global 0.21 ±0.03 0.14 ±0.01 -30.2% (noise) 0.005
poi_resample_from_to 0.77 ±0.04 0.59 ±0.02 -23.9% (noise) 0.000
poi_map_labels 0.20 ±0.01 0.15 ±0.01 -22.7% (noise) 0.000
poi_load 1.09 ±0.04 0.89 ±0.05 -18.2% (noise) 0.001
poi_local_to_global_arr 0.32 ±0.05 0.27 ±0.01 -15.9% (noise) 0.042
poi_rescale 0.20 ±0.02 0.17 ±0.01 -15.7% (noise) 0.051
nii_load_seg 2.75 ±0.42 3.09 ±0.12 +12.3% (noise) 0.816
poi_reorient 0.37 ±0.03 0.33 ±0.02 -10.6% (noise) 0.026
nii_apply_crop 0.69 ±0.04 0.74 ±0.02 +6.9% (noise) 0.015
nii_reorient 0.90 ±0.07 0.85 ±0.08 -6.0% (noise) 0.954
nii_extract_label 0.43 ±0.02 0.45 ±0.05 +5.0% (noise) 0.885
nii_get_connected_components 2.28 ±0.27 2.37 ±0.09 +3.8% (noise) 0.570
nii_get_array 0.05 ±0.01 0.05 ±0.00 -3.8% (noise) 0.334
nii_set_dtype 0.53 ±0.05 0.55 ±0.05 +3.5% (noise) 0.355
poi_calc_centroids 2.43 ±0.08 2.37 ±0.02 -2.2% (noise) 0.088
nii_volumes 1.87 ±0.01 1.90 ±0.01 +2.0% (noise) 0.012
nii_unique 0.69 ±0.03 0.70 ±0.01 +1.6% (noise) 0.871
nii_compute_crop 0.33 ±0.01 0.33 ±0.02 +0.9% (noise) 0.286
nii_center_of_masses 1.75 ±0.03 1.76 ±0.01 +0.8% (noise) 0.584
nii_pad_to 0.70 ±0.05 0.70 ±0.06 -0.5% (noise) 0.258
nii_map_labels 1.09 ±0.03 1.09 ±0.03 -0.1% (noise) 0.460

ct_2d — shape (73, 47, 1)

Measurement baseline ms (median ±½·range) head ms (median ±½·range) Δ % p
nii_resample_from_to 7.61 ±0.20 8.01 ±0.74 +5.3% 0.111
nii_rescale 5.64 ±0.10 5.71 ±0.18 +1.2% 0.868
nii_dilate_msk 14.89 ±0.08 14.89 ±0.08 +0.0% 0.603
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_unique 0.15 ±0.06 0.13 ±0.00 -14.6% (noise) 0.100
poi_save 0.55 ±0.08 0.47 ±0.02 -14.0% (noise) 0.110
nii_get_array 0.02 ±0.00 0.02 ±0.00 +13.0% (noise) 0.170
nii_center_of_masses 0.25 ±0.02 0.22 ±0.01 -11.9% (noise) 0.036
nii_apply_crop 0.69 ±0.03 0.75 ±0.04 +8.1% (noise) 0.190
poi_map_labels 0.22 ±0.01 0.20 ±0.01 -7.9% (noise) 0.003
nii_get_connected_components 0.70 ±0.02 0.75 ±0.04 +6.5% (noise) 0.047
poi_reorient 0.50 ±0.02 0.47 ±0.02 -6.4% (noise) 0.019
nii_extract_label 0.49 ±0.02 0.46 ±0.02 -6.2% (noise) 0.051
nii_rescale_seg 0.84 ±0.03 0.89 ±0.02 +6.1% (noise) 0.025
nii_fill_holes 0.93 ±0.01 0.98 ±0.04 +5.6% (noise) 0.066
poi_to_global 0.25 ±0.09 0.24 ±0.02 -5.1% (noise) 0.341
nii_save 1.54 ±0.05 1.46 ±0.02 -5.1% (noise) 0.004
nii_set_dtype 0.46 ±0.02 0.48 ±0.02 +5.1% (noise) 0.345
poi_local_to_global_arr 0.34 ±0.01 0.32 ±0.02 -4.0% (noise) 0.425
nii_erode_msk 1.10 ±0.06 1.07 ±0.02 -2.6% (noise) 0.154
poi_calc_centroids 1.00 ±0.02 0.98 ±0.01 -2.5% (noise) 0.061
nii_compute_crop 0.15 ±0.01 0.15 ±0.01 +1.8% (noise) 0.844
nii_filter_connected_components 0.89 ±0.02 0.91 ±0.19 +1.6% (noise) 0.301
nii_load_seg 1.82 ±0.38 1.85 ±0.20 +1.6% (noise) 0.684
poi_rescale 0.26 ±0.01 0.26 ±0.02 -1.4% (noise) 0.986
nii_reorient 0.90 ±0.09 0.89 ±0.04 -1.4% (noise) 0.299
nii_map_labels 0.54 ±0.08 0.53 ±0.02 -1.1% (noise) 0.437
poi_calc_centroids_nocrop 1.22 ±0.24 1.22 ±0.02 +0.6% (noise) 0.442
nii_pad_to 0.61 ±0.04 0.61 ±0.01 +0.4% (noise) 0.264
nii_load_img 1.63 ±0.03 1.63 ±0.03 +0.4% (noise) 0.481
poi_load 1.11 ±0.08 1.11 ±0.06 -0.1% (noise) 0.290
nii_volumes 0.31 ±0.02 0.31 ±0.01 +0.1% (noise) 0.993
poi_resample_from_to 0.79 ±0.03 0.79 ±0.02 +0.1% (noise) 0.857

mri_3d — shape (68, 52, 67)

Measurement baseline ms (median ±½·range) head ms (median ±½·range) Δ % p
nii_fill_holes 5.41 ±0.09 5.66 ±0.08 +4.6% 0.002
poi_calc_poi_from_subreg_vert 11.97 ±0.06 12.40 ±0.08 +3.6% 0.000
nii_filter_connected_components 4.70 ±0.10 4.87 ±0.07 +3.6% 0.005
nii_erode_msk 7.80 ±0.06 8.01 ±0.27 +2.6% 0.055
poi_calc_centroids_nocrop 8.93 ±0.17 9.07 ±0.15 +1.6% 0.393
… 31 more measurements
Measurement baseline ms (median ±½·range) head ms (median ±½·range) Δ % p
nii_load_img 8.86 ±0.14 8.74 ±0.11 -1.4% 0.074
nii_rescale_seg 3.42 ±0.09 3.39 ±0.12 -1.0% 0.409
nii_save 9.41 ±0.10 9.35 ±0.10 -0.6% 0.298
nii_rescale 38.09 ±0.42 37.92 ±0.28 -0.4% 0.266
nii_resample_from_to 40.26 ±0.41 40.11 ±0.45 -0.4% 0.522
nii_dilate_msk 164.24 ±0.28 164.03 ±1.97 -0.1% 0.833
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_extract_label 0.51 ±0.02 0.45 ±0.02 -11.4% (noise) 0.005
nii_get_array 0.05 ±0.00 0.06 ±0.00 +10.7% (noise) 0.003
nii_set_dtype 0.58 ±0.06 0.53 ±0.01 -9.1% (noise) 0.071
nii_map_labels 1.12 ±0.06 1.03 ±0.05 -8.0% (noise) 0.021
poi_rescale 0.31 ±0.01 0.29 ±0.01 -6.9% (noise) 0.006
poi_to_global 0.27 ±0.02 0.26 ±0.02 -4.6% (noise) 0.503
poi_load 2.46 ±0.15 2.35 ±0.31 -4.4% (noise) 0.580
poi_calc_centroids 2.81 ±0.06 2.93 ±0.08 +4.2% (noise) 0.068
nii_unique 0.70 ±0.02 0.67 ±0.01 -4.2% (noise) 0.017
nii_compute_crop 0.34 ±0.03 0.33 ±0.01 -3.5% (noise) 0.367
nii_reorient 0.94 ±0.03 0.91 ±0.05 -3.4% (noise) 0.307
poi_resample_from_to 0.79 ±0.02 0.81 ±0.02 +2.1% (noise) 0.199
poi_save 0.57 ±0.02 0.56 ±0.02 -2.0% (noise) 0.357
poi_map_labels 0.25 ±0.02 0.25 ±0.02 -1.8% (noise) 0.887
nii_pad_to 0.76 ±0.04 0.75 ±0.04 -1.5% (noise) 0.748
nii_center_of_masses 2.01 ±0.08 1.99 ±0.02 -1.3% (noise) 0.195
nii_apply_crop 0.72 ±0.02 0.71 ±0.01 -0.9% (noise) 0.511
nii_get_connected_components 2.36 ±0.17 2.38 ±0.12 +0.9% (noise) 0.684
poi_local_to_global_arr 0.35 ±0.02 0.35 ±0.01 -0.7% (noise) 0.315
poi_reorient 0.48 ±0.01 0.48 ±0.03 +0.3% (noise) 0.871
nii_volumes 1.97 ±0.03 1.98 ±0.02 +0.3% (noise) 0.910
nii_load_seg 2.91 ±0.07 2.91 ±0.09 -0.1% (noise) 0.553

mri_2d — shape (68, 52, 1)

Measurement baseline ms (median ±½·range) head ms (median ±½·range) Δ % p
nii_dilate_msk 19.64 ±0.09 20.20 ±0.05 +2.8% 0.000
nii_resample_from_to 7.60 ±0.09 7.65 ±0.07 +0.7% 0.174
nii_rescale 5.48 ±0.16 5.46 ±0.05 -0.4% 0.666
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_compute_crop 0.14 ±0.00 0.16 ±0.01 +12.5% (noise) 0.004
poi_rescale 0.28 ±0.01 0.32 ±0.04 +11.7% (noise) 0.113
poi_resample_from_to 0.80 ±0.02 0.89 ±0.01 +11.4% (noise) 0.000
nii_pad_to 0.54 ±0.02 0.59 ±0.04 +9.3% (noise) 0.095
poi_local_to_global_arr 0.36 ±0.02 0.33 ±0.01 -8.0% (noise) 0.031
poi_save 0.57 ±0.03 0.61 ±0.04 +7.5% (noise) 0.510
nii_rescale_seg 0.78 ±0.03 0.83 ±0.09 +7.3% (noise) 0.226
nii_unique 0.12 ±0.01 0.13 ±0.01 +7.2% (noise) 0.085
nii_save 1.51 ±0.07 1.40 ±0.02 -6.8% (noise) 0.036
nii_extract_label 0.42 ±0.02 0.45 ±0.02 +6.4% (noise) 0.170
poi_calc_centroids 1.19 ±0.10 1.25 ±0.21 +4.8% (noise) 0.405
poi_calc_centroids_nocrop 1.45 ±0.03 1.52 ±0.02 +4.4% (noise) 0.017
nii_erode_msk 1.34 ±0.01 1.40 ±0.02 +4.3% (noise) 0.000
nii_fill_holes 1.16 ±0.03 1.19 ±0.23 +2.9% (noise) 0.256
poi_to_global 0.27 ±0.03 0.27 ±0.00 +2.9% (noise) 0.765
nii_get_connected_components 0.69 ±0.04 0.67 ±0.02 -2.8% (noise) 0.182
nii_map_labels 0.55 ±0.03 0.53 ±0.01 -2.5% (noise) 0.725
nii_filter_connected_components 0.89 ±0.04 0.91 ±0.01 +2.3% (noise) 0.345
nii_get_array 0.02 ±0.01 0.02 ±0.00 +2.2% (noise) 0.990
nii_load_img 1.62 ±0.10 1.59 ±0.09 -2.1% (noise) 0.289
poi_reorient 0.48 ±0.02 0.49 ±0.02 +1.5% (noise) 0.307
nii_set_dtype 0.42 ±0.02 0.43 ±0.01 +1.3% (noise) 0.759
nii_load_seg 1.78 ±0.06 1.80 ±0.23 +1.1% (noise) 0.397
nii_reorient 0.87 ±0.05 0.88 ±0.01 +1.0% (noise) 0.520
nii_apply_crop 0.67 ±0.04 0.67 ±0.01 +1.0% (noise) 0.710
nii_volumes 0.50 ±0.01 0.50 ±0.02 -0.7% (noise) 0.267
nii_center_of_masses 0.44 ±0.02 0.44 ±0.01 +0.5% (noise) 0.938
poi_load 2.21 ±0.09 2.20 ±0.15 -0.4% (noise) 0.916
poi_map_labels 0.24 ±0.01 0.24 ±0.01 +0.2% (noise) 0.678

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 7d95bc9 vs head 7d95bc9 · python 3.11.17 · 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.18 ±0.00 5.27 ±0.00 +1.8% —
nii_reorient 5.81 ±0.00 5.91 ±0.00 +1.6% —
nii_rescale 6.07 ±0.00 6.15 ±0.00 +1.3% —
nii_resample_from_to 6.20 ±0.00 6.28 ±0.00 +1.3% —
nii_load_seg 5.91 ±0.00 5.99 ±0.00 +1.3% —
… 31 more measurements
Measurement baseline MiB (median ±½·range) head MiB (median ±½·range) Δ % p
nii_dilate_msk 5.86 ±0.00 5.93 ±0.00 +1.1% —
nii_erode_msk 5.88 ±0.00 5.95 ±0.00 +1.1% —
nii_set_dtype 5.19 ±0.00 5.24 ±0.00 +1.1% —
nii_extract_label 5.31 ±0.00 5.36 ±0.00 +1.0% —
nii_pad_to 5.37 ±0.00 5.42 ±0.00 +0.9% —
nii_map_labels 5.44 ±0.00 5.49 ±0.00 +0.9% —
poi_calc_centroids_nocrop 5.87 ±0.00 5.93 ±0.00 +0.9% —
poi_calc_centroids 6.00 ±0.00 6.05 ±0.00 +0.9% —
nii_load_img 3.52 ±0.00 3.54 ±0.00 +0.7% —
nii_rescale_seg 5.89 ±0.00 5.93 ±0.00 +0.7% —
nii_apply_crop 5.81 ±0.00 5.84 ±0.00 +0.6% —
poi_calc_poi_from_subreg_vert 7.05 ±0.00 7.09 ±0.00 +0.6% —
nii_get_connected_components 6.56 ±0.00 6.60 ±0.00 +0.5% —
nii_filter_connected_components 6.81 ±0.00 6.85 ±0.00 +0.5% 0.000
nii_save 3.70 ±0.00 3.69 ±0.00 -0.3% —
poi_to_global 3.08 ±0.00 3.08 ±0.00 +0.1% —
nii_fill_holes 6.27 ±0.00 6.28 ±0.00 +0.1% —
poi_reorient 3.83 ±0.00 3.83 ±0.00 +0.1% —
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% —
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_save 1.39 ±0.00 1.38 ±0.00 -0.8% (noise) —
poi_load 1.61 ±0.00 1.61 ±0.00 +0.2% (noise) —
nii_get_array 1.64 ±0.00 1.65 ±0.00 +0.2% (noise) —
poi_map_labels 1.77 ±0.00 1.77 ±0.00 +0.2% (noise) —
poi_local_to_global_arr 2.95 ±0.00 2.96 ±0.00 +0.1% (noise) —
nii_compute_crop 2.89 ±0.00 2.89 ±0.00 +0.0% (noise) —
nii_unique 2.96 ±0.00 2.96 ±0.00 +0.0% (noise) —
poi_rescale 2.60 ±0.00 2.60 ±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.12 ±0.00 5.21 ±0.00 +1.8% —
nii_reorient 5.75 ±0.00 5.84 ±0.00 +1.6% —
nii_rescale 6.01 ±0.00 6.09 ±0.00 +1.3% —
nii_resample_from_to 6.13 ±0.00 6.21 ±0.00 +1.3% —
nii_load_seg 5.85 ±0.00 5.92 ±0.00 +1.3% —
… 30 more measurements
Measurement baseline MiB (median ±½·range) head MiB (median ±½·range) Δ % p
nii_dilate_msk 5.80 ±0.00 5.86 ±0.00 +1.1% —
nii_erode_msk 5.81 ±0.00 5.88 ±0.00 +1.1% —
nii_set_dtype 5.12 ±0.00 5.18 ±0.00 +1.1% —
nii_extract_label 5.25 ±0.00 5.30 ±0.00 +1.0% —
nii_pad_to 5.30 ±0.00 5.36 ±0.00 +1.0% —
nii_map_labels 5.37 ±0.00 5.43 ±0.00 +1.0% —
poi_calc_centroids_nocrop 5.87 ±0.00 5.92 ±0.00 +0.9% —
poi_calc_centroids 5.93 ±0.00 5.99 ±0.00 +0.9% —
nii_apply_crop 5.74 ±0.00 5.78 ±0.00 +0.7% —
nii_load_img 3.45 ±0.00 3.48 ±0.00 +0.7% —
nii_rescale_seg 5.82 ±0.00 5.86 ±0.00 +0.7% —
nii_get_connected_components 6.50 ±0.00 6.54 ±0.00 +0.6% —
nii_filter_connected_components 6.75 ±0.00 6.79 ±0.00 +0.6% —
nii_save 3.64 ±0.00 3.62 ±0.00 -0.3% —
poi_to_global 3.01 ±0.00 3.02 ±0.00 +0.1% —
nii_fill_holes 6.21 ±0.00 6.21 ±0.00 +0.1% —
nii_center_of_masses 3.20 ±0.00 3.21 ±0.00 +0.1% —
nii_volumes 3.45 ±0.00 3.46 ±0.00 +0.1% —
poi_reorient 3.76 ±0.00 3.77 ±0.00 +0.1% —
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_save 1.33 ±0.00 1.32 ±0.00 -0.9% (noise) —
poi_load 1.54 ±0.00 1.55 ±0.00 +0.3% (noise) —
nii_get_array 1.58 ±0.00 1.58 ±0.00 +0.2% (noise) —
poi_map_labels 1.70 ±0.00 1.71 ±0.00 +0.2% (noise) —
poi_rescale 2.54 ±0.00 2.54 ±0.00 +0.2% (noise) —
nii_compute_crop 2.83 ±0.00 2.83 ±0.00 +0.1% (noise) —
poi_local_to_global_arr 2.89 ±0.00 2.89 ±0.00 +0.1% (noise) —
nii_unique 2.89 ±0.00 2.89 ±0.00 +0.1% (noise) —

mri_3d — shape (68, 52, 67)

Measurement baseline MiB (median ±½·range) head MiB (median ±½·range) Δ % p
poi_resample_from_to 5.12 ±0.00 5.21 ±0.00 +1.8% —
nii_reorient 5.75 ±0.00 5.84 ±0.00 +1.6% —
nii_rescale 6.01 ±0.00 6.09 ±0.00 +1.3% —
nii_resample_from_to 6.13 ±0.00 6.21 ±0.00 +1.3% —
nii_load_seg 5.85 ±0.00 5.92 ±0.00 +1.3% —
… 31 more measurements
Measurement baseline MiB (median ±½·range) head MiB (median ±½·range) Δ % p
nii_dilate_msk 5.80 ±0.00 5.86 ±0.00 +1.1% —
nii_erode_msk 5.81 ±0.00 5.88 ±0.00 +1.1% —
nii_set_dtype 5.18 ±0.00 5.24 ±0.00 +1.1% —
nii_extract_label 5.25 ±0.00 5.30 ±0.00 +1.0% —
nii_pad_to 5.30 ±0.00 5.36 ±0.00 +1.0% —
nii_map_labels 5.37 ±0.00 5.43 ±0.00 +1.0% —
poi_calc_centroids_nocrop 5.80 ±0.00 5.86 ±0.00 +0.9% —
poi_calc_centroids 5.93 ±0.00 5.99 ±0.00 +0.9% —
nii_apply_crop 5.74 ±0.00 5.78 ±0.00 +0.7% —
nii_rescale_seg 5.82 ±0.00 5.86 ±0.00 +0.7% —
nii_load_img 3.52 ±0.00 3.54 ±0.00 +0.7% —
nii_get_connected_components 6.50 ±0.00 6.54 ±0.00 +0.6% —
nii_filter_connected_components 6.75 ±0.00 6.79 ±0.00 +0.6% —
poi_calc_poi_from_subreg_vert 7.05 ±0.00 7.09 ±0.00 +0.6% —
nii_save 3.64 ±0.00 3.62 ±0.00 -0.3% —
poi_to_global 3.01 ±0.00 3.02 ±0.00 +0.1% —
nii_fill_holes 6.21 ±0.00 6.21 ±0.00 +0.1% —
nii_center_of_masses 3.20 ±0.00 3.21 ±0.00 +0.1% —
nii_volumes 3.45 ±0.00 3.46 ±0.00 +0.1% —
poi_reorient 3.76 ±0.00 3.77 ±0.00 +0.1% —
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_save 1.39 ±0.00 1.38 ±0.00 -0.8% (noise) —
poi_load 1.54 ±0.00 1.55 ±0.00 +0.3% (noise) —
nii_get_array 1.58 ±0.00 1.58 ±0.00 +0.2% (noise) —
poi_map_labels 1.70 ±0.00 1.71 ±0.00 +0.2% (noise) —
poi_rescale 2.54 ±0.00 2.54 ±0.00 +0.2% (noise) —
nii_compute_crop 2.83 ±0.00 2.83 ±0.00 +0.1% (noise) —
poi_local_to_global_arr 2.89 ±0.00 2.89 ±0.00 +0.1% (noise) —
nii_unique 2.89 ±0.00 2.89 ±0.00 +0.1% (noise) —

mri_2d — shape (68, 52, 1)

Measurement baseline MiB (median ±½·range) head MiB (median ±½·range) Δ % p
poi_resample_from_to 5.12 ±0.00 5.21 ±0.00 +1.8% —
nii_reorient 5.75 ±0.00 5.84 ±0.00 +1.6% —
nii_rescale 6.01 ±0.00 6.09 ±0.00 +1.3% —
nii_resample_from_to 6.13 ±0.00 6.21 ±0.00 +1.3% —
nii_load_seg 5.85 ±0.00 5.92 ±0.00 +1.3% —
… 30 more measurements
Measurement baseline MiB (median ±½·range) head MiB (median ±½·range) Δ % p
nii_dilate_msk 5.80 ±0.00 5.86 ±0.00 +1.1% —
nii_erode_msk 5.81 ±0.00 5.88 ±0.00 +1.1% —
nii_set_dtype 5.18 ±0.00 5.24 ±0.00 +1.1% —
nii_extract_label 5.25 ±0.00 5.30 ±0.00 +1.0% —
nii_pad_to 5.30 ±0.00 5.36 ±0.00 +1.0% —
nii_map_labels 5.37 ±0.00 5.43 ±0.00 +1.0% —
poi_calc_centroids_nocrop 5.87 ±0.00 5.92 ±0.00 +0.9% —
poi_calc_centroids 5.93 ±0.00 5.99 ±0.00 +0.9% —
nii_apply_crop 5.74 ±0.00 5.78 ±0.00 +0.7% —
nii_rescale_seg 5.82 ±0.00 5.86 ±0.00 +0.7% —
nii_load_img 3.52 ±0.00 3.54 ±0.00 +0.7% —
nii_get_connected_components 6.50 ±0.00 6.54 ±0.00 +0.6% —
nii_filter_connected_components 6.75 ±0.00 6.79 ±0.00 +0.6% —
nii_save 3.64 ±0.00 3.62 ±0.00 -0.3% —
poi_reorient 3.76 ±0.00 3.77 ±0.00 +0.2% —
poi_to_global 3.01 ±0.00 3.02 ±0.00 +0.1% —
nii_fill_holes 6.21 ±0.00 6.21 ±0.00 +0.1% —
nii_center_of_masses 3.20 ±0.00 3.21 ±0.00 +0.1% —
nii_volumes 3.45 ±0.00 3.46 ±0.00 +0.1% —
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_save 1.39 ±0.00 1.38 ±0.00 -0.8% (noise) —
poi_load 1.54 ±0.00 1.55 ±0.00 +0.3% (noise) —
nii_get_array 1.58 ±0.00 1.58 ±0.00 +0.2% (noise) —
poi_map_labels 1.70 ±0.00 1.71 ±0.00 +0.2% (noise) —
poi_rescale 2.54 ±0.00 2.54 ±0.00 +0.2% (noise) —
nii_compute_crop 2.83 ±0.00 2.83 ±0.00 +0.1% (noise) —
poi_local_to_global_arr 2.89 ±0.00 2.89 ±0.00 +0.1% (noise) —
nii_unique 2.89 ±0.00 2.89 ±0.00 +0.1% (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.

robert-graf and others added 3 commits September 22, 2026 11:10
Cherry-picked content from tanja@50c8b4d without the whitespace-only
reformatting noise:
- Add h2d/tse2d1-4/tser2d regex to series-description → format mapping;
  drop the broad ".*t2w?_tse.*" match.
- Fall back to SequenceName when SeriesDescription is empty.

Co-Authored-By: Tanja Lerchl <tanja.lerchl@tum.de>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@robert-graf robert-graf added the enhancement New feature or request label Sep 22, 2026
robert-graf and others added 6 commits September 22, 2026 11:18
Manual fixes in ``TPTBox/spine/spinestats/_load_nako_wh.py``:
- D205 (verify_missing_images, verify_hardlink): reword docstrings so the
  summary line stands alone from the description body.
- PLW2901 (``v = preferred`` at line 787): add ``# noqa: PLW2901`` to
  match the existing noqa on the sibling ``k = mapping.get(k, k)``
  rebinding — the loop deliberately overrides both bindings.
- PERF102 (line 1385): iterate the ``t2w_chunk`` mapping via ``.values()``
  since only the value is used.
- TRY300 (``_grid_worker``): move the success ``return`` out of the
  ``try`` block; only ``_add_grid_info_to_json`` may raise.

Everything else is ``ruff check --fix`` and ``ruff format`` output.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants