#!/usr/bin/env python # -*- coding: utf-8 -*- """ This experiment was created using PsychoPy3 Experiment Builder (v2023.2.3), on Sat Dec 16 23:08:01 2023 If you publish work using this script the most relevant publication is: Peirce J, Gray JR, Simpson S, MacAskill M, Höchenberger R, Sogo H, Kastman E, Lindeløv JK. (2019) PsychoPy2: Experiments in behavior made easy Behav Res 51: 195. https://doi.org/10.3758/s13428-018-01193-y """ # --- Import packages --- from psychopy import locale_setup from psychopy import prefs from psychopy import plugins plugins.activatePlugins() prefs.hardware['audioLib'] = 'ptb' prefs.hardware['audioLatencyMode'] = '3' from psychopy import sound, gui, visual, core, data, event, logging, clock, colors, layout from psychopy.tools import environmenttools from psychopy.constants import (NOT_STARTED, STARTED, PLAYING, PAUSED, STOPPED, FINISHED, PRESSED, RELEASED, FOREVER, priority) import numpy as np # whole numpy lib is available, prepend 'np.' from numpy import (sin, cos, tan, log, log10, pi, average, sqrt, std, deg2rad, rad2deg, linspace, asarray) from numpy.random import random, randint, normal, shuffle, choice as randchoice import os # handy system and path functions import sys # to get file system encoding import psychopy.iohub as io from psychopy.hardware import keyboard # Run 'Before Experiment' code from parameter_inputs import os import datetime from time import sleep import argparse from device.data_client import NeuracleDataClient from device.trigger_box import TriggerNeuracle from device.fubo_pneumatic_finger import FuboPneumaticFingerClient from settings.config import settings from bci_core.online import Controller, model_loader from settings.config import settings config_info = settings.CONFIG_INFO fingermodel_ids_inverse = settings.FINGERMODEL_IDS_INVERSE # get train params def parse_args(): parser = argparse.ArgumentParser( description='Hand gesture train' ) parser.add_argument( '--subj', dest='subj', help='Subject name', default=None, type=str ) parser.add_argument( '--com', dest='com', help='Peripheral serial port', type=str ) parser.add_argument( '--state-change-threshold', '-scth', dest='state_change_threshold', help='Threshold for HMM state change', type=float ) parser.add_argument( '--state-trans-prob', '-stp', dest='state_trans_prob', help='Transition probability for HMM state change', default=0.8, type=float ) parser.add_argument( '--model-path', dest='model_path', help='Path to model file', default=None, type=str ) return parser.parse_args() args = parse_args() # load model input_kwargs = { 'state_trans_prob': args.state_trans_prob, 'state_change_threshold': args.state_change_threshold } control_model = model_loader(args.model_path, **input_kwargs) # build bci controller controller = Controller(0., control_model) # Run 'Before Experiment' code from device # connect neo receiver = NeuracleDataClient(n_channel=len(config_info['channel_labels']), samplerate=config_info['sample_rate'], host=config_info['host'], port=config_info['port'], buffer_len=config_info['buffer_length']) # connect to trigger box trigger = TriggerNeuracle() # connect to mechanical hand hand_device = FuboPneumaticFingerClient({'port': args.com}) # --- Setup global variables (available in all functions) --- # Ensure that relative paths start from the same directory as this script _thisDir = os.path.dirname(os.path.abspath(__file__)) # Store info about the experiment session psychopyVersion = '2023.2.3' expName = 'free_grasp' # from the Builder filename that created this script expInfo = { 'participant': f"{randint(0, 999999):06.0f}", 'session': '001', 'date': data.getDateStr(), # add a simple timestamp 'expName': expName, 'psychopyVersion': psychopyVersion, } def showExpInfoDlg(expInfo): """ Show participant info dialog. Parameters ========== expInfo : dict Information about this experiment, created by the `setupExpInfo` function. Returns ========== dict Information about this experiment. """ # temporarily remove keys which the dialog doesn't need to show poppedKeys = { 'date': expInfo.pop('date', data.getDateStr()), 'expName': expInfo.pop('expName', expName), 'psychopyVersion': expInfo.pop('psychopyVersion', psychopyVersion), } # show participant info dialog dlg = gui.DlgFromDict(dictionary=expInfo, sortKeys=False, title=expName) if dlg.OK == False: core.quit() # user pressed cancel # restore hidden keys expInfo.update(poppedKeys) # return expInfo return expInfo def setupData(expInfo, dataDir=None): """ Make an ExperimentHandler to handle trials and saving. Parameters ========== expInfo : dict Information about this experiment, created by the `setupExpInfo` function. dataDir : Path, str or None Folder to save the data to, leave as None to create a folder in the current directory. Returns ========== psychopy.data.ExperimentHandler Handler object for this experiment, contains the data to save and information about where to save it to. """ # data file name stem = absolute path + name; later add .psyexp, .csv, .log, etc if dataDir is None: dataDir = _thisDir filename = u'data/%s_%s_%s' % (expInfo['participant'], expName, expInfo['date']) # make sure filename is relative to dataDir if os.path.isabs(filename): dataDir = os.path.commonprefix([dataDir, filename]) filename = os.path.relpath(filename, dataDir) # an ExperimentHandler isn't essential but helps with data saving thisExp = data.ExperimentHandler( name=expName, version='', extraInfo=expInfo, runtimeInfo=None, originPath='/Users/dingkunliu/Projects/MI-BCI-Proj/kraken/backend/free_grasp.py', savePickle=True, saveWideText=True, dataFileName=dataDir + os.sep + filename, sortColumns='time' ) thisExp.setPriority('thisRow.t', priority.CRITICAL) thisExp.setPriority('expName', priority.LOW) # return experiment handler return thisExp def setupLogging(filename): """ Setup a log file and tell it what level to log at. Parameters ========== filename : str or pathlib.Path Filename to save log file and data files as, doesn't need an extension. Returns ========== psychopy.logging.LogFile Text stream to receive inputs from the logging system. """ # this outputs to the screen, not a file logging.console.setLevel(logging.DEBUG) # save a log file for detail verbose info logFile = logging.LogFile(filename+'.log', level=logging.DEBUG) return logFile def setupWindow(expInfo=None, win=None): """ Setup the Window Parameters ========== expInfo : dict Information about this experiment, created by the `setupExpInfo` function. win : psychopy.visual.Window Window to setup - leave as None to create a new window. Returns ========== psychopy.visual.Window Window in which to run this experiment. """ if win is None: # if not given a window to setup, make one win = visual.Window( size=[1920, 1080], fullscr=True, screen=0, winType='pyglet', allowStencil=False, monitor='testMonitor', color=[1,1,1], colorSpace='rgb', backgroundImage='', backgroundFit='none', blendMode='avg', useFBO=True, units='height' ) if expInfo is not None: # store frame rate of monitor if we can measure it expInfo['frameRate'] = win.getActualFrameRate() else: # if we have a window, just set the attributes which are safe to set win.color = [1,1,1] win.colorSpace = 'rgb' win.backgroundImage = '' win.backgroundFit = 'none' win.units = 'height' win.mouseVisible = True win.hideMessage() return win def setupInputs(expInfo, thisExp, win): """ Setup whatever inputs are available (mouse, keyboard, eyetracker, etc.) Parameters ========== expInfo : dict Information about this experiment, created by the `setupExpInfo` function. thisExp : psychopy.data.ExperimentHandler Handler object for this experiment, contains the data to save and information about where to save it to. win : psychopy.visual.Window Window in which to run this experiment. Returns ========== dict Dictionary of input devices by name. """ # --- Setup input devices --- inputs = {} ioConfig = {} # Setup iohub keyboard ioConfig['Keyboard'] = dict(use_keymap='psychopy') ioSession = '1' if 'session' in expInfo: ioSession = str(expInfo['session']) ioServer = io.launchHubServer(window=win, **ioConfig) eyetracker = None # create a default keyboard (e.g. to check for escape) defaultKeyboard = keyboard.Keyboard(backend='iohub') # return inputs dict return { 'ioServer': ioServer, 'defaultKeyboard': defaultKeyboard, 'eyetracker': eyetracker, } def pauseExperiment(thisExp, inputs=None, win=None, timers=[], playbackComponents=[]): """ Pause this experiment, preventing the flow from advancing to the next routine until resumed. Parameters ========== thisExp : psychopy.data.ExperimentHandler Handler object for this experiment, contains the data to save and information about where to save it to. inputs : dict Dictionary of input devices by name. win : psychopy.visual.Window Window for this experiment. timers : list, tuple List of timers to reset once pausing is finished. playbackComponents : list, tuple List of any components with a `pause` method which need to be paused. """ # if we are not paused, do nothing if thisExp.status != PAUSED: return # pause any playback components for comp in playbackComponents: comp.pause() # prevent components from auto-drawing win.stashAutoDraw() # run a while loop while we wait to unpause while thisExp.status == PAUSED: # make sure we have a keyboard if inputs is None: inputs = { 'defaultKeyboard': keyboard.Keyboard(backend='ioHub') } # check for quit (typically the Esc key) if inputs['defaultKeyboard'].getKeys(keyList=['escape']): endExperiment(thisExp, win=win, inputs=inputs) # flip the screen win.flip() # if stop was requested while paused, quit if thisExp.status == FINISHED: endExperiment(thisExp, inputs=inputs, win=win) # resume any playback components for comp in playbackComponents: comp.play() # restore auto-drawn components win.retrieveAutoDraw() # reset any timers for timer in timers: timer.reset() def run(expInfo, thisExp, win, inputs, globalClock=None, thisSession=None): """ Run the experiment flow. Parameters ========== expInfo : dict Information about this experiment, created by the `setupExpInfo` function. thisExp : psychopy.data.ExperimentHandler Handler object for this experiment, contains the data to save and information about where to save it to. psychopy.visual.Window Window in which to run this experiment. inputs : dict Dictionary of input devices by name. globalClock : psychopy.core.clock.Clock or None Clock to get global time from - supply None to make a new one. thisSession : psychopy.session.Session or None Handle of the Session object this experiment is being run from, if any. """ # mark experiment as started thisExp.status = STARTED # make sure variables created by exec are available globally exec = environmenttools.setExecEnvironment(globals()) # get device handles from dict of input devices ioServer = inputs['ioServer'] defaultKeyboard = inputs['defaultKeyboard'] eyetracker = inputs['eyetracker'] # make sure we're running in the directory for this experiment os.chdir(_thisDir) # get filename from ExperimentHandler for convenience filename = thisExp.dataFileName frameTolerance = 0.001 # how close to onset before 'same' frame endExpNow = False # flag for 'escape' or other condition => quit the exp # get frame duration from frame rate in expInfo if 'frameRate' in expInfo and expInfo['frameRate'] is not None: frameDur = 1.0 / round(expInfo['frameRate']) else: frameDur = 1.0 / 60.0 # could not measure, so guess # Start Code - component code to be run after the window creation # --- Initialize components for Routine "initialize" --- text = visual.TextStim(win=win, name='text', text='您将在接下来的任务中自主控制气动手,\n进度条提示您当前时刻的抓握力度。\n希望气动手握紧请用力尝试握手,\n希望气动手松开请尝试放松。\n按空格键继续', font='Open Sans', pos=(0, 0), height=0.05, wrapWidth=None, ori=0.0, color='black', colorSpace='rgb', opacity=None, languageStyle='LTR', depth=0.0); key_resp = keyboard.Keyboard() # --- Initialize components for Routine "decision" --- feedback_bar = visual.Progress( win, name='feedback_bar', progress=0, pos=(0, -0.25), size=(0.5, 0.1), anchor='bottom-left', units='height', barColor='black', backColor=None, borderColor='black', colorSpace='rgb', lineWidth=4.0, opacity=1.0, ori=270.0, depth=0 ) # --- Initialize components for Routine "feedback" --- feedback_bar1 = visual.Progress( win, name='feedback_bar1', progress=0, pos=(0, -0.25), size=(0.5, 0.1), anchor='bottom-left', units='height', barColor='black', backColor=None, borderColor='black', colorSpace='rgb', lineWidth=4.0, opacity=1.0, ori=270.0, depth=-1 ) # create some handy timers if globalClock is None: globalClock = core.Clock() # to track the time since experiment started if ioServer is not None: ioServer.syncClock(globalClock) logging.setDefaultClock(globalClock) routineTimer = core.Clock() # to track time remaining of each (possibly non-slip) routine win.flip() # flip window to reset last flip timer # store the exact time the global clock started expInfo['expStart'] = data.getDateStr(format='%Y-%m-%d %Hh%M.%S.%f %z', fractionalSecondDigits=6) # --- Prepare to start Routine "initialize" --- continueRoutine = True # update component parameters for each repeat thisExp.addData('initialize.started', globalClock.getTime()) key_resp.keys = [] key_resp.rt = [] _key_resp_allKeys = [] # keep track of which components have finished initializeComponents = [text, key_resp] for thisComponent in initializeComponents: thisComponent.tStart = None thisComponent.tStop = None thisComponent.tStartRefresh = None thisComponent.tStopRefresh = None if hasattr(thisComponent, 'status'): thisComponent.status = NOT_STARTED # reset timers t = 0 _timeToFirstFrame = win.getFutureFlipTime(clock="now") frameN = -1 # --- Run Routine "initialize" --- routineForceEnded = not continueRoutine while continueRoutine: # get current time t = routineTimer.getTime() tThisFlip = win.getFutureFlipTime(clock=routineTimer) tThisFlipGlobal = win.getFutureFlipTime(clock=None) frameN = frameN + 1 # number of completed frames (so 0 is the first frame) # update/draw components on each frame # *text* updates # if text is starting this frame... if text.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance: # keep track of start time/frame for later text.frameNStart = frameN # exact frame index text.tStart = t # local t and not account for scr refresh text.tStartRefresh = tThisFlipGlobal # on global time win.timeOnFlip(text, 'tStartRefresh') # time at next scr refresh # add timestamp to datafile thisExp.timestampOnFlip(win, 'text.started') # update status text.status = STARTED text.setAutoDraw(True) # if text is active this frame... if text.status == STARTED: # update params pass # *key_resp* updates waitOnFlip = False # if key_resp is starting this frame... if key_resp.status == NOT_STARTED and tThisFlip >= 1-frameTolerance: # keep track of start time/frame for later key_resp.frameNStart = frameN # exact frame index key_resp.tStart = t # local t and not account for scr refresh key_resp.tStartRefresh = tThisFlipGlobal # on global time win.timeOnFlip(key_resp, 'tStartRefresh') # time at next scr refresh # add timestamp to datafile thisExp.timestampOnFlip(win, 'key_resp.started') # update status key_resp.status = STARTED # keyboard checking is just starting waitOnFlip = True win.callOnFlip(key_resp.clock.reset) # t=0 on next screen flip win.callOnFlip(key_resp.clearEvents, eventType='keyboard') # clear events on next screen flip if key_resp.status == STARTED and not waitOnFlip: theseKeys = key_resp.getKeys(keyList=['space'], ignoreKeys=["escape"], waitRelease=False) _key_resp_allKeys.extend(theseKeys) if len(_key_resp_allKeys): key_resp.keys = _key_resp_allKeys[-1].name # just the last key pressed key_resp.rt = _key_resp_allKeys[-1].rt key_resp.duration = _key_resp_allKeys[-1].duration # a response ends the routine continueRoutine = False # check for quit (typically the Esc key) if defaultKeyboard.getKeys(keyList=["escape"]): thisExp.status = FINISHED if thisExp.status == FINISHED or endExpNow: endExperiment(thisExp, inputs=inputs, win=win) return # check if all components have finished if not continueRoutine: # a component has requested a forced-end of Routine routineForceEnded = True break continueRoutine = False # will revert to True if at least one component still running for thisComponent in initializeComponents: if hasattr(thisComponent, "status") and thisComponent.status != FINISHED: continueRoutine = True break # at least one component has not yet finished # refresh the screen if continueRoutine: # don't flip if this routine is over or we'll get a blank screen win.flip() # --- Ending Routine "initialize" --- for thisComponent in initializeComponents: if hasattr(thisComponent, "setAutoDraw"): thisComponent.setAutoDraw(False) thisExp.addData('initialize.stopped', globalClock.getTime()) # check responses if key_resp.keys in ['', [], None]: # No response was made key_resp.keys = None thisExp.addData('key_resp.keys',key_resp.keys) if key_resp.keys != None: # we had a response thisExp.addData('key_resp.rt', key_resp.rt) thisExp.addData('key_resp.duration', key_resp.duration) thisExp.nextEntry() # the Routine "initialize" was not non-slip safe, so reset the non-slip timer routineTimer.reset() # set up handler to look after randomisation of conditions etc trials = data.TrialHandler(nReps=10000.0, method='random', extraInfo=expInfo, originPath=-1, trialList=[None], seed=None, name='trials') thisExp.addLoop(trials) # add the loop to the experiment thisTrial = trials.trialList[0] # so we can initialise stimuli with some values # abbreviate parameter names if possible (e.g. rgb = thisTrial.rgb) if thisTrial != None: for paramName in thisTrial: globals()[paramName] = thisTrial[paramName] for thisTrial in trials: currentLoop = trials thisExp.timestampOnFlip(win, 'thisRow.t') # pause experiment here if requested if thisExp.status == PAUSED: pauseExperiment( thisExp=thisExp, inputs=inputs, win=win, timers=[routineTimer], playbackComponents=[] ) # abbreviate parameter names if possible (e.g. rgb = thisTrial.rgb) if thisTrial != None: for paramName in thisTrial: globals()[paramName] = thisTrial[paramName] # --- Prepare to start Routine "decision" --- continueRoutine = True # update component parameters for each repeat thisExp.addData('decision.started', globalClock.getTime()) # Run 'Begin Routine' code from decision # decision data_from_buffer = receiver.get_trial_data(clear=False) decision = controller.decision(data_from_buffer, None) force = controller.real_feedback_model.probability feedback_bar.progress = force # logging logging.exp('decision: {}'.format(decision)) logging.exp('probability: {}'.format(force)) # keep track of which components have finished decisionComponents = [feedback_bar] for thisComponent in decisionComponents: thisComponent.tStart = None thisComponent.tStop = None thisComponent.tStartRefresh = None thisComponent.tStopRefresh = None if hasattr(thisComponent, 'status'): thisComponent.status = NOT_STARTED # reset timers t = 0 _timeToFirstFrame = win.getFutureFlipTime(clock="now") frameN = -1 # --- Run Routine "decision" --- routineForceEnded = not continueRoutine while continueRoutine and routineTimer.getTime() < 0.1: # get current time t = routineTimer.getTime() tThisFlip = win.getFutureFlipTime(clock=routineTimer) tThisFlipGlobal = win.getFutureFlipTime(clock=None) frameN = frameN + 1 # number of completed frames (so 0 is the first frame) # update/draw components on each frame # *feedback_bar* updates # if feedback_bar is starting this frame... if feedback_bar.status == NOT_STARTED and tThisFlip >= 0-frameTolerance: # keep track of start time/frame for later feedback_bar.frameNStart = frameN # exact frame index feedback_bar.tStart = t # local t and not account for scr refresh feedback_bar.tStartRefresh = tThisFlipGlobal # on global time win.timeOnFlip(feedback_bar, 'tStartRefresh') # time at next scr refresh # add timestamp to datafile thisExp.timestampOnFlip(win, 'feedback_bar.started') # update status feedback_bar.status = STARTED feedback_bar.setAutoDraw(True) # if feedback_bar is active this frame... if feedback_bar.status == STARTED: # update params pass # if feedback_bar is stopping this frame... if feedback_bar.status == STARTED: # is it time to stop? (based on global clock, using actual start) if tThisFlipGlobal > feedback_bar.tStartRefresh + 0.1-frameTolerance: # keep track of stop time/frame for later feedback_bar.tStop = t # not accounting for scr refresh feedback_bar.frameNStop = frameN # exact frame index # add timestamp to datafile thisExp.timestampOnFlip(win, 'feedback_bar.stopped') # update status feedback_bar.status = FINISHED feedback_bar.setAutoDraw(False) # check for quit (typically the Esc key) if defaultKeyboard.getKeys(keyList=["escape"]): thisExp.status = FINISHED if thisExp.status == FINISHED or endExpNow: endExperiment(thisExp, inputs=inputs, win=win) return # check if all components have finished if not continueRoutine: # a component has requested a forced-end of Routine routineForceEnded = True break continueRoutine = False # will revert to True if at least one component still running for thisComponent in decisionComponents: if hasattr(thisComponent, "status") and thisComponent.status != FINISHED: continueRoutine = True break # at least one component has not yet finished # refresh the screen if continueRoutine: # don't flip if this routine is over or we'll get a blank screen win.flip() # --- Ending Routine "decision" --- for thisComponent in decisionComponents: if hasattr(thisComponent, "setAutoDraw"): thisComponent.setAutoDraw(False) thisExp.addData('decision.stopped', globalClock.getTime()) # using non-slip timing so subtract the expected duration of this Routine (unless ended on request) if routineForceEnded: routineTimer.reset() else: routineTimer.addTime(-0.100000) # --- Prepare to start Routine "feedback" --- continueRoutine = True # update component parameters for each repeat thisExp.addData('feedback.started', globalClock.getTime()) # Run 'Begin Routine' code from send_feedback # state changed feedback_bar1.progress = force if decision != -1: feedback_time = 3 trigger.send_trigger(int(decision)) hand_device.start(model=fingermodel_ids_inverse[decision]) else: feedback_time = 0 # keep track of which components have finished feedbackComponents = [feedback_bar1] for thisComponent in feedbackComponents: thisComponent.tStart = None thisComponent.tStop = None thisComponent.tStartRefresh = None thisComponent.tStopRefresh = None if hasattr(thisComponent, 'status'): thisComponent.status = NOT_STARTED # reset timers t = 0 _timeToFirstFrame = win.getFutureFlipTime(clock="now") frameN = -1 # --- Run Routine "feedback" --- routineForceEnded = not continueRoutine while continueRoutine: # get current time t = routineTimer.getTime() tThisFlip = win.getFutureFlipTime(clock=routineTimer) tThisFlipGlobal = win.getFutureFlipTime(clock=None) frameN = frameN + 1 # number of completed frames (so 0 is the first frame) # update/draw components on each frame # *feedback_bar1* updates # if feedback_bar1 is starting this frame... if feedback_bar1.status == NOT_STARTED and tThisFlip >= 0-frameTolerance: # keep track of start time/frame for later feedback_bar1.frameNStart = frameN # exact frame index feedback_bar1.tStart = t # local t and not account for scr refresh feedback_bar1.tStartRefresh = tThisFlipGlobal # on global time win.timeOnFlip(feedback_bar1, 'tStartRefresh') # time at next scr refresh # add timestamp to datafile thisExp.timestampOnFlip(win, 'feedback_bar1.started') # update status feedback_bar1.status = STARTED feedback_bar1.setAutoDraw(True) # if feedback_bar1 is active this frame... if feedback_bar1.status == STARTED: # update params pass # if feedback_bar1 is stopping this frame... if feedback_bar1.status == STARTED: # is it time to stop? (based on global clock, using actual start) if tThisFlipGlobal > feedback_bar1.tStartRefresh + feedback_time-frameTolerance: # keep track of stop time/frame for later feedback_bar1.tStop = t # not accounting for scr refresh feedback_bar1.frameNStop = frameN # exact frame index # add timestamp to datafile thisExp.timestampOnFlip(win, 'feedback_bar1.stopped') # update status feedback_bar1.status = FINISHED feedback_bar1.setAutoDraw(False) # check for quit (typically the Esc key) if defaultKeyboard.getKeys(keyList=["escape"]): thisExp.status = FINISHED if thisExp.status == FINISHED or endExpNow: endExperiment(thisExp, inputs=inputs, win=win) return # check if all components have finished if not continueRoutine: # a component has requested a forced-end of Routine routineForceEnded = True break continueRoutine = False # will revert to True if at least one component still running for thisComponent in feedbackComponents: if hasattr(thisComponent, "status") and thisComponent.status != FINISHED: continueRoutine = True break # at least one component has not yet finished # refresh the screen if continueRoutine: # don't flip if this routine is over or we'll get a blank screen win.flip() # --- Ending Routine "feedback" --- for thisComponent in feedbackComponents: if hasattr(thisComponent, "setAutoDraw"): thisComponent.setAutoDraw(False) thisExp.addData('feedback.stopped', globalClock.getTime()) # the Routine "feedback" was not non-slip safe, so reset the non-slip timer routineTimer.reset() thisExp.nextEntry() if thisSession is not None: # if running in a Session with a Liaison client, send data up to now thisSession.sendExperimentData() # completed 10000.0 repeats of 'trials' # Run 'End Experiment' code from device receiver.close() # mark experiment as finished endExperiment(thisExp, win=win, inputs=inputs) def saveData(thisExp): """ Save data from this experiment Parameters ========== thisExp : psychopy.data.ExperimentHandler Handler object for this experiment, contains the data to save and information about where to save it to. """ filename = thisExp.dataFileName # these shouldn't be strictly necessary (should auto-save) thisExp.saveAsWideText(filename + '.csv', delim='auto') thisExp.saveAsPickle(filename) def endExperiment(thisExp, inputs=None, win=None): """ End this experiment, performing final shut down operations. This function does NOT close the window or end the Python process - use `quit` for this. Parameters ========== thisExp : psychopy.data.ExperimentHandler Handler object for this experiment, contains the data to save and information about where to save it to. inputs : dict Dictionary of input devices by name. win : psychopy.visual.Window Window for this experiment. """ if win is not None: # remove autodraw from all current components win.clearAutoDraw() # Flip one final time so any remaining win.callOnFlip() # and win.timeOnFlip() tasks get executed win.flip() # mark experiment handler as finished thisExp.status = FINISHED # shut down eyetracker, if there is one if inputs is not None: if 'eyetracker' in inputs and inputs['eyetracker'] is not None: inputs['eyetracker'].setConnectionState(False) logging.flush() def quit(thisExp, win=None, inputs=None, thisSession=None): """ Fully quit, closing the window and ending the Python process. Parameters ========== win : psychopy.visual.Window Window to close. inputs : dict Dictionary of input devices by name. thisSession : psychopy.session.Session or None Handle of the Session object this experiment is being run from, if any. """ thisExp.abort() # or data files will save again on exit # make sure everything is closed down if win is not None: # Flip one final time so any remaining win.callOnFlip() # and win.timeOnFlip() tasks get executed before quitting win.flip() win.close() if inputs is not None: if 'eyetracker' in inputs and inputs['eyetracker'] is not None: inputs['eyetracker'].setConnectionState(False) logging.flush() if thisSession is not None: thisSession.stop() # terminate Python process core.quit() # if running this experiment as a script... if __name__ == '__main__': # call all functions in order expInfo = showExpInfoDlg(expInfo=expInfo) thisExp = setupData(expInfo=expInfo) logFile = setupLogging(filename=thisExp.dataFileName) win = setupWindow(expInfo=expInfo) inputs = setupInputs(expInfo=expInfo, thisExp=thisExp, win=win) run( expInfo=expInfo, thisExp=thisExp, win=win, inputs=inputs ) saveData(thisExp=thisExp) quit(thisExp=thisExp, win=win, inputs=inputs)