boris-behav-obs 9.3__tar.gz → 9.3.2__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 (124) hide show
  1. boris_behav_obs-9.3.2/PKG-INFO +132 -0
  2. boris_behav_obs-9.3.2/boris/analysis_plugins/_latency.py +59 -0
  3. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/behav_coding_map_creator.py +2 -6
  4. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/core.py +25 -59
  5. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/dialog.py +8 -3
  6. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/gui_utilities.py +30 -4
  7. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/modifier_coding_map_creator.py +6 -21
  8. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/observation.py +25 -20
  9. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/observation_operations.py +2 -2
  10. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/player_dock_widget.py +7 -5
  11. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/plugins.py +4 -0
  12. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/utilities.py +35 -54
  13. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/version.py +2 -2
  14. boris_behav_obs-9.3.2/boris_behav_obs.egg-info/PKG-INFO +132 -0
  15. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris_behav_obs.egg-info/SOURCES.txt +1 -1
  16. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/pyproject.toml +8 -3
  17. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/tests/test_utilities.py +99 -67
  18. boris_behav_obs-9.3/PKG-INFO +0 -33
  19. boris_behav_obs-9.3/boris/1.py +0 -45
  20. boris_behav_obs-9.3/boris_behav_obs.egg-info/PKG-INFO +0 -33
  21. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/LICENSE.TXT +0 -0
  22. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/MANIFEST.in +0 -0
  23. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/README.TXT +0 -0
  24. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/README.md +0 -0
  25. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/__init__.py +0 -0
  26. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/__main__.py +0 -0
  27. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/about.py +0 -0
  28. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/add_modifier.py +0 -0
  29. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/add_modifier_ui.py +0 -0
  30. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/advanced_event_filtering.py +0 -0
  31. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/analysis_plugins/__init__.py +0 -0
  32. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/analysis_plugins/number_of_occurences.py +0 -0
  33. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/analysis_plugins/number_of_occurences_by_independent_variable.py +0 -0
  34. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/analysis_plugins/time_budget.py +0 -0
  35. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/behavior_binary_table.py +0 -0
  36. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/behaviors_coding_map.py +0 -0
  37. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/boris_cli.py +0 -0
  38. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/cmd_arguments.py +0 -0
  39. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/coding_pad.py +0 -0
  40. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/config.py +0 -0
  41. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/config_file.py +0 -0
  42. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/connections.py +0 -0
  43. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/converters.py +0 -0
  44. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/converters_ui.py +0 -0
  45. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/cooccurence.py +0 -0
  46. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/core_qrc.py +0 -0
  47. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/core_ui.py +0 -0
  48. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/db_functions.py +0 -0
  49. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/dev.py +0 -0
  50. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/duration_widget.py +0 -0
  51. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/edit_event.py +0 -0
  52. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/edit_event_ui.py +0 -0
  53. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/event_operations.py +0 -0
  54. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/events_cursor.py +0 -0
  55. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/events_snapshots.py +0 -0
  56. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/exclusion_matrix.py +0 -0
  57. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/export_events.py +0 -0
  58. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/export_observation.py +0 -0
  59. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/external_processes.py +0 -0
  60. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/geometric_measurement.py +0 -0
  61. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/image_overlay.py +0 -0
  62. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/import_observations.py +0 -0
  63. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/irr.py +0 -0
  64. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/latency.py +0 -0
  65. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/measurement_widget.py +0 -0
  66. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/media_file.py +0 -0
  67. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/menu_options.py +0 -0
  68. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/modifiers_coding_map.py +0 -0
  69. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/mpv-1.0.3.py +0 -0
  70. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/mpv.py +0 -0
  71. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/mpv2.py +0 -0
  72. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/observation_ui.py +0 -0
  73. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/observations_list.py +0 -0
  74. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/otx_parser.py +0 -0
  75. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/param_panel.py +0 -0
  76. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/param_panel_ui.py +0 -0
  77. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/plot_data_module.py +0 -0
  78. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/plot_events.py +0 -0
  79. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/plot_events_rt.py +0 -0
  80. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/plot_spectrogram_rt.py +0 -0
  81. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/plot_waveform_rt.py +0 -0
  82. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/portion/__init__.py +0 -0
  83. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/portion/const.py +0 -0
  84. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/portion/dict.py +0 -0
  85. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/portion/func.py +0 -0
  86. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/portion/interval.py +0 -0
  87. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/portion/io.py +0 -0
  88. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/preferences.py +0 -0
  89. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/preferences_ui.py +0 -0
  90. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/project.py +0 -0
  91. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/project_functions.py +0 -0
  92. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/project_import_export.py +0 -0
  93. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/project_ui.py +0 -0
  94. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/qrc_boris.py +0 -0
  95. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/qrc_boris5.py +0 -0
  96. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/select_modifiers.py +0 -0
  97. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/select_observations.py +0 -0
  98. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/select_subj_behav.py +0 -0
  99. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/state_events.py +0 -0
  100. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/subjects_pad.py +0 -0
  101. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/synthetic_time_budget.py +0 -0
  102. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/time_budget_functions.py +0 -0
  103. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/time_budget_widget.py +0 -0
  104. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/transitions.py +0 -0
  105. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/video_equalizer.py +0 -0
  106. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/video_equalizer_ui.py +0 -0
  107. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/video_operations.py +0 -0
  108. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/view_df.py +0 -0
  109. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/view_df_ui.py +0 -0
  110. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris/write_event.py +0 -0
  111. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris_behav_obs.egg-info/dependency_links.txt +0 -0
  112. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris_behav_obs.egg-info/entry_points.txt +0 -0
  113. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris_behav_obs.egg-info/requires.txt +0 -0
  114. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/boris_behav_obs.egg-info/top_level.txt +0 -0
  115. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/setup.cfg +0 -0
  116. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/tests/test_db_functions.py +0 -0
  117. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/tests/test_export_observation.py +0 -0
  118. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/tests/test_irr.py +0 -0
  119. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/tests/test_observation_gui.py +0 -0
  120. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/tests/test_otx_parser.py +0 -0
  121. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/tests/test_preferences_gui.py +0 -0
  122. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/tests/test_project_functions.py +0 -0
  123. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/tests/test_time_budget.py +0 -0
  124. {boris_behav_obs-9.3 → boris_behav_obs-9.3.2}/tests/test_utilities2.py +0 -0
