Pydicom
Overview
Pydicom is a pure Python package for working with DICOM files, the standard format for medical imaging data. This skill provides guidance on reading, writing, and manipulating DICOM files, including working with pixel data, metadata, and various compression formats.
When to Use This Skill
Use this skill when working with:
- Medical imaging files (CT, MRI, X-ray, ultrasound, PET, etc.)
- DICOM datasets requiring metadata extraction or modification
- Pixel data extraction and image processing from medical scans
- DICOM anonymization for research or data sharing
- Converting DICOM files to standard image formats
- Compressed DICOM data requiring decompression
- DICOM sequences and structured reports
- Multi-slice volume reconstruction
- PACS (Picture Archiving and Communication System) integration
Does NOT Trigger
| Scenario | Use Instead |
|---|---|
| Finding and downloading public cancer imaging collections (NCI Imaging Data Commons) | alterlab-imaging-data-commons |
| Tiling or preprocessing whole-slide pathology images (H&E WSI) | alterlab-histolab |
| Training ML models on clinical imaging or EHR datasets | alterlab-pyhealth |
| Processing ECG/EEG/EDA physiological waveforms | alterlab-neurokit2 |
Installation
Targets pydicom 3.x (current release 3.0.2 as of 2026-09; Python >= 3.10). 3.0 reorganized the pixel APIs — see the version note below. Install pydicom and common dependencies:
bashuv pip install "pydicom>=3.0" uv pip install pillow # For image format conversion uv pip install numpy # For pixel array manipulation uv pip install matplotlib # For visualization
For handling compressed DICOM files, additional packages may be needed:
bashuv pip install pylibjpeg pylibjpeg-libjpeg pylibjpeg-openjpeg # JPEG / JPEG 2000 decode; JPEG 2000 encode uv pip install pylibjpeg-rle # faster RLE encode/decode (pydicom's own RLE codec also works) uv pip install python-gdcm # Alternative decoder for most compressed syntaxes uv pip install pyjpegls # JPEG-LS encode/decode
pydicom 3.0 API changes (important)
3.0 moved all pixel handling into the pydicom.pixels module and deprecated some long-standing APIs (the old names still work but emit warnings and are removed in 4.0):
from pydicom.pixel_data_handlers.util import apply_voi_lut→from pydicom.pixels import apply_voi_lut(same forconvert_color_space).pydicom.encoders→pydicom.pixels.encoders.ds.save_as(path, write_like_original=False)→ds.save_as(path, enforce_file_format=True)(write_like_original=True, the default, became implicit and is no longer needed).- New helpers:
ds.set_pixel_data(arr, photometric_interpretation, bits_stored)writes Pixel Data and the matching Image Pixel elements;ds.compress()/ds.decompress()convert between transfer syntaxes.
All examples below use the 3.x APIs.
Core Workflows
Reading DICOM Files
Read a DICOM file using pydicom.dcmread():
pythonimport pydicom # Read a DICOM file ds = pydicom.dcmread('path/to/file.dcm') # Access metadata print(f"Patient Name: {ds.PatientName}") print(f"Study Date: {ds.StudyDate}") print(f"Modality: {ds.Modality}") # Display all elements print(ds)
Key points:
dcmread()returns aDatasetobject- Access data elements using attribute notation (e.g.,
ds.PatientName) or tag notation (e.g.,ds[0x0010, 0x0010]) - Use
ds.file_metato access file metadata like Transfer Syntax UID - Handle missing attributes with
getattr(ds, 'AttributeName', default_value)orhasattr(ds, 'AttributeName')
Working with Pixel Data
Extract and manipulate image data from DICOM files:
pythonimport pydicom import numpy as np import matplotlib.pyplot as plt # Read DICOM file ds = pydicom.dcmread('image.dcm') # Get pixel array (requires numpy) pixel_array = ds.pixel_array # Image information print(f"Shape: {pixel_array.shape}") print(f"Data type: {pixel_array.dtype}") print(f"Rows: {ds.Rows}, Columns: {ds.Columns}") # Apply windowing for display (CT/MRI) if hasattr(ds, 'WindowCenter') and hasattr(ds, 'WindowWidth'): from pydicom.pixels import apply_voi_lut # pydicom 3.x location windowed_image = apply_voi_lut(pixel_array, ds) else: windowed_image = pixel_array # Display image plt.imshow(windowed_image, cmap='gray') plt.title(f"{ds.Modality} - {ds.StudyDescription}") plt.axis('off') plt.show()
Working with color images:
python# RGB images have shape (rows, columns, 3) if ds.PhotometricInterpretation == 'RGB': rgb_image = ds.pixel_array plt.imshow(rgb_image) elif ds.PhotometricInterpretation == 'YBR_FULL': from pydicom.pixels import convert_color_space # pydicom 3.x location rgb_image = convert_color_space(ds.pixel_array, 'YBR_FULL', 'RGB') plt.imshow(rgb_image)
Multi-frame images (videos/series):
python# For multi-frame DICOM files if hasattr(ds, 'NumberOfFrames') and ds.NumberOfFrames > 1: frames = ds.pixel_array # Shape: (num_frames, rows, columns) print(f"Number of frames: {frames.shape[0]}") # Display specific frame plt.imshow(frames[0], cmap='gray')
Converting DICOM to Image Formats
Use the provided dicom_to_image.py script or convert manually:
pythonfrom PIL import Image import pydicom import numpy as np ds = pydicom.dcmread('input.dcm') pixel_array = ds.pixel_array # Normalize to 0-255 range if pixel_array.dtype != np.uint8: pixel_array = ((pixel_array - pixel_array.min()) / (pixel_array.max() - pixel_array.min()) * 255).astype(np.uint8) # Save as PNG image = Image.fromarray(pixel_array) image.save('output.png')
Use the script: python scripts/dicom_to_image.py input.dcm output.png
Modifying Metadata
Modify DICOM data elements:
pythonimport pydicom from datetime import datetime ds = pydicom.dcmread('input.dcm') # Modify existing elements ds.PatientName = "Doe^John" ds.StudyDate = datetime.now().strftime('%Y%m%d') ds.StudyDescription = "Modified Study" # Add new elements ds.SeriesNumber = 1 ds.SeriesDescription = "New Series" # Remove elements if hasattr(ds, 'PatientComments'): delattr(ds, 'PatientComments') # Or using del if 'PatientComments' in ds: del ds.PatientComments # Save modified file ds.save_as('modified.dcm')
Anonymizing DICOM Files
Remove or replace patient identifiable information:
pythonimport pydicom from datetime import datetime ds = pydicom.dcmread('input.dcm') # Tags commonly containing PHI (Protected Health Information) tags_to_anonymize = [ 'PatientName', 'PatientID', 'PatientBirthDate', 'PatientSex', 'PatientAge', 'PatientAddress', 'InstitutionName', 'InstitutionAddress', 'ReferringPhysicianName', 'PerformingPhysicianName', 'OperatorsName', 'StudyDescription', 'SeriesDescription', ] # Remove or replace sensitive data for tag in tags_to_anonymize: if hasattr(ds, tag): if tag in ['PatientName', 'PatientID']: setattr(ds, tag, 'ANONYMOUS') elif tag == 'PatientBirthDate': setattr(ds, tag, '19000101') else: delattr(ds, tag) # Vendor private tags frequently carry identifiers ds.remove_private_tags() # Replace dates consistently (e.g. one per-patient offset) to keep intervals meaningful if hasattr(ds, 'StudyDate'): ds.StudyDate = '20000101' ds.PatientIdentityRemoved = 'YES' # Keep pixel data intact ds.save_as('anonymized.dcm')
Use the provided script: python scripts/anonymize_dicom.py input.dcm output.dcm
A tag list like this is a starting point, not a complete de-identification. Identifiers also hide in other date/time elements, UIDs, accession numbers, nested sequences, free-text fields, and text burned into the pixels (check BurnedInAnnotation and inspect ultrasound/secondary-capture images). For data leaving your institution, apply a profile based on DICOM PS3.15 Annex E (Basic Application Level Confidentiality Profile) with a dedicated tool such as deid (pydicom project) or dicognito, and have the output reviewed before release — the same standard your IRB or data use agreement will expect.
Writing DICOM Files
Create DICOM files from scratch:
pythonimport pydicom from pydicom.dataset import Dataset, FileDataset, FileMetaDataset from datetime import datetime import numpy as np # Create file meta information (use FileMetaDataset, not a bare Dataset) file_meta = FileMetaDataset() file_meta.MediaStorageSOPClassUID = pydicom.uid.CTImageStorage file_meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid() file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRLittleEndian # Create the FileDataset instance ds = FileDataset('new_dicom.dcm', {}, file_meta=file_meta, preamble=b"\0" * 128) # Add required DICOM elements ds.PatientName = "Test^Patient" ds.PatientID = "123456" ds.Modality = "CT" ds.StudyDate = datetime.now().strftime('%Y%m%d') ds.StudyTime = datetime.now().strftime('%H%M%S') ds.ContentDate = ds.StudyDate ds.ContentTime = ds.StudyTime # Add image-specific elements ds.SamplesPerPixel = 1 ds.PhotometricInterpretation = "MONOCHROME2" ds.Rows = 512 ds.Columns = 512 ds.BitsAllocated = 16 ds.BitsStored = 16 ds.HighBit = 15 ds.PixelRepresentation = 0 # Create pixel data (pydicom >= 3.0 alternative for the block above and this one: # ds.set_pixel_data(pixel_array, photometric_interpretation="MONOCHROME2", bits_stored=12)) pixel_array = np.random.randint(0, 4096, (512, 512), dtype=np.uint16) ds.PixelData = pixel_array.tobytes() # Add required UIDs ds.SOPClassUID = pydicom.uid.CTImageStorage ds.SOPInstanceUID = file_meta.MediaStorageSOPInstanceUID ds.SeriesInstanceUID = pydicom.uid.generate_uid() ds.StudyInstanceUID = pydicom.uid.generate_uid() # Save the file ds.save_as('new_dicom.dcm')
Compression and Decompression
Handle compressed DICOM files:
pythonimport pydicom # Read compressed DICOM file ds = pydicom.dcmread('compressed.dcm') # Check transfer syntax print(f"Transfer Syntax: {ds.file_meta.TransferSyntaxUID}") print(f"Transfer Syntax Name: {ds.file_meta.TransferSyntaxUID.name}") # Decompress and save as uncompressed (Transfer Syntax becomes Explicit VR LE) ds.decompress() ds.save_as('uncompressed.dcm', enforce_file_format=True) # Or compress (RLE Lossless works with numpy alone) ds_uncompressed = pydicom.dcmread('uncompressed.dcm') ds_uncompressed.compress(pydicom.uid.RLELossless) ds_uncompressed.save_as('compressed_rle.dcm')
pydicom 3.0 can encode only RLE Lossless (native, pylibjpeg-rle, or gdcm), JPEG-LS Lossless/Near-Lossless (pyjpegls), and JPEG 2000 Lossless/lossy (pylibjpeg-openjpeg). JPEG Baseline and JPEG Lossless are decode-only — compress() raises NotImplementedError for them.
Common transfer syntaxes:
ExplicitVRLittleEndian- Uncompressed, most commonJPEGBaseline8Bit- JPEG lossy compressionJPEGLossless- JPEG lossless compressionJPEG2000Lossless- JPEG 2000 losslessRLELossless- Run-Length Encoding lossless
See references/transfer_syntaxes.md for complete list.
Working with DICOM Sequences
Handle nested data structures:
pythonimport pydicom ds = pydicom.dcmread('file.dcm') # Access sequences if 'ReferencedStudySequence' in ds: for item in ds.ReferencedStudySequence: print(f"Referenced SOP Instance UID: {item.ReferencedSOPInstanceUID}") # Create a sequence from pydicom.sequence import Sequence sequence_item = Dataset() sequence_item.ReferencedSOPClassUID = pydicom.uid.CTImageStorage sequence_item.ReferencedSOPInstanceUID = pydicom.uid.generate_uid() ds.ReferencedImageSequence = Sequence([sequence_item])
Processing DICOM Series
Work with multiple related DICOM files:
pythonimport pydicom import numpy as np from pathlib import Path # Read all DICOM files in a directory dicom_dir = Path('dicom_series/') slices = [] for file_path in dicom_dir.glob('*.dcm'): ds = pydicom.dcmread(file_path) slices.append(ds) # Sort by slice location or instance number slices.sort(key=lambda x: float(x.ImagePositionPatient[2])) # Or: slices.sort(key=lambda x: int(x.InstanceNumber)) # Create 3D volume volume = np.stack([s.pixel_array for s in slices]) print(f"Volume shape: {volume.shape}") # (num_slices, rows, columns) # Get spacing information for proper scaling pixel_spacing = slices[0].PixelSpacing # [row_spacing, col_spacing] slice_thickness = slices[0].SliceThickness print(f"Voxel size: {pixel_spacing[0]}x{pixel_spacing[1]}x{slice_thickness} mm")
Helper Scripts
This skill includes utility scripts in the scripts/ directory:
anonymize_dicom.py
Anonymize DICOM files by removing or replacing Protected Health Information (PHI).
bashpython scripts/anonymize_dicom.py input.dcm output.dcm
dicom_to_image.py
Convert DICOM files to common image formats (PNG, JPEG, TIFF).
bashpython scripts/dicom_to_image.py input.dcm output.png python scripts/dicom_to_image.py input.dcm output.jpg --format JPEG
extract_metadata.py
Extract and display DICOM metadata in a readable format.
bashpython scripts/extract_metadata.py file.dcm python scripts/extract_metadata.py file.dcm --output metadata.txt
Reference Materials
Detailed reference information is available in the references/ directory:
- common_tags.md: Comprehensive list of commonly used DICOM tags organized by category (Patient, Study, Series, Image, etc.)
- transfer_syntaxes.md: Complete reference of DICOM transfer syntaxes and compression formats
Common Issues and Solutions
Issue: "Unable to decode pixel data"
- Solution: Install additional compression handlers:
uv pip install pylibjpeg pylibjpeg-libjpeg python-gdcm
Issue: "AttributeError" when accessing tags
- Solution: Check if attribute exists with
hasattr(ds, 'AttributeName')or useds.get('AttributeName', default)
Issue: Incorrect image display (too dark/bright)
- Solution: Apply VOI LUT windowing:
apply_voi_lut(pixel_array, ds)or manually adjust withWindowCenterandWindowWidth
Issue: Memory issues with large series
- Solution: Process files iteratively, use memory-mapped arrays, or downsample images
Best Practices
- Always check for required attributes before accessing them using
hasattr()orget() - Preserve original encoding when modifying files: plain
ds.save_as(path)keeps the source layout (pydicom 3.x makes this the default); passenforce_file_format=Trueonly when you need a fully standard-compliant File Format output - Use Transfer Syntax UIDs to understand compression format before processing pixel data
- Handle exceptions when reading files from untrusted sources
- Apply proper windowing (VOI LUT) for medical image visualization
- Maintain spatial information (pixel spacing, slice thickness) when processing 3D volumes
- Verify anonymization thoroughly before sharing medical data (private tags, burned-in text, UIDs, and dates included)
- Use UIDs correctly - generate new UIDs when creating new instances, preserve them when modifying
Documentation
Official pydicom documentation (stable = current 3.x release): https://pydicom.github.io/pydicom/stable/
- User Guide: https://pydicom.github.io/pydicom/stable/guides/user/index.html
- Tutorials: https://pydicom.github.io/pydicom/stable/tutorials/index.html
- API Reference: https://pydicom.github.io/pydicom/stable/reference/index.html
- v3.0 release notes (API migration): https://pydicom.github.io/pydicom/stable/release_notes/v3.0.0.html
Part of the AlterLab Academic Skills suite.


