Skip to content

Commit 48d3e52

Browse files
Hendrik-codeclaude
andcommitted
Fix post-merge errors, missing docstrings, and docs build config
Merge introduced new functions and a renamed parameter that broke ruff: - dicom_extract.py: rename parameter hunk → chunk (was chunk in body, causing F821 undefined name + ARG001 unused arg) - dicom_extract.py: add docstring + return type to dicom_to_nifti_multiframe_2d - elastic_deform.py (new file): fix D212/D415 docstring format, add docstrings and return types for pad() and get_random_deform_parameter() - nii_wrapper.py: add docstring + return types for from_deepali, erode_msk_euclid, dilate_msk_euclid - np_utils.py: fix D205/D415 and add return types for np_erode_msk_euclid, np_dilate_msk_euclid - vert_constants.py: add docstring + return type for Full_Body_Instance.feet - log_file.py: add docstrings for warning() and error() methods Fix ReadTheDocs docs build: extra_requirements=[docs] requires a pip extra in pyproject.toml, but docs is only a Poetry optional group. Switch to build.jobs.post_install to install mkdocs packages directly instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2 parents 24765c8 + d35ecdc commit 48d3e52

32 files changed

Lines changed: 1272 additions & 173 deletions

.readthedocs.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ build:
44
os: ubuntu-24.04
55
tools:
66
python: "3.12"
7+
jobs:
8+
post_install:
9+
- pip install "mkdocs>=1.6" "mkdocs-material>=9.5" "mkdocstrings[python]>=0.25"
710

811
mkdocs:
912
configuration: mkdocs.yml
@@ -12,5 +15,3 @@ python:
1215
install:
1316
- method: pip
1417
path: .
15-
extra_requirements:
16-
- docs

CLAUDE.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
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).
8+
9+
## Commands
10+
11+
### Installation
12+
```bash
13+
pip install poetry
14+
poetry install --with dev
15+
```
16+
17+
### Running Tests
18+
```bash
19+
# All tests
20+
pytest unit_tests/
21+
22+
# Single test file
23+
pytest unit_tests/test_nii.py
24+
25+
# Single test function
26+
pytest unit_tests/test_nii.py::test_function_name
27+
28+
# With coverage
29+
coverage run --source=TPTBox -m pytest unit_tests/
30+
```
31+
32+
### Linting & Formatting
33+
```bash
34+
# Lint (auto-fix where possible)
35+
ruff check . --fix
36+
37+
# Format
38+
ruff format .
39+
40+
# Both (mirrors pre-commit behavior)
41+
pre-commit run --all-files
42+
```
43+
44+
### CI Linting (as run in GitHub Actions)
45+
```bash
46+
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
47+
```
48+
49+
## Architecture
50+
51+
### Core Abstractions
52+
53+
**`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.
54+
55+
**`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/`.
56+
57+
**`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`.
58+
59+
**`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.
60+
61+
### Package Layout
62+
63+
```
64+
TPTBox/
65+
├── core/ # NII, POI, BIDS parsing, numpy utilities, vertebra constants
66+
│ └── poi_fun/ # POI calculation strategies and serialization
67+
├── spine/ # Spine-specific: 2D snapshots, statistics (distances, angles)
68+
├── segmentation/ # SPINEPS integration, nnU-Net inference, defacing
69+
├── registration/ # ANTs rigid/deformable, DeepALI deep learning registration
70+
├── mesh3D/ # 3D mesh generation and rendering from segmentations
71+
├── stitching/ # Multi-station image stitching
72+
└── logger/ # Logging infrastructure (Logger, Print_Logger, No_Logger)
73+
```
74+
75+
Public API is re-exported from `TPTBox/__init__.py`. All major classes and utility functions are importable directly from `TPTBox`.
76+
77+
### Key Relationships
78+
79+
- `NII` + `POI` are used together constantly: compute POIs from segmentation `NII`s, then use POIs to guide further processing.
80+
- `BIDS_Global_info` iterates subjects/sessions; each subject has a `Subject_Container` with `BIDS_FILE` entries; `BIDS_FILE.get_nii()` returns a `NII`.
81+
- `vert_constants.py` defines the shared label space (`Location` enum) that ties segmentation labels to POI keys to snapshot rendering.
82+
- External tool integrations (SPINEPS, nnU-Net, ANTs, DeepALI) are optional; their imports are guarded so the core works without them.
83+
84+
### Test Layout
85+
86+
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.
87+
88+
## Code Style
89+
90+
- **Line length**: 140 characters
91+
- **Formatter**: Ruff (Black-compatible, double quotes)
92+
- **Target Python**: 3.10+ syntax, but the package supports 3.9–3.14
93+
- **Naming**: Ruff N-rules are largely ignored — mixed-case class/method names are acceptable in this codebase (medical domain conventions)
94+
- **Complexity**: McCabe max=20; research code legitimately has deep branching
95+
- `from __future__ import annotations` is used widely for forward references

