TI/Programowanie dla Fizyków Medycznych/Manipulacja obrazem: Różnice pomiędzy wersjami

Z Brain-wiki
 
(Nie pokazano 4 pośrednich wersji utworzonych przez tego samego użytkownika)
Linia 1: Linia 1:
 
==Manipulacja obrazem==
 
==Manipulacja obrazem==
Ćwiczenia z mainuplacji obrazem rozpocznijmy od wczytania pliku który będziemy przetwarzać. Dla fizyków medycznych naturalnie będzie to plik DICOM.
+
Ćwiczenia z mainuplacji obrazem rozpocznijmy od wczytania pliku, który będziemy przetwarzać. Dla fizyków medycznych naturalnie będzie to plik DICOM.
 
<source lang="python">
 
<source lang="python">
 
import dicom
 
import dicom
Linia 11: Linia 11:
 
>>>(2964, 2364)
 
>>>(2964, 2364)
 
</source>
 
</source>
Stworzona w ten sposób tablica ma dosyć duże rozmiary. O ile wyświetlenie takiego pliku nie jest problemem dla znajdujących się w pracowni komputerów, to bardziej złożona modyfikacja takiej grafiki mogła by z czasem obliczeń znacznie wykraczać poza czas przewidziany na zajęcia. Wyłącznie dla celów dydaktycznych, by ułatwić i przyspieszyć pracę zmniejszymy rozmiar badanego obrazu.
+
Stworzona w ten sposób tablica ma dosyć duże rozmiary. O ile wyświetlenie takiego pliku nie jest problemem dla znajdujących się w pracowni komputerów, to bardziej złożona modyfikacja takiej grafiki mogłaby z czasem obliczeń znacznie wykraczać poza czas przewidziany na zajęcia. Wyłącznie dla celów dydaktycznych, by ułatwić i przyspieszyć pracę zmniejszymy rozmiar badanego obrazu.
 
<source lang="python">
 
<source lang="python">
 
pixel=pixel[::5,::5]
 
pixel=pixel[::5,::5]
Linia 21: Linia 21:
 
pixel=pixel*1.0/max(pixel.flatten())
 
pixel=pixel*1.0/max(pixel.flatten())
 
</source>
 
</source>
Tak powstały obraz możemy dowolnie modyfikować. Aby lepiej widoczne były skutki manipulacji obrazem dodajmy u góry obrazka þlynne przejście od czerni do bieli
+
Tak powstały obraz możemy dowolnie modyfikować. Aby lepiej widoczne były skutki manipulacji obrazem dodajmy u góry obrazka płynne przejście od czerni do bieli
 
<source lang="python">
 
<source lang="python">
 
def dodajPasek(tablica, n):
 
def dodajPasek(tablica, n):
Linia 31: Linia 31:
  
 
[[Plik:dicom2.png]]
 
[[Plik:dicom2.png]]
Możemy teraz zdefiniować dowolną funkcję przyjmującą za arguemnty wartości z przedziału [0,1] i zwracającą wartości z tego samego przedziału. Działając taką funkcją na każdy pixel obrazu dokonamy jego transformacji. Najprostrze funkcje jakie mogą przyjść od razu do głowy posiadają już swoje tradycyjne nazwy związane z wpływem jaki mają na obraz.
+
Możemy teraz zdefiniować dowolną funkcję przyjmującą za argumenty wartości z przedziału [0,1] i zwracającą wartości z tego samego przedziału. Działając taką funkcją na każdy pixel obrazu dokonamy jego transformacji. Najprostsze funkcje jakie mogą przyjść od razu do głowy posiadają już swoje tradycyjne nazwy związane z wpływem jaki mają na obraz.
 
===Jasność===
 
===Jasność===
Najprostszą funkcję od której na Wydziale Fizyki zaczynamy zawsze jest funkcja liniowa. Musimy jedynie zapewnić aby przy wspólczynniku nachylenia większym  od 1.0 wynikowe wartości nie przekraczały jedności. Zdefiniujmy zatem jednoparametrową funkcje medyfikującą obraz.
+
Najprostszą funkcją, od której zawsze zaczynamy na Wydziale Fizyki jest funkcja liniowa. Musimy jedynie zapewnić, aby przy współczynniku nachylenia większym  od 1.0 wynikowe wartości nie przekraczały jedności. Zdefiniujmy zatem jednoparametrową funkcję modyfikującą obraz.
 
