PaIRS-UniNa 0.2.7__cp311-cp311-win_amd64.whl → 0.2.11__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.
Files changed (50) hide show
  1. PaIRS_UniNa/Calibration_Tab.py +40 -24
  2. PaIRS_UniNa/Changes.txt +50 -0
  3. PaIRS_UniNa/Explorer.py +257 -77
  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 +30 -28
  8. PaIRS_UniNa/Output_Tab.py +1 -3
  9. PaIRS_UniNa/PaIRS_pypacks.py +171 -67
  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 +3 -0
  14. PaIRS_UniNa/TabTools.py +201 -9
  15. PaIRS_UniNa/Vis_Tab.py +221 -65
  16. PaIRS_UniNa/Vis_Tab_CalVi.py +139 -12
  17. PaIRS_UniNa/Whatsnew.py +4 -3
  18. PaIRS_UniNa/_PaIRS_PIV.pyd +0 -0
  19. PaIRS_UniNa/__init__.py +3 -3
  20. PaIRS_UniNa/addwidgets_ps.py +773 -97
  21. PaIRS_UniNa/calibView.py +5 -2
  22. PaIRS_UniNa/gPaIRS.py +307 -48
  23. PaIRS_UniNa/icons/closeAllFloat.png +0 -0
  24. PaIRS_UniNa/icons/defaultWinSize.png +0 -0
  25. PaIRS_UniNa/icons/dockVis.png +0 -0
  26. PaIRS_UniNa/icons/dockVis_disable.png +0 -0
  27. PaIRS_UniNa/icons/floatingVisSize.png +0 -0
  28. PaIRS_UniNa/icons/folder_loop_cleanup.png +0 -0
  29. PaIRS_UniNa/icons/folder_loop_cleanup_off.png +0 -0
  30. PaIRS_UniNa/icons/fullWinsize.png +0 -0
  31. PaIRS_UniNa/icons/icon_PaIRS.ico +0 -0
  32. PaIRS_UniNa/icons/information.png +0 -0
  33. PaIRS_UniNa/icons/information2.png +0 -0
  34. PaIRS_UniNa/icons/scan_path_loop.png +0 -0
  35. PaIRS_UniNa/icons/scan_path_loop_off.png +0 -0
  36. PaIRS_UniNa/icons/smallWinSize.png +0 -0
  37. PaIRS_UniNa/icons/spiv_setup_no.png +0 -0
  38. PaIRS_UniNa/icons/spiv_setup_ok.png +0 -0
  39. PaIRS_UniNa/icons/undockVis.png +0 -0
  40. PaIRS_UniNa/procTools.py +46 -1
  41. PaIRS_UniNa/rqrdpckgs.txt +7 -7
  42. PaIRS_UniNa/tabSplitter.py +6 -1
  43. PaIRS_UniNa/ui_Calibration_Tab.py +92 -59
  44. PaIRS_UniNa/ui_gPairs.py +92 -50
  45. PaIRS_UniNa/ui_infoPaIRS.py +8 -8
  46. PaIRS_UniNa/whatsnew.txt +2 -3
  47. {pairs_unina-0.2.7.dist-info → pairs_unina-0.2.11.dist-info}/METADATA +6 -8
  48. {pairs_unina-0.2.7.dist-info → pairs_unina-0.2.11.dist-info}/RECORD +50 -33
  49. {pairs_unina-0.2.7.dist-info → pairs_unina-0.2.11.dist-info}/WHEEL +1 -1
  50. {pairs_unina-0.2.7.dist-info → pairs_unina-0.2.11.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(gPaIRS_QMenu_style)
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
 
@@ -475,13 +476,16 @@ class PaIRSTree(QTreeWidget):
475
476
  #self.setStyleSheet(f"QTreeWidget::item:selected {{{style}}}")
476
477
  #self.setStyleSheet(f"QTreeWidget::item:selected:active {{{style}}}")
477
478
 
478
- style = """
479
- QTreeWidget::item:selected:!active {
480
- background-color: rgba(0, 116, 255, 0.4);
481
- }
482
- QTreeWidget::item:selected:active {
483
- background-color: rgba(0, 116, 255, 0.8);
484
- }
479
+ style = f"""
480
+ QTreeWidget::item:hover {{
481
+ background-color: {PaIRS_ghostblue};
482
+ }}
483
+ QTreeWidget::item:selected:!active {{
484
+ background-color: {PaIRS_lightblue};
485
+ }}
486
+ QTreeWidget::item:selected:active {{
487
+ background-color: {PaIRS_blue};
488
+ }}
485
489
  """
486
490
  self.setStyleSheet(style)
487
491
 
@@ -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):
@@ -2156,7 +2160,7 @@ class ImageTreeWidget(QWidget):
2156
2160
  self.imTree.spinSelection([r+1,c+1,f+1])
2157
2161
  else: #single tree
2158
2162
  self.label.setText(self.name + f' (cam: {c+1}, frame: {f+1})')
2159
- self.label.setStyleSheet('QLabel{color:rgba(0, 116, 255, 1)}')
2163
+ self.label.setStyleSheet(f'QLabel{{color: {PaIRS_blue}}}')
2160
2164
  self.icon_label.setVisible(True)
2161
2165
  indTree=1+c*2+f
2162
2166
  if indTree!=self.indTree:
@@ -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(gPaIRS_QMenu_style)
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,17 +2630,17 @@ 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
- style = """
2636
- QTreeWidget::item:selected:!active {
2637
- background-color: rgba(0, 116, 255, 0.4);
2638
- }
2639
- QTreeWidget::item:selected:active {
2640
- background-color: rgba(0, 116, 255, 0.8);
2641
- }
2634
+ style = f"""
2635
+ QTableWidget::item:hover {{
2636
+ background-color: {PaIRS_ghostblue};
2637
+ }}
2638
+ QTableWidget::item:selected:!active {{
2639
+ background-color: {PaIRS_lightblue};
2640
+ }}
2641
+ QTableWidget::item:selected:active {{
2642
+ background-color: {PaIRS_blue};
2643
+ }}
2642
2644
  """
2643
2645
  self.setStyleSheet(style)
2644
2646
 
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,74 @@ 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
+ PaIRS_blue_transp = 0.8
154
+ PaIRS_blue_transp_str = f"{PaIRS_blue_transp:.2f}"
155
+ PaIRS_blue = f"rgba(0, 116, 255, {PaIRS_blue_transp_str})"
156
+ PaIRS_lightblue = PaIRS_blue.replace(PaIRS_blue_transp_str,f"{PaIRS_blue_transp/2:.2f}")
157
+ PaIRS_faintblue = PaIRS_blue.replace(PaIRS_blue_transp_str,f"{PaIRS_blue_transp/4:.2f}")
158
+ PaIRS_ghostblue = PaIRS_blue.replace(PaIRS_blue_transp_str,f"{PaIRS_blue_transp/8:.2f}")
159
+ PaIRS_pink = "rgb(255,230,230)"
160
+ PaIRS_darkblue = "rgb(33,33,255)"
161
+ PaIRS_gray = "rgb(128,128,128)"
162
+ gPaIRS_QMenu_style=f"""
163
+ QMenu::item:selected{{
164
+ background-color: {PaIRS_blue};
165
+ color: white;
166
+ }}
167
+ QMenu::item:checked,
168
+ QMenu::item:pressed {{
169
+ background-color: {PaIRS_blue};
170
+ color: none;
171
+ }}
172
+ QMenu::item:hover {{
173
+ background-color: {PaIRS_lightblue};
174
+ color: none;
175
+ }}
176
+ """
177
+ gPaIRS_Completer_style = f"""
178
+ QAbstractItemView {{
179
+ background: transparent,
180
+ }}
181
+ QAbstractItemView::item {{
182
+ padding: 4px 3px;
183
+ }}
184
+ QAbstractItemView::item:hover {{
185
+ background-color: {PaIRS_faintblue};
186
+ color: none;
187
+ }}
188
+ QAbstractItemView::item:focus {{
189
+ background-color: {PaIRS_blue};
190
+ color: white;
191
+ }}
192
+ """
193
+
194
+ def str2QColor(s: str):
195
+ s = s.strip().lower()
196
+
197
+ # Match rgba(r, g, b, a)
198
+ m = re.match(r"rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([0-9.]+)\s*\)", s)
199
+ if m:
200
+ r, g, b, a = m.groups()
201
+ r, g, b = int(r), int(g), int(b)
202
+ # CSS alpha is 0..1, Qt uses 0..255
203
+ a = int(float(a) * 255)
204
+ return QColor(r, g, b, a)
205
+
206
+ # Match rgb(r, g, b)
207
+ m = re.match(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)", s)
208
+ if m:
209
+ r, g, b = map(int, m.groups())
210
+ return QColor(r, g, b)
211
+
212
+ raise ValueError(f"Invalid CSS color format: {s}")
213
+
214
+
150
215
  from psutil import cpu_count
151
216
  NUMTHREADS_MAX=cpu_count(logical=True)#-1
152
217
  if NUMTHREADS_MAX<1: NUMTHREADS_MAX=1
@@ -276,7 +341,7 @@ def activateFlagDebug(Flag=True):
276
341
 
277
342
  PaIRS_Header=f'PaIRS - version {__version__}\n'+\
278
343
  'Particle Image Reconstruction Software\n'+\
279
- f'(C) {__year__} Gerardo Paolillo & Tommaso Astarita.\nAll rights reserved.\n'+\
344
+ f'(C) 2023-{__year__} Gerardo Paolillo & Tommaso Astarita.\nAll rights reserved.\n'+\
280
345
  f'email: {__mail__}\n'+\
281
346
  '****************************************\n'
282
347
 
@@ -634,6 +699,13 @@ def myStandardRoot(root):
634
699
  currroot = re.sub('/+', '/', currroot) # Reduce consecutive slashes to a single slash
635
700
  return currroot
636
701
 
702
+ def relativizePath(currpath:str):
703
+ return currpath
704
+ directory_path = myStandardPath(os.getcwd())
705
+ if directory_path in currpath:
706
+ currpath=currpath.replace(directory_path,'./')
707
+ return currpath
708
+
637
709
  def findFiles_sorted(pattern):
638
710
  list_files=glob.glob(pattern)
639
711
  files=sorted([re.sub(r'\\+',r'/',f) for f in list_files],key=str.lower)
@@ -758,17 +830,48 @@ def identifierName(typeObject:str='proc'):
758
830
  name=version_user_info+'_'+typeObject+'_'+id
759
831
  return name, username, __version__
760
832
 
833
+ def fileIdenitifierCheck(id: str, filename: str) -> bool:
834
+ """
835
+ Extract the date/time from the identifier 'name' and check whether 'filename'
836
+ has been modified after that timestamp.
837
+ Returns True if file is newer, False otherwise.
838
+ """
839
+
840
+ # --- Extract timestamp from name ---
841
+ # Expected pattern: ..._<date>-<time>_...
842
+ # Example: 'PaIRS-v0.2.8_Linux-user_2025/11/07-12:45:31_proc_...'
843
+ try:
844
+ parts = id.split('_')
845
+ date_str, time_str = parts[2].split('-')[0], parts[2].split('-')[1]
846
+ except Exception:
847
+ pri.Error.red("Identifier format not recognized: cannot extract date and time.")
848
+ return False
849
+
850
+ # Convert date/time strings into QDateTime
851
+ qdate = QDate.fromString(date_str, 'yyyy/MM/dd')
852
+ qtime = QTime.fromString(time_str, 'HH:mm:ss')
853
+ qdt_identifier = QDateTime(qdate, qtime)
854
+ qdt_identifier = qdt_identifier.addSecs(-1) #to be safe
855
+
856
+ if not qdt_identifier.isValid():
857
+ pri.Error.red("Parsed QDateTime is not valid. Check identifier format.")
858
+ return False
859
+
860
+ # --- File timestamp ---
861
+ if not os.path.exists(filename):
862
+ return False
863
+
864
+ file_mtime = os.path.getmtime(filename)
865
+ qdt_file = QDateTime.fromSecsSinceEpoch(int(file_mtime))
866
+
867
+ # True if file was modified after the timestamp stored in name
868
+ return qdt_file > qdt_identifier
869
+
761
870
  PlainTextConverter=QtGui.QTextDocument()
762
871
  def toPlainText(text):
763
872
  PlainTextConverter.setHtml(text) #for safety
764
873
  return PlainTextConverter.toPlainText()
765
874
 
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
875
  def clean_tree(tree:QTreeWidget):
773
876
  def remove_children(item:QTreeWidgetItem):
774
877
  while item.childCount() > 0:
@@ -1121,59 +1224,6 @@ def checkOutDated(packageName:str,printOutDated):
1121
1224
  f3=executor.submit(asyncio.run,checkOutDatedInternal(packageName))
1122
1225
  f3.add_done_callback(checkOutDatedComplete)
1123
1226
 
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
1227
  import webbrowser
1178
1228
  def downloadExampleData(self,url):
1179
1229
  Message=f'Test data are available at the following link:\n{url}'
@@ -1242,6 +1292,8 @@ class PaIRSApp(QApplication):
1242
1292
  super().__init__(*args)
1243
1293
  self.installMessageHandler()
1244
1294
  self.setStyle('Fusion')
1295
+ self.setAttribute(Qt.AA_EnableHighDpiScaling, True)
1296
+ self.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
1245
1297
 
1246
1298
  def applicationSupportsSecureRestorableState(self):
1247
1299
  return True
@@ -1309,7 +1361,7 @@ def checkRequiredPackages(self, filename=rqrdpckgs_filename, FlagDisplay=False,
1309
1361
  else:
1310
1362
  pri.Error.red(f"Malformed line: {line}")
1311
1363
 
1312
- Flag = False
1364
+ FlagUpdateFile = False
1313
1365
  warnings = []
1314
1366
 
1315
1367
  for i, pkg in enumerate(required_packages):
@@ -1319,9 +1371,10 @@ def checkRequiredPackages(self, filename=rqrdpckgs_filename, FlagDisplay=False,
1319
1371
  installed_version = None
1320
1372
 
1321
1373
  # 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
1374
+ if installed_version is not None:
1375
+ if installed_version != vcurr_list[i]:
1376
+ vcurr_list[i] = installed_version
1377
+ FlagUpdateFile = True
1325
1378
 
1326
1379
  # Check if within [vmin, vmax]
1327
1380
  if not (le_ver(vmin_list[i],installed_version) and le_ver(installed_version,vmax_list[i])):
@@ -1336,7 +1389,7 @@ def checkRequiredPackages(self, filename=rqrdpckgs_filename, FlagDisplay=False,
1336
1389
 
1337
1390
  # Show warning
1338
1391
  if len(warnings)>0: self.FlagPackIssue=True
1339
- if len(warnings)>0 or FlagForcePrint:
1392
+ if ( (FlagUpdateFile or FlagDisplay) and len(warnings)>0 ) or FlagForcePrint :
1340
1393
  message = (
1341
1394
  "Some installed packages have a version outside the target range used to develop "
1342
1395
  "the current release of the PaIRS_UniNa package.\n\n"
@@ -1368,9 +1421,60 @@ def checkRequiredPackages(self, filename=rqrdpckgs_filename, FlagDisplay=False,
1368
1421
  warningDialog(self, Message="All installed packages are within the expected version range.", flagScreenCenter=True,pixmap=icons_path+'greenv.png')
1369
1422
 
1370
1423
  # Update file if needed
1371
- if Flag:
1424
+ if FlagUpdateFile:
1372
1425
  with open(filename, "w") as f:
1373
1426
  for pkg, vmin, vmax, vcurr in zip(required_packages, vmin_list, vmax_list, vcurr_list):
1374
1427
  f.write(f"{pkg}\t{vmin}\t{vmax}\t{vcurr if vcurr else 0}\n")
1375
1428
 
1376
- return required_packages, vmin_list, vmax_list, vcurr_list
1429
+ return required_packages, vmin_list, vmax_list, vcurr_list
1430
+
1431
+ import base64
1432
+ def save_frame_geometry_to_param(self: QMainWindow):
1433
+ print_frame_geometry(self,f"({self.objectName()}) Saving frame geometry ***")
1434
+ fg = self.frameGeometry()
1435
+ payload = {"type": "frame", "x": fg.x(), "y": fg.y(), "w": fg.width(), "h": fg.height()}
1436
+ return base64.b64encode(json.dumps(payload).encode("utf-8")).decode("ascii")
1437
+
1438
+ def print_frame_geometry(self: QMainWindow, tag="", target = ""):
1439
+ fg = self.frameGeometry()
1440
+ g = self.geometry()
1441
+ wh = self.windowHandle()
1442
+ m = wh.frameMargins() if wh else None
1443
+ pri.Callback.yellow(tag,
1444
+ "frame", fg.x(), fg.y(), fg.width(), fg.height(),
1445
+ "client", g.x(), g.y(), g.width(), g.height(),
1446
+ "margins", (m.left(), m.top(), m.right(), m.bottom()) if m else None)
1447
+ if target:
1448
+ pri.Callback.yellow(tag,
1449
+ "target", target.x(), target.y(), target.width(), target.height())
1450
+
1451
+ def restore_frame_geometry_from_param(self: QMainWindow, geometry: str, max_iter: int = 10):
1452
+ try:
1453
+ payload = json.loads(base64.b64decode(geometry).decode("utf-8"))
1454
+ except Exception:
1455
+ return
1456
+ if payload.get("type") != "frame":
1457
+ return
1458
+
1459
+ target = QRect(payload["x"], payload["y"], payload["w"], payload["h"])
1460
+ tag=f"({self.objectName()}) Setting frame geometry | "
1461
+ print_frame_geometry(self,tag,target)
1462
+ spaces=tag[:-3]+'---'
1463
+
1464
+ def step(i=0):
1465
+ cur = self.frameGeometry()
1466
+ dx = target.x() - cur.x()
1467
+ dy = target.y() - cur.y()
1468
+ dw = target.width() - cur.width()
1469
+ dh = target.height() - cur.height()
1470
+
1471
+ if (dx, dy, dw, dh) == (0, 0, 0, 0) or i >= max_iter:
1472
+ return False
1473
+
1474
+ g = self.geometry() # client
1475
+ self.setGeometry(QRect(g.x() + dx, g.y() + dy, g.width() + dw, g.height() + dh))
1476
+ print_frame_geometry(self,spaces)
1477
+ if i>0: QTimer.singleShot(200, lambda: step(i + 1))
1478
+ return True
1479
+
1480
+ if step(0): QTimer.singleShot(200, lambda: step(1))
@@ -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(gPaIRS_QMenu_style)
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