Klasik makine öğrenmesi tarihine baktığımızda, aslında yapay zeka medikal görüntüleme alanında uzun süredir kullanılıyor. Ancak sadece son 3 yılda olan gelişmeler bile bu alanı bambaşka bir seviyeye taşıdı. 2017 yılında medikal görüntüleme analizinde kullanılabilir halka açık veri setlerinin yayınlanmaya ve yaygınlaşmaya başlaması belki de bunun en büyük tetikçisi. Bir de bunun tabi FDA (Food and Drugs Administration) tarafından onaylanma süreci var, orası ayrı bir hikaye. Kısaca veri varsa, hayat var. Medikal görüntüleme ve derin öğrenmenin son birkaç yılda filizlenen aşkının tarihi hakkında daha fazla şey öğrenmek istiyorsanız bu makale ilginizi çekebilir.

Radyoloji Amerikan Koleji tarafından hazırlanmış Radyolojide AI kullanım yerlerinin bir listesi https://www.acrdsi.org/DSI-Services/Define-AI

Ve bu alanda yapılan çalışmalardan FDA onayı alan araç ve cihazların listesi https://www.acrdsi.org/DSI-Services/FDA-Cleared-AI-Algorithms

Verinin tüketilebileceğinden daha hızlı üretildiği bir devirde yaşıyoruz. Sağlık alanında da bu fazlasıyla geçerli. Özellikle son dönemlerde mesleki yıpranmışlığın ve tükenmişliğin göz ardı edilemeyecek şekilde artışta olduğunu görüyoruz. Doktorların her yıl daha kısa sürede daha fazla hastayı görüp, hastalığı teşhis edip tedavi uygulaması bekleniyor. Bu da doğal olarak doktorların daha hızlı tükenmelerine ve hata oranlarının artmasına ortam hazırlıyor. Medikal görüntüleme alanı da bundan bağımsız değil. Doktorlar ve radyologlar için artan bu talep, yapay zekanın alanda yaygınlaşması için mükemmel şartları sağlıyor diyebiliriz. 2020 yılında da Bilgisayar Destekli Tanılama (CADx) kategorisinde onlarca yeni ürün ve araştırma projesi piyasaya çıktı. CADx sistemlere güzel bir örnek göğüs XRay görüntüsünde şüpheli bölgeleri işaretleyen bir sistem olabilir.

Nodüllerin yerini görmeyi kolaylaştıran yardımcı sistem

Ya da diğer bir örnek, hastaların kritiklik seviyelerine göre radyologun iş listesini düzenleyen bir yapay zeka olabilir. Daha yeni yeni sofralarımızda sunulmaya başlanan diğer bir teknoloji de, büyük veri setlerinin işlenerek benzer tıbbi geçmişe sahip insanlara kişiselleştirilmiş tedavi planları öneren bir yapay zeka ürünü olabilir.

Tabi ki ille de yapay zeka diye bir şey yok. Her ne kadar cazip olsa da, bazen yapay zeka doğru çözüm olmuyor. Eğer hastanedeki iş akışını yavaşlatıyorsa, ya da bir hastanın tedavi görme sürecini (çarpıcı bir avantajı olmadığı sürece) yavaşlatıyorsa, aslanım yapay zeka aşamasından önce bir kez daha düşünmek gerekir. Ayrıca (en azından şimdilik) bir yapay zeka radyologun veya bir doktorun yerini almak yerine, ona yardımcı olmak için çalışmalıdır.

Makine öğrenmesi algoritmaları her zaman kompleks ve ağır olmak zorunda değil. Aslında makine öğrenmesini tıbbi görüntülemenin bir çok farklı aşamasında kullanabiliyoruz.

Bu yazıda önce basit bir algoritmayla, mamogramdan elde edilmiş görüntüden arkaplanı ayıracağız. Daha sonra bu görüntülerin piksel yoğunluğunu analiz edip iki çeşit doku tipini ele alacağız: yağlı ve yoğun (fatty ve dense). Çok basit ve yaygın bir teknik olan thresholding kullanacağız. Aslında tam olarak yapay zeka veya makine öğrenmesi kategorisine girmiyor olsa da, tıbbi görüntü analizine giriş için güzel bir başlangıç olacağına inanıyorum.

Şunu da belirtmem gerekir, tıp alanında herhangi bir eğitimim veya akademik çalışmam yok. O yüzden bu konuları genellikle bir yazılımcı ve potansiyel bir hasta gözünden işleyeceğim. Ayrıca tıp eğitimi veren üniversite ve kurumların, ana derslerine yapay zeka alanını da eklemesinin kaçınılmaz olduğunu düşünüyorum. Çoğu üniversitenin tıp öğrencileri de artık bilgisayar kulüplerine ilgi göstermeye başladı. Umarım bu konuda en yakın zamanda bir adım atılır.

Öncelikle

Mamografi, meme kanserinin teşhisindeki en etkin görüntüleme yöntemidir. Her 8 kadından 1'i meme kanseri riski taşır. Kadınlardaki kanserin %30'u meme kanseri olmakla birlikte, tanıların %85'i meme kanseri geçmişi olmayan bir ailenin bireyine konulmaktadır. 2018 yılında yaklaşık 2.1 milyon meme kanseri tanısı konulmuş, ve maalesef 625.000 kişi bu hastalıktan dolayı yaşamını yitimiştir. Hastalığın erken teşhisi bu kadar önemliyken, mamografileri inceleyen radyologların üstündeki baskı da gün geçtikçe artıyor. Bu durumda bize de yükü hafifletmek ve stresi azaltmak için çözümler geliştirmek düşüyor.

Bu yazıda anlatacağım yöntemler klinik şartlarda kullanılmak için yetersiz olacaktır, ancak bu konuya ilgi duyan veya işin ucundan tutma isteği taşıyan herkes için yararlı olacağını düşünüyorum.

⭐Hadi başlayalım!

Proje için gereken dosyaları ve projenin tamamlanmış halini GitHub'da bulabilirsiniz.

Çoğu zaman bir veriyi elimize aldığımızda yapmamız gereken ilk şey veriyi görmektir. Elimizdeki doku etiketleri dense ve fatty olarak iki farklı klasörde toplanmış.

#from skimage import io
## 2 farklı tipten mamogram örnekleri alalım
dense = io.imread('dense/mdb003.pgm')
fatty = io.imread('fatty/mdb005.pgm')
#from matplotlib import pyplot as plt
plt.imshow(dense)
plt.imshow(fatty)

Resimlere baktığımızda sağ-sol kenarlarda boşluklar olduğunu, ve üst kısımda da bir etiket bulunduğunu görüyoruz. Bunları kırpalım.

# Resimler 1024x1024 boyutunda
# Yukarıdan 100 piksel, soldan 200 piksel, sağdan 224 piksel kırp
dense = dense[100:, 200:800]
fatty = fatty[100:, 200:800]

Tekrar bakalım

plt.imshow(fatty)

Daha iyi gözüküyor. Dairesel kırpma veya bulanıklaştırma gibi onlarca farklı Image preprocessing tekniği kullanılabilir.

Dense yani yoğun dokuda meme kanseri riskinin normal yoğunluktaki dokuya göre 4-6 kat daha fazla olduğunu, yoğun dokuda meme kanserini saptamanın da neredeyse 2 kat daha zor olduğunu biliyoruz. (John Hopkins Medicine)

Çıplak gözle bakınca bu iki örnek arasındaki farkı görmek pek de zor değil. Peki bunu bilgisayarın diline nasıl çeviririz? Histogramlar ile. Fotoğrafçılıkla uğraşanların fazlasıyla aşina olduğu bir yöntem. Bir resmin histogramına bakarak resmin genel parlaklığı hakkında yorum yapabiliriz.

Karanlık bir resimde piksel yoğunluğu histogramın sol tarafında yoğunken, aydınlık bir resimde sağ tarafında yoğunlaşacaktır. Bunu kullanarak yoğun dokunun oluşturduğu parlak bölgeyi ayrıştırabiliriz.

Resimlerin histogramlarını çıkarırsak

