Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Wizualizacja EKG i Detekcja Arytmii — baza MIT-BIH

Źródło danych — PhysioNet MIT-BIH Arrhythmia Database

Baza MIT-BIH Arrhythmia Database jest publicznie dostępnym zbiorem nagrań elektrokardiograficznych opublikowanym przez PhysioNet pod adresem:

https://physionet.org/content/mitdb/1.0.0/

Zawiera 48 półgodzinnych nagrań dwukanałowych zebranych od 47 pacjentów w Beth Israel Hospital w Bostonie w latach 1975–1979. Nagrania zostały manualnie zaadnotowane przez co najmniej dwóch niezależnych kardiologów i są szeroko stosowane w badaniach nad automatyczną detekcją arytmii.

Rekord 205

Przykład korzysta z rekordu 205 — nagrania 59-letniego mężczyzny leczonego Digoksyną i Quinaglutem. Rekord zawiera przypadki częstoskurczu komorowego (VT) i jest często cytowany w literaturze jako trudny diagnostycznie ze względu na dwie morfologicznie odmienne formy dodatkowych pobudzeń komorowych (PVC).

Parametry nagrania:

ParametrWartość
Czas trwania≈ 30 min
Częstotliwość próbkowania360 Hz
Liczba próbek650 000
Kanał 1 (MLII)Zmodyfikowane odprowadzenie kończynowe II
Kanał 2 (V1)Odprowadzenie przedsercowe V1
Rozdzielczość12 bitów, wzmocnienie 200 LSB/mV, punkt zerowy 1024

Wartości surowe przechowywane są jako liczby całkowite bez jednostek (tzw. wartości ADC). Przeliczenie na miliwolty:

\[\text{mV} = \frac{\text{ADC} - 1024}{200}\]

Zakres rzeczywistych wartości w pliku rec205 mieści się w przedziale 589–1315 (MLII) i 718–1106 (V1), co odpowiada amplitudzie sygnału w granicach ok. ±1,5 mV.

Przygotowanie danych

Oryginalne pliki nagrania (205.hea, 205.dat, 205.atr) są dostarczane w formacie MIT-BIH i wymagają konwersji do formatu binarnego rozpoznawanego przez RetractorDB.

Format MIT-BIH 212

Sygnał w pliku 205.dat jest spakowany dwanaście-bitowo w formacie 212: każde trzy bajty przechowują dwie kolejne próbki obu kanałów według schematu:

[B0][B1][B2] → MLII = B0  | ((B1 & 0x0F) << 8)
               V1   = B2  | ((B1 >>  4)  << 8)

Wartości są 12-bitowe ze znakiem (zakres –2048..2047).

Konwersja do formatu RetractorDB

Skrypt examples/ecg/mitbih2rdb.py czyta nagłówek 205.hea, dekoduje pary próbek i zapisuje je jako rekordy int32 little-endian do pliku rec205:

650 000 rekordów × 2 pola × 4 bajty = 5 200 000 bajtów

Jednocześnie skrypt generuje skrypt RQL odtwarzający sygnał (rec205-replay.rql). Plik deskryptora rec205.desc jest tworzony przez build.sh.

Całość przygotowania uruchamia się jednym poleceniem z katalogu głównego projektu:

bash examples/ecg/build.sh

Wynikiem są trzy pliki w katalogu examples/ecg/rec205/:

PlikGenerujeOpis
rec205mitbih2rdb.pyDane binarne (int32 LE)
rec205.descbuild.shDeskryptor strumienia
rec205-replay.rqlmitbih2rdb.pySkrypt RQL odtwarzania

Zapytanie RQL

Plik rec205-replay.rql definiuje dwa strumienie:

DECLARE MLII INTEGER, V1 INTEGER STREAM ecg, 1/360 FILE 'rec205'

SELECT ecg.MLII, ecg.V1 STREAM s205out FROM ecg VOLATILE

Klauzula STREAM ecg, 1/360 określa interwał czasowy jednej próbki jako 1/360 s, co odpowiada rzeczywistej częstotliwości próbkowania 360 Hz. Klauzula TYPE DEVICE w deskryptorze powoduje, że plik rec205 jest czytany sekwencyjnie w pętli (po ostatniej próbce odczyt wraca do początku), co umożliwia ciągłe odtwarzanie nagrania.

Strumień wyjściowy s205out jest zadeklarowany jako VOLATILE, dlatego nie jest zapisywany na dysk — dane trafiają wyłącznie do procesu konsumenta (xqry).

Wizualizacja na ekranie

Do wyświetlenia wykresu w czasie rzeczywistym służy cel ecg w systemie budowania. Uruchamia on skrypt scripts/xplot.sh, który startuje xretractor w tle, a następnie przepuszcza strumień danych przez xqry do gnuplot.

# z katalogu build/Debug
ninja ecg

