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.
- 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
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
# Auto-generated at build time. Do not edit.
|
|
2
|
-
BUILD_TIMESTAMP = "2026-01-
|
|
3
|
-
APP_VERSION = "1.
|
|
2
|
+
BUILD_TIMESTAMP = "2026-01-26T19:14:34Z"
|
|
3
|
+
APP_VERSION = "1.8.0.post3"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# saspro/accel_installer.py
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
import platform
|
|
4
4
|
import subprocess
|
|
@@ -77,21 +77,34 @@ def _nvidia_driver_ok(log_cb: LogCB) -> bool:
|
|
|
77
77
|
return False
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
def ensure_torch_installed(prefer_gpu: bool, log_cb: LogCB) -> tuple[bool, Optional[str]]:
|
|
80
|
+
def ensure_torch_installed(prefer_gpu: bool, log_cb: LogCB, preferred_backend: str = "auto") -> tuple[bool, Optional[str]]:
|
|
81
|
+
|
|
81
82
|
try:
|
|
83
|
+
preferred_backend = (preferred_backend or "auto").lower()
|
|
82
84
|
is_windows = platform.system() == "Windows"
|
|
85
|
+
|
|
83
86
|
has_nv = _has_nvidia() and platform.system() in ("Windows", "Linux")
|
|
84
87
|
has_intel = (not has_nv) and _has_intel_arc() and platform.system() in ("Windows", "Linux")
|
|
85
88
|
|
|
86
|
-
prefer_cuda = prefer_gpu and has_nv
|
|
87
|
-
prefer_xpu = prefer_gpu and (not has_nv) and has_intel
|
|
89
|
+
prefer_cuda = prefer_gpu and (preferred_backend in ("auto", "cuda")) and has_nv
|
|
90
|
+
prefer_xpu = prefer_gpu and (preferred_backend in ("auto", "xpu")) and (not has_nv) and has_intel
|
|
88
91
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
# NEW: DirectML preference is ONLY meaningful on Windows with no NVIDIA
|
|
93
|
+
prefer_dml = (
|
|
94
|
+
is_windows
|
|
95
|
+
and (not has_nv)
|
|
96
|
+
and preferred_backend in ("directml", "auto")
|
|
97
|
+
and prefer_gpu
|
|
98
|
+
and (not prefer_xpu) # if Intel XPU actually works, prefer that over DML
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# If user forced CPU, disable everything
|
|
102
|
+
if preferred_backend == "cpu":
|
|
103
|
+
prefer_cuda = prefer_xpu = prefer_dml = False
|
|
92
104
|
|
|
93
105
|
# Install torch (tries CUDA → XPU → CPU)
|
|
94
|
-
torch = import_torch(prefer_cuda=prefer_cuda, prefer_xpu=prefer_xpu, status_cb=log_cb)
|
|
106
|
+
torch = import_torch(prefer_cuda=prefer_cuda, prefer_xpu=prefer_xpu, prefer_dml=prefer_dml, status_cb=log_cb)
|
|
107
|
+
|
|
95
108
|
|
|
96
109
|
cuda_ok = bool(getattr(torch, "cuda", None) and torch.cuda.is_available())
|
|
97
110
|
xpu_ok = bool(hasattr(torch, "xpu") and torch.xpu.is_available())
|
|
@@ -1,25 +1,24 @@
|
|
|
1
|
-
#
|
|
1
|
+
# saspro/accel_workers.py
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
from PyQt6.QtCore import QObject, pyqtSignal, QThread
|
|
4
4
|
from setiastro.saspro.accel_installer import ensure_torch_installed
|
|
5
5
|
|
|
6
6
|
class AccelInstallWorker(QObject):
|
|
7
|
-
progress = pyqtSignal(str) # emitted from worker thread
|
|
7
|
+
progress = pyqtSignal(str) # emitted from worker thread
|
|
8
8
|
finished = pyqtSignal(bool, str) # (ok, message)
|
|
9
9
|
|
|
10
|
-
def __init__(self, prefer_gpu: bool = True):
|
|
10
|
+
def __init__(self, prefer_gpu: bool = True, preferred_backend: str = "auto"):
|
|
11
11
|
super().__init__()
|
|
12
|
-
self.prefer_gpu = prefer_gpu
|
|
13
|
-
|
|
14
|
-
def _log(self, s: str):
|
|
15
|
-
# Never touch widgets here; just emit text
|
|
16
|
-
self.progress.emit(s)
|
|
12
|
+
self.prefer_gpu = bool(prefer_gpu)
|
|
13
|
+
self.preferred_backend = (preferred_backend or "auto").lower()
|
|
17
14
|
|
|
18
15
|
def run(self):
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
ok, msg = ensure_torch_installed(
|
|
17
|
+
prefer_gpu=self.prefer_gpu,
|
|
18
|
+
preferred_backend=self.preferred_backend,
|
|
19
|
+
log_cb=self.progress.emit
|
|
20
|
+
)
|
|
21
21
|
|
|
22
|
-
# honor cancellation if requested
|
|
23
22
|
if QThread.currentThread().isInterruptionRequested():
|
|
24
23
|
self.finished.emit(False, "Canceled.")
|
|
25
24
|
return
|
|
@@ -27,4 +26,4 @@ class AccelInstallWorker(QObject):
|
|
|
27
26
|
if ok:
|
|
28
27
|
self.finished.emit(True, "PyTorch installed and ready.")
|
|
29
28
|
else:
|
|
30
|
-
self.finished.emit(False, msg or "Installation failed.")
|
|
29
|
+
self.finished.emit(False, msg or "Installation failed.")
|
|
@@ -13,15 +13,52 @@ from functools import lru_cache
|
|
|
13
13
|
from astropy.io import fits
|
|
14
14
|
from astropy.stats import sigma_clipped_stats
|
|
15
15
|
import sep
|
|
16
|
-
from setiastro.saspro.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
_resolve_darkstar_exe, _ensure_exec_bit, _purge_darkstar_io
|
|
20
|
-
|
|
16
|
+
from setiastro.saspro.cosmicclarity_engines.darkstar_engine import (
|
|
17
|
+
darkstar_starremoval_rgb01,
|
|
18
|
+
DarkStarParams,
|
|
21
19
|
)
|
|
20
|
+
|
|
21
|
+
|
|
22
22
|
from setiastro.saspro.legacy.image_manager import (load_image, save_image)
|
|
23
23
|
from setiastro.saspro.imageops.stretch import stretch_color_image, stretch_mono_image
|
|
24
24
|
|
|
25
|
+
def _get_setting_any(settings, keys, default=None):
|
|
26
|
+
"""
|
|
27
|
+
Try multiple keys against QSettings-like or dict-like settings.
|
|
28
|
+
keys: iterable of strings
|
|
29
|
+
"""
|
|
30
|
+
if settings is None:
|
|
31
|
+
return default
|
|
32
|
+
|
|
33
|
+
# QSettings / object with .value()
|
|
34
|
+
if hasattr(settings, "value") and callable(getattr(settings, "value")):
|
|
35
|
+
for k in keys:
|
|
36
|
+
try:
|
|
37
|
+
v = settings.value(k, None)
|
|
38
|
+
except Exception:
|
|
39
|
+
v = None
|
|
40
|
+
if v not in (None, "", "None"):
|
|
41
|
+
return v
|
|
42
|
+
return default
|
|
43
|
+
|
|
44
|
+
# dict-like
|
|
45
|
+
if isinstance(settings, dict):
|
|
46
|
+
for k in keys:
|
|
47
|
+
if k in settings and settings[k] not in (None, "", "None"):
|
|
48
|
+
return settings[k]
|
|
49
|
+
return default
|
|
50
|
+
|
|
51
|
+
# fallback: attribute lookup
|
|
52
|
+
for k in keys:
|
|
53
|
+
try:
|
|
54
|
+
v = getattr(settings, k)
|
|
55
|
+
except Exception:
|
|
56
|
+
v = None
|
|
57
|
+
if v not in (None, "", "None"):
|
|
58
|
+
return v
|
|
59
|
+
return default
|
|
60
|
+
|
|
61
|
+
|
|
25
62
|
def _blackpoint_nonzero(img_norm: np.ndarray, p: float = 0.1) -> float:
|
|
26
63
|
"""Scalar blackpoint from non-zero pixels across all channels (linked).
|
|
27
64
|
p in [0..100]: small percentile to resist outliers; use 0 for strict min."""
|
|
@@ -66,6 +103,18 @@ def _u16_to_float01(x: np.ndarray) -> np.ndarray:
|
|
|
66
103
|
|
|
67
104
|
# comet_stacking.py (or wherever this lives)
|
|
68
105
|
|
|
106
|
+
def _ensure_exec_bit(path: str) -> None:
|
|
107
|
+
try:
|
|
108
|
+
if os.name == "nt":
|
|
109
|
+
return
|
|
110
|
+
p = os.path.realpath(path)
|
|
111
|
+
st = os.stat(p)
|
|
112
|
+
if not (st.st_mode & 0o111):
|
|
113
|
+
os.chmod(p, st.st_mode | 0o111)
|
|
114
|
+
except Exception:
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
|
|
69
118
|
def starnet_starless_pair_from_array(
|
|
70
119
|
src_rgb01,
|
|
71
120
|
settings,
|
|
@@ -211,78 +260,42 @@ def starnet_starless_pair_from_array(
|
|
|
211
260
|
return np.clip(protected_unstretch, 0.0, 1.0), np.clip(protected_unstretch, 0.0, 1.0)
|
|
212
261
|
|
|
213
262
|
|
|
214
|
-
|
|
215
|
-
|
|
263
|
+
def darkstar_starless_from_array(
|
|
264
|
+
src_rgb01: np.ndarray,
|
|
265
|
+
settings,
|
|
266
|
+
*,
|
|
267
|
+
use_gpu: bool = True,
|
|
268
|
+
chunk_size: int = 512,
|
|
269
|
+
overlap_frac: float = 0.125,
|
|
270
|
+
mode: str = "unscreen",
|
|
271
|
+
progress_cb=None,
|
|
272
|
+
status_cb=None,
|
|
273
|
+
) -> np.ndarray:
|
|
216
274
|
"""
|
|
217
|
-
|
|
218
|
-
|
|
275
|
+
In-process Dark Star removal via darkstar_engine.py
|
|
276
|
+
Input: float32 [0..1], HxW or HxWx1 or HxWx3
|
|
277
|
+
Output: float32 [0..1], same "mono-ness" behavior as engine (HxWx1 if mono input)
|
|
219
278
|
"""
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
# resolve exe and base folder
|
|
235
|
-
exe, base = _resolve_darkstar_exe(type("Dummy", (), {"settings": settings})())
|
|
236
|
-
if not exe or not base:
|
|
237
|
-
raise RuntimeError("Cosmic Clarity DarkStar executable path is not set.")
|
|
238
|
-
|
|
239
|
-
_ensure_exec_bit(exe)
|
|
240
|
-
|
|
241
|
-
input_dir = os.path.join(base, "input")
|
|
242
|
-
output_dir = os.path.join(base, "output")
|
|
243
|
-
os.makedirs(input_dir, exist_ok=True)
|
|
244
|
-
os.makedirs(output_dir, exist_ok=True)
|
|
245
|
-
|
|
246
|
-
# purge any prior files (safe; scoped to imagetoremovestars*)
|
|
247
|
-
_purge_darkstar_io(base, prefix="imagetoremovestars", clear_input=True, clear_output=True)
|
|
248
|
-
|
|
249
|
-
in_path = os.path.join(input_dir, "imagetoremovestars.tif")
|
|
250
|
-
out_path = os.path.join(output_dir, "imagetoremovestars_starless.tif")
|
|
251
|
-
|
|
252
|
-
# save input as float32 TIFF
|
|
253
|
-
# save input as float32 TIFF
|
|
254
|
-
save_image(img_to_save, in_path, original_format="tif", bit_depth="32-bit floating point",
|
|
255
|
-
original_header=None, is_mono=False, image_meta=None, file_meta=None)
|
|
256
|
-
|
|
257
|
-
# build command (SASv2 parity): default unscreen, show extracted stars off, stride 512
|
|
258
|
-
cmd = [exe, "--star_removal_mode", "unscreen", "--chunk_size", "512"]
|
|
259
|
-
|
|
260
|
-
rc = subprocess.call(cmd, cwd=output_dir)
|
|
261
|
-
if rc != 0 or not os.path.exists(out_path):
|
|
262
|
-
try: os.remove(in_path)
|
|
263
|
-
except Exception as e:
|
|
264
|
-
import logging
|
|
265
|
-
logging.debug(f"Exception suppressed: {type(e).__name__}: {e}")
|
|
266
|
-
raise RuntimeError(f"DarkStar failed (rc={rc}).")
|
|
267
|
-
|
|
268
|
-
starless, _, _, _ = load_image(out_path)
|
|
269
|
-
# cleanup
|
|
270
|
-
try:
|
|
271
|
-
os.remove(in_path)
|
|
272
|
-
os.remove(out_path)
|
|
273
|
-
_purge_darkstar_io(base, prefix="imagetoremovestars", clear_input=True, clear_output=True)
|
|
274
|
-
except Exception:
|
|
275
|
-
pass
|
|
276
|
-
|
|
277
|
-
if starless is None:
|
|
278
|
-
raise RuntimeError("DarkStar produced no output.")
|
|
279
|
+
if progress_cb is None:
|
|
280
|
+
progress_cb = lambda done, total, stage: None
|
|
281
|
+
if status_cb is None:
|
|
282
|
+
status_cb = lambda msg: None
|
|
283
|
+
|
|
284
|
+
img = np.asarray(src_rgb01, np.float32)
|
|
285
|
+
params = DarkStarParams(
|
|
286
|
+
use_gpu=bool(use_gpu),
|
|
287
|
+
chunk_size=int(chunk_size),
|
|
288
|
+
overlap_frac=float(overlap_frac),
|
|
289
|
+
mode=str(mode),
|
|
290
|
+
output_stars_only=False,
|
|
291
|
+
)
|
|
279
292
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
293
|
+
starless, _stars_only, _was_mono = darkstar_starremoval_rgb01(
|
|
294
|
+
img,
|
|
295
|
+
params=params,
|
|
296
|
+
progress_cb=progress_cb,
|
|
297
|
+
status_cb=status_cb,
|
|
298
|
+
)
|
|
286
299
|
return np.clip(starless.astype(np.float32, copy=False), 0.0, 1.0)
|
|
287
300
|
|
|
288
301
|
# ---------- small helpers ----------
|
|
@@ -1106,16 +1119,37 @@ def _starless_frame_for_comet(img: np.ndarray,
|
|
|
1106
1119
|
|
|
1107
1120
|
# run
|
|
1108
1121
|
if tool == "CosmicClarityDarkStar":
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1122
|
+
base_for_mask = src # RGB [0..1]
|
|
1123
|
+
|
|
1124
|
+
# optional: forward progress from comet stacking if you want
|
|
1125
|
+
def _ds_progress(done, total, stage):
|
|
1126
|
+
# stage is like "Dark Star removal"
|
|
1127
|
+
# you can route to log or status_cb; keep lightweight
|
|
1128
|
+
pass
|
|
1129
|
+
|
|
1130
|
+
starless = darkstar_starless_from_array(
|
|
1131
|
+
src,
|
|
1132
|
+
settings,
|
|
1133
|
+
use_gpu=True,
|
|
1134
|
+
chunk_size=512,
|
|
1135
|
+
overlap_frac=0.125,
|
|
1136
|
+
mode="unscreen",
|
|
1137
|
+
progress_cb=_ds_progress,
|
|
1138
|
+
status_cb=None, # or log
|
|
1139
|
+
)
|
|
1140
|
+
|
|
1141
|
+
# DarkStar may return HxWx1 for mono-ish inputs — expand to RGB for nucleus protection
|
|
1142
|
+
if starless.ndim == 2:
|
|
1143
|
+
starless = np.repeat(starless[..., None], 3, axis=2)
|
|
1144
|
+
elif starless.ndim == 3 and starless.shape[2] == 1:
|
|
1145
|
+
starless = np.repeat(starless, 3, axis=2)
|
|
1112
1146
|
|
|
1113
|
-
# protect nucleus (blend original back where mask=1), in *current* domain
|
|
1114
1147
|
m = core_mask.astype(np.float32)
|
|
1115
1148
|
m3 = np.repeat(m[..., None], 3, axis=2)
|
|
1116
1149
|
protected = starless * (1.0 - m3) + base_for_mask * m3
|
|
1117
1150
|
return np.clip(protected, 0.0, 1.0)
|
|
1118
1151
|
|
|
1152
|
+
|
|
1119
1153
|
else:
|
|
1120
1154
|
# StarNet path: do mask-blend inside the function (in its stretched domain)
|
|
1121
1155
|
protected, _ = starnet_starless_pair_from_array(
|
|
@@ -1302,13 +1336,7 @@ class CometCentroidPreview(QDialog):
|
|
|
1302
1336
|
def _prep_slider(self, s, lo, hi, val):
|
|
1303
1337
|
s.setRange(lo, hi); s.setValue(val); s.setSingleStep(1); s.setPageStep(5)
|
|
1304
1338
|
|
|
1305
|
-
|
|
1306
|
-
if obj is self.view.viewport() and ev.type() == QEvent.Type.MouseButtonPress:
|
|
1307
|
-
if ev.button() == Qt.MouseButton.LeftButton:
|
|
1308
|
-
pos = self.view.mapToScene(ev.position().toPoint())
|
|
1309
|
-
self._set_xy_current(pos.x(), pos.y())
|
|
1310
|
-
return True
|
|
1311
|
-
return super().eventFilter(obj, ev)
|
|
1339
|
+
|
|
1312
1340
|
|
|
1313
1341
|
def keyPressEvent(self, ev):
|
|
1314
1342
|
k = ev.key()
|