boris-behav-obs 9.7.1__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.
boris/about.py CHANGED
@@ -40,7 +40,7 @@ def actionAbout_activated(self):
40
40
  About dialog
41
41
  """
42
42
 
43
- programs_versions: list = ["MPV media player"]
43
+ programs_versions: list[str] = ["MPV media player"]
44
44
 
45
45
  mpv_lib_version, mpv_lib_file_path, mpv_api_version = util.mpv_lib_version()
46
46
  programs_versions.append(
@@ -53,13 +53,13 @@ def actionAbout_activated(self):
53
53
 
54
54
  # ffmpeg
55
55
  if self.ffmpeg_bin == "ffmpeg" and sys.platform.startswith("linux"):
56
- ffmpeg_true_path = subprocess.getoutput("which ffmpeg")
56
+ ffmpeg_true_path: str = subprocess.getoutput("which ffmpeg")
57
57
  else:
58
58
  ffmpeg_true_path = self.ffmpeg_bin
59
59
  programs_versions.extend(
60
60
  [
61
61
  "\nFFmpeg",
62
- subprocess.getoutput(f'"{self.ffmpeg_bin}" -version').split("\n")[0],
62
+ subprocess.getoutput(cmd=f'"{self.ffmpeg_bin}" -version').split(sep="\n")[0],
63
63
  f"Path: {ffmpeg_true_path}",
64
64
  "https://www.ffmpeg.org",
65
65
  ]
@@ -75,11 +75,11 @@ def actionAbout_activated(self):
75
75
  programs_versions.extend(["\nPandas", f"version {pd.__version__}", "https://pandas.pydata.org"])
76
76
 
77
77
  # graphviz
78
- gv_result = subprocess.getoutput("dot -V")
78
+ gv_result = subprocess.getoutput(cmd="dot -V")
79
79
 
80
80
  programs_versions.extend(["\nGraphViz", gv_result if "graphviz" in gv_result else "not installed", "https://www.graphviz.org/"])
81
81
 
82
- about_dialog = QMessageBox()
82
+ about_dialog: QMessageBox = QMessageBox()
83
83
  about_dialog.setIconPixmap(QPixmap(":/boris_unito"))
84
84
 
85
85
  about_dialog.setWindowTitle(f"About {cfg.programName}")
boris/add_modifier_ui.py CHANGED
@@ -3,7 +3,7 @@
3
3
  ################################################################################
4
4
  ## Form generated from reading UI file 'add_modifier.ui'
5
5
  ##
6
- ## Created by: Qt User Interface Compiler version 6.8.0
6
+ ## Created by: Qt User Interface Compiler version 6.10.0
7
7
  ##
8
8
  ## WARNING! All changes made in this file will be lost when recompiling UI file!
9
9
  ################################################################################
@@ -24,18 +24,16 @@ class Ui_Dialog(object):
24
24
  def setupUi(self, Dialog):
25
25
  if not Dialog.objectName():
26
26
  Dialog.setObjectName(u"Dialog")
27
- Dialog.resize(1088, 654)
28
- self.verticalLayout_5 = QVBoxLayout(Dialog)
29
- self.verticalLayout_5.setObjectName(u"verticalLayout_5")
27
+ Dialog.resize(1339, 789)
28
+ self.verticalLayout_4 = QVBoxLayout(Dialog)
29
+ self.verticalLayout_4.setObjectName(u"verticalLayout_4")
30
30
  self.cb_ask_at_stop = QCheckBox(Dialog)
31
31
  self.cb_ask_at_stop.setObjectName(u"cb_ask_at_stop")
32
32
 
33
- self.verticalLayout_5.addWidget(self.cb_ask_at_stop)
33
+ self.verticalLayout_4.addWidget(self.cb_ask_at_stop)
34
34
 
35
- self.verticalLayout_4 = QVBoxLayout()
36
- self.verticalLayout_4.setObjectName(u"verticalLayout_4")
37
- self.horizontalLayout_5 = QHBoxLayout()
38
- self.horizontalLayout_5.setObjectName(u"horizontalLayout_5")
35
+ self.horizontalLayout_8 = QHBoxLayout()
36
+ self.horizontalLayout_8.setObjectName(u"horizontalLayout_8")
39
37
  self.verticalLayout_2 = QVBoxLayout()
40
38
  self.verticalLayout_2.setObjectName(u"verticalLayout_2")
41
39
  self.lbModifier = QLabel(Dialog)
@@ -69,7 +67,7 @@ class Ui_Dialog(object):
69
67
  self.verticalLayout_2.addItem(self.verticalSpacer)
70
68
 
71
69
 
72
- self.horizontalLayout_5.addLayout(self.verticalLayout_2)
70
+ self.horizontalLayout_8.addLayout(self.verticalLayout_2)
73
71
 
74
72
  self.verticalLayout_3 = QVBoxLayout()
75
73
  self.verticalLayout_3.setObjectName(u"verticalLayout_3")
@@ -88,7 +86,7 @@ class Ui_Dialog(object):
88
86
  self.verticalLayout_3.addItem(self.verticalSpacer_2)
89
87
 
90
88
 
91
- self.horizontalLayout_5.addLayout(self.verticalLayout_3)
89
+ self.horizontalLayout_8.addLayout(self.verticalLayout_3)
92
90
 
93
91
  self.verticalLayout = QVBoxLayout()
94
92
  self.verticalLayout.setObjectName(u"verticalLayout")
@@ -102,30 +100,42 @@ class Ui_Dialog(object):
102
100
 
103
101
  self.verticalLayout.addWidget(self.tabWidgetModifiersSets)
104
102
 
103
+ self.horizontalLayout_5 = QHBoxLayout()
104
+ self.horizontalLayout_5.setObjectName(u"horizontalLayout_5")
105
105
  self.lb_name = QLabel(Dialog)
106
106
  self.lb_name.setObjectName(u"lb_name")
107
107
 
108
- self.verticalLayout.addWidget(self.lb_name)
108
+ self.horizontalLayout_5.addWidget(self.lb_name)
109
109
 
110
110
  self.le_name = QLineEdit(Dialog)
111
111
  self.le_name.setObjectName(u"le_name")
112
112
 
113
- self.verticalLayout.addWidget(self.le_name)
113
+ self.horizontalLayout_5.addWidget(self.le_name)
114
114
 
115
+
116
+ self.verticalLayout.addLayout(self.horizontalLayout_5)
117
+
118
+ self.horizontalLayout_6 = QHBoxLayout()
119
+ self.horizontalLayout_6.setObjectName(u"horizontalLayout_6")
115
120
  self.lb_description = QLabel(Dialog)
116
121
  self.lb_description.setObjectName(u"lb_description")
117
122
 
118
- self.verticalLayout.addWidget(self.lb_description)
123
+ self.horizontalLayout_6.addWidget(self.lb_description)
119
124
 
120
125
  self.le_description = QLineEdit(Dialog)
121
126
  self.le_description.setObjectName(u"le_description")
122
127
 
123
- self.verticalLayout.addWidget(self.le_description)
128
+ self.horizontalLayout_6.addWidget(self.le_description)
129
+
124
130
 
131
+ self.verticalLayout.addLayout(self.horizontalLayout_6)
132
+
133
+ self.horizontalLayout_7 = QHBoxLayout()
134
+ self.horizontalLayout_7.setObjectName(u"horizontalLayout_7")
125
135
  self.lbType = QLabel(Dialog)
126
136
  self.lbType.setObjectName(u"lbType")
127
137
 
128
- self.verticalLayout.addWidget(self.lbType)
138
+ self.horizontalLayout_7.addWidget(self.lbType)
129
139
 
130
140
  self.cbType = QComboBox(Dialog)
131
141
  self.cbType.addItem("")
@@ -134,7 +144,14 @@ class Ui_Dialog(object):
134
144
  self.cbType.addItem("")
135
145
  self.cbType.setObjectName(u"cbType")
136
146
 
137
- self.verticalLayout.addWidget(self.cbType)
147
+ self.horizontalLayout_7.addWidget(self.cbType)
148
+
149
+ self.horizontalSpacer_3 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
150
+
151
+ self.horizontalLayout_7.addItem(self.horizontalSpacer_3)
152
+
153
+
154
+ self.verticalLayout.addLayout(self.horizontalLayout_7)
138
155
 
139
156
  self.lbValues = QLabel(Dialog)
140
157
  self.lbValues.setObjectName(u"lbValues")
@@ -198,28 +215,32 @@ class Ui_Dialog(object):
198
215
 
199
216
  self.horizontalLayout_4 = QHBoxLayout()
200
217
  self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
201
-
202
- self.verticalLayout.addLayout(self.horizontalLayout_4)
203
-
204
218
  self.pb_add_subjects = QPushButton(Dialog)
205
219
  self.pb_add_subjects.setObjectName(u"pb_add_subjects")
206
220
 
207
- self.verticalLayout.addWidget(self.pb_add_subjects)
221
+ self.horizontalLayout_4.addWidget(self.pb_add_subjects)
208
222
 
209
223
  self.pb_load_file = QPushButton(Dialog)
210
224
  self.pb_load_file.setObjectName(u"pb_load_file")
211
225
 
212
- self.verticalLayout.addWidget(self.pb_load_file)
226
+ self.horizontalLayout_4.addWidget(self.pb_load_file)
227
+
228
+ self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
229
+
230
+ self.horizontalLayout_4.addItem(self.horizontalSpacer_2)
231
+
232
+
233
+ self.verticalLayout.addLayout(self.horizontalLayout_4)
213
234
 
214
235
  self.verticalSpacer_3 = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
215
236
 
216
237
  self.verticalLayout.addItem(self.verticalSpacer_3)
217
238
 
218
239
 
219
- self.horizontalLayout_5.addLayout(self.verticalLayout)
240
+ self.horizontalLayout_8.addLayout(self.verticalLayout)
220
241
 
221
242
 
222
- self.verticalLayout_4.addLayout(self.horizontalLayout_5)
243
+ self.verticalLayout_4.addLayout(self.horizontalLayout_8)
223
244
 
224
245
  self.horizontalLayout_2 = QHBoxLayout()
225
246
  self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
@@ -241,9 +262,6 @@ class Ui_Dialog(object):
241
262
  self.verticalLayout_4.addLayout(self.horizontalLayout_2)
242
263
 
243
264
 
244
- self.verticalLayout_5.addLayout(self.verticalLayout_4)
245
-
246
-
247
265
  self.retranslateUi(Dialog)
248
266
 
249
267
  self.tabWidgetModifiersSets.setCurrentIndex(-1)
@@ -256,8 +274,8 @@ class Ui_Dialog(object):
256
274
  Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"Set modifiers", None))
257
275
  self.cb_ask_at_stop.setText(QCoreApplication.translate("Dialog", u"Ask for modifier(s) when behavior stops", None))
258
276
  self.lbModifier.setText(QCoreApplication.translate("Dialog", u"Modifier", None))
259
- self.lbCode.setText(QCoreApplication.translate("Dialog", u"Key code", None))
260
- self.lbCodeHelp.setText(QCoreApplication.translate("Dialog", u"Key code is case sensitive. Type one character or a function key (F1, F2... F12)", None))
277
+ self.lbCode.setText(QCoreApplication.translate("Dialog", u"Shortcut", None))
278
+ self.lbCodeHelp.setText(QCoreApplication.translate("Dialog", u"The shortcut is case sensitive. Type one character or a function key (F1, F2... F12)", None))
261
279
  self.pbAddModifier.setText(QCoreApplication.translate("Dialog", u"->", None))
262
280
  self.pbModifyModifier.setText(QCoreApplication.translate("Dialog", u"<-", None))
263
281
  self.lb_name.setText(QCoreApplication.translate("Dialog", u"Set name", None))
@@ -0,0 +1,225 @@
1
+ """
2
+ BORIS plugin
3
+
4
+ Export to FERAL (getferal.ai)
5
+ """
6
+
7
+ import pandas as pd
8
+ import json
9
+ from pathlib import Path
10
+
11
+ from PySide6.QtWidgets import QFileDialog
12
+
13
+ # dependencies for CategoryDialog
14
+ from PySide6.QtWidgets import QListWidget, QListWidgetItem, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QDialog
15
+ from PySide6.QtCore import Qt
16
+
17
+
18
+ __version__ = "0.1.1"
19
+ __version_date__ = "2025-11-28"
20
+ __plugin_name__ = "Export observations to FERAL"
21
+ __author__ = "Olivier Friard - University of Torino - Italy"
22
+
23
+
24
+ class CategoryDialog(QDialog):
25
+ def __init__(self, items, parent=None):
26
+ super().__init__(parent)
27
+
28
+ self.setWindowTitle("Organize the videos in categories")
29
+
30
+ self.setModal(True)
31
+
32
+ # Main layout
33
+ main_layout = QVBoxLayout(self)
34
+ lists_layout = QHBoxLayout()
35
+
36
+ # All videos
37
+ self.list_unclassified = self._create_list_widget()
38
+ self.label_unclassified = QLabel("All videos")
39
+ col0_layout = QVBoxLayout()
40
+ col0_layout.addWidget(self.label_unclassified)
41
+ col0_layout.addWidget(self.list_unclassified)
42
+
43
+ self.list_cat1 = self._create_list_widget()
44
+ self.label_cat1 = QLabel("train")
45
+ col1_layout = QVBoxLayout()
46
+ col1_layout.addWidget(self.label_cat1)
47
+ col1_layout.addWidget(self.list_cat1)
48
+
49
+ self.list_cat2 = self._create_list_widget()
50
+ self.label_cat2 = QLabel("val")
51
+ col2_layout = QVBoxLayout()
52
+ col2_layout.addWidget(self.label_cat2)
53
+ col2_layout.addWidget(self.list_cat2)
54
+
55
+ self.list_cat3 = self._create_list_widget()
56
+ self.label_cat3 = QLabel("test")
57
+ col3_layout = QVBoxLayout()
58
+ col3_layout.addWidget(self.label_cat3)
59
+ col3_layout.addWidget(self.list_cat3)
60
+
61
+ self.list_cat4 = self._create_list_widget()
62
+ self.label_cat4 = QLabel("inference")
63
+ col4_layout = QVBoxLayout()
64
+ col4_layout.addWidget(self.label_cat4)
65
+ col4_layout.addWidget(self.list_cat4)
66
+
67
+ # Add all columns to the horizontal layout
68
+ lists_layout.addLayout(col0_layout)
69
+ lists_layout.addLayout(col1_layout)
70
+ lists_layout.addLayout(col2_layout)
71
+ lists_layout.addLayout(col3_layout)
72
+ lists_layout.addLayout(col4_layout)
73
+
74
+ main_layout.addLayout(lists_layout)
75
+
76
+ buttons_layout = QHBoxLayout()
77
+ self.btn_ok = QPushButton("OK")
78
+ self.btn_cancel = QPushButton("Cancel")
79
+
80
+ self.btn_ok.clicked.connect(self.accept)
81
+ self.btn_cancel.clicked.connect(self.reject)
82
+
83
+ buttons_layout.addStretch()
84
+ buttons_layout.addWidget(self.btn_ok)
85
+ buttons_layout.addWidget(self.btn_cancel)
86
+
87
+ main_layout.addLayout(buttons_layout)
88
+
89
+ # Populate "Unclassified" with input items
90
+ for text in items:
91
+ QListWidgetItem(text, self.list_unclassified)
92
+
93
+ def _create_list_widget(self):
94
+ """
95
+ Create a QListWidget ready for drag & drop.
96
+ """
97
+ lw = QListWidget()
98
+ lw.setSelectionMode(QListWidget.ExtendedSelection)
99
+ lw.setDragEnabled(True)
100
+ lw.setAcceptDrops(True)
101
+ lw.setDropIndicatorShown(True)
102
+ lw.setDragDropMode(QListWidget.DragDrop)
103
+ lw.setDefaultDropAction(Qt.MoveAction)
104
+ return lw
105
+
106
+ def get_categories(self):
107
+ """
108
+ Return the content of all categories as a dictionary of lists.
109
+ """
110
+
111
+ def collect(widget):
112
+ return [widget.item(i).text().rstrip("*") for i in range(widget.count())]
113
+
114
+ return {
115
+ "unclassified": collect(self.list_unclassified),
116
+ "train": collect(self.list_cat1),
117
+ "val": collect(self.list_cat2),
118
+ "test": collect(self.list_cat3),
119
+ "inference": collect(self.list_cat4),
120
+ }
121
+
122
+
123
+ def run(df: pd.DataFrame, project: dict):
124
+ """
125
+ Export observations to FERAL
126
+ See https://www.getferal.ai/ > Label Preparation
127
+ """
128
+
129
+ out: dict = {
130
+ "is_multilabel": False,
131
+ "splits": {
132
+ "train": [],
133
+ "val": [],
134
+ "test": [],
135
+ "inference": [],
136
+ },
137
+ }
138
+
139
+ log: list = []
140
+
141
+ # class names
142
+ class_names = {x: project["behaviors_conf"][x]["code"] for x in project["behaviors_conf"]}
143
+ out["class_names"] = class_names
144
+ reversed_class_names = {project["behaviors_conf"][x]["code"]: int(x) for x in project["behaviors_conf"]}
145
+ log.append(f"{class_names=}")
146
+
147
+ observations: list = sorted([x for x in project["observations"]])
148
+ log.append(f"Selected observation: {observations}")
149
+
150
+ labels: dict = {}
151
+ video_list: list = []
152
+ for observation_id in observations:
153
+ log.append("---")
154
+ log.append(observation_id)
155
+
156
+ # check number of media file in player #1
157
+ if len(project["observations"][observation_id]["file"]["1"]) != 1:
158
+ log.append(f"The observation {observation_id} contains more than one video")
159
+ continue
160
+
161
+ # check number of coded subjects
162
+ if len(set([x[1] for x in project["observations"][observation_id]["events"]])) > 1:
163
+ log.append(f"The observation {observation_id} contains more than one subject")
164
+ continue
165
+
166
+ media_file_path: str = project["observations"][observation_id]["file"]["1"][0]
167
+ media_file_name = str(Path(media_file_path).name)
168
+
169
+ # skip if no events
170
+ if not project["observations"][observation_id]["events"]:
171
+ video_list.append(media_file_name)
172
+ log.append(f"No events for observation {observation_id}")
173
+ continue
174
+ else:
175
+ video_list.append(media_file_name + "*")
176
+
177
+ # extract FPS
178
+ FPS = project["observations"][observation_id]["media_info"]["fps"][media_file_path]
179
+ log.append(f"{media_file_name} {FPS=}")
180
+ # extract media duration
181
+ duration = project["observations"][observation_id]["media_info"]["length"][media_file_path]
182
+ log.append(f"{media_file_name} {duration=}")
183
+
184
+ number_of_frames = int(duration / (1 / FPS))
185
+ log.append(f"{number_of_frames=}")
186
+
187
+ labels[media_file_name] = [0] * number_of_frames
188
+
189
+ for idx in range(number_of_frames):
190
+ t = idx * (1 / FPS)
191
+ behaviors = (
192
+ df[(df["Observation id"] == observation_id) & (df["Start (s)"] <= t) & (df["Stop (s)"] >= t)]["Behavior"].unique().tolist()
193
+ )
194
+ if len(behaviors) > 1:
195
+ log.append(f"The observation {observation_id} contains more than one behavior for frame {idx}")
196
+ del labels[media_file_name]
197
+ break
198
+ if behaviors:
199
+ behaviors_idx = reversed_class_names[behaviors[0]]
200
+ labels[media_file_name][idx] = behaviors_idx
201
+
202
+ out["labels"] = labels
203
+
204
+ # splits
205
+ dlg = CategoryDialog(video_list)
206
+
207
+ if dlg.exec(): # Dialog accepted
208
+ result = dlg.get_categories()
209
+ del result["unclassified"]
210
+ out["splits"] = result
211
+
212
+ filename, _ = QFileDialog.getSaveFileName(
213
+ None,
214
+ "Choose a file to save",
215
+ "", # start directory
216
+ "JSON files (*.json);;All files (*.*)",
217
+ )
218
+ if filename:
219
+ with open(filename, "w") as f_out:
220
+ f_out.write(json.dumps(out, separators=(",", ": "), indent=1))
221
+
222
+ else:
223
+ log.append("splits section missing")
224
+
225
+ return "\n".join(log)
@@ -119,12 +119,12 @@ class BehaviorsMapCreatorWindow(QMainWindow):
119
119
  self.saveMapAction.setShortcut("Ctrl+S")
120
120
  self.saveMapAction.setStatusTip("Save the behavior coding map")
121
121
  self.saveMapAction.setEnabled(False)
122
- self.saveMapAction.triggered.connect(self.saveMap_clicked)
122
+ self.saveMapAction.triggered.connect(self.save_map_clicked)
123
123
 
124
124
  self.saveAsMapAction = QAction(QIcon(), "Save the behavior coding map as ...", self)
125
125
  self.saveAsMapAction.setStatusTip("Save the behavior coding map as ...")
126
126
  self.saveAsMapAction.setEnabled(False)
127
- self.saveAsMapAction.triggered.connect(self.saveAsMap_clicked)
127
+ self.saveAsMapAction.triggered.connect(self.save_as_map_clicked)
128
128
 
129
129
  self.mapNameAction = QAction(QIcon(), "&Edit name of behaviors coding map", self)
130
130
  self.mapNameAction.setShortcut("Ctrl+M")
@@ -389,7 +389,7 @@ class BehaviorsMapCreatorWindow(QMainWindow):
389
389
  )
390
390
 
391
391
  if response == cfg.SAVE:
392
- if not self.saveMap_clicked():
392
+ if not self.save_map_clicked():
393
393
  event.ignore()
394
394
 
395
395
  if response == cfg.CANCEL:
@@ -615,7 +615,7 @@ class BehaviorsMapCreatorWindow(QMainWindow):
615
615
  )
616
616
 
617
617
  if response == cfg.SAVE:
618
- if not self.saveMap_clicked():
618
+ if not self.save_map_clicked():
619
619
  return
620
620
 
621
621
  if response == cfg.CANCEL:
@@ -659,7 +659,7 @@ class BehaviorsMapCreatorWindow(QMainWindow):
659
659
  ["Save", "Discard", "Cancel"],
660
660
  )
661
661
 
662
- if (response == "Save" and not self.saveMap_clicked()) or (response == "Cancel"):
662
+ if (response == "Save" and not self.save_map_clicked()) or (response == "Cancel"):
663
663
  return
664
664
 
665
665
  fileName, _ = QFileDialog(self).getOpenFileName(
@@ -783,7 +783,7 @@ class BehaviorsMapCreatorWindow(QMainWindow):
783
783
  else:
784
784
  return False
785
785
 
786
- def saveAsMap_clicked(self):
786
+ def save_as_map_clicked(self):
787
787
  filters = "Behaviors coding map (*.behav_coding_map);;All files (*)"
788
788
 
789
789
  self.fileName, _ = QFileDialog.getSaveFileName(self, "Save behaviors coding map as", "", filters)
@@ -794,7 +794,7 @@ class BehaviorsMapCreatorWindow(QMainWindow):
794
794
  self.fileName += ".behav_coding_map"
795
795
  self.saveMap()
796
796
 
797
- def saveMap_clicked(self):
797
+ def save_map_clicked(self):
798
798
  if not self.fileName:
799
799
  self.fileName, _ = QFileDialog().getSaveFileName(
800
800
  self,
boris/coding_pad.py CHANGED
@@ -38,7 +38,7 @@ class Button(QWidget):
38
38
 
39
39
 
40
40
  class CodingPad(QWidget):
41
- clickSignal = Signal(str)
41
+ click_signal = Signal(str)
42
42
  sendEventSignal = Signal(QEvent)
43
43
  close_signal = Signal(QRect, dict)
44
44
 
@@ -208,7 +208,8 @@ class CodingPad(QWidget):
208
208
  """
209
209
  Button clicked
210
210
  """
211
- self.clickSignal.emit(behavior_code)
211
+ print(f"{behavior_code=}")
212
+ self.click_signal.emit(behavior_code)
212
213
 
213
214
  def eventFilter(self, receiver, event) -> bool:
214
215
  """
@@ -261,7 +262,7 @@ def show_coding_pad(self):
261
262
  self.codingpad.setWindowFlags(Qt.WindowStaysOnTopHint)
262
263
  self.codingpad.sendEventSignal.connect(self.signal_from_widget)
263
264
 
264
- self.codingpad.clickSignal.connect(self.click_signal_from_coding_pad)
265
+ self.codingpad.click_signal.connect(self.click_signal_from_coding_pad)
265
266
  self.codingpad.close_signal.connect(self.close_signal_from_coding_pad)
266
267
  self.codingpad.show()
267
268
 
boris/config.py CHANGED
@@ -132,6 +132,8 @@ POINT_EVENT_PLOT_COLOR = "black"
132
132
 
133
133
  CHAR_FORBIDDEN_IN_MODIFIERS = "(|),`~"
134
134
 
135
+ FAST_FORWARD_DEFAULT_VALUE: float = 10.0
136
+
135
137
  ADAPT_FAST_JUMP = "adapt_fast_jump"
136
138
  ADAPT_FAST_JUMP_DEFAULT = False
137
139
 
@@ -419,6 +421,10 @@ MPV_HWDEC_AUTOSAFE = "auto-safe"
419
421
  MPV_HWDEC_OPTIONS = (MPV_HWDEC_AUTO, MPV_HWDEC_AUTOSAFE, MPV_HWDEC_NO)
420
422
  MPV_HWDEC_DEFAULT_VALUE = MPV_HWDEC_AUTO
421
423
 
424
+ # frame step size (disabled)
425
+ # FRAME_STEP_SIZE: str = "frame_step_size"
426
+ # FRAME_STEP_SIZE_DEFAULT_VALUE: int = 1
427
+
422
428
  ANALYSIS_PLUGINS = "analysis_plugins"
423
429
  EXCLUDED_PLUGINS = "excluded_plugins"
424
430
  PERSONAL_PLUGINS_DIR = "personal_plugins_dir"
@@ -730,6 +736,7 @@ INIT_PARAM = {
730
736
  MPV_HWDEC: MPV_HWDEC_DEFAULT_VALUE,
731
737
  PROJECT_FILE_INDENTATION: PROJECT_FILE_INDENTATION_DEFAULT_VALUE,
732
738
  f"{MEDIA} tw fields": MEDIA_TW_EVENTS_FIELDS_DEFAULT,
739
+ # FRAME_STEP_SIZE: FRAME_STEP_SIZE_DEFAULT_VALUE,
733
740
  }
734
741
 
735
742
  SDIS_EXT = "sds"
boris/config_file.py CHANGED
@@ -82,11 +82,11 @@ def read(self):
82
82
 
83
83
  logging.debug(f"time format: {self.timeFormat}")
84
84
 
85
- self.fast = 10
85
+ self.fast = cfg.FAST_FORWARD_DEFAULT_VALUE
86
86
  try:
87
- self.fast = int(settings.value("Time/fast_forward_speed"))
87
+ self.fast = float(settings.value("Time/fast_forward_speed"))
88
88
  except Exception:
89
- self.fast = 10
89
+ self.fast = cfg.FAST_FORWARD_DEFAULT_VALUE
90
90
 
91
91
  logging.debug(f"Time/fast_forward_speed: {self.fast}")
92
92