boris-behav-obs 9.0.8__py2.py3-none-any.whl → 9.1.1__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/dialog.py CHANGED
@@ -20,15 +20,16 @@ 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
+ import datetime as dt
23
26
  import logging
24
- import sys
27
+ import math
25
28
  import pathlib as pl
26
- import traceback
27
29
  import platform
28
- import datetime as dt
29
30
  import subprocess
30
- from decimal import Decimal as dec
31
- from typing import Union
31
+ import sys
32
+ import traceback
32
33
 
33
34
  from PySide6.QtCore import Qt, Signal, qVersion, QRect, QTime, QDateTime, QSize
34
35
  from PySide6.QtWidgets import (
@@ -409,6 +410,9 @@ class get_time_widget(QWidget):
409
410
  set time on time widget
410
411
  """
411
412
 
413
+ if math.isnan(new_time):
414
+ return
415
+
412
416
  self.pb_sign.setText("-" if new_time < 0 else "+")
413
417
 
414
418
  # seconds
boris/edit_event.py CHANGED
@@ -21,6 +21,7 @@ This file is part of BORIS.
21
21
  """
22
22
 
23
23
  from decimal import Decimal as dec
24
+ import math
24
25
 
25
26
  from PySide6.QtWidgets import (
26
27
  QDialog,
@@ -67,14 +68,7 @@ class DlgEditEvent(QDialog, Ui_Form):
67
68
  for w in (self.lb_frame_idx, self.sb_frame_idx, self.cb_set_frame_idx_na):
68
69
  w.setVisible(False)
69
70
 
70
- if (observation_type in (cfg.LIVE, cfg.MEDIA)) or (observation_type == cfg.IMAGES and self.time_value != cfg.NA):
71
- # self.time_widget = duration_widget.Duration_widget(self.time_value)
72
- # if time_format == cfg.S:
73
- # self.time_widget.set_format_s()
74
- # if time_format == cfg.HHMMSS:
75
- # self.time_widget.set_format_hhmmss()
76
-
77
- # future time widget
71
+ if (observation_type in (cfg.LIVE, cfg.MEDIA)) or (observation_type == cfg.IMAGES and not math.isnan(self.time_value)):
78
72
  self.time_widget = dialog.get_time_widget(self.time_value)
79
73
 
80
74
  if time_format == cfg.S:
@@ -87,6 +81,7 @@ class DlgEditEvent(QDialog, Ui_Form):
87
81
  self.horizontalLayout_2.insertWidget(0, self.time_widget)
88
82
 
89
83
  if observation_type == cfg.IMAGES:
84
+ self.time_widget = dialog.get_time_widget(self.time_value)
90
85
  # hide frame index widgets
91
86
  for w in (self.lb_frame_idx, self.sb_frame_idx, self.cb_set_frame_idx_na, self.pb_set_to_current_time):
92
87
  w.setVisible(False)
boris/event_operations.py CHANGED
@@ -78,10 +78,13 @@ def add_event(self):
78
78
  )
79
79
  editWindow.setWindowTitle("Add a new event")
80
80
 
81
- sortedSubjects = [""] + sorted([self.pj[cfg.SUBJECTS][x][cfg.SUBJECT_NAME] for x in self.pj[cfg.SUBJECTS]])
81
+ sortedSubjects = [cfg.NO_FOCAL_SUBJECT] + sorted([self.pj[cfg.SUBJECTS][x][cfg.SUBJECT_NAME] for x in self.pj[cfg.SUBJECTS]])
82
+
83
+ print(f"{sortedSubjects=}")
82
84
 
83
85
  editWindow.cobSubject.addItems(sortedSubjects)
84
- editWindow.cobSubject.setCurrentIndex(editWindow.cobSubject.findText(self.currentSubject, Qt.MatchFixedString))
86
+ if self.currentSubject:
87
+ editWindow.cobSubject.setCurrentIndex(editWindow.cobSubject.findText(self.currentSubject, Qt.MatchFixedString))
85
88
 
86
89
  sortedCodes = sorted([self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] for x in self.pj[cfg.ETHOGRAM]])
87
90
 
@@ -103,7 +106,9 @@ def add_event(self):
103
106
  if self.pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_CODE] == editWindow.cobCode.currentText():
104
107
  event = self.full_event(idx)
105
108
 
106
- event[cfg.SUBJECT] = editWindow.cobSubject.currentText()
109
+ event[cfg.SUBJECT] = (
110
+ "" if editWindow.cobSubject.currentText() == cfg.NO_FOCAL_SUBJECT else editWindow.cobSubject.currentText()
111
+ )
107
112
  if editWindow.leComment.toPlainText():
