Home Искусственный интеллект Введение в трехмерную медицинскую визуализацию для машинного обучения: предварительная обработка и дополнения | DeepTech

Введение в трехмерную медицинскую визуализацию для машинного обучения: предварительная обработка и дополнения | DeepTech

0
Введение в трехмерную медицинскую визуализацию для машинного обучения: предварительная обработка и дополнения
 | DeepTech

Когда я понял, что не могу применять обычные конвейеры обработки изображений к медицинским изображениям, я был полностью обескуражен. Почему такого функционала нет? Итак, я придумал этот пост (плюс блокнот) для отчаявшихся людей, которые, как и я, заинтересованы в решении проблем медицинской визуализации.

Мы уже обсудили сегментацию медицинских изображений и некоторые начальные сведения о системах координат и файлах DICOM. Мой опыт в этой области позволяет мне продолжать анализ данных, предварительную обработку и некоторые дополнения. Как я всегда говорю, если вы просто понимаете свои данные и их особенности, вы, вероятно, играете в бинго. В области медицинской визуализации я считаю, что некоторые манипуляции с данными, которые широко используются при предварительной обработке и дополнении в современных методах, имеют решающее значение для нашего понимания. С этой целью я предоставляю ноутбук для всех, чтобы поиграть. Он выполняет преобразования медицинских изображений, которые представляют собой просто структурированную трехмерную сетку.

Чтобы глубже погрузиться в то, как ИИ используется в медицине, вы не ошибетесь с ИИ для медицины онлайн-курс, предлагаемый Coursera. Если вы хотите сосредоточиться на анализе медицинских изображений с помощью глубокого обучения, я настоятельно рекомендую начать с Курс Udemy на основе Pytorch.

Данные: Мы будем играть с двумя изображениями МРТ, которые предоставлены nibabel (библиотека Python) для иллюстрации. Изображения хранятся в виде изящных файлов. Но перед этим давайте напишем код для визуализации трехмерных медицинских объемов.

Изображения будут показаны в 3 плоскостях: сагиттальной, коронарной и аксиальной, если смотреть слева направо на протяжении всего поста.

Визуализация двумерных плоскостей

На протяжении всего урока мы будем широко использовать функцию, которая визуализирует три срединных среза в сагиттальный, корональный и аксиальный самолетов соответственно. Напишем для этого минимальную функцию:

def show_mid_slice(img_numpy, title='img'):

"""

Accepts an 3D numpy array and shows median slices in all three planes

"""

assert img_numpy.ndim == 3

n_i, n_j, n_k = img_numpy.shape

center_i1 = int((n_i - 1) / 2)

center_j1 = int((n_j - 1) / 2)

center_k1 = int((n_k - 1) / 2)

show_slices((img_numpy(center_i1, :, :),

img_numpy(:, center_j1, :),

img_numpy(:, :, center_k1)))

plt.suptitle(title)

def show_slices(slices):

"""

Function to display a row of image slices

Input is a list of numpy 2D image slices

"""

fig, axes = plt.subplots(1, len(slices))

for i, slice in enumerate(slices):

axes(i).imshow(slice.T, cmap="gray", origin="lower")

Ничего больше, чем matplotlib “imshow" и манипуляции с массивом numpy. Для справки, медицинские изображения представляют собой один канал, и мы визуализируем их в оттенках серого.

Два изображения, которые мы будем использовать для игры с множеством преобразований, можно проиллюстрировать ниже:

show_mid_slice(epi_img_numpy, 'first image')

show_mid_slice(anatomy_img_numpy,'second image')


мозг-мрис-учебник-нибабель


Исходные снимки МРТ головного мозга, которые мы будем использовать.

Теперь мы готовы идти! Начнем с изменения размера и масштабирования медицинских изображений. Да, это не совсем то же самое.

Изменение размера медицинского изображения (уменьшение/повышение дискретизации)

