Skip to content

Commit

Permalink
Merge pull request #2557 from peircej/timing-improvements
Browse files Browse the repository at this point in the history
Timing improvements
  • Loading branch information
peircej committed Aug 8, 2019
2 parents 4011fe0 + 12d4781 commit bacf23f
Show file tree
Hide file tree
Showing 21 changed files with 108 additions and 91 deletions.
1 change: 1 addition & 0 deletions psychopy/constants.py
Expand Up @@ -22,6 +22,7 @@
STOPPED = -1
FINISHED = STOPPED
SKIP = -2
STOPPING = -3

# for button box:
PRESSED = 1
Expand Down
78 changes: 32 additions & 46 deletions psychopy/experiment/components/_base.py
Expand Up @@ -189,50 +189,41 @@ def writeExperimentEndCode(self, buff):
"""
pass

def writeTimeTestCode(self, buff):
"""Original code for testing whether to draw.
All objects have now migrated to using writeStartTestCode and
writeEndTestCode
"""
# unused internally; deprecated March 2016 v1.83.x, will remove 1.85
logging.warning(
'Deprecation warning: writeTimeTestCode() is not supported;\n'
'will be removed. Please use writeStartTestCode() instead')
if self.params['duration'].val == '':
code = "if %(startTime)s <= t:\n"
else:
code = "if %(startTime)s <= t < %(startTime)s + %(duration)s:\n"
buff.writeIndentedLines(code % self.params)

def writeStartTestCode(self, buff):
"""Test whether we need to start
"""
if self.params['syncScreenRefresh']:
tCompare = 'tThisFlip'
else:
tCompare = 't'
if self.params['startType'].val == 'time (s)':
# if startVal is an empty string then set to be 0.0
if (isinstance(self.params['startVal'].val, basestring) and
not self.params['startVal'].val.strip()):
self.params['startVal'].val = '0.0'
code = ("if t >= %(startVal)s "
"and %(name)s.status == NOT_STARTED:\n")
code = ("if {params[name]}.status == NOT_STARTED and "
"{t} >= {params[startVal]}-frameTolerance:\n")
elif self.params['startType'].val == 'frame N':
code = ("if frameN >= %(startVal)s "
"and %(name)s.status == NOT_STARTED:\n")
code = ("if {params[name]}.status == NOT_STARTED and "
"frameN >= {params[startVal]}:\n")
elif self.params['startType'].val == 'condition':
code = ("if (%(startVal)s) "
"and %(name)s.status == NOT_STARTED:\n")
code = ("if {params[name]}.status == NOT_STARTED and "
"{params[startVal]}:\n")
else:
msg = "Not a known startType (%(startType)s) for %(name)s"
raise CodeGenerationException(msg % self.params)

buff.writeIndented(code % self.params)
buff.writeIndented(code.format(params=self.params, t=tCompare))

buff.setIndentLevel(+1, relative=True)
code = ("# keep track of start time/frame for later\n"
"%(name)s.tStart = t # not accounting for scr refresh\n"
"%(name)s.frameNStart = frameN # exact frame index\n"
"win.timeOnFlip(%(name)s, 'tStartRefresh')"
" # time at next scr refresh\n")
buff.writeIndentedLines(code % self.params)
"{params[name]}.frameNStart = frameN # exact frame index\n"
"{params[name]}.tStart = t # local t and not account for scr refresh\n"
"{params[name]}.tStartRefresh = tThisFlipGlobal # on global time\n")
if self.type != "Sound": # for sounds, don't update to actual frame time
code += ("win.timeOnFlip({params[name]}, 'tStartRefresh')"
" # time at next scr refresh\n")
buff.writeIndentedLines(code.format(params=self.params))

def writeStartTestCodeJS(self, buff):
"""Test whether we need to start
Expand Down Expand Up @@ -265,29 +256,24 @@ def writeStartTestCodeJS(self, buff):
def writeStopTestCode(self, buff):
"""Test whether we need to stop
"""
buff.writeIndentedLines("if %(name)s.status == STARTED:\n" % self.params)
buff.setIndentLevel(+1, relative=True)

if self.params['stopType'].val == 'time (s)':
code = ("frameRemains = %(stopVal)s "
"- win.monitorFramePeriod * 0.75"
" # most of one frame period left\n"
"if %(name)s.status == STARTED and t >= frameRemains:\n")
code = ("# is it time to stop? (based on local clock)\n"
"if tThisFlip > %(stopVal)s-frameTolerance:\n"
)
# duration in time (s)
elif (self.params['stopType'].val == 'duration (s)' and
self.params['startType'].val == 'time (s)'):
code = ("frameRemains = %(startVal)s + %(stopVal)s"
"- win.monitorFramePeriod * 0.75"
" # most of one frame period left\n"
"if %(name)s.status == STARTED and t >= frameRemains:\n")
# start at frame and end with duration (need to use approximate)
elif self.params['stopType'].val == 'duration (s)':
code = ("if %(name)s.status == STARTED and t >= (%(name)s.tStart "
"+ %(stopVal)s):\n")
elif (self.params['stopType'].val == 'duration (s)'):
code = ("# is it time to stop? (based on global clock, using actual start)\n"
"if tThisFlipGlobal > %(name)s.tStartRefresh + %(stopVal)s-frameTolerance:\n"
)
elif self.params['stopType'].val == 'duration (frames)':
code = ("if %(name)s.status == STARTED and frameN >= "
"(%(name)s.frameNStart + %(stopVal)s):\n")
code = ("if frameN >= (%(name)s.frameNStart + %(stopVal)s):\n")
elif self.params['stopType'].val == 'frame N':
code = "if %(name)s.status == STARTED and frameN >= %(stopVal)s:\n"
code = "if frameN >= %(stopVal)s:\n"
elif self.params['stopType'].val == 'condition':
code = "if %(name)s.status == STARTED and bool(%(stopVal)s):\n"
code = "if bool(%(stopVal)s):\n"
else:
msg = ("Didn't write any stop line for startType=%(startType)s, "
"stopType=%(stopType)s")
Expand Down Expand Up @@ -629,7 +615,7 @@ def writeFrameCode(self, buff):
self.writeStopTestCode(buff)
buff.writeIndented("%(name)s.setAutoDraw(False)\n" % self.params)
# to get out of the if statement
buff.setIndentLevel(-1, relative=True)
buff.setIndentLevel(-2, relative=True)

# set parameters that need updating every frame
# do any params need updating? (this method inherited from _base)
Expand Down
2 changes: 1 addition & 1 deletion psychopy/experiment/components/aperture/__init__.py
Expand Up @@ -90,7 +90,7 @@ def writeFrameCode(self, buff):
self.writeStopTestCode(buff)
buff.writeIndented("%(name)s.enabled = False\n" % self.params)
# to get out of the if statement
buff.setIndentLevel(-1, relative=True)
buff.setIndentLevel(-2, relative=True)
# set parameters that need updating every frame
# do any params need updating? (this method inherited from _base)
if self.checkNeedToUpdate('set every frame'):
Expand Down
2 changes: 1 addition & 1 deletion psychopy/experiment/components/cedrusBox/__init__.py
Expand Up @@ -209,7 +209,7 @@ def writeFrameCode(self, buff):
# writes an if statement to determine whether to draw etc
self.writeStopTestCode(buff)
buff.writeIndented("%(name)s.status = FINISHED\n" % self.params)
buff.setIndentLevel(-1, True)
buff.setIndentLevel(-2, True)

buff.writeIndented("if %(name)s.status == STARTED:\n" % self.params)
buff.setIndentLevel(1, relative=True) # to get out of if statement
Expand Down
2 changes: 1 addition & 1 deletion psychopy/experiment/components/envelopegrating/__init__.py
Expand Up @@ -288,7 +288,7 @@ def writeFrameCode(self, buff):
#if self.params['blendmode'].val!='default':
#buff.writeIndented("win.blendMode=%(name)s_SaveBlendMode\n" % self.params)
# to get out of the if statement
buff.setIndentLevel(-1, relative=True)
buff.setIndentLevel(-2, relative=True)

