A standardized Python framework for medical image processing.
Project description
Medical Image Standard Library
A standardized Python framework for medical image processing with GPU acceleration, built on PyTorch tensors. Provides abstract interfaces, extensible algorithm pipelines, and automatic device management for DICOM and other medical image formats.
A demo is available Here.
Purpose
Provide standardized abstractions for medical image processing workflows:
- Abstract base classes defining core interfaces (
Image,Algorithm) - Lazy loading pattern for memory-efficient image handling
- Static processing methods for filters, thresholding, morphology, and metrics
- Extensible algorithm framework using lambda composition
- Patch-based processing for large images
- GPU acceleration via PyTorch with automatic device inference, OOM fallback, mixed precision, and batch processing
Architecture Overview
Core Design Principles
- Abstraction-First: Define interfaces through abstract base classes
- Lazy Loading:
__init__()stores metadata,load()loads data - Static Methods: Stateless processing operations
- Composition: Build algorithms from lambda functions
- Device-Aware: Processing follows the image's device automatically
- Extensibility: Easy to add formats, algorithms, and operations
Package Structure
medical_image/
├── data/ # Image ABC, DicomImage, PNGImage, InMemoryImage, Patch, ROI
├── datasets/ # BaseDataset ABC, INbreastDataset, CustomINbreastDataset, CBISDDSMDataset
├── process/ # Filters, Threshold, Morphology, Metrics, Frequency
├── algorithms/ # Algorithm ABC, FEBDS, FCM, PFCM, KMeans, TopHat, BreastMask, DicomWindow
└── utils/ # device (GPU), logging, annotations, pairing, mask_utils, downloader
Design Patterns
- Strategy & Template (Algorithms): Algorithms inherit from
AlgorithmABC, define steps as lambdas in__init__, and execute via theapply()template method. Strategies like FEBDS, FCM, KMeans are swappable. - Lazy Loading (Data): Image classes store file paths on creation;
.load()defers I/O until needed. - Static Factories (Processing):
Filters,Threshold,MorphologyOperationsoperate statelessly on Image inputs. - Device Flow (GPU): All processing methods infer the device from input images by default. Explicit
device=overrides are still supported.
Installation
Requirements
- Python 3.11+
- Linux OS
- CUDA GPU (optional, for GPU acceleration)
Option A — Using venv
git clone https://github.com/LATIS-DocumentAI-Group/medical-image-std.git
cd medical-image-std
python -m venv .venv
source .venv/bin/activate
pip install -e .
Option B — Using uv (Astral)
git clone https://github.com/LATIS-DocumentAI-Group/medical-image-std.git
cd medical-image-std
uv venv
source .venv/bin/activate
uv pip install -e .
Optional Dependencies
# Development tools (pytest, black, ruff, mypy)
pip install -e ".[dev]"
# GPU support (requires CUDA-compatible GPU and drivers)
pip install -e ".[gpu]"
# Everything
pip install -e ".[all]"
GPU Requirements
GPU acceleration requires:
- NVIDIA GPU with CUDA support
- CUDA toolkit installed (12.x recommended)
- PyTorch with CUDA backend (
torch.cuda.is_available()should returnTrue)
Without a GPU, all operations run on CPU automatically.
Quick Start
1. Load Image (Lazy Loading)
from medical_image import DicomImage
# Create object (no data loaded yet)
image = DicomImage("mammogram.dcm")
image.load() # Lazy loading
image.display_info()
2. Apply Processing
from medical_image import Filters, Threshold
output = image.clone()
# device is inferred from image automatically (no device= needed)
Filters.gaussian_filter(image, output, sigma=2.0)
Threshold.otsu_threshold(output, output)
3. Use Algorithms
from medical_image import FebdsAlgorithm
algo = FebdsAlgorithm(method="dog")
output = image.clone()
algo(image, output) # __call__ returns output
4. Visualize Results
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 2, figsize=(10, 5))
axes[0].imshow(image.pixel_data.cpu().numpy(), cmap="gray")
axes[0].set_title("Original")
axes[1].imshow(output.pixel_data.cpu().numpy(), cmap="gray")
axes[1].set_title("FEBDS Output")
for ax in axes:
ax.axis("off")
plt.tight_layout()
plt.show()
5. Display Image Info
display_info() logs metadata via Python's logging module. Enable logging to see the output:
import logging
logging.basicConfig(level=logging.INFO)
image.display_info()
# Output:
# === Image Info ===
# File Path: mammogram.dcm
# Pixel Data: Loaded
# Pixel Data Type: torch.float32
# Shape (H x W): torch.Size([4096, 3328])
# Device: cpu
# Width: 3328
# Height: 4096
# Annotations: None
# =================
6. Patch-based Processing
from medical_image import PatchGrid
patch_grid = PatchGrid(image, patch_size=(256, 256))
for patch in patch_grid.patches:
p = patch.load()
# process p.pixel_data
GPU Acceleration
Automatic Device Inference
All processing methods use device=None by default. The device is resolved automatically from the input image:
# Move image to GPU once — all operations follow
image.to("cuda")
output = image.clone()
# No device= parameter needed — inferred from image
Filters.gaussian_filter(image, output, sigma=2.0)
Filters.median_filter(output, output, size=5)
Threshold.otsu_threshold(output, output)
# Explicit override still works
Filters.gaussian_filter(image, output, sigma=2.0, device="cpu")
The resolve_device() helper implements the priority: explicit parameter > image device > CPU fallback.
from medical_image import resolve_device
device = resolve_device(image, explicit=None) # returns image.pixel_data.device
DeviceContext Manager
Manages GPU memory with automatic cache clearing and OOM fallback:
from medical_image import DeviceContext
with DeviceContext("cuda", verbose=True) as ctx:
image.to(ctx.device)
output = image.clone()
algo = FebdsAlgorithm(method="dog", device=str(ctx.device))
algo(image, output)
print(ctx.memory_stats())
# GPU cache automatically cleared on exit
If CUDA is not available, DeviceContext falls back to CPU automatically.
OOM Fallback with @gpu_safe
The @gpu_safe decorator catches CUDA Out-of-Memory errors and retries on CPU:
from medical_image import gpu_safe
@gpu_safe
def my_processing(image, output, device=None):
Filters.gaussian_filter(image, output, sigma=2.0, device=device)
return output
# If GPU runs out of memory, automatically retries on CPU
result = my_processing(image, output, device="cuda")
Mixed Precision
Control floating-point precision globally. Medical imaging at 12-bit depth (0-4095) fits within float16 range (max 65504), enabling 2x throughput with half the memory:
from medical_image import Precision, set_default_precision, get_dtype
# Default is float32
set_default_precision(Precision.HALF) # float16 — 2x faster
set_default_precision(Precision.BFLOAT16) # bfloat16 — 2x faster, better range
set_default_precision(Precision.FULL) # float32 — default
# Algorithms use autocast when precision != FULL on GPU
algo = FebdsAlgorithm(method="dog", device="cuda")
algo.precision = Precision.HALF
algo(image, output) # runs under torch.cuda.amp.autocast
Batch Processing
Process multiple images in a single GPU kernel launch:
import torch
from medical_image import Filters, TopHatAlgorithm, InMemoryImage
# --- Filter-level batching (single GPU kernel) ---
batch = torch.randn(8, 1, 256, 256, device="cuda")
filtered = Filters.gaussian_filter_batch(batch, sigma=2.0)
# --- Algorithm-level batching ---
# Build Image objects from tensors
images = []
outputs = []
for i in range(4):
img = InMemoryImage.from_array(torch.randn(256, 256))
img.to("cuda")
images.append(img)
outputs.append(img.clone())
algo = TopHatAlgorithm(radius=4, device="cuda")
results = algo.apply_batch(images, outputs) # loops over apply(); subclasses can override
GPU Memory Clearing
To free GPU memory when you are done processing:
import torch
# Option 1: Manual cache clearing
del output # remove references to GPU tensors
torch.cuda.empty_cache() # release cached memory back to CUDA
# Option 2: DeviceContext (automatic)
from medical_image import DeviceContext
with DeviceContext("cuda") as ctx:
# ... all GPU work here ...
pass
# GPU cache is automatically cleared when the context exits
# Option 3: Full GPU memory reset (clears everything)
torch.cuda.reset_peak_memory_stats()
torch.cuda.empty_cache()
# Check current GPU memory usage
print(f"Allocated: {torch.cuda.memory_allocated() / 1e6:.1f} MB")
print(f"Cached: {torch.cuda.memory_reserved() / 1e6:.1f} MB")
Pinned Memory
For faster CPU-to-GPU transfers:
image.pin_memory() # page-locked memory
image.to("cuda") # faster transfer
Async GPU Pipeline
Overlap disk I/O and GPU compute using CUDA streams:
from medical_image import AsyncGPUPipeline
pipeline = AsyncGPUPipeline(device="cuda")
results = pipeline.process_images(loaded_images, algorithm)
Multi-GPU Support
Distribute processing across multiple GPUs:
from medical_image import MultiGPUAlgorithm, FebdsAlgorithm
multi = MultiGPUAlgorithm(FebdsAlgorithm, gpu_ids=[0, 1], method="dog")
outputs = multi.apply_batch(images, [img.clone() for img in images])
Datasets
Production-grade PyTorch Dataset classes with lazy loading, automatic file pairing, and standardized outputs.
Available Datasets
| Dataset | Class | Description |
|---|---|---|
| INbreast | INbreastDataset |
Mammography with DICOM + XML annotations, generates binary masks |
| Custom INbreast | CustomINbreastDataset |
INbreast with TIF mask support (TIF > XML > empty priority) |
| CBIS-DDSM | CBISDDSMDataset |
Large-scale mammography with full-image and patch-based modes |
Quick Example
from medical_image.datasets import CBISDDSMDataset
from torch.utils.data import DataLoader
dataset = CBISDDSMDataset(
root_dir="/data/ddsm",
mode="full_image",
target_size=(1024, 1024),
percentage=0.1, # 10% subset for prototyping
)
loader = DataLoader(
dataset,
batch_size=4,
collate_fn=CBISDDSMDataset.collate_fn,
num_workers=4,
pin_memory=True,
)
for batch in loader:
images = batch["image"] # [B, 1, 1024, 1024]
masks = batch["mask"] # [B, 1, 1024, 1024]
metadata = batch["metadata"]
All datasets return dictionaries with "image" (Tensor), "mask" (Tensor), and "metadata" (dict). See the Dataset Guide for full documentation.
Algorithms
Available Algorithms
| Algorithm | Class | Description |
|---|---|---|
| Top-Hat | TopHatAlgorithm |
White top-hat transform highlighting bright structures smaller than the SE |
| K-Means | KMeansAlgorithm |
Hard clustering with k-means++, isolates brightest cluster |
| FCM | FCMAlgorithm |
Fuzzy C-Means with soft membership, isolates brightest cluster |
| PFCM | PFCMAlgorithm |
Possibilistic FCM detecting microcalcifications via atypicality |
| FEBDS | FebdsAlgorithm |
Fourier Enhancement + Band-pass filtering with DoG/LoG/FFT modes |
| Breast Mask | BreastMaskAlgorithm |
Automatic breast region segmentation |
| DICOM Window | DicomWindowAlgorithm |
DICOM windowing (window center/width) |
| Grail Window | GrailWindowAlgorithm |
Grail-style DICOM windowing |
| Bit Depth Norm | BitDepthNormAlgorithm |
Bit-depth normalization |
Custom Algorithm
from medical_image import Algorithm, Filters, Threshold
class MyAlgorithm(Algorithm):
def __init__(self, device=None):
super().__init__(device=device)
self.blur = lambda img, out: Filters.gaussian_filter(img, out, sigma=2.0, device=self.device)
self.thresh = lambda img, out: Threshold.otsu_threshold(img, out, device=self.device)
def apply(self, image, output):
self.blur(image, output)
self.thresh(output, output)
return output
Processing Modules
Filters
gaussian_filter— Gaussian blurmedian_filter— Median denoisingconvolution— Generic 2D convolutiondifference_of_gaussian— DoG band-pass filterlaplacian_of_gaussian— LoG edge detectionbutterworth_kernel— Frequency-domain band-passgamma_correction— Gamma correctioncontrast_adjust— Contrast and brightness adjustmentgaussian_filter_batch— Batched Gaussian filter (B, C, H, W)
Threshold
otsu_threshold— Global Otsu binarizationsauvola_threshold— Adaptive local thresholdingbinarize— Local/global variance-based binarization
Morphology
morphology_closing— Binary closing (dilation + erosion)region_fill— Binary hole fillingerosion— Grayscale erosion with disk SEdilation— Grayscale dilation with disk SEwhite_top_hat— White top-hat transform
Metrics
entropy— Shannon entropyjoint_entropy— Joint Shannon entropymutual_information— Mutual information between two imageslocal_variance— Per-region local variancevariance— Global variance
Frequency
fft— 2D Fast Fourier Transforminverse_fft— 2D Inverse FFT
Key Concepts
Lazy Loading Pattern
- Object Creation:
image = DicomImage("path.dcm")— only stores path - Data Loading:
image.load()— loads pixel data to memory - Memory Efficient: load only when needed, clone lightweight copies
Image Lifecycle
image = DicomImage("scan.dcm") # metadata only
image.load() # pixel_data loaded as torch.Tensor
image.to("cuda") # move to GPU
output = image.clone() # lightweight clone (no heavy DICOM objects)
output.pin_memory() # page-lock for fast transfers
Device Flow
All ~30 processing methods follow the same pattern:
def some_filter(image, output, ..., device=None):
device = resolve_device(image, explicit=device) # infer from image
img = image.pixel_data.to(device).float()
# ... processing ...
output.pixel_data = result
Logging
The library uses Python's standard logging with NullHandler (no output by default):
from medical_image.utils.logging import configure_logging
# Enable console + file logging
configure_logging(level=logging.DEBUG, log_file="processing.log")
Visual Examples
The following demonstrates all algorithms on a mammogram ROI (20527054.dcm, center cx=1250, cy=2000, half-size 127).
Base Region Of Interest (ROI)
Algorithm Outputs
Top-Hat Transform
Enhances brighter elements matching the disk structural element radius.
K-Means Clustering Sequence
Identifies calcifications by hard partitioning pixel frequency and marking the brightest cluster.
FCM Clustering Sequence
Similar to K-Means, but assigns fuzzy membership probabilities to elements to better separate border intensities.
PFCM Typicality Mapping
Averages across noise using cluster typicality measurements, masking out all "atypical" calcified structures apart from dark backgrounds.
FEBDS Output
Uses a hybrid approach of localized difference-of-gaussian (or frequency band-pass) filters and adaptive binarizations.
Testing
# Run all tests
pytest medical_image/tests/
# Run specific test suites
pytest medical_image/tests/test_dicom.py # DICOM + processing tests
pytest medical_image/tests/test_mc_algorithms.py # Algorithm unit tests
pytest medical_image/tests/test_gpu.py # GPU + device inference tests
# Tests auto-detect CUDA — GPU tests run on both CPU and CUDA when available
# Tests that require CUDA are skipped on CPU-only machines
Test Coverage
| Suite | Tests | What it covers |
|---|---|---|
test_dicom.py |
18 | DICOM loading, filters vs scikit-image, morphology vs scipy, FEBDS pipeline, patches |
test_mc_algorithms.py |
71 | KMeans, FCM, PFCM, TopHat, full pipeline integration, ROI extraction |
test_gpu.py |
47 | Device inference, DeviceContext, Precision, pin_memory, all modules on CPU+CUDA, batch ops |
| Total | 136+ |
Documentation
| Document | Description |
|---|---|
| INDEX | Documentation navigation and overview |
| Architecture | Design patterns, diagrams, workflows |
| API Reference | Complete API documentation |
| User Guide | Tutorials and examples |
| Algorithms | Algorithm theory and implementation |
| Datasets | CBIS-DDSM and custom datasets |
| Contributing | Development guide and CI requirements |
| Quick Reference | Code snippets cheat sheet |
Development
Code Formatting
black medical_image/
black --check . # CI check
Adding Features
- New Image Format: Extend
ImageABC, implementload()andsave() - New Processing Method: Add static method to the appropriate class, use
device=None+resolve_device() - New Algorithm: Extend
AlgorithmABC, implementapply(), use lambda composition in__init__
Contributing
- Fork the repository
- Create feature branch
- Follow code standards (Black formatting)
- Write tests following existing structure
- Ensure CI passes locally:
pytest medical_image/tests/ && black --check . - Submit pull request
License
MIT License - See LICENSE file
Links
- Repository: https://github.com/LATIS-DocumentAI-Group/medical-image-std
- Documentation: docs/INDEX.md
Version
Current: 0.4.1
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file medical_image_std-0.6.1.tar.gz.
File metadata
- Download URL: medical_image_std-0.6.1.tar.gz
- Upload date:
- Size: 91.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
783d43c1afbda52783b8e655b9d3d161d5cd01ece20a8845c3ef661f6ed48a56
|
|
| MD5 |
7573775fcbebd6c322af47b5e3601398
|
|
| BLAKE2b-256 |
399e8cac0f664d1c1e14aab1d896b6e5583fb0d3d71de6efb293b8d084aa8d3e
|
File details
Details for the file medical_image_std-0.6.1-py3-none-any.whl.
File metadata
- Download URL: medical_image_std-0.6.1-py3-none-any.whl
- Upload date:
- Size: 109.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b9f639eac8368e7e0d2c00536887acdfb6c1ae04a1b2177ef1f8d0a5573ae7fd
|
|
| MD5 |
6f720a2b3df76b8b627235fe7c8b160a
|
|
| BLAKE2b-256 |
4a22e2f896f7ba6d851ca30b1173b517cf7192448811decfc75a9d186d4d83d9
|