setiastrosuitepro 1.6.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.
- setiastro/__init__.py +2 -0
- setiastro/saspro/__init__.py +20 -0
- setiastro/saspro/__main__.py +784 -0
- setiastro/saspro/_generated/__init__.py +7 -0
- setiastro/saspro/_generated/build_info.py +2 -0
- setiastro/saspro/abe.py +1295 -0
- setiastro/saspro/abe_preset.py +196 -0
- setiastro/saspro/aberration_ai.py +694 -0
- setiastro/saspro/aberration_ai_preset.py +224 -0
- setiastro/saspro/accel_installer.py +218 -0
- setiastro/saspro/accel_workers.py +30 -0
- setiastro/saspro/add_stars.py +621 -0
- setiastro/saspro/astrobin_exporter.py +1007 -0
- setiastro/saspro/astrospike.py +153 -0
- setiastro/saspro/astrospike_python.py +1839 -0
- setiastro/saspro/autostretch.py +196 -0
- setiastro/saspro/backgroundneutral.py +560 -0
- setiastro/saspro/batch_convert.py +325 -0
- setiastro/saspro/batch_renamer.py +519 -0
- setiastro/saspro/blemish_blaster.py +488 -0
- setiastro/saspro/blink_comparator_pro.py +2923 -0
- setiastro/saspro/bundles.py +61 -0
- setiastro/saspro/bundles_dock.py +114 -0
- setiastro/saspro/cheat_sheet.py +168 -0
- setiastro/saspro/clahe.py +342 -0
- setiastro/saspro/comet_stacking.py +1377 -0
- setiastro/saspro/config.py +38 -0
- setiastro/saspro/config_bootstrap.py +40 -0
- setiastro/saspro/config_manager.py +316 -0
- setiastro/saspro/continuum_subtract.py +1617 -0
- setiastro/saspro/convo.py +1397 -0
- setiastro/saspro/convo_preset.py +414 -0
- setiastro/saspro/copyastro.py +187 -0
- setiastro/saspro/cosmicclarity.py +1564 -0
- setiastro/saspro/cosmicclarity_preset.py +407 -0
- setiastro/saspro/crop_dialog_pro.py +948 -0
- setiastro/saspro/crop_preset.py +189 -0
- setiastro/saspro/curve_editor_pro.py +2544 -0
- setiastro/saspro/curves_preset.py +375 -0
- setiastro/saspro/debayer.py +670 -0
- setiastro/saspro/debug_utils.py +29 -0
- setiastro/saspro/dnd_mime.py +35 -0
- setiastro/saspro/doc_manager.py +2634 -0
- setiastro/saspro/exoplanet_detector.py +2166 -0
- setiastro/saspro/file_utils.py +284 -0
- setiastro/saspro/fitsmodifier.py +744 -0
- setiastro/saspro/free_torch_memory.py +48 -0
- setiastro/saspro/frequency_separation.py +1343 -0
- setiastro/saspro/function_bundle.py +1594 -0
- setiastro/saspro/ghs_dialog_pro.py +660 -0
- setiastro/saspro/ghs_preset.py +284 -0
- setiastro/saspro/graxpert.py +634 -0
- setiastro/saspro/graxpert_preset.py +287 -0
- setiastro/saspro/gui/__init__.py +0 -0
- setiastro/saspro/gui/main_window.py +8494 -0
- setiastro/saspro/gui/mixins/__init__.py +33 -0
- setiastro/saspro/gui/mixins/dock_mixin.py +263 -0
- setiastro/saspro/gui/mixins/file_mixin.py +445 -0
- setiastro/saspro/gui/mixins/geometry_mixin.py +403 -0
- setiastro/saspro/gui/mixins/header_mixin.py +441 -0
- setiastro/saspro/gui/mixins/mask_mixin.py +421 -0
- setiastro/saspro/gui/mixins/menu_mixin.py +361 -0
- setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
- setiastro/saspro/gui/mixins/toolbar_mixin.py +1324 -0
- setiastro/saspro/gui/mixins/update_mixin.py +309 -0
- setiastro/saspro/gui/mixins/view_mixin.py +435 -0
- setiastro/saspro/halobgon.py +462 -0
- setiastro/saspro/header_viewer.py +445 -0
- setiastro/saspro/headless_utils.py +88 -0
- setiastro/saspro/histogram.py +753 -0
- setiastro/saspro/history_explorer.py +939 -0
- setiastro/saspro/image_combine.py +414 -0
- setiastro/saspro/image_peeker_pro.py +1596 -0
- setiastro/saspro/imageops/__init__.py +37 -0
- setiastro/saspro/imageops/mdi_snap.py +292 -0
- setiastro/saspro/imageops/scnr.py +36 -0
- setiastro/saspro/imageops/starbasedwhitebalance.py +210 -0
- setiastro/saspro/imageops/stretch.py +244 -0
- setiastro/saspro/isophote.py +1179 -0
- setiastro/saspro/layers.py +208 -0
- setiastro/saspro/layers_dock.py +714 -0
- setiastro/saspro/lazy_imports.py +193 -0
- setiastro/saspro/legacy/__init__.py +2 -0
- setiastro/saspro/legacy/image_manager.py +2226 -0
- setiastro/saspro/legacy/numba_utils.py +3659 -0
- setiastro/saspro/legacy/xisf.py +1071 -0
- setiastro/saspro/linear_fit.py +534 -0
- setiastro/saspro/live_stacking.py +1830 -0
- setiastro/saspro/log_bus.py +5 -0
- setiastro/saspro/logging_config.py +460 -0
- setiastro/saspro/luminancerecombine.py +309 -0
- setiastro/saspro/main_helpers.py +201 -0
- setiastro/saspro/mask_creation.py +928 -0
- setiastro/saspro/masks_core.py +56 -0
- setiastro/saspro/mdi_widgets.py +353 -0
- setiastro/saspro/memory_utils.py +666 -0
- setiastro/saspro/metadata_patcher.py +75 -0
- setiastro/saspro/mfdeconv.py +3826 -0
- setiastro/saspro/mfdeconv_earlystop.py +71 -0
- setiastro/saspro/mfdeconvcudnn.py +3263 -0
- setiastro/saspro/mfdeconvsport.py +2382 -0
- setiastro/saspro/minorbodycatalog.py +567 -0
- setiastro/saspro/morphology.py +382 -0
- setiastro/saspro/multiscale_decomp.py +1290 -0
- setiastro/saspro/nbtorgb_stars.py +531 -0
- setiastro/saspro/numba_utils.py +3044 -0
- setiastro/saspro/numba_warmup.py +141 -0
- setiastro/saspro/ops/__init__.py +9 -0
- setiastro/saspro/ops/command_help_dialog.py +623 -0
- setiastro/saspro/ops/command_runner.py +217 -0
- setiastro/saspro/ops/commands.py +1594 -0
- setiastro/saspro/ops/script_editor.py +1102 -0
- setiastro/saspro/ops/scripts.py +1413 -0
- setiastro/saspro/ops/settings.py +560 -0
- setiastro/saspro/parallel_utils.py +554 -0
- setiastro/saspro/pedestal.py +121 -0
- setiastro/saspro/perfect_palette_picker.py +1053 -0
- setiastro/saspro/pipeline.py +110 -0
- setiastro/saspro/pixelmath.py +1600 -0
- setiastro/saspro/plate_solver.py +2435 -0
- setiastro/saspro/project_io.py +797 -0
- setiastro/saspro/psf_utils.py +136 -0
- setiastro/saspro/psf_viewer.py +549 -0
- setiastro/saspro/pyi_rthook_astroquery.py +95 -0
- setiastro/saspro/remove_green.py +314 -0
- setiastro/saspro/remove_stars.py +1625 -0
- setiastro/saspro/remove_stars_preset.py +404 -0
- setiastro/saspro/resources.py +472 -0
- setiastro/saspro/rgb_combination.py +207 -0
- setiastro/saspro/rgb_extract.py +19 -0
- setiastro/saspro/rgbalign.py +723 -0
- setiastro/saspro/runtime_imports.py +7 -0
- setiastro/saspro/runtime_torch.py +754 -0
- setiastro/saspro/save_options.py +72 -0
- setiastro/saspro/selective_color.py +1552 -0
- setiastro/saspro/sfcc.py +1425 -0
- setiastro/saspro/shortcuts.py +2807 -0
- setiastro/saspro/signature_insert.py +1099 -0
- setiastro/saspro/stacking_suite.py +17712 -0
- setiastro/saspro/star_alignment.py +7420 -0
- setiastro/saspro/star_alignment_preset.py +329 -0
- setiastro/saspro/star_metrics.py +49 -0
- setiastro/saspro/star_spikes.py +681 -0
- setiastro/saspro/star_stretch.py +470 -0
- setiastro/saspro/stat_stretch.py +502 -0
- setiastro/saspro/status_log_dock.py +78 -0
- setiastro/saspro/subwindow.py +3267 -0
- setiastro/saspro/supernovaasteroidhunter.py +1712 -0
- setiastro/saspro/swap_manager.py +99 -0
- setiastro/saspro/torch_backend.py +89 -0
- setiastro/saspro/torch_rejection.py +434 -0
- setiastro/saspro/view_bundle.py +1555 -0
- setiastro/saspro/wavescale_hdr.py +624 -0
- setiastro/saspro/wavescale_hdr_preset.py +100 -0
- setiastro/saspro/wavescalede.py +657 -0
- setiastro/saspro/wavescalede_preset.py +228 -0
- setiastro/saspro/wcs_update.py +374 -0
- setiastro/saspro/whitebalance.py +456 -0
- setiastro/saspro/widgets/__init__.py +48 -0
- setiastro/saspro/widgets/common_utilities.py +305 -0
- setiastro/saspro/widgets/graphics_views.py +122 -0
- setiastro/saspro/widgets/image_utils.py +518 -0
- setiastro/saspro/widgets/preview_dialogs.py +280 -0
- setiastro/saspro/widgets/spinboxes.py +275 -0
- setiastro/saspro/widgets/themed_buttons.py +13 -0
- setiastro/saspro/widgets/wavelet_utils.py +299 -0
- setiastro/saspro/window_shelf.py +185 -0
- setiastro/saspro/xisf.py +1123 -0
- setiastrosuitepro-1.6.0.dist-info/METADATA +266 -0
- setiastrosuitepro-1.6.0.dist-info/RECORD +174 -0
- setiastrosuitepro-1.6.0.dist-info/WHEEL +4 -0
- setiastrosuitepro-1.6.0.dist-info/entry_points.txt +6 -0
- setiastrosuitepro-1.6.0.dist-info/licenses/LICENSE +674 -0
- setiastrosuitepro-1.6.0.dist-info/licenses/license.txt +2580 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# pro/layers.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Optional, List
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
BLEND_MODES = [
|
|
8
|
+
"Normal",
|
|
9
|
+
"Multiply",
|
|
10
|
+
"Screen",
|
|
11
|
+
"Overlay",
|
|
12
|
+
"Soft Light",
|
|
13
|
+
"Hard Light",
|
|
14
|
+
"Color Dodge",
|
|
15
|
+
"Color Burn",
|
|
16
|
+
"Pin Light",
|
|
17
|
+
"Add",
|
|
18
|
+
"Lighten",
|
|
19
|
+
"Darken",
|
|
20
|
+
"Sigmoid",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class ImageLayer:
|
|
25
|
+
name: str
|
|
26
|
+
src_doc: object # ImageDocument
|
|
27
|
+
visible: bool = True
|
|
28
|
+
opacity: float = 1.0 # 0..1
|
|
29
|
+
mode: str = "Normal" # one of BLEND_MODES
|
|
30
|
+
mask_doc: Optional[object] = None # ImageDocument whose active mask we read
|
|
31
|
+
mask_invert: bool = False
|
|
32
|
+
mask_feather: float = 0.0 # (reserved)
|
|
33
|
+
mask_use_luma: bool = False
|
|
34
|
+
|
|
35
|
+
# Sigmoid blend parameters
|
|
36
|
+
sigmoid_center: float = 0.5 # where transition happens (0..1)
|
|
37
|
+
sigmoid_strength: float = 10.0 # steepness of the curve
|
|
38
|
+
|
|
39
|
+
def _float01(arr: np.ndarray) -> np.ndarray:
|
|
40
|
+
a = np.asarray(arr)
|
|
41
|
+
if a.dtype.kind in "ui":
|
|
42
|
+
info = np.iinfo(a.dtype)
|
|
43
|
+
if info.max == 0:
|
|
44
|
+
return a.astype(np.float32)
|
|
45
|
+
return (a.astype(np.float32) / float(info.max))
|
|
46
|
+
return np.clip(a.astype(np.float32), 0.0, 1.0)
|
|
47
|
+
|
|
48
|
+
def _ensure_3c(a: np.ndarray) -> np.ndarray:
|
|
49
|
+
if a.ndim == 2:
|
|
50
|
+
return np.stack([a, a, a], axis=-1)
|
|
51
|
+
if a.ndim == 3 and a.shape[2] == 1:
|
|
52
|
+
return np.repeat(a, 3, axis=2)
|
|
53
|
+
return a
|
|
54
|
+
|
|
55
|
+
def _resize_like(src: np.ndarray, tgt_shape_hw: tuple[int, int]) -> np.ndarray:
|
|
56
|
+
"""Nearest resize without dependencies. src: (H,W[,C]), target: (H,W)."""
|
|
57
|
+
Ht, Wt = int(tgt_shape_hw[0]), int(tgt_shape_hw[1])
|
|
58
|
+
Hs, Ws = src.shape[0], src.shape[1]
|
|
59
|
+
if (Hs, Ws) == (Ht, Wt):
|
|
60
|
+
return src
|
|
61
|
+
yi = (np.linspace(0, Hs - 1, Ht)).astype(np.int32)
|
|
62
|
+
xi = (np.linspace(0, Ws - 1, Wt)).astype(np.int32)
|
|
63
|
+
return src[yi][:, xi, ...] if src.ndim == 3 else src[yi][:, xi]
|
|
64
|
+
|
|
65
|
+
def _luminance01(img: np.ndarray) -> np.ndarray:
|
|
66
|
+
a = _float01(img)
|
|
67
|
+
a = _ensure_3c(a)
|
|
68
|
+
# Rec. 709 luma
|
|
69
|
+
y = 0.2126 * a[..., 0] + 0.7152 * a[..., 1] + 0.0722 * a[..., 2]
|
|
70
|
+
return np.clip(y.astype(np.float32, copy=False), 0.0, 1.0)
|
|
71
|
+
|
|
72
|
+
def _mask_from_doc(doc, *, use_luma: bool = False) -> Optional[np.ndarray]:
|
|
73
|
+
if doc is None:
|
|
74
|
+
return None
|
|
75
|
+
if use_luma:
|
|
76
|
+
img = getattr(doc, "image", None)
|
|
77
|
+
if img is None:
|
|
78
|
+
return None
|
|
79
|
+
return _luminance01(img)
|
|
80
|
+
|
|
81
|
+
# existing active-mask path
|
|
82
|
+
masks = getattr(doc, "masks", {}) or {}
|
|
83
|
+
mid = getattr(doc, "active_mask_id", None)
|
|
84
|
+
layer = masks.get(mid) if mid else None
|
|
85
|
+
data = getattr(layer, "data", None) if layer is not None else None
|
|
86
|
+
if data is None:
|
|
87
|
+
return None
|
|
88
|
+
m = np.asarray(data)
|
|
89
|
+
if m.ndim == 3 and m.shape[2] == 1:
|
|
90
|
+
m = m[..., 0]
|
|
91
|
+
if m.ndim != 2:
|
|
92
|
+
return None
|
|
93
|
+
return np.clip(m.astype(np.float32, copy=False), 0.0, 1.0)
|
|
94
|
+
|
|
95
|
+
# ---- blend ops (src over base) ---------------------------------------
|
|
96
|
+
def _apply_mode(base: np.ndarray, src: np.ndarray, layer: ImageLayer) -> np.ndarray:
|
|
97
|
+
"""
|
|
98
|
+
base, src: float32, [0..1], same shape.
|
|
99
|
+
Uses layer.mode and optional extra params (e.g. sigmoid_center/strength).
|
|
100
|
+
"""
|
|
101
|
+
mode = getattr(layer, "mode", "Normal") or "Normal"
|
|
102
|
+
|
|
103
|
+
if mode == "Multiply":
|
|
104
|
+
return base * src
|
|
105
|
+
|
|
106
|
+
if mode == "Screen":
|
|
107
|
+
return 1.0 - (1.0 - base) * (1.0 - src)
|
|
108
|
+
|
|
109
|
+
if mode == "Overlay":
|
|
110
|
+
return np.where(
|
|
111
|
+
base <= 0.5,
|
|
112
|
+
2.0 * base * src,
|
|
113
|
+
1.0 - 2.0 * (1.0 - base) * (1.0 - src),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if mode == "Soft Light":
|
|
117
|
+
# SVG / W3C-style soft light
|
|
118
|
+
return (1.0 - 2.0 * src) * (base * base) + 2.0 * src * base
|
|
119
|
+
|
|
120
|
+
if mode == "Hard Light":
|
|
121
|
+
# Overlay, but conditioned on src
|
|
122
|
+
return np.where(
|
|
123
|
+
src <= 0.5,
|
|
124
|
+
2.0 * base * src,
|
|
125
|
+
1.0 - 2.0 * (1.0 - base) * (1.0 - src),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if mode == "Color Dodge":
|
|
129
|
+
eps = 1e-6
|
|
130
|
+
denom = np.maximum(1.0 - src, eps)
|
|
131
|
+
out = base / denom
|
|
132
|
+
return np.clip(out, 0.0, 1.0)
|
|
133
|
+
|
|
134
|
+
if mode == "Color Burn":
|
|
135
|
+
eps = 1e-6
|
|
136
|
+
denom = np.maximum(src, eps)
|
|
137
|
+
out = 1.0 - (1.0 - base) / denom
|
|
138
|
+
return np.clip(out, 0.0, 1.0)
|
|
139
|
+
|
|
140
|
+
if mode == "Pin Light":
|
|
141
|
+
hi = np.maximum(base, 2.0 * src - 1.0)
|
|
142
|
+
lo = np.minimum(base, 2.0 * src)
|
|
143
|
+
return np.where(src > 0.5, hi, lo)
|
|
144
|
+
|
|
145
|
+
if mode == "Add":
|
|
146
|
+
return np.clip(base + src, 0.0, 1.0)
|
|
147
|
+
|
|
148
|
+
if mode == "Lighten":
|
|
149
|
+
return np.maximum(base, src)
|
|
150
|
+
|
|
151
|
+
if mode == "Darken":
|
|
152
|
+
return np.minimum(base, src)
|
|
153
|
+
|
|
154
|
+
if mode == "Sigmoid":
|
|
155
|
+
# Per-layer sigmoid blend:
|
|
156
|
+
# dark base → stay closer to base
|
|
157
|
+
# bright base → move towards src
|
|
158
|
+
luma = _luminance01(base) # (H, W)
|
|
159
|
+
|
|
160
|
+
center = float(getattr(layer, "sigmoid_center", 0.5) or 0.5)
|
|
161
|
+
strength = float(getattr(layer, "sigmoid_strength", 10.0) or 10.0)
|
|
162
|
+
|
|
163
|
+
# weight in [0..1]
|
|
164
|
+
w = 1.0 / (1.0 + np.exp(-strength * (luma - center)))
|
|
165
|
+
w = w[..., None] # broadcast over channels
|
|
166
|
+
|
|
167
|
+
return base * (1.0 - w) + src * w
|
|
168
|
+
|
|
169
|
+
# Normal
|
|
170
|
+
return src
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def composite_stack(base_img: np.ndarray, layers: List[ImageLayer]) -> np.ndarray:
|
|
174
|
+
if base_img is None:
|
|
175
|
+
return None
|
|
176
|
+
out = _float01(base_img)
|
|
177
|
+
out = _ensure_3c(out)
|
|
178
|
+
|
|
179
|
+
H, W = out.shape[0], out.shape[1]
|
|
180
|
+
|
|
181
|
+
# iterate bottom → top so the top-most layer renders last
|
|
182
|
+
for L in reversed(layers or []):
|
|
183
|
+
if not L.visible:
|
|
184
|
+
continue
|
|
185
|
+
src = getattr(L.src_doc, "image", None)
|
|
186
|
+
if src is None:
|
|
187
|
+
continue
|
|
188
|
+
s = _ensure_3c(_float01(src))
|
|
189
|
+
s = _resize_like(s, (H, W))
|
|
190
|
+
|
|
191
|
+
if getattr(L, "mode", None) not in BLEND_MODES:
|
|
192
|
+
L.mode = "Normal"
|
|
193
|
+
|
|
194
|
+
blended = _apply_mode(out, s, L)
|
|
195
|
+
|
|
196
|
+
alpha = float(L.opacity if 0.0 <= L.opacity <= 1.0 else 1.0)
|
|
197
|
+
if L.mask_doc is not None:
|
|
198
|
+
m = _mask_from_doc(L.mask_doc, use_luma=bool(L.mask_use_luma))
|
|
199
|
+
if m is not None:
|
|
200
|
+
m = _resize_like(m, (H, W))
|
|
201
|
+
if L.mask_invert:
|
|
202
|
+
m = 1.0 - m
|
|
203
|
+
alpha_map = np.clip(alpha * m, 0.0, 1.0)[..., None]
|
|
204
|
+
out = out * (1.0 - alpha_map) + blended * alpha_map
|
|
205
|
+
continue
|
|
206
|
+
out = out * (1.0 - alpha) + blended * alpha
|
|
207
|
+
|
|
208
|
+
return out
|