PaIRS-UniNa 0.1.13__cp310-cp310-win_amd64.whl → 0.2.8__cp310-cp310-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 (292) hide show
  1. PaIRS_UniNa/Calibration_Tab.py +331 -0
  2. PaIRS_UniNa/Changes.txt +134 -2
  3. PaIRS_UniNa/Custom_Top.py +299 -299
  4. PaIRS_UniNa/Explorer.py +3136 -0
  5. PaIRS_UniNa/FolderLoop.py +562 -0
  6. PaIRS_UniNa/Input_Tab.py +831 -0
  7. PaIRS_UniNa/Input_Tab_CalVi.py +786 -0
  8. PaIRS_UniNa/Input_Tab_tools.py +3025 -0
  9. PaIRS_UniNa/Log_Tab.py +57 -14
  10. PaIRS_UniNa/Output_Tab.py +922 -0
  11. PaIRS_UniNa/PaIRS_PIV.py +111 -18
  12. PaIRS_UniNa/PaIRS_pypacks.py +698 -140
  13. PaIRS_UniNa/Process_Tab.py +1221 -1402
  14. PaIRS_UniNa/Process_Tab_CalVi.py +137 -262
  15. PaIRS_UniNa/Process_Tab_Disp.py +163 -0
  16. PaIRS_UniNa/Process_Tab_Min.py +120 -0
  17. PaIRS_UniNa/Saving_tools.py +296 -0
  18. PaIRS_UniNa/TabTools.py +863 -543
  19. PaIRS_UniNa/Vis_Tab.py +1898 -1315
  20. PaIRS_UniNa/Vis_Tab_CalVi.py +484 -356
  21. PaIRS_UniNa/Whatsnew.py +59 -10
  22. PaIRS_UniNa/_PaIRS_PIV.pyd +0 -0
  23. PaIRS_UniNa/__init__.py +4 -3
  24. PaIRS_UniNa/addwidgets_ps.py +326 -56
  25. PaIRS_UniNa/calib.py +19 -12
  26. PaIRS_UniNa/calibView.py +48 -25
  27. PaIRS_UniNa/gPaIRS.py +3902 -3461
  28. PaIRS_UniNa/gPalette.py +189 -170
  29. PaIRS_UniNa/icons/align_all.png +0 -0
  30. PaIRS_UniNa/icons/announcement.png +0 -0
  31. PaIRS_UniNa/icons/automatic_levels_off.png +0 -0
  32. PaIRS_UniNa/icons/automatic_levels_on.png +0 -0
  33. PaIRS_UniNa/icons/automatic_off.png +0 -0
  34. PaIRS_UniNa/icons/automatic_on.png +0 -0
  35. PaIRS_UniNa/icons/automatic_size_off.png +0 -0
  36. PaIRS_UniNa/icons/automatic_size_on.png +0 -0
  37. PaIRS_UniNa/icons/bin_off.png +0 -0
  38. PaIRS_UniNa/icons/bin_on.png +0 -0
  39. PaIRS_UniNa/icons/brush_cursor.png +0 -0
  40. PaIRS_UniNa/icons/bugfix.png +0 -0
  41. PaIRS_UniNa/icons/cal_proc.png +0 -0
  42. PaIRS_UniNa/icons/cal_proc_off.png +0 -0
  43. PaIRS_UniNa/icons/cal_step.png +0 -0
  44. PaIRS_UniNa/icons/cal_step_off.png +0 -0
  45. PaIRS_UniNa/icons/calibration_logo.png +0 -0
  46. PaIRS_UniNa/icons/change_folder.png +0 -0
  47. PaIRS_UniNa/icons/change_folder_off.png +0 -0
  48. PaIRS_UniNa/icons/close_all.png +0 -0
  49. PaIRS_UniNa/icons/close_workspace.png +0 -0
  50. PaIRS_UniNa/icons/colormap.png +0 -0
  51. PaIRS_UniNa/icons/colormaps/Accent.png +0 -0
  52. PaIRS_UniNa/icons/colormaps/BrBG.png +0 -0
  53. PaIRS_UniNa/icons/colormaps/Dark2.png +0 -0
  54. PaIRS_UniNa/icons/colormaps/PRGn.png +0 -0
  55. PaIRS_UniNa/icons/colormaps/Paired.png +0 -0
  56. PaIRS_UniNa/icons/colormaps/Pastel1.png +0 -0
  57. PaIRS_UniNa/icons/colormaps/Pastel2.png +0 -0
  58. PaIRS_UniNa/icons/colormaps/PiYG.png +0 -0
  59. PaIRS_UniNa/icons/colormaps/PuOr.png +0 -0
  60. PaIRS_UniNa/icons/colormaps/RdBu.png +0 -0
  61. PaIRS_UniNa/icons/colormaps/RdGy.png +0 -0
  62. PaIRS_UniNa/icons/colormaps/RdYlBu.png +0 -0
  63. PaIRS_UniNa/icons/colormaps/RdYlGn.png +0 -0
  64. PaIRS_UniNa/icons/colormaps/Set1.png +0 -0
  65. PaIRS_UniNa/icons/colormaps/Set2.png +0 -0
  66. PaIRS_UniNa/icons/colormaps/Set3.png +0 -0
  67. PaIRS_UniNa/icons/colormaps/Spectral.png +0 -0
  68. PaIRS_UniNa/icons/colormaps/Wistia.png +0 -0
  69. PaIRS_UniNa/icons/colormaps/afmhot.png +0 -0
  70. PaIRS_UniNa/icons/colormaps/autumn.png +0 -0
  71. PaIRS_UniNa/icons/colormaps/binary.png +0 -0
  72. PaIRS_UniNa/icons/colormaps/blackVector.png +0 -0
  73. PaIRS_UniNa/icons/colormaps/blueVector.png +0 -0
  74. PaIRS_UniNa/icons/colormaps/bone.png +0 -0
  75. PaIRS_UniNa/icons/colormaps/brg.png +0 -0
  76. PaIRS_UniNa/icons/colormaps/bwr.png +0 -0
  77. PaIRS_UniNa/icons/colormaps/cividis.png +0 -0
  78. PaIRS_UniNa/icons/colormaps/cool.png +0 -0
  79. PaIRS_UniNa/icons/colormaps/coolwarm.png +0 -0
  80. PaIRS_UniNa/icons/colormaps/copper.png +0 -0
  81. PaIRS_UniNa/icons/colormaps/cubehelix.png +0 -0
  82. PaIRS_UniNa/icons/colormaps/cyanVector.png +0 -0
  83. PaIRS_UniNa/icons/colormaps/flag.png +0 -0
  84. PaIRS_UniNa/icons/colormaps/gist_heat.png +0 -0
  85. PaIRS_UniNa/icons/colormaps/gray.png +0 -0
  86. PaIRS_UniNa/icons/colormaps/greenVector.png +0 -0
  87. PaIRS_UniNa/icons/colormaps/hot.png +0 -0
  88. PaIRS_UniNa/icons/colormaps/hsv.png +0 -0
  89. PaIRS_UniNa/icons/colormaps/inferno.png +0 -0
  90. PaIRS_UniNa/icons/colormaps/jet.png +0 -0
  91. PaIRS_UniNa/icons/colormaps/magentaVector.png +0 -0
  92. PaIRS_UniNa/icons/colormaps/magma.png +0 -0
  93. PaIRS_UniNa/icons/colormaps/ocean.png +0 -0
  94. PaIRS_UniNa/icons/colormaps/pink.png +0 -0
  95. PaIRS_UniNa/icons/colormaps/plasma.png +0 -0
  96. PaIRS_UniNa/icons/colormaps/prism.png +0 -0
  97. PaIRS_UniNa/icons/colormaps/rainbow.png +0 -0
  98. PaIRS_UniNa/icons/colormaps/redVector.png +0 -0
  99. PaIRS_UniNa/icons/colormaps/seismic.png +0 -0
  100. PaIRS_UniNa/icons/colormaps/spring.png +0 -0
  101. PaIRS_UniNa/icons/colormaps/summer.png +0 -0
  102. PaIRS_UniNa/icons/colormaps/tab10.png +0 -0
  103. PaIRS_UniNa/icons/colormaps/tab20.png +0 -0
  104. PaIRS_UniNa/icons/colormaps/tab20b.png +0 -0
  105. PaIRS_UniNa/icons/colormaps/tab20c.png +0 -0
  106. PaIRS_UniNa/icons/colormaps/terrain.png +0 -0
  107. PaIRS_UniNa/icons/colormaps/twilight.png +0 -0
  108. PaIRS_UniNa/icons/colormaps/viridis.png +0 -0
  109. PaIRS_UniNa/icons/colormaps/whiteVector.png +0 -0
  110. PaIRS_UniNa/icons/colormaps/winter.png +0 -0
  111. PaIRS_UniNa/icons/colormaps/yellowVector.png +0 -0
  112. PaIRS_UniNa/icons/common_region.png +0 -0
  113. PaIRS_UniNa/icons/common_region_off.png +0 -0
  114. PaIRS_UniNa/icons/completed.png +0 -0
  115. PaIRS_UniNa/icons/contourf_off.png +0 -0
  116. PaIRS_UniNa/icons/contourf_on.png +0 -0
  117. PaIRS_UniNa/icons/copy.png +0 -0
  118. PaIRS_UniNa/icons/copy_process.png +0 -0
  119. PaIRS_UniNa/icons/copy_process_off.png +0 -0
  120. PaIRS_UniNa/icons/cut.png +0 -0
  121. PaIRS_UniNa/icons/cut_warnings.png +0 -0
  122. PaIRS_UniNa/icons/darkmode.png +0 -0
  123. PaIRS_UniNa/icons/disp_step.png +0 -0
  124. PaIRS_UniNa/icons/disp_step_off.png +0 -0
  125. PaIRS_UniNa/icons/edit_list.png +0 -0
  126. PaIRS_UniNa/icons/example_list.png +0 -0
  127. PaIRS_UniNa/icons/flaticon_PaIRS_beta.png +0 -0
  128. PaIRS_UniNa/icons/flaticon_PaIRS_download_warning.png +0 -0
  129. PaIRS_UniNa/icons/flip_y_off.png +0 -0
  130. PaIRS_UniNa/icons/flip_y_on.png +0 -0
  131. PaIRS_UniNa/icons/folder_loop_cleanup.png +0 -0
  132. PaIRS_UniNa/icons/folder_loop_cleanup_off.png +0 -0
  133. PaIRS_UniNa/icons/gear.gif +0 -0
  134. PaIRS_UniNa/icons/gear.png +0 -0
  135. PaIRS_UniNa/icons/import_set.png +0 -0
  136. PaIRS_UniNa/icons/information.png +0 -0
  137. PaIRS_UniNa/icons/input_logo.png +0 -0
  138. PaIRS_UniNa/icons/issue.png +0 -0
  139. PaIRS_UniNa/icons/laser_NTR.png +0 -0
  140. PaIRS_UniNa/icons/laser_TR_double.png +0 -0
  141. PaIRS_UniNa/icons/laser_TR_single.png +0 -0
  142. PaIRS_UniNa/icons/link.png +0 -0
  143. PaIRS_UniNa/icons/linked.png +0 -0
  144. PaIRS_UniNa/icons/log_logo.png +0 -0
  145. PaIRS_UniNa/icons/logo_opaco.png +0 -0
  146. PaIRS_UniNa/icons/measure.png +0 -0
  147. PaIRS_UniNa/icons/measure_off.png +0 -0
  148. PaIRS_UniNa/icons/min_proc.png +0 -0
  149. PaIRS_UniNa/icons/min_proc_off.png +0 -0
  150. PaIRS_UniNa/icons/min_step.png +0 -0
  151. PaIRS_UniNa/icons/min_step_off.png +0 -0
  152. PaIRS_UniNa/icons/new_workspace.png +0 -0
  153. PaIRS_UniNa/icons/open_image.png +0 -0
  154. PaIRS_UniNa/icons/open_new_window.png +0 -0
  155. PaIRS_UniNa/icons/open_result.png +0 -0
  156. PaIRS_UniNa/icons/open_workspace.png +0 -0
  157. PaIRS_UniNa/icons/output_logo.png +0 -0
  158. PaIRS_UniNa/icons/paste_above.png +0 -0
  159. PaIRS_UniNa/icons/paste_below.png +0 -0
  160. PaIRS_UniNa/icons/paused.png +0 -0
  161. PaIRS_UniNa/icons/piv_proc.png +0 -0
  162. PaIRS_UniNa/icons/piv_proc_off.png +0 -0
  163. PaIRS_UniNa/icons/piv_step.png +0 -0
  164. PaIRS_UniNa/icons/piv_step_off.png +0 -0
  165. PaIRS_UniNa/icons/process_logo.png +0 -0
  166. PaIRS_UniNa/icons/process_loop.png +0 -0
  167. PaIRS_UniNa/icons/project.png +0 -0
  168. PaIRS_UniNa/icons/pylog.png +0 -0
  169. PaIRS_UniNa/icons/python_warning.png +0 -0
  170. PaIRS_UniNa/icons/queue.png +0 -0
  171. PaIRS_UniNa/icons/read.png +0 -0
  172. PaIRS_UniNa/icons/read_list.png +0 -0
  173. PaIRS_UniNa/icons/reset.png +0 -0
  174. PaIRS_UniNa/icons/reset_levels.png +0 -0
  175. PaIRS_UniNa/icons/restore_undo.png +0 -0
  176. PaIRS_UniNa/icons/running.gif +0 -0
  177. PaIRS_UniNa/icons/running.png +0 -0
  178. PaIRS_UniNa/icons/running_warn.png +0 -0
  179. PaIRS_UniNa/icons/save_and_stop.png +0 -0
  180. PaIRS_UniNa/icons/save_cfg.png +0 -0
  181. PaIRS_UniNa/icons/saveas_workspace.png +0 -0
  182. PaIRS_UniNa/icons/scale_all.png +0 -0
  183. PaIRS_UniNa/icons/scale_down.png +0 -0
  184. PaIRS_UniNa/icons/scale_up.png +0 -0
  185. PaIRS_UniNa/icons/scan_list.png +0 -0
  186. PaIRS_UniNa/icons/scan_path.png +0 -0
  187. PaIRS_UniNa/icons/scan_path_loop.png +0 -0
  188. PaIRS_UniNa/icons/scan_path_loop_off.png +0 -0
  189. PaIRS_UniNa/icons/search.png +0 -0
  190. PaIRS_UniNa/icons/showIW_off.png +0 -0
  191. PaIRS_UniNa/icons/showIW_on.png +0 -0
  192. PaIRS_UniNa/icons/show_all.png +0 -0
  193. PaIRS_UniNa/icons/sort.png +0 -0
  194. PaIRS_UniNa/icons/sort_reversed.png +0 -0
  195. PaIRS_UniNa/icons/spiv_proc.png +0 -0
  196. PaIRS_UniNa/icons/spiv_proc_off.png +0 -0
  197. PaIRS_UniNa/icons/star.png +0 -0
  198. PaIRS_UniNa/icons/step_inheritance.png +0 -0
  199. PaIRS_UniNa/icons/subMIN_off.png +0 -0
  200. PaIRS_UniNa/icons/subMIN_on.png +0 -0
  201. PaIRS_UniNa/icons/unedited.png +0 -0
  202. PaIRS_UniNa/icons/uninitialized.png +0 -0
  203. PaIRS_UniNa/icons/unlink.png +0 -0
  204. PaIRS_UniNa/icons/unwrap_items.png +0 -0
  205. PaIRS_UniNa/icons/vectorColor.png +0 -0
  206. PaIRS_UniNa/icons/view.png +0 -0
  207. PaIRS_UniNa/icons/view_off.png +0 -0
  208. PaIRS_UniNa/icons/vis_logo.png +0 -0
  209. PaIRS_UniNa/icons/warning_circle.png +0 -0
  210. PaIRS_UniNa/icons/window.png +0 -0
  211. PaIRS_UniNa/icons/workspace.png +0 -0
  212. PaIRS_UniNa/icons/wrap_items.png +0 -0
  213. PaIRS_UniNa/icons/write_list.png +0 -0
  214. PaIRS_UniNa/listLib.py +303 -0
  215. PaIRS_UniNa/mtfPIV.py +8 -8
  216. PaIRS_UniNa/parForMulti.py +7 -5
  217. PaIRS_UniNa/parForWorkers.py +370 -31
  218. PaIRS_UniNa/pivParFor.py +233 -229
  219. PaIRS_UniNa/plt_util.py +141 -141
  220. PaIRS_UniNa/preProcParFor.py +153 -148
  221. PaIRS_UniNa/procTools.py +631 -178
  222. PaIRS_UniNa/readcfg.py +31 -1
  223. PaIRS_UniNa/rqrdpckgs.txt +9 -0
  224. PaIRS_UniNa/stereoPivParFor.py +227 -0
  225. PaIRS_UniNa/tAVarie.py +215 -215
  226. PaIRS_UniNa/tabSplitter.py +612 -0
  227. PaIRS_UniNa/ui_Calibration_Tab.py +545 -0
  228. PaIRS_UniNa/ui_Custom_Top.py +5 -5
  229. PaIRS_UniNa/ui_Input_Tab.py +1101 -0
  230. PaIRS_UniNa/{ui_Import_Tab_CalVi.py → ui_Input_Tab_CalVi.py} +1282 -1275
  231. PaIRS_UniNa/ui_Log_Tab.py +262 -257
  232. PaIRS_UniNa/{ui_Export_Tab.py → ui_Output_Tab.py} +2361 -1778
  233. PaIRS_UniNa/ui_Process_Tab.py +3809 -3758
  234. PaIRS_UniNa/ui_Process_Tab_CalVi.py +1547 -1546
  235. PaIRS_UniNa/ui_Process_Tab_Disp.py +1141 -0
  236. PaIRS_UniNa/ui_Process_Tab_Min.py +437 -0
  237. PaIRS_UniNa/ui_Vis_Tab.py +1626 -1208
  238. PaIRS_UniNa/ui_Vis_Tab_CalVi.py +1249 -1249
  239. PaIRS_UniNa/ui_Whatsnew.py +131 -131
  240. PaIRS_UniNa/ui_gPairs.py +876 -950
  241. PaIRS_UniNa/ui_infoPaIRS.py +550 -425
  242. PaIRS_UniNa/whatsnew.txt +6 -4
  243. {PaIRS_UniNa-0.1.13.dist-info → pairs_unina-0.2.8.dist-info}/METADATA +69 -51
  244. pairs_unina-0.2.8.dist-info/RECORD +329 -0
  245. {PaIRS_UniNa-0.1.13.dist-info → pairs_unina-0.2.8.dist-info}/WHEEL +1 -1
  246. PaIRS_UniNa/CalVi.py +0 -18
  247. PaIRS_UniNa/Export_Tab.py +0 -574
  248. PaIRS_UniNa/Import_Tab.py +0 -657
  249. PaIRS_UniNa/Import_Tab_CalVi.py +0 -861
  250. PaIRS_UniNa/Import_Tab_tools.py +0 -598
  251. PaIRS_UniNa/Tree_Tab.py +0 -543
  252. PaIRS_UniNa/gCalVi.py +0 -2024
  253. PaIRS_UniNa/icons/add.png +0 -0
  254. PaIRS_UniNa/icons/cancelled.png +0 -0
  255. PaIRS_UniNa/icons/chain.png +0 -0
  256. PaIRS_UniNa/icons/chain_broken.png +0 -0
  257. PaIRS_UniNa/icons/default_sizes.png +0 -0
  258. PaIRS_UniNa/icons/dock_tabs.png +0 -0
  259. PaIRS_UniNa/icons/done.png +0 -0
  260. PaIRS_UniNa/icons/down_arrow.png +0 -0
  261. PaIRS_UniNa/icons/export_logo.png +0 -0
  262. PaIRS_UniNa/icons/fast_delete.png +0 -0
  263. PaIRS_UniNa/icons/flip_y.png +0 -0
  264. PaIRS_UniNa/icons/ganci.png +0 -0
  265. PaIRS_UniNa/icons/import_blue.png +0 -0
  266. PaIRS_UniNa/icons/import_logo.png +0 -0
  267. PaIRS_UniNa/icons/missing.png +0 -0
  268. PaIRS_UniNa/icons/process_icon.png +0 -0
  269. PaIRS_UniNa/icons/processing.png +0 -0
  270. PaIRS_UniNa/icons/restart.png +0 -0
  271. PaIRS_UniNa/icons/right_arrow.png +0 -0
  272. PaIRS_UniNa/icons/run_piv.png +0 -0
  273. PaIRS_UniNa/icons/terminal.png +0 -0
  274. PaIRS_UniNa/icons/undock_tabs.png +0 -0
  275. PaIRS_UniNa/icons/vect_field.png +0 -0
  276. PaIRS_UniNa/icons/w0.png +0 -0
  277. PaIRS_UniNa/icons/w1.png +0 -0
  278. PaIRS_UniNa/icons/w2.png +0 -0
  279. PaIRS_UniNa/icons/w3.png +0 -0
  280. PaIRS_UniNa/icons/w4.png +0 -0
  281. PaIRS_UniNa/icons/w5.png +0 -0
  282. PaIRS_UniNa/run_CalVi.py +0 -14
  283. PaIRS_UniNa/run_gcalvi.py +0 -5
  284. PaIRS_UniNa/ui_Import_Tab.py +0 -1077
  285. PaIRS_UniNa/ui_Tree_Tab.py +0 -684
  286. PaIRS_UniNa/ui_gCalVi.py +0 -640
  287. PaIRS_UniNa/ui_infoCalVi.py +0 -428
  288. PaIRS_UniNa-0.1.13.dist-info/LICENSE +0 -19
  289. PaIRS_UniNa-0.1.13.dist-info/RECORD +0 -174
  290. /PaIRS_UniNa/icons/{clean_queue.png → clean.png} +0 -0
  291. /PaIRS_UniNa/icons/{waiting_c.png → sandglass.png} +0 -0
  292. {PaIRS_UniNa-0.1.13.dist-info → pairs_unina-0.2.8.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,10 @@
1
+ from math import ceil, floor
1
2
  #PrintTA.flagPriority=PrintTAPriority.always
2
- Flag_DEBUG=True
3
+ Flag_DEBUG=False
3
4
  Flag_DEBUG_PARPOOL=False
5
+ FlagPrintTime=False
6
+ FlagPrintCoding=False
7
+
4
8
  pwddbg='Buss4Co1Pied1'
5
9
  time_warnings_debug=-1 #10000 #milliseconds #5000
6
10
 
@@ -8,34 +12,112 @@ import uuid
8
12
  basefold='./'
9
13
  basefold_DEBUGOptions=[]
10
14
  basefold_DEBUG='./'
15
+ basefold_DEBUG_VIS=''
11
16
  #basefold='B:/dl/apairs/jetcross'
12
- if hex(uuid.getnode())=='0x7824af430781': #gerardo windows
17
+
18
+
19
+ developerIDs={
20
+ 'GP_Win_Office': '231128824800632', #'0x7824af430781',
21
+ 'GP_Win_Office_New': '140626882900161', #'0x7824af430781',
22
+ 'GP_Mac_Laptop': 'V94LRP93FV', #'0xa275dd445ab0',
23
+ 'GP_WSL' : 'b44ec1c0e5a74ffd97bb050c39ef6cb1',
24
+ 'TA_Win_Office': '160983906000941', #'0xccb0da8c896e'
25
+ 'TA_Win_Office_New': '231128824801036', #??
26
+ }
27
+
28
+ import psutil,subprocess
29
+ def getCurrentID():
30
+ #return hex(uuid.getnode())
31
+ serial_number=None
32
+ try:
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
+ """"
56
+ # On Linux, the motherboard serial number can be obtained from the /sys/class/dmi/id/board_serial file
57
+ with open('/sys/class/dmi/id/board_serial', 'r') as f:
58
+ serial_number = f.read().strip()
59
+ """
60
+ elif psutil.WINDOWS:
61
+ # On Windows, the motherboard serial number can be obtained using WMI
62
+ output = subprocess.check_output(["wmic", "baseboard", "get", "SerialNumber"]).decode('utf-8')
63
+ serial_number = output.strip().split('\n')[1].strip()
64
+ elif psutil.MACOS:
65
+ # On macOS, the motherboard serial number can be obtained using the system_profiler command
66
+ output = subprocess.check_output(["system_profiler", "SPHardwareDataType"])
67
+ for line in output.splitlines():
68
+ if b'Serial Number (system)' in line:
69
+ serial_number = line.split(b':')[1].strip().decode('utf-8')
70
+ except Exception as e:
71
+ if Flag_DEBUG:
72
+ print(f"Error while retrieving motherboard serial number: {e}")
73
+ return serial_number
74
+
75
+ currentID=getCurrentID()
76
+ FlagAddMotherBoard=False
77
+ if currentID in (developerIDs['GP_Win_Office'],developerIDs['GP_Win_Office_New']): #gerardo windows
13
78
  basefold_DEBUG='C:/desk/PIV_Img/_data/PIV_data/virtual_case/'
14
79
  basefold_DEBUGOptions=[
15
80
  'C:/desk/PIV_Img/img1/',
16
- 'C:/desk/PIV_Img/_data/PIV_data/virtual_case/',
81
+ 'C:/desk/PIV_Img/_data/PIV_data/virtual_case/',
17
82
  'C:/desk/PIV_Img/_data/PIV_data/real_case/',
18
83
  'C:/desk/PIV_Img/_data/Calibration_data/pinhole/',
19
- 'C:/desk/PIV_Img/_data/Calibration_data/cylinder/'
84
+ 'C:/desk/PIV_Img/_data/Calibration_data/cylinder/',
85
+ ]
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/',
20
95
  ]
21
- elif hex(uuid.getnode())=='0xa275dd445ab0': #gerardo mac
22
- basefold_DEBUG='/Users/gerardo/Desktop/PIV_Img/img1/'
96
+ basefold_DEBUG_VIS='/mnt/c/desk/PIV_Img/_data/PIV_data/real_case/'
97
+ elif currentID==developerIDs['GP_Mac_Laptop']: #gerardo mac
98
+ basefold_DEBUG='/Users/gerardo/Desktop/PIV_Img/swirler_png/' #'/Users/gerardo/Desktop/PIV_Img/img1/'
23
99
  basefold_DEBUGOptions=[
24
100
  '/Users/gerardo/Desktop/PIV_Img/img1/',
25
- '/Users/gerardo/Desktop/PaIRS_examples/PIV_data/virtual_case/',
101
+ '/Users/gerardo/Desktop/PaIRS_examples/PIV_data/virtual_case/',
102
+ #'/Users/gerardo/Desktop/PaIRS_examples/PIV_data/virtual_case_2/',
26
103
  '/Users/gerardo/Desktop/PaIRS_examples/PIV_data/real_case/',
104
+ '/Users/gerardo/Desktop/PaIRS_examples/SPIV_data/real_case/img/',
27
105
  '/Users/gerardo/Desktop/PaIRS_examples/Calibration_data/pinhole/',
28
106
  '/Users/gerardo/Desktop/PaIRS_examples/Calibration_data/cylinder/'
29
107
  ]
