Commit 65c73dc7 by Laura Rodríguez

12 jun

parent c5ccffbe
import pygame
import random
import sys
import time
SCREEN_WIDTH, SCREEN_HEIGHT = 1920, 1080
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])
ARROW_OFFSET_Y = 60 # altura flecha arriba target
RADIUS = 38
def draw_arrow(screen, position, direction):
color = (255, 255, 255)
x, y = position
y -= ARROW_OFFSET_Y
if direction == "left":
points = [(x, y), (x + 20, y + 10), (x + 20, y - 10)]
else: # right
points = [(x, y), (x - 20, y + 10), (x - 20, y - 10)]
pygame.draw.polygon(screen, color, points)
def wait_for_response(timeout=3000):
start = time.time()
while (time.time() - start) * 1000 < timeout:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key in [pygame.K_LEFT, pygame.K_RIGHT]:
return event.key
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
pygame.time.delay(10)
return None
def pro_saccade(screen):
# Target verde indica mirar al target (pro-saccade)
screen.fill((0,0,0))
side = random.choice(["left", "right"])
pos = LEFT_POS if side == "left" else RIGHT_POS
# Dibuja target verde en el lado correcto
pygame.draw.circle(screen, (0,255,0), pos, RADIUS)
# Dibuja target rojo en el lado contrario (no target)
other_pos = RIGHT_POS if side == "left" else LEFT_POS
pygame.draw.circle(screen, (255,0,0), other_pos, RADIUS)
# Flecha arriba en el target correcto
draw_arrow(screen, pos, side)
pygame.display.flip()
key = wait_for_response()
if key == pygame.K_LEFT:
resp = "left"
elif key == pygame.K_RIGHT:
resp = "right"
else:
resp = None
correcto = (resp == side)
return correcto, resp, side
def anti_saccade(screen):
# Target rojo indica mirar al lado contrario (anti-saccade)
screen.fill((0,0,0))
side = random.choice(["left", "right"])
pos = LEFT_POS if side == "left" else RIGHT_POS
# Dibuja target rojo en lado seleccionado
pygame.draw.circle(screen, (255,0,0), pos, RADIUS)
# Dibuja target verde en lado contrario
other_pos = RIGHT_POS if side == "left" else LEFT_POS
pygame.draw.circle(screen, (0,255,0), other_pos, RADIUS)
# Flecha arriba en el lado contrario al target rojo (donde debe mirar)
arrow_side = "right" if side == "left" else "left"
arrow_pos = LEFT_POS if arrow_side == "left" else RIGHT_POS
draw_arrow(screen, arrow_pos, arrow_side)
pygame.display.flip()
key = wait_for_response()
if key == pygame.K_LEFT:
resp = "left"
elif key == pygame.K_RIGHT:
resp = "right"
else:
resp = None
# Correcto si mira al lado contrario al target rojo
correcto = (resp == arrow_side)
return correcto, resp, arrow_side
def no_go(screen):
# Estímulo amarillo = No mover la mirada (NoGo)
screen.fill((0,0,0))
pygame.draw.circle(screen, (255, 255, 0), CENTER, RADIUS) # amarillo centro
pygame.display.flip()
key = wait_for_response(2000) # Espera respuesta pero no debe moverse
if key is None:
# Correcto si no responde (no se mueve)
correcto = True
resp = None
else:
# Incorrecto si responde (se mueve)
correcto = False
resp = "movimiento"
return correcto, resp, "centro"
def fixation(screen):
# Mantener fijación centro 2 seg
screen.fill((0,0,0))
pygame.draw.circle(screen, (255, 255, 255), CENTER, RADIUS)
pygame.display.flip()
start = time.time()
while (time.time() - start)*1000 < 2000:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
pygame.time.delay(10)
return True, None, "centro"
def saccade_simple(screen):
# Aparace target verde a izquierda o derecha sin flecha, debe mirar ahí
screen.fill((0,0,0))
side = random.choice(["left", "right"])
pos = LEFT_POS if side == "left" else RIGHT_POS
other_pos = RIGHT_POS if side == "left" else LEFT_POS
pygame.draw.circle(screen, (0, 255, 0), pos, RADIUS)
pygame.draw.circle(screen, (255, 0, 0), other_pos, RADIUS)
pygame.display.flip()
key = wait_for_response()
if key == pygame.K_LEFT:
resp = "left"
elif key == pygame.K_RIGHT:
resp = "right"
else:
resp = None
correcto = (resp == side)
return correcto, resp, side
def main():
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Pruebas oficiales Pro/Anti/NoGo")
tests = [
("Pro-saccade", pro_saccade),
("Anti-saccade", anti_saccade),
("NoGo", no_go),
("Fijación", fixation),
("Saccade simple", saccade_simple),
]
for i, (name, func) in enumerate(tests, 1):
screen.fill((0, 0, 0))
font = pygame.font.SysFont(None, 48)
text = font.render(f"Prueba {i}: {name}", True, (255, 255, 255))
rect = text.get_rect(center=(CENTER[0], CENTER[1] - 200))
screen.blit(text, rect)
pygame.display.flip()
pygame.time.delay(1500)
result = func(screen)
correcto, resp, lado = result
print(f"Prueba {i} - {name}: Correcto? {correcto}, Respuesta: {resp}, Lado esperado: {lado}")
# Pausa entre pruebas
font_small = pygame.font.SysFont(None, 36)
text2 = font_small.render("Presiona ESPACIO para continuar...", True, (255, 255, 255))
rect2 = text2.get_rect(center=(CENTER[0], CENTER[1] + 200))
screen.blit(text2, rect2)
pygame.display.flip()
waiting = True
while waiting:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
waiting = False
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
pygame.quit()
if __name__ == "__main__":
main()
import sys
import random
import time
import threading
import pygame
import pandas as pd
import os
import subprocess
from PyQt6.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QLabel, QPushButton
)
from PyQt6.QtGui import QFont, QColor, QPalette
from PyQt6.QtCore import Qt
from pygaze.display import Display
from pygaze.eyetracker import EyeTracker
# --- CONSTANTES DEL EXPERIMENTO ---
SCREEN_WIDTH, SCREEN_HEIGHT = 1920, 1080
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])
RESULTS_FILENAME = "compelled_saccade_results.xlsx"
# Colores para feedback: verde si correcto, rojo si incorrecto
feedbackscreens = {
True: (0, 255, 0), # verde
False: (255, 0, 0) # rojo
}
def feedback_screen(disp: Display, correct: bool):
disp.fill(feedbackscreens[correct])
disp.show()
time.sleep(0.5) # mostrar 0.5 segundos
def wait_for_saccade_start(tracker, timeout=2000):
"""Espera hasta detectar inicio de sacada o timeout (ms)."""
start = time.time()
while (time.time() - start) * 1000 < timeout:
sample = tracker.sample()
if sample and hasattr(sample, "saccade") and sample.saccade:
return True
pygame.time.delay(5)
return False
def wait_for_saccade_end(tracker, timeout=2000):
"""Espera hasta fin de sacada y devuelve la muestra con info."""
start = time.time()
while (time.time() - start) * 1000 < timeout:
sample = tracker.sample()
if sample and hasattr(sample, "end_pos") and sample.end_pos is not None:
return sample
pygame.time.delay(5)
return None
def compelled_saccade_test(trials_per_block=30, n_blocks=3):
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Compelled Saccade Task")
font = pygame.font.SysFont(None, 36)
def draw_text(text, pos, color=(255, 255, 255)):
img = font.render(text, True, color)
rect = img.get_rect(center=pos)
screen.blit(img, rect)
def check_exit_key():
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN and event.key == pygame.K_e:
pygame.quit()
sys.exit()
def color_name(rgb):
if rgb == (0, 255, 0): return "Verde"
elif rgb == (255, 0, 0): return "Rojo"
elif rgb == (255, 255, 0): return "Amarillo"
else: return "Desconocido"
def pause_between_blocks(block_num, total_blocks):
screen.fill((0, 0, 0))
draw_text(f"Bloque {block_num}/{total_blocks} completado", (CENTER[0], CENTER[1] - 40))
draw_text("Presiona ESPACIO para continuar o E para salir", (CENTER[0], CENTER[1] + 20))
pygame.display.flip()
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()
sys.exit()
disp = Display()
tracker = EyeTracker(disp)
# tracker.calibrate()
results = []
radius = 38
trial_counter = 1
for block in range(1, n_blocks + 1):
for trial in range(1, trials_per_block + 1):
check_exit_key()
# Estímulo central rojo
stim_color = (255, 0, 0)
screen.fill((0, 0, 0))
pygame.draw.circle(screen, stim_color, CENTER, radius)
pygame.display.flip()
pygame.time.delay(800)
# Estímulos amarillos anticipatorios
screen.fill((0, 0, 0))
pygame.draw.circle(screen, stim_color, CENTER, radius)
pygame.draw.circle(screen, (255, 255, 0), LEFT_POS, radius)
pygame.draw.circle(screen, (255, 255, 0), RIGHT_POS, radius)
pygame.display.flip()
pygame.time.delay(800)
# Colores laterales: uno verde, otro rojo aleatorio
if random.choice([True, False]):
left_color, right_color = (0, 255, 0), (255, 0, 0)
else:
left_color, right_color = (255, 0, 0), (0, 255, 0)
screen.fill((0, 0, 0))
pygame.draw.circle(screen, left_color, LEFT_POS, radius)
pygame.draw.circle(screen, right_color, RIGHT_POS, radius)
pygame.display.flip()
tracker.start_recording()
tracker.log("TARGETS_ON")
saccade = None
# Esperar inicio sacada
if wait_for_saccade_start(tracker):
# Esperar fin sacada y obtener datos
saccade = wait_for_saccade_end(tracker)
if saccade:
end_x = saccade.end_pos[0]
choice = 'left' if abs(end_x - LEFT_POS[0]) < abs(end_x - RIGHT_POS[0]) else 'right'
chosen_color = left_color if choice == 'left' else right_color
correct = int(chosen_color == (0, 255, 0)) # verde correcto
rt = saccade.latency * 1000
feedback_screen(disp, correct)
else:
# No sacada detectada (omisión)
choice = 'none'
chosen_color = "Ninguno"
correct = 0
rt = None
tracker.stop_recording()
trial_data = {
"trial": trial_counter,
"block": block,
"stim_color": color_name(stim_color),
"left_color": color_name(left_color),
"right_color": color_name(right_color),
"choice": choice,
"chosen_color": color_name(chosen_color) if isinstance(chosen_color, tuple) else chosen_color,
"RT_ms": rt,
"correct": correct
}
results.append(trial_data)
# Guardar resultados tras ensayo
df_trial = pd.DataFrame([trial_data])
try:
df_existing = pd.read_excel(RESULTS_FILENAME)
df_combined = pd.concat([df_existing, df_trial], ignore_index=True)
except FileNotFoundError:
df_combined = df_trial
df_combined.to_excel(RESULTS_FILENAME, index=False)
trial_counter += 1
if block < n_blocks:
pause_between_blocks(block, n_blocks)
pygame.quit()
print(f"Test finalizado. Resultados guardados en {RESULTS_FILENAME}")
def open_results_file():
if os.path.exists(RESULTS_FILENAME):
if sys.platform == "win32":
os.startfile(RESULTS_FILENAME)
elif sys.platform == "darwin":
subprocess.call(["open", RESULTS_FILENAME])
else:
subprocess.call(["xdg-open", RESULTS_FILENAME])
else:
print("No hay resultados guardados todavía.")
# --- EJECUCIÓN DIRECTA ---
if __name__ == "__main__":
compelled_saccade_test()
# # --- INTERFAZ PyQt6 ---
# class BlueWhiteInterface(QWidget):
# def __init__(self):
# super().__init__()
# self.setWindowTitle("Test de Elección Sacádica Urgente")
# self.setGeometry(100, 100, 500, 300)
# self.init_ui()
# def init_ui(self):
# self.setAutoFillBackground(True)
# palette = self.palette()
# palette.setColor(QPalette.ColorRole.Window, QColor("#FFFFFF"))
# self.setPalette(palette)
# layout = QVBoxLayout()
# title = QLabel("Compelled saccade test")
# title.setFont(QFont("Arial", 20, QFont.Weight.Bold))
# title.setStyleSheet("color: #1E90FF;")
# title.setAlignment(Qt.AlignmentFlag.AlignCenter)
# layout.addWidget(title)
# self.start_button = QPushButton("Iniciar test")
# self.start_button.setFont(QFont("Arial", 14))
# self.start_button.setStyleSheet("""
# QPushButton {
# background-color: #1E90FF;
# color: white;
# padding: 10px;
# border-radius: 10px;
# }
# QPushButton:hover {
# background-color: #187bcd;
# }
# """)
# self.start_button.clicked.connect(self.start_test)
# layout.addWidget(self.start_button, alignment=Qt.AlignmentFlag.AlignCenter)
# self.view_button = QPushButton("Ver resultados")
# self.view_button.setFont(QFont("Arial", 14))
# self.view_button.setStyleSheet("""
# QPushButton {
# background-color: #1E90FF;
# color: white;
# padding: 10px;
# border-radius: 10px;
# margin-top: 20px;
# }
# QPushButton:hover {
# background-color: #187bcd;
# }
# """)
# self.view_button.clicked.connect(open_results_file)
# layout.addWidget(self.view_button, alignment=Qt.AlignmentFlag.AlignCenter)
# self.setLayout(layout)
# def start_test(self):
# self.start_button.setEnabled(False)
# self.start_button.setText("Ejecutando...")
# thread = threading.Thread(target=self.run_test, daemon=True)
# thread.start()
# def run_test(self):
# compelled_saccade_test()
# # Volver a habilitar botón tras terminar el test
# self.start_button.setEnabled(True)
# self.start_button.setText("Iniciar test")
# # --- EJECUCIÓN ---
# if __name__ == "__main__":
# app = QApplication(sys.argv)
# window = BlueWhiteInterface()
# window.show()
# sys.exit(app.exec())
This diff is collapsed. Click to expand it.
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