Esempio n. 1
0
File: cli.py Progetto: a-hurst/klibs
def rebuild_db(path):
    from klibs import P
    from klibs.KLDatabase import DatabaseManager

    # Sanitize and switch to path, exiting with error if not a KLibs project directory
    project_name = initialize_path(path)

    # set initial param values for project's context
    P.initialize_paths(project_name)

    # import params defined in project's local params file in ExpAssets/Config
    for k, v in load_source(P.params_file_path).items():
        setattr(P, k, v)

    # Validate database path and rebuild
    P.database_path = validate_database_path(P.database_path)
    try:
        DatabaseManager().rebuild()
        cso("Database successfully rebuilt! Please make sure to update experiment.py\n"
            "to reflect any changes you might have made to tables or column names."
            )
    except Exception as e:
        exc_txt = traceback.format_exc().split("\n")[-2]
        schema_filename = os.path.basename(P.schema_file_path)
        err = (
            "<red>Syntax error encountered in database schema ('{0}'):</red>\n\n"
            "  {1}\n\n"
            "<red>Please double-check the file's formatting and try again.</red>"
        )
        cso(err.format(schema_filename, exc_txt))
Esempio n. 2
0
File: cli.py Progetto: a-hurst/klibs
def hard_reset(path):
    import shutil

    # Sanitize and switch to path, exiting with error if not a KLibs project directory
    project_name = initialize_path(path)

    # Initialize file paths for the project & list ones to remove/rebuild
    P.initialize_paths(project_name)
    reset_files = [P.database_path, P.database_backup_path]
    reset_dirs = [
        P.incomplete_data_dir, P.incomplete_edf_dir, P.logs_dir, P.versions_dir
    ]

    reset_prompt = cso(
        "<red>Warning: doing a hard reset will delete all collected data, "
        "all logs, all copies of experiment.py and Config files in the .versions folder "
        "that previous participants were run with, and reset the project's database. "
        "Are you sure you want to continue?</red> (Y/N): ", False)
    if getinput(reset_prompt).lower() != "y":
        return

    # Remove and replace folders to reset
    for d in reset_dirs:
        if os.path.isdir(d):
            shutil.rmtree(d)
    ensure_directory_structure(path, create_missing=True)

    # Remove (but don't replace) files to reset
    for f in reset_files:
        if os.path.isfile(f):
            os.remove(f)

    cso("\nProject reset successfully.")
Esempio n. 3
0
File: cli.py Progetto: a-hurst/klibs
def validate_database_path(db_path, prompt=False):

    if prompt == False and not os.path.isfile(db_path):
        err("unable to locate project database at '{0}'.\nIt may have been renamed, "
            "or may not exist yet.".format(db_path))

    while not os.path.isfile(db_path):
        cso("<green_d>No database file was present at '{0}'.</green_d>".format(
            db_path))
        db_prompt = cso(
            "<green_d>You can "
            "<purple>(c)</purple>reate it, "
            "<purple>(s)</purple>upply a different path or "
            "<purple>(q)</purple>uit: </green_d>",
            print_string=False)
        response = getinput(db_prompt).lower()
        print("")

        while response not in ['c', 's', 'q']:
            err_prompt = cso(
                "<red>Please respond with one of 'c', 's', or 'q': </red>",
                False)
            response = getinput(err_prompt).lower()

        if response == "c":
            open(db_path, "a").close()
        elif response == "s":
            db_path = getinput(
                cso("<green_d>Great, where might it be?: </green_d>", False))
            db_path = os.path.normpath(db_path)
        elif response == "q":
            sys.exit()

    return db_path
Esempio n. 4
0
    def quit(self):
        """Safely exits the program, ensuring data has been saved and any connected EyeLink unit's
		recording is stopped. This, not Python's sys.exit(), should be used to exit an experiment.

		"""
        import sdl2
        if P.verbose_mode:
            print_tb(print_stack(), 6)

        err = ''
        try:
            self.database.commit()
            self.database.close()
        except Exception:
            err += "<red>Error encountered closing database connection:</red>\n\n"
            err += full_trace() + "\n\n"
            err += "<red>Some data may not have been saved.</red>\n\n\n"

        if P.eye_tracking and P.eye_tracker_available:
            try:
                self.el.shut_down(incomplete=self.incomplete)
            except Exception:
                err += "<red>Eye tracker encountered error during shutdown:</red>\n\n"
                err += full_trace() + "\n\n"
                err += "<red>You may need to manually stop the tracker from recording.</red>\n\n\n"

        if P.multi_user and P.version_dir:
            newpath = P.version_dir.replace(str(P.random_seed),
                                            str(P.participant_id))
            os.rename(P.version_dir, newpath)

        self.audio.shut_down()
        sdl2.ext.quit()

        if err:
            cso("\n\n" + err +
                "<red>*** Errors encountered during shutdown. ***</red>\n\n")
            os._exit(1)
        cso("\n\n<green>*** '{0}' successfully shut down. ***</green>\n\n".
            format(P.project_name))
        os._exit(1)
Esempio n. 5
0
File: cli.py Progetto: a-hurst/klibs
def export(path, table=None, combined=False, join=None):
    from klibs import P
    from klibs.KLDatabase import DatabaseManager

    # Sanitize and switch to path, exiting with error if not a KLibs project directory
    project_name = initialize_path(path)
    cso("\n<green>*** Exporting data from {0} ***</green>\n".format(
        project_name))

    # set initial param values for project's context
    P.initialize_paths(project_name)

    # ensure that 'Data' and 'Data/incomplete' directories exist, creating if missing
    if not os.path.isdir(P.incomplete_data_dir):
        os.makedirs(P.incomplete_data_dir)

    # import params defined in project's local params file in ExpAssets/Config
    for k, v in load_source(P.params_file_path).items():
        setattr(P, k, v)
    multi_file = combined != True

    # Validate database path and export
    P.database_path = validate_database_path(P.database_path)
    DatabaseManager().export(table, multi_file, join)
Esempio n. 6
0
File: cli.py Progetto: a-hurst/klibs
def update(branch=None):
    import subprocess as sub

    git_repo = 'a-hurst/klibs'
    if branch:
        url = 'https://github.com/{0}.git@{1}'.format(git_repo, branch)
        cmd = ['pip', 'install', '-U', 'git+{0}#egg=klibs'.format(url)]
        msg1 = "most recent commit\nfrom the <purple>{0}</purple> branch of"
        msg1 = msg1.format(branch)
        msg2 = "commit from '{0}'".format(branch)
    else:
        url = "https://github.com/{0}/releases/latest/download/klibs.tar.gz"
        cmd = ['pip', 'install', url.format(git_repo)]
        msg1 = "latest release\nfrom"
        msg2 = "release from '{0}'".format(git_repo)

    update_prompt = cso(
        "<green_d>Updating will replace the current install of KLibs with the "
        "{0} <purple>'{1}'</purple></green_d>.\n\n"
        "Are you sure you want to continue? (Y/N): ".format(msg1, git_repo),
        False)
    if getinput(update_prompt).lower() != "y":
        return

    # Set environment variable to avoid pip update warnings & run update command
    cso("\n<green_d>Updating to the latest {0}...</green_d>\n".format(msg2))
    env = os.environ.copy()
    env['PIP_DISABLE_PIP_VERSION_CHECK'] = '1'
    p = sub.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr, env=env)
    p.communicate()

    # Indicate whether update succeeded
    if p.returncode != 0:
        cso("\n<red>Errors encountered during KLibs update.</red>")
    else:
        cso("\n<green_d>Update completed successfully!</green_d>")
