dicom-processing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

DICOM Processing

DICOM 处理

Overview

概述

Pipeline skill for converting, reading, and de-identifying medical images in DICOM and NIfTI formats. Encodes deterministic commands and code for:
  • DICOM → NIfTI conversion with
    dcm2niix
    (including BIDS sidecars)
  • Reading DICOM headers and pixel data with
    pydicom
  • PHI removal (de-identification) with
    pydicom
    or CTP DicomAnonymizer
  • NIfTI inspection with
    nibabel
  • Batch processing grouped by
    SeriesInstanceUID
  • BIDS-compliant directory organization
Use this skill whenever the user works with
.dcm
,
.nii
,
.nii.gz
, DICOM directories, or anonymization workflows.
用于转换、读取和去标识化 DICOM 与 NIfTI 格式医学图像的流水线 skill。为以下任务提供确定性命令和代码:
  • 使用
    dcm2niix
    将 DICOM 转换为 NIfTI(包括 BIDS sidecar)
  • 使用
    pydicom
    读取 DICOM 头信息和像素数据
  • 使用
    pydicom
    或 CTP DicomAnonymizer 移除 PHI(去标识化)
  • 使用
    nibabel
    检查 NIfTI
  • 按
    SeriesInstanceUID
    分组进行批处理
  • 构建符合 BIDS 的目录结构
当用户处理
.dcm
、
.nii
、
.nii.gz
、DICOM 目录或去标识化工作流时,请使用此 skill。

Usage

使用方法

1. Convert a DICOM series to NIfTI (dcm2niix)

1. 将 DICOM 序列转换为 NIfTI(dcm2niix)

bash
undefined
bash
undefined

Standard conversion: gzip output, BIDS sidecar, pattern-named files

Standard conversion: gzip output, BIDS sidecar, pattern-named files

dcm2niix -z y -b y -f %p_%s -o output_dir input_dir

Flags (memorize):

| Flag | Meaning |
| --- | --- |
| `-z y` | gzip output (`.nii.gz`) |
| `-b y` | emit BIDS JSON sidecar |
| `-f %p_%s` | filename pattern: `%p` = protocol, `%s` = series number |
| `-m n` | do NOT merge 2D slices across series (prevents cross-series mixing) |
| `-o` | output directory |

Common naming tokens: `%p` protocol, `%s` series, `%t` time, `%i` patient ID, `%n` patient name, `%d` description.
dcm2niix -z y -b y -f %p_%s -o output_dir input_dir

参数(需记忆):

| 参数 | 含义 |
| --- | --- |
| `-z y` | gzip 输出(`.nii.gz`) |
| `-b y` | 输出 BIDS JSON sidecar |
| `-f %p_%s` | 文件名模式:`%p` = protocol,`%s` = series number |
| `-m n` | 不跨序列合并 2D 切片(防止混合不同序列) |
| `-o` | 输出目录 |

常用命名标记:`%p` protocol、`%s` series、`%t` time、`%i` patient ID、`%n` patient name、`%d` description。

2. Read a DICOM file with pydicom

2. 使用 pydicom 读取 DICOM 文件

python
import pydicom

ds = pydicom.dcmread('file.dcm')
print(ds.PatientName, ds.StudyDate, ds.Modality)
print(ds.pixel_array.shape)  # numpy array of pixel data
python
import pydicom

ds = pydicom.dcmread('file.dcm')
print(ds.PatientName, ds.StudyDate, ds.Modality)
print(ds.pixel_array.shape)  # numpy array of pixel data

3. De-identify a DICOM file (pydicom)

3. 使用 pydicom 对 DICOM 文件进行去标识化

python
import pydicom
from pydicom.uid import generate_uid

ds = pydicom.dcmread('file.dcm')

tags_to_remove = [
    'PatientName', 'PatientID', 'PatientBirthDate',
    'InstitutionName', 'ReferringPhysicianName', 'StudyDate',
]
for tag in tags_to_remove:
    if tag in ds:
        del ds[tag]

ds.PatientID = 'ANON_001'
python
import pydicom
from pydicom.uid import generate_uid

ds = pydicom.dcmread('file.dcm')

