alterlab-pydicom — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited alterlab-pydicom (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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.
Use this skill when working with:
| 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 |
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:
uv 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 visualizationFor handling compressed DICOM files, additional packages may be needed:
uv 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/decode3.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 for convert_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).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.
Read a DICOM file using pydicom.dcmread():
import 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 a Dataset objectds.PatientName) or tag notation (e.g., ds[0x0010, 0x0010])ds.file_meta to access file metadata like Transfer Syntax UIDgetattr(ds, 'AttributeName', default_value) or hasattr(ds, 'AttributeName')Extract and manipulate image data from DICOM files:
import 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:
# 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):
# 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')Use the provided dicom_to_image.py script or convert manually:
from 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
Modify DICOM data elements:
import 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')Remove or replace patient identifiable information:
import 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.
Create DICOM files from scratch:
import 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')Handle compressed DICOM files:
import 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 losslessSee references/transfer_syntaxes.md for complete list.
Handle nested data structures:
import 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])Work with multiple related DICOM files:
import 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")This skill includes utility scripts in the scripts/ directory:
Anonymize DICOM files by removing or replacing Protected Health Information (PHI).
python scripts/anonymize_dicom.py input.dcm output.dcmConvert DICOM files to common image formats (PNG, JPEG, TIFF).
python scripts/dicom_to_image.py input.dcm output.png
python scripts/dicom_to_image.py input.dcm output.jpg --format JPEGExtract and display DICOM metadata in a readable format.
python scripts/extract_metadata.py file.dcm
python scripts/extract_metadata.py file.dcm --output metadata.txtDetailed reference information is available in the references/ directory:
Issue: "Unable to decode pixel data"
uv pip install pylibjpeg pylibjpeg-libjpeg python-gdcmIssue: "AttributeError" when accessing tags
hasattr(ds, 'AttributeName') or use ds.get('AttributeName', default)Issue: Incorrect image display (too dark/bright)
apply_voi_lut(pixel_array, ds) or manually adjust with WindowCenter and WindowWidthIssue: Memory issues with large series
hasattr() or get()ds.save_as(path) keeps the source layout (pydicom 3.x makes this the default); pass enforce_file_format=True only when you need a fully standard-compliant File Format outputOfficial pydicom documentation (stable = current 3.x release): https://pydicom.github.io/pydicom/stable/
Part of the AlterLab Academic Skills suite.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.