Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
T
test_gonogo
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Laura Rodríguez
test_gonogo
Commits
65c73dc7
Commit
65c73dc7
authored
Jun 12, 2025
by
Laura Rodríguez
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
12 jun
parent
c5ccffbe
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
1988 additions
and
0 deletions
+1988
-0
bateria2_pruebas.py
+200
-0
bateria_pruebas.py
+325
-0
cs_test.py
+291
-0
test_final.py
+1172
-0
No files found.
bateria2_pruebas.py
0 → 100644
View file @
65c73dc7
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
()
bateria_pruebas.py
0 → 100644
View file @
65c73dc7
import
sys
import
random
import
time
from
matplotlib.pyplot
import
margins
from
numpy
import
disp
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
from
PyQt6.QtWidgets
import
(
QApplication
,
QWidget
,
QVBoxLayout
,
QPushButton
,
QLabel
,
QMessageBox
)
from
PyQt6.QtGui
import
QFont
from
PyQt6.QtCore
import
Qt
# ============ 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()
self
.
disp
.
close
()
#cierro display
return
True
except
Exception
as
e
:
print
(
f
"Error durante calibración: {e}"
)
return
False
# ============ TEST GO/NOGO ======================
def
run_bateria_test
(
self
):
pygame
.
init
()
self
.
disp
=
libscreen
.
Display
(
bgcolor
=
(
0
,
0
,
0
))
# abro display de nuevo
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
))
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
# Orden fijo de las pruebas:
fixed_trialtypes
=
[
'PS'
,
'AS'
,
'NoGo'
,
'AS'
,
'NoGo'
]
results
=
[]
try
:
for
trialnr
,
trialtype
in
enumerate
(
fixed_trialtypes
,
start
=
1
):
for
event
in
pygame
.
event
.
get
():
if
event
.
type
==
pygame
.
KEYDOWN
and
event
.
key
==
pygame
.
K_e
:
raise
KeyboardInterrupt
# Mostrar instrucción previa según tipo de prueba
instruction_text
=
""
if
trialtype
==
'PS'
:
instruction_text
=
"Mira al círculo GRANDE"
elif
trialtype
==
'AS'
:
instruction_text
=
"Mira al círculo PEQUEÑO"
elif
trialtype
==
'NoGo'
:
instruction_text
=
"NO mires ningún círculo
\n
Mantente en el centro"
trial_instruction
=
libscreen
.
Screen
()
trial_instruction
.
clear
(
colour
=
(
0
,
0
,
0
))
trial_instruction
.
draw_text
(
text
=
instruction_text
,
fontsize
=
40
,
pos
=
CENTER
,
colour
=
(
255
,
255
,
255
),
center
=
True
)
self
.
disp
.
fill
(
trial_instruction
)
self
.
disp
.
show
()
time
.
sleep
(
1.5
)
# 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}"
)
t1
,
startpos
=
self
.
tracker
.
wait_for_saccade_start
()
endtime
,
startpos
,
endpos
=
self
.
tracker
.
wait_for_saccade_end
()
latency
=
(
t1
-
t0
)
/
1000
self
.
tracker
.
stop_recording
()
if
trialtype
==
'PS'
:
correct
=
int
(
(
targetside
==
'left'
and
endpos
[
0
]
<=
constants
.
DISPSIZE
[
0
]
/
2
)
or
(
targetside
==
'right'
and
endpos
[
0
]
>=
constants
.
DISPSIZE
[
0
]
/
2
)
)
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'
:
# Para NoGo: el correcto es no mover la mirada fuera del centro
correct
=
int
(
constants
.
DISPSIZE
[
0
]
/
2
-
100
<
endpos
[
0
]
<
constants
.
DISPSIZE
[
0
]
/
2
+
100
)
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;"
)
button_style
=
"""
QPushButton {
background-color: #3399ff;
color: white;
padding: 10px;
border-radius: 5px;
font-weight: bold;
}
QPushButton:hover {
background-color: #1a8cff;
color: #e6f7ff;
}
QPushButton:pressed {
background-color: #006bb3;
color: #ccf2ff;
padding-top: 11px;
padding-bottom: 9px;
}
"""
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
(
button_style
)
self
.
btn_calibrate
.
clicked
.
connect
(
self
.
calibrate
)
layout
.
addWidget
(
self
.
btn_calibrate
)
self
.
btn_test
=
QPushButton
(
"Bateria pruebas"
)
self
.
btn_test
.
setFont
(
QFont
(
"Arial"
,
14
))
self
.
btn_test
.
setStyleSheet
(
button_style
)
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
(
button_style
)
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_bateria_test
(
self
)
def
view_results
(
self
):
file_path
=
"gonogo_results.xlsx"
if
os
.
path
.
exists
(
file_path
):
try
:
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
cs_test.py
0 → 100644
View file @
65c73dc7
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())
test_final.py
0 → 100644
View file @
65c73dc7
import
sys
import
random
import
threading
import
time
from
matplotlib.pyplot
import
margins
from
numpy
import
disp
import
pygame
from
PyQt6.QtWidgets
import
QApplication
,
QWidget
,
QVBoxLayout
,
QPushButton
,
QLabel
,
QMessageBox
from
PyQt6.QtCore
import
Qt
from
PyQt6.QtGui
import
QFont
# from display import Display
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
from
PyQt6.QtWidgets
import
(
QApplication
,
QWidget
,
QVBoxLayout
,
QPushButton
,
QLabel
,
QMessageBox
)
from
PyQt6.QtGui
import
QFont
from
PyQt6.QtCore
import
Qt
from
PyQt6.QtGui
import
QFont
,
QColor
,
QPalette
from
PyQt6.QtCore
import
Qt
from
pygaze.display
import
Display
from
pygaze.keyboard
import
Keyboard
from
PyQt6.QtWidgets
import
QGridLayout
# asegúrate de importar esto arriba
# ============ LÍNEA DE CALIBRACIÓN ============
# # Crear display (pantalla)
# disp = Display()
# kb = Keyboard()
# # Obtener tamaño de pantalla
# SCREEN_WIDTH, SCREEN_HEIGHT = disp.dispsize
# center_y = SCREEN_HEIGHT // 2
# # Crear pantalla negra con línea blanca horizontal en el centro
# screen = libscreen.Screen()
# screen.clear(colour=(0, 0, 0)) # Fondo negro
# screen.draw_line(
# colour=(255, 255, 255), # Color blanco
# spos=(0, center_y), # Punto de inicio: lado izquierdo del centro
# epos=(SCREEN_WIDTH, center_y), # Punto final: lado derecho del centro
# pw=2 # Grosor de la línea
# )
# # Mostrar pantalla
# disp.fill(screen)
# disp.show()
# # Esperar hasta que se presione la tecla espacio
# while True:
# key, _ = kb.get_key()
# if key == 'space':
# break
# time.sleep(0.01)
# # Cerrar pantalla
# disp.close()
# ============ 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
))
SCREEN_WIDTH
,
SCREEN_HEIGHT
=
constants
.
DISPSIZE
center_y
=
SCREEN_HEIGHT
//
2
self
.
disp
.
fill
(
instruction_screen
)
self
.
disp
.
show
()
time
.
sleep
(
1
)
print
(
"Iniciando calibración..."
)
self
.
tracker
.
calibrate
()
print
(
"Calibración completada."
)
self
.
disp
.
close
()
#cierro display
return
True
except
Exception
as
e
:
print
(
f
"Error durante calibración: {e}"
)
return
False
# ============ TEST PRO/NOGO ======================
def
run_pro_nogo_test
(
self
):
pygame
.
init
()
# self.test_window.disp = self.disp
self
.
disp
=
libscreen
.
Display
(
bgcolor
=
(
0
,
0
,
0
))
#abro display de nuevo
# 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 grande)
\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
(),
'NoGo'
:
libscreen
.
Screen
()
}
for
key
,
color
in
[(
'PS'
,
(
0
,
255
,
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
:
self
.
tracker
.
start_recording
()
for
block
in
range
(
max_blocks
):
fixation_duration
=
random
.
choice
([
1000
,
2000
,
3000
])
# milisegundos
trials
=
[
'PS'
,
'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))
# libtime.pause(fixation_duration) # Usamos la duración fija del bloque
# 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
)
# 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
()
latency
=
(
t1
-
t0
)
/
1000
# 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
==
'NoGo'
:
# En NoGo no debe haber movimiento ocular (sacada)
try
:
# Espera hasta 900 ms para detectar el inicio de una sacada
t1
,
_
=
self
.
tracker
.
wait_for_saccade_start
(
latency
=
200
)
correct
=
0
# Si hay una sacada, se considera incorrecto
latency
=
(
t1
-
t0
)
/
1000
# Calcula la latencia en segundos
print
(
"Incorrecto"
)
except
Exception
:
# Si no se detecta ninguna sacada en ese tiempo, se considera correcto
correct
=
1
latency
=
0
# No hay latencia porque no hubo movimiento
print
(
"Correcto"
)
self
.
disp
.
fill
(
feedbackscreens
[
correct
])
self
.
disp
.
show
()
time
.
sleep
(
0.5
)
results
.
append
([
trialnr
,
trialtype
,
targetside
,
endpos
,
round
(
latency
,
3
),
correct
])
self
.
tracker
.
stop_recording
()
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'."
)
# ============ TEST PR/AS ===================
def
run_pr_as_test
(
self
):
pygame
.
init
()
# self.test_window.disp = self.disp
self
.
disp
=
libscreen
.
Display
(
bgcolor
=
(
0
,
0
,
0
))
#abro display de nuevo
# 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 grande
\n
"
"- Rojo: mira al estímulo blanco pequeño)
\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
(),
}
for
key
,
color
in
[(
'PS'
,
(
0
,
255
,
0
)),
(
'AS'
,
(
255
,
0
,
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'
]
fixation_duration
=
random
.
choice
([
1000
,
2000
,
3000
])
# milisegundos
# 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
))
libtime
.
pause
(
fixation_duration
)
# Usamos la duración fija del bloque
# # Esperar que el usuario presione espacio
# waiting = T rue
# 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
()
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
)
)
print
(
correct
)
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 Pro/AS 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'."
)
# ============ TEST CS_TASK ===================
# --- 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
.
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."
)
# ============ TEST BATERIA ===================
def
run_bateria_test
(
self
):
pygame
.
init
()
self
.
disp
=
libscreen
.
Display
(
bgcolor
=
(
0
,
0
,
0
))
# abro display de nuevo
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
))
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
# Orden fijo de las pruebas:
fixed_trialtypes
=
[
'PS'
,
'AS'
,
'NoGo'
,
'AS'
,
'NoGo'
]
results
=
[]
try
:
for
trialnr
,
trialtype
in
enumerate
(
fixed_trialtypes
,
start
=
1
):
for
event
in
pygame
.
event
.
get
():
if
event
.
type
==
pygame
.
KEYDOWN
and
event
.
key
==
pygame
.
K_e
:
raise
KeyboardInterrupt
# Mostrar instrucción previa según tipo de prueba
instruction_text
=
""
if
trialtype
==
'PS'
:
instruction_text
=
"Mira al círculo GRANDE"
elif
trialtype
==
'AS'
:
instruction_text
=
"Mira al círculo PEQUEÑO"
elif
trialtype
==
'NoGo'
:
instruction_text
=
"NO mires ningún círculo
\n
Mantente en el centro"
trial_instruction
=
libscreen
.
Screen
()
trial_instruction
.
clear
(
colour
=
(
0
,
0
,
0
))
trial_instruction
.
draw_text
(
text
=
instruction_text
,
fontsize
=
40
,
pos
=
CENTER
,
colour
=
(
255
,
255
,
255
),
center
=
True
)
self
.
disp
.
fill
(
trial_instruction
)
self
.
disp
.
show
()
time
.
sleep
(
1.5
)
# 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}"
)
t1
,
startpos
=
self
.
tracker
.
wait_for_saccade_start
()
endtime
,
startpos
,
endpos
=
self
.
tracker
.
wait_for_saccade_end
()
latency
=
(
t1
-
t0
)
/
1000
self
.
tracker
.
stop_recording
()
if
trialtype
==
'PS'
:
correct
=
int
(
(
targetside
==
'left'
and
endpos
[
0
]
<=
constants
.
DISPSIZE
[
0
]
/
2
)
or
(
targetside
==
'right'
and
endpos
[
0
]
>=
constants
.
DISPSIZE
[
0
]
/
2
)
)
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'
:
# Para NoGo: el correcto es no mover la mirada fuera del centro
correct
=
int
(
constants
.
DISPSIZE
[
0
]
/
2
-
100
<
endpos
[
0
]
<
constants
.
DISPSIZE
[
0
]
/
2
+
100
)
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
(
"EYETRACKING"
)
self
.
setGeometry
(
100
,
100
,
800
,
600
)
self
.
calibrated
=
False
self
.
setup_ui
()
self
.
disp
=
None
self
.
tracker
=
None
def
setup_ui
(
self
):
self
.
setStyleSheet
(
"background-color: #e6f0ff;"
)
blue_button_style
=
"""
QPushButton {
background-color: #3399ff;
color: white;
padding: 10px;
border-radius: 5px;
font-weight: bold;
}
QPushButton:hover {
background-color: #1a8cff;
color: #e6f7ff;
}
QPushButton:pressed {
background-color: #006bb3;
color: #ccf2ff;
padding-top: 11px;
padding-bottom: 9px;
}
"""
green_button_style
=
"""
QPushButton {
background-color: #33cc33;
color: white;
padding: 10px;
border-radius: 5px;
font-weight: bold;
}
QPushButton:hover {
background-color: #28a428;
color: #e6ffe6;
}
QPushButton:pressed {
background-color: #1f7a1f;
color: #ccffcc;
padding-top: 11px;
padding-bottom: 9px;
}
"""
# Añade un nuevo estilo para el botón calibrar en verde oscuro
dark_green_button_style
=
"""
QPushButton {
background-color: #1f7a1f; /* Verde oscuro */
color: white;
padding: 10px;
border-radius: 5px;
font-weight: bold;
}
QPushButton:hover {
background-color: #145214; /* Verde aún más oscuro */
color: #e6ffe6;
}
QPushButton:pressed {
background-color: #0d3a0d; /* Verde muy oscuro */
color: #ccffcc;
padding-top: 11px;
padding-bottom: 9px;
}
"""
dark_yellow_button_style
=
"""
QPushButton {
background-color: #b58900; /* Amarillo oscuro */
color: white;
padding: 10px;
border-radius: 5px;
font-weight: bold;
}
QPushButton:hover {
background-color: #8c6d00; /* Amarillo más oscuro */
color: #fffacd; /* Amarillo claro */
}
QPushButton:pressed {
background-color: #665200; /* Amarillo muy oscuro */
color: #fff8dc;
padding-top: 11px;
padding-bottom: 9px;
}
"""
half_green_red_style
=
"""
QPushButton {
background: qlineargradient(
x1: 0, y1: 0, x2: 1, y2: 0,
stop: 0 #33cc33, /* Verde desde el 0
% *
/
stop: 0.5 #33cc33, /* Verde hasta el 50
% *
/
stop: 0.5 #ff3333, /* Rojo desde el 50
% *
/
stop: 1 #ff3333 /* Rojo hasta el 100
% *
/
);
color: white;
padding: 10px;
border-radius: 5px;
font-weight: bold;
border: none;
}
QPushButton:hover {
background: qlineargradient(
x1: 0, y1: 0, x2: 1, y2: 0,
stop: 0 #28a428,
stop: 0.5 #28a428,
stop: 0.5 #cc2929,
stop: 1 #cc2929
);
}
QPushButton:pressed {
background: qlineargradient(
x1: 0, y1: 0, x2: 1, y2: 0,
stop: 0 #1f7a1f,
stop: 0.5 #1f7a1f,
stop: 0.5 #991f1f,
stop: 1 #991f1f
);
padding-top: 11px;
padding-bottom: 9px;
}
"""
green_yellow_button_style
=
"""
QPushButton {
background: qlineargradient(
x1: 0, y1: 0, x2: 1, y2: 0,
stop: 0 #33cc33, /* Verde */
stop: 0.5 #33cc33, /* Verde hasta mitad */
stop: 0.5 #ffcc00, /* Amarillo desde mitad */
stop: 1 #ffcc00 /* Amarillo */
);
color: black;
padding: 10px;
border-radius: 5px;
font-weight: bold;
border: none;
}
QPushButton:hover {
background: qlineargradient(
x1: 0, y1: 0, x2: 1, y2: 0,
stop: 0 #39d339,
stop: 0.5 #39d339,
stop: 0.5 #ffd633,
stop: 1 #ffd633
);
color: black;
}
QPushButton:pressed {
background: qlineargradient(
x1: 0, y1: 0, x2: 1, y2: 0,
stop: 0 #2b992b,
stop: 0.5 #2b992b,
stop: 0.5 #ccaa00,
stop: 1 #ccaa00
);
padding-top: 11px;
padding-bottom: 9px;
color: black;
}
"""
green_red_yellow_button_style
=
"""
QPushButton {
background: qlineargradient(
x1: 0, y1: 0, x2: 1, y2: 0,
stop: 0 #33cc33, /* Verde */
stop: 0.33 #33cc33, /* Verde hasta 33
% *
/
stop: 0.33 #ff3333, /* Rojo desde 33
% *
/
stop: 0.66 #ff3333, /* Rojo hasta 66
% *
/
stop: 0.66 #ffcc00, /* Amarillo desde 66
% *
/
stop: 1 #ffcc00 /* Amarillo */
);
color: black;
padding: 10px;
border-radius: 5px;
font-weight: bold;
border: none;
}
QPushButton:hover {
background: qlineargradient(
x1: 0, y1: 0, x2: 1, y2: 0,
stop: 0 #39d339,
stop: 0.33 #39d339,
stop: 0.33 #ff4d4d,
stop: 0.66 #ff4d4d,
stop: 0.66 #ffd633,
stop: 1 #ffd633
);
color: black;
}
QPushButton:pressed {
background: qlineargradient(
x1: 0, y1: 0, x2: 1, y2: 0,
stop: 0 #2b992b,
stop: 0.33 #2b992b,
stop: 0.33 #cc2929,
stop: 0.66 #cc2929,
stop: 0.66 #ccaa00,
stop: 1 #ccaa00
);
padding-top: 11px;
padding-bottom: 9px;
color: black;
}
"""
main_layout
=
QVBoxLayout
()
main_layout
.
setContentsMargins
(
20
,
20
,
20
,
20
)
# margen alrededor del layout principal
# Título centrado
title
=
QLabel
(
"EYETRACKING"
)
title
.
setFont
(
QFont
(
"Arial"
,
44
,
QFont
.
Weight
.
Bold
))
title
.
setAlignment
(
Qt
.
AlignmentFlag
.
AlignCenter
)
main_layout
.
addWidget
(
title
)
# Grid layout para botones (2 columnas)
grid
=
QGridLayout
()
grid
.
setHorizontalSpacing
(
30
)
# espacio horizontal entre botones
grid
.
setVerticalSpacing
(
20
)
# espacio vertical entre botones
# Crear botones
btn_linea
=
QPushButton
(
"Línea de calibración"
)
btn_linea
.
setFont
(
QFont
(
"Arial"
,
14
))
btn_linea
.
setStyleSheet
(
green_button_style
)
btn_linea
.
clicked
.
connect
(
self
.
show_calibration_line
)
btn_calibrate
=
QPushButton
(
"Calibrar"
)
btn_calibrate
.
setFont
(
QFont
(
"Arial"
,
14
))
btn_calibrate
.
setStyleSheet
(
dark_green_button_style
)
btn_calibrate
.
clicked
.
connect
(
self
.
calibrate
)
btn_battery
=
QPushButton
(
"Test batería pruebas"
)
btn_battery
.
setFont
(
QFont
(
"Arial"
,
14
))
btn_battery
.
setStyleSheet
(
blue_button_style
)
btn_battery
.
clicked
.
connect
(
self
.
start_test0
)
btn_pronogo
=
QPushButton
(
"Test Go/NoGo"
)
btn_pronogo
.
setFont
(
QFont
(
"Arial"
,
14
))
btn_pronogo
.
setStyleSheet
(
green_yellow_button_style
)
btn_pronogo
.
clicked
.
connect
(
self
.
start_test
)
btn_proas
=
QPushButton
(
"Test Ps/As"
)
btn_proas
.
setFont
(
QFont
(
"Arial"
,
14
))
btn_proas
.
setStyleSheet
(
half_green_red_style
)
btn_proas
.
clicked
.
connect
(
self
.
start_test1
)
btn_cs_task
=
QPushButton
(
"Test CS_Task"
)
btn_cs_task
.
setFont
(
QFont
(
"Arial"
,
14
))
btn_cs_task
.
setStyleSheet
(
green_red_yellow_button_style
)
btn_cs_task
.
clicked
.
connect
(
self
.
cs_task_test
)
btn_results
=
QPushButton
(
"Consultar resultados"
)
btn_results
.
setFont
(
QFont
(
"Arial"
,
14
))
btn_results
.
setStyleSheet
(
dark_yellow_button_style
)
btn_results
.
clicked
.
connect
(
self
.
view_results
)
botones
=
[
btn_linea
,
btn_calibrate
,
btn_battery
,
btn_pronogo
,
btn_proas
,
btn_cs_task
,
btn_results
]
for
i
,
boton
in
enumerate
(
botones
):
row
=
i
//
2
# 2 columnas
col
=
i
%
2
grid
.
addWidget
(
boton
,
row
,
col
)
main_layout
.
addLayout
(
grid
)
self
.
setLayout
(
main_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
show_calibration_line
(
self
):
try
:
disp
=
Display
()
kb
=
Keyboard
()
SCREEN_WIDTH
,
SCREEN_HEIGHT
=
disp
.
dispsize
center_y
=
SCREEN_HEIGHT
//
2
screen
=
libscreen
.
Screen
()
screen
.
clear
(
colour
=
(
0
,
0
,
0
))
# Negro
screen
.
draw_line
(
colour
=
(
255
,
255
,
255
),
# Blanco
spos
=
(
0
,
center_y
),
epos
=
(
SCREEN_WIDTH
,
center_y
),
pw
=
2
)
disp
.
fill
(
screen
)
disp
.
show
()
while
True
:
key
,
_
=
kb
.
get_key
()
if
key
==
'space'
:
break
time
.
sleep
(
0.01
)
disp
.
close
()
except
Exception
as
e
:
QMessageBox
.
critical
(
self
,
"Error"
,
f
"No se pudo mostrar la línea de calibración: {e}"
)
def
start_test
(
self
):
run_pro_nogo_test
(
self
)
def
start_test0
(
self
):
run_bateria_test
(
self
)
def
start_test1
(
self
):
run_pr_as_test
(
self
)
def
cs_task_test
(
self
):
compelled_saccade_test
(
self
)
def
view_results
(
self
):
file_path
=
"gonogo_results.xlsx"
if
os
.
path
.
exists
(
file_path
):
try
:
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
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment