setiastrosuitepro 1.7.1.post2__py3-none-any.whl → 1.7.4__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 (62) hide show
  1. setiastro/images/3dplanet.png +0 -0
  2. setiastro/saspro/__init__.py +20 -8
  3. setiastro/saspro/__main__.py +349 -290
  4. setiastro/saspro/_generated/build_info.py +2 -2
  5. setiastro/saspro/abe.py +4 -4
  6. setiastro/saspro/autostretch.py +29 -18
  7. setiastro/saspro/doc_manager.py +4 -1
  8. setiastro/saspro/gui/main_window.py +46 -7
  9. setiastro/saspro/gui/mixins/file_mixin.py +6 -2
  10. setiastro/saspro/gui/mixins/menu_mixin.py +1 -0
  11. setiastro/saspro/gui/mixins/toolbar_mixin.py +9 -2
  12. setiastro/saspro/imageops/serloader.py +101 -17
  13. setiastro/saspro/layers.py +186 -10
  14. setiastro/saspro/layers_dock.py +198 -5
  15. setiastro/saspro/legacy/image_manager.py +10 -4
  16. setiastro/saspro/legacy/numba_utils.py +301 -119
  17. setiastro/saspro/numba_utils.py +998 -270
  18. setiastro/saspro/ops/settings.py +6 -6
  19. setiastro/saspro/pixelmath.py +1 -1
  20. setiastro/saspro/planetprojection.py +4059 -0
  21. setiastro/saspro/resources.py +2 -0
  22. setiastro/saspro/save_options.py +45 -13
  23. setiastro/saspro/ser_stack_config.py +21 -1
  24. setiastro/saspro/ser_stacker.py +8 -2
  25. setiastro/saspro/ser_stacker_dialog.py +37 -10
  26. setiastro/saspro/ser_tracking.py +57 -35
  27. setiastro/saspro/serviewer.py +164 -16
  28. setiastro/saspro/sfcc.py +14 -8
  29. setiastro/saspro/stacking_suite.py +292 -111
  30. setiastro/saspro/subwindow.py +64 -36
  31. setiastro/saspro/translations/all_source_strings.json +2 -2
  32. setiastro/saspro/translations/ar_translations.py +3 -3
  33. setiastro/saspro/translations/de_translations.py +2 -2
  34. setiastro/saspro/translations/es_translations.py +2 -2
  35. setiastro/saspro/translations/fr_translations.py +2 -2
  36. setiastro/saspro/translations/hi_translations.py +2 -2
  37. setiastro/saspro/translations/it_translations.py +2 -2
  38. setiastro/saspro/translations/ja_translations.py +2 -2
  39. setiastro/saspro/translations/pt_translations.py +2 -2
  40. setiastro/saspro/translations/ru_translations.py +2 -2
  41. setiastro/saspro/translations/saspro_ar.ts +2 -2
  42. setiastro/saspro/translations/saspro_de.ts +4 -4
  43. setiastro/saspro/translations/saspro_es.ts +2 -2
  44. setiastro/saspro/translations/saspro_fr.ts +2 -2
  45. setiastro/saspro/translations/saspro_hi.ts +2 -2
  46. setiastro/saspro/translations/saspro_it.ts +4 -4
  47. setiastro/saspro/translations/saspro_ja.ts +2 -2
  48. setiastro/saspro/translations/saspro_pt.ts +2 -2
  49. setiastro/saspro/translations/saspro_ru.ts +2 -2
  50. setiastro/saspro/translations/saspro_sw.ts +2 -2
  51. setiastro/saspro/translations/saspro_uk.ts +2 -2
  52. setiastro/saspro/translations/saspro_zh.ts +2 -2
  53. setiastro/saspro/translations/sw_translations.py +2 -2
  54. setiastro/saspro/translations/uk_translations.py +2 -2
  55. setiastro/saspro/translations/zh_translations.py +2 -2
  56. setiastro/saspro/window_shelf.py +62 -1
  57. {setiastrosuitepro-1.7.1.post2.dist-info → setiastrosuitepro-1.7.4.dist-info}/METADATA +1 -1
  58. {setiastrosuitepro-1.7.1.post2.dist-info → setiastrosuitepro-1.7.4.dist-info}/RECORD +62 -60
  59. {setiastrosuitepro-1.7.1.post2.dist-info → setiastrosuitepro-1.7.4.dist-info}/entry_points.txt +1 -1
  60. {setiastrosuitepro-1.7.1.post2.dist-info → setiastrosuitepro-1.7.4.dist-info}/WHEEL +0 -0
  61. {setiastrosuitepro-1.7.1.post2.dist-info → setiastrosuitepro-1.7.4.dist-info}/licenses/LICENSE +0 -0
  62. {setiastrosuitepro-1.7.1.post2.dist-info → setiastrosuitepro-1.7.4.dist-info}/licenses/license.txt +0 -0
@@ -12,6 +12,27 @@ called via the `main()` function when invoked as an entry point.
12
12
  import sys
