2
0

free_grasp.py 34 KB

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