Выявление положительных пикселей после деконволюции цвета без учета границ - PullRequest
2 голосов
/ 08 апреля 2020

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

Я использую деконволюцию цвета (separate_stains из skimage.color), чтобы получить канал AE C ( соответствует красному маркеру), отделяя его от фона (синий цвет гематоксилина) и применяя пороговое значение cts2 Otsu для идентификации положительных пикселей, используя cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU), но он также улавливает границы ткани (см. белые линии на изображении примера, иногда он даже имеет случайные цвета, отличные от белого), а иногда даже неположительные ячейки (синие области в примере изображения). Также отсутствуют некоторые слабые положительные пиксели, которые я хотел бы захватить.

В целом: (1) как отфильтровать ложно-положительные границы тканей и синие пиксели? и (2) как настроить порог Otsu для захвата слабых красных позитивов?

Добавление исправленного примера изображения -

  1. в верхнем левом углу исходного изображения после использования HistoQ C чтобы идентифицировать области ткани и применить маску, которую она идентифицировала на ткани так, чтобы все нетканые области были черными. Мне следует корректировать его параметры, чтобы исключить складчатые участки ткани, которые выглядят более темными (в нижней левой части этого изображения). Приветствуются предложения по другим инструментам для идентификации областей ткани.
  2. верхний правый гематоксилин после деконволюции
  3. нижний левый AE C после деконволюции
  4. нижний правый отсечение порога Оцу не применено исходное изображение RGB, которое пытается захватить только пиксели AE C, но также содержит ложные и ложные негативы

Revised example image AEC WS

Спасибо

Ответы [ 3 ]

1 голос
/ 18 апреля 2020

В итоге я включил некоторые из приведенных выше отзывов Криса в следующее возможное нетрадиционное решение, для которого я был бы признателен за получение обратной связи (на конкретные c вопросы ниже, но также и за общие предложения по улучшению или более эффективные / точные инструменты или стратегия):

  1. Определить (но пока не применять) маску для ткани (HistoQ C) после оптимизации сценария HistoQ C, чтобы удалить как можно больше складок ткани без удаления области нормальной ткани
  2. Применить деконволюцию к исходному изображению RGB, используя hax_from_rgb
  3. Используя второй канал, который должен соответствовать пикселям с красным пятном, и вычесть из него третий канал, который, насколько я вижу, соответствует фону не красные / синие пиксели изображения. Этот шаг удаляет высокие значения во втором канале, которые из-за складок ткани или других артефактов, которые не были удалены на первом шаге (что соответствует третьему каналу? Зеленый элемент RGB?)
  4. Размытие скорректированного изображения и порога на основе медианы изображения плюс 20 (полу-произвольно, но это работает. Есть ли лучшие альтернативы? Здесь Оцу не работает вообще)
  5. Применить маску областей ткани на пороговое изображение, дающее только положительные красные / красные sh пикселя без областей без ткани
  6. Подсчитать% положительных пикселей относительно области маски ткани

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

Кроме этого, преобразование LoG, похоже, не сработало, потому что оно дало много растянутых ярких сегментов, а не просто круглых пятен, где расположены клетки. , Я не уверен, почему это происходит.

0 голосов
/ 19 апреля 2020

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

Я использую DIPlib , потому что я с ним наиболее знаком ( автор). У него есть Python привязок, которые я здесь использую, но их нельзя установить с pip, библиотеку нужно устанавливать отдельно. Тем не менее, ничто из этого не является сложной обработкой изображения, и вы должны иметь возможность выполнять аналогичную обработку с другими библиотеками.

Загрузка изображения

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

import PyDIP as dip
import numpy as np

image = dip.ImageRead('example.png')
image = dip.Gauss(image, [1]) # because of the severe JPEG compression artifacts

Несмешивание пятен

[Личное примечание: мне жаль, что Руифрок и Джонстон, авторы Бумага, представляющая метод размешивания пятен , назвала его «деконволюция», поскольку этот термин уже имел установленное значение при обработке изображений, особенно в сочетании с микроскопией. Я всегда называю это «размешиванием пятен», а не «деконволюцией».]

