diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..a97abfaa --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,85 @@ +# Contributing to TPTBox + +Thanks for helping improve this codebase. The following overview will help you as guide to contribute. + +## 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 + +## Get started! + +Ready to contribute? + +### 1) Create an issue on the GitHub repository + +### 2) Fork the repository + +### 3) Clone your fork + +```bash +git clone https://github.com//TPTBox.git +cd TPTBox +``` + +### 4) Local install (alternative) + +Make a venv (in whatever fashion you like), then: +```bash +pip install -e . +``` + +### 5) Ensure you install the pre-commit hook: +```bash +pre-commit install +``` +This pre-commit hook will automatically fix some linting issues and block your commits so that every commit is clean in terms of formatting + +### 6) Running checks locally + +Before pushing, make sure all unit tests and ruff pass. 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 test files are very large. + +```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 +``` + + + + +### 7) Submitting changes + +1. Create a feature branch from `main`: + + ```bash + git checkout -b my-feature + ``` + +2. Make your changes and commit with a clear, descriptive message. +3. Push your branch and open a pull request against `main`. +4. CI runs on all pull requests — ensure all checks are green before requesting review. diff --git a/README.md b/README.md index 8115a435..26ff5fb9 100755 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- +


@@ -11,42 +11,28 @@ [![Documentation](https://readthedocs.org/projects/tptbox/badge/?version=latest)](https://tptbox.readthedocs.io/en/latest/) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +

+ Quick use · + Documentation · + Contributing +

-The Torso Processing ToolBox (TPTBox) is a multi-functional package to handle any sort of bids-conform dataset (CT, MRI, ...) -It can find, filter, search any BIDS_Family and subjects, and has many functionalities, among them: -- Easily loop over datasets, and the required files -- Read, Write Niftys, centroid jsons, ... -- Reorient, Resample, Shift Niftys, Centroids, labels -- Modular 2D snapshot generation (different views, MIPs, ...) -- 3D Mesh generation from segmentation and snapshots from them -- Registration -- Logging everything consistently -- ... -## Documentation +The Torso Processing ToolBox (TPTBox) is a multi-functional package to handle any sort of bids-conform dataset (CT, MRI, ...) -Full API reference and usage guides are available at **https://tptbox.readthedocs.io**. +## Features -The docs cover all sub-packages — `NII`, `POI`, `BIDS_FILE`, NumPy utilities, -vertebra constants, spine analysis, registration, segmentation, mesh3D, -stitching, and the logger — with hyperlinks back to the GitHub source. +- **Dataset Handling**: Loop over datasets, search query, and find images and their corresponding derivatives -## Modules -Each sub-package has its own README with API tables and examples: +- **I/O Handling**: Read and Write nifti files and point coordinate files as JSONs +- **Image Processing**: Reorient, Resample, Shift Niftys, Centroids, labels, compute connected components, and so much more +- **Visualization**: Modular 2D snapshot generation (different views, maximum intensity projections, depth-color map) +- **3D Mesh generation**: Use 3D segmentations to create 3D meshes and then take snapshots for visualization +- **Registration**: Register two images to each other, using available data (image, segmentation, points) +- **Stitching**: You have multiple MRI of the same person, split into different regions? Use our stitching algorithm to create one unified view. +- **Logger**: Log every function in a file automatically, color important messages in the terminal for easy recognition. -| Module | Description | -|---|---| -| [`core`](TPTBox/core/README.md) | `NII` (NIfTI I/O and transforms), `POI` (anatomical landmarks), BIDS dataset navigation, NumPy utilities, vertebra constants | -| [`core/poi_fun`](TPTBox/core/poi_fun/README.md) | Internal POI computation strategies (surface points, corpus centers, disc points) | -| [`spine`](TPTBox/spine/README.md) | Spine-specific tools: 2D snapshot generation and statistical measurements | -| [`spine/snapshot2D`](TPTBox/spine/snapshot2D/README.md) | Modular 2D image generation — axial/sagittal/coronal slices, MIPs, segmentation overlays | -| [`spine/spinestats`](TPTBox/spine/spinestats/README.md) | Clinical spine measurements: distances, angles, disc heights, IVD landmarks | -| [`registration`](TPTBox/registration/README.md) | Rigid and deformable image registration via ANTs and DeepALI | -| [`segmentation`](TPTBox/segmentation/README.md) | Integration with SPINEPS, VibeSeg/TotalVibeSeg, and nnU-Net pipelines | -| [`mesh3D`](TPTBox/mesh3D/README.md) | 3D surface mesh generation and rendering from segmentation volumes | -| [`stitching`](TPTBox/stitching/README.md) | Multi-station NIfTI stitching for whole-body or long-spine acquisitions | -| [`logger`](TPTBox/logger/README.md) | Structured, consistent logging for medical image processing pipelines | ## Install the package ```bash @@ -69,29 +55,8 @@ pip install poetry poetry install --with dev ``` -## Functionalities - -Each folder in this package represents a different functionality. - -The top-level-hierarchy incorporates the most important files, the BIDS_files. - -### BIDS_Files - -This file builds a data model out of the BIDS file names. -It can load a dataset as a BIDS_Global_info file, from which search queries and loops over the dataset can be started. -See ```tutorial_BIDS_files.ipynb``` for details. - -### bids_constants -Defines constants for the BIDS nomenclature (sequence-splitting keys, naming conventions...) - -### vert_constants - -Contains definitions and sort order for our intern labels, for vertebrae, POI, ... - -### Rotation and Resampling - -Example rotate and resample. +### Quick Use: ```python from TPTBox import NII @@ -110,172 +75,108 @@ nii.header # NIFTY header nii.orientation # Orientation in 3-Letters nii.zoom # Scale of the three image axis nii.shape #shape -``` -### Stitching -Python function and script for arbitrary image stitching. [See Details](TPTBox/stitching/) - -![Example of a stitching](TPTBox/stitching/stitching.jpg) -### Spineps and Points of Interests (POI) integration - -![Example of two lumbar vertebrae. The left example is derived from 1 mm isotropic CT, the right from sagittal MRI with a resolution of 3.3 mm in the left–right direction. Top row: Subregion of the vertebra used for analysis. Middle row: Extreme points. Bottom row: Corpus edge and ligamentum flavum points.](TPTBox/images/poi_preview.png) -For our Spine segmentation pipline follow the installation of [SPINEPS](https://github.com/Hendrik-code/spineps). -Image Source: Rule-based Key-Point Extraction for MR-Guided Biomechanical Digital Twins of the Spine; - - - -SPINEPS will produce two mask: instance and semantic labels. With these we can compute our POIs. There are either center of mass points or surface points with bioloical meaning. See [Validation of a Patient-Specific Musculoskeletal Model for Lumbar Load Estimation Generated by an Automated Pipeline From Whole Body CT](https://pubmed.ncbi.nlm.nih.gov/35898642/) -```python -from TPTBox import NII, POI, Location, POI_Global, calc_poi_from_subreg_vert -from TPTBox.core.vert_constants import v_name2idx -from TPTBox.segmentation.spineps import run_spineps_single - -# This requires that spineps is installed -output_paths = run_spineps_single( - "file-path-of_T2w.nii.gz", - model_semantic="t2w", - ignore_compatibility_issues=True, -) -out_spine = output_paths["out_spine"] -out_vert = output_paths["out_vert"] -semantic_nii = NII.load(out_spine, seg=True) -instance_nii = NII.load(out_vert, seg=True) - -poi = calc_poi_from_subreg_vert( - instance_nii, - semantic_nii, - subreg_id=[ - Location.Vertebra_Full, - Location.Arcus_Vertebrae, - Location.Spinosus_Process, - Location.Costal_Process_Left, - Location.Costal_Process_Right, - Location.Superior_Articular_Left, - Location.Superior_Articular_Right, - Location.Inferior_Articular_Left, - Location.Inferior_Articular_Right, - # Location.Vertebra_Corpus_border, CT only - Location.Vertebra_Corpus, - Location.Vertebra_Disc, - Location.Muscle_Inserts_Spinosus_Process, - Location.Muscle_Inserts_Transverse_Process_Left, - Location.Muscle_Inserts_Transverse_Process_Right, - Location.Muscle_Inserts_Vertebral_Body_Left, - Location.Muscle_Inserts_Vertebral_Body_Right, - Location.Muscle_Inserts_Articulate_Process_Inferior_Left, - Location.Muscle_Inserts_Articulate_Process_Inferior_Right, - Location.Muscle_Inserts_Articulate_Process_Superior_Left, - Location.Muscle_Inserts_Articulate_Process_Superior_Right, - Location.Ligament_Attachment_Point_Anterior_Longitudinal_Superior_Median, - Location.Ligament_Attachment_Point_Posterior_Longitudinal_Superior_Median, - Location.Ligament_Attachment_Point_Anterior_Longitudinal_Inferior_Median, - Location.Ligament_Attachment_Point_Posterior_Longitudinal_Inferior_Median, - Location.Additional_Vertebral_Body_Middle_Superior_Median, - Location.Additional_Vertebral_Body_Posterior_Central_Median, - Location.Additional_Vertebral_Body_Middle_Inferior_Median, - Location.Additional_Vertebral_Body_Anterior_Central_Median, - Location.Ligament_Attachment_Point_Anterior_Longitudinal_Superior_Left, - Location.Ligament_Attachment_Point_Posterior_Longitudinal_Superior_Left, - Location.Ligament_Attachment_Point_Anterior_Longitudinal_Inferior_Left, - Location.Ligament_Attachment_Point_Posterior_Longitudinal_Inferior_Left, - Location.Additional_Vertebral_Body_Middle_Superior_Left, - Location.Additional_Vertebral_Body_Posterior_Central_Left, - Location.Additional_Vertebral_Body_Middle_Inferior_Left, - Location.Additional_Vertebral_Body_Anterior_Central_Left, - Location.Ligament_Attachment_Point_Anterior_Longitudinal_Superior_Right, - Location.Ligament_Attachment_Point_Posterior_Longitudinal_Superior_Right, - Location.Ligament_Attachment_Point_Anterior_Longitudinal_Inferior_Right, - Location.Ligament_Attachment_Point_Posterior_Longitudinal_Inferior_Right, - Location.Additional_Vertebral_Body_Middle_Superior_Right, - Location.Additional_Vertebral_Body_Posterior_Central_Right, - Location.Additional_Vertebral_Body_Middle_Inferior_Right, - Location.Additional_Vertebral_Body_Anterior_Central_Right, - Location.Ligament_Attachment_Point_Flava_Superior_Median, - Location.Ligament_Attachment_Point_Flava_Inferior_Median, - Location.Vertebra_Direction_Posterior, - Location.Vertebra_Direction_Inferior, - Location.Vertebra_Direction_Right, - ], -) -poi = poi.round(2) -print("Vertebra T4 Vertebra Corpus Center of mass:", poi[v_name2idx["T4"], Location.Vertebra_Corpus]) -print("The id number of T4 Vertebra_Corpus is ", v_name2idx["T4"], Location.Vertebra_Corpus.value) - -# rescale/reorante local poi like nii -poi_new = poi.reorient(("P", "I", "R")).rescale((1, 1, 1)) -# Local and global POIs can be rescaled to a target spacing with: -poi_new = poi.resample_from_to(other_nii_or_poi) - -# local to global poi -global_poi = poi.to_global(itk_coords=True) -# You can save global pois in mrk.json format for import and editing in slicer. -global_poi.save_mrk("FILE.mrk.json", glyphScale=3.0) -# Import as a Markup in slicer; To make points editable you must click on the "lock" symbol under Markups - Control Points - Interaction - -# Save in our format: -poi.save(poi_path) -# Loading local/global Poi -poi = POI.load(poi_path) -poi = POI_Global.load(poi_path) - - - ``` -### Snapshot2D Spine -![Snapshot2D Spine example](TPTBox/images/snp2D_example.png) -The snapshot function automatically generates sag, cor, axial cuts in the center of a segmentation. - -```python -from TPTBox.spine.snapshot2D import Snapshot_Frame, create_snapshot - -ct = Path("Path to CT") -mri = Path("Path to MRI") -vert = Path("Path to Vertebra segmentation") -subreg = Path("Path to Vertebra subregions") -poi_ct = Path("Path to Vertebra poi") -poi_mr = Path("Path to Vertebra poi") - -ct_frame = Snapshot_Frame(image=ct, segmentation=vert, centroids=poi_ct, mode="CT", coronal=True, axial=True) -mr_frame = Snapshot_Frame(image=mri, segmentation=vert, centroids=poi_mr, mode="MRI", coronal=True, axial=True) -create_snapshot(snp_path="snapshot.jpg", frames=[ct_frame, mr_frame]) -``` +## Documentation +Full API reference and usage guides are available at **https://tptbox.readthedocs.io**. -### Snapshot3D -![Snapshot3D example](TPTBox/images/snp3D_example.jpg) -Requires additonal python packages: vtk fury xvfbwrapper +The docs cover all sub-packages — `NII`, `POI`, `BIDS_FILE`, NumPy utilities, +vertebra constants, spine analysis, registration, segmentation, mesh3D, +stitching, and the logger — with hyperlinks back to the GitHub source. -```python -from TPTBox.mesh3D.snapshot3D import make_snapshot3D, make_snapshot3D_parallel - -# all segmentation; view give the rotation of an image -make_snapshot3D("sub-101000_msk.nii.gz", "snapshot3D.png", view=["A", "L", "P", "R"]) -# Select witch segmentation per panel are chosen. -make_snapshot3D("sub-101000_msk.nii.gz", "snapshot3D_v2.png", view=["A"], ids_list=[[1, 2], [3]]) -# we proviede a implementation to process multiple images at the same time. -make_snapshot3D_parallel(["a.nii.gz", "b.nii.gz"], ["snp_a.png", "snp_b.png"], view=["A"]) -``` +## The three pillars - +| Module | Description | +|---|---| +| [`core`](https://tptbox.readthedocs.io/en/latest/modules/core/) | `NII` (NIfTI I/O and transforms), `POI` (anatomical landmarks), BIDS dataset navigation, NumPy utilities, vertebra constants | +| [`core/poi_fun`](https://tptbox.readthedocs.io/en/latest/modules/poi_fun/) | Internal POI computation strategies (surface points, corpus centers, disc points) | +| [`spine`](https://tptbox.readthedocs.io/en/latest/modules/spine/) | Spine-specific tools: 2D snapshot generation and statistical measurements | +| [`spine/snapshot2D`](https://tptbox.readthedocs.io/en/latest/modules/snapshot2d/) | Modular 2D image generation — axial/sagittal/coronal slices, MIPs, segmentation overlays | +| [`spine/spinestats`](https://tptbox.readthedocs.io/en/latest/modules/spinestats/) | Clinical spine measurements: distances, angles, disc heights, IVD landmarks | +| [`registration`](https://tptbox.readthedocs.io/en/latest/modules/registration/) | Rigid and deformable image registration via ANTs and DeepALI | +| [`segmentation`](https://tptbox.readthedocs.io/en/latest/modules/segmentation/) | Integration with SPINEPS, VibeSeg/TotalVibeSeg, and nnU-Net pipelines | +| [`mesh3D`](https://tptbox.readthedocs.io/en/latest/modules/mesh3d/) | 3D surface mesh generation and rendering from segmentation volumes | +| [`stitching`](https://tptbox.readthedocs.io/en/latest/modules/stitching/) | Multi-station NIfTI stitching for whole-body or long-spine acquisitions | +| [`logger`](https://tptbox.readthedocs.io/en/latest/modules/logger/) | Structured, consistent logging for medical image processing pipelines | + + + +# Publications + +An incomplete list of publications that actively used TPTBox: + +1. **Denoising diffusion-based MRI to CT image translation enables automated spinal segmentation**; Graf, Robert; +Schmitt, Joachim; Schlaeger, Sarah; Möller, Hendrik Kristian; Sideri-Lampretsa, Vasiliki; Sekuboyina, Anjany; Krieg, Sandro Manuel; +Wiestler, Benedikt; Menze, Bjoern; Rueckert, Daniel; Kirschke, Jan; **European Radiology Experimental, 2023** + +2. **Modeling the acquisition shift between axial and sagittal MRI for di usion super-resolution to enable axial spine segmentation**; Graf, Robert; Möller, Hendrik; McGinnis, Julian; Rühling, Sebastian; Weihrauch, Maren; Atad, Matan; Shit, +Suprosanna; Menze, Bjoern; Mühlau, Mark; Paetzold, Johannes C.; Rueckert, Daniel; Kirschke, Jan S.; **Proceedings of Machine Learning Research, 2024** + +3. **Detecting unforeseen data properties with diffusion autoencoder embeddings using spine MRI data**; Graf, Robert; Hunecke, Florian; Pohl, Soeren; Atad, Matan; Möller, Hendrik; Starck, Sophie; Kröncke, Thomas; Bette, Stefanie; Bamberg, +Fabian; Pischon, Tobias; Niendorf, Thoralf; Schmidt, Carsten; Paetzold, Johannes C.; Rueckert, Daniel; Kirschke, Jan S.; **International Conference on Medical Image Computing and Computer-Assisted Intervention (MICCAI), 2024** + +4. **SPINEPS—automatic whole spine segmentation of T2-weighted MR images using a two-phase approach to multi-class semantic and instance segmentation**; Möller, Hendrik; Graf, Robert; Schmitt, Joachim; Keinert-Weth, Benjamin; +Schön, Hanna; Atad, Matan; Sekuboyina, Anjany; Streckenbach, Felix; Kofler, Florian; +Kroencke, Thomas; Bette, Stefanie; Willich, Stefan N.; Keil, Thomas; Niendorf, Thoralf; +Pischon, Tobias; Endemann, Beate; Menze, Bjoern; Rueckert, Daniel; Kirschke, Jan S.; +**European Radiology, 2025** + +5. **VIBESegmentator: full body MRI segmentation for the NAKO and UK Biobank**; +Graf, Robert; Platzek, Paul; Riedel, Evamaria Olga; Ramschütz, Constanze; Starck, Sophie; Möller, Hendrik K.; Atad, Matan; Völzke, +Henry; Bülow, Robin; Schmidt, Carsten Oliver; Rüdebusch, Julia; Jung, Matthias; Reisert, Marco; Weiss, Jakob; Lö ler, Maximilian T.; +Bamberg, Fabian; Wiestler, Benedikt; Paetzold, Johannes C.; Rueckert, Daniel; Kirschke, Jan S.; **European Radiology, 2025** + +6. **Generating synthetic high-resolution spinal STIR and T1w images from T2w FSE and low-resolution axial Dixon**; Graf, Robert; Platzek, Paul-Sören; Riedel, Evamaria Olga; Kim, Su Hwan; Lenhart, Nicolas; Ramschütz, Constanze; Paprottka, +Karolin Johanna; Kertels, Olivia Ruriko; Möller, Hendrik Kristian; Atad, Matan; Bülow, Robin; Werner, Nicole; Völzke, Henry; Schmidt, +Carsten Oliver; Wiestler, Benedikt; Paetzold, Johannes C.; Rueckert, Daniel; Kirschke, Jan S.; **European Radiology, 2025** + +7. **MAGO-SP: detection and correction of water-fat swaps in magnitude-only VIBE MRI**; +Graf, Robert; Möller, Hendrik; Starck, Sophie; Atad, Matan; Braun, Philipp; Stelter, Jonathan; Peters, Annette; Krist, Lilian; Willich, +Stefan N.; Völzke, Henry; Bülow, Robin; Pischon, Tobias; Niendorf, Thoralf; Paetzold, Johannes C.; Karampinos, Dimitrios; Rueckert, +Daniel; Kirschke, Jan S.; **International Conference on Medical Image Computing and Computer-Assisted Intervention (MICCAI), 2025** + +8. **Automated Thoracolumbar Stump Rib Detection and Analysis in a Large CT Cohort**; +Möller, Hendrik; Dima, Alina; Keinert-Weth, Benjamin; Graf, Robert; Atad, Matan; Paetzold, +Johannes; Jungmann, Friederike; Braren, Rickmer; Kofler, Florian; Menze, Bjoern; Rueckert, +Daniel; Kirschke, Jan S.; Schön, Hanna; **MDPI AI, 2026** + +9. **PARASIDE: An automatic paranasal sinus segmentation and structure analysis tool for magnetic resonance imaging**; Möller, Hendrik; +Krautschick, Lukas; Graf, Robert; Atad, Matan; Busch, Chia-Jung; Beule, Achim Georg; +Scharf, Christian; Kaderali, Lars; Menze, Bjoern; Rueckert, Daniel; Kirschke, Jan S.; +Paperlein, Fabian; **Computers in Biology and Medicine, 2026** + +10. **One Sequence to Segment Them All: Efficient Data Augmentation for CT and MRI Cross-Domain 3D Spine Segmentation;** Molinier, +Nathan*; Möller, Hendrik*; Dagonneau, Thomas; Curto-Vilalta, Anna; Graf, Robert; Atad, +Matan; Rueckert, Daniel; Kirschke, Jan S.; Cohen-Adad, Julien; **International Conference on +Medical Image Computing and Computer-Assisted Intervention (MICCAI) , 2026** + +11. **VERIDAH: Solving Enumeration Anomaly Aware Vertebra +Labeling across Imaging Sequences;** Möller, Hendrik; Schön, Hanna; Graf, Robert; +Atad, Matan; Molinier, Nathan; Sekuboyina, Anjany; Budai, Bettina; Bamberg, Fabian; +Ringhof, Steffen; Schlett, Christopher; Pischon, Tobias; Niendorf, Thoralf; Decker, Josua; +Weber, Marc-André; Menze, Bjoern; Rueckert, Daniel; Kirschke, Jan S.; **European +Radiology (under review), 2026** + +12. **Rule-based key-point extraction for MR-guided biomechanical digital twins of the spine**; Graf, Robert; Lerchl, +Tanja; Nispel, Kati; Möller, Hendrik; Atad, Matan; McGinnis, Julian; Watrinet, Julius Maria; Paetzold, Johannes C.; Rueckert, Daniel; +Kirschke, Jan S.; **International Workshop on Digital Twin for Healthcare (DT4H), 2025** + +13. **VERPEX: Anatomical Landmark Extraction on 3D Vertebrae exploiting Segmentation Masks**; Möller, Hendrik; Wang, Alissa Yuxuan; Graf, Robert; +Nispel, Kati; Atad, Matan; Menze, Bjoern; Rueckert, Daniel; Kirschke, Jan S.; Lerchl, Tanja; +**International Workshop on Digital Twin for Healthcare (DT4H), 2026** diff --git a/TPTBox/core/README.md b/TPTBox/core/README.md index 9c2887ff..d4f6c5e4 100644 --- a/TPTBox/core/README.md +++ b/TPTBox/core/README.md @@ -3,53 +3,25 @@ The `core` subpackage is the foundation of TPTBox. It provides the three primary abstractions — `NII`, `POI`, and `BIDS_FILE` — along with helper utilities for array operations and anatomical constants. -## Key Classes and Functions -### `nii_wrapper.py` — NIfTI image wrapper +## The three pillars -- NII, POI, BIDS -| Symbol | Description | -|---|---| -| `NII` | Wraps `nibabel.Nifti1Image`; the central image type throughout TPTBox | -| `NII.load(path, seg)` | Load a NIfTI file from disk (classmethod) | -| `NII.from_numpy(arr, affine, seg)` | Construct from a numpy array and affine matrix | -| `NII.reorient(axcodes_to)` | Reorient to a canonical axis code (e.g. `("R","A","S")`) | -| `NII.rescale(voxel_spacing)` | Resample to new voxel spacing in mm | -| `NII.resample_from_to(other)` | Resample to match the grid of another `NII` | -| `NII.apply_mask(mask)` | Zero-out voxels outside a binary/label mask | -| `NII.map_labels(label_map)` | Remap integer labels | -| `NII.save(path)` | Save to disk as `.nii` or `.nii.gz` | -| `NII.get_array()` | Return a copy of the underlying numpy array | -| `NII.get_seg_array()` | Same as `get_array()` but asserts `seg=True` | -| `Image_Reference` | Type alias: `BIDS_FILE | Nifti1Image | Path | str | NII` | - -### `bids_files.py` — BIDS dataset navigation +### NII: nii_wrapper.py -- NIfTI image wrapper +This is the core of image handling, this takes care of loading images and segmentations, and any data processing -| Symbol | Description | -|---|---| -| `BIDS_Global_info` | Scans a dataset root and indexes all BIDS files | -| `BIDS_Global_info.enumerate_subjects()` | Iterate over subjects as `(subject_id, Subject_Container)` | -| `Subject_Container` | Per-subject file index; entry point for queries | -| `Subject_Container.new_query()` | Returns a `Searchquery` for this subject | -| `BIDS_FILE` | One file parsed into BIDS entities (sub, ses, format, …) | -| `BIDS_FILE.open_nii()` | Load this file's NIfTI | -| `BIDS_FILE.get_changed_path(...)` | Derive a new path with changed BIDS entities | -| `Searchquery` | Fluent query builder: `.filter()`, `.loop_dict()`, `.first()` | -| `BIDS_Family` | `dict[str, list[BIDS_FILE]]` grouping files by format | - -### `poi.py` — Points of Interest +### POI: poi.py -- Points of Interests +This is the core of handling 2D/3D coordinates in any defined space. Center of mass locations can be computed in this format, and other landmarks. Similar to Niftis, this contains an affine matrix so it is aware of its global space relation, voxel spacing, ... + +### BIDS: bids_files.py -- Dataset Handling +This is the core of handling datasets that are BIDS-compliant. Easily search through your datasets and find all images following your constraints, such as every CT that also has a specific segmentation available. -| Symbol | Description | -|---|---| -| `POI` | Maps `(vertebra_id, subregion_id) → (x, y, z)` | -| `calc_centroids(seg_nii)` | Compute centroids for every label in a segmentation | -| `calc_poi_from_subreg_vert(vert, subreg)` | Compute POIs from paired vertebra + subregion segmentations | -| `POI.save(path)` | Serialise to JSON | -| `POI.load(path)` | Deserialise from JSON | -| `POI.to_global(ref)` | Convert from voxel to world (mm) coordinates | -| `POI.to_local(ref)` | Convert from world to voxel coordinates | + +## Other Key Classes and Functions ### `np_utils.py` — NumPy utilities +Numpy functionalities that a lot of NII functions above utilize under the hood. Most of them are optimized to run on uint numpy arrays. + | Symbol | Description | |---|---| | `np_extract_label(arr, label)` | Extract a single label as a binary mask | @@ -63,6 +35,15 @@ The `core` subpackage is the foundation of TPTBox. It provides the three primary | `np_map_labels(arr, label_map)` | Remap label integers via a dict | | `np_unique(arr)` | Unique values (faster than `np.unique` for uint arrays) | +```python +from TPTBox.core.np_utils import np_unique, np_center_of_mass + +a = np.array([0,1,2,3], [4,5,6,7], dtype=np.uint8) + +label = np_unique(a) +center_of_mass_of_label_four = np_center_of_mass(a)[4] +``` + ### `vert_constants.py` — Anatomical constants | Symbol | Description | @@ -76,22 +57,11 @@ The `core` subpackage is the foundation of TPTBox. It provides the three primary | `AX_CODES` | Type alias: `tuple[str, str, str]` | | `AFFINE` | Type alias: `np.ndarray` (4×4) | -## Quick Example - ```python -from TPTBox import NII, BIDS_Global_info, calc_centroids - -# Load and resample a CT -ct = NII.load("sub-001_ct.nii.gz", seg=False) -ct_ras = ct.reorient(("R", "A", "S")).rescale((1.0, 1.0, 1.0)) - -# Compute centroids from a segmentation -seg = NII.load("sub-001_seg.nii.gz", seg=True) -poi = calc_centroids(seg) -print(poi) +from TPTBox import NII, Location +# Segmentation +seg = NII.load("path/to/seg.nii.gz", seg=True) -# Scan a BIDS dataset -bids = BIDS_Global_info(["dataset/"], parents=["rawdata"]) -for subj, container in bids.enumerate_subjects(): - t2 = container.new_query().filter("format", "T2w").first() +# Get the segmentation of the Vertebra Corpus +seg_corpus = seg.extract_label(Location.Vertebra_Corpus) ``` diff --git a/TPTBox/core/README_BIDS.md b/TPTBox/core/README_BIDS.md new file mode 100644 index 00000000..e617f80e --- /dev/null +++ b/TPTBox/core/README_BIDS.md @@ -0,0 +1,89 @@ +# TPTBox `bids_files.py` — BIDS dataset navigation + +| Symbol | Description | +|---|---| +| `BIDS_Global_info` | Scans a dataset root and indexes all BIDS files | +| `BIDS_Global_info.enumerate_subjects()` | Iterate over subjects as `(subject_id, Subject_Container)` | +| `Subject_Container` | Per-subject file index; entry point for queries | +| `Subject_Container.new_query()` | Returns a `Searchquery` for this subject | +| `BIDS_FILE` | One file parsed into BIDS entities (sub, ses, format, …) | +| `BIDS_FILE.open_nii()` | Load this file's NIfTI | +| `BIDS_FILE.get_changed_path(...)` | Derive a new path with changed BIDS entities | +| `Searchquery` | Fluent query builder: `.filter()`, `.loop_dict()`, `.first()` | +| `BIDS_Family` | `dict[str, list[BIDS_FILE]]` grouping files by format | + + +Loop over every T2w MRI in a dataset: +```python +from TPTBox import BIDS_Global_info, BIDS_FILE, NII + +# Initialize the dataset and the folders therein to use +bids_dataset = BIDS_Global_info(["path/to/dataset"], parents=["rawdata"]) + +# looping over every subject in the dataset +for subject, container in bids.enumerate_subjects(): + q = container.new_query() + q.filter("format", "T2w") + # more filter here + bids_families = q.loop_dict() + # A subject can have multiple MRI images + for bids_family in bids_families: + # ensure this family has a T2w + if "T2w" in bids_family: + # get the reference to the T2w image + t2w_ref: BIDS_FILE = bids_family["T2w"][0] + # load the nifty + t2w: NII = t2w.open_nii() + + # further processing or analysis that would be + # run on every T2w MRI in this dataset +``` + +Investigate one BIDS_FILE and get a BIDS-compliant file path relative to it, guaranteeing a valid BIDS filename. +```python +from pathlib import Path + +from TPTBox import BIDS_FILE + +# Dataset root directory (must start with "dataset-") +root = Path("path/to/dataset-dsname") + +# Example BIDS-compliant input file +example_file = ( + root + / "rawdata/sub-Max-Mustermann/ses-01012026/anat/" + "sub-Max-Mustermann_ses-01012026_acq-sag_ce-GBCA_T1w.nii.gz" +) + +# Create a BIDS_FILE object +bf_file = BIDS_FILE(example_file, root) + +# Access individual BIDS keys +print(f"Subject name : {bf_file.get('sub')}") +print(f"Session : {bf_file.get('ses')}") +print(f"Acquisition direction : {bf_file.get('acq')}") +print(f"Contrast agent : {bf_file.get('ce')}") +print(f"Modality : {bf_file.bids_format}") + +# Generate a new BIDS-compliant file path relative to the source file +# (keys that are not explicitly overridden remain unchanged) +seg_path = bf_file.get_changed_path( + # File extension + file_type="nii.gz", + # Final suffix without a key, e.g., *_msk.nii.gz + bids_format="msk", + # Parent folder relative to the dataset root + parent="derivatives", + info={ + # Name of the segmentation + "seg": "spine", + # Modality from which this file was generated + "mod": bf_file.mod, + }, + # If True, disables sorting of keys according to the BIDS specification + no_sorting_mode=False, + # If True, disables strict validation against predefined key--value pairs + non_strict_mode=False, +) + +``` diff --git a/TPTBox/core/README_NII.md b/TPTBox/core/README_NII.md new file mode 100644 index 00000000..d581b69b --- /dev/null +++ b/TPTBox/core/README_NII.md @@ -0,0 +1,37 @@ +# TPTBox: `nii_wrapper.py` — NIfTI image wrapper + +The `core` subpackage is the foundation of TPTBox. It provides the three primary abstractions — +`NII`, `POI`, and `BIDS_FILE` — along with helper utilities for array operations and anatomical constants. + +| Symbol | Description | +|---|---| +| `NII` | Wraps `nibabel.Nifti1Image`; the central image type throughout TPTBox | +| `NII.load(path, seg)` | Load a NIfTI file from disk (classmethod) | +| `NII.from_numpy(arr, affine, seg)` | Construct from a numpy array and affine matrix | +| `NII.reorient(axcodes_to)` | Reorient to a canonical axis code (e.g. `("R","A","S")`) | +| `NII.rescale(voxel_spacing)` | Resample to new voxel spacing in mm | +| `NII.resample_from_to(other)` | Resample to match the grid of another `NII` | +| `NII.apply_mask(mask)` | Zero-out voxels outside a binary/label mask | +| `NII.map_labels(label_map)` | Remap integer labels | +| `NII.save(path)` | Save to disk as `.nii` or `.nii.gz` | +| `NII.get_array()` | Return a copy of the underlying numpy array | +| `NII.get_seg_array()` | Same as `get_array()` but asserts `seg=True` | +| `Image_Reference` | Type alias: `BIDS_FILE | Nifti1Image | Path | str | NII` | + +```python +from TPTBox import NII +# Image +nii = NII.load("path/to/img.nii.gz", seg=False) +# Segmentation +seg = NII.load("path/to/seg.nii.gz", seg=True) + +# Standardize the image to a fixed orientation (Right-Anterior-Superior in the nibabel coordinate system) +# and resample it to an isotropic resolution of 1 mm x 1 mm x 1 mm +nii_rescaled = nii.reorient("RAS").rescale((1, 1, 1)) + +# One-line function to resample another image to match a reference image +seg_resampled = seg.resample_from_to(nii_rescaled) + +# The appropriate resampling method is automatically selected depending on +# whether the image represents a segmentation or a continuous-valued image. +``` diff --git a/TPTBox/core/README_POI.md b/TPTBox/core/README_POI.md new file mode 100644 index 00000000..5f911a87 --- /dev/null +++ b/TPTBox/core/README_POI.md @@ -0,0 +1,142 @@ +# TPTBox `poi.py` — Points of Interest + +![Example of two lumbar vertebrae. The left example is derived from 1 mm isotropic CT, the right from sagittal MRI with a resolution of 3.3 mm in the left–right direction. Top row: Subregion of the vertebra used for analysis. Middle row: Extreme points. Bottom row: Corpus edge and ligamentum flavum points.](../images/poi_preview.png) + + +| Symbol | Description | +|---|---| +| `POI` | Maps `(vertebra_id, subregion_id) → (x, y, z)` | +| `calc_centroids(seg_nii)` | Compute centroids for every label in a segmentation | +| `calc_poi_from_subreg_vert(vert, subreg)` | Compute POIs from paired vertebra + subregion segmentations | +| `POI.save(path)` | Serialise to JSON | +| `POI.load(path)` | Deserialise from JSON | +| `POI.to_global(ref)` | Convert from voxel to world (mm) coordinates | +| `POI.to_local(ref)` | Convert from world to voxel coordinates | +| `POI.save_mrk(ref)` | Saves the POI as Markup (to be used for 3D Slicer for example) | + +Compute a simple poi object from a segmentation file +```python +from TPTBox import NII, calc_centroids + +vert = NII.load("path/to/seg.nii.gz", True) +label_id = 20 +second_stage = 1 +# compute CMS +poi = calc_centroids(vert, second_stage=second_stage) +# The coordinate can be extracted by [Label-id, second_stage_id] +coords = poi[label_id, second_stage] +``` + +Compute a full set of anatomical landmarks. The registry of supported non-centroid POI strategies is exposed as +```python +from TPTBox.core.poi_fun.vertebra_pois_non_centroids import all_poi_functions + +poi_full = calc_poi_from_subreg_vert( + instance_nii, semantic_nii, + subreg_id=list(all_poi_functions.keys()), +) +# export as a 3D Slicer markup file +poi_full.to_global().save_mrk( + "poi_as_markup.mrk.json", + split_by_region=True, + pointLabelsVisibility=True +) +``` + + +```python +from TPTBox import NII, POI, Location, POI_Global, calc_poi_from_subreg_vert +from TPTBox.core.vert_constants import v_name2idx +from TPTBox.segmentation.spineps import run_spineps_single + +# This requires that spineps is installed +output_paths = run_spineps_single( + "file-path-of_T2w.nii.gz", + model_semantic="t2w", + ignore_compatibility_issues=True, +) +out_spine = output_paths["out_spine"] +out_vert = output_paths["out_vert"] +semantic_nii = NII.load(out_spine, seg=True) +instance_nii = NII.load(out_vert, seg=True) + +poi = calc_poi_from_subreg_vert( + instance_nii, + semantic_nii, + subreg_id=[ + Location.Vertebra_Full, + Location.Arcus_Vertebrae, + Location.Spinosus_Process, + Location.Costal_Process_Left, + Location.Costal_Process_Right, + Location.Superior_Articular_Left, + Location.Superior_Articular_Right, + Location.Inferior_Articular_Left, + Location.Inferior_Articular_Right, + # Location.Vertebra_Corpus_border, CT only + Location.Vertebra_Corpus, + Location.Vertebra_Disc, + Location.Muscle_Inserts_Spinosus_Process, + Location.Muscle_Inserts_Transverse_Process_Left, + Location.Muscle_Inserts_Transverse_Process_Right, + Location.Muscle_Inserts_Vertebral_Body_Left, + Location.Muscle_Inserts_Vertebral_Body_Right, + Location.Muscle_Inserts_Articulate_Process_Inferior_Left, + Location.Muscle_Inserts_Articulate_Process_Inferior_Right, + Location.Muscle_Inserts_Articulate_Process_Superior_Left, + Location.Muscle_Inserts_Articulate_Process_Superior_Right, + Location.Ligament_Attachment_Point_Anterior_Longitudinal_Superior_Median, + Location.Ligament_Attachment_Point_Posterior_Longitudinal_Superior_Median, + Location.Ligament_Attachment_Point_Anterior_Longitudinal_Inferior_Median, + Location.Ligament_Attachment_Point_Posterior_Longitudinal_Inferior_Median, + Location.Additional_Vertebral_Body_Middle_Superior_Median, + Location.Additional_Vertebral_Body_Posterior_Central_Median, + Location.Additional_Vertebral_Body_Middle_Inferior_Median, + Location.Additional_Vertebral_Body_Anterior_Central_Median, + Location.Ligament_Attachment_Point_Anterior_Longitudinal_Superior_Left, + Location.Ligament_Attachment_Point_Posterior_Longitudinal_Superior_Left, + Location.Ligament_Attachment_Point_Anterior_Longitudinal_Inferior_Left, + Location.Ligament_Attachment_Point_Posterior_Longitudinal_Inferior_Left, + Location.Additional_Vertebral_Body_Middle_Superior_Left, + Location.Additional_Vertebral_Body_Posterior_Central_Left, + Location.Additional_Vertebral_Body_Middle_Inferior_Left, + Location.Additional_Vertebral_Body_Anterior_Central_Left, + Location.Ligament_Attachment_Point_Anterior_Longitudinal_Superior_Right, + Location.Ligament_Attachment_Point_Posterior_Longitudinal_Superior_Right, + Location.Ligament_Attachment_Point_Anterior_Longitudinal_Inferior_Right, + Location.Ligament_Attachment_Point_Posterior_Longitudinal_Inferior_Right, + Location.Additional_Vertebral_Body_Middle_Superior_Right, + Location.Additional_Vertebral_Body_Posterior_Central_Right, + Location.Additional_Vertebral_Body_Middle_Inferior_Right, + Location.Additional_Vertebral_Body_Anterior_Central_Right, + Location.Ligament_Attachment_Point_Flava_Superior_Median, + Location.Ligament_Attachment_Point_Flava_Inferior_Median, + Location.Vertebra_Direction_Posterior, + Location.Vertebra_Direction_Inferior, + Location.Vertebra_Direction_Right, + ], +) +poi = poi.round(2) +print("Vertebra T4 Vertebra Corpus Center of mass:", poi[v_name2idx["T4"], Location.Vertebra_Corpus]) +print("The id number of T4 Vertebra_Corpus is ", v_name2idx["T4"], Location.Vertebra_Corpus.value) + +# rescale/reorante local poi like nii +poi_new = poi.reorient(("P", "I", "R")).rescale((1, 1, 1)) +# Local and global POIs can be rescaled to a target spacing with: +poi_new = poi.resample_from_to(other_nii_or_poi) + +# local to global poi +global_poi = poi.to_global(itk_coords=True) +# You can save global pois in mrk.json format for import and editing in slicer. +global_poi.save_mrk("FILE.mrk.json", glyphScale=3.0) +# Import as a Markup in slicer; To make points editable you must click on the "lock" symbol under Markups - Control Points - Interaction + +# Save in our format: +poi.save(poi_path) +# Loading local/global Poi +poi = POI.load(poi_path) +poi = POI_Global.load(poi_path) + + + +``` diff --git a/TPTBox/images/TPTBox_overview.png b/TPTBox/images/TPTBox_overview.png new file mode 100755 index 00000000..52b21226 Binary files /dev/null and b/TPTBox/images/TPTBox_overview.png differ diff --git a/TPTBox/logger/README.md b/TPTBox/logger/README.md index f423b78a..3f677e2f 100644 --- a/TPTBox/logger/README.md +++ b/TPTBox/logger/README.md @@ -9,6 +9,8 @@ Provides a simple interface with configurable verbosity, message categories, and from TPTBox import Logger, Print_Logger, No_Logger, String_Logger, Log_Type ``` +![Example of logging](logging.png?raw=true "Example of logging messages") + ## Key classes | Class | Description | @@ -29,9 +31,33 @@ from TPTBox import Logger, Print_Logger, No_Logger, String_Logger, Log_Type | `Log_Type.WARNING` | Non-fatal warning | | `Log_Type.FAIL` | Error or failure | | `Log_Type.TEXT` | Plain informational text | +| `Log_Type.SAVE` | Saving files to disk | +| `Log_Type.STAGE` | Marking start of different phases | +| `Log_Type.LOG` | Information regarding the logger itself | ## Example +```python +logger.print() # logs/prints empty line + +logger.print("Started logging to path: ./logs/test.log", lt.LOG) +logger.print() +logger.print("Phase 1: Data Preprocessing", lt.STAGE) +with logger: + logger.print("Loading data...") + logger.print("Data loaded successfully.", lt.OK) + logger.print("Saving preprocessed data...", lt.SAVE) + logger.print("Warning: Some data points were missing and have been filled with default values.", lt.WARNING) +logger.print("Phase 2: Measurement", lt.STAGE) +with logger: + logger.print("Starting measurements...") + logger.print("Error: Measurement failed due to missing data.", lt.FAIL) +``` + +The output would be: +![Example of logging](loggingexample.png?raw=true "Example of logging messages") + + ```python from TPTBox import Logger, Log_Type diff --git a/TPTBox/logger/logging.png b/TPTBox/logger/logging.png new file mode 100755 index 00000000..464bc5d4 Binary files /dev/null and b/TPTBox/logger/logging.png differ diff --git a/TPTBox/logger/loggingexample.png b/TPTBox/logger/loggingexample.png new file mode 100755 index 00000000..ed345dba Binary files /dev/null and b/TPTBox/logger/loggingexample.png differ diff --git a/TPTBox/mesh3D/README.md b/TPTBox/mesh3D/README.md index 858f5f1f..2d018cb9 100644 --- a/TPTBox/mesh3D/README.md +++ b/TPTBox/mesh3D/README.md @@ -3,6 +3,8 @@ 3D surface mesh generation from segmentation NIfTI volumes and rendering of 3D snapshots. Requires `pyvista` and `vtk` (included in the `dev` extras). +![Snapshot3D example](TPTBox/images/snp3D_example.jpg) + ## Key symbols | Symbol | Module | Description | @@ -13,6 +15,16 @@ Requires `pyvista` and `vtk` (included in the `dev` extras). | `label_to_color(label_id)` | `mesh_colors.py` | Look up the RGB colour for a given label | | `create_html_preview(meshes)` | `html_preview.py` | Generate an interactive HTML file with an embedded 3D viewer | + +## Installation + +```bash +pip install pyvista vtk +# or via the dev extras: +poetry install --with dev +``` + + ## Example ```python @@ -27,10 +39,38 @@ meshes = [Mesh(seg, label=lbl) for lbl in seg.unique_labels()] create_snapshot3D(meshes, to="snapshot3D.png") ``` -## Installation +More extensive: +```python +from TPTBox.core.vert_constants import Full_Body_Instance +from TPTBox.mesh3D.snapshot3D import make_snapshot3D_parallel -```bash -pip install pyvista vtk -# or via the dev extras: -poetry install --with dev +path = "/path/to/folder" +seg = path / "seg-VIBESeg-11-lr_msk.nii.gz" +out_path = path / "snp3D.jpg" +out_path2 = path / "snp3D_2.jpg" +# We recommend using the parallel application of this function +# because it takes a minute, but does not need a lot of resources. +make_snapshot3D_parallel( + [seg], + [out_path], + view=["A"], + ids_list=[ + [a.value for a in Full_Body_Instance.bone()], + [a.value for a in Full_Body_Instance.lung_system()], + [a.value for a in Full_Body_Instance.organs()], + [a.value for a in Full_Body_Instance.digestion()], + [a.value for a in Full_Body_Instance.vessels()], + [a.value for a in Full_Body_Instance.full_spine()], + [a.value for a in Full_Body_Instance.muscle()], + [a.value for a in Full_Body_Instance.body_comp()], + ], +) +make_snapshot3D_parallel( + [seg], + [out_path2], + view=["A", "R", "P", "L"], + ids_list=[ + [a.value for a in Full_Body_Instance.bone()], + ], +) ``` diff --git a/TPTBox/registration/README.md b/TPTBox/registration/README.md index 16ac47d8..8d9f23a7 100644 --- a/TPTBox/registration/README.md +++ b/TPTBox/registration/README.md @@ -38,15 +38,16 @@ pip install hf-deepali # only needed for General_Registration / Rigid_Elements ## Example ```python -from TPTBox.registration import ridged_points_from_poi +from TPTBox import NII, POI +from TPTBox.registration import Point_Registration + +poi_fixed = POI.load("path/to/poi.json") +poi_moving = POI.load("path/to/poi.json") +# update resolution/orientation of poi_fixed, if you would like the resampe into an specific space +reg_obj = Point_Registration(poi_fixed, poi_moving) +# appling the transformation +nii_moving = NII.load("path/to/moving_img.nii.gz", False) +nii_moved = reg_obj.transform_nii(nii_moving) +poi_moved = reg_obj.transform_poi(poi_moving) -fixed_nii = NII.load("fixed.nii.gz", seg=False) -moving_nii = NII.load("moving.nii.gz", seg=False) - -registered, transform = ridged_points_from_poi( - fixed_nii, moving_nii, - poi_fixed=poi_fixed, - poi_moving=poi_moving, -) -registered.save("registered.nii.gz") ``` diff --git a/TPTBox/segmentation/README.md b/TPTBox/segmentation/README.md index ee1bfdb9..0d7daea5 100644 --- a/TPTBox/segmentation/README.md +++ b/TPTBox/segmentation/README.md @@ -25,7 +25,6 @@ from TPTBox.segmentation import ( | `run_totalvibeseg(img_nii, ...)` | `VibeSeg/vibeseg.py` | Run TotalVibeSeg — extended label set | | `run_nnunet(img_nii, model_dir, ...)` | `VibeSeg/vibeseg.py` | Generic nnU-Net inference on a single NIfTI | | `run_inference_on_file(path, ...)` | `nnUnet_utils/inference_api.py` | Low-level nnU-Net inference on a file path | -| `extract_vertebra_bodies_from_VibeSeg(seg)` | `VibeSeg/vibeseg.py` | Post-process VibeSeg output to isolate vertebra bodies | ## Dependencies @@ -48,3 +47,43 @@ ct = NII.load("ct.nii.gz", seg=False) vert_seg, subreg_seg = run_spineps(ct, model="small") vert_seg.save("vertebrae.nii.gz") ``` + + +Full script example for VIBEseg: +```python +""" +Example usage of VIBESeg for full-body MRI segmentation. + +This script demonstrates how to run the VIBESeg pipeline on a single +NIfTI image and store the resulting segmentation to disk. +""" + +from TPTBox.segmentation import run_vibeseg + + +def main() -> None: + """ + Run VIBESeg on a single input image. + """ + image = "path_or_nii_of_img.nii.gz" + output_path = "VIBESeg.nii.gz" + + run_vibeseg( + image=image, + out_path=output_path, + override=True, + gpu=0, + ddevice="cuda", + # dataset_id=100, # defaults to the newest available model + padd=5, + # Update the memory estimation + memory_base=5000, # Base memory in MB, default is 5GB + memory_factor=160, # prod(shape)*memory_factor/1000, 160 -> 30 GB + memory_max=16000, # in MB, here is 16GB + wait_till_gpu_percent_is_free=0.1, + ) + + +if __name__ == "__main__": + main() +``` diff --git a/TPTBox/spine/snapshot2D/README.md b/TPTBox/spine/snapshot2D/README.md index f8bf7927..c2cdd642 100644 --- a/TPTBox/spine/snapshot2D/README.md +++ b/TPTBox/spine/snapshot2D/README.md @@ -3,6 +3,8 @@ Modular 2D image generation for NIfTI data. Supports axial/sagittal/coronal slices, maximum intensity projections (MIPs), and segmentation overlays. +![Snapshot2D Spine example](../../images/snp2D_example.png) + ## Key symbols | Symbol | Module | Description | @@ -24,3 +26,55 @@ frames = [ ] create_snapshot(frames, to="output.png") ``` + +More extensive: +```python +from pathlib import Path +from TPTBox import calc_poi_from_subreg_vert +from TPTBox.spine.snapshot2D import Snapshot_Frame, Visualization_Type, create_snapshot + +path = Path("root") +img = path / "ct.nii.gz" +vert = path / "seg-vert_msk.nii.gz" +subreg = path / "seg-spine_msk.nii.gz" +out_path = path / "snp.jpg" +out_path2 = path / "snp2.jpg" +poi = calc_poi_from_subreg_vert(vert, subreg) +create_snapshot( + out_path, + [ + Snapshot_Frame(img, vert, poi, sagittal=True, coronal=True, + mode="CT"), + Snapshot_Frame(img, subreg, poi, sagittal=True, coronal=True, + axial=True, mode="CTs", axial_heights=[0.20, 0.4, 0.6, 0.8]), + ], +) +create_snapshot( + out_path2, + [ + Snapshot_Frame( + img, subreg, poi, + sagittal=True, coronal=True, + mode="MINMAX", + only_mask_area=True, + hide_segmentation=True, + ), + Snapshot_Frame( + img, subreg, poi, + sagittal=True, coronal=True, + mode="MINMAX", + only_mask_area=True, + visualization_type=Visualization_Type.Maximum_Intensity, + hide_segmentation=True, + ), + Snapshot_Frame( + img, subreg, poi, + sagittal=True, coronal=True, + mode="MINMAX", + visualization_type=Visualization_Type.Maximum_Intensity, + hide_segmentation=True, + ), + ], +) + +``` diff --git a/TPTBox/stitching/README.md b/TPTBox/stitching/README.md index bc801624..43096d51 100644 --- a/TPTBox/stitching/README.md +++ b/TPTBox/stitching/README.md @@ -10,13 +10,13 @@ You can verify alignment by opening the images in ITKSnap with "open additional |---|---| | `stitching(nii_list, out, ...)` | Stitch a list of `NII` objects; returns `(result_nii, ramp_nii)` | | `stitching_raw(paths, out, ...)` | Stitch from file paths directly | -| `GNC_stitch_T2w(nii_list, ...)` | GNC-based stitching optimised for T2w spine MRI | +| `NAKO_stitch_T2w(nii_list, ...)` | NAKO-based stitching optimised for T2w spine MRI | ![Example of a stitching](stitching.jpg?raw=true "Example of a stitching") ### Standalone -This script can be run directly from the console. Copy 'stiching.py' and install the necessary package. +This script can be run directly from the console. Copy 'stitching.py' and install the necessary package. ``` stitching.py @@ -56,18 +56,22 @@ pip install TPTBox ``` ```python -from TPTBox import NII from TPTBox.stitching import stitching -out_nii,_ = stitching([NII.load("a.nii.gz",seg=False), NII.load("b.nii.gz",seg=False), NII.load("c.nii.gz",seg=False)], out="out.nii.gz") -``` - -or - - -```python -from TPTBox.stitching import stitching_raw -stitching_raw(["a.nii.gz", "b.nii.gz", "c.nii.gz"], "out.nii.gz", is_segmentation=False) +list_of_files = ["File_A.nii.gz", "File_B.nii.gz", "File_C.nii.gz",] + +# Call the stitching function +# This will combine your images into a single NIfTI file +stitching( + list_of_files, # List of input files + out="out_path_stitched_image.nii.gz", # Path to save stitched output + is_seg=False, # Set True if these are segmentation masks + is_ct=False, # True for CT, min_value will by -1024 instead of 0 + kick_out_fully_integrated_images=True, + dtype=float, # Data type of the output image + match_histogram=False, # Match intensity histograms across images + store_ramp=False, # Store blending ramp (optional) +) ```