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/advanced_event_filtering.py +9 -4
- boris/analysis_plugins/number_of_occurences.py +1 -2
- boris/analysis_plugins/time_budget.py +1 -3
- boris/behavior_binary_table.py +12 -0
- boris/config.py +5 -0
- boris/config_file.py +8 -8
- boris/connections.py +3 -1
- boris/cooccurence.py +9 -1
- boris/core.py +178 -62
- boris/dialog.py +9 -5
- boris/edit_event.py +3 -8
- boris/event_operations.py +17 -12
- boris/events_snapshots.py +5 -5
- boris/export_events.py +29 -13
- boris/export_observation.py +17 -2
- boris/observation_operations.py +37 -1
- boris/param_panel.py +12 -0
- boris/param_panel_ui.py +6 -0
- boris/plot_events.py +13 -6
- boris/project_functions.py +22 -12
- boris/project_import_export.py +9 -9
- boris/select_observations.py +1 -3
- boris/select_subj_behav.py +12 -1
- boris/synthetic_time_budget.py +8 -0
- boris/time_budget_functions.py +20 -15
- boris/time_budget_widget.py +18 -7
- boris/utilities.py +3 -3
- boris/version.py +2 -2
- boris/write_event.py +2 -1
- {boris_behav_obs-9.0.8.dist-info → boris_behav_obs-9.1.1.dist-info}/METADATA +1 -1
- {boris_behav_obs-9.0.8.dist-info → boris_behav_obs-9.1.1.dist-info}/RECORD +35 -35
- {boris_behav_obs-9.0.8.dist-info → boris_behav_obs-9.1.1.dist-info}/WHEEL +1 -1
- {boris_behav_obs-9.0.8.dist-info → boris_behav_obs-9.1.1.dist-info}/LICENSE.TXT +0 -0
- {boris_behav_obs-9.0.8.dist-info → boris_behav_obs-9.1.1.dist-info}/entry_points.txt +0 -0
- {boris_behav_obs-9.0.8.dist-info → boris_behav_obs-9.1.1.dist-info}/top_level.txt +0 -0
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
|
|
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
|
-
|
|
31
|
-
|
|
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
|
|
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 = [
|
|
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
|
-
|
|
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] =
|
|
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] =
|
|
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
|
-
|
|
691
|
-
|
|
692
|
-
|
|
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] =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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 =
|
|
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
|
|
boris/export_observation.py
CHANGED
|
@@ -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>
|
|
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>
|
|
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
|
boris/observation_operations.py
CHANGED
|
@@ -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)
|
|
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"]
|
|
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
|
|
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
|
|
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
|
|
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
|
|
533
|
+
("SELECT start, stop FROM aggregated_events WHERE subject = ? AND behavior = ? AND modifiers = ?"),
|
|
527
534
|
(
|
|
528
535
|
subject,
|
|
529
536
|
behavior,
|
boris/project_functions.py
CHANGED
|
@@ -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="{
|
|
587
|
-
|
|
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
|
|
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
|
|
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:
|
|
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]
|
|
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 (
|
|
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 (
|
|
1624
|
-
lst
|
|
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
|
-
|
|
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:
|