Это всегда должно быть первым шагом в любой попытке количественной оценки изображения байтфилда. Здесь необходимо определить три важных триплета RGB: значение RGB фона (яркость источника света) и значение RGB каждого из пятен. Процесс размешивания состоит из двух компонентов:

Сначала мы применяем отображение Пиво-Ламберта . Это отображение нелинейное. Он преобразует прошедший свет (как записано микроскопом) в значения поглощения. Абсорбция показывает, насколько сильно каждая точка на слайде поглощает свет с различными длинами волн. Пятна поглощают свет и различаются по относительной оптической плотности в каждом из каналов камеры R, G и B.

background_intensity = [209, 208, 215]
image = dip.BeerLambertMapping(image, background_intensity)

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

Второй шаг - фактическое смешение. Смешивание оптических характеристик является линейным процессом, поэтому смешивание представляет собой решение системы линейных уравнений для каждого пикселя. Для этого нам нужно знать значения поглощения для каждого из пятен в каждом из каналов. Использование стандартных значений (как в skimage.color.hax_from_rgb) может дать хорошее первое приближение, но редко дает наилучшую количественную оценку.

Цвета окраски меняются от анализа к анализу (например, гематоксилин имеет различный цвет в зависимости от того, кто Сделано это, какие ткани окрашены, и т. д. c.), и изменить также в зависимости от камеры, используемой для изображения слайда (каждая модель имеет различные фильтры RGB). Лучший способ определить эти цвета - подготовить слайд для каждого пятна, используя тот же протокол, но не нанося другие красители. Из этих слайдов вы можете легко получить цвета пятен, которые подходят для вашего анализа и вашего сканера слайдов. Однако на практике это происходит редко, если вообще когда-либо это делается.

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

hematoxylin_color = np.array([0.2712, 0.2448, 0.1674])
hematoxylin_color = (hematoxylin_color/np.linalg.norm(hematoxylin_color)).tolist()
aec_color = np.array([0.2129, 0.2806, 0.4348])
aec_color = (aec_color/np.linalg.norm(aec_color)).tolist()
stains = dip.UnmixStains(image, [hematoxylin_color, aec_color])
stains = dip.ClipLow(stains, 0) # set negative values to 0
hematoxylin = stains.TensorElement(0)
aec = stains.TensorElement(1)

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

Определение площади ткани

У вас уже есть хороший метод для этого, который применяется к исходному изображению RGB. Тем не менее, не применяйте маску к исходному изображению перед выполнением рассмешивания выше, сохраняйте маску как отдельное изображение. Я написал следующий фрагмент кода, который находит область ткани на основе окраски гематоксилином. Это не очень хорошо, и это не сложно улучшить, но я не хотел тратить здесь слишком много времени.

tissue = dip.MedianFilter(hematoxylin, dip.Kernel(5))
tissue = dip.Dilation(tissue, [20])
tissue = dip.Closing(tissue, [50])
area = tissue > 0.2

Определение складок ткани

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

folds = dip.Gauss(hematoxylin - aec, [20])
area -= folds > 0.2

Определение положительных пикселей

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

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

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

positive = aec > 0.1 # pick a threshold according to pathologist's idea what is positive and what is not

pp = 100 * dip.Count(dip.And(positive, area)) / dip.Count(area)
print("Percent positive:", pp)

Я получу 1,35% в этом образце. Обратите внимание, что% положительных пикселей не обязательно связан с% положительных ячеек и не должен использоваться в качестве замены.

intermediate and final images produced by the code above

0 голосов
/ 16 апреля 2020

Используйте ML для этого случая.

  1. Создайте вручную двоичную маску для своих изображений: каждый красный пиксель - белый, фоновые пиксели - черный.
  2. Работа в цветовом пространстве HSV или Lab .
  3. Обучите простой классификатор: дерево решений или SVM (линейный или с RBF) ..
  4. Давайте проверим!

См. Хороший и очень простой пример с сегментация цвета кожи .

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

...