Ejemplo n.º 1
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())
Ejemplo n.º 2
0
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:
        dialog.error_message(sys._getframe().f_code.co_name, sys.exc_info())
Ejemplo n.º 3
0
def export_events_jwatcher(parameters: list,
                           obsId: str,
                           observation: list,
                           ethogram: dict,
                           file_name: str,
                           output_format: str):
    """
    export events jwatcher .dat format

    Args:
        parameters (dict): subjects, behaviors
        obsId (str): observation id
        observation (dict): observation
        ethogram (dict): ethogram of project
        file_name (str): file name for exporting events
        output_format (str): Not used for compatibility with export_events function

    Returns:
        bool: result: True if OK else False
        str: error message
    """
    try:
        for subject in parameters["selected subjects"]:

            # select events for current subject
            events = []
            for event in observation[EVENTS]:
                if event[SUBJECT_EVENT_FIELD] == subject or (subject == "No focal subject" and event[SUBJECT_EVENT_FIELD] == ""):
                    events.append(event)

            if not events:
                continue

            total_length = 0   # in seconds
            if observation[EVENTS]:
                total_length = observation[EVENTS][-1][0] - observation[EVENTS][0][0]  # last event time - first event time

            file_name_subject = str(pathlib.Path(file_name).parent / pathlib.Path(file_name).stem) + "_" + subject + ".dat"

            rows = ["FirstLineOfData"]  # to be completed
            rows.append("#-----------------------------------------------------------")
            rows.append(f"# Name: {pathlib.Path(file_name_subject).name}")
            rows.append("# Format: Focal Data File 1.0")
            rows.append(f"# Updated: {datetime.datetime.now().isoformat()}")
            rows.append("#-----------------------------------------------------------")
            rows.append("")
            rows.append(f"FocalMasterFile={pathlib.Path(file_name_subject).with_suffix('.fmf')}")
            rows.append("")

            rows.append(f"# Observation started: {observation['date']}")
            try:
                start_time = datetime.datetime.strptime(observation["date"], '%Y-%m-%dT%H:%M:%S')
            except ValueError:
                start_time = datetime.datetime(1970, 1, 1, 0, 0)
            start_time_epoch = int((start_time - datetime.datetime(1970, 1, 1, 0, 0)).total_seconds() * 1000)
            rows.append(f"StartTime={start_time_epoch}")

            stop_time = (start_time + datetime.timedelta(seconds=float(total_length))).isoformat()
            stop_time_epoch = int(start_time_epoch + float(total_length) * 1000)

            rows.append(f"# Observation stopped: {stop_time}")
            rows.append(f"StopTime={stop_time_epoch}")

            rows.extend([""] * 3)
            rows.append("#BEGIN DATA")
            rows[0] = f"FirstLineOfData={len(rows) + 1}"

            all_observed_behaviors = []
            mem_number_of_state_events = {}
            for event in events:
                behav_code = event[EVENT_BEHAVIOR_FIELD_IDX]

                try:
                    behavior_key = [ethogram[k][BEHAVIOR_KEY] for k in ethogram if ethogram[k][BEHAVIOR_CODE] == behav_code][0]
                except Exception:
                    # coded behavior not defined in ethogram
                    continue
                if [ethogram[k][TYPE] for k in ethogram if ethogram[k][BEHAVIOR_CODE] == behav_code] == [STATE_EVENT]:
                    if behav_code in mem_number_of_state_events:
                        mem_number_of_state_events[behav_code] += 1
                    else:
                        mem_number_of_state_events[behav_code] = 1
                    # skip the STOP event in case of STATE
                    if mem_number_of_state_events[behav_code] % 2 == 0:
                        continue

                rows.append(f"{int(event[EVENT_TIME_FIELD_IDX] * 1000)}, {behavior_key}")
                if (event[EVENT_BEHAVIOR_FIELD_IDX], behavior_key) not in all_observed_behaviors:
                    all_observed_behaviors.append((event[EVENT_BEHAVIOR_FIELD_IDX], behavior_key))

            rows.append(f"{int(events[-1][0] * 1000)}, EOF\n")

            try:
                with open(file_name_subject, "w") as f_out:
                    f_out.write("\n".join(rows))
            except Exception:
                return False, f"File DAT not created for subject {subject}: {sys.exc_info()[1]}"

            # create fmf file
            fmf_file_path = pathlib.Path(file_name_subject).with_suffix(".fmf")
            fmf_creation_answer = ""
            if fmf_file_path.exists():
                fmf_creation_answer = dialog.MessageDialog(
                    programName,
                    (f"The {fmf_file_path} file already exists.<br>"
                     "What do you want to do?"),
                    [OVERWRITE, "Skip file creation", CANCEL])

                if fmf_creation_answer == CANCEL:
                    return True, ""

            rows = []
            rows.append("#-----------------------------------------------------------")
            rows.append(f"# Name: {pathlib.Path(file_name_subject).with_suffix('.fmf').name}")
            rows.append("# Format: Focal Master File 1.0")
            rows.append(f"# Updated: {datetime.datetime.now().isoformat()}")
            rows.append("#-----------------------------------------------------------")
            for (behav, key) in all_observed_behaviors:
                rows.append(f"Behaviour.name.{key}={behav}")
                behav_description = [ethogram[k][DESCRIPTION] for k in ethogram if ethogram[k][BEHAVIOR_CODE] == behav][0]
                rows.append(f"Behaviour.description.{key}={behav_description}")

            rows.append(f"DurationMilliseconds={int(float(total_length) * 1000)}")
            rows.append("CountUp=false")
            rows.append("Question.1=")
            rows.append("Question.2=")
            rows.append("Question.3=")
            rows.append("Question.4=")
            rows.append("Question.5=")
            rows.append("Question.6=")
            rows.append("Notes=")
            rows.append("Supplementary=\n")

            if fmf_creation_answer == OVERWRITE or fmf_creation_answer == "":
                try:
                    with open(fmf_file_path, "w") as f_out:
                        f_out.write("\n".join(rows))
                except Exception:
                    return False, f"File FMF not created: {sys.exc_info()[1]}"

            # create FAF file
            faf_file_path = pathlib.Path(file_name_subject).with_suffix(".faf")
            faf_creation_answer = ""
            if faf_file_path.exists():
                faf_creation_answer = dialog.MessageDialog(programName,
                                                           (f"The {faf_file_path} file already exists.<br>"
                                                            "What do you want to do?"),
                                                           [OVERWRITE, "Skip file creation", CANCEL])
                if faf_creation_answer == CANCEL:
                    return True, ""

            rows = []
            rows.append("#-----------------------------------------------------------")
            rows.append("# Name: {}".format(pathlib.Path(file_name_subject).with_suffix(".faf").name))
            rows.append("# Format: Focal Analysis Master File 1.0")
            rows.append("# Updated: {}".format(datetime.datetime.now().isoformat()))
            rows.append("#-----------------------------------------------------------")
            rows.append("FocalMasterFile={}".format(str(pathlib.Path(file_name_subject).with_suffix(".fmf"))))
            rows.append("")
            rows.append("TimeBinDuration=0.0")
            rows.append("EndWithLastCompleteBin=true")
            rows.append("")
            rows.append("ScoreFromBeginning=true")
            rows.append("ScoreFromBehavior=false")
            rows.append("ScoreFromFirstBehavior=false")
            rows.append("ScoreFromOffset=false")
            rows.append("")
            rows.append("Offset=0.0")
            rows.append("BehaviorToScoreFrom=")
            rows.append("")
            rows.append("OutOfSightCode=")
            rows.append("")
            rows.append("Report.StateNaturalInterval.Occurrence=false")
            rows.append("Report.StateNaturalInterval.TotalTime=false")
            rows.append("Report.StateNaturalInterval.Average=false")
            rows.append("Report.StateNaturalInterval.StandardDeviation=false")
            rows.append("Report.StateNaturalInterval.ProportionOfTime=false")
            rows.append("Report.StateNaturalInterval.ProportionOfTimeInSight=false")
            rows.append("Report.StateNaturalInterval.ConditionalProportionOfTime=false")
            rows.append("")
            rows.append("Report.StateNaturalDuration.Occurrence=false")
            rows.append("Report.StateNaturalDuration.TotalTime=false")
            rows.append("Report.StateNaturalDuration.Average=false")
            rows.append("Report.StateNaturalDuration.StandardDeviation=false")
            rows.append("Report.StateNaturalDuration.ProportionOfTime=false")
            rows.append("Report.StateNaturalDuration.ProportionOfTimeInSight=false")
            rows.append("Report.StateNaturalDuration.ConditionalProportionOfTime=false")
            rows.append("")
            rows.append("Report.StateAllInterval.Occurrence=false")
            rows.append("Report.StateAllInterval.TotalTime=false")
            rows.append("Report.StateAllInterval.Average=false")
            rows.append("Report.StateAllInterval.StandardDeviation=false")
            rows.append("Report.StateAllInterval.ProportionOfTime=false")
            rows.append("Report.StateAllInterval.ProportionOfTimeInSight=false")
            rows.append("Report.StateAllInterval.ConditionalProportionOfTime=false")
            rows.append("")
            rows.append("Report.StateAllDuration.Occurrence=true")
            rows.append("Report.StateAllDuration.TotalTime=true")
            rows.append("Report.StateAllDuration.Average=true")
            rows.append("Report.StateAllDuration.StandardDeviation=false")
            rows.append("Report.StateAllDuration.ProportionOfTime=false")
            rows.append("Report.StateAllDuration.ProportionOfTimeInSight=true")
            rows.append("Report.StateAllDuration.ConditionalProportionOfTime=false")
            rows.append("")
            rows.append("Report.EventNaturalInterval.EventCount=false")
            rows.append("Report.EventNaturalInterval.Occurrence=false")
            rows.append("Report.EventNaturalInterval.Average=false")
            rows.append("Report.EventNaturalInterval.StandardDeviation=false")
            rows.append("Report.EventNaturalInterval.ConditionalNatEventCount=false")
            rows.append("Report.EventNaturalInterval.ConditionalNatRate=false")
            rows.append("Report.EventNaturalInterval.ConditionalNatIntervalOccurance=false")
            rows.append("Report.EventNaturalInterval.ConditionalNatIntervalAverage=false")
            rows.append("Report.EventNaturalInterval.ConditionalNatIntervalStandardDeviation=false")
            rows.append("Report.EventNaturalInterval.ConditionalAllEventCount=false")
            rows.append("Report.EventNaturalInterval.ConditionalAllRate=false")
            rows.append("Report.EventNaturalInterval.ConditionalAllIntervalOccurance=false")
            rows.append("Report.EventNaturalInterval.ConditionalAllIntervalAverage=false")
            rows.append("Report.EventNaturalInterval.ConditionalAllIntervalStandardDeviation=false")
            rows.append("")
            rows.append("AllCodesMutuallyExclusive=true")
            rows.append("")

            for (behav, key) in all_observed_behaviors:
                rows.append(f"Behavior.isModified.{key}=false")
                rows.append(f"Behavior.isSubtracted.{key}=false")
                rows.append(f"Behavior.isIgnored.{key}=false")
                rows.append(f"Behavior.isEventAnalyzed.{key}=false")
                rows.append(f"Behavior.switchesOff.{key}=")
                rows.append("")

            if faf_creation_answer == "" or faf_creation_answer == OVERWRITE:
                try:
                    with open(pathlib.Path(file_name_subject).with_suffix(".faf"), "w") as f_out:
                        f_out.write("\n".join(rows))
                except Exception:
                    return False, f"File FAF not created: {sys.exc_info()[1]}"

        return True, ""

    except Exception:
        logging.critical("Error during exporting the events for JWatcher")
        dialog.error_message("exporting the events for JWatcher", sys.exc_info())
        return False, ""
Ejemplo n.º 4
0
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())
Ejemplo n.º 5
0
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:
            dialog.error_message(sys._getframe().f_code.co_name,
                                 sys.exc_info())
Ejemplo n.º 6
0
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())
Ejemplo n.º 7
0
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())
Ejemplo n.º 8
0
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:
        dialog.error_message(sys._getframe().f_code.co_name, sys.exc_info())
Ejemplo n.º 9
0
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:
        dialog.error_message(sys._getframe().f_code.co_name, sys.exc_info())
def time_budget_analysis(ethogram: dict,
                         cursor,
                         selected_observations: list,
                         parameters: dict,
                         by_category: bool = False):
    """
    extract number of occurrences, total duration, mean ...
    if start_time = 0 and end_time = 0 all events are extracted

    Args:
        ethogram (dict): project ethogram
        cursor: cursor on temporary database
        selected_observations (list): selected observations
        parameters (dict): parameters for analysis
        by_category (bool): True for grouping in category else False

    Returns:
        list: results
        dict:
    """

    try:
        categories, out = {}, []
        for subject in parameters[SELECTED_SUBJECTS]:
            out_cat, categories[subject] = [], {}

            for behavior in parameters[SELECTED_BEHAVIORS]:

                if parameters[INCLUDE_MODIFIERS]:

                    cursor.execute(
                        "SELECT DISTINCT modifiers FROM events WHERE subject = ? AND code = ?",
                        (subject, behavior))
                    distinct_modifiers = list(cursor.fetchall())

                    if not distinct_modifiers:
                        if not parameters[EXCLUDE_BEHAVIORS]:

                            if STATE in project_functions.event_type(
                                    behavior, ethogram):

                                out.append({
                                    "subject": subject,
                                    "behavior": behavior,
                                    "modifiers": "",
                                    "duration": 0,
                                    "duration_mean": 0,
                                    "duration_stdev": "NA",
                                    "number": "0",
                                    "inter_duration_mean": "NA",
                                    "inter_duration_stdev": "NA"
                                })
                            else:  # point
                                out.append({
                                    "subject": subject,
                                    "behavior": behavior,
                                    "modifiers": "",
                                    "duration": 0,
                                    "duration_mean": 0,
                                    "duration_stdev": "NA",
                                    "number": "0",
                                    "inter_duration_mean": "NA",
                                    "inter_duration_stdev": "NA"
                                })
                        continue

                    if POINT in project_functions.event_type(
                            behavior, ethogram):

                        for modifier in distinct_modifiers:
                            cursor.execute(
                                ("SELECT occurence, observation FROM events "
                                 "WHERE subject = ? "
                                 "AND code = ? "
                                 "AND modifiers = ? "
                                 "ORDER BY observation, occurence"),
                                (subject, behavior, modifier[0]))

                            rows = cursor.fetchall()

                            # inter events duration
                            all_event_interdurations = []
                            for idx, row in enumerate(rows):
                                if idx and row[1] == rows[idx - 1][1]:
                                    all_event_interdurations.append(
                                        float(row[0]) -
                                        float(rows[idx - 1][0]))

                            out_cat.append({
                                "subject":
                                subject,
                                "behavior":
                                behavior,
                                "modifiers":
                                modifier[0],
                                "duration":
                                NA,
                                "duration_mean":
                                NA,
                                "duration_stdev":
                                NA,
                                "number":
                                len(rows),
                                "inter_duration_mean":
                                round(
                                    statistics.mean(all_event_interdurations),
                                    3)
                                if len(all_event_interdurations) else NA,
                                "inter_duration_stdev":
                                round(
                                    statistics.stdev(all_event_interdurations),
                                    3)
                                if len(all_event_interdurations) > 1 else NA
                            })

                    if STATE in project_functions.event_type(
                            behavior, ethogram):

                        for modifier in distinct_modifiers:
                            cursor.execute(
                                ("SELECT occurence, observation FROM events "
                                 "WHERE subject = ? "
                                 "AND code = ? "
                                 "AND modifiers = ? "
                                 "ORDER BY observation, occurence"),
                                (subject, behavior, modifier[0]))

                            rows = list(cursor.fetchall())
                            if len(rows) % 2:
                                out.append({
                                    "subject": subject,
                                    "behavior": behavior,
                                    "modifiers": modifier[0],
                                    "duration": UNPAIRED,
                                    "duration_mean": UNPAIRED,
                                    "duration_stdev": UNPAIRED,
                                    "number": UNPAIRED,
                                    "inter_duration_mean": UNPAIRED,
                                    "inter_duration_stdev": UNPAIRED
                                })
                            else:
                                all_event_durations, all_event_interdurations = [], []
                                for idx, row in enumerate(rows):
                                    # event
                                    if idx % 2 == 0:
                                        new_init, new_end = float(
                                            row[0]), float(rows[idx + 1][0])

                                        all_event_durations.append(new_end -
                                                                   new_init)

                                    # inter event if same observation
                                    if idx % 2 and idx != len(
                                            rows) - 1 and row[1] == rows[idx +
                                                                         1][1]:
                                        if (parameters["start time"] <= row[0]
                                                <= parameters["end time"]
                                                and parameters["start time"] <=
                                                rows[idx + 1][0] <=
                                                parameters["end time"]):
                                            all_event_interdurations.append(
                                                float(rows[idx + 1][0]) -
                                                float(row[0]))

                                out_cat.append({
                                    "subject":
                                    subject,
                                    "behavior":
                                    behavior,
                                    "modifiers":
                                    modifier[0],
                                    "duration":
                                    round(sum(all_event_durations), 3),
                                    "duration_mean":
                                    round(statistics.mean(all_event_durations),
                                          3)
                                    if len(all_event_durations) else "NA",
                                    "duration_stdev":
                                    round(
                                        statistics.stdev(all_event_durations),
                                        3)
                                    if len(all_event_durations) > 1 else "NA",
                                    "number":
                                    len(all_event_durations),
                                    "inter_duration_mean":
                                    round(
                                        statistics.mean(
                                            all_event_interdurations), 3)
                                    if len(all_event_interdurations) else "NA",
                                    "inter_duration_stdev":
                                    round(
                                        statistics.stdev(
                                            all_event_interdurations), 3) if
                                    len(all_event_interdurations) > 1 else "NA"
                                })

                else:  # no modifiers

                    if POINT in project_functions.event_type(
                            behavior, ethogram):

                        cursor.execute((
                            "SELECT occurence,observation FROM events "
                            "WHERE subject = ? AND code = ? ORDER BY observation, occurence"
                        ), (subject, behavior))

                        rows = list(cursor.fetchall())

                        if len(selected_observations) == 1:
                            new_rows = []
                            for occurence, observation in rows:
                                new_occurence = max(
                                    float(parameters["start time"]), occurence)
                                new_occurence = min(
                                    new_occurence,
                                    float(parameters["end time"]))
                                new_rows.append([new_occurence, observation])
                            rows = list(new_rows)

                        # include behaviors without events
                        if not len(rows):
                            if not parameters[EXCLUDE_BEHAVIORS]:
                                out.append({
                                    "subject": subject,
                                    "behavior": behavior,
                                    "modifiers": "",
                                    "duration": NA,
                                    "duration_mean": NA,
                                    "duration_stdev": NA,
                                    "number": "0",
                                    "inter_duration_mean": NA,
                                    "inter_duration_stdev": NA
                                })
                            continue

                        # inter events duration
                        all_event_interdurations = []
                        for idx, row in enumerate(rows):
                            if idx and row[1] == rows[idx - 1][1]:
                                all_event_interdurations.append(
                                    float(row[0]) - float(rows[idx - 1][0]))

                        out_cat.append({
                            "subject":
                            subject,
                            "behavior":
                            behavior,
                            "modifiers":
                            "",
                            "duration":
                            NA,
                            "duration_mean":
                            NA,
                            "duration_stdev":
                            NA,
                            "number":
                            len(rows),
                            "inter_duration_mean":
                            round(statistics.mean(all_event_interdurations), 3)
                            if len(all_event_interdurations) else NA,
                            "inter_duration_stdev":
                            round(statistics.stdev(all_event_interdurations),
                                  3)
                            if len(all_event_interdurations) > 1 else NA
                        })

                    if STATE in project_functions.event_type(
                            behavior, ethogram):

                        cursor.execute((
                            "SELECT occurence, observation FROM events "
                            "WHERE subject = ? AND code = ? ORDER BY observation, occurence"
                        ), (subject, behavior))

                        rows = list(cursor.fetchall())
                        if not len(rows):
                            if not parameters[
                                    EXCLUDE_BEHAVIORS]:  # include behaviors without events
                                out.append({
                                    "subject": subject,
                                    "behavior": behavior,
                                    "modifiers": "",
                                    "duration": 0,
                                    "duration_mean": 0,
                                    "duration_stdev": "NA",
                                    "number": 0,
                                    "inter_duration_mean": "-",
                                    "inter_duration_stdev": "-"
                                })
                            continue

                        if len(rows) % 2:
                            out.append({
                                "subject": subject,
                                "behavior": behavior,
                                "modifiers": "",
                                "duration": UNPAIRED,
                                "duration_mean": UNPAIRED,
                                "duration_stdev": UNPAIRED,
                                "number": UNPAIRED,
                                "inter_duration_mean": UNPAIRED,
                                "inter_duration_stdev": UNPAIRED
                            })
                        else:
                            all_event_durations, all_event_interdurations = [], []
                            for idx, row in enumerate(rows):
                                # event
                                if idx % 2 == 0:
                                    new_init, new_end = float(row[0]), float(
                                        rows[idx + 1][0])

                                    all_event_durations.append(new_end -
                                                               new_init)

                                # inter event if same observation
                                if idx % 2 and idx != len(rows) - 1 and row[
                                        1] == rows[idx + 1][1]:
                                    if (parameters["start time"] <= row[0] <=
                                            parameters["end time"]
                                            and parameters["start time"] <=
                                            rows[idx + 1][0] <=
                                            parameters["end time"]):
                                        all_event_interdurations.append(
                                            float(rows[idx + 1][0]) -
                                            float(row[0]))

                            out_cat.append({
                                "subject":
                                subject,
                                "behavior":
                                behavior,
                                "modifiers":
                                "",
                                "duration":
                                round(sum(all_event_durations), 3),
                                "duration_mean":
                                round(statistics.mean(all_event_durations), 3)
                                if len(all_event_durations) else NA,
                                "duration_stdev":
                                round(statistics.stdev(all_event_durations), 3)
                                if len(all_event_durations) > 1 else NA,
                                "number":
                                len(all_event_durations),
                                "inter_duration_mean":
                                round(
                                    statistics.mean(all_event_interdurations),
                                    3)
                                if len(all_event_interdurations) else NA,
                                "inter_duration_stdev":
                                round(
                                    statistics.stdev(all_event_interdurations),
                                    3)
                                if len(all_event_interdurations) > 1 else NA
                            })

            out += out_cat

            if by_category:  # and flagCategories:

                for behav in out_cat:

                    try:
                        category = [
                            ethogram[x]["category"] for x in ethogram
                            if "category" in ethogram[x]
                            and ethogram[x]["code"] == behav['behavior']
                        ][0]
                    except Exception:
                        category = ""

                    if category in categories[subject]:
                        if behav["duration"] not in ["-", "NA"] and categories[
                                subject][category]["duration"] not in [
                                    "-", "NA"
                                ]:
                            categories[subject][category]["duration"] += behav[
                                "duration"]
                        else:
                            categories[subject][category]["duration"] = "-"
                        categories[subject][category]["number"] += behav[
                            "number"]
                    else:
                        categories[subject][category] = {
                            "duration": behav["duration"],
                            "number": behav["number"]
                        }

        out_sorted = []
        for subject in parameters[SELECTED_SUBJECTS]:
            for behavior in parameters[SELECTED_BEHAVIORS]:
                for row in out:
                    if row["subject"] == subject and row[
                            "behavior"] == behavior:
                        out_sorted.append(row)

        # http://stackoverflow.com/questions/673867/python-arbitrary-order-by
        return out_sorted, categories

    except Exception:
        dialog.error_message("time_budget", sys.exc_info())

        return [], []
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 = ["Observations id", "Total length (s)"]
        subj_header, behav_header, modif_header = [""] * len(param_header), [
            ""
        ] * len(param_header), [""] * len(param_header)
        subj_header[1] = "Subjects:"
        behav_header[1] = "Behaviors:"
        modif_header[1] = "Modifiers:"

        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:
        dialog.error_message("synthetic_time_budget", sys.exc_info())

        return (False, msg, tablib.Dataset())

    return True, msg, data_report
