Example #1
0
    def run(self, *args, **kwargs):
        # PLEASE REMEMBER , THE SCREEN ORIGIN IS ALWAYS IN THE CENTER OF THE SCREEN,
        # REGARDLESS OF THE COORDINATE SPACE YOU ARE RUNNING IN. THIS MEANS 0,0 IS SCREEN CENTER,
        # -x_min, -y_min is the screen bottom left
        # +x_max, +y_max is the screen top right
        #
        # RIGHT NOW, ONLY PIXEL COORD SPACE IS SUPPORTED. THIS WILL BE FIXED.

        # Let's make some short-cuts to the devices we will be using in this 'experiment'.
        # using getDevice() returns None if the device is not found,
        tracker = self.hub.getDevice("tracker")

        display = self.devices.display
        kb = self.devices.kb
        mouse = self.devices.mouse

        if tracker is None:
            print "EyeTracker Device cdid not load."
            # return 0

        # get the experiment condition variable excel file to use.
        fdialog = FileDialog(
            message="Select a Condition Variable File",
            defaultDir=self.paths.CONDITION_FILES.getPath(),
            defaultFile="",
            openFile=True,
            allowMultipleSelections=False,
            allowChangingDirectories=True,
            fileTypes=(FileDialog.EXCEL_FILES, FileDialog.ALL_FILES),
            display_index=display.getIndex(),
        )

        result, conditionVariablesFile = fdialog.show()
        fdialog.destroy()

        if result != FileDialog.OK_RESULT:
            print "User cancelled Condition Variable Selection... Exiting Experiment."
            return

        if conditionVariablesFile:
            conditionVariablesFile = conditionVariablesFile[0]

        # create a condition set provider
        self.conditionVariablesProvider = ExperimentVariableProvider(
            conditionVariablesFile,
            "BLOCK_LABEL",
            practiceBlockValues="PRACTICE",
            randomizeBlocks=False,
            randomizeTrials=True,
        )

        # initialize (or create) a table in the ioDataStore to hold the condition variable data
        self.hub.initializeConditionVariableTable(self.conditionVariablesProvider)

        # Hide the 'system mouse cursor' so it does not bother us.
        mouse.setSystemCursorVisibility(False)

        # Create a psychopy window, full screen resolution, full screen mode, pix units, with no border, using the monitor
        # profile name 'test monitor', which is created on the fly right now by the script
        self.window = FullScreenWindow(display)

        # create screen states

        # screen state that can be used to just clear the screen to blank.
        self.clearScreen = ClearScreen(self)
        self.clearScreen.flip(text="EXPERIMENT_INIT")

        self.clearScreen.sendMessage("IO_HUB EXPERIMENT_INFO START")
        self.clearScreen.sendMessage("ioHub Experiment started {0}".format(ioHub.util.getCurrentDateTimeString()))
        self.clearScreen.sendMessage(
            "Experiment ID: {0}, Session ID: {1}".format(self.hub.experimentID, self.hub.experimentSessionID)
        )
        self.clearScreen.sendMessage(
            "Stimulus Screen ID: {0}, Size (pixels): {1}, CoordType: {2}".format(
                display.getIndex(), display.getPixelResolution(), display.getCoordinateType()
            )
        )
        self.clearScreen.sendMessage("Calculated Pixels Per Degree: {0} x, {1} y".format(*display.getPixelsPerDegree()))
        self.clearScreen.sendMessage("IO_HUB EXPERIMENT_INFO END")

        # screen for showing text and waiting for a keyboard response or something
        dtrigger = DeviceEventTrigger(kb, EventConstants.KEYBOARD_PRESS, {"key": "SPACE"})
        self.instructionScreen = InstructionScreen(
            self, "Press Space Key when Ready to Start Experiment.", dtrigger, 5 * 60
        )

        # screen state used during the data collection / runtime of the experiment to move the
        # target from one point to another.
        self.targetScreen = TargetScreen(self)

        xyEventTrigs = [
            DeviceEventTrigger(
                kb, EventConstants.KEYBOARD_PRESS, {"key": "F1"}, self.targetScreen.toggleDynamicStimVisibility
            )
        ]
        if tracker:
            self.targetScreen.dynamicStimPositionFuncPtr = tracker.getLastGazePosition
            msampleTrig = DeviceEventTrigger(
                tracker, EventConstants.MONOCULAR_EYE_SAMPLE, {}, self.targetScreen.setDynamicStimPosition
            )
            bsampleTrig = DeviceEventTrigger(
                tracker, EventConstants.BINOCULAR_EYE_SAMPLE, {}, self.targetScreen.setDynamicStimPosition
            )
            xyEventTrigs.extend([msampleTrig, bsampleTrig])
        else:
            self.targetScreen.dynamicStimPositionFuncPtr = mouse.getPosition
            msampleTrig = DeviceEventTrigger(
                mouse, EventConstants.MOUSE_MOVE, {}, self.targetScreen.setDynamicStimPosition
            )
            xyEventTrigs.append(msampleTrig)

        # setup keyboard event hook on target screen state
        # to catch any press space bar events for responses to color changes.

        dtrigger = DeviceEventTrigger(
            kb, EventConstants.KEYBOARD_PRESS, {"key": "SPACE"}, self._spaceKeyPressedDuringTargetState
        )
        xyEventTrigs.append(dtrigger)
        self.targetScreen.setEventTriggers(xyEventTrigs)

        # set all screen states background color to the first screen background color in the Excel file
        # i.e. the SCREEN_COLOR column
        displayColor = tuple(self.conditionVariablesProvider.getData()[0]["SCREEN_COLOR"])
        self.clearScreen.setScreenColor(displayColor)
        self.instructionScreen.setScreenColor(displayColor)
        self.targetScreen.setScreenColor(displayColor)

        # clear the display a few times to be sure front and back buffers are clean.
        self.clearScreen.flip()

        self.hub.clearEvents("all")

        # show the opening instruction screen, clearing events so events pre display of the
        # screen state change are not picked up by the event monitoring. This is the default,
        # so you can just call .switchTo() if you want all events cleared right after the flip
        # returns. If you 'do not' want events cleared, use .switchTo(False)
        #
        flip_time, time_since_flip, event = self.instructionScreen.switchTo(clearEvents=True, msg="EXPERIMENT_START")

        self.clearScreen.flip(text="PRACTICE_BLOCKS_START")
        # Run Practice Blocks
        self.runBlockSet(self.conditionVariablesProvider.getPracticeBlocks())
        self.clearScreen.flip(text="PRACTICE_BLOCKS_END")

        # Run Experiment Blocks
        self.clearScreen.flip(text="EXPERIMENT_BLOCKS_START")
        self.runBlockSet(self.conditionVariablesProvider.getExperimentBlocks())
        self.clearScreen.flip(text="EXPERIMENT_BLOCKS_END")

        # show the 'thanks for participating screen'
        self.instructionScreen.setText("Experiment Complete. Thank you for Participating.")
        self.instructionScreen.setTimeout(10 * 60)  # 10 minute timeout
        dtrigger = DeviceEventTrigger(kb, EventConstants.KEYBOARD_PRESS, {"key": "SPACE"})
        self.instructionScreen.setEventTriggers(dtrigger)
        flip_time, time_since_flip, event = self.instructionScreen.switchTo(msg="EXPERIMENT_END")

        # close the psychopy window
        self.window.close()

        # Done Experiment close the tracker connection if it is open.

        if tracker:
            tracker.setConnectionState(False)
