setiastrosuitepro 1.6.4__py3-none-any.whl → 1.7.1.post2__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 (132) hide show
  1. setiastro/images/TextureClarity.svg +56 -0
  2. setiastro/images/abeicon.svg +16 -0
  3. setiastro/images/acv_icon.png +0 -0
  4. setiastro/images/colorwheel.svg +97 -0
  5. setiastro/images/cosmic.svg +40 -0
  6. setiastro/images/cosmicsat.svg +24 -0
  7. setiastro/images/first_quarter.png +0 -0
  8. setiastro/images/full_moon.png +0 -0
  9. setiastro/images/graxpert.svg +19 -0
  10. setiastro/images/last_quarter.png +0 -0
  11. setiastro/images/linearfit.svg +32 -0
  12. setiastro/images/narrowbandnormalization.png +0 -0
  13. setiastro/images/new_moon.png +0 -0
  14. setiastro/images/pixelmath.svg +42 -0
  15. setiastro/images/planetarystacker.png +0 -0
  16. setiastro/images/waning_crescent_1.png +0 -0
  17. setiastro/images/waning_crescent_2.png +0 -0
  18. setiastro/images/waning_crescent_3.png +0 -0
  19. setiastro/images/waning_crescent_4.png +0 -0
  20. setiastro/images/waning_crescent_5.png +0 -0
  21. setiastro/images/waning_gibbous_1.png +0 -0
  22. setiastro/images/waning_gibbous_2.png +0 -0
  23. setiastro/images/waning_gibbous_3.png +0 -0
  24. setiastro/images/waning_gibbous_4.png +0 -0
  25. setiastro/images/waning_gibbous_5.png +0 -0
  26. setiastro/images/waxing_crescent_1.png +0 -0
  27. setiastro/images/waxing_crescent_2.png +0 -0
  28. setiastro/images/waxing_crescent_3.png +0 -0
  29. setiastro/images/waxing_crescent_4.png +0 -0
  30. setiastro/images/waxing_crescent_5.png +0 -0
  31. setiastro/images/waxing_gibbous_1.png +0 -0
  32. setiastro/images/waxing_gibbous_2.png +0 -0
  33. setiastro/images/waxing_gibbous_3.png +0 -0
  34. setiastro/images/waxing_gibbous_4.png +0 -0
  35. setiastro/images/waxing_gibbous_5.png +0 -0
  36. setiastro/qml/ResourceMonitor.qml +84 -82
  37. setiastro/saspro/__main__.py +20 -1
  38. setiastro/saspro/_generated/build_info.py +2 -2
  39. setiastro/saspro/abe.py +37 -4
  40. setiastro/saspro/aberration_ai.py +364 -33
  41. setiastro/saspro/aberration_ai_preset.py +29 -3
  42. setiastro/saspro/acv_exporter.py +379 -0
  43. setiastro/saspro/add_stars.py +33 -6
  44. setiastro/saspro/astrospike_python.py +45 -3
  45. setiastro/saspro/backgroundneutral.py +108 -40
  46. setiastro/saspro/blemish_blaster.py +4 -1
  47. setiastro/saspro/blink_comparator_pro.py +150 -55
  48. setiastro/saspro/clahe.py +4 -1
  49. setiastro/saspro/continuum_subtract.py +4 -1
  50. setiastro/saspro/convo.py +13 -7
  51. setiastro/saspro/cosmicclarity.py +129 -18
  52. setiastro/saspro/crop_dialog_pro.py +123 -7
  53. setiastro/saspro/curve_editor_pro.py +181 -64
  54. setiastro/saspro/curves_preset.py +249 -47
  55. setiastro/saspro/doc_manager.py +245 -15
  56. setiastro/saspro/exoplanet_detector.py +120 -28
  57. setiastro/saspro/frequency_separation.py +1158 -204
  58. setiastro/saspro/ghs_dialog_pro.py +81 -16
  59. setiastro/saspro/graxpert.py +1 -0
  60. setiastro/saspro/gui/main_window.py +706 -264
  61. setiastro/saspro/gui/mixins/dock_mixin.py +245 -24
  62. setiastro/saspro/gui/mixins/file_mixin.py +35 -16
  63. setiastro/saspro/gui/mixins/menu_mixin.py +35 -1
  64. setiastro/saspro/gui/mixins/theme_mixin.py +160 -14
  65. setiastro/saspro/gui/mixins/toolbar_mixin.py +499 -24
  66. setiastro/saspro/gui/mixins/update_mixin.py +138 -36
  67. setiastro/saspro/gui/mixins/view_mixin.py +42 -0
  68. setiastro/saspro/halobgon.py +4 -0
  69. setiastro/saspro/histogram.py +184 -8
  70. setiastro/saspro/image_combine.py +4 -0
  71. setiastro/saspro/image_peeker_pro.py +4 -0
  72. setiastro/saspro/imageops/narrowband_normalization.py +816 -0
  73. setiastro/saspro/imageops/serloader.py +1345 -0
  74. setiastro/saspro/imageops/starbasedwhitebalance.py +23 -52
  75. setiastro/saspro/imageops/stretch.py +582 -62
  76. setiastro/saspro/isophote.py +4 -0
  77. setiastro/saspro/layers.py +13 -9
  78. setiastro/saspro/layers_dock.py +183 -3
  79. setiastro/saspro/legacy/image_manager.py +154 -20
  80. setiastro/saspro/legacy/numba_utils.py +68 -48
  81. setiastro/saspro/legacy/xisf.py +240 -98
  82. setiastro/saspro/live_stacking.py +203 -82
  83. setiastro/saspro/luminancerecombine.py +228 -27
  84. setiastro/saspro/mask_creation.py +174 -15
  85. setiastro/saspro/mfdeconv.py +113 -35
  86. setiastro/saspro/mfdeconvcudnn.py +119 -70
  87. setiastro/saspro/mfdeconvsport.py +112 -35
  88. setiastro/saspro/morphology.py +4 -0
  89. setiastro/saspro/multiscale_decomp.py +81 -29
  90. setiastro/saspro/narrowband_normalization.py +1618 -0
  91. setiastro/saspro/numba_utils.py +72 -57
  92. setiastro/saspro/ops/commands.py +18 -18
  93. setiastro/saspro/ops/script_editor.py +10 -2
  94. setiastro/saspro/ops/scripts.py +122 -0
  95. setiastro/saspro/perfect_palette_picker.py +37 -3
  96. setiastro/saspro/plate_solver.py +84 -49
  97. setiastro/saspro/psf_viewer.py +119 -37
  98. setiastro/saspro/remove_green.py +1 -1
  99. setiastro/saspro/resources.py +73 -0
  100. setiastro/saspro/rgbalign.py +460 -12
  101. setiastro/saspro/selective_color.py +4 -1
  102. setiastro/saspro/ser_stack_config.py +82 -0
  103. setiastro/saspro/ser_stacker.py +2321 -0
  104. setiastro/saspro/ser_stacker_dialog.py +1838 -0
  105. setiastro/saspro/ser_tracking.py +206 -0
  106. setiastro/saspro/serviewer.py +1625 -0
  107. setiastro/saspro/sfcc.py +662 -216
  108. setiastro/saspro/shortcuts.py +171 -33
  109. setiastro/saspro/signature_insert.py +692 -33
  110. setiastro/saspro/stacking_suite.py +1347 -485
  111. setiastro/saspro/star_alignment.py +247 -123
  112. setiastro/saspro/star_spikes.py +4 -0
  113. setiastro/saspro/star_stretch.py +38 -3
  114. setiastro/saspro/stat_stretch.py +892 -129
  115. setiastro/saspro/subwindow.py +787 -363
  116. setiastro/saspro/supernovaasteroidhunter.py +1 -1
  117. setiastro/saspro/texture_clarity.py +593 -0
  118. setiastro/saspro/wavescale_hdr.py +4 -1
  119. setiastro/saspro/wavescalede.py +4 -1
  120. setiastro/saspro/whitebalance.py +84 -12
  121. setiastro/saspro/widgets/common_utilities.py +28 -21
  122. setiastro/saspro/widgets/resource_monitor.py +209 -111
  123. setiastro/saspro/widgets/spinboxes.py +10 -13
  124. setiastro/saspro/wimi.py +27 -656
  125. setiastro/saspro/wims.py +13 -3
  126. setiastro/saspro/xisf.py +101 -11
  127. {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/METADATA +4 -2
  128. {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/RECORD +132 -87
  129. {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/WHEEL +0 -0
  130. {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/entry_points.txt +0 -0
  131. {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/licenses/LICENSE +0 -0
  132. {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.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.
@@ -219,6 +222,10 @@ class MultiscaleDecompDialog(QDialog):
219
222
  self.setWindowFlag(Qt.WindowType.Window, True)
220
223
  self.setWindowModality(Qt.WindowModality.NonModal)
221
224
  self.setModal(False)
225
+ try:
226
+ self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
227
+ except Exception:
228
+ pass # older PyQt6 versions
222
229
  self.setMinimumSize(1050, 700)
223
230
  self.residual_enabled = True
224
231
  self._layer_noise = None # list[float] per detail layer
@@ -451,7 +458,7 @@ class MultiscaleDecompDialog(QDialog):
451
458
 
452
459
  # --- Spin boxes ---
453
460
  self.spin_gain = QDoubleSpinBox()
454
- self.spin_gain.setRange(0.0, 3.0)
461
+ self.spin_gain.setRange(0.0, 10.0)
455
462
  self.spin_gain.setSingleStep(0.05)
456
463
  self.spin_gain.setValue(1.0)
457
464
  self.spin_gain.setToolTip(
@@ -487,7 +494,7 @@ class MultiscaleDecompDialog(QDialog):
487
494
 
488
495
  # --- Sliders (int ranges, mapped to spins) ---
489
496
  self.slider_gain = QSlider(Qt.Orientation.Horizontal)
490
- self.slider_gain.setRange(0, 300) # 0..3.00
497
+ self.slider_gain.setRange(0, 1000) # 0..10.00
491
498
  self.slider_gain.setToolTip(self.spin_gain.toolTip())
492
499
 
493
500
  self.slider_thr = QSlider(Qt.Orientation.Horizontal)
@@ -575,19 +582,29 @@ class MultiscaleDecompDialog(QDialog):
575
582
 
576
583
  # ---------- Preview plumbing ----------
577
584
  def _spinner_on(self):
578
- 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:
579
596
  return
580
- self.busy_spinner.setVisible(True)
581
- if getattr(self, "_busy_movie", None) is not None:
582
- if self._busy_movie.state() != QMovie.MovieState.Running:
583
- self._busy_movie.start()
584
597
 
585
598
  def _spinner_off(self):
586
- 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:
587
607
  return
588
- if getattr(self, "_busy_movie", None) is not None:
589
- self._busy_movie.stop()
590
- self.busy_spinner.setVisible(False)
591
608
 
592
609
 
593
610
  def _show_busy_overlay(self):
@@ -619,11 +636,13 @@ class MultiscaleDecompDialog(QDialog):
619
636
  self._schedule_preview()
620
637
 
621
638
  def _schedule_preview(self):
622
- # generic “something changed” entry point
639
+ if getattr(self, "_closing", False):
640
+ return
623
641
  self._preview_timer.start(60)
624
642
 
625
643
  def _schedule_roi_preview(self):
626
- # view changed (scroll/zoom/pan) — still debounced
644
+ if getattr(self, "_closing", False):
645
+ return
627
646
  self._preview_timer.start(60)
628
647
 
629
648
  def _connect_viewport_signals(self):
@@ -732,6 +751,9 @@ class MultiscaleDecompDialog(QDialog):
732
751
  cfg = self.cfgs[i]
733
752
  if not cfg.enabled:
734
753
  return i, np.zeros_like(w)
754
+
755
+ layer_sigma = self.base_sigma * (2 ** i)
756
+
735
757
  sigma = self._layer_noise[i] if self._layer_noise and i < len(self._layer_noise) else None
736
758
  out = apply_layer_ops(
737
759
  w,
@@ -740,6 +762,7 @@ class MultiscaleDecompDialog(QDialog):
740
762
  cfg.amount,
741
763
  cfg.denoise,
742
764
  sigma,
765
+ layer_index=i,
743
766
  mode=mode,
744
767
  )
745
768
  return i, out
@@ -760,8 +783,15 @@ class MultiscaleDecompDialog(QDialog):
760
783
  return tuned, residual
761
784
 
762
785
  def _rebuild_preview(self):
786
+ if getattr(self, "_closing", False):
787
+ return
763
788
  self._spinner_on()
764
- 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
+
765
795
  #self._begin_busy()
766
796
  try:
767
797
  # ROI preview can't work until we have *some* pixmap in the scene to derive visible rects from.
@@ -1241,11 +1271,17 @@ class MultiscaleDecompDialog(QDialog):
1241
1271
  cfg = self.cfgs[i]
1242
1272
  if not cfg.enabled:
1243
1273
  return i, np.zeros_like(w)
1274
+
1275
+ layer_sigma = base_sigma * (2 ** i)
1276
+
1244
1277
  return i, apply_layer_ops(
1245
1278
  w, cfg.bias_gain, cfg.thr, cfg.amount, cfg.denoise,
1246
- layer_noise[i], mode=mode
1279
+ layer_noise[i],
1280
+ layer_index=i,
1281
+ mode=mode
1247
1282
  )
1248
1283
 
1284
+
1249
1285
  tuned = [None] * len(details)
1250
1286
  max_workers = min(os.cpu_count() or 4, len(details) or 1)
1251
1287
  with ThreadPoolExecutor(max_workers=max_workers) as ex:
@@ -1745,3 +1781,19 @@ class _MultiScaleDecompPresetDialog(QDialog):
1745
1781
  "linked_rgb": bool(self.cb_linked.isChecked()),
1746
1782
  "layers_cfg": out_layers,
1747
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)