Example #1
0
def feature_dimension_fit(matrix, k_range, fName = None):
        Var = []
        timer = Clock()
        for k in k_range:
                print "Reducing feature-rank to {}".format(k)
                timer.reset()
                Rn = TruncatedSVD(n_components = k, random_state = 42).fit(matrix)
                t = timer.getTime()
                Var.append([k,sum(Rn.explained_variance_ratio_)*100,t])
                print "processing time (s): {}".format(t)
        Var = np.array(Var)
        if fName != None:
                plt.plot(Var.T[0],Var.T[1])
                plt.ylabel("%variance")
                plt.xlabel("dimension in feature space")
                plt.savefig(fName)
                plt.show()
        return Var, Rn
Example #2
0
def testClock():
    try:
        c = Clock()

        t1=c.getTime()
        time.sleep(1.0)
        t2=c.getTime()

        startTime=c.getLastResetTime()

        assert t2>t1
        assert t2-t1 > 0.95
        assert t2-t1 < 1.05
        assert startTime > 0

        c.reset()
        t=c.getTime()
        assert t < 0.01

        c.reset(10)
        t=c.getTime()
        assert t > -10.0
        assert t < -9.9

        t1=c.getTime()
        c.add(50)
        t2=c.getTime()
        assert t2-t1 > -50.0
        assert t2-t1 < -49.9

        printf(">> Clock Test: PASSED")

    except Exception:
        printf(">> Clock Test: FAILED")
        printExceptionDetails()

    printf("-------------------------------------\n")
Example #3
0
def test_Clock():
    try:
        c = Clock()

        t1 = c.getTime()
        time.sleep(1.0)
        t2 = c.getTime()

        startTime = c.getLastResetTime()

        assert t2 > t1
        assert t2 - t1 > 0.95
        assert t2 - t1 < 1.05
        assert startTime > 0

        c.reset()
        t = c.getTime()
        assert t < 0.01

        c.reset(10)
        t = c.getTime()
        assert t > -10.0
        assert t < -9.9

        t1 = c.getTime()
        c.add(50)
        t2 = c.getTime()
        assert t2 - t1 > -50.0
        assert t2 - t1 < -49.9

        printf(">> Clock Test: PASSED")

    except Exception:
        printf(">> Clock Test: FAILED")
        printExceptionDetails()

    printf("-------------------------------------\n")
Example #4
0
class Session:
    """ Base Session class """
    def __init__(self, settings_file=None, eyetracker_on=False):
        """ Initializes base Session class.

        parameters
        ----------
        settings_file : str
            Path to settings file. If None, default_settings.yml is used
        eyetracker_on : bool
            Whether to enable eyetracker
        """
        self.settings_file = settings_file
        self.eyetracker_on=eyetracker_on
        self.clock = Clock()
        self.timer = Clock()
        self.start_exp = None
        self.current_trial = None
        self.log = []
        self.logfile = logging.LogFile(f='log.txt', filemode='w', level=logging.EXP)

        # Initialize
        self.settings = self._load_settings()
        self.monitor = self._create_monitor()
        self.win = self._create_window()
        self.mouse = Mouse(**self.settings['mouse'])
        self.default_fix = Circle(self.win, radius=0.3, fillColor='white', edges=1000)
        self.mri_simulator = self._setup_mri_simulator() if self.settings['mri']['simulate'] else None
        self.tracker = None

    def _load_settings(self):
        """ Loads settings and sets preferences. """
        if self.settings_file is None:
            self.settings_file = op.join(op.dirname(__file__), 'default_settings.yml')
            logging.warn(f"Using default logfile ({self.settings_file}")

        with open(self.settings_file, 'r') as f_in:
            settings = yaml.load(f_in)

        exp_prefs = settings['preferences']  # set preferences globally
        for preftype, these_settings in exp_prefs.items():
            for key, value in these_settings.items():
                pref_subclass = getattr(psychopy_prefs, preftype)
                pref_subclass[key] = value
                setattr(psychopy_prefs, preftype, pref_subclass)

        return settings

    def _create_monitor(self):
        monitor = Monitor(**self.settings['monitor'])
        monitor.save()  # needed for iohub eyetracker
        return monitor

    def _create_window(self):
        win = Window(monitor=self.monitor, **self.settings['window'])
        return win

    def _setup_mri_simulator(self):
        args = self.settings['mri'].copy()
        args.pop('simulate')
        return SyncGenerator(**args)

    def start_experiment(self):
        """ Logs the onset of the start of the experiment """
        self.start_exp = getTime()  # abs time
        self.clock.reset()  # resets global clock
        self.timer.reset()  # phase-timer

        if self.mri_simulator is not None:
            self.mri_simulator.start()

    def display_text(self, text, keys=['return'], **kwargs):
        stim = TextStim(self.win, text=text, **kwargs)
        stim.draw()
        self.win.flip()
        waitKeys(keyList=keys)

    def close(self):
        self.exp_stop = self.clock.getTime()
        print(f"Duration experiment: {self.exp_stop:.3f}\n")
        self.log = pd.concat(self.log)
        self.log['onset_abs'] = self.log['onset'] + self.start_exp
        print(self.log)

        if self.mri_simulator is not None:
            self.mri_simulator.stop()

        quit()

    def init_eyetracker(self):

        if not self.eyetracker_on:
            raise ValueError("Cannot initialize eyetracker if eyetracker_on=False!")

        EYETRACKER_NAME = 'eyetracker.hw.sr_research.eyelink.EyeTracker'
        # default_native_data_file_name: et_data
        self.iohub = launchHubServer(
            psychopy_monitor_name=self.monitor.name,
            datastore_name='test_et',
            **{EYETRACKER_NAME: {
                'enable_interface_without_connection': True
            }}
        )

        self.tracker = self.iohub.getDevice('eyetracker.hw.sr_research.eyelink.EyeTracker')

    def start_recording_eyetracker(self):
        self.tracker.setRecordingState(True)

    def stop_recording_eyetracker(self):
        self.tracker.setRecordingState(False)

    def calibrate_eyetracker(self):
        self.tracker.runSetupProcedure()

    def close_tracker(self):
        self.stop_recording_eyetracker()
        self.iohub.quit()
Example #5
0
class MDTT(object):
    def __init__(self, logfile, imgDir, subjectNum, screenType, numStim,
                 numBlocks, trialDuration, ISI, selfPaced, runPractice,
                 inputButtons, pauseButton):

        self.logfile = logfile
        self.imgDir = imgDir
        self.subjectNum = subjectNum
        self.numStim = numStim
        self.numBlocks = numBlocks
        self.trialDuration = trialDuration
        self.selfPaced = selfPaced
        self.ISI = ISI
        self.numCats = 4
        self.trialsPer = int((self.numStim / self.numCats) / 2)
        self.runPractice = runPractice
        self.leftButton = inputButtons[0]
        self.rightButton = inputButtons[1]
        self.pauseButton = pauseButton

        #Set up window, center, left and right image sizes + positions

        if (screenType == 'Windowed'):
            screenSelect = False
        elif (screenType == 'Fullscreen'):
            screenSelect = True

        self.window = Window(fullscr=screenSelect,
                             units='pix',
                             color='White',
                             allowGUI=False)
        self.imageWidth = self.window.size[1] / 5.5
        self.centerImage = ImageStim(self.window)
        self.centerImage.setSize((self.imageWidth, self.imageWidth))
        self.leftImage = ImageStim(self.window)
        self.leftImage.setPos((-1.5 * self.imageWidth, 0))
        self.leftImage.setSize((self.imageWidth, self.imageWidth))
        self.rightImage = ImageStim(self.window)
        self.rightImage.setPos((1.5 * self.imageWidth, 0))
        self.rightImage.setSize((self.imageWidth, self.imageWidth))
        self.clock = Clock()

        #Init score list for 4 categories: [correct,incorrect,response]
        self.scoreList = []
        for i in range(0, 4):
            self.scoreList.append([0, 0, 0])

    def SplitRange(self, rangeMin, rangeMax, start=0):
        """Creates a pair of indexes separated by a value. The value itself
        can be between a min-max range. Neither index can be an index that is
        in a list of already used indexes.

        rangeMin: minimum amount that indexes can be separated by
        rangeMax: maximum amount that indexes can be separated by
        start: start index of used list 
        return: a pair of indexes (index1,index2)
                (-1,-1) if range split failed
        """

        #Search through list of used indexes to ensure no duplicates
        #Ignore start/end of list, as it's already used by primacy/recency
        for i in range(0, self.numStim):
            if (i in self.usedList):
                continue
            added = random.randint(rangeMin, rangeMax)
            startPt = added
            searchedRange = False
            #Loop through the min to max range of added values
            while not searchedRange:
                if ((i + added < self.numStim)
                        and (i + added not in self.usedList)):
                    self.usedList.append(i)
                    self.usedList.append(i + added)
                    return (i, i + added)
                if (added > rangeMin):
                    added -= 1
                else:
                    added = rangeMax
                if (added == startPt):
                    searchedRange = True
        return (-1, -1)

    def CreatePairsSpaced(self):
        """Creates a list, each element containing two indexes as well as a
        trial type. The trial type is based upon the spacing of the indexes as
        follows:

        adjacent (1): numbers next to eachother e.g. (3,4) or (8,9)
        eightish (2): numbers separated by between 7-9 e.g. (5,12) or (14,23)
        sixteenish (3): numbers separated by between 15-17 e.g. (3,18) or (8,25)
        primacy/recency: (4): start and end of list numbers e.g. (1,30) or (0,31)

        Occassionally, the list will fail to successfully split into index pairs.
        (SplitRange() returns (-1,-1) when this happens). The function will retry 
        the index splitting until all indexes are used

        return: list containing elements each with: (index1,index2,trialType)
        """

        startList = range(0, self.trialsPer)
        endList = range(self.numStim - self.trialsPer, self.numStim)
        trialOrder = range(0, (self.trialsPer * 3))  #3 categories besides P/R
        random.shuffle(startList)
        random.shuffle(endList)

        #Attempt to split 0-31 range into 4 index categories
        #Split fails if any one of the index pairs is (-1,-1) at end
        def AttemptSplit():

            # 3 categories besides P/R
            trialOrder = range(0, (self.trialsPer * 3))
            random.shuffle(trialOrder)
            self.usedList = []
            attemptList = []
            finalList = []

            #Add edge index pairs (primacy/recency)
            for i in range(0, self.trialsPer):
                finalList.append((startList[i], endList[i], 4))
                self.usedList.append(startList[i])
                self.usedList.append(endList[i])

            #Add spaced (separated) pairs of indexes to list
            for trial in trialOrder:
                if (trial % 3 == 0):  #Adjacent
                    (idxOne, idxTwo) = self.SplitRange(1, 1)
                    attemptList.append((idxOne, idxTwo, 1))
                elif (trial % 3 == 1):  #Eightish
                    (idxOne, idxTwo) = self.SplitRange(7, 9)
                    attemptList.append((idxOne, idxTwo, 2))
                elif (trial % 3 == 2):  #Sixteenish
                    (idxOne, idxTwo) = self.SplitRange(15, 17)
                    attemptList.append((idxOne, idxTwo, 3))

            #Ensures PR trials (type 4) occur first. Randomize successive trials
            random.shuffle(attemptList)
            finalList.extend(attemptList)
            return finalList

        #Try AttemptSplit() until index split is successful
        splitSuccess = False
        while (not splitSuccess):
            splitList = AttemptSplit()
            foundError = False
            for pair in splitList:
                if ((pair[0] == -1) or (pair[1] == -1)):
                    foundError = True
            if (foundError == True):
                continue
            else:
                splitSuccess = True

        return splitList

    def RunTrialSingle(self, img):
        """Displays a single image at the center of the screen for a period of
        time, and captures keypresses and their respective reaction times.

        img: the image to Displays
        return: a list of keypresses and respective reaction times
        """
        self.centerImage.setImage(self.imgDir + "/%s" % (img))
        self.centerImage.draw(self.window)
        clearEvents()
        self.window.flip()
        self.clock.reset()
        keyPresses = []
        if (self.selfPaced == False):
            wait(self.trialDuration, self.trialDuration)
            keyPresses = getKeys(keyList=[
                self.leftButton, self.rightButton, self.pauseButton, "escape"
            ],
                                 timeStamped=self.clock)
        elif (self.selfPaced == True):
            keyPresses = waitKeys(keyList=[
                self.leftButton, self.rightButton, self.pauseButton, "escape"
            ],
                                  timeStamped=self.clock)
        self.window.flip()
        wait(self.ISI)
        return keyPresses

    def RunTrialDual(self, leftImg, rightImg):
        """Displays two images on the screen for a period of time, and captures
        keypresses and their respective reaction times.

        leftimg: the image to display on the left
        rightimg: the image to display on the right
        return: a list of keypresses and respective reaction times
        """
        self.leftImage.setImage(self.imgDir + "/%s" % (leftImg))
        self.rightImage.setImage(self.imgDir + "/%s" % (rightImg))
        self.leftImage.draw(self.window)
        self.rightImage.draw(self.window)
        clearEvents()
        self.window.flip()
        self.clock.reset()
        if (self.selfPaced == False):
            wait(self.trialDuration, self.trialDuration)
            keyPresses = getKeys(keyList=[
                self.leftButton, self.rightButton, self.pauseButton, "escape"
            ],
                                 timeStamped=self.clock)
        elif (self.selfPaced == True):
            keyPresses = waitKeys(keyList=[
                self.leftButton, self.rightButton, self.pauseButton, "escape"
            ],
                                  timeStamped=self.clock)
        self.window.flip()
        wait(self.ISI)
        return keyPresses

    def RunStudy(self, imageBlock, session):
        """Runs the study, i.e. the first half of each experimental block.
        Writes all relevant information about the study to a logfile.

        imageBlock: List of images to display during the study
        session: the number of the session (block number) that is running
        """
        studyPrompt = (
            "Test Session {}/{}: Are the following objects indoor or outdoor?\n\n('{}' to continue)"
            .format(session, 10, self.pauseButton))
        studyText = TextStim(self.window, studyPrompt, color='Black')
        studyText.draw(self.window)
        self.window.flip()
        continueKey = waitKeys(keyList=[self.pauseButton, 'escape'])

        if (continueKey[0] == 'escape'):
            self.logfile.write("\n\n\nStudy Not Run Early\n\n\n")
            return

        self.logfile.write("\nBegin Study %d\n" % (session))
        self.logfile.write("{h1:<6}{h2:<23}{h3:<10}{h4}\n".format(
            h1="Trial", h2="Image", h3="Response", h4="RT"))

        #Run trial for each image in the image block
        for i in range(0, len(imageBlock)):
            keyPresses = self.RunTrialSingle(imageBlock[i])
            if (keyPresses == []):
                respKey = ''
                respRT = 0
            else:
                respKey = keyPresses[0][0]
                respRT = keyPresses[0][1]
            if (respKey == "escape"):
                self.logfile.write("\n\n\nStudy block terminated early\n\n\n")
                break
            elif (respKey == self.pauseButton):
                self.Pause()

            self.logfile.write("{:^5}{:<23}{:^11}{:<1.3f}\n".format(
                i + 1, imageBlock[i], respKey, respRT))

        return

    def RunTest(self, imageBlock, pairList, session):
        """Runs the test, i.e. the second half of each experimental block.
        Wites all relevant information about the test to a logfile

        imageBlock: List of images to display during the test
        pairList: List of paired image indexes w/ trial type
        session: the number of the session (block number) that is running
        """
        testPrompt = (
            "In this phase, the same series of objects will be shown\n\nWhich came first: Left or Right?\n\n('{}' to continue)"
            .format(self.pauseButton))
        testText = TextStim(self.window, testPrompt, color='Black')
        testText.draw(self.window)
        self.window.flip()
        continueKey = waitKeys(keyList=[self.pauseButton, 'escape'])

        if (continueKey[0] == 'escape'):
            self.logfile.write("\n\n\nTest Not Run\n\n\n")
            return 0

        self.logfile.write("\nBegin Test %d\n" % (session))
        lghead = "{a:<7}{b:<7}{c:<23}{d:<23}{e:<7}{f:<7}{g:<10}{h:<7}{i}\n".format(
            a="Trial",
            b="TType",
            c="LeftImage",
            d="RightImage",
            e="LNum",
            f="RNum",
            g="CorResp",
            h="Resp",
            i="RT")
        self.logfile.write(lghead)

        #Randomize if pair is shown: (bef > aft) or (aft > bef) order
        sideOrder = range(0, len(pairList))
        random.shuffle(sideOrder)
        correct = ''
        keyPresses = []

        #Run dual image trial for each pair in the pairlist
        for i in range(0, len(pairList)):
            trialNum = i + 1
            trialType = pairList[i][2]
            firstIdx = pairList[i][0]
            secondIdx = pairList[i][1]
            firstImg = imageBlock[pairList[i][0]]
            secondImg = imageBlock[pairList[i][1]]

            #Preserve the order images were shown in
            if (sideOrder[i] % 2 == 0):
                correct = self.leftButton
                leftIdx = firstIdx
                rightIdx = secondIdx
                leftImg = firstImg
                rightImg = secondImg
                keyPresses = self.RunTrialDual(leftImg, rightImg)
            #Reverse order images were shown
            elif (sideOrder[i] % 2 == 1):
                correct = self.rightButton
                leftIdx = secondIdx
                rightIdx = firstIdx
                leftImg = secondImg
                rightImg = firstImg
                keyPresses = self.RunTrialDual(leftImg, rightImg)

            #Get first response, or set to none if no response
            if (keyPresses == []):
                respKey = ''
                respRT = 0
            else:
                respKey = keyPresses[0][0]
                respRT = keyPresses[0][1]
            #Break out of image block with escape, break out of program with f5
            if (respKey == 'escape'):
                self.logfile.write("\n\nTest block terminated early\n\n")
                break
            elif (respKey == self.pauseButton):
                self.Pause()

            #Keep track of score
            if (respKey):
                self.scoreList[pairList[i][2] - 1][2] += 1
                if (respKey == correct):
                    self.scoreList[pairList[i][2] - 1][0] += 1
                else:
                    self.scoreList[pairList[i][2] - 1][1] += 1

            #Write info to logfile
            lgspace = "{:^5}{:^9}{:<23}{:<23}{:<7}{:<10}{:<8}{:<6}{:<1.3f}\n"
            lgform = (lgspace.format(trialNum, trialType, leftImg, rightImg,
                                     leftIdx, rightIdx, correct, respKey,
                                     respRT))
            self.logfile.write(lgform)

        return 1

    def Pause(self):
        """Pauses the task, and displays a message waiting for a spacebar
        input from the user before continuing to proceed.
        """
        pauseMsg = "Experiment Paused\n\nPress '{}' to continue".format(
            self.pauseButton)
        pauseText = TextStim(self.window,
                             text=pauseMsg,
                             color='Black',
                             height=40)
        pauseText.draw(self.window)
        self.window.flip()
        waitKeys(keyList=[self.pauseButton])
        clearEvents()

    def SegmentPracticeImages(self, images):
        '''
        Return the indexes for the test, it will index the image list from study:
            [[index_left_image, index_right_image, trialType]]
        '''
        # Since we know that the images are already randomized, we can just iterate over them
        # In order for the code to work, we want 4 practice images per practice block
        if len(images) != 4:
            print "Assertion error: length of practice images is not equal to 4"
            self.window.close()
            sys.exit()

        # Trial type of 4 means long distance
        large_dist = (0, 3, 4) if random.random() > .5 else (3, 0, 4)
        mid_dist_1 = (0, 2, 2) if random.random() > .5 else (2, 0, 2)
        mid_dist_2 = (1, 3, 2) if random.random() > .5 else (3, 1, 2)
        adjacent = (0, 1, 1) if random.random() > .5 else (1, 0, 1)
        adjacent_2 = (1, 2, 1) if random.random() > .5 else (2, 1, 1)
        adjacent = adjacent if random.random() > .5 else adjacent_2

        all = [large_dist, mid_dist_1, mid_dist_2, adjacent]
        random.shuffle(all)

        return all

    def ShowPromptAndWaitForSpace(self, prompt, keylist=['space', 'escape']):
        '''
        Show the prompt on the screen and wait for space, or the keylist specified
        returns the key pressed
        '''
        keylist = [self.pauseButton, 'escape']
        text = TextStim(self.window, prompt, color='Black')
        text.draw(self.window)
        self.window.flip()
        continueKey = waitKeys(keyList=keylist)
        if len(continueKey) != 0 and continueKey[0] == 'escape':
            self.logfile.write("Terminated early.")
            self.logfile.close()
            sys.exit()
        return continueKey

    def RunSinglePractice(self, practiceBlock, imgs):
        '''
        Read in the images we want, and run the practice block for this subject
        Run encoding and test, and write to the logs 
        
        Return:
           float: ratio correct
        '''
        random.shuffle(imgs)

        ### Encoding
        # imgs = [[img, trialType, Study(x,y), Test(x,y)]]
        testIdxs = self.SegmentPracticeImages(imgs)

        self.ShowPromptAndWaitForSpace(
            " Indoor or Outdoor?\n\n('{}' to continue)".format(
                self.pauseButton))

        self.logfile.write("\nBegin Practice Study {}\n".format(practiceBlock))
        self.logfile.write("{h1:<6}{h2:<23}{h3:<10}{h4}\n".format(
            h1="Trial", h2="Image", h3="Response", h4="RT"))

        # Run the trial for each encoding trial
        for i in range(0, len(imgs)):
            keyPresses = self.RunTrialSingle(imgs[i])
            if (keyPresses == []):
                respKey = ''
                respRT = 0
            else:
                respKey = keyPresses[0][0]
                respRT = keyPresses[0][1]
            if (respKey == "escape"):
                self.logfile.write("\n\n\nStudy block terminated early\n\n\n")
                break
            elif (respKey == self.pauseButton):
                self.Pause()

            self.logfile.write("{:^5}{:<23}{:^11}{:<1.3f}\n".format(
                i + 1, imgs[i], respKey, respRT))

        ### Test
        self.ShowPromptAndWaitForSpace(
            " Which came first? Left or right? ('{}' to continue)".format(
                self.pauseButton))

        self.logfile.write("\nBegin Practice Test {}\n".format(practiceBlock))
        self.logfile.write(
            "{a:<7}{b:<7}{c:<23}{d:<23}{e:<7}{f:<7}{g:<10}{h:<7}{i}\n".format(
                a="Trial",
                b="TType",
                c="LeftImage",
                d="RightImage",
                e="LNum",
                f="RNum",
                g="CorResp",
                h="Resp",
                i="RT"))

        # Keep track of the total number they got correct
        totalCorrect = 0
        for trialNum, idxes in enumerate(testIdxs):
            leftImgIdx, rightImgIdx, trialType = idxes

            leftImg = imgs[leftImgIdx]
            rightImg = imgs[rightImgIdx]

            keyPresses = self.RunTrialDual(leftImg, rightImg)
            correct = self.leftButton if leftImgIdx < rightImgIdx else self.rightButton

            #Get first response, or set to none if no response
            if (keyPresses == []):
                respKey = ''
                respRT = 0
            else:
                respKey = keyPresses[0][0]
                respRT = keyPresses[0][1]
            #Break out of image block with escape, break out of program with f5
            if (respKey == 'escape'):
                self.logfile.write("\n\nPractice block terminated early\n\n")
                self.logfile.close()
                sys.exit()

            #Write info to logfile
            lgspace = "{:^5}{:^9}{:<23}{:<23}{:<7}{:<10}{:<8}{:<6}{:<1.3f}\n"
            lgform = (lgspace.format(trialNum + 1, trialType, leftImg,
                                     rightImg, leftImgIdx, rightImgIdx,
                                     correct, respKey, respRT))
            self.logfile.write(lgform)

            if respKey == correct:
                totalCorrect += 1

        # Return the percentage correct
        return totalCorrect / len(imgs)

    def RunPractice(self):
        '''
        Runs three rounds of practice trials. 
        If the participant gets a certain amount correct, they move on to the real test.
        '''

        dirFiles = os.listdir(self.imgDir)
        practiceImages = [img for img in dirFiles if "PR_" in img]
        if len(practiceImages) == 0:
            print "No practice images found"
            self.window.close()
            sys.exit()

        random.shuffle(practiceImages)

        # Split the practice images into three sets
        practiceImages = np.array_split(practiceImages, 3)

        # Run each practice session
        for i in range(3):
            practicePrompt = "Let's practice\n\n('{}' to continue)".format(
                self.pauseButton)
            self.ShowPromptAndWaitForSpace(practicePrompt)

            results = self.RunSinglePractice(
                i + 1, [img for img in practiceImages[i]])

            # If they get a certain percentage correct, then stop the practice
            self.ShowPromptAndWaitForSpace(
                "You got {}% correct! ('{}' to continue)".format(
                    int(results * 100), self.pauseButton))
            if results > .6:
                return

    def RunExp(self):
        """Runs through an instance of the MDT-T experiment, which includes
        arranging the images into lists/sublists, running through a given
        number of study/test blocks, and writing the scores to a logfile. 
        """

        #Print task ending message to the screen; wait for user to press escape
        def EndExp():
            exitPrompt = ("This concludes the session. Thank you for "
                          "participating!\n\nPress Escape to quit")
            exitText = TextStim(self.window, exitPrompt, color='Black')
            exitText.draw(self.window)
            self.window.flip()
            waitKeys(keyList=['escape'])
            self.window.close()

        # Run practice
        if self.runPractice:
            self.RunPractice()

        #Put image files from folder into list
        imageList = []
        for img in os.listdir(self.imgDir):
            if (
                    img[-4:] == ".jpg" and "PR_" not in img
            ):  # Make sure that PR (practice image) is not included for study/tests
                imageList.append(img)
        random.shuffle(imageList)

        #Divide imagelist into <numBlocks> # of lists, put into one list
        #Each sublist contains <numStim> # of stimuli
        imageBlockList = []
        for i in range(0, self.numBlocks):
            block = []
            for j in range(i * self.numStim,
                           self.numStim + (i * self.numStim)):
                block.append(imageList[j])
            imageBlockList.append(block)

        #Run through each study/test block
        blockOrder = range(0, self.numBlocks)
        random.shuffle(blockOrder)
        writeScores = True
        for i in range(0, len(blockOrder)):
            pairList = self.CreatePairsSpaced()
            self.RunStudy(imageBlockList[i], i + 1)
            testFinished = self.RunTest(imageBlockList[i], pairList, i + 1)
            if not testFinished:
                writeScores = False
                continue

        EndExp()
        #Return logfile and scorelist if all study/test blocks gone through
        if writeScores:
            return (self.logfile, self.scoreList)
        else:
            return (-1, -1)
