- Overview
- Installation
- Usage Modes
- Command-Line Interface
- Python API
- Configuration
- Examples
- Output
- Troubleshooting
PyTIA computes Time-Integrated Activity (TIA) maps from PET/SPECT imaging data.
TIA represents the integral of activity over time, essential for:
- Absorbed dose calculations
- Biokinetic modeling
- Treatment planning
- Fits kinetic models to activity curves
- Estimates TIA through model integration
- Provides model fit quality (R²)
- Uncertainty via bootstrap
- Direct TIA calculation from single snapshot
- Three methods: physical decay, Hänscheid, prior half-life
- Ideal for clinical routine with time constraints
pip install pytiagit clone https://github.com/devhliu/PyTIA.git
cd PyTIA
pip install -e ".[dev]" # With dev toolsMost common and flexible approach:
# Run analysis
pytia run --config config.yaml
# Validate config before running
pytia validate --config config.yaml
# View config file
pytia info --config config.yamlAdvantages:
- No Python coding required
- Reproducible (config files can be versioned)
- Batch processing scripts easily
Direct Python control:
from pytia import run_tia
result = run_tia(
images=["t0.nii.gz", "t1.nii.gz"],
times=[0.0, 60.0],
config="config.yaml"
)
# Access results
tia_data = result.tia_img.get_fdata()Advantages:
- Fine-grained control
- Scripting and automation
- Integration with other tools
pytia run --config config.yamlArguments:
--config(required): Path to YAML configuration file
Output: Saves results to io.output_dir specified in config
Example:
pytia run --config examples/config_multitime.yamlpytia validate --config config.yamlChecks:
- YAML syntax validity
- Required config sections
- Input file existence
Example:
pytia validate --config config.yaml
✓ Config file is valid
Config structure:
- inputs
- io
- physics
- single_timepytia info --config config.yamlDisplays config file contents (useful for verification before running).
from pytia import run_tia
from pytia import Config
# Method 1: Use config dict
result = run_tia(
images=["img1.nii.gz", "img2.nii.gz"],
times=[0.0, 60.0],
config={"physics": {"half_life_seconds": 3600.0}}
)
# Method 2: Load from YAML
result = run_tia(
images=["img1.nii.gz", "img2.nii.gz"],
times=[0.0, 60.0],
config="config.yaml"
)
# Method 3: Use Config object
cfg = Config.load("config.yaml")
result = run_tia(
images=["img1.nii.gz", "img2.nii.gz"],
times=[0.0, 60.0],
config=cfg.data
)from pytia import Results
# Access output images (nibabel objects)
tia_img = result.tia_img
r2_img = result.r2_img
model_img = result.model_id_img
status_img = result.status_id_img
sigma_img = result.sigma_tia_img
# Convert to numpy arrays
import numpy as np
tia_data = np.asarray(tia_img.dataobj)
# Summary statistics
summary = result.summary
print(f"Times: {summary['times_seconds']}")
print(f"Valid voxels: {summary['status_counts']['ok']}")
# Output file paths
for key, path in result.output_paths.items():
print(f"{key}: {path}")from pytia import load_images, stack_4d
import nibabel as nib
# Load images
imgs = load_images(["t0.nii.gz", "t1.nii.gz", "t2.nii.gz"])
# Stack into 4D array
data_4d, ref_img = stack_4d(imgs)
print(f"Shape: {data_4d.shape}") # (X, Y, Z, T)All settings are in YAML config files. No hardcoding!
inputs: # Image paths and timepoints
io: # Output directory
physics: # Half-life settings
time: # Unit conversion
mask: # Masking strategy
denoise: # Denoising
noise_floor: # Validity filtering
bootstrap: # Uncertainty
model_selection: # Curve fitting
integration: # Integration parameters
regions: # Optional ROI analysis
single_time: # Single-timepoint settingsinputs:
images:
- activity_t0.nii.gz
- activity_t1.nii.gz
times: [0.0, 60.0]
io:
output_dir: ./output
physics:
half_life_seconds: 21600.0See CONFIG.md for detailed reference of all options.
# config_tc99m.yaml
inputs:
images: [scan_0h.nii.gz, scan_1h.nii.gz, scan_2h.nii.gz]
times: [0.0, 3600.0, 7200.0]
io:
output_dir: ./output/tc99m
physics:
half_life_seconds: 21600.0 # 6 hours
denoise:
enabled: true
sigma_vox: 1.5
noise_floor:
enabled: true
relative_fraction_of_voxel_max: 0.01
bootstrap:
enabled: true
n: 100Run:
pytia run --config config_tc99m.yaml# config_stp_phys.yaml
inputs:
images: [activity_snapshot.nii.gz]
times: [0.0]
io:
output_dir: ./output/stp_phys
physics:
half_life_seconds: 6600.0 # F-18
single_time:
enabled: true
method: physRun:
pytia run --config config_stp_phys.yaml# config_stp_organs.yaml
inputs:
images: [activity.nii.gz]
times: [0.0]
io:
output_dir: ./output/stp_organs
single_time:
enabled: true
method: prior_half_life
label_map_path: segmentation.nii.gz
label_half_lives:
1: 1800.0 # Tumor: 30 min
2: 3600.0 # Liver: 60 min
3: 5400.0 # Kidney: 90 min
half_life_seconds: 3600.0 # DefaultSee examples/ folder for runnable examples:
example_multitime.py— Multi-timepoint demoexample_stp.py— Single-timepoint demos- Config files in
examples/*.yaml
| File | Content | Notes |
|---|---|---|
tia.nii.gz |
Time-integrated activity | Bq·s/ml |
r2.nii.gz |
Model fit quality | NaN for STP |
sigma_tia.nii.gz |
Uncertainty (std dev) | If bootstrap enabled |
model_id.nii.gz |
Method ID per voxel | 10/11/20/30/101/102/103 |
status_id.nii.gz |
Validity status | 0-5 |
pytia_summary.yaml |
Metadata and config | YAML format |
The status_id.nii.gz output map provides a voxel-wise report on the outcome of the TIA calculation. Each voxel is assigned an integer code.
| Code | Status | Description |
|---|---|---|
| 0 | outside mask/background |
The voxel was outside the processing mask and was ignored. |
| 1 | ok |
A valid TIA value was successfully computed. |
| 2 | not applicable: <2 valid points |
The voxel had fewer than two valid data points after noise filtering, so no model could be fit. |
| 3 | fit failed |
A model was attempted but failed to converge. This can happen with very noisy data or missing configuration (e.g., physics.half_life_seconds for hybrid models). |
| 4 | all points below noise floor |
All activity timepoints for this voxel were below the configured noise floor and were excluded. |
| 5 | nonphysical parameters |
The model fit produced parameters that are not physically plausible. |
| Code | Model |
|---|---|
| 10 | Hybrid (rising) |
| 11 | Hybrid with phys tail |
| 20 | Exponential (falling) |
| 30 | Gamma-linear (hump) |
| Code | Method |
|---|---|
| 101 | Physical decay |
| 102 | Hänscheid effective |
| 103 | Prior half-life |
Example pytia_summary.yaml:
pytia_version: "0.1.0"
times_seconds: [0.0, 60.0, 120.0]
voxel_volume_ml: 8.0
status_legend:
0: "outside mask/background"
1: "ok"
2: "not applicable: invalid decay rate"
3: "fit failed"
4: "all points below noise floor"
status_counts:
ok: 125400
outside mask/background: 10000
all points below noise floor: 234
timing_ms:
load_sort_ms: 245.3
mask_denoise_ms: 1523.4
voxel_fit_ms: 8234.1
bootstrap_ms: 15023.4
assemble_ms: 132.1
save_ms: 234.5
total_ms: 25393.3Cause: Missing inputs section in config
Solution:
inputs:
images: [activity_t0.nii.gz, activity_t1.nii.gz]
times: [0.0, 60.0]Cause: Missing physics.half_life_seconds
Solution:
physics:
half_life_seconds: 21600.0 # Add thisCause: Half-life unit mismatch
Solution: Ensure all times in seconds:
- 1 hour = 3600 seconds
- 1 minute = 60 seconds
- 110 minutes = 6600 seconds
Cause: Label values don't match mapping
Solution:
- Check label image:
nibabelviewer or ITK-SNAP - Match keys in
label_half_lives:
import nibabel as nib
import numpy as np
img = nib.load("segmentation.nii.gz")
labels = np.unique(np.asarray(img.dataobj))
print(f"Unique labels: {labels}")Then update config:
label_half_lives:
1: 1800.0
2: 3600.0
# ... etcCause: Processing all voxels at once
Solution: Enable chunking in config:
performance:
chunk_size_vox: 250000 # Reduce if still too largeCreate a shell script:
#!/bin/bash
for config in configs/*.yaml; do
echo "Processing $config..."
pytia run --config "$config"
doneAutomate config generation:
import yaml
from pytia import run_tia
for patient_id in ["P001", "P002", "P003"]:
config = {
"inputs": {
"images": [f"data/{patient_id}/t0.nii.gz", f"data/{patient_id}/t1.nii.gz"],
"times": [0.0, 60.0],
},
"io": {"output_dir": f"output/{patient_id}"},
"physics": {"half_life_seconds": 21600.0},
}
result = run_tia(
images=config["inputs"]["images"],
times=config["inputs"]["times"],
config=config,
)
print(f"{patient_id}: TIA computed!")Check summary after running:
import yaml
with open("output/pytia_summary.yaml") as f:
summary = yaml.safe_load(f)
print(f"Valid voxels: {summary['status_counts']['ok']}")
print(f"Total time: {summary['timing_ms']['total_ms']:.1f} ms")For more information: See docs/CONFIG.md for complete configuration reference.