PaIRS-UniNa 0.2.7__cp312-cp312-macosx_11_0_universal2.whl → 0.2.10__cp312-cp312-macosx_11_0_universal2.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.
- PaIRS_UniNa/Calibration_Tab.py +16 -0
- PaIRS_UniNa/Changes.txt +39 -0
- PaIRS_UniNa/Explorer.py +311 -75
- PaIRS_UniNa/FolderLoop.py +196 -6
- PaIRS_UniNa/Input_Tab.py +160 -53
- PaIRS_UniNa/Input_Tab_CalVi.py +11 -12
- PaIRS_UniNa/Input_Tab_tools.py +17 -15
- PaIRS_UniNa/Output_Tab.py +1 -3
- PaIRS_UniNa/PaIRS_pypacks.py +63 -65
- PaIRS_UniNa/Process_Tab.py +19 -15
- PaIRS_UniNa/Process_Tab_Disp.py +8 -1
- PaIRS_UniNa/SPIVCalHelp.py +155 -0
- PaIRS_UniNa/Saving_tools.py +2 -0
- PaIRS_UniNa/TabTools.py +165 -6
- PaIRS_UniNa/Vis_Tab.py +50 -22
- PaIRS_UniNa/Vis_Tab_CalVi.py +1 -2
- PaIRS_UniNa/Whatsnew.py +4 -3
- PaIRS_UniNa/_PaIRS_PIV.so +0 -0
- PaIRS_UniNa/__init__.py +3 -3
- PaIRS_UniNa/addwidgets_ps.py +570 -70
- PaIRS_UniNa/gPaIRS.py +118 -17
- PaIRS_UniNa/icons/folder_loop_cleanup.png +0 -0
- PaIRS_UniNa/icons/folder_loop_cleanup_off.png +0 -0
- PaIRS_UniNa/icons/information.png +0 -0
- PaIRS_UniNa/icons/information2.png +0 -0
- PaIRS_UniNa/icons/scan_path_loop.png +0 -0
- PaIRS_UniNa/icons/scan_path_loop_off.png +0 -0
- PaIRS_UniNa/icons/spiv_setup_no.png +0 -0
- PaIRS_UniNa/icons/spiv_setup_ok.png +0 -0
- PaIRS_UniNa/procTools.py +46 -1
- PaIRS_UniNa/rqrdpckgs.txt +7 -7
- PaIRS_UniNa/ui_Calibration_Tab.py +92 -59
- PaIRS_UniNa/ui_gPairs.py +8 -8
- PaIRS_UniNa/whatsnew.txt +2 -3
- {pairs_unina-0.2.7.dist-info → pairs_unina-0.2.10.dist-info}/METADATA +7 -8
- {pairs_unina-0.2.7.dist-info → pairs_unina-0.2.10.dist-info}/RECORD +38 -30
- {pairs_unina-0.2.7.dist-info → pairs_unina-0.2.10.dist-info}/WHEEL +0 -0
- {pairs_unina-0.2.7.dist-info → pairs_unina-0.2.10.dist-info}/top_level.txt +0 -0
PaIRS_UniNa/FolderLoop.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
from PySide6.QtWidgets import (
|
|
3
|
-
QApplication, QDialog, QVBoxLayout, QLabel, QHBoxLayout, QWidget, QSpacerItem,
|
|
3
|
+
QApplication, QDialog, QVBoxLayout, QLabel, QHBoxLayout, QWidget, QSpacerItem, QTreeWidget, QTreeWidgetItem,
|
|
4
4
|
QPushButton, QProgressBar, QSizePolicy,
|
|
5
5
|
QFileDialog, QListView, QAbstractItemView, QTreeView, QFileSystemModel
|
|
6
6
|
)
|
|
@@ -83,14 +83,18 @@ class FolderLoopDialog(QDialog):
|
|
|
83
83
|
self.tooltips = {
|
|
84
84
|
"copy": "Copy the item",
|
|
85
85
|
"link": "Link the item",
|
|
86
|
-
"change_folder": "Change the folder"
|
|
86
|
+
"change_folder": "Change the folder",
|
|
87
|
+
"auto_purge": "Auto-remove missing-image pairs",
|
|
88
|
+
"rescan": "Re-scan destination paths"
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
# Icons for the buttons
|
|
90
92
|
self.icons = {
|
|
91
93
|
"copy": [QIcon(icons_path + "copy_process.png"), QIcon(icons_path + "copy_process_off.png")],
|
|
92
94
|
"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")]
|
|
95
|
+
"change_folder": [QIcon(icons_path + "change_folder.png"), QIcon(icons_path + "change_folder_off.png")],
|
|
96
|
+
"auto_purge": [QIcon(icons_path + "folder_loop_cleanup.png"), QIcon(icons_path + "folder_loop_cleanup_off.png")],
|
|
97
|
+
"rescan": [QIcon(icons_path + "scan_path_loop.png"), QIcon(icons_path + "scan_path_loop_off.png")],
|
|
94
98
|
}
|
|
95
99
|
self.options_list=list(self.icons)
|
|
96
100
|
|
|
@@ -109,7 +113,8 @@ class FolderLoopDialog(QDialog):
|
|
|
109
113
|
self.header_icon.setFixedSize(self.icon_size)
|
|
110
114
|
header_layout.addWidget(self.header_icon)
|
|
111
115
|
|
|
112
|
-
|
|
116
|
+
header_text=process_name[:48]+f"{'...' if len(process_name)>50 else ''}"
|
|
117
|
+
self.header_label = QLabel(header_text)
|
|
113
118
|
self.header_label.setFont(self.title_font)
|
|
114
119
|
self.header_label.setAlignment(Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignVCenter)
|
|
115
120
|
self.header_label.setFixedHeight(self.title_height)
|
|
@@ -191,6 +196,82 @@ class FolderLoopDialog(QDialog):
|
|
|
191
196
|
|
|
192
197
|
layout.addWidget(widget)
|
|
193
198
|
|
|
199
|
+
# --- Cleanup row ---
|
|
200
|
+
cleanup_spacing = 5
|
|
201
|
+
self.cleanup_widget = QWidget()
|
|
202
|
+
cw_layout = QHBoxLayout(self.cleanup_widget)
|
|
203
|
+
cw_layout.setContentsMargins(0, cleanup_spacing, 0, cleanup_spacing)
|
|
204
|
+
cw_layout.setSpacing(5)
|
|
205
|
+
|
|
206
|
+
self.cleanup_button = QPushButton(self)
|
|
207
|
+
self.cleanup_button.setCheckable(True)
|
|
208
|
+
self.cleanup_button.setChecked(False)
|
|
209
|
+
self.cleanup_button.setIcon(self.icons["auto_purge"][1])
|
|
210
|
+
self.cleanup_button.setFixedSize(self.button_size)
|
|
211
|
+
self.cleanup_button.setStyleSheet("QPushButton{border: none;}")
|
|
212
|
+
self.cleanup_button.setCursor(QCursor(Qt.PointingHandCursor))
|
|
213
|
+
self.cleanup_button.setToolTip(self.tooltips["auto_purge"])
|
|
214
|
+
self.cleanup_button.setIconSize(self.icon_size_off)
|
|
215
|
+
|
|
216
|
+
self.cleanup_label = QLabel("", self)
|
|
217
|
+
self.cleanup_label_font = parent.font()
|
|
218
|
+
self.cleanup_label_font.setPixelSize(fontPixelSize)
|
|
219
|
+
self.cleanup_label_font.setItalic(True)
|
|
220
|
+
self.cleanup_label.setFont(self.cleanup_label_font)
|
|
221
|
+
self.cleanup_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
|
222
|
+
|
|
223
|
+
self.cleanup_button.toggled.connect(self.on_cleanup_toggled)
|
|
224
|
+
cw_layout.addItem(QSpacerItem(10, 0, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
|
225
|
+
cw_layout.addWidget(self.cleanup_label)
|
|
226
|
+
cw_layout.addWidget(self.cleanup_button)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# --- Rescan row ---
|
|
230
|
+
rescan_spacing = 5
|
|
231
|
+
self.rescan_widget = QWidget()
|
|
232
|
+
rw_layout = QHBoxLayout(self.rescan_widget)
|
|
233
|
+
rw_layout.setContentsMargins(0, rescan_spacing, 0, rescan_spacing)
|
|
234
|
+
rw_layout.setSpacing(5)
|
|
235
|
+
|
|
236
|
+
self.rescan_button = QPushButton(self)
|
|
237
|
+
self.rescan_button.setCheckable(True)
|
|
238
|
+
self.rescan_button.setChecked(False)
|
|
239
|
+
self.rescan_button.setIcon(self.icons["rescan"][1])
|
|
240
|
+
self.rescan_button.setFixedSize(self.button_size)
|
|
241
|
+
self.rescan_button.setStyleSheet("QPushButton{border: none;}")
|
|
242
|
+
self.rescan_button.setCursor(QCursor(Qt.PointingHandCursor))
|
|
243
|
+
self.rescan_button.setToolTip(self.tooltips["rescan"])
|
|
244
|
+
self.rescan_button.setIconSize(self.icon_size_off)
|
|
245
|
+
|
|
246
|
+
self.rescan_label = QLabel("", self)
|
|
247
|
+
self.rescan_label_font = parent.font()
|
|
248
|
+
self.rescan_label_font.setPixelSize(fontPixelSize)
|
|
249
|
+
self.rescan_label_font.setItalic(True)
|
|
250
|
+
self.rescan_label.setFont(self.rescan_label_font)
|
|
251
|
+
self.rescan_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
|
252
|
+
|
|
253
|
+
self.rescan_button.toggled.connect(self.on_rescan_toggled)
|
|
254
|
+
rw_layout.addItem(QSpacerItem(10, 0, QSizePolicy.Expanding, QSizePolicy.Minimum))
|
|
255
|
+
rw_layout.addWidget(self.rescan_label)
|
|
256
|
+
rw_layout.addWidget(self.rescan_button)
|
|
257
|
+
|
|
258
|
+
# --- Container for both rows ---
|
|
259
|
+
self.extra_opts_widget = QWidget()
|
|
260
|
+
eo_layout = QVBoxLayout(self.extra_opts_widget)
|
|
261
|
+
eo_layout.setContentsMargins(0, 5, 0, 50) # top=10, bottom=10
|
|
262
|
+
eo_layout.setSpacing(5)
|
|
263
|
+
eo_layout.addWidget(self.rescan_widget)
|
|
264
|
+
eo_layout.addWidget(self.cleanup_widget)
|
|
265
|
+
self.extra_opts_widget.setFixedHeight(self.button_size.height()*2+60)
|
|
266
|
+
|
|
267
|
+
self.extra_opts_widget.setVisible(False)
|
|
268
|
+
self.widgets.append(self.extra_opts_widget)
|
|
269
|
+
|
|
270
|
+
# Add container to parent layout
|
|
271
|
+
layout.addWidget(self.extra_opts_widget)
|
|
272
|
+
|
|
273
|
+
self.warnings=[False]*len(self.paths)
|
|
274
|
+
|
|
194
275
|
# Progress bar and final buttons (Cancel, Proceed)
|
|
195
276
|
progress_widget = QWidget()
|
|
196
277
|
progress_layout = QHBoxLayout()
|
|
@@ -228,8 +309,13 @@ class FolderLoopDialog(QDialog):
|
|
|
228
309
|
self.setLayout(layout)
|
|
229
310
|
|
|
230
311
|
# Set window maximum height and margins
|
|
312
|
+
self.on_cleanup_toggled(self.cleanup_button.isChecked())
|
|
313
|
+
self.on_rescan_toggled(self.rescan_button.isChecked())
|
|
314
|
+
self.update_extraopts_visibility(FlagAnimation=False)
|
|
231
315
|
self.setFixedHeight(self.max_height)
|
|
232
316
|
self.setContentsMargins(self.margin_size*2, self.margin_size, self.margin_size*2, self.margin_size)
|
|
317
|
+
self.setMinimumWidth(640)
|
|
318
|
+
self.setMaximumWidth(800)
|
|
233
319
|
|
|
234
320
|
# Timer for progress
|
|
235
321
|
self.iteration = 0
|
|
@@ -271,6 +357,51 @@ class FolderLoopDialog(QDialog):
|
|
|
271
357
|
button.setIconSize(self.icon_size)
|
|
272
358
|
button.setIcon(self.icons[self.options_list[k]][0]) # Checked state icon
|
|
273
359
|
self.step_options[index]=k
|
|
360
|
+
self.update_extraopts_visibility()
|
|
361
|
+
|
|
362
|
+
def update_extraopts_visibility(self,FlagAnimation=True):
|
|
363
|
+
"""Show extra row if any row has 'change_folder' checked; hide otherwise."""
|
|
364
|
+
try:
|
|
365
|
+
idx_change = self.options_list.index("change_folder")
|
|
366
|
+
except ValueError:
|
|
367
|
+
idx_change = -1
|
|
368
|
+
|
|
369
|
+
show = (idx_change >= 0) and any(
|
|
370
|
+
(opt == idx_change) for opt in self.step_options if isinstance(opt, int)
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
if self.extra_opts_widget.isVisible() != show:
|
|
374
|
+
self.extra_opts_widget.setVisible(show)
|
|
375
|
+
extra_opts_widget = self.extra_opts_widget.height()
|
|
376
|
+
maxHeight = self.nstep * (self.button_size.height() + self.row_spacing) + int(show)*extra_opts_widget + self.min_height
|
|
377
|
+
if FlagAnimation:
|
|
378
|
+
self.window_resize(maxHeight)
|
|
379
|
+
self.max_height = maxHeight
|
|
380
|
+
|
|
381
|
+
def on_cleanup_toggled(self, checked: bool):
|
|
382
|
+
"""Sync icon/size and label text when the extra toggle is changed."""
|
|
383
|
+
self.cleanup_button.setIcon(self.icons["auto_purge"][0 if checked else 1])
|
|
384
|
+
self.cleanup_button.setIconSize(self.icon_size if checked else self.icon_size_off)
|
|
385
|
+
self.cleanup_label.setText(
|
|
386
|
+
"Image-missing pairs will be removed automatically."
|
|
387
|
+
if checked else
|
|
388
|
+
"Image-missing pairs are kept; batch copy may raise warnings."
|
|
389
|
+
)
|
|
390
|
+
self.cleanup_label.setStyleSheet("color: none;" if checked else "color: rgb(125,125,125);")
|
|
391
|
+
self.cleanup_enabled = bool(checked)
|
|
392
|
+
|
|
393
|
+
def on_rescan_toggled(self, checked: bool):
|
|
394
|
+
"""Sync icon/size and label text when the rescan toggle is changed."""
|
|
395
|
+
self.rescan_button.setIcon(self.icons["rescan"][0 if checked else 1])
|
|
396
|
+
self.rescan_button.setIconSize(self.icon_size if checked else self.icon_size_off)
|
|
397
|
+
|
|
398
|
+
self.rescan_label.setText(
|
|
399
|
+
"The input folder will be re-scanned; input image list may differ or trigger warnings."
|
|
400
|
+
if checked else
|
|
401
|
+
"No folder scan; images identified in the master process will be used."
|
|
402
|
+
)
|
|
403
|
+
self.rescan_label.setStyleSheet("color: none;" if checked else "color: rgb(125,125,125);")
|
|
404
|
+
self.rescan_enabled = bool(checked)
|
|
274
405
|
|
|
275
406
|
def on_proceed(self):
|
|
276
407
|
"""Start or stop the progress loop depending on the button state."""
|
|
@@ -300,14 +431,17 @@ class FolderLoopDialog(QDialog):
|
|
|
300
431
|
"""Update the progress bar on each timer tick."""
|
|
301
432
|
if self.iteration < self.progress_bar.maximum():
|
|
302
433
|
timesleep(time_sleep_loop)
|
|
303
|
-
self.
|
|
304
|
-
self.
|
|
434
|
+
title_text=self.paths[self.iteration][:48]+f"{'...' if len(self.paths[self.iteration])>50 else ''}"
|
|
435
|
+
self.title_label.setText(title_text)
|
|
436
|
+
self.warnings[self.iteration]=self.func(self.iteration,self.step_options,self.cleanup_enabled,self.rescan_enabled)
|
|
305
437
|
self.progress_bar.setValue(self.iteration)
|
|
306
438
|
self.iteration += 1
|
|
307
439
|
if self.loop_running: self.timer.start(0)
|
|
308
440
|
else:
|
|
309
441
|
self.progress_bar.setValue(self.progress_bar.maximum())
|
|
310
442
|
self.stop_progress()
|
|
443
|
+
self.hide()
|
|
444
|
+
self.show_batch_issues_dialog()
|
|
311
445
|
if not self.animation: self.done(0) # Closes the dialog when complete
|
|
312
446
|
|
|
313
447
|
def cancel(self):
|
|
@@ -332,6 +466,62 @@ class FolderLoopDialog(QDialog):
|
|
|
332
466
|
|
|
333
467
|
def window_resize(self, h):
|
|
334
468
|
self.setFixedHeight(h)
|
|
469
|
+
|
|
470
|
+
def show_batch_issues_dialog(self):
|
|
471
|
+
"""Show a summary dialog listing all destination folders with warnings=True."""
|
|
472
|
+
issues=[(i,p) for i,p in enumerate(self.paths) if i<len(self.warnings) and self.warnings[i]]
|
|
473
|
+
if not issues: return
|
|
474
|
+
|
|
475
|
+
dlg=QDialog(self); dlg.setWindowTitle("Batch copy issues")
|
|
476
|
+
font=dlg.font()
|
|
477
|
+
font.setPixelSize(fontPixelSize)
|
|
478
|
+
dlg.setFont(font)
|
|
479
|
+
lay=QVBoxLayout(dlg)
|
|
480
|
+
|
|
481
|
+
# --- Warning header with icon ---
|
|
482
|
+
icon_width=96
|
|
483
|
+
warn_layout = QHBoxLayout()
|
|
484
|
+
warn_icon = QLabel()
|
|
485
|
+
warn_pix = QPixmap(icons_path + "warning.png").scaled(
|
|
486
|
+
icon_width, icon_width, Qt.KeepAspectRatio, Qt.SmoothTransformation
|
|
487
|
+
)
|
|
488
|
+
warn_icon.setPixmap(warn_pix)
|
|
489
|
+
warn_icon.setScaledContents(False)
|
|
490
|
+
#warn_icon.setAlignment(Qt.AlignTop)
|
|
491
|
+
warn_icon.setFixedWidth(icon_width)
|
|
492
|
+
|
|
493
|
+
head = QLabel(
|
|
494
|
+
f"{len(issues)} destination folder{'s' if len(issues)>1 else ''} could require attention: "
|
|
495
|
+
"potential warnings during batch copy or image-set mismatch!\n\n"
|
|
496
|
+
"Please note that the interface may not explicitly flag these cases. "
|
|
497
|
+
"Copy the paths below if you wish to inspect the corresponding processes.\n",
|
|
498
|
+
dlg
|
|
499
|
+
)
|
|
500
|
+
head.setWordWrap(True)
|
|
501
|
+
|
|
502
|
+
warn_layout.addWidget(warn_icon)
|
|
503
|
+
warn_layout.addWidget(head)
|
|
504
|
+
lay.addLayout(warn_layout)
|
|
505
|
+
|
|
506
|
+
tree=QTreeWidget(dlg); tree.setHeaderHidden(True)
|
|
507
|
+
tree.header().setStretchLastSection(True)
|
|
508
|
+
for _,p in issues:
|
|
509
|
+
item=QTreeWidgetItem([p])
|
|
510
|
+
item.setToolTip(0,p)
|
|
511
|
+
tree.addTopLevelItem(item)
|
|
512
|
+
lay.addWidget(tree)
|
|
513
|
+
|
|
514
|
+
# actions
|
|
515
|
+
btns=QHBoxLayout()
|
|
516
|
+
def _copy():
|
|
517
|
+
txt="\n".join(p for _,p in issues)
|
|
518
|
+
QApplication.clipboard().setText(txt)
|
|
519
|
+
copy_btn=QPushButton("Copy paths"); copy_btn.clicked.connect(_copy)
|
|
520
|
+
close_btn=QPushButton("Close"); close_btn.clicked.connect(dlg.accept)
|
|
521
|
+
btns.addWidget(copy_btn); btns.addStretch(1); btns.addWidget(close_btn)
|
|
522
|
+
lay.addLayout(btns)
|
|
523
|
+
|
|
524
|
+
dlg.resize(720, 360); dlg.exec()
|
|
335
525
|
|
|
336
526
|
if __name__ == "__main__":
|
|
337
527
|
import random
|
PaIRS_UniNa/Input_Tab.py
CHANGED
|
@@ -71,13 +71,14 @@ class INPpar(TABpar):
|
|
|
71
71
|
self.inp_cam = 1
|
|
72
72
|
self.ind_in = 0
|
|
73
73
|
self.npairs = 0
|
|
74
|
-
self.step =
|
|
74
|
+
self.step = 1
|
|
75
75
|
self.FlagTR_Import = False
|
|
76
76
|
self.FlagImport = True
|
|
77
77
|
|
|
78
78
|
FlagAutoList = True
|
|
79
79
|
FlagAutoFrame = True
|
|
80
80
|
FlagExample = True
|
|
81
|
+
pathCompleter = basefold_DEBUGOptions
|
|
81
82
|
|
|
82
83
|
def __init__(self,Process=ProcessTypes.null,Step=StepTypes.null):
|
|
83
84
|
self.setup(Process,Step)
|
|
@@ -104,7 +105,7 @@ class INPpar(TABpar):
|
|
|
104
105
|
self.inp_cam = 1
|
|
105
106
|
self.ind_in = 0
|
|
106
107
|
self.npairs = 0
|
|
107
|
-
self.step =
|
|
108
|
+
self.step = 1
|
|
108
109
|
self.FlagTR_Import = False
|
|
109
110
|
|
|
110
111
|
self.nExImTree = 3
|
|
@@ -132,11 +133,10 @@ class INPpar(TABpar):
|
|
|
132
133
|
|
|
133
134
|
#self.FlagDISP = Step==StepTypes.disp
|
|
134
135
|
#self.dispFile = ''
|
|
135
|
-
|
|
136
|
-
self.pathCompleter=basefold_DEBUGOptions
|
|
137
|
-
|
|
136
|
+
|
|
138
137
|
class Input_Tab(gPaIRS_Tab):
|
|
139
138
|
class Import_Tab_Signals(gPaIRS_Tab.Tab_Signals):
|
|
139
|
+
tooltipRequested = Signal(str)
|
|
140
140
|
pass
|
|
141
141
|
|
|
142
142
|
def __init__(self,parent: QWidget =None, flagInit= __name__ == "__main__"):
|
|
@@ -148,11 +148,33 @@ class Input_Tab(gPaIRS_Tab):
|
|
|
148
148
|
#------------------------------------- Graphical interface: widgets
|
|
149
149
|
self.TABname='Input'
|
|
150
150
|
self.ui: Ui_InputTab
|
|
151
|
-
self.exImTree=self.ui.exImTree=GlobalImageTree(self,FlagNum=True)
|
|
152
|
-
self.ui.g_ImSet_layout.insertWidget(2,self.exImTree)
|
|
151
|
+
self.exImTree=self.ui.exImTree=GlobalImageTree(self,FlagNum=True)
|
|
153
152
|
self.exImTree.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
|
153
|
+
self.exImTree.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
|
154
154
|
self.exImTree.setSelectionBehavior(QTreeWidget.SelectionBehavior.SelectRows)
|
|
155
|
-
self.exImTree.setHeaderHidden(True)
|
|
155
|
+
#self.exImTree.setHeaderHidden(True)
|
|
156
|
+
self.exImTree.header().setVisible(True)
|
|
157
|
+
self.hbarExImTree = QScrollBar(Qt.Horizontal)
|
|
158
|
+
self.hbarExImTree.setStyleSheet("""
|
|
159
|
+
QScrollBar::handle:horizontal {
|
|
160
|
+
min-width: 40px;
|
|
161
|
+
}
|
|
162
|
+
""")
|
|
163
|
+
self.exImTree.setVisible(True)
|
|
164
|
+
self.hbarExImTree.setVisible(True)
|
|
165
|
+
# Sync ranges and values
|
|
166
|
+
origHbar = self.exImTree.horizontalScrollBar()
|
|
167
|
+
origHbar.rangeChanged.connect(self.hbarExImTree.setRange)
|
|
168
|
+
origHbar.valueChanged.connect(self.hbarExImTree.setValue)
|
|
169
|
+
self.hbarExImTree.valueChanged.connect(origHbar.setValue)
|
|
170
|
+
# Layout
|
|
171
|
+
self.containerExImTree = QWidget()
|
|
172
|
+
layoutExImTree = QVBoxLayout(self.containerExImTree)
|
|
173
|
+
layoutExImTree.setContentsMargins(0,0,0,0)
|
|
174
|
+
layoutExImTree.setSpacing(0)
|
|
175
|
+
layoutExImTree.addWidget(self.exImTree)
|
|
176
|
+
layoutExImTree.addWidget(self.hbarExImTree)
|
|
177
|
+
self.ui.g_ImSet_layout.insertWidget(2,self.containerExImTree)
|
|
156
178
|
|
|
157
179
|
#necessary to change the name and the order of the items
|
|
158
180
|
for g in list(globals()):
|
|
@@ -170,6 +192,8 @@ class Input_Tab(gPaIRS_Tab):
|
|
|
170
192
|
self.pixmap_wait = QPixmap(''+ icons_path +'sandglass.png')
|
|
171
193
|
self.pixmap_warn = QPixmap(u""+ icons_path +"warning.png")
|
|
172
194
|
|
|
195
|
+
self.signals.tooltipRequested.connect(self.show_import_tooltip)
|
|
196
|
+
|
|
173
197
|
#------------------------------------- Declaration of parameters
|
|
174
198
|
self.INPpar_base=INPpar()
|
|
175
199
|
self.INPpar:INPpar=self.TABpar
|
|
@@ -291,11 +315,39 @@ class Input_Tab(gPaIRS_Tab):
|
|
|
291
315
|
def scanImList(self,ind=None):
|
|
292
316
|
if ind: INP:INPpar=self.TABpar_at(ind)
|
|
293
317
|
else: INP:INPpar=self.INPpar
|
|
318
|
+
FlagWarning=False
|
|
294
319
|
for c in range(len(INP.imList)): #INP.ncam
|
|
295
320
|
for f in range(2):
|
|
296
321
|
for k in range(len(INP.imList[0][0])): #INP.nimg
|
|
297
322
|
#ex=INP.imEx[c][f][k]
|
|
298
323
|
INP.imEx[c][f][k]=os.path.exists(INP.path+INP.imList[c][f][k]) if INP.imList[c][f][k] else False
|
|
324
|
+
if not FlagWarning and not INP.imEx[c][f][k]: FlagWarning=True
|
|
325
|
+
return FlagWarning
|
|
326
|
+
|
|
327
|
+
def purgeImList(self, ind=None):
|
|
328
|
+
"""Drop every k where any INP.imEx[c][f][k] is False; remove k from both imEx and imList across all c,f."""
|
|
329
|
+
INP = self.INPpar if ind is None else self.TABpar_at(ind)
|
|
330
|
+
if not hasattr(INP,"imEx") or not hasattr(INP,"imList"): return 0
|
|
331
|
+
|
|
332
|
+
# Collect all k to drop if False appears anywhere at that k
|
|
333
|
+
ks_to_drop=set()
|
|
334
|
+
for c in range(len(INP.imEx)):
|
|
335
|
+
for f in range(len(INP.imEx[c])):
|
|
336
|
+
row=INP.imEx[c][f]
|
|
337
|
+
for k, ex in enumerate(row):
|
|
338
|
+
if not ex: ks_to_drop.add(k)
|
|
339
|
+
|
|
340
|
+
if not ks_to_drop: return 0
|
|
341
|
+
ks_sorted=sorted(ks_to_drop, reverse=True)
|
|
342
|
+
|
|
343
|
+
# Delete k across all c,f for both imEx and imList (bounds-checked, ragged-safe)
|
|
344
|
+
for c in range(len(INP.imEx)):
|
|
345
|
+
for f in range(len(INP.imEx[c])):
|
|
346
|
+
for k in ks_sorted:
|
|
347
|
+
if k < len(INP.imEx[c][f]): del INP.imEx[c][f][k]
|
|
348
|
+
if c < len(INP.imList) and f < len(INP.imList[c]) and k < len(INP.imList[c][f]):
|
|
349
|
+
del INP.imList[c][f][k]
|
|
350
|
+
return len(ks_to_drop)
|
|
299
351
|
|
|
300
352
|
#*************************************************** Layout
|
|
301
353
|
def setINPlayout(self):
|
|
@@ -320,9 +372,9 @@ class Input_Tab(gPaIRS_Tab):
|
|
|
320
372
|
self.ui.spin_inp_cam.setEnabled(self.INPpar.inp_ncam>1)
|
|
321
373
|
self.ui.spin_inp_cam.setMaximum(self.INPpar.inp_ncam)
|
|
322
374
|
self.setImageNumberSpinLimits()
|
|
323
|
-
self.
|
|
375
|
+
self.containerExImTree.setVisible(INPpar.FlagExample)
|
|
324
376
|
self.layoutExampleImageList()
|
|
325
|
-
self.ui.button_import.setEnabled(not self.INPpar.FlagImport)
|
|
377
|
+
#self.ui.button_import.setEnabled(not self.INPpar.FlagImport)
|
|
326
378
|
|
|
327
379
|
"""
|
|
328
380
|
if self.INPpar.ind[-1]<len(self.TABpar_prev_at(self.INPpar.ind))-1:
|
|
@@ -397,13 +449,13 @@ class Input_Tab(gPaIRS_Tab):
|
|
|
397
449
|
def line_edit_path_preaction(self):
|
|
398
450
|
currpath=myStandardPath(self.ui.line_edit_path.text())
|
|
399
451
|
self.FlagScanPath=os.path.normpath(self.INPpar.path)!=currpath
|
|
400
|
-
|
|
401
|
-
if
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
452
|
+
currpath=relativizePath(currpath)
|
|
453
|
+
if os.path.exists(currpath) and currpath!='./':
|
|
454
|
+
pathCompleter=INPpar.pathCompleter #self.INPpar.pathCompleter
|
|
455
|
+
if currpath in pathCompleter: pathCompleter.remove(currpath)
|
|
456
|
+
pathCompleter.insert(0,currpath)
|
|
457
|
+
if len(pathCompleter)>pathCompleterLength:
|
|
458
|
+
INPpar.pathCompleter=pathCompleter[:pathCompleterLength]
|
|
407
459
|
self.ui.line_edit_path.setText(currpath)
|
|
408
460
|
|
|
409
461
|
def line_edit_path_action_future(self):
|
|
@@ -426,15 +478,7 @@ class Input_Tab(gPaIRS_Tab):
|
|
|
426
478
|
self.line_edit_path_action_future()
|
|
427
479
|
|
|
428
480
|
def button_scan_path_action(self):
|
|
429
|
-
self.
|
|
430
|
-
if self.INPpar.imSet.count:
|
|
431
|
-
self.INPpar.frame_1[0]=0
|
|
432
|
-
self.INPpar.frame_1,self.INPpar.frame_2=self.automaticFrames()
|
|
433
|
-
else:
|
|
434
|
-
self.INPpar.frame_1=[-1]*self.INPpar.inp_ncam
|
|
435
|
-
self.INPpar.frame_2=[-1]*self.INPpar.inp_ncam
|
|
436
|
-
self.INPpar.ind_in, _, self.INPpar.npairs, _= self.getIndNpairs()
|
|
437
|
-
self.INPpar.step=1 if self.INPpar.npairs else 0
|
|
481
|
+
self.scanInputPath()
|
|
438
482
|
if INPpar.FlagAutoList:
|
|
439
483
|
self.button_import_action()
|
|
440
484
|
else:
|
|
@@ -442,20 +486,65 @@ class Input_Tab(gPaIRS_Tab):
|
|
|
442
486
|
self.ui.imTreeWidget.imTree.scanLists()
|
|
443
487
|
self.INPpar.imEx=self.ui.imTreeWidget.imTree.imEx
|
|
444
488
|
|
|
445
|
-
def
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
489
|
+
def scanInputPath(self,ind=None,patterns=None,FlagNoWarning=True):
|
|
490
|
+
INP = self.INPpar if ind is None else self.TABpar_at(ind)
|
|
491
|
+
INP.imSet.scanPath(INP.path)
|
|
492
|
+
ncam = INP.inp_ncam
|
|
493
|
+
|
|
494
|
+
FlagAutomaticFrames=True
|
|
495
|
+
if patterns and len(patterns)==2 and INP.imSet.count:
|
|
496
|
+
A,B = patterns # lists of patterns (len <= ncam)
|
|
497
|
+
INP.frame_1 = [-1]*ncam
|
|
498
|
+
INP.frame_2 = [-1]*ncam
|
|
499
|
+
|
|
500
|
+
# Build first-occurrence lookup: pattern -> index in slave's INP.imSet.pattern
|
|
501
|
+
pat_list = getattr(INP.imSet, "pattern", [])
|
|
502
|
+
idx_map = {}
|
|
503
|
+
for idx,p in enumerate(pat_list):
|
|
504
|
+
if p not in idx_map: idx_map[p]=idx
|
|
505
|
+
|
|
506
|
+
# Assign frames by matching patterns; leave -1 if not found
|
|
507
|
+
def _assign(dst, src, ind0=0):
|
|
508
|
+
for k in range(ncam):
|
|
509
|
+
key = src[k] if k < len(src) else None
|
|
510
|
+
dst[k] = idx_map.get(key, -1)
|
|
511
|
+
if ind0 and key is not None: dst[k]+=ind0
|
|
512
|
+
|
|
513
|
+
_assign(INP.frame_1, A)
|
|
514
|
+
_assign(INP.frame_2, B, 1)
|
|
515
|
+
|
|
516
|
+
valid=any(f>-1 for f in INP.frame_1) or any(f>0 for f in INP.frame_2)
|
|
517
|
+
if not valid and FlagNoWarning:
|
|
518
|
+
FlagAutomaticFrames = True
|
|
519
|
+
else:
|
|
520
|
+
FlagAutomaticFrames = False
|
|
521
|
+
|
|
522
|
+
if FlagAutomaticFrames:
|
|
523
|
+
if INP.imSet.count:
|
|
524
|
+
INP.frame_1[0]=0
|
|
525
|
+
INP.frame_1,INP.frame_2=self.automaticFrames(ind)
|
|
526
|
+
else:
|
|
527
|
+
INP.frame_1=[-1]*INP.inp_ncam
|
|
528
|
+
INP.frame_2=[-1]*INP.inp_ncam
|
|
529
|
+
# Update derived indices and safety on step
|
|
530
|
+
INP.ind_in, _, INP.npairs, _= self.getIndNpairs(ind)
|
|
531
|
+
if INP.npairs<1: INP.step=1
|
|
532
|
+
|
|
533
|
+
def automaticFrames(self,ind=None):
|
|
534
|
+
INP = self.INPpar if ind is None else self.TABpar_at(ind)
|
|
535
|
+
cam=INP.inp_cam-1
|
|
536
|
+
if not INP.imSet.nimg:
|
|
537
|
+
return INP.frame_1,INP.frame_2
|
|
538
|
+
f1=INP.frame_1[cam]
|
|
539
|
+
link1=INP.imSet.link[f1]
|
|
451
540
|
nlink1=len(link1)
|
|
452
|
-
frame_1=[f1]*
|
|
453
|
-
frame_2=[f1+1]*
|
|
541
|
+
frame_1=[f1]*INP.inp_ncam
|
|
542
|
+
frame_2=[f1+1]*INP.inp_ncam
|
|
454
543
|
frame_2[0]=link1[0]+1 if link1[0]!=f1 else 0
|
|
455
|
-
for c in range(1,
|
|
544
|
+
for c in range(1,INP.inp_ncam):
|
|
456
545
|
flagValid=c<nlink1-1
|
|
457
546
|
frame_1[c]=f1c=link1[c] if flagValid else -1
|
|
458
|
-
frame_2[c]=
|
|
547
|
+
frame_2[c]=INP.imSet.link[f1c][0]+1 if flagValid else -1
|
|
459
548
|
if cam:
|
|
460
549
|
frame_1=frame_1[-cam:]+frame_1[:-cam]
|
|
461
550
|
frame_2=frame_2[-cam:]+frame_2[:-cam]
|
|
@@ -511,7 +600,7 @@ class Input_Tab(gPaIRS_Tab):
|
|
|
511
600
|
INPpar.FlagAutoFrame=self.ui.button_automatic_frame.isChecked()
|
|
512
601
|
return True
|
|
513
602
|
|
|
514
|
-
def combo_frame_a_action(self):
|
|
603
|
+
def combo_frame_a_action(self):
|
|
515
604
|
self.INPpar.frame_1[self.INPpar.inp_cam-1]=self.ui.combo_frame_a.currentIndex()
|
|
516
605
|
if INPpar.FlagAutoFrame:
|
|
517
606
|
self.INPpar.frame_1,self.INPpar.frame_2=self.automaticFrames()
|
|
@@ -522,7 +611,8 @@ class Input_Tab(gPaIRS_Tab):
|
|
|
522
611
|
|
|
523
612
|
def button_example_list_action(self):
|
|
524
613
|
INPpar.FlagExample=self.ui.button_example_list.isChecked()
|
|
525
|
-
self.
|
|
614
|
+
self.layoutExampleImageList()
|
|
615
|
+
self.containerExImTree.setVisible(INPpar.FlagExample)
|
|
526
616
|
return True
|
|
527
617
|
|
|
528
618
|
def spin_inp_cam_action(self):
|
|
@@ -538,16 +628,24 @@ class Input_Tab(gPaIRS_Tab):
|
|
|
538
628
|
item.setSelected(True)
|
|
539
629
|
|
|
540
630
|
def button_import_action(self):
|
|
541
|
-
INPpar.FlagExample=False
|
|
542
631
|
self.INPpar.imList,self.INPpar.imEx=self.INPpar.imSet.genListsFromFrame(self.INPpar.frame_1,self.INPpar.frame_2,self.INPpar.ind_in,self.INPpar.npairs,self.INPpar.step,self.INPpar.FlagTR_Import)
|
|
543
|
-
self.INPpar.selection=[1,1,1]
|
|
544
|
-
self.InputAdjustSelection()
|
|
545
632
|
if self.INPpar.isDifferentFrom(self.INPpar_old,fields=['imList','imEx']):
|
|
633
|
+
INPpar.FlagExample=False
|
|
634
|
+
self.INPpar.selection=[1,1,1]
|
|
635
|
+
self.InputAdjustSelection()
|
|
546
636
|
self.INPpar.FlagImport=True
|
|
547
637
|
self.INPpar.importPar.copyfrom(self.INPpar,exceptions=['name','surname'])
|
|
548
|
-
|
|
549
|
-
|
|
638
|
+
self.ui.CollapBox_ImSet.toggle_button.click()
|
|
639
|
+
self.ui.imTreeWidget.setFocus()
|
|
640
|
+
else:
|
|
641
|
+
# --- Tooltip warning ---
|
|
642
|
+
msg = "No changes to be applied to the image list!"
|
|
643
|
+
self.signals.tooltipRequested.emit(msg) # thread-safe
|
|
550
644
|
|
|
645
|
+
@Slot(str)
|
|
646
|
+
def show_import_tooltip(self, msg: str):
|
|
647
|
+
show_mouse_tooltip(self,msg)
|
|
648
|
+
|
|
551
649
|
#******************** Settings
|
|
552
650
|
"""
|
|
553
651
|
def button_box_set(self):
|
|
@@ -577,7 +675,7 @@ class Input_Tab(gPaIRS_Tab):
|
|
|
577
675
|
ind_in, ind_fin, npairs, npairs_max= self.getIndNpairs()
|
|
578
676
|
self.INPpar.ind_in=min([max([ind_in,self.INPpar.ind_in]),ind_fin])
|
|
579
677
|
self.INPpar.npairs=npairs if self.INPpar.isDifferentFrom(self.INPpar_old,fields=['FlagTR_Import']) else min([self.INPpar.npairs,npairs])
|
|
580
|
-
self.INPpar.step=min([self.INPpar.step,npairs])
|
|
678
|
+
self.INPpar.step=max([min([self.INPpar.step,npairs]),1])
|
|
581
679
|
|
|
582
680
|
self.ui.spin_ind_in.setMinimum(ind_in)
|
|
583
681
|
self.ui.spin_ind_in.setMaximum(ind_fin)
|
|
@@ -596,27 +694,28 @@ class Input_Tab(gPaIRS_Tab):
|
|
|
596
694
|
#ind_in=self.INPpar.imSet.ind_in[self.INPpar.frame_a]
|
|
597
695
|
pass
|
|
598
696
|
|
|
599
|
-
def getIndNpairs(self):
|
|
600
|
-
|
|
601
|
-
|
|
697
|
+
def getIndNpairs(self,ind=None):
|
|
698
|
+
INP = self.INPpar if ind is None else self.TABpar_at(ind)
|
|
699
|
+
if INP.imSet.count:
|
|
700
|
+
l=[INP.imSet.ind_in[f] for f in INP.frame_1 if f>-1]
|
|
602
701
|
ind_in_1=min(l) if l else None
|
|
603
|
-
l=[
|
|
702
|
+
l=[INP.imSet.ind_in[f-1] for f in INP.frame_2 if f>0]
|
|
604
703
|
ind_in_2=min(l) if l else ind_in_1 if ind_in_1 is not None else None
|
|
605
704
|
ind_in=min([ind_in_1,ind_in_2]) if ind_in_2 is not None else 0
|
|
606
705
|
|
|
607
|
-
l=[
|
|
706
|
+
l=[INP.imSet.ind_fin[f] for f in INP.frame_1 if f>-1]
|
|
608
707
|
ind_fin_1=max(l) if l else None
|
|
609
|
-
l=[
|
|
708
|
+
l=[INP.imSet.ind_fin[f-1] for f in INP.frame_2 if f>0]
|
|
610
709
|
ind_fin_2=max(l) if l else ind_fin_1 if ind_fin_1 is not None else None
|
|
611
710
|
ind_fin=min([ind_fin_1,ind_fin_2]) if ind_fin_2 is not None else -1
|
|
612
711
|
else:
|
|
613
712
|
ind_in=ind_in_2=0
|
|
614
713
|
ind_fin=-1
|
|
615
714
|
npairs=nimg=ind_fin-ind_in+1
|
|
616
|
-
if not all(
|
|
715
|
+
if not all(INP.frame_2):
|
|
617
716
|
npairs=int(npairs/2)
|
|
618
|
-
if
|
|
619
|
-
npairs=2*npairs-1+nimg%2 #(1+int(any(
|
|
717
|
+
if INP.FlagTR_Import:
|
|
718
|
+
npairs=2*npairs-1+nimg%2 #(1+int(any(INP.frame_2)))*npairs-1
|
|
620
719
|
if npairs==0: ind_in=ind_fin=0
|
|
621
720
|
npairs_max=npairs
|
|
622
721
|
return ind_in, ind_fin, npairs, npairs_max
|
|
@@ -655,6 +754,14 @@ class Input_Tab(gPaIRS_Tab):
|
|
|
655
754
|
self.exImTree.setMaximumHeight(height)
|
|
656
755
|
self.exImTree.verticalScrollBar().setMinimumHeight(height)
|
|
657
756
|
self.exImTree.verticalScrollBar().setMaximumHeight(height)
|
|
757
|
+
if self.hbarExImTree.maximum()==self.hbarExImTree.minimum():
|
|
758
|
+
self.hbarExImTree.setVisible(False)
|
|
759
|
+
containerHeight=height
|
|
760
|
+
else:
|
|
761
|
+
self.hbarExImTree.setVisible(True)
|
|
762
|
+
containerHeight=height+self.hbarExImTree.height()
|
|
763
|
+
self.containerExImTree.setMinimumHeight(containerHeight)
|
|
764
|
+
self.containerExImTree.setMaximumHeight(containerHeight)
|
|
658
765
|
self.ui.CollapBox_ImSet.heightArea=height+110 if INPpar.FlagExample else 110
|
|
659
766
|
self.ui.CollapBox_ImSet.heightOpened=self.ui.CollapBox_ImSet.heightArea+20
|
|
660
767
|
self.ui.CollapBox_ImSet.on_click()
|