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.

Files changed (174) hide show
  1. setiastro/__init__.py +2 -0
  2. setiastro/saspro/__init__.py +20 -0
  3. setiastro/saspro/__main__.py +784 -0
  4. setiastro/saspro/_generated/__init__.py +7 -0
  5. setiastro/saspro/_generated/build_info.py +2 -0
  6. setiastro/saspro/abe.py +1295 -0
  7. setiastro/saspro/abe_preset.py +196 -0
  8. setiastro/saspro/aberration_ai.py +694 -0
  9. setiastro/saspro/aberration_ai_preset.py +224 -0
  10. setiastro/saspro/accel_installer.py +218 -0
  11. setiastro/saspro/accel_workers.py +30 -0
  12. setiastro/saspro/add_stars.py +621 -0
  13. setiastro/saspro/astrobin_exporter.py +1007 -0
  14. setiastro/saspro/astrospike.py +153 -0
  15. setiastro/saspro/astrospike_python.py +1839 -0
  16. setiastro/saspro/autostretch.py +196 -0
  17. setiastro/saspro/backgroundneutral.py +560 -0
  18. setiastro/saspro/batch_convert.py +325 -0
  19. setiastro/saspro/batch_renamer.py +519 -0
  20. setiastro/saspro/blemish_blaster.py +488 -0
  21. setiastro/saspro/blink_comparator_pro.py +2923 -0
  22. setiastro/saspro/bundles.py +61 -0
  23. setiastro/saspro/bundles_dock.py +114 -0
  24. setiastro/saspro/cheat_sheet.py +168 -0
  25. setiastro/saspro/clahe.py +342 -0
  26. setiastro/saspro/comet_stacking.py +1377 -0
  27. setiastro/saspro/config.py +38 -0
  28. setiastro/saspro/config_bootstrap.py +40 -0
  29. setiastro/saspro/config_manager.py +316 -0
  30. setiastro/saspro/continuum_subtract.py +1617 -0
  31. setiastro/saspro/convo.py +1397 -0
  32. setiastro/saspro/convo_preset.py +414 -0
  33. setiastro/saspro/copyastro.py +187 -0
  34. setiastro/saspro/cosmicclarity.py +1564 -0
  35. setiastro/saspro/cosmicclarity_preset.py +407 -0
  36. setiastro/saspro/crop_dialog_pro.py +948 -0
  37. setiastro/saspro/crop_preset.py +189 -0
  38. setiastro/saspro/curve_editor_pro.py +2544 -0
  39. setiastro/saspro/curves_preset.py +375 -0
  40. setiastro/saspro/debayer.py +670 -0
  41. setiastro/saspro/debug_utils.py +29 -0
  42. setiastro/saspro/dnd_mime.py +35 -0
  43. setiastro/saspro/doc_manager.py +2634 -0
  44. setiastro/saspro/exoplanet_detector.py +2166 -0
  45. setiastro/saspro/file_utils.py +284 -0
  46. setiastro/saspro/fitsmodifier.py +744 -0
  47. setiastro/saspro/free_torch_memory.py +48 -0
  48. setiastro/saspro/frequency_separation.py +1343 -0
  49. setiastro/saspro/function_bundle.py +1594 -0
  50. setiastro/saspro/ghs_dialog_pro.py +660 -0
  51. setiastro/saspro/ghs_preset.py +284 -0
  52. setiastro/saspro/graxpert.py +634 -0
  53. setiastro/saspro/graxpert_preset.py +287 -0
  54. setiastro/saspro/gui/__init__.py +0 -0
  55. setiastro/saspro/gui/main_window.py +8494 -0
  56. setiastro/saspro/gui/mixins/__init__.py +33 -0
  57. setiastro/saspro/gui/mixins/dock_mixin.py +263 -0
  58. setiastro/saspro/gui/mixins/file_mixin.py +445 -0
  59. setiastro/saspro/gui/mixins/geometry_mixin.py +403 -0
  60. setiastro/saspro/gui/mixins/header_mixin.py +441 -0
  61. setiastro/saspro/gui/mixins/mask_mixin.py +421 -0
  62. setiastro/saspro/gui/mixins/menu_mixin.py +361 -0
  63. setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
  64. setiastro/saspro/gui/mixins/toolbar_mixin.py +1324 -0
  65. setiastro/saspro/gui/mixins/update_mixin.py +309 -0
  66. setiastro/saspro/gui/mixins/view_mixin.py +435 -0
  67. setiastro/saspro/halobgon.py +462 -0
  68. setiastro/saspro/header_viewer.py +445 -0
  69. setiastro/saspro/headless_utils.py +88 -0
  70. setiastro/saspro/histogram.py +753 -0
  71. setiastro/saspro/history_explorer.py +939 -0
  72. setiastro/saspro/image_combine.py +414 -0
  73. setiastro/saspro/image_peeker_pro.py +1596 -0
  74. setiastro/saspro/imageops/__init__.py +37 -0
  75. setiastro/saspro/imageops/mdi_snap.py +292 -0
  76. setiastro/saspro/imageops/scnr.py +36 -0
  77. setiastro/saspro/imageops/starbasedwhitebalance.py +210 -0
  78. setiastro/saspro/imageops/stretch.py +244 -0
  79. setiastro/saspro/isophote.py +1179 -0
  80. setiastro/saspro/layers.py +208 -0
  81. setiastro/saspro/layers_dock.py +714 -0
  82. setiastro/saspro/lazy_imports.py +193 -0
  83. setiastro/saspro/legacy/__init__.py +2 -0
  84. setiastro/saspro/legacy/image_manager.py +2226 -0
  85. setiastro/saspro/legacy/numba_utils.py +3659 -0
  86. setiastro/saspro/legacy/xisf.py +1071 -0
  87. setiastro/saspro/linear_fit.py +534 -0
  88. setiastro/saspro/live_stacking.py +1830 -0
  89. setiastro/saspro/log_bus.py +5 -0
  90. setiastro/saspro/logging_config.py +460 -0
  91. setiastro/saspro/luminancerecombine.py +309 -0
  92. setiastro/saspro/main_helpers.py +201 -0
  93. setiastro/saspro/mask_creation.py +928 -0
  94. setiastro/saspro/masks_core.py +56 -0
  95. setiastro/saspro/mdi_widgets.py +353 -0
  96. setiastro/saspro/memory_utils.py +666 -0
  97. setiastro/saspro/metadata_patcher.py +75 -0
  98. setiastro/saspro/mfdeconv.py +3826 -0
  99. setiastro/saspro/mfdeconv_earlystop.py +71 -0
  100. setiastro/saspro/mfdeconvcudnn.py +3263 -0
  101. setiastro/saspro/mfdeconvsport.py +2382 -0
  102. setiastro/saspro/minorbodycatalog.py +567 -0
  103. setiastro/saspro/morphology.py +382 -0
  104. setiastro/saspro/multiscale_decomp.py +1290 -0
  105. setiastro/saspro/nbtorgb_stars.py +531 -0
  106. setiastro/saspro/numba_utils.py +3044 -0
  107. setiastro/saspro/numba_warmup.py +141 -0
  108. setiastro/saspro/ops/__init__.py +9 -0
  109. setiastro/saspro/ops/command_help_dialog.py +623 -0
  110. setiastro/saspro/ops/command_runner.py +217 -0
  111. setiastro/saspro/ops/commands.py +1594 -0
  112. setiastro/saspro/ops/script_editor.py +1102 -0
  113. setiastro/saspro/ops/scripts.py +1413 -0
  114. setiastro/saspro/ops/settings.py +560 -0
  115. setiastro/saspro/parallel_utils.py +554 -0
  116. setiastro/saspro/pedestal.py +121 -0
  117. setiastro/saspro/perfect_palette_picker.py +1053 -0
  118. setiastro/saspro/pipeline.py +110 -0
  119. setiastro/saspro/pixelmath.py +1600 -0
  120. setiastro/saspro/plate_solver.py +2435 -0
  121. setiastro/saspro/project_io.py +797 -0
  122. setiastro/saspro/psf_utils.py +136 -0
  123. setiastro/saspro/psf_viewer.py +549 -0
  124. setiastro/saspro/pyi_rthook_astroquery.py +95 -0
  125. setiastro/saspro/remove_green.py +314 -0
  126. setiastro/saspro/remove_stars.py +1625 -0
  127. setiastro/saspro/remove_stars_preset.py +404 -0
  128. setiastro/saspro/resources.py +472 -0
  129. setiastro/saspro/rgb_combination.py +207 -0
  130. setiastro/saspro/rgb_extract.py +19 -0
  131. setiastro/saspro/rgbalign.py +723 -0
  132. setiastro/saspro/runtime_imports.py +7 -0
  133. setiastro/saspro/runtime_torch.py +754 -0
  134. setiastro/saspro/save_options.py +72 -0
  135. setiastro/saspro/selective_color.py +1552 -0
  136. setiastro/saspro/sfcc.py +1425 -0
  137. setiastro/saspro/shortcuts.py +2807 -0
  138. setiastro/saspro/signature_insert.py +1099 -0
  139. setiastro/saspro/stacking_suite.py +17712 -0
  140. setiastro/saspro/star_alignment.py +7420 -0
  141. setiastro/saspro/star_alignment_preset.py +329 -0
  142. setiastro/saspro/star_metrics.py +49 -0
  143. setiastro/saspro/star_spikes.py +681 -0
  144. setiastro/saspro/star_stretch.py +470 -0
  145. setiastro/saspro/stat_stretch.py +502 -0
  146. setiastro/saspro/status_log_dock.py +78 -0
  147. setiastro/saspro/subwindow.py +3267 -0
  148. setiastro/saspro/supernovaasteroidhunter.py +1712 -0
  149. setiastro/saspro/swap_manager.py +99 -0
  150. setiastro/saspro/torch_backend.py +89 -0
  151. setiastro/saspro/torch_rejection.py +434 -0
  152. setiastro/saspro/view_bundle.py +1555 -0
  153. setiastro/saspro/wavescale_hdr.py +624 -0
  154. setiastro/saspro/wavescale_hdr_preset.py +100 -0
  155. setiastro/saspro/wavescalede.py +657 -0
  156. setiastro/saspro/wavescalede_preset.py +228 -0
  157. setiastro/saspro/wcs_update.py +374 -0
  158. setiastro/saspro/whitebalance.py +456 -0
  159. setiastro/saspro/widgets/__init__.py +48 -0
  160. setiastro/saspro/widgets/common_utilities.py +305 -0
  161. setiastro/saspro/widgets/graphics_views.py +122 -0
  162. setiastro/saspro/widgets/image_utils.py +518 -0
  163. setiastro/saspro/widgets/preview_dialogs.py +280 -0
  164. setiastro/saspro/widgets/spinboxes.py +275 -0
  165. setiastro/saspro/widgets/themed_buttons.py +13 -0
  166. setiastro/saspro/widgets/wavelet_utils.py +299 -0
  167. setiastro/saspro/window_shelf.py +185 -0
  168. setiastro/saspro/xisf.py +1123 -0
  169. setiastrosuitepro-1.6.0.dist-info/METADATA +266 -0
  170. setiastrosuitepro-1.6.0.dist-info/RECORD +174 -0
  171. setiastrosuitepro-1.6.0.dist-info/WHEEL +4 -0
  172. setiastrosuitepro-1.6.0.dist-info/entry_points.txt +6 -0
  173. setiastrosuitepro-1.6.0.dist-info/licenses/LICENSE +674 -0
  174. 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()