boris-behav-obs 9.3.2__tar.gz → 9.3.3__tar.gz

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 (121) hide show
  1. {boris_behav_obs-9.3.2/boris_behav_obs.egg-info → boris_behav_obs-9.3.3}/PKG-INFO +1 -1
  2. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/config.py +4 -1
  3. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/core.py +0 -5
  4. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/dialog.py +1 -1
  5. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/media_file.py +12 -10
  6. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/observation_operations.py +4 -4
  7. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/project.py +69 -21
  8. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/project_import_export.py +3 -1
  9. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/utilities.py +0 -1
  10. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/version.py +2 -2
  11. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3/boris_behav_obs.egg-info}/PKG-INFO +1 -1
  12. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/pyproject.toml +2 -2
  13. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/tests/test_utilities.py +6 -3
  14. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/LICENSE.TXT +0 -0
  15. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/MANIFEST.in +0 -0
  16. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/README.TXT +0 -0
  17. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/README.md +0 -0
  18. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/__init__.py +0 -0
  19. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/__main__.py +0 -0
  20. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/about.py +0 -0
  21. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/add_modifier.py +0 -0
  22. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/add_modifier_ui.py +0 -0
  23. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/advanced_event_filtering.py +0 -0
  24. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/analysis_plugins/__init__.py +0 -0
  25. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/analysis_plugins/_latency.py +0 -0
  26. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/analysis_plugins/number_of_occurences.py +0 -0
  27. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/analysis_plugins/number_of_occurences_by_independent_variable.py +0 -0
  28. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/analysis_plugins/time_budget.py +0 -0
  29. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/behav_coding_map_creator.py +0 -0
  30. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/behavior_binary_table.py +0 -0
  31. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/behaviors_coding_map.py +0 -0
  32. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/boris_cli.py +0 -0
  33. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/cmd_arguments.py +0 -0
  34. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/coding_pad.py +0 -0
  35. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/config_file.py +0 -0
  36. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/connections.py +0 -0
  37. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/converters.py +0 -0
  38. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/converters_ui.py +0 -0
  39. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/cooccurence.py +0 -0
  40. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/core_qrc.py +0 -0
  41. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/core_ui.py +0 -0
  42. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/db_functions.py +0 -0
  43. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/dev.py +0 -0
  44. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/duration_widget.py +0 -0
  45. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/edit_event.py +0 -0
  46. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/edit_event_ui.py +0 -0
  47. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/event_operations.py +0 -0
  48. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/events_cursor.py +0 -0
  49. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/events_snapshots.py +0 -0
  50. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/exclusion_matrix.py +0 -0
  51. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/export_events.py +0 -0
  52. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/export_observation.py +0 -0
  53. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/external_processes.py +0 -0
  54. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/geometric_measurement.py +0 -0
  55. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/gui_utilities.py +0 -0
  56. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/image_overlay.py +0 -0
  57. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/import_observations.py +0 -0
  58. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/irr.py +0 -0
  59. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/latency.py +0 -0
  60. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/measurement_widget.py +0 -0
  61. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/menu_options.py +0 -0
  62. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/modifier_coding_map_creator.py +0 -0
  63. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/modifiers_coding_map.py +0 -0
  64. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/mpv-1.0.3.py +0 -0
  65. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/mpv.py +0 -0
  66. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/mpv2.py +0 -0
  67. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/observation.py +0 -0
  68. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/observation_ui.py +0 -0
  69. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/observations_list.py +0 -0
  70. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/otx_parser.py +0 -0
  71. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/param_panel.py +0 -0
  72. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/param_panel_ui.py +0 -0
  73. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/player_dock_widget.py +0 -0
  74. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/plot_data_module.py +0 -0
  75. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/plot_events.py +0 -0
  76. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/plot_events_rt.py +0 -0
  77. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/plot_spectrogram_rt.py +0 -0
  78. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/plot_waveform_rt.py +0 -0
  79. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/plugins.py +0 -0
  80. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/portion/__init__.py +0 -0
  81. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/portion/const.py +0 -0
  82. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/portion/dict.py +0 -0
  83. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/portion/func.py +0 -0
  84. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/portion/interval.py +0 -0
  85. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/portion/io.py +0 -0
  86. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/preferences.py +0 -0
  87. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/preferences_ui.py +0 -0
  88. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/project_functions.py +0 -0
  89. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/project_ui.py +0 -0
  90. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/qrc_boris.py +0 -0
  91. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/qrc_boris5.py +0 -0
  92. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/select_modifiers.py +0 -0
  93. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/select_observations.py +0 -0
  94. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/select_subj_behav.py +0 -0
  95. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/state_events.py +0 -0
  96. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/subjects_pad.py +0 -0
  97. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/synthetic_time_budget.py +0 -0
  98. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/time_budget_functions.py +0 -0
  99. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/time_budget_widget.py +0 -0
  100. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/transitions.py +0 -0
  101. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/video_equalizer.py +0 -0
  102. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/video_equalizer_ui.py +0 -0
  103. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/video_operations.py +0 -0
  104. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/view_df.py +0 -0
  105. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/view_df_ui.py +0 -0
  106. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris/write_event.py +0 -0
  107. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris_behav_obs.egg-info/SOURCES.txt +0 -0
  108. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris_behav_obs.egg-info/dependency_links.txt +0 -0
  109. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris_behav_obs.egg-info/entry_points.txt +0 -0
  110. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris_behav_obs.egg-info/requires.txt +0 -0
  111. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/boris_behav_obs.egg-info/top_level.txt +0 -0
  112. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/setup.cfg +0 -0
  113. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/tests/test_db_functions.py +0 -0
  114. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/tests/test_export_observation.py +0 -0
  115. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/tests/test_irr.py +0 -0
  116. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/tests/test_observation_gui.py +0 -0
  117. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/tests/test_otx_parser.py +0 -0
  118. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/tests/test_preferences_gui.py +0 -0
  119. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/tests/test_project_functions.py +0 -0
  120. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/tests/test_time_budget.py +0 -0
  121. {boris_behav_obs-9.3.2 → boris_behav_obs-9.3.3}/tests/test_utilities2.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boris-behav-obs
