boris-behav-obs 8.16.5__py3-none-any.whl → 9.7.12__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 (126) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +28 -40
  4. boris/add_modifier.py +88 -80
  5. boris/add_modifier_ui.py +266 -144
  6. boris/advanced_event_filtering.py +23 -29
  7. boris/analysis_plugins/__init__.py +0 -0
  8. boris/analysis_plugins/_export_to_feral.py +225 -0
  9. boris/analysis_plugins/_latency.py +59 -0
  10. boris/analysis_plugins/irr_cohen_kappa.py +109 -0
  11. boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
  12. boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
  13. boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
  14. boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
  15. boris/analysis_plugins/number_of_occurences.py +22 -0
  16. boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
  17. boris/analysis_plugins/time_budget.py +61 -0
  18. boris/behav_coding_map_creator.py +235 -236
  19. boris/behavior_binary_table.py +33 -50
  20. boris/behaviors_coding_map.py +17 -18
  21. boris/boris_cli.py +6 -25
  22. boris/cmd_arguments.py +12 -1
  23. boris/coding_pad.py +19 -36
  24. boris/config.py +109 -50
  25. boris/config_file.py +58 -67
  26. boris/connections.py +105 -58
  27. boris/converters.py +13 -37
  28. boris/converters_ui.py +187 -110
  29. boris/cooccurence.py +250 -0
  30. boris/core.py +2174 -1303
  31. boris/core_qrc.py +15892 -10829
  32. boris/core_ui.py +941 -806
  33. boris/db_functions.py +17 -42
  34. boris/dev.py +27 -7
  35. boris/dialog.py +461 -242
  36. boris/duration_widget.py +9 -14
  37. boris/edit_event.py +61 -31
  38. boris/edit_event_ui.py +208 -97
  39. boris/event_operations.py +405 -281
  40. boris/events_cursor.py +25 -17
  41. boris/events_snapshots.py +36 -82
  42. boris/exclusion_matrix.py +4 -9
  43. boris/export_events.py +180 -203
  44. boris/export_observation.py +60 -73
  45. boris/external_processes.py +123 -98
  46. boris/geometric_measurement.py +427 -218
  47. boris/gui_utilities.py +91 -14
  48. boris/image_overlay.py +4 -4
  49. boris/import_observations.py +190 -98
  50. boris/ipc_mpv.py +325 -0
  51. boris/irr.py +20 -57
  52. boris/latency.py +31 -24
  53. boris/measurement_widget.py +14 -18
  54. boris/media_file.py +17 -19
  55. boris/menu_options.py +16 -6
  56. boris/modifier_coding_map_creator.py +1013 -0
  57. boris/modifiers_coding_map.py +7 -9
  58. boris/mpv2.py +128 -35
  59. boris/observation.py +501 -211
  60. boris/observation_operations.py +1037 -393
  61. boris/observation_ui.py +573 -363
  62. boris/observations_list.py +51 -58
  63. boris/otx_parser.py +74 -68
  64. boris/param_panel.py +45 -59
  65. boris/param_panel_ui.py +254 -138
  66. boris/player_dock_widget.py +91 -56
  67. boris/plot_data_module.py +20 -53
  68. boris/plot_events.py +56 -153
  69. boris/plot_events_rt.py +16 -30
  70. boris/plot_spectrogram_rt.py +83 -56
  71. boris/plot_waveform_rt.py +27 -49
  72. boris/plugins.py +468 -0
  73. boris/portion/__init__.py +18 -8
  74. boris/portion/const.py +35 -18
  75. boris/portion/dict.py +5 -5
  76. boris/portion/func.py +2 -2
  77. boris/portion/interval.py +21 -41
  78. boris/portion/io.py +41 -32
  79. boris/preferences.py +307 -123
  80. boris/preferences_ui.py +686 -227
  81. boris/project.py +294 -271
  82. boris/project_functions.py +626 -537
  83. boris/project_import_export.py +204 -213
  84. boris/project_ui.py +673 -441
  85. boris/qrc_boris.py +6 -3
  86. boris/qrc_boris5.py +6 -3
  87. boris/select_modifiers.py +62 -90
  88. boris/select_observations.py +19 -197
  89. boris/select_subj_behav.py +67 -39
  90. boris/state_events.py +51 -33
  91. boris/subjects_pad.py +7 -9
  92. boris/synthetic_time_budget.py +42 -26
  93. boris/time_budget_functions.py +169 -169
  94. boris/time_budget_widget.py +77 -89
  95. boris/transitions.py +41 -41
  96. boris/utilities.py +594 -226
  97. boris/version.py +3 -3
  98. boris/video_equalizer.py +16 -14
  99. boris/video_equalizer_ui.py +199 -130
  100. boris/video_operations.py +86 -28
  101. boris/view_df.py +104 -0
  102. boris/view_df_ui.py +75 -0
  103. boris/write_event.py +240 -136
  104. boris_behav_obs-9.7.12.dist-info/METADATA +139 -0
  105. boris_behav_obs-9.7.12.dist-info/RECORD +110 -0
  106. {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.12.dist-info}/WHEEL +1 -1
  107. boris_behav_obs-9.7.12.dist-info/entry_points.txt +2 -0
  108. boris/README.TXT +0 -22
  109. boris/add_modifier.ui +0 -323
  110. boris/converters.ui +0 -289
  111. boris/core.qrc +0 -37
  112. boris/core.ui +0 -1571
  113. boris/edit_event.ui +0 -233
  114. boris/icons/logo_eye.ico +0 -0
  115. boris/map_creator.py +0 -982
  116. boris/observation.ui +0 -814
  117. boris/param_panel.ui +0 -379
  118. boris/preferences.ui +0 -537
  119. boris/project.ui +0 -1074
  120. boris/vlc_local.py +0 -90
  121. boris_behav_obs-8.16.5.dist-info/LICENSE.TXT +0 -674
  122. boris_behav_obs-8.16.5.dist-info/METADATA +0 -134
  123. boris_behav_obs-8.16.5.dist-info/RECORD +0 -107
  124. boris_behav_obs-8.16.5.dist-info/entry_points.txt +0 -2
  125. {boris → boris_behav_obs-9.7.12.dist-info/licenses}/LICENSE.TXT +0 -0
  126. {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.12.dist-info}/top_level.txt +0 -0
boris/converters_ui.py CHANGED
@@ -1,148 +1,225 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- # Form implementation generated from reading ui file 'converters.ui'
4
- #
5
- # Created by: PyQt5 UI code generator 5.9
6
- #
7
- # WARNING! All changes made in this file will be lost!
3
+ ################################################################################
4
+ ## Form generated from reading UI file 'converters.ui'
5
+ ##
6
+ ## Created by: Qt User Interface Compiler version 6.8.0
7
+ ##
8
+ ## WARNING! All changes made in this file will be lost when recompiling UI file!
9
+ ################################################################################
8
10
 
9
- from PyQt5 import QtCore, QtGui, QtWidgets
11
+ from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
12
+ QMetaObject, QObject, QPoint, QRect,
13
+ QSize, QTime, QUrl, Qt)
14
+ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
15
+ QFont, QFontDatabase, QGradient, QIcon,
16
+ QImage, QKeySequence, QLinearGradient, QPainter,
17
+ QPalette, QPixmap, QRadialGradient, QTransform)
18
+ from PySide6.QtWidgets import (QAbstractItemView, QApplication, QHBoxLayout, QHeaderView,
19
+ QLabel, QLineEdit, QPlainTextEdit, QPushButton,
20
+ QSizePolicy, QSpacerItem, QTableWidget, QTableWidgetItem,
21
+ QVBoxLayout, QWidget)
10
22
 
11
23
  class Ui_converters(object):
12
24
  def setupUi(self, converters):
13
- converters.setObjectName("converters")
25
+ if not converters.objectName():
26
+ converters.setObjectName(u"converters")
14
27
  converters.resize(1029, 530)
15
- self.verticalLayout = QtWidgets.QVBoxLayout(converters)
16
- self.verticalLayout.setObjectName("verticalLayout")
17
- self.label_4 = QtWidgets.QLabel(converters)
18
- self.label_4.setObjectName("label_4")
28
+ self.verticalLayout = QVBoxLayout(converters)
29
+ self.verticalLayout.setObjectName(u"verticalLayout")
30
+ self.label_4 = QLabel(converters)
31
+ self.label_4.setObjectName(u"label_4")
32
+
19
33
  self.verticalLayout.addWidget(self.label_4)