13
13
  import os
14
14
 
15
+ _STARTUP_PROFILE = os.environ.get("SASPRO_STARTUP_PROFILE", "").strip().lower() in ("1","true","yes","on")
16
+
17
+
18
+ def _is_wayland_session() -> bool:
19
+ # Best-effort detection that does NOT require a QApplication instance
20
+ xdg = (os.environ.get("XDG_SESSION_TYPE") or "").lower()
21
+ if xdg == "wayland":
22
+ return True
23
+ # QT can still run wayland even if XDG_SESSION_TYPE isn't set
24
+ if os.environ.get("WAYLAND_DISPLAY"):
25
+ return True
26
+ return False
27
+
28
+ def _allow_window_opacity_effects() -> bool:
29
+ # User override: allow opacity effects even on Wayland if they want
30
+ if os.environ.get("SASPRO_ALLOW_OPACITY", "").strip().lower() in ("1","true","yes","on"):
31
+ return True
32
+ # Default: disable opacity effects on Wayland
33
+ return not _is_wayland_session()
34
+
35
+
15
36
  from pathlib import Path
16
37
 
17
38
  if sys.platform.startswith("win"):
@@ -24,7 +45,7 @@ if sys.platform.startswith("win"):
24
45
  pass
25
46
  os.environ["PATH"] = str(internal) + os.pathsep + os.environ.get("PATH", "")
26
47
 
27
- from PyQt6.QtCore import QCoreApplication
48
+
28
49
 
29
50
  # ---- Linux Qt stability guard (must run BEFORE any PyQt6 import) ----
30
51
  # Default behavior: DO NOT override Wayland.
@@ -41,6 +62,8 @@ if sys.platform.startswith("linux"):
41
62
  os.environ.setdefault("QT_OPENGL", "software")
42
63
  os.environ.setdefault("LIBGL_ALWAYS_SOFTWARE", "1")
43
64
 
65
+ from PyQt6.QtCore import QCoreApplication
66
+
44
67
  # Global variables for splash screen and app
45
68
  _splash = None
46
69
  _app = None
@@ -48,6 +71,10 @@ _app = None
48
71
  # Flag to track if splash was initialized
49
72
  _splash_initialized = False
50
73
 
74
+ # Flag to ensure heavy imports/bootstrap happens only once
75
+ _imports_bootstrapped = False
76
+
77
+
51
78
  from setiastro.saspro.versioning import get_app_version
52
79
  _EARLY_VERSION = get_app_version("setiastrosuitepro")
53
80
 
@@ -413,18 +440,31 @@ def _init_splash():
413
440
  self.close()
414
441
  self.deleteLater()
415
442
 
416
- def start_fade_out(self):
417
- """Smoothly fade out the splash screen."""
443
+ def start_fade_out(self, on_finished=None):
444
+ if not _allow_window_opacity_effects():
445
+ self.finish()
446
+ if callable(on_finished):
447
+ on_finished()
448
+ return
449
+
418
450
  self._anim = QPropertyAnimation(self, b"windowOpacity")
419
451
  self._anim.setDuration(1000)
420
452
  self._anim.setStartValue(1.0)
421
453
  self._anim.setEndValue(0.0)
422
454
  self._anim.setEasingCurve(QEasingCurve.Type.OutQuad)
423
- self._anim.finished.connect(self.finish)
455
+
456
+ def _done():
457
+ self.finish()
458
+ if callable(on_finished):
459
+ on_finished()
460
+
461
+ self._anim.finished.connect(_done)
424
462
  self._anim.start()
425
463
 
426
464
  def start_fade_in(self):
427
- """Smoothly fade in the splash screen."""
465
+ if not _allow_window_opacity_effects():
466
+ self.setWindowOpacity(1.0)
467
+ return
428
468
  self.setWindowOpacity(0.0)
429
469
  self._anim = QPropertyAnimation(self, b"windowOpacity")
430
470
  self._anim.setDuration(800)
@@ -462,11 +502,6 @@ def _init_splash():
462
502
  _splash_initialized = True
463
503
 
464
504
 
465
- # Initialize splash immediately before any heavy imports
466
- # This ensures the splash is visible while PyTorch, NumPy, etc. are loading
467
- _init_splash()
468
-
469
-
470
505
  # =============================================================================
471
506
  # Now proceed with all the heavy imports (splash is visible)
472
507
  # =============================================================================
@@ -478,271 +513,279 @@ def _update_splash(msg: str, progress: int):
478
513
  _splash.setMessage(msg)
479
514
  _splash.setProgress(progress)
480
515
 
481
- _update_splash(QCoreApplication.translate("Splash", "Loading PyTorch runtime..."), 5)
516
+ def _bootstrap_imports():
517
+ """
518
+ Heavy imports + runtime bootstrap.
519
+ This must NOT run at module import time, only when main() is called.
520
+ """
521
+ global _imports_bootstrapped
522
+ if _imports_bootstrapped:
523
+ return
524
+ _imports_bootstrapped = True
525
+
526
+ # Make sure splash exists before we start doing heavy work
527
+ if not _splash_initialized:
528
+ _init_splash()
482
529
 
