DICOM (Digital Imaging and Communications in Medicine) is the universal standard for digital medical images: X-rays, CT scans, MRIs, ultrasounds, and more. A DICOM file combines the image with clinical patient metadata.
What is DICOM?
A DICOM file (.dcm) contains:
- Image data: pixel array (typically 16-bit per pixel)
- Patient metadata: name, date of birth, ID
- Study information: modality (CT, MRI, XR…), date, physician
- Technical parameters: voltage, current, slice thickness, etc.
Basic DICOM structure:
├── File Meta Information ← standard header
├── Data Elements ← tags (Tag + VR + Value)
│ ├── (0010,0010) Patient Name
│ ├── (0010,0020) Patient ID
│ ├── (0008,0060) Modality [CT, MR, XR, US...]
│ ├── (0028,0010) Rows
│ ├── (0028,0011) Columns
│ └── (7FE0,0010) Pixel Data ← image data
Installation
pip install pydicom pillow numpy matplotlib
Read a DICOM file
import pydicom
import numpy as np
ds = pydicom.dcmread('image.dcm')
# Patient information
print(f"Patient: {ds.PatientName}")
print(f"ID: {ds.PatientID}")
print(f"Birth date: {ds.get('PatientBirthDate', 'N/A')}")
print(f"Sex: {ds.get('PatientSex', 'N/A')}")
# Study information
print(f"\nModality: {ds.Modality}") # CT, MR, XR, US...
print(f"Date: {ds.StudyDate}")
print(f"Desc: {ds.get('StudyDescription', 'N/A')}")
# Image properties
print(f"\nDimensions: {ds.Rows} x {ds.Columns}")
print(f"Bits: {ds.BitsAllocated}")
# Access pixels as numpy array
pixel_array = ds.pixel_array
print(f"\nArray shape: {pixel_array.shape}")
print(f"Dtype: {pixel_array.dtype}")
print(f"Min/Max: {pixel_array.min()} / {pixel_array.max()}")
View all metadata
import pydicom
ds = pydicom.dcmread('image.dcm')
for elem in ds:
if elem.tag != (0x7FE0, 0x0010): # skip pixel data
print(f" {elem.tag} {elem.keyword:40s} = {str(elem.value)[:60]}")
print(ds.get('Manufacturer', 'N/A'))
print(ds.get('KVP', 'N/A')) # kilovoltage (CT)
print(ds.get('SliceThickness', 'N/A')) # slice thickness (CT)
Convert DICOM to PNG/JPG
import pydicom
import numpy as np
from PIL import Image
def dicom_to_png(dcm_path, png_path, apply_windowing=True):
"""
Convert DICOM image to PNG.
apply_windowing: normalizes value range for visualization.
"""
ds = pydicom.dcmread(dcm_path)
pixel_array = ds.pixel_array.astype(np.float64)
if apply_windowing:
wc = float(ds.get('WindowCenter', pixel_array.mean()))
ww = float(ds.get('WindowWidth', pixel_array.max() - pixel_array.min()))
if isinstance(wc, pydicom.multival.MultiValue):
wc = float(wc[0])
if isinstance(ww, pydicom.multival.MultiValue):
ww = float(ww[0])
pixel_array = np.clip(pixel_array, wc - ww / 2, wc + ww / 2)
p_min, p_max = pixel_array.min(), pixel_array.max()
if p_max > p_min:
pixel_norm = ((pixel_array - p_min) / (p_max - p_min) * 255).astype(np.uint8)
else:
pixel_norm = np.zeros_like(pixel_array, dtype=np.uint8)
Image.fromarray(pixel_norm).save(png_path)
print(f"PNG saved: {png_path} ({ds.Rows}x{ds.Columns})")
dicom_to_png('xray.dcm', 'xray.png')
Process DICOM series (multiple slices)
import pydicom
import numpy as np
from pathlib import Path
from PIL import Image
def process_dicom_series(input_dir, output_dir, fmt='png'):
"""Process all DICOM files in a directory (CT/MRI series)."""
source = Path(input_dir)
dest = Path(output_dir)
dest.mkdir(parents=True, exist_ok=True)
# Load and sort by InstanceNumber
dicom_files = []
for dcm_file in source.glob('*.dcm'):
try:
ds = pydicom.dcmread(dcm_file, stop_before_pixels=True)
number = int(ds.get('InstanceNumber', 0))
dicom_files.append((number, dcm_file))
except Exception as e:
print(f" Error reading {dcm_file.name}: {e}")
dicom_files.sort(key=lambda x: x[0])
print(f"DICOM series: {len(dicom_files)} slices found")
for i, (number, dcm_file) in enumerate(dicom_files, 1):
try:
ds = pydicom.dcmread(dcm_file)
pixels = ds.pixel_array.astype(np.float64)
p_min, p_max = pixels.min(), pixels.max()
if p_max > p_min:
pixels = ((pixels - p_min) / (p_max - p_min) * 255).astype(np.uint8)
else:
pixels = np.zeros_like(pixels, dtype=np.uint8)
filename = f"slice_{i:04d}.{fmt}"
Image.fromarray(pixels).save(dest / filename)
if i % 10 == 0 or i == 1:
print(f" Processed slice {i}/{len(dicom_files)}: {filename}")
except Exception as e:
print(f" ERROR in {dcm_file.name}: {e}")
print(f"\nSeries exported to: {dest}")
process_dicom_series('CT_head/', 'CT_head_png/')
DICOM anonymization (remove patient data)
import pydicom
from pathlib import Path
TAGS_TO_ANONYMIZE = [
'PatientName', 'PatientID', 'PatientBirthDate', 'PatientAddress',
'PatientTelephoneNumbers', 'PatientSex', 'OtherPatientIDs',
'PatientAge', 'PatientWeight', 'PatientComments',
'ReferringPhysicianName', 'PerformingPhysicianName',
'InstitutionName', 'InstitutionAddress', 'StudyID',
]
def anonymize_dicom(input_path, output_path, anon_id='ANONYMOUS_PATIENT'):
"""Anonymize a DICOM file by removing patient data."""
ds = pydicom.dcmread(input_path)
for tag_keyword in TAGS_TO_ANONYMIZE:
if hasattr(ds, tag_keyword):
try:
setattr(ds, tag_keyword, '')
except AttributeError:
pass
ds.PatientName = anon_id
ds.PatientID = anon_id
ds.save_as(output_path)
print(f"Anonymized: {input_path} → {output_path}")
def anonymize_directory(input_dir, output_dir):
source = Path(input_dir)
dest = Path(output_dir)
dest.mkdir(parents=True, exist_ok=True)
for dcm in sorted(source.glob('*.dcm')):
anonymize_dicom(dcm, dest / dcm.name)
anonymize_directory('original_dicoms/', 'anonymized_dicoms/')
Visualize with matplotlib
import pydicom
import numpy as np
import matplotlib.pyplot as plt
ds = pydicom.dcmread('ct_scan.dcm')
pixel_array = ds.pixel_array
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# Original grayscale
axes[0].imshow(pixel_array, cmap='gray')
axes[0].set_title(f"Original ({pixel_array.shape[0]}x{pixel_array.shape[1]})")
axes[0].axis('off')
# Lung window (CT)
wc_lung, ww_lung = -600, 1500
lung = np.clip(pixel_array, wc_lung - ww_lung/2, wc_lung + ww_lung/2)
axes[1].imshow(lung, cmap='gray')
axes[1].set_title("Lung window")
axes[1].axis('off')
# Bone window (CT)
wc_bone, ww_bone = 400, 1000
bone = np.clip(pixel_array, wc_bone - ww_bone/2, wc_bone + ww_bone/2)
axes[2].imshow(bone, cmap='gray')
axes[2].set_title("Bone window")
axes[2].axis('off')
plt.suptitle(f"Patient: {ds.get('PatientName', 'N/A')} | Modality: {ds.Modality}")
plt.tight_layout()
plt.savefig('ct_visualization.png', dpi=150, bbox_inches='tight')
plt.show()
Related conversions
Most teams that read this guide convert images in one of these directions: