setiastrosuitepro 1.7.5.post1__py3-none-any.whl → 1.8.0.post3__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/saspro/_generated/build_info.py +2 -2
- setiastro/saspro/accel_installer.py +21 -8
- setiastro/saspro/accel_workers.py +11 -12
- setiastro/saspro/comet_stacking.py +113 -85
- setiastro/saspro/cosmicclarity.py +604 -826
- setiastro/saspro/cosmicclarity_engines/benchmark_engine.py +732 -0
- setiastro/saspro/cosmicclarity_engines/darkstar_engine.py +576 -0
- setiastro/saspro/cosmicclarity_engines/denoise_engine.py +567 -0
- setiastro/saspro/cosmicclarity_engines/satellite_engine.py +620 -0
- setiastro/saspro/cosmicclarity_engines/sharpen_engine.py +587 -0
- setiastro/saspro/cosmicclarity_engines/superres_engine.py +412 -0
- setiastro/saspro/gui/main_window.py +14 -0
- setiastro/saspro/gui/mixins/menu_mixin.py +2 -0
- setiastro/saspro/model_manager.py +324 -0
- setiastro/saspro/model_workers.py +102 -0
- setiastro/saspro/ops/benchmark.py +320 -0
- setiastro/saspro/ops/settings.py +407 -10
- setiastro/saspro/remove_stars.py +424 -442
- setiastro/saspro/resources.py +73 -10
- setiastro/saspro/runtime_torch.py +107 -22
- setiastro/saspro/signature_insert.py +14 -3
- {setiastrosuitepro-1.7.5.post1.dist-info → setiastrosuitepro-1.8.0.post3.dist-info}/METADATA +2 -1
- {setiastrosuitepro-1.7.5.post1.dist-info → setiastrosuitepro-1.8.0.post3.dist-info}/RECORD +27 -18
- {setiastrosuitepro-1.7.5.post1.dist-info → setiastrosuitepro-1.8.0.post3.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.7.5.post1.dist-info → setiastrosuitepro-1.8.0.post3.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.7.5.post1.dist-info → setiastrosuitepro-1.8.0.post3.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.7.5.post1.dist-info → setiastrosuitepro-1.8.0.post3.dist-info}/licenses/license.txt +0 -0
setiastro/saspro/ops/settings.py
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
# ops.settings.py
|
|
2
2
|
from PyQt6.QtWidgets import (
|
|
3
|
-
QLineEdit, QDialogButtonBox, QFileDialog, QDialog, QPushButton, QFormLayout,QApplication,
|
|
3
|
+
QLineEdit, QDialogButtonBox, QFileDialog, QDialog, QPushButton, QFormLayout,QApplication, QMenu,
|
|
4
4
|
QHBoxLayout, QVBoxLayout, QWidget, QCheckBox, QComboBox, QSpinBox, QDoubleSpinBox, QLabel, QColorDialog, QFontDialog, QSlider)
|
|
5
5
|
from PyQt6.QtCore import QSettings, Qt
|
|
6
|
+
from PyQt6.QtGui import QAction
|
|
6
7
|
import pytz # for timezone list
|
|
7
|
-
|
|
8
|
+
from setiastro.saspro.accel_installer import current_backend
|
|
9
|
+
import sys, platform
|
|
10
|
+
from PyQt6.QtWidgets import QToolButton, QProgressDialog
|
|
11
|
+
from PyQt6.QtCore import QThread
|
|
8
12
|
# i18n support
|
|
9
13
|
from setiastro.saspro.i18n import get_available_languages, get_saved_language, save_language
|
|
10
|
-
|
|
14
|
+
import importlib.util
|
|
15
|
+
import importlib.metadata
|
|
16
|
+
import webbrowser
|
|
11
17
|
|
|
12
18
|
class SettingsDialog(QDialog):
|
|
13
19
|
"""
|
|
@@ -24,7 +30,7 @@ class SettingsDialog(QDialog):
|
|
|
24
30
|
|
|
25
31
|
# ---- Existing fields (paths, checkboxes, etc.) ----
|
|
26
32
|
self.le_graxpert = QLineEdit()
|
|
27
|
-
|
|
33
|
+
|
|
28
34
|
self.le_starnet = QLineEdit()
|
|
29
35
|
self.le_astap = QLineEdit()
|
|
30
36
|
|
|
@@ -75,12 +81,12 @@ class SettingsDialog(QDialog):
|
|
|
75
81
|
self._initial_language = "en" # placeholder, set in refresh_ui
|
|
76
82
|
|
|
77
83
|
btn_grax = QPushButton(self.tr("Browse…")); btn_grax.clicked.connect(lambda: self._browse_into(self.le_graxpert))
|
|
78
|
-
|
|
84
|
+
|
|
79
85
|
btn_star = QPushButton(self.tr("Browse…")); btn_star.clicked.connect(lambda: self._browse_into(self.le_starnet))
|
|
80
86
|
btn_astap = QPushButton(self.tr("Browse…")); btn_astap.clicked.connect(lambda: self._browse_into(self.le_astap))
|
|
81
87
|
|
|
82
88
|
row_grax = QHBoxLayout(); row_grax.addWidget(self.le_graxpert); row_grax.addWidget(btn_grax)
|
|
83
|
-
|
|
89
|
+
|
|
84
90
|
row_star = QHBoxLayout(); row_star.addWidget(self.le_starnet); row_star.addWidget(btn_star)
|
|
85
91
|
row_astap = QHBoxLayout(); row_astap.addWidget(self.le_astap); row_astap.addWidget(btn_astap)
|
|
86
92
|
|
|
@@ -163,7 +169,7 @@ class SettingsDialog(QDialog):
|
|
|
163
169
|
# ---- Left column: Paths & Integrations ----
|
|
164
170
|
left_col.addRow(QLabel(self.tr("<b>Paths & Integrations</b>")))
|
|
165
171
|
w = QWidget(); w.setLayout(row_grax); left_col.addRow(self.tr("GraXpert executable:"), w)
|
|
166
|
-
|
|
172
|
+
|
|
167
173
|
w = QWidget(); w.setLayout(row_star); left_col.addRow(self.tr("StarNet executable:"), w)
|
|
168
174
|
w = QWidget(); w.setLayout(row_astap); left_col.addRow(self.tr("ASTAP executable:"), w)
|
|
169
175
|
left_col.addRow(self.tr("Astrometry.net API key:"), self.le_astrometry)
|
|
@@ -182,6 +188,78 @@ class SettingsDialog(QDialog):
|
|
|
182
188
|
left_col.addRow(self.tr("Background Opacity:"), w_bg_opacity)
|
|
183
189
|
left_col.addRow(self.tr("Background Image:"), w_bg_image)
|
|
184
190
|
|
|
191
|
+
left_col.addRow(QLabel(self.tr("<b>Acceleration</b>")))
|
|
192
|
+
|
|
193
|
+
accel_row = QHBoxLayout()
|
|
194
|
+
self.backend_label = QLabel(self.tr("Backend: {0}").format(current_backend()))
|
|
195
|
+
accel_row.addWidget(self.backend_label)
|
|
196
|
+
# NEW: dependency status label (rich text)
|
|
197
|
+
self.accel_deps_label = QLabel()
|
|
198
|
+
self.accel_deps_label.setTextFormat(Qt.TextFormat.RichText)
|
|
199
|
+
self.accel_deps_label.setStyleSheet("color:#888;") # subtle
|
|
200
|
+
self.accel_deps_label.setToolTip(self.tr("Installed acceleration-related Python packages"))
|
|
201
|
+
accel_row.addSpacing(12)
|
|
202
|
+
accel_row.addWidget(self.accel_deps_label)
|
|
203
|
+
|
|
204
|
+
# NEW: preference combo
|
|
205
|
+
self.cb_accel_pref = QComboBox()
|
|
206
|
+
self.cb_accel_pref.addItems([
|
|
207
|
+
"Auto (recommended)",
|
|
208
|
+
"CUDA (NVIDIA)",
|
|
209
|
+
"Intel XPU (Arc/Xe)",
|
|
210
|
+
"DirectML (Windows AMD/Intel)",
|
|
211
|
+
"CPU only",
|
|
212
|
+
])
|
|
213
|
+
# hide DirectML option on non-Windows if you want:
|
|
214
|
+
if platform.system() != "Windows":
|
|
215
|
+
# remove the DirectML entry (index 3)
|
|
216
|
+
self.cb_accel_pref.removeItem(3)
|
|
217
|
+
|
|
218
|
+
accel_row.addSpacing(8)
|
|
219
|
+
accel_row.addWidget(QLabel(self.tr("Preference:")))
|
|
220
|
+
accel_row.addWidget(self.cb_accel_pref)
|
|
221
|
+
|
|
222
|
+
self.install_accel_btn = QPushButton(self.tr("Install/Update GPU Acceleration…"))
|
|
223
|
+
accel_row.addWidget(self.install_accel_btn)
|
|
224
|
+
|
|
225
|
+
gpu_help_btn = QToolButton()
|
|
226
|
+
gpu_help_btn.setText("?")
|
|
227
|
+
gpu_help_btn.setToolTip(self.tr("If GPU still not being used — click for fix steps"))
|
|
228
|
+
gpu_help_btn.clicked.connect(self._show_gpu_accel_fix_help)
|
|
229
|
+
accel_row.addWidget(gpu_help_btn)
|
|
230
|
+
|
|
231
|
+
accel_row.addStretch(1)
|
|
232
|
+
w_accel = QWidget()
|
|
233
|
+
w_accel.setLayout(accel_row)
|
|
234
|
+
left_col.addRow(w_accel)
|
|
235
|
+
|
|
236
|
+
# ---- Models ----
|
|
237
|
+
right_col.addRow(QLabel(self.tr("<b>AI Models</b>")))
|
|
238
|
+
|
|
239
|
+
self.lbl_models_status = QLabel(self.tr("Status: (unknown)"))
|
|
240
|
+
self.lbl_models_status.setStyleSheet("color:#888;")
|
|
241
|
+
right_col.addRow(self.lbl_models_status)
|
|
242
|
+
|
|
243
|
+
self.btn_models_update = QPushButton(self.tr("Download/Update Models…"))
|
|
244
|
+
self.btn_models_update.clicked.connect(self._models_update_clicked)
|
|
245
|
+
|
|
246
|
+
self.btn_models_install_zip = QPushButton(self.tr("Install from ZIP…"))
|
|
247
|
+
self.btn_models_install_zip.setToolTip(self.tr("Use a manually downloaded models .zip file"))
|
|
248
|
+
self.btn_models_install_zip.clicked.connect(self._models_install_from_zip_clicked)
|
|
249
|
+
|
|
250
|
+
self.btn_models_open_drive = QPushButton(self.tr("Open Drive…"))
|
|
251
|
+
self.btn_models_open_drive.setToolTip(self.tr("Open the models folder in your browser (Primary/Backup)"))
|
|
252
|
+
self.btn_models_open_drive.clicked.connect(self._models_open_drive_clicked)
|
|
253
|
+
|
|
254
|
+
row_models = QHBoxLayout()
|
|
255
|
+
row_models.addWidget(self.btn_models_update, 1)
|
|
256
|
+
row_models.addWidget(self.btn_models_install_zip)
|
|
257
|
+
row_models.addWidget(self.btn_models_open_drive)
|
|
258
|
+
|
|
259
|
+
w_models = QWidget()
|
|
260
|
+
w_models.setLayout(row_models)
|
|
261
|
+
right_col.addRow(w_models)
|
|
262
|
+
|
|
185
263
|
# ---- Right column: WIMS + RA/Dec + Updates + Display ----
|
|
186
264
|
right_col.addRow(QLabel(self.tr("<b>What's In My Sky — Defaults</b>")))
|
|
187
265
|
right_col.addRow(self.tr("Latitude (°):"), self.sp_lat)
|
|
@@ -227,13 +305,298 @@ class SettingsDialog(QDialog):
|
|
|
227
305
|
btns = QDialogButtonBox(
|
|
228
306
|
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, parent=self
|
|
229
307
|
)
|
|
308
|
+
self.cb_accel_pref.currentIndexChanged.connect(self._accel_pref_changed)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
|
|
230
312
|
btns.accepted.connect(self._save_and_accept)
|
|
231
313
|
btns.rejected.connect(self.reject)
|
|
232
314
|
root.addWidget(btns)
|
|
233
|
-
|
|
315
|
+
self.install_accel_btn.clicked.connect(self._install_or_update_accel)
|
|
234
316
|
# Initial Load:
|
|
235
317
|
self.refresh_ui()
|
|
236
318
|
|
|
319
|
+
def _models_open_drive_clicked(self):
|
|
320
|
+
PRIMARY_FOLDER = "https://drive.google.com/drive/folders/1-fktZb3I9l-mQimJX2fZAmJCBj_t0yAF?usp=drive_link"
|
|
321
|
+
BACKUP_FOLDER = "https://drive.google.com/drive/folders/1j46RV6touQtOmtxkhdFWGm_LQKwEpTl9?usp=drive_link"
|
|
322
|
+
|
|
323
|
+
menu = QMenu(self)
|
|
324
|
+
act_primary = menu.addAction(self.tr("Primary"))
|
|
325
|
+
act_backup = menu.addAction(self.tr("Backup"))
|
|
326
|
+
|
|
327
|
+
chosen = menu.exec(self.btn_models_open_drive.mapToGlobal(self.btn_models_open_drive.rect().bottomLeft()))
|
|
328
|
+
if chosen == act_primary:
|
|
329
|
+
webbrowser.open(PRIMARY_FOLDER)
|
|
330
|
+
elif chosen == act_backup:
|
|
331
|
+
webbrowser.open(BACKUP_FOLDER)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _models_install_from_zip_clicked(self):
|
|
335
|
+
from PyQt6.QtWidgets import QFileDialog, QMessageBox, QProgressDialog
|
|
336
|
+
from PyQt6.QtCore import Qt, QThread
|
|
337
|
+
import os
|
|
338
|
+
|
|
339
|
+
zip_path, _ = QFileDialog.getOpenFileName(
|
|
340
|
+
self,
|
|
341
|
+
self.tr("Select models ZIP"),
|
|
342
|
+
"",
|
|
343
|
+
self.tr("ZIP files (*.zip);;All files (*)")
|
|
344
|
+
)
|
|
345
|
+
if not zip_path:
|
|
346
|
+
return
|
|
347
|
+
|
|
348
|
+
if not os.path.exists(zip_path):
|
|
349
|
+
QMessageBox.warning(self, self.tr("Models"), self.tr("File not found."))
|
|
350
|
+
return
|
|
351
|
+
|
|
352
|
+
self.btn_models_update.setEnabled(False)
|
|
353
|
+
self.btn_models_install_zip.setEnabled(False)
|
|
354
|
+
|
|
355
|
+
pd = QProgressDialog(self.tr("Preparing…"), self.tr("Cancel"), 0, 0, self)
|
|
356
|
+
pd.setWindowTitle(self.tr("Installing Models"))
|
|
357
|
+
pd.setWindowModality(Qt.WindowModality.ApplicationModal)
|
|
358
|
+
pd.setAutoClose(True)
|
|
359
|
+
pd.setMinimumDuration(0)
|
|
360
|
+
pd.show()
|
|
361
|
+
|
|
362
|
+
from setiastro.saspro.model_workers import ModelsInstallZipWorker
|
|
363
|
+
|
|
364
|
+
self._models_thread = QThread(self)
|
|
365
|
+
self._models_worker = ModelsInstallZipWorker(zip_path)
|
|
366
|
+
self._models_worker.moveToThread(self._models_thread)
|
|
367
|
+
|
|
368
|
+
self._models_thread.started.connect(self._models_worker.run, Qt.ConnectionType.QueuedConnection)
|
|
369
|
+
self._models_worker.progress.connect(pd.setLabelText, Qt.ConnectionType.QueuedConnection)
|
|
370
|
+
|
|
371
|
+
def _cancel():
|
|
372
|
+
if self._models_thread.isRunning():
|
|
373
|
+
self._models_thread.requestInterruption()
|
|
374
|
+
pd.canceled.connect(_cancel, Qt.ConnectionType.QueuedConnection)
|
|
375
|
+
|
|
376
|
+
def _done(ok: bool, msg: str):
|
|
377
|
+
pd.reset()
|
|
378
|
+
pd.deleteLater()
|
|
379
|
+
|
|
380
|
+
self._models_thread.quit()
|
|
381
|
+
self._models_thread.wait()
|
|
382
|
+
|
|
383
|
+
self.btn_models_update.setEnabled(True)
|
|
384
|
+
self.btn_models_install_zip.setEnabled(True)
|
|
385
|
+
self._refresh_models_status()
|
|
386
|
+
|
|
387
|
+
if ok:
|
|
388
|
+
QMessageBox.information(self, self.tr("Models"), self.tr("✅ {0}").format(msg))
|
|
389
|
+
else:
|
|
390
|
+
QMessageBox.warning(self, self.tr("Models"), self.tr("❌ {0}").format(msg))
|
|
391
|
+
|
|
392
|
+
self._models_worker.finished.connect(_done, Qt.ConnectionType.QueuedConnection)
|
|
393
|
+
self._models_thread.finished.connect(self._models_worker.deleteLater, Qt.ConnectionType.QueuedConnection)
|
|
394
|
+
self._models_thread.finished.connect(self._models_thread.deleteLater, Qt.ConnectionType.QueuedConnection)
|
|
395
|
+
|
|
396
|
+
self._models_thread.start()
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _models_update_clicked(self):
|
|
400
|
+
from PyQt6.QtWidgets import QMessageBox, QProgressDialog
|
|
401
|
+
from PyQt6.QtCore import Qt, QThread
|
|
402
|
+
|
|
403
|
+
# NOTE: these must be FILE links or file IDs, not folder links.
|
|
404
|
+
# Put your actual *zip file* share links here once you create them.
|
|
405
|
+
PRIMARY = "https://drive.google.com/file/d/1n4p0grtNpfllalMqtgaEmsTYaFhT5u7Y/view?usp=drive_link"
|
|
406
|
+
BACKUP = "https://drive.google.com/file/d/1uRGJCITlfMMN89ZkOO5ICWEKMH24KGit/view?usp=drive_link"
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
self.btn_models_update.setEnabled(False)
|
|
410
|
+
|
|
411
|
+
pd = QProgressDialog(self.tr("Preparing…"), self.tr("Cancel"), 0, 0, self)
|
|
412
|
+
pd.setWindowTitle(self.tr("Updating Models"))
|
|
413
|
+
pd.setWindowModality(Qt.WindowModality.ApplicationModal)
|
|
414
|
+
pd.setAutoClose(True)
|
|
415
|
+
pd.setMinimumDuration(0)
|
|
416
|
+
pd.show()
|
|
417
|
+
|
|
418
|
+
from setiastro.saspro.model_workers import ModelsDownloadWorker
|
|
419
|
+
|
|
420
|
+
self._models_thread = QThread(self)
|
|
421
|
+
self._models_worker = ModelsDownloadWorker(PRIMARY, BACKUP, expected_sha256=None)
|
|
422
|
+
self._models_worker.moveToThread(self._models_thread)
|
|
423
|
+
|
|
424
|
+
self._models_thread.started.connect(self._models_worker.run, Qt.ConnectionType.QueuedConnection)
|
|
425
|
+
self._models_worker.progress.connect(pd.setLabelText, Qt.ConnectionType.QueuedConnection)
|
|
426
|
+
def should_cancel():
|
|
427
|
+
return self._models_thread.isInterruptionRequested()
|
|
428
|
+
def _cancel():
|
|
429
|
+
if self._models_thread.isRunning():
|
|
430
|
+
self._models_thread.requestInterruption()
|
|
431
|
+
pd.canceled.connect(_cancel, Qt.ConnectionType.QueuedConnection)
|
|
432
|
+
|
|
433
|
+
def _done(ok: bool, msg: str):
|
|
434
|
+
pd.reset()
|
|
435
|
+
pd.deleteLater()
|
|
436
|
+
|
|
437
|
+
self._models_thread.quit()
|
|
438
|
+
self._models_thread.wait()
|
|
439
|
+
|
|
440
|
+
self.btn_models_update.setEnabled(True)
|
|
441
|
+
self._refresh_models_status()
|
|
442
|
+
|
|
443
|
+
if ok:
|
|
444
|
+
QMessageBox.information(self, self.tr("Models"), self.tr("✅ {0}").format(msg))
|
|
445
|
+
else:
|
|
446
|
+
QMessageBox.warning(self, self.tr("Models"), self.tr("❌ {0}").format(msg))
|
|
447
|
+
|
|
448
|
+
self._models_worker.finished.connect(_done, Qt.ConnectionType.QueuedConnection)
|
|
449
|
+
self._models_thread.finished.connect(self._models_worker.deleteLater, Qt.ConnectionType.QueuedConnection)
|
|
450
|
+
self._models_thread.finished.connect(self._models_thread.deleteLater, Qt.ConnectionType.QueuedConnection)
|
|
451
|
+
|
|
452
|
+
self._models_thread.start()
|
|
453
|
+
|
|
454
|
+
def _refresh_models_status(self):
|
|
455
|
+
from setiastro.saspro.model_manager import read_installed_manifest, models_root
|
|
456
|
+
m = read_installed_manifest()
|
|
457
|
+
if not m:
|
|
458
|
+
self.lbl_models_status.setText(self.tr("Status: not installed"))
|
|
459
|
+
return
|
|
460
|
+
fid = m.get("file_id", "")
|
|
461
|
+
self.lbl_models_status.setText(
|
|
462
|
+
self.tr("Status: installed\nLocation: {0}").format(models_root())
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def _accel_pref_changed(self, idx: int):
|
|
467
|
+
inv = {0:"auto", 1:"cuda", 2:"xpu", 3:"directml", 4:"cpu"}
|
|
468
|
+
val = inv.get(idx, "auto")
|
|
469
|
+
self.settings.setValue("accel/preferred_backend", val)
|
|
470
|
+
self.settings.sync()
|
|
471
|
+
|
|
472
|
+
def _show_gpu_accel_fix_help(self):
|
|
473
|
+
from PyQt6.QtWidgets import QMessageBox
|
|
474
|
+
QMessageBox.information(
|
|
475
|
+
self, self.tr("GPU Acceleration Help"),
|
|
476
|
+
self.tr(
|
|
477
|
+
"If GPU is not being used:\n"
|
|
478
|
+
" • Click Install/Update GPU Acceleration…\n"
|
|
479
|
+
" • Restart SAS Pro\n"
|
|
480
|
+
" • On NVIDIA systems, verify drivers and that 'nvidia-smi' works.\n"
|
|
481
|
+
" • On Windows non-NVIDIA, DirectML may be used.\n"
|
|
482
|
+
)
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
def _pkg_status(self, dist_name: str, import_name: str | None = None) -> tuple[bool, str]:
|
|
486
|
+
"""
|
|
487
|
+
Returns (installed?, display_text). Does NOT import the module.
|
|
488
|
+
dist_name = pip distribution name used for version lookup (e.g., 'torchvision')
|
|
489
|
+
import_name = python import name used for find_spec (e.g., 'torch_directml')
|
|
490
|
+
"""
|
|
491
|
+
mod = import_name or dist_name.replace("-", "_")
|
|
492
|
+
present = importlib.util.find_spec(mod) is not None
|
|
493
|
+
|
|
494
|
+
ver = ""
|
|
495
|
+
if present:
|
|
496
|
+
try:
|
|
497
|
+
ver = importlib.metadata.version(dist_name)
|
|
498
|
+
except importlib.metadata.PackageNotFoundError:
|
|
499
|
+
# Sometimes dist name differs; fall back to "installed" without version
|
|
500
|
+
ver = ""
|
|
501
|
+
except Exception:
|
|
502
|
+
ver = ""
|
|
503
|
+
|
|
504
|
+
if present:
|
|
505
|
+
return True, (f"✅ {ver}" if ver else "✅ installed")
|
|
506
|
+
return False, "— not installed"
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def _format_accel_deps_text(self) -> str:
|
|
510
|
+
# Torch + friends
|
|
511
|
+
torch_ok, torch_txt = self._pkg_status("torch", "torch")
|
|
512
|
+
dml_ok, dml_txt = self._pkg_status("torch-directml", "torch_directml")
|
|
513
|
+
ta_ok, ta_txt = self._pkg_status("torchaudio", "torchaudio")
|
|
514
|
+
tv_ok, tv_txt = self._pkg_status("torchvision", "torchvision")
|
|
515
|
+
|
|
516
|
+
# Pretty, compact, and stable in a QLabel
|
|
517
|
+
return (
|
|
518
|
+
f"Torch: <b>{torch_txt}</b><br>"
|
|
519
|
+
f"Torch-DirectML: <b>{dml_txt}</b><br>"
|
|
520
|
+
f"TorchAudio: <b>{ta_txt}</b><br>"
|
|
521
|
+
f"TorchVision: <b>{tv_txt}</b>"
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def _install_or_update_accel(self):
|
|
526
|
+
import sys, platform
|
|
527
|
+
from PyQt6.QtWidgets import QMessageBox, QProgressDialog
|
|
528
|
+
from PyQt6.QtCore import Qt, QThread
|
|
529
|
+
from setiastro.saspro.accel_installer import current_backend
|
|
530
|
+
from setiastro.saspro.accel_workers import AccelInstallWorker # wherever yours lives
|
|
531
|
+
|
|
532
|
+
v = sys.version_info
|
|
533
|
+
if not (v.major == 3 and v.minor in (10, 11, 12)):
|
|
534
|
+
why = self.tr("This app is running on Python {0}.{1}. GPU acceleration requires Python 3.10, 3.11, or 3.12.").format(v.major, v.minor)
|
|
535
|
+
tip = ""
|
|
536
|
+
sysname = platform.system()
|
|
537
|
+
if sysname == "Darwin":
|
|
538
|
+
tip = self.tr("\n\nmacOS tip (Apple Silicon):\n • Install Python 3.12: brew install python@3.12\n • Relaunch the app.")
|
|
539
|
+
elif sysname == "Windows":
|
|
540
|
+
tip = self.tr("\n\nWindows tip:\n • Install Python 3.12/3.11/3.10 (x64) from python.org\n • Relaunch the app.")
|
|
541
|
+
else:
|
|
542
|
+
tip = self.tr("\n\nLinux tip:\n • Install python3.12 or 3.11 via your package manager\n • Relaunch the app.")
|
|
543
|
+
|
|
544
|
+
QMessageBox.warning(self, self.tr("Unsupported Python Version"), why + tip)
|
|
545
|
+
self.backend_label.setText(self.tr("Backend: CPU (Python version not supported for GPU install)"))
|
|
546
|
+
return
|
|
547
|
+
|
|
548
|
+
self.install_accel_btn.setEnabled(False)
|
|
549
|
+
self.backend_label.setText(self.tr("Backend: installing…"))
|
|
550
|
+
|
|
551
|
+
pref = (self.settings.value("accel/preferred_backend", "auto", type=str) or "auto").lower()
|
|
552
|
+
|
|
553
|
+
self._accel_pd = QProgressDialog(self.tr("Preparing runtime…"), self.tr("Cancel"), 0, 0, self)
|
|
554
|
+
self._accel_pd.setWindowTitle(self.tr("Installing GPU Acceleration"))
|
|
555
|
+
self._accel_pd.setWindowModality(Qt.WindowModality.ApplicationModal)
|
|
556
|
+
self._accel_pd.setAutoClose(True)
|
|
557
|
+
self._accel_pd.setMinimumDuration(0)
|
|
558
|
+
self._accel_pd.show()
|
|
559
|
+
|
|
560
|
+
self._accel_thread = QThread(self)
|
|
561
|
+
self._accel_worker = AccelInstallWorker(prefer_gpu=True, preferred_backend=pref)
|
|
562
|
+
self._accel_worker.moveToThread(self._accel_thread)
|
|
563
|
+
|
|
564
|
+
self._accel_thread.started.connect(self._accel_worker.run, Qt.ConnectionType.QueuedConnection)
|
|
565
|
+
self._accel_worker.progress.connect(self._accel_pd.setLabelText, Qt.ConnectionType.QueuedConnection)
|
|
566
|
+
|
|
567
|
+
def _cancel():
|
|
568
|
+
if self._accel_thread.isRunning():
|
|
569
|
+
self._accel_thread.requestInterruption()
|
|
570
|
+
self._accel_pd.canceled.connect(_cancel, Qt.ConnectionType.QueuedConnection)
|
|
571
|
+
|
|
572
|
+
def _done(ok: bool, msg: str):
|
|
573
|
+
if getattr(self, "_accel_pd", None):
|
|
574
|
+
self._accel_pd.reset()
|
|
575
|
+
self._accel_pd.deleteLater()
|
|
576
|
+
self._accel_pd = None
|
|
577
|
+
|
|
578
|
+
self._accel_thread.quit()
|
|
579
|
+
self._accel_thread.wait()
|
|
580
|
+
|
|
581
|
+
self.install_accel_btn.setEnabled(True)
|
|
582
|
+
self.backend_label.setText(self.tr("Backend: {0}").format(current_backend()))
|
|
583
|
+
try:
|
|
584
|
+
self.accel_deps_label.setText(self._format_accel_deps_text())
|
|
585
|
+
except Exception:
|
|
586
|
+
pass
|
|
587
|
+
|
|
588
|
+
if ok:
|
|
589
|
+
QMessageBox.information(self, self.tr("Acceleration"), self.tr("✅ {0}").format(msg))
|
|
590
|
+
else:
|
|
591
|
+
QMessageBox.warning(self, self.tr("Acceleration"), self.tr("❌ {0}").format(msg))
|
|
592
|
+
|
|
593
|
+
self._accel_worker.finished.connect(_done, Qt.ConnectionType.QueuedConnection)
|
|
594
|
+
self._accel_thread.finished.connect(self._accel_worker.deleteLater, Qt.ConnectionType.QueuedConnection)
|
|
595
|
+
self._accel_thread.finished.connect(self._accel_thread.deleteLater, Qt.ConnectionType.QueuedConnection)
|
|
596
|
+
|
|
597
|
+
self._accel_thread.start()
|
|
598
|
+
|
|
599
|
+
|
|
237
600
|
def refresh_ui(self):
|
|
238
601
|
"""
|
|
239
602
|
Reloads all settings from self.settings and updates the UI widgets.
|
|
@@ -279,7 +642,7 @@ class SettingsDialog(QDialog):
|
|
|
279
642
|
|
|
280
643
|
# Path fields
|
|
281
644
|
self.le_graxpert.setText(self.settings.value("paths/graxpert", "", type=str))
|
|
282
|
-
|
|
645
|
+
|
|
283
646
|
self.le_starnet.setText(self.settings.value("paths/starnet", "", type=str))
|
|
284
647
|
self.le_astap.setText(self.settings.value("paths/astap", "", type=str))
|
|
285
648
|
self.le_astrometry.setText(self.settings.value("api/astrometry_key", "", type=str))
|
|
@@ -330,6 +693,29 @@ class SettingsDialog(QDialog):
|
|
|
330
693
|
self.sp_wcs_step.setEnabled(self.cb_wcs_mode.currentIndex() == 1)
|
|
331
694
|
self.sp_wcs_step.setSuffix(" °" if self.cb_wcs_unit.currentIndex() == 0 else " arcmin")
|
|
332
695
|
|
|
696
|
+
pref = (self.settings.value("accel/preferred_backend", "auto", type=str) or "auto").lower()
|
|
697
|
+
|
|
698
|
+
# map stored -> combobox index (adjust if you removed DirectML on non-Windows)
|
|
699
|
+
idx_map = {"auto": 0, "cuda": 1, "xpu": 2, "directml": 3, "cpu": 4}
|
|
700
|
+
|
|
701
|
+
idx = idx_map.get(pref, 0)
|
|
702
|
+
# if non-Windows and directml was saved earlier, clamp to Auto
|
|
703
|
+
if platform.system() != "Windows" and pref == "directml":
|
|
704
|
+
idx = 0
|
|
705
|
+
|
|
706
|
+
self.cb_accel_pref.setCurrentIndex(idx)
|
|
707
|
+
|
|
708
|
+
from setiastro.saspro.accel_installer import current_backend
|
|
709
|
+
self.backend_label.setText(self.tr("Backend: {0}").format(current_backend()))
|
|
710
|
+
try:
|
|
711
|
+
self.accel_deps_label.setText(self._format_accel_deps_text())
|
|
712
|
+
except Exception:
|
|
713
|
+
self.accel_deps_label.setText(self.tr("Torch: — unknown"))
|
|
714
|
+
try:
|
|
715
|
+
self._refresh_models_status()
|
|
716
|
+
except Exception:
|
|
717
|
+
pass
|
|
718
|
+
|
|
333
719
|
def reject(self):
|
|
334
720
|
"""User cancelled: restore the original background opacity (revert live changes)."""
|
|
335
721
|
try:
|
|
@@ -439,7 +825,7 @@ class SettingsDialog(QDialog):
|
|
|
439
825
|
def _save_and_accept(self):
|
|
440
826
|
# Paths / Integrations
|
|
441
827
|
self.settings.setValue("paths/graxpert", self.le_graxpert.text().strip())
|
|
442
|
-
|
|
828
|
+
|
|
443
829
|
self.settings.setValue("paths/starnet", self.le_starnet.text().strip())
|
|
444
830
|
self.settings.setValue("paths/astap", self.le_astap.text().strip())
|
|
445
831
|
self.settings.setValue("shortcuts/save_on_exit", self.chk_save_shortcuts.isChecked())
|
|
@@ -466,6 +852,12 @@ class SettingsDialog(QDialog):
|
|
|
466
852
|
self.settings.setValue("updates/url", self.le_updates_url.text().strip())
|
|
467
853
|
self.settings.setValue("display/autostretch_24bit", self.chk_autostretch_24bit.isChecked())
|
|
468
854
|
|
|
855
|
+
# accel preference
|
|
856
|
+
pref_idx = self.cb_accel_pref.currentIndex()
|
|
857
|
+
# map index -> stored string (again, adjust if DirectML removed on non-Windows)
|
|
858
|
+
inv = {0:"auto", 1:"cuda", 2:"xpu", 3:"directml", 4:"cpu"}
|
|
859
|
+
self.settings.setValue("accel/preferred_backend", inv.get(pref_idx, "auto"))
|
|
860
|
+
|
|
469
861
|
# Custom background: persist the chosen path (empty -> remove)
|
|
470
862
|
bg_path = (self.le_bg_path.text() or "").strip()
|
|
471
863
|
if bg_path:
|
|
@@ -520,6 +912,11 @@ class SettingsDialog(QDialog):
|
|
|
520
912
|
if hasattr(p, "mdi") and hasattr(p.mdi, "viewport"):
|
|
521
913
|
p.mdi.viewport().update()
|
|
522
914
|
|
|
915
|
+
try:
|
|
916
|
+
self.settings.remove("paths/cosmic_clarity")
|
|
917
|
+
except Exception:
|
|
918
|
+
pass
|
|
919
|
+
|
|
523
920
|
self.accept()
|
|
524
921
|
|
|
525
922
|
from PyQt6.QtGui import QColor, QFont
|