基于MONAI与PAI-DSW的2D医学影像配准:MedNIST手部X光片实战
本实验将引导您使用业界优秀的医疗AI框架MONAI,在阿里云PAI-DSW环境中,完成一个经典的2D医学影像配准任务。您将学习如何训练一个深度学习网络,使其能够自动对一张经过旋转和缩放的“移动图像”进行变换,从而与原始的“固定图像”精确对齐。
实验简介
本实验将引导您使用业界优秀的医疗AI框架MONAI,在阿里云PAI-DSW环境中,完成一个经典的2D医学影像配准任务。您将学习如何训练一个深度学习网络,使其能够自动对一张经过旋转和缩放的“移动图像”进行变换,从而与原始的“固定图像”精确对齐。
背景知识
医学影像配准 (Medical Image Registration): 这是医疗AI中的一个核心任务,其目标是寻找一种空间变换,使得一张或多张图片(移动图像)能与另一张参考图片(固定图像)在空间位置上对齐。它在临床上意义重大,例如,通过对齐患者不同时期的CT扫描来评估病情变化,或者融合不同模态的影像(如MRI和PET)以获得更全面的诊断信息。
MONAI框架: MONAI (Medical Open Network for AI) 是一个基于PyTorch、由社区共同维护的开源框架,专门为医疗影像AI设计。它提供了大量针对医疗数据的预处理、数据增强、标准网络模型(如U-Net)和工作流工具,是目前进行医疗影像研究和开发的首选框架。
MONAI框架: MONAI (Medical Open Network for AI) 是一个基于PyTorch、由社区共同维护的开源框架,专门为医疗影像AI设计。它提供了大量针对医疗数据的预处理、数据增强、标准网络模型(如U-Net)和工作流工具,是目前进行医疗影像研究和开发的首选框架。
MONAI框架: MONAI (Medical Open Network for AI) 是一个基于PyTorch、由社区共同维护的开源框架,专门为医疗影像AI设计。它提供了大量针对医疗数据的预处理、数据增强、标准网络模型(如U-Net)和工作流工具,是目前进行医疗影像研究和开发的首选框架。
实验室资源方式简介
进入实操前,请确保阿里云账号满足以下条件:
个人账号资源
使用您个人的云资源进行操作,资源归属于个人。
平台仅提供手册参考,不会对资源做任何操作。
确保已完成云工开物 300 元代金券领取。
已通过实名认证且账户余额 ≥0 元。
在实验页面,当您已阅读并同意上述创建资源的目的以及部分资源可能产生的计费规则。
资源消耗说明
本场景主要涉及以下云产品和服务:PAI-DSW、对象存储OSS。
本实验预计产生资源消耗:约10元(以使用ecs.gn6i-c8g1.2xlarge规格的PAI-DSW实例进行1小时的数据处理与模型训练为例估算)。
如果您调整了资源规格、延长了使用时长,或执行了本方案以外的操作,可能导致费用发生变化,请以控制台显示的实际价格和最终账单为准。
PAI-DSW: 费用主要由DSW实例的运行时长和其规格决定。本实验选用GPU实例进行模型训练,关机后即停止计费。
对象存储 OSS: 费用由数据存储容量和少量外网下行流量(仅在下载结果时产生)决定。
领取专属权益及创建实验资源
第一步:在开始实验之前,请先点击右侧屏幕的“进入实操”再进行后续操作

第二步:本次实验需要您通过领取阿里云云工开物学生专属300元抵扣券兑换本次实操的云资源,如未领取请先点击领取。(若已领取请跳过)
重要实验产生的费用优先使用优惠券,优惠券使用完毕后需您自行承担。

实验步骤
进入DSW控制台
登录阿里云,进入机器学习PAI控制台,在左侧导航栏选择【工作空间列表】,点击进入您的工作空间

在工作空间内,选择左侧的【模型开发与训练】—【DSW(Data Science Workshop)】

创建DSW实例
点击【创建实例】

实例名称:自定义一个名称,如 medical-2d-regist
资源组(机型):为了进行深度学习模型训练,我们需要选择GPU实例。点击【筛选】,勾选【GPU】,然后选择一款有库存的GPU机型,例如 ecs.gn6i-c8g1.2xlarge(vCPU: 8, 内存: 32GiB, GPU: NVIDIA T4 16GB)
说明这是成本消耗的主要来源,请务必注意实验后及时停止或删除实例!

