def generate_all_output( experiments: List[Experiment], config: Configuration = Configuration()) -> str: """ Generates a string in csv format of all measures specified in config file for a list of experiments. Args: experiments (List[Experiment]): List of experiments. config (Configuration): Configuration. """ region_measures = config.measures.region trial_measures = config.measures.trial columns = config.output.columns output = ",".join([value.header for value in columns.values()]) + "\n" for experiment in experiments: for trial in experiment.trials.values(): for (measure, value) in trial_measures.items(): output += measure_output(measure, value.cutoff, columns, experiment, trial, None) for region in trial.item.regions: for (measure, value) in region_measures.items(): output += measure_output(measure, value.cutoff, columns, experiment, trial, region) return output
def generate_all_output_wide_format( experiments: List[Experiment], config: Configuration = Configuration()) -> str: """ Generates a string in csv format of all measures specified in config file for a list of experiments, with all measures as columns. Args: experiments (List[Experiment]): List of experiments. config (Configuration): Configuration. """ columns: Dict[str, OutputColumnConfig] = { **config.output.columns, **config.measures.all, } output = ",".join([value.header for value in columns.values()]) + "\n" for experiment in experiments: for trial in experiment.trials.values(): for region in trial.item.regions: output += ( ",".join( map( lambda col: write_column( col[0], experiment, trial, region, col[0], col[1].cutoff, # pylint: disable=no-member ), columns.items(), )) + "\n") return output
def calculate_all_measures( experiments: List[Experiment], output_file: str = None, config: Configuration = Configuration(), ): """ Given an array of experiments and config file, calculate all measures specified in the config file for the experiment, and optionally output the results as a csv. Args: experiments (List[Experiment]): List of experiments to calculate measures for. output_file (str): Name of output file. if `None`, no file is produced. config (Configuration): SideEye configuration. """ wide_format = config.wide_format for measure in config.measures.names: calculate_measure(experiments, measure, config.terminal_output) output_text = (generate_all_output_wide_format(experiments, config) if wide_format else generate_all_output(experiments, config)) if output_file is not None: with open(output_file, "w") as output: output.write(output_text) return output_text
def get_new_fixations( new_fixation: Fixation, fixations: List[Fixation], config: ASCParsingConfig = Configuration().asc_parsing, ) -> List[Fixation]: """Append a new fixation or merge with the previous fixation.""" if fixations and new_fixation.duration() < config.fixation_min_cutoff: old_fix = fixations[-1] return fixations[:-1] + [ Fixation( Point(old_fix.char, old_fix.line), old_fix.start, new_fixation.end, old_fix.index, old_fix.region, ) ] if fixations and fixations[-1].duration() < config.fixation_min_cutoff: old_fix = fixations[-1] return fixations[:-1] + [ Fixation( Point(new_fixation.char, new_fixation.line), old_fix.start, new_fixation.end, old_fix.index, new_fixation.region, ) ] return fixations[:] + [new_fixation]
def parse_files( experiment_files: List[str], region_file: str, config: Configuration = Configuration(), ) -> List[Experiment]: """ Given a list of DA1 or ASC files, a region file, and config file, parse all files in the list into Experiments. If config is not provided, default config will be used. Args: experiment_files: List of DA1 or ASC files. region_file: Name of region file (.cnt, .reg, or .txt). config (Config): Configuration. """ verbose = config.terminal_output if region_file[-4:].lower() == ".txt": items = region.textfile(region_file, verbose=verbose) else: items = region.file(region_file, config, verbose=verbose) experiments: List[Experiment] = [] for experiment_file in experiment_files: if experiment_file[-4:].lower() == ".da1": experiments += [da1.parse(experiment_file, items, config)] elif experiment_file[-4:].lower() == ".asc": experiments += [ asc.parse(experiment_file, items, config.asc_parsing) ] else: print("Skipping %s: not a DA1 or ASC file." % experiment_file) return experiments
def parse( experiment_file: str, region_file: str, config: Configuration = Configuration()) -> Experiment: """ Given a DA1 file and region file, and config file, parse an Experiment. If config is not provided, default config will be used. Args: experiment_file (str): Name of DA1 or ASC file. region_file: Name of region file (.cnt, .reg, or .txt). config (Configuration): Configuration. """ verbose = config.terminal_output if region_file[-4:].lower() == ".txt": items = region.textfile(region_file, verbose=verbose) else: items = region.file(region_file, config, verbose=verbose) if experiment_file[-4:].lower() == ".da1": experiment = da1.parse(experiment_file, items, config) if experiment_file[-4:].lower() == ".asc": experiment = asc.parse(experiment_file, items, config.asc_parsing) return experiment
def file( filename: str, config: Configuration = Configuration(), verbose: int = 0 ) -> Dict[ItemNum, Dict[Condition, Item]]: """ Parses a .reg or .cnt file into a dictionary of sideeye Item objects. Args: filename (str): Region filename. config (Configuration): Configuration. """ if verbose > 0: print("\nParsing region file: %s" % filename) number_location = config.region_fields.number condition_location = config.region_fields.condition boundaries_start = config.region_fields.boundaries_start includes_y = config.region_fields.includes_y validate_region_file(filename) def line_to_regions(line): """Helper function to convert a list of region boundaries into an item.""" regions = [] if includes_y: for boundary in range(0, len(line) - 3, 2): x_start = line[boundary] y_start = line[boundary + 1] x_end = line[boundary + 2] y_end = line[boundary + 3] regions += [Region(Point(x_start, y_start), Point(x_end, y_end))] else: for boundary in range(0, len(line) - 1): x_start = line[boundary] x_end = line[boundary + 1] regions += [Region(Point(x_start, 0), Point(x_end, 0))] return regions with open(filename, "r") as region_file: items: DefaultDict[ItemNum, Dict[Condition, Item]] = defaultdict(dict) for region_line in region_file: split_line = region_line.split() condition = split_line[condition_location] number = split_line[number_location] line = [int(x) for x in split_line] if verbose == 2 or verbose >= 5: print("\tParsing item: %s, condition: %s" % (number, condition)) items[number][condition] = Item( number, condition, line_to_regions(line[boundaries_start:]) ) return items
def generate_trial_output( experiments: List[Experiment], config: Configuration = Configuration()) -> str: """ Generates a string in csv format of list of experiments' trial measures using columns and measures specified in config file. Args: experiments (List[Experiment]): List of experiments. config (Configuration): Configuration. """ measures = config.measures.trial columns = config.output.trial output = ",".join([value.header for value in columns.values()]) + "\n" for experiment in experiments: for trial in experiment.trials.values(): for (measure, value) in measures.items(): measure_output(measure, value.cutoff, columns, experiment, trial, None) return output
def parse( asc_file: str, items: Dict[Condition, Dict[ItemNum, Item]], config: ASCParsingConfig = Configuration().asc_parsing, ): """ Parses a .ASC file into an Experiment object. Args: asc_file (string): Path to .ASC file. items (Dict[str, Dict[str, Item]]): List of items in experiments. config (ASCParsingConfig): Configuration for .ASC parsing. """ with open(asc_file) as file: trials = get_trials(file.read(), items, config) return Experiment( "".join(os.path.split(asc_file)[1].split(".")[:-1]), trials, asc_file, datetime.fromtimestamp(os.path.getmtime(asc_file)), )
def parse( filename: str, items: Dict[ItemNum, Dict[Condition, Item]], config: Configuration = Configuration(), da1_type: str = None, ) -> Experiment: """ Parses DA1-like files into sideeye Experiment objects, given column positions. Args: filename (str): DA1 file. items (Dict[ItemNum, Dict[Condition, Item]]): List of items in the experiment. da1_type (str): Type of DA1 file - `timdrop`, `robodoc`, or `None` for any other type. """ if config.terminal_output > 0: print("\nParsing DA1 file: %s" % filename) validate(filename, config.da1_fields.fixation_start, da1_type) def parse_fixations(line, item): """Parses a list of (x, y, start time, end time) numbers into a list of Fixations.""" fixations = [] for pos in range(0, len(line), 4): x_pos = line[pos] y_pos = line[pos + 1] start = line[pos + 2] end = line[pos + 3] if (end - start) > config.cutoffs.min and ( config.cutoffs.max < 0 or (end - start) < config.cutoffs.max): fixations += [ Fixation( Point(x_pos, y_pos), start, end, len(fixations), item.find_region(x_pos, y_pos), ) ] else: fixations += [ Fixation( Point(x_pos, y_pos), start, end, len(fixations), item.find_region(x_pos, y_pos), excluded=True, ) ] return fixations with open(filename) as da1_file: trials: List[Trial] = [] for da1_line in da1_file: split_line = da1_line.split() number = split_line[config.da1_fields.number] condition = split_line[config.da1_fields.condition] line: List[int] = [int(x) for x in split_line] if config.terminal_output == 2 or config.terminal_output >= 5: print("\tParsing trial: %s" % line[config.da1_fields.index]) if items[number][condition]: fixations = parse_fixations( line[config.da1_fields.fixation_start:], items[number][condition]) trials += [ Trial( line[config.da1_fields.index], line[config.da1_fields.time], items[number][condition], fixations, config.cutoffs.include_fixation, config.cutoffs.include_saccades, ) ] else: print( "Item number", number, ", condition", condition, "does not exist. It was not added to the Experiment object.", ) return Experiment("".join(os.path.split(filename)[1].split(".")[:-1]), trials, filename)
def get_trials( asc: str, items: Dict[Condition, Dict[ItemNum, Item]], config: ASCParsingConfig = Configuration().asc_parsing, ) -> List[Trial]: """ Parses .ASC text into a list of Trial objects. Args: asc (string): Text of .ASC file. items (Dict[str, Dict[str, Item]]): List of items in experiments. config (ASCParsingConfig): Configuration for .ASC parsing. """ characters: List[CharPosition] = [] fixations: List[Fixation] = [] fixation_start_time = 0 trials: List[Trial] = [] exclude = False blinks = 0 start_time = 0 condition: Optional[str] = None item: Optional[str] = None for line in asc.split("\n"): if line.split() and line.split()[0] in LINE_TYPES: start_time = get_start(line) or start_time condition = get_condition(line) or condition item = get_item(line) or item char = get_char(line) characters = characters + [char] if char else characters new_fixation, fixation_start_time = ( get_fixation( line, characters, items[item][condition], len(fixations), fixation_start_time, ) if start_time and item and condition and item in items and condition in items[item] else (None, fixation_start_time) ) fixations = ( get_new_fixations(new_fixation, fixations, config) if new_fixation else fixations ) if ( config.max_saccade_dur and len(fixations) > 1 and fixations[-1].start - fixations[-2].end > config.max_saccade_dur ): exclude = True blink_dur = get_blink_dur(line) if blink_dur: blinks += 1 if config.blink_max_dur and blink_dur > config.blink_max_dur: exclude = True if config.blink_max_count and blinks > config.blink_max_count: exclude = True end_time = get_end(line) if end_time: if ( item and condition and item in items and condition in items[item] and not exclude ): trials += [ Trial( len(trials), end_time - start_time, items[item][condition], fixations, ) ] start_time = 0 fixations = [] fixation_start_time = 0 characters = [] exclude = False blinks = 0 item = None condition = None return trials