3
- Version: 9.3.2
3
+ Version: 9.3.3
4
4
  Summary: BORIS - Behavioral Observation Research Interactive Software
5
5
  Author-email: Olivier Friard <olivier.friard@unito.it>
6
6
  License-Expression: GPL-3.0-only
@@ -155,6 +155,7 @@ CHECK_PROJECT_INTEGRITY = "check_project_integrity"
155
155
  YES = "Yes"
156
156
  NO = "No"
157
157
  CANCEL = "Cancel"
158
+ IGNORE = "Ignore"
158
159
  APPEND = "Append"
159
160
  CLOSE = "Close"
160
161
  REPLACE = "Replace"
@@ -454,7 +455,8 @@ POINT = "POINT"
454
455
  START = "START"
455
456
  STOP = "STOP"
456
457
 
457
- PLAYER1, PLAYER2 = "1", "2"
458
+ PLAYER1 = "1"
459
+ PLAYER2 = "2"
458
460
  ALL_PLAYERS = [str(x + 1) for x in range(N_PLAYER)]
459
461
 
460
462
  VISUALIZE_SPECTROGRAM = "visualize_spectrogram"
@@ -701,6 +703,7 @@ EMPTY_PROJECT = {
701
703
  ETHOGRAM: {},
702
704
  OBSERVATIONS: {},
703
705
  BEHAVIORAL_CATEGORIES: [],
706
+ BEHAVIORAL_CATEGORIES_CONF: {},
704
707
  INDEPENDENT_VARIABLES: {},
705
708
  CODING_MAP: {},
706
709
  BEHAVIORS_CODING_MAP: [],
@@ -114,7 +114,6 @@ from . import cmd_arguments
114
114
 
115
115
  from . import core_qrc
116
116
  from .core_ui import Ui_MainWindow
117
- import exifread
118
117
  from . import config as cfg
119
118
  from . import video_operations
120
119
 
@@ -4317,12 +4316,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4317
4316
  if not sys.platform.startswith(cfg.MACOS_CODE):
4318
4317
  if self.dw_player[0].player.time_pos is not None:
4319
4318
  for n_player in range(1, len(self.dw_player)):
4320
- print(f"{n_player=}")
4321
-
4322
4319
  ct = self.getLaps(n_player=n_player)
4323
4320
 
4324
- print(f"{ct=}")
4325
-
4326
4321
  # sync players 2..8 if time diff >= 1 s
4327
4322
  if (
4328
4323
  abs(ct0 - (ct + dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)])))