30
- elif hex(uuid.getnode())=='0xccb0da8c896e': #TA Ufficio
31
- basefold_DEBUG='C:\desk\Attuali\PythonLibC\PIV\img'
108
+ basefold_DEBUG_VIS='/Users/gerardo/Desktop/PaIRS_examples/PIV_data/real_case/'
109
+ basefold_DEBUG_VIS='/Users/gerardo/Desktop/PIV_Img/img1/'
110
+ elif currentID in (developerIDs['TA_Win_Office'],developerIDs['TA_Win_Office_New']): #TA windows
111
+ basefold_DEBUG=r'C:\desk\Attuali\PythonLibC\PIV\img'
32
112
  basefold_DEBUGOptions=[
33
113
  'C:/desk/PIV_Img/img1/',
34
114
  'C:/desk/PIV_Img/swirler_png/',
35
115
  '../../img/calib/',
36
- 'C:\desk\Attuali\PythonLibC\PIV\img',
116
+ r'C:\desk\Attuali\PythonLibC\PIV\img',
37
117
  ]
38
-
118
+ basefold_DEBUG_VIS=''
119
+ else:
120
+ FlagAddMotherBoard=True
39
121
 
40
122
  #fontName='Inter'
41
123
  #fontName='Cambria'
@@ -58,17 +140,17 @@ imin_im_pair=1 #minimum index value for image pair
58
140
 
