setiastrosuitepro 1.7.3__py3-none-any.whl → 1.7.4__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 (47) hide show
  1. setiastro/saspro/__init__.py +15 -4
  2. setiastro/saspro/__main__.py +23 -5
  3. setiastro/saspro/_generated/build_info.py +2 -2
  4. setiastro/saspro/abe.py +4 -4
  5. setiastro/saspro/autostretch.py +29 -18
  6. setiastro/saspro/gui/main_window.py +5 -5
  7. setiastro/saspro/gui/mixins/toolbar_mixin.py +2 -2
  8. setiastro/saspro/legacy/numba_utils.py +301 -119
  9. setiastro/saspro/numba_utils.py +998 -270
  10. setiastro/saspro/ops/settings.py +6 -6
  11. setiastro/saspro/pixelmath.py +1 -1
  12. setiastro/saspro/planetprojection.py +310 -105
  13. setiastro/saspro/sfcc.py +14 -8
  14. setiastro/saspro/stacking_suite.py +292 -111
  15. setiastro/saspro/subwindow.py +28 -35
  16. setiastro/saspro/translations/all_source_strings.json +2 -2
  17. setiastro/saspro/translations/ar_translations.py +3 -3
  18. setiastro/saspro/translations/de_translations.py +2 -2
  19. setiastro/saspro/translations/es_translations.py +2 -2
  20. setiastro/saspro/translations/fr_translations.py +2 -2
  21. setiastro/saspro/translations/hi_translations.py +2 -2
  22. setiastro/saspro/translations/it_translations.py +2 -2
  23. setiastro/saspro/translations/ja_translations.py +2 -2
  24. setiastro/saspro/translations/pt_translations.py +2 -2
  25. setiastro/saspro/translations/ru_translations.py +2 -2
  26. setiastro/saspro/translations/saspro_ar.ts +2 -2
  27. setiastro/saspro/translations/saspro_de.ts +4 -4
  28. setiastro/saspro/translations/saspro_es.ts +2 -2
  29. setiastro/saspro/translations/saspro_fr.ts +2 -2
  30. setiastro/saspro/translations/saspro_hi.ts +2 -2
  31. setiastro/saspro/translations/saspro_it.ts +4 -4
  32. setiastro/saspro/translations/saspro_ja.ts +2 -2
  33. setiastro/saspro/translations/saspro_pt.ts +2 -2
  34. setiastro/saspro/translations/saspro_ru.ts +2 -2
  35. setiastro/saspro/translations/saspro_sw.ts +2 -2
  36. setiastro/saspro/translations/saspro_uk.ts +2 -2
  37. setiastro/saspro/translations/saspro_zh.ts +2 -2
  38. setiastro/saspro/translations/sw_translations.py +2 -2
  39. setiastro/saspro/translations/uk_translations.py +2 -2
  40. setiastro/saspro/translations/zh_translations.py +2 -2
  41. setiastro/saspro/window_shelf.py +62 -1
  42. {setiastrosuitepro-1.7.3.dist-info → setiastrosuitepro-1.7.4.dist-info}/METADATA +1 -1
  43. {setiastrosuitepro-1.7.3.dist-info → setiastrosuitepro-1.7.4.dist-info}/RECORD +47 -47
  44. {setiastrosuitepro-1.7.3.dist-info → setiastrosuitepro-1.7.4.dist-info}/entry_points.txt +1 -1
  45. {setiastrosuitepro-1.7.3.dist-info → setiastrosuitepro-1.7.4.dist-info}/WHEEL +0 -0
  46. {setiastrosuitepro-1.7.3.dist-info → setiastrosuitepro-1.7.4.dist-info}/licenses/LICENSE +0 -0
  47. {setiastrosuitepro-1.7.3.dist-info → setiastrosuitepro-1.7.4.dist-info}/licenses/license.txt +0 -0