Esempio n. 7
0
File: cli.py Progetto: a-hurst/klibs
def run(screen_size, path, condition, devmode, no_tracker, seed):

    # Ensure the specified screen size is valid
    if screen_size <= 0:
        err("Invalid screen size '{0}'. Size must be the diagonal size of the screen\n"
            "in inches (e.g. 'klibs run 24' for a 24-inch screen).".format(
                screen_size))

    cso("\n<green>*** Now Loading KLibs Environment ***</green>\n")

    # Suppresses possible pysdl2-dll warning message on import
    import warnings
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        import sdl2

    from klibs import P
    from klibs import env
    from klibs.KLGraphics.core import display_init
    from klibs.KLDatabase import DatabaseManager
    from klibs.KLEventInterface import EventManager
    from klibs.KLText import TextManager
    from klibs.KLCommunication import init_messaging, collect_demographics, init_default_textstyles

    # Sanitize and switch to path, exiting with error if not a KLibs project directory
    project_name = initialize_path(path)

    # create any missing project directories
    missing_dirs = ensure_directory_structure(path)
    if len(missing_dirs):
        cso("<red>Some expected or required directories for this project appear to be missing.</red>"
            )
        while True:
            query = cso(
                "<green_d>You can "
                "<purple>(c)</purple>reate them automatically, view a "
                "<purple>(r)</purple>eport on the missing directories,\nor "
                "<purple>(q)</purple>uit klibs: </green_d>", False)
            action = getinput(query).lower()
            action = action[0] if len(
                action) > 1 else action  # only check first letter of input
            if action == "r":
                cso("\n<green_d>The following required directories were not found:</green_d>"
                    )
                for md in missing_dirs:
                    cso("<purple> - {0}</purple>".format(md))
            elif action == "c":
                ensure_directory_structure(path, create_missing=True)
                break
            elif action == "q":
                return
            else:
                cso("\n<red>Please enter a valid response.</red>")
            print("")

    # set initial param values for project's context
    P.initialize_runtime(project_name, seed)
    P.database_path = validate_database_path(P.database_path, prompt=True)

    # Add ExpAssets/Resources/code to pythonpath for easy importing
    sys.path.append(P.code_dir)

    # If a condition was specified, set it in Params
    P.condition = condition

    # import params defined in project's local params file in ExpAssets/Config
    try:
        for k, v in load_source(P.params_file_path).items():
            setattr(P, k, v)
    except IOError:
        err("Unable to locate the experiment's '_params.py' file. Please ensure that this "
            "file exists, and that the name of the experiment folder matches the name of the "
            "Experiment class defined in 'experiment.py'.")

    # if a local params file exists, do the same:
    if os.path.exists(
            P.params_local_file_path) and not P.dm_ignore_local_overrides:
        for k, v in load_source(P.params_local_file_path).items():
            setattr(P, k, v)

    # If a condition has been specified, make sure it's a valid condition as per params.py
    if P.condition == None:
        P.condition = P.default_condition
    if P.condition != None:
        if len(P.conditions) == 0:
            err("No between-participant conditions have been defined for this experiment. "
                "You can define valid condition names in your experiment's params.py file."
                )
        elif P.condition not in P.conditions:
            cond_list = "', '".join(P.conditions)
            err("'{0}' is not a valid condition for this experiment (must be one of '{1}'). "
                "Please relaunch the experiment.".format(
                    P.condition, cond_list))

    # set some basic global Params
    if devmode:
        P.development_mode = True
        P.collect_demographics = False
    if not P.labjack_available:
        P.labjacking = False
    #TODO: check if current commit matches experiment.py and warn user if not

    # create runtime environment
    env.txtm = TextManager()
    if P.eye_tracking:
        if no_tracker is True:
            P.eye_tracker_available = False
        if P.development_mode and P.dm_show_gaze_dot:
            P.show_gaze_dot = True
        from klibs import KLEyeTracking  # needs to be imported after params are read in
        try:
            env.el = KLEyeTracking.Tracker()
        except RuntimeError:
            return
    env.db = DatabaseManager()
    env.evm = EventManager()

    try:
        # create basic text styles, load in user queries, and initialize slack (if enabled)
        init_messaging()

        # finally, import the project's Experiment class and instantiate
        experiment = load_source("experiment.py")[project_name]
        env.exp = experiment()

        # create a display context if everything's gone well so far
        env.exp.window = display_init(screen_size)
        env.exp.show_logo()

        # once display size and pixels-per-degree known, initialize default text styles
        init_default_textstyles()

        # create an anonymous user if not collecting demographic information
        if not P.manual_demographics_collection:
            collect_demographics(not P.collect_demographics
                                 or P.development_mode)

        # off to the races team...
        env.exp.run()

    except Exception as e:
        print("".join(["\n"] +
                      traceback.format_exception_only(sys.exc_info()[0],
                                                      sys.exc_info()[1]) +
                      traceback.format_tb(sys.exc_info()[2])))