<source lang="python">
 
<source lang="python">
 
def jasnosc(tablica, a):
 
def jasnosc(tablica, a):
Linia 44: Linia 44:
 
     return wynik
 
     return wynik
 
</source>
 
</source>
Aby móc porównać wynik w pierwotnym obrazem znów wstawiamy pasek szarości ale już o połowę węższy. dzieki temu możemy porównać pasek po operacji przekształcenia i przed nią. Dla a>1 obraz powinien się rozjaśnić, natomiast dla a<1 powinien być ciemniejszy.
+
Aby móc porównać wynik z pierwotnym obrazem znów wstawiamy pasek szarości, ale już o połowę węższy. Dzięki temu możemy porównać pasek po operacji przekształcenia i przed nią. Dla a>1 obraz powinien się rozjaśnić, natomiast dla a<1 powinien być ciemniejszy.
Zaobaczmy teraz na dwóch przykładach w jaki sposób funkcja jasnosc modyfikuje obraz
+
Zobaczmy teraz na dwóch przykładach w jaki sposób funkcja jasność modyfikuje obraz
 
<source lang="python">
 
<source lang="python">
 
py.imshow(jasnosc(pixel,2),cmap=py.cm.gray,interpolation='nearest')
 
py.imshow(jasnosc(pixel,2),cmap=py.cm.gray,interpolation='nearest')
Linia 59: Linia 59:
  
 
===Gamma===
 
===Gamma===
Kolejną narzucającą się funkcją jest podnoszenie wartości zapisanej  w tablicy do potęgi. Dla wykładników większych od jeden jasne kolory staną się jeszcze jaśniejsze a przejście między bardzo podobnymi ciemniejszymi odcieniami szarości staną się bardziej widoczne. Dla wykładników mniejszych od jeden, ciemne kolory staną się jeszcze ciemniejsze, natomiast bardzo biskie siebie jasne odcienie staną sie lepiej rozróżnialne. Zdefiniujmy zatem funkcję gamma.
+
Kolejną narzucającą się funkcją jest podnoszenie wartości zapisanej  w tablicy do potęgi. Dla wykładników większych od jeden - jasne kolory staną się jeszcze jaśniejsze, a przejścia między bardzo podobnymi ciemniejszymi odcieniami szarości staną się bardziej widoczne. Dla wykładników mniejszych od jeden - ciemne kolory staną się jeszcze ciemniejsze, natomiast bardzo bliskie siebie jasne odcienie staną się lepiej rozróżnialne. Zdefiniujmy zatem funkcję gamma.
  
 
<source lang="python">
 
<source lang="python">
Linia 85: Linia 85:
  
 
===Próg===
 
===Próg===
Czasami isnieje potrzeba przekształcenia obrazu w skali szarości na obraz czarno biały. Na takim czarnobiałym obrazie łatwo w sposób automatyczny dokonywać pomiarów odległości na rysunku. Plik DICOM jest dodatkowo wyposażony w pole opisujące rzeczywiste rozmiary pojedynczego pixela, dzieki czemu taki pomiar w pixelach możemy przełożyć na rzeczywistą odległość. Najporostszym możliwym sposobem przekształcenia obrazu w odcieniach szarości na obraz czarno biały jest ustalenie pewnego progu. Powyżej ustalonej wartości zamieniamy wartości na 1, poniżej na zero. Przykładowa implementacja tej metody przedstawiona jest poniżej.  
+
Czasami istnieje potrzeba przekształcenia obrazu w skali szarości na obraz czarno-biały. Na takim czarno-białym obrazie łatwo w sposób automatyczny dokonywać pomiarów odległości na rysunku. Plik DICOM jest dodatkowo wyposażony w pole opisujące rzeczywiste rozmiary pojedynczego pixela, dzięki czemu taki pomiar w pixelach możemy przełożyć na rzeczywistą odległość. Najprostszym możliwym sposobem przekształcenia obrazu w odcieniach szarości na obraz czarno-biały jest ustalenie pewnego progu. Powyżej ustalonej wartości zamieniamy wartości na jeden, poniżej na zero. Przykładowa implementacja tej metody przedstawiona jest poniżej.  
 