@@ -6,7 +6,7 @@ stacking, registration, photometry, and visualization.
6
6
 
7
7
  Important:
8
8
  - __init__.py must remain import-safe (no UI side effects, no splash, no QApplication).
9
- - Do NOT import setiastro.saspro.__main__ from here.
9
+ - Do NOT import setiastro.saspro.__main__ at import time.
10
10
  """
11
11
 
12
12
  __all__ = []
@@ -15,7 +15,18 @@ __all__ = []
15
15
  try:
16
16
  from .doc_manager import DocManager, ImageDocument
17
17
  from .subwindow import ImageSubWindow
18
- __all__ = ["DocManager", "ImageDocument", "ImageSubWindow"]
18
+ __all__ += ["DocManager", "ImageDocument", "ImageSubWindow"]
19
19
  except Exception:
20
- # During initial setup or partial installs, some modules may not be available
21
- __all__ = []
20
+ pass
21
+
22
+ def main():
23
+ """
24
+ Console entrypoint shim.
25
+
26
+ IMPORTANT: This must stay import-safe. We import the real entrypoint
27
+ lazily only when the command is executed.
28
+ """
29
+ from .__main__ import main as _main
30
+ return _main()
31
+
32
+ __all__ += ["main"]
@@ -440,17 +440,25 @@ def _init_splash():
440
440
  self.close()
441
441
  self.deleteLater()
442
442
 
443
- def start_fade_out(self):
443
+ def start_fade_out(self, on_finished=None):
444
444
  if not _allow_window_opacity_effects():
445
445
  self.finish()
446
+ if callable(on_finished):
447
+ on_finished()
446
448
  return
447
- """Smoothly fade out the splash screen."""
449
+
448
450
  self._anim = QPropertyAnimation(self, b"windowOpacity")
449
451
  self._anim.setDuration(1000)
450
452
  self._anim.setStartValue(1.0)
451
453
  self._anim.setEndValue(0.0)
452
454
  self._anim.setEasingCurve(QEasingCurve.Type.OutQuad)
453
- self._anim.finished.connect(self.finish)
455
+
456
+ def _done():
457
+ self.finish()
458
+ if callable(on_finished):
459
+ on_finished()
460
+
461
+ self._anim.finished.connect(_done)
454
462
  self._anim.start()
455
463
 
456
464
  def start_fade_in(self):
@@ -790,6 +798,7 @@ def main():
790
798
  - When running as a module: python -m setiastro.saspro
791
799
  """
792
800
  global _splash, _app, _splash_initialized
801
+ from PyQt6.QtCore import QTimer
793
802
 
794
803
  # Initialize splash if not already done
795
804
  if not _splash_initialized:
@@ -910,7 +919,16 @@ def main():
910
919
  version=VERSION,
911
920
  build_timestamp=BUILD_TIMESTAMP,
912
921
  )
913
-
922
+
923
+ def _kick_updates_after_splash():
924
+ try:
925
+ win.raise_()
926
+ win.activateWindow()
927
+ if win.settings.value("updates/check_on_startup", True, type=bool):
928
+ QTimer.singleShot(1000, win.check_for_updates_startup)
929
+ except Exception:
930
+ pass
931
+
914
932
  if _splash:
915
933
  _splash.setMessage(QCoreApplication.translate("Splash", "Showing main window..."))
916
934
  _splash.setProgress(95)
@@ -965,7 +983,7 @@ def main():
965
983
 
966
984
  # 2. Animate Splash Fade Out
967
985
  # Note: We do NOT use finish() directly here. The animation calls it when done.
968
- _splash.start_fade_out()
986
+ _splash.start_fade_out(on_finished=_kick_updates_after_splash)
969
987
 
970
988
  # NOTE: We keep a reference to _splash (global) so it doesn't get GC'd during animation.
971
989
  # It will deleteLater() itself.
@@ -1,3 +1,3 @@
1
1
  # Auto-generated at build time. Do not edit.
