setiastrosuitepro 1.6.12__py3-none-any.whl → 1.7.3__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 (51) hide show
  1. setiastro/images/3dplanet.png +0 -0
  2. setiastro/images/TextureClarity.svg +56 -0
  3. setiastro/images/narrowbandnormalization.png +0 -0
  4. setiastro/images/planetarystacker.png +0 -0
  5. setiastro/saspro/__init__.py +9 -8
  6. setiastro/saspro/__main__.py +326 -285
  7. setiastro/saspro/_generated/build_info.py +2 -2
  8. setiastro/saspro/aberration_ai.py +128 -13
  9. setiastro/saspro/aberration_ai_preset.py +29 -3
  10. setiastro/saspro/astrospike_python.py +45 -3
  11. setiastro/saspro/blink_comparator_pro.py +116 -71
  12. setiastro/saspro/curve_editor_pro.py +72 -22
  13. setiastro/saspro/curves_preset.py +249 -47
  14. setiastro/saspro/doc_manager.py +4 -1
  15. setiastro/saspro/gui/main_window.py +326 -46
  16. setiastro/saspro/gui/mixins/file_mixin.py +41 -18
  17. setiastro/saspro/gui/mixins/menu_mixin.py +9 -0
  18. setiastro/saspro/gui/mixins/toolbar_mixin.py +123 -7
  19. setiastro/saspro/histogram.py +179 -7
  20. setiastro/saspro/imageops/narrowband_normalization.py +816 -0
  21. setiastro/saspro/imageops/serloader.py +1429 -0
  22. setiastro/saspro/layers.py +186 -10
  23. setiastro/saspro/layers_dock.py +198 -5
  24. setiastro/saspro/legacy/image_manager.py +10 -4
  25. setiastro/saspro/legacy/numba_utils.py +1 -1
  26. setiastro/saspro/live_stacking.py +24 -4
  27. setiastro/saspro/multiscale_decomp.py +30 -17
  28. setiastro/saspro/narrowband_normalization.py +1618 -0
  29. setiastro/saspro/planetprojection.py +3854 -0
  30. setiastro/saspro/remove_green.py +1 -1
  31. setiastro/saspro/resources.py +8 -0
  32. setiastro/saspro/rgbalign.py +456 -12
  33. setiastro/saspro/save_options.py +45 -13
  34. setiastro/saspro/ser_stack_config.py +102 -0
  35. setiastro/saspro/ser_stacker.py +2327 -0
  36. setiastro/saspro/ser_stacker_dialog.py +1865 -0
  37. setiastro/saspro/ser_tracking.py +228 -0
  38. setiastro/saspro/serviewer.py +1773 -0
  39. setiastro/saspro/sfcc.py +298 -64
  40. setiastro/saspro/shortcuts.py +14 -7
  41. setiastro/saspro/stacking_suite.py +21 -6
  42. setiastro/saspro/stat_stretch.py +179 -31
  43. setiastro/saspro/subwindow.py +38 -5
  44. setiastro/saspro/texture_clarity.py +593 -0
  45. setiastro/saspro/widgets/resource_monitor.py +122 -74
  46. {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/METADATA +3 -2
  47. {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/RECORD +51 -37
  48. {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/WHEEL +0 -0
  49. {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/entry_points.txt +0 -0
  50. {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/licenses/LICENSE +0 -0
  51. {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.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
 
@@ -414,6 +441,9 @@ def _init_splash():
414
441
  self.deleteLater()
415
442
 
416
443
  def start_fade_out(self):
444
+ if not _allow_window_opacity_effects():
445
+ self.finish()
446
+ return
417
447
  """Smoothly fade out the splash screen."""
418
448
  self._anim = QPropertyAnimation(self, b"windowOpacity")
419
449
  self._anim.setDuration(1000)
@@ -424,7 +454,9 @@ def _init_splash():
424
454
  self._anim.start()
425
455
 
426
456
  def start_fade_in(self):
427
- """Smoothly fade in the splash screen."""
457
+ if not _allow_window_opacity_effects():
458
+ self.setWindowOpacity(1.0)
459
+ return
428
460
  self.setWindowOpacity(0.0)
429
461
  self._anim = QPropertyAnimation(self, b"windowOpacity")
430
462
  self._anim.setDuration(800)
@@ -462,11 +494,6 @@ def _init_splash():
462
494
  _splash_initialized = True
463
495
 
464
496
 
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
497
  # =============================================================================
471
498
  # Now proceed with all the heavy imports (splash is visible)
472
499
  # =============================================================================
@@ -478,271 +505,279 @@ def _update_splash(msg: str, progress: int):
478
505
  _splash.setMessage(msg)
479
506
  _splash.setProgress(progress)
480
507
 
481
- _update_splash(QCoreApplication.translate("Splash", "Loading PyTorch runtime..."), 5)
508
+ def _bootstrap_imports():
509
+ """
510
+ Heavy imports + runtime bootstrap.
511
+ This must NOT run at module import time, only when main() is called.
512
+ """
513
+ global _imports_bootstrapped
514
+ if _imports_bootstrapped:
515
+ return
516
+ _imports_bootstrapped = True
517
+
518
+ # Make sure splash exists before we start doing heavy work
519
+ if not _splash_initialized:
520
+ _init_splash()
521
+
522
+ _update_splash(QCoreApplication.translate("Splash", "Loading PyTorch runtime..."), 5)
523
+
524
+ from setiastro.saspro.runtime_torch import (
525
+ add_runtime_to_sys_path,
526
+ _ban_shadow_torch_paths,
527
+ _purge_bad_torch_from_sysmodules,
528
+ )
529
+
530
+ add_runtime_to_sys_path(status_cb=lambda *_: None)
531
+ _ban_shadow_torch_paths(status_cb=lambda *_: None)
532
+ _purge_bad_torch_from_sysmodules(status_cb=lambda *_: None)
533
+
534
+ _update_splash(QCoreApplication.translate("Splash", "Loading standard libraries..."), 10)
535
+
536
+ # ----------------------------------------
537
+ # Standard library imports (consolidated)
538
+ # ----------------------------------------
539
+ import importlib
540
+ import json
541
+ import logging
542
+ import math
543
+ import multiprocessing
544
+ import os
545
+ import re
546
+ import sys
547
+ import threading
548
+ import time
549
+ import traceback
550
+ import warnings
551
+ import webbrowser
552
+
553
+ from collections import defaultdict
554
+ from datetime import datetime
555
+ from decimal import getcontext
556
+ from io import BytesIO
557
+ from itertools import combinations
558
+ from math import isnan
559
+ from pathlib import Path
560
+ from typing import Dict, List, Optional, Set, Tuple
561
+ from urllib.parse import quote, quote_plus
482
562
 
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
- )
563
+ _update_splash(QCoreApplication.translate("Splash", "Loading NumPy..."), 15)
488
564
 
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)
565
+ # ----------------------------------------
566
+ # Third-party imports
567
+ # ----------------------------------------
568
+ import numpy as np
492
569
 
493
- _update_splash(QCoreApplication.translate("Splash", "Loading standard libraries..."), 10)
570
+ _update_splash(QCoreApplication.translate("Splash", "Loading image libraries..."), 20)
571
+ from tifffile import imwrite
572
+ from setiastro.saspro.xisf import XISF
494
573
 
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."""
574
+ _update_splash(QCoreApplication.translate("Splash", "Configuring matplotlib..."), 25)
575
+ from setiastro.saspro.config_bootstrap import ensure_mpl_config_dir
576
+ _MPL_CFG_DIR = ensure_mpl_config_dir()
577
+
578
+ # Apply metadata patches for frozen builds
579
+ from setiastro.saspro.metadata_patcher import apply_metadata_patches
580
+ apply_metadata_patches()
581
+
582
+ warnings.filterwarnings(
583
+ "ignore",
584
+ message=r"Call to deprecated function \(or staticmethod\) _destroy\.",
585
+ category=DeprecationWarning,
586
+ )
587
+
588
+ os.environ['LIGHTKURVE_STYLE'] = 'default'
589
+
590
+ # ----------------------------------------
591
+ # Matplotlib configuration
592
+ # ----------------------------------------
593
+ import matplotlib
594
+ matplotlib.use("QtAgg")
595
+
596
+ # Configure stdout encoding
597
+ if (sys.stdout is not None) and (hasattr(sys.stdout, "reconfigure")):
598
+ sys.stdout.reconfigure(encoding='utf-8')
599
+
600
+ # --- Lazy imports for heavy dependencies (performance optimization) ---
601
+ # photutils: loaded on first use
565
602
  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."""
603
+ _photutils_isophote = None
604
+
605
+ def _get_photutils_isophote():
606
+ global _photutils_isophote
607
+ if _photutils_isophote is None:
608
+ try:
609
+ from photutils import isophote as _isophote_module
610
+ _photutils_isophote = _isophote_module
611
+ except Exception:
612
+ _photutils_isophote = False
613
+ return _photutils_isophote if _photutils_isophote else None
614
+
615
+ def get_Ellipse():
616
+ mod = _get_photutils_isophote()
617
+ return mod.Ellipse if mod else None
618
+
619
+ def get_EllipseGeometry():
620
+ mod = _get_photutils_isophote()
621
+ return mod.EllipseGeometry if mod else None
622
+
623
+ def get_build_ellipse_model():
624
+ mod = _get_photutils_isophote()
625
+ return mod.build_ellipse_model if mod else None
626
+
593
627
  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)
628
+ _lightkurve_module = None
629
+
630
+ def get_lightkurve():
631
+ global _lightkurve_module
632
+ if _lightkurve_module is None:
633
+ try:
634
+ import lightkurve as _lk
635
+ _lk.MPLSTYLE = None
636
+ _lightkurve_module = _lk
637
+ except Exception:
638
+ _lightkurve_module = False
639
+ return _lightkurve_module if _lightkurve_module else None
640
+
641
+ _update_splash(QCoreApplication.translate("Splash", "Loading UI utilities..."), 30)
642
+
643
+ from setiastro.saspro.widgets.common_utilities import (
644
+ AboutDialog,
645
+ ProjectSaveWorker as _ProjectSaveWorker,
646
+ DECOR_GLYPHS,
647
+ _strip_ui_decorations,
648
+ install_crash_handlers,
649
+ )
650
+
651
+ _update_splash(QCoreApplication.translate("Splash", "Loading reproject library..."), 35)
652
+
653
+ try:
654
+ from reproject import reproject_interp
655
+ except ImportError:
656
+ reproject_interp = None
657
+
658
+ _update_splash(QCoreApplication.translate("Splash", "Loading OpenCV..."), 40)
659
+
660
+ try:
661
+ import cv2
662
+ OPENCV_AVAILABLE = True
663
+ except ImportError:
664
+ OPENCV_AVAILABLE = False
665
+
666
+ _update_splash(QCoreApplication.translate("Splash", "Loading PyQt6 components..."), 45)
667
+
668
+ from PyQt6 import sip
669
+
670
+ from PyQt6.QtWidgets import (QDialog, QApplication, QMainWindow, QWidget, QHBoxLayout, QFileDialog, QMessageBox, QSizePolicy, QToolBar, QPushButton, QAbstractItemDelegate,
671
+ QLineEdit, QMenu, QListWidget, QListWidgetItem, QSplashScreen, QDockWidget, QListView, QCompleter, QMdiArea, QMdiSubWindow, QWidgetAction, QAbstractItemView,
672
+ QInputDialog, QVBoxLayout, QLabel, QCheckBox, QProgressBar, QProgressDialog, QGraphicsItem, QTabWidget, QTableWidget, QHeaderView, QTableWidgetItem, QToolButton, QPlainTextEdit
673
+ )
674
+
675
+ from PyQt6.QtGui import (QPixmap, QColor, QIcon, QKeySequence, QShortcut, QGuiApplication, QStandardItemModel, QStandardItem, QAction, QPalette, QBrush, QActionGroup, QDesktopServices, QFont, QTextCursor
676
+ )
677
+
678
+ from PyQt6.QtCore import (Qt, pyqtSignal, QTimer, QSize, QSignalBlocker, QModelIndex, QThread, QUrl, QSettings, QEvent, QByteArray, QObject,
679
+ QPropertyAnimation, QEasingCurve
680
+ )
681
+
682
+
683
+ from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
684
+
685
+ global BUILD_TIMESTAMP
686
+ try:
687
+ from setiastro.saspro._generated.build_info import BUILD_TIMESTAMP
688
+ except Exception:
689
+ BUILD_TIMESTAMP = "dev"
690
+
691
+ _update_splash(QCoreApplication.translate("Splash", "Loading resources..."), 50)
692
+
693
+ from setiastro.saspro.resources import (
694
+ icon_path, windowslogo_path, green_path, neutral_path, whitebalance_path,
695
+ morpho_path, clahe_path, starnet_path, staradd_path, LExtract_path,
696
+ LInsert_path, slot0_path, slot1_path, slot2_path, slot3_path, slot4_path,
697
+ rgbcombo_path, rgbextract_path, copyslot_path, graxperticon_path,
698
+ cropicon_path, openfile_path, abeicon_path, undoicon_path, redoicon_path,
699
+ blastericon_path, hdr_path, invert_path, fliphorizontal_path,
700
+ flipvertical_path, rotateclockwise_path, rotatecounterclockwise_path,
701
+ rotate180_path, maskcreate_path, maskapply_path, maskremove_path,
702
+ slot5_path, slot6_path, slot7_path, slot8_path, slot9_path, pixelmath_path,
703
+ histogram_path, mosaic_path, rescale_path, staralign_path, mask_path,
704
+ platesolve_path, psf_path, supernova_path, starregistration_path,
705
+ stacking_path, pedestal_icon_path, starspike_path, aperture_path,
706
+ jwstpupil_path, signature_icon_path, livestacking_path, hrdiagram_path,
707
+ convoicon_path, spcc_icon_path, sasp_data_path, exoicon_path, peeker_icon,
708
+ dse_icon_path, astrobin_filters_csv_path, isophote_path, statstretch_path,
709
+ starstretch_path, curves_path, disk_path, uhs_path, blink_path, ppp_path,
710
+ nbtorgb_path, freqsep_path, contsub_path, halo_path, cosmic_path,
711
+ satellite_path, imagecombine_path, wrench_path, eye_icon_path,
712
+ disk_icon_path, nuke_path, hubble_path, collage_path, annotated_path,
713
+ colorwheel_path, font_path, csv_icon_path, spinner_path, wims_path,
714
+ wimi_path, linearfit_path, debayer_path, aberration_path,
715
+ functionbundles_path, viewbundles_path, selectivecolor_path, rgbalign_path,
716
+ )
717
+
718
+ _update_splash(QCoreApplication.translate("Splash", "Configuring Qt message handler..."), 55)
719
+
720
+ from PyQt6.QtCore import qInstallMessageHandler, QtMsgType
721
+
722
+ def _qt_msg_handler(mode, ctx, msg):
723
+ lvl = {
724
+ QtMsgType.QtDebugMsg: logging.DEBUG,
725
+ QtMsgType.QtInfoMsg: logging.INFO,
726
+ QtMsgType.QtWarningMsg: logging.WARNING,
727
+ QtMsgType.QtCriticalMsg: logging.ERROR,
728
+ QtMsgType.QtFatalMsg: logging.CRITICAL,
729
+ }.get(mode, logging.ERROR)
730
+ logging.log(lvl, "Qt: %s (%s:%s)", msg, getattr(ctx, "file", "?"), getattr(ctx, "line", -1))
731
+
732
+ qInstallMessageHandler(_qt_msg_handler)
733
+
734
+ _update_splash(QCoreApplication.translate("Splash", "Loading MDI widgets..."), 60)
735
+
736
+ from setiastro.saspro.mdi_widgets import (
737
+ MdiArea, ViewLinkController, ConsoleListWidget, QtLogStream, _DocProxy,
738
+ ROLE_ACTION as _ROLE_ACTION,
739
+ )
740
+
741
+ from setiastro.saspro.main_helpers import (
742
+ safe_join_dir_and_name as _safe_join_dir_and_name,
743
+ normalize_save_path_chosen_filter as _normalize_save_path_chosen_filter,
744
+ display_name as _display_name,
745
+ best_doc_name as _best_doc_name,
746
+ doc_looks_like_table as _doc_looks_like_table,
747
+ is_alive as _is_alive,
748
+ safe_widget as _safe_widget,
749
+ )
750
+
751
+ from setiastro.saspro.file_utils import (
752
+ _normalize_ext,
753
+ _sanitize_filename,
754
+ _exts_from_filter,
755
+ REPLACE_SPACES_WITH_UNDERSCORES as _REPLACE_SPACES_WITH_UNDERSCORES,
756
+ WIN_RESERVED_NAMES as _WIN_RESERVED,
757
+ )
758
+
759
+ _update_splash(QCoreApplication.translate("Splash", "Loading main window module..."), 65)
760
+
761
+ from setiastro.saspro.gui.main_window import AstroSuiteProMainWindow
762
+
763
+ _update_splash(QCoreApplication.translate("Splash", "Modules loaded, finalizing..."), 70)
764
+
765
+ # Export things main() already relies on as globals (min disruption)
766
+ globals().update({
767
+ "logging": logging,
768
+ "multiprocessing": multiprocessing,
769
+ "warnings": warnings,
770
+ "QIcon": QIcon,
771
+ "QMessageBox": QMessageBox,
772
+ "QPropertyAnimation": QPropertyAnimation,
773
+ "QEasingCurve": QEasingCurve,
774
+ "windowslogo_path": windowslogo_path,
775
+ "icon_path": icon_path,
776
+ "install_crash_handlers": install_crash_handlers,
777
+ "AstroSuiteProMainWindow": AstroSuiteProMainWindow,
778
+ "BUILD_TIMESTAMP": BUILD_TIMESTAMP,
779
+ })
780
+
746
781
 
747
782
 
748
783
  def main():
@@ -759,7 +794,8 @@ def main():
759
794
  # Initialize splash if not already done
760
795
  if not _splash_initialized:
761
796
  _init_splash()
762
-
797
+
798
+ _bootstrap_imports()
763
799
  # Update splash with build info now that we have VERSION and BUILD_TIMESTAMP
764
800
  if _splash:
765
801
  _splash.setBuildInfo(VERSION, BUILD_TIMESTAMP)
@@ -882,29 +918,34 @@ def main():
882
918
  # --- Smooth Transition: App Fade In + Splash Fade Out ---
883
919
  # MITIGATION: Prevent "White Flash" on startup
884
920
  # 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
921
+ win.setStyleSheet("QMainWindow { background-color: #0F0F19; }")
887
922
  win.winId()
888
- # 3. Set opacity to 0
889
- win.setWindowOpacity(0.0)
890
-
923
+
924
+ if _allow_window_opacity_effects():
925
+ win.setWindowOpacity(0.0)
926
+
891
927
  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():
928
+
929
+ if _allow_window_opacity_effects():
930
+ anim_app = QPropertyAnimation(win, b"windowOpacity")
931
+ anim_app.setDuration(1200)
932
+ anim_app.setStartValue(0.0)
933
+ anim_app.setEndValue(1.0)
934
+ anim_app.setEasingCurve(QEasingCurve.Type.OutQuad)
935
+
936
+ def _on_fade_in_finished():
937
+ win.setStyleSheet("")
938
+ if hasattr(win, "on_fade_in_complete"):
939
+ win.on_fade_in_complete()
940
+
941
+ anim_app.finished.connect(_on_fade_in_finished)
942
+ anim_app.start()
943
+ else:
944
+ # No opacity animation on Wayland: just show immediately and clear the temp stylesheet
902
945
  win.setStyleSheet("")
903
946
  if hasattr(win, "on_fade_in_complete"):
904
947
  win.on_fade_in_complete()
905
-
906
- anim_app.finished.connect(_on_fade_in_finished)
907
- anim_app.start()
948
+
908
949
 
909
950
  # Start background Numba warmup after UI is visible
910
951
  try: