pydicom DICOM Toolkit
davila7/claude-code-templates
Reads, edits, anonymizes and converts DICOM medical imaging files with pydicom, including pixel data extraction and compressed transfer syntaxes.
Pure Python DICOM for medical imaging (CT, MRI, X-ray, ultrasound).
$ npx skills add jaechang-hits/SciAgent-Skills --skill pydicom-medical-imaging -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install jaechang-hits/SciAgent-Skills pydicom-medical-imaging --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/jaechang-hits/SciAgent-Skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/medical-imaging/pydicom-medical-imaging .claude/skills/pydicom-medical-imaging && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "pydicom-medical-imaging" agent skill from https://github.com/jaechang-hits/SciAgent-Skills/tree/main/skills/medical-imaging/pydicom-medical-imaging into .claude/skills/pydicom-medical-imaging/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "pydicom-medical-imaging", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/jaechang-hits/SciAgent-Skills/tree/main/skills/medical-imaging/pydicom-medical-imagingType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add jaechang-hits/SciAgent-Skills --skill pydicom-medical-imaging -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install jaechang-hits/SciAgent-Skills pydicom-medical-imaging --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/jaechang-hits/SciAgent-Skills.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/medical-imaging/pydicom-medical-imaging .agents/skills/pydicom-medical-imaging && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "pydicom-medical-imaging" agent skill from https://github.com/jaechang-hits/SciAgent-Skills/tree/main/skills/medical-imaging/pydicom-medical-imaging into .agents/skills/pydicom-medical-imaging/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "pydicom-medical-imaging", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add jaechang-hits/SciAgent-Skills --skill pydicom-medical-imaging -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install jaechang-hits/SciAgent-Skills pydicom-medical-imaging --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/jaechang-hits/SciAgent-Skills.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/medical-imaging/pydicom-medical-imaging .cursor/skills/pydicom-medical-imaging && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "pydicom-medical-imaging" agent skill from https://github.com/jaechang-hits/SciAgent-Skills/tree/main/skills/medical-imaging/pydicom-medical-imaging into .cursor/skills/pydicom-medical-imaging/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "pydicom-medical-imaging", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/jaechang-hits/SciAgent-Skills.git --path skills/medical-imaging/pydicom-medical-imaging--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add jaechang-hits/SciAgent-Skills --skill pydicom-medical-imaging -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install jaechang-hits/SciAgent-Skills pydicom-medical-imaging --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/jaechang-hits/SciAgent-Skills.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/medical-imaging/pydicom-medical-imaging .gemini/skills/pydicom-medical-imaging && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "pydicom-medical-imaging" agent skill from https://github.com/jaechang-hits/SciAgent-Skills/tree/main/skills/medical-imaging/pydicom-medical-imaging into .gemini/skills/pydicom-medical-imaging/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "pydicom-medical-imaging", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install jaechang-hits/SciAgent-Skills pydicom-medical-imagingInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add jaechang-hits/SciAgent-Skills --skill pydicom-medical-imaging -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/jaechang-hits/SciAgent-Skills.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/medical-imaging/pydicom-medical-imaging .github/skills/pydicom-medical-imaging && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "pydicom-medical-imaging" agent skill from https://github.com/jaechang-hits/SciAgent-Skills/tree/main/skills/medical-imaging/pydicom-medical-imaging into .github/skills/pydicom-medical-imaging/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "pydicom-medical-imaging", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add jaechang-hits/SciAgent-Skills --skill pydicom-medical-imaging -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install jaechang-hits/SciAgent-Skills pydicom-medical-imaging --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/jaechang-hits/SciAgent-Skills.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/medical-imaging/pydicom-medical-imaging .opencode/skills/pydicom-medical-imaging && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "pydicom-medical-imaging" agent skill from https://github.com/jaechang-hits/SciAgent-Skills/tree/main/skills/medical-imaging/pydicom-medical-imaging into .opencode/skills/pydicom-medical-imaging/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "pydicom-medical-imaging", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
pydicom-medical-imagingPure Python DICOM for medical imaging (CT, MRI, X-ray, ultrasound).
Pydicom Medical Imaging is an agent skill from jaechang-hits/SciAgent-Skills. Pure Python DICOM for medical imaging (CT, MRI, X-ray, ultrasound). Read/write DICOM, pixels as NumPy, edit tags, windowing (VOI LUT), PHI anonymization, build DICOM, series→3D volumes. Use histolab for WSI pathology; nibabel for NIfTI.
Its SKILL.md is about 7.5k tokens, which your agent loads only when the skill is triggered. The skill folder holds 2 other files, including reference files (for example `references/dicom_standards.md`).
It sits in Research & Science, covering Clinical and healthcare research. It works with NumPy and Python. The repository describes itself as: 197 bioinformatics & life science skills for Claude Code and AI agents — BixBench 92.0% accuracy. RNA-seq, single-cell, drug discovery, proteomics, and more. Powers OmicsHorizon. The licence is MIT.
7 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit 82c862c. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
pipFrom the folder's file list and the shell code blocks in SKILL.md.
Links to these hosts (documentation or services it may open):
github.compydicom.github.iodicom.innolitics.comFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Pydicom Medical Imaging loads about 7.5k tokens when it runs, and up to ~11k if it reads all its reference files. Until then it costs about 65 tokens; SKILL.md has 1,547 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from jaechang-hits/SciAgent-Skills at commit 82c862c, republished under its MIT licence (© jaechang-hits). 1,547 words, ~7,547 tokens.
.claude/skills/pydicom-medical-imaging/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.Pydicom is a pure Python library for reading, writing, and modifying DICOM (Digital Imaging and Communications in Medicine) files. It provides access to DICOM metadata tags and pixel data as NumPy arrays, supporting CT, MRI, X-ray, ultrasound, and other medical imaging modalities. The library handles compressed and uncompressed transfer syntaxes with optional codec plugins.
histolab-wsi-processing insteadnibabel insteadpydicom, numpy, pillowpylibjpeg + pylibjpeg-libjpeg (JPEG), pylibjpeg-openjpeg (JPEG 2000), python-gdcm (most formats)pip install pydicom numpy pillow
# Optional: compression codec handlers (install as needed)
pip install pylibjpeg pylibjpeg-libjpeg # JPEG Baseline/Lossless
pip install pylibjpeg-openjpeg # JPEG 2000
pip install python-gdcm # Comprehensive codec supportimport pydicom
import numpy as np
# Read a DICOM file
ds = pydicom.dcmread("scan.dcm")
# Access metadata
print(f"Patient: {ds.PatientName}, Modality: {ds.Modality}")
print(f"Size: {ds.Rows}x{ds.Columns}, Bits: {ds.BitsAllocated}")
# Extract pixel data as NumPy array
pixels = ds.pixel_array
print(f"Pixel array shape: {pixels.shape}, dtype: {pixels.dtype}")
# Apply windowing for display (CT/MR)
from pydicom.pixel_data_handlers.util import apply_voi_lut
display = apply_voi_lut(pixels, ds)
print(f"Windowed range: [{display.min()}, {display.max()}]")Read DICOM files and access metadata using attribute names or tag notation.
import pydicom
# Read DICOM file (defer_size delays loading large elements)
ds = pydicom.dcmread("scan.dcm")
ds_lazy = pydicom.dcmread("large.dcm", defer_size="1 KB")
# Access by attribute name (standard DICOM keywords)
print(f"Patient Name: {ds.PatientName}")
print(f"Study Date: {ds.StudyDate}")
print(f"Modality: {ds.Modality}")
print(f"Image Size: {ds.Rows} x {ds.Columns}")
# Access by tag number (group, element)
print(f"Patient ID: {ds[0x0010, 0x0020].value}")
# Safe access with getattr (avoids AttributeError)
slice_thick = getattr(ds, 'SliceThickness', 'N/A')
print(f"Slice Thickness: {slice_thick}")
# Iterate all elements
for elem in ds:
if elem.VR != 'SQ': # Skip sequences
print(f" {elem.tag} {elem.keyword}: {elem.value}")# Read DICOM directory (DICOMDIR)
from pydicom.filereader import dcmread
dicomdir = pydicom.dcmread("DICOMDIR")
for record in dicomdir.DirectoryRecordSequence:
if record.DirectoryRecordType == "IMAGE":
ref_file = record.ReferencedFileID
# ref_file is a list of path components
print(f"Image file: {'/'.join(ref_file)}")Extract pixel data as NumPy arrays with support for grayscale, color, windowing, and multi-frame.
import pydicom
import numpy as np
from pydicom.pixel_data_handlers.util import apply_voi_lut, apply_modality_lut
ds = pydicom.dcmread("ct_scan.dcm")
# Basic pixel extraction
pixels = ds.pixel_array # NumPy ndarray
print(f"Shape: {pixels.shape}, dtype: {pixels.dtype}")
# Apply Modality LUT (rescale to Hounsfield Units for CT)
hu_pixels = apply_modality_lut(pixels, ds)
print(f"HU range: [{hu_pixels.min()}, {hu_pixels.max()}]")
# Apply VOI LUT (windowing for display contrast)
display = apply_voi_lut(hu_pixels, ds)
print(f"Display range: [{display.min()}, {display.max()}]")
# Manual windowing (when VOI LUT metadata is absent)
center, width = 40, 400 # Soft tissue window
lower = center - width / 2
upper = center + width / 2
windowed = np.clip(hu_pixels, lower, upper)
print(f"Manual window [{lower}, {upper}]")# Color images (ultrasound, photos) — handle YBR color space
import pydicom
ds = pydicom.dcmread("ultrasound.dcm")
pixels = ds.pixel_array
print(f"Color shape: {pixels.shape}") # (rows, cols, 3)
# Convert YBR to RGB if needed
photo_interp = ds.PhotometricInterpretation
if "YBR" in photo_interp:
from pydicom.pixel_data_handlers.util import convert_color_space
rgb = convert_color_space(pixels, photo_interp, "RGB")
print(f"Converted {photo_interp} -> RGB")
# Multi-frame (cine/video DICOM)
ds_multi = pydicom.dcmread("cine.dcm")
frames = ds_multi.pixel_array # Shape: (num_frames, rows, cols)
print(f"Frames: {frames.shape[0]}, Frame size: {frames.shape[1:]}")Convert DICOM pixel data to standard image formats for visualization and export.
import pydicom
import numpy as np
from PIL import Image
from pydicom.pixel_data_handlers.util import apply_voi_lut
ds = pydicom.dcmread("scan.dcm")
pixels = ds.pixel_array
# Apply windowing
display = apply_voi_lut(pixels, ds)
# Normalize to 8-bit for standard image formats
if display.dtype != np.uint8:
dmin, dmax = display.min(), display.max()
if dmax > dmin:
normalized = ((display - dmin) / (dmax - dmin) * 255).astype(np.uint8)
else:
normalized = np.zeros_like(display, dtype=np.uint8)
else:
normalized = display
# Save as PNG
img = Image.fromarray(normalized)
img.save("output.png")
print(f"Saved output.png ({img.size[0]}x{img.size[1]})")
# Save as JPEG with quality control
img.save("output.jpg", quality=95)# Batch conversion: directory of DICOM files to PNG
import pydicom
import numpy as np
from PIL import Image
from pathlib import Path
from pydicom.pixel_data_handlers.util import apply_voi_lut
def dicom_to_image(dcm_path, out_path, fmt="PNG"):
"""Convert a single DICOM file to standard image format."""
ds = pydicom.dcmread(str(dcm_path))
pixels = apply_voi_lut(ds.pixel_array, ds)
dmin, dmax = float(pixels.min()), float(pixels.max())
if dmax > dmin:
norm = ((pixels - dmin) / (dmax - dmin) * 255).astype(np.uint8)
else:
norm = np.zeros_like(pixels, dtype=np.uint8)
Image.fromarray(norm).save(str(out_path))
dcm_dir = Path("dicom_files/")
out_dir = Path("images/")
out_dir.mkdir(exist_ok=True)
for dcm_file in sorted(dcm_dir.glob("*.dcm")):
out_file = out_dir / f"{dcm_file.stem}.png"
dicom_to_image(dcm_file, out_file)
print(f"Converted: {dcm_file.name} -> {out_file.name}")Modify DICOM attributes and remove Protected Health Information for de-identification.
import pydicom
from pydicom.uid import generate_uid
ds = pydicom.dcmread("original.dcm")
# Modify attributes
ds.PatientName = "Anonymous"
ds.PatientID = "ANON001"
ds.InstitutionName = "Research Lab"
# Add new attribute
ds.add_new(0x00081030, 'LO', 'Research Study') # Study Description
# Delete attribute
if 'PatientBirthDate' in ds:
del ds.PatientBirthDate
# Generate new UIDs for de-identification
ds.StudyInstanceUID = generate_uid()
ds.SeriesInstanceUID = generate_uid()
ds.SOPInstanceUID = generate_uid()
# Save modified file (preserves original)
ds.save_as("modified.dcm")
print(f"Saved modified.dcm with new UIDs")# PHI anonymization: remove patient-identifying tags (DICOM PS3.15 Annex E)
import pydicom
from pydicom.uid import generate_uid
PHI_TAGS = [ # Core set — extend per institutional policy
'PatientName', 'PatientID', 'PatientBirthDate', 'PatientSex',
'PatientAge', 'PatientWeight', 'PatientAddress',
'OtherPatientIDs', 'OtherPatientNames',
'InstitutionName', 'InstitutionAddress',
'ReferringPhysicianName', 'PerformingPhysicianName',
'OperatorsName', 'StudyID', 'AccessionNumber',
]
def anonymize_dicom(ds, prefix="ANON"):
"""Remove PHI tags and assign anonymous identifiers."""
for tag in PHI_TAGS:
if hasattr(ds, tag): delattr(ds, tag)
ds.PatientName, ds.PatientID = f"{prefix}_Patient", f"{prefix}_ID"
ds.StudyInstanceUID = generate_uid()
ds.SeriesInstanceUID = generate_uid()
ds.SOPInstanceUID = generate_uid()
return ds
ds = pydicom.dcmread("patient_scan.dcm")
anonymize_dicom(ds, prefix="STUDY001").save_as("anonymized.dcm")
print("Anonymized: PHI tags removed, UIDs replaced")Create new DICOM files from NumPy arrays with proper metadata.
import pydicom, numpy as np, datetime
from pydicom.dataset import FileDataset, FileMetaDataset
from pydicom.uid import ExplicitVRLittleEndian, generate_uid
# File meta header
file_meta = FileMetaDataset()
file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.2' # CT Image Storage
file_meta.MediaStorageSOPInstanceUID = generate_uid()
file_meta.TransferSyntaxUID = ExplicitVRLittleEndian
# Dataset with required attributes
ds = FileDataset("new.dcm", {}, file_meta=file_meta, preamble=b"\x00" * 128)
ds.SOPClassUID = file_meta.MediaStorageSOPClassUID
ds.SOPInstanceUID = file_meta.MediaStorageSOPInstanceUID
ds.StudyInstanceUID, ds.SeriesInstanceUID = generate_uid(), generate_uid()
ds.Modality, ds.Manufacturer = 'CT', 'Research'
ds.is_little_endian, ds.is_implicit_VR = True, False
dt = datetime.datetime.now()
ds.ContentDate, ds.ContentTime = dt.strftime('%Y%m%d'), dt.strftime('%H%M%S.%f')
# Pixel data from NumPy array
pixels = np.random.randint(0, 4096, (512, 512), dtype=np.uint16)
ds.Rows, ds.Columns = pixels.shape
ds.BitsAllocated, ds.BitsStored, ds.HighBit = 16, 12, 11
ds.PixelRepresentation = 0 # Unsigned
ds.SamplesPerPixel, ds.PhotometricInterpretation = 1, 'MONOCHROME2'
ds.PixelData = pixels.tobytes()
ds.save_as("new.dcm")
print(f"Created DICOM: {ds.Rows}x{ds.Columns}, {ds.BitsStored}-bit")Load a DICOM series, sort by spatial position, and stack into a 3D NumPy array.
import pydicom
import numpy as np
from pathlib import Path
def load_dicom_series(series_dir):
"""Load and sort a DICOM series by slice position."""
dcm_files = []
for f in Path(series_dir).iterdir():
try:
ds = pydicom.dcmread(str(f))
dcm_files.append(ds)
except Exception:
continue # Skip non-DICOM files
if not dcm_files:
raise ValueError(f"No DICOM files found in {series_dir}")
# Sort by ImagePositionPatient (z-coordinate) or InstanceNumber
try:
dcm_files.sort(key=lambda x: float(x.ImagePositionPatient[2]))
except (AttributeError, IndexError):
dcm_files.sort(key=lambda x: int(x.InstanceNumber))
print(f"Loaded {len(dcm_files)} slices, "
f"Series: {getattr(dcm_files[0], 'SeriesDescription', 'N/A')}")
return dcm_files
def series_to_volume(dcm_files):
"""Stack sorted DICOM slices into a 3D NumPy array."""
from pydicom.pixel_data_handlers.util import apply_modality_lut
slices = []
for ds in dcm_files:
pixels = apply_modality_lut(ds.pixel_array, ds)
slices.append(pixels)
volume = np.stack(slices, axis=0)
# Calculate voxel spacing
pixel_spacing = dcm_files[0].PixelSpacing
if len(dcm_files) > 1:
try:
z0 = float(dcm_files[0].ImagePositionPatient[2])
z1 = float(dcm_files[1].ImagePositionPatient[2])
slice_spacing = abs(z1 - z0)
except (AttributeError, IndexError):
slice_spacing = float(getattr(dcm_files[0], 'SliceThickness', 1.0))
else:
slice_spacing = float(getattr(dcm_files[0], 'SliceThickness', 1.0))
spacing = (slice_spacing, float(pixel_spacing[0]), float(pixel_spacing[1]))
print(f"Volume shape: {volume.shape}, Spacing (z,y,x): {spacing} mm")
return volume, spacing
# Usage
dcm_files = load_dicom_series("ct_series/")
volume, spacing = series_to_volume(dcm_files)
print(f"HU range: [{volume.min()}, {volume.max()}]")DICOM organizes medical imaging data in a four-level hierarchy:
| Level | Key UID | Description |
|---|---|---|
| Patient | PatientID | A single individual |
| Study | StudyInstanceUID | One imaging session (may contain multiple modalities) |
| Series | SeriesInstanceUID | One acquisition sequence (e.g., T1-weighted MRI) |
| Instance (Image) | SOPInstanceUID | One image/frame (one DICOM file) |
Each DICOM file contains one Instance with metadata tags organized by group. Tags use (group, element) notation (e.g., (0010,0010) for PatientName).
Transfer Syntax defines how DICOM data is encoded (byte order, VR encoding, pixel compression):
| Transfer Syntax | UID | Compression | Handler Needed |
|---|---|---|---|
| Implicit VR Little Endian | 1.2.840.10008.1.2 | None | No |
| Explicit VR Little Endian | 1.2.840.10008.1.2.1 | None | No |
| Explicit VR Big Endian | 1.2.840.10008.1.2.2 | None | No |
| JPEG Baseline | 1.2.840.10008.1.2.4.50 | Lossy JPEG | pylibjpeg |
| JPEG Lossless | 1.2.840.10008.1.2.4.70 | Lossless JPEG | pylibjpeg |
| JPEG 2000 Lossless | 1.2.840.10008.1.2.4.90 | Lossless J2K | pylibjpeg-openjpeg |
| JPEG 2000 | 1.2.840.10008.1.2.4.91 | Lossy J2K | pylibjpeg-openjpeg |
| RLE Lossless | 1.2.840.10008.1.2.5 | RLE | pydicom (built-in) |
Check transfer syntax: ds.file_meta.TransferSyntaxUID. Install the appropriate handler before accessing pixel_array on compressed files.
Most commonly accessed tags (full catalog in references/dicom_standards.md):
| Tag | Keyword | VR | Description |
|---|---|---|---|
| (0008,0060) | Modality | CS | CT, MR, US, CR, DX, PT, NM |
| (0010,0010) | PatientName | PN | Patient's full name |
| (0010,0020) | PatientID | LO | Patient identifier |
| (0008,0020) | StudyDate | DA | Date of study (YYYYMMDD) |
| (0020,000D) | StudyInstanceUID | UI | Unique study identifier |
| (0020,0013) | InstanceNumber | IS | Image number in series |
| (0020,0032) | ImagePositionPatient | DS | x,y,z position (mm) |
| (0028,0010) | Rows | US | Image height in pixels |
| (0028,0011) | Columns | US | Image width in pixels |
| (0028,0030) | PixelSpacing | DS | Row,column spacing (mm) |
| (0028,1050) | WindowCenter | DS | Display window center |
| (0028,0004) | PhotometricInterpretation | CS | MONOCHROME1/2, RGB, YBR_FULL |
| (0028,0100) | BitsAllocated | US | 8 or 16 |
VR defines the data type for each element. Most common types (full table in references/dicom_standards.md):
| VR | Name | Python Type | Example |
|---|---|---|---|
| CS | Code String | str | "CT", "MR" |
| DA | Date | str | "20240115" |
| DS | Decimal String | DSfloat | "1.5" |
| IS | Integer String | IS | "42" |
| LO | Long String | str | "Study description" |
| PN | Person Name | PersonName | "Doe^John" |
| SQ | Sequence | Sequence | Nested datasets |
| UI | Unique Identifier | UID | "1.2.840..." |
| US | Unsigned Short | int | 512 |
Goal: Walk a directory of DICOM files, extract key metadata fields, and export to a CSV manifest.
import pydicom
import pandas as pd
from pathlib import Path
FIELDS = ['PatientID', 'Modality', 'StudyDate', 'SeriesDescription',
'StudyInstanceUID', 'SeriesInstanceUID', 'InstanceNumber',
'Rows', 'Columns', 'SliceThickness', 'BitsStored']
def extract_metadata(dcm_path):
"""Extract key metadata from a DICOM file."""
try:
ds = pydicom.dcmread(str(dcm_path), stop_before_pixels=True)
except Exception as e:
return {"file": str(dcm_path), "error": str(e)}
rec = {"file": str(dcm_path)}
for f in FIELDS:
rec[f] = str(getattr(ds, f, ''))
return rec
# Scan directory recursively
dicom_dir = Path("dicom_archive/")
records = [extract_metadata(f) for f in sorted(dicom_dir.rglob("*")) if f.is_file()]
df = pd.DataFrame(records)
df.to_csv("dicom_manifest.csv", index=False)
print(f"Extracted metadata from {len(df)} files")
print(f"Modalities: {df['Modality'].value_counts().to_dict()}")
print(f"Unique patients: {df['PatientID'].nunique()}")Goal: Load a CT series, build a 3D volume in Hounsfield Units, and display axial/sagittal/coronal views.
import pydicom, numpy as np, matplotlib.pyplot as plt
from pathlib import Path
from pydicom.pixel_data_handlers.util import apply_modality_lut
# Load and sort series by z-position
dcm_files = []
for f in sorted(Path("ct_series/").glob("*")):
try: dcm_files.append(pydicom.dcmread(str(f)))
except Exception: continue
dcm_files.sort(key=lambda x: float(x.ImagePositionPatient[2]))
# Stack into 3D volume (Hounsfield Units)
volume = np.stack([apply_modality_lut(ds.pixel_array, ds) for ds in dcm_files])
ps = dcm_files[0].PixelSpacing
z_sp = abs(float(dcm_files[1].ImagePositionPatient[2])
- float(dcm_files[0].ImagePositionPatient[2]))
print(f"Volume: {volume.shape}, spacing: {z_sp:.2f}x{float(ps[0]):.2f}x{float(ps[1]):.2f} mm")
# Display orthogonal views (soft tissue window)
vmin, vmax = -160, 240 # center=40, width=400
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
mid = [s // 2 for s in volume.shape]
axes[0].imshow(volume[mid[0]], cmap='gray', vmin=vmin, vmax=vmax)
axes[0].set_title(f"Axial (slice {mid[0]})")
axes[1].imshow(volume[:, mid[1], :], cmap='gray', vmin=vmin, vmax=vmax,
aspect=z_sp/float(ps[1]))
axes[1].set_title(f"Coronal")
axes[2].imshow(volume[:, :, mid[2]], cmap='gray', vmin=vmin, vmax=vmax,
aspect=z_sp/float(ps[0]))
axes[2].set_title(f"Sagittal")
for ax in axes: ax.axis('off')
plt.tight_layout()
plt.savefig("orthogonal_views.png", dpi=150, bbox_inches='tight')
print("Saved orthogonal_views.png")Goal: Anonymize all DICOM files in a directory, preserving series structure with new UIDs.
import pydicom
from pydicom.uid import generate_uid
from pathlib import Path
PHI_TAGS = [
'PatientName', 'PatientID', 'PatientBirthDate', 'PatientSex',
'PatientAge', 'PatientWeight', 'PatientAddress',
'OtherPatientIDs', 'OtherPatientNames',
'InstitutionName', 'InstitutionAddress',
'ReferringPhysicianName', 'PerformingPhysicianName',
'OperatorsName', 'PhysiciansOfRecord', 'StudyID', 'AccessionNumber',
]
uid_map = {} # Preserves study/series relationships across files
def get_mapped_uid(uid):
if uid not in uid_map: uid_map[uid] = generate_uid()
return uid_map[uid]
input_dir, output_dir = Path("original_dicoms/"), Path("anonymized_dicoms/")
output_dir.mkdir(exist_ok=True)
count, errors = 0, 0
for dcm_path in sorted(input_dir.rglob("*")):
if not dcm_path.is_file(): continue
try: ds = pydicom.dcmread(str(dcm_path))
except Exception: errors += 1; continue
for tag in PHI_TAGS:
if hasattr(ds, tag): delattr(ds, tag)
ds.PatientName, ds.PatientID = "ANONYMOUS", "ANON"
ds.StudyInstanceUID = get_mapped_uid(ds.StudyInstanceUID)
ds.SeriesInstanceUID = get_mapped_uid(ds.SeriesInstanceUID)
ds.SOPInstanceUID = generate_uid()
ds.save_as(str(output_dir / f"anon_{count:06d}.dcm"))
count += 1
print(f"Anonymized {count} files, {errors} errors, {len(uid_map)} UIDs mapped")| Parameter | Module | Default | Range / Options | Effect |
|---|---|---|---|---|
defer_size | dcmread | None | "1 KB", "1 MB", int bytes | Defer loading elements larger than size |
stop_before_pixels | dcmread | False | True/False | Skip pixel data loading (metadata only) |
force | dcmread | False | True/False | Force read even if missing DICOM preamble |
WindowCenter | Windowing | from file | Any numeric | Center of display window (HU for CT) |
WindowWidth | Windowing | from file | > 0 | Width of display window |
BitsAllocated | Writing | 16 | 8, 16, 32 | Bits allocated per pixel |
BitsStored | Writing | 12 | 1-BitsAllocated | Actual significant bits |
PixelRepresentation | Writing | 0 | 0 (unsigned), 1 (signed) | Pixel value signedness |
PhotometricInterpretation | Writing | "MONOCHROME2" | MONOCHROME1, MONOCHROME2, RGB, YBR_FULL | Color space |
TransferSyntaxUID | Writing | Explicit VR LE | See Transfer Syntax table | Encoding format |
Use stop_before_pixels=True for metadata-only operations: Avoids loading large pixel arrays when only reading tags. Dramatically faster for batch metadata extraction.
ds = pydicom.dcmread("scan.dcm", stop_before_pixels=True)Always use getattr() with defaults for optional tags: DICOM files vary widely in which tags are present. Direct attribute access raises AttributeError on missing tags.
# Good
thickness = getattr(ds, 'SliceThickness', None)
# Bad — will crash on files without SliceThickness
thickness = ds.SliceThicknessApply Modality LUT before windowing for CT data: Raw pixel values are stored values; apply apply_modality_lut() first to convert to Hounsfield Units, then apply_voi_lut() for display.
Install compression handlers before accessing compressed pixel data: Check ds.file_meta.TransferSyntaxUID and install the appropriate handler. Attempting pixel_array without the handler raises RuntimeError.
Generate new UIDs for every modified file: Never reuse original SOPInstanceUID after modifications. Use pydicom.uid.generate_uid() to ensure global uniqueness.
Use save_as() instead of overwriting originals: Always save to a new path to preserve original data. DICOM archives may have integrity checks that fail if originals are modified in-place.
Sort series by ImagePositionPatient for 3D reconstruction: InstanceNumber is not always reliable. ImagePositionPatient[2] (z-coordinate) gives correct physical ordering for axial CT/MR series.
When to use: Read compressed DICOM files or compress uncompressed ones.
import pydicom
ds = pydicom.dcmread("compressed.dcm")
ts = ds.file_meta.TransferSyntaxUID
# Check if compressed
print(f"Transfer Syntax: {ts}")
print(f"Compressed: {ts.is_compressed}")
# Decompress in-place (requires appropriate handler installed)
if ts.is_compressed:
ds.decompress()
print(f"Decompressed to {ds.file_meta.TransferSyntaxUID}")
# Access pixel data (works after decompression)
pixels = ds.pixel_array
print(f"Pixel shape: {pixels.shape}")When to use: Access nested data structures like referenced series, procedure codes, or protocol elements.
import pydicom
ds = pydicom.dcmread("structured.dcm")
# Access sequence elements (SQ VR = list of datasets)
if hasattr(ds, 'ReferencedStudySequence'):
for item in ds.ReferencedStudySequence:
print(f" Referenced Study: {item.ReferencedSOPInstanceUID}")
# Access procedure code sequence
if hasattr(ds, 'ProcedureCodeSequence'):
for code in ds.ProcedureCodeSequence:
print(f" Procedure: {code.CodeMeaning} ({code.CodeValue})")
# Create a sequence when writing DICOM
from pydicom.dataset import Dataset
from pydicom.sequence import Sequence
ref_item = Dataset()
ref_item.ReferencedSOPClassUID = '1.2.840.10008.5.1.4.1.1.2'
ref_item.ReferencedSOPInstanceUID = pydicom.uid.generate_uid()
ds.ReferencedImageSequence = Sequence([ref_item])When to use: Extract individual frames from cine, video, or enhanced multi-frame DICOM files.
import pydicom
import numpy as np
from PIL import Image
from pathlib import Path
ds = pydicom.dcmread("multiframe.dcm")
frames = ds.pixel_array # Shape: (num_frames, rows, cols) or (num_frames, rows, cols, 3)
num_frames = frames.shape[0]
print(f"Total frames: {num_frames}, Frame size: {frames.shape[1:]}")
# Extract all frames as images
out_dir = Path("frames/")
out_dir.mkdir(exist_ok=True)
for i in range(num_frames):
frame = frames[i]
# Normalize to uint8
if frame.dtype != np.uint8:
fmin, fmax = frame.min(), frame.max()
if fmax > fmin:
frame = ((frame - fmin) / (fmax - fmin) * 255).astype(np.uint8)
else:
frame = np.zeros_like(frame, dtype=np.uint8)
Image.fromarray(frame).save(out_dir / f"frame_{i:04d}.png")
print(f"Extracted {num_frames} frames to {out_dir}/")| Problem | Cause | Solution |
|---|---|---|
RuntimeError: No available image handler | Compressed transfer syntax without codec | Install appropriate handler: pip install pylibjpeg pylibjpeg-libjpeg (JPEG), pip install pylibjpeg-openjpeg (JPEG 2000), or pip install python-gdcm (all formats) |
AttributeError: 'Dataset' has no attribute 'X' | Optional tag not present in file | Use getattr(ds, 'X', default) or check 'X' in ds before access |
InvalidDicomError: File is missing DICOM preamble | Non-standard DICOM file or non-DICOM file | Try pydicom.dcmread(path, force=True) to skip preamble check |
| Wrong pixel values (no negative HU) | Missing Modality LUT application | Apply apply_modality_lut(pixels, ds) before analysis — raw stored values differ from actual HU |
| Image appears inverted (bright/dark swapped) | MONOCHROME1 photometric interpretation | Check ds.PhotometricInterpretation; invert with np.max(pixels) - pixels for MONOCHROME1 |
MemoryError loading large series | All slices loaded into memory at once | Process slices in batches; use stop_before_pixels=True for metadata scans; use defer_size for large elements |
| Inconsistent slice ordering in 3D volume | Sorted by InstanceNumber instead of position | Sort by ImagePositionPatient[2] for correct physical ordering |
| Garbled text in PatientName | Character encoding mismatch | Check SpecificCharacterSet tag; pydicom auto-decodes but some files have incorrect charset declarations |
TypeError when setting PixelData | Wrong byte format for pixel array | Use pixel_array.tobytes() and ensure dtype matches BitsAllocated (uint16 for 16-bit) |
Consolidated DICOM tag catalogs and transfer syntax reference. Complete tag tables for patient demographics, study/series identification, image geometry, pixel data encoding, windowing parameters, and modality-specific tags (CT Hounsfield parameters, MR sequence parameters, equipment identification, timing). Full transfer syntax UID table with compression types and handler installation. Value Representation (VR) type reference.
© jaechang-hits, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 1 other file (references) in skills/medical-imaging/pydicom-medical-imaging of jaechang-hits/SciAgent-Skills.
Open the folder on GitHubat commit 82c862c
We found 1 copy of this SKILL.md (exact, near-identical or edited) in other folders, from 1 other GitHub owner. This page covers the copy in jaechang-hits/SciAgent-Skills, which our catalogue first saw on October 7, 2026.
Pydicom Medical Imaging next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Pydicom Medical Imaging this skilljaechang-hits/SciAgent-Skills | 374 | 1 repos | ~7.5k | Automated safety check: Pass | MIT | |
| pydicom DICOM Toolkitdavila7/claude-code-templates | 33k | 11 repos | ~3.3k | Automated safety check: Pass | MIT | |
| 13C Metabolic Flux AnalysisK-Dense-AI/scientific-agent-skills | 48k | 1 repos | ~3.2k | Automated safety check: Pass | MIT | |
| Cantera Ignition DelayK-Dense-AI/scientific-agent-skills | 48k | 1 repos | ~2.2k | Automated safety check: Pass | MIT | |
| Histolab Whole Slide Image Tilingdavila7/claude-code-templates | 33k | 11 repos | ~5.1k | Automated safety check: Pass | MIT | |
| NeuroKit2 Biosignal Processingdavila7/claude-code-templates | 33k | 11 repos | ~3k | Automated safety check: Pass | MIT |
davila7/claude-code-templates
Reads, edits, anonymizes and converts DICOM medical imaging files with pydicom, including pixel data extraction and compressed transfer syntaxes.
K-Dense-AI/scientific-agent-skills
Estimates reaction fluxes inside cells from steady-state carbon-13 labeling data with a bundled mfapy-based solver, and reports which fluxes the data pin down.
K-Dense-AI/scientific-agent-skills
Runs Cantera constant-volume or constant-pressure ignition simulations and reports temperature-based ignition delay with mechanism provenance and checks.
davila7/claude-code-templates
Processes digital pathology whole slide images with histolab: tissue detection, mask creation, tile extraction and dataset preparation for deep learning.
davila7/claude-code-templates
Processes physiological signals with NeuroKit2 in Python: ECG, PPG, EEG, EDA, respiration, EMG and EOG, including HRV, events and complexity measures.
davila7/claude-code-templates
Builds machine learning pipelines on clinical data with PyHealth: EHR datasets, prediction tasks, medical code mapping, healthcare models and evaluation.
jaechang-hits/SciAgent-Skills
NEB-IRC activation energy pipeline for reaction barriers using GFN2-xTB and pysisyphus.
jaechang-hits/SciAgent-Skills
3Dmol.js WebGL molecular visualization emitted as self-contained HTML.
jaechang-hits/SciAgent-Skills
Constraint-based (COBRA) analysis of genome-scale metabolic models: FBA, FVA, knockouts, flux sampling, production envelopes, gapfilling, media optimization.
jaechang-hits/SciAgent-Skills
Read, write, and edit ChemDraw CDX/CDXML files with RDKit's rdkit.Chem.rdChemDraw plus direct XML editing, always paired with a rendered PNG.
jaechang-hits/SciAgent-Skills
Programmatic PubMed access via NCBI E-utilities REST API. An agent skill from jaechang-hits/SciAgent-Skills.
jaechang-hits/SciAgent-Skills
Scaffold a new SciAgent-Skills entry. An agent skill from jaechang-hits/SciAgent-Skills.
Categories
Pure Python DICOM for medical imaging (CT, MRI, X-ray, ultrasound). Pydicom Medical Imaging is an agent skill from jaechang-hits/SciAgent-Skills. Pure Python DICOM for medical imaging (CT, MRI, X-ray, ultrasound).
Pydicom Medical Imaging fits situations like: tasks that involve Clinical and healthcare research.
Run `npx skills add jaechang-hits/SciAgent-Skills --skill pydicom-medical-imaging -a claude-code`. Or copy the skill folder (skills/medical-imaging/pydicom-medical-imaging in jaechang-hits/SciAgent-Skills) into .claude/skills/pydicom-medical-imaging in your project. Claude Code loads it when a task matches its description.
Run `npx skills add jaechang-hits/SciAgent-Skills --skill pydicom-medical-imaging -a codex`. Or copy the skill folder (skills/medical-imaging/pydicom-medical-imaging in jaechang-hits/SciAgent-Skills) into .agents/skills/pydicom-medical-imaging in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add jaechang-hits/SciAgent-Skills --skill pydicom-medical-imaging -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/pydicom-medical-imaging, .gemini/skills/pydicom-medical-imaging, .github/skills/pydicom-medical-imaging and .opencode/skills/pydicom-medical-imaging in your project.
Going by SKILL.md and its folder, Pydicom Medical Imaging needs the command-line tools its instructions call (pip). Our summary lists: Python 3.
SKILL.md names 3 domains. As links in the text: github.com, pydicom.github.io and dicom.innolitics.com. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Pydicom Medical Imaging is published under the MIT licence (declared in SKILL.md). It allows redistribution, so the full SKILL.md is shown on this page.
About 7.5k tokens (SKILL.md is roughly 30k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 3.1k tokens, read only when the agent opens those files.
Skills that share tags, products or a category with Pydicom Medical Imaging: pydicom DICOM Toolkit (davila7/claude-code-templates, 33k stars), 13C Metabolic Flux Analysis (K-Dense-AI/scientific-agent-skills, 48k stars), Cantera Ignition Delay (K-Dense-AI/scientific-agent-skills, 48k stars) and Histolab Whole Slide Image Tiling (davila7/claude-code-templates, 33k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
jaechang-hits (a GitHub user) maintains it in jaechang-hits/SciAgent-Skills, which has 374 GitHub stars. The repository holds 169 skills in this directory. The repository was last updated on September 29, 2026.
Source: jaechang-hits/SciAgent-Skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.