boris-behav-obs 9.3__py2.py3-none-any.whl → 9.3.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.
@@ -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
- window.resize(cfg.CODING_MAP_RESIZE_W, cfg.CODING_MAP_RESIZE_H)
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/core.py CHANGED
@@ -135,8 +135,9 @@ __version__ = version.__version__
135
135
  __version_date__ = version.__version_date__
136
136
 
137
137
  # check minimal version of python
138
- if util.versiontuple(platform.python_version()) < util.versiontuple("3.8"):
139
- msg = f"BORIS requires Python 3.8+! You are using Python v. {platform.python_version()}\n"
138
+ MIN_PYTHON_VERSION = "3.12"
139
+ if util.versiontuple(platform.python_version()) < util.versiontuple(MIN_PYTHON_VERSION):
140
+ msg = f"BORIS requires Python {MIN_PYTHON_VERSION}+! You are using Python v. {platform.python_version()}\n"
140
141
  logging.critical(msg)
141
142
  sys.exit()
142
143
 
@@ -237,19 +238,6 @@ class TableModel(QAbstractTableModel):
237
238
  return self._data[row][event_idx]
238
239
 
239
240
 
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
241
  class MainWindow(QMainWindow, Ui_MainWindow):
254
242
  """
255
243
  Main BORIS window
@@ -301,7 +289,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
301
289
  ext_data_timer_list: list = []
302
290
  projectFileName: str = ""
303
291
  mediaTotalLength = None
304
- beep_every = 0
292
+ beep_every: float = 0.0
305
293
 
306
294
  plot_colors = cfg.BEHAVIORS_PLOT_COLORS
307
295
  behav_category_colors = cfg.CATEGORY_COLORS_LIST
@@ -315,19 +303,20 @@ class MainWindow(QMainWindow, Ui_MainWindow):
315
303
  fast = 10
316
304
 
317
305
  currentStates: dict = {}
318
- subject_name_index = {}
306
+ subject_name_index: dict = {}
319
307
  flag_slow = False
320
308
  play_rate: float = 1
321
309
  play_rate_step: float = 0.1
322
310
  currentSubject: str = "" # contains the current subject of observation
323
- coding_map_window_geometry = 0
324
311
 
325
312
  # FFmpeg
326
- memx, memy, mem_player = -1, -1, -1
313
+ memx = -1
314
+ memy = -1
315
+ mem_player = -1
327
316
 
328
317
  # path for ffmpeg/ffmpeg.exe program
329
- ffmpeg_bin = ""
330
- ffmpeg_cache_dir = ""
318
+ ffmpeg_bin: str = ""
319
+ ffmpeg_cache_dir: str = ""
331
320
 
332
321
  # dictionary for FPS storing
333
322
  fps = 0
@@ -390,9 +379,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
390
379
  super(MainWindow, self).__init__(parent)
391
380
  self.setupUi(self)
392
381
 
393
- # disable trigger with RETURN or SPACE keys
394
- """filter_obj = ButtonEventFilter()
395
- self.pb_live_obs.installEventFilter(filter_obj)"""
396
382
  self.pb_live_obs.setFocusPolicy(Qt.NoFocus)
397
383
 
398
384
  self.ffmpeg_bin = ffmpeg_bin
@@ -418,25 +404,18 @@ class MainWindow(QMainWindow, Ui_MainWindow):
418
404
  self.tb_export.setMenu(self.menu)
419
405
  """
420
406
 
421
- gui_utilities.set_icons(self, theme_mode=self.theme_mode())
407
+ gui_utilities.set_icons(self, theme_mode=gui_utilities.theme_mode())
422
408
 
423
409
  self.setWindowTitle(f"{cfg.programName} ({__version__})")
424
410
 
425
- self.w_obs_info.setVisible(False)
426
-
427
411
  self.lbLogoBoris.setPixmap(QPixmap(":/logo"))
428
-
429
412
  self.lbLogoBoris.setScaledContents(False)
430
413
  self.lbLogoBoris.setAlignment(Qt.AlignCenter)
431
414
 
432
- # self.lbLogoUnito.setPixmap(QPixmap(":/dbios_unito"))
433
- # self.lbLogoUnito.setScaledContents(False)
434
- # self.lbLogoUnito.setAlignment(Qt.AlignCenter)
435
-
436
415
  self.toolBar.setEnabled(True)
437
416
 
438
417
  # start with dock widget invisible
439
- for w in [self.dwEvents, self.dwEthogram, self.dwSubjects]:
418
+ for w in (self.w_obs_info, self.dwEvents, self.dwEthogram, self.dwSubjects):
440
419
  w.setVisible(False)
441
420
  w.keyPressEvent = self.keyPressEvent
442
421
 
@@ -475,15 +454,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
475
454
  self.lbTimeOffset.setMinimumWidth(160)
476
455
  self.statusbar.addPermanentWidget(self.lbTimeOffset)
477
456
 
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
457
  # set painter for tv_events to highlight current row
488
458
  delegate = self.CustomItemDelegate()
489
459
  self.tv_events.setItemDelegate(delegate)
@@ -499,14 +469,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
499
469
  plugins.load_plugins(self)
500
470
  plugins.add_plugins_to_menu(self)
501
471
 
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
472
  class CustomItemDelegate(QStyledItemDelegate):
511
473
  def paint(self, painter, option, index):
512
474
  # Custom drawing logic here (overriding paint)
@@ -1506,7 +1468,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1506
1468
  # one media
1507
1469
  if self.dw_player[player].player.playlist_count == 1:
1508
1470
  if new_time < self.dw_player[player].player.duration:
1509
- self.dw_player[player].player.seek(new_time, "absolute+exact")
1471
+ new_time_float = round(float(new_time), 3)
1472
+
1473
+ self.dw_player[player].player.seek(new_time_float, "absolute+exact")
1510
1474
 
1511
1475
  if player == 0 and not self.user_move_slider:
1512
1476
  self.video_slider.setValue(
@@ -3835,7 +3799,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3835
3799
  if self.geometric_measurements_mode:
3836
3800
  geometric_measurement.redraw_measurements(self)
3837
3801
 
3838
- self.actionPlay.setIcon(QIcon(f":/play_{self.theme_mode()}"))
3802
+ self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
3839
3803
 
3840
3804
  def previous_frame(self) -> None:
3841
3805
  """
@@ -3859,7 +3823,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3859
3823
  if self.geometric_measurements_mode:
3860
3824
  geometric_measurement.redraw_measurements(self)
3861
3825
 
3862
- self.actionPlay.setIcon(QIcon(f":/play_{self.theme_mode()}"))
3826
+ self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
3863
3827
 
3864
3828
  def run_event_outside(self):
3865
3829
  """
@@ -4353,8 +4317,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4353
4317
  if not sys.platform.startswith(cfg.MACOS_CODE):
4354
4318
  if self.dw_player[0].player.time_pos is not None:
4355
4319
  for n_player in range(1, len(self.dw_player)):
4320
+ print(f"{n_player=}")
4321
+
4356
4322
  ct = self.getLaps(n_player=n_player)
4357
4323
 
4324
+ print(f"{ct=}")
4325
+
4358
4326
  # sync players 2..8 if time diff >= 1 s
4359
4327
  if (
4360
4328
  abs(ct0 - (ct + dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)])))
@@ -4463,7 +4431,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4463
4431
  for data_timer in self.ext_data_timer_list:
4464
4432
  data_timer.stop()
4465
4433
 
4466
- self.actionPlay.setIcon(QIcon(f":/play_{self.theme_mode()}"))
4434
+ self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
4467
4435
 
4468
4436
  if msg:
4469
4437
  self.lb_current_media_time.setText(msg)
@@ -5634,7 +5602,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5634
5602
  for data_timer in self.ext_data_timer_list:
5635
5603
  data_timer.start()
5636
5604
 