2
- BUILD_TIMESTAMP = "2026-01-20T16:24:15Z"
3
- APP_VERSION = "1.7.3"
2
+ BUILD_TIMESTAMP = "2026-01-21T15:42:11Z"
3
+ APP_VERSION = "1.7.4"
setiastro/saspro/abe.py CHANGED
@@ -951,7 +951,7 @@ class ABEDialog(QDialog):
951
951
 
952
952
  # show autostretched or raw; siril_style_autostretch() already clips its result
953
953
  src_to_show = (hard_autostretch(self._preview_source_f01, target_median=0.5, sigma=2,
954
- linked=False, use_16bit=True)
954
+ linked=False, use_24bit=True)
955
955
  if getattr(self, "_autostretch_on", False) else self._preview_source_f01)
956
956
 
957
957
  if src_to_show.ndim == 2 or (src_to_show.ndim == 3 and src_to_show.shape[2] == 1):
@@ -1229,7 +1229,7 @@ class ABEDialog(QDialog):
1229
1229
  self._preview_source_f01 = a # ← no np.clip
1230
1230
 
1231
1231
  src_to_show = (hard_autostretch(self._preview_source_f01, target_median=0.5, sigma=2,
1232
- linked=False, use_16bit=True)
1232
+ linked=False, use_24bit=True)
1233
1233
  if getattr(self, "_autostretch_on", False) else self._preview_source_f01)
1234
1234
 
1235
1235
  if src_to_show.ndim == 2 or (src_to_show.ndim == 3 and src_to_show.shape[2] == 1):
@@ -1349,7 +1349,7 @@ class ABEDialog(QDialog):
1349
1349
  # Prefer float source (avoids 8-bit clipping); fall back to decoding _last_preview if needed
1350
1350
  arr = self._preview_source_f01 if self._preview_source_f01 is not None else (self._last_preview.astype(np.float32)/255.0)
1351
1351
 
1352
- stretched = hard_autostretch(arr, target_median=0.5, sigma=2, linked=False, use_16bit=True)
1352
+ stretched = hard_autostretch(arr, target_median=0.5, sigma=2, linked=False, use_24bit=True)
1353
1353
 
1354
1354
  buf8 = (np.clip(stretched, 0.0, 1.0) * 255.0).astype(np.uint8)
1355
1355
  if buf8.ndim == 2:
@@ -1367,7 +1367,7 @@ class ABEDialog(QDialog):
1367
1367
  if self._preview_source_f01 is None:
1368
1368
  return
1369
1369
  stretched = hard_autostretch(self._preview_source_f01, target_median=0.5, sigma=2,
1370
- linked=False, use_16bit=True)
1370
+ linked=False, use_24bit=True)
1371
1371
  buf8 = (np.clip(stretched, 0.0, 1.0) * 255.0).astype(np.uint8)
1372
1372
  if buf8.ndim == 2:
1373
1373
  buf8 = np.stack([buf8] * 3, axis=-1)
@@ -4,12 +4,18 @@ import numpy as np
4
4
  _MAX_STATS_PIXELS = 1_000_000
5
5
  _DEFAULT_SIGMA = 3
6
6
  _U8_MAX = 4095 # 12-bit output for better gradations than 255
7
- _U16_MAX = 65535
7
+ _U24_MAX = 16777215 # 24-bit output for better gradations
8
8
 
9
9
  # ---------- helpers (generic N-level pipeline) ----------
10
10
  def _to_uN(a: np.ndarray, maxv: int) -> np.ndarray:
11
- """Convert to uint8/uint16 [0..maxv] for cheap hist/LUT work."""
12
- tgt_dtype = np.uint16 if maxv > _U8_MAX else np.uint8
11
+ """Convert to uint8/uint16/uint32 [0..maxv] for cheap hist/LUT work."""
12
+ # uint8 for maxv <= 255, uint16 for 256..65535, uint32 for larger (24-bit)
13
+ if maxv > 65535:
14
+ tgt_dtype = np.uint32
15
+ elif maxv > 255:
16
+ tgt_dtype = np.uint16
17
+ else:
18
+ tgt_dtype = np.uint8
13
19
  if a.dtype == tgt_dtype:
14
20
  return a
15
21
  if np.issubdtype(a.dtype, np.integer):
@@ -17,10 +23,12 @@ def _to_uN(a: np.ndarray, maxv: int) -> np.ndarray:
17
23
  if info.max <= 0:
18
24
  return np.zeros_like(a, dtype=tgt_dtype)
19
25
  scaled = np.clip(a.astype(np.float32), 0, info.max) * (maxv / float(info.max))
20
- return (scaled + 0.5).astype(tgt_dtype)
26
+ # Clamp to maxv to avoid index-out-of-bounds when used as LUT indices
27
+ return np.minimum((scaled + 0.5).astype(tgt_dtype), maxv)
21
28
  # float-ish
22
29
  af = np.clip(a.astype(np.float32), 0.0, 1.0)
23
- return (af * maxv + 0.5).astype(tgt_dtype)
30
+ # Clamp to maxv: when af=1.0, af*maxv+0.5 can exceed maxv
31
+ return np.minimum((af * maxv + 0.5).astype(tgt_dtype), maxv)
24
32
 
25
33
  def _choose_stride(h: int, w: int, max_pixels: int) -> tuple[int, int]:
26
34
  n = h * w
@@ -132,29 +140,32 @@ def autostretch(
132
140
  linked: bool = False,
133
141
  sigma: float = _DEFAULT_SIGMA,
134
142
  *,
135
- use_16bit: bool | None = None,
143
+ use_24bit: bool | None = None,
144
+ use_16bit: bool | None = None, # <-- legacy compat (ignored / mapped)
145
+ **_ignored_kwargs, # <-- swallow any other legacy flags safely
136
146
  ) -> np.ndarray:
137
- """
138
- High-quality autostretch that can operate in 16-bit (HQ, default) or 8-bit (fast) mode.
139
-
140
- • 16-bit mode: smooth gradients, minimal posterization (recommended).
141
- • 8-bit mode: slightly faster on very large images, lower fidelity.
142
147
 
143
- If use_16bit is None, we try to read QSettings("display/autostretch_16bit") and
144
- default to True on failure (no Qt in context).
145
- """
146
148
  if img is None:
147
149
  return None
148
150
 
151
+ # ---- legacy compat -------------------------------------------------
152
+ # Old callers may pass use_16bit. We no longer support 16-bit preview output.
153
+ # If they pass it, we just treat it as "use higher precision display", i.e. 24-bit.
154
+ if use_16bit is not None:
155
+ # If caller explicitly asked for 16-bit, we interpret that as "high precision".
156
+ # Only override if caller didn't explicitly pass use_24bit.
157
+ if use_24bit is None:
158
+ use_24bit = True
159
+
149
160
  # Optional auto-read from QSettings if caller didn’t pass a flag.
150
- if use_16bit is None:
161
+ if use_24bit is None:
151
162
  try:
152
163
  from PyQt6.QtCore import QSettings
153
- use_16bit = QSettings().value("display/autostretch_16bit", True, type=bool)
164
+ use_24bit = QSettings().value("display/autostretch_24bit", True, type=bool)
154
165
  except Exception:
155
- use_16bit = True
166
+ use_24bit = True
156
167
 
157
- maxv = _U16_MAX if use_16bit else _U8_MAX
168
+ maxv = _U24_MAX if use_24bit else _U8_MAX
158
169
  a = np.asarray(img)
159
170
 
160
171
  # MONO (or pseudo-mono)
