pydicom-medical-imaging について
pydicom-medical-imagingは、医療画像を扱う開発者や研究者向けのPython DICOMワークフロー用スキルです。メタデータや画素データの取得、匿名化、形式変換、画像シリーズの3D配列化を扱います。
主な機能
- 01DICOMファイルの読み取りと変更
- 02メタデータと画素配列の抽出
- 03PNG、JPEG、TIFFへの変換
- 04個人識別情報の削除
- 05画像シリーズからの3D配列作成
活用シーン
- 01DICOMディレクトリからのメタデータ一括抽出
- 02可視化用画像の準備
- 03患者ファイルの匿名化
- 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 | 説明 |
|---|---|---|
| 患者 | PatientID | 1 人の患者 |
| 検査 | StudyInstanceUID | 1 回の撮像セッション(複数のモダリティを含む場合があります) |
| シリーズ | SeriesInstanceUID | 1 回の撮像シーケンス(例:T1 強調 MRI) |
| インスタンス(画像) | SOPInstanceUID | 1 枚の画像/フレーム(DICOM ファイル 1 つ) |
各 DICOM ファイルには、グループごとに整理されたメタデータタグを持つインスタンスが 1 つ含まれます。タグには (group, element) 表記を使用します(例:PatientName の (0010,0010))。
転送構文と圧縮
転送構文は、DICOM データのエンコード方法(バイト順、VR エンコード、ピクセル圧縮)を定義します。
| 転送構文 | UID | 圧縮 | 必要なハンドラー |
|---|---|---|---|
| Implicit VR Little Endian | 1.2.840.10008.1.2 | なし | いいえ |
| Explicit VR Little Endian | 1.2.840.10008.1.2.1 | なし | いいえ |
| Explicit VR Big Endian | 1.2.840.10008.1.2.2 | なし | いいえ |
| JPEG Baseline | 1.2.840.10008.1.2.4.50 | 非可逆 JPEG | pylibjpeg |
| JPEG Lossless | 1.2.840.10008.1.2.4.70 | 可逆 JPEG | pylibjpeg |
| JPEG 2000 Lossless | 1.2.840.10008.1.2.4.90 | 可逆 J2K | pylibjpeg-openjpeg |
| JPEG 2000 | 1.2.840.10008.1.2.4.91 | 非可逆 J2K | pylibjpeg-openjpeg |
| RLE Lossless | 1.2.840.10008.1.2.5 | RLE | pydicom(組み込み) |
転送構文は ds.file_meta.TransferSyntaxUID で確認します。圧縮ファイルの pixel_array にアクセスする前に、適切なハンドラーをインストールしてください。
重要な DICOM タグ
最もよくアクセスされるタグ(完全なカタログは references/dicom_standards.md を参照してください):
| タグ | キーワード | VR | 説明 |
|---|---|---|---|
| (0008,0060) | Modality | CS | CT、MR、US、CR、DX、PT、NM |
| (0010,0010) | PatientName | PN | 患者の氏名 |
| (0010,0020) | PatientID | LO | 患者識別子 |
| (0008,0020) | StudyDate | DA | 検査日(YYYYMMDD) |
| (0020,000D) | StudyInstanceUID | UI | 一意の検査識別子 |
| (0020,0013) | InstanceNumber | IS | シリーズ内の画像番号 |
| (0020,0032) | ImagePositionPatient | DS | x、y、z の位置(mm) |
| (0028,0010) | Rows | US | 画像の高さ(ピクセル単位) |
| (0028,0011) | Columns | US | 画像の幅(ピクセル単位) |
| (0028,0030) | PixelSpacing | DS | 行と列の間隔(mm) |
| (0028,1050) | WindowCenter | DS | 表示ウィンドウの中心 |
| (0028,0004) | PhotometricInterpretation | CS | MONOCHROME1/2、RGB、YBR_FULL |
| (0028,0100) | BitsAllocated | US | 8 または 16 |
値表現(VR)
VR は各要素のデータ型を定義します。最も一般的な型(完全な表は references/dicom_standards.md を参照してください):
| VR | 名前 | Python 型 | 例 |
|---|---|---|---|
| CS | コード文字列 | str | "CT", "MR" |
| DA | 日付 | str | "20240115" |
| DS | 10 進文字列 | DSfloat | "1.5" |
| IS | 整数文字列 | IS | "42" |
| LO | 長い文字列 | str | "検査の説明" |
| PN | 人名 | PersonName | "Doe^John" |
| SQ | シーケンス | Sequence | ネストされたデータセット |
| UI | 一意識別子 | UID | "1.2.840..." |
| US | 符号なし短整数 | int | 512 |
一般的なワークフロー
ワークフロー 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_size | dcmread | None | "1 KB"、"1 MB"、バイト数を表す整数 | 指定サイズを超える要素の読み込みを遅延します |
stop_before_pixels | dcmread | False | True/False | ピクセルデータの読み込みをスキップします(メタデータのみ) |
force | dcmread | False | True/False | DICOM プリアンブルがない場合でも強制的に読み込みます |
WindowCenter | ウィンドウ設定 | ファイルから取得 | 任意の数値 | 表示ウィンドウの中心(CT の場合は HU) |
WindowWidth | ウィンドウ設定 | ファイルから取得 | > 0 | 表示ウィンドウの幅 |
BitsAllocated | 書き込み | 16 | 8、16、32 | ピクセルごとに割り当てるビット数 |
BitsStored | 書き込み | 12 | 1~BitsAllocated | 実際に有効なビット数 |
PixelRepresentation | 書き込み | 0 | 0(符号なし)、1(符号付き) | ピクセル値の符号の有無 |
PhotometricInterpretation | 書き込み | "MONOCHROME2" | MONOCHROME1、MONOCHROME2、RGB、YBR_FULL | 色空間 |
TransferSyntaxUID | 書き込み | 明示的 VR LE | Transfer Syntax テーブルを参照 | エンコード形式 |
ベストプラクティス
-
メタデータのみを扱う処理では、
stop_before_pixels=Trueを使用します:タグの読み取りだけが目的の場合に、大きなピクセル配列の読み込みを避けられます。メタデータを一括抽出する際に大幅に高速化できます。ds = pydicom.dcmread("scan.dcm", stop_before_pixels=True) -
オプションのタグには、デフォルト値を指定して常に
getattr()を使用します:DICOM ファイルによって存在するタグは大きく異なります。属性に直接アクセスすると、タグがない場合にAttributeErrorが発生します。# Good thickness = getattr(ds, 'SliceThickness', None) # Bad — will crash on files without SliceThickness thickness = ds.SliceThickness -
CT データでは、ウィンドウ処理の前に Modality LUT を適用します:未処理のピクセル値は保存値です。まず
apply_modality_lut()を適用して Hounsfield 単位に変換し、次に表示用のapply_voi_lut()を適用します。 -
圧縮されたピクセルデータにアクセスする前に、圧縮ハンドラーをインストールします:
ds.file_meta.TransferSyntaxUIDを確認し、適切なハンドラーをインストールしてください。ハンドラーなしでpixel_arrayを試みると、RuntimeErrorが発生します。 -
変更したファイルごとに新しい UID を生成します:変更後に元の SOPInstanceUID を再利用してはいけません。
pydicom.uid.generate_uid()を使い、グローバルに一意な UID を確保します。 -
元のファイルを上書きせず、
save_as()を使用します:元のデータを保持するため、必ず新しいパスに保存してください。DICOM アーカイブでは整合性チェックが行われる場合があり、元のファイルを直接変更するとチェックに失敗することがあります。 -
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 ファイル):
- SKILL.md(434 行)-- 移行:概要、ワークフロー、ベストプラクティス、よくある問題を新しい SKILL.md 形式に再構成
- references/common_tags.md(229 行)-- (b) 統合:必須 20 タグのテーブルを Key Concepts に、完全なカタログを references/dicom_standards.md に統合
- references/transfer_syntaxes.md(353 行)-- (b) 統合:概要テーブル(8 種類の構文)を Key Concepts に、完全な UID テーブルとハンドラーの詳細を references/dicom_standards.md に統合
- scripts/anonymize_dicom.py(138 行)-- (c) 吸収:PHI タグのリストと匿名化関数を Core API Module 4 に、バッチパイプラインを Workflow 3 に吸収
- scripts/dicom_to_image.py(173 行)-- (c) 吸収:変換関数を Core API Module 3(dicom_to_image helper)に、バッチ変換を Module 3 の 2 つ目のコードブロックに吸収
- scripts/extract_metadata.py(174 行)-- (c) 吸収:メタデータ抽出関数を Workflow 1 に吸収(CSV へのバッチ抽出)
関連スキル
- histolab-wsi-processing -- 全スライド病理画像処理(SVS、NDPI)。デジタル病理画像のタイル抽出に使用します
- nibabel (planned) -- 脳 MRI のボリューム解析向け NIfTI 神経画像フォーマット
- matplotlib-scientific-plotting -- DICOM 画像と 3D ボリュームスライスの出版品質の可視化
参考資料
- Pydicom のドキュメント -- 公式 API リファレンスとユーザーガイド
- Pydicom GitHub リポジトリ -- ソースコード、サンプル、課題トラッカー
- DICOM Standard Browser -- DICOM タグと IOD のインタラクティブなリファレンス
- pylibjpeg のドキュメント -- pydicom 用 JPEG コーデックプラグイン