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,375 @@
|
|
|
1
|
+
# pro/curves_preset.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import List, Tuple, Dict
|
|
4
|
+
import numpy as np
|
|
5
|
+
import json
|
|
6
|
+
from PyQt6.QtCore import QSettings
|
|
7
|
+
# optional PCHIP (same as editor)
|
|
8
|
+
try:
|
|
9
|
+
from scipy.interpolate import PchipInterpolator as _PCHIP
|
|
10
|
+
_HAS_PCHIP = True
|
|
11
|
+
except Exception:
|
|
12
|
+
_HAS_PCHIP = False
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# ---------------------- preset schema ----------------------
|
|
17
|
+
# {
|
|
18
|
+
# "mode": "K (Brightness)" | "R" | "G" | "B" | "L*" | "a*" | "b*" | "Chroma" | "Saturation" | aliases ("rgb","k","lum"…),
|
|
19
|
+
# "shape": "linear" | "s_mild" | "s_med" | "s_strong" | "lift_shadows" | "crush_shadows"
|
|
20
|
+
# | "fade_blacks" | "rolloff_highlights" | "flatten" | "custom",
|
|
21
|
+
# "amount": 0..1 (intensity, ignored for custom),
|
|
22
|
+
# "points_norm": [[x,y], ...] # optional when shape="custom" (normalized 0..1 domain/range)
|
|
23
|
+
# }
|
|
24
|
+
#
|
|
25
|
+
# Default if missing: mode="K (Brightness)", shape="linear", amount=0.5
|
|
26
|
+
|
|
27
|
+
# ---------------------- shape library (normalized) ----------------------
|
|
28
|
+
def _shape_points_norm(shape: str, amount: float) -> List[Tuple[float, float]]:
|
|
29
|
+
a = float(max(0.0, min(1.0, amount)))
|
|
30
|
+
s = shape.lower()
|
|
31
|
+
|
|
32
|
+
# identity
|
|
33
|
+
if s in ("linear", "none", "id"):
|
|
34
|
+
return [(0.0, 0.0), (1.0, 1.0)]
|
|
35
|
+
|
|
36
|
+
# simple parametric S-curves (mid anchored)
|
|
37
|
+
if s in ("s", "s_mild", "s-curve-mild"):
|
|
38
|
+
k = 0.15 * a
|
|
39
|
+
return [(0.0, 0.0), (0.25, max(0.0, 0.25 - k)), (0.5, 0.5), (0.75, min(1.0, 0.75 + k)), (1.0, 1.0)]
|
|
40
|
+
if s in ("s_med", "s-curve-med", "s-curve"):
|
|
41
|
+
k = 0.25 * a
|
|
42
|
+
return [(0.0, 0.0), (0.25, max(0.0, 0.25 - k)), (0.5, 0.5), (0.75, min(1.0, 0.75 + k)), (1.0, 1.0)]
|
|
43
|
+
if s in ("s_strong", "s-curve-strong"):
|
|
44
|
+
k = 0.36 * a
|
|
45
|
+
return [(0.0, 0.0), (0.25, max(0.0, 0.25 - k)), (0.5, 0.5), (0.75, min(1.0, 0.75 + k)), (1.0, 1.0)]
|
|
46
|
+
|
|
47
|
+
# lift/crush shadows, fade blacks, highlight roll-off, flatten
|
|
48
|
+
if s == "lift_shadows":
|
|
49
|
+
k = 0.35 * a
|
|
50
|
+
return [(0.0, k), (0.3, k + 0.25*a), (1.0, 1.0)]
|
|
51
|
+
if s == "crush_shadows":
|
|
52
|
+
k = 0.35 * a
|
|
53
|
+
return [(0.0, 0.0), (0.3, max(0.0, 0.3 - k)), (1.0, 1.0)]
|
|
54
|
+
if s == "fade_blacks":
|
|
55
|
+
k = 0.25 * a
|
|
56
|
+
return [(0.0, k), (0.2, k*0.8), (0.6, 0.6 + 0.15*a), (1.0, 1.0)]
|
|
57
|
+
if s == "rolloff_highlights":
|
|
58
|
+
k = 0.35 * a
|
|
59
|
+
return [(0.0, 0.0), (0.6, 0.6 + 0.15*a), (1.0, 1.0 - k)]
|
|
60
|
+
if s == "flatten":
|
|
61
|
+
k = 0.40 * a
|
|
62
|
+
return [(0.0, 0.0 + 0.25*k), (0.25, 0.35), (0.5, 0.5), (0.75, 0.65), (1.0, 1.0 - 0.25*k)]
|
|
63
|
+
|
|
64
|
+
# default
|
|
65
|
+
return [(0.0, 0.0), (1.0, 1.0)]
|
|
66
|
+
|
|
67
|
+
def _norm_mode(m: str | None) -> str:
|
|
68
|
+
m = (m or "K (Brightness)").strip().lower()
|
|
69
|
+
alias = {
|
|
70
|
+
"k": "K (Brightness)",
|
|
71
|
+
"brightness": "K (Brightness)",
|
|
72
|
+
"rgb": "K (Brightness)",
|
|
73
|
+
"lum": "L*",
|
|
74
|
+
"l": "L*",
|
|
75
|
+
"lab_l": "L*",
|
|
76
|
+
"lab_a": "a*",
|
|
77
|
+
"lab_b": "b*",
|
|
78
|
+
"chroma": "Chroma",
|
|
79
|
+
"sat": "Saturation",
|
|
80
|
+
"s": "Saturation",
|
|
81
|
+
"r": "R", "g": "G", "b": "B",
|
|
82
|
+
}
|
|
83
|
+
# already proper label?
|
|
84
|
+
proper = {"k (brightness)":"K (Brightness)","r":"R","g":"G","b":"B",
|
|
85
|
+
"l*":"L*","a*":"a*","b*":"b*","chroma":"Chroma","saturation":"Saturation"}
|
|
86
|
+
if m in proper: return proper[m]
|
|
87
|
+
return alias.get(m, "K (Brightness)")
|
|
88
|
+
|
|
89
|
+
def _points_norm_to_scene(points_norm: List[Tuple[float, float]]) -> List[Tuple[float, float]]:
|
|
90
|
+
"""
|
|
91
|
+
normalized (x:[0..1], y:[0..1] up-is-positive) -> scene coords (x:[0..360], y:[0..360] down-is-positive)
|
|
92
|
+
"""
|
|
93
|
+
pts = []
|
|
94
|
+
for x, y in points_norm:
|
|
95
|
+
x = float(max(0.0, min(1.0, x)))
|
|
96
|
+
y = float(max(0.0, min(1.0, y)))
|
|
97
|
+
xs = 360.0 * x
|
|
98
|
+
ys = 360.0 * (1.0 - y) # invert Y for scene
|
|
99
|
+
pts.append((xs, ys))
|
|
100
|
+
# ensure endpoints exist
|
|
101
|
+
if not any(abs(px - 0.0) < 1e-6 for px, _ in pts): pts.append((0.0, 360.0))
|
|
102
|
+
if not any(abs(px - 360.0) < 1e-6 for px, _ in pts): pts.append((360.0, 0.0))
|
|
103
|
+
# x strictly increasing
|
|
104
|
+
pts = sorted(pts, key=lambda t: t[0])
|
|
105
|
+
out = []
|
|
106
|
+
lastx = -1e9
|
|
107
|
+
for x, y in pts:
|
|
108
|
+
if x <= lastx: x = lastx + 1e-3
|
|
109
|
+
out.append((min(360.0, max(0.0, x)), min(360.0, max(0.0, y))))
|
|
110
|
+
lastx = out[-1][0]
|
|
111
|
+
return out
|
|
112
|
+
|
|
113
|
+
def build_curve_lut(curve_func, size=65536):
|
|
114
|
+
"""Map v∈[0..1] → y∈[0..1] using a curve defined on x∈[0..360] (scene coords)."""
|
|
115
|
+
x = np.linspace(0.0, 360.0, size, dtype=np.float32)
|
|
116
|
+
y = 360.0 - curve_func(x)
|
|
117
|
+
y = (y / 360.0).clip(0.0, 1.0).astype(np.float32)
|
|
118
|
+
return y
|
|
119
|
+
|
|
120
|
+
def _interpolator_from_scene_points(points_scene: List[Tuple[float, float]]):
|
|
121
|
+
xs = np.array([p[0] for p in points_scene], dtype=np.float64)
|
|
122
|
+
ys = np.array([p[1] for p in points_scene], dtype=np.float64)
|
|
123
|
+
if _HAS_PCHIP and xs.size >= 2:
|
|
124
|
+
return _PCHIP(xs, ys)
|
|
125
|
+
# fallback
|
|
126
|
+
def _lin(x):
|
|
127
|
+
return np.interp(x, xs, ys)
|
|
128
|
+
return _lin
|
|
129
|
+
|
|
130
|
+
def _lut_from_preset(preset: Dict) -> tuple[np.ndarray, str]:
|
|
131
|
+
"""
|
|
132
|
+
Build LUT from any compatible preset / last-action dict.
|
|
133
|
+
"""
|
|
134
|
+
pts_scene = _scene_points_from_preset(preset or {})
|
|
135
|
+
fn = _interpolator_from_scene_points(pts_scene)
|
|
136
|
+
lut01 = build_curve_lut(fn, size=65536)
|
|
137
|
+
mode = _norm_mode((preset or {}).get("mode"))
|
|
138
|
+
return lut01, mode
|
|
139
|
+
|
|
140
|
+
def _unwrap_preset_dict(preset: Dict) -> Dict:
|
|
141
|
+
"""
|
|
142
|
+
Accept a variety of containers and peel down to the actual curve data.
|
|
143
|
+
|
|
144
|
+
Examples we handle:
|
|
145
|
+
{"step_name":"Curves","mode":..., "preset":{...}}
|
|
146
|
+
{"curves": {...}}
|
|
147
|
+
{"state": {...}} # if state contains curve points
|
|
148
|
+
"""
|
|
149
|
+
p = dict(preset or {})
|
|
150
|
+
|
|
151
|
+
# Case 1: full metadata from doc.apply_edit: {"step_name":"Curves", "mode":..., "preset": {...}}
|
|
152
|
+
inner = p.get("preset")
|
|
153
|
+
if isinstance(inner, dict) and ("points_norm" in inner or "handles" in inner
|
|
154
|
+
or "points_scene" in inner or "scene_points" in inner):
|
|
155
|
+
return inner
|
|
156
|
+
|
|
157
|
+
# Case 2: payloads like {"curves": {...}}
|
|
158
|
+
inner = p.get("curves")
|
|
159
|
+
if isinstance(inner, dict) and ("points_norm" in inner or "handles" in inner
|
|
160
|
+
or "points_scene" in inner or "scene_points" in inner):
|
|
161
|
+
return inner
|
|
162
|
+
|
|
163
|
+
# Case 3: {"state": {...}} (if you stored the curve state under that key)
|
|
164
|
+
inner = p.get("state")
|
|
165
|
+
if isinstance(inner, dict) and ("points_norm" in inner or "handles" in inner
|
|
166
|
+
or "points_scene" in inner or "scene_points" in inner):
|
|
167
|
+
return inner
|
|
168
|
+
|
|
169
|
+
# Otherwise assume p already *is* the preset
|
|
170
|
+
return p
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
_SETTINGS_KEY = "curves/custom_presets_v1"
|
|
174
|
+
|
|
175
|
+
def _settings() -> QSettings | None:
|
|
176
|
+
try:
|
|
177
|
+
return QSettings()
|
|
178
|
+
except Exception:
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
def list_custom_presets() -> list[dict]:
|
|
182
|
+
"""Return a list of dicts: {"name", "mode", "shape":"custom", "points_norm":[[x,y],...]}"""
|
|
183
|
+
s = _settings()
|
|
184
|
+
if not s:
|
|
185
|
+
return []
|
|
186
|
+
raw = s.value(_SETTINGS_KEY, "", type=str) or ""
|
|
187
|
+
try:
|
|
188
|
+
lst = json.loads(raw)
|
|
189
|
+
if isinstance(lst, list):
|
|
190
|
+
return [p for p in lst if isinstance(p, dict)]
|
|
191
|
+
except Exception:
|
|
192
|
+
pass
|
|
193
|
+
return []
|
|
194
|
+
|
|
195
|
+
def save_custom_preset(name: str, mode: str, points_norm: list[tuple[float,float]]) -> bool:
|
|
196
|
+
"""Create/overwrite by name."""
|
|
197
|
+
s = _settings()
|
|
198
|
+
if not s:
|
|
199
|
+
return False
|
|
200
|
+
name = (name or "").strip()
|
|
201
|
+
if not name:
|
|
202
|
+
return False
|
|
203
|
+
preset = {
|
|
204
|
+
"name": name,
|
|
205
|
+
"mode": _norm_mode(mode),
|
|
206
|
+
"shape": "custom",
|
|
207
|
+
"amount": 1.0,
|
|
208
|
+
"points_norm": [(float(x), float(y)) for (x, y) in points_norm],
|
|
209
|
+
}
|
|
210
|
+
lst = list_custom_presets()
|
|
211
|
+
lst = [p for p in lst if (p.get("name","").lower() != name.lower())]
|
|
212
|
+
lst.append(preset)
|
|
213
|
+
s.setValue(_SETTINGS_KEY, json.dumps(lst))
|
|
214
|
+
s.sync()
|
|
215
|
+
return True
|
|
216
|
+
|
|
217
|
+
def delete_custom_preset(name: str) -> bool:
|
|
218
|
+
s = _settings()
|
|
219
|
+
if not s:
|
|
220
|
+
return False
|
|
221
|
+
lst = list_custom_presets()
|
|
222
|
+
lst = [p for p in lst if (p.get("name","").lower() != (name or "").strip().lower())]
|
|
223
|
+
s.setValue(_SETTINGS_KEY, json.dumps(lst))
|
|
224
|
+
s.sync()
|
|
225
|
+
return True
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# ---------------------- headless apply ----------------------
|
|
229
|
+
def apply_curves_via_preset(main_window, doc, preset: Dict):
|
|
230
|
+
import numpy as _np
|
|
231
|
+
from setiastro.saspro.curves_preset import _lut_from_preset, _unwrap_preset_dict # self
|
|
232
|
+
# lazy import to avoid cycle
|
|
233
|
+
from setiastro.saspro.curve_editor_pro import _apply_mode_any
|
|
234
|
+
|
|
235
|
+
img = getattr(doc, "image", None)
|
|
236
|
+
if img is None:
|
|
237
|
+
return
|
|
238
|
+
|
|
239
|
+
# Accept full last-action dicts and unwrap down to the actual curve definition
|
|
240
|
+
core_preset = _unwrap_preset_dict(preset or {})
|
|
241
|
+
|
|
242
|
+
arr = _np.asarray(img)
|
|
243
|
+
if arr.dtype.kind in "ui":
|
|
244
|
+
arr01 = arr.astype(_np.float32) / _np.iinfo(arr.dtype).max
|
|
245
|
+
elif arr.dtype.kind == "f":
|
|
246
|
+
mx = float(arr.max()) if arr.size else 1.0
|
|
247
|
+
arr01 = (arr / (mx if mx > 1.0 else 1.0)).astype(_np.float32)
|
|
248
|
+
else:
|
|
249
|
+
arr01 = arr.astype(_np.float32)
|
|
250
|
+
|
|
251
|
+
lut01, mode = _lut_from_preset(core_preset)
|
|
252
|
+
out01 = _apply_mode_any(arr01, mode, lut01)
|
|
253
|
+
|
|
254
|
+
meta = {
|
|
255
|
+
"step_name": "Curves",
|
|
256
|
+
"mode": mode,
|
|
257
|
+
"preset": dict(core_preset), # store the normalized core preset
|
|
258
|
+
}
|
|
259
|
+
doc.apply_edit(out01, metadata=meta, step_name="Curves")
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
# ---------------------- open UI with preset ----------------------
|
|
263
|
+
# ---------------------- open UI with preset ----------------------
|
|
264
|
+
def open_curves_with_preset(main_window, preset: Dict | None = None):
|
|
265
|
+
# lazy import UI to avoid cycle
|
|
266
|
+
from setiastro.saspro.curve_editor_pro import CurvesDialogPro
|
|
267
|
+
|
|
268
|
+
dm = getattr(main_window, "doc_manager", getattr(main_window, "docman", None))
|
|
269
|
+
if dm is None:
|
|
270
|
+
return
|
|
271
|
+
doc = dm.get_active_document() if hasattr(dm, "get_active_document") else getattr(dm, "active_document", None)
|
|
272
|
+
if doc is None:
|
|
273
|
+
return
|
|
274
|
+
|
|
275
|
+
dlg = CurvesDialogPro(main_window, doc)
|
|
276
|
+
|
|
277
|
+
# Peel down any wrapper (metadata / last-action container) to the actual curve definition
|
|
278
|
+
core_preset = _unwrap_preset_dict(preset or {})
|
|
279
|
+
|
|
280
|
+
# set mode radio from the *core* preset
|
|
281
|
+
want = _norm_mode(core_preset.get("mode"))
|
|
282
|
+
for b in dlg.mode_group.buttons():
|
|
283
|
+
if b.text().lower() == want.lower():
|
|
284
|
+
b.setChecked(True)
|
|
285
|
+
break
|
|
286
|
+
|
|
287
|
+
# Seed control handles from the same logic used by LUT building
|
|
288
|
+
pts_scene = _scene_points_from_preset(core_preset)
|
|
289
|
+
|
|
290
|
+
# remove exact endpoints if present; editor expects control handles only
|
|
291
|
+
filt = [(x, y) for (x, y) in pts_scene if x > 0.0 + 1e-6 and x < 360.0 - 1e-6]
|
|
292
|
+
dlg.editor.setControlHandles(filt)
|
|
293
|
+
|
|
294
|
+
dlg.show()
|
|
295
|
+
dlg.raise_()
|
|
296
|
+
dlg.activateWindow()
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _sanitize_scene_points(points_scene: List[Tuple[float, float]]) -> List[Tuple[float, float]]:
|
|
300
|
+
"""
|
|
301
|
+
Take scene-space points (x,y in [0..360]) and:
|
|
302
|
+
- clamp to [0..360]
|
|
303
|
+
- ensure endpoints at x=0 and x=360 exist
|
|
304
|
+
- enforce strictly increasing x
|
|
305
|
+
"""
|
|
306
|
+
pts = []
|
|
307
|
+
for x, y in points_scene:
|
|
308
|
+
xs = float(x)
|
|
309
|
+
ys = float(y)
|
|
310
|
+
xs = min(360.0, max(0.0, xs))
|
|
311
|
+
ys = min(360.0, max(0.0, ys))
|
|
312
|
+
pts.append((xs, ys))
|
|
313
|
+
|
|
314
|
+
# ensure endpoints exist
|
|
315
|
+
if not any(abs(px - 0.0) < 1e-6 for px, _ in pts):
|
|
316
|
+
pts.append((0.0, 360.0))
|
|
317
|
+
if not any(abs(px - 360.0) < 1e-6 for px, _ in pts):
|
|
318
|
+
pts.append((360.0, 0.0))
|
|
319
|
+
|
|
320
|
+
# x strictly increasing
|
|
321
|
+
pts = sorted(pts, key=lambda t: t[0])
|
|
322
|
+
out: List[Tuple[float, float]] = []
|
|
323
|
+
lastx = -1e9
|
|
324
|
+
for x, y in pts:
|
|
325
|
+
if x <= lastx:
|
|
326
|
+
x = lastx + 1e-3
|
|
327
|
+
out.append((min(360.0, max(0.0, x)), min(360.0, max(0.0, y))))
|
|
328
|
+
lastx = out[-1][0]
|
|
329
|
+
return out
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _scene_points_from_preset(preset: Dict) -> List[Tuple[float, float]]:
|
|
333
|
+
"""
|
|
334
|
+
Accepts any of:
|
|
335
|
+
- preset["points_scene"] / preset["scene_points"]
|
|
336
|
+
→ list of [x,y] or {"x":..,"y":..} in scene coords
|
|
337
|
+
- preset["handles"] / preset["control_points"]
|
|
338
|
+
→ same as above, in scene coords (what the editor stores)
|
|
339
|
+
- preset["points_norm"]
|
|
340
|
+
→ normalized [0..1] points
|
|
341
|
+
- otherwise falls back to shape/amount library
|
|
342
|
+
Returns a sanitized list of scene-space points.
|
|
343
|
+
"""
|
|
344
|
+
p = preset or {}
|
|
345
|
+
|
|
346
|
+
# 1) explicit scene coords from several possible keys
|
|
347
|
+
for key in ("points_scene", "scene_points", "handles", "control_points"):
|
|
348
|
+
raw = p.get(key)
|
|
349
|
+
if isinstance(raw, (list, tuple)) and len(raw) >= 2:
|
|
350
|
+
pts: List[Tuple[float, float]] = []
|
|
351
|
+
for entry in raw:
|
|
352
|
+
if isinstance(entry, (list, tuple)) and len(entry) >= 2:
|
|
353
|
+
x, y = entry[0], entry[1]
|
|
354
|
+
elif isinstance(entry, dict):
|
|
355
|
+
x, y = entry.get("x"), entry.get("y")
|
|
356
|
+
else:
|
|
357
|
+
continue
|
|
358
|
+
if x is None or y is None:
|
|
359
|
+
continue
|
|
360
|
+
pts.append((float(x), float(y)))
|
|
361
|
+
if pts:
|
|
362
|
+
return _sanitize_scene_points(pts)
|
|
363
|
+
|
|
364
|
+
# 2) normalized points (what we already support)
|
|
365
|
+
shape = str(p.get("shape", "linear"))
|
|
366
|
+
amount = float(p.get("amount", 0.5))
|
|
367
|
+
ptsN = p.get("points_norm")
|
|
368
|
+
|
|
369
|
+
if isinstance(ptsN, (list, tuple)) and len(ptsN) >= 2:
|
|
370
|
+
pts_norm = [(float(x), float(y)) for (x, y) in ptsN]
|
|
371
|
+
else:
|
|
372
|
+
pts_norm = _shape_points_norm(shape, amount)
|
|
373
|
+
|
|
374
|
+
return _points_norm_to_scene(pts_norm)
|
|
375
|
+
|