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.

Potentially problematic release.


This version of setiastrosuitepro might be problematic. Click here for more details.

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
@@ -0,0 +1,228 @@
1
+ # src/setiastro/saspro/ser_tracking.py
2
+ from __future__ import annotations
3
+ import numpy as np
4
+
5
+ try:
6
+ import cv2
7
+ except Exception:
8
+ cv2 = None
9
+
10
+
11
+ def _to_mono01(img: np.ndarray) -> np.ndarray:
12
+ """Convert frame float [0..1] mono/RGB -> mono float32 [0..1]."""
13
+ if img.ndim == 2:
14
+ m = img
15
+ else:
16
+ # simple luma; keep fast
17
+ m = 0.2126 * img[..., 0] + 0.7152 * img[..., 1] + 0.0722 * img[..., 2]
18
+ return np.asarray(m, dtype=np.float32)
19
+
20
+
21
+ class PlanetaryTracker:
22
+ def __init__(
23
+ self,
24
+ *,
25
+ smooth_sigma: float = 1.5,
26
+ thresh_pct: float = 92.0,
27
+ min_val: float = 0.02, # ✅ NEW
28
+ use_norm: bool = True,
29
+ norm_hi_pct: float = 99.5,
30
+ norm_lo_pct: float = 1.0,
31
+ ):
32
+ self.smooth_sigma = float(smooth_sigma)
33
+ self.thresh_pct = float(thresh_pct)
34
+ self.min_val = float(min_val) # ✅ store
35
+ self.use_norm = bool(use_norm)
36
+ self.norm_hi_pct = float(norm_hi_pct)
37
+ self.norm_lo_pct = float(norm_lo_pct)
38
+
39
+ def reset(self):
40
+ self._ref_center = None
41
+
42
+ def _blur(self, m: np.ndarray) -> np.ndarray:
43
+ if cv2 is None:
44
+ return m
45
+ # sigma->ksize
46
+ sigma = max(0.0, self.smooth_sigma)
47
+ if sigma <= 0:
48
+ return m
49
+ k = int(max(3, (sigma * 6) // 2 * 2 + 1))
50
+ return cv2.GaussianBlur(m, (k, k), sigmaX=sigma, sigmaY=sigma)
51
+
52
+ def _largest_component_mask(self, mask: np.ndarray) -> np.ndarray:
53
+ if cv2 is None:
54
+ return mask
55
+ # mask uint8 0/255
56
+ num, labels, stats, _ = cv2.connectedComponentsWithStats(mask, connectivity=8)
57
+ if num <= 1:
58
+ return mask
59
+ # skip background 0
60
+ areas = stats[1:, cv2.CC_STAT_AREA]
61
+ k = 1 + int(np.argmax(areas))
62
+ out = (labels == k).astype(np.uint8) * 255
63
+ return out
64
+
65
+ def _centroid(self, m: np.ndarray, mask: np.ndarray) -> tuple[float, float, float]:
66
+ if cv2 is None:
67
+ # fallback: simple average of mask pixels
68
+ ys, xs = np.nonzero(mask > 0)
69
+ if len(xs) < 10:
70
+ return (0.0, 0.0, 0.0)
71
+ return (float(xs.mean()), float(ys.mean()), 1.0)
72
+
73
+ mm = cv2.moments((mask > 0).astype(np.uint8), binaryImage=True)
74
+ if mm["m00"] <= 0:
75
+ return (0.0, 0.0, 0.0)
76
+ cx = float(mm["m10"] / mm["m00"])
77
+ cy = float(mm["m01"] / mm["m00"])
78
+ # confidence: area fraction
79
+ conf = float(np.clip(mm["m00"] / float(mask.size), 0.0, 1.0))
80
+ return (cx, cy, conf)
81
+
82
+ def _normalize_for_detect(self, m: np.ndarray) -> np.ndarray:
83
+ if not self.use_norm:
84
+ return m
85
+
86
+ lo = float(np.percentile(m, self.norm_lo_pct))
87
+ hi = float(np.percentile(m, self.norm_hi_pct))
88
+ if (not np.isfinite(lo)) or (not np.isfinite(hi)) or (hi <= lo + 1e-12):
89
+ return m
90
+
91
+ det = (m - lo) / (hi - lo)
92
+ return np.clip(det, 0.0, 1.0).astype(np.float32, copy=False)
93
+
94
+ def step(self, img01: np.ndarray) -> tuple[float, float, float]:
95
+ cx, cy, conf = self.compute_center(img01)
96
+ if conf <= 0.0:
97
+ return 0.0, 0.0, 0.0
98
+
99
+ if self._ref_center is None:
100
+ self._ref_center = (cx, cy)
101
+ return 0.0, 0.0, conf
102
+
103
+ rx, ry = self._ref_center
104
+ dx = rx - cx
105
+ dy = ry - cy
106
+ return float(dx), float(dy), float(conf)
107
+
108
+ def _prep_mono01(self, img01: np.ndarray) -> np.ndarray:
109
+ m = _to_mono01(img01).astype(np.float32, copy=False)
110
+ m = np.nan_to_num(m, nan=0.0, posinf=0.0, neginf=0.0)
111
+
112
+ # match step(): blur then lo/hi percentile normalize
113
+ if self.smooth_sigma > 0.0 and cv2 is not None:
114
+ m = cv2.GaussianBlur(m, (0, 0), float(self.smooth_sigma))
115
+
116
+ m = self._normalize_for_detect(m)
117
+ return np.clip(m, 0.0, 1.0).astype(np.float32, copy=False)
118
+
119
+ def compute_center(self, img01: np.ndarray):
120
+ m = self._prep_mono01(img01) # already blurred + normalized
121
+
122
+ thr = float(np.percentile(m, np.clip(self.thresh_pct, 0.0, 100.0)))
123
+ if not np.isfinite(thr):
124
+ return (m.shape[1] * 0.5), (m.shape[0] * 0.5), 0.0
125
+
126
+ # ✅ critical: threshold cannot be below min_val (same domain: normalized [0..1])
127
+ thr = max(thr, float(self.min_val))
128
+
129
+ mask = (m >= thr).astype(np.uint8)
130
+ if int(mask.sum()) < 10:
131
+ return (m.shape[1] * 0.5), (m.shape[0] * 0.5), 0.0
132
+
133
+ ys, xs = np.nonzero(mask)
134
+ w = m[ys, xs]
135
+ sw = float(w.sum()) + 1e-12
136
+ cx = float((xs * w).sum() / sw)
137
+ cy = float((ys * w).sum() / sw)
138
+
139
+ conf = float(np.clip((float(np.mean(w)) - thr) / max(1e-6, (1.0 - thr)), 0.0, 1.0))
140
+ return cx, cy, conf
141
+
142
+
143
+ def shift_to_ref(self, img01: np.ndarray, ref_center: tuple[float, float]) -> tuple[float, float, float]:
144
+ """
145
+ Returns (dx, dy, conf) shifting FROM current frame TO the provided reference center.
146
+ """
147
+ cx, cy, conf = self.compute_center(img01)
148
+ if conf <= 0.0:
149
+ return 0.0, 0.0, 0.0
150
+ rx, ry = ref_center
151
+ return float(rx - cx), float(ry - cy), float(conf)
152
+
153
+ class SurfaceTracker:
154
+ """
155
+ Tracks by translation using phase correlation between anchor patch and current patch.
156
+ Good for: lunar/solar/planetary surface close-ups.
157
+ """
158
+ def __init__(self, anchor_patch: np.ndarray, hann_window: bool = True):
159
+ self.ref = np.asarray(anchor_patch, dtype=np.float32)
160
+ self.ref = _to_mono01(self.ref)
161
+ self.hann_window = bool(hann_window)
162
+ self._ref_fft = None
163
+ self._win = None
164
+ self._prep()
165
+
166
+ def _prep(self):
167
+ h, w = self.ref.shape
168
+ if self.hann_window:
169
+ wy = np.hanning(h).astype(np.float32)
170
+ wx = np.hanning(w).astype(np.float32)
171
+ self._win = (wy[:, None] * wx[None, :]).astype(np.float32)
172
+ else:
173
+ self._win = None
174
+
175
+ a = self.ref.copy()
176
+ a -= float(np.mean(a))
177
+ if self._win is not None:
178
+ a *= self._win
179
+ self._ref_fft = np.fft.rfft2(a)
180
+
181
+ def _phase_corr_shift(ref_m: np.ndarray, cur_m: np.ndarray) -> tuple[float, float, float]:
182
+ """
183
+ Returns (dx, dy, response) such that shifting cur by (dx,dy) aligns to ref.
184
+ Uses cv2.phaseCorrelate with mean subtraction + Hann window.
185
+ """
186
+ if cv2 is None:
187
+ return 0.0, 0.0, 0.0
188
+
189
+ ref = ref_m.astype(np.float32, copy=False)
190
+ cur = cur_m.astype(np.float32, copy=False)
191
+
192
+ # stabilize
193
+ ref = ref - float(ref.mean())
194
+ cur = cur - float(cur.mean())
195
+
196
+ # hann window (OpenCV expects float32)
197
+ try:
198
+ win = cv2.createHanningWindow((ref.shape[1], ref.shape[0]), cv2.CV_32F)
199
+ (dx, dy), resp = cv2.phaseCorrelate(ref, cur, win)
200
+ except Exception:
201
+ (dx, dy), resp = cv2.phaseCorrelate(ref, cur)
202
+
203
+ return float(dx), float(dy), float(resp)
204
+
205
+
206
+ @staticmethod
207
+ def _phase_corr(ref_m: np.ndarray, cur_m: np.ndarray) -> tuple[float, float, float]:
208
+ if cv2 is None:
209
+ return 0.0, 0.0, 0.0
210
+
211
+ ref = ref_m.astype(np.float32, copy=False)
212
+ cur = cur_m.astype(np.float32, copy=False)
213
+
214
+ ref = ref - float(ref.mean())
215
+ cur = cur - float(cur.mean())
216
+
217
+ try:
218
+ win = cv2.createHanningWindow((ref.shape[1], ref.shape[0]), cv2.CV_32F)
219
+ (dx, dy), resp = cv2.phaseCorrelate(ref, cur, win)
220
+ except Exception:
221
+ (dx, dy), resp = cv2.phaseCorrelate(ref, cur)
222
+
223
+ return float(dx), float(dy), float(resp)
224
+
225
+ def step(self, cur_patch: np.ndarray) -> tuple[float, float, float]:
226
+ cur = _to_mono01(np.asarray(cur_patch, dtype=np.float32))
227
+ ref = self.ref
228
+ return self._phase_corr(ref, cur)