boris-behav-obs 8.27.10__py2.py3-none-any.whl → 9.0.2__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. boris/about.py +7 -5
  2. boris/add_modifier.py +35 -35
  3. boris/add_modifier_ui.py +229 -129
  4. boris/advanced_event_filtering.py +3 -3
  5. boris/analysis_plugins/__init__.py +0 -0
  6. boris/analysis_plugins/number_of_occurences.py +60 -0
  7. boris/analysis_plugins/number_of_occurences_by_independent_variable.py +72 -0
  8. boris/analysis_plugins/time_budget.py +95 -0
  9. boris/behav_coding_map_creator.py +103 -108
  10. boris/behavior_binary_table.py +1 -1
  11. boris/behaviors_coding_map.py +8 -8
  12. boris/coding_pad.py +6 -6
  13. boris/config.py +6 -0
  14. boris/config_file.py +1 -1
  15. boris/connections.py +4 -2
  16. boris/converters.py +2 -3
  17. boris/converters_ui.py +187 -110
  18. boris/cooccurence.py +2 -2
  19. boris/core.py +341 -94
  20. boris/core_qrc.py +16088 -13246
  21. boris/core_ui.py +922 -812
  22. boris/dialog.py +14 -13
  23. boris/duration_widget.py +5 -5
  24. boris/edit_event.py +1 -1
  25. boris/edit_event_ui.py +162 -88
  26. boris/event_operations.py +4 -25
  27. boris/events_cursor.py +17 -9
  28. boris/events_snapshots.py +5 -5
  29. boris/exclusion_matrix.py +1 -1
  30. boris/export_events.py +38 -28
  31. boris/export_observation.py +1 -1
  32. boris/external_processes.py +3 -5
  33. boris/geometric_measurement.py +49 -26
  34. boris/gui_utilities.py +31 -30
  35. boris/import_observations.py +2 -4
  36. boris/irr.py +1 -1
  37. boris/latency.py +1 -1
  38. boris/map_creator.py +77 -89
  39. boris/measurement_widget.py +4 -4
  40. boris/media_file.py +2 -4
  41. boris/menu_options.py +1 -3
  42. boris/modifiers_coding_map.py +4 -4
  43. boris/mpv2.py +0 -2
  44. boris/observation.py +124 -29
  45. boris/observation_operations.py +18 -40
  46. boris/observation_ui.py +566 -374
  47. boris/observations_list.py +6 -6
  48. boris/param_panel.py +2 -2
  49. boris/param_panel_ui.py +246 -141
  50. boris/player_dock_widget.py +16 -21
  51. boris/plot_data_module.py +6 -6
  52. boris/plot_events_rt.py +7 -8
  53. boris/plot_spectrogram_rt.py +7 -8
  54. boris/plot_waveform_rt.py +6 -7
  55. boris/plugins.py +79 -0
  56. boris/preferences.py +127 -17
  57. boris/preferences_ui.py +464 -240
  58. boris/project.py +69 -72
  59. boris/project_functions.py +233 -31
  60. boris/project_import_export.py +59 -67
  61. boris/project_ui.py +672 -440
  62. boris/qrc_boris.py +6 -3
  63. boris/qrc_boris5.py +6 -3
  64. boris/select_modifiers.py +2 -2
  65. boris/select_observations.py +2 -2
  66. boris/select_subj_behav.py +3 -3
  67. boris/state_events.py +1 -1
  68. boris/subjects_pad.py +5 -5
  69. boris/synthetic_time_budget.py +2 -2
  70. boris/time_budget_functions.py +15 -0
  71. boris/time_budget_widget.py +4 -4
  72. boris/transitions.py +34 -25
  73. boris/utilities.py +96 -3
  74. boris/version.py +2 -2
  75. boris/video_equalizer.py +4 -4
  76. boris/video_equalizer_ui.py +199 -130
  77. boris/video_operations.py +1 -1
  78. boris/view_df.py +106 -0
  79. boris/view_df_ui.py +75 -0
  80. boris/write_event.py +9 -1
  81. {boris_behav_obs-8.27.10.dist-info → boris_behav_obs-9.0.2.dist-info}/METADATA +5 -5
  82. boris_behav_obs-9.0.2.dist-info/RECORD +103 -0
  83. {boris_behav_obs-8.27.10.dist-info → boris_behav_obs-9.0.2.dist-info}/WHEEL +1 -1
  84. boris/qdarkstyle/__init__.py +0 -479
  85. boris/qdarkstyle/__main__.py +0 -66
  86. boris/qdarkstyle/colorsystem.py +0 -38
  87. boris/qdarkstyle/dark/__init__.py +0 -1
  88. boris/qdarkstyle/dark/darkstyle_rc.py +0 -11379
  89. boris/qdarkstyle/dark/palette.py +0 -38
  90. boris/qdarkstyle/example/__init__.py +0 -4
  91. boris/qdarkstyle/example/__main__.py +0 -386
  92. boris/qdarkstyle/example/ui/__init__.py +0 -4
  93. boris/qdarkstyle/light/__init__.py +0 -1
  94. boris/qdarkstyle/light/lightstyle_rc.py +0 -11305
  95. boris/qdarkstyle/light/palette.py +0 -37
  96. boris/qdarkstyle/palette.py +0 -102
  97. boris/qdarkstyle/utils/__init__.py +0 -73
  98. boris/qdarkstyle/utils/__main__.py +0 -96
  99. boris/qdarkstyle/utils/images.py +0 -449
  100. boris/qdarkstyle/utils/scss.py +0 -318
  101. boris/vlc_local.py +0 -83
  102. boris_behav_obs-8.27.10.dist-info/RECORD +0 -114
  103. {boris_behav_obs-8.27.10.dist-info → boris_behav_obs-9.0.2.dist-info}/LICENSE.TXT +0 -0
  104. {boris_behav_obs-8.27.10.dist-info → boris_behav_obs-9.0.2.dist-info}/entry_points.txt +0 -0
  105. {boris_behav_obs-8.27.10.dist-info → boris_behav_obs-9.0.2.dist-info}/top_level.txt +0 -0
boris/qrc_boris.py CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  # Resource object code
4
4
  #
5
- # Created by: The Resource Compiler for PyQt5 (Qt v5.13.0)
5
+ # Created by: The Resource Compiler for PySide6 (Qt v5.13.0)
6
6
  #
7
7
  # WARNING! All changes made in this file will be lost!
8
8
 
9
- from PyQt5 import QtCore
9
+ from PySide6 import QtCore
10
10
 
11
11
  qt_resource_data = b"\
12
12
  \x00\x00\x04\xba\
@@ -10369,7 +10369,7 @@ qt_resource_struct_v2 = b"\
10369
10369
  \x00\x00\x01\x70\xe3\xd1\xd7\xb0\
10370
10370
  "
10371
10371
 
10372
- qt_version = [int(v) for v in QtCore.qVersion().split('.')]
10372
+ qt_version = [int(v) for v in QtCore.qVersion().split(".")]
10373
10373
  if qt_version < [5, 8, 0]:
10374
10374
  rcc_version = 1
10375
10375
  qt_resource_struct = qt_resource_struct_v1
@@ -10377,10 +10377,13 @@ else:
10377
10377
  rcc_version = 2
10378
10378
  qt_resource_struct = qt_resource_struct_v2
10379
10379
 
10380
+
10380
10381
  def qInitResources():
10381
10382
  QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
10382
10383
 
10384
+
10383
10385
  def qCleanupResources():
10384
10386
  QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
10385
10387
 
10388
+
10386
10389
  qInitResources()
boris/qrc_boris5.py CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  # Resource object code
4
4
  #
5
- # Created by: The Resource Compiler for PyQt5 (Qt v5.11.2)
5
+ # Created by: The Resource Compiler for PySide6 (Qt v5.11.2)
6
6
  #
7
7
  # WARNING! All changes made in this file will be lost!
8
8
 
9
- from PyQt5 import QtCore
9
+ from PySide6 import QtCore
10
10
 
11
11
  qt_resource_data = b"\
12
12
  \x00\x00\x04\xa6\
@@ -2559,7 +2559,7 @@ qt_resource_struct_v2 = b"\
2559
2559
  \x00\x00\x01\x68\x32\x38\xb4\xfd\
2560
2560
  "
2561
2561
 
2562
- qt_version = [int(v) for v in QtCore.qVersion().split('.')]
2562
+ qt_version = [int(v) for v in QtCore.qVersion().split(".")]
2563
2563
  if qt_version < [5, 8, 0]:
2564
2564
  rcc_version = 1
