setiastrosuitepro 1.6.7__py3-none-any.whl → 1.7.0__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 (68) hide show
  1. setiastro/images/abeicon.svg +16 -0
  2. setiastro/images/colorwheel.svg +97 -0
  3. setiastro/images/cosmic.svg +40 -0
  4. setiastro/images/cosmicsat.svg +24 -0
  5. setiastro/images/graxpert.svg +19 -0
  6. setiastro/images/linearfit.svg +32 -0
  7. setiastro/images/narrowbandnormalization.png +0 -0
  8. setiastro/images/pixelmath.svg +42 -0
  9. setiastro/images/planetarystacker.png +0 -0
  10. setiastro/saspro/__main__.py +1 -1
  11. setiastro/saspro/_generated/build_info.py +2 -2
  12. setiastro/saspro/aberration_ai.py +49 -11
  13. setiastro/saspro/aberration_ai_preset.py +29 -3
  14. setiastro/saspro/add_stars.py +29 -5
  15. setiastro/saspro/backgroundneutral.py +73 -33
  16. setiastro/saspro/blink_comparator_pro.py +150 -55
  17. setiastro/saspro/convo.py +9 -6
  18. setiastro/saspro/cosmicclarity.py +125 -18
  19. setiastro/saspro/crop_dialog_pro.py +96 -2
  20. setiastro/saspro/curve_editor_pro.py +132 -61
  21. setiastro/saspro/curves_preset.py +249 -47
  22. setiastro/saspro/doc_manager.py +178 -11
  23. setiastro/saspro/frequency_separation.py +1159 -208
  24. setiastro/saspro/gui/main_window.py +340 -88
  25. setiastro/saspro/gui/mixins/dock_mixin.py +245 -24
  26. setiastro/saspro/gui/mixins/file_mixin.py +35 -16
  27. setiastro/saspro/gui/mixins/menu_mixin.py +31 -1
  28. setiastro/saspro/gui/mixins/theme_mixin.py +160 -14
  29. setiastro/saspro/gui/mixins/toolbar_mixin.py +132 -10
  30. setiastro/saspro/gui/mixins/update_mixin.py +121 -33
  31. setiastro/saspro/histogram.py +179 -7
  32. setiastro/saspro/imageops/narrowband_normalization.py +816 -0
  33. setiastro/saspro/imageops/serloader.py +769 -0
  34. setiastro/saspro/imageops/starbasedwhitebalance.py +23 -52
  35. setiastro/saspro/imageops/stretch.py +582 -62
  36. setiastro/saspro/layers.py +13 -9
  37. setiastro/saspro/layers_dock.py +183 -3
  38. setiastro/saspro/legacy/numba_utils.py +68 -48
  39. setiastro/saspro/live_stacking.py +181 -73
  40. setiastro/saspro/multiscale_decomp.py +77 -29
  41. setiastro/saspro/narrowband_normalization.py +1618 -0
  42. setiastro/saspro/numba_utils.py +72 -57
  43. setiastro/saspro/ops/commands.py +18 -18
  44. setiastro/saspro/ops/script_editor.py +5 -0
  45. setiastro/saspro/ops/scripts.py +119 -0
  46. setiastro/saspro/remove_green.py +1 -1
  47. setiastro/saspro/resources.py +4 -0
  48. setiastro/saspro/ser_stack_config.py +68 -0
  49. setiastro/saspro/ser_stacker.py +2245 -0
  50. setiastro/saspro/ser_stacker_dialog.py +1481 -0
  51. setiastro/saspro/ser_tracking.py +206 -0
  52. setiastro/saspro/serviewer.py +1242 -0
  53. setiastro/saspro/sfcc.py +602 -214
  54. setiastro/saspro/shortcuts.py +154 -25
  55. setiastro/saspro/signature_insert.py +688 -33
  56. setiastro/saspro/stacking_suite.py +853 -401
  57. setiastro/saspro/star_alignment.py +243 -122
  58. setiastro/saspro/stat_stretch.py +878 -131
  59. setiastro/saspro/subwindow.py +303 -74
  60. setiastro/saspro/whitebalance.py +24 -0
  61. setiastro/saspro/widgets/common_utilities.py +28 -21
  62. setiastro/saspro/widgets/resource_monitor.py +128 -80
  63. {setiastrosuitepro-1.6.7.dist-info → setiastrosuitepro-1.7.0.dist-info}/METADATA +2 -2
  64. {setiastrosuitepro-1.6.7.dist-info → setiastrosuitepro-1.7.0.dist-info}/RECORD +68 -51
  65. {setiastrosuitepro-1.6.7.dist-info → setiastrosuitepro-1.7.0.dist-info}/WHEEL +0 -0
  66. {setiastrosuitepro-1.6.7.dist-info → setiastrosuitepro-1.7.0.dist-info}/entry_points.txt +0 -0
  67. {setiastrosuitepro-1.6.7.dist-info → setiastrosuitepro-1.7.0.dist-info}/licenses/LICENSE +0 -0
  68. {setiastrosuitepro-1.6.7.dist-info → setiastrosuitepro-1.7.0.dist-info}/licenses/license.txt +0 -0
