boris-behav-obs 9.7.6__py3-none-any.whl → 9.7.8__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/add_modifier_ui.py CHANGED
@@ -3,7 +3,7 @@
3
3
  ################################################################################
4
4
  ## Form generated from reading UI file 'add_modifier.ui'
5
5
  ##
6
- ## Created by: Qt User Interface Compiler version 6.8.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
  ################################################################################
@@ -24,18 +24,16 @@ class Ui_Dialog(object):
24
24
  def setupUi(self, Dialog):
25
25
  if not Dialog.objectName():
26
26
  Dialog.setObjectName(u"Dialog")
27
- Dialog.resize(1088, 654)
28
- self.verticalLayout_5 = QVBoxLayout(Dialog)
29
- self.verticalLayout_5.setObjectName(u"verticalLayout_5")
27
+ Dialog.resize(1339, 789)
28
+ self.verticalLayout_4 = QVBoxLayout(Dialog)
29
+ self.verticalLayout_4.setObjectName(u"verticalLayout_4")
30
30
  self.cb_ask_at_stop = QCheckBox(Dialog)
31
31
  self.cb_ask_at_stop.setObjectName(u"cb_ask_at_stop")
32
32
 
33
- self.verticalLayout_5.addWidget(self.cb_ask_at_stop)
33
+ self.verticalLayout_4.addWidget(self.cb_ask_at_stop)
34
34
 
35
- self.verticalLayout_4 = QVBoxLayout()
36
- self.verticalLayout_4.setObjectName(u"verticalLayout_4")
37
- self.horizontalLayout_5 = QHBoxLayout()
38
- self.horizontalLayout_5.setObjectName(u"horizontalLayout_5")
35
+ self.horizontalLayout_8 = QHBoxLayout()
36
+ self.horizontalLayout_8.setObjectName(u"horizontalLayout_8")
39
37
  self.verticalLayout_2 = QVBoxLayout()
40
38
  self.verticalLayout_2.setObjectName(u"verticalLayout_2")
41
39
  self.lbModifier = QLabel(Dialog)
@@ -69,7 +67,7 @@ class Ui_Dialog(object):
69
67
  self.verticalLayout_2.addItem(self.verticalSpacer)
70
68
 
71
69
 
72
- self.horizontalLayout_5.addLayout(self.verticalLayout_2)
70
+ self.horizontalLayout_8.addLayout(self.verticalLayout_2)
73
71
 
74
72
  self.verticalLayout_3 = QVBoxLayout()
75
73
  self.verticalLayout_3.setObjectName(u"verticalLayout_3")
@@ -88,7 +86,7 @@ class Ui_Dialog(object):
88
86
  self.verticalLayout_3.addItem(self.verticalSpacer_2)
89
87
 
90
88
 
91
- self.horizontalLayout_5.addLayout(self.verticalLayout_3)
89
+ self.horizontalLayout_8.addLayout(self.verticalLayout_3)
92
90
 
93
91
  self.verticalLayout = QVBoxLayout()
94
92
  self.verticalLayout.setObjectName(u"verticalLayout")
@@ -102,30 +100,42 @@ class Ui_Dialog(object):
102
100
 
103
101
  self.verticalLayout.addWidget(self.tabWidgetModifiersSets)
104
102
 
103
+ self.horizontalLayout_5 = QHBoxLayout()
104
+ self.horizontalLayout_5.setObjectName(u"horizontalLayout_5")
105
105
  self.lb_name = QLabel(Dialog)
106
106
  self.lb_name.setObjectName(u"lb_name")
107
107
 
108
- self.verticalLayout.addWidget(self.lb_name)
108
+ self.horizontalLayout_5.addWidget(self.lb_name)
109
109
 
110
110
  self.le_name = QLineEdit(Dialog)
111
111
  self.le_name.setObjectName(u"le_name")
112
112
 
113
- self.verticalLayout.addWidget(self.le_name)
113
+ self.horizontalLayout_5.addWidget(self.le_name)
114
114
 
115
+
116
+ self.verticalLayout.addLayout(self.horizontalLayout_5)
117
+
118
+ self.horizontalLayout_6 = QHBoxLayout()
119
+ self.horizontalLayout_6.setObjectName(u"horizontalLayout_6")
115
120
  self.lb_description = QLabel(Dialog)
