boris-behav-obs 8.16.5__py3-none-any.whl → 9.7.1__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 +24 -36
- boris/add_modifier.py +88 -80
- boris/add_modifier_ui.py +235 -131
- boris/advanced_event_filtering.py +23 -29
- boris/analysis_plugins/__init__.py +0 -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 +228 -229
- 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 +16 -34
- boris/config.py +102 -50
- boris/config_file.py +55 -64
- boris/connections.py +105 -58
- boris/converters.py +13 -37
- boris/converters_ui.py +187 -110
- boris/cooccurence.py +250 -0
- boris/core.py +2108 -1275
- 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 +304 -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 +493 -210
- boris/observation_operations.py +1010 -391
- 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 +18 -53
- boris/plot_events.py +56 -153
- boris/plot_events_rt.py +16 -30
- boris/plot_spectrogram_rt.py +80 -56
- boris/plot_waveform_rt.py +23 -48
- boris/plugins.py +431 -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 +298 -123
- boris/preferences_ui.py +664 -225
- boris/project.py +293 -270
- boris/project_functions.py +610 -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 +6 -8
- 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 +562 -222
- boris/version.py +3 -3
- boris/video_equalizer.py +16 -14
- boris/video_equalizer_ui.py +199 -130
- boris/video_operations.py +78 -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.1.dist-info/METADATA +140 -0
- boris_behav_obs-9.7.1.dist-info/RECORD +109 -0
- {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.1.dist-info}/WHEEL +1 -1
- boris_behav_obs-9.7.1.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.1.dist-info/licenses}/LICENSE.TXT +0 -0
- {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.1.dist-info}/top_level.txt +0 -0
boris/project_import_export.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
|
|
|
@@ -28,9 +28,9 @@ import pandas as pd
|
|
|
28
28
|
import tablib
|
|
29
29
|
import pickle
|
|
30
30
|
|
|
31
|
-
from
|
|
32
|
-
from
|
|
33
|
-
from
|
|
31
|
+
from PySide6.QtCore import Qt
|
|
32
|
+
from PySide6.QtGui import QFont
|
|
33
|
+
from PySide6.QtWidgets import QApplication, QFileDialog, QListWidgetItem, QMessageBox, QTableWidgetItem
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
from . import config as cfg
|
|
@@ -62,7 +62,7 @@ def export_ethogram(self) -> None:
|
|
|
62
62
|
"""
|
|
63
63
|
export ethogram in various format
|
|
64
64
|
"""
|
|
65
|
-
extended_file_formats = [
|
|
65
|
+
extended_file_formats: list = [
|
|
66
66
|
"BORIS project file (*.boris)",
|
|
67
67
|
"Tab Separated Values (*.tsv)",
|
|
68
68
|
"Comma Separated Values (*.csv)",
|
|
@@ -71,7 +71,7 @@ def export_ethogram(self) -> None:
|
|
|
71
71
|
"Legacy Microsoft Excel Spreadsheet XLS (*.xls)",
|
|
72
72
|
"HTML (*.html)",
|
|
73
73
|
]
|
|
74
|
-
file_formats = ["boris", cfg.TSV_EXT,
|
|
74
|
+
file_formats: list = ["boris", cfg.TSV_EXT, cfg.CSV_EXT, cfg.ODS_EXT, cfg.XLSX_EXT, cfg.XLS_EXT, cfg.HTML_EXT]
|
|
75
75
|
|
|
76
76
|
filediag_func = QFileDialog().getSaveFileName
|
|
77
77
|
|
|
@@ -79,62 +79,23 @@ def export_ethogram(self) -> None:
|
|
|
79
79
|
if not file_name:
|
|
80
80
|
return
|
|
81
81
|
|
|
82
|
-
output_format = file_formats[extended_file_formats.index(filter_)]
|
|
82
|
+
output_format: str = file_formats[extended_file_formats.index(filter_)]
|
|
83
83
|
if pl.Path(file_name).suffix != "." + output_format:
|
|
84
84
|
file_name = str(pl.Path(file_name)) + "." + output_format
|
|
85
85
|
|
|
86
|
-
ethogram_data = tablib.Dataset()
|
|
87
|
-
ethogram_data.title = "Ethogram"
|
|
88
|
-
if self.leProjectName.text():
|
|
89
|
-
ethogram_data.title = f"Ethogram of {self.leProjectName.text()} project"
|
|
90
|
-
|
|
91
|
-
ethogram_data.headers = [
|
|
92
|
-
"Behavior code",
|
|
93
|
-
"Behavior type",
|
|
94
|
-
"Description",
|
|
95
|
-
"Key",
|
|
96
|
-
"Behavioral category",
|
|
97
|
-
"Excluded behaviors",
|
|
98
|
-
"modifiers",
|
|
99
|
-
# "modifiers (JSON)",
|
|
100
|
-
]
|
|
101
|
-
|
|
102
|
-
for r in range(self.twBehaviors.rowCount()):
|
|
103
|
-
row = []
|
|
104
|
-
for field in ("code", cfg.TYPE, "description", "key", cfg.COLOR, "category", "excluded"):
|
|
105
|
-
row.append(self.twBehaviors.item(r, cfg.behavioursFields[field]).text())
|
|
106
|
-
|
|
107
|
-
# modifiers
|
|
108
|
-
if self.twBehaviors.item(r, cfg.behavioursFields[cfg.MODIFIERS]).text():
|
|
109
|
-
modifiers_dict = eval(self.twBehaviors.item(r, cfg.behavioursFields[cfg.MODIFIERS]).text())
|
|
110
|
-
modifiers_list = []
|
|
111
|
-
for key in modifiers_dict:
|
|
112
|
-
if modifiers_dict[key]["values"]:
|
|
113
|
-
values = ", ".join(modifiers_dict[key]["values"])
|
|
114
|
-
modifiers_list.append(f"{modifiers_dict[key]['name']} ({values})")
|
|
115
|
-
else:
|
|
116
|
-
modifiers_list.append(modifiers_dict[key]["name"])
|
|
117
|
-
|
|
118
|
-
row.append(", ".join(modifiers_list))
|
|
119
|
-
else:
|
|
120
|
-
row.append("")
|
|
121
|
-
|
|
122
|
-
ethogram_data.append(row)
|
|
123
|
-
|
|
124
86
|
if output_format == "boris":
|
|
125
87
|
r = self.check_ethogram()
|
|
126
88
|
if cfg.CANCEL in r:
|
|
127
89
|
return
|
|
128
90
|
pj = dict(cfg.EMPTY_PROJECT)
|
|
129
91
|
pj[cfg.ETHOGRAM] = dict(r)
|
|
130
|
-
# behavioral categories
|
|
131
92
|
|
|
93
|
+
# behavioral categories
|
|
132
94
|
pj[cfg.BEHAVIORAL_CATEGORIES] = list(self.pj[cfg.BEHAVIORAL_CATEGORIES])
|
|
95
|
+
pj[cfg.BEHAVIORAL_CATEGORIES_CONF] = dict(self.pj.get(cfg.BEHAVIORAL_CATEGORIES_CONF, {}))
|
|
133
96
|
|
|
134
97
|
# project file indentation
|
|
135
|
-
file_indentation = self.config_param.get(
|
|
136
|
-
cfg.PROJECT_FILE_INDENTATION, cfg.PROJECT_FILE_INDENTATION_DEFAULT_VALUE
|
|
137
|
-
)
|
|
98
|
+
file_indentation = self.config_param.get(cfg.PROJECT_FILE_INDENTATION, cfg.PROJECT_FILE_INDENTATION_DEFAULT_VALUE)
|
|
138
99
|
try:
|
|
139
100
|
with open(file_name, "w") as f_out:
|
|
140
101
|
f_out.write(json.dumps(pj, indent=file_indentation))
|
|
@@ -148,6 +109,47 @@ def export_ethogram(self) -> None:
|
|
|
148
109
|
)
|
|
149
110
|
|
|
150
111
|
else:
|
|
112
|
+
ethogram_data = tablib.Dataset()
|
|
113
|
+
ethogram_data.title = "Ethogram"
|
|
114
|
+
if self.leProjectName.text():
|
|
115
|
+
ethogram_data.title = f"Ethogram of {self.leProjectName.text()} project"
|
|
116
|
+
|
|
117
|
+
ethogram_data.headers = [
|
|
118
|
+
"Behavior code",
|
|
119
|
+
"Behavior type",
|
|
120
|
+
"Description",
|
|
121
|
+
"Key",
|
|
122
|
+
"Color",
|
|
123
|
+
"Behavioral category",
|
|
124
|
+
"Excluded behaviors",
|
|
125
|
+
"Modifiers",
|
|
126
|
+
"Modifiers (JSON)",
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
for r in range(self.twBehaviors.rowCount()):
|
|
130
|
+
row: list = []
|
|
131
|
+
for field in ("code", cfg.TYPE, "description", "key", cfg.COLOR, "category", "excluded"):
|
|
132
|
+
row.append(self.twBehaviors.item(r, cfg.behavioursFields[field]).text())
|
|
133
|
+
|
|
134
|
+
# modifiers
|
|
135
|
+
if self.twBehaviors.item(r, cfg.behavioursFields[cfg.MODIFIERS]).text():
|
|
136
|
+
# modifiers a string
|
|
137
|
+
modifiers_dict = json.loads(self.twBehaviors.item(r, cfg.behavioursFields[cfg.MODIFIERS]).text())
|
|
138
|
+
modifiers_list = []
|
|
139
|
+
for key in modifiers_dict:
|
|
140
|
+
values = ",".join(modifiers_dict[key]["values"])
|
|
141
|
+
modifiers_list.append(f"{modifiers_dict[key]['name']}:{values}")
|
|
142
|
+
row.append(";".join(modifiers_list))
|
|
143
|
+
# modifiers as JSON
|
|
144
|
+
row.append(self.twBehaviors.item(r, cfg.behavioursFields[cfg.MODIFIERS]).text())
|
|
145
|
+
else:
|
|
146
|
+
# modifiers a string
|
|
147
|
+
row.append("")
|
|
148
|
+
# modifiers as JSON
|
|
149
|
+
row.append("")
|
|
150
|
+
|
|
151
|
+
ethogram_data.append(row)
|
|
152
|
+
|
|
151
153
|
ok, msg = export_observation.dataset_write(ethogram_data, file_name, output_format)
|
|
152
154
|
if not ok:
|
|
153
155
|
QMessageBox.critical(None, cfg.programName, msg, QMessageBox.Ok | QMessageBox.Default, QMessageBox.NoButton)
|
|
@@ -157,15 +159,15 @@ def export_subjects(self) -> None:
|
|
|
157
159
|
"""
|
|
158
160
|
export the subjetcs list in various format
|
|
159
161
|
"""
|
|
160
|
-
extended_file_formats = [
|
|
162
|
+
extended_file_formats: list = [
|
|
161
163
|
cfg.TSV,
|
|
162
164
|
cfg.CSV,
|
|
163
165
|
cfg.ODS,
|
|
164
166
|
cfg.XLSX,
|
|
165
167
|
cfg.XLS,
|
|
166
|
-
cfg.
|
|
168
|
+
cfg.HTML,
|
|
167
169
|
]
|
|
168
|
-
file_formats = [cfg.TSV_EXT,
|
|
170
|
+
file_formats: list = [cfg.TSV_EXT, cfg.CSV_EXT, cfg.ODS_EXT, cfg.XLSX_EXT, cfg.XLS_EXT, cfg.HTML_EXT]
|
|
169
171
|
|
|
170
172
|
filediag_func = QFileDialog().getSaveFileName
|
|
171
173
|
|
|
@@ -182,15 +184,14 @@ def export_subjects(self) -> None:
|
|
|
182
184
|
if self.leProjectName.text():
|
|
183
185
|
subjects_data.title = f"Subjects defined in the {self.leProjectName.text()} project"
|
|
184
186
|
|
|
185
|
-
subjects_data.headers = [
|
|
187
|
+
subjects_data.headers: list = [
|
|
186
188
|
"Key",
|
|
187
189
|
"Subject name",
|
|
188
190
|
"Description",
|
|
189
191
|
]
|
|
190
192
|
|
|
191
193
|
for r in range(self.twSubjects.rowCount()):
|
|
192
|
-
|
|
193
|
-
row = []
|
|
194
|
+
row: list = []
|
|
194
195
|
for idx, _ in enumerate(("Key", "Subject name", "Description")):
|
|
195
196
|
row.append(self.twSubjects.item(r, idx).text())
|
|
196
197
|
|
|
@@ -223,7 +224,7 @@ def select_behaviors(
|
|
|
223
224
|
paramPanelWindow.resize(800, 600)
|
|
224
225
|
paramPanelWindow.setWindowTitle(title)
|
|
225
226
|
paramPanelWindow.lbBehaviors.setText(text)
|
|
226
|
-
for w in
|
|
227
|
+
for w in (
|
|
227
228
|
paramPanelWindow.lwSubjects,
|
|
228
229
|
paramPanelWindow.pbSelectAllSubjects,
|
|
229
230
|
paramPanelWindow.pbUnselectAllSubjects,
|
|
@@ -233,7 +234,7 @@ def select_behaviors(
|
|
|
233
234
|
paramPanelWindow.cbExcludeBehaviors,
|
|
234
235
|
paramPanelWindow.frm_time,
|
|
235
236
|
paramPanelWindow.frm_time_bin_size,
|
|
236
|
-
|
|
237
|
+
):
|
|
237
238
|
w.setVisible(False)
|
|
238
239
|
|
|
239
240
|
if behavioral_categories:
|
|
@@ -245,9 +246,7 @@ def select_behaviors(
|
|
|
245
246
|
categories = ["###no category###"]
|
|
246
247
|
|
|
247
248
|
for category in categories:
|
|
248
|
-
|
|
249
249
|
if category != "###no category###":
|
|
250
|
-
|
|
251
250
|
if category == "":
|
|
252
251
|
paramPanelWindow.item = QListWidgetItem("No category")
|
|
253
252
|
paramPanelWindow.item.setData(34, "No category")
|
|
@@ -265,7 +264,6 @@ def select_behaviors(
|
|
|
265
264
|
|
|
266
265
|
# check if behavior type must be shown
|
|
267
266
|
for behavior in [ethogram[x][cfg.BEHAVIOR_CODE] for x in util.sorted_keys(ethogram)]:
|
|
268
|
-
|
|
269
267
|
if (categories == ["###no category###"]) or (
|
|
270
268
|
behavior
|
|
271
269
|
in [
|
|
@@ -274,7 +272,6 @@ def select_behaviors(
|
|
|
274
272
|
if cfg.BEHAVIOR_CATEGORY in ethogram[x] and ethogram[x][cfg.BEHAVIOR_CATEGORY] == category
|
|
275
273
|
]
|
|
276
274
|
):
|
|
277
|
-
|
|
278
275
|
paramPanelWindow.item = QListWidgetItem(behavior)
|
|
279
276
|
paramPanelWindow.item.setCheckState(Qt.Unchecked)
|
|
280
277
|
|
|
@@ -313,6 +310,7 @@ def import_ethogram_from_dict(self, project: dict):
|
|
|
313
310
|
"""
|
|
314
311
|
# import behavioral_categories
|
|
315
312
|
self.pj[cfg.BEHAVIORAL_CATEGORIES] = list(project.get(cfg.BEHAVIORAL_CATEGORIES, []))
|
|
313
|
+
self.pj[cfg.BEHAVIORAL_CATEGORIES_CONF] = list(project.get(cfg.BEHAVIORAL_CATEGORIES_CONF, {}))
|
|
316
314
|
|
|
317
315
|
# configuration of behaviours
|
|
318
316
|
if not (cfg.ETHOGRAM in project and project[cfg.ETHOGRAM]):
|
|
@@ -322,7 +320,7 @@ def import_ethogram_from_dict(self, project: dict):
|
|
|
322
320
|
if self.twBehaviors.rowCount():
|
|
323
321
|
response = dialog.MessageDialog(
|
|
324
322
|
cfg.programName,
|
|
325
|
-
("Some behaviors are already configured.
|
|
323
|
+
("Some behaviors are already configured. Do you want to append behaviors or replace them?"),
|
|
326
324
|
[cfg.APPEND, cfg.REPLACE, cfg.CANCEL],
|
|
327
325
|
)
|
|
328
326
|
if response == cfg.REPLACE:
|
|
@@ -340,39 +338,42 @@ def import_ethogram_from_dict(self, project: dict):
|
|
|
340
338
|
)
|
|
341
339
|
|
|
342
340
|
for i in util.sorted_keys(project[cfg.ETHOGRAM]):
|
|
343
|
-
|
|
344
341
|
if project[cfg.ETHOGRAM][i][cfg.BEHAVIOR_CODE] not in behaviors_to_import:
|
|
345
342
|
continue
|
|
346
343
|
|
|
347
344
|
self.twBehaviors.setRowCount(self.twBehaviors.rowCount() + 1)
|
|
348
345
|
|
|
349
346
|
for field in project[cfg.ETHOGRAM][i]:
|
|
350
|
-
|
|
351
347
|
item = QTableWidgetItem()
|
|
352
348
|
|
|
353
349
|
if field == cfg.TYPE:
|
|
354
350
|
item.setText(project[cfg.ETHOGRAM][i][field])
|
|
355
351
|
item.setFlags(Qt.ItemIsEnabled)
|
|
356
|
-
item.setBackground(QColor(230, 230, 230))
|
|
352
|
+
# item.setBackground(QColor(230, 230, 230))
|
|
353
|
+
item.setBackground(self.not_editable_column_color())
|
|
357
354
|
|
|
358
355
|
else:
|
|
359
|
-
if field == cfg.MODIFIERS
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
356
|
+
if field == cfg.MODIFIERS:
|
|
357
|
+
if isinstance(project[cfg.ETHOGRAM][i][field], str):
|
|
358
|
+
modif_set_dict = {}
|
|
359
|
+
if project[cfg.ETHOGRAM][i][field]:
|
|
360
|
+
modif_set_list = project[cfg.ETHOGRAM][i][field].split("|")
|
|
361
|
+
for modif_set in modif_set_list:
|
|
362
|
+
modif_set_dict[str(len(modif_set_dict))] = {
|
|
363
|
+
"name": "",
|
|
364
|
+
"type": cfg.SINGLE_SELECTION,
|
|
365
|
+
"values": modif_set.split(","),
|
|
366
|
+
}
|
|
367
|
+
project[cfg.ETHOGRAM][i][field] = dict(modif_set_dict)
|
|
368
|
+
else:
|
|
369
|
+
item.setText(json.dumps(project[cfg.ETHOGRAM][i][field]))
|
|
370
|
+
else:
|
|
371
|
+
item.setText(project[cfg.ETHOGRAM][i][field])
|
|
372
372
|
|
|
373
373
|
if field not in cfg.ETHOGRAM_EDITABLE_FIELDS:
|
|
374
374
|
item.setFlags(Qt.ItemIsEnabled)
|
|
375
|
-
item.setBackground(QColor(230, 230, 230))
|
|
375
|
+
# item.setBackground(QColor(230, 230, 230))
|
|
376
|
+
item.setBackground(self.not_editable_column_color())
|
|
376
377
|
|
|
377
378
|
self.twBehaviors.setItem(self.twBehaviors.rowCount() - 1, cfg.behavioursFields[field], item)
|
|
378
379
|
|
|
@@ -382,9 +383,12 @@ def import_ethogram_from_dict(self, project: dict):
|
|
|
382
383
|
def load_dataframe_into_behaviors_tablewidget(self, df: pd.DataFrame) -> int:
|
|
383
384
|
"""
|
|
384
385
|
Load pandas dataframe into the twBehaviors table widget
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
int: 0 if no error else error code
|
|
385
389
|
"""
|
|
386
390
|
|
|
387
|
-
expected_labels = [
|
|
391
|
+
expected_labels: list = [
|
|
388
392
|
"Behavior code",
|
|
389
393
|
"Behavior type",
|
|
390
394
|
"Description",
|
|
@@ -393,19 +397,32 @@ def load_dataframe_into_behaviors_tablewidget(self, df: pd.DataFrame) -> int:
|
|
|
393
397
|
"Excluded behaviors",
|
|
394
398
|
]
|
|
395
399
|
|
|
400
|
+
ethogram_header: dict = {
|
|
401
|
+
"code": "Behavior code",
|
|
402
|
+
"description": "Description",
|
|
403
|
+
"key": "Key",
|
|
404
|
+
"color": "Color",
|
|
405
|
+
"category": "Behavioral category",
|
|
406
|
+
"excluded": "Excluded behaviors",
|
|
407
|
+
"modifiers": "modifiers (JSON)",
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
# change all column names to uppercase
|
|
411
|
+
df.columns = df.columns.str.upper()
|
|
412
|
+
|
|
396
413
|
for column in expected_labels:
|
|
397
|
-
if column not in list(df.columns):
|
|
414
|
+
if column.upper() not in list(df.columns):
|
|
398
415
|
QMessageBox.warning(
|
|
399
416
|
None,
|
|
400
417
|
cfg.programName,
|
|
401
418
|
(
|
|
402
|
-
f"The {column
|
|
419
|
+
f"The {column} column was not found in the file header.<br>"
|
|
403
420
|
"For information the current file header contains the following labels:<br>"
|
|
404
421
|
f"{'<br>'.join(['<b>' + util.replace_leading_trailing_chars(x, ' ', '█') + '</b>' for x in df.columns])}<br>"
|
|
405
422
|
"<br>"
|
|
406
423
|
"The first row of the spreadsheet must contain the following labels:<br>"
|
|
407
424
|
f"{'<br>'.join(['<b>' + x + '</b>' for x in expected_labels])}<br>"
|
|
408
|
-
"<br>The order is not mandatory
|
|
425
|
+
"<br>The order is not mandatory."
|
|
409
426
|
),
|
|
410
427
|
QMessageBox.Ok | QMessageBox.Default,
|
|
411
428
|
QMessageBox.NoButton,
|
|
@@ -413,16 +430,12 @@ def load_dataframe_into_behaviors_tablewidget(self, df: pd.DataFrame) -> int:
|
|
|
413
430
|
return 1
|
|
414
431
|
|
|
415
432
|
for _, row in df.iterrows():
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
"excluded": row["Excluded behaviors"] if str(row["Excluded behaviors"]) != "nan" else "",
|
|
423
|
-
"coding map": "",
|
|
424
|
-
"category": row["Behavioral category"] if str(row["Behavioral category"]) != "nan" else "",
|
|
425
|
-
}
|
|
433
|
+
behavior = {"coding map": ""}
|
|
434
|
+
for x in ethogram_header:
|
|
435
|
+
if ethogram_header[x].upper() in row:
|
|
436
|
+
behavior[x] = row[ethogram_header[x].upper()] if str(row[ethogram_header[x].upper()]) != "nan" else ""
|
|
437
|
+
else:
|
|
438
|
+
behavior[x] = ""
|
|
426
439
|
|
|
427
440
|
self.twBehaviors.setRowCount(self.twBehaviors.rowCount() + 1)
|
|
428
441
|
|
|
@@ -430,9 +443,9 @@ def load_dataframe_into_behaviors_tablewidget(self, df: pd.DataFrame) -> int:
|
|
|
430
443
|
if field_type == cfg.TYPE:
|
|
431
444
|
item = QTableWidgetItem(cfg.DEFAULT_BEHAVIOR_TYPE)
|
|
432
445
|
# add type combobox
|
|
433
|
-
if cfg.POINT in row["Behavior type"].upper():
|
|
446
|
+
if cfg.POINT in row["Behavior type".upper()].upper():
|
|
434
447
|
item = QTableWidgetItem(cfg.POINT_EVENT)
|
|
435
|
-
elif cfg.STATE in row["Behavior type"].upper():
|
|
448
|
+
elif cfg.STATE in row["Behavior type".upper()].upper():
|
|
436
449
|
item = QTableWidgetItem(cfg.STATE_EVENT)
|
|
437
450
|
else:
|
|
438
451
|
QMessageBox.critical(
|
|
@@ -449,7 +462,8 @@ def load_dataframe_into_behaviors_tablewidget(self, df: pd.DataFrame) -> int:
|
|
|
449
462
|
|
|
450
463
|
if field_type not in cfg.ETHOGRAM_EDITABLE_FIELDS:
|
|
451
464
|
item.setFlags(Qt.ItemIsEnabled)
|
|
452
|
-
item.setBackground(QColor(230, 230, 230))
|
|
465
|
+
# item.setBackground(QColor(230, 230, 230))
|
|
466
|
+
item.setBackground(self.not_editable_column_color())
|
|
453
467
|
|
|
454
468
|
self.twBehaviors.setItem(self.twBehaviors.rowCount() - 1, cfg.behavioursFields[field_type], item)
|
|
455
469
|
|
|
@@ -457,12 +471,12 @@ def load_dataframe_into_behaviors_tablewidget(self, df: pd.DataFrame) -> int:
|
|
|
457
471
|
|
|
458
472
|
|
|
459
473
|
def import_behaviors_from_project(self):
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
474
|
+
"""
|
|
475
|
+
import ethogram from a BORIS project file
|
|
476
|
+
"""
|
|
477
|
+
file_name, _ = QFileDialog.getOpenFileName(
|
|
478
|
+
self, "Import behaviors from BORIS project file", "", ("Project files (*.boris *.boris.gz);;All files (*)")
|
|
463
479
|
)
|
|
464
|
-
file_name = fn[0] if type(fn) is tuple else fn
|
|
465
|
-
|
|
466
480
|
if not file_name:
|
|
467
481
|
return
|
|
468
482
|
_, _, project, _ = project_functions.open_project_json(file_name)
|
|
@@ -472,7 +486,7 @@ def import_behaviors_from_project(self):
|
|
|
472
486
|
|
|
473
487
|
def import_behaviors_from_text_file(self):
|
|
474
488
|
"""
|
|
475
|
-
Import
|
|
489
|
+
Import ethogram from text file (CSV or TSV)
|
|
476
490
|
"""
|
|
477
491
|
|
|
478
492
|
if self.twBehaviors.rowCount():
|
|
@@ -484,10 +498,9 @@ def import_behaviors_from_text_file(self):
|
|
|
484
498
|
if response == cfg.CANCEL:
|
|
485
499
|
return
|
|
486
500
|
|
|
487
|
-
|
|
501
|
+
file_name, _ = QFileDialog.getOpenFileName(
|
|
488
502
|
self, "Import behaviors from text file (CSV, TSV)", "", "Text files (*.txt *.tsv *.csv);;All files (*)"
|
|
489
503
|
)
|
|
490
|
-
file_name = fn[0] if type(fn) is tuple else fn
|
|
491
504
|
|
|
492
505
|
if not file_name:
|
|
493
506
|
return
|
|
@@ -538,25 +551,22 @@ def import_behaviors_from_spreadsheet(self):
|
|
|
538
551
|
if response == cfg.CANCEL:
|
|
539
552
|
return
|
|
540
553
|
|
|
541
|
-
|
|
542
|
-
self, "Import behaviors from a spreadsheet file", "", "Spreadsheet files (*.xlsx);;All files (*)"
|
|
554
|
+
file_name, _ = QFileDialog.getOpenFileName(
|
|
555
|
+
self, "Import behaviors from a spreadsheet file", "", "Spreadsheet files (*.xlsx *.ods);;All files (*)"
|
|
543
556
|
)
|
|
544
|
-
file_name = fn[0] if type(fn) is tuple else fn
|
|
545
557
|
|
|
546
558
|
if not file_name:
|
|
547
559
|
return
|
|
548
560
|
|
|
549
561
|
if pl.Path(file_name).suffix.upper() == ".XLSX":
|
|
550
562
|
engine = "openpyxl"
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
engine = "odf"
|
|
554
|
-
"""
|
|
563
|
+
elif pl.Path(file_name).suffix.upper() == ".ODS":
|
|
564
|
+
engine = "odf"
|
|
555
565
|
else:
|
|
556
566
|
QMessageBox.warning(
|
|
557
567
|
None,
|
|
558
568
|
cfg.programName,
|
|
559
|
-
("The type of file was not recognized. Must be Microsoft-Excel XLSX format"),
|
|
569
|
+
("The type of file was not recognized. Must be Microsoft-Excel XLSX format or OpenDocument ODS"),
|
|
560
570
|
QMessageBox.Ok | QMessageBox.Default,
|
|
561
571
|
QMessageBox.NoButton,
|
|
562
572
|
)
|
|
@@ -568,7 +578,7 @@ def import_behaviors_from_spreadsheet(self):
|
|
|
568
578
|
QMessageBox.warning(
|
|
569
579
|
None,
|
|
570
580
|
cfg.programName,
|
|
571
|
-
("The type of file was not recognized. Must be Microsoft-Excel XLSX format"),
|
|
581
|
+
("The type of file was not recognized. Must be Microsoft-Excel XLSX format or OpenDocument ODS"),
|
|
572
582
|
QMessageBox.Ok | QMessageBox.Default,
|
|
573
583
|
QMessageBox.NoButton,
|
|
574
584
|
)
|
|
@@ -632,9 +642,7 @@ def import_behaviors_from_clipboard(self):
|
|
|
632
642
|
for idx, field in enumerate(row.split("\t")):
|
|
633
643
|
if idx == 0:
|
|
634
644
|
behavior["type"] = (
|
|
635
|
-
cfg.STATE_EVENT
|
|
636
|
-
if cfg.STATE in field.upper()
|
|
637
|
-
else (cfg.POINT_EVENT if cfg.POINT in field.upper() else "")
|
|
645
|
+
cfg.STATE_EVENT if cfg.STATE in field.upper() else (cfg.POINT_EVENT if cfg.POINT in field.upper() else "")
|
|
638
646
|
)
|
|
639
647
|
if idx == 1:
|
|
640
648
|
behavior["key"] = field.strip() if len(field.strip()) == 1 else ""
|
|
@@ -653,11 +661,10 @@ def import_behaviors_from_clipboard(self):
|
|
|
653
661
|
else:
|
|
654
662
|
item = QTableWidgetItem(behavior.get(field_type, ""))
|
|
655
663
|
|
|
656
|
-
if
|
|
657
|
-
field_type not in cfg.ETHOGRAM_EDITABLE_FIELDS
|
|
658
|
-
): # [TYPE, "excluded", "coding map", "modifiers", "category"]:
|
|
664
|
+
if field_type not in cfg.ETHOGRAM_EDITABLE_FIELDS: # [TYPE, "excluded", "coding map", "modifiers", "category"]:
|
|
659
665
|
item.setFlags(Qt.ItemIsEnabled)
|
|
660
|
-
item.setBackground(QColor(230, 230, 230))
|
|
666
|
+
# item.setBackground(QColor(230, 230, 230))
|
|
667
|
+
item.setBackground(self.not_editable_column_color())
|
|
661
668
|
|
|
662
669
|
self.twBehaviors.setItem(self.twBehaviors.rowCount() - 1, cfg.behavioursFields[field_type], item)
|
|
663
670
|
|
|
@@ -676,74 +683,68 @@ def import_behaviors_from_JWatcher(self):
|
|
|
676
683
|
if response == cfg.CANCEL:
|
|
677
684
|
return
|
|
678
685
|
|
|
679
|
-
|
|
680
|
-
self, "Import behaviors from JWatcher", "", "Global Definition File (*.gdf);;All files (*)"
|
|
681
|
-
)
|
|
682
|
-
fileName = fn[0] if type(fn) is tuple else fn
|
|
686
|
+
fileName, _ = QFileDialog().getOpenFileName(self, "Import behaviors from JWatcher", "", "Global Definition File (*.gdf);;All files (*)")
|
|
683
687
|
|
|
684
|
-
if fileName:
|
|
685
|
-
|
|
686
|
-
|
|
688
|
+
if not fileName:
|
|
689
|
+
return
|
|
690
|
+
if self.twBehaviors.rowCount() and response == cfg.REPLACE:
|
|
691
|
+
self.twBehaviors.setRowCount(0)
|
|
687
692
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
693
|
+
with open(fileName, "r") as f:
|
|
694
|
+
rows = f.readlines()
|
|
695
|
+
|
|
696
|
+
for idx, row in enumerate(rows):
|
|
697
|
+
if row and row[0] == "#":
|
|
698
|
+
continue
|
|
699
|
+
|
|
700
|
+
if "Behavior.name." in row and "=" in row:
|
|
701
|
+
key, code = row.split("=")
|
|
702
|
+
key = key.replace("Behavior.name.", "")
|
|
703
|
+
# read description
|
|
704
|
+
if idx < len(rows) and "Behavior.description." in rows[idx + 1]:
|
|
705
|
+
description = rows[idx + 1].split("=")[-1]
|
|
706
|
+
|
|
707
|
+
behavior = {
|
|
708
|
+
"key": key,
|
|
709
|
+
"code": code,
|
|
710
|
+
"description": description,
|
|
711
|
+
"modifiers": "",
|
|
712
|
+
"excluded": "",
|
|
713
|
+
"coding map": "",
|
|
714
|
+
"category": "",
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
self.twBehaviors.setRowCount(self.twBehaviors.rowCount() + 1)
|
|
718
|
+
|
|
719
|
+
for field_type in cfg.behavioursFields:
|
|
720
|
+
if field_type == cfg.TYPE:
|
|
721
|
+
item = QTableWidgetItem(cfg.DEFAULT_BEHAVIOR_TYPE)
|
|
722
|
+
else:
|
|
723
|
+
item = QTableWidgetItem(behavior[field_type])
|
|
719
724
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
725
|
+
if field_type in [cfg.TYPE, "excluded", "category", "coding map", "modifiers"]:
|
|
726
|
+
item.setFlags(Qt.ItemIsEnabled)
|
|
727
|
+
# item.setBackground(QColor(230, 230, 230))
|
|
728
|
+
item.setBackground(self.not_editable_column_color())
|
|
723
729
|
|
|
724
|
-
|
|
730
|
+
self.twBehaviors.setItem(self.twBehaviors.rowCount() - 1, cfg.behavioursFields[field_type], item)
|
|
725
731
|
|
|
726
732
|
|
|
727
733
|
def import_behaviors_from_repository(self):
|
|
728
734
|
"""
|
|
729
735
|
import behaviors from the BORIS ethogram repository
|
|
730
736
|
"""
|
|
731
|
-
|
|
737
|
+
|
|
732
738
|
try:
|
|
733
|
-
ethogram_list = urllib.request.urlopen(
|
|
739
|
+
ethogram_list = urllib.request.urlopen(f"{cfg.ETHOGRAM_REPOSITORY_URL}/ethogram_list.json").read().strip().decode("utf-8")
|
|
734
740
|
except Exception:
|
|
735
|
-
|
|
736
|
-
QMessageBox.critical(
|
|
737
|
-
self, cfg.programName, "An error occured during retrieving the ethogram list from BORIS repository"
|
|
738
|
-
)
|
|
741
|
+
QMessageBox.critical(self, cfg.programName, "An error occured during retrieving the ethogram list from BORIS repository")
|
|
739
742
|
return
|
|
740
743
|
|
|
741
744
|
try:
|
|
742
745
|
ethogram_list_list = json.loads(ethogram_list)
|
|
743
746
|
except Exception:
|
|
744
|
-
QMessageBox.critical(
|
|
745
|
-
self, cfg.programName, "An error occured during loading ethogram list from BORIS repository"
|
|
746
|
-
)
|
|
747
|
+
QMessageBox.critical(self, cfg.programName, "An error occured during loading ethogram list from BORIS repository")
|
|
747
748
|
return
|
|
748
749
|
|
|
749
750
|
choice_dialog = dialog.ChooseObservationsToImport(
|
|
@@ -769,17 +770,9 @@ def import_behaviors_from_repository(self):
|
|
|
769
770
|
break
|
|
770
771
|
|
|
771
772
|
try:
|
|
772
|
-
boris_project_str = (
|
|
773
|
-
urllib.request.urlopen(f"http://www.boris.unito.it/static/ethograms/{file_name}")
|
|
774
|
-
.read()
|
|
775
|
-
.strip()
|
|
776
|
-
.decode("utf-8")
|
|
777
|
-
)
|
|
773
|
+
boris_project_str = urllib.request.urlopen(f"{cfg.ETHOGRAM_REPOSITORY_URL}/{file_name}").read().strip().decode("utf-8")
|
|
778
774
|
except Exception:
|
|
779
|
-
|
|
780
|
-
QMessageBox.critical(
|
|
781
|
-
self, cfg.programName, f"An error occured during retrieving {file_name} from BORIS repository"
|
|
782
|
-
)
|
|
775
|
+
QMessageBox.critical(self, cfg.programName, f"An error occured during retrieving {file_name} from BORIS repository")
|
|
783
776
|
return
|
|
784
777
|
boris_project = json.loads(boris_project_str)
|
|
785
778
|
|
|
@@ -789,16 +782,27 @@ def import_behaviors_from_repository(self):
|
|
|
789
782
|
def load_dataframe_into_subjects_tablewidget(self, df: pd.DataFrame) -> int:
|
|
790
783
|
"""
|
|
791
784
|
Load pandas dataframe into the twSubjects table widget
|
|
785
|
+
|
|
786
|
+
Returns:
|
|
787
|
+
int: 0 if no error else error code
|
|
788
|
+
|
|
792
789
|
"""
|
|
793
|
-
|
|
794
|
-
|
|
790
|
+
|
|
791
|
+
expected_labels: list = ["Key", "Subject name", "Description"]
|
|
792
|
+
|
|
793
|
+
# change all column names to uppercase
|
|
794
|
+
df.columns = df.columns.str.upper()
|
|
795
|
+
|
|
796
|
+
for column in expected_labels:
|
|
797
|
+
if column.upper() not in list(df.columns):
|
|
795
798
|
QMessageBox.warning(
|
|
796
799
|
None,
|
|
797
800
|
cfg.programName,
|
|
798
801
|
(
|
|
802
|
+
f"The column {column} was not found in the file header.<br>"
|
|
799
803
|
"The first row of spreadsheet must contain the following labels:<br>"
|
|
800
804
|
"Subject name, Description, Key<br>"
|
|
801
|
-
"
|
|
805
|
+
"The order is not mandatory."
|
|
802
806
|
),
|
|
803
807
|
QMessageBox.Ok | QMessageBox.Default,
|
|
804
808
|
QMessageBox.NoButton,
|
|
@@ -806,14 +810,13 @@ def load_dataframe_into_subjects_tablewidget(self, df: pd.DataFrame) -> int:
|
|
|
806
810
|
return 1
|
|
807
811
|
|
|
808
812
|
for _, row in df.iterrows():
|
|
809
|
-
|
|
810
813
|
self.twSubjects.setRowCount(self.twSubjects.rowCount() + 1)
|
|
811
814
|
|
|
812
|
-
for idx, field in enumerate(
|
|
815
|
+
for idx, field in enumerate(expected_labels):
|
|
813
816
|
self.twSubjects.setItem(
|
|
814
817
|
self.twSubjects.rowCount() - 1,
|
|
815
818
|
idx,
|
|
816
|
-
QTableWidgetItem(str(row[field]) if str(row[field]) != "nan" else ""),
|
|
819
|
+
QTableWidgetItem(str(row[field.upper()]) if str(row[field.upper()]) != "nan" else ""),
|
|
817
820
|
)
|
|
818
821
|
|
|
819
822
|
return 0
|
|
@@ -885,11 +888,9 @@ def import_subjects_from_project(self):
|
|
|
885
888
|
import subjects from a BORIS project
|
|
886
889
|
"""
|
|
887
890
|
|
|
888
|
-
|
|
889
|
-
self, "Import subjects from project file", "", ("Project files (*.boris *.boris.gz);;
|
|
891
|
+
file_name, _ = QFileDialog().getOpenFileName(
|
|
892
|
+
self, "Import subjects from project file", "", ("Project files (*.boris *.boris.gz);;All files (*)")
|
|
890
893
|
)
|
|
891
|
-
file_name = fn[0] if type(fn) is tuple else fn
|
|
892
|
-
|
|
893
894
|
if not file_name:
|
|
894
895
|
return
|
|
895
896
|
|
|
@@ -908,7 +909,7 @@ def import_subjects_from_project(self):
|
|
|
908
909
|
if self.twSubjects.rowCount():
|
|
909
910
|
response = dialog.MessageDialog(
|
|
910
911
|
cfg.programName,
|
|
911
|
-
("There are subjects already configured.
|
|
912
|
+
("There are subjects already configured. Do you want to append subjects or replace them?"),
|
|
912
913
|
[cfg.APPEND, cfg.REPLACE, cfg.CANCEL],
|
|
913
914
|
)
|
|
914
915
|
|
|
@@ -919,11 +920,9 @@ def import_subjects_from_project(self):
|
|
|
919
920
|
return
|
|
920
921
|
|
|
921
922
|
for idx in util.sorted_keys(project[cfg.SUBJECTS]):
|
|
922
|
-
|
|
923
923
|
self.twSubjects.setRowCount(self.twSubjects.rowCount() + 1)
|
|
924
924
|
|
|
925
925
|
for idx2, sbjField in enumerate(cfg.subjectsFields):
|
|
926
|
-
|
|
927
926
|
if sbjField in project[cfg.SUBJECTS][idx]:
|
|
928
927
|
self.twSubjects.setItem(
|
|
929
928
|
self.twSubjects.rowCount() - 1,
|
|
@@ -944,18 +943,16 @@ def import_subjects_from_text_file(self):
|
|
|
944
943
|
if self.twSubjects.rowCount():
|
|
945
944
|
response = dialog.MessageDialog(
|
|
946
945
|
cfg.programName,
|
|
947
|
-
("There are subjects already configured.
|
|
946
|
+
("There are subjects already configured. Do you want to append subjects or replace them?"),
|
|
948
947
|
[cfg.APPEND, cfg.REPLACE, cfg.CANCEL],
|
|
949
948
|
)
|
|
950
949
|
|
|
951
950
|
if response == cfg.CANCEL:
|
|
952
951
|
return
|
|
953
952
|
|
|
954
|
-
|
|
953
|
+
file_name, _ = QFileDialog().getOpenFileName(
|
|
955
954
|
self, "Import behaviors from text file (CSV, TSV)", "", "Text files (*.txt *.tsv *.csv);;All files (*)"
|
|
956
955
|
)
|
|
957
|
-
file_name = fn[0] if type(fn) is tuple else fn
|
|
958
|
-
|
|
959
956
|
if not file_name:
|
|
960
957
|
return
|
|
961
958
|
|
|
@@ -999,18 +996,16 @@ def import_subjects_from_spreadsheet(self):
|
|
|
999
996
|
if self.twSubjects.rowCount():
|
|
1000
997
|
response = dialog.MessageDialog(
|
|
1001
998
|
cfg.programName,
|
|
1002
|
-
("There are subjects already configured.
|
|
999
|
+
("There are subjects already configured. Do you want to append subjects or replace them?"),
|
|
1003
1000
|
[cfg.APPEND, cfg.REPLACE, cfg.CANCEL],
|
|
1004
1001
|
)
|
|
1005
1002
|
|
|
1006
1003
|
if response == cfg.CANCEL:
|
|
1007
1004
|
return
|
|
1008
1005
|
|
|
1009
|
-
|
|
1010
|
-
self, "Import subjects from a spreadsheet file", "", "Spreadsheet files (*.xlsx);;All files (*)"
|
|
1006
|
+
file_name, _ = QFileDialog().getOpenFileName(
|
|
1007
|
+
self, "Import subjects from a spreadsheet file", "", "Spreadsheet files (*.xlsx *.ods);;All files (*)"
|
|
1011
1008
|
)
|
|
1012
|
-
file_name = fn[0] if type(fn) is tuple else fn
|
|
1013
|
-
|
|
1014
1009
|
if not file_name:
|
|
1015
1010
|
return
|
|
1016
1011
|
|
|
@@ -1019,13 +1014,13 @@ def import_subjects_from_spreadsheet(self):
|
|
|
1019
1014
|
|
|
1020
1015
|
if pl.Path(file_name).suffix.upper() == ".XLSX":
|
|
1021
1016
|
engine = "openpyxl"
|
|
1022
|
-
|
|
1023
|
-
|
|
1017
|
+
elif pl.Path(file_name).suffix.upper() == ".ODS":
|
|
1018
|
+
engine = "odf"
|
|
1024
1019
|
else:
|
|
1025
1020
|
QMessageBox.warning(
|
|
1026
1021
|
None,
|
|
1027
1022
|
cfg.programName,
|
|
1028
|
-
("The type of file was not recognized. Must be Microsoft-Excel XLSX format"),
|
|
1023
|
+
("The type of file was not recognized. Must be Microsoft-Excel XLSX format or OpenDocument ODS"),
|
|
1029
1024
|
QMessageBox.Ok | QMessageBox.Default,
|
|
1030
1025
|
QMessageBox.NoButton,
|
|
1031
1026
|
)
|
|
@@ -1037,7 +1032,7 @@ def import_subjects_from_spreadsheet(self):
|
|
|
1037
1032
|
QMessageBox.warning(
|
|
1038
1033
|
None,
|
|
1039
1034
|
cfg.programName,
|
|
1040
|
-
("The type of file was not recognized. Must be Microsoft-Excel XLSX format"),
|
|
1035
|
+
("The type of file was not recognized. Must be Microsoft-Excel XLSX format or OpenDocument ODS"),
|
|
1041
1036
|
QMessageBox.Ok | QMessageBox.Default,
|
|
1042
1037
|
QMessageBox.NoButton,
|
|
1043
1038
|
)
|
|
@@ -1051,15 +1046,12 @@ def import_indep_variables_from_project(self):
|
|
|
1051
1046
|
import independent variables from another project
|
|
1052
1047
|
"""
|
|
1053
1048
|
|
|
1054
|
-
|
|
1049
|
+
file_name, _ = QFileDialog().getOpenFileName(
|
|
1055
1050
|
self,
|
|
1056
1051
|
"Import independent variables from project file",
|
|
1057
1052
|
"",
|
|
1058
|
-
("Project files (*.boris *.boris.gz);;
|
|
1053
|
+
("Project files (*.boris *.boris.gz);;All files (*)"),
|
|
1059
1054
|
)
|
|
1060
|
-
|
|
1061
|
-
file_name = fn[0] if type(fn) is tuple else fn
|
|
1062
|
-
|
|
1063
1055
|
if not file_name:
|
|
1064
1056
|
return
|
|
1065
1057
|
|
|
@@ -1082,7 +1074,6 @@ def import_indep_variables_from_project(self):
|
|
|
1082
1074
|
existing_var.append(self.twVariables.item(r, 0).text().strip().upper())
|
|
1083
1075
|
|
|
1084
1076
|
for i in util.sorted_keys(project[cfg.INDEPENDENT_VARIABLES]):
|
|
1085
|
-
|
|
1086
1077
|
self.twVariables.setRowCount(self.twVariables.rowCount() + 1)
|
|
1087
1078
|
flag_renamed = False
|
|
1088
1079
|
for idx, field in enumerate(cfg.tw_indVarFields):
|