-
Notifications
You must be signed in to change notification settings - Fork 2
/
SimpleVisionEgg.py
260 lines (204 loc) · 10 KB
/
SimpleVisionEgg.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
"""Simple script to set up and run vision egg in the way that we often need to
do. This module serves simply to set things up and keep track of VisionEgg
related values. It shouldn't really _do_ anything."""
import pygame
import VisionEgg
from VisionEgg.Core import get_default_screen, Viewport
from VisionEgg.FlowControl import Presentation, FunctionController
from VisionEgg.ResponseControl import KeyboardResponseController
from VisionEgg.DaqKeyboard import KeyboardTriggerInController
from VisionEgg.ParameterTypes import NoneType
#################################
# Set some VisionEgg Defaults: #
#################################
# Turning the GUI completely off keeps this from crashing on SnowLeopard
VisionEgg.config.VISIONEGG_GUI_INIT = 0
VisionEgg.config.VISIONEGG_GUI_ON_ERROR = 0
VisionEgg.config.VISIONEGG_FULLSCREEN = 1
class MultiStimHelper:
"""Meant to be embedded in MultiStim"""
stims = None
def __init__(self, stims):
# We need to do this because of our __setattr__
self.__dict__['stims'] = stims
def __setattr__(self, name, value):
for s in self.stims:
setattr(s.parameters, name, value)
def __getattr__(self, name):
return [getattr(s.parameters, name) for s in self.stims]
class MultiStim:
"""Very simple surrogate class to get or set values of multiple classes at
once."""
parameters = None
def __init__(self, *stims):
self.parameters = MultiStimHelper(stims)
def set(self, **parms):
for k, v in parms.items():
setattr(self.parameters, k, v)
class SimpleVisionEgg:
keyboard_controller = None
trigger_controller = None
screen = None
presentation = None
keys = None
presses = None
releases = None
def __init__(self):
"""We break up initialization a bit as we need to go back and forth with
some information. In this case, we need screen size before specifying
the stimuli"""
VisionEgg.start_default_logging()
VisionEgg.watch_exceptions()
# get screen size for setting fullscreen resolution
# comment this block out if you don't want to use full-screen.
screen = pygame.display.set_mode((0,0)) # this opens up a pygame window
WIDTH, HEIGHT = screen.get_size()
pygame.quit() # close this pygame window, or else it interferes w/ VE
VisionEgg.config.VISIONEGG_SCREEN_W = WIDTH
VisionEgg.config.VISIONEGG_SCREEN_H = HEIGHT
self.screen = get_default_screen()
self.keys = []
self.presses = []
self.releases = []
def set_stimuli(self, stimuli, trigger=None, kb_controller=False):
"""Now that we have our stimuli, we initialize everything we can"""
viewport = Viewport(screen=self.screen, size=self.screen.size,
stimuli=stimuli)
# We disable "check_events" so that we don't lose "instantaneous" key
# presses and can check these in our Response classes
self.presentation = Presentation(viewports=[viewport],
check_events=False)
if trigger:
trigger_controller = KeyboardTriggerInController(trigger)
self.presentation.add_controller(self.presentation,
'trigger_go_if_armed', trigger_controller)
self.presentation.set(trigger_go_if_armed=0)
if kb_controller:
self.keyboard_controller = KeyboardResponseController()
self.presentation.add_controller(None, None, self.keyboard_controller)
def set_functions(self, update=None, pause_update=None):
"""Interface for cognac.StimulusController or similar"""
self.presentation.add_controller(None, None,
FunctionController(during_go_func=update,
between_go_func=pause_update,
return_type=NoneType) )
def go(self, go_duration=('forever',)):
self.presentation.parameters.go_duration = go_duration
self.presentation.go()
def pause(self):
self.presentation.parameters.go_duration = (0, 'frames')
def quit(self):
# Note - despite what you might expect from the documentation, the
# following doesn't work (as of release-1.2.1)
# self.presentation.parameters.quit = True
# This does work, via the main while loop in Presentation.go()
self.presentation.set(go_duration=(-1, 'seconds'))
def get_new_response(self, t, min_interval=2.0 / 60, releases=False):
"""(key, press) = get_new_response(self, t, min_interval=2.0 / 60)
DEPRECATED!
Use this function to get responses from the keyboard controller in real
time.
Returns (None, None) if no new response is available.
Maintains three instance variables - keys, presses and releases, which
you can also access directly (but they won't be updated during loops
where you don't call this function)
This function makes a number of assumptions and is a little brittle
right now. By not hard-coding the min_interval and maybe using key
presses and release events directly, we'd have a much better function.
But I don't really care right now.
DJC
"""
raise DeprecationWarning("please use pygame directly, as in" +
"StimController.Response")
# Note - this is deprecated anyway, but it'd probably make more sense to
# use the keyboard_controller.get_responses() to simply get the keys
# that are down _right_now_
press_time = self.keyboard_controller.get_time_last_response_since_go()
key = self.keyboard_controller.get_last_response_since_go()
# Our first response!
if len(self.keys) == 0:
if key:
self.keys.append(key)
self.presses.append(press_time)
self.releases.append(None)
if releases:
return (key, None)
else:
return (key, press_time)
else:
return (None, None)
# We haven't seen a key press for min_interval
if t >= press_time + min_interval and not self.releases[-1]:
# This is only approximate!
self.releases[-1] = t
if releases:
return (self.keys[-1], t)
else:
return (None, None)
# We've seen a release, or we see a new key
if (self.releases[-1] and press_time > self.releases[-1]) or \
key != self.keys[-1]:
if not self.releases[-1]:
self.releases[-1] = press_time
self.keys.append(key)
self.presses.append(press_time)
self.releases.append(None)
if releases:
return (key, None)
else:
return (key, press_time)
return (None, None)
def get_responses(self, timeToSubtract=0, min_interval=2.0/60):
"""
Use this function to post-process the results of a KeyboardController
VisionEgg's keyboard libraries records a keypress and timestamp every
time something is down. So if a key is held down for 100 ms, there will
be an entry in the keylist for every sample during that 100ms. This is
a bit much; I'd rather just save onsets and offsets for every key. This
function evaluates that.
"""
'''
If we're using the FORP, this isn't necessary, as events have no
duration; they are represented as instantaneous keypresses.
-- John
'''
response = self.keyboard_controller.get_responses_since_go()
responseTime = self.keyboard_controller.get_time_responses_since_go()
# If I've only got one item in my response list, then it's silly to worry about onset/offset. Just keep it.
if len(response) < 2:
return (response,responseTime)
# Save the first response, as by definition that's the first onset:
goodResp = [response[0]]
goodRespTime = [responseTime[0]-timeToSubtract]
# Now step through every other item in the response list to check for unique-ness.
for i in range(1,len(responseTime)):
if (not(response[i] == response[i-1]) or \
(responseTime[i] - responseTime[i-1] > \
min_interval)):
# ie, if something changed, or we have a long gap:
offsetResp = [] # we might want to save an offset
for item in response[i-1]: # loop through last item's data
if (responseTime[i] - responseTime[i-1] < \
min_interval) and \
not(item in response[i]):
# Bit clunky. Basically, holding down a key while pressing another creates
# a unique response. So if you only let up one of those keys, code the
# offset just for that key.
offsetResp.append(item+'_Off')
else:
# it's been long enough that everything that was on should be called off.
offsetResp.append(item+'_Off')
if len(offsetResp) > 0:
# If there's offset stuff to worry about, save it.
goodResp.append(offsetResp)
goodRespTime.append(responseTime[i-1]-timeToSubtract)
# Save the new (onset) response.
goodResp.append(response[i])
goodRespTime.append(responseTime[i]-timeToSubtract)
# The final event should be an offset for whatever was down.
offsetResp = []
for item in response[-1]:
offsetResp.append(item+'_Off')
goodResp.append(offsetResp) #goodResp.append(response[-1]+'_Off')
goodRespTime.append(responseTime[-1]-timeToSubtract)
return (goodResp, goodRespTime)