def test_error1(self): try: 1 / 0 except: r = utilities.error_info(sys.exc_info()) assert str(r[0]) == 'division by zero' assert r[1] == "test_utilities.py"
def import_subjects_from_clipboard(self): """ import subjects from clipboard """ try: cb = QApplication.clipboard() cb_text = cb.text() if not cb_text: QMessageBox.warning(None, programName, "The clipboard is empty", QMessageBox.Ok | QMessageBox.Default, QMessageBox.NoButton) return if self.twSubjects.rowCount(): response = dialog.MessageDialog( programName, "Some subjects are already configured. Do you want to append subjects or replace them?", ["Append", "Replace", CANCEL]) if response == CANCEL: return if response == "Replace": self.twSubjects.setRowCount(0) cb_text_splitted = cb_text.split("\n") if len(set([len(x.split("\t")) for x in cb_text_splitted])) != 1: QMessageBox.warning(None, programName, ( "The clipboard content does not have a constant number of fields.<br>" "From your spreadsheet: CTRL + A (select all cells), CTRL + C (copy to clipboard)" ), QMessageBox.Ok | QMessageBox.Default, QMessageBox.NoButton) return for row in cb_text_splitted: if set(row.split("\t")) != set([""]): subject = {} for idx, field in enumerate(row.split("\t")): if idx == 0: subject["key"] = field.strip() if len( field.strip()) == 1 else "" if idx == 1: subject[SUBJECT_NAME] = field.strip() if idx == 2: subject["description"] = field.strip() self.twSubjects.setRowCount(self.twSubjects.rowCount() + 1) for idx, field_name in enumerate(subjectsFields): item = QTableWidgetItem(subject.get(field_name, "")) self.twSubjects.setItem(self.twSubjects.rowCount() - 1, idx, item) except Exception: error_type, error_file_name, error_lineno = utilities.error_info( sys.exc_info()) logging.critical( f"Error in function '{sys._getframe().f_code.co_name}': {error_type} {error_file_name} {error_lineno}" ) dialog.error_message_box(sys._getframe().f_code.co_name, error_type, error_file_name, error_lineno)
def error_message(task: str, exc_info: tuple) -> None: """ Show details about the error in a message box write entry to log as CRITICAL """ error_type, error_file_name, error_lineno = utilities.error_info(exc_info) logging.critical(f"Error during {task}: {error_type} in {error_file_name} at line #{error_lineno}") error_message_box(task, error_type, error_file_name, error_lineno)
def error_message(task: str, exc_info: tuple) -> None: """ show details about the error """ error_type, error_file_name, error_lineno = utilities.error_info(exc_info) QMessageBox.critical(None, programName, ( f"An error occured during {task}.<br>" f"BORIS version: {version.__version__}<br>" f"Error: {error_type}<br>" f"in {error_file_name} " f"at line # {error_lineno}<br><br>" "Please report this problem to improve the software at:<br>" '<a href="https://github.com/olivierfriard/BORIS/issues">https://github.com/olivierfriard/BORIS/issues</a>' ))
def observation_to_behavioral_sequences(pj, selected_observations, parameters, behaviors_separator, separated_subjects, timed, file_name): try: with open(file_name, "w", encoding="utf-8") as out_file: for obs_id in selected_observations: # observation id out_file.write("\n" + f"# observation id: {obs_id}" + "\n") # observation description descr = pj[OBSERVATIONS][obs_id]["description"] if "\r\n" in descr: descr = descr.replace("\r\n", "\n# ") elif "\r" in descr: descr = descr.replace("\r", "\n# ") out_file.write(f"# observation description: {descr}\n\n") # media file name if pj[OBSERVATIONS][obs_id][TYPE] in [MEDIA]: out_file.write(f"# Media file name: {', '.join([os.path.basename(x) for x in pj[OBSERVATIONS][obs_id][FILE][PLAYER1]])}\n\n") if pj[OBSERVATIONS][obs_id][TYPE] in [LIVE]: out_file.write(f"# Live observation{os.linesep}{os.linesep}") # independent variables if INDEPENDENT_VARIABLES in pj[OBSERVATIONS][obs_id]: out_file.write("# Independent variables\n") for variable in pj[OBSERVATIONS][obs_id][INDEPENDENT_VARIABLES]: out_file.write(f"# {variable}: {pj[OBSERVATIONS][obs_id][INDEPENDENT_VARIABLES][variable]}\n") out_file.write("\n") # one sequence for all subjects if not separated_subjects: out = events_to_behavioral_sequences_all_subj(pj, obs_id, parameters[SELECTED_SUBJECTS], parameters, behaviors_separator) if out: out_file.write(out + "\n") # one sequence by subject if separated_subjects: # selected subjects for subject in parameters[SELECTED_SUBJECTS]: out_file.write(f"\n# {subject if subject else NO_FOCAL_SUBJECT}:\n") if not timed: out = events_to_behavioral_sequences(pj, obs_id, subject, parameters, behaviors_separator) if timed: out = events_to_timed_behavioral_sequences(pj, obs_id, subject, parameters, 0.001, behaviors_separator) if out: out_file.write(out + "\n") return True, "" except Exception: raise error_type, error_file_name, error_lineno = utilities.error_info(sys.exc_info()) return False, f"{error_type} {error_file_name} {error_lineno}"
def create_behaviors_bar_plot(pj: dict, selected_observations: list, param: dict, plot_directory: str, output_format: str, plot_colors:list=BEHAVIORS_PLOT_COLORS): """ time budget bar plot Args: pj (dict): project param (dict): parameters plot_directory (str): path of directory output_format (str): image format Returns: dict: """ selected_subjects = param[SELECTED_SUBJECTS] selected_behaviors = param[SELECTED_BEHAVIORS] time_interval = param["time"] start_time = param[START_TIME] end_time = param[END_TIME] parameters = ["duration", "number of occurences"] ok, msg, db_connector = db_functions.load_aggregated_events_in_db(pj, selected_subjects, selected_observations, selected_behaviors) if not ok: return {"error": True, "message": msg} try: # extract all behaviors from ethogram for colors in plot all_behaviors = [pj[ETHOGRAM][x][BEHAVIOR_CODE] for x in utilities.sorted_keys(pj[ETHOGRAM])] for obs_id in selected_observations: cursor = db_connector.cursor() # distinct behaviors cursor.execute("SELECT distinct behavior FROM aggregated_events WHERE observation = ?", (obs_id,)) distinct_behav = [rows["behavior"] for rows in cursor.fetchall()] # add selected behaviors that are not observed ''' if not param[EXCLUDE_BEHAVIORS]: for behavior in selected_behaviors: if [x for x in distinct_behav if x == behavior] == []: distinct_behav.append(behavior) ''' # distinct subjects cursor.execute("SELECT distinct subject FROM aggregated_events WHERE observation = ?", (obs_id,)) distinct_subjects = [rows["subject"] for rows in cursor.fetchall()] behaviors = init_behav(pj[ETHOGRAM], distinct_subjects, distinct_behav, parameters) # plot creation if len(distinct_subjects) > 1: fig, axs = plt.subplots(nrows=1, ncols=len(distinct_subjects), sharey=True) fig2, axs2 = plt.subplots(nrows=1, ncols=len(distinct_subjects), sharey=True) else: fig, ax = plt.subplots(nrows=1, ncols=len(distinct_subjects), sharey=True) axs = np.ndarray(shape=(1), dtype=type(ax)) axs[0] = ax fig2, ax2 = plt.subplots(nrows=1, ncols=len(distinct_subjects), sharey=True) axs2 = np.ndarray(shape=(1), dtype=type(ax2)) axs2[0] = ax2 fig.suptitle("Durations of behaviors") fig2.suptitle("Number of occurences of behaviors") # if modifiers not to be included set modifiers to "" cursor.execute("UPDATE aggregated_events SET modifiers = ''") # time obs_length = project_functions.observation_total_length(pj[OBSERVATIONS][obs_id]) if obs_length == -1: obs_length = 0 if param["time"] == TIME_FULL_OBS: min_time = float(0) max_time = float(obs_length) if param["time"] == TIME_EVENTS: try: min_time = float(pj[OBSERVATIONS][obs_id][EVENTS][0][0]) except Exception: min_time = float(0) try: max_time = float(pj[OBSERVATIONS][obs_id][EVENTS][-1][0]) except Exception: max_time = float(obs_length) if param["time"] == TIME_ARBITRARY_INTERVAL: min_time = float(start_time) max_time = float(end_time) cursor.execute("UPDATE aggregated_events SET start = ? WHERE observation = ? AND start < ? AND stop BETWEEN ? AND ?", (min_time, obs_id, min_time, min_time, max_time, )) cursor.execute("UPDATE aggregated_events SET stop = ? WHERE observation = ? AND stop > ? AND start BETWEEN ? AND ?", (max_time, obs_id, max_time, min_time, max_time, )) cursor.execute("UPDATE aggregated_events SET start = ?, stop = ? WHERE observation = ? AND start < ? AND stop > ?", (min_time, max_time, obs_id, min_time, max_time, )) for ax_idx, subject in enumerate(sorted(distinct_subjects)): for behavior in distinct_behav: # number of occurences cursor.execute(("SELECT COUNT(*) AS count FROM aggregated_events " "WHERE observation = ? AND subject = ? AND behavior = ?"), (obs_id, subject, behavior, )) for row in cursor.fetchall(): behaviors[subject][behavior]["number of occurences"] = 0 if row["count"] is None else row["count"] # total duration if STATE in project_functions.event_type(behavior, pj[ETHOGRAM]): cursor.execute(("SELECT SUM(stop - start) AS duration FROM aggregated_events " "WHERE observation = ? AND subject = ? AND behavior = ?"), (obs_id, subject, behavior, )) for row in cursor.fetchall(): behaviors[subject][behavior]["duration"] = 0 if row["duration"] is None else row["duration"] durations, n_occurences, colors, x_labels, colors_duration, x_labels_duration = [], [], [], [], [], [] for behavior in sorted(distinct_behav): if param[EXCLUDE_BEHAVIORS] and behaviors[subject][behavior]["number of occurences"] == 0: continue n_occurences.append(behaviors[subject][behavior]["number of occurences"]) x_labels.append(behavior) try: colors.append(utilities.behavior_color(plot_colors, all_behaviors.index(behavior))) except Exception: colors.append("darkgray") if STATE in project_functions.event_type(behavior, pj[ETHOGRAM]): durations.append(behaviors[subject][behavior]["duration"]) x_labels_duration.append(behavior) try: colors_duration.append(utilities.behavior_color(plot_colors, all_behaviors.index(behavior))) except Exception: colors_duration.append("darkgray") #width = 0.35 # the width of the bars: can also be len(x) sequence axs2[ax_idx].bar(np.arange(len(n_occurences)), n_occurences, #width, color=colors ) axs[ax_idx].bar(np.arange(len(durations)), durations, #width, color=colors_duration ) if ax_idx == 0: axs[ax_idx].set_ylabel("Duration (s)") axs[ax_idx].set_xlabel("Behaviors") axs[ax_idx].set_title(f"{subject}") axs[ax_idx].set_xticks(np.arange(len(durations))) axs[ax_idx].set_xticklabels(x_labels_duration, rotation='vertical', fontsize=8) if ax_idx == 0: axs2[ax_idx].set_ylabel("Number of occurences") axs2[ax_idx].set_xlabel("Behaviors") axs2[ax_idx].set_title(f"{subject}") axs2[ax_idx].set_xticks(np.arange(len(n_occurences))) axs2[ax_idx].set_xticklabels(x_labels, rotation='vertical', fontsize=8) fig.align_labels() fig.tight_layout(rect=[0, 0.03, 1, 0.95]) fig2.align_labels() fig2.tight_layout(rect=[0, 0.03, 1, 0.95]) if plot_directory: # output_file_name = f"{pathlib.Path(plot_directory) / utilities.safeFileName(obs_id)}.{output_format}" fig.savefig(f"{pathlib.Path(plot_directory) / utilities.safeFileName(obs_id)}.duration.{output_format}") fig2.savefig(f"{pathlib.Path(plot_directory) / utilities.safeFileName(obs_id)}.number_of_occurences.{output_format}") plt.close() else: fig.show() fig2.show() return {} except Exception: error_type, error_file_name, error_lineno = utilities.error_info(sys.exc_info()) logging.critical(f"Error in time budget bar plot: {error_type} {error_file_name} {error_lineno}") return {"error": True, "exception": sys.exc_info()}
def synthetic_time_budget(pj: dict, selected_observations: list, parameters_obs: dict): """ create a synthetic time budget Args: pj (dict): project dictionary selected_observations (list): list of observations to include in time budget parameters_obs (dict): Returns: bool: True if everything OK str: message tablib.Dataset: dataset containing synthetic time budget data """ try: selected_subjects = parameters_obs[SELECTED_SUBJECTS] selected_behaviors = parameters_obs[SELECTED_BEHAVIORS] include_modifiers = parameters_obs[INCLUDE_MODIFIERS] interval = parameters_obs["time"] start_time = parameters_obs["start time"] end_time = parameters_obs["end time"] parameters = [ ["duration", "Total duration"], ["number", "Number of occurrences"], ["duration mean", "Duration mean"], ["duration stdev", "Duration std dev"], ["proportion of time", "Proportion of time"], ] data_report = tablib.Dataset() data_report.title = "Synthetic time budget" ok, msg, db_connector = db_functions.load_aggregated_events_in_db( pj, selected_subjects, selected_observations, selected_behaviors) if not ok: return False, msg, None db_connector.create_aggregate("stdev", 1, StdevFunc) cursor = db_connector.cursor() # modifiers if include_modifiers: cursor.execute( "SELECT distinct behavior, modifiers FROM aggregated_events") distinct_behav_modif = [[rows["behavior"], rows["modifiers"]] for rows in cursor.fetchall()] else: cursor.execute("SELECT distinct behavior FROM aggregated_events") distinct_behav_modif = [[rows["behavior"], ""] for rows in cursor.fetchall()] # add selected behaviors that are not observed for behav in selected_behaviors: if [x for x in distinct_behav_modif if x[0] == behav] == []: distinct_behav_modif.append([behav, ""]) behaviors = init_behav_modif(pj[ETHOGRAM], selected_subjects, distinct_behav_modif, include_modifiers, parameters) param_header = ["", "Total length (s)"] subj_header, behav_header, modif_header = [""] * len(param_header), [ "" ] * len(param_header), [""] * len(param_header) for subj in selected_subjects: for behavior_modifiers in distinct_behav_modif: behavior, modifiers = behavior_modifiers behavior_modifiers_str = "|".join( behavior_modifiers) if modifiers else behavior for param in parameters: subj_header.append(subj) behav_header.append(behavior) modif_header.append(modifiers) param_header.append(param[1]) ''' if parameters_obs["group observations"]: cursor.execute("UPDATE aggregated_events SET observation = 'all' " ) #selected_observations = ["all"] ''' data_report.append(subj_header) data_report.append(behav_header) if include_modifiers: data_report.append(modif_header) data_report.append(param_header) # select time interval for obs_id in selected_observations: ok, msg, db_connector = db_functions.load_aggregated_events_in_db( pj, selected_subjects, [obs_id], selected_behaviors) if not ok: return False, msg, None db_connector.create_aggregate("stdev", 1, StdevFunc) cursor = db_connector.cursor() # if modifiers not to be included set modifiers to "" if not include_modifiers: cursor.execute("UPDATE aggregated_events SET modifiers = ''") # time obs_length = project_functions.observation_total_length( pj[OBSERVATIONS][obs_id]) if obs_length == -1: obs_length = 0 if interval == TIME_FULL_OBS: min_time = float(0) max_time = float(obs_length) if interval == TIME_EVENTS: try: min_time = float(pj[OBSERVATIONS][obs_id][EVENTS][0][0]) except Exception: min_time = float(0) try: max_time = float(pj[OBSERVATIONS][obs_id][EVENTS][-1][0]) except Exception: max_time = float(obs_length) if interval == TIME_ARBITRARY_INTERVAL: min_time = float(start_time) max_time = float(end_time) # adapt start and stop to the selected time interval cursor.execute( "UPDATE aggregated_events SET start = ? WHERE observation = ? AND start < ? AND stop BETWEEN ? AND ?", ( min_time, obs_id, min_time, min_time, max_time, )) cursor.execute( "UPDATE aggregated_events SET stop = ? WHERE observation = ? AND stop > ? AND start BETWEEN ? AND ?", ( max_time, obs_id, max_time, min_time, max_time, )) cursor.execute( "UPDATE aggregated_events SET start = ?, stop = ? WHERE observation = ? AND start < ? AND stop > ?", ( min_time, max_time, obs_id, min_time, max_time, )) cursor.execute( "DELETE FROM aggregated_events WHERE observation = ? AND (start < ? AND stop < ?) OR (start > ? AND stop > ?)", ( obs_id, min_time, min_time, max_time, max_time, )) for subject in selected_subjects: # check if behaviors are to exclude from total time time_to_subtract = 0 if EXCLUDED_BEHAVIORS in parameters_obs: for excluded_behav in parameters_obs[EXCLUDED_BEHAVIORS]: cursor.execute(( "SELECT SUM(stop-start) " "FROM aggregated_events " "WHERE observation = ? AND subject = ? AND behavior = ? " ), ( obs_id, subject, excluded_behav, )) for row in cursor.fetchall(): if row[0] is not None: time_to_subtract += row[0] for behavior_modifiers in distinct_behav_modif: behavior, modifiers = behavior_modifiers behavior_modifiers_str = "|".join( behavior_modifiers) if modifiers else behavior cursor.execute(( "SELECT SUM(stop-start), COUNT(*), AVG(stop-start), stdev(stop-start) " "FROM aggregated_events " "WHERE observation = ? AND subject = ? AND behavior = ? AND modifiers = ? " ), ( obs_id, subject, behavior, modifiers, )) for row in cursor.fetchall(): behaviors[subject][behavior_modifiers_str][ "duration"] = (0 if row[0] is None else f"{row[0]:.3f}") behaviors[subject][behavior_modifiers_str][ "number"] = 0 if row[1] is None else row[1] behaviors[subject][behavior_modifiers_str][ "duration mean"] = (0 if row[2] is None else f"{row[2]:.3f}") behaviors[subject][behavior_modifiers_str][ "duration stdev"] = (0 if row[3] is None else f"{row[3]:.3f}") if behavior not in parameters_obs[EXCLUDED_BEHAVIORS]: try: behaviors[subject][behavior_modifiers_str][ "proportion of time"] = ( 0 if row[0] is None else f"{row[0] / ((max_time - min_time) - time_to_subtract):.3f}" ) except ZeroDivisionError: behaviors[subject][behavior_modifiers_str][ "proportion of time"] = "-" else: # behavior subtracted behaviors[subject][behavior_modifiers_str][ "proportion of time"] = ( 0 if row[0] is None else f"{row[0] / (max_time - min_time):.3f}") columns = [obs_id, f"{max_time - min_time:0.3f}"] for subj in selected_subjects: for behavior_modifiers in distinct_behav_modif: behavior, modifiers = behavior_modifiers behavior_modifiers_str = "|".join( behavior_modifiers) if modifiers else behavior for param in parameters: columns.append( behaviors[subj][behavior_modifiers_str][param[0]]) data_report.append(columns) except Exception: error_type, error_file_name, error_lineno = utilities.error_info( sys.exc_info()) logging.critical( f"Error in edit_event function: {error_type} {error_file_name} {error_lineno}" ) msg = f"Error type: {error_type}\nError file name: {error_file_name}\nError line number: {error_lineno}" logging.critical(msg) return (False, msg, tablib.Dataset()) return True, msg, data_report
def behavior_binary_table(pj: dict): """ ask user for parameters for behavior binary table call create_behavior_binary_table """ result, selected_observations = select_observations.select_observations( pj, MULTIPLE, "Select observations for the behavior binary table") if not selected_observations: return # check if state events are paired out = "" not_paired_obs_list = [] for obs_id in selected_observations: r, msg = project_functions.check_state_events_obs( obs_id, pj[ETHOGRAM], pj[OBSERVATIONS][obs_id]) if not r: out += f"Observation: <strong>{obs_id}</strong><br>{msg}<br>" not_paired_obs_list.append(obs_id) if out: out = f"The observations with UNPAIRED state events will be removed from the analysis<br><br>{out}" results = dialog.Results_dialog() results.setWindowTitle(f"{programName} - Check selected observations") results.ptText.setReadOnly(True) results.ptText.appendHtml(out) results.pbSave.setVisible(False) results.pbCancel.setVisible(True) if not results.exec_(): return selected_observations = [ x for x in selected_observations if x not in not_paired_obs_list ] if not selected_observations: return max_obs_length, selectedObsTotalMediaLength = project_functions.observation_length( pj, selected_observations) if max_obs_length == -1: # media length not available, user choose to not use events return parameters = dialog.choose_obs_subj_behav_category( pj, selected_observations, maxTime=max_obs_length, flagShowIncludeModifiers=True, flagShowExcludeBehaviorsWoEvents=True, by_category=False) if not parameters[SELECTED_SUBJECTS] or not parameters[SELECTED_BEHAVIORS]: QMessageBox.warning(None, programName, "Select subject(s) and behavior(s) to analyze") return # ask for time interval i, ok = QInputDialog.getDouble(None, "Behavior binary table", "Time interval (in seconds):", 1.0, 0.001, 86400, 3) if not ok: return time_interval = utilities.float2decimal(i) ''' iw = dialog.Info_widget() iw.lwi.setVisible(False) iw.resize(350, 200) iw.setWindowFlags(Qt.WindowStaysOnTopHint) iw.setWindowTitle("Behavior binary table") iw.label.setText("Creating the behavior binary table...") iw.show() QApplication.processEvents() ''' results_df = create_behavior_binary_table(pj, selected_observations, parameters, time_interval) ''' iw.hide() ''' if "error" in results_df: QMessageBox.warning(None, programName, results_df["msg"]) return # save results if len(selected_observations) == 1: extended_file_formats = [ "Tab Separated Values (*.tsv)", "Comma Separated Values (*.csv)", "Open Document Spreadsheet ODS (*.ods)", "Microsoft Excel Spreadsheet XLSX (*.xlsx)", "Legacy Microsoft Excel Spreadsheet XLS (*.xls)", "HTML (*.html)" ] file_formats = ["tsv", "csv", "ods", "xlsx", "xls", "html"] file_name, filter_ = QFileDialog().getSaveFileName( None, "Save results", "", ";;".join(extended_file_formats)) if not file_name: return output_format = file_formats[extended_file_formats.index(filter_)] if pathlib.Path(file_name).suffix != "." + output_format: file_name = str(pathlib.Path(file_name)) + "." + output_format # check if file with new extension already exists if pathlib.Path(file_name).is_file(): if dialog.MessageDialog( programName, f"The file {file_name} already exists.", [CANCEL, OVERWRITE]) == CANCEL: return else: items = ("Tab Separated Values (*.tsv)", "Comma separated values (*.csv)", "Open Document Spreadsheet (*.ods)", "Microsoft Excel Spreadsheet XLSX (*.xlsx)", "Legacy Microsoft Excel Spreadsheet XLS (*.xls)", "HTML (*.html)") item, ok = QInputDialog.getItem(None, "Save results", "Available formats", items, 0, False) if not ok: return output_format = re.sub(".* \(\*\.", "", item)[:-1] export_dir = QFileDialog().getExistingDirectory( None, "Choose a directory to save results", os.path.expanduser("~"), options=QFileDialog.ShowDirsOnly) if not export_dir: return mem_command = "" for obs_id in results_df: for subject in results_df[obs_id]: if len(selected_observations) > 1: file_name_with_subject = str( pathlib.Path(export_dir) / utilities.safeFileName(obs_id + "_" + subject)) + "." + output_format else: file_name_with_subject = str( os.path.splitext(file_name)[0] + utilities.safeFileName("_" + subject)) + "." + output_format # check if file with new extension already exists if mem_command != OVERWRITE_ALL and pathlib.Path( file_name_with_subject).is_file(): if mem_command == "Skip all": continue mem_command = dialog.MessageDialog( programName, f"The file {file_name_with_subject} already exists.", [OVERWRITE, OVERWRITE_ALL, "Skip", "Skip all", CANCEL]) if mem_command == CANCEL: return if mem_command in ["Skip", "Skip all"]: continue try: if output_format in ["csv", "tsv", "html"]: with open(file_name_with_subject, "wb") as f: f.write( str.encode(results_df[obs_id][subject].export( output_format))) if output_format in ["ods", "xlsx", "xls"]: with open(file_name_with_subject, "wb") as f: f.write( results_df[obs_id][subject].export(output_format)) except Exception: error_type, error_file_name, error_lineno = utilities.error_info( sys.exc_info()) logging.critical( f"Error in behavior binary table function: {error_type} {error_file_name} {error_lineno}" ) QMessageBox.critical(None, programName, f"Error saving file: {error_type}") return
def filter(self): """ filter events """ if not self.logic.text(): return if self.logic.text().count('"') % 2: QMessageBox.warning(self, programName, f'Wrong number of double quotes (")') return sb_list = re.findall('"([^"]*)"', self.logic.text()) self.out = [] flag_error = False for obs_id in self.events: logic = self.logic.text() for sb in set(sb_list): logic = logic.replace(f'"{sb}"', f'self.events[obs_id]["{sb}"]') if sb not in self.events[obs_id]: self.events[obs_id][sb] = io([0, 0]) try: eval_result = eval(logic) for i in eval_result: if not i.empty: self.out.append([ obs_id, "", f"{i.lower}", f"{i.upper}", f"{i.upper - i.lower:.3f}" ]) except KeyError: self.out.append( [obs_id, "subject / behavior not found", "NA", "NA", "NA"]) except Exception: # out += f"Error in {self.logic.text()}" + "\n" error_type, error_file_name, error_lineno = utilities.error_info( sys.exc_info()) self.out.append([ obs_id, f"Error in {self.logic.text()}: {error_type} ", "NA", "NA", "NA" ]) flag_error = True self.tw.clear() if flag_error or self.rb_details.isChecked(): self.lb_results.setText( f"Results ({len(self.out)} event{'s'*(len(self.out) > 1)})") self.tw.setRowCount(len(self.out)) self.tw.setColumnCount(len( self.details_header)) # obs_id, comment, start, stop, duration self.tw.setHorizontalHeaderLabels(self.details_header) if not flag_error and self.rb_summary.isChecked(): summary = {} for row in self.out: obs_id, _, start, stop, duration = row if obs_id not in summary: summary[obs_id] = [] summary[obs_id].append(float(duration)) self.out = [] for obs_id in summary: self.out.append([ obs_id, str(len(summary[obs_id])), str(round(sum(summary[obs_id]), 3)), str(round(statistics.mean(summary[obs_id]), 3)), str(round(statistics.stdev(summary[obs_id]), 3)) if len(summary[obs_id]) > 1 else "NA" ]) self.lb_results.setText( f"Results ({len(summary)} observation{'s'*(len(summary) > 1)})" ) self.tw.setRowCount(len(summary)) self.tw.setColumnCount(len( self.summary_header)) # obs_id, mean, stdev self.tw.setHorizontalHeaderLabels(self.summary_header) for r in range(len(self.out)): for c in range(self.tw.columnCount()): item = QTableWidgetItem() item.setText(self.out[r][c]) item.setFlags(Qt.ItemIsEnabled) self.tw.setItem(r, c, item)
def import_indep_variables_from_project(self): """ import independent variables from another project """ try: fn = QFileDialog().getOpenFileName( self, "Import independent variables from project file", "", ("Project files (*.boris *.boris.gz);;" "All files (*)")) file_name = fn[0] if type(fn) is tuple else fn if file_name: _, _, project, _ = project_functions.open_project_json(file_name) if "error" in project: logging.debug(project["error"]) QMessageBox.critical(self, programName, project["error"]) return # independent variables if INDEPENDENT_VARIABLES in project and project[ INDEPENDENT_VARIABLES]: # check if variables are already present existing_var = [] for r in range(self.twVariables.rowCount()): existing_var.append( self.twVariables.item(r, 0).text().strip().upper()) for i in utilities.sorted_keys(project[INDEPENDENT_VARIABLES]): self.twVariables.setRowCount(self.twVariables.rowCount() + 1) flag_renamed = False for idx, field in enumerate(tw_indVarFields): item = QTableWidgetItem() if field in project[INDEPENDENT_VARIABLES][i]: if field == "label": txt = project[INDEPENDENT_VARIABLES][i][ "label"].strip() while txt.upper() in existing_var: txt += "_2" flag_renamed = True else: txt = project[INDEPENDENT_VARIABLES][i][ field].strip() item.setText(txt) else: item.setText("") self.twVariables.setItem( self.twVariables.rowCount() - 1, idx, item) self.twVariables.resizeColumnsToContents() if flag_renamed: QMessageBox.information( self, programName, "Some variables already present were renamed") else: QMessageBox.warning( self, programName, "No independent variables found in project") except Exception: error_type, error_file_name, error_lineno = utilities.error_info( sys.exc_info()) logging.critical( f"Import independent variable from project: {error_type} {error_file_name} {error_lineno}" ) dialog.error_message_box("Import independent variable from project", error_type, error_file_name, error_lineno)
def import_subjects_from_project(self): """ import subjects from another project """ try: fn = QFileDialog().getOpenFileName( self, "Import subjects from project file", "", ("Project files (*.boris *.boris.gz);;" "All files (*)")) file_name = fn[0] if type(fn) is tuple else fn if file_name: _, _, project, _ = project_functions.open_project_json(file_name) if "error" in project: logging.debug(project["error"]) QMessageBox.critical(self, programName, project["error"]) return # configuration of behaviours if SUBJECTS in project and project[SUBJECTS]: if self.twSubjects.rowCount(): response = dialog.MessageDialog( programName, ("There are subjects already configured. " "Do you want to append subjects or replace them?"), ['Append', 'Replace', 'Cancel']) if response == "Replace": self.twSubjects.setRowCount(0) if response == CANCEL: return for idx in utilities.sorted_keys(project[SUBJECTS]): self.twSubjects.setRowCount(self.twSubjects.rowCount() + 1) for idx2, sbjField in enumerate(subjectsFields): if sbjField in project[SUBJECTS][idx]: self.twSubjects.setItem( self.twSubjects.rowCount() - 1, idx2, QTableWidgetItem( project[SUBJECTS][idx][sbjField])) else: self.twSubjects.setItem( self.twSubjects.rowCount() - 1, idx2, QTableWidgetItem("")) self.twSubjects.resizeColumnsToContents() else: QMessageBox.warning( self, programName, "No subjects configuration found in project") except Exception: error_type, error_file_name, error_lineno = utilities.error_info( sys.exc_info()) logging.critical( f"Error in function '{sys._getframe().f_code.co_name}': {error_type} {error_file_name} {error_lineno}" ) dialog.error_message_box(sys._getframe().f_code.co_name, error_type, error_file_name, error_lineno)
def import_behaviors_from_project(self): try: fn = QFileDialog().getOpenFileName( self, "Import behaviors from project file", "", ("Project files (*.boris *.boris.gz);;" "All files (*)")) file_name = fn[0] if type(fn) is tuple else fn if file_name: _, _, project, _ = project_functions.open_project_json(file_name) # import behavioral_categories if BEHAVIORAL_CATEGORIES in project: self.pj[BEHAVIORAL_CATEGORIES] = list( project[BEHAVIORAL_CATEGORIES]) # configuration of behaviours if ETHOGRAM in project and project[ETHOGRAM]: if self.twBehaviors.rowCount(): response = dialog.MessageDialog( programName, ("Some behaviors are already configured. " "Do you want to append behaviors or replace them?"), ["Append", "Replace", CANCEL]) if response == "Replace": self.twBehaviors.setRowCount(0) if response == CANCEL: return behaviors_to_import = select_behaviors( title="Select the behaviors to import", text="Behaviors", behavioral_categories=list(project[BEHAVIORAL_CATEGORIES]), ethogram=dict(project[ETHOGRAM]), behavior_type=[STATE_EVENT, POINT_EVENT]) for i in utilities.sorted_keys(project[ETHOGRAM]): if project[ETHOGRAM][i][ BEHAVIOR_CODE] not in behaviors_to_import: continue self.twBehaviors.setRowCount(self.twBehaviors.rowCount() + 1) for field in project[ETHOGRAM][i]: item = QTableWidgetItem() if field == TYPE: item.setText(project[ETHOGRAM][i][field]) item.setFlags(Qt.ItemIsEnabled) item.setBackground(QColor(230, 230, 230)) else: if field == "modifiers" and isinstance( project[ETHOGRAM][i][field], str): modif_set_dict = {} if project[ETHOGRAM][i][field]: modif_set_list = project[ETHOGRAM][i][ field].split("|") for modif_set in modif_set_list: modif_set_dict[str( len(modif_set_dict))] = { "name": "", "type": SINGLE_SELECTION, "values": modif_set.split(",") } project[ETHOGRAM][i][field] = dict( modif_set_dict) item.setText(str(project[ETHOGRAM][i][field])) if field not in ETHOGRAM_EDITABLE_FIELDS: item.setFlags(Qt.ItemIsEnabled) item.setBackground(QColor(230, 230, 230)) self.twBehaviors.setItem( self.twBehaviors.rowCount() - 1, behavioursFields[field], item) self.twBehaviors.resizeColumnsToContents() else: QMessageBox.warning( self, programName, "No behaviors configuration found in project") except Exception: error_type, error_file_name, error_lineno = utilities.error_info( sys.exc_info()) logging.critical( f"Error in function '{sys._getframe().f_code.co_name}': {error_type} {error_file_name} {error_lineno}" ) dialog.error_message_box(sys._getframe().f_code.co_name, error_type, error_file_name, error_lineno)
def select_behaviors(title="Record value from external data file", text="Behaviors", behavioral_categories=[], ethogram={}, behavior_type=[STATE_EVENT, POINT_EVENT]): """ allow user to select behaviors to import Args: title (str): title of dialog box text (str): text of dialog box behavioral_categories (list): behavioral categories ethogram (dict): ethogram """ try: paramPanelWindow = param_panel.Param_panel() paramPanelWindow.resize(800, 600) paramPanelWindow.setWindowTitle(title) paramPanelWindow.lbBehaviors.setText(text) for w in [ paramPanelWindow.lwSubjects, paramPanelWindow.pbSelectAllSubjects, paramPanelWindow.pbUnselectAllSubjects, paramPanelWindow.pbReverseSubjectsSelection, paramPanelWindow.lbSubjects, paramPanelWindow.cbIncludeModifiers, paramPanelWindow.cbExcludeBehaviors, paramPanelWindow.frm_time ]: w.setVisible(False) if behavioral_categories: categories = behavioral_categories # check if behavior not included in a category if "" in [ ethogram[idx][BEHAVIOR_CATEGORY] for idx in ethogram if BEHAVIOR_CATEGORY in ethogram[idx] ]: categories += [""] else: categories = ["###no category###"] for category in categories: if category != "###no category###": if category == "": paramPanelWindow.item = QListWidgetItem("No category") paramPanelWindow.item.setData(34, "No category") else: paramPanelWindow.item = QListWidgetItem(category) paramPanelWindow.item.setData(34, category) font = QFont() font.setBold(True) paramPanelWindow.item.setFont(font) paramPanelWindow.item.setData(33, "category") paramPanelWindow.item.setData(35, False) paramPanelWindow.lwBehaviors.addItem(paramPanelWindow.item) # check if behavior type must be shown for behavior in [ ethogram[x][BEHAVIOR_CODE] for x in utilities.sorted_keys(ethogram) ]: if ((categories == ["###no category###"]) or (behavior in [ ethogram[x][BEHAVIOR_CODE] for x in ethogram if BEHAVIOR_CATEGORY in ethogram[x] and ethogram[x][BEHAVIOR_CATEGORY] == category ])): paramPanelWindow.item = QListWidgetItem(behavior) paramPanelWindow.item.setCheckState(Qt.Unchecked) if category != "###no category###": paramPanelWindow.item.setData(33, "behavior") if category == "": paramPanelWindow.item.setData(34, "No category") else: paramPanelWindow.item.setData(34, category) paramPanelWindow.lwBehaviors.addItem(paramPanelWindow.item) if paramPanelWindow.exec_(): return paramPanelWindow.selectedBehaviors return [] except Exception: error_type, error_file_name, error_lineno = utilities.error_info( sys.exc_info()) logging.critical( f"Error in function '{sys._getframe().f_code.co_name}'': {error_type} {error_file_name} {error_lineno}" ) dialog.error_message_box(sys._getframe().f_code.co_name, error_type, error_file_name, error_lineno)
def import_from_text_file(self): if self.twBehaviors.rowCount(): response = dialog.MessageDialog( programName, "There are behaviors already configured. Do you want to append behaviors or replace them?", ['Append', 'Replace', CANCEL]) if response == CANCEL: return fn = QFileDialog().getOpenFileName( self, "Import behaviors from text file", "", "Text files (*.txt *.tsv *.csv);;All files (*)") fileName = fn[0] if type(fn) is tuple else fn if fileName: if self.twBehaviors.rowCount() and response == "Replace": self.twBehaviors.setRowCount(0) try: with open(fileName, mode="rb") as f: rows_b = f.read().splitlines() rows = [] idx = 1 for row in rows_b: try: rows.append(row.decode("utf-8")) except Exception: QMessageBox.critical( None, programName, (f"Error while reading file\nThe line # {idx}\n" f"{row}\ncontains characters that are not readable."), QMessageBox.Ok | QMessageBox.Default, QMessageBox.NoButton) return idx += 1 fieldSeparator, fieldsNumber = check_text_file_type(rows) logging.debug( f"fields separator: {fieldSeparator} fields number: {fieldsNumber}" ) if fieldSeparator is None: QMessageBox.critical( self, programName, "Separator character not found! Use plain text file and TAB or comma as value separator" ) else: for row in rows: type_, key, code, description = "", "", "", "" if fieldsNumber == 3: # fields: type, key, code type_, key, code = row.split(fieldSeparator) description = "" if fieldsNumber == 4: # fields: type, key, code, description type_, key, code, description = row.split( fieldSeparator) if fieldsNumber > 4: type_, key, code, description = row.split( fieldSeparator)[:4] behavior = { "key": key, "code": code, "description": description, "modifiers": "", "excluded": "", "coding map": "", "category": "" } self.twBehaviors.setRowCount(self.twBehaviors.rowCount() + 1) for field_type in behavioursFields: if field_type == TYPE: item = QTableWidgetItem(DEFAULT_BEHAVIOR_TYPE) # add type combobox if POINT in type_.upper(): item = QTableWidgetItem(POINT_EVENT) if STATE in type_.upper(): item = QTableWidgetItem(STATE_EVENT) else: item = QTableWidgetItem(behavior[field_type]) if field_type not in ETHOGRAM_EDITABLE_FIELDS: item.setFlags(Qt.ItemIsEnabled) item.setBackground(QColor(230, 230, 230)) self.twBehaviors.setItem( self.twBehaviors.rowCount() - 1, behavioursFields[field_type], item) except Exception: error_type, error_file_name, error_lineno = utilities.error_info( sys.exc_info()) logging.critical( f"Error in function '{sys._getframe().f_code.co_name}': {error_type} {error_file_name} {error_lineno}" ) dialog.error_message_box(sys._getframe().f_code.co_name, error_type, error_file_name, error_lineno)
def import_from_JWatcher(self): """ import behaviors configuration from JWatcher (GDF file) """ try: if self.twBehaviors.rowCount(): response = dialog.MessageDialog( programName, "There are behaviors already configured. Do you want to append behaviors or replace them?", ["Append", "Replace", CANCEL]) if response == CANCEL: return fn = QFileDialog().getOpenFileName( self, "Import behaviors from JWatcher", "", "Global Definition File (*.gdf);;All files (*)") fileName = fn[0] if type(fn) is tuple else fn if fileName: if self.twBehaviors.rowCount() and response == "Replace": self.twBehaviors.setRowCount(0) with open(fileName, "r") as f: rows = f.readlines() for idx, row in enumerate(rows): if row and row[0] == "#": continue if "Behavior.name." in row and "=" in row: key, code = row.split('=') key = key.replace("Behavior.name.", "") # read description if idx < len(rows) and "Behavior.description." in rows[ idx + 1]: description = rows[idx + 1].split("=")[-1] behavior = { "key": key, "code": code, "description": description, "modifiers": "", "excluded": "", "coding map": "", "category": "" } self.twBehaviors.setRowCount(self.twBehaviors.rowCount() + 1) for field_type in behavioursFields: if field_type == TYPE: item = QTableWidgetItem(DEFAULT_BEHAVIOR_TYPE) else: item = QTableWidgetItem(behavior[field_type]) if field_type in [ TYPE, "excluded", "category", "coding map", "modifiers" ]: item.setFlags(Qt.ItemIsEnabled) item.setBackground(QColor(230, 230, 230)) self.twBehaviors.setItem( self.twBehaviors.rowCount() - 1, behavioursFields[field_type], item) except Exception: error_type, error_file_name, error_lineno = utilities.error_info( sys.exc_info()) logging.critical( f"Error in function '{sys._getframe().f_code.co_name}'': {error_type} {error_file_name} {error_lineno}" ) dialog.error_message_box(sys._getframe().f_code.co_name, error_type, error_file_name, error_lineno)
def import_behaviors_from_clipboard(self): """ import ethogram from clipboard """ try: cb = QApplication.clipboard() cb_text = cb.text() if not cb_text: QMessageBox.warning(None, programName, "The clipboard is empty", QMessageBox.Ok | QMessageBox.Default, QMessageBox.NoButton) return if self.twBehaviors.rowCount(): response = dialog.MessageDialog( programName, "Some behaviors are already configured. Do you want to append behaviors or replace them?", ["Append", "Replace", CANCEL]) if response == CANCEL: return if response == "Replace": self.twBehaviors.setRowCount(0) cb_text_splitted = cb_text.split("\n") while "" in cb_text_splitted: cb_text_splitted.remove("") if len(set([len(x.split("\t")) for x in cb_text_splitted])) != 1: QMessageBox.warning(None, programName, ( "The clipboard content does not have a constant number of fields.<br>" "From your spreadsheet: CTRL + A (select all cells), CTRL + C (copy to clipboard)" ), QMessageBox.Ok | QMessageBox.Default, QMessageBox.NoButton) return for row in cb_text_splitted: if set(row.split("\t")) != set([""]): behavior = {"type": DEFAULT_BEHAVIOR_TYPE} for idx, field in enumerate(row.split("\t")): if idx == 0: behavior["type"] = STATE_EVENT if STATE in field.upper( ) else (POINT_EVENT if POINT in field.upper() else "") if idx == 1: behavior["key"] = field.strip() if len( field.strip()) == 1 else "" if idx == 2: behavior["code"] = field.strip() if idx == 3: behavior["description"] = field.strip() if idx == 4: behavior["category"] = field.strip() self.twBehaviors.setRowCount(self.twBehaviors.rowCount() + 1) for field_type in behavioursFields: if field_type == TYPE: item = QTableWidgetItem( behavior.get("type", DEFAULT_BEHAVIOR_TYPE)) else: item = QTableWidgetItem(behavior.get(field_type, "")) if field_type not in ETHOGRAM_EDITABLE_FIELDS: # [TYPE, "excluded", "coding map", "modifiers", "category"]: item.setFlags(Qt.ItemIsEnabled) item.setBackground(QColor(230, 230, 230)) self.twBehaviors.setItem(self.twBehaviors.rowCount() - 1, behavioursFields[field_type], item) except Exception: error_type, error_file_name, error_lineno = utilities.error_info( sys.exc_info()) logging.critical( f"Error in function '{sys._getframe().f_code.co_name}': {error_type} {error_file_name} {error_lineno}" ) dialog.error_message_box(sys._getframe().f_code.co_name, error_type, error_file_name, error_lineno)