tags_to_remove = [
    'PatientName', 'PatientID', 'PatientBirthDate',
    'InstitutionName', 'ReferringPhysicianName', 'StudyDate',
]
for tag in tags_to_remove:
    if tag in ds:
        del ds[tag]

ds.PatientID = 'ANON_001'

Remove private (vendor) tags — they frequently contain PHI

Remove private (vendor) tags — they frequently contain PHI

ds.remove_private_tags()
ds.remove_private_tags()

Replace UIDs so the study/series/instance cannot be linked back

Replace UIDs so the study/series/instance cannot be linked back

ds.StudyInstanceUID = generate_uid() ds.SeriesInstanceUID = generate_uid() ds.SOPInstanceUID = generate_uid()
ds.save_as('anon.dcm')
undefined
ds.StudyInstanceUID = generate_uid() ds.SeriesInstanceUID = generate_uid() ds.SOPInstanceUID = generate_uid()
ds.save_as('anon.dcm')
undefined

4. Batch de-identification with CTP DicomAnonymizer

4. 使用 CTP DicomAnonymizer 进行批量去标识化

bash
undefined
bash
undefined

RSNA CTP / DicomBrowser DAP (DICOM Anonymizer Program)

RSNA CTP / DicomBrowser DAP (DICOM Anonymizer Program)

java -jar DAP.jar -p profile.script -i input/ -o output/

Use this for large cohorts or when a validated DICOM Supplement 142 profile is required.
java -jar DAP.jar -p profile.script -i input/ -o output/

对于大型队列,或需要使用经过验证的 DICOM Supplement 142 profile 时,请采用此方法。

5. Load a NIfTI volume with nibabel

5. 使用 nibabel 加载 NIfTI volume

python
import nibabel as nib

img = nib.load('file.nii.gz')
data   = img.get_fdata()  # numpy ndarray
affine = img.affine       # 4x4 voxel-to-world transform
header = img.header
python
import nibabel as nib

img = nib.load('file.nii.gz')
data   = img.get_fdata()  # numpy ndarray
affine = img.affine       # 4x4 voxel-to-world transform
header = img.header

6. Batch pattern: group by SeriesInstanceUID, convert per series

6. 批处理模式:按 SeriesInstanceUID 分组并逐序列转换

python
from pathlib import Path
from collections import defaultdict
import pydicom, subprocess

def group_by_series(input_dir):
    series = defaultdict(list)
    for p in Path(input_dir).rglob('*.dcm'):
        ds = pydicom.dcmread(p, stop_before_pixels=True)
        series[ds.SeriesInstanceUID].append(p)
    return series

def convert_all(input_dir, output_dir):
    Path(output_dir).mkdir(parents=True, exist_ok=True)
    # dcm2niix handles series grouping internally when given a directory
    subprocess.run(
        ['dcm2niix', '-z', 'y', '-b', 'y', '-f', '%p_%s',
         '-o', output_dir, input_dir],
        check=True,
    )
python
from pathlib import Path
from collections import defaultdict
import pydicom, subprocess

def group_by_series(input_dir):
    series = defaultdict(list)
    for p in Path(input_dir).rglob('*.dcm'):
        ds = pydicom.dcmread(p, stop_before_pixels=True)
        series[ds.SeriesInstanceUID].append(p)
    return series

def convert_all(input_dir, output_dir):
    Path(output_dir).mkdir(parents=True, exist_ok=True)
    # dcm2niix handles series grouping internally when given a directory
    subprocess.run(
        ['dcm2niix', '-z', 'y', '-b', 'y', '-f', '%p_%s',
         '-o', output_dir, input_dir],
        check=True,
    )

7. BIDS organization

7. BIDS 目录组织

dataset/
├── dataset_description.json
├── participants.tsv
└── sub-01/
    └── ses-01/
        ├── anat/  sub-01_ses-01_T1w.nii.gz
        ├── func/  sub-01_ses-01_task-rest_bold.nii.gz
        └── dwi/   sub-01_ses-01_dwi.nii.gz