@@ -120,37 +120,41 @@ def soft_threshold(x: np.ndarray, t: float):
120
120
  def apply_layer_ops(
121
121
  w: np.ndarray,
122
122
  bias_gain: float,
123
- thr_sigma: float, # threshold in units of σ
123
+ thr_sigma: float,
124
124
  amount: float,
125
125
  denoise_strength: float = 0.0,
126
126
  sigma: float | np.ndarray | None = None,
127
+ layer_index: int | None = None,
127
128
  *,
128
129
  mode: str = "μ–σ Thresholding",
129
130
  ):
130
131
  w2 = w
131
132
 
132
- # Normalize mode to something robust to label wording
133
133
  m = (mode or "").strip().lower()
134
134
  is_linear = m.startswith("linear")
135
-
136
- # --- Linear mode: strictly linear multiscale transform ---
137
135
  if is_linear:
138
- # Ignore thresholding and denoise; just apply gain
139
- if abs(bias_gain - 1.0) > 1e-6:
140
- return w * bias_gain
141
- return w
136
+ return w * bias_gain if abs(bias_gain - 1.0) > 1e-6 else w
142
137
 
143
- # --- μ–σ Thresholding mode (robust nonlinear) ---
144
138
  # 1) Noise reduction step (MMT-style NR)
145
139
  if denoise_strength > 0.0:
146
140
  if sigma is None:
147
141
  sigma = _robust_sigma(w2)
148
142
  sigma_f = float(sigma)
149
- # 3σ at denoise=1, scaled linearly
150
- t_dn = max(0.0, denoise_strength * 3.0 * sigma_f)
143
+
144
+ i = int(layer_index or 0)
145
+
146
+ # --- SMOOTH scaling option (pick ONE) ---
147
+ # Option A: linear growth (very controllable)
148
+ # scale = 1.0 + 0.75 * i
149
+
150
+ # Option B: sqrt growth of 2^i (gentle, "natural")
151
+ scale = (2.0 ** i) ** 0.5 # 1, 1.41, 2, 2.83, 4, ...
152
+
153
+ # Base: 3σ at denoise=1 for layer 0, increases by scale
154
+ t_dn = denoise_strength * 3.0 * scale * sigma_f
155
+
151
156
  if t_dn > 0.0:
152
157
  w_dn = soft_threshold(w2, t_dn)
153
- # Blend original vs denoised based on denoise_strength
154
158
  w2 = (1.0 - denoise_strength) * w2 + denoise_strength * w_dn
155
159
 
156
160
  # 2) Threshold in σ units + bias shaping
