| name | DICOM Processing |
| description | Work with DICOM medical imaging files programmatically using pydicom, dcmtk, and related tools. Read, write, modify, and validate DICOM tags, pixel data, and metadata. Use when reading DICOM files, extracting metadata, modifying tags, working with pixel data, converting transfer syntaxes, validating DICOM conformance, or scripting bulk DICOM operations. |
DICOM Processing
What This Skill Does
Generates correct code for reading, writing, and manipulating DICOM (Digital Imaging and Communications in Medicine) files. Covers the pydicom Python library (primary), DCMTK command-line tools, and the DICOM data model including tags, VRs, transfer syntaxes, and SOP classes.
Prerequisites
- Python 3.8+ with pydicom (
pip install pydicom)
- Optional:
pillow or numpy for pixel data operations
- Optional:
pylibjpeg + pylibjpeg-libjpeg for compressed transfer syntaxes
- Optional: DCMTK toolkit for command-line operations
pip install pydicom
pip install pydicom[all]
pip install numpy pillow pylibjpeg pylibjpeg-libjpeg pylibjpeg-openjpeg
brew install dcmtk
apt-get install dcmtk
Quick Start
Read a DICOM File
import pydicom
ds = pydicom.dcmread("image.dcm")
print(ds.PatientName)
print(ds.StudyDate)
print(ds.Modality)
print(ds.StudyInstanceUID)
print(ds.SeriesInstanceUID)
print(ds.SOPInstanceUID)
Modify and Save
ds = pydicom.dcmread("image.dcm")
ds.PatientName = "ANONYMOUS"
ds.PatientID = "ANON001"
ds.save_as("modified.dcm")
Access Pixel Data
ds = pydicom.dcmread("image.dcm")
pixel_array = ds.pixel_array
print(pixel_array.shape)
print(pixel_array.dtype)
DICOM Data Model
Tags
Every DICOM attribute is identified by a (group, element) tag pair:
from pydicom.tag import Tag
ds.PatientName
ds[0x0010, 0x0010]
ds[Tag(0x0010, 0x0010)]
if "PatientName" in ds:
print(ds.PatientName)
Common Tags Reference
| Tag | Keyword | VR | Description |
|---|
| (0008,0020) | StudyDate | DA | Date the study started |
| (0008,0030) | StudyTime | TM | Time the study started |
| (0008,0050) | AccessionNumber | SH | RIS accession number |
| (0008,0060) | Modality | CS | CT, MR, US, CR, XA, etc. |
| (0008,0070) | Manufacturer | LO | Equipment manufacturer |
| (0008,103E) | SeriesDescription | LO | Description of the series |
| (0008,1030) | StudyDescription | LO | Description of the study |
| (0010,0010) | PatientName | PN | Patient's full name |
| (0010,0020) | PatientID | LO | Patient identifier |
| (0010,0030) | PatientBirthDate | DA | Patient date of birth |
| (0010,0040) | PatientSex | CS | M, F, or O |
| (0020,000D) | StudyInstanceUID | UI | Unique study identifier |
| (0020,000E) | SeriesInstanceUID | UI | Unique series identifier |
| (0008,0018) | SOPInstanceUID | UI | Unique instance identifier |
| (0008,0016) | SOPClassUID | UI | Type of DICOM object |
| (0028,0010) | Rows | US | Image height in pixels |
| (0028,0011) | Columns | US | Image width in pixels |
| (0028,0100) | BitsAllocated | US | Bits per pixel (8, 16) |
| (0028,0004) | PhotometricInterpretation | CS | MONOCHROME1, MONOCHROME2, RGB |
| (7FE0,0010) | PixelData | OB/OW | The actual pixel data |
Value Representations (VRs)
VRs define the data type and format of a DICOM value:
| VR | Name | Python Type | Example |
|---|
| CS | Code String | str | "CT", "MR" |
| DA | Date | str | "20250115" (YYYYMMDD) |
| DS | Decimal String | DSfloat/str | "1.5" |
| IS | Integer String | IS/str | "512" |
| LO | Long String | str | Max 64 chars |
| PN | Person Name | PersonName | "Smith^John" |
| SH | Short String | str | Max 16 chars |
| TM | Time | str | "143025.000" (HHMMSS.FFFFFF) |
| UI | Unique Identifier | UID | "1.2.840..." |
| US | Unsigned Short | int | 512 |
| OB | Other Byte | bytes | Binary data |
| OW | Other Word | bytes | Binary data |
| SQ | Sequence | Sequence | List of datasets |
Sequences
Sequences are nested datasets (like arrays of objects):
if "ReferencedStudySequence" in ds:
for item in ds.ReferencedStudySequence:
print(item.ReferencedSOPClassUID)
print(item.ReferencedSOPInstanceUID)
from pydicom.dataset import Dataset
from pydicom.sequence import Sequence
item = Dataset()
item.ReferencedSOPClassUID = "1.2.840.10008.5.1.4.1.1.2"
item.ReferencedSOPInstanceUID = pydicom.uid.generate_uid()
ds.ReferencedStudySequence = Sequence([item])
Working with Pixel Data
Read Pixel Data as NumPy Array
import pydicom
import numpy as np
ds = pydicom.dcmread("ct_image.dcm")
pixels = ds.pixel_array
if hasattr(ds, "RescaleSlope") and hasattr(ds, "RescaleIntercept"):
hu = pixels * ds.RescaleSlope + ds.RescaleIntercept
Window/Level for Display
def apply_window(pixels, window_center, window_width):
"""Apply window/level to pixel data for display."""
img_min = window_center - window_width // 2
img_max = window_center + window_width // 2
windowed = np.clip(pixels, img_min, img_max)
windowed = ((windowed - img_min) / (img_max - img_min) * 255)
return windowed.astype(np.uint8)
LUNG_WINDOW = (-600, 1500)
BONE_WINDOW = (400, 1800)
SOFT_TISSUE = (40, 400)
BRAIN_WINDOW = (40, 80)
display = apply_window(hu, *SOFT_TISSUE)
Save as PNG
from PIL import Image
img = Image.fromarray(display, mode="L")
img.save("output.png")
if ds.PhotometricInterpretation == "RGB":
img = Image.fromarray(pixels, mode="RGB")
img.save("output.png")
Multi-frame Images
ds = pydicom.dcmread("multiframe.dcm")
pixels = ds.pixel_array
print(f"Frames: {ds.NumberOfFrames}")
print(f"Shape: {pixels.shape}")
frame_0 = pixels[0]
Transfer Syntaxes
Transfer syntaxes define how DICOM data is encoded (byte order, compression):
print(ds.file_meta.TransferSyntaxUID)
| UID | Name | Compression |
|---|
| 1.2.840.10008.1.2 | Implicit VR Little Endian | None |
| 1.2.840.10008.1.2.1 | Explicit VR Little Endian | None |
| 1.2.840.10008.1.2.4.50 | JPEG Baseline | Lossy |
| 1.2.840.10008.1.2.4.70 | JPEG Lossless | Lossless |
| 1.2.840.10008.1.2.4.90 | JPEG 2000 Lossless | Lossless |
| 1.2.840.10008.1.2.4.91 | JPEG 2000 | Lossy |
| 1.2.840.10008.1.2.5 | RLE Lossless | Lossless |
Decompressing Pixel Data
ds = pydicom.dcmread("compressed.dcm")
ds.decompress()
pixels = ds.pixel_array
Converting Transfer Syntax
dcmconv +te input.dcm output.dcm
dcmcjp2k +e2 input.dcm output.dcm
dcmcjpls +el input.dcm output.dcm
Creating DICOM Files
Create a DICOM File from Scratch
import pydicom
from pydicom.dataset import Dataset, FileDataset
from pydicom.uid import generate_uid, ExplicitVRLittleEndian
from pydicom.sequence import Sequence
import numpy as np
import datetime
filename = "new_image.dcm"
file_meta = pydicom.Dataset()
file_meta.MediaStorageSOPClassUID = "1.2.840.10008.5.1.4.1.1.2"
file_meta.MediaStorageSOPInstanceUID = generate_uid()
file_meta.TransferSyntaxUID = ExplicitVRLittleEndian
ds = FileDataset(filename, {}, file_meta=file_meta, preamble=b"\x00" * 128)
ds.PatientName = "Test^Patient"
ds.PatientID = "TEST001"
ds.PatientBirthDate = "19900101"
ds.PatientSex = "O"
ds.StudyInstanceUID = generate_uid()
ds.StudyDate = datetime.date.today().strftime("%Y%m%d")
ds.StudyTime = datetime.datetime.now().strftime("%H%M%S")
ds.Modality = "CT"
ds.SeriesInstanceUID = generate_uid()
ds.SeriesNumber = 1
ds.SOPClassUID = "1.2.840.10008.5.1.4.1.1.2"
ds.SOPInstanceUID = file_meta.MediaStorageSOPInstanceUID
ds.InstanceNumber = 1
ds.Rows = 512
ds.Columns = 512
ds.BitsAllocated = 16
ds.BitsStored = 16
ds.HighBit = 15
ds.PixelRepresentation = 1
ds.SamplesPerPixel = 1
ds.PhotometricInterpretation = "MONOCHROME2"
pixel_data = np.zeros((512, 512), dtype=np.int16)
ds.PixelData = pixel_data.tobytes()
ds.save_as(filename)
Bulk Operations
Iterate DICOM Files in a Directory
from pathlib import Path
import pydicom
def iter_dicom_files(directory: str):
"""Yield (path, dataset) for all DICOM files in a directory tree."""
for path in Path(directory).rglob("*"):
if path.is_file():
try:
ds = pydicom.dcmread(str(path), stop_before_pixels=True)
yield path, ds
except pydicom.errors.InvalidDicomError:
continue
for path, ds in iter_dicom_files("/data/studies"):
print(f"{path}: {ds.PatientName} | {ds.Modality} | {ds.StudyDate}")
Group Files by Study/Series
from collections import defaultdict
studies = defaultdict(lambda: defaultdict(list))
for path, ds in iter_dicom_files("/data/incoming"):
study_uid = ds.StudyInstanceUID
series_uid = ds.SeriesInstanceUID
studies[study_uid][series_uid].append(path)
for study_uid, series in studies.items():
print(f"Study {study_uid}: {len(series)} series")
for series_uid, files in series.items():
print(f" Series {series_uid}: {len(files)} instances")
Read Metadata Only (Fast)
ds = pydicom.dcmread("large_image.dcm", stop_before_pixels=True)
Extract Specific Tags
ds = pydicom.dcmread("image.dcm", specific_tags=[
"PatientName", "PatientID", "StudyDate", "Modality",
"StudyInstanceUID", "SeriesInstanceUID",
])
DCMTK Command-Line Tools
Common Commands
dcmdump image.dcm
dcmdump +P "0010,0010" +P "0008,0060" image.dcm
dcmodify -m "(0010,0010)=ANONYMOUS" image.dcm
dcmconv +te input.dcm output.dcm
dcmpschk image.dcm
storescu -v -aec REMOTE_AE host port image.dcm
findscu -v -aec REMOTE_AE host port -k "0008,0060=CT" -k "0010,0010=Smith*"
movescu -v -aec REMOTE_AE -aem MY_AE host port -k "0020,000D=1.2.3..."
Gotchas
- PatientName uses
^ as separator: "Family^Given^Middle^Prefix^Suffix". Use str(ds.PatientName) for display, ds.PatientName.family_name for components.
- Dates are strings, not date objects:
StudyDate is "20250115", not a Python date. Parse with datetime.strptime(ds.StudyDate, "%Y%m%d").
- UIDs must be globally unique: Always use
pydicom.uid.generate_uid() when creating new studies/series/instances. Never reuse UIDs.
- Pixel data may be compressed: Always handle the case where
ds.pixel_array raises an error due to missing decompression handlers. Install pylibjpeg packages.
- Private tags: Vendor-specific data uses odd group numbers (e.g.,
(0009,xxxx)). Access with ds[0x0009, 0x0010].
- Encoding: DICOM defaults to ISO-IR 100 (Latin-1). Check
SpecificCharacterSet for non-Latin text. pydicom handles decoding automatically.
- File vs dataset: Use
pydicom.dcmread() to read files. The returned FileDataset includes file meta information. For in-memory datasets, use Dataset() directly.
- Modifying PixelData: If you modify pixel data, update
Rows, Columns, BitsAllocated, BitsStored, HighBit, PixelRepresentation, and PhotometricInterpretation to match.
Resources