Skip to content

Commit 597a205

Browse files
authored
Merge pull request #106 from Hendrik-code/refactor
Refactor
2 parents d35ecdc + 48d3e52 commit 597a205

106 files changed

Lines changed: 9007 additions & 1946 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/docs.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Docs
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: actions/setup-python@v5
16+
with:
17+
python-version: "3.12"
18+
19+
- name: Install package and docs dependencies
20+
run: |
21+
pip install -e .
22+
pip install mkdocs mkdocs-material "mkdocstrings[python]" mkdocs-gen-files mkdocs-literate-nav
23+
24+
- name: Build docs
25+
run: mkdocs build

.readthedocs.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
version: 2
2+
3+
build:
4+
os: ubuntu-24.04
5+
tools:
6+
python: "3.12"
7+
jobs:
8+
post_install:
9+
- pip install "mkdocs>=1.6" "mkdocs-material>=9.5" "mkdocstrings[python]>=0.25"
10+
11+
mkdocs:
12+
configuration: mkdocs.yml
13+
14+
python:
15+
install:
16+
- method: pip
17+
path: .

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/README.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# TPTBox Core
2+
3+
The `core` subpackage is the foundation of TPTBox. It provides the three primary abstractions —
4+
`NII`, `POI`, and `BIDS_FILE` — along with helper utilities for array operations and anatomical constants.
5+
6+
## Key Classes and Functions
7+
8+
### `nii_wrapper.py` — NIfTI image wrapper
9+
10+
| Symbol | Description |
11+
|---|---|
12+
| `NII` | Wraps `nibabel.Nifti1Image`; the central image type throughout TPTBox |
13+
| `NII.load(path, seg)` | Load a NIfTI file from disk (classmethod) |
14+
| `NII.from_numpy(arr, affine, seg)` | Construct from a numpy array and affine matrix |
15+
| `NII.reorient(axcodes_to)` | Reorient to a canonical axis code (e.g. `("R","A","S")`) |
16+
| `NII.rescale(voxel_spacing)` | Resample to new voxel spacing in mm |
17+
| `NII.resample_from_to(other)` | Resample to match the grid of another `NII` |
18+
| `NII.apply_mask(mask)` | Zero-out voxels outside a binary/label mask |
19+
| `NII.map_labels(label_map)` | Remap integer labels |
20+
| `NII.save(path)` | Save to disk as `.nii` or `.nii.gz` |
21+
| `NII.get_array()` | Return a copy of the underlying numpy array |
22+
| `NII.get_seg_array()` | Same as `get_array()` but asserts `seg=True` |
23+
| `Image_Reference` | Type alias: `BIDS_FILE | Nifti1Image | Path | str | NII` |
24+
25+
### `bids_files.py` — BIDS dataset navigation
26+
27+
| Symbol | Description |
28+
|---|---|
29+
| `BIDS_Global_info` | Scans a dataset root and indexes all BIDS files |
30+
| `BIDS_Global_info.enumerate_subjects()` | Iterate over subjects as `(subject_id, Subject_Container)` |
31+
| `Subject_Container` | Per-subject file index; entry point for queries |
32+
| `Subject_Container.new_query()` | Returns a `Searchquery` for this subject |
33+
| `BIDS_FILE` | One file parsed into BIDS entities (sub, ses, format, …) |
34+
| `BIDS_FILE.open_nii()` | Load this file's NIfTI |
35+
| `BIDS_FILE.get_changed_path(...)` | Derive a new path with changed BIDS entities |
36+
| `Searchquery` | Fluent query builder: `.filter()`, `.loop_dict()`, `.first()` |
37+
| `BIDS_Family` | `dict[str, list[BIDS_FILE]]` grouping files by format |
38+
39+
### `poi.py` — Points of Interest
40+
41+
| Symbol | Description |
42+
|---|---|
43+
| `POI` | Maps `(vertebra_id, subregion_id) → (x, y, z)` |
44+
| `calc_centroids(seg_nii)` | Compute centroids for every label in a segmentation |
45+
| `calc_poi_from_subreg_vert(vert, subreg)` | Compute POIs from paired vertebra + subregion segmentations |
46+
| `POI.save(path)` | Serialise to JSON |
47+
| `POI.load(path)` | Deserialise from JSON |
48+
| `POI.to_global(ref)` | Convert from voxel to world (mm) coordinates |
49+
| `POI.to_local(ref)` | Convert from world to voxel coordinates |
50+
51+
### `np_utils.py` — NumPy utilities
52+
53+
| Symbol | Description |
54+
|---|---|
55+
| `np_extract_label(arr, label)` | Extract a single label as a binary mask |
56+
| `np_center_of_mass(arr)` | Per-label centre-of-mass |
57+
| `np_volume(arr)` | Per-label voxel count |
58+
| `np_bbox_binary(mask)` | Bounding-box slice tuple for a binary array |
59+
| `np_dilate_msk(arr, mm, zoom)` | Morphological dilation by `mm` millimetres |
60+
| `np_erode_msk(arr, mm, zoom)` | Morphological erosion |
61+
| `np_fill_holes(arr)` | Fill holes per label |
62+
| `np_connected_components(arr)` | Label connected components |
63+
| `np_map_labels(arr, label_map)` | Remap label integers via a dict |
64+
| `np_unique(arr)` | Unique values (faster than `np.unique` for uint arrays) |
65+
66+
### `vert_constants.py` — Anatomical constants
67+
68+
| Symbol | Description |
69+
|---|---|
70+
| `Location` | `IntEnum` of anatomical subregion IDs (used as POI keys) |
71+
| `Vertebra_Instance` | Maps integer IDs → anatomical names (C1–S1) |
72+
| `v_name2idx` | Dict: `"L1" → 20`, etc. |
73+
| `v_idx2name` | Dict: `20 → "L1"`, etc. |
74+
| `v_idx_order` | Canonical sort order for vertebra IDs |
75+
| `ZOOMS` | Type alias: `tuple[float, float, float]` |
76+
| `AX_CODES` | Type alias: `tuple[str, str, str]` |
77+
| `AFFINE` | Type alias: `np.ndarray` (4×4) |
78+
79+
## Quick Example
80+
81+
```python
82+
from TPTBox import NII, BIDS_Global_info, calc_centroids
83+
84+
# Load and resample a CT
85+
ct = NII.load("sub-001_ct.nii.gz", seg=False)
86+
ct_ras = ct.reorient(("R", "A", "S")).rescale((1.0, 1.0, 1.0))
87+
88+
# Compute centroids from a segmentation
89+
seg = NII.load("sub-001_seg.nii.gz", seg=True)
90+
poi = calc_centroids(seg)
91+
print(poi)
92+
93+
# Scan a BIDS dataset
94+
bids = BIDS_Global_info(["dataset/"], parents=["rawdata"])
95+
for subj, container in bids.enumerate_subjects():
96+
t2 = container.new_query().filter("format", "T2w").first()
97+
```

TPTBox/core/bids_constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@
311311
"Hemisphere": "hemi", # [L,R]
312312
"Sample": "sample", # such as tissue, primary cell or cell-free sample.
313313
# Sub recordings - Use when necessary.
314+
"Split": "split", # Never use, legacy. Is applied inconsistently by other datasets.
314315
## Contrast
315316
"Contrast enhancement phase": "ce",
316317
"Tracer": "trc", # use ce before this one.
@@ -340,7 +341,6 @@
340341
# Single class segmentation
341342
"Label": "label",
342343
# Others (never used)
343-
"Split": "split", # Never use, legacy. Is applied inconsistently by other datasets.
344344
"Density": "den",
345345
"version": "version",
346346
"Description": "desc",

0 commit comments

Comments
 (0)