boris-behav-obs 9.0.1__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.
Files changed (80) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +2 -2
  4. boris/add_modifier.py +1 -1
  5. boris/add_modifier_ui.py +22 -22
  6. boris/advanced_event_filtering.py +1 -1
  7. boris/analysis_plugins/number_of_occurences.py +6 -3
  8. boris/analysis_plugins/number_of_occurences_by_independent_variable.py +6 -4
  9. boris/analysis_plugins/time_budget.py +24 -14
  10. boris/behav_coding_map_creator.py +1 -1
  11. boris/behavior_binary_table.py +1 -1
  12. boris/behaviors_coding_map.py +1 -1
  13. boris/boris_cli.py +1 -1
  14. boris/cmd_arguments.py +1 -1
  15. boris/coding_pad.py +1 -1
  16. boris/config.py +16 -9
  17. boris/config_file.py +1 -1
  18. boris/connections.py +1 -1
  19. boris/converters.py +1 -1
  20. boris/cooccurence.py +1 -1
  21. boris/core.py +144 -176
  22. boris/db_functions.py +1 -1
  23. boris/dialog.py +10 -277
  24. boris/edit_event.py +8 -1
  25. boris/event_operations.py +1 -1
  26. boris/events_cursor.py +1 -1
  27. boris/events_snapshots.py +1 -1
  28. boris/exclusion_matrix.py +1 -1
  29. boris/export_events.py +1 -1
  30. boris/export_observation.py +1 -1
  31. boris/external_processes.py +1 -1
  32. boris/geometric_measurement.py +1 -1
  33. boris/gui_utilities.py +1 -1
  34. boris/image_overlay.py +1 -1
  35. boris/import_observations.py +1 -1
  36. boris/irr.py +1 -1
  37. boris/latency.py +1 -1
  38. boris/map_creator.py +1 -1
  39. boris/measurement_widget.py +1 -1
  40. boris/media_file.py +1 -1
  41. boris/menu_options.py +1 -1
  42. boris/modifiers_coding_map.py +1 -1
  43. boris/observation.py +1 -1
  44. boris/observation_operations.py +530 -425
  45. boris/observations_list.py +3 -3
  46. boris/otx_parser.py +1 -1
  47. boris/param_panel.py +1 -1
  48. boris/player_dock_widget.py +13 -9
  49. boris/plot_data_module.py +1 -1
  50. boris/plot_events.py +1 -1
  51. boris/plot_events_rt.py +1 -1
  52. boris/plot_spectrogram_rt.py +1 -1
  53. boris/plot_waveform_rt.py +1 -1
  54. boris/plugins.py +2 -2
  55. boris/preferences.py +6 -1
  56. boris/preferences_ui.py +7 -1
  57. boris/project.py +1 -1
  58. boris/project_functions.py +73 -76
  59. boris/project_import_export.py +1 -1
  60. boris/select_modifiers.py +1 -1
  61. boris/select_observations.py +6 -6
  62. boris/select_subj_behav.py +1 -1
  63. boris/state_events.py +1 -1
  64. boris/subjects_pad.py +1 -1
  65. boris/synthetic_time_budget.py +1 -1
  66. boris/time_budget_functions.py +1 -1
  67. boris/time_budget_widget.py +5 -5
  68. boris/transitions.py +1 -1
  69. boris/utilities.py +8 -8
  70. boris/version.py +3 -3
  71. boris/video_equalizer.py +1 -1
  72. boris/video_operations.py +1 -1
  73. boris/write_event.py +1 -1
  74. {boris_behav_obs-9.0.1.dist-info → boris_behav_obs-9.0.4.dist-info}/METADATA +1 -1
  75. boris_behav_obs-9.0.4.dist-info/RECORD +103 -0
  76. boris_behav_obs-9.0.1.dist-info/RECORD +0 -103
  77. {boris_behav_obs-9.0.1.dist-info → boris_behav_obs-9.0.4.dist-info}/LICENSE.TXT +0 -0
  78. {boris_behav_obs-9.0.1.dist-info → boris_behav_obs-9.0.4.dist-info}/WHEEL +0 -0
  79. {boris_behav_obs-9.0.1.dist-info → boris_behav_obs-9.0.4.dist-info}/entry_points.txt +0 -0
  80. {boris_behav_obs-9.0.1.dist-info → boris_behav_obs-9.0.4.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2024 Olivier Friard
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
- import pathlib as pl
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 = pl.Path(export_dir) / pl.Path(util.safeFileName(obs_id)).with_suffix(".srt")
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 = pl.Path(export_dir) / pl.Path(pl.Path(media_file).stem).with_suffix(".srt")
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
- pl.Path(img_dir).relative_to(pl.Path(project_file_name).parent)
903
+ Path(img_dir).relative_to(Path(project_file_name).parent)
905
904
  except ValueError:
906
- if pl.Path(img_dir).is_absolute() or not (pl.Path(project_file_name).parent / pl.Path(img_dir)).is_dir():
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
- pl.Path(media_file).relative_to(pl.Path(project_file_name).parent)
918
+ Path(media_file).relative_to(Path(project_file_name).parent)
920
919
  except ValueError:
921
- if pl.Path(media_file).is_absolute() or not (pl.Path(project_file_name).parent / pl.Path(media_file)).is_file():
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(pl.Path(img_dir).relative_to(pl.Path(project_file_name).parent)))
938
+ new_dir_list.append(str(Path(img_dir).relative_to(Path(project_file_name).parent)))
940
939
  except ValueError:
941
- if not pl.Path(img_dir).is_absolute() and (pl.Path(project_file_name).parent / pl.Path(img_dir)).is_dir():
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(pl.Path(media_file).relative_to(pl.Path(project_file_name).parent))
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
- pl.Path(v[cfg.FILE_PATH]).relative_to(pl.Path(project_file_name).parent)
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(pl.Path(v[cfg.FILE_PATH]).relative_to(pl.Path(project_file_name).parent))
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(pl.Path(pj[cfg.OBSERVATIONS][obs_id][cfg.PLOT_DATA][idx]["file_path"]).name)
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(pl.Path(img_dir).name, project_file_name) == "":
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(pl.Path(media_file).name, project_file_name) == "":
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 != pl.Path(img_dir).name:
1100
+ if img_dir != Path(img_dir).name:
1111
1101
  flag_changed = True
1112
- new_img_dir_list.append(str(pl.Path(img_dir).name))
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 = pl.Path(media_file).name
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 = pl.Path(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 = pl.Path(project_file_name)
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(projectFileName: str) -> tuple:
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: {projectFileName}")
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 os.path.isfile(projectFileName):
1281
+ if not Path(project_file_name).is_file():
1292
1282
  return (
1293
- projectFileName,
1283
+ project_file_name,
1294
1284
  projectChanged,
1295
- {"error": f"File {projectFileName} not found"},
1285
+ {"error": f"File {project_file_name} not found"},
1296
1286
  msg,
1297
1287
  )
1298
1288
 
1299
1289
  try:
1300
- if projectFileName.endswith(".boris.gz"):
1301
- file_in = gzip.open(projectFileName, mode="rt", encoding="utf-8")
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(projectFileName, "r")
1293
+ file_in = open(project_file_name, "r")
1304
1294
  file_content = file_in.read()
1305
1295
  except PermissionError:
1306
1296
  return (
1307
- projectFileName,
1297
+ project_file_name,
1308
1298
  projectChanged,
1309
- {"error": f"File {projectFileName}: Permission denied"},
1299
+ {"error": f"File {project_file_name}: Permission denied"},
1310
1300
  msg,
1311
1301
  )
1312
1302
  except Exception:
1313
1303
  return (
1314
- projectFileName,
1304
+ project_file_name,
1315
1305
  projectChanged,
1316
- {"error": f"Error on file {projectFileName}: {sys.exc_info()[1]}"},
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
- projectFileName,
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
- projectFileName,
1321
+ project_file_name,
1332
1322
  projectChanged,
1333
- {"error": f"Error on file {projectFileName}: {sys.exc_info()[1]}"},
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
- projectFileName,
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
- projectFileName = ""
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 "category" not in pj[cfg.ETHOGRAM][idx]:
1429
- pj[cfg.ETHOGRAM][idx]["category"] = ""
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
- projectFileName,
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 = projectFileName.replace(".boris", f".v{pj['project_format_version']}.boris")
1520
- copyfile(projectFileName, old_project_file_name)
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 projectFileName, projectChanged, pj, msg
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["independent_variables"][idx]["label"], pj["independent_variables"][idx]["type"]) for idx in pj["independent_variables"]]
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([(pj["behaviors_conf"][x]["code"], pj["behaviors_conf"][x]["category"]) for x in pj["behaviors_conf"]])
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["behaviors_conf"]:
1789
- modifier_names = []
1784
+ for behavior_id in pj[cfg.ETHOGRAM]:
1785
+ modifier_names: list = []
1790
1786
  set_count = 0
1791
- if pj["behaviors_conf"][behavior_id]["modifiers"] == "":
1787
+ if pj[cfg.ETHOGRAM][behavior_id][cfg.MODIFIERS] == "":
1792
1788
  continue
1793
- for modifier in pj["behaviors_conf"][behavior_id]["modifiers"].values():
1789
+ for modifier in pj[cfg.ETHOGRAM][behavior_id][cfg.MODIFIERS].values():
1794
1790
  if modifier["name"]:
1795
- modifier_names.append((pj["behaviors_conf"][behavior_id]["code"], modifier["name"]))
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["behaviors_conf"][behavior_id]["code"], f"set #{set_count}"))
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] == "numeric" else "string"
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["behaviors_conf"][x]["code"] for x in pj["behaviors_conf"] if pj["behaviors_conf"][x]["type"] == "State event"]
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["observations"]:
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["observations"][obs_id]["events"]):
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(pj["observations"][obs_id]["independent_variables"][indep_var])
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[1])
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("State event" if event[2] in state_behaviors else "Point event")
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["observations"][obs_id]["events"][idx_event + 1 :], start=idx_event + 1):
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
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2024 Olivier Friard
4
+ Copyright 2012-2025 Olivier Friard
5
5
 
6
6
  This file is part of BORIS.
7
7
 
boris/select_modifiers.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2024 Olivier Friard
4
+ Copyright 2012-2025 Olivier Friard
5
5
 
6
6
  This file is part of BORIS.
7
7
 
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2024 Olivier Friard
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]:
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2024 Olivier Friard
4
+ Copyright 2012-2025 Olivier Friard
5
5
 
6
6
  This file is part of BORIS.
7
7
 
boris/state_events.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2024 Olivier Friard
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-2024 Olivier Friard
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
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2024 Olivier Friard
4
+ Copyright 2012-2025 Olivier Friard
5
5
 
6
6
  This file is part of BORIS.
7
7
 
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2024 Olivier Friard
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
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2024 Olivier Friard
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]): # self.eventType(behav).upper():
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
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2024 Olivier Friard
4
+ Copyright 2012-2025 Olivier Friard
5
5
 
6
6
  This file is part of BORIS.
7
7
 
boris/utilities.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2024 Olivier Friard
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
@@ -89,10 +89,10 @@ def extract_exif_DateTimeOriginal(file_path: str) -> int:
89
89
  tags = exifread.process_file(f_in, details=False, stop_tag="EXIF DateTimeOriginal")
90
90
  if "EXIF DateTimeOriginal" in tags:
91
91
  date_time_original = (
92
- f'{tags["EXIF DateTimeOriginal"].values[:4]}-'
93
- f'{tags["EXIF DateTimeOriginal"].values[5:7]}-'
94
- f'{tags["EXIF DateTimeOriginal"].values[8:10]} '
95
- f'{tags["EXIF DateTimeOriginal"].values.split(" ")[-1]}'
92
+ f"{tags['EXIF DateTimeOriginal'].values[:4]}-"
93
+ f"{tags['EXIF DateTimeOriginal'].values[5:7]}-"
94
+ f"{tags['EXIF DateTimeOriginal'].values[8:10]} "
95
+ f"{tags['EXIF DateTimeOriginal'].values.split(' ')[-1]}"
96
96
  )
97
97
  return int(datetime.datetime.strptime(date_time_original, "%Y-%m-%d %H:%M:%S").timestamp())
98
98
  else:
@@ -143,14 +143,14 @@ def extract_date_time_from_file_name(file_path: str) -> int:
143
143
  if matches:
144
144
  if pattern == r"\d{4}-\d{2}-\d{2}_\d{6}":
145
145
  logging.debug(
146
- f"extract_date_time_from_file_name timestamp from {file_path}: {int(datetime.datetime.strptime(matches[0], "%Y-%m-%d_%H%M%S").timestamp())}"
146
+ f"extract_date_time_from_file_name timestamp from {file_path}: {int(datetime.datetime.strptime(matches[0], '%Y-%m-%d_%H%M%S').timestamp())}"
147
147
  )
148
148
 
149
149
  return int(datetime.datetime.strptime(matches[0], "%Y-%m-%d_%H%M%S").timestamp())
150
150
 
151
151
  if pattern == r"\d{4}-\d{2}-\d{2}_\d{2}:\d{2}:\d{2}":
152
152
  logging.debug(
153
- f"extract_date_time_from_file_name timestamp from {file_path}: {int(datetime.datetime.strptime(matches[0], "%Y-%m-%d_%H:%M:%S").timestamp())}"
153
+ f"extract_date_time_from_file_name timestamp from {file_path}: {int(datetime.datetime.strptime(matches[0], '%Y-%m-%d_%H:%M:%S').timestamp())}"
154
154
  )
155
155
 
156
156
  return int(datetime.datetime.strptime(matches[0], "%Y-%m-%d_%H:%M:%S").timestamp())
@@ -1617,7 +1617,7 @@ def has_coding_map(ethogram: dict, behavior_idx: str) -> bool:
1617
1617
  return False
1618
1618
  if not ethogram[behavior_idx].get("coding map", False):
1619
1619
  return False
1620
- return False
1620
+ return True
1621
1621
 
1622
1622
 
1623
1623
  def dir_images_number(dir_path_str: str) -> dict:
boris/version.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2024 Olivier Friard
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.1"
24
- __version_date__ = "2025-01-10"
23
+ __version__ = "9.0.4"
24
+ __version_date__ = "2025-02-10"