острый библиотека предоставляет множество функций для многомерных изображений. Поскольку медицинские изображения являются трехмерными, можно использовать множество функций. На этот раз мы будем использовать scipy.ndimage.interpolation.zoom для изменения размера изображения в желаемых размерах. Это похоже на понижение частоты дискретизации в 2D-изображении. Эту же функцию можно использовать для интерполяции для увеличения пространственных размеров. В качестве иллюстрации мы удвоим и уменьшим вдвое исходный размер изображения.

Имейте в виду, что при таком преобразовании обычно важно соблюдать соотношения.

Вы, наверное, не хотите потерять анатомию человеческого тела 🙂

import scipy

def resize_data_volume_by_scale(data, scale):

"""

Resize the data based on the provided scale

"""

scale_list = (scale,scale,scale)

return scipy.ndimage.interpolation.zoom(data, scale_list, order=0)

result = resize_data_volume_by_scale(epi_img_numpy, 0.5)

result2 = resize_data_volume_by_scale(epi_img_numpy, 2)

Такой вид масштабирования обычно называют изометрическим. Честно говоря, я не большой поклонник терминологии scipy, чтобы использовать слово масштабирование для этой функциональности.


МРТ-изображения с измененным размером


Изображение с пониженной и повышенной частотой дискретизации в 2 раза

Для тяжелого машинного обучения очень часто понижают разрешение изображения в более низком измерении.

Обратите внимание, что существует еще один тип изменения размера. Вместо предоставления желаемой выходной формы вы указываете желаемый размер вокселя (т.е. voxel_size=(1,1,1) мм). Nibabel предоставляет функцию resample_to_output(). Он работает с файлами nifti, а не с массивами numpy. Честно говоря, я бы не рекомендовал его в одиночку, так как полученные изображения могут иметь разную форму. Это может быть проблемой для глубокого обучения. Например, для создания пакетов с загрузчиками данных измерение должно быть одинаковым для всех экземпляров. Однако вы можете включить его на предыдущем этапе конвейера. Его можно использовать для приведения разных изображений к одинаковому или близкому размеру вокселя.

Масштабирование медицинских изображений (увеличение/уменьшение)

Масштабирование можно рассматривать как аффинное преобразование. Мы будем случайным образом увеличивать и уменьшать изображение. Это увеличение обычно помогает модели изучить масштабно-инвариантные функции.

def random_zoom(matrix,min_percentage=0.7, max_percentage=1.2):

z = np.random.sample() *(max_percentage-min_percentage) + min_percentage

zoom_matrix = np.array(((z, 0, 0, 0),

(0, z, 0, 0),

(0, 0, z, 0),

(0, 0, 0, 1)))

return ndimage.interpolation.affine_transform(matrix, zoom_matrix)


увеличение-уменьшение-мрт


Случайное увеличение и уменьшение масштаба

Важно видеть, что пустая область заполнена черными пикселями (нулевой интенсивности).

Обратите внимание, что окружающий воздух на медицинских изображениях не имеет нулевой интенсивности. Черный действительно связан с медицинскими изображениями.

Следующим в списке является 3D-вращение.

Вращение медицинских изображений

Вращение — один из наиболее распространенных методов увеличения данных в компьютерном зрении. Это также считается методом самоконтроля с замечательными результатами (Спирос Гидарис и др. ).

В медицинской визуализации это аналогичная функциональность импорта, которая также использовалась при предварительном обучении с самоконтролем (Синьруи Чжуан и др. 2019 ). Простой случайный трехмерный поворот в заданном диапазоне градусов можно проиллюстрировать кодом ниже:

def random_rotate3D(img_numpy, min_angle, max_angle):

"""

Returns a random rotated array in the same shape

:param img_numpy: 3D numpy array

:param min_angle: in degrees

:param max_angle: in degrees

"""

assert img_numpy.ndim == 3, "provide a 3d numpy array"

assert min_angle < max_angle, "min should be less than max val"

assert min_angle > -360 or max_angle < 360

all_axes = ((1, 0), (1, 2), (0, 2))

angle = np.random.randint(low=min_angle, high=max_angle+1)

axes_random_id = np.random.randint(low=0, high=len(all_axes))

axes = all_axes(axes_random_id)

return scipy.ndimage.rotate(img_numpy, angle, axes=axes)


