setiastrosuitepro 1.6.5.post3__py3-none-any.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 (368) hide show
  1. setiastro/__init__.py +2 -0
  2. setiastro/data/SASP_data.fits +0 -0
  3. setiastro/data/catalogs/List_of_Galaxies_with_Distances_Gly.csv +488 -0
  4. setiastro/data/catalogs/astrobin_filters.csv +890 -0
  5. setiastro/data/catalogs/astrobin_filters_page1_local.csv +51 -0
  6. setiastro/data/catalogs/cali2.csv +63 -0
  7. setiastro/data/catalogs/cali2color.csv +65 -0
  8. setiastro/data/catalogs/celestial_catalog - original.csv +16471 -0
  9. setiastro/data/catalogs/celestial_catalog.csv +24031 -0
  10. setiastro/data/catalogs/detected_stars.csv +24784 -0
  11. setiastro/data/catalogs/fits_header_data.csv +46 -0
  12. setiastro/data/catalogs/test.csv +8 -0
  13. setiastro/data/catalogs/updated_celestial_catalog.csv +16471 -0
  14. setiastro/images/Astro_Spikes.png +0 -0
  15. setiastro/images/Background_startup.jpg +0 -0
  16. setiastro/images/HRDiagram.png +0 -0
  17. setiastro/images/LExtract.png +0 -0
  18. setiastro/images/LInsert.png +0 -0
  19. setiastro/images/Oxygenation-atm-2.svg.png +0 -0
  20. setiastro/images/RGB080604.png +0 -0
  21. setiastro/images/abeicon.png +0 -0
  22. setiastro/images/aberration.png +0 -0
  23. setiastro/images/andromedatry.png +0 -0
  24. setiastro/images/andromedatry_satellited.png +0 -0
  25. setiastro/images/annotated.png +0 -0
  26. setiastro/images/aperture.png +0 -0
  27. setiastro/images/astrosuite.ico +0 -0
  28. setiastro/images/astrosuite.png +0 -0
  29. setiastro/images/astrosuitepro.icns +0 -0
  30. setiastro/images/astrosuitepro.ico +0 -0
  31. setiastro/images/astrosuitepro.png +0 -0
  32. setiastro/images/background.png +0 -0
  33. setiastro/images/background2.png +0 -0
  34. setiastro/images/benchmark.png +0 -0
  35. setiastro/images/big_moon_stabilizer_timeline.png +0 -0
  36. setiastro/images/big_moon_stabilizer_timeline_clean.png +0 -0
  37. setiastro/images/blaster.png +0 -0
  38. setiastro/images/blink.png +0 -0
  39. setiastro/images/clahe.png +0 -0
  40. setiastro/images/collage.png +0 -0
  41. setiastro/images/colorwheel.png +0 -0
  42. setiastro/images/contsub.png +0 -0
  43. setiastro/images/convo.png +0 -0
  44. setiastro/images/copyslot.png +0 -0
  45. setiastro/images/cosmic.png +0 -0
  46. setiastro/images/cosmicsat.png +0 -0
  47. setiastro/images/crop1.png +0 -0
  48. setiastro/images/cropicon.png +0 -0
  49. setiastro/images/curves.png +0 -0
  50. setiastro/images/cvs.png +0 -0
  51. setiastro/images/debayer.png +0 -0
  52. setiastro/images/denoise_cnn_custom.png +0 -0
  53. setiastro/images/denoise_cnn_graph.png +0 -0
  54. setiastro/images/disk.png +0 -0
  55. setiastro/images/dse.png +0 -0
  56. setiastro/images/exoicon.png +0 -0
  57. setiastro/images/eye.png +0 -0
  58. setiastro/images/fliphorizontal.png +0 -0
  59. setiastro/images/flipvertical.png +0 -0
  60. setiastro/images/font.png +0 -0
  61. setiastro/images/freqsep.png +0 -0
  62. setiastro/images/functionbundle.png +0 -0
  63. setiastro/images/graxpert.png +0 -0
  64. setiastro/images/green.png +0 -0
  65. setiastro/images/gridicon.png +0 -0
  66. setiastro/images/halo.png +0 -0
  67. setiastro/images/hdr.png +0 -0
  68. setiastro/images/histogram.png +0 -0
  69. setiastro/images/hubble.png +0 -0
  70. setiastro/images/imagecombine.png +0 -0
  71. setiastro/images/invert.png +0 -0
  72. setiastro/images/isophote.png +0 -0
  73. setiastro/images/isophote_demo_figure.png +0 -0
  74. setiastro/images/isophote_demo_image.png +0 -0
  75. setiastro/images/isophote_demo_model.png +0 -0
  76. setiastro/images/isophote_demo_residual.png +0 -0
  77. setiastro/images/jwstpupil.png +0 -0
  78. setiastro/images/linearfit.png +0 -0
  79. setiastro/images/livestacking.png +0 -0
  80. setiastro/images/mask.png +0 -0
  81. setiastro/images/maskapply.png +0 -0
  82. setiastro/images/maskcreate.png +0 -0
  83. setiastro/images/maskremove.png +0 -0
  84. setiastro/images/morpho.png +0 -0
  85. setiastro/images/mosaic.png +0 -0
  86. setiastro/images/multiscale_decomp.png +0 -0
  87. setiastro/images/nbtorgb.png +0 -0
  88. setiastro/images/neutral.png +0 -0
  89. setiastro/images/nuke.png +0 -0
  90. setiastro/images/openfile.png +0 -0
  91. setiastro/images/pedestal.png +0 -0
  92. setiastro/images/pen.png +0 -0
  93. setiastro/images/pixelmath.png +0 -0
  94. setiastro/images/platesolve.png +0 -0
  95. setiastro/images/ppp.png +0 -0
  96. setiastro/images/pro.png +0 -0
  97. setiastro/images/project.png +0 -0
  98. setiastro/images/psf.png +0 -0
  99. setiastro/images/redo.png +0 -0
  100. setiastro/images/redoicon.png +0 -0
  101. setiastro/images/rescale.png +0 -0
  102. setiastro/images/rgbalign.png +0 -0
  103. setiastro/images/rgbcombo.png +0 -0
  104. setiastro/images/rgbextract.png +0 -0
  105. setiastro/images/rotate180.png +0 -0
  106. setiastro/images/rotatearbitrary.png +0 -0
  107. setiastro/images/rotateclockwise.png +0 -0
  108. setiastro/images/rotatecounterclockwise.png +0 -0
  109. setiastro/images/satellite.png +0 -0
  110. setiastro/images/script.png +0 -0
  111. setiastro/images/selectivecolor.png +0 -0
  112. setiastro/images/simbad.png +0 -0
  113. setiastro/images/slot0.png +0 -0
  114. setiastro/images/slot1.png +0 -0
  115. setiastro/images/slot2.png +0 -0
  116. setiastro/images/slot3.png +0 -0
  117. setiastro/images/slot4.png +0 -0
  118. setiastro/images/slot5.png +0 -0
  119. setiastro/images/slot6.png +0 -0
  120. setiastro/images/slot7.png +0 -0
  121. setiastro/images/slot8.png +0 -0
  122. setiastro/images/slot9.png +0 -0
  123. setiastro/images/spcc.png +0 -0
  124. setiastro/images/spin_precession_vs_lunar_distance.png +0 -0
  125. setiastro/images/spinner.gif +0 -0
  126. setiastro/images/stacking.png +0 -0
  127. setiastro/images/staradd.png +0 -0
  128. setiastro/images/staralign.png +0 -0
  129. setiastro/images/starnet.png +0 -0
  130. setiastro/images/starregistration.png +0 -0
  131. setiastro/images/starspike.png +0 -0
  132. setiastro/images/starstretch.png +0 -0
  133. setiastro/images/statstretch.png +0 -0
  134. setiastro/images/supernova.png +0 -0
  135. setiastro/images/uhs.png +0 -0
  136. setiastro/images/undoicon.png +0 -0
  137. setiastro/images/upscale.png +0 -0
  138. setiastro/images/viewbundle.png +0 -0
  139. setiastro/images/whitebalance.png +0 -0
  140. setiastro/images/wimi_icon_256x256.png +0 -0
  141. setiastro/images/wimilogo.png +0 -0
  142. setiastro/images/wims.png +0 -0
  143. setiastro/images/wrench_icon.png +0 -0
  144. setiastro/images/xisfliberator.png +0 -0
  145. setiastro/qml/ResourceMonitor.qml +126 -0
  146. setiastro/saspro/__init__.py +20 -0
  147. setiastro/saspro/__main__.py +958 -0
  148. setiastro/saspro/_generated/__init__.py +7 -0
  149. setiastro/saspro/_generated/build_info.py +3 -0
  150. setiastro/saspro/abe.py +1346 -0
  151. setiastro/saspro/abe_preset.py +196 -0
  152. setiastro/saspro/aberration_ai.py +698 -0
  153. setiastro/saspro/aberration_ai_preset.py +224 -0
  154. setiastro/saspro/accel_installer.py +218 -0
  155. setiastro/saspro/accel_workers.py +30 -0
  156. setiastro/saspro/add_stars.py +624 -0
  157. setiastro/saspro/astrobin_exporter.py +1010 -0
  158. setiastro/saspro/astrospike.py +153 -0
  159. setiastro/saspro/astrospike_python.py +1841 -0
  160. setiastro/saspro/autostretch.py +198 -0
  161. setiastro/saspro/backgroundneutral.py +611 -0
  162. setiastro/saspro/batch_convert.py +328 -0
  163. setiastro/saspro/batch_renamer.py +522 -0
  164. setiastro/saspro/blemish_blaster.py +491 -0
  165. setiastro/saspro/blink_comparator_pro.py +3149 -0
  166. setiastro/saspro/bundles.py +61 -0
  167. setiastro/saspro/bundles_dock.py +114 -0
  168. setiastro/saspro/cheat_sheet.py +213 -0
  169. setiastro/saspro/clahe.py +368 -0
  170. setiastro/saspro/comet_stacking.py +1442 -0
  171. setiastro/saspro/common_tr.py +107 -0
  172. setiastro/saspro/config.py +38 -0
  173. setiastro/saspro/config_bootstrap.py +40 -0
  174. setiastro/saspro/config_manager.py +316 -0
  175. setiastro/saspro/continuum_subtract.py +1617 -0
  176. setiastro/saspro/convo.py +1400 -0
  177. setiastro/saspro/convo_preset.py +414 -0
  178. setiastro/saspro/copyastro.py +190 -0
  179. setiastro/saspro/cosmicclarity.py +1589 -0
  180. setiastro/saspro/cosmicclarity_preset.py +407 -0
  181. setiastro/saspro/crop_dialog_pro.py +983 -0
  182. setiastro/saspro/crop_preset.py +189 -0
  183. setiastro/saspro/curve_editor_pro.py +2562 -0
  184. setiastro/saspro/curves_preset.py +375 -0
  185. setiastro/saspro/debayer.py +673 -0
  186. setiastro/saspro/debug_utils.py +29 -0
  187. setiastro/saspro/dnd_mime.py +35 -0
  188. setiastro/saspro/doc_manager.py +2664 -0
  189. setiastro/saspro/exoplanet_detector.py +2166 -0
  190. setiastro/saspro/file_utils.py +284 -0
  191. setiastro/saspro/fitsmodifier.py +748 -0
  192. setiastro/saspro/fix_bom.py +32 -0
  193. setiastro/saspro/free_torch_memory.py +48 -0
  194. setiastro/saspro/frequency_separation.py +1349 -0
  195. setiastro/saspro/function_bundle.py +1596 -0
  196. setiastro/saspro/generate_translations.py +3092 -0
  197. setiastro/saspro/ghs_dialog_pro.py +663 -0
  198. setiastro/saspro/ghs_preset.py +284 -0
  199. setiastro/saspro/graxpert.py +637 -0
  200. setiastro/saspro/graxpert_preset.py +287 -0
  201. setiastro/saspro/gui/__init__.py +0 -0
  202. setiastro/saspro/gui/main_window.py +8792 -0
  203. setiastro/saspro/gui/mixins/__init__.py +33 -0
  204. setiastro/saspro/gui/mixins/dock_mixin.py +375 -0
  205. setiastro/saspro/gui/mixins/file_mixin.py +450 -0
  206. setiastro/saspro/gui/mixins/geometry_mixin.py +503 -0
  207. setiastro/saspro/gui/mixins/header_mixin.py +441 -0
  208. setiastro/saspro/gui/mixins/mask_mixin.py +421 -0
  209. setiastro/saspro/gui/mixins/menu_mixin.py +390 -0
  210. setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
  211. setiastro/saspro/gui/mixins/toolbar_mixin.py +1619 -0
  212. setiastro/saspro/gui/mixins/update_mixin.py +323 -0
  213. setiastro/saspro/gui/mixins/view_mixin.py +435 -0
  214. setiastro/saspro/gui/statistics_dialog.py +47 -0
  215. setiastro/saspro/halobgon.py +488 -0
  216. setiastro/saspro/header_viewer.py +448 -0
  217. setiastro/saspro/headless_utils.py +88 -0
  218. setiastro/saspro/histogram.py +756 -0
  219. setiastro/saspro/history_explorer.py +941 -0
  220. setiastro/saspro/i18n.py +168 -0
  221. setiastro/saspro/image_combine.py +417 -0
  222. setiastro/saspro/image_peeker_pro.py +1604 -0
  223. setiastro/saspro/imageops/__init__.py +37 -0
  224. setiastro/saspro/imageops/mdi_snap.py +292 -0
  225. setiastro/saspro/imageops/scnr.py +36 -0
  226. setiastro/saspro/imageops/starbasedwhitebalance.py +210 -0
  227. setiastro/saspro/imageops/stretch.py +236 -0
  228. setiastro/saspro/isophote.py +1182 -0
  229. setiastro/saspro/layers.py +208 -0
  230. setiastro/saspro/layers_dock.py +714 -0
  231. setiastro/saspro/lazy_imports.py +193 -0
  232. setiastro/saspro/legacy/__init__.py +2 -0
  233. setiastro/saspro/legacy/image_manager.py +2360 -0
  234. setiastro/saspro/legacy/numba_utils.py +3676 -0
  235. setiastro/saspro/legacy/xisf.py +1213 -0
  236. setiastro/saspro/linear_fit.py +537 -0
  237. setiastro/saspro/live_stacking.py +1854 -0
  238. setiastro/saspro/log_bus.py +5 -0
  239. setiastro/saspro/logging_config.py +460 -0
  240. setiastro/saspro/luminancerecombine.py +510 -0
  241. setiastro/saspro/main_helpers.py +201 -0
  242. setiastro/saspro/mask_creation.py +1086 -0
  243. setiastro/saspro/masks_core.py +56 -0
  244. setiastro/saspro/mdi_widgets.py +353 -0
  245. setiastro/saspro/memory_utils.py +666 -0
  246. setiastro/saspro/metadata_patcher.py +75 -0
  247. setiastro/saspro/mfdeconv.py +3909 -0
  248. setiastro/saspro/mfdeconv_earlystop.py +71 -0
  249. setiastro/saspro/mfdeconvcudnn.py +3312 -0
  250. setiastro/saspro/mfdeconvsport.py +2459 -0
  251. setiastro/saspro/minorbodycatalog.py +567 -0
  252. setiastro/saspro/morphology.py +407 -0
  253. setiastro/saspro/multiscale_decomp.py +1747 -0
  254. setiastro/saspro/nbtorgb_stars.py +541 -0
  255. setiastro/saspro/numba_utils.py +3145 -0
  256. setiastro/saspro/numba_warmup.py +141 -0
  257. setiastro/saspro/ops/__init__.py +9 -0
  258. setiastro/saspro/ops/command_help_dialog.py +623 -0
  259. setiastro/saspro/ops/command_runner.py +217 -0
  260. setiastro/saspro/ops/commands.py +1594 -0
  261. setiastro/saspro/ops/script_editor.py +1105 -0
  262. setiastro/saspro/ops/scripts.py +1476 -0
  263. setiastro/saspro/ops/settings.py +637 -0
  264. setiastro/saspro/parallel_utils.py +554 -0
  265. setiastro/saspro/pedestal.py +121 -0
  266. setiastro/saspro/perfect_palette_picker.py +1105 -0
  267. setiastro/saspro/pipeline.py +110 -0
  268. setiastro/saspro/pixelmath.py +1604 -0
  269. setiastro/saspro/plate_solver.py +2445 -0
  270. setiastro/saspro/project_io.py +797 -0
  271. setiastro/saspro/psf_utils.py +136 -0
  272. setiastro/saspro/psf_viewer.py +549 -0
  273. setiastro/saspro/pyi_rthook_astroquery.py +95 -0
  274. setiastro/saspro/remove_green.py +331 -0
  275. setiastro/saspro/remove_stars.py +1599 -0
  276. setiastro/saspro/remove_stars_preset.py +446 -0
  277. setiastro/saspro/resources.py +503 -0
  278. setiastro/saspro/rgb_combination.py +208 -0
  279. setiastro/saspro/rgb_extract.py +19 -0
  280. setiastro/saspro/rgbalign.py +723 -0
  281. setiastro/saspro/runtime_imports.py +7 -0
  282. setiastro/saspro/runtime_torch.py +754 -0
  283. setiastro/saspro/save_options.py +73 -0
  284. setiastro/saspro/selective_color.py +1611 -0
  285. setiastro/saspro/sfcc.py +1472 -0
  286. setiastro/saspro/shortcuts.py +3116 -0
  287. setiastro/saspro/signature_insert.py +1102 -0
  288. setiastro/saspro/stacking_suite.py +19066 -0
  289. setiastro/saspro/star_alignment.py +7380 -0
  290. setiastro/saspro/star_alignment_preset.py +329 -0
  291. setiastro/saspro/star_metrics.py +49 -0
  292. setiastro/saspro/star_spikes.py +765 -0
  293. setiastro/saspro/star_stretch.py +507 -0
  294. setiastro/saspro/stat_stretch.py +538 -0
  295. setiastro/saspro/status_log_dock.py +78 -0
  296. setiastro/saspro/subwindow.py +3407 -0
  297. setiastro/saspro/supernovaasteroidhunter.py +1719 -0
  298. setiastro/saspro/swap_manager.py +134 -0
  299. setiastro/saspro/torch_backend.py +89 -0
  300. setiastro/saspro/torch_rejection.py +434 -0
  301. setiastro/saspro/translations/all_source_strings.json +4726 -0
  302. setiastro/saspro/translations/ar_translations.py +4096 -0
  303. setiastro/saspro/translations/de_translations.py +3728 -0
  304. setiastro/saspro/translations/es_translations.py +4169 -0
  305. setiastro/saspro/translations/fr_translations.py +4090 -0
  306. setiastro/saspro/translations/hi_translations.py +3803 -0
  307. setiastro/saspro/translations/integrate_translations.py +271 -0
  308. setiastro/saspro/translations/it_translations.py +4728 -0
  309. setiastro/saspro/translations/ja_translations.py +3834 -0
  310. setiastro/saspro/translations/pt_translations.py +3847 -0
  311. setiastro/saspro/translations/ru_translations.py +3082 -0
  312. setiastro/saspro/translations/saspro_ar.qm +0 -0
  313. setiastro/saspro/translations/saspro_ar.ts +16019 -0
  314. setiastro/saspro/translations/saspro_de.qm +0 -0
  315. setiastro/saspro/translations/saspro_de.ts +14548 -0
  316. setiastro/saspro/translations/saspro_es.qm +0 -0
  317. setiastro/saspro/translations/saspro_es.ts +16202 -0
  318. setiastro/saspro/translations/saspro_fr.qm +0 -0
  319. setiastro/saspro/translations/saspro_fr.ts +15870 -0
  320. setiastro/saspro/translations/saspro_hi.qm +0 -0
  321. setiastro/saspro/translations/saspro_hi.ts +14855 -0
  322. setiastro/saspro/translations/saspro_it.qm +0 -0
  323. setiastro/saspro/translations/saspro_it.ts +19046 -0
  324. setiastro/saspro/translations/saspro_ja.qm +0 -0
  325. setiastro/saspro/translations/saspro_ja.ts +14980 -0
  326. setiastro/saspro/translations/saspro_pt.qm +0 -0
  327. setiastro/saspro/translations/saspro_pt.ts +15024 -0
  328. setiastro/saspro/translations/saspro_ru.qm +0 -0
  329. setiastro/saspro/translations/saspro_ru.ts +11835 -0
  330. setiastro/saspro/translations/saspro_sw.qm +0 -0
  331. setiastro/saspro/translations/saspro_sw.ts +15237 -0
  332. setiastro/saspro/translations/saspro_uk.qm +0 -0
  333. setiastro/saspro/translations/saspro_uk.ts +15248 -0
  334. setiastro/saspro/translations/saspro_zh.qm +0 -0
  335. setiastro/saspro/translations/saspro_zh.ts +15289 -0
  336. setiastro/saspro/translations/sw_translations.py +3897 -0
  337. setiastro/saspro/translations/uk_translations.py +3929 -0
  338. setiastro/saspro/translations/zh_translations.py +3910 -0
  339. setiastro/saspro/versioning.py +77 -0
  340. setiastro/saspro/view_bundle.py +1558 -0
  341. setiastro/saspro/wavescale_hdr.py +645 -0
  342. setiastro/saspro/wavescale_hdr_preset.py +101 -0
  343. setiastro/saspro/wavescalede.py +680 -0
  344. setiastro/saspro/wavescalede_preset.py +230 -0
  345. setiastro/saspro/wcs_update.py +374 -0
  346. setiastro/saspro/whitebalance.py +513 -0
  347. setiastro/saspro/widgets/__init__.py +48 -0
  348. setiastro/saspro/widgets/common_utilities.py +306 -0
  349. setiastro/saspro/widgets/graphics_views.py +122 -0
  350. setiastro/saspro/widgets/image_utils.py +518 -0
  351. setiastro/saspro/widgets/minigame/game.js +991 -0
  352. setiastro/saspro/widgets/minigame/index.html +53 -0
  353. setiastro/saspro/widgets/minigame/style.css +241 -0
  354. setiastro/saspro/widgets/preview_dialogs.py +280 -0
  355. setiastro/saspro/widgets/resource_monitor.py +263 -0
  356. setiastro/saspro/widgets/spinboxes.py +290 -0
  357. setiastro/saspro/widgets/themed_buttons.py +13 -0
  358. setiastro/saspro/widgets/wavelet_utils.py +331 -0
  359. setiastro/saspro/wimi.py +7996 -0
  360. setiastro/saspro/wims.py +578 -0
  361. setiastro/saspro/window_shelf.py +185 -0
  362. setiastro/saspro/xisf.py +1213 -0
  363. setiastrosuitepro-1.6.5.post3.dist-info/METADATA +278 -0
  364. setiastrosuitepro-1.6.5.post3.dist-info/RECORD +368 -0
  365. setiastrosuitepro-1.6.5.post3.dist-info/WHEEL +4 -0
  366. setiastrosuitepro-1.6.5.post3.dist-info/entry_points.txt +6 -0
  367. setiastrosuitepro-1.6.5.post3.dist-info/licenses/LICENSE +674 -0
  368. setiastrosuitepro-1.6.5.post3.dist-info/licenses/license.txt +2580 -0
@@ -0,0 +1,958 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Seti Astro Suite Pro - Main Entry Point Module
4
+
5
+ This module contains the main application entry point logic.
6
+ It can be executed directly via `python -m setiastro.saspro` or
7
+ called via the `main()` function when invoked as an entry point.
8
+ """
9
+
10
+ # Show splash screen IMMEDIATELY before any heavy imports
11
+
12
+ import sys
13
+ import os
14
+
15
+ from pathlib import Path
16
+
17
+ if sys.platform.startswith("win"):
18
+ exe_dir = Path(sys.executable).resolve().parent
19
+ internal = exe_dir / "_internal"
20
+ if internal.is_dir():
21
+ try:
22
+ os.add_dll_directory(str(internal))
23
+ except Exception:
24
+ pass
25
+ os.environ["PATH"] = str(internal) + os.pathsep + os.environ.get("PATH", "")
26
+
27
+ from PyQt6.QtCore import QCoreApplication
28
+
29
+ # ---- Linux Qt stability guard (must run BEFORE any PyQt6 import) ----
30
+ # Default behavior: DO NOT override Wayland.
31
+ # If a user needs the "safe" path, they can opt-in by setting:
32
+ # SASPRO_QT_SAFE=1
33
+ #
34
+ # This avoids punishing all Wayland users for one bad driver/Qt stack.
35
+ if sys.platform.startswith("linux"):
36
+ if os.environ.get("SASPRO_QT_SAFE", "").strip() in ("1", "true", "yes", "on"):
37
+ # Prefer X11/xcb unless user explicitly set a platform plugin
38
+ os.environ.setdefault("QT_QPA_PLATFORM", "xcb")
39
+
40
+ # Prefer software GL unless user explicitly set something else
41
+ os.environ.setdefault("QT_OPENGL", "software")
42
+ os.environ.setdefault("LIBGL_ALWAYS_SOFTWARE", "1")
43
+
44
+ # Global variables for splash screen and app
45
+ _splash = None
46
+ _app = None
47
+
48
+ # Flag to track if splash was initialized
49
+ _splash_initialized = False
50
+
51
+ from setiastro.saspro.versioning import get_app_version
52
+ _EARLY_VERSION = get_app_version("setiastrosuitepro")
53
+
54
+ VERSION = _EARLY_VERSION
55
+
56
+ def _init_splash():
57
+ """Initialize the splash screen. Safe to call multiple times."""
58
+ global _splash, _app, _splash_initialized
59
+
60
+ if _splash_initialized:
61
+ return
62
+
63
+ # Minimal imports for splash screen
64
+ from PyQt6.QtWidgets import QApplication, QWidget
65
+ from PyQt6.QtCore import Qt, QCoreApplication, QRect, QPropertyAnimation, QEasingCurve
66
+ import time
67
+ from PyQt6.QtGui import QGuiApplication, QIcon, QPixmap, QColor, QPainter, QFont, QLinearGradient
68
+
69
+
70
+ # If we're forcing software OpenGL, do it *before* QApplication is created.
71
+ if sys.platform.startswith("linux"):
72
+ if os.environ.get("SASPRO_QT_SAFE", "").strip() in ("1", "true", "yes", "on"):
73
+ if os.environ.get("QT_OPENGL", "").lower() == "software":
74
+ try:
75
+ QCoreApplication.setAttribute(Qt.ApplicationAttribute.AA_UseSoftwareOpenGL, True)
76
+ except Exception:
77
+ pass
78
+
79
+ # Set application attributes before creating QApplication
80
+ try:
81
+ QGuiApplication.setHighDpiScaleFactorRoundingPolicy(
82
+ Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
83
+ )
84
+ except Exception:
85
+ pass
86
+
87
+ QCoreApplication.setAttribute(Qt.ApplicationAttribute.AA_DontShowIconsInMenus, False)
88
+ QCoreApplication.setOrganizationName("SetiAstro")
89
+ QCoreApplication.setOrganizationDomain("setiastrosuite.pro")
90
+ QCoreApplication.setApplicationName("Seti Astro Suite Pro")
91
+
92
+ # Create QApplication
93
+ _app = QApplication(sys.argv)
94
+
95
+ if sys.platform.startswith("linux"):
96
+ try:
97
+ print("Qt platform:", _app.platformName())
98
+ print("XDG_SESSION_TYPE:", os.environ.get("XDG_SESSION_TYPE"))
99
+ print("QT_QPA_PLATFORM:", os.environ.get("QT_QPA_PLATFORM"))
100
+ print("QT_OPENGL:", os.environ.get("QT_OPENGL"))
101
+ except Exception:
102
+ pass
103
+
104
+ # Determine icon paths early
105
+ # Determine icon paths early
106
+ def _find_icon_path():
107
+ """Legacy fallback if resources import fails."""
108
+ if hasattr(sys, '_MEIPASS'):
109
+ base = sys._MEIPASS
110
+ else:
111
+ try:
112
+ import setiastro
113
+ package_dir = os.path.dirname(os.path.abspath(setiastro.__file__))
114
+ package_parent = os.path.dirname(package_dir)
115
+ images_dir_installed = os.path.join(package_parent, 'images')
116
+ if os.path.exists(images_dir_installed):
117
+ base = package_parent
118
+ else:
119
+ base = os.path.dirname(
120
+ os.path.dirname(
121
+ os.path.dirname(
122
+ os.path.dirname(os.path.abspath(__file__))
123
+ )
124
+ )
125
+ )
126
+ except (ImportError, AttributeError):
127
+ base = os.path.dirname(
128
+ os.path.dirname(
129
+ os.path.dirname(
130
+ os.path.dirname(os.path.abspath(__file__))
131
+ )
132
+ )
133
+ )
134
+
135
+ candidates = [
136
+ os.path.join(base, "images", "astrosuitepro.png"),
137
+ os.path.join(base, "images", "astrosuitepro.ico"),
138
+ os.path.join(base, "images", "astrosuite.png"),
139
+ os.path.join(base, "images", "astrosuite.ico"),
140
+ ]
141
+ for p in candidates:
142
+ if os.path.exists(p):
143
+ return p
144
+ return "" # nothing found
145
+
146
+ # NEW: Prefer centralized resources resolver
147
+ try:
148
+ from setiastro.saspro.resources import icon_path, background_startup_path
149
+ _early_icon_path = icon_path
150
+ if not os.path.exists(_early_icon_path):
151
+ # fall back to legacy search if for some reason this is missing
152
+ _early_icon_path = _find_icon_path()
153
+
154
+ # Load startup background path
155
+ _startup_bg_path = background_startup_path
156
+ if not os.path.exists(_startup_bg_path):
157
+ _startup_bg_path = None
158
+
159
+ except Exception:
160
+ _early_icon_path = _find_icon_path()
161
+ _startup_bg_path = None
162
+
163
+
164
+ # =========================================================================
165
+ # PhotoshopStyleSplash - Custom splash screen widget
166
+ # =========================================================================
167
+ class _EarlySplash(QWidget):
168
+ """
169
+ A modern, Photoshop-style splash screen shown immediately on startup.
170
+ """
171
+ def __init__(self, logo_path: str):
172
+ super().__init__()
173
+ self._version = _EARLY_VERSION
174
+ self._build = ""
175
+ self.current_message = QCoreApplication.translate("Splash", "Starting...")
176
+ self.progress_value = 0
177
+
178
+ # Window setup
179
+ self.setWindowFlags(
180
+ Qt.WindowType.SplashScreen |
181
+ Qt.WindowType.FramelessWindowHint |
182
+ Qt.WindowType.WindowStaysOnTopHint
183
+ )
184
+ self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, False)
185
+ self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
186
+
187
+ # Splash dimensions
188
+ self.splash_width = 600
189
+ self.splash_height = 400
190
+ self.setFixedSize(self.splash_width, self.splash_height)
191
+
192
+ # Center on screen
193
+ screen = QGuiApplication.primaryScreen()
194
+ if screen:
195
+ screen_geo = screen.availableGeometry()
196
+ x = (screen_geo.width() - self.splash_width) // 2 + screen_geo.x()
197
+ y = (screen_geo.height() - self.splash_height) // 2 + screen_geo.y()
198
+ self.move(x, y)
199
+
200
+ # Load and scale logo
201
+ self.logo_pixmap = self._load_logo(logo_path)
202
+
203
+ # Load background image
204
+ self.bg_image_pixmap = QPixmap()
205
+ if _startup_bg_path:
206
+ self.bg_image_pixmap = QPixmap(_startup_bg_path)
207
+
208
+ # Fonts
209
+ self.title_font = QFont("Segoe UI", 28, QFont.Weight.Bold)
210
+ self.subtitle_font = QFont("Segoe UI", 11)
211
+ self.message_font = QFont("Segoe UI", 9)
212
+ self.copyright_font = QFont("Segoe UI", 8)
213
+
214
+ def _load_logo(self, path: str) -> QPixmap:
215
+ """Load the logo and scale appropriately."""
216
+ if not path or not os.path.exists(path):
217
+ return QPixmap()
218
+
219
+ ext = os.path.splitext(path)[1].lower()
220
+ if ext == ".ico":
221
+ ic = QIcon(path)
222
+ pm = ic.pixmap(256, 256)
223
+ if pm.isNull():
224
+ pm = QPixmap(path)
225
+ else:
226
+ pm = QPixmap(path)
227
+ if pm.isNull():
228
+ pm = QIcon(path).pixmap(256, 256)
229
+
230
+ if not pm.isNull():
231
+ pm = pm.scaled(
232
+ 180, 180,
233
+ Qt.AspectRatioMode.KeepAspectRatio,
234
+ Qt.TransformationMode.SmoothTransformation
235
+ )
236
+ return pm
237
+
238
+ def setMessage(self, message: str):
239
+ """Update the loading message."""
240
+ self.current_message = message
241
+ self.repaint()
242
+ if _app:
243
+ _app.processEvents()
244
+
245
+ def setProgress(self, value: int):
246
+ """Update progress (0-100) with smooth animation."""
247
+ target = max(0, min(100, value))
248
+ start = self.progress_value
249
+
250
+ # If jumping backwards or small change, just set it
251
+ if target <= start or (target - start) < 1:
252
+ self.progress_value = target
253
+ self.repaint()
254
+ if _app: _app.processEvents()
255
+ return
256
+
257
+ # Animate forward
258
+ steps = 15 # number of frames for the slide
259
+ # We want the total slide to take ~100-150ms max to feel responsive but smooth
260
+ dt = 0.005 # 5ms per frame
261
+
262
+ for i in range(1, steps + 1):
263
+ # Ease out interpolator
264
+ t = i / steps
265
+ # Quadratic ease out: f(t) = -t*(t-2)
266
+ factor = -t * (t - 2)
267
+
268
+ cur = start + (target - start) * factor
269
+ self.progress_value = cur
270
+ self.repaint()
271
+ if _app: _app.processEvents()
272
+ time.sleep(dt)
273
+
274
+ self.progress_value = target
275
+ self.repaint()
276
+ if _app:
277
+ _app.processEvents()
278
+
279
+ def setBuildInfo(self, version: str, build: str):
280
+ """Update version and build info once available."""
281
+ self._version = _EARLY_VERSION
282
+ self._build = build
283
+ self.repaint()
284
+
285
+ def paintEvent(self, event):
286
+ """Custom paint for the splash screen."""
287
+ painter = QPainter(self)
288
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing)
289
+ painter.setRenderHint(QPainter.RenderHint.TextAntialiasing)
290
+
291
+ w, h = self.splash_width, self.splash_height
292
+
293
+ # --- Background gradient (deep space theme) ---
294
+ gradient = QLinearGradient(0, 0, 0, h)
295
+ gradient.setColorAt(0.0, QColor(15, 15, 25))
296
+ gradient.setColorAt(0.5, QColor(25, 25, 45))
297
+ gradient.setColorAt(1.0, QColor(10, 10, 20))
298
+ painter.fillRect(0, 0, w, h, gradient)
299
+
300
+ # --- Background Image (Centered with Fade Out) ---
301
+ if not self.bg_image_pixmap.isNull():
302
+ # Create a temporary pixmap to handle the masking
303
+ temp = QPixmap(w, h)
304
+ temp.fill(Qt.GlobalColor.transparent)
305
+
306
+ ptmp = QPainter(temp)
307
+ ptmp.setRenderHint(QPainter.RenderHint.Antialiasing)
308
+ ptmp.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
309
+
310
+ # Scale image to cover the entire splash screen
311
+ scaled = self.bg_image_pixmap.scaled(
312
+ w, h,
313
+ Qt.AspectRatioMode.KeepAspectRatioByExpanding,
314
+ Qt.TransformationMode.SmoothTransformation
315
+ )
316
+
317
+ # Center the image
318
+ sx = (w - scaled.width()) // 2
319
+ sy = (h - scaled.height()) // 2
320
+ ptmp.drawPixmap(sx, sy, scaled)
321
+
322
+ # Apply Fade Out Mask (Gradient Alpha)
323
+ ptmp.setCompositionMode(QPainter.CompositionMode.CompositionMode_DestinationIn)
324
+ fade_gradient = QLinearGradient(0, 0, 0, h)
325
+ # Keep top half fully visible (subject to global opacity)
326
+ fade_gradient.setColorAt(0.0, QColor(0, 0, 0, 255))
327
+ fade_gradient.setColorAt(0.5, QColor(0, 0, 0, 255))
328
+ # Fade out completely at the bottom
329
+ fade_gradient.setColorAt(1.0, QColor(0, 0, 0, 0))
330
+ ptmp.fillRect(0, 0, w, h, fade_gradient)
331
+ ptmp.end()
332
+
333
+ # Draw combined result with 50% opacity
334
+ painter.save()
335
+ painter.setOpacity(0.25)
336
+ painter.drawPixmap(0, 0, temp)
337
+ painter.restore()
338
+
339
+ # --- Subtle border ---
340
+ painter.setPen(QColor(60, 60, 80))
341
+ painter.drawRect(0, 0, w - 1, h - 1)
342
+
343
+ # --- Logo (centered upper area) ---
344
+ if not self.logo_pixmap.isNull():
345
+ logo_x = (w - self.logo_pixmap.width()) // 2
346
+ logo_y = 40
347
+ painter.drawPixmap(logo_x, logo_y, self.logo_pixmap)
348
+
349
+ # --- Title ---
350
+ painter.setFont(self.title_font)
351
+ painter.setPen(QColor(255, 255, 255))
352
+ title_rect = QRect(0, 230, w, 40)
353
+ painter.drawText(title_rect, Qt.AlignmentFlag.AlignCenter, "Seti Astro Suite Pro")
354
+
355
+ # --- Subtitle with version ---
356
+ painter.setFont(self.subtitle_font)
357
+ painter.setPen(QColor(180, 180, 200))
358
+ subtitle_text = QCoreApplication.translate("Splash", "Version {0}").format(self._version)
359
+
360
+ if self._build:
361
+ if self._build == "dev":
362
+ # No build_info → running from source checkout
363
+ subtitle_text += QCoreApplication.translate("Splash", " • Running locally from source code")
364
+ else:
365
+ subtitle_text += QCoreApplication.translate("Splash", " • Build {0}").format(self._build)
366
+
367
+ subtitle_rect = QRect(0, 270, w, 25)
368
+ painter.drawText(subtitle_rect, Qt.AlignmentFlag.AlignCenter, subtitle_text)
369
+
370
+ # --- Progress bar ---
371
+ bar_margin = 50
372
+ bar_height = 4
373
+ bar_y = h - 70
374
+ bar_width = w - (bar_margin * 2)
375
+
376
+ painter.setPen(Qt.PenStyle.NoPen)
377
+ painter.setBrush(QColor(40, 40, 60))
378
+ painter.drawRoundedRect(bar_margin, bar_y, bar_width, bar_height, 2, 2)
379
+
380
+ if self.progress_value > 0:
381
+ fill_width = int(bar_width * self.progress_value / 100)
382
+ bar_gradient = QLinearGradient(bar_margin, 0, bar_margin + bar_width, 0)
383
+ bar_gradient.setColorAt(0.0, QColor(80, 140, 220))
384
+ bar_gradient.setColorAt(1.0, QColor(140, 180, 255))
385
+ painter.setBrush(bar_gradient)
386
+ painter.drawRoundedRect(bar_margin, bar_y, fill_width, bar_height, 2, 2)
387
+
388
+ # --- Loading message ---
389
+ painter.setFont(self.message_font)
390
+ painter.setPen(QColor(150, 150, 180))
391
+ msg_rect = QRect(bar_margin, bar_y + 10, bar_width, 20)
392
+ painter.drawText(msg_rect, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter,
393
+ self.current_message)
394
+
395
+ # --- Copyright ---
396
+ painter.setFont(self.copyright_font)
397
+ painter.setPen(QColor(100, 100, 130))
398
+ copyright_text = "© 2024-2025 Franklin Marek (Seti Astro) • All Rights Reserved"
399
+ copyright_rect = QRect(0, h - 30, w, 20)
400
+ painter.drawText(copyright_rect, Qt.AlignmentFlag.AlignCenter, copyright_text)
401
+
402
+ painter.end()
403
+
404
+ def finish(self):
405
+ """Hide and cleanup the splash."""
406
+ self.hide()
407
+ self.close()
408
+ self.deleteLater()
409
+
410
+ def start_fade_out(self):
411
+ """Smoothly fade out the splash screen."""
412
+ self._anim = QPropertyAnimation(self, b"windowOpacity")
413
+ self._anim.setDuration(1000)
414
+ self._anim.setStartValue(1.0)
415
+ self._anim.setEndValue(0.0)
416
+ self._anim.setEasingCurve(QEasingCurve.Type.OutQuad)
417
+ self._anim.finished.connect(self.finish)
418
+ self._anim.start()
419
+
420
+ def start_fade_in(self):
421
+ """Smoothly fade in the splash screen."""
422
+ self.setWindowOpacity(0.0)
423
+ self._anim = QPropertyAnimation(self, b"windowOpacity")
424
+ self._anim.setDuration(800)
425
+ self._anim.setStartValue(0.0)
426
+ self._anim.setEndValue(1.0)
427
+ self._anim.setEasingCurve(QEasingCurve.Type.InQuad)
428
+ self._anim.start()
429
+
430
+ # --- Show splash IMMEDIATELY ---
431
+ _splash = _EarlySplash(_early_icon_path)
432
+ _splash.start_fade_in()
433
+ _splash.show()
434
+
435
+ # Block briefly to allow fade-in to progress smoothly before heavy imports start
436
+ # We use a busy loop with processEvents to keep the UI responsive during fade
437
+ t_start = time.time()
438
+ while time.time() - t_start < 0.85: # slightly longer than animation
439
+ _app.processEvents()
440
+ if _splash.windowOpacity() >= 0.99:
441
+ break
442
+ time.sleep(0.01)
443
+
444
+ _splash.setMessage(QCoreApplication.translate("Splash", "Initializing Python runtime..."))
445
+ _splash.setProgress(2)
446
+ _app.processEvents()
447
+
448
+ # Load translation BEFORE any other widgets are created
449
+ try:
450
+ from setiastro.saspro.i18n import load_language, get_translations_dir
451
+ ok = load_language(app=_app)
452
+ except Exception as e:
453
+ print("i18n load failed:", repr(e))
454
+
455
+
456
+ _splash_initialized = True
457
+
458
+
459
+ # Initialize splash immediately before any heavy imports
460
+ # This ensures the splash is visible while PyTorch, NumPy, etc. are loading
461
+ _init_splash()
462
+
463
+
464
+ # =============================================================================
465
+ # Now proceed with all the heavy imports (splash is visible)
466
+ # =============================================================================
467
+
468
+ # Helper to update splash during imports
469
+ def _update_splash(msg: str, progress: int):
470
+ global _splash
471
+ if _splash is not None:
472
+ _splash.setMessage(msg)
473
+ _splash.setProgress(progress)
474
+
475
+ _update_splash(QCoreApplication.translate("Splash", "Loading PyTorch runtime..."), 5)
476
+
477
+ from setiastro.saspro.runtime_torch import (
478
+ add_runtime_to_sys_path,
479
+ _ban_shadow_torch_paths,
480
+ _purge_bad_torch_from_sysmodules,
481
+ )
482
+
483
+ add_runtime_to_sys_path(status_cb=lambda *_: None)
484
+ _ban_shadow_torch_paths(status_cb=lambda *_: None)
485
+ _purge_bad_torch_from_sysmodules(status_cb=lambda *_: None)
486
+
487
+ _update_splash(QCoreApplication.translate("Splash", "Loading standard libraries..."), 10)
488
+
489
+ # ----------------------------------------
490
+ # Standard library imports (consolidated)
491
+ # ----------------------------------------
492
+ import importlib
493
+ import json
494
+ import logging
495
+ import math
496
+ import multiprocessing
497
+ import os
498
+ import re
499
+ import sys
500
+ import threading
501
+ import time
502
+ import traceback
503
+ import warnings
504
+ import webbrowser
505
+
506
+ from collections import defaultdict
507
+ from datetime import datetime
508
+ from decimal import getcontext
509
+ from io import BytesIO
510
+ from itertools import combinations
511
+ from math import isnan
512
+ from pathlib import Path
513
+ from typing import Dict, List, Optional, Set, Tuple
514
+ from urllib.parse import quote, quote_plus
515
+
516
+ _update_splash(QCoreApplication.translate("Splash", "Loading NumPy..."), 15)
517
+
518
+ # ----------------------------------------
519
+ # Third-party imports
520
+ # ----------------------------------------
521
+ import numpy as np
522
+
523
+ _update_splash(QCoreApplication.translate("Splash", "Loading image libraries..."), 20)
524
+ from tifffile import imwrite
525
+ from setiastro.saspro.xisf import XISF
526
+
527
+ _update_splash(QCoreApplication.translate("Splash", "Configuring matplotlib..."), 25)
528
+ from setiastro.saspro.config_bootstrap import ensure_mpl_config_dir
529
+ _MPL_CFG_DIR = ensure_mpl_config_dir()
530
+
531
+ # Apply metadata patches for frozen builds
532
+ from setiastro.saspro.metadata_patcher import apply_metadata_patches
533
+ apply_metadata_patches()
534
+ # ----------------------------------------
535
+
536
+ warnings.filterwarnings(
537
+ "ignore",
538
+ message=r"Call to deprecated function \(or staticmethod\) _destroy\.",
539
+ category=DeprecationWarning,
540
+ )
541
+
542
+ os.environ['LIGHTKURVE_STYLE'] = 'default'
543
+
544
+ # ----------------------------------------
545
+ # Matplotlib configuration
546
+ # ----------------------------------------
547
+ import matplotlib
548
+ matplotlib.use("QtAgg")
549
+
550
+ # Configure stdout encoding
551
+ if (sys.stdout is not None) and (hasattr(sys.stdout, "reconfigure")):
552
+ sys.stdout.reconfigure(encoding='utf-8')
553
+
554
+ # --- Lazy imports for heavy dependencies (performance optimization) ---
555
+ # photutils: loaded on first use
556
+ _photutils_isophote = None
557
+ def _get_photutils_isophote():
558
+ """Lazy loader for photutils.isophote module."""
559
+ global _photutils_isophote
560
+ if _photutils_isophote is None:
561
+ try:
562
+ from photutils import isophote as _isophote_module
563
+ _photutils_isophote = _isophote_module
564
+ except Exception:
565
+ _photutils_isophote = False # Mark as failed
566
+ return _photutils_isophote if _photutils_isophote else None
567
+
568
+ def get_Ellipse():
569
+ """Get photutils.isophote.Ellipse, loading lazily."""
570
+ mod = _get_photutils_isophote()
571
+ return mod.Ellipse if mod else None
572
+
573
+ def get_EllipseGeometry():
574
+ """Get photutils.isophote.EllipseGeometry, loading lazily."""
575
+ mod = _get_photutils_isophote()
576
+ return mod.EllipseGeometry if mod else None
577
+
578
+ def get_build_ellipse_model():
579
+ """Get photutils.isophote.build_ellipse_model, loading lazily."""
580
+ mod = _get_photutils_isophote()
581
+ return mod.build_ellipse_model if mod else None
582
+
583
+ # lightkurve: loaded on first use
584
+ _lightkurve_module = None
585
+ def get_lightkurve():
586
+ """Lazy loader for lightkurve module."""
587
+ global _lightkurve_module
588
+ if _lightkurve_module is None:
589
+ try:
590
+ import lightkurve as _lk
591
+ _lk.MPLSTYLE = None
592
+ _lightkurve_module = _lk
593
+ except Exception:
594
+ _lightkurve_module = False # Mark as failed
595
+ return _lightkurve_module if _lightkurve_module else None
596
+ # --- End lazy imports ---
597
+
598
+ _update_splash(QCoreApplication.translate("Splash", "Loading UI utilities..."), 30)
599
+
600
+ # Shared UI utilities (avoiding code duplication)
601
+ from setiastro.saspro.widgets.common_utilities import (
602
+ AboutDialog,
603
+ ProjectSaveWorker as _ProjectSaveWorker,
604
+ DECOR_GLYPHS,
605
+ _strip_ui_decorations,
606
+ install_crash_handlers,
607
+ )
608
+
609
+ _update_splash(QCoreApplication.translate("Splash", "Loading reproject library..."), 35)
610
+
611
+ # Reproject for WCS-based alignment
612
+ try:
613
+ from reproject import reproject_interp
614
+ except ImportError:
615
+ reproject_interp = None # fallback if not installed
616
+
617
+ _update_splash(QCoreApplication.translate("Splash", "Loading OpenCV..."), 40)
618
+
619
+ # OpenCV for transform estimation & warping
620
+ try:
621
+ import cv2
622
+ OPENCV_AVAILABLE = True
623
+ except ImportError:
624
+ OPENCV_AVAILABLE = False
625
+
626
+
627
+ _update_splash(QCoreApplication.translate("Splash", "Loading PyQt6 components..."), 45)
628
+
629
+ #################################
630
+ # PyQt6 Imports
631
+ #################################
632
+ from PyQt6 import sip
633
+
634
+ # ----- QtWidgets -----
635
+ from PyQt6.QtWidgets import (QDialog, QApplication, QMainWindow, QWidget, QHBoxLayout, QFileDialog, QMessageBox, QSizePolicy, QToolBar, QPushButton, QAbstractItemDelegate,
636
+ QLineEdit, QMenu, QListWidget, QListWidgetItem, QSplashScreen, QDockWidget, QListView, QCompleter, QMdiArea, QMdiSubWindow, QWidgetAction, QAbstractItemView,
637
+ QInputDialog, QVBoxLayout, QLabel, QCheckBox, QProgressBar, QProgressDialog, QGraphicsItem, QTabWidget, QTableWidget, QHeaderView, QTableWidgetItem, QToolButton, QPlainTextEdit
638
+ )
639
+
640
+ # ----- QtGui -----
641
+ from PyQt6.QtGui import (QPixmap, QColor, QIcon, QKeySequence, QShortcut, QGuiApplication, QStandardItemModel, QStandardItem, QAction, QPalette, QBrush, QActionGroup, QDesktopServices, QFont, QTextCursor
642
+ )
643
+
644
+ # ----- QtCore -----
645
+ from PyQt6.QtCore import (Qt, pyqtSignal, QCoreApplication, QTimer, QSize, QSignalBlocker, QModelIndex, QThread, QUrl, QSettings, QEvent, QByteArray, QObject,
646
+ QPropertyAnimation, QEasingCurve
647
+ )
648
+
649
+ from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
650
+
651
+
652
+ try:
653
+ from setiastro.saspro._generated.build_info import BUILD_TIMESTAMP
654
+ except Exception:
655
+ # No generated build info → running from local source checkout
656
+ BUILD_TIMESTAMP = "dev"
657
+
658
+
659
+
660
+ _update_splash(QCoreApplication.translate("Splash", "Loading resources..."), 50)
661
+
662
+ # Icon paths are now centralized in setiastro.saspro.resources module
663
+ from setiastro.saspro.resources import (
664
+ icon_path, windowslogo_path, green_path, neutral_path, whitebalance_path,
665
+ morpho_path, clahe_path, starnet_path, staradd_path, LExtract_path,
666
+ LInsert_path, slot0_path, slot1_path, slot2_path, slot3_path, slot4_path,
667
+ rgbcombo_path, rgbextract_path, copyslot_path, graxperticon_path,
668
+ cropicon_path, openfile_path, abeicon_path, undoicon_path, redoicon_path,
669
+ blastericon_path, hdr_path, invert_path, fliphorizontal_path,
670
+ flipvertical_path, rotateclockwise_path, rotatecounterclockwise_path,
671
+ rotate180_path, maskcreate_path, maskapply_path, maskremove_path,
672
+ slot5_path, slot6_path, slot7_path, slot8_path, slot9_path, pixelmath_path,
673
+ histogram_path, mosaic_path, rescale_path, staralign_path, mask_path,
674
+ platesolve_path, psf_path, supernova_path, starregistration_path,
675
+ stacking_path, pedestal_icon_path, starspike_path, aperture_path,
676
+ jwstpupil_path, signature_icon_path, livestacking_path, hrdiagram_path,
677
+ convoicon_path, spcc_icon_path, sasp_data_path, exoicon_path, peeker_icon,
678
+ dse_icon_path, astrobin_filters_csv_path, isophote_path, statstretch_path,
679
+ starstretch_path, curves_path, disk_path, uhs_path, blink_path, ppp_path,
680
+ nbtorgb_path, freqsep_path, contsub_path, halo_path, cosmic_path,
681
+ satellite_path, imagecombine_path, wrench_path, eye_icon_path,
682
+ disk_icon_path, nuke_path, hubble_path, collage_path, annotated_path,
683
+ colorwheel_path, font_path, csv_icon_path, spinner_path, wims_path,
684
+ wimi_path, linearfit_path, debayer_path, aberration_path,
685
+ functionbundles_path, viewbundles_path, selectivecolor_path, rgbalign_path,
686
+ )
687
+
688
+
689
+ _update_splash(QCoreApplication.translate("Splash", "Configuring Qt message handler..."), 55)
690
+
691
+ from PyQt6.QtCore import qInstallMessageHandler, QtMsgType
692
+
693
+ def _qt_msg_handler(mode, ctx, msg):
694
+ lvl = {
695
+ QtMsgType.QtDebugMsg: logging.DEBUG,
696
+ QtMsgType.QtInfoMsg: logging.INFO,
697
+ QtMsgType.QtWarningMsg: logging.WARNING,
698
+ QtMsgType.QtCriticalMsg: logging.ERROR,
699
+ QtMsgType.QtFatalMsg: logging.CRITICAL,
700
+ }.get(mode, logging.ERROR)
701
+ logging.log(lvl, "Qt: %s (%s:%s)", msg, getattr(ctx, "file", "?"), getattr(ctx, "line", -1))
702
+
703
+ qInstallMessageHandler(_qt_msg_handler)
704
+
705
+ _update_splash(QCoreApplication.translate("Splash", "Loading MDI widgets..."), 60)
706
+
707
+ # MDI widgets imported from setiastro.saspro.mdi_widgets
708
+ from setiastro.saspro.mdi_widgets import (
709
+ MdiArea, ViewLinkController, ConsoleListWidget, QtLogStream, _DocProxy,
710
+ ROLE_ACTION as _ROLE_ACTION,
711
+ )
712
+
713
+ # Helper functions imported from setiastro.saspro.main_helpers
714
+ from setiastro.saspro.main_helpers import (
715
+ safe_join_dir_and_name as _safe_join_dir_and_name,
716
+ normalize_save_path_chosen_filter as _normalize_save_path_chosen_filter,
717
+ display_name as _display_name,
718
+ best_doc_name as _best_doc_name,
719
+ doc_looks_like_table as _doc_looks_like_table,
720
+ is_alive as _is_alive,
721
+ safe_widget as _safe_widget,
722
+ )
723
+
724
+ # AboutDialog, DECOR_GLYPHS, _strip_ui_decorations imported from setiastro.saspro.widgets.common_utilities
725
+
726
+ # File utilities imported from setiastro.saspro.file_utils
727
+ from setiastro.saspro.file_utils import (
728
+ _normalize_ext,
729
+ _sanitize_filename,
730
+ _exts_from_filter,
731
+ REPLACE_SPACES_WITH_UNDERSCORES as _REPLACE_SPACES_WITH_UNDERSCORES,
732
+ WIN_RESERVED_NAMES as _WIN_RESERVED,
733
+ )
734
+
735
+ _update_splash(QCoreApplication.translate("Splash", "Loading main window module..."), 65)
736
+
737
+ from setiastro.saspro.gui.main_window import AstroSuiteProMainWindow
738
+
739
+ _update_splash(QCoreApplication.translate("Splash", "Modules loaded, finalizing..."), 70)
740
+
741
+
742
+ def main():
743
+ """
744
+ Main entry point for Seti Astro Suite Pro.
745
+
746
+ This function can be called from:
747
+ - The package entry point (setiastrosuitepro command)
748
+ - Direct import and call
749
+ - When running as a module: python -m setiastro.saspro
750
+ """
751
+ global _splash, _app, _splash_initialized
752
+
753
+ # Initialize splash if not already done
754
+ if not _splash_initialized:
755
+ _init_splash()
756
+
757
+ # Update splash with build info now that we have VERSION and BUILD_TIMESTAMP
758
+ if _splash:
759
+ _splash.setBuildInfo(VERSION, BUILD_TIMESTAMP)
760
+ _splash.setMessage(QCoreApplication.translate("Splash", "Setting up logging..."))
761
+ _splash.setProgress(72)
762
+
763
+ # --- Logging (catch unhandled exceptions to a file) ---
764
+ import tempfile
765
+ from pathlib import Path
766
+
767
+ # Cross-platform log file location
768
+ def get_log_file_path():
769
+ """Get appropriate log file path for the current platform."""
770
+
771
+ if hasattr(sys, '_MEIPASS'):
772
+ # Running in PyInstaller bundle - use platform-appropriate user directory
773
+ if sys.platform.startswith('win'):
774
+ # Windows: %APPDATA%\SetiAstroSuitePro\logs\
775
+ log_dir = Path(os.path.expandvars('%APPDATA%')) / 'SetiAstroSuitePro' / 'logs'
776
+ elif sys.platform.startswith('darwin'):
777
+ # macOS: ~/Library/Logs/SetiAstroSuitePro/
778
+ log_dir = Path.home() / 'Library' / 'Logs' / 'SetiAstroSuitePro'
779
+ else:
780
+ # Linux: ~/.local/share/SetiAstroSuitePro/logs/
781
+ log_dir = Path.home() / '.local' / 'share' / 'SetiAstroSuitePro' / 'logs'
782
+
783
+ # Create directory if it doesn't exist
784
+ try:
785
+ log_dir.mkdir(parents=True, exist_ok=True)
786
+ log_file = log_dir / 'saspro.log'
787
+ except (OSError, PermissionError):
788
+ # Fallback to temp directory if user directory fails
789
+ log_file = Path(tempfile.gettempdir()) / 'saspro.log'
790
+ else:
791
+ # Development mode - use logs folder in project
792
+ log_dir = Path('logs')
793
+ try:
794
+ log_dir.mkdir(parents=True, exist_ok=True)
795
+ log_file = log_dir / 'saspro.log'
796
+ except (OSError, PermissionError):
797
+ log_file = Path('saspro.log')
798
+
799
+ return str(log_file)
800
+
801
+ # Configure logging with cross-platform path
802
+ log_file_path = get_log_file_path()
803
+
804
+ try:
805
+ logging.basicConfig(
806
+ filename=log_file_path,
807
+ level=logging.INFO,
808
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
809
+ filemode='a' # Append mode
810
+ )
811
+ logging.info(f"Logging to: {log_file_path}")
812
+ logging.info(f"Platform: {sys.platform}")
813
+ logging.info(f"PyInstaller bundle: {hasattr(sys, '_MEIPASS')}")
814
+ except Exception as e:
815
+ # Ultimate fallback - console only logging
816
+ logging.basicConfig(
817
+ level=logging.INFO,
818
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
819
+ handlers=[logging.StreamHandler(sys.stdout)]
820
+ )
821
+ print(f"Warning: Could not write to log file {log_file_path}: {e}")
822
+ print("Using console-only logging")
823
+
824
+
825
+ # Setup crash handlers and app icon
826
+ if _splash:
827
+ _splash.setMessage(QCoreApplication.translate("Splash", "Installing crash handlers..."))
828
+ _splash.setProgress(75)
829
+ install_crash_handlers(_app)
830
+ _app.setWindowIcon(QIcon(windowslogo_path if os.path.exists(windowslogo_path) else icon_path))
831
+
832
+ # --- Windows exe / multiprocessing friendly ---
833
+ if _splash:
834
+ _splash.setMessage(QCoreApplication.translate("Splash", "Configuring multiprocessing..."))
835
+ _splash.setProgress(78)
836
+ try:
837
+ multiprocessing.freeze_support()
838
+ try:
839
+ multiprocessing.set_start_method("spawn", force=True)
840
+ except RuntimeError:
841
+ # Already set in this interpreter
842
+ pass
843
+ except Exception:
844
+ logging.exception("Multiprocessing init failed (continuing).")
845
+
846
+ try:
847
+ if _splash:
848
+ _splash.setMessage(QCoreApplication.translate("Splash", "Loading image manager..."))
849
+ _splash.setProgress(80)
850
+ from setiastro.saspro.legacy.image_manager import ImageManager
851
+
852
+ if _splash:
853
+ _splash.setMessage(QCoreApplication.translate("Splash", "Suppressing warnings..."))
854
+ _splash.setProgress(82)
855
+ from matplotlib import MatplotlibDeprecationWarning
856
+ warnings.filterwarnings("ignore", category=MatplotlibDeprecationWarning)
857
+
858
+ if _splash:
859
+ _splash.setMessage(QCoreApplication.translate("Splash", "Creating image manager..."))
860
+ _splash.setProgress(85)
861
+ imgr = ImageManager(max_slots=100)
862
+
863
+ if _splash:
864
+ _splash.setMessage(QCoreApplication.translate("Splash", "Building main window..."))
865
+ _splash.setProgress(90)
866
+ win = AstroSuiteProMainWindow(
867
+ image_manager=imgr,
868
+ version=VERSION,
869
+ build_timestamp=BUILD_TIMESTAMP,
870
+ )
871
+
872
+ if _splash:
873
+ _splash.setMessage(QCoreApplication.translate("Splash", "Showing main window..."))
874
+ _splash.setProgress(95)
875
+
876
+ # --- Smooth Transition: App Fade In + Splash Fade Out ---
877
+ # MITIGATION: Prevent "White Flash" on startup
878
+ # 1. Force a dark background immediately so if opacity lags, it's dark not white
879
+ win.setStyleSheet("QMainWindow { background-color: #0F0F19; }")
880
+ # 2. Ensure native window handle exists so setWindowOpacity works immediately
881
+ win.winId()
882
+ # 3. Set opacity to 0
883
+ win.setWindowOpacity(0.0)
884
+
885
+ win.show()
886
+
887
+ # 1. Animate Main Window Fade In
888
+ anim_app = QPropertyAnimation(win, b"windowOpacity")
889
+ anim_app.setDuration(1200)
890
+ anim_app.setStartValue(0.0)
891
+ anim_app.setEndValue(1.0)
892
+ anim_app.setEasingCurve(QEasingCurve.Type.OutQuad)
893
+
894
+ # Cleanup temp stylesheet upon completion to avoid interfering with ThemeMixin
895
+ def _on_fade_in_finished():
896
+ win.setStyleSheet("")
897
+ if hasattr(win, "on_fade_in_complete"):
898
+ win.on_fade_in_complete()
899
+
900
+ anim_app.finished.connect(_on_fade_in_finished)
901
+ anim_app.start()
902
+
903
+ # Start background Numba warmup after UI is visible
904
+ try:
905
+ from setiastro.saspro.numba_warmup import start_background_warmup
906
+ start_background_warmup()
907
+ except Exception:
908
+ pass # Non-critical if warmup fails
909
+
910
+ if _splash:
911
+ _splash.setMessage(QCoreApplication.translate("Splash", "Ready!"))
912
+ _splash.setProgress(100)
913
+ _app.processEvents()
914
+
915
+ # Small delay to ensure "Ready!" is seen briefly before fade starts
916
+ import time
917
+ time.sleep(0.1)
918
+
919
+ # 2. Animate Splash Fade Out
920
+ # Note: We do NOT use finish() directly here. The animation calls it when done.
921
+ _splash.start_fade_out()
922
+
923
+ # NOTE: We keep a reference to _splash (global) so it doesn't get GC'd during animation.
924
+ # It will deleteLater() itself.
925
+
926
+ if BUILD_TIMESTAMP == "dev":
927
+ build_label = "running from local source code"
928
+ else:
929
+ build_label = f"build {BUILD_TIMESTAMP}"
930
+
931
+ print(f"Seti Astro Suite Pro v{VERSION} ({build_label}) up and running!")
932
+ sys.exit(_app.exec())
933
+
934
+ except Exception:
935
+ import traceback
936
+ if _splash:
937
+ try:
938
+ _splash.hide()
939
+ _splash.close()
940
+ _splash.deleteLater()
941
+ except Exception:
942
+ pass
943
+ tb = traceback.format_exc()
944
+ logging.error("Unhandled exception occurred\n%s", tb)
945
+ msg = QMessageBox(None)
946
+ msg.setIcon(QMessageBox.Icon.Critical)
947
+ msg.setWindowTitle(QCoreApplication.translate("Main", "Application Error"))
948
+ msg.setText(QCoreApplication.translate("Main", "An unexpected error occurred."))
949
+ msg.setInformativeText(tb.splitlines()[-1] if tb else "See details.")
950
+ msg.setDetailedText(tb)
951
+ msg.setStandardButtons(QMessageBox.StandardButton.Ok)
952
+ msg.exec()
953
+ sys.exit(1)
954
+
955
+
956
+ # When run as a module, execute main()
957
+ if __name__ == "__main__":
958
+ main()