Автор Амин Буранкулов, 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, что дало нам довольно хорошие результаты.