setiastrosuitepro 1.8.2__py3-none-any.whl → 1.8.3__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/images/magnitude.png +0 -0
- setiastro/saspro/_generated/build_info.py +2 -2
- setiastro/saspro/backgroundneutral.py +54 -16
- setiastro/saspro/finder_chart.py +20 -9
- setiastro/saspro/gui/main_window.py +96 -4
- setiastro/saspro/gui/mixins/header_mixin.py +40 -15
- setiastro/saspro/gui/mixins/menu_mixin.py +1 -0
- setiastro/saspro/gui/mixins/toolbar_mixin.py +8 -1
- setiastro/saspro/imageops/stretch.py +1 -1
- setiastro/saspro/magnitude_tool.py +1724 -0
- setiastro/saspro/remove_stars.py +13 -30
- setiastro/saspro/resources.py +2 -0
- setiastro/saspro/runtime_torch.py +20 -0
- setiastro/saspro/sfcc.py +81 -74
- setiastro/saspro/torch_rejection.py +59 -28
- {setiastrosuitepro-1.8.2.dist-info → setiastrosuitepro-1.8.3.dist-info}/METADATA +1 -1
- {setiastrosuitepro-1.8.2.dist-info → setiastrosuitepro-1.8.3.dist-info}/RECORD +21 -19
- {setiastrosuitepro-1.8.2.dist-info → setiastrosuitepro-1.8.3.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.8.2.dist-info → setiastrosuitepro-1.8.3.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.8.2.dist-info → setiastrosuitepro-1.8.3.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.8.2.dist-info → setiastrosuitepro-1.8.3.dist-info}/licenses/license.txt +0 -0
|
Binary file
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
# Auto-generated at build time. Do not edit.
|
|
2
|
-
BUILD_TIMESTAMP = "2026-01-
|
|
3
|
-
APP_VERSION = "1.8.
|
|
2
|
+
BUILD_TIMESTAMP = "2026-01-29T18:14:59Z"
|
|
3
|
+
APP_VERSION = "1.8.3"
|
|
@@ -168,40 +168,78 @@ def _find_best_patch_center(lum: np.ndarray) -> tuple[int, int]:
|
|
|
168
168
|
return best_pt
|
|
169
169
|
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
|
|
172
|
+
def auto_rect_box(img_rgb: np.ndarray, box: int = 50, margin: int = 100) -> tuple[int, int, int, int]:
|
|
172
173
|
"""
|
|
173
|
-
Find a robust
|
|
174
|
+
Find a robust box×box background rectangle (>= margin px margins) in image space.
|
|
174
175
|
Returns (x, y, w, h).
|
|
176
|
+
|
|
177
|
+
Notes:
|
|
178
|
+
- img_rgb must be HxWx3 float/uint in any range (we only use relative luminance).
|
|
179
|
+
- box is clamped so it always fits within the image + margins.
|
|
175
180
|
"""
|
|
176
|
-
|
|
177
|
-
if ch != 3:
|
|
181
|
+
if img_rgb.ndim != 3 or img_rgb.shape[2] != 3:
|
|
178
182
|
raise ValueError("Auto background finder expects a 3-channel RGB image.")
|
|
179
|
-
lum = img_rgb.mean(axis=2).astype(np.float32)
|
|
180
183
|
|
|
184
|
+
H, W, _ = img_rgb.shape
|
|
185
|
+
box = int(box)
|
|
186
|
+
|
|
187
|
+
# Clamp box so it fits inside the image after margins.
|
|
188
|
+
# Ensure at least 10px and at least 1px interior.
|
|
189
|
+
max_box_w = max(10, W - 2 * margin - 2)
|
|
190
|
+
max_box_h = max(10, H - 2 * margin - 2)
|
|
191
|
+
max_box = max(10, min(max_box_w, max_box_h))
|
|
192
|
+
box = int(np.clip(box, 10, max_box))
|
|
193
|
+
|
|
194
|
+
half = box // 2
|
|
195
|
+
|
|
196
|
+
# Luminance proxy
|
|
197
|
+
lum = img_rgb.mean(axis=2).astype(np.float32, copy=False)
|
|
198
|
+
|
|
199
|
+
# Your existing routine (assumed to return (cy, cx) in image coords)
|
|
181
200
|
cy, cx = _find_best_patch_center(lum)
|
|
182
201
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
min_cy, max_cy = margin + half, h - (margin + half)
|
|
202
|
+
# Keep center far enough from edges so the full box fits
|
|
203
|
+
min_cx, max_cx = margin + half, W - (margin + half)
|
|
204
|
+
min_cy, max_cy = margin + half, H - (margin + half)
|
|
187
205
|
cx = int(np.clip(cx, min_cx, max_cx))
|
|
188
206
|
cy = int(np.clip(cy, min_cy, max_cy))
|
|
189
207
|
|
|
190
|
-
#
|
|
208
|
+
# Refine around the center.
|
|
209
|
+
# Step scales with box so the search is meaningful at different sizes.
|
|
210
|
+
step = max(4, half // 2) # e.g. 50->12, 80->20, 30->7
|
|
191
211
|
best_val = np.inf
|
|
192
212
|
ty, tx = cy, cx
|
|
193
|
-
|
|
194
|
-
|
|
213
|
+
|
|
214
|
+
for dy in (-step, 0, +step):
|
|
215
|
+
for dx in (-step, 0, +step):
|
|
195
216
|
y = int(np.clip(cy + dy, min_cy, max_cy))
|
|
196
217
|
x = int(np.clip(cx + dx, min_cx, max_cx))
|
|
197
|
-
y0, y1 = y - half, y +
|
|
198
|
-
x0, x1 = x - half, x +
|
|
199
|
-
|
|
218
|
+
y0, y1 = y - half, y - half + box
|
|
219
|
+
x0, x1 = x - half, x - half + box
|
|
220
|
+
|
|
221
|
+
# Safety (should already be safe due to clamping, but keep it robust)
|
|
222
|
+
if y0 < 0 or x0 < 0 or y1 > H or x1 > W:
|
|
223
|
+
continue
|
|
224
|
+
|
|
225
|
+
m = float(np.median(lum[y0:y1, x0:x1]))
|
|
200
226
|
if m < best_val:
|
|
201
227
|
best_val, ty, tx = m, y, x
|
|
202
228
|
|
|
203
|
-
|
|
229
|
+
# Top-left anchored rect, exact box size
|
|
230
|
+
x0 = int(tx - half)
|
|
231
|
+
y0 = int(ty - half)
|
|
232
|
+
|
|
233
|
+
# Final clamp (again, belt + suspenders)
|
|
234
|
+
x0 = int(np.clip(x0, margin, W - margin - box))
|
|
235
|
+
y0 = int(np.clip(y0, margin, H - margin - box))
|
|
204
236
|
|
|
237
|
+
return (x0, y0, box, box)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def auto_rect_50x50(img_rgb: np.ndarray) -> tuple[int, int, int, int]:
|
|
241
|
+
"""Backward-compatible wrapper."""
|
|
242
|
+
return auto_rect_box(img_rgb, box=50, margin=100)
|
|
205
243
|
|
|
206
244
|
# --------------------------------
|
|
207
245
|
# Headless apply (doc + preset in)
|
setiastro/saspro/finder_chart.py
CHANGED
|
@@ -658,16 +658,26 @@ def _place_label_inside(ax, x, y, text, *, dx=6, dy=4, fontsize=9,
|
|
|
658
658
|
|
|
659
659
|
return t
|
|
660
660
|
|
|
661
|
+
SURVEY_HIPS = {
|
|
662
|
+
# Optical
|
|
663
|
+
"DSS2 (Color)": "CDS/P/DSS2/color",
|
|
664
|
+
"DSS2 (Red)": "CDS/P/DSS2/red",
|
|
665
|
+
"DSS2 (Blue)": "CDS/P/DSS2/blue",
|
|
666
|
+
"DSS2 (IR)": "CDS/P/DSS2/IR",
|
|
667
|
+
"Pan-STARRS DR1 (Color)": "CDS/P/PanSTARRS/DR1/color",
|
|
668
|
+
"SDSS9 (Color Alt)": "CDS/P/SDSS9/color-alt",
|
|
669
|
+
"DESI Legacy DR10 (Color)": "CDS/P/DESI-Legacy-Surveys/DR10/color",
|
|
670
|
+
|
|
671
|
+
# Infrared
|
|
672
|
+
"2MASS (J)": "CDS/P/2MASS/J",
|
|
673
|
+
"2MASS (H)": "CDS/P/2MASS/H",
|
|
674
|
+
|
|
675
|
+
# Gaia
|
|
676
|
+
"Gaia DR3 (Flux Color)": "CDS/P/Gaia/DR3/flux-color",
|
|
677
|
+
}
|
|
661
678
|
|
|
662
679
|
def _survey_to_hips_id(label: str) -> str:
|
|
663
|
-
|
|
664
|
-
if "dss" in key:
|
|
665
|
-
return "CDS/P/DSS2/color"
|
|
666
|
-
if "pan" in key:
|
|
667
|
-
return "CDS/P/PanSTARRS/DR1/color"
|
|
668
|
-
if "gaia" in key:
|
|
669
|
-
return "CDS/P/Gaia/DR3/flux-color"
|
|
670
|
-
return "CDS/P/DSS2/color"
|
|
680
|
+
return SURVEY_HIPS.get(label, "CDS/P/DSS2/color")
|
|
671
681
|
|
|
672
682
|
def try_fetch_hips_cutout(center: "SkyCoord", fov_deg: float, out_px: int, survey_label: str):
|
|
673
683
|
"""
|
|
@@ -1149,7 +1159,8 @@ class FinderChartDialog(QDialog):
|
|
|
1149
1159
|
|
|
1150
1160
|
row1.addWidget(QLabel("Survey:"))
|
|
1151
1161
|
self.cmb_survey = QComboBox()
|
|
1152
|
-
self.cmb_survey.
|
|
1162
|
+
self.cmb_survey.clear()
|
|
1163
|
+
self.cmb_survey.addItems(list(SURVEY_HIPS.keys()))
|
|
1153
1164
|
row1.addWidget(self.cmb_survey)
|
|
1154
1165
|
|
|
1155
1166
|
row1.addSpacing(12)
|
|
@@ -186,7 +186,7 @@ from setiastro.saspro.resources import (
|
|
|
186
186
|
colorwheel_path, font_path, csv_icon_path, spinner_path, wims_path, narrowbandnormalization_path,
|
|
187
187
|
wimi_path, linearfit_path, debayer_path, aberration_path, acv_icon_path,
|
|
188
188
|
functionbundles_path, viewbundles_path, selectivecolor_path, rgbalign_path, planetarystacker_path,
|
|
189
|
-
background_path, script_icon_path, planetprojection_path,clonestampicon_path, finderchart_path,
|
|
189
|
+
background_path, script_icon_path, planetprojection_path,clonestampicon_path, finderchart_path,magnitude_path,
|
|
190
190
|
)
|
|
191
191
|
|
|
192
192
|
import faulthandler
|
|
@@ -3185,6 +3185,45 @@ class AstroSuiteProMainWindow(
|
|
|
3185
3185
|
pass
|
|
3186
3186
|
self.SFCC_window.show()
|
|
3187
3187
|
|
|
3188
|
+
def _open_magnitude_tool(self):
|
|
3189
|
+
import os
|
|
3190
|
+
from PyQt6.QtGui import QIcon
|
|
3191
|
+
from PyQt6.QtWidgets import QMessageBox
|
|
3192
|
+
|
|
3193
|
+
# Keep same window-singleton behavior as SFCC
|
|
3194
|
+
if getattr(self, "MAG_window", None) and self.MAG_window.isVisible():
|
|
3195
|
+
self.MAG_window.raise_()
|
|
3196
|
+
self.MAG_window.activateWindow()
|
|
3197
|
+
return
|
|
3198
|
+
|
|
3199
|
+
# ensure we have a DocManager (mirror SFCC pattern)
|
|
3200
|
+
from setiastro.saspro.doc_manager import DocManager
|
|
3201
|
+
if not hasattr(self, "doc_manager") or self.doc_manager is None:
|
|
3202
|
+
self.doc_manager = DocManager(image_manager=getattr(self, "image_manager", None), parent=self)
|
|
3203
|
+
|
|
3204
|
+
# import tool
|
|
3205
|
+
from setiastro.saspro.magnitude_tool import MagnitudeToolDialog
|
|
3206
|
+
|
|
3207
|
+
self.MAG_window = MagnitudeToolDialog(
|
|
3208
|
+
doc_manager=self.doc_manager,
|
|
3209
|
+
parent=self
|
|
3210
|
+
)
|
|
3211
|
+
|
|
3212
|
+
# optional icon
|
|
3213
|
+
try:
|
|
3214
|
+
self.MAG_window.setWindowIcon(QIcon(magnitude_path))
|
|
3215
|
+
except Exception:
|
|
3216
|
+
pass
|
|
3217
|
+
|
|
3218
|
+
# cleanup
|
|
3219
|
+
try:
|
|
3220
|
+
self.MAG_window.destroyed.connect(lambda _=None: setattr(self, "MAG_window", None))
|
|
3221
|
+
except Exception:
|
|
3222
|
+
pass
|
|
3223
|
+
|
|
3224
|
+
self.MAG_window.show()
|
|
3225
|
+
|
|
3226
|
+
|
|
3188
3227
|
def show_convo_deconvo(self, doc=None):
|
|
3189
3228
|
# Reuse existing dialog if it's already open
|
|
3190
3229
|
sw = self.mdi.activeSubWindow()
|
|
@@ -3496,13 +3535,53 @@ class AstroSuiteProMainWindow(
|
|
|
3496
3535
|
base = base_title or (getattr(doc, "display_name", lambda: None)() or "RGB")
|
|
3497
3536
|
|
|
3498
3537
|
def _open(arr, suffix):
|
|
3499
|
-
|
|
3538
|
+
from astropy.io import fits
|
|
3539
|
+
|
|
3540
|
+
# ---- 1) start from source metadata (preserve headers/WCS) ----
|
|
3541
|
+
src_meta = getattr(doc, "metadata", {}) or {}
|
|
3542
|
+
meta = dict(src_meta) # shallow copy of top-level dict
|
|
3543
|
+
|
|
3544
|
+
# Preserve header objects safely
|
|
3545
|
+
fits_hdr = src_meta.get("fits_header")
|
|
3546
|
+
wcs_hdr = src_meta.get("wcs_header")
|
|
3547
|
+
orig_hdr = src_meta.get("original_header")
|
|
3548
|
+
|
|
3549
|
+
# Keep them if they are astropy Headers; otherwise fall back gracefully
|
|
3550
|
+
meta["fits_header"] = fits_hdr.copy() if isinstance(fits_hdr, fits.Header) else fits_hdr
|
|
3551
|
+
meta["wcs_header"] = wcs_hdr.copy() if isinstance(wcs_hdr, fits.Header) else wcs_hdr
|
|
3552
|
+
meta["original_header"] = orig_hdr.copy() if isinstance(orig_hdr, fits.Header) else orig_hdr
|
|
3553
|
+
|
|
3554
|
+
# Preserve WCS object + flags (ok if None)
|
|
3555
|
+
if "wcs" in src_meta:
|
|
3556
|
+
meta["wcs"] = src_meta.get("wcs")
|
|
3557
|
+
if "HasAstrometricSolution" in src_meta:
|
|
3558
|
+
meta["HasAstrometricSolution"] = src_meta.get("HasAstrometricSolution")
|
|
3559
|
+
|
|
3560
|
+
# Preserve image_meta mirror, but make it a copy so we don't mutate parent
|
|
3561
|
+
im = src_meta.get("image_meta")
|
|
3562
|
+
if isinstance(im, dict):
|
|
3563
|
+
meta["image_meta"] = dict(im)
|
|
3564
|
+
|
|
3565
|
+
# ---- 2) overwrite/extend with RGB-extract specific fields ----
|
|
3566
|
+
base = base_title or (getattr(doc, "display_name", lambda: None)() or "RGB")
|
|
3567
|
+
title = f"{base}_{suffix}"
|
|
3568
|
+
try:
|
|
3569
|
+
fh = meta.get("fits_header")
|
|
3570
|
+
if isinstance(fh, fits.Header):
|
|
3571
|
+
fh["NAXIS"] = 2
|
|
3572
|
+
if "NAXIS3" in fh:
|
|
3573
|
+
del fh["NAXIS3"]
|
|
3574
|
+
except Exception:
|
|
3575
|
+
pass
|
|
3576
|
+
meta.update({
|
|
3500
3577
|
"source": "RGB Extract",
|
|
3501
3578
|
"is_mono": True,
|
|
3502
3579
|
"bit_depth": "32-bit floating point",
|
|
3503
3580
|
"parent_title": base,
|
|
3504
|
-
|
|
3505
|
-
|
|
3581
|
+
"channel": suffix, # handy for later
|
|
3582
|
+
})
|
|
3583
|
+
|
|
3584
|
+
# ---- 3) create doc ----
|
|
3506
3585
|
try:
|
|
3507
3586
|
if hasattr(dm, "open_array"):
|
|
3508
3587
|
newdoc = dm.open_array(arr, metadata=meta, title=title)
|
|
@@ -3510,10 +3589,23 @@ class AstroSuiteProMainWindow(
|
|
|
3510
3589
|
newdoc = dm.open_numpy(arr, metadata=meta, title=title)
|
|
3511
3590
|
else:
|
|
3512
3591
|
newdoc = dm.create_document(image=arr, metadata=meta, name=title)
|
|
3592
|
+
|
|
3593
|
+
# ---- 4) ensure WCS is internally consistent on the new doc ----
|
|
3594
|
+
# If source had a WCS solution, rebuild original_header+wcs using your canonical path.
|
|
3595
|
+
try:
|
|
3596
|
+
if meta.get("HasAstrometricSolution") or (meta.get("wcs_header") is not None):
|
|
3597
|
+
wcs_dict = self._extract_wcs_dict(doc) # from SOURCE doc
|
|
3598
|
+
if wcs_dict:
|
|
3599
|
+
self._apply_wcs_dict_to_doc(newdoc, dict(wcs_dict))
|
|
3600
|
+
except Exception:
|
|
3601
|
+
pass
|
|
3602
|
+
|
|
3513
3603
|
self._spawn_subwindow_for(newdoc)
|
|
3604
|
+
|
|
3514
3605
|
except Exception as ex:
|
|
3515
3606
|
QMessageBox.critical(self, "RGB Extract", f"Failed to open '{title}':\n{ex}")
|
|
3516
3607
|
|
|
3608
|
+
|
|
3517
3609
|
_open(r, "R")
|
|
3518
3610
|
_open(g, "G")
|
|
3519
3611
|
_open(b, "B")
|
|
@@ -36,16 +36,31 @@ class HeaderMixin:
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
def _ensure_header_map(self, doc):
|
|
39
|
-
"""Ensure doc has
|
|
39
|
+
"""Ensure doc has an original_header container; preserve fits.Header if present."""
|
|
40
40
|
meta = getattr(doc, "metadata", None)
|
|
41
41
|
if meta is None:
|
|
42
42
|
return None
|
|
43
|
+
|
|
43
44
|
hdr = meta.get("original_header")
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
|
|
46
|
+
# Preserve astropy Header
|
|
47
|
+
try:
|
|
48
|
+
from astropy.io.fits import Header
|
|
49
|
+
if isinstance(hdr, Header):
|
|
50
|
+
return hdr
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
# Preserve dict
|
|
55
|
+
if isinstance(hdr, dict):
|
|
56
|
+
return hdr
|
|
57
|
+
|
|
58
|
+
# If missing/unknown, create dict (but do NOT overwrite a valid Header above)
|
|
59
|
+
hdr = {}
|
|
60
|
+
meta["original_header"] = hdr
|
|
47
61
|
return hdr
|
|
48
62
|
|
|
63
|
+
|
|
49
64
|
def _coerce_wcs_numbers(self, d: dict) -> dict:
|
|
50
65
|
"""Convert common WCS/SIP values to int/float where appropriate."""
|
|
51
66
|
numeric = {
|
|
@@ -72,23 +87,33 @@ class HeaderMixin:
|
|
|
72
87
|
return out
|
|
73
88
|
|
|
74
89
|
def _extract_wcs_dict(self, doc) -> dict:
|
|
75
|
-
"""Collect a complete WCS/SIP dict from the doc's header/meta."""
|
|
76
90
|
if doc is None:
|
|
77
91
|
return {}
|
|
78
|
-
src = (getattr(doc, "metadata", {}) or {}).get("original_header")
|
|
79
92
|
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
meta = getattr(doc, "metadata", {}) or {}
|
|
94
|
+
|
|
95
|
+
# 0) Preferred: astropy WCS object in metadata
|
|
96
|
+
w = meta.get("wcs", None)
|
|
97
|
+
try:
|
|
98
|
+
from astropy.wcs import WCS as _AstroWCS
|
|
99
|
+
if isinstance(w, _AstroWCS):
|
|
100
|
+
h = w.to_header(relax=True)
|
|
101
|
+
return self._coerce_wcs_numbers({k.upper(): h[k] for k in h.keys()})
|
|
102
|
+
except Exception:
|
|
82
103
|
pass
|
|
83
|
-
|
|
104
|
+
|
|
105
|
+
src = meta.get("original_header")
|
|
106
|
+
|
|
107
|
+
wcs = {}
|
|
108
|
+
src = meta.get("original_header")
|
|
109
|
+
if src is not None:
|
|
84
110
|
try:
|
|
85
|
-
|
|
111
|
+
# fits.Header has .keys()
|
|
112
|
+
items = src.items() if hasattr(src, "items") else dict(src).items()
|
|
113
|
+
for k, v in items:
|
|
86
114
|
K = str(k).upper()
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
K in {"RADECSYS", "RADESYS", "NAXIS1", "NAXIS2"} or
|
|
90
|
-
K.startswith(("A_", "B_", "AP_", "BP_"))):
|
|
91
|
-
wcs[K] = v
|
|
115
|
+
...
|
|
116
|
+
wcs[K] = v
|
|
92
117
|
except Exception:
|
|
93
118
|
pass
|
|
94
119
|
|
|
@@ -181,6 +181,7 @@ class MenuMixin:
|
|
|
181
181
|
m_tools.addAction(self.act_ppp)
|
|
182
182
|
|
|
183
183
|
m_tools.addAction(self.act_selective_color)
|
|
184
|
+
m_tools.addAction(self.act_magnitude)
|
|
184
185
|
m_tools.addSeparator()
|
|
185
186
|
m_tools.addAction(self.act_view_bundles)
|
|
186
187
|
m_tools.addAction(self.act_function_bundles)
|
|
@@ -36,7 +36,7 @@ from setiastro.saspro.resources import (
|
|
|
36
36
|
nbtorgb_path, freqsep_path, multiscale_decomp_path, contsub_path, halo_path, cosmic_path,
|
|
37
37
|
satellite_path, imagecombine_path, wims_path, wimi_path, linearfit_path,
|
|
38
38
|
debayer_path, aberration_path, functionbundles_path, viewbundles_path, planetarystacker_path,
|
|
39
|
-
selectivecolor_path, rgbalign_path, planetprojection_path, clonestampicon_path, finderchart_path,
|
|
39
|
+
selectivecolor_path, rgbalign_path, planetprojection_path, clonestampicon_path, finderchart_path,magnitude_path,
|
|
40
40
|
)
|
|
41
41
|
|
|
42
42
|
# Import shortcuts module
|
|
@@ -254,6 +254,7 @@ class ToolbarMixin:
|
|
|
254
254
|
tb_tl.addAction(self.act_multiscale_decomp)
|
|
255
255
|
tb_tl.addAction(self.act_contsub)
|
|
256
256
|
tb_tl.addAction(self.act_image_combine)
|
|
257
|
+
tb_tl.addAction(self.act_magnitude)
|
|
257
258
|
|
|
258
259
|
self._restore_toolbar_order(tb_tl, "Toolbar/Tools")
|
|
259
260
|
try:
|
|
@@ -1149,6 +1150,11 @@ class ToolbarMixin:
|
|
|
1149
1150
|
self.act_selective_color.setStatusTip(self.tr("Adjust specific hue ranges with CMY/RGB controls"))
|
|
1150
1151
|
self.act_selective_color.triggered.connect(self._open_selective_color_tool)
|
|
1151
1152
|
|
|
1153
|
+
self.act_magnitude = QAction(QIcon(magnitude_path), self.tr("Magnitude / Surface Brightness..."), self)
|
|
1154
|
+
self.act_magnitude.setStatusTip(self.tr("Measure magnitude and mag/arcsec² from the active linear view"))
|
|
1155
|
+
self.act_magnitude.setIconVisibleInMenu(True)
|
|
1156
|
+
self.act_magnitude.triggered.connect(self._open_magnitude_tool)
|
|
1157
|
+
|
|
1152
1158
|
# NEW: Frequency Separation
|
|
1153
1159
|
self.act_freqsep = QAction(QIcon(freqsep_path), self.tr("Frequency Separation..."), self)
|
|
1154
1160
|
self.act_freqsep.setStatusTip(self.tr("Split into LF/HF and enhance HF (scale, wavelet, denoise)"))
|
|
@@ -1441,6 +1447,7 @@ class ToolbarMixin:
|
|
|
1441
1447
|
reg("image_combine", self.act_image_combine)
|
|
1442
1448
|
reg("psf_viewer", self.act_psf_viewer)
|
|
1443
1449
|
reg("plate_solve", self.act_plate_solve)
|
|
1450
|
+
reg("magnitude_tool", self.act_magnitude)
|
|
1444
1451
|
reg("star_align", self.act_star_align)
|
|
1445
1452
|
reg("star_register", self.act_star_register)
|
|
1446
1453
|
reg("mosaic_master", self.act_mosaic_master)
|