boris-behav-obs 9.7.1__py3-none-any.whl → 9.7.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
boris/about.py CHANGED
@@ -40,7 +40,7 @@ def actionAbout_activated(self):
40
40
  About dialog
41
41
  """
42
42
 
43
- programs_versions: list = ["MPV media player"]
43
+ programs_versions: list[str] = ["MPV media player"]
44
44
 
45
45
  mpv_lib_version, mpv_lib_file_path, mpv_api_version = util.mpv_lib_version()
46
46
  programs_versions.append(
@@ -53,13 +53,13 @@ def actionAbout_activated(self):
53
53
 
54
54
  # ffmpeg
55
55
  if self.ffmpeg_bin == "ffmpeg" and sys.platform.startswith("linux"):
56
- ffmpeg_true_path = subprocess.getoutput("which ffmpeg")
56
+ ffmpeg_true_path: str = subprocess.getoutput("which ffmpeg")
57
57
  else:
58
58
  ffmpeg_true_path = self.ffmpeg_bin
59
59
  programs_versions.extend(
60
60
  [
61
61
  "\nFFmpeg",
62
- subprocess.getoutput(f'"{self.ffmpeg_bin}" -version').split("\n")[0],
62
+ subprocess.getoutput(cmd=f'"{self.ffmpeg_bin}" -version').split(sep="\n")[0],
63
63
  f"Path: {ffmpeg_true_path}",
64
64
  "https://www.ffmpeg.org",
65
65
  ]
@@ -75,11 +75,11 @@ def actionAbout_activated(self):
75
75
  programs_versions.extend(["\nPandas", f"version {pd.__version__}", "https://pandas.pydata.org"])
76
76
 
77
77
  # graphviz
78
- gv_result = subprocess.getoutput("dot -V")
78
+ gv_result = subprocess.getoutput(cmd="dot -V")
79
79
 
80
80
  programs_versions.extend(["\nGraphViz", gv_result if "graphviz" in gv_result else "not installed", "https://www.graphviz.org/"])
81
81
 
82
- about_dialog = QMessageBox()
82
+ about_dialog: QMessageBox = QMessageBox()
83
83
  about_dialog.setIconPixmap(QPixmap(":/boris_unito"))
84
84
 
85
85
  about_dialog.setWindowTitle(f"About {cfg.programName}")
boris/config.py CHANGED
@@ -132,6 +132,8 @@ POINT_EVENT_PLOT_COLOR = "black"
132
132
 
133
133
  CHAR_FORBIDDEN_IN_MODIFIERS = "(|),`~"
134
134
 
135
+ FAST_FORWARD_DEFAULT_VALUE:float = 10.0
136
+
135
137
  ADAPT_FAST_JUMP = "adapt_fast_jump"
136
138
  ADAPT_FAST_JUMP_DEFAULT = False
137
139
 
@@ -419,6 +421,10 @@ MPV_HWDEC_AUTOSAFE = "auto-safe"
419
421
  MPV_HWDEC_OPTIONS = (MPV_HWDEC_AUTO, MPV_HWDEC_AUTOSAFE, MPV_HWDEC_NO)
420
422
  MPV_HWDEC_DEFAULT_VALUE = MPV_HWDEC_AUTO
421
423
 
424
+ # frame step size (disabled)
425
+ # FRAME_STEP_SIZE: str = "frame_step_size"
426
+ # FRAME_STEP_SIZE_DEFAULT_VALUE: int = 1
427
+
422
428
  ANALYSIS_PLUGINS = "analysis_plugins"
423
429
  EXCLUDED_PLUGINS = "excluded_plugins"
424
430
  PERSONAL_PLUGINS_DIR = "personal_plugins_dir"
@@ -730,6 +736,7 @@ INIT_PARAM = {
730
736
  MPV_HWDEC: MPV_HWDEC_DEFAULT_VALUE,
731
737
  PROJECT_FILE_INDENTATION: PROJECT_FILE_INDENTATION_DEFAULT_VALUE,
732
738
  f"{MEDIA} tw fields": MEDIA_TW_EVENTS_FIELDS_DEFAULT,
739
+ # FRAME_STEP_SIZE: FRAME_STEP_SIZE_DEFAULT_VALUE,
733
740
  }
734
741
 
735
742
  SDIS_EXT = "sds"
boris/config_file.py CHANGED
@@ -82,11 +82,11 @@ def read(self):
82
82
 
83
83
  logging.debug(f"time format: {self.timeFormat}")
84
84
 
85
- self.fast = 10
85
+ self.fast = cfg.FAST_FORWARD_DEFAULT_VALUE
86
86
  try:
87
- self.fast = int(settings.value("Time/fast_forward_speed"))
87
+ self.fast = float(settings.value("Time/fast_forward_speed"))
88
88
  except Exception:
89
- self.fast = 10
89
+ self.fast = cfg.FAST_FORWARD_DEFAULT_VALUE
90
90
 
91
91
  logging.debug(f"Time/fast_forward_speed: {self.fast}")
92
92
 
boris/core.py CHANGED
@@ -43,7 +43,6 @@ import locale
43
43
  import tempfile
44
44
  import time
45
45
  import urllib.request
46
- from typing import Union, Tuple
47
46
  from decimal import Decimal as dec
48
47
  from decimal import ROUND_DOWN
49
48
  import gzip
@@ -242,7 +241,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
242
241
  current_player: int = 0 # id of the selected (left click) video player
243
242
 
244
243
  mem_media_name: str = "" # record current media name. Use to check if media changed
245
- mem_playlist_index: Union[int, None] = None
244
+ mem_playlist_index: int | None = None
246
245
  saved_state = None
247
246
  user_move_slider: bool = False
248
247
  observationId: str = "" # current observation id
@@ -739,7 +738,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
739
738
  text: str = "Behaviors to show in ethogram list",
740
739
  table: str = cfg.ETHOGRAM,
741
740
  behavior_type: list = cfg.STATE_EVENT_TYPES,
742
- ) -> Tuple[bool, list]:
741
+ ) -> tuple[bool, list]:
743
742
  """