Rule:
sub-<label>/ses-<label>/<modality>/<sub>_<ses>_<suffix>.nii.gz
, with a matching
.json
sidecar.
dataset/
├── dataset_description.json
├── participants.tsv
└── sub-01/
    └── ses-01/
        ├── anat/  sub-01_ses-01_T1w.nii.gz
        ├── func/  sub-01_ses-01_task-rest_bold.nii.gz
        └── dwi/   sub-01_ses-01_dwi.nii.gz
规则:
sub-<label>/ses-<label>/<modality>/<sub>_<ses>_<suffix>.nii.gz
,并提供匹配的
.json
sidecar。

Response Format

响应格式

  • Lead with the command or code the user needs — explain after
  • Structure as: confirm inputs → working code → key parameters explained → gotchas
  • One complete working example per task; do not show every alternative
  • Keep code comments minimal and functional (what, not why-it-exists)
  • Target: 50-100 lines of code with brief surrounding explanation
  • 先给出用户所需的命令或代码,之后再进行解释
  • 按以下结构组织:确认输入 → 可运行代码 → 关键参数说明 → 注意事项
  • 每项任务提供一个完整可运行示例;不要展示每一种替代方案
  • 保持代码注释精简且实用(说明做什么,而非为什么需要)
  • 目标:提供 50–100 行代码及简短 surrounding explanation

Core Concepts

核心概念

Format and De-identification Decision Tree

格式与去标识化决策树

What is the target format?
├─ NIfTI (analysis) → dcm2niix -z y -b y
│  ├─ Need BIDS layout? → add -f sub-%i_ses-%t_%p, reorganize post-hoc
│  └─ Single series only? → point dcm2niix at series directory directly
└─ DICOM (archive/PACS) → pydicom read/write

De-identification method:
├─ Small cohort (< 100 studies) → pydicom script (remove tags + private + replace UIDs)
├─ Large cohort or regulatory → CTP DicomAnonymizer with Supplement 142 profile
└─ Burned-in pixel PHI risk?
   ├─ US / Secondary Capture / Screenshots → YES: add OCR + pixel masking step
   └─ CT / MR / PT → Usually NO: tag-level de-ID sufficient
DICOM hierarchy.
Patient → Study → Series → Instance
. A "series" is the atomic unit for conversion — one NIfTI volume typically corresponds to one
SeriesInstanceUID
.
UIDs are identifiers, not secrets, but they are linkable. Two files sharing a
StudyInstanceUID
came from the same exam. De-identification must replace UIDs (
generate_uid()
) to break linkage across releases.
Private tags carry PHI. Vendor-specific tags (odd group numbers) often embed operator notes, patient IDs, or burned-in metadata. Always call
ds.remove_private_tags()
.
Pixel data can contain PHI too. Ultrasound frames, secondary captures, and screenshots frequently have burned-in patient name/MRN in the pixels. Tag removal does not scrub pixels — run OCR + masking separately when this risk applies.
Key DICOM tags for geometry and modality:
TagNamePurpose
(0008,0060)
ModalityCT / MR / CR / US / PT ...
(0018,0050)
SliceThicknessmm
(0028,0030)
PixelSpacingin-plane spacing
[row, col]
mm
(0020,0032)
ImagePositionPatientorigin of slice in patient coords
(0020,0037)
ImageOrientationPatientrow/col direction cosines
These five tags are sufficient to reconstruct the NIfTI
affine
matrix.
NIfTI vs DICOM. NIfTI stores a single 3D/4D volume with one affine; DICOM stores slice-level metadata. Converters (
dcm2niix
) aggregate slices of a series and compute the affine from the tags above.
What is the target format?
├─ NIfTI (analysis) → dcm2niix -z y -b y
│  ├─ Need BIDS layout? → add -f sub-%i_ses-%t_%p, reorganize post-hoc
│  └─ Single series only? → point dcm2niix at series directory directly
└─ DICOM (archive/PACS) → pydicom read/write

De-identification method:
├─ Small cohort (< 100 studies) → pydicom script (remove tags + private + replace UIDs)
├─ Large cohort or regulatory → CTP DicomAnonymizer with Supplement 142 profile
└─ Burned-in pixel PHI risk?
   ├─ US / Secondary Capture / Screenshots → YES: add OCR + pixel masking step
   └─ CT / MR / PT → Usually NO: tag-level de-ID sufficient