镜像:选择一个预置了PyTorch框架的镜像,例如 pytorch:1.12-gpu-py39-cu113-ubuntu20.04

其他保持默认,选择完成后点击【确定】

等待约2-3分钟,直到实例状态变为“运行中”

进入JupyterLab环境
在DSW实例列表中,找到刚刚创建的实例,点击右侧的【打开】
返回JupyterLab的启动器(Launcher)页面,点击【Python 3 (PyTorch 1.12)】
创建一个新的Notebook文件

安装MONAI并导入环境
%env BUILD_MONAI=1 !python -c "import monai" || pip install -q "monai[all]" from monai.utils import set_determinism, first from monai.transforms import ( EnsureChannelFirstD, Compose, LoadImageD, RandRotateD, RandZoomD, ScaleIntensityRanged, ) from monai.data import DataLoader, Dataset, CacheDataset from monai.config import print_config, USE_COMPILED from monai.networks.nets import GlobalNet from monai.networks.blocks import Warp from monai.apps import MedNISTDataset import numpy as np import torch from torch.nn import MSELoss import matplotlib.pyplot as plt import os import tempfile print_config() set_determinism(42)导入必要的库
这是我们的第一步,导入所有需要用到的Python库。
import os import cv2 import random import numpy as np from tqdm import tqdm import torch import torch.nn as nn from torch.utils.data import Dataset, DataLoader from torchvision import transforms import matplotlib.pyplot as plt # 检查是否有可用的GPU device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print(f"Using device: {device}")准备MedNIST数据集并创建训练对
MONAI内置了常用数据集的下载工具。
我们将使用它自动下载MedNIST数据集,并筛选出其 中的“手部X光片”作为实验数据。
- 说明
核心思想: 对于每一张手部X光片,我们都创建一对图像:一张是原始的“固定图像”(fixed_hand),另一张是它的拷贝,我们将其称为“移动图像”(moving_hand),后续会对它进行随机变换。
# 设置数据存储目录 root_dir = tempfile.mkdtemp() print(f"数据将下载到临时目录: {root_dir}") # 下载数据并筛选出手部X光片(label=4) train_data = MedNISTDataset(root_dir=root_dir, section="training", download=True, transform=None) training_datadict = [ {"fixed_hand": item["image"], "moving_hand": item["image"]} for item in train_data.data if item["label"] == 4 ] print(f"\n成功加载 {len(training_datadict)} 张手部X光片。")定义数据增强并可视化训练样本(Dataset)
- 重要
重点说明: 影像配准任务的关键在于,我们需要人为地对“移动图像”进行随机的旋转和缩放,制造出它与“固定图像”之间的不对齐。这样,模型学习的目标就是预测出这个变换,把它“变回去”。
# 定义数据预处理和增强流程 train_transforms = Compose( [ LoadImageD(keys=["fixed_hand", "moving_hand"]), EnsureChannelFirstD(keys=["fixed_hand", "moving_hand"]), ScaleIntensityRanged(keys=["fixed_hand", "moving_hand"], a_min=0.0, a_max=255.0, b_min=0.0, b_max=1.0, clip=True), # 只对 "moving_hand" 进行随机旋转和缩放 RandRotateD(keys=["moving_hand"], range_x=np.pi / 4, prob=1.0, keep_size=True, mode="bicubic"), RandZoomD(keys=["moving_hand"], min_zoom=0.9, max_zoom=1.1, prob=1.0, mode="bicubic", align_corners=False), ] ) # 可视化一对处理后的图像,直观感受变换效果 check_ds = Dataset(data=training_datadict, transform=train_transforms) check_loader = DataLoader(check_ds, batch_size=1, shuffle=True) check_data = first(check_loader) fixed_image = check_data["fixed_hand"][0][0] moving_image = check_data["moving_hand"][0][0] plt.figure("check", (12, 6)) plt.subplot(1, 2, 1) plt.title("Moving Image (After Transform)") plt.imshow(moving_image, cmap="gray") plt.subplot(1, 2, 2) plt.title("Fixed Image (Original)") plt.imshow(fixed_image, cmap="gray") plt.show()
定义模型并开始训练
我们将使用MONAI提供的GlobalNet模型,它能预测出全局的仿射变换参数。Warp层则根据预测的参数来对移动图像进行变换。损失函数使用简单的均方误差(MSE),衡量变换后的图像与固定图像之间的像素差异。
# 使用CacheDataset加速训练 train_ds = CacheDataset(data=training_datadict[:1000], transform=train_transforms, cache_rate=1.0) train_loader = DataLoader(train_ds, batch_size=16, shuffle=True) # 初始化模型、损失函数和优化器 device = torch.device("cuda:0") model = GlobalNet(image_size=(64, 64), spatial_dims=2, in_channels=2, num_channel_initial=16, depth=3).to(device) image_loss = MSELoss() warp_layer = Warp("bilinear", "border").to(device) optimizer = torch.optim.Adam(model.parameters(), 1e-5) # 开始训练 max_epochs = 200 epoch_loss_values = [] for epoch in range(max_epochs): model.train() epoch_loss = 0 step = 0 for batch_data in train_loader: step += 1 optimizer.zero_grad() moving = batch_data["moving_hand"].to(device) fixed = batch_data["fixed_hand"].to(device) ddf = model(torch.cat((moving, fixed), dim=1)) pred_image = warp_layer(moving, ddf) loss = image_loss(pred_image, fixed) loss.backward() optimizer.step() epoch_loss += loss.item() epoch_loss /= step epoch_loss_values.append(epoch_loss) if (epoch + 1) % 20 == 0: # 每20个epoch打印一次日志 print(f"Epoch {epoch + 1}/{max_epochs}, Average Loss: {epoch_loss:.4f}") print("训练完成!")验证模型效果并可视化结果
定义损失函数和优化器,然后编写训练循环。我们会遍历数据集EPOCHS次,并在每个周期结束后打印损失值。
# 准备验证数据 val_ds = CacheDataset(data=training_datadict[2000:2500], transform=train_transforms, cache_rate=1.0) val_loader = DataLoader(val_ds, batch_size=16) # 获取一个批次的预测结果 model.eval() with torch.no_grad(): for batch_data in val_loader: moving = batch_data["moving_hand"].to(device) fixed = batch_data["fixed_hand"].to(device) ddf = model(torch.cat((moving, fixed), dim=1)) pred_image = warp_layer(moving, ddf) break # 只取第一个batch进行可视化 # 将数据转为numpy用于绘图 fixed_image_np = fixed.detach().cpu().numpy()[:, 0] moving_image_np = moving.detach().cpu().numpy()[:, 0] pred_image_np = pred_image.detach().cpu().numpy()[:, 0] # 绘图 batch_size_to_show = 5 plt.figure(figsize=(8, 10)) for b in range(batch_size_to_show): plt.subplot(batch_size_to_show, 3, b * 3 + 1) if b == 0: plt.title("Moving Image") plt.imshow(moving_image_np[b], cmap="gray") plt.axis("off") plt.subplot(batch_size_to_show, 3, b * 3 + 2) if b == 0: plt.title("Fixed Image") plt.imshow(fixed_image_np[b], cmap="gray") plt.axis("off") plt.subplot(batch_size_to_show, 3, b * 3 + 3) if b == 0: plt.title("Predicted Image") plt.imshow(pred_image_np[b], cmap="gray") plt.axis("off") plt.show()

清理资源
为避免产生不必要的个人扣费,实验完成后请务必按照以下步骤清理所有资源!
释放PAI-DSW实例
返回 机器学习PAI控制台 的DSW实例列表页面
找到本次实验创建的实例,点击右侧的【停止】

等待实例状态变为“已停止”后,为确保完全释放,再次点击右侧的【...】更多操作,选择【删除】

在弹出的确认框中点击【停止实例】/【删除实例】

等待一段时间检查是否删除成功

删除OSS数据和Bucket
进入 对象存储OSS控制台,找到为本次实验创建的Bucket,点击进入
选中所有上传的数据文件和文件夹,点击【删除】,返回Bucket列表,选中该Bucket,点击【删除】,根据提示完成删除操作(可能需要清空碎片)

关闭实验
在完成实验后,点击 结束实操

点击 取消 回到实验页面,点击 确定 跳转实验评分

请为本次实验评分,并给出您的建议,点击 确认,结束本次实验

