def synthetic_time_budget_bin(pj: dict, selected_observations: list,
                              parameters_obs: dict):
    """
    create a synthetic time budget divised in time bin

    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
    """
    def interval_len(interval):
        if interval.empty:
            return dec(0)
        else:
            return sum([x.upper - x.lower for x in interval])

    def interval_number(interval):
        if interval.empty:
            return dec(0)
        else:
            return len(interval)

    def interval_mean(interval):
        if interval.empty:
            return dec(0)
        else:
            return sum([x.upper - x.lower for x in interval]) / len(interval)

    def interval_std_dev(interval):
        if interval.empty:
            return "NA"
        else:
            try:
                return round(
                    statistics.stdev([x.upper - x.lower for x in interval]), 3)
            except:
                return "NA"

    try:
        selected_subjects = parameters_obs[SELECTED_SUBJECTS]
        selected_behaviors = parameters_obs[SELECTED_BEHAVIORS]
        include_modifiers = parameters_obs[INCLUDE_MODIFIERS]
        time_interval = parameters_obs["time"]
        start_time = parameters_obs[START_TIME]
        end_time = parameters_obs[END_TIME]
        time_bin_size = dec(parameters_obs[TIME_BIN_SIZE])

        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 with time bin"

        distinct_behav_modif = []
        for obs_id in selected_observations:
            for event in pj[OBSERVATIONS][obs_id][EVENTS]:
                if include_modifiers:
                    if (event[EVENT_BEHAVIOR_FIELD_IDX],
                            event[EVENT_MODIFIER_FIELD_IDX]
                        ) not in distinct_behav_modif:
                        distinct_behav_modif.append(
                            (event[EVENT_BEHAVIOR_FIELD_IDX],
                             event[EVENT_MODIFIER_FIELD_IDX]))
                else:
                    if (event[EVENT_BEHAVIOR_FIELD_IDX],
                            "") not in distinct_behav_modif:
                        distinct_behav_modif.append(
                            (event[EVENT_BEHAVIOR_FIELD_IDX], ""))

        distinct_behav_modif.sort()
        '''
        print("distinct_behav_modif", distinct_behav_modif)
        '''

        # 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, ""])
        '''
        print("distinct_behav_modif with not observed behav", distinct_behav_modif)
        '''

        behaviors = init_behav_modif_bin(pj[ETHOGRAM], selected_subjects,
                                         distinct_behav_modif,
                                         include_modifiers, parameters)
        '''
        print("init behaviors", behaviors)

        print(f"selected subjects: {selected_subjects}")
        '''

        param_header = [
            "Observations id", "Total length (s)", "Time interval (s)"
        ]
        subj_header, behav_header, modif_header = [""] * len(param_header), [
            ""
        ] * len(param_header), [""] * len(param_header)
        subj_header[1] = "Subjects:"
        behav_header[1] = "Behaviors:"
        modif_header[1] = "Modifiers:"

        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])

        data_report.append(subj_header)
        data_report.append(behav_header)
        if include_modifiers:
            data_report.append(modif_header)
        data_report.append(param_header)

        state_events_list = [
            pj[ETHOGRAM][x][BEHAVIOR_CODE] for x in pj[ETHOGRAM]
            if STATE in pj[ETHOGRAM][x][TYPE].upper()
        ]
        # select time interval
        for obs_id in selected_observations:

            obs_length = project_functions.observation_total_length(
                pj[OBSERVATIONS][obs_id])

            if obs_length == -1:
                obs_length = 0
            if time_interval == TIME_FULL_OBS:
                min_time = dec(0)
                max_time = dec(obs_length)

            if time_interval == TIME_EVENTS:
                try:
                    min_time = dec(pj[OBSERVATIONS][obs_id][EVENTS][0][0])
                except Exception:
                    min_time = dec(0)
                try:
                    max_time = dec(pj[OBSERVATIONS][obs_id][EVENTS][-1][0])
                except Exception:
                    max_time = dec(obs_length)

            if time_interval == TIME_ARBITRARY_INTERVAL:
                min_time = dec(start_time)
                max_time = dec(end_time)

            #print("observation:", obs_id)
            events_interval = {}
            mem_events_interval = {}

            for event in pj[OBSERVATIONS][obs_id][EVENTS]:
                if event[EVENT_SUBJECT_FIELD_IDX] == "":
                    current_subject = NO_FOCAL_SUBJECT
                else:
                    current_subject = event[EVENT_SUBJECT_FIELD_IDX]

                if current_subject not in selected_subjects:
                    continue
                if current_subject not in events_interval:
                    events_interval[current_subject] = {}
                    mem_events_interval[current_subject] = {}

                if include_modifiers:
                    modif = event[EVENT_MODIFIER_FIELD_IDX]
                else:
                    modif = ""
                if (event[EVENT_BEHAVIOR_FIELD_IDX],
                        modif) not in distinct_behav_modif:
                    continue

                if (event[EVENT_BEHAVIOR_FIELD_IDX],
                        modif) not in events_interval[current_subject]:
                    events_interval[current_subject][(
                        event[EVENT_BEHAVIOR_FIELD_IDX], modif)] = I.empty()
                    mem_events_interval[current_subject][(
                        event[EVENT_BEHAVIOR_FIELD_IDX], modif)] = []

                if event[EVENT_BEHAVIOR_FIELD_IDX] in state_events_list:
                    mem_events_interval[current_subject][(
                        event[EVENT_BEHAVIOR_FIELD_IDX],
                        modif)].append(event[EVENT_TIME_FIELD_IDX])
                    if len(mem_events_interval[current_subject][(
                            event[EVENT_BEHAVIOR_FIELD_IDX], modif)]) == 2:
                        events_interval[current_subject][(event[EVENT_BEHAVIOR_FIELD_IDX], modif)] |= \
                                I.closedopen(mem_events_interval[current_subject][(event[EVENT_BEHAVIOR_FIELD_IDX], modif)][0],
                                            mem_events_interval[current_subject][(event[EVENT_BEHAVIOR_FIELD_IDX], modif)][1])
                        mem_events_interval[current_subject][(
                            event[EVENT_BEHAVIOR_FIELD_IDX], modif)] = []
                else:
                    events_interval[current_subject][(
                        event[EVENT_BEHAVIOR_FIELD_IDX],
                        modif)] |= I.singleton(event[EVENT_TIME_FIELD_IDX])
            '''
            print("\n\n events interval", events_interval)
            '''

            time_bin_start = min_time

            if time_bin_size:
                time_bin_end = time_bin_start + time_bin_size
                if time_bin_end > max_time:
                    time_bin_end = max_time
            else:
                time_bin_end = max_time
            '''
            print("time_bin_start type", type(time_bin_start))
            '''
            while True:

                for subject in events_interval:

                    # check behavior to exclude from total time
                    time_to_subtract = 0
                    if EXCLUDED_BEHAVIORS in parameters_obs:
                        for behav in events_interval[subject]:
                            if behav[0] in parameters_obs.get(
                                    EXCLUDED_BEHAVIORS, []):
                                interval_intersec = events_interval[
                                    subject][behav] & I.closed(
                                        time_bin_start, time_bin_end)
                                time_to_subtract += interval_len(
                                    interval_intersec)

                    for behav in events_interval[subject]:

                        interval_intersec = events_interval[subject][
                            behav] & I.closed(time_bin_start, time_bin_end)

                        dur = interval_len(interval_intersec)
                        nocc = interval_number(interval_intersec)
                        mean = interval_mean(interval_intersec)
                        '''
                        print(interval_intersec)
                        print(subject, behav)

                        print("duration", dur)
                        print("n. occ", nocc)
                        print("mean", mean)
                        print("std dev", interval_std_dev(interval_intersec))

                        print(time_bin_start)
                        print(time_bin_end)
                        print(time_to_subtract)
                        '''
                        if behav[0] in parameters_obs.get(
                                EXCLUDED_BEHAVIORS, []):
                            proportion = dur / (
                                (time_bin_end - time_bin_start))
                        else:
                            proportion = dur / (
                                (time_bin_end - time_bin_start) -
                                time_to_subtract)

                        behaviors[subject][behav]["duration"] = dur
                        behaviors[subject][behav]["number"] = nocc
                        behaviors[subject][behav]["duration mean"] = mean
                        behaviors[subject][behav][
                            "duration stdev"] = interval_std_dev(
                                interval_intersec)
                        behaviors[subject][behav][
                            "proportion of time"] = f"{proportion:.3f}"

                columns = [
                    obs_id, f"{max_time - min_time:0.3f}",
                    f"{time_bin_start:.3f}-{time_bin_end:.3f}"
                ]
                for subject in selected_subjects:
                    for behavior_modifiers in distinct_behav_modif:
                        behavior, modifiers = behavior_modifiers
                        #behavior_modifiers_str = "|".join(behavior_modifiers) if modifiers else behavior
                        behavior_modifiers_str = behavior_modifiers

                        for param in parameters:
                            columns.append(behaviors[subject]
                                           [behavior_modifiers_str][param[0]])
                            #columns.append(behaviors[subject][behavior][param[0]])

                data_report.append(columns)

                time_bin_start = time_bin_end
                time_bin_end = time_bin_start + time_bin_size
                if time_bin_end > max_time:
                    time_bin_end = max_time
                #print(f"start: {time_bin_start} end: {time_bin_end}  max time: {max_time}")

                if time_bin_start == time_bin_end:
                    break

    except Exception:
        dialog.error_message("synthetic_time_budget_bin", sys.exc_info())
        return (False, tablib.Dataset())

    return True, data_report