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.
Files changed (38) hide show
  1. PaIRS_UniNa/Calibration_Tab.py +16 -0
  2. PaIRS_UniNa/Changes.txt +39 -0
  3. PaIRS_UniNa/Explorer.py +311 -75
  4. PaIRS_UniNa/FolderLoop.py +196 -6
  5. PaIRS_UniNa/Input_Tab.py +160 -53
  6. PaIRS_UniNa/Input_Tab_CalVi.py +11 -12
  7. PaIRS_UniNa/Input_Tab_tools.py +17 -15
  8. PaIRS_UniNa/Output_Tab.py +1 -3
  9. PaIRS_UniNa/PaIRS_pypacks.py +63 -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 +2 -0
  14. PaIRS_UniNa/TabTools.py +165 -6
  15. PaIRS_UniNa/Vis_Tab.py +50 -22
  16. PaIRS_UniNa/Vis_Tab_CalVi.py +1 -2
  17. PaIRS_UniNa/Whatsnew.py +4 -3
  18. PaIRS_UniNa/_PaIRS_PIV.so +0 -0
  19. PaIRS_UniNa/__init__.py +3 -3
  20. PaIRS_UniNa/addwidgets_ps.py +570 -70
  21. PaIRS_UniNa/gPaIRS.py +118 -17
  22. PaIRS_UniNa/icons/folder_loop_cleanup.png +0 -0
  23. PaIRS_UniNa/icons/folder_loop_cleanup_off.png +0 -0
  24. PaIRS_UniNa/icons/information.png +0 -0
  25. PaIRS_UniNa/icons/information2.png +0 -0
  26. PaIRS_UniNa/icons/scan_path_loop.png +0 -0
  27. PaIRS_UniNa/icons/scan_path_loop_off.png +0 -0
  28. PaIRS_UniNa/icons/spiv_setup_no.png +0 -0
  29. PaIRS_UniNa/icons/spiv_setup_ok.png +0 -0
  30. PaIRS_UniNa/procTools.py +46 -1
  31. PaIRS_UniNa/rqrdpckgs.txt +7 -7
  32. PaIRS_UniNa/ui_Calibration_Tab.py +92 -59
  33. PaIRS_UniNa/ui_gPairs.py +8 -8
  34. PaIRS_UniNa/whatsnew.txt +2 -3
  35. {pairs_unina-0.2.7.dist-info → pairs_unina-0.2.10.dist-info}/METADATA +7 -8
  36. {pairs_unina-0.2.7.dist-info → pairs_unina-0.2.10.dist-info}/RECORD +38 -30
  37. {pairs_unina-0.2.7.dist-info → pairs_unina-0.2.10.dist-info}/WHEEL +0 -0
  38. {pairs_unina-0.2.7.dist-info → pairs_unina-0.2.10.dist-info}/top_level.txt +0 -0
@@ -34,6 +34,8 @@ button_tips={
34
34
  }
35
35
  combo_tips={}
36
36
  class INPpar_CalVi(TABpar):
37
+ pathCompleter=basefold_DEBUGOptions
38
+
37
39
  def __init__(self,Process=ProcessTypes.null,Step=StepTypes.null):
38
40
  self.setup(Process,Step)
39
41
  super().__init__('INPpar_CalVi','Input_CalVi')
@@ -79,8 +81,6 @@ class INPpar_CalVi(TABpar):
79
81
 
80
82
  self.errorMessage = ''
81
83
  self.FlagReadCalib = False
82
-
83
- self.pathCompleter=basefold_DEBUGOptions
84
84
 
85
85
  class Input_Tab_CalVi(gPaIRS_Tab):
86
86
 
@@ -178,6 +178,7 @@ class Input_Tab_CalVi(gPaIRS_Tab):
178
178
 
179
179
  def listContextMenuEvent(self, list_images:QTableWidget, event):
180
180
  menu=QMenu(list_images)
181
+ menu.setStyleSheet(self.gui.ui.menu.styleSheet())
181
182
  buttons=['import', 'import_plane',
182
183
  -1,'down','up',
183
184
  -1,'delete','clean']
@@ -290,13 +291,13 @@ class Input_Tab_CalVi(gPaIRS_Tab):
290
291
  def line_edit_path_preaction(self):
291
292
  currpath=myStandardPath(self.ui.line_edit_path.text())
292
293
  self.FlagScanPath=os.path.normpath(self.INPpar.path)!=currpath
293
- directory_path = myStandardPath(os.getcwd())
294
- if directory_path in currpath:
295
- currpath=currpath.replace(directory_path,'./')
296
- if os.path.exists(currpath):
297
- if currpath in self.INPpar.pathCompleter: self.INPpar.pathCompleter.remove(currpath)
298
- self.INPpar.pathCompleter.insert(0,currpath)
299
- if len(self.INPpar.pathCompleter)>10: self.INPpar.pathCompleter=self.INPpar.pathCompleter[:10]
294
+ currpath=relativizePath(currpath)
295
+ if os.path.exists(currpath) and currpath!='./':
296
+ pathCompleter=INPpar_CalVi.pathCompleter
297
+ if currpath in pathCompleter: pathCompleter.remove(currpath)
298
+ pathCompleter.insert(0,currpath)
299
+ if len(pathCompleter)>pathCompleterLength:
300
+ INPpar_CalVi.pathCompleter=pathCompleter[:pathCompleterLength]
300
301
  self.ui.line_edit_path.setText(currpath)
301
302
 
302
303
  def button_path_action(self):
@@ -691,9 +692,7 @@ class Input_Tab_CalVi(gPaIRS_Tab):
691
692
 
692
693
  def line_edit_path_out_preaction(self):
693
694
  currpath=myStandardPath(self.ui.line_edit_path_out.text())
694
- directory_path = myStandardPath(os.getcwd())
695
- if directory_path in currpath:
696
- currpath=currpath.replace(directory_path,'./')
695
+ currpath=relativizePath(currpath)
697
696
  self.ui.line_edit_path_out.setText(currpath)
698
697
 
699
698
  def button_path_out_action(self):
@@ -188,7 +188,8 @@ class ImageSet(TABpar):
188
188
  pri.Error.red(f'Trying to access a non-existing index position ({k}) in the image set structure ({self.count} sets identified)')
189
189
  return []
190
190
  f=i+npairs*step
191
- if k>-1: return [self.nameF(self.fname[k],j) for j in range(i,f,step)]
191
+ if k>-1 and step>0:
192
+ return [self.nameF(self.fname[k],j) for j in range(i,f,step)]
192
193
  else:
193
194
  return ['' for _ in range(i,f,step)] if step else []
194
195
 
@@ -476,6 +477,9 @@ class PaIRSTree(QTreeWidget):
476
477
  #self.setStyleSheet(f"QTreeWidget::item:selected:active {{{style}}}")
477
478
 
