Beispiel #1
0
 def set_to_current_time(self):
     """
     set time to current media time
     """
     self.teTime.setTime(
         QTime.fromString(seconds2time(self.current_time), HHMMSSZZZ))
     self.dsbTime.setValue(float(self.current_time))
Beispiel #2
0
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.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.dsbStartTime.setValue(0.0)
        paramPanelWindow.dsbEndTime.setValue(maxTime)
        paramPanelWindow.teStartTime.setVisible(False)
        paramPanelWindow.teEndTime.setVisible(False)

    # 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}")

    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 after 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
            }
Beispiel #3
0
def create_subtitles(pj: dict, selected_observations: list, parameters: dict,
                     export_dir: str) -> tuple:
    """
    create subtitles for selected observations, subjects and behaviors

    Args:
        pj (dict): project
        selected_observations (list): list of observations
        parameters (dict):
        export_dir (str): directory to save subtitles

    Returns:
        bool: True if OK else False
        str: error message
    """
    def subject_color(subject):
        """
        subject color

        Args:
            subject (str): subject name

        Returns:
            str: HTML tag for color font (beginning)
            str: HTML tag for color font (closing)
        """
        if subject == NO_FOCAL_SUBJECT:
            return "", ""
        else:
            return (
                """<font color="{}">""".format(
                    subtitlesColors[parameters["selected subjects"].index(
                        row["subject"]) % len(subtitlesColors)]),
                "</font>",
            )

    try:
        ok, msg, db_connector = db_functions.load_aggregated_events_in_db(
            pj, parameters["selected subjects"], selected_observations,
            parameters["selected behaviors"])
        if not ok:
            return False, msg

        cursor = db_connector.cursor()
        flag_ok = True
        msg = ""
        for obsId in selected_observations:
            if pj[OBSERVATIONS][obsId][TYPE] in [LIVE]:
                out = ""
                cursor.execute(
                    ("SELECT subject, behavior, start, stop, type, modifiers FROM aggregated_events "
                     "WHERE observation = ? AND subject in ({}) "
                     "AND behavior in ({}) "
                     "ORDER BY start").format(
                         ",".join(["?"] *
                                  len(parameters["selected subjects"])),
                         ",".join(["?"] *
                                  len(parameters["selected behaviors"]))),
                    [obsId] + parameters["selected subjects"] +
                    parameters["selected behaviors"],
                )

                for idx, row in enumerate(cursor.fetchall()):
                    col1, col2 = subject_color(row["subject"])
                    if parameters["include modifiers"]:
                        modifiers_str = "\n{}".format(row["modifiers"].replace(
                            "|", ", "))
                    else:
                        modifiers_str = ""
                    out += ("{idx}\n"
                            "{start} --> {stop}\n"
                            "{col1}{subject}: {behavior}"
                            "{modifiers}"
                            "{col2}\n\n").format(
                                idx=idx + 1,
                                start=utilities.seconds2time(
                                    row["start"]).replace(".", ","),
                                stop=utilities.seconds2time(
                                    row["stop"] if row["type"] ==
                                    STATE else row["stop"] +
                                    POINT_EVENT_ST_DURATION).replace(".", ","),
                                col1=col1,
                                col2=col2,
                                subject=row["subject"],
                                behavior=row["behavior"],
                                modifiers=modifiers_str,
                            )

                file_name = str(
                    pathlib.Path(
                        pathlib.Path(export_dir) /
                        utilities.safeFileName(obsId)).with_suffix(".srt"))
                try:
                    with open(file_name, "w") as f:
                        f.write(out)
                except Exception:
                    flag_ok = False
                    msg += "observation: {}\ngave the following error:\n{}\n".format(
                        obsId, str(sys.exc_info()[1]))

            elif pj[OBSERVATIONS][obsId][TYPE] in [MEDIA]:

                for nplayer in ALL_PLAYERS:
                    if not pj[OBSERVATIONS][obsId][FILE][nplayer]:
                        continue
                    init = 0
                    for mediaFile in pj[OBSERVATIONS][obsId][FILE][nplayer]:
                        try:
                            end = init + pj[OBSERVATIONS][obsId][MEDIA_INFO][
                                LENGTH][mediaFile]
                        except KeyError:
                            return False, f"The length for media file {mediaFile} is not available"
                        out = ""

                        print(",".join(["?"] *
                                       len(parameters["selected subjects"])))

                        print(",".join(["?"] *
                                       len(parameters["selected behaviors"])))

                        print([obsId, init, end] +
                              parameters["selected subjects"] +
                              parameters["selected behaviors"])

                        cursor.execute(
                            ("SELECT subject, behavior, type, start, stop, modifiers FROM aggregated_events "
                             "WHERE observation = ? AND start BETWEEN ? and ? "
                             "AND subject in ({}) "
                             "AND behavior in ({}) "
                             "ORDER BY start").format(
                                 ",".join(
                                     ["?"] *
                                     len(parameters["selected subjects"])),
                                 ",".join(
                                     ["?"] *
                                     len(parameters["selected behaviors"])),
                             ),
                            [obsId, init, end] +
                            parameters["selected subjects"] +
                            parameters["selected behaviors"],
                        )

                        for idx, row in enumerate(cursor.fetchall()):
                            col1, col2 = subject_color(row["subject"])
                            if parameters["include modifiers"]:
                                modifiers_str = "\n{}".format(
                                    row["modifiers"].replace("|", ", "))
                            else:
                                modifiers_str = ""

                            out += (
                                "{idx}\n"
                                "{start} --> {stop}\n"
                                "{col1}{subject}: {behavior}"
                                "{modifiers}"
                                "{col2}\n\n").format(
                                    idx=idx + 1,
                                    start=utilities.seconds2time(row["start"] -
                                                                 init).replace(
                                                                     ".", ","),
                                    stop=utilities.seconds2time(
                                        (row["stop"] if row["type"] ==
                                         STATE else row["stop"] +
                                         POINT_EVENT_ST_DURATION) -
                                        init).replace(".", ","),
                                    col1=col1,
                                    col2=col2,
                                    subject=row["subject"],
                                    behavior=row["behavior"],
                                    modifiers=modifiers_str,
                                )

                        file_name = str(
                            pathlib.Path(
                                pathlib.Path(export_dir) /
                                pathlib.Path(mediaFile).name).with_suffix(
                                    ".srt"))
                        try:
                            with open(file_name, "w") as f:
                                f.write(out)
                        except Exception:
                            flag_ok = False
                            msg += "observation: {}\ngave the following error:\n{}\n".format(
                                obsId, str(sys.exc_info()[1]))

                        init = end

        return flag_ok, msg
    except Exception:
        return False, str(sys.exc_info()[1])
Beispiel #4
0
def check_state_events_obs(obsId: str,
                           ethogram: dict,
                           observation: dict,
                           time_format: str = HHMMSS) -> tuple:
    """
    check state events for the observation obsId
    check if behaviors in observation are defined in ethogram
    check if number is odd

    Args:
        obsId (str): id of observation to check
        ethogram (dict): ethogram of project
        observation (dict): observation to be checked
        time_format (str): time format

    Returns:
        tuple (bool, str): if OK True else False , message
    """

    out = ""

    # check if behaviors are defined as "state event"
    event_types = {ethogram[idx]["type"] for idx in ethogram}

    if not event_types or event_types == {"Point event"}:
        return (True, "No behavior is defined as `State event`")

    flagStateEvent = False
    subjects = [
        event[EVENT_SUBJECT_FIELD_IDX] for event in observation[EVENTS]
    ]
    ethogram_behaviors = {ethogram[idx][BEHAVIOR_CODE] for idx in ethogram}

    for subject in sorted(set(subjects)):

        behaviors = [
            event[EVENT_BEHAVIOR_FIELD_IDX] for event in observation[EVENTS]
            if event[EVENT_SUBJECT_FIELD_IDX] == subject
        ]

        for behavior in sorted(set(behaviors)):
            if behavior not in ethogram_behaviors:
                # return (False, "The behaviour <b>{}</b> is not defined in the ethogram.<br>".format(behavior))
                continue
            else:
                if STATE in event_type(behavior, ethogram).upper():
                    flagStateEvent = True
                    lst, memTime = [], {}
                    for event in [
                            event for event in observation[EVENTS]
                            if event[EVENT_BEHAVIOR_FIELD_IDX] == behavior
                            and event[EVENT_SUBJECT_FIELD_IDX] == subject
                    ]:

                        behav_modif = [
                            event[EVENT_BEHAVIOR_FIELD_IDX],
                            event[EVENT_MODIFIER_FIELD_IDX]
                        ]

                        if behav_modif in lst:
                            lst.remove(behav_modif)
                            del memTime[str(behav_modif)]
                        else:
                            lst.append(behav_modif)
                            memTime[str(
                                behav_modif)] = event[EVENT_TIME_FIELD_IDX]

                    for event in lst:
                        out += (
                            """The behavior <b>{behavior}</b> {modifier} is not PAIRED for subject"""
                            """ "<b>{subject}</b>" at <b>{time}</b><br>"""
                        ).format(
                            behavior=behavior,
                            modifier=("(modifier " + event[1] +
                                      ") ") if event[1] else "",
                            subject=subject if subject else NO_FOCAL_SUBJECT,
                            time=memTime[str(event)] if time_format == S else
                            utilities.seconds2time(memTime[str(event)]),
                        )

    return (False, out) if out else (True, "No problem detected")
Beispiel #5
0
 def test_10(self):
     assert utilities.seconds2time(Decimal(10.0)) == "00:00:10.000"
Beispiel #6
0
 def test_gt_86400(self):
     assert utilities.seconds2time(
         Decimal(86400.999)) == "24:00:00.999"
Beispiel #7
0
 def test_negative(self):
     assert utilities.seconds2time(
         Decimal(-2.123)) == "-00:00:02.123"
Beispiel #8
0
 def test_10(self):
     assert utilities.seconds2time(Decimal(10.0)) == "00:00:10.000"
Beispiel #9
0
 def test_gt_86400(self):
     assert utilities.seconds2time(
         Decimal(86400.999)) == "24:00:00.999"
Beispiel #10
0
 def test_negative(self):
     assert utilities.seconds2time(
         Decimal(-2.123)) == "-00:00:02.123"
def create_subtitles(pj: dict,
                     selected_observations: list,
                     parameters: dict,
                     export_dir: str) -> tuple:
    """
    create subtitles for selected observations, subjects and behaviors

    Args:
        pj (dict): project
        selected_observations (list): list of observations
        parameters (dict):
        export_dir (str): directory to save subtitles

    Returns:
        bool: True if OK else False
        str: error message
    """

    def subject_color(subject):
        """
        subject color

        Args:
            subject (str): subject name

        Returns:
            str: HTML tag for color font (beginning)
            str: HTML tag for color font (closing)
        """
        if subject == NO_FOCAL_SUBJECT:
            return "", ""
        else:
            return (
                """<font color="{}">""".format(
                    subtitlesColors[parameters["selected subjects"].index(row["subject"]) % len(subtitlesColors)]
                ),
                "</font>",
            )

    try:
        ok, msg, db_connector = db_functions.load_aggregated_events_in_db(
            pj, parameters["selected subjects"], selected_observations, parameters["selected behaviors"]
        )
        if not ok:
            return False, msg

        cursor = db_connector.cursor()
        flag_ok = True
        msg = ""
        for obsId in selected_observations:
            if pj[OBSERVATIONS][obsId][TYPE] in [LIVE]:
                out = ""
                cursor.execute(
                    (
                        "SELECT subject, behavior, start, stop, type, modifiers FROM aggregated_events "
                        "WHERE observation = ? AND subject in ({}) "
                        "AND behavior in ({}) "
                        "ORDER BY start"
                    ).format(",".join(["?"] * len(parameters["selected subjects"])),
                             ",".join(["?"] * len(parameters["selected behaviors"]))),
                    [obsId] + parameters["selected subjects"] + parameters["selected behaviors"],
                )

                for idx, row in enumerate(cursor.fetchall()):
                    col1, col2 = subject_color(row["subject"])
                    if parameters["include modifiers"]:
                        modifiers_str = "\n{}".format(row["modifiers"].replace("|", ", "))
                    else:
                        modifiers_str = ""
                    out += ("{idx}\n" "{start} --> {stop}\n" "{col1}{subject}: {behavior}" "{modifiers}" "{col2}\n\n").format(
                        idx=idx + 1,
                        start=utilities.seconds2time(row["start"]).replace(".", ","),
                        stop=utilities.seconds2time(row["stop"] if row["type"] == STATE else row["stop"] + POINT_EVENT_ST_DURATION).replace(
                            ".", ","
                        ),
                        col1=col1,
                        col2=col2,
                        subject=row["subject"],
                        behavior=row["behavior"],
                        modifiers=modifiers_str,
                    )

                file_name = str(pathlib.Path(pathlib.Path(export_dir) / utilities.safeFileName(obsId)).with_suffix(".srt"))
                try:
                    with open(file_name, "w") as f:
                        f.write(out)
                except Exception:
                    flag_ok = False
                    msg += "observation: {}\ngave the following error:\n{}\n".format(obsId, str(sys.exc_info()[1]))

            elif pj[OBSERVATIONS][obsId][TYPE] in [MEDIA]:

                for nplayer in ALL_PLAYERS:
                    if not pj[OBSERVATIONS][obsId][FILE][nplayer]:
                        continue
                    init = 0
                    for mediaFile in pj[OBSERVATIONS][obsId][FILE][nplayer]:
                        try:
                            end = init + pj[OBSERVATIONS][obsId][MEDIA_INFO][LENGTH][mediaFile]
                        except KeyError:
                            return False, f"The length for media file {mediaFile} is not available"
                        out = ""

                        print(",".join(["?"] * len(parameters["selected subjects"])))

                        print(",".join(["?"] * len(parameters["selected behaviors"])))

                        print([obsId, init, end] + parameters["selected subjects"] + parameters["selected behaviors"])

                        cursor.execute(
                            (
                                "SELECT subject, behavior, type, start, stop, modifiers FROM aggregated_events "
                                "WHERE observation = ? AND start BETWEEN ? and ? "
                                "AND subject in ({}) "
                                "AND behavior in ({}) "
                                "ORDER BY start"
                            ).format(
                                ",".join(["?"] * len(parameters["selected subjects"])),
                                ",".join(["?"] * len(parameters["selected behaviors"])),
                            ),
                            [obsId, init, end] + parameters["selected subjects"] + parameters["selected behaviors"],
                        )

                        for idx, row in enumerate(cursor.fetchall()):
                            col1, col2 = subject_color(row["subject"])
                            if parameters["include modifiers"]:
                                modifiers_str = "\n{}".format(row["modifiers"].replace("|", ", "))
                            else:
                                modifiers_str = ""

                            out += ("{idx}\n" "{start} --> {stop}\n" "{col1}{subject}: {behavior}" "{modifiers}" "{col2}\n\n").format(
                                idx=idx + 1,
                                start=utilities.seconds2time(row["start"] - init).replace(".", ","),
                                stop=utilities.seconds2time(
                                    (row["stop"] if row["type"] == STATE else row["stop"] + POINT_EVENT_ST_DURATION) - init
                                ).replace(".", ","),
                                col1=col1,
                                col2=col2,
                                subject=row["subject"],
                                behavior=row["behavior"],
                                modifiers=modifiers_str,
                            )

                        file_name = str(pathlib.Path(pathlib.Path(export_dir) / pathlib.Path(mediaFile).name).with_suffix(".srt"))
                        try:
                            with open(file_name, "w") as f:
                                f.write(out)
                        except Exception:
                            flag_ok = False
                            msg += "observation: {}\ngave the following error:\n{}\n".format(obsId, str(sys.exc_info()[1]))

                        init = end

        return flag_ok, msg
    except Exception:
        return False, str(sys.exc_info()[1])