@@ -0,0 +1,132 @@
1
+ Metadata-Version: 2.4
2
+ Name: boris-behav-obs
3
+ Version: 9.3.2
4
+ Summary: BORIS - Behavioral Observation Research Interactive Software
5
+ Author-email: Olivier Friard <olivier.friard@unito.it>
6
+ License-Expression: GPL-3.0-only
7
+ Project-URL: Homepage, http://www.boris.unito.it
8
+ Project-URL: Documentation, https://boris.readthedocs.io/en/latest/
9
+ Project-URL: Change_log, https://github.com/olivierfriard/BORIS/wiki/BORIS-change-log-v.8
10
+ Project-URL: Source_code, https://github.com/olivierfriard/BORIS
11
+ Project-URL: Issues, https://github.com/olivierfriard/BORIS/issues
12
+ Classifier: Topic :: Scientific/Engineering
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Intended Audience :: Education
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Topic :: Scientific/Engineering
18
+ Requires-Python: >=3.12
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE.TXT
21
+ Requires-Dist: exifread>=3.0.0
22
+ Requires-Dist: numpy>=1.26.4
23
+ Requires-Dist: matplotlib>=3.3.3
24
+ Requires-Dist: pandas>=2.2.2
25
+ Requires-Dist: tablib[cli,html,ods,pandas,xls,xlsx]>=3
26
+ Requires-Dist: pyreadr
27
+ Requires-Dist: pyside6==6.8.0.2
28
+ Requires-Dist: hachoir>=3.3.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: ruff; extra == "dev"
31
+ Requires-Dist: pytest; extra == "dev"
32
+ Requires-Dist: pytest-cov; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ BORIS (Behavioral Observation Research Interactive Software)
36
+ ===============================================================
37
+
38
+
39
+ ![BORIS logo](https://github.com/olivierfriard/BORIS/blob/master/boris/icons/logo_boris.png?raw=true)
40
+
41
+ BORIS is an easy-to-use event logging software for video/audio coding or live observations.
42
+
43
+ BORIS is a free and open-source software available for GNU/Linux and Windows.
44
+ You can not longer run BORIS natively on MacOS (since v.8). Some alternatives to run the last version of BORIS are available, see [BORIS on MacOS](https://www.boris.unito.it/download_mac).
45
+
46
+ It provides also some analysis tools like time budget and some plotting functions.
47
+
48
+ The BORIS paper has more than [![BORIS citations counter](http://penelope.unito.it/friard/boris_scopus_citations.png) citations](https://www.boris.unito.it/citations) in peer-reviewed scientific publications.
49
+
50
+
51
+
52
+
53
+ See the official [BORIS web site](https://www.boris.unito.it).
54
+
55
+ [![Python web site](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org)
56
+ ![Python versions](https://img.shields.io/pypi/pyversions/boris-behav-obs)
57
+ ![BORIS license](https://img.shields.io/pypi/l/boris-behav-obs)
58
+ [![Number of downloads](https://static.pepy.tech/personalized-badge/boris-behav-obs?period=total&units=international_system&left_color=black&right_color=orange&left_text=Downloads)](https://pepy.tech/project/boris-behav-obs)
59
+ ![commit-activity](https://img.shields.io/github/commit-activity/m/olivierfriard/BORIS)
60
+ [![PyPI version](https://img.shields.io/pypi/v/boris-behav-obs.svg)](https://pypi.org/project/boris-behav-obs/)
61
+ ![BORIS scopus citations badge](http://penelope.unito.it/friard/boris_scopus_citations.svg)
62
+
63
+
64
+
65
+
66
+ # Documentation
67
+
68
+
69
+
70
+ The [user guide](https://www.boris.unito.it/user_guide/) provides a good starting point for learning how to use BORIS.
71
+
72
+ Some [video tutorials](https://www.boris.unito.it/video_tutorials/) are available.
73
+
74
+
75
+
76
+
77
+
78
+ # Bug reports and feature requests
79
+
80
+
81
+ To search for bugs, report them or request a feature, please use the github tracker:
82
+ https://github.com/olivierfriard/BORIS/issues
83
+
84
+
85
+
86
+
87
+
88
+ # Citing BORIS
89
+
90
+
91
+ Please acknowledge and cite the use of this software and its authors when
92
+ results are used in publications or published elsewhere. You can use the
93
+ following BibTex entry
94
+
95
+ ```
96
+ @article {MEE3:MEE312584,
97
+ author = {Friard, Olivier and Gamba, Marco},
98
+ title = {BORIS: a free, versatile open-source event-logging software for video/audio coding and live observations},
99
+ journal = {Methods in Ecology and Evolution},
100
+ issn = {2041-210X},
101
+ url = {http://dx.doi.org/10.1111/2041-210X.12584},
102
+ doi = {10.1111/2041-210X.12584},
103
+ pages = {1324--1330},
104
+ year = {2016},
105
+ }
106
+ ```
107
+
108
+ You can also send us a nice postcard! See the [user testimonials](https://www.boris.unito.it/postcards).
109
+
110
+
111
+
112
+
113
+
114
+
115
+
116
+
117
+ # Licence
118
+
119
+
120
+ This program is distributed in the hope that it will be useful,
121
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
122
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
123
+ GNU General Public License for more details.
124
+
125
+
126
+ Distributed with a [GPL v.3 license](LICENSE.TXT).
127
+
128
+ Copyright (C) 2012-2024 Olivier Friard
129
+
130
+
131
+
132
+
@@ -0,0 +1,59 @@
1
+ """
2
+ BORIS plugin
3
+
4
+ number of occurences of behaviors
5
+ """
6
+
7
+ import pandas as pd
8
+
9
+ __version__ = "0.0.1"
10
+ __version_date__ = "2025-04-10"
11
+ __plugin_name__ = "Behavior latency"
12
+ __author__ = "Olivier Friard - University of Torino - Italy"
13
+
14
+
15
+ import itertools
16
+
17
+
18
+ def run(df: pd.DataFrame):
19
+ """
20
+ Latency of a behavior after another.
21
+ """
22
+
23
+ df["start_time"] = pd.to_datetime(df["Start (s)"])
24
+ df["end_time"] = pd.to_datetime(df["Stop (s)"])
25
+
26
+ latency_by_subject: dict = {}
27
+
28
+ for subject, group in df.groupby("subject"):
29
+ behaviors = group["behavior"].tolist()
30
+ # combinations = []
31
+ # Utiliser itertools pour créer des combinaisons 2 à 2 des comportements
32
+ for comb in itertools.combinations(behaviors, 2):
33
+ # combinations.append(comb)
34
+
35
+ last_A_end_time = None
36
+
37
+ # Liste pour stocker les latences de chaque sujet
38
+ subject_latency = []
39
+
40
+ for index, row in group.iterrows():
41
+ if row["behavior"] == comb[0]:
42
+ # Si on rencontre un comportement A, on réinitialise le temps de fin du comportement A
43
+ last_A_end_time = row["end_time"]
44
+ subject_latency.append(None) # Pas de latence pour A
45
+ elif row["behavior"] == comb[1] and last_A_end_time is not None:
46
+ # Si on rencontre un comportement B et qu'on a déjà vu un A avant
47
+ latency_time = row["start_time"] - last_A_end_time
48
+ subject_latency.append(latency_time)
49
+ else:
50
+ # Si on rencontre un B mais sans A avant
51
+ subject_latency.append(None)
52
+
53
+ # Ajout des latences calculées au DataFrame
54
+ df.loc[group.index, f"latency {comb[1]} after {comb[0]}"] = subject_latency
55
+
56
+ # Calcul de la latence totale ou moyenne par sujet
57
+ latency_by_subject[(subject, comb)] = df.groupby("subject")["latency"].agg(["sum", "mean"])
58
+
59
+ return str(latency_by_subject)
@@ -24,6 +24,7 @@ import binascii
24
24
  import io
25
25
  import json
26
26
  from pathlib import Path
27
+ import gui_utilities
27
28
 
28
29
  from PySide6.QtCore import QBuffer, QByteArray, QIODevice, QLineF, QPoint, Qt, Signal
29
30
  from PySide6.QtGui import QBrush, QColor, QIcon, QMouseEvent, QPen, QPixmap, QPolygonF, QAction
@@ -790,7 +791,6 @@ class BehaviorsMapCreatorWindow(QMainWindow):
790
791
 
791
792
  if not self.fileName:
792
793
  return
793
- """if os.path.splitext(self.fileName)[1] != ".behav_coding_map":"""
794
794
  if Path(self.fileName).suffix != ".behav_coding_map":
795
795
  self.fileName += ".behav_coding_map"
796
796
  self.saveMap()
@@ -1106,10 +1106,6 @@ if __name__ == "__main__":
1106
1106
  app = QApplication(sys.argv)
1107
1107
  window = BehaviorsMapCreatorWindow(["North zone", "East zone", "South zone", "West zone"])
1108
1108
  window.bcm_list = []
1109
- window.resize(cfg.CODING_MAP_RESIZE_W, cfg.CODING_MAP_RESIZE_H)
1110
- screen_geometry = app.primaryScreen().geometry()
1111
- center_x = (screen_geometry.width() - window.width()) // 2
1112
- center_y = (screen_geometry.height() - window.height()) // 2
1113
- window.move(center_x, center_y)
1109
+ gui_utilities.resize_center(app, window, cfg.CODING_MAP_RESIZE_W, cfg.CODING_MAP_RESIZE_H)
1114
1110
  window.show()
1115
1111
  sys.exit(app.exec())
@@ -135,8 +135,9 @@ __version__ = version.__version__
135
135
  __version_date__ = version.__version_date__
136
136
 
137
137
  # check minimal version of python
138
- if util.versiontuple(platform.python_version()) < util.versiontuple("3.8"):
139
- msg = f"BORIS requires Python 3.8+! You are using Python v. {platform.python_version()}\n"
138
+ MIN_PYTHON_VERSION = "3.12"
139
+ if util.versiontuple(platform.python_version()) < util.versiontuple(MIN_PYTHON_VERSION):
140
+ msg = f"BORIS requires Python {MIN_PYTHON_VERSION}+! You are using Python v. {platform.python_version()}\n"
140
141
  logging.critical(msg)
141
142
  sys.exit()
142
143
 
@@ -237,19 +238,6 @@ class TableModel(QAbstractTableModel):
237
238
  return self._data[row][event_idx]
238
239
 
239
240
 
240
- """
241
- class ButtonEventFilter(QObject):
242
- def eventFilter(self, obj, event):
243
- print("event filter")
244
- if isinstance(obj, QPushButton) and event.type() == QEvent.KeyPress:
245
- print("keypress")
246
- if event.key() in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Space):
247
- print("enter sapce")
248
- return False # Block the event
249
- return super().eventFilter(obj, event)
250
- """
251
-
252
-
253
241
  class MainWindow(QMainWindow, Ui_MainWindow):
254
242
  """
255
243
  Main BORIS window
@@ -301,7 +289,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
301
289
  ext_data_timer_list: list = []
302
290
  projectFileName: str = ""
303
291
  mediaTotalLength = None
304
- beep_every = 0
292
+ beep_every: float = 0.0
305
293
 
306
294
  plot_colors = cfg.BEHAVIORS_PLOT_COLORS
307
295
  behav_category_colors = cfg.CATEGORY_COLORS_LIST
@@ -315,19 +303,20 @@ class MainWindow(QMainWindow, Ui_MainWindow):
315
303
  fast = 10
316
304
 
317
305
  currentStates: dict = {}
318
- subject_name_index = {}
306
+ subject_name_index: dict = {}
319
307
  flag_slow = False
320
308
  play_rate: float = 1
321
309
  play_rate_step: float = 0.1
322
310
  currentSubject: str = "" # contains the current subject of observation
323
- coding_map_window_geometry = 0
324
311
 
325
312
  # FFmpeg
326
- memx, memy, mem_player = -1, -1, -1
313
+ memx = -1
314
+ memy = -1
315
+ mem_player = -1
327
316
 
328
317
  # path for ffmpeg/ffmpeg.exe program
329
- ffmpeg_bin = ""
330
- ffmpeg_cache_dir = ""
318
+ ffmpeg_bin: str = ""
319
+ ffmpeg_cache_dir: str = ""
331
320
 
332
321
  # dictionary for FPS storing
333
322
  fps = 0
@@ -390,9 +379,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
390
379
  super(MainWindow, self).__init__(parent)
391
380
  self.setupUi(self)
392
381
 
393
- # disable trigger with RETURN or SPACE keys
394
- """filter_obj = ButtonEventFilter()
395
- self.pb_live_obs.installEventFilter(filter_obj)"""
396
382
  self.pb_live_obs.setFocusPolicy(Qt.NoFocus)
397
383
 
398
384
  self.ffmpeg_bin = ffmpeg_bin
@@ -418,25 +404,18 @@ class MainWindow(QMainWindow, Ui_MainWindow):
418
404
  self.tb_export.setMenu(self.menu)
419
405
  """
420
406
 
421
- gui_utilities.set_icons(self, theme_mode=self.theme_mode())
407
+ gui_utilities.set_icons(self, theme_mode=gui_utilities.theme_mode())
422
408
 
423
409
  self.setWindowTitle(f"{cfg.programName} ({__version__})")
424
410
 
425
- self.w_obs_info.setVisible(False)
426
-
427
411
  self.lbLogoBoris.setPixmap(QPixmap(":/logo"))
428
-
429
412
  self.lbLogoBoris.setScaledContents(False)
430
413
  self.lbLogoBoris.setAlignment(Qt.AlignCenter)
431
414
 
432
- # self.lbLogoUnito.setPixmap(QPixmap(":/dbios_unito"))
433
- # self.lbLogoUnito.setScaledContents(False)
434
- # self.lbLogoUnito.setAlignment(Qt.AlignCenter)
435
-
436
415
  self.toolBar.setEnabled(True)
437
416
 
438
417
  # start with dock widget invisible
439
- for w in [self.dwEvents, self.dwEthogram, self.dwSubjects]:
418
+ for w in (self.w_obs_info, self.dwEvents, self.dwEthogram, self.dwSubjects):
440
419
  w.setVisible(False)
441
420
  w.keyPressEvent = self.keyPressEvent
442
421
 
@@ -475,15 +454,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
475
454
  self.lbTimeOffset.setMinimumWidth(160)
476
455
  self.statusbar.addPermanentWidget(self.lbTimeOffset)
477
456
 
478
- # play rate are now displayed in the main info widget
479
- """
480
- # SPEED
481
- self.lbSpeed = QLabel()
482
- self.lbSpeed.setFrameStyle(QFrame.StyledPanel)
483
- self.lbSpeed.setMinimumWidth(40)
484
- self.statusbar.addPermanentWidget(self.lbSpeed)
485
- """
486
-
487
457
  # set painter for tv_events to highlight current row
488
458
  delegate = self.CustomItemDelegate()
489
459
  self.tv_events.setItemDelegate(delegate)
@@ -499,14 +469,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
499
469
  plugins.load_plugins(self)
500
470
  plugins.add_plugins_to_menu(self)
501
471
 
502
- def theme_mode(self):
503
- """
504
- return the theme mode (dark or light) of the OS
505
- """
506
- palette = QApplication.instance().palette()
507
- color = palette.window().color()
508
- return "dark" if color.value() < 128 else "light" # Dark mode if the color value is less than 128
509
-
510
472
  class CustomItemDelegate(QStyledItemDelegate):
511
473
  def paint(self, painter, option, index):
512
474
  # Custom drawing logic here (overriding paint)
@@ -1506,7 +1468,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1506
1468
  # one media
1507
1469
  if self.dw_player[player].player.playlist_count == 1:
1508
1470
  if new_time < self.dw_player[player].player.duration:
1509
- self.dw_player[player].player.seek(new_time, "absolute+exact")
1471
+ new_time_float = round(float(new_time), 3)
1472
+
1473
+ self.dw_player[player].player.seek(new_time_float, "absolute+exact")
1510
1474
 
1511
1475
  if player == 0 and not self.user_move_slider:
1512
1476
  self.video_slider.setValue(
@@ -3835,7 +3799,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3835
3799
  if self.geometric_measurements_mode:
3836
3800
  geometric_measurement.redraw_measurements(self)
3837
3801
 
3838
- self.actionPlay.setIcon(QIcon(f":/play_{self.theme_mode()}"))
3802
+ self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
3839
3803
 
3840
3804
  def previous_frame(self) -> None:
3841
3805
  """
@@ -3859,7 +3823,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3859
3823
  if self.geometric_measurements_mode:
3860
3824
  geometric_measurement.redraw_measurements(self)
3861
3825
 
3862
- self.actionPlay.setIcon(QIcon(f":/play_{self.theme_mode()}"))
3826
+ self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
3863
3827
 
3864
3828
  def run_event_outside(self):
3865
3829
  """
@@ -4353,8 +4317,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4353
4317
  if not sys.platform.startswith(cfg.MACOS_CODE):
4354
4318
  if self.dw_player[0].player.time_pos is not None:
4355
4319
  for n_player in range(1, len(self.dw_player)):
4320
+ print(f"{n_player=}")
4321
+
4356
4322
  ct = self.getLaps(n_player=n_player)
4357
4323
 
4324
+ print(f"{ct=}")
4325
+
4358
4326
  # sync players 2..8 if time diff >= 1 s
4359
4327
  if (
4360
4328
  abs(ct0 - (ct + dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)])))
@@ -4463,7 +4431,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4463
4431
  for data_timer in self.ext_data_timer_list:
4464
4432
  data_timer.stop()
4465
4433
 
4466
- self.actionPlay.setIcon(QIcon(f":/play_{self.theme_mode()}"))
4434
+ self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
4467
4435
 
4468
4436
  if msg:
4469
4437
  self.lb_current_media_time.setText(msg)
@@ -5634,7 +5602,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5634
5602
  for data_timer in self.ext_data_timer_list:
5635
5603
  data_timer.start()
5636
5604
 
5637
- self.actionPlay.setIcon(QIcon(f":/pause_{self.theme_mode()}"))
5605
+ self.actionPlay.setIcon(QIcon(f":/pause_{gui_utilities.theme_mode()}"))
5638
5606
  self.actionPlay.setText("Pause")
5639
5607
 
5640
5608
  return True
@@ -5668,7 +5636,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5668
5636
  for idx in self.plot_data:
5669
5637
  self.timer_plot_data_out(self.plot_data[idx])
5670
5638
 
5671
- self.actionPlay.setIcon(QIcon(f":/play_{self.theme_mode()}"))
5639
+ self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
5672
5640
  self.actionPlay.setText("Play")
5673
5641
 
5674
5642
  def play_activated(self):
@@ -5823,8 +5791,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5823
5791
 
5824
5792
 
5825
5793
  def main():
5826
- # QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
5827
-
5828
5794
  app = QApplication(sys.argv)
5829
5795
  app.setStyle("Fusion")
5830
5796
 
@@ -5917,7 +5883,7 @@ def main():
5917
5883
  results.show()
5918
5884
 
5919
5885
  window.show()
5920
- window.raise_()
5886
+ window.raise_() # for overlapping widget (?)
5921
5887
 
5922
5888
  if observation_to_open and "error" not in pj:
5923
5889
  r = observation_operations.load_observation(window, obs_id=observation_to_open, mode=cfg.OBS_START)
@@ -71,7 +71,12 @@ from . import utilities as util
71
71
 
72
72
  def MessageDialog(title: str, text: str, buttons: tuple) -> str:
73
73
  """
74
- generic message dialog
74
+ show a generic message dialog and returns the text of the clicked button
75
+
76
+ Args:
77
+ title (str): Title of the dialog box
78
+ text (str): text of the dialog box
79
+ buttons (tuple): text for buttons
75
80
 
76
81
  Return
77
82
  str: text of the clicked button
@@ -83,8 +88,8 @@ def MessageDialog(title: str, text: str, buttons: tuple) -> str:
83
88
  for button in buttons:
84
89
  message.addButton(button, QMessageBox.YesRole)
85
90
 
86
- # message.setWindowFlags(Qt.WindowStaysOnTopHint)
87
- message.exec_()
91
+ message.setWindowFlags(Qt.WindowStaysOnTopHint)
92
+ message.exec()
88
93
  return message.clickedButton().text()
89
94
 
90
95
 
@@ -22,10 +22,19 @@ Copyright 2012-2025 Olivier Friard
22
22
  import pathlib as pl
23
23
  import logging
24
24
  from PySide6.QtCore import QSettings
25
- from PySide6.QtWidgets import QWidget
25
+ from PySide6.QtWidgets import QWidget, QApplication
26
26
  from PySide6.QtGui import QIcon
27
27
 
28
28
 
29
+ def theme_mode() -> str:
30
+ """
31
+ return the theme mode (dark or light) of the OS
32
+ """
33
+ palette = QApplication.instance().palette()
34
+ color = palette.window().color()
35
+ return "dark" if color.value() < 128 else "light" # Dark mode if the color value is less than 128
36
+
37
+
29
38
  def save_geometry(widget: QWidget, widget_name: str):
30
39
  """
31
40
  save window geometry in ini file
@@ -44,6 +53,7 @@ def restore_geometry(widget: QWidget, widget_name: str, default_width_height):
44
53
  """
45
54
  restore window geometry in ini file
46
55
  """
56
+
47
57
  def default_resize(widget, default_width_height):
48
58
  if default_width_height != (0, 0):
49
59
  try:
@@ -51,15 +61,14 @@ def restore_geometry(widget: QWidget, widget_name: str, default_width_height):
51
61
  except Exception:
52
62
  logging.warning("Error during restoring default")
53
63
 
54
-
55
- logging.debug(f'restore geometry function for {widget_name}')
64
+ logging.debug(f"restore geometry function for {widget_name}")
56
65
  try:
57
66
  ini_file_path = pl.Path.home() / pl.Path(".boris")
58
67
  if ini_file_path.is_file():
59
68
  settings = QSettings(str(ini_file_path), QSettings.IniFormat)
60
69
  print(settings.value(f"{widget_name} geometry"))
61
70
  widget.restoreGeometry(settings.value(f"{widget_name} geometry"))
62
- logging.debug(f'geometry restored for {widget_name} {settings.value(f"{widget_name} geometry")}')
71
+ logging.debug(f"geometry restored for {widget_name} {settings.value(f'{widget_name} geometry')}")
63
72
  else:
64
73
  default_resize(widget, default_width_height)
65
74
  except Exception:
@@ -108,3 +117,20 @@ def set_icons(self, theme_mode: str) -> None:
108
117
  self.action_geometric_measurements.setIcon(QIcon(f":/measurement_{theme_mode}"))
109
118
  self.actionFind_in_current_obs.setIcon(QIcon(f":/find_{theme_mode}"))
110
119
  self.actionExplore_project.setIcon(QIcon(f":/explore_{theme_mode}"))
120
+
121
+
122
+ def resize_center(app, window, width: int, height: int) -> None:
123
+ """
124
+ resize and center window
125
+ """
126
+ window.resize(width, height)
127
+ screen_geometry = app.primaryScreen().geometry()
128
+ if window.height() > screen_geometry.height():
129
+ window.resize(window.width(), int(screen_geometry.height() * 0.8))
130
+ if window.width() > screen_geometry.width():
131
+ window.resize(screen_geometry.width(), window.height())
132
+ # center
133
+ center_x = (screen_geometry.width() - window.width()) // 2
134
+ center_y = (screen_geometry.height() - window.height()) // 2
135
+
136
+ window.move(center_x, center_y)
@@ -26,8 +26,9 @@ This file is part of BORIS.
26
26
  import binascii
27
27
  import io
28
28
  import json
29
- import os
29
+ from pathlib import Path
30
30
  import re
31
+ import gui_utilities
31
32
 
32
33
  from PySide6.QtCore import (
33
34
  Qt,
@@ -727,7 +728,8 @@ class ModifiersMapCreatorWindow(QMainWindow):
727
728
  self.fileName = fn
728
729
 
729
730
  if self.fileName:
730
- if os.path.splitext(self.fileName)[1] != ".boris_map":
731
+ # if os.path.splitext(self.fileName)[1] != ".boris_map":
732
+ if Path(self.fileName).suffix != ".boris_map":
731
733
  self.fileName += ".boris_map"
732
734
  self.saveMap()
733
735
 
@@ -744,7 +746,7 @@ class ModifiersMapCreatorWindow(QMainWindow):
744
746
  else:
745
747
  self.fileName = fn
746
748
 
747
- if self.fileName and os.path.splitext(self.fileName)[1] != ".boris_map":
749
+ if self.fileName and Path(self.fileName).suffix() != ".boris_map":
748
750
  self.fileName += ".boris_map"
749
751
 
750
752
  if self.fileName:
@@ -1006,25 +1008,8 @@ if __name__ == "__main__":
1006
1008
 
1007
1009
  app = QApplication(sys.argv)
1008
1010
  window = ModifiersMapCreatorWindow()
1009
- window.resize(800, 700)
1010
1011
 
1011
- print(f"{window.width()=}")
1012
- print(f"{window.height()=}")
1013
-
1014
- # Get the screen geometry (screen size and position)
1015
- screen_geometry = app.primaryScreen().geometry()
1016
-
1017
- print(f"{screen_geometry=}")
1018
-
1019
- # Calculate the center of the screen
1020
- center_x = (screen_geometry.width() - window.width()) // 2
1021
- center_y = (screen_geometry.height() - window.height()) // 2
1022
-
1023
- print(f"{center_x=}")
1024
- print(f"{center_y=}")
1025
-
1026
- # Move the widget to the center of the screen
1027
- window.move(center_x, center_y)
1012
+ gui_utilities.resize_center(app, window, cfg.CODING_MAP_RESIZE_W, cfg.CODING_MAP_RESIZE_H)
1028
1013
 
1029
1014
  window.show()
1030
1015
  sys.exit(app.exec())
@@ -1008,27 +1008,30 @@ class Observation(QDialog, Ui_Form):
1008
1008
 
1009
1009
  # check if observation id not empty
1010
1010
  if not self.leObservationId.text():
1011
- self.qm = QMessageBox()
1012
- self.qm.setIcon(QMessageBox.Critical)
1013
- self.qm.setText("The <b>observation id</b> is mandatory and must be unique.")
1014
- self.qm.exec_()
1011
+ QMessageBox.critical(
1012
+ self,
1013
+ cfg.programName,
1014
+ "The <b>observation id</b> is mandatory and must be unique.",
1015
+ )
1015
1016
  return False
1016
1017
 
1017
1018
  # check if observation_type
1018
1019
  if not any((self.rb_media_files.isChecked(), self.rb_live.isChecked(), self.rb_images.isChecked())):
1019
- self.qm = QMessageBox()
1020
- self.qm.setIcon(QMessageBox.Critical)
1021
- self.qm.setText("Choose an observation type.")
1022
- self.qm.exec_()
1020
+ QMessageBox.critical(
1021
+ self,
1022
+ cfg.programName,
1023
+ "Choose an observation type.",
1024
+ )
1023
1025
  return False
1024
1026
 
1025
1027
  # check if offset is correct
1026
1028
  if self.cb_time_offset.isChecked():
1027
1029
  if self.obs_time_offset.get_time() is None:
1028
- self.qm = QMessageBox()
1029
- self.qm.setIcon(QMessageBox.Critical)
1030
- self.qm.setText("Check the time offset value.")
1031
- self.qm.exec_()
1030
+ QMessageBox.critical(
1031
+ self,
1032
+ cfg.programName,
1033
+ "Check the time offset value.",
1034
+ )
1032
1035
  return False
1033
1036
 
1034
1037
  if self.rb_media_files.isChecked(): # observation based on media file(s)
@@ -1051,18 +1054,20 @@ class Observation(QDialog, Ui_Form):
1051
1054
 
1052
1055
  # check if player #1 is used
1053
1056
  if not players_list or min(players_list) > 1:
1054
- self.qm = QMessageBox()
1055
- self.qm.setIcon(QMessageBox.Critical)
1056
- self.qm.setText("A media file must be loaded in player #1")
1057
- self.qm.exec_()
1057
+ QMessageBox.critical(
1058
+ self,
1059
+ cfg.programName,
1060
+ "A media file must be loaded in player #1",
1061
+ )
1058
1062
  return False
1059
1063
 
1060
1064
  # check if players are used in crescent order
1061
1065
  if set(list(range(min(players_list), max(players_list) + 1))) != set(players_list):
1062
- self.qm = QMessageBox()
1063
- self.qm.setIcon(QMessageBox.Critical)
1064
- self.qm.setText("Some player are not used. Please reorganize your media files")
1065
- self.qm.exec_()
1066
+ QMessageBox.critical(
1067
+ self,
1068
+ cfg.programName,
1069
+ "Some player are not used. Please reorganize your media files",
1070
+ )
1066
1071
  return False
1067
1072
 
1068
1073
  # check if more media in player #1 and media in other players
@@ -888,7 +888,7 @@ def new_observation(self, mode: str = cfg.NEW, obsId: str = "") -> None:
888
888
  self.pj[cfg.OBSERVATIONS][obsId][cfg.CLOSE_BEHAVIORS_BETWEEN_VIDEOS]
889
889
  )
890
890
 
891
- rv = observationWindow.exec_()
891
+ rv = observationWindow.exec()
892
892
 
893
893
  # save geometry
894
894
  gui_utilities.save_geometry(observationWindow, "new observation")
@@ -2012,7 +2012,7 @@ def initialize_new_media_observation(self) -> bool:
2012
2012
  self.mpv_eof_reached_signal.connect(self.mpv_eof_reached)
2013
2013
  self.video_click_signal.connect(self.player_clicked)
2014
2014
 
2015
- self.actionPlay.setIcon(QIcon(f":/play_{self.theme_mode()}"))
2015
+ self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
2016
2016
 
2017
2017
  self.display_statusbar_info(self.observationId)
2018
2018