478
479
  style = """
480
+ QTreeWidget::item:hover {
481
+ background-color: rgba(0, 116, 255, 0.1);
482
+ }
479
483
  QTreeWidget::item:selected:!active {
480
484
  background-color: rgba(0, 116, 255, 0.4);
481
485
  }
@@ -1208,7 +1212,7 @@ class GlobalImageTree(AsynPaIRSTree):
1208
1212
  header.setSectionResizeMode(2, QHeaderView.ResizeMode.Interactive)
1209
1213
  self.headerItem().setTextAlignment(0,Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
1210
1214
  self.setUniformRowHeights(True)
1211
- self.setStyleSheet(f"QTreeView::item {{ height: {self.default_row_height} px; }}")
1215
+ self.setStyleSheet(self.styleSheet()+f"QTreeView::item {{ height: {self.default_row_height} px; }}")
1212
1216
 
1213
1217
  self.path=''
1214
1218
  self.ncam=1
@@ -1319,11 +1323,11 @@ class GlobalImageTree(AsynPaIRSTree):
1319
1323
  topLevelItem.setIcon(0,self.icon_warning)
1320
1324
  topLevelItem.setToolTip(0,'Files (!) missing')
1321
1325
  topLevelItem.setStatusTip(0,'Files (!) missing')
1322
- else:
1326
+ elif k in self.warns:
1323
1327
  self.warns.remove(k)
1324
1328
  topLevelItem.setIcon(0,QIcon())
1325
1329
  topLevelItem.setToolTip(0,'')
1326
- topLevelItem.setStatusTip(0,'Files (!) missing')
1330
+ topLevelItem.setStatusTip(0,'')
1327
1331
  return
1328
1332
  for k in range(self.topLevelItemCount()): scan_items(k)
1329
1333
  self.resetImNumber()
@@ -1816,7 +1820,7 @@ class ImageTreeWidget(QWidget):
1816
1820
  self.imTree.setVisible(True)
1817
1821
  self.imTree.keyPressEvent=lambda e: self.treeKeyPressEvent(e)
1818
1822
  self.imTree.installEventFilter(self)
1819
-
1823
+
1820
1824
  self.FlagSettingPar=None
1821
1825
 
1822
1826
  def eventFilter(self, obj, event):
@@ -2416,6 +2420,7 @@ class ImageTreeWidget(QWidget):
2416
2420
  item=tree.currentItem()
2417
2421
  if not item: return
2418
2422
  menu=QMenu(tree)
2423
+ menu.setStyleSheet(self.window().ui.menu.styleSheet())
2419
2424
  name=[]
2420
2425
  act=[]
2421
2426
  fun=[]
@@ -2461,14 +2466,11 @@ class CalibrationTree(PaIRSTree):
2461
2466
  def __init__(self, parent: QWidget=None,listDim=2,listDepth=1):
2462
2467
  super().__init__(parent,listDim,listDepth)
2463
2468
 
2464
- columns=["#","filename"]
2465
- self.setColumnCount(len(columns))
2466
- self.setHeaderLabels(columns)
2467
2469
  header=self.header()
2468
- self.headerItem().setTextAlignment(0,Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
2469
2470
  header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
2470
2471
  header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
2471
- self.setStyleSheet(f"QTreeView::item {{ height: {self.default_row_height} px; }}")
2472
+ self.setStyleSheet(self.styleSheet()+f"QTreeView::item {{ height: {self.default_row_height} px; }}")
2473
+ self.headerItem().setTextAlignment(0,Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
2472
2474
 
2473
2475
  self.ncam=1
2474
2476
  self.calList=create_empty_list_of_dimension(1)
@@ -2628,15 +2630,15 @@ class PaIRSTable(QTableWidget):
2628
2630
 
2629
2631
  self.pen = QPen(qRgba(127,227,255,0.9))
2630
2632
  self.pen.setWidth(3)
2631
- #style="background-color: rgba(173,216,230,0.1); color: rgba(128,128,128,0.25);"
2632
- #self.setStyleSheet(f"QTreeWidget::item:selected {{{style}}}")
2633
- #self.setStyleSheet(f"QTreeWidget::item:selected:active {{{style}}}")
2634
2633
 
2635
2634
  style = """
2636
- QTreeWidget::item:selected:!active {
2635
+ QTableWidget::item:hover {
2636
+ background-color: rgba(0, 116, 255, 0.1);
2637
+ }
2638
+ QTableWidget::item:selected:!active {
2637
2639
  background-color: rgba(0, 116, 255, 0.4);
2638
2640
  }
2639
- QTreeWidget::item:selected:active {
2641
+ QTableWidget::item:selected:active {
2640
2642
  background-color: rgba(0, 116, 255, 0.8);
2641
2643
  }
2642
2644
  """
PaIRS_UniNa/Output_Tab.py CHANGED
@@ -810,9 +810,7 @@ class Output_Tab(gPaIRS_Tab):
810
810
 
811
811
  def line_edit_path_preaction(self):
812
812
  currpath=myStandardPath(self.ui.line_edit_path.text())
813
- directory_path = myStandardPath(os.getcwd())
814
- if directory_path in currpath:
815
- currpath=currpath.replace(directory_path,'./')
813
+ currpath=relativizePath(currpath)
816
814
  self.ui.line_edit_path.setText(currpath)
817
815
 
818
816
  def button_path_action(self):
@@ -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']:
@@ -142,11 +144,21 @@ f_empty_width=250 #blank space in scrollable area within the main window
142
144
  time_ScrollBar=250 #time of animation of scroll area
143
145
  time_callback2_async=0 #time to test async callbacks
144
146
  time_showSplashOnTop=250
147
+ pathCompleterLength=10
145
148
 
146
149
  fileChanges='Changes.txt'
147
150
  fileWhatsNew=['whatsnew.txt','whatwasnew.txt']
148
151
  icons_path="icons/"
149
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
+
150
162
  from psutil import cpu_count
151
163
  NUMTHREADS_MAX=cpu_count(logical=True)#-1
152
164
  if NUMTHREADS_MAX<1: NUMTHREADS_MAX=1
@@ -634,6 +646,13 @@ def myStandardRoot(root):
634
646
  currroot = re.sub('/+', '/', currroot) # Reduce consecutive slashes to a single slash
635
647
  return currroot
636
648
 
649
+ def relativizePath(currpath:str):
650
+ return currpath
651
+ directory_path = myStandardPath(os.getcwd())
652
+ if directory_path in currpath:
653
+ currpath=currpath.replace(directory_path,'./')
654
+ return currpath
655
+
637
656
  def findFiles_sorted(pattern):
638
657
  list_files=glob.glob(pattern)
639
658
  files=sorted([re.sub(r'\\+',r'/',f) for f in list_files],key=str.lower)
@@ -758,17 +777,48 @@ def identifierName(typeObject:str='proc'):
758
777
  name=version_user_info+'_'+typeObject+'_'+id
759
778
  return name, username, __version__
760
779
 
780
+ def fileIdenitifierCheck(id: str, filename: str) -> bool:
781
+ """
782
+ Extract the date/time from the identifier 'name' and check whether 'filename'
783
+ has been modified after that timestamp.
784
+ Returns True if file is newer, False otherwise.
785
+ """
786
+
787
+ # --- Extract timestamp from name ---
788
+ # Expected pattern: ..._<date>-<time>_...
789
+ # Example: 'PaIRS-v0.2.8_Linux-user_2025/11/07-12:45:31_proc_...'
790
+ try:
791
+ parts = id.split('_')
792
+ date_str, time_str = parts[2].split('-')[0], parts[2].split('-')[1]
793
+ except Exception:
794
+ pri.Error.red("Identifier format not recognized: cannot extract date and time.")
795
+ return False
796
+
797
+ # Convert date/time strings into QDateTime
798
+ qdate = QDate.fromString(date_str, 'yyyy/MM/dd')
799
+ qtime = QTime.fromString(time_str, 'HH:mm:ss')
800
+ qdt_identifier = QDateTime(qdate, qtime)
801
+ qdt_identifier = qdt_identifier.addSecs(-1) #to be safe
802
+
803
+ if not qdt_identifier.isValid():
804
+ pri.Error.red("Parsed QDateTime is not valid. Check identifier format.")
805
+ return False
806
+
807
+ # --- File timestamp ---
808
+ if not os.path.exists(filename):
809
+ return False
810
+
811
+ file_mtime = os.path.getmtime(filename)
812
+ qdt_file = QDateTime.fromSecsSinceEpoch(int(file_mtime))
813
+
814
+ # True if file was modified after the timestamp stored in name
815
+ return qdt_file > qdt_identifier
816
+
761
817
  PlainTextConverter=QtGui.QTextDocument()
762
818
  def toPlainText(text):
763
819
  PlainTextConverter.setHtml(text) #for safety
764
820
  return PlainTextConverter.toPlainText()
765
821
 
766
- def showTip(obj,message):
767
- toolTipDuration=obj.toolTipDuration()
768
- obj.setToolTipDuration(3000)
769
- QToolTip.showText(QCursor.pos(),message)
770
- obj.setToolTipDuration(toolTipDuration)
771
-
772
822
  def clean_tree(tree:QTreeWidget):
773
823
  def remove_children(item:QTreeWidgetItem):
774
824
  while item.childCount() > 0:
@@ -1121,59 +1171,6 @@ def checkOutDated(packageName:str,printOutDated):
1121
1171
  f3=executor.submit(asyncio.run,checkOutDatedInternal(packageName))
1122
1172
  f3.add_done_callback(checkOutDatedComplete)
1123
1173
 
1124
- def changes(self,TabType,filename,title=" Changes"):
1125
- FlagShow=False
1126
- if self.logChanges:
1127
- if self.logChanges.isVisible():
1128
- FlagShow=True
1129
- if FlagShow:
1130
- self.logChanges.hide()
1131
- self.logChanges.show()
1132
- else:
1133
- self.logChanges=TabType(self,True)
1134
- self.logChanges.resize(720,720)
1135
- self.logChanges.show()
1136
- self.logChanges.ui.progress_Proc.hide()
1137
- self.logChanges.ui.button_close_tab.hide()
1138
- icon=QPixmap(''+ icons_path +'news.png')
1139
- self.logChanges.ui.icon.setPixmap(icon)
1140
- self.logChanges.setWindowIcon(self.windowIcon())
1141
- self.logChanges.setWindowTitle(title)
1142
- self.logChanges.ui.name_tab.setText(title)
1143
-
1144
- self.logChanges.ui.log.setLineWrapColumnOrWidth(self.logChanges.ui.log.width()-20)
1145
- self.logChanges.ui.log.setStyleSheet("")
1146
-
1147
- def setFontPixelSize(logChanges:type(self.logChanges),fPixSize):
1148
- logfont=self.font()
1149
- logfont.setFamily(fontName)
1150
- logfont.setPixelSize(fPixSize+2)
1151
- logChanges.ui.log.setFont(logfont)
1152
- fPixSize_TabNames=min([fPixSize*2,30])
1153
- lab=logChanges.ui.name_tab
1154
- font=lab.font()
1155
- font.setPixelSize(fPixSize_TabNames)
1156
- lab.setFont(font)
1157
- self.logChanges.setFontPixelSize=lambda fS: setFontPixelSize(self.logChanges,fS)
1158
- self.logChanges.setFontPixelSize(self.TABpar.fontPixelSize)
1159
- def logResizeEvent(logChanges:type(self.logChanges),e):
1160
- super(type(logChanges),logChanges).resizeEvent(e)
1161
- logChanges.ui.log.setLineWrapColumnOrWidth(logChanges.ui.log.width()-20)
1162
- self.logChanges.ui.log.resizeEvent=lambda e: logResizeEvent(self.logChanges,e)
1163
-
1164
- self.logChanges.ui.icon.addfuncclick['whatsnew']=self.whatsNew
1165
- self.logChanges.ui.icon.setCustomCursor()
1166
-
1167
- try:
1168
- file = open(filename, "rb")
1169
- content = file.read().decode("utf-8")
1170
- self.logChanges.ui.log.setText(content)
1171
- file.close()
1172
- except Exception as inst:
1173
- pri.Error.red(f'There was a problem while reading the file {filename}:\n{inst}')
1174
- 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__}.')
1175
- return
1176
-
1177
1174
  import webbrowser
1178
1175
  def downloadExampleData(self,url):
1179
1176
  Message=f'Test data are available at the following link:\n{url}'
@@ -1309,7 +1306,7 @@ def checkRequiredPackages(self, filename=rqrdpckgs_filename, FlagDisplay=False,
1309
1306
  else:
1310
1307
  pri.Error.red(f"Malformed line: {line}")
1311
1308
 
1312
- Flag = False
1309
+ FlagUpdateFile = False
1313
1310
  warnings = []
1314
1311
 
1315
1312
  for i, pkg in enumerate(required_packages):
@@ -1319,9 +1316,10 @@ def checkRequiredPackages(self, filename=rqrdpckgs_filename, FlagDisplay=False,
1319
1316
  installed_version = None
1320
1317
 
1321
1318
  # Update current installed version
1322
- if installed_version is not None and (installed_version != vcurr_list[i] or FlagDisplay):
1323
- vcurr_list[i] = installed_version
1324
- 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
1325
1323
 
1326
1324
  # Check if within [vmin, vmax]
1327
1325
  if not (le_ver(vmin_list[i],installed_version) and le_ver(installed_version,vmax_list[i])):
@@ -1336,7 +1334,7 @@ def checkRequiredPackages(self, filename=rqrdpckgs_filename, FlagDisplay=False,
1336
1334
 
1337
1335
  # Show warning
1338
1336
  if len(warnings)>0: self.FlagPackIssue=True
1339
- if len(warnings)>0 or FlagForcePrint:
1337
+ if ( (FlagUpdateFile or FlagDisplay) and len(warnings)>0 ) or FlagForcePrint :
1340
1338
  message = (
1341
1339
  "Some installed packages have a version outside the target range used to develop "
1342
1340
  "the current release of the PaIRS_UniNa package.\n\n"
@@ -1368,7 +1366,7 @@ def checkRequiredPackages(self, filename=rqrdpckgs_filename, FlagDisplay=False,
1368
1366
  warningDialog(self, Message="All installed packages are within the expected version range.", flagScreenCenter=True,pixmap=icons_path+'greenv.png')
1369
1367
 
1370
1368
  # Update file if needed
1371
- if Flag:
1369
+ if FlagUpdateFile:
1372
1370
  with open(filename, "w") as f:
1373
1371
  for pkg, vmin, vmax, vcurr in zip(required_packages, vmin_list, vmax_list, vcurr_list):
1374
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())
@@ -64,6 +64,8 @@ class GPApar(TABpar):
64
64
 
65
65
  self.printTypes = printTypes
66
66
  self.NumCores = 0
67
+ self.globalVals = {}
68
+ self.globalExceptions = {'Calibration': ['FlagSPIVCal']}
67
69
 
68
70
  self.stateFields=[f for f,_ in self.__dict__.items() if f not in self.infoFields]
69
71