@@ -88,7 +88,7 @@ def MessageDialog(title: str, text: str, buttons: tuple) -> str:
88
88
  for button in buttons:
89
89
  message.addButton(button, QMessageBox.YesRole)
90
90
 
91
- message.setWindowFlags(Qt.WindowStaysOnTopHint)
91
+ message.setWindowFlags(message.windowFlags() | Qt.WindowStaysOnTopHint)
92
92
  message.exec()
93
93
  return message.clickedButton().text()
94
94
 
@@ -20,11 +20,13 @@ This file is part of BORIS.
20
20
 
21
21
  """
22
22
 
23
+ from PySide6.QtWidgets import QFileDialog
24
+
23
25
  from . import config as cfg
24
26
  from . import utilities as util
25
27
  from . import dialog
26
28
  from . import project_functions
27
- from PySide6.QtWidgets import QFileDialog
29
+ from . import utilities as util
28
30
 
29
31
 
30
32
  def get_info(self) -> None:
@@ -38,17 +40,17 @@ def get_info(self) -> None:
38
40
  if "error" in r:
39
41
  ffmpeg_output = f"File path: {media_full_path}<br><br>{r['error']}<br><br>"
40
42
  else:
41
- ffmpeg_output = f"<br><b>{r['analysis_program'] } analysis</b><br>"
43
+ ffmpeg_output = f"<br><b>{r['analysis_program']} analysis</b><br>"
42
44
 
43
45
  ffmpeg_output += (
44
46
  f"File path: <b>{media_full_path}</b><br><br>"
45
47
  f"Duration: {r['duration']} seconds ({util.convertTime(self.timeFormat, r['duration'])})<br>"
48
+ f"FPS: {r['fps']}<br>"
49
+ f"Resolution: {r['resolution']} pixels<br>"
46
50
  f"Format long name: {r.get('format_long_name', cfg.NA)}<br>"
47
51
  f"Creation time: {r.get('creation_time', cfg.NA)}<br>"
48
- f"Resolution: {r['resolution']}<br>"
49
52
  f"Number of frames: {r['frames_number']}<br>"
50
53
  f"Bitrate: {util.smart_size_format(r['bitrate'])} <br>"
51
- f"FPS: {r['fps']}<br>"
52
54
  f"Has video: {r['has_video']}<br>"
53
55
  f"Has audio: {r['has_audio']}<br>"
54
56
  f"File size: {util.smart_size_format(r.get('file size', cfg.NA))}<br>"
@@ -70,6 +72,12 @@ def get_info(self) -> None:
70
72
 
71
73
  mpv_output = (
72
74
  "<b>MPV information</b><br>"
75
+ f"Duration: {dw.player.duration} seconds ({util.seconds2time(dw.player.duration)})<br>"
76
+ # "Position: {} %<br>"
77
+ f"FPS: {dw.player.container_fps}<br>"
78
+ # "Rate: {}<br>"
79
+ f"Resolution: {dw.player.width}x{dw.player.height} pixels<br>"
80
+ # "Scale: {}<br>"
73
81
  f"Video format: {dw.player.video_format}<br>"
74
82
  # "State: {}<br>"
75
83
  # "Media Resource Location: {}<br>"
@@ -77,12 +85,6 @@ def get_info(self) -> None:
77
85
  # "Track: {}/{}<br>"
78
86
  f"Number of media in media list: {dw.player.playlist_count}<br>"
79
87
  f"Current time position: {dw.player.time_pos}<br>"
80
- f"Duration: {dw.player.duration}<br>"
81
- # "Position: {} %<br>"
82
- f"FPS: {dw.player.container_fps}<br>"
83
- # "Rate: {}<br>"
84
- f"Video size: {dw.player.width}x{dw.player.height}<br>"
85
- # "Scale: {}<br>"
86
88
  f"Aspect ratio: {round(dw.player.width / dw.player.height, 3)}<br>"
87
89
  # "is seekable? {}<br>"
88
90
  # "has_vout? {}<br>"
@@ -2406,15 +2406,15 @@ def event2media_file_name(observation: dict, timestamp: dec) -> Optional[str]:
2406
2406
  """
