boris-behav-obs 9.0.7__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/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 -4
- boris/config_file.py +8 -8
- boris/cooccurence.py +9 -1
- boris/core.py +178 -62
- boris/event_operations.py +3 -5
- 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 +4 -4
- boris/version.py +2 -2
- boris/write_event.py +2 -1
- {boris_behav_obs-9.0.7.dist-info → boris_behav_obs-9.1.dist-info}/METADATA +1 -1
- {boris_behav_obs-9.0.7.dist-info → boris_behav_obs-9.1.dist-info}/RECORD +32 -32
- {boris_behav_obs-9.0.7.dist-info → boris_behav_obs-9.1.dist-info}/WHEEL +1 -1
- {boris_behav_obs-9.0.7.dist-info → boris_behav_obs-9.1.dist-info}/LICENSE.TXT +0 -0
- {boris_behav_obs-9.0.7.dist-info → boris_behav_obs-9.1.dist-info}/entry_points.txt +0 -0
- {boris_behav_obs-9.0.7.dist-info → boris_behav_obs-9.1.dist-info}/top_level.txt +0 -0
boris/event_operations.py
CHANGED
|
@@ -40,6 +40,7 @@ from .edit_event import DlgEditEvent, EditSelectedEvents
|
|
|
40
40
|
|
|
41
41
|
from PySide6.QtWidgets import QMessageBox, QInputDialog, QLineEdit, QAbstractItemView, QApplication
|
|
42
42
|
from PySide6.QtCore import QTime, Qt
|
|
43
|
+
from PySide6.QtGui import QClipboard
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
def add_event(self):
|
|
@@ -219,9 +220,6 @@ def filter_events(self):
|
|
|
219
220
|
parameters = select_subj_behav.choose_obs_subj_behav_category(
|
|
220
221
|
self,
|
|
221
222
|
selected_observations=[], # empty selection of observations for selecting all subjects and behaviors
|
|
222
|
-
start_coding=dec("NaN"),
|
|
223
|
-
end_coding=dec("NaN"),
|
|
224
|
-
maxTime=None,
|
|
225
223
|
show_include_modifiers=False,
|
|
226
224
|
show_exclude_non_coded_behaviors=False,
|
|
227
225
|
by_category=False,
|
|
@@ -879,8 +877,8 @@ def copy_selected_events(self):
|
|
|
879
877
|
print(f"{copied_events=}")
|
|
880
878
|
|
|
881
879
|
cb = QApplication.clipboard()
|
|
882
|
-
cb.clear(mode=
|
|
883
|
-
cb.setText("\n".join(copied_events), mode=
|
|
880
|
+
cb.clear(mode=QClipboard.Mode.Clipboard)
|
|
881
|
+
cb.setText("\n".join(copied_events), mode=QClipboard.Mode.Clipboard)
|
|
884
882
|
|
|
885
883
|
logging.debug("Selected events copied in clipboard")
|
|
886
884
|
|
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:
|
boris/project_import_export.py
CHANGED
|
@@ -29,7 +29,7 @@ import tablib
|
|
|
29
29
|
import pickle
|
|
30
30
|
|
|
31
31
|
from PySide6.QtCore import Qt
|
|
32
|
-
from PySide6.QtGui import
|
|
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.
|
|
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);;
|
|
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);;
|
|
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.
|
|
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.
|
|
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);;
|
|
1051
|
+
("Project files (*.boris *.boris.gz);;All files (*)"),
|
|
1052
1052
|
)
|
|
1053
1053
|
if not file_name:
|
|
1054
1054
|
return
|
boris/select_observations.py
CHANGED
|
@@ -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 = []
|