setiastrosuitepro 1.8.1.post2__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/finderchart.png +0 -0
- setiastro/images/magnitude.png +0 -0
- setiastro/saspro/__main__.py +29 -38
- setiastro/saspro/_generated/build_info.py +2 -2
- setiastro/saspro/abe.py +1 -1
- setiastro/saspro/backgroundneutral.py +54 -16
- setiastro/saspro/blink_comparator_pro.py +3 -1
- setiastro/saspro/bright_stars.py +305 -0
- setiastro/saspro/continuum_subtract.py +2 -1
- setiastro/saspro/cosmicclarity_preset.py +2 -1
- setiastro/saspro/doc_manager.py +8 -0
- setiastro/saspro/exoplanet_detector.py +22 -17
- setiastro/saspro/finder_chart.py +1650 -0
- setiastro/saspro/gui/main_window.py +131 -17
- setiastro/saspro/gui/mixins/header_mixin.py +40 -15
- setiastro/saspro/gui/mixins/menu_mixin.py +3 -0
- setiastro/saspro/gui/mixins/toolbar_mixin.py +16 -1
- setiastro/saspro/imageops/stretch.py +1 -1
- setiastro/saspro/legacy/image_manager.py +18 -4
- setiastro/saspro/legacy/xisf.py +3 -3
- setiastro/saspro/magnitude_tool.py +1724 -0
- setiastro/saspro/main_helpers.py +18 -0
- setiastro/saspro/memory_utils.py +18 -14
- setiastro/saspro/remove_stars.py +13 -30
- setiastro/saspro/resources.py +177 -161
- setiastro/saspro/runtime_torch.py +71 -10
- setiastro/saspro/sfcc.py +86 -77
- setiastro/saspro/stacking_suite.py +4 -3
- setiastro/saspro/star_alignment.py +4 -2
- setiastro/saspro/texture_clarity.py +1 -1
- setiastro/saspro/torch_rejection.py +59 -28
- setiastro/saspro/widgets/image_utils.py +12 -4
- setiastro/saspro/wimi.py +2 -1
- setiastro/saspro/xisf.py +3 -3
- {setiastrosuitepro-1.8.1.post2.dist-info → setiastrosuitepro-1.8.3.dist-info}/METADATA +4 -4
- {setiastrosuitepro-1.8.1.post2.dist-info → setiastrosuitepro-1.8.3.dist-info}/RECORD +40 -35
- {setiastrosuitepro-1.8.1.post2.dist-info → setiastrosuitepro-1.8.3.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.8.1.post2.dist-info → setiastrosuitepro-1.8.3.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.8.1.post2.dist-info → setiastrosuitepro-1.8.3.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.8.1.post2.dist-info → setiastrosuitepro-1.8.3.dist-info}/licenses/license.txt +0 -0
|
@@ -33,8 +33,8 @@ from urllib.parse import quote, quote_plus
|
|
|
33
33
|
# ============================================================================
|
|
34
34
|
import numpy as np
|
|
35
35
|
import matplotlib
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
# tifffile and XISF imports removed (unused in this file)
|
|
37
|
+
|
|
38
38
|
|
|
39
39
|
# ============================================================================
|
|
40
40
|
# Bootstrap Configuration (must run early)
|
|
@@ -93,18 +93,8 @@ from setiastro.saspro.widgets.common_utilities import (
|
|
|
93
93
|
)
|
|
94
94
|
|
|
95
95
|
|
|
96
|
-
# Reproject
|
|
97
|
-
try:
|
|
98
|
-
from reproject import reproject_interp
|
|
99
|
-
except ImportError:
|
|
100
|
-
reproject_interp = None # fallback if not installed
|
|
96
|
+
# Reproject and OpenCV imports removed (unused or available via lazy_imports)
|
|
101
97
|
|
|
102
|
-
# OpenCV for transform estimation & warping
|
|
103
|
-
try:
|
|
104
|
-
import cv2
|
|
105
|
-
OPENCV_AVAILABLE = True
|
|
106
|
-
except ImportError:
|
|
107
|
-
OPENCV_AVAILABLE = False
|
|
108
98
|
|
|
109
99
|
|
|
110
100
|
|
|
@@ -196,7 +186,7 @@ from setiastro.saspro.resources import (
|
|
|
196
186
|
colorwheel_path, font_path, csv_icon_path, spinner_path, wims_path, narrowbandnormalization_path,
|
|
197
187
|
wimi_path, linearfit_path, debayer_path, aberration_path, acv_icon_path,
|
|
198
188
|
functionbundles_path, viewbundles_path, selectivecolor_path, rgbalign_path, planetarystacker_path,
|
|
199
|
-
background_path, script_icon_path, planetprojection_path,clonestampicon_path,
|
|
189
|
+
background_path, script_icon_path, planetprojection_path,clonestampicon_path, finderchart_path,magnitude_path,
|
|
200
190
|
)
|
|
201
191
|
|
|
202
192
|
import faulthandler
|
|
@@ -3195,6 +3185,45 @@ class AstroSuiteProMainWindow(
|
|
|
3195
3185
|
pass
|
|
3196
3186
|
self.SFCC_window.show()
|
|
3197
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
|
+
|
|
3198
3227
|
def show_convo_deconvo(self, doc=None):
|
|
3199
3228
|
# Reuse existing dialog if it's already open
|
|
3200
3229
|
sw = self.mdi.activeSubWindow()
|
|
@@ -3506,13 +3535,53 @@ class AstroSuiteProMainWindow(
|
|
|
3506
3535
|
base = base_title or (getattr(doc, "display_name", lambda: None)() or "RGB")
|
|
3507
3536
|
|
|
3508
3537
|
def _open(arr, suffix):
|
|
3509
|
-
|
|
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({
|
|
3510
3577
|
"source": "RGB Extract",
|
|
3511
3578
|
"is_mono": True,
|
|
3512
3579
|
"bit_depth": "32-bit floating point",
|
|
3513
3580
|
"parent_title": base,
|
|
3514
|
-
|
|
3515
|
-
|
|
3581
|
+
"channel": suffix, # handy for later
|
|
3582
|
+
})
|
|
3583
|
+
|
|
3584
|
+
# ---- 3) create doc ----
|
|
3516
3585
|
try:
|
|
3517
3586
|
if hasattr(dm, "open_array"):
|
|
3518
3587
|
newdoc = dm.open_array(arr, metadata=meta, title=title)
|
|
@@ -3520,10 +3589,23 @@ class AstroSuiteProMainWindow(
|
|
|
3520
3589
|
newdoc = dm.open_numpy(arr, metadata=meta, title=title)
|
|
3521
3590
|
else:
|
|
3522
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
|
+
|
|
3523
3603
|
self._spawn_subwindow_for(newdoc)
|
|
3604
|
+
|
|
3524
3605
|
except Exception as ex:
|
|
3525
3606
|
QMessageBox.critical(self, "RGB Extract", f"Failed to open '{title}':\n{ex}")
|
|
3526
3607
|
|
|
3608
|
+
|
|
3527
3609
|
_open(r, "R")
|
|
3528
3610
|
_open(g, "G")
|
|
3529
3611
|
_open(b, "B")
|
|
@@ -4238,6 +4320,38 @@ class AstroSuiteProMainWindow(
|
|
|
4238
4320
|
except Exception:
|
|
4239
4321
|
pass
|
|
4240
4322
|
|
|
4323
|
+
def _doc_has_wcs(self, doc) -> bool:
|
|
4324
|
+
if doc is None:
|
|
4325
|
+
return False
|
|
4326
|
+
meta = getattr(doc, "metadata", None) or {}
|
|
4327
|
+
if meta.get("wcs") is not None:
|
|
4328
|
+
return True
|
|
4329
|
+
|
|
4330
|
+
hdr = meta.get("original_header") or meta.get("fits_header") or meta.get("header")
|
|
4331
|
+
if hdr is None:
|
|
4332
|
+
return False
|
|
4333
|
+
|
|
4334
|
+
try:
|
|
4335
|
+
keys = {str(k).upper() for k in hdr.keys()}
|
|
4336
|
+
except Exception:
|
|
4337
|
+
try:
|
|
4338
|
+
keys = {str(k).upper() for k in dict(hdr).keys()}
|
|
4339
|
+
except Exception:
|
|
4340
|
+
return False
|
|
4341
|
+
|
|
4342
|
+
return {"CTYPE1","CTYPE2","CRVAL1","CRVAL2"}.issubset(keys)
|
|
4343
|
+
|
|
4344
|
+
|
|
4345
|
+
def _open_finder_chart(self):
|
|
4346
|
+
doc = self._active_doc()
|
|
4347
|
+
if not self._doc_has_wcs(doc):
|
|
4348
|
+
QMessageBox.information(self, self.tr("Finder Chart"), self.tr("Active image has no astrometric solution (WCS). Plate solve first."))
|
|
4349
|
+
return
|
|
4350
|
+
|
|
4351
|
+
from setiastro.saspro.finder_chart import FinderChartDialog
|
|
4352
|
+
dlg = FinderChartDialog(doc=doc, settings=self.settings, parent=self)
|
|
4353
|
+
dlg.setWindowIcon(QIcon(finderchart_path))
|
|
4354
|
+
dlg.show()
|
|
4241
4355
|
|
|
4242
4356
|
def _open_stellar_alignment(self):
|
|
4243
4357
|
from setiastro.saspro.star_alignment import StellarAlignmentDialog
|
|
@@ -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)
|
|
@@ -204,6 +205,7 @@ class MenuMixin:
|
|
|
204
205
|
|
|
205
206
|
m_star.addAction(self.act_astrospike)
|
|
206
207
|
m_star.addAction(self.act_exo_detector)
|
|
208
|
+
|
|
207
209
|
m_star.addAction(self.act_image_peeker)
|
|
208
210
|
m_star.addAction(self.act_isophote)
|
|
209
211
|
m_star.addAction(self.act_live_stacking)
|
|
@@ -233,6 +235,7 @@ class MenuMixin:
|
|
|
233
235
|
m_wim = mb.addMenu(self.tr("&What's In My..."))
|
|
234
236
|
m_wim.addAction(self.act_whats_in_my_sky)
|
|
235
237
|
m_wim.addAction(self.act_wimi)
|
|
238
|
+
m_wim.addAction(self.act_finder_chart)
|
|
236
239
|
|
|
237
240
|
m_scripts = mb.addMenu(self.tr("&Scripts"))
|
|
238
241
|
self.menu_scripts = m_scripts
|
|
@@ -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,
|
|
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:
|
|
@@ -298,6 +299,7 @@ class ToolbarMixin:
|
|
|
298
299
|
tb_star.addAction(self.act_planetary_stacker)
|
|
299
300
|
tb_star.addAction(self.act_planet_projection)
|
|
300
301
|
tb_star.addAction(self.act_plate_solve)
|
|
302
|
+
|
|
301
303
|
tb_star.addAction(self.act_star_align)
|
|
302
304
|
tb_star.addAction(self.act_star_register)
|
|
303
305
|
tb_star.addAction(self.act_rgb_align)
|
|
@@ -332,6 +334,7 @@ class ToolbarMixin:
|
|
|
332
334
|
tb_wim = DraggableToolBar(self.tr("What's In My..."), self)
|
|
333
335
|
tb_wim.setObjectName("What's In My...")
|
|
334
336
|
tb_wim.setSettingsKey("Toolbar/WhatsInMy")
|
|
337
|
+
tb_wim.addAction(self.act_finder_chart)
|
|
335
338
|
self.addToolBar(Qt.ToolBarArea.TopToolBarArea, tb_wim)
|
|
336
339
|
|
|
337
340
|
tb_wim.addAction(self.act_whats_in_my_sky)
|
|
@@ -1147,6 +1150,11 @@ class ToolbarMixin:
|
|
|
1147
1150
|
self.act_selective_color.setStatusTip(self.tr("Adjust specific hue ranges with CMY/RGB controls"))
|
|
1148
1151
|
self.act_selective_color.triggered.connect(self._open_selective_color_tool)
|
|
1149
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
|
+
|
|
1150
1158
|
# NEW: Frequency Separation
|
|
1151
1159
|
self.act_freqsep = QAction(QIcon(freqsep_path), self.tr("Frequency Separation..."), self)
|
|
1152
1160
|
self.act_freqsep.setStatusTip(self.tr("Split into LF/HF and enhance HF (scale, wavelet, denoise)"))
|
|
@@ -1245,6 +1253,11 @@ class ToolbarMixin:
|
|
|
1245
1253
|
self.act_rgb_align.setStatusTip(self.tr("Align R and B channels to G using astroalign (affine/homography/poly)"))
|
|
1246
1254
|
self.act_rgb_align.triggered.connect(self._open_rgb_align)
|
|
1247
1255
|
|
|
1256
|
+
self.act_finder_chart = QAction(QIcon(finderchart_path), self.tr("Finder Chart..."), self)
|
|
1257
|
+
self.act_finder_chart.setIconVisibleInMenu(True)
|
|
1258
|
+
self.act_finder_chart.setStatusTip(self.tr("Show a finder chart for the active plate-solved image"))
|
|
1259
|
+
self.act_finder_chart.triggered.connect(self._open_finder_chart)
|
|
1260
|
+
|
|
1248
1261
|
self.act_whats_in_my_sky = QAction(QIcon(wims_path), self.tr("What's In My Sky..."), self)
|
|
1249
1262
|
self.act_whats_in_my_sky.setIconVisibleInMenu(True)
|
|
1250
1263
|
self.act_whats_in_my_sky.setStatusTip(self.tr("Plan targets by altitude, transit time, and lunar separation"))
|
|
@@ -1434,6 +1447,7 @@ class ToolbarMixin:
|
|
|
1434
1447
|
reg("image_combine", self.act_image_combine)
|
|
1435
1448
|
reg("psf_viewer", self.act_psf_viewer)
|
|
1436
1449
|
reg("plate_solve", self.act_plate_solve)
|
|
1450
|
+
reg("magnitude_tool", self.act_magnitude)
|
|
1437
1451
|
reg("star_align", self.act_star_align)
|
|
1438
1452
|
reg("star_register", self.act_star_register)
|
|
1439
1453
|
reg("mosaic_master", self.act_mosaic_master)
|
|
@@ -1446,6 +1460,7 @@ class ToolbarMixin:
|
|
|
1446
1460
|
reg("astrospike", self.act_astrospike)
|
|
1447
1461
|
reg("exo_detector", self.act_exo_detector)
|
|
1448
1462
|
reg("isophote", self.act_isophote)
|
|
1463
|
+
reg("finder_chart", self.act_finder_chart)
|
|
1449
1464
|
reg("rgb_align", self.act_rgb_align)
|
|
1450
1465
|
reg("whats_in_my_sky", self.act_whats_in_my_sky)
|
|
1451
1466
|
reg("whats_in_my_image", self.act_wimi)
|
|
@@ -242,8 +242,16 @@ class ImageManager(QObject):
|
|
|
242
242
|
def set_image(self, new_image, metadata, step_name=None):
|
|
243
243
|
slot = self.current_slot
|
|
244
244
|
if self._images[slot] is not None:
|
|
245
|
+
# OPTIMIZATION: If we are setting the EXACT SAME image object (e.g. metadata update),
|
|
246
|
+
# do not deep-copy the image data to undo stack.
|
|
247
|
+
# This saves massive memory when just renaming a slot or changing WCS.
|
|
248
|
+
if new_image is self._images[slot]:
|
|
249
|
+
stored_img = self._images[slot] # Shallow copy / reference
|
|
250
|
+
else:
|
|
251
|
+
stored_img = self._images[slot].copy() # Deep copy for safety
|
|
252
|
+
|
|
245
253
|
self._undo_stacks[slot].append(
|
|
246
|
-
(
|
|
254
|
+
(stored_img, self._metadata[slot].copy(), step_name or "Unnamed Step")
|
|
247
255
|
)
|
|
248
256
|
self._redo_stacks[slot].clear()
|
|
249
257
|
print(f"ImageManager: Previous image in slot {slot} pushed to undo stack.")
|
|
@@ -1526,8 +1534,12 @@ def load_image(filename, max_retries=3, wait_seconds=3, return_metadata: bool =
|
|
|
1526
1534
|
# 1) PixInsight astrometric solution (fallback only)
|
|
1527
1535
|
try:
|
|
1528
1536
|
if not all(k in hdr for k in ("CRPIX1","CRPIX2","CRVAL1","CRVAL2")):
|
|
1529
|
-
|
|
1530
|
-
|
|
1537
|
+
p_img = props['PCL:AstrometricSolution:ReferenceImageCoordinates']
|
|
1538
|
+
p_sky = props['PCL:AstrometricSolution:ReferenceCelestialCoordinates']
|
|
1539
|
+
|
|
1540
|
+
# Resolve lazy properties (decode base64/binary)
|
|
1541
|
+
ref_img = xisf.resolve_property(p_img)
|
|
1542
|
+
ref_sky = xisf.resolve_property(p_sky)
|
|
1531
1543
|
|
|
1532
1544
|
# Some files store extra values; only first two are CRPIX/CRVAL
|
|
1533
1545
|
im0, im1 = float(ref_img[0]), float(ref_img[1])
|
|
@@ -1547,7 +1559,9 @@ def load_image(filename, max_retries=3, wait_seconds=3, return_metadata: bool =
|
|
|
1547
1559
|
# 2) CD matrix (fallback only)
|
|
1548
1560
|
try:
|
|
1549
1561
|
if not all(k in hdr for k in ("CD1_1","CD1_2","CD2_1","CD2_2")):
|
|
1550
|
-
|
|
1562
|
+
p_mat = props['PCL:AstrometricSolution:LinearTransformationMatrix']
|
|
1563
|
+
lin = np.asarray(xisf.resolve_property(p_mat), float)
|
|
1564
|
+
|
|
1551
1565
|
hdr['CD1_1'], hdr['CD1_2'] = float(lin[0,0]), float(lin[0,1])
|
|
1552
1566
|
hdr['CD2_1'], hdr['CD2_2'] = float(lin[1,0]), float(lin[1,1])
|
|
1553
1567
|
_filled |= {'CD1_1','CD1_2','CD2_1','CD2_2'}
|
setiastro/saspro/legacy/xisf.py
CHANGED
|
@@ -1072,7 +1072,7 @@ class XISF:
|
|
|
1072
1072
|
}
|
|
1073
1073
|
try:
|
|
1074
1074
|
return _dtypes[s]
|
|
1075
|
-
except:
|
|
1075
|
+
except KeyError:
|
|
1076
1076
|
raise NotImplementedError(f"sampleFormat {s} not implemented")
|
|
1077
1077
|
|
|
1078
1078
|
# Return XISF data type from numpy dtype
|
|
@@ -1087,7 +1087,7 @@ class XISF:
|
|
|
1087
1087
|
}
|
|
1088
1088
|
try:
|
|
1089
1089
|
return _sampleFormats[str(dtype)]
|
|
1090
|
-
except:
|
|
1090
|
+
except KeyError:
|
|
1091
1091
|
raise NotImplementedError(f"sampleFormat for {dtype} not implemented")
|
|
1092
1092
|
|
|
1093
1093
|
@staticmethod
|
|
@@ -1121,7 +1121,7 @@ class XISF:
|
|
|
1121
1121
|
}
|
|
1122
1122
|
try:
|
|
1123
1123
|
return _dtypes[type_prefix]
|
|
1124
|
-
except:
|
|
1124
|
+
except KeyError:
|
|
1125
1125
|
raise NotImplementedError(f"data type {type_name} not implemented")
|
|
1126
1126
|
|
|
1127
1127
|
# __/ Auxiliary functions for compression/shuffling \________
|