From f6ca2d979c3757223ded10a47c77fb1687f4d3ed Mon Sep 17 00:00:00 2001
From: tiborauer <tibor.auer@gmail.com>
Date: Fri, 8 Apr 2022 14:38:56 +0100
Subject: [PATCH] INITIAL - multiblock

---
 rMtS_multiblock.py | 318 ++++++++++++++++++++++++++-------------------
 1 file changed, 183 insertions(+), 135 deletions(-)

diff --git a/rMtS_multiblock.py b/rMtS_multiblock.py
index 21459e0..fe66c4d 100644
--- a/rMtS_multiblock.py
+++ b/rMtS_multiblock.py
@@ -1,6 +1,19 @@
 """
 Repetitive match-to-sample test
 A combination of Alekseichuk et al., 2016 (https://doi.org/10.1016/j.cub.2016.04.035) and Berger et al., 2019 (https://doi.org/10.1038/s41467-019-12057-0)
+
+conditionBlocks * nConditionBlock
+    onsetConditionBlock
+    frequency
+    trialBlocks * nTrialBlock
+        onsetTrialBlock
+        sampleTrials * nSample
+            onsetSample
+            sample
+            onsetSample
+            matchTrials * nMatch
+                match
+                onsetResponse
 """
 from psychopy import visual, data, logging, gui, core, clock, monitors
 import os
@@ -77,7 +90,6 @@ if __name__ == '__main__':
         'frequency': 10,
         'phase': 0,
         'duration': 
-            stimRamp+stimRamp+ # do not include rampUp and rampDown in the stimulation
             -array(trialBlockJitterRange).mean()+ # do not stimulate during first ISI_trialBlock
             nTrialBlock*(
             array(trialBlockJitterRange).mean()-array(sampleJitterRange).mean()+
@@ -206,8 +218,8 @@ if __name__ == '__main__':
                         ])]
                 sampleTrials[-1]['matchTrials'] = matchTrials
             trialBlocks[-1]['sampleTrials'] = sampleTrials    
-        conditionBlocks[-1]['trialBlocks'] = trialBlocks
         conditionBlocks[-1]['frequency'] = frequencies[cb]
+        conditionBlocks[-1]['trialBlocks'] = trialBlocks
 
     loopConditionBlock = data.TrialHandler(nReps=1, method='sequential', trialList=conditionBlocks, autoLog=False)
     thisExp.addLoop(loopConditionBlock)
@@ -295,14 +307,14 @@ if __name__ == '__main__':
 
     ######## RUN ########
     # Main loop
-    for thisBlock in loopBlock:
+    for thisConditionBlock in loopConditionBlock:
 
         # Rest
-        if thisBlock.thisTrialN == 0: trialClock.reset() 
+        if thisConditionBlock.thisTrialN == 0: trialClock.reset() 
         else: trialClock.reset(-interimClock.getTime()) 
         restStim.status = NOT_STARTED
 
-        while trialClock.getTime() < restDuration:
+        while trialClock.getTime() < thisConditionBlock['onsetConditionBlock']:
             # get current time
             t = trialClock.getTime()
             frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
@@ -320,78 +332,70 @@ if __name__ == '__main__':
                 restStim.draw()
 
             win.flip()
-        interimClock.reset(restDuration-trialClock.getTime())
+        interimClock.reset(thisConditionBlock['onsetConditionBlock']-trialClock.getTime())
             
         if restStim.status == STARTED:
             restStim.status = STOPPED
             win.logOnFlip(level=logging.EXP, msg='Rest - STOPPED')
 
-        loopSample = data.TrialHandler(nReps=1, method='sequential', trialList=thisBlock['sampleTrials'], autoLog=False)
-        thisExp.addLoop(loopSample)
-
-        if doSTIMULATION:
-            if thisBlock['frequency']: 
-                wave1.frequency = thisBlock['frequency']
-                wave2.frequency = thisBlock['frequency']
-                BSO.initialize()
-                BSO.loadWaveform([wave1, wave2])
-                BSO.stimulate()
-                logging.log(level=logging.DATA, msg='Stimulation - {:.3f} - Frequency: {}'.format(SSO.clock,BSO.waves[0].frequency))
-            else:
-                logging.log(level=logging.DATA, msg='Stimulation - {:.3f} - Frequency: {}'.format(SSO.clock,0))
+        loopTrialBlock = data.TrialHandler(nReps=1, method='sequential', trialList=thisConditionBlock['trialBlocks'], autoLog=False)
+        thisExp.addLoop(loopTrialBlock)
 
-        for thisSample in loopSample:
-            # Sample
-            trialClock.reset(-interimClock.getTime()) 
-            jitter = thisSample['onsetSample']
+        for thisTrialBlock in loopTrialBlock:
+            # trial block
 
-            sampleCoordinates = cellCoordinates[thisSample['sample'],:]
-            sampleForm = visual.ElementArrayStim(win, nElements=sampleCoordinates.shape[0], sizes=sampleSize, xys = sampleCoordinates, units='pix', 
-                elementTex=None, elementMask="circle", colors=colour, colorSpace='rgb', autoLog=False)
-            sampleForm.status = NOT_STARTED   
+            # ISI_trialblock
+            trialClock.reset(-interimClock.getTime()) 
+            restStim.status = NOT_STARTED
 
-            while trialClock.getTime() < (jitter + sampleDuration):
+            while trialClock.getTime() < thisTrialBlock['onsetTrialBlock']:
                 # get current time
                 t = trialClock.getTime()
                 frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
 
-                # update/draw components on each frame
-                gridForm.draw()
-                
                 # *sampleForm* updates
-                if t >= jitter and sampleForm.status == NOT_STARTED:
+                if restStim.status == NOT_STARTED:
                     # keep track of start time/frame for later
-                    sampleForm.tStart = t
-                    sampleForm.frameNStart = frameN  # exact frame index
-                    sampleForm.status = STARTED
-                    win.logOnFlip(level=logging.EXP, msg='Sample - STARTED - ' + array2string(thisSample['sample']))
-                    if doTrigger: trigger.send(TRIGGER['sample'])
+                    restStim.tStart = t
+                    restStim.frameNStart = frameN  # exact frame index
+                    restStim.status = STARTED
+                    win.logOnFlip(level=logging.EXP, msg='Rest - STARTED')
+                    if doTrigger: trigger.send(TRIGGER['rest'])
                 
-                if sampleForm.status == STARTED:
-                    sampleForm.draw()
+                if restStim.status == STARTED:
+                    restStim.draw()
 
                 win.flip()
-            interimClock.reset((jitter + sampleDuration) - trialClock.getTime()) 
-
-            if sampleForm.status == STARTED:
-                sampleForm.status = STOPPED
-                win.logOnFlip(level=logging.EXP, msg='Sample - STOPPED')
-            
-            # Match
-            loopMatch = data.TrialHandler(nReps=1, method='sequential', trialList=thisSample['matchTrials'], autoLog=False)
-            thisExp.addLoop(loopMatch)
-            for thisMatch in loopMatch:
-                # Recall
+            interimClock.reset(thisTrialBlock['onsetTrialBlock']-trialClock.getTime())
+                
+            if restStim.status == STARTED:
+                restStim.status = STOPPED
+                win.logOnFlip(level=logging.EXP, msg='Rest - STOPPED')
+
+            # Stimulation
+            if doSTIMULATION and thisTrialBlock.thisTrialN == 0:
+                if thisConditionBlock['frequency']: 
+                    wave1.frequency = thisConditionBlock['frequency']
+                    wave2.frequency = thisConditionBlock['frequency']
+                    BSO.initialize()
+                    BSO.loadWaveform([wave1, wave2])
+                    BSO.stimulate()
+                    logging.log(level=logging.DATA, msg='Stimulation - {:.3f} - Frequency: {}'.format(SSO.clock,BSO.waves[0].frequency))
+                else:
+                    logging.log(level=logging.DATA, msg='Stimulation - {:.3f} - Frequency: {}'.format(SSO.clock,0))
+
+            loopSample = data.TrialHandler(nReps=1, method='sequential', trialList=thisTrialBlock['sampleTrials'], autoLog=False)
+            thisExp.addLoop(loopSample)
+
+            for thisSample in loopSample:
+                # Sample
                 trialClock.reset(-interimClock.getTime()) 
-                if loopMatch.thisTrialN == 0: 
-                    jitter = thisSample['onsetMatch']
-                    fullForm.status = NOT_STARTED
-                else: jitter = 0
+                jitter = thisSample['onsetSample']
 
-                sampleCoordinates = cellCoordinates[thisMatch['match'],:]
-                recallForm = visual.ElementArrayStim(win, nElements=sampleCoordinates.shape[0], sizes=sampleSize, xys = sampleCoordinates, units='pix', 
+                sampleCoordinates = cellCoordinates[thisSample['sample'],:]
+                sampleForm = visual.ElementArrayStim(win, nElements=sampleCoordinates.shape[0], sizes=sampleSize, xys = sampleCoordinates, units='pix', 
                     elementTex=None, elementMask="circle", colors=colour, colorSpace='rgb', autoLog=False)
-                recallForm.status == NOT_STARTED
+                sampleForm.status = NOT_STARTED   
 
                 while trialClock.getTime() < (jitter + sampleDuration):
                     # get current time
@@ -400,98 +404,142 @@ if __name__ == '__main__':
 
                     # update/draw components on each frame
                     gridForm.draw()
-
-                    # fullForm
-                    if jitter:
-                        if t < fillerDuration and fullForm.status == NOT_STARTED:
-                            fullForm.tStart = t
-                            fullForm.frameNStart = frameN  # exact frame index
-                            fullForm.status = STARTED
-                            win.logOnFlip(level=logging.EXP, msg='Mask - STARTED - ' + array2string(thisMatch['match']))
-                            if doTrigger: trigger.send(TRIGGER['mask'])
-                        
-                        if t >= fillerDuration and fullForm.status == STARTED:
-                            fullForm.status = STOPPED
-                            win.logOnFlip(level=logging.EXP, msg='Mask - STOPPED')
-
-                    # *recallForm* updates
-                    if t >= jitter and recallForm.status == NOT_STARTED:
-                        recallForm.tStart = t
-                        recallForm.frameNStart = frameN  # exact frame index
-                        recallForm.status = STARTED
-                        win.logOnFlip(level=logging.EXP, msg='Match - STARTED - ' + array2string(thisMatch['match']))
-                        if doTrigger: trigger.send(TRIGGER['testBase']+loopMatch.thisTrialN+1)
                     
-                    if fullForm.status == STARTED:
-                        fullForm.draw()
-
-                    if recallForm.status == STARTED:
-                        recallForm.draw()
+                    # *sampleForm* updates
+                    if t >= jitter and sampleForm.status == NOT_STARTED:
+                        # keep track of start time/frame for later
+                        sampleForm.tStart = t
+                        sampleForm.frameNStart = frameN  # exact frame index
+                        sampleForm.status = STARTED
+                        win.logOnFlip(level=logging.EXP, msg='Sample - STARTED - ' + array2string(thisSample['sample']))
+                        if doTrigger: trigger.send(TRIGGER['sample'])
+                    
+                    if sampleForm.status == STARTED:
+                        sampleForm.draw()
 
                     win.flip()
                 interimClock.reset((jitter + sampleDuration) - trialClock.getTime()) 
 
-                if recallForm.status == STARTED:
-                    recallForm.status = STOPPED
-                    win.logOnFlip(level=logging.EXP, msg='Match - STOPPED')
-                
-                # Response 
-                trialClock.reset(-interimClock.getTime()) 
-                jitter = thisMatch['onsetResponse']
-
-                responseStim.status = NOT_STARTED
-                SSO.reset_buttons()
+                if sampleForm.status == STARTED:
+                    sampleForm.status = STOPPED
+                    win.logOnFlip(level=logging.EXP, msg='Sample - STOPPED')
                 
-                while trialClock.getTime() < (jitter + responseDuration):
-                    # get current time
-                    t = trialClock.getTime()
-                    frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
+                # Match
+                loopMatch = data.TrialHandler(nReps=1, method='sequential', trialList=thisSample['matchTrials'], autoLog=False)
+                thisExp.addLoop(loopMatch)
+                for thisMatch in loopMatch:
+                    # Recall
+                    trialClock.reset(-interimClock.getTime()) 
+                    if loopMatch.thisTrialN == 0: 
+                        jitter = thisSample['onsetMatch']
+                        fullForm.status = NOT_STARTED
+                    else: jitter = 0
+
+                    sampleCoordinates = cellCoordinates[thisMatch['match'],:]
+                    recallForm = visual.ElementArrayStim(win, nElements=sampleCoordinates.shape[0], sizes=sampleSize, xys = sampleCoordinates, units='pix', 
+                        elementTex=None, elementMask="circle", colors=colour, colorSpace='rgb', autoLog=False)
+                    recallForm.status == NOT_STARTED
+
+                    while trialClock.getTime() < (jitter + sampleDuration):
+                        # get current time
+                        t = trialClock.getTime()
+                        frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
+
+                        # update/draw components on each frame
+                        gridForm.draw()
+
+                        # fullForm
+                        if jitter:
+                            if t < fillerDuration and fullForm.status == NOT_STARTED:
+                                fullForm.tStart = t
+                                fullForm.frameNStart = frameN  # exact frame index
+                                fullForm.status = STARTED
+                                win.logOnFlip(level=logging.EXP, msg='Mask - STARTED - ' + array2string(thisMatch['match']))
+                                if doTrigger: trigger.send(TRIGGER['mask'])
+                            
+                            if t >= fillerDuration and fullForm.status == STARTED:
+                                fullForm.status = STOPPED
+                                win.logOnFlip(level=logging.EXP, msg='Mask - STOPPED')
+
+                        # *recallForm* updates
+                        if t >= jitter and recallForm.status == NOT_STARTED:
+                            recallForm.tStart = t
+                            recallForm.frameNStart = frameN  # exact frame index
+                            recallForm.status = STARTED
+                            win.logOnFlip(level=logging.EXP, msg='Match - STARTED - ' + array2string(thisMatch['match']))
+                            if doTrigger: trigger.send(TRIGGER['testBase']+loopMatch.thisTrialN+1)
+                        
+                        if fullForm.status == STARTED:
+                            fullForm.draw()
 
-                    # update/draw components on each frame
-                    gridForm.draw()
-                    
-                    # *sampleForm* updates
-                    if t >= jitter and responseStim.status == NOT_STARTED:
-                        # keep track of start time/frame for later
-                        responseStim.tStart = t
-                        responseStim.frameNStart = frameN  # exact frame index
-                        responseStim.setAutoDraw(True)
-                        win.logOnFlip(level=logging.EXP, msg='Response - STARTED')
-                        if doTrigger: trigger.send(TRIGGER['responseBase']+loopMatch.thisTrialN+1)
-                        SSO.wait_for_button(no_block=True) 
+                        if recallForm.status == STARTED:
+                            recallForm.draw()
 
-                    win.flip()
-                interimClock.reset((jitter + responseDuration)-trialClock.getTime())
+                        win.flip()
+                    interimClock.reset((jitter + sampleDuration) - trialClock.getTime()) 
 
-                if responseStim.status == STARTED:
-                    responseStim.status = STOPPED
-                    responseStim.setAutoDraw(False)
-                    win.logOnFlip(level=logging.EXP, msg='Response - STOPPED')
-                
-                if len(SSO.buttonpresses): # no - SSO.buttonpresses[-1][0] = bNo; yes - SSO.buttonpresses[-1][0] = bYes
-                    logging.log(level=logging.EXP, msg='Button - {:.3f} - {}'.format(SSO.buttonpresses[-1][1],SSO.buttonpresses[-1][0]))
-                    thisExp.addData('resp.key',SSO.buttonpresses[-1][0])
-                    thisExp.addData('resp.rt',SSO.buttonpresses[-1][1]-(SSO.clock-trialClock.getTime())-responseStim.tStart)
+                    if recallForm.status == STARTED:
+                        recallForm.status = STOPPED
+                        win.logOnFlip(level=logging.EXP, msg='Match - STOPPED')
                     
-                    if expInfo['match type'] == 'single':
-                        isMatch = isin(thisMatch['match'],thisSample['sample'])
-                    elif expInfo['match type'] == 'multi':
-                        isMatch = all(thisMatch['match'] == thisSample['sample'])
+                    # Response 
+                    trialClock.reset(-interimClock.getTime()) 
+                    jitter = thisMatch['onsetResponse']
+
+                    responseStim.status = NOT_STARTED
+                    SSO.reset_buttons()
                     
-                    if isMatch and (SSO.buttonpresses[-1][0] == bYes): 
-                        thisExp.addData('resp.code','hit')
-                        if doTrigger: trigger.send(TRIGGER['responseCorrect'])
-                    elif not(isMatch) and (SSO.buttonpresses[-1][0] == bNo): 
-                        thisExp.addData('resp.code','cr')
-                        if doTrigger: trigger.send(TRIGGER['responseCorrect'])
-                    else:
-                        thisExp.addData('resp.code','false')
-                        if doTrigger: trigger.send(TRIGGER['responseIncorrect'])
+                    while trialClock.getTime() < (jitter + responseDuration):
+                        # get current time
+                        t = trialClock.getTime()
+                        frameN = frameN + 1  # number of completed frames (so 0 is the first frame)
 
-                else: thisExp.addData('resp.code','miss')
-                thisExp.nextEntry()
+                        # update/draw components on each frame
+                        gridForm.draw()
+                        
+                        # *sampleForm* updates
+                        if t >= jitter and responseStim.status == NOT_STARTED:
+                            # keep track of start time/frame for later
+                            responseStim.tStart = t
+                            responseStim.frameNStart = frameN  # exact frame index
+                            responseStim.setAutoDraw(True)
+                            win.logOnFlip(level=logging.EXP, msg='Response - STARTED')
+                            if doTrigger: trigger.send(TRIGGER['responseBase']+loopMatch.thisTrialN+1)
+                            SSO.wait_for_button(no_block=True) 
+
+                        win.flip()
+                    interimClock.reset((jitter + responseDuration)-trialClock.getTime())
+
+                    if responseStim.status == STARTED:
+                        responseStim.status = STOPPED
+                        responseStim.setAutoDraw(False)
+                        win.logOnFlip(level=logging.EXP, msg='Response - STOPPED')
+                    
+                    if len(SSO.buttonpresses): # no - SSO.buttonpresses[-1][0] = bNo; yes - SSO.buttonpresses[-1][0] = bYes
+                        logging.log(level=logging.EXP, msg='Button - {:.3f} - {}'.format(SSO.buttonpresses[-1][1],SSO.buttonpresses[-1][0]))
+                        thisExp.addData('resp.key',SSO.buttonpresses[-1][0])
+                        thisExp.addData('resp.rt',SSO.buttonpresses[-1][1]-(SSO.clock-trialClock.getTime())-responseStim.tStart)
+                        
+                        if expInfo['match type'] == 'single':
+                            isMatch = isin(thisMatch['match'],thisSample['sample'])
+                        elif expInfo['match type'] == 'multi':
+                            isMatch = all(thisMatch['match'] == thisSample['sample'])
+                        
+                        if isMatch and (SSO.buttonpresses[-1][0] == bYes): 
+                            thisExp.addData('resp.code','hit')
+                            if doTrigger: trigger.send(TRIGGER['responseCorrect'])
+                        elif not(isMatch) and (SSO.buttonpresses[-1][0] == bNo): 
+                            thisExp.addData('resp.code','cr')
+                            if doTrigger: trigger.send(TRIGGER['responseCorrect'])
+                        else:
+                            thisExp.addData('resp.code','false')
+                            if doTrigger: trigger.send(TRIGGER['responseIncorrect'])
+
+                    else: thisExp.addData('resp.code','miss')
+                    thisExp.nextEntry()
     
     # Rest
+    restDuration = conditionBlockJitter[0] # final rest based on first
     trialClock.reset(-interimClock.getTime()) 
     restStim.status = NOT_STARTED
 
-- 
GitLab