116
121
  self.lb_description.setObjectName(u"lb_description")
117
122
 
118
- self.verticalLayout.addWidget(self.lb_description)
123
+ self.horizontalLayout_6.addWidget(self.lb_description)
119
124
 
120
125
  self.le_description = QLineEdit(Dialog)
121
126
  self.le_description.setObjectName(u"le_description")
122
127
 
123
- self.verticalLayout.addWidget(self.le_description)
128
+ self.horizontalLayout_6.addWidget(self.le_description)
129
+
124
130
 
131
+ self.verticalLayout.addLayout(self.horizontalLayout_6)
132
+
133
+ self.horizontalLayout_7 = QHBoxLayout()
134
+ self.horizontalLayout_7.setObjectName(u"horizontalLayout_7")
125
135
  self.lbType = QLabel(Dialog)
126
136
  self.lbType.setObjectName(u"lbType")
127
137
 
128
- self.verticalLayout.addWidget(self.lbType)
138
+ self.horizontalLayout_7.addWidget(self.lbType)
129
139
 
130
140
  self.cbType = QComboBox(Dialog)
131
141
  self.cbType.addItem("")
@@ -134,7 +144,14 @@ class Ui_Dialog(object):
134
144
  self.cbType.addItem("")
135
145
  self.cbType.setObjectName(u"cbType")
136
146
 
137
- self.verticalLayout.addWidget(self.cbType)
147
+ self.horizontalLayout_7.addWidget(self.cbType)
148
+
149
+ self.horizontalSpacer_3 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
150
+
151
+ self.horizontalLayout_7.addItem(self.horizontalSpacer_3)
152
+
153
+
154
+ self.verticalLayout.addLayout(self.horizontalLayout_7)
138
155
 
139
156
  self.lbValues = QLabel(Dialog)
140
157
  self.lbValues.setObjectName(u"lbValues")
@@ -198,28 +215,32 @@ class Ui_Dialog(object):
198
215
 
199
216
  self.horizontalLayout_4 = QHBoxLayout()
200
217
  self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
201
-
202
- self.verticalLayout.addLayout(self.horizontalLayout_4)
203
-
204
218
  self.pb_add_subjects = QPushButton(Dialog)
205
219
  self.pb_add_subjects.setObjectName(u"pb_add_subjects")
206
220
 
207
- self.verticalLayout.addWidget(self.pb_add_subjects)
221
+ self.horizontalLayout_4.addWidget(self.pb_add_subjects)
208
222
 
209
223
  self.pb_load_file = QPushButton(Dialog)
210
224
  self.pb_load_file.setObjectName(u"pb_load_file")
211
225
 
212
- self.verticalLayout.addWidget(self.pb_load_file)
226
+ self.horizontalLayout_4.addWidget(self.pb_load_file)
227
+
228
+ self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
229
+
230
+ self.horizontalLayout_4.addItem(self.horizontalSpacer_2)
231
+
232
+
233
+ self.verticalLayout.addLayout(self.horizontalLayout_4)
213
234
 
214
235
  self.verticalSpacer_3 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
215
236
 
216
237
  self.verticalLayout.addItem(self.verticalSpacer_3)
217
238
 
218
239
 
219
- self.horizontalLayout_5.addLayout(self.verticalLayout)
240
+ self.horizontalLayout_8.addLayout(self.verticalLayout)
220
241
 
221
242
 
222
- self.verticalLayout_4.addLayout(self.horizontalLayout_5)
243
+ self.verticalLayout_4.addLayout(self.horizontalLayout_8)
223
244
 
224
245
  self.horizontalLayout_2 = QHBoxLayout()
225
246
  self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
@@ -241,9 +262,6 @@ class Ui_Dialog(object):
241
262
  self.verticalLayout_4.addLayout(self.horizontalLayout_2)
242
263
 
243
264
 
244
- self.verticalLayout_5.addLayout(self.verticalLayout_4)
245
-
246
-
247
265
  self.retranslateUi(Dialog)
248
266
 
249
267
  self.tabWidgetModifiersSets.setCurrentIndex(-1)
@@ -256,8 +274,8 @@ class Ui_Dialog(object):
256
274
  Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"Set modifiers", None))
257
275
  self.cb_ask_at_stop.setText(QCoreApplication.translate("Dialog", u"Ask for modifier(s) when behavior stops", None))
258
276
  self.lbModifier.setText(QCoreApplication.translate("Dialog", u"Modifier", None))
259
- self.lbCode.setText(QCoreApplication.translate("Dialog", u"Key code", None))
260
- self.lbCodeHelp.setText(QCoreApplication.translate("Dialog", u"Key code is case sensitive. Type one character or a function key (F1, F2... F12)", None))
277
+ self.lbCode.setText(QCoreApplication.translate("Dialog", u"Shortcut", None))
278
+ self.lbCodeHelp.setText(QCoreApplication.translate("Dialog", u"The shortcut is case sensitive. Type one character or a function key (F1, F2... F12)", None))
261
279
  self.pbAddModifier.setText(QCoreApplication.translate("Dialog", u"->", None))
262
280
  self.pbModifyModifier.setText(QCoreApplication.translate("Dialog", u"<-", None))
263
281
  self.lb_name.setText(QCoreApplication.translate("Dialog", u"Set name", None))
boris/core.py CHANGED
@@ -41,6 +41,7 @@ from PIL.ImageQt import Image
41
41
  import subprocess
42
42
  import locale
43
43
  import tempfile
44
+ import math
44
45
  import time
45
46
  import urllib.request
46
47
  from decimal import Decimal as dec
@@ -53,18 +54,7 @@ import shutil
53
54
 
54
55
  matplotlib.use("QtAgg")
55
56
 
56
- from PySide6.QtCore import (
57
- Qt,
58
- QPoint,
59
- Signal,
60
- QEvent,
61
- QDateTime,
62
- QUrl,
63
- QAbstractTableModel,
64
- QElapsedTimer,
65
- QSettings,
66
- QTimer
67
- )
57
+ from PySide6.QtCore import Qt, QPoint, Signal, QEvent, QDateTime, QUrl, QAbstractTableModel, QElapsedTimer, QSettings, QTimer
68
58
  from PySide6.QtGui import QIcon, QPixmap, QFont, QKeyEvent, QDesktopServices, QColor, QPainter, QPolygon, QAction
69
59
  from PySide6.QtMultimedia import QSoundEffect