Wywołanie rozwijane przez CMake:

scripts/xplot.sh s205out rec205-replay.rql 720,560,1360 --gnuplot-rtl

Znaczenie parametrów:

ParametrZnaczenie
s205outNazwa strumienia wynikowego
rec205-replay.rqlPlik zapytań
720Szerokość okna danych (próbki widoczne jednocześnie)
560,1360Zakres osi Y (wartości ADC pasujące do rzeczywistego sygnału)
--gnuplot-rtlNajnowsze próbki po prawej stronie, wykres przesuwa się od prawej do lewej

Opcja --gnuplot-rtl jest parametrem xqry powodującym odwrócenie osi X gnuplota (set xrange [720:0]). Efekt jest taki, że najświeższe próbki pojawiają się po prawej stronie okna, a starsze przesuwają się w lewo — analogicznie do klasycznego wydruku EKG na taśmie papierowej.

Widok okna gnuplot z przebiegiem EKG odtwarzanym w RetractorDB

Rys. 48 Widok okna gnuplot z odtwarzanym sygnałem EKG (rekord 205)

Okno prezentuje 720 próbek, czyli dokładnie 2 sekundy sygnału przy 360 Hz, co odpowiada typowej szerokości jednego paska EKG używanej w diagnostyce.

Detekcja QRS i identyfikacja arytmii

Kontekst — algorytm Pan-Tompkins

Detekcja zespołów QRS jest fundamentem automatycznej analizy EKG. Zespół QRS reprezentuje depolaryzację komór serca i odpowiada każdemu uderzeniu serca widocznemu jako ostry pik w sygnale. Znając położenia QRS w czasie, można wyliczyć interwały RR, a na ich podstawie rozpoznać podstawowe zaburzenia rytmu:

Miara pochodna od QRSZastosowanie
Interwały RRCzęstotliwość akcji serca (HR), VT, bradykardia
Zmienność RR (HRV)Autonomiczny układ nerwowy, przewidywanie zdarzeń
Morfologia QRSRozróżnienie PVC od normalnego rytmu, APC
Czas trwania QRSBlok odnogi pęczka Hisa (BBB)

Algorytm Pan-Tompkins (1985) jest klasycznym, pięcioetapowym potokowym algorytmem cyfrowego przetwarzania sygnałów realizowanym za pomocą filtrów FIR. RetractorDB implementuje go bezpośrednio jako strumień zapytań RQL, bez specjalistycznych bibliotek DSP.

Generowanie filtrów sygnałowych (coef)

Algorytm wymaga dwóch zestawów współczynników FIR, przechowywanych jako pliki tekstowe (bp_coef.txt, d_coef.txt). Generowane są jednorazowo skryptami Pythona przed uruchomieniem detekcji.

Filtr pasmowoprzepustowy — gen_bp_coef.py

Krok 1 algorytmu wymaga filtru wycinającego szumy i artefakty poza pasmem QRS. Pasm przepustowe 5–15 Hz przy fs = 360 Hz daje odpowiedź zawierającą morfologię QRS przy jednoczesnym tłumieniu linii bazowej (< 5 Hz) i szumów mięśniowych (> 15 Hz).

Metoda projektowania to okienkowy sinc (windowed sinc):

h_bp[n] = (h_lp2[n] − h_lp1[n]) · w[n]

gdzie:

  • h_lp[n] = 2·fc·sinc(2·fc·(n−M)) — idealny filtr dolnoprzepustowy
  • w[n] = 0,54 − 0,46·cos(2πn/(N−1)) — okno Hamminga tłumiące efekty Gibbsa
  • M = (N−1)/2 = 12 — punkt centralny filtru (opóźnienie grupowe = 12 próbek)

Parametry:

ParametrWartość
Długość filtru N25 współczynników
Dolna f. graniczna fc₁5 Hz (znorm. 5/360)
Górna f. graniczna fc₂15 Hz (znorm. 15/360)
Skala całkowitoliczbowa×1000 (dzielona /1000 w RQL)

Uruchomienie skryptu:

cd examples/ecg/rec205
python3 gen_bp_coef.py
# Zapisano 25 współczynników do bp_coef.txt
# Współczynniki: [-2, -2, -1, 0, 3, 8, 14, 23, 32, 41, 49, 54, 56, ...]
# Suma (wzmocnienie DC): 5 / 1000 = 0.0050

Współczynniki są symetryczne względem centrum (n=12), co potwierdza fazę liniową filtru — niezbędną właściwość przy analizie EKG, gdyż gwarantuje brak zniekształceń fazowych morfologii QRS.

Filtr różniczkujący — gen_d_coef.py

Krok 2 algorytmu stosuje filtr podkreślający strome zbocza QRS. Pan i Tompkins zaproponowali 5-punktowy estymator pochodnej:

y[n] = (1/8T) · (−x[n−4] − 2·x[n−3] + 2·x[n−1] + x[n])

Współczynniki (od najstarszej do najnowszej próbki):

h = [−1, −2, 0, 2, 1]

Właściwości filtru:

WłaściwośćWartość
Suma współczynników0 (zerowe wzmocnienie DC — eliminuje offsety)
Maksymalna odpowiedźf ≈ 10–25 Hz (zakres zbocza QRS)
Czynnik skali (1/8T)360/8 = 45 Hz (pomijany — nie wpływa na detekcję)
cd examples/ecg/rec205
python3 gen_d_coef.py
# Zapisano 5 współczynników do d_coef.txt
# Współczynniki: [-1, -2, 0, 2, 1]
# Suma (wzmocnienie DC): 0  (powinno być 0)

Implementacja potoku w RQL — rec205-detect.rql

Plik rec205-detect.rql implementuje kompletny pięcioetapowy potok dla dwóch kanałów EKG (MLII i V1):

DECLARE MLII INTEGER, V1 INTEGER STREAM ecg, 1/360 FILE 'rec205'
DECLARE bp_coef INTEGER[25] STREAM bpf, 1 FILE 'bp_coef.txt'
DECLARE d_coef INTEGER[5]   STREAM df,  1 FILE 'd_coef.txt'

# Wyodrębnienie kanałów
SELECT ecg.MLII STREAM mlii FROM ecg VOLATILE
SELECT ecg.V1   STREAM v1   FROM ecg VOLATILE

# 1. Filtr pasmowoprzepustowy (5-15 Hz) — splot FIR 25-tap
SELECT *                        STREAM mlii_win FROM mlii@(1,25)  VOLATILE
SELECT mlii_win[_]*bpf[_]       STREAM bp_acc   FROM mlii_win+bpf VOLATILE
SELECT bp_acc[0]/1000           STREAM bp_out   FROM bp_acc.sumc  VOLATILE

# 2. Różniczkowanie — splot FIR 5-tap
SELECT *                        STREAM bp_win   FROM bp_out@(1,5) VOLATILE
SELECT bp_win[_]*df[_]          STREAM d_acc    FROM bp_win+df    VOLATILE
SELECT d_acc[0]                 STREAM d_out    FROM d_acc.sumc   VOLATILE

# 3. Kwadrat (/1000 zapobiega przepełnieniu int32)
SELECT d_out[0]*d_out[0]/1000   STREAM sq_out   FROM d_out        VOLATILE

# 4. Całkowanie ruchome 30 próbek (~83 ms)
SELECT *                        STREAM mwi_win  FROM sq_out@(1,30) VOLATILE
SELECT mwi_win[0]               STREAM mwi      FROM mwi_win.avg   VOLATILE

# 5. Próg adaptacyjny — 2× średnia ruchoma 180 próbek (0,5 s)
SELECT *                        STREAM mwi_long FROM mwi@(1,180)  VOLATILE
SELECT mwi_long[0]              STREAM mwi_thr  FROM mwi_long.avg VOLATILE

# Wyjście: MLII wycentrowane, V1 wycentrowane, sygnał detekcji ×5
SELECT mlii[0]-900, v1[0]-900, (mwi[0]-mwi_thr[0]*2)*5
STREAM detect_out FROM mlii+v1+mwi+mwi_thr VOLATILE

Uzasadnienie parametrów

Operator @(1,25) tworzy ruchome okno 25 próbek, natomiast [_] i sumc realizują splot dyskretny — patrz rozdział Przetwarzanie symbolu _.

Dzielenie /1000 w kroku 3 kompensuje skalę całkowitoliczbową współczynników — bez tego iloczyn d_out × d_out osiągnąłby wartości przekraczające zakres int32 (2 147 483 647) dla typowych amplitud EKG.

Wyrażenie wyjściowe (mwi[0]-mwi_thr[0]*2)*5 implementuje próg adaptacyjny: wartość jest dodatnia tylko wówczas, gdy obwiednia MWI przekracza dwukrotność bieżącej średniej ruchomej — co wskazuje na wykryty QRS. Mnożnik ×5 skaluje sygnał detekcji do zakresu wizualnie porównywalnego z surowym EKG na wykresie.

Uruchomienie — ninja ecg-detect-qrs

Proces uruchamia się jedną komendą z katalogu build/Debug:

cd build/Debug
ninja ecg-detect-qrs

CMake rozwinął ten cel do polecenia:

scripts/xplot.sh detect_out rec205-detect.rql 720,-400,400 --gnuplot-rtl

Znaczenie parametrów:

ParametrZnaczenie
detect_outNazwa strumienia wynikowego (3 pola)
rec205-detect.rqlPlik zapytań z powyższym potokiem
720Szerokość okna: 720 próbek = 2 sekundy przy 360 Hz
−400,400Zakres osi Y w jednostkach ADC (≈ ±2 mV)
--gnuplot-rtlNajnowsze próbki po prawej stronie (prawo-lewo)

Skrypt xplot.sh uruchamia xretractor w tle (kompiluje i wykonuje zapytania), a następnie przez xqry przekazuje strumień detect_out do gnuplot w trybie ciągłym. Okno gnuplot odświeża się przy każdej nowej paczce próbek.

Opis rysunku — okno gnuplot

Okno gnuplot detekcji QRS: MLII, V1 i sygnał detekcji na rekordzie 205

Rys. 49 Okno gnuplot uruchomionego celem ninja ecg-detect-qrs — rekord 205 MIT-BIH, 720 próbek (2 s), RTL

Na rysunku widoczne są trzy sygnały odpowiadające trzem polom strumienia detect_out:

[detect-out-0] linia czerwona — MLII wycentrowane (mlii − 900)

Surowy sygnał EKG z odprowadzenia MLII przesunięty o punkt bazowy 900 ADC tak, że oś zerowa odpowiada izolinii. Dwa ostre piki (amplituda ≈ 280 ADC ≈ 1,4 mV) w okolicach próbek 520 i 350 od prawej krawędzi reprezentują dwa kolejne zespoły QRS. Wyraźna morfologia QRS z dominującym pikiem R potwierdza prawidłowe działanie filtru pasmowoprzepustowego — szumy zostały stłumione, a pik zachował amplitudę.

[detect-out-1] linia niebieska — V1 wycentrowane (v1 − 900)

Sygnał z odprowadzenia V1 tego samego nagrania. Morfologia QRS w V1 jest z reguły mniej wyrażona niż w MLII, co widać na rysunku — sygnał niebieski wykazuje mniejszą amplitudę piku R przy podobnych pozycjach czasowych QRS. Jednoczesna obecność obu kanałów pozwala różnicować pobudzenia nadkomorowe (APC) od komorowych (PVC), ponieważ QRS komorowe wykazują odmienną morfologię w V1.

[detect-out-2] linia zielona — sygnał detekcji QRS ((mwi − 2·mwi_thr) × 5)

Sygnał wyniku algorytmu. Wartość dodatnia oznacza wykryty zespół QRS — obwiednia całkowania ruchomego przekroczyła dwukrotność progu adaptacyjnego. Na rysunku widoczne są dwa wyraźne dodatnie impulsy pokrywające się w czasie z pikami QRS na kanale MLII. Między uderzeniami linia pozostaje blisko zera lub nieznacznie poniżej — potwierdzając specyficzność detekcji.

Odstęp między dwoma widocznymi QRS wynosi w przybliżeniu 170 próbek, co przy 360 Hz daje:

RR ≈ 170 / 360 ≈ 0,47 s  →  HR ≈ 127 bpm

Wartość ta mieści się w zakresie odnotowanego w rekordzie 205 częstoskurczu komorowego (VT, 79–216 bpm), co sugeruje że wizualizowany fragment nagrania pochodzi z jednego z 6 epizodów VT odnotowanych przez kardiologów MIT-BIH.

Schemat przepływu procesu

Poniższy diagram pokazuje kompletny przepływ danych od surowego nagrania MIT-BIH do identyfikacji arytmii, ze wskazaniem miejsca, w którym RetractorDB realizuje algorytm Pan-Tompkins, oraz powiązania z klasycznymi metodami rozpoznawania arytmii:

Schemat przepływu danych w procesie detekcji QRS i identyfikacji arytmii

Rys. 50 Przepływ danych — od nagrania MIT-BIH przez potok Pan-Tompkins w RQL do wizualizacji i identyfikacji arytmii

Prawa gałąź diagramu — Identyfikacja arytmii — reprezentuje klasyczne metody analizy po detekcji QRS, które można zbudować jako kolejne zapytania RQL nadbudowane na strumieniu detect_out:

MetodaOpisPowiązanie z QRS
Interwały RRCzas między kolejnymi QRS → HRbezpośrednio z pozycji detekcji
HRV (zmienność)Odchylenie standardowe RRstatystyki strumienia RR
Klasyfikacja PVCSzerokość QRS > 120 ms, morfologia V1szerokość okna mwi
Detekcja VTSekwencja ≥ 3 PVC z HR > 100 bpmRULE na strumieniu HR+PVC
Detekcja APCWczesny, wąski QRS poprzedzający pauzęmorfologia MLII vs V1

RetractorDB udostępnia operatory RULE oraz agregaty okienkowe (.avg, .sumc), które umożliwiają implementację powyższych metod w tym samym języku zapytań RQL, bez wychodzenia poza środowisko systemu. Detekcja QRS jest pierwszym i niezbędnym etapem tej hierarchii.