def __init__(self, modifiers_str, subjects=[], parent=None): super().__init__() self.setupUi(self) self.subjects = subjects if not self.subjects: self.pb_add_subjects.setEnabled(False) self.pbAddModifier.clicked.connect(self.addModifier) self.pbAddModifier.setIcon(QIcon(":/frame_forward")) self.pbAddSet.clicked.connect(self.addSet) self.pbRemoveSet.clicked.connect(self.removeSet) self.pbModifyModifier.clicked.connect(self.modifyModifier) self.pbModifyModifier.setIcon(QIcon(":/frame_backward")) self.pbMoveUp.clicked.connect(self.moveModifierUp) self.pbMoveDown.clicked.connect(self.moveModifierDown) self.pbMoveSetLeft.clicked.connect(self.moveSetLeft) self.pbMoveSetRight.clicked.connect(self.moveSetRight) self.pbRemoveModifier.clicked.connect(self.removeModifier) self.pb_sort_modifiers.clicked.connect(self.sort_modifiers) self.pb_add_subjects.clicked.connect(self.add_subjects) self.pb_load_file.clicked.connect(self.add_modifiers_from_file) self.pbOK.clicked.connect(lambda: self.pb_pushed("ok")) self.pbCancel.clicked.connect(lambda: self.pb_pushed("cancel")) self.leSetName.textChanged.connect(self.set_name_changed) self.cbType.currentIndexChanged.connect(self.type_changed) dummy_dict = eval(modifiers_str) if modifiers_str else {} modif_values = [] for idx in sorted_keys(dummy_dict): modif_values.append(dummy_dict[idx]) self.modifiers_sets_dict = {} for modif in modif_values: self.modifiers_sets_dict[str(len(self.modifiers_sets_dict))] = dict(modif) self.tabWidgetModifiersSets.currentChanged.connect(self.tabWidgetModifiersSets_changed) # create tab for idx in sorted_keys(self.modifiers_sets_dict): self.tabWidgetModifiersSets.addTab(QWidget(), f"Set #{int(idx) + 1}") if self.tabWidgetModifiersSets.currentIndex() == -1: for w in [self.lbSetName, self.lbType, self.lbValues, self.leSetName, self.cbType, self.lwModifiers, self.pbMoveUp, self.pbMoveDown, self.pbRemoveModifier, self.pbRemoveSet, self.pbMoveSetLeft, self.pbMoveSetRight, self.pb_add_subjects, self.pb_load_file, self.pb_sort_modifiers]: w.setVisible(False) for w in [self.leModifier, self.leCode, self.pbAddModifier, self.pbModifyModifier]: w.setEnabled(False) # set first tab as active self.tabMem = 0
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: dialog.error_message(sys._getframe().f_code.co_name, sys.exc_info())
def test_str_keys(self): r = utilities.sorted_keys({ "10": "x", "0": "x", "1": "x", "11": "x", "05": "x" }) assert r == ['0', '1', '5', '10', '11']
def pbOK_clicked(self): for idx in sorted_keys(self.modifiers_dict): if self.modifiers_dict[idx]["type"] == NUMERIC_MODIFIER: if self.modifiers_dict[idx]["widget"].text(): try: val = float(self.modifiers_dict[idx]["widget"].text()) except Exception: QMessageBox.warning( self, programName, "<b>{}</b> is not a numeric value".format( self.modifiers_dict[idx]["widget"].text())) return self.accept()
def get_modifiers(self): """ get modifiers returns list of selected modifiers """ modifiers = [] for idx in sorted_keys(self.modifiers_dict): if self.modifiers_dict[idx]["type"] in [ SINGLE_SELECTION, MULTI_SELECTION, NUMERIC_MODIFIER ]: self.modifiers_dict[idx]["selected"] = [] if self.modifiers_dict[idx]["type"] == MULTI_SELECTION: for j in range(self.modifiers_dict[idx]["widget"].count()): if self.modifiers_dict[idx]["widget"].item( j).checkState() == Qt.Checked: self.modifiers_dict[idx]["selected"].append( re.sub( " \(.*\)", "", self.modifiers_dict[idx] ["widget"].item(j).text())) if not self.modifiers_dict[idx]["selected"]: self.modifiers_dict[idx]["selected"].append("None") if self.modifiers_dict[idx]["type"] == SINGLE_SELECTION: for item in self.modifiers_dict[idx]["widget"].selectedItems(): self.modifiers_dict[idx]["selected"].append( re.sub(" \(.*\)", "", item.text())) if self.modifiers_dict[idx]["type"] == NUMERIC_MODIFIER: self.modifiers_dict[ idx]["selected"] = self.modifiers_dict[idx]["widget"].text( ) if self.modifiers_dict[idx]["widget"].text() else "None" ''' for widget in self.children(): if widget.objectName() == "lw_modifiers_classic": for item in widget.selectedItems(): modifiers.append(re.sub(" \(.*\)", "", item.text())) if widget.objectName() == "lw_modifiers_from_set": for idx in range(widget.count()): if widget.item(idx).checkState() == Qt.Checked: modifiers.append(widget.item(idx).text()) ''' return self.modifiers_dict
def removeSet(self): """ remove set of modifiers """ if self.tabWidgetModifiersSets.currentIndex() != -1: if dialog.MessageDialog(cfg.programName, "Confirm deletion of this set of modifiers?", [cfg.YES, cfg.NO]) == cfg.YES: index_to_delete = self.tabWidgetModifiersSets.currentIndex() for k in range(index_to_delete, len(self.modifiers_sets_dict) - 1): self.modifiers_sets_dict[str(k)] = self.modifiers_sets_dict[str(k + 1)] # del last key del self.modifiers_sets_dict[str(len(self.modifiers_sets_dict) - 1)] # remove all tabs while self.tabWidgetModifiersSets.count(): self.tabWidgetModifiersSets.removeTab(0) # recreate tabs for idx in sorted_keys(self.modifiers_sets_dict): self.tabWidgetModifiersSets.addTab(QWidget(), f"Set #{int(idx) + 1}") # set not visible and not available buttons and others elements if self.tabWidgetModifiersSets.currentIndex() == -1: for w in [self.lbSetName, self.lbType, self.lbValues, self.leSetName, self.cbType, self.lwModifiers, self.pbMoveUp, self.pbMoveDown, self.pbRemoveModifier, self.pbRemoveSet, self.pbMoveSetLeft, self.pbMoveSetRight]: w.setVisible(False) for w in [self.leModifier, self.leCode, self.pbAddModifier, self.pbModifyModifier]: w.setEnabled(False) if not len(self.modifiers_sets_dict): # set invisible and unavailable buttons and others elements for w in [self.lbSetName, self.lbType, self.lbValues, self.leSetName, self.cbType, self.lwModifiers, self.pbMoveUp, self.pbMoveDown, self.pbRemoveModifier, self.pbRemoveSet, self.pbMoveSetLeft, self.pbMoveSetRight, self.pb_add_subjects, self.pb_load_file, self.pb_sort_modifiers]: w.setVisible(False) for w in [self.leModifier, self.leCode, self.pbAddModifier, self.pbModifyModifier]: w.setEnabled(False) return else: QMessageBox.information(self, cfg.programName, "It is not possible to remove the last modifiers' set.")
def __init__(self, code, modifiers_dict, currentModifier): super().__init__() self.setWindowTitle(programName) self.setWindowFlags(Qt.WindowStaysOnTopHint) self.modifiers_dict = dict(modifiers_dict) currentModifierList = currentModifier.split("|") V1layout = QVBoxLayout() label = QLabel() label.setText( f"Choose the modifier{'s' * (len(self.modifiers_dict) > 1)} for <b>{code}</b> behavior" ) V1layout.addWidget(label) Hlayout = QHBoxLayout() self.modifiersSetNumber = 0 for idx in sorted_keys(modifiers_dict): if self.modifiers_dict[idx]["type"] not in [ SINGLE_SELECTION, MULTI_SELECTION, NUMERIC_MODIFIER ]: continue V2layout = QVBoxLayout() self.modifiersSetNumber += 1 lb = QLabel() lb.setText(f"Modifier <b>{self.modifiers_dict[idx]['name']}</b>") V2layout.addWidget(lb) if self.modifiers_dict[idx]["type"] in [ SINGLE_SELECTION, MULTI_SELECTION ]: lw = QListWidget() self.modifiers_dict[idx]["widget"] = lw lw.setObjectName( f"lw_modifiers_({self.modifiers_dict[idx]['type']})") lw.installEventFilter(self) if self.modifiers_dict[idx]["type"] == SINGLE_SELECTION: item = QListWidgetItem("None") lw.addItem(item) item.setSelected(True) for modifier in self.modifiers_dict[idx]["values"]: item = QListWidgetItem(modifier) if self.modifiers_dict[idx]["type"] == MULTI_SELECTION: item.setCheckState(Qt.Unchecked) # previously selected try: if currentModifierList != [""] and re.sub( " \(.\)", "", modifier ) in currentModifierList[int(idx)].split(","): item.setCheckState(Qt.Checked) except Exception: # for old projects due to a fixed bug pass lw.addItem(item) if self.modifiers_dict[idx]["type"] == SINGLE_SELECTION: try: if currentModifierList != [""] and re.sub( " \(.\)", "", modifier) == currentModifierList[int(idx)]: item.setSelected(True) except Exception: # for old projects due to a fixed bug pass V2layout.addWidget(lw) if self.modifiers_dict[idx]["type"] in [NUMERIC_MODIFIER]: le = QLineEdit() self.modifiers_dict[idx]["widget"] = le if currentModifierList != [ "" ] and currentModifierList[int(idx)] != "None": le.setText(currentModifierList[int(idx)]) V2layout.addWidget(le) # vertical spacer spacerItem = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) V2layout.addItem(spacerItem) Hlayout.addLayout(V2layout) V1layout.addLayout(Hlayout) H2layout = QHBoxLayout() H2layout.addStretch(1) pbCancel = QPushButton(CANCEL) pbCancel.clicked.connect(self.reject) H2layout.addWidget(pbCancel) pbOK = QPushButton(OK) pbOK.setDefault(True) pbOK.clicked.connect(self.pbOK_clicked) H2layout.addWidget(pbOK) V1layout.addLayout(H2layout) self.setLayout(V1layout) self.installEventFilter(self) self.setMaximumSize(1024, 960)
def select_observations(pj: dict, mode: str, windows_title: str = "") -> tuple: """ allow user to select observations mode: accepted values: OPEN, EDIT, SINGLE, MULTIPLE, SELECT1 Args: pj (dict): BORIS project dictionary mode (str): mode for selection: OPEN, EDIT, SINGLE, MULTIPLE, SELECT1 windows_title (str): title for windows Returns: str: selected mode: OPEN, EDIT, VIEW list: list of selected observations """ obsListFields = [ "id", "date", "description", "subjects", "observation duration", "exhaustivity %", "media" ] indepVarHeader, column_type = [], [ TEXT, TEXT, TEXT, TEXT, NUMERIC, NUMERIC, TEXT ] if INDEPENDENT_VARIABLES in pj: for idx in utilities.sorted_keys(pj[INDEPENDENT_VARIABLES]): indepVarHeader.append(pj[INDEPENDENT_VARIABLES][idx]["label"]) column_type.append(pj[INDEPENDENT_VARIABLES][idx]["type"]) data = [] not_paired = [] state_events_list = [ pj[ETHOGRAM][x][BEHAVIOR_CODE] for x in pj[ETHOGRAM] if STATE in pj[ETHOGRAM][x][TYPE].upper() ] for obs in sorted(list(pj[OBSERVATIONS].keys())): date = pj[OBSERVATIONS][obs]["date"].replace("T", " ") descr = utilities.eol2space(pj[OBSERVATIONS][obs][DESCRIPTION]) # subjects observedSubjects = [ NO_FOCAL_SUBJECT if x == "" else x for x in project_functions.extract_observed_subjects(pj, [obs]) ] subjectsList = ", ".join(observedSubjects) # observed time interval interval = project_functions.observed_interval(pj[OBSERVATIONS][obs]) observed_interval_str = str(interval[1] - interval[0]) # media mediaList = [] if pj[OBSERVATIONS][obs][TYPE] in [MEDIA]: if pj[OBSERVATIONS][obs][FILE]: for player in sorted(pj[OBSERVATIONS][obs][FILE].keys()): for media in pj[OBSERVATIONS][obs][FILE][player]: mediaList.append(f"#{player}: {media}") if len(mediaList) > 8: media = " ".join(mediaList) else: media = "\n".join(mediaList) elif pj[OBSERVATIONS][obs][TYPE] in [LIVE]: media = LIVE # independent variables indepvar = [] if INDEPENDENT_VARIABLES in pj[OBSERVATIONS][obs]: for var_label in indepVarHeader: if var_label in pj[OBSERVATIONS][obs][INDEPENDENT_VARIABLES]: indepvar.append(pj[OBSERVATIONS][obs] [INDEPENDENT_VARIABLES][var_label]) else: indepvar.append("") # check unpaired events ok, _ = project_functions.check_state_events_obs( obs, pj[ETHOGRAM], pj[OBSERVATIONS][obs], HHMMSS) if not ok: not_paired.append(obs) # check exhaustivity of observation exhaustivity = project_functions.check_observation_exhaustivity( pj[OBSERVATIONS][obs][EVENTS], [], state_events_list) data.append([ obs, date, descr, subjectsList, observed_interval_str, str(exhaustivity), media ] + indepvar) obsList = observations_list.observationsList_widget( data, header=obsListFields + indepVarHeader, column_type=column_type, not_paired=not_paired) if windows_title: obsList.setWindowTitle(windows_title) obsList.pbOpen.setVisible(False) obsList.pbView.setVisible(False) obsList.pbEdit.setVisible(False) obsList.pbOk.setVisible(False) obsList.pbSelectAll.setVisible(False) obsList.pbUnSelectAll.setVisible(False) obsList.mode = mode if mode == OPEN: obsList.view.setSelectionMode(QAbstractItemView.SingleSelection) obsList.pbOpen.setVisible(True) if mode == VIEW: obsList.view.setSelectionMode(QAbstractItemView.SingleSelection) obsList.pbView.setVisible(True) if mode == EDIT: obsList.view.setSelectionMode(QAbstractItemView.SingleSelection) obsList.pbEdit.setVisible(True) if mode == SINGLE: obsList.view.setSelectionMode(QAbstractItemView.SingleSelection) obsList.pbOpen.setVisible(True) obsList.pbView.setVisible(True) obsList.pbEdit.setVisible(True) if mode == MULTIPLE: obsList.view.setSelectionMode(QAbstractItemView.MultiSelection) obsList.pbOk.setVisible(True) obsList.pbSelectAll.setVisible(True) obsList.pbUnSelectAll.setVisible(True) if mode == SELECT1: obsList.view.setSelectionMode(QAbstractItemView.SingleSelection) obsList.pbOk.setVisible(True) # restore window geometry gui_utilities.restore_geometry(obsList, "observations list", (900, 600)) obsList.view.sortItems(0, Qt.AscendingOrder) for row in range(obsList.view.rowCount()): obsList.view.resizeRowToContents(row) selected_observations = [] result = obsList.exec_() # saving window geometry in ini file gui_utilities.save_geometry(obsList, "observations list") if result: if obsList.view.selectedIndexes(): for idx in obsList.view.selectedIndexes(): if idx.column() == 0: # first column selected_observations.append(idx.data()) if result == 0: # cancel resultStr = "" if result == 1: # select resultStr = "ok" if result == 2: # open resultStr = OPEN if result == 3: # edit resultStr = EDIT if result == 4: # view resultStr = VIEW return resultStr, selected_observations
def test_numeric_keys(self): r = utilities.sorted_keys({5: "a", 4: "7", 0: "z", 6: "a"}) assert r == ['0', '4', '5', '6']
def create_events_plot(pj, selected_observations, parameters, plot_colors=BEHAVIORS_PLOT_COLORS, plot_directory="", file_format="png"): """ create a time diagram plot (sort of gantt chart) with matplotlib barh function (https://matplotlib.org/3.1.0/api/_as_gen/matplotlib.pyplot.barh.html) """ selected_subjects = parameters[SELECTED_SUBJECTS] selected_behaviors = parameters[SELECTED_BEHAVIORS] include_modifiers = parameters[INCLUDE_MODIFIERS] interval = parameters[TIME_INTERVAL] start_time = parameters[START_TIME] end_time = parameters[END_TIME] 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 cursor = db_connector.cursor() # if modifiers not to be included set modifiers to "" if not include_modifiers: cursor.execute("UPDATE aggregated_events SET modifiers = ''") cursor.execute("SELECT distinct behavior, modifiers FROM aggregated_events") distinct_behav_modif = [[rows["behavior"], rows["modifiers"]] 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, "-"]) distinct_behav_modif = sorted(distinct_behav_modif) max_len = len(distinct_behav_modif) all_behaviors = [pj[ETHOGRAM][x][BEHAVIOR_CODE] for x in utilities.sorted_keys(pj[ETHOGRAM])] par1 = 1 bar_height = 0.5 init = dt.datetime(2017, 1, 1) for obs_id in selected_observations: if len(selected_subjects) > 1: fig, axs = plt.subplots(figsize=(20, 8), nrows=len(selected_subjects), ncols=1, sharex=True) else: fig, ax = plt.subplots(figsize=(20, 8), nrows=len(selected_subjects), ncols=1, sharex=True) axs = np.ndarray(shape=(1), dtype=type(ax)) axs[0] = ax ok, msg, db_connector = db_functions.load_aggregated_events_in_db( pj, selected_subjects, [obs_id], selected_behaviors) cursor = db_connector.cursor() # if modifiers not to be included set modifiers to "" if not include_modifiers: cursor.execute("UPDATE aggregated_events SET modifiers = ''") cursor = db_connector.cursor() cursor.execute("SELECT distinct behavior, modifiers FROM aggregated_events") distinct_behav_modif = [[rows["behavior"], rows["modifiers"]] for rows in cursor.fetchall()] # add selected behaviors that are not observed if not parameters["exclude behaviors"]: for behav in selected_behaviors: if [x for x in distinct_behav_modif if x[0] == behav] == []: distinct_behav_modif.append([behav, "-"]) distinct_behav_modif = sorted(distinct_behav_modif) max_len = len(distinct_behav_modif) # time obs_length = project_functions.observation_total_length(pj[OBSERVATIONS][obs_id]) if obs_length == -1: # media length not available interval = TIME_EVENTS 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) 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, )) ylabels = [" ".join(x) for x in distinct_behav_modif] for ax_idx, subject in enumerate(selected_subjects): if parameters["exclude behaviors"]: cursor.execute("SELECT distinct behavior, modifiers FROM aggregated_events WHERE subject = ?", (subject, )) distinct_behav_modif = [[rows["behavior"], rows["modifiers"]] for rows in cursor.fetchall()] # add selected behaviors that are not observed if not parameters["exclude behaviors"]: for behav in selected_behaviors: if [x for x in distinct_behav_modif if x[0] == behav] == []: distinct_behav_modif.append([behav, "-"]) distinct_behav_modif = sorted(distinct_behav_modif) max_len = len(distinct_behav_modif) ylabels = [" ".join(x) for x in distinct_behav_modif] if not ax_idx: axs[ax_idx].set_title(f"Observation {obs_id}\n{subject}", fontsize=14) else: axs[ax_idx].set_title(subject, fontsize=14) bars = {} i = 0 for behavior_modifiers in distinct_behav_modif: behavior, modifiers = behavior_modifiers behavior_modifiers_str = "|".join(behavior_modifiers) if modifiers else behavior bars[behavior_modifiers_str] = [] # total duration cursor.execute(("SELECT start, stop FROM aggregated_events " "WHERE observation = ? AND subject = ? AND behavior = ? AND modifiers = ?"), (obs_id, subject, behavior, modifiers,)) for row in cursor.fetchall(): bars[behavior_modifiers_str].append((row["start"], row["stop"])) start_date = matplotlib.dates.date2num(init + dt.timedelta(seconds=row["start"])) end_date = matplotlib.dates.date2num( init + dt.timedelta(seconds=row["stop"] + POINT_EVENT_PLOT_DURATION * (row["stop"] == row["start"]))) try: bar_color = utilities.behavior_color(plot_colors, all_behaviors.index(behavior)) except Exception: bar_color = "darkgray" bar_color = POINT_EVENT_PLOT_COLOR if row["stop"] == row["start"] else bar_color # sage colors removed from matplotlib colors list if bar_color in ["sage", "darksage", "lightsage"]: bar_color = {"darksage": "#598556", "lightsage": "#bcecac", "sage": "#87ae73"}[bar_color] try: axs[ax_idx].barh((i * par1) + par1, end_date - start_date, left=start_date, height=bar_height, align="center", edgecolor=bar_color, color=bar_color, alpha=1) except Exception: axs[ax_idx].barh((i * par1) + par1, end_date - start_date, left=start_date, height=bar_height, align="center", edgecolor="darkgray", color="darkgray", alpha=1) i += 1 axs[ax_idx].set_ylim(bottom=0, top=(max_len * par1) + par1) pos = np.arange(par1, max_len * par1 + par1 + 1, par1) axs[ax_idx].set_yticks(pos[:len(ylabels)]) axs[ax_idx].set_yticklabels(ylabels, fontdict={"fontsize": 10}) axs[ax_idx].set_ylabel("Behaviors" + " (modifiers)" * include_modifiers, fontdict={"fontsize": 10}) axs[ax_idx].set_xlim(left=matplotlib.dates.date2num(init + dt.timedelta(seconds=min_time)), right=matplotlib.dates.date2num(init + dt.timedelta(seconds=max_time + 1))) axs[ax_idx].grid(color="g", linestyle=":") axs[ax_idx].xaxis_date() axs[ax_idx].xaxis.set_major_formatter(DateFormatter("%H:%M:%S")) axs[ax_idx].set_xlabel("Time (HH:MM:SS)", fontdict={"fontsize": 12}) axs[ax_idx].invert_yaxis() fig.autofmt_xdate() plt.tight_layout() if len(selected_observations) > 1: plt.savefig(f"{pathlib.Path(plot_directory) / utilities.safeFileName(obs_id)}.{file_format}") else: plt.show()
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: dialog.error_message("Import independent variable from project", sys.exc_info())
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: dialog.error_message(sys._getframe().f_code.co_name, sys.exc_info())
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: dialog.error_message(sys._getframe().f_code.co_name, sys.exc_info())
def test_empty_dict(self): r = utilities.sorted_keys({}) assert r == []
def choose_obs_subj_behav_category(pj: dict, selected_observations: list, maxTime=0, flagShowIncludeModifiers: bool = True, flagShowExcludeBehaviorsWoEvents: bool = True, by_category: bool = False, show_time: bool = False, timeFormat: str = HHMMSS): """ show window for: - selection of subjects - selection of behaviors (based on selected subjects) - selection of time interval - inclusion/exclusion of modifiers - inclusion/exclusion of behaviors without events (flagShowExcludeBehaviorsWoEvents == True) Returns: dict: {"selected subjects": selectedSubjects, "selected behaviors": selectedBehaviors, "include modifiers": True/False, "exclude behaviors": True/False, "time": TIME_FULL_OBS / TIME_EVENTS / TIME_ARBITRARY_INTERVAL "start time": startTime, "end time": endTime } """ paramPanelWindow = param_panel.Param_panel() paramPanelWindow.resize(600, 500) paramPanelWindow.setWindowTitle("Select subjects and behaviors") paramPanelWindow.selectedObservations = selected_observations paramPanelWindow.pj = pj if not flagShowIncludeModifiers: paramPanelWindow.cbIncludeModifiers.setVisible(False) if not flagShowExcludeBehaviorsWoEvents: paramPanelWindow.cbExcludeBehaviors.setVisible(False) if by_category: paramPanelWindow.cbIncludeModifiers.setVisible(False) paramPanelWindow.cbExcludeBehaviors.setVisible(False) paramPanelWindow.frm_time_interval.setEnabled(False) if timeFormat == HHMMSS: paramPanelWindow.start_time.set_format_hhmmss() paramPanelWindow.end_time.set_format_hhmmss() ''' paramPanelWindow.teStartTime.setTime(QTime.fromString("00:00:00.000", "hh:mm:ss.zzz")) paramPanelWindow.teEndTime.setTime(QTime.fromString(utilities.seconds2time(maxTime), "hh:mm:ss.zzz")) paramPanelWindow.dsbStartTime.setVisible(False) paramPanelWindow.dsbEndTime.setVisible(False) ''' if timeFormat == S: paramPanelWindow.start_time.set_format_s() paramPanelWindow.end_time.set_format_s() ''' paramPanelWindow.dsbStartTime.setValue(0.0) paramPanelWindow.dsbEndTime.setValue(maxTime) paramPanelWindow.teStartTime.setVisible(False) paramPanelWindow.teEndTime.setVisible(False) ''' paramPanelWindow.start_time.set_time(0) paramPanelWindow.end_time.set_time(maxTime) # hide max time if not maxTime: paramPanelWindow.frm_time.setVisible(False) if selected_observations: observedSubjects = project_functions.extract_observed_subjects(pj, selected_observations) else: # load all subjects and "No focal subject" observedSubjects = [pj[SUBJECTS][x][SUBJECT_NAME] for x in pj[SUBJECTS]] + [""] selectedSubjects = [] # add 'No focal subject' if "" in observedSubjects: selectedSubjects.append(NO_FOCAL_SUBJECT) paramPanelWindow.item = QListWidgetItem(paramPanelWindow.lwSubjects) paramPanelWindow.ch = QCheckBox() paramPanelWindow.ch.setText(NO_FOCAL_SUBJECT) paramPanelWindow.ch.stateChanged.connect(paramPanelWindow.cb_changed) paramPanelWindow.ch.setChecked(True) paramPanelWindow.lwSubjects.setItemWidget(paramPanelWindow.item, paramPanelWindow.ch) all_subjects = [pj[SUBJECTS][x][SUBJECT_NAME] for x in utilities.sorted_keys(pj[SUBJECTS])] for subject in all_subjects: paramPanelWindow.item = QListWidgetItem(paramPanelWindow.lwSubjects) paramPanelWindow.ch = QCheckBox() paramPanelWindow.ch.setText(subject) paramPanelWindow.ch.stateChanged.connect(paramPanelWindow.cb_changed) if subject in observedSubjects: selectedSubjects.append(subject) paramPanelWindow.ch.setChecked(True) paramPanelWindow.lwSubjects.setItemWidget(paramPanelWindow.item, paramPanelWindow.ch) logging.debug(f'selectedSubjects: {selectedSubjects}') if selected_observations: observedBehaviors = paramPanelWindow.extract_observed_behaviors(selected_observations, selectedSubjects) # not sorted else: # load all behaviors observedBehaviors = [pj[ETHOGRAM][x][BEHAVIOR_CODE] for x in pj[ETHOGRAM]] logging.debug(f'observed behaviors: {observedBehaviors}') if BEHAVIORAL_CATEGORIES in pj: categories = pj[BEHAVIORAL_CATEGORIES][:] # check if behavior not included in a category try: if "" in [pj[ETHOGRAM][idx][BEHAVIOR_CATEGORY] for idx in pj[ETHOGRAM] if BEHAVIOR_CATEGORY in pj[ETHOGRAM][idx]]: categories += [""] except Exception: categories = ["###no category###"] 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) for behavior in [pj[ETHOGRAM][x][BEHAVIOR_CODE] for x in utilities.sorted_keys(pj[ETHOGRAM])]: if ((categories == ["###no category###"]) or (behavior in [pj[ETHOGRAM][x][BEHAVIOR_CODE] for x in pj[ETHOGRAM] if BEHAVIOR_CATEGORY in pj[ETHOGRAM][x] and pj[ETHOGRAM][x][BEHAVIOR_CATEGORY] == category])): paramPanelWindow.item = QListWidgetItem(behavior) if behavior in observedBehaviors: paramPanelWindow.item.setCheckState(Qt.Checked) else: 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 not paramPanelWindow.exec_(): return {SELECTED_SUBJECTS: [], SELECTED_BEHAVIORS: []} selectedSubjects = paramPanelWindow.selectedSubjects selectedBehaviors = paramPanelWindow.selectedBehaviors logging.debug(f"selected subjects: {selectedSubjects}") logging.debug(f"selected behaviors: {selectedBehaviors}") startTime = paramPanelWindow.start_time.get_time() endTime = paramPanelWindow.end_time.get_time() ''' if timeFormat == HHMMSS: startTime = utilities.time2seconds(paramPanelWindow.teStartTime.time().toString(HHMMSSZZZ)) endTime = utilities.time2seconds(paramPanelWindow.teEndTime.time().toString(HHMMSSZZZ)) if timeFormat == S: startTime = Decimal(paramPanelWindow.dsbStartTime.value()) endTime = Decimal(paramPanelWindow.dsbEndTime.value()) ''' if startTime > endTime: QMessageBox.warning(None, programName, "The start time is greater than the end time", QMessageBox.Ok | QMessageBox.Default, QMessageBox.NoButton) return {SELECTED_SUBJECTS: [], SELECTED_BEHAVIORS: []} if paramPanelWindow.rb_full.isChecked(): time_param = TIME_FULL_OBS if paramPanelWindow.rb_limit.isChecked(): time_param = TIME_EVENTS if paramPanelWindow.rb_interval.isChecked(): time_param = TIME_ARBITRARY_INTERVAL return {SELECTED_SUBJECTS: selectedSubjects, SELECTED_BEHAVIORS: selectedBehaviors, INCLUDE_MODIFIERS: paramPanelWindow.cbIncludeModifiers.isChecked(), EXCLUDE_BEHAVIORS: paramPanelWindow.cbExcludeBehaviors.isChecked(), "time": time_param, START_TIME: startTime, END_TIME: endTime }
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 export_observations_list(pj: dict, selected_observations: list, file_name: str, output_format: str) -> bool: """ create file with a list of selected observations Args: pj (dict): project dictionary selected_observations (list): list of observations to export file_name (str): path of file to save list of observations output_format (str): format output Returns: bool: True of OK else False """ data = tablib.Dataset() data.headers = [ "Observation id", "Date", "Description", "Subjects", "Media files/Live observation" ] indep_var_header = [] if INDEPENDENT_VARIABLES in pj: for idx in utilities.sorted_keys(pj[INDEPENDENT_VARIABLES]): indep_var_header.append(pj[INDEPENDENT_VARIABLES][idx]["label"]) data.headers.extend(indep_var_header) for obs_id in selected_observations: subjects_list = sorted( list( set([ x[EVENT_SUBJECT_FIELD_IDX] for x in pj[OBSERVATIONS][obs_id][EVENTS] ]))) if "" in subjects_list: subjects_list = [NO_FOCAL_SUBJECT] + subjects_list subjects_list.remove("") subjects = ", ".join(subjects_list) if pj[OBSERVATIONS][obs_id][TYPE] == LIVE: media_files = ["Live observation"] elif pj[OBSERVATIONS][obs_id][TYPE] == MEDIA: media_files = [] if pj[OBSERVATIONS][obs_id][FILE]: for player in sorted(pj[OBSERVATIONS][obs_id][FILE].keys()): for media in pj[OBSERVATIONS][obs_id][FILE][player]: media_files.append(f"#{player}: {media}") # independent variables indep_var = [] if INDEPENDENT_VARIABLES in pj[OBSERVATIONS][obs_id]: for var_label in indep_var_header: if var_label in pj[OBSERVATIONS][obs_id][ INDEPENDENT_VARIABLES]: indep_var.append(pj[OBSERVATIONS][obs_id] [INDEPENDENT_VARIABLES][var_label]) else: indep_var.append("") data.append([ obs_id, pj[OBSERVATIONS][obs_id]["date"], pj[OBSERVATIONS][obs_id] ["description"], subjects, ", ".join(media_files) ] + indep_var) if output_format in ["tsv", "csv", "html"]: try: with open(file_name, "wb") as f: f.write(str.encode(data.export(output_format))) except Exception: return False if output_format in ["ods", "xlsx", "xls"]: try: with open(file_name, "wb") as f: f.write(data.export(output_format)) except Exception: return False return True
def export_aggregated_events(pj: dict, parameters: dict, obsId: str): """ export aggregated events Args: pj (dict): BORIS project parameters (dict): subjects, behaviors obsId (str): observation id Returns: tablib.Dataset: """ logging.debug(f"function: export aggregated events {parameters} {obsId}") interval = parameters["time"] start_time = parameters[START_TIME] end_time = parameters[END_TIME] data = tablib.Dataset() observation = pj[OBSERVATIONS][obsId] # obs description obs_description = observation["description"] duration1 = [] # in seconds if observation[TYPE] in [MEDIA]: try: for mediaFile in observation[FILE][PLAYER1]: if MEDIA_INFO in observation: duration1.append(observation[MEDIA_INFO]["length"][mediaFile]) except Exception: duration1 = [] obs_length = project_functions.observation_total_length(pj[OBSERVATIONS][obsId]) if obs_length == Decimal("-1"): # media length not available interval = TIME_EVENTS logging.debug(f"obs_length: {obs_length}") ok, msg, connector = db_functions.load_aggregated_events_in_db(pj, parameters[SELECTED_SUBJECTS], [obsId], parameters[SELECTED_BEHAVIORS]) if connector is None: logging.critical(f"error when loading aggregated events in DB") return data # time cursor = connector.cursor() if interval == TIME_FULL_OBS: min_time = float(0) max_time = float(obs_length) if interval == TIME_EVENTS: try: min_time = float(pj[OBSERVATIONS][obsId][EVENTS][0][0]) except Exception: min_time = float(0) try: max_time = float(pj[OBSERVATIONS][obsId][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, obsId, min_time, min_time, max_time, )) cursor.execute("UPDATE aggregated_events SET stop = ? WHERE observation = ? AND stop > ? AND start BETWEEN ? AND ?", (max_time, obsId, max_time, min_time, max_time, )) cursor.execute("UPDATE aggregated_events SET start = ?, stop = ? WHERE observation = ? AND start < ? AND stop > ?", (min_time, max_time, obsId, min_time, max_time, )) cursor.execute("DELETE FROM aggregated_events WHERE observation = ? AND (start < ? AND stop < ?) OR (start > ? AND stop > ?)", (obsId, min_time, min_time, max_time, max_time, )) behavioral_category = project_functions.behavior_category(pj[ETHOGRAM]) for subject in parameters[SELECTED_SUBJECTS]: for behavior in parameters[SELECTED_BEHAVIORS]: cursor.execute("SELECT distinct modifiers FROM aggregated_events where subject=? AND behavior=? order by modifiers", (subject, behavior,)) rows_distinct_modifiers = list(x[0] for x in cursor.fetchall()) for distinct_modifiers in rows_distinct_modifiers: cursor.execute(("SELECT start, stop, type, modifiers, comment, comment_stop FROM aggregated_events " "WHERE subject = ? AND behavior = ? AND modifiers = ? ORDER by start"), (subject, behavior, distinct_modifiers)) rows = list(cursor.fetchall()) for row in rows: if observation[TYPE] in [MEDIA]: if duration1: mediaFileIdx = [idx1 for idx1, x in enumerate(duration1) if row["start"] >= sum(duration1[0:idx1])][-1] mediaFileString = observation[FILE][PLAYER1][mediaFileIdx] try: fpsString = observation[MEDIA_INFO]["fps"][observation[FILE][PLAYER1][mediaFileIdx]] except Exception: fpsString = "NA" else: try: if len(observation[FILE][PLAYER1]) == 1: mediaFileString = observation[FILE][PLAYER1][0] else: mediaFileString = "NA" except Exception: mediaFileString = "NA" fpsString = "NA" if observation[TYPE] in [LIVE]: mediaFileString = "LIVE" fpsString = "NA" if row["type"] == POINT: row_data = [] row_data.extend([obsId, observation["date"].replace("T", " "), obs_description, mediaFileString, f"{obs_length:.3f}" if obs_length != Decimal("-1") else "NA", fpsString]) # independent variables if INDEPENDENT_VARIABLES in pj: for idx_var in utilities.sorted_keys(pj[INDEPENDENT_VARIABLES]): if pj[INDEPENDENT_VARIABLES][idx_var]["label"] in observation[INDEPENDENT_VARIABLES]: row_data.append(observation[INDEPENDENT_VARIABLES][pj[INDEPENDENT_VARIABLES][idx_var]["label"]]) else: row_data.append("") row_data.extend([subject, behavior, behavioral_category[behavior], row["modifiers"], POINT, f"{row['start']:.3f}", # start f"{row['stop']:.3f}", # stop "NA", # duration row["comment"], "" ]) data.append(row_data) if row["type"] == STATE: if idx % 2 == 0: row_data = [] row_data.extend([obsId, observation["date"].replace("T", " "), obs_description, mediaFileString, f"{obs_length:.3f}" if obs_length != Decimal("-1") else "NA", fpsString]) # independent variables if INDEPENDENT_VARIABLES in pj: for idx_var in utilities.sorted_keys(pj[INDEPENDENT_VARIABLES]): if pj[INDEPENDENT_VARIABLES][idx_var]["label"] in observation[INDEPENDENT_VARIABLES]: row_data.append(observation[INDEPENDENT_VARIABLES][pj[INDEPENDENT_VARIABLES][idx_var]["label"]]) else: row_data.append("") row_data.extend([subject, behavior, behavioral_category[behavior], row["modifiers"], STATE, f"{row['start']:.3f}", f"{row['stop']:.3f}", f"{row['stop'] - row['start']:.3f}", row["comment"], row["comment_stop"] ]) data.append(row_data) return data
def select_observations(pj: dict, mode: str, windows_title: str = "") -> tuple: """ allow user to select observations mode: accepted values: OPEN, EDIT, SINGLE, MULTIPLE, SELECT1 Args: pj (dict): BORIS project dictionary mode (str): mode foe selection: OPEN, EDIT, SINGLE, MULTIPLE, SELECT1 windows_title (str): title for windows Returns: str: selected mode: OPEN, EDIT, VIEW list: list of selected observations """ obsListFields = ["id", "date", "description", "subjects", "media"] indepVarHeader, column_type = [], [TEXT] * len(obsListFields) if INDEPENDENT_VARIABLES in pj: for idx in utilities.sorted_keys(pj[INDEPENDENT_VARIABLES]): indepVarHeader.append(pj[INDEPENDENT_VARIABLES][idx]["label"]) column_type.append(pj[INDEPENDENT_VARIABLES][idx]["type"]) data = [] for obs in sorted(list(pj[OBSERVATIONS].keys())): date = pj[OBSERVATIONS][obs]["date"].replace("T", " ") descr = utilities.eol2space(pj[OBSERVATIONS][obs][DESCRIPTION]) # subjects observedSubjects = [ NO_FOCAL_SUBJECT if x == "" else x for x in project_functions.extract_observed_subjects(pj, [obs]) ] ''' removed 2020-01-13 if "" in observedSubjects: observedSubjects.remove("") ''' subjectsList = ", ".join(observedSubjects) mediaList = [] if pj[OBSERVATIONS][obs][TYPE] in [MEDIA]: if pj[OBSERVATIONS][obs][FILE]: for player in sorted(pj[OBSERVATIONS][obs][FILE].keys()): for media in pj[OBSERVATIONS][obs][FILE][player]: mediaList.append(f"#{player}: {media}") if len(mediaList) > 8: media = " ".join(mediaList) else: media = "\n".join(mediaList) elif pj[OBSERVATIONS][obs][TYPE] in [LIVE]: media = LIVE # independent variables indepvar = [] if INDEPENDENT_VARIABLES in pj[OBSERVATIONS][obs]: for var_label in indepVarHeader: if var_label in pj[OBSERVATIONS][obs][INDEPENDENT_VARIABLES]: indepvar.append(pj[OBSERVATIONS][obs] [INDEPENDENT_VARIABLES][var_label]) else: indepvar.append("") data.append([obs, date, descr, subjectsList, media] + indepvar) obsList = observations_list.observationsList_widget( data, header=obsListFields + indepVarHeader, column_type=column_type) if windows_title: obsList.setWindowTitle(windows_title) obsList.pbOpen.setVisible(False) obsList.pbView.setVisible(False) obsList.pbEdit.setVisible(False) obsList.pbOk.setVisible(False) obsList.pbSelectAll.setVisible(False) obsList.pbUnSelectAll.setVisible(False) obsList.mode = mode if mode == OPEN: obsList.view.setSelectionMode(QAbstractItemView.SingleSelection) obsList.pbOpen.setVisible(True) if mode == VIEW: obsList.view.setSelectionMode(QAbstractItemView.SingleSelection) obsList.pbView.setVisible(True) if mode == EDIT: obsList.view.setSelectionMode(QAbstractItemView.SingleSelection) obsList.pbEdit.setVisible(True) if mode == SINGLE: obsList.view.setSelectionMode(QAbstractItemView.SingleSelection) obsList.pbOpen.setVisible(True) obsList.pbView.setVisible(True) obsList.pbEdit.setVisible(True) if mode == MULTIPLE: obsList.view.setSelectionMode(QAbstractItemView.MultiSelection) obsList.pbOk.setVisible(True) obsList.pbSelectAll.setVisible(True) obsList.pbUnSelectAll.setVisible(True) if mode == SELECT1: obsList.view.setSelectionMode(QAbstractItemView.SingleSelection) obsList.pbOk.setVisible(True) obsList.resize(900, 600) obsList.view.sortItems(0, Qt.AscendingOrder) for row in range(obsList.view.rowCount()): obsList.view.resizeRowToContents(row) selectedObs = [] result = obsList.exec_() if result: if obsList.view.selectedIndexes(): for idx in obsList.view.selectedIndexes(): if idx.column() == 0: # first column selectedObs.append(idx.data()) if result == 0: # cancel resultStr = "" if result == 1: # select resultStr = "ok" if result == 2: # open resultStr = OPEN if result == 3: # edit resultStr = EDIT if result == 4: # view resultStr = VIEW return resultStr, selectedObs