setiastrosuitepro 1.8.0.post3__py3-none-any.whl → 1.8.1.post2__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/__main__.py +12 -1
- setiastro/saspro/_generated/build_info.py +2 -2
- setiastro/saspro/cosmicclarity_engines/darkstar_engine.py +22 -2
- setiastro/saspro/cosmicclarity_engines/denoise_engine.py +68 -15
- setiastro/saspro/cosmicclarity_engines/satellite_engine.py +7 -3
- setiastro/saspro/cosmicclarity_engines/sharpen_engine.py +371 -98
- setiastro/saspro/cosmicclarity_engines/superres_engine.py +1 -0
- setiastro/saspro/model_manager.py +65 -0
- setiastro/saspro/model_workers.py +58 -24
- setiastro/saspro/ops/settings.py +45 -8
- setiastro/saspro/planetprojection.py +68 -36
- setiastro/saspro/resources.py +18 -14
- setiastro/saspro/runtime_torch.py +571 -127
- setiastro/saspro/star_alignment.py +262 -210
- setiastro/saspro/widgets/spinboxes.py +5 -7
- {setiastrosuitepro-1.8.0.post3.dist-info → setiastrosuitepro-1.8.1.post2.dist-info}/METADATA +1 -1
- {setiastrosuitepro-1.8.0.post3.dist-info → setiastrosuitepro-1.8.1.post2.dist-info}/RECORD +21 -21
- {setiastrosuitepro-1.8.0.post3.dist-info → setiastrosuitepro-1.8.1.post2.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.8.0.post3.dist-info → setiastrosuitepro-1.8.1.post2.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.8.0.post3.dist-info → setiastrosuitepro-1.8.1.post2.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.8.0.post3.dist-info → setiastrosuitepro-1.8.1.post2.dist-info}/licenses/license.txt +0 -0
|
@@ -131,6 +131,71 @@ def _parse_gdrive_download_form(html: str) -> tuple[Optional[str], Optional[dict
|
|
|
131
131
|
|
|
132
132
|
return action, params
|
|
133
133
|
|
|
134
|
+
def download_http_file(
|
|
135
|
+
url: str,
|
|
136
|
+
dst_path: str | os.PathLike,
|
|
137
|
+
*,
|
|
138
|
+
progress_cb: ProgressCB = None,
|
|
139
|
+
should_cancel=None,
|
|
140
|
+
timeout: int = 60,
|
|
141
|
+
chunk_size: int = 1024 * 1024,
|
|
142
|
+
) -> Path:
|
|
143
|
+
import requests
|
|
144
|
+
|
|
145
|
+
dst = Path(dst_path)
|
|
146
|
+
tmp = dst.with_suffix(dst.suffix + ".part")
|
|
147
|
+
tmp.parent.mkdir(parents=True, exist_ok=True)
|
|
148
|
+
|
|
149
|
+
def log(msg: str):
|
|
150
|
+
if progress_cb:
|
|
151
|
+
progress_cb(msg)
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
tmp.unlink(missing_ok=True)
|
|
155
|
+
except Exception:
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
with requests.Session() as s:
|
|
159
|
+
log(f"Connecting… {url}")
|
|
160
|
+
r = s.get(url, stream=True, timeout=timeout, allow_redirects=True)
|
|
161
|
+
r.raise_for_status()
|
|
162
|
+
|
|
163
|
+
total = int(r.headers.get("Content-Length") or 0)
|
|
164
|
+
done = 0
|
|
165
|
+
t_last = time.time()
|
|
166
|
+
done_last = 0
|
|
167
|
+
|
|
168
|
+
with open(tmp, "wb") as f:
|
|
169
|
+
for chunk in r.iter_content(chunk_size=chunk_size):
|
|
170
|
+
if should_cancel and should_cancel():
|
|
171
|
+
try:
|
|
172
|
+
f.close()
|
|
173
|
+
tmp.unlink(missing_ok=True)
|
|
174
|
+
except Exception:
|
|
175
|
+
pass
|
|
176
|
+
raise RuntimeError("Download canceled.")
|
|
177
|
+
|
|
178
|
+
if not chunk:
|
|
179
|
+
continue
|
|
180
|
+
|
|
181
|
+
f.write(chunk)
|
|
182
|
+
done += len(chunk)
|
|
183
|
+
|
|
184
|
+
now = time.time()
|
|
185
|
+
if now - t_last >= 0.5:
|
|
186
|
+
if total > 0:
|
|
187
|
+
pct = (done * 100.0) / total
|
|
188
|
+
log(f"Downloading… {pct:5.1f}% ({done}/{total} bytes)")
|
|
189
|
+
else:
|
|
190
|
+
bps = (done - done_last) / max(now - t_last, 1e-9)
|
|
191
|
+
log(f"Downloading… {done} bytes ({bps/1024/1024:.1f} MB/s)")
|
|
192
|
+
t_last = now
|
|
193
|
+
done_last = done
|
|
194
|
+
|
|
195
|
+
os.replace(str(tmp), str(dst))
|
|
196
|
+
log(f"Download complete: {dst}")
|
|
197
|
+
return dst
|
|
198
|
+
|
|
134
199
|
|
|
135
200
|
def download_google_drive_file(
|
|
136
201
|
file_id: str,
|
|
@@ -9,6 +9,7 @@ import zipfile
|
|
|
9
9
|
from setiastro.saspro.model_manager import (
|
|
10
10
|
extract_drive_file_id,
|
|
11
11
|
download_google_drive_file,
|
|
12
|
+
download_http_file,
|
|
12
13
|
install_models_zip,
|
|
13
14
|
sha256_file,
|
|
14
15
|
)
|
|
@@ -54,49 +55,82 @@ class ModelsDownloadWorker(QObject):
|
|
|
54
55
|
progress = pyqtSignal(str)
|
|
55
56
|
finished = pyqtSignal(bool, str)
|
|
56
57
|
|
|
57
|
-
def __init__(self, primary: str, backup: str,
|
|
58
|
+
def __init__(self, primary: str, backup: str, tertiary: str | None = None,
|
|
59
|
+
expected_sha256: str | None = None, should_cancel=None):
|
|
58
60
|
super().__init__()
|
|
59
61
|
self.primary = primary
|
|
60
62
|
self.backup = backup
|
|
63
|
+
self.tertiary = (tertiary or "").strip() or None
|
|
61
64
|
self.expected_sha256 = (expected_sha256 or "").strip() or None
|
|
62
65
|
self.should_cancel = should_cancel # callable -> bool
|
|
63
66
|
|
|
64
67
|
def run(self):
|
|
65
68
|
try:
|
|
66
|
-
# The inputs should be FILE links (or IDs), not folder links.
|
|
67
|
-
fid = extract_drive_file_id(self.primary) or extract_drive_file_id(self.backup)
|
|
68
|
-
if not fid:
|
|
69
|
-
raise RuntimeError(
|
|
70
|
-
"Models URL is not a Google Drive *file* link or id.\n"
|
|
71
|
-
"Please provide a shared file link (…/file/d/<ID>/view) to the models zip."
|
|
72
|
-
)
|
|
73
|
-
|
|
74
69
|
tmp = os.path.join(tempfile.gettempdir(), "saspro_models_latest.zip")
|
|
75
|
-
try:
|
|
76
|
-
self.progress.emit("Downloading from primary…")
|
|
77
|
-
download_google_drive_file(fid, tmp, progress_cb=lambda s: self.progress.emit(s), should_cancel=self.should_cancel)
|
|
78
|
-
except Exception as e:
|
|
79
|
-
# Try backup if primary fails AND backup has a different file id
|
|
80
|
-
fid2 = extract_drive_file_id(self.backup)
|
|
81
|
-
if fid2 and fid2 != fid:
|
|
82
|
-
self.progress.emit("Primary failed. Trying backup…")
|
|
83
|
-
download_google_drive_file(fid2, tmp, progress_cb=lambda s: self.progress.emit(s), should_cancel=self.should_cancel)
|
|
84
|
-
else:
|
|
85
|
-
raise
|
|
86
70
|
|
|
71
|
+
# 1) Try Google Drive primary/backup (by file id)
|
|
72
|
+
fid_primary = extract_drive_file_id(self.primary)
|
|
73
|
+
fid_backup = extract_drive_file_id(self.backup)
|
|
74
|
+
|
|
75
|
+
drive_ok = False
|
|
76
|
+
used_source = None
|
|
77
|
+
|
|
78
|
+
if fid_primary or fid_backup:
|
|
79
|
+
try:
|
|
80
|
+
if fid_primary:
|
|
81
|
+
self.progress.emit("Downloading from primary (Google Drive)…")
|
|
82
|
+
download_google_drive_file(
|
|
83
|
+
fid_primary, tmp,
|
|
84
|
+
progress_cb=lambda s: self.progress.emit(s),
|
|
85
|
+
should_cancel=self.should_cancel
|
|
86
|
+
)
|
|
87
|
+
drive_ok = True
|
|
88
|
+
used_source = ("google_drive", fid_primary)
|
|
89
|
+
else:
|
|
90
|
+
raise RuntimeError("Primary is not a valid Drive file link/id.")
|
|
91
|
+
except Exception:
|
|
92
|
+
# Try backup if different id
|
|
93
|
+
if fid_backup and fid_backup != fid_primary:
|
|
94
|
+
self.progress.emit("Primary failed. Trying backup (Google Drive)…")
|
|
95
|
+
download_google_drive_file(
|
|
96
|
+
fid_backup, tmp,
|
|
97
|
+
progress_cb=lambda s: self.progress.emit(s),
|
|
98
|
+
should_cancel=self.should_cancel
|
|
99
|
+
)
|
|
100
|
+
drive_ok = True
|
|
101
|
+
used_source = ("google_drive", fid_backup)
|
|
102
|
+
|
|
103
|
+
# 2) If Drive failed (or links weren’t Drive), try GitHub / HTTP tertiary
|
|
104
|
+
if not drive_ok:
|
|
105
|
+
if not self.tertiary:
|
|
106
|
+
raise RuntimeError(
|
|
107
|
+
"Google Drive download failed and no tertiary mirror URL was provided."
|
|
108
|
+
)
|
|
109
|
+
self.progress.emit("Google Drive failed. Trying GitHub mirror…")
|
|
110
|
+
download_http_file(
|
|
111
|
+
self.tertiary, tmp,
|
|
112
|
+
progress_cb=lambda s: self.progress.emit(s),
|
|
113
|
+
should_cancel=self.should_cancel
|
|
114
|
+
)
|
|
115
|
+
used_source = ("http", self.tertiary)
|
|
116
|
+
|
|
117
|
+
# 3) Optional checksum
|
|
87
118
|
if self.expected_sha256:
|
|
88
119
|
self.progress.emit("Verifying checksum…")
|
|
89
120
|
got = sha256_file(tmp)
|
|
90
121
|
if got.lower() != self.expected_sha256.lower():
|
|
91
122
|
raise RuntimeError(f"SHA256 mismatch.\nExpected: {self.expected_sha256}\nGot: {got}")
|
|
92
123
|
|
|
124
|
+
# 4) Install + manifest
|
|
125
|
+
src_kind, src_val = used_source if used_source else ("unknown", "")
|
|
93
126
|
manifest = {
|
|
94
|
-
"source":
|
|
95
|
-
"
|
|
96
|
-
"sha256": self.expected_sha256,
|
|
127
|
+
"source": src_kind,
|
|
128
|
+
"source_ref": src_val,
|
|
129
|
+
"sha256": self.expected_sha256 or "",
|
|
97
130
|
}
|
|
98
|
-
install_models_zip(tmp, progress_cb=lambda s: self.progress.emit(s), manifest=manifest)
|
|
99
131
|
|
|
132
|
+
install_models_zip(tmp, progress_cb=lambda s: self.progress.emit(s), manifest=manifest)
|
|
100
133
|
self.finished.emit(True, "Models updated successfully.")
|
|
101
134
|
except Exception as e:
|
|
102
135
|
self.finished.emit(False, str(e))
|
|
136
|
+
|
setiastro/saspro/ops/settings.py
CHANGED
|
@@ -248,7 +248,8 @@ class SettingsDialog(QDialog):
|
|
|
248
248
|
self.btn_models_install_zip.clicked.connect(self._models_install_from_zip_clicked)
|
|
249
249
|
|
|
250
250
|
self.btn_models_open_drive = QPushButton(self.tr("Open Drive…"))
|
|
251
|
-
self.btn_models_open_drive.setToolTip(self.tr("
|
|
251
|
+
self.btn_models_open_drive.setToolTip(self.tr("Download models (Primary/Backup/GitHub mirror)"))
|
|
252
|
+
|
|
252
253
|
self.btn_models_open_drive.clicked.connect(self._models_open_drive_clicked)
|
|
253
254
|
|
|
254
255
|
row_models = QHBoxLayout()
|
|
@@ -319,16 +320,22 @@ class SettingsDialog(QDialog):
|
|
|
319
320
|
def _models_open_drive_clicked(self):
|
|
320
321
|
PRIMARY_FOLDER = "https://drive.google.com/drive/folders/1-fktZb3I9l-mQimJX2fZAmJCBj_t0yAF?usp=drive_link"
|
|
321
322
|
BACKUP_FOLDER = "https://drive.google.com/drive/folders/1j46RV6touQtOmtxkhdFWGm_LQKwEpTl9?usp=drive_link"
|
|
323
|
+
GITHUB_ZIP = "https://github.com/setiastro/setiastrosuitepro/releases/download/benchmarkFIT/SASPro_Models_Latest.zip"
|
|
322
324
|
|
|
323
325
|
menu = QMenu(self)
|
|
324
|
-
act_primary = menu.addAction(self.tr("Primary"))
|
|
325
|
-
act_backup = menu.addAction(self.tr("Backup"))
|
|
326
|
+
act_primary = menu.addAction(self.tr("Primary (Google Drive)"))
|
|
327
|
+
act_backup = menu.addAction(self.tr("Backup (Google Drive)"))
|
|
328
|
+
menu.addSeparator()
|
|
329
|
+
act_gh = menu.addAction(self.tr("GitHub (no quota limit)"))
|
|
326
330
|
|
|
327
331
|
chosen = menu.exec(self.btn_models_open_drive.mapToGlobal(self.btn_models_open_drive.rect().bottomLeft()))
|
|
328
332
|
if chosen == act_primary:
|
|
329
333
|
webbrowser.open(PRIMARY_FOLDER)
|
|
330
334
|
elif chosen == act_backup:
|
|
331
335
|
webbrowser.open(BACKUP_FOLDER)
|
|
336
|
+
elif chosen == act_gh:
|
|
337
|
+
webbrowser.open(GITHUB_ZIP)
|
|
338
|
+
|
|
332
339
|
|
|
333
340
|
|
|
334
341
|
def _models_install_from_zip_clicked(self):
|
|
@@ -404,6 +411,7 @@ class SettingsDialog(QDialog):
|
|
|
404
411
|
# Put your actual *zip file* share links here once you create them.
|
|
405
412
|
PRIMARY = "https://drive.google.com/file/d/1n4p0grtNpfllalMqtgaEmsTYaFhT5u7Y/view?usp=drive_link"
|
|
406
413
|
BACKUP = "https://drive.google.com/file/d/1uRGJCITlfMMN89ZkOO5ICWEKMH24KGit/view?usp=drive_link"
|
|
414
|
+
TERTIARY = "https://github.com/setiastro/setiastrosuitepro/releases/download/benchmarkFIT/SASPro_Models_Latest.zip"
|
|
407
415
|
|
|
408
416
|
|
|
409
417
|
self.btn_models_update.setEnabled(False)
|
|
@@ -418,7 +426,7 @@ class SettingsDialog(QDialog):
|
|
|
418
426
|
from setiastro.saspro.model_workers import ModelsDownloadWorker
|
|
419
427
|
|
|
420
428
|
self._models_thread = QThread(self)
|
|
421
|
-
self._models_worker = ModelsDownloadWorker(PRIMARY, BACKUP, expected_sha256=None)
|
|
429
|
+
self._models_worker = ModelsDownloadWorker(PRIMARY, BACKUP, TERTIARY, expected_sha256=None)
|
|
422
430
|
self._models_worker.moveToThread(self._models_thread)
|
|
423
431
|
|
|
424
432
|
self._models_thread.started.connect(self._models_worker.run, Qt.ConnectionType.QueuedConnection)
|
|
@@ -457,10 +465,39 @@ class SettingsDialog(QDialog):
|
|
|
457
465
|
if not m:
|
|
458
466
|
self.lbl_models_status.setText(self.tr("Status: not installed"))
|
|
459
467
|
return
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
)
|
|
468
|
+
|
|
469
|
+
# New fields (preferred)
|
|
470
|
+
src = (m.get("source") or "").strip()
|
|
471
|
+
ref = (m.get("source_ref") or "").strip()
|
|
472
|
+
sha = (m.get("sha256") or "").strip()
|
|
473
|
+
|
|
474
|
+
# Back-compat with older manifests
|
|
475
|
+
if not src:
|
|
476
|
+
# old versions used google_drive + file_id
|
|
477
|
+
src = "google_drive" if m.get("file_id") else (m.get("source") or "unknown")
|
|
478
|
+
if not ref:
|
|
479
|
+
ref = (m.get("file_id") or m.get("file") or "").strip()
|
|
480
|
+
|
|
481
|
+
# Keep it short + readable
|
|
482
|
+
src_label = {
|
|
483
|
+
"google_drive": "Google Drive",
|
|
484
|
+
"http": "GitHub/HTTP",
|
|
485
|
+
"manual_zip": "Manual ZIP",
|
|
486
|
+
}.get(src, src or "Unknown")
|
|
487
|
+
|
|
488
|
+
lines = [self.tr("Status: installed"),
|
|
489
|
+
self.tr("Location: {0}").format(models_root())]
|
|
490
|
+
|
|
491
|
+
if ref:
|
|
492
|
+
# show just a compact hint (id or url or filename)
|
|
493
|
+
lines.append(self.tr("Source: {0}").format(src_label))
|
|
494
|
+
lines.append(self.tr("Ref: {0}").format(ref))
|
|
495
|
+
|
|
496
|
+
if sha:
|
|
497
|
+
lines.append(self.tr("SHA256: {0}").format(sha[:12] + "…"))
|
|
498
|
+
|
|
499
|
+
self.lbl_models_status.setText("\n".join(lines))
|
|
500
|
+
self.lbl_models_status.setStyleSheet("color:#888;")
|
|
464
501
|
|
|
465
502
|
|
|
466
503
|
def _accel_pref_changed(self, idx: int):
|
|
@@ -2175,6 +2175,52 @@ class PlanetProjectionDialog(QDialog):
|
|
|
2175
2175
|
self._show_stereo_pair(cross_eye=cross_eye)
|
|
2176
2176
|
return
|
|
2177
2177
|
|
|
2178
|
+
# ---- GALAXY TOP-DOWN (early exit) ----
|
|
2179
|
+
# IMPORTANT: do this BEFORE any ROI crop, otherwise huge galaxies get clipped by ROI sizing.
|
|
2180
|
+
is_galaxy = (ptype == 3) or (mode == 5) # planet_type==Galaxy OR output==Galaxy Polar View
|
|
2181
|
+
if is_galaxy:
|
|
2182
|
+
def to01(x):
|
|
2183
|
+
if x.dtype == np.uint8:
|
|
2184
|
+
return x.astype(np.float32) / 255.0
|
|
2185
|
+
if x.dtype == np.uint16:
|
|
2186
|
+
return x.astype(np.float32) / 65535.0
|
|
2187
|
+
return x.astype(np.float32, copy=False)
|
|
2188
|
+
|
|
2189
|
+
roi = img[..., :3] # FULL IMAGE
|
|
2190
|
+
cx0 = float(cx) # FULL-IMAGE coords
|
|
2191
|
+
cy0 = float(cy)
|
|
2192
|
+
roi01 = to01(roi)
|
|
2193
|
+
|
|
2194
|
+
pa = float(self.spin_ring_pa.value()) # reuse ring PA widget as galaxy PA
|
|
2195
|
+
tilt = float(self.spin_ring_tilt.value()) # reuse ring tilt widget as galaxy b/a
|
|
2196
|
+
|
|
2197
|
+
# output size: tied to full image (clamped)
|
|
2198
|
+
out_size = int(max(256, min(2400, max(roi.shape[0], roi.shape[1]))))
|
|
2199
|
+
|
|
2200
|
+
try:
|
|
2201
|
+
top8 = deproject_galaxy_topdown_u8(
|
|
2202
|
+
roi01,
|
|
2203
|
+
cx0=cx0, cy0=cy0,
|
|
2204
|
+
rpx=float(r),
|
|
2205
|
+
pa_deg=pa,
|
|
2206
|
+
tilt=tilt,
|
|
2207
|
+
out_size=out_size,
|
|
2208
|
+
)
|
|
2209
|
+
except Exception as e:
|
|
2210
|
+
QMessageBox.warning(self, "Galaxy Polar View", f"Failed to deproject galaxy:\n{e}")
|
|
2211
|
+
return
|
|
2212
|
+
|
|
2213
|
+
# push single-frame output
|
|
2214
|
+
self._left = None
|
|
2215
|
+
self._right = None
|
|
2216
|
+
self._wiggle_frames = None
|
|
2217
|
+
self._wiggle_state = False
|
|
2218
|
+
|
|
2219
|
+
self._last_preview_u8 = top8
|
|
2220
|
+
self._push_preview_u8(top8)
|
|
2221
|
+
return
|
|
2222
|
+
|
|
2223
|
+
|
|
2178
2224
|
# ---- Saturn rings ROI expansion (only increases s) ----
|
|
2179
2225
|
is_saturn = (self.cmb_planet_type.currentIndex() == 1)
|
|
2180
2226
|
rings_on = bool(is_saturn and getattr(self, "chk_rings", None) is not None and self.chk_rings.isChecked())
|
|
@@ -2209,6 +2255,28 @@ class PlanetProjectionDialog(QDialog):
|
|
|
2209
2255
|
y1 = min(Hfull, y0 + s)
|
|
2210
2256
|
|
|
2211
2257
|
roi = img[y0:y1, x0:x1, :3]
|
|
2258
|
+
# ---- GALAXY ROI expansion (ensure full projected ellipse fits) ----
|
|
2259
|
+
if ptype == 3:
|
|
2260
|
+
tilt = float(self.spin_ring_tilt.value())
|
|
2261
|
+
pa = float(self.spin_ring_pa.value())
|
|
2262
|
+
|
|
2263
|
+
tilt = float(np.clip(tilt, 0.02, 1.0))
|
|
2264
|
+
|
|
2265
|
+
# ellipse in SOURCE pixels
|
|
2266
|
+
a = float(r) # semi-major
|
|
2267
|
+
b = max(1.0, a * tilt) # semi-minor
|
|
2268
|
+
|
|
2269
|
+
th = np.deg2rad(pa)
|
|
2270
|
+
cth, sth = np.cos(th), np.sin(th)
|
|
2271
|
+
|
|
2272
|
+
# bounding half-extents of rotated ellipse
|
|
2273
|
+
dx = np.sqrt((a * cth) ** 2 + (b * sth) ** 2)
|
|
2274
|
+
dy = np.sqrt((a * sth) ** 2 + (b * cth) ** 2)
|
|
2275
|
+
need_half = float(max(dx, dy))
|
|
2276
|
+
|
|
2277
|
+
margin = 12.0
|
|
2278
|
+
s_need = int(np.ceil(2.0 * (need_half + margin)))
|
|
2279
|
+
s = max(s, s_need)
|
|
2212
2280
|
|
|
2213
2281
|
# ---- disk mask (ROI coords) ----
|
|
2214
2282
|
H0, W0 = roi.shape[:2]
|
|
@@ -2226,42 +2294,6 @@ class PlanetProjectionDialog(QDialog):
|
|
|
2226
2294
|
|
|
2227
2295
|
theta = float(self.spin_theta.value())
|
|
2228
2296
|
|
|
2229
|
-
# ---- GALAXY TOP-DOWN (early exit) ----
|
|
2230
|
-
is_galaxy = (ptype == 3) or (mode == 5) # planet_type==Galaxy OR output==Galaxy Polar View
|
|
2231
|
-
|
|
2232
|
-
if is_galaxy:
|
|
2233
|
-
# Galaxy wants the ROI disk params (cx0, cy0, r) + PA/tilt
|
|
2234
|
-
roi01 = to01(roi)
|
|
2235
|
-
|
|
2236
|
-
pa = float(self.spin_ring_pa.value()) # reuse ring PA widget as galaxy PA
|
|
2237
|
-
tilt = float(self.spin_ring_tilt.value()) # reuse ring tilt widget as galaxy b/a
|
|
2238
|
-
|
|
2239
|
-
# choose output size: use ROI size or clamp to something reasonable
|
|
2240
|
-
out_size = int(max(256, min(2000, max(roi.shape[0], roi.shape[1]))))
|
|
2241
|
-
|
|
2242
|
-
try:
|
|
2243
|
-
top8 = deproject_galaxy_topdown_u8(
|
|
2244
|
-
roi01,
|
|
2245
|
-
cx0=float(cx0), cy0=float(cy0),
|
|
2246
|
-
rpx=float(r),
|
|
2247
|
-
pa_deg=pa,
|
|
2248
|
-
tilt=tilt,
|
|
2249
|
-
out_size=out_size,
|
|
2250
|
-
)
|
|
2251
|
-
except Exception as e:
|
|
2252
|
-
QMessageBox.warning(self, "Galaxy Polar View", f"Failed to deproject galaxy:\n{e}")
|
|
2253
|
-
return
|
|
2254
|
-
|
|
2255
|
-
# push single-frame output
|
|
2256
|
-
self._left = None
|
|
2257
|
-
self._right = None
|
|
2258
|
-
self._wiggle_frames = None
|
|
2259
|
-
self._wiggle_state = False
|
|
2260
|
-
|
|
2261
|
-
self._last_preview_u8 = top8
|
|
2262
|
-
self._push_preview_u8(top8)
|
|
2263
|
-
return
|
|
2264
|
-
|
|
2265
2297
|
# ---- BODY (sphere reprojection) ----
|
|
2266
2298
|
interp = cv2.INTER_LANCZOS4
|
|
2267
2299
|
left_w, right_w, maskL, maskR = make_stereo_pair(
|
setiastro/saspro/resources.py
CHANGED
|
@@ -603,23 +603,27 @@ class Resources:
|
|
|
603
603
|
|
|
604
604
|
@lru_cache(maxsize=8)
|
|
605
605
|
def get_models_dir() -> str:
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
606
|
+
"""
|
|
607
|
+
Models are NOT packaged resources. They must be installed via the model manager.
|
|
608
|
+
This returns the user models root and never falls back to _internal/data/models.
|
|
609
|
+
"""
|
|
610
|
+
from setiastro.saspro.model_manager import models_root
|
|
611
|
+
p = Path(models_root())
|
|
612
|
+
# Ensure dir exists (models_root should already do this, but harmless)
|
|
613
|
+
p.mkdir(parents=True, exist_ok=True)
|
|
614
|
+
return str(p)
|
|
615
|
+
|
|
615
616
|
|
|
616
|
-
|
|
617
|
-
|
|
617
|
+
def _assert_not_internal_models_path(p: str):
|
|
618
|
+
s = str(p).lower().replace("/", "\\")
|
|
619
|
+
if "\\_internal\\" in s and "\\data\\models\\" in s:
|
|
620
|
+
raise RuntimeError(f"Legacy internal model path detected: {p}")
|
|
618
621
|
|
|
619
622
|
def model_path(filename: str) -> str:
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
+
from setiastro.saspro.model_manager import require_model
|
|
624
|
+
p = str(require_model(filename))
|
|
625
|
+
_assert_not_internal_models_path(p)
|
|
626
|
+
return p
|
|
623
627
|
|
|
624
628
|
# Export all legacy paths as module-level variables
|
|
625
629
|
_legacy = _init_legacy_paths()
|