<jupyter_start><jupyter_text>8. Ablakolt Fourier-transzformációs módszer, WFTMethodEzt az eljárást szimulált interferogramon mutatom be. Példaként generálok egyet:<jupyter_code>import numpy as np
import matplotlib.pyplot as plt
import pysprint as ps
g = ps.Generator(1, 4, 2.5, delay=900, GDD=400, FOD=40000, pulse_width=3, resolution=0.05)
g.generate()

mywft = ps.WFTMethod(*g.data)
mywft.plot()<jupyter_output><empty_output><jupyter_text>Ablakfüggvény sorozatot adok hozzá az interferogramhoz:<jupyter_code>mywft.add_window_linspace(2.2, 3.1, 1000, fwhm=0.04, order=2)<jupyter_output><empty_output><jupyter_text>**FONTOS!** Ha a fenti `add_window_linspace` lefutott, akkor az ablakfüggvény sorozat már hozzáadódott az interferogramhoz. Ha később megváltoztatnánk bármelyik paraméterét (pl. inkább más `fwhm`-ot szeretnénk beállítani), akkor az előző ablakfüggvények is megmaradak. Emiatt ajánlott a fenti cellát a következőképpen használni:<jupyter_code># az összes hozzáadott ablakfüggvény eltávolítása függetlenül attól, hogy korábban voltak-e
mywft.remove_all_windows()

# ezután adom hozzá az ablakfüggvény sorozatot
mywft.add_window_linspace(2.2, 3.1, 1000, fwhm=0.05, order=2)<jupyter_output><empty_output><jupyter_text>Az aktuálisan hozzáadott ablakfüggvényeket a `WFTMethod.windows` paraméterrel érjük el, ami egy `dictionary`-t ad vissza, amelyben a *key* az ablakfüggvény központi helye, a hozzá tartozó *value* pedig maga az ablakfüggvény reprezentációja (`ps.core.window.GaussianWindow`). Az interferogram teljes tartományát a `cover(N, **kwargs)` függvénnyel gyorsabban is elvégezhetjük.<jupyter_code>len(mywft.windows)<jupyter_output><empty_output><jupyter_text>Ablakfüggvényt törölni a `remove_window_at(center)` függvénnyel lehetséges. Ennek az ablakfüggvény központi helyét kell megadni. Ha nincs olyan ablakfüggvény, akkor a hibaüzenetben segítséget nyújt: kiiírja a legközelebbi ablakfüggvényt. Ablakfüggvény intervallum törléséhez a `remove_window_interval(start, stop)` függvény használható.<jupyter_code>mywft.remove_window_at(2.25)<jupyter_output><empty_output><jupyter_text>Mivel ez a módszer ún. [*embarrassingly parallel*](https://en.wikipedia.org/wiki/Embarrassingly_parallel) számítás, ezért lehetőség van több szálon futtatni a kiértékelést. Ehhez a [*Dask*](https://dask.org/) csomagnak telepítve kell lennie (Anaconda-ban alapértelmezetten benne van). Ezt a `parallel` argumentummal szabályozhatjuk. A többszálas futás általában 50-70% gyorsulást eredményez a single-core módhoz képest. A kiértékelést most `parallel=True` módon fogom futtatni:<jupyter_code>mywft.calculate(reference_point=2.5, order=4, fastmath=False, parallel=True);<jupyter_output>[########################################] | 100% Completed |  9.2s
Skipped: 0<jupyter_text>A fenti `calculate` függvény ha már egyszer lefutott és megpróbáljuk újra lefuttatni más referencia ponttal vagy más illesztési renddel, akkor a gyorsítótárazás miatt azok már azonnal végrehajtódnak (csak az illesztést számolja újra).<jupyter_code>mywft.heatmap()
mywft.errorplot(percent=True)<jupyter_output><empty_output><jupyter_text>Példa tetszőleges plotok készítésére:<jupyter_code>fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(13, 11))

mywft.plot(ax=ax1)
ax1.set(title="Eredeti ifg")

mywft.view_windows(ax=ax2, maxsize=200, alpha=0.3)
ax2.set(title="Eredeti ifg ablakfüggvényekkel")

mywft.heatmap(ax=ax3, include_ridge=True)
ax3.set(title="Contourplot");

mywft.errorplot(ax=ax4, percent=True)<jupyter_output>c:\pyt\pysprint\pysprint\core\methods\wft.py:181: PySprintWarning: Image seems crowded, displaying only a subsample of the given windows.
  PySprintWarning
[ legend.py:1193 -   _parse_legend_args() ] No handles with labels found to put in legend.<jupyter_text>Egy másik szimulált interferogram kiértékelését most `parallel=False` módon fogom elvégezni, és csak a `build_GD` függvényt használom, amivel a *GD* görbét kapom vissza (`ps.core.phase.Phase`).<jupyter_code>f = ps.Generator(1, 4, 2.5, delay=1900, GDD=-600, TOD=4000, pulse_width=3, resolution=0.05)
f.generate()

mywft2 = ps.WFTMethod(*f.data)
mywft2.add_window_arange(1.5, 3.5, 0.01, std=0.04)
GD_gorbe = mywft2.build_GD()
GD_gorbe.plot()
GD_gorbe.fit(reference_point=2.5, order=3);
GD_gorbe.errorplot(percent=True)<jupyter_output><empty_output><jupyter_text>A `fastmath` opciót itt nem adtam meg. Ez alapértelmezetten `True`, ilyenkor a `heatmap` nem hívható, mivel ekkor az ahhoz szükséges adatokat nem építi fel a program.<jupyter_code>mywft2.heatmap()<jupyter_output><empty_output><jupyter_text>Néhány hiányosság: * A gerincvonalat a program automatikusan keresi meg, jelenleg még nincs lehetőség manuálisan állítani. Ez jól működik szimulált példákon, viszont rosszul is teljesíthet valós mérések esetén. Jelenleg ez a része a programnak fejlesztés alatt van (manuális állítás, különböző filterezése a detektált gerincvonalnak, pl. moving avarage validation.)* Csak egyetlen egy gerincvonalat keres meg, így nem alkalmas pl. kettősen törő optikai szálak esetén, amikor mindkét polarizációs tengelye mentén terjedő módus egyidejűleg gerjesztve van. Szintén fejlesztés alatt van.  8.1 Saját ablakfüggvény használataA program alapbeállításként Gauss ablakfüggvényeket használ, azonban néhány módosítással akár tetszőleges ablakfüggvényt is használhatunk. Tekintsünk most egy Mexican hat waveletet (vagy Ricker waveletet). Ez a következő alakú:$A \cdot (1 - (\frac{x-x_0}{a})^2) \cdot e^{-0.5\cdot(\frac{x-x_0}{a})^2}$, ahol $A = \frac{2}{\sqrt{3a}\cdot\pi^{0.25}}$A továbbiakban ezt a waveletet fogjuk példaként használni. Az első lépés a szükséges eszközök importálása. Itt két dologra van szükségünk, a `numpy` a numerikus rutinokra, illetve a `WindowBase` osztályra, amely a `pysprint.core.window` útvonalon érhető el. ```pythonimport numpy as npfrom pysprint.core.window import WindowBase``` Minden ablakfüggvényt a `WindowBase` alosztályaként kell definiálni. Az `__init__` szignatúrájában két kötelező argumentum van: `x` és `center`. Az `x` az adott interferogram x tengelyéből kerül a számítás közben megállításra. A `center` egy egyedi azonosítója az adott ablakfüggvénynek (pl. Gauss függvényeknél a maximum helye, a fenti waveletnél ezt $x_0$-val jelöltük). Minden osztály első argumentuma `self`, ezután definiáljuk a kötelező argumentumokat. Eddig ez így fest:```pythonimport numpy as npfrom pysprint.core.window import WindowBaseclass MexicanHat(WindowBase):    def __init__(self, x, center):```A wavelethez azonban kell egy másik paraméter is, ez az `a`. Minden használt paramétert meg kell adnunk az `__init__` metódusnak, így ezt is adjuk meg. A `self` kivételével az összes argumentumot az osztályhoz kell "kötni", hogy a további metódusokban is elérhetőek maradjanak. Tegyük meg:```pythonimport numpy as npfrom pysprint.core.window import WindowBaseclass MexicanHat(WindowBase):    def __init__(self, x, center, a):         a megadott paraméterek összekötése az osztállyal        self.x = x        self.center = center        self.a = a```Természetesen alapértékeket is adhatuk az általunk használt paramétereknek (itt csak az `a`-nak), így számítás közben azokat használja majd a szoftver.Esetünkben az `A` paraméter az `a` paraméterből számított, így arra az `__init__` metódusban nincs szükségünk. *Megjegyzés: Az* `__init__` *metódusban ne végezzük számolást, csak a paraméterek inicializálását. Bár ez nem szigorú szabály, mégis a számolásokat a legtöbbször meggyorsítja.*A következő lépés, hogy definiáljuk hogyan számolhatóak ki az ablakfüggvény értékei. Ez egy ún. `property` lesz, amihez a decorator szintaxist használjuk. Ne aggódjunk, ennek megértése nem fontos és nem szükséges, tekintsünk rá úgy, mint recept. A megfelelő függvényt mindig nevezzük `y`-nak, amely egyetlen argumentumot fogad el, a `self`-et. A függvény testébe a matematikai formulákat és egyéb logikát írjuk, ami megmondja hogyan számoljuk ki az ablakfüggvény értékeit. Emlékezzünk vissza, az `__init__`-ben megadott paramétereket elérhetjük itt is! A fenti wavelet formuláját beírva kapjuk:```python@propertydef y(self):     kiszámoljuk A-t    A = 2 / (np.sqrt(3 * self.a) * (np.pi ** 0.25))     kiszámítjuk a formulából az értékeket    ys = A * (1 - ((self.x - self.center) / self.a) ** 2) * np.exp(-0.5 * ((self.x - self.center) / self.a) ** 2)     visszaadjuk a kiszámított értékeket    return ys```Összerakva az egészet:<jupyter_code>import numpy as np
from pysprint.core.window import WindowBase