TPTBox/core/bids_constants.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
"FLASH",
6565
"VF",
6666
"defacemas",
67+
"fluroscopy",
6768
"dw",
6869
"TB1TFL",
6970
"TB1RFM",
@@ -162,6 +163,8 @@
162163
"localizer",
163164
"difference",
164165
"labels",
166+
"report",
167+
"pet",
165168
]
166169
# https://bids-specification.readthedocs.io/en/stable/appendices/entity-table.html
167170
formats_relaxed = [*formats, "t2", "t1", "t2c", "t1c", "mr", "snapshot", "t1dixon", "dwi", "ctb"]
@@ -220,7 +223,7 @@
220223
"OPT": "Ophthalmic Tomography",
221224
"OPV": "Ophthalmic Visual Field",
222225
"OSS": "Optical Surface Scan",
223-
"OT": "Other ",
226+
"OT": "Other",
224227
"PLAN": "Plan",
225228
"PR": "Presentation State",
226229
"PT": "Positron emission tomography (PET)",

TPTBox/core/bids_files.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -859,7 +859,7 @@ def rename_files(self, path: Path | str, ending: str = ".nii.gz") -> None:
859859
p = Path(path + "." + key)
860860
value.rename(p)
861861

862-
def symlink_files(self, path: Path | str, ending: str = ".nii.gz") -> None:
862+
def symlink_files(self, path: Path | str, ending: str = ".nii.gz", exist_ok: bool = False) -> None:
863863
"""Create symbolic links for all associated files at a new base path.
864864
865865
Equivalent to :meth:`rename_files` but creates symlinks rather than
@@ -881,9 +881,13 @@ def symlink_files(self, path: Path | str, ending: str = ".nii.gz") -> None:
881881
path = path.replace(ending, "")
882882
for key, value in self.file.items():
883883
p = Path(path + "." + key)
884+
884885
if os.path.islink(p):
885886
assert Path(os.readlink(p)) == value, f"{p} exists"
886887
continue
888+
if exist_ok and p.exists():
889+
continue
890+
887891
os.symlink(value, p)
888892

889893
def get_path_decomposed(self, file_type: str | None = None) -> tuple[Path, str, str, str]:
@@ -958,7 +962,7 @@ def get_changed_bids(
958962
auto_add_run_id: bool = False,
959963
additional_folder: str | None = None,
960964
dataset_path: str | None = None,
961-
make_parent: bool = True,
965+
make_parent: bool = False,
962966
non_strict_mode: bool = False,
963967
) -> BIDS_FILE:
964968
"""Construct a new :class:`BIDS_FILE` pointing to a derived output path.

TPTBox/core/dicom/dicom2nii_utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,8 @@ def test_name_conflict(json_ob: dict, file: str | Path) -> bool:
285285
if Path(file).exists():
286286
with open(file) as f:
287287
js = json.load(f)
288+
if "grid" in js:
289+
del js["grid"]
288290
return js != json_ob
289291
return False
290292

0 commit comments

Comments
 (0)