Esempio n. 8
0
File: cli.py Progetto: a-hurst/klibs
def err(err_string):
    cso("<red>Error: " + err_string + "</red>\n")
    sys.exit()
Esempio n. 9
0
File: cli.py Progetto: a-hurst/klibs
def create(name, path):
    import shutil
    from random import choice
    from os.path import join
    from tempfile import mkdtemp
    from pkg_resources import resource_filename

    template_files = [("schema.sql", ["ExpAssets", "Config"]),
                      ("independent_variables.py", ["ExpAssets", "Config"]),
                      ("params.py", ["ExpAssets", "Config"]),
                      ("user_queries.json", ["ExpAssets", "Config"]),
                      ("experiment.py", []), (".gitignore", [])]

    # TODO: Prompt user if project involves eye tracking & configure params accordingly

    # Validate name (must be valid Python identifier)
    valid_name = re.match(re.compile(r"^[A-Za-z_]+([A-Za-z0-9_]+)?$"),
                          name) != None
    if not valid_name:
        err("'{0}' is not a valid project name. Project names must not contain any spaces or "
            "special characters apart from '_', and cannot start with a number."
            .format(name))

    # Ensure destination folder 1) exists, 2) is a folder, and 3) is writeable by the current user.
    if not os.path.exists(path):
        err("The path '{0}' does not exist.\n\nPlease enter a path to a valid writeable folder and "
            "try again.".format(path))
    elif not os.path.isdir(path):
        err("The path '{0}' does not point to a valid folder.\n\nPlease enter a different path and "
            "try again.".format(path))
    elif not os.access(path, os.W_OK | os.X_OK):
        err("You do not have the permissions required to write to the folder '{0}'.\n\nPlease "
            "enter a path to a folder you have write access to and try again.".
            format(path))

    # Initialize project path and make sure it doesn't already exist
    project_path = join(path, name)
    if os.path.exists(project_path):
        err("Folder named '{0}' already exists in the directory '{1}'.\n\nPlease remove it or give "
            "your project a different name and try again.".format(name, path))

    # Get author name for adding to project files
    author = getinput(
        cso("<green_d>Please provide your first and last name: </green_d>",
            False))
    if len(author.split()) < 2:
        one_name_peeps = ["Madonna", "Prince", "Cher", "Bono", "Sting"]
        cso("<red>\nOk {0}, take it easy.</red> "
            "<green_d>First <cyan>*and*</cyan> last name. "
            "Let's try that again--you got this champ...</green_d>".format(
                choice(one_name_peeps)))
        return create(name, path)

    # Verify author name and project path before creating it
    cso("\n<green_d>*** <purple>Confirm Project Details</purple> ***</green_d>"
        )
    cso("<cyan>Project Author:</cyan> <blue>{0} {1}</blue>".format(
        *author.split()))
    cso("<cyan>Project Path:</cyan> <blue>{0}</blue>\n".format(project_path))
    verified = False
    while not verified:
        query = cso(
            "<green_d>Is this correct? Answer with "
            "<purple>Y</purple>es, "
            "<purple>N</purple>o, or "
            "<purple>Q</purple>uit: </green_d>", False)
        response = getinput(query).lower()
        response = response[0] if len(response) > 1 else response
        if response == "y":
            verified = True
        elif response == "n":
            return create(name, path)
        elif response == "q":
            cso("\n<green_d>Fine. Be that way. But I think we both know you'll be back.</green_d>"
                )
            return
        else:
            cso("\n<green_d>Pardon? I didn't catch that.</green_d>\n")

    # Create temporary folder and assemble project template inside it
    tmp_path = mkdtemp(prefix='klibs_')
    tmp_dir = os.path.split(tmp_path)[1]
    ensure_directory_structure(tmp_path, create_missing=True)
    cso("  <cyan>...Project template folders successfully created.</cyan>")

    source_path = resource_filename('klibs', 'resources/template')
    for tf in template_files:  # replace generic file names with project-specific names
        filename = tf[0] if tf[0] in [".gitignore", "experiment.py"
                                      ] else "{0}_{1}".format(name, tf[0])
        template_f_path = join(
            source_path, tf[0] if tf[0] != ".gitignore" else "gitignore.txt")
        project_f_path = filename if len(tf[1]) == 0 else join(
            join(*tf[1]), filename)
        with open(template_f_path,
                  "rt") as temp, open(join(tmp_path, project_f_path),
                                      "w+") as out:
            contents = temp.read()
            contents = contents.replace('PROJECT_NAME', name)
            contents = contents.replace('EXPERIMENTER_NAME', author)
            out.write(contents)
        cso("  <cyan>...'{0}' successfully created.</cyan>".format(
            project_f_path))

    # Once successfully initialized, copy template to target directory
    shutil.move(tmp_path, path)
    os.rename(join(path, tmp_dir), join(path, name))
    cso("<green_d>\nProject successfully created at:</green_d> '<blue>{0}</blue>'"
        .format(project_path))
