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,309 @@
1
+ # pro/gui/mixins/update_mixin.py
2
+ """
3
+ Update check mixin for AstroSuiteProMainWindow.
4
+
5
+ This mixin contains all functionality for checking for application updates,
6
+ downloading updates, and handling the update installation process.
7
+ """
8
+ from __future__ import annotations
9
+ import json
10
+ import sys
11
+ import webbrowser
12
+ from typing import TYPE_CHECKING
13
+
14
+ from PyQt6.QtCore import QUrl
15
+ from PyQt6.QtNetwork import QNetworkRequest, QNetworkReply
16
+ from PyQt6.QtWidgets import QMessageBox, QApplication
17
+
18
+ if TYPE_CHECKING:
19
+ pass
20
+
21
+
22
+ class UpdateMixin:
23
+ """
24
+ Mixin for application update functionality.
25
+
26
+ Provides methods for checking for updates, downloading updates,
27
+ and managing the update installation process.
28
+ """
29
+
30
+ # Default URL for update checks
31
+ _updates_url = "https://raw.githubusercontent.com/user/repo/main/updates.json"
32
+
33
+ @property
34
+ def _current_version_str(self) -> str:
35
+ """
36
+ Return the current app version as a string.
37
+
38
+ Prefer an attribute set on the main window (self._version),
39
+ fall back to any module-level VERSION if present, then "0.0.0".
40
+ """
41
+ v = getattr(self, "_version", None)
42
+ if not v:
43
+ v = globals().get("VERSION", None)
44
+ return str(v or "0.0.0")
45
+
46
+ def _ensure_network_manager(self):
47
+ """Ensure the network access manager exists."""
48
+ from PyQt6.QtNetwork import QNetworkAccessManager
49
+
50
+ if not hasattr(self, "_nam") or self._nam is None:
51
+ self._nam = QNetworkAccessManager(self)
52
+ self._nam.finished.connect(self._on_update_reply)
53
+
54
+ def _kick_update_check(self, *, interactive: bool):
55
+ """
56
+ Start an update check request.
57
+
58
+ Args:
59
+ interactive: If True, show UI feedback for the check
60
+ """
61
+ self._ensure_network_manager()
62
+ url_str = self.settings.value("updates/url", self._updates_url, type=str) or self._updates_url
63
+ req = QNetworkRequest(QUrl(url_str))
64
+ req.setRawHeader(
65
+ b"User-Agent",
66
+ f"SASPro/{self._current_version_str}".encode("utf-8")
67
+ )
68
+ reply = self._nam.get(req)
69
+ reply.setProperty("interactive", interactive)
70
+
71
+ def check_for_updates_now(self):
72
+ """Check for updates interactively (show result to user)."""
73
+ if self.statusBar():
74
+ self.statusBar().showMessage("Checking for updates...")
75
+ self._kick_update_check(interactive=True)
76
+
77
+ def check_for_updates_startup(self):
78
+ """Check for updates silently at startup."""
79
+ self._kick_update_check(interactive=False)
80
+
81
+ def _parse_version_tuple(self, v: str):
82
+ """
83
+ Parse a version string into a tuple for comparison.
84
+
85
+ Args:
86
+ v: Version string like "1.2.3"
87
+
88
+ Returns:
89
+ Tuple of integers, or None if parsing fails
90
+ """
91
+ try:
92
+ parts = str(v).strip().split(".")
93
+ return tuple(int(p) for p in parts)
94
+ except Exception:
95
+ return None
96
+
97
+ def _on_update_reply(self, reply: QNetworkReply):
98
+ """Handle network reply from update check or download."""
99
+ interactive = bool(reply.property("interactive"))
100
+
101
+ # Was this the second request (the actual installer download)?
102
+ if bool(reply.property("is_update_download")):
103
+ self._on_windows_update_download_finished(reply)
104
+ return
105
+
106
+ try:
107
+ if reply.error() != QNetworkReply.NetworkError.NoError:
108
+ err = reply.errorString()
109
+ if self.statusBar():
110
+ self.statusBar().showMessage("Update check failed.", 5000)
111
+ if interactive:
112
+ QMessageBox.warning(self, "Update Check Failed",
113
+ f"Unable to check for updates.\n\n{err}")
114
+ else:
115
+ print(f"[updates] check failed: {err}")
116
+ return
117
+
118
+ raw = bytes(reply.readAll())
119
+ try:
120
+ data = json.loads(raw.decode("utf-8"))
121
+ except Exception as je:
122
+ if self.statusBar():
123
+ self.statusBar().showMessage("Update check failed (bad JSON).", 5000)
124
+ if interactive:
125
+ QMessageBox.warning(self, "Update Check Failed",
126
+ f"Update JSON is invalid.\n\n{je}")
127
+ else:
128
+ print(f"[updates] bad JSON: {je}")
129
+ return
130
+
131
+ latest_str = str(data.get("version", "")).strip()
132
+ notes = str(data.get("notes", "") or "")
133
+ downloads = data.get("downloads", {}) or {}
134
+
135
+ if not latest_str:
136
+ if self.statusBar():
137
+ self.statusBar().showMessage("Update check failed (no 'version').", 5000)
138
+ if interactive:
139
+ QMessageBox.warning(self, "Update Check Failed",
140
+ "Update JSON missing the 'version' field.")
141
+ else:
142
+ print("[updates] JSON missing 'version'")
143
+ return
144
+
145
+ cur_tuple = self._parse_version_tuple(self._current_version_str)
146
+ latest_tuple = self._parse_version_tuple(latest_str)
147
+ available = bool(latest_tuple and cur_tuple and latest_tuple > cur_tuple)
148
+
149
+ if available:
150
+ if self.statusBar():
151
+ self.statusBar().showMessage(f"Update available: {latest_str}", 5000)
152
+ msg_box = QMessageBox(self)
153
+ msg_box.setIcon(QMessageBox.Icon.Information)
154
+ msg_box.setWindowTitle("Update Available")
155
+ msg_box.setText(f"A new version ({latest_str}) is available!")
156
+ if notes:
157
+ msg_box.setInformativeText(f"Release Notes:\n{notes}")
158
+ msg_box.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
159
+ msg_box.setDefaultButton(QMessageBox.StandardButton.Yes)
160
+
161
+ if downloads:
162
+ details = "\n".join([f"{k}: {v}" for k, v in downloads.items()])
163
+ msg_box.setDetailedText(details)
164
+
165
+ if msg_box.exec() == QMessageBox.StandardButton.Yes:
166
+ plat = sys.platform
167
+ link = downloads.get(
168
+ "Windows" if plat.startswith("win") else
169
+ "macOS" if plat.startswith("darwin") else
170
+ "Linux" if plat.startswith("linux") else "", ""
171
+ )
172
+ if not link:
173
+ QMessageBox.warning(self, "Download", "No download link available for this platform.")
174
+ return
175
+
176
+ if plat.startswith("win"):
177
+ # Use in-app updater for Windows
178
+ self._start_windows_update_download(link)
179
+ else:
180
+ # Open browser for other platforms
181
+ webbrowser.open(link)
182
+ else:
183
+ if self.statusBar():
184
+ self.statusBar().showMessage("You're up to date.", 3000)
185
+ if interactive:
186
+ QMessageBox.information(self, "Up to Date",
187
+ "You're already running the latest version.")
188
+ finally:
189
+ reply.deleteLater()
190
+
191
+ def _is_windows(self) -> bool:
192
+ """Check if running on Windows."""
193
+ return sys.platform.startswith("win")
194
+
195
+ def _start_windows_update_download(self, url: str):
196
+ """
197
+ Download the update file for Windows.
198
+
199
+ Args:
200
+ url: URL to download from
201
+ """
202
+ from PyQt6.QtCore import QStandardPaths
203
+ from pathlib import Path
204
+ import os
205
+
206
+ self._ensure_network_manager()
207
+
208
+ downloads_dir = QStandardPaths.writableLocation(QStandardPaths.StandardLocation.DownloadLocation)
209
+ if not downloads_dir:
210
+ import tempfile
211
+ downloads_dir = tempfile.gettempdir()
212
+
213
+ os.makedirs(downloads_dir, exist_ok=True)
214
+
215
+ # filename from URL
216
+ fname = url.split("/")[-1] or "setiastrosuitepro_windows.zip"
217
+ target_path = Path(downloads_dir) / fname
218
+
219
+ req = QNetworkRequest(QUrl(url))
220
+ req.setRawHeader(
221
+ b"User-Agent",
222
+ f"SASPro/{self._current_version_str}".encode("utf-8")
223
+ )
224
+
225
+ reply = self._nam.get(req)
226
+ # mark this reply as "this is the actual installer file, not updates.json"
227
+ reply.setProperty("is_update_download", True)
228
+ reply.setProperty("target_path", str(target_path))
229
+
230
+ reply.downloadProgress.connect(
231
+ lambda rec, tot: self.statusBar().showMessage(
232
+ f"Downloading update... {rec / 1024:.1f} KB / {tot / 1024:.1f} KB" if tot > 0 else "Downloading update..."
233
+ )
234
+ )
235
+
236
+ def _on_windows_update_download_finished(self, reply: QNetworkReply):
237
+ """Handle completion of Windows update download."""
238
+ from pathlib import Path
239
+ import os
240
+ import zipfile
241
+ import subprocess
242
+ import tempfile
243
+
244
+ target_path = Path(reply.property("target_path"))
245
+
246
+ if reply.error() != QNetworkReply.NetworkError.NoError:
247
+ QMessageBox.warning(self, "Update Failed",
248
+ f"Could not download update:\n{reply.errorString()}")
249
+ return
250
+
251
+ # Write the .zip
252
+ data = bytes(reply.readAll())
253
+ try:
254
+ with open(target_path, "wb") as f:
255
+ f.write(data)
256
+ except Exception as e:
257
+ QMessageBox.warning(self, "Update Failed",
258
+ f"Could not save update to disk:\n{e}")
259
+ return
260
+
261
+ self.statusBar().showMessage(f"Update downloaded to {target_path}", 5000)
262
+
263
+ # Extract zip if needed
264
+ if target_path.suffix.lower() == ".zip":
265
+ extract_dir = Path(tempfile.mkdtemp(prefix="saspro-update-"))
266
+ try:
267
+ with zipfile.ZipFile(target_path, "r") as zf:
268
+ zf.extractall(extract_dir)
269
+ except Exception as e:
270
+ QMessageBox.warning(self, "Update Failed",
271
+ f"Could not extract update zip:\n{e}")
272
+ return
273
+
274
+ # Look recursively for an .exe
275
+ exe_cands = list(extract_dir.rglob("*.exe"))
276
+ if not exe_cands:
277
+ QMessageBox.warning(
278
+ self,
279
+ "Update Failed",
280
+ f"Downloaded ZIP did not contain an .exe installer.\nFolder: {extract_dir}"
281
+ )
282
+ return
283
+
284
+ installer_path = exe_cands[0]
285
+ else:
286
+ # In case one day Windows points straight to .exe
287
+ installer_path = target_path
288
+
289
+ # Ask to run
290
+ ok = QMessageBox.question(
291
+ self,
292
+ "Run Installer",
293
+ "The update has been downloaded.\n\nRun the installer now? (SAS will close.)",
294
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
295
+ QMessageBox.StandardButton.Yes,
296
+ )
297
+ if ok != QMessageBox.StandardButton.Yes:
298
+ return
299
+
300
+ # Launch installer
301
+ try:
302
+ subprocess.Popen([str(installer_path)], shell=False)
303
+ except Exception as e:
304
+ QMessageBox.warning(self, "Update Failed",
305
+ f"Could not start installer:\n{e}")
306
+ return
307
+
308
+ # Close app so the installer can overwrite files
309
+ QApplication.instance().quit()