def check_state_events_obs(obsId: str, ethogram: dict, observation: dict, time_format: str = HHMMSS) -> tuple:
    """
    check state events for the observation obsId
    check if behaviors in observation are defined in ethogram
    check if number is odd

    Args:
        obsId (str): id of observation to check
        ethogram (dict): ethogram of project
        observation (dict): observation to be checked
        time_format (str): time format

    Returns:
        tuple (bool, str): if OK True else False , message
    """

    out = ""

    # check if behaviors are defined as "state event"
    event_types = {ethogram[idx]["type"] for idx in ethogram}

    if not event_types or event_types == {"Point event"}:
        return (True, "No behavior is defined as `State event`")

    flagStateEvent = False
    subjects = [event[EVENT_SUBJECT_FIELD_IDX] for event in observation[EVENTS]]
    ethogram_behaviors = {ethogram[idx][BEHAVIOR_CODE] for idx in ethogram}

    for subject in sorted(set(subjects)):

        behaviors = [event[EVENT_BEHAVIOR_FIELD_IDX] for event in observation[EVENTS] if event[EVENT_SUBJECT_FIELD_IDX] == subject]

        for behavior in sorted(set(behaviors)):
            if behavior not in ethogram_behaviors:
                # return (False, "The behaviour <b>{}</b> is not defined in the ethogram.<br>".format(behavior))
                continue
            else:
                if STATE in event_type(behavior, ethogram).upper():
                    flagStateEvent = True
                    lst, memTime = [], {}
                    for event in [
                        event
                        for event in observation[EVENTS]
                        if event[EVENT_BEHAVIOR_FIELD_IDX] == behavior and event[EVENT_SUBJECT_FIELD_IDX] == subject
                    ]:

                        behav_modif = [event[EVENT_BEHAVIOR_FIELD_IDX], event[EVENT_MODIFIER_FIELD_IDX]]

                        if behav_modif in lst:
                            lst.remove(behav_modif)
                            del memTime[str(behav_modif)]
                        else:
                            lst.append(behav_modif)
                            memTime[str(behav_modif)] = event[EVENT_TIME_FIELD_IDX]

                    for event in lst:
                        out += (
                            """The behavior <b>{behavior}</b> {modifier} is not PAIRED for subject"""
                            """ "<b>{subject}</b>" at <b>{time}</b><br>"""
                        ).format(
                            behavior=behavior,
                            modifier=("(modifier " + event[1] + ") ") if event[1] else "",
                            subject=subject if subject else NO_FOCAL_SUBJECT,
                            time=memTime[str(event)] if time_format == S else utilities.seconds2time(memTime[str(event)]),
                        )

    return (False, out) if out else (True, "No problem detected")