mcpField
P
pydicom-medical-imaging

pydicom-medical-imaging

github.com/jaechang-hitsデータ・データベース384101 回インストール6 日前に更新
リポジトリ

pydicom-medical-imaging について

pydicom-medical-imagingは、医療画像を扱う開発者や研究者向けのPython DICOMワークフロー用スキルです。メタデータや画素データの取得、匿名化、形式変換、画像シリーズの3D配列化を扱います。

主な機能

  1. 01DICOMファイルの読み取りと変更
  2. 02メタデータと画素配列の抽出
  3. 03PNG、JPEG、TIFFへの変換
  4. 04個人識別情報の削除
  5. 05画像シリーズからの3D配列作成

活用シーン

  1. 01DICOMディレクトリからのメタデータ一括抽出
  2. 02可視化用画像の準備
  3. 03患者ファイルの匿名化
  4. 04CTまたはMRIシリーズのボリューム処理

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jaechang-hits/sciagent-skills --skill pydicom-medical-imaging

インストール後、Claude Code で/pydicom-medical-imagingを入力して読み込まれたか確認します。

同梱ファイルreferences/dicom_standards.md

詳細

Pydicom 医用画像

概要

Pydicom は、DICOM(医用デジタル画像通信)ファイルの読み込み、書き込み、変更を行うための純粋な Python ライブラリです。DICOM メタデータタグやピクセルデータに NumPy 配列としてアクセスでき、CT、MRI、X 線、超音波など、さまざまな医用画像モダリティに対応しています。オプションのコーデックプラグインを使って、圧縮転送構文と非圧縮転送構文の両方を処理できます。

使用する場面

  • DICOM ファイルを読み込み、メタデータ(患者情報、検査パラメーター、撮影設定)を抽出する
  • DICOM 画像からピクセルデータを抽出して、分析や可視化に利用する
  • DICOM 画像を標準形式(PNG、JPEG、TIFF)に変換する
  • 保護対象保健情報(PHI)を削除して DICOM ファイルを匿名化する
  • 再ラベル付けや修正のために DICOM メタデータタグを変更する
  • NumPy 配列を DICOM としてラップするなどして、DICOM ファイルを一から作成する
  • CT/MRI シリーズを 3D ボリューム配列に処理して再構成する
  • マルチフレーム DICOM(シネ/動画)からフレームを抽出する
  • 全スライド病理画像(SVS、NDPI)には、代わりに histolab-wsi-processing を使用してください
  • NIfTI 神経画像ボリューム(.nii/.nii.gz)には、代わりに nibabel を使用してください

前提条件

  • Python パッケージ:pydicom、numpy、pillow
  • オプションのコーデック:pylibjpeg + pylibjpeg-libjpeg(JPEG)、pylibjpeg-openjpeg(JPEG 2000)、python-gdcm(ほとんどの形式)
  • データ形式:NEMA PS3.10 に準拠した DICOM ファイル(.dcm、.ima、または拡張子なし)
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 support

クイックスタート

import 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()}]")

コア API

モジュール 1:読み込みとメタデータへのアクセス

属性名またはタグ表記を使って DICOM ファイルを読み込み、メタデータにアクセスします。

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)}")
モジュール 2:ピクセルデータの抽出

グレースケール、カラー、ウィンドウ処理、マルチフレームに対応し、ピクセルデータを NumPy 配列として抽出します。

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:]}")
モジュール 3:画像変換

可視化やエクスポートのために、DICOM ピクセルデータを標準画像形式に変換します。

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}")
モジュール 4:メタデータの変更と匿名化

DICOM 属性を変更し、保護対象保健情報を削除して、個人を特定できないようにします。

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")
モジュール 5:DICOM の新規作成

適切なメタデータを設定して、NumPy 配列から新しい DICOM ファイルを作成します。

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")
モジュール 6:シリーズ処理と 3D ボリューム

DICOM シリーズを読み込み、空間位置で並べ替え、3D NumPy 配列に積み重ねます。

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 データモデル

DICOM では、医用画像データを次の 4 段階の階層で整理します。

