PaIRS-UniNa 0.2.8__cp312-cp312-win_amd64.whl → 0.2.10__cp312-cp312-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.
Files changed (49) hide show
  1. PaIRS_UniNa/Calibration_Tab.py +16 -0
  2. PaIRS_UniNa/Changes.txt +21 -0
  3. PaIRS_UniNa/Explorer.py +3312 -3126
  4. PaIRS_UniNa/FolderLoop.py +561 -561
  5. PaIRS_UniNa/Input_Tab.py +824 -826
  6. PaIRS_UniNa/Input_Tab_CalVi.py +1 -0
  7. PaIRS_UniNa/Input_Tab_tools.py +3020 -3019
  8. PaIRS_UniNa/PaIRS.py +17 -17
  9. PaIRS_UniNa/PaIRS_pypacks.py +18 -65
  10. PaIRS_UniNa/Process_Tab.py +19 -15
  11. PaIRS_UniNa/Process_Tab_Disp.py +8 -1
  12. PaIRS_UniNa/SPIVCalHelp.py +155 -0
  13. PaIRS_UniNa/Saving_tools.py +279 -277
  14. PaIRS_UniNa/TabTools.py +165 -6
  15. PaIRS_UniNa/Vis_Tab.py +11 -4
  16. PaIRS_UniNa/Vis_Tab_CalVi.py +1 -2
  17. PaIRS_UniNa/_PaIRS_PIV.pyd +0 -0
  18. PaIRS_UniNa/__init__.py +3 -3
  19. PaIRS_UniNa/addwidgets_ps.py +570 -70
  20. PaIRS_UniNa/gPaIRS.py +3933 -3889
  21. PaIRS_UniNa/icons/information.png +0 -0
  22. PaIRS_UniNa/icons/information2.png +0 -0
  23. PaIRS_UniNa/icons/spiv_setup_no.png +0 -0
  24. PaIRS_UniNa/icons/spiv_setup_ok.png +0 -0
  25. PaIRS_UniNa/listLib.py +301 -301
  26. PaIRS_UniNa/parForMulti.py +433 -433
  27. PaIRS_UniNa/rqrdpckgs.txt +7 -7
  28. PaIRS_UniNa/tabSplitter.py +606 -606
  29. PaIRS_UniNa/ui_Calibration_Tab.py +576 -543
  30. PaIRS_UniNa/ui_Custom_Top.py +294 -294
  31. PaIRS_UniNa/ui_Input_Tab.py +1098 -1098
  32. PaIRS_UniNa/ui_Input_Tab_CalVi.py +1280 -1280
  33. PaIRS_UniNa/ui_Log_Tab.py +261 -261
  34. PaIRS_UniNa/ui_Output_Tab.py +2360 -2360
  35. PaIRS_UniNa/ui_Process_Tab.py +3808 -3808
  36. PaIRS_UniNa/ui_Process_Tab_CalVi.py +1547 -1547
  37. PaIRS_UniNa/ui_Process_Tab_Disp.py +1139 -1139
  38. PaIRS_UniNa/ui_Process_Tab_Min.py +435 -435
  39. PaIRS_UniNa/ui_ResizePopup.py +203 -203
  40. PaIRS_UniNa/ui_Vis_Tab.py +1626 -1626
  41. PaIRS_UniNa/ui_Vis_Tab_CalVi.py +1249 -1249
  42. PaIRS_UniNa/ui_Whatsnew.py +131 -131
  43. PaIRS_UniNa/ui_gPairs.py +873 -873
  44. PaIRS_UniNa/ui_infoPaIRS.py +550 -550
  45. PaIRS_UniNa/whatsnew.txt +2 -4
  46. {pairs_unina-0.2.8.dist-info → pairs_unina-0.2.10.dist-info}/METADATA +7 -13
  47. {pairs_unina-0.2.8.dist-info → pairs_unina-0.2.10.dist-info}/RECORD +49 -45
  48. {pairs_unina-0.2.8.dist-info → pairs_unina-0.2.10.dist-info}/WHEEL +0 -0
  49. {pairs_unina-0.2.8.dist-info → pairs_unina-0.2.10.dist-info}/top_level.txt +0 -0
PaIRS_UniNa/PaIRS.py CHANGED
@@ -1,18 +1,18 @@
1
1
  from .gPaIRS import *
2
-
3
- def run():
4
- gui:gPaIRS
5
- app,gui,flagPrint=launchPaIRS()
6
- quitPaIRS(app,flagPrint)
7
-
8
- def cleanRun():
9
- if os.path.exists(lastcfgname):
10
- os.remove(lastcfgname)
11
- run()
12
-
13
- def debugRun():
14
- gui:gPaIRS
15
- app,gui,flagPrint=launchPaIRS(flagInputDebug=True)
16
- quitPaIRS(app,flagPrint)
17
-
18
-
2
+
3
+ def run():
4
+ gui:gPaIRS
5
+ app,gui,flagPrint=launchPaIRS()
6
+ quitPaIRS(app,flagPrint)
7
+
8
+ def cleanRun():
9
+ if os.path.exists(lastcfgname):
10
+ os.remove(lastcfgname)
11
+ run()
12
+
13
+ def debugRun():
14
+ gui:gPaIRS
15
+ app,gui,flagPrint=launchPaIRS(flagInputDebug=True)
16
+ quitPaIRS(app,flagPrint)
17
+
18
+
@@ -80,8 +80,10 @@ if currentID in (developerIDs['GP_Win_Office'],developerIDs['GP_Win_Office_New']
80
80
  'C:/desk/PIV_Img/img1/',
81
81
  'C:/desk/PIV_Img/_data/PIV_data/virtual_case/',
82
82
  'C:/desk/PIV_Img/_data/PIV_data/real_case/',
83
+ 'C:/desk/PIV_Img/_data/SPIV_data/real_case/img/',
83
84
  'C:/desk/PIV_Img/_data/Calibration_data/pinhole/',
84
85
  'C:/desk/PIV_Img/_data/Calibration_data/cylinder/',
86
+ 'C:/desk/PIV_Img/_data/SPIV_data/real_case/calib/',
85
87
  ]
86
88
  basefold_DEBUG_VIS='C:/desk/PIV_Img/_data/PIV_data/real_case/'
87
89
  elif currentID==developerIDs['GP_WSL']:
@@ -148,6 +150,15 @@ fileChanges='Changes.txt'
148
150
  fileWhatsNew=['whatsnew.txt','whatwasnew.txt']
149
151
  icons_path="icons/"
150
152
 
153
+ gPaIRS_QMenu_style="""
154
+ QMenu::item:selected,
155
+ QMenu::item:checked,
156
+ QMenu::item:pressed {
157
+ background-color: rgba(0, 116, 255, 0.8);
158
+ color: white;
159
+ }
160
+ """
161
+
151
162
  from psutil import cpu_count
152
163
  NUMTHREADS_MAX=cpu_count(logical=True)#-1
153
164
  if NUMTHREADS_MAX<1: NUMTHREADS_MAX=1
@@ -808,12 +819,6 @@ def toPlainText(text):
808
819
  PlainTextConverter.setHtml(text) #for safety
809
820
  return PlainTextConverter.toPlainText()
810
821
 
811
- def showTip(obj,message):
812
- toolTipDuration=obj.toolTipDuration()
813
- obj.setToolTipDuration(3000)
814
- QToolTip.showText(QCursor.pos(),message)
815
- obj.setToolTipDuration(toolTipDuration)
816
-
817
822
  def clean_tree(tree:QTreeWidget):
818
823
  def remove_children(item:QTreeWidgetItem):
819
824
  while item.childCount() > 0:
@@ -1166,59 +1171,6 @@ def checkOutDated(packageName:str,printOutDated):
1166
1171
  f3=executor.submit(asyncio.run,checkOutDatedInternal(packageName))
1167
1172
  f3.add_done_callback(checkOutDatedComplete)
1168
1173
 
1169
- def changes(self,TabType,filename,title=" Changes"):
1170
- FlagShow=False
1171
- if self.logChanges:
1172
- if self.logChanges.isVisible():
1173
- FlagShow=True
1174
- if FlagShow:
1175
- self.logChanges.hide()
1176
- self.logChanges.show()
1177
- else:
1178
- self.logChanges=TabType(self,True)
1179
- self.logChanges.resize(720,720)
1180
- self.logChanges.show()
1181
- self.logChanges.ui.progress_Proc.hide()
1182
- self.logChanges.ui.button_close_tab.hide()
1183
- icon=QPixmap(''+ icons_path +'news.png')
1184
- self.logChanges.ui.icon.setPixmap(icon)
1185
- self.logChanges.setWindowIcon(self.windowIcon())
1186
- self.logChanges.setWindowTitle(title)
1187
- self.logChanges.ui.name_tab.setText(title)
1188
-
1189
- self.logChanges.ui.log.setLineWrapColumnOrWidth(self.logChanges.ui.log.width()-20)
1190
- self.logChanges.ui.log.setStyleSheet("")
1191
-
1192
- def setFontPixelSize(logChanges:type(self.logChanges),fPixSize):
1193
- logfont=self.font()
1194
- logfont.setFamily(fontName)
1195
- logfont.setPixelSize(fPixSize+2)
1196
- logChanges.ui.log.setFont(logfont)
1197
- fPixSize_TabNames=min([fPixSize*2,30])
1198
- lab=logChanges.ui.name_tab
1199
- font=lab.font()
1200
- font.setPixelSize(fPixSize_TabNames)
1201
- lab.setFont(font)
1202
- self.logChanges.setFontPixelSize=lambda fS: setFontPixelSize(self.logChanges,fS)
1203
- self.logChanges.setFontPixelSize(self.TABpar.fontPixelSize)
1204
- def logResizeEvent(logChanges:type(self.logChanges),e):
1205
- super(type(logChanges),logChanges).resizeEvent(e)
1206
- logChanges.ui.log.setLineWrapColumnOrWidth(logChanges.ui.log.width()-20)
1207
- self.logChanges.ui.log.resizeEvent=lambda e: logResizeEvent(self.logChanges,e)
1208
-
1209
- self.logChanges.ui.icon.addfuncclick['whatsnew']=self.whatsNew
1210
- self.logChanges.ui.icon.setCustomCursor()
1211
-
1212
- try:
1213
- file = open(filename, "rb")
1214
- content = file.read().decode("utf-8")
1215
- self.logChanges.ui.log.setText(content)
1216
- file.close()
1217
- except Exception as inst:
1218
- pri.Error.red(f'There was a problem while reading the file {filename}:\n{inst}')
1219
- self.logChanges.ui.log.setText(f'No information about PaIRS-UniNa updates available!\n\nSorry for this, try to reinstall PaIRS-UniNa or alternatively contact the authors at {__mail__}.')
1220
- return
1221
-
1222
1174
  import webbrowser
1223
1175
  def downloadExampleData(self,url):
1224
1176
  Message=f'Test data are available at the following link:\n{url}'
@@ -1354,7 +1306,7 @@ def checkRequiredPackages(self, filename=rqrdpckgs_filename, FlagDisplay=False,
1354
1306
  else:
1355
1307
  pri.Error.red(f"Malformed line: {line}")
1356
1308
 
1357
- Flag = False
1309
+ FlagUpdateFile = False
1358
1310
  warnings = []
1359
1311
 
1360
1312
  for i, pkg in enumerate(required_packages):
@@ -1364,9 +1316,10 @@ def checkRequiredPackages(self, filename=rqrdpckgs_filename, FlagDisplay=False,
1364
1316
  installed_version = None
1365
1317
 
1366
1318
  # Update current installed version
1367
- if installed_version is not None and (installed_version != vcurr_list[i] or FlagDisplay):
1368
- vcurr_list[i] = installed_version
1369
- Flag = True
1319
+ if installed_version is not None:
1320
+ if installed_version != vcurr_list[i]:
1321
+ vcurr_list[i] = installed_version
1322
+ FlagUpdateFile = True
1370
1323
 
1371
1324
  # Check if within [vmin, vmax]
1372
1325
  if not (le_ver(vmin_list[i],installed_version) and le_ver(installed_version,vmax_list[i])):
@@ -1381,7 +1334,7 @@ def checkRequiredPackages(self, filename=rqrdpckgs_filename, FlagDisplay=False,
1381
1334
 
1382
1335
  # Show warning
1383
1336
  if len(warnings)>0: self.FlagPackIssue=True
1384
- if len(warnings)>0 or FlagForcePrint:
1337
+ if ( (FlagUpdateFile or FlagDisplay) and len(warnings)>0 ) or FlagForcePrint :
1385
1338
  message = (
1386
1339
  "Some installed packages have a version outside the target range used to develop "
1387
1340
  "the current release of the PaIRS_UniNa package.\n\n"
@@ -1413,7 +1366,7 @@ def checkRequiredPackages(self, filename=rqrdpckgs_filename, FlagDisplay=False,
1413
1366
  warningDialog(self, Message="All installed packages are within the expected version range.", flagScreenCenter=True,pixmap=icons_path+'greenv.png')
1414
1367
 
1415
1368
  # Update file if needed
1416
- if Flag:
1369
+ if FlagUpdateFile:
1417
1370
  with open(filename, "w") as f:
1418
1371
  for pkg, vmin, vmax, vcurr in zip(required_packages, vmin_list, vmax_list, vcurr_list):
1419
1372
  f.write(f"{pkg}\t{vmin}\t{vmax}\t{vcurr if vcurr else 0}\n")
@@ -735,13 +735,12 @@ class Process_Tab(gPaIRS_Tab):
735
735
  message="Please, insert at least one element!"
736
736
  else:
737
737
  message="Items must be inserted in decreasing order!"
738
- wlab.setToolTip(message)
739
- wlab.setStatusTip(message)
740
- """
741
- QToolTip.showText(QCursor.pos(),wlab.toolTip(),wedit,QRect(),3000)
742
- """
743
738
  else:
744
739
  wlab.setPixmap(QPixmap())
740
+ message=""
741
+ show_mouse_tooltip(wedit,message)
742
+ wlab.setToolTip(message)
743
+ wlab.setStatusTip(message)
745
744
  self.PROpar.VectFlag[self.Vect_widgets.index(wedit)]=not FlagError
746
745
  return split_text, vect, FlagError
747
746
 
@@ -1292,18 +1291,25 @@ class Process_Tab(gPaIRS_Tab):
1292
1291
  def line_edit_IW_action(self):
1293
1292
  text=self.ui.line_edit_IW.text()
1294
1293
  split_text=re.split(r'(\d+)', text)[1:-1:2]
1295
- vect=[int(split_text[i]) for i in (0,2,1,3)]
1296
- if len(vect)==4:
1294
+ if len(split_text)!=4:
1295
+ message="Please insert four distinct values to edit the current PIV process iteration!"
1296
+ show_mouse_tooltip(self,message)
1297
+ self.line_edit_IW_set()
1298
+ else:
1299
+ vect=[int(split_text[i]) for i in (0,2,1,3)]
1297
1300
  k=self.PROpar.row
1298
1301
  FlagValid=True
1299
1302
  if k>0: FlagValid=FlagValid and all([vect[i]<=self.PROpar.Vect[i][k-1] for i in range(4)])
1300
1303
  if k<self.PROpar.Nit-1 and FlagValid: FlagValid=FlagValid and all([vect[i]>=self.PROpar.Vect[i][k+1] for i in range(4)])
1301
1304
  if FlagValid:
1305
+ message=""
1306
+ show_mouse_tooltip(self,message)
1302
1307
  for i in range(4):
1303
1308
  self.PROpar.Vect[i][k]=vect[i] #np.array([vect[i]])
1304
1309
  else:
1305
- message='IW sizes o r spacings were assigned inconsistently! Please, retry!'
1306
- showTip(self,message)
1310
+ message='IW sizes or spacings were assigned inconsistently! They must be inserted in decreasing order across iterations. Please, retry!'
1311
+ show_mouse_tooltip(self,message)
1312
+ self.line_edit_IW_set()
1307
1313
  self.PROpar.flag_rect_wind=any([v!=w for v,w in zip(self.PROpar.Vect[0],self.PROpar.Vect[2])]) or any([v!=w for v,w in zip(self.PROpar.Vect[1],self.PROpar.Vect[3])])
1308
1314
  return
1309
1315
 
@@ -1329,6 +1335,7 @@ class Process_Tab(gPaIRS_Tab):
1329
1335
  item=table_iter.currentItem()
1330
1336
  if not item: return
1331
1337
  menu=QMenu(table_iter)
1338
+ menu.setStyleSheet(self.gui.ui.menu.styleSheet())
1332
1339
  buttons=['add', 'delete']
1333
1340
  name=[]
1334
1341
  tips=['Add new iteration to the PIV process','Delete current iteration from the PIV process']
@@ -1361,10 +1368,7 @@ class Process_Tab(gPaIRS_Tab):
1361
1368
  item.setStatusTip('')
1362
1369
 
1363
1370
  message='No context menu available! Please, pause processing.'
1364
- toolTipDuration=self.toolTipDuration()
1365
- self.setToolTipDuration(3000)
1366
- QToolTip.showText(QCursor.pos(),message)
1367
- self.setToolTipDuration(toolTipDuration)
1371
+ show_mouse_tooltip(self,message)
1368
1372
  item.setToolTip(toolTip)
1369
1373
  item.setStatusTip(toolTip)
1370
1374
 
@@ -1688,13 +1692,13 @@ class Process_Tab(gPaIRS_Tab):
1688
1692
  FlagStable=FlagStable and not flagUnstable
1689
1693
  if k==self.PROpar.Nit-1:
1690
1694
  if j==0:
1691
- if self.father and hasattr(self.father,'w_Export'): Res=self.father.w_Export.OUTpar.xres
1695
+ if self.father and hasattr(self.father,'w_Output'): Res=self.father.w_Output.OUTpar.xres
1692
1696
  else: Res=0
1693
1697
  self.MTF=[lam,MTF.T,f'IW size-spacing: {Wa:d}-{Wc:d}. Vel.-correl. windowing: {VelWin}-{CorrWin}.',Res]
1694
1698
 
1695
1699
  else:
1696
1700
  if self.PROpar.Vect[j*2][kVect]>self.PROpar.Vect[(j-1)*2][kVect]:
1697
- if self.father: Res=self.father.w_Export.OUTpar.xres*self.father.w_Export.OUTpar.pixAR
1701
+ if self.father: Res=self.father.w_Output.OUTpar.xres*self.father.w_Output.OUTpar.pixAR
1698
1702
  else: Res=0
1699
1703
  self.MTF=[lam,MTF.T,f'IW size-spacing: {Wa:d}-{Wc:d}. Vel.-correl. windowing: {VelWin}-{CorrWin}.',Res]
1700
1704
  if FlagStable:
@@ -134,15 +134,22 @@ class Process_Tab_Disp(gPaIRS_Tab):
134
134
  def line_edit_IW_action(self):
135
135
  text=self.ui.line_edit_IW.text()
136
136
  split_text=re.split(r'(\d+)', text)[1:-1:2]
137
+ if len(split_text)==0:
138
+ message="Please insert at least one value!"
139
+ show_mouse_tooltip(self,message)
140
+ self.line_edit_IW_set()
141
+ return
137
142
  split_num=[int(t) for t in split_text]
138
143
  if len(split_num)<4: split_num+=[split_num[-1]]*(4-len(split_num))
139
144
  vect=[int(split_num[i]) for i in (0,2,1,3)]
140
145
  FlagValid=len(vect)==4 and all([v>0 for v in vect])
141
146
  if FlagValid:
142
147
  self.PROpar.Vect=vect
148
+ message=""
143
149
  else:
144
150
  message='IW sizes or spacings were assigned inconsistently! Please, retry!'
145
- showTip(self,message)
151
+ show_mouse_tooltip(self,message)
152
+ self.line_edit_IW_set()
146
153
  return
147
154
 
148
155
  #******************** Settings
@@ -0,0 +1,155 @@
1
+ from PySide6 import QtWidgets, QtGui, QtCore
2
+ import sys
3
+ from .PaIRS_pypacks import icons_path, fontPixelSize
4
+
5
+ def showSPIVCalHelp(parent=None,disable_callback=None):
6
+ """
7
+ Shows an informational dialog explaining the correct calibration setup
8
+ for stereoscopic PIV in PaIRS, ensuring compatibility with disparity
9
+ correction and full stereoscopic reconstruction.
10
+ """
11
+ dlg = QtWidgets.QDialog(parent)
12
+ dlg.setWindowTitle("Guidelines for stereoscopic PIV calibration")
13
+ #dlg.resize(900, 750)
14
+ dlg.setMinimumWidth(900)
15
+ dlg.setMinimumHeight(750)
16
+
17
+ main_layout = QtWidgets.QVBoxLayout(dlg)
18
+
19
+ # --- Explanatory text (English, corrected axes) ---
20
+ text = (
21
+ "For stereoscopic PIV, PaIRS assumes that:<br><br>"
22
+
23
+ "&nbsp;&nbsp;• the <b>calibration plate defines "
24
+ "the x–y plane</b> of the calibration coordinate system;<br>"
25
+
26
+ "&nbsp;&nbsp;• the <b>x-axis</b> is <b>aligned with the stereoscopic baseline</b>, i.e. "
27
+ "the direction along which the projections of the two camera viewing rays diverge "
28
+ "on the calibration plate (the dominant disparity direction);<br>"
29
+
30
+ "&nbsp;&nbsp;• the <b>y-axis</b> is then defined as the axis <b>perpendicular to the plane containing "
31
+ "the two cameras</b> (i.e. perpendicular to the triangulation plane formed by the two "
32
+ "optical axes);<br>"
33
+
34
+ "&nbsp;&nbsp;• the <b>z-axis is normal to the plate</b> (typically pointing towards the cameras).<br><br>"
35
+
36
+ "To ensure full compatibility with the operations performed in the disparity correction step and the stereoscopic reconstruction,"
37
+ " the calibration procedure must always adhere to the above coordinate convention.<br>"
38
+ " The example below shows a <b>correct</b> configuration (left) and an <b>incorrect</b> one (right).<br>"
39
+ )
40
+
41
+ text_label = QtWidgets.QLabel()
42
+ text_label.setWordWrap(True)
43
+ text_label.setTextFormat(QtCore.Qt.RichText)
44
+ text_label.setText(f"<div>{text}</div>")
45
+ main_layout.addWidget(text_label)
46
+ font=dlg.font()
47
+ font.setPixelSize(fontPixelSize+4)
48
+ text_label.setFont(font)
49
+
50
+ # --- Side-by-side images ---
51
+ img_layout = QtWidgets.QHBoxLayout()
52
+ img_layout.setSpacing(10)
53
+ main_layout.addLayout(img_layout)
54
+
55
+ # Paths to images (adjust to match PaIRS resources folder)
56
+ img_ok_path = icons_path+"spiv_setup_ok.png"
57
+ img_no_path = icons_path+"spiv_setup_no.png"
58
+
59
+ # --- Correct configuration image ---
60
+ ok_widget = QtWidgets.QVBoxLayout()
61
+ ok_caption = QtWidgets.QLabel()
62
+ caption_text = "<b>Correct configuration (x–z stereo plane)</b>"
63
+ ok_caption.setText(f"<div>{caption_text}</div>")
64
+ ok_caption.setFont(font)
65
+ ok_caption.setTextFormat(QtCore.Qt.RichText)
66
+ ok_caption.setAlignment(QtCore.Qt.AlignCenter)
67
+
68
+ ok_label_img = QtWidgets.QLabel()
69
+ ok_label_img.setAlignment(QtCore.Qt.AlignCenter)
70
+ ok_label_img.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
71
+ ok_label_img.setScaledContents(True)
72
+
73
+ ok_pix = QtGui.QPixmap(img_ok_path)
74
+ if not ok_pix.isNull():
75
+ ok_pix = ok_pix.scaledToWidth(400, QtCore.Qt.SmoothTransformation)
76
+ ok_label_img.setPixmap(ok_pix)
77
+ ok_label_img.setFixedSize(ok_pix.width(),ok_pix.height())
78
+
79
+ ok_widget.addWidget(ok_caption)
80
+ ok_widget.addWidget(ok_label_img)
81
+ img_layout.addLayout(ok_widget)
82
+
83
+ # --- Incorrect configuration image ---
84
+ no_widget = QtWidgets.QVBoxLayout()
85
+ no_caption = QtWidgets.QLabel()
86
+ caption_text = "<b>Incorrect configuration</b>"
87
+ no_caption.setText(f"<div'>{caption_text}</div>")
88
+ no_caption.setFont(font)
89
+ no_caption.setTextFormat(QtCore.Qt.RichText)
90
+ no_caption.setAlignment(QtCore.Qt.AlignCenter)
91
+
92
+ no_label_img = QtWidgets.QLabel()
93
+ no_label_img.setAlignment(QtCore.Qt.AlignCenter)
94
+ no_label_img.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
95
+ no_label_img.setScaledContents(True)
96
+
97
+ no_pix = QtGui.QPixmap(img_no_path)
98
+ if not no_pix.isNull():
99
+ no_pix = no_pix.scaledToWidth(400, QtCore.Qt.SmoothTransformation)
100
+ no_label_img.setPixmap(no_pix)
101
+ no_label_img.setFixedSize(no_pix.width(),no_pix.height())
102
+
103
+ no_widget.addWidget(no_caption)
104
+ no_widget.addWidget(no_label_img)
105
+ img_layout.addLayout(no_widget)
106
+
107
+ # Prevent bottom clipping: allow images row to shrink/expand as needed
108
+ main_layout.setStretch(0, 1)
109
+ main_layout.setStretch(1, 0)
110
+
111
+ # Create a horizontal layout for buttons
112
+ button_layout = QtWidgets.QHBoxLayout()
113
+
114
+ # Spacer pushes OK to the right
115
+ button_layout.addStretch(1)
116
+
117
+ # Left button
118
+ if disable_callback is not None:
119
+ button_disable = QtWidgets.QPushButton("Don't show this message again")
120
+ def on_disable():
121
+ disable_callback()
122
+ dlg.accept()
123
+ button_disable.clicked.connect(on_disable)
124
+ button_layout.addWidget(button_disable)
125
+
126
+ # Right button (OK)
127
+ button_ok = QtWidgets.QPushButton("OK")
128
+ def on_ok():
129
+ dlg.accept()
130
+ button_ok.clicked.connect(on_ok)
131
+ button_layout.addWidget(button_ok)
132
+
133
+ # Add the layout to the dialog
134
+ main_layout.addSpacing(12)
135
+ main_layout.addLayout(button_layout)
136
+
137
+ button_ok.setDefault(True)
138
+ button_ok.setFocus()
139
+
140
+ main_layout.setContentsMargins(20, 25, 20, 25) # slightly larger bottom margin to avoid macOS clipping
141
+
142
+ dlg.exec()
143
+
144
+ if __name__ == "__main__":
145
+ # QApplication MUST be created before any QWidget
146
+ app = QtWidgets.QApplication(sys.argv)
147
+
148
+ # Parent window (optional)
149
+ main_window = QtWidgets.QMainWindow()
150
+ main_window.show()
151
+
152
+ # Show the SPIV calibration dialog
153
+ showSPIVCalHelp(parent=main_window)
154
+
155
+ sys.exit(app.exec())