setiastrosuitepro 1.7.3__py3-none-any.whl → 1.7.5__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.
- setiastro/images/clonestamp.png +0 -0
- setiastro/saspro/__init__.py +15 -4
- setiastro/saspro/__main__.py +23 -5
- setiastro/saspro/_generated/build_info.py +2 -2
- setiastro/saspro/abe.py +4 -4
- setiastro/saspro/autostretch.py +29 -18
- setiastro/saspro/blemish_blaster.py +54 -14
- setiastro/saspro/clone_stamp.py +753 -0
- setiastro/saspro/gui/main_window.py +27 -6
- setiastro/saspro/gui/mixins/menu_mixin.py +1 -0
- setiastro/saspro/gui/mixins/toolbar_mixin.py +10 -15
- setiastro/saspro/legacy/numba_utils.py +301 -119
- setiastro/saspro/numba_utils.py +998 -270
- setiastro/saspro/ops/settings.py +6 -6
- setiastro/saspro/pixelmath.py +1 -1
- setiastro/saspro/planetprojection.py +310 -105
- setiastro/saspro/resources.py +2 -0
- setiastro/saspro/sfcc.py +14 -8
- setiastro/saspro/stacking_suite.py +413 -174
- setiastro/saspro/subwindow.py +28 -35
- setiastro/saspro/translations/all_source_strings.json +2 -2
- setiastro/saspro/translations/ar_translations.py +3 -3
- setiastro/saspro/translations/de_translations.py +2 -2
- setiastro/saspro/translations/es_translations.py +2 -2
- setiastro/saspro/translations/fr_translations.py +2 -2
- setiastro/saspro/translations/hi_translations.py +2 -2
- setiastro/saspro/translations/it_translations.py +2 -2
- setiastro/saspro/translations/ja_translations.py +2 -2
- setiastro/saspro/translations/pt_translations.py +2 -2
- setiastro/saspro/translations/ru_translations.py +2 -2
- setiastro/saspro/translations/saspro_ar.ts +2 -2
- setiastro/saspro/translations/saspro_de.ts +4 -4
- setiastro/saspro/translations/saspro_es.ts +2 -2
- setiastro/saspro/translations/saspro_fr.ts +2 -2
- setiastro/saspro/translations/saspro_hi.ts +2 -2
- setiastro/saspro/translations/saspro_it.ts +4 -4
- setiastro/saspro/translations/saspro_ja.ts +2 -2
- setiastro/saspro/translations/saspro_pt.ts +2 -2
- setiastro/saspro/translations/saspro_ru.ts +2 -2
- setiastro/saspro/translations/saspro_sw.ts +2 -2
- setiastro/saspro/translations/saspro_uk.ts +2 -2
- setiastro/saspro/translations/saspro_zh.ts +2 -2
- setiastro/saspro/translations/sw_translations.py +2 -2
- setiastro/saspro/translations/uk_translations.py +2 -2
- setiastro/saspro/translations/zh_translations.py +2 -2
- setiastro/saspro/window_shelf.py +62 -1
- {setiastrosuitepro-1.7.3.dist-info → setiastrosuitepro-1.7.5.dist-info}/METADATA +1 -1
- {setiastrosuitepro-1.7.3.dist-info → setiastrosuitepro-1.7.5.dist-info}/RECORD +52 -50
- {setiastrosuitepro-1.7.3.dist-info → setiastrosuitepro-1.7.5.dist-info}/entry_points.txt +1 -1
- {setiastrosuitepro-1.7.3.dist-info → setiastrosuitepro-1.7.5.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.7.3.dist-info → setiastrosuitepro-1.7.5.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.7.3.dist-info → setiastrosuitepro-1.7.5.dist-info}/licenses/license.txt +0 -0
|
Binary file
|
setiastro/saspro/__init__.py
CHANGED
|
@@ -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__
|
|
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__
|
|
18
|
+
__all__ += ["DocManager", "ImageDocument", "ImageSubWindow"]
|
|
19
19
|
except Exception:
|
|
20
|
-
|
|
21
|
-
|
|
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"]
|
setiastro/saspro/__main__.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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-
|
|
3
|
-
APP_VERSION = "1.7.
|
|
2
|
+
BUILD_TIMESTAMP = "2026-01-22T14:55:21Z"
|
|
3
|
+
APP_VERSION = "1.7.5"
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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)
|
setiastro/saspro/autostretch.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
161
|
+
if use_24bit is None:
|
|
151
162
|
try:
|
|
152
163
|
from PyQt6.QtCore import QSettings
|
|
153
|
-
|
|
164
|
+
use_24bit = QSettings().value("display/autostretch_24bit", True, type=bool)
|
|
154
165
|
except Exception:
|
|
155
|
-
|
|
166
|
+
use_24bit = True
|
|
156
167
|
|
|
157
|
-
maxv =
|
|
168
|
+
maxv = _U24_MAX if use_24bit else _U8_MAX
|
|
158
169
|
a = np.asarray(img)
|
|
159
170
|
|
|
160
171
|
# MONO (or pseudo-mono)
|
|
@@ -6,8 +6,8 @@ from typing import Optional
|
|
|
6
6
|
from PyQt6.QtCore import Qt, QEvent, QPointF, QRunnable, QThreadPool, pyqtSlot, QObject, pyqtSignal
|
|
7
7
|
from PyQt6.QtGui import QImage, QPixmap, QPen, QBrush, QAction, QKeySequence, QColor, QWheelEvent, QIcon
|
|
8
8
|
from PyQt6.QtWidgets import (
|
|
9
|
-
QDialog, QVBoxLayout, QHBoxLayout, QFormLayout, QGroupBox, QLabel, QPushButton, QSlider,
|
|
10
|
-
QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, QGraphicsEllipseItem, QMessageBox, QScrollArea, QCheckBox, QDoubleSpinBox
|
|
9
|
+
QDialog, QVBoxLayout, QHBoxLayout, QFormLayout, QGroupBox, QLabel, QPushButton, QSlider, QSizePolicy,
|
|
10
|
+
QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, QGraphicsEllipseItem, QMessageBox, QScrollArea, QCheckBox, QDoubleSpinBox, QWidget, QFrame
|
|
11
11
|
)
|
|
12
12
|
from setiastro.saspro.imageops.stretch import stretch_color_image, stretch_mono_image
|
|
13
13
|
|
|
@@ -209,7 +209,7 @@ class BlemishBlasterDialogPro(QDialog):
|
|
|
209
209
|
self.scroll = QScrollArea(self)
|
|
210
210
|
self.scroll.setWidgetResizable(True)
|
|
211
211
|
self.scroll.setWidget(self.view)
|
|
212
|
-
|
|
212
|
+
self.scroll.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
|
213
213
|
# --- Zoom controls (buttons) ---------------------------------
|
|
214
214
|
# --- Zoom controls (standard themed toolbuttons) ---------------
|
|
215
215
|
self._zoom = 1.0 # initial zoom factor
|
|
@@ -273,17 +273,57 @@ class BlemishBlasterDialogPro(QDialog):
|
|
|
273
273
|
bb.addWidget(self.btn_apply)
|
|
274
274
|
bb.addWidget(self.btn_close)
|
|
275
275
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
276
|
+
# ─────────────────────────────────────────────────────────────
|
|
277
|
+
# Layout: Left = Controls, Right = Preview (so preview gets height)
|
|
278
|
+
# ─────────────────────────────────────────────────────────────
|
|
279
|
+
root = QHBoxLayout(self)
|
|
280
|
+
root.setContentsMargins(8, 8, 8, 8)
|
|
281
|
+
root.setSpacing(10)
|
|
282
|
+
|
|
283
|
+
# ---- LEFT: Zoom + Controls + Buttons (scrollable on small screens) ----
|
|
284
|
+
left = QVBoxLayout()
|
|
285
|
+
left.setSpacing(10)
|
|
286
|
+
|
|
287
|
+
zoom_box = QGroupBox(self.tr("Zoom"))
|
|
288
|
+
zoom_lay = QHBoxLayout(zoom_box)
|
|
289
|
+
zoom_lay.addStretch(1)
|
|
290
|
+
zoom_lay.addWidget(self.btn_zoom_out)
|
|
291
|
+
zoom_lay.addWidget(self.btn_zoom_in)
|
|
292
|
+
zoom_lay.addWidget(self.btn_zoom_fit)
|
|
293
|
+
zoom_lay.addStretch(1)
|
|
294
|
+
|
|
295
|
+
left.addWidget(zoom_box)
|
|
296
|
+
left.addWidget(ctrls, 0)
|
|
297
|
+
|
|
298
|
+
btn_row = QWidget(self)
|
|
299
|
+
btn_row.setLayout(bb)
|
|
300
|
+
left.addWidget(btn_row, 0)
|
|
301
|
+
|
|
302
|
+
left.addStretch(1)
|
|
303
|
+
|
|
304
|
+
left_widget = QWidget(self)
|
|
305
|
+
left_widget.setLayout(left)
|
|
306
|
+
|
|
307
|
+
left_scroll = QScrollArea(self)
|
|
308
|
+
left_scroll.setWidgetResizable(True)
|
|
309
|
+
left_scroll.setMinimumWidth(320) # tweak 300–360
|
|
310
|
+
left_scroll.setWidget(left_widget)
|
|
311
|
+
|
|
312
|
+
# ---- vertical separator ----
|
|
313
|
+
sep = QFrame(self)
|
|
314
|
+
sep.setFrameShape(QFrame.Shape.VLine)
|
|
315
|
+
sep.setFrameShadow(QFrame.Shadow.Sunken)
|
|
316
|
+
|
|
317
|
+
# ---- RIGHT: Preview ----
|
|
318
|
+
right = QVBoxLayout()
|
|
319
|
+
right.setSpacing(8)
|
|
320
|
+
right.addWidget(self.scroll, 1)
|
|
321
|
+
|
|
322
|
+
# Add in order: controls left, preview right
|
|
323
|
+
root.addWidget(left_scroll, 0)
|
|
324
|
+
root.addWidget(sep)
|
|
325
|
+
root.addLayout(right, 1)
|
|
326
|
+
|
|
287
327
|
|
|
288
328
|
# behavior
|
|
289
329
|
self._threadpool = QThreadPool.globalInstance()
|