Pracownia Sygnałów Biologicznych/Zajecia 7
Pomiar Elektrookulogramu
Spis treści
Wstęp
Kolejnym narządem, w którym znajdują się generatory czynności bioelektrycznej jest oko. W narządzie tym zachodzą skomplikowane procesy biochemiczne i elektryczne, umożliwiające widzenie. Procesy te zostaną omówione na oddzielnym wykładzie, w tym miejscu zaś wymienimy tylko te zjawiska, które mają wpływ na powstawanie w oku czynności elektrycznej.
- Jednym z najważniejszych części oka jest siatkówka, której zadaniem jest odbieranie bodźców świetlnych oraz zamiana ich na sygnały elektryczne, przekazywane dalej do kory wzrokowej. W siatkówce płynie nieustannie prąd, którego natężenie zmienia się wraz z intensywnością padającego na nią światła. Związane z tym sygnały bioelektryczne można rejestrować za pomocą elektrod umieszczonych na powierzchni oka lub nawet za pomocą elektrod umieszczonych na powierzchni skóry wokół oka. Tak zarejestrowany sygnał elektryczny, powstały w oku w trakcie widzenia, nazywamy Elektroretinogramem (ERG). Sygnał ten ma amplitudę od kilku nanowoltów do kilku μV i jest wykorzystywany w diagnostyce wielu chorób siatkówki.
- Rogówka (zewnętrzna warstwa oka znajdująca się w jego przedniej części) jest naładowana dodatnio względem siatkówki umiejscowionej po przeciwnej stronie oka. Rogówka wraz z siatkówką tworzą zatem w przybliżeniu układ dipola elektrycznego. W momencie ruchu okiem, dipol ten zmienia orientację w przestrzeni, zaburzając rozkład natężenia pola elektrycznego. Związany z tym sygnał o amplitudzie kilku miliwoltów można zmierzyć za pomocą elektrod umieszczonych na skórze wokół oka. Widoczny jest ona także na elektrodach umieszczonych na powierzchni głowy w trakcie pomiary czynności elektrycznej mózgu. Sygnał ten, czyli elektryczny zapis ruchu gałek ocznych, nazywamy Elektrookulogramem (EOG). Czynność elektryczną związaną z ruchem gałek ocznych obserwuje się również w trakcie mrugania, kiedy to gałki oczne skręcają nieco ku górze (tzw. zjawiska Bella), a także w trakcie badań diagnostycznych zaburzeń snu (w różnych etapach snu występują wolne lub szybkie ruchy gałek ocznych).
Sygnał EOG można też wykorzystać do konstrukcji interfejsów [[1]].
- Ruch oka sterowany jest za pomocą mięśni. W trakcie rejestracji Elektrookulogramu widoczne będą również wyładowania elektryczne związane z działaniem mięśni.
Ćwiczenie I
Wykonaj pomiar Elektrokulogramu za pomocą dwóch par elektrod połączonych w montażu dwubiegunowym.
- Jedną parę elektrod umieść poniżej i powyżej oka, drugą w pobliżu lewej i prawej skroni.
- Skonfiguruj program do rejestracji i przeglądania mierzonego sygnału w czasie rzeczywistym.
- Wykonaj ruch oczami w górę i opisz zarejestrowany sygnał.
- Wykonaj ruch oczami w dół i opisz zarejestrowany sygnał.
- Wykonaj mrugnięcie i opisz zarejestrowany sygnał.
- Przeczytaj fragment tekstu, zaobserwuj efekty związane z ruchem sakadowym oka.
Zarejestruj sygnały w wyżej wymienionych sytuacjach. Wyświetl te sygnały w programie napisanym samodzielnie, bez stosowania filtra grórnoprzepustowego.
Ćwiczenie II
Powtórz ćwiczenie I, łącząc elektrody z monopolarnymi wejściami wzmacniacza, elektrodę odniesienia umieść na lewym płatku uszu lub wyrostku sutkowatym.
Ćwiczenie III
Napisz program wyświetlający na macierzy 5x5 następujące sekwencje:
- kwadrat środkowy - kwadrat górny - kwadrat środkowy
- kwadrat środkowy - kwadrat prawy - kwadrat środkowy
- kwadrat środkowy - kwadrat dolny - kwadrat środkowy
- kwadrat środkowy - kwadrat lewy - kwadrat środkowy
Zapisuj do pliku tekstowego czasy i kody poszczególnych sekwencji.
Wyświetl te sygnały w programie napisanym samodzielnie, bez stosowania filtra grórnoprzepustowego.
Zaprojektuj detektor, wykrywający, która sekwencja została wykonana.
Ćwiczenie IV
Zrealizuj prosty system do rejestracji ruchu gałki ocznej, tzw. Eye tracker. w tym celu:
Napisz moduł:
- Korzystając detektora sekwencji napisanego w ramach ćwiczenia III napisz moduł wykrywający sekwencje w czasie rzeczywistym. Niech Twój moduł wypisuje wynik detekcji na konsolę. Opis korzystania i pisania modułów do systemu OBCI znajduje się [tutaj]. W dużym skrócie to co należy zrobić to:
- W katalogu domowym utwórz podkatalog:
~/obci/scenarios
- w katalogu tym umieść plik:
eog.ini
powinien on zawierać następującą treść:
[peers]
scenario_dir=
;***********************************************
[peers.mx]
path=multiplexer-install/bin/mxcontrol
;***********************************************
[peers.config_server]
path=control/peer/config_server.py
;***********************************************
;***********************************************
[peers.amplifier]
path = drivers/eeg/cpp_amplifiers/amplifier_tmsi.py
;ponizsza sciezka pokazuje na plik zaierajacy nasze ustawienia parametrow wzmacniacza
config=~/obci/scenarios/eog_local_params.ini
[peers.analysis]
path=~/obci/analysis/eog_realtime.py
config=~/obci/analysis/eog_realtime.ini
- W powyższym pliku zadeklarowaliśmy, że lokalne parametry dla wzmacniacza znajduję się w pliku:
~/obci/scenarios/eog_local_params.ini
zatem musimy ten plik wytworzyć i wypełnić go np. taką treścią:
[local_params]
channel_names=gora;dol;lewa;prawa
active_channels=0;1;2;3
sampling_rate=256
- W pliku ze scenariuszem zadeklarowaliśmy też, że nasz moduł analizy danych znajduje się w pliku:
~/obci/analysis/eog_realtime.py
tak więc musimy ten plik stworzyć i tam właśnie wpisać algorytm detekcji. Dla zachowania konwencji w tym samym katalogu powinien znajdować się plik na ewentualne parametry dla modułu eog_realtime.py. Musi on się nazywać tak samo, tyle, że ma rozszerzenie .ini. Musimy więc wytworzyć pusty plik:
~/obci/analysis/eog_realtime.ini
Ponieważ nasz algorytm musi on się komunikować z resztą systemu obci trzeba go opakować w poniższy kod-szkielet:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import random, time, numpy
from multiplexer.multiplexer_constants import peers, types
from obci.control.peer.configured_multiplexer_server import ConfiguredMultiplexerServer
from obci.configs import settings, variables_pb2
from collections import deque
import obci.utils.openbci_logging as logger
#LOGGER = logger.get_logger("sample_analysis", "info")
LOGGER = logger.get_logger("sample_analysis", "debug")
class SampleAnalysis(ConfiguredMultiplexerServer):
"""A class responsible for handling signal message and making proper decision.
The class inherits from generic class for convinience - all technical stuff
is being done in this super-class"""
def __init__(self, addresses):
"""Initialization - super() and ready() calls are required..."""
super(SampleAnalysis, self).__init__(addresses=addresses,
type=peers.ANALYSIS)
self.ready()
LOGGER.info("Sample analysis init finished!")
def handle_message(self, mxmsg):
"""The only required function in the class
that will be fired every time message is received"""
if mxmsg.type == types.AMPLIFIER_SIGNAL_MESSAGE:
# Got proper message, let`s unpack it ...
# Messages are transmitted in bunches so lets define SampleVector
# in order to unpack bunch of Sample messages ...
l_vect = variables_pb2.SampleVector()
l_vect.ParseFromString(mxmsg.message)
# Now we have message unpacked, lets iterate over every sample ...
for s in l_vect.samples:
# Every sample has two fields:
# timestamp - system clock time of a moment of Sample`s creation
# channels - a list of values - one for every channel
LOGGER.debug("Got sample with timestamp: "+str(s.timestamp))
# One can copy samples to numpy array ...
a = numpy.array(s.channels) # w tym miejscu mamy w tablicy a "paczke" próbek (domyślnie 4próbki ) ze wszystkich zadeklarowanych kanalow
#################### TU TRZEBA WPISAC SWOJ KOD BUFOROWANIA i ANALIZY ##############
print a #na dobry poczatek wypiszmy probki
####################################################################
# Or just iterate over values ...
for ch in s.channels:
LOGGER.debug(ch)
# Having a new bunch of values one can fire some magic analysis and
# generate decision ....
# Below we have quite simple decision-maker - it generates a random
# decision every ~100 samples-bunch
########## TU NA PODSTAWIE ANALLIZY PODEJMUJEMY DECYZJE I MOZEMY JA PRZEKAZAC DO RESZTY SYSTEMU OBCI ######################
########## W TYM PRZYKLADZIE JEST TO LOSOWA DECYZJA ##############
########## W TYM CWICZENIU WYSTARCZY JESLI WYPISZECIE DECYZJE NA EKRAN ###########
if random.random() > 0.99:
# Here we send DECISION message somewhere-to-the-system ...
# It's up to scenario's configuration how the decision will be used ...
# Eg. it might be used by LOGIC module to push some button in speller.
self.conn.send_message(message = str(random.randint(0,7)),
type = types.DECISION_MESSAGE,
flush=True)
else:
LOGGER.warning("Got unrecognised message type: "+str(mxmsg.type))
# Tell the system 'I`ll not respond to this message, I`m just receiving'
self.no_response()
if __name__ == "__main__":
# Initialize and run an object in order to have your analysis up and running
SampleAnalysis(settings.MULTIPLEXER_ADDRESSES).loop()
- Aby uruchomić nasz scenariusz trzeba wywołać:
obci launch ~/obci/scenarios/eog.ini
- Zatrzymanie scenariusza robimy tak:
obci kill eog
W tym momencie komunikaty ze wszystkich modułów wypisywane są na jednej konsoli. Przydatne jest korzystanie z obiektu 'LOGGER' zamiast funkcji 'print' - w konsoli mamy informację o źródle komunikatu i jego czasie.
OpenBCI działa tak, że jeśli w jednym module pojawi się błąd, to wszystkie inne moduły są zamykane, stąd komunikat podobny do poniższego sugeruje, że w którymś module wystąpił błąd. W takim wypadku należy przejrzeć konsolę i wyszukać komunikat błędu. Niezbędne jest ustawienie bufora konsoli na 'nieograniczony' wykonując: Edycja->Preferencje profilu->Przewijanie->Nieograniczone .
Przetestuj moduł
- Umieść elektrody do rejestracji ruchu gałki ocznej jak w ćwiczeniu I i przetestuj działanie modułu. Miłej zabawy :-)