boris-behav-obs 9.7.7__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.
- boris/__init__.py +26 -0
- boris/__main__.py +25 -0
- boris/about.py +143 -0
- boris/add_modifier.py +635 -0
- boris/add_modifier_ui.py +303 -0
- boris/advanced_event_filtering.py +455 -0
- boris/analysis_plugins/__init__.py +0 -0
- boris/analysis_plugins/_latency.py +59 -0
- boris/analysis_plugins/irr_cohen_kappa.py +109 -0
- boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
- boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
- boris/analysis_plugins/number_of_occurences.py +22 -0
- boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
- boris/analysis_plugins/time_budget.py +61 -0
- boris/behav_coding_map_creator.py +1110 -0
- boris/behavior_binary_table.py +305 -0
- boris/behaviors_coding_map.py +239 -0
- boris/boris_cli.py +340 -0
- boris/cmd_arguments.py +49 -0
- boris/coding_pad.py +280 -0
- boris/config.py +785 -0
- boris/config_file.py +356 -0
- boris/connections.py +409 -0
- boris/converters.py +333 -0
- boris/converters_ui.py +225 -0
- boris/cooccurence.py +250 -0
- boris/core.py +5901 -0
- boris/core_qrc.py +15958 -0
- boris/core_ui.py +1107 -0
- boris/db_functions.py +324 -0
- boris/dev.py +134 -0
- boris/dialog.py +1108 -0
- boris/duration_widget.py +238 -0
- boris/edit_event.py +245 -0
- boris/edit_event_ui.py +233 -0
- boris/event_operations.py +1040 -0
- boris/events_cursor.py +61 -0
- boris/events_snapshots.py +596 -0
- boris/exclusion_matrix.py +141 -0
- boris/export_events.py +1006 -0
- boris/export_observation.py +1203 -0
- boris/external_processes.py +332 -0
- boris/geometric_measurement.py +941 -0
- boris/gui_utilities.py +135 -0
- boris/image_overlay.py +72 -0
- boris/import_observations.py +242 -0
- boris/ipc_mpv.py +325 -0
- boris/irr.py +634 -0
- boris/latency.py +244 -0
- boris/measurement_widget.py +161 -0
- boris/media_file.py +115 -0
- boris/menu_options.py +213 -0
- boris/modifier_coding_map_creator.py +1013 -0
- boris/modifiers_coding_map.py +157 -0
- boris/mpv.py +2016 -0
- boris/mpv2.py +2193 -0
- boris/observation.py +1453 -0
- boris/observation_operations.py +2538 -0
- boris/observation_ui.py +679 -0
- boris/observations_list.py +337 -0
- boris/otx_parser.py +442 -0
- boris/param_panel.py +201 -0
- boris/param_panel_ui.py +305 -0
- boris/player_dock_widget.py +198 -0
- boris/plot_data_module.py +536 -0
- boris/plot_events.py +634 -0
- boris/plot_events_rt.py +237 -0
- boris/plot_spectrogram_rt.py +316 -0
- boris/plot_waveform_rt.py +230 -0
- boris/plugins.py +431 -0
- boris/portion/__init__.py +31 -0
- boris/portion/const.py +95 -0
- boris/portion/dict.py +365 -0
- boris/portion/func.py +52 -0
- boris/portion/interval.py +581 -0
- boris/portion/io.py +181 -0
- boris/preferences.py +510 -0
- boris/preferences_ui.py +770 -0
- boris/project.py +2007 -0
- boris/project_functions.py +2041 -0
- boris/project_import_export.py +1096 -0
- boris/project_ui.py +794 -0
- boris/qrc_boris.py +10389 -0
- boris/qrc_boris5.py +2579 -0
- boris/select_modifiers.py +312 -0
- boris/select_observations.py +210 -0
- boris/select_subj_behav.py +286 -0
- boris/state_events.py +197 -0
- boris/subjects_pad.py +106 -0
- boris/synthetic_time_budget.py +290 -0
- boris/time_budget_functions.py +1136 -0
- boris/time_budget_widget.py +1039 -0
- boris/transitions.py +365 -0
- boris/utilities.py +1810 -0
- boris/version.py +24 -0
- boris/video_equalizer.py +159 -0
- boris/video_equalizer_ui.py +248 -0
- boris/video_operations.py +310 -0
- boris/view_df.py +104 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +538 -0
- boris_behav_obs-9.7.7.dist-info/METADATA +139 -0
- boris_behav_obs-9.7.7.dist-info/RECORD +109 -0
- boris_behav_obs-9.7.7.dist-info/WHEEL +5 -0
- boris_behav_obs-9.7.7.dist-info/entry_points.txt +2 -0
- boris_behav_obs-9.7.7.dist-info/licenses/LICENSE.TXT +674 -0
- boris_behav_obs-9.7.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BORIS
|
|
3
|
+
Behavioral Observation Research Interactive Software
|
|
4
|
+
Copyright 2012-2025 Olivier Friard
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
This program is free software; you can redistribute it and/or modify
|
|
8
|
+
it under the terms of the GNU General Public License as published by
|
|
9
|
+
the Free Software Foundation; either version 2 of the License, or
|
|
10
|
+
(at your option) any later version.
|
|
11
|
+
|
|
12
|
+
This program is distributed in the hope that it will be useful,
|
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
GNU General Public License for more details.
|
|
16
|
+
|
|
17
|
+
You should have received a copy of the GNU General Public License
|
|
18
|
+
along with this program; if not, write to the Free Software
|
|
19
|
+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
20
|
+
MA 02110-1301, USA.
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import logging
|
|
25
|
+
import pathlib as pl
|
|
26
|
+
import shutil
|
|
27
|
+
from math import log2
|
|
28
|
+
|
|
29
|
+
from PySide6.QtWidgets import QFileDialog
|
|
30
|
+
|
|
31
|
+
from . import config as cfg
|
|
32
|
+
from . import dialog
|
|
33
|
+
|
|
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
|
+
|
|
46
|
+
def snapshot(self):
|
|
47
|
+
"""
|
|
48
|
+
MEDIA obs: take snapshot of current video at current position
|
|
49
|
+
IMAGES obs: save a copy of the current image
|
|
50
|
+
|
|
51
|
+
snapshot is saved on media path following the template: MEDIA-FILE-NAME_TIME-POSITION.png
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
if self.playerType == cfg.MEDIA:
|
|
55
|
+
for i, player in enumerate(self.dw_player):
|
|
56
|
+
if (
|
|
57
|
+
str(i + 1) in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE]
|
|
58
|
+
and self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][str(i + 1)]
|
|
59
|
+
):
|
|
60
|
+
p = pl.Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"])
|
|
61
|
+
|
|
62
|
+
snapshot_file_path = str(p.parent / f"{p.stem}_{player.player.time_pos:0.3f}.png")
|
|
63
|
+
|
|
64
|
+
player.player.screenshot_to_file(snapshot_file_path)
|
|
65
|
+
self.statusbar.showMessage(f"Video snapshot saved in {snapshot_file_path}", 0)
|
|
66
|
+
|
|
67
|
+
logging.debug(f"video snapshot saved in {snapshot_file_path}")
|
|
68
|
+
|
|
69
|
+
if self.playerType == cfg.IMAGES:
|
|
70
|
+
output_file_name, _ = QFileDialog().getSaveFileName(
|
|
71
|
+
self, "Save copy of the current image", pl.Path(self.images_list[self.image_idx]).name
|
|
72
|
+
)
|
|
73
|
+
if output_file_name:
|
|
74
|
+
shutil.copyfile(self.images_list[self.image_idx], output_file_name)
|
|
75
|
+
self.statusbar.showMessage(f"Image saved in {output_file_name}", 0)
|
|
76
|
+
|
|
77
|
+
logging.debug(f"video snapshot saved in {output_file_name}")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def zoom_level(self):
|
|
81
|
+
"""
|
|
82
|
+
display dialog box for setting the zoom level
|
|
83
|
+
"""
|
|
84
|
+
logging.info("change zoom level of player")
|
|
85
|
+
|
|
86
|
+
players_list: list = []
|
|
87
|
+
for idx, dw in enumerate(self.dw_player):
|
|
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")
|
|
91
|
+
if not zl.exec_():
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
if cfg.ZOOM_LEVEL not in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO]:
|
|
95
|
+
self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.ZOOM_LEVEL] = {}
|
|
96
|
+
|
|
97
|
+
for idx, dw in enumerate(self.dw_player):
|
|
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()))
|
|
103
|
+
|
|
104
|
+
logging.debug(f"video zoom changed in {dw.player.video_zoom} for player {idx + 1}")
|
|
105
|
+
|
|
106
|
+
self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.ZOOM_LEVEL][str(idx + 1)] = float(
|
|
107
|
+
zl.elements[f"Player #{idx + 1}"].value()
|
|
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
|
+
|
|
156
|
+
self.project_changed()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def rotate_displayed_video(self):
|
|
160
|
+
"""
|
|
161
|
+
rotate the displayed video
|
|
162
|
+
"""
|
|
163
|
+
players_list: list = []
|
|
164
|
+
for idx, dw in enumerate(self.dw_player):
|
|
165
|
+
rotation_angles: list = []
|
|
166
|
+
for choice in (0, 90, 180, 270):
|
|
167
|
+
rotation_angles.append((str(choice), "selected" if choice == dw.player.video_rotate else ""))
|
|
168
|
+
players_list.append(("il", f"Player #{idx + 1}", rotation_angles))
|
|
169
|
+
|
|
170
|
+
w = dialog.Input_dialog(label_caption="Select the rotation angle", elements_list=players_list, title="Video rotation angle")
|
|
171
|
+
if not w.exec_():
|
|
172
|
+
return
|
|
173
|
+
if cfg.ROTATION_ANGLE not in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO]:
|
|
174
|
+
self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.ROTATION_ANGLE] = {}
|
|
175
|
+
|
|
176
|
+
for idx, dw in enumerate(self.dw_player):
|
|
177
|
+
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.ROTATION_ANGLE].get(
|
|
178
|
+
str(idx + 1), dw.player.video_rotate
|
|
179
|
+
) != float(w.elements[f"Player #{idx + 1}"].currentText()):
|
|
180
|
+
dw.player.video_rotate = int(w.elements[f"Player #{idx + 1}"].currentText())
|
|
181
|
+
|
|
182
|
+
logging.debug(f"video rotation changed to {dw.player.video_rotate} for player {idx + 1}")
|
|
183
|
+
|
|
184
|
+
self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.ROTATION_ANGLE][str(idx + 1)] = int(
|
|
185
|
+
w.elements[f"Player #{idx + 1}"].currentText()
|
|
186
|
+
)
|
|
187
|
+
self.project_changed()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def display_subtitles(self):
|
|
191
|
+
"""
|
|
192
|
+
display dialog for subtitles display
|
|
193
|
+
"""
|
|
194
|
+
players_list = []
|
|
195
|
+
for idx, dw in enumerate(self.dw_player):
|
|
196
|
+
if cfg.DISPLAY_MEDIA_SUBTITLES in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO]:
|
|
197
|
+
default = self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.DISPLAY_MEDIA_SUBTITLES].get(
|
|
198
|
+
str(idx + 1), dw.player.sub_visibility
|
|
199
|
+
)
|
|
200
|
+
else:
|
|
201
|
+
default = dw.player.sub_visibility
|
|
202
|
+
players_list.append(("cb", f"Player #{idx + 1}", default))
|
|
203
|
+
|
|
204
|
+
st = dialog.Input_dialog("Display subtitles", players_list)
|
|
205
|
+
if not st.exec_():
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
if cfg.DISPLAY_MEDIA_SUBTITLES not in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO]:
|
|
209
|
+
self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.DISPLAY_MEDIA_SUBTITLES] = {}
|
|
210
|
+
|
|
211
|
+
for idx, dw in enumerate(self.dw_player):
|
|
212
|
+
if (
|
|
213
|
+
self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.DISPLAY_MEDIA_SUBTITLES].get(
|
|
214
|
+
str(idx + 1), dw.player.sub_visibility
|
|
215
|
+
)
|
|
216
|
+
!= st.elements[f"Player #{idx + 1}"].isChecked()
|
|
217
|
+
):
|
|
218
|
+
dw.player.sub_visibility = st.elements[f"Player #{idx + 1}"].isChecked()
|
|
219
|
+
|
|
220
|
+
logging.debug(f"subtitle visibility for player {idx + 1}: {dw.player.sub_visibility}")
|
|
221
|
+
|
|
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()
|
|
225
|
+
self.project_changed()
|
|
226
|
+
|
|
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
|
+
if player.player.video_zoom is not None:
|
|
239
|
+
msg += f"{2**player.player.video_zoom:.1f} "
|
|
240
|
+
else:
|
|
241
|
+
msg += "NA "
|
|
242
|
+
msg += "</b>"
|
|
243
|
+
self.lb_zoom_level.setText(msg)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def display_play_rate(self) -> None:
|
|
247
|
+
"""
|
|
248
|
+
display current play rate in status bar widget
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
self.lb_video_info.setText(f"Play rate: <b>x{self.play_rate:.3f}</b>")
|
|
252
|
+
|
|
253
|
+
logging.debug(f"play rate: {self.play_rate:.3f}")
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def video_normalspeed_activated(self):
|
|
257
|
+
"""
|
|
258
|
+
set playing speed at normal speed (1x)
|
|
259
|
+
"""
|
|
260
|
+
|
|
261
|
+
if self.playerType != cfg.MEDIA:
|
|
262
|
+
return
|
|
263
|
+
|
|
264
|
+
self.play_rate = 1
|
|
265
|
+
for i, player in enumerate(self.dw_player):
|
|
266
|
+
if (
|
|
267
|
+
str(i + 1) in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE]
|
|
268
|
+
and self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][str(i + 1)]
|
|
269
|
+
):
|
|
270
|
+
player.player.speed = self.play_rate
|
|
271
|
+
|
|
272
|
+
display_play_rate(self)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def video_faster_activated(self):
|
|
276
|
+
"""
|
|
277
|
+
increase playing speed by play_rate_step value
|
|
278
|
+
"""
|
|
279
|
+
|
|
280
|
+
if self.playerType != cfg.MEDIA:
|
|
281
|
+
return
|
|
282
|
+
|
|
283
|
+
if self.play_rate + self.play_rate_step <= 60:
|
|
284
|
+
self.play_rate += self.play_rate_step
|
|
285
|
+
for i, player in enumerate(self.dw_player):
|
|
286
|
+
if (
|
|
287
|
+
str(i + 1) in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE]
|
|
288
|
+
and self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][str(i + 1)]
|
|
289
|
+
):
|
|
290
|
+
player.player.speed = self.play_rate
|
|
291
|
+
print("speed")
|
|
292
|
+
|
|
293
|
+
display_play_rate(self)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def video_slower_activated(self):
|
|
297
|
+
"""
|
|
298
|
+
decrease playing speed by play_rate_step value
|
|
299
|
+
"""
|
|
300
|
+
|
|
301
|
+
if self.playerType != cfg.MEDIA:
|
|
302
|
+
return
|
|
303
|
+
|
|
304
|
+
if self.play_rate - self.play_rate_step >= 0.1:
|
|
305
|
+
self.play_rate -= self.play_rate_step
|
|
306
|
+
|
|
307
|
+
for i, player in enumerate(self.dw_player):
|
|
308
|
+
player.player.speed = round(self.play_rate, 3)
|
|
309
|
+
|
|
310
|
+
display_play_rate(self)
|
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
|
+
|