boris-behav-obs 9.0.8__py2.py3-none-any.whl → 9.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/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:
@@ -29,7 +29,7 @@ import tablib
29
29
  import pickle
30
30
 
31
31
  from PySide6.QtCore import Qt
32
- from PySide6.QtGui import QColor, QFont
32
+ from PySide6.QtGui import QFont
33
33
  from PySide6.QtWidgets import QApplication, QFileDialog, QListWidgetItem, QMessageBox, QTableWidgetItem
34
34
 
35
35
 
@@ -223,7 +223,7 @@ def select_behaviors(
223
223
  paramPanelWindow.resize(800, 600)
224
224
  paramPanelWindow.setWindowTitle(title)
225
225
  paramPanelWindow.lbBehaviors.setText(text)
226
- for w in [
226
+ for w in (
227
227
  paramPanelWindow.lwSubjects,
228
228
  paramPanelWindow.pbSelectAllSubjects,
229
229
  paramPanelWindow.pbUnselectAllSubjects,
@@ -233,7 +233,7 @@ def select_behaviors(
233
233
  paramPanelWindow.cbExcludeBehaviors,
234
234
  paramPanelWindow.frm_time,
235
235
  paramPanelWindow.frm_time_bin_size,
236
- ]:
236
+ ):
237
237
  w.setVisible(False)
238
238
 
239
239
  if behavioral_categories:
@@ -318,7 +318,7 @@ def import_ethogram_from_dict(self, project: dict):
318
318
  if self.twBehaviors.rowCount():
319
319
  response = dialog.MessageDialog(
320
320
  cfg.programName,
321
- ("Some behaviors are already configured. " "Do you want to append behaviors or replace them?"),
321
+ ("Some behaviors are already configured. Do you want to append behaviors or replace them?"),
322
322
  [cfg.APPEND, cfg.REPLACE, cfg.CANCEL],
323
323
  )
324
324
  if response == cfg.REPLACE:
@@ -473,7 +473,7 @@ def import_behaviors_from_project(self):
473
473
  import ethogram from a BORIS project file
474
474
  """
475
475
  file_name, _ = QFileDialog.getOpenFileName(
476
- self, "Import behaviors from BORIS project file", "", ("Project files (*.boris *.boris.gz);;" "All files (*)")
476
+ self, "Import behaviors from BORIS project file", "", ("Project files (*.boris *.boris.gz);;All files (*)")
477
477
  )
478
478
  if not file_name:
479
479
  return
@@ -887,7 +887,7 @@ def import_subjects_from_project(self):
887
887
  """
888
888
 
889
889
  file_name, _ = QFileDialog().getOpenFileName(
890
- self, "Import subjects from project file", "", ("Project files (*.boris *.boris.gz);;" "All files (*)")
890
+ self, "Import subjects from project file", "", ("Project files (*.boris *.boris.gz);;All files (*)")
891
891
  )
892
892
  if not file_name:
893
893
  return
@@ -907,7 +907,7 @@ def import_subjects_from_project(self):
907
907
  if self.twSubjects.rowCount():
908
908
  response = dialog.MessageDialog(
909
909
  cfg.programName,
910
- ("There are subjects already configured. " "Do you want to append subjects or replace them?"),
910
+ ("There are subjects already configured. Do you want to append subjects or replace them?"),
911
911
  [cfg.APPEND, cfg.REPLACE, cfg.CANCEL],
912
912
  )
913
913
 
@@ -941,7 +941,7 @@ def import_subjects_from_text_file(self):
941
941
  if self.twSubjects.rowCount():
942
942
  response = dialog.MessageDialog(
943
943
  cfg.programName,
944
- ("There are subjects already configured. " "Do you want to append subjects or replace them?"),
944
+ ("There are subjects already configured. Do you want to append subjects or replace them?"),
945
945
  [cfg.APPEND, cfg.REPLACE, cfg.CANCEL],
946
946
  )
947
947
 
@@ -1048,7 +1048,7 @@ def import_indep_variables_from_project(self):
1048
1048
  self,
1049
1049
  "Import independent variables from project file",
1050
1050
  "",
1051
- ("Project files (*.boris *.boris.gz);;" "All files (*)"),
1051
+ ("Project files (*.boris *.boris.gz);;All files (*)"),
1052
1052
  )
1053
1053
  if not file_name:
1054
1054
  return
@@ -55,9 +55,7 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
55
55
  indep_var_header.append(pj[cfg.INDEPENDENT_VARIABLES][idx]["label"])
56
56
  column_type.append(pj[cfg.INDEPENDENT_VARIABLES][idx]["type"])
57
57
 
58
- state_events_list = [
59
- pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] for x in pj[cfg.ETHOGRAM] if cfg.STATE in pj[cfg.ETHOGRAM][x][cfg.TYPE].upper()
60
- ]
58
+ state_events_list = util.state_behavior_codes(pj[cfg.ETHOGRAM])
61
59
 
62
60
  data: list = []
63
61
  not_paired: list = []
@@ -38,6 +38,8 @@ def choose_obs_subj_behav_category(
38
38
  selected_observations: list,
39
39
  start_coding: Optional[dec] = dec("NaN"), # Union[..., None]
40
40
  end_coding: Optional[dec] = dec("NaN"),
41
+ start_interval: Optional[dec] = dec("NaN"),
42
+ end_interval: Optional[dec] = dec("NaN"),
41
43
  maxTime: Optional[dec] = None,
42
44
  show_include_modifiers: bool = True,
43
45
  show_exclude_non_coded_behaviors: bool = True,
@@ -66,7 +68,7 @@ def choose_obs_subj_behav_category(
66
68
  "selected behaviors": selectedBehaviors,
67
69
  "include modifiers": True/False,
68
70
  "exclude behaviors": True/False,
69
- "time": TIME_FULL_OBS / TIME_EVENTS / TIME_ARBITRARY_INTERVAL
71
+ "time": TIME_FULL_OBS / TIME_EVENTS / TIME_ARBITRARY_INTERVAL / TIME_OBS_INTERVAL
70
72
  "start time": startTime,
71
73
  "end time": endTime
72
74
  }
@@ -97,6 +99,8 @@ def choose_obs_subj_behav_category(
97
99
  paramPanelWindow.media_duration = maxTime
98
100
  paramPanelWindow.start_coding = start_coding
99
101
  paramPanelWindow.end_coding = end_coding
102
+ paramPanelWindow.start_interval = start_interval
103
+ paramPanelWindow.end_interval = end_interval
100
104
 
101
105
  if self.timeFormat == cfg.S:
102
106
  paramPanelWindow.start_time.rb_seconds.setChecked(True)
@@ -112,6 +116,7 @@ def choose_obs_subj_behav_category(
112
116
  paramPanelWindow.rb_observed_events.setEnabled(False)
113
117
  paramPanelWindow.frm_time_interval.setVisible(False)
114
118
  paramPanelWindow.rb_user_defined.setVisible(False)
119
+ paramPanelWindow.rb_obs_interval.setVisible(False)
115
120
  paramPanelWindow.rb_media_duration.setVisible(False)
116
121
  else:
117
122
  paramPanelWindow.frm_time_interval.setEnabled(False)
@@ -242,6 +247,10 @@ def choose_obs_subj_behav_category(
242
247
  )
243
248
  return {cfg.SELECTED_SUBJECTS: [], cfg.SELECTED_BEHAVIORS: []}
244
249
 
250
+ elif paramPanelWindow.rb_obs_interval.isChecked() and not ((start_interval is None) or start_interval.is_nan()):
251
+ startTime = paramPanelWindow.start_time.get_time()
252
+ endTime = paramPanelWindow.end_time.get_time()
253
+
245
254
  else:
246
255
  startTime = None
247
256
  endTime = None
@@ -255,6 +264,8 @@ def choose_obs_subj_behav_category(
255
264
  time_param = cfg.TIME_FULL_OBS
256
265
  if paramPanelWindow.rb_observed_events.isChecked():
257
266
  time_param = cfg.TIME_EVENTS
267
+ if paramPanelWindow.rb_obs_interval.isChecked():
268
+ time_param = cfg.TIME_OBS_INTERVAL
258
269
  if paramPanelWindow.rb_user_defined.isChecked():
259
270
  time_param = cfg.TIME_ARBITRARY_INTERVAL
260
271