5637
- self.actionPlay.setIcon(QIcon(f":/pause_{self.theme_mode()}"))
5605
+ self.actionPlay.setIcon(QIcon(f":/pause_{gui_utilities.theme_mode()}"))
5638
5606
  self.actionPlay.setText("Pause")
5639
5607
 
5640
5608
  return True
@@ -5668,7 +5636,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5668
5636
  for idx in self.plot_data:
5669
5637
  self.timer_plot_data_out(self.plot_data[idx])
5670
5638
 
5671
- self.actionPlay.setIcon(QIcon(f":/play_{self.theme_mode()}"))
5639
+ self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
5672
5640
  self.actionPlay.setText("Play")
5673
5641
 
5674
5642
  def play_activated(self):
@@ -5823,8 +5791,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5823
5791
 
5824
5792
 
5825
5793
  def main():
5826
- # QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
5827
-
5828
5794
  app = QApplication(sys.argv)
5829
5795
  app.setStyle("Fusion")
5830
5796
 
@@ -5917,7 +5883,7 @@ def main():
5917
5883
  results.show()
5918
5884
 
5919
5885
  window.show()
5920
- window.raise_()
5886
+ window.raise_() # for overlapping widget (?)
5921
5887
 
5922
5888
  if observation_to_open and "error" not in pj:
5923
5889
  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
- # message.setWindowFlags(Qt.WindowStaysOnTopHint)
87
- message.exec_()
91
+ message.setWindowFlags(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'geometry restored for {widget_name} {settings.value(f"{widget_name} geometry")}')
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)
@@ -26,8 +26,9 @@ This file is part of BORIS.
26
26
  import binascii
27
27
  import io
28
28
  import json
29
- import os
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 os.path.splitext(self.fileName)[1] != ".boris_map":
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
- print(f"{window.width()=}")
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
- self.qm = QMessageBox()
1012
- self.qm.setIcon(QMessageBox.Critical)
1013
- self.qm.setText("The <b>observation id</b> is mandatory and must be unique.")
1014
- self.qm.exec_()
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
- self.qm = QMessageBox()
1020
- self.qm.setIcon(QMessageBox.Critical)
1021
- self.qm.setText("Choose an observation type.")
1022
- self.qm.exec_()
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
- self.qm = QMessageBox()
1029
- self.qm.setIcon(QMessageBox.Critical)
1030
- self.qm.setText("Check the time offset value.")
1031
- self.qm.exec_()
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
- self.qm = QMessageBox()
1055
- self.qm.setIcon(QMessageBox.Critical)
1056
- self.qm.setText("A media file must be loaded in player #1")
1057
- self.qm.exec_()
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
- self.qm = QMessageBox()
1063
- self.qm.setIcon(QMessageBox.Critical)
1064
- self.qm.setText("Some player are not used. Please reorganize your media files")
1065
- self.qm.exec_()
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
@@ -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.exec_()
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_{self.theme_mode()}"))
2015
+ self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
2016
2016
 
2017
2017
  self.display_statusbar_info(self.observationId)
2018
2018
 
@@ -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 = "dark" if QApplication.instance().palette().window().color().value() < 128 else "light"
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 = "dark" if QApplication.instance().palette().window().color().value() < 128 else "light"
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/utilities.py CHANGED
@@ -28,7 +28,6 @@ import math
28
28
  import os
29
29
  import pathlib as pl
30
30
  import re
31
- import socket
32
31
  import subprocess
33
32
  import sys
34
33
  import urllib.parse
@@ -292,27 +291,6 @@ def return_file_header_footer(file_name: str, file_row_number: int = 0, row_numb
292
291
  return header, footer
293
292
 
294
293
 
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
294
  def convertTime(time_format: str, sec: Union[float, dec]) -> Union[str, None]:
317
295
  """
318
296
  convert time in base at the current format (S or HHMMSS)
@@ -381,20 +359,6 @@ def count_media_file(media_files: dict) -> int:
381
359
  return sum([len(media_files[idx]) for idx in media_files])
382
360
 
383
361
 
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
362
  def txt2np_array(
399
363
  file_name: str, columns_str: str, substract_first_value: str, converters=None, column_converter=None
400
364
  ) -> Tuple[bool, str, np.array]:
@@ -760,19 +724,6 @@ def get_current_points_by_subject(
760
724
  return current_points
761
725
 
762
726
 
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
727
  def check_txt_file(file_name: str) -> dict:
777
728
  """
778
729
  Extract parameters of txt file (test for tsv csv)
@@ -950,7 +901,7 @@ def intfloatstr(s: str) -> int:
950
901
  return s
951
902
 
952
903
 
953
- def distance(p1, p2):
904
+ def distance(p1: tuple, p2: tuple) -> float:
954
905
  """
955
906
  euclidean distance between 2 points
956
907
  """
@@ -987,12 +938,12 @@ def oriented_angle(P1: tuple, P2: tuple, P3: tuple) -> float:
987
938
  Calculate the oriented angle between two segments.
988
939
 
989
940
  Args:
990
- P1: Coordinates of the vertex
991
- P2: Coordinates of the first point
992
- P3: Coordinates of the second point
941
+ P1 (tuple): Coordinates of the vertex
942
+ P2 (tuple): Coordinates of the first point
943
+ P3 (tuple): Coordinates of the second point
993
944
 
994
945
  Returns:
995
- The oriented angle between the two segments in degrees.
946
+ float: The oriented angle between the two segments in degrees.
996
947
  """
997
948
 
998
949
  x1, y1 = P1
@@ -1008,6 +959,36 @@ def oriented_angle(P1: tuple, P2: tuple, P3: tuple) -> float:
1008
959
  return oriented_angle
1009
960
 
1010
961
 
962
+ def oriented_angle_trigo(B: Tuple[float, float], A: Tuple[float, float], C: Tuple[float, float]) -> float:
963
+ """
964
+ Calculates the oriented angle between vectors BA and BC, in degrees.
965
+ The angle is positive in the counter-clockwise (trigonometric) direction.
966
+
967
+ Parameters:
968
+ B: The pivot point (the origin of the vectors BA and BC).
969
+ A, C: Points that define the vectors.
970
+
971
+ Returns:
972
+ Angle in degrees, between 0 and 360.
973
+ """
974
+ # Vectors BA and BC
975
+ v1 = (A[0] - B[0], A[1] - B[1])
976
+ v2 = (C[0] - B[0], C[1] - B[1])
977
+
978
+ # Dot product and 2D cross product (determinant)
979
+ dot = v1[0] * v2[0] + v1[1] * v2[1]
980
+ det = v1[0] * v2[1] - v1[1] * v2[0]
981
+
982
+ # Signed angle in radians, then converted to degrees
983
+ angle_rad = math.atan2(det, dot)
984
+ angle_deg = math.degrees(angle_rad)
985
+
986
+ if angle_deg < 0:
987
+ angle_deg += 360
988
+
989
+ return angle_deg
990
+
991
+
1011
992
  def mem_info():
1012
993
  """
1013
994
  get info about total mem, used mem and available mem using:
boris/version.py CHANGED
@@ -20,5 +20,5 @@ This file is part of BORIS.
20
20
 
21
21
  """
22
22
 
23
- __version__ = "9.3"
24
- __version_date__ = "2025-04-07"
23
+ __version__ = "9.3.2"
24
+ __version_date__ = "2025-04-14"
@@ -0,0 +1,132 @@
1
+ Metadata-Version: 2.4
2
+ Name: boris-behav-obs
3
+ Version: 9.3.2
4
+ Summary: BORIS - Behavioral Observation Research Interactive Software
5
+ Author-email: Olivier Friard <olivier.friard@unito.it>
6
+ License-Expression: GPL-3.0-only
7
+ Project-URL: Homepage, http://www.boris.unito.it
8
+ Project-URL: Documentation, https://boris.readthedocs.io/en/latest/
9
+ Project-URL: Change_log, https://github.com/olivierfriard/BORIS/wiki/BORIS-change-log-v.8
10
+ Project-URL: Source_code, https://github.com/olivierfriard/BORIS
11
+ Project-URL: Issues, https://github.com/olivierfriard/BORIS/issues
12
+ Classifier: Topic :: Scientific/Engineering
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Intended Audience :: Education
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Topic :: Scientific/Engineering
18
+ Requires-Python: >=3.12
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE.TXT
21
+ Requires-Dist: exifread>=3.0.0
22
+ Requires-Dist: numpy>=1.26.4
23
+ Requires-Dist: matplotlib>=3.3.3
24
+ Requires-Dist: pandas>=2.2.2
25
+ Requires-Dist: tablib[cli,html,ods,pandas,xls,xlsx]>=3
26
+ Requires-Dist: pyreadr
27
+ Requires-Dist: pyside6==6.8.0.2
28
+ Requires-Dist: hachoir>=3.3.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: ruff; extra == "dev"
31
+ Requires-Dist: pytest; extra == "dev"
32
+ Requires-Dist: pytest-cov; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ BORIS (Behavioral Observation Research Interactive Software)
36
+ ===============================================================
37
+
38
+
39
+ ![BORIS logo](https://github.com/olivierfriard/BORIS/blob/master/boris/icons/logo_boris.png?raw=true)
40
+
41
+ BORIS is an easy-to-use event logging software for video/audio coding or live observations.
42
+
43
+ BORIS is a free and open-source software available for GNU/Linux and Windows.
44
+ You can not longer run BORIS natively on MacOS (since v.8). Some alternatives to run the last version of BORIS are available, see [BORIS on MacOS](https://www.boris.unito.it/download_mac).
45
+
46
+ It provides also some analysis tools like time budget and some plotting functions.
47
+
48
+ The BORIS paper has more than [![BORIS citations counter](http://penelope.unito.it/friard/boris_scopus_citations.png) citations](https://www.boris.unito.it/citations) in peer-reviewed scientific publications.
49
+
50
+
51
+
52
+
53
+ See the official [BORIS web site](https://www.boris.unito.it).
54
+
55
+ [![Python web site](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org)
56
+ ![Python versions](https://img.shields.io/pypi/pyversions/boris-behav-obs)
57
+ ![BORIS license](https://img.shields.io/pypi/l/boris-behav-obs)
58
+ [![Number of downloads](https://static.pepy.tech/personalized-badge/boris-behav-obs?period=total&units=international_system&left_color=black&right_color=orange&left_text=Downloads)](https://pepy.tech/project/boris-behav-obs)
59
+ ![commit-activity](https://img.shields.io/github/commit-activity/m/olivierfriard/BORIS)
60
+ [![PyPI version](https://img.shields.io/pypi/v/boris-behav-obs.svg)](https://pypi.org/project/boris-behav-obs/)
61
+ ![BORIS scopus citations badge](http://penelope.unito.it/friard/boris_scopus_citations.svg)
62
+
63
+
64
+
65
+
66
+ # Documentation
67
+
68
+
69
+
70
+ The [user guide](https://www.boris.unito.it/user_guide/) provides a good starting point for learning how to use BORIS.
71
+
72
+ Some [video tutorials](https://www.boris.unito.it/video_tutorials/) are available.
73
+
74
+
75
+
76
+
77
+
78
+ # Bug reports and feature requests
79
+
80
+
81
+ To search for bugs, report them or request a feature, please use the github tracker:
82
+ https://github.com/olivierfriard/BORIS/issues
83
+
84
+
85
+
86
+
87
+
88
+ # Citing BORIS
89
+
90
+
91
+ Please acknowledge and cite the use of this software and its authors when
92
+ results are used in publications or published elsewhere. You can use the
93
+ following BibTex entry
94
+
95
+ ```
96
+ @article {MEE3:MEE312584,
97
+ author = {Friard, Olivier and Gamba, Marco},
98
+ title = {BORIS: a free, versatile open-source event-logging software for video/audio coding and live observations},
99
+ journal = {Methods in Ecology and Evolution},
100
+ issn = {2041-210X},
101
+ url = {http://dx.doi.org/10.1111/2041-210X.12584},
102
+ doi = {10.1111/2041-210X.12584},
103
+ pages = {1324--1330},
104
+ year = {2016},
105
+ }
106
+ ```
107
+
108
+ You can also send us a nice postcard! See the [user testimonials](https://www.boris.unito.it/postcards).
109
+
110
+
111
+
112
+
113
+
114
+
115
+
116
+
117
+ # Licence
118
+
119
+
120
+ This program is distributed in the hope that it will be useful,
121
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
122
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
123
+ GNU General Public License for more details.
124
+
125
+
126
+ Distributed with a [GPL v.3 license](LICENSE.TXT).
127
+
128
+ Copyright (C) 2012-2024 Olivier Friard
129
+
130
+
131
+
132
+
@@ -1,11 +1,10 @@
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=gzBmfMmG-BsvREdkmiCtu73OuvnHkWb_aVLsMKxTZzU,39071
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
@@ -17,12 +16,12 @@ 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=f3sEuCVf9zYzyuPuLviYI9IddrZgNWhz5EqueQmNJtc,234758
19
+ boris/core.py,sha256=qO8ZQoPxr_jjdy7-oihjcPcNvWgHvs-IWd7JWmxy_M0,233608
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=IAX5_CtTfEUGm5lhzxajjx0oVTs8HNEAw2twTW8AmkE,32859
24
+ boris/dialog.py,sha256=0mWbOQu9QvkCr6AIa1Tue5EYaTxbHo7rm-tnGAqQ3nk,33047
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,7 +33,7 @@ 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=5vjIWbUOHFbqKSti-kT0GoteBBEQ5fUYdNGdMxcg_0A,4607
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
@@ -42,25 +41,25 @@ boris/latency.py,sha256=48z9L_A582-wKCfD0M3h0uyYkeL2ezjlQAS_GzeoOe0,9739
42
41
  boris/measurement_widget.py,sha256=lZV62KtK6TjdoNbKxj3uyNAuL5dfnQnn7mYwzMo-dOM,4480
43
42
  boris/media_file.py,sha256=QzUC0mT905SzlONvcXUJB2OCxhj8kJ0h0W6PN1ssSIY,4722
44
43
  boris/menu_options.py,sha256=UEB3GxRh6YKNCg67qbhOVhJW1ZOznuPe15bADc_CNTI,7062
45
- boris/modifier_coding_map_creator.py,sha256=AHGi1s-T0EB1p9qazQerQH1IlsrF8ND9v0OvLC-fWW4,33643
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=oop08nflDLZAgDbIB8GOiVdTgLhppJ_ODH0Z24cyqvE,57176
51
- boris/observation_operations.py,sha256=j7TSGzWSRmAdmD7vdrfvEBFtCfM0HXbo9VeIHNew2PY,105748
49
+ boris/observation.py,sha256=d-7q-RkMHuLDV87nF4yahvDFPYhlXp6GmE80vckn5zU,57073
50
+ boris/observation_operations.py,sha256=hYtwMk3dx9O1UURbxPUewlyL111jDThO5_b3CnNmI1o,105756
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=aDC6a7JEEY64TWMDU_jBBaavN-Z-fd9JQo9BnnNI4_M,6303
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=FSeZdbGFPGfi5TDM3RQG6mEVwsp1cGKhJIZs-H0BMy4,9901
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
65
  boris/project.py,sha256=hAeAb5pD0u_l0bezU9ePvbTOYQKfxrFGvYB2NAqSDHg,84377
@@ -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=H69vrYGkQuQJVE_ywcYC6DNYcS8VTC-GoClmVEUXacA,52748
82
- boris/version.py,sha256=IUFwHM5I3N9z8S643mnqHfhCv_cXd1ezR8vcI-qnSO0,785
80
+ boris/utilities.py,sha256=SoVTDiFkidh-POwYcu_TVfJsFeXHYZV--s1tdxKH1VU,52654
81
+ boris/version.py,sha256=1P9L6ZnUsH7kDLTVpQPQLqCJMN5PAWXFyIRRUTkgDkA,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.dist-info/licenses/LICENSE.TXT,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
100
- boris_behav_obs-9.3.dist-info/METADATA,sha256=cHGsRQCF1g8SC8mtf9DJ6a_WlGkh5wekzhkKCHJVPZs,1340
101
- boris_behav_obs-9.3.dist-info/WHEEL,sha256=MAQBAzGbXNI3bUmkDsiV_duv8i-gcdnLzw7cfUFwqhU,109
102
- boris_behav_obs-9.3.dist-info/entry_points.txt,sha256=k__8XvFi4vaA4QFvQehCZjYkKmZH34HSAJI2iYCWrMs,52
103
- boris_behav_obs-9.3.dist-info/top_level.txt,sha256=fJSgm62S7WesiwTorGbOO4nNN0yzgZ3klgfGi3Px4qI,6
104
- boris_behav_obs-9.3.dist-info/RECORD,,
99
+ boris_behav_obs-9.3.2.dist-info/licenses/LICENSE.TXT,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
100
+ boris_behav_obs-9.3.2.dist-info/METADATA,sha256=ZUt0hNpFSkVJweQn-PndnIpkRpCN--vQaJOqYwfEZMg,4518
101
+ boris_behav_obs-9.3.2.dist-info/WHEEL,sha256=MAQBAzGbXNI3bUmkDsiV_duv8i-gcdnLzw7cfUFwqhU,109
102
+ boris_behav_obs-9.3.2.dist-info/entry_points.txt,sha256=k__8XvFi4vaA4QFvQehCZjYkKmZH34HSAJI2iYCWrMs,52
103
+ boris_behav_obs-9.3.2.dist-info/top_level.txt,sha256=fJSgm62S7WesiwTorGbOO4nNN0yzgZ3klgfGi3Px4qI,6
104
+ boris_behav_obs-9.3.2.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
- '''
@@ -1,33 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: boris-behav-obs
3
- Version: 9.3
4
- Summary: BORIS - Behavioral Observation Research Interactive Software
5
- Author-email: Olivier Friard <olivier.friard@unito.it>
6
- License-Expression: GPL-3.0-only
7
- Project-URL: Homepage, http://www.boris.unito.it
8
- Project-URL: Documentation, https://boris.readthedocs.io/en/latest/
9
- Project-URL: Change_log, https://github.com/olivierfriard/BORIS/wiki/BORIS-change-log-v.8
10
- Project-URL: Source_code, https://github.com/olivierfriard/BORIS
11
- Project-URL: Issues, https://github.com/olivierfriard/BORIS/issues
12
- Classifier: Topic :: Scientific/Engineering
13
- Classifier: Intended Audience :: Science/Research
14
- Classifier: Intended Audience :: Education
15
- Classifier: Programming Language :: Python :: 3.12
16
- Classifier: Operating System :: OS Independent
17
- Classifier: Topic :: Scientific/Engineering
18
- Requires-Python: >=3.12
19
- Description-Content-Type: text/x-rst
20
- License-File: LICENSE.TXT
21
- Requires-Dist: exifread>=3.0.0
22
- Requires-Dist: numpy>=1.26.4
23
- Requires-Dist: matplotlib>=3.3.3
24
- Requires-Dist: pandas>=2.2.2
25
- Requires-Dist: tablib[cli,html,ods,pandas,xls,xlsx]>=3
26
- Requires-Dist: pyreadr
27
- Requires-Dist: pyside6==6.8.0.2
28
- Requires-Dist: hachoir>=3.3.0
29
- Provides-Extra: dev
30
- Requires-Dist: ruff; extra == "dev"
31
- Requires-Dist: pytest; extra == "dev"
32
- Requires-Dist: pytest-cov; extra == "dev"
33
- Dynamic: license-file