DICOM 层级结构。
Patient → Study → Series → Instance
。“series”是转换的原子单位——一个 NIfTI volume 通常对应一个
SeriesInstanceUID
。
UID 是标识符,而不是机密,但它们是可关联的。 两个文件共享同一个
StudyInstanceUID
,说明它们来自同一次检查。去标识化必须替换 UID(
generate_uid()
),以断开不同数据发布版本之间的关联。
私有标签可能包含 PHI。 厂商特定标签(奇数组号)经常嵌入操作人员备注、患者 ID 或烧录元数据。始终调用
ds.remove_private_tags()
。
像素数据也可能包含 PHI。 超声帧、Secondary Capture 和截图经常在像素中烧录患者姓名或 MRN。移除标签无法清除像素中的 PHI;当存在此风险时,应单独运行 OCR 和掩膜处理。
用于几何和 modality 的关键 DICOM 标签:
标签名称用途
(0008,0060)
ModalityCT / MR / CR / US / PT ...
(0018,0050)
SliceThicknessmm
(0028,0030)
PixelSpacing面内间距
[row, col]
mm
(0020,0032)
ImagePositionPatient患者坐标系中切片的原点
(0020,0037)
ImageOrientationPatient行/列方向余弦
这五个标签足以重建 NIfTI 的
affine
矩阵。
NIfTI 与 DICOM 的区别。 NIfTI 存储单个 3D/4D volume,并包含一个 affine;DICOM 则存储切片级元数据。转换器(
dcm2niix
)会聚合一个 series 的切片,并根据上述标签计算 affine。

Quick Reference

快速参考

bash
undefined
bash
undefined

Convert

Convert

dcm2niix -z y -b y -f %p_%s -o out/ in/
dcm2niix -z y -b y -f %p_%s -o out/ in/

Skip merging across series (safer)

Skip merging across series (safer)

dcm2niix -z y -b y -m n -f %p_%s -o out/ in/
dcm2niix -z y -b y -m n -f %p_%s -o out/ in/

Batch anonymization (CTP)

Batch anonymization (CTP)

java -jar DAP.jar -p profile.script -i input/ -o output/

```python
java -jar DAP.jar -p profile.script -i input/ -o output/

```python

Read DICOM

Read DICOM

ds = pydicom.dcmread('file.dcm') ds.PatientName, ds.Modality, ds.pixel_array.shape
ds = pydicom.dcmread('file.dcm') ds.PatientName, ds.Modality, ds.pixel_array.shape

Header-only (fast, no pixels)

Header-only (fast, no pixels)

ds = pydicom.dcmread('file.dcm', stop_before_pixels=True)
ds = pydicom.dcmread('file.dcm', stop_before_pixels=True)

Anonymize core fields

Anonymize core fields

for t in ['PatientName','PatientID','PatientBirthDate', 'InstitutionName','ReferringPhysicianName','StudyDate']: if t in ds: del ds[t] ds.remove_private_tags() ds.StudyInstanceUID = generate_uid() ds.SeriesInstanceUID = generate_uid() ds.SOPInstanceUID = generate_uid()
for t in ['PatientName','PatientID','PatientBirthDate', 'InstitutionName','ReferringPhysicianName','StudyDate']: if t in ds: del ds[t] ds.remove_private_tags() ds.StudyInstanceUID = generate_uid() ds.SeriesInstanceUID = generate_uid() ds.SOPInstanceUID = generate_uid()

Load NIfTI

Load NIfTI

img = nib.load('file.nii.gz'); data = img.get_fdata(); aff = img.affine
undefined
img = nib.load('file.nii.gz'); data = img.get_fdata(); aff = img.affine
undefined

Common Mistakes

