boris-behav-obs 8.16.5__py3-none-any.whl → 9.7.12__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/__init__.py +1 -1
- boris/__main__.py +1 -1
- boris/about.py +28 -40
- boris/add_modifier.py +88 -80
- boris/add_modifier_ui.py +266 -144
- boris/advanced_event_filtering.py +23 -29
- boris/analysis_plugins/__init__.py +0 -0
- boris/analysis_plugins/_export_to_feral.py +225 -0
- boris/analysis_plugins/_latency.py +59 -0
- boris/analysis_plugins/irr_cohen_kappa.py +109 -0
- boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
- boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
- boris/analysis_plugins/number_of_occurences.py +22 -0
- boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
- boris/analysis_plugins/time_budget.py +61 -0
- boris/behav_coding_map_creator.py +235 -236
- boris/behavior_binary_table.py +33 -50
- boris/behaviors_coding_map.py +17 -18
- boris/boris_cli.py +6 -25
- boris/cmd_arguments.py +12 -1
- boris/coding_pad.py +19 -36
- boris/config.py +109 -50
- boris/config_file.py +58 -67
- boris/connections.py +105 -58
- boris/converters.py +13 -37
- boris/converters_ui.py +187 -110
- boris/cooccurence.py +250 -0
- boris/core.py +2174 -1303
- boris/core_qrc.py +15892 -10829
- boris/core_ui.py +941 -806
- boris/db_functions.py +17 -42
- boris/dev.py +27 -7
- boris/dialog.py +461 -242
- boris/duration_widget.py +9 -14
- boris/edit_event.py +61 -31
- boris/edit_event_ui.py +208 -97
- boris/event_operations.py +405 -281
- boris/events_cursor.py +25 -17
- boris/events_snapshots.py +36 -82
- boris/exclusion_matrix.py +4 -9
- boris/export_events.py +180 -203
- boris/export_observation.py +60 -73
- boris/external_processes.py +123 -98
- boris/geometric_measurement.py +427 -218
- boris/gui_utilities.py +91 -14
- boris/image_overlay.py +4 -4
- boris/import_observations.py +190 -98
- boris/ipc_mpv.py +325 -0
- boris/irr.py +20 -57
- boris/latency.py +31 -24
- boris/measurement_widget.py +14 -18
- boris/media_file.py +17 -19
- boris/menu_options.py +16 -6
- boris/modifier_coding_map_creator.py +1013 -0
- boris/modifiers_coding_map.py +7 -9
- boris/mpv2.py +128 -35
- boris/observation.py +501 -211
- boris/observation_operations.py +1037 -393
- boris/observation_ui.py +573 -363
- boris/observations_list.py +51 -58
- boris/otx_parser.py +74 -68
- boris/param_panel.py +45 -59
- boris/param_panel_ui.py +254 -138
- boris/player_dock_widget.py +91 -56
- boris/plot_data_module.py +20 -53
- boris/plot_events.py +56 -153
- boris/plot_events_rt.py +16 -30
- boris/plot_spectrogram_rt.py +83 -56
- boris/plot_waveform_rt.py +27 -49
- boris/plugins.py +468 -0
- boris/portion/__init__.py +18 -8
- boris/portion/const.py +35 -18
- boris/portion/dict.py +5 -5
- boris/portion/func.py +2 -2
- boris/portion/interval.py +21 -41
- boris/portion/io.py +41 -32
- boris/preferences.py +307 -123
- boris/preferences_ui.py +686 -227
- boris/project.py +294 -271
- boris/project_functions.py +626 -537
- boris/project_import_export.py +204 -213
- boris/project_ui.py +673 -441
- boris/qrc_boris.py +6 -3
- boris/qrc_boris5.py +6 -3
- boris/select_modifiers.py +62 -90
- boris/select_observations.py +19 -197
- boris/select_subj_behav.py +67 -39
- boris/state_events.py +51 -33
- boris/subjects_pad.py +7 -9
- boris/synthetic_time_budget.py +42 -26
- boris/time_budget_functions.py +169 -169
- boris/time_budget_widget.py +77 -89
- boris/transitions.py +41 -41
- boris/utilities.py +594 -226
- boris/version.py +3 -3
- boris/video_equalizer.py +16 -14
- boris/video_equalizer_ui.py +199 -130
- boris/video_operations.py +86 -28
- boris/view_df.py +104 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +240 -136
- boris_behav_obs-9.7.12.dist-info/METADATA +139 -0
- boris_behav_obs-9.7.12.dist-info/RECORD +110 -0
- {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.12.dist-info}/WHEEL +1 -1
- boris_behav_obs-9.7.12.dist-info/entry_points.txt +2 -0
- boris/README.TXT +0 -22
- boris/add_modifier.ui +0 -323
- boris/converters.ui +0 -289
- boris/core.qrc +0 -37
- boris/core.ui +0 -1571
- boris/edit_event.ui +0 -233
- boris/icons/logo_eye.ico +0 -0
- boris/map_creator.py +0 -982
- boris/observation.ui +0 -814
- boris/param_panel.ui +0 -379
- boris/preferences.ui +0 -537
- boris/project.ui +0 -1074
- boris/vlc_local.py +0 -90
- boris_behav_obs-8.16.5.dist-info/LICENSE.TXT +0 -674
- boris_behav_obs-8.16.5.dist-info/METADATA +0 -134
- boris_behav_obs-8.16.5.dist-info/RECORD +0 -107
- boris_behav_obs-8.16.5.dist-info/entry_points.txt +0 -2
- {boris → boris_behav_obs-9.7.12.dist-info/licenses}/LICENSE.TXT +0 -0
- {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.12.dist-info}/top_level.txt +0 -0
boris/project.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
BORIS
|
|
3
3
|
Behavioral Observation Research Interactive Software
|
|
4
|
-
Copyright 2012-
|
|
4
|
+
Copyright 2012-2025 Olivier Friard
|
|
5
5
|
|
|
6
6
|
This file is part of BORIS.
|
|
7
7
|
|
|
@@ -24,10 +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,
|
|
31
|
+
QApplication,
|
|
30
32
|
QCheckBox,
|
|
33
|
+
QColorDialog,
|
|
31
34
|
QDialog,
|
|
32
35
|
QFileDialog,
|
|
33
36
|
QHBoxLayout,
|
|
@@ -40,11 +43,9 @@ from PyQt5.QtWidgets import (
|
|
|
40
43
|
QPushButton,
|
|
41
44
|
QSizePolicy,
|
|
42
45
|
QSpacerItem,
|
|
46
|
+
QTableWidget,
|
|
43
47
|
QTableWidgetItem,
|
|
44
48
|
QVBoxLayout,
|
|
45
|
-
QColorDialog,
|
|
46
|
-
QTableWidget,
|
|
47
|
-
QAbstractItemView,
|
|
48
49
|
)
|
|
49
50
|
|
|
50
51
|
from . import add_modifier
|
|
@@ -81,12 +82,12 @@ class BehavioralCategories(QDialog):
|
|
|
81
82
|
# add categories
|
|
82
83
|
self.lw.setColumnCount(2)
|
|
83
84
|
self.lw.setHorizontalHeaderLabels(["Category name", "Color"])
|
|
84
|
-
# self.lw.verticalHeader().hide()
|
|
85
85
|
self.lw.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
|
86
86
|
|
|
87
|
-
# self.lw.setSelectionBehavior(QAbstractItemView.SelectRows)
|
|
88
87
|
self.lw.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
89
88
|
|
|
89
|
+
behavioral_categories: list = []
|
|
90
|
+
|
|
90
91
|
if cfg.BEHAVIORAL_CATEGORIES_CONF in pj:
|
|
91
92
|
self.lw.setRowCount(len(pj.get(cfg.BEHAVIORAL_CATEGORIES_CONF, {})))
|
|
92
93
|
behav_cat = pj.get(cfg.BEHAVIORAL_CATEGORIES_CONF, {})
|
|
@@ -94,7 +95,7 @@ class BehavioralCategories(QDialog):
|
|
|
94
95
|
# name
|
|
95
96
|
item = QTableWidgetItem()
|
|
96
97
|
item.setText(behav_cat[key]["name"])
|
|
97
|
-
|
|
98
|
+
behavioral_categories.append(behav_cat[key]["name"])
|
|
98
99
|
self.lw.setItem(idx, 0, item)
|
|
99
100
|
# color
|
|
100
101
|
item = QTableWidgetItem()
|
|
@@ -102,23 +103,21 @@ class BehavioralCategories(QDialog):
|
|
|
102
103
|
if behav_cat[key].get(cfg.COLOR, ""):
|
|
103
104
|
item.setBackground(QColor(behav_cat[key].get(cfg.COLOR, "")))
|
|
104
105
|
else:
|
|
105
|
-
item.setBackground(
|
|
106
|
-
# item.setFlags(Qt.ItemIsEnabled)
|
|
106
|
+
item.setBackground(self.not_editable_column_color())
|
|
107
107
|
self.lw.setItem(idx, 1, item)
|
|
108
108
|
else:
|
|
109
109
|
self.lw.setRowCount(len(pj.get(cfg.BEHAVIORAL_CATEGORIES, [])))
|
|
110
110
|
for idx, category in enumerate(sorted(pj.get(cfg.BEHAVIORAL_CATEGORIES, []))):
|
|
111
|
+
# name
|
|
111
112
|
item = QTableWidgetItem()
|
|
112
113
|
item.setText(category)
|
|
113
|
-
|
|
114
|
+
behavioral_categories.append(category)
|
|
114
115
|
self.lw.setItem(idx, 0, item)
|
|
115
|
-
|
|
116
|
+
# color
|
|
116
117
|
item = QTableWidgetItem()
|
|
117
118
|
item.setText("")
|
|
118
|
-
# item.setFlags(Qt.ItemIsEnabled)
|
|
119
|
-
self.lw.setItem(idx, 1, item)
|
|
120
119
|
|
|
121
|
-
|
|
120
|
+
self.lw.setItem(idx, 1, item)
|
|
122
121
|
|
|
123
122
|
self.vbox.addWidget(self.lw)
|
|
124
123
|
|
|
@@ -135,8 +134,8 @@ class BehavioralCategories(QDialog):
|
|
|
135
134
|
self.vbox.addLayout(self.hbox0)
|
|
136
135
|
|
|
137
136
|
hbox1 = QHBoxLayout()
|
|
138
|
-
self.pbOK = QPushButton(
|
|
139
|
-
self.pbCancel = QPushButton(
|
|
137
|
+
self.pbOK = QPushButton(cfg.OK, clicked=self.accept)
|
|
138
|
+
self.pbCancel = QPushButton(cfg.CANCEL, clicked=self.accept)
|
|
140
139
|
|
|
141
140
|
spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
|
|
142
141
|
hbox1.addItem(spacerItem)
|
|
@@ -146,6 +145,54 @@ class BehavioralCategories(QDialog):
|
|
|
146
145
|
|
|
147
146
|
self.setLayout(self.vbox)
|
|
148
147
|
|
|
148
|
+
# check if behavioral categories are present in events
|
|
149
|
+
behavioral_categories_in_ethogram = set(
|
|
150
|
+
sorted([pj[cfg.ETHOGRAM][idx].get(cfg.BEHAVIOR_CATEGORY, "") for idx in pj.get(cfg.ETHOGRAM, {})])
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
if behavioral_categories_in_ethogram.difference(set(behavioral_categories)) and behavioral_categories_in_ethogram.difference(
|
|
154
|
+
set(behavioral_categories)
|
|
155
|
+
) != {""}:
|
|
156
|
+
if (
|
|
157
|
+
dialog.MessageDialog(
|
|
158
|
+
cfg.programName,
|
|
159
|
+
(
|
|
160
|
+
"There are behavioral categories that are present in ethogram but not defined.<br>"
|
|
161
|
+
f"{behavioral_categories_in_ethogram.difference(set(behavioral_categories))}<br>"
|
|
162
|
+
"<br>"
|
|
163
|
+
"Do you want to add them in the behavioral categories list?"
|
|
164
|
+
),
|
|
165
|
+
[cfg.YES, cfg.NO],
|
|
166
|
+
)
|
|
167
|
+
== cfg.YES
|
|
168
|
+
):
|
|
169
|
+
# add behavioral categories present in ethogram in behavioal categories list
|
|
170
|
+
rc = self.lw.rowCount()
|
|
171
|
+
self.lw.setRowCount(rc + len(behavioral_categories_in_ethogram.difference(set(behavioral_categories))))
|
|
172
|
+
for idx, category in enumerate(sorted(list(behavioral_categories_in_ethogram.difference(set(behavioral_categories))))):
|
|
173
|
+
print(category)
|
|
174
|
+
# name
|
|
175
|
+
item = QTableWidgetItem()
|
|
176
|
+
item.setText(category)
|
|
177
|
+
# behavioral_categories.append(category)
|
|
178
|
+
self.lw.setItem(rc + idx, 0, item)
|
|
179
|
+
# color
|
|
180
|
+
item = QTableWidgetItem()
|
|
181
|
+
item.setText("")
|
|
182
|
+
|
|
183
|
+
self.lw.setItem(rc + idx, 1, item)
|
|
184
|
+
|
|
185
|
+
def not_editable_column_color(self):
|
|
186
|
+
"""
|
|
187
|
+
return a color for the not editable column
|
|
188
|
+
"""
|
|
189
|
+
window_color = QApplication.instance().palette().window().color()
|
|
190
|
+
return QColor(
|
|
191
|
+
window_color.red() - cfg.DARKER_DIFFERENCE,
|
|
192
|
+
window_color.green() - cfg.DARKER_DIFFERENCE,
|
|
193
|
+
window_color.blue() - cfg.DARKER_DIFFERENCE,
|
|
194
|
+
)
|
|
195
|
+
|
|
149
196
|
def lw_double_clicked(self, row: int, column: int):
|
|
150
197
|
"""
|
|
151
198
|
change color
|
|
@@ -165,7 +212,7 @@ class BehavioralCategories(QDialog):
|
|
|
165
212
|
color = col_diag.currentColor()
|
|
166
213
|
if color.name() == "#000000": # black -> delete color
|
|
167
214
|
self.lw.item(row, 1).setText("")
|
|
168
|
-
self.lw.item(row, 1).setBackground(
|
|
215
|
+
self.lw.item(row, 1).setBackground(self.not_editable_column_color())
|
|
169
216
|
elif color.isValid():
|
|
170
217
|
self.lw.item(row, 1).setText(color.name())
|
|
171
218
|
self.lw.item(row, 1).setBackground(color)
|
|
@@ -253,9 +300,7 @@ class BehavioralCategories(QDialog):
|
|
|
253
300
|
flag_rename = (
|
|
254
301
|
dialog.MessageDialog(
|
|
255
302
|
cfg.programName,
|
|
256
|
-
("Some behavior belong to the <b>{
|
|
257
|
-
"<br>".join(behaviors_in_category), category_to_rename
|
|
258
|
-
),
|
|
303
|
+
(f"Some behavior belong to the <b>{category_to_rename}</b> to rename:<br>{'<br>'.join(behaviors_in_category)}<br>"),
|
|
259
304
|
["Rename category", cfg.CANCEL],
|
|
260
305
|
)
|
|
261
306
|
== "Rename category"
|
|
@@ -271,12 +316,11 @@ class BehavioralCategories(QDialog):
|
|
|
271
316
|
self.lw.item(self.lw.indexFromItem(selected_item).row(), 0).setText(new_category_name)
|
|
272
317
|
# check behaviors belonging to the renamed category
|
|
273
318
|
self.renamed = [category_to_rename, new_category_name]
|
|
274
|
-
self.accept()
|
|
319
|
+
# self.accept()
|
|
275
320
|
|
|
276
321
|
|
|
277
322
|
class projectDialog(QDialog, Ui_dlgProject):
|
|
278
323
|
def __init__(self, parent=None):
|
|
279
|
-
|
|
280
324
|
super().__init__()
|
|
281
325
|
|
|
282
326
|
self.setupUi(self)
|
|
@@ -292,23 +336,23 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
292
336
|
"remove all|Remove all behaviors",
|
|
293
337
|
"lower|Convert keys to lower case",
|
|
294
338
|
]
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
self.add_button_menu(behavior_button_items,
|
|
298
|
-
self.pb_behavior.setMenu(
|
|
339
|
+
self.behavior_menu = QMenu()
|
|
340
|
+
self.behavior_menu.triggered.connect(lambda x: self.behavior(action=x.statusTip()))
|
|
341
|
+
self.add_button_menu(behavior_button_items, self.behavior_menu)
|
|
342
|
+
self.pb_behavior.setMenu(self.behavior_menu)
|
|
299
343
|
|
|
300
344
|
import_button_items = [
|
|
301
345
|
"boris|from a BORIS project",
|
|
302
|
-
"spreadsheet|from a spreadsheet file (XLSX)",
|
|
346
|
+
"spreadsheet|from a spreadsheet file (XLSX/ODS)",
|
|
303
347
|
"jwatcher|from a JWatcher project",
|
|
304
348
|
"text|from a text file (CSV or TSV)",
|
|
305
349
|
"clipboard|from the clipboard",
|
|
306
350
|
"repository|from the BORIS repository",
|
|
307
351
|
]
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
self.add_button_menu(import_button_items,
|
|
311
|
-
self.pb_import.setMenu(
|
|
352
|
+
self.import_behaviors_menu = QMenu()
|
|
353
|
+
self.import_behaviors_menu.triggered.connect(lambda x: self.import_ethogram(action=x.statusTip()))
|
|
354
|
+
self.add_button_menu(import_button_items, self.import_behaviors_menu)
|
|
355
|
+
self.pb_import.setMenu(self.import_behaviors_menu)
|
|
312
356
|
|
|
313
357
|
self.pbBehaviorsCategories.clicked.connect(self.pbBehaviorsCategories_clicked)
|
|
314
358
|
|
|
@@ -332,21 +376,21 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
332
376
|
"lower|Convert keys to lower case",
|
|
333
377
|
]
|
|
334
378
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
self.add_button_menu(subjects_button_items,
|
|
338
|
-
self.pb_subjects.setMenu(
|
|
379
|
+
self.subject_menu = QMenu()
|
|
380
|
+
self.subject_menu.triggered.connect(lambda x: self.subjects(action=x.statusTip()))
|
|
381
|
+
self.add_button_menu(subjects_button_items, self.subject_menu)
|
|
382
|
+
self.pb_subjects.setMenu(self.subject_menu)
|
|
339
383
|
|
|
340
384
|
subjects_import_button_items = [
|
|
341
385
|
"boris|from a BORIS project",
|
|
342
|
-
"spreadsheet|from a spreadsheet file (XLSX)",
|
|
386
|
+
"spreadsheet|from a spreadsheet file (XLSX/ODS)",
|
|
343
387
|
"text|from a text file (CSV or TSV)",
|
|
344
388
|
"clipboard|from the clipboard",
|
|
345
389
|
]
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
self.add_button_menu(subjects_import_button_items,
|
|
349
|
-
self.pbImportSubjectsFromProject.setMenu(
|
|
390
|
+
self.import_subjects_menu = QMenu()
|
|
391
|
+
self.import_subjects_menu.triggered.connect(lambda x: self.import_subjects(action=x.statusTip()))
|
|
392
|
+
self.add_button_menu(subjects_import_button_items, self.import_subjects_menu)
|
|
393
|
+
self.pbImportSubjectsFromProject.setMenu(self.import_subjects_menu)
|
|
350
394
|
|
|
351
395
|
self.pb_export_subjects.clicked.connect(lambda: project_import_export.export_subjects(self))
|
|
352
396
|
|
|
@@ -416,6 +460,17 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
416
460
|
self.twSubjects.horizontalHeader().sortIndicatorChanged.connect(self.sort_twSubjects)
|
|
417
461
|
self.twVariables.horizontalHeader().sortIndicatorChanged.connect(self.sort_twVariables)
|
|
418
462
|
|
|
463
|
+
def not_editable_column_color(self):
|
|
464
|
+
"""
|
|
465
|
+
return a color for the not editable column
|
|
466
|
+
"""
|
|
467
|
+
window_color = QApplication.instance().palette().window().color()
|
|
468
|
+
return QColor(
|
|
469
|
+
window_color.red() - cfg.DARKER_DIFFERENCE,
|
|
470
|
+
window_color.green() - cfg.DARKER_DIFFERENCE,
|
|
471
|
+
window_color.blue() - cfg.DARKER_DIFFERENCE,
|
|
472
|
+
)
|
|
473
|
+
|
|
419
474
|
def add_button_menu(self, data, menu_obj):
|
|
420
475
|
"""
|
|
421
476
|
add menu option from dictionary
|
|
@@ -529,20 +584,14 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
529
584
|
|
|
530
585
|
# check if some keys will be duplicated after conversion
|
|
531
586
|
try:
|
|
532
|
-
all_keys = [
|
|
533
|
-
self.twBehaviors.item(row, cfg.behavioursFields["key"]).text()
|
|
534
|
-
for row in range(self.twBehaviors.rowCount())
|
|
535
|
-
]
|
|
587
|
+
all_keys = [self.twBehaviors.item(row, cfg.behavioursFields["key"]).text() for row in range(self.twBehaviors.rowCount())]
|
|
536
588
|
except Exception:
|
|
537
589
|
pass
|
|
538
590
|
if all_keys == [x.lower() for x in all_keys]:
|
|
539
591
|
QMessageBox.information(self, cfg.programName, "All keys are already lower case")
|
|
540
592
|
return
|
|
541
593
|
|
|
542
|
-
if (
|
|
543
|
-
dialog.MessageDialog(cfg.programName, "Confirm the conversion of key to lower case.", [cfg.YES, cfg.CANCEL])
|
|
544
|
-
== cfg.CANCEL
|
|
545
|
-
):
|
|
594
|
+
if dialog.MessageDialog(cfg.programName, "Confirm the conversion of key to lower case.", [cfg.YES, cfg.CANCEL]) == cfg.CANCEL:
|
|
546
595
|
return
|
|
547
596
|
|
|
548
597
|
if len([x.lower() for x in all_keys]) != len(set([x.lower() for x in all_keys])):
|
|
@@ -564,9 +613,8 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
564
613
|
|
|
565
614
|
# convert modifier shortcuts
|
|
566
615
|
if self.twBehaviors.item(row, cfg.behavioursFields[cfg.MODIFIERS]).text():
|
|
567
|
-
|
|
568
616
|
modifiers_dict = (
|
|
569
|
-
|
|
617
|
+
json.loads(self.twBehaviors.item(row, cfg.behavioursFields[cfg.MODIFIERS]).text())
|
|
570
618
|
if self.twBehaviors.item(row, cfg.behavioursFields[cfg.MODIFIERS]).text()
|
|
571
619
|
else {}
|
|
572
620
|
)
|
|
@@ -576,16 +624,12 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
576
624
|
for idx2, value in enumerate(modifiers_dict[modifier_set]["values"]):
|
|
577
625
|
if re.findall(r"\((\w+)\)", value):
|
|
578
626
|
modifiers_dict[modifier_set]["values"][idx2] = (
|
|
579
|
-
value.split("(")[0]
|
|
580
|
-
+ "("
|
|
581
|
-
+ re.findall(r"\((\w+)\)", value)[0].lower()
|
|
582
|
-
+ ")"
|
|
583
|
-
+ value.split(")")[-1]
|
|
627
|
+
value.split("(")[0] + "(" + re.findall(r"\((\w+)\)", value)[0].lower() + ")" + value.split(")")[-1]
|
|
584
628
|
)
|
|
585
629
|
except Exception:
|
|
586
630
|
logging.warning("error during conversion of modifier short cut to lower case")
|
|
587
631
|
|
|
588
|
-
self.twBehaviors.item(row, cfg.behavioursFields[cfg.MODIFIERS]).setText(
|
|
632
|
+
self.twBehaviors.item(row, cfg.behavioursFields[cfg.MODIFIERS]).setText(json.dumps(modifiers_dict))
|
|
589
633
|
|
|
590
634
|
def convert_subjects_keys_to_lower_case(self):
|
|
591
635
|
"""
|
|
@@ -593,20 +637,14 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
593
637
|
"""
|
|
594
638
|
# check if some keys will be duplicated after conversion
|
|
595
639
|
try:
|
|
596
|
-
all_keys = [
|
|
597
|
-
self.twSubjects.item(row, cfg.subjectsFields.index("key")).text()
|
|
598
|
-
for row in range(self.twSubjects.rowCount())
|
|
599
|
-
]
|
|
640
|
+
all_keys = [self.twSubjects.item(row, cfg.subjectsFields.index("key")).text() for row in range(self.twSubjects.rowCount())]
|
|
600
641
|
except Exception:
|
|
601
642
|
pass
|
|
602
643
|
if all_keys == [x.lower() for x in all_keys]:
|
|
603
644
|
QMessageBox.information(self, cfg.programName, "All keys are already lower case")
|
|
604
645
|
return
|
|
605
646
|
|
|
606
|
-
if (
|
|
607
|
-
dialog.MessageDialog(cfg.programName, "Confirm the conversion of key to lower case.", [cfg.YES, cfg.CANCEL])
|
|
608
|
-
== cfg.CANCEL
|
|
609
|
-
):
|
|
647
|
+
if dialog.MessageDialog(cfg.programName, "Confirm the conversion of key to lower case.", [cfg.YES, cfg.CANCEL]) == cfg.CANCEL:
|
|
610
648
|
return
|
|
611
649
|
|
|
612
650
|
if len([x.lower() for x in all_keys]) != len(set([x.lower() for x in all_keys])):
|
|
@@ -631,47 +669,45 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
631
669
|
Add a behaviors coding map from file
|
|
632
670
|
"""
|
|
633
671
|
|
|
634
|
-
|
|
672
|
+
file_name, _ = QFileDialog().getOpenFileName(
|
|
635
673
|
self, "Open a behaviors coding map", "", "Behaviors coding map (*.behav_coding_map);;All files (*)"
|
|
636
674
|
)
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
675
|
+
if not file_name:
|
|
676
|
+
return
|
|
677
|
+
try:
|
|
678
|
+
bcm = json.loads(open(file_name, "r").read())
|
|
679
|
+
except Exception:
|
|
680
|
+
QMessageBox.critical(self, cfg.programName, f"The file {file_name} is not a behaviors coding map.")
|
|
681
|
+
return
|
|
644
682
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
self, cfg.programName, f"The file {file_name} is not a BORIS behaviors coding map."
|
|
648
|
-
)
|
|
683
|
+
if "coding_map_type" not in bcm or bcm["coding_map_type"] != "BORIS behaviors coding map":
|
|
684
|
+
QMessageBox.critical(self, cfg.programName, f"The file {file_name} is not a BORIS behaviors coding map.")
|
|
649
685
|
|
|
650
|
-
|
|
651
|
-
|
|
686
|
+
if cfg.BEHAVIORS_CODING_MAP not in self.pj:
|
|
687
|
+
self.pj[cfg.BEHAVIORS_CODING_MAP] = []
|
|
652
688
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
689
|
+
bcm_code_not_found = []
|
|
690
|
+
existing_codes = [self.pj[cfg.ETHOGRAM][key][cfg.BEHAVIOR_CODE] for key in self.pj[cfg.ETHOGRAM]]
|
|
691
|
+
for code in [bcm["areas"][key][cfg.BEHAVIOR_CODE] for key in bcm["areas"]]:
|
|
692
|
+
if code not in existing_codes:
|
|
693
|
+
bcm_code_not_found.append(code)
|
|
658
694
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
695
|
+
if bcm_code_not_found:
|
|
696
|
+
QMessageBox.warning(
|
|
697
|
+
self,
|
|
698
|
+
cfg.programName,
|
|
699
|
+
("The following behavior{} are not defined in the ethogram:<br>{}").format(
|
|
700
|
+
"s" if len(bcm_code_not_found) > 1 else "", ",".join(bcm_code_not_found)
|
|
701
|
+
),
|
|
702
|
+
)
|
|
667
703
|
|
|
668
|
-
|
|
704
|
+
self.pj[cfg.BEHAVIORS_CODING_MAP].append(dict(bcm))
|
|
669
705
|
|
|
670
|
-
|
|
706
|
+
self.twBehavCodingMap.setRowCount(self.twBehavCodingMap.rowCount() + 1)
|
|
671
707
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
708
|
+
self.twBehavCodingMap.setItem(self.twBehavCodingMap.rowCount() - 1, 0, QTableWidgetItem(bcm["name"]))
|
|
709
|
+
codes = ", ".join([bcm["areas"][idx][cfg.BEHAVIOR_CODE] for idx in bcm["areas"]])
|
|
710
|
+
self.twBehavCodingMap.setItem(self.twBehavCodingMap.rowCount() - 1, 1, QTableWidgetItem(codes))
|
|
675
711
|
|
|
676
712
|
def remove_behaviors_coding_map(self):
|
|
677
713
|
"""
|
|
@@ -680,27 +716,28 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
680
716
|
if not self.twBehavCodingMap.selectedIndexes():
|
|
681
717
|
QMessageBox.warning(self, cfg.programName, "Select a behaviors coding map")
|
|
682
718
|
else:
|
|
683
|
-
if (
|
|
684
|
-
dialog.MessageDialog(
|
|
685
|
-
cfg.programName, "Remove the selected behaviors coding map?", [cfg.YES, cfg.CANCEL]
|
|
686
|
-
)
|
|
687
|
-
== cfg.YES
|
|
688
|
-
):
|
|
719
|
+
if dialog.MessageDialog(cfg.programName, "Remove the selected behaviors coding map?", [cfg.YES, cfg.CANCEL]) == cfg.YES:
|
|
689
720
|
del self.pj[cfg.BEHAVIORS_CODING_MAP][self.twBehavCodingMap.selectedIndexes()[0].row()]
|
|
690
721
|
self.twBehavCodingMap.removeRow(self.twBehavCodingMap.selectedIndexes()[0].row())
|
|
691
722
|
|
|
692
723
|
def leLabel_changed(self):
|
|
693
|
-
|
|
724
|
+
"""
|
|
725
|
+
independent variable label changed
|
|
726
|
+
"""
|
|
694
727
|
if self.selected_twvariables_row != -1:
|
|
695
728
|
self.twVariables.item(self.selected_twvariables_row, 0).setText(self.leLabel.text())
|
|
696
729
|
|
|
697
730
|
def leDescription_changed(self):
|
|
698
|
-
|
|
731
|
+
"""
|
|
732
|
+
independent variable description changed
|
|
733
|
+
"""
|
|
699
734
|
if self.selected_twvariables_row != -1:
|
|
700
735
|
self.twVariables.item(self.selected_twvariables_row, 1).setText(self.leDescription.text())
|
|
701
736
|
|
|
702
737
|
def lePredefined_changed(self):
|
|
703
|
-
|
|
738
|
+
"""
|
|
739
|
+
independent variable predefined value changed
|
|
740
|
+
"""
|
|
704
741
|
if self.selected_twvariables_row != -1:
|
|
705
742
|
self.twVariables.item(self.selected_twvariables_row, 3).setText(self.lePredefined.text())
|
|
706
743
|
if not self.lePredefined.hasFocus():
|
|
@@ -709,15 +746,19 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
709
746
|
QMessageBox.warning(self, f"{cfg.programName} - Independent variables error", msg)
|
|
710
747
|
|
|
711
748
|
def leSetValues_changed(self):
|
|
712
|
-
|
|
749
|
+
"""
|
|
750
|
+
independent variable available values changed
|
|
751
|
+
"""
|
|
713
752
|
if self.selected_twvariables_row != -1:
|
|
714
753
|
self.twVariables.item(self.selected_twvariables_row, 4).setText(self.leSetValues.text())
|
|
715
754
|
|
|
716
755
|
def dte_default_date_changed(self):
|
|
717
|
-
|
|
756
|
+
"""
|
|
757
|
+
independent variable default timestamp changed
|
|
758
|
+
"""
|
|
718
759
|
if self.selected_twvariables_row != -1:
|
|
719
760
|
self.twVariables.item(self.selected_twvariables_row, 3).setText(
|
|
720
|
-
self.dte_default_date.dateTime().toString(
|
|
761
|
+
self.dte_default_date.dateTime().toString("yyyy-MM-ddTHH:mm:ss.zzz")
|
|
721
762
|
)
|
|
722
763
|
|
|
723
764
|
def pbBehaviorsCategories_clicked(self):
|
|
@@ -763,13 +804,8 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
763
804
|
if bc.renamed:
|
|
764
805
|
for row in range(self.twBehaviors.rowCount()):
|
|
765
806
|
if self.twBehaviors.item(row, cfg.behavioursFields[cfg.BEHAVIOR_CATEGORY]):
|
|
766
|
-
if (
|
|
767
|
-
self.twBehaviors.item(row, cfg.behavioursFields[cfg.BEHAVIOR_CATEGORY]).
|
|
768
|
-
== bc.renamed[0]
|
|
769
|
-
):
|
|
770
|
-
self.twBehaviors.item(row, cfg.behavioursFields[cfg.BEHAVIOR_CATEGORY]).setText(
|
|
771
|
-
bc.renamed[1]
|
|
772
|
-
)
|
|
807
|
+
if self.twBehaviors.item(row, cfg.behavioursFields[cfg.BEHAVIOR_CATEGORY]).text() == bc.renamed[0]:
|
|
808
|
+
self.twBehaviors.item(row, cfg.behavioursFields[cfg.BEHAVIOR_CATEGORY]).setText(bc.renamed[1])
|
|
773
809
|
|
|
774
810
|
def twBehaviors_cellDoubleClicked(self, row: int, column: int) -> None:
|
|
775
811
|
"""
|
|
@@ -785,20 +821,18 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
785
821
|
column (int): column double-clicked
|
|
786
822
|
"""
|
|
787
823
|
|
|
788
|
-
#
|
|
789
|
-
if column == cfg.behavioursFields[
|
|
824
|
+
# excluded column
|
|
825
|
+
if column == cfg.behavioursFields[cfg.EXCLUDED]:
|
|
790
826
|
self.exclusion_matrix()
|
|
791
827
|
|
|
792
|
-
#
|
|
793
|
-
if column == cfg.behavioursFields[
|
|
828
|
+
# coding map
|
|
829
|
+
if column == cfg.behavioursFields[cfg.CODING_MAP_sp]:
|
|
794
830
|
if "with coding map" in self.twBehaviors.item(row, cfg.behavioursFields[cfg.TYPE]).text():
|
|
795
|
-
self.
|
|
831
|
+
self.behavior_type_changed(row)
|
|
796
832
|
else:
|
|
797
|
-
QMessageBox.information(
|
|
798
|
-
self, cfg.programName, "Change the behavior type on first column to select a coding map"
|
|
799
|
-
)
|
|
833
|
+
QMessageBox.information(self, cfg.programName, "Change the behavior type on first column to select a coding map")
|
|
800
834
|
|
|
801
|
-
#
|
|
835
|
+
# behavior type
|
|
802
836
|
if column == cfg.behavioursFields["type"]:
|
|
803
837
|
self.behavior_type_doubleclicked(row)
|
|
804
838
|
|
|
@@ -810,30 +844,30 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
810
844
|
if column == cfg.behavioursFields[cfg.BEHAVIOR_CATEGORY]:
|
|
811
845
|
self.category_doubleclicked(row)
|
|
812
846
|
|
|
847
|
+
# modifiers
|
|
813
848
|
if column == cfg.behavioursFields[cfg.MODIFIERS]:
|
|
814
849
|
# check if behavior has coding map
|
|
815
850
|
if (
|
|
816
|
-
self.twBehaviors.item(row, cfg.behavioursFields[
|
|
817
|
-
and self.twBehaviors.item(row, cfg.behavioursFields[
|
|
851
|
+
self.twBehaviors.item(row, cfg.behavioursFields[cfg.CODING_MAP_sp]) is not None
|
|
852
|
+
and self.twBehaviors.item(row, cfg.behavioursFields[cfg.CODING_MAP_sp]).text()
|
|
818
853
|
):
|
|
819
854
|
QMessageBox.warning(self, cfg.programName, "Use the coding map to set/modify the areas")
|
|
820
855
|
else:
|
|
821
856
|
subjects_list = []
|
|
822
857
|
for subject_row in range(self.twSubjects.rowCount()):
|
|
823
858
|
key = self.twSubjects.item(subject_row, 0).text() if self.twSubjects.item(subject_row, 0) else ""
|
|
824
|
-
subjectName = (
|
|
825
|
-
self.twSubjects.item(subject_row, 1).text().strip()
|
|
826
|
-
if self.twSubjects.item(subject_row, 1)
|
|
827
|
-
else ""
|
|
828
|
-
)
|
|
859
|
+
subjectName = self.twSubjects.item(subject_row, 1).text().strip() if self.twSubjects.item(subject_row, 1) else ""
|
|
829
860
|
subjects_list.append((subjectName, key))
|
|
830
861
|
|
|
831
862
|
addModifierWindow = add_modifier.addModifierDialog(
|
|
832
|
-
self.twBehaviors.item(row, column).text(),
|
|
863
|
+
self.twBehaviors.item(row, column).text(),
|
|
864
|
+
subjects=subjects_list,
|
|
865
|
+
ask_at_stop_enabled=self.twBehaviors.item(row, cfg.behavioursFields["type"]).text() == cfg.STATE_EVENT,
|
|
833
866
|
)
|
|
834
867
|
addModifierWindow.setWindowTitle(f'Set modifiers for "{self.twBehaviors.item(row, 2).text()}" behavior')
|
|
868
|
+
|
|
835
869
|
if addModifierWindow.exec_():
|
|
836
|
-
self.twBehaviors.item(row, column).setText(addModifierWindow.
|
|
870
|
+
self.twBehaviors.item(row, column).setText(addModifierWindow.get_modifiers())
|
|
837
871
|
|
|
838
872
|
def behavior_type_doubleclicked(self, row):
|
|
839
873
|
"""
|
|
@@ -845,14 +879,12 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
845
879
|
else:
|
|
846
880
|
selected = 0
|
|
847
881
|
|
|
848
|
-
new_type, ok = QInputDialog.getItem(
|
|
849
|
-
self, "Select a behavior type", "Types of behavior", cfg.BEHAVIOR_TYPES, selected, False
|
|
850
|
-
)
|
|
882
|
+
new_type, ok = QInputDialog.getItem(self, "Select a behavior type", "Types of behavior", cfg.BEHAVIOR_TYPES, selected, False)
|
|
851
883
|
|
|
852
884
|
if ok and new_type:
|
|
853
885
|
self.twBehaviors.item(row, cfg.behavioursFields["type"]).setText(new_type)
|
|
854
886
|
|
|
855
|
-
self.
|
|
887
|
+
self.behavior_type_changed(row)
|
|
856
888
|
|
|
857
889
|
def color_doubleclicked(self, row: int) -> None:
|
|
858
890
|
"""
|
|
@@ -866,16 +898,17 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
866
898
|
if self.twBehaviors.item(row, cfg.behavioursFields[cfg.COLOR]).text():
|
|
867
899
|
current_color = QColor(self.twBehaviors.item(row, cfg.behavioursFields[cfg.COLOR]).text())
|
|
868
900
|
if current_color.isValid():
|
|
901
|
+
print(f"{current_color=}")
|
|
869
902
|
col_diag.setCurrentColor(current_color)
|
|
870
903
|
|
|
871
|
-
if col_diag.
|
|
904
|
+
if col_diag.exec():
|
|
872
905
|
color = col_diag.currentColor()
|
|
873
906
|
if color.name() == "#000000": # black -> delete color
|
|
874
907
|
self.twBehaviors.item(row, cfg.behavioursFields[cfg.COLOR]).setText("")
|
|
875
|
-
self.twBehaviors.item(row, cfg.behavioursFields[cfg.COLOR]).setBackground(
|
|
908
|
+
self.twBehaviors.item(row, cfg.behavioursFields[cfg.COLOR]).setBackground(self.not_editable_column_color())
|
|
876
909
|
elif color.isValid():
|
|
910
|
+
self.twBehaviors.item(row, cfg.behavioursFields[cfg.COLOR]).setBackground(QColor(color.name()))
|
|
877
911
|
self.twBehaviors.item(row, cfg.behavioursFields[cfg.COLOR]).setText(color.name())
|
|
878
|
-
self.twBehaviors.item(row, cfg.behavioursFields[cfg.COLOR]).setBackground(color)
|
|
879
912
|
|
|
880
913
|
def category_doubleclicked(self, row):
|
|
881
914
|
"""
|
|
@@ -889,9 +922,7 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
889
922
|
else:
|
|
890
923
|
selected = 0
|
|
891
924
|
|
|
892
|
-
category, ok = QInputDialog.getItem(
|
|
893
|
-
self, "Select a behavioral category", "Behavioral categories", categories, selected, False
|
|
894
|
-
)
|
|
925
|
+
category, ok = QInputDialog.getItem(self, "Select a behavioral category", "Behavioral categories", categories, selected, False)
|
|
895
926
|
|
|
896
927
|
if ok and category:
|
|
897
928
|
if category == "None":
|
|
@@ -920,22 +951,15 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
920
951
|
|
|
921
952
|
if self.twVariables.cellWidget(row, cfg.tw_indVarFields.index("type")).currentText() == cfg.SET_OF_VALUES:
|
|
922
953
|
if self.twVariables.item(row, cfg.tw_indVarFields.index("possible values")).text() == "NA":
|
|
923
|
-
self.twVariables.item(row, cfg.tw_indVarFields.index("possible values")).setText(
|
|
924
|
-
"Double-click to add values"
|
|
925
|
-
)
|
|
954
|
+
self.twVariables.item(row, cfg.tw_indVarFields.index("possible values")).setText("Double-click to add values")
|
|
926
955
|
else:
|
|
927
956
|
# check if set of values defined
|
|
928
957
|
if self.twVariables.item(row, cfg.tw_indVarFields.index("possible values")).text() not in [
|
|
929
958
|
"NA",
|
|
930
959
|
"Double-click to add values",
|
|
931
960
|
]:
|
|
932
|
-
if (
|
|
933
|
-
|
|
934
|
-
== cfg.CANCEL
|
|
935
|
-
):
|
|
936
|
-
self.twVariables.cellWidget(row, cfg.tw_indVarFields.index("type")).setCurrentIndex(
|
|
937
|
-
cfg.SET_OF_VALUES_idx
|
|
938
|
-
)
|
|
961
|
+
if dialog.MessageDialog(cfg.programName, "Erase the set of values?", [cfg.YES, cfg.CANCEL]) == cfg.CANCEL:
|
|
962
|
+
self.twVariables.cellWidget(row, cfg.tw_indVarFields.index("type")).setCurrentIndex(cfg.SET_OF_VALUES_idx)
|
|
939
963
|
return
|
|
940
964
|
else:
|
|
941
965
|
self.twVariables.item(row, cfg.tw_indVarFields.index("possible values")).setText("NA")
|
|
@@ -964,7 +988,6 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
964
988
|
|
|
965
989
|
existing_var = []
|
|
966
990
|
for r in range(self.twVariables.rowCount()):
|
|
967
|
-
|
|
968
991
|
if self.twVariables.item(r, 0).text().strip().upper() in existing_var:
|
|
969
992
|
return (
|
|
970
993
|
False,
|
|
@@ -1002,7 +1025,6 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1002
1025
|
return True, "OK"
|
|
1003
1026
|
|
|
1004
1027
|
def cbtype_changed(self):
|
|
1005
|
-
|
|
1006
1028
|
self.leSetValues.setVisible(self.cbType.currentText() == cfg.SET_OF_VALUES)
|
|
1007
1029
|
self.label_5.setVisible(self.cbType.currentText() == cfg.SET_OF_VALUES)
|
|
1008
1030
|
|
|
@@ -1012,10 +1034,9 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1012
1034
|
self.label_4.setVisible(self.cbType.currentText() != cfg.TIMESTAMP)
|
|
1013
1035
|
|
|
1014
1036
|
def cbtype_activated(self):
|
|
1015
|
-
|
|
1016
1037
|
if self.cbType.currentText() == cfg.TIMESTAMP:
|
|
1017
1038
|
self.twVariables.item(self.selected_twvariables_row, 3).setText(
|
|
1018
|
-
self.dte_default_date.dateTime().toString(
|
|
1039
|
+
self.dte_default_date.dateTime().toString("yyyy-MM-ddTHH:mm:ss.zzz")
|
|
1019
1040
|
)
|
|
1020
1041
|
self.twVariables.item(self.selected_twvariables_row, 4).setText("")
|
|
1021
1042
|
else:
|
|
@@ -1125,12 +1146,9 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1125
1146
|
)
|
|
1126
1147
|
|
|
1127
1148
|
for r in range(self.twBehaviors.rowCount()):
|
|
1128
|
-
|
|
1129
1149
|
if self.twBehaviors.item(r, cfg.behavioursFields[cfg.BEHAVIOR_CODE]):
|
|
1130
|
-
|
|
1131
1150
|
if include_point_events == cfg.YES or (
|
|
1132
|
-
include_point_events == cfg.NO
|
|
1133
|
-
and "State" in self.twBehaviors.item(r, cfg.behavioursFields[cfg.TYPE]).text()
|
|
1151
|
+
include_point_events == cfg.NO and "State" in self.twBehaviors.item(r, cfg.behavioursFields[cfg.TYPE]).text()
|
|
1134
1152
|
):
|
|
1135
1153
|
allBehaviors.append(self.twBehaviors.item(r, cfg.behavioursFields[cfg.BEHAVIOR_CODE]).text())
|
|
1136
1154
|
|
|
@@ -1185,9 +1203,7 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1185
1203
|
|
|
1186
1204
|
if c_name != r_name:
|
|
1187
1205
|
ex.checkboxes[f"{r_name}|{c_name}"] = QCheckBox()
|
|
1188
|
-
ex.checkboxes[f"{r_name}|{c_name}"].setStyleSheet(
|
|
1189
|
-
"text-align: center; margin-left:50%; margin-right:50%;"
|
|
1190
|
-
)
|
|
1206
|
+
ex.checkboxes[f"{r_name}|{c_name}"].setStyleSheet("text-align: center; margin-left:50%; margin-right:50%;")
|
|
1191
1207
|
|
|
1192
1208
|
if flag_left_bottom:
|
|
1193
1209
|
# hide if cell in left-bottom part of table
|
|
@@ -1215,18 +1231,15 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1215
1231
|
|
|
1216
1232
|
# update excluded field
|
|
1217
1233
|
for r in range(self.twBehaviors.rowCount()):
|
|
1218
|
-
if include_point_events == cfg.YES or (
|
|
1219
|
-
include_point_events == cfg.NO and "State" in self.twBehaviors.item(r, 0).text()
|
|
1220
|
-
):
|
|
1234
|
+
if include_point_events == cfg.YES or (include_point_events == cfg.NO and "State" in self.twBehaviors.item(r, 0).text()):
|
|
1221
1235
|
for e in excl:
|
|
1222
1236
|
if e == self.twBehaviors.item(r, cfg.behavioursFields[cfg.BEHAVIOR_CODE]).text():
|
|
1223
1237
|
item = QTableWidgetItem(",".join(new_excl[e]))
|
|
1224
1238
|
item.setFlags(Qt.ItemIsEnabled)
|
|
1225
|
-
item.setBackground(
|
|
1239
|
+
item.setBackground(self.not_editable_column_color())
|
|
1226
1240
|
self.twBehaviors.setItem(r, cfg.behavioursFields["excluded"], item)
|
|
1227
1241
|
|
|
1228
1242
|
def remove_all_behaviors(self):
|
|
1229
|
-
|
|
1230
1243
|
if not self.twBehaviors.rowCount():
|
|
1231
1244
|
QMessageBox.critical(
|
|
1232
1245
|
None,
|
|
@@ -1282,7 +1295,6 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1282
1295
|
self.lbObservationsState.setText("")
|
|
1283
1296
|
|
|
1284
1297
|
for r in range(self.twBehaviors.rowCount()):
|
|
1285
|
-
|
|
1286
1298
|
# check key
|
|
1287
1299
|
if self.twBehaviors.item(r, cfg.PROJECT_BEHAVIORS_KEY_FIELD_IDX):
|
|
1288
1300
|
key = self.twBehaviors.item(r, cfg.PROJECT_BEHAVIORS_KEY_FIELD_IDX).text()
|
|
@@ -1327,13 +1339,14 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1327
1339
|
self.twBehaviors.setItem(self.twBehaviors.rowCount() - 1, cfg.behavioursFields[field], item)
|
|
1328
1340
|
if field in (cfg.TYPE, "category", "excluded", "coding map", "modifiers"):
|
|
1329
1341
|
item.setFlags(Qt.ItemIsEnabled)
|
|
1330
|
-
item.setBackground(
|
|
1342
|
+
item.setBackground(self.not_editable_column_color())
|
|
1331
1343
|
if field == cfg.COLOR:
|
|
1332
1344
|
item.setFlags(Qt.ItemIsEnabled)
|
|
1333
1345
|
if QColor(self.twBehaviors.item(row, cfg.behavioursFields[field]).text()).isValid():
|
|
1334
1346
|
item.setBackground(QColor(self.twBehaviors.item(row, cfg.behavioursFields[field]).text()))
|
|
1335
1347
|
else:
|
|
1336
|
-
item.setBackground(
|
|
1348
|
+
item.setBackground(self.not_editable_column_color())
|
|
1349
|
+
|
|
1337
1350
|
self.twBehaviors.scrollToBottom()
|
|
1338
1351
|
|
|
1339
1352
|
def remove_behavior(self):
|
|
@@ -1353,26 +1366,22 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1353
1366
|
|
|
1354
1367
|
if not self.twBehaviors.selectedIndexes():
|
|
1355
1368
|
QMessageBox.warning(self, cfg.programName, "Select a behaviour to be removed")
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
== cfg.CANCEL
|
|
1370
|
-
):
|
|
1371
|
-
return
|
|
1372
|
-
break
|
|
1369
|
+
return
|
|
1370
|
+
|
|
1371
|
+
if dialog.MessageDialog(cfg.programName, "Remove the selected behavior?", [cfg.YES, cfg.CANCEL]) == cfg.YES:
|
|
1372
|
+
# check if behavior already used in observations
|
|
1373
|
+
codeToDelete = self.twBehaviors.item(self.twBehaviors.selectedIndexes()[0].row(), 2).text()
|
|
1374
|
+
for obs_id in self.pj[cfg.OBSERVATIONS]:
|
|
1375
|
+
if codeToDelete in [event[cfg.EVENT_BEHAVIOR_FIELD_IDX] for event in self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]]:
|
|
1376
|
+
if (
|
|
1377
|
+
dialog.MessageDialog(cfg.programName, "The code to remove is used in observations!", [cfg.REMOVE, cfg.CANCEL])
|
|
1378
|
+
== cfg.CANCEL
|
|
1379
|
+
):
|
|
1380
|
+
return
|
|
1381
|
+
break
|
|
1373
1382
|
|
|
1374
|
-
|
|
1375
|
-
|
|
1383
|
+
self.twBehaviors.removeRow(self.twBehaviors.selectedIndexes()[0].row())
|
|
1384
|
+
self.twBehaviors_cellChanged(0, 0)
|
|
1376
1385
|
|
|
1377
1386
|
def add_behavior(self):
|
|
1378
1387
|
"""
|
|
@@ -1388,36 +1397,34 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1388
1397
|
# no manual editing, gray back ground
|
|
1389
1398
|
if field_type in (cfg.TYPE, cfg.COLOR, "category", cfg.MODIFIERS, "modifiers", "excluded", "coding map"):
|
|
1390
1399
|
item.setFlags(Qt.ItemIsEnabled)
|
|
1391
|
-
item.setBackground(QColor(230, 230, 230))
|
|
1400
|
+
# item.setBackground(QColor(230, 230, 230))
|
|
1401
|
+
item.setBackground(self.not_editable_column_color())
|
|
1392
1402
|
self.twBehaviors.setItem(self.twBehaviors.rowCount() - 1, cfg.behavioursFields[field_type], item)
|
|
1393
1403
|
self.twBehaviors.scrollToBottom()
|
|
1394
1404
|
|
|
1395
|
-
def
|
|
1405
|
+
def behavior_type_changed(self, row: int) -> None:
|
|
1396
1406
|
"""
|
|
1397
1407
|
event type combobox changed
|
|
1398
1408
|
"""
|
|
1399
1409
|
|
|
1400
|
-
if
|
|
1410
|
+
if cfg.CODING_MAP_sp in self.twBehaviors.item(row, cfg.behavioursFields[cfg.TYPE]).text():
|
|
1401
1411
|
# let user select a coding maop
|
|
1402
|
-
|
|
1412
|
+
file_name, _ = QFileDialog().getOpenFileName(
|
|
1403
1413
|
self,
|
|
1404
|
-
"Select a coding map for "
|
|
1405
|
-
f"{self.twBehaviors.item(row, cfg.behavioursFields['code']).text()} behavior",
|
|
1414
|
+
f"Select a modifier coding map for {self.twBehaviors.item(row, cfg.behavioursFields['code']).text()} behavior",
|
|
1406
1415
|
"",
|
|
1407
1416
|
"BORIS map files (*.boris_map);;All files (*)",
|
|
1408
1417
|
)
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
if fileName:
|
|
1418
|
+
if file_name:
|
|
1412
1419
|
try:
|
|
1413
|
-
new_map = json.loads(open(
|
|
1420
|
+
new_map = json.loads(open(file_name, "r").read())
|
|
1414
1421
|
except Exception:
|
|
1415
|
-
QMessageBox.critical(self, cfg.programName, "Error reding the
|
|
1422
|
+
QMessageBox.critical(self, cfg.programName, "Error reding the coding map")
|
|
1416
1423
|
return
|
|
1417
1424
|
self.pj[cfg.CODING_MAP][new_map["name"]] = new_map
|
|
1418
1425
|
|
|
1419
1426
|
# add modifiers from coding map areas
|
|
1420
|
-
modifstr =
|
|
1427
|
+
modifstr = json.dumps(
|
|
1421
1428
|
{
|
|
1422
1429
|
"0": {
|
|
1423
1430
|
"name": new_map["name"],
|
|
@@ -1433,9 +1440,7 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1433
1440
|
else:
|
|
1434
1441
|
# if coding map already exists do not reset the behavior type if no filename selected
|
|
1435
1442
|
if not self.twBehaviors.item(row, cfg.behavioursFields["coding map"]).text():
|
|
1436
|
-
QMessageBox.critical(
|
|
1437
|
-
self, cfg.programName, 'No coding map was selected.\nEvent type will be reset to "Point event" '
|
|
1438
|
-
)
|
|
1443
|
+
QMessageBox.critical(self, cfg.programName, 'No coding map was selected.\nEvent type will be reset to "Point event" ')
|
|
1439
1444
|
self.twBehaviors.item(row, cfg.behavioursFields["type"]).setText("Point event")
|
|
1440
1445
|
else:
|
|
1441
1446
|
self.twBehaviors.item(row, cfg.behavioursFields["coding map"]).setText("")
|
|
@@ -1461,12 +1466,9 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1461
1466
|
QMessageBox.warning(self, cfg.programName, "Select a subject to remove")
|
|
1462
1467
|
else:
|
|
1463
1468
|
if dialog.MessageDialog(cfg.programName, "Remove the selected subject?", [cfg.YES, cfg.CANCEL]) == cfg.YES:
|
|
1464
|
-
|
|
1465
1469
|
flagDel = False
|
|
1466
1470
|
if self.twSubjects.item(self.twSubjects.selectedIndexes()[0].row(), 1):
|
|
1467
|
-
subjectToDelete = self.twSubjects.item(
|
|
1468
|
-
self.twSubjects.selectedIndexes()[0].row(), 1
|
|
1469
|
-
).text() # 1: subject name
|
|
1471
|
+
subjectToDelete = self.twSubjects.item(self.twSubjects.selectedIndexes()[0].row(), 1).text() # 1: subject name
|
|
1470
1472
|
|
|
1471
1473
|
subjectsInObs = []
|
|
1472
1474
|
for obs in self.pj[cfg.OBSERVATIONS]:
|
|
@@ -1546,19 +1548,18 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1546
1548
|
|
|
1547
1549
|
self.twSubjects_cellChanged(0, 0)
|
|
1548
1550
|
|
|
1549
|
-
def twSubjects_cellChanged(self, row: int, column: int):
|
|
1551
|
+
def twSubjects_cellChanged(self, row: int, column: int) -> None:
|
|
1550
1552
|
"""
|
|
1551
1553
|
check if subject not unique
|
|
1552
1554
|
"""
|
|
1553
1555
|
|
|
1554
|
-
subjects
|
|
1556
|
+
subjects: list = []
|
|
1557
|
+
"""keys: list = []"""
|
|
1555
1558
|
self.lbSubjectsState.setText("")
|
|
1556
1559
|
|
|
1557
1560
|
for r in range(self.twSubjects.rowCount()):
|
|
1558
|
-
|
|
1559
1561
|
# check key
|
|
1560
1562
|
if self.twSubjects.item(r, 0):
|
|
1561
|
-
|
|
1562
1563
|
# check key length
|
|
1563
1564
|
if (
|
|
1564
1565
|
self.twSubjects.item(r, 0).text().upper() not in list(cfg.function_keys.values())
|
|
@@ -1573,11 +1574,14 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1573
1574
|
)
|
|
1574
1575
|
return
|
|
1575
1576
|
|
|
1577
|
+
# control of duplicated key removed 2024-01-29
|
|
1578
|
+
"""
|
|
1576
1579
|
if self.twSubjects.item(r, 0).text() in keys:
|
|
1577
1580
|
self.lbSubjectsState.setText(f'<font color="red">Key duplicated at row # {r + 1}</font>')
|
|
1578
1581
|
else:
|
|
1579
1582
|
if self.twSubjects.item(r, 0).text():
|
|
1580
1583
|
keys.append(self.twSubjects.item(r, 0).text())
|
|
1584
|
+
"""
|
|
1581
1585
|
|
|
1582
1586
|
# check subject
|
|
1583
1587
|
if self.twSubjects.item(r, 1):
|
|
@@ -1596,14 +1600,14 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1596
1600
|
logging.debug(f"selected row: {self.selected_twvariables_row}")
|
|
1597
1601
|
|
|
1598
1602
|
if self.selected_twvariables_row == -1:
|
|
1599
|
-
for widget in
|
|
1603
|
+
for widget in (
|
|
1600
1604
|
self.leLabel,
|
|
1601
1605
|
self.leDescription,
|
|
1602
1606
|
self.cbType,
|
|
1603
1607
|
self.lePredefined,
|
|
1604
1608
|
self.dte_default_date,
|
|
1605
1609
|
self.leSetValues,
|
|
1606
|
-
|
|
1610
|
+
):
|
|
1607
1611
|
widget.setEnabled(False)
|
|
1608
1612
|
self.leLabel.setText("")
|
|
1609
1613
|
self.leDescription.setText("")
|
|
@@ -1614,20 +1618,27 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1614
1618
|
return
|
|
1615
1619
|
|
|
1616
1620
|
# enable widget for indep var setting
|
|
1617
|
-
for widget in
|
|
1621
|
+
for widget in (
|
|
1618
1622
|
self.leLabel,
|
|
1619
1623
|
self.leDescription,
|
|
1620
1624
|
self.cbType,
|
|
1621
1625
|
self.lePredefined,
|
|
1622
1626
|
self.dte_default_date,
|
|
1623
1627
|
self.leSetValues,
|
|
1624
|
-
|
|
1628
|
+
):
|
|
1625
1629
|
widget.setEnabled(True)
|
|
1626
1630
|
|
|
1627
1631
|
self.leLabel.setText(self.twVariables.item(row, 0).text())
|
|
1628
1632
|
self.leDescription.setText(self.twVariables.item(row, 1).text())
|
|
1629
1633
|
self.lePredefined.setText(self.twVariables.item(row, 3).text())
|
|
1630
1634
|
self.leSetValues.setText(self.twVariables.item(row, 4).text())
|
|
1635
|
+
if self.twVariables.item(row, 2).text() == cfg.TIMESTAMP:
|
|
1636
|
+
if len(self.twVariables.item(row, 3).text()) == len("yyyy-MM-ddTHH:mm:ss.zzz"):
|
|
1637
|
+
datetime_format = "yyyy-MM-ddThh:mm:ss.zzz"
|
|
1638
|
+
if len(self.twVariables.item(row, 3).text()) == len("yyyy-MM-ddTHH:mm:ss"):
|
|
1639
|
+
datetime_format = "yyyy-MM-ddThh:mm:ss"
|
|
1640
|
+
|
|
1641
|
+
self.dte_default_date.setDateTime(QDateTime.fromString(self.twVariables.item(row, 3).text(), datetime_format))
|
|
1631
1642
|
|
|
1632
1643
|
self.cbType.clear()
|
|
1633
1644
|
self.cbType.addItems(cfg.AVAILABLE_INDEP_VAR_TYPES)
|
|
@@ -1637,12 +1648,7 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1637
1648
|
|
|
1638
1649
|
def pbCancel_clicked(self):
|
|
1639
1650
|
if self.flag_modified:
|
|
1640
|
-
if (
|
|
1641
|
-
dialog.MessageDialog(
|
|
1642
|
-
"BORIS", "The converters were modified. Are you sure to cancel?", [cfg.CANCEL, cfg.OK]
|
|
1643
|
-
)
|
|
1644
|
-
== cfg.OK
|
|
1645
|
-
):
|
|
1651
|
+
if dialog.MessageDialog("BORIS", "The converters were modified. Are you sure to cancel?", [cfg.CANCEL, cfg.OK]) == cfg.OK:
|
|
1646
1652
|
self.reject()
|
|
1647
1653
|
else:
|
|
1648
1654
|
self.reject()
|
|
@@ -1650,12 +1656,12 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1650
1656
|
def check_ethogram(self) -> dict:
|
|
1651
1657
|
"""
|
|
1652
1658
|
check ethogram for various parameter
|
|
1653
|
-
returns ethogram dict or {cfg.CANCEL: True
|
|
1659
|
+
returns ethogram dict or {cfg.CANCEL: True} in case of error
|
|
1654
1660
|
|
|
1655
1661
|
"""
|
|
1656
1662
|
# store behaviors
|
|
1657
|
-
missing_data = []
|
|
1658
|
-
checked_ethogram = {}
|
|
1663
|
+
missing_data: list = []
|
|
1664
|
+
checked_ethogram: dict = {}
|
|
1659
1665
|
|
|
1660
1666
|
# Ethogram
|
|
1661
1667
|
# coding maps in ethogram
|
|
@@ -1663,18 +1669,19 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1663
1669
|
# check for leading/trailing space in behaviors and modifiers
|
|
1664
1670
|
code_with_leading_trailing_spaces, modifiers_with_leading_trailing_spaces = [], []
|
|
1665
1671
|
for r in range(self.twBehaviors.rowCount()):
|
|
1666
|
-
|
|
1667
1672
|
if (
|
|
1668
1673
|
self.twBehaviors.item(r, cfg.behavioursFields[cfg.BEHAVIOR_CODE]).text()
|
|
1669
1674
|
!= self.twBehaviors.item(r, cfg.behavioursFields[cfg.BEHAVIOR_CODE]).text().strip()
|
|
1670
1675
|
):
|
|
1671
|
-
code_with_leading_trailing_spaces.append(
|
|
1672
|
-
self.twBehaviors.item(r, cfg.behavioursFields[cfg.BEHAVIOR_CODE]).text()
|
|
1673
|
-
)
|
|
1676
|
+
code_with_leading_trailing_spaces.append(self.twBehaviors.item(r, cfg.behavioursFields[cfg.BEHAVIOR_CODE]).text())
|
|
1674
1677
|
|
|
1675
1678
|
if self.twBehaviors.item(r, cfg.behavioursFields["modifiers"]).text():
|
|
1676
1679
|
try:
|
|
1677
|
-
modifiers_dict =
|
|
1680
|
+
modifiers_dict = (
|
|
1681
|
+
json.loads(self.twBehaviors.item(r, cfg.behavioursFields["modifiers"]).text())
|
|
1682
|
+
if self.twBehaviors.item(r, cfg.behavioursFields["modifiers"]).text()
|
|
1683
|
+
else {}
|
|
1684
|
+
)
|
|
1678
1685
|
for k in modifiers_dict:
|
|
1679
1686
|
for value in modifiers_dict[k]["values"]:
|
|
1680
1687
|
modif_code = value.split(" (")[0]
|
|
@@ -1724,12 +1731,8 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1724
1731
|
row = {}
|
|
1725
1732
|
for field in cfg.behavioursFields:
|
|
1726
1733
|
if self.twBehaviors.item(r, cfg.behavioursFields[field]):
|
|
1727
|
-
|
|
1728
1734
|
# check for | char in code
|
|
1729
|
-
if (
|
|
1730
|
-
field == cfg.BEHAVIOR_CODE
|
|
1731
|
-
and "|" in self.twBehaviors.item(r, cfg.behavioursFields[field]).text()
|
|
1732
|
-
):
|
|
1735
|
+
if field == cfg.BEHAVIOR_CODE and "|" in self.twBehaviors.item(r, cfg.behavioursFields[field]).text():
|
|
1733
1736
|
QMessageBox.warning(
|
|
1734
1737
|
self,
|
|
1735
1738
|
cfg.programName,
|
|
@@ -1746,10 +1749,9 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1746
1749
|
row[field] = self.twBehaviors.item(r, cfg.behavioursFields[field]).text()
|
|
1747
1750
|
|
|
1748
1751
|
if field == "modifiers" and row["modifiers"]:
|
|
1749
|
-
|
|
1750
1752
|
if remove_leading_trailing_spaces_in_modifiers == cfg.YES:
|
|
1751
1753
|
try:
|
|
1752
|
-
modifiers_dict =
|
|
1754
|
+
modifiers_dict = json.loads(row["modifiers"]) if row["modifiers"] else {}
|
|
1753
1755
|
for k in modifiers_dict:
|
|
1754
1756
|
for idx, value in enumerate(modifiers_dict[k]["values"]):
|
|
1755
1757
|
modif_code = value.split(" (")[0]
|
|
@@ -1760,15 +1762,12 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1760
1762
|
|
|
1761
1763
|
row["modifiers"] = dict(modifiers_dict)
|
|
1762
1764
|
except Exception:
|
|
1763
|
-
|
|
1764
1765
|
logging.critical("Error removing leading/trailing spaces in modifiers")
|
|
1765
1766
|
|
|
1766
|
-
QMessageBox.critical(
|
|
1767
|
-
self, cfg.programName, "Error removing leading/trailing spaces in modifiers"
|
|
1768
|
-
)
|
|
1767
|
+
QMessageBox.critical(self, cfg.programName, "Error removing leading/trailing spaces in modifiers")
|
|
1769
1768
|
|
|
1770
1769
|
else:
|
|
1771
|
-
row["modifiers"] =
|
|
1770
|
+
row["modifiers"] = json.loads(row["modifiers"]) if row["modifiers"] else {}
|
|
1772
1771
|
else:
|
|
1773
1772
|
row[field] = ""
|
|
1774
1773
|
|
|
@@ -1794,27 +1793,40 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1794
1793
|
return {cfg.CANCEL: True}
|
|
1795
1794
|
|
|
1796
1795
|
# check if behavior belong to category that is not in categories list
|
|
1797
|
-
|
|
1796
|
+
missing_behavior_category: list = []
|
|
1798
1797
|
for idx in checked_ethogram:
|
|
1799
1798
|
if cfg.BEHAVIOR_CATEGORY in checked_ethogram[idx]:
|
|
1800
1799
|
if checked_ethogram[idx][cfg.BEHAVIOR_CATEGORY]:
|
|
1801
1800
|
if checked_ethogram[idx][cfg.BEHAVIOR_CATEGORY] not in self.pj[cfg.BEHAVIORAL_CATEGORIES]:
|
|
1802
|
-
|
|
1801
|
+
missing_behavior_category.append(
|
|
1803
1802
|
(checked_ethogram[idx][cfg.BEHAVIOR_CODE], checked_ethogram[idx][cfg.BEHAVIOR_CATEGORY])
|
|
1804
1803
|
)
|
|
1805
|
-
if
|
|
1806
|
-
|
|
1804
|
+
if missing_behavior_category:
|
|
1807
1805
|
response = dialog.MessageDialog(
|
|
1808
1806
|
f"{cfg.programName} - Behavioral categories",
|
|
1809
1807
|
(
|
|
1810
|
-
"The behavioral
|
|
1811
|
-
f"{', '.join(set(['<b>' + x[1] + '</b>' + ' (used with <b>' + x[0] + '</b>)' for x in
|
|
1812
|
-
"are
|
|
1808
|
+
"The behavioral category/ies<br> "
|
|
1809
|
+
f"{', '.join(set(['<b>' + x[1] + '</b>' + ' (used with <b>' + x[0] + '</b>)<br>' for x in missing_behavior_category]))} "
|
|
1810
|
+
"are not defined in behavioral categories list.<br>"
|
|
1813
1811
|
),
|
|
1814
|
-
["Add behavioral category/ies",
|
|
1812
|
+
["Add behavioral category/ies", cfg.IGNORE, cfg.CANCEL],
|
|
1815
1813
|
)
|
|
1816
1814
|
if response == "Add behavioral category/ies":
|
|
1817
|
-
|
|
1815
|
+
if cfg.BEHAVIORAL_CATEGORIES_CONF not in self.pj:
|
|
1816
|
+
self.pj[cfg.BEHAVIORAL_CATEGORIES_CONF] = {}
|
|
1817
|
+
for x1 in set(x[1] for x in missing_behavior_category):
|
|
1818
|
+
self.pj[cfg.BEHAVIORAL_CATEGORIES].append(x1)
|
|
1819
|
+
|
|
1820
|
+
if self.pj[cfg.BEHAVIORAL_CATEGORIES_CONF]:
|
|
1821
|
+
index = str(max([int(k) for k in self.pj[cfg.BEHAVIORAL_CATEGORIES_CONF]]) + 1)
|
|
1822
|
+
else:
|
|
1823
|
+
index = "0"
|
|
1824
|
+
|
|
1825
|
+
self.pj[cfg.BEHAVIORAL_CATEGORIES_CONF][index] = {
|
|
1826
|
+
"name": x1,
|
|
1827
|
+
cfg.COLOR: "",
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1818
1830
|
if response == cfg.CANCEL:
|
|
1819
1831
|
return {cfg.CANCEL: True}
|
|
1820
1832
|
|
|
@@ -1845,7 +1857,7 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1845
1857
|
self.pj[cfg.TIME_FORMAT] = cfg.HHMMSS
|
|
1846
1858
|
|
|
1847
1859
|
# store subjects
|
|
1848
|
-
self.subjects_conf = {}
|
|
1860
|
+
self.subjects_conf: dict = {}
|
|
1849
1861
|
|
|
1850
1862
|
# check for leading/trailing spaces in subjects names
|
|
1851
1863
|
subjects_name_with_leading_trailing_spaces = ""
|
|
@@ -1856,7 +1868,6 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1856
1868
|
|
|
1857
1869
|
remove_leading_trailing_spaces = cfg.NO
|
|
1858
1870
|
if subjects_name_with_leading_trailing_spaces:
|
|
1859
|
-
|
|
1860
1871
|
remove_leading_trailing_spaces = dialog.MessageDialog(
|
|
1861
1872
|
cfg.programName,
|
|
1862
1873
|
(
|
|
@@ -1872,10 +1883,9 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1872
1883
|
# check subjects
|
|
1873
1884
|
for row in range(self.twSubjects.rowCount()):
|
|
1874
1885
|
# check key
|
|
1886
|
+
key: str = ""
|
|
1875
1887
|
if self.twSubjects.item(row, 0):
|
|
1876
1888
|
key = self.twSubjects.item(row, 0).text()
|
|
1877
|
-
else:
|
|
1878
|
-
key = ""
|
|
1879
1889
|
|
|
1880
1890
|
# check subject name
|
|
1881
1891
|
if self.twSubjects.item(row, 1):
|
|
@@ -1886,9 +1896,7 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1886
1896
|
|
|
1887
1897
|
# check if subject name is empty
|
|
1888
1898
|
if subjectName == "":
|
|
1889
|
-
QMessageBox.warning(
|
|
1890
|
-
self, cfg.programName, f"The subject name can not be empty (check row #{row + 1})."
|
|
1891
|
-
)
|
|
1899
|
+
QMessageBox.warning(self, cfg.programName, f"The subject name can not be empty (check row #{row + 1}).")
|
|
1892
1900
|
return
|
|
1893
1901
|
|
|
1894
1902
|
if "|" in subjectName:
|
|
@@ -1899,13 +1907,11 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1899
1907
|
)
|
|
1900
1908
|
return
|
|
1901
1909
|
else:
|
|
1902
|
-
QMessageBox.warning(
|
|
1903
|
-
self, cfg.programName, f"Missing subject name in subjects configuration at row #{row + 1}"
|
|
1904
|
-
)
|
|
1910
|
+
QMessageBox.warning(self, cfg.programName, f"Missing subject name in subjects configuration at row #{row + 1}")
|
|
1905
1911
|
return
|
|
1906
1912
|
|
|
1907
1913
|
# description
|
|
1908
|
-
subjectDescription = ""
|
|
1914
|
+
subjectDescription: str = ""
|
|
1909
1915
|
if self.twSubjects.item(row, 2):
|
|
1910
1916
|
subjectDescription = self.twSubjects.item(row, 2).text().strip()
|
|
1911
1917
|
|
|
@@ -1915,6 +1921,25 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1915
1921
|
"description": subjectDescription,
|
|
1916
1922
|
}
|
|
1917
1923
|
|
|
1924
|
+
# check if coded subjects are defined in the subjects list
|
|
1925
|
+
subjects_list: list = [self.subjects_conf[x]["name"] for x in self.subjects_conf]
|
|
1926
|
+
coded_subjects = set(
|
|
1927
|
+
util.flatten_list([[y[1] for y in self.pj[cfg.OBSERVATIONS][x].get(cfg.EVENTS, [])] for x in self.pj[cfg.OBSERVATIONS]])
|
|
1928
|
+
)
|
|
1929
|
+
|
|
1930
|
+
not_defined_subjects: list = []
|
|
1931
|
+
for subject in coded_subjects:
|
|
1932
|
+
if subject and subject not in subjects_list:
|
|
1933
|
+
not_defined_subjects.append(subject)
|
|
1934
|
+
|
|
1935
|
+
if not_defined_subjects:
|
|
1936
|
+
QMessageBox.warning(
|
|
1937
|
+
self,
|
|
1938
|
+
cfg.programName,
|
|
1939
|
+
f"The coded subject(s) <b>{', '.join(not_defined_subjects)}</b> is/are not defined in the subjects list.<br>You can use the <b>Explore project</b> to fix it.",
|
|
1940
|
+
)
|
|
1941
|
+
return
|
|
1942
|
+
|
|
1918
1943
|
self.pj[cfg.SUBJECTS] = dict(self.subjects_conf)
|
|
1919
1944
|
|
|
1920
1945
|
# check ethogram
|
|
@@ -1952,7 +1977,7 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1952
1977
|
self.pj[cfg.INDEPENDENT_VARIABLES] = dict(self.indVar)
|
|
1953
1978
|
|
|
1954
1979
|
# converters
|
|
1955
|
-
converters = {}
|
|
1980
|
+
converters: dict = {}
|
|
1956
1981
|
for row in range(self.tw_converters.rowCount()):
|
|
1957
1982
|
converters[self.tw_converters.item(row, 0).text()] = {
|
|
1958
1983
|
"name": self.tw_converters.item(row, 0).text(),
|
|
@@ -1972,9 +1997,7 @@ class projectDialog(QDialog, Ui_dlgProject):
|
|
|
1972
1997
|
for converter in sorted(self.converters.keys()):
|
|
1973
1998
|
self.tw_converters.setRowCount(self.tw_converters.rowCount() + 1)
|
|
1974
1999
|
self.tw_converters.setItem(self.tw_converters.rowCount() - 1, 0, QTableWidgetItem(converter)) # id / name
|
|
1975
|
-
self.tw_converters.setItem(
|
|
1976
|
-
self.tw_converters.rowCount() - 1, 1, QTableWidgetItem(self.converters[converter]["description"])
|
|
1977
|
-
)
|
|
2000
|
+
self.tw_converters.setItem(self.tw_converters.rowCount() - 1, 1, QTableWidgetItem(self.converters[converter]["description"]))
|
|
1978
2001
|
self.tw_converters.setItem(
|
|
1979
2002
|
self.tw_converters.rowCount() - 1,
|
|
1980
2003
|
2,
|