2565
2565
  qt_resource_struct = qt_resource_struct_v1
@@ -2567,10 +2567,13 @@ else:
2567
2567
  rcc_version = 2
2568
2568
  qt_resource_struct = qt_resource_struct_v2
2569
2569
 
2570
+
2570
2571
  def qInitResources():
2571
2572
  QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
2572
2573
 
2574
+
2573
2575
  def qCleanupResources():
2574
2576
  QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
2575
2577
 
2578
+
2576
2579
  qInitResources()
boris/select_modifiers.py CHANGED
@@ -22,8 +22,8 @@ This file is part of BORIS.
22
22
 
23
23
  import re
24
24
 
25
- from PyQt5.QtCore import Qt, QEvent
26
- from PyQt5.QtWidgets import (
25
+ from PySide6.QtCore import Qt, QEvent
26
+ from PySide6.QtWidgets import (
27
27
  QDialog,
28
28
  QVBoxLayout,
29
29
  QLabel,
@@ -22,8 +22,8 @@ Copyright 2012-2024 Olivier Friard
22
22
  """
23
23
 
24
24
  from typing import Tuple
25
- from PyQt5.QtCore import Qt
26
- from PyQt5.QtWidgets import QAbstractItemView
25
+ from PySide6.QtCore import Qt
26
+ from PySide6.QtWidgets import QAbstractItemView
27
27
 
28
28
  from . import config as cfg
29
29
  from . import gui_utilities, observations_list, project_functions
@@ -24,9 +24,9 @@ import logging
24
24
  from decimal import Decimal as dec
25
25
  from typing import Optional
26
26
 
27
- from PyQt5.QtCore import Qt
28
- from PyQt5.QtGui import QFont
29
- from PyQt5.QtWidgets import QCheckBox, QListWidgetItem, QMessageBox
27
+ from PySide6.QtCore import Qt
28
+ from PySide6.QtGui import QFont
29
+ from PySide6.QtWidgets import QCheckBox, QListWidgetItem, QMessageBox
30
30
 
31
31
  from . import config as cfg
32
32
  from . import gui_utilities, param_panel, project_functions
boris/state_events.py CHANGED
@@ -25,7 +25,7 @@ Module containing functions for state events
25
25
  import time
26
26
  from decimal import Decimal as dec
27
27
 
28
- from PyQt5.QtWidgets import QMessageBox, QAbstractItemView
28
+ from PySide6.QtWidgets import QMessageBox, QAbstractItemView
29
29
 
30
30
  from . import config as cfg
31
31
  from . import dialog, project_functions, select_observations
boris/subjects_pad.py CHANGED
@@ -19,17 +19,17 @@ Copyright 2012-2024 Olivier Friard
19
19
  MA 02110-1301, USA.
20
20
  """
21
21
 
22
- from PyQt5.QtCore import pyqtSignal, QRect, QEvent, Qt
23
- from PyQt5.QtWidgets import QGridLayout, QPushButton, QHBoxLayout, QWidget
22
+ from PySide6.QtCore import Signal, QRect, QEvent, Qt
23
+ from PySide6.QtWidgets import QGridLayout, QPushButton, QHBoxLayout, QWidget
24
24
 
25
25
  from . import config as cfg
26
26
  from . import utilities as util
27
27
 
28
28
 
29
29
  class SubjectsPad(QWidget):
30
- clickSignal = pyqtSignal(str)
31
- sendEventSignal = pyqtSignal(QEvent)
32
- close_signal = pyqtSignal(QRect)
30
+ clickSignal = Signal(str)
31
+ sendEventSignal = Signal(QEvent)
32
+ close_signal = Signal(QRect)
33
33
 
34
34
  def __init__(self, pj, filtered_subjects, parent=None):
35
35
  super(SubjectsPad, self).__init__(parent)
@@ -23,8 +23,8 @@ This file is part of BORIS.
23
23
  import logging
24
24
  import pathlib as pl
25
25
 
26
- from PyQt5.QtWidgets import QFileDialog, QMessageBox
27
- from PyQt5.QtGui import QFont, QTextOption, QTextCursor
26
+ from PySide6.QtWidgets import QFileDialog, QMessageBox
27
+ from PySide6.QtGui import QFont, QTextOption, QTextCursor
28
28
 
29
29
  from . import config as cfg
30
30
  from . import (
@@ -671,10 +671,14 @@ def time_budget_analysis(
671
671
  categories: dict = {}
672
672
  out: list = []
673
673
  for subject in parameters[cfg.SELECTED_SUBJECTS]:
674
+ logging.debug(f"{subject=}")
675
+
674
676
  out_cat: list = []
675
677
  categories[subject] = {}
676
678
 
677
679
  for behavior in parameters[cfg.SELECTED_BEHAVIORS]:
680
+ logging.debug(f"{behavior=}")
681
+
678
682
  if parameters[cfg.INCLUDE_MODIFIERS]: # with modifiers
679
683
  if parameters[cfg.EXCLUDE_NON_CODED_MODIFIERS]:
680
684
  # get coded modifiers
@@ -1012,6 +1016,15 @@ def time_budget_analysis(
1012
1016
  continue
1013
1017
 
1014
1018
  if len(rows) % 2: # unpaired events
1019
+ """
1020
+ print()
1021
+ print(f"{subject=}")
1022
+ print(f"{behavior=}")
1023
+ print()
1024
+ for row in rows:
1025
+ print(f"{row['observation']=} {row['occurence']=}")
1026
+ print()
1027
+ """
1015
1028
  out_cat.append(
1016
1029
  {
1017
1030
  "subject": subject,
@@ -1108,6 +1121,8 @@ def time_budget_analysis(
1108
1121
  "-",
1109
1122
  cfg.NA,
1110
1123
  ):
1124
+ # print(f"{categories[subject][category]["duration"]=}")
1125
+ # print(f"{behav["duration"]=}")
1111
1126
  categories[subject][category]["duration"] += behav["duration"]
1112
1127
  else:
1113
1128
  categories[subject][category]["duration"] = cfg.NA
@@ -37,8 +37,8 @@ except ModuleNotFoundError:
37
37
 
38
38
 
39
39
  import tablib
40
- from PyQt5.QtCore import Qt
41
- from PyQt5.QtWidgets import (
40
+ from PySide6.QtCore import Qt
41
+ from PySide6.QtWidgets import (
42
42
  QFileDialog,
43
43
  QHBoxLayout,
44
44
  QInputDialog,
@@ -631,8 +631,8 @@ def time_budget(self, mode: str, mode2: str = "list"):
631
631
  else:
632
632
  self.tb.excluded_behaviors_list.setVisible(False)
633
633
 
634
- self.statusbar.showMessage(f"Time budget generated in {round(time.time() - t0, 3)} s")
635
- logging.debug("Time budget generated", 5000)
634
+ self.statusbar.showMessage(f"Time budget generated in {round(time.time() - t0, 3)} s", 5000)
635
+ logging.debug("Time budget generated")
636
636
 
637
637
  if mode == "by_behavior":
638
638
  tb_fields = [
boris/transitions.py CHANGED
@@ -24,8 +24,9 @@ import logging
24
24
  import os
25
25
  import subprocess
26
26
  import tempfile
27
+ from pathlib import Path
27
28
 
28
- from PyQt5.QtWidgets import QFileDialog, QMessageBox
29
+ from PySide6.QtWidgets import QFileDialog, QMessageBox
29
30
 
30
31
  from . import config as cfg
31
32
  from . import dialog, export_observation, select_subj_behav
@@ -170,6 +171,8 @@ def transitions_matrix(self, mode):
170
171
  * number
171
172
  * frequencies_after_behaviors
172
173
  """
174
+ logging.debug("flag transitions_matrix function")
175
+
173
176
  # ask user observations to analyze
174
177
  _, selected_observations = select_observations.select_observations2(
175
178
  self, cfg.MULTIPLE, windows_title="Select observations for transitions matrix"
@@ -194,20 +197,20 @@ def transitions_matrix(self, mode):
194
197
 
195
198
  flagMulti = False
196
199
  if len(parameters[cfg.SELECTED_SUBJECTS]) == 1:
197
- fn = QFileDialog().getSaveFileName(
200
+ file_name, _ = QFileDialog().getSaveFileName(
198
201
  None,
199
202
  "Create matrix of transitions " + mode,
200
203
  "",
201
204
  "Transitions matrix files (*.txt *.tsv);;All files (*)",
202
205
  )
203
- fileName = fn[0] if type(fn) is tuple else fn # PyQt4/5
204
-
206
+ if not file_name:
207
+ return
205
208
  else:
206
- exportDir = QFileDialog(self).getExistingDirectory(
209
+ exportDir = QFileDialog.getExistingDirectory(
207
210
  self,
208
211
  "Choose a directory to save the transitions matrices",
209
- os.path.expanduser("~"),
210
- options=QFileDialog(self).ShowDirsOnly,
212
+ str(Path.home()),
213
+ options=QFileDialog.ShowDirsOnly,
211
214
  )
212
215
  if not exportDir:
213
216
  return
@@ -256,11 +259,11 @@ def transitions_matrix(self, mode):
256
259
  QMessageBox.critical(self, cfg.programName, f"The file {nf} can not be saved")
257
260
  else:
258
261
  try:
259
- with open(fileName, "w") as outfile:
262
+ with open(file_name, "w") as outfile:
260
263
  outfile.write(observed_matrix)
261
264
 
262
265
  except Exception:
263
- QMessageBox.critical(self, cfg.programName, f"The file {fileName} can not be saved")
266
+ QMessageBox.critical(self, cfg.programName, f"The file {file_name} can not be saved")
264
267
 
265
268
 
266
269
  def transitions_dot_script():
@@ -268,18 +271,17 @@ def transitions_dot_script():
268
271
  create dot script (graphviz language) from transitions frequencies matrix
269
272
  """
270
273
 
271
- fn = QFileDialog().getOpenFileNames(
274
+ file_names, _ = QFileDialog().getOpenFileNames(
272
275
  None,
273
276
  "Select one or more transitions matrix files",
274
277
  "",
275
278
  "Transitions matrix files (*.txt *.tsv);;All files (*)",
276
279
  )
277
- fileNames = fn[0] if type(fn) is tuple else fn
278
280
 
279
281
  out = ""
280
282
 
281
- for fileName in fileNames:
282
- with open(fileName, "r") as infile:
283
+ for file_name in file_names:
284
+ with open(file_name, "r") as infile:
283
285
  result, gv = create_transitions_gv_from_matrix(infile.read(), cutoff_all=0, cutoff_behavior=0, edge_label="percent_node")
284
286
  if result:
285
287
  QMessageBox.critical(
@@ -289,16 +291,24 @@ def transitions_dot_script():
289
291
  )
290
292
  return
291
293
 
292
- with open(fileName + ".gv", "w") as f:
293
- f.write(gv)
294
+ try:
295
+ with open(file_name + ".gv", "w") as file_out:
296
+ file_out.write(gv)
297
+ except Exception:
298
+ QMessageBox.critical(
299
+ None,
300
+ cfg.programName,
301
+ ("Error saving the file"),
302
+ )
303
+ return
294
304
 
295
- out += f"<b>{fileName}.gv</b> created<br>"
305
+ out += f"<b>{file_name}.gv</b> created<br>"
296
306
 
297
307
  if out:
298
308
  QMessageBox.information(
299
309
  None,
300
310
  cfg.programName,
301
- (f"{out}<br><br>The DOT scripts can be used with Graphviz or WebGraphviz " "to generate diagram"),
311
+ (f"{out}<br><br>The DOT scripts can be used with the Graphviz package or WebGraphviz to generate diagram"),
302
312
  )
303
313
 
304
314
 
@@ -322,17 +332,16 @@ def transitions_flow_diagram():
322
332
  )
323
333
  return
324
334
 
325
- fn = QFileDialog().getOpenFileNames(
335
+ file_names, _ = QFileDialog().getOpenFileNames(
326
336
  None,
327
337
  "Select one or more transitions matrix files",
328
338
  "",
329
339
  "Transitions matrix files (*.txt *.tsv);;All files (*)",
330
340
  )
331
- fileNames = fn[0] if type(fn) is tuple else fn
332
341
 
333
342
  out = ""
334
- for fileName in fileNames:
335
- with open(fileName, "r") as infile:
343
+ for file_name in file_names:
344
+ with open(file_name, "r") as infile:
336
345
  result, gv = create_transitions_gv_from_matrix(infile.read(), cutoff_all=0, cutoff_behavior=0, edge_label="percent_node")
337
346
  if result:
338
347
  QMessageBox.critical(
@@ -342,15 +351,15 @@ def transitions_flow_diagram():
342
351
  )
343
352
  return
344
353
 
345
- with open(tempfile.gettempdir() + os.sep + os.path.basename(fileName) + ".tmp.gv", "w") as f:
354
+ with open(tempfile.gettempdir() + os.sep + os.path.basename(file_name) + ".tmp.gv", "w") as f:
346
355
  f.write(gv)
347
356
  result = subprocess.getoutput(
348
- (f'dot -Tpng -o "{fileName}.png" ' f'"{tempfile.gettempdir() + os.sep + os.path.basename(fileName)}.tmp.gv"')
357
+ (f'dot -Tpng -o "{file_name}.png" ' f'"{tempfile.gettempdir() + os.sep + os.path.basename(file_name)}.tmp.gv"')
349
358
  )
350
359
  if not result:
351
- out += f"<b>{fileName}.png</b> created<br>"
360
+ out += f"<b>{file_name}.png</b> created<br>"
352
361
  else:
353
- out += f"Problem with <b>{fileName}</b><br>"
362
+ out += f"Problem with <b>{file_name}</b><br>"
354
363
 
355
364
  if out:
356
365
  QMessageBox.information(None, cfg.programName, out)
boris/utilities.py CHANGED
@@ -33,13 +33,19 @@ import subprocess
33
33
  import sys
34
34
  import urllib.parse
35
35
  import wave
36
+ import exifread
37
+ import datetime
36
38
  from decimal import Decimal as dec
37
39
  from decimal import getcontext, ROUND_DOWN
38
40
  from shutil import copyfile
39
41
  from typing import Union, Tuple
40
42
 
43
+ from hachoir.parser import createParser
44
+ from hachoir.metadata import extractMetadata
45
+
46
+
41
47
  import numpy as np
42
- from PyQt5.QtGui import QPixmap, QImage
48
+ from PySide6.QtGui import QPixmap, QImage
43
49
 
44
50
  from PIL.ImageQt import Image
45
51
 
@@ -65,6 +71,93 @@ except RuntimeError: # libmpv found but version too old
65
71
  """
66
72
 
67
73
 
74
+ def extract_exif_DateTimeOriginal(file_path: str) -> int:
75
+ """
76
+ extract the EXIF DateTimeOriginal tag
77
+ return epoch time
78
+ if the tag is not available return -1
79
+
80
+ Args:
81
+ file_path (str): path of the media file
82
+
83
+ Returns:
84
+ int: timestamp
85
+
86
+ """
87
+ try:
88
+ with open(file_path, "rb") as f_in:
89
+ tags = exifread.process_file(f_in, details=False, stop_tag="EXIF DateTimeOriginal")
90
+ if "EXIF DateTimeOriginal" in tags:
91
+ date_time_original = (
92
+ f"{tags['EXIF DateTimeOriginal'].values[:4]}-"
93
+ f"{tags['EXIF DateTimeOriginal'].values[5:7]}-"
94
+ f"{tags['EXIF DateTimeOriginal'].values[8:10]} "
95
+ f"{tags['EXIF DateTimeOriginal'].values.split(' ')[-1]}"
96
+ )
97
+ return int(datetime.datetime.strptime(date_time_original, "%Y-%m-%d %H:%M:%S").timestamp())
98
+ else:
99
+ try:
100
+ # read from file name (YYYY-MM-DD_HHMMSS)
101
+ return int(datetime.datetime.strptime(pl.Path(file_path).stem, "%Y-%m-%d_%H%M%S").timestamp())
102
+ except Exception:
103
+ # read from file name (YYYY-MM-DD_HH:MM:SS)
104
+ return int(datetime.datetime.strptime(pl.Path(file_path).stem, "%Y-%m-%d_%H:%M:%S").timestamp())
105
+
106
+ except Exception:
107
+ return -1
108
+
109
+
110
+ def extract_video_creation_date(file_path: str) -> int | None:
111
+ """
112
+ returns the timestamp of the media creation date time with Hachoir
113
+ """
114
+
115
+ logging.debug(f"extract_video_creation_date for {file_path}")
116
+
117
+ if not pl.Path(file_path).is_file():
118
+ logging.debug(f"{file_path} not found")
119
+ return None
120
+ try:
121
+ parser = createParser(file_path)
122
+ metadata = extractMetadata(parser)
123
+ except Exception:
124
+ return None
125
+
126
+ if metadata.has("creation_date"):
127
+ if metadata.get("creation_date") == datetime.datetime(1904, 1, 1, 0, 0):
128
+ return None
129
+ return metadata.get("creation_date").timestamp()
130
+ else:
131
+ return None
132
+
133
+
134
+ def extract_date_time_from_file_name(file_path: str) -> int:
135
+ """
136
+ extract YYYY-MM-DD_HHMMSS or YYYY-MM-DD_HH:MM:SS from file name
137
+ """
138
+
139
+ patterns = (r"\d{4}-\d{2}-\d{2}_\d{6}", r"\d{4}-\d{2}-\d{2}_\d{2}:\d{2}:\d{2}")
140
+ for pattern in patterns:
141
+ matches = re.findall(pattern, file_path)
142
+
143
+ if matches:
144
+ if pattern == r"\d{4}-\d{2}-\d{2}_\d{6}":
145
+ logging.debug(
146
+ f"extract_date_time_from_file_name timestamp from {file_path}: {int(datetime.datetime.strptime(matches[0], '%Y-%m-%d_%H%M%S').timestamp())}"
147
+ )
148
+
149
+ return int(datetime.datetime.strptime(matches[0], "%Y-%m-%d_%H%M%S").timestamp())
150
+
151
+ if pattern == r"\d{4}-\d{2}-\d{2}_\d{2}:\d{2}:\d{2}":
152
+ logging.debug(
153
+ f"extract_date_time_from_file_name timestamp from {file_path}: {int(datetime.datetime.strptime(matches[0], '%Y-%m-%d_%H:%M:%S').timestamp())}"
154
+ )
155
+
156
+ return int(datetime.datetime.strptime(matches[0], "%Y-%m-%d_%H:%M:%S").timestamp())
157
+
158
+ return None
159
+
160
+
68
161
  def mpv_lib_version() -> Tuple[str, str, str]:
69
162
  """
70
163
  Version of MPV library
@@ -438,7 +531,7 @@ def state_behavior_codes(ethogram: dict) -> list:
438
531
  list: list of behavior codes defined as STATE event
439
532
 
440
533
  """
441
- return [ethogram[x][cfg.BEHAVIOR_CODE] for x in ethogram if cfg.STATE in ethogram[x][cfg.TYPE].upper()]
534
+ return [ethogram[x][cfg.BEHAVIOR_CODE] for x in ethogram if ethogram[x][cfg.TYPE] == cfg.STATE_EVENT]
442
535
 
443
536
 
444
537
  def point_behavior_codes(ethogram: dict) -> list:
@@ -1524,7 +1617,7 @@ def has_coding_map(ethogram: dict, behavior_idx: str) -> bool:
1524
1617
  return False
1525
1618
  if not ethogram[behavior_idx].get("coding map", False):
1526
1619
  return False
1527
- return False
1620
+ return True
1528
1621
 
1529
1622
 
1530
1623
  def dir_images_number(dir_path_str: str) -> dict:
boris/version.py CHANGED
@@ -20,5 +20,5 @@ This file is part of BORIS.
20
20
 
21
21
  """
22
22
 
23
- __version__ = "8.27.10"
24
- __version_date__ = "2024-10-07"
23
+ __version__ = "9.0.2"
24
+ __version_date__ = "2025-01-15"
boris/video_equalizer.py CHANGED
@@ -20,8 +20,8 @@ This file is part of BORIS.
20
20
 
21
21
  """
22
22
 
23
- from PyQt5.QtWidgets import QDialog
24
- from PyQt5.QtCore import pyqtSignal, QEvent
23
+ from PySide6.QtWidgets import QDialog
24
+ from PySide6.QtCore import Signal, QEvent
25
25
  from .video_equalizer_ui import Ui_Equalizer
26
26
 
27
27
 
@@ -30,8 +30,8 @@ class Video_equalizer(QDialog, Ui_Equalizer):
30
30
  management of video equalizer: brightness, saturation, contrast, gamma and hue
31
31
  """
32
32
 
33
- sendEventSignal = pyqtSignal(int, str, int)
34
- sendKeyPressSignal = pyqtSignal(QEvent)
33
+ sendEventSignal = Signal(int, str, int)
34
+ sendKeyPressSignal = Signal(QEvent)
35
35
 
36
36
  def __init__(self, equalizer, parent=None):
37
37
  super().__init__(parent)