2407
2407
 
2408
2408
  cumul_media_durations: list = [dec(0)]
2409
- for media_file in observation[cfg.FILE]["1"]:
2409
+ for media_file in observation[cfg.FILE][cfg.PLAYER1]:
2410
2410
  media_duration = dec(str(observation[cfg.MEDIA_INFO][cfg.LENGTH][media_file]))
2411
- cumul_media_durations.append(cumul_media_durations[-1] + media_duration)
2411
+ cumul_media_durations.append(round(cumul_media_durations[-1] + media_duration, 3))
2412
2412
 
2413
2413
  cumul_media_durations.remove(dec(0))
2414
2414
 
2415
2415
  # test if timestamp is at end of last media
2416
2416
  if timestamp == cumul_media_durations[-1]:
2417
- player_idx = len(observation[cfg.FILE]["1"]) - 1
2417
+ player_idx = len(observation[cfg.FILE][cfg.PLAYER1]) - 1
2418
2418
  else:
2419
2419
  player_idx = -1
2420
2420
  for idx, value in enumerate(cumul_media_durations):
@@ -2424,7 +2424,7 @@ def event2media_file_name(observation: dict, timestamp: dec) -> Optional[str]:
2424
2424
  break
2425
2425
 
2426
2426
  if player_idx != -1:
2427
- video_file_name = observation[cfg.FILE]["1"][player_idx]
2427
+ video_file_name = observation[cfg.FILE][cfg.PLAYER1][player_idx]
2428
2428
  else:
2429
2429
  video_file_name = None
2430
2430
 
@@ -82,12 +82,12 @@ class BehavioralCategories(QDialog):
82
82
  # add categories
83
83
  self.lw.setColumnCount(2)
84
84
  self.lw.setHorizontalHeaderLabels(["Category name", "Color"])
85
- # self.lw.verticalHeader().hide()
86
85
  self.lw.setEditTriggers(QAbstractItemView.NoEditTriggers)
87
86
 
88
- # self.lw.setSelectionBehavior(QAbstractItemView.SelectRows)
89
87
  self.lw.setSelectionMode(QAbstractItemView.SingleSelection)
90
88
 
89
+ behavioral_categories: list = []
90
+
91
91
  if cfg.BEHAVIORAL_CATEGORIES_CONF in pj:
92
92
  self.lw.setRowCount(len(pj.get(cfg.BEHAVIORAL_CATEGORIES_CONF, {})))
93
93
  behav_cat = pj.get(cfg.BEHAVIORAL_CATEGORIES_CONF, {})
@@ -95,7 +95,7 @@ class BehavioralCategories(QDialog):
95
95
  # name
96
96
  item = QTableWidgetItem()
97
97
  item.setText(behav_cat[key]["name"])
98
- # item.setFlags(Qt.ItemIsEnabled)
98
+ behavioral_categories.append(behav_cat[key]["name"])
99
99
  self.lw.setItem(idx, 0, item)
100
100
  # color
101
101
  item = QTableWidgetItem()
@@ -103,24 +103,21 @@ class BehavioralCategories(QDialog):
103
103
  if behav_cat[key].get(cfg.COLOR, ""):
