This guide provides comprehensive documentation for using PyTIA, a Python package for computing voxel-wise Time-Integrated Activity (TIA) maps from PET/SPECT imaging data.
- Python 3.12 or later
- pip package manager
pip install pytiagit clone https://github.com/devhliu/PyTIA.git
cd PyTIA
pip install -e .pip install -e ".[dev]"# Multi-timepoint TIA calculation
pytia nifti --images scan1.nii.gz scan2.nii.gz scan3.nii.gz \
--times 1.0 24.0 72.0 \
--time-unit hours \
--half-life 21636.0
# Single-timepoint TIA calculation
pytia nifti --images scan1.nii.gz \
--times 24.0 \
--single-time \
--stp-method phys \
--half-life 21636.0from pytia import run_tia_from_nifti
# Multi-timepoint TIA calculation
result = run_tia_from_nifti(
images=["scan1.nii.gz", "scan2.nii.gz", "scan3.nii.gz"],
times=[1.0, 24.0, 72.0],
time_unit="hours",
half_life_seconds=21636.0,
)
# Single-timepoint TIA calculation
from pytia import run_single_timepoint_tia
result = run_single_timepoint_tia(
image="scan1.nii.gz",
time=24.0,
method="phys",
half_life_seconds=21636.0,
)PyTIA provides a command-line interface (CLI) with three main commands:
pytia run- Run TIA estimation with a configuration filepytia nifti- Run TIA estimation directly from NIfTI filespytia validate- Validate a configuration filepytia info- Show configuration file contents
The nifti command provides a convenient way to run TIA estimation without creating a configuration file.
pytia nifti --images <files> --times <times> [options]--images: NIfTI image files (one or more)--times: Timepoints for each image (space-separated)
| Argument | Description | Default |
|---|---|---|
--time-unit |
Time unit: hours or seconds |
hours |
--output-dir |
Output directory | ./pytia_output |
--prefix |
Output file prefix | pytia |
--half-life |
Radionuclide half-life in seconds | None |
--mask |
Mask image file | None |
--mask-mode |
Mask mode: none, provided, auto |
none |
--no-denoise |
Disable denoising | False |
--no-noise-floor |
Disable noise floor filtering | False |
--no-bootstrap |
Disable bootstrap | False |
--bootstrap |
Enable bootstrap with N replicates | False |
--bootstrap-seed |
Bootstrap random seed | 42 |
--chunk-size |
Chunk size for voxel processing | None |
--single-time |
Enable single-timepoint mode | False |
--stp-method |
Single-timepoint method | phys |
--eff-half-life |
Effective half-life for Hänscheid method | None |
--prior-half-life |
Prior half-life for prior_half_life method | None |
--label-map |
Label map for segmentation-based priors | None |
--label-half-lives |
Label half-lives mapping (JSON format) | None |
Multi-timepoint TIA (2+ images):
pytia nifti --images img1.nii.gz img2.nii.gz img3.nii.gz \
--times 1 24 48 \
--time-unit hoursSingle-timepoint TIA (physical decay):
pytia nifti --images img1.nii.gz \
--times 24 \
--single-time \
--stp-method phys \
--half-life 21636.0Single-timepoint TIA (Hänscheid method):
pytia nifti --images img1.nii.gz \
--times 24 \
--single-time \
--stp-method haenscheid \
--eff-half-life 3600.0Single-timepoint TIA (segmentation-based priors):
pytia nifti --images img1.nii.gz \
--times 24 \
--single-time \
--stp-method prior_half_life \
--label-map labels.nii.gz \
--label-half-lives '{"1": 1800.0, "2": 3600.0}'With mask and custom output:
pytia nifti --images img*.nii.gz \
--times 1 24 48 \
--mask mask.nii.gz \
--output-dir ./results \
--prefix patient1With bootstrap for uncertainty:
pytia nifti --images img*.nii.gz \
--times 1 24 48 \
--bootstrap 100 \
--bootstrap-seed 42The run command executes TIA estimation using a YAML configuration file.
pytia run --config <config_file>pytia run --config config.yamlValidates a configuration file without running the analysis.
pytia validate --config <config_file>pytia validate --config config.yamlDisplays the contents of a configuration file.
pytia info --config <config_file>pytia info --config config.yamlPyTIA provides a comprehensive Python API for integrating TIA calculation into custom workflows.
Run TIA estimation from NIfTI files with simplified API.
Parameters:
images: NIfTI image files or nibabel objects (one or more)times: Timepoints for each imageoutput_dir: Output directory for results (default:./pytia_output)prefix: Prefix for output files (default:pytia)half_life_seconds: Radionuclide half-life in secondsmask: Optional mask image file or nibabel objectmask_mode: Mask mode:"none","provided", or"auto"(default:"none")time_unit: Time unit:"hours"or"seconds"(default:"hours")denoise: Enable spatial denoising (default:True)noise_floor: Enable noise floor filtering (default:True)bootstrap: Enable bootstrap for uncertainty (default:False)bootstrap_n: Number of bootstrap replicates (default:100)bootstrap_seed: Random seed for bootstrap (default:42)chunk_size: Chunk size for voxel processing (default:None)**kwargs: Additional configuration options
Returns: Results object containing TIA maps and metadata
Example:
from pytia import run_tia_from_nifti
result = run_tia_from_nifti(
images=["scan1.nii.gz", "scan2.nii.gz", "scan3.nii.gz"],
times=[1.0, 24.0, 72.0],
time_unit="hours",
half_life_seconds=21636.0,
output_dir="./output",
prefix="patient_001",
)
tia_data = result.tia_img.get_fdata()
print(f"TIA shape: {tia_data.shape}")Run single-timepoint TIA estimation from NIfTI file.
Parameters:
image: Single NIfTI image file or nibabel objecttime: Timepoint for the imagemethod: Method:"phys","haenscheid", or"prior_half_life"(default:"phys")output_dir: Output directory for results (default:./pytia_output)prefix: Prefix for output files (default:pytia)half_life_seconds: Radionuclide half-life in seconds (forphysmethod)eff_half_life_seconds: Effective half-life in seconds (forhaenscheidmethod)prior_half_life_seconds: Prior half-life in seconds (forprior_half_lifemethod)label_map: Label map for segmentation-based priors (forprior_half_lifemethod)label_half_lives: Mapping of label IDs to half-lives in seconds (forprior_half_lifemethod)time_unit: Time unit:"hours"or"seconds"(default:"hours")mask: Optional mask image file or nibabel objectmask_mode: Mask mode:"none","provided", or"auto"(default:"none")denoise: Enable spatial denoising (default:True)noise_floor: Enable noise floor filtering (default:True)**kwargs: Additional configuration options
Returns: Results object containing TIA maps and metadata
Example:
from pytia import run_single_timepoint_tia
result = run_single_timepoint_tia(
image="scan1.nii.gz",
time=24.0,
method="phys",
half_life_seconds=21636.0,
)
tia_data = result.tia_img.get_fdata()
print(f"TIA shape: {tia_data.shape}")Run TIA estimation with full configuration control.
Parameters:
images: Single image or sequence of images (NIfTI paths or nibabel objects)times: Timepoints in specified unit (one per image)config: Config dict, YAML path, orNone(uses defaults)mask: Optional mask image (path or nibabel object)
Returns: Results object containing TIA maps and metadata
Example:
from pytia import run_tia
config = {
"inputs": {
"images": ["scan1.nii.gz", "scan2.nii.gz", "scan3.nii.gz"],
"times": [1.0, 24.0, 72.0],
},
"time": {"unit": "hours"},
"io": {
"output_dir": "./output",
"prefix": "patient_001",
},
"physics": {"half_life_seconds": 21636.0},
}
result = run_tia(
images=["scan1.nii.gz", "scan2.nii.gz", "scan3.nii.gz"],
times=[1.0, 24.0, 72.0],
config=config,
)Load a NIfTI file as a numpy array with metadata.
Parameters:
path: Path to NIfTI file
Returns: Tuple of (data_array, nibabel_image)
Example:
from pytia import load_nifti_as_array
data, img = load_nifti_as_array("scan.nii.gz")
print(f"Shape: {data.shape}")
print(f"Affine: {img.affine}")Save a numpy array as a NIfTI file using reference image metadata.
Parameters:
data: Data array to savereference: Reference NIfTI file or nibabel object for metadataoutput_path: Output file pathdtype: Optional data type for output (default: same as input)
Returns: nibabel image object
Example:
from pytia import save_array_as_nifti
save_array_as_nifti(
data=tia_data,
reference="reference.nii.gz",
output_path="tia.nii.gz",
)Get information about a NIfTI file.
Parameters:
path: Path to NIfTI file
Returns: Dictionary with file information (shape, affine, voxel sizes, etc.)
Example:
from pytia import get_nifti_info
info = get_nifti_info("scan.nii.gz")
print(f"Shape: {info['shape']}")
print(f"Voxel sizes: {info['voxel_sizes']}")Extract TIA statistics for a region of interest.
Parameters:
tia_img: TIA NIfTI file or nibabel objectmask_img: Mask NIfTI file or nibabel objectmask_value: Value in mask to use as ROI (default:1)
Returns: Dictionary with statistics (mean, std, min, max, sum, count)
Example:
from pytia import extract_roi_stats
stats = extract_roi_stats("tia.nii.gz", "roi.nii.gz")
print(f"Mean TIA: {stats['mean']:.2f}")Compare two TIA results and compute difference statistics.
Parameters:
result1: FirstResultsobjectresult2: SecondResultsobjectmetric: Metric to compare:"tia","r2","sigma_tia"(default:"tia")
Returns: Dictionary with comparison statistics
Example:
from pytia import compare_results
diff = compare_results(result1, result2, metric="tia")
print(f"Mean difference: {diff['mean_diff']:.2f}")The Results object contains:
tia_img: TIA map (nibabel image)r2_img: R² map (nibabel image)sigma_tia_img: TIA uncertainty map (nibabel image)model_id_img: Model ID map (nibabel image)status_id_img: Status ID map (nibabel image)tpeak_img: Peak time map (nibabel image, orNone)summary: Summary dictionary with metadataoutput_paths: Dictionary of output file pathsconfig: Configuration dictionarytimes_s: Timepoints in seconds (numpy array)
Example:
import numpy as np
result = run_tia_from_nifti(...)
# Access data arrays
tia_data = result.tia_img.get_fdata()
r2_data = result.r2_img.get_fdata()
sigma_tia_data = result.sigma_tia_img.get_fdata()
# Print statistics
print(f"TIA mean: {np.nanmean(tia_data):.2f}")
print(f"TIA std: {np.nanstd(tia_data):.2f}")
print(f"R² mean: {np.nanmean(r2_data):.4f}")
# Access summary
print(f"Times (s): {result.times_s}")
print(f"Status counts: {result.summary.get('status_counts', {})}")
print(f"Timing: {result.summary.get('timing_ms', {})}")
# Access output paths
print(f"Output paths: {result.output_paths}")PyTIA uses YAML configuration files to control all aspects of TIA calculation.
# config.yaml
inputs:
images:
- "scan1.nii.gz"
- "scan2.nii.gz"
- "scan3.nii.gz"
times: [1.0, 24.0, 72.0]
time:
unit: hours
sort_timepoints: true
io:
output_dir: ./pytia_output
prefix: patient_001
write_summary_yaml: true
write_status_map: true
physics:
half_life_seconds: 21636.0 # Tc-99m half-life in seconds
enforce_lambda_ge_phys: true
mask:
mode: provided # options: provided, otsu, none
provided_path: "body_mask.nii.gz"
min_fraction_of_max: 0.02
denoise:
enabled: true
method: masked_gaussian
sigma_vox: 1.2
noise_floor:
enabled: true
mode: relative # options: absolute, relative
absolute_bq_per_ml: 0.0
relative_fraction_of_voxel_max: 0.01
behavior: exclude
model_selection:
mode: auto
min_points_for_gamma: 3
integration:
start_time_seconds: 0.0
tail_mode: phys # options: phys, fitted, hybrid, none
rising_tail_mode: phys # options: phys, peak_at_last
min_tail_points: 2
fit_tail_slope: false
lambda_phys_constraint: true
include_t0: true
bootstrap:
enabled: true
n: 100
seed: 42
reclassify_each_replicate: true
performance:
chunk_size_vox: 500000
enable_profiling: false
regions:
enabled: false
label_map_path: null
mode: roi_aggregate
aggregation: mean
voxel_level_r2: false
classes: {}
scaling:
mode: tref
reference_time: peak
single_time:
enabled: false
method: phys # options: phys, haenscheid, prior_half_life
haenscheid_eff_half_life_seconds: null
half_life_seconds: null
label_map_path: null
label_half_lives: {}images: List of NIfTI image file pathstimes: List of timepoints corresponding to each image
unit: Time unit (hoursorseconds)sort_timepoints: Whether to sort timepoints (default:true)
output_dir: Output directory pathprefix: Prefix for output fileswrite_summary_yaml: Write summary YAML file (default:true)write_status_map: Write status map (default:true)
half_life_seconds: Radionuclide half-life in secondsenforce_lambda_ge_phys: Enforce lambda >= lambda_phys (default:true)
mode: Mask mode (provided,otsu,none)provided_path: Path to provided mask imagemin_fraction_of_max: Minimum fraction of max for auto mask (default:0.02)
enabled: Enable denoising (default:true)method: Denoising method (masked_gaussian)sigma_vox: Gaussian sigma in voxels (default:1.2)
enabled: Enable noise floor filtering (default:true)mode: Mode (absoluteorrelative)absolute_bq_per_ml: Absolute threshold in Bq/mlrelative_fraction_of_voxel_max: Relative threshold (default:0.01)behavior: Behavior (exclude)
enabled: Enable bootstrap (default:true)n: Number of bootstrap replicates (default:100)seed: Random seed (default:42)reclassify_each_replicate: Reclassify each replicate (default:true)
chunk_size_vox: Chunk size for voxel processing (default:500000)enable_profiling: Enable profiling (default:false)
enabled: Enable single-timepoint mode (default:false)method: Method (phys,haenscheid,prior_half_life)haenscheid_eff_half_life_seconds: Effective half-life for Hänscheid methodhalf_life_seconds: Prior half-life for prior_half_life methodlabel_map_path: Label map path for segmentation-based priorslabel_half_lives: Mapping of label IDs to half-lives
PyTIA generates the following output files:
{prefix}_tia.nii.gz- Time-Integrated Activity map (Bq·s/voxel){prefix}_r2.nii.gz- R² goodness-of-fit map{prefix}_sigma_tia.nii.gz- TIA uncertainty map (Bq·s/voxel){prefix}_model_id.nii.gz- Model ID map{prefix}_status_id.nii.gz- Status ID map{prefix}_pytia_summary.yaml- Summary YAML file
| Code | Model |
|---|---|
| 10 | Hybrid (rising) |
| 11 | Hybrid (hump) |
| 20 | Mono-exponential (falling) |
| 30 | Gamma-variate |
| 101 | Single-timepoint (physical decay) |
| 102 | Single-timepoint (Hänscheid) |
| 103 | Single-timepoint (prior half-life) |
| Code | Status |
|---|---|
| 0 | Outside mask/background |
| 1 | OK |
| 2 | Not applicable: <2 valid points |
| 3 | Fit failed |
| 4 | All points below noise floor |
| 5 | Nonphysical parameters |
The summary YAML file contains:
pytia_version: PyTIA versiontimes_seconds: Timepoints in secondsvoxel_volume_ml: Voxel volume in mlstatus_legend: Status code legendstatus_counts: Count of voxels per statustiming_ms: Timing information for each processing stepconfig: Configuration used for the analysis
CLI:
pytia nifti --images scan1.nii.gz scan2.nii.gz scan3.nii.gz \
--times 1.0 24.0 72.0 \
--time-unit hours \
--half-life 21636.0 \
--output-dir ./results \
--prefix patient_001Python API:
from pytia import run_tia_from_nifti
result = run_tia_from_nifti(
images=["scan1.nii.gz", "scan2.nii.gz", "scan3.nii.gz"],
times=[1.0, 24.0, 72.0],
time_unit="hours",
half_life_seconds=21636.0,
output_dir="./results",
prefix="patient_001",
)CLI:
pytia nifti --images scan1.nii.gz \
--times 24.0 \
--single-time \
--stp-method phys \
--half-life 21636.0 \
--output-dir ./results \
--prefix patient_001_stpPython API:
from pytia import run_single_timepoint_tia
result = run_single_timepoint_tia(
image="scan1.nii.gz",
time=24.0,
method="phys",
half_life_seconds=21636.0,
output_dir="./results",
prefix="patient_001_stp",
)CLI:
pytia nifti --images scan1.nii.gz scan2.nii.gz scan3.nii.gz \
--times 1.0 24.0 72.0 \
--time-unit hours \
--half-life 21636.0 \
--mask body_mask.nii.gz \
--bootstrap 100 \
--bootstrap-seed 42 \
--output-dir ./results \
--prefix patient_001Python API:
from pytia import run_tia_from_nifti
result = run_tia_from_nifti(
images=["scan1.nii.gz", "scan2.nii.gz", "scan3.nii.gz"],
times=[1.0, 24.0, 72.0],
time_unit="hours",
half_life_seconds=21636.0,
mask="body_mask.nii.gz",
bootstrap=True,
bootstrap_n=100,
bootstrap_seed=42,
output_dir="./results",
prefix="patient_001",
)Python API:
from pytia import run_tia_from_nifti
patients = [
{"id": "001", "images": ["p001_s1.nii.gz", "p001_s2.nii.gz", "p001_s3.nii.gz"], "times": [1.0, 24.0, 72.0]},
{"id": "002", "images": ["p002_s1.nii.gz", "p002_s2.nii.gz", "p002_s3.nii.gz"], "times": [1.0, 24.0, 72.0]},
{"id": "003", "images": ["p003_s1.nii.gz", "p003_s2.nii.gz", "p003_s3.nii.gz"], "times": [1.0, 24.0, 72.0]},
]
results = []
for patient in patients:
result = run_tia_from_nifti(
images=patient["images"],
times=patient["times"],
time_unit="hours",
half_life_seconds=21636.0,
output_dir=f"./results/patient_{patient['id']}",
prefix=f"patient_{patient['id']}",
)
results.append(result)
print(f"Processed patient {patient['id']}")Python API:
from pytia import run_tia_from_nifti, extract_roi_stats
result = run_tia_from_nifti(
images=["scan1.nii.gz", "scan2.nii.gz", "scan3.nii.gz"],
times=[1.0, 24.0, 72.0],
time_unit="hours",
half_life_seconds=21636.0,
)
stats = extract_roi_stats(
tia_img=result.tia_img,
mask_img="tumor_roi.nii.gz",
mask_value=1,
)
print(f"ROI Statistics:")
print(f" Mean TIA: {stats['mean']:.2f} Bq·s/voxel")
print(f" Std TIA: {stats['std']:.2f} Bq·s/voxel")
print(f" Min TIA: {stats['min']:.2f} Bq·s/voxel")
print(f" Max TIA: {stats['max']:.2f} Bq·s/voxel")
print(f" Sum TIA: {stats['sum']:.2f} Bq·s")
print(f" Count: {stats['count']} voxels")- CLI Examples - Detailed CLI usage examples
- Python API Examples - Detailed Python API examples
- Configuration Reference - Complete configuration reference
- Architecture Documentation - Developer-focused architecture guide
- User Guide - Detailed user guide
For issues, questions, or contributions, please visit the PyTIA GitHub repository.