вращение3d


Случайное 3D-вращение

Нам просто нужно определить ось и угол поворота. Поскольку масштабирование предоставило модели больше разнообразия для изучения инвариантных к масштабу функций, вращение помогает в изучении инвариантных к вращению функций.

Следующим в нашем списке является переворачивание изображений.

Переворот медицинского изображения

Подобно обычным изображениям RGB, мы можем выполнять переворот оси в медицинских изображениях. На данный момент очень важно прояснить одну вещь:

Когда мы выполняем дополнения и/или предварительную обработку наших данных, нам, возможно, придется применить аналогичные операции к данным наземной достоверности.

Например, если мы решаем задачу сегментации медицинских изображений, важно перевернуть карту целевой сегментации. Простую реализацию можно найти ниже:

def random_flip(img, label=None):

axes = (0, 1, 2)

rand = np.random.randint(0, 3)

img = flip_axis(img, axes(rand))

img = np.squeeze(img)

if label is None:

return img

else:

y = flip_axis(y, axes(rand))

y = np.squeeze(y)

return x, y

def flip_axis(x, axis):

x = np.asarray(x).swapaxes(axis, 0)

x = x(::-1, ...)

x = x.swapaxes(0, axis)

return x


переворот изображения


Исходное изображение в качестве эталона и две перевернутые версии

Обратите внимание, что при повороте одной оси меняются два вида. Первое изображение сверху является исходным изображением в качестве эталона.

Медицинское смещение изображения (смещение)

Здесь я хотел бы рассказать о другом.

Вращение, смещение и масштабирование — не что иное, как аффинные преобразования.

Иногда я реализую их, просто определяя аффинные преобразования и применяя их к изображению с помощью scipy, а иногда использую уже реализованные функции для многомерной обработки изображений.

Чтобы использовать эту операцию в моем конвейере увеличения данных, вы можете видеть, что я включил функцию-оболочку. Последний в основном выбирает случайное число, обычно в желаемом диапазоне, и вызывает функцию аффинного преобразования. Ниже приведена реализация для случайного смещения/смещения.

def transform_matrix_offset_center_3d(matrix, x, y, z):

offset_matrix = np.array(((1, 0, 0, x), (0, 1, 0, y), (0, 0, 1, z), (0, 0, 0, 1)))

return ndimage.interpolation.affine_transform(matrix, offset_matrix)

def random_shift(img_numpy, max_percentage=0.4):

dim1, dim2, dim3 = img_numpy.shape

m1,m2,m3 = int(dim1*max_percentage/2),int(dim1*max_percentage/2), int(dim1*max_percentage/2)

d1 = np.random.randint(-m1,m1)

d2 = np.random.randint(-m2,m2)

d3 = np.random.randint(-m3,m3)

return transform_matrix_offset_center_3d(img_numpy, d1, d2, d3)


смещенный-мрис


Смещенные медицинские изображения

Эта аугментация не очень распространена в аугментации медицинских изображений, но мы включили их сюда для полноты картины.

Причина, по которой мы ее не включаем, заключается в том, что сверточные нейронные сети по определению предназначены для изучения свойств, инвариантных к трансляции.

Случайная 3D-обрезка

Обрезка также не сильно отличается от естественных изображений. Однако имейте в виду, что обычно нам приходится брать все срезы измерения, и нам нужно позаботиться об этом. Причина в том, что одно измерение может иметь меньше срезов, чем другие. Например, однажды мне пришлось иметь дело с изображением размером 384x384x64, которое часто встречается в КТ-изображения.

def crop_3d_volume(img_tensor, crop_dim, crop_size):

assert img_tensor.ndim == 3, '3d tensor must be provided'

full_dim1, full_dim2, full_dim3 = img_tensor.shape

slices_crop, w_crop, h_crop = crop_dim

dim1, dim2, dim3 = crop_size

if full_dim1 == dim1:

img_tensor = img_tensor(:, w_crop:w_crop + dim2,

h_crop:h_crop + dim3)

elif full_dim2 == dim2:

img_tensor = img_tensor(slices_crop:slices_crop + dim1, :,

h_crop:h_crop + dim3)

elif full_dim3 == dim3:

img_tensor = img_tensor(slices_crop:slices_crop + dim1, w_crop:w_crop + dim2, :)

else:

img_tensor = img_tensor(slices_crop:slices_crop + dim1, w_crop:w_crop + dim2,

h_crop:h_crop + dim3)

return img_tensor

def find_random_crop_dim(full_vol_dim, crop_size):

assert full_vol_dim(0) >= crop_size(0), "crop size is too big"

assert full_vol_dim(1) >= crop_size(1), "crop size is too big"

assert full_vol_dim(2) >= crop_size(2), "crop size is too big"

if full_vol_dim(0) == crop_size(0):

slices = crop_size(0)

else:

slices = np.random.randint(full_vol_dim(0) - crop_size(0))

if full_vol_dim(1) == crop_size(1):

w_crop = crop_size(1)

else:

w_crop = np.random.randint(full_vol_dim(1) - crop_size(1))

if full_vol_dim(2) == crop_size(2):

h_crop = crop_size(2)

else:

h_crop = np.random.randint(full_vol_dim(2) - crop_size(2))

return (slices, w_crop, h_crop)


случайная обрезка


Пример случайной обрезки

Существуют и другие методы обрезки, которые фокусируются на интересующей нас области, например на опухоли, но мы не будем сейчас в них углубляться.

До сих пор мы играли с геометрическими преобразованиями. Давайте посмотрим, что мы можем сделать с интенсивностью изображения.

Значения интенсивности клипа (выбросы)

Этот шаг неприменим для этого урока, но в целом он может оказаться весьма полезным. Особенно для КТ-изображения. Причина, по которой это неприменимо, заключается в том, что изображения МРТ находятся в довольно узком диапазоне значений.

def percentile_clip(img_numpy, min_val=5, max_val=95):

"""

Intensity normalization based on percentile

Clips the range based on the quartile values.

:param min_val: should be in the range (0,100)

:param max_val: should be in the range (0,100)

:return: intensity normalized image

"""

low = np.percentile(img_numpy, min_val)

high = np.percentile(img_numpy, max_val)

img_numpy(img_numpy < low) = low

img_numpy(img_numpy > high) = high

return img_numpy

def clip_range(img_numpy, min_intensity=10, max_intensity=240):

return np.clip(img_numpy, min_intensity, max_intensity)

Нормализация интенсивности в медицинских изображениях

Здесь я включаю наиболее распространенные нормализации интенсивности: минимум-максимум и среднее/стандартное значение. Следует иметь в виду одну маленькую вещь:

Когда мы выполняем нормализацию среднего/стандартного значения, мы обычно опускаем воксели с нулевой интенсивностью при расчете среднего. Это справедливо в основном для изображений МРТ.

Один из способов взглянуть на это, если у нас есть изображение мозга; мы, вероятно, не хотим нормализовать его с интенсивностью вокселей вокруг него.

def normalize_intensity(img_tensor, normalization="mean"):

"""

Accepts an image tensor and normalizes it

:param normalization: choices = "max", "mean" , type=str

For mean normalization we use the non zero voxels only.

"""

if normalization == "mean":

mask = img_tensor.ne(0.0)

desired = img_tensor(mask)

mean_val, std_val = desired.mean(), desired.std()

img_tensor = (img_tensor - mean_val) / std_val

elif normalization == "max":

MAX, MIN = img_tensor.max(), img_tensor.min()

img_tensor = (img_tensor - MIN) / (MAX - MIN)

return img_tensor

Нет смысла визуализировать это преобразование, поскольку его цель — передать предварительно обработанные данные в модель глубокого обучения. Конечно, любой другой вид нормализации интенсивности может применяться к медицинским изображениям.

Упругая деформация

Когда я впервые прочитал это преобразование в оригинальной статье Unet, я не понял ни единого слова из абзаца:

«Что касается наших задач, то доступных обучающих данных очень мало, мы используем избыточную аугментацию данных, применяя упругие деформации к имеющимся обучающим изображениям. Это позволяет сети научиться инвариантности к таким деформациям без необходимости видеть эти преобразования в аннотированном корпусе изображений. Это особенно важно в биомедицинской сегментации, поскольку деформация раньше была наиболее распространенным изменением ткани, и реалистичные деформации можно эффективно моделировать» ~ Олаф Роннебергер и др. 2015 (бумага Unet)

Честно говоря, я не заглядывал в оригинальную публикацию 2003 года. И вы, вероятно, тоже не будете. Я изучил некоторые другие реализации кода и попытался сделать его более простым. Я решил включить его в свой учебник, потому что вы часто встретите его в литературе.

def elastic_transform_3d(image, labels=None, alpha=4, sigma=35, bg_val=0.1):

"""

Elastic deformation of images as described in

Simard, Steinkraus and Platt, "Best Practices for

Convolutional Neural Networks applied to Visual

Document Analysis", in

Proc. of the International Conference on Document Analysis and

Recognition, 2003.

Modified from:

https://gist.github.com/chsasank/4d8f68caf01f041a6453e67fb30f8f5a

https://github.com/fcalvet/image_tools/blob/master/image_augmentation.py#L62

Modified to take 3D inputs

Deforms both the image and corresponding label file

image linear/trilinear interpolated

Label volumes nearest neighbour interpolated

"""

assert image.ndim == 3

shape = image.shape

dtype = image.dtype

coords = np.arange(shape(0)), np.arange(shape(1)), np.arange(shape(2))

im_intrps = RegularGridInterpolator(coords, image,

method="linear",

bounds_error=False,

fill_value=bg_val)

dx = gaussian_filter((np.random.rand(*shape) * 2 - 1), sigma,

mode="constant", cval=0.) * alpha

dy = gaussian_filter((np.random.rand(*shape) * 2 - 1), sigma,

mode="constant", cval=0.) * alpha

dz = gaussian_filter((np.random.rand(*shape) * 2 - 1), sigma,

mode="constant", cval=0.) * alpha

x, y, z = np.mgrid(0:shape(0), 0:shape(1), 0:shape(2))

indices = np.reshape(x + dx, (-1, 1)), \

np.reshape(y + dy, (-1, 1)), \

np.reshape(z + dz, (-1, 1))

image = np.empty(shape=image.shape, dtype=dtype)

image = im_intrps(indices).reshape(shape)

if labels is not None:

lab_intrp = RegularGridInterpolator(coords, labels,

method="nearest",

bounds_error=False,

fill_value=0)

labels = lab_intrp(indices).reshape(shape).astype(labels.dtype)

return image, labels

return image


упругая деформация


До и после упругой деформации

Вам нужно иметь в виду, что это преобразование изменяет интенсивность и применяет некоторый гауссовский шум в каждом измерении. Для получения дополнительной информации вы должны вернуться к оригиналу работа.

Заключение

К настоящему времени вы можете понять мои мысли об особенностях предварительной обработки и дополнений медицинских изображений. Но не забывайте: вы можете играть с учебник онлайн и увидеть преобразования самостоятельно. Помогает, поверьте. Понимание наших медицинских изображений очень важно. Теперь вы можете выбрать, какие преобразования применить в вашем проекте.

Если вам понравился наш урок, не стесняйтесь поделиться им на своей странице в социальных сетях в качестве награды за нашу работу. Было бы весьма признателен.

Оставайтесь с нами, чтобы не пропустить новые летние статьи об искусственном интеллекте!

Книга «Глубокое обучение в производстве» 📖

Узнайте, как создавать, обучать, развертывать, масштабировать и поддерживать модели глубокого обучения. Изучите инфраструктуру машинного обучения и MLOps на практических примерах.

Узнать больше

* Раскрытие информации: Обратите внимание, что некоторые из приведенных выше ссылок могут быть партнерскими ссылками, и без дополнительной оплаты для вас мы будем получать комиссию, если вы решите совершить покупку после перехода по ссылке.

LEAVE A REPLY

Please enter your comment!
Please enter your name here