setiastrosuitepro 1.6.12__py3-none-any.whl → 1.7.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.
- setiastro/images/3dplanet.png +0 -0
- setiastro/images/TextureClarity.svg +56 -0
- setiastro/images/narrowbandnormalization.png +0 -0
- setiastro/images/planetarystacker.png +0 -0
- setiastro/saspro/__init__.py +9 -8
- setiastro/saspro/__main__.py +326 -285
- setiastro/saspro/_generated/build_info.py +2 -2
- setiastro/saspro/aberration_ai.py +128 -13
- setiastro/saspro/aberration_ai_preset.py +29 -3
- setiastro/saspro/astrospike_python.py +45 -3
- setiastro/saspro/blink_comparator_pro.py +116 -71
- setiastro/saspro/curve_editor_pro.py +72 -22
- setiastro/saspro/curves_preset.py +249 -47
- setiastro/saspro/doc_manager.py +4 -1
- setiastro/saspro/gui/main_window.py +326 -46
- setiastro/saspro/gui/mixins/file_mixin.py +41 -18
- setiastro/saspro/gui/mixins/menu_mixin.py +9 -0
- setiastro/saspro/gui/mixins/toolbar_mixin.py +123 -7
- setiastro/saspro/histogram.py +179 -7
- setiastro/saspro/imageops/narrowband_normalization.py +816 -0
- setiastro/saspro/imageops/serloader.py +1429 -0
- setiastro/saspro/layers.py +186 -10
- setiastro/saspro/layers_dock.py +198 -5
- setiastro/saspro/legacy/image_manager.py +10 -4
- setiastro/saspro/legacy/numba_utils.py +1 -1
- setiastro/saspro/live_stacking.py +24 -4
- setiastro/saspro/multiscale_decomp.py +30 -17
- setiastro/saspro/narrowband_normalization.py +1618 -0
- setiastro/saspro/planetprojection.py +3854 -0
- setiastro/saspro/remove_green.py +1 -1
- setiastro/saspro/resources.py +8 -0
- setiastro/saspro/rgbalign.py +456 -12
- setiastro/saspro/save_options.py +45 -13
- setiastro/saspro/ser_stack_config.py +102 -0
- setiastro/saspro/ser_stacker.py +2327 -0
- setiastro/saspro/ser_stacker_dialog.py +1865 -0
- setiastro/saspro/ser_tracking.py +228 -0
- setiastro/saspro/serviewer.py +1773 -0
- setiastro/saspro/sfcc.py +298 -64
- setiastro/saspro/shortcuts.py +14 -7
- setiastro/saspro/stacking_suite.py +21 -6
- setiastro/saspro/stat_stretch.py +179 -31
- setiastro/saspro/subwindow.py +38 -5
- setiastro/saspro/texture_clarity.py +593 -0
- setiastro/saspro/widgets/resource_monitor.py +122 -74
- {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/METADATA +3 -2
- {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/RECORD +51 -37
- {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/licenses/license.txt +0 -0
setiastro/saspro/save_options.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# pro/save_options.py
|
|
2
2
|
from __future__ import annotations
|
|
3
|
-
from PyQt6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QPushButton
|
|
3
|
+
from PyQt6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QPushButton, QSpinBox, QWidget
|
|
4
4
|
from PyQt6.QtCore import Qt
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
from setiastro.saspro.file_utils import _normalize_ext
|
|
7
8
|
|
|
8
9
|
# Allowed bit depths per output format (what your saver actually supports)
|
|
@@ -16,22 +17,28 @@ _BIT_DEPTHS = {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
class SaveOptionsDialog(QDialog):
|
|
19
|
-
def __init__(
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
parent,
|
|
23
|
+
target_ext: str,
|
|
24
|
+
current_bit_depth: str | None,
|
|
25
|
+
current_jpeg_quality: int | None = None,
|
|
26
|
+
):
|
|
20
27
|
super().__init__(parent)
|
|
21
28
|
self.setWindowTitle(self.tr("Save Options"))
|
|
22
29
|
self.setWindowFlag(Qt.WindowType.Window, True)
|
|
23
30
|
self.setWindowModality(Qt.WindowModality.NonModal)
|
|
24
31
|
self.setModal(False)
|
|
25
|
-
#self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
raw_ext = (target_ext or "").lower().strip()
|
|
33
|
+
self.jpeg_quality_spin = None
|
|
29
34
|
|
|
30
|
-
#
|
|
35
|
+
# -----------------------------
|
|
36
|
+
# Normalize extension FIRST
|
|
37
|
+
# -----------------------------
|
|
38
|
+
raw_ext = (target_ext or "").lower().strip()
|
|
31
39
|
if "." in raw_ext:
|
|
32
40
|
raw_ext = raw_ext.split(".")[-1]
|
|
33
41
|
|
|
34
|
-
# Handle common synonyms / compressed variants
|
|
35
42
|
if raw_ext in ("fit", "fits", "fz", "fits.gz", "fit.gz"):
|
|
36
43
|
self._ext = "fits"
|
|
37
44
|
elif raw_ext in ("tif", "tiff"):
|
|
@@ -39,19 +46,42 @@ class SaveOptionsDialog(QDialog):
|
|
|
39
46
|
elif raw_ext in ("jpg", "jpeg"):
|
|
40
47
|
self._ext = "jpg"
|
|
41
48
|
else:
|
|
42
|
-
# Fallback – already lowercase, no leading dot
|
|
43
49
|
self._ext = raw_ext
|
|
44
50
|
|
|
45
51
|
allowed = _BIT_DEPTHS.get(self._ext, ["32-bit floating point"])
|
|
46
52
|
|
|
53
|
+
# -----------------------------
|
|
54
|
+
# Build layout
|
|
55
|
+
# -----------------------------
|
|
56
|
+
lay = QVBoxLayout(self)
|
|
57
|
+
|
|
58
|
+
lbl = QLabel(self.tr("Choose bit depth for export:"))
|
|
59
|
+
lbl.setWordWrap(True)
|
|
60
|
+
lay.addWidget(lbl)
|
|
61
|
+
|
|
47
62
|
self.combo = QComboBox(self)
|
|
48
63
|
self.combo.addItems(allowed)
|
|
49
64
|
if current_bit_depth in allowed:
|
|
50
65
|
self.combo.setCurrentText(current_bit_depth)
|
|
66
|
+
lay.addWidget(self.combo)
|
|
51
67
|
|
|
52
|
-
|
|
53
|
-
|
|
68
|
+
# -----------------------------
|
|
69
|
+
# JPEG quality (only for jpg)
|
|
70
|
+
# -----------------------------
|
|
71
|
+
if self._ext == "jpg":
|
|
72
|
+
qlbl = QLabel(self.tr("JPEG quality (1–100):"))
|
|
73
|
+
qlbl.setWordWrap(True)
|
|
74
|
+
lay.addWidget(qlbl)
|
|
75
|
+
|
|
76
|
+
self.jpeg_quality_spin = QSpinBox(self)
|
|
77
|
+
self.jpeg_quality_spin.setRange(1, 100)
|
|
78
|
+
default_q = int(current_jpeg_quality) if current_jpeg_quality is not None else 95
|
|
79
|
+
self.jpeg_quality_spin.setValue(max(1, min(100, default_q)))
|
|
80
|
+
lay.addWidget(self.jpeg_quality_spin)
|
|
54
81
|
|
|
82
|
+
# -----------------------------
|
|
83
|
+
# Buttons
|
|
84
|
+
# -----------------------------
|
|
55
85
|
btn_ok = QPushButton(self.tr("OK"))
|
|
56
86
|
btn_cancel = QPushButton(self.tr("Cancel"))
|
|
57
87
|
btn_ok.clicked.connect(self.accept)
|
|
@@ -62,12 +92,14 @@ class SaveOptionsDialog(QDialog):
|
|
|
62
92
|
row.addWidget(btn_ok)
|
|
63
93
|
row.addWidget(btn_cancel)
|
|
64
94
|
|
|
65
|
-
lay = QVBoxLayout(self)
|
|
66
|
-
lay.addWidget(lbl)
|
|
67
|
-
lay.addWidget(self.combo)
|
|
68
95
|
lay.addStretch(1)
|
|
69
96
|
lay.addLayout(row)
|
|
70
97
|
|
|
98
|
+
|
|
71
99
|
def selected_bit_depth(self) -> str:
|
|
72
100
|
return self.combo.currentText()
|
|
73
101
|
|
|
102
|
+
def selected_jpeg_quality(self) -> int | None:
|
|
103
|
+
if self.jpeg_quality_spin is None:
|
|
104
|
+
return None
|
|
105
|
+
return int(self.jpeg_quality_spin.value())
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# src/setiastro/saspro/ser_stack_config.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Optional, Tuple, Literal, Union, Sequence, TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
import numpy as np
|
|
8
|
+
KeepMask = "np.ndarray"
|
|
9
|
+
else:
|
|
10
|
+
KeepMask = object
|
|
11
|
+
|
|
12
|
+
from setiastro.saspro.imageops.serloader import PlanetaryFrameSource
|
|
13
|
+
|
|
14
|
+
TrackMode = Literal["off", "planetary", "surface"]
|
|
15
|
+
PlanetarySource = Union[str, Sequence[str], PlanetaryFrameSource]
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class SERStackConfig:
|
|
19
|
+
source: PlanetarySource
|
|
20
|
+
roi: Optional[Tuple[int, int, int, int]] = None
|
|
21
|
+
track_mode: TrackMode = "planetary"
|
|
22
|
+
surface_anchor: Optional[Tuple[int, int, int, int]] = None
|
|
23
|
+
keep_percent: float = 20.0
|
|
24
|
+
bayer_pattern: Optional[str] = None
|
|
25
|
+
|
|
26
|
+
# AP / alignment
|
|
27
|
+
ap_size: int = 64
|
|
28
|
+
ap_spacing: int = 48
|
|
29
|
+
ap_min_mean: float = 0.03
|
|
30
|
+
ap_multiscale: bool = False
|
|
31
|
+
ssd_refine_bruteforce: bool = False
|
|
32
|
+
keep_mask: Optional[KeepMask] = None
|
|
33
|
+
planet_smooth_sigma: float = 1.5
|
|
34
|
+
planet_thresh_pct: float = 92.0
|
|
35
|
+
planet_use_norm: bool = True
|
|
36
|
+
planet_norm_lo_pct: float = 1.0
|
|
37
|
+
planet_norm_hi_pct: float = 99.5
|
|
38
|
+
planet_min_val: float = 0.02
|
|
39
|
+
# ✅ Drizzle
|
|
40
|
+
drizzle_scale: float = 1.0 # 1.0 = off, 1.5, 2.0
|
|
41
|
+
drizzle_pixfrac: float = 0.80 # "drop shrink" in output pixels (roughly)
|
|
42
|
+
drizzle_kernel: str = "gaussian" # "square" | "circle" | "gaussian"
|
|
43
|
+
drizzle_sigma: float = 0.0 # only used for gaussian; 0 => auto from pixfrac
|
|
44
|
+
|
|
45
|
+
def __init__(self, source: PlanetarySource, **kwargs):
|
|
46
|
+
# Allow deprecated/ignored kwargs without crashing
|
|
47
|
+
kwargs.pop("multipoint", None) # accept but ignore
|
|
48
|
+
|
|
49
|
+
self.source = source
|
|
50
|
+
self.roi = kwargs.pop("roi", None)
|
|
51
|
+
self.track_mode = kwargs.pop("track_mode", "planetary")
|
|
52
|
+
self.surface_anchor = kwargs.pop("surface_anchor", None)
|
|
53
|
+
self.keep_percent = float(kwargs.pop("keep_percent", 20.0))
|
|
54
|
+
self.bayer_pattern = kwargs.pop("bayer_pattern", None)
|
|
55
|
+
if isinstance(self.bayer_pattern, str):
|
|
56
|
+
s = self.bayer_pattern.strip().upper()
|
|
57
|
+
self.bayer_pattern = s if s in ("RGGB", "BGGR", "GRBG", "GBRG") else None
|
|
58
|
+
else:
|
|
59
|
+
self.bayer_pattern = None
|
|
60
|
+
self.ap_size = int(kwargs.pop("ap_size", 64))
|
|
61
|
+
self.ap_spacing = int(kwargs.pop("ap_spacing", 48))
|
|
62
|
+
self.ap_min_mean = float(kwargs.pop("ap_min_mean", 0.03))
|
|
63
|
+
self.ap_multiscale = bool(kwargs.pop("ap_multiscale", False))
|
|
64
|
+
self.ssd_refine_bruteforce = bool(kwargs.pop("ssd_refine_bruteforce", False))
|
|
65
|
+
self.keep_mask = kwargs.pop("keep_mask", None)
|
|
66
|
+
# Planetary centroid knobs (pure data, no UI references)
|
|
67
|
+
self.planet_smooth_sigma = float(kwargs.pop("planet_smooth_sigma", 1.5))
|
|
68
|
+
self.planet_thresh_pct = float(kwargs.pop("planet_thresh_pct", 92.0))
|
|
69
|
+
self.planet_min_val = float(kwargs.pop("planet_min_val", 0.02))
|
|
70
|
+
self.planet_use_norm = bool(kwargs.pop("planet_use_norm", True))
|
|
71
|
+
self.planet_norm_lo_pct = float(kwargs.pop("planet_norm_lo_pct", 1.0))
|
|
72
|
+
self.planet_norm_hi_pct = float(kwargs.pop("planet_norm_hi_pct", 99.5))
|
|
73
|
+
|
|
74
|
+
# sanitize
|
|
75
|
+
self.planet_smooth_sigma = max(0.0, self.planet_smooth_sigma)
|
|
76
|
+
self.planet_thresh_pct = float(np.clip(self.planet_thresh_pct, 0.0, 100.0)) if "np" in globals() else self.planet_thresh_pct
|
|
77
|
+
self.planet_min_val = float(max(0.0, min(1.0, self.planet_min_val)))
|
|
78
|
+
self.planet_norm_lo_pct = float(np.clip(self.planet_norm_lo_pct, 0.0, 100.0)) if "np" in globals() else self.planet_norm_lo_pct
|
|
79
|
+
self.planet_norm_hi_pct = float(np.clip(self.planet_norm_hi_pct, 0.0, 100.0)) if "np" in globals() else self.planet_norm_hi_pct
|
|
80
|
+
if self.planet_norm_hi_pct <= self.planet_norm_lo_pct:
|
|
81
|
+
self.planet_norm_hi_pct = min(100.0, self.planet_norm_lo_pct + 1.0)
|
|
82
|
+
# ✅ NEW: Drizzle params
|
|
83
|
+
self.drizzle_scale = float(kwargs.pop("drizzle_scale", 1.0))
|
|
84
|
+
if self.drizzle_scale not in (1.0, 1.5, 2.0):
|
|
85
|
+
self.drizzle_scale = 1.0
|
|
86
|
+
|
|
87
|
+
self.drizzle_pixfrac = float(kwargs.pop("drizzle_pixfrac", 0.80))
|
|
88
|
+
self.drizzle_kernel = str(kwargs.pop("drizzle_kernel", "gaussian")).strip().lower()
|
|
89
|
+
self.drizzle_sigma = float(kwargs.pop("drizzle_sigma", 0.0))
|
|
90
|
+
|
|
91
|
+
# sanitize a bit
|
|
92
|
+
if self.drizzle_scale < 1.0:
|
|
93
|
+
self.drizzle_scale = 1.0
|
|
94
|
+
if self.drizzle_pixfrac <= 0.0:
|
|
95
|
+
self.drizzle_pixfrac = 0.01
|
|
96
|
+
if self.drizzle_kernel not in ("square", "circle", "gaussian"):
|
|
97
|
+
self.drizzle_kernel = "gaussian"
|
|
98
|
+
if self.drizzle_sigma < 0.0:
|
|
99
|
+
self.drizzle_sigma = 0.0
|
|
100
|
+
|
|
101
|
+
if kwargs:
|
|
102
|
+
raise TypeError(f"Unexpected config keys: {sorted(kwargs.keys())}")
|