setiastrosuitepro 1.6.0__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.
Potentially problematic release.
This version of setiastrosuitepro might be problematic. Click here for more details.
- setiastro/__init__.py +2 -0
- setiastro/saspro/__init__.py +20 -0
- setiastro/saspro/__main__.py +784 -0
- setiastro/saspro/_generated/__init__.py +7 -0
- setiastro/saspro/_generated/build_info.py +2 -0
- setiastro/saspro/abe.py +1295 -0
- setiastro/saspro/abe_preset.py +196 -0
- setiastro/saspro/aberration_ai.py +694 -0
- setiastro/saspro/aberration_ai_preset.py +224 -0
- setiastro/saspro/accel_installer.py +218 -0
- setiastro/saspro/accel_workers.py +30 -0
- setiastro/saspro/add_stars.py +621 -0
- setiastro/saspro/astrobin_exporter.py +1007 -0
- setiastro/saspro/astrospike.py +153 -0
- setiastro/saspro/astrospike_python.py +1839 -0
- setiastro/saspro/autostretch.py +196 -0
- setiastro/saspro/backgroundneutral.py +560 -0
- setiastro/saspro/batch_convert.py +325 -0
- setiastro/saspro/batch_renamer.py +519 -0
- setiastro/saspro/blemish_blaster.py +488 -0
- setiastro/saspro/blink_comparator_pro.py +2923 -0
- setiastro/saspro/bundles.py +61 -0
- setiastro/saspro/bundles_dock.py +114 -0
- setiastro/saspro/cheat_sheet.py +168 -0
- setiastro/saspro/clahe.py +342 -0
- setiastro/saspro/comet_stacking.py +1377 -0
- setiastro/saspro/config.py +38 -0
- setiastro/saspro/config_bootstrap.py +40 -0
- setiastro/saspro/config_manager.py +316 -0
- setiastro/saspro/continuum_subtract.py +1617 -0
- setiastro/saspro/convo.py +1397 -0
- setiastro/saspro/convo_preset.py +414 -0
- setiastro/saspro/copyastro.py +187 -0
- setiastro/saspro/cosmicclarity.py +1564 -0
- setiastro/saspro/cosmicclarity_preset.py +407 -0
- setiastro/saspro/crop_dialog_pro.py +948 -0
- setiastro/saspro/crop_preset.py +189 -0
- setiastro/saspro/curve_editor_pro.py +2544 -0
- setiastro/saspro/curves_preset.py +375 -0
- setiastro/saspro/debayer.py +670 -0
- setiastro/saspro/debug_utils.py +29 -0
- setiastro/saspro/dnd_mime.py +35 -0
- setiastro/saspro/doc_manager.py +2634 -0
- setiastro/saspro/exoplanet_detector.py +2166 -0
- setiastro/saspro/file_utils.py +284 -0
- setiastro/saspro/fitsmodifier.py +744 -0
- setiastro/saspro/free_torch_memory.py +48 -0
- setiastro/saspro/frequency_separation.py +1343 -0
- setiastro/saspro/function_bundle.py +1594 -0
- setiastro/saspro/ghs_dialog_pro.py +660 -0
- setiastro/saspro/ghs_preset.py +284 -0
- setiastro/saspro/graxpert.py +634 -0
- setiastro/saspro/graxpert_preset.py +287 -0
- setiastro/saspro/gui/__init__.py +0 -0
- setiastro/saspro/gui/main_window.py +8494 -0
- setiastro/saspro/gui/mixins/__init__.py +33 -0
- setiastro/saspro/gui/mixins/dock_mixin.py +263 -0
- setiastro/saspro/gui/mixins/file_mixin.py +445 -0
- setiastro/saspro/gui/mixins/geometry_mixin.py +403 -0
- setiastro/saspro/gui/mixins/header_mixin.py +441 -0
- setiastro/saspro/gui/mixins/mask_mixin.py +421 -0
- setiastro/saspro/gui/mixins/menu_mixin.py +361 -0
- setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
- setiastro/saspro/gui/mixins/toolbar_mixin.py +1324 -0
- setiastro/saspro/gui/mixins/update_mixin.py +309 -0
- setiastro/saspro/gui/mixins/view_mixin.py +435 -0
- setiastro/saspro/halobgon.py +462 -0
- setiastro/saspro/header_viewer.py +445 -0
- setiastro/saspro/headless_utils.py +88 -0
- setiastro/saspro/histogram.py +753 -0
- setiastro/saspro/history_explorer.py +939 -0
- setiastro/saspro/image_combine.py +414 -0
- setiastro/saspro/image_peeker_pro.py +1596 -0
- setiastro/saspro/imageops/__init__.py +37 -0
- setiastro/saspro/imageops/mdi_snap.py +292 -0
- setiastro/saspro/imageops/scnr.py +36 -0
- setiastro/saspro/imageops/starbasedwhitebalance.py +210 -0
- setiastro/saspro/imageops/stretch.py +244 -0
- setiastro/saspro/isophote.py +1179 -0
- setiastro/saspro/layers.py +208 -0
- setiastro/saspro/layers_dock.py +714 -0
- setiastro/saspro/lazy_imports.py +193 -0
- setiastro/saspro/legacy/__init__.py +2 -0
- setiastro/saspro/legacy/image_manager.py +2226 -0
- setiastro/saspro/legacy/numba_utils.py +3659 -0
- setiastro/saspro/legacy/xisf.py +1071 -0
- setiastro/saspro/linear_fit.py +534 -0
- setiastro/saspro/live_stacking.py +1830 -0
- setiastro/saspro/log_bus.py +5 -0
- setiastro/saspro/logging_config.py +460 -0
- setiastro/saspro/luminancerecombine.py +309 -0
- setiastro/saspro/main_helpers.py +201 -0
- setiastro/saspro/mask_creation.py +928 -0
- setiastro/saspro/masks_core.py +56 -0
- setiastro/saspro/mdi_widgets.py +353 -0
- setiastro/saspro/memory_utils.py +666 -0
- setiastro/saspro/metadata_patcher.py +75 -0
- setiastro/saspro/mfdeconv.py +3826 -0
- setiastro/saspro/mfdeconv_earlystop.py +71 -0
- setiastro/saspro/mfdeconvcudnn.py +3263 -0
- setiastro/saspro/mfdeconvsport.py +2382 -0
- setiastro/saspro/minorbodycatalog.py +567 -0
- setiastro/saspro/morphology.py +382 -0
- setiastro/saspro/multiscale_decomp.py +1290 -0
- setiastro/saspro/nbtorgb_stars.py +531 -0
- setiastro/saspro/numba_utils.py +3044 -0
- setiastro/saspro/numba_warmup.py +141 -0
- setiastro/saspro/ops/__init__.py +9 -0
- setiastro/saspro/ops/command_help_dialog.py +623 -0
- setiastro/saspro/ops/command_runner.py +217 -0
- setiastro/saspro/ops/commands.py +1594 -0
- setiastro/saspro/ops/script_editor.py +1102 -0
- setiastro/saspro/ops/scripts.py +1413 -0
- setiastro/saspro/ops/settings.py +560 -0
- setiastro/saspro/parallel_utils.py +554 -0
- setiastro/saspro/pedestal.py +121 -0
- setiastro/saspro/perfect_palette_picker.py +1053 -0
- setiastro/saspro/pipeline.py +110 -0
- setiastro/saspro/pixelmath.py +1600 -0
- setiastro/saspro/plate_solver.py +2435 -0
- setiastro/saspro/project_io.py +797 -0
- setiastro/saspro/psf_utils.py +136 -0
- setiastro/saspro/psf_viewer.py +549 -0
- setiastro/saspro/pyi_rthook_astroquery.py +95 -0
- setiastro/saspro/remove_green.py +314 -0
- setiastro/saspro/remove_stars.py +1625 -0
- setiastro/saspro/remove_stars_preset.py +404 -0
- setiastro/saspro/resources.py +472 -0
- setiastro/saspro/rgb_combination.py +207 -0
- setiastro/saspro/rgb_extract.py +19 -0
- setiastro/saspro/rgbalign.py +723 -0
- setiastro/saspro/runtime_imports.py +7 -0
- setiastro/saspro/runtime_torch.py +754 -0
- setiastro/saspro/save_options.py +72 -0
- setiastro/saspro/selective_color.py +1552 -0
- setiastro/saspro/sfcc.py +1425 -0
- setiastro/saspro/shortcuts.py +2807 -0
- setiastro/saspro/signature_insert.py +1099 -0
- setiastro/saspro/stacking_suite.py +17712 -0
- setiastro/saspro/star_alignment.py +7420 -0
- setiastro/saspro/star_alignment_preset.py +329 -0
- setiastro/saspro/star_metrics.py +49 -0
- setiastro/saspro/star_spikes.py +681 -0
- setiastro/saspro/star_stretch.py +470 -0
- setiastro/saspro/stat_stretch.py +502 -0
- setiastro/saspro/status_log_dock.py +78 -0
- setiastro/saspro/subwindow.py +3267 -0
- setiastro/saspro/supernovaasteroidhunter.py +1712 -0
- setiastro/saspro/swap_manager.py +99 -0
- setiastro/saspro/torch_backend.py +89 -0
- setiastro/saspro/torch_rejection.py +434 -0
- setiastro/saspro/view_bundle.py +1555 -0
- setiastro/saspro/wavescale_hdr.py +624 -0
- setiastro/saspro/wavescale_hdr_preset.py +100 -0
- setiastro/saspro/wavescalede.py +657 -0
- setiastro/saspro/wavescalede_preset.py +228 -0
- setiastro/saspro/wcs_update.py +374 -0
- setiastro/saspro/whitebalance.py +456 -0
- setiastro/saspro/widgets/__init__.py +48 -0
- setiastro/saspro/widgets/common_utilities.py +305 -0
- setiastro/saspro/widgets/graphics_views.py +122 -0
- setiastro/saspro/widgets/image_utils.py +518 -0
- setiastro/saspro/widgets/preview_dialogs.py +280 -0
- setiastro/saspro/widgets/spinboxes.py +275 -0
- setiastro/saspro/widgets/themed_buttons.py +13 -0
- setiastro/saspro/widgets/wavelet_utils.py +299 -0
- setiastro/saspro/window_shelf.py +185 -0
- setiastro/saspro/xisf.py +1123 -0
- setiastrosuitepro-1.6.0.dist-info/METADATA +266 -0
- setiastrosuitepro-1.6.0.dist-info/RECORD +174 -0
- setiastrosuitepro-1.6.0.dist-info/WHEEL +4 -0
- setiastrosuitepro-1.6.0.dist-info/entry_points.txt +6 -0
- setiastrosuitepro-1.6.0.dist-info/licenses/LICENSE +674 -0
- setiastrosuitepro-1.6.0.dist-info/licenses/license.txt +2580 -0
|
@@ -0,0 +1,784 @@
|
|
|
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
|
+
# ---- Linux Qt stability guard (must run BEFORE any PyQt6 import) ----
|
|
16
|
+
# Default behavior: DO NOT override Wayland.
|
|
17
|
+
# If a user needs the "safe" path, they can opt-in by setting:
|
|
18
|
+
# SASPRO_QT_SAFE=1
|
|
19
|
+
#
|
|
20
|
+
# This avoids punishing all Wayland users for one bad driver/Qt stack.
|
|
21
|
+
if sys.platform.startswith("linux"):
|
|
22
|
+
if os.environ.get("SASPRO_QT_SAFE", "").strip() in ("1", "true", "yes", "on"):
|
|
23
|
+
# Prefer X11/xcb unless user explicitly set a platform plugin
|
|
24
|
+
os.environ.setdefault("QT_QPA_PLATFORM", "xcb")
|
|
25
|
+
|
|
26
|
+
# Prefer software GL unless user explicitly set something else
|
|
27
|
+
os.environ.setdefault("QT_OPENGL", "software")
|
|
28
|
+
os.environ.setdefault("LIBGL_ALWAYS_SOFTWARE", "1")
|
|
29
|
+
|
|
30
|
+
# Global variables for splash screen and app
|
|
31
|
+
_splash = None
|
|
32
|
+
_app = None
|
|
33
|
+
|
|
34
|
+
# Flag to track if splash was initialized
|
|
35
|
+
_splash_initialized = False
|
|
36
|
+
|
|
37
|
+
def _init_splash():
|
|
38
|
+
"""Initialize the splash screen. Safe to call multiple times."""
|
|
39
|
+
global _splash, _app, _splash_initialized
|
|
40
|
+
|
|
41
|
+
if _splash_initialized:
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
# Minimal imports for splash screen
|
|
45
|
+
from PyQt6.QtWidgets import QApplication, QWidget
|
|
46
|
+
from PyQt6.QtCore import Qt, QCoreApplication, QRect
|
|
47
|
+
from PyQt6.QtGui import QGuiApplication, QIcon, QPixmap, QColor, QPainter, QFont, QLinearGradient
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# If we're forcing software OpenGL, do it *before* QApplication is created.
|
|
51
|
+
if sys.platform.startswith("linux"):
|
|
52
|
+
if os.environ.get("SASPRO_QT_SAFE", "").strip() in ("1", "true", "yes", "on"):
|
|
53
|
+
if os.environ.get("QT_OPENGL", "").lower() == "software":
|
|
54
|
+
try:
|
|
55
|
+
QCoreApplication.setAttribute(Qt.ApplicationAttribute.AA_UseSoftwareOpenGL, True)
|
|
56
|
+
except Exception:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
# Set application attributes before creating QApplication
|
|
60
|
+
try:
|
|
61
|
+
QGuiApplication.setHighDpiScaleFactorRoundingPolicy(
|
|
62
|
+
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
|
|
63
|
+
)
|
|
64
|
+
except Exception:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
QCoreApplication.setAttribute(Qt.ApplicationAttribute.AA_DontShowIconsInMenus, False)
|
|
68
|
+
QCoreApplication.setOrganizationName("SetiAstro")
|
|
69
|
+
QCoreApplication.setOrganizationDomain("setiastrosuite.pro")
|
|
70
|
+
QCoreApplication.setApplicationName("Seti Astro Suite Pro")
|
|
71
|
+
|
|
72
|
+
# Create QApplication
|
|
73
|
+
_app = QApplication(sys.argv)
|
|
74
|
+
|
|
75
|
+
if sys.platform.startswith("linux"):
|
|
76
|
+
try:
|
|
77
|
+
print("Qt platform:", _app.platformName())
|
|
78
|
+
print("XDG_SESSION_TYPE:", os.environ.get("XDG_SESSION_TYPE"))
|
|
79
|
+
print("QT_QPA_PLATFORM:", os.environ.get("QT_QPA_PLATFORM"))
|
|
80
|
+
print("QT_OPENGL:", os.environ.get("QT_OPENGL"))
|
|
81
|
+
except Exception:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
# Determine icon paths early
|
|
85
|
+
def _find_icon_path():
|
|
86
|
+
"""Find the best available icon path."""
|
|
87
|
+
if hasattr(sys, '_MEIPASS'):
|
|
88
|
+
base = sys._MEIPASS
|
|
89
|
+
else:
|
|
90
|
+
# When running from package, try to find images directory
|
|
91
|
+
try:
|
|
92
|
+
import setiastro
|
|
93
|
+
package_dir = os.path.dirname(os.path.abspath(setiastro.__file__))
|
|
94
|
+
# Check if images/ exists at package root level (for pip-installed packages)
|
|
95
|
+
package_parent = os.path.dirname(package_dir)
|
|
96
|
+
images_dir_installed = os.path.join(package_parent, 'images')
|
|
97
|
+
if os.path.exists(images_dir_installed):
|
|
98
|
+
base = package_parent
|
|
99
|
+
else:
|
|
100
|
+
# Try project root (for development)
|
|
101
|
+
base = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
|
102
|
+
except (ImportError, AttributeError):
|
|
103
|
+
# Fallback to project root relative to this file
|
|
104
|
+
base = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
|
105
|
+
|
|
106
|
+
candidates = [
|
|
107
|
+
os.path.join(base, "images", "astrosuitepro.png"),
|
|
108
|
+
os.path.join(base, "images", "astrosuitepro.ico"),
|
|
109
|
+
os.path.join(base, "images", "astrosuite.png"),
|
|
110
|
+
os.path.join(base, "images", "astrosuite.ico"),
|
|
111
|
+
]
|
|
112
|
+
for p in candidates:
|
|
113
|
+
if os.path.exists(p):
|
|
114
|
+
return p
|
|
115
|
+
return candidates[0] # fallback
|
|
116
|
+
|
|
117
|
+
_early_icon_path = _find_icon_path()
|
|
118
|
+
|
|
119
|
+
# =========================================================================
|
|
120
|
+
# PhotoshopStyleSplash - Custom splash screen widget
|
|
121
|
+
# =========================================================================
|
|
122
|
+
class _EarlySplash(QWidget):
|
|
123
|
+
"""
|
|
124
|
+
A modern, Photoshop-style splash screen shown immediately on startup.
|
|
125
|
+
"""
|
|
126
|
+
def __init__(self, logo_path: str):
|
|
127
|
+
super().__init__()
|
|
128
|
+
self._version = "1.6.0" # Hardcoded for early display
|
|
129
|
+
self._build = ""
|
|
130
|
+
self.current_message = "Starting..."
|
|
131
|
+
self.progress_value = 0
|
|
132
|
+
|
|
133
|
+
# Window setup
|
|
134
|
+
self.setWindowFlags(
|
|
135
|
+
Qt.WindowType.SplashScreen |
|
|
136
|
+
Qt.WindowType.FramelessWindowHint |
|
|
137
|
+
Qt.WindowType.WindowStaysOnTopHint
|
|
138
|
+
)
|
|
139
|
+
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, False)
|
|
140
|
+
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
|
|
141
|
+
|
|
142
|
+
# Splash dimensions
|
|
143
|
+
self.splash_width = 600
|
|
144
|
+
self.splash_height = 400
|
|
145
|
+
self.setFixedSize(self.splash_width, self.splash_height)
|
|
146
|
+
|
|
147
|
+
# Center on screen
|
|
148
|
+
screen = QGuiApplication.primaryScreen()
|
|
149
|
+
if screen:
|
|
150
|
+
screen_geo = screen.availableGeometry()
|
|
151
|
+
x = (screen_geo.width() - self.splash_width) // 2 + screen_geo.x()
|
|
152
|
+
y = (screen_geo.height() - self.splash_height) // 2 + screen_geo.y()
|
|
153
|
+
self.move(x, y)
|
|
154
|
+
|
|
155
|
+
# Load and scale logo
|
|
156
|
+
self.logo_pixmap = self._load_logo(logo_path)
|
|
157
|
+
|
|
158
|
+
# Fonts
|
|
159
|
+
self.title_font = QFont("Segoe UI", 28, QFont.Weight.Bold)
|
|
160
|
+
self.subtitle_font = QFont("Segoe UI", 11)
|
|
161
|
+
self.message_font = QFont("Segoe UI", 9)
|
|
162
|
+
self.copyright_font = QFont("Segoe UI", 8)
|
|
163
|
+
|
|
164
|
+
def _load_logo(self, path: str) -> QPixmap:
|
|
165
|
+
"""Load the logo and scale appropriately."""
|
|
166
|
+
if not path or not os.path.exists(path):
|
|
167
|
+
return QPixmap()
|
|
168
|
+
|
|
169
|
+
ext = os.path.splitext(path)[1].lower()
|
|
170
|
+
if ext == ".ico":
|
|
171
|
+
ic = QIcon(path)
|
|
172
|
+
pm = ic.pixmap(256, 256)
|
|
173
|
+
if pm.isNull():
|
|
174
|
+
pm = QPixmap(path)
|
|
175
|
+
else:
|
|
176
|
+
pm = QPixmap(path)
|
|
177
|
+
if pm.isNull():
|
|
178
|
+
pm = QIcon(path).pixmap(256, 256)
|
|
179
|
+
|
|
180
|
+
if not pm.isNull():
|
|
181
|
+
pm = pm.scaled(
|
|
182
|
+
180, 180,
|
|
183
|
+
Qt.AspectRatioMode.KeepAspectRatio,
|
|
184
|
+
Qt.TransformationMode.SmoothTransformation
|
|
185
|
+
)
|
|
186
|
+
return pm
|
|
187
|
+
|
|
188
|
+
def setMessage(self, message: str):
|
|
189
|
+
"""Update the loading message."""
|
|
190
|
+
self.current_message = message
|
|
191
|
+
self.repaint()
|
|
192
|
+
if _app:
|
|
193
|
+
_app.processEvents()
|
|
194
|
+
|
|
195
|
+
def setProgress(self, value: int):
|
|
196
|
+
"""Update progress (0-100)."""
|
|
197
|
+
self.progress_value = max(0, min(100, value))
|
|
198
|
+
self.repaint()
|
|
199
|
+
if _app:
|
|
200
|
+
_app.processEvents()
|
|
201
|
+
|
|
202
|
+
def setBuildInfo(self, version: str, build: str):
|
|
203
|
+
"""Update version and build info once available."""
|
|
204
|
+
self._version = version
|
|
205
|
+
self._build = build
|
|
206
|
+
self.repaint()
|
|
207
|
+
|
|
208
|
+
def paintEvent(self, event):
|
|
209
|
+
"""Custom paint for the splash screen."""
|
|
210
|
+
painter = QPainter(self)
|
|
211
|
+
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
|
212
|
+
painter.setRenderHint(QPainter.RenderHint.TextAntialiasing)
|
|
213
|
+
|
|
214
|
+
w, h = self.splash_width, self.splash_height
|
|
215
|
+
|
|
216
|
+
# --- Background gradient (deep space theme) ---
|
|
217
|
+
gradient = QLinearGradient(0, 0, 0, h)
|
|
218
|
+
gradient.setColorAt(0.0, QColor(15, 15, 25))
|
|
219
|
+
gradient.setColorAt(0.5, QColor(25, 25, 45))
|
|
220
|
+
gradient.setColorAt(1.0, QColor(10, 10, 20))
|
|
221
|
+
painter.fillRect(0, 0, w, h, gradient)
|
|
222
|
+
|
|
223
|
+
# --- Subtle border ---
|
|
224
|
+
painter.setPen(QColor(60, 60, 80))
|
|
225
|
+
painter.drawRect(0, 0, w - 1, h - 1)
|
|
226
|
+
|
|
227
|
+
# --- Logo (centered upper area) ---
|
|
228
|
+
if not self.logo_pixmap.isNull():
|
|
229
|
+
logo_x = (w - self.logo_pixmap.width()) // 2
|
|
230
|
+
logo_y = 40
|
|
231
|
+
painter.drawPixmap(logo_x, logo_y, self.logo_pixmap)
|
|
232
|
+
|
|
233
|
+
# --- Title ---
|
|
234
|
+
painter.setFont(self.title_font)
|
|
235
|
+
painter.setPen(QColor(255, 255, 255))
|
|
236
|
+
title_rect = QRect(0, 230, w, 40)
|
|
237
|
+
painter.drawText(title_rect, Qt.AlignmentFlag.AlignCenter, "Seti Astro Suite Pro")
|
|
238
|
+
|
|
239
|
+
# --- Subtitle with version ---
|
|
240
|
+
painter.setFont(self.subtitle_font)
|
|
241
|
+
painter.setPen(QColor(180, 180, 200))
|
|
242
|
+
subtitle_text = f"Version {self._version}"
|
|
243
|
+
|
|
244
|
+
if self._build:
|
|
245
|
+
if self._build == "dev":
|
|
246
|
+
# No build_info → running from source checkout
|
|
247
|
+
subtitle_text += " • Running locally from source code"
|
|
248
|
+
else:
|
|
249
|
+
subtitle_text += f" • Build {self._build}"
|
|
250
|
+
|
|
251
|
+
subtitle_rect = QRect(0, 270, w, 25)
|
|
252
|
+
painter.drawText(subtitle_rect, Qt.AlignmentFlag.AlignCenter, subtitle_text)
|
|
253
|
+
|
|
254
|
+
# --- Progress bar ---
|
|
255
|
+
bar_margin = 50
|
|
256
|
+
bar_height = 4
|
|
257
|
+
bar_y = h - 70
|
|
258
|
+
bar_width = w - (bar_margin * 2)
|
|
259
|
+
|
|
260
|
+
painter.setPen(Qt.PenStyle.NoPen)
|
|
261
|
+
painter.setBrush(QColor(40, 40, 60))
|
|
262
|
+
painter.drawRoundedRect(bar_margin, bar_y, bar_width, bar_height, 2, 2)
|
|
263
|
+
|
|
264
|
+
if self.progress_value > 0:
|
|
265
|
+
fill_width = int(bar_width * self.progress_value / 100)
|
|
266
|
+
bar_gradient = QLinearGradient(bar_margin, 0, bar_margin + bar_width, 0)
|
|
267
|
+
bar_gradient.setColorAt(0.0, QColor(80, 140, 220))
|
|
268
|
+
bar_gradient.setColorAt(1.0, QColor(140, 180, 255))
|
|
269
|
+
painter.setBrush(bar_gradient)
|
|
270
|
+
painter.drawRoundedRect(bar_margin, bar_y, fill_width, bar_height, 2, 2)
|
|
271
|
+
|
|
272
|
+
# --- Loading message ---
|
|
273
|
+
painter.setFont(self.message_font)
|
|
274
|
+
painter.setPen(QColor(150, 150, 180))
|
|
275
|
+
msg_rect = QRect(bar_margin, bar_y + 10, bar_width, 20)
|
|
276
|
+
painter.drawText(msg_rect, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter,
|
|
277
|
+
self.current_message)
|
|
278
|
+
|
|
279
|
+
# --- Copyright ---
|
|
280
|
+
painter.setFont(self.copyright_font)
|
|
281
|
+
painter.setPen(QColor(100, 100, 130))
|
|
282
|
+
copyright_text = "© 2024-2025 Franklin Marek (Seti Astro) • All Rights Reserved"
|
|
283
|
+
copyright_rect = QRect(0, h - 30, w, 20)
|
|
284
|
+
painter.drawText(copyright_rect, Qt.AlignmentFlag.AlignCenter, copyright_text)
|
|
285
|
+
|
|
286
|
+
painter.end()
|
|
287
|
+
|
|
288
|
+
def finish(self):
|
|
289
|
+
"""Hide and cleanup the splash."""
|
|
290
|
+
self.hide()
|
|
291
|
+
self.close()
|
|
292
|
+
self.deleteLater()
|
|
293
|
+
|
|
294
|
+
# --- Show splash IMMEDIATELY ---
|
|
295
|
+
_splash = _EarlySplash(_early_icon_path)
|
|
296
|
+
_splash.show()
|
|
297
|
+
_splash.setMessage("Initializing Python runtime...")
|
|
298
|
+
_splash.setProgress(2)
|
|
299
|
+
_app.processEvents()
|
|
300
|
+
|
|
301
|
+
_splash_initialized = True
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
# Initialize splash if running as main module
|
|
305
|
+
if __name__ == "__main__":
|
|
306
|
+
_init_splash()
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
# =============================================================================
|
|
310
|
+
# Now proceed with all the heavy imports (splash is visible)
|
|
311
|
+
# =============================================================================
|
|
312
|
+
|
|
313
|
+
# Helper to update splash during imports
|
|
314
|
+
def _update_splash(msg: str, progress: int):
|
|
315
|
+
global _splash
|
|
316
|
+
if _splash is not None:
|
|
317
|
+
_splash.setMessage(msg)
|
|
318
|
+
_splash.setProgress(progress)
|
|
319
|
+
|
|
320
|
+
_update_splash("Loading PyTorch runtime...", 5)
|
|
321
|
+
|
|
322
|
+
from setiastro.saspro.runtime_torch import (
|
|
323
|
+
add_runtime_to_sys_path,
|
|
324
|
+
_ban_shadow_torch_paths,
|
|
325
|
+
_purge_bad_torch_from_sysmodules,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
add_runtime_to_sys_path(status_cb=lambda *_: None)
|
|
329
|
+
_ban_shadow_torch_paths(status_cb=lambda *_: None)
|
|
330
|
+
_purge_bad_torch_from_sysmodules(status_cb=lambda *_: None)
|
|
331
|
+
|
|
332
|
+
_update_splash("Loading standard libraries...", 10)
|
|
333
|
+
|
|
334
|
+
# ----------------------------------------
|
|
335
|
+
# Standard library imports (consolidated)
|
|
336
|
+
# ----------------------------------------
|
|
337
|
+
import importlib
|
|
338
|
+
import json
|
|
339
|
+
import logging
|
|
340
|
+
import math
|
|
341
|
+
import multiprocessing
|
|
342
|
+
import os
|
|
343
|
+
import re
|
|
344
|
+
import sys
|
|
345
|
+
import threading
|
|
346
|
+
import time
|
|
347
|
+
import traceback
|
|
348
|
+
import warnings
|
|
349
|
+
import webbrowser
|
|
350
|
+
|
|
351
|
+
from collections import defaultdict
|
|
352
|
+
from datetime import datetime
|
|
353
|
+
from decimal import getcontext
|
|
354
|
+
from io import BytesIO
|
|
355
|
+
from itertools import combinations
|
|
356
|
+
from math import isnan
|
|
357
|
+
from pathlib import Path
|
|
358
|
+
from typing import Dict, List, Optional, Set, Tuple
|
|
359
|
+
from urllib.parse import quote, quote_plus
|
|
360
|
+
|
|
361
|
+
_update_splash("Loading NumPy...", 15)
|
|
362
|
+
|
|
363
|
+
# ----------------------------------------
|
|
364
|
+
# Third-party imports
|
|
365
|
+
# ----------------------------------------
|
|
366
|
+
import numpy as np
|
|
367
|
+
|
|
368
|
+
_update_splash("Loading image libraries...", 20)
|
|
369
|
+
from tifffile import imwrite
|
|
370
|
+
from setiastro.saspro.xisf import XISF
|
|
371
|
+
|
|
372
|
+
_update_splash("Configuring matplotlib...", 25)
|
|
373
|
+
from setiastro.saspro.config_bootstrap import ensure_mpl_config_dir
|
|
374
|
+
_MPL_CFG_DIR = ensure_mpl_config_dir()
|
|
375
|
+
|
|
376
|
+
# Apply metadata patches for frozen builds
|
|
377
|
+
from setiastro.saspro.metadata_patcher import apply_metadata_patches
|
|
378
|
+
apply_metadata_patches()
|
|
379
|
+
# ----------------------------------------
|
|
380
|
+
|
|
381
|
+
warnings.filterwarnings(
|
|
382
|
+
"ignore",
|
|
383
|
+
message=r"Call to deprecated function \(or staticmethod\) _destroy\.",
|
|
384
|
+
category=DeprecationWarning,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
os.environ['LIGHTKURVE_STYLE'] = 'default'
|
|
388
|
+
|
|
389
|
+
# ----------------------------------------
|
|
390
|
+
# Matplotlib configuration
|
|
391
|
+
# ----------------------------------------
|
|
392
|
+
import matplotlib
|
|
393
|
+
matplotlib.use("QtAgg")
|
|
394
|
+
|
|
395
|
+
# Configure stdout encoding
|
|
396
|
+
if (sys.stdout is not None) and (hasattr(sys.stdout, "reconfigure")):
|
|
397
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
398
|
+
|
|
399
|
+
# --- Lazy imports for heavy dependencies (performance optimization) ---
|
|
400
|
+
# photutils: loaded on first use
|
|
401
|
+
_photutils_isophote = None
|
|
402
|
+
def _get_photutils_isophote():
|
|
403
|
+
"""Lazy loader for photutils.isophote module."""
|
|
404
|
+
global _photutils_isophote
|
|
405
|
+
if _photutils_isophote is None:
|
|
406
|
+
try:
|
|
407
|
+
from photutils import isophote as _isophote_module
|
|
408
|
+
_photutils_isophote = _isophote_module
|
|
409
|
+
except Exception:
|
|
410
|
+
_photutils_isophote = False # Mark as failed
|
|
411
|
+
return _photutils_isophote if _photutils_isophote else None
|
|
412
|
+
|
|
413
|
+
def get_Ellipse():
|
|
414
|
+
"""Get photutils.isophote.Ellipse, loading lazily."""
|
|
415
|
+
mod = _get_photutils_isophote()
|
|
416
|
+
return mod.Ellipse if mod else None
|
|
417
|
+
|
|
418
|
+
def get_EllipseGeometry():
|
|
419
|
+
"""Get photutils.isophote.EllipseGeometry, loading lazily."""
|
|
420
|
+
mod = _get_photutils_isophote()
|
|
421
|
+
return mod.EllipseGeometry if mod else None
|
|
422
|
+
|
|
423
|
+
def get_build_ellipse_model():
|
|
424
|
+
"""Get photutils.isophote.build_ellipse_model, loading lazily."""
|
|
425
|
+
mod = _get_photutils_isophote()
|
|
426
|
+
return mod.build_ellipse_model if mod else None
|
|
427
|
+
|
|
428
|
+
# lightkurve: loaded on first use
|
|
429
|
+
_lightkurve_module = None
|
|
430
|
+
def get_lightkurve():
|
|
431
|
+
"""Lazy loader for lightkurve module."""
|
|
432
|
+
global _lightkurve_module
|
|
433
|
+
if _lightkurve_module is None:
|
|
434
|
+
try:
|
|
435
|
+
import lightkurve as _lk
|
|
436
|
+
_lk.MPLSTYLE = None
|
|
437
|
+
_lightkurve_module = _lk
|
|
438
|
+
except Exception:
|
|
439
|
+
_lightkurve_module = False # Mark as failed
|
|
440
|
+
return _lightkurve_module if _lightkurve_module else None
|
|
441
|
+
# --- End lazy imports ---
|
|
442
|
+
|
|
443
|
+
_update_splash("Loading UI utilities...", 30)
|
|
444
|
+
|
|
445
|
+
# Shared UI utilities (avoiding code duplication)
|
|
446
|
+
from setiastro.saspro.widgets.common_utilities import (
|
|
447
|
+
AboutDialog,
|
|
448
|
+
ProjectSaveWorker as _ProjectSaveWorker,
|
|
449
|
+
DECOR_GLYPHS,
|
|
450
|
+
_strip_ui_decorations,
|
|
451
|
+
install_crash_handlers,
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
_update_splash("Loading reproject library...", 35)
|
|
455
|
+
|
|
456
|
+
# Reproject for WCS-based alignment
|
|
457
|
+
try:
|
|
458
|
+
from reproject import reproject_interp
|
|
459
|
+
except ImportError:
|
|
460
|
+
reproject_interp = None # fallback if not installed
|
|
461
|
+
|
|
462
|
+
_update_splash("Loading OpenCV...", 40)
|
|
463
|
+
|
|
464
|
+
# OpenCV for transform estimation & warping
|
|
465
|
+
try:
|
|
466
|
+
import cv2
|
|
467
|
+
OPENCV_AVAILABLE = True
|
|
468
|
+
except ImportError:
|
|
469
|
+
OPENCV_AVAILABLE = False
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
_update_splash("Loading PyQt6 components...", 45)
|
|
473
|
+
|
|
474
|
+
#################################
|
|
475
|
+
# PyQt6 Imports
|
|
476
|
+
#################################
|
|
477
|
+
from PyQt6 import sip
|
|
478
|
+
|
|
479
|
+
# ----- QtWidgets -----
|
|
480
|
+
from PyQt6.QtWidgets import (QDialog, QApplication, QMainWindow, QWidget, QHBoxLayout, QFileDialog, QMessageBox, QSizePolicy, QToolBar, QPushButton, QAbstractItemDelegate,
|
|
481
|
+
QLineEdit, QMenu, QListWidget, QListWidgetItem, QSplashScreen, QDockWidget, QListView, QCompleter, QMdiArea, QMdiSubWindow, QWidgetAction, QAbstractItemView,
|
|
482
|
+
QInputDialog, QVBoxLayout, QLabel, QCheckBox, QProgressBar, QProgressDialog, QGraphicsItem, QTabWidget, QTableWidget, QHeaderView, QTableWidgetItem, QToolButton, QPlainTextEdit
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
# ----- QtGui -----
|
|
486
|
+
from PyQt6.QtGui import (QPixmap, QColor, QIcon, QKeySequence, QShortcut, QGuiApplication, QStandardItemModel, QStandardItem, QAction, QPalette, QBrush, QActionGroup, QDesktopServices, QFont, QTextCursor
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
# ----- QtCore -----
|
|
490
|
+
from PyQt6.QtCore import (Qt, pyqtSignal, QCoreApplication, QTimer, QSize, QSignalBlocker, QModelIndex, QThread, QUrl, QSettings, QEvent, QByteArray, QObject
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
try:
|
|
497
|
+
from setiastro.saspro._generated.build_info import BUILD_TIMESTAMP
|
|
498
|
+
except Exception:
|
|
499
|
+
# No generated build info → running from local source checkout
|
|
500
|
+
BUILD_TIMESTAMP = "dev"
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
try:
|
|
504
|
+
from importlib.metadata import version as _get_version
|
|
505
|
+
VERSION = _get_version("setiastrosuitepro")
|
|
506
|
+
except Exception:
|
|
507
|
+
# Fallback if package not installed (e.g. running from source without install)
|
|
508
|
+
VERSION = "1.6.0-dev"
|
|
509
|
+
|
|
510
|
+
_update_splash("Loading resources...", 50)
|
|
511
|
+
|
|
512
|
+
# Icon paths are now centralized in setiastro.saspro.resources module
|
|
513
|
+
from setiastro.saspro.resources import (
|
|
514
|
+
icon_path, windowslogo_path, green_path, neutral_path, whitebalance_path,
|
|
515
|
+
morpho_path, clahe_path, starnet_path, staradd_path, LExtract_path,
|
|
516
|
+
LInsert_path, slot0_path, slot1_path, slot2_path, slot3_path, slot4_path,
|
|
517
|
+
rgbcombo_path, rgbextract_path, copyslot_path, graxperticon_path,
|
|
518
|
+
cropicon_path, openfile_path, abeicon_path, undoicon_path, redoicon_path,
|
|
519
|
+
blastericon_path, hdr_path, invert_path, fliphorizontal_path,
|
|
520
|
+
flipvertical_path, rotateclockwise_path, rotatecounterclockwise_path,
|
|
521
|
+
rotate180_path, maskcreate_path, maskapply_path, maskremove_path,
|
|
522
|
+
slot5_path, slot6_path, slot7_path, slot8_path, slot9_path, pixelmath_path,
|
|
523
|
+
histogram_path, mosaic_path, rescale_path, staralign_path, mask_path,
|
|
524
|
+
platesolve_path, psf_path, supernova_path, starregistration_path,
|
|
525
|
+
stacking_path, pedestal_icon_path, starspike_path, aperture_path,
|
|
526
|
+
jwstpupil_path, signature_icon_path, livestacking_path, hrdiagram_path,
|
|
527
|
+
convoicon_path, spcc_icon_path, sasp_data_path, exoicon_path, peeker_icon,
|
|
528
|
+
dse_icon_path, astrobin_filters_csv_path, isophote_path, statstretch_path,
|
|
529
|
+
starstretch_path, curves_path, disk_path, uhs_path, blink_path, ppp_path,
|
|
530
|
+
nbtorgb_path, freqsep_path, contsub_path, halo_path, cosmic_path,
|
|
531
|
+
satellite_path, imagecombine_path, wrench_path, eye_icon_path,
|
|
532
|
+
disk_icon_path, nuke_path, hubble_path, collage_path, annotated_path,
|
|
533
|
+
colorwheel_path, font_path, csv_icon_path, spinner_path, wims_path,
|
|
534
|
+
wimi_path, linearfit_path, debayer_path, aberration_path,
|
|
535
|
+
functionbundles_path, viewbundles_path, selectivecolor_path, rgbalign_path,
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
_update_splash("Configuring Qt message handler...", 55)
|
|
540
|
+
|
|
541
|
+
from PyQt6.QtCore import qInstallMessageHandler, QtMsgType
|
|
542
|
+
|
|
543
|
+
def _qt_msg_handler(mode, ctx, msg):
|
|
544
|
+
lvl = {
|
|
545
|
+
QtMsgType.QtDebugMsg: logging.DEBUG,
|
|
546
|
+
QtMsgType.QtInfoMsg: logging.INFO,
|
|
547
|
+
QtMsgType.QtWarningMsg: logging.WARNING,
|
|
548
|
+
QtMsgType.QtCriticalMsg: logging.ERROR,
|
|
549
|
+
QtMsgType.QtFatalMsg: logging.CRITICAL,
|
|
550
|
+
}.get(mode, logging.ERROR)
|
|
551
|
+
logging.log(lvl, "Qt: %s (%s:%s)", msg, getattr(ctx, "file", "?"), getattr(ctx, "line", -1))
|
|
552
|
+
|
|
553
|
+
qInstallMessageHandler(_qt_msg_handler)
|
|
554
|
+
|
|
555
|
+
_update_splash("Loading MDI widgets...", 60)
|
|
556
|
+
|
|
557
|
+
# MDI widgets imported from setiastro.saspro.mdi_widgets
|
|
558
|
+
from setiastro.saspro.mdi_widgets import (
|
|
559
|
+
MdiArea, ViewLinkController, ConsoleListWidget, QtLogStream, _DocProxy,
|
|
560
|
+
ROLE_ACTION as _ROLE_ACTION,
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
# Helper functions imported from setiastro.saspro.main_helpers
|
|
564
|
+
from setiastro.saspro.main_helpers import (
|
|
565
|
+
safe_join_dir_and_name as _safe_join_dir_and_name,
|
|
566
|
+
normalize_save_path_chosen_filter as _normalize_save_path_chosen_filter,
|
|
567
|
+
display_name as _display_name,
|
|
568
|
+
best_doc_name as _best_doc_name,
|
|
569
|
+
doc_looks_like_table as _doc_looks_like_table,
|
|
570
|
+
is_alive as _is_alive,
|
|
571
|
+
safe_widget as _safe_widget,
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
# AboutDialog, DECOR_GLYPHS, _strip_ui_decorations imported from setiastro.saspro.widgets.common_utilities
|
|
575
|
+
|
|
576
|
+
# File utilities imported from setiastro.saspro.file_utils
|
|
577
|
+
from setiastro.saspro.file_utils import (
|
|
578
|
+
_normalize_ext,
|
|
579
|
+
_sanitize_filename,
|
|
580
|
+
_exts_from_filter,
|
|
581
|
+
REPLACE_SPACES_WITH_UNDERSCORES as _REPLACE_SPACES_WITH_UNDERSCORES,
|
|
582
|
+
WIN_RESERVED_NAMES as _WIN_RESERVED,
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
_update_splash("Loading main window module...", 65)
|
|
586
|
+
|
|
587
|
+
from setiastro.saspro.gui.main_window import AstroSuiteProMainWindow
|
|
588
|
+
|
|
589
|
+
_update_splash("Modules loaded, finalizing...", 70)
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def main():
|
|
593
|
+
"""
|
|
594
|
+
Main entry point for Seti Astro Suite Pro.
|
|
595
|
+
|
|
596
|
+
This function can be called from:
|
|
597
|
+
- The package entry point (setiastrosuitepro command)
|
|
598
|
+
- Direct import and call
|
|
599
|
+
- When running as a module: python -m setiastro.saspro
|
|
600
|
+
"""
|
|
601
|
+
global _splash, _app, _splash_initialized
|
|
602
|
+
|
|
603
|
+
# Initialize splash if not already done
|
|
604
|
+
if not _splash_initialized:
|
|
605
|
+
_init_splash()
|
|
606
|
+
|
|
607
|
+
# Update splash with build info now that we have VERSION and BUILD_TIMESTAMP
|
|
608
|
+
if _splash:
|
|
609
|
+
_splash.setBuildInfo(VERSION, BUILD_TIMESTAMP)
|
|
610
|
+
_splash.setMessage("Setting up logging...")
|
|
611
|
+
_splash.setProgress(72)
|
|
612
|
+
|
|
613
|
+
# --- Logging (catch unhandled exceptions to a file) ---
|
|
614
|
+
import tempfile
|
|
615
|
+
from pathlib import Path
|
|
616
|
+
|
|
617
|
+
# Cross-platform log file location
|
|
618
|
+
def get_log_file_path():
|
|
619
|
+
"""Get appropriate log file path for the current platform."""
|
|
620
|
+
|
|
621
|
+
if hasattr(sys, '_MEIPASS'):
|
|
622
|
+
# Running in PyInstaller bundle - use platform-appropriate user directory
|
|
623
|
+
if sys.platform.startswith('win'):
|
|
624
|
+
# Windows: %APPDATA%\SetiAstroSuitePro\logs\
|
|
625
|
+
log_dir = Path(os.path.expandvars('%APPDATA%')) / 'SetiAstroSuitePro' / 'logs'
|
|
626
|
+
elif sys.platform.startswith('darwin'):
|
|
627
|
+
# macOS: ~/Library/Logs/SetiAstroSuitePro/
|
|
628
|
+
log_dir = Path.home() / 'Library' / 'Logs' / 'SetiAstroSuitePro'
|
|
629
|
+
else:
|
|
630
|
+
# Linux: ~/.local/share/SetiAstroSuitePro/logs/
|
|
631
|
+
log_dir = Path.home() / '.local' / 'share' / 'SetiAstroSuitePro' / 'logs'
|
|
632
|
+
|
|
633
|
+
# Create directory if it doesn't exist
|
|
634
|
+
try:
|
|
635
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
636
|
+
log_file = log_dir / 'saspro.log'
|
|
637
|
+
except (OSError, PermissionError):
|
|
638
|
+
# Fallback to temp directory if user directory fails
|
|
639
|
+
log_file = Path(tempfile.gettempdir()) / 'saspro.log'
|
|
640
|
+
else:
|
|
641
|
+
# Development mode - use logs folder in project
|
|
642
|
+
log_dir = Path('logs')
|
|
643
|
+
try:
|
|
644
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
645
|
+
log_file = log_dir / 'saspro.log'
|
|
646
|
+
except (OSError, PermissionError):
|
|
647
|
+
log_file = Path('saspro.log')
|
|
648
|
+
|
|
649
|
+
return str(log_file)
|
|
650
|
+
|
|
651
|
+
# Configure logging with cross-platform path
|
|
652
|
+
log_file_path = get_log_file_path()
|
|
653
|
+
|
|
654
|
+
try:
|
|
655
|
+
logging.basicConfig(
|
|
656
|
+
filename=log_file_path,
|
|
657
|
+
level=logging.INFO,
|
|
658
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
659
|
+
filemode='a' # Append mode
|
|
660
|
+
)
|
|
661
|
+
logging.info(f"Logging to: {log_file_path}")
|
|
662
|
+
logging.info(f"Platform: {sys.platform}")
|
|
663
|
+
logging.info(f"PyInstaller bundle: {hasattr(sys, '_MEIPASS')}")
|
|
664
|
+
except Exception as e:
|
|
665
|
+
# Ultimate fallback - console only logging
|
|
666
|
+
logging.basicConfig(
|
|
667
|
+
level=logging.INFO,
|
|
668
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
669
|
+
handlers=[logging.StreamHandler(sys.stdout)]
|
|
670
|
+
)
|
|
671
|
+
print(f"Warning: Could not write to log file {log_file_path}: {e}")
|
|
672
|
+
print("Using console-only logging")
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
# Setup crash handlers and app icon
|
|
676
|
+
if _splash:
|
|
677
|
+
_splash.setMessage("Installing crash handlers...")
|
|
678
|
+
_splash.setProgress(75)
|
|
679
|
+
install_crash_handlers(_app)
|
|
680
|
+
_app.setWindowIcon(QIcon(windowslogo_path if os.path.exists(windowslogo_path) else icon_path))
|
|
681
|
+
|
|
682
|
+
# --- Windows exe / multiprocessing friendly ---
|
|
683
|
+
if _splash:
|
|
684
|
+
_splash.setMessage("Configuring multiprocessing...")
|
|
685
|
+
_splash.setProgress(78)
|
|
686
|
+
try:
|
|
687
|
+
multiprocessing.freeze_support()
|
|
688
|
+
try:
|
|
689
|
+
multiprocessing.set_start_method("spawn", force=True)
|
|
690
|
+
except RuntimeError:
|
|
691
|
+
# Already set in this interpreter
|
|
692
|
+
pass
|
|
693
|
+
except Exception:
|
|
694
|
+
logging.exception("Multiprocessing init failed (continuing).")
|
|
695
|
+
|
|
696
|
+
try:
|
|
697
|
+
if _splash:
|
|
698
|
+
_splash.setMessage("Loading image manager...")
|
|
699
|
+
_splash.setProgress(80)
|
|
700
|
+
from setiastro.saspro.legacy.image_manager import ImageManager
|
|
701
|
+
|
|
702
|
+
if _splash:
|
|
703
|
+
_splash.setMessage("Suppressing warnings...")
|
|
704
|
+
_splash.setProgress(82)
|
|
705
|
+
from matplotlib import MatplotlibDeprecationWarning
|
|
706
|
+
warnings.filterwarnings("ignore", category=MatplotlibDeprecationWarning)
|
|
707
|
+
|
|
708
|
+
if _splash:
|
|
709
|
+
_splash.setMessage("Creating image manager...")
|
|
710
|
+
_splash.setProgress(85)
|
|
711
|
+
imgr = ImageManager(max_slots=100)
|
|
712
|
+
|
|
713
|
+
if _splash:
|
|
714
|
+
_splash.setMessage("Building main window...")
|
|
715
|
+
_splash.setProgress(90)
|
|
716
|
+
win = AstroSuiteProMainWindow(
|
|
717
|
+
image_manager=imgr,
|
|
718
|
+
version=VERSION,
|
|
719
|
+
build_timestamp=BUILD_TIMESTAMP,
|
|
720
|
+
)
|
|
721
|
+
|
|
722
|
+
if _splash:
|
|
723
|
+
_splash.setMessage("Showing main window...")
|
|
724
|
+
_splash.setProgress(95)
|
|
725
|
+
win.show()
|
|
726
|
+
|
|
727
|
+
# Start background Numba warmup after UI is visible
|
|
728
|
+
try:
|
|
729
|
+
from setiastro.saspro.numba_warmup import start_background_warmup
|
|
730
|
+
start_background_warmup()
|
|
731
|
+
except Exception:
|
|
732
|
+
pass # Non-critical if warmup fails
|
|
733
|
+
|
|
734
|
+
if _splash:
|
|
735
|
+
_splash.setMessage("Ready!")
|
|
736
|
+
_splash.setProgress(100)
|
|
737
|
+
_app.processEvents()
|
|
738
|
+
|
|
739
|
+
# Small delay to show "Ready!" before closing
|
|
740
|
+
import time
|
|
741
|
+
time.sleep(0.3)
|
|
742
|
+
_app.processEvents()
|
|
743
|
+
|
|
744
|
+
# Ensure the splash cannot resurrect later:
|
|
745
|
+
try:
|
|
746
|
+
_splash.finish()
|
|
747
|
+
finally:
|
|
748
|
+
_splash.hide()
|
|
749
|
+
_splash.close()
|
|
750
|
+
_splash.deleteLater()
|
|
751
|
+
|
|
752
|
+
if BUILD_TIMESTAMP == "dev":
|
|
753
|
+
build_label = "running from local source code"
|
|
754
|
+
else:
|
|
755
|
+
build_label = f"build {BUILD_TIMESTAMP}"
|
|
756
|
+
|
|
757
|
+
print(f"Seti Astro Suite Pro v{VERSION} ({build_label}) up and running!")
|
|
758
|
+
sys.exit(_app.exec())
|
|
759
|
+
|
|
760
|
+
except Exception:
|
|
761
|
+
import traceback
|
|
762
|
+
if _splash:
|
|
763
|
+
try:
|
|
764
|
+
_splash.hide()
|
|
765
|
+
_splash.close()
|
|
766
|
+
_splash.deleteLater()
|
|
767
|
+
except Exception:
|
|
768
|
+
pass
|
|
769
|
+
tb = traceback.format_exc()
|
|
770
|
+
logging.error("Unhandled exception occurred\n%s", tb)
|
|
771
|
+
msg = QMessageBox(None)
|
|
772
|
+
msg.setIcon(QMessageBox.Icon.Critical)
|
|
773
|
+
msg.setWindowTitle("Application Error")
|
|
774
|
+
msg.setText("An unexpected error occurred.")
|
|
775
|
+
msg.setInformativeText(tb.splitlines()[-1] if tb else "See details.")
|
|
776
|
+
msg.setDetailedText(tb)
|
|
777
|
+
msg.setStandardButtons(QMessageBox.StandardButton.Ok)
|
|
778
|
+
msg.exec()
|
|
779
|
+
sys.exit(1)
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
# When run as a module, execute main()
|
|
783
|
+
if __name__ == "__main__":
|
|
784
|
+
main()
|