boris-behav-obs 8.16.5__py3-none-any.whl → 9.7.1__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 (125) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +24 -36
  4. boris/add_modifier.py +88 -80
  5. boris/add_modifier_ui.py +235 -131
  6. boris/advanced_event_filtering.py +23 -29
  7. boris/analysis_plugins/__init__.py +0 -0
  8. boris/analysis_plugins/_latency.py +59 -0
  9. boris/analysis_plugins/irr_cohen_kappa.py +109 -0
  10. boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
  11. boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
  12. boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
  13. boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
  14. boris/analysis_plugins/number_of_occurences.py +22 -0
  15. boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
  16. boris/analysis_plugins/time_budget.py +61 -0
  17. boris/behav_coding_map_creator.py +228 -229
  18. boris/behavior_binary_table.py +33 -50
  19. boris/behaviors_coding_map.py +17 -18
  20. boris/boris_cli.py +6 -25
  21. boris/cmd_arguments.py +12 -1
  22. boris/coding_pad.py +16 -34
  23. boris/config.py +102 -50
  24. boris/config_file.py +55 -64
  25. boris/connections.py +105 -58
  26. boris/converters.py +13 -37
  27. boris/converters_ui.py +187 -110
  28. boris/cooccurence.py +250 -0
  29. boris/core.py +2108 -1275
  30. boris/core_qrc.py +15892 -10829
  31. boris/core_ui.py +941 -806
  32. boris/db_functions.py +17 -42
  33. boris/dev.py +27 -7
  34. boris/dialog.py +461 -242
  35. boris/duration_widget.py +9 -14
  36. boris/edit_event.py +61 -31
  37. boris/edit_event_ui.py +208 -97
  38. boris/event_operations.py +405 -281
  39. boris/events_cursor.py +25 -17
  40. boris/events_snapshots.py +36 -82
  41. boris/exclusion_matrix.py +4 -9
  42. boris/export_events.py +180 -203
  43. boris/export_observation.py +60 -73
  44. boris/external_processes.py +123 -98
  45. boris/geometric_measurement.py +427 -218
  46. boris/gui_utilities.py +91 -14
  47. boris/image_overlay.py +4 -4
  48. boris/import_observations.py +190 -98
  49. boris/ipc_mpv.py +304 -0
  50. boris/irr.py +20 -57
  51. boris/latency.py +31 -24
  52. boris/measurement_widget.py +14 -18
  53. boris/media_file.py +17 -19
  54. boris/menu_options.py +16 -6
  55. boris/modifier_coding_map_creator.py +1013 -0
  56. boris/modifiers_coding_map.py +7 -9
  57. boris/mpv2.py +128 -35
  58. boris/observation.py +493 -210
  59. boris/observation_operations.py +1010 -391
  60. boris/observation_ui.py +573 -363
  61. boris/observations_list.py +51 -58
  62. boris/otx_parser.py +74 -68
  63. boris/param_panel.py +45 -59
  64. boris/param_panel_ui.py +254 -138
  65. boris/player_dock_widget.py +91 -56
  66. boris/plot_data_module.py +18 -53
  67. boris/plot_events.py +56 -153
  68. boris/plot_events_rt.py +16 -30
  69. boris/plot_spectrogram_rt.py +80 -56
  70. boris/plot_waveform_rt.py +23 -48
  71. boris/plugins.py +431 -0
  72. boris/portion/__init__.py +18 -8
  73. boris/portion/const.py +35 -18
  74. boris/portion/dict.py +5 -5
  75. boris/portion/func.py +2 -2
  76. boris/portion/interval.py +21 -41
  77. boris/portion/io.py +41 -32
  78. boris/preferences.py +298 -123
  79. boris/preferences_ui.py +664 -225
  80. boris/project.py +293 -270
  81. boris/project_functions.py +610 -537
  82. boris/project_import_export.py +204 -213
  83. boris/project_ui.py +673 -441
  84. boris/qrc_boris.py +6 -3
  85. boris/qrc_boris5.py +6 -3
  86. boris/select_modifiers.py +62 -90
  87. boris/select_observations.py +19 -197
  88. boris/select_subj_behav.py +67 -39
  89. boris/state_events.py +51 -33
  90. boris/subjects_pad.py +6 -8
  91. boris/synthetic_time_budget.py +42 -26
  92. boris/time_budget_functions.py +169 -169
  93. boris/time_budget_widget.py +77 -89
  94. boris/transitions.py +41 -41
  95. boris/utilities.py +562 -222
  96. boris/version.py +3 -3
  97. boris/video_equalizer.py +16 -14
  98. boris/video_equalizer_ui.py +199 -130
  99. boris/video_operations.py +78 -28
  100. boris/view_df.py +104 -0
  101. boris/view_df_ui.py +75 -0
  102. boris/write_event.py +240 -136
  103. boris_behav_obs-9.7.1.dist-info/METADATA +140 -0
  104. boris_behav_obs-9.7.1.dist-info/RECORD +109 -0
  105. {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.1.dist-info}/WHEEL +1 -1
  106. boris_behav_obs-9.7.1.dist-info/entry_points.txt +2 -0
  107. boris/README.TXT +0 -22
  108. boris/add_modifier.ui +0 -323
  109. boris/converters.ui +0 -289
  110. boris/core.qrc +0 -37
  111. boris/core.ui +0 -1571
  112. boris/edit_event.ui +0 -233
  113. boris/icons/logo_eye.ico +0 -0
  114. boris/map_creator.py +0 -982
  115. boris/observation.ui +0 -814
  116. boris/param_panel.ui +0 -379
  117. boris/preferences.ui +0 -537
  118. boris/project.ui +0 -1074
  119. boris/vlc_local.py +0 -90
  120. boris_behav_obs-8.16.5.dist-info/LICENSE.TXT +0 -674
  121. boris_behav_obs-8.16.5.dist-info/METADATA +0 -134
  122. boris_behav_obs-8.16.5.dist-info/RECORD +0 -107
  123. boris_behav_obs-8.16.5.dist-info/entry_points.txt +0 -2
  124. {boris → boris_behav_obs-9.7.1.dist-info/licenses}/LICENSE.TXT +0 -0
  125. {boris_behav_obs-8.16.5.dist-info → boris_behav_obs-9.7.1.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()