class MexicanHat(WindowBase):
    def __init__(self, x, center, a):
        self.x = x
        self.center = center
        self.a = a

    @property
    def y(self):
        A = 2 / (np.sqrt(3 * self.a) * (np.pi ** 0.25))
        ys = A * (1 - ((self.x - self.center) / self.a) ** 2) * np.exp(-0.5 * ((self.x - self.center) / self.a) ** 2)
        return ys<jupyter_output><empty_output><jupyter_text>Ezen a ponton minden szükséges részletet megadtunk ahhoz, hogy a program használni tudja a MexicanHat ablakfüggvényt. Előtte azonban ellenőrizzük le, hogy valóban helyes-e a definíció. Minden `WindowBase` leszármazott osztálynak van `plot` metódusa. Próbáljuk ki azt!<jupyter_code>hat = MexicanHat(x=np.linspace(-10, 10, 500), center=0, a=2)
hat.plot()<jupyter_output><empty_output><jupyter_text>Ez úgy tűnik helyesen adta vissza a kívánt ablakfüggvényt. Használjuk az Ablakot Fourier-transzformációs kiértékeléshez! A korábbiakkal teljesen analóg a számolás menete, egyetlen változtatás, hogy a `window_class` argumentumnak meg kell adnunk az általunk definiált `MexicanHat` osztályt.<jupyter_code>g = ps.Generator(1, 4, 2.5, delay=900, GDD=400, FOD=40000, pulse_width=3, resolution=0.05)
g.generate()