104
104
  item.setBackground(QColor(behav_cat[key].get(cfg.COLOR, "")))
105
105
  else:
106
- # item.setBackground(QColor(230, 230, 230))
107
106
  item.setBackground(self.not_editable_column_color())
108
- # item.setFlags(Qt.ItemIsEnabled)
109
107
  self.lw.setItem(idx, 1, item)
110
108
  else:
111
109
  self.lw.setRowCount(len(pj.get(cfg.BEHAVIORAL_CATEGORIES, [])))
112
110
  for idx, category in enumerate(sorted(pj.get(cfg.BEHAVIORAL_CATEGORIES, []))):
111
+ # name
113
112
  item = QTableWidgetItem()
114
113
  item.setText(category)
115
- # item.setFlags(Qt.ItemIsEnabled)
114
+ behavioral_categories.append(category)
116
115
  self.lw.setItem(idx, 0, item)
117
-
116
+ # color
118
117
  item = QTableWidgetItem()
119
118
  item.setText("")
120
- # item.setFlags(Qt.ItemIsEnabled)
121
- self.lw.setItem(idx, 1, item)
122
119
 
123
- # self.lw.addItem(QListWidgetItem(category))
120
+ self.lw.setItem(idx, 1, item)
124
121
 
125
122
  self.vbox.addWidget(self.lw)
126
123
 
@@ -148,6 +145,41 @@ class BehavioralCategories(QDialog):
148
145
 
149
146
  self.setLayout(self.vbox)
150
147
 
148
+ # check if behavioral categories are present in events
149
+ behavioral_categories_in_ethogram = set(
150
+ sorted([pj[cfg.ETHOGRAM][idx].get(cfg.BEHAVIOR_CATEGORY, "") for idx in pj.get(cfg.ETHOGRAM, {})])
151
+ )
152
+
153
+ if behavioral_categories_in_ethogram.difference(set(behavioral_categories)):
154
+ if (
155
+ dialog.MessageDialog(
156
+ cfg.programName,
157
+ (
158
+ "They are behavioral categories that are present in ethogram but not defined.<br>"
159
+ f"{behavioral_categories_in_ethogram.difference(set(behavioral_categories))}<br>"
160
+ "<br>"
161
+ "Do you want to add them in the behavioral categories list?"
162
+ ),
163
+ [cfg.YES, cfg.NO],
164
+ )
165
+ == cfg.YES
166
+ ):
167
+ # add behavioral categories present in ethogram in behavioal categories list
168
+ rc = self.lw.rowCount()
169
+ self.lw.setRowCount(rc + len(behavioral_categories_in_ethogram.difference(set(behavioral_categories))))
170
+ for idx, category in enumerate(sorted(list(behavioral_categories_in_ethogram.difference(set(behavioral_categories))))):
171
+ print(category)
172
+ # name
173
+ item = QTableWidgetItem()
174
+ item.setText(category)
175
+ # behavioral_categories.append(category)
176
+ self.lw.setItem(rc + idx, 0, item)
177
+ # color
178
+ item = QTableWidgetItem()
179
+ item.setText("")
180
+
181
+ self.lw.setItem(rc + idx, 1, item)
182
+
151
183
  def not_editable_column_color(self):
152
184
  """
153
185
  return a color for the not editable column
@@ -663,7 +695,7 @@ class projectDialog(QDialog, Ui_dlgProject):
663
695
  QMessageBox.warning(
664
696
  self,
665
697
  cfg.programName,
666
- ("The following behavior{} are not defined in the ethogram:<br>" "{}").format(
698
+ ("The following behavior{} are not defined in the ethogram:<br>{}").format(
667
699
  "s" if len(bcm_code_not_found) > 1 else "", ",".join(bcm_code_not_found)
668
700
  ),
669
701
  )
@@ -733,7 +765,7 @@ class projectDialog(QDialog, Ui_dlgProject):
733
765
  behavioral categories manager
734
766
  """
735
767
 