常见错误

  • Wrong: Not removing private tags during DICOM de-identification Right: Always call
    ds.remove_private_tags()
    as part of the de-identification pipeline Why: Vendor private tags (odd group numbers) routinely contain PHI that survives standard tag-level scrubbing
  • Wrong: Keeping original UIDs after anonymization Right: Replace
    StudyInstanceUID
    ,
    SeriesInstanceUID
    , and
    SOPInstanceUID
    with
    generate_uid()
    for every de-identified file Why: Original UIDs are linkable back to the source PACS — two files sharing a StudyInstanceUID reveal they came from the same exam
  • Wrong: Using default dcm2niix merge behavior without verification Right: Use
    -m n
    to disable merging, or inspect output file counts against the expected series list Why: Default merging can combine distinct acquisitions into one NIfTI volume, corrupting the data
  • Wrong: Ignoring burned-in annotations in pixel data during de-identification Right: Add an OCR detection and pixel masking step for ultrasound, secondary capture, and screenshot modalities Why: Tag-level de-identification does not scrub pixels — patient name/MRN burned into the image persists after tag removal
  • Wrong: Outputting uncompressed
    .nii
    files from dcm2niix Right: Always pass
    -z y
    to dcm2niix for gzipped
    .nii.gz
    output unless downstream tools explicitly require uncompressed Why: Uncompressed NIfTI files are 3-5× larger, wasting storage and slowing transfers with no benefit for most pipelines
  • Wrong: Reading full pixel data when only DICOM headers are needed Right: Use
    pydicom.dcmread(path, stop_before_pixels=True)
    for metadata-only scans Why: Loading pixel arrays for large cohorts wastes memory and time when only tags like Modality, SeriesInstanceUID, or SliceThickness are needed
  • Wrong: Treating DICOMDIR as a file directory and iterating its entries Right: Iterate actual
    .dcm
    files using
    Path(input_dir).rglob('*.dcm')
    instead Why: DICOMDIR is an index file with its own structure — it may be incomplete, outdated, or absent; direct file iteration is more reliable
  • 错误: DICOM 去标识化期间未删除私有标签 正确: 始终将
    ds.remove_private_tags()
    纳入去标识化流水线 原因: 厂商私有标签(奇数组号)经常包含 PHI,即使执行标准标签级清理也仍会残留
  • 错误: 匿名化后保留原始 UID 正确: 对每个去标识化文件,使用
    generate_uid()
    替换
    StudyInstanceUID
    、
    SeriesInstanceUID
    和
    SOPInstanceUID
    原因: 原始 UID 可以关联回源 PACS——两个文件共享同一个 StudyInstanceUID,意味着它们来自同一次检查
  • 错误: 未经验证即使用 dcm2niix 的默认合并行为 正确: 使用
    -m n
    禁用合并,或将输出文件数量与预期 series 列表进行核对 原因: 默认合并可能把不同的采集数据组合到一个 NIfTI volume 中,从而破坏数据
  • 错误: 去标识化时忽略像素数据中烧录的标注信息 正确: 对 ultrasound、Secondary Capture 和 screenshot modality 增加 OCR 检测和像素掩膜步骤 原因: 标签级去标识化无法清除像素——烧录在图像中的患者姓名/MRN 在移除标签后仍会保留
  • 错误: dcm2niix 输出未压缩的
    .nii
    文件 正确: 除非下游工具明确要求未压缩格式,否则始终向 dcm2niix 传递
    -z y
    ,以生成 gzip 压缩的
    .nii.gz
    输出 原因: 未压缩的 NIfTI 文件会大 3–5 倍,在大多数流水线中没有任何收益,却会浪费存储空间并降低传输速度
  • 错误: 只需要 DICOM 头信息时仍读取完整像素数据 正确: 使用
    pydicom.dcmread(path, stop_before_pixels=True)
    扫描纯元数据 原因: 对大型队列加载 pixel array 会浪费内存和时间,尤其是在只需要 Modality、SeriesInstanceUID 或 SliceThickness 等标签时
  • 错误: 将 DICOMDIR 当作文件目录并遍历其中的条目 正确: 使用
    Path(input_dir).rglob('*.dcm')
    遍历实际的
    .dcm
    文件 原因: DICOMDIR 是具有特定结构的索引文件——它可能不完整、已过期或不存在;直接遍历文件更加可靠

References

参考资料