Example #2
0
class ExperimentRuntime(ioHubExperimentRuntime):
    HORZ_SCALING = 0.9
    VERT_SCALING = 0.9
    HORZ_POS_COUNT = 7
    VERT_POS_COUNT = 7
    RANDOMIZE_TRIALS = True

    def __init__(self, configFileDirectory, configFile):
        ioHubExperimentRuntime.__init__(self, configFileDirectory, configFile)

    def run(self, *args, **kwargs):
        # PLEASE REMEMBER , THE SCREEN ORIGIN IS ALWAYS IN THE CENTER OF THE SCREEN,
        # REGARDLESS OF THE COORDINATE SPACE YOU ARE RUNNING IN. THIS MEANS 0,0 IS SCREEN CENTER,
        # -x_min, -y_min is the screen bottom left
        # +x_max, +y_max is the screen top right
        #
        # RIGHT NOW, ONLY PIXEL COORD SPACE IS SUPPORTED. THIS WILL BE FIXED.

        # Let's make some short-cuts to the devices we will be using in this 'experiment'.
        # using getDevice() returns None if the device is not found,
        tracker = self.hub.getDevice("tracker")

        display = self.devices.display
        kb = self.devices.kb
        mouse = self.devices.mouse

        if tracker is None:
            print "EyeTracker Device cdid not load."
            # return 0

        # get the experiment condition variable excel file to use.
        fdialog = FileDialog(
            message="Select a Condition Variable File",
            defaultDir=self.paths.CONDITION_FILES.getPath(),
            defaultFile="",
            openFile=True,
            allowMultipleSelections=False,
            allowChangingDirectories=True,
            fileTypes=(FileDialog.EXCEL_FILES, FileDialog.ALL_FILES),
            display_index=display.getIndex(),
        )

        result, conditionVariablesFile = fdialog.show()
        fdialog.destroy()

        if result != FileDialog.OK_RESULT:
            print "User cancelled Condition Variable Selection... Exiting Experiment."
            return

        if conditionVariablesFile:
            conditionVariablesFile = conditionVariablesFile[0]

        # create a condition set provider
        self.conditionVariablesProvider = ExperimentVariableProvider(
            conditionVariablesFile,
            "BLOCK_LABEL",
            practiceBlockValues="PRACTICE",
            randomizeBlocks=False,
            randomizeTrials=True,
        )

        # initialize (or create) a table in the ioDataStore to hold the condition variable data
        self.hub.initializeConditionVariableTable(self.conditionVariablesProvider)

        # Hide the 'system mouse cursor' so it does not bother us.
        mouse.setSystemCursorVisibility(False)

        # Create a psychopy window, full screen resolution, full screen mode, pix units, with no border, using the monitor
        # profile name 'test monitor', which is created on the fly right now by the script
        self.window = FullScreenWindow(display)

        # create screen states

        # screen state that can be used to just clear the screen to blank.
        self.clearScreen = ClearScreen(self)
        self.clearScreen.flip(text="EXPERIMENT_INIT")

        self.clearScreen.sendMessage("IO_HUB EXPERIMENT_INFO START")
        self.clearScreen.sendMessage("ioHub Experiment started {0}".format(ioHub.util.getCurrentDateTimeString()))
        self.clearScreen.sendMessage(
            "Experiment ID: {0}, Session ID: {1}".format(self.hub.experimentID, self.hub.experimentSessionID)
        )
        self.clearScreen.sendMessage(
            "Stimulus Screen ID: {0}, Size (pixels): {1}, CoordType: {2}".format(
                display.getIndex(), display.getPixelResolution(), display.getCoordinateType()
            )
        )
        self.clearScreen.sendMessage("Calculated Pixels Per Degree: {0} x, {1} y".format(*display.getPixelsPerDegree()))
        self.clearScreen.sendMessage("IO_HUB EXPERIMENT_INFO END")

        # screen for showing text and waiting for a keyboard response or something
        dtrigger = DeviceEventTrigger(kb, EventConstants.KEYBOARD_PRESS, {"key": "SPACE"})
        self.instructionScreen = InstructionScreen(
            self, "Press Space Key when Ready to Start Experiment.", dtrigger, 5 * 60
        )

        # screen state used during the data collection / runtime of the experiment to move the
        # target from one point to another.
        self.targetScreen = TargetScreen(self)

        xyEventTrigs = [
            DeviceEventTrigger(
                kb, EventConstants.KEYBOARD_PRESS, {"key": "F1"}, self.targetScreen.toggleDynamicStimVisibility
            )
        ]
        if tracker:
            self.targetScreen.dynamicStimPositionFuncPtr = tracker.getLastGazePosition
            msampleTrig = DeviceEventTrigger(
                tracker, EventConstants.MONOCULAR_EYE_SAMPLE, {}, self.targetScreen.setDynamicStimPosition
            )
            bsampleTrig = DeviceEventTrigger(
                tracker, EventConstants.BINOCULAR_EYE_SAMPLE, {}, self.targetScreen.setDynamicStimPosition
            )
            xyEventTrigs.extend([msampleTrig, bsampleTrig])
        else:
            self.targetScreen.dynamicStimPositionFuncPtr = mouse.getPosition
            msampleTrig = DeviceEventTrigger(
                mouse, EventConstants.MOUSE_MOVE, {}, self.targetScreen.setDynamicStimPosition
            )
            xyEventTrigs.append(msampleTrig)

        # setup keyboard event hook on target screen state
        # to catch any press space bar events for responses to color changes.

        dtrigger = DeviceEventTrigger(
            kb, EventConstants.KEYBOARD_PRESS, {"key": "SPACE"}, self._spaceKeyPressedDuringTargetState
        )
        xyEventTrigs.append(dtrigger)
        self.targetScreen.setEventTriggers(xyEventTrigs)

        # set all screen states background color to the first screen background color in the Excel file
        # i.e. the SCREEN_COLOR column
        displayColor = tuple(self.conditionVariablesProvider.getData()[0]["SCREEN_COLOR"])
        self.clearScreen.setScreenColor(displayColor)
        self.instructionScreen.setScreenColor(displayColor)
        self.targetScreen.setScreenColor(displayColor)

        # clear the display a few times to be sure front and back buffers are clean.
        self.clearScreen.flip()

        self.hub.clearEvents("all")

        # show the opening instruction screen, clearing events so events pre display of the
        # screen state change are not picked up by the event monitoring. This is the default,
        # so you can just call .switchTo() if you want all events cleared right after the flip
        # returns. If you 'do not' want events cleared, use .switchTo(False)
        #
        flip_time, time_since_flip, event = self.instructionScreen.switchTo(clearEvents=True, msg="EXPERIMENT_START")

        self.clearScreen.flip(text="PRACTICE_BLOCKS_START")
        # Run Practice Blocks
        self.runBlockSet(self.conditionVariablesProvider.getPracticeBlocks())
        self.clearScreen.flip(text="PRACTICE_BLOCKS_END")

        # Run Experiment Blocks
        self.clearScreen.flip(text="EXPERIMENT_BLOCKS_START")
        self.runBlockSet(self.conditionVariablesProvider.getExperimentBlocks())
        self.clearScreen.flip(text="EXPERIMENT_BLOCKS_END")

        # show the 'thanks for participating screen'
        self.instructionScreen.setText("Experiment Complete. Thank you for Participating.")
        self.instructionScreen.setTimeout(10 * 60)  # 10 minute timeout
        dtrigger = DeviceEventTrigger(kb, EventConstants.KEYBOARD_PRESS, {"key": "SPACE"})
        self.instructionScreen.setEventTriggers(dtrigger)
        flip_time, time_since_flip, event = self.instructionScreen.switchTo(msg="EXPERIMENT_END")

        # close the psychopy window
        self.window.close()

        # Done Experiment close the tracker connection if it is open.

        if tracker:
            tracker.setConnectionState(False)

        ### End of experiment logic

    # Called by the run() method to perform a sequence of blocks in the experiment.
    # So this method has the guts of the experiment logic.
    # This method is called once to run any practice blocks, and once to run the experimental blocks.
    #
    def runBlockSet(self, blockSet):
        # using getDevice() returns None if the device is not found,
        tracker = self.hub.getDevice("tracker")

        daq = self.hub.getDevice("daq")

        # using self.devices.xxxxx raises an exception if the
        # device is not present
        kb = self.devices.kb
        display = self.devices.display

        # for each block in the group of blocks.....
        for trialSet in blockSet.getNextConditionSet():
            # if an eye tracker is connected,
            if tracker:
                self.instructionScreen.setTimeout(30 * 60.0)  # 30 minute timeout, long enough for a break if needed.
                dtrigger = DeviceEventTrigger(kb, EventConstants.KEYBOARD_PRESS, {"key": ["RETURN", "ESCAPE"]})
                self.instructionScreen.setEventTriggers(dtrigger)
                self.instructionScreen.setText(
                    "Press 'Enter' to go to eye tracker Calibration mode.\n\nTo skip calibration and start Data Recording press 'Escape'"
                )
                flip_time, time_since_flip, event = self.instructionScreen.switchTo(msg="CALIBRATION_SELECT")
                if event and event.key == "RETURN":
                    runEyeTrackerSetupAndCalibration(tracker, self.window)
                elif event and event.key == "ESCAPE":
                    print "** Calibration stage skipped for block ", blockSet.getCurrentConditionSetIteration()
                else:
                    print "** Time out occurred. Entering calibration mode to play it safe. ;)"
                    runEyeTrackerSetupAndCalibration(tracker, self.window)

            dres = display.getPixelResolution()
            # right now, target positions are automatically generated based on point grid size, screen size, and a scaling factor (a gain).
            TARGET_POSITIONS = generatedPointGrid(
                dres[0], dres[1], self.HORZ_SCALING, self.VERT_SCALING, self.HORZ_POS_COUNT, self.VERT_POS_COUNT
            )

            # indexes to display the condition variable order in start out 'non' randomized.
            RAND_INDEXES = np.arange(TARGET_POSITIONS.shape[0])

            # if conditionVariablesProvider was told to randomize trials, then randomize trial index access list.
            if self.conditionVariablesProvider.randomizeTrials is True:
                self.hub.sendMessageEvent(
                    "RAND SEED = {0}".format(ExperimentVariableProvider._randomGeneratorSeed),
                    sec_time=ExperimentVariableProvider._randomGeneratorSeed / 1000.0,
                )
                np.random.shuffle(RAND_INDEXES)

            dtrigger = DeviceEventTrigger(kb, EventConstants.KEYBOARD_PRESS, {"key": "SPACE"})
            self.instructionScreen.setEventTriggers(dtrigger)
            self.instructionScreen.setText(
                "Press 'Space' key when Ready to Start Block %d" % (blockSet.getCurrentConditionSetIteration())
            )
            flip_time, time_since_flip, event = self.instructionScreen.switchTo(msg="BLOCK_START")

            # enable high priority for the experiment process only. Not sure this is necessary, or a good idea,
            # based on tests so far frankly. Running at std priority seems to usually be just fine.
            Computer.enableRealTimePriority(True)

            # if we have a tracker, start recording.......
            if tracker:
                tracker.setRecordingState(True)

            # delay a short time to let " the data start flow'in "
            self.hub.wait(0.050)

            # In this paradigm, each 'trial' is the movement from one target location to another.
            # Recording of eye data is on for the whole block of XxY target positions within the block.
            # A rough outline of the runtime / data collection portion of a block is as follows:
            #      a) Start each block with the target at screen center.
            #      b) Wait sec.msec duration after showing the target [ column PRE_POS_CHANGE_INTERVAL ] in excel file
            #      c) Then schedule move of target to next target position at the time of the next retrace.
            #      d) Once the Target has moved to the 2nd position for the trial, wait PRE_COLOR_CHANGE_INTERVAL
            #         sec.msec before 'possibly changing the color of the center of the target. The new color is
            #         determined by the FP_INNER_COLOR2 column. If no color change is wanted, simply make this color
            #         equal to the color of the target center in column FP_INNER_COLOR for that row of the spreadsheet.
            #      e) Once the target has been redrawn (either with or without a color change, it stays in position for
            #         another POST_COLOR_CHANGE_INTERVAL sec.msec. Since ioHub is being used, all keyboard activity
            #         is being recorded to the ioDataStore file, so there is no need really to 'monitor' for
            #         the participants key presses, since we do not use it for feedback. It can be retrieved from the
            #         data file for analysis post hoc.
            #      f) After the POST_COLOR_CHANGE_INTERVAL, the current 'trial' officially ends, and the next trial
            #         starts, with the target remaining in the position it was at in the end of the last trial, but
            #         with the target center color switching to FP_INNER_COLOR.
            #      g) Then the sequence from b) starts again for the number of target positions in the block
            #        (49 currently).
            #

            self.hub.clearEvents("all")

            self._TRIAL_STATE = None
            self.targetScreen.nextAreaOfInterest = None

            for trial in trialSet.getNextConditionSet():
                currentTrialIndex = trialSet.getCurrentConditionSetIndex()

                nextTargetPosition = TARGET_POSITIONS[currentTrialIndex]
                trial["FP_X"] = nextTargetPosition[0]
                trial["FP_Y"] = nextTargetPosition[1]

                ppd_x, ppd_y = self.devices.display.getPixelsPerDegree()

                fp_outer_radius = int(trial["FP_OUTER_RADIUS"] * ppd_x), int(trial["FP_OUTER_RADIUS"] * ppd_y)
                fp_inner_radius = int(trial["FP_INNER_RADIUS"] * ppd_x), int(trial["FP_INNER_RADIUS"] * ppd_y)

                self.targetScreen.setScreenColor(tuple(trial["SCREEN_COLOR"]))
                self.targetScreen.setTargetOuterColor(tuple(trial["FP_OUTER_COLOR"]))
                self.targetScreen.setTargetInnerColor(tuple(trial["FP_INNER_COLOR"]))
                self.targetScreen.setTargetOuterSize(fp_outer_radius)
                self.targetScreen.setTargetInnerSize(fp_inner_radius)

                self.hub.clearEvents("kb")

                self.targetScreen.setTimeout(trial["PRE_POS_CHANGE_INTERVAL"])
                self._TRIAL_STATE = trial, "FIRST_PRE_POS_CHANGE_KEY"
                target_pos1_color1_time, time_since_flip, event = self.targetScreen.switchTo(
                    msg="TRIAL_TARGET_INITIAL_COLOR"
                )

                self.targetScreen.setTargetPosition(nextTargetPosition)
                self.targetScreen.setTimeout(trial["PRE_COLOR_CHANGE_INTERVAL"])
                self._TRIAL_STATE = trial, "FIRST_POST_POS_CHANGE_KEY"

                # create a 3 degree circular region (1.5 degree radius) around the next target position
                # for use as out invisible boundary
                self.targetScreen.nextAreaOfInterest = Point(*nextTargetPosition).buffer(((ppd_x + ppd_y) / 2.0) * 1.5)

                target_pos2_color1_time, time_since_flip, event = self.targetScreen.switchTo(msg="TRIAL_TARGET_MOVE")

                self.targetScreen.setTargetInnerColor(tuple(trial["FP_INNER_COLOR2"]))
                self.targetScreen.setTimeout(trial["POST_COLOR_CHANGE_INTERVAL"])
                self._TRIAL_STATE = trial, "FIRST_POST_COLOR_CHANGE_KEY"
                target_pos2_color2_time, time_since_flip, event = self.targetScreen.switchTo(
                    msg="TRIAL_TARGET_COLOR_TWO"
                )

                # end of 'trial sequence'
                # send condition variables used / populated to ioDataStore
                toSend = [self.hub.experimentSessionID, trialSet.getCurrentConditionSetIteration()]
                trial["TSTART_TIME"] = target_pos1_color1_time
                trial["APPROX_TEND_TIME"] = target_pos2_color2_time + time_since_flip
                trial["target_pos1_color1_time"] = target_pos1_color1_time
                trial["target_pos2_color1_time"] = target_pos2_color1_time
                trial["target_pos2_color2_time"] = target_pos2_color2_time

                if self.targetScreen.aoiTriggeredID:
                    trial["VOG_SAMPLE_ID_AOI_TRIGGER"] = self.targetScreen.aoiTriggeredID
                    trial["VOG_SAMPLE_TIME_AOI_TRIGGER"] = self.targetScreen.aoiTriggeredTime
                if self.targetScreen.aoiBestGaze:
                    trial["BEST_GAZE_X"] = self.targetScreen.aoiBestGaze[0]
                    trial["BEST_GAZE_Y"] = self.targetScreen.aoiBestGaze[1]

                self._TRIAL_STATE = None
                if self.targetScreen.nextAreaOfInterest:
                    del self.targetScreen.nextAreaOfInterest
                    self.targetScreen.nextAreaOfInterest = None

                toSend.extend(trial.tolist())
                self.hub.addRowToConditionVariableTable(toSend)

            # end of block of trials, clear screen
            self.clearScreen.flip(text="BLOCK_END")

            self._TRIAL_STATE = None

            # if tracking eye position, turn off eye tracking.
            if tracker:
                tracker.setRecordingState(False)
            if daq:
                daq.enableEventReporting(False)

            # turn off high priority so python GC can clean up if it needs to.
            Computer.disableHighPriority()

            # give a 100 msec delay before starting next block
            self.hub.wait(0.100)

        # end of block set, return from method.
        self.clearScreen.flip(text="BLOCK_SET_END")
        return True

    def _spaceKeyPressedDuringTargetState(self, flipTime, stateDuration, event):
        if self._TRIAL_STATE:
            trial, column_name = self._TRIAL_STATE
            if trial[column_name] <= -100:  # no RT has been registered yet
                trial[column_name] = event.time - flipTime
        return False