예제 #1
0
    def tabWidgetModifiersSets_changed(self, tabIndex):
        """
        user changed the tab widget
        """

        # check if modifier field empty
        if self.leModifier.text() and tabIndex != self.tabMem:
            if dialog.MessageDialog(
                    programName,
                ("You are working on a behavior.<br>"
                 "If you change the modifier's set it will be lost.<br>"
                 "Do you want to change modifiers set"), [YES, NO]) == NO:
                self.tabWidgetModifiersSets.setCurrentIndex(self.tabMem)
                return

        if tabIndex != self.tabMem:
            self.lwModifiers.clear()
            self.leCode.clear()
            self.leModifier.clear()

            self.tabMem = tabIndex

            if tabIndex != -1:
                self.leSetName.setText(
                    self.modifiers_sets_dict[str(tabIndex)]["name"])
                self.cbType.setCurrentIndex(
                    self.modifiers_sets_dict[str(tabIndex)]["type"])
                self.lwModifiers.addItems(
                    self.modifiers_sets_dict[str(tabIndex)]["values"])
예제 #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:
        error_type, error_file_name, error_lineno = utilities.error_info(
            sys.exc_info())
        logging.critical(
            f"Error in function '{sys._getframe().f_code.co_name}': {error_type} {error_file_name} {error_lineno}"
        )
        dialog.error_message_box(sys._getframe().f_code.co_name, error_type,
                                 error_file_name, error_lineno)
예제 #3
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())
예제 #4
0
    def pb_pushed(self, button):

        if self.leModifier.text():
            if dialog.MessageDialog(cfg.programName, ("You are working on a behavior.<br>"
                                                  "If you close the window it will be lost.<br>"
                                                  "Do you want to change modifiers set"), ["Close", cfg.CANCEL]) == cfg.CANCEL:
                return
        if button == "ok":
            self.accept()
        if button == "cancel":
            self.reject()
예제 #5
0
 def pbClose_clicked(self):
     if not self.flagSaved:
         response = dialog.MessageDialog(
             programName,
             "The current results are not saved. Do you want to save results before closing?",
             [YES, NO, CANCEL])
         if response == YES:
             self.pbSave_clicked()
         if response == CANCEL:
             return
     self.closeSignal.emit()
예제 #6
0
    def save_results(self):
        """
        save results
        """

        extended_file_formats = ["Tab Separated Values (*.tsv)",
                                 "Comma Separated Values (*.csv)",
                                 "Open Document Spreadsheet ODS (*.ods)",
                                 "Microsoft Excel Spreadsheet XLSX (*.xlsx)",
                                 "Legacy Microsoft Excel Spreadsheet XLS (*.xls)",
                                 "HTML (*.html)"]
        file_formats = ["tsv",
                        "csv",
                        "ods",
                        "xlsx",
                        "xls",
                        "html"]

        file_name, filter_ = QFileDialog().getSaveFileName(None, "Save results", "", ";;".join(extended_file_formats))
        if not file_name:
            return

        output_format = file_formats[extended_file_formats.index(filter_)]

        if pathlib.Path(file_name).suffix != "." + output_format:
            file_name = str(pathlib.Path(file_name)) + "." + output_format
            # check if file with new extension already exists
            if pathlib.Path(file_name).is_file():
                if dialog.MessageDialog(programName,
                                        f"The file {file_name} already exists.",
                                        [CANCEL, OVERWRITE]) == CANCEL:
                    return

        if self.rb_details.isChecked():
            tablib_dataset = tablib.Dataset(headers=self.details_header)
        if self.rb_summary.isChecked():
            tablib_dataset = tablib.Dataset(headers=self.summary_header)
        tablib_dataset.title = utilities.safe_xl_worksheet_title(self.logic.text(), output_format)

        [tablib_dataset.append(x) for x in self.out]

        try:
            if output_format in ["csv", "tsv", "html"]:
                with open(file_name, "wb") as f:
                    f.write(str.encode(tablib_dataset.export(output_format)))

            if output_format in ["ods", "xlsx", "xls"]:
                with open(file_name, "wb") as f:
                    f.write(tablib_dataset.export(output_format))

        except Exception:
            QMessageBox.critical(self, programName, f"The file {file_name} can not be saved")