# set parameters that need updating every frame
# do any params need updating? (this method inherited from _base)
Expand Down
2 changes: 1 addition & 1 deletion psychopy/experiment/components/eyetracker/__init__.py
Expand Up @@ -148,7 +148,7 @@ def writeFrameCode(self, buff):
buff.writeIndentedLines(code % self.params)

# to get out of the if statement
buff.setIndentLevel(-1, relative=True)
buff.setIndentLevel(-2, relative=True)

# if STARTED and not FINISHED!
code = "if %(name)s.status == STARTED: # only update if started and not finished!\n" % self.params
Expand Down
2 changes: 1 addition & 1 deletion psychopy/experiment/components/ioLabs/__init__.py
Expand Up @@ -171,7 +171,7 @@ def writeFrameCode(self, buff):
# writes an if statement to determine whether to draw etc
self.writeStopTestCode(buff)
buff.writeIndented("%(name)s.status = FINISHED\n" % self.params)
buff.setIndentLevel(-1, True)
buff.setIndentLevel(-2, True)

buff.writeIndented("if %(name)s.status == STARTED:\n" % self.params)
buff.setIndentLevel(1, relative=True) # to get out of the if statement
Expand Down
2 changes: 1 addition & 1 deletion psychopy/experiment/components/joyButtons/__init__.py
Expand Up @@ -298,7 +298,7 @@ def writeFrameCode(self, buff):
self.writeStopTestCode(buff)
buff.writeIndented("%(name)s.status = FINISHED\n" % self.params)
# to get out of the if statement
buff.setIndentLevel(-1, relative=True)
buff.setIndentLevel(-2, relative=True)

buff.writeIndented("if %(name)s.status == STARTED:\n" % self.params)
buff.setIndentLevel(1, relative=True) # to get out of if statement
Expand Down
2 changes: 1 addition & 1 deletion psychopy/experiment/components/joystick/__init__.py
Expand Up @@ -393,7 +393,7 @@ def writeFrameCode(self, buff):
self.writeStopTestCode(buff)
buff.writeIndented("%(name)s.status = FINISHED\n" % self.params)
# to get out of the if statement
buff.setIndentLevel(-1, relative=True)
buff.setIndentLevel(-2, relative=True)

# if STARTED and not FINISHED!
code = ("if %(name)s.status == STARTED: "
Expand Down
2 changes: 1 addition & 1 deletion psychopy/experiment/components/keyboard/__init__.py
Expand Up @@ -229,7 +229,7 @@ def writeFrameCode(self, buff):
self.writeStopTestCode(buff)
buff.writeIndented("%(name)s.status = FINISHED\n" % self.params)
# to get out of the if statement
buff.setIndentLevel(-1, relative=True)
buff.setIndentLevel(-2, relative=True)

buff.writeIndented("if %s.status == STARTED%s:\n"
% (self.params['name'], ['', ' and not waitOnFlip'][visualSync]))
Expand Down
2 changes: 1 addition & 1 deletion psychopy/experiment/components/mouse/__init__.py
Expand Up @@ -275,7 +275,7 @@ def writeFrameCode(self, buff):
self.writeStopTestCode(buff)
buff.writeIndented("%(name)s.status = FINISHED\n" % self.params)
# to get out of the if statement
buff.setIndentLevel(-1, relative=True)
buff.setIndentLevel(-2, relative=True)

# if STARTED and not FINISHED!
code = ("if %(name)s.status == STARTED: "
Expand Down
2 changes: 1 addition & 1 deletion psychopy/experiment/components/movie/__init__.py
Expand Up @@ -240,7 +240,7 @@ def writeFrameCode(self, buff):
self.writeStopTestCode(buff)
buff.writeIndented("%(name)s.setAutoDraw(False)\n" % self.params)
# to get out of the if statement
buff.setIndentLevel(-1, relative=True)
buff.setIndentLevel(-2, relative=True)
# set parameters that need updating every frame
# do any params need updating? (this method inherited from _base)
if self.checkNeedToUpdate('set every frame'):
Expand Down
2 changes: 1 addition & 1 deletion psychopy/experiment/components/parallelOut/__init__.py
Expand Up @@ -132,7 +132,7 @@ def writeFrameCode(self, buff):
buff.writeIndented(code)

# to get out of the if statement
buff.setIndentLevel(-1, relative=True)
buff.setIndentLevel(-2, relative=True)

# dedent
# buff.setIndentLevel(-dedentAtEnd, relative=True)#'if' statement of the
Expand Down
2 changes: 1 addition & 1 deletion psychopy/experiment/components/pump/__init__.py
Expand Up @@ -173,7 +173,7 @@ def writeFrameCode(self, buff):
code = '%(name)s.stop()\n' % self.params

buff.writeIndentedLines(code)
buff.setIndentLevel(-1, relative=True)
buff.setIndentLevel(-2, relative=True)

def writeRoutineEndCode(self, buff):
# Make sure that we stop the pumps even if the routine has been
Expand Down
2 changes: 2 additions & 0 deletions psychopy/experiment/components/settings/__init__.py
Expand Up @@ -694,6 +694,8 @@ def writeStartCode(self, buff, version):
buff.writeIndentedLines("\nendExpNow = False # flag for 'escape'"
" or other condition => quit the exp\n")

buff.writeIndented("frameTolerance = 0.001 # how close to onset before 'same' frame\n")

def writeWindowCode(self, buff):
"""Setup the window code.
"""
Expand Down
9 changes: 5 additions & 4 deletions psychopy/experiment/components/sound/__init__.py
Expand Up @@ -89,10 +89,11 @@ def writeInitCode(self, buff):
inits['stopVal'].val = -1
elif float(inits['stopVal'].val) > 2:
inits['stopVal'].val = -1
buff.writeIndented("%s = sound.Sound(%s, secs=%s, stereo=%s, hamming=%s)\n" %
buff.writeIndented("%s = sound.Sound(%s, secs=%s, stereo=%s, hamming=%s,\n"
" name='%s')\n" %
(inits['name'], inits['sound'], inits['stopVal'],
self.exp.settings.params['Force stereo'],
inits['hamming']))
inits['hamming'], inits['name']))
buff.writeIndented("%(name)s.setVolume(%(volume)s)\n" % (inits))

def writeRoutineStartCode(self, buff):
Expand Down Expand Up @@ -161,7 +162,7 @@ def writeFrameCode(self, buff):
" %(name)s.stop()\n")
buff.writeIndentedLines(code % self.params)
# because of the 'if' statement of the time test
buff.setIndentLevel(-1, relative=True)
buff.setIndentLevel(-2, relative=True)

def writeFrameCodeJS(self, buff):
"""Write the code that will be called every frame
Expand Down Expand Up @@ -206,7 +207,7 @@ def writeRoutineEndCode(self, buff):
code = "%s.stop() # ensure sound has stopped at end of routine\n"
buff.writeIndented(code % self.params['name'])
# get parent to write code too (e.g. store onset/offset times)
super().writeRoutineEndCode(buff)
super().writeRoutineEndCode(buff) # noinspection

def writeRoutineEndCodeJS(self, buff):
code = "%s.stop(); // ensure sound has stopped at end of routine\n"
Expand Down
6 changes: 4 additions & 2 deletions psychopy/experiment/routine.py
Expand Up @@ -156,10 +156,12 @@ def writeMainCode(self, buff):
buff.setIndentLevel(1, True)
# on each frame
code = ('# get current time\n'
't = %s.getTime()\n'
't = {clockName}.getTime()\n'
'tThisFlip = win.getFutureFlipTime(clock={clockName})\n'
'tThisFlipGlobal = win.getFutureFlipTime(clock=None)\n'
'frameN = frameN + 1 # number of completed frames '
'(so 0 is the first frame)\n')
buff.writeIndentedLines(code % self._clockName)
buff.writeIndentedLines(code.format(clockName=self._clockName))

# write the code for each component during frame
buff.writeIndentedLines('# update/draw components on each frame\n')
Expand Down

0 comments on commit bacf23f

Please sign in to comment.