20
- self.tw_converters = QtWidgets.QTableWidget(converters)
21
- self.tw_converters.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
22
- self.tw_converters.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
23
- self.tw_converters.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
24
- self.tw_converters.setObjectName("tw_converters")
25
- self.tw_converters.setColumnCount(3)
26
- self.tw_converters.setRowCount(0)
27
- item = QtWidgets.QTableWidgetItem()
28
- self.tw_converters.setHorizontalHeaderItem(0, item)
29
- item = QtWidgets.QTableWidgetItem()
30
- self.tw_converters.setHorizontalHeaderItem(1, item)
31
- item = QtWidgets.QTableWidgetItem()
32
- self.tw_converters.setHorizontalHeaderItem(2, item)
34
+
35
+ self.tw_converters = QTableWidget(converters)
36
+ if (self.tw_converters.columnCount() < 3):
37
+ self.tw_converters.setColumnCount(3)
38
+ __qtablewidgetitem = QTableWidgetItem()
39
+ self.tw_converters.setHorizontalHeaderItem(0, __qtablewidgetitem)
40
+ __qtablewidgetitem1 = QTableWidgetItem()
41
+ self.tw_converters.setHorizontalHeaderItem(1, __qtablewidgetitem1)
42
+ __qtablewidgetitem2 = QTableWidgetItem()
43
+ self.tw_converters.setHorizontalHeaderItem(2, __qtablewidgetitem2)
44
+ self.tw_converters.setObjectName(u"tw_converters")
45
+ self.tw_converters.setEditTriggers(QAbstractItemView.NoEditTriggers)
46
+ self.tw_converters.setSelectionMode(QAbstractItemView.SingleSelection)
47
+ self.tw_converters.setSelectionBehavior(QAbstractItemView.SelectRows)
48
+ self.tw_converters.setSortingEnabled(True)
49
+
33
50
  self.verticalLayout.addWidget(self.tw_converters)
34
- self.horizontalLayout = QtWidgets.QHBoxLayout()
35
- self.horizontalLayout.setObjectName("horizontalLayout")
36
- self.pb_add_converter = QtWidgets.QPushButton(converters)
37
- self.pb_add_converter.setObjectName("pb_add_converter")
51
+
52
+ self.horizontalLayout = QHBoxLayout()
53
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
54
+ self.pb_add_converter = QPushButton(converters)
55
+ self.pb_add_converter.setObjectName(u"pb_add_converter")
56
+
38
57
  self.horizontalLayout.addWidget(self.pb_add_converter)
39
- self.pb_modify_converter = QtWidgets.QPushButton(converters)
40
- self.pb_modify_converter.setObjectName("pb_modify_converter")
58
+
59
+ self.pb_modify_converter = QPushButton(converters)
60
+ self.pb_modify_converter.setObjectName(u"pb_modify_converter")
61
+
41
62
  self.horizontalLayout.addWidget(self.pb_modify_converter)
42
- self.pb_delete_converter = QtWidgets.QPushButton(converters)
43
- self.pb_delete_converter.setObjectName("pb_delete_converter")
63
+
64
+ self.pb_delete_converter = QPushButton(converters)
65
+ self.pb_delete_converter.setObjectName(u"pb_delete_converter")
66
+
44
67
  self.horizontalLayout.addWidget(self.pb_delete_converter)
45
- self.pb_load_from_file = QtWidgets.QPushButton(converters)
46
- self.pb_load_from_file.setObjectName("pb_load_from_file")
68
+
69
+ self.pb_load_from_file = QPushButton(converters)
70
+ self.pb_load_from_file.setObjectName(u"pb_load_from_file")
71
+
47
72
  self.horizontalLayout.addWidget(self.pb_load_from_file)
48
- self.pb_load_from_repo = QtWidgets.QPushButton(converters)
49
- self.pb_load_from_repo.setObjectName("pb_load_from_repo")
73
+
74
+ self.pb_load_from_repo = QPushButton(converters)
75
+ self.pb_load_from_repo.setObjectName(u"pb_load_from_repo")
76
+
50
77
  self.horizontalLayout.addWidget(self.pb_load_from_repo)
51
- spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
52
- self.horizontalLayout.addItem(spacerItem)
78
+
79
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
80
+
81
+ self.horizontalLayout.addItem(self.horizontalSpacer)
82
+
83
+
53
84
  self.verticalLayout.addLayout(self.horizontalLayout)
54
- self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
55
- self.horizontalLayout_3.setObjectName("horizontalLayout_3")
56
- self.label_2 = QtWidgets.QLabel(converters)
57
- self.label_2.setMinimumSize(QtCore.QSize(120, 0))
58
- self.label_2.setObjectName("label_2")
85
+
86
+ self.horizontalLayout_3 = QHBoxLayout()
87
+ self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
88
+ self.label_2 = QLabel(converters)
89
+ self.label_2.setObjectName(u"label_2")
90
+ self.label_2.setMinimumSize(QSize(120, 0))
91
+
59
92
  self.horizontalLayout_3.addWidget(self.label_2)
60
- self.le_converter_name = QtWidgets.QLineEdit(converters)
61
- self.le_converter_name.setObjectName("le_converter_name")
93
+
94
+ self.le_converter_name = QLineEdit(converters)
95
+ self.le_converter_name.setObjectName(u"le_converter_name")
96
+
62
97
  self.horizontalLayout_3.addWidget(self.le_converter_name)
63
- spacerItem1 = QtWidgets.QSpacerItem(10, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
64
- self.horizontalLayout_3.addItem(spacerItem1)
98
+
99
+ self.horizontalSpacer_3 = QSpacerItem(10, 20, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
100
+
101
+ self.horizontalLayout_3.addItem(self.horizontalSpacer_3)
102
+
103
+
65
104
  self.verticalLayout.addLayout(self.horizontalLayout_3)
66
- self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
67
- self.horizontalLayout_5.setObjectName("horizontalLayout_5")
68
- self.label_3 = QtWidgets.QLabel(converters)
69
- self.label_3.setMinimumSize(QtCore.QSize(120, 0))
70
- self.label_3.setObjectName("label_3")
105
+
106
+ self.horizontalLayout_5 = QHBoxLayout()
107
+ self.horizontalLayout_5.setObjectName(u"horizontalLayout_5")
108
+ self.label_3 = QLabel(converters)
109
+ self.label_3.setObjectName(u"label_3")
110
+ self.label_3.setMinimumSize(QSize(120, 0))
111
+
71
112
  self.horizontalLayout_5.addWidget(self.label_3)
72
- self.le_converter_description = QtWidgets.QLineEdit(converters)
73
- self.le_converter_description.setObjectName("le_converter_description")
113
+
114
+ self.le_converter_description = QLineEdit(converters)
115
+ self.le_converter_description.setObjectName(u"le_converter_description")
116
+
74
117
  self.horizontalLayout_5.addWidget(self.le_converter_description)
75
- spacerItem2 = QtWidgets.QSpacerItem(10, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
76
- self.horizontalLayout_5.addItem(spacerItem2)
118
+
119
+ self.horizontalSpacer_4 = QSpacerItem(10, 20, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
120
+
121
+ self.horizontalLayout_5.addItem(self.horizontalSpacer_4)
122
+
123
+
77
124
  self.verticalLayout.addLayout(self.horizontalLayout_5)
78
- self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
79
- self.horizontalLayout_2.setObjectName("horizontalLayout_2")
80
- self.verticalLayout_3 = QtWidgets.QVBoxLayout()
81
- self.verticalLayout_3.setObjectName("verticalLayout_3")
82
- self.label = QtWidgets.QLabel(converters)
83
- self.label.setObjectName("label")
125
+
126
+ self.horizontalLayout_2 = QHBoxLayout()
127
+ self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
128
+ self.verticalLayout_3 = QVBoxLayout()
129
+ self.verticalLayout_3.setObjectName(u"verticalLayout_3")
130
+ self.label = QLabel(converters)
131
+ self.label.setObjectName(u"label")
132
+
84
133
  self.verticalLayout_3.addWidget(self.label)
85
- self.pb_code_help = QtWidgets.QPushButton(converters)
86
- self.pb_code_help.setObjectName("pb_code_help")
134
+
135
+ self.pb_code_help = QPushButton(converters)
136
+ self.pb_code_help.setObjectName(u"pb_code_help")
137
+
87
138
  self.verticalLayout_3.addWidget(self.pb_code_help)
88
- spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
89
- self.verticalLayout_3.addItem(spacerItem3)
139
+
140
+ self.verticalSpacer_2 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
141
+
142
+ self.verticalLayout_3.addItem(self.verticalSpacer_2)
143
+
144
+
90
145
  self.horizontalLayout_2.addLayout(self.verticalLayout_3)
91
- self.pteCode = QtWidgets.QPlainTextEdit(converters)
92
- font = QtGui.QFont()
93
- font.setFamily("Monospace")
146
+
147
+ self.pteCode = QPlainTextEdit(converters)
148
+ self.pteCode.setObjectName(u"pteCode")
149
+ font = QFont()
150
+ font.setFamilies([u"Monospace"])
94
151
  self.pteCode.setFont(font)
95
- self.pteCode.setObjectName("pteCode")
152
+
96
153
  self.horizontalLayout_2.addWidget(self.pteCode)
97
- self.verticalLayout_2 = QtWidgets.QVBoxLayout()
98
- self.verticalLayout_2.setObjectName("verticalLayout_2")
99
- self.pb_save_converter = QtWidgets.QPushButton(converters)
100
- self.pb_save_converter.setObjectName("pb_save_converter")
154
+
155
+ self.verticalLayout_2 = QVBoxLayout()
156
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
157
+ self.pb_save_converter = QPushButton(converters)
158
+ self.pb_save_converter.setObjectName(u"pb_save_converter")
159
+
101
160
  self.verticalLayout_2.addWidget(self.pb_save_converter)
102
- self.pb_cancel_converter = QtWidgets.QPushButton(converters)
103
- self.pb_cancel_converter.setObjectName("pb_cancel_converter")
161
+
162
+ self.pb_cancel_converter = QPushButton(converters)
163
+ self.pb_cancel_converter.setObjectName(u"pb_cancel_converter")
164
+
104
165
  self.verticalLayout_2.addWidget(self.pb_cancel_converter)
105
- spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
106
- self.verticalLayout_2.addItem(spacerItem4)
166
+
167
+ self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
168
+
169
+ self.verticalLayout_2.addItem(self.verticalSpacer)
170
+
171
+
107
172
  self.horizontalLayout_2.addLayout(self.verticalLayout_2)
173
+
174
+
108
175
  self.verticalLayout.addLayout(self.horizontalLayout_2)
109
- self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
110
- self.horizontalLayout_4.setObjectName("horizontalLayout_4")
111
- spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
112
- self.horizontalLayout_4.addItem(spacerItem5)
113
- self.pb_cancel_widget = QtWidgets.QPushButton(converters)
114
- self.pb_cancel_widget.setObjectName("pb_cancel_widget")
176
+
177
+ self.horizontalLayout_4 = QHBoxLayout()
178
+ self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
179
+ self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
180
+
181
+ self.horizontalLayout_4.addItem(self.horizontalSpacer_2)
182
+
183
+ self.pb_cancel_widget = QPushButton(converters)
184
+ self.pb_cancel_widget.setObjectName(u"pb_cancel_widget")
185
+
115
186
  self.horizontalLayout_4.addWidget(self.pb_cancel_widget)
116
- self.pbOK = QtWidgets.QPushButton(converters)
117
- self.pbOK.setObjectName("pbOK")
187
+
188
+ self.pbOK = QPushButton(converters)
189
+ self.pbOK.setObjectName(u"pbOK")
190
+
118
191
  self.horizontalLayout_4.addWidget(self.pbOK)
192
+
193
+
119
194
  self.verticalLayout.addLayout(self.horizontalLayout_4)
120
195
 
196
+
121
197
  self.retranslateUi(converters)
122
- QtCore.QMetaObject.connectSlotsByName(converters)
198
+
199
+ QMetaObject.connectSlotsByName(converters)
200
+ # setupUi
123
201
 
124
202
  def retranslateUi(self, converters):
125
- _translate = QtCore.QCoreApplication.translate
126
- converters.setWindowTitle(_translate("converters", "Time converters"))
127
- self.label_4.setText(_translate("converters", "Converters"))
128
- self.tw_converters.setSortingEnabled(True)
129
- item = self.tw_converters.horizontalHeaderItem(0)
130
- item.setText(_translate("converters", "Name"))
131
- item = self.tw_converters.horizontalHeaderItem(1)
132
- item.setText(_translate("converters", "Description"))
133
- item = self.tw_converters.horizontalHeaderItem(2)
134
- item.setText(_translate("converters", "Code"))
135
- self.pb_add_converter.setText(_translate("converters", "Add new converter"))
136
- self.pb_modify_converter.setText(_translate("converters", "Modify converter"))
137
- self.pb_delete_converter.setText(_translate("converters", "Delete converter"))
138
- self.pb_load_from_file.setText(_translate("converters", "Load converters from file"))
139
- self.pb_load_from_repo.setText(_translate("converters", "Load converters from BORIS repository"))
140
- self.label_2.setText(_translate("converters", "Name"))
141
- self.label_3.setText(_translate("converters", "Description"))
142
- self.label.setText(_translate("converters", "Python code"))
143
- self.pb_code_help.setText(_translate("converters", "Help"))
144
- self.pb_save_converter.setText(_translate("converters", "Save converter"))
145
- self.pb_cancel_converter.setText(_translate("converters", "Cancel"))
146
- self.pb_cancel_widget.setText(_translate("converters", "Cancel"))
147
- self.pbOK.setText(_translate("converters", "OK"))
203
+ converters.setWindowTitle(QCoreApplication.translate("converters", u"Time converters", None))
204
+ self.label_4.setText(QCoreApplication.translate("converters", u"Converters", None))
205
+ ___qtablewidgetitem = self.tw_converters.horizontalHeaderItem(0)
206
+ ___qtablewidgetitem.setText(QCoreApplication.translate("converters", u"Name", None));
207
+ ___qtablewidgetitem1 = self.tw_converters.horizontalHeaderItem(1)
208
+ ___qtablewidgetitem1.setText(QCoreApplication.translate("converters", u"Description", None));
209
+ ___qtablewidgetitem2 = self.tw_converters.horizontalHeaderItem(2)
210
+ ___qtablewidgetitem2.setText(QCoreApplication.translate("converters", u"Code", None));
211
+ self.pb_add_converter.setText(QCoreApplication.translate("converters", u"Add new converter", None))
212
+ self.pb_modify_converter.setText(QCoreApplication.translate("converters", u"Modify converter", None))
213
+ self.pb_delete_converter.setText(QCoreApplication.translate("converters", u"Delete converter", None))
214
+ self.pb_load_from_file.setText(QCoreApplication.translate("converters", u"Load converters from file", None))
215
+ self.pb_load_from_repo.setText(QCoreApplication.translate("converters", u"Load converters from BORIS repository", None))
216
+ self.label_2.setText(QCoreApplication.translate("converters", u"Name", None))
217
+ self.label_3.setText(QCoreApplication.translate("converters", u"Description", None))
218
+ self.label.setText(QCoreApplication.translate("converters", u"Python code", None))
219
+ self.pb_code_help.setText(QCoreApplication.translate("converters", u"Help", None))
220
+ self.pb_save_converter.setText(QCoreApplication.translate("converters", u"Save converter", None))
221
+ self.pb_cancel_converter.setText(QCoreApplication.translate("converters", u"Cancel", None))
222
+ self.pb_cancel_widget.setText(QCoreApplication.translate("converters", u"Cancel", None))
223
+ self.pbOK.setText(QCoreApplication.translate("converters", u"OK", None))
224
+ # retranslateUi
148
225
 
boris/cooccurence.py ADDED
@@ -0,0 +1,250 @@
1
+ """
2
+ BORIS
3
+ Behavioral Observation Research Interactive Software
4
+ Copyright 2012-2025 Olivier Friard
5
+
6
+ This program is free software; you can redistribute it and/or modify
7
+ it under the terms of the GNU General Public License as published by
8
+ the Free Software Foundation; either version 2 of the License, or
9
+ (at your option) any later version.
10
+
11
+ This program is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with this program; if not, write to the Free Software
18
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
+ MA 02110-1301, USA.
20
+
21
+
22
+ Module for analyzing the co-occurence of behaviors
23
+
24
+ """
25
+
26
+ from . import config as cfg
27
+ from . import select_subj_behav
28
+ from . import dialog
29
+ from . import utilities as util
30
+ from . import select_observations
31
+
32
+ from . import project_functions, observation_operations
33
+
34
+ from PySide6.QtWidgets import QMessageBox
35
+ from PySide6.QtGui import QFont, QTextOption
36
+ from . import portion as I
37
+ import itertools
38
+ import logging
39
+ from decimal import Decimal as dec
40
+
41
+
42
+ def get_cooccurence(self):
43
+ """
44
+ get co-occurence of selected behaviors
45
+ """
46
+
47
+ QMessageBox.warning(
48
+ None,
49
+ cfg.programName,
50
+ (
51
+ "This function is experimental. Please test it and report any bug and suggestions at <br>"
52
+ '<a href="https://github.com/olivierfriard/BORIS/issues">'
53
+ "https://github.com/olivierfriard/BORIS/issues</a><br>"
54
+ "Thank you for your collaboration!"
55
+ ),
56
+ QMessageBox.Ok | QMessageBox.Default,
57
+ QMessageBox.NoButton,
58
+ )
59
+
60
+ def interval_len(interval: I) -> dec:
61
+ """ "
62
+ returns duration of an interval or a set of intervals
63
+ """
64
+ if interval.empty:
65
+ return dec(0)
66
+ else:
67
+ return dec(sum([x.upper - x.lower for x in interval]))
68
+
69
+ _, selected_observations = select_observations.select_observations2(
70
+ self, cfg.MULTIPLE, windows_title="Select the observations for behaviors co-occurence analysis"
71
+ )
72
+
73
+ if not selected_observations:
74
+ return
75
+
76
+ # check if coded behaviors are defined in ethogram
77
+ if project_functions.check_coded_behaviors_in_obs_list(self.pj, selected_observations):
78
+ return
79
+
80
+ # check if state events are paired
81
+ not_ok, selected_observations = project_functions.check_state_events(self.pj, selected_observations)
82
+ if not_ok or not selected_observations:
83
+ return
84
+
85
+ max_media_duration_all_obs, _ = observation_operations.media_duration(self.pj[cfg.OBSERVATIONS], selected_observations)
86
+
87
+ start_coding, end_coding, _ = observation_operations.coding_time(self.pj[cfg.OBSERVATIONS], selected_observations)
88
+ # exit with message if events do not have timestamp
89
+ if start_coding.is_nan():
90
+ QMessageBox.critical(
91
+ None,
92
+ cfg.programName,
93
+ ("This function is not available for observations with events that do not have timestamp"),
94
+ QMessageBox.Ok | QMessageBox.Default,
95
+ QMessageBox.NoButton,
96
+ )
97
+ return
98
+
99
+ start_interval, end_interval = observation_operations.time_intervals_range(self.pj[cfg.OBSERVATIONS], selected_observations)
100
+
101
+ # loop on choose subjects /behaviors until parameters are OK
102
+ while True:
103
+ flag_ok: bool = True
104
+ parameters = select_subj_behav.choose_obs_subj_behav_category(
105
+ self,
106
+ selected_observations,
107
+ start_coding=start_coding,
108
+ end_coding=end_coding,
109
+ # start_interval=start_interval,
110
+ # end_interval=end_interval,
111
+ start_interval=None,
112
+ end_interval=None,
113
+ maxTime=max_media_duration_all_obs,
114
+ n_observations=len(selected_observations),
115
+ show_include_modifiers=False,
116
+ show_exclude_non_coded_behaviors=True,
117
+ )
118
+
119
+ if not parameters: # cancel button pressed
120
+ return
121
+
122
+ if not parameters[cfg.SELECTED_SUBJECTS]:
123
+ QMessageBox.warning(None, cfg.programName, "Select the subject(s) to analyze")
124
+ flag_ok = False
125
+
126
+ # check number of behaviors (must be <=4)
127
+ if flag_ok and len(parameters[cfg.SELECTED_BEHAVIORS]) > 4:
128
+ QMessageBox.warning(None, cfg.programName, "You cannot select more than 4 behaviors")
129
+ flag_ok = False
130
+
131
+ # check number of behaviors (must be > 1)
132
+ if flag_ok and len(parameters[cfg.SELECTED_BEHAVIORS]) < 2:
133
+ QMessageBox.warning(None, cfg.programName, "You must select almost 2 behaviors")
134
+ flag_ok = False
135
+
136
+ if flag_ok:
137
+ break
138
+
139
+ logging.debug(f"{parameters[cfg.SELECTED_BEHAVIORS]}")
140
+
141
+ state_events_list = util.state_behavior_codes(self.pj[cfg.ETHOGRAM])
142
+
143
+ events_interval: dict = {}
144
+ mem_events_interval: dict = {}
145
+
146
+ for obs_id in selected_observations:
147
+ events_interval[obs_id] = {}
148
+ mem_events_interval[obs_id] = {}
149
+
150
+ for event in self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]:
151
+ if event[cfg.EVENT_SUBJECT_FIELD_IDX] not in events_interval[obs_id]:
152
+ events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]] = {}
153
+ mem_events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]] = {}
154
+
155
+ if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] not in events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]]:
156
+ events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]] = I.empty()
157
+ mem_events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]] = []
158
+
159
+ # state event
160
+ if event[cfg.EVENT_BEHAVIOR_FIELD_IDX] in state_events_list:
161
+ mem_events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]].append(
162
+ event[cfg.EVENT_TIME_FIELD_IDX]
163
+ )
164
+ if len(mem_events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]]) == 2:
165
+ events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]] |= I.closedopen(
166
+ mem_events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]][0],
167
+ mem_events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]][1],
168
+ )
169
+ mem_events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]] = []
170
+ # point event
171
+ else:
172
+ events_interval[obs_id][event[cfg.EVENT_SUBJECT_FIELD_IDX]][event[cfg.EVENT_BEHAVIOR_FIELD_IDX]] |= I.singleton(
173
+ event[cfg.EVENT_TIME_FIELD_IDX]
174
+ )
175
+
176
+ logging.debug(f"events_interval: {events_interval}")
177
+
178
+ cooccurence_results: dict = {}
179
+
180
+ for obs_id in selected_observations:
181
+ logging.debug(f"obs_id: {obs_id}")
182
+
183
+ for subject in parameters[cfg.SELECTED_SUBJECTS]:
184
+ if subject == "No focal subject":
185
+ subj = ""
186
+ else:
187
+ subj = subject
188
+
189
+ if subject not in cooccurence_results:
190
+ cooccurence_results[subject] = {}
191
+
192
+ logging.debug(f"subject {subject}")
193
+
194
+ for n_combinations in range(2, len(parameters[cfg.SELECTED_BEHAVIORS]) + 1):
195
+ union = I.empty()
196
+
197
+ logging.debug(f"{n_combinations=}")
198
+
199
+ for combination in itertools.combinations(parameters[cfg.SELECTED_BEHAVIORS], n_combinations):
200
+ logging.debug(f"{combination=}")
201
+ if subj in events_interval[obs_id]:
202
+ # init
203
+ if combination[0] in events_interval[obs_id][subj]:
204
+ union = events_interval[obs_id][subj][combination[0]]
205
+ else:
206
+ union = I.empty()
207
+
208
+ logging.debug(f"{combination[0]=} {union=}")
209
+
210
+ for combination2 in combination[1:]:
211
+ if combination2 in events_interval[obs_id][subj]:
212
+ inter2 = events_interval[obs_id][subj][combination2]
213
+ else:
214
+ inter2 = I.empty()
215
+
216
+ logging.debug(f"{combination2=} {inter2=}")
217
+
218
+ union &= inter2
219
+
220
+ if combination not in cooccurence_results[subject]:
221
+ cooccurence_results[subject][combination] = 0
222
+
223
+ logging.debug(f"{combination=} {union=}")
224
+ cooccurence_results[subject][combination] += interval_len(union)
225
+ else:
226
+ if combination not in cooccurence_results[subject]:
227
+ cooccurence_results[subject][combination] = 0
228
+ cooccurence_results[subject][combination] += 0
229
+
230
+ logging.debug(f"{cooccurence_results[subject][combination]=}")
231
+
232
+ logging.debug(cooccurence_results)
233
+
234
+ out = f"<b>Co-occurence of behaviors: {','.join(parameters[cfg.SELECTED_BEHAVIORS])}</b><br><br>"
235
+ for subject in parameters[cfg.SELECTED_SUBJECTS]:
236
+ out += f"<br>Subject <b>{subject}</b><br><br>"
237
+ for combination in cooccurence_results[subject]:
238
+ if parameters[cfg.EXCLUDE_BEHAVIORS] and not cooccurence_results[subject][combination]:
239
+ continue
240
+ duration = f"<b>{cooccurence_results[subject][combination]}</b>" if cooccurence_results[subject][combination] else "0"
241
+ out += f"<b>{'</b> and <b>'.join(combination)}</b>: {duration} s<br>"
242
+
243
+ self.results = dialog.Results_dialog()
244
+ self.results.setWindowTitle("Behaviors co-occurence")
245
+ self.results.ptText.setFont(QFont("Courier", 12))
246
+ self.results.ptText.setWordWrapMode(QTextOption.NoWrap)
247
+ self.results.ptText.setReadOnly(True)
248
+ self.results.ptText.clear()
249
+ self.results.ptText.appendHtml(out)
250
+ self.results.show()