Example #6
0
class MDTS(object):
    def __init__(self, logfile, imgDir, screenType, trialDuration, ISI,
                 trialsPer, selfPaced, practiceTrials, inputButtons,
                 pauseButton):

        self.logfile = logfile
        self.trialDuration = trialDuration
        self.selfPaced = selfPaced
        self.ISI = ISI
        self.trialsPer = trialsPer
        self.numTrials = (self.trialsPer * 4)  #Trials/phase = 4x trials/cond
        self.imgDir = imgDir
        self.imgIdx = 0
        self.runPracticeTrials = practiceTrials
        self.leftButton = inputButtons[0]
        self.rightButton = inputButtons[1]
        self.pauseButton = pauseButton

        if (screenType == 'Windowed'):
            screenSelect = False
        elif (screenType == 'Fullscreen'):
            screenSelect = True
        self.window = Window(fullscr=screenSelect,
                             units='pix',
                             color='White',
                             allowGUI=False)
        self.imageWidth = self.window.size[1] / 6

        #Window must be set up before imgs, as img position based on window size
        self.imageList = self.SegmentImages()
        self.clock = Clock()

        #Initialize scorelist for 4 categories;; [correct,inc,resp]
        self.scoreList = []
        for i in range(0, 4):
            self.scoreList.append([0, 0, 0])

    def Pause(self):
        """Pauses the task, and displays a message waiting for a spacebar
        input from the user before continuing to proceed.
        """
        pauseMsg = "Experiment Paused\n\nPress '{}' to continue".format(
            self.pauseButton)
        pauseText = TextStim(self.window,
                             text=pauseMsg,
                             color='Black',
                             height=40)
        pauseText.draw(self.window)
        self.window.flip()
        waitKeys(keyList=[self.pauseButton])
        clearEvents()

    def CreatePosPair(self, moveType):
        """Generates two (x,y) coordinates to be associated with a particular
        image - the first being the study phase position, and second being the
        test phase position, which is a translation in any direction by 4
        degrees of distances - none, small, big, corner.

        moveType: the amount of relative distance across the screen to move
            -0: Non move (xA,yA) = (xB,yB)
            -1: Lure High: Max Distance / 3
            -2: Lure Low: (Max Distance*2) / 3
            -3: Opposite Corners: Max Distance
        return: a tuple of two coordinate pairs ((xA,yA),(xB,yB))
                (start position of img, end position of img) 
        """

        #Map a bottom left oriented coordinate system to a center oriented one
        def CoordMap(x, y):
            xM = int(x - winL / 2)
            yM = int(y - winH / 2)
            return (xM, yM)

        #Checks whether (x,y) coordinate is near corner
        def IsNearCorner(xTest, yTest):
            if ((xTest <= (x1 + imgDis) and yTest <= (y1 + imgDis))
                    or (xTest <= (x1 + imgDis) and yTest >= (y2 - imgDis))
                    or (xTest >= (x2 - imgDis) and yTest <=
                        (y1 + imgDis)) or (xTest >= (x2 - imgDis) and yTest >=
                                           (y2 - imgDis))):
                return True
            else:
                return False

        #Generates a random point on a circle about (xA,yA) with a given radius
        def GenCoordMove(xA, yA, radius):
            deg = random.randint(0, 359)
            rad = math.radians(deg)
            vecX = math.cos(rad)
            vecY = math.sin(rad)
            xR = int(radius * vecX)
            yR = int(radius * vecY)
            xB = xA + xR
            yB = yA + yR
            return (xB, yB)

        #Standard distance formula - use for diagnostics
        def Dist(x1, y1, x2, y2):
            return (math.sqrt(math.pow(x2 - x1, 2) + math.pow(y2 - y1, 2)))

        #Calculate the length, in pixels, of the small/large moves
        winL = self.window.size[0]
        winH = self.window.size[1]
        midDis = self.imageWidth / 2
        imgDis = self.imageWidth
        x2 = math.ceil(winL - midDis)
        x1 = math.floor(0 + midDis)
        y2 = math.ceil(winH - midDis)
        y1 = math.floor(0 + midDis)
        maxDis = math.sqrt(math.pow(x2 - x1, 2) + math.pow(y2 - y1, 2))
        distSmall = math.floor(maxDis / 4)
        distLarge = math.floor((maxDis * 2) / 4)

        #Opposite corner condition: randomly choose 1 of 4 corner moves
        if (moveType == 3):
            corner = random.randint(0, 3)
            if (corner == 0): return (CoordMap(x2, y2), CoordMap(x1, y1))
            elif (corner == 1): return (CoordMap(x1, y2), CoordMap(x2, y1))
            elif (corner == 2): return (CoordMap(x1, y1), CoordMap(x2, y2))
            elif (corner == 3): return (CoordMap(x2, y1), CoordMap(x1, y2))

        #If not corner, generate random starting position and create
        #ending position based on
        else:
            while (1):
                xT = random.randint(x1, x2)
                yT = random.randint(y1, y2)

                #Check that starting coorindates are not near corner
                if (IsNearCorner(xT, yT)):
                    continue
                else:
                    (xA, yA) = (xT, yT)

                if (moveType == 0):  #moveType (0): maintain same position
                    (xB, yB) = (xA, yA)
                elif (moveType == 1):  #moveType (1): small move (1/3 max dist)
                    (xB, yB) = GenCoordMove(xA, yA, distSmall)
                elif (moveType == 2):  #moveType (2): large move (2/3 max dist)
                    (xB, yB) = GenCoordMove(xA, yA, distLarge)

                #Redo random generation if ending coordinates are near corner
                if (IsNearCorner(xB, yB)):
                    continue
                #Redo random generation if ending coordinates are out of bounds
                if ((xB < x1) or (xB > x2) or (yB < y1) or (yB > y2)):
                    continue
                else:
                    return (CoordMap(xA, yA), CoordMap(xB, yB))

    def SegmentImages(self):
        """Shuffles images in a folder into a list, then successively adds
        two coordinate pairs to each image. The image list is divided into 4
        sections, one for each type of trial (repeat, move small, move big, and
        opposite corners). For each section of images, the images are given a
        corresponding type of coordinate pair as governed by createPosPair()

        return: createdList, each element as: [image,<study(x,y)>,<test(x,y)>]
        """

        #Nested function - populates each division of the image list
        def SegmentFill(imageList, addingList, moveType):

            order = range(0, self.trialsPer)
            random.shuffle(order)
            for i in order:
                pospair = self.CreatePosPair(moveType)
                addingList.append(
                    [imageListSec[self.imgIdx], pospair[0], pospair[1]])
                self.imgIdx += 1
            return addingList

        imageListFull = []
        imageListSec = []

        #First put all available images into list and shuffle
        for i in os.listdir(self.imgDir):
            if i.lower().find('.jpg') != -1 and i.lower().find('PR_') == -1:
                imageListFull.append(i)
        random.shuffle(imageListFull)

        #Fill another list with the number of images per phase using prev list
        for j in range(0, self.numTrials):
            imageListSec.append(imageListFull[j])

        #Successively add each group of trials to madeImgList
        madeImgList = []
        addRepeatList = SegmentFill(imageListSec, madeImgList, 0)
        addLureSmall = SegmentFill(imageListSec, addRepeatList, 1)
        addLureLarge = SegmentFill(imageListSec, addLureSmall, 2)
        addCorners = SegmentFill(imageListSec, addLureLarge, 3)
        createdList = addCorners

        return createdList

    def ImageDiagnostic(self):
        """Draws colored dots onto the window. The dots' positions represent
        the respective location of where images will be placed throughout the
        course of the task.

        This function is to only be used as a diagnostic tool, so that one can
        get a general sense of where images might appear, without having to 
        actually run through the task. To use this function properly:
            taskSpatial = mdts.MDTS(...)
            taskSpatial.ImageDiagnostic
            #taskSpatial.RunExp()
        """
        win = self.window
        cRad = 50
        tp = self.trialsPer
        ls = self.imageList
        shapes = []
        leng = len(ls)
        for i in range(0, leng):
            color = "black"
            #print "{:<24}{:<15}{:<15}".format(img[0],img[1],img[2])
            img = ls[i]
            if i < tp:
                color = "black"
            elif (i > tp) and (i < tp * 2):
                color = "blue"
            elif (i > tp * 2) and (i < tp * 3):
                color = "orange"
            elif i > tp * 3:
                color = "green"

            shapes.append(Circle(win, radius=cRad, pos=img[1],
                                 fillColor=color))
            shapes.append(Circle(win, radius=cRad, pos=img[2],
                                 fillColor=color))
            shapes.append(
                ShapeStim(win,
                          units='pix',
                          lineWidth=5,
                          lineColor=color,
                          vertices=(img[1], img[2])))

            for shape in shapes:
                shape.draw(self.window)

            self.window.flip()
        waitKeys(keyList=['escape'])
        self.window.close()

    def RunTrial(self, image, pos):
        """Runs a particular trial, which includes displaying the image to the
        screen, and gathering the keypresses and their respective response times. 

        image: The filename of the image to display
        pos: Coordinates (on 6x4 grid) where image will be displayed
        return: tuple of first keypress info: (keyPress, reactionTime)
        """
        ShownImage = ImageStim(self.window)
        ShownImage.setPos(pos)
        ShownImage.setSize((self.imageWidth, self.imageWidth))
        ShownImage.setImage(self.imgDir + '/%s' % (image))
        ShownImage.draw(self.window)
        self.window.flip()
        clearEvents()
        self.clock.reset()
        keypresses = []
        if (self.selfPaced == False):
            wait(self.trialDuration, self.trialDuration)
            keypresses = getKeys(keyList=[
                self.leftButton, self.rightButton, self.pauseButton, "escape"
            ],
                                 timeStamped=self.clock)
        elif (self.selfPaced == True):
            keypresses = waitKeys(keyList=[
                self.leftButton, self.rightButton, self.pauseButton, "escape"
            ],
                                  timeStamped=self.clock)
        self.window.flip()
        wait(self.ISI)
        if len(keypresses) < 1:
            return '', 0
        return keypresses[0][0], keypresses[0][1]

    def ShowPromptAndWaitForSpace(self, prompt, keylist=['p', 'escape']):
        '''
        Show the prompt on the screen and wait for space, or the keylist specified
        returns the key pressed
        '''
        keylist = [self.pauseButton, 'escape']
        text = TextStim(self.window, prompt, color='Black')
        text.draw(self.window)
        self.window.flip()
        continueKey = waitKeys(keyList=keylist)
        if len(continueKey) != 0 and continueKey[0] == 'escape':
            self.logfile.write("Terminated early.")
            self.logfile.close()
            sys.exit()
        return continueKey

    def RunPhase(self, phaseType):
        """Runs a phase (study or test) of the task, which includes randomizing a 
        list of images, running trials for each of those images, writing
        trial information to a logfile for each trial ran, and keeping track of
        a subject's score, based on their response to each trial. 

        phaseType: 0 -> Run Study (use starting position of image)
                   1 -> Run Test (use ending position of image)
        return: 0 -> task terminated early
                1 -> task ran to completion 
        """

        studyPrompt = (
            "Let's do the real test. \n\n Are the following objects indoor or outdoor?\n\n('{}' to continue)"
            .format(self.pauseButton))
        testPrompt = (
            "In this phase, you will see the same series of objects one at a time.\n\nAre the object locations same or new? \n\n('{}' to continue)"
            .format(self.pauseButton))
        studyText = TextStim(self.window, studyPrompt, color='Black')
        testText = TextStim(self.window, testPrompt, color='Black')

        if (phaseType == 0):
            studyText.draw(self.window)  #phaseType = 0 -> Study Phase
            self.window.flip()
            self.logfile.write("\nBegin Study\n")
        elif (phaseType == 1):
            testText.draw(self.window)  #phaseType = 1 -> Test Phase
            self.window.flip()
            self.logfile.write("\nBegin Test\n")

        log = self.logfile
        log.write("{a:<22}{b:<12}{c:<14}{d:<11}{e:<9}{f:<8}{g}\n".format(
            a='Image',
            b='Type',
            c='Start',
            d='End',
            e='Correct',
            f='Resp',
            g='RT'))

        continueKey = waitKeys(keyList=[self.pauseButton, 'escape'])
        if (continueKey[0] == 'escape'):
            self.logfile.write("\n\n\nPhase Not Run\n\n\n")
            return 0

        imgs = self.imageList
        trialOrder = range(0, len(imgs))
        random.shuffle(trialOrder)

        #Run through each trial
        for i in range(0, len(trialOrder)):

            imgIdx = trialOrder[i]
            correct = ""
            #Divide image index by the trials/cond, take floor for trial type
            trialFactor = int(math.floor(imgIdx / self.trialsPer))
            trialType = ""
            if (trialFactor == 0):
                trialType = "Same"
                if (phaseType == 1):
                    correct = self.leftButton
            elif (trialFactor == 1):
                trialType = "Small"
                if (phaseType == 1):
                    correct = self.rightButton
            elif (trialFactor == 2):
                trialType = "Large"
                if (phaseType == 1):
                    correct = self.rightButton
            elif (trialFactor == 3):
                trialType = "Crnr"
                if (phaseType == 1):
                    correct = self.rightButton

            #Display image in start position in study, end position in test
            if (phaseType == 0):
                (response, RT) = self.RunTrial(imgs[imgIdx][0],
                                               imgs[imgIdx][1])
            elif (phaseType == 1):
                (response, RT) = self.RunTrial(imgs[imgIdx][0],
                                               imgs[imgIdx][2])

            if (response == "escape"):
                self.logfile.write("\n\nPhase terminated early\n\n")
                break
            elif (response == self.pauseButton):
                self.Pause()

            #Write formatted info about trial to logfile
            log.write("{:<22}{:<9}{:<14}{:<17}{:<7}{:<6}{:>0.3f}\n".format(
                imgs[imgIdx][0], trialType, imgs[imgIdx][1], imgs[imgIdx][2],
                correct, response, RT))

            #If in test phase, tally responses, correct + incorrect answers
            if (phaseType == 1):
                if (response):
                    if (trialType == 'Same'):
                        self.scoreList[0][2] += 1
                        if (response == correct):
                            self.scoreList[0][0] += 1
                        else:
                            self.scoreList[0][1] += 1
                    elif (trialType == 'Small'):
                        self.scoreList[1][2] += 1
                        if (response == correct):
                            self.scoreList[1][0] += 1
                        else:
                            self.scoreList[1][1] += 1
                    elif (trialType == 'Large'):
                        self.scoreList[2][2] += 1
                        if (response == correct):
                            self.scoreList[2][0] += 1
                        else:
                            self.scoreList[2][1] += 1
                    elif (trialType == 'Crnr'):
                        self.scoreList[3][2] += 1
                        if (response == correct):
                            self.scoreList[3][0] += 1
                        else:
                            self.scoreList[3][1] += 1

        #Implies test phase ran through to completion
        return 1

    def SegmentPracticeImages(self, images):
        '''
        Segment practice image list into the 4 conditions and
        add two coordinate pairs to each image. Add a study coordinate location
        and test coordinate location
        
        Return:
            List [image, trialType, studyCoord(x,y), testCoord(x,y)]
        '''
        images = np.array_split(images, 4)
        allImages = []

        for idx, imageType in enumerate([0, 1, 2, 3]):
            for img in images[idx]:
                xyStudy, xyTest = self.CreatePosPair(imageType)
                allImages.append([img, imageType, xyStudy, xyTest])
        return allImages

    def RunSinglePractice(self, practiceBlock, images):
        '''
        Read in the images we want, and run the practice block for this subject
        Run encoding and test, and write to the logs 
        
        Return:
           float: ratio correct
        '''
        ### Encoding

        # imgs = [[img, trialType, Study(x,y), Test(x,y)]]
        imgs = self.SegmentPracticeImages(images)

        self.ShowPromptAndWaitForSpace(
            " Outdoor or Indoor? ('{}' to continue)".format(self.pauseButton))
        random.shuffle(imgs)

        self.logfile.write(
            "\nBegin Practice Encoding {}\n\n".format(practiceBlock))
        self.logfile.write(
            "{a:<22}{b:<12}{c:<14}{d:<11}{e:<9}{f:<8}{g}\n".format(a='Image',
                                                                   b='Type',
                                                                   c='Start',
                                                                   d='End',
                                                                   e='Correct',
                                                                   f='Resp',
                                                                   g='RT'))

        # Run the trial for each encoding trial
        for i, trial in enumerate(imgs):
            img, trialType, studyCoord, testCoord = trial
            response, RT = self.RunTrial(img, studyCoord)

            if (response == "escape"):
                self.logfile.write("\n\n Practice terminated early\n\n")
                self.logfile.close()
                sys.exit()
            elif (response == self.pauseButton):
                self.Pause()

            trialTypeMap = {0: 'Same', 1: 'Small', 2: 'Large', 3: 'Crnr'}

            trialTypeStr = trialTypeMap[trialType]
            correct = ""

            self.logfile.write(
                "{:<22}{:<9}{:<14}{:<17}{:<7}{:<6}{:>0.3f}\n".format(
                    img, trialTypeStr, studyCoord, testCoord, correct,
                    response, RT))

        ### Test
        self.ShowPromptAndWaitForSpace(
            "Is the object location same or new? ('{}' to continue)".format(
                self.pauseButton))
        random.shuffle(imgs)

        self.logfile.write(
            "\nBegin Practice Test {}\n\n".format(practiceBlock))
        self.logfile.write(
            "{a:<22}{b:<12}{c:<14}{d:<11}{e:<9}{f:<8}{g}\n".format(a='Image',
                                                                   b='Type',
                                                                   c='Start',
                                                                   d='End',
                                                                   e='Correct',
                                                                   f='Resp',
                                                                   g='RT'))

        # Keep track of the total number they got correct
        totalCorrect = 0
        for i, trial in enumerate(imgs):
            img, trialType, studyCoord, testCoord = trial
            response, RT = self.RunTrial(img, testCoord)

            if (response == "escape"):
                self.logfile.write("\n\n Practice terminated early\n\n")
                self.logfile.close()
                sys.exit()
            elif (response == self.pauseButton):
                self.Pause()

            trialTypeMap = {0: 'Same', 1: 'Small', 2: 'Large', 3: 'Crnr'}
            trialTypeStr = trialTypeMap[trialType]
            correct = self.leftButton if trialType == 0 else self.rightButton  # It should only be correct if its 'Same'

            self.logfile.write(
                "{:<22}{:<9}{:<14}{:<17}{:<7}{:<6}{:>0.3f}\n".format(
                    img, trialTypeStr, studyCoord, testCoord, correct,
                    response, RT))
            if correct == response:
                totalCorrect += 1

        # Return the percentage correct
        return totalCorrect / len(imgs)

    def RunPractice(self):
        '''
        Runs three rounds of practice trials. 
        If the participant gets a certain amount correct, they move on to the real test.
        '''

        dirFiles = os.listdir(self.imgDir)
        practiceImages = [img for img in dirFiles if "PR_" in img]
        random.shuffle(practiceImages)

        # Split the practice images into three sets
        practiceImages = np.array_split(practiceImages, 3)

        # Run each practice session
        for i in range(3):
            practicePrompt = "Let's practice.\n\n('{}' to continue)".format(
                self.pauseButton)
            self.ShowPromptAndWaitForSpace(practicePrompt)

            results = self.RunSinglePractice(
                i + 1, [img for img in practiceImages[i]])

            # If they get a certain percentage correct, then stop the practice
            self.ShowPromptAndWaitForSpace(
                "You got {}% correct! ('{}' to continue)".format(
                    int(results * 100), self.pauseButton))
            if results > .6:
                return

    def RunExp(self):
        """Run through an instance of the task, which includes the study and test
        phases. Also prints the exit message at the end, and closes the logfile
        if scores are not being written to it.

        return: (logfile, scorelist) if the test was run through. Assuming this
                happens, scores will be written to the logfile
        return: (-1,-1) if the task was quit prior to the completion of the
                study phase, meaning scores will not be writtent to the logfile
        """
        def EndExp():
            exitPrompt = ("This concludes the session. Thank you for "
                          "participating!\n\nPress Escape to quit")
            exitText = TextStim(self.window, exitPrompt, color='Black')
            exitText.draw(self.window)
            self.window.flip()
            waitKeys(keyList=['escape'])
            self.window.close()

        # Show main welcome window
        welcomePrompt = "Thank you for participating in our study! Press '{}' to begin".format(
            self.pauseButton)
        self.ShowPromptAndWaitForSpace(welcomePrompt)

        # If run practice trials, then RunPractice
        if self.runPracticeTrials:
            self.RunPractice()

        self.RunPhase(0)
        testFinished = self.RunPhase(1)
        if (testFinished):
            EndExp()
            return (self.logfile, self.scoreList)
        else:
            EndExp()
            self.logfile.close()
            return (-1, -1)
            print(
                f"The {key.name} key was pressed within {key.rt:.3f} seconds for a total of {key.duration:.3f} seconds."
            )
        break  # break out of the loop!

