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
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# src/setiastro/saspro/ops/benchmark.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from PyQt6.QtCore import QThread, pyqtSignal, QObject
|
|
6
|
+
from PyQt6.QtWidgets import (
|
|
7
|
+
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QComboBox,
|
|
8
|
+
QProgressBar, QTextEdit, QMessageBox, QApplication
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from setiastro.saspro.cosmicclarity_engines.benchmark_engine import (
|
|
12
|
+
benchmark_image_path, download_benchmark_image, run_benchmark, # keep your run_benchmark
|
|
13
|
+
)
|
|
14
|
+
from setiastro.saspro.cosmicclarity_engines.benchmark_engine import BENCHMARK_FITS_URL # or define here
|
|
15
|
+
|
|
16
|
+
class _BenchWorker(QObject):
|
|
17
|
+
log = pyqtSignal(str)
|
|
18
|
+
prog = pyqtSignal(int, int) # done,total
|
|
19
|
+
done = pyqtSignal(bool, dict, str) # ok, results, err
|
|
20
|
+
|
|
21
|
+
def __init__(self, mode: str, use_gpu: bool):
|
|
22
|
+
super().__init__()
|
|
23
|
+
self.mode = mode
|
|
24
|
+
self.use_gpu = use_gpu
|
|
25
|
+
|
|
26
|
+
def run(self):
|
|
27
|
+
try:
|
|
28
|
+
def status_cb(s: str):
|
|
29
|
+
self.log.emit(str(s))
|
|
30
|
+
|
|
31
|
+
def progress_cb(done: int, total: int) -> bool:
|
|
32
|
+
self.prog.emit(int(done), int(total))
|
|
33
|
+
return not QThread.currentThread().isInterruptionRequested()
|
|
34
|
+
|
|
35
|
+
results = run_benchmark(
|
|
36
|
+
mode=self.mode,
|
|
37
|
+
use_gpu=self.use_gpu,
|
|
38
|
+
status_cb=status_cb,
|
|
39
|
+
progress_cb=progress_cb,
|
|
40
|
+
)
|
|
41
|
+
self.done.emit(True, results, "")
|
|
42
|
+
except Exception as e:
|
|
43
|
+
msg = str(e)
|
|
44
|
+
if "Canceled" in msg or "cancel" in msg.lower():
|
|
45
|
+
self.done.emit(False, {}, "Canceled.")
|
|
46
|
+
else:
|
|
47
|
+
self.done.emit(False, {}, msg)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class _DownloadWorker(QObject):
|
|
51
|
+
log = pyqtSignal(str)
|
|
52
|
+
prog = pyqtSignal(int, int) # bytes_done, bytes_total
|
|
53
|
+
done = pyqtSignal(bool, str) # ok, message/path
|
|
54
|
+
|
|
55
|
+
def __init__(self, url: str):
|
|
56
|
+
super().__init__()
|
|
57
|
+
self.url = url
|
|
58
|
+
|
|
59
|
+
def run(self):
|
|
60
|
+
try:
|
|
61
|
+
def status_cb(s: str):
|
|
62
|
+
self.log.emit(str(s))
|
|
63
|
+
|
|
64
|
+
def progress_cb(done: int, total: int):
|
|
65
|
+
self.prog.emit(int(done), int(total))
|
|
66
|
+
|
|
67
|
+
def cancel_cb() -> bool:
|
|
68
|
+
return QThread.currentThread().isInterruptionRequested()
|
|
69
|
+
|
|
70
|
+
p = download_benchmark_image(
|
|
71
|
+
None,
|
|
72
|
+
status_cb=status_cb,
|
|
73
|
+
progress_cb=progress_cb,
|
|
74
|
+
cancel_cb=cancel_cb,
|
|
75
|
+
)
|
|
76
|
+
self.done.emit(True, str(p))
|
|
77
|
+
except Exception as e:
|
|
78
|
+
self.done.emit(False, str(e))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class BenchmarkDialog(QDialog):
|
|
82
|
+
def __init__(self, parent=None):
|
|
83
|
+
super().__init__(parent)
|
|
84
|
+
self.setWindowTitle("Seti Astro Benchmark")
|
|
85
|
+
self.setModal(False)
|
|
86
|
+
self.setMinimumSize(560, 520)
|
|
87
|
+
|
|
88
|
+
self._results = None
|
|
89
|
+
self._thread = None
|
|
90
|
+
|
|
91
|
+
outer = QVBoxLayout(self)
|
|
92
|
+
outer.setContentsMargins(10, 10, 10, 10)
|
|
93
|
+
outer.setSpacing(8)
|
|
94
|
+
|
|
95
|
+
# Top row: image status + download
|
|
96
|
+
top = QHBoxLayout()
|
|
97
|
+
self.lbl_img = QLabel(self)
|
|
98
|
+
self.btn_dl = QPushButton("Download Benchmark Image…", self)
|
|
99
|
+
self.btn_dl.clicked.connect(self._download_image)
|
|
100
|
+
top.addWidget(self.lbl_img, 1)
|
|
101
|
+
top.addWidget(self.btn_dl)
|
|
102
|
+
outer.addLayout(top)
|
|
103
|
+
|
|
104
|
+
# Mode row
|
|
105
|
+
row = QHBoxLayout()
|
|
106
|
+
row.addWidget(QLabel("Run:", self))
|
|
107
|
+
self.cmb = QComboBox(self)
|
|
108
|
+
self.cmb.addItems(["CPU", "GPU", "Both"])
|
|
109
|
+
self.cmb.setCurrentText("Both")
|
|
110
|
+
row.addWidget(self.cmb)
|
|
111
|
+
|
|
112
|
+
self.btn_run = QPushButton("Run Benchmark", self)
|
|
113
|
+
self.btn_run.clicked.connect(self._run_benchmark)
|
|
114
|
+
row.addWidget(self.btn_run)
|
|
115
|
+
row.addStretch(1)
|
|
116
|
+
outer.addLayout(row)
|
|
117
|
+
|
|
118
|
+
# Progress
|
|
119
|
+
self.pbar = QProgressBar(self)
|
|
120
|
+
self.pbar.setRange(0, 100)
|
|
121
|
+
outer.addWidget(self.pbar)
|
|
122
|
+
|
|
123
|
+
# Log / results
|
|
124
|
+
self.txt = QTextEdit(self)
|
|
125
|
+
self.txt.setReadOnly(True)
|
|
126
|
+
outer.addWidget(self.txt, 1)
|
|
127
|
+
|
|
128
|
+
# Bottom buttons
|
|
129
|
+
bot = QHBoxLayout()
|
|
130
|
+
self.btn_copy = QPushButton("Copy JSON", self)
|
|
131
|
+
self.btn_copy.setEnabled(False)
|
|
132
|
+
self.btn_copy.clicked.connect(self._copy_json)
|
|
133
|
+
|
|
134
|
+
self.btn_save = QPushButton("Save Locally", self)
|
|
135
|
+
self.btn_save.setEnabled(False)
|
|
136
|
+
self.btn_save.clicked.connect(self._save_local)
|
|
137
|
+
|
|
138
|
+
self.btn_submit = QPushButton("Submit…", self)
|
|
139
|
+
self.btn_submit.clicked.connect(self._submit)
|
|
140
|
+
|
|
141
|
+
self.btn_close = QPushButton("Close", self)
|
|
142
|
+
self.btn_close.clicked.connect(self.close)
|
|
143
|
+
|
|
144
|
+
bot.addWidget(self.btn_copy)
|
|
145
|
+
bot.addWidget(self.btn_save)
|
|
146
|
+
bot.addStretch(1)
|
|
147
|
+
bot.addWidget(self.btn_submit)
|
|
148
|
+
bot.addWidget(self.btn_close)
|
|
149
|
+
outer.addLayout(bot)
|
|
150
|
+
|
|
151
|
+
self.refresh_ui()
|
|
152
|
+
|
|
153
|
+
def refresh_ui(self):
|
|
154
|
+
p = benchmark_image_path()
|
|
155
|
+
if p.exists():
|
|
156
|
+
self.lbl_img.setText(f"Benchmark image: Ready ({p.name})")
|
|
157
|
+
self.btn_run.setEnabled(True)
|
|
158
|
+
else:
|
|
159
|
+
self.lbl_img.setText("Benchmark image: Not downloaded")
|
|
160
|
+
self.btn_run.setEnabled(False)
|
|
161
|
+
|
|
162
|
+
self.pbar.setValue(0)
|
|
163
|
+
|
|
164
|
+
# ---------- download ----------
|
|
165
|
+
def _download_image(self):
|
|
166
|
+
self._stop_thread_if_any()
|
|
167
|
+
|
|
168
|
+
self.txt.append("Starting download…")
|
|
169
|
+
self.btn_dl.setEnabled(False)
|
|
170
|
+
self.btn_run.setEnabled(False)
|
|
171
|
+
self.pbar.setValue(0)
|
|
172
|
+
|
|
173
|
+
t = QThread(self)
|
|
174
|
+
w = _DownloadWorker(BENCHMARK_FITS_URL)
|
|
175
|
+
w.moveToThread(t)
|
|
176
|
+
|
|
177
|
+
w.log.connect(self._log)
|
|
178
|
+
w.prog.connect(self._dl_progress)
|
|
179
|
+
w.done.connect(lambda ok, msg: self._dl_done(ok, msg, t, w))
|
|
180
|
+
t.started.connect(w.run)
|
|
181
|
+
t.start()
|
|
182
|
+
self._thread = t
|
|
183
|
+
|
|
184
|
+
def _dl_progress(self, done: int, total: int):
|
|
185
|
+
if total > 0:
|
|
186
|
+
pct = int(done * 100 / total)
|
|
187
|
+
self.pbar.setRange(0, 100)
|
|
188
|
+
self.pbar.setValue(max(0, min(100, pct)))
|
|
189
|
+
|
|
190
|
+
# 👇 add this
|
|
191
|
+
self.pbar.setFormat(f"{pct}% ({done/1e6:.0f}/{total/1e6:.0f} MB)")
|
|
192
|
+
else:
|
|
193
|
+
# unknown length
|
|
194
|
+
self.pbar.setRange(0, 0)
|
|
195
|
+
self.pbar.setFormat(f"{done/1e6:.0f} MB")
|
|
196
|
+
|
|
197
|
+
def _dl_done(self, ok: bool, msg: str, t: QThread, w: QObject):
|
|
198
|
+
t.quit(); t.wait()
|
|
199
|
+
self._thread = None
|
|
200
|
+
|
|
201
|
+
self.pbar.setRange(0, 100)
|
|
202
|
+
self.pbar.setFormat("%p%")
|
|
203
|
+
self.btn_dl.setEnabled(True)
|
|
204
|
+
|
|
205
|
+
if ok:
|
|
206
|
+
self._log(f"✅ Downloaded: {msg}")
|
|
207
|
+
self.refresh_ui()
|
|
208
|
+
else:
|
|
209
|
+
self._log(f"❌ Download failed: {msg}")
|
|
210
|
+
QMessageBox.warning(self, "Download failed", msg)
|
|
211
|
+
self.refresh_ui()
|
|
212
|
+
|
|
213
|
+
def closeEvent(self, e):
|
|
214
|
+
self._stop_thread_if_any()
|
|
215
|
+
self.pbar.setRange(0, 100)
|
|
216
|
+
super().closeEvent(e)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# ---------- run benchmark ----------
|
|
220
|
+
def _run_benchmark(self):
|
|
221
|
+
p = benchmark_image_path()
|
|
222
|
+
if not p.exists():
|
|
223
|
+
QMessageBox.information(self, "Benchmark image missing", "Please download the benchmark image first.")
|
|
224
|
+
return
|
|
225
|
+
|
|
226
|
+
self._stop_thread_if_any()
|
|
227
|
+
self.btn_run.setEnabled(False)
|
|
228
|
+
self.btn_dl.setEnabled(False)
|
|
229
|
+
self._results = None
|
|
230
|
+
self.btn_copy.setEnabled(False)
|
|
231
|
+
self.btn_save.setEnabled(False)
|
|
232
|
+
|
|
233
|
+
self.txt.clear()
|
|
234
|
+
self._log("Running benchmark…")
|
|
235
|
+
self.pbar.setValue(0)
|
|
236
|
+
|
|
237
|
+
mode = self.cmb.currentText()
|
|
238
|
+
use_gpu = True # benchmark engine will pick CPU if no CUDA/DML
|
|
239
|
+
|
|
240
|
+
t = QThread(self)
|
|
241
|
+
w = _BenchWorker(mode=mode, use_gpu=use_gpu)
|
|
242
|
+
w.moveToThread(t)
|
|
243
|
+
|
|
244
|
+
w.log.connect(self._log)
|
|
245
|
+
w.prog.connect(self._bench_progress)
|
|
246
|
+
w.done.connect(lambda ok, results, err: self._bench_done(ok, results, err, t, w))
|
|
247
|
+
t.started.connect(w.run)
|
|
248
|
+
t.start()
|
|
249
|
+
self._thread = t
|
|
250
|
+
|
|
251
|
+
def _bench_progress(self, done: int, total: int):
|
|
252
|
+
if total > 0:
|
|
253
|
+
self.pbar.setValue(int(done * 100 / total))
|
|
254
|
+
QApplication.processEvents()
|
|
255
|
+
|
|
256
|
+
def _bench_done(self, ok: bool, results: dict, err: str, t: QThread, w: QObject):
|
|
257
|
+
t.quit(); t.wait()
|
|
258
|
+
self._thread = None
|
|
259
|
+
self.pbar.setValue(100 if ok else 0)
|
|
260
|
+
self.btn_run.setEnabled(True)
|
|
261
|
+
self.btn_dl.setEnabled(True)
|
|
262
|
+
if not ok:
|
|
263
|
+
self._log(f"❌ Benchmark failed: {err}")
|
|
264
|
+
if str(err).strip().lower().startswith("canceled"):
|
|
265
|
+
# no scary dialog for user-cancel
|
|
266
|
+
self.pbar.setValue(0)
|
|
267
|
+
return
|
|
268
|
+
QMessageBox.warning(self, "Benchmark failed", err)
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
self._results = results
|
|
272
|
+
self._log("✅ Benchmark complete.\n")
|
|
273
|
+
self._log(json.dumps([results], indent=2))
|
|
274
|
+
|
|
275
|
+
self.btn_copy.setEnabled(True)
|
|
276
|
+
self.btn_save.setEnabled(True)
|
|
277
|
+
|
|
278
|
+
# ---------- actions ----------
|
|
279
|
+
def _copy_json(self):
|
|
280
|
+
if not self._results:
|
|
281
|
+
return
|
|
282
|
+
s = json.dumps([self._results], indent=4)
|
|
283
|
+
QApplication.clipboard().setText(s)
|
|
284
|
+
QMessageBox.information(self, "Copied", "Benchmark JSON copied to clipboard.")
|
|
285
|
+
|
|
286
|
+
def _save_local(self):
|
|
287
|
+
if not self._results:
|
|
288
|
+
return
|
|
289
|
+
# reuse your existing helper if you want; simplest local save:
|
|
290
|
+
import os, time
|
|
291
|
+
fn = "benchmark_results.json"
|
|
292
|
+
try:
|
|
293
|
+
if os.path.exists(fn):
|
|
294
|
+
with open(fn, "r", encoding="utf-8") as f:
|
|
295
|
+
try:
|
|
296
|
+
allr = json.load(f)
|
|
297
|
+
except Exception:
|
|
298
|
+
allr = []
|
|
299
|
+
else:
|
|
300
|
+
allr = []
|
|
301
|
+
allr.append(self._results)
|
|
302
|
+
with open(fn, "w", encoding="utf-8") as f:
|
|
303
|
+
json.dump(allr, f, indent=4)
|
|
304
|
+
self._log(f"\n✅ Saved to {fn}")
|
|
305
|
+
except Exception as e:
|
|
306
|
+
QMessageBox.warning(self, "Save failed", str(e))
|
|
307
|
+
|
|
308
|
+
def _submit(self):
|
|
309
|
+
import webbrowser
|
|
310
|
+
webbrowser.open("https://setiastro.com/benchmark-submit")
|
|
311
|
+
|
|
312
|
+
def _log(self, s: str):
|
|
313
|
+
self.txt.append(str(s))
|
|
314
|
+
|
|
315
|
+
def _stop_thread_if_any(self):
|
|
316
|
+
if self._thread is not None and self._thread.isRunning():
|
|
317
|
+
self._thread.requestInterruption()
|
|
318
|
+
self._thread.quit()
|
|
319
|
+
self._thread.wait()
|
|
320
|
+
self._thread = None
|