PaIRS-UniNa 0.2.4__cp311-cp311-win_amd64.whl → 0.2.6__cp311-cp311-win_amd64.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 PaIRS-UniNa might be problematic. Click here for more details.

Files changed (58) hide show
  1. PaIRS_UniNa/Changes.txt +35 -0
  2. PaIRS_UniNa/Custom_Top.py +1 -1
  3. PaIRS_UniNa/Explorer.py +3063 -3049
  4. PaIRS_UniNa/FolderLoop.py +371 -371
  5. PaIRS_UniNa/Input_Tab.py +717 -709
  6. PaIRS_UniNa/Input_Tab_CalVi.py +4 -4
  7. PaIRS_UniNa/Input_Tab_tools.py +3018 -3009
  8. PaIRS_UniNa/Output_Tab.py +2 -2
  9. PaIRS_UniNa/PaIRS.py +17 -17
  10. PaIRS_UniNa/PaIRS_PIV.py +56 -1
  11. PaIRS_UniNa/PaIRS_pypacks.py +323 -60
  12. PaIRS_UniNa/Process_Tab.py +8 -13
  13. PaIRS_UniNa/Process_Tab_Disp.py +9 -4
  14. PaIRS_UniNa/Saving_tools.py +277 -277
  15. PaIRS_UniNa/TabTools.py +63 -21
  16. PaIRS_UniNa/Vis_Tab.py +293 -115
  17. PaIRS_UniNa/Whatsnew.py +13 -0
  18. PaIRS_UniNa/_PaIRS_PIV.pyd +0 -0
  19. PaIRS_UniNa/__init__.py +3 -3
  20. PaIRS_UniNa/gPaIRS.py +3825 -3600
  21. PaIRS_UniNa/icons/flaticon_PaIRS_download_warning.png +0 -0
  22. PaIRS_UniNa/icons/pencil_bw.png +0 -0
  23. PaIRS_UniNa/icons/pylog.png +0 -0
  24. PaIRS_UniNa/icons/python_warning.png +0 -0
  25. PaIRS_UniNa/icons/queue.png +0 -0
  26. PaIRS_UniNa/icons/uninitialized.png +0 -0
  27. PaIRS_UniNa/icons/window.png +0 -0
  28. PaIRS_UniNa/listLib.py +301 -301
  29. PaIRS_UniNa/parForMulti.py +433 -433
  30. PaIRS_UniNa/parForWorkers.py +46 -1
  31. PaIRS_UniNa/pivParFor.py +1 -1
  32. PaIRS_UniNa/procTools.py +17 -7
  33. PaIRS_UniNa/rqrdpckgs.txt +9 -0
  34. PaIRS_UniNa/stereoPivParFor.py +1 -1
  35. PaIRS_UniNa/tabSplitter.py +606 -606
  36. PaIRS_UniNa/ui_Calibration_Tab.py +542 -542
  37. PaIRS_UniNa/ui_Custom_Top.py +294 -294
  38. PaIRS_UniNa/ui_Input_Tab.py +1098 -1098
  39. PaIRS_UniNa/ui_Input_Tab_CalVi.py +1280 -1280
  40. PaIRS_UniNa/ui_Log_Tab.py +261 -261
  41. PaIRS_UniNa/ui_Output_Tab.py +2360 -2360
  42. PaIRS_UniNa/ui_Process_Tab.py +3808 -3808
  43. PaIRS_UniNa/ui_Process_Tab_CalVi.py +1547 -1547
  44. PaIRS_UniNa/ui_Process_Tab_Disp.py +1139 -968
  45. PaIRS_UniNa/ui_Process_Tab_Min.py +435 -435
  46. PaIRS_UniNa/ui_ResizePopup.py +203 -203
  47. PaIRS_UniNa/ui_Vis_Tab.py +1626 -1533
  48. PaIRS_UniNa/ui_Vis_Tab_CalVi.py +1249 -1249
  49. PaIRS_UniNa/ui_Whatsnew.py +131 -131
  50. PaIRS_UniNa/ui_gPairs.py +873 -849
  51. PaIRS_UniNa/ui_infoPaIRS.py +550 -428
  52. PaIRS_UniNa/whatsnew.txt +4 -4
  53. {PaIRS_UniNa-0.2.4.dist-info → pairs_unina-0.2.6.dist-info}/METADATA +47 -30
  54. {PaIRS_UniNa-0.2.4.dist-info → pairs_unina-0.2.6.dist-info}/RECORD +56 -51
  55. {PaIRS_UniNa-0.2.4.dist-info → pairs_unina-0.2.6.dist-info}/WHEEL +1 -1
  56. PaIRS_UniNa/stereo.py +0 -685
  57. PaIRS_UniNa-0.2.4.dist-info/LICENSE +0 -19
  58. {PaIRS_UniNa-0.2.4.dist-info → pairs_unina-0.2.6.dist-info}/top_level.txt +0 -0
PaIRS_UniNa/FolderLoop.py CHANGED
@@ -1,372 +1,372 @@
1
- import sys
2
- from PySide6.QtWidgets import (
3
- QApplication, QDialog, QVBoxLayout, QLabel, QHBoxLayout, QWidget, QSpacerItem,
4
- QPushButton, QProgressBar, QSizePolicy,
5
- QFileDialog, QListView, QAbstractItemView, QTreeView, QFileSystemModel
6
- )
7
- from PySide6.QtGui import QPixmap, QFont, QIcon, QCursor
8
- from PySide6.QtCore import Qt, QTimer, QSize, QVariantAnimation
9
- from time import sleep as timesleep
10
- from pathlib import Path
11
- from typing import Optional, List
1
+ import sys
2
+ from PySide6.QtWidgets import (
3
+ QApplication, QDialog, QVBoxLayout, QLabel, QHBoxLayout, QWidget, QSpacerItem,
4
+ QPushButton, QProgressBar, QSizePolicy,
5
+ QFileDialog, QListView, QAbstractItemView, QTreeView, QFileSystemModel
6
+ )
7
+ from PySide6.QtGui import QPixmap, QFont, QIcon, QCursor
8
+ from PySide6.QtCore import Qt, QTimer, QSize, QVariantAnimation
9
+ from time import sleep as timesleep
10
+ from pathlib import Path
11
+ from typing import Optional, List
12
12
  from .PaIRS_pypacks import fontPixelSize, fontName, icons_path
13
-
14
- time_sleep_loop=0
15
-
16
- def choose_directories(base:Path = Path('.'),parent=None) -> Optional[List[str]]:
17
- """
18
- Open a dialogue to select multiple directories
19
- Args:
20
- base (Path): Starting directory to show when opening dialogue
21
- Returns:
22
- List[str]: List of paths that were selected, ``None`` if "cancel" selected"
23
- References:
24
- Mildly adapted from https://stackoverflow.com/a/28548773
25
- to use outside an exising Qt Application
26
- """
27
-
28
- file_dialog = QFileDialog(parent)
29
- file_dialog.setWindowTitle("Select folders for process loop")
30
- file_dialog.setWindowIcon(QIcon(icons_path + "process_loop.png"))
31
- file_dialog.setWindowIconText("Select folders for process loop")
32
- file_dialog.setOption(QFileDialog.Option.DontUseNativeDialog, True)
33
- file_dialog.setFileMode(QFileDialog.Directory)
34
- for widget_type in (QListView, QTreeView):
35
- for view in file_dialog.findChildren(widget_type):
36
- if isinstance(view.model(), QFileSystemModel):
37
- view.setSelectionMode(
38
- QAbstractItemView.ExtendedSelection)
39
-
40
- paths=[]
41
- if file_dialog.exec():
42
- paths = file_dialog.selectedFiles()
43
- return paths
44
-
45
- class FolderLoopDialog(QDialog):
46
- def __init__(self, pixmap_list, name_list, flag_list, parent=None, func=lambda it, opt: print(f"Iteration {it}"), paths=[], process_name = 'Process', *args, **kwargs):
47
- super().__init__(parent=parent, *args, **kwargs)
48
-
49
- self.func=func
50
- self.nfolders=len(paths)
51
- self.paths=paths
52
-
53
- # Variables for easy customization
54
- self.setWindowTitle("Process loop over folders")
55
- self.setWindowIcon(QIcon(icons_path + "process_loop.png"))
56
- self.setWindowIconText("Process loop over folders")
57
- self.title_text = "Configure each step of the process"
58
- if parent is None:
59
- self.title_font = QFont(fontName, fontPixelSize+12, QFont.Bold)
60
- self.item_font = QFont(fontName, fontPixelSize+8)
61
- self.button_font = QFont(fontName, fontPixelSize+2)
62
- else:
63
- self.title_font:QFont = parent.font()
64
- self.title_font.setBold(True)
65
- self.title_font.setPixelSize(fontPixelSize+12)
66
- self.item_font:QFont = parent.font()
67
- self.item_font = parent.font()
68
- self.item_font.setPixelSize(fontPixelSize+8)
69
- self.button_font: QFont = parent.font()
70
- self.button_font.setPixelSize(fontPixelSize+2)
71
- self.title_height = 48
72
- self.icon_size = QSize(42, 42) # Icon size inside buttons
73
- self.icon_size_off = QSize(24, 24) # Icon size inside buttons when unchecked
74
- self.button_size = QSize(56, 56) # Button size
75
- self.row_spacing = 18 # Spacing between rows
76
- self.margin_size = 5 # Window margin size
77
- self.progress_bar_height = 36
78
-
79
- self.min_height = self.row_spacing + self.margin_size*2 + self.title_height *2 + self.progress_bar_height # min window height
80
- self.max_height = len(name_list) * (self.button_size.height() + self.row_spacing) + self.min_height # Max window height
81
-
82
- # Tooltip texts
83
- self.tooltips = {
84
- "copy": "Copy the item",
85
- "link": "Link the item",
86
- "change_folder": "Change the folder"
87
- }
88
-
89
- # Icons for the buttons
90
- self.icons = {
91
- "copy": [QIcon(icons_path + "copy_process.png"), QIcon(icons_path + "copy_process_off.png")],
92
- "link": [QIcon(icons_path + "link.png"), QIcon(icons_path + "unlink.png")],
93
- "change_folder": [QIcon(icons_path + "change_folder.png"), QIcon(icons_path + "change_folder_off.png")]
94
- }
95
- self.options_list=list(self.icons)
96
-
97
- # Main layout
98
- layout = QVBoxLayout()
99
- layout.setSpacing(self.row_spacing)
100
-
101
- # Left-aligned title
102
- header_layout = QHBoxLayout()
103
- header_layout.setContentsMargins(0,0,0,0)
104
- header_layout.setSpacing(10)
105
-
106
- self.header_icon = QLabel('')
107
- self.header_icon.setPixmap(QPixmap(icons_path + "process_loop.png"))
108
- self.header_icon.setScaledContents(True)
109
- self.header_icon.setFixedSize(self.icon_size)
110
- header_layout.addWidget(self.header_icon)
111
-
112
- self.header_label = QLabel(process_name)
113
- self.header_label.setFont(self.title_font)
114
- self.header_label.setAlignment(Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignVCenter)
115
- self.header_label.setFixedHeight(self.title_height)
116
- header_layout.addWidget(self.header_label)
117
-
118
- layout.addLayout(header_layout)
119
-
120
- # Left-aligned title
121
- title_layout = QHBoxLayout()
122
- title_layout.setContentsMargins(0,0,0,0)
123
- title_layout.setSpacing(10)
124
-
125
- """
126
- self.title_icon = QLabel('')
127
- self.title_icon.setPixmap(QPixmap(icons_path + "process_loop.png"))
128
- self.title_icon.setScaledContents(True)
129
- self.title_icon.setFixedSize(self.icon_size)
130
- title_layout.addWidget(self.title_icon)
131
- """
132
-
133
- self.title_label = QLabel(self.title_text)
134
- self.title_label.setFont(self.title_font)
135
- self.title_label.setAlignment(Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignVCenter)
136
- self.title_label.setFixedHeight(self.title_height)
137
- title_layout.addWidget(self.title_label)
138
-
139
- layout.addLayout(title_layout)
140
-
141
- # List to store button states (0, 1, 2)
142
- self.button_states = [0] * len(name_list)
143
-
144
- # Add the additional n elements
145
- self.buttons_group = []
146
- self.step_options = []
147
- self.widgets = []
148
- self.nstep=len(pixmap_list)
149
- for i in range(self.nstep):
150
- widget = QWidget()
151
- item_layout = QHBoxLayout()
152
- item_layout.setContentsMargins(0,0,0,0)
153
- widget.setLayout(item_layout)
154
- widget.setFixedHeight(self.button_size.height())
155
- self.widgets.append(widget)
156
-
157
- # Label with pixmap (fitted to 32x32 pixels)
158
- pixmap_label = QLabel(self)
159
- pixmap = QPixmap(pixmap_list[i])#.scaled(self.button_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
160
- pixmap_label.setPixmap(pixmap)
161
- pixmap_label.setScaledContents(True)
162
- pixmap_label.setFixedSize(self.button_size)
163
- item_layout.addWidget(pixmap_label)
164
-
165
- spacer=QSpacerItem(10, 0, QSizePolicy.Minimum, QSizePolicy.Minimum)
166
- item_layout.addItem(spacer)
167
-
168
- # Label with text from the name list (larger font)
169
- name_label = QLabel(name_list[i],self)
170
- name_label.setFont(self.item_font)
171
- name_label.setAlignment(Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignVCenter)
172
- item_layout.addWidget(name_label)
173
-
174
- # Stretch the name label to fill the row
175
- item_layout.addStretch()
176
-
177
- # Three checkable buttons with icons
178
- self.step_options.append(-1)
179
- copy_button = self.create_icon_button(i, "copy")
180
- link_button = self.create_icon_button(i, "link")
181
- if flag_list[i]:
182
- folder_button = self.create_icon_button(i, "change_folder")
183
- else:
184
- folder_button = QLabel(self,text='')
185
- folder_button.setFixedWidth(self.button_size.width())
186
- self.buttons_group.append([copy_button, link_button, folder_button])
187
-
188
- item_layout.addWidget(copy_button)
189
- item_layout.addWidget(link_button)
190
- item_layout.addWidget(folder_button)
191
-
192
- layout.addWidget(widget)
193
-
194
- # Progress bar and final buttons (Cancel, Proceed)
195
- progress_widget = QWidget()
196
- progress_layout = QHBoxLayout()
197
- progress_layout.setContentsMargins(0,0,0,0)
198
- progress_layout.setSpacing(10)
199
- progress_widget.setLayout(progress_layout)
200
- progress_widget.setFixedHeight(self.progress_bar_height)
201
- self.progress_bar = QProgressBar(self)
202
- self.progress_bar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
203
- self.progress_bar.setVisible(False) # Hidden initially
204
- self.title_label.adjustSize()
205
- self.progress_bar.setFixedWidth(self.title_label.width())
206
- progress_layout.addWidget(self.progress_bar)
207
-
208
- # Spacer to extend before the buttons
209
- progress_layout.addStretch()
210
-
211
- cancel_button_text = "Cancel"
212
- self.proceed_button_text = "Proceed"
213
- self.stop_button_text = "Stop"
214
-
215
- cancel_button = QPushButton(cancel_button_text,self)
216
- cancel_button.setFixedHeight(self.progress_bar_height)
217
- cancel_button.setFont(self.button_font)
218
- cancel_button.clicked.connect(self.cancel) # Closes the dialog without doing anything
219
- progress_layout.addWidget(cancel_button)
220
-
221
- self.proceed_button = QPushButton(self.proceed_button_text,self)
222
- self.proceed_button.setFixedHeight(self.progress_bar_height)
223
- self.proceed_button.setFont(self.button_font)
224
- self.proceed_button.clicked.connect(self.on_proceed)
225
- progress_layout.addWidget(self.proceed_button)
226
-
227
- layout.addWidget(progress_widget)
228
- self.setLayout(layout)
229
-
230
- # Set window maximum height and margins
231
- self.setFixedHeight(self.max_height)
232
- self.setContentsMargins(self.margin_size*2, self.margin_size, self.margin_size*2, self.margin_size)
233
-
234
- # Timer for progress
235
- self.iteration = 0
236
- self.timer = QTimer(self)
237
- self.timer.timeout.connect(self.update_progress)
238
- self.timer.setSingleShot(True)
239
- self.loop_running = False
240
-
241
- self.animation=None
242
-
243
- def create_icon_button(self, index, button_type):
244
- """Create a checkable button with icon based on its type (copy, link, change_folder)."""
245
- button = QPushButton(self)
246
- button.setCheckable(True)
247
- button.setChecked((button_type=='change_folder' and index==self.nstep-1) or (button_type=='copy' and index<self.nstep-1))
248
- button.setIcon(self.icons[button_type][int(not button.isChecked())]) # Set initial 'off' icon
249
- button.setFixedSize(self.button_size) # Set button size to 32x32 pixels
250
- button.setStyleSheet("QPushButton{border: none;}") # Remove button borders
251
- button.setCursor(QCursor(Qt.PointingHandCursor)) # Set cursor to pointing hand on hover
252
- button.setToolTip(self.tooltips[button_type]) # Set tooltip
253
- button.clicked.connect(lambda: self.update_buttons(index, button_type))
254
- if button.isChecked():
255
- self.step_options[index]=self.options_list.index(button_type)
256
- button.setIconSize(self.icon_size)
257
- else:
258
- button.setIconSize(self.icon_size_off) # Set icon size to 28x28 pixels
259
- return button
260
-
261
- def update_buttons(self, index, button_type):
262
- """Update the icons and states of the buttons, ensuring only one is checked at a time."""
263
- for k, button in enumerate(self.buttons_group[index]):
264
- if not isinstance(button,QPushButton): continue
265
- if button_type!=self.options_list[k]:
266
- button.setChecked(False) # Uncheck all buttons
267
- button.setIconSize(self.icon_size_off)
268
- button.setIcon(self.icons[self.options_list[k]][1])
269
- else:
270
- button.setChecked(True)
271
- button.setIconSize(self.icon_size)
272
- button.setIcon(self.icons[self.options_list[k]][0]) # Checked state icon
273
- self.step_options[index]=k
274
-
275
- def on_proceed(self):
276
- """Start or stop the progress loop depending on the button state."""
277
- if not self.loop_running:
278
- self.start_progress()
279
- else:
280
- self.stop_progress()
281
-
282
- def start_progress(self):
283
- """Start the progress and update button text to 'Stop'."""
284
- self.loop_running = True
285
- self.title_label.setText('Preparing copy...')
286
- self.title_label.setFont(self.item_font)
287
- self.proceed_button.setText(self.stop_button_text)
288
- self.proceed_button.setVisible(False)
289
- self.animate_window_resize()
290
- self.progress_bar.setVisible(True) # Show the progress bar when Proceed is clicked
291
- self.progress_bar.setMaximum(self.nfolders)
292
-
293
- def stop_progress(self):
294
- """Stop the progress and update button text to 'Proceed'."""
295
- self.loop_running = False
296
- self.proceed_button.setText(self.proceed_button_text)
297
- self.timer.stop()
298
-
299
- def update_progress(self):
300
- """Update the progress bar on each timer tick."""
301
- if self.iteration < self.progress_bar.maximum():
302
- timesleep(time_sleep_loop)
303
- self.title_label.setText(self.paths[self.iteration])
304
- self.func(self.iteration,self.step_options)
305
- self.progress_bar.setValue(self.iteration)
306
- self.iteration += 1
307
- if self.loop_running: self.timer.start(0)
308
- else:
309
- self.progress_bar.setValue(self.progress_bar.maximum())
310
- self.stop_progress()
311
- if not self.animation: self.done(0) # Closes the dialog when complete
312
-
313
- def cancel(self):
314
- if self.loop_running: self.stop_progress()
315
- if not self.animation: self.done(0) # Closes the dialog when complete
316
-
317
- def animate_window_resize(self):
318
- for w in self.widgets:
319
- w:QWidget
320
- w.setVisible(False)
321
- self.animation = QVariantAnimation(self)
322
- self.animation.valueChanged.connect(self.window_resize)
323
- self.animation.setDuration(300)
324
- self.animation.setStartValue(self.max_height)
325
- self.animation.setEndValue(self.min_height)
326
- self.animation.finished.connect(self.finishedAnimation)
327
- self.animation.start()
328
-
329
- def finishedAnimation(self):
330
- self.animation=None
331
- self.timer.start(0) # Start or resume the timer
332
-
333
- def window_resize(self, h):
334
- self.setFixedHeight(h)
335
-
336
- if __name__ == "__main__":
337
- import random
338
- import string
339
-
340
- # Function to generate a random string
341
- def generate_random_string(length):
342
- letters = string.ascii_letters # Includes uppercase and lowercase letters
343
- return ''.join(random.choice(letters) for i in range(length))
344
-
345
- # Number of strings and length of each string
346
- num_strings = 10 # Number of strings in the list
347
- string_length = 25 # Length of each string
348
-
349
- # Generate the list of random strings
350
- random_strings_list = [generate_random_string(string_length) for _ in range(num_strings)]
351
-
352
-
353
- app = QApplication(sys.argv)
354
- app.setStyle('Fusion')
355
-
356
- time_sleep_loop=0.01
357
-
358
- print(choose_directories())
359
-
360
- # Example lists
361
- pixmap_list = [icons_path + "cal_step.png",
362
- icons_path + "min_step.png",
363
- icons_path + "disp_step.png",
364
- icons_path + "piv_step.png"]
365
- flag_list = [False,True,True,True]
366
- name_list = ["Camera calibration",
367
- "Image pre-processing",
368
- "Disparity correction",
369
- "Stereoscopic PIV analysis"]
370
-
371
- dialog = FolderLoopDialog(pixmap_list, name_list, flag_list, paths=random_strings_list, process_name='Stereoscopic PIV process 1')
372
- sys.exit(dialog.exec())
13
+
14
+ time_sleep_loop=0
15
+
16
+ def choose_directories(base:Path = Path('.'),parent=None) -> Optional[List[str]]:
17
+ """
18
+ Open a dialogue to select multiple directories
19
+ Args:
20
+ base (Path): Starting directory to show when opening dialogue
21
+ Returns:
22
+ List[str]: List of paths that were selected, ``None`` if "cancel" selected"
23
+ References:
24
+ Mildly adapted from https://stackoverflow.com/a/28548773
25
+ to use outside an exising Qt Application
26
+ """
27
+
28
+ file_dialog = QFileDialog(parent)
29
+ file_dialog.setWindowTitle("Select folders for process loop")
30
+ file_dialog.setWindowIcon(QIcon(icons_path + "process_loop.png"))
31
+ file_dialog.setWindowIconText("Select folders for process loop")
32
+ file_dialog.setOption(QFileDialog.Option.DontUseNativeDialog, True)
33
+ file_dialog.setFileMode(QFileDialog.Directory)
34
+ for widget_type in (QListView, QTreeView):
35
+ for view in file_dialog.findChildren(widget_type):
36
+ if isinstance(view.model(), QFileSystemModel):
37
+ view.setSelectionMode(
38
+ QAbstractItemView.ExtendedSelection)
39
+
40
+ paths=[]
41
+ if file_dialog.exec():
42
+ paths = file_dialog.selectedFiles()
43
+ return paths
44
+
45
+ class FolderLoopDialog(QDialog):
46
+ def __init__(self, pixmap_list, name_list, flag_list, parent=None, func=lambda it, opt: print(f"Iteration {it}"), paths=[], process_name = 'Process', *args, **kwargs):
47
+ super().__init__(parent=parent, *args, **kwargs)
48
+
49
+ self.func=func
50
+ self.nfolders=len(paths)
51
+ self.paths=paths
52
+
53
+ # Variables for easy customization
54
+ self.setWindowTitle("Process loop over folders")
55
+ self.setWindowIcon(QIcon(icons_path + "process_loop.png"))
56
+ self.setWindowIconText("Process loop over folders")
57
+ self.title_text = "Configure each step of the process"
58
+ if parent is None:
59
+ self.title_font = QFont(fontName, fontPixelSize+12, QFont.Bold)
60
+ self.item_font = QFont(fontName, fontPixelSize+8)
61
+ self.button_font = QFont(fontName, fontPixelSize+2)
62
+ else:
63
+ self.title_font:QFont = parent.font()
64
+ self.title_font.setBold(True)
65
+ self.title_font.setPixelSize(fontPixelSize+12)
66
+ self.item_font:QFont = parent.font()
67
+ self.item_font = parent.font()
68
+ self.item_font.setPixelSize(fontPixelSize+8)
69
+ self.button_font: QFont = parent.font()
70
+ self.button_font.setPixelSize(fontPixelSize+2)
71
+ self.title_height = 48
72
+ self.icon_size = QSize(42, 42) # Icon size inside buttons
73
+ self.icon_size_off = QSize(24, 24) # Icon size inside buttons when unchecked
74
+ self.button_size = QSize(56, 56) # Button size
75
+ self.row_spacing = 18 # Spacing between rows
76
+ self.margin_size = 5 # Window margin size
77
+ self.progress_bar_height = 36
78
+
79
+ self.min_height = self.row_spacing + self.margin_size*2 + self.title_height *2 + self.progress_bar_height # min window height
80
+ self.max_height = len(name_list) * (self.button_size.height() + self.row_spacing) + self.min_height # Max window height
81
+
82
+ # Tooltip texts
83
+ self.tooltips = {
84
+ "copy": "Copy the item",
85
+ "link": "Link the item",
86
+ "change_folder": "Change the folder"
87
+ }
88
+
89
+ # Icons for the buttons
90
+ self.icons = {
91
+ "copy": [QIcon(icons_path + "copy_process.png"), QIcon(icons_path + "copy_process_off.png")],
92
+ "link": [QIcon(icons_path + "link.png"), QIcon(icons_path + "unlink.png")],
93
+ "change_folder": [QIcon(icons_path + "change_folder.png"), QIcon(icons_path + "change_folder_off.png")]
94
+ }
95
+ self.options_list=list(self.icons)
96
+
97
+ # Main layout
98
+ layout = QVBoxLayout()
99
+ layout.setSpacing(self.row_spacing)
100
+
101
+ # Left-aligned title
102
+ header_layout = QHBoxLayout()
103
+ header_layout.setContentsMargins(0,0,0,0)
104
+ header_layout.setSpacing(10)
105
+
106
+ self.header_icon = QLabel('')
107
+ self.header_icon.setPixmap(QPixmap(icons_path + "process_loop.png"))
108
+ self.header_icon.setScaledContents(True)
109
+ self.header_icon.setFixedSize(self.icon_size)
110
+ header_layout.addWidget(self.header_icon)
111
+
112
+ self.header_label = QLabel(process_name)
113
+ self.header_label.setFont(self.title_font)
114
+ self.header_label.setAlignment(Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignVCenter)
115
+ self.header_label.setFixedHeight(self.title_height)
116
+ header_layout.addWidget(self.header_label)
117
+
118
+ layout.addLayout(header_layout)
119
+
120
+ # Left-aligned title
121
+ title_layout = QHBoxLayout()
122
+ title_layout.setContentsMargins(0,0,0,0)
123
+ title_layout.setSpacing(10)
124
+
125
+ """
126
+ self.title_icon = QLabel('')
127
+ self.title_icon.setPixmap(QPixmap(icons_path + "process_loop.png"))
128
+ self.title_icon.setScaledContents(True)
129
+ self.title_icon.setFixedSize(self.icon_size)
130
+ title_layout.addWidget(self.title_icon)
131
+ """
132
+
133
+ self.title_label = QLabel(self.title_text)
134
+ self.title_label.setFont(self.title_font)
135
+ self.title_label.setAlignment(Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignVCenter)
136
+ self.title_label.setFixedHeight(self.title_height)
137
+ title_layout.addWidget(self.title_label)
138
+
139
+ layout.addLayout(title_layout)
140
+
141
+ # List to store button states (0, 1, 2)
142
+ self.button_states = [0] * len(name_list)
143
+
144
+ # Add the additional n elements
145
+ self.buttons_group = []
146
+ self.step_options = []
147
+ self.widgets = []
148
+ self.nstep=len(pixmap_list)
149
+ for i in range(self.nstep):
150
+ widget = QWidget()
151
+ item_layout = QHBoxLayout()
152
+ item_layout.setContentsMargins(0,0,0,0)
153
+ widget.setLayout(item_layout)
154
+ widget.setFixedHeight(self.button_size.height())
155
+ self.widgets.append(widget)
156
+
157
+ # Label with pixmap (fitted to 32x32 pixels)
158
+ pixmap_label = QLabel(self)
159
+ pixmap = QPixmap(pixmap_list[i])#.scaled(self.button_size, Qt.KeepAspectRatio, Qt.SmoothTransformation)
160
+ pixmap_label.setPixmap(pixmap)
161
+ pixmap_label.setScaledContents(True)
162
+ pixmap_label.setFixedSize(self.button_size)
163
+ item_layout.addWidget(pixmap_label)
164
+
165
+ spacer=QSpacerItem(10, 0, QSizePolicy.Minimum, QSizePolicy.Minimum)
166
+ item_layout.addItem(spacer)
167
+
168
+ # Label with text from the name list (larger font)
169
+ name_label = QLabel(name_list[i],self)
170
+ name_label.setFont(self.item_font)
171
+ name_label.setAlignment(Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignVCenter)
172
+ item_layout.addWidget(name_label)
173
+
174
+ # Stretch the name label to fill the row
175
+ item_layout.addStretch()
176
+
177
+ # Three checkable buttons with icons
178
+ self.step_options.append(-1)
179
+ copy_button = self.create_icon_button(i, "copy")
180
+ link_button = self.create_icon_button(i, "link")
181
+ if flag_list[i]:
182
+ folder_button = self.create_icon_button(i, "change_folder")
183
+ else:
184
+ folder_button = QLabel(self,text='')
185
+ folder_button.setFixedWidth(self.button_size.width())
186
+ self.buttons_group.append([copy_button, link_button, folder_button])
187
+
188
+ item_layout.addWidget(copy_button)
189
+ item_layout.addWidget(link_button)
190
+ item_layout.addWidget(folder_button)
191
+
192
+ layout.addWidget(widget)
193
+
194
+ # Progress bar and final buttons (Cancel, Proceed)
195
+ progress_widget = QWidget()
196
+ progress_layout = QHBoxLayout()
197
+ progress_layout.setContentsMargins(0,0,0,0)
198
+ progress_layout.setSpacing(10)
199
+ progress_widget.setLayout(progress_layout)
200
+ progress_widget.setFixedHeight(self.progress_bar_height)
201
+ self.progress_bar = QProgressBar(self)
202
+ self.progress_bar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
203
+ self.progress_bar.setVisible(False) # Hidden initially
204
+ self.title_label.adjustSize()
205
+ self.progress_bar.setFixedWidth(self.title_label.width())
206
+ progress_layout.addWidget(self.progress_bar)
207
+
208
+ # Spacer to extend before the buttons
209
+ progress_layout.addStretch()
210
+
211
+ cancel_button_text = "Cancel"
212
+ self.proceed_button_text = "Proceed"
213
+ self.stop_button_text = "Stop"
214
+
215
+ cancel_button = QPushButton(cancel_button_text,self)
216
+ cancel_button.setFixedHeight(self.progress_bar_height)
217
+ cancel_button.setFont(self.button_font)
218
+ cancel_button.clicked.connect(self.cancel) # Closes the dialog without doing anything
219
+ progress_layout.addWidget(cancel_button)
220
+
221
+ self.proceed_button = QPushButton(self.proceed_button_text,self)
222
+ self.proceed_button.setFixedHeight(self.progress_bar_height)
223
+ self.proceed_button.setFont(self.button_font)
224
+ self.proceed_button.clicked.connect(self.on_proceed)
225
+ progress_layout.addWidget(self.proceed_button)
226
+
227
+ layout.addWidget(progress_widget)
228
+ self.setLayout(layout)
229
+
230
+ # Set window maximum height and margins
231
+ self.setFixedHeight(self.max_height)
232
+ self.setContentsMargins(self.margin_size*2, self.margin_size, self.margin_size*2, self.margin_size)
233
+
234
+ # Timer for progress
235
+ self.iteration = 0
236
+ self.timer = QTimer(self)
237
+ self.timer.timeout.connect(self.update_progress)
238
+ self.timer.setSingleShot(True)
239
+ self.loop_running = False
240
+
241
+ self.animation=None
242
+
243
+ def create_icon_button(self, index, button_type):
244
+ """Create a checkable button with icon based on its type (copy, link, change_folder)."""
245
+ button = QPushButton(self)
246
+ button.setCheckable(True)
247
+ button.setChecked((button_type=='change_folder' and index==self.nstep-1) or (button_type=='copy' and index<self.nstep-1))
248
+ button.setIcon(self.icons[button_type][int(not button.isChecked())]) # Set initial 'off' icon
249
+ button.setFixedSize(self.button_size) # Set button size to 32x32 pixels
250
+ button.setStyleSheet("QPushButton{border: none;}") # Remove button borders
251
+ button.setCursor(QCursor(Qt.PointingHandCursor)) # Set cursor to pointing hand on hover
252
+ button.setToolTip(self.tooltips[button_type]) # Set tooltip
253
+ button.clicked.connect(lambda: self.update_buttons(index, button_type))
254
+ if button.isChecked():
255
+ self.step_options[index]=self.options_list.index(button_type)
256
+ button.setIconSize(self.icon_size)
257
+ else:
258
+ button.setIconSize(self.icon_size_off) # Set icon size to 28x28 pixels
259
+ return button
260
+
261
+ def update_buttons(self, index, button_type):
262
+ """Update the icons and states of the buttons, ensuring only one is checked at a time."""
263
+ for k, button in enumerate(self.buttons_group[index]):
264
+ if not isinstance(button,QPushButton): continue
265
+ if button_type!=self.options_list[k]:
266
+ button.setChecked(False) # Uncheck all buttons
267
+ button.setIconSize(self.icon_size_off)
268
+ button.setIcon(self.icons[self.options_list[k]][1])
269
+ else:
270
+ button.setChecked(True)
271
+ button.setIconSize(self.icon_size)
272
+ button.setIcon(self.icons[self.options_list[k]][0]) # Checked state icon
273
+ self.step_options[index]=k
274
+
275
+ def on_proceed(self):
276
+ """Start or stop the progress loop depending on the button state."""
277
+ if not self.loop_running:
278
+ self.start_progress()
279
+ else:
280
+ self.stop_progress()
281
+
282
+ def start_progress(self):
283
+ """Start the progress and update button text to 'Stop'."""
284
+ self.loop_running = True
285
+ self.title_label.setText('Preparing copy...')
286
+ self.title_label.setFont(self.item_font)
287
+ self.proceed_button.setText(self.stop_button_text)
288
+ self.proceed_button.setVisible(False)
289
+ self.animate_window_resize()
290
+ self.progress_bar.setVisible(True) # Show the progress bar when Proceed is clicked
291
+ self.progress_bar.setMaximum(self.nfolders)
292
+
293
+ def stop_progress(self):
294
+ """Stop the progress and update button text to 'Proceed'."""
295
+ self.loop_running = False
296
+ self.proceed_button.setText(self.proceed_button_text)
297
+ self.timer.stop()
298
+
299
+ def update_progress(self):
300
+ """Update the progress bar on each timer tick."""
301
+ if self.iteration < self.progress_bar.maximum():
302
+ timesleep(time_sleep_loop)
303
+ self.title_label.setText(self.paths[self.iteration])
304
+ self.func(self.iteration,self.step_options)
305
+ self.progress_bar.setValue(self.iteration)
306
+ self.iteration += 1
307
+ if self.loop_running: self.timer.start(0)
308
+ else:
309
+ self.progress_bar.setValue(self.progress_bar.maximum())
310
+ self.stop_progress()
311
+ if not self.animation: self.done(0) # Closes the dialog when complete
312
+
313
+ def cancel(self):
314
+ if self.loop_running: self.stop_progress()
315
+ if not self.animation: self.done(0) # Closes the dialog when complete
316
+
317
+ def animate_window_resize(self):
318
+ for w in self.widgets:
319
+ w:QWidget
320
+ w.setVisible(False)
321
+ self.animation = QVariantAnimation(self)
322
+ self.animation.valueChanged.connect(self.window_resize)
323
+ self.animation.setDuration(300)
324
+ self.animation.setStartValue(self.max_height)
325
+ self.animation.setEndValue(self.min_height)
326
+ self.animation.finished.connect(self.finishedAnimation)
327
+ self.animation.start()
328
+
329
+ def finishedAnimation(self):
330
+ self.animation=None
331
+ self.timer.start(0) # Start or resume the timer
332
+
333
+ def window_resize(self, h):
334
+ self.setFixedHeight(h)
335
+
336
+ if __name__ == "__main__":
337
+ import random
338
+ import string
339
+
340
+ # Function to generate a random string
341
+ def generate_random_string(length):
342
+ letters = string.ascii_letters # Includes uppercase and lowercase letters
343
+ return ''.join(random.choice(letters) for i in range(length))
344
+
345
+ # Number of strings and length of each string
346
+ num_strings = 10 # Number of strings in the list
347
+ string_length = 25 # Length of each string
348
+
349
+ # Generate the list of random strings
350
+ random_strings_list = [generate_random_string(string_length) for _ in range(num_strings)]
351
+
352
+
353
+ app = QApplication(sys.argv)
354
+ app.setStyle('Fusion')
355
+
356
+ time_sleep_loop=0.01
357
+
358
+ print(choose_directories())
359
+
360
+ # Example lists
361
+ pixmap_list = [icons_path + "cal_step.png",
362
+ icons_path + "min_step.png",
363
+ icons_path + "disp_step.png",
364
+ icons_path + "piv_step.png"]
365
+ flag_list = [False,True,True,True]
366
+ name_list = ["Camera calibration",
367
+ "Image pre-processing",
368
+ "Disparity correction",
369
+ "Stereoscopic PIV analysis"]
370
+
371
+ dialog = FolderLoopDialog(pixmap_list, name_list, flag_list, paths=random_strings_list, process_name='Stereoscopic PIV process 1')
372
+ sys.exit(dialog.exec())