Автор Амин Буранкулов, Cerebra.ai Ltd.
Введение
В настоящее время использование методов глубокого обучения все больше распространяется в области медицины. Как следствие, эти подходы подлежат постоянному совершенствованию в этой области. С каждым годом появляются новые технологии и решения, которые оптимизируют и повышают качество различных моделей машинного обучения для различных задач медицины.
Одной из таких задач в области здравоохранения является выявление инсульта, который является основной причиной смертности людей. В свете этого технология сегментации сосудов может иметь жизненно важное значение для его обнаружения. Подробнее об инсульте и его статистике можно узнать здесь.
В зависимости от контрастности сегментированного сосуда можно будет понять, поврежден ли он. В нашем случае мы сможем сегментировать правильно выраженные сосуды, а вышедшие или порвавшиеся пропустить, указав тем самым место поражения. Причиной ишемического инсульта является тромб, закрывающий просвет сосудов головного мозга. Следовательно, врач сможет увидеть, где именно и из какого сосуда образовался тромб.
Сегментация сосудов является важной задачей, так как помогает врачам гораздо быстрее и точнее выявить инсульт, тем самым обеспечить своевременное лечение и предотвратить необратимые последствия.
В этой статье я покажу свои попытки сегментации сосудов, начиная со сбора данных и заканчивая результатами модели.
Одной из трудностей, с которыми я столкнулся, является отсутствие достаточного количества данных. Ведь на сегодняшний день очень сложно получить медицинские снимки, ввиду их недоступности из медицинских учреждений. Однако я попытался компенсировать это увеличением данных с помощью псевдоразметки.
Рис. 1. Изображение сосудов внутри человека.
Каковы наши данные?
Так как я сотрудник медицинского стартапа, мы получили несколько случаев СТА от больниц, с которыми сотрудничаем. Всего 22 дела. Каждое дело представляет собой файл формата nifti, размера (512, 512) и с разным количеством ломтиков (в среднем около 600 ломтиков).
Рис. 2 и 3. Вид СТА-скана с разных позиций.
Однако, как мы знаем, для обучения модели, определяющей сосуды, одних CTA-кейсов нам будет недостаточно, нужна разметка. Увы, но маркировка судов очень сложный и долгий процесс, и у нас не хватило средств на его осуществление, поэтому мы решили провести маркировку искусственно.
Во-первых, мне пришлось сделать такую процедуру, как корегистрация, с помощью которой я преобразовал СТА в КТ, тем самым получив маску черепа в СТА. Затем я избавился от маски черепа и использовал максимальную интенсивность шкалы, чтобы подчеркнуть сосуды. Вот и все, мы получили свои наценки!
Рис. 4–5: Вид маски сосудов с разных позиций
Как мы разделили данные на обучение и тестирование?
Набор данных поезда состоит из 18 случаев, а набор тестовых данных состоит из 4 случаев. Образец изображения и маска тестовых данных выглядят как примеры на рисунках 2–5, в то время как для данных поезда были использованы некоторые этапы предварительной обработки.
Предварительная обработка
Поскольку 3D-изображения занимают много памяти, было принято решение использовать скользящее окно, обрезая обучающие изображения срезами с определенным шагом.
import nibabel as nb def sliding_window(image_path: str, label_path: str, step: int = 32, windowSize: Tuple[int]=(512, 512, 64) ) -> List: # read image image = nb.load(image_path).get_fdata() label = nb.load(label_path).get_fdata() # make sliding window sw_images = []; sw_labels = [] for i in range(0, image.shape[-1], step): sw_image = image[:windowSize[0], :windowSize[1], i:windowSize[2]+i] sw_images.append(sw_image) sw_label = label[:windowSize[0], :windowSize[1], i:windowSize[2]+i] sw_labels.append(sw_label) return sw_images, sw_labels
После этого мы получили 321 срез кейсов, прошедших серию аугментаций из библиотеки МОНАИ:
def vessel_train_sw_aug(): train_transforms = Compose( [ LoadImaged(keys=[“image”, “label”]), AddChanneld(keys=[“image”, “label”]), ScaleIntensityRanged(keys=[“image”], a_min=-50, a_max=150, b_min=0, b_max=1, clip=True), RandFlipd(keys=[“image”, “label”], prob=0.5, spatial_axis=0), RandFlipd(keys=[“image”, “label”], prob=0.5, spatial_axis=1), RandFlipd(keys=[“image”, “label”], prob=0.5, spatial_axis=2), RandAffined( keys=[‘image’, ‘label’], mode=(‘bilinear’, ‘nearest’), rotate_range=(0, 0, np.pi/15), scale_range=(0.1, 0.1, 0.1)), MoreThanZero(keys=[“label”]), EnsureTyped(keys=[“image”, “label”]) ] ) return train_transforms
Рис. 6–7: Вид образца поезда и маски после предварительной обработки.
Модель
В качестве модели я взял 3D UNet, а в качестве функции потерь выбрал DiceLoss из библиотеки MONAI. Поскольку мы будем обучаться на 3D-сканах, укажем, что space_dims равен 3. Оптимизатор — AdamW и планировщик — CosineAnnealingWarmRestarts.
import monai import torch device = torch.device(“cuda” if torch.cuda.is_available() else “cpu”) model = monai.networks.nets.UNet( spatial_dims=3, in_channels=1, out_channels=1, channels=(16, 32, 64, 128), strides=(2, 2, 2, 2), num_res_units=2, ).to(device) loss_function = monai.losses.DiceLoss(sigmoid=True) optimizer = torch.optim.AdamW(model.parameters(), 0.001) scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, T_0=1, T_mult=2)
Модель обучалась в течение 350 эпох с размером пакета 1 и оценивалась с помощью метрики в кости.
Результаты
После обучения модели лучший средний балл из 4 проверочных случаев достиг 0,91 кости.
Анимация 1: выходные данные модели после ее использования в одном из случаев проверки. Прогноз сверху, разметка снизу.
Рис. 8: График потерь поездов
Рис. 9: График потерь при проверке
Заключение
Подводя итог, можно сказать, что выявление инсульта является основной проблемой в медицинской отрасли. Сегментация сосудов помогает решить эту проблему, поскольку позволяет врачам гораздо быстрее и правильнее идентифицировать инсульт.
Мы взяли изображения CTA из медицинских учреждений и обучили их 3D UNet, что дало нам довольно хорошие результаты.