custom_wft = ps.WFTMethod(g.x, g.y, window_class=MexicanHat)<jupyter_output><empty_output><jupyter_text>Futtassuk a szokásos lépéseket: adjunk hozzá ablakfüggvény-sorozatot, futtassuk a kiértékelést.<jupyter_code>custom_wft.add_window_linspace(2.2, 3, 500, a=0.01)
custom_wft.calculate(2.5, 4, fastmath=False, parallel=True, ransac=True);
custom_wft.heatmap()<jupyter_output><empty_output><jupyter_text>8.2 Egy teljes példaA következőkben egy rossz minőségű interferogramon bemutatom hogyan hozható ki belőle a maximális mennyiségű értékes információ. Töltsük be és vizsgáljuk meg!<jupyter_code>poor_ifg = ps.WFTMethod.parse_raw('datasets/ifg_poor.trt',
                                  skiprows=8,
                                  decimal=",",
                                  sep=";",
                                  meta_len=6)
poor_ifg.chdomain()<jupyter_output><empty_output><jupyter_text>Megjegyzendő, hogy a betöltéssel azonos cellába tettem a tartományváltást, hogy biztosan ne futtassuk le többször az adott interferogramon.<jupyter_code>poor_ifg.plot()<jupyter_output><empty_output><jupyter_text>Az első dolgunk, hogy eldöntsük milyen ablakfüggvény-sorozatot állítunk be. Itt egy rövidítést fogok használni, a `cover` függvényt, amely a teljes tartományt egyenközű módon fogja az ablakfüggvényekket kitölteni.<jupyter_code>poor_ifg.cover(
    N=500, # db ablakfüggvény
    fwhm=0.05, # PHz az ablakfüggvény félértékszélessége
    order=2, # normál Gauss-függvény
)<jupyter_output><empty_output><jupyter_text>Arra számítunk, hogy azonnali futás után nem adódik jó eredmény, a kapott GD görbén még dolgoznunk kell. Ehhez használjuk a `build_GD` függvényt.<jupyter_code>gd = poor_ifg.build_GD(fastmath=False, parallel=True)<jupyter_output>[########################################] | 100% Completed |  1.0s
Skipped: 85<jupyter_text>Vizsgáljuk meg az eredményül kapott csoportkésleltetés görbét:<jupyter_code>gd.plot()<jupyter_output><empty_output><jupyter_text>Evidens, hogy a hasznos rész a 1.8 és 3.5 PHz közötti tartomány. Ezt vágjuk ki:<jupyter_code>gd.slice(1.8, 3.5)
gd.plot()<jupyter_output><empty_output><jupyter_text>A grafikon alapján rossznak tűnhet a helyzet, de a függőleges koordináta nagyságrendben különbözik az előző ábrától. Ezen a ponton illeszthetnénk is, de van még további mód a javításra. Használjuk az ún. [RANSAC](https://en.wikipedia.org/wiki/Random_sample_consensus) szűrést. Az algoritmus leírása túlmutat ezen oldal feladatán, a megadott linken olvashatunk róla. A `gd` objektumon a `ransac_filter` függvénnyel, illetve az `apply_filter` függvénnyel használható. Ugyan elsőrendű függvény várunk, mégis valamilyen kis nemzéró maradék diszperziója az üres spektrométernek is lehet. Emiatt a szűrést legalább másodrendben kell végeznünk, hogy a valódi zajt távolítsuk el, és ne a spektrométerre jellemző sajátosságokat.<jupyter_code>gd.ransac_filter(order=2, plot=True, residual_threshold=0.4)
plt.legend();<jupyter_output><empty_output><jupyter_text>Ezzel intelligens módon kiszűrtük a zaj legnagyobb részét a csoportkésleltetés görbéből. Innen már nincs más dolgunk, mint alkalmazni a szűrőt, majd függvényt illeszteni és kiszámolni az együtthatókat.<jupyter_code>gd.apply_filter()
gd.fit(reference_point=2.355, order=3);<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09666975023126735, 'fraction_numerical': 0.018038852913968548, 'mean_word_length': 5.882228116710875, 'pattern_counts': {'":': 0, '<': 86, '<?xml version=': 0, '>': 86, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '22', 'dir': 'jupyter-structured-clean-dedup', 'id': '263380', 'license': 'MIT', 'n_tokens_mistral': 5601, 'n_tokens_neox': 5583, 'n_words': 1253, 'path': 'doc/hu_wft.ipynb', 'repo_name': 'Ptrskay3/PySprint', 'size': '12972'}