Pure Swift DICOM decoder for iOS and macOS. Read DICOM files, extract medical metadata, and process pixel data without UIKit or Objective-C dependencies.
Suitable for lightweight DICOM viewers, PACS clients, telemedicine apps, and research tools.
- Repository:
ThalesMMS/DICOM-Decoder - Latest release:
1.0.1 - Documentation: API Reference | Getting Started | Glossary | Troubleshooting
- Overview
- Features
- Performance
- Quick Start
- Installation
- Usage Examples
- Command-Line Tool
- SwiftUI Components
- Architecture
- Documentation
- Integration
- Contributing
- License
- Support
This project is a full DICOM decoder written in Swift, modernized from a legacy medical viewer. It provides:
- Complete DICOM file parsing (metadata and pixels)
- Pixel extraction for 8-bit, 16-bit grayscale and 24-bit RGB images
- Window/level with medical presets and automatic suggestions
- Modern async/await APIs for non-blocking operations
- File validation before processing
- Zero external dependencies
DICOM (Digital Imaging and Communications in Medicine) is the standard for medical imaging used by CT, MRI, X-ray, ultrasound, and hospital PACS systems.
- Little/Big Endian, Explicit/Implicit VR
- Grayscale 8/16-bit and RGB 24-bit
- Native JPEG Lossless decoding (Process 14, all selection values 0-7) for transfer syntaxes 1.2.840.10008.1.2.4.57 and 1.2.840.10008.1.2.4.70
- Best-effort single-frame JPEG and JPEG2000 decoding via ImageIO
- Automatic memory mapping for large files (>10MB)
- Downsampling for fast thumbnail generation
- Parses Image Orientation (Patient) (0020,0037) and Image Position (Patient) (0020,0032); exposes normalized row/column vectors and origin.
- Reads Pixel Spacing (0028,0030) and slice spacing/thickness; exposes spacingX/Y/Z.
- Exposes width/height, bitsAllocated, pixelRepresentation (signed/unsigned), rescale slope/intercept.
- Returns Series Description and raw tag access via
info(for:).
- Directory-level loader that scans
.dcmfiles, orders slices by IPP projection on the IOP normal (fallback: Instance Number), and computes Z spacing from IPP deltas. - Validates single-channel 16-bit geometry consistency and assembles a contiguous volume buffer (signed/unsigned preserved).
- Progress callback per slice and lightweight
DicomSeriesVolumewith voxels, spacing, orientation matrix, origin, rescale parameters, and description.
- Window/Level with medical presets (CT, mammography, PET, and more)
- Automatic preset suggestions based on modality and body part
- Quality metrics (SNR, contrast, dynamic range)
- Basic helpers for contrast stretching and noise reduction (CLAHE placeholder, simple blur)
- Hounsfield Unit conversions for CT images
- Swift-idiomatic throwing initializers for type-safe error handling
- Type-safe DicomTag enum for metadata access (preferred over raw hex values)
- Type-safe value types (WindowSettings, PixelSpacing, RescaleParameters) with Codable support
- V2 APIs returning structs instead of tuples for better type safety
- Async/await support (iOS 13+, macOS 10.15+) with async throwing initializers
- Static factory methods for alternative initialization patterns
- Validation before loading
- Convenience metadata helpers (patient, study, series)
- Tag caching for frequent lookups
- Complete documentation with practical examples
- DICOM glossary
- Troubleshooting guide for common issues
- Tests covering parsing and series loading
The library uses vDSP (Accelerate framework) as the baseline CPU implementation for window/level operations. vDSP leverages hand-tuned ARM NEON assembly for SIMD operations, providing optimal CPU performance on Apple Silicon and Intel processors.
For applications requiring higher throughput, Metal GPU acceleration delivers significant performance gains over the vDSP baseline:
| Image Size | vDSP (CPU) | Metal (GPU) | Speedup |
|---|---|---|---|
| 512×512 | 2.14 ms | 1.16 ms | 1.84× |
| 1024×1024 | 8.67 ms | 2.20 ms | 3.94× |
Benchmark Environment:
- Hardware: Apple M4 (2024)
- OS: macOS 15+
- Iterations: 100 (after 20 warmup iterations)
- Algorithm: Window/level transformation on 16-bit grayscale DICOM pixels
Key Findings:
- vDSP baseline is optimal - Uses ARM NEON assembly; further CPU SIMD optimizations yield negligible gains
- Metal GPU shines on larger images - 3.94× speedup on 1024×1024 images (typical CT/MRI size)
- Small images favor CPU - 512×512 images show 1.84× speedup due to GPU setup overhead
- Production recommendation - Use
.automode for automatic backend selection, or choose.metal/.vdspexplicitly
Usage:
Metal GPU acceleration is integrated into DCMWindowingProcessor.applyWindowLevel() via the processingMode parameter:
// Default behavior (backward compatible) - uses vDSP
let pixels8bit = DCMWindowingProcessor.applyWindowLevel(
pixels16: pixels16,
center: 50.0,
width: 400.0
)
// Explicit Metal GPU acceleration
let pixels8bit = DCMWindowingProcessor.applyWindowLevel(
pixels16: pixels16,
center: 50.0,
width: 400.0,
processingMode: .metal // Force GPU (falls back to vDSP if unavailable)
)
// Automatic selection (recommended)
let pixels8bit = DCMWindowingProcessor.applyWindowLevel(
pixels16: pixels16,
center: 50.0,
width: 400.0,
processingMode: .auto // Auto-selects Metal for ≥800×800 images
)See CLAUDE.md for detailed usage examples and performance characteristics.
📊 Comprehensive Benchmarks: For complete benchmark methodology, regression detection, and historical performance tracking, see BENCHMARKS.md. Our automated benchmarking suite validates these performance claims on every commit and detects performance regressions >10%.
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/ThalesMMS/DICOM-Decoder.git", from: "1.0.0")
]import DicomCore
do {
// Load DICOM file with throwing initializer (recommended)
let decoder = try DCMDecoder(contentsOfFile: "/path/to/image.dcm")
print("Dimensions: \(decoder.width) x \(decoder.height)")
// Recommended: Use type-safe DicomTag enum
print("Modality: \(decoder.info(for: .modality))")
print("Patient: \(decoder.info(for: .patientName))")
// Legacy (deprecated): Raw hex values (still supported for custom/private tags)
// print("Modality: \(decoder.info(for: 0x00080060))")
if let pixels = decoder.getPixels16() {
print("\(pixels.count) pixels loaded")
}
} catch DICOMError.fileNotFound(let path) {
print("File not found: \(path)")
} catch DICOMError.invalidDICOMFormat(let path, let reason) {
print("Invalid DICOM file: \(reason)")
} catch {
print("Error: \(error)")
}Alternative patterns:
// Static factory method
let decoder = try DCMDecoder.load(fromFile: "/path/to/image.dcm")
// Async for non-blocking load
let decoder = try await DCMDecoder(contentsOfFile: "/path/to/image.dcm")
// URL-based initialization
let url = URL(fileURLWithPath: "/path/to/image.dcm")
let decoder = try DCMDecoder(contentsOf: url)For a detailed walkthrough, see GETTING_STARTED.md and USAGE_EXAMPLES.md.
The library provides a type-safe DicomTag enum for accessing DICOM metadata, eliminating the need for raw hex values:
// Recommended: Type-safe and discoverable via autocomplete
let patientName = decoder.info(for: .patientName)
let modality = decoder.info(for: .modality)
let studyUID = decoder.info(for: .studyInstanceUID)
let rows = decoder.intValue(for: .rows) ?? 0
let windowCenter = decoder.doubleValue(for: .windowCenter)
// Legacy (deprecated): Raw hex values (still supported for custom/private tags)
let customTag = decoder.info(for: 0x00091001) // Private tagBenefits:
- Type safety - Compiler-checked tag names
- Discoverability - Autocomplete shows all available tags
- Readability - Semantic names instead of hex codes
- Backward compatible - Raw hex values still work for custom/private tags
See Common DICOM Tags for a full list of supported tags.
The library provides dedicated structs for common DICOM parameters, offering better type safety and Codable conformance than tuple-based APIs:
// Window settings as a struct (recommended)
let settings = decoder.windowSettingsV2 // WindowSettings struct
if settings.isValid {
print("Window: center=\(settings.center), width=\(settings.width)")
}
// Pixel spacing as a struct (recommended)
let spacing = decoder.pixelSpacingV2 // PixelSpacing struct
if spacing.isValid {
print("Spacing: \(spacing.x) × \(spacing.y) × \(spacing.z) mm")
}
// Rescale parameters as a struct (recommended)
let rescale = decoder.rescaleParametersV2 // RescaleParameters struct
if !rescale.isIdentity {
let hounsfieldValue = rescale.apply(to: pixelValue)
}
// V2 windowing methods return WindowSettings
let optimal = DCMWindowingProcessor.calculateOptimalWindowLevelV2(pixels16: pixels)
let preset = DCMWindowingProcessor.getPresetValuesV2(preset: .lung)
// Legacy (deprecated): Tuple-based APIs (deprecated but still supported)
let (center, width) = decoder.windowSettings // Returns tupleBenefits of V2 APIs:
- Type safety - Structs prevent parameter order mistakes
- Codable support - Serialize to JSON for persistence
- Sendable conformance - Safe across concurrency boundaries
- Computed properties -
.isValid,.isIdentitychecks - Methods -
.apply(to:)for transformations - Better autocomplete - Named properties instead of tuple labels
See USAGE_EXAMPLES.md for detailed migration examples.
- File -> Add Packages...
- Paste
https://github.com/ThalesMMS/DICOM-Decoder.git - Select version
1.0.0or later - Add Package
// Package.swift
dependencies: [
.package(url: "https://github.com/ThalesMMS/DICOM-Decoder.git", from: "1.0.0")
],
targets: [
.target(
name: "MyApp",
dependencies: [
.product(name: "DicomCore", package: "DICOM-Decoder")
]
)
]- Swift 5.9+
- iOS 13.0+ or macOS 12.0+
- Xcode 14.0+
import DicomCore
do {
// Recommended: Use throwing initializer
let decoder = try DCMDecoder(contentsOfFile: "/path/to/ct_scan.dcm")
// Use type-safe DicomTag enum for metadata access
print("Patient: \(decoder.info(for: .patientName))")
print("Modality: \(decoder.info(for: .modality))")
print("Dimensions: \(decoder.width) x \(decoder.height)")
if let pixels = decoder.getPixels16() {
// Process image...
}
} catch {
print("Load error: \(error)")
}func loadDICOM() async {
do {
let decoder = try await DCMDecoder(contentsOfFile: "/path/to/image.dcm")
if let pixels = await decoder.getPixels16Async() {
await showImage(pixels, decoder.width, decoder.height)
}
} catch {
print("Error: \(error)")
}
}guard let pixels = decoder.getPixels16() else { return }
// Use type-safe DicomTag enum
let modality = decoder.info(for: .modality)
let suggestions = DCMWindowingProcessor.suggestPresets(for: modality)
let lungPreset = DCMWindowingProcessor.getPresetValues(preset: .lung)
let lungImage = DCMWindowingProcessor.applyWindowLevel(
pixels16: pixels,
center: lungPreset.center,
width: lungPreset.width
)
if let optimal = decoder.calculateOptimalWindow() {
let optimizedImage = DCMWindowingProcessor.applyWindowLevel(
pixels16: pixels,
center: optimal.center,
width: optimal.width
)
}let tempDecoder = DCMDecoder()
let validation = tempDecoder.validateDICOMFile("/path/to/image.dcm")
if !validation.isValid {
print("Invalid file:")
for issue in validation.issues {
print(" - \(issue)")
}
return
}
let decoder = try DCMDecoder(contentsOfFile: "/path/to/image.dcm")let patient = decoder.getPatientInfo()
let study = decoder.getStudyInfo()
let series = decoder.getSeriesInfo()if let thumb = decoder.getDownsampledPixels16(maxDimension: 150) {
let thumbWindowed = DCMWindowingProcessor.applyWindowLevel(
pixels16: thumb.pixels,
center: 40.0,
width: 80.0
)
}if let metrics = decoder.getQualityMetrics() {
print("Image quality:")
print(" Mean: \(metrics["mean"] ?? 0)")
print(" Standard deviation: \(metrics["std_deviation"] ?? 0)")
print(" SNR: \(metrics["snr"] ?? 0)")
print(" Contrast: \(metrics["contrast"] ?? 0)")
print(" Dynamic range: \(metrics["dynamic_range"] ?? 0) dB")
}let pixelValue: Double = 1024.0
let hu = decoder.applyRescale(to: pixelValue)
if hu < -500 {
print("Likely air or lung")
} else if hu > 700 {
print("Likely bone")
}More examples: USAGE_EXAMPLES.md.
The dicomtool command-line utility provides fast DICOM file inspection, validation, and image export capabilities for developer workflows, scripting, and CI integration.
Homebrew (recommended):
# Install from Homebrew tap (once published)
brew install thalesmms/dicom/dicomtool
# Or install from local formula
brew install ./dicomtool.rbBuild from source:
git clone https://github.com/ThalesMMS/DICOM-Decoder.git
cd DICOM-Decoder
swift build -c release
cp .build/release/dicomtool /usr/local/bin/# Inspect DICOM metadata
dicomtool inspect image.dcm
# Validate DICOM file conformance
dicomtool validate image.dcm
# Extract image with lung preset
dicomtool extract ct.dcm --output lung.png --preset lung
# Batch process directory
dicomtool batch --pattern "*.dcm" --operation validate --format jsonDisplay DICOM metadata in human-readable or JSON format.
# Show common metadata tags
dicomtool inspect image.dcm
# Show all available tags
dicomtool inspect image.dcm --all
# Show specific tags
dicomtool inspect image.dcm --tags PatientName,Modality,StudyDate
# JSON output for scripting
dicomtool inspect image.dcm --format jsonExample output (text):
DICOM File: image.dcm
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Patient Information:
Patient Name: DOE^JOHN
Patient ID: 12345
Patient Birth: 1970-01-01
Study Information:
Study Date: 2024-01-15
Study Description: CT Chest with Contrast
Modality: CT
Image Properties:
Dimensions: 512 × 512
Bits Allocated: 16
Pixel Spacing: 0.742 × 0.742 mm
Validate DICOM file structure and report issues.
# Validate single file
dicomtool validate image.dcm
# JSON output with detailed errors
dicomtool validate image.dcm --format jsonExample output:
✓ Valid DICOM file: image.dcm
Validation Results:
• File size: 524,288 bytes
• DICOM prefix: Present
• Transfer syntax: 1.2.840.10008.1.2.1 (Explicit VR Little Endian)
• Required tags: Complete
Extract pixel data with medical windowing presets or custom parameters.
Medical presets available:
lung- Lung tissue (-600/1500 HU)bone- Bone structures (400/1800 HU)brain- Brain tissue (40/80 HU)softtissue- Soft tissue (50/350 HU)liver- Liver imaging (80/150 HU)mediastinum- Mediastinum (50/350 HU)abdomen- Abdominal organs (60/400 HU)spine- Spine imaging (40/400 HU)pelvis- Pelvic structures (40/400 HU)angiography- Vascular imaging (300/600 HU)pulmonaryembolism- PE protocol (100/700 HU)mammography- Breast imaging (50/500 HU)petscan- PET imaging (0/5000)
# Extract with medical preset
dicomtool extract ct.dcm --output lung.png --preset lung
# Custom window/level
dicomtool extract ct.dcm --output custom.png \
--window-center 50 --window-width 400
# Automatic optimal windowing
dicomtool extract ct.dcm --output auto.png
# TIFF format with GPU acceleration
dicomtool extract ct.dcm --output high-quality.tiff \
--format tiff --processing-mode metal
# Overwrite existing file
dicomtool extract ct.dcm --output result.png --overwriteProcessing modes:
vdsp- CPU acceleration (best for <800×800 images)metal- GPU acceleration (best for ≥800×800 images, 3.94× speedup)auto- Automatic selection based on image size (default)
Process multiple DICOM files using glob patterns with concurrent execution.
# Inspect all DICOM files in directory
dicomtool batch --pattern "*.dcm" --operation inspect
# Validate all files recursively with JSON output
dicomtool batch --pattern "**/*.dcm" --operation validate --format json
# Extract all files with lung preset to exports directory
dicomtool batch --pattern "studies/*/*.dcm" --operation extract \
--output-dir ./exports --preset lung --image-format png
# Sequential processing (no concurrency)
dicomtool batch --pattern "*.dcm" --operation inspect --max-concurrent 1
# Custom windowing for batch extraction
dicomtool batch --pattern "*.dcm" --operation extract \
--output-dir ./out --window-center 40 --window-width 80Glob pattern examples:
*.dcm- All .dcm files in current directory**/*.dcm- All .dcm files recursivelystudy_*/series_*/*.dcm- Complex patterns with wildcards{CT,MR}/*.dcm- Multiple alternatives (brace expansion)
Example output:
Processing 48 files with pattern: *.dcm
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Progress: [████████████████████] 48/48 (100%)
Summary:
Total files: 48
Successful: 46
Failed: 2
Duration: 3.2s
Failed files:
• corrupt.dcm: Invalid DICOM format
• partial.dcm: Unexpected EOF
Developer workflows:
# Quick file inspection during debugging
dicomtool inspect mysterious_file.dcm
# Verify DICOM conformance before processing
if dicomtool validate input.dcm --format json | jq -e '.valid'; then
echo "Processing valid DICOM file"
fi
# Generate preview images for web display
dicomtool extract series/*.dcm --output-dir ./previews --preset softtissueCI/CD integration:
#!/bin/bash
# Validate DICOM test fixtures in CI pipeline
echo "Validating DICOM test files..."
if dicomtool batch --pattern "tests/fixtures/**/*.dcm" \
--operation validate \
--format json > validation.json; then
echo "✓ All DICOM files valid"
exit 0
else
echo "✗ DICOM validation failed"
cat validation.json | jq '.errors'
exit 1
fiBatch conversion scripts:
#!/bin/bash
# Convert hospital study archive to PNG previews
for study_dir in /mnt/pacs/studies/*; do
study_id=$(basename "$study_dir")
echo "Processing study: $study_id"
dicomtool batch \
--pattern "$study_dir/**/*.dcm" \
--operation extract \
--output-dir "./previews/$study_id" \
--preset softtissue \
--max-concurrent 8
doneResearch data validation:
# Validate and report on research dataset
dicomtool batch --pattern "dataset/**/*.dcm" \
--operation validate \
--format json | \
jq -r '.results[] | select(.valid == false) | .file' > invalid_files.txt
echo "Found $(wc -l < invalid_files.txt) invalid files"All commands support --format json for programmatic parsing:
{
"file": "image.dcm",
"valid": true,
"metadata": {
"PatientName": "DOE^JOHN",
"Modality": "CT",
"StudyDate": "20240115",
"Rows": "512",
"Columns": "512"
}
}- Concurrent processing: Default 4 concurrent operations (configurable with
--max-concurrent) - Memory efficient: Processes files individually, no bulk loading
- GPU acceleration: Optional Metal backend for extract operations (3.94× speedup on 1024×1024 images)
- macOS 12.0+ (10.15+ for basic functionality)
- Swift 5.9+
- Xcode 14.0+ (for building from source)
The library includes DicomSwiftUI, a complete set of pre-built SwiftUI components for building DICOM medical image viewers with minimal code.
Requires iOS 14+ / macOS 12+ (SwiftUI components use
@StateObject).
| Component | Description | Key Features |
|---|---|---|
| DicomImageView | Display DICOM images | Automatic scaling, windowing modes, GPU acceleration |
| WindowingControlView | Interactive window/level controls | 13 medical presets, sliders, automatic optimization |
| SeriesNavigatorView | Navigate DICOM series | Slice navigation, progress indicator, keyboard shortcuts |
| MetadataView | Display DICOM metadata | Organized sections, formatted values, accessibility |
Add DicomSwiftUI to your SwiftUI project:
// Package.swift
dependencies: [
.package(url: "https://github.com/ThalesMMS/DICOM-Decoder.git", from: "1.0.0")
],
targets: [
.target(
name: "MyApp",
dependencies: [
.product(name: "DicomSwiftUI", package: "DICOM-Decoder")
]
)
]import SwiftUI
import DicomSwiftUIDisplay a DICOM image with automatic windowing:
import SwiftUI
import DicomSwiftUI
struct ContentView: View {
let dicomURL: URL
var body: some View {
DicomImageView(url: dicomURL)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}Features:
- Automatic file loading
- Optimal window/level calculation
- Aspect ratio preservation
- Loading and error states
Add medical preset buttons and interactive sliders:
struct DicomViewerView: View {
let dicomURL: URL
@StateObject private var imageViewModel = DicomImageViewModel()
@StateObject private var windowingViewModel = WindowingViewModel()
var body: some View {
VStack {
// Display image
DicomImageView(url: dicomURL, viewModel: imageViewModel)
// Windowing controls with presets
if let decoder = imageViewModel.decoder {
WindowingControlView(
decoder: decoder,
viewModel: windowingViewModel,
onWindowChange: { center, width in
imageViewModel.updateWindowing(.custom(center: center, width: width))
}
)
}
}
}
}Compatibility note:
- The
DicomViewerViewexample above uses@StateObjectwithDicomImageViewModelandWindowingViewModel, which requires iOS 14+. - For iOS 13 targets, use
@ObservedObjectand initialize/inject the view models ininit:
struct DicomViewerView: View {
let dicomURL: URL
@ObservedObject private var imageViewModel: DicomImageViewModel
@ObservedObject private var windowingViewModel: WindowingViewModel
init(
dicomURL: URL,
imageViewModel: DicomImageViewModel = DicomImageViewModel(),
windowingViewModel: WindowingViewModel = WindowingViewModel()
) {
self.dicomURL = dicomURL
self.imageViewModel = imageViewModel
self.windowingViewModel = windowingViewModel
}
var body: some View {
// Same body as the @StateObject example
DicomImageView(url: dicomURL, viewModel: imageViewModel)
}
}Available presets:
- CT: Lung, Bone, Brain, Liver, Mediastinum, Abdomen, Spine, Pelvis
- Specialized: Angiography, Pulmonary Embolism, Mammography, PET Scan
Navigate through multi-slice DICOM series:
struct SeriesViewerView: View {
let seriesURLs: [URL]
@State private var currentIndex = 0
var body: some View {
VStack(spacing: 0) {
// Display current slice
DicomImageView(url: seriesURLs[currentIndex])
.frame(maxWidth: .infinity, maxHeight: .infinity)
Divider()
// Navigation controls
SeriesNavigatorView(
currentIndex: $currentIndex,
totalCount: seriesURLs.count,
onNavigate: { newIndex in
// Optional: Preload adjacent slices
preloadSlice(at: newIndex)
}
)
.frame(height: 80)
}
}
func preloadSlice(at index: Int) {
// Preload logic here
}
}Features:
- First/Previous/Next/Last buttons
- Interactive slider
- Slice counter with progress percentage
- Keyboard shortcuts support
- Thumbnail strip placeholder
Show formatted DICOM metadata:
struct MetadataDisplayView: View {
let dicomURL: URL
var body: some View {
VStack(spacing: 0) {
DicomImageView(url: dicomURL)
.frame(maxHeight: 400)
Divider()
MetadataView(url: dicomURL)
.frame(maxHeight: 250)
}
}
}Displayed information:
- Patient: Name, ID, Age, Sex, Birth Date
- Study: Date, Time, Description, Modality, Study UID
- Series: Number, Description, Series UID
- Image: Dimensions, Spacing, Position, Window/Level settings
Combine all components into a full-featured viewer:
struct CompleteDicomViewerView: View {
let seriesURLs: [URL]
@State private var currentIndex = 0
@StateObject private var imageViewModel = DicomImageViewModel()
@StateObject private var windowingViewModel = WindowingViewModel()
var body: some View {
VStack(spacing: 0) {
// Main image display
DicomImageView(
url: seriesURLs[currentIndex],
viewModel: imageViewModel,
windowingMode: .custom(
center: windowingViewModel.center,
width: windowingViewModel.width
)
)
.frame(maxWidth: .infinity, maxHeight: .infinity)
Divider()
// Metadata panel
MetadataView(url: seriesURLs[currentIndex])
.frame(height: 150)
Divider()
// Windowing controls
if let decoder = imageViewModel.decoder {
WindowingControlView(
decoder: decoder,
viewModel: windowingViewModel,
layout: .compact,
onWindowChange: { center, width in
imageViewModel.updateWindowing(.custom(center: center, width: width))
}
)
.padding()
}
Divider()
// Series navigation
SeriesNavigatorView(
currentIndex: $currentIndex,
totalCount: seriesURLs.count
)
.frame(height: 80)
}
}
}DicomImageView supports multiple windowing strategies:
// Automatic optimal window calculation
DicomImageView(url: dicomURL, windowingMode: .automatic)
// Medical preset (13 presets available)
DicomImageView(url: dicomURL, windowingMode: .preset(.lung))
DicomImageView(url: dicomURL, windowingMode: .preset(.bone))
DicomImageView(url: dicomURL, windowingMode: .preset(.brain))
// Custom window/level values
DicomImageView(url: dicomURL, windowingMode: .custom(center: 50.0, width: 400.0))
// Use window/level from DICOM file tags
DicomImageView(url: dicomURL, windowingMode: .fromDecoder)Enable GPU acceleration for large images:
// Automatic selection (recommended)
DicomImageView(
url: dicomURL,
processingMode: .auto // Uses Metal for ≥800×800 images
)
// Force GPU processing
DicomImageView(
url: dicomURL,
processingMode: .metal // Always use Metal (falls back to vDSP if unavailable)
)
// Force CPU processing
DicomImageView(
url: dicomURL,
processingMode: .vdsp // Always use vDSP (Accelerate framework)
)Performance benefits:
- 3.94× speedup for 1024×1024 images on Apple Silicon
- Automatic fallback to vDSP if Metal unavailable
- See Performance section for benchmarks
All components support customization:
// Custom styling
DicomImageView(url: dicomURL)
.background(Color.black)
.cornerRadius(8)
.shadow(radius: 4)
// Compact layout
WindowingControlView(decoder: decoder, layout: .compact)
// Expanded layout with more controls
WindowingControlView(decoder: decoder, layout: .expanded)
// Custom error handling
DicomImageView(url: dicomURL)
.overlay {
if let error = viewModel.error {
ErrorView(error: error)
}
}All components include comprehensive accessibility support:
- ✅ VoiceOver labels and hints
- ✅ Dynamic Type text scaling
- ✅ Keyboard navigation support
- ✅ Dark mode adaptive colors
- ✅ High contrast compatibility
- ✅ Reduced motion preferences
A complete reference implementation is available in Examples/DicomSwiftUIExample/:
# Run the example app
swift run DicomSwiftUIExampleDemonstrates:
- All four SwiftUI components
- Multiple windowing modes (automatic, presets, custom, GPU)
- Series loading and navigation
- Metadata display with different layouts
- Dark mode and accessibility features
See Examples/DicomSwiftUIExample/README.md for detailed usage.
- Getting Started:
Sources/DicomSwiftUI/DicomSwiftUI.docc/GettingStarted.md - API Reference: Run
swift package generate-documentation --target DicomSwiftUI - Code Samples:
Sources/DicomSwiftUI/DicomSwiftUI.docc/Resources/code-samples/ - Example App:
Examples/DicomSwiftUIExample/
- Core vs SwiftUI:
DicomCoresupports iOS 13.0+ / macOS 12.0+;DicomSwiftUIcomponents require iOS 14.0+ because examples use@StateObject. - Recommended (new): Use the
@StateObjectexamples on iOS 14+ / macOS 12+. - Legacy fallback (iOS 13): Use the
@ObservedObjectinitialization/injection pattern shown above, then migrate to@StateObjectwhen iOS 14+ is your minimum target.
| Component | Description | Primary Use |
|---|---|---|
DCMDecoder |
Core DICOM decoder | Load files, extract pixels and metadata |
DCMWindowingProcessor |
Image processing | Window/level, presets, quality metrics |
StudyDataService |
Data service | Scan directories, group studies |
DICOMError |
Error system | Typed error handling |
DCMDictionary |
Tag dictionary | Map numeric tags to names |
1. DICOM file
|
2. validateDICOMFile() (optional)
|
3. try DCMDecoder(contentsOfFile:) or try await DCMDecoder(contentsOfFile:)
|
4. Decoder parses:
- Header (128 bytes + "DICM")
- Meta Information
- Dataset (tags + values)
- Pixel Data (lazy loading)
|
5. Access data:
- info(for: .modality) -> Metadata
- getPixels16() -> Pixel buffer
- applyWindowLevel() -> Processed pixels
DICOM-Decoder/
|-- Package.swift
|-- Sources/DicomCore/
| |-- DCMDecoder.swift # Core DICOM parser
| |-- DCMDecoder+Async.swift # Async/await extensions
| |-- DCMWindowingProcessor.swift # Window/level processing
| |-- MetalWindowingProcessor.swift # GPU-accelerated windowing
| |-- DCMPixelReader.swift # Pixel data extraction
| |-- DCMTagParser.swift # Tag parsing logic
| |-- DCMBinaryReader.swift # Binary data reader
| |-- DCMDictionary.swift # Tag name/number mapping
| |-- DICOMError.swift # Typed error definitions
| |-- DicomConstants.swift # DicomTag enum and constants
| |-- DicomSeriesLoader.swift # Series/volume loading
| |-- JPEGLosslessDecoder.swift # Native JPEG Lossless decoder
| |-- PatientModel.swift # Data model structures
| |-- StudyDataService.swift # Study/series grouping
| |-- ValueTypes.swift # V2 type-safe value types
| |-- Protocols/ # Protocol abstractions
| |-- TagHandlers/ # Per-VR tag handling
| |-- Logging/ # Logging utilities
| |-- DicomCore.docc/ # DocC documentation
| `-- Resources/DCMDictionary.plist
|-- Tests/DicomCoreTests/
`-- ViewerReference/
Use the type-safe DicomTag enum for accessing standard DICOM tags:
Patient Information:
.patientName // (0010,0010) - Patient Name
.patientID // (0010,0020) - Patient ID
.patientBirthDate // (0010,0030) - Patient Birth Date
.patientSex // (0010,0040) - Patient Sex
.patientAge // (0010,1010) - Patient AgeStudy/Series Information:
.studyInstanceUID // (0020,000D) - Study Instance UID
.seriesInstanceUID // (0020,000E) - Series Instance UID
.modality // (0008,0060) - Modality (CT, MR, XR, etc.)
.studyDescription // (0008,1030) - Study Description
.seriesDescription // (0008,103E) - Series DescriptionImage Properties:
.rows // (0028,0010) - Rows (height)
.columns // (0028,0011) - Columns (width)
.bitsAllocated // (0028,0100) - Bits Allocated
.bitsStored // (0028,0101) - Bits Stored
.pixelRepresentation // (0028,0103) - Pixel RepresentationSpatial Information:
.imagePositionPatient // (0020,0032) - Image Position (Patient)
.imageOrientationPatient // (0020,0037) - Image Orientation (Patient)
.pixelSpacing // (0028,0030) - Pixel Spacing
.sliceThickness // (0018,0050) - Slice ThicknessWindow/Level:
.windowCenter // (0028,1050) - Window Center
.windowWidth // (0028,1051) - Window Width
.rescaleSlope // (0028,1053) - Rescale Slope
.rescaleIntercept // (0028,1052) - Rescale InterceptFor custom or private tags not in the enum, use raw hex values:
let privateTag = decoder.info(for: 0x00091001) // Private manufacturer tagComplete API documentation generated with DocC is available online:
Swift DICOM Decoder API Reference
The API reference includes:
- Detailed class and method documentation
- Code examples and usage patterns
- Type definitions and protocols
- Complete symbol index
| Document | Description | Best For |
|---|---|---|
| Getting Started | End-to-end tutorial | New to DICOM |
| DICOM Glossary | Terminology reference | Understanding terms |
| Troubleshooting | Common issues and fixes | Debugging problems |
| Document | Description | Best For |
|---|---|---|
| Usage Examples | Complete, ready-to-use code samples | Copy and adapt |
| CHANGELOG | Release history | Tracking changes |
Controls brightness and contrast of DICOM images:
- Level (Center): brightness
- Width: contrast
// Lung: Center -600 HU, Width 1500 HU
// Bone: Center 400 HU, Width 1800 HU
// Brain: Center 40 HU, Width 80 HUThe library provides a type-safe DicomTag enum for accessing metadata:
// Recommended: Type-safe DicomTag enum
decoder.info(for: .patientName) // Patient Name
decoder.info(for: .modality) // Modality (CT, MR, etc.)
decoder.info(for: .rows) // Image height
decoder.intValue(for: .columns) // Image width (as Int)
decoder.doubleValue(for: .windowCenter) // Window center (as Double)
// Legacy (deprecated): Raw hex values (still supported for custom/private tags)
decoder.info(for: 0x00100010) // Patient Name
decoder.info(for: 0x00080060) // Modality
decoder.info(for: 0x00280010) // RowsDensity scale in CT imaging:
- Air: -1000 HU
- Lung: -500 HU
- Water: 0 HU
- Muscle: +40 HU
- Bone: +700 to +3000 HU
- Use background processing for large files:
Task.detached {
let decoder = try await DCMDecoder(contentsOfFile: path)
}- Validate before loading to improve UX:
let validation = decoder.validateDICOMFile(path)
if !validation.isValid {
showError(validation.issues)
}- Use thumbnails for image lists:
let thumb = decoder.getDownsampledPixels16(maxDimension: 150)- Cache decoder instances per study:
var decoders: [String: DCMDecoder] = [:]
decoders[studyUID] = decoder- Release memory during batch processing:
autoreleasepool {
// Process file
}- Compressed transfer syntaxes: Native support for JPEG Lossless (Process 14, all selection values 0-7). Best-effort single-frame JPEG/JPEG2000 via ImageIO. RLE and multi-frame encapsulated compression are not supported - convert first if needed.
- Thread safety: The decoder is not thread-safe. Use one instance per thread or synchronize access.
- Very large files (>1GB): May consume significant memory. Process in chunks or downsample.
Native Apple frameworks only:
FoundationCoreGraphicsImageIOAccelerate
Contributions are welcome.
- Fork the repository.
- Create a feature branch (
git checkout -b feature/MyFeature). - Update code in
Sources/DicomCore/and add tests. - Run the tests:
swift test swift build - Commit with a clear message.
- Push to your branch.
- Open a Pull Request.
- Documentation improvements
- Additional test cases
- Bug fixes
- Performance optimizations
- New medical presets
- Internationalization
- Be respectful and constructive.
- Follow Swift code conventions.
- Add tests for new functionality.
- Preserve backward compatibility.
MIT License. See LICENSE for details.
MIT License
Copyright (c) 2024 ThalesMMS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software...
This project originates from the Objective-C DICOM decoder by kesalin. The Swift package modernizes that codebase while preserving credit to the original author.
- Documentation: GETTING_STARTED.md
- Bug reports: GitHub Issues
- Discussions: GitHub Discussions
- Email: Please open an issue first
If this project is useful, consider starring the repository or contributing improvements.
