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.
Files changed (51) hide show
  1. setiastro/images/3dplanet.png +0 -0
  2. setiastro/images/TextureClarity.svg +56 -0
  3. setiastro/images/narrowbandnormalization.png +0 -0
  4. setiastro/images/planetarystacker.png +0 -0
  5. setiastro/saspro/__init__.py +9 -8
  6. setiastro/saspro/__main__.py +326 -285
  7. setiastro/saspro/_generated/build_info.py +2 -2
  8. setiastro/saspro/aberration_ai.py +128 -13
  9. setiastro/saspro/aberration_ai_preset.py +29 -3
  10. setiastro/saspro/astrospike_python.py +45 -3
  11. setiastro/saspro/blink_comparator_pro.py +116 -71
  12. setiastro/saspro/curve_editor_pro.py +72 -22
  13. setiastro/saspro/curves_preset.py +249 -47
  14. setiastro/saspro/doc_manager.py +4 -1
  15. setiastro/saspro/gui/main_window.py +326 -46
  16. setiastro/saspro/gui/mixins/file_mixin.py +41 -18
  17. setiastro/saspro/gui/mixins/menu_mixin.py +9 -0
  18. setiastro/saspro/gui/mixins/toolbar_mixin.py +123 -7
  19. setiastro/saspro/histogram.py +179 -7
  20. setiastro/saspro/imageops/narrowband_normalization.py +816 -0
  21. setiastro/saspro/imageops/serloader.py +1429 -0
  22. setiastro/saspro/layers.py +186 -10
  23. setiastro/saspro/layers_dock.py +198 -5
  24. setiastro/saspro/legacy/image_manager.py +10 -4
  25. setiastro/saspro/legacy/numba_utils.py +1 -1
  26. setiastro/saspro/live_stacking.py +24 -4
  27. setiastro/saspro/multiscale_decomp.py +30 -17
  28. setiastro/saspro/narrowband_normalization.py +1618 -0
  29. setiastro/saspro/planetprojection.py +3854 -0
  30. setiastro/saspro/remove_green.py +1 -1
  31. setiastro/saspro/resources.py +8 -0
  32. setiastro/saspro/rgbalign.py +456 -12
  33. setiastro/saspro/save_options.py +45 -13
  34. setiastro/saspro/ser_stack_config.py +102 -0
  35. setiastro/saspro/ser_stacker.py +2327 -0
  36. setiastro/saspro/ser_stacker_dialog.py +1865 -0
  37. setiastro/saspro/ser_tracking.py +228 -0
  38. setiastro/saspro/serviewer.py +1773 -0
  39. setiastro/saspro/sfcc.py +298 -64
  40. setiastro/saspro/shortcuts.py +14 -7
  41. setiastro/saspro/stacking_suite.py +21 -6
  42. setiastro/saspro/stat_stretch.py +179 -31
  43. setiastro/saspro/subwindow.py +38 -5
  44. setiastro/saspro/texture_clarity.py +593 -0
  45. setiastro/saspro/widgets/resource_monitor.py +122 -74
  46. {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/METADATA +3 -2
  47. {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/RECORD +51 -37
  48. {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/WHEEL +0 -0
  49. {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/entry_points.txt +0 -0
  50. {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/licenses/LICENSE +0 -0
  51. {setiastrosuitepro-1.6.12.dist-info → setiastrosuitepro-1.7.3.dist-info}/licenses/license.txt +0 -0
@@ -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__(self, parent, target_ext: str, current_bit_depth: str | None):
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
- # Normalize extension aggressively so it matches _BIT_DEPTHS keys
28
- raw_ext = (target_ext or "").lower().strip()
33
+ self.jpeg_quality_spin = None
29
34
 
30
- # If it's like ".fits" or "image.fits" just keep the part after last dot
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
- lbl = QLabel(self.tr("Choose bit depth for export:"))
53
- lbl.setWordWrap(True)
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())}")