From 3ec43f3784c821ed1a6c3fa69ef738c71c870949 Mon Sep 17 00:00:00 2001 From: iback Date: Fri, 22 May 2026 20:17:28 +0000 Subject: [PATCH 1/7] changed order for split key --- TPTBox/core/bids_constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TPTBox/core/bids_constants.py b/TPTBox/core/bids_constants.py index d0ab80a..e146dd4 100755 --- a/TPTBox/core/bids_constants.py +++ b/TPTBox/core/bids_constants.py @@ -308,6 +308,7 @@ "Hemisphere": "hemi", # [L,R] "Sample": "sample", # such as tissue, primary cell or cell-free sample. # Sub recordings - Use when necessary. + "Split": "split", # Never use, legacy. Is applied inconsistently by other datasets. ## Contrast "Contrast enhancement phase": "ce", "Tracer": "trc", # use ce before this one. @@ -337,7 +338,6 @@ # Single class segmentation "Label": "label", # Others (never used) - "Split": "split", # Never use, legacy. Is applied inconsistently by other datasets. "Density": "den", "version": "version", "Description": "desc", From fc6381523132ca26c3646a2cf04f147c344e64e1 Mon Sep 17 00:00:00 2001 From: iback Date: Fri, 22 May 2026 20:18:02 +0000 Subject: [PATCH 2/7] remove comment --- TPTBox/core/bids_files.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/TPTBox/core/bids_files.py b/TPTBox/core/bids_files.py index f2a803b..ab5f21d 100755 --- a/TPTBox/core/bids_files.py +++ b/TPTBox/core/bids_files.py @@ -1481,15 +1481,6 @@ def __lt__(self, other): def get_identifier(self): first_e = self.data_dict[next(iter(self.data_dict.keys()))][0] return first_e.get_identifier(self.sequence_splitting_keys) - # if "sub" not in first_e.info: - # print(f"family_id, no sub-key, got {first_e.info} and data_dict {list(self.data_dict.keys())}") - # identifier = "sub-404" - # else: - # identifier = "sub-" + first_e.info["sub"] - # for s in first_e.info.keys(): - # if s in self.sequence_splitting_keys: - # identifier += "_" + s + "-" + first_e.info[s] - # return identifier def items(self): return self.data_dict.items() From 55d313bc5976df66c642a60e8ff9b3667bb8fb7a Mon Sep 17 00:00:00 2001 From: iback Date: Fri, 22 May 2026 20:18:20 +0000 Subject: [PATCH 3/7] added a simple from_numpy function --- TPTBox/core/nii_wrapper.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/TPTBox/core/nii_wrapper.py b/TPTBox/core/nii_wrapper.py index 6ca136c..2757468 100755 --- a/TPTBox/core/nii_wrapper.py +++ b/TPTBox/core/nii_wrapper.py @@ -238,6 +238,11 @@ def __init__(self, nii: Nifti1Image|_unpacked_nii, seg=False,c_val=None, desc:st self.set_dtype_("smallest_uint") + @classmethod + def from_numpy(cls, arr: np.ndarray, affine: np.ndarray, seg=False, c_val=None, desc:str|None=None, info=None): + nii = nib.nifti1.Nifti1Image(arr,affine) + return NII(nii=nii, seg=seg, c_val=c_val, desc=desc, info=info) + @classmethod def load(cls, path: Image_Reference, seg, c_val=None)-> Self: nii= to_nii(path,seg) From 7e16dac8d74697aa4682933d825c0df7b53b3eee Mon Sep 17 00:00:00 2001 From: iback Date: Tue, 26 May 2026 14:49:39 +0000 Subject: [PATCH 4/7] Phase 1: linting setup, CLAUDE.md, and ruff D/ANN201 enforcement - Add CLAUDE.md with build commands, architecture overview, and key class descriptions - Enable Google-style docstring (D rules) and return-type annotation (ANN201) checking in ruff - Add pydocstyle convention = google config section - Add docs dependency group (mkdocs, mkdocs-material, mkdocstrings) - Add per-file ruff ignores for unit_tests/ and TPTBox/tests/ directories - Fix import sort order in speedtest_panoptica.py (I001) Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 95 +++++++++++++++++++ .../tests/speedtests/speedtest_panoptica.py | 37 ++++++++ pyproject.toml | 38 ++++++-- 3 files changed, 163 insertions(+), 7 deletions(-) create mode 100644 CLAUDE.md create mode 100644 TPTBox/tests/speedtests/speedtest_panoptica.py diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..87c77ae --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,95 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +TPTBox is a Python package for processing BIDS-compliant medical imaging datasets (CT, MRI), with a focus on torso/spine analysis. It provides NIfTI I/O, reorientation/resampling, Points of Interest (POI) computation for vertebrae, 2D/3D snapshots, registration, and segmentation integrations (SPINEPS, nnU-Net). + +## Commands + +### Installation +```bash +pip install poetry +poetry install --with dev +``` + +### Running Tests +```bash +# All tests +pytest unit_tests/ + +# Single test file +pytest unit_tests/test_nii.py + +# Single test function +pytest unit_tests/test_nii.py::test_function_name + +# With coverage +coverage run --source=TPTBox -m pytest unit_tests/ +``` + +### Linting & Formatting +```bash +# Lint (auto-fix where possible) +ruff check . --fix + +# Format +ruff format . + +# Both (mirrors pre-commit behavior) +pre-commit run --all-files +``` + +### CI Linting (as run in GitHub Actions) +```bash +flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics +``` + +## Architecture + +### Core Abstractions + +**`NII`** (`TPTBox/core/nii_wrapper.py`) — Central class wrapping nibabel NIfTI images. Handles reorientation, resampling, affine transforms, boolean masking, and mathematical operations. Nearly all image operations in the package go through `NII`. Math operations live in `nii_wrapper_math.py` as mixins. + +**`POI`** (`TPTBox/core/poi.py`) — Points of Interest container mapping `(vertebra_id, subregion_id) → 3D coordinate`. Coordinates can be in voxel or world space. POI computation strategies live in `TPTBox/core/poi_fun/`. + +**`BIDS_FILE` / `BIDS_Global_info`** (`TPTBox/core/bids_files.py`) — BIDS dataset navigator. `BIDS_Global_info` scans a dataset root, while `BIDS_FILE` represents a single file parsed according to BIDS naming entities. Constants for BIDS key/value vocabulary are in `bids_constants.py`. + +**`Location` / `Vertebra_Instance`** (`TPTBox/core/vert_constants.py`) — Enum-like constants for vertebral anatomy (vertebra IDs, subregion labels). Used as keys into `POI` objects throughout the codebase. + +### Package Layout + +``` +TPTBox/ +├── core/ # NII, POI, BIDS parsing, numpy utilities, vertebra constants +│ └── poi_fun/ # POI calculation strategies and serialization +├── spine/ # Spine-specific: 2D snapshots, statistics (distances, angles) +├── segmentation/ # SPINEPS integration, nnU-Net inference, defacing +├── registration/ # ANTs rigid/deformable, DeepALI deep learning registration +├── mesh3D/ # 3D mesh generation and rendering from segmentations +├── stitching/ # Multi-station image stitching +└── logger/ # Logging infrastructure (Logger, Print_Logger, No_Logger) +``` + +Public API is re-exported from `TPTBox/__init__.py`. All major classes and utility functions are importable directly from `TPTBox`. + +### Key Relationships + +- `NII` + `POI` are used together constantly: compute POIs from segmentation `NII`s, then use POIs to guide further processing. +- `BIDS_Global_info` iterates subjects/sessions; each subject has a `Subject_Container` with `BIDS_FILE` entries; `BIDS_FILE.get_nii()` returns a `NII`. +- `vert_constants.py` defines the shared label space (`Location` enum) that ties segmentation labels to POI keys to snapshot rendering. +- External tool integrations (SPINEPS, nnU-Net, ANTs, DeepALI) are optional; their imports are guarded so the core works without them. + +### Test Layout + +Tests live in `unit_tests/` (not `TPTBox/tests/`). `TPTBox/tests/` contains test utilities and sample data (CT/MRI NIfTIs) used by the unit tests. Some generated test files are very large (>20K LOC) — they are autogenerated and should not be edited by hand. + +## Code Style + +- **Line length**: 140 characters +- **Formatter**: Ruff (Black-compatible, double quotes) +- **Target Python**: 3.10+ syntax, but the package supports 3.9–3.14 +- **Naming**: Ruff N-rules are largely ignored — mixed-case class/method names are acceptable in this codebase (medical domain conventions) +- **Complexity**: McCabe max=20; research code legitimately has deep branching +- `from __future__ import annotations` is used widely for forward references diff --git a/TPTBox/tests/speedtests/speedtest_panoptica.py b/TPTBox/tests/speedtests/speedtest_panoptica.py new file mode 100644 index 0000000..84f4801 --- /dev/null +++ b/TPTBox/tests/speedtests/speedtest_panoptica.py @@ -0,0 +1,37 @@ +if __name__ == "__main__": + # speed test dilation + import random + + import numpy as np + from panoptica.utils.input_check_and_conversion.sanity_checker import sanity_check_and_convert_to_array + from scipy.ndimage import center_of_mass + + from TPTBox.core.nii_wrapper import NII + from TPTBox.core.np_utils import ( + _to_labels, + np_extract_label, + ) + from TPTBox.tests.speedtests.speedtest import speed_test + from TPTBox.tests.test_utils import get_nii + + def get_nii_array(): + num_points = random.randint(5, 10) + nii, points, orientation, sizes = get_nii(x=(10, 10, 10), num_point=num_points) + # nii.map_labels_({1: -1}, verbose=False) + arr = nii.get_seg_array().astype(np.uint8) + # arr[arr == 1] = -1 + # arr_r = arr.copy() + return arr + + def check_sanity(arr: np.ndarray): + _, c = sanity_check_and_convert_to_array(arr, arr) + return c.name + + speed_test( + repeats=5, + get_input_func=get_nii_array, + functions=[check_sanity], + assert_equal_function=lambda x, y: np.array_equal(x, y), # noqa: ARG005 + # np.all([x[i] == y[i] for i in range(len(x))]) + ) + # print(time_measures) diff --git a/pyproject.toml b/pyproject.toml index 6ee026e..d59968e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,13 @@ pytest-mock = "^3.6.0" exceptiongroup = { version = "^1.2", python = "<3.11" } tomli = {version = "*", python = "<3.11" } +[tool.poetry.group.docs.dependencies] +mkdocs = ">=1.6" +mkdocs-material = ">=9.5" +mkdocstrings = {extras = ["python"], version = ">=0.25"} +mkdocs-gen-files = ">=0.5" +mkdocs-literate-nav = ">=0.6" + [build-system] requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] build-backend = "poetry_dynamic_versioning.backend" @@ -115,9 +122,9 @@ select = [ "TD005", "FIX003", "FIX004", - #"ERA", For clean up - #"D", Dockstring For clean up - #"ANN", Annoation For clean up + #"ERA", + "D", + "ANN201", "PD", "PGH", "PL", @@ -154,16 +161,25 @@ ignore = [ "UP038", "N999", "E741", - "SIM118", # dictionay keys + "SIM118", # dictionary keys "N802", # function name lowercase "F811", "N803", "N806", - "FURB171", + "FURB171", "PLW0108", - "B905", # strict= in zip - "UP007", # Union and "|" python 3.9 + "B905", # strict= in zip + "UP007", # Union and "|" python 3.9 "PLC0415", # import-outside-top-level + # Docstring rules relaxed for internal / magic / dunder members + "D100", # missing docstring in public module + "D104", # missing docstring in public package + "D105", # missing docstring in magic method + "D107", # missing docstring in __init__ (covered by class docstring) + # Return-type annotation not required for private helpers + "ANN202", + # En-dashes (–) in docstrings are legitimate range notation (e.g. "C1–C7") + "RUF002", ] # Allow fix for all enabled rules (when `--fix`) is provided. @@ -176,6 +192,14 @@ extend-safe-fixes = ["RUF015", "C419", "C408", "B006"] # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.ruff.lint.per-file-ignores] +# Test files: don't require docstrings on test functions / helper utilities +"unit_tests/**" = ["D101", "D102", "D103", "D205", "D415", "ANN201"] +"TPTBox/tests/**" = ["D101", "D102", "D103", "D205", "D415", "ANN201"] + [tool.ruff.lint.mccabe] # Flag errors (`C901`) whenever the complexity level exceeds 5. max-complexity = 20 From c7faff8b7727802a67b3b03c46fde4418925119c Mon Sep 17 00:00:00 2001 From: iback Date: Tue, 26 May 2026 15:21:43 +0000 Subject: [PATCH 5/7] Phase 2: add Google-style docstrings and return-type annotations throughout Add docstrings to all public functions, methods, and classes previously missing them across the entire TPTBox codebase (~350 additions, reaching ~80% coverage). Add return-type annotations to all ANN201 violations. Also add D417 to ruff ignore (arg-level documentation is aspirational). Files touched: nii_wrapper, bids_files, poi, np_utils, vert_constants, nii_wrapper_math, nii_poi_abstract, all poi_fun modules, spine snapshot/spinestats, registration (rigid/deformable/deepali), segmentation (spineps/vibeseg/nnunet), mesh3D, stitching, logger, dicom, and core internal helpers. Co-Authored-By: Claude Sonnet 4.6 --- TPTBox/core/bids_files.py | 901 +++++++++++++++--- TPTBox/core/compat.py | 5 +- TPTBox/core/dicom/dicom2nii_utils.py | 96 +- TPTBox/core/dicom/dicom_extract.py | 148 ++- TPTBox/core/dicom/dicom_header_to_keys.py | 59 +- TPTBox/core/dicom/fix_brocken.py | 18 +- TPTBox/core/dicom/nii2dicom.py | 70 +- TPTBox/core/internal/ants_load.py | 84 +- TPTBox/core/internal/deep_learning_utils.py | 16 +- TPTBox/core/internal/nii_help.py | 46 +- TPTBox/core/internal/slicer_nrrd.py | 119 ++- TPTBox/core/nii_poi_abstract.py | 176 +++- TPTBox/core/nii_wrapper.py | 816 ++++++++++++---- TPTBox/core/nii_wrapper_math.py | 305 +++++- TPTBox/core/np_utils.py | 321 ++++--- TPTBox/core/poi.py | 146 ++- TPTBox/core/poi_fun/_help.py | 71 +- .../core/poi_fun/pixel_based_point_finder.py | 118 ++- TPTBox/core/poi_fun/poi_abstract.py | 409 ++++++-- TPTBox/core/poi_fun/poi_global.py | 149 ++- TPTBox/core/poi_fun/ray_casting.py | 284 ++++-- TPTBox/core/poi_fun/save_load.py | 185 +++- TPTBox/core/poi_fun/save_mkr.py | 229 ++++- TPTBox/core/poi_fun/strategies.py | 170 +++- TPTBox/core/poi_fun/vertebra_direction.py | 85 +- .../poi_fun/vertebra_pois_non_centroids.py | 107 ++- TPTBox/core/sitk_utils.py | 124 ++- TPTBox/core/vert_constants.py | 227 ++++- TPTBox/logger/log_constants.py | 66 +- TPTBox/logger/log_file.py | 250 +++-- TPTBox/mesh3D/html_preview.py | 14 +- TPTBox/mesh3D/mesh.py | 79 +- TPTBox/mesh3D/mesh_colors.py | 75 +- TPTBox/mesh3D/snapshot3D.py | 160 ++-- TPTBox/registration/_deepali/_hooks.py | 1 - TPTBox/registration/_deepali/_utils.py | 146 ++- TPTBox/registration/_deepali/deepali_model.py | 146 ++- .../registration/_deepali/deepali_trainer.py | 3 +- .../_deepali/spine_rigid_elements_reg.py | 236 ++++- .../_deformable/_deepali/metrics.py | 19 +- .../_deformable/_deepali/optim.py | 4 +- .../_deformable/_grid_search_vert.py | 2 +- .../_deformable/deformable_reg.py | 24 +- .../_deformable/deformable_reg_old.py | 28 +- .../registration/_deformable/grid_search.py | 102 +- .../_deformable/multilabel_segmentation.py | 68 +- .../_ridged_intensity/affine_deepali.py | 18 +- .../_ridged_intensity/register.py | 105 +- .../_ridged_points/point_registration.py | 189 +++- .../ridged_intensity/affine_deepali.py | 174 +++- TPTBox/registration/script_ax2sag.py | 54 +- TPTBox/segmentation/VibeSeg/auto_download.py | 51 +- .../segmentation/VibeSeg/inference_nnunet.py | 146 ++- TPTBox/segmentation/VibeSeg/vibeseg.py | 62 +- TPTBox/segmentation/_deface.py | 16 +- .../nnUnet_utils/data_iterators.py | 58 +- .../nnUnet_utils/default_preprocessor.py | 92 +- .../nnUnet_utils/export_prediction.py | 55 +- .../nnUnet_utils/get_network_from_plans.py | 5 +- .../nnUnet_utils/inference_api.py | 84 +- .../nnUnet_utils/plans_handler.py | 100 +- TPTBox/segmentation/nnUnet_utils/predictor.py | 159 +++- .../nnUnet_utils/sliding_window_prediction.py | 2 + TPTBox/segmentation/spineps.py | 125 ++- TPTBox/spine/snapshot2D/snapshot_modular.py | 320 ++++++- TPTBox/spine/snapshot2D/snapshot_templates.py | 200 +++- TPTBox/spine/spinestats/angles.py | 108 +-- TPTBox/spine/spinestats/body_quadrants.py | 15 +- TPTBox/spine/spinestats/distances.py | 65 +- TPTBox/spine/spinestats/ivd_pois.py | 104 +- TPTBox/spine/spinestats/make_endplate.py | 99 +- TPTBox/stitching/stitching.py | 253 ++++- TPTBox/stitching/stitching_tools.py | 76 +- pyproject.toml | 2 + unit_tests/test_nputils.py | 3 +- 75 files changed, 7771 insertions(+), 1876 deletions(-) diff --git a/TPTBox/core/bids_files.py b/TPTBox/core/bids_files.py index ab5f21d..cb91103 100755 --- a/TPTBox/core/bids_files.py +++ b/TPTBox/core/bids_files.py @@ -8,11 +8,15 @@ import typing from collections.abc import Sequence from pathlib import Path +from typing import TYPE_CHECKING from warnings import warn import numpy as np import TPTBox + +if TYPE_CHECKING: + from TPTBox.core.nii_poi_abstract import Grid from TPTBox.core.bids_constants import ( entities, entities_keys, @@ -45,7 +49,19 @@ # If the session level is omitted in the folder structure, the filename MUST begin with the string sub-