forked from janfreyberg/binocular-rivalry
-
Notifications
You must be signed in to change notification settings - Fork 0
/
rivalry.py
329 lines (280 loc) · 11.3 KB
/
rivalry.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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
from psychopy import core, visual, event, monitors, gui, data
from datetime import datetime
import random # random for randomisation
import numpy as np # for array handling
import os
import os.path
# Stimulus Parameters
trialdur = 8.0 # trial duration in seconds
breakdur = 4.0 # minimum duration of a break in seconds
totaltrials = 4
stimsize = 8 # size of stimuli in deg of visual angle
repetitions = 1 # how many trials per condition
screenrefresh = 60.0 # refresh rate of screen
simulationpercepts = [1.5, 3.5] # interval for simulated percept length
# durations will be sampled evenly from this interval
wedges = [8, 16] # how many "pie" slices in stimulus
concentrics = [6, 3] # how many "rings" in stimulus
colors = [1, 0, 0.9] # the rgb layers of your stimulus
# the 0.8 is due to the anaglyph blue being brighter than red
# What buttons you want to use, and what image they correspond to:
responses = {'right': 'red', 'up': 'mixture', 'left': 'blue'}
# Get participant information:
sessionInfo = {'subject': 'test',
'time': datetime.now().strftime('%Y-%m-%d %H-%M')}
assert gui.DlgFromDict(dictionary=sessionInfo, title='Pupil Dilation').OK
# Set the screen parameters: (This is important!)
screen = monitors.Monitor("testMonitor")
screen.setSizePix([1680, 1050])
screen.setWidth(47.475)
screen.setDistance(57)
# Open the display window:
win = visual.Window([500, 500], allowGUI=True, monitor=screen,
units='deg', fullscr=True)
# Make the vergence cues (using list comprehension)
square = visual.Circle(win, radius=np.sqrt(2) * stimsize / 2,
fillColor=None, autoDraw=True, edges=128,
lineWidth=2, pos=[0, 0], lineColor=-1)
# Make fixation cross (using list comprehension)
fixation = visual.GratingStim(win, tex="sqr", mask="cross", sf=0, size=0.5,
pos=[0, 0], color=-1,
autoDraw=True)
# Make a dummy message
message = visual.TextStim(win, units='norm', pos=[0, 0.75], height=0.06,
alignVert='center', alignHoriz='center',
text='')
backreport = visual.TextStim(win, units='norm', pos=[0.9, -0.9], height=0.03,
alignVert='center', alignHoriz='center',
text='')
# The arrays needed to construct the stimuli:
# Mask
mask = np.concatenate([np.array([-1]), np.ones(min(concentrics))])
# Checkerboards
texlayer = [np.repeat(
np.repeat(
np.tile(np.array([[1, 0], [0, 1]]), (concentrics[stim] +
concentrics[stim] /
min(concentrics),
wedges[stim] / 2)),
concentrics[stim - 1] + concentrics[stim - 1] /
min(concentrics), axis=0
),
6 * wedges[stim - 1], axis=1
) * color * 2 - 1
for stim, color in enumerate([colors[0], colors[2]])]
# Blank (all black)
blanklayer = np.zeros(texlayer[0].shape) - 1
# Make stimuli. This will be remade during each trial anyways tho
stimuli = visual.RadialStim(win, size=stimsize, pos=fixation.pos,
mask=mask, colorSpace='rgb', angularRes=600,
# Radial and Angular Frequencies:
radialCycles=0.5,
angularCycles=1,
# Baseline Texture:
tex=np.dstack((texlayer[0],
blanklayer,
texlayer[1]))
)
# Define an instruction function
def instruct(displaystring):
message.text = displaystring + "\nPress [space] to continue."
message.draw()
win.flip()
if 'escape' in event.waitKeys(keyList=['space', 'escape']):
raise KeyboardInterrupt
def trigger(value):
print(value)
# function for demonstration
def demonstrate():
core.wait(0.5)
# Draw Red:
message.text = ("This is what a red stimulus will look like. In "
"the experiment, you would report seeing this by pressing "
"right.\nPress [space] to continue.")
frame = 0
while 'space' not in event.getKeys(keyList=['space']):
stimuli.tex = np.dstack((texlayer[0],
blanklayer,
blanklayer))
message.draw()
stimuli.draw()
win.flip()
# Clear & Flush
win.flip()
event.getKeys()
core.wait(1)
# Draw Blue:
message.text = ("And this is what a blue stimulus will look like. "
"In the experiment, you would report seeing this by "
"pressing left.\nPress [space] to continue.")
frame = 0
while 'space' not in event.getKeys(keyList=['space']):
stimuli.tex = np.dstack((blanklayer,
blanklayer,
texlayer[1]))
message.draw()
stimuli.draw()
win.flip()
# Clear & Flush
win.flip()
event.getKeys()
core.wait(1)
# Draw both rotating:
message.text = ("And this is what the two will look like together. "
"In the experiment, you will have to report what "
"you are seeing at any given point by pressing "
"either [left], [right], or [up]."
"\nPress [space] to continue.")
frame = 0
while not event.getKeys(keyList=['space']):
stimuli.tex = np.dstack((texlayer[0],
blanklayer,
texlayer[1]))
message.draw()
stimuli.draw()
win.flip()
# Clear & Flush
win.flip()
core.wait(1)
# Define a trial function:
def rivaltrial(info):
# Arrays to keep track of responses:
keyArray = np.zeros((int(trialdur * screenrefresh), 3),
dtype=int) # start with up pressed
timeArray = np.zeros((int(trialdur * screenrefresh), 1),
dtype=float) # start at time 0
# Wait for participant to start:
message.text = ("Trial number " + str(1 + trials.thisN) + " of " +
str(trials.nTotal) +
". Remember: UP is for mixture, LEFT is for "
"blue, and RIGHT is for red.\nPress and hold [up] "
"to begin the trial.")
message.draw()
win.flip()
if "escape" in event.waitKeys(keyList=['up', 'escape']):
raise KeyboardInterrupt("You interrupted the script manually.")
# This will keep track of response time:
trialClock = core.Clock()
win.callOnFlip(trialClock.reset)
for frame in range(int(trialdur * screenrefresh)):
# Update the stimulus texture
stimuli.tex = np.dstack((texlayer[0],
blanklayer,
texlayer[1]))
stimuli.draw()
# Flip
win.flip()
# Check response & save to array
currKeys = event.getKeys()
keyArray[frame, :] = [response in currKeys
for response in responses.keys()]
timeArray[frame] = trialClock.getTime()
if keyArray[frame, 1]:
backreport.text = 'mixture'
elif keyArray[frame, 0]:
backreport.text = 'red'
elif keyArray[frame, 2]:
backreport.text = 'blue'
backreport.draw()
# After trial is over, clear the screen
backreport.text = ''
win.flip()
# force first key to be mix
keyArray[0, :] = np.array([0, 1, 0])
# find all rows that are zero
notblanks = np.any(keyArray, axis=1)
# remove them
keyArray = keyArray[notblanks, :]
timeArray = timeArray[notblanks, :]
# return the two arrays
return keyArray, timeArray
# Define a break function:
def rivalbreak():
fixation.autoDraw = False # disable fixation
# Display message
for sec in range(int(breakdur)):
message.text = "Break for %d seconds." % (breakdur - sec)
message.draw()
win.flip()
# Pause for a second (interrupt if key pressed)
if event.waitKeys(maxWait=1, keyList=['escape']):
# Escape stops script:
raise KeyboardInterrupt("You interrupted the script manually.")
fixation.autoDraw = True # enable fixation
win.flip()
# Explain the experiment
instruct("This experiment is called binocular rivalry, and you will need the "
"red and blue glasses to do so. Your right eye should be viewing "
"through a red lense, and your left eye should be viewing through "
"a blue lens.")
instruct("In this experiment, we will be showing a different pattern "
"to each of your eyes. This will mean that for a while, you will "
"be seeing one image, and then it will suddenly switch.")
instruct("All you need to do during each trial of this experiment is "
"report what you are seeing continuously. To do so, you have "
"to hold down either the UP arrow, the LEFT arrow, or the RIGHT "
"arrow.")
instruct("These buttons stand for different images:\nThe RIGHT arrow stands "
"for the RED image.\nThe LEFT arrow stands for the blue image.\n"
"And the UP arrow stands for a mixture, so for example, when about "
"50% is red and 50% is blue.")
instruct("It's rare for an image to be 100% blue or red, but you should "
"still only press the UP arrow when you can't decide which color "
"is dominant.")
instruct("Please report what you are seeing continuously. So, during one trial,"
" you should be pressing one button at any one time. Next, we will "
"demonstrate what the images are like.")
# demonstrate the stimuli:
demonstrate()
# check before starting:
instruct("If you have any other questions, please ask the experimenter now. "
"Otherwise, go ahead to start the experiment.")
# randomise trial sequence:
trials = data.TrialHandler(
[{'pattern': [0, 1] if pattern else [1, 0],
'flickering': False,
'simulation': True if trialtype == 2 else False}
for pattern in range(2)
for trialtype in range(2)],
repetitions)
# make a directory for data storage
# datadir = os.path.join(os.getcwd(), 'data', sessionInfo[
# 'time'] + sessionInfo['subject'], '')
# os.makedirs(datadir)
# Loop through trials:
keylists = []
timelists = []
for trial in trials:
keys, times = rivaltrial(trial)
keylists.append(keys)
timelists.append(times)
print(keys)
# Take a break (not on last)
if trials.thisN < totaltrials - 1:
rivalbreak()
else:
break
# work out the average mix percept
mixpercepts = []
for times, keys in zip(timelists, keylists):
durations = np.diff(np.concatenate(
(times.flatten(), np.zeros(1) + trialdur)
))
durations = durations[keys[:, 1] == 1]
for dur in durations:
mixpercepts.append(dur)
print(keylists)
print(timelists)
print(mixpercepts)
avmix = np.mean(mixpercepts)
message.text = ("Nice work, thanks for taking part!\n\n"
"The average time you saw the mixed percept for was \n" +
str(round(avmix, 1)) + " seconds. Let the experimenters know!"
"\n\nPress any key to finish.")
message.draw()
win.flip()
core.wait(2)
event.waitKeys()
# Close everything neatly
win.close()
core.quit()