483
- from setiastro.saspro.runtime_torch import (
484
- add_runtime_to_sys_path,
485
- _ban_shadow_torch_paths,
486
- _purge_bad_torch_from_sysmodules,
487
- )
530
+ _update_splash(QCoreApplication.translate("Splash", "Loading PyTorch runtime..."), 5)
531
+
532
+ from setiastro.saspro.runtime_torch import (
533
+ add_runtime_to_sys_path,
534
+ _ban_shadow_torch_paths,
535
+ _purge_bad_torch_from_sysmodules,
536
+ )
537
+
538
+ add_runtime_to_sys_path(status_cb=lambda *_: None)
539
+ _ban_shadow_torch_paths(status_cb=lambda *_: None)
540
+ _purge_bad_torch_from_sysmodules(status_cb=lambda *_: None)
541
+
542
+ _update_splash(QCoreApplication.translate("Splash", "Loading standard libraries..."), 10)
543
+
544
+ # ----------------------------------------
545
+ # Standard library imports (consolidated)
546
+ # ----------------------------------------
547
+ import importlib
548
+ import json
549
+ import logging
550
+ import math
551
+ import multiprocessing
552
+ import os
553
+ import re
554
+ import sys
555
+ import threading
556
+ import time
557
+ import traceback
558
+ import warnings
559
+ import webbrowser
560
+
561
+ from collections import defaultdict
562
+ from datetime import datetime
563
+ from decimal import getcontext
564
+ from io import BytesIO
565
+ from itertools import combinations
566
+ from math import isnan
567
+ from pathlib import Path
568
+ from typing import Dict, List, Optional, Set, Tuple
569
+ from urllib.parse import quote, quote_plus
488
570
 
489
- add_runtime_to_sys_path(status_cb=lambda *_: None)
490
- _ban_shadow_torch_paths(status_cb=lambda *_: None)
491
- _purge_bad_torch_from_sysmodules(status_cb=lambda *_: None)
571
+ _update_splash(QCoreApplication.translate("Splash", "Loading NumPy..."), 15)
492
572
 
493
- _update_splash(QCoreApplication.translate("Splash", "Loading standard libraries..."), 10)
573
+ # ----------------------------------------
574
+ # Third-party imports
575
+ # ----------------------------------------
576
+ import numpy as np
494
577
 
495
- # ----------------------------------------
496
- # Standard library imports (consolidated)
497
- # ----------------------------------------
498
- import importlib
499
- import json
500
- import logging
501
- import math
502
- import multiprocessing
503
- import os
504
- import re
505
- import sys
506
- import threading
507
- import time
508
- import traceback
509
- import warnings
510
- import webbrowser
511
-
512
- from collections import defaultdict
513
- from datetime import datetime
514
- from decimal import getcontext
515
- from io import BytesIO
516
- from itertools import combinations
517
- from math import isnan
518
- from pathlib import Path
519
- from typing import Dict, List, Optional, Set, Tuple
520
- from urllib.parse import quote, quote_plus
521
-
522
- _update_splash(QCoreApplication.translate("Splash", "Loading NumPy..."), 15)
523
-
524
- # ----------------------------------------
525
- # Third-party imports
526
- # ----------------------------------------
527
- import numpy as np
528
-
529
- _update_splash(QCoreApplication.translate("Splash", "Loading image libraries..."), 20)
530
- from tifffile import imwrite
531
- from setiastro.saspro.xisf import XISF
532
-
533
- _update_splash(QCoreApplication.translate("Splash", "Configuring matplotlib..."), 25)
534
- from setiastro.saspro.config_bootstrap import ensure_mpl_config_dir
535
- _MPL_CFG_DIR = ensure_mpl_config_dir()
536
-
537
- # Apply metadata patches for frozen builds
538
- from setiastro.saspro.metadata_patcher import apply_metadata_patches
539
- apply_metadata_patches()
540
- # ----------------------------------------
541
-
542
- warnings.filterwarnings(
543
- "ignore",
544
- message=r"Call to deprecated function \(or staticmethod\) _destroy\.",
545
- category=DeprecationWarning,
546
- )
547
-
548
- os.environ['LIGHTKURVE_STYLE'] = 'default'
549
-
550
- # ----------------------------------------
551
- # Matplotlib configuration
552
- # ----------------------------------------
553
- import matplotlib
554
- matplotlib.use("QtAgg")
555
-
556
- # Configure stdout encoding
557
- if (sys.stdout is not None) and (hasattr(sys.stdout, "reconfigure")):
558
- sys.stdout.reconfigure(encoding='utf-8')
559
-
560
- # --- Lazy imports for heavy dependencies (performance optimization) ---
561
- # photutils: loaded on first use
562
- _photutils_isophote = None
563
- def _get_photutils_isophote():
564
- """Lazy loader for photutils.isophote module."""
578
+ _update_splash(QCoreApplication.translate("Splash", "Loading image libraries..."), 20)
579
+ from tifffile import imwrite
580
+ from setiastro.saspro.xisf import XISF
581
+
582
+ _update_splash(QCoreApplication.translate("Splash", "Configuring matplotlib..."), 25)
583
+ from setiastro.saspro.config_bootstrap import ensure_mpl_config_dir
584
+ _MPL_CFG_DIR = ensure_mpl_config_dir()
585
+
586
+ # Apply metadata patches for frozen builds
587
+ from setiastro.saspro.metadata_patcher import apply_metadata_patches
588
+ apply_metadata_patches()
589
+
590
+ warnings.filterwarnings(
591
+ "ignore",
592
+ message=r"Call to deprecated function \(or staticmethod\) _destroy\.",
593
+ category=DeprecationWarning,
594
+ )
595
+
596
+ os.environ['LIGHTKURVE_STYLE'] = 'default'
597
+
598
+ # ----------------------------------------
599
+ # Matplotlib configuration
600
+ # ----------------------------------------
601
+ import matplotlib
602
+ matplotlib.use("QtAgg")
603
+
604
+ # Configure stdout encoding
605
+ if (sys.stdout is not None) and (hasattr(sys.stdout, "reconfigure")):
606
+ sys.stdout.reconfigure(encoding='utf-8')
607
+
608
+ # --- Lazy imports for heavy dependencies (performance optimization) ---
609
+ # photutils: loaded on first use
565
610
  global _photutils_isophote
