pydicom · diff

git:20260611.1b8fae3 to v1.1

282 added, 332 removed. Audit A to A.

---
name: pydicom
- description: Python library for working with DICOM (Digital Imaging and Communications in Medicine) files. Use this skill when reading, writing, or modifying medical imaging data in DICOM format, extracting pixel data from medical images (CT, MRI, X-ray, ultrasound), anonymizing DICOM files, working with DICOM metadata and tags, converting DICOM images to other formats, handling compressed DICOM data, or processing medical imaging datasets. Applies to tasks involving medical image analysis, PACS systems, radiology workflows, and healthcare imaging applications.
- license: https://github.com/pydicom/pydicom/blob/main/LICENSE
- metadata: {"version": "1.0", "skill-author": "K-Dense Inc."}
+ description: Use pydicom to read, inspect, write, transform, and safely preflight local DICOM datasets and pixel data. Applies to DICOM metadata, transfer syntaxes, compression plugins, frames, private elements, JSON, and bounded de-identification review.
+ license: MIT
+ compatibility: Python 3.10+ with pydicom 3.0.2; optional pinned NumPy, Pillow, and pixel plugins. Helper CLIs are local-only and require authorized data.
+ metadata:
+ version: "1.1"
+ skill-author: "K-Dense Inc."
+ last-reviewed: "2026-07-23"
---
- # Pydicom
-
- ## Overview
+ # pydicom
- 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 pydicom for DICOM dataset I/O and pixel processing. Version 3.0.2 is the
+ current stable release reviewed here. It fixes CVE-2026-32711, a crafted
+ DICOMDIR path-traversal issue. pydicom 3.0.2 declares Python `>=3.10`; its
+ bundled DICOM dictionary is 2024c, while the live DICOM Standard may be newer.
- ## When to Use This Skill
+ ## Mandatory safety boundary
- 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
+ - Work only with local data that the user is authorized to access.
+ - DICOM metadata, file names, private elements, overlays, structured content,
+ and pixels may contain protected health information (PHI).
+ - Never print `Dataset`, export full metadata/JSON, or log element values by
+ default. Use a documented allowlist and aggregate output.
+ - pydicom is a general DICOM framework, not a diagnostic viewer. Pixel output,
+ validation, conversion, and plugin availability are not diagnostic claims.
+ - De-identification is profile-, purpose-, recipient-, jurisdiction-, and
+ threat-context-specific. It requires privacy/DICOM expert verification.
+ - Never claim that a tag-removal script is DICOM PS3.15, HIPAA, GDPR, or other
+ compliance. Preserve originals and audit derived outputs.
+ - Treat deterministic pseudonymization keys and UID maps as re-identification
+ secrets: use least privilege and encrypted/managed secret storage, never
+ commit, sync, log, or share them with derivatives, and define backup,
+ rotation, revocation, and destruction procedures. A leaked key invalidates
+ the intended separation; rotation also changes deterministic mappings.
+ - Set explicit input-file, file-count, frame-count, decoded-byte, and output
+ limits before parsing untrusted or unusually large datasets.
## Installation
- Install pydicom and common dependencies:
+ Create or activate an isolated environment, then install the exact reviewed
+ release:
```bash
- uv pip install pydicom
- uv pip install pillow # For image format conversion
- uv pip install numpy # For pixel array manipulation
- uv pip install matplotlib # For visualization
+ uv pip install "pydicom==3.0.2"
```
- For handling compressed DICOM files, additional packages may be needed:
+ Uncompressed pixel arrays and image rendering:
```bash
- uv pip install pylibjpeg pylibjpeg-libjpeg pylibjpeg-openjpeg # JPEG compression
- uv pip install python-gdcm # Alternative compression handler
- ```
-
- ## Core Workflows
-
- ### Reading DICOM Files
-
- Read a DICOM file using `pydicom.dcmread()`:
-
- ```python
- 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)
+ uv pip install "pydicom==3.0.2" "numpy==2.5.1" "Pillow==12.3.0"
```
- **Key points:**
- - `dcmread()` returns a `Dataset` object
- - Access data elements using attribute notation (e.g., `ds.PatientName`) or tag notation (e.g., `ds[0x0010, 0x0010]`)
- - Use `ds.file_meta` to access file metadata like Transfer Syntax UID
- - Handle missing attributes with `getattr(ds, 'AttributeName', default_value)` or `hasattr(ds, 'AttributeName')`
-
- ### Working with Pixel Data
-
- Extract and manipulate image data from DICOM files:
-
- ```python
- 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
+ Install only the transfer-syntax plugins required by the deployment:
- # Image information
- print(f"Shape: {pixel_array.shape}")
- print(f"Data type: {pixel_array.dtype}")
- print(f"Rows: {ds.Rows}, Columns: {ds.Columns}")
+ ```bash
+ # JPEG/JPEG-LS, JPEG 2000/HTJ2K, and faster RLE through pylibjpeg
+ uv pip install "numpy==2.5.1" "pylibjpeg==2.1.0" \
+ "pylibjpeg-libjpeg==2.4.0" "pylibjpeg-openjpeg==2.5.0" \
+ "pylibjpeg-rle==2.2.0"
- # Apply windowing for display (CT/MRI)
- if hasattr(ds, 'WindowCenter') and hasattr(ds, 'WindowWidth'):
- from pydicom.pixel_data_handlers.util import apply_voi_lut
- windowed_image = apply_voi_lut(pixel_array, ds)
- else:
- windowed_image = pixel_array
+ # JPEG-LS encoder/decoder
+ uv pip install "numpy==2.5.1" "pyjpegls==1.5.1"
- # Display image
- plt.imshow(windowed_image, cmap='gray')
- plt.title(f"{ds.Modality} - {ds.StudyDescription}")
- plt.axis('off')
- plt.show()
+ # Alternative decoder with platform-specific wheels
+ uv pip install "python-gdcm==3.2.6"
```
- **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.pixel_data_handlers.util import convert_color_space
- rgb_image = convert_color_space(ds.pixel_array, 'YBR_FULL', 'RGB')
- plt.imshow(rgb_image)
- ```
+ Plugin licenses and wheels differ by package/platform; review them before
+ deployment. Pillow has documented decoding limitations and pydicom cautions
+ that plugin output must be independently checked.
- **Multi-frame images (videos/series):**
+ Native codec wheels widen the supply-chain and memory-safety boundary. For a
+ controlled deployment, resolve these exact pins on a trusted build host, lock
+ and verify wheel hashes/provenance, mirror approved artifacts internally, scan
+ them, and install with hash enforcement rather than resolving from the public
+ index at runtime.
- ```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]}")
+ ## Choose the workflow
- # Display specific frame
- plt.imshow(frames[0], cmap='gray')
- ```
+ 1. Need an aggregate overview: run `scripts/extract_metadata.py`.
+ 2. Need bounded technical checks: run `scripts/dicom_inventory.py`.
+ 3. Need codec deployment preflight: run
+ `scripts/transfer_syntax_inspector.py`.
+ 4. Need frame/memory planning: run `scripts/pixel_frame_planner.py`.
+ 5. Need one non-diagnostic rendered frame: run
+ `scripts/dicom_to_image.py`.
+ 6. Need a pseudonymized derivative: read the de-identification section, create
+ a site-reviewed action profile, then run `scripts/anonymize_dicom.py` and
+ `scripts/deidentification_audit.py`.
+ 7. Need to check a sensitive UID map: run
+ `scripts/uid_mapping_validator.py`.
- ### Converting DICOM to Image Formats
+ ## Read datasets safely
- Use the provided `dicom_to_image.py` script or convert manually:
+ `dcmread()` returns a `FileDataset`, a `Dataset` subclass with File Format
+ state such as `file_meta`, preamble, and original encoding.
```python
- from PIL import Image
+ from pathlib import Path
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)
+ path = Path("authorized/input.dcm")
+ ds = pydicom.dcmread(
+ path,
+ stop_before_pixels=True,
+ specific_tags=[
+ "SOPClassUID",
+ "Modality",
+ "Rows",
+ "Columns",
+ "NumberOfFrames",
+ ],
+ )
- # Save as PNG
- image = Image.fromarray(pixel_array)
- image.save('output.png')
+ technical = {
+ "sop_class": ds.get("SOPClassUID"),
+ "modality": ds.get("Modality"),
+ "rows": ds.get("Rows"),
+ "columns": ds.get("Columns"),
+ }
```
- Use the script: `python scripts/dicom_to_image.py input.dcm output.png`
-
- ### Modifying Metadata
-
- Modify DICOM data elements:
-
- ```python
- import pydicom
- from datetime import datetime
+ Use:
- ds = pydicom.dcmread('input.dcm')
+ - `stop_before_pixels=True` for metadata-only work.
+ - `specific_tags=[...]` for a minimum allowlist.
+ - `defer_size="1 MiB"` when a later write must preserve large values.
+ - `force=False` (default). `force=True` only bypasses the File Format header
+ check; it does not prove the bytes are valid DICOM.
- # Modify existing elements
- ds.PatientName = "Doe^John"
- ds.StudyDate = datetime.now().strftime('%Y%m%d')
- ds.StudyDescription = "Modified Study"
+ Do not call `print(ds)`, `repr(ds)`, or iterate values into logs on clinical
+ data.
- # Add new elements
- ds.SeriesNumber = 1
- ds.SeriesDescription = "New Series"
+ ## Dataset, DataElement, and sequences
- # Remove elements
- if hasattr(ds, 'PatientComments'):
- delattr(ds, 'PatientComments')
- # Or using del
- if 'PatientComments' in ds:
- del ds.PatientComments
+ Access standard elements by keyword and check for absence:
- # Save modified file
- ds.save_as('modified.dcm')
+ ```python
+ modality = ds.get("Modality", "UNSPECIFIED")
+ if "ReferencedImageSequence" in ds:
+ for item in ds.ReferencedImageSequence:
+ referenced_class = item.get("ReferencedSOPClassUID")
```
- ### Anonymizing DICOM Files
+ Tag access, such as `ds[0x0010, 0x0010]`, returns a `DataElement`; its `.value`
+ is separate. `Sequence` behaves like a list of nested `Dataset` items. Privacy
+ actions must recurse through every sequence item, not only the top level.
- Remove or replace patient identifiable information:
+ When creating a file, use `FileMetaDataset` for group `0002`, keep dataset and
+ file-meta SOP UIDs consistent, set a Transfer Syntax UID, and write in enforced
+ File Format:
```python
- 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)
+ from pydicom import dcmwrite
+ from pydicom.dataset import FileDataset, FileMetaDataset
+ from pydicom.uid import CTImageStorage, ExplicitVRLittleEndian, generate_uid
- # Update dates to maintain temporal relationships
- if hasattr(ds, 'StudyDate'):
- # Shift dates by a random offset
- ds.StudyDate = '20000101'
+ meta = FileMetaDataset()
+ meta.MediaStorageSOPClassUID = CTImageStorage
+ meta.MediaStorageSOPInstanceUID = generate_uid()
+ meta.TransferSyntaxUID = ExplicitVRLittleEndian
- # Keep pixel data intact
- ds.save_as('anonymized.dcm')
+ ds = FileDataset(None, {}, file_meta=meta, preamble=b"\0" * 128)
+ ds.SOPClassUID = meta.MediaStorageSOPClassUID
+ ds.SOPInstanceUID = meta.MediaStorageSOPInstanceUID
+ # Add all attributes required by the selected IOD before writing.
+ dcmwrite("new.dcm", ds, enforce_file_format=True, overwrite=False)
```
- Use the provided script: `python scripts/anonymize_dicom.py input.dcm output.dcm`
+ `write_like_original` is deprecated in pydicom 3.0; use
+ `enforce_file_format`. A successful write is not full PS3.3 IOD conformance.
- ### Writing DICOM Files
+ ## UIDs and transfer syntax
- Create DICOM files from scratch:
+ The File Meta Information Transfer Syntax UID controls dataset encoding and
+ pixel compression:
```python
- import pydicom
- from pydicom.dataset import Dataset, FileDataset
- from datetime import datetime
- import numpy as np
-
- # Create file meta information
- file_meta = Dataset()
- file_meta.MediaStorageSOPClassUID = pydicom.uid.generate_uid()
- 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
- 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')
+ ts = ds.file_meta.TransferSyntaxUID
+ summary = {
+ "uid": str(ts),
+ "name": ts.name,
+ "compressed": ts.is_compressed,
+ "implicit_vr": ts.is_implicit_VR,
+ "little_endian": ts.is_little_endian,
+ }
```
- ### Compression and Decompression
-
- Handle compressed DICOM files:
+ pydicom 3.0 chooses write encoding from the Transfer Syntax UID before legacy
+ dataset flags. Do not replace structural UIDs (Transfer Syntax, SOP Class, or
+ coding-scheme UIDs) during pseudonymization. Instance/reference UID replacement
+ must be one-to-one and consistent across the complete declared scope.
- ```python
- import pydicom
+ Read [references/transfer_syntaxes.md](references/transfer_syntaxes.md) before
+ compression, decompression, or encapsulation.
- # Read compressed DICOM file
- ds = pydicom.dcmread('compressed.dcm')
+ ## Pixel data and frames
- # Check transfer syntax
- print(f"Transfer Syntax: {ds.file_meta.TransferSyntaxUID}")
- print(f"Transfer Syntax Name: {ds.file_meta.TransferSyntaxUID.name}")
+ The stable `pydicom.pixels` API supports path-based, frame-specific decoding:
- # Decompress and save as uncompressed
- ds.decompress()
- ds.save_as('uncompressed.dcm', write_like_original=False)
+ ```python
+ from pydicom.pixels import pixel_array
- # Or compress when saving (requires appropriate encoder)
- ds_uncompressed = pydicom.dcmread('uncompressed.dcm')
- ds_uncompressed.compress(pydicom.uid.JPEGBaseline8Bit)
- ds_uncompressed.save_as('compressed_jpeg.dcm')
+ # Reads only the selected frame where the source permits it.
+ frame = pixel_array("authorized/image.dcm", index=0, raw=False)
```
- **Common transfer syntaxes:**
- - `ExplicitVRLittleEndian` - Uncompressed, most common
- - `JPEGBaseline8Bit` - JPEG lossy compression
- - `JPEGLossless` - JPEG lossless compression
- - `JPEG2000Lossless` - JPEG 2000 lossless
- - `RLELossless` - Run-Length Encoding lossless
+ Shape semantics:
- See `references/transfer_syntaxes.md` for complete list.
+ - grayscale single frame: `(rows, columns)`
+ - grayscale multi-frame: `(frames, rows, columns)`
+ - color single frame: `(rows, columns, samples)`
+ - color multi-frame: `(frames, rows, columns, samples)`
- ### Working with DICOM Sequences
+ `raw=False` converts YCbCr pixel data to RGB when possible; `raw=True` retains
+ the decoded color space after mandatory minimal processing. Use
+ `iter_pixels(path, indices=[...])` for bounded multi-frame iteration.
- Handle nested data structures:
+ For grayscale display, apply transforms in this order:
```python
- 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()
+ from pydicom.pixels import apply_modality_lut, apply_voi_lut
- ds.ReferencedImageSequence = Sequence([sequence_item])
+ modality_values = apply_modality_lut(frame, ds)
+ display_values = apply_voi_lut(modality_values, ds, index=0)
```
- ### Processing DICOM Series
+ Modality LUT/rescale and VOI/windowing change display/value semantics.
+ MONOCHROME1 may require presentation inversion. Palette Color requires
+ `apply_color_lut()`. Presentation states and ICC behavior may require a
+ validated viewer. Never use per-frame min/max normalization for quantitative
+ analysis.
- Work with multiple related DICOM files:
+ ## Compression, decompression, and encapsulation
- ```python
- import pydicom
- import numpy as np
- from pathlib import Path
+ - Accessing `pixel_array` decodes as needed but does not change the dataset.
+ - `Dataset.decompress()` changes Pixel Data in place, sets Explicit VR Little
+ Endian, updates image metadata, and generates a new SOP Instance UID by
+ default.
+ - `Dataset.compress(uid)` changes Pixel Data and Transfer Syntax in place and
+ generates a new SOP Instance UID by default.
+ - pydicom 3.0 built-in/found encoders cover RLE Lossless, JPEG-LS, and JPEG
+ 2000 combinations documented in the stable plugin matrix.
+ - Each compressed frame is separately encoded and then encapsulated. Use
+ `encapsulate()` or `encapsulate_extended()` for externally encoded frames.
+ - Read frames with current `pydicom.encaps.generate_frames()` or `get_frame()`;
+ legacy encapsulation generator names are deprecated for pydicom 4.
- # Read all DICOM files in a directory
- dicom_dir = Path('dicom_series/')
- slices = []
+ Always inspect capabilities first, limit decoded bytes/frames, and verify pixel
+ correctness independently. Lossy compression acceptability is outside pydicom
+ and the DICOM encoding specification.
- for file_path in dicom_dir.glob('*.dcm'):
- ds = pydicom.dcmread(file_path)
- slices.append(ds)
+ ## DICOM JSON and private elements
- # 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))
+ `Dataset.to_json()`, `to_json_dict()`, and `Dataset.from_json()` implement the
+ DICOM JSON Model, but pydicom documents JSON support as beta. Full JSON may
+ inline binary data and expose every identifier and pixel payload. Do not emit
+ it as a metadata report. A `BulkDataURI` handler introduces separate storage,
+ authorization, and retrieval obligations.
- # Create 3D volume
- volume = np.stack([s.pixel_array for s in slices])
- print(f"Volume shape: {volume.shape}") # (num_slices, rows, columns)
+ Private elements are not standardized and may contain PHI:
- # 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")
+ ```python
+ # Recursive removal, but not sufficient de-identification by itself.
+ ds.remove_private_tags()
```
- ## Helper Scripts
+ Retain private elements only under an explicit reviewed safe-private policy.
+ Read [references/common_tags.md](references/common_tags.md) for tag access,
+ privacy classes, and standard pointers.
- This skill includes utility scripts in the `scripts/` directory:
+ ## De-identification workflow
- ### anonymize_dicom.py
- Anonymize DICOM files by removing or replacing Protected Health Information (PHI).
+ DICOM PS3.15 Annex E explicitly states that confidentiality profiles do not
+ guarantee removal of all identifying information and do not replace a complete
+ de-identification process.
- ```bash
- python scripts/anonymize_dicom.py input.dcm output.dcm
- ```
+ 1. Define purpose, recipients, linkage needs, regulations, threat model, and
+ acceptable re-identification risk.
+ 2. Select the Basic Application Level Confidentiality Profile and needed
+ options (pixel, recognizable visual features, graphics, structured content,
+ descriptors, temporal information, patient characteristics, devices,
+ institutions, UIDs, and safe private data).
+ 3. Preserve source objects unchanged in controlled storage.
+ 4. Apply every action recursively, including nested sequences.
+ 5. Replace instance/reference UIDs consistently across the complete scope;
+ preserve structural UIDs.
+ 6. Decide date/time handling explicitly. A fixed shift can preserve intervals
+ but partial dates, time zones, standalone times, leap days, longitudinal
+ linkage, and external events require reviewed policy.
+ 7. Inspect pixels, overlays, graphics, structured content, and recognizable
+ visual features. Do not infer clean pixels from missing metadata or set
+ `BurnedInAnnotation=NO` without verification.
+ 8. Rebuild File Meta Information and preamble to prevent leakage.
+ 9. Run technical validation and a de-identification audit, then perform expert
+ verification and documented risk review.
- ### dicom_to_image.py
- Convert DICOM files to common image formats (PNG, JPEG, TIFF).
+ The bundled script intentionally sets `PatientIdentityRemoved` to `NO` because
+ it cannot establish successful de-identification.
- ```bash
- python scripts/dicom_to_image.py input.dcm output.png
- python scripts/dicom_to_image.py input.dcm output.jpg --format JPEG
- ```
+ ## Helper CLIs
- ### extract_metadata.py
- Extract and display DICOM metadata in a readable format.
+ All `--help` paths are dependency-free. The tools perform no network access and
+ emit no DICOM values beyond narrow technical allowlists.
- ```bash
- python scripts/extract_metadata.py file.dcm
- python scripts/extract_metadata.py file.dcm --output metadata.txt
- ```
+ Bundled content consists of the two linked references, the documented helper
+ scripts, and synthetic tests. The pydicom runtime dependency is installed from
+ the pinned PyPI release.
- ## Reference Materials
+ ```bash
+ # Redacted aggregate metadata
+ python scripts/extract_metadata.py authorized/ --recursive
- Detailed reference information is available in the `references/` directory:
+ # Metadata-only technical inventory
+ python scripts/dicom_inventory.py authorized/ --recursive
- - **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
+ # Installed codec/plugin capabilities
+ python scripts/transfer_syntax_inspector.py --input authorized/image.dcm
- ## Common Issues and Solutions
+ # Frame shape, byte, and transform plan
+ python scripts/pixel_frame_planner.py authorized/image.dcm --frames 0,2-4
- **Issue: "Unable to decode pixel data"**
- - Solution: Install additional compression handlers: `uv pip install pylibjpeg pylibjpeg-libjpeg python-gdcm`
+ # One non-diagnostic frame
+ python scripts/dicom_to_image.py authorized/image.dcm frame.png \
+ --acknowledge-pixel-phi
- **Issue: "AttributeError" when accessing tags**
- - Solution: Check if attribute exists with `hasattr(ds, 'AttributeName')` or use `ds.get('AttributeName', default)`
+ # Create a secret key, then a scoped pseudonymized derivative plus audit
+ python scripts/anonymize_dicom.py --generate-uid-key project.key
+ python scripts/anonymize_dicom.py authorized/in.dcm derived/out.dcm \
+ --uid-key-file project.key --uid-scope export-v1 \
+ --audit-report derived/out.audit.json
- **Issue: Incorrect image display (too dark/bright)**
- - Solution: Apply VOI LUT windowing: `apply_voi_lut(pixel_array, ds)` or manually adjust with `WindowCenter` and `WindowWidth`
+ # Audit candidate metadata; no pixel decompression
+ python scripts/deidentification_audit.py derived/out.dcm
- **Issue: Memory issues with large series**
- - Solution: Process files iteratively, use memory-mapped arrays, or downsample images
+ # Validate an explicitly requested sensitive UID mapping
+ python scripts/uid_mapping_validator.py derived/uid-map.json \
+ --uid-key-file project.key --uid-scope export-v1
+ ```
- ## Best Practices
+ The generated raw key file is a controlled-local convenience and is created
+ with owner-only permissions. For production, materialize key bytes from an
+ approved secret manager into a locked ephemeral file, restrict access to the
+ de-identification service, and securely remove it afterward. Store any optional
+ UID map separately from derivatives; it directly links original and replacement
+ identifiers.
- 1. **Always check for required attributes** before accessing them using `hasattr()` or `get()`
- 2. **Preserve file metadata** when modifying files by using `save_as()` with `write_like_original=True`
- 3. **Use Transfer Syntax UIDs** to understand compression format before processing pixel data
- 4. **Handle exceptions** when reading files from untrusted sources
- 5. **Apply proper windowing** (VOI LUT) for medical image visualization
- 6. **Maintain spatial information** (pixel spacing, slice thickness) when processing 3D volumes
- 7. **Verify anonymization** thoroughly before sharing medical data
- 8. **Use UIDs correctly** - generate new UIDs when creating new instances, preserve them when modifying
+ ## pydicom 3.0 migration notes
- ## Documentation
+ - `read_file()` and `write_file()` were removed; use `dcmread()` and
+ `dcmwrite()`.
+ - `write_like_original` is deprecated; use `enforce_file_format`.
+ - `pydicom.pixel_data_handlers` is deprecated for removal in v4; use
+ `pydicom.pixels`.
+ - `Dataset.pixel_array` uses the new pixels backend by default and converts
+ YCbCr to RGB when possible.
+ - `JPEGLossless` now means UID `1.2.840.10008.1.2.4.57`;
+ `JPEGLosslessSV1` is `.70`.
+ - `Dataset.is_little_endian` and `is_implicit_VR` are deprecated for v4.
- Official pydicom documentation: https://pydicom.github.io/pydicom/dev/
- - User Guide: https://pydicom.github.io/pydicom/dev/guides/user/index.html
- - Tutorials: https://pydicom.github.io/pydicom/dev/tutorials/index.html
- - API Reference: https://pydicom.github.io/pydicom/dev/reference/index.html
- - Examples: https://pydicom.github.io/pydicom/dev/auto_examples/index.html
+ ## Sources (verified 2026-07-23)
+ - [pydicom 3.0.2 on PyPI](https://pypi.org/project/pydicom/) — released
+ 2026-03-19; Python `>=3.10`.
+ - [pydicom releases](https://github.com/pydicom/pydicom/releases) — 3.0.2 and
+ CVE-2026-32711 details.
+ - [Stable release notes](https://pydicom.github.io/pydicom/stable/release_notes/index.html)
+ - [Stable installation guide](https://pydicom.github.io/pydicom/stable/tutorials/installation.html)
+ - [Dataset basics](https://pydicom.github.io/pydicom/stable/tutorials/dataset_basics.html)
+ - [Stable pixel tutorial](https://pydicom.github.io/pydicom/stable/tutorials/pixel_data/introduction.html)
+ - [Stable pixel plugins](https://pydicom.github.io/pydicom/stable/guides/user/image_data_handlers.html)
+ - [Stable compression tutorial](https://pydicom.github.io/pydicom/stable/tutorials/pixel_data/compressing.html)
+ - [Stable DICOM JSON tutorial](https://pydicom.github.io/pydicom/stable/tutorials/dicom_json.html)
+ - [Stable private-element guide](https://pydicom.github.io/pydicom/stable/guides/user/private_data_elements.html)
+ - [Current DICOM Standard](https://www.dicomstandard.org/current)
+ - [DICOM PS3.3](https://dicom.nema.org/medical/dicom/current/output/chtml/part03/PS3.3.html),
+ [PS3.5](https://dicom.nema.org/medical/dicom/current/output/chtml/part05/PS3.5.html),
+ [PS3.6](https://dicom.nema.org/medical/dicom/current/output/chtml/part06/PS3.6.html),
+ and [PS3.15](https://dicom.nema.org/medical/dicom/current/output/html/part15.html)