예제 #7
0
    def newMap(self):
        """
        create a new map
        """

        if self.flagMapChanged:

            response = dialog.MessageDialog(
                programName + " - Behaviors coding map creator",
                "What to do about the current unsaved coding map?",
                [SAVE, DISCARD, CANCEL])

            if response == SAVE:
                if not self.saveMap_clicked():
                    return

            if response == CANCEL:
                return

        self.cancelMap()

        while True:
            map_name, ok = QInputDialog.getText(
                self, "Behaviors coding map name",
                "Enter a name for the new coding map")
            if map_name.upper() in self.bcm_list:
                QMessageBox.critical(self, "", (
                    "The name for the new coding map already exists.<br>"
                    f"{', '.join(self.bcm_list)} are already defined.<br>"
                    "To reuse the same name the existing coding map must be deleted (File > Edit project)"
                ))
            if ok and map_name and map_name.upper() not in self.bcm_list:
                self.mapName = map_name
                break
            if not ok:
                return
        '''
        if not self.mapName:
            QMessageBox.critical(self, "", "You must define a name for the new coding map")
            return
        '''

        self.setWindowTitle(
            f"{programName} - Behaviors coding map creator tool - {self.mapName}"
        )

        self.btLoad.setVisible(True)
        self.statusBar().showMessage(
            'Click "Load bitmap" button to select and load a bitmap into the viewer'
        )
예제 #8
0
    def closeEvent(self, event):

        if self.flagMapChanged:

            response = dialog.MessageDialog("BORIS - Modifiers map creator", "What to do about the current unsaved modifiers coding map?", ["Save", "Discard", "Cancel"])

            if response == "Save":
                if not self.saveMap_clicked():
                    event.ignore()

            if response == "Cancel":
                event.ignore()
                return

        self.closed.emit()
        event.accept()
예제 #9
0
def observation_length(pj, selected_observations: list) -> tuple:
    """
    max length of selected observations
    total media length

    Args:
        selected_observations (list): list of selected observations

    Returns:
        float: maximum media length for all observations
        float: total media length for all observations
    """
    selectedObsTotalMediaLength = dec("0.0")
    max_obs_length = 0
    for obs_id in selected_observations:
        obs_length = observation_total_length(pj[OBSERVATIONS][obs_id])
        if obs_length in [dec("0"), dec("-1")]:
            selectedObsTotalMediaLength = -1
            break
        max_obs_length = max(max_obs_length, obs_length)
        selectedObsTotalMediaLength += obs_length

    # an observation media length is not available
    if selectedObsTotalMediaLength == -1:
        # propose to user to use max event time
        if dialog.MessageDialog(
                programName,
            (f"A media length is not available for the observation <b>{obs_id}</b>.<br>"
             "Use last event time as media length?"), [YES, NO]) == YES:
            maxTime = 0  # max length for all events all subjects
            max_length = 0
            for obs_id in selected_observations:
                if pj[OBSERVATIONS][obs_id][EVENTS]:
                    maxTime += max(pj[OBSERVATIONS][obs_id][EVENTS])[0]
                    max_length = max(max_length,
                                     max(pj[OBSERVATIONS][obs_id][EVENTS])[0])

            logging.debug(f"max time all events all subjects: {maxTime}")

            max_obs_length = max_length
            selectedObsTotalMediaLength = maxTime

        else:
            max_obs_length = -1
            selectedObsTotalMediaLength = dec("-1")

    return max_obs_length, selectedObsTotalMediaLength
예제 #10
0
    def newMap(self):
        """
        create a new map
        """

        if self.flagMapChanged:

            response = dialog.MessageDialog(
                programName + ' - Modifiers map creator',
                'What to do about the current unsaved coding map?',
                ['Save', 'Discard', 'Cancel'])

            if response == 'Save':
                if not self.saveMap_clicked():
                    return

            if response == 'Cancel':
                return

        self.cancelMap()

        text, ok = QInputDialog.getText(self, 'Map name',
                                        'Enter a name for the new map')
        if ok:
            self.mapName = text
        else:
            return

        if self.mapName == '':
            QMessageBox.critical(self, '',
                                 'You must define a name for the new map')
            return

        if self.mapName in ['areas', 'bitmap']:
            QMessageBox.critical(self, '', 'This name is not allowed')
            return

        self.setWindowTitle(programName + ' - Map creator tool - ' +
                            self.mapName)

        self.btLoad.setVisible(True)
        '''self.btCancelMap.setVisible(True)'''

        self.statusBar().showMessage(
            'Click "Load bitmap" button to select and load a bitmap into the viewer'
        )
예제 #11
0
    def add_media_from_dir(self, flag_path):
        """
        add all media from a selected directory

        Args:
            flag_path (bool): True include full path of media else only basename
        """

        # check if project saved
        if (not flag_path) and (not self.project_file_name):
            QMessageBox.critical(
                self, programName,
                ("It is not possible to add media without full path "
                 "if the project is not saved"))
            return

        fd = QFileDialog()
        fd.setDirectory(
            os.path.expanduser("~") if flag_path else str(
                Path(self.project_path).parent))

        dir_name = fd.getExistingDirectory(self, "Select directory")
        if dir_name:
            r, response = "", ""
            for file_path in glob.glob(dir_name + os.sep + "*"):
                r, msg = self.check_media(file_path, flag_path)
                if not r:
                    if response != "Skip all non media files":
                        response = dialog.MessageDialog(
                            programName, f"<b>{file_path}</b> {msg}",
                            ["Continue", "Skip all non media files", "Cancel"])
                        if response == "Cancel":
                            break

        for w in [
                self.cbVisualizeSpectrogram, self.cb_visualize_waveform,
                self.cb_observation_time_interval,
                self.cbCloseCurrentBehaviorsBetweenVideo
        ]:
            w.setEnabled(self.twVideo1.rowCount() > 0)

        # disabled for problems
        self.cbCloseCurrentBehaviorsBetweenVideo.setEnabled(False)
예제 #12
0
    def removeSet(self):
        """
        remove set of modifiers
        """

        if self.tabWidgetModifiersSets.currentIndex() != -1:
            if dialog.MessageDialog(cfg.programName, "Confirm deletion of this set of modifiers?", [cfg.YES, cfg.NO]) == cfg.YES:
                index_to_delete = self.tabWidgetModifiersSets.currentIndex()

                for k in range(index_to_delete, len(self.modifiers_sets_dict) - 1):
                    self.modifiers_sets_dict[str(k)] = self.modifiers_sets_dict[str(k + 1)]
                # del last key
                del self.modifiers_sets_dict[str(len(self.modifiers_sets_dict) - 1)]

                # remove all tabs
                while self.tabWidgetModifiersSets.count():
                    self.tabWidgetModifiersSets.removeTab(0)

                # recreate tabs
                for idx in sorted_keys(self.modifiers_sets_dict):
                    self.tabWidgetModifiersSets.addTab(QWidget(), f"Set #{int(idx) + 1}")

                # set not visible and not available buttons and others elements
                if self.tabWidgetModifiersSets.currentIndex() == -1:
                    for w in [self.lbSetName, self.lbType, self.lbValues, self.leSetName, self.cbType, self.lwModifiers, self.pbMoveUp,
                              self.pbMoveDown, self.pbRemoveModifier, self.pbRemoveSet, self.pbMoveSetLeft, self.pbMoveSetRight]:
                        w.setVisible(False)
                    for w in [self.leModifier, self.leCode, self.pbAddModifier, self.pbModifyModifier]:
                        w.setEnabled(False)

                if not len(self.modifiers_sets_dict):
                    # set invisible and unavailable buttons and others elements
                    for w in [self.lbSetName, self.lbType, self.lbValues, self.leSetName, self.cbType, self.lwModifiers, self.pbMoveUp,
                              self.pbMoveDown, self.pbRemoveModifier, self.pbRemoveSet, self.pbMoveSetLeft, self.pbMoveSetRight,
                              self.pb_add_subjects, self.pb_load_file, self.pb_sort_modifiers]:
                        w.setVisible(False)
                    for w in [self.leModifier, self.leCode, self.pbAddModifier, self.pbModifyModifier]:
                        w.setEnabled(False)
                    return

        else:
            QMessageBox.information(self, cfg.programName, "It is not possible to remove the last modifiers' set.")
