Advertisement
simondp

Mock experiment

Jun 15th, 2018
60
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.76 KB | None | 0 0
  1. # -*- coding: utf-8 -*-
  2.  
  3. """
  4. SET VARIABLES
  5. """
  6. # Monitor parameters
  7. MON_DISTANCE = 60  # Distance between subject's eyes and monitor
  8. MON_WIDTH = 33  # Width of your monitor in cm
  9. MON_SIZE = [1024, 768]  # Pixel-dimensions of your monitor
  10. SAVE_FOLDER = 'templateData'  # Log is saved to this folder. The folder is created if it does not exist.
  11.  
  12. # Stimulus parameters
  13. GABOR_SF = 4  # 4 cycles per degree visual angle
  14. GABOR_SIZE = 8  # in degrees visual angle
  15.  
  16. FIX_HEIGHT = 2  # Text height of fixation cross
  17.  
  18. # Timings
  19. FRAMES_FIX = 30  # in frames. ~ 500 ms on 60 Hz
  20. FRAMES_STIM = [6, 9, 12]  # in frames. ~ 100, 150 and 200 ms on 60 Hz
  21. FRAMES_MASK = 3  # in frames. ~ 50 ms on 60 Hz
  22.  
  23. # Condition parameters
  24. REPETITIONS = 2  # number of trials per condition
  25. POSITIONS = [-12, 12]  # x-positions
  26. ORIS = {'right': 0, 'left': 90}  # Orientations and corresponding responses! values = ORIS.values() and keyboard keys = ORIS.keys()
  27. PAUSE_INTERVAL = 10  # Number of trials between breaks
  28.  
  29. # Questions and messages
  30. MESSAGE_POS = [0, 3]  # [x, y]
  31. MESSAGE_HEIGHT = 1  # Height of the text, still in degrees visual angle
  32. TEXT_BREAK = 'Press any key to continue...'  # text of regular break
  33. KEYS_QUIT = ['escape']  # Keys that quits the experiment
  34.  
  35. TEXT_INSTRUCT = """
  36.    Press LEFT if the lines are horizontal.
  37.    Press RIGHT if the lines are vertical.
  38.  
  39.    Keep your gaze at the cross at all times."""
  40.  
  41.  
  42. """
  43. SHOW DIALOGUE AND INITIATE PSYCHOPY STIMULI
  44. This is computationally heavy stuff. Thus we do it in the beginning of our experiment
  45. """
  46.  
  47. # Import stuff
  48. import ppc
  49. print 'the physical diameter of the gabor patch should be', ppc.deg2cm(GABOR_SIZE, MON_DISTANCE), 'cm'
  50. print 'the physical size of the fixation cross should be', ppc.deg2cm(FIX_HEIGHT, MON_DISTANCE), 'cm'
  51.  
  52. from psychopy import core, visual, gui, monitors, sound, event, data
  53. import random
  54.  
  55. # Intro-dialogue. Get subject-id and other variables.
  56. # Save input variables in "V" dictionary (V for "variables")
  57. V = {'subject':'', 'condition': ['falseFix'], 'age':'', 'gender':['male', 'female']}
  58. if not gui.DlgFromDict(V, order=['subject', 'age', 'gender']).OK:
  59.     core.quit()
  60.  
  61. # Stuff
  62. clock = core.Clock()  # A clock wich will be used throughout the experiment to time events on a trial-per-trial basis (stimuli and reaction times).
  63. writer = ppc.csvWriter(str(V['subject']), saveFolder=SAVE_FOLDER)  # writer.write(trial) will write individual trials with low latency
  64.  
  65. # Create psychopy window
  66. my_monitor = monitors.Monitor('testMonitor', width=MON_WIDTH, distance=MON_DISTANCE)  # Create monitor object from the variables above. This is needed to control size of stimuli in degrees.
  67. my_monitor.setSizePix(MON_SIZE)
  68. win = visual.Window(monitor=my_monitor, units='deg', fullscr=True, allowGUI=False, color='black')  # Initiate psychopy Window as the object "win", using the myMon object from last line. Use degree as units!
  69.  
  70. # Stimuli.
  71. stim_gabor = visual.Polygon(win,edges=4, size=GABOR_SIZE)  # A gabor patch. Again, units are inherited.
  72. #stim_lure = visual.GratingStim(win, mask='gauss', sf=2, size=GABOR_SIZE)  # A gabor patch. Again, units are inherited.
  73. stim_lure = visual.Polygon(win,edges=3, size=GABOR_SIZE)  # A gabor patch. Again, units are inherited.
  74. stim_fix = visual.TextStim(win, '+', height=FIX_HEIGHT)  # Fixation cross is just the character "+". Units are inherited from Window when not explicitly specified.
  75. stim_text = visual.TextStim(win, pos=MESSAGE_POS, height=MESSAGE_HEIGHT, wrapWidth=999)  # Message / question stimulus. Will be used to display instructions and questions.
  76. sound_success = sound.Sound('C', secs=0.1, octave=6)  # Obs, ppc.Sound() is much more accurate, but only works on windows.
  77. sound_fail = sound.Sound('C', secs=0.4, octave=4)
  78. probe = visual.Line(win, size=[0.6], lineWidth=3 , start=(0, 0), end=(0.5, 0.5), lineColor =(1,-1,-1))
  79.  
  80. # Staircase for position
  81. staircase1 = data.QuestHandler(6, startValSd=2,
  82.     pThreshold=0.63, gamma=0.5, ntrial=1,
  83.     minVal=5, maxVal=14, range = 10)
  84.  
  85.  
  86. """
  87. FUNCTIONS
  88. """
  89.  
  90. def ask(text='', keyList=None):
  91.     """
  92.    Ask subject something. Shows question and returns answer (keypress)
  93.    and reaction time. Defaults to no text and all keys.
  94.    """
  95.     # Draw the TextStims to visual buffer, then show it and reset timing immediately (at stimulus onset)
  96.     stim_text.text = text
  97.     stim_text.draw()
  98.     time_flip = win.flip()  # time of core.monotonicClock.getTime() at flip
  99.  
  100.     # Halt everything and wait for (first) responses matching the keys given in the Q object.
  101.     if keyList:
  102.         keyList += KEYS_QUIT
  103.     key, time_key = event.waitKeys(keyList=keyList, timeStamped=True)[0]  # timestamped according to core.monotonicClock.getTime() at keypress. Select the first and only answer.
  104.     if key in KEYS_QUIT:  # Look at first reponse [0]. Quit everything if quit-key was pressed
  105.         core.quit()
  106.     return key, time_key - time_flip  # When answer given, return it.
  107.  
  108.  
  109. def make_trial_list(condition):
  110.     """
  111.    Return a list of trials (list of dictionaries) in advance of actually displaying them.
  112.    A makeTriallList('falseFix') with REPETITIONS=1 could generate a trial list like this::
  113.  
  114.    trial_list = [
  115.         {'xpos': 0, 'ori': 90, 'durationReal': '', 'response': '', 'condition': 'falseFix', 'subject': u'nick', 'rt': '', 'no': 0, 'gender': u'male', 'age': u'42', 'score': '', 'fixPos': -3},
  116.         {'xpos': -3, 'ori': 90, 'durationReal': '', 'response': '', 'condition': 'falseFix', 'subject': u'nick', 'rt': '', 'no': 1, 'gender': u'male', 'age': u'42', 'score': '', 'fixPos': 0},
  117.         {'xpos': -3, 'ori': 0, 'durationReal': '', 'response': '', 'condition': 'falseFix', 'subject': u'nick', 'rt': '', 'no': 2, 'gender': u'male', 'age': u'42', 'score': '', 'fixPos': 3},
  118.         {'xpos': 3, 'ori': 0, 'durationReal': '', 'response': '', 'condition': 'falseFix', 'subject': u'nick', 'rt': '', 'no': 3, 'gender': u'male', 'age': u'42', 'score': '', 'fixPos': -3},
  119.         {'xpos': 0, 'ori': 0, 'durationReal': '', 'response': '', 'condition': 'falseFix', 'subject': u'nick', 'rt': '', 'no': 4, 'gender': u'male', 'age': u'42', 'score': '', 'fixPos': -3},
  120.         {'xpos': 3, 'ori': 90, 'durationReal': '', 'response': '', 'condition': 'falseFix', 'subject': u'nick', 'rt': '', 'no': 5, 'gender': u'male', 'age': u'42', 'score': '', 'fixPos': -3}
  121.    ]
  122.  
  123.    Calling trials[0] gives the first trial, trials[1] the 2nd trial etc.
  124.    When the experiment executes, it will simply loop over these trials from first to last using
  125.  
  126.    for trial in trial_list:
  127.        # do something with the trial here
  128.  
  129.    It is suggested to keep all non-constant information in trials instead of
  130.    external variables, "non-constant" being what is not the same for all trials
  131.    and all subjects. This is stuff that you want to do statistics on.
  132.    Remember, the problem is never too much data - it's too little data.
  133.  
  134.    Note that every trial-dictionary should have the same fields. Note also
  135.    that there are placeholders for answers to be collected (e.g. ans and ansTime).
  136.  
  137.    You could use psychopy.data.TrialHandler instead of the code below, which
  138.    would be great for this simple case. But building your trials like below
  139.    is scaleable to more complex cases and you remain in control.
  140.    """
  141.  
  142.     # Factorial design
  143.     trial_list = []
  144.     for ori in ORIS.values():
  145.         for pos in POSITIONS:
  146.             for dur in FRAMES_STIM:
  147.                 for rep in range(REPETITIONS):
  148.                     # Add a dictionary for every trial
  149.                     trial_list += [{
  150.                         'ori': ori,
  151.                         'xpos': pos,
  152.                         'fixPos': random.choice([x for x in POSITIONS if x is not pos]),
  153.                         'subject': V['subject'],
  154.                         'age': V['age'],
  155.                         'gender': V['gender'],
  156.                         'condition': condition,
  157.                         'duration': dur,
  158.                         'durationReal': '',
  159.                         'response': '',
  160.                         'rt': '',
  161.                         'score': ''
  162.                     }]
  163.  
  164.     # Randomize order
  165.     from random import sample
  166.     trial_list = sample(trial_list, len(trial_list))
  167.  
  168.     # Add trial numbers and return
  169.     for i, trial in enumerate(trial_list):
  170.         trial['no'] = i + 1  # start at 1 instead of 0
  171.     return trial_list
  172.  
  173.  
  174. def run_condition(condition):
  175.     """
  176.    Runs a block of trials. This is the presentation of stimuli,
  177.    collection of responses and saving the trial
  178.    """
  179.     ask(TEXT_INSTRUCT)  # Instruction
  180.  
  181.     # Displays trials. Remember: prepare --> timing critical stuff --> score, save etc.
  182.     for trial in make_trial_list(condition):
  183.         # Prepare trial here, before entering the time-critical period
  184.         stim_gabor.ori = trial['ori']
  185.         stim_gabor.pos = [trial['xpos'], 0]
  186.         stim_lure.pos = [trial['fixPos'], 0]
  187.         stim_fix.pos = [0, 0]
  188.  
  189.         # A break at regular interval. Also at the beginning of the experiment
  190.         # Show break message when trial['no'] is a multiple of PAUSE_INTERVAL (% is modulus)
  191.         if trial['no'] % PAUSE_INTERVAL is 0:
  192.             ask(TEXT_BREAK)
  193.  
  194.         # ACTION: THIS IS THE TIMING CRITICAL PART
  195.         # Fixation cue
  196.         win.callOnFlip(clock.reset)
  197.         for frame in range(FRAMES_FIX):
  198.             stim_fix.draw()
  199.             win.flip()
  200.  
  201.         # Stimulus
  202.         for frame in range(trial['duration']):
  203.             stim_gabor.draw()
  204.             stim_lure.draw()
  205.             stim_fix.draw()
  206.             win.flip()
  207.  
  208.  
  209.         # Get actual duration at offset
  210.         #stim_fix.draw()
  211.         win.flip()  # blank screen
  212.         trial['durationReal'] = clock.getTime()
  213.        
  214.         # Probe
  215.         for frame in range(84):
  216.             probe.setEnd([trial['xpos'], 0])
  217.             probe.draw()
  218.             win.flip()
  219.            
  220.         # Present object (right or wrong)
  221.         stim_gabor.pos = [0, 0]
  222.         stim_gabor.draw()
  223.  
  224.  
  225.         # END OF TIMING CRITICAL SECTION
  226.         # Ask question and record responses.
  227.         #stim_fix.draw()
  228.         trial['response'], trial['rt'] = ask(' ', ORIS.keys())
  229.         trial['score'] = 1 if ORIS[trial['response']] == trial['ori'] else 0  # 1 if key corresponds to the shown orientation
  230.         sound_success.play() if trial['score'] else sound_fail.play()  # feedback
  231.  
  232.         # Save trial
  233.         writer.write(trial)
  234.  
  235.  
  236. """
  237. RUN EXPERIMENT
  238. Now it's really simple. You simply execute things using the functions ask and
  239. run_condition. Here we order block types given input from dialogue box
  240. """
  241. ask()
  242. if V['condition'] == 'trueFix':
  243.     run_condition('trueFix')
  244.     run_condition('falseFix')
  245.  
  246. elif V['condition'] == 'falseFix':
  247.     run_condition('falseFix')
  248.     run_condition('trueFix')
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement