boris-behav-obs 8.27.10__py2.py3-none-any.whl → 9.0.2__py2.py3-none-any.whl
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/about.py +7 -5
- boris/add_modifier.py +35 -35
- boris/add_modifier_ui.py +229 -129
- boris/advanced_event_filtering.py +3 -3
- boris/analysis_plugins/__init__.py +0 -0
- boris/analysis_plugins/number_of_occurences.py +60 -0
- boris/analysis_plugins/number_of_occurences_by_independent_variable.py +72 -0
- boris/analysis_plugins/time_budget.py +95 -0
- boris/behav_coding_map_creator.py +103 -108
- boris/behavior_binary_table.py +1 -1
- boris/behaviors_coding_map.py +8 -8
- boris/coding_pad.py +6 -6
- boris/config.py +6 -0
- boris/config_file.py +1 -1
- boris/connections.py +4 -2
- boris/converters.py +2 -3
- boris/converters_ui.py +187 -110
- boris/cooccurence.py +2 -2
- boris/core.py +341 -94
- boris/core_qrc.py +16088 -13246
- boris/core_ui.py +922 -812
- boris/dialog.py +14 -13
- boris/duration_widget.py +5 -5
- boris/edit_event.py +1 -1
- boris/edit_event_ui.py +162 -88
- boris/event_operations.py +4 -25
- boris/events_cursor.py +17 -9
- boris/events_snapshots.py +5 -5
- boris/exclusion_matrix.py +1 -1
- boris/export_events.py +38 -28
- boris/export_observation.py +1 -1
- boris/external_processes.py +3 -5
- boris/geometric_measurement.py +49 -26
- boris/gui_utilities.py +31 -30
- boris/import_observations.py +2 -4
- boris/irr.py +1 -1
- boris/latency.py +1 -1
- boris/map_creator.py +77 -89
- boris/measurement_widget.py +4 -4
- boris/media_file.py +2 -4
- boris/menu_options.py +1 -3
- boris/modifiers_coding_map.py +4 -4
- boris/mpv2.py +0 -2
- boris/observation.py +124 -29
- boris/observation_operations.py +18 -40
- boris/observation_ui.py +566 -374
- boris/observations_list.py +6 -6
- boris/param_panel.py +2 -2
- boris/param_panel_ui.py +246 -141
- boris/player_dock_widget.py +16 -21
- boris/plot_data_module.py +6 -6
- boris/plot_events_rt.py +7 -8
- boris/plot_spectrogram_rt.py +7 -8
- boris/plot_waveform_rt.py +6 -7
- boris/plugins.py +79 -0
- boris/preferences.py +127 -17
- boris/preferences_ui.py +464 -240
- boris/project.py +69 -72
- boris/project_functions.py +233 -31
- boris/project_import_export.py +59 -67
- boris/project_ui.py +672 -440
- boris/qrc_boris.py +6 -3
- boris/qrc_boris5.py +6 -3
- boris/select_modifiers.py +2 -2
- boris/select_observations.py +2 -2
- boris/select_subj_behav.py +3 -3
- boris/state_events.py +1 -1
- boris/subjects_pad.py +5 -5
- boris/synthetic_time_budget.py +2 -2
- boris/time_budget_functions.py +15 -0
- boris/time_budget_widget.py +4 -4
- boris/transitions.py +34 -25
- boris/utilities.py +96 -3
- boris/version.py +2 -2
- boris/video_equalizer.py +4 -4
- boris/video_equalizer_ui.py +199 -130
- boris/video_operations.py +1 -1
- boris/view_df.py +106 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +9 -1
- {boris_behav_obs-8.27.10.dist-info → boris_behav_obs-9.0.2.dist-info}/METADATA +5 -5
- boris_behav_obs-9.0.2.dist-info/RECORD +103 -0
- {boris_behav_obs-8.27.10.dist-info → boris_behav_obs-9.0.2.dist-info}/WHEEL +1 -1
- boris/qdarkstyle/__init__.py +0 -479
- boris/qdarkstyle/__main__.py +0 -66
- boris/qdarkstyle/colorsystem.py +0 -38
- boris/qdarkstyle/dark/__init__.py +0 -1
- boris/qdarkstyle/dark/darkstyle_rc.py +0 -11379
- boris/qdarkstyle/dark/palette.py +0 -38
- boris/qdarkstyle/example/__init__.py +0 -4
- boris/qdarkstyle/example/__main__.py +0 -386
- boris/qdarkstyle/example/ui/__init__.py +0 -4
- boris/qdarkstyle/light/__init__.py +0 -1
- boris/qdarkstyle/light/lightstyle_rc.py +0 -11305
- boris/qdarkstyle/light/palette.py +0 -37
- boris/qdarkstyle/palette.py +0 -102
- boris/qdarkstyle/utils/__init__.py +0 -73
- boris/qdarkstyle/utils/__main__.py +0 -96
- boris/qdarkstyle/utils/images.py +0 -449
- boris/qdarkstyle/utils/scss.py +0 -318
- boris/vlc_local.py +0 -83
- boris_behav_obs-8.27.10.dist-info/RECORD +0 -114
- {boris_behav_obs-8.27.10.dist-info → boris_behav_obs-9.0.2.dist-info}/LICENSE.TXT +0 -0
- {boris_behav_obs-8.27.10.dist-info → boris_behav_obs-9.0.2.dist-info}/entry_points.txt +0 -0
- {boris_behav_obs-8.27.10.dist-info → boris_behav_obs-9.0.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BORIS plugin
|
|
3
|
+
|
|
4
|
+
number of occurences of behaviors by independent_variable
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
__version__ = "0.1.0"
|
|
10
|
+
__version_date__ = "2024-11-14"
|
|
11
|
+
__plugin_name__ = "Number of occurences of behaviors by subject by independent_variable"
|
|
12
|
+
__author__ = "Olivier Friard - University of Torino - Italy"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def run(df: pd.DataFrame):
|
|
16
|
+
"""
|
|
17
|
+
Calculate the number of occurrences of behaviors by subject and by independent_variable.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
results = (
|
|
21
|
+
df.groupby(
|
|
22
|
+
[
|
|
23
|
+
"independent variable 'Weather'",
|
|
24
|
+
"Subject",
|
|
25
|
+
"Behavior",
|
|
26
|
+
]
|
|
27
|
+
)["Behavior"]
|
|
28
|
+
.count()
|
|
29
|
+
.reset_index(name="number of occurences")
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
return results
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def main(df: pd.DataFrame, observations_list: list = [], parameters: dict = {}) -> pd.DataFrame:
|
|
36
|
+
"""
|
|
37
|
+
filter by selected observations.
|
|
38
|
+
filter by selected subjects.
|
|
39
|
+
filter by selected behaviors.
|
|
40
|
+
filter by time interval.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# filter selected observations
|
|
44
|
+
if observations_list:
|
|
45
|
+
df = df[df["Observation id"].isin(observations_list)]
|
|
46
|
+
|
|
47
|
+
if parameters:
|
|
48
|
+
# filter selected subjects
|
|
49
|
+
df = df[df["Subject"].isin(parameters["selected subjects"])]
|
|
50
|
+
|
|
51
|
+
# filter selected behaviors
|
|
52
|
+
df = df[df["Behavior"].isin(parameters["selected behaviors"])]
|
|
53
|
+
|
|
54
|
+
# filter selected time interval
|
|
55
|
+
if parameters["start time"] is not None and parameters["end time"] is not None:
|
|
56
|
+
MIN_TIME = parameters["start time"]
|
|
57
|
+
MAX_TIME = parameters["end time"]
|
|
58
|
+
df_interval = df[
|
|
59
|
+
(
|
|
60
|
+
((df["Start (s)"] >= MIN_TIME) & (df["Start (s)"] <= MAX_TIME))
|
|
61
|
+
| ((df["Stop (s)"] >= MIN_TIME) & (df["Stop (s)"] <= MAX_TIME))
|
|
62
|
+
)
|
|
63
|
+
| ((df["Start (s)"] < MIN_TIME) & (df["Stop (s)"] > MAX_TIME))
|
|
64
|
+
]
|
|
65
|
+
df_interval.loc[df["Start (s)"] < MIN_TIME, "Start (s)"] = MIN_TIME
|
|
66
|
+
df_interval.loc[df["Stop (s)"] > MAX_TIME, "Stop (s)"] = MAX_TIME
|
|
67
|
+
|
|
68
|
+
df_interval.loc[:, "Duration (s)"] = df_interval["Stop (s)"] - df_interval["Start (s)"]
|
|
69
|
+
|
|
70
|
+
df = df_interval
|
|
71
|
+
|
|
72
|
+
return run(df)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BORIS plugin
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
__version_date__ = "2024-11-14"
|
|
9
|
+
__plugin_name__ = "Time budget"
|
|
10
|
+
__author__ = "Olivier Friard - University of Torino - Italy"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def run(df: pd.DataFrame):
|
|
14
|
+
"""
|
|
15
|
+
Calculate the following values:
|
|
16
|
+
|
|
17
|
+
- Total number of occurences of behavior
|
|
18
|
+
- Total duration of behavior (in seconds)
|
|
19
|
+
- Duration mean of behavior (in seconds)
|
|
20
|
+
- Standard deviation of behavior duration (in seconds)
|
|
21
|
+
- Inter-event intervals mean (in seconds)
|
|
22
|
+
- Inter-event intervals standard deviation (in seconds)
|
|
23
|
+
- % of total duration
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
dfs = [
|
|
27
|
+
df.groupby(["Subject", "Behavior"])["Behavior"].count().reset_index(name="number of occurences"),
|
|
28
|
+
df.groupby(["Subject", "Behavior"])["Duration (s)"].sum().reset_index(name="total duration"),
|
|
29
|
+
df.groupby(["Subject", "Behavior"])["Duration (s)"].mean().astype(float).round(3).reset_index(name="duration mean"),
|
|
30
|
+
df.groupby(["Subject", "Behavior"])["Duration (s)"].std().astype(float).round(3).reset_index(name="duration std dev"),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
# inter events
|
|
34
|
+
df2 = df.sort_values(by=["Observation id", "Subject", "Behavior", "Start (s)"])
|
|
35
|
+
df2["diff"] = df2.groupby(["Observation id", "Subject", "Behavior"])["Start (s)"].shift(periods=-1) - df2["Stop (s)"]
|
|
36
|
+
|
|
37
|
+
dfs.append(df2.groupby(["Subject", "Behavior"])["diff"].mean().astype(float).round(3).reset_index(name="inter-event intervals mean"))
|
|
38
|
+
|
|
39
|
+
dfs.append(df2.groupby(["Subject", "Behavior"])["diff"].std().astype(float).round(3).reset_index(name="inter-event intervals std dev"))
|
|
40
|
+
|
|
41
|
+
# % of time
|
|
42
|
+
dfs.append(
|
|
43
|
+
(100 * df.groupby(["Subject", "Behavior"])["Duration (s)"].sum() / df.groupby(["Subject"])["Duration (s)"].sum())
|
|
44
|
+
.astype(float)
|
|
45
|
+
.round(3)
|
|
46
|
+
.reset_index(name="% of total duration")
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
merged_df = dfs[0]
|
|
50
|
+
for df in dfs[1:]:
|
|
51
|
+
merged_df = pd.merge(merged_df, df, on=["Subject", "Behavior"])
|
|
52
|
+
|
|
53
|
+
return merged_df
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def main(df: pd.DataFrame, observations_list: list = [], parameters: dict = {}) -> pd.DataFrame:
|
|
57
|
+
"""
|
|
58
|
+
filter by selected observations.
|
|
59
|
+
filter by selected subjects.
|
|
60
|
+
filter by selected behaviors.
|
|
61
|
+
filter by time interval.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
# filter selected observations
|
|
65
|
+
if observations_list:
|
|
66
|
+
df = df[df["Observation id"].isin(observations_list)]
|
|
67
|
+
|
|
68
|
+
if parameters:
|
|
69
|
+
# filter selected subjects
|
|
70
|
+
df = df[df["Subject"].isin(parameters["selected subjects"])]
|
|
71
|
+
|
|
72
|
+
# filter selected behaviors
|
|
73
|
+
df = df[df["Behavior"].isin(parameters["selected behaviors"])]
|
|
74
|
+
|
|
75
|
+
# filter selected time interval
|
|
76
|
+
if parameters["start time"] is not None and parameters["end time"] is not None:
|
|
77
|
+
MIN_TIME = parameters["start time"]
|
|
78
|
+
MAX_TIME = parameters["end time"]
|
|
79
|
+
|
|
80
|
+
df_interval = df[
|
|
81
|
+
(
|
|
82
|
+
((df["Start (s)"] >= MIN_TIME) & (df["Start (s)"] <= MAX_TIME))
|
|
83
|
+
| ((df["Stop (s)"] >= MIN_TIME) & (df["Stop (s)"] <= MAX_TIME))
|
|
84
|
+
)
|
|
85
|
+
| ((df["Start (s)"] < MIN_TIME) & (df["Stop (s)"] > MAX_TIME))
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
df_interval.loc[df["Start (s)"] < MIN_TIME, "Start (s)"] = MIN_TIME
|
|
89
|
+
df_interval.loc[df["Stop (s)"] > MAX_TIME, "Stop (s)"] = MAX_TIME
|
|
90
|
+
|
|
91
|
+
df_interval.loc[:, "Duration (s)"] = df_interval["Stop (s)"] - df_interval["Start (s)"]
|
|
92
|
+
|
|
93
|
+
df = df_interval
|
|
94
|
+
|
|
95
|
+
return run(df)
|
|
@@ -23,12 +23,11 @@ This file is part of BORIS.
|
|
|
23
23
|
import binascii
|
|
24
24
|
import io
|
|
25
25
|
import json
|
|
26
|
-
import
|
|
26
|
+
from pathlib import Path
|
|
27
27
|
|
|
28
|
-
from
|
|
29
|
-
from
|
|
30
|
-
from
|
|
31
|
-
QAction,
|
|
28
|
+
from PySide6.QtCore import QBuffer, QByteArray, QIODevice, QLineF, QPoint, Qt, Signal
|
|
29
|
+
from PySide6.QtGui import QBrush, QColor, QIcon, QMouseEvent, QPen, QPixmap, QPolygonF, QAction
|
|
30
|
+
from PySide6.QtWidgets import (
|
|
32
31
|
QApplication,
|
|
33
32
|
QColorDialog,
|
|
34
33
|
QFileDialog,
|
|
@@ -68,14 +67,14 @@ selectedBrush.setColor(QColor(255, 255, 0, 255))
|
|
|
68
67
|
|
|
69
68
|
|
|
70
69
|
class BehaviorsMapCreatorWindow(QMainWindow):
|
|
71
|
-
signal_add_to_project =
|
|
70
|
+
signal_add_to_project = Signal(dict)
|
|
72
71
|
|
|
73
72
|
class View(QGraphicsView):
|
|
74
73
|
"""
|
|
75
74
|
class for handling mousepress event in QGraphicsView
|
|
76
75
|
"""
|
|
77
76
|
|
|
78
|
-
mousePress =
|
|
77
|
+
mousePress = Signal(QMouseEvent)
|
|
79
78
|
|
|
80
79
|
def mousePressEvent(self, event):
|
|
81
80
|
self.mousePress.emit(event)
|
|
@@ -199,6 +198,8 @@ class BehaviorsMapCreatorWindow(QMainWindow):
|
|
|
199
198
|
self.btCancelAreaCreation.setVisible(False)
|
|
200
199
|
hlayout_cmd.addWidget(self.btCancelAreaCreation)
|
|
201
200
|
|
|
201
|
+
hlayout_cmd.addItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
|
|
202
|
+
|
|
202
203
|
self.btDeleteArea = QPushButton("Delete selected behavior area", self)
|
|
203
204
|
self.btDeleteArea.clicked.connect(self.deleteArea)
|
|
204
205
|
self.btDeleteArea.setVisible(False)
|
|
@@ -621,6 +622,8 @@ class BehaviorsMapCreatorWindow(QMainWindow):
|
|
|
621
622
|
|
|
622
623
|
while True:
|
|
623
624
|
map_name, ok = QInputDialog.getText(self, "Behaviors coding map name", "Enter a name for the new coding map")
|
|
625
|
+
if not ok:
|
|
626
|
+
return
|
|
624
627
|
if map_name.upper() in self.bcm_list:
|
|
625
628
|
QMessageBox.critical(
|
|
626
629
|
self,
|
|
@@ -634,18 +637,13 @@ class BehaviorsMapCreatorWindow(QMainWindow):
|
|
|
634
637
|
if ok and map_name and map_name.upper() not in self.bcm_list:
|
|
635
638
|
self.mapName = map_name
|
|
636
639
|
break
|
|
637
|
-
if not ok:
|
|
638
|
-
return
|
|
639
|
-
"""
|
|
640
|
-
if not self.mapName:
|
|
641
|
-
QMessageBox.critical(self, "", "You must define a name for the new coding map")
|
|
642
|
-
return
|
|
643
|
-
"""
|
|
644
640
|
|
|
645
641
|
self.setWindowTitle(f"{cfg.programName} - Behaviors coding map creator tool - {self.mapName}")
|
|
646
642
|
|
|
647
|
-
self.
|
|
648
|
-
|
|
643
|
+
self.loadBitmap()
|
|
644
|
+
|
|
645
|
+
# self.btLoad.setVisible(True)
|
|
646
|
+
# self.statusBar().showMessage('Click "Load bitmap" button to select and load a bitmap into the viewer')
|
|
649
647
|
|
|
650
648
|
def openMap(self):
|
|
651
649
|
"""
|
|
@@ -664,75 +662,72 @@ class BehaviorsMapCreatorWindow(QMainWindow):
|
|
|
664
662
|
if (response == "Save" and not self.saveMap_clicked()) or (response == "Cancel"):
|
|
665
663
|
return
|
|
666
664
|
|
|
667
|
-
|
|
665
|
+
fileName, _ = QFileDialog(self).getOpenFileName(
|
|
668
666
|
self, "Open a behaviors coding map", "", "Behaviors coding map (*.behav_coding_map);;All files (*)"
|
|
669
667
|
)
|
|
670
|
-
fileName = fn[0] if type(fn) is tuple else fn
|
|
671
|
-
|
|
672
|
-
if fileName:
|
|
673
|
-
try:
|
|
674
|
-
self.codingMap = json.loads(open(fileName, "r").read())
|
|
675
|
-
except Exception:
|
|
676
|
-
QMessageBox.critical(self, cfg.programName, f"The file {fileName} is not a behaviors coding map.")
|
|
677
|
-
return
|
|
678
668
|
|
|
679
|
-
|
|
680
|
-
|
|
669
|
+
if not fileName:
|
|
670
|
+
self.statusBar().showMessage("No file", 5000)
|
|
671
|
+
try:
|
|
672
|
+
self.codingMap = json.loads(open(fileName, "r").read())
|
|
673
|
+
except Exception:
|
|
674
|
+
QMessageBox.critical(self, cfg.programName, f"The file {fileName} is not a behaviors coding map.")
|
|
675
|
+
return
|
|
681
676
|
|
|
682
|
-
|
|
677
|
+
if "coding_map_type" not in self.codingMap or self.codingMap["coding_map_type"] != "BORIS behaviors coding map":
|
|
678
|
+
QMessageBox.critical(self, cfg.programName, f"The file {fileName} is not a BORIS behaviors coding map.")
|
|
683
679
|
|
|
684
|
-
|
|
680
|
+
self.cancelMap()
|
|
685
681
|
|
|
686
|
-
|
|
682
|
+
self.mapName = self.codingMap["name"]
|
|
687
683
|
|
|
688
|
-
|
|
684
|
+
self.setWindowTitle(f"{cfg.programName} - Behaviors coding map creator - {self.mapName}")
|
|
689
685
|
|
|
690
|
-
|
|
686
|
+
self.bitmapFileName = True
|
|
691
687
|
|
|
692
|
-
|
|
688
|
+
self.fileName = fileName
|
|
693
689
|
|
|
694
|
-
|
|
690
|
+
bitmapContent = binascii.a2b_base64(self.codingMap["bitmap"])
|
|
695
691
|
|
|
696
|
-
|
|
697
|
-
self.view.setMinimumHeight(self.pixmap.size().height())
|
|
698
|
-
# self.view.setMaximumHeight(self.pixmap.size().height())
|
|
699
|
-
pixItem = QGraphicsPixmapItem(self.pixmap)
|
|
700
|
-
pixItem.setPos(0, 0)
|
|
701
|
-
self.view.scene().addItem(pixItem)
|
|
692
|
+
self.pixmap.loadFromData(bitmapContent)
|
|
702
693
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
694
|
+
self.view.setSceneRect(0, 0, self.pixmap.size().width(), self.pixmap.size().height())
|
|
695
|
+
self.view.setMinimumHeight(self.pixmap.size().height())
|
|
696
|
+
# self.view.setMaximumHeight(self.pixmap.size().height())
|
|
697
|
+
pixItem = QGraphicsPixmapItem(self.pixmap)
|
|
698
|
+
pixItem.setPos(0, 0)
|
|
699
|
+
self.view.scene().addItem(pixItem)
|
|
706
700
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
701
|
+
for key in self.codingMap["areas"]:
|
|
702
|
+
areaCode = self.codingMap["areas"][key]["code"]
|
|
703
|
+
points = self.codingMap["areas"][key]["geometry"]
|
|
710
704
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
polygon.setPolygon(newPolygon)
|
|
715
|
-
clr = QColor()
|
|
716
|
-
clr.setRgba(self.codingMap["areas"][key]["color"])
|
|
717
|
-
polygon.setPen(QPen(clr, penWidth, penStyle, Qt.RoundCap, Qt.RoundJoin))
|
|
718
|
-
polygon.setBrush(QBrush(clr, Qt.SolidPattern))
|
|
705
|
+
newPolygon = QPolygonF()
|
|
706
|
+
for p in points:
|
|
707
|
+
newPolygon.append(QPoint(p[0], p[1]))
|
|
719
708
|
|
|
720
|
-
|
|
709
|
+
# draw polygon
|
|
710
|
+
"""polygon = QGraphicsPolygonItem(None, None) if QT_VERSION_STR[0] == "4" else QGraphicsPolygonItem()"""
|
|
711
|
+
polygon = QGraphicsPolygonItem()
|
|
712
|
+
polygon.setPolygon(newPolygon)
|
|
713
|
+
clr = QColor()
|
|
714
|
+
clr.setRgba(self.codingMap["areas"][key]["color"])
|
|
715
|
+
polygon.setPen(QPen(clr, penWidth, penStyle, Qt.RoundCap, Qt.RoundJoin))
|
|
716
|
+
polygon.setBrush(QBrush(clr, Qt.SolidPattern))
|
|
721
717
|
|
|
722
|
-
|
|
718
|
+
self.view.scene().addItem(polygon)
|
|
723
719
|
|
|
724
|
-
self.
|
|
725
|
-
self.btLoad.setVisible(False)
|
|
720
|
+
self.polygonsList2.append([areaCode, polygon])
|
|
726
721
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
self.addToProject.setEnabled(True)
|
|
730
|
-
self.mapNameAction.setEnabled(True)
|
|
722
|
+
self.btNewArea.setVisible(True)
|
|
723
|
+
self.btLoad.setVisible(False)
|
|
731
724
|
|
|
732
|
-
|
|
725
|
+
self.saveMapAction.setEnabled(True)
|
|
726
|
+
self.saveAsMapAction.setEnabled(True)
|
|
727
|
+
self.addToProject.setEnabled(True)
|
|
728
|
+
self.mapNameAction.setEnabled(True)
|
|
733
729
|
|
|
734
|
-
|
|
735
|
-
self.statusBar().showMessage("No file", 5000)
|
|
730
|
+
self.update_area_list()
|
|
736
731
|
|
|
737
732
|
def make_coding_map_dict(self) -> dict:
|
|
738
733
|
"""
|
|
@@ -788,25 +783,25 @@ class BehaviorsMapCreatorWindow(QMainWindow):
|
|
|
788
783
|
def saveAsMap_clicked(self):
|
|
789
784
|
filters = "Behaviors coding map (*.behav_coding_map);;All files (*)"
|
|
790
785
|
|
|
791
|
-
|
|
792
|
-
self.fileName = fn[0] if type(fn) is tuple else fn
|
|
786
|
+
self.fileName, _ = QFileDialog.getSaveFileName(self, "Save behaviors coding map as", "", filters)
|
|
793
787
|
|
|
794
|
-
if self.fileName:
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
788
|
+
if not self.fileName:
|
|
789
|
+
return
|
|
790
|
+
"""if os.path.splitext(self.fileName)[1] != ".behav_coding_map":"""
|
|
791
|
+
if Path(self.fileName).suffix != ".behav_coding_map":
|
|
792
|
+
self.fileName += ".behav_coding_map"
|
|
793
|
+
self.saveMap()
|
|
798
794
|
|
|
799
795
|
def saveMap_clicked(self):
|
|
800
796
|
if not self.fileName:
|
|
801
|
-
|
|
797
|
+
self.fileName, _ = QFileDialog().getSaveFileName(
|
|
802
798
|
self,
|
|
803
799
|
"Save modifiers map",
|
|
804
800
|
self.mapName + ".behav_coding_map",
|
|
805
801
|
"Behaviors coding map (*.behav_coding_map);;All files (*)",
|
|
806
802
|
)
|
|
807
|
-
self.fileName = fn[0] if type(fn) is tuple else fn
|
|
808
803
|
|
|
809
|
-
if self.fileName and
|
|
804
|
+
if self.fileName and Path(self.fileName).suffix != ".behav_coding_map":
|
|
810
805
|
self.fileName += ".behav_coding_map"
|
|
811
806
|
|
|
812
807
|
if self.fileName:
|
|
@@ -987,50 +982,50 @@ class BehaviorsMapCreatorWindow(QMainWindow):
|
|
|
987
982
|
def loadBitmap(self):
|
|
988
983
|
"""
|
|
989
984
|
load bitmap as background for coding map
|
|
990
|
-
resize bitmap to CODING_MAP_RESIZE_W x CODING_MAP_RESIZE_H defined in config.py
|
|
985
|
+
no more resize bitmap to CODING_MAP_RESIZE_W x CODING_MAP_RESIZE_H defined in config.py
|
|
991
986
|
"""
|
|
992
987
|
|
|
993
|
-
|
|
994
|
-
fileName = fn[0] if type(fn) is tuple else fn
|
|
988
|
+
fileName, _ = QFileDialog.getOpenFileName(self, "Load bitmap", "", "bitmap files (*.png *.jpg);;All files (*)")
|
|
995
989
|
|
|
996
|
-
if fileName:
|
|
997
|
-
|
|
990
|
+
if not fileName:
|
|
991
|
+
return
|
|
992
|
+
self.bitmapFileName = fileName
|
|
998
993
|
|
|
999
|
-
|
|
994
|
+
self.pixmap.load(self.bitmapFileName)
|
|
1000
995
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
996
|
+
# scale image
|
|
997
|
+
"""
|
|
998
|
+
if (
|
|
999
|
+
self.pixmap.size().width() > cfg.CODING_MAP_RESIZE_W
|
|
1000
|
+
or self.pixmap.size().height() > cfg.CODING_MAP_RESIZE_H
|
|
1001
|
+
):
|
|
1002
|
+
self.pixmap = self.pixmap.scaled(cfg.CODING_MAP_RESIZE_W, cfg.CODING_MAP_RESIZE_H, Qt.KeepAspectRatio)
|
|
1003
|
+
QMessageBox.information(
|
|
1004
|
+
self,
|
|
1005
|
+
cfg.programName,
|
|
1006
|
+
(
|
|
1007
|
+
f"The bitmap was resized to {self.pixmap.size().width()}x{self.pixmap.size().height()} pixels\n"
|
|
1008
|
+
"The original file was not modified"
|
|
1009
|
+
),
|
|
1010
|
+
)
|
|
1011
|
+
"""
|
|
1017
1012
|
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1013
|
+
self.view.setSceneRect(0, 0, self.pixmap.size().width(), self.pixmap.size().height())
|
|
1014
|
+
pixitem = QGraphicsPixmapItem(self.pixmap)
|
|
1015
|
+
pixitem.setPos(0, 0)
|
|
1016
|
+
self.view.scene().addItem(pixitem)
|
|
1022
1017
|
|
|
1023
|
-
|
|
1018
|
+
self.btNewArea.setVisible(True)
|
|
1024
1019
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1020
|
+
self.btLoad.setVisible(False)
|
|
1021
|
+
self.saveMapAction.setEnabled(True)
|
|
1022
|
+
self.saveAsMapAction.setEnabled(True)
|
|
1023
|
+
self.addToProject.setEnabled(True)
|
|
1024
|
+
self.mapNameAction.setEnabled(True)
|
|
1030
1025
|
|
|
1031
|
-
|
|
1026
|
+
self.statusBar().showMessage("""Click "New behavior area" to create a new behavior area""")
|
|
1032
1027
|
|
|
1033
|
-
|
|
1028
|
+
self.flag_map_changed = True
|
|
1034
1029
|
|
|
1035
1030
|
|
|
1036
1031
|
def behaviors_coding_map_creator(self):
|
boris/behavior_binary_table.py
CHANGED
|
@@ -24,7 +24,7 @@ import pathlib
|
|
|
24
24
|
from decimal import Decimal as dec
|
|
25
25
|
|
|
26
26
|
import tablib
|
|
27
|
-
from
|
|
27
|
+
from PySide6.QtWidgets import QFileDialog, QInputDialog, QMessageBox
|
|
28
28
|
|
|
29
29
|
from . import observation_operations
|
|
30
30
|
|
boris/behaviors_coding_map.py
CHANGED
|
@@ -20,9 +20,9 @@ This file is part of BORIS.
|
|
|
20
20
|
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
from
|
|
24
|
-
from
|
|
25
|
-
from
|
|
23
|
+
from PySide6.QtGui import QMouseEvent, QPixmap, QPolygonF, QColor, QBrush, QPen
|
|
24
|
+
from PySide6.QtCore import Qt, Signal, QEvent, QPoint
|
|
25
|
+
from PySide6.QtWidgets import (
|
|
26
26
|
QLabel,
|
|
27
27
|
QHBoxLayout,
|
|
28
28
|
QGraphicsView,
|
|
@@ -49,8 +49,8 @@ penStyle = Qt.NoPen
|
|
|
49
49
|
|
|
50
50
|
class BehaviorsCodingMapWindowClass(QWidget):
|
|
51
51
|
class View(QGraphicsView):
|
|
52
|
-
mousePress =
|
|
53
|
-
mouseMove =
|
|
52
|
+
mousePress = Signal(QMouseEvent)
|
|
53
|
+
mouseMove = Signal(QMouseEvent)
|
|
54
54
|
|
|
55
55
|
def eventFilter(self, source, event):
|
|
56
56
|
if event.type() == QEvent.MouseMove:
|
|
@@ -71,9 +71,9 @@ class BehaviorsCodingMapWindowClass(QWidget):
|
|
|
71
71
|
self.viewport().installEventFilter(self)
|
|
72
72
|
self.setMouseTracking(True)
|
|
73
73
|
|
|
74
|
-
clickSignal =
|
|
75
|
-
keypressSignal =
|
|
76
|
-
close_signal =
|
|
74
|
+
clickSignal = Signal(str, list) # click signal to be sent to mainwindow
|
|
75
|
+
keypressSignal = Signal(QEvent)
|
|
76
|
+
close_signal = Signal(str)
|
|
77
77
|
|
|
78
78
|
def __init__(self, behaviors_coding_map, idx=0):
|
|
79
79
|
super(BehaviorsCodingMapWindowClass, self).__init__()
|
boris/coding_pad.py
CHANGED
|
@@ -19,9 +19,9 @@ Copyright 2012-2024 Olivier Friard
|
|
|
19
19
|
MA 02110-1301, USA.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
from
|
|
23
|
-
from
|
|
24
|
-
from
|
|
22
|
+
from PySide6.QtCore import Qt, Signal, QEvent, QRect
|
|
23
|
+
from PySide6.QtGui import QFont
|
|
24
|
+
from PySide6.QtWidgets import QWidget, QPushButton, QHBoxLayout, QGridLayout, QComboBox, QMessageBox
|
|
25
25
|
|
|
26
26
|
from . import config as cfg
|
|
27
27
|
from . import utilities as util
|
|
@@ -38,9 +38,9 @@ class Button(QWidget):
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
class CodingPad(QWidget):
|
|
41
|
-
clickSignal =
|
|
42
|
-
sendEventSignal =
|
|
43
|
-
close_signal =
|
|
41
|
+
clickSignal = Signal(str)
|
|
42
|
+
sendEventSignal = Signal(QEvent)
|
|
43
|
+
close_signal = Signal(QRect, dict)
|
|
44
44
|
|
|
45
45
|
def __init__(self, pj: dict, filtered_behaviors, parent=None):
|
|
46
46
|
super().__init__(parent)
|
boris/config.py
CHANGED
|
@@ -49,6 +49,7 @@ NA: str = "NA"
|
|
|
49
49
|
|
|
50
50
|
REALTIME_PLOT_CURSOR_COLOR: str = "red"
|
|
51
51
|
|
|
52
|
+
DARKER_DIFFERENCE = 5
|
|
52
53
|
|
|
53
54
|
CTRL_KEY: str = "Ctrl"
|
|
54
55
|
ALT_KEY: str = "Alt"
|
|
@@ -400,6 +401,9 @@ MPV_HWDEC_AUTOSAFE = "auto-safe"
|
|
|
400
401
|
MPV_HWDEC_OPTIONS = (MPV_HWDEC_AUTO, MPV_HWDEC_AUTOSAFE, MPV_HWDEC_NO)
|
|
401
402
|
MPV_HWDEC_DEFAULT_VALUE = MPV_HWDEC_AUTO
|
|
402
403
|
|
|
404
|
+
ANALYSIS_PLUGINS = "analysis_plugins"
|
|
405
|
+
EXCLUDED_PLUGINS = "excluded_plugins"
|
|
406
|
+
PERSONAL_PLUGINS_DIR = "personal_plugins_dir"
|
|
403
407
|
|
|
404
408
|
PROJECT_FILE_INDENTATION = "project file indentation"
|
|
405
409
|
PROJECT_FILE_INDENTATION_COMBO_OPTIONS = ("None", "Newline", "Tab", "2 spaces", "4 spaces")
|
|
@@ -409,8 +413,10 @@ PROJECT_FILE_INDENTATION_DEFAULT_VALUE = None
|
|
|
409
413
|
TOOLBAR_ICON_SIZE = "toolbar icon size"
|
|
410
414
|
DEFAULT_TOOLBAR_ICON_SIZE_VALUE = 24
|
|
411
415
|
|
|
416
|
+
"""
|
|
412
417
|
DARK_MODE = "dark_mode"
|
|
413
418
|
DARK_MODE_DEFAULT_VALUE = False
|
|
419
|
+
"""
|
|
414
420
|
|
|
415
421
|
VIDEO_VIEWER = 0
|
|
416
422
|
PICTURE_VIEWER = 1
|
boris/config_file.py
CHANGED
boris/connections.py
CHANGED
|
@@ -19,8 +19,8 @@ Copyright 2012-2024 Olivier Friard
|
|
|
19
19
|
MA 02110-1301, USA.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
from
|
|
23
|
-
from
|
|
22
|
+
from PySide6.QtCore import Qt, QTimer
|
|
23
|
+
from PySide6.QtGui import QAction
|
|
24
24
|
|
|
25
25
|
from . import config as cfg
|
|
26
26
|
from . import (
|
|
@@ -49,6 +49,7 @@ from . import (
|
|
|
49
49
|
video_operations,
|
|
50
50
|
project_functions,
|
|
51
51
|
external_processes,
|
|
52
|
+
plugins,
|
|
52
53
|
)
|
|
53
54
|
|
|
54
55
|
from . import state_events as state_events
|
|
@@ -200,6 +201,7 @@ def connections(self):
|
|
|
200
201
|
self.actionCreate_transitions_flow_diagram_2.triggered.connect(transitions.transitions_flow_diagram)
|
|
201
202
|
|
|
202
203
|
# menu Analysis
|
|
204
|
+
|
|
203
205
|
self.actionTime_budget.triggered.connect(lambda: time_budget_widget.time_budget(self, mode="by_behavior"))
|
|
204
206
|
self.actionTime_budget_by_behaviors_category.triggered.connect(lambda: time_budget_widget.time_budget(self, mode="by_category"))
|
|
205
207
|
|
boris/converters.py
CHANGED
|
@@ -27,7 +27,7 @@ import urllib.parse
|
|
|
27
27
|
import urllib.request
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
from
|
|
30
|
+
from PySide6.QtWidgets import QMessageBox, QTableWidgetItem, QFileDialog, QInputDialog, QLineEdit
|
|
31
31
|
|
|
32
32
|
from . import dialog
|
|
33
33
|
from . import config as cfg
|
|
@@ -239,8 +239,7 @@ def load_converters_from_file_repo(self, mode: str):
|
|
|
239
239
|
|
|
240
240
|
converters_from_file = {}
|
|
241
241
|
if mode == "file":
|
|
242
|
-
|
|
243
|
-
file_name = fn[0] if type(fn) is tuple else fn
|
|
242
|
+
file_name, _ = QFileDialog.getOpenFileName(self, "Load converters from file", "", "All files (*)")
|
|
244
243
|
|
|
245
244
|
if file_name:
|
|
246
245
|
with open(file_name, "r") as f_in:
|