boris-behav-obs 8.16.5__py3-none-any.whl → 9.7.12__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 (126) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +28 -40
  4. boris/add_modifier.py +88 -80
  5. boris/add_modifier_ui.py +266 -144
  6. boris/advanced_event_filtering.py +23 -29
  7. boris/analysis_plugins/__init__.py +0 -0
  8. boris/analysis_plugins/_export_to_feral.py +225 -0
  9. boris/analysis_plugins/_latency.py +59 -0
  10. boris/analysis_plugins/irr_cohen_kappa.py +109 -0
  11. boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
  12. boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
  13. boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
  14. boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
  15. boris/analysis_plugins/number_of_occurences.py +22 -0
  16. boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
  17. boris/analysis_plugins/time_budget.py +61 -0
  18. boris/behav_coding_map_creator.py +235 -236
  19. boris/behavior_binary_table.py +33 -50
  20. boris/behaviors_coding_map.py +17 -18
  21. boris/boris_cli.py +6 -25
  22. boris/cmd_arguments.py +12 -1
  23. boris/coding_pad.py +19 -36
  24. boris/config.py +109 -50
  25. boris/config_file.py +58 -67
  26. boris/connections.py +105 -58
  27. boris/converters.py +13 -37
  28. boris/converters_ui.py +187 -110
  29. boris/cooccurence.py +250 -0
  30. boris/core.py +2174 -1303
  31. boris/core_qrc.py +15892 -10829
  32. boris/core_ui.py +941 -806
  33. boris/db_functions.py +17 -42
  34. boris/dev.py +27 -7
  35. boris/dialog.py +461 -242
  36. boris/duration_widget.py +9 -14
  37. boris/edit_event.py +61 -31
  38. boris/edit_event_ui.py +208 -97
  39. boris/event_operations.py +405 -281
  40. boris/events_cursor.py +25 -17
  41. boris/events_snapshots.py +36 -82
  42. boris/exclusion_matrix.py +4 -9
  43. boris/export_events.py +180 -203
  44. boris/export_observation.py +60 -73
  45. boris/external_processes.py +123 -98
  46. boris/geometric_measurement.py +427 -218
  47. boris/gui_utilities.py +91 -14
  48. boris/image_overlay.py +4 -4
  49. boris/import_observations.py +190 -98
  50. boris/ipc_mpv.py +325 -0
  51. boris/irr.py +20 -57
  52. boris/latency.py +31 -24
  53. boris/measurement_widget.py +14 -18
  54. boris/media_file.py +17 -19
  55. boris/menu_options.py +16 -6
  56. boris/modifier_coding_map_creator.py +1013 -0
  57. boris/modifiers_coding_map.py +7 -9
  58. boris/mpv2.py +128 -35
  59. boris/observation.py +501 -211
  60. boris/observation_operations.py +1037 -393
  61. boris/observation_ui.py +573 -363
  62. boris/observations_list.py +51 -58
  63. boris/otx_parser.py +74 -68
  64. boris/param_panel.py +45 -59
  65. boris/param_panel_ui.py +254 -138
  66. boris/player_dock_widget.py +91 -56
  67. boris/plot_data_module.py +20 -53
  68. boris/plot_events.py +56 -153
  69. boris/plot_events_rt.py +16 -30
  70. boris/plot_spectrogram_rt.py +83 -56
  71. boris/plot_waveform_rt.py +27 -49
  72. boris/plugins.py +468 -0
  73. boris/portion/__init__.py +18 -8
  74. boris/portion/const.py +35 -18
  75. boris/portion/dict.py +5 -5
  76. boris/portion/func.py +2 -2
  77. boris/portion/interval.py +21 -41
  78. boris/portion/io.py +41 -32
  79. boris/preferences.py +307 -123
  80. boris/preferences_ui.py +686 -227
  81. boris/project.py +294 -271
  82. boris/project_functions.py +626 -537
  83. boris/project_import_export.py +204 -213
  84. boris/project_ui.py +673 -441
  85. boris/qrc_boris.py +6 -3
  86. boris/qrc_boris5.py +6 -3
  87. boris/select_modifiers.py +62 -90
  88. boris/select_observations.py +19 -197
  89. boris/select_subj_behav.py +67 -39
  90. boris/state_events.py +51 -33
  91. boris/subjects_pad.py +7 -9
  92. boris/synthetic_time_budget.py +42 -26
  93. boris/time_budget_functions.py +169 -169
  94. boris/time_budget_widget.py +77 -89
  95. boris/transitions.py +41 -41
  96. boris/utilities.py +594 -226
  97. boris/version.py +3 -3
  98. boris/video_equalizer.py +16 -14
  99. boris/video_equalizer_ui.py +199 -130
  100. boris/video_operations.py +86 -28
  101. boris/view_df.py +104 -0
  102. boris/view_df_ui.py +75 -0
  103. boris/write_event.py +240 -136
  104. boris_behav_obs-9.7.12.dist-info/METADATA +139 -0
  105. boris_behav_obs-9.7.12.dist-info/RECORD +110 -0
  106. {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.12.dist-info}/WHEEL +1 -1
  107. boris_behav_obs-9.7.12.dist-info/entry_points.txt +2 -0
  108. boris/README.TXT +0 -22
  109. boris/add_modifier.ui +0 -323
  110. boris/converters.ui +0 -289
  111. boris/core.qrc +0 -37
  112. boris/core.ui +0 -1571
  113. boris/edit_event.ui +0 -233
  114. boris/icons/logo_eye.ico +0 -0
  115. boris/map_creator.py +0 -982
  116. boris/observation.ui +0 -814
  117. boris/param_panel.ui +0 -379
  118. boris/preferences.ui +0 -537
  119. boris/project.ui +0 -1074
  120. boris/vlc_local.py +0 -90
  121. boris_behav_obs-8.16.5.dist-info/LICENSE.TXT +0 -674
  122. boris_behav_obs-8.16.5.dist-info/METADATA +0 -134
  123. boris_behav_obs-8.16.5.dist-info/RECORD +0 -107
  124. boris_behav_obs-8.16.5.dist-info/entry_points.txt +0 -2
  125. {boris → boris_behav_obs-9.7.12.dist-info/licenses}/LICENSE.TXT +0 -0
  126. {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.12.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2023 Olivier Friard
4
+ Copyright 2012-2025 Olivier Friard
5
5
 
6
6
  This file is part of BORIS.
7
7
 
@@ -22,9 +22,9 @@ This file is part of BORIS.
22
22
 
23
23
  import binascii
24
24
 
25
- from PyQt5.QtCore import pyqtSignal, QPoint, Qt
26
- from PyQt5.QtGui import QPen, QPixmap, QBrush, QMouseEvent, QPolygonF, QColor
27
- from PyQt5.QtWidgets import (
25
+ from PySide6.QtCore import Signal, QPoint, Qt
26
+ from PySide6.QtGui import QPen, QPixmap, QBrush, QMouseEvent, QPolygonF, QColor
27
+ from PySide6.QtWidgets import (
28
28
  QDialog,
29
29
  QGraphicsView,
30
30
  QGraphicsScene,
@@ -39,10 +39,9 @@ from PyQt5.QtWidgets import (
39
39
  )
40
40
 
41
41
 
42
- class ModifiersCodingMapWindowClass(QDialog):
42
+ class ModifiersCodingMapWindow(QDialog):
43
43
  class View(QGraphicsView):
44
-
45
- mousePress = pyqtSignal(QMouseEvent)
44
+ mousePress = Signal(QMouseEvent)
46
45
 
47
46
  def mousePressEvent(self, event):
48
47
  self.mousePress.emit(event)
@@ -57,7 +56,7 @@ class ModifiersCodingMapWindowClass(QDialog):
57
56
  self.scene().update()
58
57
 
59
58
  def __init__(self, modifiers_coding_map):
60
- super(ModifiersCodingMapWindowClass, self).__init__()
59
+ super(ModifiersCodingMapWindow, self).__init__()
61
60
 
62
61
  self.areasList = {}
63
62
  self.polygonsList2 = {}
@@ -104,7 +103,6 @@ class ModifiersCodingMapWindowClass(QDialog):
104
103
 
105
104
  for code in self.polygonsList2:
106
105
  if self.polygonsList2[code].contains(test):
107
-
108
106
  codes = self.leareaCode.text().split(self.codeSeparator)
109
107
  if "" in codes:
110
108
  codes.remove("")
boris/mpv2.py CHANGED
@@ -2,7 +2,7 @@
2
2
  # vim: ts=4 sw=4 et
3
3
  #
4
4
  # Python MPV library module
5
- # Copyright (C) 2017-2022 Sebastian Götte <code@jaseg.net>
5
+ # Copyright (C) 2017-2024 Sebastian Götte <code@jaseg.net>
6
6
  #
7
7
  # python-mpv inherits the underlying libmpv's license, which can be either GPLv2 or later (default) or LGPLv2.1 or
8
8
  # later. For details, see the mpv copyright page here: https://github.com/mpv-player/mpv/blob/master/Copyright
@@ -17,11 +17,14 @@
17
17
  #
18
18
  # You can find copies of the GPLv2 and LGPLv2.1 licenses in the project repository's LICENSE.GPL and LICENSE.LGPL files.
19
19
 
20
+ __version__ = '1.0.7'
21
+
20
22
  from ctypes import *
21
23
  import ctypes.util
22
24
  import threading
23
25
  import queue
24
26
  import os
27
+ import os.path
25
28
  import sys
26
29
  from warnings import warn
27
30
  from functools import partial, wraps
@@ -33,14 +36,30 @@ import traceback
33
36
 
34
37
  if os.name == 'nt':
35
38
  # Note: mpv-2.dll with API version 2 corresponds to mpv v0.35.0. Most things should work with the fallback, too.
36
- dll = ctypes.util.find_library('mpv-2.dll') or ctypes.util.find_library('mpv-1.dll')
37
- if dll is None:
38
- raise OSError('Cannot find mpv-1.dll or mpv-2.dll in your system %PATH%. One way to deal with this is to ship '
39
- 'the dll with your script and put the directory your script is in into %PATH% before '
40
- '"import mpv": os.environ["PATH"] = os.path.dirname(__file__) + os.pathsep + os.environ["PATH"] '
41
- 'If mpv-1.dll is located elsewhere, you can add that path to os.environ["PATH"].')
42
- backend = CDLL(dll)
39
+ names = ['mpv-2.dll', 'libmpv-2.dll', 'mpv-1.dll']
40
+ for name in names:
41
+ dll = ctypes.util.find_library(name)
42
+ if dll:
43
+ break
44
+ else:
45
+ for name in names:
46
+ dll = os.path.join(os.path.dirname(__file__), name)
47
+ if os.path.isfile(dll):
48
+ break
49
+ else:
50
+ raise OSError('Cannot find mpv-1.dll, mpv-2.dll or libmpv-2.dll in your system %PATH%. One way to deal with this is to ship the dll with your script and put the directory your script is in into %PATH% before "import mpv": os.environ["PATH"] = os.path.dirname(__file__) + os.pathsep + os.environ["PATH"] If mpv-1.dll is located elsewhere, you can add that path to os.environ["PATH"].')
51
+
52
+ try:
53
+ # flags argument: LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
54
+ # cf. https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexa
55
+ backend = CDLL(dll, 0x00001000 | 0x00000100)
56
+ except Exception as e:
57
+ if not os.path.isabs(dll): # can only be find_library, not the "look next to mpv.py" thing
58
+ raise OSError(f'ctypes.find_library found mpv.dll at {dll}, but ctypes.CDLL could not load it. It looks like find_library found mpv.dll under a relative path entry in %PATH%. Please make sure all paths in %PATH% are absolute. Instead of trying to load mpv.dll from the current working directory, put it somewhere next to your script and add that path to %PATH% using os.environ["PATH"] = os.path.dirname(__file__) + os.pathsep + os.environ["PATH"]') from e
59
+ else:
60
+ raise OSError(f'ctypes.find_library found mpv.dll at {dll}, but ctypes.CDLL could not load it.') from e
43
61
  fs_enc = 'utf-8'
62
+
44
63
  else:
45
64
  import locale
46
65
  lc, enc = locale.getlocale(locale.LC_NUMERIC)
@@ -50,10 +69,7 @@ else:
50
69
 
51
70
  sofile = ctypes.util.find_library('mpv')
52
71
  if sofile is None:
53
- raise OSError("Cannot find libmpv in the usual places. Depending on your distro, you may try installing an "
54
- "mpv-devel or mpv-libs package. If you have libmpv around but this script can't find it, consult "
55
- "the documentation for ctypes.util.find_library which this script uses to look up the library "
56
- "filename.")
72
+ raise OSError("Cannot find libmpv in the usual places. Depending on your distro, you may try installing an mpv-devel or mpv-libs package. If you have libmpv around but this script can't find it, consult the documentation for ctypes.util.find_library which this script uses to look up the library filename.")
57
73
  backend = CDLL(sofile)
58
74
  fs_enc = sys.getfilesystemencoding()
59
75
 
@@ -441,9 +457,14 @@ class MpvEventLogMessage(Structure):
441
457
  return lazy_decoder(self._text)
442
458
 
443
459
  class MpvEventEndFile(Structure):
444
- _fields_ = [('reason', c_int),
445
- ('error', c_int)]
446
-
460
+ _fields_ = [
461
+ ('reason', c_int),
462
+ ('error', c_int),
463
+ ('playlist_entry_id', c_ulonglong),
464
+ ('playlist_insert_id', c_ulonglong),
465
+ ('playlist_insert_num_entries', c_int),
466
+ ]
467
+
447
468
  EOF = 0
448
469
  RESTARTED = 1
449
470
  ABORTED = 2
@@ -892,6 +913,8 @@ class MPV(object):
892
913
  self._event_thread.start()
893
914
  else:
894
915
  self._event_thread = None
916
+ if (m := re.search(r'(\d+)\.(\d+)\.(\d+)', self.mpv_version)):
917
+ self.mpv_version_tuple = tuple(map(int, m.groups()))
895
918
 
896
919
  @contextmanager
897
920
  def _enqueue_exceptions(self):
@@ -1033,30 +1056,38 @@ class MPV(object):
1033
1056
  rv = cond(val)
1034
1057
  if rv:
1035
1058
  result.set_result(rv)
1059
+
1060
+ except InvalidStateError:
1061
+ pass
1062
+
1036
1063
  except Exception as e:
1037
1064
  try:
1038
1065
  result.set_exception(e)
1039
- except InvalidStateError:
1066
+ except:
1040
1067
  pass
1041
- except InvalidStateError:
1042
- pass
1043
- self.observe_property(name, observer)
1044
- err_unregister = self._set_error_handler(result)
1045
1068
 
1046
1069
  try:
1047
1070
  result.set_running_or_notify_cancel()
1071
+
1072
+ self.observe_property(name, observer)
1073
+ err_unregister = self._set_error_handler(result)
1048
1074
  if catch_errors:
1049
1075
  self._exception_futures.add(result)
1050
1076
 
1051
1077
  yield result
1052
1078
 
1053
- rv = cond(getattr(self, name.replace('-', '_')))
1054
- if level_sensitive and rv:
1055
- result.set_result(rv)
1079
+ if level_sensitive:
1080
+ rv = cond(getattr(self, name.replace('-', '_')))
1081
+ if rv:
1082
+ result.set_result(rv)
1083
+ return
1084
+
1085
+ self.check_core_alive()
1086
+ result.result(timeout)
1087
+
1088
+ except InvalidStateError:
1089
+ pass
1056
1090
 
1057
- else:
1058
- self.check_core_alive()
1059
- result.result(timeout)
1060
1091
  finally:
1061
1092
  err_unregister()
1062
1093
  self.unobserve_property(name, observer)
@@ -1323,9 +1354,16 @@ class MPV(object):
1323
1354
  def _encode_options(options):
1324
1355
  return ','.join('{}={}'.format(_py_to_mpv(str(key)), str(val)) for key, val in options.items())
1325
1356
 
1326
- def loadfile(self, filename, mode='replace', **options):
1357
+ def loadfile(self, filename, mode='replace', index=None, **options):
1327
1358
  """Mapped mpv loadfile command, see man mpv(1)."""
1328
- self.command('loadfile', filename.encode(fs_enc), mode, MPV._encode_options(options))
1359
+ if self.mpv_version_tuple >= (0, 38, 0):
1360
+ if index is None:
1361
+ index = -1
1362
+ self.command('loadfile', filename.encode(fs_enc), mode, index, MPV._encode_options(options))
1363
+ else:
1364
+ if index is not None:
1365
+ warn(f'The index argument to the loadfile command is only supported on mpv >= 0.38.0')
1366
+ self.command('loadfile', filename.encode(fs_enc), mode, MPV._encode_options(options))
1329
1367
 
1330
1368
  def loadlist(self, playlist, mode='replace'):
1331
1369
  """Mapped mpv loadlist command, see man mpv(1)."""
@@ -1357,11 +1395,17 @@ class MPV(object):
1357
1395
 
1358
1396
  def quit(self, code=None):
1359
1397
  """Mapped mpv quit command, see man mpv(1)."""
1360
- self.command('quit', code)
1398
+ if code is not None:
1399
+ self.command('quit', code)
1400
+ else:
1401
+ self.command('quit')
1361
1402
 
1362
1403
  def quit_watch_later(self, code=None):
1363
1404
  """Mapped mpv quit_watch_later command, see man mpv(1)."""
1364
- self.command('quit_watch_later', code)
1405
+ if code is not None:
1406
+ self.command('quit_watch_later', code)
1407
+ else:
1408
+ self.command('quit_watch_later')
1365
1409
 
1366
1410
  def stop(self, keep_playlist=False):
1367
1411
  """Mapped mpv stop command, see man mpv(1)."""
@@ -1446,7 +1490,7 @@ class MPV(object):
1446
1490
  """Mapped mpv discnav command, see man mpv(1)."""
1447
1491
  self.command('discnav', command)
1448
1492
 
1449
- def mouse(x, y, button=None, mode='single'):
1493
+ def mouse(self, x, y, button=None, mode='single'):
1450
1494
  """Mapped mpv mouse command, see man mpv(1)."""
1451
1495
  if button is None:
1452
1496
  self.command('mouse', x, y, mode)
@@ -1811,9 +1855,6 @@ class MPV(object):
1811
1855
  pass
1812
1856
  else:
1813
1857
  warnings.warn(f'Unhandled exception {e} inside stream open callback for URI {uri}\n{traceback.format_exc()}')
1814
-
1815
-
1816
-
1817
1858
  return ErrorCode.LOADING_FAILED
1818
1859
 
1819
1860
  cb_info.contents.cookie = None
@@ -1943,6 +1984,55 @@ class MPV(object):
1943
1984
  return cb
1944
1985
  return register
1945
1986
 
1987
+ @contextmanager
1988
+ def play_context(self):
1989
+ """ Context manager for streaming bytes straight into libmpv.
1990
+
1991
+ This is a convenience wrapper around python_stream. play_context returns a write method, which you can use in
1992
+ the body of the context manager to feed libmpv bytes. All bytes you feed in with write() in the body of a single
1993
+ call of this context manager are treated as one single file. A queue is used internally, so this function is
1994
+ thread-safe. The queue is unlimited, so it cannot block and is safe to call from async code. You can use this
1995
+ function to stream chunked data, e.g. from the network.
1996
+
1997
+ Use it like this:
1998
+
1999
+ with m.play_context() as write:
2000
+ with open(TESTVID, 'rb') as f:
2001
+ while (chunk := f.read(65536)): # Get some chunks of bytes
2002
+ write(chunk)
2003
+ """
2004
+ q = queue.Queue()
2005
+
2006
+ frame = sys._getframe()
2007
+ stream_name = f'__python_mpv_play_generator_{hash(frame)}'
2008
+ EOF = frame # Get some unique object as EOF marker
2009
+ @self.python_stream(stream_name)
2010
+ def reader():
2011
+ while (chunk := q.get()) is not EOF:
2012
+ if chunk:
2013
+ yield chunk
2014
+ reader.unregister()
2015
+
2016
+ def write(chunk):
2017
+ q.put(chunk)
2018
+
2019
+ # Start playback before yielding, the first call to reader() will block until write is called at least once.
2020
+ self.play(f'python://{stream_name}')
2021
+ yield write
2022
+ q.put(EOF)
2023
+
2024
+ def play_bytes(self, data):
2025
+ """ Play the given bytes object as a single file. """
2026
+ frame = sys._getframe()
2027
+ stream_name = f'__python_mpv_play_generator_{hash(frame)}'
2028
+
2029
+ @self.python_stream(stream_name)
2030
+ def reader():
2031
+ yield data
2032
+ reader.unregister() # unregister itself
2033
+
2034
+ self.play(f'python://{stream_name}')
2035
+
1946
2036
  def python_stream_catchall(self, cb):
1947
2037
  """ Register a catch-all python stream to be called when no name matches can be found. Use this decorator on a
1948
2038
  function that takes a name argument and returns a (generator, size) tuple (with size being None if unknown).
@@ -2000,7 +2090,10 @@ class MPV(object):
2000
2090
  def _set_property(self, name, value):
2001
2091
  self.check_core_alive()
2002
2092
  ename = name.encode('utf-8')
2003
- if isinstance(value, (list, set, dict)):
2093
+ if isinstance(value, dict):
2094
+ _1, _2, _3, pointer = _make_node_str_map(value)
2095
+ _mpv_set_property(self.handle, ename, MpvFormat.NODE, pointer)
2096
+ elif isinstance(value, (list, set)):
2004
2097
  _1, _2, _3, pointer = _make_node_str_list(value)
2005
2098
  _mpv_set_property(self.handle, ename, MpvFormat.NODE, pointer)
2006
2099
  else: