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.

Files changed (27) hide show
  1. setiastro/saspro/_generated/build_info.py +2 -2
  2. setiastro/saspro/accel_installer.py +21 -8
  3. setiastro/saspro/accel_workers.py +11 -12
  4. setiastro/saspro/comet_stacking.py +113 -85
  5. setiastro/saspro/cosmicclarity.py +604 -826
  6. setiastro/saspro/cosmicclarity_engines/benchmark_engine.py +732 -0
  7. setiastro/saspro/cosmicclarity_engines/darkstar_engine.py +576 -0
  8. setiastro/saspro/cosmicclarity_engines/denoise_engine.py +567 -0
  9. setiastro/saspro/cosmicclarity_engines/satellite_engine.py +620 -0
  10. setiastro/saspro/cosmicclarity_engines/sharpen_engine.py +587 -0
  11. setiastro/saspro/cosmicclarity_engines/superres_engine.py +412 -0
  12. setiastro/saspro/gui/main_window.py +14 -0
  13. setiastro/saspro/gui/mixins/menu_mixin.py +2 -0
  14. setiastro/saspro/model_manager.py +324 -0
  15. setiastro/saspro/model_workers.py +102 -0
  16. setiastro/saspro/ops/benchmark.py +320 -0
  17. setiastro/saspro/ops/settings.py +407 -10
  18. setiastro/saspro/remove_stars.py +424 -442
  19. setiastro/saspro/resources.py +73 -10
  20. setiastro/saspro/runtime_torch.py +107 -22
  21. setiastro/saspro/signature_insert.py +14 -3
  22. {setiastrosuitepro-1.7.5.post1.dist-info → setiastrosuitepro-1.8.0.post3.dist-info}/METADATA +2 -1
  23. {setiastrosuitepro-1.7.5.post1.dist-info → setiastrosuitepro-1.8.0.post3.dist-info}/RECORD +27 -18
  24. {setiastrosuitepro-1.7.5.post1.dist-info → setiastrosuitepro-1.8.0.post3.dist-info}/WHEEL +0 -0
  25. {setiastrosuitepro-1.7.5.post1.dist-info → setiastrosuitepro-1.8.0.post3.dist-info}/entry_points.txt +0 -0
  26. {setiastrosuitepro-1.7.5.post1.dist-info → setiastrosuitepro-1.8.0.post3.dist-info}/licenses/LICENSE +0 -0
  27. {setiastrosuitepro-1.7.5.post1.dist-info → setiastrosuitepro-1.8.0.post3.dist-info}/licenses/license.txt +0 -0
@@ -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
- self.le_cosmic = QLineEdit()
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
- btn_ccl = QPushButton(self.tr("Browse…")); btn_ccl.clicked.connect(lambda: self._browse_dir(self.le_cosmic))
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
- row_ccl = QHBoxLayout(); row_ccl.addWidget(self.le_cosmic); row_ccl.addWidget(btn_ccl)
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
- w = QWidget(); w.setLayout(row_ccl); left_col.addRow(self.tr("Cosmic Clarity folder:"), w)
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
- self.le_cosmic.setText(self.settings.value("paths/cosmic_clarity", "", type=str))
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
- self.settings.setValue("paths/cosmic_clarity", self.le_cosmic.text().strip())
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