<source lang="python">
 
<source lang="python">
 
def prog(tablica, a):
 
def prog(tablica, a):
Linia 96: Linia 96:
 
     return wynik
 
     return wynik
 
</source>
 
</source>
Natomiast jej działanie na obraz wygląda nastepująco
+
Natomiast jej działanie na obraz wygląda następująco
 
<source lang="python">
 
<source lang="python">
 
py.imshow(prog(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')
 
py.imshow(prog(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')
Linia 104: Linia 104:
  
 
===Schodek===
 
===Schodek===
W bardziej skomplikowanych przypadkach, mogą dla nas być nieistotne zarówno małewartości, opisujące tło obrazu, jak i duże wartości na przykład reprezentujące układ kostny na zdjęciu. Chcialibyśmy skupić się wyłącznie na tkankach miekkich. Wówczas możemy zastosować filtr  typu "schodek". Wartościom powyżej pewnego progu,  jaki i poniżej pewnegoprogu przypisujemy 0. Wartościom pomiędzy przypisujemy 1. Implementacja poniżej  
+
W bardziej skomplikowanych przypadkach, mogą dla nas być nieistotne zarówno małe wartości, opisujące tło obrazu, jak i duże wartości na przykład reprezentujące układ kostny na zdjęciu. Chcielibyśmy skupić się wyłącznie na tkankach miękkich. Wówczas możemy zastosować filtr  typu "schodek". Wartościom powyżej pewnego progu,  jaki i poniżej pewnego progu przypisujemy 0. Wartościom pomiędzy przypisujemy 1. Implementacja poniżej  
 
<source lang="python">
 
<source lang="python">
 
def schodek(tablica, a,b):
 
def schodek(tablica, a,b):
Linia 124: Linia 124:
  
 
===Rozmycie i wyostrzenie===
 
===Rozmycie i wyostrzenie===
Pierwszym nietrywialnym filtrem jest rozmycie obrazu. Operacja ta jakościowo różni się już od stosowanych poprzednio, gdyż nie opiera się na przekształcaniu wartości w pojedynczym pixelu. W rozmywaniu obrazu nowa wartość przypisywana pixelowi jest różnego rodzaju średnią z wartości pixela i wartości pixeli go otaczających. W najprostszym przypadku może to być po prostu średnia z wartości w danym pixelu i jego ośmiu sąsiadów. Powszechnie jednak stosowanym sposobem rozmywania jest tak zwane rozmycie gaussowskie, gdzie wagi sąsiadujących pixel przy liczeniu średniej liczone są z dwuwymiarowego rozkładu gaussa o zadanej dyspersji.
+
Pierwszym nietrywialnym filtrem jest rozmycie obrazu. Operacja ta już jakościowo różni się od stosowanych poprzednio, gdyż nie opiera się na przekształcaniu wartości w pojedynczym pixelu. W rozmywaniu obrazu nowa wartość przypisywana pixelowi jest różnego rodzaju średnią z wartości pixela i wartości pixeli go otaczających. W najprostszym przypadku może to być po prostu średnia z wartości w danym pixelu i jego ośmiu sąsiadów. Powszechnie jednak stosowanym sposobem rozmywania jest tak zwane rozmycie gaussowskie, gdzie wagi sąsiadujących pixeli przy liczeniu średniej liczone są z dwuwymiarowego rozkładu gaussa o zadanej dyspersji.
 
Chętnym pozostawiam napisanie takiej procedury samodzielnie, natomiast my skorzystamy z gotowej procedury z pakiecie scipy.ndimage.
 
Chętnym pozostawiam napisanie takiej procedury samodzielnie, natomiast my skorzystamy z gotowej procedury z pakiecie scipy.ndimage.
 
<source lang="python">
 
<source lang="python">
Linia 131: Linia 131:
 
print imag
 
print imag
 
</source>
 
</source>
Operacja wyostrzenia powinna być przeciwna do rozmycia. Odtworzenie pierwotnego obrazu na podstawie jego rozmycia niestety nie jest możliwe. Możemy jednak dla dowolnego obrazu policzyć jeszcze większe jego rozmycie, stąd różnice między oryginałem a obrazem rozmytym. Jeżeli taką różnicę pomnożymy razy 1 i dodamy do rozmytego obrazu odtworzymy oryginał. Jeżeli zaś do rozmytego obrazu dodamy tę różnicę pomnożoną przez liczbę większą niż jedne otrzymamy obraz w którym krawędzie będą miały znacznie ostrzejsze brzegi. Zdefiniujmy zatem funkcję wyostrz, która przyjmuje dwa parametry: sigma - czyli dyspersję rozmycia gaussowskiego, oraz a - liczbę przez którą mnożymy różnicę miedzy obrazem rozmytym i oryginałem przez dodaniem jej do obrazu rozmytego. W ten sposób dla parametru a=0 otrzymamy jedynie rozmycie, natomiast dla a>1 wyostrzenie. Przykładowa implementacja poniżej.
+
Operacja wyostrzenia powinna być przeciwna do rozmycia. Odtworzenie pierwotnego obrazu na podstawie jego rozmycia niestety nie jest możliwe. Możemy jednak dla dowolnego obrazu policzyć jeszcze większe jego rozmycie, a stąd różnice między oryginałem a obrazem rozmytym. Jeżeli taką różnicę pomnożymy razy 1 i dodamy do rozmytego obrazu odtworzymy oryginał. Jeżeli zaś do rozmytego obrazu dodamy tę różnicę pomnożoną przez liczbę większą niż jeden otrzymamy obraz, w którym krawędzie będą miały znacznie ostrzejsze brzegi. Zdefiniujmy zatem funkcję wyostrz, która przyjmuje dwa parametry: sigma - czyli dyspersję rozmycia gaussowskiego, oraz a - liczbę przez którą mnożymy różnicę miedzy obrazem rozmytym i oryginałem przed dodaniem jej do obrazu rozmytego. W ten sposób dla parametru a=0 otrzymamy jedynie rozmycie, natomiast dla a>1 wyostrzenie. Przykładowa implementacja poniżej.
 
<source lang="python">
 
<source lang="python">
 
def wyostrz(tablica, sigma, a):
 
def wyostrz(tablica, sigma, a):

Aktualna wersja na dzień 13:22, 9 cze 2015

Manipulacja obrazem

Ćwiczenia z mainuplacji obrazem rozpocznijmy od wczytania pliku, który będziemy przetwarzać. Dla fizyków medycznych naturalnie będzie to plik DICOM.

import dicom
import numpy as np
import pylab as py

plik=dicom.read_file('I00001.dcm')
pixel=plik.pixel_array
print pixel.shape
>>>(2964, 2364)

Stworzona w ten sposób tablica ma dosyć duże rozmiary. O ile wyświetlenie takiego pliku nie jest problemem dla znajdujących się w pracowni komputerów, to bardziej złożona modyfikacja takiej grafiki mogłaby z czasem obliczeń znacznie wykraczać poza czas przewidziany na zajęcia. Wyłącznie dla celów dydaktycznych, by ułatwić i przyspieszyć pracę zmniejszymy rozmiar badanego obrazu.

pixel=pixel[::5,::5]
print min(pixel.flatten()),max(pixel.flatten())
>>> 0 3907

Wartości zapisane w tablicy odpowiadającej zmniejszonemu obrazowi mają wartości w przedziale [0,3907]. Metoda wyświetlając imshow najwyższej wartości czyli 3908 przypisze kolor biały, wartości 0 kolor czarny. Aby ułatwić sobie manipulacje kolorami przypiszmy im wartości z zakresu [0,1].

pixel=pixel*1.0/max(pixel.flatten())

Tak powstały obraz możemy dowolnie modyfikować. Aby lepiej widoczne były skutki manipulacji obrazem dodajmy u góry obrazka płynne przejście od czerni do bieli

def dodajPasek(tablica, n):
    tablica[:n,:]=np.linspace(0,1,tablica.shape[1])
dodajPasek(pixel,30)
py.imshow(pixel,cmap=py.cm.gray,interpolation='nearest')
py.show()

Dicom2.png Możemy teraz zdefiniować dowolną funkcję przyjmującą za argumenty wartości z przedziału [0,1] i zwracającą wartości z tego samego przedziału. Działając taką funkcją na każdy pixel obrazu dokonamy jego transformacji. Najprostsze funkcje jakie mogą przyjść od razu do głowy posiadają już swoje tradycyjne nazwy związane z wpływem jaki mają na obraz.

Jasność

Najprostszą funkcją, od której zawsze zaczynamy na Wydziale Fizyki jest funkcja liniowa. Musimy jedynie zapewnić, aby przy współczynniku nachylenia większym od 1.0 wynikowe wartości nie przekraczały jedności. Zdefiniujmy zatem jednoparametrową funkcję modyfikującą obraz.

def jasnosc(tablica, a):
    def f(x,a):
        return min(a*x,1.0)
    wynik=tablica.copy()
    for x,y in np.ndindex(tablica.shape):
        wynik[x,y]=f(tablica[x,y],a)
    dodajPasek(wynik,15)
    return wynik

Aby móc porównać wynik z pierwotnym obrazem znów wstawiamy pasek szarości, ale już o połowę węższy. Dzięki temu możemy porównać pasek po operacji przekształcenia i przed nią. Dla a>1 obraz powinien się rozjaśnić, natomiast dla a<1 powinien być ciemniejszy. Zobaczmy teraz na dwóch przykładach w jaki sposób funkcja jasność modyfikuje obraz

py.imshow(jasnosc(pixel,2),cmap=py.cm.gray,interpolation='nearest')
py.show()

py.imshow(jasnosc(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')
py.show()

Jasnosc1.png

Jasnosc2.png

Gamma

Kolejną narzucającą się funkcją jest podnoszenie wartości zapisanej w tablicy do potęgi. Dla wykładników większych od jeden - jasne kolory staną się jeszcze jaśniejsze, a przejścia między bardzo podobnymi ciemniejszymi odcieniami szarości staną się bardziej widoczne. Dla wykładników mniejszych od jeden - ciemne kolory staną się jeszcze ciemniejsze, natomiast bardzo bliskie siebie jasne odcienie staną się lepiej rozróżnialne. Zdefiniujmy zatem funkcję gamma.

def gamma(tablica, a):
    def f(x,a):
        return min(x**a,1.0)
    wynik=tablica.copy()
    for x,y in np.ndindex(tablica.shape):
        wynik[x,y]=f(tablica[x,y],a)
    dodajPasek(wynik,15)
    return wynik

Możemy teraz na dwóch przykładach obejrzeć efekt działania przekształcenia gamma.

        
py.imshow(gamma(pixel,2),cmap=py.cm.gray,interpolation='nearest')
py.show()

py.imshow(gamma(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')
py.show()

Gamma1.png

Gamma2.png

Próg

Czasami istnieje potrzeba przekształcenia obrazu w skali szarości na obraz czarno-biały. Na takim czarno-białym obrazie łatwo w sposób automatyczny dokonywać pomiarów odległości na rysunku. Plik DICOM jest dodatkowo wyposażony w pole opisujące rzeczywiste rozmiary pojedynczego pixela, dzięki czemu taki pomiar w pixelach możemy przełożyć na rzeczywistą odległość. Najprostszym możliwym sposobem przekształcenia obrazu w odcieniach szarości na obraz czarno-biały jest ustalenie pewnego progu. Powyżej ustalonej wartości zamieniamy wartości na jeden, poniżej na zero. Przykładowa implementacja tej metody przedstawiona jest poniżej.

def prog(tablica, a):
    def f(x,a):
        return 0 if x<a else 1
    wynik=tablica.copy()
    for x,y in np.ndindex(tablica.shape):
        wynik[x,y]=f(tablica[x,y],a)
    dodajPasek(wynik,15)
    return wynik

Natomiast jej działanie na obraz wygląda następująco

py.imshow(prog(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')
py.show()

Prog.png

Schodek

W bardziej skomplikowanych przypadkach, mogą dla nas być nieistotne zarówno małe wartości, opisujące tło obrazu, jak i duże wartości na przykład reprezentujące układ kostny na zdjęciu. Chcielibyśmy skupić się wyłącznie na tkankach miękkich. Wówczas możemy zastosować filtr typu "schodek". Wartościom powyżej pewnego progu, jaki i poniżej pewnego progu przypisujemy 0. Wartościom pomiędzy przypisujemy 1. Implementacja poniżej

def schodek(tablica, a,b):
    def f(x,a,b):
        return 1 if ((x<a) or (x>b)) else 0
    wynik=tablica.copy()
    for x,y in np.ndindex(tablica.shape):
        wynik[x,y]=f(tablica[x,y],a,b)
    dodajPasek(wynik,15)
    return wynik

A przyklad działania tutaj.

py.imshow(schodek(pixel,0.2,0.6),cmap=py.cm.gray,interpolation='nearest')
py.show()

Schodek.png

Rozmycie i wyostrzenie

Pierwszym nietrywialnym filtrem jest rozmycie obrazu. Operacja ta już jakościowo różni się od stosowanych poprzednio, gdyż nie opiera się na przekształcaniu wartości w pojedynczym pixelu. W rozmywaniu obrazu nowa wartość przypisywana pixelowi jest różnego rodzaju średnią z wartości pixela i wartości pixeli go otaczających. W najprostszym przypadku może to być po prostu średnia z wartości w danym pixelu i jego ośmiu sąsiadów. Powszechnie jednak stosowanym sposobem rozmywania jest tak zwane rozmycie gaussowskie, gdzie wagi sąsiadujących pixeli przy liczeniu średniej liczone są z dwuwymiarowego rozkładu gaussa o zadanej dyspersji. Chętnym pozostawiam napisanie takiej procedury samodzielnie, natomiast my skorzystamy z gotowej procedury z pakiecie scipy.ndimage.

from scipy import ndimage
imag=ndimage.gaussian_filter(pixel,sigma=4)
print imag

Operacja wyostrzenia powinna być przeciwna do rozmycia. Odtworzenie pierwotnego obrazu na podstawie jego rozmycia niestety nie jest możliwe. Możemy jednak dla dowolnego obrazu policzyć jeszcze większe jego rozmycie, a stąd różnice między oryginałem a obrazem rozmytym. Jeżeli taką różnicę pomnożymy razy 1 i dodamy do rozmytego obrazu odtworzymy oryginał. Jeżeli zaś do rozmytego obrazu dodamy tę różnicę pomnożoną przez liczbę większą niż jeden otrzymamy obraz, w którym krawędzie będą miały znacznie ostrzejsze brzegi. Zdefiniujmy zatem funkcję wyostrz, która przyjmuje dwa parametry: sigma - czyli dyspersję rozmycia gaussowskiego, oraz a - liczbę przez którą mnożymy różnicę miedzy obrazem rozmytym i oryginałem przed dodaniem jej do obrazu rozmytego. W ten sposób dla parametru a=0 otrzymamy jedynie rozmycie, natomiast dla a>1 wyostrzenie. Przykładowa implementacja poniżej.

def wyostrz(tablica, sigma, a):
    rozmyty=np.array(ndimage.gaussian_filter(tablica,sigma=sigma))
    roznica=tablica-rozmyty
    wynik=rozmyty+a*roznica
    wynik/=max(wynik.flatten())
    dodajPasek(wynik,15)
    return wynik

Wynik działania dla przykładowych wartości parametru a oraz sigma=4 przedstawione są poniżej

py.imshow(wyostrz(pixel,4,0),cmap=py.cm.gray,interpolation='nearest')
py.show()

py.imshow(wyostrz(pixel,4,3),cmap=py.cm.gray,interpolation='nearest')
py.show()

Blur.png

Sharp.png

"Programowanie dla Fizyków Medycznych"