### TRIAL LOOP ROUTINE ###
# Read in conditions file
cond_df = pd.read_excel('emo_conditions.xlsx')
cond_df = cond_df.sample(frac=1)

# Create fixation target (a plus sign)
fix_target = TextStim(win, '+')
trial_clock = Clock()

# START exp clock
clock.reset()

# Show initial fixation
fix_target.draw()
win.flip()
wait(1)

for idx, row in cond_df.iterrows():
    # Extract current word and smiley
    curr_word = row['word']
    curr_smil = row['smiley']

    # Create and draw text/img
    stim_txt = TextStim(win, curr_word, pos=(0, 0.3))
    stim_img = ImageStim(
        win,
Example #8
0
def mainLoop(blocks=1, trialNumber=216):

    #global blockCount
    blockCount = 0

    trialCount = 0
    timer = Clock()

    maskDelay = delayForMask

    #make finalItems which will be reused for every block
    generatedItems = finalItems(blocks)
    itemsForDisplay = generatedItems

    #main trial loop runns for n-blocks
    for block in range(blocks):

        #f' is the python3 equivalent of "something{}something.format()" function for python2
        betweenBlocksInfo = (
            f'Der {blockCount}. Block ist nun vorbei, wenn Sie möchten können Sie eine kurze Pause machen.\n\
        \n\
        Fortfahren mit "Leertaste".')

        if blockCount == blocks - 1 and blockCount > 0:
            TextStim(win, (
                f'Der {blockCount}. Block ist nun vorbei, wenn Sie möchten können Sie eine kurze Pause machen. Nun folgt der letzte Durchgang.\n\
            \n\
Fortfahren mit "Leertaste".'),
                     units='norm',
                     pos=(0.0, 0.0)).draw()
            win.flip()
            waitKeys(keyList=['space'])
        elif blockCount > 0:
            TextStim(win, betweenBlocksInfo, units='norm',
                     pos=(0.0, 0.0)).draw()
            win.flip()
            waitKeys(keyList=['space'])

        fixationCross.draw()
        win.flip()
        wait(0.6)
        win.flip()

        blockCount = block + 1

        #loop run for n-trials
        for trial in range(trialNumber):
            if itemsForDisplay[trial]['itemInfo'][
                    'side'] == -1 and itemsForDisplay[trial]['itemInfo'][
                        'first'] == 1:
                corr = -1
            elif itemsForDisplay[trial]['itemInfo'][
                    'side'] == 1 and itemsForDisplay[trial]['itemInfo'][
                        'first'] == 1:
                corr = 1
            elif itemsForDisplay[trial]['itemInfo'][
                    'side'] == -1 and itemsForDisplay[trial]['itemInfo'][
                        'first'] == 0:
                corr = 1
            elif itemsForDisplay[trial]['itemInfo'][
                    'side'] == 1 and itemsForDisplay[trial]['itemInfo'][
                        'first'] == 0:
                corr = -1

            timeDelayBetweenNames = itemsForDisplay[trial]['itemInfo']['delay']

            timer.reset()
            time1 = timer.getTime()
            intertDelay = 0

            trialCount += 1

            waitDuration = StaticPeriod(screenHz=60)
            d = interTrialDelay()
            print('interTrialDelay: ', d)
            t1 = clock.getTime()

            if timeDelayBetweenNames != 0.0:
                waitDuration.start(d)

                nameToDrawFirst = itemsForDisplay[trial]['firstName']
                nameToDrawSecond = itemsForDisplay[trial]['secondName']
                if expVersion == 1:
                    mask1 = itemsForDisplay[trial]['firstMask']
                    mask2 = itemsForDisplay[trial]['secondMask']
                    maskStimuli = [mask1, mask2]

                stimuli = [nameToDrawFirst, nameToDrawSecond]

                intertDelay += d

                print('delay should be: ', timeDelayBetweenNames)

                nameToDrawFirst.draw()

                waitDuration.complete()
                print('1. loop dur: ', clock.getTime() - t1)

                win.flip()
                t1 = clock.getTime()

                w1 = clock.getTime()
                waitDuration.start(timeDelayBetweenNames)
                for s in stimuli:
                    s.draw()
                waitDuration.complete()
                print('2. loop dur: ', clock.getTime() - w1)

                win.flip()
                realTimeDelay = clock.getTime() - t1
                print('effective time delay: ', realTimeDelay)

                if expVersion == 1:
                    waitDuration.start(maskDelay)
                    for m in maskStimuli:
                        m.draw()
                    waitDuration.complete()
                    win.flip()

            else:
                waitDuration.start(d)

                nameToDrawFirst = itemsForDisplay[trial]['firstName']
                nameToDrawSecond = itemsForDisplay[trial]['secondName']

                if expVersion == 1:
                    mask1 = itemsForDisplay[trial]['firstMask']
                    mask2 = itemsForDisplay[trial]['secondMask']
                    maskStimuli = [mask1, mask2]

                stimuli = [nameToDrawFirst, nameToDrawSecond]

                print('delay should be: ', timeDelayBetweenNames)

                for s in stimuli:
                    s.draw()
                waitDuration.complete()
                realTimeDelay = 0
                win.flip()

                print('effective time delay: ', realTimeDelay)

                if expVersion == 1:
                    waitDuration.start(maskDelay)
                    for m in maskStimuli:
                        m.draw()
                    waitDuration.complete()
                    win.flip()

            #start response record
            responseStart = clock.getTime()

            #wait a specific amount of time for the keypress
            pressedKey = waitKeys(
                keyList=['f', 'j', 'space',
                         quitkey])  #escape for quiting the experiment

            #below just for simulating persons
            #            pressedKey = random.choice(['f','j','space'])
            #            wait(0.2)

            #end response clock
            responseEnd = clock.getTime()

            #calculate response time
            RT = responseEnd - responseStart

            if pressedKey == None:
                pass
            elif quitkey in pressedKey:
                quit()
            print('response time: ', RT)

            correct = 0

            #if-else statements for 'possible' feedback and response recording
            if pressedKey == None:
                correct = 0
            elif itemsForDisplay[trial]['itemInfo']['delay'] == 0.0:
                correct = -2  #with 0 delay there cannot be a correct answer
            elif 'f' in pressedKey and corr == 1:
                print('incorrect')
                correct = 0
            elif 'j' in pressedKey and corr == -1:
                print('incorrect')
                correct = 0
            elif 'f' in pressedKey and corr == -1:
                print('correct')
                correct = 1
            elif 'j' in pressedKey and corr == 1:
                print('correct')
                correct = 1
            elif 'space' in pressedKey:
                correct = -1  # -1 then means didnt know

            win.flip()
            d = interTrialDelay()
            print('interTrialDelay: ', d)
            intertDelay += d
            wait(d)
            fixationCross.draw()
            win.flip()

            #aand again save the current trials while waiting the inter-trial duration instead of doing nothing
            d = interTrialDelay()
            print('interTrialDelay: ', d)
            t1 = clock.getTime()
            waitDuration.start(d)
            intertDelay += d
            interTrialDuration = intertDelay
            trialDuration = timer.getTime() - time1 + d

            #what is saved:
            #name class
            #if garbled or not
            #left name
            #right name
            #intended time delay
            #real time delay
            #which name was displayed first
            #response time
            #if the response was correct
            #person ID, first name, last name, country, adress

            if itemsForDisplay[trial]['itemInfo']['side'] == -1:
                leftName = itemsForDisplay[trial]['itemInfo']['unfamWord']
                rightName = itemsForDisplay[trial]['itemInfo']['famWord']
                leftNameType = 'unfamiliar'
                rightNameType = 'familiar'
            else:
                leftName = itemsForDisplay[trial]['itemInfo']['famWord']
                rightName = itemsForDisplay[trial]['itemInfo']['unfamWord']
                leftNameType = 'familiar'
                rightNameType = 'unfamiliar'

            if itemsForDisplay[trial]['itemInfo']['first'] == 1:
                firstNameType = 'unfamiliar'
            else:
                firstNameType = 'familiar'

            if os.path.isfile(f'exp_degrade_{person.id}_{startDate}.txt'):
                with open(f'exp_degrade_{person.id}_{startDate}.txt',
                          'a') as file:
                    file.write('\t'.join([itemsForDisplay[trial]['itemInfo']['class'],str(itemsForDisplay[trial]['itemInfo']['garbled']),leftName ,\
                    rightName , leftNameType, rightNameType, str(itemsForDisplay[trial]['itemInfo']['delay']) , str(realTimeDelay), \
                    nameToDrawFirst.text.replace('%',''), firstNameType , str(RT) , str(pressedKey) ,str(correct) , str(blockCount) , str(trialCount) , person.id ,\
                    str(trialDuration), str(interTrialDuration)])+ '\n')

            else:
                with open(f'exp_degrade_{person.id}_{startDate}.txt',
                          'w+') as file:
                    file.write(
                        'class\tgarbled\tleft Name\tright Name\tleftNameType\trightNameType\tintendedTimeDelay\trealTimeDelay\t\
nameShowedFirst\tfirstNameType\tresponseTime\tpressedKey\tcorrectAnswer\tblock\ttrial\tID\ttrialDuration\tinterTrialDuration'
                        + '\n')
                with open(f'exp_degrade_{person.id}_{startDate}.txt',
                          'a') as file:
                    file.write('\t'.join([itemsForDisplay[trial]['itemInfo']['class'],str(itemsForDisplay[trial]['itemInfo']['garbled']),leftName ,\
                    rightName , leftNameType, rightNameType, str(itemsForDisplay[trial]['itemInfo']['delay']) , str(realTimeDelay), \
                    nameToDrawFirst.text.replace('%',''), firstNameType , str(RT) , str(pressedKey) ,str(correct) , str(blockCount) , str(trialCount) , person.id ,\
                    str(trialDuration), str(interTrialDuration)])+ '\n')

            waitDuration.complete()
            print('3. loop dur: ', clock.getTime() - t1)

            win.flip()

            print('trialDuration: ', trialDuration)

    endTime = expTime.getTime() - startTime
    #write person demographics
    with open(f'exp_degrade_{person.id}_{startDate}.txt', 'a') as file:
        file.write(' '.join(['experiment Duration: ', str(endTime),' ID: ',person.id,' first name: ',person.first,\
    ' last name: ',person.last,' country: ',person.country,\
    ' adress: ',person.adress,' birhtday: ',person.adress,' animal: ',person.animal]))

    TextStim(win,
             AppEndText,
             font='Calibri',
             units='norm',
             pos=(0, 0),
             height=0.08,
             wrapWidth=0.9).draw()
    win.flip()
    waitKeys(keyList=['escape'])
Example #9
0
class MDTO(object):
    def __init__(self, logfile, imgDir, screenType, expVariant, trialDuration,
                 ISI, trialsPer, selfPaced, practiceTrials, inputButtons,
                 pauseButton):

        self.logfile = logfile
        self.expVariant = expVariant
        self.trialDuration = trialDuration
        self.selfPaced = selfPaced
        self.ISI = ISI
        self.trialsPer = trialsPer
        self.imgDir = imgDir
        self.leftOvers = []
        self.splitLures = self.SplitLures()
        self.splitSingles = self.SplitSingles()
        self.runPracticeTrials = practiceTrials
        self.leftButton = inputButtons[0]
        self.rightButton = inputButtons[1]
        self.pauseButton = pauseButton

        if (screenType == 'Windowed'):
            screenSelect = False
        elif (screenType == 'Fullscreen'):
            screenSelect = True

        self.window = Window(fullscr=screenSelect,
                             units='pix',
                             color='White',
                             allowGUI=False)
        self.imageWidth = self.window.size[1] / 3

        #Define the black box that appears in the lower left, to signal EEG
        rW = 110  #Width
        rH = 60  #Height
        rectVertices = [[rW, -rH], [-rW, -rH], [-rW, rH], [rW, rH]]
        rectCenter = [(-self.window.size[0] / 2 + rW),
                      (-self.window.size[1] / 2) + rH]
        self.blackBox = ShapeStim(self.window,
                                  fillColor='black',
                                  units='pix',
                                  fillColorSpace='rgb',
                                  vertices=rectVertices,
                                  closeShape=True,
                                  interpolate=True,
                                  pos=rectCenter)
        self.rangeITI = numpy.arange(1, 1.4, .001)

        self.clock = Clock()

        #Initialize scorelist for 4 categories|| [correct,incorrect,response]
        self.scoreList = []
        for i in range(0, 4):
            self.scoreList.append([0, 0, 0])

    def GrabFileType(self, fileList, exts):
        """Takes an inputted list, as well as extension, and returns a list with
        the elements in the original list that have the desired extension.

        fileList: The list of files to search through
        ext: string containing the extension type, e.g. ".jpg", ".png"

        return: a new list, containing files only of ext type
        """
        fileListofType = []

        for aFile in fileList:
            for ext in exts:
                ftLen = len(ext)
                if (aFile[-ftLen:] == ext):
                    fileListofType.append(aFile)
                    break

        return fileListofType

    def SplitLures(self):
        """Creates and returns a list of image lures. Lures are taken from
        both the lure high and lure low directory, and an equal amount of 
        each are put into the list. Each element in the returned list has 2
        elements: [imgA,imgB] and a number, pertaining to the difficulty of
        the lure, i.e. the  degree of apparent difference between the two images.

        Return: a list, each element being: [[imgA,imgB], lureNum]
        """
        dirFiles = os.listdir(self.imgDir)
        imgTypes = ['.jpg', '.jpeg', '.JPG']
        allImgs = self.GrabFileType(dirFiles, imgTypes)
        allImgs = [img for img in allImgs if "PR" not in img]
        lureLowImgs = []
        lureHighImgs = []
        for img in allImgs:
            if (img[5] == "1"):
                lureHighImgs.append(img)
            elif (img[5] == "2"):
                lureLowImgs.append(img)

        #Sort images by name
        lureHighImgs.sort()
        lureLowImgs.sort()

        #Return a list of lures as a list w/: [[imgA,imgB],lureType]
        def LureListGroup(imgList):
            lureList = []
            for i in range(0, int(len(imgList) / 2)):
                imgA = imgList[i * 2]
                imgB = imgList[(i * 2) + 1]
                highSet = [imgA, imgB]
                lureList.append(highSet)
            return lureList

        #Create list of lures with embedded structure, then shuffle
        lureHighList = LureListGroup(lureHighImgs)
        lureLowList = LureListGroup(lureLowImgs)
        random.shuffle(lureHighList)
        random.shuffle(lureLowList)

        #Put number (num of trials) of list items from both lists into list
        selectedList = []
        for i in range(0, self.trialsPer):
            selectedList.append(lureHighList[i])
            selectedList.append(lureLowList[i])

        #Unused "leftover" A images will be used as singles
        #Probably want to use other method while seeded rand unimplemented
        #OK for now
        for i in range(self.trialsPer + 1, len(lureHighList)):
            try:
                self.leftOvers.append(lureHighList[i][0])
            except IndexError:
                pass
            try:
                self.leftOvers.append(lureLowList[i][0])
            except IndexError:
                pass

        random.shuffle(selectedList)
        return selectedList

    def SplitSingles(self):
        """Creates and returns a list of image "singles". Each item in the
        list is an image, followed by either "sF" (single Foil) or "sR"
        (single repeat), indicating if it is to be shown once or twice.

        return: list composed of [imageFileName, type]
                type: "sR" or "sF"
        """
        singles = self.leftOvers
        targetsFoils = []
        for i in range(0, self.trialsPer * 2):
            if (i % 2 == 0):
                targetsFoils.append([singles[i], "sR"])
            else:
                targetsFoils.append([singles[i], "sF"])

        random.shuffle(targetsFoils)
        return targetsFoils

    def Pause(self):
        """Pauses the task, and displays a message waiting for a spacebar
        input from the user before continuing to proceed.
        """
        pauseMsg = "Experiment Paused\n\nPress '{}' to continue".format(
            self.pauseButton)
        pauseText = TextStim(self.window,
                             text=pauseMsg,
                             color='Black',
                             height=40)
        pauseText.draw(self.window)
        self.window.flip()
        waitKeys(keyList=[self.pauseButton])
        clearEvents()

    def ScaleImage(self, image, maxSize=350):
        """Scales the size of the image to fit as largely as it can within the 
        window of the defined maxSize, while preserving its aspect ratio.

        image: the filename of the image to be scaled
        maxSize: maximum size, in pixels of image
        return: maximum scaling of image
        """
        im = Image.open(image)
        larger = im.size[0]
        if (im.size[0] < im.size[1]):
            larger = im.size[1]
        scale = larger / maxSize
        scaledSize = (im.size[0] / scale, im.size[1] / scale)
        return scaledSize

    def RunTrialECog(self, image, phase):
        """Runs a particular trial for an ECog (Electrocorticography) based 
        task. An ECog trial runs as follows: display the image along with
        the black box for <trial duration> amount of time, clear the screen
        for <ISI> amount of time, then asking for and getting subject input
        for <ITI> amount of time.

        image: the stimuli to display on screen
        phase: 0 (Study Phase) - prompts user "Indoor / Outdoor"
               1 (Test Phase) - prompts user "Old / New"
        return: [keyPress, reactionTime]
        """
        theImage = ImageStim(self.window)
        #Set the full path of the image, based on the image's lure type
        if (image[0][5] == "3"):
            image = (self.imgSnglDir + '%s' % (image[0]))
        elif ((image[0][5] == "1") or (image[0][5] == "2")):
            image = (self.lureHighDir + '%s' % (image[0]))
        elif ((image[0][5] == "4") or (image[0][5] == "5")):
            image = (self.lureLowDir + '%s' % (image[0]))

        theImage.setImage(image)
        imageSize = self.ScaleImage(image, self.imageWidth)
        theImage.setSize(imageSize)

        ecogISI = 0.5
        posLeftText = (-(self.window.size[0] / 8), 0)
        posRightText = ((self.window.size[0] / 8), 0)
        if (phase == 0):
            ecogTrialDur = 2.0
            leftMsg = "Indoor\n\n    1"
            rightMsg = "Outdoor\n\n     2"
        else:
            ecogTrialDur = 1.0
            leftMsg = "Old\n\n  1"
            rightMsg = "New\n\n  2"

        theImage.draw(self.window)
        self.blackBox.draw(self.window)
        self.window.flip()
        wait(ecogTrialDur, ecogTrialDur)
        self.window.flip()
        wait(ecogISI, ecogISI)
        textLeft = TextStim(self.window,
                            text=leftMsg,
                            pos=posLeftText,
                            color='Black',
                            height=50)
        textRight = TextStim(self.window,
                             text=rightMsg,
                             pos=posRightText,
                             color='Black',
                             height=50)
        textLeft.draw(self.window)
        textRight.draw(self.window)
        self.window.flip()
        clearEvents()
        self.clock.reset()
        keyPresses = waitKeys(keyList=['1', '2', 'space', 'escape'],
                              timeStamped=self.clock,
                              maxWait=1.5)
        self.window.flip()
        random.shuffle(self.rangeITI)
        wait(self.rangeITI[0], self.rangeITI[0])

        if (not keyPresses):
            return '', 0
        return keyPresses[0][0], keyPresses[0][1]

    def RunTrial(self, image):
        """Runs a particular trial, which includes displaying the image to the
        screen, and gathering the keypresses and their respective response times. 

        image: the image (filename) to display
        returns: [keyPress, reaction time]
        """
        theImage = ImageStim(self.window)
        imagePath = os.path.normpath(self.imgDir + "/%s" % (image))

        theImage.setImage(imagePath)
        imageSize = self.ScaleImage(imagePath, self.imageWidth)
        theImage.setSize(imageSize)
        theImage.draw(self.window)
        self.window.flip()
        clearEvents()
        self.clock.reset()
        keyPresses = []
        if (self.selfPaced == False):
            wait(self.trialDuration, self.trialDuration)
            keyPresses = getKeys(keyList=[
                self.leftButton, self.rightButton, self.pauseButton, 'escape'
            ],
                                 timeStamped=self.clock)
        elif (self.selfPaced == True):
            keyPresses = waitKeys(keyList=[
                self.leftButton, self.rightButton, self.pauseButton, 'escape'
            ],
                                  timeStamped=self.clock)
        self.window.flip()
        wait(self.ISI)
        if (not keyPresses):
            return '', 0
        return keyPresses[0][0], keyPresses[0][1]

    def RunStudy(self):
        """Runs the first part of the MDT-O experiment, or the Study phase.
        In this phase, all target versions of the image pairs are shown, as
        well as half of the "singles" images. Keypresses and their repsective
        reaction times are recorded during this period, and no "right or
        wrong" answers are graded.
        """
        ecog = False if self.expVariant != "ECog" else True
        studyPromptN = (
            "Let's do the real test. \n\n Are the following objects indoor or outdoor? \n\n Press 'p' to continue"
        )
        '''
        studyPromptE = ("In the following phase, a sequence of images will be "
                        "shown.\n\n-Press '1' if the image is of an indoor "
                        "object.\n\n-Press '2' if the image is of an outdoor "
                        "object.\n\n\nPress space to begin"
                       )
        '''
        studyText = TextStim(self.window, studyPromptN, color='Black')
        if ecog:
            studyText = TextStim(self.window, studyPromptE, color='Black')
        studyText.draw(self.window)
        self.window.flip()
        continueKey = waitKeys(keyList=[self.pauseButton, 'escape'])
        if (continueKey[0] == 'escape'):
            self.logfile.write("\n\n\nStudy Not Run\n\n")
            return 0

        self.logfile.write("\nBegin Study\n\n")
        logStudyFormat = '{:<7}{:<12s} {:<10s} {:<10s} {:<4s}\n'.format(
            'Trial', 'Image', 'ImageType', 'Response', 'RT')
        self.logfile.write(logStudyFormat)

        #Create list for study: "A" pairs (targets), and repeat singles
        studyImgList = []
        for pair in self.splitLures:
            studyImgList.append([pair[0], pair[0][5]])
        for img in self.splitSingles:
            if (img[1] == "sR"):
                studyImgList.append(img)

        #Shuffle study list
        random.shuffle(studyImgList)

        #Run trial for each study image
        for i in range(0, len(studyImgList)):
            if not ecog:
                (response, RT) = self.RunTrial(studyImgList[i][0])
            else:
                (response, RT) = self.RunTrialECog(studyImgList[i][0], 0)
            if (response == "escape"):
                self.logfile.write("\n\nStudy terminated early\n\n")
                return 0
            elif (response == self.pauseButton):
                self.Pause()

            trialFormat = '{:<7}{:<17s}{:<10s}{:<6s}{:<4.3f}\n'.format(
                i + 1, studyImgList[i][0], studyImgList[i][1], response, RT)
            self.logfile.write(trialFormat)

        return 1

    def RunTest(self):
        """Runs the second part of the MDT-O experiment, or the Test phase.
        In this phase, high and low "lures" are shown, as well as all of the
        "singles" shown in the study phase, as well as entirely new images
        known as "foils".

        All keypresses and their respective reaction times are recorded during
        this period. Additionally, a tally is kept of whether the subjects
        answer was wrong or right, with a separate score for "pair" answers.
        """
        ecog = False if self.expVariant != "ECog" else True
        testPromptN = (
            "In this phase, another sequence of images will be shown"
            "\n\nAre the objects old or new?\n\n Press 'p' to continue.")
        '''testPromptE = ("In this phase, another sequence of images will be shown."
                      "\n\n-Press '1' if the image presented was also shown "
                      "in the previous phase. (Old Image)\n\n-Press '2' if the" 
                      "image presented was not shown in the previous phase."
                      " (New Image)\n\n\nPress space to begin"
                      )
        '''
        testText = TextStim(self.window, text=testPromptN, color='Black')
        if ecog:
            testText = TextStim(self.window, text=testPromptE, color='Black')

        testText.draw(self.window)
        self.window.flip()
        continueKey = waitKeys(keyList=[self.pauseButton, 'escape'])
        if (continueKey[0] == 'escape'):
            self.logfile.write("\n\n\nTest Not Run\n\n")
            return 0

        self.logfile.write("\nBegin Test\n\n")
        logTestFormat = '{:<7}{:<12}{:<11}{:<9}{:<10}{:<4}\n'.format(
            'Trial', 'Image', 'ImageType', 'CorResp', 'Response', 'RT')
        self.logfile.write(logTestFormat)

        #Create trial list for test: B and C lures, and all singles
        testImgList = []
        for pair in self.splitLures:
            testImgList.append([pair[1], pair[1][5]])
        for img in self.splitSingles:
            testImgList.append(img)

        #Shuffle trial list
        random.shuffle(testImgList)

        #Run trial for each image in list, get responses
        for i in range(0, len(testImgList)):

            correct = self.rightButton
            trialType = testImgList[i][1]
            if (trialType == "sR"):
                correct = self.leftButton
            if not ecog:
                (response, RT) = self.RunTrial(testImgList[i][0])
            else:
                (response, RT) = self.RunTrialECog(testImgList[i][0], 1)
            if (response == "escape"):
                self.logfile.write("\n\nTest terminated early\n\n")
                break
            elif (response == self.pauseButton):
                self.Pause()

            trialFormat = '{:<7}{:<15}{:<11}{:<9}{:<6}{:<4.3f}\n'.format(
                i + 1, testImgList[i][0], testImgList[i][1], correct, response,
                RT)
            self.logfile.write(trialFormat)

            #Tally scores of correct/responses
            if (response):
                if (trialType == "sR"):
                    self.scoreList[0][2] += 1
                    if (response == correct):
                        self.scoreList[0][0] += 1
                    else:
                        self.scoreList[0][1] += 1
                elif (trialType == "1"):
                    self.scoreList[1][2] += 1
                    if (response == correct):
                        self.scoreList[1][0] += 1
                    else:
                        self.scoreList[1][1] += 1
                elif (trialType == "2"):
                    self.scoreList[2][2] += 1
                    if (response == correct):
                        self.scoreList[2][0] += 1
                    else:
                        self.scoreList[2][1] += 1
                elif (trialType == "sF"):
                    self.scoreList[3][2] += 1
                    if (response == correct):
                        self.scoreList[3][0] += 1
                    else:
                        self.scoreList[3][1] += 1

        return 1

    def ShowPromptAndWaitForSpace(self, prompt, keylist=['p', 'escape']):
        '''
        Show the prompt on the screen and wait for space, or the keylist specified
        returns the key pressed
        '''
        keylist = [self.pauseButton, 'escape']
        text = TextStim(self.window, prompt, color='Black')
        text.draw(self.window)
        self.window.flip()
        continueKey = waitKeys(keyList=keylist)
        if len(continueKey) != 0 and continueKey[0] == 'escape':
            self.logfile.write("Terminated early.")
            self.logfile.close()
            sys.exit()
        return continueKey

    def RunSinglePractice(self, practiceBlock, images):
        '''
        Read in the images we want, and run the practice block for this subject
        
        Run encoding and test, and write to the logs 
        
        Return:
           float: ratio correct
        '''
        imgPairs = []
        for i in range(0, len(images) - 1, 2):
            if "foil" in images[i]:
                t = "sF"
            elif "target" in images[i]:
                t = "sR"
            elif "high" in images[i]:
                t = "2"
            elif "low" in images[i]:
                t = "1"
            imgPairs.append([images[i], images[i + 1], t])

        ### Encoding
        self.ShowPromptAndWaitForSpace(
            " Outdoor or Indoor? ('{}' to continue)".format(self.pauseButton))
        random.shuffle(imgPairs)

        self.logfile.write(
            "\nBegin Practice Encoding {}\n\n".format(practiceBlock))
        logPracticeFormat = '{:<7}{:<17}{:<11}{:<9}{:<10}{:<4}\n'.format(
            'Trial', 'Image', 'ImageType', 'CorResp', 'Response', 'RT')
        self.logfile.write(logPracticeFormat)

        # Run the trial for each encoding trial
        for i, trial in enumerate(imgPairs):
            imgA, imgB, trialType = trial
            if trialType != 'sF':
                response, RT = self.RunTrial(imgA)

                if (response == 'escape'):
                    self.logfile.write(
                        "\n\nPractice block terminated early\n\n")
                    self.logfile.close()
                    sys.exit()
                elif (response == self.pauseButton):
                    self.Pause()

                trialFormat = '{:<7}{:<17}{:<11}{:<9}{:<6}{:<4.3f}\n'.format(
                    i + 1, imgA, trialType, '', response, RT)
                self.logfile.write(trialFormat)

        ### Test
        self.ShowPromptAndWaitForSpace(
            " Old or new? ('{}' to continue)".format(self.pauseButton))
        random.shuffle(imgPairs)

        self.logfile.write(
            "\nBegin Practice Test {}\n\n".format(practiceBlock))
        self.logfile.write(logPracticeFormat)

        # Keep track of the total number they got correct
        totalCorrect = 0
        for i, trial in enumerate(imgPairs):
            imgA, imgB, trialType = trial
            if trialType == 'sR' or trialType == 'sF':
                response, RT = self.RunTrial(imgA)
            else:
                response, RT = self.RunTrial(imgB)

            correct = self.leftButton if trialType == 'sR' else self.rightButton
            if response == correct:
                totalCorrect += 1
            if (response == "escape"):
                self.logfile.write("\n\nPractice terminated early\n\n")
                return -1
            elif (response == self.pauseButton):
                self.Pause()

            trialFormat = '{:<7}{:<17}{:<11}{:<9}{:<6}{:<4.3f}\n'.format(
                i + 1, imgA, trialType, correct, response, RT)
            self.logfile.write(trialFormat)

        # Return the percentage correct
        return totalCorrect / len(imgPairs)

    def RunPractice(self):
        '''
        Runs three rounds of practice trials. 
        If the participant gets a certain amount correct, they move on to the real test.
        '''

        dirFiles = os.listdir(self.imgDir)
        practiceImages = [img for img in dirFiles if "PR" in img]

        # Run each practice session
        for i in range(3):
            practicePrompt = "Let's practice. ('{}' to continue)".format(
                self.pauseButton)
            self.ShowPromptAndWaitForSpace(practicePrompt)

            imagesThisPracticeSession = sorted([
                img for img in practiceImages if "Set_{}".format(i + 1) in img
            ])
            results = self.RunSinglePractice(i + 1, imagesThisPracticeSession)

            # If they get a certain percentage correct, then stop the practice
            self.ShowPromptAndWaitForSpace(
                "You got {}% correct! ('{}' to continue)".format(
                    int(results * 100), self.pauseButton))
            if results > .6:
                return

    def RunExp(self):
        """Run through an instance of the task, which includes the study and test
        phases. Also prints the exit message at the end, and closes the logfile
        if scores are not being written to it.

        return: (logfile, scorelist) if the test was run through. Assuming this
                happens, scores will be written to the logfile
        return: (-1,-1) if the task was quit prior to the completion of the
                study phase, meaning scores will not be writtent to the logfile
        """

        #Print task ending message to the screen, and wait escape to be prssed
        def EndExp():
            exitPrompt = ("This concludes the session. Thank you for "
                          "participating!\n\nPress Esc to quit")
            exitText = TextStim(self.window, exitPrompt, color='Black')
            exitText.draw(self.window)
            self.window.flip()
            waitKeys(keyList=['escape'])
            self.window.close()

        # Show main welcome window
        welcomePrompt = "Thank you for participating in our study! Press '{}' to begin".format(
            self.pauseButton)
        self.ShowPromptAndWaitForSpace(welcomePrompt)

        # If run practice trials, then RunPractice
        if self.runPracticeTrials:
            self.RunPractice()

        #Run study, terminate if user exits early
        studyFinished = self.RunStudy()
        testFinished = self.RunTest()

        if (not studyFinished):
            EndExp()
            self.logfile.close()
            return (-1, -1)

        EndExp()
        return (self.logfile, self.scoreList)