Skip to content

Commit

Permalink
Merge pull request #37 from jfkominsky/0.9.5
Browse files Browse the repository at this point in the history
0.9.5 - HPP upgrades
  • Loading branch information
jfkominsky committed May 16, 2022
2 parents f837a71 + 215cca6 commit 32b5334
Show file tree
Hide file tree
Showing 6 changed files with 312 additions and 87 deletions.
Binary file modified PyHab User Manual.pdf
Binary file not shown.
75 changes: 48 additions & 27 deletions PyHab/PyHabBuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def __init__(self, loadedSaved=False, settingsDict={}):
'trialTypes': [],
'trialOrder': [],
'blockList': {}, # 0.8, create blocks of trials (hab remains special)
'blockDataList':[],
'blockDataList': [],
'maxHabTrials': '14',
'setCritWindow': '3',
'setCritDivisor': '2.0',
Expand Down Expand Up @@ -86,8 +86,9 @@ def __init__(self, loadedSaved=False, settingsDict={}):
'stimLoc':'PyHab' + self.dirMarker + 'upchime1.wav',
'shape':'Rectangle',
'color':'yellow'}},
'dynamicPause':[],
'midAG':{},
'dynamicPause': [],
'midAG': {},
'hppStimScrOnly': [], # new in 0.9.5, allows for some sophisticaated HPP behavior. List of trial types.
'folderPath': '',
'prefLook': '0',
'startImage': '',
Expand Down Expand Up @@ -125,12 +126,14 @@ def __init__(self, loadedSaved=False, settingsDict={}):
self.settings['durationInclude'] = '1'
if 'loadSep' not in self.settings:
self.settings['loadSep'] = '0'
if 'hppStimScrOnly' not in self.settings.keys():
self.settings['hppStimScrOnly'] = '[]'
# Settings requiring evaluation to get sensible values. Mostly dicts.
evalList = ['dataColumns','blockSum','trialSum','maxDur','condList','baseCondList','movieEnd','playThrough',
'trialOrder','stimNames', 'stimList', 'ISI', 'maxOff','minOn','durationCriterion','autoRedo',
'onTimeDeadline','autoAdvance','playAttnGetter','attnGetterList','trialTypes','habTrialList',
'calcHabOver', 'nextFlash', 'blockList', 'dynamicPause','midAG','screenWidth','screenHeight',
'screenIndex','movieWidth','movieHeight', 'durationInclude', 'loadSep'] # in 0.9, this becomes necessary.
'screenIndex','movieWidth','movieHeight', 'durationInclude', 'loadSep', 'hppStimScrOnly'] # in 0.9, this becomes necessary.
for i in evalList:
self.settings[i] = eval(self.settings[i])
if i in ['stimList','attnGetterList']:
Expand Down Expand Up @@ -684,6 +687,8 @@ def trialTypeDlg(self, trialType="TrialTypeNew", makeNew=True, prevInfo=[]):
autoRedo = False
if trialType in self.settings['onTimeDeadline'].keys():
otdl = self.settings['onTimeDeadline'][trialType]
if otdl == self.settings['maxDur'][trialType]: # Because it has to default to this under certain circumstances
otdl = -1
else:
otdl = -1
if trialType in self.settings['durationCriterion']:
Expand Down Expand Up @@ -838,6 +843,8 @@ def trialTypeDlg(self, trialType="TrialTypeNew", makeNew=True, prevInfo=[]):

if typeInfo[6] > 0:
self.settings['onTimeDeadline'][trialType] = typeInfo[6]
elif trialType in self.settings['autoRedo']:
self.settings['onTimeDeadline'][trialType] = self.settings['maxDur'][trialType]
elif trialType in self.settings['onTimeDeadline'].keys():
del self.settings['onTimeDeadline'][trialType]

