boris-behav-obs 8.12__py3-none-any.whl → 9.7.6__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.

Potentially problematic release.


This version of boris-behav-obs might be problematic. Click here for more details.

Files changed (128) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +28 -39
  4. boris/add_modifier.py +122 -109
  5. boris/add_modifier_ui.py +239 -135
  6. boris/advanced_event_filtering.py +81 -45
  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 +42 -49
  23. boris/config.py +141 -65
  24. boris/config_file.py +58 -67
  25. boris/connections.py +107 -61
  26. boris/converters.py +13 -37
  27. boris/converters_ui.py +187 -110
  28. boris/cooccurence.py +250 -0
  29. boris/core.py +2373 -1786
  30. boris/core_qrc.py +15895 -10743
  31. boris/core_ui.py +943 -798
  32. boris/db_functions.py +17 -42
  33. boris/dev.py +109 -8
  34. boris/dialog.py +482 -236
  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 +408 -293
  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 +184 -223
  43. boris/export_observation.py +74 -100
  44. boris/external_processes.py +123 -98
  45. boris/geometric_measurement.py +644 -290
  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 +325 -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 +17 -6
  55. boris/modifier_coding_map_creator.py +1013 -0
  56. boris/modifiers_coding_map.py +7 -9
  57. boris/mpv.py +1 -0
  58. boris/mpv2.py +732 -705
  59. boris/observation.py +533 -221
  60. boris/observation_operations.py +1025 -390
  61. boris/observation_ui.py +572 -362
  62. boris/observations_list.py +71 -53
  63. boris/otx_parser.py +74 -68
  64. boris/param_panel.py +31 -16
  65. boris/param_panel_ui.py +254 -138
  66. boris/player_dock_widget.py +90 -60
  67. boris/plot_data_module.py +25 -33
  68. boris/plot_events.py +127 -90
  69. boris/plot_events_rt.py +17 -31
  70. boris/plot_spectrogram_rt.py +95 -30
  71. boris/plot_waveform_rt.py +32 -21
  72. boris/plugins.py +431 -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 +306 -83
  80. boris/preferences_ui.py +684 -227
  81. boris/project.py +448 -293
  82. boris/project_functions.py +671 -238
  83. boris/project_import_export.py +213 -222
  84. boris/project_ui.py +674 -438
  85. boris/qrc_boris.py +6 -3
  86. boris/qrc_boris5.py +6 -3
  87. boris/select_modifiers.py +74 -48
  88. boris/select_observations.py +20 -198
  89. boris/select_subj_behav.py +67 -39
  90. boris/state_events.py +52 -35
  91. boris/subjects_pad.py +6 -9
  92. boris/synthetic_time_budget.py +45 -28
  93. boris/time_budget_functions.py +171 -171
  94. boris/time_budget_widget.py +84 -114
  95. boris/transitions.py +41 -47
  96. boris/utilities.py +627 -236
  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 +95 -29
  101. boris/view_df.py +104 -0
  102. boris/view_df_ui.py +75 -0
  103. boris/write_event.py +538 -0
  104. boris_behav_obs-9.7.6.dist-info/METADATA +139 -0
  105. boris_behav_obs-9.7.6.dist-info/RECORD +109 -0
  106. {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.dist-info}/WHEEL +1 -1
  107. boris_behav_obs-9.7.6.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 -36
  112. boris/core.ui +0 -1556
  113. boris/edit_event.ui +0 -233
  114. boris/icons/logo_eye.ico +0 -0
  115. boris/map_creator.py +0 -850
  116. boris/observation.ui +0 -814
  117. boris/param_panel.ui +0 -379
  118. boris/preferences.ui +0 -537
  119. boris/project.ui +0 -1069
  120. boris/project_server.py +0 -236
  121. boris/vlc.py +0 -10343
  122. boris/vlc_local.py +0 -90
  123. boris_behav_obs-8.12.dist-info/LICENSE.TXT +0 -674
  124. boris_behav_obs-8.12.dist-info/METADATA +0 -128
  125. boris_behav_obs-8.12.dist-info/RECORD +0 -108
  126. boris_behav_obs-8.12.dist-info/entry_points.txt +0 -3
  127. {boris → boris_behav_obs-9.7.6.dist-info/licenses}/LICENSE.TXT +0 -0
  128. {boris_behav_obs-8.12.dist-info → boris_behav_obs-9.7.6.dist-info}/top_level.txt +0 -0
