PageRenderTime 37ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/psychopy/experiment/components/roi/__init__.py

https://github.com/psychopy/psychopy
Python | 300 lines | 284 code | 10 blank | 6 comment | 6 complexity | 621ae70384ce87541cebf37c56218cf9 MD5 | raw file
Possible License(s): GPL-3.0, BSD-3-Clause
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # Part of the PsychoPy library
  4. # Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-2022 Open Science Tools Ltd.
  5. # Distributed under the terms of the GNU General Public License (GPL).
  6. from pathlib import Path
  7. from psychopy.alerts import alert
  8. from psychopy.experiment.components import Param, getInitVals, _translate, BaseVisualComponent
  9. from psychopy.experiment.components.eyetracker_record import EyetrackerRecordComponent
  10. from psychopy.experiment.components.polygon import PolygonComponent
  11. from psychopy.localization import _localized as __localized
  12. _localized = __localized.copy()
  13. class RegionOfInterestComponent(PolygonComponent):
  14. """A class for using one of several eyetrackers to follow gaze"""
  15. categories = ['Eyetracking']
  16. targets = ['PsychoPy']
  17. iconFile = Path(__file__).parent / 'eyetracker_roi.png'
  18. tooltip = _translate('Region Of Interest: Define a region of interest for use with eyetrackers')
  19. beta = True
  20. def __init__(self, exp, parentName, name='roi',
  21. units='from exp settings',
  22. endRoutineOn="none",
  23. shape='triangle', nVertices=4,
  24. pos=(0, 0), size=(0.5, 0.5), ori=0,
  25. startType='time (s)', startVal=0.0,
  26. stopType='duration (s)', stopVal=1.0,
  27. startEstim='', durationEstim='',
  28. timeRelativeTo='roi onset',
  29. lookDur=0.1, debug=False,
  30. save='every look'):
  31. PolygonComponent.__init__(self, exp, parentName, name=name,
  32. units=units,
  33. shape=shape, nVertices=nVertices,
  34. pos=pos, size=size, ori=ori,
  35. startType=startType, startVal=startVal,
  36. stopType=stopType, stopVal=stopVal,
  37. startEstim=startEstim, durationEstim=durationEstim)
  38. self.type = 'RegionOfInterest'
  39. self.url = "https://www.psychopy.org/builder/components/roi.html"
  40. self.exp.requirePsychopyLibs(['iohub', 'hardware'])
  41. # params
  42. self.order += ['config'] # first param after the name
  43. # Delete all appearance parameters
  44. for param in list(self.params).copy():
  45. if self.params[param].categ in ["Appearance", "Texture"]:
  46. del self.params[param]
  47. # Fix units as default
  48. self.params['units'].allowedVals = ['from exp settings']
  49. self.params['endRoutineOn'] = Param(endRoutineOn,
  50. valType='str', inputType='choice', categ='Basic',
  51. allowedVals=["look at", "look away", "none"],
  52. hint=_translate("Under what condition should this ROI end the routine?"),
  53. label=_translate("End Routine On...")
  54. )
  55. self.depends.append(
  56. {"dependsOn": "endRoutineOn", # must be param name
  57. "condition": "=='none'", # val to check for
  58. "param": "lookDur", # param property to alter
  59. "true": "hide", # what to do with param if condition is True
  60. "false": "show", # permitted: hide, show, enable, disable
  61. }
  62. )
  63. self.params['lookDur'] = Param(lookDur,
  64. valType='num', inputType='single', categ='Basic',
  65. hint=_translate("Minimum dwell time within roi (look at) or outside roi (look away)."),
  66. label=_translate("Min. Look Time")
  67. )
  68. self.params['debug'] = Param(
  69. debug, valType='bool', inputType='bool', categ='Testing',
  70. hint=_translate("In debug mode, the ROI is drawn in red. Use this to see what area of the "
  71. "screen is in the ROI."),
  72. label=_translate("Debug Mode")
  73. )
  74. self.params['save'] = Param(
  75. save, valType='str', inputType="choice", categ='Data',
  76. allowedVals=['first look', 'last look', 'every look', 'none'],
  77. direct=False,
  78. hint=_translate(
  79. "What looks on this ROI should be saved to the data output?"),
  80. label=_translate('Save...'))
  81. self.params['timeRelativeTo'] = Param(
  82. timeRelativeTo, valType='str', inputType="choice", categ='Data',
  83. allowedVals=['roi onset', 'experiment', 'routine'],
  84. updates='constant', direct=False,
  85. hint=_translate(
  86. "What should the values of roi.time should be "
  87. "relative to?"),
  88. label=_translate('Time Relative To...'))
  89. def writePreWindowCode(self, buff):
  90. pass
  91. def writeInitCode(self, buff):
  92. # do we need units code?
  93. if self.params['units'].val == 'from exp settings':
  94. unitsStr = ""
  95. else:
  96. unitsStr = "units=%(units)s, " % self.params
  97. # do writing of init
  98. inits = getInitVals(self.params, 'PsychoPy')
  99. if self.params['shape'] == 'regular polygon...':
  100. inits['shape'] = self.params['nVertices']
  101. elif self.params['shape'] == 'custom polygon...':
  102. inits['shape'] = self.params['vertices']
  103. code = (
  104. "%(name)s = visual.ROI(win, name='%(name)s', device=eyetracker,\n"
  105. )
  106. buff.writeIndentedLines(code % inits)
  107. buff.setIndentLevel(1, relative=True)
  108. code = (
  109. "debug=%(debug)s,\n"
  110. "shape=%(shape)s,\n"
  111. + unitsStr + "pos=%(pos)s, size=%(size)s, anchor=%(anchor)s, ori=0.0)\n"
  112. )
  113. buff.writeIndentedLines(code % inits)
  114. buff.setIndentLevel(-1, relative=True)
  115. def writeInitCodeJS(self, buff):
  116. pass
  117. def writeRoutineStartCode(self, buff):
  118. inits = getInitVals(self.params, 'PsychoPy')
  119. BaseVisualComponent.writeRoutineStartCode(self, buff)
  120. code = (
  121. "# clear any previous roi data\n"
  122. "%(name)s.reset()\n"
  123. )
  124. buff.writeIndentedLines(code % inits)
  125. def writeFrameCode(self, buff):
  126. """Write the code that will be called every frame
  127. """
  128. # do writing of init
  129. inits = getInitVals(self.params, 'PsychoPy')
  130. # Write basics
  131. BaseVisualComponent.writeFrameCode(self, buff)
  132. buff.setIndentLevel(1, relative=True)
  133. code = (
  134. "%(name)s.status = STARTED\n"
  135. )
  136. buff.writeIndentedLines(code % inits)
  137. buff.setIndentLevel(-1, relative=True)
  138. # String to get time
  139. if inits['timeRelativeTo'] == 'roi onset':
  140. timing = "%(name)s.clock.getTime()"
  141. elif inits['timeRelativeTo'] == 'experiment':
  142. timing = "globalClock.getTime()"
  143. elif inits['timeRelativeTo'] == 'routine':
  144. timing = "routineTimer.getTime()"
  145. else:
  146. timing = "globalClock.getTime()"
  147. # Assemble code
  148. code = (
  149. f"if %(name)s.status == STARTED:\n"
  150. )
  151. buff.writeIndentedLines(code % inits)
  152. buff.setIndentLevel(1, relative=True)
  153. code = (
  154. f"# check whether %(name)s has been looked in\n"
  155. f"if %(name)s.isLookedIn:\n"
  156. )
  157. buff.writeIndentedLines(code % inits)
  158. buff.setIndentLevel(1, relative=True)
  159. code = (
  160. f"if not %(name)s.wasLookedIn:\n"
  161. )
  162. buff.writeIndentedLines(code % inits)
  163. buff.setIndentLevel(1, relative=True)
  164. code = (
  165. f"%(name)s.timesOn.append({timing}) # store time of first look\n"
  166. f"%(name)s.timesOff.append({timing}) # store time looked until\n"
  167. )
  168. buff.writeIndentedLines(code % inits)
  169. buff.setIndentLevel(-1, relative=True)
  170. code = (
  171. f"else:\n"
  172. )
  173. buff.writeIndentedLines(code % inits)
  174. buff.setIndentLevel(1, relative=True)
  175. code = (
  176. f"%(name)s.timesOff[-1] = {timing} # update time looked until\n"
  177. )
  178. buff.writeIndentedLines(code % inits)
  179. if self.params['endRoutineOn'].val == "look at":
  180. code = (
  181. "if %(name)s.currentLookTime > %(lookDur)s: # check if they've been looking long enough\n"
  182. )
  183. buff.writeIndentedLines(code % inits)
  184. buff.setIndentLevel(1, relative=True)
  185. code = (
  186. "continueRoutine = False # end routine on sufficiently long look\n"
  187. )
  188. buff.writeIndentedLines(code % inits)
  189. buff.setIndentLevel(-1, relative=True)
  190. buff.setIndentLevel(-1, relative=True)
  191. code = (
  192. f"%(name)s.wasLookedIn = True # if %(name)s is still looked at next frame, it is not a new look\n"
  193. )
  194. buff.writeIndentedLines(code % inits)
  195. buff.setIndentLevel(-1, relative=True)
  196. code = (
  197. f"else:\n"
  198. )
  199. buff.writeIndentedLines(code % inits)
  200. buff.setIndentLevel(1, relative=True)
  201. code = (
  202. f"if %(name)s.wasLookedIn:"
  203. )
  204. buff.writeIndentedLines(code % inits)
  205. buff.setIndentLevel(1, relative=True)
  206. code = (
  207. f"%(name)s.timesOff[-1] = {timing} # update time looked until\n"
  208. )
  209. buff.writeIndentedLines(code % inits)
  210. if self.params['endRoutineOn'].val == "look away":
  211. buff.setIndentLevel(-1, relative=True)
  212. code = (
  213. f"# check if last look outside roi was long enough\n"
  214. f"if len(%(name)s.timesOff) == 0 and %(name)s.clock.getTime() > %(lookDur)s:\n"
  215. )
  216. buff.writeIndentedLines(code % inits)
  217. buff.setIndentLevel(1, relative=True)
  218. code = (
  219. f"continueRoutine = False # end routine after sufficiently long look outside roi\n"
  220. )
  221. buff.writeIndentedLines(code % inits)
  222. buff.setIndentLevel(-1, relative=True)
  223. code = (
  224. f"elif len(%(name)s.timesOff) > 0 and %(name)s.clock.getTime() - %(name)s.timesOff[-1] > %(lookDur)s:\n"
  225. )
  226. buff.writeIndentedLines(code % inits)
  227. buff.setIndentLevel(1, relative=True)
  228. code = (
  229. f"continueRoutine = False # end routine after sufficiently long look outside roi\n"
  230. )
  231. buff.writeIndentedLines(code % inits)
  232. buff.setIndentLevel(-1, relative=True)
  233. code = (
  234. f"%(name)s.wasLookedIn = False # if %(name)s is looked at next frame, it is a new look\n"
  235. )
  236. buff.writeIndentedLines(code % inits)
  237. buff.setIndentLevel(-2, relative=True)
  238. code = (
  239. f"else:\n"
  240. )
  241. buff.writeIndentedLines(code % inits)
  242. buff.setIndentLevel(1, relative=True)
  243. code = (
  244. f"%(name)s.clock.reset() # keep clock at 0 if roi hasn't started / has finished\n"
  245. f"%(name)s.wasLookedIn = False # if %(name)s is looked at next frame, it is a new look\n"
  246. )
  247. buff.writeIndentedLines(code % inits)
  248. buff.setIndentLevel(-1, relative=True)
  249. def writeRoutineEndCode(self, buff):
  250. BaseVisualComponent.writeRoutineEndCode(self, buff)
  251. if len(self.exp.flow._loopList):
  252. currLoop = self.exp.flow._loopList[-1] # last (outer-most) loop
  253. else:
  254. currLoop = self.exp._expHandler
  255. name = self.params['name']
  256. if self.params['save'] == 'first look':
  257. index = "[0]"
  258. elif self.params['save'] == 'last look':
  259. index = "[-1]"
  260. else:
  261. index = ""
  262. if self.params['save'] != 'none':
  263. code = (
  264. f"{currLoop.params['name']}.addData('{name}.numLooks', {name}.numLooks)\n"
  265. f"if {name}.numLooks:\n"
  266. f" {currLoop.params['name']}.addData('{name}.timesOn', {name}.timesOn{index})\n"
  267. f" {currLoop.params['name']}.addData('{name}.timesOff', {name}.timesOff{index})\n"
  268. f"else:\n"
  269. f" {currLoop.params['name']}.addData('{name}.timesOn', \"\")\n"
  270. f" {currLoop.params['name']}.addData('{name}.timesOff', \"\")\n"
  271. )
  272. buff.writeIndentedLines(code)
  273. def writeExperimentEndCode(self, buff):
  274. pass