setiastrosuitepro 1.6.4__py3-none-any.whl → 1.6.10__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/abeicon.svg +16 -0
- setiastro/images/acv_icon.png +0 -0
- setiastro/images/cosmic.svg +40 -0
- setiastro/images/cosmicsat.svg +24 -0
- setiastro/images/first_quarter.png +0 -0
- setiastro/images/full_moon.png +0 -0
- setiastro/images/graxpert.svg +19 -0
- setiastro/images/last_quarter.png +0 -0
- setiastro/images/linearfit.svg +32 -0
- setiastro/images/new_moon.png +0 -0
- setiastro/images/pixelmath.svg +42 -0
- setiastro/images/waning_crescent_1.png +0 -0
- setiastro/images/waning_crescent_2.png +0 -0
- setiastro/images/waning_crescent_3.png +0 -0
- setiastro/images/waning_crescent_4.png +0 -0
- setiastro/images/waning_crescent_5.png +0 -0
- setiastro/images/waning_gibbous_1.png +0 -0
- setiastro/images/waning_gibbous_2.png +0 -0
- setiastro/images/waning_gibbous_3.png +0 -0
- setiastro/images/waning_gibbous_4.png +0 -0
- setiastro/images/waning_gibbous_5.png +0 -0
- setiastro/images/waxing_crescent_1.png +0 -0
- setiastro/images/waxing_crescent_2.png +0 -0
- setiastro/images/waxing_crescent_3.png +0 -0
- setiastro/images/waxing_crescent_4.png +0 -0
- setiastro/images/waxing_crescent_5.png +0 -0
- setiastro/images/waxing_gibbous_1.png +0 -0
- setiastro/images/waxing_gibbous_2.png +0 -0
- setiastro/images/waxing_gibbous_3.png +0 -0
- setiastro/images/waxing_gibbous_4.png +0 -0
- setiastro/images/waxing_gibbous_5.png +0 -0
- setiastro/qml/ResourceMonitor.qml +84 -82
- setiastro/saspro/__main__.py +19 -0
- setiastro/saspro/_generated/build_info.py +2 -2
- setiastro/saspro/abe.py +37 -4
- setiastro/saspro/aberration_ai.py +237 -21
- setiastro/saspro/acv_exporter.py +379 -0
- setiastro/saspro/add_stars.py +33 -6
- setiastro/saspro/backgroundneutral.py +35 -7
- setiastro/saspro/blemish_blaster.py +4 -1
- setiastro/saspro/blink_comparator_pro.py +74 -24
- setiastro/saspro/clahe.py +4 -1
- setiastro/saspro/continuum_subtract.py +4 -1
- setiastro/saspro/convo.py +4 -1
- setiastro/saspro/cosmicclarity.py +129 -18
- setiastro/saspro/crop_dialog_pro.py +123 -7
- setiastro/saspro/curve_editor_pro.py +109 -42
- setiastro/saspro/doc_manager.py +67 -4
- setiastro/saspro/exoplanet_detector.py +120 -28
- setiastro/saspro/frequency_separation.py +1158 -204
- setiastro/saspro/ghs_dialog_pro.py +81 -16
- setiastro/saspro/graxpert.py +1 -0
- setiastro/saspro/gui/main_window.py +393 -204
- setiastro/saspro/gui/mixins/menu_mixin.py +1 -0
- setiastro/saspro/gui/mixins/theme_mixin.py +160 -14
- setiastro/saspro/gui/mixins/toolbar_mixin.py +356 -12
- setiastro/saspro/gui/mixins/update_mixin.py +138 -36
- setiastro/saspro/gui/mixins/view_mixin.py +42 -0
- setiastro/saspro/halobgon.py +4 -0
- setiastro/saspro/histogram.py +5 -1
- setiastro/saspro/image_combine.py +4 -0
- setiastro/saspro/image_peeker_pro.py +4 -0
- setiastro/saspro/imageops/stretch.py +531 -62
- setiastro/saspro/isophote.py +4 -0
- setiastro/saspro/layers.py +13 -9
- setiastro/saspro/layers_dock.py +183 -3
- setiastro/saspro/legacy/image_manager.py +154 -20
- setiastro/saspro/legacy/numba_utils.py +43 -0
- setiastro/saspro/legacy/xisf.py +240 -98
- setiastro/saspro/live_stacking.py +180 -79
- setiastro/saspro/luminancerecombine.py +228 -27
- setiastro/saspro/mask_creation.py +174 -15
- setiastro/saspro/mfdeconv.py +113 -35
- setiastro/saspro/mfdeconvcudnn.py +119 -70
- setiastro/saspro/mfdeconvsport.py +112 -35
- setiastro/saspro/morphology.py +4 -0
- setiastro/saspro/multiscale_decomp.py +51 -12
- setiastro/saspro/numba_utils.py +72 -2
- setiastro/saspro/ops/commands.py +18 -18
- setiastro/saspro/ops/script_editor.py +5 -2
- setiastro/saspro/ops/scripts.py +3 -0
- setiastro/saspro/perfect_palette_picker.py +37 -3
- setiastro/saspro/plate_solver.py +84 -49
- setiastro/saspro/psf_viewer.py +119 -37
- setiastro/saspro/resources.py +67 -0
- setiastro/saspro/rgbalign.py +4 -0
- setiastro/saspro/selective_color.py +4 -1
- setiastro/saspro/sfcc.py +60 -2
- setiastro/saspro/shortcuts.py +142 -23
- setiastro/saspro/signature_insert.py +692 -33
- setiastro/saspro/stacking_suite.py +1017 -400
- setiastro/saspro/star_alignment.py +4 -1
- setiastro/saspro/star_spikes.py +4 -0
- setiastro/saspro/star_stretch.py +38 -3
- setiastro/saspro/stat_stretch.py +702 -128
- setiastro/saspro/subwindow.py +786 -360
- setiastro/saspro/supernovaasteroidhunter.py +1 -1
- setiastro/saspro/wavescale_hdr.py +4 -1
- setiastro/saspro/wavescalede.py +4 -1
- setiastro/saspro/whitebalance.py +60 -12
- setiastro/saspro/widgets/common_utilities.py +28 -21
- setiastro/saspro/widgets/resource_monitor.py +109 -59
- setiastro/saspro/widgets/spinboxes.py +10 -13
- setiastro/saspro/wimi.py +27 -656
- setiastro/saspro/wims.py +13 -3
- setiastro/saspro/xisf.py +101 -11
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.6.10.dist-info}/METADATA +2 -1
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.6.10.dist-info}/RECORD +112 -80
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.6.10.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.6.10.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.6.10.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.6.10.dist-info}/licenses/license.txt +0 -0
setiastro/saspro/psf_viewer.py
CHANGED
|
@@ -13,7 +13,7 @@ from PyQt6.QtWidgets import (
|
|
|
13
13
|
)
|
|
14
14
|
from setiastro.saspro.widgets.themed_buttons import themed_toolbtn
|
|
15
15
|
|
|
16
|
-
from PyQt6.QtCore import
|
|
16
|
+
from PyQt6.QtCore import QThread, pyqtSignal, QObject
|
|
17
17
|
from PyQt6.QtWidgets import QWidget
|
|
18
18
|
|
|
19
19
|
class _ProcessingOverlay(QWidget):
|
|
@@ -108,7 +108,10 @@ class PSFViewer(QDialog):
|
|
|
108
108
|
# Accept either a view (with .document) or a doc directly
|
|
109
109
|
doc = getattr(view_or_doc, "document", None)
|
|
110
110
|
self.doc = doc if doc is not None else view_or_doc
|
|
111
|
-
|
|
111
|
+
try:
|
|
112
|
+
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
|
|
113
|
+
except Exception:
|
|
114
|
+
pass # older PyQt6 versions
|
|
112
115
|
# Image + state
|
|
113
116
|
self.image = self._grab_image()
|
|
114
117
|
self.zoom_factor = 1.0
|
|
@@ -124,12 +127,19 @@ class PSFViewer(QDialog):
|
|
|
124
127
|
self.threshold_timer.timeout.connect(self._applyThreshold)
|
|
125
128
|
|
|
126
129
|
# Auto-update when the document changes
|
|
130
|
+
|
|
131
|
+
self._psf_thread = None
|
|
132
|
+
self._psf_worker = None
|
|
133
|
+
self._doc_conn = False
|
|
127
134
|
if hasattr(self.doc, "changed"):
|
|
128
135
|
try:
|
|
129
136
|
self.doc.changed.connect(self._on_doc_changed)
|
|
137
|
+
self._doc_conn = True
|
|
130
138
|
except Exception:
|
|
131
|
-
|
|
139
|
+
self._doc_conn = False
|
|
132
140
|
|
|
141
|
+
# cleanup no matter how the dialog is dismissed (accept/reject/done)
|
|
142
|
+
self.finished.connect(self._cleanup)
|
|
133
143
|
self._build_ui()
|
|
134
144
|
# Defer first compute until after the dialog is shown/layouted
|
|
135
145
|
QTimer.singleShot(0, self._applyThreshold)
|
|
@@ -235,7 +245,7 @@ class PSFViewer(QDialog):
|
|
|
235
245
|
|
|
236
246
|
# Close
|
|
237
247
|
close_btn = QPushButton("Close", self)
|
|
238
|
-
close_btn.clicked.connect(self.
|
|
248
|
+
close_btn.clicked.connect(self.close)
|
|
239
249
|
main_layout.addWidget(close_btn)
|
|
240
250
|
|
|
241
251
|
self.setLayout(main_layout)
|
|
@@ -279,7 +289,6 @@ class PSFViewer(QDialog):
|
|
|
279
289
|
self.hist_label.resize(scaled.size())
|
|
280
290
|
|
|
281
291
|
def _applyThreshold(self):
|
|
282
|
-
# kick off worker
|
|
283
292
|
if self.image is None:
|
|
284
293
|
self.star_list = None
|
|
285
294
|
self.status_label.setText("Status: No image.")
|
|
@@ -288,46 +297,72 @@ class PSFViewer(QDialog):
|
|
|
288
297
|
|
|
289
298
|
self._show_processing("Processing… extracting stars / PSFs")
|
|
290
299
|
|
|
291
|
-
#
|
|
292
|
-
|
|
293
|
-
try:
|
|
294
|
-
self._psf_thread.quit()
|
|
295
|
-
self._psf_thread.wait(50)
|
|
296
|
-
except Exception:
|
|
297
|
-
pass
|
|
300
|
+
# stop any previous run cleanly
|
|
301
|
+
self._stop_psf_worker()
|
|
298
302
|
|
|
299
303
|
self._psf_thread = QThread(self)
|
|
300
304
|
self._psf_worker = _PSFWorker(self.image, self.detection_threshold)
|
|
301
305
|
self._psf_worker.moveToThread(self._psf_thread)
|
|
302
306
|
|
|
303
307
|
self._psf_thread.started.connect(self._psf_worker.run)
|
|
308
|
+
self._psf_worker.finished.connect(self._on_psf_done)
|
|
309
|
+
self._psf_worker.failed.connect(self._on_psf_fail)
|
|
304
310
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
self._hide_processing()
|
|
309
|
-
self.drawHistogram()
|
|
310
|
-
self._psf_thread.quit()
|
|
311
|
-
self._psf_thread.wait(100)
|
|
312
|
-
|
|
313
|
-
def _fail(msg):
|
|
314
|
-
self.star_list = None
|
|
315
|
-
self.status_label.setText(f"Status: {msg}")
|
|
316
|
-
self._hide_processing()
|
|
317
|
-
self.drawHistogram()
|
|
318
|
-
self._psf_thread.quit()
|
|
319
|
-
self._psf_thread.wait(100)
|
|
311
|
+
# ensure thread quits once worker reports anything
|
|
312
|
+
self._psf_worker.finished.connect(lambda *_: self._stop_psf_worker(quit_only=False))
|
|
313
|
+
self._psf_worker.failed.connect(lambda *_: self._stop_psf_worker(quit_only=False))
|
|
320
314
|
|
|
321
|
-
self._psf_worker.finished.connect(_done)
|
|
322
|
-
self._psf_worker.failed.connect(_fail)
|
|
323
315
|
|
|
324
316
|
self._psf_thread.start()
|
|
325
317
|
|
|
318
|
+
def _stop_psf_worker(self, quit_only: bool = False):
|
|
319
|
+
thr = getattr(self, "_psf_thread", None)
|
|
320
|
+
wkr = getattr(self, "_psf_worker", None)
|
|
321
|
+
|
|
322
|
+
if thr is None:
|
|
323
|
+
return
|
|
324
|
+
|
|
325
|
+
try:
|
|
326
|
+
thr.quit()
|
|
327
|
+
except Exception:
|
|
328
|
+
pass
|
|
329
|
+
try:
|
|
330
|
+
thr.wait(250)
|
|
331
|
+
except Exception:
|
|
332
|
+
pass
|
|
333
|
+
|
|
334
|
+
if not quit_only:
|
|
335
|
+
try:
|
|
336
|
+
if wkr is not None:
|
|
337
|
+
wkr.deleteLater()
|
|
338
|
+
except Exception:
|
|
339
|
+
pass
|
|
340
|
+
try:
|
|
341
|
+
thr.deleteLater()
|
|
342
|
+
except Exception:
|
|
343
|
+
pass
|
|
344
|
+
self._psf_worker = None
|
|
345
|
+
self._psf_thread = None
|
|
346
|
+
|
|
347
|
+
def _on_psf_done(self, tbl, status: str):
|
|
348
|
+
# tbl is an astropy Table or None
|
|
349
|
+
self.star_list = tbl
|
|
350
|
+
self.status_label.setText(status)
|
|
351
|
+
self._hide_processing()
|
|
352
|
+
self.drawHistogram()
|
|
353
|
+
|
|
354
|
+
def _on_psf_fail(self, msg: str):
|
|
355
|
+
self.star_list = None
|
|
356
|
+
self.status_label.setText(f"Status: {msg}")
|
|
357
|
+
self._hide_processing()
|
|
358
|
+
self.drawHistogram()
|
|
359
|
+
|
|
326
360
|
|
|
327
361
|
def updateImage(self, new_image):
|
|
328
362
|
self.image = np.asarray(new_image) if new_image is not None else None
|
|
329
|
-
self.
|
|
330
|
-
|
|
363
|
+
if self.threshold_timer.isActive():
|
|
364
|
+
self.threshold_timer.stop()
|
|
365
|
+
self.threshold_timer.start()
|
|
331
366
|
|
|
332
367
|
def updateZoom(self, _=None):
|
|
333
368
|
self._apply_hist_zoom()
|
|
@@ -538,12 +573,59 @@ class PSFViewer(QDialog):
|
|
|
538
573
|
it.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
539
574
|
self.stats_table.setItem(ri, ci, it)
|
|
540
575
|
|
|
576
|
+
|
|
577
|
+
def _cleanup(self):
|
|
578
|
+
# stop debounce timer
|
|
579
|
+
try:
|
|
580
|
+
if getattr(self, "threshold_timer", None) is not None:
|
|
581
|
+
self.threshold_timer.stop()
|
|
582
|
+
except Exception:
|
|
583
|
+
pass
|
|
584
|
+
|
|
585
|
+
# disconnect doc listener
|
|
586
|
+
try:
|
|
587
|
+
if self._doc_conn and hasattr(self.doc, "changed"):
|
|
588
|
+
self.doc.changed.disconnect(self._on_doc_changed)
|
|
589
|
+
except Exception:
|
|
590
|
+
pass
|
|
591
|
+
self._doc_conn = False
|
|
592
|
+
|
|
593
|
+
# stop worker/thread
|
|
594
|
+
try:
|
|
595
|
+
thr = getattr(self, "_psf_thread", None)
|
|
596
|
+
wkr = getattr(self, "_psf_worker", None)
|
|
597
|
+
|
|
598
|
+
if wkr is not None:
|
|
599
|
+
try:
|
|
600
|
+
wkr.deleteLater()
|
|
601
|
+
except Exception:
|
|
602
|
+
pass
|
|
603
|
+
|
|
604
|
+
if thr is not None:
|
|
605
|
+
try:
|
|
606
|
+
thr.requestInterruption()
|
|
607
|
+
except Exception:
|
|
608
|
+
pass
|
|
609
|
+
try:
|
|
610
|
+
thr.quit()
|
|
611
|
+
except Exception:
|
|
612
|
+
pass
|
|
613
|
+
try:
|
|
614
|
+
thr.wait(250)
|
|
615
|
+
except Exception:
|
|
616
|
+
pass
|
|
617
|
+
try:
|
|
618
|
+
thr.deleteLater()
|
|
619
|
+
except Exception:
|
|
620
|
+
pass
|
|
621
|
+
except Exception:
|
|
622
|
+
pass
|
|
623
|
+
|
|
624
|
+
self._psf_worker = None
|
|
625
|
+
self._psf_thread = None
|
|
626
|
+
|
|
541
627
|
# ---------- lifecycle ----------
|
|
542
628
|
def closeEvent(self, e):
|
|
543
|
-
|
|
544
|
-
if hasattr(self.doc, "changed"):
|
|
545
|
-
try:
|
|
546
|
-
self.doc.changed.disconnect(self._on_doc_changed)
|
|
547
|
-
except Exception:
|
|
548
|
-
pass
|
|
629
|
+
self._cleanup()
|
|
549
630
|
super().closeEvent(e)
|
|
631
|
+
|
setiastro/saspro/resources.py
CHANGED
|
@@ -245,6 +245,39 @@ class Icons:
|
|
|
245
245
|
LIVE_STACKING = property(lambda self: _resource_path('livestacking.png'))
|
|
246
246
|
IMAGE_COMBINE = property(lambda self: _resource_path('imagecombine.png'))
|
|
247
247
|
|
|
248
|
+
# Moon phase (WIMS)
|
|
249
|
+
MOON_NEW = property(lambda self: _resource_path('new_moon.png'))
|
|
250
|
+
MOON_WAXING_CRES_1 = property(lambda self: _resource_path('waxing_crescent_1.png'))
|
|
251
|
+
MOON_WAXING_CRES_2 = property(lambda self: _resource_path('waxing_crescent_2.png'))
|
|
252
|
+
MOON_WAXING_CRES_3 = property(lambda self: _resource_path('waxing_crescent_3.png'))
|
|
253
|
+
MOON_WAXING_CRES_4 = property(lambda self: _resource_path('waxing_crescent_4.png'))
|
|
254
|
+
MOON_WAXING_CRES_5 = property(lambda self: _resource_path('waxing_crescent_5.png'))
|
|
255
|
+
|
|
256
|
+
MOON_FIRST_QUARTER = property(lambda self: _resource_path('first_quarter.png'))
|
|
257
|
+
|
|
258
|
+
MOON_WAXING_GIB_1 = property(lambda self: _resource_path('waxing_gibbous_1.png'))
|
|
259
|
+
MOON_WAXING_GIB_2 = property(lambda self: _resource_path('waxing_gibbous_2.png'))
|
|
260
|
+
MOON_WAXING_GIB_3 = property(lambda self: _resource_path('waxing_gibbous_3.png'))
|
|
261
|
+
MOON_WAXING_GIB_4 = property(lambda self: _resource_path('waxing_gibbous_4.png'))
|
|
262
|
+
MOON_WAXING_GIB_5 = property(lambda self: _resource_path('waxing_gibbous_5.png'))
|
|
263
|
+
|
|
264
|
+
MOON_FULL = property(lambda self: _resource_path('full_moon.png'))
|
|
265
|
+
|
|
266
|
+
MOON_WANING_GIB_1 = property(lambda self: _resource_path('waning_gibbous_1.png'))
|
|
267
|
+
MOON_WANING_GIB_2 = property(lambda self: _resource_path('waning_gibbous_2.png'))
|
|
268
|
+
MOON_WANING_GIB_3 = property(lambda self: _resource_path('waning_gibbous_3.png'))
|
|
269
|
+
MOON_WANING_GIB_4 = property(lambda self: _resource_path('waning_gibbous_4.png'))
|
|
270
|
+
MOON_WANING_GIB_5 = property(lambda self: _resource_path('waning_gibbous_5.png'))
|
|
271
|
+
|
|
272
|
+
MOON_LAST_QUARTER = property(lambda self: _resource_path('last_quarter.png'))
|
|
273
|
+
|
|
274
|
+
MOON_WANING_CRES_1 = property(lambda self: _resource_path('waning_crescent_1.png'))
|
|
275
|
+
MOON_WANING_CRES_2 = property(lambda self: _resource_path('waning_crescent_2.png'))
|
|
276
|
+
MOON_WANING_CRES_3 = property(lambda self: _resource_path('waning_crescent_3.png'))
|
|
277
|
+
MOON_WANING_CRES_4 = property(lambda self: _resource_path('waning_crescent_4.png'))
|
|
278
|
+
MOON_WANING_CRES_5 = property(lambda self: _resource_path('waning_crescent_5.png'))
|
|
279
|
+
|
|
280
|
+
|
|
248
281
|
# Special features
|
|
249
282
|
SUPERNOVA = property(lambda self: _resource_path('supernova.png'))
|
|
250
283
|
PEDESTAL = property(lambda self: _resource_path('pedestal.png'))
|
|
@@ -284,6 +317,7 @@ class Icons:
|
|
|
284
317
|
CSV = property(lambda self: _resource_path('cvs.png'))
|
|
285
318
|
PPP = property(lambda self: _resource_path('ppp.png'))
|
|
286
319
|
SCRIPT = property(lambda self: _resource_path('script.png'))
|
|
320
|
+
ACV = property(lambda self: _resource_path('acv_icon.png'))
|
|
287
321
|
|
|
288
322
|
# Blink & comparison
|
|
289
323
|
BLINK = property(lambda self: _resource_path('blink.png'))
|
|
@@ -393,6 +427,39 @@ def _init_legacy_paths():
|
|
|
393
427
|
'slot7_path': get_icon_path('slot7.png'),
|
|
394
428
|
'slot8_path': get_icon_path('slot8.png'),
|
|
395
429
|
'slot9_path': get_icon_path('slot9.png'),
|
|
430
|
+
'acv_icon_path': get_icon_path('acv_icon.png'),
|
|
431
|
+
|
|
432
|
+
'moon_new_path': get_icon_path('new_moon.png'),
|
|
433
|
+
'moon_waxing_crescent_1_path': get_icon_path('waxing_crescent_1.png'),
|
|
434
|
+
'moon_waxing_crescent_2_path': get_icon_path('waxing_crescent_2.png'),
|
|
435
|
+
'moon_waxing_crescent_3_path': get_icon_path('waxing_crescent_3.png'),
|
|
436
|
+
'moon_waxing_crescent_4_path': get_icon_path('waxing_crescent_4.png'),
|
|
437
|
+
'moon_waxing_crescent_5_path': get_icon_path('waxing_crescent_5.png'),
|
|
438
|
+
|
|
439
|
+
'moon_first_quarter_path': get_icon_path('first_quarter.png'),
|
|
440
|
+
|
|
441
|
+
'moon_waxing_gibbous_1_path': get_icon_path('waxing_gibbous_1.png'),
|
|
442
|
+
'moon_waxing_gibbous_2_path': get_icon_path('waxing_gibbous_2.png'),
|
|
443
|
+
'moon_waxing_gibbous_3_path': get_icon_path('waxing_gibbous_3.png'),
|
|
444
|
+
'moon_waxing_gibbous_4_path': get_icon_path('waxing_gibbous_4.png'),
|
|
445
|
+
'moon_waxing_gibbous_5_path': get_icon_path('waxing_gibbous_5.png'),
|
|
446
|
+
|
|
447
|
+
'moon_full_path': get_icon_path('full_moon.png'),
|
|
448
|
+
|
|
449
|
+
'moon_waning_gibbous_1_path': get_icon_path('waning_gibbous_1.png'),
|
|
450
|
+
'moon_waning_gibbous_2_path': get_icon_path('waning_gibbous_2.png'),
|
|
451
|
+
'moon_waning_gibbous_3_path': get_icon_path('waning_gibbous_3.png'),
|
|
452
|
+
'moon_waning_gibbous_4_path': get_icon_path('waning_gibbous_4.png'),
|
|
453
|
+
'moon_waning_gibbous_5_path': get_icon_path('waning_gibbous_5.png'),
|
|
454
|
+
|
|
455
|
+
'moon_last_quarter_path': get_icon_path('last_quarter.png'),
|
|
456
|
+
|
|
457
|
+
'moon_waning_crescent_1_path': get_icon_path('waning_crescent_1.png'),
|
|
458
|
+
'moon_waning_crescent_2_path': get_icon_path('waning_crescent_2.png'),
|
|
459
|
+
'moon_waning_crescent_3_path': get_icon_path('waning_crescent_3.png'),
|
|
460
|
+
'moon_waning_crescent_4_path': get_icon_path('waning_crescent_4.png'),
|
|
461
|
+
'moon_waning_crescent_5_path': get_icon_path('waning_crescent_5.png'),
|
|
462
|
+
|
|
396
463
|
'rgbcombo_path': get_icon_path('rgbcombo.png'),
|
|
397
464
|
'rgbextract_path': get_icon_path('rgbextract.png'),
|
|
398
465
|
'copyslot_path': get_icon_path('copyslot.png'),
|
setiastro/saspro/rgbalign.py
CHANGED
|
@@ -350,6 +350,10 @@ class RGBAlignDialog(QDialog):
|
|
|
350
350
|
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
351
351
|
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
352
352
|
self.setModal(False)
|
|
353
|
+
try:
|
|
354
|
+
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
|
|
355
|
+
except Exception:
|
|
356
|
+
pass # older PyQt6 versions
|
|
353
357
|
self.parent = parent
|
|
354
358
|
# document could be a view; try to unwrap
|
|
355
359
|
self.doc_view = document
|
|
@@ -458,7 +458,10 @@ class SelectiveColorCorrection(QDialog):
|
|
|
458
458
|
self.setWindowTitle(self.tr("Selective Color Correction"))
|
|
459
459
|
if window_icon:
|
|
460
460
|
self.setWindowIcon(window_icon)
|
|
461
|
-
|
|
461
|
+
try:
|
|
462
|
+
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
|
|
463
|
+
except Exception:
|
|
464
|
+
pass # older PyQt6 versions
|
|
462
465
|
self.docman = doc_manager
|
|
463
466
|
self.document = document
|
|
464
467
|
if self.document is None or getattr(self.document, "image", None) is None:
|
setiastro/saspro/sfcc.py
CHANGED
|
@@ -349,6 +349,10 @@ class SFCCDialog(QDialog):
|
|
|
349
349
|
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
350
350
|
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
351
351
|
self.setModal(False)
|
|
352
|
+
try:
|
|
353
|
+
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
|
|
354
|
+
except Exception:
|
|
355
|
+
pass # older PyQt6 versions
|
|
352
356
|
self.setMinimumSize(800, 600)
|
|
353
357
|
|
|
354
358
|
self.doc_manager = doc_manager
|
|
@@ -375,6 +379,7 @@ class SFCCDialog(QDialog):
|
|
|
375
379
|
self.lp_filter_combo2.currentIndexChanged.connect(self.save_lp2_setting)
|
|
376
380
|
self.sens_combo.currentIndexChanged.connect(self.save_sensor_setting)
|
|
377
381
|
self.star_combo.currentIndexChanged.connect(self.save_star_setting)
|
|
382
|
+
self.finished.connect(lambda *_: self._cleanup())
|
|
378
383
|
|
|
379
384
|
self.grad_method = "poly3"
|
|
380
385
|
self.grad_method_combo.currentTextChanged.connect(lambda m: setattr(self, "grad_method", m))
|
|
@@ -528,12 +533,12 @@ class SFCCDialog(QDialog):
|
|
|
528
533
|
self.remove_curve_btn = QPushButton(self.tr("Remove Filter/Sensor Curve…"))
|
|
529
534
|
self.remove_curve_btn.clicked.connect(self.remove_custom_curve); row4.addWidget(self.remove_curve_btn)
|
|
530
535
|
row4.addStretch()
|
|
531
|
-
self.close_btn = QPushButton(self.tr("Close")); self.close_btn.clicked.connect(self.
|
|
536
|
+
self.close_btn = QPushButton(self.tr("Close")); self.close_btn.clicked.connect(self.reject); row4.addWidget(self.close_btn)
|
|
532
537
|
|
|
533
538
|
self.count_label = QLabel(""); layout.addWidget(self.count_label)
|
|
534
539
|
|
|
535
540
|
self.figure = Figure(figsize=(6, 4)); self.canvas = FigureCanvas(self.figure); self.canvas.setVisible(False); layout.addWidget(self.canvas, stretch=1)
|
|
536
|
-
self.reset_btn = QPushButton(self.tr("Reset View/Close")); self.reset_btn.clicked.connect(self.
|
|
541
|
+
self.reset_btn = QPushButton(self.tr("Reset View/Close")); self.reset_btn.clicked.connect(self.reject); layout.addWidget(self.reset_btn)
|
|
537
542
|
|
|
538
543
|
# hide gradient controls by default (enable if you like)
|
|
539
544
|
self.run_grad_btn.hide(); self.grad_method_combo.hide()
|
|
@@ -1454,11 +1459,64 @@ class SFCCDialog(QDialog):
|
|
|
1454
1459
|
self.sasp_viewer_window.show()
|
|
1455
1460
|
self.sasp_viewer_window.destroyed.connect(self._on_sasp_closed)
|
|
1456
1461
|
|
|
1462
|
+
def _cleanup(self):
|
|
1463
|
+
# 1) Close/cleanup child window (SaspViewer)
|
|
1464
|
+
try:
|
|
1465
|
+
if getattr(self, "sasp_viewer_window", None) is not None:
|
|
1466
|
+
try:
|
|
1467
|
+
self.sasp_viewer_window.destroyed.disconnect(self._on_sasp_closed)
|
|
1468
|
+
except Exception:
|
|
1469
|
+
pass
|
|
1470
|
+
try:
|
|
1471
|
+
self.sasp_viewer_window.close()
|
|
1472
|
+
except Exception:
|
|
1473
|
+
pass
|
|
1474
|
+
self.sasp_viewer_window = None
|
|
1475
|
+
except Exception:
|
|
1476
|
+
pass
|
|
1477
|
+
|
|
1478
|
+
# 2) Disconnect any long-lived external signals (add these if/when used)
|
|
1479
|
+
# Example patterns:
|
|
1480
|
+
try:
|
|
1481
|
+
self.doc_manager.activeDocumentChanged.disconnect(self._on_active_doc_changed)
|
|
1482
|
+
except Exception:
|
|
1483
|
+
pass
|
|
1484
|
+
try:
|
|
1485
|
+
self.main_win.currentDocumentChanged.disconnect(self._on_active_doc_changed)
|
|
1486
|
+
except Exception:
|
|
1487
|
+
pass
|
|
1488
|
+
|
|
1489
|
+
# 3) Release large caches/refs (important since dialog may not be deleted)
|
|
1490
|
+
try:
|
|
1491
|
+
self.current_image = None
|
|
1492
|
+
self.current_header = None
|
|
1493
|
+
self.star_list = []
|
|
1494
|
+
self._last_matched = []
|
|
1495
|
+
if hasattr(self, "wcs"):
|
|
1496
|
+
self.wcs = None
|
|
1497
|
+
if hasattr(self, "wcs_header"):
|
|
1498
|
+
self.wcs_header = None
|
|
1499
|
+
except Exception:
|
|
1500
|
+
pass
|
|
1501
|
+
|
|
1502
|
+
# 4) Matplotlib cleanup
|
|
1503
|
+
try:
|
|
1504
|
+
if getattr(self, "figure", None) is not None:
|
|
1505
|
+
self.figure.clf()
|
|
1506
|
+
if getattr(self, "canvas", None) is not None:
|
|
1507
|
+
self.canvas.setVisible(False)
|
|
1508
|
+
self.canvas.draw_idle()
|
|
1509
|
+
except Exception:
|
|
1510
|
+
pass
|
|
1511
|
+
|
|
1512
|
+
|
|
1457
1513
|
def _on_sasp_closed(self, _=None):
|
|
1458
1514
|
# Called when the SaspViewer window is destroyed
|
|
1459
1515
|
self.sasp_viewer_window = None
|
|
1516
|
+
self._cleanup()
|
|
1460
1517
|
|
|
1461
1518
|
def closeEvent(self, event):
|
|
1519
|
+
self._cleanup()
|
|
1462
1520
|
super().closeEvent(event)
|
|
1463
1521
|
|
|
1464
1522
|
|
setiastro/saspro/shortcuts.py
CHANGED
|
@@ -493,7 +493,8 @@ class DraggableToolBar(QToolBar):
|
|
|
493
493
|
cid = self._action_id(act)
|
|
494
494
|
if cid:
|
|
495
495
|
m.addSeparator()
|
|
496
|
-
m.addAction(self.tr("Hide this icon"), lambda: self.
|
|
496
|
+
m.addAction(self.tr("Hide this icon"), lambda: self.window()._hide_action_to_hidden_toolbar(act))
|
|
497
|
+
|
|
497
498
|
|
|
498
499
|
# (Optional) teach users about Alt+Drag:
|
|
499
500
|
m.addSeparator()
|
|
@@ -521,16 +522,19 @@ class DraggableToolBar(QToolBar):
|
|
|
521
522
|
m.addSeparator()
|
|
522
523
|
|
|
523
524
|
# Submenu listing hidden actions for this toolbar
|
|
524
|
-
hidden = self._load_hidden_set()
|
|
525
525
|
sub = m.addMenu(self.tr("Show hidden…"))
|
|
526
526
|
|
|
527
|
-
|
|
527
|
+
mw = self.window()
|
|
528
|
+
tb_hidden = getattr(mw, "_hidden_toolbar", lambda: None)()
|
|
528
529
|
any_hidden = False
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
530
|
+
if tb_hidden:
|
|
531
|
+
for act in tb_hidden.actions():
|
|
532
|
+
# Skip separators
|
|
533
|
+
if act.isSeparator():
|
|
534
|
+
continue
|
|
532
535
|
any_hidden = True
|
|
533
|
-
sub.addAction(act.text() or
|
|
536
|
+
sub.addAction(act.text() or (act.property("command_id") or act.objectName() or "item"),
|
|
537
|
+
lambda a=act: mw._unhide_action_from_hidden_toolbar(a))
|
|
534
538
|
|
|
535
539
|
if not any_hidden:
|
|
536
540
|
sub.setEnabled(False)
|
|
@@ -541,10 +545,15 @@ class DraggableToolBar(QToolBar):
|
|
|
541
545
|
m.exec(ev.globalPos())
|
|
542
546
|
|
|
543
547
|
def _reset_hidden_icons(self):
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
+
mw = self.window()
|
|
549
|
+
tb_hidden = getattr(mw, "_hidden_toolbar", lambda: None)()
|
|
550
|
+
if not tb_hidden:
|
|
551
|
+
return
|
|
552
|
+
# copy list because we'll mutate
|
|
553
|
+
acts = [a for a in tb_hidden.actions() if not a.isSeparator()]
|
|
554
|
+
for a in acts:
|
|
555
|
+
mw._unhide_action_from_hidden_toolbar(a)
|
|
556
|
+
|
|
548
557
|
|
|
549
558
|
|
|
550
559
|
_PRESET_UI_IDS = {
|
|
@@ -1910,50 +1919,160 @@ class ShortcutManager:
|
|
|
1910
1919
|
# legacy single-remove (kept for callers)
|
|
1911
1920
|
self.delete_by_id(sid, persist=True)
|
|
1912
1921
|
|
|
1913
|
-
|
|
1914
1922
|
class _StatStretchPresetDialog(QDialog):
|
|
1923
|
+
"""
|
|
1924
|
+
Preset editor for headless replay: command_id="stat_stretch"
|
|
1925
|
+
|
|
1926
|
+
Keys supported:
|
|
1927
|
+
target_median: float
|
|
1928
|
+
linked: bool
|
|
1929
|
+
normalize: bool
|
|
1930
|
+
apply_curves: bool
|
|
1931
|
+
curves_boost: float # 0..1
|
|
1932
|
+
blackpoint_sigma: float # 0..1 (matches your slider/100)
|
|
1933
|
+
no_black_clip: bool
|
|
1934
|
+
hdr_compress: bool
|
|
1935
|
+
hdr_amount: float # 0..1
|
|
1936
|
+
hdr_knee: float # 0..1
|
|
1937
|
+
luma_only: bool
|
|
1938
|
+
luma_mode: str # e.g. "rec709"
|
|
1939
|
+
"""
|
|
1915
1940
|
def __init__(self, parent=None, initial: dict | None = None):
|
|
1916
1941
|
super().__init__(parent)
|
|
1917
1942
|
self.setWindowTitle("Statistical Stretch — Preset")
|
|
1918
1943
|
init = dict(initial or {})
|
|
1919
1944
|
|
|
1945
|
+
# --- Target median ---
|
|
1920
1946
|
self.spin_target = QDoubleSpinBox()
|
|
1921
|
-
self.spin_target.setRange(0.0, 1.0)
|
|
1947
|
+
self.spin_target.setRange(0.0, 1.0)
|
|
1948
|
+
self.spin_target.setDecimals(3)
|
|
1922
1949
|
self.spin_target.setSingleStep(0.01)
|
|
1923
1950
|
self.spin_target.setValue(float(init.get("target_median", 0.25)))
|
|
1924
1951
|
|
|
1952
|
+
# --- Linked / Normalize ---
|
|
1925
1953
|
self.chk_linked = QCheckBox("Linked RGB channels")
|
|
1926
1954
|
self.chk_linked.setChecked(bool(init.get("linked", False)))
|
|
1927
1955
|
|
|
1928
1956
|
self.chk_normalize = QCheckBox("Normalize to [0..1]")
|
|
1929
1957
|
self.chk_normalize.setChecked(bool(init.get("normalize", False)))
|
|
1930
1958
|
|
|
1959
|
+
# --- Curves ---
|
|
1960
|
+
self.chk_curves = QCheckBox("Apply Curves")
|
|
1961
|
+
self.chk_curves.setChecked(bool(init.get("apply_curves", False)))
|
|
1962
|
+
|
|
1931
1963
|
self.spin_curves = QDoubleSpinBox()
|
|
1932
|
-
self.spin_curves.setRange(0.0, 1.0)
|
|
1964
|
+
self.spin_curves.setRange(0.0, 1.0)
|
|
1965
|
+
self.spin_curves.setDecimals(2)
|
|
1933
1966
|
self.spin_curves.setSingleStep(0.05)
|
|
1934
|
-
self.spin_curves.setValue(float(init.get("curves_boost", 0.0
|
|
1967
|
+
self.spin_curves.setValue(float(init.get("curves_boost", 0.0)))
|
|
1968
|
+
self.spin_curves.setEnabled(self.chk_curves.isChecked())
|
|
1969
|
+
self.chk_curves.toggled.connect(self.spin_curves.setEnabled)
|
|
1970
|
+
|
|
1971
|
+
# --- Blackpoint sigma ---
|
|
1972
|
+
self.spin_bp = QDoubleSpinBox()
|
|
1973
|
+
self.spin_bp.setRange(0.0, 1.0)
|
|
1974
|
+
self.spin_bp.setDecimals(2)
|
|
1975
|
+
self.spin_bp.setSingleStep(0.05)
|
|
1976
|
+
self.spin_bp.setValue(float(init.get("blackpoint_sigma", 0.0)))
|
|
1977
|
+
|
|
1978
|
+
self.chk_no_black_clip = QCheckBox("No black clip")
|
|
1979
|
+
self.chk_no_black_clip.setChecked(bool(init.get("no_black_clip", False)))
|
|
1980
|
+
|
|
1981
|
+
# --- HDR compress ---
|
|
1982
|
+
self.chk_hdr = QCheckBox("HDR compression")
|
|
1983
|
+
self.chk_hdr.setChecked(bool(init.get("hdr_compress", False)))
|
|
1984
|
+
|
|
1985
|
+
self.spin_hdr_amt = QDoubleSpinBox()
|
|
1986
|
+
self.spin_hdr_amt.setRange(0.0, 1.0)
|
|
1987
|
+
self.spin_hdr_amt.setDecimals(2)
|
|
1988
|
+
self.spin_hdr_amt.setSingleStep(0.05)
|
|
1989
|
+
self.spin_hdr_amt.setValue(float(init.get("hdr_amount", 0.0)))
|
|
1990
|
+
|
|
1991
|
+
self.spin_hdr_knee = QDoubleSpinBox()
|
|
1992
|
+
self.spin_hdr_knee.setRange(0.0, 1.0)
|
|
1993
|
+
self.spin_hdr_knee.setDecimals(2)
|
|
1994
|
+
self.spin_hdr_knee.setSingleStep(0.05)
|
|
1995
|
+
self.spin_hdr_knee.setValue(float(init.get("hdr_knee", 0.5)))
|
|
1996
|
+
|
|
1997
|
+
def _set_hdr_enabled(on: bool):
|
|
1998
|
+
on = bool(on)
|
|
1999
|
+
self.spin_hdr_amt.setEnabled(on)
|
|
2000
|
+
self.spin_hdr_knee.setEnabled(on)
|
|
2001
|
+
|
|
2002
|
+
_set_hdr_enabled(self.chk_hdr.isChecked())
|
|
2003
|
+
self.chk_hdr.toggled.connect(_set_hdr_enabled)
|
|
2004
|
+
|
|
2005
|
+
# --- Luma only ---
|
|
2006
|
+
self.chk_luma_only = QCheckBox("Luma-only mode")
|
|
2007
|
+
self.chk_luma_only.setChecked(bool(init.get("luma_only", False)))
|
|
2008
|
+
|
|
2009
|
+
self.cmb_luma = QComboBox()
|
|
2010
|
+
# keep in sync with your tool’s supported modes
|
|
2011
|
+
self.cmb_luma.addItems(["rec709", "avg", "hsp", "max"])
|
|
2012
|
+
init_mode = str(init.get("luma_mode", "rec709") or "rec709")
|
|
2013
|
+
idx = self.cmb_luma.findText(init_mode)
|
|
2014
|
+
if idx >= 0:
|
|
2015
|
+
self.cmb_luma.setCurrentIndex(idx)
|
|
2016
|
+
|
|
2017
|
+
self.cmb_luma.setEnabled(self.chk_luma_only.isChecked())
|
|
2018
|
+
self.chk_luma_only.toggled.connect(self.cmb_luma.setEnabled)
|
|
2019
|
+
|
|
2020
|
+
# --- Layout ---
|
|
2021
|
+
form = QFormLayout()
|
|
1935
2022
|
|
|
1936
|
-
form = QFormLayout(self)
|
|
1937
2023
|
form.addRow("Target median:", self.spin_target)
|
|
1938
2024
|
form.addRow("", self.chk_linked)
|
|
1939
2025
|
form.addRow("", self.chk_normalize)
|
|
2026
|
+
|
|
2027
|
+
form.addRow("", QLabel("— Tone shaping —"))
|
|
2028
|
+
form.addRow("", self.chk_curves)
|
|
1940
2029
|
form.addRow("Curves boost (0–1):", self.spin_curves)
|
|
1941
|
-
form.addRow(QLabel("Curves are applied only if boost > 0."))
|
|
1942
2030
|
|
|
1943
|
-
|
|
1944
|
-
|
|
2031
|
+
form.addRow("", QLabel("— Blackpoint / HDR —"))
|
|
2032
|
+
form.addRow("Blackpoint σ (0–1):", self.spin_bp)
|
|
2033
|
+
form.addRow("", self.chk_no_black_clip)
|
|
2034
|
+
|
|
2035
|
+
form.addRow("", self.chk_hdr)
|
|
2036
|
+
form.addRow("HDR amount (0–1):", self.spin_hdr_amt)
|
|
2037
|
+
form.addRow("HDR knee (0–1):", self.spin_hdr_knee)
|
|
2038
|
+
|
|
2039
|
+
form.addRow("", QLabel("— Luma mode —"))
|
|
2040
|
+
form.addRow("", self.chk_luma_only)
|
|
2041
|
+
form.addRow("Luma mode:", self.cmb_luma)
|
|
2042
|
+
|
|
2043
|
+
btns = QDialogButtonBox(
|
|
2044
|
+
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel,
|
|
2045
|
+
parent=self
|
|
2046
|
+
)
|
|
2047
|
+
btns.accepted.connect(self.accept)
|
|
2048
|
+
btns.rejected.connect(self.reject)
|
|
1945
2049
|
form.addRow(btns)
|
|
1946
2050
|
|
|
2051
|
+
self.setLayout(form)
|
|
2052
|
+
|
|
1947
2053
|
def result_dict(self) -> dict:
|
|
1948
|
-
|
|
2054
|
+
hdr_on = bool(self.chk_hdr.isChecked())
|
|
2055
|
+
curves_on = bool(self.chk_curves.isChecked())
|
|
2056
|
+
luma_on = bool(self.chk_luma_only.isChecked())
|
|
2057
|
+
|
|
1949
2058
|
return {
|
|
1950
2059
|
"target_median": float(self.spin_target.value()),
|
|
1951
2060
|
"linked": bool(self.chk_linked.isChecked()),
|
|
1952
2061
|
"normalize": bool(self.chk_normalize.isChecked()),
|
|
1953
|
-
|
|
1954
|
-
"
|
|
2062
|
+
|
|
2063
|
+
"apply_curves": curves_on,
|
|
2064
|
+
"curves_boost": float(self.spin_curves.value()) if curves_on else 0.0,
|
|
2065
|
+
|
|
2066
|
+
"blackpoint_sigma": float(self.spin_bp.value()),
|
|
2067
|
+
"no_black_clip": bool(self.chk_no_black_clip.isChecked()),
|
|
2068
|
+
|
|
2069
|
+
"hdr_compress": hdr_on,
|
|
2070
|
+
"hdr_amount": float(self.spin_hdr_amt.value()) if hdr_on else 0.0,
|
|
2071
|
+
"hdr_knee": float(self.spin_hdr_knee.value()) if hdr_on else 0.0,
|
|
2072
|
+
|
|
2073
|
+
"luma_only": luma_on,
|
|
2074
|
+
"luma_mode": str(self.cmb_luma.currentText()) if luma_on else "rec709",
|
|
1955
2075
|
}
|
|
1956
|
-
|
|
1957
2076
|
|
|
1958
2077
|
class _StarStretchPresetDialog(QDialog):
|
|
1959
2078
|
def __init__(self, parent=None, initial: dict | None = None):
|