736
- bc = BehavioralCategories(self.pj) # self.config_param.get(cfg.DARK_MODE, cfg.DEFAULT_FRAME_MODE)
768
+ bc = BehavioralCategories(self.pj)
737
769
 
738
770
  if bc.exec_():
739
771
  self.pj[cfg.BEHAVIORAL_CATEGORIES] = []
@@ -1377,7 +1409,7 @@ class projectDialog(QDialog, Ui_dlgProject):
1377
1409
  # let user select a coding maop
1378
1410
  file_name, _ = QFileDialog().getOpenFileName(
1379
1411
  self,
1380
- "Select a modifier coding map for " f"{self.twBehaviors.item(row, cfg.behavioursFields['code']).text()} behavior",
1412
+ f"Select a modifier coding map for {self.twBehaviors.item(row, cfg.behavioursFields['code']).text()} behavior",
1381
1413
  "",
1382
1414
  "BORIS map files (*.boris_map);;All files (*)",
1383
1415
  )
@@ -1759,24 +1791,40 @@ class projectDialog(QDialog, Ui_dlgProject):
1759
1791
  return {cfg.CANCEL: True}
1760
1792
 
1761
1793
  # check if behavior belong to category that is not in categories list
1762
- behavior_category: list = []
1794
+ missing_behavior_category: list = []
1763
1795
  for idx in checked_ethogram:
1764
1796
  if cfg.BEHAVIOR_CATEGORY in checked_ethogram[idx]:
1765
1797
  if checked_ethogram[idx][cfg.BEHAVIOR_CATEGORY]:
1766
1798
  if checked_ethogram[idx][cfg.BEHAVIOR_CATEGORY] not in self.pj[cfg.BEHAVIORAL_CATEGORIES]:
1767
- behavior_category.append((checked_ethogram[idx][cfg.BEHAVIOR_CODE], checked_ethogram[idx][cfg.BEHAVIOR_CATEGORY]))
1768
- if behavior_category:
1799
+ missing_behavior_category.append(
1800
+ (checked_ethogram[idx][cfg.BEHAVIOR_CODE], checked_ethogram[idx][cfg.BEHAVIOR_CATEGORY])
1801
+ )
1802
+ if missing_behavior_category:
1769
1803
  response = dialog.MessageDialog(
1770
1804
  f"{cfg.programName} - Behavioral categories",
1771
1805
  (
1772
- "The behavioral categorie(s) "
1773
- f"{', '.join(set(['<b>' + x[1] + '</b>' + ' (used with <b>' + x[0] + '</b>)' for x in behavior_category]))} "
1774
- "are no more defined in behavioral categories list"
1806
+ "The behavioral category/ies<br> "
1807
+ f"{', '.join(set(['<b>' + x[1] + '</b>' + ' (used with <b>' + x[0] + '</b>)<br>' for x in missing_behavior_category]))} "
1808
+ "are not defined in behavioral categories list.<br>"
1775
1809
  ),
1776
- ["Add behavioral category/ies", "Ignore", cfg.CANCEL],
1810
+ ["Add behavioral category/ies", cfg.IGNORE, cfg.CANCEL],
1777
1811
  )
1778
1812
  if response == "Add behavioral category/ies":
1779
- [self.pj[cfg.BEHAVIORAL_CATEGORIES].append(x1) for x1 in set(x[1] for x in behavior_category)]
1813
+ if cfg.BEHAVIORAL_CATEGORIES_CONF not in self.pj:
1814
+ self.pj[cfg.BEHAVIORAL_CATEGORIES_CONF] = {}
1815
+ for x1 in set(x[1] for x in missing_behavior_category):
1816
+ self.pj[cfg.BEHAVIORAL_CATEGORIES].append(x1)
1817
+
1818
+ if self.pj[cfg.BEHAVIORAL_CATEGORIES_CONF]:
1819
+ index = str(max([int(k) for k in self.pj[cfg.BEHAVIORAL_CATEGORIES_CONF]]) + 1)
1820
+ else:
1821
+ index = "0"
1822
+
1823
+ self.pj[cfg.BEHAVIORAL_CATEGORIES_CONF][index] = {
1824
+ "name": x1,
1825
+ cfg.COLOR: "",
1826
+ }
1827
+
1780
1828
  if response == cfg.CANCEL:
