Ejemplo n.º 1
0
class Experiment:
    __metaclass__ = abc.ABCMeta

    # constructor
    def __init__(self):
        self.pythonver = sys.version_info[:2]
        if self.pythonver < (3, 5):
            print("Please run with python version 3.6 or higher")
            sys.exit(0)
        else:
            print("Current python version is " + str(self.pythonver))

    # initialize experiment with defaults provided in yaml file
    def load_settings(self, filepath):
        self.gParams = yaml.load(open(filepath))  # load defaults
        self.gScreen = ExperimentDisplay(self.gParams)  # exp display
        self.gTimer = Timer(self.gParams)  # timers
        self.gData = DataManager(self.gParams)  # data manager
        self.gHardware = HardwareManager(self.gParams)  # data manager
        self.gDiagnostic = DiagnosticDisplay(self.gParams)  # diagnostic window
        self.gKeyboard = Keyboard(self.gParams)  # keyboard input polling
        self.gVariables = Variables(self.gParams)  # experiment variables
        self.gStates = []  # trial states

    # initialize data manager and provide format to expect data in
    def set_data_format(self, dataformat):
        self.gData.init_data_manager(dataformat)

    # set directory in which data is stored
    def set_data_directory(self, data_dir):
        self.gData.set_data_directory(data_dir)

    # attached hardware object with given name
    def add_hardware(self, hardware_name, hardware_obj):
        self.gHardware[hardware_name] = hardware_obj

    # set subject id (used by data manager as file name to save data to disk)
    def set_subject_id(self, subject_id):
        self.gData.set_subject_id(subject_id)

    # set states used to progress through a trial
    def set_trial_states(self, *sequential, **named):
        enums = dict(zip(sequential, range(len(sequential))), **named)
        self.gStates = type('Enum', (), enums)

    # get subject id
    def get_subject_id(self):
        return self.gData.subject_id

    # get run number
    def get_runno(self):
        return self.gData.run

    # toggle diagnostic window
    def diagnostic(self, mode):
        self.gDiagnostic.diagnostic(mode, self.gParams)

    # start run in experiment
    def start_run(self):
        # posting run starting message
        self.gData.add_dbg_event('starting run ' + str(self.get_runno()))

        # reset all timers at the start of the run
        self.gTimer.reset_all()

        # loop over all trials in the target file
        for i in range(self.gTrial.getTrialNum()):
            # run abstract function for each trial
            self.state = 0

            while self.state != self.gStates.END_TRIAL:
                self.trial()
                self.flip()

            # call onTrialEnd function to add data records for trial
            # save trial data to file
            # go to next trial
            self.onTrialEnd()
            self.gData.write_data()
            self.gTrial.nextTrial()

        # posting run ending message
        # save debug and raw data
        self.gData.add_dbg_event('run successfully completed')
        self.gData.write_dbg_data()

    # flip screen buffers
    def flip(self):
        if self.gScreen.handle != []:
            self.updateScreen()
            self.gScreen.handle.flip()

        if self.gDiagnostic.handle != []:
            self.updateDiagnostic()
            self.gDiagnostic.handle.flip()

    # close all screens associated with experiment
    def close_screens(self):
        self.gScreen.close()
        self.gDiagnostic.close()

    # control block
    def control(self):
        while True:
            resp = self.gKeyboard.poll(self.get_subject_id())

            if resp is None:
                continue

            elif resp[0] == 'quit':
                self.exit()
                break

            elif resp[0] == 'run':
                self.gData.run = resp[1]
                self.gTrial = TargetFile(resp[2])
                self.start_run()

            elif resp[0] == 'subj':
                self.set_subject_id(resp[1])

            else:
                self.define_command(resp[0])

    # define user commands
    def define_command(self, cmd):
        print('unrecognized command')

    # close all windows and clear memory
    def exit(self):
        self.close_screens()
        del self.gTimer
        del self.gData
        del self.gScreen
        del self.gDiagnostic
        del self.gKeyboard
        del self.gParams
        del self.gHardware

    # main experiment loop for each trial
    # provided as an abstract function to be overloaded by inheriting class
    @abc.abstractmethod
    def trial(self):
        """Main experiment trial loop provided by the base class"""

    # main drawing function to be called prior to screen refresh
    # provided as an abstract function to be overloaded by inheriting class
    @abc.abstractmethod
    def updateScreen(self):
        """main drawing function to be called prior to screen refresh"""

    # main updating function to be called prior to diagnostic info refresh
    # provided as an abstract function to be overloaded by inheriting class
    def updateDiagnostic(self):
        """main drawing function to be called prior to diagnostic info refresh"""

    # function is called when trial ends, can be used to save data
    # provided as an abstract function to be overloaded by inheriting class
    @abc.abstractmethod
    def onTrialEnd(self):
        """main function to be called when trial ends"""