في هذا البرنامج التعليمي، قمنا ببناء خط أنابيب شامل لتجزئة الصور الطبية ثلاثية الأبعاد باستخدام موناي لتقسيم الطحال في مجموعة بيانات Decathlon Task09 للتجزئة الطبية. نحن نعمل مع عمليات المسح المقطعي الحجمي، ونطبق تحويلات التصوير الطبي مثل محاذاة الاتجاه، وتطبيع تباعد فوكسل، ونافذة الكثافة، واقتصاص المقدمة، وأخذ العينات القائمة على التصحيح، ثم تدريب نموذج UNet ثلاثي الأبعاد لتجزئة الأعضاء الثنائية. نستخدم أيضًا تدريبًا مختلطًا للدقة، وفقدان DiceCE، واستدلال النافذة المنزلقة، والتحقق من صحة النرد، والتصور النوعي لفهم كيفية تعلم النموذج وكيفية مقارنة تنبؤاته بأقنعة الحقيقة الأرضية. كما أننا ننتقل من المجلدات الطبية الأولية إلى نظام تجزئة متكامل للتحقق من الصحة والتصور.
!pip install -q "monai[nibabel,tqdm,matplotlib]==1.5.2" 2>/dev/null
import os, time, glob, tempfile, warnings
import numpy as np
import torch
import matplotlib.pyplot as plt
from torch.amp import autocast, GradScaler
from monai.apps import DecathlonDataset
from monai.data import DataLoader, decollate_batch
from monai.networks.nets import UNet
from monai.networks.layers import Norm
from monai.losses import DiceCELoss
from monai.metrics import DiceMetric
from monai.inferers import sliding_window_inference
from monai.utils import set_determinism
from monai.transforms import (
Compose, LoadImaged, EnsureChannelFirstd, EnsureTyped, Orientationd,
Spacingd, ScaleIntensityRanged, CropForegroundd, RandCropByPosNegLabeld,
RandFlipd, RandRotate90d, RandShiftIntensityd, AsDiscrete,
)
warnings.filterwarnings("ignore")
نبدأ بتثبيت MONAI مع تبعيات التصوير الطبي والتصور المطلوبة. نقوم بعد ذلك باستيراد وحدات PyTorch وNumPy وMatplotlib ووحدات MONAI الرئيسية اللازمة لمجموعات البيانات والتحويلات والتدريب على النماذج والمقاييس والاستدلال. نقوم أيضًا بمنع التحذيرات للحفاظ على نظافة مخرجات الكمبيوتر المحمول بينما نركز على سير عمل التجزئة.
QUICK_RUN = True
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
root_dir = tempfile.mkdtemp()
roi_size = (96, 96, 96)
num_samples = 4
batch_size = 2
max_epochs = 15 if QUICK_RUN else 200
val_every = 3
train_cache = 8 if QUICK_RUN else 24
val_cache = 2 if QUICK_RUN else 6
set_determinism(seed=0)
print(f"Device: {device} | epochs: {max_epochs} | data dir: {root_dir}")
train_transforms = Compose(common + [
image_key="image", image_threshold=0),
RandFlipd(keys=["image", "label"], prob=0.2, spatial_axis=0),
RandFlipd(keys=["image", "label"], prob=0.2, spatial_axis=1),
RandFlipd(keys=["image", "label"], prob=0.2, spatial_axis=2),
RandRotate90d(keys=["image", "label"], prob=0.2, max_k=3),
RandShiftIntensityd(keys=["image"], offsets=0.10, prob=0.5),
EnsureTyped(keys=["image", "label"]),
])
val_transforms = Compose(common + [EnsureTyped(keys=["image", "label"])])
نحدد التكوين الرئيسي للبرنامج التعليمي، بما في ذلك الجهاز ودليل مجموعة البيانات وحجم التصحيح وحجم الدفعة وعدد العصور وإعدادات ذاكرة التخزين المؤقت. نقوم بعد ذلك بإنشاء خط أنابيب المعالجة المسبقة لأحجام التصوير المقطعي عن طريق تحميل الصور، ومحاذاة الاتجاه، وإعادة تشكيل تباعد الأكسيل، وتوسيع نطاق الكثافات، واقتصاص المقدمة. نحن نحدد أيضًا تحويلات التدريب والتحقق من الصحة، مع خط أنابيب التدريب بما في ذلك الاقتصاص العشوائي والقلبات والتدوير وتحولات الكثافة.
train_ds = DecathlonDataset(
root_dir=root_dir, task="Task09_Spleen", section="training",
transform=train_transforms, download=True, val_frac=0.2,
cache_num=train_cache, num_workers=2, seed=0)
val_ds = DecathlonDataset(
root_dir=root_dir, task="Task09_Spleen", section="validation",
transform=val_transforms, download=False, val_frac=0.2,
cache_num=val_cache, num_workers=2, seed=0)
train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True,
num_workers=2, pin_memory=torch.cuda.is_available())
val_loader = DataLoader(val_ds, batch_size=1, shuffle=False,
num_workers=1, pin_memory=torch.cuda.is_available())
print(f"Train volumes: {len(train_ds)} | Val volumes: {len(val_ds)}")
loss_fn = DiceCELoss(to_onehot_y=True, softmax=True)
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-5)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=max_epochs)
scaler = GradScaler("cuda", enabled=torch.cuda.is_available())
dice_metric = DiceMetric(include_background=False, reduction="mean")
post_pred = Compose([AsDiscrete(argmax=True, to_onehot=2)])
post_label = Compose([AsDiscrete(to_onehot=2)])
نقوم بتحميل مجموعة بيانات Decathlon Task09 Spleen للتقسيم الطبي باستخدام DecathlonDataset الخاص بـ MONAI. نقوم بتقسيم البيانات إلى أقسام للتدريب والتحقق من الصحة، ونطبق التحويلات المناسبة، ونغلف مجموعتي البيانات باستخدام أدوات تحميل البيانات بنمط PyTorch. نقوم بعد ذلك بإنشاء نموذج UNet ثلاثي الأبعاد، وتحديد خسارة DiceCE، وإعداد مُحسِّن AdamW، وجدولة معدل التعلم، ومقياس الدقة المختلطة، ومقياس Dice، وخطوات ما بعد المعالجة.
best_dice, best_epoch = -1.0, -1
loss_hist, dice_hist, dice_epochs = [], [], []
best_path = os.path.join(root_dir, "best_spleen_unet.pth")
for epoch in range(1, max_epochs + 1):
model.train(); epoch_loss, t0 = 0.0, time.time()
for batch in train_loader:
x, y = batch["image"].to(device), batch["label"].to(device)
optimizer.zero_grad(set_to_none=True)
with autocast("cuda", enabled=torch.cuda.is_available()):
logits = model(x)
loss = loss_fn(logits, y)
scaler.scale(loss).backward()
scaler.step(optimizer); scaler.update()
epoch_loss += loss.item()
scheduler.step()
epoch_loss /= len(train_loader); loss_hist.append(epoch_loss)
print(f"[{epoch:3d}/{max_epochs}] loss={epoch_loss:.4f} "
f"lr={scheduler.get_last_lr()[0]:.2e} ({time.time()-t0:.0f}s)")
if epoch % val_every == 0 or epoch == max_epochs:
model.eval(); dice_metric.reset()
with torch.no_grad():
for vb in val_loader:
vx, vy = vb["image"].to(device), vb["label"].to(device)
with autocast("cuda", enabled=torch.cuda.is_available()):
vout = sliding_window_inference(vx, roi_size, 4, model,
overlap=0.5)
vout = [post_pred(o) for o in decollate_batch(vout)]
vlab = [post_label(o) for o in decollate_batch(vy)]
dice_metric(y_pred=vout, y=vlab)
d = dice_metric.aggregate().item()
dice_hist.append(d); dice_epochs.append(epoch)
if d > best_dice:
best_dice, best_epoch = d, epoch
torch.save(model.state_dict(), best_path)
print(f" >> val Dice={d:.4f} (best={best_dice:.4f} @ {best_epoch})")
print(f"\nDone. Best mean Dice {best_dice:.4f} at epoch {best_epoch}.")
نقوم بتشغيل حلقة التدريب الكاملة، حيث يقوم كل عصر بتدريب 3D UNet على بقع حجمية مقصوصة من مجموعة بيانات الطحال. نحن نستخدم دقة مختلطة تلقائية لتقليل استخدام الذاكرة وتسريع التدريب عند توفر وحدة معالجة الرسومات. نحن أيضًا نتحقق من صحة النموذج على فترات منتظمة باستخدام استنتاج النافذة المنزلقة، ونتتبع نتيجة النرد، ونحفظ نقطة التفتيش الأفضل أداءً.
fig, ax = plt.subplots(1, 2, figsize=(12, 4))
ax[0].plot(range(1, len(loss_hist)+1), loss_hist, "-o", ms=3)
ax[0].set(title="Training loss", xlabel="epoch", ylabel="DiceCE loss")
ax[1].plot(dice_epochs, dice_hist, "-o", color="seagreen", ms=4)
ax[1].set(title="Validation mean Dice", xlabel="epoch", ylabel="Dice"); ax[1].set_ylim(0, 1)
plt.tight_layout(); plt.show()
model.load_state_dict(torch.load(best_path, map_location=device)); model.eval()
with torch.no_grad():
sample = next(iter(val_loader))
img = sample["image"].to(device)
with autocast("cuda", enabled=torch.cuda.is_available()):
pred = sliding_window_inference(img, roi_size, 4, model, overlap=0.5)
pred = torch.argmax(pred, dim=1).cpu().numpy()[0]
img_np, lab_np = img.cpu().numpy()[0, 0], sample["label"].numpy()[0, 0]
z = int(np.argmax(lab_np.sum(axis=(0, 1))))
fig, ax = plt.subplots(1, 3, figsize=(13, 5))
ax[0].imshow(img_np[:, :, z], cmap="gray"); ax[0].set_title("CT slice")
ax[1].imshow(lab_np[:, :, z], cmap="viridis"); ax[1].set_title("Ground truth")
ax[2].imshow(pred[:, :, z], cmap="viridis"); ax[2].set_title("Prediction")
for a in ax: a.axis("off")
plt.tight_layout(); plt.show()
نقوم أولاً بتخطيط خسارة التدريب ونقاط التحقق من صحة النرد لنرى كيف يتحسن النموذج بمرور الوقت. نقوم بعد ذلك بإعادة تحميل نقطة تفتيش النموذج الأفضل حفظًا وتشغيل الاستدلال على وحدة تخزين تحقق واحدة باستخدام تنبؤ النافذة المنزلقة. نحن نتصور شريحة الأشعة المقطعية وقناع الحقيقة الأرضية والتجزئة المتوقعة جنبًا إلى جنب لفحص الأداء النوعي للنموذج.
في الختام، انتهينا من سير العمل العملي القائم على MONAI لتجزئة الطحال ثلاثية الأبعاد باستخدام نموذج UNet ثلاثي الأبعاد. قمنا بإعداد مجموعة بيانات Decathlon للتجزئة الطبية، وقمنا بتحويل وزيادة أحجام التصوير المقطعي المحوسب، وقمنا بتدريب النموذج على خسارة DiceCE، والتحقق من صحته باستخدام استدلال النافذة المنزلقة، وتتبع كل من الخسارة ودرجة Dice بمرور الوقت. قمنا أيضًا بفحص التنبؤ النهائي بصريًا من خلال مقارنة شريحة CT وتسمية الحقيقة الأرضية ومخرجات النموذج جنبًا إلى جنب. الآن، لدينا فهم واضح لكيفية دعم MONAI لمهام التجزئة الطبية بدءًا من تحميل البيانات والمعالجة المسبقة وحتى التدريب النموذجي والتقييم وفحص الفحص والتحليل النوعي.
تحقق من رموز كاملة مع دفتر الملاحظات. أيضا، لا تتردد في متابعتنا على تغريد ولا تنسى الانضمام إلينا 150 ألف+ مل من SubReddit والاشتراك في النشرة الإخبارية لدينا. انتظر! هل أنت على برقية؟ الآن يمكنك الانضمام إلينا على التليجرام أيضًا.
هل تحتاج إلى الشراكة معنا للترويج لصفحة GitHub Repo أو صفحة الوجه المعانقة أو إصدار المنتج أو الندوة عبر الويب وما إلى ذلك؟تواصل معنا
ظهر هذا المنشور تنفيذ الترميز على MONAI لتقسيم الطحال ثلاثي الأبعاد من طرف إلى طرف باستخدام UNet على وحدات تخزين الأشعة المقطعية الطبية لأول مرة على MarkTechPost.
اكتشاف المزيد من كحيل | أخبار التقنية
اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.
