http://brain.fuw.edu.pl/edu/api.php?action=feedcontributions&user=Tgubiec&feedformat=atomBrain-wiki - Wkład użytkownika [pl]2024-03-29T06:26:03ZWkład użytkownikaMediaWiki 1.34.1http://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Optymalizacja&diff=3638TI/Programowanie dla Fizyków Medycznych/Optymalizacja2015-06-09T14:17:45Z<p>Tgubiec: /* Zadanie - Data Container */</p>
<hr />
<div>==Optymalizacja jednowymiarowa==<br />
Omawianie zagadnienia optymalizacji rozpocznijmy od prostego przykładu. Zdefiniujmy pewną funkcję i zobaczmy jak wygląda jej wykres.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
licznikTestowej=0<br />
<br />
def testowa(x):<br />
global licznikTestowej<br />
licznikTestowej+=1<br />
return 1/x+np.exp(x)<br />
<br />
xtest=np.arange(0.2,2,0.01)<br />
ytest=[testowa(x) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
[[Plik:opt1.png]]<br />
<br />
Na rozważanym przedziale [0.2,2] powyższa funkcja ma tylko jedno ekstremum lokalne. Taką funkcję nazywamy unimodalną. Zmienna licznikTestowej umożliwi nam zliczanie wywołań funkcji testowej przez analizowane procedury.<br />
Zagadnienie, którym teraz będziemy się zajmować to problem numerycznego znajdowania takiego ekstremum. Jak w każdym problemie numerycznym ekstremum szukać będziemy zakładając pewną dokładność otrzymanego wyniku, którą oznaczmy xtol. Na wstępie przyjmijmy, że poszukujemy ekstremum z dokładnością xtol=0.01. Najprostszą metodą będzie policzenie wartości funkcji dla wszystkich wartości x z podanego przedziału co xtol. Jest to metoda siłowa i wielokrotnie licząca wartość funkcji. Jej kod możemy znaleźć poniżej. <br />
<source lang="python"><br />
def bruteForce(func,xmin,xmax,args=(),xtol=0.01):<br />
xlist=np.arange(xmin,xmax,xtol)<br />
ylist=[func(x,*args) for x in xlist]<br />
return xlist[ylist.index(max(ylist))]<br />
</source><br />
Innym, znacznie efektywniejszym sposobem znajdowania minimum może być następująca procedura rekurencyjna:<br />
*podzielmy przedział [xmin,xmax] na 3 równe część: [xmin,xL],[xL,xR] oraz [xR,xmax]<br />
*jeżeli wartość funkcji w xL jest mniejsza od wartości funkcji w xR to powtórz procedurę dla przedziału [xmin,xR]. W przeciwnym przypadku powtórz procedurę dla przedziału [xL,xmax].<br />
*zakończ działanie gdy badany przedział jest krótszy niż xtol.<br />
Przykładowa implementacja tej metody wygląda następująco<br />
<source lang="python"><br />
def twoMidPointsR(func,xmin,xmax,args=(),xtol=0.01):<br />
if xmax-xmin<xtol: return 0.5*(xmax+xmin)<br />
xL=xmin+(xmax-xmin)/3.0<br />
xR=xmax-(xmax-xmin)/3.0<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
if fxL>fxR:<br />
return twoMidPointsR(func,xmin,xR,args,xtol)<br />
else:<br />
return twoMidPointsR(func,xL,xmax,args,xtol)<br />
</source><br />
Tą metodą możemy już szukać minimum z dowolną dokładnością, co nie będzie skutkowało znacznie większym czasem obliczeń. Zauważmy jednak, że w każdej iteracji wartość minimalizowanej funkcji liczona jest dwukrotnie. Jeżeli mamy do czynienia z funkcją, dla której obliczenie pojedynczej wartości jest bardzo czasochłonne to warto się zastanowić czy nie można tego wyniku poprawić. Zauważmy, że dla działania tej metody wcale nie jest konieczne dzielenie badanego odcinka dokładnie na trzy równe części. Można dokonać tego podziału w zupełnie innej proporcji. Warto tak dobrać punkty xL i xR, aby xR pokrywał się z xL (lub xL z xR) w kolejnym kroku iteracji. Jeżeli dodatkowo stworzymy zmienne przechowujące wcześniej liczone wartości funkcji to uda nam się ograniczyć liczbę wywołań funkcji o połowę. Opisana metoda to tak zwana '''metoda złotego podziału'''. Przykładowa implementacja wygląda następująco <br />
<br />
<source lang="python"><br />
def GoldenRatioRearch(func,xmin,xmax,args=(),xtol=0.01):<br />
golden=0.5*(np.sqrt(5.0)-1.0)<br />
xL=xmax-golden*(xmax-xmin)<br />
xR=xmin+golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
while xmax-xmin>xtol: <br />
if fxL<fxR:<br />
xmin=xmin<br />
xmax=xR<br />
xR=xL<br />
fxR=fxL<br />
xL=xmax-golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
else:<br />
xmin=xL<br />
xmax=xmax<br />
xL=xR<br />
fxL=fxR<br />
xR=xmin+golden*(xmax-xmin)<br />
fxR=func(xR,*args)<br />
return 0.5*(xmax+xmin)<br />
<br />
</source><br />
Napisanej metody optymalizacji możemy użyć w celu dopasowania funkcji do danych empirycznych metodą najmniejszych kwadratów. Przykład takiego zastosowania poniżej.<br />
<br />
<source lang="python"><br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a): return x*a<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print GoldenRatioRearch(squares,0,10,args=(liniowa,xlist,ylist),xtol=0.01)<br />
</source><br />
<br />
==Optymalizacja wielowymiarowa==<br />
Przejście od optymalizacji jedno- do wielowymiarowej fundamentalnie komplikuje problem. Pierwszym problemem jest istnienie tak zwanych punktów siodłowych. Nie istnieją zatem metody które zawsze znajdują szukane minimum, nawet jeżeli wiadomo że takie istnieje. Najpopularniejszą metodą jest downhill symplex lub inaczej metoda Neldera-Meada. Z powodu znacznego stopnia komplikacji nie będziemy jej samodzielnie implementować, a jedynie posłużymy się implementacją z biblioteki scipy.optimize. Dzięki niej możemy np. dopasowywać funkcję z więcej niż jednym parametrem do danych eksperymentalnych.<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a,b): return x*a+b<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23,-0.73)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print so.fmin(squares,(1,0),args=(kwadratowa,xlist,ylist))<br />
</source><br />
Oczywiście dopasowywanie możemy przeprowadzać nie tylko metodą najmniejszych kwadratów.<br />
===Zadanie - rozkład Cauchy'ego===<br />
Wylosuj 10000 liczb z rozkładu Cauchy'ego z parametrami loc=1.23 i scale=2.0. Do wylosowanych danych dopasuj rozkład Cauche'ego trzema metodami<br />
*METODA 1 - stwórz histogram otrzymanych wartości, znormalizuj go i metodą najmniejszych kwadratów dopasauj gęstość rozkładu do histogramu<br />
*METODA 2 - dopasuj gęstość rozkładu do wylosowanych danych metodą największej wiarygodności<br />
*METODA 3 - z wylosowanych danych stwórz dystrybuantę empiryczną. Metodą najmniejszych kwadratów dopasuj dystrybuantę rozkładu Cauchy'ego do dystrybuanty empirycznej.<br />
<br />
===Rozwiązanie===<br />
<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
def rho_cauchy(x,loc,scale):<br />
return (np.pi*scale*(1.0+(x-loc)**2/(scale**2)))**(-1.0)<br />
<br />
def F_cauchy(x,loc,scale):<br />
return 0.5+np.arctan((x-loc)*1.0/scale)/np.pi<br />
<br />
<br />
#losujemy 10000 liczb z rozkladu Cauchyego o loc=1.23 i scale=2.0<br />
x=2*np.random.standard_cauchy(10000)+1.23<br />
N=len(x)<br />
<br />
#METODA 1 - Dopasowanie metoda najmniejszych kwadratow do histogramu<br />
<br />
#tworzymy histogram<br />
hist,bins= np.histogram(x,bins=np.linspace(-20,20,61))<br />
#dlugosc przedzialu histogramowania<br />
przedzial=bins[1]-bins[0]<br />
#normalizujemy histogram aby moc go porownac z gestoscia<br />
hist=hist*1.0/len(x)/przedzial<br />
#liczymy wsp. srodkow przedzialow histogramowania<br />
xhist=bins[:-1]+0.5*przedzial<br />
#definiujemy sume kwadratow<br />
def squares((loc,scale)):<br />
return sum([(rho_cauchy(xhist[i],loc,scale)-hist[i])**2 for i in range(len(hist))])<br />
#szukamy minimum funkcja fmin<br />
fit1=tuple(so.fmin(squares,(0,1)))<br />
print 'wynik metody1 to '+str(fit1)<br />
#ogladamy wynik<br />
py.plot(xhist,hist)<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[rho_cauchy(a,*fit1) for a in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
<br />
<br />
#METODA 2 - Metoda najwiekszej wiarygodnosci<br />
<br />
#definiujemy -funkcje wiarygodnosci<br />
def L((loc,scale)):<br />
return -sum([np.log(rho_cauchy(a,loc,scale)) for a in x])<br />
#szukamy minimum<br />
fit2=tuple(so.fmin(L,(0,1)))<br />
print 'wynik metody2 to '+str(fit2)<br />
<br />
<br />
#METODA 3 - dopasowanie dystrybuant<br />
<br />
xx=sorted(x)<br />
yy=np.linspace(0,1,N)<br />
#definiujemy funkcje KS bedaca maksimum z roznicy miedzy dystrybuanta empiryczna a teoretyczna<br />
def KS((loc,scale)):<br />
return max([abs(F_cauchy(xx[i],loc,scale)-yy[i]) for i in xrange(N)])<br />
#szukamy minimum<br />
fit3=tuple(so.fmin(KS,(0,1)))<br />
print 'wynik metody3 to '+str(fit3)<br />
#ogladamy wynik<br />
cut=100<br />
py.plot(xx[cut:-cut],yy[cut:-cut])<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[F_cauchy(x,*fit3) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
===Zadanie - Data Container===<br />
Napisz klasę funkcja przyjmującą w konstruktorze parametry funkcji i posiadającą reprezentację tekstową. Napisz dowolną funkcję dziedziczącą po klasie funkcja, której metoda call przyjmuje jeden argument i zwraca wartość funkcji dla podanego argumentu i parametrów podanych w konstruktorze. <br />
Napisz klasę DataContainer przyjmującą w konstruktorze dwie serie danych empirycznych o tej samej długości odpowiadające współrzędnym x i y. Obiekt klasy DataContainer powinien być wyposażony w metodę o nazwie fit, która przyjmuje jako argument obiekt klasy funkcja. Metoda fit powinna zwracać obiekt takiej samej klasy jak otrzymany w argumencie, ale z parametrami, dla których funkcja jest najlepiej (metodą najmniejszych kwadratów) dopasowana do przechowywanych w obiekcie danych.<br />
<br />
===Rozwiązanie=== <br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
import time<br />
import scipy.optimize as so<br />
<br />
class funkcja(object):<br />
def __init__(self,*args):<br />
self.args=args<br />
def __str__(self):<br />
return 'to jest funkcja o nazwie '+self.__class__.__name__+' i argumentach '+str(self.args)<br />
<br />
class liniowa(funkcja):<br />
def __call__(self,x):<br />
return self.args[0]*x*x+self.args[1]<br />
<br />
class DataContainer(object):<br />
def __init__(self,x,y):<br />
self.x=np.array(x)<br />
self.y=np.array(y)<br />
self.n=len(x)<br />
def __str__(self):<br />
return '''to jest Data Container z danymi:<br />
x[:10]:'''+str(self.x[:10])+'''<br />
y[:10]:'''+str(self.y[:10])<br />
<br />
def fit(self,funkcja):<br />
parametry_poczatkowe=funkcja.args<br />
def squares(parametry):<br />
funkcja.__init__(*tuple(parametry))<br />
return sum((map(funkcja,self.x)-self.y)**2)<br />
parametry_dopasowane=so.fmin(squares,parametry_poczatkowe)<br />
funkcja.__init__(*tuple(parametry_dopasowane))<br />
return funkcja<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
#generujemy wartosci funkcji z szumem<br />
f=liniowa(1.23,-0.73)<br />
ylist=[f(x)+0.05*np.random.randn() for x in xlist]<br />
<br />
d=DataContainer(xlist,ylist)<br />
f=liniowa(1,2)<br />
f=d.fit(f)<br />
py.plot(d.x,d.y)<br />
py.plot(d.x,map(f,d.x))<br />
py.show()<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Optymalizacja&diff=3637TI/Programowanie dla Fizyków Medycznych/Optymalizacja2015-06-09T14:17:07Z<p>Tgubiec: /* Zadanie - Data Container */</p>
<hr />
<div>==Optymalizacja jednowymiarowa==<br />
Omawianie zagadnienia optymalizacji rozpocznijmy od prostego przykładu. Zdefiniujmy pewną funkcję i zobaczmy jak wygląda jej wykres.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
licznikTestowej=0<br />
<br />
def testowa(x):<br />
global licznikTestowej<br />
licznikTestowej+=1<br />
return 1/x+np.exp(x)<br />
<br />
xtest=np.arange(0.2,2,0.01)<br />
ytest=[testowa(x) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
[[Plik:opt1.png]]<br />
<br />
Na rozważanym przedziale [0.2,2] powyższa funkcja ma tylko jedno ekstremum lokalne. Taką funkcję nazywamy unimodalną. Zmienna licznikTestowej umożliwi nam zliczanie wywołań funkcji testowej przez analizowane procedury.<br />
Zagadnienie, którym teraz będziemy się zajmować to problem numerycznego znajdowania takiego ekstremum. Jak w każdym problemie numerycznym ekstremum szukać będziemy zakładając pewną dokładność otrzymanego wyniku, którą oznaczmy xtol. Na wstępie przyjmijmy, że poszukujemy ekstremum z dokładnością xtol=0.01. Najprostszą metodą będzie policzenie wartości funkcji dla wszystkich wartości x z podanego przedziału co xtol. Jest to metoda siłowa i wielokrotnie licząca wartość funkcji. Jej kod możemy znaleźć poniżej. <br />
<source lang="python"><br />
def bruteForce(func,xmin,xmax,args=(),xtol=0.01):<br />
xlist=np.arange(xmin,xmax,xtol)<br />
ylist=[func(x,*args) for x in xlist]<br />
return xlist[ylist.index(max(ylist))]<br />
</source><br />
Innym, znacznie efektywniejszym sposobem znajdowania minimum może być następująca procedura rekurencyjna:<br />
*podzielmy przedział [xmin,xmax] na 3 równe część: [xmin,xL],[xL,xR] oraz [xR,xmax]<br />
*jeżeli wartość funkcji w xL jest mniejsza od wartości funkcji w xR to powtórz procedurę dla przedziału [xmin,xR]. W przeciwnym przypadku powtórz procedurę dla przedziału [xL,xmax].<br />
*zakończ działanie gdy badany przedział jest krótszy niż xtol.<br />
Przykładowa implementacja tej metody wygląda następująco<br />
<source lang="python"><br />
def twoMidPointsR(func,xmin,xmax,args=(),xtol=0.01):<br />
if xmax-xmin<xtol: return 0.5*(xmax+xmin)<br />
xL=xmin+(xmax-xmin)/3.0<br />
xR=xmax-(xmax-xmin)/3.0<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
if fxL>fxR:<br />
return twoMidPointsR(func,xmin,xR,args,xtol)<br />
else:<br />
return twoMidPointsR(func,xL,xmax,args,xtol)<br />
</source><br />
Tą metodą możemy już szukać minimum z dowolną dokładnością, co nie będzie skutkowało znacznie większym czasem obliczeń. Zauważmy jednak, że w każdej iteracji wartość minimalizowanej funkcji liczona jest dwukrotnie. Jeżeli mamy do czynienia z funkcją, dla której obliczenie pojedynczej wartości jest bardzo czasochłonne to warto się zastanowić czy nie można tego wyniku poprawić. Zauważmy, że dla działania tej metody wcale nie jest konieczne dzielenie badanego odcinka dokładnie na trzy równe części. Można dokonać tego podziału w zupełnie innej proporcji. Warto tak dobrać punkty xL i xR, aby xR pokrywał się z xL (lub xL z xR) w kolejnym kroku iteracji. Jeżeli dodatkowo stworzymy zmienne przechowujące wcześniej liczone wartości funkcji to uda nam się ograniczyć liczbę wywołań funkcji o połowę. Opisana metoda to tak zwana '''metoda złotego podziału'''. Przykładowa implementacja wygląda następująco <br />
<br />
<source lang="python"><br />
def GoldenRatioRearch(func,xmin,xmax,args=(),xtol=0.01):<br />
golden=0.5*(np.sqrt(5.0)-1.0)<br />
xL=xmax-golden*(xmax-xmin)<br />
xR=xmin+golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
while xmax-xmin>xtol: <br />
if fxL<fxR:<br />
xmin=xmin<br />
xmax=xR<br />
xR=xL<br />
fxR=fxL<br />
xL=xmax-golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
else:<br />
xmin=xL<br />
xmax=xmax<br />
xL=xR<br />
fxL=fxR<br />
xR=xmin+golden*(xmax-xmin)<br />
fxR=func(xR,*args)<br />
return 0.5*(xmax+xmin)<br />
<br />
</source><br />
Napisanej metody optymalizacji możemy użyć w celu dopasowania funkcji do danych empirycznych metodą najmniejszych kwadratów. Przykład takiego zastosowania poniżej.<br />
<br />
<source lang="python"><br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a): return x*a<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print GoldenRatioRearch(squares,0,10,args=(liniowa,xlist,ylist),xtol=0.01)<br />
</source><br />
<br />
==Optymalizacja wielowymiarowa==<br />
Przejście od optymalizacji jedno- do wielowymiarowej fundamentalnie komplikuje problem. Pierwszym problemem jest istnienie tak zwanych punktów siodłowych. Nie istnieją zatem metody które zawsze znajdują szukane minimum, nawet jeżeli wiadomo że takie istnieje. Najpopularniejszą metodą jest downhill symplex lub inaczej metoda Neldera-Meada. Z powodu znacznego stopnia komplikacji nie będziemy jej samodzielnie implementować, a jedynie posłużymy się implementacją z biblioteki scipy.optimize. Dzięki niej możemy np. dopasowywać funkcję z więcej niż jednym parametrem do danych eksperymentalnych.<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a,b): return x*a+b<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23,-0.73)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print so.fmin(squares,(1,0),args=(kwadratowa,xlist,ylist))<br />
</source><br />
Oczywiście dopasowywanie możemy przeprowadzać nie tylko metodą najmniejszych kwadratów.<br />
===Zadanie - rozkład Cauchy'ego===<br />
Wylosuj 10000 liczb z rozkładu Cauchy'ego z parametrami loc=1.23 i scale=2.0. Do wylosowanych danych dopasuj rozkład Cauche'ego trzema metodami<br />
*METODA 1 - stwórz histogram otrzymanych wartości, znormalizuj go i metodą najmniejszych kwadratów dopasauj gęstość rozkładu do histogramu<br />
*METODA 2 - dopasuj gęstość rozkładu do wylosowanych danych metodą największej wiarygodności<br />
*METODA 3 - z wylosowanych danych stwórz dystrybuantę empiryczną. Metodą najmniejszych kwadratów dopasuj dystrybuantę rozkładu Cauchy'ego do dystrybuanty empirycznej.<br />
<br />
===Rozwiązanie===<br />
<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
def rho_cauchy(x,loc,scale):<br />
return (np.pi*scale*(1.0+(x-loc)**2/(scale**2)))**(-1.0)<br />
<br />
def F_cauchy(x,loc,scale):<br />
return 0.5+np.arctan((x-loc)*1.0/scale)/np.pi<br />
<br />
<br />
#losujemy 10000 liczb z rozkladu Cauchyego o loc=1.23 i scale=2.0<br />
x=2*np.random.standard_cauchy(10000)+1.23<br />
N=len(x)<br />
<br />
#METODA 1 - Dopasowanie metoda najmniejszych kwadratow do histogramu<br />
<br />
#tworzymy histogram<br />
hist,bins= np.histogram(x,bins=np.linspace(-20,20,61))<br />
#dlugosc przedzialu histogramowania<br />
przedzial=bins[1]-bins[0]<br />
#normalizujemy histogram aby moc go porownac z gestoscia<br />
hist=hist*1.0/len(x)/przedzial<br />
#liczymy wsp. srodkow przedzialow histogramowania<br />
xhist=bins[:-1]+0.5*przedzial<br />
#definiujemy sume kwadratow<br />
def squares((loc,scale)):<br />
return sum([(rho_cauchy(xhist[i],loc,scale)-hist[i])**2 for i in range(len(hist))])<br />
#szukamy minimum funkcja fmin<br />
fit1=tuple(so.fmin(squares,(0,1)))<br />
print 'wynik metody1 to '+str(fit1)<br />
#ogladamy wynik<br />
py.plot(xhist,hist)<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[rho_cauchy(a,*fit1) for a in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
<br />
<br />
#METODA 2 - Metoda najwiekszej wiarygodnosci<br />
<br />
#definiujemy -funkcje wiarygodnosci<br />
def L((loc,scale)):<br />
return -sum([np.log(rho_cauchy(a,loc,scale)) for a in x])<br />
#szukamy minimum<br />
fit2=tuple(so.fmin(L,(0,1)))<br />
print 'wynik metody2 to '+str(fit2)<br />
<br />
<br />
#METODA 3 - dopasowanie dystrybuant<br />
<br />
xx=sorted(x)<br />
yy=np.linspace(0,1,N)<br />
#definiujemy funkcje KS bedaca maksimum z roznicy miedzy dystrybuanta empiryczna a teoretyczna<br />
def KS((loc,scale)):<br />
return max([abs(F_cauchy(xx[i],loc,scale)-yy[i]) for i in xrange(N)])<br />
#szukamy minimum<br />
fit3=tuple(so.fmin(KS,(0,1)))<br />
print 'wynik metody3 to '+str(fit3)<br />
#ogladamy wynik<br />
cut=100<br />
py.plot(xx[cut:-cut],yy[cut:-cut])<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[F_cauchy(x,*fit3) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
===Zadanie - Data Container===<br />
Napisz klasę funkcja przyjmującą w konstruktorze parametry funkcji i posiadającą reprezentację tekstową. Napisz dowolna funkcję dziedziczącą po klasie funkcja, której metoda call przyjmuje jeden argument i zwraca wartość funkcji dla podanego argumentu i parametrów podanych w konstruktorze. <br />
Napisz klasę DataContainer przyjmującą w konstruktorze dwie serie danych empirycznych o tej samej długości odpowiadające współrzędnym x i y. Obiekt klasy DataContainer powinien być wyposażony w metodę o nazwie fit, która przyjmuje jako argument obiekt klasy funkcja. Metoda fit powinna zwracać obiekt takiej samej klasy jak otrzymany w argumencie, ale z parametrami, dla których funkcja jest najlepiej (metodą najmniejszych kwadratów) dopasowana do przechowywanych w obiekcie danych.<br />
<br />
===Rozwiązanie=== <br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
import time<br />
import scipy.optimize as so<br />
<br />
class funkcja(object):<br />
def __init__(self,*args):<br />
self.args=args<br />
def __str__(self):<br />
return 'to jest funkcja o nazwie '+self.__class__.__name__+' i argumentach '+str(self.args)<br />
<br />
class liniowa(funkcja):<br />
def __call__(self,x):<br />
return self.args[0]*x*x+self.args[1]<br />
<br />
class DataContainer(object):<br />
def __init__(self,x,y):<br />
self.x=np.array(x)<br />
self.y=np.array(y)<br />
self.n=len(x)<br />
def __str__(self):<br />
return '''to jest Data Container z danymi:<br />
x[:10]:'''+str(self.x[:10])+'''<br />
y[:10]:'''+str(self.y[:10])<br />
<br />
def fit(self,funkcja):<br />
parametry_poczatkowe=funkcja.args<br />
def squares(parametry):<br />
funkcja.__init__(*tuple(parametry))<br />
return sum((map(funkcja,self.x)-self.y)**2)<br />
parametry_dopasowane=so.fmin(squares,parametry_poczatkowe)<br />
funkcja.__init__(*tuple(parametry_dopasowane))<br />
return funkcja<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
#generujemy wartosci funkcji z szumem<br />
f=liniowa(1.23,-0.73)<br />
ylist=[f(x)+0.05*np.random.randn() for x in xlist]<br />
<br />
d=DataContainer(xlist,ylist)<br />
f=liniowa(1,2)<br />
f=d.fit(f)<br />
py.plot(d.x,d.y)<br />
py.plot(d.x,map(f,d.x))<br />
py.show()<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Optymalizacja&diff=3636TI/Programowanie dla Fizyków Medycznych/Optymalizacja2015-06-09T14:15:03Z<p>Tgubiec: /* Rozwiązanie */</p>
<hr />
<div>==Optymalizacja jednowymiarowa==<br />
Omawianie zagadnienia optymalizacji rozpocznijmy od prostego przykładu. Zdefiniujmy pewną funkcję i zobaczmy jak wygląda jej wykres.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
licznikTestowej=0<br />
<br />
def testowa(x):<br />
global licznikTestowej<br />
licznikTestowej+=1<br />
return 1/x+np.exp(x)<br />
<br />
xtest=np.arange(0.2,2,0.01)<br />
ytest=[testowa(x) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
[[Plik:opt1.png]]<br />
<br />
Na rozważanym przedziale [0.2,2] powyższa funkcja ma tylko jedno ekstremum lokalne. Taką funkcję nazywamy unimodalną. Zmienna licznikTestowej umożliwi nam zliczanie wywołań funkcji testowej przez analizowane procedury.<br />
Zagadnienie, którym teraz będziemy się zajmować to problem numerycznego znajdowania takiego ekstremum. Jak w każdym problemie numerycznym ekstremum szukać będziemy zakładając pewną dokładność otrzymanego wyniku, którą oznaczmy xtol. Na wstępie przyjmijmy, że poszukujemy ekstremum z dokładnością xtol=0.01. Najprostszą metodą będzie policzenie wartości funkcji dla wszystkich wartości x z podanego przedziału co xtol. Jest to metoda siłowa i wielokrotnie licząca wartość funkcji. Jej kod możemy znaleźć poniżej. <br />
<source lang="python"><br />
def bruteForce(func,xmin,xmax,args=(),xtol=0.01):<br />
xlist=np.arange(xmin,xmax,xtol)<br />
ylist=[func(x,*args) for x in xlist]<br />
return xlist[ylist.index(max(ylist))]<br />
</source><br />
Innym, znacznie efektywniejszym sposobem znajdowania minimum może być następująca procedura rekurencyjna:<br />
*podzielmy przedział [xmin,xmax] na 3 równe część: [xmin,xL],[xL,xR] oraz [xR,xmax]<br />
*jeżeli wartość funkcji w xL jest mniejsza od wartości funkcji w xR to powtórz procedurę dla przedziału [xmin,xR]. W przeciwnym przypadku powtórz procedurę dla przedziału [xL,xmax].<br />
*zakończ działanie gdy badany przedział jest krótszy niż xtol.<br />
Przykładowa implementacja tej metody wygląda następująco<br />
<source lang="python"><br />
def twoMidPointsR(func,xmin,xmax,args=(),xtol=0.01):<br />
if xmax-xmin<xtol: return 0.5*(xmax+xmin)<br />
xL=xmin+(xmax-xmin)/3.0<br />
xR=xmax-(xmax-xmin)/3.0<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
if fxL>fxR:<br />
return twoMidPointsR(func,xmin,xR,args,xtol)<br />
else:<br />
return twoMidPointsR(func,xL,xmax,args,xtol)<br />
</source><br />
Tą metodą możemy już szukać minimum z dowolną dokładnością, co nie będzie skutkowało znacznie większym czasem obliczeń. Zauważmy jednak, że w każdej iteracji wartość minimalizowanej funkcji liczona jest dwukrotnie. Jeżeli mamy do czynienia z funkcją, dla której obliczenie pojedynczej wartości jest bardzo czasochłonne to warto się zastanowić czy nie można tego wyniku poprawić. Zauważmy, że dla działania tej metody wcale nie jest konieczne dzielenie badanego odcinka dokładnie na trzy równe części. Można dokonać tego podziału w zupełnie innej proporcji. Warto tak dobrać punkty xL i xR, aby xR pokrywał się z xL (lub xL z xR) w kolejnym kroku iteracji. Jeżeli dodatkowo stworzymy zmienne przechowujące wcześniej liczone wartości funkcji to uda nam się ograniczyć liczbę wywołań funkcji o połowę. Opisana metoda to tak zwana '''metoda złotego podziału'''. Przykładowa implementacja wygląda następująco <br />
<br />
<source lang="python"><br />
def GoldenRatioRearch(func,xmin,xmax,args=(),xtol=0.01):<br />
golden=0.5*(np.sqrt(5.0)-1.0)<br />
xL=xmax-golden*(xmax-xmin)<br />
xR=xmin+golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
while xmax-xmin>xtol: <br />
if fxL<fxR:<br />
xmin=xmin<br />
xmax=xR<br />
xR=xL<br />
fxR=fxL<br />
xL=xmax-golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
else:<br />
xmin=xL<br />
xmax=xmax<br />
xL=xR<br />
fxL=fxR<br />
xR=xmin+golden*(xmax-xmin)<br />
fxR=func(xR,*args)<br />
return 0.5*(xmax+xmin)<br />
<br />
</source><br />
Napisanej metody optymalizacji możemy użyć w celu dopasowania funkcji do danych empirycznych metodą najmniejszych kwadratów. Przykład takiego zastosowania poniżej.<br />
<br />
<source lang="python"><br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a): return x*a<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print GoldenRatioRearch(squares,0,10,args=(liniowa,xlist,ylist),xtol=0.01)<br />
</source><br />
<br />
==Optymalizacja wielowymiarowa==<br />
Przejście od optymalizacji jedno- do wielowymiarowej fundamentalnie komplikuje problem. Pierwszym problemem jest istnienie tak zwanych punktów siodłowych. Nie istnieją zatem metody które zawsze znajdują szukane minimum, nawet jeżeli wiadomo że takie istnieje. Najpopularniejszą metodą jest downhill symplex lub inaczej metoda Neldera-Meada. Z powodu znacznego stopnia komplikacji nie będziemy jej samodzielnie implementować, a jedynie posłużymy się implementacją z biblioteki scipy.optimize. Dzięki niej możemy np. dopasowywać funkcję z więcej niż jednym parametrem do danych eksperymentalnych.<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a,b): return x*a+b<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23,-0.73)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print so.fmin(squares,(1,0),args=(kwadratowa,xlist,ylist))<br />
</source><br />
Oczywiście dopasowywanie możemy przeprowadzać nie tylko metodą najmniejszych kwadratów.<br />
===Zadanie - rozkład Cauchy'ego===<br />
Wylosuj 10000 liczb z rozkładu Cauchy'ego z parametrami loc=1.23 i scale=2.0. Do wylosowanych danych dopasuj rozkład Cauche'ego trzema metodami<br />
*METODA 1 - stwórz histogram otrzymanych wartości, znormalizuj go i metodą najmniejszych kwadratów dopasauj gęstość rozkładu do histogramu<br />
*METODA 2 - dopasuj gęstość rozkładu do wylosowanych danych metodą największej wiarygodności<br />
*METODA 3 - z wylosowanych danych stwórz dystrybuantę empiryczną. Metodą najmniejszych kwadratów dopasuj dystrybuantę rozkładu Cauchy'ego do dystrybuanty empirycznej.<br />
<br />
===Rozwiązanie===<br />
<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
def rho_cauchy(x,loc,scale):<br />
return (np.pi*scale*(1.0+(x-loc)**2/(scale**2)))**(-1.0)<br />
<br />
def F_cauchy(x,loc,scale):<br />
return 0.5+np.arctan((x-loc)*1.0/scale)/np.pi<br />
<br />
<br />
#losujemy 10000 liczb z rozkladu Cauchyego o loc=1.23 i scale=2.0<br />
x=2*np.random.standard_cauchy(10000)+1.23<br />
N=len(x)<br />
<br />
#METODA 1 - Dopasowanie metoda najmniejszych kwadratow do histogramu<br />
<br />
#tworzymy histogram<br />
hist,bins= np.histogram(x,bins=np.linspace(-20,20,61))<br />
#dlugosc przedzialu histogramowania<br />
przedzial=bins[1]-bins[0]<br />
#normalizujemy histogram aby moc go porownac z gestoscia<br />
hist=hist*1.0/len(x)/przedzial<br />
#liczymy wsp. srodkow przedzialow histogramowania<br />
xhist=bins[:-1]+0.5*przedzial<br />
#definiujemy sume kwadratow<br />
def squares((loc,scale)):<br />
return sum([(rho_cauchy(xhist[i],loc,scale)-hist[i])**2 for i in range(len(hist))])<br />
#szukamy minimum funkcja fmin<br />
fit1=tuple(so.fmin(squares,(0,1)))<br />
print 'wynik metody1 to '+str(fit1)<br />
#ogladamy wynik<br />
py.plot(xhist,hist)<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[rho_cauchy(a,*fit1) for a in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
<br />
<br />
#METODA 2 - Metoda najwiekszej wiarygodnosci<br />
<br />
#definiujemy -funkcje wiarygodnosci<br />
def L((loc,scale)):<br />
return -sum([np.log(rho_cauchy(a,loc,scale)) for a in x])<br />
#szukamy minimum<br />
fit2=tuple(so.fmin(L,(0,1)))<br />
print 'wynik metody2 to '+str(fit2)<br />
<br />
<br />
#METODA 3 - dopasowanie dystrybuant<br />
<br />
xx=sorted(x)<br />
yy=np.linspace(0,1,N)<br />
#definiujemy funkcje KS bedaca maksimum z roznicy miedzy dystrybuanta empiryczna a teoretyczna<br />
def KS((loc,scale)):<br />
return max([abs(F_cauchy(xx[i],loc,scale)-yy[i]) for i in xrange(N)])<br />
#szukamy minimum<br />
fit3=tuple(so.fmin(KS,(0,1)))<br />
print 'wynik metody3 to '+str(fit3)<br />
#ogladamy wynik<br />
cut=100<br />
py.plot(xx[cut:-cut],yy[cut:-cut])<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[F_cauchy(x,*fit3) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
===Zadanie - Data Container===<br />
Napisz klasę funkcja przyjmującą w konstruktorze parametry funkcji i posiadającąreprezentacje tekstową. Napisz dowolna funkcję dziedziczącą po klasie funkcja, której metoda call przyjmuje jeden argument i zwraca wartość funkcji dla podanego argumentu i parametrów podanych w konstruktorze. <br />
Napisz klasę DataContainer przyjmującą w konstruktorze dwie serie danych empirycznych o tej samej długości odpowiadające wspólrzędnym x i y. Obiekt klasy DataContainer powinien być wyposażony w metodę o nazwie fit, która przyjmuje jako argument obiekt klasy funkcja. Metoda fit powinna zwracać obiekt takiej samej klasy jak otrzymany w argumencie ale z parametrami dla których funkcja jest najlepiej (metodą najmniejszych kwadratów) dopasowana do przechowywanych w obiekcie danych.<br />
===Rozwiązanie=== <br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
import time<br />
import scipy.optimize as so<br />
<br />
class funkcja(object):<br />
def __init__(self,*args):<br />
self.args=args<br />
def __str__(self):<br />
return 'to jest funkcja o nazwie '+self.__class__.__name__+' i argumentach '+str(self.args)<br />
<br />
class liniowa(funkcja):<br />
def __call__(self,x):<br />
return self.args[0]*x*x+self.args[1]<br />
<br />
class DataContainer(object):<br />
def __init__(self,x,y):<br />
self.x=np.array(x)<br />
self.y=np.array(y)<br />
self.n=len(x)<br />
def __str__(self):<br />
return '''to jest Data Container z danymi:<br />
x[:10]:'''+str(self.x[:10])+'''<br />
y[:10]:'''+str(self.y[:10])<br />
<br />
def fit(self,funkcja):<br />
parametry_poczatkowe=funkcja.args<br />
def squares(parametry):<br />
funkcja.__init__(*tuple(parametry))<br />
return sum((map(funkcja,self.x)-self.y)**2)<br />
parametry_dopasowane=so.fmin(squares,parametry_poczatkowe)<br />
funkcja.__init__(*tuple(parametry_dopasowane))<br />
return funkcja<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
#generujemy wartosci funkcji z szumem<br />
f=liniowa(1.23,-0.73)<br />
ylist=[f(x)+0.05*np.random.randn() for x in xlist]<br />
<br />
d=DataContainer(xlist,ylist)<br />
f=liniowa(1,2)<br />
f=d.fit(f)<br />
py.plot(d.x,d.y)<br />
py.plot(d.x,map(f,d.x))<br />
py.show()<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Optymalizacja&diff=3635TI/Programowanie dla Fizyków Medycznych/Optymalizacja2015-06-09T14:13:20Z<p>Tgubiec: /* Zadanie - rozkład Cauchy'ego */</p>
<hr />
<div>==Optymalizacja jednowymiarowa==<br />
Omawianie zagadnienia optymalizacji rozpocznijmy od prostego przykładu. Zdefiniujmy pewną funkcję i zobaczmy jak wygląda jej wykres.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
licznikTestowej=0<br />
<br />
def testowa(x):<br />
global licznikTestowej<br />
licznikTestowej+=1<br />
return 1/x+np.exp(x)<br />
<br />
xtest=np.arange(0.2,2,0.01)<br />
ytest=[testowa(x) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
[[Plik:opt1.png]]<br />
<br />
Na rozważanym przedziale [0.2,2] powyższa funkcja ma tylko jedno ekstremum lokalne. Taką funkcję nazywamy unimodalną. Zmienna licznikTestowej umożliwi nam zliczanie wywołań funkcji testowej przez analizowane procedury.<br />
Zagadnienie, którym teraz będziemy się zajmować to problem numerycznego znajdowania takiego ekstremum. Jak w każdym problemie numerycznym ekstremum szukać będziemy zakładając pewną dokładność otrzymanego wyniku, którą oznaczmy xtol. Na wstępie przyjmijmy, że poszukujemy ekstremum z dokładnością xtol=0.01. Najprostszą metodą będzie policzenie wartości funkcji dla wszystkich wartości x z podanego przedziału co xtol. Jest to metoda siłowa i wielokrotnie licząca wartość funkcji. Jej kod możemy znaleźć poniżej. <br />
<source lang="python"><br />
def bruteForce(func,xmin,xmax,args=(),xtol=0.01):<br />
xlist=np.arange(xmin,xmax,xtol)<br />
ylist=[func(x,*args) for x in xlist]<br />
return xlist[ylist.index(max(ylist))]<br />
</source><br />
Innym, znacznie efektywniejszym sposobem znajdowania minimum może być następująca procedura rekurencyjna:<br />
*podzielmy przedział [xmin,xmax] na 3 równe część: [xmin,xL],[xL,xR] oraz [xR,xmax]<br />
*jeżeli wartość funkcji w xL jest mniejsza od wartości funkcji w xR to powtórz procedurę dla przedziału [xmin,xR]. W przeciwnym przypadku powtórz procedurę dla przedziału [xL,xmax].<br />
*zakończ działanie gdy badany przedział jest krótszy niż xtol.<br />
Przykładowa implementacja tej metody wygląda następująco<br />
<source lang="python"><br />
def twoMidPointsR(func,xmin,xmax,args=(),xtol=0.01):<br />
if xmax-xmin<xtol: return 0.5*(xmax+xmin)<br />
xL=xmin+(xmax-xmin)/3.0<br />
xR=xmax-(xmax-xmin)/3.0<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
if fxL>fxR:<br />
return twoMidPointsR(func,xmin,xR,args,xtol)<br />
else:<br />
return twoMidPointsR(func,xL,xmax,args,xtol)<br />
</source><br />
Tą metodą możemy już szukać minimum z dowolną dokładnością, co nie będzie skutkowało znacznie większym czasem obliczeń. Zauważmy jednak, że w każdej iteracji wartość minimalizowanej funkcji liczona jest dwukrotnie. Jeżeli mamy do czynienia z funkcją, dla której obliczenie pojedynczej wartości jest bardzo czasochłonne to warto się zastanowić czy nie można tego wyniku poprawić. Zauważmy, że dla działania tej metody wcale nie jest konieczne dzielenie badanego odcinka dokładnie na trzy równe części. Można dokonać tego podziału w zupełnie innej proporcji. Warto tak dobrać punkty xL i xR, aby xR pokrywał się z xL (lub xL z xR) w kolejnym kroku iteracji. Jeżeli dodatkowo stworzymy zmienne przechowujące wcześniej liczone wartości funkcji to uda nam się ograniczyć liczbę wywołań funkcji o połowę. Opisana metoda to tak zwana '''metoda złotego podziału'''. Przykładowa implementacja wygląda następująco <br />
<br />
<source lang="python"><br />
def GoldenRatioRearch(func,xmin,xmax,args=(),xtol=0.01):<br />
golden=0.5*(np.sqrt(5.0)-1.0)<br />
xL=xmax-golden*(xmax-xmin)<br />
xR=xmin+golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
while xmax-xmin>xtol: <br />
if fxL<fxR:<br />
xmin=xmin<br />
xmax=xR<br />
xR=xL<br />
fxR=fxL<br />
xL=xmax-golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
else:<br />
xmin=xL<br />
xmax=xmax<br />
xL=xR<br />
fxL=fxR<br />
xR=xmin+golden*(xmax-xmin)<br />
fxR=func(xR,*args)<br />
return 0.5*(xmax+xmin)<br />
<br />
</source><br />
Napisanej metody optymalizacji możemy użyć w celu dopasowania funkcji do danych empirycznych metodą najmniejszych kwadratów. Przykład takiego zastosowania poniżej.<br />
<br />
<source lang="python"><br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a): return x*a<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print GoldenRatioRearch(squares,0,10,args=(liniowa,xlist,ylist),xtol=0.01)<br />
</source><br />
<br />
==Optymalizacja wielowymiarowa==<br />
Przejście od optymalizacji jedno- do wielowymiarowej fundamentalnie komplikuje problem. Pierwszym problemem jest istnienie tak zwanych punktów siodłowych. Nie istnieją zatem metody które zawsze znajdują szukane minimum, nawet jeżeli wiadomo że takie istnieje. Najpopularniejszą metodą jest downhill symplex lub inaczej metoda Neldera-Meada. Z powodu znacznego stopnia komplikacji nie będziemy jej samodzielnie implementować, a jedynie posłużymy się implementacją z biblioteki scipy.optimize. Dzięki niej możemy np. dopasowywać funkcję z więcej niż jednym parametrem do danych eksperymentalnych.<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a,b): return x*a+b<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23,-0.73)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print so.fmin(squares,(1,0),args=(kwadratowa,xlist,ylist))<br />
</source><br />
Oczywiście dopasowywanie możemy przeprowadzać nie tylko metodą najmniejszych kwadratów.<br />
===Zadanie - rozkład Cauchy'ego===<br />
Wylosuj 10000 liczb z rozkładu Cauchy'ego z parametrami loc=1.23 i scale=2.0. Do wylosowanych danych dopasuj rozkład Cauche'ego trzema metodami<br />
*METODA 1 - stwórz histogram otrzymanych wartości, znormalizuj go i metodą najmniejszych kwadratów dopasauj gęstość rozkładu do histogramu<br />
*METODA 2 - dopasuj gęstość rozkładu do wylosowanych danych metodą największej wiarygodności<br />
*METODA 3 - z wylosowanych danych stwórz dystrybuantę empiryczną. Metodą najmniejszych kwadratów dopasuj dystrybuantę rozkładu Cauchy'ego do dystrybuanty empirycznej.<br />
<br />
===Rozwiązanie===<br />
<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
def rho_cauchy(x,loc,scale):<br />
return (np.pi*scale*(1.0+(x-loc)**2/(scale**2)))**(-1.0)<br />
<br />
def F_cauchy(x,loc,scale):<br />
return 0.5+np.arctan((x-loc)*1.0/scale)/np.pi<br />
<br />
<br />
#losujemy 10000 liczb z rozkladu Caychyego o loc=1.23 i scale=2.0<br />
x=2*np.random.standard_cauchy(10000)+1.23<br />
N=len(x)<br />
<br />
#METODA 1 - Dopasowanie metoda najmniejszych kwadratow do histogramu<br />
<br />
#tworzymy histogram<br />
hist,bins= np.histogram(x,bins=np.linspace(-20,20,61))<br />
#dlugosc przedzialu histogramowania<br />
przedzial=bins[1]-bins[0]<br />
#normalizujemy histogram aby moc go porownac z gestoscia<br />
hist=hist*1.0/len(x)/przedzial<br />
#liczymy wsp. srodkow przedzialow histogramowania<br />
xhist=bins[:-1]+0.5*przedzial<br />
#definiujemy sume kwadratow<br />
def squares((loc,scale)):<br />
return sum([(rho_cauchy(xhist[i],loc,scale)-hist[i])**2 for i in range(len(hist))])<br />
#szukamy minimum funkcja fmin<br />
fit1=tuple(so.fmin(squares,(0,1)))<br />
print 'wynik metody1 to '+str(fit1)<br />
#ogladamy wynik<br />
py.plot(xhist,hist)<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[rho_cauchy(a,*fit1) for a in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
<br />
<br />
#METODA 2 - Metoda najwiekszej wiarygodnosci<br />
<br />
#definiujemy -funkcje wiarygodnosci<br />
def L((loc,scale)):<br />
return -sum([np.log(rho_cauchy(a,loc,scale)) for a in x])<br />
#szukamy minimum<br />
fit2=tuple(so.fmin(L,(0,1)))<br />
print 'wynik metody2 to '+str(fit2)<br />
<br />
<br />
#METODA 3 - dopasowanie dystrybuant<br />
<br />
xx=sorted(x)<br />
yy=np.linspace(0,1,N)<br />
#definiujemy funkcje KS bedaca maksimum z roznicy miedzy dystrybuanta empiryczna a teoretyczna<br />
def KS((loc,scale)):<br />
return max([abs(F_cauchy(xx[i],loc,scale)-yy[i]) for i in xrange(N)])<br />
#szukamy minimum<br />
fit3=tuple(so.fmin(KS,(0,1)))<br />
print 'wynik metody3 to '+str(fit3)<br />
#ogladamy wynik<br />
cut=100<br />
py.plot(xx[cut:-cut],yy[cut:-cut])<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[F_cauchy(x,*fit3) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
===Zadanie - Data Container===<br />
Napisz klasę funkcja przyjmującą w konstruktorze parametry funkcji i posiadającąreprezentacje tekstową. Napisz dowolna funkcję dziedziczącą po klasie funkcja, której metoda call przyjmuje jeden argument i zwraca wartość funkcji dla podanego argumentu i parametrów podanych w konstruktorze. <br />
Napisz klasę DataContainer przyjmującą w konstruktorze dwie serie danych empirycznych o tej samej długości odpowiadające wspólrzędnym x i y. Obiekt klasy DataContainer powinien być wyposażony w metodę o nazwie fit, która przyjmuje jako argument obiekt klasy funkcja. Metoda fit powinna zwracać obiekt takiej samej klasy jak otrzymany w argumencie ale z parametrami dla których funkcja jest najlepiej (metodą najmniejszych kwadratów) dopasowana do przechowywanych w obiekcie danych.<br />
===Rozwiązanie=== <br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
import time<br />
import scipy.optimize as so<br />
<br />
class funkcja(object):<br />
def __init__(self,*args):<br />
self.args=args<br />
def __str__(self):<br />
return 'to jest funkcja o nazwie '+self.__class__.__name__+' i argumentach '+str(self.args)<br />
<br />
class liniowa(funkcja):<br />
def __call__(self,x):<br />
return self.args[0]*x*x+self.args[1]<br />
<br />
class DataContainer(object):<br />
def __init__(self,x,y):<br />
self.x=np.array(x)<br />
self.y=np.array(y)<br />
self.n=len(x)<br />
def __str__(self):<br />
return '''to jest Data Container z danymi:<br />
x[:10]:'''+str(self.x[:10])+'''<br />
y[:10]:'''+str(self.y[:10])<br />
<br />
def fit(self,funkcja):<br />
parametry_poczatkowe=funkcja.args<br />
def squares(parametry):<br />
funkcja.__init__(*tuple(parametry))<br />
return sum((map(funkcja,self.x)-self.y)**2)<br />
parametry_dopasowane=so.fmin(squares,parametry_poczatkowe)<br />
funkcja.__init__(*tuple(parametry_dopasowane))<br />
return funkcja<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
#generujemy wartosci funkcji z szumem<br />
f=liniowa(1.23,-0.73)<br />
ylist=[f(x)+0.05*np.random.randn() for x in xlist]<br />
<br />
d=DataContainer(xlist,ylist)<br />
f=liniowa(1,2)<br />
f=d.fit(f)<br />
py.plot(d.x,d.y)<br />
py.plot(d.x,map(f,d.x))<br />
py.show()<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Optymalizacja&diff=3634TI/Programowanie dla Fizyków Medycznych/Optymalizacja2015-06-09T14:12:18Z<p>Tgubiec: /* Zadanie - rozklad Cauchy'ego */</p>
<hr />
<div>==Optymalizacja jednowymiarowa==<br />
Omawianie zagadnienia optymalizacji rozpocznijmy od prostego przykładu. Zdefiniujmy pewną funkcję i zobaczmy jak wygląda jej wykres.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
licznikTestowej=0<br />
<br />
def testowa(x):<br />
global licznikTestowej<br />
licznikTestowej+=1<br />
return 1/x+np.exp(x)<br />
<br />
xtest=np.arange(0.2,2,0.01)<br />
ytest=[testowa(x) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
[[Plik:opt1.png]]<br />
<br />
Na rozważanym przedziale [0.2,2] powyższa funkcja ma tylko jedno ekstremum lokalne. Taką funkcję nazywamy unimodalną. Zmienna licznikTestowej umożliwi nam zliczanie wywołań funkcji testowej przez analizowane procedury.<br />
Zagadnienie, którym teraz będziemy się zajmować to problem numerycznego znajdowania takiego ekstremum. Jak w każdym problemie numerycznym ekstremum szukać będziemy zakładając pewną dokładność otrzymanego wyniku, którą oznaczmy xtol. Na wstępie przyjmijmy, że poszukujemy ekstremum z dokładnością xtol=0.01. Najprostszą metodą będzie policzenie wartości funkcji dla wszystkich wartości x z podanego przedziału co xtol. Jest to metoda siłowa i wielokrotnie licząca wartość funkcji. Jej kod możemy znaleźć poniżej. <br />
<source lang="python"><br />
def bruteForce(func,xmin,xmax,args=(),xtol=0.01):<br />
xlist=np.arange(xmin,xmax,xtol)<br />
ylist=[func(x,*args) for x in xlist]<br />
return xlist[ylist.index(max(ylist))]<br />
</source><br />
Innym, znacznie efektywniejszym sposobem znajdowania minimum może być następująca procedura rekurencyjna:<br />
*podzielmy przedział [xmin,xmax] na 3 równe część: [xmin,xL],[xL,xR] oraz [xR,xmax]<br />
*jeżeli wartość funkcji w xL jest mniejsza od wartości funkcji w xR to powtórz procedurę dla przedziału [xmin,xR]. W przeciwnym przypadku powtórz procedurę dla przedziału [xL,xmax].<br />
*zakończ działanie gdy badany przedział jest krótszy niż xtol.<br />
Przykładowa implementacja tej metody wygląda następująco<br />
<source lang="python"><br />
def twoMidPointsR(func,xmin,xmax,args=(),xtol=0.01):<br />
if xmax-xmin<xtol: return 0.5*(xmax+xmin)<br />
xL=xmin+(xmax-xmin)/3.0<br />
xR=xmax-(xmax-xmin)/3.0<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
if fxL>fxR:<br />
return twoMidPointsR(func,xmin,xR,args,xtol)<br />
else:<br />
return twoMidPointsR(func,xL,xmax,args,xtol)<br />
</source><br />
Tą metodą możemy już szukać minimum z dowolną dokładnością, co nie będzie skutkowało znacznie większym czasem obliczeń. Zauważmy jednak, że w każdej iteracji wartość minimalizowanej funkcji liczona jest dwukrotnie. Jeżeli mamy do czynienia z funkcją, dla której obliczenie pojedynczej wartości jest bardzo czasochłonne to warto się zastanowić czy nie można tego wyniku poprawić. Zauważmy, że dla działania tej metody wcale nie jest konieczne dzielenie badanego odcinka dokładnie na trzy równe części. Można dokonać tego podziału w zupełnie innej proporcji. Warto tak dobrać punkty xL i xR, aby xR pokrywał się z xL (lub xL z xR) w kolejnym kroku iteracji. Jeżeli dodatkowo stworzymy zmienne przechowujące wcześniej liczone wartości funkcji to uda nam się ograniczyć liczbę wywołań funkcji o połowę. Opisana metoda to tak zwana '''metoda złotego podziału'''. Przykładowa implementacja wygląda następująco <br />
<br />
<source lang="python"><br />
def GoldenRatioRearch(func,xmin,xmax,args=(),xtol=0.01):<br />
golden=0.5*(np.sqrt(5.0)-1.0)<br />
xL=xmax-golden*(xmax-xmin)<br />
xR=xmin+golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
while xmax-xmin>xtol: <br />
if fxL<fxR:<br />
xmin=xmin<br />
xmax=xR<br />
xR=xL<br />
fxR=fxL<br />
xL=xmax-golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
else:<br />
xmin=xL<br />
xmax=xmax<br />
xL=xR<br />
fxL=fxR<br />
xR=xmin+golden*(xmax-xmin)<br />
fxR=func(xR,*args)<br />
return 0.5*(xmax+xmin)<br />
<br />
</source><br />
Napisanej metody optymalizacji możemy użyć w celu dopasowania funkcji do danych empirycznych metodą najmniejszych kwadratów. Przykład takiego zastosowania poniżej.<br />
<br />
<source lang="python"><br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a): return x*a<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print GoldenRatioRearch(squares,0,10,args=(liniowa,xlist,ylist),xtol=0.01)<br />
</source><br />
<br />
==Optymalizacja wielowymiarowa==<br />
Przejście od optymalizacji jedno- do wielowymiarowej fundamentalnie komplikuje problem. Pierwszym problemem jest istnienie tak zwanych punktów siodłowych. Nie istnieją zatem metody które zawsze znajdują szukane minimum, nawet jeżeli wiadomo że takie istnieje. Najpopularniejszą metodą jest downhill symplex lub inaczej metoda Neldera-Meada. Z powodu znacznego stopnia komplikacji nie będziemy jej samodzielnie implementować, a jedynie posłużymy się implementacją z biblioteki scipy.optimize. Dzięki niej możemy np. dopasowywać funkcję z więcej niż jednym parametrem do danych eksperymentalnych.<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a,b): return x*a+b<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23,-0.73)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print so.fmin(squares,(1,0),args=(kwadratowa,xlist,ylist))<br />
</source><br />
Oczywiście dopasowywanie możemy przeprowadzać nie tylko metodą najmniejszych kwadratów.<br />
===Zadanie - rozkład Cauchy'ego===<br />
Wylosuj 1000 liczb z rozkładu Cauchy'ego z parametrami loc=1.23 i scale=2.0. Do wylosowanych danych dopasuj rozkład Cauche'ego trzema metodami<br />
*METODA 1 - stwórz histogram otrzymanych wartości, znormalizuj go i metodą najmniejszych kwadratów dopasauj gęstość rozkładu do histogramu<br />
*METODA 2 - dopasuj gęstość rozkładu do wylosowanych danych metodą największej wiarygodności<br />
*METODA 3 - z wylosowanych danych stwórz dystrybuantę empiryczną. Metodą najmniejszych kwadratów dopasuj dystrybuantę rozkładu Cauchy'ego do dystrybuanty empirycznej.<br />
<br />
===Rozwiązanie===<br />
<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
def rho_cauchy(x,loc,scale):<br />
return (np.pi*scale*(1.0+(x-loc)**2/(scale**2)))**(-1.0)<br />
<br />
def F_cauchy(x,loc,scale):<br />
return 0.5+np.arctan((x-loc)*1.0/scale)/np.pi<br />
<br />
<br />
#losujemy 10000 liczb z rozkladu Caychyego o loc=1.23 i scale=2.0<br />
x=2*np.random.standard_cauchy(10000)+1.23<br />
N=len(x)<br />
<br />
#METODA 1 - Dopasowanie metoda najmniejszych kwadratow do histogramu<br />
<br />
#tworzymy histogram<br />
hist,bins= np.histogram(x,bins=np.linspace(-20,20,61))<br />
#dlugosc przedzialu histogramowania<br />
przedzial=bins[1]-bins[0]<br />
#normalizujemy histogram aby moc go porownac z gestoscia<br />
hist=hist*1.0/len(x)/przedzial<br />
#liczymy wsp. srodkow przedzialow histogramowania<br />
xhist=bins[:-1]+0.5*przedzial<br />
#definiujemy sume kwadratow<br />
def squares((loc,scale)):<br />
return sum([(rho_cauchy(xhist[i],loc,scale)-hist[i])**2 for i in range(len(hist))])<br />
#szukamy minimum funkcja fmin<br />
fit1=tuple(so.fmin(squares,(0,1)))<br />
print 'wynik metody1 to '+str(fit1)<br />
#ogladamy wynik<br />
py.plot(xhist,hist)<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[rho_cauchy(a,*fit1) for a in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
<br />
<br />
#METODA 2 - Metoda najwiekszej wiarygodnosci<br />
<br />
#definiujemy -funkcje wiarygodnosci<br />
def L((loc,scale)):<br />
return -sum([np.log(rho_cauchy(a,loc,scale)) for a in x])<br />
#szukamy minimum<br />
fit2=tuple(so.fmin(L,(0,1)))<br />
print 'wynik metody2 to '+str(fit2)<br />
<br />
<br />
#METODA 3 - dopasowanie dystrybuant<br />
<br />
xx=sorted(x)<br />
yy=np.linspace(0,1,N)<br />
#definiujemy funkcje KS bedaca maksimum z roznicy miedzy dystrybuanta empiryczna a teoretyczna<br />
def KS((loc,scale)):<br />
return max([abs(F_cauchy(xx[i],loc,scale)-yy[i]) for i in xrange(N)])<br />
#szukamy minimum<br />
fit3=tuple(so.fmin(KS,(0,1)))<br />
print 'wynik metody3 to '+str(fit3)<br />
#ogladamy wynik<br />
cut=100<br />
py.plot(xx[cut:-cut],yy[cut:-cut])<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[F_cauchy(x,*fit3) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
===Zadanie - Data Container===<br />
Napisz klasę funkcja przyjmującą w konstruktorze parametry funkcji i posiadającąreprezentacje tekstową. Napisz dowolna funkcję dziedziczącą po klasie funkcja, której metoda call przyjmuje jeden argument i zwraca wartość funkcji dla podanego argumentu i parametrów podanych w konstruktorze. <br />
Napisz klasę DataContainer przyjmującą w konstruktorze dwie serie danych empirycznych o tej samej długości odpowiadające wspólrzędnym x i y. Obiekt klasy DataContainer powinien być wyposażony w metodę o nazwie fit, która przyjmuje jako argument obiekt klasy funkcja. Metoda fit powinna zwracać obiekt takiej samej klasy jak otrzymany w argumencie ale z parametrami dla których funkcja jest najlepiej (metodą najmniejszych kwadratów) dopasowana do przechowywanych w obiekcie danych.<br />
===Rozwiązanie=== <br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
import time<br />
import scipy.optimize as so<br />
<br />
class funkcja(object):<br />
def __init__(self,*args):<br />
self.args=args<br />
def __str__(self):<br />
return 'to jest funkcja o nazwie '+self.__class__.__name__+' i argumentach '+str(self.args)<br />
<br />
class liniowa(funkcja):<br />
def __call__(self,x):<br />
return self.args[0]*x*x+self.args[1]<br />
<br />
class DataContainer(object):<br />
def __init__(self,x,y):<br />
self.x=np.array(x)<br />
self.y=np.array(y)<br />
self.n=len(x)<br />
def __str__(self):<br />
return '''to jest Data Container z danymi:<br />
x[:10]:'''+str(self.x[:10])+'''<br />
y[:10]:'''+str(self.y[:10])<br />
<br />
def fit(self,funkcja):<br />
parametry_poczatkowe=funkcja.args<br />
def squares(parametry):<br />
funkcja.__init__(*tuple(parametry))<br />
return sum((map(funkcja,self.x)-self.y)**2)<br />
parametry_dopasowane=so.fmin(squares,parametry_poczatkowe)<br />
funkcja.__init__(*tuple(parametry_dopasowane))<br />
return funkcja<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
#generujemy wartosci funkcji z szumem<br />
f=liniowa(1.23,-0.73)<br />
ylist=[f(x)+0.05*np.random.randn() for x in xlist]<br />
<br />
d=DataContainer(xlist,ylist)<br />
f=liniowa(1,2)<br />
f=d.fit(f)<br />
py.plot(d.x,d.y)<br />
py.plot(d.x,map(f,d.x))<br />
py.show()<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Optymalizacja&diff=3633TI/Programowanie dla Fizyków Medycznych/Optymalizacja2015-06-09T14:11:02Z<p>Tgubiec: /* Optymalizacja wielowymiarowa */</p>
<hr />
<div>==Optymalizacja jednowymiarowa==<br />
Omawianie zagadnienia optymalizacji rozpocznijmy od prostego przykładu. Zdefiniujmy pewną funkcję i zobaczmy jak wygląda jej wykres.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
licznikTestowej=0<br />
<br />
def testowa(x):<br />
global licznikTestowej<br />
licznikTestowej+=1<br />
return 1/x+np.exp(x)<br />
<br />
xtest=np.arange(0.2,2,0.01)<br />
ytest=[testowa(x) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
[[Plik:opt1.png]]<br />
<br />
Na rozważanym przedziale [0.2,2] powyższa funkcja ma tylko jedno ekstremum lokalne. Taką funkcję nazywamy unimodalną. Zmienna licznikTestowej umożliwi nam zliczanie wywołań funkcji testowej przez analizowane procedury.<br />
Zagadnienie, którym teraz będziemy się zajmować to problem numerycznego znajdowania takiego ekstremum. Jak w każdym problemie numerycznym ekstremum szukać będziemy zakładając pewną dokładność otrzymanego wyniku, którą oznaczmy xtol. Na wstępie przyjmijmy, że poszukujemy ekstremum z dokładnością xtol=0.01. Najprostszą metodą będzie policzenie wartości funkcji dla wszystkich wartości x z podanego przedziału co xtol. Jest to metoda siłowa i wielokrotnie licząca wartość funkcji. Jej kod możemy znaleźć poniżej. <br />
<source lang="python"><br />
def bruteForce(func,xmin,xmax,args=(),xtol=0.01):<br />
xlist=np.arange(xmin,xmax,xtol)<br />
ylist=[func(x,*args) for x in xlist]<br />
return xlist[ylist.index(max(ylist))]<br />
</source><br />
Innym, znacznie efektywniejszym sposobem znajdowania minimum może być następująca procedura rekurencyjna:<br />
*podzielmy przedział [xmin,xmax] na 3 równe część: [xmin,xL],[xL,xR] oraz [xR,xmax]<br />
*jeżeli wartość funkcji w xL jest mniejsza od wartości funkcji w xR to powtórz procedurę dla przedziału [xmin,xR]. W przeciwnym przypadku powtórz procedurę dla przedziału [xL,xmax].<br />
*zakończ działanie gdy badany przedział jest krótszy niż xtol.<br />
Przykładowa implementacja tej metody wygląda następująco<br />
<source lang="python"><br />
def twoMidPointsR(func,xmin,xmax,args=(),xtol=0.01):<br />
if xmax-xmin<xtol: return 0.5*(xmax+xmin)<br />
xL=xmin+(xmax-xmin)/3.0<br />
xR=xmax-(xmax-xmin)/3.0<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
if fxL>fxR:<br />
return twoMidPointsR(func,xmin,xR,args,xtol)<br />
else:<br />
return twoMidPointsR(func,xL,xmax,args,xtol)<br />
</source><br />
Tą metodą możemy już szukać minimum z dowolną dokładnością, co nie będzie skutkowało znacznie większym czasem obliczeń. Zauważmy jednak, że w każdej iteracji wartość minimalizowanej funkcji liczona jest dwukrotnie. Jeżeli mamy do czynienia z funkcją, dla której obliczenie pojedynczej wartości jest bardzo czasochłonne to warto się zastanowić czy nie można tego wyniku poprawić. Zauważmy, że dla działania tej metody wcale nie jest konieczne dzielenie badanego odcinka dokładnie na trzy równe części. Można dokonać tego podziału w zupełnie innej proporcji. Warto tak dobrać punkty xL i xR, aby xR pokrywał się z xL (lub xL z xR) w kolejnym kroku iteracji. Jeżeli dodatkowo stworzymy zmienne przechowujące wcześniej liczone wartości funkcji to uda nam się ograniczyć liczbę wywołań funkcji o połowę. Opisana metoda to tak zwana '''metoda złotego podziału'''. Przykładowa implementacja wygląda następująco <br />
<br />
<source lang="python"><br />
def GoldenRatioRearch(func,xmin,xmax,args=(),xtol=0.01):<br />
golden=0.5*(np.sqrt(5.0)-1.0)<br />
xL=xmax-golden*(xmax-xmin)<br />
xR=xmin+golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
while xmax-xmin>xtol: <br />
if fxL<fxR:<br />
xmin=xmin<br />
xmax=xR<br />
xR=xL<br />
fxR=fxL<br />
xL=xmax-golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
else:<br />
xmin=xL<br />
xmax=xmax<br />
xL=xR<br />
fxL=fxR<br />
xR=xmin+golden*(xmax-xmin)<br />
fxR=func(xR,*args)<br />
return 0.5*(xmax+xmin)<br />
<br />
</source><br />
Napisanej metody optymalizacji możemy użyć w celu dopasowania funkcji do danych empirycznych metodą najmniejszych kwadratów. Przykład takiego zastosowania poniżej.<br />
<br />
<source lang="python"><br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a): return x*a<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print GoldenRatioRearch(squares,0,10,args=(liniowa,xlist,ylist),xtol=0.01)<br />
</source><br />
<br />
==Optymalizacja wielowymiarowa==<br />
Przejście od optymalizacji jedno- do wielowymiarowej fundamentalnie komplikuje problem. Pierwszym problemem jest istnienie tak zwanych punktów siodłowych. Nie istnieją zatem metody które zawsze znajdują szukane minimum, nawet jeżeli wiadomo że takie istnieje. Najpopularniejszą metodą jest downhill symplex lub inaczej metoda Neldera-Meada. Z powodu znacznego stopnia komplikacji nie będziemy jej samodzielnie implementować, a jedynie posłużymy się implementacją z biblioteki scipy.optimize. Dzięki niej możemy np. dopasowywać funkcję z więcej niż jednym parametrem do danych eksperymentalnych.<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a,b): return x*a+b<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23,-0.73)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print so.fmin(squares,(1,0),args=(kwadratowa,xlist,ylist))<br />
</source><br />
Oczywiście dopasowywanie możemy przeprowadzać nie tylko metodą najmniejszych kwadratów.<br />
===Zadanie - rozklad Cauchy'ego===<br />
Wylosuj 1000 liczb z rozkładu Cauchy'ego z parametrami loc=1.23 i scale=2.0. Do wylosowanych danych dopasuj rozkład Cauche'ego trzema metodami<br />
*METODA 1 - stwórz histogram otrzymanych wartości, znormalizuj go i metodą najmniejszych kwadratów dopsauj gęstość rozkladu do histogramu<br />
*METODA 2 - dopasuj gęstość rozkładu do wylosowanych danych metodą największej wiarygodności<br />
*METODA 3 - z wylosowanych danych stwórz dystrybuante empiryczną. Metodą najmniejszych kwadratów dopasuj dystrybuantę rozkładu cauchy'ego do dystrybuanty empirycznej.<br />
<br />
===Rozwiązanie===<br />
<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
def rho_cauchy(x,loc,scale):<br />
return (np.pi*scale*(1.0+(x-loc)**2/(scale**2)))**(-1.0)<br />
<br />
def F_cauchy(x,loc,scale):<br />
return 0.5+np.arctan((x-loc)*1.0/scale)/np.pi<br />
<br />
<br />
#losujemy 10000 liczb z rozkladu Caychyego o loc=1.23 i scale=2.0<br />
x=2*np.random.standard_cauchy(10000)+1.23<br />
N=len(x)<br />
<br />
#METODA 1 - Dopasowanie metoda najmniejszych kwadratow do histogramu<br />
<br />
#tworzymy histogram<br />
hist,bins= np.histogram(x,bins=np.linspace(-20,20,61))<br />
#dlugosc przedzialu histogramowania<br />
przedzial=bins[1]-bins[0]<br />
#normalizujemy histogram aby moc go porownac z gestoscia<br />
hist=hist*1.0/len(x)/przedzial<br />
#liczymy wsp. srodkow przedzialow histogramowania<br />
xhist=bins[:-1]+0.5*przedzial<br />
#definiujemy sume kwadratow<br />
def squares((loc,scale)):<br />
return sum([(rho_cauchy(xhist[i],loc,scale)-hist[i])**2 for i in range(len(hist))])<br />
#szukamy minimum funkcja fmin<br />
fit1=tuple(so.fmin(squares,(0,1)))<br />
print 'wynik metody1 to '+str(fit1)<br />
#ogladamy wynik<br />
py.plot(xhist,hist)<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[rho_cauchy(a,*fit1) for a in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
<br />
<br />
#METODA 2 - Metoda najwiekszej wiarygodnosci<br />
<br />
#definiujemy -funkcje wiarygodnosci<br />
def L((loc,scale)):<br />
return -sum([np.log(rho_cauchy(a,loc,scale)) for a in x])<br />
#szukamy minimum<br />
fit2=tuple(so.fmin(L,(0,1)))<br />
print 'wynik metody2 to '+str(fit2)<br />
<br />
<br />
#METODA 3 - dopasowanie dystrybuant<br />
<br />
xx=sorted(x)<br />
yy=np.linspace(0,1,N)<br />
#definiujemy funkcje KS bedaca maksimum z roznicy miedzy dystrybuanta empiryczna a teoretyczna<br />
def KS((loc,scale)):<br />
return max([abs(F_cauchy(xx[i],loc,scale)-yy[i]) for i in xrange(N)])<br />
#szukamy minimum<br />
fit3=tuple(so.fmin(KS,(0,1)))<br />
print 'wynik metody3 to '+str(fit3)<br />
#ogladamy wynik<br />
cut=100<br />
py.plot(xx[cut:-cut],yy[cut:-cut])<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[F_cauchy(x,*fit3) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
===Zadanie - Data Container===<br />
Napisz klasę funkcja przyjmującą w konstruktorze parametry funkcji i posiadającąreprezentacje tekstową. Napisz dowolna funkcję dziedziczącą po klasie funkcja, której metoda call przyjmuje jeden argument i zwraca wartość funkcji dla podanego argumentu i parametrów podanych w konstruktorze. <br />
Napisz klasę DataContainer przyjmującą w konstruktorze dwie serie danych empirycznych o tej samej długości odpowiadające wspólrzędnym x i y. Obiekt klasy DataContainer powinien być wyposażony w metodę o nazwie fit, która przyjmuje jako argument obiekt klasy funkcja. Metoda fit powinna zwracać obiekt takiej samej klasy jak otrzymany w argumencie ale z parametrami dla których funkcja jest najlepiej (metodą najmniejszych kwadratów) dopasowana do przechowywanych w obiekcie danych.<br />
===Rozwiązanie=== <br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
import time<br />
import scipy.optimize as so<br />
<br />
class funkcja(object):<br />
def __init__(self,*args):<br />
self.args=args<br />
def __str__(self):<br />
return 'to jest funkcja o nazwie '+self.__class__.__name__+' i argumentach '+str(self.args)<br />
<br />
class liniowa(funkcja):<br />
def __call__(self,x):<br />
return self.args[0]*x*x+self.args[1]<br />
<br />
class DataContainer(object):<br />
def __init__(self,x,y):<br />
self.x=np.array(x)<br />
self.y=np.array(y)<br />
self.n=len(x)<br />
def __str__(self):<br />
return '''to jest Data Container z danymi:<br />
x[:10]:'''+str(self.x[:10])+'''<br />
y[:10]:'''+str(self.y[:10])<br />
<br />
def fit(self,funkcja):<br />
parametry_poczatkowe=funkcja.args<br />
def squares(parametry):<br />
funkcja.__init__(*tuple(parametry))<br />
return sum((map(funkcja,self.x)-self.y)**2)<br />
parametry_dopasowane=so.fmin(squares,parametry_poczatkowe)<br />
funkcja.__init__(*tuple(parametry_dopasowane))<br />
return funkcja<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
#generujemy wartosci funkcji z szumem<br />
f=liniowa(1.23,-0.73)<br />
ylist=[f(x)+0.05*np.random.randn() for x in xlist]<br />
<br />
d=DataContainer(xlist,ylist)<br />
f=liniowa(1,2)<br />
f=d.fit(f)<br />
py.plot(d.x,d.y)<br />
py.plot(d.x,map(f,d.x))<br />
py.show()<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Optymalizacja&diff=3632TI/Programowanie dla Fizyków Medycznych/Optymalizacja2015-06-09T14:05:32Z<p>Tgubiec: /* Optymalizacja jednowymiarowa */</p>
<hr />
<div>==Optymalizacja jednowymiarowa==<br />
Omawianie zagadnienia optymalizacji rozpocznijmy od prostego przykładu. Zdefiniujmy pewną funkcję i zobaczmy jak wygląda jej wykres.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
licznikTestowej=0<br />
<br />
def testowa(x):<br />
global licznikTestowej<br />
licznikTestowej+=1<br />
return 1/x+np.exp(x)<br />
<br />
xtest=np.arange(0.2,2,0.01)<br />
ytest=[testowa(x) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
[[Plik:opt1.png]]<br />
<br />
Na rozważanym przedziale [0.2,2] powyższa funkcja ma tylko jedno ekstremum lokalne. Taką funkcję nazywamy unimodalną. Zmienna licznikTestowej umożliwi nam zliczanie wywołań funkcji testowej przez analizowane procedury.<br />
Zagadnienie, którym teraz będziemy się zajmować to problem numerycznego znajdowania takiego ekstremum. Jak w każdym problemie numerycznym ekstremum szukać będziemy zakładając pewną dokładność otrzymanego wyniku, którą oznaczmy xtol. Na wstępie przyjmijmy, że poszukujemy ekstremum z dokładnością xtol=0.01. Najprostszą metodą będzie policzenie wartości funkcji dla wszystkich wartości x z podanego przedziału co xtol. Jest to metoda siłowa i wielokrotnie licząca wartość funkcji. Jej kod możemy znaleźć poniżej. <br />
<source lang="python"><br />
def bruteForce(func,xmin,xmax,args=(),xtol=0.01):<br />
xlist=np.arange(xmin,xmax,xtol)<br />
ylist=[func(x,*args) for x in xlist]<br />
return xlist[ylist.index(max(ylist))]<br />
</source><br />
Innym, znacznie efektywniejszym sposobem znajdowania minimum może być następująca procedura rekurencyjna:<br />
*podzielmy przedział [xmin,xmax] na 3 równe część: [xmin,xL],[xL,xR] oraz [xR,xmax]<br />
*jeżeli wartość funkcji w xL jest mniejsza od wartości funkcji w xR to powtórz procedurę dla przedziału [xmin,xR]. W przeciwnym przypadku powtórz procedurę dla przedziału [xL,xmax].<br />
*zakończ działanie gdy badany przedział jest krótszy niż xtol.<br />
Przykładowa implementacja tej metody wygląda następująco<br />
<source lang="python"><br />
def twoMidPointsR(func,xmin,xmax,args=(),xtol=0.01):<br />
if xmax-xmin<xtol: return 0.5*(xmax+xmin)<br />
xL=xmin+(xmax-xmin)/3.0<br />
xR=xmax-(xmax-xmin)/3.0<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
if fxL>fxR:<br />
return twoMidPointsR(func,xmin,xR,args,xtol)<br />
else:<br />
return twoMidPointsR(func,xL,xmax,args,xtol)<br />
</source><br />
Tą metodą możemy już szukać minimum z dowolną dokładnością, co nie będzie skutkowało znacznie większym czasem obliczeń. Zauważmy jednak, że w każdej iteracji wartość minimalizowanej funkcji liczona jest dwukrotnie. Jeżeli mamy do czynienia z funkcją, dla której obliczenie pojedynczej wartości jest bardzo czasochłonne to warto się zastanowić czy nie można tego wyniku poprawić. Zauważmy, że dla działania tej metody wcale nie jest konieczne dzielenie badanego odcinka dokładnie na trzy równe części. Można dokonać tego podziału w zupełnie innej proporcji. Warto tak dobrać punkty xL i xR, aby xR pokrywał się z xL (lub xL z xR) w kolejnym kroku iteracji. Jeżeli dodatkowo stworzymy zmienne przechowujące wcześniej liczone wartości funkcji to uda nam się ograniczyć liczbę wywołań funkcji o połowę. Opisana metoda to tak zwana '''metoda złotego podziału'''. Przykładowa implementacja wygląda następująco <br />
<br />
<source lang="python"><br />
def GoldenRatioRearch(func,xmin,xmax,args=(),xtol=0.01):<br />
golden=0.5*(np.sqrt(5.0)-1.0)<br />
xL=xmax-golden*(xmax-xmin)<br />
xR=xmin+golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
while xmax-xmin>xtol: <br />
if fxL<fxR:<br />
xmin=xmin<br />
xmax=xR<br />
xR=xL<br />
fxR=fxL<br />
xL=xmax-golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
else:<br />
xmin=xL<br />
xmax=xmax<br />
xL=xR<br />
fxL=fxR<br />
xR=xmin+golden*(xmax-xmin)<br />
fxR=func(xR,*args)<br />
return 0.5*(xmax+xmin)<br />
<br />
</source><br />
Napisanej metody optymalizacji możemy użyć w celu dopasowania funkcji do danych empirycznych metodą najmniejszych kwadratów. Przykład takiego zastosowania poniżej.<br />
<br />
<source lang="python"><br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a): return x*a<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print GoldenRatioRearch(squares,0,10,args=(liniowa,xlist,ylist),xtol=0.01)<br />
</source><br />
<br />
==Optymalizacja wielowymiarowa==<br />
Przejście od optymalizacji jedno- do wielowymiarowej fundamentalnie komplikuje problem. Pierwszym problemem jest istnienie tak zwanych punktów siodłowych. Nie istnieją zatem metody które zawsze znajdują szukane minimum, nawet jezeli wiadomo że takie istnieje. Najpopularniejszą metodą jest downhill symplex lub inaczej metoda Neldera-Meada. Z powodu znaczengo stopnia komplikacji nie ędziemy jej samodzielnie implementować, a jedynie poslużymy sie implementacją z biblioteki scipy.optimize. Dziki niej możemy np. dopasowywać funkcję z więcej niż jednym parametrem do danych eksperymentalnych.<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a,b): return x*a+b<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23,-0.73)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print so.fmin(squares,(1,0),args=(kwadratowa,xlist,ylist))<br />
</source><br />
Oczywiście dopasowywanie możemy przeprowadzać nie tylko metodą najmniejszych kwadratów.<br />
===Zadanie - rozklad Cauchy'ego===<br />
Wylosuj 1000 liczb z rozkładu Cauchy'ego z parametrami loc=1.23 i scale=2.0. Do wylosowanych danych dopasuj rozkład Cauche'ego trzema metodami<br />
*METODA 1 - stwórz histogram otrzymanych wartości, znormalizuj go i metodą najmniejszych kwadratów dopsauj gęstość rozkladu do histogramu<br />
*METODA 2 - dopasuj gęstość rozkładu do wylosowanych danych metodą największej wiarygodności<br />
*METODA 3 - z wylosowanych danych stwórz dystrybuante empiryczną. Metodą najmniejszych kwadratów dopasuj dystrybuantę rozkładu cauchy'ego do dystrybuanty empirycznej.<br />
<br />
===Rozwiązanie===<br />
<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
def rho_cauchy(x,loc,scale):<br />
return (np.pi*scale*(1.0+(x-loc)**2/(scale**2)))**(-1.0)<br />
<br />
def F_cauchy(x,loc,scale):<br />
return 0.5+np.arctan((x-loc)*1.0/scale)/np.pi<br />
<br />
<br />
#losujemy 10000 liczb z rozkladu Caychyego o loc=1.23 i scale=2.0<br />
x=2*np.random.standard_cauchy(10000)+1.23<br />
N=len(x)<br />
<br />
#METODA 1 - Dopasowanie metoda najmniejszych kwadratow do histogramu<br />
<br />
#tworzymy histogram<br />
hist,bins= np.histogram(x,bins=np.linspace(-20,20,61))<br />
#dlugosc przedzialu histogramowania<br />
przedzial=bins[1]-bins[0]<br />
#normalizujemy histogram aby moc go porownac z gestoscia<br />
hist=hist*1.0/len(x)/przedzial<br />
#liczymy wsp. srodkow przedzialow histogramowania<br />
xhist=bins[:-1]+0.5*przedzial<br />
#definiujemy sume kwadratow<br />
def squares((loc,scale)):<br />
return sum([(rho_cauchy(xhist[i],loc,scale)-hist[i])**2 for i in range(len(hist))])<br />
#szukamy minimum funkcja fmin<br />
fit1=tuple(so.fmin(squares,(0,1)))<br />
print 'wynik metody1 to '+str(fit1)<br />
#ogladamy wynik<br />
py.plot(xhist,hist)<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[rho_cauchy(a,*fit1) for a in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
<br />
<br />
#METODA 2 - Metoda najwiekszej wiarygodnosci<br />
<br />
#definiujemy -funkcje wiarygodnosci<br />
def L((loc,scale)):<br />
return -sum([np.log(rho_cauchy(a,loc,scale)) for a in x])<br />
#szukamy minimum<br />
fit2=tuple(so.fmin(L,(0,1)))<br />
print 'wynik metody2 to '+str(fit2)<br />
<br />
<br />
#METODA 3 - dopasowanie dystrybuant<br />
<br />
xx=sorted(x)<br />
yy=np.linspace(0,1,N)<br />
#definiujemy funkcje KS bedaca maksimum z roznicy miedzy dystrybuanta empiryczna a teoretyczna<br />
def KS((loc,scale)):<br />
return max([abs(F_cauchy(xx[i],loc,scale)-yy[i]) for i in xrange(N)])<br />
#szukamy minimum<br />
fit3=tuple(so.fmin(KS,(0,1)))<br />
print 'wynik metody3 to '+str(fit3)<br />
#ogladamy wynik<br />
cut=100<br />
py.plot(xx[cut:-cut],yy[cut:-cut])<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[F_cauchy(x,*fit3) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
===Zadanie - Data Container===<br />
Napisz klasę funkcja przyjmującą w konstruktorze parametry funkcji i posiadającąreprezentacje tekstową. Napisz dowolna funkcję dziedziczącą po klasie funkcja, której metoda call przyjmuje jeden argument i zwraca wartość funkcji dla podanego argumentu i parametrów podanych w konstruktorze. <br />
Napisz klasę DataContainer przyjmującą w konstruktorze dwie serie danych empirycznych o tej samej długości odpowiadające wspólrzędnym x i y. Obiekt klasy DataContainer powinien być wyposażony w metodę o nazwie fit, która przyjmuje jako argument obiekt klasy funkcja. Metoda fit powinna zwracać obiekt takiej samej klasy jak otrzymany w argumencie ale z parametrami dla których funkcja jest najlepiej (metodą najmniejszych kwadratów) dopasowana do przechowywanych w obiekcie danych.<br />
===Rozwiązanie=== <br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
import time<br />
import scipy.optimize as so<br />
<br />
class funkcja(object):<br />
def __init__(self,*args):<br />
self.args=args<br />
def __str__(self):<br />
return 'to jest funkcja o nazwie '+self.__class__.__name__+' i argumentach '+str(self.args)<br />
<br />
class liniowa(funkcja):<br />
def __call__(self,x):<br />
return self.args[0]*x*x+self.args[1]<br />
<br />
class DataContainer(object):<br />
def __init__(self,x,y):<br />
self.x=np.array(x)<br />
self.y=np.array(y)<br />
self.n=len(x)<br />
def __str__(self):<br />
return '''to jest Data Container z danymi:<br />
x[:10]:'''+str(self.x[:10])+'''<br />
y[:10]:'''+str(self.y[:10])<br />
<br />
def fit(self,funkcja):<br />
parametry_poczatkowe=funkcja.args<br />
def squares(parametry):<br />
funkcja.__init__(*tuple(parametry))<br />
return sum((map(funkcja,self.x)-self.y)**2)<br />
parametry_dopasowane=so.fmin(squares,parametry_poczatkowe)<br />
funkcja.__init__(*tuple(parametry_dopasowane))<br />
return funkcja<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
#generujemy wartosci funkcji z szumem<br />
f=liniowa(1.23,-0.73)<br />
ylist=[f(x)+0.05*np.random.randn() for x in xlist]<br />
<br />
d=DataContainer(xlist,ylist)<br />
f=liniowa(1,2)<br />
f=d.fit(f)<br />
py.plot(d.x,d.y)<br />
py.plot(d.x,map(f,d.x))<br />
py.show()<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Optymalizacja&diff=3631TI/Programowanie dla Fizyków Medycznych/Optymalizacja2015-06-09T14:02:57Z<p>Tgubiec: /* Optymalizacja jednowymiarowa */</p>
<hr />
<div>==Optymalizacja jednowymiarowa==<br />
Omawianie zagadnienia optymalizacji rozpocznijmy od prostego przykładu. Zdefiniujmy pewną funkcję i zobaczmy jak wygląda jej wykres.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
licznikTestowej=0<br />
<br />
def testowa(x):<br />
global licznikTestowej<br />
licznikTestowej+=1<br />
return 1/x+np.exp(x)<br />
<br />
xtest=np.arange(0.2,2,0.01)<br />
ytest=[testowa(x) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
[[Plik:opt1.png]]<br />
<br />
Na rozważanym przedziale [0.2,2] powyższa funkcja ma tylko jedno ekstremum lokalne. Taką funkcję nazywamy unimodalną. Zmienna licznikTestowej umożliwi nam zliczanie wywołań funkcji testowej przez analizowane procedury.<br />
Zagadnienie, którym teraz będziemy się zajmować to problem numerycznego znajdowania takiego ekstremum. Jak w każdym problemie numerycznym ekstremum szukać będziemy zakładając pewną dokładność otrzymanego wyniku, którą oznaczmy xtol. Na wstępie przyjmijmy, że poszukujemy ekstremum z dokładnością xtol=0.01. Najprostszą metodą będzie policzenie wartości funkcji dla wszystkich wartości x z podanego przedziału co xtol. Jest to metoda siłowa i wielokrotnie licząca wartość funkcji. Jej kod możemy znaleźć poniżej. <br />
<source lang="python"><br />
def bruteForce(func,xmin,xmax,args=(),xtol=0.01):<br />
xlist=np.arange(xmin,xmax,xtol)<br />
ylist=[func(x,*args) for x in xlist]<br />
return xlist[ylist.index(max(ylist))]<br />
</source><br />
Innym, znacznie efektywniejszym sposobem znajdowania minimum może być następująca procedura rekurencyjna:<br />
*podzielmy przedział [xmin,xmax] na 3 równe część: [xmin,xL],[xL,xR] oraz [xR,xmax]<br />
*jeżeli wartość funkcji w xL jest mniejsza od wartości funkcji w xR to powtórz procedurę dla przedziału [xmin,xR]. W przeciwnym przypadku powtórz procedurę dla przedziału [xL,xmax].<br />
*zakończ działanie gdy badany przedział jest krótszy niż xtol<br />
Przykładowa implementacja tej metody wygląda następująco<br />
<source lang="python"><br />
def twoMidPointsR(func,xmin,xmax,args=(),xtol=0.01):<br />
if xmax-xmin<xtol: return 0.5*(xmax+xmin)<br />
xL=xmin+(xmax-xmin)/3.0<br />
xR=xmax-(xmax-xmin)/3.0<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
if fxL>fxR:<br />
return twoMidPointsR(func,xmin,xR,args,xtol)<br />
else:<br />
return twoMidPointsR(func,xL,xmax,args,xtol)<br />
</source><br />
Tą metodą możemy już szukać minimum z dowolną dokładnością, co nie będzie skutkowało znacznie większym czasem obliczeń. Zauważmy jednak, że w każdej iteracji wartość minimalizowanej funkcji liczona jest dwukrotnie. Jeżeli mamy do czynienia z funkcją, dla której obliczenie pojedynczej wartości jest bardzo czasochłonne to warto się zastanowić czy nie można tego wyniku poprawić. Zauważmy, że dla działania tej metody wcale nie jest konieczne dzielenie badanego odcinka dokładnie na trzy równe części. Można dokonać tego podziału w zupełnie innej proporcji. Warto tak dobrać punkty xL i xR, aby xR pokrywał się z xL (lub xL z xR) w kolejnym kroku iteracji. Jeżeli dodatkowo stworzymy zmienne przechowujące wcześniej liczone wartości funkcji to uda nam się ograniczyć liczbę wywołań funkcji o połowę. Opisana metoda to tak zwana '''metoda złotego podziału'''. Przykładowa implementacja wygląda następująco <br />
<br />
<source lang="python"><br />
def GoldenRatioRearch(func,xmin,xmax,args=(),xtol=0.01):<br />
golden=0.5*(np.sqrt(5.0)-1.0)<br />
xL=xmax-golden*(xmax-xmin)<br />
xR=xmin+golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
while xmax-xmin>xtol: <br />
if fxL<fxR:<br />
xmin=xmin<br />
xmax=xR<br />
xR=xL<br />
fxR=fxL<br />
xL=xmax-golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
else:<br />
xmin=xL<br />
xmax=xmax<br />
xL=xR<br />
fxL=fxR<br />
xR=xmin+golden*(xmax-xmin)<br />
fxR=func(xR,*args)<br />
return 0.5*(xmax+xmin)<br />
<br />
</source><br />
Napisanej metody optymalizacji możemy użyć w celu dopasowania funkcji do danych empirycznych metodą najmniejszych kwadratów. Przykład takiego zastosowania poniżej.<br />
<br />
<source lang="python"><br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a): return x*a<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print GoldenRatioRearch(squares,0,10,args=(liniowa,xlist,ylist),xtol=0.01)<br />
</source><br />
<br />
==Optymalizacja wielowymiarowa==<br />
Przejście od optymalizacji jedno- do wielowymiarowej fundamentalnie komplikuje problem. Pierwszym problemem jest istnienie tak zwanych punktów siodłowych. Nie istnieją zatem metody które zawsze znajdują szukane minimum, nawet jezeli wiadomo że takie istnieje. Najpopularniejszą metodą jest downhill symplex lub inaczej metoda Neldera-Meada. Z powodu znaczengo stopnia komplikacji nie ędziemy jej samodzielnie implementować, a jedynie poslużymy sie implementacją z biblioteki scipy.optimize. Dziki niej możemy np. dopasowywać funkcję z więcej niż jednym parametrem do danych eksperymentalnych.<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a,b): return x*a+b<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23,-0.73)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print so.fmin(squares,(1,0),args=(kwadratowa,xlist,ylist))<br />
</source><br />
Oczywiście dopasowywanie możemy przeprowadzać nie tylko metodą najmniejszych kwadratów.<br />
===Zadanie - rozklad Cauchy'ego===<br />
Wylosuj 1000 liczb z rozkładu Cauchy'ego z parametrami loc=1.23 i scale=2.0. Do wylosowanych danych dopasuj rozkład Cauche'ego trzema metodami<br />
*METODA 1 - stwórz histogram otrzymanych wartości, znormalizuj go i metodą najmniejszych kwadratów dopsauj gęstość rozkladu do histogramu<br />
*METODA 2 - dopasuj gęstość rozkładu do wylosowanych danych metodą największej wiarygodności<br />
*METODA 3 - z wylosowanych danych stwórz dystrybuante empiryczną. Metodą najmniejszych kwadratów dopasuj dystrybuantę rozkładu cauchy'ego do dystrybuanty empirycznej.<br />
<br />
===Rozwiązanie===<br />
<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
def rho_cauchy(x,loc,scale):<br />
return (np.pi*scale*(1.0+(x-loc)**2/(scale**2)))**(-1.0)<br />
<br />
def F_cauchy(x,loc,scale):<br />
return 0.5+np.arctan((x-loc)*1.0/scale)/np.pi<br />
<br />
<br />
#losujemy 10000 liczb z rozkladu Caychyego o loc=1.23 i scale=2.0<br />
x=2*np.random.standard_cauchy(10000)+1.23<br />
N=len(x)<br />
<br />
#METODA 1 - Dopasowanie metoda najmniejszych kwadratow do histogramu<br />
<br />
#tworzymy histogram<br />
hist,bins= np.histogram(x,bins=np.linspace(-20,20,61))<br />
#dlugosc przedzialu histogramowania<br />
przedzial=bins[1]-bins[0]<br />
#normalizujemy histogram aby moc go porownac z gestoscia<br />
hist=hist*1.0/len(x)/przedzial<br />
#liczymy wsp. srodkow przedzialow histogramowania<br />
xhist=bins[:-1]+0.5*przedzial<br />
#definiujemy sume kwadratow<br />
def squares((loc,scale)):<br />
return sum([(rho_cauchy(xhist[i],loc,scale)-hist[i])**2 for i in range(len(hist))])<br />
#szukamy minimum funkcja fmin<br />
fit1=tuple(so.fmin(squares,(0,1)))<br />
print 'wynik metody1 to '+str(fit1)<br />
#ogladamy wynik<br />
py.plot(xhist,hist)<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[rho_cauchy(a,*fit1) for a in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
<br />
<br />
#METODA 2 - Metoda najwiekszej wiarygodnosci<br />
<br />
#definiujemy -funkcje wiarygodnosci<br />
def L((loc,scale)):<br />
return -sum([np.log(rho_cauchy(a,loc,scale)) for a in x])<br />
#szukamy minimum<br />
fit2=tuple(so.fmin(L,(0,1)))<br />
print 'wynik metody2 to '+str(fit2)<br />
<br />
<br />
#METODA 3 - dopasowanie dystrybuant<br />
<br />
xx=sorted(x)<br />
yy=np.linspace(0,1,N)<br />
#definiujemy funkcje KS bedaca maksimum z roznicy miedzy dystrybuanta empiryczna a teoretyczna<br />
def KS((loc,scale)):<br />
return max([abs(F_cauchy(xx[i],loc,scale)-yy[i]) for i in xrange(N)])<br />
#szukamy minimum<br />
fit3=tuple(so.fmin(KS,(0,1)))<br />
print 'wynik metody3 to '+str(fit3)<br />
#ogladamy wynik<br />
cut=100<br />
py.plot(xx[cut:-cut],yy[cut:-cut])<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[F_cauchy(x,*fit3) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
===Zadanie - Data Container===<br />
Napisz klasę funkcja przyjmującą w konstruktorze parametry funkcji i posiadającąreprezentacje tekstową. Napisz dowolna funkcję dziedziczącą po klasie funkcja, której metoda call przyjmuje jeden argument i zwraca wartość funkcji dla podanego argumentu i parametrów podanych w konstruktorze. <br />
Napisz klasę DataContainer przyjmującą w konstruktorze dwie serie danych empirycznych o tej samej długości odpowiadające wspólrzędnym x i y. Obiekt klasy DataContainer powinien być wyposażony w metodę o nazwie fit, która przyjmuje jako argument obiekt klasy funkcja. Metoda fit powinna zwracać obiekt takiej samej klasy jak otrzymany w argumencie ale z parametrami dla których funkcja jest najlepiej (metodą najmniejszych kwadratów) dopasowana do przechowywanych w obiekcie danych.<br />
===Rozwiązanie=== <br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
import time<br />
import scipy.optimize as so<br />
<br />
class funkcja(object):<br />
def __init__(self,*args):<br />
self.args=args<br />
def __str__(self):<br />
return 'to jest funkcja o nazwie '+self.__class__.__name__+' i argumentach '+str(self.args)<br />
<br />
class liniowa(funkcja):<br />
def __call__(self,x):<br />
return self.args[0]*x*x+self.args[1]<br />
<br />
class DataContainer(object):<br />
def __init__(self,x,y):<br />
self.x=np.array(x)<br />
self.y=np.array(y)<br />
self.n=len(x)<br />
def __str__(self):<br />
return '''to jest Data Container z danymi:<br />
x[:10]:'''+str(self.x[:10])+'''<br />
y[:10]:'''+str(self.y[:10])<br />
<br />
def fit(self,funkcja):<br />
parametry_poczatkowe=funkcja.args<br />
def squares(parametry):<br />
funkcja.__init__(*tuple(parametry))<br />
return sum((map(funkcja,self.x)-self.y)**2)<br />
parametry_dopasowane=so.fmin(squares,parametry_poczatkowe)<br />
funkcja.__init__(*tuple(parametry_dopasowane))<br />
return funkcja<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
#generujemy wartosci funkcji z szumem<br />
f=liniowa(1.23,-0.73)<br />
ylist=[f(x)+0.05*np.random.randn() for x in xlist]<br />
<br />
d=DataContainer(xlist,ylist)<br />
f=liniowa(1,2)<br />
f=d.fit(f)<br />
py.plot(d.x,d.y)<br />
py.plot(d.x,map(f,d.x))<br />
py.show()<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Optymalizacja&diff=3630TI/Programowanie dla Fizyków Medycznych/Optymalizacja2015-06-09T14:01:17Z<p>Tgubiec: /* Optymalizacja jednowymiarowa */</p>
<hr />
<div>==Optymalizacja jednowymiarowa==<br />
Omawianie zagadnienia optymalizacji rozpocznijmy od prostego przykładu. Zdefiniujmy pewną funkcję i zobaczmy jak wygląda jej wykres.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
licznikTestowej=0<br />
<br />
def testowa(x):<br />
global licznikTestowej<br />
licznikTestowej+=1<br />
return 1/x+np.exp(x)<br />
<br />
xtest=np.arange(0.2,2,0.01)<br />
ytest=[testowa(x) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
[[Plik:opt1.png]]<br />
<br />
Na rozważanym przedziale [0.2,2] powyższa funkcja ma tylko jedno ekstremum lokalne. Taką funkcję nazywamy unimodalną. Zmienna licznikTestowej umożliwi nam zliczanie wywołań funkcji testowej przez analizowane procedury.<br />
Zagadnienie, którym teraz będziemy się zajmować to problem numerycznego znajdowania takiego ekstremum. Jak każdy problem numeryczny ekstremum szukać będziemy zakładając pewną dokładność otrzymanego wyniku, którą oznaczmy xtol. Na wstępie przyjmijmy, że poszukujemy ekstremum z dokładnością xtol=0.01. Najprostszą metodą będzie policzenie wartości funkcji dla wszystkich wartości x z podanego przedziału co xtol. Jest to metoda siłowa i wielokrotnie licząca wartość funkcji. Jej kod możemy znaleźć poniżej. <br />
<source lang="python"><br />
def bruteForce(func,xmin,xmax,args=(),xtol=0.01):<br />
xlist=np.arange(xmin,xmax,xtol)<br />
ylist=[func(x,*args) for x in xlist]<br />
return xlist[ylist.index(max(ylist))]<br />
</source><br />
Innym, znacznie efektywniejszym sposobem znajdowania minimum może być następująca procedura rekurencyjna:<br />
*podzielmy przedział [xmin,xmax] na 3 równe część: [xmin,xL],[xL,xR] oraz [xR,xmax]<br />
*jeżeli wartość funkcji w xL jest mniejsza od wartości funkcji w xR to powtórz procedurę dla przedziału [xmin,xR]. W przeciwnym przypadku powtórz procedurę dla przedziału [xL,xmax].<br />
*zakończ działanie gdy badany przedział jest krótszy niż xtol<br />
Przykładowa implementacja tej metody wygląda następująco<br />
<source lang="python"><br />
def twoMidPointsR(func,xmin,xmax,args=(),xtol=0.01):<br />
if xmax-xmin<xtol: return 0.5*(xmax+xmin)<br />
xL=xmin+(xmax-xmin)/3.0<br />
xR=xmax-(xmax-xmin)/3.0<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
if fxL>fxR:<br />
return twoMidPointsR(func,xmin,xR,args,xtol)<br />
else:<br />
return twoMidPointsR(func,xL,xmax,args,xtol)<br />
</source><br />
Tą metodą możemy już szukać minimum z dowolną dokładnością, co nie będzie skutkowało znacznie większym czasem obliczeń. Zauważmy jednak, że w każdej iteracji wartość minimalizowanej funkcji liczona jest dwukrotnie. Jeżeli mamy do czynienia z funkcją, dla której obliczenie pojedynczej wartości jest bardzo czasochłonne to warto się zastanowić czy nie można tego wyniku poprawić. Zauważmy, że dla działania tej metody wcale nie jest konieczne dzielenie badanego odcinka dokładnie na trzy równe części. Można dokonać tego podziału w zupełnie innej proporcji. Warto tak dobrać punkty xL i xR, aby xR pokrywał się z xL (lub xL z xR) w kolejnym kroku iteracji. Jeżeli dodatkowo stworzymy zmienne przechowujące wcześniej liczone wartości funkcji to uda nam się ograniczyć liczbę wywołań funkcji o połowę. Opisana metoda to tak zwana '''metoda złotego podziału'''. Przykładowa implementacja wygląda następująco <br />
<br />
<source lang="python"><br />
def GoldenRatioRearch(func,xmin,xmax,args=(),xtol=0.01):<br />
golden=0.5*(np.sqrt(5.0)-1.0)<br />
xL=xmax-golden*(xmax-xmin)<br />
xR=xmin+golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
while xmax-xmin>xtol: <br />
if fxL<fxR:<br />
xmin=xmin<br />
xmax=xR<br />
xR=xL<br />
fxR=fxL<br />
xL=xmax-golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
else:<br />
xmin=xL<br />
xmax=xmax<br />
xL=xR<br />
fxL=fxR<br />
xR=xmin+golden*(xmax-xmin)<br />
fxR=func(xR,*args)<br />
return 0.5*(xmax+xmin)<br />
<br />
</source><br />
Napisanej metody optymalizacji możemy użyć w celu dopasowania funkcji do danych empirycznych metodą najmniejszych kwadratów. Przykład takiego zastosowania poniżej.<br />
<br />
<source lang="python"><br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a): return x*a<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print GoldenRatioRearch(squares,0,10,args=(liniowa,xlist,ylist),xtol=0.01)<br />
</source><br />
<br />
==Optymalizacja wielowymiarowa==<br />
Przejście od optymalizacji jedno- do wielowymiarowej fundamentalnie komplikuje problem. Pierwszym problemem jest istnienie tak zwanych punktów siodłowych. Nie istnieją zatem metody które zawsze znajdują szukane minimum, nawet jezeli wiadomo że takie istnieje. Najpopularniejszą metodą jest downhill symplex lub inaczej metoda Neldera-Meada. Z powodu znaczengo stopnia komplikacji nie ędziemy jej samodzielnie implementować, a jedynie poslużymy sie implementacją z biblioteki scipy.optimize. Dziki niej możemy np. dopasowywać funkcję z więcej niż jednym parametrem do danych eksperymentalnych.<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a,b): return x*a+b<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23,-0.73)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print so.fmin(squares,(1,0),args=(kwadratowa,xlist,ylist))<br />
</source><br />
Oczywiście dopasowywanie możemy przeprowadzać nie tylko metodą najmniejszych kwadratów.<br />
===Zadanie - rozklad Cauchy'ego===<br />
Wylosuj 1000 liczb z rozkładu Cauchy'ego z parametrami loc=1.23 i scale=2.0. Do wylosowanych danych dopasuj rozkład Cauche'ego trzema metodami<br />
*METODA 1 - stwórz histogram otrzymanych wartości, znormalizuj go i metodą najmniejszych kwadratów dopsauj gęstość rozkladu do histogramu<br />
*METODA 2 - dopasuj gęstość rozkładu do wylosowanych danych metodą największej wiarygodności<br />
*METODA 3 - z wylosowanych danych stwórz dystrybuante empiryczną. Metodą najmniejszych kwadratów dopasuj dystrybuantę rozkładu cauchy'ego do dystrybuanty empirycznej.<br />
<br />
===Rozwiązanie===<br />
<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
def rho_cauchy(x,loc,scale):<br />
return (np.pi*scale*(1.0+(x-loc)**2/(scale**2)))**(-1.0)<br />
<br />
def F_cauchy(x,loc,scale):<br />
return 0.5+np.arctan((x-loc)*1.0/scale)/np.pi<br />
<br />
<br />
#losujemy 10000 liczb z rozkladu Caychyego o loc=1.23 i scale=2.0<br />
x=2*np.random.standard_cauchy(10000)+1.23<br />
N=len(x)<br />
<br />
#METODA 1 - Dopasowanie metoda najmniejszych kwadratow do histogramu<br />
<br />
#tworzymy histogram<br />
hist,bins= np.histogram(x,bins=np.linspace(-20,20,61))<br />
#dlugosc przedzialu histogramowania<br />
przedzial=bins[1]-bins[0]<br />
#normalizujemy histogram aby moc go porownac z gestoscia<br />
hist=hist*1.0/len(x)/przedzial<br />
#liczymy wsp. srodkow przedzialow histogramowania<br />
xhist=bins[:-1]+0.5*przedzial<br />
#definiujemy sume kwadratow<br />
def squares((loc,scale)):<br />
return sum([(rho_cauchy(xhist[i],loc,scale)-hist[i])**2 for i in range(len(hist))])<br />
#szukamy minimum funkcja fmin<br />
fit1=tuple(so.fmin(squares,(0,1)))<br />
print 'wynik metody1 to '+str(fit1)<br />
#ogladamy wynik<br />
py.plot(xhist,hist)<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[rho_cauchy(a,*fit1) for a in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
<br />
<br />
#METODA 2 - Metoda najwiekszej wiarygodnosci<br />
<br />
#definiujemy -funkcje wiarygodnosci<br />
def L((loc,scale)):<br />
return -sum([np.log(rho_cauchy(a,loc,scale)) for a in x])<br />
#szukamy minimum<br />
fit2=tuple(so.fmin(L,(0,1)))<br />
print 'wynik metody2 to '+str(fit2)<br />
<br />
<br />
#METODA 3 - dopasowanie dystrybuant<br />
<br />
xx=sorted(x)<br />
yy=np.linspace(0,1,N)<br />
#definiujemy funkcje KS bedaca maksimum z roznicy miedzy dystrybuanta empiryczna a teoretyczna<br />
def KS((loc,scale)):<br />
return max([abs(F_cauchy(xx[i],loc,scale)-yy[i]) for i in xrange(N)])<br />
#szukamy minimum<br />
fit3=tuple(so.fmin(KS,(0,1)))<br />
print 'wynik metody3 to '+str(fit3)<br />
#ogladamy wynik<br />
cut=100<br />
py.plot(xx[cut:-cut],yy[cut:-cut])<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[F_cauchy(x,*fit3) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
===Zadanie - Data Container===<br />
Napisz klasę funkcja przyjmującą w konstruktorze parametry funkcji i posiadającąreprezentacje tekstową. Napisz dowolna funkcję dziedziczącą po klasie funkcja, której metoda call przyjmuje jeden argument i zwraca wartość funkcji dla podanego argumentu i parametrów podanych w konstruktorze. <br />
Napisz klasę DataContainer przyjmującą w konstruktorze dwie serie danych empirycznych o tej samej długości odpowiadające wspólrzędnym x i y. Obiekt klasy DataContainer powinien być wyposażony w metodę o nazwie fit, która przyjmuje jako argument obiekt klasy funkcja. Metoda fit powinna zwracać obiekt takiej samej klasy jak otrzymany w argumencie ale z parametrami dla których funkcja jest najlepiej (metodą najmniejszych kwadratów) dopasowana do przechowywanych w obiekcie danych.<br />
===Rozwiązanie=== <br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
import time<br />
import scipy.optimize as so<br />
<br />
class funkcja(object):<br />
def __init__(self,*args):<br />
self.args=args<br />
def __str__(self):<br />
return 'to jest funkcja o nazwie '+self.__class__.__name__+' i argumentach '+str(self.args)<br />
<br />
class liniowa(funkcja):<br />
def __call__(self,x):<br />
return self.args[0]*x*x+self.args[1]<br />
<br />
class DataContainer(object):<br />
def __init__(self,x,y):<br />
self.x=np.array(x)<br />
self.y=np.array(y)<br />
self.n=len(x)<br />
def __str__(self):<br />
return '''to jest Data Container z danymi:<br />
x[:10]:'''+str(self.x[:10])+'''<br />
y[:10]:'''+str(self.y[:10])<br />
<br />
def fit(self,funkcja):<br />
parametry_poczatkowe=funkcja.args<br />
def squares(parametry):<br />
funkcja.__init__(*tuple(parametry))<br />
return sum((map(funkcja,self.x)-self.y)**2)<br />
parametry_dopasowane=so.fmin(squares,parametry_poczatkowe)<br />
funkcja.__init__(*tuple(parametry_dopasowane))<br />
return funkcja<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
#generujemy wartosci funkcji z szumem<br />
f=liniowa(1.23,-0.73)<br />
ylist=[f(x)+0.05*np.random.randn() for x in xlist]<br />
<br />
d=DataContainer(xlist,ylist)<br />
f=liniowa(1,2)<br />
f=d.fit(f)<br />
py.plot(d.x,d.y)<br />
py.plot(d.x,map(f,d.x))<br />
py.show()<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Optymalizacja&diff=3629TI/Programowanie dla Fizyków Medycznych/Optymalizacja2015-06-09T13:57:12Z<p>Tgubiec: /* Optymalizacja jednowymiarowa */</p>
<hr />
<div>==Optymalizacja jednowymiarowa==<br />
Omawianie zagadnienia optymalizacji rozpocznijmy od prostego przykładu. Zdefiniujmy pewną funkcję i zobaczmy jak wygląda jej wykres.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
licznikTestowej=0<br />
<br />
def testowa(x):<br />
global licznikTestowej<br />
licznikTestowej+=1<br />
return 1/x+np.exp(x)<br />
<br />
xtest=np.arange(0.2,2,0.01)<br />
ytest=[testowa(x) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
[[Plik:opt1.png]]<br />
<br />
Na rozważanym przedziale [0.2,2] powyższa funkcja ma tylko jedno ekstremum lokalne. Taką funkcję nazywamy unimodalną. Zmienna licznikTestowej umożliwi nam zliczanie wywołań funkcji testowej przez analizowane procedury.<br />
Zagadnienie, którym teraz będziemy się zajmować to problem numerycznego znajdowania takiego ekstremum. Jak każdy problem numeryczny ekstremum szukać będziemy zakładając pewną dokładność otrzymanego wyniku, którą oznaczmy xtol. Na wstępie przyjmijmy, że poszukujemy ekstremum z dokładnością xtol=0.01. Najprostszą metodą będzie policzenie wartości funkcji dla wszystkich wartości x z podanego przedziału co xtol. Jest to metoda siłowa i wielokrotnie licząca wartość funkcji. Jej kod możemy znaleźć poniżej. <br />
<source lang="python"><br />
def bruteForce(func,xmin,xmax,args=(),xtol=0.01):<br />
xlist=np.arange(xmin,xmax,xtol)<br />
ylist=[func(x,*args) for x in xlist]<br />
return xlist[ylist.index(max(ylist))]<br />
</source><br />
Innym, znacznie efektywniejszym sposobem znajdowania minimum możebyć nastepująca procedura rekurencyjna:<br />
*podzielmy przedzial [xmin,xmax] na 3 równe część: [xmin,xL],[xL,xR] oraz [xR,xmax]<br />
*jeżeli wartość funkcji w xL jest mniejsza od wartości funkcji w xR to powtórz procedurę dla przedziału [xmin,xR]. W przeciwnym przypadku powtórz proceduę dla przedziału [xL,xmax].<br />
*zakończ działanie gdy badany przedział jest krótszy nić xtol<br />
Przykładowa imlementacja tej metody wygląda nastepująco<br />
<source lang="python"><br />
def twoMidPointsR(func,xmin,xmax,args=(),xtol=0.01):<br />
if xmax-xmin<xtol: return 0.5*(xmax+xmin)<br />
xL=xmin+(xmax-xmin)/3.0<br />
xR=xmax-(xmax-xmin)/3.0<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
if fxL>fxR:<br />
return twoMidPointsR(func,xmin,xR,args,xtol)<br />
else:<br />
return twoMidPointsR(func,xL,xmax,args,xtol)<br />
</source><br />
Tą metodą możemy już szukać minimum ze dowolną dokładnością, co nie będzie skutowało znacznie większym czasem obliczeń. Zauważmy jednak że w każdej iteracji wartość minimalizowanej funkcji liczona jest dwukrotnie. Jeżeli mamy doczynienia z funkcją dla której obliczenie pojedynczej wartości jest bardzo czasochłonne to warto się zastanowić czy nie można tego wyniku poprawić. Zauważmy że dla działania tej metody wcale nie jest konieczne dzielenia badanego odcinka dokładnie na trzy równe części. Można dokonać tego podziału w zupełnie innej proporcji. Warto tak dobrać punkty xL i xR aby xR pokrywał się z xL (lub xL z xR) w kolejnym kroku iteracji. Jeżeli dodatkowo stworzymy zmienne przechowujące wcześniej liczone wartości funkcji to uda nam sie ograniczyć liczbe wywołań funkcji o połowę. Opisana metoda to tak zwana '''metoda złotego podziału'''. Przykladowa implementacja wygląda nastepująco <br />
<br />
<source lang="python"><br />
def GoldenRatioRearch(func,xmin,xmax,args=(),xtol=0.01):<br />
golden=0.5*(np.sqrt(5.0)-1.0)<br />
xL=xmax-golden*(xmax-xmin)<br />
xR=xmin+golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
while xmax-xmin>xtol: <br />
if fxL<fxR:<br />
xmin=xmin<br />
xmax=xR<br />
xR=xL<br />
fxR=fxL<br />
xL=xmax-golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
else:<br />
xmin=xL<br />
xmax=xmax<br />
xL=xR<br />
fxL=fxR<br />
xR=xmin+golden*(xmax-xmin)<br />
fxR=func(xR,*args)<br />
return 0.5*(xmax+xmin)<br />
<br />
</source><br />
Napisanej metody optymalizacji możemy użyć w celu dopasowania funkcji do danych empirycznych metodą njamniejszych kwadratów. Przykład takiego zastosowania poniżej.<br />
<br />
<source lang="python"><br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a): return x*a<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print GoldenRatioRearch(squares,0,10,args=(liniowa,xlist,ylist),xtol=0.01)<br />
</source><br />
<br />
==Optymalizacja wielowymiarowa==<br />
Przejście od optymalizacji jedno- do wielowymiarowej fundamentalnie komplikuje problem. Pierwszym problemem jest istnienie tak zwanych punktów siodłowych. Nie istnieją zatem metody które zawsze znajdują szukane minimum, nawet jezeli wiadomo że takie istnieje. Najpopularniejszą metodą jest downhill symplex lub inaczej metoda Neldera-Meada. Z powodu znaczengo stopnia komplikacji nie ędziemy jej samodzielnie implementować, a jedynie poslużymy sie implementacją z biblioteki scipy.optimize. Dziki niej możemy np. dopasowywać funkcję z więcej niż jednym parametrem do danych eksperymentalnych.<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a,b): return x*a+b<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23,-0.73)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print so.fmin(squares,(1,0),args=(kwadratowa,xlist,ylist))<br />
</source><br />
Oczywiście dopasowywanie możemy przeprowadzać nie tylko metodą najmniejszych kwadratów.<br />
===Zadanie - rozklad Cauchy'ego===<br />
Wylosuj 1000 liczb z rozkładu Cauchy'ego z parametrami loc=1.23 i scale=2.0. Do wylosowanych danych dopasuj rozkład Cauche'ego trzema metodami<br />
*METODA 1 - stwórz histogram otrzymanych wartości, znormalizuj go i metodą najmniejszych kwadratów dopsauj gęstość rozkladu do histogramu<br />
*METODA 2 - dopasuj gęstość rozkładu do wylosowanych danych metodą największej wiarygodności<br />
*METODA 3 - z wylosowanych danych stwórz dystrybuante empiryczną. Metodą najmniejszych kwadratów dopasuj dystrybuantę rozkładu cauchy'ego do dystrybuanty empirycznej.<br />
<br />
===Rozwiązanie===<br />
<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
def rho_cauchy(x,loc,scale):<br />
return (np.pi*scale*(1.0+(x-loc)**2/(scale**2)))**(-1.0)<br />
<br />
def F_cauchy(x,loc,scale):<br />
return 0.5+np.arctan((x-loc)*1.0/scale)/np.pi<br />
<br />
<br />
#losujemy 10000 liczb z rozkladu Caychyego o loc=1.23 i scale=2.0<br />
x=2*np.random.standard_cauchy(10000)+1.23<br />
N=len(x)<br />
<br />
#METODA 1 - Dopasowanie metoda najmniejszych kwadratow do histogramu<br />
<br />
#tworzymy histogram<br />
hist,bins= np.histogram(x,bins=np.linspace(-20,20,61))<br />
#dlugosc przedzialu histogramowania<br />
przedzial=bins[1]-bins[0]<br />
#normalizujemy histogram aby moc go porownac z gestoscia<br />
hist=hist*1.0/len(x)/przedzial<br />
#liczymy wsp. srodkow przedzialow histogramowania<br />
xhist=bins[:-1]+0.5*przedzial<br />
#definiujemy sume kwadratow<br />
def squares((loc,scale)):<br />
return sum([(rho_cauchy(xhist[i],loc,scale)-hist[i])**2 for i in range(len(hist))])<br />
#szukamy minimum funkcja fmin<br />
fit1=tuple(so.fmin(squares,(0,1)))<br />
print 'wynik metody1 to '+str(fit1)<br />
#ogladamy wynik<br />
py.plot(xhist,hist)<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[rho_cauchy(a,*fit1) for a in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
<br />
<br />
#METODA 2 - Metoda najwiekszej wiarygodnosci<br />
<br />
#definiujemy -funkcje wiarygodnosci<br />
def L((loc,scale)):<br />
return -sum([np.log(rho_cauchy(a,loc,scale)) for a in x])<br />
#szukamy minimum<br />
fit2=tuple(so.fmin(L,(0,1)))<br />
print 'wynik metody2 to '+str(fit2)<br />
<br />
<br />
#METODA 3 - dopasowanie dystrybuant<br />
<br />
xx=sorted(x)<br />
yy=np.linspace(0,1,N)<br />
#definiujemy funkcje KS bedaca maksimum z roznicy miedzy dystrybuanta empiryczna a teoretyczna<br />
def KS((loc,scale)):<br />
return max([abs(F_cauchy(xx[i],loc,scale)-yy[i]) for i in xrange(N)])<br />
#szukamy minimum<br />
fit3=tuple(so.fmin(KS,(0,1)))<br />
print 'wynik metody3 to '+str(fit3)<br />
#ogladamy wynik<br />
cut=100<br />
py.plot(xx[cut:-cut],yy[cut:-cut])<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[F_cauchy(x,*fit3) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
===Zadanie - Data Container===<br />
Napisz klasę funkcja przyjmującą w konstruktorze parametry funkcji i posiadającąreprezentacje tekstową. Napisz dowolna funkcję dziedziczącą po klasie funkcja, której metoda call przyjmuje jeden argument i zwraca wartość funkcji dla podanego argumentu i parametrów podanych w konstruktorze. <br />
Napisz klasę DataContainer przyjmującą w konstruktorze dwie serie danych empirycznych o tej samej długości odpowiadające wspólrzędnym x i y. Obiekt klasy DataContainer powinien być wyposażony w metodę o nazwie fit, która przyjmuje jako argument obiekt klasy funkcja. Metoda fit powinna zwracać obiekt takiej samej klasy jak otrzymany w argumencie ale z parametrami dla których funkcja jest najlepiej (metodą najmniejszych kwadratów) dopasowana do przechowywanych w obiekcie danych.<br />
===Rozwiązanie=== <br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
import time<br />
import scipy.optimize as so<br />
<br />
class funkcja(object):<br />
def __init__(self,*args):<br />
self.args=args<br />
def __str__(self):<br />
return 'to jest funkcja o nazwie '+self.__class__.__name__+' i argumentach '+str(self.args)<br />
<br />
class liniowa(funkcja):<br />
def __call__(self,x):<br />
return self.args[0]*x*x+self.args[1]<br />
<br />
class DataContainer(object):<br />
def __init__(self,x,y):<br />
self.x=np.array(x)<br />
self.y=np.array(y)<br />
self.n=len(x)<br />
def __str__(self):<br />
return '''to jest Data Container z danymi:<br />
x[:10]:'''+str(self.x[:10])+'''<br />
y[:10]:'''+str(self.y[:10])<br />
<br />
def fit(self,funkcja):<br />
parametry_poczatkowe=funkcja.args<br />
def squares(parametry):<br />
funkcja.__init__(*tuple(parametry))<br />
return sum((map(funkcja,self.x)-self.y)**2)<br />
parametry_dopasowane=so.fmin(squares,parametry_poczatkowe)<br />
funkcja.__init__(*tuple(parametry_dopasowane))<br />
return funkcja<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
#generujemy wartosci funkcji z szumem<br />
f=liniowa(1.23,-0.73)<br />
ylist=[f(x)+0.05*np.random.randn() for x in xlist]<br />
<br />
d=DataContainer(xlist,ylist)<br />
f=liniowa(1,2)<br />
f=d.fit(f)<br />
py.plot(d.x,d.y)<br />
py.plot(d.x,map(f,d.x))<br />
py.show()<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Optymalizacja&diff=3628TI/Programowanie dla Fizyków Medycznych/Optymalizacja2015-06-09T13:55:02Z<p>Tgubiec: /* Optymalizacja jednowymiarowa */</p>
<hr />
<div>==Optymalizacja jednowymiarowa==<br />
Omawianie zagadnienia optymalizacji rozpocznijmy od prostego przykładu. Zdefiniujmy pewną funkcję i zobaczmy jak wygląda jej wykres.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
licznikTestowej=0<br />
<br />
def testowa(x):<br />
global licznikTestowej<br />
licznikTestowej+=1<br />
return 1/x+np.exp(x)<br />
<br />
xtest=np.arange(0.2,2,0.01)<br />
ytest=[testowa(x) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
[[Plik:opt1.png]]<br />
<br />
Na rozważanym przedziale [0.2,2] powyższa funkcja ma tylko jedno ekstremum lokalne. Taką funkcję nazywamy unimodalną. Zmienna licznikTestowej umożliwi nam zliczanie wywołań funkcji testowej przez analizowane procedury.<br />
Zagadnienie, którym teraz będziemy się zajmować to problem numerycznego znajdowania takiego ekstremum. Jak każdy problem numeryczny ekstremum szukać będziemy zakładając pewną dokładność otrzymanego wyniku, którą oznaczmy xtol. Na wstępie przyjmijmy że poszukujemy ekstremum z dokładnością xtol=0.01. Najporstszą metodą będzie policzenie wartości funkcji dla wszystkich wartościx z podanego przedziału co xtol. Jest to metoda siłowa i wielokrotnie licząca wartość funkcji. Jej kod możemy znaleść poniżej. <br />
<source lang="python"><br />
def bruteForce(func,xmin,xmax,args=(),xtol=0.01):<br />
xlist=np.arange(xmin,xmax,xtol)<br />
ylist=[func(x,*args) for x in xlist]<br />
return xlist[ylist.index(max(ylist))]<br />
</source><br />
Innym, znacznie efektywniejszym sposobem znajdowania minimum możebyć nastepująca procedura rekurencyjna:<br />
*podzielmy przedzial [xmin,xmax] na 3 równe część: [xmin,xL],[xL,xR] oraz [xR,xmax]<br />
*jeżeli wartość funkcji w xL jest mniejsza od wartości funkcji w xR to powtórz procedurę dla przedziału [xmin,xR]. W przeciwnym przypadku powtórz proceduę dla przedziału [xL,xmax].<br />
*zakończ działanie gdy badany przedział jest krótszy nić xtol<br />
Przykładowa imlementacja tej metody wygląda nastepująco<br />
<source lang="python"><br />
def twoMidPointsR(func,xmin,xmax,args=(),xtol=0.01):<br />
if xmax-xmin<xtol: return 0.5*(xmax+xmin)<br />
xL=xmin+(xmax-xmin)/3.0<br />
xR=xmax-(xmax-xmin)/3.0<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
if fxL>fxR:<br />
return twoMidPointsR(func,xmin,xR,args,xtol)<br />
else:<br />
return twoMidPointsR(func,xL,xmax,args,xtol)<br />
</source><br />
Tą metodą możemy już szukać minimum ze dowolną dokładnością, co nie będzie skutowało znacznie większym czasem obliczeń. Zauważmy jednak że w każdej iteracji wartość minimalizowanej funkcji liczona jest dwukrotnie. Jeżeli mamy doczynienia z funkcją dla której obliczenie pojedynczej wartości jest bardzo czasochłonne to warto się zastanowić czy nie można tego wyniku poprawić. Zauważmy że dla działania tej metody wcale nie jest konieczne dzielenia badanego odcinka dokładnie na trzy równe części. Można dokonać tego podziału w zupełnie innej proporcji. Warto tak dobrać punkty xL i xR aby xR pokrywał się z xL (lub xL z xR) w kolejnym kroku iteracji. Jeżeli dodatkowo stworzymy zmienne przechowujące wcześniej liczone wartości funkcji to uda nam sie ograniczyć liczbe wywołań funkcji o połowę. Opisana metoda to tak zwana '''metoda złotego podziału'''. Przykladowa implementacja wygląda nastepująco <br />
<br />
<source lang="python"><br />
def GoldenRatioRearch(func,xmin,xmax,args=(),xtol=0.01):<br />
golden=0.5*(np.sqrt(5.0)-1.0)<br />
xL=xmax-golden*(xmax-xmin)<br />
xR=xmin+golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
while xmax-xmin>xtol: <br />
if fxL<fxR:<br />
xmin=xmin<br />
xmax=xR<br />
xR=xL<br />
fxR=fxL<br />
xL=xmax-golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
else:<br />
xmin=xL<br />
xmax=xmax<br />
xL=xR<br />
fxL=fxR<br />
xR=xmin+golden*(xmax-xmin)<br />
fxR=func(xR,*args)<br />
return 0.5*(xmax+xmin)<br />
<br />
</source><br />
Napisanej metody optymalizacji możemy użyć w celu dopasowania funkcji do danych empirycznych metodą njamniejszych kwadratów. Przykład takiego zastosowania poniżej.<br />
<br />
<source lang="python"><br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a): return x*a<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print GoldenRatioRearch(squares,0,10,args=(liniowa,xlist,ylist),xtol=0.01)<br />
</source><br />
<br />
==Optymalizacja wielowymiarowa==<br />
Przejście od optymalizacji jedno- do wielowymiarowej fundamentalnie komplikuje problem. Pierwszym problemem jest istnienie tak zwanych punktów siodłowych. Nie istnieją zatem metody które zawsze znajdują szukane minimum, nawet jezeli wiadomo że takie istnieje. Najpopularniejszą metodą jest downhill symplex lub inaczej metoda Neldera-Meada. Z powodu znaczengo stopnia komplikacji nie ędziemy jej samodzielnie implementować, a jedynie poslużymy sie implementacją z biblioteki scipy.optimize. Dziki niej możemy np. dopasowywać funkcję z więcej niż jednym parametrem do danych eksperymentalnych.<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a,b): return x*a+b<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23,-0.73)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print so.fmin(squares,(1,0),args=(kwadratowa,xlist,ylist))<br />
</source><br />
Oczywiście dopasowywanie możemy przeprowadzać nie tylko metodą najmniejszych kwadratów.<br />
===Zadanie - rozklad Cauchy'ego===<br />
Wylosuj 1000 liczb z rozkładu Cauchy'ego z parametrami loc=1.23 i scale=2.0. Do wylosowanych danych dopasuj rozkład Cauche'ego trzema metodami<br />
*METODA 1 - stwórz histogram otrzymanych wartości, znormalizuj go i metodą najmniejszych kwadratów dopsauj gęstość rozkladu do histogramu<br />
*METODA 2 - dopasuj gęstość rozkładu do wylosowanych danych metodą największej wiarygodności<br />
*METODA 3 - z wylosowanych danych stwórz dystrybuante empiryczną. Metodą najmniejszych kwadratów dopasuj dystrybuantę rozkładu cauchy'ego do dystrybuanty empirycznej.<br />
<br />
===Rozwiązanie===<br />
<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
def rho_cauchy(x,loc,scale):<br />
return (np.pi*scale*(1.0+(x-loc)**2/(scale**2)))**(-1.0)<br />
<br />
def F_cauchy(x,loc,scale):<br />
return 0.5+np.arctan((x-loc)*1.0/scale)/np.pi<br />
<br />
<br />
#losujemy 10000 liczb z rozkladu Caychyego o loc=1.23 i scale=2.0<br />
x=2*np.random.standard_cauchy(10000)+1.23<br />
N=len(x)<br />
<br />
#METODA 1 - Dopasowanie metoda najmniejszych kwadratow do histogramu<br />
<br />
#tworzymy histogram<br />
hist,bins= np.histogram(x,bins=np.linspace(-20,20,61))<br />
#dlugosc przedzialu histogramowania<br />
przedzial=bins[1]-bins[0]<br />
#normalizujemy histogram aby moc go porownac z gestoscia<br />
hist=hist*1.0/len(x)/przedzial<br />
#liczymy wsp. srodkow przedzialow histogramowania<br />
xhist=bins[:-1]+0.5*przedzial<br />
#definiujemy sume kwadratow<br />
def squares((loc,scale)):<br />
return sum([(rho_cauchy(xhist[i],loc,scale)-hist[i])**2 for i in range(len(hist))])<br />
#szukamy minimum funkcja fmin<br />
fit1=tuple(so.fmin(squares,(0,1)))<br />
print 'wynik metody1 to '+str(fit1)<br />
#ogladamy wynik<br />
py.plot(xhist,hist)<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[rho_cauchy(a,*fit1) for a in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
<br />
<br />
#METODA 2 - Metoda najwiekszej wiarygodnosci<br />
<br />
#definiujemy -funkcje wiarygodnosci<br />
def L((loc,scale)):<br />
return -sum([np.log(rho_cauchy(a,loc,scale)) for a in x])<br />
#szukamy minimum<br />
fit2=tuple(so.fmin(L,(0,1)))<br />
print 'wynik metody2 to '+str(fit2)<br />
<br />
<br />
#METODA 3 - dopasowanie dystrybuant<br />
<br />
xx=sorted(x)<br />
yy=np.linspace(0,1,N)<br />
#definiujemy funkcje KS bedaca maksimum z roznicy miedzy dystrybuanta empiryczna a teoretyczna<br />
def KS((loc,scale)):<br />
return max([abs(F_cauchy(xx[i],loc,scale)-yy[i]) for i in xrange(N)])<br />
#szukamy minimum<br />
fit3=tuple(so.fmin(KS,(0,1)))<br />
print 'wynik metody3 to '+str(fit3)<br />
#ogladamy wynik<br />
cut=100<br />
py.plot(xx[cut:-cut],yy[cut:-cut])<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[F_cauchy(x,*fit3) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
===Zadanie - Data Container===<br />
Napisz klasę funkcja przyjmującą w konstruktorze parametry funkcji i posiadającąreprezentacje tekstową. Napisz dowolna funkcję dziedziczącą po klasie funkcja, której metoda call przyjmuje jeden argument i zwraca wartość funkcji dla podanego argumentu i parametrów podanych w konstruktorze. <br />
Napisz klasę DataContainer przyjmującą w konstruktorze dwie serie danych empirycznych o tej samej długości odpowiadające wspólrzędnym x i y. Obiekt klasy DataContainer powinien być wyposażony w metodę o nazwie fit, która przyjmuje jako argument obiekt klasy funkcja. Metoda fit powinna zwracać obiekt takiej samej klasy jak otrzymany w argumencie ale z parametrami dla których funkcja jest najlepiej (metodą najmniejszych kwadratów) dopasowana do przechowywanych w obiekcie danych.<br />
===Rozwiązanie=== <br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
import time<br />
import scipy.optimize as so<br />
<br />
class funkcja(object):<br />
def __init__(self,*args):<br />
self.args=args<br />
def __str__(self):<br />
return 'to jest funkcja o nazwie '+self.__class__.__name__+' i argumentach '+str(self.args)<br />
<br />
class liniowa(funkcja):<br />
def __call__(self,x):<br />
return self.args[0]*x*x+self.args[1]<br />
<br />
class DataContainer(object):<br />
def __init__(self,x,y):<br />
self.x=np.array(x)<br />
self.y=np.array(y)<br />
self.n=len(x)<br />
def __str__(self):<br />
return '''to jest Data Container z danymi:<br />
x[:10]:'''+str(self.x[:10])+'''<br />
y[:10]:'''+str(self.y[:10])<br />
<br />
def fit(self,funkcja):<br />
parametry_poczatkowe=funkcja.args<br />
def squares(parametry):<br />
funkcja.__init__(*tuple(parametry))<br />
return sum((map(funkcja,self.x)-self.y)**2)<br />
parametry_dopasowane=so.fmin(squares,parametry_poczatkowe)<br />
funkcja.__init__(*tuple(parametry_dopasowane))<br />
return funkcja<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
#generujemy wartosci funkcji z szumem<br />
f=liniowa(1.23,-0.73)<br />
ylist=[f(x)+0.05*np.random.randn() for x in xlist]<br />
<br />
d=DataContainer(xlist,ylist)<br />
f=liniowa(1,2)<br />
f=d.fit(f)<br />
py.plot(d.x,d.y)<br />
py.plot(d.x,map(f,d.x))<br />
py.show()<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Optymalizacja&diff=3627TI/Programowanie dla Fizyków Medycznych/Optymalizacja2015-06-09T13:54:17Z<p>Tgubiec: /* Optymalizacja jednowymiarowa */</p>
<hr />
<div>==Optymalizacja jednowymiarowa==<br />
Omawianie zagadnienia optymalizacji rozpocznijmy od prostego przykładu. Zdefiniujmy pewną funkcję i zobaczmy jak wygląda jej wykres.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
licznikTestowej=0<br />
<br />
def testowa(x):<br />
global licznikTestowej<br />
licznikTestowej+=1<br />
return 1/x+np.exp(x)<br />
<br />
xtest=np.arange(0.2,2,0.01)<br />
ytest=[testowa(x) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
[[Plik:opt1.png]]<br />
<br />
Na rozważanym przedziale [0.2,2] powyższa funkcja ma tylko jedno ekstremum lokalne. Taką funkcję nazywamy unimodalną. Zmienna licznikTestowej umożliwi nam zliczanie wywołań funkcji testowej przez analizowane procedury.<br />
Zagadnienie którym teraz będziemy się zajmować to problem numerycznego znajdowania takiego ekstremum. Jak każdy problem numeryczny ekstremum szukać będziemy zakładając pewną dokładność otrzymanego wyniku, którą oznaczmy xtol. Na wstępie przyjmijmy że poszukujemy ekstremum z dokładnością xtol=0.01. Najporstszą metodą będzie policzenie wartości funkcji dla wszystkich wartościx z podanego przedziału co xtol. Jest to metoda siłowa i wielokrotnie licząca wartość funkcji. Jej kod możemy znaleść poniżej. <br />
<source lang="python"><br />
def bruteForce(func,xmin,xmax,args=(),xtol=0.01):<br />
xlist=np.arange(xmin,xmax,xtol)<br />
ylist=[func(x,*args) for x in xlist]<br />
return xlist[ylist.index(max(ylist))]<br />
</source><br />
Innym, znacznie efektywniejszym sposobem znajdowania minimum możebyć nastepująca procedura rekurencyjna:<br />
*podzielmy przedzial [xmin,xmax] na 3 równe część: [xmin,xL],[xL,xR] oraz [xR,xmax]<br />
*jeżeli wartość funkcji w xL jest mniejsza od wartości funkcji w xR to powtórz procedurę dla przedziału [xmin,xR]. W przeciwnym przypadku powtórz proceduę dla przedziału [xL,xmax].<br />
*zakończ działanie gdy badany przedział jest krótszy nić xtol<br />
Przykładowa imlementacja tej metody wygląda nastepująco<br />
<source lang="python"><br />
def twoMidPointsR(func,xmin,xmax,args=(),xtol=0.01):<br />
if xmax-xmin<xtol: return 0.5*(xmax+xmin)<br />
xL=xmin+(xmax-xmin)/3.0<br />
xR=xmax-(xmax-xmin)/3.0<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
if fxL>fxR:<br />
return twoMidPointsR(func,xmin,xR,args,xtol)<br />
else:<br />
return twoMidPointsR(func,xL,xmax,args,xtol)<br />
</source><br />
Tą metodą możemy już szukać minimum ze dowolną dokładnością, co nie będzie skutowało znacznie większym czasem obliczeń. Zauważmy jednak że w każdej iteracji wartość minimalizowanej funkcji liczona jest dwukrotnie. Jeżeli mamy doczynienia z funkcją dla której obliczenie pojedynczej wartości jest bardzo czasochłonne to warto się zastanowić czy nie można tego wyniku poprawić. Zauważmy że dla działania tej metody wcale nie jest konieczne dzielenia badanego odcinka dokładnie na trzy równe części. Można dokonać tego podziału w zupełnie innej proporcji. Warto tak dobrać punkty xL i xR aby xR pokrywał się z xL (lub xL z xR) w kolejnym kroku iteracji. Jeżeli dodatkowo stworzymy zmienne przechowujące wcześniej liczone wartości funkcji to uda nam sie ograniczyć liczbe wywołań funkcji o połowę. Opisana metoda to tak zwana '''metoda złotego podziału'''. Przykladowa implementacja wygląda nastepująco <br />
<br />
<source lang="python"><br />
def GoldenRatioRearch(func,xmin,xmax,args=(),xtol=0.01):<br />
golden=0.5*(np.sqrt(5.0)-1.0)<br />
xL=xmax-golden*(xmax-xmin)<br />
xR=xmin+golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
while xmax-xmin>xtol: <br />
if fxL<fxR:<br />
xmin=xmin<br />
xmax=xR<br />
xR=xL<br />
fxR=fxL<br />
xL=xmax-golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
else:<br />
xmin=xL<br />
xmax=xmax<br />
xL=xR<br />
fxL=fxR<br />
xR=xmin+golden*(xmax-xmin)<br />
fxR=func(xR,*args)<br />
return 0.5*(xmax+xmin)<br />
<br />
</source><br />
Napisanej metody optymalizacji możemy użyć w celu dopasowania funkcji do danych empirycznych metodą njamniejszych kwadratów. Przykład takiego zastosowania poniżej.<br />
<br />
<source lang="python"><br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a): return x*a<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print GoldenRatioRearch(squares,0,10,args=(liniowa,xlist,ylist),xtol=0.01)<br />
</source><br />
<br />
==Optymalizacja wielowymiarowa==<br />
Przejście od optymalizacji jedno- do wielowymiarowej fundamentalnie komplikuje problem. Pierwszym problemem jest istnienie tak zwanych punktów siodłowych. Nie istnieją zatem metody które zawsze znajdują szukane minimum, nawet jezeli wiadomo że takie istnieje. Najpopularniejszą metodą jest downhill symplex lub inaczej metoda Neldera-Meada. Z powodu znaczengo stopnia komplikacji nie ędziemy jej samodzielnie implementować, a jedynie poslużymy sie implementacją z biblioteki scipy.optimize. Dziki niej możemy np. dopasowywać funkcję z więcej niż jednym parametrem do danych eksperymentalnych.<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a,b): return x*a+b<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23,-0.73)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print so.fmin(squares,(1,0),args=(kwadratowa,xlist,ylist))<br />
</source><br />
Oczywiście dopasowywanie możemy przeprowadzać nie tylko metodą najmniejszych kwadratów.<br />
===Zadanie - rozklad Cauchy'ego===<br />
Wylosuj 1000 liczb z rozkładu Cauchy'ego z parametrami loc=1.23 i scale=2.0. Do wylosowanych danych dopasuj rozkład Cauche'ego trzema metodami<br />
*METODA 1 - stwórz histogram otrzymanych wartości, znormalizuj go i metodą najmniejszych kwadratów dopsauj gęstość rozkladu do histogramu<br />
*METODA 2 - dopasuj gęstość rozkładu do wylosowanych danych metodą największej wiarygodności<br />
*METODA 3 - z wylosowanych danych stwórz dystrybuante empiryczną. Metodą najmniejszych kwadratów dopasuj dystrybuantę rozkładu cauchy'ego do dystrybuanty empirycznej.<br />
<br />
===Rozwiązanie===<br />
<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
def rho_cauchy(x,loc,scale):<br />
return (np.pi*scale*(1.0+(x-loc)**2/(scale**2)))**(-1.0)<br />
<br />
def F_cauchy(x,loc,scale):<br />
return 0.5+np.arctan((x-loc)*1.0/scale)/np.pi<br />
<br />
<br />
#losujemy 10000 liczb z rozkladu Caychyego o loc=1.23 i scale=2.0<br />
x=2*np.random.standard_cauchy(10000)+1.23<br />
N=len(x)<br />
<br />
#METODA 1 - Dopasowanie metoda najmniejszych kwadratow do histogramu<br />
<br />
#tworzymy histogram<br />
hist,bins= np.histogram(x,bins=np.linspace(-20,20,61))<br />
#dlugosc przedzialu histogramowania<br />
przedzial=bins[1]-bins[0]<br />
#normalizujemy histogram aby moc go porownac z gestoscia<br />
hist=hist*1.0/len(x)/przedzial<br />
#liczymy wsp. srodkow przedzialow histogramowania<br />
xhist=bins[:-1]+0.5*przedzial<br />
#definiujemy sume kwadratow<br />
def squares((loc,scale)):<br />
return sum([(rho_cauchy(xhist[i],loc,scale)-hist[i])**2 for i in range(len(hist))])<br />
#szukamy minimum funkcja fmin<br />
fit1=tuple(so.fmin(squares,(0,1)))<br />
print 'wynik metody1 to '+str(fit1)<br />
#ogladamy wynik<br />
py.plot(xhist,hist)<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[rho_cauchy(a,*fit1) for a in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
<br />
<br />
#METODA 2 - Metoda najwiekszej wiarygodnosci<br />
<br />
#definiujemy -funkcje wiarygodnosci<br />
def L((loc,scale)):<br />
return -sum([np.log(rho_cauchy(a,loc,scale)) for a in x])<br />
#szukamy minimum<br />
fit2=tuple(so.fmin(L,(0,1)))<br />
print 'wynik metody2 to '+str(fit2)<br />
<br />
<br />
#METODA 3 - dopasowanie dystrybuant<br />
<br />
xx=sorted(x)<br />
yy=np.linspace(0,1,N)<br />
#definiujemy funkcje KS bedaca maksimum z roznicy miedzy dystrybuanta empiryczna a teoretyczna<br />
def KS((loc,scale)):<br />
return max([abs(F_cauchy(xx[i],loc,scale)-yy[i]) for i in xrange(N)])<br />
#szukamy minimum<br />
fit3=tuple(so.fmin(KS,(0,1)))<br />
print 'wynik metody3 to '+str(fit3)<br />
#ogladamy wynik<br />
cut=100<br />
py.plot(xx[cut:-cut],yy[cut:-cut])<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[F_cauchy(x,*fit3) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
===Zadanie - Data Container===<br />
Napisz klasę funkcja przyjmującą w konstruktorze parametry funkcji i posiadającąreprezentacje tekstową. Napisz dowolna funkcję dziedziczącą po klasie funkcja, której metoda call przyjmuje jeden argument i zwraca wartość funkcji dla podanego argumentu i parametrów podanych w konstruktorze. <br />
Napisz klasę DataContainer przyjmującą w konstruktorze dwie serie danych empirycznych o tej samej długości odpowiadające wspólrzędnym x i y. Obiekt klasy DataContainer powinien być wyposażony w metodę o nazwie fit, która przyjmuje jako argument obiekt klasy funkcja. Metoda fit powinna zwracać obiekt takiej samej klasy jak otrzymany w argumencie ale z parametrami dla których funkcja jest najlepiej (metodą najmniejszych kwadratów) dopasowana do przechowywanych w obiekcie danych.<br />
===Rozwiązanie=== <br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
import time<br />
import scipy.optimize as so<br />
<br />
class funkcja(object):<br />
def __init__(self,*args):<br />
self.args=args<br />
def __str__(self):<br />
return 'to jest funkcja o nazwie '+self.__class__.__name__+' i argumentach '+str(self.args)<br />
<br />
class liniowa(funkcja):<br />
def __call__(self,x):<br />
return self.args[0]*x*x+self.args[1]<br />
<br />
class DataContainer(object):<br />
def __init__(self,x,y):<br />
self.x=np.array(x)<br />
self.y=np.array(y)<br />
self.n=len(x)<br />
def __str__(self):<br />
return '''to jest Data Container z danymi:<br />
x[:10]:'''+str(self.x[:10])+'''<br />
y[:10]:'''+str(self.y[:10])<br />
<br />
def fit(self,funkcja):<br />
parametry_poczatkowe=funkcja.args<br />
def squares(parametry):<br />
funkcja.__init__(*tuple(parametry))<br />
return sum((map(funkcja,self.x)-self.y)**2)<br />
parametry_dopasowane=so.fmin(squares,parametry_poczatkowe)<br />
funkcja.__init__(*tuple(parametry_dopasowane))<br />
return funkcja<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
#generujemy wartosci funkcji z szumem<br />
f=liniowa(1.23,-0.73)<br />
ylist=[f(x)+0.05*np.random.randn() for x in xlist]<br />
<br />
d=DataContainer(xlist,ylist)<br />
f=liniowa(1,2)<br />
f=d.fit(f)<br />
py.plot(d.x,d.y)<br />
py.plot(d.x,map(f,d.x))<br />
py.show()<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Optymalizacja&diff=3626TI/Programowanie dla Fizyków Medycznych/Optymalizacja2015-06-09T13:49:01Z<p>Tgubiec: </p>
<hr />
<div>==Optymalizacja jednowymiarowa==<br />
Omawianie zagadnienia optymalizacji rozpocznijmy od prostego przykładu. Zdefiniujmy pewną funkcję i zobaczmy jak wygląda jej wykres.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
licznikTestowej=0<br />
<br />
def testowa(x):<br />
global licznikTestowej<br />
licznikTestowej+=1<br />
return 1/x+np.exp(x)<br />
<br />
xtest=np.arange(0.2,2,0.01)<br />
ytest=[testowa(x) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
[[Plik:opt1.png]]<br />
<br />
Na rozważanym przedziale [0.2,2] powyższa funkcją ma tylko jedno ekstremum lokalne. Taką funkcję nazywamy unimodalną. Zmienna licznikTestowej umożliwi nam zliczać wywołania funkcji testowej przez analizowane procedury.<br />
Zagadnienie którym teraz będziemy się zajmować to problem numerycznego znajdowania takiego ekstremum. Jak każdy problem numeryczny ekstremum szukać będziemy zakładając pewną dokładność otrzymanego wyniku, którą oznaczmy xtol. Na wstępie przyjmijmy że poszukujemy ekstremum z dokładnością xtol=0.01. Najporstszą metodą będzie policzenie wartości funkcji dla wszystkich wartościx z podanego przedziału co xtol. Jest to metoda siłowa i wielokrotnie licząca wartość funkcji. Jej kod możemy znaleść poniżej. <br />
<source lang="python"><br />
def bruteForce(func,xmin,xmax,args=(),xtol=0.01):<br />
xlist=np.arange(xmin,xmax,xtol)<br />
ylist=[func(x,*args) for x in xlist]<br />
return xlist[ylist.index(max(ylist))]<br />
</source><br />
Innym, znacznie efektywniejszym sposobem znajdowania minimum możebyć nastepująca procedura rekurencyjna:<br />
*podzielmy przedzial [xmin,xmax] na 3 równe część: [xmin,xL],[xL,xR] oraz [xR,xmax]<br />
*jeżeli wartość funkcji w xL jest mniejsza od wartości funkcji w xR to powtórz procedurę dla przedziału [xmin,xR]. W przeciwnym przypadku powtórz proceduę dla przedziału [xL,xmax].<br />
*zakończ działanie gdy badany przedział jest krótszy nić xtol<br />
Przykładowa imlementacja tej metody wygląda nastepująco<br />
<source lang="python"><br />
def twoMidPointsR(func,xmin,xmax,args=(),xtol=0.01):<br />
if xmax-xmin<xtol: return 0.5*(xmax+xmin)<br />
xL=xmin+(xmax-xmin)/3.0<br />
xR=xmax-(xmax-xmin)/3.0<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
if fxL>fxR:<br />
return twoMidPointsR(func,xmin,xR,args,xtol)<br />
else:<br />
return twoMidPointsR(func,xL,xmax,args,xtol)<br />
</source><br />
Tą metodą możemy już szukać minimum ze dowolną dokładnością, co nie będzie skutowało znacznie większym czasem obliczeń. Zauważmy jednak że w każdej iteracji wartość minimalizowanej funkcji liczona jest dwukrotnie. Jeżeli mamy doczynienia z funkcją dla której obliczenie pojedynczej wartości jest bardzo czasochłonne to warto się zastanowić czy nie można tego wyniku poprawić. Zauważmy że dla działania tej metody wcale nie jest konieczne dzielenia badanego odcinka dokładnie na trzy równe części. Można dokonać tego podziału w zupełnie innej proporcji. Warto tak dobrać punkty xL i xR aby xR pokrywał się z xL (lub xL z xR) w kolejnym kroku iteracji. Jeżeli dodatkowo stworzymy zmienne przechowujące wcześniej liczone wartości funkcji to uda nam sie ograniczyć liczbe wywołań funkcji o połowę. Opisana metoda to tak zwana '''metoda złotego podziału'''. Przykladowa implementacja wygląda nastepująco <br />
<br />
<source lang="python"><br />
def GoldenRatioRearch(func,xmin,xmax,args=(),xtol=0.01):<br />
golden=0.5*(np.sqrt(5.0)-1.0)<br />
xL=xmax-golden*(xmax-xmin)<br />
xR=xmin+golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
while xmax-xmin>xtol: <br />
if fxL<fxR:<br />
xmin=xmin<br />
xmax=xR<br />
xR=xL<br />
fxR=fxL<br />
xL=xmax-golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
else:<br />
xmin=xL<br />
xmax=xmax<br />
xL=xR<br />
fxL=fxR<br />
xR=xmin+golden*(xmax-xmin)<br />
fxR=func(xR,*args)<br />
return 0.5*(xmax+xmin)<br />
<br />
</source><br />
Napisanej metody optymalizacji możemy użyć w celu dopasowania funkcji do danych empirycznych metodą njamniejszych kwadratów. Przykład takiego zastosowania poniżej.<br />
<br />
<source lang="python"><br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a): return x*a<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print GoldenRatioRearch(squares,0,10,args=(liniowa,xlist,ylist),xtol=0.01)<br />
</source><br />
<br />
==Optymalizacja wielowymiarowa==<br />
Przejście od optymalizacji jedno- do wielowymiarowej fundamentalnie komplikuje problem. Pierwszym problemem jest istnienie tak zwanych punktów siodłowych. Nie istnieją zatem metody które zawsze znajdują szukane minimum, nawet jezeli wiadomo że takie istnieje. Najpopularniejszą metodą jest downhill symplex lub inaczej metoda Neldera-Meada. Z powodu znaczengo stopnia komplikacji nie ędziemy jej samodzielnie implementować, a jedynie poslużymy sie implementacją z biblioteki scipy.optimize. Dziki niej możemy np. dopasowywać funkcję z więcej niż jednym parametrem do danych eksperymentalnych.<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
#suma kwadratów<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a,b): return x*a+b<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[liniowa(x,1.23,-0.73)+0.000001*np.random.randn() for x in xlist]<br />
<br />
#najlepsze dopasowanie metoda golden ration <br />
print so.fmin(squares,(1,0),args=(kwadratowa,xlist,ylist))<br />
</source><br />
Oczywiście dopasowywanie możemy przeprowadzać nie tylko metodą najmniejszych kwadratów.<br />
===Zadanie - rozklad Cauchy'ego===<br />
Wylosuj 1000 liczb z rozkładu Cauchy'ego z parametrami loc=1.23 i scale=2.0. Do wylosowanych danych dopasuj rozkład Cauche'ego trzema metodami<br />
*METODA 1 - stwórz histogram otrzymanych wartości, znormalizuj go i metodą najmniejszych kwadratów dopsauj gęstość rozkladu do histogramu<br />
*METODA 2 - dopasuj gęstość rozkładu do wylosowanych danych metodą największej wiarygodności<br />
*METODA 3 - z wylosowanych danych stwórz dystrybuante empiryczną. Metodą najmniejszych kwadratów dopasuj dystrybuantę rozkładu cauchy'ego do dystrybuanty empirycznej.<br />
<br />
===Rozwiązanie===<br />
<br />
<source lang="python"><br />
import scipy.optimize as so<br />
<br />
def rho_cauchy(x,loc,scale):<br />
return (np.pi*scale*(1.0+(x-loc)**2/(scale**2)))**(-1.0)<br />
<br />
def F_cauchy(x,loc,scale):<br />
return 0.5+np.arctan((x-loc)*1.0/scale)/np.pi<br />
<br />
<br />
#losujemy 10000 liczb z rozkladu Caychyego o loc=1.23 i scale=2.0<br />
x=2*np.random.standard_cauchy(10000)+1.23<br />
N=len(x)<br />
<br />
#METODA 1 - Dopasowanie metoda najmniejszych kwadratow do histogramu<br />
<br />
#tworzymy histogram<br />
hist,bins= np.histogram(x,bins=np.linspace(-20,20,61))<br />
#dlugosc przedzialu histogramowania<br />
przedzial=bins[1]-bins[0]<br />
#normalizujemy histogram aby moc go porownac z gestoscia<br />
hist=hist*1.0/len(x)/przedzial<br />
#liczymy wsp. srodkow przedzialow histogramowania<br />
xhist=bins[:-1]+0.5*przedzial<br />
#definiujemy sume kwadratow<br />
def squares((loc,scale)):<br />
return sum([(rho_cauchy(xhist[i],loc,scale)-hist[i])**2 for i in range(len(hist))])<br />
#szukamy minimum funkcja fmin<br />
fit1=tuple(so.fmin(squares,(0,1)))<br />
print 'wynik metody1 to '+str(fit1)<br />
#ogladamy wynik<br />
py.plot(xhist,hist)<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[rho_cauchy(a,*fit1) for a in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
<br />
<br />
#METODA 2 - Metoda najwiekszej wiarygodnosci<br />
<br />
#definiujemy -funkcje wiarygodnosci<br />
def L((loc,scale)):<br />
return -sum([np.log(rho_cauchy(a,loc,scale)) for a in x])<br />
#szukamy minimum<br />
fit2=tuple(so.fmin(L,(0,1)))<br />
print 'wynik metody2 to '+str(fit2)<br />
<br />
<br />
#METODA 3 - dopasowanie dystrybuant<br />
<br />
xx=sorted(x)<br />
yy=np.linspace(0,1,N)<br />
#definiujemy funkcje KS bedaca maksimum z roznicy miedzy dystrybuanta empiryczna a teoretyczna<br />
def KS((loc,scale)):<br />
return max([abs(F_cauchy(xx[i],loc,scale)-yy[i]) for i in xrange(N)])<br />
#szukamy minimum<br />
fit3=tuple(so.fmin(KS,(0,1)))<br />
print 'wynik metody3 to '+str(fit3)<br />
#ogladamy wynik<br />
cut=100<br />
py.plot(xx[cut:-cut],yy[cut:-cut])<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[F_cauchy(x,*fit3) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
===Zadanie - Data Container===<br />
Napisz klasę funkcja przyjmującą w konstruktorze parametry funkcji i posiadającąreprezentacje tekstową. Napisz dowolna funkcję dziedziczącą po klasie funkcja, której metoda call przyjmuje jeden argument i zwraca wartość funkcji dla podanego argumentu i parametrów podanych w konstruktorze. <br />
Napisz klasę DataContainer przyjmującą w konstruktorze dwie serie danych empirycznych o tej samej długości odpowiadające wspólrzędnym x i y. Obiekt klasy DataContainer powinien być wyposażony w metodę o nazwie fit, która przyjmuje jako argument obiekt klasy funkcja. Metoda fit powinna zwracać obiekt takiej samej klasy jak otrzymany w argumencie ale z parametrami dla których funkcja jest najlepiej (metodą najmniejszych kwadratów) dopasowana do przechowywanych w obiekcie danych.<br />
===Rozwiązanie=== <br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
import time<br />
import scipy.optimize as so<br />
<br />
class funkcja(object):<br />
def __init__(self,*args):<br />
self.args=args<br />
def __str__(self):<br />
return 'to jest funkcja o nazwie '+self.__class__.__name__+' i argumentach '+str(self.args)<br />
<br />
class liniowa(funkcja):<br />
def __call__(self,x):<br />
return self.args[0]*x*x+self.args[1]<br />
<br />
class DataContainer(object):<br />
def __init__(self,x,y):<br />
self.x=np.array(x)<br />
self.y=np.array(y)<br />
self.n=len(x)<br />
def __str__(self):<br />
return '''to jest Data Container z danymi:<br />
x[:10]:'''+str(self.x[:10])+'''<br />
y[:10]:'''+str(self.y[:10])<br />
<br />
def fit(self,funkcja):<br />
parametry_poczatkowe=funkcja.args<br />
def squares(parametry):<br />
funkcja.__init__(*tuple(parametry))<br />
return sum((map(funkcja,self.x)-self.y)**2)<br />
parametry_dopasowane=so.fmin(squares,parametry_poczatkowe)<br />
funkcja.__init__(*tuple(parametry_dopasowane))<br />
return funkcja<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
#generujemy wartosci funkcji z szumem<br />
f=liniowa(1.23,-0.73)<br />
ylist=[f(x)+0.05*np.random.randn() for x in xlist]<br />
<br />
d=DataContainer(xlist,ylist)<br />
f=liniowa(1,2)<br />
f=d.fit(f)<br />
py.plot(d.x,d.y)<br />
py.plot(d.x,map(f,d.x))<br />
py.show()<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Optymalizacja&diff=3625TI/Programowanie dla Fizyków Medycznych/Optymalizacja2015-06-09T13:25:58Z<p>Tgubiec: </p>
<hr />
<div>==Optymalizacja jednowymiarowa==<br />
Omawianie zagadnienia optymalizacji rozpocznijmy od prostego przykładu. Zdefiniujmy pewną funkcję i zobaczmy jak wygląda jej wykres.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
licznikTestowej=0<br />
<br />
def testowa(x):<br />
global licznikTestowej<br />
licznikTestowej+=1<br />
return 1/x+np.exp(x)<br />
<br />
xtest=np.arange(0.2,2,0.01)<br />
ytest=[testowa(x) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
[[Plik:opt1.png]]<br />
<br />
Na rozważanym przedziale [0.2,2] powyższa funkcją ma tylko jedno ekstremum lokalne. Taką funkcję nazywamy unimodalną. Zmienna licznikTestowej umożliwi nam zliczać wywołania funkcji testowej przez analizowane procedury.<br />
Zagadnienie którym teraz będziemy się zajmować to problem numerycznego znajdowania takiego ekstremum. Jak każdy problem numeryczny ekstremum szukać będziemy zakładając pewną dokładność otrzymanego wyniku, którą oznaczmy xtol. Na wstępie przyjmijmy że poszukujemy ekstremum z dokładnością xtol=0.01. Najporstszą metodą będzie policzenie wartości funkcji dla wszystkich wartościx z podanego przedziału co xtol. Jest to metoda siłowa i wielokrotnie licząca wartość funkcji. Jej kod możemy znaleść poniżej. <br />
<source lang="python"><br />
def bruteForce(func,xmin,xmax,args=(),xtol=0.01):<br />
xlist=np.arange(xmin,xmax,xtol)<br />
ylist=[func(x,*args) for x in xlist]<br />
return xlist[ylist.index(max(ylist))]<br />
</source><br />
Innym, znacznie efektywniejszym sposobem znajdowania minimum możebyć nastepująca procedura rekurencyjna:<br />
*podzielmy przedzial [xmin,xmax] na 3 równe część: [xmin,xL],[xL,xR] oraz [xR,xmax]<br />
*jeżeli wartość funkcji w xL jest mniejsza od wartości funkcji w xR to powtórz procedurę dla przedziału [xmin,xR]. W przeciwnym przypadku powtórz proceduę dla przedziału [xL,xmax].<br />
*zakończ działanie gdy badany przedział jest krótszy nić xtol<br />
Przykładowa imlementacja tej metody wygląda nastepująco<br />
<source lang="python"><br />
def twoMidPointsR(func,xmin,xmax,args=(),xtol=0.01):<br />
if xmax-xmin<xtol: return 0.5*(xmax+xmin)<br />
xL=xmin+(xmax-xmin)/3.0<br />
xR=xmax-(xmax-xmin)/3.0<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
if fxL>fxR:<br />
return twoMidPointsR(func,xmin,xR,args,xtol)<br />
else:<br />
return twoMidPointsR(func,xL,xmax,args,xtol)<br />
</source><br />
Tą metodą możemy już szukać minimum ze dowolną dokładnością, co nie będzie skutowało znacznie większym czasem obliczeń. Zauważmy jednak że w każdej iteracji wartość minimalizowanej funkcji liczona jest dwukrotnie. Jeżeli mamy doczynienia z funkcją dla której obliczenie pojedynczej wartości jest bardzo czasochłonne to warto się zastanowić czy nie można tego wyniku poprawić. Zauważmy że dla działania tej metody wcale nie jest konieczne dzielenia badanego odcinka dokładnie na trzy równe części. Można dokonać tego podziału w zupełnie innej proporcji. Warto tak dobrać punkty xL i xR aby xR pokrywał się z xL (lub xL z xR) w kolejnym kroku iteracji. Jeżeli dodatkowo stworzymy zmienne przechowujące wcześniej liczone wartości funkcji to uda nam sie ograniczyć liczbe wywołań funkcji o połowę. Opisana metoda to tak zwana '''metoda złotego podziału'''. Przykladowa implementacja wygląda nastepująco <br />
<br />
<source lang="python"><br />
def GoldenRatioRearch(func,xmin,xmax,args=(),xtol=0.01):<br />
golden=0.5*(np.sqrt(5.0)-1.0)<br />
xL=xmax-golden*(xmax-xmin)<br />
xR=xmin+golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
while xmax-xmin>xtol: <br />
if fxL<fxR:<br />
xmin=xmin<br />
xmax=xR<br />
xR=xL<br />
fxR=fxL<br />
xL=xmax-golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
else:<br />
xmin=xL<br />
xmax=xmax<br />
xL=xR<br />
fxL=fxR<br />
xR=xmin+golden*(xmax-xmin)<br />
fxR=func(xR,*args)<br />
return 0.5*(xmax+xmin)<br />
<br />
<br />
#test metody zlotego podzialu<br />
#print GoldenRatioRearch(testowa,0,5,xtol=0.000001)<br />
#print so.fmin(testowa,np.array([1]))[0]<br />
<br />
#przypadek z jednym parametrem<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a): return x*a<br />
<br />
#funkcja kwadratowa<br />
def kwadratowa(x,a,b=0): return a*x*x+b*x<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[kwadratowa(x,1.23,-0.73)+0.000001*np.random.randn() for x in xlist]<br />
#py.plot(xlist,ylist)<br />
#py.show()<br />
<br />
#lista przeszukiwanych wartosci parametru a<br />
#alist=np.arange(0,10,0.01)<br />
#wartosci squares dla tych parametrow<br />
#squareslist=[squares(a,liniowa,xlist,ylist)for a in alist]<br />
#zobaczmy wykres<br />
#py.plot(alist,squareslist)<br />
#py.show()<br />
<br />
#najlepsze dopasowanie metoda brute force<br />
#print bruteForce(squares,0,10,args=(liniowa,xlist,ylist),xtol=0.01)<br />
<br />
#najlepsze dopasowanie metoda golden ration funkcji z 1 parametrem<br />
#print GoldenRatioRearch(squares,0,10,args=(liniowa,xlist,ylist),xtol=0.01)<br />
print so.fmin(squares,(1),args=(liniowa,xlist,ylist))<br />
<br />
#najlepsze dopasowanie metoda symplexowa z dwoma parametrami<br />
print so.fmin(squares,(1,0),args=(kwadratowa,xlist,ylist))<br />
</source><br />
<br />
==Optymalizacja wielowymiarowa==<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
import scipy.optimize as so<br />
<br />
#def squares(a,func,xlist,ylist):<br />
# return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
#print so.fmin(squares,(1,0),args=(kwadratowa,xlist,ylist))<br />
def rho_cauchy(x,loc,scale):<br />
return (np.pi*scale*(1.0+(x-loc)**2/(scale**2)))**(-1.0)<br />
<br />
def F_cauchy(x,loc,scale):<br />
return 0.5+np.arctan((x-loc)*1.0/scale)/np.pi<br />
<br />
<br />
#losujemy 10000 liczb z rozkladu Caychyego o loc=1.23 i scale=2.0<br />
x=2*np.random.standard_cauchy(10000)+1.23<br />
N=len(x)<br />
<br />
#METODA 1 - Dopasowanie metoda najmniejszych kwadratow do histogramu<br />
<br />
#tworzymy histogram<br />
hist,bins= np.histogram(x,bins=np.linspace(-20,20,61))<br />
#dlugosc przedzialu histogramowania<br />
przedzial=bins[1]-bins[0]<br />
#normalizujemy histogram aby moc go porownac z gestoscia<br />
hist=hist*1.0/len(x)/przedzial<br />
#liczymy wsp. srodkow przedzialow histogramowania<br />
xhist=bins[:-1]+0.5*przedzial<br />
#definiujemy sume kwadratow<br />
def squares((loc,scale)):<br />
return sum([(rho_cauchy(xhist[i],loc,scale)-hist[i])**2 for i in range(len(hist))])<br />
#szukamy minimum funkcja fmin<br />
fit1=tuple(so.fmin(squares,(0,1)))<br />
print 'wynik metody1 to '+str(fit1)<br />
#ogladamy wynik<br />
py.plot(xhist,hist)<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[rho_cauchy(a,*fit1) for a in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
<br />
<br />
#METODA 2 - Metoda najwiekszej wiarygodnosci<br />
<br />
#definiujemy -funkcje wiarygodnosci<br />
def L((loc,scale)):<br />
return -sum([np.log(rho_cauchy(a,loc,scale)) for a in x])<br />
#szukamy minimum<br />
fit2=tuple(so.fmin(L,(0,1)))<br />
print 'wynik metody2 to '+str(fit2)<br />
<br />
<br />
#METODA 3 - dopasowanie dystrybuant<br />
<br />
xx=sorted(x)<br />
yy=np.linspace(0,1,N)<br />
#definiujemy funkcje KS bedaca maksimum z roznicy miedzy dystrybuanta empiryczna a teoretyczna<br />
def KS((loc,scale)):<br />
return max([abs(F_cauchy(xx[i],loc,scale)-yy[i]) for i in xrange(N)])<br />
#szukamy minimum<br />
fit3=tuple(so.fmin(KS,(0,1)))<br />
print 'wynik metody3 to '+str(fit3)<br />
#ogladamy wynik<br />
cut=100<br />
py.plot(xx[cut:-cut],yy[cut:-cut])<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[F_cauchy(x,*fit3) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
===Zadanie===<br />
Napisz data container...<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
import time<br />
import scipy.optimize as so<br />
<br />
class funkcja(object):<br />
def __init__(self,*args):<br />
self.args=args<br />
def __str__(self):<br />
return 'to jest funkcja o nazwie '+self.__class__.__name__+' i argumentach '+str(self.args)<br />
<br />
class liniowa(funkcja):<br />
def __call__(self,x):<br />
return self.args[0]*x*x+self.args[1]<br />
<br />
class DataContainer(object):<br />
def __init__(self,x,y):<br />
self.x=np.array(x)<br />
self.y=np.array(y)<br />
self.n=len(x)<br />
def __str__(self):<br />
return '''to jest Data Container z danymi:<br />
x[:10]:'''+str(self.x[:10])+'''<br />
y[:10]:'''+str(self.y[:10])<br />
<br />
def fit(self,funkcja):<br />
parametry_poczatkowe=funkcja.args<br />
def squares(parametry):<br />
funkcja.__init__(*tuple(parametry))<br />
return sum((map(funkcja,self.x)-self.y)**2)<br />
parametry_dopasowane=so.fmin(squares,parametry_poczatkowe)<br />
funkcja.__init__(*tuple(parametry_dopasowane))<br />
return funkcja<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
#generujemy wartosci funkcji z szumem<br />
f=liniowa(1.23,-0.73)<br />
ylist=[f(x)+0.05*np.random.randn() for x in xlist]<br />
<br />
d=DataContainer(xlist,ylist)<br />
f=liniowa(1,2)<br />
f=d.fit(f)<br />
py.plot(d.x,d.y)<br />
py.plot(d.x,map(f,d.x))<br />
py.show()<br />
<br />
<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Morfologia_matematyczna&diff=3624TI/Programowanie dla Fizyków Medycznych/Morfologia matematyczna2015-06-09T13:25:57Z<p>Tgubiec: /* Dylacja */</p>
<hr />
<div>==Morfologia matematyczna==<br />
Morfologia matematyczna to bardzo przydatna metoda przetwarzania obrazów binarnych (czarno-białych), pozwalająca na analizę i "upraszczanie" obserwowanych kształtów. W szczególności można ją zastosować do obrazów medycznych przetworzonych przez progowanie. Metody morfologii matematycznej pozwalają także na uspójnienie otrzymanego obrazu nie zmieniając jego rozmiarów zewnętrznych co może być bardzo przydatne przy dokonywaniu pomiarów w oparciu o cyfrowy obraz medyczny. Dla osób zainteresowanych bardziej formalną definicją poszczególnych operacji polecam bardzo szeroką literaturę dostępną w internecie. Tutaj przedstawimy definicję pozwalającą na łatwiejsze zrozumienie istoty działania poszczególnych operacji. Operacje morfologii matematycznej opierają się na tak zwanym elemencie strukturalnym, który my w uproszczeniu nazywać będziemy pędzlem (brush). Zacznijmy od zdefiniowania naszego obrazu roboczego i przykładowego pędzla.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
a=np.zeros((100,100),dtype=np.bool)<br />
a[30:50,30:50]=True<br />
a[50:70,50:70]=True<br />
<br />
brush7=np.array([[0,0,1,1,1,0,0],[0,1,1,1,1,1,0],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[0,1,1,1,1,1,0],[0,0,1,1,1,0,0]],dtype=np.bool)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia1.png]]<br />
<br />
W definiowaniu funkcji morfologii matematycznej przydatna będzie procedura zmieniająca pędzel, będący kwadratową tablicą zer i jedynek na listę wektorów mających początek w środku pędzla i końce we wszystkich komórkach posiadających wartość 1. Kod takiej procedury przedstawia się następująco.<br />
<source lang="python"><br />
def brush2list(brush):<br />
result=[]<br />
N=brush.shape[0]<br />
middle=N/2<br />
for x in range(N):<br />
for y in range(N):<br />
if brush[x,y]: result.append((x-middle,y-middle))<br />
return result<br />
</source><br />
<br />
===Dylacja===<br />
Pierwszą operacją którą omówimy jest dylacja. Nasz obraz w chwili obecnej składa się z tła zer (w kolorze czarnym) i dwóch kwadratów z jedynek (w kolorze białym). Wyobraźmy sobie, że nasz pędzel służy do malowania po obrazku. Jeżeli w jakimś miejscu przyłożymy środek pędzla, to wszystkie pixele, na których znajdą się jedynki w pędzlu zmienią swoją wartość na jeden. Operacja dylacji to przyłożenie środka pędzla do wszystkich komórek, których początkowa wartość to jeden. Równoważna jest także inna definicja. Przykładamy środek pędzla po kolei do wszystkich pixeli. Tworzymy listę wartości pixeli obrazu, które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą '''maksimum''' z tej list. Implementacja drugiej z tych definicji znajduje się poniżej.<br />
<source lang="python"><br />
def dylacja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=max([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(dylacja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-dylacja.png]]<br />
<br />
W efekcie dwa kwadraty powiększyły się i zaokrągliły im się rogi.<br />
<br />
===Erozja===<br />
Drugą operacją morfologii matematycznej jest erozja. Operacja jest analogiczna, z tą zmianą, że teraz nasz pędzel maluje na czarno i przesuwamy go po czarnych pixelach. Przy alternatywnej definicji przykładamy środek pędzla po kolei do wszystkich pixeli. Tworzymy listę wartości pixeli obrazu które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą '''minimum''' z tej list. Implementacja drugiej z tych definicji znajduje się poniżej.<br />
<source lang="python"><br />
def erozja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=min([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(erozja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-erozja.png]]<br />
<br />
W wyniku tej operacji kwadraty nie zmieniły swojego kształtu, za to zmniejszyły się i powstała między nimi przerwa.<br />
<br />
===Otwarcie i zamknięcie===<br />
Skoro dylacja powiększa rozmiar naszego obrazu a erozja zmniejsza, naturalnym jest postawienie sobie pytania, co się stanie gdy obraz najpierw potraktujemy erozją a potem dylacją lub też odwrotnie. Operacje powstałe właśnie w ten sposób nazywamy otwarciem i zamknięciem. Ich definicja nie jest już problematyczna posiadając implementacje wcześniejszych metod.<br />
<source lang="python"><br />
def otwarcie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return dylacja(erozja(fig,brush),brush)<br />
<br />
def zamkniecie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return erozja(dylacja(fig,brush),brush)<br />
<br />
py.imshow(otwarcie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
py.imshow(zamkniecie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-otwarcie.png]]<br />
<br />
[[Plik:morfologia-zakmniecie.png]]<br />
<br />
Wyniki powyższych dwóch operacji są najbardziej przydatne w zastosowaniach medycznych. W obu przypadkach rozmiary obrazu nie zmieniły się. W przypadku otwarcia rogi kwadratów zaokrągliły się i stykające się kwadraty stały się wyraźniej rozłączne. W przypadku zamknięcia obszar tworzony przez dwa kwadraty został uspójniony tworząc coś w rodzaju "mostu" między nimi. Przy zastosowaniu zamknięcia wszystkie obiekty odległe od siebie o mniej niż średnica pędzla zostaną połączone.<br />
===Filtr medianowy===<br />
W poprzednich metodach przykładaliśmy środek pędzla po kolei do wszystkich pixeli, tworzyliśmy listę wartości pixeli obrazu, które w danym ułożeniu pędzla odpowiadają wartościom 1 na pędzlu. Do pixela, w którym znajduje się środek pędzla przypisujemy wartość będącą minimum lub maksimum z tej list. Co się stanie gdy na wartościach z tej listy wykonamy jakieś inne operacje? Wzięcie średniej, jak wspominałem w poprzednim rozdziale, doprowadzi do rozmycia obrazka (średnia nie musi być zerem lub jedynką). Bardzo przydatną operację uzyskamy, gdy weźmiemy '''medianę''' z wartości w tej liście. Powstanie w ten sposób tak zwany filtr medianowy, który jest doskonałym narzędziem do usuwania szumu z obrazka. Jego implementacja wygląda na przykład tak.<br />
<source lang="python"><br />
def medianowy(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=np.median([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
</source><br />
Aby przetestować działanie filtra medianowego musimy "zaszumić" nasz oryginalny obrazek<br />
<source lang="python"><br />
for x,y in np.ndindex(a.shape):<br />
if (np.random.random()<0.05): a[x,y]=False<br />
if (np.random.random()>0.95): a[x,y]=True<br />
</source><br />
Zobaczmy teraz jakie wyniki da wielokrotne zastosowanie filtra medianowego.<br />
<source lang="python"><br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-zaszumiony1.png]]<br />
<br />
[[Plik:morfologia-zaszumiony2.png]]<br />
<br />
[[Plik:morfologia-zaszumiony3.png]]<br />
<br />
[[Plik:morfologia-zaszumiony4.png]]<br />
<br />
Jak widać udało się odtworzyć kształty widoczne nawet na obrazku bardzo słabej jakości. Operacje takie pozwalają znacznie poprawić jakość wyników uzyskiwanych poprzez progowanie obrazów medycznych.<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Morfologia_matematyczna&diff=3623TI/Programowanie dla Fizyków Medycznych/Morfologia matematyczna2015-06-09T13:23:51Z<p>Tgubiec: /* Morfologia matematyczna */</p>
<hr />
<div>==Morfologia matematyczna==<br />
Morfologia matematyczna to bardzo przydatna metoda przetwarzania obrazów binarnych (czarno-białych), pozwalająca na analizę i "upraszczanie" obserwowanych kształtów. W szczególności można ją zastosować do obrazów medycznych przetworzonych przez progowanie. Metody morfologii matematycznej pozwalają także na uspójnienie otrzymanego obrazu nie zmieniając jego rozmiarów zewnętrznych co może być bardzo przydatne przy dokonywaniu pomiarów w oparciu o cyfrowy obraz medyczny. Dla osób zainteresowanych bardziej formalną definicją poszczególnych operacji polecam bardzo szeroką literaturę dostępną w internecie. Tutaj przedstawimy definicję pozwalającą na łatwiejsze zrozumienie istoty działania poszczególnych operacji. Operacje morfologii matematycznej opierają się na tak zwanym elemencie strukturalnym, który my w uproszczeniu nazywać będziemy pędzlem (brush). Zacznijmy od zdefiniowania naszego obrazu roboczego i przykładowego pędzla.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
a=np.zeros((100,100),dtype=np.bool)<br />
a[30:50,30:50]=True<br />
a[50:70,50:70]=True<br />
<br />
brush7=np.array([[0,0,1,1,1,0,0],[0,1,1,1,1,1,0],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[0,1,1,1,1,1,0],[0,0,1,1,1,0,0]],dtype=np.bool)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia1.png]]<br />
<br />
W definiowaniu funkcji morfologii matematycznej przydatna będzie procedura zmieniająca pędzel, będący kwadratową tablicą zer i jedynek na listę wektorów mających początek w środku pędzla i końce we wszystkich komórkach posiadających wartość 1. Kod takiej procedury przedstawia się następująco.<br />
<source lang="python"><br />
def brush2list(brush):<br />
result=[]<br />
N=brush.shape[0]<br />
middle=N/2<br />
for x in range(N):<br />
for y in range(N):<br />
if brush[x,y]: result.append((x-middle,y-middle))<br />
return result<br />
</source><br />
<br />
===Dylacja===<br />
Pierwszą operacją którą omówimy jest dylacja. Nasz obraz w chwili obecnej składa się z tła zer (w kolorze czarnym) i dwóch kwadratów z jedynek (w kolorze białym). Wyobraźmy sobie, że nasz pędzel służy do malowania po obrazku. Jeżeli w jakimś miejscu przyłożymy środek pędzla, to wszystkie pixele, na których znajdą się jedynki w pędzlu zmienią swoją wartość na jeden. Operacja dylacji to przyłożenie środka pędzla do wszystkich komórek, których początkowa wartość to jeden. Równoważna jest także inna definicja. Przykładamy środek pędzla po kolei do wszystkich pixeli. Tworzymy listę wartości pixeli obrazu które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą '''maksimum''' z tej list. Implementacja drugiej z tych definicji znajduje się poniżej.<br />
<source lang="python"><br />
def dylacja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=max([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(dylacja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-dylacja.png]]<br />
<br />
W efekcie dwa kwadraty powiększyły się i zaokrągliły im się rogi.<br />
===Erozja===<br />
Drugą operacją morfologii matematycznej jest erozja. Operacja jest analogiczna, z tą zmianą, że teraz nasz pędzel maluje na czarno i przesuwamy go po czarnych pixelach. Przy alternatywnej definicji przykładamy środek pędzla po kolei do wszystkich pixeli. Tworzymy listę wartości pixeli obrazu które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą '''minimum''' z tej list. Implementacja drugiej z tych definicji znajduje się poniżej.<br />
<source lang="python"><br />
def erozja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=min([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(erozja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-erozja.png]]<br />
<br />
W wyniku tej operacji kwadraty nie zmieniły swojego kształtu, za to zmniejszyły się i powstała między nimi przerwa.<br />
<br />
===Otwarcie i zamknięcie===<br />
Skoro dylacja powiększa rozmiar naszego obrazu a erozja zmniejsza, naturalnym jest postawienie sobie pytania, co się stanie gdy obraz najpierw potraktujemy erozją a potem dylacją lub też odwrotnie. Operacje powstałe właśnie w ten sposób nazywamy otwarciem i zamknięciem. Ich definicja nie jest już problematyczna posiadając implementacje wcześniejszych metod.<br />
<source lang="python"><br />
def otwarcie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return dylacja(erozja(fig,brush),brush)<br />
<br />
def zamkniecie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return erozja(dylacja(fig,brush),brush)<br />
<br />
py.imshow(otwarcie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
py.imshow(zamkniecie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-otwarcie.png]]<br />
<br />
[[Plik:morfologia-zakmniecie.png]]<br />
<br />
Wyniki powyższych dwóch operacji są najbardziej przydatne w zastosowaniach medycznych. W obu przypadkach rozmiary obrazu nie zmieniły się. W przypadku otwarcia rogi kwadratów zaokrągliły się i stykające się kwadraty stały się wyraźniej rozłączne. W przypadku zamknięcia obszar tworzony przez dwa kwadraty został uspójniony tworząc coś w rodzaju "mostu" między nimi. Przy zastosowaniu zamknięcia wszystkie obiekty odległe od siebie o mniej niż średnica pędzla zostaną połączone.<br />
===Filtr medianowy===<br />
W poprzednich metodach przykładaliśmy środek pędzla po kolei do wszystkich pixeli, tworzyliśmy listę wartości pixeli obrazu, które w danym ułożeniu pędzla odpowiadają wartościom 1 na pędzlu. Do pixela, w którym znajduje się środek pędzla przypisujemy wartość będącą minimum lub maksimum z tej list. Co się stanie gdy na wartościach z tej listy wykonamy jakieś inne operacje? Wzięcie średniej, jak wspominałem w poprzednim rozdziale, doprowadzi do rozmycia obrazka (średnia nie musi być zerem lub jedynką). Bardzo przydatną operację uzyskamy, gdy weźmiemy '''medianę''' z wartości w tej liście. Powstanie w ten sposób tak zwany filtr medianowy, który jest doskonałym narzędziem do usuwania szumu z obrazka. Jego implementacja wygląda na przykład tak.<br />
<source lang="python"><br />
def medianowy(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=np.median([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
</source><br />
Aby przetestować działanie filtra medianowego musimy "zaszumić" nasz oryginalny obrazek<br />
<source lang="python"><br />
for x,y in np.ndindex(a.shape):<br />
if (np.random.random()<0.05): a[x,y]=False<br />
if (np.random.random()>0.95): a[x,y]=True<br />
</source><br />
Zobaczmy teraz jakie wyniki da wielokrotne zastosowanie filtra medianowego.<br />
<source lang="python"><br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-zaszumiony1.png]]<br />
<br />
[[Plik:morfologia-zaszumiony2.png]]<br />
<br />
[[Plik:morfologia-zaszumiony3.png]]<br />
<br />
[[Plik:morfologia-zaszumiony4.png]]<br />
<br />
Jak widać udało się odtworzyć kształty widoczne nawet na obrazku bardzo słabej jakości. Operacje takie pozwalają znacznie poprawić jakość wyników uzyskiwanych poprzez progowanie obrazów medycznych.<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Morfologia_matematyczna&diff=3622TI/Programowanie dla Fizyków Medycznych/Morfologia matematyczna2015-06-09T13:23:10Z<p>Tgubiec: /* Morfologia Matematyczna */</p>
<hr />
<div>==Morfologia matematyczna==<br />
Morfologia matematyczna to bardzo przydatna metoda przetwarzania obrazów binarnych (czarno-białych), pozwalająca na analizę i "upraszczanie" obserwowanych kształtów. W szczególności można ją zastosować do obrazów medycznych przetworzonych przed progowanie. Metody morfologii matematycznej pozwalają także na uspójnienie otrzymanego obrazu nie zmieniając jego rozmiarów zewnętrznych co może być bardzo przydatne przy dokonywaniu pomiarów w oparciu o cyfrowy obraz medyczny. Dla osób zainteresowanych bardziej formalną definicją poszczególnych operacji polecam bardzo szeroką literaturę dostępną w internecie. Tutaj przedstawimy definicję pozwalającą na łatwiejsze zrozumienie istoty działania poszczególnych operacji. Operacje morfologii matematycznej opierają się na tak zwanym elemencie strukturalnym, który my w uproszczeniu nazywać będziemy pędzlem (brush). Zacznijmy od zdefiniowania naszego obrazu roboczego i przykładowego pędzla.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
a=np.zeros((100,100),dtype=np.bool)<br />
a[30:50,30:50]=True<br />
a[50:70,50:70]=True<br />
<br />
brush7=np.array([[0,0,1,1,1,0,0],[0,1,1,1,1,1,0],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[0,1,1,1,1,1,0],[0,0,1,1,1,0,0]],dtype=np.bool)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia1.png]]<br />
<br />
W definiowaniu funkcji morfologii matematycznej przydatna będzie procedura zmieniająca pędzel, będący kwadratową tablicą zer i jedynek na listę wektorów mających początek w środku pędzla i końce we wszystkich komórkach posiadających wartość 1. Kod takiej procedury przedstawia się następująco.<br />
<source lang="python"><br />
def brush2list(brush):<br />
result=[]<br />
N=brush.shape[0]<br />
middle=N/2<br />
for x in range(N):<br />
for y in range(N):<br />
if brush[x,y]: result.append((x-middle,y-middle))<br />
return result<br />
</source><br />
<br />
===Dylacja===<br />
Pierwszą operacją którą omówimy jest dylacja. Nasz obraz w chwili obecnej składa się z tła zer (w kolorze czarnym) i dwóch kwadratów z jedynek (w kolorze białym). Wyobraźmy sobie, że nasz pędzel służy do malowania po obrazku. Jeżeli w jakimś miejscu przyłożymy środek pędzla, to wszystkie pixele, na których znajdą się jedynki w pędzlu zmienią swoją wartość na jeden. Operacja dylacji to przyłożenie środka pędzla do wszystkich komórek, których początkowa wartość to jeden. Równoważna jest także inna definicja. Przykładamy środek pędzla po kolei do wszystkich pixeli. Tworzymy listę wartości pixeli obrazu które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą '''maksimum''' z tej list. Implementacja drugiej z tych definicji znajduje się poniżej.<br />
<source lang="python"><br />
def dylacja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=max([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(dylacja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-dylacja.png]]<br />
<br />
W efekcie dwa kwadraty powiększyły się i zaokrągliły im się rogi.<br />
===Erozja===<br />
Drugą operacją morfologii matematycznej jest erozja. Operacja jest analogiczna, z tą zmianą, że teraz nasz pędzel maluje na czarno i przesuwamy go po czarnych pixelach. Przy alternatywnej definicji przykładamy środek pędzla po kolei do wszystkich pixeli. Tworzymy listę wartości pixeli obrazu które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą '''minimum''' z tej list. Implementacja drugiej z tych definicji znajduje się poniżej.<br />
<source lang="python"><br />
def erozja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=min([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(erozja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-erozja.png]]<br />
<br />
W wyniku tej operacji kwadraty nie zmieniły swojego kształtu, za to zmniejszyły się i powstała między nimi przerwa.<br />
<br />
===Otwarcie i zamknięcie===<br />
Skoro dylacja powiększa rozmiar naszego obrazu a erozja zmniejsza, naturalnym jest postawienie sobie pytania, co się stanie gdy obraz najpierw potraktujemy erozją a potem dylacją lub też odwrotnie. Operacje powstałe właśnie w ten sposób nazywamy otwarciem i zamknięciem. Ich definicja nie jest już problematyczna posiadając implementacje wcześniejszych metod.<br />
<source lang="python"><br />
def otwarcie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return dylacja(erozja(fig,brush),brush)<br />
<br />
def zamkniecie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return erozja(dylacja(fig,brush),brush)<br />
<br />
py.imshow(otwarcie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
py.imshow(zamkniecie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-otwarcie.png]]<br />
<br />
[[Plik:morfologia-zakmniecie.png]]<br />
<br />
Wyniki powyższych dwóch operacji są najbardziej przydatne w zastosowaniach medycznych. W obu przypadkach rozmiary obrazu nie zmieniły się. W przypadku otwarcia rogi kwadratów zaokrągliły się i stykające się kwadraty stały się wyraźniej rozłączne. W przypadku zamknięcia obszar tworzony przez dwa kwadraty został uspójniony tworząc coś w rodzaju "mostu" między nimi. Przy zastosowaniu zamknięcia wszystkie obiekty odległe od siebie o mniej niż średnica pędzla zostaną połączone.<br />
===Filtr medianowy===<br />
W poprzednich metodach przykładaliśmy środek pędzla po kolei do wszystkich pixeli, tworzyliśmy listę wartości pixeli obrazu, które w danym ułożeniu pędzla odpowiadają wartościom 1 na pędzlu. Do pixela, w którym znajduje się środek pędzla przypisujemy wartość będącą minimum lub maksimum z tej list. Co się stanie gdy na wartościach z tej listy wykonamy jakieś inne operacje? Wzięcie średniej, jak wspominałem w poprzednim rozdziale, doprowadzi do rozmycia obrazka (średnia nie musi być zerem lub jedynką). Bardzo przydatną operację uzyskamy, gdy weźmiemy '''medianę''' z wartości w tej liście. Powstanie w ten sposób tak zwany filtr medianowy, który jest doskonałym narzędziem do usuwania szumu z obrazka. Jego implementacja wygląda na przykład tak.<br />
<source lang="python"><br />
def medianowy(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=np.median([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
</source><br />
Aby przetestować działanie filtra medianowego musimy "zaszumić" nasz oryginalny obrazek<br />
<source lang="python"><br />
for x,y in np.ndindex(a.shape):<br />
if (np.random.random()<0.05): a[x,y]=False<br />
if (np.random.random()>0.95): a[x,y]=True<br />
</source><br />
Zobaczmy teraz jakie wyniki da wielokrotne zastosowanie filtra medianowego.<br />
<source lang="python"><br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-zaszumiony1.png]]<br />
<br />
[[Plik:morfologia-zaszumiony2.png]]<br />
<br />
[[Plik:morfologia-zaszumiony3.png]]<br />
<br />
[[Plik:morfologia-zaszumiony4.png]]<br />
<br />
Jak widać udało się odtworzyć kształty widoczne nawet na obrazku bardzo słabej jakości. Operacje takie pozwalają znacznie poprawić jakość wyników uzyskiwanych poprzez progowanie obrazów medycznych.<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Manipulacja_obrazem&diff=3621TI/Programowanie dla Fizyków Medycznych/Manipulacja obrazem2015-06-09T13:22:53Z<p>Tgubiec: /* Rozmycie i wyostrzenie */</p>
<hr />
<div>==Manipulacja obrazem==<br />
Ćwiczenia z mainuplacji obrazem rozpocznijmy od wczytania pliku, który będziemy przetwarzać. Dla fizyków medycznych naturalnie będzie to plik DICOM.<br />
<source lang="python"><br />
import dicom<br />
import numpy as np<br />
import pylab as py<br />
<br />
plik=dicom.read_file('I00001.dcm')<br />
pixel=plik.pixel_array<br />
print pixel.shape<br />
>>>(2964, 2364)<br />
</source><br />
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.<br />
<source lang="python"><br />
pixel=pixel[::5,::5]<br />
print min(pixel.flatten()),max(pixel.flatten())<br />
>>> 0 3907<br />
</source><br />
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].<br />
<source lang="python"><br />
pixel=pixel*1.0/max(pixel.flatten())<br />
</source><br />
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<br />
<source lang="python"><br />
def dodajPasek(tablica, n):<br />
tablica[:n,:]=np.linspace(0,1,tablica.shape[1])<br />
dodajPasek(pixel,30)<br />
py.imshow(pixel,cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:dicom2.png]]<br />
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.<br />
===Jasność===<br />
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.<br />
<source lang="python"><br />
def jasnosc(tablica, a):<br />
def f(x,a):<br />
return min(a*x,1.0)<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
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.<br />
Zobaczmy teraz na dwóch przykładach w jaki sposób funkcja jasność modyfikuje obraz<br />
<source lang="python"><br />
py.imshow(jasnosc(pixel,2),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(jasnosc(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:jasnosc1.png]]<br />
<br />
[[Plik:jasnosc2.png]]<br />
<br />
===Gamma===<br />
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.<br />
<br />
<source lang="python"><br />
def gamma(tablica, a):<br />
def f(x,a):<br />
return min(x**a,1.0)<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Możemy teraz na dwóch przykładach obejrzeć efekt działania przekształcenia gamma.<br />
<source lang="python"> <br />
py.imshow(gamma(pixel,2),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(gamma(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:gamma1.png]]<br />
<br />
[[Plik:gamma2.png]]<br />
<br />
===Próg===<br />
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. <br />
<source lang="python"><br />
def prog(tablica, a):<br />
def f(x,a):<br />
return 0 if x<a else 1<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Natomiast jej działanie na obraz wygląda następująco<br />
<source lang="python"><br />
py.imshow(prog(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
[[Plik:prog.png]]<br />
<br />
===Schodek===<br />
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 <br />
<source lang="python"><br />
def schodek(tablica, a,b):<br />
def f(x,a,b):<br />
return 1 if ((x<a) or (x>b)) else 0<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a,b)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
A przyklad działania tutaj.<br />
<source lang="python"><br />
py.imshow(schodek(pixel,0.2,0.6),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:schodek.png]]<br />
<br />
===Rozmycie i wyostrzenie===<br />
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.<br />
Chętnym pozostawiam napisanie takiej procedury samodzielnie, natomiast my skorzystamy z gotowej procedury z pakiecie scipy.ndimage.<br />
<source lang="python"><br />
from scipy import ndimage<br />
imag=ndimage.gaussian_filter(pixel,sigma=4)<br />
print imag<br />
</source><br />
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.<br />
<source lang="python"><br />
def wyostrz(tablica, sigma, a):<br />
rozmyty=np.array(ndimage.gaussian_filter(tablica,sigma=sigma))<br />
roznica=tablica-rozmyty<br />
wynik=rozmyty+a*roznica<br />
wynik/=max(wynik.flatten())<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Wynik działania dla przykładowych wartości parametru a oraz sigma=4 przedstawione są poniżej<br />
<source lang="python"><br />
py.imshow(wyostrz(pixel,4,0),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(wyostrz(pixel,4,3),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:blur.png]]<br />
<br />
[[Plik:sharp.png]]<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Manipulacja_obrazem&diff=3620TI/Programowanie dla Fizyków Medycznych/Manipulacja obrazem2015-06-09T13:22:10Z<p>Tgubiec: /* Gamma */</p>
<hr />
<div>==Manipulacja obrazem==<br />
Ćwiczenia z mainuplacji obrazem rozpocznijmy od wczytania pliku, który będziemy przetwarzać. Dla fizyków medycznych naturalnie będzie to plik DICOM.<br />
<source lang="python"><br />
import dicom<br />
import numpy as np<br />
import pylab as py<br />
<br />
plik=dicom.read_file('I00001.dcm')<br />
pixel=plik.pixel_array<br />
print pixel.shape<br />
>>>(2964, 2364)<br />
</source><br />
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.<br />
<source lang="python"><br />
pixel=pixel[::5,::5]<br />
print min(pixel.flatten()),max(pixel.flatten())<br />
>>> 0 3907<br />
</source><br />
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].<br />
<source lang="python"><br />
pixel=pixel*1.0/max(pixel.flatten())<br />
</source><br />
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<br />
<source lang="python"><br />
def dodajPasek(tablica, n):<br />
tablica[:n,:]=np.linspace(0,1,tablica.shape[1])<br />
dodajPasek(pixel,30)<br />
py.imshow(pixel,cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:dicom2.png]]<br />
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.<br />
===Jasność===<br />
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.<br />
<source lang="python"><br />
def jasnosc(tablica, a):<br />
def f(x,a):<br />
return min(a*x,1.0)<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
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.<br />
Zobaczmy teraz na dwóch przykładach w jaki sposób funkcja jasność modyfikuje obraz<br />
<source lang="python"><br />
py.imshow(jasnosc(pixel,2),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(jasnosc(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:jasnosc1.png]]<br />
<br />
[[Plik:jasnosc2.png]]<br />
<br />
===Gamma===<br />
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.<br />
<br />
<source lang="python"><br />
def gamma(tablica, a):<br />
def f(x,a):<br />
return min(x**a,1.0)<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Możemy teraz na dwóch przykładach obejrzeć efekt działania przekształcenia gamma.<br />
<source lang="python"> <br />
py.imshow(gamma(pixel,2),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(gamma(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:gamma1.png]]<br />
<br />
[[Plik:gamma2.png]]<br />
<br />
===Próg===<br />
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. <br />
<source lang="python"><br />
def prog(tablica, a):<br />
def f(x,a):<br />
return 0 if x<a else 1<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Natomiast jej działanie na obraz wygląda następująco<br />
<source lang="python"><br />
py.imshow(prog(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
[[Plik:prog.png]]<br />
<br />
===Schodek===<br />
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 <br />
<source lang="python"><br />
def schodek(tablica, a,b):<br />
def f(x,a,b):<br />
return 1 if ((x<a) or (x>b)) else 0<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a,b)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
A przyklad działania tutaj.<br />
<source lang="python"><br />
py.imshow(schodek(pixel,0.2,0.6),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:schodek.png]]<br />
<br />
===Rozmycie i wyostrzenie===<br />
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 pixeli przy liczeniu średniej liczone są z dwuwymiarowego rozkładu gaussa o zadanej dyspersji.<br />
Chętnym pozostawiam napisanie takiej procedury samodzielnie, natomiast my skorzystamy z gotowej procedury z pakiecie scipy.ndimage.<br />
<source lang="python"><br />
from scipy import ndimage<br />
imag=ndimage.gaussian_filter(pixel,sigma=4)<br />
print imag<br />
</source><br />
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.<br />
<source lang="python"><br />
def wyostrz(tablica, sigma, a):<br />
rozmyty=np.array(ndimage.gaussian_filter(tablica,sigma=sigma))<br />
roznica=tablica-rozmyty<br />
wynik=rozmyty+a*roznica<br />
wynik/=max(wynik.flatten())<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Wynik działania dla przykładowych wartości parametru a oraz sigma=4 przedstawione są poniżej<br />
<source lang="python"><br />
py.imshow(wyostrz(pixel,4,0),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(wyostrz(pixel,4,3),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:blur.png]]<br />
<br />
[[Plik:sharp.png]]<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Manipulacja_obrazem&diff=3619TI/Programowanie dla Fizyków Medycznych/Manipulacja obrazem2015-06-09T13:21:23Z<p>Tgubiec: /* Jasność */</p>
<hr />
<div>==Manipulacja obrazem==<br />
Ćwiczenia z mainuplacji obrazem rozpocznijmy od wczytania pliku, który będziemy przetwarzać. Dla fizyków medycznych naturalnie będzie to plik DICOM.<br />
<source lang="python"><br />
import dicom<br />
import numpy as np<br />
import pylab as py<br />
<br />
plik=dicom.read_file('I00001.dcm')<br />
pixel=plik.pixel_array<br />
print pixel.shape<br />
>>>(2964, 2364)<br />
</source><br />
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.<br />
<source lang="python"><br />
pixel=pixel[::5,::5]<br />
print min(pixel.flatten()),max(pixel.flatten())<br />
>>> 0 3907<br />
</source><br />
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].<br />
<source lang="python"><br />
pixel=pixel*1.0/max(pixel.flatten())<br />
</source><br />
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<br />
<source lang="python"><br />
def dodajPasek(tablica, n):<br />
tablica[:n,:]=np.linspace(0,1,tablica.shape[1])<br />
dodajPasek(pixel,30)<br />
py.imshow(pixel,cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:dicom2.png]]<br />
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.<br />
===Jasność===<br />
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.<br />
<source lang="python"><br />
def jasnosc(tablica, a):<br />
def f(x,a):<br />
return min(a*x,1.0)<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
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.<br />
Zobaczmy teraz na dwóch przykładach w jaki sposób funkcja jasność modyfikuje obraz<br />
<source lang="python"><br />
py.imshow(jasnosc(pixel,2),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(jasnosc(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:jasnosc1.png]]<br />
<br />
[[Plik:jasnosc2.png]]<br />
<br />
===Gamma===<br />
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.<br />
<br />
<source lang="python"><br />
def gamma(tablica, a):<br />
def f(x,a):<br />
return min(x**a,1.0)<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Możemy teraz na dwóch przykładach obejrzeć efekt działania przekształcenia gamma.<br />
<source lang="python"> <br />
py.imshow(gamma(pixel,2),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(gamma(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:gamma1.png]]<br />
<br />
[[Plik:gamma2.png]]<br />
<br />
===Próg===<br />
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. <br />
<source lang="python"><br />
def prog(tablica, a):<br />
def f(x,a):<br />
return 0 if x<a else 1<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Natomiast jej działanie na obraz wygląda następująco<br />
<source lang="python"><br />
py.imshow(prog(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
[[Plik:prog.png]]<br />
<br />
===Schodek===<br />
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 <br />
<source lang="python"><br />
def schodek(tablica, a,b):<br />
def f(x,a,b):<br />
return 1 if ((x<a) or (x>b)) else 0<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a,b)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
A przyklad działania tutaj.<br />
<source lang="python"><br />
py.imshow(schodek(pixel,0.2,0.6),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:schodek.png]]<br />
<br />
===Rozmycie i wyostrzenie===<br />
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 pixeli przy liczeniu średniej liczone są z dwuwymiarowego rozkładu gaussa o zadanej dyspersji.<br />
Chętnym pozostawiam napisanie takiej procedury samodzielnie, natomiast my skorzystamy z gotowej procedury z pakiecie scipy.ndimage.<br />
<source lang="python"><br />
from scipy import ndimage<br />
imag=ndimage.gaussian_filter(pixel,sigma=4)<br />
print imag<br />
</source><br />
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.<br />
<source lang="python"><br />
def wyostrz(tablica, sigma, a):<br />
rozmyty=np.array(ndimage.gaussian_filter(tablica,sigma=sigma))<br />
roznica=tablica-rozmyty<br />
wynik=rozmyty+a*roznica<br />
wynik/=max(wynik.flatten())<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Wynik działania dla przykładowych wartości parametru a oraz sigma=4 przedstawione są poniżej<br />
<source lang="python"><br />
py.imshow(wyostrz(pixel,4,0),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(wyostrz(pixel,4,3),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:blur.png]]<br />
<br />
[[Plik:sharp.png]]<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Manipulacja_obrazem&diff=3618TI/Programowanie dla Fizyków Medycznych/Manipulacja obrazem2015-06-09T13:07:20Z<p>Tgubiec: /* Próg */</p>
<hr />
<div>==Manipulacja obrazem==<br />
Ćwiczenia z mainuplacji obrazem rozpocznijmy od wczytania pliku, który będziemy przetwarzać. Dla fizyków medycznych naturalnie będzie to plik DICOM.<br />
<source lang="python"><br />
import dicom<br />
import numpy as np<br />
import pylab as py<br />
<br />
plik=dicom.read_file('I00001.dcm')<br />
pixel=plik.pixel_array<br />
print pixel.shape<br />
>>>(2964, 2364)<br />
</source><br />
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.<br />
<source lang="python"><br />
pixel=pixel[::5,::5]<br />
print min(pixel.flatten()),max(pixel.flatten())<br />
>>> 0 3907<br />
</source><br />
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].<br />
<source lang="python"><br />
pixel=pixel*1.0/max(pixel.flatten())<br />
</source><br />
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<br />
<source lang="python"><br />
def dodajPasek(tablica, n):<br />
tablica[:n,:]=np.linspace(0,1,tablica.shape[1])<br />
dodajPasek(pixel,30)<br />
py.imshow(pixel,cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:dicom2.png]]<br />
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.<br />
===Jasność===<br />
Najprostszą funkcją, od której na Wydziale Fizyki zaczynamy zawsze 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.<br />
<source lang="python"><br />
def jasnosc(tablica, a):<br />
def f(x,a):<br />
return min(a*x,1.0)<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
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.<br />
Zobaczmy teraz na dwóch przykładach w jaki sposób funkcja jasność modyfikuje obraz<br />
<source lang="python"><br />
py.imshow(jasnosc(pixel,2),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(jasnosc(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:jasnosc1.png]]<br />
<br />
[[Plik:jasnosc2.png]]<br />
<br />
===Gamma===<br />
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.<br />
<br />
<source lang="python"><br />
def gamma(tablica, a):<br />
def f(x,a):<br />
return min(x**a,1.0)<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Możemy teraz na dwóch przykładach obejrzeć efekt działania przekształcenia gamma.<br />
<source lang="python"> <br />
py.imshow(gamma(pixel,2),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(gamma(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:gamma1.png]]<br />
<br />
[[Plik:gamma2.png]]<br />
<br />
===Próg===<br />
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. <br />
<source lang="python"><br />
def prog(tablica, a):<br />
def f(x,a):<br />
return 0 if x<a else 1<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Natomiast jej działanie na obraz wygląda następująco<br />
<source lang="python"><br />
py.imshow(prog(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
[[Plik:prog.png]]<br />
<br />
===Schodek===<br />
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 <br />
<source lang="python"><br />
def schodek(tablica, a,b):<br />
def f(x,a,b):<br />
return 1 if ((x<a) or (x>b)) else 0<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a,b)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
A przyklad działania tutaj.<br />
<source lang="python"><br />
py.imshow(schodek(pixel,0.2,0.6),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:schodek.png]]<br />
<br />
===Rozmycie i wyostrzenie===<br />
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 pixeli przy liczeniu średniej liczone są z dwuwymiarowego rozkładu gaussa o zadanej dyspersji.<br />
Chętnym pozostawiam napisanie takiej procedury samodzielnie, natomiast my skorzystamy z gotowej procedury z pakiecie scipy.ndimage.<br />
<source lang="python"><br />
from scipy import ndimage<br />
imag=ndimage.gaussian_filter(pixel,sigma=4)<br />
print imag<br />
</source><br />
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.<br />
<source lang="python"><br />
def wyostrz(tablica, sigma, a):<br />
rozmyty=np.array(ndimage.gaussian_filter(tablica,sigma=sigma))<br />
roznica=tablica-rozmyty<br />
wynik=rozmyty+a*roznica<br />
wynik/=max(wynik.flatten())<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Wynik działania dla przykładowych wartości parametru a oraz sigma=4 przedstawione są poniżej<br />
<source lang="python"><br />
py.imshow(wyostrz(pixel,4,0),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(wyostrz(pixel,4,3),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:blur.png]]<br />
<br />
[[Plik:sharp.png]]<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/%C4%86wiczenia_z_przetwarzania_tablic_2D&diff=3617TI/Programowanie dla Fizyków Medycznych/Ćwiczenia z przetwarzania tablic 2D2015-06-09T13:03:25Z<p>Tgubiec: /* Wyświetlanie tablic 2D */</p>
<hr />
<div>==Ćwiczenia z przetwarzania tablic 2D==<br />
===Wyświetlanie tablic 2D===<br />
Jako wstęp do przetwarzania obrazów w pythonie przećwiczmy podstawowe operacje na dwuwymiarowych tablicach numpy w postaci których będziemy takie obrazy przechowywać. Zacznijmy od zdefiniowania przykładowej tablicy, która będzie reprezentować obrazek.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
a=np.zeros((100,100),dtype=np.bool)<br />
a[30:50,30:50]=True<br />
a[50:70,50:70]=True<br />
</source><br />
Stworzyliśmy w ten sposób tablice 100 na 100 z wpisanymi wartościami False oraz w środku dwa kwadraty o boku 20 z wartościami True. Już tak stworzoną tablicę możemy wyświetlić za pomocą pylaba.<br />
<source lang="python"><br />
py.imshow(a)<br />
py.show()<br />
</source><br />
<br />
[[Plik:pil1.png]]<br />
<br />
Nie do końca można było się spodziewać, że pylab wyświetli wartości False w kolorze granatowym, a wartości True w kolorze bordowym. Za przyporządkowanie kolorów określonym wartościom odpowiada tzw. mapa kolorów, którą podajemy jako parametr w poleceniu imshow. Aby uzyskać bardziej intuicyjną skalę szarości należy wywołać polecenie następująco<br />
<source lang="python"><br />
py.imshow(a,cmap = py.cm.gray)<br />
py.show()<br />
</source><br />
<br />
[[Plik:pil2.png]]<br />
<br />
Teraz kolory (a raczej ich brak) są takie jakich można było się spodziewać. Zaskakujące jest za to rozmycie brzegów kwadratów. Obrazek wyświetlany jest w większych rozmiarach niż jego domyślne 100 na 100 pixeli. Pylab aby wyświetlić go w wyższej rozdzielczości musi zwiększyć jego rozmiary. Rozciągając obrazek można w różny sposób przypisywać wartości pixelom, których nie było w pierwotnym obrazku. Proces ten nazywamy interpolacją i aby uzyskać "ostre" krawędzie należy znów użyć odpowiedniej opcji metody imshow.<br />
<source lang="python"><br />
py.imshow(a,cmap = py.cm.gray, interpolation = 'nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:pil3.png]]<br />
<br />
=== Gra w życie Conwaya ===<br />
W ramach ćwiczeń zaprogramujmy klasyczny automat komórkowy zwany grą w życie Conwaya.<br />
W naszej wersji gra toczy się na skończonej planszy o wymiarach 100 na 100 podzielonej na kwadratowe komórki. Każda komórka (poza brzegowymi) ma ośmiu sąsiadów, czyli komórki przylegające do niej bokami i rogami. Każda komórka może znajdować się w jednym z dwóch stanów: może być albo "żywa" (stanowi przypisujemy wartość 1), albo "martwa" (wartość 0). Stan wszystkich komórek w pewnej jednostce czasu jest używany do obliczenia stanu wszystkich komórek w następnej jednostce. Po obliczeniu wszystkie komórki zmieniają swój stan jednocześnie. Stan komórki zależy tylko od liczby jej żywych sąsiadów w następujący sposób<br />
*Martwa komórka, która ma dokładnie 3 żywych sąsiadów, staje się żywa w następnej jednostce czasu (rodzi się)<br />
*Żywa komórka z 2 albo 3 żywymi sąsiadami pozostaje żywa, przy innej liczbie sąsiadów umiera<br />
<br />
Zacznijmy od zdefiniowania planszy gry<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
a=np.zeros((100,100))<br />
</source><br />
Kluczową i najtrudniejszą funkcją jest funkcja zliczająca liczbę sąsiadów danej komórki. Problematyczne jest tutaj uwzględnienie przypadku komórek znajdujących się na rogach, które mają po trzech sąsiadów, oraz komórek brzegowych mających po 5 sąsiadów. Przykładowe rozwiązanie wygląda następująco.<br />
<source lang="python"><br />
def ile_sasiadow(x,y,macierz):<br />
return np.sum(macierz[max(0,x-1):min(macierz.shape[0],x+2),max(0,y-1):min(macierz.shape[1],y+2)].flatten())-macierz[x,y]<br />
</source><br />
Z tak przygotowaną funkcją ile_sasiadow możemy zdefiniować funkcję określającą logikę gry w życie.<br />
<source lang="python"><br />
def nowy_stan_komorki(x,y,macierz):<br />
sasiadow=ile_sasiadow(x,y,macierz)<br />
if sasiadow==3: return 1<br />
if sasiadow==2 and macierz[x,y]==1: return 1<br />
return 0<br />
</source><br />
Możemy teraz zdefiniować nowy stan całej planszy.<br />
<source lang="python"><br />
def krok(macierz):<br />
wynik=macierz.copy()<br />
for x,y in np.ndindex(wynik.shape):<br />
wynik[x,y]=nowy_stan_komorki(x,y,macierz)<br />
return wynik<br />
</source><br />
Jako warunku początkowego możemy użyć tzw. lokomotywy.<br />
<source lang="python"><br />
lokomotywa=np.array([[1,1,1,0,1],[1,0,0,0,0],[0,0,0,1,1],[0,1,1,0,1],[1,0,1,0,1]])<br />
a[45:50,45:50]=lokomotywa<br />
</source><br />
W pylabie istnieje prosty sposób na wyświetlenie animacji poprzez odświeżanie już wyświetlonego obrazka.<br />
<source lang="python"><br />
py.ion()<br />
for n in range(1000):<br />
py.imshow(a, cmap='Greys', interpolation='nearest')<br />
a=krok(a)<br />
py.draw()<br />
<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/RRZ&diff=3616TI/Programowanie dla Fizyków Medycznych/RRZ2015-06-09T12:59:35Z<p>Tgubiec: /* Rozwiązanie */</p>
<hr />
<div>__NOTOC__<br />
==Równania różniczkowe zwyczajne==<br />
Zajmiemy się teraz problemem numerycznego rozwiązywania równań różniczkowych zwyczajnych o postaci:<br />
<br />
<!--<br />
{{Hide in print|This text will not be shown in the print.}}<br />
{{Only in print|This text will only be shown in the print.}}<br />
--><br />
<br />
<math> \frac{dy(t)}{dt} = f(t,y(t))</math>,<br />
<br />
z warunkeim początkowym<br />
<br />
<math>y(t_0)=y_0</math>.<br />
<br />
Zauważmy, że przykładowe równanie różniczkowe drugiego rzędu<br />
<br />
<math> \frac{d^2 x(t)}{dt^2} = \omega(t,x(t))</math>,<br />
<br />
można zapisać jako<br />
<br />
<math> \frac{d}{dt} \binom{x(t)}{x'(t)} = \binom{x'(t)}{\omega(t,x(t))}</math>.<br />
<br />
W analogiczny sposób równanie dowolnego rzędy możemy zapisać jako wektorowe równanie różniczkowe pierwszego rzędu. Wystarczy zatem, że skupimy się na rozwiązywaniu równań pierwszego rzędu, Rozwiązaniem postawionego problemu są ciągłe funkcje zmiennej czasowej t. Rozwiązanie numeryczne takiego problemu ogranicza się jednak do znalezienia wartości funkcji y(t) w skończonej liczbie punktów czasowych. W najprostrzym przypadku (do którego się tutaj ograniczymy) zakładamy, że punkty te są od siebie równo oddalone, a odległość między nimi nazywamy krokiem czasowym i tradycyjnie oznaczamy literą h. Zatem rozwiązanie równania na przedziale <math>(t_0,t_k)</math> sprwadzamy do rozwiązania w sekwencji czasów <math>t_0, t+1=t_0+h,t_2=t_0+2h,...,t_k=Nh</math>. Poprzez <math>x_n</math> oznaczać będziemy numeryczne przybliżenie ścisłego rozwiązania <math>x(t_n)</math>.<br />
<br />
==Metoda Eulera==<br />
Najprostszą metodą numeryczną rozwiązywania równań różniczkowych jest metoda Eulera. Przybliżmy pochodną czasową występującą po lewej stronie równania przez iloraz różnicowy<br />
<br />
<math> \frac{d x(t)}{dt} \approx \frac{x(t+h)-x(t)}{h}</math>,<br />
<br />
przekształcając uzyskujemy<br />
<br />
<math> x(t+h) \approx x(t)+ h \frac{d x(t)}{dt} </math><br />
<br />
a po podstawieniu rozwiązywanego równania mamy<br />
<br />
<math> x(t+h) \approx x(t)+ h f(t,x(t)) </math>.<br />
<br />
Możemy to zapisać w postaci dyskretnej<br />
<br />
<math> x_{n+1} = x_n + h f(t_n,x_n) </math>.<br />
<br />
Wartość w kolejnej chwili czasu dana jest explicite poprzez wartość w chwili poprzedniej. Metoda ta nazywa się Explicit Euler. Możemy teraz zaimplementować ją w pythonie<br />
<br />
<!--<source lang="python">--><br />
<syntaxhighlight lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
#rozwiazujemy rownanie dx(t)/dt=f(t,x)<br />
<br />
#metoda Explicit Euler<br />
#f - funkcja f z rownania<br />
#x0-wartosc poczatkowa<br />
#t0-czas poczatkowy<br />
#tk-czas koncowy<br />
#h-krok czasowy<br />
<br />
def EE(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0))) # gdy mamy do czynienia w równaniem wektorowym<br />
else: x=np.zeros(N) #dla przypadku skalarnego<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
x[i]=np.array(x[i-1]+h*f(t[i-1],x[i-1]))<br />
i+=1<br />
return t,x<br />
</syntaxhighlight><br />
<!--</source>--><br />
Najłatwiej będzie przetestować napisaną metodę na równaniu, którego ścisłe rozwiązanie jest znane. Zacznijmy zatem od równania oscylatora harmonicznego<br />
<source lang="python"><br />
def oscylator(t,y):<br />
x=y[0]<br />
xdot=y[1]<br />
return np.array([xdot,-x])<br />
</source><br />
Rozwiążmy to równanie z warunkiem początkowym [1.0,1.0] i od czasu od 0 do 100.<br />
<source lang="python"><br />
t,x=EE(oscylator,[1.0,1.0],0.0,100,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
rozwiązanie wygląda wówczas następująco<br />
<br />
[[Plik:img01.png]]<br />
<br />
Jeżeli zaś wydłużymy czas symulacji do 1000 otrzymamy<br />
<br />
[[Plik:img2.png]]<br />
<br />
Amplituda oscylacji rośnie wykładniczo i rozwiązanie numeryczne bardzo szybko przestaje mieć cokolwiek wspólnego ze ścisłym rozwiązaniem, którego amplituda jest przecież stała. Metoda Explicit Euler już po kilku krokach czasowych przestaje przypominać ścisłe rozwiązanie. Niestety trudno jest zupełnie wyeliminować to zjawisko, za to możemy użyć metody, która znacznie wolniej będzie się oddalać od ścisłego rozwiązania. Zauważmy, że w metodzie Explicit Euler w każdym kroku czasowym tylko raz liczyliśmy wartość funkcji f. Liczbę wywołań funkcji f w każdym kroku czasowym nazywamy rzędem metody, stąd Explicit Euler jest metodą pierwszego rzędu. Wprowadźmy teraz przykładowe metody rzędu drugiego.<br />
<br />
==Metoda Żabiego Skoku== <br />
W poprzedniej metodzie liczyliśmy wartość funkcji f w chwili <math>t_n</math>, która była pochodną po czasie naszego ścisłego rozwiązania. Kolejny punkt <math>x_{n+1}</math> był liczony z przybliżenia liniowego funkcji w chwili poprzedniej. Jeżeli faktyczna trajektoria ma niezerową drugą pochodną to takie liniowe przybliżenie zawsze będzie nas oddalało od ścisłego rozwiązania. Dosyć prostym pomysłem na poprawienie zbieżności metody jest tak zwany żabi skok. Policzmy najpierw wartość zmiennej x przesuwając się w czasie o h/2 i policzmy wówczas pochodną, którą oznaczmy przez <math>k_2</math><br />
<br />
<math> k_1=f(t_n,x_n) </math>.<br />
<br />
<math> k_2=f(t_n+h/2,x_n+h/2*k_1) </math>.<br />
<br />
Następnie używamy pochodnej <math> k_2 </math> zamiast pochodnej <math> k_1 </math> do obliczenia wartości funkcji w kolejnym kroku czasowym.<br />
<br />
<math> x_{n+1} = x_n + h k_2 </math>.<br />
<br />
Przykładowa implementacja tej metody wygląda następująco<br />
<source lang="python"><br />
def leapfrog(f,x0,t0,tk,h): <br />
<br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
<br />
#index<br />
i=1<br />
<br />
#petla glowna<br />
while (i<N):<br />
k1=f(t[i-1],x[i-1])<br />
k2=f(t[i-1]+h*0.5,x[i-1]+0.5*h*k1)<br />
x[i]=np.array(x[i-1]+h*k2)<br />
i+=1<br />
return t,x<br />
</source><br />
Rozwiązanie równania oscylatora tą metodą dla identycznych jak poprzednio czasów da następujące wyniki<br />
<source lang="python"><br />
t,x=leapfrog(oscylator,[1.0,1.0],0.0,100,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:img3.png]]<br />
<br />
<source lang="python"><br />
t,x=leapfrog(oscylator,[1.0,1.0],0.0,1000,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:img4.png]]<br />
<br />
==Metoda Heuna==<br />
Kolejną metodą niewiele różniącą się od poprzedniej jest metoda Heuna. Zdefiniowana jest ona przez równania<br />
<math> k_1=f(t_n,x_n) </math>,<br />
<br />
<math> k_2=f(t_n+h/2,x_n+h/2*k_1) </math>,<br />
<br />
<math> x_{n+1} = x_n + h k_2 </math>.<br />
<br />
Implementacja wygląda następująco<br />
<br />
<source lang="python"><br />
<br />
def Heun(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
#liczba krokow czasowych<br />
N=len(t)<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
k1=f(t[i-1],x[i-1])<br />
k2=f(t[i-1]+h*0.5,x[i-1]+0.5*h*k1)<br />
x[i]=np.array(x[i-1]+h*0.5*(k1+k2))<br />
i+=1<br />
return t,x<br />
</source><br />
<br />
==Runge-Kutta czwartego rzędu==<br />
Ostatnią metodą, którą omówimy jest najbardziej popularna metoda zwana w skrócie RK4. Metoda ta uznawana jest za kanoniczną i w większości zastosowań dającą najlepsze wyniki. Metody wyższego rzędu nie wnoszą już do wyniku znaczącej poprawy. Jak sugeruje nazwa metody, jej rząd to 4, czyli w każdym kroku czasowym czterokrotnie wywołujemy funkcję f. Metoda ta zdefiniowana jest wzorami<br />
<br />
<math> k_1 = f \left( t_n, x_n \right) </math>,<br />
<br />
<math> k_2 = f \left( t_n + {h \over 2}, x_n + {1 \over 2} k_1 \right) </math>,<br />
<br />
<math> k_3 = f \left( t_n + {h \over 2}, x_n + {1 \over 2} k_2 \right) </math>,<br />
<br />
<math> k_4 = f \left( t_n + h, x_n + k_3 \right) </math>,<br />
<br />
<math> x_{n+1} = x_n + {h \over 6} (k_1 + 2k_2 + 2k_3 + k_4) </math>,<br />
<br />
a implementacja wygląda następująco<br />
<br />
<source lang="python"><br />
def RK4(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
#liczba krokow czasowych<br />
N=len(t)<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
k1=h*f(t[i-1],x[i-1])<br />
k2=h*f(t[i-1]+h*0.5,x[i-1]+0.5*k1)<br />
k3=h*f(t[i-1]+h*0.5,x[i-1]+0.5*k2)<br />
k4=h*f(t[i-1]+h,x[i-1]+k3)<br />
x[i]=np.array(x[i-1]+(k1+2.0*k2+2.0*k3+k4)/6)<br />
i+=1<br />
return t,x<br />
</source><br />
<br />
==Przykłady==<br />
===Zadanie - Wahadło matematyczne z tłumieniem i siłą wymuszającą===<br />
<br />
Rozwiąż numerycznie metodą RK4 równanie różniczkowe oscylatora harmonicznego z tłumieniem i siłą wymuszającą<br />
<br />
<math> \frac{d^2x}{dt^2} + \Gamma \frac{dx}{dt} + w_0^2 x = f_0 \cos(W t) </math>,<br />
<br />
przyjmując parametry <math> f_0 =1, w_0=1, \Gamma=0.1, h=0.1 </math>. Wykreśl zależność amplitudy drgań w funkcji częstości siły wymuszającej W, dla W z przedziału [0.1,3].<br />
===Rozwiązanie===<br />
Zacznijmy od sprowadzenia równania drugiego stopnia do równania pierwszego stopnia i zapisania go w postaci funkcji<br />
<br />
<source lang="python"><br />
def oscylator(t,y):<br />
f0=1.0<br />
w0=1.0<br />
Gamma=0.1<br />
<br />
x=y[0]<br />
xdot=y[1]<br />
return np.array([xdot,f0*np.cos(oscylator.W*t)-oscylator.Gamma*xdot-w0*w0*x])<br />
<br />
oscylator.W=1.0<br />
</source><br />
Nieprzypadkowo parametr W nie jest definiowany w samej funkcji jako zmienna wewnętrzna, ale jako atrybut obiektu jakim jest funkcja. Dzięki takiej konstrukcji łatwo będzie nam zmieniać parametr W, czyli częstość siły wymuszającej. Zobaczmy jak wygląda trajektoria będąca rozwiązaniem tego równania dla warunku początkowego [0,1] i czasu końcowego równego 400.<br />
<source lang="python"><br />
py.plot(*RK4(oscylator,[0.0,1.0],0.0,400.0,0.1))<br />
py.show()<br />
</source><br />
<br />
[[Plik:oscy1.png]]<br />
<br />
Widać, że początkowo układ dochodzi do stanu regularnych oscylacji. W analizie amplitudy interesuje nas jedynie końcowa część więc ograniczymy się do analizy trajektorii od czasu 200 do czasu 400. Napiszmy teraz funkcję, która na podstawie trajektorii wyznaczy nam amplitudę oscylacji<br />
<br />
<source lang="python"><br />
def amplituda(x):<br />
lista=x[2000:,0]<br />
return (max(lista)-min(lista))*0.5<br />
</source><br />
<br />
Interesować nas będzie amplituda drgań w funkcji częstość W. Wygenerujmy listę wartości W, dla których będziemy liczyć amplitudę.<br />
<br />
<source lang="python"><br />
Omegas=np.arange(0.1,3.0,0.05)<br />
</source><br />
Możemy teraz dla każdej z wartości W rozwiązać numerycznie równanie różniczkowe i wyznaczyć odpowiadającą amplitudę oscylacji<br />
<source lang="python"><br />
amp=[amplituda(RK4(oscylator,[0.0,1.0],0.0,400.0,0.1)[1]) for oscylator.W in Omegas]<br />
</source><br />
Wynik końcowy wygląda następująco:<br />
<source lang="python"><br />
py.plot(Omegas,amp)<br />
py.show()<br />
</source><br />
[[Plik:oscy2.png]]<br />
<br />
Jak można było się domyślić amplituda jest największa gdy częstotliwość wymuszania W pokrywa się z wartością częstotliwości drgań własnych <math> w_0=1 </math><br />
<br />
===Zadanie - Układ Lorenza===<br />
Rozwiąż układ równań różniczkowych Lorenza dany wzorami<br />
<br />
<math> \begin{cases}\dot x=\sigma y-\sigma x\\\dot y=-xz+rx-y\\\dot z=xy-bz\end{cases}, </math> <br />
<br />
metodą całkowania Rungego–Kutty drugiego rzędu z α = 2/3, czyli<br />
<br />
<math>k_1 = f(t_n,x_n)</math> , <br />
<br />
<math>k_2 = f(t_n + \tfrac{2}{3}h, x_n + \tfrac{2}{3}h k_1)</math> , <br />
<br />
<math>x_{n+1} = x_n + h \left(\tfrac{1}{4}k_1+\tfrac{3}{4} k_2 \right). </math> <br />
<br />
Przyjmij sigma=10, b=8/3, r=99.96, krok czasowy h=0.005 i warunki początkowe x=1,y=0,z=0. Wykonaj 8000 kroków czasowych. Układ po pewnym czasie zacznie poruszać się po pewnej periodycznej trajektorii. Wykonaj 3 rysunki TEJ PERIODYCZNEJ TRAJEKTORII (bez okresu dochodzenia do niej) w płaszczyznach (x,y), (y,z) i (z,x). Wypisz na ekranie przedziały wartości jakie przyjmują zmienne x,y i z na periodycznej trajektorii oraz okres trajektorii periodycznej.<br />
===Rozwiązanie===<br />
Zacznijmy od implementacji podanej w treści zadania metody całkowania RK2<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
#rozwiazujemy rownanie dx(t)/dt=f(t,x)<br />
<br />
#metoda Explicit Euler<br />
#f - funkcja f z rownania<br />
#x0-wartosc poczatkowa<br />
#t0-czas poczatkowy<br />
#tk-czas koncowy<br />
#h-krok czasowy<br />
<br />
def RK2(f,x0,t0,tk,h): <br />
<br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
<br />
#index<br />
i=1<br />
<br />
#petla glowna<br />
while (i<N):<br />
k1=h*f(t[i-1],x[i-1])<br />
k2=h*f(t[i-1]+h*2.0/3.0,x[i-1]+k1*2.0/3.0)<br />
x[i]=np.array(x[i-1]+0.25*k1+0.75*k2)<br />
i+=1<br />
return t,x<br />
</source><br />
Następnie napiszmy funkcję opisującą układ Lorenza<br />
<source lang="python"><br />
def Lorenza(t,y):<br />
sigma=10.0<br />
b=8.0/3<br />
r=99.96<br />
xx=y[0]<br />
yy=y[1]<br />
zz=y[2]<br />
xdot=sigma*(yy-xx)<br />
ydot=-xx*zz+r*xx-yy<br />
zdot=xx*yy-b*zz<br />
return np.array([xdot,ydot,zdot])<br />
</source><br />
<br />
Zobaczmy teraz jak wyglądają trajektorie wszystkich trzech współrzędnych rozwiązania z zadanymi parametrami<br />
<source lang="python"><br />
t,x=RK2(Lorenza,[1.0,0.0,0.0],0.0,40.0,0.005)<br />
py.plot(t,x[:,1])<br />
py.show()<br />
py.plot(t,x[:,2])<br />
py.show()<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:lor1.png]]<br />
<br />
[[Plik:lor2.png]]<br />
<br />
[[Plik:lor3.png]]<br />
<br />
Możemy zauważyć, że układ początkowo zachowuje się chaotycznie, a potem dąży do pewnego stanu ustalonego (tzw. atraktora). Cała trajektoria składa się z 8000 punktów, przyjmijmy że powyżej punktu o numerze 2500 mamy już do czynienia tylko z periodyczną trajektorią. Wykreślmy zatem portrety fazowe, o których mowa w treści zadania<br />
<source lang="python"><br />
py.plot(x[2500:,0],x[2500:,1])<br />
py.show()<br />
py.plot(x[2500:,1],x[2500:,2])<br />
py.show()<br />
py.plot(x[2500:,2],x[2500:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:lor4.png]]<br />
<br />
[[Plik:lor5.png]]<br />
<br />
[[Plik:lor6.png]]<br />
<br />
Wypisanie zakresów jest już tylko formalnością<br />
<source lang="python"><br />
print 'zmienna x przyjmuje wartosci z zakresu: (',min(x[2500:,0]),',',max(x[2500:,0]),')'<br />
print 'zmienna y przyjmuje wartosci z zakresu: (',min(x[2500:,1]),',',max(x[2500:,1]),')'<br />
print 'zmienna z przyjmuje wartosci z zakresu: (',min(x[2500:,2]),',',max(x[2500:,2]),')'<br />
</source><br />
Okres trajektorii periodycznej możemy znaleźć na przykład w ten sposób<br />
<source lang="python"><br />
prog=140<br />
lista=[]<br />
for i in range(2500,8000):<br />
if (x[i-1,2]<prog) and (x[i,2]>prog): lista.append(i)<br />
print 'okres to:',np.mean(np.diff(lista)*0.005)<br />
<br />
>>> okres to: 1.0975<br />
>>> zmienna x przyjmuje wartosci z zakresu: ( -33.4431203059 , 25.8037953495 )<br />
>>> zmienna y przyjmuje wartosci z zakresu: ( -56.7169238157 , 37.3166709986 )<br />
>>> zmienna z przyjmuje wartosci z zakresu: ( 53.4652816712 , 144.264397579 )<br />
<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Optymalizacja&diff=3615TI/Programowanie dla Fizyków Medycznych/Optymalizacja2015-06-09T12:49:14Z<p>Tgubiec: </p>
<hr />
<div>==Optymalizacja jednowymiarowa==<br />
Omawianie zagadnienia optymalizacji rozpocznijmy od prostego przykładu. Zdefiniujmy pewną funkcję i zobaczmy jak wygląda jej wykres.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
licznikTestowej=0<br />
<br />
def testowa(x):<br />
global licznikTestowej<br />
licznikTestowej+=1<br />
return 1/x+np.exp(x)<br />
<br />
xtest=np.arange(0.2,2,0.01)<br />
ytest=[testowa(x) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
[[Plik:opt1.png]]<br />
<br />
Na rozważanym przedziale [0.2,2] powyższa funkcją ma tylko jedno ekstremum lokalne. Taką funkcję nazywamy unimodalną. Zmienna licznikTestowej umożliwi nam zliczać wywołania funkcji testowej przez analizowane procedury.<br />
Zagadnienie którym teraz będziemy się zajmować to problem numerycznego znajdowania takiego ekstremum. Jak każdy problem numeryczny ekstremum szukać będziemy zakładając pewną dokładność otrzymanego wyniku, którą oznaczmy xtol. Na wstępie przyjmijmy że poszukujemy ekstremum z dokładnością xtol=0.01. Najporstszą metodą będzie policzenie wartości funkcji dla wszystkich wartościx z podanego przedziału co xtol. Jest to metoda siłowa i wielokrotnie licząca wartość funkcji. Jej kod możemy znaleść poniżej. <br />
<source lang="python"><br />
def bruteForce(func,xmin,xmax,args=(),xtol=0.01):<br />
xlist=np.arange(xmin,xmax,xtol)<br />
ylist=[func(x,*args) for x in xlist]<br />
return xlist[ylist.index(max(ylist))]<br />
</source><br />
aaa<br />
<source lang="python"><br />
#tutaj lepiej bez stopera!<br />
def twoMidPointsR(func,xmin,xmax,args=(),xtol=0.01):<br />
if xmax-xmin<xtol: return 0.5*(xmax+xmin)<br />
xL=xmin+(xmax-xmin)/3.0<br />
xR=xmax-(xmax-xmin)/3.0<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
if fxL>fxR:<br />
return twoMidPointsR(func,xmin,xR,args,xtol)<br />
else:<br />
return twoMidPointsR(func,xL,xmax,args,xtol)<br />
<br />
#szuka maximum funkcji<br />
@stoper<br />
def GoldenRatioRearch(func,xmin,xmax,args=(),xtol=0.01):<br />
golden=0.5*(np.sqrt(5.0)-1.0)<br />
xL=xmax-golden*(xmax-xmin)<br />
xR=xmin+golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
fxR=func(xR,*args)<br />
while xmax-xmin>xtol: <br />
if fxL<fxR:<br />
xmin=xmin<br />
xmax=xR<br />
xR=xL<br />
fxR=fxL<br />
xL=xmax-golden*(xmax-xmin)<br />
fxL=func(xL,*args)<br />
else:<br />
xmin=xL<br />
xmax=xmax<br />
xL=xR<br />
fxL=fxR<br />
xR=xmin+golden*(xmax-xmin)<br />
fxR=func(xR,*args)<br />
return 0.5*(xmax+xmin)<br />
<br />
<br />
#test metody zlotego podzialu<br />
#print GoldenRatioRearch(testowa,0,5,xtol=0.000001)<br />
#print so.fmin(testowa,np.array([1]))[0]<br />
<br />
#przypadek z jednym parametrem<br />
def squares(a,func,xlist,ylist):<br />
return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
<br />
#funkcja liniowa<br />
def liniowa(x,a): return x*a<br />
<br />
#funkcja kwadratowa<br />
def kwadratowa(x,a,b=0): return a*x*x+b*x<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
#generujemy wartosci funkcji z szumem<br />
ylist=[kwadratowa(x,1.23,-0.73)+0.000001*np.random.randn() for x in xlist]<br />
#py.plot(xlist,ylist)<br />
#py.show()<br />
<br />
#lista przeszukiwanych wartosci parametru a<br />
#alist=np.arange(0,10,0.01)<br />
#wartosci squares dla tych parametrow<br />
#squareslist=[squares(a,liniowa,xlist,ylist)for a in alist]<br />
#zobaczmy wykres<br />
#py.plot(alist,squareslist)<br />
#py.show()<br />
<br />
#najlepsze dopasowanie metoda brute force<br />
#print bruteForce(squares,0,10,args=(liniowa,xlist,ylist),xtol=0.01)<br />
<br />
#najlepsze dopasowanie metoda golden ration funkcji z 1 parametrem<br />
#print GoldenRatioRearch(squares,0,10,args=(liniowa,xlist,ylist),xtol=0.01)<br />
print so.fmin(squares,(1),args=(liniowa,xlist,ylist))<br />
<br />
#najlepsze dopasowanie metoda symplexowa z dwoma parametrami<br />
print so.fmin(squares,(1,0),args=(kwadratowa,xlist,ylist))<br />
</source><br />
<br />
==Optymalizacja wielowymiarowa==<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
import scipy.optimize as so<br />
<br />
#def squares(a,func,xlist,ylist):<br />
# return sum([(func(xlist[i],*a)-ylist[i])**2 for i in range(len(xlist))])<br />
#print so.fmin(squares,(1,0),args=(kwadratowa,xlist,ylist))<br />
def rho_cauchy(x,loc,scale):<br />
return (np.pi*scale*(1.0+(x-loc)**2/(scale**2)))**(-1.0)<br />
<br />
def F_cauchy(x,loc,scale):<br />
return 0.5+np.arctan((x-loc)*1.0/scale)/np.pi<br />
<br />
<br />
#losujemy 10000 liczb z rozkladu Caychyego o loc=1.23 i scale=2.0<br />
x=2*np.random.standard_cauchy(10000)+1.23<br />
N=len(x)<br />
<br />
#METODA 1 - Dopasowanie metoda najmniejszych kwadratow do histogramu<br />
<br />
#tworzymy histogram<br />
hist,bins= np.histogram(x,bins=np.linspace(-20,20,61))<br />
#dlugosc przedzialu histogramowania<br />
przedzial=bins[1]-bins[0]<br />
#normalizujemy histogram aby moc go porownac z gestoscia<br />
hist=hist*1.0/len(x)/przedzial<br />
#liczymy wsp. srodkow przedzialow histogramowania<br />
xhist=bins[:-1]+0.5*przedzial<br />
#definiujemy sume kwadratow<br />
def squares((loc,scale)):<br />
return sum([(rho_cauchy(xhist[i],loc,scale)-hist[i])**2 for i in range(len(hist))])<br />
#szukamy minimum funkcja fmin<br />
fit1=tuple(so.fmin(squares,(0,1)))<br />
print 'wynik metody1 to '+str(fit1)<br />
#ogladamy wynik<br />
py.plot(xhist,hist)<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[rho_cauchy(a,*fit1) for a in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
<br />
<br />
#METODA 2 - Metoda najwiekszej wiarygodnosci<br />
<br />
#definiujemy -funkcje wiarygodnosci<br />
def L((loc,scale)):<br />
return -sum([np.log(rho_cauchy(a,loc,scale)) for a in x])<br />
#szukamy minimum<br />
fit2=tuple(so.fmin(L,(0,1)))<br />
print 'wynik metody2 to '+str(fit2)<br />
<br />
<br />
#METODA 3 - dopasowanie dystrybuant<br />
<br />
xx=sorted(x)<br />
yy=np.linspace(0,1,N)<br />
#definiujemy funkcje KS bedaca maksimum z roznicy miedzy dystrybuanta empiryczna a teoretyczna<br />
def KS((loc,scale)):<br />
return max([abs(F_cauchy(xx[i],loc,scale)-yy[i]) for i in xrange(N)])<br />
#szukamy minimum<br />
fit3=tuple(so.fmin(KS,(0,1)))<br />
print 'wynik metody3 to '+str(fit3)<br />
#ogladamy wynik<br />
cut=100<br />
py.plot(xx[cut:-cut],yy[cut:-cut])<br />
xtest=np.linspace(-20,20,1001)<br />
ytest=[F_cauchy(x,*fit3) for x in xtest]<br />
py.plot(xtest,ytest)<br />
py.show()<br />
</source><br />
<br />
===Zadanie===<br />
Napisz data container...<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
import time<br />
import scipy.optimize as so<br />
<br />
class funkcja(object):<br />
def __init__(self,*args):<br />
self.args=args<br />
def __str__(self):<br />
return 'to jest funkcja o nazwie '+self.__class__.__name__+' i argumentach '+str(self.args)<br />
<br />
class liniowa(funkcja):<br />
def __call__(self,x):<br />
return self.args[0]*x*x+self.args[1]<br />
<br />
class DataContainer(object):<br />
def __init__(self,x,y):<br />
self.x=np.array(x)<br />
self.y=np.array(y)<br />
self.n=len(x)<br />
def __str__(self):<br />
return '''to jest Data Container z danymi:<br />
x[:10]:'''+str(self.x[:10])+'''<br />
y[:10]:'''+str(self.y[:10])<br />
<br />
def fit(self,funkcja):<br />
parametry_poczatkowe=funkcja.args<br />
def squares(parametry):<br />
funkcja.__init__(*tuple(parametry))<br />
return sum((map(funkcja,self.x)-self.y)**2)<br />
parametry_dopasowane=so.fmin(squares,parametry_poczatkowe)<br />
funkcja.__init__(*tuple(parametry_dopasowane))<br />
return funkcja<br />
<br />
<br />
#generujemy przykladowe xlist<br />
xlist=np.arange(0,1,0.001)<br />
#generujemy wartosci funkcji z szumem<br />
f=liniowa(1.23,-0.73)<br />
ylist=[f(x)+0.05*np.random.randn() for x in xlist]<br />
<br />
d=DataContainer(xlist,ylist)<br />
f=liniowa(1,2)<br />
f=d.fit(f)<br />
py.plot(d.x,d.y)<br />
py.plot(d.x,map(f,d.x))<br />
py.show()<br />
<br />
<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Morfologia_matematyczna&diff=3614TI/Programowanie dla Fizyków Medycznych/Morfologia matematyczna2015-06-09T12:37:20Z<p>Tgubiec: /* Morfologia Matematyczna */</p>
<hr />
<div>==Morfologia Matematyczna==<br />
Morfologia matematyczna to bardzo przydatna metoda przetwarzania obrazów binarnych (czarno-białych), pozwalająca na analizę i "upraszczanie" obserwowanych kształtów. W szczególności można ją zastosować do obrazów medycznych przetworzonych przed progowanie. Metody morfologii matematycznej pozwalają także na uspójnienie otrzymanego obrazu nie zmieniając jego rozmiarów zewnętrznych co może być bardzo przydatne przy dokonywaniu pomiarów w oparciu o cyfrowy obraz medyczny. Dla osób zainteresowanych bardziej formalną definicją poszczególnych operacji polecam bardzo szeroką literaturę dostępną w internecie. Tutaj przedstawimy definicję pozwalającą na łatwiejsze zrozumienie istoty działania poszczególnych operacji. Operacje morfologii matematycznej opierają się na tak zwanym elemencie strukturalnym, który my w uproszczeniu nazywać będziemy pędzlem (brush). Zacznijmy od zdefiniowania naszego obrazu roboczego i przykładowego pędzla.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
a=np.zeros((100,100),dtype=np.bool)<br />
a[30:50,30:50]=True<br />
a[50:70,50:70]=True<br />
<br />
brush7=np.array([[0,0,1,1,1,0,0],[0,1,1,1,1,1,0],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[0,1,1,1,1,1,0],[0,0,1,1,1,0,0]],dtype=np.bool)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia1.png]]<br />
<br />
W definiowaniu funkcji morfologii matematycznej przydatna będzie procedura zmieniająca pędzel, będący kwadratową tablicą zer i jedynek na listę wektorów mających początek w środku pędzla i końce we wszystkich komórkach posiadających wartość 1. Kod takiej procedury przedstawia się następująco.<br />
<source lang="python"><br />
def brush2list(brush):<br />
result=[]<br />
N=brush.shape[0]<br />
middle=N/2<br />
for x in range(N):<br />
for y in range(N):<br />
if brush[x,y]: result.append((x-middle,y-middle))<br />
return result<br />
</source><br />
<br />
===Dylacja===<br />
Pierwszą operacją którą omówimy jest dylacja. Nasz obraz w chwili obecnej składa się z tła zer (w kolorze czarnym) i dwóch kwadratów z jedynek (w kolorze białym). Wyobraźmy sobie, że nasz pędzel służy do malowania po obrazku. Jeżeli w jakimś miejscu przyłożymy środek pędzla, to wszystkie pixele, na których znajdą się jedynki w pędzlu zmienią swoją wartość na jeden. Operacja dylacji to przyłożenie środka pędzla do wszystkich komórek, których początkowa wartość to jeden. Równoważna jest także inna definicja. Przykładamy środek pędzla po kolei do wszystkich pixeli. Tworzymy listę wartości pixeli obrazu które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą '''maksimum''' z tej list. Implementacja drugiej z tych definicji znajduje się poniżej.<br />
<source lang="python"><br />
def dylacja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=max([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(dylacja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-dylacja.png]]<br />
<br />
W efekcie dwa kwadraty powiększyły się i zaokrągliły im się rogi.<br />
===Erozja===<br />
Drugą operacją morfologii matematycznej jest erozja. Operacja jest analogiczna, z tą zmianą, że teraz nasz pędzel maluje na czarno i przesuwamy go po czarnych pixelach. Przy alternatywnej definicji przykładamy środek pędzla po kolei do wszystkich pixeli. Tworzymy listę wartości pixeli obrazu które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą '''minimum''' z tej list. Implementacja drugiej z tych definicji znajduje się poniżej.<br />
<source lang="python"><br />
def erozja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=min([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(erozja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-erozja.png]]<br />
<br />
W wyniku tej operacji kwadraty nie zmieniły swojego kształtu, za to zmniejszyły się i powstała między nimi przerwa.<br />
<br />
===Otwarcie i zamknięcie===<br />
Skoro dylacja powiększa rozmiar naszego obrazu a erozja zmniejsza, naturalnym jest postawienie sobie pytania, co się stanie gdy obraz najpierw potraktujemy erozją a potem dylacją lub też odwrotnie. Operacje powstałe właśnie w ten sposób nazywamy otwarciem i zamknięciem. Ich definicja nie jest już problematyczna posiadając implementacje wcześniejszych metod.<br />
<source lang="python"><br />
def otwarcie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return dylacja(erozja(fig,brush),brush)<br />
<br />
def zamkniecie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return erozja(dylacja(fig,brush),brush)<br />
<br />
py.imshow(otwarcie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
py.imshow(zamkniecie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-otwarcie.png]]<br />
<br />
[[Plik:morfologia-zakmniecie.png]]<br />
<br />
Wyniki powyższych dwóch operacji są najbardziej przydatne w zastosowaniach medycznych. W obu przypadkach rozmiary obrazu nie zmieniły się. W przypadku otwarcia rogi kwadratów zaokrągliły się i stykające się kwadraty stały się wyraźniej rozłączne. W przypadku zamknięcia obszar tworzony przez dwa kwadraty został uspójniony tworząc coś w rodzaju "mostu" między nimi. Przy zastosowaniu zamknięcia wszystkie obiekty odległe od siebie o mniej niż średnica pędzla zostaną połączone.<br />
===Filtr medianowy===<br />
W poprzednich metodach przykładaliśmy środek pędzla po kolei do wszystkich pixeli, tworzyliśmy listę wartości pixeli obrazu, które w danym ułożeniu pędzla odpowiadają wartościom 1 na pędzlu. Do pixela, w którym znajduje się środek pędzla przypisujemy wartość będącą minimum lub maksimum z tej list. Co się stanie gdy na wartościach z tej listy wykonamy jakieś inne operacje? Wzięcie średniej, jak wspominałem w poprzednim rozdziale, doprowadzi do rozmycia obrazka (średnia nie musi być zerem lub jedynką). Bardzo przydatną operację uzyskamy, gdy weźmiemy '''medianę''' z wartości w tej liście. Powstanie w ten sposób tak zwany filtr medianowy, który jest doskonałym narzędziem do usuwania szumu z obrazka. Jego implementacja wygląda na przykład tak.<br />
<source lang="python"><br />
def medianowy(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=np.median([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
</source><br />
Aby przetestować działanie filtra medianowego musimy "zaszumić" nasz oryginalny obrazek<br />
<source lang="python"><br />
for x,y in np.ndindex(a.shape):<br />
if (np.random.random()<0.05): a[x,y]=False<br />
if (np.random.random()>0.95): a[x,y]=True<br />
</source><br />
Zobaczmy teraz jakie wyniki da wielokrotne zastosowanie filtra medianowego.<br />
<source lang="python"><br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-zaszumiony1.png]]<br />
<br />
[[Plik:morfologia-zaszumiony2.png]]<br />
<br />
[[Plik:morfologia-zaszumiony3.png]]<br />
<br />
[[Plik:morfologia-zaszumiony4.png]]<br />
<br />
Jak widać udało się odtworzyć kształty widoczne nawet na obrazku bardzo słabej jakości. Operacje takie pozwalają znacznie poprawić jakość wyników uzyskiwanych poprzez progowanie obrazów medycznych.<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Manipulacja_obrazem&diff=3613TI/Programowanie dla Fizyków Medycznych/Manipulacja obrazem2015-06-09T12:31:48Z<p>Tgubiec: /* Manipulacja obrazem */</p>
<hr />
<div>==Manipulacja obrazem==<br />
Ćwiczenia z mainuplacji obrazem rozpocznijmy od wczytania pliku, który będziemy przetwarzać. Dla fizyków medycznych naturalnie będzie to plik DICOM.<br />
<source lang="python"><br />
import dicom<br />
import numpy as np<br />
import pylab as py<br />
<br />
plik=dicom.read_file('I00001.dcm')<br />
pixel=plik.pixel_array<br />
print pixel.shape<br />
>>>(2964, 2364)<br />
</source><br />
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.<br />
<source lang="python"><br />
pixel=pixel[::5,::5]<br />
print min(pixel.flatten()),max(pixel.flatten())<br />
>>> 0 3907<br />
</source><br />
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].<br />
<source lang="python"><br />
pixel=pixel*1.0/max(pixel.flatten())<br />
</source><br />
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<br />
<source lang="python"><br />
def dodajPasek(tablica, n):<br />
tablica[:n,:]=np.linspace(0,1,tablica.shape[1])<br />
dodajPasek(pixel,30)<br />
py.imshow(pixel,cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:dicom2.png]]<br />
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.<br />
===Jasność===<br />
Najprostszą funkcją, od której na Wydziale Fizyki zaczynamy zawsze 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.<br />
<source lang="python"><br />
def jasnosc(tablica, a):<br />
def f(x,a):<br />
return min(a*x,1.0)<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
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.<br />
Zobaczmy teraz na dwóch przykładach w jaki sposób funkcja jasność modyfikuje obraz<br />
<source lang="python"><br />
py.imshow(jasnosc(pixel,2),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(jasnosc(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:jasnosc1.png]]<br />
<br />
[[Plik:jasnosc2.png]]<br />
<br />
===Gamma===<br />
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.<br />
<br />
<source lang="python"><br />
def gamma(tablica, a):<br />
def f(x,a):<br />
return min(x**a,1.0)<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Możemy teraz na dwóch przykładach obejrzeć efekt działania przekształcenia gamma.<br />
<source lang="python"> <br />
py.imshow(gamma(pixel,2),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(gamma(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:gamma1.png]]<br />
<br />
[[Plik:gamma2.png]]<br />
<br />
===Próg===<br />
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 1, poniżej na zero. Przykładowa implementacja tej metody przedstawiona jest poniżej. <br />
<source lang="python"><br />
def prog(tablica, a):<br />
def f(x,a):<br />
return 0 if x<a else 1<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Natomiast jej działanie na obraz wygląda następująco<br />
<source lang="python"><br />
py.imshow(prog(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
[[Plik:prog.png]]<br />
<br />
===Schodek===<br />
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 <br />
<source lang="python"><br />
def schodek(tablica, a,b):<br />
def f(x,a,b):<br />
return 1 if ((x<a) or (x>b)) else 0<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a,b)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
A przyklad działania tutaj.<br />
<source lang="python"><br />
py.imshow(schodek(pixel,0.2,0.6),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:schodek.png]]<br />
<br />
===Rozmycie i wyostrzenie===<br />
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 pixeli przy liczeniu średniej liczone są z dwuwymiarowego rozkładu gaussa o zadanej dyspersji.<br />
Chętnym pozostawiam napisanie takiej procedury samodzielnie, natomiast my skorzystamy z gotowej procedury z pakiecie scipy.ndimage.<br />
<source lang="python"><br />
from scipy import ndimage<br />
imag=ndimage.gaussian_filter(pixel,sigma=4)<br />
print imag<br />
</source><br />
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.<br />
<source lang="python"><br />
def wyostrz(tablica, sigma, a):<br />
rozmyty=np.array(ndimage.gaussian_filter(tablica,sigma=sigma))<br />
roznica=tablica-rozmyty<br />
wynik=rozmyty+a*roznica<br />
wynik/=max(wynik.flatten())<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Wynik działania dla przykładowych wartości parametru a oraz sigma=4 przedstawione są poniżej<br />
<source lang="python"><br />
py.imshow(wyostrz(pixel,4,0),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(wyostrz(pixel,4,3),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:blur.png]]<br />
<br />
[[Plik:sharp.png]]<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Obs%C5%82uga_plik%C3%B3w_graficznych_i_DICOM&diff=3612TI/Programowanie dla Fizyków Medycznych/Obsługa plików graficznych i DICOM2015-06-09T12:16:56Z<p>Tgubiec: /* Obsługa plików DICOM - pyDICOM */</p>
<hr />
<div>==Wczytywanie plików graficznych ==<br />
Pliki graficzne w większości najpopularniejszych formatów można otworzyć w pythonie korzystając z PIL (Python Image Library). Można to zrobić następującym poleceniem.<br />
<source lang="python"><br />
from PIL import Image<br />
import numpy as np<br />
import pylab as py<br />
obraz=Image.open('logo2.bmp')<br />
obraz.show()<br />
</source><br />
Jak widać wczytany obiekt nie jest tablicą numpy, ma za to na przykład własną metodę pozwalającą na wyświetlanie wczytanego pliku. Dla naszej wygody możemy jednak przetworzyć tak wczytany plik do postaci tablicy.<br />
<source lang="python"><br />
im = np.asarray(obraz)<br />
py.imshow(im,cmap = py.cm.gray, interpolation = 'nearest')<br />
py.show()<br />
</source><br />
<br />
==Obsługa plików DICOM - pyDICOM==<br />
Format plików DICOM, czyli Digital Imaging and Communications in Medicine (Obrazowanie Cyfrowe i Wymiana Obrazów w Medycynie) to norma opracowana przez ACR/NEMA (American College of Radiology / National Electrical Manufacturers Association). Do obsługi plików tego typu służy między innymi biblioteka pyDICOM. Za jej pomocą wczytujemy przykładowy plik dostępny tutaj [[Media:I00001.dcm]] następującym poleceniem<br />
<source lang="python"><br />
import dicom<br />
plik=dicom.read_file('I00001.dcm')<br />
</source><br />
Plik DICOM poza danymi samego obrazu medycznego zawiera wiele dodatkowych informacji na temat przeprowadzonego badania.<br />
Listę dostępnych danych możemy uzyskać poleceniem<br />
<source lang="python"><br />
print plik.dir()<br />
>>> ['AccessionNumber', 'AcquisitionDate', 'AcquisitionDeviceProcessingCode', 'AcquisitionDeviceProcessingDescription', 'AcquisitionNumber', 'AcquisitionTime', 'AnnotationDisplayFormatID', 'BitsAllocated', 'BitsStored', 'BodyPartExamined', 'BorderDensity', 'Columns', 'ContentDate', 'ContentTime', 'ContrastBolusAgent', 'DerivationDescription', 'FilmOrientation', 'HighBit', 'ImageDisplayFormat', 'ImageType', 'ImagerPixelSpacing', 'InstanceNumber', 'InstitutionName', 'Laterality', 'LossyImageCompression', 'Manufacturer', 'Modality', 'NumberOfPoints', 'PatientBirthDate', 'PatientID', 'PatientName', 'PatientOrientation', 'PatientSex', 'PerformedProcedureStepDescription', 'PerformedProcedureStepID', 'PhotometricInterpretation', 'PixelData', 'PixelRepresentation', 'PixelSpacing', 'PlateID', 'PositionerType', 'RefdStudySequence', 'ReferencedStudySequence', 'ReferringPhysicianName', 'RequestAttributesSequence', 'RequestingService', 'RescaleIntercept', 'RescaleSlope', 'RescaleType', 'Rows', 'SOPClassUID', 'SOPInstanceUID', 'SamplesPerPixel', 'Sensitivity', 'SeriesDate', 'SeriesInstanceUID', 'SeriesNumber', 'SeriesTime', 'SpecificCharacterSet', 'StationName', 'StudyDate', 'StudyDescription', 'StudyID', 'StudyInstanceUID', 'StudyTime', 'Trim', 'ViewPosition']<br />
</source><br />
Podana lista zawiera pola, które posiada wczytany obiekt. W ten sposób mamy dostęp na przykład do nazwiska pacjenta czy daty badania.<br />
<source lang="python"><br />
print plik.PatientsName<br />
print plik.AcquisitionDate<br />
>>>GUBIEC^TOMASZ<br />
>>>20141119<br />
</source><br />
Dane te możemy dodatkowo edytować.<br />
<source lang="python"><br />
plik.PatientsName='Jan Kowalaski'<br />
print plik.PatientsName<br />
>>>Jan Kowalaski<br />
</source><br />
Najciekawsze jednak dla nas będą dane opisujące sam obraz. Możemy uzyskać do nich dostęp w wygodnej dla nas postaci tablicy numpy.<br />
<source lang="python"><br />
pixel=plik.pixel_array<br />
print pixel.shape<br />
print min(pixel.flatten()),max(pixel.flatten())<br />
py.imshow(pixel,cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
>>>(2964, 2364)<br />
>>> 0 3908<br />
</source><br />
[[Plik:dicom1.png]]<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/%C4%86wiczenia_z_przetwarzania_tablic_2D&diff=3611TI/Programowanie dla Fizyków Medycznych/Ćwiczenia z przetwarzania tablic 2D2015-06-09T12:15:22Z<p>Tgubiec: /* Gra w życie Conwaya */</p>
<hr />
<div>==Ćwiczenia z przetwarzania tablic 2D==<br />
===Wyświetlanie tablic 2D===<br />
Jako wstęp do przetwarzania obrazów w pythonie przećwiczmy podstawowe operacje na dwuwymiarowych tablicach numpy w postaci których będziemy takie obrazy przechowywać. Zacznijmy od zdefiniowania przykładowej tablicy, która będzie reprezentować obrazek.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
a=np.zeros((100,100),dtype=np.bool)<br />
a[30:50,30:50]=True<br />
a[50:70,50:70]=True<br />
</source><br />
Stworzyliśmy w ten sposób tablice 100 na 100 z wpisanymi wartościami False oraz w środku dwa kwadraty o boku 20 z wartościami True. Już tak stworzoną tablicę możemy wyświetlić za pomocą pylaba.<br />
<source lang="python"><br />
py.imshow(a)<br />
py.show()<br />
</source><br />
<br />
[[Plik:pil1.png]]<br />
<br />
Nie do końca można było się spodziewać, że pylab wyświetli wartości False w kolorze granatowym, a wartości True w kolorze bordowym. Za przyporządkowanie kolorów określonym wartościom odpowiada tzw. mapa kolorów, którą podajemy jako parametr w poleceniu imshow. Aby uzyskać bardziej intuicyjną skalę szarości należy wywołać polecenie następująco.<br />
<source lang="python"><br />
py.imshow(a,cmap = py.cm.gray)<br />
py.show()<br />
</source><br />
<br />
[[Plik:pil2.png]]<br />
<br />
Teraz kolory (a raczej ich brak) są takie jakich można było się spodziewać. Zaskakujące jest za to rozmycie brzegów kwadratów. Obrazek wyświetlany jest w większych rozmiarach niż jego domyślne 100 na 100 pixeli. Pylab aby wyświetlić go w wyższej rozdzielczości musi zwiększyć jego rozmiary. Rozciągając obrazek można w różny sposób przypisywać wartości pixelom, których nie było w pierwotnym obrazku. Proces ten nazywamy interpolacją i aby uzyskać "ostre" krawędzie należy znów użyć odpowiedniej opcji metody imshow.<br />
<source lang="python"><br />
py.imshow(a,cmap = py.cm.gray, interpolation = 'nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:pil3.png]]<br />
<br />
=== Gra w życie Conwaya ===<br />
W ramach ćwiczeń zaprogramujmy klasyczny automat komórkowy zwany grą w życie Conwaya.<br />
W naszej wersji gra toczy się na skończonej planszy o wymiarach 100 na 100 podzielonej na kwadratowe komórki. Każda komórka (poza brzegowymi) ma ośmiu sąsiadów, czyli komórki przylegające do niej bokami i rogami. Każda komórka może znajdować się w jednym z dwóch stanów: może być albo "żywa" (stanowi przypisujemy wartość 1), albo "martwa" (wartość 0). Stan wszystkich komórek w pewnej jednostce czasu jest używany do obliczenia stanu wszystkich komórek w następnej jednostce. Po obliczeniu wszystkie komórki zmieniają swój stan jednocześnie. Stan komórki zależy tylko od liczby jej żywych sąsiadów w następujący sposób<br />
*Martwa komórka, która ma dokładnie 3 żywych sąsiadów, staje się żywa w następnej jednostce czasu (rodzi się)<br />
*Żywa komórka z 2 albo 3 żywymi sąsiadami pozostaje żywa, przy innej liczbie sąsiadów umiera<br />
<br />
Zacznijmy od zdefiniowania planszy gry<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
a=np.zeros((100,100))<br />
</source><br />
Kluczową i najtrudniejszą funkcją jest funkcja zliczająca liczbę sąsiadów danej komórki. Problematyczne jest tutaj uwzględnienie przypadku komórek znajdujących się na rogach, które mają po trzech sąsiadów, oraz komórek brzegowych mających po 5 sąsiadów. Przykładowe rozwiązanie wygląda następująco.<br />
<source lang="python"><br />
def ile_sasiadow(x,y,macierz):<br />
return np.sum(macierz[max(0,x-1):min(macierz.shape[0],x+2),max(0,y-1):min(macierz.shape[1],y+2)].flatten())-macierz[x,y]<br />
</source><br />
Z tak przygotowaną funkcją ile_sasiadow możemy zdefiniować funkcję określającą logikę gry w życie.<br />
<source lang="python"><br />
def nowy_stan_komorki(x,y,macierz):<br />
sasiadow=ile_sasiadow(x,y,macierz)<br />
if sasiadow==3: return 1<br />
if sasiadow==2 and macierz[x,y]==1: return 1<br />
return 0<br />
</source><br />
Możemy teraz zdefiniować nowy stan całej planszy.<br />
<source lang="python"><br />
def krok(macierz):<br />
wynik=macierz.copy()<br />
for x,y in np.ndindex(wynik.shape):<br />
wynik[x,y]=nowy_stan_komorki(x,y,macierz)<br />
return wynik<br />
</source><br />
Jako warunku początkowego możemy użyć tzw. lokomotywy.<br />
<source lang="python"><br />
lokomotywa=np.array([[1,1,1,0,1],[1,0,0,0,0],[0,0,0,1,1],[0,1,1,0,1],[1,0,1,0,1]])<br />
a[45:50,45:50]=lokomotywa<br />
</source><br />
W pylabie istnieje prosty sposób na wyświetlenie animacji poprzez odświeżanie już wyświetlonego obrazka.<br />
<source lang="python"><br />
py.ion()<br />
for n in range(1000):<br />
py.imshow(a, cmap='Greys', interpolation='nearest')<br />
a=krok(a)<br />
py.draw()<br />
<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/%C4%86wiczenia_z_przetwarzania_tablic_2D&diff=3610TI/Programowanie dla Fizyków Medycznych/Ćwiczenia z przetwarzania tablic 2D2015-06-09T12:14:39Z<p>Tgubiec: /* Wyświetlanie tablic 2D */</p>
<hr />
<div>==Ćwiczenia z przetwarzania tablic 2D==<br />
===Wyświetlanie tablic 2D===<br />
Jako wstęp do przetwarzania obrazów w pythonie przećwiczmy podstawowe operacje na dwuwymiarowych tablicach numpy w postaci których będziemy takie obrazy przechowywać. Zacznijmy od zdefiniowania przykładowej tablicy, która będzie reprezentować obrazek.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
a=np.zeros((100,100),dtype=np.bool)<br />
a[30:50,30:50]=True<br />
a[50:70,50:70]=True<br />
</source><br />
Stworzyliśmy w ten sposób tablice 100 na 100 z wpisanymi wartościami False oraz w środku dwa kwadraty o boku 20 z wartościami True. Już tak stworzoną tablicę możemy wyświetlić za pomocą pylaba.<br />
<source lang="python"><br />
py.imshow(a)<br />
py.show()<br />
</source><br />
<br />
[[Plik:pil1.png]]<br />
<br />
Nie do końca można było się spodziewać, że pylab wyświetli wartości False w kolorze granatowym, a wartości True w kolorze bordowym. Za przyporządkowanie kolorów określonym wartościom odpowiada tzw. mapa kolorów, którą podajemy jako parametr w poleceniu imshow. Aby uzyskać bardziej intuicyjną skalę szarości należy wywołać polecenie następująco.<br />
<source lang="python"><br />
py.imshow(a,cmap = py.cm.gray)<br />
py.show()<br />
</source><br />
<br />
[[Plik:pil2.png]]<br />
<br />
Teraz kolory (a raczej ich brak) są takie jakich można było się spodziewać. Zaskakujące jest za to rozmycie brzegów kwadratów. Obrazek wyświetlany jest w większych rozmiarach niż jego domyślne 100 na 100 pixeli. Pylab aby wyświetlić go w wyższej rozdzielczości musi zwiększyć jego rozmiary. Rozciągając obrazek można w różny sposób przypisywać wartości pixelom, których nie było w pierwotnym obrazku. Proces ten nazywamy interpolacją i aby uzyskać "ostre" krawędzie należy znów użyć odpowiedniej opcji metody imshow.<br />
<source lang="python"><br />
py.imshow(a,cmap = py.cm.gray, interpolation = 'nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:pil3.png]]<br />
<br />
=== Gra w życie Conwaya ===<br />
W ramach ćwiczeń zaprogramujmy klasyczny automat komórkowy zwany gra w życie Conwaya.<br />
W naszej wersji gra toczy się na skończonej planszy o wymiarach 100 na 100 podzielonej na kwadratowe komórki. Każda komórka (poza brzegowymi) ma ośmiu sąsiadów, czyli komórki przylegające do niej bokami i rogami. Każda komórka może znajdować się w jednym z dwóch stanów: może być albo "żywa" (stanowi przypisujemy wartość 1), albo "martwa" (wartość 0). Stan wszystkich komórek w pewnej jednostce czasu jest używany do obliczenia stanu wszystkich komórek w następnej jednostce. Po obliczeniu wszystkie komórki zmieniają swój stan jednocześnie. Stan komórki zależy tylko od liczby jej żywych sąsiadów w następujący sposób<br />
*Martwa komórka, która ma dokładnie 3 żywych sąsiadów, staje się żywa w następnej jednostce czasu (rodzi się)<br />
*Żywa komórka z 2 albo 3 żywymi sąsiadami pozostaje żywa, przy innej liczbie sąsiadów umiera<br />
<br />
Zacznijmy od zdefiniowania planszy gry<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
a=np.zeros((100,100))<br />
</source><br />
Kluczową i najtrudniejszą funkcją jest funkcja zliczająca liczbę sąsiadów danej komórki. Problematyczne jest tutaj uwzględnienie przypadku komórek znajdujących się na rogach, które mają po trzech sąsiadów, oraz komórek brzegowych mających po 5 sąsiadów. Przykładowe rozwiązanie wygląda następująco.<br />
<source lang="python"><br />
def ile_sasiadow(x,y,macierz):<br />
return np.sum(macierz[max(0,x-1):min(macierz.shape[0],x+2),max(0,y-1):min(macierz.shape[1],y+2)].flatten())-macierz[x,y]<br />
</source><br />
Z tak przygotowaną funkcją ile_sasiadow możemy zdefiniować funkcję określającą logikę gry w życie.<br />
<source lang="python"><br />
def nowy_stan_komorki(x,y,macierz):<br />
sasiadow=ile_sasiadow(x,y,macierz)<br />
if sasiadow==3: return 1<br />
if sasiadow==2 and macierz[x,y]==1: return 1<br />
return 0<br />
</source><br />
Możemy teraz zdefiniować nowy stan całej planszy.<br />
<source lang="python"><br />
def krok(macierz):<br />
wynik=macierz.copy()<br />
for x,y in np.ndindex(wynik.shape):<br />
wynik[x,y]=nowy_stan_komorki(x,y,macierz)<br />
return wynik<br />
</source><br />
Jak warunku początkowego możemy użyć tzw. lokomotywy.<br />
<source lang="python"><br />
lokomotywa=np.array([[1,1,1,0,1],[1,0,0,0,0],[0,0,0,1,1],[0,1,1,0,1],[1,0,1,0,1]])<br />
a[45:50,45:50]=lokomotywa<br />
</source><br />
W pylabie istnieje prosty sposób na wyświetlenie animacji poprzez odświeżanie już wyświetlonego obrazka.<br />
<source lang="python"><br />
py.ion()<br />
for n in range(1000):<br />
py.imshow(a, cmap='Greys', interpolation='nearest')<br />
a=krok(a)<br />
py.draw()<br />
<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/RRZ&diff=3609TI/Programowanie dla Fizyków Medycznych/RRZ2015-06-09T12:12:11Z<p>Tgubiec: /* Rozwiązanie */</p>
<hr />
<div>__NOTOC__<br />
==Równania różniczkowe zwyczajne==<br />
Zajmiemy się teraz problemem numerycznego rozwiązywania równań różniczkowych zwyczajnych o postaci:<br />
<br />
<!--<br />
{{Hide in print|This text will not be shown in the print.}}<br />
{{Only in print|This text will only be shown in the print.}}<br />
--><br />
<br />
<math> \frac{dy(t)}{dt} = f(t,y(t))</math>,<br />
<br />
z warunkeim początkowym<br />
<br />
<math>y(t_0)=y_0</math>.<br />
<br />
Zauważmy, że przykładowe równanie różniczkowe drugiego rzędu<br />
<br />
<math> \frac{d^2 x(t)}{dt^2} = \omega(t,x(t))</math>,<br />
<br />
można zapisać jako<br />
<br />
<math> \frac{d}{dt} \binom{x(t)}{x'(t)} = \binom{x'(t)}{\omega(t,x(t))}</math>.<br />
<br />
W analogiczny sposób równanie dowolnego rzędy możemy zapisać jako wektorowe równanie różniczkowe pierwszego rzędu. Wystarczy zatem, że skupimy się na rozwiązywaniu równań pierwszego rzędu, Rozwiązaniem postawionego problemu są ciągłe funkcje zmiennej czasowej t. Rozwiązanie numeryczne takiego problemu ogranicza się jednak do znalezienia wartości funkcji y(t) w skończonej liczbie punktów czasowych. W najprostrzym przypadku (do którego się tutaj ograniczymy) zakładamy, że punkty te są od siebie równo oddalone, a odległość między nimi nazywamy krokiem czasowym i tradycyjnie oznaczamy literą h. Zatem rozwiązanie równania na przedziale <math>(t_0,t_k)</math> sprwadzamy do rozwiązania w sekwencji czasów <math>t_0, t+1=t_0+h,t_2=t_0+2h,...,t_k=Nh</math>. Poprzez <math>x_n</math> oznaczać będziemy numeryczne przybliżenie ścisłego rozwiązania <math>x(t_n)</math>.<br />
<br />
==Metoda Eulera==<br />
Najprostszą metodą numeryczną rozwiązywania równań różniczkowych jest metoda Eulera. Przybliżmy pochodną czasową występującą po lewej stronie równania przez iloraz różnicowy<br />
<br />
<math> \frac{d x(t)}{dt} \approx \frac{x(t+h)-x(t)}{h}</math>,<br />
<br />
przekształcając uzyskujemy<br />
<br />
<math> x(t+h) \approx x(t)+ h \frac{d x(t)}{dt} </math><br />
<br />
a po podstawieniu rozwiązywanego równania mamy<br />
<br />
<math> x(t+h) \approx x(t)+ h f(t,x(t)) </math>.<br />
<br />
Możemy to zapisać w postaci dyskretnej<br />
<br />
<math> x_{n+1} = x_n + h f(t_n,x_n) </math>.<br />
<br />
Wartość w kolejnej chwili czasu dana jest explicite poprzez wartość w chwili poprzedniej. Metoda ta nazywa się Explicit Euler. Możemy teraz zaimplementować ją w pythonie<br />
<br />
<!--<source lang="python">--><br />
<syntaxhighlight lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
#rozwiazujemy rownanie dx(t)/dt=f(t,x)<br />
<br />
#metoda Explicit Euler<br />
#f - funkcja f z rownania<br />
#x0-wartosc poczatkowa<br />
#t0-czas poczatkowy<br />
#tk-czas koncowy<br />
#h-krok czasowy<br />
<br />
def EE(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0))) # gdy mamy do czynienia w równaniem wektorowym<br />
else: x=np.zeros(N) #dla przypadku skalarnego<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
x[i]=np.array(x[i-1]+h*f(t[i-1],x[i-1]))<br />
i+=1<br />
return t,x<br />
</syntaxhighlight><br />
<!--</source>--><br />
Najłatwiej będzie przetestować napisaną metodę na równaniu, którego ścisłe rozwiązanie jest znane. Zacznijmy zatem od równania oscylatora harmonicznego<br />
<source lang="python"><br />
def oscylator(t,y):<br />
x=y[0]<br />
xdot=y[1]<br />
return np.array([xdot,-x])<br />
</source><br />
Rozwiążmy to równanie z warunkiem początkowym [1.0,1.0] i od czasu od 0 do 100.<br />
<source lang="python"><br />
t,x=EE(oscylator,[1.0,1.0],0.0,100,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
rozwiązanie wygląda wówczas następująco<br />
<br />
[[Plik:img01.png]]<br />
<br />
Jeżeli zaś wydłużymy czas symulacji do 1000 otrzymamy<br />
<br />
[[Plik:img2.png]]<br />
<br />
Amplituda oscylacji rośnie wykładniczo i rozwiązanie numeryczne bardzo szybko przestaje mieć cokolwiek wspólnego ze ścisłym rozwiązaniem, którego amplituda jest przecież stała. Metoda Explicit Euler już po kilku krokach czasowych przestaje przypominać ścisłe rozwiązanie. Niestety trudno jest zupełnie wyeliminować to zjawisko, za to możemy użyć metody, która znacznie wolniej będzie się oddalać od ścisłego rozwiązania. Zauważmy, że w metodzie Explicit Euler w każdym kroku czasowym tylko raz liczyliśmy wartość funkcji f. Liczbę wywołań funkcji f w każdym kroku czasowym nazywamy rzędem metody, stąd Explicit Euler jest metodą pierwszego rzędu. Wprowadźmy teraz przykładowe metody rzędu drugiego.<br />
<br />
==Metoda Żabiego Skoku== <br />
W poprzedniej metodzie liczyliśmy wartość funkcji f w chwili <math>t_n</math>, która była pochodną po czasie naszego ścisłego rozwiązania. Kolejny punkt <math>x_{n+1}</math> był liczony z przybliżenia liniowego funkcji w chwili poprzedniej. Jeżeli faktyczna trajektoria ma niezerową drugą pochodną to takie liniowe przybliżenie zawsze będzie nas oddalało od ścisłego rozwiązania. Dosyć prostym pomysłem na poprawienie zbieżności metody jest tak zwany żabi skok. Policzmy najpierw wartość zmiennej x przesuwając się w czasie o h/2 i policzmy wówczas pochodną, którą oznaczmy przez <math>k_2</math><br />
<br />
<math> k_1=f(t_n,x_n) </math>.<br />
<br />
<math> k_2=f(t_n+h/2,x_n+h/2*k_1) </math>.<br />
<br />
Następnie używamy pochodnej <math> k_2 </math> zamiast pochodnej <math> k_1 </math> do obliczenia wartości funkcji w kolejnym kroku czasowym.<br />
<br />
<math> x_{n+1} = x_n + h k_2 </math>.<br />
<br />
Przykładowa implementacja tej metody wygląda następująco<br />
<source lang="python"><br />
def leapfrog(f,x0,t0,tk,h): <br />
<br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
<br />
#index<br />
i=1<br />
<br />
#petla glowna<br />
while (i<N):<br />
k1=f(t[i-1],x[i-1])<br />
k2=f(t[i-1]+h*0.5,x[i-1]+0.5*h*k1)<br />
x[i]=np.array(x[i-1]+h*k2)<br />
i+=1<br />
return t,x<br />
</source><br />
Rozwiązanie równania oscylatora tą metodą dla identycznych jak poprzednio czasów da następujące wyniki<br />
<source lang="python"><br />
t,x=leapfrog(oscylator,[1.0,1.0],0.0,100,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:img3.png]]<br />
<br />
<source lang="python"><br />
t,x=leapfrog(oscylator,[1.0,1.0],0.0,1000,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:img4.png]]<br />
<br />
==Metoda Heuna==<br />
Kolejną metodą niewiele różniącą się od poprzedniej jest metoda Heuna. Zdefiniowana jest ona przez równania<br />
<math> k_1=f(t_n,x_n) </math>,<br />
<br />
<math> k_2=f(t_n+h/2,x_n+h/2*k_1) </math>,<br />
<br />
<math> x_{n+1} = x_n + h k_2 </math>.<br />
<br />
Implementacja wygląda następująco<br />
<br />
<source lang="python"><br />
<br />
def Heun(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
#liczba krokow czasowych<br />
N=len(t)<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
k1=f(t[i-1],x[i-1])<br />
k2=f(t[i-1]+h*0.5,x[i-1]+0.5*h*k1)<br />
x[i]=np.array(x[i-1]+h*0.5*(k1+k2))<br />
i+=1<br />
return t,x<br />
</source><br />
<br />
==Runge-Kutta czwartego rzędu==<br />
Ostatnią metodą, którą omówimy jest najbardziej popularna metoda zwana w skrócie RK4. Metoda ta uznawana jest za kanoniczną i w większości zastosowań dającą najlepsze wyniki. Metody wyższego rzędu nie wnoszą już do wyniku znaczącej poprawy. Jak sugeruje nazwa metody, jej rząd to 4, czyli w każdym kroku czasowym czterokrotnie wywołujemy funkcję f. Metoda ta zdefiniowana jest wzorami<br />
<br />
<math> k_1 = f \left( t_n, x_n \right) </math>,<br />
<br />
<math> k_2 = f \left( t_n + {h \over 2}, x_n + {1 \over 2} k_1 \right) </math>,<br />
<br />
<math> k_3 = f \left( t_n + {h \over 2}, x_n + {1 \over 2} k_2 \right) </math>,<br />
<br />
<math> k_4 = f \left( t_n + h, x_n + k_3 \right) </math>,<br />
<br />
<math> x_{n+1} = x_n + {h \over 6} (k_1 + 2k_2 + 2k_3 + k_4) </math>,<br />
<br />
a implementacja wygląda następująco<br />
<br />
<source lang="python"><br />
def RK4(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
#liczba krokow czasowych<br />
N=len(t)<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
k1=h*f(t[i-1],x[i-1])<br />
k2=h*f(t[i-1]+h*0.5,x[i-1]+0.5*k1)<br />
k3=h*f(t[i-1]+h*0.5,x[i-1]+0.5*k2)<br />
k4=h*f(t[i-1]+h,x[i-1]+k3)<br />
x[i]=np.array(x[i-1]+(k1+2.0*k2+2.0*k3+k4)/6)<br />
i+=1<br />
return t,x<br />
</source><br />
<br />
==Przykłady==<br />
===Zadanie - Wahadło matematyczne z tłumieniem i siłą wymuszającą===<br />
<br />
Rozwiąż numerycznie metodą RK4 równanie różniczkowe oscylatora harmonicznego z tłumieniem i siłą wymuszającą<br />
<br />
<math> \frac{d^2x}{dt^2} + \Gamma \frac{dx}{dt} + w_0^2 x = f_0 \cos(W t) </math>,<br />
<br />
przyjmując parametry <math> f_0 =1, w_0=1, \Gamma=0.1, h=0.1 </math>. Wykreśl zależność amplitudy drgań w funkcji częstości siły wymuszającej W, dla W z przedziału [0.1,3].<br />
===Rozwiązanie===<br />
Zacznijmy od sprowadzenia równania drugiego stopnia do równania pierwszego stopnia i zapisania go w postaci funkcji<br />
<br />
<source lang="python"><br />
def oscylator(t,y):<br />
f0=1.0<br />
w0=1.0<br />
Gamma=0.1<br />
<br />
x=y[0]<br />
xdot=y[1]<br />
return np.array([xdot,f0*np.cos(oscylator.W*t)-oscylator.Gamma*xdot-w0*w0*x])<br />
<br />
oscylator.W=1.0<br />
</source><br />
Nieprzypadkowo parametr W nie jest definiowany w samej funkcji jako zmienna wewnętrzna, ale jako atrybut obiektu jakim jest funkcja. Dzięki takiej konstrukcji łatwo będzie nam zmieniać parametr W, czyli częstość siły wymuszającej. Zobaczmy jak wygląda trajektoria będąca rozwiązaniem tego równania dla warunku początkowego [0,1] i czasu końcowego równego 400.<br />
<source lang="python"><br />
py.plot(*RK4(oscylator,[0.0,1.0],0.0,400.0,0.1))<br />
py.show()<br />
</source><br />
<br />
[[Plik:oscy1.png]]<br />
<br />
Widać, że początkowo układ dochodzi do stanu regularnych oscylacji. Do analizy amplitudy interesuje nas jedynie końcowa część więc ograniczymy się do analizy trajektorii od czasu 200 do czasu 400. Napiszmy teraz funkcję, która na podstawie trajektorii wyznaczy nam amplitudę oscylacji<br />
<br />
<source lang="python"><br />
def amplituda(x):<br />
lista=x[2000:,0]<br />
return (max(lista)-min(lista))*0.5<br />
</source><br />
<br />
Interesować nas będzie amplituda drgań w funkcji częstość W. Wygenerujmy listę wartości W, dla których będziemy liczyć amplitudę.<br />
<br />
<source lang="python"><br />
Omegas=np.arange(0.1,3.0,0.05)<br />
</source><br />
Możemy teraz dla każdej z wartości W rozwiązać numerycznie równanie różniczkowe i wyznaczyć odpowiadającą amplitudę oscylacji<br />
<source lang="python"><br />
amp=[amplituda(RK4(oscylator,[0.0,1.0],0.0,400.0,0.1)[1]) for oscylator.W in Omegas]<br />
</source><br />
Wynik końcowy wygląda następująco:<br />
<source lang="python"><br />
py.plot(Omegas,amp)<br />
py.show()<br />
</source><br />
[[Plik:oscy2.png]]<br />
<br />
Jak można było się domyślić amplituda jest największa gdy częstotliwość wymuszania W pokrywa się z wartością częstotliwości drgań własnych <math> w_0=1 </math><br />
<br />
===Zadanie - Układ Lorenza===<br />
Rozwiąż układ równań różniczkowych Lorenza dany wzorami<br />
<br />
<math> \begin{cases}\dot x=\sigma y-\sigma x\\\dot y=-xz+rx-y\\\dot z=xy-bz\end{cases}, </math> <br />
<br />
metodą całkowania Rungego–Kutty drugiego rzędu z α = 2/3, czyli<br />
<br />
<math>k_1 = f(t_n,x_n)</math> , <br />
<br />
<math>k_2 = f(t_n + \tfrac{2}{3}h, x_n + \tfrac{2}{3}h k_1)</math> , <br />
<br />
<math>x_{n+1} = x_n + h \left(\tfrac{1}{4}k_1+\tfrac{3}{4} k_2 \right). </math> <br />
<br />
Przyjmij sigma=10, b=8/3, r=99.96, krok czasowy h=0.005 i warunki początkowe x=1,y=0,z=0. Wykonaj 8000 kroków czasowych. Układ po pewnym czasie zacznie poruszać się po pewnej periodycznej trajektorii. Wykonaj 3 rysunki TEJ PERIODYCZNEJ TRAJEKTORII (bez okresu dochodzenia do niej) w płaszczyznach (x,y), (y,z) i (z,x). Wypisz na ekranie przedziały wartości jakie przyjmują zmienne x,y i z na periodycznej trajektorii oraz okres trajektorii periodycznej.<br />
===Rozwiązanie===<br />
Zacznijmy od implementacji podanej w treści zadania metody całkowania RK2<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
#rozwiazujemy rownanie dx(t)/dt=f(t,x)<br />
<br />
#metoda Explicit Euler<br />
#f - funkcja f z rownania<br />
#x0-wartosc poczatkowa<br />
#t0-czas poczatkowy<br />
#tk-czas koncowy<br />
#h-krok czasowy<br />
<br />
def RK2(f,x0,t0,tk,h): <br />
<br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
<br />
#index<br />
i=1<br />
<br />
#petla glowna<br />
while (i<N):<br />
k1=h*f(t[i-1],x[i-1])<br />
k2=h*f(t[i-1]+h*2.0/3.0,x[i-1]+k1*2.0/3.0)<br />
x[i]=np.array(x[i-1]+0.25*k1+0.75*k2)<br />
i+=1<br />
return t,x<br />
</source><br />
Następnie napiszmy funkcję opisującą układ Lorenza<br />
<source lang="python"><br />
def Lorenza(t,y):<br />
sigma=10.0<br />
b=8.0/3<br />
r=99.96<br />
xx=y[0]<br />
yy=y[1]<br />
zz=y[2]<br />
xdot=sigma*(yy-xx)<br />
ydot=-xx*zz+r*xx-yy<br />
zdot=xx*yy-b*zz<br />
return np.array([xdot,ydot,zdot])<br />
</source><br />
<br />
Zobaczmy teraz jak wyglądają trajektorie wszystkich trzech współrzędnych rozwiązania z zadanymi parametrami<br />
<source lang="python"><br />
t,x=RK2(Lorenza,[1.0,0.0,0.0],0.0,40.0,0.005)<br />
py.plot(t,x[:,1])<br />
py.show()<br />
py.plot(t,x[:,2])<br />
py.show()<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:lor1.png]]<br />
<br />
[[Plik:lor2.png]]<br />
<br />
[[Plik:lor3.png]]<br />
<br />
Możemy zauważyć, że układ początkowo zachowuje się chaotycznie, a potem dąży do pewnego stanu ustalonego (tzw. atraktora). Cała trajektoria składa się z 8000 punktów, przyjmijmy że powyżej punktu o numerze 2500 mamy już do czynienia tylko z periodyczną trajektorią. Wykreślmy zatem portrety fazowe, o których mowa w treści zadania<br />
<source lang="python"><br />
py.plot(x[2500:,0],x[2500:,1])<br />
py.show()<br />
py.plot(x[2500:,1],x[2500:,2])<br />
py.show()<br />
py.plot(x[2500:,2],x[2500:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:lor4.png]]<br />
<br />
[[Plik:lor5.png]]<br />
<br />
[[Plik:lor6.png]]<br />
<br />
Wypisanie zakresów jest już tylko formalnością<br />
<source lang="python"><br />
print 'zmienna x przyjmuje wartosci z zakresu: (',min(x[2500:,0]),',',max(x[2500:,0]),')'<br />
print 'zmienna y przyjmuje wartosci z zakresu: (',min(x[2500:,1]),',',max(x[2500:,1]),')'<br />
print 'zmienna z przyjmuje wartosci z zakresu: (',min(x[2500:,2]),',',max(x[2500:,2]),')'<br />
</source><br />
Okres trajektorii periodycznej możemy znaleźć na przykład w ten sposób<br />
<source lang="python"><br />
prog=140<br />
lista=[]<br />
for i in range(2500,8000):<br />
if (x[i-1,2]<prog) and (x[i,2]>prog): lista.append(i)<br />
print 'okres to:',np.mean(np.diff(lista)*0.005)<br />
<br />
>>> okres to: 1.0975<br />
>>> zmienna x przyjmuje wartosci z zakresu: ( -33.4431203059 , 25.8037953495 )<br />
>>> zmienna y przyjmuje wartosci z zakresu: ( -56.7169238157 , 37.3166709986 )<br />
>>> zmienna z przyjmuje wartosci z zakresu: ( 53.4652816712 , 144.264397579 )<br />
<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/RRZ&diff=3608TI/Programowanie dla Fizyków Medycznych/RRZ2015-06-09T12:09:21Z<p>Tgubiec: /* Rozwiązanie */</p>
<hr />
<div>__NOTOC__<br />
==Równania różniczkowe zwyczajne==<br />
Zajmiemy się teraz problemem numerycznego rozwiązywania równań różniczkowych zwyczajnych o postaci:<br />
<br />
<!--<br />
{{Hide in print|This text will not be shown in the print.}}<br />
{{Only in print|This text will only be shown in the print.}}<br />
--><br />
<br />
<math> \frac{dy(t)}{dt} = f(t,y(t))</math>,<br />
<br />
z warunkeim początkowym<br />
<br />
<math>y(t_0)=y_0</math>.<br />
<br />
Zauważmy, że przykładowe równanie różniczkowe drugiego rzędu<br />
<br />
<math> \frac{d^2 x(t)}{dt^2} = \omega(t,x(t))</math>,<br />
<br />
można zapisać jako<br />
<br />
<math> \frac{d}{dt} \binom{x(t)}{x'(t)} = \binom{x'(t)}{\omega(t,x(t))}</math>.<br />
<br />
W analogiczny sposób równanie dowolnego rzędy możemy zapisać jako wektorowe równanie różniczkowe pierwszego rzędu. Wystarczy zatem, że skupimy się na rozwiązywaniu równań pierwszego rzędu, Rozwiązaniem postawionego problemu są ciągłe funkcje zmiennej czasowej t. Rozwiązanie numeryczne takiego problemu ogranicza się jednak do znalezienia wartości funkcji y(t) w skończonej liczbie punktów czasowych. W najprostrzym przypadku (do którego się tutaj ograniczymy) zakładamy, że punkty te są od siebie równo oddalone, a odległość między nimi nazywamy krokiem czasowym i tradycyjnie oznaczamy literą h. Zatem rozwiązanie równania na przedziale <math>(t_0,t_k)</math> sprwadzamy do rozwiązania w sekwencji czasów <math>t_0, t+1=t_0+h,t_2=t_0+2h,...,t_k=Nh</math>. Poprzez <math>x_n</math> oznaczać będziemy numeryczne przybliżenie ścisłego rozwiązania <math>x(t_n)</math>.<br />
<br />
==Metoda Eulera==<br />
Najprostszą metodą numeryczną rozwiązywania równań różniczkowych jest metoda Eulera. Przybliżmy pochodną czasową występującą po lewej stronie równania przez iloraz różnicowy<br />
<br />
<math> \frac{d x(t)}{dt} \approx \frac{x(t+h)-x(t)}{h}</math>,<br />
<br />
przekształcając uzyskujemy<br />
<br />
<math> x(t+h) \approx x(t)+ h \frac{d x(t)}{dt} </math><br />
<br />
a po podstawieniu rozwiązywanego równania mamy<br />
<br />
<math> x(t+h) \approx x(t)+ h f(t,x(t)) </math>.<br />
<br />
Możemy to zapisać w postaci dyskretnej<br />
<br />
<math> x_{n+1} = x_n + h f(t_n,x_n) </math>.<br />
<br />
Wartość w kolejnej chwili czasu dana jest explicite poprzez wartość w chwili poprzedniej. Metoda ta nazywa się Explicit Euler. Możemy teraz zaimplementować ją w pythonie<br />
<br />
<!--<source lang="python">--><br />
<syntaxhighlight lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
#rozwiazujemy rownanie dx(t)/dt=f(t,x)<br />
<br />
#metoda Explicit Euler<br />
#f - funkcja f z rownania<br />
#x0-wartosc poczatkowa<br />
#t0-czas poczatkowy<br />
#tk-czas koncowy<br />
#h-krok czasowy<br />
<br />
def EE(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0))) # gdy mamy do czynienia w równaniem wektorowym<br />
else: x=np.zeros(N) #dla przypadku skalarnego<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
x[i]=np.array(x[i-1]+h*f(t[i-1],x[i-1]))<br />
i+=1<br />
return t,x<br />
</syntaxhighlight><br />
<!--</source>--><br />
Najłatwiej będzie przetestować napisaną metodę na równaniu, którego ścisłe rozwiązanie jest znane. Zacznijmy zatem od równania oscylatora harmonicznego<br />
<source lang="python"><br />
def oscylator(t,y):<br />
x=y[0]<br />
xdot=y[1]<br />
return np.array([xdot,-x])<br />
</source><br />
Rozwiążmy to równanie z warunkiem początkowym [1.0,1.0] i od czasu od 0 do 100.<br />
<source lang="python"><br />
t,x=EE(oscylator,[1.0,1.0],0.0,100,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
rozwiązanie wygląda wówczas następująco<br />
<br />
[[Plik:img01.png]]<br />
<br />
Jeżeli zaś wydłużymy czas symulacji do 1000 otrzymamy<br />
<br />
[[Plik:img2.png]]<br />
<br />
Amplituda oscylacji rośnie wykładniczo i rozwiązanie numeryczne bardzo szybko przestaje mieć cokolwiek wspólnego ze ścisłym rozwiązaniem, którego amplituda jest przecież stała. Metoda Explicit Euler już po kilku krokach czasowych przestaje przypominać ścisłe rozwiązanie. Niestety trudno jest zupełnie wyeliminować to zjawisko, za to możemy użyć metody, która znacznie wolniej będzie się oddalać od ścisłego rozwiązania. Zauważmy, że w metodzie Explicit Euler w każdym kroku czasowym tylko raz liczyliśmy wartość funkcji f. Liczbę wywołań funkcji f w każdym kroku czasowym nazywamy rzędem metody, stąd Explicit Euler jest metodą pierwszego rzędu. Wprowadźmy teraz przykładowe metody rzędu drugiego.<br />
<br />
==Metoda Żabiego Skoku== <br />
W poprzedniej metodzie liczyliśmy wartość funkcji f w chwili <math>t_n</math>, która była pochodną po czasie naszego ścisłego rozwiązania. Kolejny punkt <math>x_{n+1}</math> był liczony z przybliżenia liniowego funkcji w chwili poprzedniej. Jeżeli faktyczna trajektoria ma niezerową drugą pochodną to takie liniowe przybliżenie zawsze będzie nas oddalało od ścisłego rozwiązania. Dosyć prostym pomysłem na poprawienie zbieżności metody jest tak zwany żabi skok. Policzmy najpierw wartość zmiennej x przesuwając się w czasie o h/2 i policzmy wówczas pochodną, którą oznaczmy przez <math>k_2</math><br />
<br />
<math> k_1=f(t_n,x_n) </math>.<br />
<br />
<math> k_2=f(t_n+h/2,x_n+h/2*k_1) </math>.<br />
<br />
Następnie używamy pochodnej <math> k_2 </math> zamiast pochodnej <math> k_1 </math> do obliczenia wartości funkcji w kolejnym kroku czasowym.<br />
<br />
<math> x_{n+1} = x_n + h k_2 </math>.<br />
<br />
Przykładowa implementacja tej metody wygląda następująco<br />
<source lang="python"><br />
def leapfrog(f,x0,t0,tk,h): <br />
<br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
<br />
#index<br />
i=1<br />
<br />
#petla glowna<br />
while (i<N):<br />
k1=f(t[i-1],x[i-1])<br />
k2=f(t[i-1]+h*0.5,x[i-1]+0.5*h*k1)<br />
x[i]=np.array(x[i-1]+h*k2)<br />
i+=1<br />
return t,x<br />
</source><br />
Rozwiązanie równania oscylatora tą metodą dla identycznych jak poprzednio czasów da następujące wyniki<br />
<source lang="python"><br />
t,x=leapfrog(oscylator,[1.0,1.0],0.0,100,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:img3.png]]<br />
<br />
<source lang="python"><br />
t,x=leapfrog(oscylator,[1.0,1.0],0.0,1000,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:img4.png]]<br />
<br />
==Metoda Heuna==<br />
Kolejną metodą niewiele różniącą się od poprzedniej jest metoda Heuna. Zdefiniowana jest ona przez równania<br />
<math> k_1=f(t_n,x_n) </math>,<br />
<br />
<math> k_2=f(t_n+h/2,x_n+h/2*k_1) </math>,<br />
<br />
<math> x_{n+1} = x_n + h k_2 </math>.<br />
<br />
Implementacja wygląda następująco<br />
<br />
<source lang="python"><br />
<br />
def Heun(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
#liczba krokow czasowych<br />
N=len(t)<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
k1=f(t[i-1],x[i-1])<br />
k2=f(t[i-1]+h*0.5,x[i-1]+0.5*h*k1)<br />
x[i]=np.array(x[i-1]+h*0.5*(k1+k2))<br />
i+=1<br />
return t,x<br />
</source><br />
<br />
==Runge-Kutta czwartego rzędu==<br />
Ostatnią metodą, którą omówimy jest najbardziej popularna metoda zwana w skrócie RK4. Metoda ta uznawana jest za kanoniczną i w większości zastosowań dającą najlepsze wyniki. Metody wyższego rzędu nie wnoszą już do wyniku znaczącej poprawy. Jak sugeruje nazwa metody, jej rząd to 4, czyli w każdym kroku czasowym czterokrotnie wywołujemy funkcję f. Metoda ta zdefiniowana jest wzorami<br />
<br />
<math> k_1 = f \left( t_n, x_n \right) </math>,<br />
<br />
<math> k_2 = f \left( t_n + {h \over 2}, x_n + {1 \over 2} k_1 \right) </math>,<br />
<br />
<math> k_3 = f \left( t_n + {h \over 2}, x_n + {1 \over 2} k_2 \right) </math>,<br />
<br />
<math> k_4 = f \left( t_n + h, x_n + k_3 \right) </math>,<br />
<br />
<math> x_{n+1} = x_n + {h \over 6} (k_1 + 2k_2 + 2k_3 + k_4) </math>,<br />
<br />
a implementacja wygląda następująco<br />
<br />
<source lang="python"><br />
def RK4(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
#liczba krokow czasowych<br />
N=len(t)<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
k1=h*f(t[i-1],x[i-1])<br />
k2=h*f(t[i-1]+h*0.5,x[i-1]+0.5*k1)<br />
k3=h*f(t[i-1]+h*0.5,x[i-1]+0.5*k2)<br />
k4=h*f(t[i-1]+h,x[i-1]+k3)<br />
x[i]=np.array(x[i-1]+(k1+2.0*k2+2.0*k3+k4)/6)<br />
i+=1<br />
return t,x<br />
</source><br />
<br />
==Przykłady==<br />
===Zadanie - Wahadło matematyczne z tłumieniem i siłą wymuszającą===<br />
<br />
Rozwiąż numerycznie metodą RK4 równanie różniczkowe oscylatora harmonicznego z tłumieniem i siłą wymuszającą<br />
<br />
<math> \frac{d^2x}{dt^2} + \Gamma \frac{dx}{dt} + w_0^2 x = f_0 \cos(W t) </math>,<br />
<br />
przyjmując parametry <math> f_0 =1, w_0=1, \Gamma=0.1, h=0.1 </math>. Wykreśl zależność amplitudy drgań w funkcji częstości siły wymuszającej W, dla W z przedziału [0.1,3].<br />
===Rozwiązanie===<br />
Zacznijmy od sprowadzenia równania drugiego stopnia do równania pierwszego stopnia i zapisania go w postaci funkcji<br />
<br />
<source lang="python"><br />
def oscylator(t,y):<br />
f0=1.0<br />
w0=1.0<br />
Gamma=0.1<br />
<br />
x=y[0]<br />
xdot=y[1]<br />
return np.array([xdot,f0*np.cos(oscylator.W*t)-oscylator.Gamma*xdot-w0*w0*x])<br />
<br />
oscylator.W=1.0<br />
</source><br />
Nieprzypadkowo parametr W nie jest definiowany w samej funkcji jako zmienna wewnętrzna, ale jako atrybut obiektu jakim jest funkcja. Dzięki takiej konstrukcji łatwo będzie nam zmieniać parametr W, czyli częstość siły wymuszającej. Zobaczmy jak wygląda trajektoria będąca rozwiązaniem tego równania dla warunku początkowego [0,1] i czasu końcowego równego 400.<br />
<source lang="python"><br />
py.plot(*RK4(oscylator,[0.0,1.0],0.0,400.0,0.1))<br />
py.show()<br />
</source><br />
<br />
[[Plik:oscy1.png]]<br />
<br />
Widać, że początkowo układ dochodzi do stanu regularnych oscylacji. Do analizy amplitudy interesuje nas jedynie końcowa część więc ograniczymy się do analizy trajektorii od czasu 200 do czasu 400. Napiszmy teraz funkcję, która na podstawie trajektorii wyznaczy nam amplitudę oscylacji<br />
<br />
<source lang="python"><br />
def amplituda(x):<br />
lista=x[2000:,0]<br />
return (max(lista)-min(lista))*0.5<br />
</source><br />
<br />
Interesować nas będzie amplituda drgań w funkcji częstość W. Wygenerujmy listę wartości W, dla których będziemy liczyć amplitudę.<br />
<br />
<source lang="python"><br />
Omegas=np.arange(0.1,3.0,0.05)<br />
</source><br />
Możemy teraz dla każdej z wartości W rozwiązać numerycznie równanie różniczkowe i wyznaczyć odpowiadającą amplitudę oscylacji<br />
<source lang="python"><br />
amp=[amplituda(RK4(oscylator,[0.0,1.0],0.0,400.0,0.1)[1]) for oscylator.W in Omegas]<br />
</source><br />
Wynik końcowy wygląda następująco:<br />
<source lang="python"><br />
py.plot(Omegas,amp)<br />
py.show()<br />
</source><br />
[[Plik:oscy2.png]]<br />
<br />
Jak można było się domyślić amplituda jest największa gdy częstotliwość wymuszania W pokrywa się z wartością częstotliwości drgań własnych <math> w_0=1 </math><br />
<br />
===Zadanie - Układ Lorenza===<br />
Rozwiąż układ równań różniczkowych Lorenza dany wzorami<br />
<br />
<math> \begin{cases}\dot x=\sigma y-\sigma x\\\dot y=-xz+rx-y\\\dot z=xy-bz\end{cases}, </math> <br />
<br />
metodą całkowania Rungego–Kutty drugiego rzędu z α = 2/3, czyli<br />
<br />
<math>k_1 = f(t_n,x_n)</math> , <br />
<br />
<math>k_2 = f(t_n + \tfrac{2}{3}h, x_n + \tfrac{2}{3}h k_1)</math> , <br />
<br />
<math>x_{n+1} = x_n + h \left(\tfrac{1}{4}k_1+\tfrac{3}{4} k_2 \right). </math> <br />
<br />
Przyjmij sigma=10, b=8/3, r=99.96, krok czasowy h=0.005 i warunki początkowe x=1,y=0,z=0. Wykonaj 8000 kroków czasowych. Układ po pewnym czasie zacznie poruszać się po pewnej periodycznej trajektorii. Wykonaj 3 rysunki TEJ PERIODYCZNEJ TRAJEKTORII (bez okresu dochodzenia do niej) w płaszczyznach (x,y), (y,z) i (z,x). Wypisz na ekranie przedziały wartości jakie przyjmują zmienne x,y i z na periodycznej trajektorii oraz okres trajektorii periodycznej.<br />
===Rozwiązanie===<br />
Zacznijmu od implementacji podanej w treści zadania metody całkowania RK2<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
#rozwiazujemy rownanie dx(t)/dt=f(t,x)<br />
<br />
#metoda Explicit Euler<br />
#f - funkcja f z rownania<br />
#x0-wartosc poczatkowa<br />
#t0-czas poczatkowy<br />
#tk-czas koncowy<br />
#h-krok czasowy<br />
<br />
def RK2(f,x0,t0,tk,h): <br />
<br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
<br />
#index<br />
i=1<br />
<br />
#petla glowna<br />
while (i<N):<br />
k1=h*f(t[i-1],x[i-1])<br />
k2=h*f(t[i-1]+h*2.0/3.0,x[i-1]+k1*2.0/3.0)<br />
x[i]=np.array(x[i-1]+0.25*k1+0.75*k2)<br />
i+=1<br />
return t,x<br />
</source><br />
Następnie napiszmy funkcję opisującą układ Lorenza<br />
<source lang="python"><br />
def Lorenza(t,y):<br />
sigma=10.0<br />
b=8.0/3<br />
r=99.96<br />
xx=y[0]<br />
yy=y[1]<br />
zz=y[2]<br />
xdot=sigma*(yy-xx)<br />
ydot=-xx*zz+r*xx-yy<br />
zdot=xx*yy-b*zz<br />
return np.array([xdot,ydot,zdot])<br />
</source><br />
<br />
Zobaczmy teraz jak wyglądają trajektorie wszystkich trzech współrzędnych rozwiązania z zadanymi parametrami<br />
<source lang="python"><br />
t,x=RK2(Lorenza,[1.0,0.0,0.0],0.0,40.0,0.005)<br />
py.plot(t,x[:,1])<br />
py.show()<br />
py.plot(t,x[:,2])<br />
py.show()<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:lor1.png]]<br />
<br />
[[Plik:lor2.png]]<br />
<br />
[[Plik:lor3.png]]<br />
<br />
Możemy zauważyć że układ początkowo zachowuje się chaotycznie a potem dąży do pewnego stanu ustalonego (tzw. atraktora). Cała trajektoria składa się z 8000 punktów, przyjmijmy że powyżej punktu o numerze 2500 mamy już do czynienia tylko z periodyczną trajektorią. Wykreślmy zatem portrety fazowe o których mowa w treści zadania<br />
<source lang="python"><br />
py.plot(x[2500:,0],x[2500:,1])<br />
py.show()<br />
py.plot(x[2500:,1],x[2500:,2])<br />
py.show()<br />
py.plot(x[2500:,2],x[2500:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:lor4.png]]<br />
<br />
[[Plik:lor5.png]]<br />
<br />
[[Plik:lor6.png]]<br />
<br />
Wypisanie zakresów jest już tylko formalnością<br />
<source lang="python"><br />
print 'zmienna x przyjmuje wartosci z zakresu: (',min(x[2500:,0]),',',max(x[2500:,0]),')'<br />
print 'zmienna y przyjmuje wartosci z zakresu: (',min(x[2500:,1]),',',max(x[2500:,1]),')'<br />
print 'zmienna z przyjmuje wartosci z zakresu: (',min(x[2500:,2]),',',max(x[2500:,2]),')'<br />
</source><br />
Okres trajektorii periodycznej możemy znaleść na przykład w ten sposób<br />
<source lang="python"><br />
prog=140<br />
lista=[]<br />
for i in range(2500,8000):<br />
if (x[i-1,2]<prog) and (x[i,2]>prog): lista.append(i)<br />
print 'okres to:',np.mean(np.diff(lista)*0.005)<br />
<br />
>>> okres to: 1.0975<br />
>>> zmienna x przyjmuje wartosci z zakresu: ( -33.4431203059 , 25.8037953495 )<br />
>>> zmienna y przyjmuje wartosci z zakresu: ( -56.7169238157 , 37.3166709986 )<br />
>>> zmienna z przyjmuje wartosci z zakresu: ( 53.4652816712 , 144.264397579 )<br />
<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/RRZ&diff=3607TI/Programowanie dla Fizyków Medycznych/RRZ2015-06-09T12:06:34Z<p>Tgubiec: /* Rozwiązanie */</p>
<hr />
<div>__NOTOC__<br />
==Równania różniczkowe zwyczajne==<br />
Zajmiemy się teraz problemem numerycznego rozwiązywania równań różniczkowych zwyczajnych o postaci:<br />
<br />
<!--<br />
{{Hide in print|This text will not be shown in the print.}}<br />
{{Only in print|This text will only be shown in the print.}}<br />
--><br />
<br />
<math> \frac{dy(t)}{dt} = f(t,y(t))</math>,<br />
<br />
z warunkeim początkowym<br />
<br />
<math>y(t_0)=y_0</math>.<br />
<br />
Zauważmy, że przykładowe równanie różniczkowe drugiego rzędu<br />
<br />
<math> \frac{d^2 x(t)}{dt^2} = \omega(t,x(t))</math>,<br />
<br />
można zapisać jako<br />
<br />
<math> \frac{d}{dt} \binom{x(t)}{x'(t)} = \binom{x'(t)}{\omega(t,x(t))}</math>.<br />
<br />
W analogiczny sposób równanie dowolnego rzędy możemy zapisać jako wektorowe równanie różniczkowe pierwszego rzędu. Wystarczy zatem, że skupimy się na rozwiązywaniu równań pierwszego rzędu, Rozwiązaniem postawionego problemu są ciągłe funkcje zmiennej czasowej t. Rozwiązanie numeryczne takiego problemu ogranicza się jednak do znalezienia wartości funkcji y(t) w skończonej liczbie punktów czasowych. W najprostrzym przypadku (do którego się tutaj ograniczymy) zakładamy, że punkty te są od siebie równo oddalone, a odległość między nimi nazywamy krokiem czasowym i tradycyjnie oznaczamy literą h. Zatem rozwiązanie równania na przedziale <math>(t_0,t_k)</math> sprwadzamy do rozwiązania w sekwencji czasów <math>t_0, t+1=t_0+h,t_2=t_0+2h,...,t_k=Nh</math>. Poprzez <math>x_n</math> oznaczać będziemy numeryczne przybliżenie ścisłego rozwiązania <math>x(t_n)</math>.<br />
<br />
==Metoda Eulera==<br />
Najprostszą metodą numeryczną rozwiązywania równań różniczkowych jest metoda Eulera. Przybliżmy pochodną czasową występującą po lewej stronie równania przez iloraz różnicowy<br />
<br />
<math> \frac{d x(t)}{dt} \approx \frac{x(t+h)-x(t)}{h}</math>,<br />
<br />
przekształcając uzyskujemy<br />
<br />
<math> x(t+h) \approx x(t)+ h \frac{d x(t)}{dt} </math><br />
<br />
a po podstawieniu rozwiązywanego równania mamy<br />
<br />
<math> x(t+h) \approx x(t)+ h f(t,x(t)) </math>.<br />
<br />
Możemy to zapisać w postaci dyskretnej<br />
<br />
<math> x_{n+1} = x_n + h f(t_n,x_n) </math>.<br />
<br />
Wartość w kolejnej chwili czasu dana jest explicite poprzez wartość w chwili poprzedniej. Metoda ta nazywa się Explicit Euler. Możemy teraz zaimplementować ją w pythonie<br />
<br />
<!--<source lang="python">--><br />
<syntaxhighlight lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
#rozwiazujemy rownanie dx(t)/dt=f(t,x)<br />
<br />
#metoda Explicit Euler<br />
#f - funkcja f z rownania<br />
#x0-wartosc poczatkowa<br />
#t0-czas poczatkowy<br />
#tk-czas koncowy<br />
#h-krok czasowy<br />
<br />
def EE(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0))) # gdy mamy do czynienia w równaniem wektorowym<br />
else: x=np.zeros(N) #dla przypadku skalarnego<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
x[i]=np.array(x[i-1]+h*f(t[i-1],x[i-1]))<br />
i+=1<br />
return t,x<br />
</syntaxhighlight><br />
<!--</source>--><br />
Najłatwiej będzie przetestować napisaną metodę na równaniu, którego ścisłe rozwiązanie jest znane. Zacznijmy zatem od równania oscylatora harmonicznego<br />
<source lang="python"><br />
def oscylator(t,y):<br />
x=y[0]<br />
xdot=y[1]<br />
return np.array([xdot,-x])<br />
</source><br />
Rozwiążmy to równanie z warunkiem początkowym [1.0,1.0] i od czasu od 0 do 100.<br />
<source lang="python"><br />
t,x=EE(oscylator,[1.0,1.0],0.0,100,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
rozwiązanie wygląda wówczas następująco<br />
<br />
[[Plik:img01.png]]<br />
<br />
Jeżeli zaś wydłużymy czas symulacji do 1000 otrzymamy<br />
<br />
[[Plik:img2.png]]<br />
<br />
Amplituda oscylacji rośnie wykładniczo i rozwiązanie numeryczne bardzo szybko przestaje mieć cokolwiek wspólnego ze ścisłym rozwiązaniem, którego amplituda jest przecież stała. Metoda Explicit Euler już po kilku krokach czasowych przestaje przypominać ścisłe rozwiązanie. Niestety trudno jest zupełnie wyeliminować to zjawisko, za to możemy użyć metody, która znacznie wolniej będzie się oddalać od ścisłego rozwiązania. Zauważmy, że w metodzie Explicit Euler w każdym kroku czasowym tylko raz liczyliśmy wartość funkcji f. Liczbę wywołań funkcji f w każdym kroku czasowym nazywamy rzędem metody, stąd Explicit Euler jest metodą pierwszego rzędu. Wprowadźmy teraz przykładowe metody rzędu drugiego.<br />
<br />
==Metoda Żabiego Skoku== <br />
W poprzedniej metodzie liczyliśmy wartość funkcji f w chwili <math>t_n</math>, która była pochodną po czasie naszego ścisłego rozwiązania. Kolejny punkt <math>x_{n+1}</math> był liczony z przybliżenia liniowego funkcji w chwili poprzedniej. Jeżeli faktyczna trajektoria ma niezerową drugą pochodną to takie liniowe przybliżenie zawsze będzie nas oddalało od ścisłego rozwiązania. Dosyć prostym pomysłem na poprawienie zbieżności metody jest tak zwany żabi skok. Policzmy najpierw wartość zmiennej x przesuwając się w czasie o h/2 i policzmy wówczas pochodną, którą oznaczmy przez <math>k_2</math><br />
<br />
<math> k_1=f(t_n,x_n) </math>.<br />
<br />
<math> k_2=f(t_n+h/2,x_n+h/2*k_1) </math>.<br />
<br />
Następnie używamy pochodnej <math> k_2 </math> zamiast pochodnej <math> k_1 </math> do obliczenia wartości funkcji w kolejnym kroku czasowym.<br />
<br />
<math> x_{n+1} = x_n + h k_2 </math>.<br />
<br />
Przykładowa implementacja tej metody wygląda następująco<br />
<source lang="python"><br />
def leapfrog(f,x0,t0,tk,h): <br />
<br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
<br />
#index<br />
i=1<br />
<br />
#petla glowna<br />
while (i<N):<br />
k1=f(t[i-1],x[i-1])<br />
k2=f(t[i-1]+h*0.5,x[i-1]+0.5*h*k1)<br />
x[i]=np.array(x[i-1]+h*k2)<br />
i+=1<br />
return t,x<br />
</source><br />
Rozwiązanie równania oscylatora tą metodą dla identycznych jak poprzednio czasów da następujące wyniki<br />
<source lang="python"><br />
t,x=leapfrog(oscylator,[1.0,1.0],0.0,100,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:img3.png]]<br />
<br />
<source lang="python"><br />
t,x=leapfrog(oscylator,[1.0,1.0],0.0,1000,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:img4.png]]<br />
<br />
==Metoda Heuna==<br />
Kolejną metodą niewiele różniącą się od poprzedniej jest metoda Heuna. Zdefiniowana jest ona przez równania<br />
<math> k_1=f(t_n,x_n) </math>,<br />
<br />
<math> k_2=f(t_n+h/2,x_n+h/2*k_1) </math>,<br />
<br />
<math> x_{n+1} = x_n + h k_2 </math>.<br />
<br />
Implementacja wygląda następująco<br />
<br />
<source lang="python"><br />
<br />
def Heun(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
#liczba krokow czasowych<br />
N=len(t)<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
k1=f(t[i-1],x[i-1])<br />
k2=f(t[i-1]+h*0.5,x[i-1]+0.5*h*k1)<br />
x[i]=np.array(x[i-1]+h*0.5*(k1+k2))<br />
i+=1<br />
return t,x<br />
</source><br />
<br />
==Runge-Kutta czwartego rzędu==<br />
Ostatnią metodą, którą omówimy jest najbardziej popularna metoda zwana w skrócie RK4. Metoda ta uznawana jest za kanoniczną i w większości zastosowań dającą najlepsze wyniki. Metody wyższego rzędu nie wnoszą już do wyniku znaczącej poprawy. Jak sugeruje nazwa metody, jej rząd to 4, czyli w każdym kroku czasowym czterokrotnie wywołujemy funkcję f. Metoda ta zdefiniowana jest wzorami<br />
<br />
<math> k_1 = f \left( t_n, x_n \right) </math>,<br />
<br />
<math> k_2 = f \left( t_n + {h \over 2}, x_n + {1 \over 2} k_1 \right) </math>,<br />
<br />
<math> k_3 = f \left( t_n + {h \over 2}, x_n + {1 \over 2} k_2 \right) </math>,<br />
<br />
<math> k_4 = f \left( t_n + h, x_n + k_3 \right) </math>,<br />
<br />
<math> x_{n+1} = x_n + {h \over 6} (k_1 + 2k_2 + 2k_3 + k_4) </math>,<br />
<br />
a implementacja wygląda następująco<br />
<br />
<source lang="python"><br />
def RK4(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
#liczba krokow czasowych<br />
N=len(t)<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
k1=h*f(t[i-1],x[i-1])<br />
k2=h*f(t[i-1]+h*0.5,x[i-1]+0.5*k1)<br />
k3=h*f(t[i-1]+h*0.5,x[i-1]+0.5*k2)<br />
k4=h*f(t[i-1]+h,x[i-1]+k3)<br />
x[i]=np.array(x[i-1]+(k1+2.0*k2+2.0*k3+k4)/6)<br />
i+=1<br />
return t,x<br />
</source><br />
<br />
==Przykłady==<br />
===Zadanie - Wahadło matematyczne z tłumieniem i siłą wymuszającą===<br />
<br />
Rozwiąż numerycznie metodą RK4 równanie różniczkowe oscylatora harmonicznego z tłumieniem i siłą wymuszającą<br />
<br />
<math> \frac{d^2x}{dt^2} + \Gamma \frac{dx}{dt} + w_0^2 x = f_0 \cos(W t) </math>,<br />
<br />
przyjmując parametry <math> f_0 =1, w_0=1, \Gamma=0.1, h=0.1 </math>. Wykreśl zależność amplitudy drgań w funkcji częstości siły wymuszającej W, dla W z przedziału [0.1,3].<br />
===Rozwiązanie===<br />
Zacznijmy od sprowadzenia równania drugiego stopnia do równania pierwszego stopnia i zapisania go w postaci funkcji<br />
<br />
<source lang="python"><br />
def oscylator(t,y):<br />
f0=1.0<br />
w0=1.0<br />
Gamma=0.1<br />
<br />
x=y[0]<br />
xdot=y[1]<br />
return np.array([xdot,f0*np.cos(oscylator.W*t)-oscylator.Gamma*xdot-w0*w0*x])<br />
<br />
oscylator.W=1.0<br />
</source><br />
Nieprzypadkowo parametr W nie jest definiowany w samej funkcji jako zmienna wewnętrzna, ale jako atrybut obiektu jakim jest funkcja. Dzięki takiej konstrukcji łatwo będzie nam zmieniać parametr W, czyli częstość siły wymuszającej. Zobaczmy jak wygląda trajektoria będąca rozwiązaniem tego równania dla warunku początkowego [0,1] i czasu końcowego równego 400.<br />
<source lang="python"><br />
py.plot(*RK4(oscylator,[0.0,1.0],0.0,400.0,0.1))<br />
py.show()<br />
</source><br />
<br />
[[Plik:oscy1.png]]<br />
<br />
Widać, że początkowo układ dochodzi do stanu regularnych oscylacji. Do analizy amplitudy interesuje nas jedynie końcowa część więc ograniczymy się do analizy trajektorii od czasu 200 do czasu 400. Napiszmy teraz funkcję, która na podstawie trajektorii wyznaczy nam amplitudę oscylacji<br />
<br />
<source lang="python"><br />
def amplituda(x):<br />
lista=x[2000:,0]<br />
return (max(lista)-min(lista))*0.5<br />
</source><br />
<br />
Interesować nas będzie amplituda drgań w funkcji częstość W. Wygenerujmy listę wartości W dla których będziemy liczyć amplitudę.<br />
<br />
<source lang="python"><br />
Omegas=np.arange(0.1,3.0,0.05)<br />
</source><br />
Możemy teraz dla każdej z wartości W rozwiązać numerycznie równanie różniczkowe i wyznaczyć odpowiadającą amplitudę oscylacji<br />
<source lang="python"><br />
amp=[amplituda(RK4(oscylator,[0.0,1.0],0.0,400.0,0.1)[1]) for oscylator.W in Omegas]<br />
</source><br />
Wynik koncowy wyglada następująco<br />
<source lang="python"><br />
py.plot(Omegas,amp)<br />
py.show()<br />
</source><br />
[[Plik:oscy2.png]]<br />
<br />
Jak można było się domyślić amplituda jest największa gdy częstotliwość wymuszania W pokrywa się z wartością częstotliwości drgań własnych <math> w_0=1 </math><br />
<br />
===Zadanie - Układ Lorenza===<br />
Rozwiąż układ równań różniczkowych Lorenza dany wzorami<br />
<br />
<math> \begin{cases}\dot x=\sigma y-\sigma x\\\dot y=-xz+rx-y\\\dot z=xy-bz\end{cases}, </math> <br />
<br />
metodą całkowania Rungego–Kutty drugiego rzędu z α = 2/3, czyli<br />
<br />
<math>k_1 = f(t_n,x_n)</math> , <br />
<br />
<math>k_2 = f(t_n + \tfrac{2}{3}h, x_n + \tfrac{2}{3}h k_1)</math> , <br />
<br />
<math>x_{n+1} = x_n + h \left(\tfrac{1}{4}k_1+\tfrac{3}{4} k_2 \right). </math> <br />
<br />
Przyjmij sigma=10, b=8/3, r=99.96, krok czasowy h=0.005 i warunki początkowe x=1,y=0,z=0. Wykonaj 8000 kroków czasowych. Układ po pewnym czasie zacznie poruszać się po pewnej periodycznej trajektorii. Wykonaj 3 rysunki TEJ PERIODYCZNEJ TRAJEKTORII (bez okresu dochodzenia do niej) w płaszczyznach (x,y), (y,z) i (z,x). Wypisz na ekranie przedziały wartości jakie przyjmują zmienne x,y i z na periodycznej trajektorii oraz okres trajektorii periodycznej.<br />
===Rozwiązanie===<br />
Zacznijmu od implementacji podanej w treści zadania metody całkowania RK2<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
#rozwiazujemy rownanie dx(t)/dt=f(t,x)<br />
<br />
#metoda Explicit Euler<br />
#f - funkcja f z rownania<br />
#x0-wartosc poczatkowa<br />
#t0-czas poczatkowy<br />
#tk-czas koncowy<br />
#h-krok czasowy<br />
<br />
def RK2(f,x0,t0,tk,h): <br />
<br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
<br />
#index<br />
i=1<br />
<br />
#petla glowna<br />
while (i<N):<br />
k1=h*f(t[i-1],x[i-1])<br />
k2=h*f(t[i-1]+h*2.0/3.0,x[i-1]+k1*2.0/3.0)<br />
x[i]=np.array(x[i-1]+0.25*k1+0.75*k2)<br />
i+=1<br />
return t,x<br />
</source><br />
Następnie napiszmy funkcję opisującą układ Lorenza<br />
<source lang="python"><br />
def Lorenza(t,y):<br />
sigma=10.0<br />
b=8.0/3<br />
r=99.96<br />
xx=y[0]<br />
yy=y[1]<br />
zz=y[2]<br />
xdot=sigma*(yy-xx)<br />
ydot=-xx*zz+r*xx-yy<br />
zdot=xx*yy-b*zz<br />
return np.array([xdot,ydot,zdot])<br />
</source><br />
<br />
Zobaczmy teraz jak wyglądają trajektorie wszystkich trzech współrzędnych rozwiązania z zadanymi parametrami<br />
<source lang="python"><br />
t,x=RK2(Lorenza,[1.0,0.0,0.0],0.0,40.0,0.005)<br />
py.plot(t,x[:,1])<br />
py.show()<br />
py.plot(t,x[:,2])<br />
py.show()<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:lor1.png]]<br />
<br />
[[Plik:lor2.png]]<br />
<br />
[[Plik:lor3.png]]<br />
<br />
Możemy zauważyć że układ początkowo zachowuje się chaotycznie a potem dąży do pewnego stanu ustalonego (tzw. atraktora). Cała trajektoria składa się z 8000 punktów, przyjmijmy że powyżej punktu o numerze 2500 mamy już do czynienia tylko z periodyczną trajektorią. Wykreślmy zatem portrety fazowe o których mowa w treści zadania<br />
<source lang="python"><br />
py.plot(x[2500:,0],x[2500:,1])<br />
py.show()<br />
py.plot(x[2500:,1],x[2500:,2])<br />
py.show()<br />
py.plot(x[2500:,2],x[2500:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:lor4.png]]<br />
<br />
[[Plik:lor5.png]]<br />
<br />
[[Plik:lor6.png]]<br />
<br />
Wypisanie zakresów jest już tylko formalnością<br />
<source lang="python"><br />
print 'zmienna x przyjmuje wartosci z zakresu: (',min(x[2500:,0]),',',max(x[2500:,0]),')'<br />
print 'zmienna y przyjmuje wartosci z zakresu: (',min(x[2500:,1]),',',max(x[2500:,1]),')'<br />
print 'zmienna z przyjmuje wartosci z zakresu: (',min(x[2500:,2]),',',max(x[2500:,2]),')'<br />
</source><br />
Okres trajektorii periodycznej możemy znaleść na przykład w ten sposób<br />
<source lang="python"><br />
prog=140<br />
lista=[]<br />
for i in range(2500,8000):<br />
if (x[i-1,2]<prog) and (x[i,2]>prog): lista.append(i)<br />
print 'okres to:',np.mean(np.diff(lista)*0.005)<br />
<br />
>>> okres to: 1.0975<br />
>>> zmienna x przyjmuje wartosci z zakresu: ( -33.4431203059 , 25.8037953495 )<br />
>>> zmienna y przyjmuje wartosci z zakresu: ( -56.7169238157 , 37.3166709986 )<br />
>>> zmienna z przyjmuje wartosci z zakresu: ( 53.4652816712 , 144.264397579 )<br />
<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/RRZ&diff=3606TI/Programowanie dla Fizyków Medycznych/RRZ2015-06-09T12:03:57Z<p>Tgubiec: /* Runge-Kutta czwartego rzędu */</p>
<hr />
<div>__NOTOC__<br />
==Równania różniczkowe zwyczajne==<br />
Zajmiemy się teraz problemem numerycznego rozwiązywania równań różniczkowych zwyczajnych o postaci:<br />
<br />
<!--<br />
{{Hide in print|This text will not be shown in the print.}}<br />
{{Only in print|This text will only be shown in the print.}}<br />
--><br />
<br />
<math> \frac{dy(t)}{dt} = f(t,y(t))</math>,<br />
<br />
z warunkeim początkowym<br />
<br />
<math>y(t_0)=y_0</math>.<br />
<br />
Zauważmy, że przykładowe równanie różniczkowe drugiego rzędu<br />
<br />
<math> \frac{d^2 x(t)}{dt^2} = \omega(t,x(t))</math>,<br />
<br />
można zapisać jako<br />
<br />
<math> \frac{d}{dt} \binom{x(t)}{x'(t)} = \binom{x'(t)}{\omega(t,x(t))}</math>.<br />
<br />
W analogiczny sposób równanie dowolnego rzędy możemy zapisać jako wektorowe równanie różniczkowe pierwszego rzędu. Wystarczy zatem, że skupimy się na rozwiązywaniu równań pierwszego rzędu, Rozwiązaniem postawionego problemu są ciągłe funkcje zmiennej czasowej t. Rozwiązanie numeryczne takiego problemu ogranicza się jednak do znalezienia wartości funkcji y(t) w skończonej liczbie punktów czasowych. W najprostrzym przypadku (do którego się tutaj ograniczymy) zakładamy, że punkty te są od siebie równo oddalone, a odległość między nimi nazywamy krokiem czasowym i tradycyjnie oznaczamy literą h. Zatem rozwiązanie równania na przedziale <math>(t_0,t_k)</math> sprwadzamy do rozwiązania w sekwencji czasów <math>t_0, t+1=t_0+h,t_2=t_0+2h,...,t_k=Nh</math>. Poprzez <math>x_n</math> oznaczać będziemy numeryczne przybliżenie ścisłego rozwiązania <math>x(t_n)</math>.<br />
<br />
==Metoda Eulera==<br />
Najprostszą metodą numeryczną rozwiązywania równań różniczkowych jest metoda Eulera. Przybliżmy pochodną czasową występującą po lewej stronie równania przez iloraz różnicowy<br />
<br />
<math> \frac{d x(t)}{dt} \approx \frac{x(t+h)-x(t)}{h}</math>,<br />
<br />
przekształcając uzyskujemy<br />
<br />
<math> x(t+h) \approx x(t)+ h \frac{d x(t)}{dt} </math><br />
<br />
a po podstawieniu rozwiązywanego równania mamy<br />
<br />
<math> x(t+h) \approx x(t)+ h f(t,x(t)) </math>.<br />
<br />
Możemy to zapisać w postaci dyskretnej<br />
<br />
<math> x_{n+1} = x_n + h f(t_n,x_n) </math>.<br />
<br />
Wartość w kolejnej chwili czasu dana jest explicite poprzez wartość w chwili poprzedniej. Metoda ta nazywa się Explicit Euler. Możemy teraz zaimplementować ją w pythonie<br />
<br />
<!--<source lang="python">--><br />
<syntaxhighlight lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
#rozwiazujemy rownanie dx(t)/dt=f(t,x)<br />
<br />
#metoda Explicit Euler<br />
#f - funkcja f z rownania<br />
#x0-wartosc poczatkowa<br />
#t0-czas poczatkowy<br />
#tk-czas koncowy<br />
#h-krok czasowy<br />
<br />
def EE(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0))) # gdy mamy do czynienia w równaniem wektorowym<br />
else: x=np.zeros(N) #dla przypadku skalarnego<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
x[i]=np.array(x[i-1]+h*f(t[i-1],x[i-1]))<br />
i+=1<br />
return t,x<br />
</syntaxhighlight><br />
<!--</source>--><br />
Najłatwiej będzie przetestować napisaną metodę na równaniu, którego ścisłe rozwiązanie jest znane. Zacznijmy zatem od równania oscylatora harmonicznego<br />
<source lang="python"><br />
def oscylator(t,y):<br />
x=y[0]<br />
xdot=y[1]<br />
return np.array([xdot,-x])<br />
</source><br />
Rozwiążmy to równanie z warunkiem początkowym [1.0,1.0] i od czasu od 0 do 100.<br />
<source lang="python"><br />
t,x=EE(oscylator,[1.0,1.0],0.0,100,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
rozwiązanie wygląda wówczas następująco<br />
<br />
[[Plik:img01.png]]<br />
<br />
Jeżeli zaś wydłużymy czas symulacji do 1000 otrzymamy<br />
<br />
[[Plik:img2.png]]<br />
<br />
Amplituda oscylacji rośnie wykładniczo i rozwiązanie numeryczne bardzo szybko przestaje mieć cokolwiek wspólnego ze ścisłym rozwiązaniem, którego amplituda jest przecież stała. Metoda Explicit Euler już po kilku krokach czasowych przestaje przypominać ścisłe rozwiązanie. Niestety trudno jest zupełnie wyeliminować to zjawisko, za to możemy użyć metody, która znacznie wolniej będzie się oddalać od ścisłego rozwiązania. Zauważmy, że w metodzie Explicit Euler w każdym kroku czasowym tylko raz liczyliśmy wartość funkcji f. Liczbę wywołań funkcji f w każdym kroku czasowym nazywamy rzędem metody, stąd Explicit Euler jest metodą pierwszego rzędu. Wprowadźmy teraz przykładowe metody rzędu drugiego.<br />
<br />
==Metoda Żabiego Skoku== <br />
W poprzedniej metodzie liczyliśmy wartość funkcji f w chwili <math>t_n</math>, która była pochodną po czasie naszego ścisłego rozwiązania. Kolejny punkt <math>x_{n+1}</math> był liczony z przybliżenia liniowego funkcji w chwili poprzedniej. Jeżeli faktyczna trajektoria ma niezerową drugą pochodną to takie liniowe przybliżenie zawsze będzie nas oddalało od ścisłego rozwiązania. Dosyć prostym pomysłem na poprawienie zbieżności metody jest tak zwany żabi skok. Policzmy najpierw wartość zmiennej x przesuwając się w czasie o h/2 i policzmy wówczas pochodną, którą oznaczmy przez <math>k_2</math><br />
<br />
<math> k_1=f(t_n,x_n) </math>.<br />
<br />
<math> k_2=f(t_n+h/2,x_n+h/2*k_1) </math>.<br />
<br />
Następnie używamy pochodnej <math> k_2 </math> zamiast pochodnej <math> k_1 </math> do obliczenia wartości funkcji w kolejnym kroku czasowym.<br />
<br />
<math> x_{n+1} = x_n + h k_2 </math>.<br />
<br />
Przykładowa implementacja tej metody wygląda następująco<br />
<source lang="python"><br />
def leapfrog(f,x0,t0,tk,h): <br />
<br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
<br />
#index<br />
i=1<br />
<br />
#petla glowna<br />
while (i<N):<br />
k1=f(t[i-1],x[i-1])<br />
k2=f(t[i-1]+h*0.5,x[i-1]+0.5*h*k1)<br />
x[i]=np.array(x[i-1]+h*k2)<br />
i+=1<br />
return t,x<br />
</source><br />
Rozwiązanie równania oscylatora tą metodą dla identycznych jak poprzednio czasów da następujące wyniki<br />
<source lang="python"><br />
t,x=leapfrog(oscylator,[1.0,1.0],0.0,100,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:img3.png]]<br />
<br />
<source lang="python"><br />
t,x=leapfrog(oscylator,[1.0,1.0],0.0,1000,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:img4.png]]<br />
<br />
==Metoda Heuna==<br />
Kolejną metodą niewiele różniącą się od poprzedniej jest metoda Heuna. Zdefiniowana jest ona przez równania<br />
<math> k_1=f(t_n,x_n) </math>,<br />
<br />
<math> k_2=f(t_n+h/2,x_n+h/2*k_1) </math>,<br />
<br />
<math> x_{n+1} = x_n + h k_2 </math>.<br />
<br />
Implementacja wygląda następująco<br />
<br />
<source lang="python"><br />
<br />
def Heun(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
#liczba krokow czasowych<br />
N=len(t)<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
k1=f(t[i-1],x[i-1])<br />
k2=f(t[i-1]+h*0.5,x[i-1]+0.5*h*k1)<br />
x[i]=np.array(x[i-1]+h*0.5*(k1+k2))<br />
i+=1<br />
return t,x<br />
</source><br />
<br />
==Runge-Kutta czwartego rzędu==<br />
Ostatnią metodą, którą omówimy jest najbardziej popularna metoda zwana w skrócie RK4. Metoda ta uznawana jest za kanoniczną i w większości zastosowań dającą najlepsze wyniki. Metody wyższego rzędu nie wnoszą już do wyniku znaczącej poprawy. Jak sugeruje nazwa metody, jej rząd to 4, czyli w każdym kroku czasowym czterokrotnie wywołujemy funkcję f. Metoda ta zdefiniowana jest wzorami<br />
<br />
<math> k_1 = f \left( t_n, x_n \right) </math>,<br />
<br />
<math> k_2 = f \left( t_n + {h \over 2}, x_n + {1 \over 2} k_1 \right) </math>,<br />
<br />
<math> k_3 = f \left( t_n + {h \over 2}, x_n + {1 \over 2} k_2 \right) </math>,<br />
<br />
<math> k_4 = f \left( t_n + h, x_n + k_3 \right) </math>,<br />
<br />
<math> x_{n+1} = x_n + {h \over 6} (k_1 + 2k_2 + 2k_3 + k_4) </math>,<br />
<br />
a implementacja wygląda następująco<br />
<br />
<source lang="python"><br />
def RK4(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
#liczba krokow czasowych<br />
N=len(t)<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
k1=h*f(t[i-1],x[i-1])<br />
k2=h*f(t[i-1]+h*0.5,x[i-1]+0.5*k1)<br />
k3=h*f(t[i-1]+h*0.5,x[i-1]+0.5*k2)<br />
k4=h*f(t[i-1]+h,x[i-1]+k3)<br />
x[i]=np.array(x[i-1]+(k1+2.0*k2+2.0*k3+k4)/6)<br />
i+=1<br />
return t,x<br />
</source><br />
<br />
==Przykłady==<br />
===Zadanie - Wahadło matematyczne z tłumieniem i siłą wymuszającą===<br />
<br />
Rozwiąż numerycznie metodą RK4 równanie różniczkowe oscylatora harmonicznego z tłumieniem i siłą wymuszającą<br />
<br />
<math> \frac{d^2x}{dt^2} + \Gamma \frac{dx}{dt} + w_0^2 x = f_0 \cos(W t) </math>,<br />
<br />
przyjmując parametry <math> f_0 =1, w_0=1, \Gamma=0.1, h=0.1 </math>. Wykreśl zależność amplitudy drgań w funkcji częstości siły wymuszającej W, dla W z przedziału [0.1,3].<br />
===Rozwiązanie===<br />
Zacznijmy od sprowadzenia równania drugiego stopnia do równania pierszego stopnia i zapisania go w postaci funkcji<br />
<br />
<source lang="python"><br />
def oscylator(t,y):<br />
f0=1.0<br />
w0=1.0<br />
Gamma=0.1<br />
<br />
x=y[0]<br />
xdot=y[1]<br />
return np.array([xdot,f0*np.cos(oscylator.W*t)-oscylator.Gamma*xdot-w0*w0*x])<br />
<br />
oscylator.W=1.0<br />
</source><br />
Nieprzypadkowo parametr W nie jest definiowany w samej funkcji jako zmienna wewnętrzna, ale jako atrybut obiektu jakim jest funkcja. Dzięki takiej konstrukcji łatwo będzie nam zmieniać parametr W czyli częstość siły wymuszającej. Zobaczmy jak wygląda trajektoria będąca rozwiązaniem tego równania dla warunku początkowego [0,1] i czasu końcowego równego 400.<br />
<source lang="python"><br />
py.plot(*RK4(oscylator,[0.0,1.0],0.0,400.0,0.1))<br />
py.show()<br />
</source><br />
<br />
[[Plik:oscy1.png]]<br />
<br />
Widać że początkowo układ dochodzi do stanu regularnych oscylacji. Do analizy amplitudy interesuje nas jedynie koncowa część więc ograniczymy się do analizy trajektorii od czasu 200 do czasu 400. Napiszmy teraz funkcję, która na podstawie trajektorii wyznaczy nam amplitudę oscylacji<br />
<br />
<source lang="python"><br />
def amplituda(x):<br />
lista=x[2000:,0]<br />
return (max(lista)-min(lista))*0.5<br />
</source><br />
<br />
Interesować nas będzie amplituda drgań w funkcji częstość W. Wygenerujmy listę wartości W dla których będziemy liczyć amplitudę.<br />
<br />
<source lang="python"><br />
Omegas=np.arange(0.1,3.0,0.05)<br />
</source><br />
Możemy teraz dla każdej z wartości W rozwiązać numerycznie równanie różniczkowe i wyznaczyć odpowiadającą amplitudę oscylacji<br />
<source lang="python"><br />
amp=[amplituda(RK4(oscylator,[0.0,1.0],0.0,400.0,0.1)[1]) for oscylator.W in Omegas]<br />
</source><br />
Wynik koncowy wyglada następująco<br />
<source lang="python"><br />
py.plot(Omegas,amp)<br />
py.show()<br />
</source><br />
[[Plik:oscy2.png]]<br />
<br />
Jak można było się domyślić amplituda jest największa gdy częstotliwość wymuszania W pokrywa się z wartością częstotliwości drgań własnych <math> w_0=1 </math><br />
<br />
===Zadanie - Układ Lorenza===<br />
Rozwiąż układ równań różniczkowych Lorenza dany wzorami<br />
<br />
<math> \begin{cases}\dot x=\sigma y-\sigma x\\\dot y=-xz+rx-y\\\dot z=xy-bz\end{cases}, </math> <br />
<br />
metodą całkowania Rungego–Kutty drugiego rzędu z α = 2/3, czyli<br />
<br />
<math>k_1 = f(t_n,x_n)</math> , <br />
<br />
<math>k_2 = f(t_n + \tfrac{2}{3}h, x_n + \tfrac{2}{3}h k_1)</math> , <br />
<br />
<math>x_{n+1} = x_n + h \left(\tfrac{1}{4}k_1+\tfrac{3}{4} k_2 \right). </math> <br />
<br />
Przyjmij sigma=10, b=8/3, r=99.96, krok czasowy h=0.005 i warunki początkowe x=1,y=0,z=0. Wykonaj 8000 kroków czasowych. Układ po pewnym czasie zacznie poruszać się po pewnej periodycznej trajektorii. Wykonaj 3 rysunki TEJ PERIODYCZNEJ TRAJEKTORII (bez okresu dochodzenia do niej) w płaszczyznach (x,y), (y,z) i (z,x). Wypisz na ekranie przedziały wartości jakie przyjmują zmienne x,y i z na periodycznej trajektorii oraz okres trajektorii periodycznej.<br />
===Rozwiązanie===<br />
Zacznijmu od implementacji podanej w treści zadania metody całkowania RK2<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
#rozwiazujemy rownanie dx(t)/dt=f(t,x)<br />
<br />
#metoda Explicit Euler<br />
#f - funkcja f z rownania<br />
#x0-wartosc poczatkowa<br />
#t0-czas poczatkowy<br />
#tk-czas koncowy<br />
#h-krok czasowy<br />
<br />
def RK2(f,x0,t0,tk,h): <br />
<br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
<br />
#index<br />
i=1<br />
<br />
#petla glowna<br />
while (i<N):<br />
k1=h*f(t[i-1],x[i-1])<br />
k2=h*f(t[i-1]+h*2.0/3.0,x[i-1]+k1*2.0/3.0)<br />
x[i]=np.array(x[i-1]+0.25*k1+0.75*k2)<br />
i+=1<br />
return t,x<br />
</source><br />
Następnie napiszmy funkcję opisującą układ Lorenza<br />
<source lang="python"><br />
def Lorenza(t,y):<br />
sigma=10.0<br />
b=8.0/3<br />
r=99.96<br />
xx=y[0]<br />
yy=y[1]<br />
zz=y[2]<br />
xdot=sigma*(yy-xx)<br />
ydot=-xx*zz+r*xx-yy<br />
zdot=xx*yy-b*zz<br />
return np.array([xdot,ydot,zdot])<br />
</source><br />
<br />
Zobaczmy teraz jak wyglądają trajektorie wszystkich trzech współrzędnych rozwiązania z zadanymi parametrami<br />
<source lang="python"><br />
t,x=RK2(Lorenza,[1.0,0.0,0.0],0.0,40.0,0.005)<br />
py.plot(t,x[:,1])<br />
py.show()<br />
py.plot(t,x[:,2])<br />
py.show()<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:lor1.png]]<br />
<br />
[[Plik:lor2.png]]<br />
<br />
[[Plik:lor3.png]]<br />
<br />
Możemy zauważyć że układ początkowo zachowuje się chaotycznie a potem dąży do pewnego stanu ustalonego (tzw. atraktora). Cała trajektoria składa się z 8000 punktów, przyjmijmy że powyżej punktu o numerze 2500 mamy już do czynienia tylko z periodyczną trajektorią. Wykreślmy zatem portrety fazowe o których mowa w treści zadania<br />
<source lang="python"><br />
py.plot(x[2500:,0],x[2500:,1])<br />
py.show()<br />
py.plot(x[2500:,1],x[2500:,2])<br />
py.show()<br />
py.plot(x[2500:,2],x[2500:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:lor4.png]]<br />
<br />
[[Plik:lor5.png]]<br />
<br />
[[Plik:lor6.png]]<br />
<br />
Wypisanie zakresów jest już tylko formalnością<br />
<source lang="python"><br />
print 'zmienna x przyjmuje wartosci z zakresu: (',min(x[2500:,0]),',',max(x[2500:,0]),')'<br />
print 'zmienna y przyjmuje wartosci z zakresu: (',min(x[2500:,1]),',',max(x[2500:,1]),')'<br />
print 'zmienna z przyjmuje wartosci z zakresu: (',min(x[2500:,2]),',',max(x[2500:,2]),')'<br />
</source><br />
Okres trajektorii periodycznej możemy znaleść na przykład w ten sposób<br />
<source lang="python"><br />
prog=140<br />
lista=[]<br />
for i in range(2500,8000):<br />
if (x[i-1,2]<prog) and (x[i,2]>prog): lista.append(i)<br />
print 'okres to:',np.mean(np.diff(lista)*0.005)<br />
<br />
>>> okres to: 1.0975<br />
>>> zmienna x przyjmuje wartosci z zakresu: ( -33.4431203059 , 25.8037953495 )<br />
>>> zmienna y przyjmuje wartosci z zakresu: ( -56.7169238157 , 37.3166709986 )<br />
>>> zmienna z przyjmuje wartosci z zakresu: ( 53.4652816712 , 144.264397579 )<br />
<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/RRZ&diff=3605TI/Programowanie dla Fizyków Medycznych/RRZ2015-06-09T12:02:36Z<p>Tgubiec: /* Metoda Eulera */</p>
<hr />
<div>__NOTOC__<br />
==Równania różniczkowe zwyczajne==<br />
Zajmiemy się teraz problemem numerycznego rozwiązywania równań różniczkowych zwyczajnych o postaci:<br />
<br />
<!--<br />
{{Hide in print|This text will not be shown in the print.}}<br />
{{Only in print|This text will only be shown in the print.}}<br />
--><br />
<br />
<math> \frac{dy(t)}{dt} = f(t,y(t))</math>,<br />
<br />
z warunkeim początkowym<br />
<br />
<math>y(t_0)=y_0</math>.<br />
<br />
Zauważmy, że przykładowe równanie różniczkowe drugiego rzędu<br />
<br />
<math> \frac{d^2 x(t)}{dt^2} = \omega(t,x(t))</math>,<br />
<br />
można zapisać jako<br />
<br />
<math> \frac{d}{dt} \binom{x(t)}{x'(t)} = \binom{x'(t)}{\omega(t,x(t))}</math>.<br />
<br />
W analogiczny sposób równanie dowolnego rzędy możemy zapisać jako wektorowe równanie różniczkowe pierwszego rzędu. Wystarczy zatem, że skupimy się na rozwiązywaniu równań pierwszego rzędu, Rozwiązaniem postawionego problemu są ciągłe funkcje zmiennej czasowej t. Rozwiązanie numeryczne takiego problemu ogranicza się jednak do znalezienia wartości funkcji y(t) w skończonej liczbie punktów czasowych. W najprostrzym przypadku (do którego się tutaj ograniczymy) zakładamy, że punkty te są od siebie równo oddalone, a odległość między nimi nazywamy krokiem czasowym i tradycyjnie oznaczamy literą h. Zatem rozwiązanie równania na przedziale <math>(t_0,t_k)</math> sprwadzamy do rozwiązania w sekwencji czasów <math>t_0, t+1=t_0+h,t_2=t_0+2h,...,t_k=Nh</math>. Poprzez <math>x_n</math> oznaczać będziemy numeryczne przybliżenie ścisłego rozwiązania <math>x(t_n)</math>.<br />
<br />
==Metoda Eulera==<br />
Najprostszą metodą numeryczną rozwiązywania równań różniczkowych jest metoda Eulera. Przybliżmy pochodną czasową występującą po lewej stronie równania przez iloraz różnicowy<br />
<br />
<math> \frac{d x(t)}{dt} \approx \frac{x(t+h)-x(t)}{h}</math>,<br />
<br />
przekształcając uzyskujemy<br />
<br />
<math> x(t+h) \approx x(t)+ h \frac{d x(t)}{dt} </math><br />
<br />
a po podstawieniu rozwiązywanego równania mamy<br />
<br />
<math> x(t+h) \approx x(t)+ h f(t,x(t)) </math>.<br />
<br />
Możemy to zapisać w postaci dyskretnej<br />
<br />
<math> x_{n+1} = x_n + h f(t_n,x_n) </math>.<br />
<br />
Wartość w kolejnej chwili czasu dana jest explicite poprzez wartość w chwili poprzedniej. Metoda ta nazywa się Explicit Euler. Możemy teraz zaimplementować ją w pythonie<br />
<br />
<!--<source lang="python">--><br />
<syntaxhighlight lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
#rozwiazujemy rownanie dx(t)/dt=f(t,x)<br />
<br />
#metoda Explicit Euler<br />
#f - funkcja f z rownania<br />
#x0-wartosc poczatkowa<br />
#t0-czas poczatkowy<br />
#tk-czas koncowy<br />
#h-krok czasowy<br />
<br />
def EE(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0))) # gdy mamy do czynienia w równaniem wektorowym<br />
else: x=np.zeros(N) #dla przypadku skalarnego<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
x[i]=np.array(x[i-1]+h*f(t[i-1],x[i-1]))<br />
i+=1<br />
return t,x<br />
</syntaxhighlight><br />
<!--</source>--><br />
Najłatwiej będzie przetestować napisaną metodę na równaniu, którego ścisłe rozwiązanie jest znane. Zacznijmy zatem od równania oscylatora harmonicznego<br />
<source lang="python"><br />
def oscylator(t,y):<br />
x=y[0]<br />
xdot=y[1]<br />
return np.array([xdot,-x])<br />
</source><br />
Rozwiążmy to równanie z warunkiem początkowym [1.0,1.0] i od czasu od 0 do 100.<br />
<source lang="python"><br />
t,x=EE(oscylator,[1.0,1.0],0.0,100,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
rozwiązanie wygląda wówczas następująco<br />
<br />
[[Plik:img01.png]]<br />
<br />
Jeżeli zaś wydłużymy czas symulacji do 1000 otrzymamy<br />
<br />
[[Plik:img2.png]]<br />
<br />
Amplituda oscylacji rośnie wykładniczo i rozwiązanie numeryczne bardzo szybko przestaje mieć cokolwiek wspólnego ze ścisłym rozwiązaniem, którego amplituda jest przecież stała. Metoda Explicit Euler już po kilku krokach czasowych przestaje przypominać ścisłe rozwiązanie. Niestety trudno jest zupełnie wyeliminować to zjawisko, za to możemy użyć metody, która znacznie wolniej będzie się oddalać od ścisłego rozwiązania. Zauważmy, że w metodzie Explicit Euler w każdym kroku czasowym tylko raz liczyliśmy wartość funkcji f. Liczbę wywołań funkcji f w każdym kroku czasowym nazywamy rzędem metody, stąd Explicit Euler jest metodą pierwszego rzędu. Wprowadźmy teraz przykładowe metody rzędu drugiego.<br />
<br />
==Metoda Żabiego Skoku== <br />
W poprzedniej metodzie liczyliśmy wartość funkcji f w chwili <math>t_n</math>, która była pochodną po czasie naszego ścisłego rozwiązania. Kolejny punkt <math>x_{n+1}</math> był liczony z przybliżenia liniowego funkcji w chwili poprzedniej. Jeżeli faktyczna trajektoria ma niezerową drugą pochodną to takie liniowe przybliżenie zawsze będzie nas oddalało od ścisłego rozwiązania. Dosyć prostym pomysłem na poprawienie zbieżności metody jest tak zwany żabi skok. Policzmy najpierw wartość zmiennej x przesuwając się w czasie o h/2 i policzmy wówczas pochodną, którą oznaczmy przez <math>k_2</math><br />
<br />
<math> k_1=f(t_n,x_n) </math>.<br />
<br />
<math> k_2=f(t_n+h/2,x_n+h/2*k_1) </math>.<br />
<br />
Następnie używamy pochodnej <math> k_2 </math> zamiast pochodnej <math> k_1 </math> do obliczenia wartości funkcji w kolejnym kroku czasowym.<br />
<br />
<math> x_{n+1} = x_n + h k_2 </math>.<br />
<br />
Przykładowa implementacja tej metody wygląda następująco<br />
<source lang="python"><br />
def leapfrog(f,x0,t0,tk,h): <br />
<br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
<br />
#index<br />
i=1<br />
<br />
#petla glowna<br />
while (i<N):<br />
k1=f(t[i-1],x[i-1])<br />
k2=f(t[i-1]+h*0.5,x[i-1]+0.5*h*k1)<br />
x[i]=np.array(x[i-1]+h*k2)<br />
i+=1<br />
return t,x<br />
</source><br />
Rozwiązanie równania oscylatora tą metodą dla identycznych jak poprzednio czasów da następujące wyniki<br />
<source lang="python"><br />
t,x=leapfrog(oscylator,[1.0,1.0],0.0,100,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:img3.png]]<br />
<br />
<source lang="python"><br />
t,x=leapfrog(oscylator,[1.0,1.0],0.0,1000,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:img4.png]]<br />
<br />
==Metoda Heuna==<br />
Kolejną metodą niewiele różniącą się od poprzedniej jest metoda Heuna. Zdefiniowana jest ona przez równania<br />
<math> k_1=f(t_n,x_n) </math>,<br />
<br />
<math> k_2=f(t_n+h/2,x_n+h/2*k_1) </math>,<br />
<br />
<math> x_{n+1} = x_n + h k_2 </math>.<br />
<br />
Implementacja wygląda następująco<br />
<br />
<source lang="python"><br />
<br />
def Heun(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
#liczba krokow czasowych<br />
N=len(t)<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
k1=f(t[i-1],x[i-1])<br />
k2=f(t[i-1]+h*0.5,x[i-1]+0.5*h*k1)<br />
x[i]=np.array(x[i-1]+h*0.5*(k1+k2))<br />
i+=1<br />
return t,x<br />
</source><br />
<br />
==Runge-Kutta czwartego rzędu==<br />
Ostatnią metodą, którą omówimy jest najbardziej popularna metoda zwana w skrócie RK4. Metoda to uznawana jest za kanoniczną i w większości zastosowań dającą najlepsze wyniki. Metody wyższego rzędu nie wnoszą już do wyniku znaczącej poprawy. Jak sugeruje nazwa metody, jej rząd to 4, czyli w każdym kroku czasowym czterokrotnie wywołujemy funkcję f. Metoda ta zdefiniowana jest wzorami<br />
<br />
<math> k_1 = f \left( t_n, x_n \right) </math>,<br />
<br />
<math> k_2 = f \left( t_n + {h \over 2}, x_n + {1 \over 2} k_1 \right) </math>,<br />
<br />
<math> k_3 = f \left( t_n + {h \over 2}, x_n + {1 \over 2} k_2 \right) </math>,<br />
<br />
<math> k_4 = f \left( t_n + h, x_n + k_3 \right) </math>,<br />
<br />
<math> x_{n+1} = x_n + {h \over 6} (k_1 + 2k_2 + 2k_3 + k_4) </math>,<br />
<br />
a implementacja wygląda następująco<br />
<br />
<source lang="python"><br />
def RK4(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
#liczba krokow czasowych<br />
N=len(t)<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
k1=h*f(t[i-1],x[i-1])<br />
k2=h*f(t[i-1]+h*0.5,x[i-1]+0.5*k1)<br />
k3=h*f(t[i-1]+h*0.5,x[i-1]+0.5*k2)<br />
k4=h*f(t[i-1]+h,x[i-1]+k3)<br />
x[i]=np.array(x[i-1]+(k1+2.0*k2+2.0*k3+k4)/6)<br />
i+=1<br />
return t,x<br />
</source><br />
==Przykłady==<br />
===Zadanie - Wahadło matematyczne z tłumieniem i siłą wymuszającą===<br />
<br />
Rozwiąż numerycznie metodą RK4 równanie różniczkowe oscylatora harmonicznego z tłumieniem i siłą wymuszającą<br />
<br />
<math> \frac{d^2x}{dt^2} + \Gamma \frac{dx}{dt} + w_0^2 x = f_0 \cos(W t) </math>,<br />
<br />
przyjmując parametry <math> f_0 =1, w_0=1, \Gamma=0.1, h=0.1 </math>. Wykreśl zależność amplitudy drgań w funkcji częstości siły wymuszającej W, dla W z przedziału [0.1,3].<br />
===Rozwiązanie===<br />
Zacznijmy od sprowadzenia równania drugiego stopnia do równania pierszego stopnia i zapisania go w postaci funkcji<br />
<br />
<source lang="python"><br />
def oscylator(t,y):<br />
f0=1.0<br />
w0=1.0<br />
Gamma=0.1<br />
<br />
x=y[0]<br />
xdot=y[1]<br />
return np.array([xdot,f0*np.cos(oscylator.W*t)-oscylator.Gamma*xdot-w0*w0*x])<br />
<br />
oscylator.W=1.0<br />
</source><br />
Nieprzypadkowo parametr W nie jest definiowany w samej funkcji jako zmienna wewnętrzna, ale jako atrybut obiektu jakim jest funkcja. Dzięki takiej konstrukcji łatwo będzie nam zmieniać parametr W czyli częstość siły wymuszającej. Zobaczmy jak wygląda trajektoria będąca rozwiązaniem tego równania dla warunku początkowego [0,1] i czasu końcowego równego 400.<br />
<source lang="python"><br />
py.plot(*RK4(oscylator,[0.0,1.0],0.0,400.0,0.1))<br />
py.show()<br />
</source><br />
<br />
[[Plik:oscy1.png]]<br />
<br />
Widać że początkowo układ dochodzi do stanu regularnych oscylacji. Do analizy amplitudy interesuje nas jedynie koncowa część więc ograniczymy się do analizy trajektorii od czasu 200 do czasu 400. Napiszmy teraz funkcję, która na podstawie trajektorii wyznaczy nam amplitudę oscylacji<br />
<br />
<source lang="python"><br />
def amplituda(x):<br />
lista=x[2000:,0]<br />
return (max(lista)-min(lista))*0.5<br />
</source><br />
<br />
Interesować nas będzie amplituda drgań w funkcji częstość W. Wygenerujmy listę wartości W dla których będziemy liczyć amplitudę.<br />
<br />
<source lang="python"><br />
Omegas=np.arange(0.1,3.0,0.05)<br />
</source><br />
Możemy teraz dla każdej z wartości W rozwiązać numerycznie równanie różniczkowe i wyznaczyć odpowiadającą amplitudę oscylacji<br />
<source lang="python"><br />
amp=[amplituda(RK4(oscylator,[0.0,1.0],0.0,400.0,0.1)[1]) for oscylator.W in Omegas]<br />
</source><br />
Wynik koncowy wyglada następująco<br />
<source lang="python"><br />
py.plot(Omegas,amp)<br />
py.show()<br />
</source><br />
[[Plik:oscy2.png]]<br />
<br />
Jak można było się domyślić amplituda jest największa gdy częstotliwość wymuszania W pokrywa się z wartością częstotliwości drgań własnych <math> w_0=1 </math><br />
<br />
===Zadanie - Układ Lorenza===<br />
Rozwiąż układ równań różniczkowych Lorenza dany wzorami<br />
<br />
<math> \begin{cases}\dot x=\sigma y-\sigma x\\\dot y=-xz+rx-y\\\dot z=xy-bz\end{cases}, </math> <br />
<br />
metodą całkowania Rungego–Kutty drugiego rzędu z α = 2/3, czyli<br />
<br />
<math>k_1 = f(t_n,x_n)</math> , <br />
<br />
<math>k_2 = f(t_n + \tfrac{2}{3}h, x_n + \tfrac{2}{3}h k_1)</math> , <br />
<br />
<math>x_{n+1} = x_n + h \left(\tfrac{1}{4}k_1+\tfrac{3}{4} k_2 \right). </math> <br />
<br />
Przyjmij sigma=10, b=8/3, r=99.96, krok czasowy h=0.005 i warunki początkowe x=1,y=0,z=0. Wykonaj 8000 kroków czasowych. Układ po pewnym czasie zacznie poruszać się po pewnej periodycznej trajektorii. Wykonaj 3 rysunki TEJ PERIODYCZNEJ TRAJEKTORII (bez okresu dochodzenia do niej) w płaszczyznach (x,y), (y,z) i (z,x). Wypisz na ekranie przedziały wartości jakie przyjmują zmienne x,y i z na periodycznej trajektorii oraz okres trajektorii periodycznej.<br />
===Rozwiązanie===<br />
Zacznijmu od implementacji podanej w treści zadania metody całkowania RK2<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
#rozwiazujemy rownanie dx(t)/dt=f(t,x)<br />
<br />
#metoda Explicit Euler<br />
#f - funkcja f z rownania<br />
#x0-wartosc poczatkowa<br />
#t0-czas poczatkowy<br />
#tk-czas koncowy<br />
#h-krok czasowy<br />
<br />
def RK2(f,x0,t0,tk,h): <br />
<br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
<br />
#index<br />
i=1<br />
<br />
#petla glowna<br />
while (i<N):<br />
k1=h*f(t[i-1],x[i-1])<br />
k2=h*f(t[i-1]+h*2.0/3.0,x[i-1]+k1*2.0/3.0)<br />
x[i]=np.array(x[i-1]+0.25*k1+0.75*k2)<br />
i+=1<br />
return t,x<br />
</source><br />
Następnie napiszmy funkcję opisującą układ Lorenza<br />
<source lang="python"><br />
def Lorenza(t,y):<br />
sigma=10.0<br />
b=8.0/3<br />
r=99.96<br />
xx=y[0]<br />
yy=y[1]<br />
zz=y[2]<br />
xdot=sigma*(yy-xx)<br />
ydot=-xx*zz+r*xx-yy<br />
zdot=xx*yy-b*zz<br />
return np.array([xdot,ydot,zdot])<br />
</source><br />
<br />
Zobaczmy teraz jak wyglądają trajektorie wszystkich trzech współrzędnych rozwiązania z zadanymi parametrami<br />
<source lang="python"><br />
t,x=RK2(Lorenza,[1.0,0.0,0.0],0.0,40.0,0.005)<br />
py.plot(t,x[:,1])<br />
py.show()<br />
py.plot(t,x[:,2])<br />
py.show()<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:lor1.png]]<br />
<br />
[[Plik:lor2.png]]<br />
<br />
[[Plik:lor3.png]]<br />
<br />
Możemy zauważyć że układ początkowo zachowuje się chaotycznie a potem dąży do pewnego stanu ustalonego (tzw. atraktora). Cała trajektoria składa się z 8000 punktów, przyjmijmy że powyżej punktu o numerze 2500 mamy już do czynienia tylko z periodyczną trajektorią. Wykreślmy zatem portrety fazowe o których mowa w treści zadania<br />
<source lang="python"><br />
py.plot(x[2500:,0],x[2500:,1])<br />
py.show()<br />
py.plot(x[2500:,1],x[2500:,2])<br />
py.show()<br />
py.plot(x[2500:,2],x[2500:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:lor4.png]]<br />
<br />
[[Plik:lor5.png]]<br />
<br />
[[Plik:lor6.png]]<br />
<br />
Wypisanie zakresów jest już tylko formalnością<br />
<source lang="python"><br />
print 'zmienna x przyjmuje wartosci z zakresu: (',min(x[2500:,0]),',',max(x[2500:,0]),')'<br />
print 'zmienna y przyjmuje wartosci z zakresu: (',min(x[2500:,1]),',',max(x[2500:,1]),')'<br />
print 'zmienna z przyjmuje wartosci z zakresu: (',min(x[2500:,2]),',',max(x[2500:,2]),')'<br />
</source><br />
Okres trajektorii periodycznej możemy znaleść na przykład w ten sposób<br />
<source lang="python"><br />
prog=140<br />
lista=[]<br />
for i in range(2500,8000):<br />
if (x[i-1,2]<prog) and (x[i,2]>prog): lista.append(i)<br />
print 'okres to:',np.mean(np.diff(lista)*0.005)<br />
<br />
>>> okres to: 1.0975<br />
>>> zmienna x przyjmuje wartosci z zakresu: ( -33.4431203059 , 25.8037953495 )<br />
>>> zmienna y przyjmuje wartosci z zakresu: ( -56.7169238157 , 37.3166709986 )<br />
>>> zmienna z przyjmuje wartosci z zakresu: ( 53.4652816712 , 144.264397579 )<br />
<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/RRZ&diff=3604TI/Programowanie dla Fizyków Medycznych/RRZ2015-06-09T12:02:10Z<p>Tgubiec: /* Metoda Żabiego Skoku */</p>
<hr />
<div>__NOTOC__<br />
==Równania różniczkowe zwyczajne==<br />
Zajmiemy się teraz problemem numerycznego rozwiązywania równań różniczkowych zwyczajnych o postaci:<br />
<br />
<!--<br />
{{Hide in print|This text will not be shown in the print.}}<br />
{{Only in print|This text will only be shown in the print.}}<br />
--><br />
<br />
<math> \frac{dy(t)}{dt} = f(t,y(t))</math>,<br />
<br />
z warunkeim początkowym<br />
<br />
<math>y(t_0)=y_0</math>.<br />
<br />
Zauważmy, że przykładowe równanie różniczkowe drugiego rzędu<br />
<br />
<math> \frac{d^2 x(t)}{dt^2} = \omega(t,x(t))</math>,<br />
<br />
można zapisać jako<br />
<br />
<math> \frac{d}{dt} \binom{x(t)}{x'(t)} = \binom{x'(t)}{\omega(t,x(t))}</math>.<br />
<br />
W analogiczny sposób równanie dowolnego rzędy możemy zapisać jako wektorowe równanie różniczkowe pierwszego rzędu. Wystarczy zatem, że skupimy się na rozwiązywaniu równań pierwszego rzędu, Rozwiązaniem postawionego problemu są ciągłe funkcje zmiennej czasowej t. Rozwiązanie numeryczne takiego problemu ogranicza się jednak do znalezienia wartości funkcji y(t) w skończonej liczbie punktów czasowych. W najprostrzym przypadku (do którego się tutaj ograniczymy) zakładamy, że punkty te są od siebie równo oddalone, a odległość między nimi nazywamy krokiem czasowym i tradycyjnie oznaczamy literą h. Zatem rozwiązanie równania na przedziale <math>(t_0,t_k)</math> sprwadzamy do rozwiązania w sekwencji czasów <math>t_0, t+1=t_0+h,t_2=t_0+2h,...,t_k=Nh</math>. Poprzez <math>x_n</math> oznaczać będziemy numeryczne przybliżenie ścisłego rozwiązania <math>x(t_n)</math>.<br />
<br />
==Metoda Eulera==<br />
Najprostszą metodą numeryczną rozwiązywania równań różniczkowych jest metoda Eulera. Przybliżmy pochodną czasową występującą po lewej stronie równania przez iloraz różnicowy<br />
<br />
<math> \frac{d x(t)}{dt} \approx \frac{x(t+h)-x(t)}{h}</math>,<br />
<br />
przekształcając uzyskujemy<br />
<br />
<math> x(t+h) \approx x(t)+ h \frac{d x(t)}{dt} </math><br />
<br />
a po podstawieniu rozwiązywanego równania mamy<br />
<br />
<math> x(t+h) \approx x(t)+ h f(t,x(t)) </math>.<br />
<br />
Możemy to zapisać w postaci dyskretnej<br />
<br />
<math> x_{n+1} = x_n + h f(t_n,x_n) </math>.<br />
<br />
Wartość w kolejnej chwili czasu dana jest explicite poprzez wartość w chwili poprzedniej. Metoda ta nazywa się Explicit Euler. Możemy teraz zaimplementować ją w pythonie<br />
<br />
<!--<source lang="python">--><br />
<syntaxhighlight lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
#rozwiazujemy rownanie dx(t)/dt=f(t,x)<br />
<br />
#metoda Explicit Euler<br />
#f - funkcja f z rownania<br />
#x0-wartosc poczatkowa<br />
#t0-czas poczatkowy<br />
#tk-czas koncowy<br />
#h-krok czasowy<br />
<br />
def EE(f,x0,t0,tk,h): <br />
#generujemy wektor czasowy<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0))) # gdy mamy do czynienia w równaniem wektorowym<br />
else: x=np.zeros(N) #dla przypadku skalarnego<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
x[i]=np.array(x[i-1]+h*f(t[i-1],x[i-1]))<br />
i+=1<br />
return t,x<br />
</syntaxhighlight><br />
<!--</source>--><br />
Najłatwiej będzie przetestować napisaną metodę na równaniu, którego ścisłe rozwiązanie jest znane. Zacznijmy zatem od równania oscylatora harmonicznego<br />
<source lang="python"><br />
def oscylator(t,y):<br />
x=y[0]<br />
xdot=y[1]<br />
return np.array([xdot,-x])<br />
</source><br />
Rozwiążmy to równanie z warunkiem początkowym [1.0,1.0] i od czasu od 0 do 100.<br />
<source lang="python"><br />
t,x=EE(oscylator,[1.0,1.0],0.0,100,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
rozwiązanie wygląda wówczas następująco<br />
<br />
[[Plik:img01.png]]<br />
<br />
Jeżeli zaś wydłużymy czas symulacji do 1000 otrzymamy<br />
<br />
[[Plik:img2.png]]<br />
<br />
Amplituda oscylacji rośnie wykładniczo i rozwiązanie numeryczne bardzo szybko przestaje mieć cokolwiek wspólnego ze ścisłym rozwiązaniem, którego amplituda jest przecież stała. Metoda Explicit Euler już po kilku krokach czasowych przestaje przypominać ścisłe rozwiązanie. Niestety trudno jest zupełnie wyeliminować to zjawisko, za to możemy użyć metody, która znacznie wolniej będzie się oddalać od ścisłego rozwiązania. Zauważmy, że w metodzie Explicit Euler w każdym kroku czasowym tylko raz liczyliśmy wartość funkcji f. Liczbę wywołań funkcji f w każdym kroku czasowym nazywamy rzędem metody, stąd Explicit Euler jest metodą pierwszego rzędu. Wprowadźmy teraz przykładowe metody rzędu drugiego.<br />
<br />
==Metoda Żabiego Skoku== <br />
W poprzedniej metodzie liczyliśmy wartość funkcji f w chwili <math>t_n</math>, która była pochodną po czasie naszego ścisłego rozwiązania. Kolejny punkt <math>x_{n+1}</math> był liczony z przybliżenia liniowego funkcji w chwili poprzedniej. Jeżeli faktyczna trajektoria ma niezerową drugą pochodną to takie liniowe przybliżenie zawsze będzie nas oddalało od ścisłego rozwiązania. Dosyć prostym pomysłem na poprawienie zbieżności metody jest tak zwany żabi skok. Policzmy najpierw wartość zmiennej x przesuwając się w czasie o h/2 i policzmy wówczas pochodną, którą oznaczmy przez <math>k_2</math><br />
<br />
<math> k_1=f(t_n,x_n) </math>.<br />
<br />
<math> k_2=f(t_n+h/2,x_n+h/2*k_1) </math>.<br />
<br />
Następnie używamy pochodnej <math> k_2 </math> zamiast pochodnej <math> k_1 </math> do obliczenia wartości funkcji w kolejnym kroku czasowym.<br />
<br />
<math> x_{n+1} = x_n + h k_2 </math>.<br />
<br />
Przykładowa implementacja tej metody wygląda następująco<br />
<source lang="python"><br />
def leapfrog(f,x0,t0,tk,h): <br />
<br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
<br />
#index<br />
i=1<br />
<br />
#petla glowna<br />
while (i<N):<br />
k1=f(t[i-1],x[i-1])<br />
k2=f(t[i-1]+h*0.5,x[i-1]+0.5*h*k1)<br />
x[i]=np.array(x[i-1]+h*k2)<br />
i+=1<br />
return t,x<br />
</source><br />
Rozwiązanie równania oscylatora tą metodą dla identycznych jak poprzednio czasów da następujące wyniki<br />
<source lang="python"><br />
t,x=leapfrog(oscylator,[1.0,1.0],0.0,100,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:img3.png]]<br />
<br />
<source lang="python"><br />
t,x=leapfrog(oscylator,[1.0,1.0],0.0,1000,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:img4.png]]<br />
<br />
==Metoda Heuna==<br />
Kolejną metodą niewiele różniącą się od poprzedniej jest metoda Heuna. Zdefiniowana jest ona przez równania<br />
<math> k_1=f(t_n,x_n) </math>,<br />
<br />
<math> k_2=f(t_n+h/2,x_n+h/2*k_1) </math>,<br />
<br />
<math> x_{n+1} = x_n + h k_2 </math>.<br />
<br />
Implementacja wygląda następująco<br />
<br />
<source lang="python"><br />
<br />
def Heun(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
#liczba krokow czasowych<br />
N=len(t)<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
k1=f(t[i-1],x[i-1])<br />
k2=f(t[i-1]+h*0.5,x[i-1]+0.5*h*k1)<br />
x[i]=np.array(x[i-1]+h*0.5*(k1+k2))<br />
i+=1<br />
return t,x<br />
</source><br />
<br />
==Runge-Kutta czwartego rzędu==<br />
Ostatnią metodą, którą omówimy jest najbardziej popularna metoda zwana w skrócie RK4. Metoda to uznawana jest za kanoniczną i w większości zastosowań dającą najlepsze wyniki. Metody wyższego rzędu nie wnoszą już do wyniku znaczącej poprawy. Jak sugeruje nazwa metody, jej rząd to 4, czyli w każdym kroku czasowym czterokrotnie wywołujemy funkcję f. Metoda ta zdefiniowana jest wzorami<br />
<br />
<math> k_1 = f \left( t_n, x_n \right) </math>,<br />
<br />
<math> k_2 = f \left( t_n + {h \over 2}, x_n + {1 \over 2} k_1 \right) </math>,<br />
<br />
<math> k_3 = f \left( t_n + {h \over 2}, x_n + {1 \over 2} k_2 \right) </math>,<br />
<br />
<math> k_4 = f \left( t_n + h, x_n + k_3 \right) </math>,<br />
<br />
<math> x_{n+1} = x_n + {h \over 6} (k_1 + 2k_2 + 2k_3 + k_4) </math>,<br />
<br />
a implementacja wygląda następująco<br />
<br />
<source lang="python"><br />
def RK4(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
#liczba krokow czasowych<br />
N=len(t)<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
k1=h*f(t[i-1],x[i-1])<br />
k2=h*f(t[i-1]+h*0.5,x[i-1]+0.5*k1)<br />
k3=h*f(t[i-1]+h*0.5,x[i-1]+0.5*k2)<br />
k4=h*f(t[i-1]+h,x[i-1]+k3)<br />
x[i]=np.array(x[i-1]+(k1+2.0*k2+2.0*k3+k4)/6)<br />
i+=1<br />
return t,x<br />
</source><br />
==Przykłady==<br />
===Zadanie - Wahadło matematyczne z tłumieniem i siłą wymuszającą===<br />
<br />
Rozwiąż numerycznie metodą RK4 równanie różniczkowe oscylatora harmonicznego z tłumieniem i siłą wymuszającą<br />
<br />
<math> \frac{d^2x}{dt^2} + \Gamma \frac{dx}{dt} + w_0^2 x = f_0 \cos(W t) </math>,<br />
<br />
przyjmując parametry <math> f_0 =1, w_0=1, \Gamma=0.1, h=0.1 </math>. Wykreśl zależność amplitudy drgań w funkcji częstości siły wymuszającej W, dla W z przedziału [0.1,3].<br />
===Rozwiązanie===<br />
Zacznijmy od sprowadzenia równania drugiego stopnia do równania pierszego stopnia i zapisania go w postaci funkcji<br />
<br />
<source lang="python"><br />
def oscylator(t,y):<br />
f0=1.0<br />
w0=1.0<br />
Gamma=0.1<br />
<br />
x=y[0]<br />
xdot=y[1]<br />
return np.array([xdot,f0*np.cos(oscylator.W*t)-oscylator.Gamma*xdot-w0*w0*x])<br />
<br />
oscylator.W=1.0<br />
</source><br />
Nieprzypadkowo parametr W nie jest definiowany w samej funkcji jako zmienna wewnętrzna, ale jako atrybut obiektu jakim jest funkcja. Dzięki takiej konstrukcji łatwo będzie nam zmieniać parametr W czyli częstość siły wymuszającej. Zobaczmy jak wygląda trajektoria będąca rozwiązaniem tego równania dla warunku początkowego [0,1] i czasu końcowego równego 400.<br />
<source lang="python"><br />
py.plot(*RK4(oscylator,[0.0,1.0],0.0,400.0,0.1))<br />
py.show()<br />
</source><br />
<br />
[[Plik:oscy1.png]]<br />
<br />
Widać że początkowo układ dochodzi do stanu regularnych oscylacji. Do analizy amplitudy interesuje nas jedynie koncowa część więc ograniczymy się do analizy trajektorii od czasu 200 do czasu 400. Napiszmy teraz funkcję, która na podstawie trajektorii wyznaczy nam amplitudę oscylacji<br />
<br />
<source lang="python"><br />
def amplituda(x):<br />
lista=x[2000:,0]<br />
return (max(lista)-min(lista))*0.5<br />
</source><br />
<br />
Interesować nas będzie amplituda drgań w funkcji częstość W. Wygenerujmy listę wartości W dla których będziemy liczyć amplitudę.<br />
<br />
<source lang="python"><br />
Omegas=np.arange(0.1,3.0,0.05)<br />
</source><br />
Możemy teraz dla każdej z wartości W rozwiązać numerycznie równanie różniczkowe i wyznaczyć odpowiadającą amplitudę oscylacji<br />
<source lang="python"><br />
amp=[amplituda(RK4(oscylator,[0.0,1.0],0.0,400.0,0.1)[1]) for oscylator.W in Omegas]<br />
</source><br />
Wynik koncowy wyglada następująco<br />
<source lang="python"><br />
py.plot(Omegas,amp)<br />
py.show()<br />
</source><br />
[[Plik:oscy2.png]]<br />
<br />
Jak można było się domyślić amplituda jest największa gdy częstotliwość wymuszania W pokrywa się z wartością częstotliwości drgań własnych <math> w_0=1 </math><br />
<br />
===Zadanie - Układ Lorenza===<br />
Rozwiąż układ równań różniczkowych Lorenza dany wzorami<br />
<br />
<math> \begin{cases}\dot x=\sigma y-\sigma x\\\dot y=-xz+rx-y\\\dot z=xy-bz\end{cases}, </math> <br />
<br />
metodą całkowania Rungego–Kutty drugiego rzędu z α = 2/3, czyli<br />
<br />
<math>k_1 = f(t_n,x_n)</math> , <br />
<br />
<math>k_2 = f(t_n + \tfrac{2}{3}h, x_n + \tfrac{2}{3}h k_1)</math> , <br />
<br />
<math>x_{n+1} = x_n + h \left(\tfrac{1}{4}k_1+\tfrac{3}{4} k_2 \right). </math> <br />
<br />
Przyjmij sigma=10, b=8/3, r=99.96, krok czasowy h=0.005 i warunki początkowe x=1,y=0,z=0. Wykonaj 8000 kroków czasowych. Układ po pewnym czasie zacznie poruszać się po pewnej periodycznej trajektorii. Wykonaj 3 rysunki TEJ PERIODYCZNEJ TRAJEKTORII (bez okresu dochodzenia do niej) w płaszczyznach (x,y), (y,z) i (z,x). Wypisz na ekranie przedziały wartości jakie przyjmują zmienne x,y i z na periodycznej trajektorii oraz okres trajektorii periodycznej.<br />
===Rozwiązanie===<br />
Zacznijmu od implementacji podanej w treści zadania metody całkowania RK2<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
#rozwiazujemy rownanie dx(t)/dt=f(t,x)<br />
<br />
#metoda Explicit Euler<br />
#f - funkcja f z rownania<br />
#x0-wartosc poczatkowa<br />
#t0-czas poczatkowy<br />
#tk-czas koncowy<br />
#h-krok czasowy<br />
<br />
def RK2(f,x0,t0,tk,h): <br />
<br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
<br />
#index<br />
i=1<br />
<br />
#petla glowna<br />
while (i<N):<br />
k1=h*f(t[i-1],x[i-1])<br />
k2=h*f(t[i-1]+h*2.0/3.0,x[i-1]+k1*2.0/3.0)<br />
x[i]=np.array(x[i-1]+0.25*k1+0.75*k2)<br />
i+=1<br />
return t,x<br />
</source><br />
Następnie napiszmy funkcję opisującą układ Lorenza<br />
<source lang="python"><br />
def Lorenza(t,y):<br />
sigma=10.0<br />
b=8.0/3<br />
r=99.96<br />
xx=y[0]<br />
yy=y[1]<br />
zz=y[2]<br />
xdot=sigma*(yy-xx)<br />
ydot=-xx*zz+r*xx-yy<br />
zdot=xx*yy-b*zz<br />
return np.array([xdot,ydot,zdot])<br />
</source><br />
<br />
Zobaczmy teraz jak wyglądają trajektorie wszystkich trzech współrzędnych rozwiązania z zadanymi parametrami<br />
<source lang="python"><br />
t,x=RK2(Lorenza,[1.0,0.0,0.0],0.0,40.0,0.005)<br />
py.plot(t,x[:,1])<br />
py.show()<br />
py.plot(t,x[:,2])<br />
py.show()<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:lor1.png]]<br />
<br />
[[Plik:lor2.png]]<br />
<br />
[[Plik:lor3.png]]<br />
<br />
Możemy zauważyć że układ początkowo zachowuje się chaotycznie a potem dąży do pewnego stanu ustalonego (tzw. atraktora). Cała trajektoria składa się z 8000 punktów, przyjmijmy że powyżej punktu o numerze 2500 mamy już do czynienia tylko z periodyczną trajektorią. Wykreślmy zatem portrety fazowe o których mowa w treści zadania<br />
<source lang="python"><br />
py.plot(x[2500:,0],x[2500:,1])<br />
py.show()<br />
py.plot(x[2500:,1],x[2500:,2])<br />
py.show()<br />
py.plot(x[2500:,2],x[2500:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:lor4.png]]<br />
<br />
[[Plik:lor5.png]]<br />
<br />
[[Plik:lor6.png]]<br />
<br />
Wypisanie zakresów jest już tylko formalnością<br />
<source lang="python"><br />
print 'zmienna x przyjmuje wartosci z zakresu: (',min(x[2500:,0]),',',max(x[2500:,0]),')'<br />
print 'zmienna y przyjmuje wartosci z zakresu: (',min(x[2500:,1]),',',max(x[2500:,1]),')'<br />
print 'zmienna z przyjmuje wartosci z zakresu: (',min(x[2500:,2]),',',max(x[2500:,2]),')'<br />
</source><br />
Okres trajektorii periodycznej możemy znaleść na przykład w ten sposób<br />
<source lang="python"><br />
prog=140<br />
lista=[]<br />
for i in range(2500,8000):<br />
if (x[i-1,2]<prog) and (x[i,2]>prog): lista.append(i)<br />
print 'okres to:',np.mean(np.diff(lista)*0.005)<br />
<br />
>>> okres to: 1.0975<br />
>>> zmienna x przyjmuje wartosci z zakresu: ( -33.4431203059 , 25.8037953495 )<br />
>>> zmienna y przyjmuje wartosci z zakresu: ( -56.7169238157 , 37.3166709986 )<br />
>>> zmienna z przyjmuje wartosci z zakresu: ( 53.4652816712 , 144.264397579 )<br />
<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/RRZ&diff=3603TI/Programowanie dla Fizyków Medycznych/RRZ2015-06-09T11:58:53Z<p>Tgubiec: /* Metoda Eulera */</p>
<hr />
<div>__NOTOC__<br />
==Równania różniczkowe zwyczajne==<br />
Zajmiemy się teraz problemem numerycznego rozwiązywania równań różniczkowych zwyczajnych o postaci:<br />
<br />
<!--<br />
{{Hide in print|This text will not be shown in the print.}}<br />
{{Only in print|This text will only be shown in the print.}}<br />
--><br />
<br />
<math> \frac{dy(t)}{dt} = f(t,y(t))</math>,<br />
<br />
z warunkeim początkowym<br />
<br />
<math>y(t_0)=y_0</math>.<br />
<br />
Zauważmy, że przykładowe równanie różniczkowe drugiego rzędu<br />
<br />
<math> \frac{d^2 x(t)}{dt^2} = \omega(t,x(t))</math>,<br />
<br />
można zapisać jako<br />
<br />
<math> \frac{d}{dt} \binom{x(t)}{x'(t)} = \binom{x'(t)}{\omega(t,x(t))}</math>.<br />
<br />
W analogiczny sposób równanie dowolnego rzędy możemy zapisać jako wektorowe równanie różniczkowe pierwszego rzędu. Wystarczy zatem, że skupimy się na rozwiązywaniu równań pierwszego rzędu, Rozwiązaniem postawionego problemu są ciągłe funkcje zmiennej czasowej t. Rozwiązanie numeryczne takiego problemu ogranicza się jednak do znalezienia wartości funkcji y(t) w skończonej liczbie punktów czasowych. W najprostrzym przypadku (do którego się tutaj ograniczymy) zakładamy, że punkty te są od siebie równo oddalone, a odległość między nimi nazywamy krokiem czasowym i tradycyjnie oznaczamy literą h. Zatem rozwiązanie równania na przedziale <math>(t_0,t_k)</math> sprwadzamy do rozwiązania w sekwencji czasów <math>t_0, t+1=t_0+h,t_2=t_0+2h,...,t_k=Nh</math>. Poprzez <math>x_n</math> oznaczać będziemy numeryczne przybliżenie ścisłego rozwiązania <math>x(t_n)</math>.<br />
<br />
==Metoda Eulera==<br />
Najprostszą metodą numeryczną rozwiązywania równań różniczkowych jest metoda Eulera. Przybliżmy pochodną czasową występującą po lewej stronie równania przez iloraz różnicowy<br />
<br />
<math> \frac{d x(t)}{dt} \approx \frac{x(t+h)-x(t)}{h}</math>,<br />
<br />
przekształcając uzyskujemy<br />
<br />
<math> x(t+h) \approx x(t)+ h \frac{d x(t)}{dt} </math><br />
<br />
a po podstawieniu rozwiązywanego równania mamy<br />
<br />
<math> x(t+h) \approx x(t)+ h f(t,x(t)) </math>.<br />
<br />
Możemy to zapisać w postaci dyskretnej<br />
<br />
<math> x_{n+1} = x_n + h f(t_n,x_n) </math>.<br />
<br />
Wartość w kolejnej chwili czasu dana jest explicite poprzez wartość w chwili poprzedniej. Metoda ta nazywa się Explicit Euler. Możemy teraz zaimplementować ją w pythonie<br />
<br />
<!--<source lang="python">--><br />
<syntaxhighlight lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
#rozwiazujemy rownanie dx(t)/dt=f(t,x)<br />
<br />
#metoda Explicit Euler<br />
#f - funkcja f z rownania<br />
#x0-wartosc poczatkowa<br />
#t0-czas poczatkowy<br />
#tk-czas koncowy<br />
#h-krok czasowy<br />
<br />
def EE(f,x0,t0,tk,h): <br />
#generujemy wektor czasowy<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0))) # gdy mamy do czynienia w równaniem wektorowym<br />
else: x=np.zeros(N) #dla przypadku skalarnego<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
x[i]=np.array(x[i-1]+h*f(t[i-1],x[i-1]))<br />
i+=1<br />
return t,x<br />
</syntaxhighlight><br />
<!--</source>--><br />
Najłatwiej będzie przetestować napisaną metodę na równaniu, którego ścisłe rozwiązanie jest znane. Zacznijmy zatem od równania oscylatora harmonicznego<br />
<source lang="python"><br />
def oscylator(t,y):<br />
x=y[0]<br />
xdot=y[1]<br />
return np.array([xdot,-x])<br />
</source><br />
Rozwiążmy to równanie z warunkiem początkowym [1.0,1.0] i od czasu od 0 do 100.<br />
<source lang="python"><br />
t,x=EE(oscylator,[1.0,1.0],0.0,100,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
rozwiązanie wygląda wówczas następująco<br />
<br />
[[Plik:img01.png]]<br />
<br />
Jeżeli zaś wydłużymy czas symulacji do 1000 otrzymamy<br />
<br />
[[Plik:img2.png]]<br />
<br />
Amplituda oscylacji rośnie wykładniczo i rozwiązanie numeryczne bardzo szybko przestaje mieć cokolwiek wspólnego ze ścisłym rozwiązaniem, którego amplituda jest przecież stała. Metoda Explicit Euler już po kilku krokach czasowych przestaje przypominać ścisłe rozwiązanie. Niestety trudno jest zupełnie wyeliminować to zjawisko, za to możemy użyć metody, która znacznie wolniej będzie się oddalać od ścisłego rozwiązania. Zauważmy, że w metodzie Explicit Euler w każdym kroku czasowym tylko raz liczyliśmy wartość funkcji f. Liczbę wywołań funkcji f w każdym kroku czasowym nazywamy rzędem metody, stąd Explicit Euler jest metodą pierwszego rzędu. Wprowadźmy teraz przykładowe metody rzędu drugiego.<br />
<br />
==Metoda Żabiego Skoku== <br />
W poprzedniej metodzie liczyliśmy wartość funkcji f w chwili <math>t_n</math>, która była pochodzną po czasie naszego ścisłego rozwiązania. Kolejny punkt <math>x_{n+1}</math> był liczony z przybliżenia liniowego funkcji w chwili poprzedniej. Jeżeli faktyczna trajektoria ma niezerową drugą pochodzną to takie liniowe przybliżenie zawsze będzie nas oddalało od ścisłego rozwiązania. Dosyć prostym pomysłem na poprawienie zbieżności metody jest tak zwany żabi skok. Policzmy najpierw wartość zmiennej x przesuwając się w czasie o h/2 i policzmy wówczas pochodną, którą oznaczmy przez <math>k_2</math><br />
<br />
<math> k_1=f(t_n,x_n) </math>.<br />
<br />
<math> k_2=f(t_n+h/2,x_n+h/2*k_1) </math>.<br />
<br />
Nastepnie używamy pochodznej <math> k_2 </math> zamiast pochodznej <math> k_1 </math> do obliczenia wartości funkcji w kolejnym kroku czasowym.<br />
<br />
<math> x_{n+1} = x_n + h k_2 </math>.<br />
<br />
Przykładowa implementacja tej metody wygląda nastepująco<br />
<source lang="python"><br />
def leapfrog(f,x0,t0,tk,h): <br />
<br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
<br />
#index<br />
i=1<br />
<br />
#petla glowna<br />
while (i<N):<br />
k1=f(t[i-1],x[i-1])<br />
k2=f(t[i-1]+h*0.5,x[i-1]+0.5*h*k1)<br />
x[i]=np.array(x[i-1]+h*k2)<br />
i+=1<br />
return t,x<br />
</source><br />
Rozwiązanie równania oscylatora tą metodą dla identycznych jak poprzednio czasów da nastepujące wyniki<br />
<source lang="python"><br />
t,x=leapfrog(oscylator,[1.0,1.0],0.0,100,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:img3.png]]<br />
<br />
<source lang="python"><br />
t,x=leapfrog(oscylator,[1.0,1.0],0.0,1000,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:img4.png]]<br />
<br />
==Metoda Heuna==<br />
Kolejną metodą niewiele różniącą się od poprzedniej jest metoda Heuna. Zdefiniowana jest ona przez równania<br />
<math> k_1=f(t_n,x_n) </math>,<br />
<br />
<math> k_2=f(t_n+h/2,x_n+h/2*k_1) </math>,<br />
<br />
<math> x_{n+1} = x_n + h k_2 </math>.<br />
<br />
Implementacja wygląda następująco<br />
<br />
<source lang="python"><br />
<br />
def Heun(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
#liczba krokow czasowych<br />
N=len(t)<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
k1=f(t[i-1],x[i-1])<br />
k2=f(t[i-1]+h*0.5,x[i-1]+0.5*h*k1)<br />
x[i]=np.array(x[i-1]+h*0.5*(k1+k2))<br />
i+=1<br />
return t,x<br />
</source><br />
<br />
==Runge-Kutta czwartego rzędu==<br />
Ostatnią metodą, którą omówimy jest najbardziej popularna metoda zwana w skrócie RK4. Metoda to uznawana jest za kanoniczną i w większości zastosowań dającą najlepsze wyniki. Metody wyższego rzędu nie wnoszą już do wyniku znaczącej poprawy. Jak sugeruje nazwa metody, jej rząd to 4, czyli w każdym kroku czasowym czterokrotnie wywołujemy funkcję f. Metoda ta zdefiniowana jest wzorami<br />
<br />
<math> k_1 = f \left( t_n, x_n \right) </math>,<br />
<br />
<math> k_2 = f \left( t_n + {h \over 2}, x_n + {1 \over 2} k_1 \right) </math>,<br />
<br />
<math> k_3 = f \left( t_n + {h \over 2}, x_n + {1 \over 2} k_2 \right) </math>,<br />
<br />
<math> k_4 = f \left( t_n + h, x_n + k_3 \right) </math>,<br />
<br />
<math> x_{n+1} = x_n + {h \over 6} (k_1 + 2k_2 + 2k_3 + k_4) </math>,<br />
<br />
a implementacja wygląda następująco<br />
<br />
<source lang="python"><br />
def RK4(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
#liczba krokow czasowych<br />
N=len(t)<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
k1=h*f(t[i-1],x[i-1])<br />
k2=h*f(t[i-1]+h*0.5,x[i-1]+0.5*k1)<br />
k3=h*f(t[i-1]+h*0.5,x[i-1]+0.5*k2)<br />
k4=h*f(t[i-1]+h,x[i-1]+k3)<br />
x[i]=np.array(x[i-1]+(k1+2.0*k2+2.0*k3+k4)/6)<br />
i+=1<br />
return t,x<br />
</source><br />
==Przykłady==<br />
===Zadanie - Wahadło matematyczne z tłumieniem i siłą wymuszającą===<br />
<br />
Rozwiąż numerycznie metodą RK4 równanie różniczkowe oscylatora harmonicznego z tłumieniem i siłą wymuszającą<br />
<br />
<math> \frac{d^2x}{dt^2} + \Gamma \frac{dx}{dt} + w_0^2 x = f_0 \cos(W t) </math>,<br />
<br />
przyjmując parametry <math> f_0 =1, w_0=1, \Gamma=0.1, h=0.1 </math>. Wykreśl zależność amplitudy drgań w funkcji częstości siły wymuszającej W, dla W z przedziału [0.1,3].<br />
===Rozwiązanie===<br />
Zacznijmy od sprowadzenia równania drugiego stopnia do równania pierszego stopnia i zapisania go w postaci funkcji<br />
<br />
<source lang="python"><br />
def oscylator(t,y):<br />
f0=1.0<br />
w0=1.0<br />
Gamma=0.1<br />
<br />
x=y[0]<br />
xdot=y[1]<br />
return np.array([xdot,f0*np.cos(oscylator.W*t)-oscylator.Gamma*xdot-w0*w0*x])<br />
<br />
oscylator.W=1.0<br />
</source><br />
Nieprzypadkowo parametr W nie jest definiowany w samej funkcji jako zmienna wewnętrzna, ale jako atrybut obiektu jakim jest funkcja. Dzięki takiej konstrukcji łatwo będzie nam zmieniać parametr W czyli częstość siły wymuszającej. Zobaczmy jak wygląda trajektoria będąca rozwiązaniem tego równania dla warunku początkowego [0,1] i czasu końcowego równego 400.<br />
<source lang="python"><br />
py.plot(*RK4(oscylator,[0.0,1.0],0.0,400.0,0.1))<br />
py.show()<br />
</source><br />
<br />
[[Plik:oscy1.png]]<br />
<br />
Widać że początkowo układ dochodzi do stanu regularnych oscylacji. Do analizy amplitudy interesuje nas jedynie koncowa część więc ograniczymy się do analizy trajektorii od czasu 200 do czasu 400. Napiszmy teraz funkcję, która na podstawie trajektorii wyznaczy nam amplitudę oscylacji<br />
<br />
<source lang="python"><br />
def amplituda(x):<br />
lista=x[2000:,0]<br />
return (max(lista)-min(lista))*0.5<br />
</source><br />
<br />
Interesować nas będzie amplituda drgań w funkcji częstość W. Wygenerujmy listę wartości W dla których będziemy liczyć amplitudę.<br />
<br />
<source lang="python"><br />
Omegas=np.arange(0.1,3.0,0.05)<br />
</source><br />
Możemy teraz dla każdej z wartości W rozwiązać numerycznie równanie różniczkowe i wyznaczyć odpowiadającą amplitudę oscylacji<br />
<source lang="python"><br />
amp=[amplituda(RK4(oscylator,[0.0,1.0],0.0,400.0,0.1)[1]) for oscylator.W in Omegas]<br />
</source><br />
Wynik koncowy wyglada następująco<br />
<source lang="python"><br />
py.plot(Omegas,amp)<br />
py.show()<br />
</source><br />
[[Plik:oscy2.png]]<br />
<br />
Jak można było się domyślić amplituda jest największa gdy częstotliwość wymuszania W pokrywa się z wartością częstotliwości drgań własnych <math> w_0=1 </math><br />
<br />
===Zadanie - Układ Lorenza===<br />
Rozwiąż układ równań różniczkowych Lorenza dany wzorami<br />
<br />
<math> \begin{cases}\dot x=\sigma y-\sigma x\\\dot y=-xz+rx-y\\\dot z=xy-bz\end{cases}, </math> <br />
<br />
metodą całkowania Rungego–Kutty drugiego rzędu z α = 2/3, czyli<br />
<br />
<math>k_1 = f(t_n,x_n)</math> , <br />
<br />
<math>k_2 = f(t_n + \tfrac{2}{3}h, x_n + \tfrac{2}{3}h k_1)</math> , <br />
<br />
<math>x_{n+1} = x_n + h \left(\tfrac{1}{4}k_1+\tfrac{3}{4} k_2 \right). </math> <br />
<br />
Przyjmij sigma=10, b=8/3, r=99.96, krok czasowy h=0.005 i warunki początkowe x=1,y=0,z=0. Wykonaj 8000 kroków czasowych. Układ po pewnym czasie zacznie poruszać się po pewnej periodycznej trajektorii. Wykonaj 3 rysunki TEJ PERIODYCZNEJ TRAJEKTORII (bez okresu dochodzenia do niej) w płaszczyznach (x,y), (y,z) i (z,x). Wypisz na ekranie przedziały wartości jakie przyjmują zmienne x,y i z na periodycznej trajektorii oraz okres trajektorii periodycznej.<br />
===Rozwiązanie===<br />
Zacznijmu od implementacji podanej w treści zadania metody całkowania RK2<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
#rozwiazujemy rownanie dx(t)/dt=f(t,x)<br />
<br />
#metoda Explicit Euler<br />
#f - funkcja f z rownania<br />
#x0-wartosc poczatkowa<br />
#t0-czas poczatkowy<br />
#tk-czas koncowy<br />
#h-krok czasowy<br />
<br />
def RK2(f,x0,t0,tk,h): <br />
<br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
<br />
#index<br />
i=1<br />
<br />
#petla glowna<br />
while (i<N):<br />
k1=h*f(t[i-1],x[i-1])<br />
k2=h*f(t[i-1]+h*2.0/3.0,x[i-1]+k1*2.0/3.0)<br />
x[i]=np.array(x[i-1]+0.25*k1+0.75*k2)<br />
i+=1<br />
return t,x<br />
</source><br />
Następnie napiszmy funkcję opisującą układ Lorenza<br />
<source lang="python"><br />
def Lorenza(t,y):<br />
sigma=10.0<br />
b=8.0/3<br />
r=99.96<br />
xx=y[0]<br />
yy=y[1]<br />
zz=y[2]<br />
xdot=sigma*(yy-xx)<br />
ydot=-xx*zz+r*xx-yy<br />
zdot=xx*yy-b*zz<br />
return np.array([xdot,ydot,zdot])<br />
</source><br />
<br />
Zobaczmy teraz jak wyglądają trajektorie wszystkich trzech współrzędnych rozwiązania z zadanymi parametrami<br />
<source lang="python"><br />
t,x=RK2(Lorenza,[1.0,0.0,0.0],0.0,40.0,0.005)<br />
py.plot(t,x[:,1])<br />
py.show()<br />
py.plot(t,x[:,2])<br />
py.show()<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:lor1.png]]<br />
<br />
[[Plik:lor2.png]]<br />
<br />
[[Plik:lor3.png]]<br />
<br />
Możemy zauważyć że układ początkowo zachowuje się chaotycznie a potem dąży do pewnego stanu ustalonego (tzw. atraktora). Cała trajektoria składa się z 8000 punktów, przyjmijmy że powyżej punktu o numerze 2500 mamy już do czynienia tylko z periodyczną trajektorią. Wykreślmy zatem portrety fazowe o których mowa w treści zadania<br />
<source lang="python"><br />
py.plot(x[2500:,0],x[2500:,1])<br />
py.show()<br />
py.plot(x[2500:,1],x[2500:,2])<br />
py.show()<br />
py.plot(x[2500:,2],x[2500:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:lor4.png]]<br />
<br />
[[Plik:lor5.png]]<br />
<br />
[[Plik:lor6.png]]<br />
<br />
Wypisanie zakresów jest już tylko formalnością<br />
<source lang="python"><br />
print 'zmienna x przyjmuje wartosci z zakresu: (',min(x[2500:,0]),',',max(x[2500:,0]),')'<br />
print 'zmienna y przyjmuje wartosci z zakresu: (',min(x[2500:,1]),',',max(x[2500:,1]),')'<br />
print 'zmienna z przyjmuje wartosci z zakresu: (',min(x[2500:,2]),',',max(x[2500:,2]),')'<br />
</source><br />
Okres trajektorii periodycznej możemy znaleść na przykład w ten sposób<br />
<source lang="python"><br />
prog=140<br />
lista=[]<br />
for i in range(2500,8000):<br />
if (x[i-1,2]<prog) and (x[i,2]>prog): lista.append(i)<br />
print 'okres to:',np.mean(np.diff(lista)*0.005)<br />
<br />
>>> okres to: 1.0975<br />
>>> zmienna x przyjmuje wartosci z zakresu: ( -33.4431203059 , 25.8037953495 )<br />
>>> zmienna y przyjmuje wartosci z zakresu: ( -56.7169238157 , 37.3166709986 )<br />
>>> zmienna z przyjmuje wartosci z zakresu: ( 53.4652816712 , 144.264397579 )<br />
<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/RRZ&diff=3602TI/Programowanie dla Fizyków Medycznych/RRZ2015-06-09T11:51:28Z<p>Tgubiec: </p>
<hr />
<div>__NOTOC__<br />
==Równania różniczkowe zwyczajne==<br />
Zajmiemy się teraz problemem numerycznego rozwiązywania równań różniczkowych zwyczajnych o postaci:<br />
<br />
<!--<br />
{{Hide in print|This text will not be shown in the print.}}<br />
{{Only in print|This text will only be shown in the print.}}<br />
--><br />
<br />
<math> \frac{dy(t)}{dt} = f(t,y(t))</math>,<br />
<br />
z warunkeim początkowym<br />
<br />
<math>y(t_0)=y_0</math>.<br />
<br />
Zauważmy, że przykładowe równanie różniczkowe drugiego rzędu<br />
<br />
<math> \frac{d^2 x(t)}{dt^2} = \omega(t,x(t))</math>,<br />
<br />
można zapisać jako<br />
<br />
<math> \frac{d}{dt} \binom{x(t)}{x'(t)} = \binom{x'(t)}{\omega(t,x(t))}</math>.<br />
<br />
W analogiczny sposób równanie dowolnego rzędy możemy zapisać jako wektorowe równanie różniczkowe pierwszego rzędu. Wystarczy zatem, że skupimy się na rozwiązywaniu równań pierwszego rzędu, Rozwiązaniem postawionego problemu są ciągłe funkcje zmiennej czasowej t. Rozwiązanie numeryczne takiego problemu ogranicza się jednak do znalezienia wartości funkcji y(t) w skończonej liczbie punktów czasowych. W najprostrzym przypadku (do którego się tutaj ograniczymy) zakładamy, że punkty te są od siebie równo oddalone, a odległość między nimi nazywamy krokiem czasowym i tradycyjnie oznaczamy literą h. Zatem rozwiązanie równania na przedziale <math>(t_0,t_k)</math> sprwadzamy do rozwiązania w sekwencji czasów <math>t_0, t+1=t_0+h,t_2=t_0+2h,...,t_k=Nh</math>. Poprzez <math>x_n</math> oznaczać będziemy numeryczne przybliżenie ścisłego rozwiązania <math>x(t_n)</math>.<br />
<br />
==Metoda Eulera==<br />
Najprostszą metodą numeryczną rozwiązywania równań różniczkowych jest metoda Eulera. Przybliżmy pochodzną czasową występującą po lewej stronie równania przez iloraz różnicowy<br />
<br />
<math> \frac{d x(t)}{dt} \approx \frac{x(t+h)-x(t)}{h}</math>,<br />
<br />
przekształacjąc uzyskujemy<br />
<br />
<math> x(t+h) \approx x(t)+ h \frac{d x(t)}{dt} </math><br />
<br />
a po podstawieniu rozwiązywanego równania mamy<br />
<br />
<math> x(t+h) \approx x(t)+ h f(t,x(t)) </math>.<br />
<br />
Możemy to zapisać w postaci dyskretnej<br />
<br />
<math> x_{n+1} = x_n + h f(t_n,x_n) </math>.<br />
<br />
Wartość w kolejnej chwili czasu dana jest explicite poprzez wartość w chwili poprzedniej. Metoda ta nazywa się Explicit Euler. Możemy teraz zaimplementować ją w pythonie<br />
<br />
<!--<source lang="python">--><br />
<syntaxhighlight lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
#rozwiazujemy rownanie dx(t)/dt=f(t,x)<br />
<br />
#metoda Explicit Euler<br />
#f - funkcja f z rownania<br />
#x0-wartosc poczatkowa<br />
#t0-czas poczatkowy<br />
#tk-czas koncowy<br />
#h-krok czasowy<br />
<br />
def EE(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0))) # gdy mamy doczynienia w równaniem wektorowym<br />
else: x=np.zeros(N) #dla przypadku skalarnego<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
x[i]=np.array(x[i-1]+h*f(t[i-1],x[i-1]))<br />
i+=1<br />
return t,x<br />
</syntaxhighlight><br />
<!--</source>--><br />
Najłatwiej będzie przetestować napisaną metodę na równaniu którego ścisłe rozwiązanie jest znane.Zacznijmy zatem od równania oscylatora harmonicznego<br />
<source lang="python"><br />
def oscylator(t,y):<br />
x=y[0]<br />
xdot=y[1]<br />
return np.array([xdot,-x])<br />
</source><br />
Rozwiążmy to równanie z warunkeim początkowym [1.0,1.0] i od czasu od 0 do 100.<br />
<source lang="python"><br />
t,x=EE(oscylator,[1.0,1.0],0.0,100,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
rozwiązanie wygląda wówczas nastepująco<br />
<br />
[[Plik:img01.png]]<br />
<br />
Jeżeli zaś wydłuzymy czas symulacji to 1000 otrzymamy<br />
<br />
[[Plik:img2.png]]<br />
<br />
Amplituta oscylacji rośnie wykładniczo i rozwiązanie numeryczne bardzo szybko przestaje mieć cokolwiek wspólnego ze ścisłym rozwiązaniem którego amplituda jest przecież stała. Metoda Explicit Euler już po kilku krokach czasowych przestaje przypominać ścisłe rozwiązanie. Niestety trudno jest zupełnie wyeliminować to zjawisko, za to możemy użyć metody, która znacznie wolniej będzie się oddalać od ścisłego rozwiązania. Zauważmy, że w metodzie Explicit Euler w każdym kroku czasowym tylko raz liczyliśmy wartość funkcji f. Liczbę wywołan funkcji f w każdym kroku czasowym nazywamy rzędem metody, stąd Explicit Euler jest metodą pierwszego rzędu. Wprowadźmy teraz przykładowe metody rzędu drugiego<br />
<br />
==Metoda Żabiego Skoku== <br />
W poprzedniej metodzie liczyliśmy wartość funkcji f w chwili <math>t_n</math>, która była pochodzną po czasie naszego ścisłego rozwiązania. Kolejny punkt <math>x_{n+1}</math> był liczony z przybliżenia liniowego funkcji w chwili poprzedniej. Jeżeli faktyczna trajektoria ma niezerową drugą pochodzną to takie liniowe przybliżenie zawsze będzie nas oddalało od ścisłego rozwiązania. Dosyć prostym pomysłem na poprawienie zbieżności metody jest tak zwany żabi skok. Policzmy najpierw wartość zmiennej x przesuwając się w czasie o h/2 i policzmy wówczas pochodną, którą oznaczmy przez <math>k_2</math><br />
<br />
<math> k_1=f(t_n,x_n) </math>.<br />
<br />
<math> k_2=f(t_n+h/2,x_n+h/2*k_1) </math>.<br />
<br />
Nastepnie używamy pochodznej <math> k_2 </math> zamiast pochodznej <math> k_1 </math> do obliczenia wartości funkcji w kolejnym kroku czasowym.<br />
<br />
<math> x_{n+1} = x_n + h k_2 </math>.<br />
<br />
Przykładowa implementacja tej metody wygląda nastepująco<br />
<source lang="python"><br />
def leapfrog(f,x0,t0,tk,h): <br />
<br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
<br />
#index<br />
i=1<br />
<br />
#petla glowna<br />
while (i<N):<br />
k1=f(t[i-1],x[i-1])<br />
k2=f(t[i-1]+h*0.5,x[i-1]+0.5*h*k1)<br />
x[i]=np.array(x[i-1]+h*k2)<br />
i+=1<br />
return t,x<br />
</source><br />
Rozwiązanie równania oscylatora tą metodą dla identycznych jak poprzednio czasów da nastepujące wyniki<br />
<source lang="python"><br />
t,x=leapfrog(oscylator,[1.0,1.0],0.0,100,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:img3.png]]<br />
<br />
<source lang="python"><br />
t,x=leapfrog(oscylator,[1.0,1.0],0.0,1000,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:img4.png]]<br />
<br />
==Metoda Heuna==<br />
Kolejną metodą niewiele różniącą się od poprzedniej jest metoda Heuna. Zdefiniowana jest ona przez równania<br />
<math> k_1=f(t_n,x_n) </math>,<br />
<br />
<math> k_2=f(t_n+h/2,x_n+h/2*k_1) </math>,<br />
<br />
<math> x_{n+1} = x_n + h k_2 </math>.<br />
<br />
Implementacja wygląda następująco<br />
<br />
<source lang="python"><br />
<br />
def Heun(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
#liczba krokow czasowych<br />
N=len(t)<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
k1=f(t[i-1],x[i-1])<br />
k2=f(t[i-1]+h*0.5,x[i-1]+0.5*h*k1)<br />
x[i]=np.array(x[i-1]+h*0.5*(k1+k2))<br />
i+=1<br />
return t,x<br />
</source><br />
<br />
==Runge-Kutta czwartego rzędu==<br />
Ostatnią metodą, którą omówimy jest najbardziej popularna metoda zwana w skrócie RK4. Metoda to uznawana jest za kanoniczną i w większości zastosowań dającą najlepsze wyniki. Metody wyższego rzędu nie wnoszą już do wyniku znaczącej poprawy. Jak sugeruje nazwa metody, jej rząd to 4, czyli w każdym kroku czasowym czterokrotnie wywołujemy funkcję f. Metoda ta zdefiniowana jest wzorami<br />
<br />
<math> k_1 = f \left( t_n, x_n \right) </math>,<br />
<br />
<math> k_2 = f \left( t_n + {h \over 2}, x_n + {1 \over 2} k_1 \right) </math>,<br />
<br />
<math> k_3 = f \left( t_n + {h \over 2}, x_n + {1 \over 2} k_2 \right) </math>,<br />
<br />
<math> k_4 = f \left( t_n + h, x_n + k_3 \right) </math>,<br />
<br />
<math> x_{n+1} = x_n + {h \over 6} (k_1 + 2k_2 + 2k_3 + k_4) </math>,<br />
<br />
a implementacja wygląda następująco<br />
<br />
<source lang="python"><br />
def RK4(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
#liczba krokow czasowych<br />
N=len(t)<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
k1=h*f(t[i-1],x[i-1])<br />
k2=h*f(t[i-1]+h*0.5,x[i-1]+0.5*k1)<br />
k3=h*f(t[i-1]+h*0.5,x[i-1]+0.5*k2)<br />
k4=h*f(t[i-1]+h,x[i-1]+k3)<br />
x[i]=np.array(x[i-1]+(k1+2.0*k2+2.0*k3+k4)/6)<br />
i+=1<br />
return t,x<br />
</source><br />
==Przykłady==<br />
===Zadanie - Wahadło matematyczne z tłumieniem i siłą wymuszającą===<br />
<br />
Rozwiąż numerycznie metodą RK4 równanie różniczkowe oscylatora harmonicznego z tłumieniem i siłą wymuszającą<br />
<br />
<math> \frac{d^2x}{dt^2} + \Gamma \frac{dx}{dt} + w_0^2 x = f_0 \cos(W t) </math>,<br />
<br />
przyjmując parametry <math> f_0 =1, w_0=1, \Gamma=0.1, h=0.1 </math>. Wykreśl zależność amplitudy drgań w funkcji częstości siły wymuszającej W, dla W z przedziału [0.1,3].<br />
===Rozwiązanie===<br />
Zacznijmy od sprowadzenia równania drugiego stopnia do równania pierszego stopnia i zapisania go w postaci funkcji<br />
<br />
<source lang="python"><br />
def oscylator(t,y):<br />
f0=1.0<br />
w0=1.0<br />
Gamma=0.1<br />
<br />
x=y[0]<br />
xdot=y[1]<br />
return np.array([xdot,f0*np.cos(oscylator.W*t)-oscylator.Gamma*xdot-w0*w0*x])<br />
<br />
oscylator.W=1.0<br />
</source><br />
Nieprzypadkowo parametr W nie jest definiowany w samej funkcji jako zmienna wewnętrzna, ale jako atrybut obiektu jakim jest funkcja. Dzięki takiej konstrukcji łatwo będzie nam zmieniać parametr W czyli częstość siły wymuszającej. Zobaczmy jak wygląda trajektoria będąca rozwiązaniem tego równania dla warunku początkowego [0,1] i czasu końcowego równego 400.<br />
<source lang="python"><br />
py.plot(*RK4(oscylator,[0.0,1.0],0.0,400.0,0.1))<br />
py.show()<br />
</source><br />
<br />
[[Plik:oscy1.png]]<br />
<br />
Widać że początkowo układ dochodzi do stanu regularnych oscylacji. Do analizy amplitudy interesuje nas jedynie koncowa część więc ograniczymy się do analizy trajektorii od czasu 200 do czasu 400. Napiszmy teraz funkcję, która na podstawie trajektorii wyznaczy nam amplitudę oscylacji<br />
<br />
<source lang="python"><br />
def amplituda(x):<br />
lista=x[2000:,0]<br />
return (max(lista)-min(lista))*0.5<br />
</source><br />
<br />
Interesować nas będzie amplituda drgań w funkcji częstość W. Wygenerujmy listę wartości W dla których będziemy liczyć amplitudę.<br />
<br />
<source lang="python"><br />
Omegas=np.arange(0.1,3.0,0.05)<br />
</source><br />
Możemy teraz dla każdej z wartości W rozwiązać numerycznie równanie różniczkowe i wyznaczyć odpowiadającą amplitudę oscylacji<br />
<source lang="python"><br />
amp=[amplituda(RK4(oscylator,[0.0,1.0],0.0,400.0,0.1)[1]) for oscylator.W in Omegas]<br />
</source><br />
Wynik koncowy wyglada następująco<br />
<source lang="python"><br />
py.plot(Omegas,amp)<br />
py.show()<br />
</source><br />
[[Plik:oscy2.png]]<br />
<br />
Jak można było się domyślić amplituda jest największa gdy częstotliwość wymuszania W pokrywa się z wartością częstotliwości drgań własnych <math> w_0=1 </math><br />
<br />
===Zadanie - Układ Lorenza===<br />
Rozwiąż układ równań różniczkowych Lorenza dany wzorami<br />
<br />
<math> \begin{cases}\dot x=\sigma y-\sigma x\\\dot y=-xz+rx-y\\\dot z=xy-bz\end{cases}, </math> <br />
<br />
metodą całkowania Rungego–Kutty drugiego rzędu z α = 2/3, czyli<br />
<br />
<math>k_1 = f(t_n,x_n)</math> , <br />
<br />
<math>k_2 = f(t_n + \tfrac{2}{3}h, x_n + \tfrac{2}{3}h k_1)</math> , <br />
<br />
<math>x_{n+1} = x_n + h \left(\tfrac{1}{4}k_1+\tfrac{3}{4} k_2 \right). </math> <br />
<br />
Przyjmij sigma=10, b=8/3, r=99.96, krok czasowy h=0.005 i warunki początkowe x=1,y=0,z=0. Wykonaj 8000 kroków czasowych. Układ po pewnym czasie zacznie poruszać się po pewnej periodycznej trajektorii. Wykonaj 3 rysunki TEJ PERIODYCZNEJ TRAJEKTORII (bez okresu dochodzenia do niej) w płaszczyznach (x,y), (y,z) i (z,x). Wypisz na ekranie przedziały wartości jakie przyjmują zmienne x,y i z na periodycznej trajektorii oraz okres trajektorii periodycznej.<br />
===Rozwiązanie===<br />
Zacznijmu od implementacji podanej w treści zadania metody całkowania RK2<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
#rozwiazujemy rownanie dx(t)/dt=f(t,x)<br />
<br />
#metoda Explicit Euler<br />
#f - funkcja f z rownania<br />
#x0-wartosc poczatkowa<br />
#t0-czas poczatkowy<br />
#tk-czas koncowy<br />
#h-krok czasowy<br />
<br />
def RK2(f,x0,t0,tk,h): <br />
<br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
<br />
#index<br />
i=1<br />
<br />
#petla glowna<br />
while (i<N):<br />
k1=h*f(t[i-1],x[i-1])<br />
k2=h*f(t[i-1]+h*2.0/3.0,x[i-1]+k1*2.0/3.0)<br />
x[i]=np.array(x[i-1]+0.25*k1+0.75*k2)<br />
i+=1<br />
return t,x<br />
</source><br />
Następnie napiszmy funkcję opisującą układ Lorenza<br />
<source lang="python"><br />
def Lorenza(t,y):<br />
sigma=10.0<br />
b=8.0/3<br />
r=99.96<br />
xx=y[0]<br />
yy=y[1]<br />
zz=y[2]<br />
xdot=sigma*(yy-xx)<br />
ydot=-xx*zz+r*xx-yy<br />
zdot=xx*yy-b*zz<br />
return np.array([xdot,ydot,zdot])<br />
</source><br />
<br />
Zobaczmy teraz jak wyglądają trajektorie wszystkich trzech współrzędnych rozwiązania z zadanymi parametrami<br />
<source lang="python"><br />
t,x=RK2(Lorenza,[1.0,0.0,0.0],0.0,40.0,0.005)<br />
py.plot(t,x[:,1])<br />
py.show()<br />
py.plot(t,x[:,2])<br />
py.show()<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:lor1.png]]<br />
<br />
[[Plik:lor2.png]]<br />
<br />
[[Plik:lor3.png]]<br />
<br />
Możemy zauważyć że układ początkowo zachowuje się chaotycznie a potem dąży do pewnego stanu ustalonego (tzw. atraktora). Cała trajektoria składa się z 8000 punktów, przyjmijmy że powyżej punktu o numerze 2500 mamy już do czynienia tylko z periodyczną trajektorią. Wykreślmy zatem portrety fazowe o których mowa w treści zadania<br />
<source lang="python"><br />
py.plot(x[2500:,0],x[2500:,1])<br />
py.show()<br />
py.plot(x[2500:,1],x[2500:,2])<br />
py.show()<br />
py.plot(x[2500:,2],x[2500:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:lor4.png]]<br />
<br />
[[Plik:lor5.png]]<br />
<br />
[[Plik:lor6.png]]<br />
<br />
Wypisanie zakresów jest już tylko formalnością<br />
<source lang="python"><br />
print 'zmienna x przyjmuje wartosci z zakresu: (',min(x[2500:,0]),',',max(x[2500:,0]),')'<br />
print 'zmienna y przyjmuje wartosci z zakresu: (',min(x[2500:,1]),',',max(x[2500:,1]),')'<br />
print 'zmienna z przyjmuje wartosci z zakresu: (',min(x[2500:,2]),',',max(x[2500:,2]),')'<br />
</source><br />
Okres trajektorii periodycznej możemy znaleść na przykład w ten sposób<br />
<source lang="python"><br />
prog=140<br />
lista=[]<br />
for i in range(2500,8000):<br />
if (x[i-1,2]<prog) and (x[i,2]>prog): lista.append(i)<br />
print 'okres to:',np.mean(np.diff(lista)*0.005)<br />
<br />
>>> okres to: 1.0975<br />
>>> zmienna x przyjmuje wartosci z zakresu: ( -33.4431203059 , 25.8037953495 )<br />
>>> zmienna y przyjmuje wartosci z zakresu: ( -56.7169238157 , 37.3166709986 )<br />
>>> zmienna z przyjmuje wartosci z zakresu: ( 53.4652816712 , 144.264397579 )<br />
<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Morfologia_matematyczna&diff=3601TI/Programowanie dla Fizyków Medycznych/Morfologia matematyczna2015-06-09T11:14:51Z<p>Tgubiec: </p>
<hr />
<div>==Morfologia Matematyczna==<br />
Morfologia matematyczna to bardzo przydatna metoda przetwarzania obrazów binarnych (czarno białych), pozwalająca na analizę i "upraszczanie" obserwowanych kształtów. W szczególności można ją zastosować do obrazów medycznych przetworzonych przed progowanie. Metody morfologii matematycznej pozwalają także na uspójnienie otrzymanego obrazu nie zmieniając jego rozmiarów zewnętrznych co może być bardzo przydatne przy dokonywaniu pomiarów w oparciu o cyfrowy obraz medyczny. Dla osób zainteresowanych bardziej formalną definicją poszczególnych operacji polecam bardzo szeroką literaturę dostępną w internecie. Tutaj przedstawimy definicję pozwalającą na łatwiejsze zrozumienie istoty działania poszczególnych operacji. Operacje morfologii matematycznej opierają się na tak zwanym elemencie strukturalnym, który my w uproszczeniu nazywać będziemy pędzlem (brush). Zacznijmy od zdefiniowania naszego obrazu roboczego i przykładowego pędzla.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
a=np.zeros((100,100),dtype=np.bool)<br />
a[30:50,30:50]=True<br />
a[50:70,50:70]=True<br />
<br />
brush7=np.array([[0,0,1,1,1,0,0],[0,1,1,1,1,1,0],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[0,1,1,1,1,1,0],[0,0,1,1,1,0,0]],dtype=np.bool)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia1.png]]<br />
<br />
W definiowaniu funkcji morfologii matematycznej przydatna będzie procedura zmieniająca pędzel, będący kwadratową tablicą zer i jedynek na listę wektorów mających początek w środku pędzla i końce wa wszystkich komórkach posiadających wartość 1. Kod takiej procedury przedstawia się następująco.<br />
<source lang="python"><br />
def brush2list(brush):<br />
result=[]<br />
N=brush.shape[0]<br />
middle=N/2<br />
for x in range(N):<br />
for y in range(N):<br />
if brush[x,y]: result.append((x-middle,y-middle))<br />
return result<br />
</source><br />
<br />
===Dylacja===<br />
Pierwszą operacją którą omówimy jest dylacja. Nasz obraz w chwili obecnej składa się z tła zer (w kolorze czarnym) i dwóch kwadratów z jedynek (w kolorze białym. Wyobraźmy sobie że nasz pędzel służy do malowania po obrazku. Jeżeli w jakimś miejscu przyłożymy środek pędzla, to wszystkie pixele na których znajdą się jedynki w pędzlu zmienią swoją wartość na jeden. Operacja dylacji to przyłożenie środka pędzla do wszystkich komórek których początkowa wartość to jeden. Równoważna jest także inna definicja. Przykładamy środek pędzla po kolei do wszystkich pixeli. Tworzymy listę wartości pixeli obrazu które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą '''maksimum''' z tej list. Implementacja drugiej z tych definicji znajduje się poniżej.<br />
<source lang="python"><br />
def dylacja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=max([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(dylacja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-dylacja.png]]<br />
<br />
W efekcie dwa kwadraty powiększyły się i zaokrągliły im się rogi.<br />
===Erozja===<br />
Drugą operacją morfologii matematycznej jest erozja. Operacja jest analogiczna, z tą zmianą, że teraz nasz pędzel maluje na czarno i przesuwamy go po czarnych pixelach. Przy alternatywnej definicji przykładamy środek pędzla po kolei do wszystkich pixeli. Tworzymy listę wartości pixeli obrazu które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą '''minimum''' z tej list. Implementacja drugiej z tych definicji znajduje się poniżej.<br />
<source lang="python"><br />
def erozja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=min([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(erozja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-erozja.png]]<br />
<br />
W wyniku tej operacji kwadraty nie zmieniły swojego kształtu, za to zmniejszyły się i powstała między nimi przerwa.<br />
<br />
===Otwarcie i zamknięcie===<br />
Skoro dylacja powiększa rozmiar naszego obrazu a erozja zmniejsza, naturalnym jest postawienie sobie pytania, co się stanie gdy obraz najpierw potraktujemy erozją a potem dylacją lub też odwrotnie. Operacje powstałe właśnie w ten sposób nazywamy otwarcie i zamknięciem. Ich definicja nie jest już problematyczna posiadając implementacje wcześniejszych metod.<br />
<source lang="python"><br />
def otwarcie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return dylacja(erozja(fig,brush),brush)<br />
<br />
def zamkniecie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return erozja(dylacja(fig,brush),brush)<br />
<br />
py.imshow(otwarcie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
py.imshow(zamkniecie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-otwarcie.png]]<br />
<br />
[[Plik:morfologia-zakmniecie.png]]<br />
<br />
Wyniki powyższych dwóch operacji są najbardziej przydatne w zastosowaniach medycznych. W obu przypadkach rozmiary obrazu nie zmieniły się. W przypadku otwarcia rogi kwadratów zaokrągliły się i stykające się kwadraty stały się wyraźniej rozłączne. W przypadku zamknięcia obszar tworzony przez dwa kwadraty został uspójniony tworząc coś w rodzaju "mostu" między nimi. Przy zastosowaniu zamknięcia wszystki obiekty odległe od siebie o mniej niż średnica pędzla zostaną połączone.<br />
===Filtr medianowy===<br />
W poprzednich metodach przykładaliśmy środek pędzla po kolei do wszystkich pixeli, tworzyliśmy listę wartości pixeli obrazu które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą minimum lub maksimum z tej list. Co się stanie gdy na wartościach z tej listy wykonamy jakieś inne operacje? Wzięcie średniej, jak wspominałem w poprzednim rozdziale, doprowadzi do rozmycia obrazka (średnia nie musi być zerem lub jedynką). Bardzo przydatną operację uzyskamy, gdy weźmiemy '''medianę''' z wartości w tej liście. Powstanie w ten sposób tak zwany filtr medianowy, który jest doskonałym narzędziem do usuwania szumu z obrazka. Jego implementacja wygląda na przykład tak.<br />
<source lang="python"><br />
def medianowy(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=np.median([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
</source><br />
Aby przetestować działanie filtra medianowego musimy "zaszumić" nasz oryginalny obrazek<br />
<source lang="python"><br />
for x,y in np.ndindex(a.shape):<br />
if (np.random.random()<0.05): a[x,y]=False<br />
if (np.random.random()>0.95): a[x,y]=True<br />
</source><br />
Zobaczmy teraz jakie wyniki da wielokrotne zastosowanie filtra medianowego.<br />
<source lang="python"><br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-zaszumiony1.png]]<br />
<br />
[[Plik:morfologia-zaszumiony2.png]]<br />
<br />
[[Plik:morfologia-zaszumiony3.png]]<br />
<br />
[[Plik:morfologia-zaszumiony4.png]]<br />
<br />
Jak widać udało się odtworzyć kształty widoczne nawet na obrazku bardzo słabej jakości. Operacje takie pozwalają znacznie poprawić jakość wyników uzyskiwanych poprzez progowanie obrazów medycznych.<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Morfologia_matematyczna&diff=3600TI/Programowanie dla Fizyków Medycznych/Morfologia matematyczna2015-06-09T11:12:27Z<p>Tgubiec: </p>
<hr />
<div>==Morfologia Matematyczna==<br />
Morfologia matematyczna to bardzo przydatna metoda przetwarzania obrazów binarnych (czarno białych), pozwalająca na analizę i "upraszczanie" obserwowanych kształtów. W szczególności można ją zastosować do obrazów medycznych przetworzonych przed progowanie. Metody morfologii matematycznej pozwalają także na uspójnienie otrzymanego obrazu nie zmieniając jego rozmiarów zewnętrznych co może być bardzo przydatne przy dokonywaniu pomiarów w oparciu o cyfrowy obraz medyczny. Dla osób zainteresowanych bardziej formalną definicją poszczególnych operacji polecam bardzo szeroką literaturę dostępną w internecie. Tutaj przedstawimy definicję pozwalającą na łatwiejsze zrozumienie istoty działania poszczególnych operacji. Operacje morfologii matematycznej opierają się na tak zwanym elemencie strukturalnym, który my w uproszczeniu nazywać będziemy pędzlem (brush). Zacznijmy od zdefiniowania naszego obrazu roboczego i przykładowego pędzla.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
a=np.zeros((100,100),dtype=np.bool)<br />
a[30:50,30:50]=True<br />
a[50:70,50:70]=True<br />
<br />
brush7=np.array([[0,0,1,1,1,0,0],[0,1,1,1,1,1,0],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[0,1,1,1,1,1,0],[0,0,1,1,1,0,0]],dtype=np.bool)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia1.png]]<br />
<br />
W definiowaniu funkcji morfologii matematycznej przydatna będzie procedura zmieniająca pędzel, będący kwadratową tablicą zer i jedynek na listę wektorów mających początek w środku pędzla i końce wa wszystkich komórkach posiadających wartość 1. Kod takiej procedury przedstawia się następująco.<br />
<source lang="python"><br />
def brush2list(brush):<br />
result=[]<br />
N=brush.shape[0]<br />
middle=N/2<br />
for x in range(N):<br />
for y in range(N):<br />
if brush[x,y]: result.append((x-middle,y-middle))<br />
return result<br />
</source><br />
<br />
===Dylacja===<br />
Pierwszą operacją którą omówimy jest dylacja. Nasz obraz w chwili obecnej składa się z tła zer (w kolorze czarnym) i dwóch kwadratów z jedynek (w kolorze białym. Wyobraźmy sobie że nasz pędzel służy do malowania po obrazku. Jeżeli w jakimś miejscu przyłożymy środek pędzla, to wszystkie pixele na których znajdą się jedynki w pędzlu zmienią swoją wartość na jeden. Operacja dylacji to przyłożenie środka pędzla do wszystkich komórek których początkowa wartość to jeden. Równoważna jest także inna definicja. Przykładamy środek pędzla po kolei do wszystkich pixeli. Tworzymy listę wartości pixeli obrazu które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą '''maksimum''' z tej list. Implementacja drugiej z tych definicji znajduje się poniżej.<br />
<source lang="python"><br />
def dylacja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=max([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(dylacja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-dylacja.png]]<br />
<br />
W efekcie dwa kwadraty powiększyły się i zaokrągliły im się rogi.<br />
===Erozja===<br />
Drugą operacją morfologii matematycznej jest erozja. Operacja jest analogiczna, z tą zmianą, że teraz nasz pędzel maluje na czarno i przesuwamy go po czarnych pixelach. Przy alternatywnej definicji przykładamy środek pędzla po kolei do wszystkich pixeli. Tworzymy listę wartości pixeli obrazu które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą '''minimum''' z tej list. Implementacja drugiej z tych definicji znajduje się poniżej.<br />
<source lang="python"><br />
def erozja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=min([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(erozja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-erozja.png]]<br />
<br />
W wyniku tej operacji kwadraty nie zmieniły swojego kształtu, za to zmniejszyły się i powstała między nimi przerwa.<br />
<br />
===Otwarcie i zamknięcie===<br />
Skoro dylacja powiększa rozmiar naszego obrazu a erozja zmniejsza, naturalnym jest postawienie sobie pytania, co się stanie gdy obraz najpierw potraktujemy erozją a potem dylacją lub też odwrotnie. Operacje powstałe właśnie w ten sposób nazywamy otwarcie i zamknięciem. Ich definicja nie jest już problematyczna posiadając implementacje wcześniejszych metod.<br />
<source lang="python"><br />
def otwarcie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return dylacja(erozja(fig,brush),brush)<br />
<br />
def zamkniecie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return erozja(dylacja(fig,brush),brush)<br />
<br />
py.imshow(otwarcie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
py.imshow(zamkniecie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-otwarcie.png]]<br />
<br />
[[Plik:morfologia-zakmniecie.png]]<br />
<br />
Wyniki powyższych dwóch operacji są najbardziej przydatne w zastosowaniach medycznych. W obu przypadkach rozmiary obrazu nie zmieniły się. W przypadku otwarcia rogi kwadratów zaokrągliły się i stykające się kwadraty stały się wyraźniej rozłączne. W przypadku zamknięcia obszar tworzony przez dwa kwadraty został uspójniony tworząc coś w rodzaju "mostu" między nimi. Przy zastosowaniu zamknięcia wszystki obiekty odległe od siebie o mniej niż średnica pędzla zostaną połączone.<br />
===Filtr medianowy===<br />
W poprzednich metodach przykładaliśmy środek pędzla po kolei do wszystkich pixeli, tworzyliśmy listę wartości pixeli obrazu które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą minimum lub maksimum z tej list. Co się stanie gdy na wartościach z tej listy wykonamy jakieś inne operacje? Wzięcie średniej, jak wspominałem w poprzednim rozdziale, doprowadzi do rozmycia obrazka (średnia nie musi być zerem lub jedynką). Bardzo przydatną operację uzyskamy, gdy weźmiemy '''medianę''' z wartości w tej liście. Powstanie w ten sposób tak zwany filtr medianowy, który jest doskonałym narzędziem do usuwania szumu z obrazka. Jego implementacja wygląda na przykład tak.<br />
<source lang="python"><br />
def medianowy(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=np.median([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
</source><br />
Aby przetestować działanie filtra medianowego musimy "zaszumić" nasz oryginalny obrazek<br />
<source lang="python"><br />
for x,y in np.ndindex(a.shape):<br />
if (np.random.random()<0.05): a[x,y]=False<br />
if (np.random.random()>0.95): a[x,y]=True<br />
</source><br />
Zobaczmy teraz jakie wyniki da wielokrotne zastosowanie filtra medianowego.<br />
<source lang="python"><br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-zaszumiony1.png]]<br />
<br />
[[Plik:morfologia-zaszumiony2.png]]<br />
<br />
[[Plik:morfologia-zaszumiony3.png]]<br />
<br />
[[Plik:morfologia-zaszumiony4.png]]<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Morfologia_matematyczna&diff=3599TI/Programowanie dla Fizyków Medycznych/Morfologia matematyczna2015-06-09T11:11:29Z<p>Tgubiec: </p>
<hr />
<div>==Morfologia Matematyczna==<br />
Morfologia matematyczna to bardzo przydatna metoda przetwarzania obrazów binarnych (czarno białych), pozwalająca na analizę i "upraszczanie" obserwowanych kształtów. W szczególności można ją zastosować do obrazów medycznych przetworzonych przed progowanie. Metody morfologii matematycznej pozwalają także na uspójnienie otrzymanego obrazu nie zmieniając jego rozmiarów zewnętrznych co może być bardzo przydatne przy dokonywaniu pomiarów w oparciu o cyfrowy obraz medyczny. Dla osób zainteresowanych bardziej formalną definicją poszczególnych operacji polecam bardzo szeroką literaturę dostępną w internecie. Tutaj przedstawimy definicję pozwalającą na łatwiejsze zrozumienie istoty działania poszczególnych operacji. Operacje morfologii matematycznej opierają się na tak zwanym elemencie strukturalnym, który my w uproszczeniu nazywać będziemy pędzlem (brush). Zacznijmy od zdefiniowania naszego obrazu roboczego i przykładowego pędzla.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
a=np.zeros((100,100),dtype=np.bool)<br />
a[30:50,30:50]=True<br />
a[50:70,50:70]=True<br />
<br />
brush7=np.array([[0,0,1,1,1,0,0],[0,1,1,1,1,1,0],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[0,1,1,1,1,1,0],[0,0,1,1,1,0,0]],dtype=np.bool)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia1.png]]<br />
<br />
W definiowaniu funkcji morfologii matematycznej przydatna będzie procedura zmieniająca pędzel, będący kwadratową tablicą zer i jedynek na listę wektorów mających początek w środku pędzla i końce wa wszystkich komórkach posiadających wartość 1. Kod takiej procedury przedstawia się następująco.<br />
<source lang="python"><br />
def brush2list(brush):<br />
result=[]<br />
N=brush.shape[0]<br />
middle=N/2<br />
for x in range(N):<br />
for y in range(N):<br />
if brush[x,y]: result.append((x-middle,y-middle))<br />
return result<br />
</source><br />
<br />
===Dylacja===<br />
Pierwszą operacją którą omówimy jest dylacja. Nasz obraz w chwili obecnej składa się z tła zer (w kolorze czarnym) i dwóch kwadratów z jedynek (w kolorze białym. Wyobraźmy sobie że nasz pędzel służy do malowania po obrazku. Jeżeli w jakimś miejscu przyłożymy środek pędzla, to wszystkie pixele na których znajdą się jedynki w pędzlu zmienią swoją wartość na jeden. Operacja dylacji to przyłożenie środka pędzla do wszystkich komórek których początkowa wartość to jeden. Równoważna jest także inna definicja. Przykładamy środek pędzla po kolei do wszystkich pixeli. Tworzymy listę wartości pixeli obrazu które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą '''maksimum''' z tej list. Implementacja drugiej z tych definicji znajduje się poniżej.<br />
<source lang="python"><br />
def dylacja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=max([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(dylacja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-dylacja.png]]<br />
<br />
W efekcie dwa kwadraty powiększyły się i zaokrągliły im się rogi.<br />
===Erozja===<br />
Drugą operacją morfologii matematycznej jest erozja. Operacja jest analogiczna, z tą zmianą, że teraz nasz pędzel maluje na czarno i przesuwamy go po czarnych pixelach. Przy alternatywnej definicji przykładamy środek pędzla po kolei do wszystkich pixeli. Tworzymy listę wartości pixeli obrazu które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą '''minimum''' z tej list. Implementacja drugiej z tych definicji znajduje się poniżej.<br />
<source lang="python"><br />
def erozja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=min([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(erozja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-erozja.png]]<br />
<br />
W wyniku tej operacji kwadraty nie zmieniły swojego kształtu, za to zmniejszyły się i powstała między nimi przerwa.<br />
===Otwarcie i zamknięcie=<br />
Skoro dylacja powiększa rozmiar naszego obrazu a erozja zmniejsza, naturalnym jest postawienie sobie pytania, co się stanie gdy obraz najpierw potraktujemy erozją a potem dylacją lub też odwrotnie. Operacje powstałe właśnie w ten sposób nazywamy otwarcie i zamknięciem. Ich definicja nie jest już problematyczna posiadając implementacje wcześniejszych metod.<br />
<source lang="python"><br />
def otwarcie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return dylacja(erozja(fig,brush),brush)<br />
<br />
def zamkniecie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return erozja(dylacja(fig,brush),brush)<br />
<br />
py.imshow(otwarcie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
py.imshow(zamkniecie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-otwarcie.png]]<br />
<br />
[[Plik:morfologia-zakmniecie.png]]<br />
<br />
Wyniki powyższych dwóch operacji są najbardziej przydatne w zastosowaniach medycznych. W obu przypadkach rozmiary obrazu nie zmieniły się. W przypadku otwarcia rogi kwadratów zaokrągliły się i stykające się kwadraty stały się wyraźniej rozłączne. W przypadku zamknięcia obszar tworzony przez dwa kwadraty został uspójniony tworząc coś w rodzaju "mostu" między nimi. Przy zastosowaniu zamknięcia wszystki obiekty odległe od siebie o mniej niż średnica pędzla zostaną połączone.<br />
===Filtr medianowy===<br />
W poprzednich metodach przykładaliśmy środek pędzla po kolei do wszystkich pixeli, tworzyliśmy listę wartości pixeli obrazu które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą minimum lub maksimum z tej list. Co się stanie gdy na wartościach z tej listy wykonamy jakieś inne operacje? Wzięcie średniej, jak wspominałem w poprzednim rozdziale, doprowadzi do rozmycia obrazka (średnia nie musi być zerem lub jedynką). Bardzo przydatną operację uzyskamy, gdy weźmiemy '''medianę''' z wartości w tej liście. Powstanie w ten sposób tak zwany filtr medianowy, który jest doskonałym narzędziem do usuwania szumu z obrazka. Jego implementacja wygląda na przykład tak.<br />
<source lang="python"><br />
def medianowy(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=np.median([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
</source><br />
Aby przetestować działanie filtra medianowego musimy "zaszumić" nasz oryginalny obrazek<br />
<source lang="python"><br />
for x,y in np.ndindex(a.shape):<br />
if (np.random.random()<0.05): a[x,y]=False<br />
if (np.random.random()>0.95): a[x,y]=True<br />
</source><br />
Zobaczmy teraz jakie wyniki da wielokrotne zastosowanie filtra medianowego.<br />
<source lang="python"><br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-zaszumiony1.png]]<br />
<br />
[[Plik:morfologia-zaszumiony2.png]]<br />
<br />
[[Plik:morfologia-zaszumiony3.png]]<br />
<br />
[[Plik:morfologia-zaszumiony4.png]]<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Morfologia_matematyczna&diff=3598TI/Programowanie dla Fizyków Medycznych/Morfologia matematyczna2015-06-09T10:55:39Z<p>Tgubiec: </p>
<hr />
<div>==Morfologia Matematyczna==<br />
Morfologia matematyczna to bardzo przydatna metoda przetwarzania obrazów binarnych (czarno białych), pozwalająca na analizę i "upraszczanie" obserwowanych kształtów. W szczególności można ją zastosować do obrazów medycznych przetworzonych przed progowanie. Metody morfologii matematycznej pozwalają także na uspójnienie otrzymanego obrazu nie zmieniając jego rozmiarów zewnętrznych co może być bardzo przydatne przy dokonywaniu pomiarów w oparciu o cyfrowy obraz medyczny. Dla osób zainteresowanych bardziej formalną definicją poszczególnych operacji polecam bardzo szeroką literaturę dostępną w internecie. Tutaj przedstawimy definicję pozwalającą na łatwiejsze zrozumienie istoty działania poszczególnych operacji. Operacje morfologii matematycznej opierają się na tak zwanym elemencie strukturalnym, który my w uproszczeniu nazywać będziemy pędzlem (brush). Zacznijmy od zdefiniowania naszego obrazu roboczego i przykładowego pędzla.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
a=np.zeros((100,100),dtype=np.bool)<br />
a[30:50,30:50]=True<br />
a[50:70,50:70]=True<br />
<br />
brush7=np.array([[0,0,1,1,1,0,0],[0,1,1,1,1,1,0],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[0,1,1,1,1,1,0],[0,0,1,1,1,0,0]],dtype=np.bool)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia1.png]]<br />
<br />
W definiowaniu funkcji morfologii matematycznej przydatna będzie procedura zmieniająca pędzel, będący kwadratową tablicą zer i jedynek na listę wektorów mających początek w środku pędzla i końce wa wszystkich komórkach posiadających wartość 1. Kod takiej procedury przedstawia się następująco.<br />
<source lang="python"><br />
def brush2list(brush):<br />
result=[]<br />
N=brush.shape[0]<br />
middle=N/2<br />
for x in range(N):<br />
for y in range(N):<br />
if brush[x,y]: result.append((x-middle,y-middle))<br />
return result<br />
</source><br />
<br />
===Dylacja===<br />
Pierwszą operacją którą omówimy jest dylacja. Nasz obraz w chwili obecnej składa się z tła zer (w kolorze czarnym) i dwóch kwadratów z jedynek (w kolorze białym. Wyobraźmy sobie że nasz pędzel służy do malowania po obrazku. Jeżeli w jakimś miejscu przyłożymy środek pędzla, to wszystkie pixele na których znajdą się jedynki w pędzlu zmienią swoją wartość na jeden. Operacja dylacji to przyłożenie środka pędzla do wszystkich komórek których początkowa wartość to jeden. Równoważna jest także inna definicja. Przykładamy środek pędzla po kolei do wszystkich pixeli. Tworzymy listę wartości pixeli obrazu które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą '''maksimum''' z tej list. Implementacja drugiej z tych definicji znajduje się poniżej.<br />
<source lang="python"><br />
def dylacja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=max([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(dylacja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-dylacja.png]]<br />
<br />
W efekcie dwa kwadraty powiększyły się i zaokrągliły im się rogi.<br />
===Erozja===<br />
Drugą operacją morfologii matematycznej jest erozja. Operacja jest analogiczna, z tą zmianą, że teraz nasz pędzel maluje na czarno i przesuwamy go po czarnych pixelach. Przy alternatywnej definicji przykładamy środek pędzla po kolei do wszystkich pixeli. Tworzymy listę wartości pixeli obrazu które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą '''minimum''' z tej list. Implementacja drugiej z tych definicji znajduje się poniżej.<br />
<source lang="python"><br />
def erozja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=min([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(erozja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-erozja.png]]<br />
<br />
W wyniku tej operacji kwadraty nie zmieniły swojego kształtu, za to zmniejszyły się i powstała między nimi przerwa.<br />
===Otwarcie i zamknięcie===<br />
W tym momencie <br />
<source lang="python"><br />
def otwarcie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return dylacja(erozja(fig,brush),brush)<br />
<br />
def zamkniecie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return erozja(dylacja(fig,brush),brush)<br />
<br />
py.imshow(otwarcie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
py.imshow(zamkniecie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-otwarcie.png]]<br />
<br />
[[Plik:morfologia-zakmniecie.png]]<br />
<br />
===Filtr medianowy===<br />
<source lang="python"><br />
def medianowy(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=np.median([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
</source><br />
Służy do usuwania szumu.<br />
<source lang="python"><br />
for x,y in np.ndindex(a.shape):<br />
if (np.random.random()<0.05): a[x,y]=False<br />
if (np.random.random()>0.95): a[x,y]=True<br />
<br />
<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-zaszumiony1.png]]<br />
<br />
[[Plik:morfologia-zaszumiony2.png]]<br />
<br />
[[Plik:morfologia-zaszumiony3.png]]<br />
<br />
[[Plik:morfologia-zaszumiony4.png]]<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Morfologia_matematyczna&diff=3597TI/Programowanie dla Fizyków Medycznych/Morfologia matematyczna2015-06-09T10:49:35Z<p>Tgubiec: </p>
<hr />
<div>==Morfologia Matematyczna==<br />
Morfologia matematyczna to bardzo przydatna metoda przetwarzania obrazów binarnych (czarno białych), pozwalająca na analizę i "upraszczanie" obserwowanych kształtów. W szczególności można ją zastosować do obrazów medycznych przetworzonych przed progowanie. Metody morfologii matematycznej pozwalają także na uspójnienie otrzymanego obrazu nie zmieniając jego rozmiarów zewnętrznych co może być bardzo przydatne przy dokonywaniu pomiarów w oparciu o cyfrowy obraz medyczny. Dla osób zainteresowanych bardziej formalną definicją poszczególnych operacji polecam bardzo szeroką literaturę dostępną w internecie. Tutaj przedstawimy definicję pozwalającą na łatwiejsze zrozumienie istoty działania poszczególnych operacji. Operacje morfologii matematycznej opierają się na tak zwanym elemencie strukturalnym, który my w uproszczeniu nazywać będziemy pędzlem (brush). Zacznijmy od zdefiniowania naszego obrazu roboczego i przykładowego pędzla.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
a=np.zeros((100,100),dtype=np.bool)<br />
a[30:50,30:50]=True<br />
a[50:70,50:70]=True<br />
<br />
brush7=np.array([[0,0,1,1,1,0,0],[0,1,1,1,1,1,0],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[0,1,1,1,1,1,0],[0,0,1,1,1,0,0]],dtype=np.bool)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia1.png]]<br />
<br />
W definiowaniu funkcji morfologii matematycznej przydatna będzie procedura zmieniająca pędzel, będący kwadratową tablicą zer i jedynek na listę wektorów mających początek w środku pędzla i końce wa wszystkich komórkach posiadających wartość 1. Kod takiej procedury przedstawia się następująco.<br />
<source lang="python"><br />
def brush2list(brush):<br />
result=[]<br />
N=brush.shape[0]<br />
middle=N/2<br />
for x in range(N):<br />
for y in range(N):<br />
if brush[x,y]: result.append((x-middle,y-middle))<br />
return result<br />
</source><br />
<br />
===Dylacja===<br />
Pierwszą operacją którą omówimy jest dylacja. Nasz obraz w chwili obecnej składa się z tła zer (w kolorze czarnym) i dwóch kwadratów z jedynek (w kolorze białym. Wyobraźmy sobie że nasz pędzel służy do malowania po obrazku. Jeżeli w jakimś miejscu przyłożymy środek pędzla, to wszystkie pixele na których znajdą się jedynki w pędzlu zmienią swoją wartość na jeden. Operacja dylacji to przyłożenie środka pędzla do wszystkich komórek których początkowa wartość to jeden. Równoważna jest także inna definicja. Przykładamy środek pędzla po kolei do wszystkich pixeli. Tworzymy listę wartości pixeli obrazu które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą '''maksimum''' z tej list. Implementacja drugiej z tych definicji znajduje się poniżej.<br />
<source lang="python"><br />
def dylacja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=max([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(dylacja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-dylacja.png]]<br />
W efekcie dwa kwadraty powiększyły się i zaokrągliły im się rogi.<br />
===Erozja===<br />
Drugą operacją morfologii matematycznej jest erozja. Operacja jest analogiczna, z tą zmianą, że teraz nasz pędzel maluje na czarno i przesuwamy go po czarnych pixelach. Przy alternatywnej definicji przykładamy środek pędzla po kolei do wszystkich pixeli. Tworzymy listę wartości pixeli obrazu które w danym ułożeniu pędzla odpowiadając wartościom 1 na pędzlu. Do pixela w którym znajduje się środek pędzla przypisujemy wartość będącą '''minimum''' z tej list. Implementacja drugiej z tych definicji znajduje się poniżej.<br />
<source lang="python"><br />
def erozja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=min([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(erozja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-erozja.png]]<br />
<br />
===Otwarcie i zamknięcie===<br />
<source lang="python"><br />
def otwarcie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return dylacja(erozja(fig,brush),brush)<br />
<br />
def zamkniecie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return erozja(dylacja(fig,brush),brush)<br />
<br />
py.imshow(otwarcie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
py.imshow(zamkniecie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-otwarcie.png]]<br />
<br />
[[Plik:morfologia-zakmniecie.png]]<br />
<br />
===Filtr medianowy===<br />
<source lang="python"><br />
def medianowy(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=np.median([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
</source><br />
Służy do usuwania szumu.<br />
<source lang="python"><br />
for x,y in np.ndindex(a.shape):<br />
if (np.random.random()<0.05): a[x,y]=False<br />
if (np.random.random()>0.95): a[x,y]=True<br />
<br />
<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-zaszumiony1.png]]<br />
<br />
[[Plik:morfologia-zaszumiony2.png]]<br />
<br />
[[Plik:morfologia-zaszumiony3.png]]<br />
<br />
[[Plik:morfologia-zaszumiony4.png]]<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Morfologia_matematyczna&diff=3596TI/Programowanie dla Fizyków Medycznych/Morfologia matematyczna2015-06-09T10:25:33Z<p>Tgubiec: </p>
<hr />
<div>==Morfologia Matematyczna==<br />
Morfologia matematyczna to bardzo przydatna metoda przetwarzania obrazów binarnych (czarno białych), pozwalająca na analizę i "upraszczanie" obserwowanych kształtów. W szczególności można ją zastosować do obrazów medycznych przetworzonych przed progowanie. Metody morfologii matematycznej pozwalają także na uspójnienie otrzymanego obrazu nie zmieniając jego rozmiarów zewnętrznych co może być bardzo przydatne przy dokonywaniu pomiarów w oparciu o cyfrowy obraz medyczny. Dla osób zainteresowanych bardziej formalną definicją poszczególnych operacji polecam bardzo szeroką literaturę dostępną w internecie. Tutaj przedstawimy definicję pozwalającą na łatwiejsze zrozumienie istoty działania poszczególnych operacji. Operacje morfologii matematycznej opierają się na tak zwanym elemencie strukturalnym, który my w uproszczeniu nazywać będziemy pędzlem (brush). Zacznijmy od zdefiniowania naszego obrazu roboczego i przykładowego pędzla.<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
a=np.zeros((100,100),dtype=np.bool)<br />
a[30:50,30:50]=True<br />
a[50:70,50:70]=True<br />
<br />
brush7=np.array([[0,0,1,1,1,0,0],[0,1,1,1,1,1,0],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[0,1,1,1,1,1,0],[0,0,1,1,1,0,0]],dtype=np.bool)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia1.png]]<br />
<br />
W definiowaniu funkcji morfologii matematycznej przydatna będzie procedura zmieniająca pędzel, będący kwadratową tablicą zer i jedynek na listę wektorów mających początek w środku pędzla i końce wa wszystkich komórkach posiadających wartość 1. Kod takiej procedury przedstawia się następująco.<br />
<source lang="python"><br />
def brush2list(brush):<br />
result=[]<br />
N=brush.shape[0]<br />
middle=N/2<br />
for x in range(N):<br />
for y in range(N):<br />
if brush[x,y]: result.append((x-middle,y-middle))<br />
return result<br />
</source><br />
<br />
===Dylacja===<br />
<source lang="python"><br />
def dylacja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=max([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(dylacja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-dylacja.png]]<br />
<br />
===Erozja===<br />
<source lang="python"><br />
def erozja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=min([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(erozja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-erozja.png]]<br />
<br />
===Otwarcie i zamknięcie===<br />
<source lang="python"><br />
def otwarcie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return dylacja(erozja(fig,brush),brush)<br />
<br />
def zamkniecie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return erozja(dylacja(fig,brush),brush)<br />
<br />
py.imshow(otwarcie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
py.imshow(zamkniecie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-otwarcie.png]]<br />
<br />
[[Plik:morfologia-zakmniecie.png]]<br />
<br />
===Filtr medianowy===<br />
<source lang="python"><br />
def medianowy(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=np.median([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
</source><br />
Służy do usuwania szumu.<br />
<source lang="python"><br />
for x,y in np.ndindex(a.shape):<br />
if (np.random.random()<0.05): a[x,y]=False<br />
if (np.random.random()>0.95): a[x,y]=True<br />
<br />
<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-zaszumiony1.png]]<br />
<br />
[[Plik:morfologia-zaszumiony2.png]]<br />
<br />
[[Plik:morfologia-zaszumiony3.png]]<br />
<br />
[[Plik:morfologia-zaszumiony4.png]]<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Manipulacja_obrazem&diff=3594TI/Programowanie dla Fizyków Medycznych/Manipulacja obrazem2015-06-09T09:46:22Z<p>Tgubiec: </p>
<hr />
<div>==Manipulacja obrazem==<br />
Ćwiczenia z mainuplacji obrazem rozpocznijmy od wczytania pliku który będziemy przetwarzać. Dla fizyków medycznych naturalnie będzie to plik DICOM.<br />
<source lang="python"><br />
import dicom<br />
import numpy as np<br />
import pylab as py<br />
<br />
plik=dicom.read_file('I00001.dcm')<br />
pixel=plik.pixel_array<br />
print pixel.shape<br />
>>>(2964, 2364)<br />
</source><br />
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.<br />
<source lang="python"><br />
pixel=pixel[::5,::5]<br />
print min(pixel.flatten()),max(pixel.flatten())<br />
>>> 0 3907<br />
</source><br />
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].<br />
<source lang="python"><br />
pixel=pixel*1.0/max(pixel.flatten())<br />
</source><br />
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<br />
<source lang="python"><br />
def dodajPasek(tablica, n):<br />
tablica[:n,:]=np.linspace(0,1,tablica.shape[1])<br />
dodajPasek(pixel,30)<br />
py.imshow(pixel,cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:dicom2.png]]<br />
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.<br />
===Jasność===<br />
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.<br />
<source lang="python"><br />
def jasnosc(tablica, a):<br />
def f(x,a):<br />
return min(a*x,1.0)<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
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.<br />
Zaobaczmy teraz na dwóch przykładach w jaki sposób funkcja jasnosc modyfikuje obraz<br />
<source lang="python"><br />
py.imshow(jasnosc(pixel,2),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(jasnosc(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:jasnosc1.png]]<br />
<br />
[[Plik:jasnosc2.png]]<br />
<br />
===Gamma===<br />
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.<br />
<br />
<source lang="python"><br />
def gamma(tablica, a):<br />
def f(x,a):<br />
return min(x**a,1.0)<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Możemy teraz na dwóch przykładach obejrzeć efekt działania przekształcenia gamma.<br />
<source lang="python"> <br />
py.imshow(gamma(pixel,2),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(gamma(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:gamma1.png]]<br />
<br />
[[Plik:gamma2.png]]<br />
<br />
===Próg===<br />
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. <br />
<source lang="python"><br />
def prog(tablica, a):<br />
def f(x,a):<br />
return 0 if x<a else 1<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Natomiast jej działanie na obraz wygląda nastepująco<br />
<source lang="python"><br />
py.imshow(prog(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
[[Plik:prog.png]]<br />
<br />
===Schodek===<br />
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 <br />
<source lang="python"><br />
def schodek(tablica, a,b):<br />
def f(x,a,b):<br />
return 1 if ((x<a) or (x>b)) else 0<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a,b)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
A przyklad działania tutaj.<br />
<source lang="python"><br />
py.imshow(schodek(pixel,0.2,0.6),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:schodek.png]]<br />
<br />
===Rozmycie i wyostrzenie===<br />
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.<br />
Chętnym pozostawiam napisanie takiej procedury samodzielnie, natomiast my skorzystamy z gotowej procedury z pakiecie scipy.ndimage.<br />
<source lang="python"><br />
from scipy import ndimage<br />
imag=ndimage.gaussian_filter(pixel,sigma=4)<br />
print imag<br />
</source><br />
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.<br />
<source lang="python"><br />
def wyostrz(tablica, sigma, a):<br />
rozmyty=np.array(ndimage.gaussian_filter(tablica,sigma=sigma))<br />
roznica=tablica-rozmyty<br />
wynik=rozmyty+a*roznica<br />
wynik/=max(wynik.flatten())<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Wynik działania dla przykładowych wartości parametru a oraz sigma=4 przedstawione są poniżej<br />
<source lang="python"><br />
py.imshow(wyostrz(pixel,4,0),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(wyostrz(pixel,4,3),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:blur.png]]<br />
<br />
[[Plik:sharp.png]]<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Manipulacja_obrazem&diff=3593TI/Programowanie dla Fizyków Medycznych/Manipulacja obrazem2015-06-09T09:01:27Z<p>Tgubiec: </p>
<hr />
<div>==Manipulacja obrazem==<br />
Ćwiczenia z mainuplacji obrazem rozpocznijmy od wczytania pliku który będziemy przetwarzać. Dla fizyków medycznych naturalnie będzie to plik DICOM.<br />
<source lang="python"><br />
import dicom<br />
import numpy as np<br />
import pylab as py<br />
<br />
plik=dicom.read_file('I00001.dcm')<br />
pixel=plik.pixel_array<br />
print pixel.shape<br />
>>>(2964, 2364)<br />
</source><br />
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.<br />
<source lang="python"><br />
pixel=pixel[::5,::5]<br />
print min(pixel.flatten()),max(pixel.flatten())<br />
>>> 0 3907<br />
</source><br />
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].<br />
<source lang="python"><br />
pixel=pixel*1.0/max(pixel.flatten())<br />
</source><br />
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<br />
<source lang="python"><br />
def dodajPasek(tablica, n):<br />
tablica[:n,:]=np.linspace(0,1,tablica.shape[1])<br />
dodajPasek(pixel,30)<br />
py.imshow(pixel,cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:dicom2.png]]<br />
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.<br />
===Jasność===<br />
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.<br />
<source lang="python"><br />
def jasnosc(tablica, a):<br />
def f(x,a):<br />
return min(a*x,1.0)<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
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.<br />
Zaobaczmy teraz na dwóch przykładach w jaki sposób funkcja jasnosc modyfikuje obraz<br />
<source lang="python"><br />
py.imshow(jasnosc(pixel,2),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(jasnosc(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:jasnosc1.png]]<br />
<br />
[[Plik:jasnosc2.png]]<br />
<br />
===Gamma===<br />
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.<br />
<br />
<source lang="python"><br />
def gamma(tablica, a):<br />
def f(x,a):<br />
return min(x**a,1.0)<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Możemy teraz na dwóch przykładach obejrzeć efekt działania przekształcenia gamma.<br />
<source lang="python"> <br />
py.imshow(gamma(pixel,2),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(gamma(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:gamma1.png]]<br />
<br />
[[Plik:gamma2.png]]<br />
<br />
===Próg===<br />
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. <br />
<source lang="python"><br />
def prog(tablica, a):<br />
def f(x,a):<br />
return 0 if x<a else 1<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Natomiast jej działanie na obraz wygląda nastepująco<br />
<source lang="python"><br />
py.imshow(prog(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
[[Plik:prog.png]]<br />
<br />
===Schodek===<br />
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 <br />
<source lang="python"><br />
def schodek(tablica, a,b):<br />
def f(x,a,b):<br />
return 1 if ((x<a) or (x>b)) else 0<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a,b)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
A przyklad działania tutaj.<br />
<source lang="python"><br />
py.imshow(schodek(pixel,0.2,0.6),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:schodek.png]]<br />
<br />
===Rozmycie i wyostrzenie===<br />
<source lang="python"><br />
from scipy import ndimage<br />
imag=ndimage.gaussian_filter(pixel,sigma=4)<br />
print imag<br />
</source><br />
<br />
WYOSTRZ<br />
<source lang="python"><br />
def wyostrz(tablica, sigma, a):<br />
rozmyty=np.array(ndimage.gaussian_filter(tablica,sigma=sigma))<br />
roznica=tablica-rozmyty<br />
wynik=rozmyty+a*roznica<br />
wynik/=max(wynik.flatten())<br />
dodajPasek(wynik,15)<br />
return wynik<br />
<br />
py.imshow(wyostrz(pixel,4,0),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(wyostrz(pixel,4,3),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:blur.png]]<br />
<br />
[[Plik:sharp.png]]<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Manipulacja_obrazem&diff=3592TI/Programowanie dla Fizyków Medycznych/Manipulacja obrazem2015-06-09T08:58:35Z<p>Tgubiec: </p>
<hr />
<div>==Manipulacja obrazem==<br />
Ćwiczenia z mainuplacji obrazem rozpocznijmy od wczytania pliku który będziemy przetwarzać. Dla fizyków medycznych naturalnie będzie to plik DICOM.<br />
<source lang="python"><br />
import dicom<br />
import numpy as np<br />
import pylab as py<br />
<br />
plik=dicom.read_file('I00001.dcm')<br />
pixel=plik.pixel_array<br />
print pixel.shape<br />
>>>(2964, 2364)<br />
</source><br />
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.<br />
<source lang="python"><br />
pixel=pixel[::5,::5]<br />
print min(pixel.flatten()),max(pixel.flatten())<br />
>>> 0 3907<br />
</source><br />
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].<br />
<source lang="python"><br />
pixel=pixel*1.0/max(pixel.flatten())<br />
</source><br />
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<br />
<source lang="python"><br />
def dodajPasek(tablica, n):<br />
tablica[:n,:]=np.linspace(0,1,tablica.shape[1])<br />
dodajPasek(pixel,30)<br />
py.imshow(pixel,cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:dicom2.png]]<br />
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.<br />
===Jasność===<br />
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.<br />
<source lang="python"><br />
def jasnosc(tablica, a):<br />
def f(x,a):<br />
return min(a*x,1.0)<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
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.<br />
Zaobaczmy teraz na dwóch przykładach w jaki sposób funkcja jasnosc modyfikuje obraz<br />
<source lang="python"><br />
py.imshow(jasnosc(pixel,2),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(jasnosc(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:jasnosc1.png]]<br />
<br />
[[Plik:jasnosc2.png]]<br />
<br />
===Gamma===<br />
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.<br />
<br />
<source lang="python"><br />
def gamma(tablica, a):<br />
def f(x,a):<br />
return min(x**a,1.0)<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Możemy teraz na dwóch przykładach obejrzeć efekt działania przekształcenia gamma.<br />
<source lang="python"> <br />
py.imshow(gamma(pixel,2),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(gamma(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:gamma1.png]]<br />
<br />
[[Plik:gamma2.png]]<br />
<br />
===Próg===<br />
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. <br />
<source lang="python"><br />
def prog(tablica, a):<br />
def f(x,a):<br />
return 0 if x<a else 1<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Natomiast jej działanie na obraz wygląda nastepująco<br />
<source lang="python"><br />
py.imshow(prog(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
[[Plik:prog.png]]<br />
<br />
===Schodek===<br />
W bardziej skomplikowanych przypadkach, mogą dla nas być nieistotne zarówno <br />
<source lang="python"><br />
def schodek(tablica, a,b):<br />
def f(x,a,b):<br />
return 1 if ((x<a) or (x>b)) else 0<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a,b)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
<br />
py.imshow(schodek(pixel,0.2,0.6),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:schodek.png]]<br />
<br />
===Rozmycie i wyostrzenie===<br />
<source lang="python"><br />
from scipy import ndimage<br />
imag=ndimage.gaussian_filter(pixel,sigma=4)<br />
print imag<br />
</source><br />
<br />
WYOSTRZ<br />
<source lang="python"><br />
def wyostrz(tablica, sigma, a):<br />
rozmyty=np.array(ndimage.gaussian_filter(tablica,sigma=sigma))<br />
roznica=tablica-rozmyty<br />
wynik=rozmyty+a*roznica<br />
wynik/=max(wynik.flatten())<br />
dodajPasek(wynik,15)<br />
return wynik<br />
<br />
py.imshow(wyostrz(pixel,4,0),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(wyostrz(pixel,4,3),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:blur.png]]<br />
<br />
[[Plik:sharp.png]]<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Manipulacja_obrazem&diff=3591TI/Programowanie dla Fizyków Medycznych/Manipulacja obrazem2015-06-09T08:49:57Z<p>Tgubiec: </p>
<hr />
<div>==Maniplacja obrazem==<br />
Ćwiczenia z mainuplacji obrazem rozpocznijmy od wczytania pliku który będziemy przetwarzać. Dla fizyków medycznych naturalnie będzie to plik DICOM.<br />
<source lang="python"><br />
import dicom<br />
import numpy as np<br />
import pylab as py<br />
<br />
plik=dicom.read_file('I00001.dcm')<br />
pixel=plik.pixel_array<br />
print pixel.shape<br />
>>>(2964, 2364)<br />
</source><br />
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.<br />
<source lang="python"><br />
pixel=pixel[::5,::5]<br />
print min(pixel.flatten()),max(pixel.flatten())<br />
>>> 0 3907<br />
</source><br />
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].<br />
<source lang="python"><br />
pixel=pixel*1.0/max(pixel.flatten())<br />
</source><br />
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<br />
<source lang="python"><br />
def dodajPasek(tablica, n):<br />
tablica[:n,:]=np.linspace(0,1,tablica.shape[1])<br />
dodajPasek(pixel,30)<br />
py.imshow(pixel,cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:dicom2.png]]<br />
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.<br />
===Jasność===<br />
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.<br />
<source lang="python"><br />
def jasnosc(tablica, a):<br />
def f(x,a):<br />
return min(a*x,1.0)<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
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.<br />
Zaobaczmy teraz na dwóch przykładach w jaki sposób funkcja jasnosc modyfikuje obraz<br />
<source lang="python"><br />
py.imshow(jasnosc(pixel,2),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(jasnosc(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:jasnosc1.png]]<br />
<br />
[[Plik:jasnosc2.png]]<br />
<br />
===Gamma===<br />
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.<br />
<br />
<source lang="python"><br />
def gamma(tablica, a):<br />
def f(x,a):<br />
return min(x**a,1.0)<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
</source><br />
Możemy teraz na dwóch przykładach obejrzeć efekt działania przekształcenia gamma.<br />
<source lang="python"> <br />
py.imshow(gamma(pixel,2),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(gamma(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:gamma1.png]]<br />
<br />
[[Plik:gamma2.png]]<br />
<br />
===Próg===<br />
<source lang="python"><br />
def prog(tablica, a):<br />
def f(x,a):<br />
return 0 if x<a else 1<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
<br />
py.imshow(prog(pixel,0.5),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:prog.png]]<br />
<br />
===Schodek===<br />
<source lang="python"><br />
def schodek(tablica, a,b):<br />
def f(x,a,b):<br />
return 1 if ((x<a) or (x>b)) else 0<br />
wynik=tablica.copy()<br />
for x,y in np.ndindex(tablica.shape):<br />
wynik[x,y]=f(tablica[x,y],a,b)<br />
dodajPasek(wynik,15)<br />
return wynik<br />
<br />
py.imshow(schodek(pixel,0.2,0.6),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:schodek.png]]<br />
<br />
===Rozmycie i wyostrzenie===<br />
<source lang="python"><br />
from scipy import ndimage<br />
imag=ndimage.gaussian_filter(pixel,sigma=4)<br />
print imag<br />
</source><br />
<br />
WYOSTRZ<br />
<source lang="python"><br />
def wyostrz(tablica, sigma, a):<br />
rozmyty=np.array(ndimage.gaussian_filter(tablica,sigma=sigma))<br />
roznica=tablica-rozmyty<br />
wynik=rozmyty+a*roznica<br />
wynik/=max(wynik.flatten())<br />
dodajPasek(wynik,15)<br />
return wynik<br />
<br />
py.imshow(wyostrz(pixel,4,0),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
<br />
py.imshow(wyostrz(pixel,4,3),cmap=py.cm.gray,interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:blur.png]]<br />
<br />
[[Plik:sharp.png]]<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/RRZ&diff=3590TI/Programowanie dla Fizyków Medycznych/RRZ2015-06-08T23:10:48Z<p>Tgubiec: </p>
<hr />
<div>__NOTOC__<br />
==Równania różniczkowe zwyczajne==<br />
Zajmieny się teraz problemem numerycznego rozwiązywania równań różniczkowych zwyczajnych o postaci:<br />
<br />
<!--<br />
{{Hide in print|This text will not be shown in the print.}}<br />
{{Only in print|This text will only be shown in the print.}}<br />
--><br />
<br />
<math> \frac{dy(t)}{dt} = f(t,y(t))</math>,<br />
<br />
z warunkeim początkowym<br />
<br />
<math>y(t_0)=y_0</math>.<br />
<br />
Zauważmy, że przykładowe równanie różniczkowe drugiego rzędu<br />
<br />
<math> \frac{d^2 x(t)}{dt^2} = \omega(t,x(t))</math>,<br />
<br />
można zapisać jako<br />
<br />
<math> \frac{d}{dt} \binom{x(t)}{x'(t)} = \binom{x'(t)}{\omega(t,x(t))}</math>.<br />
<br />
W analogiczny sposób równanie dowolnego rzędy możemy zapisać jako wektorowe równanie różniczkowe pierwszego rzędu. Wystarczy zatem, że skupimy się na rozwiązywaniu równań pierwszego rzędu, Rozwiązaniem postawionego problemu są ciągłe funkcje zmiennej czasowej t. Rozwiązanie numeryczne takiego problemu ogranicza się jednak do znalezienia wartości funkcji y(t) w skończonej liczbie punktów czasowych. W najprostrzym przypadku (do którego się tutaj ograniczymy) zakładamy, że punkty te są od siebie równo oddalone, a odległość między nimi nazywamy krokiem czasowym i tradycyjnie oznaczamy literą h. Zatem rozwiązanie równania na przedziale <math>(t_0,t_k)</math> sprwadzamy do rozwiązania w sekwencji czasów <math>t_0, t+1=t_0+h,t_2=t_0+2h,...,t_k=Nh</math>. Poprzez <math>x_n</math> oznaczać będziemy numeryczne przybliżenie ścisłego rozwiązania <math>x(t_n)</math>.<br />
<br />
==Metoda Eulera==<br />
Najprostszą metodą numeryczną rozwiązywania równań różniczkowych jest metoda Eulera. Przybliżmy pochodzną czasową występującą po lewej stronie równania przez iloraz różnicowy<br />
<br />
<math> \frac{d x(t)}{dt} \approx \frac{x(t+h)-x(t)}{h}</math>,<br />
<br />
przekształacjąc uzyskujemy<br />
<br />
<math> x(t+h) \approx x(t)+ h \frac{d x(t)}{dt} </math><br />
<br />
a po podstawieniu rozwiązywanego równania mamy<br />
<br />
<math> x(t+h) \approx x(t)+ h f(t,x(t)) </math>.<br />
<br />
Możemy to zapisać w postaci dyskretnej<br />
<br />
<math> x_{n+1} = x_n + h f(t_n,x_n) </math>.<br />
<br />
Wartość w kolejnej chwili czasu dana jest explicite poprzez wartość w chwili poprzedniej. Metoda ta nazywa się Explicit Euler. Możemy teraz zaimplementować ją w pythonie<br />
<br />
<!--<source lang="python">--><br />
<syntaxhighlight lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
#rozwiazujemy rownanie dx(t)/dt=f(t,x)<br />
<br />
#metoda Explicit Euler<br />
#f - funkcja f z rownania<br />
#x0-wartosc poczatkowa<br />
#t0-czas poczatkowy<br />
#tk-czas koncowy<br />
#h-krok czasowy<br />
<br />
def EE(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0))) # gdy mamy doczynienia w równaniem wektorowym<br />
else: x=np.zeros(N) #dla przypadku skalarnego<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
x[i]=np.array(x[i-1]+h*f(t[i-1],x[i-1]))<br />
i+=1<br />
return t,x<br />
</syntaxhighlight><br />
<!--</source>--><br />
Najłatwiej będzie przetestować napisaną metodę na równaniu którego ścisłe rozwiązanie jest znane.Zacznijmy zatem od równania oscylatora harmonicznego<br />
<source lang="python"><br />
def oscylator(t,y):<br />
x=y[0]<br />
xdot=y[1]<br />
return np.array([xdot,-x])<br />
</source><br />
Rozwiążmy to równanie z warunkeim początkowym [1.0,1.0] i od czasu od 0 do 100.<br />
<source lang="python"><br />
t,x=EE(oscylator,[1.0,1.0],0.0,100,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
rozwiązanie wygląda wówczas nastepująco<br />
<br />
[[Plik:img01.png]]<br />
<br />
Jeżeli zaś wydłuzymy czas symulacji to 1000 otrzymamy<br />
<br />
[[Plik:img2.png]]<br />
<br />
Amplituta oscylacji rośnie wykładniczo i rozwiązanie numeryczne bardzo szybko przestaje mieć cokolwiek wspólnego ze ścisłym rozwiązaniem którego amplituda jest przecież stała. Metoda Explicit Euler już po kilku krokach czasowych przestaje przypominać ścisłe rozwiązanie. Niestety trudno jest zupełnie wyeliminować to zjawisko, za to możemy użyć metody, która znacznie wolniej będzie się oddalać od ścisłego rozwiązania. Zauważmy, że w metodzie Explicit Euler w każdym kroku czasowym tylko raz liczyliśmy wartość funkcji f. Liczbę wywołan funkcji f w każdym kroku czasowym nazywamy rzędem metody, stąd Explicit Euler jest metodą pierwszego rzędu. Wprowadźmy teraz przykładowe metody rzędu drugiego<br />
<br />
==Metoda Żabiego Skoku== <br />
W poprzedniej metodzie liczyliśmy wartość funkcji f w chwili <math>t_n</math>, która była pochodzną po czasie naszego ścisłego rozwiązania. Kolejny punkt <math>x_{n+1}</math> był liczony z przybliżenia liniowego funkcji w chwili poprzedniej. Jeżeli faktyczna trajektoria ma niezerową drugą pochodzną to takie liniowe przybliżenie zawsze będzie nas oddalało od ścisłego rozwiązania. Dosyć prostym pomysłem na poprawienie zbieżności metody jest tak zwany żabi skok. Policzmy najpierw wartość zmiennej x przesuwając się w czasie o h/2 i policzmy wówczas pochodną, którą oznaczmy przez <math>k_2</math><br />
<br />
<math> k_1=f(t_n,x_n) </math>.<br />
<br />
<math> k_2=f(t_n+h/2,x_n+h/2*k_1) </math>.<br />
<br />
Nastepnie używamy pochodznej <math> k_2 </math> zamiast pochodznej <math> k_1 </math> do obliczenia wartości funkcji w kolejnym kroku czasowym.<br />
<br />
<math> x_{n+1} = x_n + h k_2 </math>.<br />
<br />
Przykładowa implementacja tej metody wygląda nastepująco<br />
<source lang="python"><br />
def leapfrog(f,x0,t0,tk,h): <br />
<br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
<br />
#index<br />
i=1<br />
<br />
#petla glowna<br />
while (i<N):<br />
k1=f(t[i-1],x[i-1])<br />
k2=f(t[i-1]+h*0.5,x[i-1]+0.5*h*k1)<br />
x[i]=np.array(x[i-1]+h*k2)<br />
i+=1<br />
return t,x<br />
</source><br />
Rozwiązanie równania oscylatora tą metodą dla identycznych jak poprzednio czasów da nastepujące wyniki<br />
<source lang="python"><br />
t,x=leapfrog(oscylator,[1.0,1.0],0.0,100,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:img3.png]]<br />
<br />
<source lang="python"><br />
t,x=leapfrog(oscylator,[1.0,1.0],0.0,1000,0.01)<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:img4.png]]<br />
<br />
==Metoda Heuna==<br />
Kolejną metodą niewiele różniącą się od poprzedniej jest metoda Heuna. Zdefiniowana jest ona przez równania<br />
<math> k_1=f(t_n,x_n) </math>,<br />
<br />
<math> k_2=f(t_n+h/2,x_n+h/2*k_1) </math>,<br />
<br />
<math> x_{n+1} = x_n + h k_2 </math>.<br />
<br />
Implementacja wygląda następująco<br />
<br />
<source lang="python"><br />
<br />
def Heun(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
#liczba krokow czasowych<br />
N=len(t)<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
k1=f(t[i-1],x[i-1])<br />
k2=f(t[i-1]+h*0.5,x[i-1]+0.5*h*k1)<br />
x[i]=np.array(x[i-1]+h*0.5*(k1+k2))<br />
i+=1<br />
return t,x<br />
</source><br />
<br />
==Runge-Kutta czwartego rzędu==<br />
Ostatnią metodą, którą omówimy jest najbardziej popularna metoda zwana w skrócie RK4. Metoda to uznawana jest za kanoniczną i w większości zastosowań dającą najlepsze wyniki. Metody wyższego rzędu nie wnoszą już do wyniku znaczącej poprawy. Jak sugeruje nazwa metody, jej rząd to 4, czyli w każdym kroku czasowym czterokrotnie wywołujemy funkcję f. Metoda ta zdefiniowana jest wzorami<br />
<br />
<math> k_1 = f \left( t_n, x_n \right) </math>,<br />
<br />
<math> k_2 = f \left( t_n + {h \over 2}, x_n + {1 \over 2} k_1 \right) </math>,<br />
<br />
<math> k_3 = f \left( t_n + {h \over 2}, x_n + {1 \over 2} k_2 \right) </math>,<br />
<br />
<math> k_4 = f \left( t_n + h, x_n + k_3 \right) </math>,<br />
<br />
<math> x_{n+1} = x_n + {h \over 6} (k_1 + 2k_2 + 2k_3 + k_4) </math>,<br />
<br />
a implementacja wygląda następująco<br />
<br />
<source lang="python"><br />
def RK4(f,x0,t0,tk,h): <br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
#liczba krokow czasowych<br />
N=len(t)<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
#index<br />
i=1<br />
#petla glowna<br />
while (i<N):<br />
k1=h*f(t[i-1],x[i-1])<br />
k2=h*f(t[i-1]+h*0.5,x[i-1]+0.5*k1)<br />
k3=h*f(t[i-1]+h*0.5,x[i-1]+0.5*k2)<br />
k4=h*f(t[i-1]+h,x[i-1]+k3)<br />
x[i]=np.array(x[i-1]+(k1+2.0*k2+2.0*k3+k4)/6)<br />
i+=1<br />
return t,x<br />
</source><br />
==Przykłady==<br />
===Zadanie - Wahadło matematyczne z tłumieniem i siłą wymuszającą===<br />
<br />
Rozwiąż numerycznie metodą RK4 równanie różniczkowe oscylatora harmonicznego z tłumieniem i siłą wymuszającą<br />
<br />
<math> \frac{d^2x}{dt^2} + \Gamma \frac{dx}{dt} + w_0^2 x = f_0 \cos(W t) </math>,<br />
<br />
przyjmując parametry <math> f_0 =1, w_0=1, \Gamma=0.1, h=0.1 </math>. Wykreśl zależność amplitudy drgań w funkcji częstości siły wymuszającej W, dla W z przedziału [0.1,3].<br />
===Rozwiązanie===<br />
Zacznijmy od sprowadzenia równania drugiego stopnia do równania pierszego stopnia i zapisania go w postaci funkcji<br />
<br />
<source lang="python"><br />
def oscylator(t,y):<br />
f0=1.0<br />
w0=1.0<br />
Gamma=0.1<br />
<br />
x=y[0]<br />
xdot=y[1]<br />
return np.array([xdot,f0*np.cos(oscylator.W*t)-oscylator.Gamma*xdot-w0*w0*x])<br />
<br />
oscylator.W=1.0<br />
</source><br />
Nieprzypadkowo parametr W nie jest definiowany w samej funkcji jako zmienna wewnętrzna, ale jako atrybut obiektu jakim jest funkcja. Dzięki takiej konstrukcji łatwo będzie nam zmieniać parametr W czyli częstość siły wymuszającej. Zobaczmy jak wygląda trajektoria będąca rozwiązaniem tego równania dla warunku początkowego [0,1] i czasu końcowego równego 400.<br />
<source lang="python"><br />
py.plot(*RK4(oscylator,[0.0,1.0],0.0,400.0,0.1))<br />
py.show()<br />
</source><br />
<br />
[[Plik:oscy1.png]]<br />
<br />
Widać że początkowo układ dochodzi do stanu regularnych oscylacji. Do analizy amplitudy interesuje nas jedynie koncowa część więc ograniczymy się do analizy trajektorii od czasu 200 do czasu 400. Napiszmy teraz funkcję, która na podstawie trajektorii wyznaczy nam amplitudę oscylacji<br />
<br />
<source lang="python"><br />
def amplituda(x):<br />
lista=x[2000:,0]<br />
return (max(lista)-min(lista))*0.5<br />
</source><br />
<br />
Interesować nas będzie amplituda drgań w funkcji częstość W. Wygenerujmy listę wartości W dla których będziemy liczyć amplitudę.<br />
<br />
<source lang="python"><br />
Omegas=np.arange(0.1,3.0,0.05)<br />
</source><br />
Możemy teraz dla każdej z wartości W rozwiązać numerycznie równanie różniczkowe i wyznaczyć odpowiadającą amplitudę oscylacji<br />
<source lang="python"><br />
amp=[amplituda(RK4(oscylator,[0.0,1.0],0.0,400.0,0.1)[1]) for oscylator.W in Omegas]<br />
</source><br />
Wynik koncowy wyglada następująco<br />
<source lang="python"><br />
py.plot(Omegas,amp)<br />
py.show()<br />
</source><br />
[[Plik:oscy2.png]]<br />
<br />
Jak można było się domyślić amplituda jest największa gdy częstotliwość wymuszania W pokrywa się z wartością częstotliwości drgań własnych <math> w_0=1 </math><br />
<br />
===Zadanie - Układ Lorenza===<br />
Rozwiąż układ równań różniczkowych Lorenza dany wzorami<br />
<br />
<math> \begin{cases}\dot x=\sigma y-\sigma x\\\dot y=-xz+rx-y\\\dot z=xy-bz\end{cases}, </math> <br />
<br />
metodą całkowania Rungego–Kutty drugiego rzędu z α = 2/3, czyli<br />
<br />
<math>k_1 = f(t_n,x_n)</math> , <br />
<br />
<math>k_2 = f(t_n + \tfrac{2}{3}h, x_n + \tfrac{2}{3}h k_1)</math> , <br />
<br />
<math>x_{n+1} = x_n + h \left(\tfrac{1}{4}k_1+\tfrac{3}{4} k_2 \right). </math> <br />
<br />
Przyjmij sigma=10, b=8/3, r=99.96, krok czasowy h=0.005 i warunki początkowe x=1,y=0,z=0. Wykonaj 8000 kroków czasowych. Układ po pewnym czasie zacznie poruszać się po pewnej periodycznej trajektorii. Wykonaj 3 rysunki TEJ PERIODYCZNEJ TRAJEKTORII (bez okresu dochodzenia do niej) w płaszczyznach (x,y), (y,z) i (z,x). Wypisz na ekranie przedziały wartości jakie przyjmują zmienne x,y i z na periodycznej trajektorii oraz okres trajektorii periodycznej.<br />
===Rozwiązanie===<br />
Zacznijmu od implementacji podanej w treści zadania metody całkowania RK2<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
<br />
#rozwiazujemy rownanie dx(t)/dt=f(t,x)<br />
<br />
#metoda Explicit Euler<br />
#f - funkcja f z rownania<br />
#x0-wartosc poczatkowa<br />
#t0-czas poczatkowy<br />
#tk-czas koncowy<br />
#h-krok czasowy<br />
<br />
def RK2(f,x0,t0,tk,h): <br />
<br />
#generujemy wektor czasow<br />
t=np.arange(t0,tk,h)<br />
<br />
#liczba krokow czasowych<br />
N=len(t)<br />
<br />
#wektor wynikowy<br />
if hasattr(x0, "__len__"): x=np.zeros((N,len(x0)))<br />
else: x=np.zeros(N)<br />
<br />
#wpisujemy wartosc poczatkowa<br />
x[0]=np.array(x0)<br />
<br />
#index<br />
i=1<br />
<br />
#petla glowna<br />
while (i<N):<br />
k1=h*f(t[i-1],x[i-1])<br />
k2=h*f(t[i-1]+h*2.0/3.0,x[i-1]+k1*2.0/3.0)<br />
x[i]=np.array(x[i-1]+0.25*k1+0.75*k2)<br />
i+=1<br />
return t,x<br />
</source><br />
Następnie napiszmy funkcję opisującą układ Lorenza<br />
<source lang="python"><br />
def Lorenza(t,y):<br />
sigma=10.0<br />
b=8.0/3<br />
r=99.96<br />
xx=y[0]<br />
yy=y[1]<br />
zz=y[2]<br />
xdot=sigma*(yy-xx)<br />
ydot=-xx*zz+r*xx-yy<br />
zdot=xx*yy-b*zz<br />
return np.array([xdot,ydot,zdot])<br />
</source><br />
<br />
Zobaczmy teraz jak wyglądają trajektorie wszystkich trzech współrzędnych rozwiązania z zadanymi parametrami<br />
<source lang="python"><br />
t,x=RK2(Lorenza,[1.0,0.0,0.0],0.0,40.0,0.005)<br />
py.plot(t,x[:,1])<br />
py.show()<br />
py.plot(t,x[:,2])<br />
py.show()<br />
py.plot(t,x[:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:lor1.png]]<br />
<br />
[[Plik:lor2.png]]<br />
<br />
[[Plik:lor3.png]]<br />
<br />
Możemy zauważyć że układ początkowo zachowuje się chaotycznie a potem dąży do pewnego stanu ustalonego (tzw. atraktora). Cała trajektoria składa się z 8000 punktów, przyjmijmy że powyżej punktu o numerze 2500 mamy już do czynienia tylko z periodyczną trajektorią. Wykreślmy zatem portrety fazowe o których mowa w treści zadania<br />
<source lang="python"><br />
py.plot(x[2500:,0],x[2500:,1])<br />
py.show()<br />
py.plot(x[2500:,1],x[2500:,2])<br />
py.show()<br />
py.plot(x[2500:,2],x[2500:,0])<br />
py.show()<br />
</source><br />
<br />
[[Plik:lor4.png]]<br />
<br />
[[Plik:lor5.png]]<br />
<br />
[[Plik:lor6.png]]<br />
<br />
Wypisanie zakresów jest już tylko formalnością<br />
<source lang="python"><br />
print 'zmienna x przyjmuje wartosci z zakresu: (',min(x[2500:,0]),',',max(x[2500:,0]),')'<br />
print 'zmienna y przyjmuje wartosci z zakresu: (',min(x[2500:,1]),',',max(x[2500:,1]),')'<br />
print 'zmienna z przyjmuje wartosci z zakresu: (',min(x[2500:,2]),',',max(x[2500:,2]),')'<br />
</source><br />
Okres trajektorii periodycznej możemy znaleść na przykład w ten sposób<br />
<source lang="python"><br />
prog=140<br />
lista=[]<br />
for i in range(2500,8000):<br />
if (x[i-1,2]<prog) and (x[i,2]>prog): lista.append(i)<br />
print 'okres to:',np.mean(np.diff(lista)*0.005)<br />
<br />
>>> okres to: 1.0975<br />
>>> zmienna x przyjmuje wartosci z zakresu: ( -33.4431203059 , 25.8037953495 )<br />
>>> zmienna y przyjmuje wartosci z zakresu: ( -56.7169238157 , 37.3166709986 )<br />
>>> zmienna z przyjmuje wartosci z zakresu: ( 53.4652816712 , 144.264397579 )<br />
<br />
</source><br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=TI/Programowanie_dla_Fizyk%C3%B3w_Medycznych/Morfologia_matematyczna&diff=3589TI/Programowanie dla Fizyków Medycznych/Morfologia matematyczna2015-06-08T23:08:58Z<p>Tgubiec: </p>
<hr />
<div>==Morfologia Matematyczna==<br />
<source lang="python"><br />
import numpy as np<br />
import pylab as py<br />
a=np.zeros((100,100),dtype=np.bool)<br />
a[30:50,30:50]=True<br />
a[50:70,50:70]=True<br />
<br />
brush7=np.array([[0,0,1,1,1,0,0],[0,1,1,1,1,1,0],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[1,1,1,1,1,1,1],[0,1,1,1,1,1,0],[0,0,1,1,1,0,0]],dtype=np.bool)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia1.png]]<br />
<br />
Przydatna będzie procedura<br />
<source lang="python"><br />
def brush2list(brush):<br />
result=[]<br />
N=brush.shape[0]<br />
middle=N/2<br />
for x in range(N):<br />
for y in range(N):<br />
if brush[x,y]: result.append((x-middle,y-middle))<br />
return result<br />
</source><br />
<br />
===Dylacja===<br />
<source lang="python"><br />
def dylacja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=max([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(dylacja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-dylacja.png]]<br />
<br />
===Erozja===<br />
<source lang="python"><br />
def erozja(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=min([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
<br />
py.imshow(erozja(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-erozja.png]]<br />
<br />
===Otwarcie i zamknięcie===<br />
<source lang="python"><br />
def otwarcie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return dylacja(erozja(fig,brush),brush)<br />
<br />
def zamkniecie(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
return erozja(dylacja(fig,brush),brush)<br />
<br />
py.imshow(otwarcie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
py.imshow(zamkniecie(a,brush7), cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-otwarcie.png]]<br />
<br />
[[Plik:morfologia-zakmniecie.png]]<br />
<br />
===Filtr medianowy===<br />
<source lang="python"><br />
def medianowy(fig,brush=np.array([[0,1,0],[1,1,1],[0,1,0]],dtype=np.bool)):<br />
result=np.zeros(fig.shape)<br />
brush_list=brush2list(brush)<br />
for x in range(3,fig.shape[0]-3):<br />
for y in range (3,fig.shape[1]-3):<br />
result[x,y]=np.median([fig[x+x_shift,y+y_shift] for (x_shift,y_shift) in brush_list])<br />
return result<br />
</source><br />
Służy do usuwania szumu.<br />
<source lang="python"><br />
for x,y in np.ndindex(a.shape):<br />
if (np.random.random()<0.05): a[x,y]=False<br />
if (np.random.random()>0.95): a[x,y]=True<br />
<br />
<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
a=medianowy(a)<br />
py.imshow(a, cmap=py.cm.gray, interpolation='nearest')<br />
py.show()<br />
</source><br />
<br />
[[Plik:morfologia-zaszumiony1.png]]<br />
<br />
[[Plik:morfologia-zaszumiony2.png]]<br />
<br />
[[Plik:morfologia-zaszumiony3.png]]<br />
<br />
[[Plik:morfologia-zaszumiony4.png]]<br />
<br />
[["Programowanie dla Fizyków Medycznych"]]</div>Tgubiechttp://brain.fuw.edu.pl/edu/index.php?title=Plik:Morfologia-zaszumiony4.png&diff=3588Plik:Morfologia-zaszumiony4.png2015-06-08T23:07:39Z<p>Tgubiec: </p>
<hr />
<div></div>Tgubiec