Skip to content

Commit f99d796

Browse files
Merge pull request #288 from pellet/dev/user_input
allow user to start and escape experiments with keyboard and vr controllers
2 parents 08e4a2d + a3aca4a commit f99d796

File tree

1 file changed

+80
-5
lines changed

1 file changed

+80
-5
lines changed

eegnb/experiments/Experiment.py

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from abc import abstractmethod
1212
from typing import Callable
1313
from psychopy import prefs
14+
from psychopy.visual.rift import Rift
1415
#change the pref libraty to PTB and set the latency mode to high precision
1516
prefs.hardware['audioLib'] = 'PTB'
1617
prefs.hardware['audioLatencyMode'] = 3
@@ -50,8 +51,11 @@ def __init__(self, exp_name, duration, eeg, save_fn, n_trials: int, iti: float,
5051
self.soa = soa
5152
self.jitter = jitter
5253
self.use_vr = use_vr
54+
if use_vr:
55+
# VR interface accessible by specific experiment classes for customizing and using controllers.
56+
self.rift: Rift = visual.Rift(monoscopic=True, headLocked=True)
5357
self.use_fullscr = use_fullscr
54-
self.window_size = [1600,800]
58+
self.window_size = [1600,800]
5559

5660
@abstractmethod
5761
def load_stimulus(self):
@@ -86,7 +90,7 @@ def setup(self, instructions=True):
8690

8791
# Setting up Graphics
8892
self.window = (
89-
visual.Rift(monoscopic=True, headLocked=True) if self.use_vr
93+
self.rift if self.use_vr
9094
else visual.Window(self.window_size, monitor="testMonitor", units="deg", fullscr=self.use_fullscr))
9195

9296
# Loading the stimulus from the specific experiment, throws an error if not overwritten in the specific experiment
@@ -123,15 +127,71 @@ def show_instructions(self):
123127
# Disabling the cursor during display of instructions
124128
self.window.mouseVisible = False
125129

126-
# Waiting for the user to press the spacebar to start the experiment
127-
while len(event.getKeys(keyList="space")) == 0:
130+
# clear/reset any old key/controller events
131+
self.__clear_user_input()
132+
133+
# Waiting for the user to press the spacebar or controller button or trigger to start the experiment
134+
while not self.__user_input('start'):
128135
# Displaying the instructions on the screen
129136
text = visual.TextStim(win=self.window, text=self.instruction_text, color=[-1, -1, -1])
130137
self.__draw(lambda: self.__draw_instructions(text))
131138

132139
# Enabling the cursor again
133140
self.window.mouseVisible = True
134141

142+
def __user_input(self, input_type):
143+
if input_type == 'start':
144+
key_input = 'spacebar'
145+
vr_inputs = [
146+
('RightTouch', 'A', True),
147+
('LeftTouch', 'X', True),
148+
('Xbox', 'A', None)
149+
]
150+
151+
elif input_type == 'cancel':
152+
key_input = 'escape'
153+
vr_inputs = [
154+
('RightTouch', 'B', False),
155+
('LeftTouch', 'Y', False),
156+
('Xbox', 'B', None)
157+
]
158+
159+
if len(event.getKeys(keyList=key_input)) > 0:
160+
return True
161+
162+
if self.use_vr:
163+
for controller, button, trigger in vr_inputs:
164+
if self.get_vr_input(controller, button, trigger):
165+
return True
166+
167+
return False
168+
169+
def get_vr_input(self, vr_controller, button=None, trigger=False):
170+
"""
171+
Method that returns True if the user presses the corresponding vr controller button or trigger
172+
Args:
173+
vr_controller: 'Xbox', 'LeftTouch' or 'RightTouch'
174+
button: None, 'A', 'B', 'X' or 'Y'
175+
trigger (bool): Set to True for trigger
176+
177+
Returns:
178+
179+
"""
180+
trigger_squeezed = False
181+
if trigger:
182+
for x in self.rift.getIndexTriggerValues(vr_controller):
183+
if x > 0.0:
184+
trigger_squeezed = True
185+
186+
button_pressed = False
187+
if button is not None:
188+
button_pressed, tsec = self.rift.getButtons([button], vr_controller, 'released')
189+
190+
if trigger_squeezed or button_pressed:
191+
return True
192+
193+
return False
194+
135195
def __draw_instructions(self, text):
136196
text.draw()
137197
self.window.flip()
@@ -147,6 +207,17 @@ def __draw(self, present_stimulus: Callable):
147207
self.window.setDefaultView()
148208
present_stimulus()
149209

210+
def __clear_user_input(self):
211+
event.getKeys()
212+
self.clear_vr_input()
213+
214+
def clear_vr_input(self):
215+
"""
216+
Clears/resets input events from vr controllers
217+
"""
218+
if self.use_vr:
219+
self.rift.updateInputState()
220+
150221
def run(self, instructions=True):
151222
""" Do the present operation for a bunch of experiments """
152223

@@ -171,7 +242,11 @@ def iti_with_jitter():
171242

172243
# Current trial being rendered
173244
rendering_trial = -1
174-
while len(event.getKeys()) == 0 and (time() - start) < self.record_duration:
245+
246+
# Clear/reset user input buffer
247+
self.__clear_user_input()
248+
249+
while not self.__user_input('cancel') and (time() - start) < self.record_duration:
175250

176251
current_experiment_seconds = time() - start
177252
# Do not present stimulus until current trial begins(Adhere to inter-trial interval).

0 commit comments

Comments
 (0)