PaIRS-UniNa 0.2.5__cp313-cp313-win_amd64.whl → 0.2.9__cp313-cp313-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.
- PaIRS_UniNa/Calibration_Tab.py +15 -0
- PaIRS_UniNa/Changes.txt +54 -2
- PaIRS_UniNa/Explorer.py +118 -19
- PaIRS_UniNa/FolderLoop.py +196 -6
- PaIRS_UniNa/Input_Tab.py +167 -55
- PaIRS_UniNa/Input_Tab_CalVi.py +15 -17
- PaIRS_UniNa/Input_Tab_tools.py +9 -10
- PaIRS_UniNa/Output_Tab.py +2 -4
- PaIRS_UniNa/PaIRS_pypacks.py +227 -56
- PaIRS_UniNa/Process_Tab.py +2 -2
- PaIRS_UniNa/Process_Tab_Disp.py +1 -1
- PaIRS_UniNa/SPIVCalHelp.py +155 -0
- PaIRS_UniNa/Saving_tools.py +7 -7
- PaIRS_UniNa/TabTools.py +7 -4
- PaIRS_UniNa/Vis_Tab.py +129 -60
- PaIRS_UniNa/Whatsnew.py +15 -3
- PaIRS_UniNa/_PaIRS_PIV.pyd +0 -0
- PaIRS_UniNa/__init__.py +4 -4
- PaIRS_UniNa/addwidgets_ps.py +28 -20
- PaIRS_UniNa/calibView.py +7 -0
- PaIRS_UniNa/gPaIRS.py +179 -34
- PaIRS_UniNa/icons/flaticon_PaIRS_download_warning.png +0 -0
- 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/pencil_bw.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/pivParFor.py +1 -1
- PaIRS_UniNa/procTools.py +51 -3
- PaIRS_UniNa/rqrdpckgs.txt +6 -5
- PaIRS_UniNa/stereoPivParFor.py +1 -1
- PaIRS_UniNa/ui_Calibration_Tab.py +90 -57
- PaIRS_UniNa/ui_gPairs.py +9 -3
- PaIRS_UniNa/whatsnew.txt +4 -4
- {pairs_unina-0.2.5.dist-info → pairs_unina-0.2.9.dist-info}/METADATA +32 -17
- {pairs_unina-0.2.5.dist-info → pairs_unina-0.2.9.dist-info}/RECORD +42 -32
- {pairs_unina-0.2.5.dist-info → pairs_unina-0.2.9.dist-info}/WHEEL +0 -0
- {pairs_unina-0.2.5.dist-info → pairs_unina-0.2.9.dist-info}/top_level.txt +0 -0
PaIRS_UniNa/PaIRS_pypacks.py
CHANGED
|
@@ -15,10 +15,12 @@ basefold_DEBUG='./'
|
|
|
15
15
|
basefold_DEBUG_VIS=''
|
|
16
16
|
#basefold='B:/dl/apairs/jetcross'
|
|
17
17
|
|
|
18
|
+
|
|
18
19
|
developerIDs={
|
|
19
20
|
'GP_Win_Office': '231128824800632', #'0x7824af430781',
|
|
20
21
|
'GP_Win_Office_New': '140626882900161', #'0x7824af430781',
|
|
21
22
|
'GP_Mac_Laptop': 'V94LRP93FV', #'0xa275dd445ab0',
|
|
23
|
+
'GP_WSL' : 'b44ec1c0e5a74ffd97bb050c39ef6cb1',
|
|
22
24
|
'TA_Win_Office': '160983906000941', #'0xccb0da8c896e'
|
|
23
25
|
'TA_Win_Office_New': '231128824801036', #??
|
|
24
26
|
}
|
|
@@ -29,9 +31,32 @@ def getCurrentID():
|
|
|
29
31
|
serial_number=None
|
|
30
32
|
try:
|
|
31
33
|
if psutil.LINUX:
|
|
34
|
+
def get_linux_serial():
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
candidates = [
|
|
37
|
+
Path("/sys/class/dmi/id/board_serial"),
|
|
38
|
+
Path("/sys/class/dmi/id/product_uuid"),
|
|
39
|
+
Path("/etc/machine-id"), Path("/var/lib/dbus/machine-id") #WSL
|
|
40
|
+
]
|
|
41
|
+
for p in candidates:
|
|
42
|
+
try:
|
|
43
|
+
if p.is_file():
|
|
44
|
+
val = p.read_text(errors="ignore").strip()
|
|
45
|
+
if val and val.lower() not in {
|
|
46
|
+
"none", "unknown", "not specified", "to be filled by o.e.m."
|
|
47
|
+
}:
|
|
48
|
+
return val
|
|
49
|
+
except Exception as e:
|
|
50
|
+
if Flag_DEBUG:
|
|
51
|
+
print(f"Error while retrieving motherboard serial number: {e}")
|
|
52
|
+
continue
|
|
53
|
+
return None
|
|
54
|
+
serial_number = get_linux_serial()
|
|
55
|
+
""""
|
|
32
56
|
# On Linux, the motherboard serial number can be obtained from the /sys/class/dmi/id/board_serial file
|
|
33
57
|
with open('/sys/class/dmi/id/board_serial', 'r') as f:
|
|
34
58
|
serial_number = f.read().strip()
|
|
59
|
+
"""
|
|
35
60
|
elif psutil.WINDOWS:
|
|
36
61
|
# On Windows, the motherboard serial number can be obtained using WMI
|
|
37
62
|
output = subprocess.check_output(["wmic", "baseboard", "get", "SerialNumber"]).decode('utf-8')
|
|
@@ -43,7 +68,8 @@ def getCurrentID():
|
|
|
43
68
|
if b'Serial Number (system)' in line:
|
|
44
69
|
serial_number = line.split(b':')[1].strip().decode('utf-8')
|
|
45
70
|
except Exception as e:
|
|
46
|
-
|
|
71
|
+
if Flag_DEBUG:
|
|
72
|
+
print(f"Error while retrieving motherboard serial number: {e}")
|
|
47
73
|
return serial_number
|
|
48
74
|
|
|
49
75
|
currentID=getCurrentID()
|
|
@@ -58,6 +84,16 @@ if currentID in (developerIDs['GP_Win_Office'],developerIDs['GP_Win_Office_New']
|
|
|
58
84
|
'C:/desk/PIV_Img/_data/Calibration_data/cylinder/',
|
|
59
85
|
]
|
|
60
86
|
basefold_DEBUG_VIS='C:/desk/PIV_Img/_data/PIV_data/real_case/'
|
|
87
|
+
elif currentID==developerIDs['GP_WSL']:
|
|
88
|
+
basefold_DEBUG='/mnt/c/desk/PIV_Img/_data/PIV_data/virtual_case/'
|
|
89
|
+
basefold_DEBUGOptions=[
|
|
90
|
+
'/mnt/c/desk/PIV_Img/img1/',
|
|
91
|
+
'/mnt/c/desk/PIV_Img/_data/PIV_data/virtual_case/',
|
|
92
|
+
'/mnt/c/desk/PIV_Img/_data/PIV_data/real_case/',
|
|
93
|
+
'/mnt/c/desk/PIV_Img/_data/Calibration_data/pinhole/',
|
|
94
|
+
'/mnt/c/desk/PIV_Img/_data/Calibration_data/cylinder/',
|
|
95
|
+
]
|
|
96
|
+
basefold_DEBUG_VIS='/mnt/c/desk/PIV_Img/_data/PIV_data/real_case/'
|
|
61
97
|
elif currentID==developerIDs['GP_Mac_Laptop']: #gerardo mac
|
|
62
98
|
basefold_DEBUG='/Users/gerardo/Desktop/PIV_Img/swirler_png/' #'/Users/gerardo/Desktop/PIV_Img/img1/'
|
|
63
99
|
basefold_DEBUGOptions=[
|
|
@@ -72,12 +108,12 @@ elif currentID==developerIDs['GP_Mac_Laptop']: #gerardo mac
|
|
|
72
108
|
basefold_DEBUG_VIS='/Users/gerardo/Desktop/PaIRS_examples/PIV_data/real_case/'
|
|
73
109
|
basefold_DEBUG_VIS='/Users/gerardo/Desktop/PIV_Img/img1/'
|
|
74
110
|
elif currentID in (developerIDs['TA_Win_Office'],developerIDs['TA_Win_Office_New']): #TA windows
|
|
75
|
-
basefold_DEBUG='C:\desk\Attuali\PythonLibC\PIV\img'
|
|
111
|
+
basefold_DEBUG=r'C:\desk\Attuali\PythonLibC\PIV\img'
|
|
76
112
|
basefold_DEBUGOptions=[
|
|
77
113
|
'C:/desk/PIV_Img/img1/',
|
|
78
114
|
'C:/desk/PIV_Img/swirler_png/',
|
|
79
115
|
'../../img/calib/',
|
|
80
|
-
'C:\desk\Attuali\PythonLibC\PIV\img',
|
|
116
|
+
r'C:\desk\Attuali\PythonLibC\PIV\img',
|
|
81
117
|
]
|
|
82
118
|
basefold_DEBUG_VIS=''
|
|
83
119
|
else:
|
|
@@ -106,6 +142,7 @@ f_empty_width=250 #blank space in scrollable area within the main window
|
|
|
106
142
|
time_ScrollBar=250 #time of animation of scroll area
|
|
107
143
|
time_callback2_async=0 #time to test async callbacks
|
|
108
144
|
time_showSplashOnTop=250
|
|
145
|
+
pathCompleterLength=10
|
|
109
146
|
|
|
110
147
|
fileChanges='Changes.txt'
|
|
111
148
|
fileWhatsNew=['whatsnew.txt','whatwasnew.txt']
|
|
@@ -149,6 +186,37 @@ import sys
|
|
|
149
186
|
import concurrent.futures
|
|
150
187
|
import asyncio
|
|
151
188
|
|
|
189
|
+
_old_init_QAction = QAction.__init__
|
|
190
|
+
def _new_init_QAction(self, *args, **kwargs):
|
|
191
|
+
_old_init_QAction(self, *args, **kwargs)
|
|
192
|
+
try:
|
|
193
|
+
self.setIconVisibleInMenu(True)
|
|
194
|
+
except Exception:
|
|
195
|
+
pass
|
|
196
|
+
QAction.__init__ = _new_init_QAction
|
|
197
|
+
_old_init_QMenu = QMenu.__init__
|
|
198
|
+
def _new_init_QMenu(self, *args, **kwargs):
|
|
199
|
+
_old_init_QMenu(self, *args, **kwargs)
|
|
200
|
+
try:
|
|
201
|
+
self.menuAction().setIconVisibleInMenu(True)
|
|
202
|
+
except Exception:
|
|
203
|
+
pass
|
|
204
|
+
QMenu.__init__ = _new_init_QMenu
|
|
205
|
+
|
|
206
|
+
# --- Patch dei metodi QMenu che CREANO/AGGIUNGONO azioni (copre gli overload C++) ---
|
|
207
|
+
_old_addAction = QMenu.addAction
|
|
208
|
+
def _new_addAction(self, *args, **kwargs):
|
|
209
|
+
act:QAction = _old_addAction(self, *args, **kwargs) # può essere creato lato C++
|
|
210
|
+
try:
|
|
211
|
+
if isinstance(act, QAction):
|
|
212
|
+
act.setIconVisibleInMenu(True)
|
|
213
|
+
except Exception:
|
|
214
|
+
pass
|
|
215
|
+
return act
|
|
216
|
+
QMenu.addAction = _new_addAction
|
|
217
|
+
|
|
218
|
+
Flag_ISEXE=getattr(sys, 'frozen', False) #made by pyInstaller
|
|
219
|
+
EXEurl='https://www.pairs.unina.it/#download'
|
|
152
220
|
|
|
153
221
|
class ColorPrint:
|
|
154
222
|
def __init__(self,flagTime=False,prio=PrintTAPriority.medium,faceStd=PrintTA.faceStd,flagFullDebug=False):
|
|
@@ -184,6 +252,7 @@ class GPaIRSPrint:
|
|
|
184
252
|
self.Info=ColorPrint(prio=PrintTAPriority.medium)
|
|
185
253
|
self.Time=ColorPrint(prio=PrintTAPriority.medium if FlagPrintTime else PrintTAPriority.veryLow,flagTime=True,faceStd=PrintTA.faceUnderline)
|
|
186
254
|
self.Error=ColorPrint(prio=PrintTAPriority.medium,faceStd=PrintTA.faceBold)
|
|
255
|
+
self.IOError=ColorPrint(prio=PrintTAPriority.veryLow,faceStd=PrintTA.faceBold)
|
|
187
256
|
self.Process=ColorPrint(prio=PrintTAPriority.veryLow)
|
|
188
257
|
self.Callback=ColorPrint(prio=PrintTAPriority.veryLow)
|
|
189
258
|
self.TABparDiff=ColorPrint(prio=PrintTAPriority.veryLow)
|
|
@@ -237,7 +306,6 @@ if __package__ or "." in __name__:
|
|
|
237
306
|
foldPaIRS = foldPaIRS.replace('\\', '/')
|
|
238
307
|
else:
|
|
239
308
|
foldPaIRS='./'
|
|
240
|
-
|
|
241
309
|
class ProcessTypes:
|
|
242
310
|
null=None
|
|
243
311
|
min=0
|
|
@@ -247,7 +315,7 @@ class ProcessTypes:
|
|
|
247
315
|
cal=10
|
|
248
316
|
|
|
249
317
|
singleCamera=[piv]
|
|
250
|
-
threeCameras=[tpiv]
|
|
318
|
+
threeCameras=[min,tpiv]
|
|
251
319
|
|
|
252
320
|
class StepTypes:
|
|
253
321
|
null=None
|
|
@@ -291,15 +359,24 @@ class outExt:
|
|
|
291
359
|
cfg_calvi='.calvi_cfg'
|
|
292
360
|
pla='.pairs_pla'
|
|
293
361
|
|
|
294
|
-
|
|
295
362
|
|
|
296
363
|
lastcfgname='lastWorkSpace'+outExt.wksp
|
|
297
|
-
|
|
298
364
|
fileChanges=foldPaIRS+'Changes.txt'
|
|
299
|
-
fileWhatsNew=[foldPaIRS+f for f in fileWhatsNew]
|
|
300
365
|
icons_path=foldPaIRS+icons_path
|
|
301
|
-
|
|
302
|
-
|
|
366
|
+
|
|
367
|
+
if not Flag_ISEXE:
|
|
368
|
+
fileWhatsNew=[foldPaIRS+f for f in fileWhatsNew]
|
|
369
|
+
|
|
370
|
+
lastcfgname=foldPaIRS+lastcfgname
|
|
371
|
+
pro_path=foldPaIRS+"pro/"
|
|
372
|
+
else:
|
|
373
|
+
from pathlib import Path
|
|
374
|
+
exe_dir = str(Path(sys.argv[0]).resolve().parent)+'/'
|
|
375
|
+
fileWhatsNew=[foldPaIRS+fileWhatsNew[0],exe_dir+fileWhatsNew[1]]
|
|
376
|
+
lastcfgname = exe_dir + lastcfgname
|
|
377
|
+
pro_path = exe_dir + "pro/"
|
|
378
|
+
|
|
379
|
+
|
|
303
380
|
if not os.path.exists(pro_path):
|
|
304
381
|
try:
|
|
305
382
|
os.mkdir(pro_path)
|
|
@@ -558,6 +635,13 @@ def myStandardRoot(root):
|
|
|
558
635
|
currroot = re.sub('/+', '/', currroot) # Reduce consecutive slashes to a single slash
|
|
559
636
|
return currroot
|
|
560
637
|
|
|
638
|
+
def relativizePath(currpath:str):
|
|
639
|
+
return currpath
|
|
640
|
+
directory_path = myStandardPath(os.getcwd())
|
|
641
|
+
if directory_path in currpath:
|
|
642
|
+
currpath=currpath.replace(directory_path,'./')
|
|
643
|
+
return currpath
|
|
644
|
+
|
|
561
645
|
def findFiles_sorted(pattern):
|
|
562
646
|
list_files=glob.glob(pattern)
|
|
563
647
|
files=sorted([re.sub(r'\\+',r'/',f) for f in list_files],key=str.lower)
|
|
@@ -682,6 +766,43 @@ def identifierName(typeObject:str='proc'):
|
|
|
682
766
|
name=version_user_info+'_'+typeObject+'_'+id
|
|
683
767
|
return name, username, __version__
|
|
684
768
|
|
|
769
|
+
def fileIdenitifierCheck(id: str, filename: str) -> bool:
|
|
770
|
+
"""
|
|
771
|
+
Extract the date/time from the identifier 'name' and check whether 'filename'
|
|
772
|
+
has been modified after that timestamp.
|
|
773
|
+
Returns True if file is newer, False otherwise.
|
|
774
|
+
"""
|
|
775
|
+
|
|
776
|
+
# --- Extract timestamp from name ---
|
|
777
|
+
# Expected pattern: ..._<date>-<time>_...
|
|
778
|
+
# Example: 'PaIRS-v0.2.8_Linux-user_2025/11/07-12:45:31_proc_...'
|
|
779
|
+
try:
|
|
780
|
+
parts = id.split('_')
|
|
781
|
+
date_str, time_str = parts[2].split('-')[0], parts[2].split('-')[1]
|
|
782
|
+
except Exception:
|
|
783
|
+
pri.Error.red("Identifier format not recognized: cannot extract date and time.")
|
|
784
|
+
return False
|
|
785
|
+
|
|
786
|
+
# Convert date/time strings into QDateTime
|
|
787
|
+
qdate = QDate.fromString(date_str, 'yyyy/MM/dd')
|
|
788
|
+
qtime = QTime.fromString(time_str, 'HH:mm:ss')
|
|
789
|
+
qdt_identifier = QDateTime(qdate, qtime)
|
|
790
|
+
qdt_identifier = qdt_identifier.addSecs(-1) #to be safe
|
|
791
|
+
|
|
792
|
+
if not qdt_identifier.isValid():
|
|
793
|
+
pri.Error.red("Parsed QDateTime is not valid. Check identifier format.")
|
|
794
|
+
return False
|
|
795
|
+
|
|
796
|
+
# --- File timestamp ---
|
|
797
|
+
if not os.path.exists(filename):
|
|
798
|
+
return False
|
|
799
|
+
|
|
800
|
+
file_mtime = os.path.getmtime(filename)
|
|
801
|
+
qdt_file = QDateTime.fromSecsSinceEpoch(int(file_mtime))
|
|
802
|
+
|
|
803
|
+
# True if file was modified after the timestamp stored in name
|
|
804
|
+
return qdt_file > qdt_identifier
|
|
805
|
+
|
|
685
806
|
PlainTextConverter=QtGui.QTextDocument()
|
|
686
807
|
def toPlainText(text):
|
|
687
808
|
PlainTextConverter.setHtml(text) #for safety
|
|
@@ -782,12 +903,16 @@ def runPaIRS(self,command='',flagQuestion=True):
|
|
|
782
903
|
def run(self):
|
|
783
904
|
try:
|
|
784
905
|
import subprocess
|
|
785
|
-
if
|
|
786
|
-
pri.Info.white(sys.executable+'
|
|
787
|
-
subprocess.call(sys.executable+'
|
|
906
|
+
if Flag_ISEXE:
|
|
907
|
+
pri.Info.white(sys.executable+' '+command)
|
|
908
|
+
subprocess.call(sys.executable+' '+command,shell=True)
|
|
788
909
|
else:
|
|
789
|
-
|
|
790
|
-
|
|
910
|
+
if Flag: #launched from package
|
|
911
|
+
pri.Info.white(sys.executable+' -m PaIRS_UniNa '+command)
|
|
912
|
+
subprocess.call(sys.executable+' -m PaIRS_UniNa '+command,shell=True)
|
|
913
|
+
else:
|
|
914
|
+
pri.Info.white(sys.executable+' -c '+'"'+f"import os; os.chdir('{os.getcwd()}'); {pyCommands[command]}"+'"')
|
|
915
|
+
subprocess.call(sys.executable+' -c '+'"'+f"import os; os.chdir('{os.getcwd()}'); {pyCommands[command]}"+'"',shell=True)
|
|
791
916
|
self.isRunning=False
|
|
792
917
|
except Exception as inst:
|
|
793
918
|
pri.Error.red(inst)
|
|
@@ -816,12 +941,12 @@ def showSplash(filename=''+ icons_path +'logo_PaIRS_completo.png'):
|
|
|
816
941
|
splash.show()
|
|
817
942
|
return splash
|
|
818
943
|
|
|
819
|
-
def checkLatestVersion(self,version,app:QApplication=None,splash:QLabel=None):
|
|
944
|
+
def checkLatestVersion(self,version,app:QApplication=None,splash:QLabel=None,flagWarning=1):
|
|
820
945
|
flagStopAndDownload=False
|
|
821
946
|
var=self.TABpar
|
|
822
947
|
#var.FlagOutDated=0 if currentVersion==var.latestVersion else var.FlagOutDated
|
|
823
948
|
if abs(var.FlagOutDated)==1:
|
|
824
|
-
warningLatestVersion(self,app,flagExit=0,flagWarning=
|
|
949
|
+
warningLatestVersion(self,app,flagExit=0,flagWarning=flagWarning,FlagStayOnTop=True)
|
|
825
950
|
var.FlagOutDated=2 if var.FlagOutDated==1 else -2
|
|
826
951
|
"""
|
|
827
952
|
flagStopAndDownload=questionDialog(self,f'A new version of the PaIRS_UniNa package is available. Do you want to download it before starting the current istance of {self.name}?')
|
|
@@ -852,7 +977,7 @@ def checkLatestVersion(self,version,app:QApplication=None,splash:QLabel=None):
|
|
|
852
977
|
var.FlagOutDated=-2 if var.FlagOutDated==-2 else -1
|
|
853
978
|
elif flagOutDated==-1000:
|
|
854
979
|
sOut=f'Error from pip: it was not possible to check for a new version of the {packageName} package!'
|
|
855
|
-
var.FlagOutDated
|
|
980
|
+
var.FlagOutDated=-1000
|
|
856
981
|
else:
|
|
857
982
|
sOut=f'{packageName} The current version ({currentVersion}) of {packageName} is up-to-date! Enjoy it!'
|
|
858
983
|
var.FlagOutDated=0
|
|
@@ -872,13 +997,18 @@ def warningLatestVersion(self,app,flagExit=0,flagWarning=0,time_milliseconds=0,F
|
|
|
872
997
|
py=myStandardRoot(sys.executable).split('/')[-1].split('.')[0]
|
|
873
998
|
command=f'{py} -m pip install --upgrade PaIRS_UniNa'
|
|
874
999
|
if self.TABpar.FlagOutDated>0:
|
|
875
|
-
|
|
1000
|
+
if Flag_ISEXE:
|
|
1001
|
+
Message=f'A new version of the PaIRS_UniNa package is available (current: {self.TABpar.currentVersion}, latest: {self.TABpar.latestVersion}).\nPlease, download it from the following link:\n{EXEurl}'
|
|
1002
|
+
else:
|
|
1003
|
+
Message=f'A new version of the PaIRS_UniNa package is available (current: {self.TABpar.currentVersion}, latest: {self.TABpar.latestVersion}).\nPlease, {exitSuggestion}install it with the following command:\n{command}'
|
|
1004
|
+
elif self.TABpar.FlagOutDated==-1000:
|
|
1005
|
+
Message = ("Unable to check for the latest official release of PaIRS_UniNa. Please check the PyPI page manually for updates:\n""https://pypi.org/project/PaIRS-UniNa/")
|
|
876
1006
|
else:
|
|
877
1007
|
Message=f'The version of the current instance of PaIRS_UniNa ({self.TABpar.currentVersion}) is newer than the latest official releas ({self.TABpar.latestVersion})!\nYou should contact Tommaso and Gerardo if you are a developer and some relevant change is made by yourself!\nIf you are a user, enjoy this beta version and please report any issue!'
|
|
878
1008
|
if flagExit:
|
|
879
1009
|
print(f"\n{'*'*100}\n"+Message+f"\n{'*'*100}\n")
|
|
880
1010
|
if flagWarning:
|
|
881
|
-
warningDialog(self,Message,time_milliseconds=time_milliseconds,flagScreenCenter=True,pixmap=''+ icons_path +'flaticon_PaIRS_download.png' if self.TABpar.FlagOutDated>0 else ''+ icons_path +'flaticon_PaIRS_beta.png',FlagStayOnTop=FlagStayOnTop,addButton={"See what's new!": lambda: QDesktopServices.openUrl(QUrl("https://pypi.org/project/PaIRS-UniNa/"))} if self.TABpar.FlagOutDated>0 else {})
|
|
1011
|
+
warningDialog(self,Message,time_milliseconds=time_milliseconds,flagScreenCenter=True,pixmap=''+ icons_path +'flaticon_PaIRS_download.png' if self.TABpar.FlagOutDated>0 else ''+ icons_path +'flaticon_PaIRS_download_warning.png' if self.TABpar.FlagOutDated==-1000 else ''+ icons_path +'flaticon_PaIRS_beta.png',FlagStayOnTop=FlagStayOnTop,addButton={"Go to the download page!": lambda: QDesktopServices.openUrl(QUrl(EXEurl))} if Flag_ISEXE else {"See what's new!": lambda: QDesktopServices.openUrl(QUrl("https://pypi.org/project/PaIRS-UniNa/"))} if self.TABpar.FlagOutDated>0 else {})
|
|
882
1012
|
|
|
883
1013
|
def downloadLatestVersion(self,app):
|
|
884
1014
|
try:
|
|
@@ -902,6 +1032,7 @@ def downloadLatestVersion(self,app):
|
|
|
902
1032
|
|
|
903
1033
|
def button_download_PaIRS_action(self,app):
|
|
904
1034
|
warningLatestVersion(self,app,flagExit=0,flagWarning=1)
|
|
1035
|
+
checkLatestVersion(self,__version__,self.app,splash=None,flagWarning=0)
|
|
905
1036
|
return
|
|
906
1037
|
flagStopAndDownload=questionDialog(self,f'A new version of the PaIRS_UniNa package is available. Do you want to close the current instance of {self.name} and download it?')
|
|
907
1038
|
if not flagStopAndDownload: return
|
|
@@ -913,6 +1044,20 @@ def button_download_PaIRS_action(self,app):
|
|
|
913
1044
|
else: command=''
|
|
914
1045
|
subprocess.call(sys.executable+' -m PaIRS_UniNa '+command,shell=True)
|
|
915
1046
|
#runPaIRS(self,flagQuestion=False)
|
|
1047
|
+
|
|
1048
|
+
import urllib.request, json, ssl
|
|
1049
|
+
|
|
1050
|
+
def get_package_version_urllib(package_name):
|
|
1051
|
+
"""Get package version using only standard library"""
|
|
1052
|
+
try:
|
|
1053
|
+
import certifi
|
|
1054
|
+
url = f"https://pypi.org/pypi/{package_name}/json"
|
|
1055
|
+
context = ssl.create_default_context(cafile=certifi.where())
|
|
1056
|
+
with urllib.request.urlopen(url, context=context, timeout=10) as response:
|
|
1057
|
+
data = json.loads(response.read().decode())
|
|
1058
|
+
return True, data['info']['version']
|
|
1059
|
+
except Exception as e:
|
|
1060
|
+
return False, f"Error: {e}"
|
|
916
1061
|
|
|
917
1062
|
def checkOutDated(packageName:str,printOutDated):
|
|
918
1063
|
'''
|
|
@@ -944,48 +1089,60 @@ def checkOutDated(packageName:str,printOutDated):
|
|
|
944
1089
|
currentVersion='none'
|
|
945
1090
|
latestVersion=''
|
|
946
1091
|
try:
|
|
947
|
-
if
|
|
1092
|
+
if Flag_ISEXE:
|
|
948
1093
|
currentVersion=__version__+'.'+__subversion__ if int(__subversion__) else __version__
|
|
949
1094
|
else:
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1095
|
+
if Flag_DEBUG:
|
|
1096
|
+
currentVersion=__version__+'.'+__subversion__ if int(__subversion__) else __version__
|
|
1097
|
+
else:
|
|
1098
|
+
command=[sys.executable, '-m', 'pip', 'show', packageName]
|
|
1099
|
+
reqs = subprocess.run(command,capture_output=True)
|
|
1100
|
+
if reqs.returncode:
|
|
1101
|
+
pri.Error.red('Error in command:\n'+' '.join(command)+'\n'+reqs.stderr.decode("utf-8") )
|
|
1102
|
+
return flagOutDated,currentVersion,latestVersion
|
|
1103
|
+
printing=reqs.stdout.decode("utf-8")
|
|
1104
|
+
pri.Info.cyan( printing )
|
|
1105
|
+
r=reqs.stdout.decode("utf-8").replace('\r','').split('\n')
|
|
1106
|
+
currentVersion='none'
|
|
1107
|
+
for s in r:
|
|
1108
|
+
if 'Version: ' in s:
|
|
1109
|
+
currentVersion=s.replace('Version: ','')
|
|
1110
|
+
break
|
|
963
1111
|
if currentVersion!=__version__:
|
|
964
1112
|
message=f'Greetings, developer!\nThe version of the current instance of PaIRS_UniNa ({__version__}) is different from that installed in the present Python environment ({currentVersion})!\nYou should contact Tommaso and Gerardo if some relevant change is made by yourself!'
|
|
965
1113
|
pri.Info.yellow(f'{"-"*50}\n{message}\n{"-"*50}\n')
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
if not reqs.returncode:
|
|
969
|
-
printing=reqs.stdout.decode("utf-8")
|
|
970
|
-
pri.Info.cyan( printing )
|
|
971
|
-
r=reqs.stdout.decode("utf-8").replace('\r','').split('\n')
|
|
972
|
-
#currentVersion=r[0].replace(packageName,'').replace('(','').replace(')','').replace(' ','')
|
|
973
|
-
latestVersion=r[1].replace('Available versions: ','').split(',')[0]
|
|
1114
|
+
if Flag_ISEXE:
|
|
1115
|
+
_, latestVersion = get_package_version_urllib("PaIRS_UniNa")
|
|
974
1116
|
else:
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
command=[sys.executable, '-m', 'pip', 'list','--outdated']
|
|
1117
|
+
command=[sys.executable, '-m', 'pip', 'index', 'versions', packageName]
|
|
978
1118
|
reqs = subprocess.run(command,capture_output=True)
|
|
979
|
-
if reqs.returncode:
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
latestVersion=outDated[i+2]
|
|
1119
|
+
if not reqs.returncode:
|
|
1120
|
+
printing=reqs.stdout.decode("utf-8")
|
|
1121
|
+
pri.Info.cyan( printing )
|
|
1122
|
+
r=reqs.stdout.decode("utf-8").replace('\r','').split('\n')
|
|
1123
|
+
#currentVersion=r[0].replace(packageName,'').replace('(','').replace(')','').replace(' ','')
|
|
1124
|
+
latestVersion=r[1].replace('Available versions: ','').split(',')[0]
|
|
986
1125
|
else:
|
|
987
|
-
latestVersion=
|
|
988
|
-
|
|
1126
|
+
flagOk,latestVersion=get_package_version_urllib(packageName)
|
|
1127
|
+
if not flagOk:
|
|
1128
|
+
pri.Error.red('Error in command:\n'+' '.join(command)+'\n'+reqs.stderr.decode("utf-8") )
|
|
1129
|
+
pri.Error.red(latestVersion)
|
|
1130
|
+
latestVersion='none'
|
|
1131
|
+
|
|
1132
|
+
"""
|
|
1133
|
+
command=[sys.executable, '-m', 'pip', 'list','--outdated']
|
|
1134
|
+
reqs = subprocess.run(command,capture_output=True)
|
|
1135
|
+
if reqs.returncode:
|
|
1136
|
+
pri.Error.red('Error in command:\n'+' '.join(command)+'\n'+reqs.stderr.decode("utf-8") )
|
|
1137
|
+
return flagOutDated,currentVersion,latestVersion
|
|
1138
|
+
outDated = [r.decode().split('==')[0] for r in reqs.stdout.split()]
|
|
1139
|
+
if packageName in outDated:
|
|
1140
|
+
i=outDated.index(packageName)
|
|
1141
|
+
latestVersion=outDated[i+2]
|
|
1142
|
+
else:
|
|
1143
|
+
latestVersion=currentVersion
|
|
1144
|
+
pri.Info.cyan(f'{packageName} ({currentVersion}). Latest version available: {latestVersion}')
|
|
1145
|
+
"""
|
|
989
1146
|
#flagOutDated=1 if currentVersion!=latestVersion else 0
|
|
990
1147
|
cV_parts=[int(c) for c in currentVersion.split('.')]
|
|
991
1148
|
lV_parts=[int(c) for c in latestVersion.split('.')]
|
|
@@ -1165,6 +1322,19 @@ def resetRequiredPackagesFile(filename=rqrdpckgs_filename):
|
|
|
1165
1322
|
else:
|
|
1166
1323
|
pri.Error.red(f"resetRequiredPackagesFile: Skipping malformed line: {line}")
|
|
1167
1324
|
|
|
1325
|
+
def to_triplet(v: Version) -> tuple[int,int,int]:
|
|
1326
|
+
r = v.release or (0,)
|
|
1327
|
+
return (r[0], r[1] if len(r) > 1 else 0, r[2] if len(r) > 2 else 0)
|
|
1328
|
+
|
|
1329
|
+
def le_ver(a: Version, b: Version) -> bool:
|
|
1330
|
+
a1,a2,a3 = to_triplet(a)
|
|
1331
|
+
b1,b2,b3 = to_triplet(b)
|
|
1332
|
+
if a1 > b1: return False
|
|
1333
|
+
if a1 < b1: return True
|
|
1334
|
+
if a2 > b2: return False
|
|
1335
|
+
if a2 < b2: return True
|
|
1336
|
+
return a3 <= b3
|
|
1337
|
+
|
|
1168
1338
|
def checkRequiredPackages(self, filename=rqrdpckgs_filename, FlagDisplay=False, FlagForcePrint=False):
|
|
1169
1339
|
required_packages = []
|
|
1170
1340
|
vmin_list = []
|
|
@@ -1199,7 +1369,7 @@ def checkRequiredPackages(self, filename=rqrdpckgs_filename, FlagDisplay=False,
|
|
|
1199
1369
|
Flag = True
|
|
1200
1370
|
|
|
1201
1371
|
# Check if within [vmin, vmax]
|
|
1202
|
-
if not (vmin_list[i]
|
|
1372
|
+
if not (le_ver(vmin_list[i],installed_version) and le_ver(installed_version,vmax_list[i])):
|
|
1203
1373
|
"""
|
|
1204
1374
|
warnings.append(
|
|
1205
1375
|
f"- {pkg}: installed = {installed_version}, target range = [{vmin_list[i]}, {vmax_list[i]}]"
|
|
@@ -1216,7 +1386,8 @@ def checkRequiredPackages(self, filename=rqrdpckgs_filename, FlagDisplay=False,
|
|
|
1216
1386
|
"Some installed packages have a version outside the target range used to develop "
|
|
1217
1387
|
"the current release of the PaIRS_UniNa package.\n\n"
|
|
1218
1388
|
"This may lead to compatibility issues. If you experience unexpected behavior, "
|
|
1219
|
-
"it is recommended to either reinstall the last tested compatible versions
|
|
1389
|
+
"it is recommended to either reinstall the last tested compatible versions or "
|
|
1390
|
+
f"download the executable at {EXEurl}."
|
|
1220
1391
|
f" If any issue occurs, please contact the authors at {__mail__}.\n\n"
|
|
1221
1392
|
#"or use the standalone executable available at:\n"
|
|
1222
1393
|
#"https://pairs.unina.it/#download\n\n"
|
PaIRS_UniNa/Process_Tab.py
CHANGED
|
@@ -724,7 +724,7 @@ class Process_Tab(gPaIRS_Tab):
|
|
|
724
724
|
#******************** Actions
|
|
725
725
|
def edit_Wind_vectors(self,wedit:QLineEdit,wlab:QLabel):
|
|
726
726
|
text=wedit.text()
|
|
727
|
-
split_text=re.split('(\d+)', text)[1:-1:2]
|
|
727
|
+
split_text=re.split(r'(\d+)', text)[1:-1:2]
|
|
728
728
|
vect=[int(i) for i in split_text]
|
|
729
729
|
FlagEmpty=len(vect)==0
|
|
730
730
|
if FlagEmpty: FlagError=True
|
|
@@ -1291,7 +1291,7 @@ class Process_Tab(gPaIRS_Tab):
|
|
|
1291
1291
|
|
|
1292
1292
|
def line_edit_IW_action(self):
|
|
1293
1293
|
text=self.ui.line_edit_IW.text()
|
|
1294
|
-
split_text=re.split('(\d+)', text)[1:-1:2]
|
|
1294
|
+
split_text=re.split(r'(\d+)', text)[1:-1:2]
|
|
1295
1295
|
vect=[int(split_text[i]) for i in (0,2,1,3)]
|
|
1296
1296
|
if len(vect)==4:
|
|
1297
1297
|
k=self.PROpar.row
|
PaIRS_UniNa/Process_Tab_Disp.py
CHANGED
|
@@ -133,7 +133,7 @@ class Process_Tab_Disp(gPaIRS_Tab):
|
|
|
133
133
|
#******************** Actions
|
|
134
134
|
def line_edit_IW_action(self):
|
|
135
135
|
text=self.ui.line_edit_IW.text()
|
|
136
|
-
split_text=re.split('(\d+)', text)[1:-1:2]
|
|
136
|
+
split_text=re.split(r'(\d+)', text)[1:-1:2]
|
|
137
137
|
split_num=[int(t) for t in split_text]
|
|
138
138
|
if len(split_num)<4: split_num+=[split_num[-1]]*(4-len(split_num))
|
|
139
139
|
vect=[int(split_num[i]) for i in (0,2,1,3)]
|
|
@@ -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
|
+
" • the <b>calibration plate defines "
|
|
24
|
+
"the x–y plane</b> of the calibration coordinate system;<br>"
|
|
25
|
+
|
|
26
|
+
" • 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
|
+
" • 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
|
+
" • 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())
|