Repository navigation
spinestats: version 3 — endplate-plane Cobb + apex + tightened multi-Cobb - #154
robert-graf wants to merge 35 commits into
Conversation
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>
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 |
|---|---|---|---|---|
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.
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>
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>
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-cleanupnako-follow-up-exportpoi-label-namesChanges
TPTBox/spine/spinestats/angles.py_get_normroutesVertebra_Direction_Rightthrough a new_endplate_r_directionhelper (analogue of_endplate_ap_direction), so Cobb lines are true endplate lines averaged across the shared disc.plot_cobb_anglealways uses the endplate right direction — the olduse_ivd_directionbranch usedVertebra_Disc_Inferiorfrom each vertebra separately, giving two different lines at one anatomic disc.compute_angel_between_two_points_orders id1/id2 byVertebra_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_multiuses an exclusive split (+ 1on thebelowslice) so sibling curves cannot share an endpoint vertebra.VERT_START_COBBis C3 (C2 excluded from Cobb search).compute_lordosis_and_kyphosisemits{curve}_apexkeys via a new_find_curve_apexhelper;plot_compute_lordosis_and_kyphosisrenders a*apexmarker.plot_cobb_angleanchors the label at the apex disc and widens the coronal pad from 50 to 80 mm.TPTBox/spine/spinestats/_run_all.pyPOI_INVALIDATING_VERSION = 2constant replaces a bare literal2in_poi_is_stale_wrt_stat; its docstring now references a symbol that actually exists (previously pointed at a nonexistentANGLES_VERSION).CURRENT_VERSIONhistory comment.TPTBox/spine/spinestats/veridah_angles.py_compute_k_variantalso returnslumbar_lordosis_apexso the per-k variants carry the new apex key.Test plan
pytest unit_tests/_run_all.pyintest=Truemode on a NAKO subject and confirmcobb/curvkeys,{curve}_apexvalues, and that a v2 stat file is recomputed to v3 without invalidating POIs.plot_cobb_anglesnapshot and aplot_compute_lordosis_and_kyphosissnapshot to verify the apex marker and label placement.🤖 Generated with Claude Code