2
0

free_grasp.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """
  4. This experiment was created using PsychoPy3 Experiment Builder (v2023.2.3),
  5. on Tue Nov 28 19:17:09 2023
  6. If you publish work using this script the most relevant publication is:
  7. Peirce J, Gray JR, Simpson S, MacAskill M, Höchenberger R, Sogo H, Kastman E, Lindeløv JK. (2019)
  8. PsychoPy2: Experiments in behavior made easy Behav Res 51: 195.
  9. https://doi.org/10.3758/s13428-018-01193-y
  10. """
  11. # --- Import packages ---
  12. from psychopy import locale_setup
  13. from psychopy import prefs
  14. from psychopy import plugins
  15. plugins.activatePlugins()
  16. prefs.hardware['audioLib'] = 'ptb'
  17. prefs.hardware['audioLatencyMode'] = '3'
  18. from psychopy import sound, gui, visual, core, data, event, logging, clock, colors, layout
  19. from psychopy.tools import environmenttools
  20. from psychopy.constants import (NOT_STARTED, STARTED, PLAYING, PAUSED,
  21. STOPPED, FINISHED, PRESSED, RELEASED, FOREVER, priority)
  22. import numpy as np # whole numpy lib is available, prepend 'np.'
  23. from numpy import (sin, cos, tan, log, log10, pi, average,
  24. sqrt, std, deg2rad, rad2deg, linspace, asarray)
  25. from numpy.random import random, randint, normal, shuffle, choice as randchoice
  26. import os # handy system and path functions
  27. import sys # to get file system encoding
  28. import psychopy.iohub as io
  29. from psychopy.hardware import keyboard
  30. # Run 'Before Experiment' code from parameter_inputs
  31. import os
  32. import datetime
  33. from time import sleep
  34. import argparse
  35. from device.data_client import NeuracleDataClient
  36. from device.trigger_box import TriggerNeuracle
  37. from device.fubo_pneumatic_finger import FuboPneumaticFingerClient
  38. from settings.config import settings
  39. from bci_core.online import Controller
  40. from settings.config import settings
  41. config_info = settings.CONFIG_INFO
  42. fingermodel_ids_inverse = settings.FINGERMODEL_IDS_INVERSE
  43. # get train params
  44. def parse_args():
  45. parser = argparse.ArgumentParser(
  46. description='Hand gesture train'
  47. )
  48. parser.add_argument(
  49. '--subj',
  50. dest='subj',
  51. help='Subject name',
  52. default=None,
  53. type=str
  54. )
  55. parser.add_argument(
  56. '--com',
  57. dest='com',
  58. help='Peripheral serial port',
  59. type=str
  60. )
  61. parser.add_argument(
  62. '--state-change-threshold',
  63. '-scth',
  64. dest='state_change_threshold',
  65. help='Threshold for HMM state change',
  66. type=float
  67. )
  68. parser.add_argument(
  69. '--model-path',
  70. dest='model_path',
  71. help='Path to model file',
  72. default=None,
  73. type=str
  74. )
  75. return parser.parse_args()
  76. args = parse_args()
  77. # build bci controller
  78. controller = Controller(0., args.model_path,
  79. state_change_threshold=args.state_change_threshold)
  80. # Run 'Before Experiment' code from device
  81. # connect neo
  82. receiver = NeuracleDataClient(n_channel=len(config_info['channel_labels']),
  83. samplerate=config_info['sample_rate'],
  84. host=config_info['host'],
  85. port=config_info['port'],
  86. buffer_len=config_info['buffer_length'])
  87. # connect to trigger box
  88. trigger = TriggerNeuracle()
  89. # connect to mechanical hand
  90. hand_device = FuboPneumaticFingerClient({'port': args.com})
  91. # --- Setup global variables (available in all functions) ---
  92. # Ensure that relative paths start from the same directory as this script
  93. _thisDir = os.path.dirname(os.path.abspath(__file__))
  94. # Store info about the experiment session
  95. psychopyVersion = '2023.2.3'
  96. expName = 'free_grasp' # from the Builder filename that created this script
  97. expInfo = {
  98. 'participant': f"{randint(0, 999999):06.0f}",
  99. 'session': '001',
  100. 'date': data.getDateStr(), # add a simple timestamp
  101. 'expName': expName,
  102. 'psychopyVersion': psychopyVersion,
  103. }
  104. def showExpInfoDlg(expInfo):
  105. """
  106. Show participant info dialog.
  107. Parameters
  108. ==========
  109. expInfo : dict
  110. Information about this experiment, created by the `setupExpInfo` function.
  111. Returns
  112. ==========
  113. dict
  114. Information about this experiment.
  115. """
  116. # temporarily remove keys which the dialog doesn't need to show
  117. poppedKeys = {
  118. 'date': expInfo.pop('date', data.getDateStr()),
  119. 'expName': expInfo.pop('expName', expName),
  120. 'psychopyVersion': expInfo.pop('psychopyVersion', psychopyVersion),
  121. }
  122. # show participant info dialog
  123. dlg = gui.DlgFromDict(dictionary=expInfo, sortKeys=False, title=expName)
  124. if dlg.OK == False:
  125. core.quit() # user pressed cancel
  126. # restore hidden keys
  127. expInfo.update(poppedKeys)
  128. # return expInfo
  129. return expInfo
  130. def setupData(expInfo, dataDir=None):
  131. """
  132. Make an ExperimentHandler to handle trials and saving.
  133. Parameters
  134. ==========
  135. expInfo : dict
  136. Information about this experiment, created by the `setupExpInfo` function.
  137. dataDir : Path, str or None
  138. Folder to save the data to, leave as None to create a folder in the current directory.
  139. Returns
  140. ==========
  141. psychopy.data.ExperimentHandler
  142. Handler object for this experiment, contains the data to save and information about
  143. where to save it to.
  144. """
  145. # data file name stem = absolute path + name; later add .psyexp, .csv, .log, etc
  146. if dataDir is None:
  147. dataDir = _thisDir
  148. filename = u'data/%s_%s_%s' % (expInfo['participant'], expName, expInfo['date'])
  149. # make sure filename is relative to dataDir
  150. if os.path.isabs(filename):
  151. dataDir = os.path.commonprefix([dataDir, filename])
  152. filename = os.path.relpath(filename, dataDir)
  153. # an ExperimentHandler isn't essential but helps with data saving
  154. thisExp = data.ExperimentHandler(
  155. name=expName, version='',
  156. extraInfo=expInfo, runtimeInfo=None,
  157. originPath='/Users/dingkunliu/Projects/MI-BCI-Proj/kraken/backend/free_grasp.py',
  158. savePickle=True, saveWideText=True,
  159. dataFileName=dataDir + os.sep + filename, sortColumns='time'
  160. )
  161. thisExp.setPriority('thisRow.t', priority.CRITICAL)
  162. thisExp.setPriority('expName', priority.LOW)
  163. # return experiment handler
  164. return thisExp
  165. def setupLogging(filename):
  166. """
  167. Setup a log file and tell it what level to log at.
  168. Parameters
  169. ==========
  170. filename : str or pathlib.Path
  171. Filename to save log file and data files as, doesn't need an extension.
  172. Returns
  173. ==========
  174. psychopy.logging.LogFile
  175. Text stream to receive inputs from the logging system.
  176. """
  177. # this outputs to the screen, not a file
  178. logging.console.setLevel(logging.DEBUG)
  179. # save a log file for detail verbose info
  180. logFile = logging.LogFile(filename+'.log', level=logging.DEBUG)
  181. return logFile
  182. def setupWindow(expInfo=None, win=None):
  183. """
  184. Setup the Window
  185. Parameters
  186. ==========
  187. expInfo : dict
  188. Information about this experiment, created by the `setupExpInfo` function.
  189. win : psychopy.visual.Window
  190. Window to setup - leave as None to create a new window.
  191. Returns
  192. ==========
  193. psychopy.visual.Window
  194. Window in which to run this experiment.
  195. """
  196. if win is None:
  197. # if not given a window to setup, make one
  198. win = visual.Window(
  199. size=[1920, 1080], fullscr=True, screen=0,
  200. winType='pyglet', allowStencil=False,
  201. monitor='testMonitor', color=[1,1,1], colorSpace='rgb',
  202. backgroundImage='', backgroundFit='none',
  203. blendMode='avg', useFBO=True,
  204. units='height'
  205. )
  206. if expInfo is not None:
  207. # store frame rate of monitor if we can measure it
  208. expInfo['frameRate'] = win.getActualFrameRate()
  209. else:
  210. # if we have a window, just set the attributes which are safe to set
  211. win.color = [1,1,1]
  212. win.colorSpace = 'rgb'
  213. win.backgroundImage = ''
  214. win.backgroundFit = 'none'
  215. win.units = 'height'
  216. win.mouseVisible = True
  217. win.hideMessage()
  218. return win
  219. def setupInputs(expInfo, thisExp, win):
  220. """
  221. Setup whatever inputs are available (mouse, keyboard, eyetracker, etc.)
  222. Parameters
  223. ==========
  224. expInfo : dict
  225. Information about this experiment, created by the `setupExpInfo` function.
  226. thisExp : psychopy.data.ExperimentHandler
  227. Handler object for this experiment, contains the data to save and information about
  228. where to save it to.
  229. win : psychopy.visual.Window
  230. Window in which to run this experiment.
  231. Returns
  232. ==========
  233. dict
  234. Dictionary of input devices by name.
  235. """
  236. # --- Setup input devices ---
  237. inputs = {}
  238. ioConfig = {}
  239. # Setup iohub keyboard
  240. ioConfig['Keyboard'] = dict(use_keymap='psychopy')
  241. ioSession = '1'
  242. if 'session' in expInfo:
  243. ioSession = str(expInfo['session'])
  244. ioServer = io.launchHubServer(window=win, **ioConfig)
  245. eyetracker = None
  246. # create a default keyboard (e.g. to check for escape)
  247. defaultKeyboard = keyboard.Keyboard(backend='iohub')
  248. # return inputs dict
  249. return {
  250. 'ioServer': ioServer,
  251. 'defaultKeyboard': defaultKeyboard,
  252. 'eyetracker': eyetracker,
  253. }
  254. def pauseExperiment(thisExp, inputs=None, win=None, timers=[], playbackComponents=[]):
  255. """
  256. Pause this experiment, preventing the flow from advancing to the next routine until resumed.
  257. Parameters
  258. ==========
  259. thisExp : psychopy.data.ExperimentHandler
  260. Handler object for this experiment, contains the data to save and information about
  261. where to save it to.
  262. inputs : dict
  263. Dictionary of input devices by name.
  264. win : psychopy.visual.Window
  265. Window for this experiment.
  266. timers : list, tuple
  267. List of timers to reset once pausing is finished.
  268. playbackComponents : list, tuple
  269. List of any components with a `pause` method which need to be paused.
  270. """
  271. # if we are not paused, do nothing
  272. if thisExp.status != PAUSED:
  273. return
  274. # pause any playback components
  275. for comp in playbackComponents:
  276. comp.pause()
  277. # prevent components from auto-drawing
  278. win.stashAutoDraw()
  279. # run a while loop while we wait to unpause
  280. while thisExp.status == PAUSED:
  281. # make sure we have a keyboard
  282. if inputs is None:
  283. inputs = {
  284. 'defaultKeyboard': keyboard.Keyboard(backend='ioHub')
  285. }
  286. # check for quit (typically the Esc key)
  287. if inputs['defaultKeyboard'].getKeys(keyList=['escape']):
  288. endExperiment(thisExp, win=win, inputs=inputs)
  289. # flip the screen
  290. win.flip()
  291. # if stop was requested while paused, quit
  292. if thisExp.status == FINISHED:
  293. endExperiment(thisExp, inputs=inputs, win=win)
  294. # resume any playback components
  295. for comp in playbackComponents:
  296. comp.play()
  297. # restore auto-drawn components
  298. win.retrieveAutoDraw()
  299. # reset any timers
  300. for timer in timers:
  301. timer.reset()
  302. def run(expInfo, thisExp, win, inputs, globalClock=None, thisSession=None):
  303. """
  304. Run the experiment flow.
  305. Parameters
  306. ==========
  307. expInfo : dict
  308. Information about this experiment, created by the `setupExpInfo` function.
  309. thisExp : psychopy.data.ExperimentHandler
  310. Handler object for this experiment, contains the data to save and information about
  311. where to save it to.
  312. psychopy.visual.Window
  313. Window in which to run this experiment.
  314. inputs : dict
  315. Dictionary of input devices by name.
  316. globalClock : psychopy.core.clock.Clock or None
  317. Clock to get global time from - supply None to make a new one.
  318. thisSession : psychopy.session.Session or None
  319. Handle of the Session object this experiment is being run from, if any.
  320. """
  321. # mark experiment as started
  322. thisExp.status = STARTED
  323. # make sure variables created by exec are available globally
  324. exec = environmenttools.setExecEnvironment(globals())
  325. # get device handles from dict of input devices
  326. ioServer = inputs['ioServer']
  327. defaultKeyboard = inputs['defaultKeyboard']
  328. eyetracker = inputs['eyetracker']
  329. # make sure we're running in the directory for this experiment
  330. os.chdir(_thisDir)
  331. # get filename from ExperimentHandler for convenience
  332. filename = thisExp.dataFileName
  333. frameTolerance = 0.001 # how close to onset before 'same' frame
  334. endExpNow = False # flag for 'escape' or other condition => quit the exp
  335. # get frame duration from frame rate in expInfo
  336. if 'frameRate' in expInfo and expInfo['frameRate'] is not None:
  337. frameDur = 1.0 / round(expInfo['frameRate'])
  338. else:
  339. frameDur = 1.0 / 60.0 # could not measure, so guess
  340. # Start Code - component code to be run after the window creation
  341. # --- Initialize components for Routine "initialize" ---
  342. text = visual.TextStim(win=win, name='text',
  343. text='您将在接下来的任务中自主控制气动手,\n进度条提示您当前时刻的抓握力度。\n希望气动手握紧请用力尝试握手,\n希望气动手松开请尝试放松。\n按空格键继续',
  344. font='Open Sans',
  345. pos=(0, 0), height=0.05, wrapWidth=None, ori=0.0,
  346. color='black', colorSpace='rgb', opacity=None,
  347. languageStyle='LTR',
  348. depth=0.0);
  349. key_resp = keyboard.Keyboard()
  350. # --- Initialize components for Routine "decision" ---
  351. feedback_bar = visual.Progress(
  352. win, name='feedback_bar',
  353. progress=0,
  354. pos=(0, -0.25), size=(0.5, 0.1), anchor='bottom-left', units='height',
  355. barColor='black', backColor=None, borderColor='black', colorSpace='rgb',
  356. lineWidth=4.0, opacity=1.0, ori=270.0,
  357. depth=0
  358. )
  359. # --- Initialize components for Routine "feedback" ---
  360. feedback_bar1 = visual.Progress(
  361. win, name='feedback_bar1',
  362. progress=0,
  363. pos=(0, -0.25), size=(0.5, 0.1), anchor='bottom-left', units='height',
  364. barColor='black', backColor=None, borderColor='black', colorSpace='rgb',
  365. lineWidth=4.0, opacity=1.0, ori=270.0,
  366. depth=-1
  367. )
  368. # create some handy timers
  369. if globalClock is None:
  370. globalClock = core.Clock() # to track the time since experiment started
  371. if ioServer is not None:
  372. ioServer.syncClock(globalClock)
  373. logging.setDefaultClock(globalClock)
  374. routineTimer = core.Clock() # to track time remaining of each (possibly non-slip) routine
  375. win.flip() # flip window to reset last flip timer
  376. # store the exact time the global clock started
  377. expInfo['expStart'] = data.getDateStr(format='%Y-%m-%d %Hh%M.%S.%f %z', fractionalSecondDigits=6)
  378. # --- Prepare to start Routine "initialize" ---
  379. continueRoutine = True
  380. # update component parameters for each repeat
  381. thisExp.addData('initialize.started', globalClock.getTime())
  382. key_resp.keys = []
  383. key_resp.rt = []
  384. _key_resp_allKeys = []
  385. # keep track of which components have finished
  386. initializeComponents = [text, key_resp]
  387. for thisComponent in initializeComponents:
  388. thisComponent.tStart = None
  389. thisComponent.tStop = None
  390. thisComponent.tStartRefresh = None
  391. thisComponent.tStopRefresh = None
  392. if hasattr(thisComponent, 'status'):
  393. thisComponent.status = NOT_STARTED
  394. # reset timers
  395. t = 0
  396. _timeToFirstFrame = win.getFutureFlipTime(clock="now")
  397. frameN = -1
  398. # --- Run Routine "initialize" ---
  399. routineForceEnded = not continueRoutine
  400. while continueRoutine:
  401. # get current time
  402. t = routineTimer.getTime()
  403. tThisFlip = win.getFutureFlipTime(clock=routineTimer)
  404. tThisFlipGlobal = win.getFutureFlipTime(clock=None)
  405. frameN = frameN + 1 # number of completed frames (so 0 is the first frame)
  406. # update/draw components on each frame
  407. # *text* updates
  408. # if text is starting this frame...
  409. if text.status == NOT_STARTED and tThisFlip >= 0.0-frameTolerance:
  410. # keep track of start time/frame for later
  411. text.frameNStart = frameN # exact frame index
  412. text.tStart = t # local t and not account for scr refresh
  413. text.tStartRefresh = tThisFlipGlobal # on global time
  414. win.timeOnFlip(text, 'tStartRefresh') # time at next scr refresh
  415. # add timestamp to datafile
  416. thisExp.timestampOnFlip(win, 'text.started')
  417. # update status
  418. text.status = STARTED
  419. text.setAutoDraw(True)
  420. # if text is active this frame...
  421. if text.status == STARTED:
  422. # update params
  423. pass
  424. # *key_resp* updates
  425. waitOnFlip = False
  426. # if key_resp is starting this frame...
  427. if key_resp.status == NOT_STARTED and tThisFlip >= 1-frameTolerance:
  428. # keep track of start time/frame for later
  429. key_resp.frameNStart = frameN # exact frame index
  430. key_resp.tStart = t # local t and not account for scr refresh
  431. key_resp.tStartRefresh = tThisFlipGlobal # on global time
  432. win.timeOnFlip(key_resp, 'tStartRefresh') # time at next scr refresh
  433. # add timestamp to datafile
  434. thisExp.timestampOnFlip(win, 'key_resp.started')
  435. # update status
  436. key_resp.status = STARTED
  437. # keyboard checking is just starting
  438. waitOnFlip = True
  439. win.callOnFlip(key_resp.clock.reset) # t=0 on next screen flip
  440. win.callOnFlip(key_resp.clearEvents, eventType='keyboard') # clear events on next screen flip
  441. if key_resp.status == STARTED and not waitOnFlip:
  442. theseKeys = key_resp.getKeys(keyList=['space'], ignoreKeys=["escape"], waitRelease=False)
  443. _key_resp_allKeys.extend(theseKeys)
  444. if len(_key_resp_allKeys):
  445. key_resp.keys = _key_resp_allKeys[-1].name # just the last key pressed
  446. key_resp.rt = _key_resp_allKeys[-1].rt
  447. key_resp.duration = _key_resp_allKeys[-1].duration
  448. # a response ends the routine
  449. continueRoutine = False
  450. # check for quit (typically the Esc key)
  451. if defaultKeyboard.getKeys(keyList=["escape"]):
  452. thisExp.status = FINISHED
  453. if thisExp.status == FINISHED or endExpNow:
  454. endExperiment(thisExp, inputs=inputs, win=win)
  455. return
  456. # check if all components have finished
  457. if not continueRoutine: # a component has requested a forced-end of Routine
  458. routineForceEnded = True
  459. break
  460. continueRoutine = False # will revert to True if at least one component still running
  461. for thisComponent in initializeComponents:
  462. if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
  463. continueRoutine = True
  464. break # at least one component has not yet finished
  465. # refresh the screen
  466. if continueRoutine: # don't flip if this routine is over or we'll get a blank screen
  467. win.flip()
  468. # --- Ending Routine "initialize" ---
  469. for thisComponent in initializeComponents:
  470. if hasattr(thisComponent, "setAutoDraw"):
  471. thisComponent.setAutoDraw(False)
  472. thisExp.addData('initialize.stopped', globalClock.getTime())
  473. # check responses
  474. if key_resp.keys in ['', [], None]: # No response was made
  475. key_resp.keys = None
  476. thisExp.addData('key_resp.keys',key_resp.keys)
  477. if key_resp.keys != None: # we had a response
  478. thisExp.addData('key_resp.rt', key_resp.rt)
  479. thisExp.addData('key_resp.duration', key_resp.duration)
  480. thisExp.nextEntry()
  481. # the Routine "initialize" was not non-slip safe, so reset the non-slip timer
  482. routineTimer.reset()
  483. # set up handler to look after randomisation of conditions etc
  484. trials = data.TrialHandler(nReps=10000.0, method='random',
  485. extraInfo=expInfo, originPath=-1,
  486. trialList=[None],
  487. seed=None, name='trials')
  488. thisExp.addLoop(trials) # add the loop to the experiment
  489. thisTrial = trials.trialList[0] # so we can initialise stimuli with some values
  490. # abbreviate parameter names if possible (e.g. rgb = thisTrial.rgb)
  491. if thisTrial != None:
  492. for paramName in thisTrial:
  493. globals()[paramName] = thisTrial[paramName]
  494. for thisTrial in trials:
  495. currentLoop = trials
  496. thisExp.timestampOnFlip(win, 'thisRow.t')
  497. # pause experiment here if requested
  498. if thisExp.status == PAUSED:
  499. pauseExperiment(
  500. thisExp=thisExp,
  501. inputs=inputs,
  502. win=win,
  503. timers=[routineTimer],
  504. playbackComponents=[]
  505. )
  506. # abbreviate parameter names if possible (e.g. rgb = thisTrial.rgb)
  507. if thisTrial != None:
  508. for paramName in thisTrial:
  509. globals()[paramName] = thisTrial[paramName]
  510. # --- Prepare to start Routine "decision" ---
  511. continueRoutine = True
  512. # update component parameters for each repeat
  513. thisExp.addData('decision.started', globalClock.getTime())
  514. # Run 'Begin Routine' code from decision
  515. # decision
  516. data_from_buffer = receiver.get_trial_data(clear=False)
  517. decision = controller.decision(data_from_buffer, None)
  518. force = controller.real_feedback_model.probability
  519. feedback_bar.progress = force
  520. # logging
  521. logging.exp('decision: {}'.format(decision))
  522. logging.exp('probability: {}'.format(force))
  523. # keep track of which components have finished
  524. decisionComponents = [feedback_bar]
  525. for thisComponent in decisionComponents:
  526. thisComponent.tStart = None
  527. thisComponent.tStop = None
  528. thisComponent.tStartRefresh = None
  529. thisComponent.tStopRefresh = None
  530. if hasattr(thisComponent, 'status'):
  531. thisComponent.status = NOT_STARTED
  532. # reset timers
  533. t = 0
  534. _timeToFirstFrame = win.getFutureFlipTime(clock="now")
  535. frameN = -1
  536. # --- Run Routine "decision" ---
  537. routineForceEnded = not continueRoutine
  538. while continueRoutine and routineTimer.getTime() < 0.1:
  539. # get current time
  540. t = routineTimer.getTime()
  541. tThisFlip = win.getFutureFlipTime(clock=routineTimer)
  542. tThisFlipGlobal = win.getFutureFlipTime(clock=None)
  543. frameN = frameN + 1 # number of completed frames (so 0 is the first frame)
  544. # update/draw components on each frame
  545. # *feedback_bar* updates
  546. # if feedback_bar is starting this frame...
  547. if feedback_bar.status == NOT_STARTED and tThisFlip >= 0-frameTolerance:
  548. # keep track of start time/frame for later
  549. feedback_bar.frameNStart = frameN # exact frame index
  550. feedback_bar.tStart = t # local t and not account for scr refresh
  551. feedback_bar.tStartRefresh = tThisFlipGlobal # on global time
  552. win.timeOnFlip(feedback_bar, 'tStartRefresh') # time at next scr refresh
  553. # add timestamp to datafile
  554. thisExp.timestampOnFlip(win, 'feedback_bar.started')
  555. # update status
  556. feedback_bar.status = STARTED
  557. feedback_bar.setAutoDraw(True)
  558. # if feedback_bar is active this frame...
  559. if feedback_bar.status == STARTED:
  560. # update params
  561. pass
  562. # if feedback_bar is stopping this frame...
  563. if feedback_bar.status == STARTED:
  564. # is it time to stop? (based on global clock, using actual start)
  565. if tThisFlipGlobal > feedback_bar.tStartRefresh + 0.1-frameTolerance:
  566. # keep track of stop time/frame for later
  567. feedback_bar.tStop = t # not accounting for scr refresh
  568. feedback_bar.frameNStop = frameN # exact frame index
  569. # add timestamp to datafile
  570. thisExp.timestampOnFlip(win, 'feedback_bar.stopped')
  571. # update status
  572. feedback_bar.status = FINISHED
  573. feedback_bar.setAutoDraw(False)
  574. # check for quit (typically the Esc key)
  575. if defaultKeyboard.getKeys(keyList=["escape"]):
  576. thisExp.status = FINISHED
  577. if thisExp.status == FINISHED or endExpNow:
  578. endExperiment(thisExp, inputs=inputs, win=win)
  579. return
  580. # check if all components have finished
  581. if not continueRoutine: # a component has requested a forced-end of Routine
  582. routineForceEnded = True
  583. break
  584. continueRoutine = False # will revert to True if at least one component still running
  585. for thisComponent in decisionComponents:
  586. if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
  587. continueRoutine = True
  588. break # at least one component has not yet finished
  589. # refresh the screen
  590. if continueRoutine: # don't flip if this routine is over or we'll get a blank screen
  591. win.flip()
  592. # --- Ending Routine "decision" ---
  593. for thisComponent in decisionComponents:
  594. if hasattr(thisComponent, "setAutoDraw"):
  595. thisComponent.setAutoDraw(False)
  596. thisExp.addData('decision.stopped', globalClock.getTime())
  597. # using non-slip timing so subtract the expected duration of this Routine (unless ended on request)
  598. if routineForceEnded:
  599. routineTimer.reset()
  600. else:
  601. routineTimer.addTime(-0.100000)
  602. # --- Prepare to start Routine "feedback" ---
  603. continueRoutine = True
  604. # update component parameters for each repeat
  605. thisExp.addData('feedback.started', globalClock.getTime())
  606. # Run 'Begin Routine' code from send_feedback
  607. # state changed
  608. feedback_bar1.progress = force
  609. if decision != -1:
  610. feedback_time = 5
  611. if not decision:
  612. trigger.send_trigger(0)
  613. hand_device.extend()
  614. else:
  615. trigger.send_trigger(int(decision))
  616. hand_device.start(model=fingermodel_ids_inverse[decision])
  617. else:
  618. feedback_time = 0
  619. # keep track of which components have finished
  620. feedbackComponents = [feedback_bar1]
  621. for thisComponent in feedbackComponents:
  622. thisComponent.tStart = None
  623. thisComponent.tStop = None
  624. thisComponent.tStartRefresh = None
  625. thisComponent.tStopRefresh = None
  626. if hasattr(thisComponent, 'status'):
  627. thisComponent.status = NOT_STARTED
  628. # reset timers
  629. t = 0
  630. _timeToFirstFrame = win.getFutureFlipTime(clock="now")
  631. frameN = -1
  632. # --- Run Routine "feedback" ---
  633. routineForceEnded = not continueRoutine
  634. while continueRoutine:
  635. # get current time
  636. t = routineTimer.getTime()
  637. tThisFlip = win.getFutureFlipTime(clock=routineTimer)
  638. tThisFlipGlobal = win.getFutureFlipTime(clock=None)
  639. frameN = frameN + 1 # number of completed frames (so 0 is the first frame)
  640. # update/draw components on each frame
  641. # *feedback_bar1* updates
  642. # if feedback_bar1 is starting this frame...
  643. if feedback_bar1.status == NOT_STARTED and tThisFlip >= 0-frameTolerance:
  644. # keep track of start time/frame for later
  645. feedback_bar1.frameNStart = frameN # exact frame index
  646. feedback_bar1.tStart = t # local t and not account for scr refresh
  647. feedback_bar1.tStartRefresh = tThisFlipGlobal # on global time
  648. win.timeOnFlip(feedback_bar1, 'tStartRefresh') # time at next scr refresh
  649. # add timestamp to datafile
  650. thisExp.timestampOnFlip(win, 'feedback_bar1.started')
  651. # update status
  652. feedback_bar1.status = STARTED
  653. feedback_bar1.setAutoDraw(True)
  654. # if feedback_bar1 is active this frame...
  655. if feedback_bar1.status == STARTED:
  656. # update params
  657. pass
  658. # if feedback_bar1 is stopping this frame...
  659. if feedback_bar1.status == STARTED:
  660. # is it time to stop? (based on global clock, using actual start)
  661. if tThisFlipGlobal > feedback_bar1.tStartRefresh + feedback_time-frameTolerance:
  662. # keep track of stop time/frame for later
  663. feedback_bar1.tStop = t # not accounting for scr refresh
  664. feedback_bar1.frameNStop = frameN # exact frame index
  665. # add timestamp to datafile
  666. thisExp.timestampOnFlip(win, 'feedback_bar1.stopped')
  667. # update status
  668. feedback_bar1.status = FINISHED
  669. feedback_bar1.setAutoDraw(False)
  670. # check for quit (typically the Esc key)
  671. if defaultKeyboard.getKeys(keyList=["escape"]):
  672. thisExp.status = FINISHED
  673. if thisExp.status == FINISHED or endExpNow:
  674. endExperiment(thisExp, inputs=inputs, win=win)
  675. return
  676. # check if all components have finished
  677. if not continueRoutine: # a component has requested a forced-end of Routine
  678. routineForceEnded = True
  679. break
  680. continueRoutine = False # will revert to True if at least one component still running
  681. for thisComponent in feedbackComponents:
  682. if hasattr(thisComponent, "status") and thisComponent.status != FINISHED:
  683. continueRoutine = True
  684. break # at least one component has not yet finished
  685. # refresh the screen
  686. if continueRoutine: # don't flip if this routine is over or we'll get a blank screen
  687. win.flip()
  688. # --- Ending Routine "feedback" ---
  689. for thisComponent in feedbackComponents:
  690. if hasattr(thisComponent, "setAutoDraw"):
  691. thisComponent.setAutoDraw(False)
  692. thisExp.addData('feedback.stopped', globalClock.getTime())
  693. # the Routine "feedback" was not non-slip safe, so reset the non-slip timer
  694. routineTimer.reset()
  695. thisExp.nextEntry()
  696. if thisSession is not None:
  697. # if running in a Session with a Liaison client, send data up to now
  698. thisSession.sendExperimentData()
  699. # completed 10000.0 repeats of 'trials'
  700. # Run 'End Experiment' code from device
  701. receiver.close()
  702. # mark experiment as finished
  703. endExperiment(thisExp, win=win, inputs=inputs)
  704. def saveData(thisExp):
  705. """
  706. Save data from this experiment
  707. Parameters
  708. ==========
  709. thisExp : psychopy.data.ExperimentHandler
  710. Handler object for this experiment, contains the data to save and information about
  711. where to save it to.
  712. """
  713. filename = thisExp.dataFileName
  714. # these shouldn't be strictly necessary (should auto-save)
  715. thisExp.saveAsWideText(filename + '.csv', delim='auto')
  716. thisExp.saveAsPickle(filename)
  717. def endExperiment(thisExp, inputs=None, win=None):
  718. """
  719. End this experiment, performing final shut down operations.
  720. This function does NOT close the window or end the Python process - use `quit` for this.
  721. Parameters
  722. ==========
  723. thisExp : psychopy.data.ExperimentHandler
  724. Handler object for this experiment, contains the data to save and information about
  725. where to save it to.
  726. inputs : dict
  727. Dictionary of input devices by name.
  728. win : psychopy.visual.Window
  729. Window for this experiment.
  730. """
  731. if win is not None:
  732. # remove autodraw from all current components
  733. win.clearAutoDraw()
  734. # Flip one final time so any remaining win.callOnFlip()
  735. # and win.timeOnFlip() tasks get executed
  736. win.flip()
  737. # mark experiment handler as finished
  738. thisExp.status = FINISHED
  739. # shut down eyetracker, if there is one
  740. if inputs is not None:
  741. if 'eyetracker' in inputs and inputs['eyetracker'] is not None:
  742. inputs['eyetracker'].setConnectionState(False)
  743. logging.flush()
  744. def quit(thisExp, win=None, inputs=None, thisSession=None):
  745. """
  746. Fully quit, closing the window and ending the Python process.
  747. Parameters
  748. ==========
  749. win : psychopy.visual.Window
  750. Window to close.
  751. inputs : dict
  752. Dictionary of input devices by name.
  753. thisSession : psychopy.session.Session or None
  754. Handle of the Session object this experiment is being run from, if any.
  755. """
  756. thisExp.abort() # or data files will save again on exit
  757. # make sure everything is closed down
  758. if win is not None:
  759. # Flip one final time so any remaining win.callOnFlip()
  760. # and win.timeOnFlip() tasks get executed before quitting
  761. win.flip()
  762. win.close()
  763. if inputs is not None:
  764. if 'eyetracker' in inputs and inputs['eyetracker'] is not None:
  765. inputs['eyetracker'].setConnectionState(False)
  766. logging.flush()
  767. if thisSession is not None:
  768. thisSession.stop()
  769. # terminate Python process
  770. core.quit()
  771. # if running this experiment as a script...
  772. if __name__ == '__main__':
  773. # call all functions in order
  774. expInfo = showExpInfoDlg(expInfo=expInfo)
  775. thisExp = setupData(expInfo=expInfo)
  776. logFile = setupLogging(filename=thisExp.dataFileName)
  777. win = setupWindow(expInfo=expInfo)
  778. inputs = setupInputs(expInfo=expInfo, thisExp=thisExp, win=win)
  779. run(
  780. expInfo=expInfo,
  781. thisExp=thisExp,
  782. win=win,
  783. inputs=inputs
  784. )
  785. saveData(thisExp=thisExp)
  786. quit(thisExp=thisExp, win=win, inputs=inputs)