744
743
  allow user to:
745
744
  filter behaviors in ethogram widget
@@ -3682,14 +3681,18 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3682
3681
  """
3683
3682
  show next frame
3684
3683
  """
3684
+ # frame_step_size = self.config_param.get(cfg.FRAME_STEP_SIZE, cfg.FRAME_STEP_SIZE_DEFAULT_VALUE)
3685
+
3685
3686
  if self.playerType == cfg.IMAGES:
3686
3687
  if self.image_idx < len(self.images_list) - 1:
3687
- self.image_idx += 1
3688
+ self.image_idx += 1 # frame_step_size
3688
3689
  self.extract_frame(self.dw_player[0])
3689
3690
 
3690
3691
  if self.playerType == cfg.MEDIA:
3691
3692
  for dw in self.dw_player:
3693
+ # for _ in range(frame_step_size):
3692
3694
  dw.player.frame_step()
3695
+ # time.sleep(0.5)
3693
3696
 
3694
3697
  if self.geometric_measurements_mode:
3695
3698
  self.extract_frame(dw)
@@ -3866,7 +3869,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3866
3869
  write_event.write_event(self, event, cumulative_time)
3867
3870
  # write_event.write_event(self, event, self.getLaps())
3868
3871
 
3869
- def get_frame_index(self, player_idx: int = 0) -> Union[int, str]:
3872
+ def get_frame_index(self, player_idx: int = 0) -> int | str:
3870
3873
  """
3871
3874
  returns frame index for player player_idx
3872
3875
  """
@@ -3947,7 +3950,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3947
3950
  slider_position = self.video_slider.value() / (cfg.SLIDER_MAXIMUM - 1)
3948
3951
  if self.dw_player[0].player.duration is None:
3949
3952
  return
3950
- video_position = slider_position * self.dw_player[0].player.duration
3953
+ print(f"{slider_position=}")
3954
+
3955
+ d = self.dw_player[0].player.duration
3956
+ print(f"{d=}")
3957
+ if d is None:
3958
+ return
3959
+ video_position = slider_position * d
3960
+ #video_position = slider_position * self.dw_player[0].player.duration
3951
3961
  # self.dw_player[0].player.command("seek", str(video_position), "absolute")
3952
3962
  self.dw_player[0].player.seek(video_position, "absolute")
3953
3963
 
@@ -4142,7 +4152,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4142
4152
  self.dw_player[n_player].player.playlist_pos = self.dw_player[n_player].player.playlist_count - 1
4143
4153
  self.seek_mediaplayer(self.dw_player[n_player].media_durations[-1], player=n_player)
4144
4154
 
4145
- def mpv_timer_out(self, value: Union[float, None] = None, scroll_slider=True):
4155
+ def mpv_timer_out(self, value: float | None = None, scroll_slider=True):
4146
4156
  """
4147
4157
  print the media current position and total length for MPV player
4148
4158
  scroll video slider to video position
@@ -4152,13 +4162,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4152
4162
  if not self.observationId:
4153
4163
  return
4154
4164
 
4155
- print("mpv timer out")
4156
-
4157
4165
  cumulative_time_pos = self.getLaps()
4158
- print(f"{cumulative_time_pos=}")
4159
4166
  # get frame index
4160
4167
  frame_idx = self.get_frame_index()
4161
- print(f"{frame_idx=}")
4162
4168
  # frame_idx = 0
4163
4169
 
4164
4170
  if value is None: # ipc mpv
@@ -4237,9 +4243,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4237
4243
  self.show_current_states_in_subjects_table()
4238
4244
 
4239
4245
  # current media name
4240
- print(f"{self.dw_player[0].player.playlist_pos=}")
4241
- print(f"{self.dw_player[0].player.playlist=}")
4242
-
4243
4246
  if self.dw_player[0].player.playlist_pos is not None:
4244
4247
  current_media_name = Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"]).name
4245
4248
  current_playlist_index = self.dw_player[0].player.playlist_pos
@@ -4315,8 +4318,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4315
4318
 
4316
4319
  self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
4317
4320
 
4318
- print(f"{msg=}")
4319
-
4320
4321
  if msg:
4321
4322
  self.lb_current_media_time.setText(msg)
4322
4323
 
@@ -4488,7 +4489,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4488
4489
  for x in self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]
4489
4490
  )
4490
4491
 
4491
- def choose_behavior(self, obs_key) -> Union[None, str]:
4492
+ def choose_behavior(self, obs_key) -> None | str:
4492
4493
  """
4493
4494
  fill listwidget with all behaviors coded by key
4494
4495
 
@@ -4520,7 +4521,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4520
4521
  else:
4521
4522
  return None
4522
4523
 
4523
- def choose_subject(self, subject_key) -> Union[None, str]:
4524
+ def choose_subject(self, subject_key) -> None | str:
4524
4525
  """
4525
4526
  fill listwidget with all subjects coded by key
4526
4527
 
@@ -4608,7 +4609,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4608
4609
 
4609
4610
  return dec(str(round(mem_laps / 1000, 3)))
4610
4611
 
4611
- def get_obs_time(self, n_player: int = 0) -> Tuple[dec, dec | None]:
4612
+ def get_obs_time(self, n_player: int = 0) -> tuple[dec, dec | None]:
4612
4613
  """
4613
4614
  returns time in current media and cumulative time from begining of observation
4614
4615
  do not add time offset
boris/ipc_mpv.py CHANGED
@@ -36,6 +36,7 @@ class IPC_MPV:
36
36
  self.process = subprocess.Popen(
37
37
  [
38
38
  "mpv",
39
+ "--ontop",
39
40
  "--no-border",
40
41
  "--osc=no", # no on screen commands
41
42
  "--input-ipc-server=" + self.socket_path,
@@ -48,30 +49,6 @@ class IPC_MPV:
48
49
  stderr=subprocess.PIPE,
49
50
  )
50
51
 
51
- '''
52
- def init_socket(self):
53
- """
54
- Initialize the JSON IPC socket.
55
- """
56
- print("init socket")
57
- self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
58
- QTimer.singleShot(5000, self.connect_socket) # Allow time for mpv to initialize
59
- '''
60
-
61
- '''
62
- def connect_socket(self):
63
- """
64
- Connect to the mpv IPC socket.
65
- """
66
- print("connect socket")
67
- try:
68
- self.sock.connect(self.socket_path)
69
- print("Connected to mpv IPC server.")
70
- except socket.error as e:
71
- print(f"Failed to connect to mpv IPC server: {e}")
72
- print("end of connect_socket fucntion")
73
- '''
74
-
75
52
  def send_command(self, command):
76
53
  """
77
54
  Send a JSON command to the mpv IPC server.
@@ -92,9 +69,7 @@ class IPC_MPV:
92
69
  # Parse the response as JSON
93
70
  response_data = json.loads(response.decode("utf-8"))
94
71
  if response_data["error"] != "success":
95
- print(f"send command: {command}")
96
- print(f"{response_data=}")
97
- print()
72
+ logging.warning(f"send command: {command} response data: {response_data}")
98
73
  # Return the 'data' field which contains the playback position
99
74
  return response_data.get("data")
100
75
  except FileNotFoundError:
@@ -128,7 +103,6 @@ class IPC_MPV:
128
103
 
129
104
  @pause.setter
130
105
  def pause(self, value):
131
- print(f"set pause to {value}")
132
106
  return self.send_command({"command": ["set_property", "pause", value]})
133
107
 
134
108
  @property
@@ -168,7 +142,6 @@ class IPC_MPV:
168
142
  @property
169
143
  def playback_time(self):
170
144
  playback_time_ = self.send_command({"command": ["get_property", "playback-time"]})
171
- print(f"playback_time: {playback_time_}")
172
145
  return playback_time_
173
146
 
174
147
  def frame_step(self):
@@ -1964,7 +1964,7 @@ def initialize_new_media_observation(self) -> bool:
1964
1964
 
1965
1965
  menu_options.update_menu(self)
1966
1966
 
1967
- if not self.MPV_IPC_MODE:
1967
+ if self.MPV_IPC_MODE:
1968
1968
  # activate timer
1969
1969
  self.ipc_mpv_timer = QTimer()
1970
1970
  self.ipc_mpv_timer.setInterval(500)
boris/preferences.py CHANGED
@@ -221,6 +221,9 @@ def preferences(self):
221
221
  preferencesWindow.cbConfirmSound.setChecked(self.confirmSound)
222
222
  # beep every
223
223
  preferencesWindow.sbBeepEvery.setValue(self.beep_every)
224
+ # frame step size
225
+ #preferencesWindow.sb_frame_step_size.setValue(self.config_param.get(cfg.FRAME_STEP_SIZE, cfg.FRAME_STEP_SIZE_DEFAULT_VALUE))
226
+
224
227
  # alert no focal subject
225
228
  preferencesWindow.cbAlertNoFocalSubject.setChecked(self.alertNoFocalSubject)
226
229
  # tracking cursor above event
@@ -418,6 +421,9 @@ def preferences(self):
418
421
 
419
422
  self.beep_every = preferencesWindow.sbBeepEvery.value()
420
423
 
424
+ # frame step size
425
+ #self.config_param[cfg.FRAME_STEP_SIZE] = preferencesWindow.sb_frame_step_size.value()
426
+
421
427
  self.alertNoFocalSubject = preferencesWindow.cbAlertNoFocalSubject.isChecked()
422
428
 
423
429
  self.trackingCursorAboveEvent = preferencesWindow.cbTrackingCursorAboveEvent.isChecked()
boris/preferences_ui.py CHANGED
@@ -3,7 +3,7 @@
3
3
  ################################################################################
4
4
  ## Form generated from reading UI file 'preferences.ui'
5
5
  ##
6
- ## Created by: Qt User Interface Compiler version 6.9.0
6
+ ## Created by: Qt User Interface Compiler version 6.10.0
7
7
  ##
8
8
  ## WARNING! All changes made in this file will be lost when recompiling UI file!
9
9
  ################################################################################
@@ -145,11 +145,8 @@ class Ui_prefDialog(object):
145
145
 
146
146
  self.horizontalLayout_4.addWidget(self.label_4)
147
147
 
148
- self.sbffSpeed = QSpinBox(self.tab_observations)
148
+ self.sbffSpeed = QDoubleSpinBox(self.tab_observations)
149
149
  self.sbffSpeed.setObjectName(u"sbffSpeed")
150
- self.sbffSpeed.setMinimum(0)
151
- self.sbffSpeed.setMaximum(10000)
152
- self.sbffSpeed.setValue(10)
153
150
 
154
151
  self.horizontalLayout_4.addWidget(self.sbffSpeed)
155
152
 
@@ -244,6 +241,26 @@ class Ui_prefDialog(object):
244
241
 
245
242
  self.verticalLayout.addWidget(self.cb_pause_before_addevent)
246
243
 
244
+ self.horizontalLayout_23 = QHBoxLayout()
245
+ self.horizontalLayout_23.setObjectName(u"horizontalLayout_23")
246
+ self.label_24 = QLabel(self.tab_observations)
247
+ self.label_24.setObjectName(u"label_24")
248
+ self.label_24.setEnabled(False)
249
+
250
+ self.horizontalLayout_23.addWidget(self.label_24)
251
+
252
+ self.sb_frame_step_size = QSpinBox(self.tab_observations)
253
+ self.sb_frame_step_size.setObjectName(u"sb_frame_step_size")
254
+ self.sb_frame_step_size.setEnabled(False)
255
+ self.sb_frame_step_size.setMinimum(1)
256
+ self.sb_frame_step_size.setMaximum(1000)
257
+ self.sb_frame_step_size.setValue(1)
258
+
259
+ self.horizontalLayout_23.addWidget(self.sb_frame_step_size)
260
+
261
+
262
+ self.verticalLayout.addLayout(self.horizontalLayout_23)
263
+
247
264
  self.verticalSpacer_4 = QSpacerItem(20, 391, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
248
265
 
249
266
  self.verticalLayout.addItem(self.verticalSpacer_4)
@@ -256,35 +273,35 @@ class Ui_prefDialog(object):
256
273
  self.splitter_2 = QSplitter(self.tab_analysis_plugins)
257
274
  self.splitter_2.setObjectName(u"splitter_2")
258
275
  self.splitter_2.setOrientation(Qt.Orientation.Horizontal)
259
- self.widget = QWidget(self.splitter_2)
260
- self.widget.setObjectName(u"widget")
261
- self.verticalLayout_11 = QVBoxLayout(self.widget)
276
+ self.layoutWidget = QWidget(self.splitter_2)
277
+ self.layoutWidget.setObjectName(u"layoutWidget")
278
+ self.verticalLayout_11 = QVBoxLayout(self.layoutWidget)
262
279
  self.verticalLayout_11.setObjectName(u"verticalLayout_11")
263
280
  self.verticalLayout_11.setContentsMargins(0, 0, 0, 0)
264
- self.label_13 = QLabel(self.widget)
281
+ self.label_13 = QLabel(self.layoutWidget)
265
282
  self.label_13.setObjectName(u"label_13")
266
283
 
267
284
  self.verticalLayout_11.addWidget(self.label_13)
268
285
 
269
- self.lv_all_plugins = QListWidget(self.widget)
286
+ self.lv_all_plugins = QListWidget(self.layoutWidget)
270
287
  self.lv_all_plugins.setObjectName(u"lv_all_plugins")
271
288
 
272
289
  self.verticalLayout_11.addWidget(self.lv_all_plugins)
273
290
 
274
- self.label_15 = QLabel(self.widget)
291
+ self.label_15 = QLabel(self.layoutWidget)
275
292
  self.label_15.setObjectName(u"label_15")
276
293
 
277
294
  self.verticalLayout_11.addWidget(self.label_15)
278
295
 
279
296
  self.horizontalLayout_16 = QHBoxLayout()
280
297
  self.horizontalLayout_16.setObjectName(u"horizontalLayout_16")
281
- self.le_personal_plugins_dir = QLineEdit(self.widget)
298
+ self.le_personal_plugins_dir = QLineEdit(self.layoutWidget)
282
299
  self.le_personal_plugins_dir.setObjectName(u"le_personal_plugins_dir")
283
300
  self.le_personal_plugins_dir.setReadOnly(True)
284
301
 
285
302
  self.horizontalLayout_16.addWidget(self.le_personal_plugins_dir)
286
303
 
287
- self.pb_browse_plugins_dir = QPushButton(self.widget)
304
+ self.pb_browse_plugins_dir = QPushButton(self.layoutWidget)
288
305
  self.pb_browse_plugins_dir.setObjectName(u"pb_browse_plugins_dir")
289
306
 
290
307
  self.horizontalLayout_16.addWidget(self.pb_browse_plugins_dir)
@@ -292,49 +309,49 @@ class Ui_prefDialog(object):
292
309
 
293
310
  self.verticalLayout_11.addLayout(self.horizontalLayout_16)
294
311
 
295
- self.lw_personal_plugins = QListWidget(self.widget)
312
+ self.lw_personal_plugins = QListWidget(self.layoutWidget)
296
313
  self.lw_personal_plugins.setObjectName(u"lw_personal_plugins")
297
314
 
298
315
  self.verticalLayout_11.addWidget(self.lw_personal_plugins)
299
316
 
300
- self.splitter_2.addWidget(self.widget)
317
+ self.splitter_2.addWidget(self.layoutWidget)
301
318
  self.splitter = QSplitter(self.splitter_2)
302
319
  self.splitter.setObjectName(u"splitter")
303
320
  self.splitter.setOrientation(Qt.Orientation.Vertical)
304
- self.widget1 = QWidget(self.splitter)
305
- self.widget1.setObjectName(u"widget1")
306
- self.verticalLayout_12 = QVBoxLayout(self.widget1)
321
+ self.layoutWidget1 = QWidget(self.splitter)
322
+ self.layoutWidget1.setObjectName(u"layoutWidget1")
323
+ self.verticalLayout_12 = QVBoxLayout(self.layoutWidget1)
307
324
  self.verticalLayout_12.setObjectName(u"verticalLayout_12")
308
325
  self.verticalLayout_12.setContentsMargins(0, 0, 0, 0)
309
- self.label_14 = QLabel(self.widget1)
326
+ self.label_14 = QLabel(self.layoutWidget1)
310
327
  self.label_14.setObjectName(u"label_14")
311
328
 
312
329
  self.verticalLayout_12.addWidget(self.label_14)
313
330
 
314
- self.pte_plugin_description = QPlainTextEdit(self.widget1)
331
+ self.pte_plugin_description = QPlainTextEdit(self.layoutWidget1)
315
332
  self.pte_plugin_description.setObjectName(u"pte_plugin_description")
316
333
  self.pte_plugin_description.setReadOnly(True)
317
334
 
318
335
  self.verticalLayout_12.addWidget(self.pte_plugin_description)
319
336
 
320
- self.splitter.addWidget(self.widget1)
321
- self.widget2 = QWidget(self.splitter)
322
- self.widget2.setObjectName(u"widget2")
323
- self.verticalLayout_14 = QVBoxLayout(self.widget2)
337
+ self.splitter.addWidget(self.layoutWidget1)
338
+ self.layoutWidget2 = QWidget(self.splitter)
339
+ self.layoutWidget2.setObjectName(u"layoutWidget2")
340
+ self.verticalLayout_14 = QVBoxLayout(self.layoutWidget2)
324
341
  self.verticalLayout_14.setObjectName(u"verticalLayout_14")
325
342
  self.verticalLayout_14.setContentsMargins(0, 0, 0, 0)
326
- self.label_23 = QLabel(self.widget2)
343
+ self.label_23 = QLabel(self.layoutWidget2)
327
344
  self.label_23.setObjectName(u"label_23")
328
345
 
329
346
  self.verticalLayout_14.addWidget(self.label_23)
330
347
 
331
- self.pte_plugin_code = QPlainTextEdit(self.widget2)
348
+ self.pte_plugin_code = QPlainTextEdit(self.layoutWidget2)
332
349
  self.pte_plugin_code.setObjectName(u"pte_plugin_code")
333
350
  self.pte_plugin_code.setLineWrapMode(QPlainTextEdit.LineWrapMode.NoWrap)
334
351
 
335
352
  self.verticalLayout_14.addWidget(self.pte_plugin_code)
336
353
 
337
- self.splitter.addWidget(self.widget2)
354
+ self.splitter.addWidget(self.layoutWidget2)
338
355
  self.splitter_2.addWidget(self.splitter)
339
356
 
340
357
  self.verticalLayout_15.addWidget(self.splitter_2)
@@ -676,7 +693,7 @@ class Ui_prefDialog(object):
676
693
 
677
694
  self.retranslateUi(prefDialog)
678
695
 
679
- self.tabWidget.setCurrentIndex(2)
696
+ self.tabWidget.setCurrentIndex(1)
680
697
 
681
698
 
682
699
  QMetaObject.connectSlotsByName(prefDialog)
@@ -707,6 +724,7 @@ class Ui_prefDialog(object):
707
724
  self.cbTrackingCursorAboveEvent.setText(QCoreApplication.translate("prefDialog", u"Tracking cursor above current event", None))
708
725
  self.cbAlertNoFocalSubject.setText(QCoreApplication.translate("prefDialog", u"Alert if focal subject is not set", None))
709
726
  self.cb_pause_before_addevent.setText(QCoreApplication.translate("prefDialog", u"Pause media before \"Add event\" command", None))
727
+ self.label_24.setText(QCoreApplication.translate("prefDialog", u"Frame step size", None))
710
728
  self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_observations), QCoreApplication.translate("prefDialog", u"Observations", None))
711
729
  self.label_13.setText(QCoreApplication.translate("prefDialog", u"BORIS plugins", None))
712
730
  self.label_15.setText(QCoreApplication.translate("prefDialog", u"Personal plugins", None))
@@ -1425,6 +1425,7 @@ def open_project_json(project_file_name: str) -> tuple:
1425
1425
  pj[cfg.OBSERVATIONS][obs][cfg.TYPE] = cfg.MEDIA
1426
1426
 
1427
1427
  # convert old media list in new one
1428
+ d1: dict = {}
1428
1429
  if len(pj[cfg.OBSERVATIONS][obs][cfg.FILE]):
1429
1430
  d1 = {cfg.PLAYER1: [pj[cfg.OBSERVATIONS][obs][cfg.FILE][0]]}
1430
1431
 
@@ -1470,6 +1471,13 @@ def open_project_json(project_file_name: str) -> tuple:
1470
1471
  pj[cfg.PROJECT_VERSION] = cfg.project_format_version
1471
1472
  projectChanged = True
1472
1473
 
1474
+ # check if behavioral categories are stored as a list
1475
+ if isinstance(pj[cfg.BEHAVIORAL_CATEGORIES_CONF], list):
1476
+ # convert to dict
1477
+ pj[cfg.BEHAVIORAL_CATEGORIES_CONF] = {str(idx): {"name": bc} for idx, bc in enumerate(pj[cfg.BEHAVIORAL_CATEGORIES_CONF])}
1478
+ logging.info("Behavioral categories was converted from a list to a dictionary")
1479
+ projectChanged = True
1480
+
1473
1481
  # add category key if not found
1474
1482
  for idx in pj[cfg.ETHOGRAM]:
1475
1483
  if cfg.BEHAVIOR_CATEGORY not in pj[cfg.ETHOGRAM][idx]:
boris/version.py CHANGED
@@ -20,5 +20,5 @@ This file is part of BORIS.
20
20
 
21
21
  """
22
22
 
23
- __version__ = "9.7.1"
24
- __version_date__ = "2025-10-22"
23
+ __version__ = "9.7.3"
24
+ __version_date__ = "2025-11-05"
boris/video_operations.py CHANGED
@@ -231,6 +231,10 @@ def display_zoom_level(self) -> None:
231
231
  """
232
232
  msg: str = "Zoom level: <b>"
233
233
  for player in self.dw_player:
234
+ vz = player.player.video_zoom
235
+ if vz is None:
236
+ self.lb_zoom_level.setText("-")
237
+ return
234
238
  msg += f"{2**player.player.video_zoom:.1f} "
235
239
  msg += "</b>"
236
240
  self.lb_zoom_level.setText(msg)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boris-behav-obs
3
- Version: 9.7.1
3
+ Version: 9.7.3
4
4
  Summary: BORIS - Behavioral Observation Research Interactive Software
5
5
  Author-email: Olivier Friard <olivier.friard@unito.it>
6
6
  License-Expression: GPL-3.0-only
@@ -44,15 +44,14 @@ BORIS (Behavioral Observation Research Interactive Software)
44
44
 
45
45
  BORIS is an easy-to-use event logging software for video/audio coding or live observations.
46
46
 
47
- BORIS is a free and open-source software available for GNU/Linux and Windows.
48
- 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).
47
+ BORIS is a free and open-source software available for GNU/Linux, Windows and macOS.
49
48
 
50
49
  It provides also some analysis tools like time budget and some plotting functions.
51
50
 
52
51
  <!-- The BO-RIS paper has more than [![BORIS citations counter](https://penelope.unito.it/friard/boris_scopus_citations.png) citations](https://www.boris.unito.it/citations) in peer-reviewed scientific publications. -->
53
52
 
54
53
 
55
- The BORIS paper has more than 2407 citations in peer-reviewed scientific publications.
54
+ The BORIS paper has more than 2408 citations in peer-reviewed scientific publications.
56
55
 
57
56
 
58
57
 
@@ -1,6 +1,6 @@
1
1
  boris/__init__.py,sha256=iAtmVMy22TJpMmxVTMSK_6-wXnCbx1ogvWgfYEcbHzU,773
2
2
  boris/__main__.py,sha256=ANjTbXgXDoz2nB1tCtOIllfIVotCa602iebACX7rXaE,764
3
- boris/about.py,sha256=VPa8zeu0bMb1LRXDq8uUSG_7mSbkb2HTk1AtWbzWQwE,5366
3
+ boris/about.py,sha256=RrK4fUbMUpyMWPZVmFYG91pVx_orvaLPw-OBWI_NcOg,5401
4
4
  boris/add_modifier.py,sha256=l9LSa_9FAV9CnBgm26tJqhMAdnFoBQafZLyt9pTKmac,26240
5
5
  boris/add_modifier_ui.py,sha256=Y7TLO5uS6zW7zpjXmjA4V_VIp_bFDNtjOTbJ9Q6m-mQ,11601
6
6
  boris/advanced_event_filtering.py,sha256=VlvU12mL6xYacZOvJAi5uLpHMcmAw5Pvuvmka-PN29c,15469
@@ -10,13 +10,13 @@ boris/behaviors_coding_map.py,sha256=xIGJxp2eghrpiGDmYH73eJPERuyc4A_54uT-Got3zTs
10
10
  boris/boris_cli.py,sha256=Bc51DEMcD79ZZfM9pCzpaWU6iT6b8gNwR3n8fr42_4E,13193
11
11
  boris/cmd_arguments.py,sha256=85zDEIW6UiXRb0TQWDxR664pQQtG5J-7Ipv91y3JixQ,2065
12
12
  boris/coding_pad.py,sha256=BaDWYIzoRgl0LHymPDmcBMFfwG7Z0XROqqMwkkADtz0,10940
13
- boris/config.py,sha256=QOPwbfxs6g0yvYtS5pQCzaap_YG-_xXQH2E-F3ZXO4M,17848
14
- boris/config_file.py,sha256=5_AB_VE5j3iHkanL5xELN42HJJMLOh40qSgBFs7fCXo,13493
13
+ boris/config.py,sha256=xQXRzSbFLS9rYnJwx6jNrBIZ16XGFbfP2_43wKqPwyM,18057
14
+ boris/config_file.py,sha256=4jBGMaVzjoVKSeOBEHXVjbsyubN08ghQTsrm4s7Kqsw,13551
15
15
  boris/connections.py,sha256=KsC17LnS4tRM6O3Nu3mD1H9kQ7uYhhad9229jXfGF94,19774
16
16
  boris/converters.py,sha256=n6gDM9x2hS-ZOoHLruiifuXxnC7ERsUukiFokhHZPoQ,11678
17
17
  boris/converters_ui.py,sha256=uu7LOBV_fKv2DBdOqsqPwjGsjgONr5ODBoscAA-EP48,9900
18
18
  boris/cooccurence.py,sha256=tVERC-V8MWjWHlGEfDuu08iS94qjt4do-38jwI62QaY,10367
19
- boris/core.py,sha256=jvWMOPszPONYn2bMp2sV0nGpszbKPVZ1NYQ5PsU3msw,232311
19
+ boris/core.py,sha256=0l6wXCn88zfFBTqKx4fw4548i-hTwVi-JDzM3jE7ED0,232445
20
20
  boris/core_qrc.py,sha256=Hz51Xw70ZIlDbYB281nfGtCm43_ItYhamMu2T5X8Tu8,639882
21
21
  boris/core_ui.py,sha256=uDAI9mbadBe2mSsgugzNfRHxASc6Mu5kUf5tB9CZCjY,77445
22
22
  boris/db_functions.py,sha256=TfCJ0Hq0pTFOKrZz3RzdvnR-NKCmrPHU2qL9BSXeeGQ,13379
@@ -36,7 +36,7 @@ boris/geometric_measurement.py,sha256=4pI-AYpBSFlJBqS-f8dnkgLtj_Z2E5kwwAdh6WwZ4k
36
36
  boris/gui_utilities.py,sha256=Kv75XgFmicPUKvdRPj_yBYoDNc912cfl1IQrgw5T2kI,5458
37
37
  boris/image_overlay.py,sha256=zZAL8MTt2i2s58CuX81Nym3rJ5pKiTeP4AO8WbIUonM,2527
38
38
  boris/import_observations.py,sha256=zKrkpk1ADxTj2BECactPPOhU6wtrh3TjtOWue2HCT5w,9074
39
- boris/ipc_mpv.py,sha256=12x1Nthp4nsYsS39fbZK9B0Jl4hCrlS2wszg680wduQ,9222
39
+ boris/ipc_mpv.py,sha256=Hb-RglR4UV1JcgK_jxKNUw0HZA4x2z_tfD7fR1gxFBY,8432
40
40
  boris/irr.py,sha256=n6Y_Y9iEKOf9_7EE_lDRNei7tq2wkFKk_JVflm9UQdk,22335
41
41
  boris/latency.py,sha256=48z9L_A582-wKCfD0M3h0uyYkeL2ezjlQAS_GzeoOe0,9739
42
42
  boris/measurement_widget.py,sha256=lZV62KtK6TjdoNbKxj3uyNAuL5dfnQnn7mYwzMo-dOM,4480
@@ -47,7 +47,7 @@ boris/modifiers_coding_map.py,sha256=oT56ZY_PXhEJsMoblEsyNMAPbDpv7ZMOCnvmt7Ibx_Y
47
47
  boris/mpv.py,sha256=EfzIHjPbgewG4w3smEtqEUPZoVwYmMQkL4Q8ZyW-a58,76410
48
48
  boris/mpv2.py,sha256=IUI4t4r9GYX7G5OXTjd3RhMMOkDdfal_15buBgksLsk,92152
49
49
  boris/observation.py,sha256=10UkVyY8TDySntIX_-H-IsuFdiF6tEcmC6JQUzD6wYg,57139
50
- boris/observation_operations.py,sha256=mOx3dU8x6LQF4TBVhyyWBo4yyonpvPMyaLoHLdlmrrc,105716
50
+ boris/observation_operations.py,sha256=PdP9K6A7z1EoOhuBQzBJo-X2vM1vJavGGgtUNBeKJ9o,105712
51
51
  boris/observation_ui.py,sha256=DAnU94QBNvkLuHT6AxTwqSk_D_n6VUhSl8PexZv_dUk,33309
52
52
  boris/observations_list.py,sha256=NqwECGHtHYmKhSe-qCfqPmJ24SSfzlXvIXS2i3op_zE,10591
53
53
  boris/otx_parser.py,sha256=70QvilzFHXbjAHR88YH0aEXJ3xxheLS5fZGgHFHGpNE,16367
@@ -60,10 +60,10 @@ boris/plot_events_rt.py,sha256=xJmjwqhQxCN4FDBYRQ0O2eHm356Rbexzr3m1qTefMDU,8326
60
60
  boris/plot_spectrogram_rt.py,sha256=wDhnkqwjd2UfCxrfOejOUxoNOqfMNo6vo1JSvYgM-2A,10925
61
61
  boris/plot_waveform_rt.py,sha256=RNXhcBzRKnoGoVjRAHsVvOaj0BJbbI2cpCMjMUiGqX0,7534
62
62
  boris/plugins.py,sha256=sn2r8kMxkzaO8kNhem-cTlTBrym9MlFPyRT9Av9rHGg,15603
63
- boris/preferences.py,sha256=-WwBlkP3uhSyO3tUhcrctHMzlv46ngW9C4KAq7UJAoI,21563
64
- boris/preferences_ui.py,sha256=wbo51aBNdcQTJni1DmUM5ZQPOwAtKSkEQam7rRzRS5g,34166
63
+ boris/preferences.py,sha256=fjyoBKWsKoRq7YKqE_BzzSSPENPwd7ZqL_HY2bqd1jA,21846
64
+ boris/preferences_ui.py,sha256=zkmbQbkb0WqhPyMtnU-DU9Y2enSph_r-LnwsmEOgzkk,35090
65
65
  boris/project.py,sha256=nyXfCDY_rLP3jC1QGv-280jUKgbABqESjOm7I19rJ1U,86432
66
- boris/project_functions.py,sha256=gSG6Pa0BF0wv5AtfvRU8KqOqzgUwbMTmIsYqQ11QwmY,82605
66
+ boris/project_functions.py,sha256=Z4e8mLeFUBOdZHYTQhTqzj-s0ppiUgOFkpjsQtkefVA,83026
67
67
  boris/project_import_export.py,sha256=oBG1CSXfKISsb3TLNT-8BH8kZPAzxIYSNemlLVH1Lh8,38560
68
68
  boris/project_ui.py,sha256=yB-ewhHt8S8DTTRIk-dNK2tPMNU24lNji9fDW_Xazu8,38805
69
69
  boris/qrc_boris.py,sha256=aH-qUirYY1CGxmTK1SFCPvuZfazIHX4DdUKF1gxZeYM,675008
@@ -78,10 +78,10 @@ boris/time_budget_functions.py,sha256=SbGyTF2xySEqBdRJZeWFTirruvK3r6pwM6e4Gz17W1
78
78
  boris/time_budget_widget.py,sha256=z-tyITBtIz-KH1H2OdMB5a8x9QQLK7Wu96-zkC6NVDA,43213
79
79
  boris/transitions.py,sha256=okyDCO-Vn4p_Fixd8cGiSIaUhUxG5ePIOqGSuP52g_c,12246
80
80
  boris/utilities.py,sha256=BJXl8IZHQC93ZcaN9cQrWmrg3Rr3F8_Q71Tz6Im3A0s,58104
81
- boris/version.py,sha256=efemsbkEaALtHXkb-K5yR_XYwDyuK_FjgmaTU7oEGu0,787
81
+ boris/version.py,sha256=MqUY71OLsiY3VknHGempTDu88Un1Zm9auFmJtTN7UGQ,787
82
82
  boris/video_equalizer.py,sha256=cm2JXe1eAPkqDzxYB2OMKyuveMBdq4a9-gD1bBorbn4,5823
83
83
  boris/video_equalizer_ui.py,sha256=1CG3s79eM4JAbaCx3i1ILZXLceb41_gGXlOLNfpBgnw,10142
84
- boris/video_operations.py,sha256=BbH9NXfNL597edX2nX0s6dJcQIvv--_DXx2kjhLAkfo,10799
84
+ boris/video_operations.py,sha256=i38CTLpD1WbkQr4WA7Mj5_47ouqDvcVgPgZ6wUyggwA,10923
85
85
  boris/view_df.py,sha256=fhcNMFFVbQ4ZTpPwN_4Bg66DDIoV25zaPryZUsOxsKg,3338
86
86
  boris/view_df_ui.py,sha256=CaMeRH_vQ00CTDDFQn73ZZaS-r8BSTWpL-dMCFqzJ_Q,2775
87
87
  boris/write_event.py,sha256=xC-dUjgPS4-tvrQfvyL7O_xi-tyQI7leSiSV8_x8hgg,23817
@@ -101,9 +101,9 @@ boris/portion/dict.py,sha256=uNM-LEY52CZ2VNMMW_C9QukoyTvPlQf8vcbGa1lQBHI,11281
101
101
  boris/portion/func.py,sha256=mSQr20YS1ug7R1fRqBg8LifjtXDRvJ6Kjc3WOeL9P34,2172
102
102
  boris/portion/interval.py,sha256=sOlj3MAGGaB-JxCkigS-n3qw0fY7TANAsXv1pavr8J4,19931
103
103
  boris/portion/io.py,sha256=kpq44pw3xnIyAlPwaR5qRHKRdZ72f8HS9YVIWs5k2pk,6367
104
- boris_behav_obs-9.7.1.dist-info/licenses/LICENSE.TXT,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
105
- boris_behav_obs-9.7.1.dist-info/METADATA,sha256=nvmFlkit_eIH_OOWnrlrZZvrPaO-NXD-8aU778Da0J4,5385
106
- boris_behav_obs-9.7.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
107
- boris_behav_obs-9.7.1.dist-info/entry_points.txt,sha256=k__8XvFi4vaA4QFvQehCZjYkKmZH34HSAJI2iYCWrMs,52
108
- boris_behav_obs-9.7.1.dist-info/top_level.txt,sha256=fJSgm62S7WesiwTorGbOO4nNN0yzgZ3klgfGi3Px4qI,6
109
- boris_behav_obs-9.7.1.dist-info/RECORD,,
104
+ boris_behav_obs-9.7.3.dist-info/licenses/LICENSE.TXT,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
105
+ boris_behav_obs-9.7.3.dist-info/METADATA,sha256=GG7qKHLtzrifFsHDgVkeDJA3qLCU-iRTM3WWlse9y5I,5203
106
+ boris_behav_obs-9.7.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
107
+ boris_behav_obs-9.7.3.dist-info/entry_points.txt,sha256=k__8XvFi4vaA4QFvQehCZjYkKmZH34HSAJI2iYCWrMs,52
108
+ boris_behav_obs-9.7.3.dist-info/top_level.txt,sha256=fJSgm62S7WesiwTorGbOO4nNN0yzgZ3klgfGi3Px4qI,6
109
+ boris_behav_obs-9.7.3.dist-info/RECORD,,