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.0 - Documentation: Getting Started | Glossary | Troubleshooting
- Overview
- Features
- Quick Start
- Installation
- Usage Examples
- 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
- Best-effort single-frame JPEG and JPEG2000 decoding via ImageIO (no JPEG Lossless/RLE)
- Automatic memory mapping for large files (>10MB)
- Downsampling for fast thumbnail generation
- 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
- Async/await (iOS 13+, macOS 10.15+)
- Validation before loading
- Convenience metadata helpers (patient, study, series)
- Parallel processing for large datasets
- Tag caching for frequent lookups
- Complete documentation with practical examples
- DICOM glossary
- Troubleshooting guide for common issues
- Extensive tests
- Step-by-step tutorials
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/ThalesMMS/DICOM-Decoder.git", from: "1.0.0")
]import DicomCore
let decoder = DCMDecoder()
decoder.setDicomFilename("/path/to/image.dcm")
guard decoder.dicomFileReadSuccess else {
print("Failed to load file")
return
}
print("Dimensions: \(decoder.width) x \(decoder.height)")
print("Modality: \(decoder.info(for: 0x00080060))")
print("Patient: \(decoder.info(for: 0x00100010))")
if let pixels = decoder.getPixels16() {
print("\(pixels.count) pixels loaded")
}For a detailed walkthrough, see GETTING_STARTED.md.
- 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
let decoder = DCMDecoder()
decoder.setDicomFilename("/path/to/ct_scan.dcm")
guard decoder.dicomFileReadSuccess else {
print("Load error")
return
}
print("Patient: \(decoder.info(for: 0x00100010))")
print("Modality: \(decoder.info(for: 0x00080060))")
print("Dimensions: \(decoder.width) x \(decoder.height)")
if let pixels = decoder.getPixels16() {
// Process image...
}func loadDICOM() async {
let decoder = DCMDecoder()
let success = await decoder.loadDICOMFileAsync("/path/to/image.dcm")
guard success else { return }
if let pixels = await decoder.getPixels16Async() {
await showImage(pixels, decoder.width, decoder.height)
}
}guard let pixels = decoder.getPixels16() else { return }
let modality = decoder.info(for: 0x00080060)
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 validation = decoder.validateDICOMFile("/path/to/image.dcm")
if !validation.isValid {
print("Invalid file:")
for issue in validation.issues {
print(" - \(issue)")
}
return
}
decoder.setDicomFilename("/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.
| 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 but recommended)
|
3. setDicomFilename() / loadDICOMFileAsync()
|
4. Decoder parses:
- Header (128 bytes + "DICM")
- Meta Information
- Dataset (tags + values)
- Pixel Data (lazy loading)
|
5. Access data:
- info(for:) -> Metadata
- getPixels16() -> Pixel buffer
- applyWindowLevel() -> Processed pixels
DICOM-Decoder/
|-- Package.swift
|-- Sources/DicomCore/
| |-- DCMDecoder.swift
| |-- DCMWindowingProcessor.swift
| |-- DICOMError.swift
| |-- StudyDataService.swift
| |-- PatientModel.swift
| |-- Protocols.swift
| |-- DCMDictionary.swift
| `-- Resources/DCMDictionary.plist
|-- Tests/DicomCoreTests/
`-- ViewerReference/
| 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 HUNumeric identifiers for metadata:
0x00100010 // Patient Name
0x00080060 // Modality (CT, MR, etc.)
0x00280010 // Image heightDensity 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 {
await decoder.loadDICOMFileAsync(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: best-effort single-frame JPEG/JPEG2000 via ImageIO only. JPEG Lossless, 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.