566
- if _photutils_isophote is None:
567
- try:
568
- from photutils import isophote as _isophote_module
569
- _photutils_isophote = _isophote_module
570
- except Exception:
571
- _photutils_isophote = False # Mark as failed
572
- return _photutils_isophote if _photutils_isophote else None
573
-
574
- def get_Ellipse():
575
- """Get photutils.isophote.Ellipse, loading lazily."""
576
- mod = _get_photutils_isophote()
577
- return mod.Ellipse if mod else None
578
-
579
- def get_EllipseGeometry():
580
- """Get photutils.isophote.EllipseGeometry, loading lazily."""
581
- mod = _get_photutils_isophote()
582
- return mod.EllipseGeometry if mod else None
583
-
584
- def get_build_ellipse_model():
585
- """Get photutils.isophote.build_ellipse_model, loading lazily."""
586
- mod = _get_photutils_isophote()
587
- return mod.build_ellipse_model if mod else None
588
-
589
- # lightkurve: loaded on first use
590
- _lightkurve_module = None
591
- def get_lightkurve():
592
- """Lazy loader for lightkurve module."""
611
+ _photutils_isophote = None
612
+
613
+ def _get_photutils_isophote():
614
+ global _photutils_isophote
615
+ if _photutils_isophote is None:
616
+ try:
617
+ from photutils import isophote as _isophote_module
618
+ _photutils_isophote = _isophote_module
619
+ except Exception:
620
+ _photutils_isophote = False
621
+ return _photutils_isophote if _photutils_isophote else None
622
+
623
+ def get_Ellipse():
624
+ mod = _get_photutils_isophote()
625
+ return mod.Ellipse if mod else None
626
+
627
+ def get_EllipseGeometry():
628
+ mod = _get_photutils_isophote()
629
+ return mod.EllipseGeometry if mod else None
630
+
631
+ def get_build_ellipse_model():
632
+ mod = _get_photutils_isophote()
633
+ return mod.build_ellipse_model if mod else None
634
+
593
635
  global _lightkurve_module
594
- if _lightkurve_module is None:
595
- try:
596
- import lightkurve as _lk
597
- _lk.MPLSTYLE = None
598
- _lightkurve_module = _lk
599
- except Exception:
600
- _lightkurve_module = False # Mark as failed
601
- return _lightkurve_module if _lightkurve_module else None
602
- # --- End lazy imports ---
603
-
604
- _update_splash(QCoreApplication.translate("Splash", "Loading UI utilities..."), 30)
605
-
606
- # Shared UI utilities (avoiding code duplication)
607
- from setiastro.saspro.widgets.common_utilities import (
608
- AboutDialog,
609
- ProjectSaveWorker as _ProjectSaveWorker,
610
- DECOR_GLYPHS,
611
- _strip_ui_decorations,
612
- install_crash_handlers,
613
- )
614
-
615
- _update_splash(QCoreApplication.translate("Splash", "Loading reproject library..."), 35)
616
-
617
- # Reproject for WCS-based alignment
618
- try:
619
- from reproject import reproject_interp
620
- except ImportError:
621
- reproject_interp = None # fallback if not installed
622
-
623
- _update_splash(QCoreApplication.translate("Splash", "Loading OpenCV..."), 40)
624
-
625
- # OpenCV for transform estimation & warping
626
- try:
627
- import cv2
628
- OPENCV_AVAILABLE = True
629
- except ImportError:
630
- OPENCV_AVAILABLE = False
631
-
632
-
633
- _update_splash(QCoreApplication.translate("Splash", "Loading PyQt6 components..."), 45)
634
-
635
- #################################
636
- # PyQt6 Imports
637
- #################################
638
- from PyQt6 import sip
639
-
640
- # ----- QtWidgets -----
641
- from PyQt6.QtWidgets import (QDialog, QApplication, QMainWindow, QWidget, QHBoxLayout, QFileDialog, QMessageBox, QSizePolicy, QToolBar, QPushButton, QAbstractItemDelegate,
642
- QLineEdit, QMenu, QListWidget, QListWidgetItem, QSplashScreen, QDockWidget, QListView, QCompleter, QMdiArea, QMdiSubWindow, QWidgetAction, QAbstractItemView,
643
- QInputDialog, QVBoxLayout, QLabel, QCheckBox, QProgressBar, QProgressDialog, QGraphicsItem, QTabWidget, QTableWidget, QHeaderView, QTableWidgetItem, QToolButton, QPlainTextEdit
644
- )
645
-
646
- # ----- QtGui -----
647
- from PyQt6.QtGui import (QPixmap, QColor, QIcon, QKeySequence, QShortcut, QGuiApplication, QStandardItemModel, QStandardItem, QAction, QPalette, QBrush, QActionGroup, QDesktopServices, QFont, QTextCursor
648
- )
649
-
650
- # ----- QtCore -----
651
- from PyQt6.QtCore import (Qt, pyqtSignal, QCoreApplication, QTimer, QSize, QSignalBlocker, QModelIndex, QThread, QUrl, QSettings, QEvent, QByteArray, QObject,
652
- QPropertyAnimation, QEasingCurve
653
- )
654
-
655
- from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
656
-
657
-
658
- try:
659
- from setiastro.saspro._generated.build_info import BUILD_TIMESTAMP
660
- except Exception:
661
- # No generated build info → running from local source checkout
662
- BUILD_TIMESTAMP = "dev"
663
-
664
-
665
-
666
- _update_splash(QCoreApplication.translate("Splash", "Loading resources..."), 50)
667
-
668
- # Icon paths are now centralized in setiastro.saspro.resources module
669
- from setiastro.saspro.resources import (
670
- icon_path, windowslogo_path, green_path, neutral_path, whitebalance_path,
671
- morpho_path, clahe_path, starnet_path, staradd_path, LExtract_path,
672
- LInsert_path, slot0_path, slot1_path, slot2_path, slot3_path, slot4_path,
673
- rgbcombo_path, rgbextract_path, copyslot_path, graxperticon_path,
674
- cropicon_path, openfile_path, abeicon_path, undoicon_path, redoicon_path,
675
- blastericon_path, hdr_path, invert_path, fliphorizontal_path,
676
- flipvertical_path, rotateclockwise_path, rotatecounterclockwise_path,
677
- rotate180_path, maskcreate_path, maskapply_path, maskremove_path,
678
- slot5_path, slot6_path, slot7_path, slot8_path, slot9_path, pixelmath_path,
679
- histogram_path, mosaic_path, rescale_path, staralign_path, mask_path,
680
- platesolve_path, psf_path, supernova_path, starregistration_path,
681
- stacking_path, pedestal_icon_path, starspike_path, aperture_path,
682
- jwstpupil_path, signature_icon_path, livestacking_path, hrdiagram_path,
683
- convoicon_path, spcc_icon_path, sasp_data_path, exoicon_path, peeker_icon,
684
- dse_icon_path, astrobin_filters_csv_path, isophote_path, statstretch_path,
685
- starstretch_path, curves_path, disk_path, uhs_path, blink_path, ppp_path,
686
- nbtorgb_path, freqsep_path, contsub_path, halo_path, cosmic_path,
687
- satellite_path, imagecombine_path, wrench_path, eye_icon_path,
688
- disk_icon_path, nuke_path, hubble_path, collage_path, annotated_path,
689
- colorwheel_path, font_path, csv_icon_path, spinner_path, wims_path,
690
- wimi_path, linearfit_path, debayer_path, aberration_path,
691
- functionbundles_path, viewbundles_path, selectivecolor_path, rgbalign_path,
692
- )
693
-
694
-
695
- _update_splash(QCoreApplication.translate("Splash", "Configuring Qt message handler..."), 55)
696
-
697
- from PyQt6.QtCore import qInstallMessageHandler, QtMsgType
698
-
699
- def _qt_msg_handler(mode, ctx, msg):
700
- lvl = {
701
- QtMsgType.QtDebugMsg: logging.DEBUG,
702
- QtMsgType.QtInfoMsg: logging.INFO,
703
- QtMsgType.QtWarningMsg: logging.WARNING,
704
- QtMsgType.QtCriticalMsg: logging.ERROR,
705
- QtMsgType.QtFatalMsg: logging.CRITICAL,
706
- }.get(mode, logging.ERROR)
707
- logging.log(lvl, "Qt: %s (%s:%s)", msg, getattr(ctx, "file", "?"), getattr(ctx, "line", -1))
708
-
709
- qInstallMessageHandler(_qt_msg_handler)
710
-
711
- _update_splash(QCoreApplication.translate("Splash", "Loading MDI widgets..."), 60)
712
-
713
- # MDI widgets imported from setiastro.saspro.mdi_widgets
714
- from setiastro.saspro.mdi_widgets import (
715
- MdiArea, ViewLinkController, ConsoleListWidget, QtLogStream, _DocProxy,
716
- ROLE_ACTION as _ROLE_ACTION,
717
- )
718
-
719
- # Helper functions imported from setiastro.saspro.main_helpers
720
- from setiastro.saspro.main_helpers import (
721
- safe_join_dir_and_name as _safe_join_dir_and_name,
722
- normalize_save_path_chosen_filter as _normalize_save_path_chosen_filter,
723
- display_name as _display_name,
724
- best_doc_name as _best_doc_name,
725
- doc_looks_like_table as _doc_looks_like_table,
726
- is_alive as _is_alive,
727
- safe_widget as _safe_widget,
728
- )
729
-
730
- # AboutDialog, DECOR_GLYPHS, _strip_ui_decorations imported from setiastro.saspro.widgets.common_utilities
731
-
732
- # File utilities imported from setiastro.saspro.file_utils
733
- from setiastro.saspro.file_utils import (
734
- _normalize_ext,
735
- _sanitize_filename,
736
- _exts_from_filter,
737
- REPLACE_SPACES_WITH_UNDERSCORES as _REPLACE_SPACES_WITH_UNDERSCORES,
738
- WIN_RESERVED_NAMES as _WIN_RESERVED,
739
- )
740
-
741
- _update_splash(QCoreApplication.translate("Splash", "Loading main window module..."), 65)
742
-
743
- from setiastro.saspro.gui.main_window import AstroSuiteProMainWindow
744
-
745
- _update_splash(QCoreApplication.translate("Splash", "Modules loaded, finalizing..."), 70)
636
+ _lightkurve_module = None
637
+
638
+ def get_lightkurve():
639
+ global _lightkurve_module
640
+ if _lightkurve_module is None:
641
+ try:
642
+ import lightkurve as _lk
643
+ _lk.MPLSTYLE = None
644
+ _lightkurve_module = _lk
645
+ except Exception:
646
+ _lightkurve_module = False
647
+ return _lightkurve_module if _lightkurve_module else None
648
+
649
+ _update_splash(QCoreApplication.translate("Splash", "Loading UI utilities..."), 30)
650
+
651
+ from setiastro.saspro.widgets.common_utilities import (
652
+ AboutDialog,
653
+ ProjectSaveWorker as _ProjectSaveWorker,
654
+ DECOR_GLYPHS,
655
+ _strip_ui_decorations,
656
+ install_crash_handlers,
657
+ )
658
+
659
+ _update_splash(QCoreApplication.translate("Splash", "Loading reproject library..."), 35)
660
+
661
+ try:
662
+ from reproject import reproject_interp
663
+ except ImportError:
664
+ reproject_interp = None
665
+
666
+ _update_splash(QCoreApplication.translate("Splash", "Loading OpenCV..."), 40)
667
+
668
+ try:
669
+ import cv2
670
+ OPENCV_AVAILABLE = True
671
+ except ImportError:
672
+ OPENCV_AVAILABLE = False
673
+
674
+ _update_splash(QCoreApplication.translate("Splash", "Loading PyQt6 components..."), 45)
675
+
676
+ from PyQt6 import sip
677
+
678
+ from PyQt6.QtWidgets import (QDialog, QApplication, QMainWindow, QWidget, QHBoxLayout, QFileDialog, QMessageBox, QSizePolicy, QToolBar, QPushButton, QAbstractItemDelegate,
679
+ QLineEdit, QMenu, QListWidget, QListWidgetItem, QSplashScreen, QDockWidget, QListView, QCompleter, QMdiArea, QMdiSubWindow, QWidgetAction, QAbstractItemView,
680
+ QInputDialog, QVBoxLayout, QLabel, QCheckBox, QProgressBar, QProgressDialog, QGraphicsItem, QTabWidget, QTableWidget, QHeaderView, QTableWidgetItem, QToolButton, QPlainTextEdit
681
+ )
682
+
683
+ from PyQt6.QtGui import (QPixmap, QColor, QIcon, QKeySequence, QShortcut, QGuiApplication, QStandardItemModel, QStandardItem, QAction, QPalette, QBrush, QActionGroup, QDesktopServices, QFont, QTextCursor
684
+ )
685
+
686
+ from PyQt6.QtCore import (Qt, pyqtSignal, QTimer, QSize, QSignalBlocker, QModelIndex, QThread, QUrl, QSettings, QEvent, QByteArray, QObject,
687
+ QPropertyAnimation, QEasingCurve
688
+ )
689
+
690
+
691
+ from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
692
+
693
+ global BUILD_TIMESTAMP
694
+ try:
695
+ from setiastro.saspro._generated.build_info import BUILD_TIMESTAMP
696
+ except Exception:
697
+ BUILD_TIMESTAMP = "dev"
698
+
699
+ _update_splash(QCoreApplication.translate("Splash", "Loading resources..."), 50)
700
+
701
+ from setiastro.saspro.resources import (
702
+ icon_path, windowslogo_path, green_path, neutral_path, whitebalance_path,
703
+ morpho_path, clahe_path, starnet_path, staradd_path, LExtract_path,
704
+ LInsert_path, slot0_path, slot1_path, slot2_path, slot3_path, slot4_path,
705
+ rgbcombo_path, rgbextract_path, copyslot_path, graxperticon_path,
706
+ cropicon_path, openfile_path, abeicon_path, undoicon_path, redoicon_path,
707
+ blastericon_path, hdr_path, invert_path, fliphorizontal_path,
708
+ flipvertical_path, rotateclockwise_path, rotatecounterclockwise_path,
709
+ rotate180_path, maskcreate_path, maskapply_path, maskremove_path,
710
+ slot5_path, slot6_path, slot7_path, slot8_path, slot9_path, pixelmath_path,
711
+ histogram_path, mosaic_path, rescale_path, staralign_path, mask_path,
712
+ platesolve_path, psf_path, supernova_path, starregistration_path,
713
+ stacking_path, pedestal_icon_path, starspike_path, aperture_path,
714
+ jwstpupil_path, signature_icon_path, livestacking_path, hrdiagram_path,
715
+ convoicon_path, spcc_icon_path, sasp_data_path, exoicon_path, peeker_icon,
716
+ dse_icon_path, astrobin_filters_csv_path, isophote_path, statstretch_path,
717
+ starstretch_path, curves_path, disk_path, uhs_path, blink_path, ppp_path,
718
+ nbtorgb_path, freqsep_path, contsub_path, halo_path, cosmic_path,
719
+ satellite_path, imagecombine_path, wrench_path, eye_icon_path,
720
+ disk_icon_path, nuke_path, hubble_path, collage_path, annotated_path,
721
+ colorwheel_path, font_path, csv_icon_path, spinner_path, wims_path,
722
+ wimi_path, linearfit_path, debayer_path, aberration_path,
723
+ functionbundles_path, viewbundles_path, selectivecolor_path, rgbalign_path,
724
+ )
725
+
726
+ _update_splash(QCoreApplication.translate("Splash", "Configuring Qt message handler..."), 55)
727
+
728
+ from PyQt6.QtCore import qInstallMessageHandler, QtMsgType
729
+
730
+ def _qt_msg_handler(mode, ctx, msg):
731
+ lvl = {
732
+ QtMsgType.QtDebugMsg: logging.DEBUG,
733
+ QtMsgType.QtInfoMsg: logging.INFO,
734
+ QtMsgType.QtWarningMsg: logging.WARNING,
735
+ QtMsgType.QtCriticalMsg: logging.ERROR,
736
+ QtMsgType.QtFatalMsg: logging.CRITICAL,
737
+ }.get(mode, logging.ERROR)
738
+ logging.log(lvl, "Qt: %s (%s:%s)", msg, getattr(ctx, "file", "?"), getattr(ctx, "line", -1))
739
+
740
+ qInstallMessageHandler(_qt_msg_handler)
741
+
742
+ _update_splash(QCoreApplication.translate("Splash", "Loading MDI widgets..."), 60)
743
+
744
+ from setiastro.saspro.mdi_widgets import (
745
+ MdiArea, ViewLinkController, ConsoleListWidget, QtLogStream, _DocProxy,
746
+ ROLE_ACTION as _ROLE_ACTION,
747
+ )
748
+
749
+ from setiastro.saspro.main_helpers import (
750
+ safe_join_dir_and_name as _safe_join_dir_and_name,
751
+ normalize_save_path_chosen_filter as _normalize_save_path_chosen_filter,
752
+ display_name as _display_name,
753
+ best_doc_name as _best_doc_name,
754
+ doc_looks_like_table as _doc_looks_like_table,
755
+ is_alive as _is_alive,
756
+ safe_widget as _safe_widget,
757
+ )
758
+
759
+ from setiastro.saspro.file_utils import (
760
+ _normalize_ext,
761
+ _sanitize_filename,
762
+ _exts_from_filter,
763
+ REPLACE_SPACES_WITH_UNDERSCORES as _REPLACE_SPACES_WITH_UNDERSCORES,
764
+ WIN_RESERVED_NAMES as _WIN_RESERVED,
765
+ )
766
+
767
+ _update_splash(QCoreApplication.translate("Splash", "Loading main window module..."), 65)
768
+
769
+ from setiastro.saspro.gui.main_window import AstroSuiteProMainWindow
770
+
771
+ _update_splash(QCoreApplication.translate("Splash", "Modules loaded, finalizing..."), 70)
772
+
773
+ # Export things main() already relies on as globals (min disruption)
774
+ globals().update({
775
+ "logging": logging,
776
+ "multiprocessing": multiprocessing,
777
+ "warnings": warnings,
778
+ "QIcon": QIcon,
779
+ "QMessageBox": QMessageBox,
780
+ "QPropertyAnimation": QPropertyAnimation,
781
+ "QEasingCurve": QEasingCurve,
782
+ "windowslogo_path": windowslogo_path,
783
+ "icon_path": icon_path,
784
+ "install_crash_handlers": install_crash_handlers,
785
+ "AstroSuiteProMainWindow": AstroSuiteProMainWindow,
786
+ "BUILD_TIMESTAMP": BUILD_TIMESTAMP,
787
+ })
788
+
746
789
 
