Commit 286c0bc5 by Laura Rodríguez

Initial commit

parents
mport sys
import random
import time
from matplotlib.pyplot import margins
import pygame
from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QLabel, QMessageBox
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QFont
from pygaze import eyetracker, libinput, libscreen, liblog
from pygaze import libtime
import tobii_research as tr
import constants # Debe contener: DISPSIZE = (1920, 1080)
import pandas as pd # Importamos pandas para guardar los resultados en un archivo Excel
import subprocess # Para abrir el archivo con el programa predeterminado
import os # Para comprobar la existencia del archivo
# ============ FUNCIÓN DE CALIBRACIÓN ============
def calibrate_tobii(self):
try:
print("Buscando dispositivo...")
found_eyes = tr.find_all_eyetrackers()
if not found_eyes:
raise Exception("No se encontró ningún dispositivo Tobii Pro.")
eye_tracker = found_eyes[0]
print(f"Dispositivo encontrado: {eye_tracker}")
self.disp = libscreen.Display(bgcolor=(0, 0, 0)) # Fondo negro
self.tracker = eyetracker.EyeTracker(self.disp)
instruction_screen = libscreen.Screen()
instruction_screen.clear(colour=(0, 0, 0))
instruction_screen.draw_text("Presiona espacio para calibrar", fontsize=24, colour=(255, 255, 255))
self.disp.fill(instruction_screen)
self.disp.show()
print("Iniciando calibración...")
self.tracker.calibrate()
print("Calibración completada.")
# self.tracker.close()
# disp.close()
return True
except Exception as e:
print(f"Error durante calibración: {e}")
return False
# ============ TEST GO/NOGO ======================
def run_gonogo_test(self):
pygame.init()
# self.disp = libscreen.Display(bgcolor=(0, 0, 0))
# self.tracker = eyetracker.EyeTracker(self.disp)
log = liblog.Logfile()
log.write(["trialnr", "trialtype", "targetside", "gaze_endpos", "latency", "correct"])
SCREEN_WIDTH, SCREEN_HEIGHT = constants.DISPSIZE
CENTER = (SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2)
TARGET_OFFSET = 382
left_pos = (CENTER[0] - TARGET_OFFSET, CENTER[1])
right_pos = (CENTER[0] + TARGET_OFFSET, CENTER[1])
blackscreen = libscreen.Screen()
blackscreen.clear(colour=(0, 0, 0))
inscreen = libscreen.Screen()
inscreen.clear(colour=(0, 0, 0))
ins_text = (
"Mira la cruz central y presiona 'espacio'.\n\n"
"El color indicará la acción:\n"
"- Verde: mira al círculo blanco (Pro-sácadico)\n"
"- Rojo: mira al estímulo blanco y negro (Anti-sácadico)\n"
"- Naranja: NO mires a ningún estímulo (NoGo)\n\n"
"Presiona 'espacio' para comenzar.\n"
"Pulsa 'E' en cualquier momento para salir."
)
inscreen.draw_text(text=ins_text, fontsize=30, pos=CENTER, colour=(255, 255, 255), center=True)
fixscreen = libscreen.Screen()
fixscreen.clear(colour=(0, 0, 0))
fixscreen.draw_fixation(fixtype='cross', diameter=57, pos=CENTER, colour=(255, 255, 255))
signal_screens = {
'PS': libscreen.Screen(),
'AS': libscreen.Screen(),
'NoGo': libscreen.Screen()
}
for key, color in [('PS', (0, 255, 0)), ('AS', (255, 0, 0)), ('NoGo', (255, 165, 0))]:
signal_screens[key].clear(colour=(0, 0, 0))
signal_screens[key].draw_circle(pos=CENTER, r=38, fill=True, colour=color)
feedbackscreens = {
1: libscreen.Screen(),
0: libscreen.Screen()
}
feedbackscreens[1].clear(colour=(0, 0, 0))
feedbackscreens[1].draw_text(text='Correcto', fontsize=30, pos=CENTER, colour=(0, 255, 0), center=True)
feedbackscreens[0].clear(colour=(0, 0, 0))
feedbackscreens[0].draw_text(text='Incorrecto', fontsize=30, pos=CENTER, colour=(255, 0, 0), center=True)
self.disp.fill(inscreen)
self.disp.show()
waiting = True
while waiting:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
waiting = False
elif event.key == pygame.K_e:
pygame.quit()
return
max_blocks = 30
results = []
try:
for block in range(max_blocks):
trials = ['PS', 'AS', 'NoGo']
# trials = ['PS', 'AS']
random.shuffle(trials)
for trialnr, trialtype in enumerate(trials, start=1):
for event in pygame.event.get():
if event.type == pygame.KEYDOWN and event.key == pygame.K_e:
raise KeyboardInterrupt
# Mostrar cruz de fijación
self.disp.fill(fixscreen)
self.disp.show()
libtime.pause(random.randint(750, 1250))
# Esperar que el usuario presione espacio
waiting = True
while waiting:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
waiting = False
elif event.key == pygame.K_e:
raise KeyboardInterrupt
time.sleep(0.2)
self.tracker.start_recording()
# Mostrar señal central de color
self.disp.fill(signal_screens[trialtype])
self.disp.show()
time.sleep(0.8)
self.disp.fill(blackscreen)
self.disp.show()
time.sleep(0.2)
targetside = random.choice(['left', 'right'])
target_pos = left_pos if targetside == 'left' else right_pos
nontarget_pos = right_pos if targetside == 'left' else left_pos
dualstim_screen = libscreen.Screen()
dualstim_screen.clear(colour=(0, 0, 0))
dualstim_screen.draw_circle(pos=nontarget_pos, r=28, fill=True, colour=(255, 255, 255))
dualstim_screen.draw_circle(pos=target_pos, r=38, fill=True, colour=(255, 255, 255))
dualstim_screen.draw_circle(pos=target_pos, r=32, fill=True, colour=(0, 0, 0))
dualstim_screen.draw_circle(pos=target_pos, r=28, fill=True, colour=(255, 255, 255))
self.disp.fill(dualstim_screen)
self.disp.show()
t0 = pygame.time.get_ticks()
self.tracker.log(f"target {targetside}")
# try:
t1, startpos = self.tracker.wait_for_saccade_start()
endtime, startpos, endpos = self.tracker.wait_for_saccade_end()
# except Exception:
# print("La estoy liando")
# t1 = pygame.time.get_ticks()
# endtime = pygame.time.get_ticks()
# endpos = constants.DISPSIZE[0]/2 # o (SCREEN_WIDTH//2, SCREEN_HEIGHT//2), posición central para 'NoGo'
latency = (t1 - t0) / 1000
self.tracker.stop_recording()
# midpoint = SCREEN_WIDTH / 2
# margin = 100
# endpos=None
if trialtype == 'PS':
correct = int(
(targetside == 'left' and endpos[0] <= constants.DISPSIZE[0]/2) or
(targetside == 'right' and endpos[0] >= constants.DISPSIZE[0]/2)
)
print(correct)
elif trialtype == 'AS':
correct = int(
(targetside == 'left' and endpos[0] >= constants.DISPSIZE[0]/2) or
(targetside == 'right' and endpos[0] <= constants.DISPSIZE[0]/2)
)
elif trialtype == 'NoGo':
correct = int(constants.DISPSIZE[0]/2 < endpos[0] <= constants.DISPSIZE[0]/2)
if correct >= 0:
dualstim_screen.clear(colour=(0, 0, 0))
self.disp.show()
self.disp.fill(feedbackscreens[correct])
self.disp.show()
time.sleep(0.5)
results.append([trialnr, trialtype, targetside, endpos, round(latency, 3), correct])
except KeyboardInterrupt:
print("Test interrumpido por el usuario con la tecla 'E'.")
finally:
log.close()
self.tracker.close()
self.disp.close()
print("Test Go/NoGo finalizado.")
df = pd.DataFrame(results, columns=["trialnr", "trialtype", "targetside", "gaze_endpos", "latency", "correct"])
df.to_excel("gonogo_results.xlsx", index=False)
print("Resultados guardados en 'gonogo_results.xlsx'.")
# ============ INTERFAZ PYQT ===================
class EyeTrackingApp(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Test Go/NoGo con Tobii")
self.setGeometry(100, 100, 400, 300)
self.calibrated = False
self.setup_ui()
self.disp = None
self.tracker = None
def setup_ui(self):
self.setStyleSheet("background-color: #e6f0ff;")
layout = QVBoxLayout()
title = QLabel("Test Go/NoGo con Tobii")
title.setFont(QFont("Arial", 18, QFont.Weight.Bold))
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(title)
self.btn_calibrate = QPushButton("Calibrar")
self.btn_calibrate.setFont(QFont("Arial", 14))
self.btn_calibrate.setStyleSheet("background-color: #3399ff; color: white; padding: 10px; border-radius: 5px;")
self.btn_calibrate.clicked.connect(self.calibrate)
layout.addWidget(self.btn_calibrate)
self.btn_test = QPushButton("Iniciar test Go/NoGo")
self.btn_test.setFont(QFont("Arial", 14))
self.btn_test.setStyleSheet("background-color: #3399ff; color: white; padding: 10px; border-radius: 5px;")
self.btn_test.clicked.connect(self.start_test)
layout.addWidget(self.btn_test)
self.btn_results = QPushButton("Consultar resultados")
self.btn_results.setFont(QFont("Arial", 14))
self.btn_results.setStyleSheet("background-color: #3399ff; color: white; padding: 10px; border-radius: 5px;")
self.btn_results.clicked.connect(self.view_results)
layout.addWidget(self.btn_results)
self.setLayout(layout)
def calibrate(self):
result = calibrate_tobii(self)
if result:
self.calibrated = True
QMessageBox.information(self, "Calibración", "Calibración completada con éxito.")
else:
QMessageBox.critical(self, "Error", "La calibración ha fallado.")
def start_test(self):
# if not self.calibrated:
# QMessageBox.warning(self, "Calibración requerida", "Debes calibrar antes de realizar el test.")
# return
run_gonogo_test(self)
def view_results(self):
file_path = "gonogo_results.xlsx"
# Verificamos si el archivo existe antes de intentar abrirlo
if os.path.exists(file_path):
try:
# Abrimos el archivo con el programa predeterminado (Windows)
subprocess.Popen([file_path], shell=True)
except Exception as e:
QMessageBox.critical(self, "Error", f"No se pudo abrir el archivo Excel. {e}")
else:
QMessageBox.warning(self, "Archivo no encontrado", "El archivo de resultados no existe.")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = EyeTrackingApp()
window.show()
sys.exit(app.exec())
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment