| name | scientific-medical-imaging |
| description | 医用イメージングスキル。DICOM/NIfTI 処理・WSI (Whole Slide Image) 解析・
PathML・MONAI・3D Slicer 連携・放射線画像解析・病理組織画像解析を支援。
「DICOM を解析して」「WSI を処理して」「医用画像をセグメンテーションして」で発火。
|
| tu_tools | [{"key":"tcia","name":"TCIA","description":"がん画像アーカイブ検索"}] |
Scientific Medical Imaging
医用イメージングのための専門解析スキル。
DICOM / NIfTI の放射線画像と WSI (Whole Slide Image) の
病理組織画像を対象とした解析パイプラインを提供する。
When to Use
- DICOM / NIfTI 医用画像の読み込み・前処理
- CT / MRI / PET 画像のセグメンテーション
- WSI (Whole Slide Image) 病理組織画像解析
- 放射線画像の特徴量抽出 (Radiomics)
- 医用画像の深層学習モデル (MONAI)
- 3D ボリュームレンダリング・可視化
Quick Start
医用イメージングパイプライン
Phase 1: Data Ingestion
- DICOM / NIfTI ファイル読み込み
- メタデータ抽出 (患者情報・撮影条件)
- 匿名化処理
↓
Phase 2: Preprocessing
- リサンプリング (Isotropic spacing)
- 正規化 (Windowing / Z-score)
- Registration (位置合わせ)
↓
Phase 3: Segmentation
- U-Net / nnU-Net / Swin UNETR
- 臓器/病変セグメンテーション
- 後処理 (形態学的操作)
↓
Phase 4: Feature Extraction
- Radiomics (PyRadiomics)
- 形状・テクスチャ・強度特徴量
- WSI パッチレベル特徴量
↓
Phase 5: Analysis
- 腫瘍体積・成長率計算
- 放射線治療計画支援
- 病理組織分類
↓
Phase 6: Reporting
- DICOM-SR (Structured Report)
- 解析レポート生成
- 3D 可視化
Workflow
1. DICOM 画像処理
import pydicom
import numpy as np
import matplotlib.pyplot as plt
from pathlib import Path
def load_dicom_series(dicom_dir):
"""DICOM シリーズ → 3D ボリューム"""
dicom_dir = Path(dicom_dir)
slices = []
for dcm_file in sorted(dicom_dir.glob("*.dcm")):
ds = pydicom.dcmread(dcm_file)
slices.append(ds)
slices.sort(key=lambda s: float(s.ImagePositionPatient[2]))
metadata = {
"patient_id": slices[0].PatientID,
"modality": slices[0].Modality,
"rows": slices[0].Rows,
"cols": slices[0].Columns,
"n_slices": len(slices),
"pixel_spacing": list(slices[0].PixelSpacing),
"slice_thickness": float(slices[0].SliceThickness),
}
volume = np.stack([s.pixel_array for s in slices])
slope = float(slices[0].RescaleSlope)
intercept = float(slices[0].RescaleIntercept)
volume_hu = volume * slope + intercept
print(f"Volume shape: {volume_hu.shape}")
print(f"HU range: [{volume_hu.min()}, {volume_hu.max()}]")
return volume_hu, metadata
def apply_window(volume, window_center, window_width):
"""CT ウィンドウ設定"""
lower = window_center - window_width / 2
upper = window_center + window_width / 2
volume_windowed = np.clip(volume, lower, upper)
volume_windowed = (volume_windowed - lower) / (upper - lower)
return volume_windowed
2. NIfTI 処理 (SimpleITK)
import SimpleITK as sitk
def load_nifti(filepath):
"""NIfTI 読み込み"""
img = sitk.ReadImage(filepath)
array = sitk.GetArrayFromImage(img)
spacing = img.GetSpacing()
origin = img.GetOrigin()
direction = img.GetDirection()
return {
"array": array,
"spacing": spacing,
"origin": origin,
"direction": direction,
"shape": array.shape,
}
def resample_isotropic(image, new_spacing=(1.0, 1.0, 1.0)):
"""Isotropic リサンプリング"""
original_spacing = image.GetSpacing()
original_size = image.GetSize()
new_size = [
int(round(osz * osp / nsp))
for osz, osp, nsp in zip(original_size, original_spacing, new_spacing)
]
resampler = sitk.ResampleImageFilter()
resampler.SetOutputSpacing(new_spacing)
resampler.SetSize(new_size)
resampler.SetOutputDirection(image.GetDirection())
resampler.SetOutputOrigin(image.GetOrigin())
resampler.SetTransform(sitk.Transform())
resampler.SetDefaultPixelValue(image.GetPixelIDValue())
resampler.SetInterpolator(sitk.sitkBSpline)
return resampler.Execute(image)
def registration(fixed_image, moving_image):
"""画像レジストレーション (位置合わせ)"""
registration_method = sitk.ImageRegistrationMethod()
registration_method.SetMetricAsMattesMutualInformation(numberOfHistogramBins=50)
registration_method.SetMetricSamplingStrategy(registration_method.RANDOM)
registration_method.SetMetricSamplingPercentage(0.01)
registration_method.SetOptimizerAsGradientDescent(
learningRate=, numberOfIterations=,
convergenceMinimumValue=, convergenceWindowSize=)
initial_transform = sitk.CenteredTransformInitializer(
fixed_image, moving_image, sitk.Euler3DTransform(),
sitk.CenteredTransformInitializerFilter.GEOMETRY)
registration_method.SetInitialTransform(initial_transform, inPlace=)
final_transform = registration_method.Execute(fixed_image, moving_image)
final_transform
3. MONAI: 医用画像深層学習
import monai
from monai.networks.nets import UNet, SwinUNETR
from monai.losses import DiceLoss, DiceCELoss
from monai.metrics import DiceMetric
from monai.transforms import (
Compose, LoadImaged, EnsureChannelFirstd, Spacingd,
ScaleIntensityRanged, CropForegroundd, RandCropByPosNegLabeld,
RandFlipd, RandRotate90d,
)
train_transforms = Compose([
LoadImaged(keys=["image", "label"]),
EnsureChannelFirstd(keys=["image", "label"]),
Spacingd(keys=["image", "label"], pixdim=(1.5, 1.5, 2.0), mode=("bilinear", "nearest")),
ScaleIntensityRanged(keys=["image"], a_min=-175, a_max=250, b_min=0.0, b_max=1.0, clip=True),
CropForegroundd(keys=["image", "label"], source_key="image"),
RandCropByPosNegLabeld(
keys=["image", "label"], label_key="label",
spatial_size=(96, 96, 96), pos=1, neg=1, num_samples=4,
),
RandFlipd(keys=["image", "label"], prob=0.5, spatial_axis=0),
RandRotate90d(keys=["image", "label"], prob=0.5),
])
model = UNet(
spatial_dims=,
in_channels=,
out_channels=,
channels=(, , , , ),
strides=(, , , ),
num_res_units=,
dropout=,
)
model_swin = SwinUNETR(
img_size=(, , ),
in_channels=,
out_channels=,
feature_size=,
use_checkpoint=,
)
loss_fn = DiceCELoss(to_onehot_y=, softmax=)
dice_metric = DiceMetric(include_background=, reduction=)
4. WSI (Whole Slide Image) 解析
import openslide
def load_wsi(wsi_path):
"""WSI (svs/ndpi/tiff) 読み込み"""
slide = openslide.OpenSlide(wsi_path)
metadata = {
"dimensions": slide.dimensions,
"level_count": slide.level_count,
"level_dimensions": slide.level_dimensions,
"level_downsamples": slide.level_downsamples,
"properties": dict(slide.properties),
}
print(f"WSI size: {slide.dimensions[0]:,} x {slide.dimensions[1]:,} pixels")
print(f"Levels: {slide.level_count}")
return slide, metadata
def extract_tissue_patches(slide, patch_size=256, level=0, threshold=0.7):
"""
組織領域からパッチを抽出。
Otsu 閾値で背景を除去し、組織含有率が threshold 以上のパッチのみ取得。
"""
from skimage.filters import threshold_otsu
from skimage.color import rgb2gray
thumb_level = min(slide.level_count - 1, 4)
thumbnail = slide.get_thumbnail(
(slide.level_dimensions[thumb_level][0], slide.level_dimensions[thumb_level][1])
)
thumb_gray = rgb2gray(np.array(thumbnail))
thresh = threshold_otsu(thumb_gray)
tissue_mask = thumb_gray < thresh
downsample = slide.level_downsamples[thumb_level]
patches = []
w, h = slide.level_dimensions[level]
y (, h, patch_size):
x (, w, patch_size):
tx = (x / downsample)
ty = (y / downsample)
tw = (patch_size / downsample)
th = (patch_size / downsample)
tx + tw <= tissue_mask.shape[] ty + th <= tissue_mask.shape[]:
tissue_ratio = tissue_mask[ty:ty+th, tx:tx+tw].mean()
tissue_ratio >= threshold:
patch = slide.read_region((x, y), level, (patch_size, patch_size))
patches.append({
: np.array(patch.convert()),
: x, : y,
: (tissue_ratio, ),
})
()
patches
5. Radiomics 特徴量抽出
from radiomics import featureextractor
def extract_radiomics(image_path, mask_path, params=None):
"""
PyRadiomics で放射線画像特徴量を抽出。
Shape, First-Order, GLCM, GLRLM, GLSZM, GLDM, NGTDM
"""
if params is None:
params = {
"setting": {
"binWidth": 25,
"resampledPixelSpacing": [1, 1, 1],
"interpolator": "sitkBSpline",
"normalize": True,
},
"featureClass": {
"shape": None,
"firstorder": None,
"glcm": None,
"glrlm": None,
"glszm": None,
},
}
extractor = featureextractor.RadiomicsFeatureExtractor()
for key, val in params.get("setting", {}).items():
extractor.settings[key] = val
features = extractor.execute(image_path, mask_path)
radiomic_features = {
k: float(v) for k, v in features.items()
if not k.startswith("diagnostics_")
}
print(f"Extracted {(radiomic_features)} radiomic features")
radiomic_features
6. レポート生成
import json
def generate_imaging_report(patient_id, modality, findings,
segmentation_results=None, radiomics=None,
output_dir="results"):
"""医用画像解析レポート"""
report = {
"patient_id": patient_id,
"modality": modality,
"analysis_date": pd.Timestamp.now().isoformat() if "pd" in dir() else "",
"findings": findings,
}
if segmentation_results:
report["segmentation"] = {
"model": segmentation_results.get("model", ""),
"dice_scores": segmentation_results.get("dice_scores", {}),
"volumes_ml": segmentation_results.get("volumes", {}),
}
if radiomics:
report["radiomics"] = {
"n_features": len(radiomics),
"top_features": dict(sorted(
radiomics.items(), key=lambda x: abs(x[1]), reverse=True
)[:20]),
}
with open(f"{output_dir}/imaging_report.json", "w") as f:
json.dump(report, f, indent=, default=)
md =
md +=
md +=
finding findings:
md +=
segmentation_results:
md +=
md +=
struct, dice segmentation_results.get(, {}).items():
vol = segmentation_results.get(, {}).get(struct, )
md +=
(, ) f:
f.write(md)
report
Best Practices
- Isotropic リサンプリング: 異方性ボクセルは必ず等方性に統一してから解析
- Windowing: CT は組織に適したウィンドウ設定を適用 (肺・骨・軟部組織)
- nnU-Net: セグメンテーションタスクでは nnU-Net が自動設定で SOTA レベル
- WSI 解析はパッチベース: ギガピクセル画像は直接処理できないためタイル化
- Dice + Hausdorff: セグメンテーション評価は Dice と Hausdorff Distance を併用
- 匿名化: DICOM メタデータの患者情報を必ず匿名化
- 3D context: 2D スライスではなく 3D ボリュームベースの解析を推奨
Completeness Checklist
ToolUniverse 連携
| TU Key | ツール名 | 連携内容 |
|---|
tcia | TCIA | がん画像アーカイブ検索 |
References
Output Files
| ファイル | 形式 | 生成タイミング |
|---|
results/imaging_report.json | 画像解析レポート(JSON) | 解析完了時 |
results/imaging_report.md | 画像解析レポート(Markdown) | レポート生成時 |
results/radiomics_features.json | Radiomics 特徴量(JSON) | 特徴量抽出時 |
figures/segmentation_overlay.png | セグメンテーション結果図 | セグメンテーション時 |
参照スキル
| スキル | 連携 |
|---|
scientific-image-analysis | ← 一般的な画像処理・セグメンテーション基盤 |
scientific-deep-learning | ← U-Net/SwinUNETR のトレーニング基盤 |
scientific-ml-classification | ← Radiomics 特徴量を用いた分類 |
scientific-precision-oncology | → 腫瘍画像の精密医療連携 |
scientific-clinical-decision-support | → 画像所見の臨床意思決定反映 |
scientific-explainable-ai | → 医用 AI の説明可能性 (GradCAM) |