boris/video_operations.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2023 Olivier Friard
4
+ Copyright 2012-2025 Olivier Friard
5
5
 
6
6
 
7
7
  This program is free software; you can redistribute it and/or modify
@@ -26,12 +26,23 @@ import pathlib as pl
26
26
  import shutil
27
27
  from math import log2
28
28
 
29
- from PyQt5.QtWidgets import QFileDialog
29
+ from PySide6.QtWidgets import QFileDialog
30
30
 
31
31
  from . import config as cfg
32
32
  from . import dialog
33
33
 
34
34
 
35
+ def deinterlace(self):
36
+ """
37
+ change the deinterlace status of player
38
+ """
39
+
40
+ logging.info("change deinterlace status of player")
41
+
42
+ for dw in self.dw_player:
43
+ dw.player.deinterlace = self.action_deinterlace.isChecked()
44
+
45
+
35
46
  def snapshot(self):
36
47
  """
37
48
  MEDIA obs: take snapshot of current video at current position
@@ -41,13 +52,11 @@ def snapshot(self):
41
52
  """
42
53
 
43
54
  if self.playerType == cfg.MEDIA:
44
-
45
55
  for i, player in enumerate(self.dw_player):
46
56
  if (
47
57
  str(i + 1) in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE]
48
58
  and self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][str(i + 1)]
49
59
  ):
50
-
51
60
  p = pl.Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"])
52
61
 
53
62
  snapshot_file_path = str(p.parent / f"{p.stem}_{player.player.time_pos:0.3f}.png")
@@ -58,7 +67,6 @@ def snapshot(self):
58
67
  logging.debug(f"video snapshot saved in {snapshot_file_path}")
59
68
 
60
69
  if self.playerType == cfg.IMAGES:
61
-
62
70
  output_file_name, _ = QFileDialog().getSaveFileName(
63
71
  self, "Save copy of the current image", pl.Path(self.images_list[self.image_idx]).name
64
72
  )
@@ -71,18 +79,15 @@ def snapshot(self):
71
79
 
72
80
  def zoom_level(self):
73
81
  """
74
- display dialog for zoom level
82
+ display dialog box for setting the zoom level
75
83
  """
84
+ logging.info("change zoom level of player")
85
+
76
86
  players_list: list = []
77
87
  for idx, dw in enumerate(self.dw_player):
78
- zoom_levels: list = []
79
- for choice in (2, 1, 0.5, 0.25):
80
- zoom_levels.append((str(choice), "selected" if log2(choice) == dw.player.video_zoom else ""))
81
- players_list.append(("il", f"Player #{idx + 1}", zoom_levels))
82
-
83
- zl = dialog.Input_dialog(
84
- label_caption="Select the zoom level", elements_list=players_list, title="Video zoom level"
85
- )
88
+ players_list.append(("dsb", f"Player #{idx + 1}", 0.1, 12, 0.1, 2**dw.player.video_zoom, 1))
89
+
90
+ zl = dialog.Input_dialog(label_caption="Select the zoom level", elements_list=players_list, title="Video zoom level")
86
91
  if not zl.exec_():
87
92
  return
88
93
 
@@ -90,21 +95,64 @@ def zoom_level(self):
90
95
  self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.ZOOM_LEVEL] = {}
91
96
 
92
97
  for idx, dw in enumerate(self.dw_player):
93
- if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.ZOOM_LEVEL].get(
94
- str(idx + 1), dw.player.video_zoom
95
- ) != float(zl.elements[f"Player #{idx + 1}"].currentText()):
96
- dw.player.video_zoom = log2(float(zl.elements[f"Player #{idx + 1}"].currentText()))
97
-
98
- """
99
- dw.player.video_pan_x = 0.25
100
- dw.player.video_pan_x = 0.25
101
- """
98
+ if (
99
+ self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.ZOOM_LEVEL].get(str(idx + 1), dw.player.video_zoom)
100
+ != zl.elements[f"Player #{idx + 1}"].value()
101
+ ):
102
+ dw.player.video_zoom = log2(float(zl.elements[f"Player #{idx + 1}"].value()))
102
103
 
103
104
  logging.debug(f"video zoom changed in {dw.player.video_zoom} for player {idx + 1}")
104
105
 
105
106
  self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.ZOOM_LEVEL][str(idx + 1)] = float(
106
- zl.elements[f"Player #{idx + 1}"].currentText()
107
+ zl.elements[f"Player #{idx + 1}"].value()
107
108
  )
109
+ display_zoom_level(self)
110
+ self.project_changed()
111
+
112
+
113
+ def change_player_offset(self):
114
+ """
115
+ display dialog box for setting the player time offset
116
+ """
117
+ logging.info("change the player time offset")
118
+
119
+ if cfg.OFFSET not in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO]:
120
+ self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET] = {}
121
+
122
+ players_list: list = []
123
+
124
+ for idx, dw in enumerate(self.dw_player):
125
+ players_list.append(
126
+ (
127
+ "dsb",
128
+ f"Player #{idx + 1}",
129
+ -100000,
130
+ 100000,
131
+ 0.001,
132
+ self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(idx + 1)],
133
+ 3,
134
+ )
135
+ )
136
+
137
+ zl = dialog.Input_dialog(label_caption="Select the time offset", elements_list=players_list, title="Time offset")
138
+ if not zl.exec_():
139
+ return
140
+
141
+ for idx, dw in enumerate(self.dw_player):
142
+ if (
143
+ self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET].get(str(idx + 1), 0)
144
+ != zl.elements[f"Player #{idx + 1}"].value()
145
+ ):
146
+ logging.debug(f"time offset of player changed in {zl.elements[f'Player #{idx + 1}'].value()} for player {idx + 1}")
147
+
148
+ self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(idx + 1)] = float(
149
+ zl.elements[f"Player #{idx + 1}"].value()
150
+ )
151
+
152
+ if self.dw_player[0].player.time_pos is not None:
153
+ cumulative_time_pos = self.getLaps() # for player 1
154
+ self.sync_time(idx, cumulative_time_pos)
155
+
108
156
  self.project_changed()
109
157
 
110
158
 
@@ -119,7 +167,7 @@ def rotate_displayed_video(self):
119
167
  rotation_angles.append((str(choice), "selected" if choice == dw.player.video_rotate else ""))
120
168
  players_list.append(("il", f"Player #{idx + 1}", rotation_angles))
121
169
 
122
- w = dialog.Input_dialog(label_caption="Select the zoom level", elements_list=players_list, title="Video zoom level")
170
+ w = dialog.Input_dialog(label_caption="Select the rotation angle", elements_list=players_list, title="Video rotation angle")
123
171
  if not w.exec_():
124
172
  return
125
173
  if cfg.ROTATION_ANGLE not in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO]:
@@ -171,17 +219,34 @@ def display_subtitles(self):
171
219
 
172
220
  logging.debug(f"subtitle visibility for player {idx + 1}: {dw.player.sub_visibility}")
173
221
 
174
- self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.DISPLAY_MEDIA_SUBTITLES][
175
- str(idx + 1)
176
- ] = st.elements[f"Player #{idx + 1}"].isChecked()
222
+ self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.DISPLAY_MEDIA_SUBTITLES][str(idx + 1)] = st.elements[
223
+ f"Player #{idx + 1}"
224
+ ].isChecked()
177
225
  self.project_changed()
178
226
 
179
227
 
228
+ def display_zoom_level(self) -> None:
229
+ """
230
+ display the zoom level
231
+ """
232
+ msg: str = "Zoom level: <b>"
233
+ for player in self.dw_player:
234
+ vz = player.player.video_zoom
235
+ if vz is None:
236
+ self.lb_zoom_level.setText("-")
237
+ return
238
+ msg += f"{2**player.player.video_zoom:.1f} "
239
+ msg += "</b>"
240
+ self.lb_zoom_level.setText(msg)
241
+
242
+
180
243
  def display_play_rate(self) -> None:
181
244
  """
182
245
  display current play rate in status bar widget
183
246
  """
184
- self.lbSpeed.setText(f"Play rate: <b>x{self.play_rate:.3f}</b>")
247
+
248
+ self.lb_video_info.setText(f"Play rate: <b>x{self.play_rate:.3f}</b>")
249
+
185
250
  logging.debug(f"play rate: {self.play_rate:.3f}")
186
251
 
187
252
 
@@ -220,6 +285,7 @@ def video_faster_activated(self):
220
285
  and self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][str(i + 1)]
221
286
  ):
222
287
  player.player.speed = self.play_rate
288
+ print("speed")
223
289
 
224
290
  display_play_rate(self)
225
291
 
boris/view_df.py ADDED
@@ -0,0 +1,104 @@
1
+ from .view_df_ui import Ui_Form
2
+ from PySide6.QtWidgets import QWidget, QFileDialog
3
+ from PySide6.QtCore import Qt, QAbstractTableModel
4
+
5
+ from . import config as cfg
6
+ from pathlib import Path
7
+ from . import dialog
8
+
9
+ try:
10
+ import pyreadr
11
+
12
+ flag_pyreadr_loaded = True
13
+ except ModuleNotFoundError:
14
+ flag_pyreadr_loaded = False
15
+
16
+
17
+ class DataFrameModel(QAbstractTableModel):
18
+ def __init__(self, dataframe):
19
+ super().__init__()
20
+ self._dataframe = dataframe
21
+
22
+ def rowCount(self, parent=None):
23
+ return self._dataframe.shape[0]
24
+
25
+ def columnCount(self, parent=None):
26
+ return self._dataframe.shape[1]
27
+
28
+ def data(self, index, role=Qt.DisplayRole):
29
+ if role == Qt.DisplayRole:
30
+ return str(self._dataframe.iat[index.row(), index.column()])
31
+ return None
32
+
33
+ def headerData(self, section, orientation, role=Qt.DisplayRole):
34
+ if role == Qt.DisplayRole:
35
+ if orientation == Qt.Horizontal:
36
+ return self._dataframe.columns[section]
37
+ elif orientation == Qt.Vertical:
38
+ return self._dataframe.index[section]
39
+ return None
40
+
41
+
42
+ class View_df(QWidget, Ui_Form):
43
+ def __init__(self, plugin_name: str, plugin_version: str, df, parent=None):
44
+ super().__init__()
45
+ self.plugin_name = plugin_name
46
+ self.df = df
47
+
48
+ self.setupUi(self)
49
+ self.lb_plugin_info.setText(f"{plugin_name} v. {plugin_version}")
50
+ self.setWindowTitle(f"{plugin_name} v. {plugin_version}")
51
+
52
+ self.pb_close.clicked.connect(self.close)
53
+ self.pb_save.clicked.connect(self.save)
54
+
55
+ model = DataFrameModel(self.df)
56
+ self.tv_df.setModel(model)
57
+
58
+ def save(self):
59
+ file_formats = (
60
+ cfg.TSV,
61
+ cfg.CSV,
62
+ cfg.ODS,
63
+ cfg.XLSX,
64
+ # cfg.XLS,
65
+ cfg.HTML,
66
+ # cfg.TBS,
67
+ cfg.PANDAS_DF,
68
+ cfg.RDS,
69
+ )
70
+
71
+ file_dialog_options = QFileDialog.Options()
72
+ file_dialog_options |= QFileDialog.DontConfirmOverwrite
73
+
74
+ file_name, filter_ = QFileDialog().getSaveFileName(
75
+ None, f"Save {self.plugin_name}", "", ";;".join(file_formats), options=file_dialog_options
76
+ )
77
+ if not file_name:
78
+ return
79
+
80
+ outputFormat = cfg.FILE_NAME_SUFFIX[filter_]
81
+ if Path(file_name).suffix != "." + outputFormat:
82
+ file_name = f"{file_name}.{outputFormat}"
83
+ if Path(file_name).exists():
84
+ if (
85
+ dialog.MessageDialog(cfg.programName, f"The file {file_name} already exists.", [cfg.CANCEL, cfg.OVERWRITE])
86
+ == cfg.CANCEL
87
+ ):
88
+ return
89
+
90
+ if filter_ == cfg.TSV:
91
+ self.df.to_csv(file_name, sep="\t", index=False)
92
+ if filter_ == cfg.CSV:
93
+ self.df.to_csv(file_name, sep=";", index=False)
94
+ if filter_ == cfg.XLSX:
95
+ self.df.to_excel(file_name, index=False)
96
+ if filter_ == cfg.ODS:
97
+ self.df.to_excel(file_name, index=False, engine="odf")
98
+ if filter_ == cfg.HTML:
99
+ self.df.to_html(file_name, index=False)
100
+ if filter_ == cfg.PANDAS_DF:
101
+ self.df.to_pickle(file_name)
102
+
103
+ if filter_ == cfg.RDS and flag_pyreadr_loaded:
104
+ pyreadr.write_rds(file_name, self.df)
boris/view_df_ui.py ADDED
@@ -0,0 +1,75 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ ################################################################################
4
+ ## Form generated from reading UI file 'view_df.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
+ ################################################################################
10
+
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 (QApplication, QHBoxLayout, QHeaderView, QLabel,
19
+ QPushButton, QSizePolicy, QSpacerItem, QTableView,
20
+ QVBoxLayout, QWidget)
21
+
22
+ class Ui_Form(object):
23
+ def setupUi(self, Form):
24
+ if not Form.objectName():
25
+ Form.setObjectName(u"Form")
26
+ Form.resize(400, 300)
27
+ self.verticalLayout_2 = QVBoxLayout(Form)
28
+ self.verticalLayout_2.setObjectName(u"verticalLayout_2")
29
+ self.lb_plugin_info = QLabel(Form)
30
+ self.lb_plugin_info.setObjectName(u"lb_plugin_info")
31
+
32
+ self.verticalLayout_2.addWidget(self.lb_plugin_info)
33
+
34
+ self.verticalLayout = QVBoxLayout()
35
+ self.verticalLayout.setObjectName(u"verticalLayout")
36
+ self.tv_df = QTableView(Form)
37
+ self.tv_df.setObjectName(u"tv_df")
38
+
39
+ self.verticalLayout.addWidget(self.tv_df)
40
+
41
+ self.horizontalLayout = QHBoxLayout()
42
+ self.horizontalLayout.setObjectName(u"horizontalLayout")
43
+ self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
44
+
45
+ self.horizontalLayout.addItem(self.horizontalSpacer)
46
+
47
+ self.pb_save = QPushButton(Form)
48
+ self.pb_save.setObjectName(u"pb_save")
49
+
50
+ self.horizontalLayout.addWidget(self.pb_save)
51
+
52
+ self.pb_close = QPushButton(Form)
53
+ self.pb_close.setObjectName(u"pb_close")
54
+
55
+ self.horizontalLayout.addWidget(self.pb_close)
56
+
57
+
58
+ self.verticalLayout.addLayout(self.horizontalLayout)
59
+
60
+
61
+ self.verticalLayout_2.addLayout(self.verticalLayout)
62
+
63
+
64
+ self.retranslateUi(Form)
65
+
66
+ QMetaObject.connectSlotsByName(Form)
67
+ # setupUi
68
+
69
+ def retranslateUi(self, Form):
70
+ Form.setWindowTitle(QCoreApplication.translate("Form", u"Form", None))
71
+ self.lb_plugin_info.setText(QCoreApplication.translate("Form", u"TextLabel", None))
72
+ self.pb_save.setText(QCoreApplication.translate("Form", u"Save results", None))
73
+ self.pb_close.setText(QCoreApplication.translate("Form", u"Close", None))
74
+ # retranslateUi
75
+