レベル主な UID説明
患者PatientID1 人の患者
検査StudyInstanceUID1 回の撮像セッション(複数のモダリティを含む場合があります)
シリーズSeriesInstanceUID1 回の撮像シーケンス(例:T1 強調 MRI)
インスタンス(画像)SOPInstanceUID1 枚の画像/フレーム(DICOM ファイル 1 つ)

各 DICOM ファイルには、グループごとに整理されたメタデータタグを持つインスタンスが 1 つ含まれます。タグには (group, element) 表記を使用します(例:PatientName の (0010,0010))。

転送構文と圧縮

転送構文は、DICOM データのエンコード方法(バイト順、VR エンコード、ピクセル圧縮)を定義します。

転送構文UID圧縮必要なハンドラー
Implicit VR Little Endian1.2.840.10008.1.2なしいいえ
Explicit VR Little Endian1.2.840.10008.1.2.1なしいいえ
Explicit VR Big Endian1.2.840.10008.1.2.2なしいいえ
JPEG Baseline1.2.840.10008.1.2.4.50非可逆 JPEGpylibjpeg
JPEG Lossless1.2.840.10008.1.2.4.70可逆 JPEGpylibjpeg
JPEG 2000 Lossless1.2.840.10008.1.2.4.90可逆 J2Kpylibjpeg-openjpeg
JPEG 20001.2.840.10008.1.2.4.91非可逆 J2Kpylibjpeg-openjpeg
RLE Lossless1.2.840.10008.1.2.5RLEpydicom(組み込み)

転送構文は ds.file_meta.TransferSyntaxUID で確認します。圧縮ファイルの pixel_array にアクセスする前に、適切なハンドラーをインストールしてください。

重要な DICOM タグ

最もよくアクセスされるタグ(完全なカタログは references/dicom_standards.md を参照してください):

タグキーワードVR説明
(0008,0060)ModalityCSCT、MR、US、CR、DX、PT、NM
(0010,0010)PatientNamePN患者の氏名
(0010,0020)PatientIDLO患者識別子
(0008,0020)StudyDateDA検査日(YYYYMMDD)
(0020,000D)StudyInstanceUIDUI一意の検査識別子
(0020,0013)InstanceNumberISシリーズ内の画像番号
(0020,0032)ImagePositionPatientDSx、y、z の位置(mm)
(0028,0010)RowsUS画像の高さ(ピクセル単位)
(0028,0011)ColumnsUS画像の幅(ピクセル単位)
(0028,0030)PixelSpacingDS行と列の間隔(mm)
(0028,1050)WindowCenterDS表示ウィンドウの中心
(0028,0004)PhotometricInterpretationCSMONOCHROME1/2、RGB、YBR_FULL
(0028,0100)BitsAllocatedUS8 または 16
値表現(VR)

VR は各要素のデータ型を定義します。最も一般的な型(完全な表は references/dicom_standards.md を参照してください):

VR名前Python 型例
CSコード文字列str"CT", "MR"
DA日付str"20240115"
DS10 進文字列DSfloat"1.5"
IS整数文字列IS"42"
LO長い文字列str"検査の説明"
PN人名PersonName"Doe^John"
SQシーケンスSequenceネストされたデータセット
UI一意識別子UID"1.2.840..."
US符号なし短整数int512

一般的なワークフロー

ワークフロー 1:メタデータを一括抽出して CSV に出力

目標:DICOM ファイルのディレクトリを走査し、主要なメタデータフィールドを抽出して、CSV マニフェストに出力します。

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()}")
ワークフロー 2:CT シリーズを可視化付きの 3D ボリュームに変換

目標:CT シリーズを読み込み、ハンスフィールド単位で 3D ボリュームを構築し、軸位断面/矢状断面/冠状断面を表示します。

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")
ワークフロー 3:一括匿名化パイプライン

目標:ディレクトリ内のすべての DICOM ファイルを匿名化し、新しい UID を使ってシリーズ構造を保持します。

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")

主要なパラメーター

パラメーターモジュールデフォルト範囲/オプション効果
defer_sizedcmreadNone"1 KB"、"1 MB"、バイト数を表す整数指定サイズを超える要素の読み込みを遅延します
stop_before_pixelsdcmreadFalseTrue/Falseピクセルデータの読み込みをスキップします(メタデータのみ)
forcedcmreadFalseTrue/FalseDICOM プリアンブルがない場合でも強制的に読み込みます
WindowCenterウィンドウ設定ファイルから取得任意の数値表示ウィンドウの中心(CT の場合は HU)
WindowWidthウィンドウ設定ファイルから取得> 0表示ウィンドウの幅
BitsAllocated書き込み168、16、32ピクセルごとに割り当てるビット数
BitsStored書き込み121~BitsAllocated実際に有効なビット数
PixelRepresentation書き込み00(符号なし)、1(符号付き)ピクセル値の符号の有無
PhotometricInterpretation書き込み"MONOCHROME2"MONOCHROME1、MONOCHROME2、RGB、YBR_FULL色空間
TransferSyntaxUID書き込み明示的 VR LETransfer Syntax テーブルを参照エンコード形式

ベストプラクティス

  1. メタデータのみを扱う処理では、stop_before_pixels=True を使用します:タグの読み取りだけが目的の場合に、大きなピクセル配列の読み込みを避けられます。メタデータを一括抽出する際に大幅に高速化できます。

    ds = pydicom.dcmread("scan.dcm", stop_before_pixels=True)
    
  2. オプションのタグには、デフォルト値を指定して常に getattr() を使用します:DICOM ファイルによって存在するタグは大きく異なります。属性に直接アクセスすると、タグがない場合に AttributeError が発生します。

    # Good
    thickness = getattr(ds, 'SliceThickness', None)
    # Bad — will crash on files without SliceThickness
    thickness = ds.SliceThickness
    
  3. CT データでは、ウィンドウ処理の前に Modality LUT を適用します:未処理のピクセル値は保存値です。まず apply_modality_lut() を適用して Hounsfield 単位に変換し、次に表示用の apply_voi_lut() を適用します。

  4. 圧縮されたピクセルデータにアクセスする前に、圧縮ハンドラーをインストールします:ds.file_meta.TransferSyntaxUID を確認し、適切なハンドラーをインストールしてください。ハンドラーなしで pixel_array を試みると、RuntimeError が発生します。

  5. 変更したファイルごとに新しい UID を生成します:変更後に元の SOPInstanceUID を再利用してはいけません。pydicom.uid.generate_uid() を使い、グローバルに一意な UID を確保します。

  6. 元のファイルを上書きせず、save_as() を使用します:元のデータを保持するため、必ず新しいパスに保存してください。DICOM アーカイブでは整合性チェックが行われる場合があり、元のファイルを直接変更するとチェックに失敗することがあります。

  7. 3D 再構成では、ImagePositionPatient でシリーズを並べ替えます:InstanceNumber は常に信頼できるとは限りません。ImagePositionPatient[2](z 座標)を使うと、軸位 CT/MR シリーズを物理的に正しい順序に並べられます。

よくあるレシピ

レシピ:圧縮と展開の処理

使用する場面:圧縮された DICOM ファイルを読み込む場合や、非圧縮ファイルを圧縮する場合。

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}")
レシピ:DICOM シーケンスの操作

使用する場面:参照シリーズ、手技コード、プロトコル要素などのネストされたデータ構造にアクセスする場合。

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])
レシピ:マルチフレームの抽出

使用する場面:シネ、ビデオ、または拡張マルチフレーム DICOM ファイルから個々のフレームを抽出する場合。

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}/")

トラブルシューティング