예제 #13
0
    def newMap(self):
        """
        create a new map
        """

        if self.flagMapChanged:

            response = dialog.MessageDialog(
                programName + " - Modifiers map creator",
                "What to do about the current unsaved coding map?",
                [SAVE, DISCARD, CANCEL])

            if response == SAVE:
                if not self.saveMap_clicked():
                    return

            if response == CANCEL:
                return

        self.cancelMap()

        text, ok = QInputDialog.getText(self, "Behaviors coding map name",
                                        "Enter a name for the new coding map")
        if ok:
            self.mapName = text
        else:
            return

        if not self.mapName:
            QMessageBox.critical(
                self, "", "You must define a name for the new coding map")
            return

        self.setWindowTitle(
            "{} - Behaviors coding map creator tool - {}".format(
                programName, self.mapName))

        self.btLoad.setVisible(True)
        self.statusBar().showMessage(
            'Click "Load bitmap" button to select and load a bitmap into the viewer'
        )
예제 #14
0
    def view_doubleClicked(self, index):

        if self.mode == config.MULTIPLE:
            return

        if self.mode == config.OPEN or self.mode == config.EDIT:
            self.done(2)
            return

        if self.mode == config.SELECT1:
            self.done(2)
            return

        response = dialog.MessageDialog(
            config.programName,
            "What do you want to do with this observation?",
            list(commands_index.keys()) + [config.CANCEL])
        if response == config.CANCEL:
            return
        else:
            self.done(commands_index[response])
예제 #15
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())
예제 #16
0
    def openMap(self):
        """
        open a coding map from file

        load bitmap from data
        show it in view scene
        """
        if self.flagMapChanged:
            response = dialog.MessageDialog(
                programName + " - Behaviors coding map creator",
                "What to do about the current unsaved coding map?",
                ['Save', 'Discard', 'Cancel'])

            if (response == "Save"
                    and not self.saveMap_clicked()) or (response == "Cancel"):
                return

        fn = QFileDialog(self).getOpenFileName(
            self, "Open a behaviors coding map", "",
            "Behaviors coding map (*.behav_coding_map);;All files (*)")
        fileName = fn[0] if type(fn) is tuple else fn

        if fileName:

            try:
                self.codingMap = json.loads(open(fileName, "r").read())
            except Exception:
                QMessageBox.critical(
                    self, programName,
                    f"The file {fileName} is not a behaviors coding map.")
                return

            if "coding_map_type" not in self.codingMap or self.codingMap[
                    "coding_map_type"] != "BORIS behaviors coding map":
                QMessageBox.critical(
                    self, programName,
                    f"The file {fileName} is not a BORIS behaviors coding map."
                )

            self.cancelMap()

            self.mapName = self.codingMap["name"]

            self.setWindowTitle(
                f"{programName} - Behaviors coding map creator - {self.mapName}"
            )

            self.bitmapFileName = True

            self.fileName = fileName

            bitmapContent = binascii.a2b_base64(self.codingMap["bitmap"])

            self.pixmap.loadFromData(bitmapContent)

            self.view.setSceneRect(0, 0,
                                   self.pixmap.size().width(),
                                   self.pixmap.size().height())
            self.view.setMinimumHeight(self.pixmap.size().height())
            # self.view.setMaximumHeight(self.pixmap.size().height())
            pixItem = QGraphicsPixmapItem(self.pixmap)
            pixItem.setPos(0, 0)
            self.view.scene().addItem(pixItem)

            for key in self.codingMap["areas"]:
                areaCode = self.codingMap["areas"][key]["code"]
                points = self.codingMap["areas"][key]["geometry"]

                newPolygon = QPolygonF()
                for p in points:
                    newPolygon.append(QPoint(p[0], p[1]))

                # draw polygon
                '''polygon = QGraphicsPolygonItem(None, None) if QT_VERSION_STR[0] == "4" else QGraphicsPolygonItem()'''
                polygon = QGraphicsPolygonItem()
                polygon.setPolygon(newPolygon)
                clr = QColor()
                clr.setRgba(self.codingMap["areas"][key]["color"])
                polygon.setPen(
                    QPen(clr, penWidth, penStyle, Qt.RoundCap, Qt.RoundJoin))
                polygon.setBrush(QBrush(clr, Qt.SolidPattern))

                self.view.scene().addItem(polygon)

                self.polygonsList2.append([areaCode, polygon])

            self.btNewArea.setVisible(True)
            self.btLoad.setVisible(False)

            self.saveMapAction.setEnabled(True)
            self.saveAsMapAction.setEnabled(True)
            self.addToProject.setEnabled(True)
            self.mapNameAction.setEnabled(True)

            self.update_area_list()

        else:
            self.statusBar().showMessage("No file", 5000)
예제 #17
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())
def behavior_binary_table(pj: dict):
    """
    ask user for parameters for behavior binary table
    call create_behavior_binary_table
    """

    result, selected_observations = select_observations.select_observations(
        pj, MULTIPLE, "Select observations for the behavior binary table")

    if not selected_observations:
        return
    # check if state events are paired
    out = ""
    not_paired_obs_list = []
    for obs_id in selected_observations:
        r, msg = project_functions.check_state_events_obs(
            obs_id, pj[ETHOGRAM], pj[OBSERVATIONS][obs_id])

        if not r:
            out += f"Observation: <strong>{obs_id}</strong><br>{msg}<br>"
            not_paired_obs_list.append(obs_id)

    if out:
        out = f"The observations with UNPAIRED state events will be removed from the analysis<br><br>{out}"
        results = dialog.Results_dialog()
        results.setWindowTitle(f"{programName} - Check selected observations")
        results.ptText.setReadOnly(True)
        results.ptText.appendHtml(out)
        results.pbSave.setVisible(False)
        results.pbCancel.setVisible(True)

        if not results.exec_():
            return
    selected_observations = [
        x for x in selected_observations if x not in not_paired_obs_list
    ]
    if not selected_observations:
        return

    max_obs_length, selectedObsTotalMediaLength = project_functions.observation_length(
        pj, selected_observations)
    if max_obs_length == -1:  # media length not available, user choose to not use events
        return

    parameters = dialog.choose_obs_subj_behav_category(
        pj,
        selected_observations,
        maxTime=max_obs_length,
        flagShowIncludeModifiers=True,
        flagShowExcludeBehaviorsWoEvents=True,
        by_category=False)

    if not parameters[SELECTED_SUBJECTS] or not parameters[SELECTED_BEHAVIORS]:
        QMessageBox.warning(None, programName,
                            "Select subject(s) and behavior(s) to analyze")
        return

    # ask for time interval
    i, ok = QInputDialog.getDouble(None, "Behavior binary table",
                                   "Time interval (in seconds):", 1.0, 0.001,
                                   86400, 3)
    if not ok:
        return
    time_interval = utilities.float2decimal(i)
    '''
    iw = dialog.Info_widget()
    iw.lwi.setVisible(False)
    iw.resize(350, 200)
    iw.setWindowFlags(Qt.WindowStaysOnTopHint)

    iw.setWindowTitle("Behavior binary table")
    iw.label.setText("Creating the behavior binary table...")
    iw.show()
    QApplication.processEvents()
    '''

    results_df = create_behavior_binary_table(pj, selected_observations,
                                              parameters, time_interval)
    '''
    iw.hide()
    '''

    if "error" in results_df:
        QMessageBox.warning(None, programName, results_df["msg"])
        return

    # save results
    if len(selected_observations) == 1:
        extended_file_formats = [
            "Tab Separated Values (*.tsv)", "Comma Separated Values (*.csv)",
            "Open Document Spreadsheet ODS (*.ods)",
            "Microsoft Excel Spreadsheet XLSX (*.xlsx)",
            "Legacy Microsoft Excel Spreadsheet XLS (*.xls)", "HTML (*.html)"
        ]
        file_formats = ["tsv", "csv", "ods", "xlsx", "xls", "html"]

        file_name, filter_ = QFileDialog().getSaveFileName(
            None, "Save results", "", ";;".join(extended_file_formats))
        if not file_name:
            return

        output_format = file_formats[extended_file_formats.index(filter_)]

        if pathlib.Path(file_name).suffix != "." + output_format:
            file_name = str(pathlib.Path(file_name)) + "." + output_format
            # check if file with new extension already exists
            if pathlib.Path(file_name).is_file():
                if dialog.MessageDialog(
                        programName, f"The file {file_name} already exists.",
                    [CANCEL, OVERWRITE]) == CANCEL:
                    return
    else:
        items = ("Tab Separated Values (*.tsv)",
                 "Comma separated values (*.csv)",
                 "Open Document Spreadsheet (*.ods)",
                 "Microsoft Excel Spreadsheet XLSX (*.xlsx)",
                 "Legacy Microsoft Excel Spreadsheet XLS (*.xls)",
                 "HTML (*.html)")

        item, ok = QInputDialog.getItem(None, "Save results",
                                        "Available formats", items, 0, False)
        if not ok:
            return
        output_format = re.sub(".* \(\*\.", "", item)[:-1]

        export_dir = QFileDialog().getExistingDirectory(
            None,
            "Choose a directory to save results",
            os.path.expanduser("~"),
            options=QFileDialog.ShowDirsOnly)
        if not export_dir:
            return

    mem_command = ""
    for obs_id in results_df:

        for subject in results_df[obs_id]:

            if len(selected_observations) > 1:
                file_name_with_subject = str(
                    pathlib.Path(export_dir) /
                    utilities.safeFileName(obs_id + "_" +
                                           subject)) + "." + output_format
            else:
                file_name_with_subject = str(
                    os.path.splitext(file_name)[0] +
                    utilities.safeFileName("_" +
                                           subject)) + "." + output_format

            # check if file with new extension already exists
            if mem_command != OVERWRITE_ALL and pathlib.Path(
                    file_name_with_subject).is_file():
                if mem_command == "Skip all":
                    continue
                mem_command = dialog.MessageDialog(
                    programName,
                    f"The file {file_name_with_subject} already exists.",
                    [OVERWRITE, OVERWRITE_ALL, "Skip", "Skip all", CANCEL])
                if mem_command == CANCEL:
                    return
                if mem_command in ["Skip", "Skip all"]:
                    continue

            try:
                if output_format in ["csv", "tsv", "html"]:
                    with open(file_name_with_subject, "wb") as f:
                        f.write(
                            str.encode(results_df[obs_id][subject].export(
                                output_format)))

                if output_format in ["ods", "xlsx", "xls"]:
                    with open(file_name_with_subject, "wb") as f:
                        f.write(
                            results_df[obs_id][subject].export(output_format))

            except Exception:

                error_type, error_file_name, error_lineno = utilities.error_info(
                    sys.exc_info())
                logging.critical(
                    f"Error in behavior binary table function: {error_type} {error_file_name} {error_lineno}"
                )

                QMessageBox.critical(None, programName,
                                     f"Error saving file: {error_type}")
                return
