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.
Potentially problematic release.
This version of setiastrosuitepro might be problematic. Click here for more details.
- 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,284 @@
|
|
|
1
|
+
# pro/ghs_preset.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import Dict, List, Tuple
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
# Reuse LUT + apply engine from Curves editor (with a safe fallback import)
|
|
7
|
+
from setiastro.saspro.curve_editor_pro import build_curve_lut, _apply_mode_any # if your file name lacks the 's'
|
|
8
|
+
|
|
9
|
+
# Optional PCHIP interpolation (nice-to-have; we’ll fall back to np.interp)
|
|
10
|
+
try:
|
|
11
|
+
from scipy.interpolate import PchipInterpolator as _PCHIP
|
|
12
|
+
_HAS_PCHIP = True
|
|
13
|
+
except Exception:
|
|
14
|
+
_HAS_PCHIP = False
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ---------- helpers: points -> scene -> interpolator -> LUT ----------
|
|
18
|
+
|
|
19
|
+
def _points_norm_to_scene(points_norm: List[Tuple[float, float]]) -> List[Tuple[float, float]]:
|
|
20
|
+
"""(x:[0..1], y:[0..1 up]) → scene coords (x:[0..360], y:[0..360] down)."""
|
|
21
|
+
pts = []
|
|
22
|
+
for x, y in points_norm:
|
|
23
|
+
x = float(max(0.0, min(1.0, x)))
|
|
24
|
+
y = float(max(0.0, min(1.0, y)))
|
|
25
|
+
xs = 360.0 * x
|
|
26
|
+
ys = 360.0 * (1.0 - y)
|
|
27
|
+
pts.append((xs, ys))
|
|
28
|
+
# Ensure strict endpoints exist
|
|
29
|
+
if not any(abs(px - 0.0) < 1e-6 for px, _ in pts): pts.append((0.0, 360.0))
|
|
30
|
+
if not any(abs(px - 360.0) < 1e-6 for px, _ in pts): pts.append((360.0, 0.0))
|
|
31
|
+
pts = sorted(pts, key=lambda t: t[0])
|
|
32
|
+
# Make X strictly increasing
|
|
33
|
+
out, lastx = [], -1e9
|
|
34
|
+
for x, y in pts:
|
|
35
|
+
if x <= lastx: x = lastx + 1e-3
|
|
36
|
+
out.append((min(360.0, max(0.0, x)), min(360.0, max(0.0, y))))
|
|
37
|
+
lastx = out[-1][0]
|
|
38
|
+
return out
|
|
39
|
+
|
|
40
|
+
def _interpolator_from_scene_points(points_scene: List[Tuple[float, float]]):
|
|
41
|
+
xs = np.array([p[0] for p in points_scene], dtype=np.float64)
|
|
42
|
+
ys = np.array([p[1] for p in points_scene], dtype=np.float64)
|
|
43
|
+
if _HAS_PCHIP and xs.size >= 2:
|
|
44
|
+
return _PCHIP(xs, ys)
|
|
45
|
+
def _lin(x):
|
|
46
|
+
return np.interp(x, xs, ys)
|
|
47
|
+
return _lin
|
|
48
|
+
|
|
49
|
+
def _norm_channel(ch: str | None) -> str:
|
|
50
|
+
m = (ch or "K (Brightness)").strip().lower()
|
|
51
|
+
alias = {"k":"K (Brightness)","brightness":"K (Brightness)","rgb":"K (Brightness)",
|
|
52
|
+
"r":"R","g":"G","b":"B"}
|
|
53
|
+
proper = {"k (brightness)":"K (Brightness)","r":"R","g":"G","b":"B"}
|
|
54
|
+
if m in proper: return proper[m]
|
|
55
|
+
return alias.get(m, "K (Brightness)")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# ---------- GHS parameterization → normalized control curve ----------
|
|
59
|
+
|
|
60
|
+
def _ghs_points_norm(alpha: float, beta: float, gamma: float,
|
|
61
|
+
pivot: float, lp: float, hp: float,
|
|
62
|
+
N: int = 128) -> List[Tuple[float, float]]:
|
|
63
|
+
"""
|
|
64
|
+
Returns a monotone set of (x,y) in [0..1] implementing your dialog’s math.
|
|
65
|
+
"""
|
|
66
|
+
a = float(np.clip(alpha, 0.02, 10.0)) # sliders 1..500 → /50 gives 0.02..10
|
|
67
|
+
b = float(np.clip(beta, 0.02, 10.0))
|
|
68
|
+
g = float(np.clip(gamma, 0.01, 5.0)) # slider 1..500 → /100 gives 0.01..5
|
|
69
|
+
SP = float(np.clip(pivot, 0.0, 1.0))
|
|
70
|
+
LP = float(np.clip(lp, 0.0, 1.0))
|
|
71
|
+
HP = float(np.clip(hp, 0.0, 1.0))
|
|
72
|
+
|
|
73
|
+
us = np.linspace(0.0, 1.0, int(max(16, N)))
|
|
74
|
+
left = us <= 0.5
|
|
75
|
+
right = ~left
|
|
76
|
+
|
|
77
|
+
# Generalized hyperbolic halves around 0.5
|
|
78
|
+
rawL = us**a / (us**a + b*(1.0-us)**a)
|
|
79
|
+
rawR = us**a / (us**a + (1.0/b)*(1.0-us)**a)
|
|
80
|
+
|
|
81
|
+
midL = (0.5**a) / (0.5**a + b*(0.5)**a)
|
|
82
|
+
midR = (0.5**a) / (0.5**a + (1.0/b)*(0.5)**a)
|
|
83
|
+
eps = 1e-6
|
|
84
|
+
|
|
85
|
+
# Domain remap to pivot SP
|
|
86
|
+
up = np.empty_like(us)
|
|
87
|
+
vp = np.empty_like(us)
|
|
88
|
+
|
|
89
|
+
# Left → [0..SP]
|
|
90
|
+
up[left] = 2.0 * SP * us[left]
|
|
91
|
+
vp[left] = rawL[left] * (SP / max(midL, eps))
|
|
92
|
+
|
|
93
|
+
# Right → [SP..1]
|
|
94
|
+
up[right] = SP + 2.0*(1.0 - SP)*(us[right] - 0.5)
|
|
95
|
+
vp[right] = SP + (rawR[right] - midR) * ((1.0 - SP) / max(1.0 - midR, eps))
|
|
96
|
+
|
|
97
|
+
# LP/HP protection (blend toward identity y=x on each side)
|
|
98
|
+
if LP > 0:
|
|
99
|
+
m = up <= SP
|
|
100
|
+
vp[m] = (1.0 - LP)*vp[m] + LP*up[m]
|
|
101
|
+
if HP > 0:
|
|
102
|
+
m = up >= SP
|
|
103
|
+
vp[m] = (1.0 - HP)*vp[m] + HP*up[m]
|
|
104
|
+
|
|
105
|
+
# Gamma lift
|
|
106
|
+
if abs(g - 1.0) > 1e-6:
|
|
107
|
+
vp = np.clip(vp, 0.0, 1.0) ** (1.0 / g)
|
|
108
|
+
|
|
109
|
+
# Clamp + enforce monotonicity (guards against tiny numerical dips)
|
|
110
|
+
vp = np.clip(vp, 0.0, 1.0)
|
|
111
|
+
vp = np.maximum.accumulate(vp)
|
|
112
|
+
|
|
113
|
+
pts = list(zip(up.tolist(), vp.tolist()))
|
|
114
|
+
# Make sure endpoints exist exactly
|
|
115
|
+
if pts[0][0] != 0.0: pts.insert(0, (0.0, 0.0))
|
|
116
|
+
if pts[-1][0] != 1.0: pts.append((1.0, 1.0))
|
|
117
|
+
return pts
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _lut_from_ghs_preset(preset: Dict) -> tuple[np.ndarray, str, Dict]:
|
|
121
|
+
p = dict(preset or {})
|
|
122
|
+
alpha = float(p.get("alpha", 1.0))
|
|
123
|
+
beta = float(p.get("beta", 1.0))
|
|
124
|
+
gamma = float(p.get("gamma", 1.0))
|
|
125
|
+
pivot = float(p.get("pivot", 0.5))
|
|
126
|
+
lp = float(p.get("lp", 0.0))
|
|
127
|
+
hp = float(p.get("hp", 0.0))
|
|
128
|
+
ch = _norm_channel(p.get("channel", "K (Brightness)"))
|
|
129
|
+
|
|
130
|
+
ptsN = _ghs_points_norm(alpha, beta, gamma, pivot, lp, hp, N=128)
|
|
131
|
+
ptsS = _points_norm_to_scene(ptsN)
|
|
132
|
+
fn = _interpolator_from_scene_points(ptsS)
|
|
133
|
+
lut01 = build_curve_lut(fn, size=65536)
|
|
134
|
+
|
|
135
|
+
# sanitized params for metadata
|
|
136
|
+
params = {"alpha":alpha, "beta":beta, "gamma":gamma, "pivot":pivot, "lp":lp, "hp":hp, "channel":ch}
|
|
137
|
+
return lut01, ch, params
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# ---------- optional: mask-aware blend (same semantics as your dialogs) ----------
|
|
141
|
+
|
|
142
|
+
def _active_mask_layer(doc):
|
|
143
|
+
mid = getattr(doc, "active_mask_id", None)
|
|
144
|
+
if not mid: return None, None, None
|
|
145
|
+
layer = getattr(doc, "masks", {}).get(mid)
|
|
146
|
+
if layer is None: return None, None, None
|
|
147
|
+
m = np.asarray(getattr(layer, "data", None))
|
|
148
|
+
if m is None or m.size == 0: return None, None, None
|
|
149
|
+
m = m.astype(np.float32, copy=False)
|
|
150
|
+
if m.dtype.kind in "ui":
|
|
151
|
+
m /= float(np.iinfo(m.dtype).max)
|
|
152
|
+
else:
|
|
153
|
+
mx = float(m.max()) if m.size else 1.0
|
|
154
|
+
if mx > 1.0: m /= mx
|
|
155
|
+
return np.clip(m, 0.0, 1.0), mid, getattr(layer, "name", "Mask")
|
|
156
|
+
|
|
157
|
+
def _resample_mask(mask: np.ndarray, out_hw: tuple[int,int]) -> np.ndarray:
|
|
158
|
+
mh, mw = mask.shape[:2]
|
|
159
|
+
th, tw = out_hw
|
|
160
|
+
if (mh, mw) == (th, tw): return mask
|
|
161
|
+
yi = np.linspace(0, mh - 1, th).astype(np.int32)
|
|
162
|
+
xi = np.linspace(0, mw - 1, tw).astype(np.int32)
|
|
163
|
+
return mask[yi][:, xi]
|
|
164
|
+
|
|
165
|
+
def _blend_with_mask(processed: np.ndarray, src: np.ndarray, doc) -> np.ndarray:
|
|
166
|
+
mask, _, _ = _active_mask_layer(doc)
|
|
167
|
+
if mask is None:
|
|
168
|
+
return processed
|
|
169
|
+
out = processed.astype(np.float32, copy=False)
|
|
170
|
+
m = _resample_mask(mask, out.shape[:2])
|
|
171
|
+
if out.ndim == 3 and out.shape[2] == 3:
|
|
172
|
+
m = m[..., None]
|
|
173
|
+
s = src
|
|
174
|
+
if s.ndim == 2 and out.ndim == 3:
|
|
175
|
+
s = np.stack([s]*3, axis=-1)
|
|
176
|
+
elif s.ndim == 3 and out.ndim == 2:
|
|
177
|
+
s = s[..., 0]
|
|
178
|
+
return (m * out + (1.0 - m) * s).astype(np.float32, copy=False)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ---------- headless apply ----------
|
|
182
|
+
|
|
183
|
+
def apply_ghs_via_preset(main_window, doc, preset: Dict):
|
|
184
|
+
"""
|
|
185
|
+
Headless Universal Hyperbolic Stretch:
|
|
186
|
+
- builds the curve from α/β/γ + pivot + LP/HP
|
|
187
|
+
- applies to K/R/G/B channel
|
|
188
|
+
- blends with active mask if any
|
|
189
|
+
- commits to document with metadata
|
|
190
|
+
"""
|
|
191
|
+
img = getattr(doc, "image", None)
|
|
192
|
+
if img is None:
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
arr = np.asarray(img)
|
|
196
|
+
|
|
197
|
+
# normalize to float01
|
|
198
|
+
if arr.dtype.kind in "ui":
|
|
199
|
+
src01 = arr.astype(np.float32) / np.iinfo(arr.dtype).max
|
|
200
|
+
elif arr.dtype.kind == "f":
|
|
201
|
+
mx = float(arr.max()) if arr.size else 1.0
|
|
202
|
+
src01 = (arr / (mx if mx > 1.0 else 1.0)).astype(np.float32)
|
|
203
|
+
else:
|
|
204
|
+
src01 = arr.astype(np.float32)
|
|
205
|
+
|
|
206
|
+
lut01, channel, params = _lut_from_ghs_preset(preset or {})
|
|
207
|
+
out01 = _apply_mode_any(src01, channel, lut01)
|
|
208
|
+
|
|
209
|
+
# 🔁 Remember this as the last headless command for Replay
|
|
210
|
+
try:
|
|
211
|
+
remember = getattr(main_window, "remember_last_headless_command", None)
|
|
212
|
+
if remember is None:
|
|
213
|
+
remember = getattr(main_window, "_remember_last_headless_command", None)
|
|
214
|
+
|
|
215
|
+
if callable(remember):
|
|
216
|
+
# store the sanitized params we just used
|
|
217
|
+
remember("ghs", params, description="Hyperbolic Stretch")
|
|
218
|
+
|
|
219
|
+
# optional debug
|
|
220
|
+
try:
|
|
221
|
+
if hasattr(main_window, "_log"):
|
|
222
|
+
main_window._log(
|
|
223
|
+
f"[Replay] GHS headless stored: command_id='ghs', "
|
|
224
|
+
f"preset_keys={list(params.keys())}"
|
|
225
|
+
)
|
|
226
|
+
except Exception:
|
|
227
|
+
pass
|
|
228
|
+
except Exception:
|
|
229
|
+
# don’t block the stretch if remembering fails
|
|
230
|
+
pass
|
|
231
|
+
|
|
232
|
+
# Mask-aware blend, same semantics as dialog
|
|
233
|
+
mask, mid, mname = _active_mask_layer(doc)
|
|
234
|
+
if mask is not None:
|
|
235
|
+
out01 = _blend_with_mask(out01, src01, doc)
|
|
236
|
+
|
|
237
|
+
meta = {
|
|
238
|
+
"step_name": "Hyperbolic Stretch",
|
|
239
|
+
"ghs": params,
|
|
240
|
+
"masked": bool(mid),
|
|
241
|
+
"mask_id": mid,
|
|
242
|
+
"mask_name": mname,
|
|
243
|
+
"mask_blend": "m*out + (1-m)*src",
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
doc.apply_edit(
|
|
247
|
+
out01.astype(np.float32, copy=False),
|
|
248
|
+
metadata=meta,
|
|
249
|
+
step_name="Hyperbolic Stretch",
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# ---------- open dialog seeded from preset ----------
|
|
256
|
+
|
|
257
|
+
def open_ghs_with_preset(main_window, preset: Dict | None = None):
|
|
258
|
+
# find active document
|
|
259
|
+
dm = getattr(main_window, "doc_manager", getattr(main_window, "docman", None))
|
|
260
|
+
doc = dm.get_active_document() if (dm and hasattr(dm, "get_active_document")) else getattr(dm, "active_document", None)
|
|
261
|
+
if doc is None:
|
|
262
|
+
return
|
|
263
|
+
|
|
264
|
+
from setiastro.saspro.ghs_dialog_pro import GhsDialogPro
|
|
265
|
+
dlg = GhsDialogPro(main_window, doc)
|
|
266
|
+
|
|
267
|
+
p = dict(preset or {})
|
|
268
|
+
# sliders use integer storage: α: *50, β:*50, γ:*100; LP/HP:*360
|
|
269
|
+
try:
|
|
270
|
+
dlg.sA.setValue(int(np.clip(float(p.get("alpha", 1.0)) * 50.0, 1, 500)))
|
|
271
|
+
dlg.sB.setValue(int(np.clip(float(p.get("beta", 1.0)) * 50.0, 1, 500)))
|
|
272
|
+
dlg.sG.setValue(int(np.clip(float(p.get("gamma", 1.0)) * 100.0, 1, 500)))
|
|
273
|
+
dlg.sLP.setValue(int(np.clip(float(p.get("lp", 0.0)) * 360.0, 0, 360)))
|
|
274
|
+
dlg.sHP.setValue(int(np.clip(float(p.get("hp", 0.0)) * 360.0, 0, 360)))
|
|
275
|
+
ch = _norm_channel(p.get("channel", "K (Brightness)"))
|
|
276
|
+
i = dlg.cmb_ch.findText(ch); dlg.cmb_ch.setCurrentIndex(i if i >= 0 else 0)
|
|
277
|
+
pv = float(np.clip(p.get("pivot", 0.5), 0.0, 1.0))
|
|
278
|
+
dlg._sym_u = pv
|
|
279
|
+
dlg.editor.setSymmetryPoint(pv * 360.0, 0)
|
|
280
|
+
dlg._rebuild_from_params()
|
|
281
|
+
except Exception:
|
|
282
|
+
pass
|
|
283
|
+
|
|
284
|
+
dlg.show(); dlg.raise_(); dlg.activateWindow()
|