1781
1829
  return {cfg.CANCEL: True}
1782
1830
 
@@ -89,9 +89,10 @@ def export_ethogram(self) -> None:
89
89
  return
90
90
  pj = dict(cfg.EMPTY_PROJECT)
91
91
  pj[cfg.ETHOGRAM] = dict(r)
92
- # behavioral categories
93
92
 
93
+ # behavioral categories
94
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, {}))
95
96
 
96
97
  # project file indentation
97
98
  file_indentation = self.config_param.get(cfg.PROJECT_FILE_INDENTATION, cfg.PROJECT_FILE_INDENTATION_DEFAULT_VALUE)
@@ -309,6 +310,7 @@ def import_ethogram_from_dict(self, project: dict):
309
310
  """
310
311
  # import behavioral_categories
311
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, {}))
312
314
 
313
315
  # configuration of behaviours
314
316
  if not (cfg.ETHOGRAM in project and project[cfg.ETHOGRAM]):
@@ -21,7 +21,6 @@ Copyright 2012-2025 Olivier Friard
21
21
 
22
22
  import csv
23
23
  import datetime as dt
24
- import hashlib
25
24
  import json
26
25
  import logging
27
26
  import math
@@ -20,5 +20,5 @@ This file is part of BORIS.
20
20
 
21
21
  """
22
22
 
23
- __version__ = "9.3.2"
24
- __version_date__ = "2025-04-14"
23
+ __version__ = "9.3.3"
24
+ __version_date__ = "2025-05-07"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boris-behav-obs
3
- Version: 9.3.2
3
+ Version: 9.3.3
4
4
  Summary: BORIS - Behavioral Observation Research Interactive Software
5
5
  Author-email: Olivier Friard <olivier.friard@unito.it>
6
6
  License-Expression: GPL-3.0-only
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "boris-behav-obs"
3
- version = "9.3.2"
3
+ version = "9.3.3"
4
4
  description = "BORIS - Behavioral Observation Research Interactive Software"
5
5
  authors = [{ name="Olivier Friard", email="olivier.friard@unito.it" }]
6
6
  readme = "README.md"
@@ -54,7 +54,7 @@ line-length = 140
54
54
  exclude = ["*_ui.py", "mpv*"]
55
55
 
56
56
  [tool.bumpver]
57
- current_version = "9.3.2"
57
+ current_version = "9.3.3"
58
58
  version_pattern = "MAJOR.MINOR.PATCH"
59
59
 
60
60
 
@@ -564,7 +564,7 @@ class Test_txt2np_array(object):
564
564
  file_name="files/xxx", columns_str="4,6", substract_first_value="False", converters={}, column_converter={}
565
565
  )
566
566
  # print(r)
567
- assert r[0] == False
567
+ assert r[0] is False
568
568
  assert r[1] == "[Errno 2] No such file or directory: 'files/xxx'"
569
569
  assert list(r[2].shape) == [0]
570
570
 
@@ -576,7 +576,10 @@ class Test_txt2np_array(object):
576
576
  converters={},
577
577
  column_converter={},
578
578
  )
579
- assert r[0] == False
579
+
580
+ print(r)
581
+
582
+ assert r[0] is False
580
583
  assert r[1] == "could not convert string to float: '14:38:58'"
581
584
  assert list(r[2].shape) == [0]
582
585
 
@@ -594,7 +597,7 @@ class Test_txt2np_array(object):
594
597
  },
595
598
  column_converter={4: "HHMMSS_2_seconds"},
596
599
  )
597
- assert r[0] == True
600
+ assert r[0] is True
598
601
  assert r[1] == ""
599
602
  assert r[2][0, 0] == 52738.0
600
603
  assert r[2][1, 0] == 52740.0