108
113
  event[cfg.COMMENT] = editWindow.leComment.toPlainText()
109
114
 
@@ -152,7 +157,9 @@ def add_event(self):
152
157
  if self.pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_CODE] == editWindow.cobCode.currentText():
153
158
  event = self.full_event(idx)
154
159
 
155
- event[cfg.SUBJECT] = editWindow.cobSubject.currentText()
160
+ event[cfg.SUBJECT] = (
161
+ "" if editWindow.cobSubject.currentText() == cfg.NO_FOCAL_SUBJECT else editWindow.cobSubject.currentText()
162
+ )
156
163
  if editWindow.leComment.toPlainText():
157
164
  event[cfg.COMMENT] = editWindow.leComment.toPlainText()
158
165
 
@@ -220,9 +227,6 @@ def filter_events(self):
220
227
  parameters = select_subj_behav.choose_obs_subj_behav_category(
221
228
  self,
222
229
  selected_observations=[], # empty selection of observations for selecting all subjects and behaviors
223
- start_coding=dec("NaN"),
224
- end_coding=dec("NaN"),
225
- maxTime=None,
226
230
  show_include_modifiers=False,
227
231
  show_exclude_non_coded_behaviors=False,
228
232
  by_category=False,
@@ -687,10 +691,9 @@ def edit_event(self):
687
691
  if self.pj[cfg.ETHOGRAM][key][cfg.BEHAVIOR_CODE] == edit_window.cobCode.currentText():
688
692
  event = self.full_event(key)
689
693
  # subject
690
- if edit_window.cobSubject.currentText() == cfg.NO_FOCAL_SUBJECT:
691
- event[cfg.SUBJECT] = ""
692
- else:
693
- event[cfg.SUBJECT] = edit_window.cobSubject.currentText()
694
+ event[cfg.SUBJECT] = (
695
+ "" if edit_window.cobSubject.currentText() == cfg.NO_FOCAL_SUBJECT else edit_window.cobSubject.currentText()
696
+ )
694
697
 
695
698
  event[cfg.COMMENT] = edit_window.leComment.toPlainText()
696
699
 
@@ -749,7 +752,9 @@ def edit_event(self):
749
752
  else:
750
753
  event[cfg.TIME] = edit_window.time_widget.get_time()
751
754
 
752
- event[cfg.SUBJECT] = edit_window.cobSubject.currentText()
755
+ event[cfg.SUBJECT] = (
756
+ "" if edit_window.cobSubject.currentText() == cfg.NO_FOCAL_SUBJECT else edit_window.cobSubject.currentText()
757
+ )
753
758
  event[cfg.COMMENT] = edit_window.leComment.toPlainText()
754
759
  event["row"] = pj_event_idx
755
760
  event["original_modifiers"] = self.pj[cfg.OBSERVATIONS][self.observationId][cfg.EVENTS][pj_event_idx][
boris/events_snapshots.py CHANGED
@@ -212,7 +212,7 @@ def events_snapshots(self):
212
212
  if start < time_interval:
213
213
  start = dec("0.000")
214
214
 
215
- if cfg.POINT in behavior_state:
215
+ if behavior_state in cfg.POINT_EVENT_TYPES:
216
216
  media_path = project_functions.full_path(
217
217
  self.pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][nplayer][mediaFileIdx],
218
218
  self.projectFileName,
@@ -222,7 +222,7 @@ def events_snapshots(self):
222
222
  if vframes == 0:
223
223
  vframes = 1
224
224
 
225
- if cfg.STATE in behavior_state:
225
+ if behavior_state in cfg.STATE_EVENT_TYPES:
226
226
  if idx % 2 == 0:
227
227
  # check if stop is on same media file
228
228
  if (
@@ -413,7 +413,7 @@ def extract_events(self):
413
413
  rows = [{"occurence": util.float2decimal(r["occurence"])} for r in cursor.fetchall()]
414
414
 
415
415
  behavior_state = project_functions.event_type(behavior, self.pj[cfg.ETHOGRAM])
416
- if cfg.STATE in behavior_state and len(rows) % 2: # unpaired events
416
+ if behavior_state in cfg.STATE_EVENT_TYPES and len(rows) % 2: # unpaired events
417
417
  continue
418
418
 
419
419
  for idx, row in enumerate(rows):
@@ -472,7 +472,7 @@ def extract_events(self):
472
472
  new_extension = ".wav"
473
473
  codecs = "-vn"
474
474
 
475
- if cfg.POINT in behavior_state:
475
+ if behavior_state in cfg.POINT_EVENT_TYPES:
476
476
  globalStart = dec("0.000") if row["occurence"] < timeOffset else round(row["occurence"] - timeOffset, 3)
477
477
  start = round(
478
478
  row["occurence"]
@@ -494,7 +494,7 @@ def extract_events(self):
494
494
  3,
495
495
  )
496
496
 
497
- if cfg.STATE in behavior_state:
497
+ if behavior_state in cfg.STATE_EVENT_TYPES:
498
498
  if idx % 2 == 0:
499
499
  # check if stop is on same media file
500
500
  if (
boris/export_events.py CHANGED
@@ -72,15 +72,19 @@ def export_events_as_behavioral_sequences(self, separated_subjects=False, timed=
72
72
  if len(selected_observations) == 1:
73
73
  max_media_duration_all_obs, _ = observation_operations.media_duration(self.pj[cfg.OBSERVATIONS], selected_observations)
74
74
  start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
75
+ start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
75
76
  else:
76
77
  max_media_duration_all_obs = None
77
78
  start_coding, end_coding = dec("NaN"), dec("NaN")
79
+ start_interval, end_interval = dec("NaN"), dec("NaN")
78
80
 
79
81
  parameters = select_subj_behav.choose_obs_subj_behav_category(
80
82
  self,
81
83
  selected_observations,
82
84
  start_coding=start_coding,
83
85
  end_coding=end_coding,
86
+ start_interval=start_interval,
87
+ end_interval=end_interval,
84
88
  maxTime=max_media_duration_all_obs,
85
89
  show_include_modifiers=True,
86
90
  show_exclude_non_coded_behaviors=False,
@@ -161,15 +165,19 @@ def export_tabular_events(self, mode: str = "tabular") -> None:
161
165
  if len(selected_observations) == 1:
162
166
  max_media_duration_all_obs, _ = observation_operations.media_duration(self.pj[cfg.OBSERVATIONS], selected_observations)
163
167
  start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
168
+ start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
164
169
  else:
165
170
  max_media_duration_all_obs = None
166
171
  start_coding, end_coding = dec("NaN"), dec("NaN")
172
+ start_interval, end_interval = dec("NaN"), dec("NaN")
167
173
 
168
174
  parameters = select_subj_behav.choose_obs_subj_behav_category(
169
175
  self,
170
176
  selected_observations,
171
177
  start_coding=start_coding,
172
178
  end_coding=end_coding,
179
+ start_interval=start_interval,
180
+ end_interval=end_interval,
173
181
  maxTime=max_media_duration_all_obs,
174
182
  show_include_modifiers=False,
175
183
  show_exclude_non_coded_behaviors=False,
@@ -361,15 +369,19 @@ def export_aggregated_events(self):
361
369
  if len(selected_observations) == 1:
362
370
  max_media_duration_all_obs, _ = observation_operations.media_duration(self.pj[cfg.OBSERVATIONS], selected_observations)
363
371
  start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
372
+ start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
364
373
  else:
365
374
  max_media_duration_all_obs = None
366
375
  start_coding, end_coding = dec("NaN"), dec("NaN")
376
+ start_interval, end_interval = dec("NaN"), dec("NaN")
367
377
 
368
378
  parameters = select_subj_behav.choose_obs_subj_behav_category(
369
379
  self,
370
380
  selected_observations,
371
381
  start_coding=start_coding,
372
382
  end_coding=end_coding,
383
+ start_interval=start_interval,
384
+ end_interval=end_interval,
373
385
  maxTime=max_media_duration_all_obs,
374
386
  show_include_modifiers=False,
375
387
  show_exclude_non_coded_behaviors=False,
@@ -587,7 +599,7 @@ def export_aggregated_events(self):
587
599
  return
588
600
 
589
601
  if outputFormat == cfg.SDIS_EXT: # SDIS format
590
- out: str = "% SDIS file created by BORIS (www.boris.unito.it) " f"at {util.datetime_iso8601(dt.datetime.now())}\nTimed <seconds>;\n"
602
+ out: str = f"% SDIS file created by BORIS (www.boris.unito.it) at {util.datetime_iso8601(dt.datetime.now())}\nTimed <seconds>;\n"
591
603
  for obs_id in selected_observations:
592
604
  # observation id
593
605
  out += "\n<{}>\n".format(obs_id)
@@ -613,10 +625,7 @@ def export_aggregated_events(self):
613
625
  fileName = f"{pl.Path(exportDir) / util.safeFileName(obs_id)}.{outputFormat}"
614
626
  with open(fileName, "wb") as f:
615
627
  f.write(str.encode(out))
616
- out = (
617
- "% SDIS file created by BORIS (www.boris.unito.it) "
618
- f"at {util.datetime_iso8601(dt.datetime.now())}\nTimed <seconds>;\n"
619
- )
628
+ out = f"% SDIS file created by BORIS (www.boris.unito.it) at {util.datetime_iso8601(dt.datetime.now())}\nTimed <seconds>;\n"
620
629
 
621
630
  if flag_group:
622
631
  with open(fileName, "wb") as f:
@@ -665,11 +674,15 @@ def export_events_as_textgrid(self) -> None:
665
674
 
666
675
  start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
667
676
 
677
+ start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
678
+
668
679
  parameters = select_subj_behav.choose_obs_subj_behav_category(
669
680
  self,
670
681
  selected_observations,
671
682
  start_coding=start_coding,
672
683
  end_coding=end_coding,
684
+ start_interval=start_interval,
685
+ end_interval=end_interval,
673
686
  show_include_modifiers=False,
674
687
  show_exclude_non_coded_behaviors=False,
675
688
  maxTime=max_obs_length,
@@ -700,9 +713,7 @@ def export_events_as_textgrid(self) -> None:
700
713
  " intervals: size = {intervalsSize}\n"
701
714
  )
702
715
 
703
- interval_template = (
704
- " intervals [{count}]:\n" " xmin = {xmin}\n" " xmax = {xmax}\n" ' text = "{name}"\n'
705
- )
716
+ interval_template = ' intervals [{count}]:\n xmin = {xmin}\n xmax = {xmax}\n text = "{name}"\n'
706
717
 
707
718
  point_subject_header = (
708
719
  " item [{subject_index}]:\n"
@@ -713,7 +724,7 @@ def export_events_as_textgrid(self) -> None:
713
724
  " points: size = {intervalsSize}\n"
714
725
  )
715
726
 
716
- point_template = " points [{count}]:\n" " number = {number}\n" ' mark = "{mark}"\n'
727
+ point_template = ' points [{count}]:\n number = {number}\n mark = "{mark}"\n'
717
728
 
718
729
  # widget for results
719
730
  self.results = dialog.Results_dialog()
@@ -773,6 +784,14 @@ def export_events_as_textgrid(self) -> None:
773
784
  min_time = float(parameters[cfg.START_TIME])
774
785
  max_time = float(parameters[cfg.END_TIME])
775
786
 
787
+ if parameters["time"] == cfg.TIME_OBS_INTERVAL:
788
+ max_media_duration, _ = observation_operations.media_duration(self.pj[cfg.OBSERVATIONS], [obs_id])
789
+ obs_interval = self.pj[cfg.OBSERVATIONS][obs_id][cfg.OBSERVATION_TIME_INTERVAL]
790
+ offset = float(self.pj[cfg.OBSERVATIONS][obs_id][cfg.TIME_OFFSET])
791
+ min_time = float(obs_interval[0]) + offset
792
+ # Use max media duration for max time if no interval is defined (=0)
793
+ max_time = float(obs_interval[1]) + offset if obs_interval[1] != 0 else float(max_media_duration)
794
+
776
795
  # delete events outside time interval
777
796
  cursor.execute(
778
797
  "DELETE FROM aggregated_events WHERE observation = ? AND (start < ? AND stop < ?) OR (start > ? AND stop > ?)",
@@ -936,10 +955,7 @@ def export_events_as_textgrid(self) -> None:
936
955
 
937
956
  # POINT events
938
957
  cursor.execute(
939
- (
940
- "SELECT start, behavior FROM aggregated_events "
941
- "WHERE observation = ? AND subject = ? AND type = 'POINT' ORDER BY start"
942
- ),
958
+ ("SELECT start, behavior FROM aggregated_events WHERE observation = ? AND subject = ? AND type = 'POINT' ORDER BY start"),
943
959
  (obs_id, subject),
944
960
  )
945
961
 
@@ -148,7 +148,7 @@ def export_events_jwatcher(
148
148
  if fmf_file_path.exists():
149
149
  fmf_creation_answer = dialog.MessageDialog(
150
150
  cfg.programName,
151
- (f"The {fmf_file_path} file already exists.<br>" "What do you want to do?"),
151
+ (f"The {fmf_file_path} file already exists.<br>What do you want to do?"),
152
152
  [cfg.OVERWRITE, "Skip file creation", cfg.CANCEL],
153
153
  )
154
154
 
@@ -190,7 +190,7 @@ def export_events_jwatcher(
190
190
  if faf_file_path.exists():
191
191
  faf_creation_answer = dialog.MessageDialog(
192
192
  cfg.programName,
193
- (f"The {faf_file_path} file already exists.<br>" "What do you want to do?"),
193
+ (f"The {faf_file_path} file already exists.<br>What do you want to do?"),
194
194
  [cfg.OVERWRITE, "Skip file creation", cfg.CANCEL],
195
195
  )
196
196
  if faf_creation_answer == cfg.CANCEL:
@@ -310,6 +310,7 @@ def export_tabular_events(
310
310
  interval = parameters["time"]
311
311
 
312
312
  start_coding, end_coding, coding_duration = observation_operations.coding_time(pj[cfg.OBSERVATIONS], [obs_id])
313
+ start_interval, end_interval = observation_operations.time_intervals_range(pj[cfg.OBSERVATIONS], [obs_id])
313
314
 
314
315
  if interval == cfg.TIME_EVENTS:
315
316
  min_time = start_coding
@@ -330,6 +331,12 @@ def export_tabular_events(
330
331
  min_time = parameters[cfg.START_TIME]
331
332
  max_time = parameters[cfg.END_TIME]
332
333
 
334
+ if interval == cfg.TIME_OBS_INTERVAL:
335
+ max_media_duration, _ = observation_operations.media_duration(pj[cfg.OBSERVATIONS], [obs_id])
336
+ min_time = start_interval
337
+ # Use max media duration for max time if no interval is defined (=0)
338
+ max_time = end_interval if end_interval != 0 else max_media_duration
339
+
333
340
  logging.debug(f"min_time: {min_time} max_time: {max_time}")
334
341
 
335
342
  events_with_status = project_functions.events_start_stop(ethogram, observation[cfg.EVENTS], observation[cfg.TYPE])
@@ -647,6 +654,8 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str, force_numbe
647
654
  data = tablib.Dataset()
648
655
 
649
656
  start_coding, end_coding, coding_duration = observation_operations.coding_time(pj[cfg.OBSERVATIONS], [obsId])
657
+ start_interval, end_interval = observation_operations.time_intervals_range(pj[cfg.OBSERVATIONS], [obsId])
658
+
650
659
  if start_coding is None and end_coding is None: # no events
651
660
  return data, 0
652
661
 
@@ -668,6 +677,12 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str, force_numbe
668
677
  min_time = float(parameters[cfg.START_TIME])
669
678
  max_time = float(parameters[cfg.END_TIME])
670
679
 
680
+ if interval == cfg.TIME_OBS_INTERVAL:
681
+ max_media_duration, _ = observation_operations.media_duration(pj[cfg.OBSERVATIONS], [obsId])
682
+ min_time = float(start_interval)
683
+ # Use max media duration for max time if no interval is defined (=0)
684
+ max_time = float(end_interval) if end_interval != 0 else float(max_media_duration)
685
+
671
686
  logging.debug(f"min_time: {min_time} max_time: {max_time}")
672
687
 
673
688
  # obs description
@@ -43,8 +43,9 @@ from PySide6.QtWidgets import (
43
43
  QSlider,
44
44
  QMainWindow,
45
45
  QDockWidget,
46
+ QPushButton,
46
47
  )
47
- from PySide6.QtCore import Qt, QDateTime, QTimer
48
+ from PySide6.QtCore import Qt, QDateTime, QTimer, QObject, QEvent
48
49
  from PySide6.QtGui import QFont, QIcon, QTextCursor
49
50
 
50
51
  from PySide6 import QtTest
@@ -346,6 +347,40 @@ def coding_time(observations: dict, observations_list: list) -> Tuple[Optional[d
346
347
  return start_coding, end_coding, coding_duration
347
348
 
348
349
 
350
+ def time_intervals_range(observations: dict, observations_list: list) -> Tuple[Optional[dec], Optional[dec]]:
351
+ """
352
+ returns earliest start interval and latest end interval
353
+
354
+ Args:
355
+ observations (dict): observations of project
356
+ observations_list (list): list of selected observations
357
+
358
+ Returns:
359
+ decimal.Decimal: time of earliest start interval
360
+ decimal.Decimal: time of latest end interval
361
+
362
+ """
363
+ start_interval_list = []
364
+ end_interval_list = []
365
+ for obs_id in observations_list:
366
+ observation = observations[obs_id]
367
+ offset = observation[cfg.TIME_OFFSET]
368
+ start_interval_list.append(dec(observation[cfg.OBSERVATION_TIME_INTERVAL][0]) + offset)
369
+ end_interval_list.append(dec(observation[cfg.OBSERVATION_TIME_INTERVAL][1]) + offset)
370
+
371
+ if not start_interval_list:
372
+ earliest_start_interval = None
373
+ else:
374
+ earliest_start_interval = min([x for x in start_interval_list])
375
+
376
+ if not end_interval_list:
377
+ latest_end_interval = None
378
+ else:
379
+ latest_end_interval = min([x for x in end_interval_list])
380
+
381
+ return earliest_start_interval, latest_end_interval
382
+
383
+
349
384
  def observation_total_length(observation: dict) -> dec:
350
385
  """
351
386
  Observation media duration (if any)
@@ -2190,6 +2225,7 @@ def initialize_new_live_observation(self):
2190
2225
 
2191
2226
  # button start enabled
2192
2227
  self.pb_live_obs.setEnabled(True)
2228
+
2193
2229
  self.w_live.setVisible(True)
2194
2230
  self.w_obs_info.setVisible(True)
2195
2231
 
boris/param_panel.py CHANGED
@@ -59,6 +59,7 @@ class Param_panel(QDialog, Ui_Dialog):
59
59
  self.rb_media_duration.clicked.connect(lambda: self.rb_time_interval_selection(cfg.TIME_FULL_OBS))
60
60
  self.rb_observed_events.clicked.connect(lambda: self.rb_time_interval_selection(cfg.TIME_EVENTS))
61
61
  self.rb_user_defined.clicked.connect(lambda: self.rb_time_interval_selection(cfg.TIME_ARBITRARY_INTERVAL))
62
+ self.rb_obs_interval.clicked.connect(lambda: self.rb_time_interval_selection(cfg.TIME_OBS_INTERVAL))
62
63
 
63
64
  self.cbIncludeModifiers.stateChanged.connect(self.cb_exclude_non_coded_modifiers_visibility)
64
65
 
@@ -90,6 +91,17 @@ class Param_panel(QDialog, Ui_Dialog):
90
91
  self.frm_time_interval.setEnabled(False)
91
92
  self.frm_time_interval.setVisible(True)
92
93
 
94
+ elif button == cfg.TIME_OBS_INTERVAL:
95
+ if not ((self.start_interval is None) or self.start_interval.is_nan()):
96
+ # Set start_time and end_time widgets values even if it is not shown with
97
+ # more than 1 observation as some analyses might use it (eg: advanced event filtering)
98
+ end_interval = self.end_interval if self.end_interval != 0 else self.media_duration
99
+ self.start_time.set_time(self.start_interval)
100
+ self.end_time.set_time(end_interval)
101
+ self.frm_time_interval.setEnabled(False)
102
+ if len(self.selectedObservations) == 1:
103
+ self.frm_time_interval.setVisible(True)
104
+
93
105
  else:
94
106
  self.frm_time_interval.setVisible(False)
95
107
 
boris/param_panel_ui.py CHANGED
@@ -175,6 +175,11 @@ class Ui_Dialog(object):
175
175
 
176
176
  self.horizontalLayout_5.addWidget(self.rb_user_defined)
177
177
 
178
+ self.rb_obs_interval = QRadioButton(self.frm_time)
179
+ self.rb_obs_interval.setObjectName(u"rb_obs_interval")
180
+
181
+ self.horizontalLayout_5.addWidget(self.rb_obs_interval)
182
+
178
183
  self.rb_media_duration = QRadioButton(self.frm_time)
179
184
  self.rb_media_duration.setObjectName(u"rb_media_duration")
180
185
  self.rb_media_duration.setCheckable(True)
@@ -288,6 +293,7 @@ class Ui_Dialog(object):
288
293
  self.lb_time_interval.setText(QCoreApplication.translate("Dialog", u"Time interval", None))
289
294
  self.rb_observed_events.setText(QCoreApplication.translate("Dialog", u"Observed events", None))
290
295
  self.rb_user_defined.setText(QCoreApplication.translate("Dialog", u"User defined", None))
296
+ self.rb_obs_interval.setText(QCoreApplication.translate("Dialog", u"Interval of observation", None))
291
297
  self.rb_media_duration.setText(QCoreApplication.translate("Dialog", u"Media file(s) duration", None))
292
298
  self.lbStartTime.setText(QCoreApplication.translate("Dialog", u"Start time", None))
293
299
  self.label_2.setText("")
boris/plot_events.py CHANGED
@@ -47,7 +47,7 @@ def default_value(ethogram, behavior, parameter):
47
47
  return value for duration in case of point event
48
48
  """
49
49
  default_value_ = 0
50
- if project_functions.event_type(behavior, ethogram) == "POINT EVENT" and parameter in ["duration"]:
50
+ if project_functions.event_type(behavior, ethogram) in cfg.POINT_EVENT_TYPES and parameter in ["duration"]:
51
51
  default_value_ = "NA"
52
52
  return default_value_
53
53
 
@@ -196,7 +196,7 @@ def create_behaviors_bar_plot(
196
196
  except Exception:
197
197
  max_time = float(obs_length)
198
198
 
199
- if param["time"] == cfg.TIME_ARBITRARY_INTERVAL:
199
+ if param["time"] in (cfg.TIME_ARBITRARY_INTERVAL, cfg.TIME_OBS_INTERVAL):
200
200
  min_time = float(start_time)
201
201
  max_time = float(end_time)
202
202
 
@@ -235,7 +235,7 @@ def create_behaviors_bar_plot(
235
235
  for behavior in distinct_behav:
236
236
  # number of occurences
237
237
  cursor.execute(
238
- ("SELECT COUNT(*) AS count FROM aggregated_events " "WHERE observation = ? AND subject = ? AND behavior = ?"),
238
+ ("SELECT COUNT(*) AS count FROM aggregated_events WHERE observation = ? AND subject = ? AND behavior = ?"),
239
239
  (
240
240
  obs_id,
241
241
  subject,
@@ -246,7 +246,7 @@ def create_behaviors_bar_plot(
246
246
  behaviors[subject][behavior]["number of occurences"] = 0 if row["count"] is None else row["count"]
247
247
 
248
248
  # total duration
249
- if cfg.STATE in project_functions.event_type(behavior, pj[cfg.ETHOGRAM]):
249
+ if project_functions.event_type(behavior, pj[cfg.ETHOGRAM]) in cfg.STATE_EVENT_TYPES:
250
250
  cursor.execute(
251
251
  (
252
252
  "SELECT SUM(stop - start) AS duration FROM aggregated_events "
@@ -290,7 +290,7 @@ def create_behaviors_bar_plot(
290
290
  except Exception:
291
291
  colors.append("darkgray")
292
292
 
293
- if cfg.STATE in project_functions.event_type(behavior, pj[cfg.ETHOGRAM]):
293
+ if project_functions.event_type(behavior, pj[cfg.ETHOGRAM]) in cfg.STATE_EVENT_TYPES:
294
294
  durations.append(behaviors[subject][behavior]["duration"])
295
295
  x_labels_duration.append(behavior)
296
296
 
@@ -440,6 +440,13 @@ def create_events_plot(
440
440
  min_time = 0.0
441
441
  max_time = float(obs_length)
442
442
 
443
+ if interval == cfg.TIME_OBS_INTERVAL:
444
+ obs_interval = self.pj[cfg.OBSERVATIONS][obs_id][cfg.OBSERVATION_TIME_INTERVAL]
445
+ offset = float(self.pj[cfg.OBSERVATIONS][obs_id][cfg.TIME_OFFSET])
446
+ min_time = float(obs_interval[0]) + offset
447
+ # Use max media duration for max time if no interval is defined (=0)
448
+ max_time = float(obs_interval[1]) + offset if obs_interval[1] != 0 else float(obs_length)
449
+
443
450
  if interval == cfg.TIME_EVENTS:
444
451
  try:
445
452
  min_time = float(self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS][0][0]) # first event
@@ -523,7 +530,7 @@ def create_events_plot(
523
530
 
524
531
  # total duration
525
532
  cursor.execute(
526
- ("SELECT start, stop FROM aggregated_events " "WHERE subject = ? AND behavior = ? AND modifiers = ?"),
533
+ ("SELECT start, stop FROM aggregated_events WHERE subject = ? AND behavior = ? AND modifiers = ?"),
527
534
  (
528
535
  subject,
529
536
  behavior,
@@ -37,10 +37,11 @@ from PySide6.QtCore import Qt
37
37
  from . import config as cfg
38
38
  from . import db_functions
39
39
  from . import dialog
40
+ from . import observation_operations
40
41
  from . import portion as I
42
+ from . import project_functions
41
43
  from . import utilities as util
42
44
  from . import version
43
- from . import observation_operations
44
45
 
45
46
 
46
47
  def check_observation_exhaustivity(
@@ -583,9 +584,9 @@ def create_subtitles(pj: dict, selected_observations: list, parameters: dict, ex
583
584
  return "", ""
584
585
  else:
585
586
  return (
586
- f"""<font color="{cfg.subtitlesColors[
587
- parameters[cfg.SELECTED_SUBJECTS].index(row['subject']) % len(cfg.subtitlesColors)
588
- ]}">""",
587
+ f"""<font color="{
588
+ cfg.subtitlesColors[parameters[cfg.SELECTED_SUBJECTS].index(row["subject"]) % len(cfg.subtitlesColors)]
589
+ }">""",
589
590
  "</font>",
590
591
  )
591
592
 
@@ -652,7 +653,7 @@ def create_subtitles(pj: dict, selected_observations: list, parameters: dict, ex
652
653
  modifiers_str = f"\n{row['modifiers'].replace('|', ', ')}"
653
654
  else:
654
655
  modifiers_str = ""
655
- out += ("{idx}\n" "{start} --> {stop}\n" "{col1}{subject}: {behavior}" "{modifiers}" "{col2}\n\n").format(
656
+ out += ("{idx}\n{start} --> {stop}\n{col1}{subject}: {behavior}{modifiers}{col2}\n\n").format(
656
657
  idx=idx + 1,
657
658
  start=util.seconds2time(row["start"]).replace(".", ","),
658
659
  stop=util.seconds2time(row["stop"] if row["type"] == cfg.STATE else row["stop"] + cfg.POINT_EVENT_ST_DURATION).replace(
@@ -760,7 +761,7 @@ def create_subtitles(pj: dict, selected_observations: list, parameters: dict, ex
760
761
  else:
761
762
  modifiers_str = ""
762
763
 
763
- out += ("{idx}\n" "{start} --> {stop}\n" "{col1}{subject}: {behavior}" "{modifiers}" "{col2}\n\n").format(
764
+ out += ("{idx}\n{start} --> {stop}\n{col1}{subject}: {behavior}{modifiers}{col2}\n\n").format(
764
765
  idx=idx + 1,
765
766
  start=util.seconds2time(row["start"] - init).replace(".", ","),
766
767
  stop=util.seconds2time(
@@ -1531,12 +1532,12 @@ def event_type(code: str, ethogram: dict) -> str | None:
1531
1532
  code (str): behavior code
1532
1533
 
1533
1534
  Returns:
1534
- str: "STATE EVENT", "POINT EVENT" or None if code not found in ethogram
1535
+ str: behavior type
1535
1536
  """
1536
1537
 
1537
1538
  for idx in ethogram:
1538
1539
  if ethogram[idx][cfg.BEHAVIOR_CODE] == code:
1539
- return ethogram[idx][cfg.TYPE].upper()
1540
+ return ethogram[idx][cfg.TYPE]
1540
1541
  return None
1541
1542
 
1542
1543
 
@@ -1563,7 +1564,7 @@ def fix_unpaired_state_events(ethogram: dict, observation: dict, fix_at_time: de
1563
1564
  ]
1564
1565
 
1565
1566
  for behavior in sorted(set(behaviors)):
1566
- if (behavior in ethogram_behaviors) and (cfg.STATE in event_type(behavior, ethogram).upper()):
1567
+ if (behavior in ethogram_behaviors) and (event_type(behavior, ethogram) in cfg.STATE_EVENT_TYPES):
1567
1568
  lst, memTime = [], {}
1568
1569
  for event in [
1569
1570
  event
@@ -1620,8 +1621,9 @@ def fix_unpaired_state_events2(ethogram: dict, events: list, fix_at_time: dec) -
1620
1621
  behaviors: list = [event[cfg.EVENT_BEHAVIOR_FIELD_IDX] for event in events if event[cfg.EVENT_SUBJECT_FIELD_IDX] == subject]
1621
1622
 
1622
1623
  for behavior in sorted(set(behaviors)):
1623
- if (behavior in ethogram_behaviors) and (cfg.STATE in event_type(behavior, ethogram).upper()):
1624
- lst, memTime = [], {}
1624
+ if (behavior in ethogram_behaviors) and (event_type(behavior, ethogram) in cfg.STATE_EVENT_TYPES):
1625
+ lst: list = []
1626
+ memTime: dict = {}
1625
1627
  for event in [
1626
1628
  event
1627
1629
  for event in events
@@ -1883,7 +1885,15 @@ def project2dataframe(pj: dict, observations_list: list = []) -> pd.DataFrame:
1883
1885
  "Comment stop": "string",
1884
1886
  }
1885
1887
 
1886
- state_behaviors = [pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] for x in pj[cfg.ETHOGRAM] if pj[cfg.ETHOGRAM][x]["type"] == cfg.STATE_EVENT]
1888
+ """
1889
+ state_behaviors = [
1890
+ pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE]
1891
+ for x in pj[cfg.ETHOGRAM]
1892
+ if pj[cfg.ETHOGRAM][x]["type"] in (cfg.STATE_EVENT, cfg.STATE_EVENT_WITH_CODING_MAP)
1893
+ ]
1894
+ """
1895
+
1896
+ state_behaviors = util.state_behavior_codes(pj[cfg.ETHOGRAM])
1887
1897
 
1888
1898
  for obs_id in pj[cfg.OBSERVATIONS]:
1889
1899
  if observations_list and obs_id not in observations_list: