def test_aperture(self): win = self.win if not win.allowStencil: pytest.skip("Don't run aperture test when no stencil is available") grating = visual.GratingStim(win, mask='gauss', sf=8.0, size=2, color='FireBrick', units='norm') aperture = visual.Aperture(win, size=1 * self.scaleFactor, pos=[0.8 * self.scaleFactor, 0]) aperture.enabled = False grating.draw() aperture.enabled = True str(aperture) #check that str(xxx) is working grating.ori = 90 grating.color = 'black' grating.draw() utils.compareScreenshot('aperture1_%s.png' % (self.contextName), win) #aperture should automatically disable on exit for shape, nVert, pos in [(None, 120, (0, 0)), ('circle', 17, (.2, -.7)), ('square', 4, (-.5, -.5)), ('triangle', 3, (1, 1))]: aperture = visual.Aperture(win, pos=pos, shape=shape, nVert=nVert) assert len(aperture.vertices) == nVert assert aperture.contains(pos)
def test_aperture_image(self): win = self.win fileName = os.path.join(utils.TESTS_DATA_PATH, 'testwedges.png') if not win.allowStencil: pytest.skip("Don't run aperture test when no stencil is available") grating = visual.GratingStim(win, mask='gauss', sf=8.0, size=2, color='FireBrick', units='norm') aperture = visual.Aperture(win, size=1 * self.scaleFactor, pos=[0.8 * self.scaleFactor, 0], shape=fileName) aperture.enabled = False grating.draw() aperture.enabled = True "{}".format(aperture) #check that str(xxx) is working grating.ori = 90 grating.color = 'black' grating.draw() utils.compareScreenshot('aperture2_%s.png' % (self.contextName), win, crit=30)
def test_aperture(self): win = self.win if not win.allowStencil: pytest.skip("Don't run aperture test when no stencil is available") grating = visual.GratingStim(win, mask='gauss', sf=8.0, size=2, color='FireBrick', units='norm', autoLog=False) aperture = visual.Aperture(win, size=1 * self.scaleFactor, pos=[0.8 * self.scaleFactor, 0], autoLog=False) aperture.disable() grating.draw() aperture.enable() str(aperture) #check that str(xxx) is working grating.setOri(90, log=False) grating.setColor('black', log=False) grating.draw() if utils._under_xvfb: pytest.xfail("not clear why fails under Xvfb" ) # skip late so we smoke test t utils.compareScreenshot('aperture1_%s.png' % (self.contextName), win)
def create_stimuli(exp): exp.win.allowStencil = True aperture = visual.Aperture( exp.win, exp.p.field_size ) fix = Point( exp.win, exp.p.fix_pos, exp.p.fix_radius, exp.p.fix_color ) # TODO incorporate fixation drift warning for training? ring = Point( exp.win, exp.p.fix_pos, exp.p.fix_radius * 1.5, exp.win.color, ) bar = RetBar( exp.win, exp.p.field_size, exp.p.bar_width, exp.p.element_size, exp.p.element_tex, exp.p.element_sf, exp.p.drift_rate, ) return locals()
def _setAperture(self): """Blocks text beyond border using Aperture Returns ------- psychopy.visual.Aperture The aperture setting viewable area for forms """ return visual.Aperture(win=self.win, name='aperture', units=self.units, shape='square', size=self.size, pos=(0, 0))
def test_aperture(self): win = self.win if not win.allowStencil: pytest.skip("Don't run aperture test when no stencil is available") grating = visual.PatchStim(win, mask='gauss',sf=8.0, size=2,color='FireBrick', units='norm') aperture = visual.Aperture(win, size=1*self.scaleFactor,pos=[0.8*self.scaleFactor,0]) aperture.disable() grating.draw() aperture.enable() grating.setOri(90) grating.setColor('black') grating.draw() utils.compareScreenshot('aperture1_%s.png' %(self.contextName), win)
def testAperture(self): win = self.win contextName = self.contextName grating = visual.PatchStim(win, mask='gauss', sf=8.0, size=2, color='FireBrick', units='norm') aperture = visual.Aperture(win, size=1 * self.scaleFactor, pos=[0.8 * self.scaleFactor, 0]) aperture.disable() grating.draw() aperture.enable() grating.setOri(90) grating.setColor('black') grating.draw() utils.compareScreenshot('aperture1_%s.png' % (contextName), win)
#Shapes mask = visual.ShapeStim(win, lineColor='white', fillColor='white', vertices=((-1*stim_width*mask_size_ratio, 0), (0, stim_height*mask_size_ratio), (stim_width*mask_size_ratio, 0), (0,-1*stim_height*mask_size_ratio))) #mask = visual.Rect(win, lineColor='white', fillColor='white', width = 2*stim_width*1.6, height = 2*stim_height*1.6) black_square = visual.Rect(win, lineColor='black', fillColor='black', width = 2*stim_width, height = 2*stim_height) white_diamond = visual.ShapeStim(win, lineColor='white', fillColor='white', vertices=((-1*stim_width, 0), (0, stim_height), (stim_width, 0), (0,-1*stim_height))) black_diamond = visual.ShapeStim(win, lineColor='black', fillColor='black', vertices=((-1*stim_width-pix_size, 0), (0, stim_height+pix_size), (stim_width+pix_size, 0), (0,-1*stim_height-pix_size))) blockers = {'left': visual.ShapeStim(win, lineWidth=.1, lineColor='black', fillColor='black', vertices=((-1, stim_height), (-1, -1*stim_height), (-1*stim_width+stim_width*blocker_size, stim_height), (-1*stim_width+stim_width*blocker_size, -1*stim_height))), 'right': visual.ShapeStim(win, lineWidth=.1, lineColor='black', fillColor='black', vertices=((1, stim_height), (1, -1*stim_height), (stim_width-stim_width*blocker_size, stim_height), (stim_width-stim_width*blocker_size, -1*stim_height))), 'top': visual.ShapeStim(win, lineWidth=.1, lineColor='black', fillColor='black', vertices=((-1, 1), (1, 1), (-1,stim_height-stim_size*blocker_size), (1,stim_height-stim_height*blocker_size))), 'bottom':visual.ShapeStim(win, lineWidth=.1, lineColor='black', fillColor='black', vertices=((-1, -1), (1, -1), (-1,-1*stim_height+stim_height*blocker_size), (1,-1*stim_height+stim_height*blocker_size))) } apert = visual.Aperture(win, size=1, pos=(0, 0), ori=0, nVert=120, shape=((-1*stim_width, 0), (0, stim_height), (stim_width, 0), (0,-1*stim_height)), inverted=False, units=None, name=None, autoLog=None) apert.enablaed = False #aperture = visual.Aperture(win, size = 1, shape= ((-1*stim_width, 1*stim_height), (-1*stim_width, -1*stim_height), (stim_width, -1*stim_height), (stim_width,1*stim_height))) #aperture.enabled = False #maskNoise = visual.ImageStim(win, image = 'maskNoise.png', size = [stim_width*mask_size_ratio*width_ratio, stim_height*mask_size_ratio*height_ratio]) noise = visual.ImageStim(win, image = 'testnoise.png') #Fixation fixation = visual.ShapeStim( win=win, name='polygon', vertices='cross', size=(stim_width/1.5, stim_height/1.5), ori=0, pos=(0, 0), fillColor=[1,1,1], fillColorSpace='rgb', lineColor = [-1,-1,-1], opacity=1, depth=0.0, interpolate=True)
instr = visual.TextStim(win, text="Any key to quit", pos=(0, -.7)) gabor1 = visual.GratingStim(win, mask='circle', sf=4, size=1.2, color=[0.5, -0.5, 1]) gabor2 = visual.GratingStim(win, mask='circle', sf=4, size=1.2, color=[-0.5, -0.5, -1]) vertices = [(-0.02, -0.0), (-.8, .2), (0, .6), (.1, 0.06), (.8, .3), (.6, -.4)] # `sizes in Aperture refers to the diameter when shape='circle'; # vertices or other shapes are scaled accordingly aperture = visual.Aperture(win, size=0.9, shape=vertices) # try shape='square' aperture.enabled = False # enabled by default when created gabor1.draw() instr.draw() # drawing will now only be done within the aperture shape: aperture.enabled = True gabor2.draw() win.flip() event.waitKeys() win.close() # The contents of this file are in the public domain.
# import psychopy modules from psychopy import visual, core, event import numpy as np # create a window myWin = visual.Window(size=(800, 600), units="pix", fullscr=False, color=[0, 0, 0], allowStencil=True) myWin.mouseVisible = False # create an aperture myApt = visual.Aperture(myWin, size=100, shape='square') #create a mouse instance myMouse = event.Mouse(visible=False) # prepare the stimuli text = visual.TextStim(myWin, text="Moving window example " * 32, height=30, color='black', wrapWidth=760) # mouse-contingent moving window while True: myApt.pos = myMouse.getPos() text.draw() myWin.flip() # press Q to quit the program if len(event.getKeys(['q'])): myWin.close() core.quit()
def main(win, globalClock): all_changes = [] ################################ Stimuli prepation ################################ globalClock = core.Clock() # Make two wedges (in opposite contrast) and alternate them for flashing grating_texture = np.tile([[1, -1], [-1, 1]], (8, 8)) wedge1 = visual.RadialStim( win, tex=grating_texture, color=1, units='pix', size=win.size[1] * 1.3, visibleWedge=[Initial_wedge_pos, Initial_wedge_pos + Wedge_width], interpolate=False, autoLog=False, radialCycles=1, angularCycles=4) wedge2 = copy.copy(wedge1) wedge2.color = -1 # fixation cross fixation = visual.ShapeStim(win, vertices=((0, -1), (0, 1), (0, 0), (-1, 0), (1, 0)), lineWidth=4, units="pix", size=(20, 20), closeShape=False, lineColor='red', autoDraw=True) # Spyder network web_circle = visual.Circle(win=win, radius=1, edges=200, units='norm', pos=[0, 0], lineWidth=1, opacity=1, interpolate=True, lineColor=[1.0, 1.0, 1.0], lineColorSpace='rgb', fillColor=None, fillColorSpace='rgb') web_dimension = (screenCorrection(win, Web_size[0]), Web_size[1]) web_line = visual.Line(win, name='Line', start=(-1.4, 0), end=(1.4, 0), pos=[0, 0], lineWidth=1, lineColor=[1.0, 1.0, 1.0], lineColorSpace='rgb', opacity=1, interpolate=True) # DEBUG stimuli if DEBUG_MODE: fps_text = visual.TextStim(win, units='norm', height=0.05, pos=(-0.98, +0.93), text='starting...', font=sans, alignHoriz='left', alignVert='bottom', color='yellow') fps_text.autoDraw = True orientation_details_string = visual.TextStim(win, text=u"eccentricity..", units='norm', height=0.05, pos=(0.95, +0.93), alignHoriz='right', alignVert='bottom', font=sans, color='yellow') orientation_details_string.autoDraw = True # External Aperture (black ring) external_aperture_size = tuple( [screenCorrection(win, External_ring_size), External_ring_size]) external_aperture = visual.Aperture(win, size=external_aperture_size, shape='circle') external_aperture.enabled = False ################################ Definitions/Functions ################################ ## handle Rkey presses each frame def escapeCondition(which_key): for key in event.getKeys(): if key in ['escape', 'q', which_key]: return False return True ################################ Animation starts ################################ # display instructions and wait message1 = visual.TextStim(win, pos=[0, 0.5], text='Hit a key when ready.') message1.draw() fixation.draw() win.flip() event.waitKeys() #pause until there's a keypress # Scanner trigger wait message3 = visual.TextStim(win, pos=[0, 0.25], text=scanner_message, font=serif, alignVert='center', wrapWidth=1.5) message3.size = .5 message3.draw() win.flip() if BUTTON_BOX: button_state = button_thread.button_state while 1: if (button_state['state'][-1] == 0): break if escapeCondition('f') == False: break else: event.waitKeys() #pause until there's a keypress # Wait Pre_post_stimuli_fixation_time before stimuli # Spyder network if Spyder_grid: for i_dim in range(Spyder_rings): web_circle.setSize( tuple([ x * (i_dim + 1) * 1. / Spyder_rings for x in web_dimension ])) web_circle.draw() for i_dim in range(2): web_line.setOri(i_dim * 90) web_line.draw() win.flip() core.wait(Pre_post_stimuli_fixation_time) t = i_cycle = last_fps_update = 0 break_flag = True globalClock.reset() inizio = globalClock.getTime() logging.data('First cycle. Number %d/%d at %f (sec.)' % (i_cycle + 1, Cycles_number, inizio)) while (globalClock.getTime() < Total_time and break_flag == True): t = globalClock.getTime() # Spyder network if Spyder_grid: for i_dim in range(Spyder_rings): web_circle.setSize( tuple([ x * (i_dim + 1) * 1. / Spyder_rings for x in web_dimension ])) web_circle.draw() for i_dim in range(2): web_line.setOri(i_dim * 90) web_line.draw() # External ring external_aperture.enabled = True # Setup stimulus if t % Flash_period < Flash_period / 2.0: # more accurate to count frames stim = wedge1 else: stim = wedge2 stim.ori = -t * Rotation_rate * 360.0 # set new rotation stim.draw() # Fixation if Rotating_cross: fixation.ori = t * Rotation_cross_rate * 360.0 # set new rotation if Color_change_cross: if t % Color_change_rate < Color_change_rate / 2.0: # more accurate to count frames fixation.lineColor = 'red' else: fixation.lineColor = 'green' if (t >= ((i_cycle + 1) * 1 / Rotation_rate)): logging.data('Change orientation. Number %d/%d at %f (sec.)' % (i_cycle + 1, Cycles_number, t)) new_record = globalClock.getTime() - sum(all_changes) all_changes.append(new_record) i_cycle += 1 external_aperture.enabled = False if DEBUG_MODE: if t - last_fps_update > Fps_update_rate: # update the fps text every second fps_text.text = "%.2f fps" % win.fps() last_fps_update += 1 orientation_details_string.text = 'Pass: %d/%d at %.3f (sec.)' % ( i_cycle + 1, Cycles_number, np.sum(all_changes)) win.flip() break_flag = escapeCondition('f') if break_flag == False: break logging.data('Total time spent: %.6f' % (globalClock.getTime() - inizio)) logging.data('Every frame duration saved in %s' % (path_out + Frames_durations_name)) logging.data('All durations: ' + str(all_changes)) logging.data('Mean: ' + str(sum(all_changes) / (len(all_changes) + EPSILON))) if DEBUG_MODE: orientation_details_string.text = 'Ended at %.3f (sec.)' % ( globalClock.getTime()) win.flip() # Wait Pre_post_stimuli_fixation_time after stimuli core.wait(Pre_post_stimuli_fixation_time) return
def manual_events(recording_path, trial_duration, skiptime=0, events={}, bracket="", volume=1., output_path="~/evaluation", non_redundant=True): """Evaluate a behavioural recording. E.g. for forced swim test, press enter when the experiment starts and s/i to annotate the onset of the respective behaviours. !!! Add optional instructons screen !!! Mention e.g. that you need to press return to start the evaluation period. Parameters ---------- recording_path: str Path to the recording file. trial_duration: int Number of seconds the trial should last after pressing the evaluation start key. """ recording_path = os.path.expanduser(recording_path) csv_output_path = os.path.expanduser(output_path) + ".csv" if os.path.exists(csv_output_path): print("There is already an evaluation file at " + csv_output_path + ". Please specify a different output path.") return keylist = ['left', 'right', 'up', 'down', 'escape', 'return'] experiment_keylist = events.keys() keylist.extend(experiment_keylist) #process brackets string x_y_brackets = bracket.split(",") if len(x_y_brackets) == 1 and x_y_brackets != [""]: x0, x1 = [float(i) / 100 for i in x_y_brackets[0].split("-")] y0 = 0. y1 = 1. elif len(x_y_brackets) == 2: try: x0, x1 = [float(i) / 100 for i in x_y_brackets[0].split("-")] except ValueError: x0 = 0. x1 = 1. try: y0, y1 = [float(i) / 100 for i in x_y_brackets[1].split("-")] except ValueError: y0 = 0. y1 = 1. else: x0 = y0 = 0. x1 = y1 = 1. win = visual.Window( size=(2048, 1152), fullscr=True, screen=0, allowGUI=False, allowStencil=True, monitor='testMonitor', color=[0, 0, 0], colorSpace='rgb', blendMode='avg', useFBO=True, units="norm", ) # Initialize components for Routine "trial" trialClock = core.Clock() mov = visual.MovieStim3( win=win, name='mov', filename=recording_path, depth=0.0, volume=volume, units="norm", ) #set full_movie dimensions based on aspect ratio (making one value from each operation float to ensure float division) if float(mov.size[0]) / mov.size[1] < float(win.size[0]) / win.size[1]: full_movie_y = 2. full_movie_x = 2 * float( win.size[1]) / win.size[0] / (float(mov.size[1]) / mov.size[0]) else: full_movie_y = 2 * float( win.size[1]) / win.size[0] / (float(mov.size[1]) / mov.size[0]) full_movie_x = 2. mov.size = (full_movie_x, full_movie_y) #position video so as to center bracket mov_xsize, mov_ysize = mov.size movsection_xpos = (x0 + x1) / 2. movsection_ypos = (y0 + y1) / 2. mov_xpos = mov_xsize * (1. / 2. - movsection_xpos) mov_ypos = mov_ysize * (1. / 2. - movsection_ypos) mov.pos = (mov_xpos, mov_ypos) #set aperture to bracket movsection_xsize = x1 - x0 movsection_ysize = y1 - y0 aperture_x = movsection_xsize * mov_xsize aperture_y = movsection_ysize * mov_ysize aperture = visual.Aperture(win, size=(aperture_x, aperture_y), pos=(0, 0), ori=0, nVert=120, shape='square', inverted=False, name=None, autoLog=None) # Create some handy timers globalClock = core.Clock() # to track the time since measurement started trialClock = core.Clock() # to track the time since measurement started routineTimer = core.CountdownTimer( ) # to track time remaining in the experiment # prepare evaluation variables mov.status = NOT_STARTED pre_evaluation = True outputWriter1 = csv.writer(open(csv_output_path, 'w'), lineterminator='\n') outputWriter1.writerow(["start", "behaviour"]) lastkey = "" # Start evaluation globalClock.reset() while pre_evaluation or routineTimer.getTime() > 0: T = globalClock.getTime() t = trialClock.getTime() resp_key = event.getKeys(keyList=keylist) if pre_evaluation: if "return" in resp_key: trialClock.reset() routineTimer.reset() routineTimer.add(trial_duration) pre_evaluation = False elif resp_key: if resp_key[0] in experiment_keylist: if non_redundant and resp_key[0] == lastkey: pass else: tempArray = [t, events[resp_key[0]]] outputWriter1.writerow(tempArray) tempArray = [] # make sure no info persists lastkey = resp_key[0] #start movie and keep track of start time if t >= 0.0 and mov.status == NOT_STARTED: mov.tStart = T mov.setAutoDraw(True) if mov.status == FINISHED: break # check for quit (the Esc key) if "escape" in resp_key: outputWriter1.writerow([t, "QUIT"]) core.quit() win.flip() outputWriter1.writerow([t, "END"]) if mov.status != FINISHED: mov.setAutoDraw(False) win.close() return
) else: tk.sendCommand( "file_sample_data = LEFT,RIGHT,GAZE,AREA,GAZERES,STATUS,INPUT") tk.sendCommand( "link_sample_data = LEFT,RIGHT,GAZE,GAZERES,AREA,STATUS,INPUT") # STEP VI: specify all possible experimental cells & prepare the visual stimuli # one may read in a spreadsheet containing all the experimentl cells; usually, a simple list # should also do the job; if we need tweenty trials, simple go with "new_list = trials[:]*10", then # random.shuffle(new_list) trials = [['mask', 'sacrmeto.jpg'], ['window', 'sacrmeto.jpg']] # prepare a circular aperture as a gaze-contingent window gazeWindow = visual.Aperture(win, size=200) gazeWindow.enabled = False # prepare a gaze-contingent mask gazeMask = visual.GratingStim(win, tex='none', mask='circle', size=200, color=[1.0, 1.0, 1.0]) # SETP VII: a couple of helpers def runTrial(pars): """ pars corresponds to a row in the trial list""" # retrieve paramters from the trial list, in the "mask" condition we simply draw mask #at the current gaze position
from psychopy import visual, core, event win = visual.Window([400, 400], allowStencil=True, units='norm') gabor1 = visual.GratingStim(win, mask='circle', pos=[0.2, 0.2], sf=4, size=.4, color=[0.5, -0.5, 1]) gabor2 = visual.GratingStim(win, mask='circle', pos=[-0.2, -0.2], sf=4, size=.4, color=[-0.5, -0.5, -1]) aperture = visual.Aperture(win, size=.5, pos=[0.16, 0.16], shape='square') aperture.enable() #actually is enabled by default when created gabor1.draw() aperture.disable() #drawing from here ignores aperture gabor2.draw() win.flip() event.waitKeys()
def main(argv=sys.argv): data_dir_root = '/Users/Eichenbaum/HWNI/Experiments/HRL_Geometry/scripts/psychopy' #'/Users/despolabtesting1/Desktop/Experiments/eichenbaum/HRL_Geo_Barker135' # '/Users/StimulusMac/Desktop/Experiments/DespoLab/adameich/HRL' d = 57 # Distance between monitor and participant in cm ## This scales everything that is coded in degrees! my_monitor = monitors.Monitor(name='DesktopLab') my_monitor.setSizePix( (1920, 1080) ) # (2560,1440) (1024, 768) (1920,1080)## This scales everything that is coded in degrees! my_monitor.setDistance( d) ## This scales everything that is coded in degrees! my_monitor.setWidth( 50.88) ## This scales everything that is coded in degrees! my_monitor.saveMon() monitor = 'DesktopLab' res = [1024, 768] # [1920,1080] [1024, 768] #################### ### Subject Info ### #################### SubID = str(sys.argv[1]) Age = int(sys.argv[2]) Gen = str(sys.argv[3]) Run = int(sys.argv[4]) Date = str(data.getDateStr()) CounterBalanceNumber = int(SubID) % 4 if Run % 2 == 1: flipOrder = False #(AB) flipOrderStr = 'AB' elif Run % 2 == 0: flipOrder = True #(BA) flipOrderStr = 'BA' print(flipOrder, flipOrderStr) win = visual.Window(res, fullscr=False, monitor=monitor, color=[-.375, -.375, -.375], units='deg', allowStencil=True) # Shared Parameter Values# h = 28.62 # 34.544#3.6221171854 # Monitor height in cm r = win.size[1] # Vertical resolution of the monitor deg_per_px = degrees(atan2(.5 * h, d)) / ( .5 * r ) # Calculate the number of degrees that correspond to a single pixel. This will generally be a very small value, something like 0.03. ######################################################################### ######## Data file creation for trial info and user response info ####### ######################################################################### file_name = SubID + '_HRL_Geo_Run_' + str(Run) complete_file_name = os.path.join(data_dir_root, 'data', file_name + '.csv') HRLDataFile = open(complete_file_name, 'w') firstLine = [ 'DateTime', ### The Date and Time at the start of the session. 'SubID', ### The 3-digit numeric code given to the participant 'Gen', ### One of three options: M = 'Male', F = 'Female', O = 'Other' 'Age', ### The age of the participant 'Run', ### The current block of the expt (currently set to 5 blocks) 'CntrBalNum', ### CounterBalanceNumber 'StimSet', ### Which of the two stimulus sets are they seeing? 'RuleOrder', ### OVERALL SoT-CoT or CoT-SoT 'FlipRunOrder', ### Within this run, is the RuleOrder going to be normal (AB) or flipped (BA)? 'RunOrderStr', ### The actual string of "AB" or "BA" 'Trial', ### Trial number 'ShapeName', ### The shape stimulus that was shown on the current trial. Will be a string. 'ShapeNumber', ### The 0 or 1 in the design matrix 'ColorName', ### The color of the border that was shown on the current trial. Will be a string. 'ColorNumber', ### The 0 or 1 in the design matrix 'TextureName', ### The texture image that was shown on the current trial. Will be an integer. 'TextureNumber', ### The 0 or 1 in the design matrix 'Rule', ### Shape-on-Top or Color-on-Top 'StimIdx', ### The number associated with that stimulus conjunction. This will be a good cross check to make sure that everything is coding appropriately for e.g. numSince 'Iter', ### Provides how many times the stimulus conjuction has been previously seen this block. 'Response', ### The keyboard input provided by the Pp for the current trial. Will be a string of either '1', '2', '3', or '4', or 'NaN' if no input detected. 'tAns', ### The correct answer for the current trial. Will be a string of either 'v', 'b', 'n', or 'm'. 'tOut', ### Whether the response was Correct (1), Incorrect (-1), or No Response provided ('Nan') 'TrialRT', ### Reaction time from Stimulus onset to response. Will be a float. If no response provided, will be a "NaN" string. 'PlannedITI', 'StimDur', 'StimOnset', ### The time when the stimulus is presented on screen, relative the the first TTL pulse. 'RespOnset', ### The time when the first button press response to the stimulus occured, relative to the first TTL pulse received. 'StimOffset', ### The time when the stimulus goes off screen (should be identical to "confOff") 'ITIOn' ] firstLine = ','.join(firstLine) HRLDataFile.write(firstLine + '\n') ######################## ## Drawing Parameters ## ######################## wrapWidth = 22.5 line_width_val = 0.0 stim_rad = 5.0 * .8 opa_val = 1.0 rect_width = 10.0 * .8 rect_height = 10.0 * .8 size = .4 # How large everything will be from the aperture perspective border_size_boost = 1.3 text_win_size = .55 ### Colors ### RGB_colors = (np.array([(255, 0, 0), (0, 0, 255), (255, 255, 0), (255, 0, 255)]) - 127.5) / 127.5 tutorial_colors = (np.array([(0, 255, 0), (0, 0, 128)]) - 127.5) / 127.5 rgbNames = np.array(['red', 'blue', 'yellow', 'magenta']) ### Texture Images used ### texIdx_array = np.array((11, 37, 65, 87)) ############################### ## Block Creation Parameters ## ############################### nDims = 3 nTrials = 32 ####################### ## Timing Parameters ## ####################### fmri_wait_time = 5 schematicViewTime = 15 RespDurLimit = 2.0 # 120 frames will allow the subject to respond within a 2000ms window. myOverallClock = core.Clock( ) # clock used for overal stimulus timing throughout the experiment TTL_start_clock = core.Clock() timingClock = core.Clock() StimOnset = np.ones(nTrials) * np.nan ITIOn = np.ones(nTrials) * np.nan ITI_base = [5, 6, 6, 7] ITIs_tmp_a = [] ITIs_tmp_b = [] ITIs_tmp_a = np.tile(ITI_base, [4]) ITIs_tmp_b = np.tile(ITI_base, [4]) np.random.shuffle(ITIs_tmp_a) np.random.shuffle(ITIs_tmp_b) ITIs = np.hstack((ITIs_tmp_a, ITIs_tmp_b)) ######################### ## Response Parameters ## ######################### keyState = key.KeyStateHandler() win.winHandle.push_handlers(keyState) answer_keys_bBox = ['1', '2', '3', '4'] answer_keys = ['v', 'b', 'n', 'm'] totalCorrect = 0 ######################## ## Circle Stimulus ## ######################## circle = visual.Circle(win, size=size, radius=stim_rad, units='deg', lineWidth=line_width_val, edges=256, opacity=opa_val, interpolate=True, name='circle') circle2 = visual.Circle(win, size=size * border_size_boost * 0.9, radius=stim_rad, units='deg', lineWidth=line_width_val, edges=256, opacity=opa_val, interpolate=True) circle3 = visual.Circle(win, size=size * 0.8, radius=stim_rad, units='deg', lineWidth=line_width_val, edges=256, opacity=opa_val, interpolate=True) ######################## ## Square Stimulus ## ######################## square = visual.Rect(win, size=size, width=rect_width, height=rect_height, units='deg', lineWidth=line_width_val, opacity=opa_val, interpolate=True, name='square') square2 = visual.Rect(win, size=size * border_size_boost * 0.9, width=rect_width, height=rect_height, units='deg', lineWidth=line_width_val, opacity=opa_val, interpolate=True) square3 = visual.Rect(win, size=size * .8, width=rect_width, height=rect_height, units='deg', lineWidth=line_width_val, opacity=opa_val, interpolate=True) ######################## ## Triangle Stimulus ## ######################## triangle = visual.Polygon(win, size=size, edges=3.0, radius=6.0, units='deg', pos=(0, -.1), lineWidth=line_width_val, opacity=opa_val, interpolate=True, name='triangle') triangle2 = visual.Polygon(win, size=size * border_size_boost * .95, edges=3.0, radius=6.0, units='deg', pos=(0, -.1), lineWidth=line_width_val, opacity=opa_val, interpolate=True) triangle3 = visual.Polygon(win, size=size * .8, edges=3.0, radius=6.0, units='deg', pos=(0, -.25), lineWidth=line_width_val, opacity=opa_val, interpolate=True) ######################## ## Pentagon Stimulus ## ######################## pentagon = visual.Polygon(win, size=size, edges=5.0, radius=stim_rad, units='deg', lineWidth=line_width_val, opacity=opa_val, interpolate=True, name='pentagon') pentagon2 = visual.Polygon(win, size=size * border_size_boost * 0.925, edges=5.0, radius=stim_rad, units='deg', lineWidth=line_width_val, opacity=opa_val, interpolate=True) pentagon3 = visual.Polygon(win, size=size * .8, edges=5.0, radius=stim_rad, units='deg', lineWidth=line_width_val, opacity=opa_val, interpolate=True) ##################### ## Wash-out Filter ## ##################### washOut_Filter = visual.Rect(win, width=win.size[0], height=win.size[1], units='pix', lineWidth=line_width_val, fillColor=[1, 1, 1], opacity=.1, interpolate=True) ##################### ## (Tut.) Diamond ## ##################### vees = np.ones((4, 2)) * np.nan for idx, tmp in enumerate([0, 90, 180, 270]): vees[idx, 0] = ((5 * np.sqrt(2)) * np.sin(np.deg2rad(tmp))) vees[idx, 1] = ((5 * np.sqrt(2)) * np.cos(np.deg2rad(tmp))) diamond = visual.Rect(win, size=size * .8, width=rect_width, height=rect_height, units='deg', lineWidth=line_width_val, opacity=opa_val, ori=45.0, interpolate=True, name='diamond') diamond2 = visual.Rect(win, size=.9 * (size * border_size_boost * 0.95), width=rect_width, height=rect_height, units='deg', lineWidth=line_width_val, opacity=opa_val, ori=45.0, interpolate=True) ##################### ## (Tut.) Octagon ## ##################### octagon = visual.Polygon(win, size=size, edges=8.0, radius=stim_rad, units='deg', lineWidth=line_width_val, opacity=opa_val, interpolate=True, name='pentagon') octagon2 = visual.Polygon(win, size=size * border_size_boost * 0.9, edges=8.0, radius=stim_rad, units='deg', lineWidth=line_width_val, opacity=opa_val, interpolate=True) ### Create Fixation Cross ### fixation1w = visual.Line(win, units='deg', start=(0, -0.35), end=(0, 0.35), lineColor='white', lineWidth=4) fixation2w = visual.Line(win, units='deg', start=(-0.35, 0), end=(0.35, 0), lineColor='white', lineWidth=4) ### Shapes ### shape_list = [circle, square, triangle, pentagon] shape_list2 = [circle2, square2, triangle2, pentagon2] shape_list3 = [circle3, square3, triangle3, pentagon3] Shape_list_wTut = [octagon, diamond] Shape_list_wTut2 = [octagon2, diamond2] if CounterBalanceNumber in [0, 2]: StimSet = 1 # Circle-Triangle, Blue-Yellow else: StimSet = 2 # Square-Pentagon, Red-Pink if CounterBalanceNumber == 0: CB_vals = [1, 2] currShapeList1 = [circle, triangle] currShapeList2 = [circle2, triangle2] currShapeList3 = [circle3, triangle3] order = 0 # Sot-Cot elif CounterBalanceNumber == 1: CB_vals = [0, 3] currShapeList1 = [square, pentagon] currShapeList2 = [square2, pentagon2] currShapeList3 = [square3, pentagon3] order = 0 # Sot-Cot elif CounterBalanceNumber == 2: CB_vals = [1, 2] currShapeList1 = [circle, triangle] currShapeList2 = [circle2, triangle2] currShapeList3 = [circle3, triangle3] order = 1 # Cot-Sot elif CounterBalanceNumber == 3: CB_vals = [0, 3] currShapeList1 = [square, pentagon] currShapeList2 = [square2, pentagon2] currShapeList3 = [square3, pentagon3] order = 1 # Cot-Sot currTexList = texIdx_array[CB_vals] currColList = RGB_colors[CB_vals] currColNameList = rgbNames[CB_vals] ########################################################################## ## Construct General Trial Array for Rule / Context #1 ==> Shape on Top ## ########################################################################## trialArray_SoT = np.zeros((2**nDims, 5)) * np.nan trialArray_SoT[:, 0] = [0, 0, 0, 0, 1, 1, 1, 1] # Shape trialArray_SoT[:, 1] = [0, 0, 1, 1, 0, 0, 1, 1] # Color trialArray_SoT[:, 2] = [0, 1, 0, 1, 0, 1, 0, 1] # Texture trialArray_SoT[:, 3] = [0, 0, 2, 2, 1, 3, 1, 3] # Answer # trialArray_SoT[:, 4] = [0, 1, 2, 3, 4, 5, 6, 7] # Unique Stim ID ########################################################################## ## Construct General Trial Array for Rule / Context #2 ==> Color on Top ## ########################################################################## trialArray_CoT = np.zeros((2**nDims, 5)) * np.nan trialArray_CoT[:, 0] = [0, 0, 0, 0, 1, 1, 1, 1] # Shape trialArray_CoT[:, 1] = [0, 0, 1, 1, 0, 0, 1, 1] # Color trialArray_CoT[:, 2] = [0, 1, 0, 1, 0, 1, 0, 1] # Texture trialArray_CoT[:, 3] = [2, 1, 3, 3, 2, 1, 0, 0] # Answer trialArray_CoT[:, 4] = [0, 1, 2, 3, 4, 5, 6, 7] # Unique Stim ID ########################### ## Construct Trial Array ## ########################### trialArray_SoT_Total = np.zeros( (trialArray_SoT.shape[0] * 2, trialArray_SoT.shape[1])) * np.nan tmp_SoT_a = [] tmp_SoT_a = np.tile(trialArray_SoT[:, :], [2, 1]) np.random.shuffle(tmp_SoT_a) trialArray_SoT_Total[0:16, :] = tmp_SoT_a trialArray_CoT_Total = np.zeros( (trialArray_CoT.shape[0] * 2, trialArray_CoT.shape[1])) * np.nan tmp_CoT_a = [] tmp_CoT_a = np.tile(trialArray_CoT[:, :], [2, 1]) np.random.shuffle(tmp_CoT_a) trialArray_CoT_Total[0:16, :] = tmp_CoT_a Full_Trial_Array = np.zeros((nTrials, 5)) * np.nan if CounterBalanceNumber in [0, 1]: if flipOrder: Full_Trial_Array[0:16, :] = trialArray_CoT_Total Full_Trial_Array[16:32, :] = trialArray_SoT_Total stringCurrRule1 = 'Color-on-Top' stringCurrRule2 = 'Shape-on-Top' else: Full_Trial_Array[0:16, :] = trialArray_SoT_Total Full_Trial_Array[16:32, :] = trialArray_CoT_Total stringCurrRule1 = 'Shape-on-Top' stringCurrRule2 = 'Color-on-Top' elif CounterBalanceNumber in [2, 3]: if flipOrder: Full_Trial_Array[0:16, :] = trialArray_SoT_Total Full_Trial_Array[16:32, :] = trialArray_CoT_Total stringCurrRule1 = 'Shape-on-Top' stringCurrRule2 = 'Color-on-Top' else: Full_Trial_Array[0:16, :] = trialArray_CoT_Total Full_Trial_Array[16:32, :] = trialArray_SoT_Total stringCurrRule1 = 'Color-on-Top' stringCurrRule2 = 'Shape-on-Top' ####################################################### ### Build Trial Duration and Trial Start-Time Array ### ####################################################### trial_durations = (np.ones(nTrials) * RespDurLimit) + ITIs trial_starts_matrix = np.ones((nTrials)) * np.nan for i in range(nTrials): if i == 0: trial_starts_matrix[i] = 0 else: trial_starts_matrix[i] = (trial_starts_matrix[i - 1] + trial_durations[i - 1]) trial_starts_matrix += fmri_wait_time trial_starts_matrix += 2 # The amount of time the 1st rule is on screen trial_starts_matrix += 7 # The ITI between the 1st Rule going off screen and Trial #1 starting trial_starts_matrix[ 16:32] += 2 # The amount of time the 2nd rule is on screen trial_starts_matrix[ 16: 32] += 7 # The ITI between the 2nd Rule going off screen and Trial #33 starting print(trial_starts_matrix) win.mouseVisible = False experimentStart = myOverallClock.getTime() ###################################### ######### INSTRUCTIONS START ######### ###################################### if CounterBalanceNumber in [0, 2]: schematicSoT = "Schematic_1_SoT.tiff" schematicCoT = "Schematic_1_CoT.tiff" else: schematicSoT = "Schematic_2_SoT.tiff" schematicCoT = "Schematic_2_CoT.tiff" for i in range(schematicViewTime): visual.TextStim(win, height=text_win_size, wrapWidth=wrapWidth, text='RULE REMINDER [%s] seconds remain' % (str(schematicViewTime - i))).draw() visual.ImageStim(win, image=schematicSoT, pos=[0, 6]).draw() visual.ImageStim(win, image=schematicCoT, pos=[0, -6]).draw() win.flip() core.wait(1) visual.TextStim(win, height=text_win_size, wrapWidth=wrapWidth, text='We are now starting the task.\n\n\ No feedback will be provided after each response. Instead you will be told of your overall accuracy at the end of each run.\n\n\ Press Any Key to Begin').draw() win.flip() core.wait(1) event.waitKeys() ####################### ### Waiting for TTL ### ####################### visual.TextStim( win, height=text_win_size, wrapWidth=wrapWidth, text= 'Scanner is warming up and making adjustments. It is crucial that you stay as still as you can.' ).draw() win.flip() event.waitKeys(keyList='5') fixation1w.draw(), fixation2w.draw() win.flip() TTL_start_clock.reset() core.wait(fmri_wait_time) ################################# ### Display text for 1st Rule ### ################################# currentRule = stringCurrRule1 visual.TextStim(win, height=text_win_size, wrapWidth=wrapWidth, text='CURRENT RULE\n\n\ %s' % (stringCurrRule1)).draw() win.flip() core.wait(2) win.flip() core.wait(7) ######################## ### START TRIAL LOOP ### ######################## for trial in np.arange(Full_Trial_Array.shape[0]): buttonPressed = None resp, Resp1Time, Response1_On = False, False, False #################################### ### Display text for Rule Change ### #################################### if trial == 16: currentRule = stringCurrRule2 visual.TextStim(win, height=text_win_size, wrapWidth=wrapWidth, text='RULE CHANGE\n\n\ %s' % (stringCurrRule2)).draw() win.flip() core.wait(2) win.flip() core.wait(7) ######################################################################### ### Determine which shape, color, texture will be drawn on this trial ### ######################################################################### shape_val = currShapeList1[Full_Trial_Array[trial, 0].astype(int)] shape_val_brd = currShapeList2[Full_Trial_Array[trial, 0].astype(int)] shape_val.setPos([0, 0]) shape_val_brd.setPos([0, 0]) ### Create the Texture Visual Patch ### imgstim = visual.ImageStim( win=win, pos=shape_val.pos, size=[10, 10], image=os.path.join( data_dir_root, 'Normalized_Brodatz/D%s.tif' % (currTexList[Full_Trial_Array[trial, 2].astype(int)]))) ### Fill the to-be Border Shape with the current trial's color ### shape_val_brd.setFillColor( currColList[Full_Trial_Array[trial, 1].astype(int), :], 'rgb') ### Create the aperture so that the texture seen is only in the size and shape of the current shape ### aperture = visual.Aperture(win, size=size, shape=shape_val.vertices, pos=shape_val.pos) aperture.enabled = False ############################################### ### Waiting for previous trial's ITI to end ### ############################################### while TTL_start_clock.getTime() < trial_starts_matrix[trial]: pass ############################## ### Start Drawing Stimulus ### ############################## event.clearEvents() Pres_stim(shape_val_brd, aperture, imgstim, washOut_Filter) win.flip() StimOnset[trial] = TTL_start_clock.getTime( ) #The stimulus has just been shown. Onset time is measured as the first moment after the window was flipped timingClock.reset() while timingClock.getTime() <= RespDurLimit: if resp: break if not resp: allKeys = event.getKeys() ### Check to see if the response made is one of the allowable responses for thisKey in allKeys: if thisKey in answer_keys_bBox: Resp1Time = timingClock.getTime( ) # Within Trial time of response to the stimulus Response1_On = TTL_start_clock.getTime( ) # Time of response to the stimulus relative to the start of the 1st TTL pulse if thisKey == answer_keys_bBox[Full_Trial_Array[ trial, 3].astype(int)]: resp, buttonPressed = 1, thisKey #Correct totalCorrect += 1 else: resp, buttonPressed = -1, thisKey #Incorrect if thisKey in ['escape']: win.close() core.quit() ########################################################## ### Remove Stimulus from Screen after full 2s duration ### ########################################################## while timingClock.getTime() <= RespDurLimit: pass win.flip() stimOffset = TTL_start_clock.getTime( ) # This stimulus has just left the screen. The ITI has begun. ################################# ### Intertrial Interval Start ### ################################# ITIOn[trial] = TTL_start_clock.getTime() if trial != Full_Trial_Array.shape[0] - 1: fixation1w.draw(), fixation2w.draw() win.flip() iteration = list(Full_Trial_Array[:trial, 4]).count( Full_Trial_Array[trial, 4]) + 1 stimDur = stimOffset - StimOnset[trial] ########################################################################## ### Data file update for trial & user response info: Saved every trial ### ########################################################################## trialDataArray = map( str, [ Date, # DateTime SubID, # SubID Gen, # Gen Age, # Age Run, # Run CounterBalanceNumber, # CntrBalNum StimSet, # StimSet: Which of the two stim sets are being used for this Pp? order, # RuleOrder: What is the overall SoT-CoT v CoT-SoT order flipOrder, # RunOrder: AB or BA, i.e. is the RuleOrder flipped for this run? flipOrderStr, # The actual string of either AB or BA trial + 1, # Trial number shape_val.name, # Shape name int(Full_Trial_Array[trial, 0]), # Shape Number currColNameList[int(Full_Trial_Array[trial, 1])], # Color name int(Full_Trial_Array[trial, 1]), # Color Number currTexList[int(Full_Trial_Array[trial, 2])], # Texture name int(Full_Trial_Array[trial, 2]), # Texture number currentRule, Full_Trial_Array[trial, 4], # Stim Idx iteration, buttonPressed, answer_keys_bBox[Full_Trial_Array[trial, 3].astype(int)], resp, Resp1Time, #RT ITIs[trial], stimDur, StimOnset[trial], # Relative to the TTL pulse (the 1st one) Response1_On, stimOffset, ITIOn[trial] ]) trialDataArray = ','.join(trialDataArray) HRLDataFile.write(trialDataArray + '\n') os.fsync(HRLDataFile) HRLDataFile.flush() ################################################################# ################# ### Final ITI ### ################# core.wait(ITIs[trial]) ### 133 TRs VALUE = str(np.round(np.divide(totalCorrect, 32) * 100, 2)) visual.TextStim(win, height=text_win_size, wrapWidth=wrapWidth, text='Your response accuracy was ' + VALUE + '%.' + ' Press Any Key to Close').draw() win.flip() event.waitKeys() experimentEnd = myOverallClock.getTime() print("Total Experiment Time was %s" % (experimentEnd - experimentStart)) HRLDataFile.close() win.mouseVisible = True win.close() core.quit()
from psychopy import visual, core, event win = visual.Window([400, 400], allowStencil=True) gabor1 = visual.PatchStim(win, mask='circle', pos=[0.3, 0.3], sf=4, size=1, color=[0.5, -0.5, 1]) gabor2 = visual.PatchStim(win, mask='circle', pos=[-0.3, -0.3], sf=4, size=1, color=[-0.5, -0.5, -1]) aperture = visual.Aperture(win, size=200, pos=[20, 0]) aperture.enable() #actually is enabled by default when created gabor1.draw() aperture.disable() #drawing from here ignores aperture gabor2.draw() win.flip() event.waitKeys()
pos=(400, 400)) triggerText = visual.TextStim( win=myWin, autoLog=False, color='white', height=30, text='Experiment will start soon. \n Waiting for scanner', ) targetText = visual.TextStim(win=myWin, autoLog=False, color='white', height=30) vertices = [(pixCover / 2, pixCover / 2), (-pixCover / 2, pixCover / 2), (-pixCover / 2, -pixCover / 2), (pixCover / 2, -pixCover / 2)] aperture = visual.Aperture(myWin, autoLog=False, shape=vertices) # try shape='square' aperture.enabled = False # %% """TIME AND TIMING PARAMETERS""" # get screen refresh rate refr_rate = myWin.getActualFrameRate() # get screen refresh rate if refr_rate is not None: frameDur = 1.0 / round(refr_rate) else: frameDur = 1.0 / 60.0 # couldn't get a reliable measure so guess logFile.write('RefreshRate=' + unicode(refr_rate) + '\n') logFile.write('FrameDuration=' + unicode(frameDur) + '\n') # set durations
coords = f"screen_pixel_coords = 0 0 {SCN_W - 1} {SCN_H - 1}" tk.sendCommand(coords) # Request Pylink to use the custom EyeLinkCoreGraphicsPsychoPy library # to draw calibration graphics (target, camera image, etc.) genv = EyeLinkCoreGraphicsPsychoPy(tk, win) pylink.openGraphicsEx(genv) # Calibrate the tracker calib_msg = visual.TextStim(win, text='Press ENTER to calibrate') calib_msg.draw() win.flip() tk.doTrackerSetup() # Set up a aperture and use it as a gaze-contingent window gaze_window = visual.Aperture(win, shape='square', size=200) gaze_window.enabled = True # Load a background image to fill up the screen img = visual.ImageStim(win, image='woods.jpg', size=(SCN_W, SCN_H)) # Put tracker in Offline mode before we start recording tk.setOfflineMode() # Start recording tk.startRecording(1, 1, 1, 1) # Cache some samples pylink.msecDelay(100) # show the image indefinitely until a key is pressed
myWin, width=2 * horiDist - squareSize, height=2 * vertiDist - squareSize, autoLog=False, name='rectStim', units='deg', fillColor=backColor, lineColor=backColor, ) vertices = [(horiDist + 0.5 * squareSize, vertiDist + 0.5 * squareSize), (-(horiDist + 0.5 * squareSize), vertiDist + 0.5 * squareSize), (-(horiDist + 0.5 * squareSize), -(vertiDist + 0.5 * squareSize)), (horiDist + 0.5 * squareSize, -(vertiDist + 0.5 * squareSize))] aperture = visual.Aperture(myWin, units='deg', autoLog=False, shape=vertices) aperture.enabled = False dotPatch = visual.ElementArrayStim( myWin, autoLog=False, elementTex=None, name='dotPatch', elementMask='circle', nElements=int(nDots), sizes=dotSize, units='deg', colors=dotColor, xys=None, fieldShape='square', fieldSize=(dimX * 2, dimY * 2),
def main(win, globalClock): all_changes = [] ################################ Stimuli prepation ################################ # Bar preparation grating_texture = np.tile([[1,-1],[-1,1]], (1,12))#(4,64)) grating_texture = np.dstack((grating_texture,grating_texture,grating_texture)) bar_size = (Bar_length[0]*resX,Bar_length[1]*resY) grating_1 = visual.GratingStim(win,tex=grating_texture,color=[1.0, 1.0, 1.0],colorSpace='rgb', units="pix", size=bar_size,ori=0,autoLog=False,interpolate=False) grating_2 = visual.GratingStim(win,tex=~grating_texture+1,color=[1.0, 1.0, 1.0],colorSpace='rgb', units="pix", size=bar_size,ori=0,autoLog=False,interpolate=False) # Vertical shifting bar_positions = [] for i_ori in range(len(Bar_orientations)): i_x = np.linspace(Bar_paths[i_ori,0,0],Bar_paths[i_ori,1,0],Bar_positions_number).reshape((-1,1)) i_y = np.linspace(Bar_paths[i_ori,0,1],Bar_paths[i_ori,1,1],Bar_positions_number).reshape((-1,1)) position_i = np.concatenate((i_x,i_y), axis=1) position_i = position_i * resY/2 bar_positions.append(position_i) # Fixation cross preparation fixation = visual.ShapeStim(win,vertices=((0,-1),(0,1),(0,0),(-1,0),(1,0)),lineWidth=4, units="pix", size=(20,20),closeShape=False,lineColor='red',autoDraw=True) # Spyder network web_circle = visual.Circle(win=win,radius=1,edges=200,units='norm',pos=[0, 0],lineWidth=1,opacity=1,interpolate=True, lineColor=[1.0, 1.0, 1.0],lineColorSpace='rgb',fillColor=None,fillColorSpace='rgb') web_dimension = (screenCorrection(win,Web_size[0]),Web_size[1]) web_line = visual.Line(win,name='Line',start=(-1.4, 0),end=(1.4, 0),pos=[0, 0],lineWidth=1, lineColor=[1.0, 1.0, 1.0],lineColorSpace='rgb',opacity=1,interpolate=True) # DEBUG stimuli if DEBUG_MODE: fps_text = visual.TextStim(win, units='norm', height=0.05,pos=(-0.98, +0.93), text='starting...', font=sans, alignHoriz='left', alignVert='bottom', color='yellow') fps_text.autoDraw = True orientation_details_string = visual.TextStim(win, text = u"Orientation..", units='norm', height=0.05, pos=(0.95, +0.93), alignHoriz='right', alignVert='bottom', font=sans, color='yellow') orientation_details_string.autoDraw = True # External Aperture (black ring) external_aperture_size = tuple([screenCorrection(win,External_ring_size),External_ring_size]) external_aperture = visual.Aperture(win, size=external_aperture_size, shape='circle') external_aperture.enabled = False ################################ Definitions/Functions ################################ ## handle Rkey presses each frame def escapeCondition(): for key in event.getKeys(): if key in ['escape', 'q']: return False return True ################################ Animation starts ################################ # Display instructions and wait message1 = visual.TextStim(win, pos=[0,0.5],text='Hit a key when ready.') message1.draw() win.flip() event.waitKeys() #pause until there's a keypress # Scanner trigger wait message3 = visual.TextStim(win,pos=[0,0.25],text=scanner_message,font=serif,alignVert='center', wrapWidth=1.5) message3.size = .5 message3.draw() win.flip() if BUTTON_BOX: button_state = button_thread.button_state while 1: if(button_state['state'][-1]==0): break else: event.waitKeys() #pause until there's a keypress # Wait Pre_post_stimuli_fixation_time before stimuli # Spyder network if Spyder_grid: for i_dim in range(Spyder_rings): web_circle.setSize(tuple([x*(i_dim+1) * 1./Spyder_rings for x in web_dimension])) web_circle.draw() for i_dim in range(2): web_line.setOri(i_dim * 90) web_line.draw() win.flip() core.wait(Pre_post_stimuli_fixation_time) i_bar_ori = n_frame = last_fps_update = 0 globalClock.reset() inizio = globalClock.getTime() timer_global = core.CountdownTimer(Total_time) break_flag = True logging.data('First orientation. Number %d/%d at %f (sec.)' % (i_bar_ori+1,len(Bar_orientations),inizio)) while (timer_global.getTime() > 0 and break_flag==True): #globalClock.getTime() < Total_time: n_frame += 1 t = globalClock.getTime() # Spyder network if Spyder_grid: for i_dim in range(Spyder_rings): web_circle.setSize(tuple([x*(i_dim+1) * 1./Spyder_rings for x in web_dimension])) web_circle.draw() for i_dim in range(2): web_line.setOri(i_dim * 90) web_line.draw() # External ring external_aperture.enabled = True # Bar if (t >= ((i_bar_ori+1)*Cycle_duration*Passagges_per_orientation)) & (i_bar_ori < (len(Bar_orientations)-1)): i_bar_ori += 1 logging.data('Change orientation. Number %d/%d at %f (sec.)' % (i_bar_ori+1,len(Bar_orientations),t)) new_record = globalClock.getTime() - sum(all_changes) all_changes.append(new_record) if t % Flash_period < Flash_period / 2.0: # more accurate to count frames stim = grating_1 else: stim = grating_2 bar_pos_indx = int((Bar_positions_number / Cycle_duration) * (t % Cycle_duration) ) i_orientation_ordered = Bar_orientation_order[i_bar_ori] stim.pos = tuple(bar_positions[i_orientation_ordered][bar_pos_indx]) stim.ori = Bar_orientations[i_orientation_ordered] stim.draw() # Fixation if Rotating_cross: fixation.ori = t * Rotation_cross_rate * 360.0 # set new rotation if Color_change_cross: if t % Color_change_rate < Color_change_rate / 2.0: # more accurate to count frames fixation.lineColor = 'red' else: fixation.lineColor = 'green' external_aperture.enabled = False if DEBUG_MODE: if t - last_fps_update > Fps_update_rate: # update the fps text every second fps_text.text = "%.2f fps" % win.fps() last_fps_update += 1 orientation_details_string.text = 'Ori: %d/%d at %.3f (sec.)' % (i_bar_ori+1,len(Bar_orientations),np.sum(all_changes)) # Update screen win.flip() break_flag = escapeCondition() if break_flag == False: break logging.data('Total time spent: %.6f' % (globalClock.getTime() - inizio)) logging.data('Every frame duration saved in %s' % (path_out+Frames_durations_name)) logging.data('All durations: ' + str(all_changes)) logging.data('Mean: ' + str(sum(all_changes)/(len(all_changes)+EPSILON))) if DEBUG_MODE: orientation_details_string.text = 'Ended at %.3f (sec.)' % (globalClock.getTime()) win.flip() # Wait Pre_post_stimuli_fixation_time after stimuli core.wait(Pre_post_stimuli_fixation_time) return
mask='raisedCos', color=0, size=1) message = visual.TextStim(win, text='Press q to quit', pos=(-0.95, -0.95), units='norm', anchorVert='bottom', anchorHoriz='left') trialClock = core.Clock() t = 0 # create aperture from mask aperture = visual.Aperture(win, size=.5, shape=mask.verticesPix) # try shape='square' for x in range(nTrials): trialClock.reset() t = 0 while t < 10: # quits after 20 secs t = trialClock.getTime() # set grating contrast grating1.contrast = sin(t * pi * 4) grating2.contrast = sin(t * pi * 4) # initally draw right side aperture.ori = 0 aperture.enabled = True # enabled by default when created # draw plaid grating1.draw()
def main(win, globalClock): all_changes_ecc = [] all_changes_pol = [] size_ecc_pxl = resY * Eccentricity_size ################################ Stimuli prepation ################################ # Make two wedges (in opposite contrast) and alternate them for flashing grating_texture = np.tile([[1, -1], [-1, 1]], (8, 8)) #(4,64)) wedge1 = visual.RadialStim(win, tex=grating_texture, color=1, units='pix', size=size_ecc_pxl, radialCycles=1, angularCycles=2, interpolate=False, autoLog=False) #, mask=radius) wedge2 = copy.copy(wedge1) wedge2.color = -1 position_from_center = np.linspace(0, size_ecc_pxl, Mask_positions_number).reshape((-1, 1)) # Make two wedges (in opposite contrast) and alternate them for flashing polar1 = visual.RadialStim( win, tex=grating_texture, color=1, units='pix', size=win.size[1] * 1.3, visibleWedge=[Initial_wedge_pos, Initial_wedge_pos + Wedge_width], interpolate=False, autoLog=False, radialCycles=1, angularCycles=4) polar2 = copy.copy(polar1) polar2.color = -1 # fixation cross fixation = visual.ShapeStim(win, vertices=((0, -1), (0, 1), (0, 0), (-1, 0), (1, 0)), lineWidth=4, units="pix", size=(20, 20), closeShape=False, lineColor='red', autoDraw=True) # Spyder network web_circle = visual.Circle(win=win, radius=1, edges=200, units='norm', pos=[0, 0], lineWidth=1, opacity=1, interpolate=True, lineColor=[1.0, 1.0, 1.0], lineColorSpace='rgb', fillColor=None, fillColorSpace='rgb') web_dimension = (screenCorrection(win, Web_size[0]), Web_size[1]) web_line = visual.Line(win, name='Line', start=(-1.4, 0), end=(1.4, 0), pos=[0, 0], lineWidth=1, lineColor=[1.0, 1.0, 1.0], lineColorSpace='rgb', opacity=1, interpolate=True) # DEBUG stimuli if DEBUG_MODE: fps_text = visual.TextStim(win, units='norm', height=0.05, pos=(-0.98, +0.93), text='starting...', font=sans, alignHoriz='left', alignVert='bottom', color='yellow') fps_text.autoDraw = True orientation_details_string = visual.TextStim(win, text=u"eccentricity..", units='norm', height=0.05, pos=(0.95, +0.93), alignHoriz='right', alignVert='bottom', font=sans, color='yellow') orientation_details_string.autoDraw = True # External Aperture (black ring) external_aperture_size = tuple( [screenCorrection(win, External_ring_size), External_ring_size]) external_aperture = visual.Aperture(win, size=external_aperture_size, shape='circle') external_aperture.enabled = False ################################ Definitions/Functions ################################ ## handle Rkey presses each frame def escapeCondition(): for key in event.getKeys(): if key in ['escape', 'q']: return False return True ################################ Animation starts ################################ # display instructions and wait message1 = visual.TextStim(win, pos=[0, 0.5], text='Hit a key when ready.') message1.draw() fixation.draw() win.flip() #to show our newly drawn 'stimuli' event.waitKeys() #pause until there's a keypress # Scanner trigger wait message3 = visual.TextStim(win, pos=[0, 0.25], text=scanner_message, font=serif, alignVert='center', wrapWidth=1.5) message3.size = .5 message3.draw() win.flip() if BUTTON_BOX: button_state = button_thread.button_state while 1: if (button_state['state'][-1] == 0): break else: event.waitKeys() #pause until there's a keypress # Wait Pre_post_stimuli_fixation_time before stimuli # Spyder network if Spyder_grid: for i_dim in range(Spyder_rings): web_circle.setSize( tuple([ x * (i_dim + 1) * 1. / Spyder_rings for x in web_dimension ])) web_circle.draw() for i_dim in range(2): web_line.setOri(i_dim * 90) web_line.draw() win.flip() core.wait(Pre_post_stimuli_fixation_time) t = last_fps_update = i_cycle_ecc = i_cycle_pol = new_record_ecc = new_record_pol = 0 break_flag = True globalClock.reset() inizio = globalClock.getTime() logging.data('First cycle Eccentricity. Number %d/%d at %f (sec.)' % (i_cycle_ecc + 1, Cycles_number_ecc, inizio)) logging.data('First cycle Polar. Number %d/%d at %f (sec.)' % (i_cycle_pol + 1, Cycles_number_polar, inizio)) while (globalClock.getTime() < Total_time and break_flag == True): t = globalClock.getTime() # Spyder network if Spyder_grid: for i_dim in range(Spyder_rings): web_circle.setSize( tuple([ x * (i_dim + 1) * 1. / Spyder_rings for x in web_dimension ])) web_circle.draw() for i_dim in range(2): web_line.setOri(i_dim * 90) web_line.draw() # External ring external_aperture.enabled = True if t % Flash_period < Flash_period / 2.0: # more accurate to count frames wedge = wedge1 polar = polar1 else: wedge = wedge2 polar = polar2 polar.ori = -t * Rotation_rate * 360.0 # set new rotation # Prepare moving mask if (t >= ((i_cycle_ecc + 1) * Cycle_duration_ecc)): logging.data( 'Change orientation (eccentricity). Number %d/%d at %f (sec.)' % (i_cycle_ecc + 1, Cycles_number_ecc, t)) new_record_ecc = globalClock.getTime() - sum(all_changes_ecc) all_changes_ecc.append(new_record_ecc) if (t >= ((i_cycle_pol + 1) * Cycle_duration_polar)): logging.data( 'Change orientation (polar). Number %d/%d at %f (sec.)' % (i_cycle_pol + 1, Cycles_number_polar, t)) new_record_pol = globalClock.getTime() - sum(all_changes_pol) all_changes_pol.append(new_record_pol) i_cycle_ecc = int(t / Cycle_duration_ecc) i_cycle_pol = int(t / Cycle_duration_polar) crown_pos_indx = int((Mask_positions_number / Cycle_duration_ecc / 2) * (t % Cycle_duration_ecc)) ## Try to understand when it changes and save it mask_begin = int(position_from_center[crown_pos_indx, 0]) mask_end = int(position_from_center[crown_pos_indx, 0] + Thickness_multiplication_factor[crown_pos_indx] * Thickness_circular_crown) annulus_mask = np.zeros((int(size_ecc_pxl / 2), 1)) annulus_mask[ mask_begin: mask_end] += 1 #int(mask_end if mask_end <= size_ecc_pxl else size_ecc_pxl)] += 1 wedge.setMask(annulus_mask) wedge.draw() polar.draw() # Fixation if Rotating_cross: fixation.ori = t * Rotation_cross_rate * 360.0 # set new rotation if Color_change_cross: if t % Color_change_rate < Color_change_rate / 2.0: # more accurate to count frames fixation.lineColor = 'red' else: fixation.lineColor = 'green' external_aperture.enabled = False if DEBUG_MODE: if t - last_fps_update > Fps_update_rate: # update the fps text every second fps_text.text = "%.2f fps" % win.fps() last_fps_update += 1 # orientation_details_string.text = 'Pass: %d/%d at %.3f (sec.)' % (i_cycle+1,Cycles_number,np.sum(all_changes)) win.flip() break_flag = escapeCondition() if break_flag == False: break logging.data('Total time planned: %.6f' % (Total_time)) logging.data('Total time spent: %.6f' % (globalClock.getTime() - inizio)) logging.data('Every frame duration saved in %s' % (path_out + Frames_durations_name)) logging.data('All durations Eccentricity: ' + str(all_changes_ecc)) logging.data('All durations Polar: ' + str(all_changes_pol)) logging.data('Mean Eccentricity: ' + str(sum(all_changes_ecc) / (len(all_changes_ecc) + EPSILON))) logging.data('Mean Polar: ' + str(sum(all_changes_pol) / (len(all_changes_pol) + EPSILON))) if DEBUG_MODE: orientation_details_string.text = 'Ended at %.3f (sec.)' % ( globalClock.getTime()) win.flip() # Wait Pre_post_stimuli_fixation_time after stimuli core.wait(Pre_post_stimuli_fixation_time) return
berry3.opacity = 1 #getting the location of mouse mouse_location = default_mouse.getPos() #drawing the elements to be drawn permanently aka every frame grass.setAutoDraw(True) berry1.setAutoDraw(True) berry2.setAutoDraw(True) berry3.setAutoDraw(True) #aperture is drawn every frame and aperture gets to position of mouse foe the 'spotlight' like movemnet to happen aperture = visual.Aperture(win, name='aperture', shape='circle', units='pix', size=100, pos=default_mouse.getPos()) #setting the position where the reward and time elapsed will be displayed text.pos = [-580, 300] time_text.pos = [580, 300] reward_text.pos = default_mouse.getPos() #changing the texts on every update time_elapsed = clock.getAbsTime() - start_time time_text.text = 'Time elapsed:' + str(clock.getAbsTime() - start_time) text.text = 'Total reward:' + str(np.sum(collected)) #making the berry images a clickable stimuli berry1_click = default_mouse.isPressedIn(berry1, buttons=[0])
# Filename: demo_aperture.py from psychopy import visual, core, event # create a window win = visual.Window(size=(800, 600), units="pix", fullscr=False, color=[0, 0, 0], allowStencil=True) # create an aperture #apt = visual.Aperture(win, size=300, shape='circle', inverted=True) vert = [(0.1, .50), (.45, .20), (.10, -.5), (-.60, -.5), (-.5, .20)] apt = visual.Aperture(win, size=200, shape=vert, inverted=True) apt.enabled = True #create a mouse instance mouse = event.Mouse(visible=False) # prepare the stimuli text = visual.TextStim(win, text="Moving window example by Zhiguo" * 24, height=30, color='black', wrapWidth=760) # mouse-contingent moving window while not event.getKeys(): apt.pos = mouse.getPos() text.draw()
# make a monitor with monitor constants from the init file mon = expsetup.makeMonitor(init['monitor_constants']) # and make a window using that monitor win = visual.Window( monitor=mon, color=init['experiment']['bkgcolor'], size=mon.getSizePix(), units='deg', screen=1, # 0 is the monitor of the experiment control fullscr=True, allowGUI=False, waitBlanking=False, allowStencil=True, winType='pyglet') # for iohub to function well aperture = visual.Aperture(win, size=20) aperture.units = 'deg' # velocity is upward, computed from the oblique velocity vector # this velocity is in periods/s # the velocity is thus relatif to the vertical period which is 1/(sin(theta)*sf) [deg] vel_ori = -90 sf_x = stims.get_angular_frequency(0, stim_params['sf'], stim_params['ori']) sf_y = stims.get_angular_frequency(90, stim_params['sf'], stim_params['ori']) v_y = stims.get_angular_velocity(90, stim_params['vel'], stim_params['ori']) velocity = lambda vel_ori: np.array([ v_y * sf_x * np.cos(stims._deg2rad(vel_ori)), v_y * sf_y * np.sin( stims._deg2rad(vel_ori)) ])
size=(1024, 768), fullscr=True, screen=0, winType='pyglet', allowGUI=False, allowStencil=True, monitor='testMonitor', color=[0,0,0], colorSpace='rgb', blendMode='avg', useFBO=True, units='height') # store frame rate of monitor if we can measure it expInfo['frameRate'] = win.getActualFrameRate() if expInfo['frameRate'] != None: frameDur = 1.0 / round(expInfo['frameRate']) else: frameDur = 1.0 / 60.0 # could not measure, so guess # Initialize components for Routine "trial" trialClock = core.Clock() aperture = visual.Aperture( win=win, name='aperture', units='norm', size=1, pos=(0, 0)) aperture.disable() # disable until its actually used # Create some handy timers globalClock = core.Clock() # to track the time since experiment started routineTimer = core.CountdownTimer() # to track time remaining of each (non-slip) routine # ------Prepare to start Routine "trial"------- t = 0 trialClock.reset() # clock frameN = -1 continueRoutine = True routineTimer.add(1.000000) # update component parameters for each repeat # keep track of which components have finished
def prf_stim(dicParam): """ Present stimuli for population receptive field mapping. If in logging mode, this script creates a stimulus log of the stimuli used for the pRF mapping that can be used for the pRF finding analysis of the pyprf library. The stimuli are saved as png files, where each png represents the status of visual stimulation for one TR (the png files contain modified screenshots of the visual stimulus, and can be directly be loaded into the py_pRF_mapping pipepline. """ # ***************************************************************************** # *** Experimental parameters (from dictionary) # Path of design matrix (npz): strPthNpz = dicParam['Path of design matrix (npz)'] # Output path & file name of log file: strPthLog = dicParam['Output path (log files)'] # Target duration [s]: varTrgtDur = float(dicParam['Target duration [s]']) # Logging mode (logging mode is for creating files for analysis, not to be # used during an experiment). lgcLogMde = dicParam['Logging mode'] # On windows, the return value may be a string, not a bool. We need to # correct for this. if not (isinstance(lgcLogMde, bool)): if lgcLogMde == 'True': lgcLogMde = True else: lgcLogMde = False # Directory where to save stimulus log (frames) for analysis if in logging # mode. strPthFrm = dicParam['Output path stimulus log (frames)'] # Frequency of stimulus bar in Hz: varGrtFrq = float(dicParam['Temporal frequency [Hz]']) # Sptial frequency of stimulus (cycles along width of bar stimulus): varBarSf = float(dicParam['Spatial frequency [cyc per bar]']) # Distance between observer and monitor [cm]: varMonDist = float(dicParam['Distance between observer and monitor [cm]']) # Width of monitor [cm]: varMonWdth = float(dicParam['Width of monitor [cm]']) # Width of monitor [pixels]: varPixX = int(dicParam['Width of monitor [pixels]']) # Height of monitor [pixels]: varPixY = int(dicParam['Height of monitor [pixels]']) # Background colour: varBckgrd = float(dicParam['Background colour [-1 to 1]']) # Show fixation grid? lgcGrd = dicParam['Show fixation grid?'] # On windows, the return value may be a string, not a bool. We need to # correct for this. if not (isinstance(lgcGrd, bool)): if lgcGrd == 'True': lgcGrd = True else: lgcGrd = False # ************************************************************************* # *** Retrieve design matrix # Load stimulus parameters from npz file. objNpz = np.load(strPthNpz) # Get design matrix (for bar positions and orientation): aryDsg = objNpz['aryDsg'] # Number of volumes: varNumVol = aryDsg.shape[0] # Vector with times of target events: vecTrgt = objNpz['vecTrgt'] # Number of target events: varNumTrgt = vecTrgt.shape[0] # Full screen mode? If no, bar stimuli are restricted to a central square. # If yes, bars appear on the entire screen. This parameter is set when # creating the design matrix. lgcFull = bool(objNpz['lgcFull']) # Number of bar positions on x-axis: varNumPosX = int(objNpz['varNumPosX']) # Number of bar positions on y-axis: varNumPosY = int(objNpz['varNumPosY']) # If in full screen mode, we need to make sure that the bar position along # the shorted axis (x-axis) is adjusted, so that the bar is always within # the screen area (and not half cut off). if lgcFull: # Ratio of bar positions (from design matrix), e.g. 18/11: varRatioPos = float(varNumPosX) / float(varNumPosY) # Ratio of screen widht/height in pixels (e.g. 1920/1200): varRatioPix = float(varPixX) / float(varPixY) # Scaling factor for bar positions along x-axis: varSclPosX = varRatioPos / varRatioPix # Adjustments for logging mode: if lgcLogMde: # Conditional imports: from PIL import Image from scipy.stats import mode # If in logging mode, only present stimuli very briefly. Note: If # 'varTr' is set too low in logging mode, frames are dropped and the # stimuli do not get logged properly. varTr = 0.2 # In log mode, don't show grid. lgcGrd = False # Otherwise, use actual volume TR: else: # Volume TR: varTr = float(objNpz['varTr']) # ************************************************************************* # *** Logging # Set clock: objClck = core.Clock() # Set clock for logging: logging.setDefaultClock(objClck) # Create a log file and set logging verbosity: fleLog = logging.LogFile(strPthLog, level=logging.DATA) # Log stimulus parameters: fleLog.write('Log file path: ' + strPthLog + '\n') fleLog.write('Design matrix: ' + strPthNpz + '\n') fleLog.write('Full screen: ' + str(lgcFull) + '\n') fleLog.write('Volume TR [s] (from design matrix): ' + str(varTr) + '\n') fleLog.write('Frequency of stimulus bar in Hz: ' + str(varGrtFrq) + '\n') fleLog.write('Sptial frequency of stimulus (cycles along width of ' + 'bar stimulus): ' + str(varBarSf) + '\n') fleLog.write('Distance between observer and monitor [cm]: ' + str(varMonDist) + '\n') fleLog.write('Width of monitor [cm]: ' + str(varMonWdth) + '\n') fleLog.write('Width of monitor [pixels]: ' + str(varPixX) + '\n') fleLog.write('Height of monitor [pixels]: ' + str(varPixY) + '\n') fleLog.write('Background colour [-1 to 1]: ' + str(varBckgrd) + '\n') fleLog.write('Target duration [s]: ' + str(varTrgtDur) + '\n') fleLog.write('Logging mode: ' + str(lgcLogMde) + '\n') # Set console logging verbosity: logging.console.setLevel(logging.WARNING) # ************************************************************************* # *** Prepare behavioural response logging # Switch target (show target or not?): varSwtTrgt = 0 # Control the logging of participant responses: varSwtRspLog = 0 # The key that the participant has to press after a target event: strTrgtKey = '1' # Counter for correct/incorrect responses: varCntHit = 0 # Counter for hits varCntMis = 0 # Counter for misses # Time (in seconds) that participants have to respond to a target event in # order for the event to be logged as a hit: varHitTme = 2.0 # ************************************************************************* # *** Setup # Create monitor object: objMon = monitors.Monitor('Screen_7T_NOVA_32_Channel_Coil', width=varMonWdth, distance=varMonDist) # Set size of monitor: objMon.setSizePix([varPixX, varPixY]) # Set screen: objWin = visual.Window( size=(varPixX, varPixY), screen=0, winType='pyglet', # winType : None, 'pyglet', 'pygame' allowGUI=False, allowStencil=True, fullscr=True, monitor=objMon, color=varBckgrd, colorSpace='rgb', units='deg', blendMode='avg') # ************************************************************************* # *** Spatial stimulus properties # The area that will be covered by the bar stimulus depends on whether # presenting in full screen mode or not. If in full screen mode, the # entire width of the screen will be covered. If not, a central square # with a side length equal to the screen height will be covered. if lgcFull: varPixCov = varPixX else: varPixCov = varPixY # Convert size in pixels to size in degrees (given the monitor settings): # varDegCover = pix2deg(varPixCov, objMon) # Numberic codes used for bar positions: vecPosCode = np.unique(aryDsg[:, 1]) # Number of bar positions: varNumPos = vecPosCode.shape[0] # The thickness of the bar stimulus depends on the size of the screen to # be covered, and on the number of positions at which to present the bar. # Bar thickness in pixels: varThckPix = float(varPixCov) / float(varNumPos) # Bar thickness in degree: varThckDgr = np.around(pix2deg(varThckPix, objMon), decimals=5) # Write stimulus parameters to log file. fleLog.write('* * * Stimulus properties in degrees of visual angle ' + '* * * \n') fleLog.write('Width of bar stimulus [deg]: ' + str(varThckDgr) + '\n') # Spatial frequency of bar stimulus is defined (in user input) as cycles # along width of the bar stimulus. We need to convert this to cycles per # pixel (for the stimulus creation) and to cycles per degree (for # reference, written to log file). # Spatial frequency in cycles per pixel: varBarSfPix = float(varBarSf) / varThckPix tplBarSfPix = (varBarSfPix, varBarSfPix) # Spatial frequency in cycles per degree of visual angle (for reference # only): varBarSfDeg = np.around((float(varBarSf) / varThckDgr), decimals=5) # Write stimulus parameters to log file. fleLog.write('Spatial frequency of bar stimulus [cyc/deg]: ' + str(varBarSfDeg) + '\n') fleLog.write('* * * \n') # Bar stimulus size (length & thickness), in pixels. tplBarSzePix = (varPixX, int(varThckPix)) # Offset of the bar stimuli. The bar stimuli should cover the screen area, # without extending beyond the screen. Because their position refers to # the centre of the bar, we need to limit the extend of positions at the # edge of the screen by an offset, Offset in pixels: varOffsetPix = varThckPix * 0.5 # Maximum bar position in pixels, with respect to origin at centre of # screen: varPosMaxPix = (float(varPixCov) * 0.5) - float(varOffsetPix) # Array of possible bar positions (displacement relative to origin at # centre of the screen) in pixels: vecPosPix = np.linspace(-varPosMaxPix, varPosMaxPix, varNumPos, endpoint=True) # Replace numeric position codes with pixel position values: for idxPos, varPos in enumerate(vecPosCode): # Replace current position code, if this is not a rest block: vecLgc = np.multiply((aryDsg[:, 1] == varPos), (aryDsg[:, 0] != 0.0)) # Place pixel position value in design matrix: aryDsg[vecLgc, 1] = vecPosPix[idxPos] # Psychopy orientation convention: "Orientation convention is like a clock: # 0 is vertical, and positive values rotate clockwise." Actually, 0 is the # positive x-axis. Orientations are coded as follows: horizontal = 0.0, # vertical = 90.0, lower left to upper right = 45.0, upper left to lower # right = 135.0. We need to convert psychopy orientation & direction # convention into x and y coordinates. lstPos = [None] * varNumVol for idxVol in range(varNumVol): # Get angle and radius of current volume: varRad = float(aryDsg[idxVol, 1]) varAngle = float(aryDsg[idxVol, 2]) # Horizontal: if varAngle == 0.0: varTmpX = 0.0 # If in full screen mode, make sure that the bar is not partially # outside of the screen area. if lgcFull: # Scale y-position. varTmpY = varRad * varSclPosX else: # Not in full screen mode, don't scale. varTmpY = varRad # Vertical: elif varAngle == 90.0: varTmpX = varRad varTmpY = 0.0 # Lower left to upper right: elif varAngle == 45.0: if varRad < 0.0: varTmpX = -np.sqrt(np.add(np.square(varRad), np.square(varRad))) elif 0.0 < varRad: varTmpX = np.sqrt(np.add(np.square(varRad), np.square(varRad))) else: varTmpX = 0.0 varTmpY = 0.0 # Upper left to lower right: elif varAngle == 135.0: if varRad < 0.0: varTmpX = -np.sqrt(np.add(np.square(varRad), np.square(varRad))) elif 0.0 < varRad: varTmpX = np.sqrt(np.add(np.square(varRad), np.square(varRad))) else: varTmpX = 0.0 varTmpY = 0.0 # Position is coded as a tuple: lstPos[idxVol] = (varTmpX, varTmpY) # ************************************************************************* # *** Stimuli # Bar stimulus: objBar = visual.GratingStim(objWin, contrast=1.0, pos=(0.0, 0.0), tex='sqrXsqr', color=[1.0, 1.0, 1.0], colorSpace='rgb', opacity=1.0, size=tplBarSzePix, sf=tplBarSfPix, ori=0.0, autoLog=False, interpolate=False, units='pix') # Colour of fixation dot: lstClrFix = [-0.69, 0.83, 0.63] # lstClrFix = [0.04, 0.95, -1.0] # Colour of fixation dot when it becomes a target: lstClrTrgt = [0.95, 0.04, -1.0] # Fixation dot: objFix = visual.Circle(objWin, units='deg', pos=(0.0, 0.0), radius=0.05, edges=24, fillColor=lstClrFix, fillColorSpace='rgb', lineColor=lstClrFix, lineColorSpace='rgb', lineWidth=0.0, interpolate=False, autoLog=False) # Fication dot surround: objFixSrd = visual.Circle(objWin, units='deg', pos=(0.0, 0.0), radius=0.09, edges=24, fillColor=lstClrTrgt, fillColorSpace='rgb', lineColor=lstClrTrgt, lineColorSpace='rgb', lineWidth=0.0, interpolate=False, autoLog=False) if lgcGrd: # Number of grid circles: varNumCrcl = 3 # Radi at which to present grid circles: vecGrdCrclRad = np.linspace((0.25 * float(varPixY)), (0.75 * float(varPixY)), num=varNumCrcl) # In practice 'radius' seems to refer to refer to the diameter of the # circle. # Fixation grid circles: lstGrdCrcl = [None] * varNumCrcl for idxCrcl, varRad in enumerate(vecGrdCrclRad): lstGrdCrcl[idxCrcl] = visual.Circle(win=objWin, pos=(0.0, 0.0), radius=varRad, edges=128, lineWidth=1.0, lineColor=[-0.8, -0.8, -0.8], lineColorSpace='rgb', fillColor=None, fillColorSpace='rgb', opacity=1.0, autoLog=False, interpolate=True, units='pix') # Fixation grid line: lstGrdLne = [None] * 4 for idxLne, varOri in enumerate([0.0, 45.0, 90.0, 135.0]): lstGrdLne[idxLne] = visual.Line(win=objWin, ori=varOri, start=(int(-varPixY), 0), end=(int(varPixY), 0), pos=(0.0, 0.0), lineWidth=1.0, lineColor=[-0.8, -0.8, -0.8], lineColorSpace='rgb', fillColor=None, fillColorSpace='rgb', opacity=1.0, autoLog=False, interpolate=True, units='pix') # ************************************************************************* # *** Aperture # The area that will be covered by the bar stimulus depends on whether # presenting in full screen mode or not. If in full screen mode, the entire # width of the screen will be covered. If not, a central square with a side # length equal to the screen height will be covered. if not (lgcFull): # Aperture side length in degree. For some reason, the aperture does # not seem to accept pixel units. varDegCov = pix2deg(float(varPixCov), objMon) # Aperture for covering left and right side of screen if not presenting # in full screen mode. objAprtr = visual.Aperture(objWin, size=varDegCov, pos=(0, 0), shape='square', inverted=False, units='deg') objAprtr.enabled = True # ************************************************************************* # *** Logging mode preparations if lgcLogMde: print('Logging mode') # Calculate area to crop in x-dimension, at left and right (zero is # full extend of screeen width is used): if lgcFull: varCrpX = 0 else: varCrpX = int(np.around((float(varPixX) - float(varPixY)) * 0.5)) print(('Stimulus log will be cropped by ' + str(varCrpX) + ' in x-direction (screen width).')) # Temporary array for screenshots, at full screen size and containing # RGB values (needed to obtain buffer content from psychopy): aryBuff = np.zeros((varPixY, varPixX, 3), dtype=np.int8) # It is not necessary to sample every pixel; only every second pixel is # sampled. Number of pixel to be sampled along x and y direction: varHalfPixX = int(np.around(varPixX * 0.5)) varHalfPixY = int(np.around(varPixY * 0.5)) # Prepare array for screenshots. One value per pixel per volume; since # the stimuli are greyscale we discard 2nd and 3rd RGB dimension. Also, # there is no need to represent the entire screen, just the part of the # screen that is actually stimulated (if not in full screen mode, this # is a square at the centre of the screen, flanked by unstimulated # areas on the left and right side). if lgcFull: aryFrames = np.zeros((varHalfPixY, varHalfPixX, varNumVol), dtype=np.uint8) else: aryFrames = np.zeros((varHalfPixY, varHalfPixY, varNumVol), dtype=np.uint8) # Counter for screenshots: idxFrame = 0 # ************************************************************************* # *** Timing & switches # Target counter: varCntTrgt = 0 # Time of the first target event: varTmeTrgt = vecTrgt[varCntTrgt] # Switch for grating polarity flicker: varSwtGrt = 0 # The input parameter 'varGrtFrq' gives the grating flicker frequency in # Hz. We need to convert to second: varGrtDur = 1.0 / float(varGrtFrq) # ************************************************************************* # *** Presentation # Hide the mouse cursor: event.Mouse(visible=False) if not (lgcLogMde): if lgcGrd: # Draw fixation grid circles: for objGrdCrcl in lstGrdCrcl: objGrdCrcl.draw(win=objWin) # Draw fixation grid lines: for objGrdLne in lstGrdLne: objGrdLne.draw(win=objWin) # Draw fixation dot & surround: objFixSrd.draw(win=objWin) objFix.draw(win=objWin) objWin.flip() # Wait for scanner trigger pulse & set clock after receiving trigger # pulse (scanner trigger pulse is received as button press ('5')): strTrgr = ['0'] while strTrgr[0][0] != '5': # Check for keypress: lstTmp = event.getKeys(keyList=['5'], timeStamped=False) # Whether the list has the correct length (if nothing has happened, # lstTmp # will have length zero): if len(lstTmp) == 1: strTrgr = lstTmp[0][0] # Trigger pulse received, reset clock: objClck.reset(newT=0.0) # Main timer which represents the starting point of the experiment: varTme01 = objClck.getTime() # Time that is updated continuously to track time: varTme02 = objClck.getTime() # Timer used to control the logging of stimulus events: varTme03 = objClck.getTime() # Timer for grating stimulus polarity flicker: varTme04 = objClck.getTime() varTme05 = objClck.getTime() # Start of the experiment: for idxVol in range(varNumVol): # Show a grating during this volume? lgcOn = (aryDsg[idxVol, 0] == 1.0) # Set grating properties for current volume: if lgcOn: # Get stimulus properties from design matrix: varTmpPos = lstPos[idxVol] varTmpOri = aryDsg[idxVol, 2] varTmpCon = aryDsg[idxVol, 3] # Set bar properties: objBar.setPos(varTmpPos) objBar.setOri(varTmpOri) objBar.setColor((varTmpCon, varTmpCon, varTmpCon)) # Still on the same volume? while varTme02 < (varTme01 + (float(idxVol + 1) * varTr)): # ***************************************************************** # *** Draw stimuli # Draw fixation grid? if lgcGrd: # Draw fixation grid circles: for objGrdCrcl in lstGrdCrcl: objGrdCrcl.draw(win=objWin) # Draw fixation grid lines: for objGrdLne in lstGrdLne: objGrdLne.draw(win=objWin) # If a grating is shown, which orientation, position, and contrast? if lgcOn: # Draw grating. objBar.draw(win=objWin) # Don't draw fixation point in logging mode: if not (lgcLogMde): # Draw fixation dot & surround: objFixSrd.draw(win=objWin) objFix.draw(win=objWin) # Flip drawn objects to screen: objWin.flip() # Update current time: varTme02 = objClck.getTime() # Update current time: varTme05 = objClck.getTime() # ***************************************************************** # *** Target control # Time for target? if ((varTmeTrgt <= varTme02) and (varTme02 <= (varTmeTrgt + varTrgtDur))): # Was the target off on the previous frame? if varSwtTrgt == 0: # Switch the target on by changing the fixation dot colour. objFix.fillColor = lstClrTrgt # Log target event: strTmp = ('TARGET scheduled for: ' + str(varTmeTrgt)) logging.data(strTmp) # Once after target onset we set varSwtRspLog to one so # that the participant's respond can be logged: varSwtRspLog = 1 # Likewise, just after target onset we set the timer for # response logging to the current time so that the response # will only be counted as a hit in a specified time # interval after target onset: varTme03 = objClck.getTime() # Switch the target switch. varSwtTrgt = 1 else: # No time for target. # Was the target just on? if varSwtTrgt == 1: # Switch the target off (by changing fixation dot colour # back to normal). objFix.fillColor = lstClrFix # Switch the target switch. varSwtTrgt = 0 # Only increase the target counter if the last target has # not been reached yet: if (varCntTrgt + 1) < varNumTrgt: # Increase the target counter: varCntTrgt = varCntTrgt + 1 # Time of next target event: varTmeTrgt = vecTrgt[varCntTrgt] # Has the participant's response not been reported yet, and is it # still within the time window? if (varSwtRspLog == 1) and (varTme02 <= (varTme03 + varHitTme)): # Check for and log participant's response: lstRsps = event.getKeys(keyList=[strTrgtKey], timeStamped=False) # Check whether the list has the correct length: if len(lstRsps) == 1: # Does the list contain the response key? if lstRsps[0] == strTrgtKey: # Log hit: logging.data('Hit') # Count hit: varCntHit += 1 # After logging the hit, we have to switch off the # response logging, so that the same hit is not logged # over and over again: varSwtRspLog = 0 elif (varSwtRspLog == 1) and (varTme02 > (varTme03 + varHitTme)): # Log miss: logging.data('Miss') # Count miss: varCntMis += 1 # If the subject does not respond to the target within time, we # log this as a miss and set varSwtRspLog to zero (so that the # response won't be logged as a hit anymore afterwards): varSwtRspLog = 0 # ***************************************************************** # *** Grating control # If a grating is shown, which orientation, position, and contrast? if lgcOn: # Change grating polarity: if (varTme04 + varGrtDur) <= varTme05: if varSwtGrt == 0: varSwtGrt = 1 objBar.contrast = 1.0 else: varSwtGrt = 0 objBar.contrast = -1.0 # Remember time at which grating polarity was switched: varTme04 = objClck.getTime() # Update current time: # varTme02 = objClck.getTime() # Update current time: # varTme05 = objClck.getTime() if lgcLogMde: # Temporary array for single frame (3 values per pixel - RGB): aryBuff[:, :, :] = objWin.getMovieFrame(buffer='front') # Clear frames (otherwise stack of frames will pile up in memory): objWin.movieFrames = [] # We only save one value per pixel per volume (because the stimuli # are greyscale we discard 2nd and 3rd RGB dimension): aryRgb = aryBuff[:, :, 0] # Sample the relevant part of the screen (all the screen if in # full screen mode, central square otherwise).0 aryRgb = aryRgb[:, varCrpX:(varPixX - varCrpX)] # Only sample every second pixel: aryRgb = aryRgb[::2, ::2] # On first frame, get pixel value of background intensity: if idxVol == 0: varBck = mode(aryRgb, axis=None)[0] # The stimulus log is supposed to contain information about where # the stimulus was presented on each volume, and at which contrast. # The pattern inside the stimulus (chequerboard) is not of # interest. Therefore, we create a logical array (True = stimulus # was present on this pixel). aryRgb = np.not_equal(aryRgb, varBck).astype(np.int8) # Contrast value on current volume: varTmpMax = int(np.around(255.0 * aryDsg[idxVol, 3])) # Rescale to range 0 to 255: aryRgb = np.multiply(aryRgb, varTmpMax).astype(np.uint8) # Hard copy: aryFrames[:, :, idxFrame] = np.copy(aryRgb) idxFrame = idxFrame + 1 # Check whether exit keys have been pressed: if func_exit() == 1: break # ************************************************************************* # *** Feedback logging.data('------End of the experiment.------') # Performance feedback only if there were any targets: if 0.0 < float(varCntHit + varCntMis): # Ratio of hits: varHitRatio = float(varCntHit) / float(varCntHit + varCntMis) # Present participant with feedback on her target detection # performance: if 0.99 < varHitRatio: # Perfect performance: strFeedback = ('You have detected ' + str(varCntHit) + ' targets out of ' + str(varCntHit + varCntMis) + '\n' + 'Keep up the good work :)') elif 0.9 < varHitRatio: # OKish performance: strFeedback = ('You have detected ' + str(varCntHit) + ' targets out of ' + str(varCntHit + varCntMis) + '\n' + 'There is still room for improvement ;)') else: # Low performance: strFeedback = ('You have detected ' + str(varCntHit) + ' targets out of ' + str(varCntHit + varCntMis) + '\n' + 'Please try to focus more :(') # Create text object: objTxtTmr = visual.TextStim(objWin, text=strFeedback, font="Courier New", pos=(0.0, 0.0), color=(1.0, 1.0, 1.0), colorSpace='rgb', opacity=1.0, contrast=1.0, ori=0.0, height=0.5, antialias=True, alignHoriz='center', alignVert='center', flipHoriz=False, flipVert=False, autoLog=False) if not (lgcLogMde): # Show feedback text: varTme04 = objClck.getTime() while varTme02 < (varTme04 + 3.0): objTxtTmr.draw() objWin.flip() varTme02 = objClck.getTime() # Log total number of hits and misses: logging.data(('Number of hits: ' + str(varCntHit))) logging.data(('Number of misses: ' + str(varCntMis))) logging.data(('Percentage of hits: ' + str(np.around((varHitRatio * 100.0), decimals=1)))) # ************************************************************************* # *** End of the experiment # Make the mouse cursor visible again: event.Mouse(visible=True) # Close window: objWin.close() # ************************************************************************* # *** Logging mode # Save screenshots (logging mode): if lgcLogMde: print('Saving screenshots') # Check whether target directory for frames (screenshots) for frames # exists, if not create it: lgcDir = os.path.isdir(strPthFrm) # If directory does not exist, create it: if not (lgcDir): # Create direcotry for segments: os.mkdir(strPthFrm) # Save stimulus frame array to npy file: np.savez_compressed((strPthFrm + os.path.sep + 'stimulus_log'), aryFrames=aryFrames) # Loop through volumes and save PNGs: for idxVol in range(varNumVol): # print(('---Frame ' # + str(idxVol) # + ' out of ' # + str(int(varNumVol)))) # Create image: objImg = Image.fromarray(aryFrames[:, :, idxVol], mode='L') # File name (with leading zeros, e.g. '*_004' or '*_042'). For # consistency with earlier versions, the numbering of frames (PNG # files corresponding to fMRI volumes) starts at '1' (not at '0'). strTmpPth = (strPthFrm + os.path.sep + 'frame_' + str(idxVol + 1).zfill(3) + '.png') # Save image to disk: objImg.save(strTmpPth) # ************************************************************************* # *** Close everyting core.quit() monitors.quit() logging.quit() event.quit()
routineTimer.reset() # ------Prepare to start Routine "RandomStimuli"------- t = 0 RandomStimuliClock.reset() # clock frameN = -1 continueRoutine = True # update component parameters for each repeat from psychopy import visual, core, event from PIL import Image import numpy as np import glob, os, random, copy, time aperture1 = visual.Aperture(win, size=2, pos=(4, -4), ori=1, shape='circle') aperture1.enable = False aperture2 = visual.Aperture(win, size=2, pos=(-4, 4), ori=1, shape='circle') aperture2.enable = False aperture3 = visual.Aperture(win, size=2, pos=(4, 4), ori=1, shape='circle') aperture3.enable = False aperture4 = visual.Aperture(win, size=2, pos=(-4, -4), ori=1,
routineTimer.reset() # ------Prepare to start Routine "RandomStimuli"------- t = 0 RandomStimuliClock.reset() # clock frameN = -1 continueRoutine = True # update component parameters for each repeat from psychopy import visual, core from PIL import Image import numpy as np import glob, os, random, copy, time aperture1 = visual.Aperture(win, size=2, pos=(4, -4), ori=1, shape='circle') #aperture1.enable = False aperture2 = visual.Aperture(win, size=2, pos=(-4, 4), ori=1, shape='circle') #aperture2.enable = False aperture3 = visual.Aperture(win, size=2, pos=(4, 4), ori=1, shape='circle') #aperture3.enable = False aperture4 = visual.Aperture(win, size=2, pos=(-4, -4), ori=1,