boris-behav-obs 9.3.4__py2.py3-none-any.whl → 9.3.5__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.
boris/config.py CHANGED
@@ -37,6 +37,7 @@ SECONDS_PER_DAY: int = 86_400
37
37
  HOUR_CUTOFF: int = 7 * 24
38
38
  DATE_CUTOFF: int = HOUR_CUTOFF * 60 * 60 # 1 week
39
39
 
40
+ # cutoff for displaying time in HH:MM:SS.zzz format
40
41
  SMART_TIME_CUTOFF_DEFAULT: int = 300
41
42
 
42
43
  # minimal project version for handling observations from images
@@ -218,6 +219,7 @@ OVERLAY = "video overlay"
218
219
 
219
220
 
220
221
  USE_EXIF_DATE = "use_exif_date"
222
+ SUBSTRACT_FIRST_EXIF_DATE = "substract_first_exif_date"
221
223
  TIME_LAPSE = "time_lapse_delay"
222
224
 
223
225
 
boris/core.py CHANGED
@@ -308,9 +308,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
308
308
  play_rate_step: float = 0.1
309
309
  currentSubject: str = "" # contains the current subject of observation
310
310
 
311
- # FFmpeg
312
- memx = -1
313
- memy = -1
311
+ # geometric measurements
314
312
  mem_player = -1
315
313
 
316
314
  # path for ffmpeg/ffmpeg.exe program
@@ -339,6 +337,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
339
337
 
340
338
  mem_hash_obs: int = 0
341
339
 
340
+ # variables for list of observations
341
+ data: list = []
342
+ not_paired: list = []
343
+
342
344
  '''
343
345
  def add_button_menu(self, data, menu_obj):
344
346
  """
@@ -1342,21 +1344,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1342
1344
  self.pj[cfg.BEHAVIORS_CODING_MAP][idx] = dict(behav_coding_map)
1343
1345
  return
1344
1346
 
1345
- """
1346
- QMessageBox.critical(
1347
- None,
1348
- cfg.programName,
1349
- (
1350
- "The current project already contains a behaviors coding map "
1351
- f"with the same name (<b>{behav_coding_map['name']}</b>)"
1352
- ),
1353
- QMessageBox.Ok | QMessageBox.Default,
1354
- QMessageBox.NoButton,
1355
- )
1356
-
1357
- return
1358
- """
1359
-
1360
1347
  self.pj[cfg.BEHAVIORS_CODING_MAP].append(behav_coding_map)
1361
1348
  QMessageBox.information(
1362
1349
  self,
@@ -1526,7 +1513,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1526
1513
  jt.setWindowTitle("Jump to specific time")
1527
1514
  jt.label.setText("Set the time")
1528
1515
 
1529
- if jt.exec_():
1516
+ if jt.exec():
1530
1517
  new_time = jt.time_widget.get_time()
1531
1518
  if new_time < 0:
1532
1519
  return
@@ -1704,7 +1691,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1704
1691
  QMessageBox.critical(
1705
1692
  None,
1706
1693
  cfg.programName,
1707
- ("The picture directory was changed since the creation of observation."),
1694
+ ("The picture directory has changed since the creation of observation."),
1708
1695
  QMessageBox.Ok | QMessageBox.Default,
1709
1696
  QMessageBox.NoButton,
1710
1697
  )
@@ -1718,15 +1705,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1718
1705
  # extract EXIF tag
1719
1706
  if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.USE_EXIF_DATE, False):
1720
1707
  date_time_original = util.extract_exif_DateTimeOriginal(self.images_list[self.image_idx])
1708
+
1721
1709
  if date_time_original != -1:
1722
1710
  msg += f"<br>EXIF Date/Time Original: <b>{datetime.datetime.fromtimestamp(date_time_original):%Y-%m-%d %H:%M:%S}</b>"
1723
- else:
1724
- msg += "<br>EXIF Date/Time Original: <b>NA</b>"
1725
1711
 
1726
- if self.image_idx == 0 and date_time_original != -1:
1727
- self.image_time_ref = date_time_original
1712
+ if self.image_idx == 0:
1713
+ self.image_time_ref = date_time_original
1728
1714
 
1729
- if date_time_original != -1:
1730
1715
  if self.image_time_ref is not None:
1731
1716
  seconds_from_1st = date_time_original - self.image_time_ref
1732
1717
 
@@ -1736,6 +1721,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1736
1721
  seconds_from_1st_formated = seconds_from_1st
1737
1722
 
1738
1723
  else:
1724
+ msg += "<br>EXIF Date/Time Original: <b>NA</b>"
1739
1725
  seconds_from_1st_formated = cfg.NA
1740
1726
 
1741
1727
  msg += f"<br>Time from 1st image: <b>{seconds_from_1st_formated}</b>"
@@ -2311,11 +2297,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
2311
2297
  self.tv_events.setModel(model)
2312
2298
 
2313
2299
  # column width
2314
- # https://doc.qt.io/qtforpython-5/PySide2/QtWidgets/QHeaderView.html#more
2315
- self.tv_events.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
2316
-
2317
- # self.table.setSortingEnabled(True)
2318
- # self.table.sortByColumn(0, Qt.AscendingOrder)
2300
+ self.tv_events.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
2319
2301
 
2320
2302
  def load_tw_events(self, obs_id) -> None:
2321
2303
  """
@@ -2343,76 +2325,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
2343
2325
 
2344
2326
  return
2345
2327
 
2346
- """
2347
- DISABLED tableview component is used
2348
-
2349
- logging.debug(f"begin load events from obs in tablewidget: {obs_id}")
2350
-
2351
- t1 = time.time()
2352
-
2353
- self.twEvents.clear()
2354
-
2355
- self.twEvents.setColumnCount(len(cfg.TW_EVENTS_FIELDS[self.playerType]))
2356
- self.twEvents.setHorizontalHeaderLabels([s.capitalize() for s in cfg.TW_EVENTS_FIELDS[self.playerType]])
2357
-
2358
- for idx, field in enumerate(cfg.TW_EVENTS_FIELDS[self.playerType]):
2359
- if field not in self.config_param.get(f"{self.playerType} tw fields", cfg.TW_EVENTS_FIELDS[self.playerType]):
2360
- self.twEvents.horizontalHeader().hideSection(idx)
2361
- else:
2362
- self.twEvents.horizontalHeader().showSection(idx)
2363
-
2364
- self.twEvents.setRowCount(len(self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]))
2365
- if self.filtered_behaviors or self.filtered_subjects:
2366
- self.twEvents.setRowCount(0)
2367
- row = 0
2368
-
2369
- for event_idx, event in enumerate(self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]):
2370
- if self.filtered_behaviors and event[cfg.PJ_OBS_FIELDS[self.playerType][cfg.BEHAVIOR_CODE]] not in self.filtered_behaviors:
2371
- continue
2372
-
2373
- if self.filtered_subjects and event[cfg.PJ_OBS_FIELDS[self.playerType][cfg.SUBJECT]] not in self.filtered_subjects:
2374
- continue
2375
-
2376
- if self.filtered_behaviors or self.filtered_subjects:
2377
- self.twEvents.insertRow(self.twEvents.rowCount())
2378
-
2379
- for field_type in cfg.TW_EVENTS_FIELDS[self.playerType]:
2380
- if field_type in cfg.PJ_EVENTS_FIELDS[self.playerType]:
2381
- field = event_operations.read_event_field(event, self.playerType, field_type)
2382
-
2383
- if field_type == cfg.TIME:
2384
- item = QTableWidgetItem(str(util.convertTime(self.timeFormat, field)))
2385
-
2386
- # add index of project events
2387
- item.setData(Qt.UserRole, event_idx)
2388
- self.twEvents.setItem(row, cfg.TW_OBS_FIELD[self.playerType][field_type], item)
2389
- continue
2390
-
2391
- if field_type in (cfg.IMAGE_INDEX, cfg.FRAME_INDEX):
2392
- field = str(field)
2393
-
2394
- self.twEvents.setItem(
2395
- row,
2396
- cfg.TW_OBS_FIELD[self.playerType][field_type],
2397
- QTableWidgetItem(field),
2398
- )
2399
-
2400
- else:
2401
- self.twEvents.setItem(
2402
- row,
2403
- cfg.TW_OBS_FIELD[self.playerType][field_type],
2404
- QTableWidgetItem(""),
2405
- )
2406
-
2407
- row += 1
2408
-
2409
- self.update_events_start_stop()
2410
-
2411
- print("load twevent:", time.time() - t1)
2412
-
2413
- logging.debug("end load events from obs")
2414
- """
2415
-
2416
2328
  def close_tool_windows(self):
2417
2329
  """
2418
2330
  close tool windows:
@@ -3493,7 +3405,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3493
3405
  return
3494
3406
 
3495
3407
  logging.debug(
3496
- f"{self.config_param[cfg.CHECK_PROJECT_INTEGRITY] if cfg.CHECK_PROJECT_INTEGRITY in self.config_param else "Check project integrity config NOT FOUND"=}"
3408
+ f"{self.config_param[cfg.CHECK_PROJECT_INTEGRITY] if cfg.CHECK_PROJECT_INTEGRITY in self.config_param else 'Check project integrity config NOT FOUND'=}"
3497
3409
  )
3498
3410
 
3499
3411
  if self.config_param.get(cfg.CHECK_PROJECT_INTEGRITY, True):
@@ -4707,7 +4619,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4707
4619
  self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.USE_EXIF_DATE, False)
4708
4620
  and util.extract_exif_DateTimeOriginal(self.images_list[self.image_idx]) != -1
4709
4621
  ):
4710
- time_ = util.extract_exif_DateTimeOriginal(self.images_list[self.image_idx]) - self.image_time_ref
4622
+ time_ = util.extract_exif_DateTimeOriginal(self.images_list[self.image_idx])
4623
+ # check if first value must be substracted
4624
+ if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.SUBSTRACT_FIRST_EXIF_DATE, True):
4625
+ time_ -= self.image_time_ref
4711
4626
 
4712
4627
  elif self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.TIME_LAPSE, 0):
4713
4628
  time_ = (self.image_idx + 1) * self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.TIME_LAPSE, 0)
@@ -4765,7 +4680,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4765
4680
  self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.USE_EXIF_DATE, False)
4766
4681
  and util.extract_exif_DateTimeOriginal(self.images_list[self.image_idx]) != -1
4767
4682
  ):
4768
- time_ = util.extract_exif_DateTimeOriginal(self.images_list[self.image_idx]) - self.image_time_ref
4683
+ time_ = util.extract_exif_DateTimeOriginal(self.images_list[self.image_idx])
4684
+ # check if first value must be substracte
4685
+ if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.SUBSTRACT_FIRST_EXIF_DATE, True):
4686
+ time_ -= self.image_time_ref
4769
4687
 
4770
4688
  elif self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.TIME_LAPSE, 0):
4771
4689
  time_ = (self.image_idx + 1) * self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.TIME_LAPSE, 0)
boris/dialog.py CHANGED
@@ -20,9 +20,8 @@ This file is part of BORIS.
20
20
 
21
21
  """
22
22
 
23
- from decimal import Decimal as dec
24
- from typing import Union
25
23
  import datetime as dt
24
+ from decimal import Decimal as dec
26
25
  import logging
27
26
  import math
28
27
  import pathlib as pl
@@ -30,6 +29,7 @@ import platform
30
29
  import subprocess
31
30
  import sys
32
31
  import traceback
32
+ from typing import Union
33
33
 
34
34
  from PySide6.QtCore import Qt, Signal, qVersion, QRect, QTime, QDateTime, QSize
35
35
  from PySide6.QtWidgets import (
@@ -383,7 +383,7 @@ class get_time_widget(QWidget):
383
383
  dec: time in seconds (None if no format selected)
384
384
  """
385
385
 
386
- time_sec = None
386
+ time_sec = dec("NaN")
387
387
 
388
388
  if self.rb_seconds.isChecked():
389
389
  try:
@@ -396,7 +396,7 @@ class get_time_widget(QWidget):
396
396
  QMessageBox.Ok | QMessageBox.Default,
397
397
  QMessageBox.NoButton,
398
398
  )
399
- return None
399
+ return dec("NaN")
400
400
 
401
401
  if self.rb_time.isChecked():
402
402
  time_sec = self.sb_hour.value() * 3600
@@ -408,7 +408,7 @@ class get_time_widget(QWidget):
408
408
  if self.pb_sign.text() == "-":
409
409
  time_sec = -time_sec
410
410
 
411
- return dec(time_sec).quantize(dec("0.001")) if time_sec is not None else None
411
+ return dec(time_sec).quantize(dec("0.001")) # if time_sec is not None else None
412
412
 
413
413
  def set_time(self, new_time: dec) -> None:
414
414
  """
@@ -489,7 +489,7 @@ class Ask_time(QDialog):
489
489
  )
490
490
  return
491
491
  # test time value
492
- if self.time_widget.get_time() is None:
492
+ if self.time_widget.get_time().is_nan():
493
493
  return
494
494
 
495
495
  self.accept()
boris/edit_event.py CHANGED
@@ -21,7 +21,7 @@ This file is part of BORIS.
21
21
  """
22
22
 
23
23
  from decimal import Decimal as dec
24
- import math
24
+ import logging
25
25
 
26
26
  from PySide6.QtWidgets import (
27
27
  QDialog,
@@ -44,11 +44,12 @@ class DlgEditEvent(QDialog, Ui_Form):
44
44
  def __init__(
45
45
  self,
46
46
  observation_type: str,
47
- time_value: dec = dec(0),
47
+ time_value: dec = dec("NaN"),
48
48
  image_idx=None,
49
49
  current_time=0,
50
50
  time_format: str = cfg.S,
51
51
  show_set_current_time: bool = False,
52
+ exif_date_time: dec | None = None,
52
53
  parent=None,
53
54
  ):
54
55
  super().__init__(parent)
@@ -59,42 +60,49 @@ class DlgEditEvent(QDialog, Ui_Form):
59
60
 
60
61
  self.pb_set_to_current_time.setVisible(show_set_current_time)
61
62
  self.current_time = current_time
63
+ self.exif_date_time = exif_date_time
64
+
65
+ # hide frame index for all observations
66
+ # frame index is determined in base of time
67
+ for w in (
68
+ self.lb_frame_idx,
69
+ self.sb_frame_idx,
70
+ self.cb_set_frame_idx_na,
71
+ ):
72
+ w.setVisible(False)
62
73
 
63
74
  # hide image index
64
75
  if observation_type in (cfg.LIVE, cfg.MEDIA):
65
- for w in (self.lb_image_idx, self.sb_image_idx, self.cb_set_time_na, self.pb_set_to_current_image_index):
66
- w.setVisible(False)
67
- # hide frame index because frame index is automatically extracted
68
- for w in (self.lb_frame_idx, self.sb_frame_idx, self.cb_set_frame_idx_na):
76
+ # hide image index
77
+ for w in (
78
+ self.cb_set_time_na,
79
+ self.gb_image_index,
80
+ ):
69
81
  w.setVisible(False)
70
82
 
71
- if (observation_type in (cfg.LIVE, cfg.MEDIA)) or (observation_type == cfg.IMAGES and not math.isnan(self.time_value)):
72
- self.time_widget = dialog.get_time_widget(self.time_value)
83
+ # widget for time
84
+ self.time_widget = dialog.get_time_widget(self.time_value)
73
85
 
74
- if time_format == cfg.S:
75
- self.time_widget.rb_seconds.setChecked(True)
76
- if time_format == cfg.HHMMSS:
77
- self.time_widget.rb_time.setChecked(True)
78
- if self.time_value > cfg.DATE_CUTOFF:
79
- self.time_widget.rb_datetime.setChecked(True)
86
+ if time_format == cfg.S:
87
+ self.time_widget.rb_seconds.setChecked(True)
88
+ if time_format == cfg.HHMMSS:
89
+ self.time_widget.rb_time.setChecked(True)
90
+ if not self.time_value.is_nan() and int(self.time_value) > cfg.DATE_CUTOFF:
91
+ self.time_widget.rb_datetime.setChecked(True)
80
92
 
81
- self.horizontalLayout_2.insertWidget(0, self.time_widget)
93
+ self.horizontalLayout_3.insertWidget(0, self.time_widget)
82
94
 
83
95
  if observation_type == cfg.IMAGES:
84
- self.time_widget = dialog.get_time_widget(self.time_value)
85
96
  # hide frame index widgets
86
- for w in (self.lb_frame_idx, self.sb_frame_idx, self.cb_set_frame_idx_na, self.pb_set_to_current_time):
87
- w.setVisible(False)
97
+ self.pb_set_to_current_time.setVisible(self.exif_date_time is not None)
88
98
  self.sb_image_idx.setValue(self.image_idx)
89
99
 
90
- # self.pb_set_to_current_time.setText("Set to current image index")
91
-
92
100
  self.pb_set_to_current_time.clicked.connect(self.set_to_current_time)
93
101
  self.pb_set_to_current_image_index.clicked.connect(self.set_to_current_image_index)
94
102
 
95
103
  self.cb_set_time_na.stateChanged.connect(self.time_na)
96
104
 
97
- self.cb_set_frame_idx_na.stateChanged.connect(self.frame_idx_na)
105
+ # self.cb_set_frame_idx_na.stateChanged.connect(self.frame_idx_na)
98
106
  self.pbOK.clicked.connect(self.close_widget)
99
107
  self.pbCancel.clicked.connect(self.reject)
100
108
 
@@ -119,22 +127,34 @@ class DlgEditEvent(QDialog, Ui_Form):
119
127
  """
120
128
  set time to current media time
121
129
  """
130
+
131
+ print(f"{self.current_time=}")
132
+
122
133
  if self.observation_type in (cfg.LIVE, cfg.MEDIA):
123
134
  self.time_widget.set_time(dec(float(self.current_time)))
124
135
 
125
- def frame_idx_na(self):
126
- """
127
- set/unset frame index NA
128
- """
129
- self.lb_frame_idx.setEnabled(not self.cb_set_frame_idx_na.isChecked())
130
- self.sb_frame_idx.setEnabled(not self.cb_set_frame_idx_na.isChecked())
136
+ if self.observation_type == cfg.IMAGES:
137
+ if self.exif_date_time is not None:
138
+ self.time_widget.set_time(dec(self.exif_date_time))
139
+
140
+ # def frame_idx_na(self):
141
+ # """
142
+ # set/unset frame index NA
143
+ # """
144
+ # self.lb_frame_idx.setEnabled(not self.cb_set_frame_idx_na.isChecked())
145
+ # self.sb_frame_idx.setEnabled(not self.cb_set_frame_idx_na.isChecked())
131
146
 
132
147
  def time_na(self):
133
148
  """
134
149
  set/unset time to NA
135
150
  """
136
151
 
152
+ logging.debug("time_na function")
153
+
154
+ self.time_widget.setVisible(not self.cb_set_time_na.isChecked())
137
155
  self.time_widget.setEnabled(not self.cb_set_time_na.isChecked())
156
+
157
+ self.pb_set_to_current_time.setVisible(not self.cb_set_time_na.isChecked() and self.exif_date_time is not None)
138
158
  self.pb_set_to_current_time.setEnabled(not self.cb_set_time_na.isChecked())
139
159
 
140
160
 
boris/edit_event_ui.py CHANGED
@@ -3,7 +3,7 @@
3
3
  ################################################################################
4
4
  ## Form generated from reading UI file 'edit_event.ui'
5
5
  ##
6
- ## Created by: Qt User Interface Compiler version 6.8.0
6
+ ## Created by: Qt User Interface Compiler version 6.9.0
7
7
  ##
8
8
  ## WARNING! All changes made in this file will be lost when recompiling UI file!
9
9
  ################################################################################
@@ -15,60 +15,89 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
15
15
  QFont, QFontDatabase, QGradient, QIcon,
16
16
  QImage, QKeySequence, QLinearGradient, QPainter,
17
17
  QPalette, QPixmap, QRadialGradient, QTransform)
18
- from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QHBoxLayout,
19
- QLabel, QPlainTextEdit, QPushButton, QSizePolicy,
20
- QSpacerItem, QSpinBox, QVBoxLayout, QWidget)
18
+ from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QGroupBox,
19
+ QHBoxLayout, QLabel, QPlainTextEdit, QPushButton,
20
+ QSizePolicy, QSpacerItem, QSpinBox, QVBoxLayout,
21
+ QWidget)
21
22
 
22
23
  class Ui_Form(object):
23
24
  def setupUi(self, Form):
24
25
  if not Form.objectName():
25
26
  Form.setObjectName(u"Form")
26
- Form.resize(413, 488)
27
- self.verticalLayout = QVBoxLayout(Form)
27
+ Form.resize(600, 638)
28
+ self.verticalLayout_3 = QVBoxLayout(Form)
29
+ self.verticalLayout_3.setObjectName(u"verticalLayout_3")
30
+ self.gb_time = QGroupBox(Form)
31
+ self.gb_time.setObjectName(u"gb_time")
32
+ self.verticalLayout = QVBoxLayout(self.gb_time)
28
33
  self.verticalLayout.setObjectName(u"verticalLayout")
34
+ self.horizontalLayout_8 = QHBoxLayout()
35
+ self.horizontalLayout_8.setObjectName(u"horizontalLayout_8")
36
+ self.cb_set_time_na = QCheckBox(self.gb_time)
37
+ self.cb_set_time_na.setObjectName(u"cb_set_time_na")
38
+
39
+ self.horizontalLayout_8.addWidget(self.cb_set_time_na)
40
+
41
+ self.horizontalSpacer_7 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
42
+
43
+ self.horizontalLayout_8.addItem(self.horizontalSpacer_7)
44
+
45
+
46
+ self.verticalLayout.addLayout(self.horizontalLayout_8)
47
+
29
48
  self.horizontalLayout_3 = QHBoxLayout()
30
49
  self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
31
- self.label = QLabel(Form)
32
- self.label.setObjectName(u"label")
50
+ self.lb_time = QLabel(self.gb_time)
51
+ self.lb_time.setObjectName(u"lb_time")
33
52
 
34
- self.horizontalLayout_3.addWidget(self.label)
53
+ self.horizontalLayout_3.addWidget(self.lb_time)
35
54
 
36
55
  self.horizontalLayout_2 = QHBoxLayout()
37
56
  self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
38
- self.pb_set_to_current_time = QPushButton(Form)
57
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
58
+
59
+ self.horizontalLayout_2.addItem(self.horizontalSpacer)
60
+
61
+
62
+ self.horizontalLayout_3.addLayout(self.horizontalLayout_2)
63
+
64
+
65
+ self.verticalLayout.addLayout(self.horizontalLayout_3)
66
+
67
+ self.horizontalLayout_9 = QHBoxLayout()
68
+ self.horizontalLayout_9.setObjectName(u"horizontalLayout_9")
69
+ self.pb_set_to_current_time = QPushButton(self.gb_time)
39
70
  self.pb_set_to_current_time.setObjectName(u"pb_set_to_current_time")
40
71
 
41
- self.horizontalLayout_2.addWidget(self.pb_set_to_current_time)
72
+ self.horizontalLayout_9.addWidget(self.pb_set_to_current_time)
42
73
 
43
- self.cb_set_time_na = QCheckBox(Form)
44
- self.cb_set_time_na.setObjectName(u"cb_set_time_na")
74
+ self.horizontalSpacer_8 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
45
75
 
46
- self.horizontalLayout_2.addWidget(self.cb_set_time_na)
76
+ self.horizontalLayout_9.addItem(self.horizontalSpacer_8)
47
77
 
48
- self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
49
78
 
50
- self.horizontalLayout_2.addItem(self.horizontalSpacer)
79
+ self.verticalLayout.addLayout(self.horizontalLayout_9)
51
80
 
81
+ self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
52
82
 
53
- self.horizontalLayout_3.addLayout(self.horizontalLayout_2)
83
+ self.verticalLayout.addItem(self.verticalSpacer)
54
84
 
55
85
 
56
- self.verticalLayout.addLayout(self.horizontalLayout_3)
86
+ self.verticalLayout_3.addWidget(self.gb_time)
57
87
 
88
+ self.gb_image_index = QGroupBox(Form)
89
+ self.gb_image_index.setObjectName(u"gb_image_index")
90
+ self.verticalLayout_2 = QVBoxLayout(self.gb_image_index)
91
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
58
92
  self.horizontalLayout_7 = QHBoxLayout()
59
93
  self.horizontalLayout_7.setObjectName(u"horizontalLayout_7")
60
- self.lb_image_idx = QLabel(Form)
61
- self.lb_image_idx.setObjectName(u"lb_image_idx")
62
-
63
- self.horizontalLayout_7.addWidget(self.lb_image_idx)
64
-
65
- self.sb_image_idx = QSpinBox(Form)
94
+ self.sb_image_idx = QSpinBox(self.gb_image_index)
66
95
  self.sb_image_idx.setObjectName(u"sb_image_idx")
67
96
  self.sb_image_idx.setMaximum(10000000)
68
97
 
69
98
  self.horizontalLayout_7.addWidget(self.sb_image_idx)
70
99
 
71
- self.pb_set_to_current_image_index = QPushButton(Form)
100
+ self.pb_set_to_current_image_index = QPushButton(self.gb_image_index)
72
101
  self.pb_set_to_current_image_index.setObjectName(u"pb_set_to_current_image_index")
73
102
 
74
103
  self.horizontalLayout_7.addWidget(self.pb_set_to_current_image_index)
@@ -78,7 +107,10 @@ class Ui_Form(object):
78
107
  self.horizontalLayout_7.addItem(self.horizontalSpacer_6)
79
108
 
80
109
 
81
- self.verticalLayout.addLayout(self.horizontalLayout_7)
110
+ self.verticalLayout_2.addLayout(self.horizontalLayout_7)
111
+
112
+
113
+ self.verticalLayout_3.addWidget(self.gb_image_index)
82
114
 
83
115
  self.horizontalLayout_4 = QHBoxLayout()
84
116
  self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
@@ -97,7 +129,7 @@ class Ui_Form(object):
97
129
  self.horizontalLayout_4.addItem(self.horizontalSpacer_2)
98
130
 
99
131
 
100
- self.verticalLayout.addLayout(self.horizontalLayout_4)
132
+ self.verticalLayout_3.addLayout(self.horizontalLayout_4)
101
133
 
102
134
  self.horizontalLayout_5 = QHBoxLayout()
103
135
  self.horizontalLayout_5.setObjectName(u"horizontalLayout_5")
@@ -116,7 +148,7 @@ class Ui_Form(object):
116
148
  self.horizontalLayout_5.addItem(self.horizontalSpacer_3)
117
149
 
118
150
 
119
- self.verticalLayout.addLayout(self.horizontalLayout_5)
151
+ self.verticalLayout_3.addLayout(self.horizontalLayout_5)
120
152
 
121
153
  self.horizontalLayout_6 = QHBoxLayout()
122
154
  self.horizontalLayout_6.setObjectName(u"horizontalLayout_6")
@@ -142,17 +174,17 @@ class Ui_Form(object):
142
174
  self.horizontalLayout_6.addItem(self.horizontalSpacer_5)
143
175
 
144
176
 
145
- self.verticalLayout.addLayout(self.horizontalLayout_6)
177
+ self.verticalLayout_3.addLayout(self.horizontalLayout_6)
146
178
 
147
179
  self.label_4 = QLabel(Form)
148
180
  self.label_4.setObjectName(u"label_4")
149
181
 
150
- self.verticalLayout.addWidget(self.label_4)
182
+ self.verticalLayout_3.addWidget(self.label_4)
151
183
 
152
184
  self.leComment = QPlainTextEdit(Form)
153
185
  self.leComment.setObjectName(u"leComment")
154
186
 
155
- self.verticalLayout.addWidget(self.leComment)
187
+ self.verticalLayout_3.addWidget(self.leComment)
156
188
 
157
189
  self.horizontalLayout = QHBoxLayout()
158
190
  self.horizontalLayout.setObjectName(u"horizontalLayout")
@@ -171,7 +203,7 @@ class Ui_Form(object):
171
203
  self.horizontalLayout.addWidget(self.pbOK)
172
204
 
173
205
 
174
- self.verticalLayout.addLayout(self.horizontalLayout)
206
+ self.verticalLayout_3.addLayout(self.horizontalLayout)
175
207
 
176
208
 
177
209
  self.retranslateUi(Form)
@@ -184,13 +216,14 @@ class Ui_Form(object):
184
216
 
185
217
  def retranslateUi(self, Form):
186
218
  Form.setWindowTitle(QCoreApplication.translate("Form", u"Edit event", None))
187
- self.label.setText(QCoreApplication.translate("Form", u"Time", None))
219
+ self.gb_time.setTitle(QCoreApplication.translate("Form", u"Time", None))
220
+ self.cb_set_time_na.setText(QCoreApplication.translate("Form", u"Set time to NA", None))
221
+ self.lb_time.setText("")
188
222
  self.pb_set_to_current_time.setText(QCoreApplication.translate("Form", u"Set to current time", None))
189
- self.cb_set_time_na.setText(QCoreApplication.translate("Form", u"Set NA", None))
190
- self.lb_image_idx.setText(QCoreApplication.translate("Form", u"Image index", None))
223
+ self.gb_image_index.setTitle(QCoreApplication.translate("Form", u"Image index", None))
191
224
  self.pb_set_to_current_image_index.setText(QCoreApplication.translate("Form", u"Set to current image index", None))
192
225
  self.lbSubject.setText(QCoreApplication.translate("Form", u"Subject", None))
193
- self.label_2.setText(QCoreApplication.translate("Form", u"Code", None))
226
+ self.label_2.setText(QCoreApplication.translate("Form", u"Behavior", None))
194
227
  self.lb_frame_idx.setText(QCoreApplication.translate("Form", u"Frame index", None))
195
228
  self.cb_set_frame_idx_na.setText(QCoreApplication.translate("Form", u"Set NA", None))
196
229
  self.label_4.setText(QCoreApplication.translate("Form", u"Comment", None))
boris/event_operations.py CHANGED
@@ -70,7 +70,7 @@ def add_event(self):
70
70
 
71
71
  editWindow = DlgEditEvent(
72
72
  observation_type=self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE],
73
- time_value=0,
73
+ time_value=dec("NaN"),
74
74
  image_idx=0,
75
75
  current_time=current_time,
76
76
  time_format=self.timeFormat,
@@ -80,8 +80,6 @@ def add_event(self):
80
80
 
81
81
  sortedSubjects = [cfg.NO_FOCAL_SUBJECT] + sorted([self.pj[cfg.SUBJECTS][x][cfg.SUBJECT_NAME] for x in self.pj[cfg.SUBJECTS]])
82
82
 
83
- print(f"{sortedSubjects=}")
84
-
85
83
  editWindow.cobSubject.addItems(sortedSubjects)
86
84
  if self.currentSubject:
87
85
  editWindow.cobSubject.setCurrentIndex(editWindow.cobSubject.findText(self.currentSubject, Qt.MatchFixedString))
@@ -173,8 +171,13 @@ def add_event(self):
173
171
  time_ = dec("NaN")
174
172
  if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.USE_EXIF_DATE, False):
175
173
  if self.playerType != cfg.VIEWER_IMAGES:
176
- if self.extract_exif_DateTimeOriginal(self.images_list[new_index]) != -1:
177
- time_ = self.extract_exif_DateTimeOriginal(self.images_list[new_index]) - self.image_time_ref
174
+ exif_date_time = util.extract_exif_DateTimeOriginal(self.images_list[new_index])
175
+ if exif_date_time != -1:
176
+ time_ = exif_date_time
177
+
178
+ # check if first value must be substracted
179
+ if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.SUBSTRACT_FIRST_EXIF_DATE, True):
180
+ time_ -= self.image_time_ref
178
181
 
179
182
  elif self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.TIME_LAPSE, 0):
180
183
  time_ = new_index * self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.TIME_LAPSE, 0)
@@ -576,8 +579,6 @@ def edit_event(self):
576
579
 
577
580
  pj_event_idx = self.tv_idx2events_idx[tvevents_row]
578
581
 
579
- print(f"{self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS]=}")
580
-
581
582
  time_value = self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS][pj_event_idx][
582
583
  cfg.PJ_OBS_FIELDS[self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE]][cfg.TIME]
583
584
  ]
@@ -593,6 +594,12 @@ def edit_event(self):
593
594
  else:
594
595
  current_value = self.getLaps()
595
596
 
597
+ # get exif date time
598
+ if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.USE_EXIF_DATE, False):
599
+ exif_date_time = util.extract_exif_DateTimeOriginal(self.images_list[self.image_idx])
600
+ else:
601
+ exif_date_time = None
602
+
596
603
  edit_window = DlgEditEvent(
597
604
  observation_type=self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE],
598
605
  time_value=time_value,
@@ -600,15 +607,15 @@ def edit_event(self):
600
607
  current_time=current_value,
601
608
  time_format=self.timeFormat,
602
609
  show_set_current_time=True,
610
+ exif_date_time=exif_date_time,
603
611
  )
604
612
  edit_window.setWindowTitle("Edit event")
605
613
 
606
- print(f"{time_value=}")
607
-
608
614
  # time
609
615
  if time_value.is_nan():
610
616
  edit_window.cb_set_time_na.setChecked(True)
611
617
 
618
+ # remove visibility of 'set current time' widget if VIEWER mode
612
619
  if self.playerType in (cfg.VIEWER_MEDIA, cfg.VIEWER_LIVE, cfg.VIEWER_IMAGES):
613
620
  edit_window.pb_set_to_current_time.setVisible(False)
614
621
 
@@ -642,20 +649,16 @@ def edit_event(self):
642
649
  sortedCodes.index(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS][pj_event_idx][cfg.EVENT_BEHAVIOR_FIELD_IDX])
643
650
  )
644
651
  else:
645
- logging.warning(
646
- (
647
- f"The behaviour {self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS][pj_event_idx][cfg.EVENT_BEHAVIOR_FIELD_IDX]} "
648
- "does not exist longer in the ethogram"
649
- )
652
+ msg: str = (
653
+ f"The behaviour {self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS][pj_event_idx][cfg.EVENT_BEHAVIOR_FIELD_IDX]} "
654
+ "does not exist longer in the ethogram"
650
655
  )
656
+ logging.warning(msg)
657
+
651
658
  QMessageBox.warning(
652
659
  self,
653
660
  cfg.programName,
654
- (
655
- "The behaviour "
656
- f"<b>{self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS][pj_event_idx][cfg.EVENT_BEHAVIOR_FIELD_IDX]}</b> "
657
- "does not exist more in the ethogram"
658
- ),
661
+ msg,
659
662
  )
660
663
  edit_window.cobCode.setCurrentIndex(0)
661
664
 
@@ -663,15 +666,15 @@ def edit_event(self):
663
666
  f"original modifiers: {self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS][pj_event_idx][cfg.EVENT_MODIFIER_FIELD_IDX]}"
664
667
  )
665
668
 
666
- # frame index
667
- frame_idx = read_event_field(
668
- self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS][pj_event_idx],
669
- self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE],
670
- cfg.FRAME_INDEX,
671
- )
672
- edit_window.sb_frame_idx.setValue(0 if frame_idx in (cfg.NA, None) else frame_idx)
673
- if frame_idx in (cfg.NA, None):
674
- edit_window.cb_set_frame_idx_na.setChecked(True)
669
+ # # frame index
670
+ # frame_idx = read_event_field(
671
+ # self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS][pj_event_idx],
672
+ # self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE],
673
+ # cfg.FRAME_INDEX,
674
+ # )
675
+ # edit_window.sb_frame_idx.setValue(0 if frame_idx in (cfg.NA, None) else frame_idx)
676
+ # if frame_idx in (cfg.NA, None):
677
+ # edit_window.cb_set_frame_idx_na.setChecked(True)
675
678
 
676
679
  # comment
677
680
  edit_window.leComment.setPlainText(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS][pj_event_idx][cfg.EVENT_COMMENT_FIELD_IDX])
@@ -681,13 +684,14 @@ def edit_event(self):
681
684
  if edit_window.exec(): # button OK
682
685
  self.project_changed()
683
686
 
684
- # MEDIA / LIVE
685
- if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] in (cfg.MEDIA, cfg.LIVE):
686
- new_time = edit_window.time_widget.get_time()
687
+ new_time = edit_window.time_widget.get_time()
687
688
 
688
- print(f"{new_time=}")
689
+ if edit_window.cb_set_time_na.isChecked():
690
+ new_time = dec("NaN")
689
691
 
690
- if new_time is None:
692
+ # MEDIA / LIVE
693
+ if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] in (cfg.MEDIA, cfg.LIVE):
694
+ if new_time.is_nan():
691
695
  QMessageBox.warning(
692
696
  self,
693
697
  cfg.programName,
@@ -715,19 +719,17 @@ def edit_event(self):
715
719
  event[cfg.FRAME_INDEX] = frame_idx
716
720
  self.seek_mediaplayer(mem_time)
717
721
 
718
- """
719
- if not edit_window.sb_frame_idx.value() or edit_window.cb_set_frame_idx_na.isChecked():
720
- event[cfg.FRAME_INDEX] = cfg.NA
721
- else:
722
- if self.playerType == cfg.MEDIA:
723
- mem_time = self.getLaps()
724
- if not self.seek_mediaplayer(new_time):
725
- frame_idx = self.get_frame_index()
726
- event[cfg.FRAME_INDEX] = frame_idx
727
- self.seek_mediaplayer(mem_time)
728
-
729
- # event[cfg.FRAME_INDEX] = edit_window.sb_frame_idx.value()
730
- """
722
+ # if not edit_window.sb_frame_idx.value() or edit_window.cb_set_frame_idx_na.isChecked():
723
+ # event[cfg.FRAME_INDEX] = cfg.NA
724
+ # else:
725
+ # if self.playerType == cfg.MEDIA:
726
+ # mem_time = self.getLaps()
727
+ # if not self.seek_mediaplayer(new_time):
728
+ # frame_idx = self.get_frame_index()
729
+ # event[cfg.FRAME_INDEX] = frame_idx
730
+ # self.seek_mediaplayer(mem_time)
731
+ #
732
+ # # event[cfg.FRAME_INDEX] = edit_window.sb_frame_idx.value()
731
733
 
732
734
  event["row"] = pj_event_idx
733
735
  event["original_modifiers"] = self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS][pj_event_idx][
@@ -755,14 +757,7 @@ def edit_event(self):
755
757
  for key in self.pj[cfg.ETHOGRAM]:
756
758
  if self.pj[cfg.ETHOGRAM][key][cfg.BEHAVIOR_CODE] == edit_window.cobCode.currentText():
757
759
  event = self.full_event(key)
758
- if (
759
- edit_window.time_value == cfg.NA
760
- or (isinstance(edit_window.time_value, dec) and edit_window.time_value.is_nan())
761
- or (edit_window.cb_set_time_na.isChecked())
762
- ):
763
- event[cfg.TIME] = dec("NaN")
764
- else:
765
- event[cfg.TIME] = edit_window.time_widget.get_time()
760
+ event[cfg.TIME] = new_time
766
761
 
767
762
  event[cfg.SUBJECT] = (
768
763
  "" if edit_window.cobSubject.currentText() == cfg.NO_FOCAL_SUBJECT else edit_window.cobSubject.currentText()
@@ -820,7 +815,7 @@ def edit_time_selected_events(self):
820
815
  return
821
816
 
822
817
  d = w.time_widget.get_time()
823
- if d is None or not d:
818
+ if d.is_nan() or not d:
824
819
  return
825
820
 
826
821
  if ":" in util.smart_time_format(abs(d)):
@@ -894,8 +889,6 @@ def copy_selected_events(self):
894
889
  else:
895
890
  copied_events.append("\t".join([str(x) for x in event]))
896
891
 
897
- print(f"{copied_events=}")
898
-
899
892
  cb = QApplication.clipboard()
900
893
  cb.clear(mode=QClipboard.Mode.Clipboard)
901
894
  cb.setText("\n".join(copied_events), mode=QClipboard.Mode.Clipboard)
boris/observation.py CHANGED
@@ -24,10 +24,9 @@ import logging
24
24
  import os
25
25
  import pandas as pd
26
26
  import pathlib as pl
27
- import datetime as dt
28
27
 
29
28
  from PySide6.QtCore import Qt
30
- from PySide6.QtGui import QColor, QTextCursor, QAction
29
+ from PySide6.QtGui import QColor
31
30
  from PySide6.QtWidgets import (
32
31
  QDialog,
33
32
  QVBoxLayout,
@@ -1024,7 +1024,21 @@ def new_observation(self, mode: str = cfg.NEW, obsId: str = "") -> None:
1024
1024
  self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.DIRECTORIES_LIST] = [
1025
1025
  observationWindow.lw_images_directory.item(i).text() for i in range(observationWindow.lw_images_directory.count())
1026
1026
  ]
1027
+
1028
+ # check if exif data must be used
1027
1029
  self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.USE_EXIF_DATE] = observationWindow.rb_use_exif.isChecked()
1030
+
1031
+ # ask if the value of the exif date time of the first picture must be substracted
1032
+ # TODO: improve this
1033
+ if self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.USE_EXIF_DATE]:
1034
+ response = dialog.MessageDialog(
1035
+ cfg.programName,
1036
+ "You choose to use the EXIF metadata. Do you want to substract the date time value of the first picture?",
1037
+ (cfg.YES, cfg.NO),
1038
+ )
1039
+ self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.SUBSTRACT_FIRST_EXIF_DATE] = response == cfg.YES
1040
+
1041
+ # check if time lapse
1028
1042
  if observationWindow.rb_time_lapse.isChecked():
1029
1043
  self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.TIME_LAPSE] = observationWindow.sb_time_lapse.value()
1030
1044
  else:
@@ -1086,7 +1100,6 @@ def new_observation(self, mode: str = cfg.NEW, obsId: str = "") -> None:
1086
1100
  return "Observation not launched"
1087
1101
 
1088
1102
  if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.IMAGES:
1089
- # QMessageBox.critical(self, cfg.programName, "Observation from images directory is not yet implemented")
1090
1103
  initialize_new_images_observation(self)
1091
1104
 
1092
1105
  self.load_tw_events(self.observationId)
@@ -1286,7 +1299,7 @@ def check_creation_date(self) -> Tuple[int, dict]:
1286
1299
  def init_mpv(self):
1287
1300
  """Start mpv process and embed it in the PySide6 application."""
1288
1301
 
1289
- print("start MPV process")
1302
+ logging.debug("function: init_mpv")
1290
1303
 
1291
1304
  """
1292
1305
  print(f"{self.winId()=}")
@@ -1313,7 +1326,7 @@ def init_mpv(self):
1313
1326
  def send_command(command):
1314
1327
  """Send a JSON command to the mpv IPC server."""
1315
1328
 
1316
- print(f"send commnand {command}")
1329
+ logging.debug("function: send commnand {command}")
1317
1330
  # print(f"{self.mpv_process=}")
1318
1331
 
1319
1332
  try:
@@ -1984,6 +1984,6 @@ def project2dataframe(pj: dict, observations_list: list = []) -> pd.DataFrame:
1984
1984
 
1985
1985
  pd.DataFrame(data).info()
1986
1986
 
1987
- print(pd.DataFrame(data))
1987
+ # print(pd.DataFrame(data))
1988
1988
 
1989
1989
  return pd.DataFrame(data)
@@ -21,7 +21,9 @@ Copyright 2012-2025 Olivier Friard
21
21
 
22
22
  """
23
23
 
24
+ import logging
24
25
  from typing import Tuple
26
+
25
27
  from PySide6.QtCore import Qt
26
28
  from PySide6.QtWidgets import QAbstractItemView
27
29
 
@@ -60,7 +62,10 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
60
62
  data: list = []
61
63
  not_paired: list = []
62
64
 
65
+ # check if observations changed
63
66
  if hash(str(self.pj[cfg.OBSERVATIONS])) != self.mem_hash_obs:
67
+ logging.debug("observations changed")
68
+
64
69
  for obs in sorted(list(pj[cfg.OBSERVATIONS].keys())):
65
70
  date = pj[cfg.OBSERVATIONS][obs]["date"].replace("T", " ")
66
71
  descr = util.eol2space(pj[cfg.OBSERVATIONS][obs][cfg.DESCRIPTION])
@@ -124,11 +129,12 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
124
129
  data, header=fields_list + indep_var_header, column_type=column_type, not_paired=not_paired
125
130
  )
126
131
  self.data = data
132
+ self.not_paired = not_paired
127
133
  self.mem_hash_obs = hash(str(self.pj[cfg.OBSERVATIONS]))
128
134
 
129
135
  else:
130
136
  obsList = observations_list.observationsList_widget(
131
- self.data, header=fields_list + indep_var_header, column_type=column_type, not_paired=not_paired
137
+ self.data, header=fields_list + indep_var_header, column_type=column_type, not_paired=self.not_paired
132
138
  )
133
139
 
134
140
  if windows_title:
boris/state_events.py CHANGED
@@ -109,7 +109,7 @@ def fix_unpaired_events(self, silent_mode: bool = False):
109
109
  return
110
110
 
111
111
  fix_at_time = w.time_widget.get_time()
112
- if fix_at_time is None:
112
+ if fix_at_time.is_nan():
113
113
  QMessageBox.warning(
114
114
  self,
115
115
  cfg.programName,
boris/version.py CHANGED
@@ -20,5 +20,5 @@ This file is part of BORIS.
20
20
 
21
21
  """
22
22
 
23
- __version__ = "9.3.4"
24
- __version_date__ = "2025-05-08"
23
+ __version__ = "9.3.5"
24
+ __version_date__ = "2025-05-12"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boris-behav-obs
3
- Version: 9.3.4
3
+ Version: 9.3.5
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
@@ -10,22 +10,22 @@ boris/behaviors_coding_map.py,sha256=xIGJxp2eghrpiGDmYH73eJPERuyc4A_54uT-Got3zTs
10
10
  boris/boris_cli.py,sha256=n0OiVvZM1gM6E7yKaff9wlgmpAGK4TK052VRi8AabJo,13196
11
11
  boris/cmd_arguments.py,sha256=oWb-FvhKLbKJhATlTHy9muWu8XnnUfOZ-3Fmz2M8Yzc,1848
12
12
  boris/coding_pad.py,sha256=fBKdp7DDyupySJosIYtqNd8s2E-GruzCgVhDFsoVWKE,10986
13
- boris/config.py,sha256=rPfhmdE5XilliNjioC1KuDL_LNze4MZBDy_p0zK4tt8,17349
13
+ boris/config.py,sha256=PQXm3gJgdCtPOFKLH65sDGw8sk_cdMP53p6eMrlRJl0,17457
14
14
  boris/config_file.py,sha256=1-2ZmTvKET57rwrLR1dXblt0AxMpGB1LAiHxu-Sy8es,13543
15
15
  boris/connections.py,sha256=rVI18AuXh8cEnnoCKJk0RMWAaiNOpiaS554Okgk3SBY,19383
16
16
  boris/converters.py,sha256=c1Jps-URoglY5ILQHz-pCCf6-4DFUHZLtqr_ofsrFg0,11722
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=EK19Xn0Z_sxu9TaqeRimMzzpn9ThZdTqUtlJJXz6pVY,233512
19
+ boris/core.py,sha256=kmWCzeMv-26bL3a2f_NDnfyes2jLehVwh-Hmlg7c1dI,230405
20
20
  boris/core_qrc.py,sha256=J0kom27nrmi4-TCDLJZCZbtUAitiXQ4WEDfErawuxt8,639879
21
21
  boris/core_ui.py,sha256=SeC26uveDCjrCBLsRPuQ6FaapKfON_HIRcQStEDLhl4,76384
22
22
  boris/db_functions.py,sha256=Uw9wWH_Pe-qNzpV1k21YG_jKsoOmfY_iiK_7ARZHGDc,13352
23
23
  boris/dev.py,sha256=9pUElbjl9g17rFUJXX5aVSu55_iIKIuDxNdrB0DI_d0,3671
24
- boris/dialog.py,sha256=AxqXiTNiNOM5Ec9hGQ54aSeG-0jN_rycQdxJvhM7XWg,34956
24
+ boris/dialog.py,sha256=ppgaWBLCPfXHLK3_9xpu8h0Nj_m0Ls_4dsZ6lym5wjE,34972
25
25
  boris/duration_widget.py,sha256=GjZgCAMGOcsNjoPiRImEVe6yMkH2vuNoh44ulpd5nlg,6924
26
- boris/edit_event.py,sha256=2hpxn9DYuJW1CK02hF4iU0--J_0C_KTiN9gGZ1frJBc,7678
27
- boris/edit_event_ui.py,sha256=vhglQrhkF9tt0HVlkXnkG7symW0eOFA7nhbk6l_qn3k,7772
28
- boris/event_operations.py,sha256=JNSK9zTbVaPSP2H4hGUtbygyBlFX4hmJXjOqKYRxKoc,38670
26
+ boris/edit_event.py,sha256=IMZnopPALNJHgGenl_kMZjqO_Q9GKtyEcyVq5w5VGyU,8001
27
+ boris/edit_event_ui.py,sha256=qFgt00cejGB6UGC1mFkyZcsIAdvMeYMK0WYjZtJl1T0,9207
28
+ boris/event_operations.py,sha256=bqUZjgJaJ1Z8oTiidG9wfCp2LLUH1Zf4kBDeg_yjC-o,38514
29
29
  boris/events_cursor.py,sha256=VPY_ygD0fxE5lp25mcd2l00XQXurCR6hprffF4tKRbU,2078
30
30
  boris/events_snapshots.py,sha256=PjWzQvUGQtIcEc_7FDsRphf7fAhhTccQgYc2eQSA65g,27621
31
31
  boris/exclusion_matrix.py,sha256=ff88xD6aqc8bpIuj9ZCz9ju_HeqqgibQwoaJrIOJ6RI,5272
@@ -46,8 +46,8 @@ boris/modifiers_coding_map.py,sha256=oT56ZY_PXhEJsMoblEsyNMAPbDpv7ZMOCnvmt7Ibx_Y
46
46
  boris/mpv-1.0.3.py,sha256=EXRtzQqFjOn4wMC6482Ilq3fNQ9N1GRP1VxwLzdeaBY,88077
47
47
  boris/mpv.py,sha256=EfzIHjPbgewG4w3smEtqEUPZoVwYmMQkL4Q8ZyW-a58,76410
48
48
  boris/mpv2.py,sha256=IUI4t4r9GYX7G5OXTjd3RhMMOkDdfal_15buBgksLsk,92152
49
- boris/observation.py,sha256=d-7q-RkMHuLDV87nF4yahvDFPYhlXp6GmE80vckn5zU,57073
50
- boris/observation_operations.py,sha256=5OLdnnzdlazhevJzalDMtXSxo9dRZOLN_5jVE54ZnKE,106039
49
+ boris/observation.py,sha256=PcyRjeWnDDniug-cJTr9tqfL0lfMQ02hMk4xY7ZxIvk,57029
50
+ boris/observation_operations.py,sha256=6krVy8PrxZ_EUHzUN9FA6Qy17v8QhayVt0uMOMlSDac,106603
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,22 +63,22 @@ boris/plugins.py,sha256=CCS1I44OMkGZqcfLGKNPGfEQXPgngocy5YhWveXQPKM,10029
63
63
  boris/preferences.py,sha256=qPfd9Tyg7u30kXwVqMOgkdy2RXri9bItRa5U2-ZVQmg,16847
64
64
  boris/preferences_ui.py,sha256=D2bFLb3E0m6IwSeqKoItRDiqvPmJGoeXLHF2K02n1Zo,26293
65
65
  boris/project.py,sha256=nyXfCDY_rLP3jC1QGv-280jUKgbABqESjOm7I19rJ1U,86432
66
- boris/project_functions.py,sha256=eP-YEjoFvZXEPuzNPBeXsRDYurt0wHP6B09a8k8SlDc,80792
66
+ boris/project_functions.py,sha256=7Pf-u7rPKQGWxO2AwLuR4WljxOpuoH6bxsAEkttPCzw,80794
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
70
70
  boris/qrc_boris5.py,sha256=prnOw7VGXWXRuVCYp_yIrmWhrlG1F9rx-3BQvkPenjY,161608
71
71
  boris/select_modifiers.py,sha256=42uG9F75pfPoPJ-blp-vFgmpBpVJtL42FlIxpNpq9z4,13319
72
- boris/select_observations.py,sha256=HM0suMes1YxVwQ-Xakw7ROaxbN0EyeSiZFZNrTQIHAA,7886
72
+ boris/select_observations.py,sha256=k7c3FNVQW74YGH9oFmtHXRVCRnpKGhjCVk3cQtyLML8,8027
73
73
  boris/select_subj_behav.py,sha256=ulXbsRY-AIyQRSwXhVlvkNRS_eqWaCvkDKTTyOLqvoE,11742
74
- boris/state_events.py,sha256=R5CcT_cldnxqhQkvX_V1AMNxH0Nc6jLzMZYiWkeJwAE,7770
74
+ boris/state_events.py,sha256=Vrxn3CxC3yf8aeFE76l24n03qgaQ1QXpMtprYT0pq64,7771
75
75
  boris/subjects_pad.py,sha256=lSRRGfLfD10_YpGua8RGVdKhoXlsXawGhNibPkRhuzM,3541
76
76
  boris/synthetic_time_budget.py,sha256=3Eb9onMLmgqCLd50CuxV9L8RV2ESzfaMWvPK_bXUMMk,10489
77
77
  boris/time_budget_functions.py,sha256=y5He8crz0xsTxVfz0jATwFFQVnPAIrNHja_0sF6NtRE,52551
78
78
  boris/time_budget_widget.py,sha256=z-tyITBtIz-KH1H2OdMB5a8x9QQLK7Wu96-zkC6NVDA,43213
79
79
  boris/transitions.py,sha256=_aZJfJWv3EBrtmQ7qsdTCayQo6uWU7BXqtQQgflEhr4,12250
80
80
  boris/utilities.py,sha256=4b3BjO96eR54sPXIYkHwxkUnROGgY5f5vhWKIy8THUo,52646
81
- boris/version.py,sha256=st-9YZePMhFx60fiF0uKR_IhGjVv4ik2D_Td5O2XQr0,787
81
+ boris/version.py,sha256=GfkFu2oCmCqcl0AuaKWJ5PwDcop2IbfKoZ_56iiDrIA,787
82
82
  boris/video_equalizer.py,sha256=FartoGghFK-T53zklP70rPKYqTuzL8qdvfGlsOF2wwc,5854
83
83
  boris/video_equalizer_ui.py,sha256=1CG3s79eM4JAbaCx3i1ILZXLceb41_gGXlOLNfpBgnw,10142
84
84
  boris/video_operations.py,sha256=mh3iR__Sm2KnV44L_sW2pOo3AgLwlM7wiTnnqQiAVs4,9381
@@ -96,9 +96,9 @@ boris/portion/dict.py,sha256=SyHxc7PfDw2ufNLFQycwJtzmRfL48rDp4UrM2KN7IWc,11282
96
96
  boris/portion/func.py,sha256=3TkQtFKLfsqntwd27HSGHceFhnXHmT-EbNMqktElC5Q,2174
97
97
  boris/portion/interval.py,sha256=bAdUiJjGeUAPgsBAImwNeviiwfQq5odfhFZccAWzOTA,20299
98
98
  boris/portion/io.py,sha256=ppNeRpiLNrocF1yzGeuEUIhYMf2LfsR-cji3d0nmvUs,6371
99
- boris_behav_obs-9.3.4.dist-info/licenses/LICENSE.TXT,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
100
- boris_behav_obs-9.3.4.dist-info/METADATA,sha256=G03KB1bA9U-pdA0egf0dsvhkvRG2O3jQZudOyEdjfyA,4514
101
- boris_behav_obs-9.3.4.dist-info/WHEEL,sha256=oSJJyWjO7Z2XSScFQUpXG1HL-N0sFMqqeKVVbZTPkWc,109
102
- boris_behav_obs-9.3.4.dist-info/entry_points.txt,sha256=k__8XvFi4vaA4QFvQehCZjYkKmZH34HSAJI2iYCWrMs,52
103
- boris_behav_obs-9.3.4.dist-info/top_level.txt,sha256=fJSgm62S7WesiwTorGbOO4nNN0yzgZ3klgfGi3Px4qI,6
104
- boris_behav_obs-9.3.4.dist-info/RECORD,,
99
+ boris_behav_obs-9.3.5.dist-info/licenses/LICENSE.TXT,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
100
+ boris_behav_obs-9.3.5.dist-info/METADATA,sha256=X_4eXVCfsqHmHTFqUlJXwAAwNbqVSwEwJTl78vDXC3o,4514
101
+ boris_behav_obs-9.3.5.dist-info/WHEEL,sha256=joeZ_q2kZqPjVkNy_YbjGrynLS6bxmBj74YkvIORXVI,109
102
+ boris_behav_obs-9.3.5.dist-info/entry_points.txt,sha256=k__8XvFi4vaA4QFvQehCZjYkKmZH34HSAJI2iYCWrMs,52
103
+ boris_behav_obs-9.3.5.dist-info/top_level.txt,sha256=fJSgm62S7WesiwTorGbOO4nNN0yzgZ3klgfGi3Px4qI,6
104
+ boris_behav_obs-9.3.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.3.1)
2
+ Generator: setuptools (80.4.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any