plt.hist(dense.ravel(), bins=256);
Yoğun Doku Histogram
plt.hist(fatty.ravel(), bins=256);
Yağlı Doku Histogram

Yağlı dokuda 100-150 değerleri arasında bir tepe varken, yoğun dokuda bu tepe çok daha küçük. En soldaki boylu poslu tepe de resmin siyah arkaplanı olmalı.

Resimi arkaplanından ayırıp piksel yoğunluğunu incelemeye başlayabiliriz. Arkaplandan ayırmak için istemeyeceğiniz kadar fazla teknik var ama bu durumda kullanabileceğimiz en uygun yöntem thresholding.

Thresholding burada histogramı iki parçaya bölüp ilgilendiğimiz kısmı almamızı sağlayacak.

Threshold ile 2 parçaya bölünen histogram
#from scipy.ndimage import gaussian_filter
threshold = 50
dense_bin = (dense > threshold) * 255
fatty_bin = (fatty > threshold) * 255

Burada kendi belirlediğimiz bir eşik/threshold ile bu eşikten düşük olan parçaları resimden çıkardık, yüksek olanları da daha iyi görebilmek için parlak hale getirdik (255 en yüksek parlaklık değeri).

Bu eşik değerini çoğu zaman histograma bakarak kendimiz seçebiliriz. Ancak bu değeri otomatik belirlemek için çok başarılı algoritmalar var. Otsu Algoritması gibi.


🌿Otsu Algoritması

Otsu, Japon matematikçi Nobuyuki Otsu tarafından geliştirilen, otomatik thresholding yapmamızı sağlayan, basit ama etkili bir algoritmadır.

Temel olarak herhangi bir resmin histogramında diskriminant analizi yaparak optimum bölünme noktasını arar. K-Means algoritmasına benzer.

Meraklısı için Wikipedia ve bir blog.

Otsu için algoritmayı okuyup baştan kendimiz kodu yazabilir, ya da skimage kütüphanesinden tek satırda kullanabiliriz. Maceracı hissediyorsanız, Wikipedia sayfasındaki pseudocode ve algoritmaya bakarak yazabilirsiniz.

Otsu algoritmasına giren bir fotoğraf örneği

https://scipy-lectures.org/packages/scikit-image/auto_examples/plot_threshold.html

Bizim göz kararı belirlediğimiz eşik değerine dönecek olursak

plt.imshow(dense_bin)
plt.imshow(fatty_bin)

Arkaplan ayrılmış ve kenarlara göre dokunun tamamı seçilmiş gözüküyor. Bu işleme aslında binarizasyon/binarizing deniyor. Burada seçtiğimiz 50 değeri yerine, Otsu metodunun belirlediği bir eşik değeri de kullanabiliriz. Threshold değerini değiştirerek nasıl sonuçlar elde edeceğinizi denemenizi tavsiye ederim.

Görüntü işlemede binarizasyona başlamadan önce daha iyi sonuçlar elde etmek için uygulayabileceğiniz yöntemlerden biri de yumuşatmadır(smoothing). En basit haliyle gaussian filter ile resmi bulanıklaştırmak bir yumuşatma tekniğidir. Resimdeki detay ve keskinlik azaldığı için, gürültü azalacaktır.

Yukarıdaki örnekte gördüğünüz kenarlarda "tırtıklı" ve kesik görüntüyü de smoothing ile azaltıp düzleştirebilirdik.

Gaussian filtering yapmak için örnek kod:

## from scipy.ndimage import gaussian_filter
img_smooth = gaussian_filter(img, sigma = 5)
#sigma değeri bulanıklık seviyesini temsil ediyor

Şimdi elimizdeki bütün resimleri kullanarak genel bir maske oluşturmaya çalışacağız. Bu maske ile de dokunun fatty veya dense sınıflarından hangisine ait olduğunu tahmin edeceğiz.

Önce göğüs dokusunun piksel yoğunluklarını aşağıdaki metodla bütün resimler için çıkaralım.

fatty_imgs = glob.glob("fatty/*")
dense_imgs = glob.glob("dense/*")

fatty_intensities = []

for i in fatty_imgs:
    img = plt.imread(i)[100:,200:800] # kırpma
    thresh = filters.threshold_otsu(img) # otsu
    
    img_mask = (img > thresh) # maskeleme
    fatty_intensities.extend(img[img_mask].tolist())
plt.hist(fatty_intensities,bins=256);
Yağlı Doku Genel Yoğunluk Dağılımı

Aynısını yoğun/dense doku için de yapalım

dense_intensities = []

for i in dense_imgs: 
    img = plt.imread(i)[100:,200:800] # kırpma
    thresh = filters.threshold_otsu(img) # otsu
    
    img_mask = (img > thresh) # maskeleme
    dense_intensities.extend(img[img_mask].tolist())
plt.hist(dense_intensities,bins=256);
Yoğun Doku Genel Yoğunluk Dağılımı

Bütün resimlerde tekrar eden örüntüyü yakalamak için bu yoğunluk listesinin modunu alalım.

##import scipy.stats
dense_mode = scipy.stats.mode(dense_intensities)[0][0]
fatty_mode = scipy.stats.mode(fatty_intensities)[0][0]

Son olarak bütün fatty/yağlı doku resimlerini yukarıdaki işlemlerden geçirerek, histogram yoğunluk tepelerini, bulduğumuz örüntü ile karşılaştıralım.

for i in fatty_imgs: 
    img = plt.imread(i)[100:,200:800] # Kırpma
    thresh = filters.threshold_otsu(img) # Otsu
    
    img_mask = (img > thresh) # Maskeleme
    
    ## Mod alma
    img_mode = scipy.stats.mode(img[img_mask])[0][0]
    
    fatty_delta = img_mode - fatty_mode # Resmin modundan doku modunu çıkar
    dense_delta = img_mode - dense_mode # Resmin modundan doku modunu çıkar
    
    # Delta farkına göre (yakınlığa göre) doku tipi belirlenir
    if (np.abs(fatty_delta) < np.abs(dense_delta)):
        print("Fatty Doku") 
    else:
        print("Dense Doku")
Fatty Doku
Fatty Doku
Fatty Doku
Fatty Doku
Dense Doku
Dense Doku
Fatty Doku
Fatty Doku
Fatty Doku
Fatty Doku

Aynısını dense doku için yapalım. Sadece ilk satırı değiştirmemiz yeterli.

for i in dense_imgs:
    img = plt.imread(i)[100:,200:800] # Kırpma
    thresh = filters.threshold_otsu(img) # Otsu
    
    img_mask = (img > thresh) # Maskeleme
    
    ## Mod alma
    img_mode = scipy.stats.mode(img[img_mask])[0][0]
    
    fatty_delta = img_mode - fatty_mode # Resmin modundan doku modunu çıkar
    dense_delta = img_mode - dense_mode # Resmin modundan doku modunu çıkar
    
    # Delta farkına göre (yakınlığa göre) doku tipi belirlenir
    if (np.abs(fatty_delta) < np.abs(dense_delta)):
        print("Fatty Doku") 
    else:
        print("Dense Doku")
Dense Doku
Dense Doku
Dense Doku
Dense Doku
Dense Doku
Dense Doku
Dense Doku
Dense Doku
Dense Doku
Dense Doku

Bu basit yöntemle fatty doku türü için 80% doğruluk oranı, dense doku türü için 100% doğruluk oranı elde ettik!

Tabi ki elimizdeki veri seti küçük, ayrıca doğruluk oranı sınıflandırma algoritmaları için yanıltıcı bir ölçüm yöntemidir. 80% yüksek görünebilir, ancak 10 değil 100.000 veri noktası için 80% oranı 20.000 yanlış sonuç verecektir.

Fatty ve dense dokuların toplumdaki yaygınlık oranının fatty için 99% ve dense için 1% olduğunu varsayalım (gerçek oranlar değil). Algoritmamız da aşağıdaki gibi olsun

def siniflandir(resim):
	return "fatty"

Değerlendirdiği bütün resimleri hiçbir şey yapmadan fatty olarak sınıflandırsa da 99% doğruluk oranı elde edecektir. Diğer adıyla accuracy bu yüzden etkili bir performans ölçütü olmayacaktır, özellikle eşit dağılmamış sınıflarda.

Günümüzde en popüler deep learning performans ölçüm araçları arasında, F1 skoru, Recall, Precision, Specificity ve Sensitivity bulunmaktadır. Bu değerleri hesaplamak için Confusion Matrix kullanılmaktadır.

Geçtiğimiz aylarda popüler olan COVID-19(Koronavirüs) testlerinde de gördüğümüz, benim testimde 94.2% doğruluk oranı var, benim testimde 99.8% doğruluk oranı var! diye yapılan yarışların aslında biraz da mantıksız olduğunu görmek zor değil. Popülasyonun sadece 0.01% inde görülen bir hastalık için (COVID-19'dan bahsetmiyorum)  herkesi negatif olarak sınıflandıran bir test 99.9% doğruluk oranın sahip olurdu.

Confusion matrix kısaca doğrunun ne kadar doğru olduğunu görmemizi kolaylaştıran basit bir tablo.

TP(True Positive) gerçekte pozitif olan ve algoritmanın pozitif tahmin ettiği, TN(True Negative) gerçekte negatif olan ve algoritmanın negatif tahmin ettiği, FP(False Positive) gerçekte negatif olan ama algoritmanın pozitif tahmin ettiği, FN(False Negative) gerçekte pozitif olan ama algoritmanın negatif tahmin ettiği durumların sayısıdır.

Bu yazıda kullandığımız yöntem için bir confusion matrix oluşturalım. Pozitif değer dense, negatif değer dense olmayan(yani fatty) olsun.

Buradan iki önemli metrik olan specificity ve sensitivity'i hesaplayalım.

Sensitivity algoritmanın pozitif bulduğu ve doğru olan değerlerle, bütün pozitif değerlerin oranıdır.

Specificity algoritmanın negatif bulduğu ve negatif olan değerlerle, bütün negatif değerlerin oranıdır.

Sensitivity buradan 1.0, specificity 0.8 çıkıyor.

Peki aşağıdaki fonksiyonun sensitivity ve specificity değerleri ne olur?

def siniflandir(veri):
	return False

1.000 veri noktasının her birinin negatif olduğunu varsayarsak, sensitivity değeri 0.0, specificity değeri 1.0 çıkacaktır. Algoritmanın performansını hesaplarken, sensitivity ve specificity değerlerinin dengeli olması çoğu durumda yararlı olacaktır.

Çoğu durumda tıbbi bir tanılama yaparken sensitivity değerinin yüksek olması gerekir. Çünkü hastalığı olan bir insana, hasta değilsin demek genellikle uzun vadede daha büyük sorunlara yol açacaktır. Ayrıca bilgisayar destekli tanı sistemleri günümüzde pratisyenlere yardımcı olmak için kullanılmaktadır, yani kararı yine bir doktor vermektedir.

Specificity değerinin yüksek olması beklenen testlere güzel bir örnek hamilelik testi olabilir. Hamilelik testi pozitif çıktığı zaman hamile olmama çok ihtimali düşüktür, ama negatif çıktığı zaman hamilelik potansiyeli az da olsa yine devam etmektedir. Hamilelik testi pozitif çıktığı zaman testin "kendine güveni", negatife göre çok daha yüksektir.

Meraklısına, medikal test ve sınıflandırma konusunda yazılmış güzel bir blog yazısı.

Bu yazıda basit bir görüntü işleme tekniğiyle mamogram resminden doku türü tespiti yaptık ve kısaca birkaç makine öğrenmesi performans değerlendirme kriterinden bahsettik. Ayrıca Otsu metoduyla belirlediğimiz threshold değerinin, yoğunluk dağılımları açık bir şekilde belli olmayan histogramlarda başarısız olabileceğini gördük.

Bir sonraki yazılarımdan biri göğüs XRay'inden Zatürre(Pnömoni) Tespiti yapan bir Deep Learning algoritması olacak.

Teşekkür ederim.