def add_media_from_dir(self, n_player, flag_path): """ add all media from a selected directory Args: nPlayer (str): player flag_path (bool): True include full path of media else only basename """ dirName = QFileDialog().getExistingDirectory(self, "Select directory") if dirName: r = "" for file_path in glob.glob(dirName + os.sep + "*"): if not self.check_media(n_player, file_path, flag_path): if r != "Skip all non media files": r = dialog.MessageDialog(programName, ( "The <b>{file_path}</b> file does not seem to be a media file." "").format(file_path=file_path), [ "Continue", "Skip all non media files", "Cancel" ]) if r == "Cancel": break self.cbVisualizeSpectrogram.setEnabled(self.twVideo1.rowCount() > 0) self.cbCloseCurrentBehaviorsBetweenVideo.setEnabled( self.twVideo1.rowCount() > 0)
def add_media_from_dir(self, n_player, flag_path): """ add all media from a selected directory Args: n_player (str): player 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 = "" for file_path in glob.glob(dir_name + os.sep + "*"): if not self.check_media(n_player, file_path, flag_path): if r != "Skip all non media files": r = dialog.MessageDialog(programName, ("The <b>{file_path}</b> file does not seem to be a media file." "").format(file_path=file_path), ["Continue", "Skip all non media files", "Cancel"]) if r == "Cancel": break self.cbVisualizeSpectrogram.setEnabled(self.twVideo1.rowCount() > 0) self.cb_visualize_waveform.setEnabled(self.twVideo1.rowCount() > 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')
def view_data_file_head(self): """ view first parts of data file """ if self.tw_data_files.selectedIndexes() or self.tw_data_files.rowCount() == 1: if self.tw_data_files.rowCount() == 1: header = self.return_file_header(self.tw_data_files.item(0, 0).text()) else: header = self.return_file_header(self.tw_data_files.item(self.tw_data_files.selectedIndexes()[0].row(), 0).text()) if header: dialog.MessageDialog(programName, "<pre>{}</pre>".format(header), [OK]) ''' self.data_file_head = dialog.ResultsWidget() #self.results.setWindowFlags(Qt.WindowStaysOnTopHint) self.data_file_head.resize(540, 340) self.data_file_head.setWindowTitle(programName + " - Data file first lines") self.data_file_head.lb.setText(os.path.basename(self.tw_data_files.item(self.tw_data_files.selectedIndexes()[0].row(), 0).text())) self.data_file_head.ptText.setReadOnly(True) self.data_file_head.ptText.appendHtml("<pre>" + text + "</pre>") self.data_file_head.show() ''' else: QMessageBox.warning(self, programName, "Select a data file")
def add_media_from_dir(self, n_player, flag_path): """ add all media from a selected directory Args: nPlayer (str): player flag_path (bool): True include full path of media else only basename """ fd = QFileDialog(self) 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 = "" for file_path in glob.glob(dir_name + os.sep + "*"): if not self.check_media(n_player, file_path, flag_path): if r != "Skip all non media files": r = dialog.MessageDialog(programName, ("<b>{file_path}</b> arquivo não parece ser um arquivo de mídia." "").format(file_path=file_path), ["Continue", "Skip all non media files", "Cancel"]) if r == "Cancel": break self.cbVisualizeSpectrogram.setEnabled(self.twVideo1.rowCount() > 0) self.cbCloseCurrentBehaviorsBetweenVideo.setEnabled(self.twVideo1.rowCount() > 0)
def tabWidgetModifiersSets_changed(self, tabIndex): """ user changed the tab widget """ print("tabIndex", tabIndex) print("self.modifiers_sets_dict", self.modifiers_sets_dict) # 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 print(self.modifiers_sets_dict) 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"])
def pb_cancel_widget_clicked(self): if self.flag_modified: if dialog.MessageDialog( "BORIS", "The converters were modified. Are sure to quit?", [CANCEL, OK]) == OK: self.reject() else: self.reject()
def delete_converter(self): """Delete selected converter""" if self.tw_converters.selectedIndexes(): if dialog.MessageDialog("BORIS", "Confirm converter deletion", [CANCEL, OK]) == OK: self.tw_converters.removeRow(self.tw_converters.selectedIndexes()[0].row()) else: QMessageBox.warning(self, programName, "Select a converter in the table")
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()
def pb_pushed(self, button): if self.leModifier.text(): if dialog.MessageDialog(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", CANCEL]) == CANCEL: return if button == "ok": self.accept() if button == "cancel": self.reject()
def generate_spectrogram(self): """ generate spectrogram of all media files loaded in player #1 """ if self.cbVisualizeSpectrogram.isChecked(): if dialog.MessageDialog( programName, ("You choose to visualize the spectrogram for the media in player #1.<br>" "Choose YES to generate the spectrogram.\n\n" "Spectrogram generation can take some time for long media, be patient" ), [YES, NO]) == YES: if not self.ffmpeg_cache_dir: tmp_dir = tempfile.gettempdir() else: tmp_dir = self.ffmpeg_cache_dir w = dialog.Info_widget() w.resize(350, 100) w.setWindowFlags(Qt.WindowStaysOnTopHint) w.setWindowTitle("BORIS") w.label.setText("Generating spectrogram...") #for media in self.pj[OBSERVATIONS][self.observationId][FILE][PLAYER1]: for row in range(self.twVideo1.rowCount()): media_file_path = project_functions.media_full_path( self.twVideo1.item(row, 0).text(), self.project_path) if os.path.isfile(media_file_path): process = plot_spectrogram.create_spectrogram_multiprocessing( mediaFile=media_file_path, tmp_dir=tmp_dir, chunk_size=self.chunk_length, ffmpeg_bin=self.ffmpeg_bin, spectrogramHeight=self.spectrogramHeight, spectrogram_color_map=self.spectrogram_color_map) if process: w.show() while True: QApplication.processEvents() if not process.is_alive(): w.hide() break else: QMessageBox.warning( self, programName, "<b>{}</b> file not found".format(media_file_path)) else: self.cbVisualizeSpectrogram.setChecked(False)
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")
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()
def removeSet(self): """ remove set of modifiers """ if self.tabWidgetModifiersSets.currentIndex() != -1: if dialog.MessageDialog( programName, "Confirm deletion of this set of modifiers?", [YES, NO]) == 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) else: QMessageBox.information( self, programName, "It is not possible to remove the last modifiers' set.")
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 = Decimal("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 [Decimal("0"), Decimal("-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 = Decimal("-1") return max_obs_length, selectedObsTotalMediaLength
def generate_spectrogram(self): """ generate spectrogram of all media files loaded in player #1 """ if self.cbVisualizeSpectrogram.isChecked(): if dialog.MessageDialog(programName, ("Você escolhe visualizar o espectrograma para a mídia no player #1.<br>" "Escolha SIM para gerar o espectrograma.\n\n" "Geração de espectrograma pode levar algum tempo para mídia longa, seja paciente"), [YES, NO]) == YES: if not self.ffmpeg_cache_dir: tmp_dir = tempfile.gettempdir() else: tmp_dir = self.ffmpeg_cache_dir w = dialog.Info_widget() w.resize(350, 100) w.setWindowFlags(Qt.WindowStaysOnTopHint) w.setWindowTitle("eMOC") w.label.setText("Gerando espectrograma...") #for media in self.pj[OBSERVATIONS][self.observationId][FILE][PLAYER1]: for row in range(self.twVideo1.rowCount()): media_file_path = project_functions.media_full_path(self.twVideo1.item(row, 0).text(), self.project_path) if os.path.isfile(media_file_path): process = plot_spectrogram.create_spectrogram_multiprocessing(mediaFile=media_file_path, tmp_dir=tmp_dir, chunk_size=self.chunk_length, ffmpeg_bin=self.ffmpeg_bin, spectrogramHeight=self.spectrogramHeight, spectrogram_color_map=self.spectrogram_color_map) if process: w.show() while True: QApplication.processEvents() if not process.is_alive(): w.hide() break else: QMessageBox.warning(self, programName , "<b>{}</b> Arquivo não encontrado".format(media_file_path)) else: self.cbVisualizeSpectrogram.setChecked(False)
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' )
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?", ["Open", "Edit", config.CANCEL]) if response == "Open": self.done(2) if response == "Edit": self.done(3)
def add_media_from_dir(self, n_player, flag_path): """ add all media from a selected directory Args: n_player (str): player 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 = "" for file_path in glob.glob(dir_name + os.sep + "*"): r, msg = self.check_media(n_player, file_path, flag_path) if not r: if r != "Skip all non media files": r = dialog.MessageDialog( programName, f"<b>{file_path}</b> {msg}", ["Continue", "Skip all non media files", "Cancel"]) if r == "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)
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])
def behavior_binary_table(pj: dict): """ ask user for parameters for behavior binary table call create_behavior_binary_table """ result, selected_observations = select_observations.select_observations( pj, MULTIPLE, "Select observations for the behavior binary table") if not selected_observations: return # check if state events are paired out = "" not_paired_obs_list = [] for obs_id in selected_observations: r, msg = project_functions.check_state_events_obs( obs_id, pj[ETHOGRAM], pj[OBSERVATIONS][obs_id]) if not r: out += f"Observation: <strong>{obs_id}</strong><br>{msg}<br>" not_paired_obs_list.append(obs_id) if out: out = f"The observations with UNPAIRED state events will be removed from the analysis<br><br>{out}" results = dialog.Results_dialog() results.setWindowTitle(f"{programName} - Check selected observations") results.ptText.setReadOnly(True) results.ptText.appendHtml(out) results.pbSave.setVisible(False) results.pbCancel.setVisible(True) if not results.exec_(): return selected_observations = [ x for x in selected_observations if x not in not_paired_obs_list ] if not selected_observations: return max_obs_length, selectedObsTotalMediaLength = project_functions.observation_length( pj, selected_observations) if max_obs_length == -1: # media length not available, user choose to not use events return parameters = dialog.choose_obs_subj_behav_category( pj, selected_observations, maxTime=max_obs_length, flagShowIncludeModifiers=True, flagShowExcludeBehaviorsWoEvents=True, by_category=False) if not parameters[SELECTED_SUBJECTS] or not parameters[SELECTED_BEHAVIORS]: QMessageBox.warning(None, programName, "Select subject(s) and behavior(s) to analyze") return # ask for time interval i, ok = QInputDialog.getDouble(None, "Behavior binary table", "Time interval (in seconds):", 1.0, 0.001, 86400, 3) if not ok: return time_interval = utilities.float2decimal(i) ''' iw = dialog.Info_widget() iw.lwi.setVisible(False) iw.resize(350, 200) iw.setWindowFlags(Qt.WindowStaysOnTopHint) iw.setWindowTitle("Behavior binary table") iw.label.setText("Creating the behavior binary table...") iw.show() QApplication.processEvents() ''' results_df = create_behavior_binary_table(pj, selected_observations, parameters, time_interval) ''' iw.hide() ''' if "error" in results_df: QMessageBox.warning(None, programName, results_df["msg"]) return # save results if len(selected_observations) == 1: extended_file_formats = [ "Tab Separated Values (*.tsv)", "Comma Separated Values (*.csv)", "Open Document Spreadsheet ODS (*.ods)", "Microsoft Excel Spreadsheet XLSX (*.xlsx)", "Legacy Microsoft Excel Spreadsheet XLS (*.xls)", "HTML (*.html)" ] file_formats = ["tsv", "csv", "ods", "xlsx", "xls", "html"] file_name, filter_ = QFileDialog().getSaveFileName( None, "Save results", "", ";;".join(extended_file_formats)) if not file_name: return output_format = file_formats[extended_file_formats.index(filter_)] if pathlib.Path(file_name).suffix != "." + output_format: file_name = str(pathlib.Path(file_name)) + "." + output_format # check if file with new extension already exists if pathlib.Path(file_name).is_file(): if dialog.MessageDialog( programName, f"The file {file_name} already exists.", [CANCEL, OVERWRITE]) == CANCEL: return else: items = ("Tab Separated Values (*.tsv)", "Comma separated values (*.csv)", "Open Document Spreadsheet (*.ods)", "Microsoft Excel Spreadsheet XLSX (*.xlsx)", "Legacy Microsoft Excel Spreadsheet XLS (*.xls)", "HTML (*.html)") item, ok = QInputDialog.getItem(None, "Save results", "Available formats", items, 0, False) if not ok: return output_format = re.sub(".* \(\*\.", "", item)[:-1] export_dir = QFileDialog().getExistingDirectory( None, "Choose a directory to save results", os.path.expanduser("~"), options=QFileDialog.ShowDirsOnly) if not export_dir: return mem_command = "" for obs_id in results_df: for subject in results_df[obs_id]: if len(selected_observations) > 1: file_name_with_subject = str( pathlib.Path(export_dir) / utilities.safeFileName(obs_id + "_" + subject)) + "." + output_format else: file_name_with_subject = str( os.path.splitext(file_name)[0] + utilities.safeFileName("_" + subject)) + "." + output_format # check if file with new extension already exists if mem_command != OVERWRITE_ALL and pathlib.Path( file_name_with_subject).is_file(): if mem_command == "Skip all": continue mem_command = dialog.MessageDialog( programName, f"The file {file_name_with_subject} already exists.", [OVERWRITE, OVERWRITE_ALL, "Skip", "Skip all", CANCEL]) if mem_command == CANCEL: return if mem_command in ["Skip", "Skip all"]: continue try: if output_format in ["csv", "tsv", "html"]: with open(file_name_with_subject, "wb") as f: f.write( str.encode(results_df[obs_id][subject].export( output_format))) if output_format in ["ods", "xlsx", "xls"]: with open(file_name_with_subject, "wb") as f: f.write( results_df[obs_id][subject].export(output_format)) except Exception: error_type, error_file_name, error_lineno = utilities.error_info( sys.exc_info()) logging.critical( f"Error in behavior binary table function: {error_type} {error_file_name} {error_lineno}" ) QMessageBox.critical(None, programName, f"Error saving file: {error_type}") return
def 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"] filediag_func = QFileDialog().getSaveFileName file_name, filter_ = filediag_func(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, "The file {} already exists.".format(file_name), [CANCEL, OVERWRITE]) == CANCEL: return rows = [] # observations list rows.append(["Observations:"]) for idx in range(self.lw.count()): rows.append([""]) rows.append([self.lw.item(idx).text()]) 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 cols = [] for col in range(self.twTB.columnCount()): cols.append(self.twTB.horizontalHeaderItem(col).text()) rows.append(cols) rows.append([""]) for row in range(self.twTB.rowCount()): values = [] for col in range(self.twTB.columnCount()): values.append(intfloatstr(self.twTB.item(row, col).text())) rows.append(values) maxLen = max([len(r) for r in rows]) data = tablib.Dataset() data.title = "Time budget" for row in rows: data.append(complete(row, maxLen)) 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
def extract_wav(self): """ extract wav of all media files loaded in player #1 """ if self.cbVisualizeSpectrogram.isChecked() or self.cb_visualize_waveform.isChecked(): flag_wav_produced = False # check if player 1 is selected flag_player1 = False for row in range(self.twVideo1.rowCount()): if self.twVideo1.cellWidget(row, 0).currentText() == "1": flag_player1 = True if not flag_player1: QMessageBox.critical(self, programName , "The player #1 is not selected") self.cbVisualizeSpectrogram.setChecked(False) self.cb_visualize_waveform.setChecked(False) return if dialog.MessageDialog(programName, ("You choose to visualize the spectrogram or waveform for the media in player #1.<br>" "The WAV will be extracted from the media files, be patient"), [YES, NO]) == YES: w = dialog.Info_widget() w.resize(350, 100) w.setWindowFlags(Qt.WindowStaysOnTopHint) w.setWindowTitle("BORIS") w.label.setText("Extracting WAV from media files...") for row in range(self.twVideo1.rowCount()): # check if player 1 if self.twVideo1.cellWidget(row, 0).currentText() != "1": continue media_file_path = project_functions.media_full_path(self.twVideo1.item(row, MEDIA_FILE_PATH_IDX).text(), self.project_path) if self.twVideo1.item(row, HAS_AUDIO_IDX).text() == "False": QMessageBox.critical(self, programName , f"The media file {media_file_path} do not seem to have audio") flag_wav_produced = False break if os.path.isfile(media_file_path): w.show() QApplication.processEvents() if utilities.extract_wav(self.ffmpeg_bin, media_file_path, self.tmp_dir) == "": QMessageBox.critical(self, programName , f"Error during extracting WAV of the media file {media_file_path}") flag_wav_produced = False break w.hide() flag_wav_produced = True else: QMessageBox.warning(self, programName , f"<b>{media_file_path}</b> file not found") if not flag_wav_produced: self.cbVisualizeSpectrogram.setChecked(False) self.cb_visualize_waveform.setChecked(False) else: self.cbVisualizeSpectrogram.setChecked(False) self.cb_visualize_waveform.setChecked(False)
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: QMessageBox.critical(self, programName, "The file {} seems not a behaviors coding map...".format(fileName)) return if "coding_map_type" not in self.codingMap or self.codingMap["coding_map_type"] != "BORIS behaviors coding map": QMessageBox.critical(self, programName, "The file {} seems not a BORIS behaviors coding map...".format(fileName)) self.cancelMap() self.mapName = self.codingMap["name"] self.setWindowTitle("{} - Behaviors coding map creator - {}".format(programName, 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.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)
def errbox(parent, msg): md = dialog.MessageDialog(parent, "Error", msg, 1) md.ShowModal()
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(self).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 ''' if QT_VERSION_STR[0] == "4": polygon = QGraphicsPolygonItem(None, None) else: polygon = QGraphicsPolygonItem() ''' polygon = QGraphicsPolygonItem(None, None) if QT_VERSION_STR[0] == "4" else 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.btCancelMap.setVisible(True)''' 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)
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, ""
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