Homebrew (Wikipedie), správce balíčků pro macOS a od verze 2.0.0 také pro Linux, byl vydán ve verzi 4.5.0. Na stránce Homebrew Formulae lze procházet seznamem balíčků. K dispozici jsou také různé statistiky.
Byl vydán Mozilla Firefox 138.0. Přehled novinek v poznámkách k vydání a poznámkách k vydání pro vývojáře. Řešeny jsou rovněž bezpečnostní chyby. Nový Firefox 138 je již k dispozici také na Flathubu a Snapcraftu.
Šestnáctý ročník ne-konference jOpenSpace se koná 3. – 5. října 2025 v Hotelu Antoň v Telči. Pro účast je potřeba vyplnit registrační formulář. Ne-konference neznamená, že se organizátorům nechce připravovat program, ale naopak dává prostor všem pozvaným, aby si program sami složili z toho nejzajímavějšího, čím se v poslední době zabývají nebo co je oslovilo. Obsah, který vytvářejí všichni účastníci, se skládá z desetiminutových
… více »Richard Stallman přednáší ve středu 7. května od 16:30 na Technické univerzitě v Liberci o vlivu technologií na svobodu. Přednáška je určená jak odborné tak laické veřejnosti.
Jean-Baptiste Mardelle se v příspěvku na blogu rozepsal o novinkám v nejnovější verzi 25.04.0 editoru videa Kdenlive (Wikipedie). Ke stažení také na Flathubu.
TmuxAI (GitHub) je AI asistent pro práci v terminálu. Vyžaduje účet na OpenRouter.
Byla vydána nová verze R14.1.4 desktopového prostředí Trinity Desktop Environment (TDE, fork KDE 3.5, Wikipedie). Přehled novinek i s náhledy v poznámkách k vydání. Podrobný přehled v Changelogu.
Bylo vydáno OpenBSD 7.7. Opět bez písničky.
V Tiraně proběhl letošní Linux App Summit (LAS) (Mastodon). Zatím nesestříhané videozáznamy přednášek jsou k dispozici na YouTube.
V druhém díle DSP kuchařky se podíváme na posunutí signálu na jinou frekvenci a filtrování lowpass/bandpass filtrem.
V předchozím příspěvku jsme viděli, že vzorečkem e-i*postupně_se_zvětšující_číslo vytvoříme bod rotující po jednotkové kružnici v komplexní rovině, čili signál na nějaké frekvenci. Počítat to ovšem z definice, tj. funkcí exp, není moc dobré:
Jenom pro představu v jakých délkách sekvence se pohybujeme, tak pokud samplujete 10 MS/s (standardní hodnota u trošku lepších rádií) a běží to celý den, tak potřebujete bilion vzorků.
Vyřešíme to tím, že zvoleným bodem budeme rotovat pomocí matice rotace, kterou si na začátku předpočítáme a pak už na ni nesáhneme. Případ pro matici 2x2 je na Wikipedii přímo rozepsán a vyžaduje 4 floatová násobení, 1 sčítání a 1 odčítání.
Problém s opakovaným násobením nějakou maticí, přičemž výsledek má být vždycky jednotkový vektor (a mění se jenom jeho směr), je, že se vlivem nepřesností může tento vektor časem prodlužovat nebo zkracovat - spočítaná matice rotace může mít vlivem nepřesností třeba normu 0.99999 nebo 1.00001, takže vektor s každou iterací nepatrně natáhne nebo smrskne, a po výše zmíněném bilionu vzorků to naprosto ustřelí. Proto je potřeba vektor rotátoru občas normalizovat, tj. spočítat jeho skutečnou velikost a tou ho vydělit, čímž se z něj zase stane vektor jednotkový. V praxi se normalizuje každých 512 nebo 1024 vzorků.
Ukážeme si výrobu rotároru pro frekvenci 0.95 radiánů na vzorek (tu jsem si vycucal z prstu a nechtěl jsem aby to bylo přesně 1, protože se třeba může zjistit, že sin(1) mají knihovny předpočítaný). Kód úmyslně nepoužívá komplexní aritmetiku, aby bylo vidět, co se skutečně počítá. A napsal jsem to v C, protože neumím Python/NumPy přesvědčit, aby počítalo v jednoduché přesnosti (ano, polím se dá nastavit dtype=float32, ale skalární operace se furt dělají v doublech). Nejdříve samotný kód v C:
#include <stdio.h> #include <complex.h> #include <math.h> int main() { const int nsamples = 110000; const float freq = 0.95; // naivní implementace s exp FILE * ef = fopen("/fs/ef.cfile", "wb"); for(int i = 0; i<nsamples; i++) { complex float result = cexpf(I * freq * i); fwrite_unlocked(&result, 8, 1, ef); } fclose(ef); // implementace s rotací pomocí předpočítaných hodnot FILE * mf = fopen("/fs/mf.cfile", "wb"); float rot_cos = cosf(freq); float rot_sin = sinf(freq); float re = 1; float im = 0; for(int i = 0; i<nsamples; i++) { fwrite_unlocked(&re, 4, 1, mf); fwrite_unlocked(&im, 4, 1, mf); float re_new = re * rot_cos - im * rot_sin; float im_new = re * rot_sin + im * rot_cos; re = re_new; im = im_new; if(i % 10240 == 0 && i > 0) { // úmyslně hodně, aby bylo vidět, jak to diverguje float abs = hypotf(re, im); re = re/abs; im = im/abs; } } fclose(mf); }
A vyhodnocení:
#!/usr/bin/env python3 import numpy as np import matplotlib.pyplot as plt rotator_from_exp = np.fromfile("/fs/ef.cfile", dtype=np.complex64) rotator_matmul = np.fromfile("/fs/mf.cfile", dtype=np.complex64) ax = plt.subplot(211) plt.title("Rozdíl fáze po sobě jdoucích vzorků") plt.plot(np.fmod(np.angle(rotator_from_exp[:-1]) - np.angle(rotator_from_exp[1:]) + np.pi, 2*np.pi) - np.pi, label="exp") plt.plot(np.fmod(np.angle(rotator_matmul[:-1]) - np.angle(rotator_matmul[1:]) + np.pi, 2*np.pi) - np.pi, label="matmul") plt.legend() plt.subplot(212, sharex = ax) plt.title("Absolutní hodnota (měla by být 1)") plt.plot(np.abs(rotator_from_exp)) plt.plot(np.abs(rotator_matmul)) plt.show()
Vidíme, že fáze u naivního počítání začne být velmi brzy nepřesná. Oproti tomu rotátor s použitím matice diverguje v absolutní hodnotě - ale zde je schválně nastavená normalizace málo často, aby to bylo vidět. Zde ještě detail, na kterém je vidět, jak cexpf šumí:
Jako ukázkový signál si vygenerujeme chirp. Jde o signál s postupně rostoucí frekvencí a používá se jako nejjednodušší varianta rozprostřeného spektra. Jak na to? Výše jsme měli e-i*lineárně_se_zvětšující_číslo a byla to konstantní frekvence. Pokud budeme zvětšovat zvětšování toho čísla, bude se zvětšovat i frekvence. Nejjednodušší je i toto zvětšování dělat lineárně, čili to bude e-i*kvadraticky_se_zvětšující_číslo. Proto se tomu taky někdy říká signál s kvadratickou fází. Vyrobíme to takto:
nsamples = 10240 dphase = 15 quadratic_ramp = np.linspace(-dphase, dphase, nsamples)**2 df = quadratic_ramp[0] - quadratic_ramp[1] hbin = 256 - df * 512 print("Frekvenční výchylka jednostranná: %f Hz; pro FFT velikosti 512 očekáváme %i. bin"%(df, hbin)) chirp = np.exp(2*np.pi*1j*quadratic_ramp)
Nakreslíme si waterfall jako minule (vyznačil jsem do něj středové osy a 211. bin, aby šlo porovnat, na jaké frekvenci to začíná a jakou jsme spočítali):
Mimochodem zde by signál měl být dokonale čistý a tedy ty artefakty jsou asi artefakty vzniklé použitím okýnka.
Fázi signálu posuneme vynásobením e-i*konstanta. Normálně bych to sem nedával, ale zrovna jsem to nedávno použil (poprvé) - debugoval jsem radar tak, že jsem koukal na odraz od vzdáleného kopce, a porovnával jsem ho s tím, co si myslím, že vysílám. Pokud to souhlasí, tak se tím ověří, že celá ta cesta (od vymyšlení signálu, přes jeho vygenerování, zesílení, odvysílání, putování atmosférou, odraz, přijetí, ofiltrování a analýzu) funguje. Ale signál samozřejmě přijde náhodně fázově posunutý - a aby to šlo porovnat, tak se musí posunout zpátky.
otocenej = chirp * np.exp(-1j*0.95)
Další běžná operace je posunutí (změna) frekvence signálu. Provede se vynásobením (po složkách) s rotátorem. Já tady použiju adhoc vyrobený z definice, ale jak jsme psali výše, pro delší signály se to musí vygenerovat chytřeji. Takhle ho posuneme ve frekvenci o 0.95 radiánů na sample:
freq=0.95 posunutej = chirp * np.exp(1j*np.linspace(0, nsamples*freq, nsamples))
Zkuste si, co se stane, když ho posunete o -1, o PI (wrapne se to), o 1000, o 100000000000 (zaokrouhlovací chyby se projeví i při počítání v doublech).
Pokud chcete tohle dělat s delšími signály, doporučuji použít příslušnou funkci z Volku, jejich implementace je ručně vektorizována pomocí SSE a AVX instrukcí.
Často je potřeba vybrat ze signálu oblast (ve frekvencích) od-do a zbytek potlačit. K tomu budeme potřebovat lowpass FIR filtr. To je pole koeficientů, se kterými se signál zkonvolvuje. Vyrobit se dá GNU Radiem, jak jsme řešili v článku o GNU Radiu, ale SciPy na to má taky funkci. První parametr říká délku filtru (čím delší, tím ostřejší bude mít filtr hrany, ale asi bude mít horší nějaké parametry co rozmazávají v čase a bude náročnější na vyhodnocení, zejména pokud se to nebude vyhodnocovat FFT jak je popsáno níže), druhý říká, kolik pásma se má propustit. Někdy je potřeba filtr speciálního tvaru (např. raised cosine), to pak záleží na konkrétní aplikaci.
coeffs = scipy.signal.firwin(64, 0.1)
Zkonvolvujeme ho se signálem:
filtered = np.convolve(chirp, coeffs, "same")
Protože možná není na první pohled vidět, jak funguje konvoluce komplexního a reálného filtru, tak to rozepíšu:
filtered2 = np.convolve(np.real(chirp), coeffs, "same") + 1j * np.convolve(np.imag(chirp), coeffs, "same") print(np.allclose(filtered, filtered2)) # True
tj. reálnou a imaginární složku natahujeme zcela nezávisle.
Mód konvoluce (zde same
) říká, co se má dělat na krajích, kde se signál a filtr plně nepřekrývají. same
vrátí stejně dlouhý výstup jako je vstup, tj. jakoby tam vždycky nechá půlku té nevalidní oblasti. Pak ještě existuje valid
(vrací kratší výstup, a to pouze data, kde došlo k plnému překryvu) a full
(delší výstup, všude kde byl překryv nenulový).
Někdy je potřeba nemít lowpass, ale vybrat si signál, který není na nulové frekvenci / není symetrický podle středu. Naštěstí filtr lze posouvat úplně stejně jako signál, a tak můžeme posunutím z lowpassu udělat bandpass. Takový filtr bude komplexní, filtry, které nejsou symetrické kolem nuly, musí být komplexní.
coeffs_shifted = coeffs * np.exp(1j*np.linspace(0, len(coeffs)*0.95, len(coeffs))) filtered_shifted = np.convolve(chirp, coeffs_shifted, "same")
Konvoluce může být poměrně pomalá: v každém bodě výstupního signálu musíte provést tolik násobení, kolik je délka filtru. Pokud je navíc filtr komplexní (bandpass z předchozí kapitoly), tak jsou i tato násobení komplexní. Pro dlouhé filtry, které mohou mít třeba 1500 tapů, je to tak obtížně použitelné. Nyní se dostáváme k tomu, co jsem zmínil v předchozím díle: problém přeformulujeme tak, aby šlo aplikovat FFT, provést nějaký rychlý výpočet, a aplikovat zpětnou FFT. A takové přeformulování konvoluce právě nabízí Věta o konvoluci. Říká, že konvoluci lze provést následujícím algoritmem:
full
, musí být velikost FFT signál + filtr - 1; pokud nám stačí same
nebo valid
, stačí filtru půlka (protože to ve skutečnosti počítá cyklickou konvoluci a ty půlky se potkají v místě, které budeme zahazovat, protože není valid) nebo snad pro valid
tam žádné místo navíc být nemusí.full
), resp. úsek co nás zajímá.FFT má složitost NlogN a násobení po složkách má složitost N, zatímco konvoluce počítaná přímo ze vstupních dat měla složitost N*M (pro M=délka filtru). Pro dlouhé filtry a husté [ve smyslu dense = opak sparse] výstupy se to tak nejspíš vyplatí. Tady je implementace:
# right-pad numpy array to size with zeros def padto(a, size): return np.pad(a, (0, size - len(a)), mode="constant") # velikost FFT, kterou budeme používat fftsize = 16*1024 # FIR doplníme nulami na velikost FFT a ztransformujeme coeffs_shifted_padded = padto(coeffs_shifted, fftsize) coeffs_shifted_padded_fft = np.fft.fft(coeffs_shifted_padded) # signál doplníme nulami na velikost FFT a ztransformujeme chirp_padded = padto(chirp, fftsize) chirp_padded_fft = np.fft.fft(chirp_padded) # vynásobíme po složkách ztransformované convolved_fft = coeffs_shifted_padded_fft * chirp_padded_fft # ztransformujeme zpět convolved = np.fft.ifft(convolved_fft) # nyní máme výsledek dlouhý fftsize, ale jenom část z něj je "same" (resp. "valid"). Vybereme tuto část. convolved = convolved[len(coeffs)//2-1:len(chirp)+len(coeffs)//2-1] print(np.allclose(convolved, filtered_shifted)) # True
Za povšimnutí stojí, že pokud aplikujeme stále stejný filtr na velké množství dat, tak nám ho stačí ztransformovat jen jednou na začátku a pak stále používat.
Co dělat, když data zpracováváme proudově, případně jsou tak dlouhá, že nemůžeme udělat jednu velkou FFT? K tomu se používá metoda overlap add, která umožňuje zpracovávat pomocí malých transformací postupně. Bohužel jsem ji nikdy osobně neimplementoval, tak se neodvažuji ji předvádět.
Nyní nejspíš máme signál, který je ofiltrovaný, a v části jeho spektra tak nic není. Decimací, tedy vyházením některých vzorků, tak můžeme snížit jeho vzorkovací rychlost.
Teď to asi bude trochu mlhavé, protože úplně nevím jak to vysvětlit textem (na tabuli to jde líp), ale pokusím se. Pointa je, že spektrum signálu je jakoby periodické, nekonečně se opakující, a těmto věcem mimo „hlavní“ interval od -fs/2 do fs/2 se někdy říká spektrální repliky. Je to vidět na této ilustraci z již odkazovaného článku:
Nastavením vzorkovací frekvence jsme umístili značky na B/2, 3B/2, 5B/2 atd., kolem kterých se to neustále opakuje. Pokud vzorkovací frekvenci zvýšíme - tím, že mezi původní vzorky vložíme nuly - tak se nám jakoby zviditelní větší část tohoto myšleného spektra.
Pokud naopak vzorkovací frekvenci snížíme - například výše jsem ponechal jenom 1 z každých 10 vzorků (decim = chirp[::10]
) - tak se nám značky B/2, 3B/2 atd. přeskupí a spektrální repliky se přeskupí podle nich - a vše, co tam bylo původně, se zamíchá do sebe. V případě výše je to v pořádku, protože signál byl pásmově omezený (bandlimited) a nově vzniklý signál tak neobsahuje žádný nežádoucí zamíchaný bordel, ale třeba na obrázku níže je příklad, který téměř určitě nechcete, případně je to hezky ilustrované tady.
Vše uvedené by se hodilo nějak shrnout, uvést typický příklad, k čemu to použít. Typicky tedy řešíte, že máte nějaký signál, a teď z něj chcete vybrat kanál, zbytek zahodit a zdecimovat to na rozumnou vzorkovací frekvenci. Jaké tedy máte možnosti?
Tiskni
Sdílej:
eště to +- de :D
spíš víc by se hodilo mit ty skriptíky jakoby nějak rychle ke stažení git/zip/něco by se to nemuselo kopírovat :D ;D
ale nechce se mi pálit čas kvůli neschopnosti státuTak hlavně při té jízdě na červenou dejte pozor, abyste nevyhrál bouchalův pohár. Potichu jedoucí elektroauto je svině.
Problém s opakovaným násobením nějakou maticí, přičemž výsledek má být vždycky jednotkový vektor (a mění se jenom jeho směr), je, že se vlivem nepřesností může tento vektor časem prodlužovat nebo zkracovat - spočítaná matice rotace může mít vlivem nepřesností třeba normu 0.99999 nebo 1.00001, takže vektor s každou iterací nepatrně natáhne nebo smrskne, a po výše zmíněném bilionu vzorků to naprosto ustřelí. Proto je potřeba vektor rotátoru občas normalizovat, tj. spočítat jeho skutečnou velikost a tou ho vydělit, čímž se z něj zase stane vektor jednotkový.
Nemáme ale úplně stejný problém i s fází? Tak jako se mi norma nepřenásobí úplně přesně jedničkou, nebude ani úhel otočení úplně přesně takový, jaký jsem chtěl, a při opakovaném násobení se ty chyby budou taky kumulovat. Nebo to u fáze nevadí?
protože má pravdu :D :D ;D ;D
A že vlastně nevím, jaká citlivost na vstupu.Může tam být šmejdské rtl-sdr nebo frontend magnetické rezonance za několik mega.