boris-behav-obs 9.0.2__py2.py3-none-any.whl → 9.0.4__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- boris/__init__.py +1 -1
- boris/__main__.py +1 -1
- boris/about.py +2 -2
- boris/add_modifier.py +1 -1
- boris/add_modifier_ui.py +22 -22
- boris/advanced_event_filtering.py +1 -1
- boris/analysis_plugins/number_of_occurences.py +6 -3
- boris/analysis_plugins/number_of_occurences_by_independent_variable.py +6 -4
- boris/analysis_plugins/time_budget.py +24 -14
- boris/behav_coding_map_creator.py +1 -1
- boris/behavior_binary_table.py +1 -1
- boris/behaviors_coding_map.py +1 -1
- boris/boris_cli.py +1 -1
- boris/cmd_arguments.py +1 -1
- boris/coding_pad.py +1 -1
- boris/config.py +16 -9
- boris/config_file.py +1 -1
- boris/connections.py +1 -1
- boris/converters.py +1 -1
- boris/cooccurence.py +1 -1
- boris/core.py +143 -176
- boris/db_functions.py +1 -1
- boris/dialog.py +10 -277
- boris/edit_event.py +8 -1
- boris/event_operations.py +1 -1
- boris/events_cursor.py +1 -1
- boris/events_snapshots.py +1 -1
- boris/exclusion_matrix.py +1 -1
- boris/export_events.py +1 -1
- boris/export_observation.py +1 -1
- boris/external_processes.py +1 -1
- boris/geometric_measurement.py +1 -1
- boris/gui_utilities.py +1 -1
- boris/image_overlay.py +1 -1
- boris/import_observations.py +1 -1
- boris/irr.py +1 -1
- boris/latency.py +1 -1
- boris/map_creator.py +1 -1
- boris/measurement_widget.py +1 -1
- boris/media_file.py +1 -1
- boris/menu_options.py +1 -1
- boris/modifiers_coding_map.py +1 -1
- boris/observation.py +1 -1
- boris/observation_operations.py +530 -425
- boris/observations_list.py +3 -3
- boris/otx_parser.py +1 -1
- boris/param_panel.py +1 -1
- boris/player_dock_widget.py +13 -9
- boris/plot_data_module.py +1 -1
- boris/plot_events.py +1 -1
- boris/plot_events_rt.py +1 -1
- boris/plot_spectrogram_rt.py +1 -1
- boris/plot_waveform_rt.py +1 -1
- boris/plugins.py +2 -2
- boris/preferences.py +6 -1
- boris/preferences_ui.py +7 -1
- boris/project.py +1 -1
- boris/project_functions.py +73 -76
- boris/project_import_export.py +1 -1
- boris/select_modifiers.py +1 -1
- boris/select_observations.py +6 -6
- boris/select_subj_behav.py +1 -1
- boris/state_events.py +1 -1
- boris/subjects_pad.py +1 -1
- boris/synthetic_time_budget.py +1 -1
- boris/time_budget_functions.py +1 -1
- boris/time_budget_widget.py +5 -5
- boris/transitions.py +1 -1
- boris/utilities.py +1 -1
- boris/version.py +3 -3
- boris/video_equalizer.py +1 -1
- boris/video_operations.py +1 -1
- boris/write_event.py +1 -1
- {boris_behav_obs-9.0.2.dist-info → boris_behav_obs-9.0.4.dist-info}/METADATA +1 -1
- boris_behav_obs-9.0.4.dist-info/RECORD +103 -0
- boris_behav_obs-9.0.2.dist-info/RECORD +0 -103
- {boris_behav_obs-9.0.2.dist-info → boris_behav_obs-9.0.4.dist-info}/LICENSE.TXT +0 -0
- {boris_behav_obs-9.0.2.dist-info → boris_behav_obs-9.0.4.dist-info}/WHEEL +0 -0
- {boris_behav_obs-9.0.2.dist-info → boris_behav_obs-9.0.4.dist-info}/entry_points.txt +0 -0
- {boris_behav_obs-9.0.2.dist-info → boris_behav_obs-9.0.4.dist-info}/top_level.txt +0 -0
boris/project_functions.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 program is free software; you can redistribute it and/or modify
|
|
7
7
|
it under the terms of the GNU General Public License as published by
|
|
@@ -22,10 +22,9 @@ Copyright 2012-2024 Olivier Friard
|
|
|
22
22
|
import gzip
|
|
23
23
|
import json
|
|
24
24
|
import logging
|
|
25
|
-
import os
|
|
26
25
|
import pandas as pd
|
|
27
26
|
import numpy as np
|
|
28
|
-
|
|
27
|
+
from pathlib import Path
|
|
29
28
|
import sys
|
|
30
29
|
from decimal import Decimal as dec
|
|
31
30
|
from shutil import copyfile
|
|
@@ -666,7 +665,7 @@ def create_subtitles(pj: dict, selected_observations: list, parameters: dict, ex
|
|
|
666
665
|
modifiers=modifiers_str,
|
|
667
666
|
)
|
|
668
667
|
|
|
669
|
-
file_name =
|
|
668
|
+
file_name = Path(export_dir) / Path(util.safeFileName(obs_id)).with_suffix(".srt")
|
|
670
669
|
|
|
671
670
|
if mem_command not in (cfg.OVERWRITE_ALL, cfg.SKIP_ALL) and file_name.is_file():
|
|
672
671
|
mem_command = dialog.MessageDialog(
|
|
@@ -773,7 +772,7 @@ def create_subtitles(pj: dict, selected_observations: list, parameters: dict, ex
|
|
|
773
772
|
behavior=row["behavior"],
|
|
774
773
|
modifiers=modifiers_str,
|
|
775
774
|
)
|
|
776
|
-
file_name =
|
|
775
|
+
file_name = Path(export_dir) / Path(Path(media_file).stem).with_suffix(".srt")
|
|
777
776
|
|
|
778
777
|
if mem_command not in (cfg.OVERWRITE_ALL, cfg.SKIP_ALL) and file_name.is_file():
|
|
779
778
|
mem_command = dialog.MessageDialog(
|
|
@@ -901,9 +900,9 @@ def set_media_paths_relative_to_project_dir(pj: dict, project_file_name: str) ->
|
|
|
901
900
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.IMAGES:
|
|
902
901
|
for img_dir in pj[cfg.OBSERVATIONS][obs_id][cfg.DIRECTORIES_LIST]:
|
|
903
902
|
try:
|
|
904
|
-
|
|
903
|
+
Path(img_dir).relative_to(Path(project_file_name).parent)
|
|
905
904
|
except ValueError:
|
|
906
|
-
if
|
|
905
|
+
if Path(img_dir).is_absolute() or not (Path(project_file_name).parent / Path(img_dir)).is_dir():
|
|
907
906
|
QMessageBox.critical(
|
|
908
907
|
None,
|
|
909
908
|
cfg.programName,
|
|
@@ -916,9 +915,9 @@ def set_media_paths_relative_to_project_dir(pj: dict, project_file_name: str) ->
|
|
|
916
915
|
if n_player in pj[cfg.OBSERVATIONS][obs_id][cfg.FILE]:
|
|
917
916
|
for idx, media_file in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][n_player]):
|
|
918
917
|
try:
|
|
919
|
-
|
|
918
|
+
Path(media_file).relative_to(Path(project_file_name).parent)
|
|
920
919
|
except ValueError:
|
|
921
|
-
if
|
|
920
|
+
if Path(media_file).is_absolute() or not (Path(project_file_name).parent / Path(media_file)).is_file():
|
|
922
921
|
QMessageBox.critical(
|
|
923
922
|
None,
|
|
924
923
|
cfg.programName,
|
|
@@ -936,9 +935,9 @@ def set_media_paths_relative_to_project_dir(pj: dict, project_file_name: str) ->
|
|
|
936
935
|
new_dir_list = []
|
|
937
936
|
for img_dir in pj[cfg.OBSERVATIONS][obs_id][cfg.DIRECTORIES_LIST]:
|
|
938
937
|
try:
|
|
939
|
-
new_dir_list.append(str(
|
|
938
|
+
new_dir_list.append(str(Path(img_dir).relative_to(Path(project_file_name).parent)))
|
|
940
939
|
except ValueError:
|
|
941
|
-
if not
|
|
940
|
+
if not Path(img_dir).is_absolute() and (Path(project_file_name).parent / Path(img_dir)).is_dir():
|
|
942
941
|
new_dir_list.append(img_dir)
|
|
943
942
|
|
|
944
943
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.DIRECTORIES_LIST] != new_dir_list:
|
|
@@ -950,12 +949,9 @@ def set_media_paths_relative_to_project_dir(pj: dict, project_file_name: str) ->
|
|
|
950
949
|
if n_player in pj[cfg.OBSERVATIONS][obs_id][cfg.FILE]:
|
|
951
950
|
for idx, media_file in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][n_player]):
|
|
952
951
|
try:
|
|
953
|
-
p = str(
|
|
952
|
+
p = str(Path(media_file).relative_to(Path(project_file_name).parent))
|
|
954
953
|
except ValueError:
|
|
955
|
-
if (
|
|
956
|
-
not pl.Path(media_file).is_absolute()
|
|
957
|
-
and (pl.Path(project_file_name).parent / pl.Path(media_file)).is_file()
|
|
958
|
-
):
|
|
954
|
+
if not Path(media_file).is_absolute() and (Path(project_file_name).parent / Path(media_file)).is_file():
|
|
959
955
|
p = media_file
|
|
960
956
|
if p != media_file:
|
|
961
957
|
flag_changed = True
|
|
@@ -996,13 +992,10 @@ def set_data_paths_relative_to_project_dir(pj: dict, project_file_name: str) ->
|
|
|
996
992
|
for _, v in pj[cfg.OBSERVATIONS][obs_id].get(cfg.PLOT_DATA, {}).items():
|
|
997
993
|
if cfg.FILE_PATH in v:
|
|
998
994
|
try:
|
|
999
|
-
|
|
995
|
+
Path(v[cfg.FILE_PATH]).relative_to(Path(project_file_name).parent)
|
|
1000
996
|
except ValueError:
|
|
1001
997
|
# check if file is in project dir
|
|
1002
|
-
if (
|
|
1003
|
-
pl.Path(v[cfg.FILE_PATH]).is_absolute()
|
|
1004
|
-
or not (pl.Path(project_file_name).parent / pl.Path(v[cfg.FILE_PATH])).is_file()
|
|
1005
|
-
):
|
|
998
|
+
if Path(v[cfg.FILE_PATH]).is_absolute() or not (Path(project_file_name).parent / Path(v[cfg.FILE_PATH])).is_file():
|
|
1006
999
|
QMessageBox.critical(
|
|
1007
1000
|
None,
|
|
1008
1001
|
cfg.programName,
|
|
@@ -1021,13 +1014,10 @@ def set_data_paths_relative_to_project_dir(pj: dict, project_file_name: str) ->
|
|
|
1021
1014
|
for idx, v in pj[cfg.OBSERVATIONS][obs_id].get(cfg.PLOT_DATA, {}).items():
|
|
1022
1015
|
if cfg.FILE_PATH in v:
|
|
1023
1016
|
try:
|
|
1024
|
-
p = str(
|
|
1017
|
+
p = str(Path(v[cfg.FILE_PATH]).relative_to(Path(project_file_name).parent))
|
|
1025
1018
|
except ValueError:
|
|
1026
1019
|
# check if file is in project dir
|
|
1027
|
-
if (
|
|
1028
|
-
not pl.Path(v[cfg.FILE_PATH]).is_absolute()
|
|
1029
|
-
and (pl.Path(project_file_name).parent / pl.Path(v[cfg.FILE_PATH])).is_file()
|
|
1030
|
-
):
|
|
1020
|
+
if not Path(v[cfg.FILE_PATH]).is_absolute() and (Path(project_file_name).parent / Path(v[cfg.FILE_PATH])).is_file():
|
|
1031
1021
|
p = v[cfg.FILE_PATH]
|
|
1032
1022
|
|
|
1033
1023
|
if p != v[cfg.FILE_PATH]:
|
|
@@ -1054,7 +1044,7 @@ def remove_data_files_path(pj: dict) -> None:
|
|
|
1054
1044
|
if cfg.PLOT_DATA in pj[cfg.OBSERVATIONS][obs_id]:
|
|
1055
1045
|
for idx in pj[cfg.OBSERVATIONS][obs_id][cfg.PLOT_DATA]:
|
|
1056
1046
|
if "file_path" in pj[cfg.OBSERVATIONS][obs_id][cfg.PLOT_DATA][idx]:
|
|
1057
|
-
p = str(
|
|
1047
|
+
p = str(Path(pj[cfg.OBSERVATIONS][obs_id][cfg.PLOT_DATA][idx]["file_path"]).name)
|
|
1058
1048
|
if p != pj[cfg.OBSERVATIONS][obs_id][cfg.PLOT_DATA][idx]["file_path"]:
|
|
1059
1049
|
pj[cfg.OBSERVATIONS][obs_id][cfg.PLOT_DATA][idx]["file_path"] = p
|
|
1060
1050
|
|
|
@@ -1076,14 +1066,14 @@ def remove_media_files_path(pj: dict, project_file_name: str) -> bool:
|
|
|
1076
1066
|
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
1077
1067
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.IMAGES:
|
|
1078
1068
|
for img_dir in pj[cfg.OBSERVATIONS][obs_id][cfg.DIRECTORIES_LIST]:
|
|
1079
|
-
if full_path(
|
|
1069
|
+
if full_path(Path(img_dir).name, project_file_name) == "":
|
|
1080
1070
|
file_not_found.append(img_dir)
|
|
1081
1071
|
|
|
1082
1072
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.MEDIA:
|
|
1083
1073
|
for n_player in cfg.ALL_PLAYERS:
|
|
1084
1074
|
if n_player in pj[cfg.OBSERVATIONS][obs_id][cfg.FILE]:
|
|
1085
1075
|
for idx, media_file in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][n_player]):
|
|
1086
|
-
if full_path(
|
|
1076
|
+
if full_path(Path(media_file).name, project_file_name) == "":
|
|
1087
1077
|
file_not_found.append(media_file)
|
|
1088
1078
|
|
|
1089
1079
|
file_not_found = set(file_not_found)
|
|
@@ -1107,16 +1097,16 @@ def remove_media_files_path(pj: dict, project_file_name: str) -> bool:
|
|
|
1107
1097
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.IMAGES:
|
|
1108
1098
|
new_img_dir_list = []
|
|
1109
1099
|
for img_dir in pj[cfg.OBSERVATIONS][obs_id][cfg.DIRECTORIES_LIST]:
|
|
1110
|
-
if img_dir !=
|
|
1100
|
+
if img_dir != Path(img_dir).name:
|
|
1111
1101
|
flag_changed = True
|
|
1112
|
-
new_img_dir_list.append(str(
|
|
1102
|
+
new_img_dir_list.append(str(Path(img_dir).name))
|
|
1113
1103
|
pj[cfg.OBSERVATIONS][obs_id][cfg.DIRECTORIES_LIST] = new_img_dir_list
|
|
1114
1104
|
|
|
1115
1105
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] == cfg.MEDIA:
|
|
1116
1106
|
for n_player in cfg.ALL_PLAYERS:
|
|
1117
1107
|
if n_player in pj[cfg.OBSERVATIONS][obs_id][cfg.FILE]:
|
|
1118
1108
|
for idx, media_file in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][n_player]):
|
|
1119
|
-
p =
|
|
1109
|
+
p = Path(media_file).name
|
|
1120
1110
|
if p != media_file:
|
|
1121
1111
|
flag_changed = True
|
|
1122
1112
|
pj[cfg.OBSERVATIONS][obs_id][cfg.FILE][n_player][idx] = p
|
|
@@ -1154,12 +1144,12 @@ def full_path(path: str, project_file_name: str) -> str:
|
|
|
1154
1144
|
str: full path
|
|
1155
1145
|
"""
|
|
1156
1146
|
|
|
1157
|
-
source_path =
|
|
1147
|
+
source_path = Path(path)
|
|
1158
1148
|
if source_path.exists():
|
|
1159
1149
|
return str(source_path)
|
|
1160
1150
|
else:
|
|
1161
1151
|
# check relative path (to project path)
|
|
1162
|
-
project_path =
|
|
1152
|
+
project_path = Path(project_file_name)
|
|
1163
1153
|
if (project_path.parent / source_path).exists():
|
|
1164
1154
|
return str(project_path.parent / source_path)
|
|
1165
1155
|
else:
|
|
@@ -1269,7 +1259,7 @@ def extract_observed_subjects(pj: dict, selected_observations: list) -> list:
|
|
|
1269
1259
|
return list(set(observed_subjects))
|
|
1270
1260
|
|
|
1271
1261
|
|
|
1272
|
-
def open_project_json(
|
|
1262
|
+
def open_project_json(project_file_name: str) -> tuple:
|
|
1273
1263
|
"""
|
|
1274
1264
|
open BORIS project file in json format or GZ compressed json format
|
|
1275
1265
|
|
|
@@ -1283,37 +1273,37 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1283
1273
|
str: message
|
|
1284
1274
|
"""
|
|
1285
1275
|
|
|
1286
|
-
logging.debug(f"open project: {
|
|
1276
|
+
logging.debug(f"open project: {project_file_name}")
|
|
1287
1277
|
|
|
1288
1278
|
projectChanged: bool = False
|
|
1289
1279
|
msg: str = ""
|
|
1290
1280
|
|
|
1291
|
-
if not
|
|
1281
|
+
if not Path(project_file_name).is_file():
|
|
1292
1282
|
return (
|
|
1293
|
-
|
|
1283
|
+
project_file_name,
|
|
1294
1284
|
projectChanged,
|
|
1295
|
-
{"error": f"File {
|
|
1285
|
+
{"error": f"File {project_file_name} not found"},
|
|
1296
1286
|
msg,
|
|
1297
1287
|
)
|
|
1298
1288
|
|
|
1299
1289
|
try:
|
|
1300
|
-
if
|
|
1301
|
-
file_in = gzip.open(
|
|
1290
|
+
if project_file_name.endswith(".boris.gz"):
|
|
1291
|
+
file_in = gzip.open(project_file_name, mode="rt", encoding="utf-8")
|
|
1302
1292
|
else:
|
|
1303
|
-
file_in = open(
|
|
1293
|
+
file_in = open(project_file_name, "r")
|
|
1304
1294
|
file_content = file_in.read()
|
|
1305
1295
|
except PermissionError:
|
|
1306
1296
|
return (
|
|
1307
|
-
|
|
1297
|
+
project_file_name,
|
|
1308
1298
|
projectChanged,
|
|
1309
|
-
{"error": f"File {
|
|
1299
|
+
{"error": f"File {project_file_name}: Permission denied"},
|
|
1310
1300
|
msg,
|
|
1311
1301
|
)
|
|
1312
1302
|
except Exception:
|
|
1313
1303
|
return (
|
|
1314
|
-
|
|
1304
|
+
project_file_name,
|
|
1315
1305
|
projectChanged,
|
|
1316
|
-
{"error": f"Error on file {
|
|
1306
|
+
{"error": f"Error on file {project_file_name}: {sys.exc_info()[1]}"},
|
|
1317
1307
|
msg,
|
|
1318
1308
|
)
|
|
1319
1309
|
|
|
@@ -1321,16 +1311,16 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1321
1311
|
pj = json.loads(file_content)
|
|
1322
1312
|
except json.decoder.JSONDecodeError:
|
|
1323
1313
|
return (
|
|
1324
|
-
|
|
1314
|
+
project_file_name,
|
|
1325
1315
|
projectChanged,
|
|
1326
1316
|
{"error": "This project file seems corrupted"},
|
|
1327
1317
|
msg,
|
|
1328
1318
|
)
|
|
1329
1319
|
except Exception:
|
|
1330
1320
|
return (
|
|
1331
|
-
|
|
1321
|
+
project_file_name,
|
|
1332
1322
|
projectChanged,
|
|
1333
|
-
{"error": f"Error on file {
|
|
1323
|
+
{"error": f"Error on file {project_file_name}: {sys.exc_info()[1]}"},
|
|
1334
1324
|
msg,
|
|
1335
1325
|
)
|
|
1336
1326
|
|
|
@@ -1352,7 +1342,7 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1352
1342
|
# check if project file version is newer than current BORIS project file version
|
|
1353
1343
|
if cfg.PROJECT_VERSION in pj and util.versiontuple(pj[cfg.PROJECT_VERSION]) > util.versiontuple(version.__version__):
|
|
1354
1344
|
return (
|
|
1355
|
-
|
|
1345
|
+
project_file_name,
|
|
1356
1346
|
projectChanged,
|
|
1357
1347
|
{
|
|
1358
1348
|
"error": (
|
|
@@ -1395,7 +1385,7 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1395
1385
|
f"The project file was converted to the new format (v. {cfg.project_format_version}) in use with your version of BORIS.<br>"
|
|
1396
1386
|
"Choose a new file name for saving it."
|
|
1397
1387
|
)
|
|
1398
|
-
|
|
1388
|
+
project_file_name = ""
|
|
1399
1389
|
|
|
1400
1390
|
# update modifiers to JSON format
|
|
1401
1391
|
|
|
@@ -1425,8 +1415,8 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1425
1415
|
|
|
1426
1416
|
# add category key if not found
|
|
1427
1417
|
for idx in pj[cfg.ETHOGRAM]:
|
|
1428
|
-
if
|
|
1429
|
-
pj[cfg.ETHOGRAM][idx][
|
|
1418
|
+
if cfg.BEHAVIOR_CATEGORY not in pj[cfg.ETHOGRAM][idx]:
|
|
1419
|
+
pj[cfg.ETHOGRAM][idx][cfg.BEHAVIOR_CATEGORY] = ""
|
|
1430
1420
|
|
|
1431
1421
|
# if one file is present in player #1 -> set "media_info" key with value of media_file_info
|
|
1432
1422
|
for obs in pj[cfg.OBSERVATIONS]:
|
|
@@ -1447,7 +1437,7 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1447
1437
|
ret, ffmpeg_bin = util.check_ffmpeg_path()
|
|
1448
1438
|
if not ret:
|
|
1449
1439
|
return (
|
|
1450
|
-
|
|
1440
|
+
project_file_name,
|
|
1451
1441
|
projectChanged,
|
|
1452
1442
|
{"error": "FFmpeg path not found"},
|
|
1453
1443
|
"",
|
|
@@ -1516,8 +1506,8 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1516
1506
|
msg = f"The project was updated to the current project version ({cfg.project_format_version})."
|
|
1517
1507
|
|
|
1518
1508
|
try:
|
|
1519
|
-
old_project_file_name =
|
|
1520
|
-
copyfile(
|
|
1509
|
+
old_project_file_name = project_file_name.replace(".boris", f".v{pj['project_format_version']}.boris")
|
|
1510
|
+
copyfile(project_file_name, old_project_file_name)
|
|
1521
1511
|
msg += f"\n\nThe old file project was saved as {old_project_file_name}"
|
|
1522
1512
|
except Exception:
|
|
1523
1513
|
QMessageBox.critical(cfg.programName, f"Error saving old project to {old_project_file_name}")
|
|
@@ -1529,12 +1519,12 @@ def open_project_json(projectFileName: str) -> tuple:
|
|
|
1529
1519
|
if pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE] in (cfg.LIVE, cfg.MEDIA):
|
|
1530
1520
|
pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS].sort()
|
|
1531
1521
|
|
|
1532
|
-
return
|
|
1522
|
+
return project_file_name, projectChanged, pj, msg
|
|
1533
1523
|
|
|
1534
1524
|
|
|
1535
|
-
def event_type(code: str, ethogram: dict) -> str:
|
|
1525
|
+
def event_type(code: str, ethogram: dict) -> str | None:
|
|
1536
1526
|
"""
|
|
1537
|
-
returns type of event for code
|
|
1527
|
+
returns type of event for behavior code
|
|
1538
1528
|
|
|
1539
1529
|
Args:
|
|
1540
1530
|
ethogram (dict); ethogram of project
|
|
@@ -1764,6 +1754,9 @@ def explore_project(self) -> None:
|
|
|
1764
1754
|
|
|
1765
1755
|
|
|
1766
1756
|
def project2dataframe(pj: dict, observations_list: list = []) -> pd.DataFrame:
|
|
1757
|
+
"""
|
|
1758
|
+
returns a pandas dataframe containing observations data
|
|
1759
|
+
"""
|
|
1767
1760
|
# print(pj.keys())
|
|
1768
1761
|
|
|
1769
1762
|
# print(pj["independent_variables"])
|
|
@@ -1771,7 +1764,7 @@ def project2dataframe(pj: dict, observations_list: list = []) -> pd.DataFrame:
|
|
|
1771
1764
|
# indep_var = [pj["independent_variables"][idx]["label"] for idx in pj["independent_variables"]]
|
|
1772
1765
|
|
|
1773
1766
|
indep_variables = dict(
|
|
1774
|
-
[(pj[
|
|
1767
|
+
[(pj[cfg.INDEPENDENT_VARIABLES][idx]["label"], pj[cfg.INDEPENDENT_VARIABLES][idx]["type"]) for idx in pj[cfg.INDEPENDENT_VARIABLES]]
|
|
1775
1768
|
)
|
|
1776
1769
|
|
|
1777
1770
|
# print()
|
|
@@ -1780,22 +1773,25 @@ def project2dataframe(pj: dict, observations_list: list = []) -> pd.DataFrame:
|
|
|
1780
1773
|
# n_max_set_modifiers = max([len(pj["behaviors_conf"][behavior_id]["modifiers"]) for behavior_id in pj["behaviors_conf"]])
|
|
1781
1774
|
|
|
1782
1775
|
# behavioral_categories
|
|
1783
|
-
behavioral_category = dict(
|
|
1776
|
+
behavioral_category = dict(
|
|
1777
|
+
[(pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE], pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CATEGORY]) for x in pj[cfg.ETHOGRAM]]
|
|
1778
|
+
)
|
|
1784
1779
|
|
|
1785
1780
|
# print(f"{pj["behaviors_conf"]=}")
|
|
1786
1781
|
|
|
1782
|
+
# check all modifiers
|
|
1787
1783
|
all_modifier_sets: list = []
|
|
1788
|
-
for behavior_id in pj[
|
|
1789
|
-
modifier_names = []
|
|
1784
|
+
for behavior_id in pj[cfg.ETHOGRAM]:
|
|
1785
|
+
modifier_names: list = []
|
|
1790
1786
|
set_count = 0
|
|
1791
|
-
if pj[
|
|
1787
|
+
if pj[cfg.ETHOGRAM][behavior_id][cfg.MODIFIERS] == "":
|
|
1792
1788
|
continue
|
|
1793
|
-
for modifier in pj[
|
|
1789
|
+
for modifier in pj[cfg.ETHOGRAM][behavior_id][cfg.MODIFIERS].values():
|
|
1794
1790
|
if modifier["name"]:
|
|
1795
|
-
modifier_names.append((pj[
|
|
1791
|
+
modifier_names.append((pj[cfg.ETHOGRAM][behavior_id][cfg.BEHAVIOR_CODE], modifier["name"]))
|
|
1796
1792
|
else:
|
|
1797
1793
|
set_count += 1
|
|
1798
|
-
modifier_names.append((pj[
|
|
1794
|
+
modifier_names.append((pj[cfg.ETHOGRAM][behavior_id][cfg.BEHAVIOR_CODE], f"set #{set_count}"))
|
|
1799
1795
|
|
|
1800
1796
|
# print(modifier_names)
|
|
1801
1797
|
if modifier_names:
|
|
@@ -1861,7 +1857,7 @@ def project2dataframe(pj: dict, observations_list: list = []) -> pd.DataFrame:
|
|
|
1861
1857
|
|
|
1862
1858
|
# TODO: set correct type in base of the var type
|
|
1863
1859
|
for indep_var in indep_variables:
|
|
1864
|
-
type_[f"independent variable '{indep_var}'"] = "float64" if indep_variables[indep_var] ==
|
|
1860
|
+
type_[f"independent variable '{indep_var}'"] = "float64" if indep_variables[indep_var] == cfg.NUMERIC else "string"
|
|
1865
1861
|
|
|
1866
1862
|
type_ = type_ | {
|
|
1867
1863
|
"Subject": "string",
|
|
@@ -1887,14 +1883,14 @@ def project2dataframe(pj: dict, observations_list: list = []) -> pd.DataFrame:
|
|
|
1887
1883
|
"Comment stop": "string",
|
|
1888
1884
|
}
|
|
1889
1885
|
|
|
1890
|
-
state_behaviors = [pj[
|
|
1886
|
+
state_behaviors = [pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] for x in pj[cfg.ETHOGRAM] if pj[cfg.ETHOGRAM][x]["type"] == cfg.STATE_EVENT]
|
|
1891
1887
|
|
|
1892
|
-
for obs_id in pj[
|
|
1888
|
+
for obs_id in pj[cfg.OBSERVATIONS]:
|
|
1893
1889
|
if observations_list and obs_id not in observations_list:
|
|
1894
1890
|
continue
|
|
1895
1891
|
# print(obs_id)
|
|
1896
1892
|
stop_event_idx = set()
|
|
1897
|
-
for idx_event, event in enumerate(pj[
|
|
1893
|
+
for idx_event, event in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]):
|
|
1898
1894
|
if idx_event in stop_event_idx:
|
|
1899
1895
|
continue
|
|
1900
1896
|
data["Observation id"].append(obs_id)
|
|
@@ -1908,9 +1904,11 @@ def project2dataframe(pj: dict, observations_list: list = []) -> pd.DataFrame:
|
|
|
1908
1904
|
# data["FPS (frame/s)"].append("")
|
|
1909
1905
|
|
|
1910
1906
|
for indep_var in indep_variables:
|
|
1911
|
-
data[f"independent variable '{indep_var}'"].append(
|
|
1907
|
+
data[f"independent variable '{indep_var}'"].append(
|
|
1908
|
+
pj[cfg.OBSERVATIONS][obs_id][cfg.INDEPENDENT_VARIABLES].get(indep_var, None)
|
|
1909
|
+
)
|
|
1912
1910
|
|
|
1913
|
-
data["Subject"].append(event[
|
|
1911
|
+
data["Subject"].append(event[cfg.EVENT_SUBJECT_FIELD_IDX] if event[cfg.EVENT_SUBJECT_FIELD_IDX] != "" else cfg.NO_FOCAL_SUBJECT)
|
|
1914
1912
|
data["Observation duration by subject by observation"].append(-1)
|
|
1915
1913
|
data["Behavior"].append(event[2])
|
|
1916
1914
|
data["Behavioral category"].append(behavioral_category[event[2]])
|
|
@@ -1923,12 +1921,12 @@ def project2dataframe(pj: dict, observations_list: list = []) -> pd.DataFrame:
|
|
|
1923
1921
|
else:
|
|
1924
1922
|
data[modifier_set].append(np.nan)
|
|
1925
1923
|
|
|
1926
|
-
data["Behavior type"].append(
|
|
1924
|
+
data["Behavior type"].append(cfg.STATE_EVENT if event[2] in state_behaviors else cfg.POINT_EVENT)
|
|
1927
1925
|
data["Start (s)"].append(event[0])
|
|
1928
1926
|
if event[2] in state_behaviors:
|
|
1929
1927
|
# search stop
|
|
1930
1928
|
# print(f"==> {idx_event=} {event[1:4]=}")
|
|
1931
|
-
for idx_event2, event2 in enumerate(pj[
|
|
1929
|
+
for idx_event2, event2 in enumerate(pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS][idx_event + 1 :], start=idx_event + 1):
|
|
1932
1930
|
# print(f"{idx_event2=} {event2[1:4]=}")
|
|
1933
1931
|
if event2[1:4] == event[1:4]:
|
|
1934
1932
|
# print("found")
|
|
@@ -1939,7 +1937,6 @@ def project2dataframe(pj: dict, observations_list: list = []) -> pd.DataFrame:
|
|
|
1939
1937
|
data["Comment stop"].append(event2[4])
|
|
1940
1938
|
break
|
|
1941
1939
|
else:
|
|
1942
|
-
# print("not paired")
|
|
1943
1940
|
raise ("not paired")
|
|
1944
1941
|
|
|
1945
1942
|
else: # point
|
boris/project_import_export.py
CHANGED
boris/select_modifiers.py
CHANGED
boris/select_observations.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
|
|
|
7
7
|
This program is free software; you can redistribute it and/or modify
|
|
@@ -74,12 +74,12 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
|
|
|
74
74
|
|
|
75
75
|
# observed time interval
|
|
76
76
|
interval = project_functions.observed_interval(pj[cfg.OBSERVATIONS][obs])
|
|
77
|
-
observed_interval_str = str(interval[1] - interval[0])
|
|
77
|
+
observed_interval_str = str(round(interval[1] - interval[0], 3))
|
|
78
78
|
|
|
79
79
|
# media
|
|
80
|
-
media = ""
|
|
80
|
+
media: str = ""
|
|
81
81
|
if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.MEDIA:
|
|
82
|
-
media_list = []
|
|
82
|
+
media_list: list = []
|
|
83
83
|
if pj[cfg.OBSERVATIONS][obs][cfg.FILE]:
|
|
84
84
|
for player in sorted(pj[cfg.OBSERVATIONS][obs][cfg.FILE].keys()):
|
|
85
85
|
for media in pj[cfg.OBSERVATIONS][obs][cfg.FILE][player]:
|
|
@@ -94,13 +94,13 @@ def select_observations2(self, mode: str, windows_title: str = "") -> Tuple[str,
|
|
|
94
94
|
media = cfg.LIVE
|
|
95
95
|
|
|
96
96
|
if pj[cfg.OBSERVATIONS][obs][cfg.TYPE] == cfg.IMAGES:
|
|
97
|
-
dir_list = []
|
|
97
|
+
dir_list: list = []
|
|
98
98
|
for dir_path in pj[cfg.OBSERVATIONS][obs].get(cfg.DIRECTORIES_LIST, []):
|
|
99
99
|
dir_list.append(dir_path)
|
|
100
100
|
media = "; ".join(dir_list)
|
|
101
101
|
|
|
102
102
|
# independent variables
|
|
103
|
-
indepvar = []
|
|
103
|
+
indepvar: list = []
|
|
104
104
|
if cfg.INDEPENDENT_VARIABLES in pj[cfg.OBSERVATIONS][obs]:
|
|
105
105
|
for var_label in indep_var_header:
|
|
106
106
|
if var_label in pj[cfg.OBSERVATIONS][obs][cfg.INDEPENDENT_VARIABLES]:
|
boris/select_subj_behav.py
CHANGED
boris/state_events.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 program is free software; you can redistribute it and/or modify
|
|
7
7
|
it under the terms of the GNU General Public License as published by
|
boris/subjects_pad.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 program is free software; you can redistribute it and/or modify
|
|
7
7
|
it under the terms of the GNU General Public License as published by
|
boris/synthetic_time_budget.py
CHANGED
boris/time_budget_functions.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 program is free software; you can redistribute it and/or modify
|
|
7
7
|
it under the terms of the GNU General Public License as published by
|
boris/time_budget_widget.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
|
|
|
@@ -515,8 +515,10 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
515
515
|
# check intervals
|
|
516
516
|
for subj in parameters[cfg.SELECTED_SUBJECTS]:
|
|
517
517
|
for behav in parameters[cfg.SELECTED_BEHAVIORS]:
|
|
518
|
-
if cfg.POINT in self.eventType(behav).upper():
|
|
518
|
+
# if cfg.POINT in self.eventType(behav).upper():
|
|
519
|
+
if cfg.POINT in project_functions.event_type(behav, self.pj[cfg.ETHOGRAM]):
|
|
519
520
|
continue
|
|
521
|
+
|
|
520
522
|
# extract modifiers
|
|
521
523
|
|
|
522
524
|
cursor.execute(
|
|
@@ -851,10 +853,8 @@ def time_budget(self, mode: str, mode2: str = "list"):
|
|
|
851
853
|
# check intervals
|
|
852
854
|
for subj in parameters[cfg.SELECTED_SUBJECTS]:
|
|
853
855
|
for behav in parameters[cfg.SELECTED_BEHAVIORS]:
|
|
854
|
-
if cfg.POINT in project_functions.event_type(behav, self.pj[cfg.ETHOGRAM]):
|
|
856
|
+
if cfg.POINT in project_functions.event_type(behav, self.pj[cfg.ETHOGRAM]):
|
|
855
857
|
continue
|
|
856
|
-
# extract modifiers
|
|
857
|
-
# if plot_parameters["include modifiers"]:
|
|
858
858
|
|
|
859
859
|
cursor.execute(
|
|
860
860
|
"SELECT distinct modifiers FROM events WHERE observation = ? AND subject = ? AND code = ?",
|
boris/transitions.py
CHANGED
boris/utilities.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 program is free software; you can redistribute it and/or modify
|
|
7
7
|
it under the terms of the GNU General Public License as published by
|
boris/version.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
|
|
|
@@ -20,5 +20,5 @@ This file is part of BORIS.
|
|
|
20
20
|
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
__version__ = "9.0.
|
|
24
|
-
__version_date__ = "2025-
|
|
23
|
+
__version__ = "9.0.4"
|
|
24
|
+
__version_date__ = "2025-02-10"
|
boris/video_equalizer.py
CHANGED
boris/video_operations.py
CHANGED
boris/write_event.py
CHANGED