Create Typer command line interfaces from functions that use SimpleITK images and transforms as arguments or return types.
Key features:
- 🔄 Automatic file I/O handling for SimpleITK images and transforms
- 🎯 Type-safe with full type annotation support
- 🚀 Built on Typer for modern CLI experiences
- 🐍 Pythonic CLI design using native
*,syntax for keyword-only parameters - 📁 Auto-create output directories with optional overwrite protection
- 📝 Optional verbose logging with Rich integration
- 🐍 Python 3.11+ with modern syntax
pip install sitk-cliOptional dependencies:
# For enhanced logging output with colors and formatting
pip install sitk-cli[rich]Requirements: Python 3.11 or higher
from __future__ import annotations
import SimpleITK as sitk
import typer
from sitk_cli import register_command
app = typer.Typer()
@register_command(app)
def fill_holes_slice_by_slice(mask: sitk.Image) -> sitk.Image:
"""Fill holes in a binary mask slice by slice."""
mask = mask != 0
output = sitk.Image(mask.GetSize(), mask.GetPixelID())
output.CopyInformation(mask)
for k in range(mask.GetSize()[2]):
output[:, :, k] = sitk.BinaryFillhole(mask[:, :, k], fullyConnected=False)
return output
if __name__ == "__main__":
app()How it works: sitk-cli inspects the type annotations and creates a wrapper that:
- Converts CLI file path arguments to SimpleITK images/transforms
- Calls your function with the loaded objects
- Saves returned images/transforms to the specified output file
Use Python's native *, syntax to control whether CLI arguments are positional or named:
@register_command(app)
def process(input: sitk.Image, *, mask: sitk.Image) -> sitk.Image:
"""Mix positional and keyword-only arguments.
CLI: process INPUT OUTPUT --mask MASK
"""
return input * maskBehavior:
- Required Image/Transform parameters → positional by default
- Parameters after
*,→ keyword-only (named options) - Optional parameters (with defaults) → named options
- Output → positional if any input is positional, otherwise named
from sitk_cli import logger, register_command
@register_command(app, verbose=True)
def process_with_logging(input: sitk.Image) -> sitk.Image:
logger.info("Starting processing...")
result = sitk.Median(input, [2] * input.GetDimension())
logger.debug(f"Result size: {result.GetSize()}")
return resultpython script.py process-with-logging input.nii output.nii -v # INFO level
python script.py process-with-logging input.nii output.nii -vv # DEBUG level@register_command(app, overwrite=False)
def protected_process(input: sitk.Image) -> sitk.Image:
"""Prevent accidental overwrites."""
return sitk.Median(input, [2] * input.GetDimension())python script.py protected-process input.nii output.nii
python script.py protected-process input.nii output.nii # Error: file exists
python script.py protected-process input.nii output.nii --force # OK, overwritesModes: overwrite=True (default), overwrite=False (requires --force), overwrite="prompt" (asks user)
Optional parameters with defaults automatically become named options:
@register_command(app)
def median_filter(input: sitk.Image, radius: int = 2) -> sitk.Image:
"""Apply median filtering to an image.
CLI: median-filter INPUT OUTPUT [--radius 3]
"""
return sitk.Median(input, [radius] * input.GetDimension())git clone https://github.com/dyollb/sitk-cli.git
cd sitk-cli
python -m venv .venv
source .venv/bin/activate # or `.venv\Scripts\activate` on Windows
pip install -e '.[dev,rich]'pytest tests/ -v --covruff check .
ruff format .
mypy src/sitk_clipre-commit install
pre-commit run --all-filesMIT License - see LICENSE file for details.