747
790
 
748
791
  def main():
@@ -755,11 +798,13 @@ def main():
755
798
  - When running as a module: python -m setiastro.saspro
756
799
  """
757
800
  global _splash, _app, _splash_initialized
801
+ from PyQt6.QtCore import QTimer
758
802
 
759
803
  # Initialize splash if not already done
760
804
  if not _splash_initialized:
761
805
  _init_splash()
762
-
806
+
807
+ _bootstrap_imports()
763
808
  # Update splash with build info now that we have VERSION and BUILD_TIMESTAMP
764
809
  if _splash:
765
810
  _splash.setBuildInfo(VERSION, BUILD_TIMESTAMP)
@@ -874,7 +919,16 @@ def main():
874
919
  version=VERSION,
875
920
  build_timestamp=BUILD_TIMESTAMP,
876
921
  )
877
-
922
+
923
+ def _kick_updates_after_splash():
924
+ try:
925
+ win.raise_()
926
+ win.activateWindow()
927
+ if win.settings.value("updates/check_on_startup", True, type=bool):
928
+ QTimer.singleShot(1000, win.check_for_updates_startup)
929
+ except Exception:
930
+ pass
931
+
878
932
  if _splash:
879
933
  _splash.setMessage(QCoreApplication.translate("Splash", "Showing main window..."))
880
934
  _splash.setProgress(95)
@@ -882,29 +936,34 @@ def main():
882
936
  # --- Smooth Transition: App Fade In + Splash Fade Out ---
883
937
  # MITIGATION: Prevent "White Flash" on startup
884
938
  # 1. Force a dark background immediately so if opacity lags, it's dark not white
885
- win.setStyleSheet("QMainWindow { background-color: #0F0F19; }")
886
- # 2. Ensure native window handle exists so setWindowOpacity works immediately
939
+ win.setStyleSheet("QMainWindow { background-color: #0F0F19; }")
887
940
  win.winId()
888
- # 3. Set opacity to 0
889
- win.setWindowOpacity(0.0)
890
-
941
+
942
+ if _allow_window_opacity_effects():
943
+ win.setWindowOpacity(0.0)
944
+
891
945
  win.show()
892
-
893
- # 1. Animate Main Window Fade In
894
- anim_app = QPropertyAnimation(win, b"windowOpacity")
895
- anim_app.setDuration(1200)
896
- anim_app.setStartValue(0.0)
897
- anim_app.setEndValue(1.0)
898
- anim_app.setEasingCurve(QEasingCurve.Type.OutQuad)
899
-
900
- # Cleanup temp stylesheet upon completion to avoid interfering with ThemeMixin
901
- def _on_fade_in_finished():
946
+
947
+ if _allow_window_opacity_effects():
948
+ anim_app = QPropertyAnimation(win, b"windowOpacity")
949
+ anim_app.setDuration(1200)
950
+ anim_app.setStartValue(0.0)
951
+ anim_app.setEndValue(1.0)
952
+ anim_app.setEasingCurve(QEasingCurve.Type.OutQuad)
953
+
954
+ def _on_fade_in_finished():
955
+ win.setStyleSheet("")
956
+ if hasattr(win, "on_fade_in_complete"):
957
+ win.on_fade_in_complete()
958
+
959
+ anim_app.finished.connect(_on_fade_in_finished)
960
+ anim_app.start()
961
+ else:
962
+ # No opacity animation on Wayland: just show immediately and clear the temp stylesheet
902
963
  win.setStyleSheet("")
903
964
  if hasattr(win, "on_fade_in_complete"):
904
965
  win.on_fade_in_complete()
905
-
906
- anim_app.finished.connect(_on_fade_in_finished)
907
- anim_app.start()
966
+
908
967
 
909
968
  # Start background Numba warmup after UI is visible
910
969
  try:
@@ -924,7 +983,7 @@ def main():
924
983
 
925
984
  # 2. Animate Splash Fade Out
926
985
  # Note: We do NOT use finish() directly here. The animation calls it when done.
927
- _splash.start_fade_out()
986
+ _splash.start_fade_out(on_finished=_kick_updates_after_splash)
928
987
 
929
988
  # NOTE: We keep a reference to _splash (global) so it doesn't get GC'd during animation.
930
989
  # It will deleteLater() itself.