boris-behav-obs 9.3.1__py2.py3-none-any.whl → 9.3.3__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/analysis_plugins/_latency.py +59 -0
- boris/behav_coding_map_creator.py +2 -6
- boris/config.py +4 -1
- boris/core.py +21 -60
- boris/dialog.py +8 -3
- boris/gui_utilities.py +30 -4
- boris/media_file.py +12 -10
- boris/modifier_coding_map_creator.py +6 -21
- boris/observation.py +25 -20
- boris/observation_operations.py +6 -6
- boris/player_dock_widget.py +7 -5
- boris/plugins.py +4 -0
- boris/project.py +69 -21
- boris/project_import_export.py +3 -1
- boris/utilities.py +35 -55
- boris/version.py +2 -2
- {boris_behav_obs-9.3.1.dist-info → boris_behav_obs-9.3.3.dist-info}/METADATA +1 -1
- {boris_behav_obs-9.3.1.dist-info → boris_behav_obs-9.3.3.dist-info}/RECORD +22 -22
- {boris_behav_obs-9.3.1.dist-info → boris_behav_obs-9.3.3.dist-info}/WHEEL +1 -1
- boris/1.py +0 -45
- {boris_behav_obs-9.3.1.dist-info → boris_behav_obs-9.3.3.dist-info}/entry_points.txt +0 -0
- {boris_behav_obs-9.3.1.dist-info → boris_behav_obs-9.3.3.dist-info}/licenses/LICENSE.TXT +0 -0
- {boris_behav_obs-9.3.1.dist-info → boris_behav_obs-9.3.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BORIS plugin
|
|
3
|
+
|
|
4
|
+
number of occurences of behaviors
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
__version__ = "0.0.1"
|
|
10
|
+
__version_date__ = "2025-04-10"
|
|
11
|
+
__plugin_name__ = "Behavior latency"
|
|
12
|
+
__author__ = "Olivier Friard - University of Torino - Italy"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
import itertools
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def run(df: pd.DataFrame):
|
|
19
|
+
"""
|
|
20
|
+
Latency of a behavior after another.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
df["start_time"] = pd.to_datetime(df["Start (s)"])
|
|
24
|
+
df["end_time"] = pd.to_datetime(df["Stop (s)"])
|
|
25
|
+
|
|
26
|
+
latency_by_subject: dict = {}
|
|
27
|
+
|
|
28
|
+
for subject, group in df.groupby("subject"):
|
|
29
|
+
behaviors = group["behavior"].tolist()
|
|
30
|
+
# combinations = []
|
|
31
|
+
# Utiliser itertools pour créer des combinaisons 2 à 2 des comportements
|
|
32
|
+
for comb in itertools.combinations(behaviors, 2):
|
|
33
|
+
# combinations.append(comb)
|
|
34
|
+
|
|
35
|
+
last_A_end_time = None
|
|
36
|
+
|
|
37
|
+
# Liste pour stocker les latences de chaque sujet
|
|
38
|
+
subject_latency = []
|
|
39
|
+
|
|
40
|
+
for index, row in group.iterrows():
|
|
41
|
+
if row["behavior"] == comb[0]:
|
|
42
|
+
# Si on rencontre un comportement A, on réinitialise le temps de fin du comportement A
|
|
43
|
+
last_A_end_time = row["end_time"]
|
|
44
|
+
subject_latency.append(None) # Pas de latence pour A
|
|
45
|
+
elif row["behavior"] == comb[1] and last_A_end_time is not None:
|
|
46
|
+
# Si on rencontre un comportement B et qu'on a déjà vu un A avant
|
|
47
|
+
latency_time = row["start_time"] - last_A_end_time
|
|
48
|
+
subject_latency.append(latency_time)
|
|
49
|
+
else:
|
|
50
|
+
# Si on rencontre un B mais sans A avant
|
|
51
|
+
subject_latency.append(None)
|
|
52
|
+
|
|
53
|
+
# Ajout des latences calculées au DataFrame
|
|
54
|
+
df.loc[group.index, f"latency {comb[1]} after {comb[0]}"] = subject_latency
|
|
55
|
+
|
|
56
|
+
# Calcul de la latence totale ou moyenne par sujet
|
|
57
|
+
latency_by_subject[(subject, comb)] = df.groupby("subject")["latency"].agg(["sum", "mean"])
|
|
58
|
+
|
|
59
|
+
return str(latency_by_subject)
|
|
@@ -24,6 +24,7 @@ import binascii
|
|
|
24
24
|
import io
|
|
25
25
|
import json
|
|
26
26
|
from pathlib import Path
|
|
27
|
+
import gui_utilities
|
|
27
28
|
|
|
28
29
|
from PySide6.QtCore import QBuffer, QByteArray, QIODevice, QLineF, QPoint, Qt, Signal
|
|
29
30
|
from PySide6.QtGui import QBrush, QColor, QIcon, QMouseEvent, QPen, QPixmap, QPolygonF, QAction
|
|
@@ -790,7 +791,6 @@ class BehaviorsMapCreatorWindow(QMainWindow):
|
|
|
790
791
|
|
|
791
792
|
if not self.fileName:
|
|
792
793
|
return
|
|
793
|
-
"""if os.path.splitext(self.fileName)[1] != ".behav_coding_map":"""
|
|
794
794
|
if Path(self.fileName).suffix != ".behav_coding_map":
|
|
795
795
|
self.fileName += ".behav_coding_map"
|
|
796
796
|
self.saveMap()
|
|
@@ -1106,10 +1106,6 @@ if __name__ == "__main__":
|
|
|
1106
1106
|
app = QApplication(sys.argv)
|
|
1107
1107
|
window = BehaviorsMapCreatorWindow(["North zone", "East zone", "South zone", "West zone"])
|
|
1108
1108
|
window.bcm_list = []
|
|
1109
|
-
|
|
1110
|
-
screen_geometry = app.primaryScreen().geometry()
|
|
1111
|
-
center_x = (screen_geometry.width() - window.width()) // 2
|
|
1112
|
-
center_y = (screen_geometry.height() - window.height()) // 2
|
|
1113
|
-
window.move(center_x, center_y)
|
|
1109
|
+
gui_utilities.resize_center(app, window, cfg.CODING_MAP_RESIZE_W, cfg.CODING_MAP_RESIZE_H)
|
|
1114
1110
|
window.show()
|
|
1115
1111
|
sys.exit(app.exec())
|
boris/config.py
CHANGED
|
@@ -155,6 +155,7 @@ CHECK_PROJECT_INTEGRITY = "check_project_integrity"
|
|
|
155
155
|
YES = "Yes"
|
|
156
156
|
NO = "No"
|
|
157
157
|
CANCEL = "Cancel"
|
|
158
|
+
IGNORE = "Ignore"
|
|
158
159
|
APPEND = "Append"
|
|
159
160
|
CLOSE = "Close"
|
|
160
161
|
REPLACE = "Replace"
|
|
@@ -454,7 +455,8 @@ POINT = "POINT"
|
|
|
454
455
|
START = "START"
|
|
455
456
|
STOP = "STOP"
|
|
456
457
|
|
|
457
|
-
PLAYER1
|
|
458
|
+
PLAYER1 = "1"
|
|
459
|
+
PLAYER2 = "2"
|
|
458
460
|
ALL_PLAYERS = [str(x + 1) for x in range(N_PLAYER)]
|
|
459
461
|
|
|
460
462
|
VISUALIZE_SPECTROGRAM = "visualize_spectrogram"
|
|
@@ -701,6 +703,7 @@ EMPTY_PROJECT = {
|
|
|
701
703
|
ETHOGRAM: {},
|
|
702
704
|
OBSERVATIONS: {},
|
|
703
705
|
BEHAVIORAL_CATEGORIES: [],
|
|
706
|
+
BEHAVIORAL_CATEGORIES_CONF: {},
|
|
704
707
|
INDEPENDENT_VARIABLES: {},
|
|
705
708
|
CODING_MAP: {},
|
|
706
709
|
BEHAVIORS_CODING_MAP: [],
|
boris/core.py
CHANGED
|
@@ -114,7 +114,6 @@ from . import cmd_arguments
|
|
|
114
114
|
|
|
115
115
|
from . import core_qrc
|
|
116
116
|
from .core_ui import Ui_MainWindow
|
|
117
|
-
import exifread
|
|
118
117
|
from . import config as cfg
|
|
119
118
|
from . import video_operations
|
|
120
119
|
|
|
@@ -135,8 +134,9 @@ __version__ = version.__version__
|
|
|
135
134
|
__version_date__ = version.__version_date__
|
|
136
135
|
|
|
137
136
|
# check minimal version of python
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
MIN_PYTHON_VERSION = "3.12"
|
|
138
|
+
if util.versiontuple(platform.python_version()) < util.versiontuple(MIN_PYTHON_VERSION):
|
|
139
|
+
msg = f"BORIS requires Python {MIN_PYTHON_VERSION}+! You are using Python v. {platform.python_version()}\n"
|
|
140
140
|
logging.critical(msg)
|
|
141
141
|
sys.exit()
|
|
142
142
|
|
|
@@ -237,19 +237,6 @@ class TableModel(QAbstractTableModel):
|
|
|
237
237
|
return self._data[row][event_idx]
|
|
238
238
|
|
|
239
239
|
|
|
240
|
-
"""
|
|
241
|
-
class ButtonEventFilter(QObject):
|
|
242
|
-
def eventFilter(self, obj, event):
|
|
243
|
-
print("event filter")
|
|
244
|
-
if isinstance(obj, QPushButton) and event.type() == QEvent.KeyPress:
|
|
245
|
-
print("keypress")
|
|
246
|
-
if event.key() in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Space):
|
|
247
|
-
print("enter sapce")
|
|
248
|
-
return False # Block the event
|
|
249
|
-
return super().eventFilter(obj, event)
|
|
250
|
-
"""
|
|
251
|
-
|
|
252
|
-
|
|
253
240
|
class MainWindow(QMainWindow, Ui_MainWindow):
|
|
254
241
|
"""
|
|
255
242
|
Main BORIS window
|
|
@@ -301,7 +288,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
301
288
|
ext_data_timer_list: list = []
|
|
302
289
|
projectFileName: str = ""
|
|
303
290
|
mediaTotalLength = None
|
|
304
|
-
beep_every = 0
|
|
291
|
+
beep_every: float = 0.0
|
|
305
292
|
|
|
306
293
|
plot_colors = cfg.BEHAVIORS_PLOT_COLORS
|
|
307
294
|
behav_category_colors = cfg.CATEGORY_COLORS_LIST
|
|
@@ -315,19 +302,20 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
315
302
|
fast = 10
|
|
316
303
|
|
|
317
304
|
currentStates: dict = {}
|
|
318
|
-
subject_name_index = {}
|
|
305
|
+
subject_name_index: dict = {}
|
|
319
306
|
flag_slow = False
|
|
320
307
|
play_rate: float = 1
|
|
321
308
|
play_rate_step: float = 0.1
|
|
322
309
|
currentSubject: str = "" # contains the current subject of observation
|
|
323
|
-
coding_map_window_geometry = 0
|
|
324
310
|
|
|
325
311
|
# FFmpeg
|
|
326
|
-
memx
|
|
312
|
+
memx = -1
|
|
313
|
+
memy = -1
|
|
314
|
+
mem_player = -1
|
|
327
315
|
|
|
328
316
|
# path for ffmpeg/ffmpeg.exe program
|
|
329
|
-
ffmpeg_bin = ""
|
|
330
|
-
ffmpeg_cache_dir = ""
|
|
317
|
+
ffmpeg_bin: str = ""
|
|
318
|
+
ffmpeg_cache_dir: str = ""
|
|
331
319
|
|
|
332
320
|
# dictionary for FPS storing
|
|
333
321
|
fps = 0
|
|
@@ -390,9 +378,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
390
378
|
super(MainWindow, self).__init__(parent)
|
|
391
379
|
self.setupUi(self)
|
|
392
380
|
|
|
393
|
-
# disable trigger with RETURN or SPACE keys
|
|
394
|
-
"""filter_obj = ButtonEventFilter()
|
|
395
|
-
self.pb_live_obs.installEventFilter(filter_obj)"""
|
|
396
381
|
self.pb_live_obs.setFocusPolicy(Qt.NoFocus)
|
|
397
382
|
|
|
398
383
|
self.ffmpeg_bin = ffmpeg_bin
|
|
@@ -418,25 +403,18 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
418
403
|
self.tb_export.setMenu(self.menu)
|
|
419
404
|
"""
|
|
420
405
|
|
|
421
|
-
gui_utilities.set_icons(self, theme_mode=
|
|
406
|
+
gui_utilities.set_icons(self, theme_mode=gui_utilities.theme_mode())
|
|
422
407
|
|
|
423
408
|
self.setWindowTitle(f"{cfg.programName} ({__version__})")
|
|
424
409
|
|
|
425
|
-
self.w_obs_info.setVisible(False)
|
|
426
|
-
|
|
427
410
|
self.lbLogoBoris.setPixmap(QPixmap(":/logo"))
|
|
428
|
-
|
|
429
411
|
self.lbLogoBoris.setScaledContents(False)
|
|
430
412
|
self.lbLogoBoris.setAlignment(Qt.AlignCenter)
|
|
431
413
|
|
|
432
|
-
# self.lbLogoUnito.setPixmap(QPixmap(":/dbios_unito"))
|
|
433
|
-
# self.lbLogoUnito.setScaledContents(False)
|
|
434
|
-
# self.lbLogoUnito.setAlignment(Qt.AlignCenter)
|
|
435
|
-
|
|
436
414
|
self.toolBar.setEnabled(True)
|
|
437
415
|
|
|
438
416
|
# start with dock widget invisible
|
|
439
|
-
for w in
|
|
417
|
+
for w in (self.w_obs_info, self.dwEvents, self.dwEthogram, self.dwSubjects):
|
|
440
418
|
w.setVisible(False)
|
|
441
419
|
w.keyPressEvent = self.keyPressEvent
|
|
442
420
|
|
|
@@ -475,15 +453,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
475
453
|
self.lbTimeOffset.setMinimumWidth(160)
|
|
476
454
|
self.statusbar.addPermanentWidget(self.lbTimeOffset)
|
|
477
455
|
|
|
478
|
-
# play rate are now displayed in the main info widget
|
|
479
|
-
"""
|
|
480
|
-
# SPEED
|
|
481
|
-
self.lbSpeed = QLabel()
|
|
482
|
-
self.lbSpeed.setFrameStyle(QFrame.StyledPanel)
|
|
483
|
-
self.lbSpeed.setMinimumWidth(40)
|
|
484
|
-
self.statusbar.addPermanentWidget(self.lbSpeed)
|
|
485
|
-
"""
|
|
486
|
-
|
|
487
456
|
# set painter for tv_events to highlight current row
|
|
488
457
|
delegate = self.CustomItemDelegate()
|
|
489
458
|
self.tv_events.setItemDelegate(delegate)
|
|
@@ -499,14 +468,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
499
468
|
plugins.load_plugins(self)
|
|
500
469
|
plugins.add_plugins_to_menu(self)
|
|
501
470
|
|
|
502
|
-
def theme_mode(self):
|
|
503
|
-
"""
|
|
504
|
-
return the theme mode (dark or light) of the OS
|
|
505
|
-
"""
|
|
506
|
-
palette = QApplication.instance().palette()
|
|
507
|
-
color = palette.window().color()
|
|
508
|
-
return "dark" if color.value() < 128 else "light" # Dark mode if the color value is less than 128
|
|
509
|
-
|
|
510
471
|
class CustomItemDelegate(QStyledItemDelegate):
|
|
511
472
|
def paint(self, painter, option, index):
|
|
512
473
|
# Custom drawing logic here (overriding paint)
|
|
@@ -1506,7 +1467,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1506
1467
|
# one media
|
|
1507
1468
|
if self.dw_player[player].player.playlist_count == 1:
|
|
1508
1469
|
if new_time < self.dw_player[player].player.duration:
|
|
1509
|
-
|
|
1470
|
+
new_time_float = round(float(new_time), 3)
|
|
1471
|
+
|
|
1472
|
+
self.dw_player[player].player.seek(new_time_float, "absolute+exact")
|
|
1510
1473
|
|
|
1511
1474
|
if player == 0 and not self.user_move_slider:
|
|
1512
1475
|
self.video_slider.setValue(
|
|
@@ -3835,7 +3798,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3835
3798
|
if self.geometric_measurements_mode:
|
|
3836
3799
|
geometric_measurement.redraw_measurements(self)
|
|
3837
3800
|
|
|
3838
|
-
self.actionPlay.setIcon(QIcon(f":/play_{
|
|
3801
|
+
self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
|
|
3839
3802
|
|
|
3840
3803
|
def previous_frame(self) -> None:
|
|
3841
3804
|
"""
|
|
@@ -3859,7 +3822,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3859
3822
|
if self.geometric_measurements_mode:
|
|
3860
3823
|
geometric_measurement.redraw_measurements(self)
|
|
3861
3824
|
|
|
3862
|
-
self.actionPlay.setIcon(QIcon(f":/play_{
|
|
3825
|
+
self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
|
|
3863
3826
|
|
|
3864
3827
|
def run_event_outside(self):
|
|
3865
3828
|
"""
|
|
@@ -4463,7 +4426,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4463
4426
|
for data_timer in self.ext_data_timer_list:
|
|
4464
4427
|
data_timer.stop()
|
|
4465
4428
|
|
|
4466
|
-
self.actionPlay.setIcon(QIcon(f":/play_{
|
|
4429
|
+
self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
|
|
4467
4430
|
|
|
4468
4431
|
if msg:
|
|
4469
4432
|
self.lb_current_media_time.setText(msg)
|
|
@@ -5634,7 +5597,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5634
5597
|
for data_timer in self.ext_data_timer_list:
|
|
5635
5598
|
data_timer.start()
|
|
5636
5599
|
|
|
5637
|
-
self.actionPlay.setIcon(QIcon(f":/pause_{
|
|
5600
|
+
self.actionPlay.setIcon(QIcon(f":/pause_{gui_utilities.theme_mode()}"))
|
|
5638
5601
|
self.actionPlay.setText("Pause")
|
|
5639
5602
|
|
|
5640
5603
|
return True
|
|
@@ -5668,7 +5631,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5668
5631
|
for idx in self.plot_data:
|
|
5669
5632
|
self.timer_plot_data_out(self.plot_data[idx])
|
|
5670
5633
|
|
|
5671
|
-
self.actionPlay.setIcon(QIcon(f":/play_{
|
|
5634
|
+
self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
|
|
5672
5635
|
self.actionPlay.setText("Play")
|
|
5673
5636
|
|
|
5674
5637
|
def play_activated(self):
|
|
@@ -5823,8 +5786,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5823
5786
|
|
|
5824
5787
|
|
|
5825
5788
|
def main():
|
|
5826
|
-
# QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
|
|
5827
|
-
|
|
5828
5789
|
app = QApplication(sys.argv)
|
|
5829
5790
|
app.setStyle("Fusion")
|
|
5830
5791
|
|
|
@@ -5917,7 +5878,7 @@ def main():
|
|
|
5917
5878
|
results.show()
|
|
5918
5879
|
|
|
5919
5880
|
window.show()
|
|
5920
|
-
window.raise_()
|
|
5881
|
+
window.raise_() # for overlapping widget (?)
|
|
5921
5882
|
|
|
5922
5883
|
if observation_to_open and "error" not in pj:
|
|
5923
5884
|
r = observation_operations.load_observation(window, obs_id=observation_to_open, mode=cfg.OBS_START)
|
boris/dialog.py
CHANGED
|
@@ -71,7 +71,12 @@ from . import utilities as util
|
|
|
71
71
|
|
|
72
72
|
def MessageDialog(title: str, text: str, buttons: tuple) -> str:
|
|
73
73
|
"""
|
|
74
|
-
generic message dialog
|
|
74
|
+
show a generic message dialog and returns the text of the clicked button
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
title (str): Title of the dialog box
|
|
78
|
+
text (str): text of the dialog box
|
|
79
|
+
buttons (tuple): text for buttons
|
|
75
80
|
|
|
76
81
|
Return
|
|
77
82
|
str: text of the clicked button
|
|
@@ -83,8 +88,8 @@ def MessageDialog(title: str, text: str, buttons: tuple) -> str:
|
|
|
83
88
|
for button in buttons:
|
|
84
89
|
message.addButton(button, QMessageBox.YesRole)
|
|
85
90
|
|
|
86
|
-
|
|
87
|
-
message.
|
|
91
|
+
message.setWindowFlags(message.windowFlags() | Qt.WindowStaysOnTopHint)
|
|
92
|
+
message.exec()
|
|
88
93
|
return message.clickedButton().text()
|
|
89
94
|
|
|
90
95
|
|
boris/gui_utilities.py
CHANGED
|
@@ -22,10 +22,19 @@ Copyright 2012-2025 Olivier Friard
|
|
|
22
22
|
import pathlib as pl
|
|
23
23
|
import logging
|
|
24
24
|
from PySide6.QtCore import QSettings
|
|
25
|
-
from PySide6.QtWidgets import QWidget
|
|
25
|
+
from PySide6.QtWidgets import QWidget, QApplication
|
|
26
26
|
from PySide6.QtGui import QIcon
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
def theme_mode() -> str:
|
|
30
|
+
"""
|
|
31
|
+
return the theme mode (dark or light) of the OS
|
|
32
|
+
"""
|
|
33
|
+
palette = QApplication.instance().palette()
|
|
34
|
+
color = palette.window().color()
|
|
35
|
+
return "dark" if color.value() < 128 else "light" # Dark mode if the color value is less than 128
|
|
36
|
+
|
|
37
|
+
|
|
29
38
|
def save_geometry(widget: QWidget, widget_name: str):
|
|
30
39
|
"""
|
|
31
40
|
save window geometry in ini file
|
|
@@ -44,6 +53,7 @@ def restore_geometry(widget: QWidget, widget_name: str, default_width_height):
|
|
|
44
53
|
"""
|
|
45
54
|
restore window geometry in ini file
|
|
46
55
|
"""
|
|
56
|
+
|
|
47
57
|
def default_resize(widget, default_width_height):
|
|
48
58
|
if default_width_height != (0, 0):
|
|
49
59
|
try:
|
|
@@ -51,15 +61,14 @@ def restore_geometry(widget: QWidget, widget_name: str, default_width_height):
|
|
|
51
61
|
except Exception:
|
|
52
62
|
logging.warning("Error during restoring default")
|
|
53
63
|
|
|
54
|
-
|
|
55
|
-
logging.debug(f'restore geometry function for {widget_name}')
|
|
64
|
+
logging.debug(f"restore geometry function for {widget_name}")
|
|
56
65
|
try:
|
|
57
66
|
ini_file_path = pl.Path.home() / pl.Path(".boris")
|
|
58
67
|
if ini_file_path.is_file():
|
|
59
68
|
settings = QSettings(str(ini_file_path), QSettings.IniFormat)
|
|
60
69
|
print(settings.value(f"{widget_name} geometry"))
|
|
61
70
|
widget.restoreGeometry(settings.value(f"{widget_name} geometry"))
|
|
62
|
-
logging.debug(f
|
|
71
|
+
logging.debug(f"geometry restored for {widget_name} {settings.value(f'{widget_name} geometry')}")
|
|
63
72
|
else:
|
|
64
73
|
default_resize(widget, default_width_height)
|
|
65
74
|
except Exception:
|
|
@@ -108,3 +117,20 @@ def set_icons(self, theme_mode: str) -> None:
|
|
|
108
117
|
self.action_geometric_measurements.setIcon(QIcon(f":/measurement_{theme_mode}"))
|
|
109
118
|
self.actionFind_in_current_obs.setIcon(QIcon(f":/find_{theme_mode}"))
|
|
110
119
|
self.actionExplore_project.setIcon(QIcon(f":/explore_{theme_mode}"))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def resize_center(app, window, width: int, height: int) -> None:
|
|
123
|
+
"""
|
|
124
|
+
resize and center window
|
|
125
|
+
"""
|
|
126
|
+
window.resize(width, height)
|
|
127
|
+
screen_geometry = app.primaryScreen().geometry()
|
|
128
|
+
if window.height() > screen_geometry.height():
|
|
129
|
+
window.resize(window.width(), int(screen_geometry.height() * 0.8))
|
|
130
|
+
if window.width() > screen_geometry.width():
|
|
131
|
+
window.resize(screen_geometry.width(), window.height())
|
|
132
|
+
# center
|
|
133
|
+
center_x = (screen_geometry.width() - window.width()) // 2
|
|
134
|
+
center_y = (screen_geometry.height() - window.height()) // 2
|
|
135
|
+
|
|
136
|
+
window.move(center_x, center_y)
|
boris/media_file.py
CHANGED
|
@@ -20,11 +20,13 @@ This file is part of BORIS.
|
|
|
20
20
|
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
+
from PySide6.QtWidgets import QFileDialog
|
|
24
|
+
|
|
23
25
|
from . import config as cfg
|
|
24
26
|
from . import utilities as util
|
|
25
27
|
from . import dialog
|
|
26
28
|
from . import project_functions
|
|
27
|
-
from
|
|
29
|
+
from . import utilities as util
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
def get_info(self) -> None:
|
|
@@ -38,17 +40,17 @@ def get_info(self) -> None:
|
|
|
38
40
|
if "error" in r:
|
|
39
41
|
ffmpeg_output = f"File path: {media_full_path}<br><br>{r['error']}<br><br>"
|
|
40
42
|
else:
|
|
41
|
-
ffmpeg_output = f"<br><b>{r['analysis_program']
|
|
43
|
+
ffmpeg_output = f"<br><b>{r['analysis_program']} analysis</b><br>"
|
|
42
44
|
|
|
43
45
|
ffmpeg_output += (
|
|
44
46
|
f"File path: <b>{media_full_path}</b><br><br>"
|
|
45
47
|
f"Duration: {r['duration']} seconds ({util.convertTime(self.timeFormat, r['duration'])})<br>"
|
|
48
|
+
f"FPS: {r['fps']}<br>"
|
|
49
|
+
f"Resolution: {r['resolution']} pixels<br>"
|
|
46
50
|
f"Format long name: {r.get('format_long_name', cfg.NA)}<br>"
|
|
47
51
|
f"Creation time: {r.get('creation_time', cfg.NA)}<br>"
|
|
48
|
-
f"Resolution: {r['resolution']}<br>"
|
|
49
52
|
f"Number of frames: {r['frames_number']}<br>"
|
|
50
53
|
f"Bitrate: {util.smart_size_format(r['bitrate'])} <br>"
|
|
51
|
-
f"FPS: {r['fps']}<br>"
|
|
52
54
|
f"Has video: {r['has_video']}<br>"
|
|
53
55
|
f"Has audio: {r['has_audio']}<br>"
|
|
54
56
|
f"File size: {util.smart_size_format(r.get('file size', cfg.NA))}<br>"
|
|
@@ -70,6 +72,12 @@ def get_info(self) -> None:
|
|
|
70
72
|
|
|
71
73
|
mpv_output = (
|
|
72
74
|
"<b>MPV information</b><br>"
|
|
75
|
+
f"Duration: {dw.player.duration} seconds ({util.seconds2time(dw.player.duration)})<br>"
|
|
76
|
+
# "Position: {} %<br>"
|
|
77
|
+
f"FPS: {dw.player.container_fps}<br>"
|
|
78
|
+
# "Rate: {}<br>"
|
|
79
|
+
f"Resolution: {dw.player.width}x{dw.player.height} pixels<br>"
|
|
80
|
+
# "Scale: {}<br>"
|
|
73
81
|
f"Video format: {dw.player.video_format}<br>"
|
|
74
82
|
# "State: {}<br>"
|
|
75
83
|
# "Media Resource Location: {}<br>"
|
|
@@ -77,12 +85,6 @@ def get_info(self) -> None:
|
|
|
77
85
|
# "Track: {}/{}<br>"
|
|
78
86
|
f"Number of media in media list: {dw.player.playlist_count}<br>"
|
|
79
87
|
f"Current time position: {dw.player.time_pos}<br>"
|
|
80
|
-
f"Duration: {dw.player.duration}<br>"
|
|
81
|
-
# "Position: {} %<br>"
|
|
82
|
-
f"FPS: {dw.player.container_fps}<br>"
|
|
83
|
-
# "Rate: {}<br>"
|
|
84
|
-
f"Video size: {dw.player.width}x{dw.player.height}<br>"
|
|
85
|
-
# "Scale: {}<br>"
|
|
86
88
|
f"Aspect ratio: {round(dw.player.width / dw.player.height, 3)}<br>"
|
|
87
89
|
# "is seekable? {}<br>"
|
|
88
90
|
# "has_vout? {}<br>"
|
|
@@ -26,8 +26,9 @@ This file is part of BORIS.
|
|
|
26
26
|
import binascii
|
|
27
27
|
import io
|
|
28
28
|
import json
|
|
29
|
-
import
|
|
29
|
+
from pathlib import Path
|
|
30
30
|
import re
|
|
31
|
+
import gui_utilities
|
|
31
32
|
|
|
32
33
|
from PySide6.QtCore import (
|
|
33
34
|
Qt,
|
|
@@ -727,7 +728,8 @@ class ModifiersMapCreatorWindow(QMainWindow):
|
|
|
727
728
|
self.fileName = fn
|
|
728
729
|
|
|
729
730
|
if self.fileName:
|
|
730
|
-
if os.path.splitext(self.fileName)[1] != ".boris_map":
|
|
731
|
+
# if os.path.splitext(self.fileName)[1] != ".boris_map":
|
|
732
|
+
if Path(self.fileName).suffix != ".boris_map":
|
|
731
733
|
self.fileName += ".boris_map"
|
|
732
734
|
self.saveMap()
|
|
733
735
|
|
|
@@ -744,7 +746,7 @@ class ModifiersMapCreatorWindow(QMainWindow):
|
|
|
744
746
|
else:
|
|
745
747
|
self.fileName = fn
|
|
746
748
|
|
|
747
|
-
if self.fileName and
|
|
749
|
+
if self.fileName and Path(self.fileName).suffix() != ".boris_map":
|
|
748
750
|
self.fileName += ".boris_map"
|
|
749
751
|
|
|
750
752
|
if self.fileName:
|
|
@@ -1006,25 +1008,8 @@ if __name__ == "__main__":
|
|
|
1006
1008
|
|
|
1007
1009
|
app = QApplication(sys.argv)
|
|
1008
1010
|
window = ModifiersMapCreatorWindow()
|
|
1009
|
-
window.resize(800, 700)
|
|
1010
1011
|
|
|
1011
|
-
|
|
1012
|
-
print(f"{window.height()=}")
|
|
1013
|
-
|
|
1014
|
-
# Get the screen geometry (screen size and position)
|
|
1015
|
-
screen_geometry = app.primaryScreen().geometry()
|
|
1016
|
-
|
|
1017
|
-
print(f"{screen_geometry=}")
|
|
1018
|
-
|
|
1019
|
-
# Calculate the center of the screen
|
|
1020
|
-
center_x = (screen_geometry.width() - window.width()) // 2
|
|
1021
|
-
center_y = (screen_geometry.height() - window.height()) // 2
|
|
1022
|
-
|
|
1023
|
-
print(f"{center_x=}")
|
|
1024
|
-
print(f"{center_y=}")
|
|
1025
|
-
|
|
1026
|
-
# Move the widget to the center of the screen
|
|
1027
|
-
window.move(center_x, center_y)
|
|
1012
|
+
gui_utilities.resize_center(app, window, cfg.CODING_MAP_RESIZE_W, cfg.CODING_MAP_RESIZE_H)
|
|
1028
1013
|
|
|
1029
1014
|
window.show()
|
|
1030
1015
|
sys.exit(app.exec())
|
boris/observation.py
CHANGED
|
@@ -1008,27 +1008,30 @@ class Observation(QDialog, Ui_Form):
|
|
|
1008
1008
|
|
|
1009
1009
|
# check if observation id not empty
|
|
1010
1010
|
if not self.leObservationId.text():
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1011
|
+
QMessageBox.critical(
|
|
1012
|
+
self,
|
|
1013
|
+
cfg.programName,
|
|
1014
|
+
"The <b>observation id</b> is mandatory and must be unique.",
|
|
1015
|
+
)
|
|
1015
1016
|
return False
|
|
1016
1017
|
|
|
1017
1018
|
# check if observation_type
|
|
1018
1019
|
if not any((self.rb_media_files.isChecked(), self.rb_live.isChecked(), self.rb_images.isChecked())):
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1020
|
+
QMessageBox.critical(
|
|
1021
|
+
self,
|
|
1022
|
+
cfg.programName,
|
|
1023
|
+
"Choose an observation type.",
|
|
1024
|
+
)
|
|
1023
1025
|
return False
|
|
1024
1026
|
|
|
1025
1027
|
# check if offset is correct
|
|
1026
1028
|
if self.cb_time_offset.isChecked():
|
|
1027
1029
|
if self.obs_time_offset.get_time() is None:
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1030
|
+
QMessageBox.critical(
|
|
1031
|
+
self,
|
|
1032
|
+
cfg.programName,
|
|
1033
|
+
"Check the time offset value.",
|
|
1034
|
+
)
|
|
1032
1035
|
return False
|
|
1033
1036
|
|
|
1034
1037
|
if self.rb_media_files.isChecked(): # observation based on media file(s)
|
|
@@ -1051,18 +1054,20 @@ class Observation(QDialog, Ui_Form):
|
|
|
1051
1054
|
|
|
1052
1055
|
# check if player #1 is used
|
|
1053
1056
|
if not players_list or min(players_list) > 1:
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1057
|
+
QMessageBox.critical(
|
|
1058
|
+
self,
|
|
1059
|
+
cfg.programName,
|
|
1060
|
+
"A media file must be loaded in player #1",
|
|
1061
|
+
)
|
|
1058
1062
|
return False
|
|
1059
1063
|
|
|
1060
1064
|
# check if players are used in crescent order
|
|
1061
1065
|
if set(list(range(min(players_list), max(players_list) + 1))) != set(players_list):
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
+
QMessageBox.critical(
|
|
1067
|
+
self,
|
|
1068
|
+
cfg.programName,
|
|
1069
|
+
"Some player are not used. Please reorganize your media files",
|
|
1070
|
+
)
|
|
1066
1071
|
return False
|
|
1067
1072
|
|
|
1068
1073
|
# check if more media in player #1 and media in other players
|
boris/observation_operations.py
CHANGED
|
@@ -888,7 +888,7 @@ def new_observation(self, mode: str = cfg.NEW, obsId: str = "") -> None:
|
|
|
888
888
|
self.pj[cfg.OBSERVATIONS][obsId][cfg.CLOSE_BEHAVIORS_BETWEEN_VIDEOS]
|
|
889
889
|
)
|
|
890
890
|
|
|
891
|
-
rv = observationWindow.
|
|
891
|
+
rv = observationWindow.exec()
|
|
892
892
|
|
|
893
893
|
# save geometry
|
|
894
894
|
gui_utilities.save_geometry(observationWindow, "new observation")
|
|
@@ -2012,7 +2012,7 @@ def initialize_new_media_observation(self) -> bool:
|
|
|
2012
2012
|
self.mpv_eof_reached_signal.connect(self.mpv_eof_reached)
|
|
2013
2013
|
self.video_click_signal.connect(self.player_clicked)
|
|
2014
2014
|
|
|
2015
|
-
self.actionPlay.setIcon(QIcon(f":/play_{
|
|
2015
|
+
self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
|
|
2016
2016
|
|
|
2017
2017
|
self.display_statusbar_info(self.observationId)
|
|
2018
2018
|
|
|
@@ -2406,15 +2406,15 @@ def event2media_file_name(observation: dict, timestamp: dec) -> Optional[str]:
|
|
|
2406
2406
|
"""
|
|
2407
2407
|
|
|
2408
2408
|
cumul_media_durations: list = [dec(0)]
|
|
2409
|
-
for media_file in observation[cfg.FILE][
|
|
2409
|
+
for media_file in observation[cfg.FILE][cfg.PLAYER1]:
|
|
2410
2410
|
media_duration = dec(str(observation[cfg.MEDIA_INFO][cfg.LENGTH][media_file]))
|
|
2411
|
-
cumul_media_durations.append(cumul_media_durations[-1] + media_duration)
|
|
2411
|
+
cumul_media_durations.append(round(cumul_media_durations[-1] + media_duration, 3))
|
|
2412
2412
|
|
|
2413
2413
|
cumul_media_durations.remove(dec(0))
|
|
2414
2414
|
|
|
2415
2415
|
# test if timestamp is at end of last media
|
|
2416
2416
|
if timestamp == cumul_media_durations[-1]:
|
|
2417
|
-
player_idx = len(observation[cfg.FILE][
|
|
2417
|
+
player_idx = len(observation[cfg.FILE][cfg.PLAYER1]) - 1
|
|
2418
2418
|
else:
|
|
2419
2419
|
player_idx = -1
|
|
2420
2420
|
for idx, value in enumerate(cumul_media_durations):
|
|
@@ -2424,7 +2424,7 @@ def event2media_file_name(observation: dict, timestamp: dec) -> Optional[str]:
|
|
|
2424
2424
|
break
|
|
2425
2425
|
|
|
2426
2426
|
if player_idx != -1:
|
|
2427
|
-
video_file_name = observation[cfg.FILE][
|
|
2427
|
+
video_file_name = observation[cfg.FILE][cfg.PLAYER1][player_idx]
|
|
2428
2428
|
else:
|
|
2429
2429
|
video_file_name = None
|
|
2430
2430
|
|
boris/player_dock_widget.py
CHANGED
|
@@ -23,8 +23,12 @@ This file is part of BORIS.
|
|
|
23
23
|
import sys
|
|
24
24
|
import logging
|
|
25
25
|
import functools
|
|
26
|
+
from . import mpv2 as mpv
|
|
27
|
+
import config as cfg
|
|
28
|
+
import gui_utilities
|
|
29
|
+
|
|
30
|
+
|
|
26
31
|
from PySide6.QtWidgets import (
|
|
27
|
-
QApplication,
|
|
28
32
|
QLabel,
|
|
29
33
|
QDockWidget,
|
|
30
34
|
QWidget,
|
|
@@ -38,8 +42,6 @@ from PySide6.QtWidgets import (
|
|
|
38
42
|
from PySide6.QtCore import Signal, QEvent, Qt
|
|
39
43
|
from PySide6.QtGui import QIcon, QAction
|
|
40
44
|
|
|
41
|
-
from . import mpv2 as mpv
|
|
42
|
-
import config as cfg
|
|
43
45
|
|
|
44
46
|
"""
|
|
45
47
|
try:
|
|
@@ -153,7 +155,7 @@ class DW_player(QDockWidget):
|
|
|
153
155
|
self.mute_button.setAutoRaise(True)
|
|
154
156
|
self.mute_action = QAction()
|
|
155
157
|
|
|
156
|
-
theme_mode =
|
|
158
|
+
theme_mode = gui_utilities.theme_mode()
|
|
157
159
|
|
|
158
160
|
self.mute_action.setIcon(QIcon(f":/volume_xmark_{theme_mode}"))
|
|
159
161
|
self.mute_action.triggered.connect(self.mute_action_triggered)
|
|
@@ -194,7 +196,7 @@ class DW_player(QDockWidget):
|
|
|
194
196
|
"""
|
|
195
197
|
emit signal when mute action is triggered
|
|
196
198
|
"""
|
|
197
|
-
theme_mode =
|
|
199
|
+
theme_mode = gui_utilities.theme_mode()
|
|
198
200
|
if self.player.mute:
|
|
199
201
|
self.mute_action.setIcon(QIcon(f":/volume_xmark_{theme_mode}"))
|
|
200
202
|
else:
|
boris/plugins.py
CHANGED
|
@@ -82,6 +82,8 @@ def load_plugins(self):
|
|
|
82
82
|
for file_ in sorted((Path(__file__).parent / "analysis_plugins").glob("*.py")):
|
|
83
83
|
if file_.name == "__init__.py":
|
|
84
84
|
continue
|
|
85
|
+
if file_.name.startswith("_"):
|
|
86
|
+
continue
|
|
85
87
|
plugin_name = get_plugin_name(file_)
|
|
86
88
|
if plugin_name is not None and plugin_name not in self.config_param.get(cfg.EXCLUDED_PLUGINS, set()):
|
|
87
89
|
# check if plugin with same name already loaded
|
|
@@ -96,6 +98,8 @@ def load_plugins(self):
|
|
|
96
98
|
for file_ in sorted(Path(self.config_param.get(cfg.PERSONAL_PLUGINS_DIR, "")).glob("*.py")):
|
|
97
99
|
if file_.name == "__init__.py":
|
|
98
100
|
continue
|
|
101
|
+
if file_.name.startswith("_"):
|
|
102
|
+
continue
|
|
99
103
|
plugin_name = get_plugin_name(file_)
|
|
100
104
|
if plugin_name is not None and plugin_name not in self.config_param.get(cfg.EXCLUDED_PLUGINS, set()):
|
|
101
105
|
# check if plugin with same name already loaded
|
boris/project.py
CHANGED
|
@@ -82,12 +82,12 @@ class BehavioralCategories(QDialog):
|
|
|
82
82
|
# add categories
|
|
83
83
|
self.lw.setColumnCount(2)
|
|
84
84
|
self.lw.setHorizontalHeaderLabels(["Category name", "Color"])
|
|
85
|
-
# self.lw.verticalHeader().hide()
|
|
86
85
|
self.lw.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
|
87
86
|
|
|
88
|
-
# self.lw.setSelectionBehavior(QAbstractItemView.SelectRows)
|
|
89
87
|
self.lw.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
90
88
|
|
|
89
|
+
behavioral_categories: list = []
|
|
90
|
+
|
|
91
91
|
if cfg.BEHAVIORAL_CATEGORIES_CONF in pj:
|
|
92
92
|
self.lw.setRowCount(len(pj.get(cfg.BEHAVIORAL_CATEGORIES_CONF, {})))
|
|
93
93
|
behav_cat = pj.get(cfg.BEHAVIORAL_CATEGORIES_CONF, {})
|
|
@@ -95,7 +95,7 @@ class BehavioralCategories(QDialog):
|
|
|
95
95
|
# name
|
|
96
96
|
item = QTableWidgetItem()
|
|
97
97
|
item.setText(behav_cat[key]["name"])
|
|
98
|
-
|
|
98
|
+
behavioral_categories.append(behav_cat[key]["name"])
|
|
99
99
|
self.lw.setItem(idx, 0, item)
|
|
100
100
|
# color
|
|
101
101
|
item = QTableWidgetItem()
|
|
@@ -103,24 +103,21 @@ class BehavioralCategories(QDialog):
|
|
|
103
103
|
if behav_cat[key].get(cfg.COLOR, ""):
|
|
104
104
|
item.setBackground(QColor(behav_cat[key].get(cfg.COLOR, "")))
|
|
105
105
|
else:
|
|
106
|
-
# item.setBackground(QColor(230, 230, 230))
|
|
107
106
|
item.setBackground(self.not_editable_column_color())
|
|
108
|
-
# item.setFlags(Qt.ItemIsEnabled)
|
|
109
107
|
self.lw.setItem(idx, 1, item)
|
|
110
108
|
else:
|
|
111
109
|
self.lw.setRowCount(len(pj.get(cfg.BEHAVIORAL_CATEGORIES, [])))
|
|
112
110
|
for idx, category in enumerate(sorted(pj.get(cfg.BEHAVIORAL_CATEGORIES, []))):
|
|
111
|
+
# name
|
|
113
112
|
item = QTableWidgetItem()
|
|
114
113
|
item.setText(category)
|
|
115
|
-
|
|
114
|
+
behavioral_categories.append(category)
|
|
116
115
|
self.lw.setItem(idx, 0, item)
|
|
117
|
-
|
|
116
|
+
# color
|
|
118
117
|
item = QTableWidgetItem()
|
|
119
118
|
item.setText("")
|
|
120
|
-
# item.setFlags(Qt.ItemIsEnabled)
|
|
121
|
-
self.lw.setItem(idx, 1, item)
|
|
122
119
|
|
|
123
|
-
|
|
120
|
+
self.lw.setItem(idx, 1, item)
|
|
124
121
|
|
|
125
122
|
self.vbox.addWidget(self.lw)
|
|
126
123
|
|
|
@@ -148,6 +145,41 @@ class BehavioralCategories(QDialog):
|
|
|
148
145
|
|
|
149
146
|
self.setLayout(self.vbox)
|
|
150
147
|
|
|
148
|
+
# check if behavioral categories are present in events
|
|
149
|
+
behavioral_categories_in_ethogram = set(
|
|
150
|
+
sorted([pj[cfg.ETHOGRAM][idx].get(cfg.BEHAVIOR_CATEGORY, "") for idx in pj.get(cfg.ETHOGRAM, {})])
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
if behavioral_categories_in_ethogram.difference(set(behavioral_categories)):
|
|
154
|
+
if (
|
|
155
|
+
dialog.MessageDialog(
|
|
156
|
+
cfg.programName,
|
|
157
|
+
(
|
|
158
|
+
"They are behavioral categories that are present in ethogram but not defined.<br>"
|
|
159
|
+
f"{behavioral_categories_in_ethogram.difference(set(behavioral_categories))}<br>"
|
|
160
|
+
"<br>"
|
|
161
|
+
"Do you want to add them in the behavioral categories list?"
|
|
162
|
+
),
|
|
163
|
+
[cfg.YES, cfg.NO],
|
|
164
|
+
)
|
|
165
|
+
== cfg.YES
|
|
166
|
+
):
|
|
167
|
+
# add behavioral categories present in ethogram in behavioal categories list
|
|
168
|
+
rc = self.lw.rowCount()
|
|
169
|
+
self.lw.setRowCount(rc + len(behavioral_categories_in_ethogram.difference(set(behavioral_categories))))
|
|
170
|
+
for idx, category in enumerate(sorted(list(behavioral_categories_in_ethogram.difference(set(behavioral_categories))))):
|
|
171
|
+
print(category)
|
|
172
|
+
# name
|
|
173
|
+
item = QTableWidgetItem()
|
|
174
|
+
item.setText(category)
|
|
175
|
+
# behavioral_categories.append(category)
|
|
176
|
+
self.lw.setItem(rc + idx, 0, item)
|
|
177
|
+
# color
|
|
178
|
+
item = QTableWidgetItem()
|
|
179
|
+
item.setText("")
|
|
180
|
+
|
|
181
|
+
self.lw.setItem(rc + idx, 1, item)
|
|
182
|
+
|
|
151
183
|
def not_editable_column_color(self):
|
|
152
184
|
"""
|
|
153
185
|
return a color for the not editable column
|
|
@@ -663,7 +695,7 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
663
695
|
QMessageBox.warning(
|
|
664
696
|
self,
|
|
665
697
|
cfg.programName,
|
|
666
|
-
("The following behavior{} are not defined in the ethogram:<br>
|
|
698
|
+
("The following behavior{} are not defined in the ethogram:<br>{}").format(
|
|
667
699
|
"s" if len(bcm_code_not_found) > 1 else "", ",".join(bcm_code_not_found)
|
|
668
700
|
),
|
|
669
701
|
)
|
|
@@ -733,7 +765,7 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
733
765
|
behavioral categories manager
|
|
734
766
|
"""
|
|
735
767
|
|
|
736
|
-
bc = BehavioralCategories(self.pj)
|
|
768
|
+
bc = BehavioralCategories(self.pj)
|
|
737
769
|
|
|
738
770
|
if bc.exec_():
|
|
739
771
|
self.pj[cfg.BEHAVIORAL_CATEGORIES] = []
|
|
@@ -1377,7 +1409,7 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1377
1409
|
# let user select a coding maop
|
|
1378
1410
|
file_name, _ = QFileDialog().getOpenFileName(
|
|
1379
1411
|
self,
|
|
1380
|
-
"Select a modifier coding map for
|
|
1412
|
+
f"Select a modifier coding map for {self.twBehaviors.item(row, cfg.behavioursFields['code']).text()} behavior",
|
|
1381
1413
|
"",
|
|
1382
1414
|
"BORIS map files (*.boris_map);;All files (*)",
|
|
1383
1415
|
)
|
|
@@ -1759,24 +1791,40 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1759
1791
|
return {cfg.CANCEL: True}
|
|
1760
1792
|
|
|
1761
1793
|
# check if behavior belong to category that is not in categories list
|
|
1762
|
-
|
|
1794
|
+
missing_behavior_category: list = []
|
|
1763
1795
|
for idx in checked_ethogram:
|
|
1764
1796
|
if cfg.BEHAVIOR_CATEGORY in checked_ethogram[idx]:
|
|
1765
1797
|
if checked_ethogram[idx][cfg.BEHAVIOR_CATEGORY]:
|
|
1766
1798
|
if checked_ethogram[idx][cfg.BEHAVIOR_CATEGORY] not in self.pj[cfg.BEHAVIORAL_CATEGORIES]:
|
|
1767
|
-
|
|
1768
|
-
|
|
1799
|
+
missing_behavior_category.append(
|
|
1800
|
+
(checked_ethogram[idx][cfg.BEHAVIOR_CODE], checked_ethogram[idx][cfg.BEHAVIOR_CATEGORY])
|
|
1801
|
+
)
|
|
1802
|
+
if missing_behavior_category:
|
|
1769
1803
|
response = dialog.MessageDialog(
|
|
1770
1804
|
f"{cfg.programName} - Behavioral categories",
|
|
1771
1805
|
(
|
|
1772
|
-
"The behavioral
|
|
1773
|
-
f"{', '.join(set(['<b>' + x[1] + '</b>' + ' (used with <b>' + x[0] + '</b>)' for x in
|
|
1774
|
-
"are
|
|
1806
|
+
"The behavioral category/ies<br> "
|
|
1807
|
+
f"{', '.join(set(['<b>' + x[1] + '</b>' + ' (used with <b>' + x[0] + '</b>)<br>' for x in missing_behavior_category]))} "
|
|
1808
|
+
"are not defined in behavioral categories list.<br>"
|
|
1775
1809
|
),
|
|
1776
|
-
["Add behavioral category/ies",
|
|
1810
|
+
["Add behavioral category/ies", cfg.IGNORE, cfg.CANCEL],
|
|
1777
1811
|
)
|
|
1778
1812
|
if response == "Add behavioral category/ies":
|
|
1779
|
-
|
|
1813
|
+
if cfg.BEHAVIORAL_CATEGORIES_CONF not in self.pj:
|
|
1814
|
+
self.pj[cfg.BEHAVIORAL_CATEGORIES_CONF] = {}
|
|
1815
|
+
for x1 in set(x[1] for x in missing_behavior_category):
|
|
1816
|
+
self.pj[cfg.BEHAVIORAL_CATEGORIES].append(x1)
|
|
1817
|
+
|
|
1818
|
+
if self.pj[cfg.BEHAVIORAL_CATEGORIES_CONF]:
|
|
1819
|
+
index = str(max([int(k) for k in self.pj[cfg.BEHAVIORAL_CATEGORIES_CONF]]) + 1)
|
|
1820
|
+
else:
|
|
1821
|
+
index = "0"
|
|
1822
|
+
|
|
1823
|
+
self.pj[cfg.BEHAVIORAL_CATEGORIES_CONF][index] = {
|
|
1824
|
+
"name": x1,
|
|
1825
|
+
cfg.COLOR: "",
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1780
1828
|
if response == cfg.CANCEL:
|
|
1781
1829
|
return {cfg.CANCEL: True}
|
|
1782
1830
|
|
boris/project_import_export.py
CHANGED
|
@@ -89,9 +89,10 @@ def export_ethogram(self) -> None:
|
|
|
89
89
|
return
|
|
90
90
|
pj = dict(cfg.EMPTY_PROJECT)
|
|
91
91
|
pj[cfg.ETHOGRAM] = dict(r)
|
|
92
|
-
# behavioral categories
|
|
93
92
|
|
|
93
|
+
# behavioral categories
|
|
94
94
|
pj[cfg.BEHAVIORAL_CATEGORIES] = list(self.pj[cfg.BEHAVIORAL_CATEGORIES])
|
|
95
|
+
pj[cfg.BEHAVIORAL_CATEGORIES_CONF] = dict(self.pj.get(cfg.BEHAVIORAL_CATEGORIES_CONF, {}))
|
|
95
96
|
|
|
96
97
|
# project file indentation
|
|
97
98
|
file_indentation = self.config_param.get(cfg.PROJECT_FILE_INDENTATION, cfg.PROJECT_FILE_INDENTATION_DEFAULT_VALUE)
|
|
@@ -309,6 +310,7 @@ def import_ethogram_from_dict(self, project: dict):
|
|
|
309
310
|
"""
|
|
310
311
|
# import behavioral_categories
|
|
311
312
|
self.pj[cfg.BEHAVIORAL_CATEGORIES] = list(project.get(cfg.BEHAVIORAL_CATEGORIES, []))
|
|
313
|
+
self.pj[cfg.BEHAVIORAL_CATEGORIES_CONF] = list(project.get(cfg.BEHAVIORAL_CATEGORIES_CONF, {}))
|
|
312
314
|
|
|
313
315
|
# configuration of behaviours
|
|
314
316
|
if not (cfg.ETHOGRAM in project and project[cfg.ETHOGRAM]):
|
boris/utilities.py
CHANGED
|
@@ -21,14 +21,12 @@ Copyright 2012-2025 Olivier Friard
|
|
|
21
21
|
|
|
22
22
|
import csv
|
|
23
23
|
import datetime as dt
|
|
24
|
-
import hashlib
|
|
25
24
|
import json
|
|
26
25
|
import logging
|
|
27
26
|
import math
|
|
28
27
|
import os
|
|
29
28
|
import pathlib as pl
|
|
30
29
|
import re
|
|
31
|
-
import socket
|
|
32
30
|
import subprocess
|
|
33
31
|
import sys
|
|
34
32
|
import urllib.parse
|
|
@@ -292,27 +290,6 @@ def return_file_header_footer(file_name: str, file_row_number: int = 0, row_numb
|
|
|
292
290
|
return header, footer
|
|
293
291
|
|
|
294
292
|
|
|
295
|
-
def bytes_to_str(b: bytes) -> str:
|
|
296
|
-
"""
|
|
297
|
-
Translate bytes to string.
|
|
298
|
-
|
|
299
|
-
Args:
|
|
300
|
-
b (bytes): byte to convert
|
|
301
|
-
|
|
302
|
-
Returns:
|
|
303
|
-
str: converted byte
|
|
304
|
-
"""
|
|
305
|
-
|
|
306
|
-
if isinstance(b, bytes):
|
|
307
|
-
fileSystemEncoding = sys.getfilesystemencoding()
|
|
308
|
-
# hack for PyInstaller
|
|
309
|
-
if fileSystemEncoding is None:
|
|
310
|
-
fileSystemEncoding = "UTF-8"
|
|
311
|
-
return b.decode(fileSystemEncoding)
|
|
312
|
-
else:
|
|
313
|
-
return b
|
|
314
|
-
|
|
315
|
-
|
|
316
293
|
def convertTime(time_format: str, sec: Union[float, dec]) -> Union[str, None]:
|
|
317
294
|
"""
|
|
318
295
|
convert time in base at the current format (S or HHMMSS)
|
|
@@ -381,20 +358,6 @@ def count_media_file(media_files: dict) -> int:
|
|
|
381
358
|
return sum([len(media_files[idx]) for idx in media_files])
|
|
382
359
|
|
|
383
360
|
|
|
384
|
-
def file_content_md5(file_name: str) -> str:
|
|
385
|
-
"""
|
|
386
|
-
returns the MD5 sum of file content
|
|
387
|
-
"""
|
|
388
|
-
hash_md5 = hashlib.md5()
|
|
389
|
-
try:
|
|
390
|
-
with open(file_name, "rb") as f:
|
|
391
|
-
for chunk in iter(lambda: f.read(4096), b""):
|
|
392
|
-
hash_md5.update(chunk)
|
|
393
|
-
return hash_md5.hexdigest()
|
|
394
|
-
except FileNotFoundError:
|
|
395
|
-
return ""
|
|
396
|
-
|
|
397
|
-
|
|
398
361
|
def txt2np_array(
|
|
399
362
|
file_name: str, columns_str: str, substract_first_value: str, converters=None, column_converter=None
|
|
400
363
|
) -> Tuple[bool, str, np.array]:
|
|
@@ -760,19 +723,6 @@ def get_current_points_by_subject(
|
|
|
760
723
|
return current_points
|
|
761
724
|
|
|
762
725
|
|
|
763
|
-
def get_ip_address():
|
|
764
|
-
"""Get current IP address
|
|
765
|
-
|
|
766
|
-
Args:
|
|
767
|
-
|
|
768
|
-
Returns:
|
|
769
|
-
str: IP address
|
|
770
|
-
"""
|
|
771
|
-
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
772
|
-
s.connect(("8.8.8.8", 80))
|
|
773
|
-
return s.getsockname()[0]
|
|
774
|
-
|
|
775
|
-
|
|
776
726
|
def check_txt_file(file_name: str) -> dict:
|
|
777
727
|
"""
|
|
778
728
|
Extract parameters of txt file (test for tsv csv)
|
|
@@ -950,7 +900,7 @@ def intfloatstr(s: str) -> int:
|
|
|
950
900
|
return s
|
|
951
901
|
|
|
952
902
|
|
|
953
|
-
def distance(p1, p2):
|
|
903
|
+
def distance(p1: tuple, p2: tuple) -> float:
|
|
954
904
|
"""
|
|
955
905
|
euclidean distance between 2 points
|
|
956
906
|
"""
|
|
@@ -987,12 +937,12 @@ def oriented_angle(P1: tuple, P2: tuple, P3: tuple) -> float:
|
|
|
987
937
|
Calculate the oriented angle between two segments.
|
|
988
938
|
|
|
989
939
|
Args:
|
|
990
|
-
P1: Coordinates of the vertex
|
|
991
|
-
P2: Coordinates of the first point
|
|
992
|
-
P3: Coordinates of the second point
|
|
940
|
+
P1 (tuple): Coordinates of the vertex
|
|
941
|
+
P2 (tuple): Coordinates of the first point
|
|
942
|
+
P3 (tuple): Coordinates of the second point
|
|
993
943
|
|
|
994
944
|
Returns:
|
|
995
|
-
The oriented angle between the two segments in degrees.
|
|
945
|
+
float: The oriented angle between the two segments in degrees.
|
|
996
946
|
"""
|
|
997
947
|
|
|
998
948
|
x1, y1 = P1
|
|
@@ -1008,6 +958,36 @@ def oriented_angle(P1: tuple, P2: tuple, P3: tuple) -> float:
|
|
|
1008
958
|
return oriented_angle
|
|
1009
959
|
|
|
1010
960
|
|
|
961
|
+
def oriented_angle_trigo(B: Tuple[float, float], A: Tuple[float, float], C: Tuple[float, float]) -> float:
|
|
962
|
+
"""
|
|
963
|
+
Calculates the oriented angle between vectors BA and BC, in degrees.
|
|
964
|
+
The angle is positive in the counter-clockwise (trigonometric) direction.
|
|
965
|
+
|
|
966
|
+
Parameters:
|
|
967
|
+
B: The pivot point (the origin of the vectors BA and BC).
|
|
968
|
+
A, C: Points that define the vectors.
|
|
969
|
+
|
|
970
|
+
Returns:
|
|
971
|
+
Angle in degrees, between 0 and 360.
|
|
972
|
+
"""
|
|
973
|
+
# Vectors BA and BC
|
|
974
|
+
v1 = (A[0] - B[0], A[1] - B[1])
|
|
975
|
+
v2 = (C[0] - B[0], C[1] - B[1])
|
|
976
|
+
|
|
977
|
+
# Dot product and 2D cross product (determinant)
|
|
978
|
+
dot = v1[0] * v2[0] + v1[1] * v2[1]
|
|
979
|
+
det = v1[0] * v2[1] - v1[1] * v2[0]
|
|
980
|
+
|
|
981
|
+
# Signed angle in radians, then converted to degrees
|
|
982
|
+
angle_rad = math.atan2(det, dot)
|
|
983
|
+
angle_deg = math.degrees(angle_rad)
|
|
984
|
+
|
|
985
|
+
if angle_deg < 0:
|
|
986
|
+
angle_deg += 360
|
|
987
|
+
|
|
988
|
+
return angle_deg
|
|
989
|
+
|
|
990
|
+
|
|
1011
991
|
def mem_info():
|
|
1012
992
|
"""
|
|
1013
993
|
get info about total mem, used mem and available mem using:
|
boris/version.py
CHANGED
|
@@ -1,28 +1,27 @@
|
|
|
1
|
-
boris/1.py,sha256=rb6Nstw1vIHlBwMnzExVOlgU2F75Tuf1VYRXBTrZUFg,1082
|
|
2
1
|
boris/__init__.py,sha256=iAtmVMy22TJpMmxVTMSK_6-wXnCbx1ogvWgfYEcbHzU,773
|
|
3
2
|
boris/__main__.py,sha256=ANjTbXgXDoz2nB1tCtOIllfIVotCa602iebACX7rXaE,764
|
|
4
3
|
boris/about.py,sha256=KEUz6nryrg8FceVyFsf8sMz-xWd2cGwIUfuVydHxqC4,5366
|
|
5
4
|
boris/add_modifier.py,sha256=DWqxkKDBm21QH_kPvhpnltwLtFvPxne0VmZ1SY26hj8,26340
|
|
6
5
|
boris/add_modifier_ui.py,sha256=Y7TLO5uS6zW7zpjXmjA4V_VIp_bFDNtjOTbJ9Q6m-mQ,11601
|
|
7
6
|
boris/advanced_event_filtering.py,sha256=VlvU12mL6xYacZOvJAi5uLpHMcmAw5Pvuvmka-PN29c,15469
|
|
8
|
-
boris/behav_coding_map_creator.py,sha256=
|
|
7
|
+
boris/behav_coding_map_creator.py,sha256=_WmfWTYkKh_a7pZa49h2GtORCi6h8joZTWihud6YDBE,38826
|
|
9
8
|
boris/behavior_binary_table.py,sha256=bpmRDpEjq0rw3YOCoN_He3kfUe8A_R6E48kQR7KnkH8,12453
|
|
10
9
|
boris/behaviors_coding_map.py,sha256=xIGJxp2eghrpiGDmYH73eJPERuyc4A_54uT-Got3zTs,7302
|
|
11
10
|
boris/boris_cli.py,sha256=n0OiVvZM1gM6E7yKaff9wlgmpAGK4TK052VRi8AabJo,13196
|
|
12
11
|
boris/cmd_arguments.py,sha256=oWb-FvhKLbKJhATlTHy9muWu8XnnUfOZ-3Fmz2M8Yzc,1848
|
|
13
12
|
boris/coding_pad.py,sha256=fBKdp7DDyupySJosIYtqNd8s2E-GruzCgVhDFsoVWKE,10986
|
|
14
|
-
boris/config.py,sha256=
|
|
13
|
+
boris/config.py,sha256=rPfhmdE5XilliNjioC1KuDL_LNze4MZBDy_p0zK4tt8,17349
|
|
15
14
|
boris/config_file.py,sha256=1-2ZmTvKET57rwrLR1dXblt0AxMpGB1LAiHxu-Sy8es,13543
|
|
16
15
|
boris/connections.py,sha256=rVI18AuXh8cEnnoCKJk0RMWAaiNOpiaS554Okgk3SBY,19383
|
|
17
16
|
boris/converters.py,sha256=c1Jps-URoglY5ILQHz-pCCf6-4DFUHZLtqr_ofsrFg0,11722
|
|
18
17
|
boris/converters_ui.py,sha256=uu7LOBV_fKv2DBdOqsqPwjGsjgONr5ODBoscAA-EP48,9900
|
|
19
18
|
boris/cooccurence.py,sha256=tVERC-V8MWjWHlGEfDuu08iS94qjt4do-38jwI62QaY,10367
|
|
20
|
-
boris/core.py,sha256=
|
|
19
|
+
boris/core.py,sha256=EK19Xn0Z_sxu9TaqeRimMzzpn9ThZdTqUtlJJXz6pVY,233512
|
|
21
20
|
boris/core_qrc.py,sha256=T3ki5e2Pj0I0QBGz63MPUgZzl8F_VHZwSq074mRNBDU,650669
|
|
22
21
|
boris/core_ui.py,sha256=SeC26uveDCjrCBLsRPuQ6FaapKfON_HIRcQStEDLhl4,76384
|
|
23
22
|
boris/db_functions.py,sha256=Uw9wWH_Pe-qNzpV1k21YG_jKsoOmfY_iiK_7ARZHGDc,13352
|
|
24
23
|
boris/dev.py,sha256=9pUElbjl9g17rFUJXX5aVSu55_iIKIuDxNdrB0DI_d0,3671
|
|
25
|
-
boris/dialog.py,sha256=
|
|
24
|
+
boris/dialog.py,sha256=6MayZdOsLZCXP1ns02EH9bygYbWPNoEKrXNQrOaw_aw,33071
|
|
26
25
|
boris/duration_widget.py,sha256=GjZgCAMGOcsNjoPiRImEVe6yMkH2vuNoh44ulpd5nlg,6924
|
|
27
26
|
boris/edit_event.py,sha256=2hpxn9DYuJW1CK02hF4iU0--J_0C_KTiN9gGZ1frJBc,7678
|
|
28
27
|
boris/edit_event_ui.py,sha256=vhglQrhkF9tt0HVlkXnkG7symW0eOFA7nhbk6l_qn3k,7772
|
|
@@ -34,38 +33,38 @@ boris/export_events.py,sha256=3B336WEA0g_8oW3VDo_kfq5D0ISu-e7z2f-_ROUvU9c,39756
|
|
|
34
33
|
boris/export_observation.py,sha256=SvKhuGa-Ag_kK3igL9DFdJ0TKoQLDneu54R_uiSHUyo,50813
|
|
35
34
|
boris/external_processes.py,sha256=vpmhA4Lj2GblBIrDD0YjesB8HPOgx4K9gSWVhTop4Cg,11927
|
|
36
35
|
boris/geometric_measurement.py,sha256=4pI-AYpBSFlJBqS-f8dnkgLtj_Z2E5kwwAdh6WwZ4kk,35049
|
|
37
|
-
boris/gui_utilities.py,sha256=
|
|
36
|
+
boris/gui_utilities.py,sha256=2HdWFxo2y0oxC29VJAA3R-TOMxVbOy3FuVwspjrTD6A,5519
|
|
38
37
|
boris/image_overlay.py,sha256=zZAL8MTt2i2s58CuX81Nym3rJ5pKiTeP4AO8WbIUonM,2527
|
|
39
38
|
boris/import_observations.py,sha256=hwEPIag741AXTFIuxDdZLDvLrsmvaqTkjyTjQu5M_RA,8798
|
|
40
39
|
boris/irr.py,sha256=o5QN3B2b-02AUkrklMJCitFGsfiUDtmI0MxUbPv2cBg,22472
|
|
41
40
|
boris/latency.py,sha256=48z9L_A582-wKCfD0M3h0uyYkeL2ezjlQAS_GzeoOe0,9739
|
|
42
41
|
boris/measurement_widget.py,sha256=lZV62KtK6TjdoNbKxj3uyNAuL5dfnQnn7mYwzMo-dOM,4480
|
|
43
|
-
boris/media_file.py,sha256=
|
|
42
|
+
boris/media_file.py,sha256=Wnw-PCyAz6CA00zhjrx0UTgXZ0wmHuNlnElV_TzJ_2M,4818
|
|
44
43
|
boris/menu_options.py,sha256=UEB3GxRh6YKNCg67qbhOVhJW1ZOznuPe15bADc_CNTI,7062
|
|
45
|
-
boris/modifier_coding_map_creator.py,sha256=
|
|
44
|
+
boris/modifier_coding_map_creator.py,sha256=NQHy_txgxKZnGByXiro_Oy_cq4DrFaFiAYwVp1CWrTs,33281
|
|
46
45
|
boris/modifiers_coding_map.py,sha256=oT56ZY_PXhEJsMoblEsyNMAPbDpv7ZMOCnvmt7Ibx_Y,4554
|
|
47
46
|
boris/mpv-1.0.3.py,sha256=EXRtzQqFjOn4wMC6482Ilq3fNQ9N1GRP1VxwLzdeaBY,88077
|
|
48
47
|
boris/mpv.py,sha256=EfzIHjPbgewG4w3smEtqEUPZoVwYmMQkL4Q8ZyW-a58,76410
|
|
49
48
|
boris/mpv2.py,sha256=IUI4t4r9GYX7G5OXTjd3RhMMOkDdfal_15buBgksLsk,92152
|
|
50
|
-
boris/observation.py,sha256=
|
|
51
|
-
boris/observation_operations.py,sha256=
|
|
49
|
+
boris/observation.py,sha256=d-7q-RkMHuLDV87nF4yahvDFPYhlXp6GmE80vckn5zU,57073
|
|
50
|
+
boris/observation_operations.py,sha256=Eh-D4ApnuHaXX6fhrFwSsAvEFjMHu7eaI_9vci-drgQ,105790
|
|
52
51
|
boris/observation_ui.py,sha256=DAnU94QBNvkLuHT6AxTwqSk_D_n6VUhSl8PexZv_dUk,33309
|
|
53
52
|
boris/observations_list.py,sha256=NqwECGHtHYmKhSe-qCfqPmJ24SSfzlXvIXS2i3op_zE,10591
|
|
54
53
|
boris/otx_parser.py,sha256=70QvilzFHXbjAHR88YH0aEXJ3xxheLS5fZGgHFHGpNE,16367
|
|
55
54
|
boris/param_panel.py,sha256=G0XzNmJIX89-n2OQTDccuY_wWMhr3p7GB4ZorbU6EWc,8786
|
|
56
55
|
boris/param_panel_ui.py,sha256=4emQDFmuL4_R7bKxosLjdUb-VSPWkDm7suy38F5EKcA,13260
|
|
57
|
-
boris/player_dock_widget.py,sha256=
|
|
56
|
+
boris/player_dock_widget.py,sha256=cJ2UB6qfdxrk22nLWgOzs5EomCfR7OcznJq67UjF8dg,6186
|
|
58
57
|
boris/plot_data_module.py,sha256=6QbLKfyGp4TYRyHnB9G45y5XrpeXLytcorltEAWfYak,16562
|
|
59
58
|
boris/plot_events.py,sha256=CF6gnsTeaPG-P1USwh4An2s31NoMJ1roHDImcQrQj3c,24060
|
|
60
59
|
boris/plot_events_rt.py,sha256=xig__Uea3mQqO5raMBVB3pm3vuQkjAbJpwSS7AwIob8,8327
|
|
61
60
|
boris/plot_spectrogram_rt.py,sha256=JV8N7T8133wGVhlPxmgOb426u1g1p21-LbTqgaeddkk,8361
|
|
62
61
|
boris/plot_waveform_rt.py,sha256=05JN_6HCq674ROore_6PNw93GQNZJQDlDxp2ODAFkkA,7474
|
|
63
|
-
boris/plugins.py,sha256=
|
|
62
|
+
boris/plugins.py,sha256=CCS1I44OMkGZqcfLGKNPGfEQXPgngocy5YhWveXQPKM,10029
|
|
64
63
|
boris/preferences.py,sha256=qPfd9Tyg7u30kXwVqMOgkdy2RXri9bItRa5U2-ZVQmg,16847
|
|
65
64
|
boris/preferences_ui.py,sha256=D2bFLb3E0m6IwSeqKoItRDiqvPmJGoeXLHF2K02n1Zo,26293
|
|
66
|
-
boris/project.py,sha256=
|
|
65
|
+
boris/project.py,sha256=Yrpoqr5kF8wlvbeYiwm_RK2A9-dLfnxfnhp8TU5ZQbM,86452
|
|
67
66
|
boris/project_functions.py,sha256=mPaKTjcegsC6n-J8ZsOhWh_4TepJ3Y-vwXiIgl9nij0,79702
|
|
68
|
-
boris/project_import_export.py,sha256=
|
|
67
|
+
boris/project_import_export.py,sha256=oBG1CSXfKISsb3TLNT-8BH8kZPAzxIYSNemlLVH1Lh8,38560
|
|
69
68
|
boris/project_ui.py,sha256=yB-ewhHt8S8DTTRIk-dNK2tPMNU24lNji9fDW_Xazu8,38805
|
|
70
69
|
boris/qrc_boris.py,sha256=aH-qUirYY1CGxmTK1SFCPvuZfazIHX4DdUKF1gxZeYM,675008
|
|
71
70
|
boris/qrc_boris5.py,sha256=prnOw7VGXWXRuVCYp_yIrmWhrlG1F9rx-3BQvkPenjY,161608
|
|
@@ -78,8 +77,8 @@ boris/synthetic_time_budget.py,sha256=3Eb9onMLmgqCLd50CuxV9L8RV2ESzfaMWvPK_bXUMM
|
|
|
78
77
|
boris/time_budget_functions.py,sha256=y5He8crz0xsTxVfz0jATwFFQVnPAIrNHja_0sF6NtRE,52551
|
|
79
78
|
boris/time_budget_widget.py,sha256=z-tyITBtIz-KH1H2OdMB5a8x9QQLK7Wu96-zkC6NVDA,43213
|
|
80
79
|
boris/transitions.py,sha256=_aZJfJWv3EBrtmQ7qsdTCayQo6uWU7BXqtQQgflEhr4,12250
|
|
81
|
-
boris/utilities.py,sha256=
|
|
82
|
-
boris/version.py,sha256=
|
|
80
|
+
boris/utilities.py,sha256=7iZ6XQ8G1by9j-xVjTCBX6dp9FQg_9MbC5Ym1sZJYp0,52639
|
|
81
|
+
boris/version.py,sha256=wqhoE3kKrpzgOgOUWT2QNHWqh24C1Kp05JOwqZKmU8k,787
|
|
83
82
|
boris/video_equalizer.py,sha256=FartoGghFK-T53zklP70rPKYqTuzL8qdvfGlsOF2wwc,5854
|
|
84
83
|
boris/video_equalizer_ui.py,sha256=1CG3s79eM4JAbaCx3i1ILZXLceb41_gGXlOLNfpBgnw,10142
|
|
85
84
|
boris/video_operations.py,sha256=mh3iR__Sm2KnV44L_sW2pOo3AgLwlM7wiTnnqQiAVs4,9381
|
|
@@ -87,6 +86,7 @@ boris/view_df.py,sha256=AKScLASX2Uatw7rqPbsnio83eVT4GZYCFhL091eMvlY,3370
|
|
|
87
86
|
boris/view_df_ui.py,sha256=CaMeRH_vQ00CTDDFQn73ZZaS-r8BSTWpL-dMCFqzJ_Q,2775
|
|
88
87
|
boris/write_event.py,sha256=Fsy_apFl7RLnRsBAwXqACr_URnE_QoAFiPMh0o95ANg,23852
|
|
89
88
|
boris/analysis_plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
89
|
+
boris/analysis_plugins/_latency.py,sha256=vLWCPh0cPpAEpdMboCkVIuYO1e3pJlP5tEICe27Sqms,2027
|
|
90
90
|
boris/analysis_plugins/number_of_occurences.py,sha256=IDyDrdezqvSKT3BlD8QWpSYk8X9nnBBLI80OUnFJ3bY,509
|
|
91
91
|
boris/analysis_plugins/number_of_occurences_by_independent_variable.py,sha256=t39bmmmZIDCSbcDvVeiKAhKNNP2SdpHp417JczHEnP4,793
|
|
92
92
|
boris/analysis_plugins/time_budget.py,sha256=C1wNYwd5Jugr8h5z2aXRUBY8dF8pD4n953dPwNHY5VY,2244
|
|
@@ -96,9 +96,9 @@ boris/portion/dict.py,sha256=SyHxc7PfDw2ufNLFQycwJtzmRfL48rDp4UrM2KN7IWc,11282
|
|
|
96
96
|
boris/portion/func.py,sha256=3TkQtFKLfsqntwd27HSGHceFhnXHmT-EbNMqktElC5Q,2174
|
|
97
97
|
boris/portion/interval.py,sha256=bAdUiJjGeUAPgsBAImwNeviiwfQq5odfhFZccAWzOTA,20299
|
|
98
98
|
boris/portion/io.py,sha256=ppNeRpiLNrocF1yzGeuEUIhYMf2LfsR-cji3d0nmvUs,6371
|
|
99
|
-
boris_behav_obs-9.3.
|
|
100
|
-
boris_behav_obs-9.3.
|
|
101
|
-
boris_behav_obs-9.3.
|
|
102
|
-
boris_behav_obs-9.3.
|
|
103
|
-
boris_behav_obs-9.3.
|
|
104
|
-
boris_behav_obs-9.3.
|
|
99
|
+
boris_behav_obs-9.3.3.dist-info/licenses/LICENSE.TXT,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
|
|
100
|
+
boris_behav_obs-9.3.3.dist-info/METADATA,sha256=oiGWIVEJKIfM9EOznjvVeDr15l2rtecxzculq_IsJAg,4518
|
|
101
|
+
boris_behav_obs-9.3.3.dist-info/WHEEL,sha256=oSJJyWjO7Z2XSScFQUpXG1HL-N0sFMqqeKVVbZTPkWc,109
|
|
102
|
+
boris_behav_obs-9.3.3.dist-info/entry_points.txt,sha256=k__8XvFi4vaA4QFvQehCZjYkKmZH34HSAJI2iYCWrMs,52
|
|
103
|
+
boris_behav_obs-9.3.3.dist-info/top_level.txt,sha256=fJSgm62S7WesiwTorGbOO4nNN0yzgZ3klgfGi3Px4qI,6
|
|
104
|
+
boris_behav_obs-9.3.3.dist-info/RECORD,,
|
boris/1.py
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import time
|
|
2
|
-
from PIL import Image, ImageDraw, ImageFont
|
|
3
|
-
import mpv
|
|
4
|
-
|
|
5
|
-
player = mpv.MPV()
|
|
6
|
-
|
|
7
|
-
player.loop = True
|
|
8
|
-
player.play('/home/olivier/gdrive_sync/src/python/generate_video_test/video1.mp4')
|
|
9
|
-
player.wait_until_playing()
|
|
10
|
-
|
|
11
|
-
font = ImageFont.truetype('DejaVuSans.ttf', 40)
|
|
12
|
-
|
|
13
|
-
overlay = player.create_image_overlay()
|
|
14
|
-
|
|
15
|
-
img = Image.new('RGBA', (400, 150), (255, 255, 255, 0))
|
|
16
|
-
d = ImageDraw.Draw(img)
|
|
17
|
-
d.text((10, 10), 'Hello World', font=font, fill=(0, 255, 255, 128))
|
|
18
|
-
#d.text((10, 60), f't={ts:.3f}', font=font, fill=(255, 0, 255, 255))
|
|
19
|
-
|
|
20
|
-
pos = 100
|
|
21
|
-
|
|
22
|
-
overlay.update(img, pos=(2*pos, pos))
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
while not player.core_idle:
|
|
26
|
-
pass
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
'''
|
|
30
|
-
for pos in range(0, 500, 5):
|
|
31
|
-
ts = player.time_pos
|
|
32
|
-
if ts is None:
|
|
33
|
-
break
|
|
34
|
-
|
|
35
|
-
img = Image.new('RGBA', (400, 150), (255, 255, 255, 0))
|
|
36
|
-
d = ImageDraw.Draw(img)
|
|
37
|
-
d.text((10, 10), 'Hello World', font=font, fill=(0, 255, 255, 128))
|
|
38
|
-
d.text((10, 60), f't={ts:.3f}', font=font, fill=(255, 0, 255, 255))
|
|
39
|
-
|
|
40
|
-
overlay.update(img, pos=(2*pos, pos))
|
|
41
|
-
time.sleep(0.05)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
overlay.remove()
|
|
45
|
-
'''
|
|
File without changes
|
|
File without changes
|
|
File without changes
|