boris-behav-obs 8.27.9__py2.py3-none-any.whl → 9.0.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/about.py +7 -5
- boris/add_modifier.py +35 -35
- boris/add_modifier_ui.py +229 -129
- boris/advanced_event_filtering.py +3 -3
- boris/analysis_plugins/__init__.py +0 -0
- boris/analysis_plugins/number_of_occurences.py +60 -0
- boris/analysis_plugins/number_of_occurences_by_independent_variable.py +72 -0
- boris/analysis_plugins/time_budget.py +95 -0
- boris/behav_coding_map_creator.py +103 -108
- boris/behavior_binary_table.py +1 -1
- boris/behaviors_coding_map.py +8 -8
- boris/coding_pad.py +6 -6
- boris/config.py +6 -0
- boris/config_file.py +1 -1
- boris/connections.py +4 -2
- boris/converters.py +2 -3
- boris/converters_ui.py +187 -110
- boris/cooccurence.py +2 -2
- boris/core.py +340 -94
- boris/core_qrc.py +16088 -13246
- boris/core_ui.py +922 -812
- boris/db_functions.py +3 -1
- boris/dialog.py +14 -13
- boris/duration_widget.py +5 -5
- boris/edit_event.py +1 -1
- boris/edit_event_ui.py +162 -88
- boris/event_operations.py +4 -25
- boris/events_cursor.py +17 -9
- boris/events_snapshots.py +5 -5
- boris/exclusion_matrix.py +1 -1
- boris/export_events.py +38 -28
- boris/export_observation.py +1 -1
- boris/external_processes.py +3 -5
- boris/geometric_measurement.py +49 -26
- boris/gui_utilities.py +31 -30
- boris/import_observations.py +2 -4
- boris/irr.py +1 -1
- boris/latency.py +1 -1
- boris/map_creator.py +77 -89
- boris/measurement_widget.py +4 -4
- boris/media_file.py +2 -4
- boris/menu_options.py +1 -3
- boris/modifiers_coding_map.py +4 -4
- boris/mpv2.py +0 -2
- boris/observation.py +124 -29
- boris/observation_operations.py +18 -40
- boris/observation_ui.py +566 -374
- boris/observations_list.py +6 -6
- boris/param_panel.py +2 -2
- boris/param_panel_ui.py +246 -141
- boris/player_dock_widget.py +16 -21
- boris/plot_data_module.py +6 -6
- boris/plot_events_rt.py +7 -8
- boris/plot_spectrogram_rt.py +7 -8
- boris/plot_waveform_rt.py +6 -7
- boris/plugins.py +79 -0
- boris/preferences.py +127 -17
- boris/preferences_ui.py +464 -240
- boris/project.py +69 -72
- boris/project_functions.py +233 -31
- boris/project_import_export.py +59 -67
- boris/project_ui.py +672 -440
- boris/qrc_boris.py +6 -3
- boris/qrc_boris5.py +6 -3
- boris/select_modifiers.py +2 -2
- boris/select_observations.py +2 -2
- boris/select_subj_behav.py +3 -3
- boris/state_events.py +1 -1
- boris/subjects_pad.py +5 -5
- boris/synthetic_time_budget.py +2 -2
- boris/time_budget_functions.py +15 -0
- boris/time_budget_widget.py +4 -4
- boris/transitions.py +34 -25
- boris/utilities.py +95 -2
- boris/version.py +2 -2
- boris/video_equalizer.py +4 -4
- boris/video_equalizer_ui.py +199 -130
- boris/video_operations.py +1 -1
- boris/view_df.py +106 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +9 -1
- {boris_behav_obs-8.27.9.dist-info → boris_behav_obs-9.0.1.dist-info}/METADATA +5 -5
- boris_behav_obs-9.0.1.dist-info/RECORD +103 -0
- {boris_behav_obs-8.27.9.dist-info → boris_behav_obs-9.0.1.dist-info}/WHEEL +1 -1
- boris/qdarkstyle/__init__.py +0 -479
- boris/qdarkstyle/__main__.py +0 -66
- boris/qdarkstyle/colorsystem.py +0 -38
- boris/qdarkstyle/dark/__init__.py +0 -1
- boris/qdarkstyle/dark/darkstyle_rc.py +0 -11379
- boris/qdarkstyle/dark/palette.py +0 -38
- boris/qdarkstyle/example/__init__.py +0 -4
- boris/qdarkstyle/example/__main__.py +0 -386
- boris/qdarkstyle/example/ui/__init__.py +0 -4
- boris/qdarkstyle/light/__init__.py +0 -1
- boris/qdarkstyle/light/lightstyle_rc.py +0 -11305
- boris/qdarkstyle/light/palette.py +0 -37
- boris/qdarkstyle/palette.py +0 -102
- boris/qdarkstyle/utils/__init__.py +0 -73
- boris/qdarkstyle/utils/__main__.py +0 -96
- boris/qdarkstyle/utils/images.py +0 -449
- boris/qdarkstyle/utils/scss.py +0 -318
- boris/vlc_local.py +0 -83
- boris_behav_obs-8.27.9.dist-info/RECORD +0 -114
- {boris_behav_obs-8.27.9.dist-info → boris_behav_obs-9.0.1.dist-info}/LICENSE.TXT +0 -0
- {boris_behav_obs-8.27.9.dist-info → boris_behav_obs-9.0.1.dist-info}/entry_points.txt +0 -0
- {boris_behav_obs-8.27.9.dist-info → boris_behav_obs-9.0.1.dist-info}/top_level.txt +0 -0
boris/project.py
CHANGED
|
@@ -24,11 +24,13 @@ import json
|
|
|
24
24
|
import logging
|
|
25
25
|
import re
|
|
26
26
|
|
|
27
|
-
from
|
|
28
|
-
from
|
|
29
|
-
from
|
|
27
|
+
from PySide6.QtCore import Qt, QDateTime
|
|
28
|
+
from PySide6.QtGui import QColor
|
|
29
|
+
from PySide6.QtWidgets import (
|
|
30
|
+
QAbstractItemView,
|
|
30
31
|
QApplication,
|
|
31
32
|
QCheckBox,
|
|
33
|
+
QColorDialog,
|
|
32
34
|
QDialog,
|
|
33
35
|
QFileDialog,
|
|
34
36
|
QHBoxLayout,
|
|
@@ -41,11 +43,9 @@ from PyQt5.QtWidgets import (
|
|
|
41
43
|
QPushButton,
|
|
42
44
|
QSizePolicy,
|
|
43
45
|
QSpacerItem,
|
|
46
|
+
QTableWidget,
|
|
44
47
|
QTableWidgetItem,
|
|
45
48
|
QVBoxLayout,
|
|
46
|
-
QColorDialog,
|
|
47
|
-
QTableWidget,
|
|
48
|
-
QAbstractItemView,
|
|
49
49
|
)
|
|
50
50
|
|
|
51
51
|
from . import add_modifier
|
|
@@ -60,11 +60,10 @@ class BehavioralCategories(QDialog):
|
|
|
60
60
|
Class for managing the behavioral categories
|
|
61
61
|
"""
|
|
62
62
|
|
|
63
|
-
def __init__(self, pj
|
|
63
|
+
def __init__(self, pj):
|
|
64
64
|
super().__init__()
|
|
65
65
|
|
|
66
66
|
self.pj = pj
|
|
67
|
-
self.dark_mode = dark_mode
|
|
68
67
|
self.setWindowTitle("Behavioral categories")
|
|
69
68
|
|
|
70
69
|
self.renamed = None
|
|
@@ -153,10 +152,12 @@ class BehavioralCategories(QDialog):
|
|
|
153
152
|
"""
|
|
154
153
|
return a color for the not editable column
|
|
155
154
|
"""
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
155
|
+
window_color = QApplication.instance().palette().window().color()
|
|
156
|
+
return QColor(
|
|
157
|
+
window_color.red() - cfg.DARKER_DIFFERENCE,
|
|
158
|
+
window_color.green() - cfg.DARKER_DIFFERENCE,
|
|
159
|
+
window_color.blue() - cfg.DARKER_DIFFERENCE,
|
|
160
|
+
)
|
|
160
161
|
|
|
161
162
|
def lw_double_clicked(self, row: int, column: int):
|
|
162
163
|
"""
|
|
@@ -302,10 +303,10 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
302
303
|
"remove all|Remove all behaviors",
|
|
303
304
|
"lower|Convert keys to lower case",
|
|
304
305
|
]
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
self.add_button_menu(behavior_button_items,
|
|
308
|
-
self.pb_behavior.setMenu(
|
|
306
|
+
self.behavior_menu = QMenu()
|
|
307
|
+
self.behavior_menu.triggered.connect(lambda x: self.behavior(action=x.statusTip()))
|
|
308
|
+
self.add_button_menu(behavior_button_items, self.behavior_menu)
|
|
309
|
+
self.pb_behavior.setMenu(self.behavior_menu)
|
|
309
310
|
|
|
310
311
|
import_button_items = [
|
|
311
312
|
"boris|from a BORIS project",
|
|
@@ -315,10 +316,10 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
315
316
|
"clipboard|from the clipboard",
|
|
316
317
|
"repository|from the BORIS repository",
|
|
317
318
|
]
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
self.add_button_menu(import_button_items,
|
|
321
|
-
self.pb_import.setMenu(
|
|
319
|
+
self.import_behaviors_menu = QMenu()
|
|
320
|
+
self.import_behaviors_menu.triggered.connect(lambda x: self.import_ethogram(action=x.statusTip()))
|
|
321
|
+
self.add_button_menu(import_button_items, self.import_behaviors_menu)
|
|
322
|
+
self.pb_import.setMenu(self.import_behaviors_menu)
|
|
322
323
|
|
|
323
324
|
self.pbBehaviorsCategories.clicked.connect(self.pbBehaviorsCategories_clicked)
|
|
324
325
|
|
|
@@ -342,10 +343,10 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
342
343
|
"lower|Convert keys to lower case",
|
|
343
344
|
]
|
|
344
345
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
self.add_button_menu(subjects_button_items,
|
|
348
|
-
self.pb_subjects.setMenu(
|
|
346
|
+
self.subject_menu = QMenu()
|
|
347
|
+
self.subject_menu.triggered.connect(lambda x: self.subjects(action=x.statusTip()))
|
|
348
|
+
self.add_button_menu(subjects_button_items, self.subject_menu)
|
|
349
|
+
self.pb_subjects.setMenu(self.subject_menu)
|
|
349
350
|
|
|
350
351
|
subjects_import_button_items = [
|
|
351
352
|
"boris|from a BORIS project",
|
|
@@ -353,10 +354,10 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
353
354
|
"text|from a text file (CSV or TSV)",
|
|
354
355
|
"clipboard|from the clipboard",
|
|
355
356
|
]
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
self.add_button_menu(subjects_import_button_items,
|
|
359
|
-
self.pbImportSubjectsFromProject.setMenu(
|
|
357
|
+
self.import_subjects_menu = QMenu()
|
|
358
|
+
self.import_subjects_menu.triggered.connect(lambda x: self.import_subjects(action=x.statusTip()))
|
|
359
|
+
self.add_button_menu(subjects_import_button_items, self.import_subjects_menu)
|
|
360
|
+
self.pbImportSubjectsFromProject.setMenu(self.import_subjects_menu)
|
|
360
361
|
|
|
361
362
|
self.pb_export_subjects.clicked.connect(lambda: project_import_export.export_subjects(self))
|
|
362
363
|
|
|
@@ -430,10 +431,12 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
430
431
|
"""
|
|
431
432
|
return a color for the not editable column
|
|
432
433
|
"""
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
434
|
+
window_color = QApplication.instance().palette().window().color()
|
|
435
|
+
return QColor(
|
|
436
|
+
window_color.red() - cfg.DARKER_DIFFERENCE,
|
|
437
|
+
window_color.green() - cfg.DARKER_DIFFERENCE,
|
|
438
|
+
window_color.blue() - cfg.DARKER_DIFFERENCE,
|
|
439
|
+
)
|
|
437
440
|
|
|
438
441
|
def add_button_menu(self, data, menu_obj):
|
|
439
442
|
"""
|
|
@@ -633,45 +636,45 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
633
636
|
Add a behaviors coding map from file
|
|
634
637
|
"""
|
|
635
638
|
|
|
636
|
-
|
|
639
|
+
file_name, _ = QFileDialog().getOpenFileName(
|
|
637
640
|
self, "Open a behaviors coding map", "", "Behaviors coding map (*.behav_coding_map);;All files (*)"
|
|
638
641
|
)
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
642
|
+
if not file_name:
|
|
643
|
+
return
|
|
644
|
+
try:
|
|
645
|
+
bcm = json.loads(open(file_name, "r").read())
|
|
646
|
+
except Exception:
|
|
647
|
+
QMessageBox.critical(self, cfg.programName, f"The file {file_name} is not a behaviors coding map.")
|
|
648
|
+
return
|
|
646
649
|
|
|
647
|
-
|
|
648
|
-
|
|
650
|
+
if "coding_map_type" not in bcm or bcm["coding_map_type"] != "BORIS behaviors coding map":
|
|
651
|
+
QMessageBox.critical(self, cfg.programName, f"The file {file_name} is not a BORIS behaviors coding map.")
|
|
649
652
|
|
|
650
|
-
|
|
651
|
-
|
|
653
|
+
if cfg.BEHAVIORS_CODING_MAP not in self.pj:
|
|
654
|
+
self.pj[cfg.BEHAVIORS_CODING_MAP] = []
|
|
652
655
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
656
|
+
bcm_code_not_found = []
|
|
657
|
+
existing_codes = [self.pj[cfg.ETHOGRAM][key][cfg.BEHAVIOR_CODE] for key in self.pj[cfg.ETHOGRAM]]
|
|
658
|
+
for code in [bcm["areas"][key][cfg.BEHAVIOR_CODE] for key in bcm["areas"]]:
|
|
659
|
+
if code not in existing_codes:
|
|
660
|
+
bcm_code_not_found.append(code)
|
|
658
661
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
662
|
+
if bcm_code_not_found:
|
|
663
|
+
QMessageBox.warning(
|
|
664
|
+
self,
|
|
665
|
+
cfg.programName,
|
|
666
|
+
("The following behavior{} are not defined in the ethogram:<br>" "{}").format(
|
|
667
|
+
"s" if len(bcm_code_not_found) > 1 else "", ",".join(bcm_code_not_found)
|
|
668
|
+
),
|
|
669
|
+
)
|
|
667
670
|
|
|
668
|
-
|
|
671
|
+
self.pj[cfg.BEHAVIORS_CODING_MAP].append(dict(bcm))
|
|
669
672
|
|
|
670
|
-
|
|
673
|
+
self.twBehavCodingMap.setRowCount(self.twBehavCodingMap.rowCount() + 1)
|
|
671
674
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
+
self.twBehavCodingMap.setItem(self.twBehavCodingMap.rowCount() - 1, 0, QTableWidgetItem(bcm["name"]))
|
|
676
|
+
codes = ", ".join([bcm["areas"][idx][cfg.BEHAVIOR_CODE] for idx in bcm["areas"]])
|
|
677
|
+
self.twBehavCodingMap.setItem(self.twBehavCodingMap.rowCount() - 1, 1, QTableWidgetItem(codes))
|
|
675
678
|
|
|
676
679
|
def remove_behaviors_coding_map(self):
|
|
677
680
|
"""
|
|
@@ -730,7 +733,7 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
730
733
|
behavioral categories manager
|
|
731
734
|
"""
|
|
732
735
|
|
|
733
|
-
bc = BehavioralCategories(self.pj
|
|
736
|
+
bc = BehavioralCategories(self.pj) # self.config_param.get(cfg.DARK_MODE, cfg.DEFAULT_FRAME_MODE)
|
|
734
737
|
|
|
735
738
|
if bc.exec_():
|
|
736
739
|
self.pj[cfg.BEHAVIORAL_CATEGORIES] = []
|
|
@@ -868,7 +871,6 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
868
871
|
color = col_diag.currentColor()
|
|
869
872
|
if color.name() == "#000000": # black -> delete color
|
|
870
873
|
self.twBehaviors.item(row, cfg.behavioursFields[cfg.COLOR]).setText("")
|
|
871
|
-
# self.twBehaviors.item(row, cfg.behavioursFields[cfg.COLOR]).setBackground(QColor(230, 230, 230))
|
|
872
874
|
self.twBehaviors.item(row, cfg.behavioursFields[cfg.COLOR]).setBackground(self.not_editable_column_color())
|
|
873
875
|
elif color.isValid():
|
|
874
876
|
self.twBehaviors.item(row, cfg.behavioursFields[cfg.COLOR]).setText(color.name())
|
|
@@ -1200,7 +1202,6 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1200
1202
|
if e == self.twBehaviors.item(r, cfg.behavioursFields[cfg.BEHAVIOR_CODE]).text():
|
|
1201
1203
|
item = QTableWidgetItem(",".join(new_excl[e]))
|
|
1202
1204
|
item.setFlags(Qt.ItemIsEnabled)
|
|
1203
|
-
# item.setBackground(QColor(230, 230, 230))
|
|
1204
1205
|
item.setBackground(self.not_editable_column_color())
|
|
1205
1206
|
self.twBehaviors.setItem(r, cfg.behavioursFields["excluded"], item)
|
|
1206
1207
|
|
|
@@ -1304,14 +1305,12 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1304
1305
|
self.twBehaviors.setItem(self.twBehaviors.rowCount() - 1, cfg.behavioursFields[field], item)
|
|
1305
1306
|
if field in (cfg.TYPE, "category", "excluded", "coding map", "modifiers"):
|
|
1306
1307
|
item.setFlags(Qt.ItemIsEnabled)
|
|
1307
|
-
# item.setBackground(QColor(230, 230, 230))
|
|
1308
1308
|
item.setBackground(self.not_editable_column_color())
|
|
1309
1309
|
if field == cfg.COLOR:
|
|
1310
1310
|
item.setFlags(Qt.ItemIsEnabled)
|
|
1311
1311
|
if QColor(self.twBehaviors.item(row, cfg.behavioursFields[field]).text()).isValid():
|
|
1312
1312
|
item.setBackground(QColor(self.twBehaviors.item(row, cfg.behavioursFields[field]).text()))
|
|
1313
1313
|
else:
|
|
1314
|
-
# item.setBackground(QColor(230, 230, 230))
|
|
1315
1314
|
item.setBackground(self.not_editable_column_color())
|
|
1316
1315
|
|
|
1317
1316
|
self.twBehaviors.scrollToBottom()
|
|
@@ -1376,17 +1375,15 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1376
1375
|
|
|
1377
1376
|
if cfg.CODING_MAP_sp in self.twBehaviors.item(row, cfg.behavioursFields[cfg.TYPE]).text():
|
|
1378
1377
|
# let user select a coding maop
|
|
1379
|
-
|
|
1378
|
+
file_name, _ = QFileDialog().getOpenFileName(
|
|
1380
1379
|
self,
|
|
1381
1380
|
"Select a modifier coding map for " f"{self.twBehaviors.item(row, cfg.behavioursFields['code']).text()} behavior",
|
|
1382
1381
|
"",
|
|
1383
1382
|
"BORIS map files (*.boris_map);;All files (*)",
|
|
1384
1383
|
)
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
if fileName:
|
|
1384
|
+
if file_name:
|
|
1388
1385
|
try:
|
|
1389
|
-
new_map = json.loads(open(
|
|
1386
|
+
new_map = json.loads(open(file_name, "r").read())
|
|
1390
1387
|
except Exception:
|
|
1391
1388
|
QMessageBox.critical(self, cfg.programName, "Error reding the coding map")
|
|
1392
1389
|
return
|
boris/project_functions.py
CHANGED
|
@@ -23,6 +23,8 @@ import gzip
|
|
|
23
23
|
import json
|
|
24
24
|
import logging
|
|
25
25
|
import os
|
|
26
|
+
import pandas as pd
|
|
27
|
+
import numpy as np
|
|
26
28
|
import pathlib as pl
|
|
27
29
|
import sys
|
|
28
30
|
from decimal import Decimal as dec
|
|
@@ -30,8 +32,8 @@ from shutil import copyfile
|
|
|
30
32
|
from typing import List, Tuple, Dict
|
|
31
33
|
|
|
32
34
|
import tablib
|
|
33
|
-
from
|
|
34
|
-
from
|
|
35
|
+
from PySide6.QtWidgets import QMessageBox, QTableWidgetItem, QAbstractItemView
|
|
36
|
+
from PySide6.QtCore import Qt
|
|
35
37
|
|
|
36
38
|
from . import config as cfg
|
|
37
39
|
from . import db_functions
|
|
@@ -294,7 +296,7 @@ def check_state_events_obs(obsId: str, ethogram: dict, observation: dict, time_f
|
|
|
294
296
|
tuple (bool, str): if OK True else False , message
|
|
295
297
|
"""
|
|
296
298
|
|
|
297
|
-
out = ""
|
|
299
|
+
out: str = ""
|
|
298
300
|
|
|
299
301
|
# check if behaviors are defined as "state event"
|
|
300
302
|
event_types = {ethogram[idx]["type"] for idx in ethogram}
|
|
@@ -304,6 +306,7 @@ def check_state_events_obs(obsId: str, ethogram: dict, observation: dict, time_f
|
|
|
304
306
|
|
|
305
307
|
subjects = [event[cfg.EVENT_SUBJECT_FIELD_IDX] for event in observation[cfg.EVENTS]]
|
|
306
308
|
ethogram_behaviors = {ethogram[idx][cfg.BEHAVIOR_CODE] for idx in ethogram}
|
|
309
|
+
state_behaviors = set(util.state_behavior_codes(ethogram))
|
|
307
310
|
|
|
308
311
|
for subject in sorted(set(subjects)):
|
|
309
312
|
behaviors = [
|
|
@@ -315,33 +318,35 @@ def check_state_events_obs(obsId: str, ethogram: dict, observation: dict, time_f
|
|
|
315
318
|
# return (False, "The behaviour <b>{}</b> is not defined in the ethogram.<br>".format(behavior))
|
|
316
319
|
continue
|
|
317
320
|
else:
|
|
318
|
-
if
|
|
319
|
-
|
|
320
|
-
memTime: dict = {}
|
|
321
|
-
for event in [
|
|
322
|
-
event
|
|
323
|
-
for event in observation[cfg.EVENTS]
|
|
324
|
-
if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] == behavior and event[cfg.EVENT_SUBJECT_FIELD_IDX] == subject
|
|
325
|
-
]:
|
|
326
|
-
behav_modif = [
|
|
327
|
-
event[cfg.EVENT_BEHAVIOR_FIELD_IDX],
|
|
328
|
-
event[cfg.EVENT_MODIFIER_FIELD_IDX],
|
|
329
|
-
]
|
|
321
|
+
if behavior not in state_behaviors:
|
|
322
|
+
continue
|
|
330
323
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
)
|
|
324
|
+
lst: list = []
|
|
325
|
+
memTime: dict = {}
|
|
326
|
+
for event in [
|
|
327
|
+
event
|
|
328
|
+
for event in observation[cfg.EVENTS]
|
|
329
|
+
if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] == behavior and event[cfg.EVENT_SUBJECT_FIELD_IDX] == subject
|
|
330
|
+
]:
|
|
331
|
+
behav_modif = [
|
|
332
|
+
event[cfg.EVENT_BEHAVIOR_FIELD_IDX],
|
|
333
|
+
event[cfg.EVENT_MODIFIER_FIELD_IDX],
|
|
334
|
+
]
|
|
335
|
+
|
|
336
|
+
if behav_modif in lst:
|
|
337
|
+
lst.remove(behav_modif)
|
|
338
|
+
del memTime[str(behav_modif)]
|
|
339
|
+
else:
|
|
340
|
+
lst.append(behav_modif)
|
|
341
|
+
memTime[str(behav_modif)] = event[cfg.EVENT_TIME_FIELD_IDX]
|
|
342
|
+
|
|
343
|
+
for event in lst:
|
|
344
|
+
out += (
|
|
345
|
+
f"The behavior <b>{behavior}</b> "
|
|
346
|
+
f"{('(modifier ' + event[1] + ') ') if event[1] else ''} is not PAIRED "
|
|
347
|
+
f'for subject "<b>{subject if subject else cfg.NO_FOCAL_SUBJECT}</b>" at '
|
|
348
|
+
f"<b>{memTime[str(event)] if time_format == cfg.S else util.seconds2time(memTime[str(event)])}</b><br>"
|
|
349
|
+
)
|
|
345
350
|
|
|
346
351
|
return (False, out) if out else (True, "No problem detected")
|
|
347
352
|
|
|
@@ -410,6 +415,10 @@ def check_project_integrity(
|
|
|
410
415
|
|
|
411
416
|
Returns:
|
|
412
417
|
str: message
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
TODO: implement check on order of events (for live and media)
|
|
421
|
+
|
|
413
422
|
"""
|
|
414
423
|
out: str = ""
|
|
415
424
|
|
|
@@ -1276,8 +1285,8 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1276
1285
|
|
|
1277
1286
|
logging.debug(f"open project: {projectFileName}")
|
|
1278
1287
|
|
|
1279
|
-
projectChanged = False
|
|
1280
|
-
msg = ""
|
|
1288
|
+
projectChanged: bool = False
|
|
1289
|
+
msg: str = ""
|
|
1281
1290
|
|
|
1282
1291
|
if not os.path.isfile(projectFileName):
|
|
1283
1292
|
return (
|
|
@@ -1515,6 +1524,11 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1515
1524
|
|
|
1516
1525
|
pj[cfg.PROJECT_VERSION] = cfg.project_format_version
|
|
1517
1526
|
|
|
1527
|
+
# sort events by time asc
|
|
1528
|
+
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
1529
|
+
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] in (cfg.LIVE, cfg.MEDIA):
|
|
1530
|
+
pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS].sort()
|
|
1531
|
+
|
|
1518
1532
|
return projectFileName, projectChanged, pj, msg
|
|
1519
1533
|
|
|
1520
1534
|
|
|
@@ -1747,3 +1761,191 @@ def explore_project(self) -> None:
|
|
|
1747
1761
|
|
|
1748
1762
|
else:
|
|
1749
1763
|
QMessageBox.information(self, cfg.programName, "No events found")
|
|
1764
|
+
|
|
1765
|
+
|
|
1766
|
+
def project2dataframe(pj: dict, observations_list: list = []) -> pd.DataFrame:
|
|
1767
|
+
# print(pj.keys())
|
|
1768
|
+
|
|
1769
|
+
# print(pj["independent_variables"])
|
|
1770
|
+
|
|
1771
|
+
# indep_var = [pj["independent_variables"][idx]["label"] for idx in pj["independent_variables"]]
|
|
1772
|
+
|
|
1773
|
+
indep_variables = dict(
|
|
1774
|
+
[(pj["independent_variables"][idx]["label"], pj["independent_variables"][idx]["type"]) for idx in pj["independent_variables"]]
|
|
1775
|
+
)
|
|
1776
|
+
|
|
1777
|
+
# print()
|
|
1778
|
+
# print(f"{indep_variables=}")
|
|
1779
|
+
|
|
1780
|
+
# n_max_set_modifiers = max([len(pj["behaviors_conf"][behavior_id]["modifiers"]) for behavior_id in pj["behaviors_conf"]])
|
|
1781
|
+
|
|
1782
|
+
# behavioral_categories
|
|
1783
|
+
behavioral_category = dict([(pj["behaviors_conf"][x]["code"], pj["behaviors_conf"][x]["category"]) for x in pj["behaviors_conf"]])
|
|
1784
|
+
|
|
1785
|
+
# print(f"{pj["behaviors_conf"]=}")
|
|
1786
|
+
|
|
1787
|
+
all_modifier_sets: list = []
|
|
1788
|
+
for behavior_id in pj["behaviors_conf"]:
|
|
1789
|
+
modifier_names = []
|
|
1790
|
+
set_count = 0
|
|
1791
|
+
if pj["behaviors_conf"][behavior_id]["modifiers"] == "":
|
|
1792
|
+
continue
|
|
1793
|
+
for modifier in pj["behaviors_conf"][behavior_id]["modifiers"].values():
|
|
1794
|
+
if modifier["name"]:
|
|
1795
|
+
modifier_names.append((pj["behaviors_conf"][behavior_id]["code"], modifier["name"]))
|
|
1796
|
+
else:
|
|
1797
|
+
set_count += 1
|
|
1798
|
+
modifier_names.append((pj["behaviors_conf"][behavior_id]["code"], f"set #{set_count}"))
|
|
1799
|
+
|
|
1800
|
+
# print(modifier_names)
|
|
1801
|
+
if modifier_names:
|
|
1802
|
+
all_modifier_sets.extend(modifier_names)
|
|
1803
|
+
|
|
1804
|
+
# print()
|
|
1805
|
+
# print(f"{all_modifier_sets=}")
|
|
1806
|
+
|
|
1807
|
+
# create df
|
|
1808
|
+
|
|
1809
|
+
data = {
|
|
1810
|
+
"Observation id": [],
|
|
1811
|
+
# "Observation date": [],
|
|
1812
|
+
# "Description": [],
|
|
1813
|
+
# "Observation type": [],
|
|
1814
|
+
# "Source": [],
|
|
1815
|
+
# "Time offset (s)": [],
|
|
1816
|
+
# "Coding duration": [],
|
|
1817
|
+
# "Media duration (s)": [],
|
|
1818
|
+
# "FPS (frame/s)": [],
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
for indep_var in indep_variables:
|
|
1822
|
+
data[f"independent variable '{indep_var}'"] = []
|
|
1823
|
+
|
|
1824
|
+
data = data | {
|
|
1825
|
+
"Subject": [],
|
|
1826
|
+
"Observation duration by subject by observation": [],
|
|
1827
|
+
"Behavior": [],
|
|
1828
|
+
"Behavioral category": [],
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
for modifier_set in all_modifier_sets:
|
|
1832
|
+
data[modifier_set] = []
|
|
1833
|
+
|
|
1834
|
+
data = data | {
|
|
1835
|
+
"Behavior type": [],
|
|
1836
|
+
"Start (s)": [],
|
|
1837
|
+
"Stop (s)": [],
|
|
1838
|
+
"Duration (s)": [],
|
|
1839
|
+
# "Media file name": [],
|
|
1840
|
+
# "Image index start": [],
|
|
1841
|
+
# "Image index stop": [],
|
|
1842
|
+
# "Image file path start": [],
|
|
1843
|
+
# "Image file path stop": [],
|
|
1844
|
+
"Comment start": [],
|
|
1845
|
+
"Comment stop": [],
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
#
|
|
1849
|
+
|
|
1850
|
+
type_ = {
|
|
1851
|
+
"Observation id": "string",
|
|
1852
|
+
# "Observation date": "string",
|
|
1853
|
+
# "Description": "string",
|
|
1854
|
+
# "Observation type": "string",
|
|
1855
|
+
# "Source": "string",
|
|
1856
|
+
# "Time offset (s)": "string",
|
|
1857
|
+
# "Coding duration": "float64",
|
|
1858
|
+
# "Media duration (s)": "string",
|
|
1859
|
+
# "FPS (frame/s)": "float64",
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
# TODO: set correct type in base of the var type
|
|
1863
|
+
for indep_var in indep_variables:
|
|
1864
|
+
type_[f"independent variable '{indep_var}'"] = "float64" if indep_variables[indep_var] == "numeric" else "string"
|
|
1865
|
+
|
|
1866
|
+
type_ = type_ | {
|
|
1867
|
+
"Subject": "string",
|
|
1868
|
+
"Observation duration by subject by observation": "float64",
|
|
1869
|
+
"Behavior": "string",
|
|
1870
|
+
"Behavioral category": "string",
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
for modifer_set in all_modifier_sets:
|
|
1874
|
+
type_[modifer_set] = "string"
|
|
1875
|
+
|
|
1876
|
+
type_ = type_ | {
|
|
1877
|
+
"Behavior type": "string",
|
|
1878
|
+
"Start (s)": "float64",
|
|
1879
|
+
"Stop (s)": "float64",
|
|
1880
|
+
"Duration (s)": "float64",
|
|
1881
|
+
# "Media file name": "string",
|
|
1882
|
+
# "Image index start": "float64",
|
|
1883
|
+
# "Image index stop": "float64",
|
|
1884
|
+
# "Image file path start": "string",
|
|
1885
|
+
# "Image file path stop": "string",
|
|
1886
|
+
"Comment start": "string",
|
|
1887
|
+
"Comment stop": "string",
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
state_behaviors = [pj["behaviors_conf"][x]["code"] for x in pj["behaviors_conf"] if pj["behaviors_conf"][x]["type"] == "State event"]
|
|
1891
|
+
|
|
1892
|
+
for obs_id in pj["observations"]:
|
|
1893
|
+
if observations_list and obs_id not in observations_list:
|
|
1894
|
+
continue
|
|
1895
|
+
# print(obs_id)
|
|
1896
|
+
stop_event_idx = set()
|
|
1897
|
+
for idx_event, event in enumerate(pj["observations"][obs_id]["events"]):
|
|
1898
|
+
if idx_event in stop_event_idx:
|
|
1899
|
+
continue
|
|
1900
|
+
data["Observation id"].append(obs_id)
|
|
1901
|
+
# data["Observation date"].append(pj["observations"][obs_id]["date"])
|
|
1902
|
+
# data["Description"].append(pj["observations"][obs_id]["description"])
|
|
1903
|
+
# data["Observation type"].append(pj["observations"][obs_id]["type"])
|
|
1904
|
+
# data["Source"].append("")
|
|
1905
|
+
# data["Time offset (s)"].append(pj["observations"][obs_id]["time offset"])
|
|
1906
|
+
# data["Coding duration"].append("")
|
|
1907
|
+
# data["Media duration (s)"].append("")
|
|
1908
|
+
# data["FPS (frame/s)"].append("")
|
|
1909
|
+
|
|
1910
|
+
for indep_var in indep_variables:
|
|
1911
|
+
data[f"independent variable '{indep_var}'"].append(pj["observations"][obs_id]["independent_variables"][indep_var])
|
|
1912
|
+
|
|
1913
|
+
data["Subject"].append(event[1])
|
|
1914
|
+
data["Observation duration by subject by observation"].append(-1)
|
|
1915
|
+
data["Behavior"].append(event[2])
|
|
1916
|
+
data["Behavioral category"].append(behavioral_category[event[2]])
|
|
1917
|
+
|
|
1918
|
+
count_set = 0
|
|
1919
|
+
for modifier_set in all_modifier_sets:
|
|
1920
|
+
if event[2] == modifier_set[0]:
|
|
1921
|
+
data[modifier_set].append(event[3].split("|")[count_set])
|
|
1922
|
+
count_set += 1
|
|
1923
|
+
else:
|
|
1924
|
+
data[modifier_set].append(np.nan)
|
|
1925
|
+
|
|
1926
|
+
data["Behavior type"].append("State event" if event[2] in state_behaviors else "Point event")
|
|
1927
|
+
data["Start (s)"].append(event[0])
|
|
1928
|
+
if event[2] in state_behaviors:
|
|
1929
|
+
# search stop
|
|
1930
|
+
# print(f"==> {idx_event=} {event[1:4]=}")
|
|
1931
|
+
for idx_event2, event2 in enumerate(pj["observations"][obs_id]["events"][idx_event + 1 :], start=idx_event + 1):
|
|
1932
|
+
# print(f"{idx_event2=} {event2[1:4]=}")
|
|
1933
|
+
if event2[1:4] == event[1:4]:
|
|
1934
|
+
# print("found")
|
|
1935
|
+
stop_event_idx.add(idx_event2)
|
|
1936
|
+
data["Stop (s)"].append(event2[0])
|
|
1937
|
+
data["Duration (s)"].append(event2[0] - event[0])
|
|
1938
|
+
data["Comment start"].append(event[4])
|
|
1939
|
+
data["Comment stop"].append(event2[4])
|
|
1940
|
+
break
|
|
1941
|
+
else:
|
|
1942
|
+
# print("not paired")
|
|
1943
|
+
raise ("not paired")
|
|
1944
|
+
|
|
1945
|
+
else: # point
|
|
1946
|
+
data["Stop (s)"].append(event[0])
|
|
1947
|
+
data["Duration (s)"].append(np.nan)
|
|
1948
|
+
data["Comment start"].append(event[4])
|
|
1949
|
+
data["Comment stop"].append(event[4])
|
|
1950
|
+
|
|
1951
|
+
return pd.DataFrame(data)
|