DICOM anonymization in Python is a tag-strip: open the file with pydicom (dcmread), overwrite the identifiers, save_as. This page is that how-to. It is not “pick an anonymizer product” and it is not “what is data anonymization.”
If you meant pick a tool (Pixelmed, CTP, DicomCleaner) → DICOM anonymizer software. If you meant what anonymization is (tabular / Safe Harbor / GDPR) → what is data anonymization. If you meant read a DICOM in Python, not strip it → how to read DICOM files. If you meant convert DICOM↔JPEG → how to convert a DICOM image into JPG or PNG. If you meant a PYCAD build → DICOM de-identification software (this URL stays here; do not treat that services page as this script).
What this script actually does
| Layer | Job | This page |
|---|---|---|
| Direct identifiers | Name, Patient ID, birth date, MRN-like IDs | Overwrite or delete |
| Dates and sites | Study / series / acquisition dates, institution, referring physician | Blank or shift; do not leave a unique day |
| UIDs | Study / Series / SOP Instance UID | Replace with new UIDs so the file still parses |
| Burned-in pixels | Name drawn on the image itself | Not covered — that is OCR / pixel scrub, not a tag-strip |
DICOM PS3.15 defines the Basic Confidentiality Profile: a checklist of attributes that must be removed or replaced before a dataset leaves a controlled environment. HIPAA Safe Harbor’s 18 identifiers (names, geo smaller than a state, dates except year, phones, SSN, MRN, device serials, full-face photos, …) land on many of the same tags. The general list lives on what is data anonymization. Here they become pydicom keywords.
Tag docs: pydicom user guide.
Environment
python -m venv dicom_anon
source dicom_anon/bin/activate # Windows: dicom_anonScriptsactivate
pip install pydicom
glob2 is optional. The standard library glob / pathlib is enough for a folder of slices. Sanity check: import pydicom with no ImportError.
One file: dcmread, overwrite, save_as
import pydicom as pm
dicom_path = "path/to/dicom/file.dcm"
ds = pm.dcmread(dicom_path)
print("The patient name is:", ds.PatientName)
ds.PatientName = "Anonymous"
ds.save_as("anonymous_slice.dcm")
dcmread returns a FileDataset. Keywords (PatientName, PatientID) are easier than hex tags; both work. Setting PatientName = "Anonymous" and calling save_as is the whole mechanism. A missing keyword raises AttributeError — modalities omit tags. Use getattr(ds, "PatientName", "") or test with in ds before you write.
That one-tag script is not a Confidentiality Profile. Name-only still leaves Patient ID, birth date, Study Date, Accession Number, and the UIDs that stitch the series together.
A real tag-strip
import pydicom as pm
from pydicom.uid import generate_uid
# Keywords that map onto PS3.15 Basic Confidentiality / Safe Harbor.
# Delete if present; do not invent empty values for tags the IOD forbids.
STRIP = [
"PatientName",
"PatientID",
"PatientBirthDate",
"PatientBirthTime",
"PatientSex",
"PatientAge",
"PatientAddress",
"PatientTelephoneNumbers",
"OtherPatientNames",
"OtherPatientIDs",
"EthnicGroup",
"PatientComments",
"ReferringPhysicianName",
"PerformingPhysicianName",
"OperatorsName",
"InstitutionName",
"InstitutionAddress",
"InstitutionalDepartmentName",
"StationName",
"DeviceSerialNumber",
"AccessionNumber",
"StudyID",
"RequestingPhysician",
]
DATE_TAGS = [
"StudyDate",
"SeriesDate",
"AcquisitionDate",
"ContentDate",
"InstanceCreationDate",
]
def strip_dataset(ds, patient_name="Anonymous"):
for keyword in STRIP:
if keyword in ds:
del ds[keyword]
ds.PatientName = patient_name
ds.PatientID = "ANON"
for keyword in DATE_TAGS:
if keyword in ds:
ds.data_element(keyword).value = ""
# New UIDs so the object still hangs together, but not the original ones.
ds.StudyInstanceUID = generate_uid()
ds.SeriesInstanceUID = generate_uid()
ds.SOPInstanceUID = generate_uid()
if hasattr(ds, "file_meta") and ds.file_meta is not None:
ds.file_meta.MediaStorageSOPInstanceUID = ds.SOPInstanceUID
return ds
ds = pm.dcmread("path/to/dicom/file.dcm")
strip_dataset(ds)
ds.save_as("anonymous_slice.dcm")
Dates: emptying them is the blunt option. A trial often shifts every date by the same offset so intervals stay useful and the calendar day does not. That is a policy choice, not a second library. Do not leave a raw birth date or a unique Study Date if the rest of the header is stripped — zip + date of birth + sex is the classic linkage attack on what is data anonymization.
UIDs: replacing Study / Series / SOP Instance UID is part of the profile. Keep the new Study UID the same across every slice of one study, or a viewer will treat the folder as unrelated files. The batch below does that.
A folder of slices
import os
from glob import glob
from pathlib import Path
import pydicom as pm
from pydicom.uid import generate_uid
def anonymize_dicom(in_path, out_path, patient_name="Anonymous",
study_uid=None, series_uid=None):
ds = pm.dcmread(in_path)
strip_dataset(ds, patient_name=patient_name)
if study_uid:
ds.StudyInstanceUID = study_uid
if series_uid:
ds.SeriesInstanceUID = series_uid
if hasattr(ds, "file_meta") and ds.file_meta is not None:
ds.file_meta.MediaStorageSOPInstanceUID = ds.SOPInstanceUID
Path(out_path).parent.mkdir(parents=True, exist_ok=True)
ds.save_as(out_path)
if __name__ == "__main__":
src = "path/to/all/the/dicoms"
dst = "path/to/anonymized"
study_uid = generate_uid()
series_uid = generate_uid()
for src_path in glob(os.path.join(src, "*")):
if not os.path.isfile(src_path):
continue
out_path = os.path.join(dst, os.path.basename(src_path))
anonymize_dicom(src_path, out_path,
study_uid=study_uid, series_uid=series_uid)
Pass the same in_path and out_path if you intend to overwrite. Prefer a second directory until you have checked a slice in a viewer. PatientName defaults to Anonymous; pass another string if a trial protocol wants a study code instead.
Burned-in pixels
Some secondary captures and ultrasound overlays paint the name on the pixels. A header strip will not touch that. BurnedInAnnotation ((0028,0301)) is a flag, not a scrubber. Pixel OCR / black-box redaction is a different job and is not this script. If the flag is YES, do not call the file anonymized because the tags are clean.
What this page is not
This is not a product picker — that is DICOM anonymizer software. It is not a Safe Harbor essay — that is what is data anonymization. It is not dcmread + .pixel_array — that is how to read DICOM files. k-anonymity (every record indistinguishable from k−1 others) is a tabular guarantee, not a DICOM tag list; it is named on the privacy page, not implemented here.
PYCAD builds imaging pipelines that have to strip these tags for real. Case studies.