Esempio n. 10
0
	def drift_correct(self, location=None, target=None, fill_color=None, draw_target=True):
		"""Checks the accuracy of the EyeLink's calibration by presenting a fixation stimulus
		and requiring the participant to press the space bar while looking directly at it. If
		there is a large difference between the gaze location at the time the key was pressed
		and the true location of the fixation, it indicates that there has been drift in the
		calibration.

		On older EyeLink models (EyeLink I & II), the recorded drift is used to adjust the
		calibration for improved accuracy on future trials. On recent models (EyeLink 1000 and
		up), drift corrections will *check* for drift and prompt the participant to try again
		if the drift is large, but they do not affect the tracker's calibration.

		Args:
			location (Tuple(int, int), optional): The (x,y) pixel coordinates where the drift
				correct target should be located. Defaults to the center of the screen.
			target: A :obj:`Drawbject` or other :func:`KLGraphics.blit`-able shape to use as
				the drift correct target. Defaults to a circular :func:`drift_correct_target`.
			fill_color: A :obj:`List` or :obj:`Tuple` containing an RGBA colour to use for the
				background for the drift correct screen. Defaults to the value of
				``P.default_fill_color``.
			draw_target (bool, optional): A flag indicating whether the function should draw
				the drift correct target itself (True), or whether it should leave it to the
				programmer to draw the target before :meth:`drift_correct` is called (False). 
				Defaults to True.

		Raises:
			TrialException: If repeated EyeLink errors are encountered while attempting to
				perform the drift correct.

		"""
		hide_mouse_cursor()

		target = drift_correct_target() if target is None else target
		draw_target = EL_TRUE if draw_target in [EL_TRUE, True] else EL_FALSE
		location = P.screen_c if location is None else location
		if not valid_coords(location):
			raise ValueError("'location' must be a pair of (x,y) pixel coordinates.")

		try:
			while True:
				if draw_target == EL_TRUE:
					fill(P.default_fill_color if not fill_color else fill_color)
					blit(target, 5, location)
					flip()
				ret = self.doDriftCorrect(location[0], location[1], draw_target, EL_TRUE)
				if ret != 27: # 27 means we hit Esc to enter calibration, so redo drift correct
					break
			if draw_target == EL_TRUE:
				fill(P.default_fill_color if not fill_color else fill_color)
				flip()
			return self.applyDriftCorrect()
		except RuntimeError:
			try:
				self.setOfflineMode()
			except RuntimeError:
				self._unresolved_exceptions += 1
				if self._unresolved_exceptions > 5:
					cso("\n<red>*** Fatal Error: Unresolvable EyeLink Error ***</red>")
					print(full_trace())
					self._unresolved_exceptions = 0
					raise TrialException("EyeLink not ready.")
			return self.drift_correct(location, target, fill_color, draw_target)