예제 #19
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())
예제 #20
0
    def openMap(self):
        """
        load bitmap from data
        show it in view scene
        """
        if self.flagMapChanged:

            response = dialog.MessageDialog(programName + ' - Map creator', 'What to do about the current unsaved coding map?', ['Save', 'Discard', 'Cancel'])

            if response == "Save":
                if not self.saveMap_clicked():
                    return

            if response == "Cancel":
                return

        fn = QFileDialog().getOpenFileName(self, 'Open a coding map', '', 'BORIS coding map (*.boris_map);;All files (*)')
        fileName = fn[0] if type(fn) is tuple else fn

        if fileName:

            try:
                self.codingMap = json.loads( open( fileName , 'r').read() )
            except:
                QMessageBox.critical(self, programName, "The file {} seems not a behaviors coding map...".format(fileName))
                return              

            self.cancelMap()
            
            self.mapName = self.codingMap['name']

            self.setWindowTitle(programName + ' - Map creator tool - ' + self.mapName)

            self.bitmapFileName = True

            self.fileName = fileName

            self.areasList = self.codingMap['areas']   # dictionary of dictionaries
            bitmapContent = binascii.a2b_base64( self.codingMap['bitmap'] )

            self.pixmap.loadFromData(bitmapContent)

            self.btDeleteArea.setEnabled(False)


            self.view.setSceneRect(0, 0, self.pixmap.size().width(), self.pixmap.size().height())
            pixItem = QGraphicsPixmapItem(self.pixmap)
            pixItem.setPos(0,0)
            self.view.scene().addItem(pixItem)

            for areaCode in self.areasList:
                points = self.areasList[ areaCode ]['geometry']

                newPolygon = QPolygonF()
                for p in points:
                    newPolygon.append(QPoint(p[0], p[1]))


                clr = QColor( )
                clr.setRgba( self.areasList[ areaCode ]['color'] )

                # draw polygon
                polygon = QGraphicsPolygonItem()


                polygon.setPolygon(newPolygon)

                polygon.setPen(QPen(clr, penWidth, penStyle, Qt.RoundCap, Qt.RoundJoin))

                polygon.setBrush( QBrush( clr, Qt.SolidPattern ) )

                self.view.scene().addItem( polygon )
                self.polygonsList2[ areaCode ] = polygon

            self.btNewArea.setVisible(True)

            self.btLoad.setVisible(False)

            self.saveMapAction.setEnabled(True)
            self.saveAsMapAction.setEnabled(True)
            self.mapNameAction.setEnabled(True)
            self.statusBar().showMessage('Click "New area" to create a new area')
        else:
            self.statusBar().showMessage('No file', 5000)
예제 #21
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())
예제 #22
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, ""
예제 #23
0
    def pbSave_clicked(self):
        """
        save time budget analysis results in TSV, CSV, ODS, XLS format
        """
        def complete(l: list, max_: int) -> list:
            """
            complete list with empty string until len = max

            Args:
                l (list): list to complete
                max_ (int): length of the returned list

            Returns:
                list: completed list
            """

            while len(l) < max_:
                l.append("")
            return l

        logging.debug("save time budget results to file")

        extended_file_formats = [
            "Tab Separated Values (*.tsv)", "Comma Separated Values (*.csv)",
            "Open Document Spreadsheet ODS (*.ods)",
            "Microsoft Excel Spreadsheet XLSX (*.xlsx)",
            "Legacy Microsoft Excel Spreadsheet XLS (*.xls)", "HTML (*.html)"
        ]
        file_formats = ["tsv", "csv", "ods", "xlsx", "xls", "html"]

        file_name, filter_ = QFileDialog().getSaveFileName(
            self, "Save Time budget analysis", "",
            ";;".join(extended_file_formats))

        if not file_name:
            return

        outputFormat = file_formats[extended_file_formats.index(filter_)]
        if pathlib.Path(file_name).suffix != "." + outputFormat:
            file_name = str(pathlib.Path(file_name)) + "." + outputFormat
            # check if file with new extension already exists
            if pathlib.Path(file_name).is_file():
                if dialog.MessageDialog(
                        programName, f"The file {file_name} already exists.",
                    [CANCEL, OVERWRITE]) == CANCEL:
                    return

        rows = []

        # 1 observation
        if (self.lw.count() == 1 and self.config_param.get(
                TIME_BUDGET_FORMAT, DEFAULT_TIME_BUDGET_FORMAT)
                == COMPACT_TIME_BUDGET_FORMAT):
            col1, indep_var_label = [], []
            # add obs id
            col1.append(self.lw.item(0).text())
            # add obs date
            col1.append(self.pj[OBSERVATIONS][self.lw.item(0).text()].get(
                "date", ""))

            # description
            col1.append(
                utilities.eol2space(
                    self.pj[OBSERVATIONS][self.lw.item(0).text()].get(
                        DESCRIPTION, "")))
            header = ["Observation id", "Observation date", "Description"]

            # indep var
            for var in self.pj[OBSERVATIONS][self.lw.item(0).text()].get(
                    INDEPENDENT_VARIABLES, {}):
                indep_var_label.append(var)
                col1.append(self.pj[OBSERVATIONS][self.lw.item(0).text()]
                            [INDEPENDENT_VARIABLES][var])

            header.extend(indep_var_label)

            col1.extend([
                f"{self.min_time:0.3f}", f"{self.max_time:0.3f}",
                f"{self.max_time - self.min_time:0.3f}"
            ])
            header.extend([
                "Time budget start", "Time budget stop", "Time budget duration"
            ])

            for col_idx in range(self.twTB.columnCount()):
                header.append(self.twTB.horizontalHeaderItem(col_idx).text())
            rows.append(header)

            for row_idx in range(self.twTB.rowCount()):
                values = []
                for col_idx in range(self.twTB.columnCount()):
                    values.append(
                        intfloatstr(self.twTB.item(row_idx, col_idx).text()))
                rows.append(col1 + values)

        else:
            # observations list
            rows.append(["Observations:"])
            for idx in range(self.lw.count()):
                rows.append([""])
                rows.append(["Observation id", self.lw.item(idx).text()])
                rows.append([
                    "Observation date",
                    self.pj[OBSERVATIONS][self.lw.item(idx).text()].get(
                        "date", "")
                ])
                rows.append([
                    "Description",
                    utilities.eol2space(
                        self.pj[OBSERVATIONS][self.lw.item(idx).text()].get(
                            DESCRIPTION, ""))
                ])

                if INDEPENDENT_VARIABLES in self.pj[OBSERVATIONS][self.lw.item(
                        idx).text()]:
                    rows.append(["Independent variables:"])
                    for var in self.pj[OBSERVATIONS][self.lw.item(
                            idx).text()][INDEPENDENT_VARIABLES]:
                        rows.append([
                            var, self.pj[OBSERVATIONS][self.lw.item(
                                idx).text()][INDEPENDENT_VARIABLES][var]
                        ])

            if self.excluded_behaviors_list.text():
                s1, s2 = self.excluded_behaviors_list.text().split(": ")
                rows.extend([[""], [s1] + s2.split(", ")])

            rows.extend([[""], [""], ["Time budget:"]])

            # write header
            header = []
            for col_idx in range(self.twTB.columnCount()):
                header.append(self.twTB.horizontalHeaderItem(col_idx).text())

            rows.append(header)
            rows.append([""])

            for row in range(self.twTB.rowCount()):
                values = []
                for col_idx in range(self.twTB.columnCount()):
                    values.append(
                        intfloatstr(self.twTB.item(row, col_idx).text()))

                rows.append(values)

        max_row_length = max([len(r) for r in rows])
        data = tablib.Dataset()
        data.title = "Time budget"

        for row in rows:
            data.append(complete(row, max_row_length))

        if outputFormat in ["tsv", "csv", "html"]:
            with open(file_name, "wb") as f:
                f.write(str.encode(data.export(outputFormat)))
            return

        if outputFormat in ["ods", "xlsx", "xls"]:
            with open(file_name, "wb") as f:
                f.write(data.export(outputFormat))
            return