Skip to content

Commit 47244b6

Browse files
committed
Merge branch 'tek-visionegg'
2 parents 16bb4be + 223f35d commit 47244b6

File tree

6 files changed

+132
-33
lines changed

6 files changed

+132
-33
lines changed

src/FeedbackBase/VisionEggFeedback.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,8 @@ def _trigger(self, trigger, wait=False):
138138
if wait:
139139
time.sleep(0.03)
140140

141-
def stimulus_sequence(self, prepare, presentation_time, suspendable=True,
142-
pre_stimulus_function=None):
141+
def stimulus_sequence(self, prepare, presentation_time=None,
142+
suspendable=True, pre_stimulus_function=None):
143143
""" Returns an object presenting a series of stimuli.
144144
@param prepare: This is the core connection between the sequence
145145
handler and user code. It can either be a generator (so
@@ -151,6 +151,8 @@ def stimulus_sequence(self, prepare, presentation_time, suspendable=True,
151151
single stimulus, in seconds. Can also be a sequence of values.
152152
If the prepare function doesn't terminate when the sequence is
153153
exhausted, it is restarted.
154+
If the argument is a sequence, any element that is a sequence
155+
again is used as an interval for random duration selection.
154156
@param suspendable: Whether the sequence should halt when pause
155157
is pressed.
156158
@param pre_stimulus_function: If given, it is called exactly

src/lib/speller/__init__.py

+17
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
class Speller(object):
2424
__stimulus = None
2525
__sequences = None
26+
__stim_gen = None
2627

2728
def __init__(self):
2829
self.__init_attributes()
@@ -44,6 +45,7 @@ def __init_attributes(self):
4445
self.countdown_start = 1
4546
# allow classifier input to be simulated by keyboard
4647
self.allow_keyboard_input = True
48+
self.target_present_time = .1
4749

4850
def update_parameters(self):
4951
super(Speller, self).update_parameters()
@@ -55,6 +57,14 @@ def stimulus(self, f):
5557
self.__stimulus = f
5658
return f
5759

60+
@classmethod
61+
def stimulus_generator(self, **kw):
62+
def decorate(f):
63+
self.__stim_gen = f
64+
return f
65+
self.__stim_gen_kw = kw
66+
return decorate
67+
5868
@classmethod
5969
def sequences(self, f):
6070
self.__sequences = f
@@ -71,6 +81,9 @@ def _setup_trial(self):
7181
self)
7282
if self.__stimulus:
7383
self._trial._sequence = getattr(self, self.__stimulus.__name__)
84+
elif self.__stim_gen:
85+
self._trial._sequence = self._stimulus_generator
86+
self.__stimulus_generator = getattr(self, self.__stim_gen.__name__)
7487

7588
def _setup_input_handler(self):
7689
input_handler_type = self._trial_name + 'InputHandler'
@@ -103,3 +116,7 @@ def on_control_event(self, data):
103116
cls = data.get('cl_output', None)
104117
if cls is not None:
105118
self._input_handler.eeg_select(cls)
119+
120+
def _stimulus_generator(self, *a, **kw):
121+
self.stimulus_sequence(self.__stimulus_generator(*a, **kw),
122+
**self.__stim_gen_kw).run()

src/lib/speller/experiment.py

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def __init__(self, view, trial, phrases, input_handler, flag, iter, config):
3535
self._nr_sequences = config.nr_sequences
3636
self._countdown = config.phrase_countdown
3737
self._min_dist = config.min_dist
38+
self._target_present_time = config.target_present_time
3839

3940
def run(self):
4041
self._input_handler.start_experiment(self)
@@ -65,6 +66,7 @@ def run(self):
6566

6667
def trial(self, index, target):
6768
self._trial.target(target)
69+
self._view.present(self._target_present_time)
6870
Experiment.trial(self)
6971
if self._flag:
7072
self._view.next_target()

src/lib/vision_egg/model/color_word.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__copyright__ = """ Copyright (c) 2010 Torsten Schmits
1+
__copyright__ = """ Copyright (c) 2010-2011 Torsten Schmits
22
33
This program is free software; you can redistribute it and/or modify it under
44
the terms of the GNU General Public License as published by the Free Software
@@ -54,7 +54,7 @@ def set_target(self, target):
5454
if isinstance(target, int) and 0 <= target <= len(self.text):
5555
self._target = self.text[target]
5656
self._target_index = target
57-
elif isinstance(target, (str, unicode)) and target in self.text:
57+
elif isinstance(target, basestring) and target in self.text:
5858
self._target = target
5959
self._target_index = self.text.index(target)
6060
else:

src/lib/vision_egg/util/frame_counter.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__copyright__ = """ Copyright (c) 2010 Torsten Schmits
1+
__copyright__ = """ Copyright (c) 2010-2011 Torsten Schmits
22
33
This program is free software; you can redistribute it and/or modify it
44
under the terms of the GNU General Public License as published by the
@@ -36,9 +36,12 @@ def run(self):
3636
logging.getLogger('FrameCounter').error(unicode(e))
3737

3838
def step(self):
39-
pygame.display.flip()
39+
self.sync()
4040
self.frame += 1
4141

42+
def sync(self):
43+
pygame.display.flip()
44+
4245
def lock(self):
4346
self._locked_frame = self.frame
4447

src/lib/vision_egg/util/stimulus.py

+102-27
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__copyright__ = """ Copyright (c) 2010 Torsten Schmits
1+
__copyright__ = """ Copyright (c) 2010-2011 Torsten Schmits
22
33
This program is free software; you can redistribute it and/or modify it
44
under the terms of the GNU General Public License as published by the
@@ -17,9 +17,9 @@
1717

1818
from time import sleep
1919
from datetime import datetime, timedelta
20-
import collections, logging, itertools
20+
import collections, logging, itertools, random
2121

22-
import pygame, VisionEgg
22+
import VisionEgg
2323

2424
from lib.vision_egg.util.frame_counter import FrameCounter
2525

@@ -29,11 +29,73 @@
2929
def _frames(time):
3030
return int(round(float(time) * _refresh_rate))
3131

32+
def _is_seq(l):
33+
return isinstance(l, collections.Sequence)
34+
35+
class StimulusTime(object):
36+
def __init__(self, time, vsync=True):
37+
self._vsync = vsync
38+
self._frames = None
39+
self.set(time)
40+
41+
def set(self, time):
42+
self._adapted = self._time = time
43+
if self:
44+
self._frames = _frames(self._time)
45+
if self._vsync:
46+
self._adapted = round(self._frames * _frame_duration, 6)
47+
48+
@property
49+
def time(self):
50+
return timedelta(seconds=self._adapted)
51+
52+
@property
53+
def original(self):
54+
return self._time
55+
56+
@property
57+
def adapted(self):
58+
return self._adapted
59+
60+
@property
61+
def frames(self):
62+
return self._frames
63+
64+
def __call__(self, frames):
65+
return self.frames if frames else self.time
66+
67+
def __nonzero__(self):
68+
return self._time is not None
69+
70+
class RandomStimulusTime(StimulusTime):
71+
def __init__(self, interval, *a, **kw):
72+
self._interval = interval
73+
StimulusTime.__init__(self, 0, *a, **kw)
74+
75+
def _resample(self):
76+
self.set(random.uniform(*self._interval))
77+
78+
@property
79+
def original(self):
80+
return 'random(%s, %s)' % tuple(self._interval)
81+
82+
@property
83+
def adapted(self):
84+
return self.original
85+
86+
def __call__(self, frames):
87+
self._resample()
88+
return StimulusTime.__call__(self, frames)
89+
90+
def _stimulus_time(time, vsync):
91+
typ = lambda t: RandomStimulusTime if _is_seq(t) else StimulusTime
92+
return typ(time)(time, vsync=vsync)
93+
3294
class StimulusPainter(object):
3395
""" Painter for a series of stimuli. """
3496
def __init__(self, prepare, wait, view, flag, wait_style_fixed=False,
3597
print_frames=False, suspendable=True, pre_stimulus=None,
36-
frame_transition=False):
98+
frame_transition=False, vsync=True):
3799
self._prepare_func = prepare
38100
self._wait_times = itertools.cycle(wait)
39101
self._view = view
@@ -43,10 +105,12 @@ def __init__(self, prepare, wait, view, flag, wait_style_fixed=False,
43105
self._suspendable = suspendable
44106
self._pre_stimulus = pre_stimulus
45107
self._frame_transition = frame_transition
108+
self._vsync = vsync
46109
self._logger = logging.getLogger('StimulusPainter')
47110
self._frame_counter = FrameCounter(self._flag)
48111
self._suspended_time = timedelta()
49112
self._wait = self._frame_wait if frame_transition else self._time_wait
113+
self._online_times = []
50114

51115
def run(self):
52116
if self._print_frames or self._frame_transition:
@@ -67,7 +131,7 @@ def run(self):
67131
def _frame_wait(self):
68132
next_interval = self._next_duration
69133
while self._flag and self._frame_counter.last_interval < next_interval:
70-
sleep(0.001)
134+
self._frame_counter.sync()
71135
if self._print_frames:
72136
self._logger.debug('Frames after waiting: %d' %
73137
self._frame_counter.last_interval)
@@ -94,15 +158,18 @@ def _present(self):
94158
if self._print_frames:
95159
self._logger.debug('Frames before stimulus change: %d' %
96160
self._frame_counter.last_interval)
97-
self._frame_counter.lock()
98161
if self._pre_stimulus is not None:
99162
self._pre_stimulus()
163+
self._frame_counter.lock()
100164
self._view.update()
101165

102166
@property
103167
def _next_duration(self):
104-
nxt = self._wait_times.next() + self._suspended
105-
return nxt
168+
try:
169+
nxt = self._online_times.pop(0) or self._wait_times.next()
170+
except StopIteration:
171+
raise Exception('No specified stimulus times available!')
172+
return nxt(self._frame_transition) + self._suspended
106173

107174
@property
108175
def _suspended(self):
@@ -116,9 +183,13 @@ def _do_prepare(self):
116183

117184
class StimulusIterator(StimulusPainter):
118185
""" Painter using an iterator. """
186+
def __init__(self, *a, **kw):
187+
StimulusPainter.__init__(self, *a, **kw)
188+
119189
def _do_prepare(self):
120190
try:
121-
self._prepare_func.next()
191+
nxt = self._prepare_func.next()
192+
self._online_times.append(_stimulus_time(nxt, self._vsync))
122193
return True
123194
except StopIteration:
124195
return False
@@ -138,37 +209,41 @@ def __init__(self, view, flag, print_frames=False, vsync_times=False,
138209
self._frame_transition = frame_transition
139210
self._logger = logging.getLogger('StimulusSequenceFactory')
140211

141-
def create(self, prepare, times, wait_style_fixed, suspendable=True,
142-
pre_stimulus=None):
212+
def create(self, prepare, times=None, wait_style_fixed=True,
213+
suspendable=True, pre_stimulus=None):
143214
""" Create a StimulusPainter using the preparation object
144215
prepare, with given presentation times and wait style.
145216
If suspendable is True, the sequence halts when on_pause is
146217
pressed.
147218
Global parameters from pyff are used as given in __init__.
148219
"""
149-
if not isinstance(times, collections.Sequence):
150-
times = [times]
151-
times = self._adapt_times(times)
152-
typ = StimulusIterator if hasattr(prepare, '__iter__') else \
153-
StimulusSequence
154-
if not self._frame_transition:
155-
times = [timedelta(seconds=t) for t in times]
220+
if times is None:
221+
times = []
222+
else:
223+
times = self._times(times)
224+
self._debug_times(times)
225+
typ = (StimulusIterator if hasattr(prepare, '__iter__') else
226+
StimulusSequence)
156227
return typ(prepare, times, self._view, self._flag,
157228
wait_style_fixed=wait_style_fixed,
158229
print_frames=self._print_frames, suspendable=suspendable,
159230
pre_stimulus=pre_stimulus,
160-
frame_transition=self._frame_transition)
231+
frame_transition=self._frame_transition,
232+
vsync=self._vsync_times)
233+
234+
def _times(self, times):
235+
if not _is_seq(times):
236+
times = [times]
237+
return [_stimulus_time(t, vsync=self._vsync_times) for t in times]
161238

162-
def _adapt_times(self, times):
163-
frames = [_frames(time) for time in times]
164-
new_times = [round(t * _frame_duration, 6) for t in frames]
239+
def _debug_times(self, times):
240+
original = [t.original for t in times]
241+
adapted = [t.adapted for t in times]
242+
frames = [t.frames for t in times]
165243
if self._frame_transition:
166244
text = ('Adapted stimulus times %s to %s frames (%s)' %
167-
(times, frames, new_times))
168-
times = frames
245+
(original, frames, adapted))
169246
self._logger.debug(text)
170247
elif self._vsync_times:
171-
text = 'Adapted stimulus times %s to %s' % (times, new_times)
172-
times = new_times
248+
text = 'Adapted stimulus times %s to %s' % (original, adapted)
173249
self._logger.debug(text)
174-
return times

0 commit comments

Comments
 (0)