@@ -158,7 +162,7 @@ def apply_layer_ops(
158
162
  if sigma is None:
159
163
  sigma = _robust_sigma(w2)
160
164
  sigma_f = float(sigma)
161
- t = thr_sigma * sigma_f # convert N·σ → absolute threshold
165
+ t = thr_sigma * sigma_f
162
166
  if t > 0.0:
163
167
  wt = soft_threshold(w2, t)
164
168
  w2 = (1.0 - amount) * w2 + amount * wt
@@ -167,7 +171,6 @@ def apply_layer_ops(
167
171
  w2 = w2 * bias_gain
168
172
  return w2
169
173
 
170
-
171
174
  def _robust_sigma(arr: np.ndarray) -> float:
172
175
  """
173
176
  Robust per-layer sigma estimate using MAD, fallback to std if needed.
@@ -455,7 +458,7 @@ class MultiscaleDecompDialog(QDialog):
455
458
 
456
459
  # --- Spin boxes ---
457
460
  self.spin_gain = QDoubleSpinBox()
458
- self.spin_gain.setRange(0.0, 3.0)
461
+ self.spin_gain.setRange(0.0, 10.0)
459
462
  self.spin_gain.setSingleStep(0.05)
460
463
  self.spin_gain.setValue(1.0)
461
464
  self.spin_gain.setToolTip(
@@ -491,7 +494,7 @@ class MultiscaleDecompDialog(QDialog):
491
494
 
492
495
  # --- Sliders (int ranges, mapped to spins) ---
493
496
  self.slider_gain = QSlider(Qt.Orientation.Horizontal)
494
- self.slider_gain.setRange(0, 300) # 0..3.00
497
+ self.slider_gain.setRange(0, 1000) # 0..10.00
495
498
  self.slider_gain.setToolTip(self.spin_gain.toolTip())
496
499
 
497
500
  self.slider_thr = QSlider(Qt.Orientation.Horizontal)
@@ -579,19 +582,29 @@ class MultiscaleDecompDialog(QDialog):
579
582
 
580
583
  # ---------- Preview plumbing ----------
581
584
  def _spinner_on(self):
582
- if getattr(self, "busy_spinner", None) is None:
585
+ if getattr(self, "_closing", False):
586
+ return
587
+ try:
588
+ sp = getattr(self, "busy_spinner", None)
589
+ if sp is None:
590
+ return
591
+ sp.setVisible(True)
592
+ mv = getattr(self, "_busy_movie", None)
593
+ if mv is not None and mv.state() != QMovie.MovieState.Running:
594
+ mv.start()
595
+ except RuntimeError:
583
596
  return
584
- self.busy_spinner.setVisible(True)
585
- if getattr(self, "_busy_movie", None) is not None:
586
- if self._busy_movie.state() != QMovie.MovieState.Running:
587
- self._busy_movie.start()
588
597
 
589
598
  def _spinner_off(self):
590
- if getattr(self, "busy_spinner", None) is None:
599
+ try:
600
+ sp = getattr(self, "busy_spinner", None)
601
+ mv = getattr(self, "_busy_movie", None)
602
+ if mv is not None:
603
+ mv.stop()
604
+ if sp is not None:
605
+ sp.setVisible(False)
606
+ except RuntimeError:
591
607
  return
592
- if getattr(self, "_busy_movie", None) is not None:
593
- self._busy_movie.stop()
594
- self.busy_spinner.setVisible(False)
595
608
 
596
609
 
597
610
  def _show_busy_overlay(self):
@@ -623,11 +636,13 @@ class MultiscaleDecompDialog(QDialog):
623
636
  self._schedule_preview()
624
637
 
625
638
  def _schedule_preview(self):
626
- # generic “something changed” entry point
639
+ if getattr(self, "_closing", False):
640
+ return
627
641
  self._preview_timer.start(60)
628
642
 
629
643
  def _schedule_roi_preview(self):
630
- # view changed (scroll/zoom/pan) — still debounced
644
+ if getattr(self, "_closing", False):
645
+ return
631
646
  self._preview_timer.start(60)
632
647
 
633
648
  def _connect_viewport_signals(self):
@@ -736,6 +751,9 @@ class MultiscaleDecompDialog(QDialog):
736
751
  cfg = self.cfgs[i]
737
752
  if not cfg.enabled:
738
753
  return i, np.zeros_like(w)
754
+
755
+ layer_sigma = self.base_sigma * (2 ** i)
756
+
739
757
  sigma = self._layer_noise[i] if self._layer_noise and i < len(self._layer_noise) else None
740
758
  out = apply_layer_ops(
741
759
  w,
@@ -744,6 +762,7 @@ class MultiscaleDecompDialog(QDialog):
744
762
  cfg.amount,
745
763
  cfg.denoise,
746
764
  sigma,
765
+ layer_index=i,
747
766
  mode=mode,
748
767
  )
749
768
  return i, out
@@ -764,8 +783,15 @@ class MultiscaleDecompDialog(QDialog):
764
783
  return tuned, residual
765
784
 
766
785
  def _rebuild_preview(self):
786
+ if getattr(self, "_closing", False):
787
+ return
767
788
  self._spinner_on()
768
- QApplication.processEvents()
789
+ QTimer.singleShot(0, self._rebuild_preview_impl)
790
+
791
+ def _rebuild_preview_impl(self):
792
+ if getattr(self, "_closing", False):
793
+ return
794
+
769
795
  #self._begin_busy()
770
796
  try:
771
797
  # ROI preview can't work until we have *some* pixmap in the scene to derive visible rects from.
@@ -1245,11 +1271,17 @@ class MultiscaleDecompDialog(QDialog):
1245
1271
  cfg = self.cfgs[i]
1246
1272
  if not cfg.enabled:
1247
1273
  return i, np.zeros_like(w)
1274
+
1275
+ layer_sigma = base_sigma * (2 ** i)
1276
+
1248
1277
  return i, apply_layer_ops(
1249
1278
  w, cfg.bias_gain, cfg.thr, cfg.amount, cfg.denoise,
1250
- layer_noise[i], mode=mode
1279
+ layer_noise[i],
1280
+ layer_index=i,
1281
+ mode=mode
1251
1282
  )
1252
1283
 
1284
+
1253
1285
  tuned = [None] * len(details)
1254
1286
  max_workers = min(os.cpu_count() or 4, len(details) or 1)
1255
1287
  with ThreadPoolExecutor(max_workers=max_workers) as ex:
@@ -1749,3 +1781,19 @@ class _MultiScaleDecompPresetDialog(QDialog):
1749
1781
  "linked_rgb": bool(self.cb_linked.isChecked()),
1750
1782
  "layers_cfg": out_layers,
1751
1783
  }
1784
+ def closeEvent(self, ev):
1785
+ self._closing = True
1786
+ try:
1787
+ if hasattr(self, "_preview_timer"):
1788
+ self._preview_timer.stop()
1789
+ if hasattr(self, "_busy_show_timer"):
1790
+ self._busy_show_timer.stop()
1791
+ # Optional: disconnect scrollbars to stop ROI scheduling
1792
+ try:
1793
+ self.view.horizontalScrollBar().valueChanged.disconnect(self._schedule_roi_preview)
1794
+ self.view.verticalScrollBar().valueChanged.disconnect(self._schedule_roi_preview)
1795
+ except Exception:
1796
+ pass
1797
+ except Exception:
1798
+ pass
1799
+ super().closeEvent(ev)