70
60
  from PySide6.QtWidgets import (
@@ -319,7 +309,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
319
309
  data: list = []
320
310
  not_paired: list = []
321
311
 
322
-
323
312
  '''
324
313
  def add_button_menu(self, data, menu_obj):
325
314
  """
@@ -1511,15 +1500,17 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1511
1500
  logging.debug("previous media file")
1512
1501
 
1513
1502
  if self.playerType == cfg.MEDIA:
1514
- if len(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][cfg.PLAYER1]) == 1:
1515
- return
1503
+ #if len(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][cfg.PLAYER1]) == 1:
1504
+ # self.seek_mediaplayer(dec(0))
1505
+ # return
1516
1506
 
1517
1507
  # check if media not first media
1518
1508
  if self.dw_player[0].player.playlist_pos > 0:
1519
1509
  self.dw_player[0].player.playlist_prev()
1520
1510
 
1521
1511
  elif self.dw_player[0].player.playlist_count == 1:
1522
- self.statusbar.showMessage("There is only one media file", 5000)
1512
+ self.seek_mediaplayer(dec(0))
1513
+ #self.statusbar.showMessage("There is only one media file", 5000)
1523
1514
 
1524
1515
  if hasattr(self, "spectro"):
1525
1516
  self.spectro.memChunk = -1
@@ -4154,6 +4145,21 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4154
4145
  self.dw_player[n_player].player.playlist_pos = self.dw_player[n_player].player.playlist_count - 1
4155
4146
  self.seek_mediaplayer(self.dw_player[n_player].media_durations[-1], player=n_player)
4156
4147
 
4148
+ def activate_main_window(self):
4149
+ """
4150
+ activate main window in order to capture keyboard events
4151
+ called only in IPC mode
4152
+ """
4153
+ # check if eof reached
4154
+ #print(f"{self.dw_player[0].player.playlist_pos=}")
4155
+ #print(f"{self.dw_player[0].player.playlist_count=}")
4156
+ if self.dw_player[0].player.eof_reached and self.dw_player[0].player.core_idle:
4157
+ logging.debug("end of playlist reached")
4158
+ if self.dw_player[0].player.playlist_pos is not None and self.dw_player[0].player.playlist_count is not None:
4159
+ if self.dw_player[0].player.playlist_pos == self.dw_player[0].player.playlist_count - 1:
4160
+ self.pause_video()
4161
+ self.activateWindow()
4162
+
4157
4163
  def mpv_timer_out(self, value: float | None = None, scroll_slider=True):
4158
4164
  """
4159
4165
  print the media current position and total length for MPV player
@@ -4213,8 +4219,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4213
4219
  ct = self.getLaps(n_player=n_player)
4214
4220
 
4215
4221
  # sync players 2..8 if time diff >= 1 s
4216
- if abs(ct0 - (ct + dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)]))) >= 1:
4217
- self.sync_time(n_player, ct0) # self.seek_mediaplayer(ct0, n_player)
4222
+ if not math.isnan(ct) and not math.isnan(ct0):
4223
+ if abs(ct0 - (ct + dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)]))) >= 1:
4224
+ self.sync_time(n_player, ct0) # self.seek_mediaplayer(ct0, n_player)
4218
4225
 
4219
4226
  currentTimeOffset = dec(cumulative_time_pos + self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TIME_OFFSET])
4220
4227
 
@@ -4245,9 +4252,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4245
4252
  self.show_current_states_in_subjects_table()
4246
4253
 
4247
4254
  # current media name
4248
- if self.dw_player[0].player.playlist_pos is not None:
4249
- current_media_name = Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"]).name
4250
- current_playlist_index = self.dw_player[0].player.playlist_pos
4255
+ playlist = self.dw_player[0].player.playlist
4256
+ playlist_pos = self.dw_player[0].player.playlist_pos
4257
+ if playlist is not None and playlist_pos is not None:
4258
+ current_media_name = Path(playlist[playlist_pos]["filename"]).name
4259
+ current_playlist_index = playlist_pos
4251
4260
  else:
4252
4261
  current_media_name = ""
4253
4262
  current_playlist_index = None
@@ -4605,6 +4614,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4605
4614
 
4606
4615
  if self.playerType == cfg.MEDIA:
4607
4616
  # cumulative time
4617
+ if self.dw_player[n_player].player.time_pos is None:
4618
+ return dec("NaN")
4608
4619
  mem_laps = sum(self.dw_player[n_player].media_durations[0 : self.dw_player[n_player].player.playlist_pos]) + (
4609
4620
  0 if self.dw_player[n_player].player.time_pos is None else self.dw_player[n_player].player.time_pos * 1000
4610
4621
  )
@@ -4624,7 +4635,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4624
4635
  """
4625
4636
 
4626
4637
  if not self.observationId:
4627
- return dec("0")
4638
+ return dec("0"), dec("0")
4628
4639
 
4629
4640
  if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.LIVE:
4630
4641
  if "finished" in self.pb_live_obs.text():
@@ -5468,6 +5479,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5468
5479
  if self.playerType != cfg.MEDIA:
5469
5480
  return
5470
5481
 
5482
+ # check if playlist is ended. If it is ended restart playlist from beginning
5483
+ if self.dw_player[0].player.eof_reached and self.dw_player[0].player.core_idle:
5484
+ if self.dw_player[0].player.playlist_pos is not None and self.dw_player[0].player.playlist_count is not None:
5485
+ if self.dw_player[0].player.playlist_pos == self.dw_player[0].player.playlist_count - 1:
5486
+ self.seek_mediaplayer(dec(0))
5487
+
5488
+
5471
5489
  # check if player 1 is ended
5472
5490
  for i, dw in enumerate(self.dw_player):
5473
5491
  if (
@@ -5478,9 +5496,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5478
5496
 
5479
5497
  self.lb_player_status.clear()
5480
5498
 
5481
- # if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.VISUALIZE_WAVEFORM, False) \
5482
- # or self.pj[cfg.OBSERVATIONS][self.observationId].get(VISUALIZE_SPECTROGRAM, False):
5483
-
5484
5499
  self.statusbar.showMessage("", 0)
5485
5500
 
5486
5501
  if self.ipc_mpv_timer is not None:
@@ -5557,16 +5572,19 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5557
5572
 
5558
5573
  decrement = self.fast * self.play_rate if self.config_param.get(cfg.ADAPT_FAST_JUMP, cfg.ADAPT_FAST_JUMP_DEFAULT) else self.fast
5559
5574
 
5560
- new_time = (
5561
- sum(self.dw_player[0].media_durations[0 : self.dw_player[0].player.playlist_pos]) / 1000
5562
- + self.dw_player[0].player.playback_time
5563
- - decrement
5564
- )
5575
+ try:
5576
+ new_time = (
5577
+ sum(self.dw_player[0].media_durations[0 : self.dw_player[0].player.playlist_pos]) / 1000
5578
+ + self.dw_player[0].player.playback_time
5579
+ - decrement
5580
+ )
5581
+ except Exception:
5582
+ return
5565
5583
 
5566
5584
  if new_time < decrement:
5567
5585
  new_time = 0
5568
5586
 
5569
- self.seek_mediaplayer(new_time)
5587
+ self.seek_mediaplayer(dec(new_time))
5570
5588
 
5571
5589
  self.update_visualizations()
5572
5590
 
@@ -5580,13 +5598,16 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5580
5598
 
5581
5599
  logging.info(f"Jump forward for {increment} seconds")
5582
5600
 
5583
- new_time = (
5584
- sum(self.dw_player[0].media_durations[0 : self.dw_player[0].player.playlist_pos]) / 1000
5585
- + self.dw_player[0].player.playback_time
5586
- + increment
5587
- )
5601
+ try:
5602
+ new_time = (
5603
+ sum(self.dw_player[0].media_durations[0 : self.dw_player[0].player.playlist_pos]) / 1000
5604
+ + self.dw_player[0].player.playback_time
5605
+ + increment
5606
+ )
5607
+ except Exception:
5608
+ return
5588
5609
 
5589
- self.seek_mediaplayer(new_time)
5610
+ self.seek_mediaplayer(dec(new_time))
5590
5611
 
5591
5612
  self.update_visualizations()
5592
5613
 
@@ -5844,12 +5865,12 @@ def main():
5844
5865
 
5845
5866
  if sys.platform.startswith("darwin"):
5846
5867
  QMessageBox.warning(
5847
- None,
5848
- cfg.programName,
5849
- (f"This version of BORIS for macOS is still EXPERIMENTAL and should be used at your own risk."),
5850
- QMessageBox.Ok | QMessageBox.Default,
5851
- QMessageBox.NoButton,
5852
- )
5868
+ None,
5869
+ cfg.programName,
5870
+ (f"This version of BORIS for macOS is still EXPERIMENTAL and should be used at your own risk."),
5871
+ QMessageBox.Ok | QMessageBox.Default,
5872
+ QMessageBox.NoButton,
5873
+ )
5853
5874
 
5854
5875
  window.show()
5855
5876
  window.raise_() # for overlapping widget (?)
@@ -5877,3 +5898,4 @@ def main():
5877
5898
  del window
5878
5899
 
5879
5900
  sys.exit(return_code)
5901
+
boris/ipc_mpv.py CHANGED
@@ -62,7 +62,8 @@ class IPC_MPV:
62
62
  "--osc=no", # no on screen commands
63
63
  "--input-ipc-server=" + self.socket_path,
64
64
  # "--wid=" + str(int(self.winId())), # Embed in the widget
65
- "--idle", # Keeps mpv running with no video
65
+ "--idle=yes", # Keeps mpv running with no video
66
+ "--keep-open=always",
66
67
  "--input-default-bindings=no",
67
68
  "--input-vo-keyboard=no",
68
69
  ],
@@ -312,7 +313,6 @@ class IPC_MPV:
312
313
  self.send_command({"command": ["set_property", "video-pan-y", value]})
313
314
  return
314
315
 
315
-
316
316
  """
317
317
  @property
318
318
  def xxx(self):
@@ -1856,7 +1856,8 @@ def initialize_new_media_observation(self) -> bool:
1856
1856
  # start timer for activating the main window
1857
1857
  self.main_window_activation_timer = QTimer()
1858
1858
  self.main_window_activation_timer.setInterval(500)
1859
- self.main_window_activation_timer.timeout.connect(self.activateWindow)
1859
+ #self.main_window_activation_timer.timeout.connect(self.activateWindow)
1860
+ self.main_window_activation_timer.timeout.connect(self.activate_main_window)
1860
1861
  self.main_window_activation_timer.start()
1861
1862
 
1862
1863
 
@@ -422,9 +422,12 @@ def check_project_integrity(
422
422
  out: str = ""
423
423
 
424
424
  # check if coded behaviors are defined in ethogram
425
- r = check_coded_behaviors(pj)
425
+ r = check_coded_behaviors(pj)
426
426
  if r:
427
427
  out += f"The following behaviors are not defined in the ethogram: <b>{', '.join(r)}</b><br>"
428
+ flag_all_behaviors_defined = False
429
+ else:
430
+ flag_all_behaviors_defined = True
428
431
 
429
432
  # check for unpaired state events
430
433
  for obs_id in pj[cfg.OBSERVATIONS]:
@@ -577,26 +580,27 @@ def check_project_integrity(
577
580
  obs_results: dict = {}
578
581
  for obs_id in pj[cfg.OBSERVATIONS]:
579
582
  for event_idx, event in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]):
580
- # event[2]
581
583
  for idx in pj[cfg.ETHOGRAM]:
582
- if pj[cfg.ETHOGRAM][idx]["code"] == event[2]:
584
+ if pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_CODE] == event[cfg.EVENT_BEHAVIOR_FIELD_IDX]:
583
585
  break
584
586
  else:
585
- raise
586
- if (not event[3]) and not pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS]:
587
+ # behavior not defined in ethogram
588
+ continue
589
+
590
+ if (not event[cfg.EVENT_MODIFIER_FIELD_IDX]) and not pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS]: # no modifiers
587
591
  continue
588
592
 
589
- if len(event[3].split("|")) != len(pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS]):
590
- print("behavior", event[2])
591
- print(f"modifier(s) #{event[3]}#", len(event[3].split("|")))
592
- print(pj[cfg.ETHOGRAM][idx]["code"], pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS])
593
- print()
593
+ if len(event[cfg.EVENT_MODIFIER_FIELD_IDX].split("|")) != len(pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS]):
594
+ # print("behavior", event[cfg.EVENT_BEHAVIOR_FIELD_IDX])
595
+ # print(f"modifier(s) #{event[cfg.EVENT_MODIFIER_FIELD_IDX]}#", len(event[cfg.EVENT_MODIFIER_FIELD_IDX].split("|")))
596
+ # print(pj[cfg.ETHOGRAM][idx]["code"], pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS])
597
+ # print()
594
598
  if obs_id not in obs_results:
595
599
  obs_results[obs_id] = []
596
600
 
597
601
  obs_results[obs_id].append(
598
602
  (
599
- f"Event #{event_idx}: the coded modifiers for {event[2]} are {len(event[3].split('|'))} "
603
+ f"Event #{event_idx}: the coded modifiers for {event[cfg.EVENT_BEHAVIOR_FIELD_IDX]} are {len(event[cfg.EVENT_MODIFIER_FIELD_IDX].split('|'))} "
600
604
  f"but {len(pj[cfg.ETHOGRAM][idx][cfg.MODIFIERS])} sets were defined in ethogram."
601
605
  )
602
606
  )
@@ -1482,7 +1486,6 @@ def open_project_json(project_file_name: str) -> tuple:
1482
1486
  pj[cfg.BEHAVIORAL_CATEGORIES_CONF] = dict()
1483
1487
  projectChanged = True
1484
1488
 
1485
-
1486
1489
  # add category key if not found
1487
1490
  for idx in pj[cfg.ETHOGRAM]:
1488
1491
  if cfg.BEHAVIOR_CATEGORY not in pj[cfg.ETHOGRAM][idx]:
boris/utilities.py CHANGED
@@ -1206,7 +1206,7 @@ def time2seconds(time_: str) -> dec:
1206
1206
  return dec("0.000")
1207
1207
 
1208
1208
 
1209
- def seconds2time(sec: dec) -> str:
1209
+ def seconds2time(sec: dec | None) -> str:
1210
1210
  """
1211
1211
  convert seconds to hh:mm:ss.sss format
1212
1212
 
@@ -1215,6 +1215,8 @@ def seconds2time(sec: dec) -> str:
1215
1215
  Returns:
1216
1216
  str: time in format hh:mm:ss
1217
1217
  """
1218
+ if sec is None:
1219
+ return cfg.NA
1218
1220
 
1219
1221
  if math.isnan(sec):
1220
1222
  return cfg.NA
boris/version.py CHANGED
@@ -20,5 +20,5 @@ This file is part of BORIS.
20
20
 
21
21
  """
22
22
 
23
- __version__ = "9.7.6"
24
- __version_date__ = "2025-11-13"
23
+ __version__ = "9.7.8"
24
+ __version_date__ = "2025-11-19"
boris/video_operations.py CHANGED
@@ -235,7 +235,10 @@ def display_zoom_level(self) -> None:
235
235
  if vz is None:
236
236
  self.lb_zoom_level.setText("-")
237
237
  return
238
- msg += f"{2**player.player.video_zoom:.1f} "
238
+ if player.player.video_zoom is not None:
239
+ msg += f"{2**player.player.video_zoom:.1f} "
240
+ else:
241
+ msg += "NA "
239
242
  msg += "</b>"
240
243
  self.lb_zoom_level.setText(msg)
241
244
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boris-behav-obs
3
- Version: 9.7.6
3
+ Version: 9.7.8
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
@@ -51,7 +51,7 @@ It provides also some analysis tools like time budget and some plotting function
51
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. -->
52
52
 
53
53
 
54
- The BORIS paper has more than 2423 citations in peer-reviewed scientific publications.
54
+ The BORIS paper has more than 2442 citations in peer-reviewed scientific publications.
55
55
 
56
56
 
57
57
 
@@ -2,7 +2,7 @@ boris/__init__.py,sha256=iAtmVMy22TJpMmxVTMSK_6-wXnCbx1ogvWgfYEcbHzU,773
2
2
  boris/__main__.py,sha256=ANjTbXgXDoz2nB1tCtOIllfIVotCa602iebACX7rXaE,764
3
3
  boris/about.py,sha256=RrK4fUbMUpyMWPZVmFYG91pVx_orvaLPw-OBWI_NcOg,5401
4
4
  boris/add_modifier.py,sha256=l9LSa_9FAV9CnBgm26tJqhMAdnFoBQafZLyt9pTKmac,26240
5
- boris/add_modifier_ui.py,sha256=Y7TLO5uS6zW7zpjXmjA4V_VIp_bFDNtjOTbJ9Q6m-mQ,11601
5
+ boris/add_modifier_ui.py,sha256=5PbyQKCrMhLXYkOqOvqis1E_oCYQI_994-uofIIIb0k,12366
6
6
  boris/advanced_event_filtering.py,sha256=VlvU12mL6xYacZOvJAi5uLpHMcmAw5Pvuvmka-PN29c,15469
7
7
  boris/behav_coding_map_creator.py,sha256=5XGY4AZOsGrOj0xHT-dIeiR5ejBhPShsnYNfIQFEYsU,38788
8
8
  boris/behavior_binary_table.py,sha256=bpmRDpEjq0rw3YOCoN_He3kfUe8A_R6E48kQR7KnkH8,12453
@@ -16,7 +16,7 @@ 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=O-TQdVNtzY7mObsnBqKD48fIDE55wVR64FTb5bV4nnw,232856
19
+ boris/core.py,sha256=gTlVvKH_khobEZ0aBwtZaKFH3BIitR8XjqkAz93Vr6c,234295
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=2806GdobV30ATW7HJcFIIceNwTNzm-VD8mr32yreefs,9884
39
+ boris/ipc_mpv.py,sha256=bnc-xjp9CbyqO4uEcAxMgxyJdG3-6SJUd_gNI_sILns,9925
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=K-Xi99BMLSohx6pPLKk-Eqi56fLWDFLScHxXKkoFAlg,57507
50
- boris/observation_operations.py,sha256=AOfrVV7tZOeOPACeXl-QbLZtGFaAMYAmsfLIVUJ5UH0,106249
50
+ boris/observation_operations.py,sha256=YYbaaecwd2C0zxQYAxes-rUx-XY6aQ94hzkPazmPcFw,106339
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
@@ -63,7 +63,7 @@ boris/plugins.py,sha256=sn2r8kMxkzaO8kNhem-cTlTBrym9MlFPyRT9Av9rHGg,15603
63
63
  boris/preferences.py,sha256=YcGBaXRfimH4jpFLfcUAgCAGESXSLhB6cli_cg4cDl0,21902
64
64
  boris/preferences_ui.py,sha256=zkmbQbkb0WqhPyMtnU-DU9Y2enSph_r-LnwsmEOgzkk,35090
65
65
  boris/project.py,sha256=4dE4nqo8at1lz9KykprF5Rr62R78yu4vBrTfde1gQ-g,86438
66
- boris/project_functions.py,sha256=ZmOoXmGEPuOE2E-H7uQAIStjk625BZpvldCcWg1A_pI,83184
66
+ boris/project_functions.py,sha256=3DQkUXrbcimhk8B9ilJE1Jlx0j0sqqmjrOb2wPN-QmA,83562
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
@@ -77,11 +77,11 @@ boris/synthetic_time_budget.py,sha256=3Eb9onMLmgqCLd50CuxV9L8RV2ESzfaMWvPK_bXUMM
77
77
  boris/time_budget_functions.py,sha256=SbGyTF2xySEqBdRJZeWFTirruvK3r6pwM6e4Gz17W1s,52186
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
- boris/utilities.py,sha256=pFKtfGTSBah0PSRvS3ZLiSsrGvHzg95Z2WuX7Espyh0,58572
81
- boris/version.py,sha256=bKLzAmD7Sx3MAu2HQpAstMFrwOyYqljaoejvpPIHSPY,787
80
+ boris/utilities.py,sha256=hZF3yTNgjKN057VGLvy6gLYZsjV8b_DHCDGy6_DERd8,58621
81
+ boris/version.py,sha256=lcCIuAdOq-j1yLj0CPlt2bedzEeQSaKQemxANe9BGpA,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=i38CTLpD1WbkQr4WA7Mj5_47ouqDvcVgPgZ6wUyggwA,10923
84
+ boris/video_operations.py,sha256=YlwcX3rgVsv6h78ylQYS9ITZs5MsXT8h0XsyaBU5mN8,11015
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.6.dist-info/licenses/LICENSE.TXT,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
105
- boris_behav_obs-9.7.6.dist-info/METADATA,sha256=MUyAllcHGrWNjV-O0WPsQLwXosMPdQxzCJKINKIm1uI,5203
106
- boris_behav_obs-9.7.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
107
- boris_behav_obs-9.7.6.dist-info/entry_points.txt,sha256=k__8XvFi4vaA4QFvQehCZjYkKmZH34HSAJI2iYCWrMs,52
108
- boris_behav_obs-9.7.6.dist-info/top_level.txt,sha256=fJSgm62S7WesiwTorGbOO4nNN0yzgZ3klgfGi3Px4qI,6
109
- boris_behav_obs-9.7.6.dist-info/RECORD,,
104
+ boris_behav_obs-9.7.8.dist-info/licenses/LICENSE.TXT,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
105
+ boris_behav_obs-9.7.8.dist-info/METADATA,sha256=1ehrEM-hNzY-uiRRZn8x2xRwnMUV7cG8CmbW5U-MS0o,5203
106
+ boris_behav_obs-9.7.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
107
+ boris_behav_obs-9.7.8.dist-info/entry_points.txt,sha256=k__8XvFi4vaA4QFvQehCZjYkKmZH34HSAJI2iYCWrMs,52
108
+ boris_behav_obs-9.7.8.dist-info/top_level.txt,sha256=fJSgm62S7WesiwTorGbOO4nNN0yzgZ3klgfGi3Px4qI,6
109
+ boris_behav_obs-9.7.8.dist-info/RECORD,,