boris-behav-obs 9.7.8__tar.gz → 9.7.12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/PKG-INFO +2 -2
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/README.md +1 -1
- boris_behav_obs-9.7.12/boris/analysis_plugins/_export_to_feral.py +225 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/behav_coding_map_creator.py +7 -7
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/coding_pad.py +4 -3
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/core.py +19 -15
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/modifier_coding_map_creator.py +6 -6
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/observation_operations.py +48 -40
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/plot_spectrogram_rt.py +3 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/plot_waveform_rt.py +4 -1
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/plugins.py +60 -23
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/preferences_ui.py +2 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/project_functions.py +1 -1
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/subjects_pad.py +2 -2
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/utilities.py +9 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/version.py +2 -2
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/video_operations.py +3 -2
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris_behav_obs.egg-info/PKG-INFO +2 -2
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris_behav_obs.egg-info/SOURCES.txt +1 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/pyproject.toml +1 -1
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/LICENSE.TXT +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/MANIFEST.in +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/README.TXT +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/__init__.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/__main__.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/about.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/add_modifier.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/add_modifier_ui.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/advanced_event_filtering.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/analysis_plugins/__init__.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/analysis_plugins/_latency.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/analysis_plugins/irr_cohen_kappa.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/analysis_plugins/irr_weighted_cohen_kappa.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/analysis_plugins/list_of_dataframe_columns.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/analysis_plugins/number_of_occurences.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/analysis_plugins/number_of_occurences_by_independent_variable.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/analysis_plugins/time_budget.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/behavior_binary_table.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/behaviors_coding_map.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/boris_cli.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/cmd_arguments.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/config.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/config_file.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/connections.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/converters.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/converters_ui.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/cooccurence.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/core_qrc.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/core_ui.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/db_functions.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/dev.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/dialog.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/duration_widget.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/edit_event.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/edit_event_ui.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/event_operations.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/events_cursor.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/events_snapshots.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/exclusion_matrix.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/export_events.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/export_observation.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/external_processes.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/geometric_measurement.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/gui_utilities.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/image_overlay.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/import_observations.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/ipc_mpv.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/irr.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/latency.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/measurement_widget.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/media_file.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/menu_options.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/modifiers_coding_map.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/mpv.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/mpv2.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/observation.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/observation_ui.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/observations_list.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/otx_parser.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/param_panel.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/param_panel_ui.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/player_dock_widget.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/plot_data_module.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/plot_events.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/plot_events_rt.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/portion/__init__.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/portion/const.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/portion/dict.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/portion/func.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/portion/interval.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/portion/io.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/preferences.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/project.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/project_import_export.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/project_ui.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/qrc_boris.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/qrc_boris5.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/select_modifiers.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/select_observations.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/select_subj_behav.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/state_events.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/synthetic_time_budget.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/time_budget_functions.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/time_budget_widget.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/transitions.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/video_equalizer.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/video_equalizer_ui.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/view_df.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/view_df_ui.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris/write_event.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris_behav_obs.egg-info/dependency_links.txt +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris_behav_obs.egg-info/entry_points.txt +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris_behav_obs.egg-info/requires.txt +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/boris_behav_obs.egg-info/top_level.txt +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/setup.cfg +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/tests/test_db_functions.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/tests/test_export_observation.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/tests/test_irr.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/tests/test_observation_gui.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/tests/test_otx_parser.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/tests/test_preferences_gui.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/tests/test_project_functions.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/tests/test_time_budget.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/tests/test_utilities.py +0 -0
- {boris_behav_obs-9.7.8 → boris_behav_obs-9.7.12}/tests/test_utilities2.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: boris-behav-obs
|
|
3
|
-
Version: 9.7.
|
|
3
|
+
Version: 9.7.12
|
|
4
4
|
Summary: BORIS - Behavioral Observation Research Interactive Software
|
|
5
5
|
Author-email: Olivier Friard <olivier.friard@unito.it>
|
|
6
6
|
License-Expression: GPL-3.0-only
|
|
@@ -51,7 +51,7 @@ It provides also some analysis tools like time budget and some plotting function
|
|
|
51
51
|
<!-- The BO-RIS paper has more than [ citations](https://www.boris.unito.it/citations) in peer-reviewed scientific publications. -->
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
The BORIS paper has more than
|
|
54
|
+
The BORIS paper has more than 2472 citations in peer-reviewed scientific publications.
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
|
|
@@ -13,7 +13,7 @@ It provides also some analysis tools like time budget and some plotting function
|
|
|
13
13
|
<!-- The BO-RIS paper has more than [ citations](https://www.boris.unito.it/citations) in peer-reviewed scientific publications. -->
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
The BORIS paper has more than
|
|
16
|
+
The BORIS paper has more than 2472 citations in peer-reviewed scientific publications.
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BORIS plugin
|
|
3
|
+
|
|
4
|
+
Export to FERAL (getferal.ai)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from PySide6.QtWidgets import QFileDialog
|
|
12
|
+
|
|
13
|
+
# dependencies for CategoryDialog
|
|
14
|
+
from PySide6.QtWidgets import QListWidget, QListWidgetItem, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QDialog
|
|
15
|
+
from PySide6.QtCore import Qt
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__version__ = "0.1.1"
|
|
19
|
+
__version_date__ = "2025-11-28"
|
|
20
|
+
__plugin_name__ = "Export observations to FERAL"
|
|
21
|
+
__author__ = "Olivier Friard - University of Torino - Italy"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CategoryDialog(QDialog):
|
|
25
|
+
def __init__(self, items, parent=None):
|
|
26
|
+
super().__init__(parent)
|
|
27
|
+
|
|
28
|
+
self.setWindowTitle("Organize the videos in categories")
|
|
29
|
+
|
|
30
|
+
self.setModal(True)
|
|
31
|
+
|
|
32
|
+
# Main layout
|
|
33
|
+
main_layout = QVBoxLayout(self)
|
|
34
|
+
lists_layout = QHBoxLayout()
|
|
35
|
+
|
|
36
|
+
# All videos
|
|
37
|
+
self.list_unclassified = self._create_list_widget()
|
|
38
|
+
self.label_unclassified = QLabel("All videos")
|
|
39
|
+
col0_layout = QVBoxLayout()
|
|
40
|
+
col0_layout.addWidget(self.label_unclassified)
|
|
41
|
+
col0_layout.addWidget(self.list_unclassified)
|
|
42
|
+
|
|
43
|
+
self.list_cat1 = self._create_list_widget()
|
|
44
|
+
self.label_cat1 = QLabel("train")
|
|
45
|
+
col1_layout = QVBoxLayout()
|
|
46
|
+
col1_layout.addWidget(self.label_cat1)
|
|
47
|
+
col1_layout.addWidget(self.list_cat1)
|
|
48
|
+
|
|
49
|
+
self.list_cat2 = self._create_list_widget()
|
|
50
|
+
self.label_cat2 = QLabel("val")
|
|
51
|
+
col2_layout = QVBoxLayout()
|
|
52
|
+
col2_layout.addWidget(self.label_cat2)
|
|
53
|
+
col2_layout.addWidget(self.list_cat2)
|
|
54
|
+
|
|
55
|
+
self.list_cat3 = self._create_list_widget()
|
|
56
|
+
self.label_cat3 = QLabel("test")
|
|
57
|
+
col3_layout = QVBoxLayout()
|
|
58
|
+
col3_layout.addWidget(self.label_cat3)
|
|
59
|
+
col3_layout.addWidget(self.list_cat3)
|
|
60
|
+
|
|
61
|
+
self.list_cat4 = self._create_list_widget()
|
|
62
|
+
self.label_cat4 = QLabel("inference")
|
|
63
|
+
col4_layout = QVBoxLayout()
|
|
64
|
+
col4_layout.addWidget(self.label_cat4)
|
|
65
|
+
col4_layout.addWidget(self.list_cat4)
|
|
66
|
+
|
|
67
|
+
# Add all columns to the horizontal layout
|
|
68
|
+
lists_layout.addLayout(col0_layout)
|
|
69
|
+
lists_layout.addLayout(col1_layout)
|
|
70
|
+
lists_layout.addLayout(col2_layout)
|
|
71
|
+
lists_layout.addLayout(col3_layout)
|
|
72
|
+
lists_layout.addLayout(col4_layout)
|
|
73
|
+
|
|
74
|
+
main_layout.addLayout(lists_layout)
|
|
75
|
+
|
|
76
|
+
buttons_layout = QHBoxLayout()
|
|
77
|
+
self.btn_ok = QPushButton("OK")
|
|
78
|
+
self.btn_cancel = QPushButton("Cancel")
|
|
79
|
+
|
|
80
|
+
self.btn_ok.clicked.connect(self.accept)
|
|
81
|
+
self.btn_cancel.clicked.connect(self.reject)
|
|
82
|
+
|
|
83
|
+
buttons_layout.addStretch()
|
|
84
|
+
buttons_layout.addWidget(self.btn_ok)
|
|
85
|
+
buttons_layout.addWidget(self.btn_cancel)
|
|
86
|
+
|
|
87
|
+
main_layout.addLayout(buttons_layout)
|
|
88
|
+
|
|
89
|
+
# Populate "Unclassified" with input items
|
|
90
|
+
for text in items:
|
|
91
|
+
QListWidgetItem(text, self.list_unclassified)
|
|
92
|
+
|
|
93
|
+
def _create_list_widget(self):
|
|
94
|
+
"""
|
|
95
|
+
Create a QListWidget ready for drag & drop.
|
|
96
|
+
"""
|
|
97
|
+
lw = QListWidget()
|
|
98
|
+
lw.setSelectionMode(QListWidget.ExtendedSelection)
|
|
99
|
+
lw.setDragEnabled(True)
|
|
100
|
+
lw.setAcceptDrops(True)
|
|
101
|
+
lw.setDropIndicatorShown(True)
|
|
102
|
+
lw.setDragDropMode(QListWidget.DragDrop)
|
|
103
|
+
lw.setDefaultDropAction(Qt.MoveAction)
|
|
104
|
+
return lw
|
|
105
|
+
|
|
106
|
+
def get_categories(self):
|
|
107
|
+
"""
|
|
108
|
+
Return the content of all categories as a dictionary of lists.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
def collect(widget):
|
|
112
|
+
return [widget.item(i).text().rstrip("*") for i in range(widget.count())]
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
"unclassified": collect(self.list_unclassified),
|
|
116
|
+
"train": collect(self.list_cat1),
|
|
117
|
+
"val": collect(self.list_cat2),
|
|
118
|
+
"test": collect(self.list_cat3),
|
|
119
|
+
"inference": collect(self.list_cat4),
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def run(df: pd.DataFrame, project: dict):
|
|
124
|
+
"""
|
|
125
|
+
Export observations to FERAL
|
|
126
|
+
See https://www.getferal.ai/ > Label Preparation
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
out: dict = {
|
|
130
|
+
"is_multilabel": False,
|
|
131
|
+
"splits": {
|
|
132
|
+
"train": [],
|
|
133
|
+
"val": [],
|
|
134
|
+
"test": [],
|
|
135
|
+
"inference": [],
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
log: list = []
|
|
140
|
+
|
|
141
|
+
# class names
|
|
142
|
+
class_names = {x: project["behaviors_conf"][x]["code"] for x in project["behaviors_conf"]}
|
|
143
|
+
out["class_names"] = class_names
|
|
144
|
+
reversed_class_names = {project["behaviors_conf"][x]["code"]: int(x) for x in project["behaviors_conf"]}
|
|
145
|
+
log.append(f"{class_names=}")
|
|
146
|
+
|
|
147
|
+
observations: list = sorted([x for x in project["observations"]])
|
|
148
|
+
log.append(f"Selected observation: {observations}")
|
|
149
|
+
|
|
150
|
+
labels: dict = {}
|
|
151
|
+
video_list: list = []
|
|
152
|
+
for observation_id in observations:
|
|
153
|
+
log.append("---")
|
|
154
|
+
log.append(observation_id)
|
|
155
|
+
|
|
156
|
+
# check number of media file in player #1
|
|
157
|
+
if len(project["observations"][observation_id]["file"]["1"]) != 1:
|
|
158
|
+
log.append(f"The observation {observation_id} contains more than one video")
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
# check number of coded subjects
|
|
162
|
+
if len(set([x[1] for x in project["observations"][observation_id]["events"]])) > 1:
|
|
163
|
+
log.append(f"The observation {observation_id} contains more than one subject")
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
media_file_path: str = project["observations"][observation_id]["file"]["1"][0]
|
|
167
|
+
media_file_name = str(Path(media_file_path).name)
|
|
168
|
+
|
|
169
|
+
# skip if no events
|
|
170
|
+
if not project["observations"][observation_id]["events"]:
|
|
171
|
+
video_list.append(media_file_name)
|
|
172
|
+
log.append(f"No events for observation {observation_id}")
|
|
173
|
+
continue
|
|
174
|
+
else:
|
|
175
|
+
video_list.append(media_file_name + "*")
|
|
176
|
+
|
|
177
|
+
# extract FPS
|
|
178
|
+
FPS = project["observations"][observation_id]["media_info"]["fps"][media_file_path]
|
|
179
|
+
log.append(f"{media_file_name} {FPS=}")
|
|
180
|
+
# extract media duration
|
|
181
|
+
duration = project["observations"][observation_id]["media_info"]["length"][media_file_path]
|
|
182
|
+
log.append(f"{media_file_name} {duration=}")
|
|
183
|
+
|
|
184
|
+
number_of_frames = int(duration / (1 / FPS))
|
|
185
|
+
log.append(f"{number_of_frames=}")
|
|
186
|
+
|
|
187
|
+
labels[media_file_name] = [0] * number_of_frames
|
|
188
|
+
|
|
189
|
+
for idx in range(number_of_frames):
|
|
190
|
+
t = idx * (1 / FPS)
|
|
191
|
+
behaviors = (
|
|
192
|
+
df[(df["Observation id"] == observation_id) & (df["Start (s)"] <= t) & (df["Stop (s)"] >= t)]["Behavior"].unique().tolist()
|
|
193
|
+
)
|
|
194
|
+
if len(behaviors) > 1:
|
|
195
|
+
log.append(f"The observation {observation_id} contains more than one behavior for frame {idx}")
|
|
196
|
+
del labels[media_file_name]
|
|
197
|
+
break
|
|
198
|
+
if behaviors:
|
|
199
|
+
behaviors_idx = reversed_class_names[behaviors[0]]
|
|
200
|
+
labels[media_file_name][idx] = behaviors_idx
|
|
201
|
+
|
|
202
|
+
out["labels"] = labels
|
|
203
|
+
|
|
204
|
+
# splits
|
|
205
|
+
dlg = CategoryDialog(video_list)
|
|
206
|
+
|
|
207
|
+
if dlg.exec(): # Dialog accepted
|
|
208
|
+
result = dlg.get_categories()
|
|
209
|
+
del result["unclassified"]
|
|
210
|
+
out["splits"] = result
|
|
211
|
+
|
|
212
|
+
filename, _ = QFileDialog.getSaveFileName(
|
|
213
|
+
None,
|
|
214
|
+
"Choose a file to save",
|
|
215
|
+
"", # start directory
|
|
216
|
+
"JSON files (*.json);;All files (*.*)",
|
|
217
|
+
)
|
|
218
|
+
if filename:
|
|
219
|
+
with open(filename, "w") as f_out:
|
|
220
|
+
f_out.write(json.dumps(out, separators=(",", ": "), indent=1))
|
|
221
|
+
|
|
222
|
+
else:
|
|
223
|
+
log.append("splits section missing")
|
|
224
|
+
|
|
225
|
+
return "\n".join(log)
|
|
@@ -119,12 +119,12 @@ class BehaviorsMapCreatorWindow(QMainWindow):
|
|
|
119
119
|
self.saveMapAction.setShortcut("Ctrl+S")
|
|
120
120
|
self.saveMapAction.setStatusTip("Save the behavior coding map")
|
|
121
121
|
self.saveMapAction.setEnabled(False)
|
|
122
|
-
self.saveMapAction.triggered.connect(self.
|
|
122
|
+
self.saveMapAction.triggered.connect(self.save_map_clicked)
|
|
123
123
|
|
|
124
124
|
self.saveAsMapAction = QAction(QIcon(), "Save the behavior coding map as ...", self)
|
|
125
125
|
self.saveAsMapAction.setStatusTip("Save the behavior coding map as ...")
|
|
126
126
|
self.saveAsMapAction.setEnabled(False)
|
|
127
|
-
self.saveAsMapAction.triggered.connect(self.
|
|
127
|
+
self.saveAsMapAction.triggered.connect(self.save_as_map_clicked)
|
|
128
128
|
|
|
129
129
|
self.mapNameAction = QAction(QIcon(), "&Edit name of behaviors coding map", self)
|
|
130
130
|
self.mapNameAction.setShortcut("Ctrl+M")
|
|
@@ -389,7 +389,7 @@ class BehaviorsMapCreatorWindow(QMainWindow):
|
|
|
389
389
|
)
|
|
390
390
|
|
|
391
391
|
if response == cfg.SAVE:
|
|
392
|
-
if not self.
|
|
392
|
+
if not self.save_map_clicked():
|
|
393
393
|
event.ignore()
|
|
394
394
|
|
|
395
395
|
if response == cfg.CANCEL:
|
|
@@ -615,7 +615,7 @@ class BehaviorsMapCreatorWindow(QMainWindow):
|
|
|
615
615
|
)
|
|
616
616
|
|
|
617
617
|
if response == cfg.SAVE:
|
|
618
|
-
if not self.
|
|
618
|
+
if not self.save_map_clicked():
|
|
619
619
|
return
|
|
620
620
|
|
|
621
621
|
if response == cfg.CANCEL:
|
|
@@ -659,7 +659,7 @@ class BehaviorsMapCreatorWindow(QMainWindow):
|
|
|
659
659
|
["Save", "Discard", "Cancel"],
|
|
660
660
|
)
|
|
661
661
|
|
|
662
|
-
if (response == "Save" and not self.
|
|
662
|
+
if (response == "Save" and not self.save_map_clicked()) or (response == "Cancel"):
|
|
663
663
|
return
|
|
664
664
|
|
|
665
665
|
fileName, _ = QFileDialog(self).getOpenFileName(
|
|
@@ -783,7 +783,7 @@ class BehaviorsMapCreatorWindow(QMainWindow):
|
|
|
783
783
|
else:
|
|
784
784
|
return False
|
|
785
785
|
|
|
786
|
-
def
|
|
786
|
+
def save_as_map_clicked(self):
|
|
787
787
|
filters = "Behaviors coding map (*.behav_coding_map);;All files (*)"
|
|
788
788
|
|
|
789
789
|
self.fileName, _ = QFileDialog.getSaveFileName(self, "Save behaviors coding map as", "", filters)
|
|
@@ -794,7 +794,7 @@ class BehaviorsMapCreatorWindow(QMainWindow):
|
|
|
794
794
|
self.fileName += ".behav_coding_map"
|
|
795
795
|
self.saveMap()
|
|
796
796
|
|
|
797
|
-
def
|
|
797
|
+
def save_map_clicked(self):
|
|
798
798
|
if not self.fileName:
|
|
799
799
|
self.fileName, _ = QFileDialog().getSaveFileName(
|
|
800
800
|
self,
|
|
@@ -38,7 +38,7 @@ class Button(QWidget):
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
class CodingPad(QWidget):
|
|
41
|
-
|
|
41
|
+
click_signal = Signal(str)
|
|
42
42
|
sendEventSignal = Signal(QEvent)
|
|
43
43
|
close_signal = Signal(QRect, dict)
|
|
44
44
|
|
|
@@ -208,7 +208,8 @@ class CodingPad(QWidget):
|
|
|
208
208
|
"""
|
|
209
209
|
Button clicked
|
|
210
210
|
"""
|
|
211
|
-
|
|
211
|
+
print(f"{behavior_code=}")
|
|
212
|
+
self.click_signal.emit(behavior_code)
|
|
212
213
|
|
|
213
214
|
def eventFilter(self, receiver, event) -> bool:
|
|
214
215
|
"""
|
|
@@ -261,7 +262,7 @@ def show_coding_pad(self):
|
|
|
261
262
|
self.codingpad.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
262
263
|
self.codingpad.sendEventSignal.connect(self.signal_from_widget)
|
|
263
264
|
|
|
264
|
-
self.codingpad.
|
|
265
|
+
self.codingpad.click_signal.connect(self.click_signal_from_coding_pad)
|
|
265
266
|
self.codingpad.close_signal.connect(self.close_signal_from_coding_pad)
|
|
266
267
|
self.codingpad.show()
|
|
267
268
|
|
|
@@ -630,7 +630,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
630
630
|
"""
|
|
631
631
|
handle click received from coding pad
|
|
632
632
|
"""
|
|
633
|
-
q = QKeyEvent(QEvent.KeyPress, Qt.Key_Enter, Qt.NoModifier, text=behaviorCode)
|
|
633
|
+
q = QKeyEvent(QEvent.Type.KeyPress, Qt.Key.Key_Enter, Qt.KeyboardModifier.NoModifier, text=behaviorCode)
|
|
634
|
+
|
|
634
635
|
self.keyPressEvent(q)
|
|
635
636
|
|
|
636
637
|
def close_signal_from_coding_pad(self, geometry, preferences):
|
|
@@ -644,7 +645,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
644
645
|
"""
|
|
645
646
|
handle click received from subjects pad
|
|
646
647
|
"""
|
|
647
|
-
q = QKeyEvent(QEvent.KeyPress, Qt.Key_Enter, Qt.NoModifier, text="#subject#" + subject)
|
|
648
|
+
q = QKeyEvent(QEvent.Type.KeyPress, Qt.Key.Key_Enter, Qt.KeyboardModifier.NoModifier, text="#subject#" + subject)
|
|
648
649
|
self.keyPressEvent(q)
|
|
649
650
|
|
|
650
651
|
def signal_from_subjects_pad(self, event):
|
|
@@ -698,7 +699,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
698
699
|
self.subjects_pad = subjects_pad.SubjectsPad(self.pj, filtered_subjects)
|
|
699
700
|
self.subjects_pad.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
700
701
|
self.subjects_pad.sendEventSignal.connect(self.signal_from_subjects_pad)
|
|
701
|
-
self.subjects_pad.
|
|
702
|
+
self.subjects_pad.click_signal.connect(self.click_signal_from_subjects_pad)
|
|
702
703
|
self.subjects_pad.close_signal.connect(self.close_signal_from_subjects_pad)
|
|
703
704
|
self.subjects_pad.show()
|
|
704
705
|
|
|
@@ -1500,7 +1501,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1500
1501
|
logging.debug("previous media file")
|
|
1501
1502
|
|
|
1502
1503
|
if self.playerType == cfg.MEDIA:
|
|
1503
|
-
#if len(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][cfg.PLAYER1]) == 1:
|
|
1504
|
+
# if len(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][cfg.PLAYER1]) == 1:
|
|
1504
1505
|
# self.seek_mediaplayer(dec(0))
|
|
1505
1506
|
# return
|
|
1506
1507
|
|
|
@@ -1510,7 +1511,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1510
1511
|
|
|
1511
1512
|
elif self.dw_player[0].player.playlist_count == 1:
|
|
1512
1513
|
self.seek_mediaplayer(dec(0))
|
|
1513
|
-
#self.statusbar.showMessage("There is only one media file", 5000)
|
|
1514
|
+
# self.statusbar.showMessage("There is only one media file", 5000)
|
|
1514
1515
|
|
|
1515
1516
|
if hasattr(self, "spectro"):
|
|
1516
1517
|
self.spectro.memChunk = -1
|
|
@@ -4151,8 +4152,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4151
4152
|
called only in IPC mode
|
|
4152
4153
|
"""
|
|
4153
4154
|
# check if eof reached
|
|
4154
|
-
#print(f"{self.dw_player[0].player.playlist_pos=}")
|
|
4155
|
-
#print(f"{self.dw_player[0].player.playlist_count=}")
|
|
4155
|
+
# print(f"{self.dw_player[0].player.playlist_pos=}")
|
|
4156
|
+
# print(f"{self.dw_player[0].player.playlist_count=}")
|
|
4156
4157
|
if self.dw_player[0].player.eof_reached and self.dw_player[0].player.core_idle:
|
|
4157
4158
|
logging.debug("end of playlist reached")
|
|
4158
4159
|
if self.dw_player[0].player.playlist_pos is not None and self.dw_player[0].player.playlist_count is not None:
|
|
@@ -4220,8 +4221,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4220
4221
|
|
|
4221
4222
|
# sync players 2..8 if time diff >= 1 s
|
|
4222
4223
|
if not math.isnan(ct) and not math.isnan(ct0):
|
|
4223
|
-
if
|
|
4224
|
-
|
|
4224
|
+
if (
|
|
4225
|
+
abs(ct0 - (ct + dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)])))
|
|
4226
|
+
>= 1
|
|
4227
|
+
):
|
|
4228
|
+
self.sync_time(n_player, float(ct0)) # self.seek_mediaplayer(ct0, n_player)
|
|
4225
4229
|
|
|
4226
4230
|
currentTimeOffset = dec(cumulative_time_pos + self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TIME_OFFSET])
|
|
4227
4231
|
|
|
@@ -4254,7 +4258,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4254
4258
|
# current media name
|
|
4255
4259
|
playlist = self.dw_player[0].player.playlist
|
|
4256
4260
|
playlist_pos = self.dw_player[0].player.playlist_pos
|
|
4257
|
-
if playlist is not None and playlist_pos is not None:
|
|
4261
|
+
if playlist is not None and playlist_pos is not None and playlist:
|
|
4258
4262
|
current_media_name = Path(playlist[playlist_pos]["filename"]).name
|
|
4259
4263
|
current_playlist_index = playlist_pos
|
|
4260
4264
|
else:
|
|
@@ -4335,7 +4339,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4335
4339
|
# set video scroll bar
|
|
4336
4340
|
|
|
4337
4341
|
if scroll_slider and not self.user_move_slider:
|
|
4338
|
-
|
|
4342
|
+
if current_media_time_pos is not None and current_media_duration is not None:
|
|
4343
|
+
self.video_slider.setValue(round(current_media_time_pos / current_media_duration * (cfg.SLIDER_MAXIMUM - 1)))
|
|
4339
4344
|
|
|
4340
4345
|
def mpv_eof_reached(self):
|
|
4341
4346
|
"""
|
|
@@ -4842,7 +4847,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4842
4847
|
|
|
4843
4848
|
# play / pause with space bar
|
|
4844
4849
|
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.LIVE:
|
|
4845
|
-
if ek in (Qt.Key_Space, Qt.Key_Enter, Qt.Key_Return):
|
|
4850
|
+
# if ek in (Qt.Key_Space, Qt.Key_Enter, Qt.Key_Return):
|
|
4851
|
+
if ek == Qt.Key_Space:
|
|
4846
4852
|
if self.liveObservationStarted:
|
|
4847
4853
|
if (
|
|
4848
4854
|
dialog.MessageDialog(cfg.programName, "Are you sure to stop the current live observation?", [cfg.YES, cfg.NO])
|
|
@@ -5485,7 +5491,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5485
5491
|
if self.dw_player[0].player.playlist_pos == self.dw_player[0].player.playlist_count - 1:
|
|
5486
5492
|
self.seek_mediaplayer(dec(0))
|
|
5487
5493
|
|
|
5488
|
-
|
|
5489
5494
|
# check if player 1 is ended
|
|
5490
5495
|
for i, dw in enumerate(self.dw_player):
|
|
5491
5496
|
if (
|
|
@@ -5867,7 +5872,7 @@ def main():
|
|
|
5867
5872
|
QMessageBox.warning(
|
|
5868
5873
|
None,
|
|
5869
5874
|
cfg.programName,
|
|
5870
|
-
(
|
|
5875
|
+
("This version of BORIS for macOS is still EXPERIMENTAL and should be used at your own risk."),
|
|
5871
5876
|
QMessageBox.Ok | QMessageBox.Default,
|
|
5872
5877
|
QMessageBox.NoButton,
|
|
5873
5878
|
)
|
|
@@ -5898,4 +5903,3 @@ def main():
|
|
|
5898
5903
|
del window
|
|
5899
5904
|
|
|
5900
5905
|
sys.exit(return_code)
|
|
5901
|
-
|
|
@@ -134,7 +134,7 @@ class ModifiersMapCreatorWindow(QMainWindow):
|
|
|
134
134
|
self.saveMapAction.setShortcut("Ctrl+S")
|
|
135
135
|
self.saveMapAction.setStatusTip("Save modifiers map")
|
|
136
136
|
self.saveMapAction.setEnabled(False)
|
|
137
|
-
self.saveMapAction.triggered.connect(self.
|
|
137
|
+
self.saveMapAction.triggered.connect(self.save_map_clicked)
|
|
138
138
|
|
|
139
139
|
self.saveAsMapAction = QAction(QIcon(), "Save modifiers map as", self)
|
|
140
140
|
self.saveAsMapAction.setStatusTip("Save modifiers map as")
|
|
@@ -343,7 +343,7 @@ class ModifiersMapCreatorWindow(QMainWindow):
|
|
|
343
343
|
)
|
|
344
344
|
|
|
345
345
|
if response == cfg.SAVE:
|
|
346
|
-
if not self.
|
|
346
|
+
if not self.save_map_clicked():
|
|
347
347
|
event.ignore()
|
|
348
348
|
|
|
349
349
|
if response == cfg.CANCEL:
|
|
@@ -567,7 +567,7 @@ class ModifiersMapCreatorWindow(QMainWindow):
|
|
|
567
567
|
)
|
|
568
568
|
|
|
569
569
|
if response == "Save":
|
|
570
|
-
if not self.
|
|
570
|
+
if not self.save_map_clicked():
|
|
571
571
|
return
|
|
572
572
|
|
|
573
573
|
if response == "Cancel":
|
|
@@ -606,7 +606,7 @@ class ModifiersMapCreatorWindow(QMainWindow):
|
|
|
606
606
|
)
|
|
607
607
|
|
|
608
608
|
if response == cfg.SAVE:
|
|
609
|
-
if not self.
|
|
609
|
+
if not self.save_map_clicked():
|
|
610
610
|
return
|
|
611
611
|
|
|
612
612
|
if response == cfg.CANCEL:
|
|
@@ -733,7 +733,7 @@ class ModifiersMapCreatorWindow(QMainWindow):
|
|
|
733
733
|
self.fileName += ".boris_map"
|
|
734
734
|
self.saveMap()
|
|
735
735
|
|
|
736
|
-
def
|
|
736
|
+
def save_map_clicked(self):
|
|
737
737
|
if not self.fileName:
|
|
738
738
|
fn = QFileDialog(self).getSaveFileName(
|
|
739
739
|
self,
|
|
@@ -746,7 +746,7 @@ class ModifiersMapCreatorWindow(QMainWindow):
|
|
|
746
746
|
else:
|
|
747
747
|
self.fileName = fn
|
|
748
748
|
|
|
749
|
-
if self.fileName and Path(self.fileName).suffix
|
|
749
|
+
if self.fileName and Path(self.fileName).suffix != ".boris_map":
|
|
750
750
|
self.fileName += ".boris_map"
|
|
751
751
|
|
|
752
752
|
if self.fileName:
|