問題原因解決策
RuntimeError: No available image handlerコーデックがない圧縮転送構文適切なハンドラーをインストールします:pip install pylibjpeg pylibjpeg-libjpeg(JPEG)、pip install pylibjpeg-openjpeg(JPEG 2000)、または pip install python-gdcm(すべての形式)
AttributeError: 'Dataset' has no attribute 'X'ファイルにオプションのタグがないgetattr(ds, 'X', default) を使うか、アクセスする前に 'X' in ds を確認します
InvalidDicomError: File is missing DICOM preamble標準外の DICOM ファイル、または DICOM ではないファイルpydicom.dcmread(path, force=True) を試して、プリアンブルのチェックを省略します
ピクセル値が正しくない(負の HU がない)Modality LUT が適用されていない解析の前に apply_modality_lut(pixels, ds) を適用します。未処理の保存値は実際の HU と異なります
画像が反転して見える(明暗が逆)MONOCHROME1 のフォトメトリック解釈ds.PhotometricInterpretation を確認します。MONOCHROME1 の場合は np.max(pixels) - pixels で反転します
大きなシリーズの読み込み時に MemoryError が発生するすべてのスライスを一度にメモリに読み込んでいるスライスをバッチ処理します。メタデータのスキャンには stop_before_pixels=True を、大きな要素には defer_size を使用します
3D ボリュームのスライス順が一定しない位置ではなく InstanceNumber で並べ替えている物理的に正しい順序にするため、ImagePositionPatient[2] で並べ替えます
PatientName のテキストが文字化けする文字エンコーディングの不一致SpecificCharacterSet タグを確認します。pydicom は自動的にデコードしますが、一部のファイルでは文字セットの宣言が誤っています
PixelData の設定時に TypeError が発生するピクセル配列のバイト形式が正しくないpixel_array.tobytes() を使用し、dtype が BitsAllocated と一致していることを確認します(16 ビットの場合は uint16)

同梱リソース

references/dicom_standards.md

DICOM タグカタログと転送構文のリファレンスをまとめたものです。患者の基本情報、検査/シリーズの識別、画像の幾何情報、ピクセルデータのエンコード、ウィンドウ設定、およびモダリティ固有のタグ(CT のハウンスフィールド値パラメーター、MR シーケンスパラメーター、装置の識別、タイミング)に関するタグテーブルをすべて掲載しています。圧縮形式とハンドラーのインストール方法を含む転送構文 UID テーブル、および値表現(VR)型のリファレンスも掲載しています。

  • 対象範囲: common_tags.md にあるすべてのタグカテゴリ(患者、検査、シリーズ、画像、ピクセル、ウィンドウ設定、CT 固有、MR 固有、装置、タイミング)、圧縮形式とハンドラーの対応付けを含む transfer_syntaxes.md のすべての転送構文 UID、VR 型カタログ
  • インラインから移動: 必須タグのテーブル(最も一般的な 20 個)を Key Concepts に移動。転送構文の概要テーブル(最も一般的な 8 個)を Key Concepts に移動。VR の概要テーブルを Key Concepts に移動
  • 省略: ハンドラーのインストール手順のチュートリアル(Prerequisites と Troubleshooting で解説)、各タグの詳細な文章による説明(テーブルだけで内容がわかるため)
元ファイルの処理(5 ファイル):
  1. SKILL.md(434 行)-- 移行:概要、ワークフロー、ベストプラクティス、よくある問題を新しい SKILL.md 形式に再構成
  2. references/common_tags.md(229 行)-- (b) 統合:必須 20 タグのテーブルを Key Concepts に、完全なカタログを references/dicom_standards.md に統合
  3. references/transfer_syntaxes.md(353 行)-- (b) 統合:概要テーブル(8 種類の構文)を Key Concepts に、完全な UID テーブルとハンドラーの詳細を references/dicom_standards.md に統合
  4. scripts/anonymize_dicom.py(138 行)-- (c) 吸収:PHI タグのリストと匿名化関数を Core API Module 4 に、バッチパイプラインを Workflow 3 に吸収
  5. scripts/dicom_to_image.py(173 行)-- (c) 吸収:変換関数を Core API Module 3(dicom_to_image helper)に、バッチ変換を Module 3 の 2 つ目のコードブロックに吸収
  6. scripts/extract_metadata.py(174 行)-- (c) 吸収:メタデータ抽出関数を Workflow 1 に吸収(CSV へのバッチ抽出)

関連スキル

  • histolab-wsi-processing -- 全スライド病理画像処理(SVS、NDPI)。デジタル病理画像のタイル抽出に使用します
  • nibabel (planned) -- 脳 MRI のボリューム解析向け NIfTI 神経画像フォーマット
  • matplotlib-scientific-plotting -- DICOM 画像と 3D ボリュームスライスの出版品質の可視化

参考資料

コメント