59
141
  f_empty_width=250 #blank space in scrollable area within the main window
60
142
  time_ScrollBar=250 #time of animation of scroll area
61
- time_fun2_async=0 #time to test async callbacks
143
+ time_callback2_async=0 #time to test async callbacks
62
144
  time_showSplashOnTop=250
145
+ pathCompleterLength=10
63
146
 
64
147
  fileChanges='Changes.txt'
65
148
  fileWhatsNew=['whatsnew.txt','whatwasnew.txt']
66
149
  icons_path="icons/"
67
150
 
68
151
  from psutil import cpu_count
69
- NUMTHREADS_PIV=cpu_count(logical=True)#-1
70
- if NUMTHREADS_PIV<1: NUMTHREADS_PIV=1
71
- NUMTHREADS_PIV_MAX=NUMTHREADS_PIV
152
+ NUMTHREADS_MAX=cpu_count(logical=True)#-1
153
+ if NUMTHREADS_MAX<1: NUMTHREADS_MAX=1
72
154
  ParFor_sleepTime=0.1
73
155
  #multithreading
74
156
  FlagStopWorkers=[0]#messo qui ma utilizzato solo da min e PIV
@@ -76,7 +158,7 @@ NUMTHREADS_gPaIRS=0
76
158
  SleepTime_Workers=0.5 #for multithreading and other stuff
77
159
  timeOutWorker=0 # used in parfor when the proces is stuck
78
160
 
79
- from .__init__ import __version__,__year__,__mail__
161
+ from .__init__ import __version__,__subversion__,__year__,__mail__
80
162
  from PySide6 import QtCore, QtGui, QtWidgets
81
163
  from PySide6.QtCore import*
82
164
  from PySide6.QtGui import *
@@ -98,14 +180,43 @@ from .tAVarie import *
98
180
  deltaTimePlot=0.75
99
181
  import concurrent.futures
100
182
  import gc#garbage collection si può eliminare
101
- import psutil
102
183
  from .mtfPIV import *
103
184
 
104
- import subprocess
105
185
  import sys
106
186
  import concurrent.futures
107
187
  import asyncio
108
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'
109
220
 
110
221
  class ColorPrint:
111
222
  def __init__(self,flagTime=False,prio=PrintTAPriority.medium,faceStd=PrintTA.faceStd,flagFullDebug=False):
@@ -138,14 +249,15 @@ class ColorPrint:
138
249
  #flagFullDebug=True means that the printing is available only if fullDebug mode is active
139
250
  class GPaIRSPrint:
140
251
  def __init__(self):
141
- self.General=ColorPrint(prio=PrintTAPriority.medium)
142
252
  self.Info=ColorPrint(prio=PrintTAPriority.medium)
143
- self.Time=ColorPrint(prio=PrintTAPriority.veryLow,flagTime=True,faceStd=PrintTA.faceUnderline)
253
+ self.Time=ColorPrint(prio=PrintTAPriority.medium if FlagPrintTime else PrintTAPriority.veryLow,flagTime=True,faceStd=PrintTA.faceUnderline)
144
254
  self.Error=ColorPrint(prio=PrintTAPriority.medium,faceStd=PrintTA.faceBold)
255
+ self.IOError=ColorPrint(prio=PrintTAPriority.veryLow,faceStd=PrintTA.faceBold)
145
256
  self.Process=ColorPrint(prio=PrintTAPriority.veryLow)
146
257
  self.Callback=ColorPrint(prio=PrintTAPriority.veryLow)
147
- self.Geometry=ColorPrint(prio=PrintTAPriority.veryLow,flagFullDebug=True)
258
+ self.TABparDiff=ColorPrint(prio=PrintTAPriority.veryLow)
148
259
  self.PlotTime=ColorPrint(prio=PrintTAPriority.veryLow,flagTime=True,faceStd=PrintTA.faceUnderline,flagFullDebug=True)
260
+ self.Coding=ColorPrint(prio=PrintTAPriority.medium if FlagPrintCoding else PrintTAPriority.never,flagFullDebug=True)
149
261
 
150
262
  pri=GPaIRSPrint()
151
263
  printTypes={}
@@ -169,12 +281,6 @@ PaIRS_Header=f'PaIRS - version {__version__}\n'+\
169
281
  f'email: {__mail__}\n'+\
170
282
  '****************************************\n'
171
283
 
172
- CalVi_Header=f'CalVi - version {__version__}\n'+\
173
- 'Calibration Visualizer\n'+\
174
- f'(C) {__year__} Gerardo Paolillo & Tommaso Astarita.\nAll rights reserved.\n'+\
175
- f'email: {__mail__}\n'+\
176
- '****************************************\n'
177
-
178
284
  from .parForMulti import *
179
285
  #from pkg_resources import resource_filename
180
286
  from .parForMulti import ParForMul
@@ -194,35 +300,90 @@ else:
194
300
  import PaIRS_PIV as PaIRS_lib
195
301
 
196
302
  if __package__ or "." in __name__:
197
- from pkg_resources import resource_filename
198
- foldPaIRS=resource_filename(__package__,'')+"\\"
199
- foldPaIRS=foldPaIRS.replace('\\','/')
303
+ import importlib.resources as resources
304
+ resources_path = resources.files(__package__)
305
+ foldPaIRS = str(resources_path)+"\\"
306
+ foldPaIRS = foldPaIRS.replace('\\', '/')
200
307
  else:
201
308
  foldPaIRS='./'
309
+ class ProcessTypes:
310
+ null=None
311
+ min=0
312
+ piv=1
313
+ spiv=2
314
+ tpiv=3
315
+ cal=10
316
+
317
+ singleCamera=[piv]
318
+ threeCameras=[min,tpiv]
202
319
 
320
+ class StepTypes:
321
+ null=None
322
+ min=0
323
+ piv=1
324
+ spiv=2
325
+ cal=10
326
+ disp=11
327
+
328
+ process={
329
+ ProcessTypes.null: '-',
330
+ ProcessTypes.min: 'minimum',
331
+ ProcessTypes.piv: 'PIV',
332
+ ProcessTypes.spiv: 'SPIV',
333
+ ProcessTypes.tpiv: 'TPIV',
334
+ ProcessTypes.cal: 'calibration',
335
+ }
336
+ process_items=[v for v in process.values()]
337
+ process_ord=range(len(process_items))
203
338
  class outExt:
339
+ #legacy
204
340
  cfg='.pairs_cfg'
205
341
  dum='.pairs_dum'
342
+
343
+ #Workspaces and projects
344
+ wksp='.pairs_wksp'
345
+ proj='.pairs_proj'
346
+
347
+ #StepTypes
206
348
  min='.pairs_min'
207
349
  piv='.pairs_piv'
350
+ spiv='.pairs_spiv'
351
+ cal='.pairs_cal'
352
+ calvi='.calvi'
353
+ disp='.pairs_disp'
354
+
355
+ #Further types of variable
356
+ #PIV process
208
357
  pro='.pairs_pro'
358
+ #CalVi
209
359
  cfg_calvi='.calvi_cfg'
210
- cal='.pairs_cal'
211
360
  pla='.pairs_pla'
212
361
 
213
- lastcfgname='lastuicfg'+outExt.cfg
214
- lastcfgname_CalVi='lastuicfg'+outExt.cfg_calvi
215
362
 
363
+ lastcfgname='lastWorkSpace'+outExt.wksp
216
364
  fileChanges=foldPaIRS+'Changes.txt'
217
- fileWhatsNew=[foldPaIRS+f for f in fileWhatsNew]
218
365
  icons_path=foldPaIRS+icons_path
219
- lastcfgname=foldPaIRS+lastcfgname
220
- pro_path=foldPaIRS+"pro/"
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
+
221
380
  if not os.path.exists(pro_path):
222
- os.mkdir(pro_path)
381
+ try:
382
+ os.mkdir(pro_path)
383
+ except Exception as inst:
384
+ pri.Error.red(f'It was not possible to make the directory {pro_path}:\n{traceback.format_exc()}\n\n{inst}')
223
385
  custom_list_file="pro_list.txt"
224
386
 
225
-
226
387
  exts = Image.registered_extensions()
227
388
  supported_exts = sorted({ex for ex, f in exts.items() if f in Image.OPEN})
228
389
  text_filter = "Common image files (*.bmp *.gif *.ico *.jpeg *.jpg *.png *.tif *.tiff *.webp"\
@@ -237,31 +398,50 @@ if Flag_NATIVEDIALOGS:
237
398
  else:
238
399
  optionNativeDialog=QFileDialog.Option.DontUseNativeDialog
239
400
 
240
- def warningDialog(self,Message,time_milliseconds=0,flagScreenCenter=False,icon=QIcon(),palette=None,pixmap=None,title='Warning!',flagRichText=False,flagNoButtons=False,addButton=None): #addButton=['Print Message',lambda: print(Message)]
401
+ def warningDialog(self:QWidget,Message,time_milliseconds=0,flagScreenCenter=False,icon:QIcon=QIcon(),palette=None,pixmap=None,title='Warning!',flagRichText=False,flagNoButtons=False,addButton:dict=None,FlagStayOnTop=False,pixmapSize=64): #addButton=['Print Message',lambda: print(Message)]
241
402
  dlg=None
242
403
  if Message:
243
- dlg = QMessageBox(self)
404
+ if isinstance(self,QMainWindow) and hasattr(self,'w_Input'):
405
+ dlg = QMessageBox(self.w_Input)
406
+ else:
407
+ dlg = QMessageBox(self)
244
408
  dlg.setWindowTitle(title)
245
409
  dlg.setText(str(Message))
410
+
246
411
  if flagRichText: dlg.setTextFormat(Qt.TextFormat.RichText)
247
412
  if flagNoButtons:
248
413
  dlg.setStandardButtons(QMessageBox.StandardButton.NoButton)
249
414
  else:
250
415
  dlg.setStandardButtons(QMessageBox.StandardButton.Ok)
251
- if addButton:
252
- abutt = dlg.addButton(addButton[0], QtWidgets.QMessageBox.YesRole)
253
- abutt.clicked.disconnect()
254
- def aFun():
255
- addButton[1]()
256
- dlg.done(0)
257
- abutt.clicked.connect(aFun)
416
+ if addButton:
417
+ for addB, addAction in addButton.items():
418
+ abutt = dlg.addButton(addB, QtWidgets.QMessageBox.YesRole)
419
+ abutt.clicked.disconnect()
420
+ def aFun(fun):
421
+ fun()
422
+ dlg.done(0)
423
+ abutt.clicked.connect(lambda flag=None,fun=addAction: aFun(fun))
258
424
  dlg.setIcon(QMessageBox.Warning)
259
425
  if icon:
260
- dlg.setWindowIcon(icon)
426
+ if type(icon)==QIcon: dlg.setWindowIcon(icon)
427
+ else:
428
+ try:
429
+ iconW=QIcon()
430
+ iconW.addFile(icon)
431
+ dlg.setWindowIcon(iconW)
432
+ except Exception as e:
433
+ pri.Error.red(f'Error while reading the window icon from the file {icon}:\n{e}')
434
+ else:
435
+ if not hasattr(self,'windowIcon') or not self.windowIcon():
436
+ iconW=QIcon()
437
+ iconW.addFile(icons_path+'icon_PaIRS.png')
438
+ dlg.setWindowIcon(iconW)
439
+ else:
440
+ dlg.setWindowIcon(self.windowIcon())
261
441
  if palette:
262
442
  dlg.setPalette(palette)
263
443
  if pixmap:
264
- dlg.setIconPixmap(QPixmap(pixmap).scaled(64, 64, Qt.AspectRatioMode.KeepAspectRatio))
444
+ dlg.setIconPixmap(QPixmap(pixmap).scaled(pixmapSize, pixmapSize, Qt.AspectRatioMode.KeepAspectRatio,Qt.SmoothTransformation))
265
445
  if self:
266
446
  dlg.setFont(self.font())
267
447
  c=dlg.findChildren(QObject)
@@ -272,22 +452,33 @@ def warningDialog(self,Message,time_milliseconds=0,flagScreenCenter=False,icon=Q
272
452
  w.setFont(font)
273
453
  #dlg.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint)
274
454
  dlg.show()
275
- if flagScreenCenter and hasattr(self,'MaxGeo'):
455
+ if flagScreenCenter and hasattr(self,'maximumGeometry'):
276
456
  geom=dlg.geometry()
277
- geom.moveCenter(self.MaxGeo.center())
457
+ geom.moveCenter(self.maximumGeometry.center())
278
458
  dlg.setGeometry(geom)
279
459
  if time_milliseconds:
280
460
  QTimer.singleShot(time_milliseconds, lambda : dlg.done(0))
281
461
  else:
282
462
  if Flag_DEBUG and time_warnings_debug>=0:
283
463
  QTimer.singleShot(time_warnings_debug, lambda : dlg.done(0))
464
+ if FlagStayOnTop: dlg.setWindowFlag(Qt.WindowStaysOnTopHint, True)
284
465
  if not flagNoButtons: dlg.exec()
285
466
  return dlg
286
-
467
+
287
468
  def questionDialog(self,Message,icon=QMessageBox.Warning):
288
- dlg = QMessageBox(self)
469
+ if isinstance(self,QMainWindow) and hasattr(self,'w_Input'):
470
+ dlg = QMessageBox(self.w_Input)
471
+ else:
472
+ dlg = QMessageBox(self)
289
473
  dlg.setWindowTitle("Warning!")
290
474
  dlg.setText(str(Message))
475
+ if not self.windowIcon():
476
+ icons_path+'icon_PaIRS.png'
477
+ iconW=QIcon()
478
+ iconW.addFile(icon)
479
+ dlg.setWindowIcon(iconW)
480
+ else:
481
+ dlg.setWindowIcon(self.windowIcon())
291
482
 
292
483
  dlg.setStandardButtons(QMessageBox.Yes|QMessageBox.No)
293
484
  dlg.setDefaultButton(QMessageBox.Yes)
@@ -337,10 +528,10 @@ def inputDialog(self,title,label,icon=None,palette=None,completer_list=[],width=
337
528
  geom.moveCenter(QtGui.QCursor.pos())
338
529
  dlg.setGeometry(geom)
339
530
 
340
- if flagScreenCenter and hasattr(self,'MaxGeo'):
531
+ if flagScreenCenter and hasattr(self,'maximumGeometry'):
341
532
  dlg.show()
342
533
  geom=dlg.geometry()
343
- geom.moveCenter(self.MaxGeo.center())
534
+ geom.moveCenter(self.maximumGeometry.center())
344
535
  dlg.setGeometry(geom)
345
536
 
346
537
  c=dlg.findChildren(QObject)
@@ -432,24 +623,25 @@ def noPrint(*args,**kwargs):
432
623
 
433
624
  #import unidecode
434
625
  def myStandardPath(path):
435
- #path=unidecode.unidecode(path)
436
- currpath=path
437
- if currpath:
438
- while currpath[-1]==" ": currpath=currpath[:-1]
439
- currpath=re.sub(r'\\+',r'/',currpath)
440
- currpath=currpath+'/'
441
- currpath=re.sub('/+', '/',currpath)
626
+ currpath = path.rstrip() # Remove trailing white spaces from the path
627
+ currpath = currpath.replace('\\', '/') # Replace all backslashes with forward slashes
628
+ currpath = currpath.rstrip('/') + '/' if currpath else './' # Add a trailing slash to the path if not present
629
+ currpath = re.sub('/+', '/', currpath) # Reduce consecutive slashes to a single slash
442
630
  return currpath
443
631
 
444
632
  def myStandardRoot(root):
445
- #root=unidecode.unidecode(root)
446
- currroot=root
447
- if currroot:
448
- while currroot[-1]==" ": currroot=currroot[:-1]
449
- currroot=re.sub(r'\\+',r'/',currroot)
450
- currroot=re.sub('/+', '/',currroot)
633
+ currroot = root.rstrip() # Remove trailing white spaces from the root
634
+ currroot = currroot.replace('\\', '/') # Replace all backslashes with forward slashes
635
+ currroot = re.sub('/+', '/', currroot) # Reduce consecutive slashes to a single slash
451
636
  return currroot
452
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
+
453
645
  def findFiles_sorted(pattern):
454
646
  list_files=glob.glob(pattern)
455
647
  files=sorted([re.sub(r'\\+',r'/',f) for f in list_files],key=str.lower)
@@ -462,6 +654,7 @@ def transfIm(OUT,flagTransf:int=2,Images:list=[],flagRot=1):
462
654
  flagTransf==2 solo entrambi (default)
463
655
  '''
464
656
  if len(Images)==0: return
657
+ if OUT.FlagNone: return Images
465
658
 
466
659
  if flagTransf==1: #solo PIV
467
660
  ops=OUT.aimop
@@ -508,15 +701,18 @@ def readCustomListFile():
508
701
  custom_list=[]
509
702
  filename=pro_path+custom_list_file
510
703
  if os.path.exists(filename):
511
- with open(pro_path+custom_list_file,'r') as file:
512
- while True:
513
- line = file.readline()
514
- if not line:
515
- break
516
- else:
517
- l=line.strip()
518
- if l: custom_list.append(l)
519
- file.close()
704
+ try:
705
+ with open(filename,'r') as file:
706
+ while True:
707
+ line = file.readline()
708
+ if not line:
709
+ break
710
+ else:
711
+ l=line.strip()
712
+ if l: custom_list.append(l)
713
+ file.close()
714
+ except:
715
+ pri.Error.red(f'Error while opening the custom process list file: {filename}.\n{traceback.format_exc()}\n')
520
716
  return custom_list
521
717
 
522
718
  def setCustomList(task):
@@ -528,7 +724,7 @@ def setCustomList(task):
528
724
  var=pickle.load(file)
529
725
  task(var,name)
530
726
  except Exception as inst:
531
- pri.Error.red(f'Error while loading custom process file {filename}\t[from list]:\n{traceback.print_exc}\n\n{inst}')
727
+ pri.Error.red(f'Error while loading custom process file {filename}\t[from list]:\n{traceback.format_exc()}\n\n{inst}')
532
728
  custom_list.pop(k)
533
729
  if os.path.exists(filename):
534
730
  os.remove(filename)
@@ -543,7 +739,7 @@ def setCustomList(task):
543
739
  task(var,name)
544
740
  custom_list.append(name)
545
741
  except Exception as inst:
546
- pri.Error.red(f'Error while loading custom process file {filename}\t[from disk]:\n{traceback.print_exc}\n\n{inst}')
742
+ pri.Error.red(f'Error while loading the custom process file {filename}\t[from disk]:\n{traceback.format_exc()}\n\n{inst}')
547
743
  if os.path.exists(filename):
548
744
  os.remove(filename)
549
745
  rewriteCustomList(custom_list)
@@ -551,10 +747,61 @@ def setCustomList(task):
551
747
 
552
748
  def rewriteCustomList(custom_list):
553
749
  filename=pro_path+custom_list_file
554
- with open(filename,'w') as file:
555
- for c in custom_list:
556
- file.write(c+'\n')
557
- file.close()
750
+ try:
751
+ with open(filename,'w') as file:
752
+ for c in custom_list:
753
+ file.write(c+'\n')
754
+ file.close()
755
+ except:
756
+ pri.Error.red(f'Error while rewriting the custom process file {filename}\t[from disk]:\n{traceback.format_exc()}\n')
757
+
758
+ def identifierName(typeObject:str='proc'):
759
+ username=platform.system()+'-'+os.environ.get('USER', os.environ.get('USERNAME'))
760
+ date_time=QDate.currentDate().toString('yyyy/MM/dd')+'-'+\
761
+ QTime().currentTime().toString()
762
+ ppid=str(os.getppid())+'-'+str(os.getpid())
763
+ version='PaIRS-v'+__version__
764
+ version_user_info=version+'_'+username+'_'+date_time
765
+ id=ppid+'_'+str(uuid.uuid4())
766
+ name=version_user_info+'_'+typeObject+'_'+id
767
+ return name, username, __version__
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
558
805
 
559
806
  PlainTextConverter=QtGui.QTextDocument()
560
807
  def toPlainText(text):
@@ -562,12 +809,70 @@ def toPlainText(text):
562
809
  return PlainTextConverter.toPlainText()
563
810
 
564
811
  def showTip(obj,message):
565
- tip=QToolTip(obj)
566
812
  toolTipDuration=obj.toolTipDuration()
567
813
  obj.setToolTipDuration(3000)
568
- tip.showText(QCursor.pos(),message)
814
+ QToolTip.showText(QCursor.pos(),message)
569
815
  obj.setToolTipDuration(toolTipDuration)
570
816
 
817
+ def clean_tree(tree:QTreeWidget):
818
+ def remove_children(item:QTreeWidgetItem):
819
+ while item.childCount() > 0:
820
+ child = item.takeChild(0)
821
+ remove_children(child)
822
+ del item
823
+
824
+ while tree.topLevelItemCount() > 0:
825
+ item = tree.takeTopLevelItem(0)
826
+ # Elimina ricorsivamente tutti i figli dell'elemento
827
+ remove_children(item)
828
+
829
+ def html_image(icon_path,size=16):
830
+ text=f"""
831
+ <img src="{icon_path}" width="{size}" height="{size}" style="margin-right: 0px;">
832
+ """
833
+ return text
834
+
835
+ def procOutName(self):
836
+ for attr_name, attr_value in vars(ProcessTypes).items():
837
+ if attr_value == self.Process:
838
+ procExt=getattr(outExt,attr_name)
839
+ break
840
+ return f'{self.outPathRoot}{procExt}'
841
+
842
+ def stepOutName(self):
843
+ for attr_name, attr_value in vars(StepTypes).items():
844
+ if attr_value == self.Step:
845
+ procExt=getattr(outExt,attr_name)
846
+ break
847
+ return f'{self.outPathRoot}{procExt}'
848
+
849
+ def findIDFromLog(file_path):
850
+ nMaximumLines=50
851
+ try:
852
+ with open(file_path, 'r') as file:
853
+ # Legge fino a 50 righe (o meno, se il file ha meno righe)
854
+ for _ in range(nMaximumLines):
855
+ line = file.readline()
856
+ if not line: # Fine del file
857
+ break
858
+ if line.startswith("PaIRS-v"):
859
+ return line.strip() # Ritorna la riga trovata senza spazi extra
860
+ return None # Nessuna riga trovata
861
+ except FileNotFoundError:
862
+ pri.Error.red(f"File not found: {file_path}")
863
+ return None
864
+
865
+ def resultCheck(tab,par,ind=None):
866
+ if ind is None: ind=par.ind
867
+ ITE=tab.gui.ui.Explorer.ITEfromInd(ind)
868
+ filename=stepOutName(ITE.procdata)+'.log'
869
+ if os.path.exists(filename):
870
+ id=findIDFromLog(filename)
871
+ FlagResult=id==ITE.procdata.name_proc
872
+ else:
873
+ FlagResult=False
874
+ return FlagResult
875
+
571
876
  def runPaIRS(self,command='',flagQuestion=True):
572
877
  Flag=__package__ or "." in __name__
573
878
  pyCommands={
@@ -598,12 +903,16 @@ def runPaIRS(self,command='',flagQuestion=True):
598
903
  def run(self):
599
904
  try:
600
905
  import subprocess
601
- if Flag: #launched from package
602
- pri.Info.white(sys.executable+' -m PaIRS_UniNa '+command)
603
- subprocess.call(sys.executable+' -m PaIRS_UniNa '+command,shell=True)
906
+ if Flag_ISEXE:
907
+ pri.Info.white(sys.executable+' '+command)
908
+ subprocess.call(sys.executable+' '+command,shell=True)
604
909
  else:
605
- pri.Info.white(sys.executable+' -c '+'"'+f"import os; os.chdir('{os.getcwd()}'); {pyCommands[command]}"+'"')
606
- subprocess.call(sys.executable+' -c '+'"'+f"import os; os.chdir('{os.getcwd()}'); {pyCommands[command]}"+'"',shell=True)
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)
607
916
  self.isRunning=False
608
917
  except Exception as inst:
609
918
  pri.Error.red(inst)
@@ -632,13 +941,13 @@ def showSplash(filename=''+ icons_path +'logo_PaIRS_completo.png'):
632
941
  splash.show()
633
942
  return splash
634
943
 
635
- def checkLatestVersion(self,version,app:QApplication=None,splash:QLabel=None):
944
+ def checkLatestVersion(self,version,app:QApplication=None,splash:QLabel=None,flagWarning=1):
636
945
  flagStopAndDownload=False
637
946
  var=self.TABpar
638
947
  #var.FlagOutDated=0 if currentVersion==var.latestVersion else var.FlagOutDated
639
- if var.FlagOutDated==1:
640
- warningLatestVersion(self,app,flagExit=0,flagWarning=1)
641
- var.FlagOutDated=2
948
+ if abs(var.FlagOutDated)==1:
949
+ warningLatestVersion(self,app,flagExit=0,flagWarning=flagWarning,FlagStayOnTop=True)
950
+ var.FlagOutDated=2 if var.FlagOutDated==1 else -2
642
951
  """
643
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}?')
644
953
  if flagStopAndDownload:
@@ -664,31 +973,42 @@ def checkLatestVersion(self,version,app:QApplication=None,splash:QLabel=None):
664
973
  sOut=f'{packageName} the current version ({currentVersion}) of {packageName} is obsolete! Please, install the latest version: {latestVersion} by using:\npython -m pip install --upgrade {packageName}'
665
974
  var.FlagOutDated=2 if var.FlagOutDated==2 else 1
666
975
  elif flagOutDated==-1:
976
+ sOut=f'The version of the current instance of {packageName} ({currentVersion}) is newer than the latest official releas ({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!'
977
+ var.FlagOutDated=-2 if var.FlagOutDated==-2 else -1
978
+ elif flagOutDated==-1000:
667
979
  sOut=f'Error from pip: it was not possible to check for a new version of the {packageName} package!'
668
- var.FlagOutDated=0
980
+ var.FlagOutDated=-1000
669
981
  else:
670
982
  sOut=f'{packageName} The current version ({currentVersion}) of {packageName} is up-to-date! Enjoy it!'
671
983
  var.FlagOutDated=0
672
- pri.Callback.cyan(f'[{var.FlagOutDated}] '+sOut)
673
- self.signals.printOutDated.emit(flagOutDated>0)
984
+ pri.Info.cyan(f'[{var.FlagOutDated}] '+sOut)
985
+ self.signals.printOutDated.emit()
674
986
  #self.ui.button_PaIRS_download.setVisible(flagOutDated>0)
675
987
  pass
676
988
 
677
989
  checkOutDated(packageName,printOutDated)
678
990
  return flagStopAndDownload
679
991
 
680
- def warningLatestVersion(self,app,flagExit=0,flagWarning=0):
992
+ def warningLatestVersion(self,app,flagExit=0,flagWarning=0,time_milliseconds=0,FlagStayOnTop=False):
681
993
  if not flagExit:
682
994
  exitSuggestion=f'exit the current instance of {self.name} and '
683
995
  else:
684
996
  exitSuggestion=''
685
997
  py=myStandardRoot(sys.executable).split('/')[-1].split('.')[0]
686
998
  command=f'{py} -m pip install --upgrade PaIRS_UniNa'
687
- 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}'
999
+ if self.TABpar.FlagOutDated>0:
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/")
1006
+ else:
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!'
688
1008
  if flagExit:
689
1009
  print(f"\n{'*'*100}\n"+Message+f"\n{'*'*100}\n")
690
1010
  if flagWarning:
691
- warningDialog(self,Message,flagScreenCenter=True,pixmap=''+ icons_path +'flaticon_PaIRS_download.png')
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 {})
692
1012
 
693
1013
  def downloadLatestVersion(self,app):
694
1014
  try:
@@ -710,8 +1030,9 @@ def downloadLatestVersion(self,app):
710
1030
  except Exception as inst:
711
1031
  print(inst)
712
1032
 
713
- def button_download_PaIRS_callback(self,app):
1033
+ def button_download_PaIRS_action(self,app):
714
1034
  warningLatestVersion(self,app,flagExit=0,flagWarning=1)
1035
+ checkLatestVersion(self,__version__,self.app,splash=None,flagWarning=0)
715
1036
  return
716
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?')
717
1038
  if not flagStopAndDownload: return
@@ -723,6 +1044,20 @@ def button_download_PaIRS_callback(self,app):
723
1044
  else: command=''
724
1045
  subprocess.call(sys.executable+' -m PaIRS_UniNa '+command,shell=True)
725
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}"
726
1061
 
727
1062
  def checkOutDated(packageName:str,printOutDated):
728
1063
  '''
@@ -741,7 +1076,6 @@ def checkOutDated(packageName:str,printOutDated):
741
1076
  pass #in this case the last version of the package is installed
742
1077
  '''
743
1078
  async def checkOutDatedInternal(packageName):
744
- flagOutDated=0
745
1079
  #reqs = subprocess.check_output([sys.executable, '-m', 'pip', 'list','--outdated'])
746
1080
  #reqs = subprocess.run([sys.executable, '-m', 'pip', 'list','--outdated'],capture_output=True)
747
1081
  #reqs = subprocess.check_output([sys.executable, '-m', 'pip', 'index','versions','PaIRS_UniNa'])
@@ -751,50 +1085,77 @@ def checkOutDated(packageName:str,printOutDated):
751
1085
  reqs = subprocess.run([sys.executable, '-m', 'pip', 'list','--outdated'],capture_output=True)
752
1086
  outDated = [r.decode().split('==')[0] for r in reqs.stdout.split()]
753
1087
  """
754
- flagOutDated=-1
1088
+ flagOutDated=-1000
755
1089
  currentVersion='none'
756
1090
  latestVersion=''
757
1091
  try:
758
- command=[sys.executable, '-m', 'pip', 'show',packageName]
759
- reqs = subprocess.run(command,capture_output=True)
760
- if reqs.returncode:
761
- pri.Error.red('Error in command:\n'+' '.join(command)+'\n'+reqs.stderr.decode("utf-8") )
762
- return flagOutDated,currentVersion,latestVersion
763
- printing=reqs.stdout.decode("utf-8")
764
- pri.Info.cyan( printing )
765
- r=reqs.stdout.decode("utf-8").replace('\r','').split('\n')
766
- currentVersion='none'
767
- for s in r:
768
- if 'Version: ' in s:
769
- currentVersion=s.replace('Version: ','')
770
- break
1092
+ if Flag_ISEXE:
1093
+ currentVersion=__version__+'.'+__subversion__ if int(__subversion__) else __version__
1094
+ else:
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
771
1111
  if currentVersion!=__version__:
772
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!'
773
1113
  pri.Info.yellow(f'{"-"*50}\n{message}\n{"-"*50}\n')
774
- command=[sys.executable, '-m', 'pip', 'index','versions',packageName]
775
- reqs = subprocess.run(command,capture_output=True)
776
- if not reqs.returncode:
777
- printing=reqs.stdout.decode("utf-8")
778
- pri.Info.cyan( printing )
779
- r=reqs.stdout.decode("utf-8").replace('\r','').split('\n')
780
- #currentVersion=r[0].replace(packageName,'').replace('(','').replace(')','').replace(' ','')
781
- latestVersion=r[1].replace('Available versions: ','').split(',')[0]
1114
+ if Flag_ISEXE:
1115
+ _, latestVersion = get_package_version_urllib("PaIRS_UniNa")
782
1116
  else:
783
- pri.Error.red('Error in command:\n'+' '.join(command)+'\n'+reqs.stderr.decode("utf-8") )
784
-
785
- command=[sys.executable, '-m', 'pip', 'list','--outdated']
1117
+ command=[sys.executable, '-m', 'pip', 'index', 'versions', packageName]
786
1118
  reqs = subprocess.run(command,capture_output=True)
787
- if reqs.returncode:
788
- pri.Error.red('Error in command:\n'+' '.join(command)+'\n'+reqs.stderr.decode("utf-8") )
789
- return flagOutDated,currentVersion,latestVersion
790
- outDated = [r.decode().split('==')[0] for r in reqs.stdout.split()]
791
- if packageName in outDated:
792
- i=outDated.index(packageName)
793
- 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]
794
1125
  else:
795
- latestVersion=currentVersion
796
- pri.Info.cyan(f'{packageName} ({currentVersion}). Latest version available: {latestVersion}')
797
- flagOutDated=1 if currentVersion!=latestVersion else 0
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
+ """
1146
+ #flagOutDated=1 if currentVersion!=latestVersion else 0
1147
+ cV_parts=[int(c) for c in currentVersion.split('.')]
1148
+ lV_parts=[int(c) for c in latestVersion.split('.')]
1149
+ flagOutDated=1 if (cV_parts[0] < lV_parts[0] or
1150
+ cV_parts[1] < lV_parts[1] or
1151
+ cV_parts[2] < lV_parts[2]) \
1152
+ else -1 if (cV_parts[0] > lV_parts[0] or
1153
+ cV_parts[1] > lV_parts[1] or
1154
+ cV_parts[2] > lV_parts[2] ) \
1155
+ or (cV_parts[0] == lV_parts[0] and
1156
+ cV_parts[1] == lV_parts[1] and
1157
+ cV_parts[2] == lV_parts[2] and len(cV_parts)>len(lV_parts)) \
1158
+ else 0
798
1159
  except Exception as inst:
799
1160
  pri.Error.red(inst)
800
1161
  return flagOutDated,currentVersion,latestVersion
@@ -805,7 +1166,6 @@ def checkOutDated(packageName:str,printOutDated):
805
1166
  f3=executor.submit(asyncio.run,checkOutDatedInternal(packageName))
806
1167
  f3.add_done_callback(checkOutDatedComplete)
807
1168
 
808
-
809
1169
  def changes(self,TabType,filename,title=" Changes"):
810
1170
  FlagShow=False
811
1171
  if self.logChanges:
@@ -846,6 +1206,9 @@ def changes(self,TabType,filename,title=" Changes"):
846
1206
  logChanges.ui.log.setLineWrapColumnOrWidth(logChanges.ui.log.width()-20)
847
1207
  self.logChanges.ui.log.resizeEvent=lambda e: logResizeEvent(self.logChanges,e)
848
1208
 
1209
+ self.logChanges.ui.icon.addfuncclick['whatsnew']=self.whatsNew
1210
+ self.logChanges.ui.icon.setCustomCursor()
1211
+
849
1212
  try:
850
1213
  file = open(filename, "rb")
851
1214
  content = file.read().decode("utf-8")
@@ -856,8 +1219,203 @@ def changes(self,TabType,filename,title=" Changes"):
856
1219
  self.logChanges.ui.log.setText(f'No information about PaIRS-UniNa updates available!\n\nSorry for this, try to reinstall PaIRS-UniNa or alternatively contact the authors at {__mail__}.')
857
1220
  return
858
1221
 
859
-
860
1222
  import webbrowser
861
1223
  def downloadExampleData(self,url):
862
1224
  Message=f'Test data are available at the following link:\n{url}'
863
- warningDialog(self,Message,pixmap=''+ icons_path +'flaticon_PaIRS_download.png',title='Download test data',addButton=['Download data!',lambda:webbrowser.open(url)])
1225
+ warningDialog(self,Message,pixmap=''+ icons_path +'flaticon_PaIRS_download.png',title='Download test data',addButton={'Download data!':lambda:webbrowser.open(url)})
1226
+
1227
+ def optimalPivCores(totCore,nImgs,penCore=1):
1228
+ ''' Used to determine the optimal number of pivCores as a function of the total number of cores and the number of imag to be processed
1229
+ totCore is the total number of cores that can be used
1230
+ nImgs is the number of images
1231
+ penCore is a penalization for the internal parallelization of the PIV process if = to 1 the parallelization it is assumed to be perfect
1232
+ Most probably a value of 0.95-1 should work correctly
1233
+ with 0.95 adding the xth pivCore is counted as: x=10->0.63 20->0.38 40->0.14 80->0.017
1234
+ with 0.98 adding the xth pivCore is counted as: x=10->0.83 20->0.68 40->0.45 80->0.20
1235
+ Output
1236
+ nPivMax the number of pivCores to be used
1237
+ nCoreMax the number of multiProces to be used
1238
+ '''
1239
+ pen=1 #initially the penalization is zero
1240
+ procPower=1 # the processing power of piv is not directly proportional to the numbers of cores
1241
+ nCorePerImgMax=0
1242
+ nPivMax=0
1243
+ for nPiv in range(1,totCore+1):
1244
+ nProc=floor(totCore/nPiv)
1245
+ nCicli=ceil(nImgs/nProc)
1246
+ nCorePerImg=procPower/nCicli
1247
+ #♥print(nPiv,nProc,pen,procPower,nCorePerImg,nCicli)
1248
+ if nCorePerImg>nCorePerImgMax:
1249
+ nCorePerImgMax=nCorePerImg
1250
+ nPivMax=nPiv
1251
+ pen*=penCore
1252
+ procPower+=pen
1253
+ nCoreMax=floor(totCore/nPivMax)
1254
+ #nPivMax=floor(totCore/nCoreMax)
1255
+ return nPivMax,nCoreMax
1256
+
1257
+ from PySide6.QtCore import qInstallMessageHandler, QtMsgType
1258
+ def custom_qt_message_handler(mode, context, message):
1259
+ if ("QPainter" in message or "paintEngine" in message):
1260
+ return #Silenzia questi messaggi
1261
+ print(message) #Altrimenti stampali normalmente (oppure loggali)
1262
+ qInstallMessageHandler(custom_qt_message_handler)
1263
+
1264
+ """
1265
+ def custom_qt_message_handler(mode, context, message):
1266
+ if "QPainter" in message or "paintEngine" in message:
1267
+ print("\n!!! Intercepted Qt message:")
1268
+ print(message)
1269
+ print("\n*** Current Python stacktrace:")
1270
+ traceback.print_stack() # Questo stampa lo stack in cui è stato generato il messaggio
1271
+ else:
1272
+ print(message)
1273
+ qInstallMessageHandler(custom_qt_message_handler)
1274
+ import functools
1275
+ import traceback
1276
+ def log_qpainter_usage(func):
1277
+ @functools.wraps(func)
1278
+ def wrapper(*args, **kwargs):
1279
+ print(f"\n°°° Execution of {func.__name__} in {func.__module__}")
1280
+ traceback.print_stack(limit=4) # Mostra solo lo stack alto
1281
+ return func(*args, **kwargs)
1282
+ return wrapper
1283
+ """
1284
+
1285
+ class PaIRSApp(QApplication):
1286
+ def __init__(self,*args):
1287
+ super().__init__(*args)
1288
+ self.installMessageHandler()
1289
+ self.setStyle('Fusion')
1290
+
1291
+ def applicationSupportsSecureRestorableState(self):
1292
+ return True
1293
+
1294
+ def message_handler(self, mode, context, message):
1295
+ if "QBasicTimer::start" not in message and "QObject::startTimer" not in message:
1296
+ print(message)
1297
+
1298
+ def installMessageHandler(self):
1299
+ qInstallMessageHandler(self.message_handler)
1300
+
1301
+ rqrdpckgs_filename=foldPaIRS+"rqrdpckgs.txt"
1302
+ from packaging.version import Version
1303
+ import importlib.metadata
1304
+
1305
+ def resetRequiredPackagesFile(filename=rqrdpckgs_filename):
1306
+ # Leggi il contenuto esistente
1307
+ try:
1308
+ with open(filename, "r") as f:
1309
+ lines = f.readlines()
1310
+ except FileNotFoundError:
1311
+ pri.Error.red(f"resetRequiredPackagesFile: File {filename} not found.")
1312
+ return
1313
+
1314
+ with open(filename, "w") as f:
1315
+ for line in lines:
1316
+ parts = line.strip().split()
1317
+ if len(parts) >= 3:
1318
+ pkg = parts[0]
1319
+ vmin = parts[1]
1320
+ vmax = parts[2]
1321
+ f.write(f"{pkg}\t{vmin}\t{vmax}\t0\n")
1322
+ else:
1323
+ pri.Error.red(f"resetRequiredPackagesFile: Skipping malformed line: {line}")
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
+
1338
+ def checkRequiredPackages(self, filename=rqrdpckgs_filename, FlagDisplay=False, FlagForcePrint=False):
1339
+ required_packages = []
1340
+ vmin_list = []
1341
+ vmax_list = []
1342
+ vcurr_list = []
1343
+
1344
+ # Read file
1345
+ with open(filename, "r") as f:
1346
+ for line in f:
1347
+ #pri.Info.white(line)
1348
+ parts = line.strip().split()
1349
+ if len(parts) >= 4:
1350
+ required_packages.append(parts[0])
1351
+ vmin_list.append(Version(parts[1]))
1352
+ vmax_list.append(Version(parts[2]))
1353
+ vcurr_list.append(Version(parts[3]) if parts[3] != "0" else None)
1354
+ else:
1355
+ pri.Error.red(f"Malformed line: {line}")
1356
+
1357
+ Flag = False
1358
+ warnings = []
1359
+
1360
+ for i, pkg in enumerate(required_packages):
1361
+ try:
1362
+ installed_version = Version(importlib.metadata.version(pkg))
1363
+ except importlib.metadata.PackageNotFoundError:
1364
+ installed_version = None
1365
+
1366
+ # Update current installed version
1367
+ if installed_version is not None and (installed_version != vcurr_list[i] or FlagDisplay):
1368
+ vcurr_list[i] = installed_version
1369
+ Flag = True
1370
+
1371
+ # Check if within [vmin, vmax]
1372
+ if not (le_ver(vmin_list[i],installed_version) and le_ver(installed_version,vmax_list[i])):
1373
+ """
1374
+ warnings.append(
1375
+ f"- {pkg}: installed = {installed_version}, target range = [{vmin_list[i]}, {vmax_list[i]}]"
1376
+ )
1377
+ """
1378
+ warnings.append(
1379
+ f"- {pkg} {installed_version} not in [{vmin_list[i]}, {vmax_list[i]}]"
1380
+ )
1381
+
1382
+ # Show warning
1383
+ if len(warnings)>0: self.FlagPackIssue=True
1384
+ if len(warnings)>0 or FlagForcePrint:
1385
+ message = (
1386
+ "Some installed packages have a version outside the target range used to develop "
1387
+ "the current release of the PaIRS_UniNa package.\n\n"
1388
+ "This may lead to compatibility issues. If you experience unexpected behavior, "
1389
+ "it is recommended to either reinstall the last tested compatible versions or "
1390
+ f"download the executable at {EXEurl}."
1391
+ f" If any issue occurs, please contact the authors at {__mail__}.\n\n"
1392
+ #"or use the standalone executable available at:\n"
1393
+ #"https://pairs.unina.it/#download\n\n"
1394
+ "Incompatible packages:\n"
1395
+ + "\n".join(warnings) +
1396
+ "\n\nYou may reinstall the last compatible versions using the following commands:\n\n"
1397
+ )
1398
+ for i, pkg in enumerate(required_packages):
1399
+ if vcurr_list[i] is not None and (not (vmin_list[i] <= vcurr_list[i] <= vmax_list[i]) or FlagForcePrint):
1400
+ message += (
1401
+ f"python -m pip uninstall {pkg}\n"
1402
+ f"python -m pip install {pkg}=={vmax_list[i]}\n"
1403
+ )
1404
+
1405
+ warningDialog(
1406
+ self,
1407
+ Message=message,
1408
+ flagScreenCenter=True,
1409
+ pixmap=icons_path + 'python_warning.png',
1410
+ pixmapSize=96
1411
+ )
1412
+ elif FlagDisplay:
1413
+ warningDialog(self, Message="All installed packages are within the expected version range.", flagScreenCenter=True,pixmap=icons_path+'greenv.png')
1414
+
1415
+ # Update file if needed
1416
+ if Flag:
1417
+ with open(filename, "w") as f:
1418
+ for pkg, vmin, vmax, vcurr in zip(required_packages, vmin_list, vmax_list, vcurr_list):
1419
+ f.write(f"{pkg}\t{vmin}\t{vmax}\t{vcurr if vcurr else 0}\n")
1420
+
1421
+ return required_packages, vmin_list, vmax_list, vcurr_list