Expand Down Expand Up @@ -909,19 +916,21 @@ def advTrialDlg(self, trialType):
"""
A dialog for advanced trial settings mostly having to do with attention-getters and stimulus presentation
0/-7 = cutoff attention-getter on gaze-on? T/F [if trial has an AG - not always present]
0/-8 = cutoff attention-getter on gaze-on? T/F [if trial has an AG - not always present]
1/-6 = cutoff on-time: How long do they have to look to cut off presentation? [if trial has an AG - not always present]
1/-7 = cutoff on-time: How long do they have to look to cut off presentation? [if trial has an AG - not always present]
2/-5 = Pause stimulus presentation when infant is not looking at screen?
2/-6 = Pause stimulus presentation when infant is not looking at screen?
3/-4 = Mid-trial AG: Play an attention-getter if infant looks away mid-trial?
3/-5 = Mid-trial AG: Play an attention-getter if infant looks away mid-trial?
4/-3 = mid-trial AG trigger: Minimum time to trigger mid-trial AG
4/-4 = mid-trial AG trigger: Minimum time to trigger mid-trial AG
5/-2 = mid-trial AG cutoff: Stop mid-trial AG on gaze-on?
5/-3 = mid-trial AG cutoff: Stop mid-trial AG on gaze-on?
6/-1 = mid-trial AG cutoff ontime
6/-2 = mid-trial AG cutoff ontime
7/-1 = HPP ONLY: Only count gaze-on to stimulus-presenting screen? T/F, casts to hppStimScrOnly
Expand Down Expand Up @@ -986,57 +995,69 @@ def advTrialDlg(self, trialType):
adTypeDlg.addField("Stop mid-trial AG on gaze-on?", initial=midcutoff)
adTypeDlg.addField("Minimum on-time to stop mid-trial AG", midonmin)

hppstimonly = False
if trialType in self.settings['hppStimScrOnly']:
hppstimonly = True
adTypeDlg.addField("HPP Only: Only count stim screen as gaze-on for ending trials?", initial=hppstimonly)


adTypeInfo = adTypeDlg.show()
if adTypeDlg.OK:
fail = [] # amass invalid inputs, save the rest
if trialType in self.settings['playAttnGetter']:
if adTypeInfo[len(adTypeInfo)-7] in [True, 1, 'True', '1']:
if adTypeInfo[len(adTypeInfo)-8] in [True, 1, 'True', '1']:
self.settings['playAttnGetter'][trialType]['cutoff'] = 1
else:
self.settings['playAttnGetter'][trialType]['cutoff'] = 0
if not isinstance(adTypeInfo[len(adTypeInfo)-6], float) and not isinstance(adTypeInfo[len(adTypeInfo)-6], int):
if not isinstance(adTypeInfo[len(adTypeInfo)-7], float) and not isinstance(adTypeInfo[len(adTypeInfo)-6], int):
try:
eval(adTypeInfo[len(adTypeInfo)-6])
eval(adTypeInfo[len(adTypeInfo)-7])
except:
fail.append("Number expected for minimum on-time to end attention-getter, got text instead!")
if isinstance(adTypeInfo[len(adTypeInfo)-6], float) and not isinstance(adTypeInfo[len(adTypeInfo)-6], int):
self.settings['playAttnGetter'][trialType]['onmin'] = adTypeInfo[len(adTypeInfo)-6]
if isinstance(adTypeInfo[len(adTypeInfo)-7], float) and not isinstance(adTypeInfo[len(adTypeInfo)-7], int):
self.settings['playAttnGetter'][trialType]['onmin'] = adTypeInfo[len(adTypeInfo)-7]
# Pause behavior
if adTypeInfo[len(adTypeInfo)-5] in [True, 1, 'True', '1'] and trialType not in self.settings['dynamicPause']:
if adTypeInfo[len(adTypeInfo)-6] in [True, 1, 'True', '1'] and trialType not in self.settings['dynamicPause']:
self.settings['dynamicPause'].append(trialType)
elif adTypeInfo[len(adTypeInfo)-5] in [False, 0, 'False', '0'] and trialType in self.settings['dynamicPause']:
elif adTypeInfo[len(adTypeInfo)-6] in [False, 0, 'False', '0'] and trialType in self.settings['dynamicPause']:
# Remove if this behavior has been turned off.
self.settings['dynamicPause'].pop(self.settings['dynamicPause'].index(trialType))
# Mid-trial AG
if adTypeInfo[len(adTypeInfo)-4] in self.settings['attnGetterList']:
if adTypeInfo[len(adTypeInfo)-5] in self.settings['attnGetterList']:
# We have the luxury of only caring about certain inputs if there is a mid-trial AG
if not isinstance(adTypeInfo[len(adTypeInfo) - 3], float) and not isinstance(adTypeInfo[len(adTypeInfo) - 3], int):
if not isinstance(adTypeInfo[len(adTypeInfo) - 4], float) and not isinstance(adTypeInfo[len(adTypeInfo) - 4], int):
try:
trigger = eval(adTypeInfo[len(adTypeInfo) - 3])
trigger = eval(adTypeInfo[len(adTypeInfo) - 4])
except:
trigger = 0.0
fail.append("Number expected for minimum off-time to trigger mid-trail AG, got text instead!")
else:
trigger = adTypeInfo[len(adTypeInfo) - 3]
trigger = adTypeInfo[len(adTypeInfo) - 4]

if adTypeInfo[len(adTypeInfo)-2] in [1, '1', True, 'True']:
if adTypeInfo[len(adTypeInfo)-3] in [1, '1', True, 'True']:
midcut = 1
if not isinstance(adTypeInfo[len(adTypeInfo) - 1], float) and not isinstance(adTypeInfo[len(adTypeInfo) - 1], int):
if not isinstance(adTypeInfo[len(adTypeInfo) - 2], float) and not isinstance(adTypeInfo[len(adTypeInfo) - 2], int):
try:
midmin = eval(adTypeInfo[len(adTypeInfo) - 1])
midmin = eval(adTypeInfo[len(adTypeInfo) - 2])
except:
midmin = 0.0
fail.append("Number expected for minimum on-time to cutoff mid-trail AG, got text instead!")
else:
midmin = adTypeInfo[len(adTypeInfo) - 1]
midmin = adTypeInfo[len(adTypeInfo) - 2]
else:
midcut = 0
midmin = 0.0
self.settings['midAG'][trialType] = {'attnGetter':adTypeInfo[len(adTypeInfo)-4], 'trigger':trigger,
self.settings['midAG'][trialType] = {'attnGetter':adTypeInfo[len(adTypeInfo)-5], 'trigger':trigger,
'cutoff':midcut, 'onmin':midmin}
elif trialType in self.settings['midAG'].keys():
# If the mid-trial AG has been set to "none" but was previously set to something, remove it.
self.settings['midAG'].pop(trialType)
if adTypeInfo[len(adTypeInfo) - 1] in [1, '1', True, 'True']:
if trialType not in self.settings['hppStimScrOnly']:
self.settings['hppStimScrOnly'].append(trialType)
elif trialType in self.settings['hppStimScrOnly']:
# remove it.
self.settings['hppStimScrOnly'].pop(self.settings['hppStimScrOnly'].index(trialType))

if len(fail) > 0:
errDlg = gui.Dlg("Problem!")
Expand Down
51 changes: 27 additions & 24 deletions PyHab/PyHabClass.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ def __init__(self, settingsDict):
self.loadSep = eval(settingsDict['loadSep'])
except:
self.loadSep = 0
try:
self.hppStimScrOnly = eval(settingsDict['hppStimScrOnly'])
except:
self.hppStimScrOnly = []


# ORDER OF PRESENTATION
Expand Down Expand Up @@ -476,6 +480,8 @@ def checkStop(self):
TODO: Habituation using trial duration instead of on-time
Todo: Reconfigure for multiple habituation blocks
:return: True if hab criteria have been met, False otherwise
:rtype:
"""
Expand Down Expand Up @@ -532,7 +538,9 @@ def checkStop(self):
return True
elif self.habCount > self.setCritWindow and self.habSetWhen > -1: # if we're far enough in that we can plausibly meet the hab criterion
# Problem: Fixed window, peak, and max as relates to habsetwhen....
# Fixed window is probably the only thing that should ignore habsetwhen.
# Not problem per se. Essentially, trials that set the criterion are never included when evaluating it.
# TODO: Make that an option instead of a general behavior?
# Fixed window is the only thing that ignores habsetwhen.
# Last needs to ignore HabSetWhen, or rather, cannot wait MetCritWindow trials past when it is set.
if self.habCount < self.habSetWhen + self.metCritWindow and self.metCritStatic == 'Moving' and self.setCritType != 'Last': # Was the hab set "late" and are we too early as a result
return False
Expand Down Expand Up @@ -642,8 +650,13 @@ def attnGetter(self, trialType, cutoff=False, onmin=0.0, midTrial=False):
self.trialText.draw()
if self.blindPres < 1:
self.readyText.draw()
self.win2.flip() # If you don't refresh the expeirmenter window it doesn't read the keyboard!
if cutoff and self.lookKeysPressed():
self.win2.flip() # If you don't refresh the experimenter window it doesn't read the keyboard!
# HPP: Make aware of hppStimScrOnly.
if trialType in self.hppStimScrOnly:
lookCheck = self.lookScreenKeyPressed()
else:
lookCheck = self.lookKeysPressed()
if cutoff and lookCheck:
# Update the relevant box so it actually shows the key is down.
self.statusSquareA.fillColor = 'green'
self.statusTextA.text = "ON"
Expand All @@ -653,12 +666,12 @@ def attnGetter(self, trialType, cutoff=False, onmin=0.0, midTrial=False):
# End early, reset audio
attnGetter['file'].stop(reset=True)
break
elif cutoff and onCheck > 0: # A clever little way to say "if they aren't looking but were earlier"
elif cutoff and onCheck > 0: # A clever little way to say "if they aren't looking but were earlier"
onCheck = 0
self.statusSquareA.fillColor = 'blue'
self.statusTextA.text = "RDY"
elif i > 30 and self.keyboard[self.key.K]:
# If more than half a second (30 frames) has passed and "S" is pressed.
# If more than half a second (30 frames) has passed and "skip" is pressed.
attnGetter['file'].stop(reset=True)
break
else:
Expand All @@ -678,7 +691,12 @@ def attnGetter(self, trialType, cutoff=False, onmin=0.0, midTrial=False):
if self.blindPres < 1:
self.readyText.draw()
self.win2.flip() # If you don't refresh the expeirmenter window, it doesn't read the keyboard!
if cutoff and self.lookKeysPressed():
# HPP edge case
if trialType in self.hppStimScrOnly:
lookCheck = self.lookScreenKeyPressed()
else:
lookCheck = self.lookKeysPressed()
if cutoff and lookCheck:
self.statusSquareA.fillColor='green'
self.statusTextA.text='ON'
if onCheck == 0 and onmin > 0:
Expand Down Expand Up @@ -1263,24 +1281,8 @@ def doExperiment(self):
onCheck = 0
core.wait(self.freezeFrame)
elif self.lookKeysPressed():
# in HPP mode, the disMovie dict will have 'C', 'L', and 'R' as top-level keys and then the rest
# below it. What we need to do here is check if we're in HPP mode and then if so use
# lookScreenKeysPressed()
if self.stimPres:
if 'C' in disMovie.keys(): # if HPP, in other words
# TODO: make this a setting rather than a universal HPP behavior
checkscreens = disMovie.keys()
stimscreens = [x for x in checkscreens if disMovie[x] not in [0, '0']]
# needs to be inverse.
waitStart = self.lookScreenKeyPressed(screen=stimscreens) == False
if not waitStart:
self.dispCoderWindow(trialType)
else:
waitStart = False
self.dispCoderWindow(trialType)
else:
waitStart = False
self.dispCoderWindow(trialType)
waitStart = False
self.dispCoderWindow(trialType)
elif self.keyboard[self.key.R] and not didRedo: # Redo last trial, mark last trial as bad
if self.counters[trialType] > 0:
self.counters[trialType] -= 1
Expand Down Expand Up @@ -1722,6 +1724,7 @@ def onDuration(adds=0, subs=0): # A function for the duration switch, while lea
if self.stimPres and disMovie['stimType'] == 'Movie':
disMovie['stim'].seek(0.0)
disMovie['stim'].pause()
# Todo: Do a proper redo, including rewinding trials using redoSetup.
self.abortTrial(onArray, offArray, number, dataType, onArray2, offArray2, self.stimName, habDataRec)
return 3
else:
Expand Down

0 comments on commit 32b5334

Please sign in to comment.