@@ -691,8 +691,8 @@ class AstroSuiteProMainWindow(
691
691
  self.apply_theme_from_settings()
692
692
  self._populate_view_panels_menu()
693
693
  # Startup check (no lambdas)
694
- if self.settings.value("updates/check_on_startup", True, type=bool):
695
- QTimer.singleShot(1500, self.check_for_updates_startup)
694
+ #if self.settings.value("updates/check_on_startup", True, type=bool):
695
+ # QTimer.singleShot(1500, self.check_for_updates_startup)
696
696
 
697
697
  self._hdr_refresh_timer = QTimer(self)
698
698
  self._hdr_refresh_timer.setSingleShot(True)
@@ -1473,7 +1473,7 @@ class AstroSuiteProMainWindow(
1473
1473
 
1474
1474
  def _open_user_scripts_github(self):
1475
1475
  # User script examples on GitHub
1476
- url = QUrl("https://github.com/setiastro/setiastrosuitepro/tree/main/scripts")
1476
+ url = QUrl("https://drive.google.com/drive/folders/1TSxKZey4R_t7F2RsB53Hd1SBIGXv3-Nl?usp=drive_link")
1477
1477
  QDesktopServices.openUrl(url)
1478
1478
 
1479
1479
  def _open_scripts_discord_forum(self):
@@ -2798,7 +2798,7 @@ class AstroSuiteProMainWindow(
2798
2798
  target = float(self.settings.value("display/target", 0.30, type=float))
2799
2799
  sigma = float(self.settings.value("display/sigma", 5.0, type=float))
2800
2800
  linked = bool(self.settings.value("display/stretch_linked", False, type=bool))
2801
- use_16 = self.settings.value("display/autostretch_16bit", True, type=bool)
2801
+ use_24 = self.settings.value("display/autostretch_24bit", True, type=bool)
2802
2802
 
2803
2803
  # if your view exposes per-view overrides, prefer those
2804
2804
  if hasattr(view, "autostretch_target"):
@@ -2824,7 +2824,7 @@ class AstroSuiteProMainWindow(
2824
2824
  target_median=target,
2825
2825
  linked=linked,
2826
2826
  sigma=sigma,
2827
- use_16bit=use_16,
2827
+ use_24bit=use_24,
2828
2828
  )
2829
2829
  except Exception as e:
2830
2830
  QMessageBox.warning(self, "Display-Stretch", f"Failed to apply autostretch:\n{e}")
@@ -1196,7 +1196,7 @@ class ToolbarMixin:
1196
1196
  self.act_planetary_stacker.setStatusTip(self.tr("Stack SER videos (planetary/solar/lunar)"))
1197
1197
  self.act_planetary_stacker.triggered.connect(self._open_planetary_stacker)
1198
1198
 
1199
- self.act_planet_projection = QAction(QIcon(planetprojection_path), self.tr("Planetary Projection..."), self)
1199
+ self.act_planet_projection = QAction(QIcon(planetprojection_path), self.tr("3D Projection..."), self)
1200
1200
  self.act_planet_projection.setIconVisibleInMenu(True)
1201
1201
  self.act_planet_projection.setStatusTip(self.tr("View your planets with stereographic projection"))
1202
1202
  self.act_planet_projection.triggered.connect(self._open_planet_projection)
@@ -1278,7 +1278,7 @@ class ToolbarMixin:
1278
1278
  self.act_script_editor.setStatusTip(self.tr("Open the built-in script editor"))
1279
1279
  self.act_script_editor.triggered.connect(self._show_script_editor)
1280
1280
 
1281
- self.act_open_user_scripts_github = QAction(self.tr("Open User Scripts (GitHub)..."), self)
1281
+ self.act_open_user_scripts_github = QAction(self.tr("Open User Scripts (GoogleDrive)..."), self)
1282
1282
  self.act_open_user_scripts_github.triggered.connect(self._open_user_scripts_github)
1283
1283
 
1284
1284
  self.act_open_scripts_discord = QAction(self.tr("Open Scripts Forum (Discord)..."), self)