I just released dicom-clean — an open-source CLI tool for HIPAA-compliant DICOM de-identification. Here's how it works and why I built it.
The Problem
Medical imaging files (CT scans, MRIs, X-rays) use a format called DICOM, which contains two things:
- Metadata — patient name, birth date, MRN, hospital, referring physician, etc.
- Pixel data — the actual image
When hospitals want to share DICOM data for research, AI training, or vendor evaluation, they must remove the patient identifiers first — this is required by HIPAA Safe Harbor (45 CFR §164.514(b)(2)).
But existing options are problematic:
- Commercial tools (RSNA CTP, DicomCleaner) cost $5,000+/year
- Cloud solutions upload patient data → HIPAA violation risk
- DCMTK / GDCM work but have steep learning curves (C++ toolchain, complex syntax)
So I built dicom-clean to be:
- ✅ 100% offline (air-gapped compatible)
- ✅ Simple CLI (one command)
- ✅ Zero-install (standalone
.exe) - ✅ Free and open source (AGPL-3.0)
What It Does
1. Strips All 18 HIPAA Patient Identifiers
The tool blanks out 40+ scalar PHI tags and deletes 8 sequences that may carry nested PHI:
HIPAA_TAGS_TO_BLANK = (
(0x0010, 0x0010), # PatientName
(0x0010, 0x0020), # PatientID
(0x0010, 0x0030), # PatientBirthDate
(0x0010, 0x1040), # PatientAddress
(0x0008, 0x0080), # InstitutionName
(0x0008, 0x0090), # ReferringPhysicianName
# ... 40+ more
)
HIPAA_TAGS_TO_REMOVE = (
(0x0008, 0x1110), # ReferencedStudySequence
(0x0008, 0x1115), # ReferencedSeriesSequence
# ... 6 more
)
Design decision: I blank the scalar tags (preserving tag + VR) rather than deleting them, so downstream PACS and DICOM viewers don't break when they expect those fields.
2. Deterministic UID Pseudonymization
DICOM uses UIDs (Unique Identifiers) to link files together. A study has a StudyInstanceUID, each series has a SeriesInstanceUID, and each image has a SOPInstanceUID.
If you just replace these with random UUIDs, you lose the relationships between files — a PACS server won't be able to group images from the same study.
My solution: SHA-256 with an optional salt.
def pseudonymize_uid(original_uid: str, salt: str = "") -> str:
"""Deterministically pseudonymize a DICOM UID."""
if not original_uid:
return original_uid
digest = hashlib.sha256(
(salt + str(original_uid)).encode("utf-8")
).hexdigest()
number = int(digest[:32], 16) # 128 bits
return f"2.25.{number}"
Why this works:
- Deterministic — Same input + salt → same output, always
- Reversible relationships — All files from one study get consistent pseudonyms
- Non-reversible — Without the salt, you can't reverse it
-
Valid format — Output is a proper DICOM UID (
2.25.<128-bit-int>)
3. Image Integrity
The pixel data is never touched. Same for:
-
Modality(CT, MR, US, etc.) PixelDataTransferSyntaxUIDPhotometricInterpretation-
Rows/Columns -
BitsAllocated/BitsStored -
PixelSpacing/SliceThickness - Window/Level tags
So the image looks identical in any DICOM viewer — only the metadata changes.
4. Date Reduction
HIPAA permits keeping the year but not the month/day. So:
Before: StudyDate = "20250315"
After: StudyDate = "20250101"
Tech Stack
| Component | Purpose |
|---|---|
| pydicom | DICOM parsing & writing |
| Typer | CLI framework |
| Rich | Terminal UI (panels, progress bars, tables) |
| PyInstaller | Standalone .exe build |
What It Looks Like
$ dicom-clean sanitize patient.dcm output\ --salt "my-secret"
The output shows a live progress bar and a colored summary table:
- Total Files Scanned: 1,250
- Successfully Anonymized: 1,248
- Skipped (Non-DICOM): 2
- Errors / Failed: 0
Directory Batch Processing
The tool recursively processes directories and preserves the original subfolder structure:
$ dicom-clean sanitize C:\Hospital_Data C:\Clean_Output --salt "hospital-2025"
Robustness
- Non-DICOM files are skipped with a warning instead of crashing
- Per-file errors are collected into a summary
- Uses
try/exceptforInvalidDicomError,OSError,IOError, and generic exceptions - Exit code
1if any file failed,0otherwise
Installation
Windows Standalone (No Python Required)
Download dicom-clean.exe from the Releases page and run:
dicom-clean.exe sanitize input.dcm output\ --salt "my-secret"
From Source
git clone https://github.com/kaveen-ai/dicom-clean.git
cd dicom-clean
pip install -r requirements.txt
python anonymizer.py --help
Design Tradeoffs
Some decisions I made and why:
1. SHA-256 hashing vs random UUIDs for UIDs
- ✅ Hash preserves relationships across files
- ✅ Deterministic (reproducible)
- ❌ Requires salt management for security
2. Blank tags vs delete tags
- ✅ Blanking preserves tag structure (PACS-safe)
- ✅ Downstream tools don't break
- ❌ Leaves an empty tag with metadata footprint
3. AGPL-3.0 + Commercial dual-license
- ✅ Free for open-source use
- ✅ Commercial license funds development
- ❌ AGPL can be a dealbreaker for some hospitals
4. Single-file CLI vs library-first
- ✅ Standalone
.exefor non-technical users - ✅ The core
DicomAnonymizerclass is importable as a library - ✅ CLI is just a thin Typer wrapper
The DicomAnonymizer Class
If you want to use the engine programmatically:
from anonymizer import DicomAnonymizer
anonymizer = DicomAnonymizer(
salt="my-hospital-secret",
remove_private_tags=True,
reduce_dates_to_year=True,
)
summary = anonymizer.anonymize(
input_path="patient.dcm",
output_path="output/",
)
print(f"Anonymized {summary.anonymized} files")
The class handles:
- UID pseudonymization
- HIPAA tag blanking
- Private tag removal
- Date reduction
- File meta sanitization
What's Next
I'm planning:
- GitHub Actions CI — test matrix across Python 3.10-3.13
-
PyPI publication —
pip install dicom-clean - Docker image — for hospital IT deployments
- Pixel OCR redaction — for burned-in PHI in images (commercial edition)
Feedback Welcome
I'd love feedback from anyone working with DICOM data, healthcare IT, or medical imaging.
- Star on GitHub: github.com/kaveen-ai/dicom-clean
- Report bugs: Issues
- Discussions: GitHub Discussions
What would you like to see next?
Tags: python healthcare opensource dicom privacy
Top comments (0)