setiastrosuitepro 1.6.4__py3-none-any.whl → 1.7.1.post2__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/TextureClarity.svg +56 -0
- setiastro/images/abeicon.svg +16 -0
- setiastro/images/acv_icon.png +0 -0
- setiastro/images/colorwheel.svg +97 -0
- setiastro/images/cosmic.svg +40 -0
- setiastro/images/cosmicsat.svg +24 -0
- setiastro/images/first_quarter.png +0 -0
- setiastro/images/full_moon.png +0 -0
- setiastro/images/graxpert.svg +19 -0
- setiastro/images/last_quarter.png +0 -0
- setiastro/images/linearfit.svg +32 -0
- setiastro/images/narrowbandnormalization.png +0 -0
- setiastro/images/new_moon.png +0 -0
- setiastro/images/pixelmath.svg +42 -0
- setiastro/images/planetarystacker.png +0 -0
- setiastro/images/waning_crescent_1.png +0 -0
- setiastro/images/waning_crescent_2.png +0 -0
- setiastro/images/waning_crescent_3.png +0 -0
- setiastro/images/waning_crescent_4.png +0 -0
- setiastro/images/waning_crescent_5.png +0 -0
- setiastro/images/waning_gibbous_1.png +0 -0
- setiastro/images/waning_gibbous_2.png +0 -0
- setiastro/images/waning_gibbous_3.png +0 -0
- setiastro/images/waning_gibbous_4.png +0 -0
- setiastro/images/waning_gibbous_5.png +0 -0
- setiastro/images/waxing_crescent_1.png +0 -0
- setiastro/images/waxing_crescent_2.png +0 -0
- setiastro/images/waxing_crescent_3.png +0 -0
- setiastro/images/waxing_crescent_4.png +0 -0
- setiastro/images/waxing_crescent_5.png +0 -0
- setiastro/images/waxing_gibbous_1.png +0 -0
- setiastro/images/waxing_gibbous_2.png +0 -0
- setiastro/images/waxing_gibbous_3.png +0 -0
- setiastro/images/waxing_gibbous_4.png +0 -0
- setiastro/images/waxing_gibbous_5.png +0 -0
- setiastro/qml/ResourceMonitor.qml +84 -82
- setiastro/saspro/__main__.py +20 -1
- setiastro/saspro/_generated/build_info.py +2 -2
- setiastro/saspro/abe.py +37 -4
- setiastro/saspro/aberration_ai.py +364 -33
- setiastro/saspro/aberration_ai_preset.py +29 -3
- setiastro/saspro/acv_exporter.py +379 -0
- setiastro/saspro/add_stars.py +33 -6
- setiastro/saspro/astrospike_python.py +45 -3
- setiastro/saspro/backgroundneutral.py +108 -40
- setiastro/saspro/blemish_blaster.py +4 -1
- setiastro/saspro/blink_comparator_pro.py +150 -55
- setiastro/saspro/clahe.py +4 -1
- setiastro/saspro/continuum_subtract.py +4 -1
- setiastro/saspro/convo.py +13 -7
- setiastro/saspro/cosmicclarity.py +129 -18
- setiastro/saspro/crop_dialog_pro.py +123 -7
- setiastro/saspro/curve_editor_pro.py +181 -64
- setiastro/saspro/curves_preset.py +249 -47
- setiastro/saspro/doc_manager.py +245 -15
- setiastro/saspro/exoplanet_detector.py +120 -28
- setiastro/saspro/frequency_separation.py +1158 -204
- setiastro/saspro/ghs_dialog_pro.py +81 -16
- setiastro/saspro/graxpert.py +1 -0
- setiastro/saspro/gui/main_window.py +706 -264
- setiastro/saspro/gui/mixins/dock_mixin.py +245 -24
- setiastro/saspro/gui/mixins/file_mixin.py +35 -16
- setiastro/saspro/gui/mixins/menu_mixin.py +35 -1
- setiastro/saspro/gui/mixins/theme_mixin.py +160 -14
- setiastro/saspro/gui/mixins/toolbar_mixin.py +499 -24
- setiastro/saspro/gui/mixins/update_mixin.py +138 -36
- setiastro/saspro/gui/mixins/view_mixin.py +42 -0
- setiastro/saspro/halobgon.py +4 -0
- setiastro/saspro/histogram.py +184 -8
- setiastro/saspro/image_combine.py +4 -0
- setiastro/saspro/image_peeker_pro.py +4 -0
- setiastro/saspro/imageops/narrowband_normalization.py +816 -0
- setiastro/saspro/imageops/serloader.py +1345 -0
- setiastro/saspro/imageops/starbasedwhitebalance.py +23 -52
- setiastro/saspro/imageops/stretch.py +582 -62
- setiastro/saspro/isophote.py +4 -0
- setiastro/saspro/layers.py +13 -9
- setiastro/saspro/layers_dock.py +183 -3
- setiastro/saspro/legacy/image_manager.py +154 -20
- setiastro/saspro/legacy/numba_utils.py +68 -48
- setiastro/saspro/legacy/xisf.py +240 -98
- setiastro/saspro/live_stacking.py +203 -82
- setiastro/saspro/luminancerecombine.py +228 -27
- setiastro/saspro/mask_creation.py +174 -15
- setiastro/saspro/mfdeconv.py +113 -35
- setiastro/saspro/mfdeconvcudnn.py +119 -70
- setiastro/saspro/mfdeconvsport.py +112 -35
- setiastro/saspro/morphology.py +4 -0
- setiastro/saspro/multiscale_decomp.py +81 -29
- setiastro/saspro/narrowband_normalization.py +1618 -0
- setiastro/saspro/numba_utils.py +72 -57
- setiastro/saspro/ops/commands.py +18 -18
- setiastro/saspro/ops/script_editor.py +10 -2
- setiastro/saspro/ops/scripts.py +122 -0
- setiastro/saspro/perfect_palette_picker.py +37 -3
- setiastro/saspro/plate_solver.py +84 -49
- setiastro/saspro/psf_viewer.py +119 -37
- setiastro/saspro/remove_green.py +1 -1
- setiastro/saspro/resources.py +73 -0
- setiastro/saspro/rgbalign.py +460 -12
- setiastro/saspro/selective_color.py +4 -1
- setiastro/saspro/ser_stack_config.py +82 -0
- setiastro/saspro/ser_stacker.py +2321 -0
- setiastro/saspro/ser_stacker_dialog.py +1838 -0
- setiastro/saspro/ser_tracking.py +206 -0
- setiastro/saspro/serviewer.py +1625 -0
- setiastro/saspro/sfcc.py +662 -216
- setiastro/saspro/shortcuts.py +171 -33
- setiastro/saspro/signature_insert.py +692 -33
- setiastro/saspro/stacking_suite.py +1347 -485
- setiastro/saspro/star_alignment.py +247 -123
- setiastro/saspro/star_spikes.py +4 -0
- setiastro/saspro/star_stretch.py +38 -3
- setiastro/saspro/stat_stretch.py +892 -129
- setiastro/saspro/subwindow.py +787 -363
- setiastro/saspro/supernovaasteroidhunter.py +1 -1
- setiastro/saspro/texture_clarity.py +593 -0
- setiastro/saspro/wavescale_hdr.py +4 -1
- setiastro/saspro/wavescalede.py +4 -1
- setiastro/saspro/whitebalance.py +84 -12
- setiastro/saspro/widgets/common_utilities.py +28 -21
- setiastro/saspro/widgets/resource_monitor.py +209 -111
- setiastro/saspro/widgets/spinboxes.py +10 -13
- setiastro/saspro/wimi.py +27 -656
- setiastro/saspro/wims.py +13 -3
- setiastro/saspro/xisf.py +101 -11
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/METADATA +4 -2
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/RECORD +132 -87
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/licenses/license.txt +0 -0
|
@@ -12,18 +12,96 @@ except Exception:
|
|
|
12
12
|
_HAS_PCHIP = False
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
|
|
16
15
|
# ---------------------- preset schema ----------------------
|
|
16
|
+
# Legacy (v1 single-curve) preset:
|
|
17
17
|
# {
|
|
18
|
+
# "name": "MyPreset",
|
|
18
19
|
# "mode": "K (Brightness)" | "R" | "G" | "B" | "L*" | "a*" | "b*" | "Chroma" | "Saturation" | aliases ("rgb","k","lum"…),
|
|
19
|
-
# "shape": "linear" | "s_mild" |
|
|
20
|
-
#
|
|
21
|
-
# "
|
|
22
|
-
# "points_norm": [[x,y], ...] # optional when shape="custom" (normalized 0..1 domain/range)
|
|
20
|
+
# "shape": "linear" | "s_mild" | ... | "custom",
|
|
21
|
+
# "amount": 0..1,
|
|
22
|
+
# "points_norm": [[x,y], ...] # normalized 0..1 domain/range
|
|
23
23
|
# }
|
|
24
24
|
#
|
|
25
|
-
#
|
|
25
|
+
# New (v2 multi-curve) preset:
|
|
26
|
+
# {
|
|
27
|
+
# "name": "MyPreset",
|
|
28
|
+
# "kind": "curves_multi",
|
|
29
|
+
# "version": 2,
|
|
30
|
+
# "active": "K" | "R" | "G" | "B" | "L*" | "a*" | "b*" | "Chroma" | "Saturation",
|
|
31
|
+
# "modes": {
|
|
32
|
+
# "K": [[x,y], ...],
|
|
33
|
+
# "R": [[x,y], ...],
|
|
34
|
+
# ...
|
|
35
|
+
# }
|
|
36
|
+
# }
|
|
37
|
+
|
|
38
|
+
_MODE_KEY_TO_LABEL = {
|
|
39
|
+
"K": "K (Brightness)",
|
|
40
|
+
"R": "R",
|
|
41
|
+
"G": "G",
|
|
42
|
+
"B": "B",
|
|
43
|
+
"L*": "L*",
|
|
44
|
+
"a*": "a*",
|
|
45
|
+
"b*": "b*",
|
|
46
|
+
"Chroma": "Chroma",
|
|
47
|
+
"Saturation": "Saturation",
|
|
48
|
+
}
|
|
26
49
|
|
|
50
|
+
|
|
51
|
+
_LABEL_TO_MODE_KEY = {v: k for k, v in _MODE_KEY_TO_LABEL.items()}
|
|
52
|
+
|
|
53
|
+
_APPLY_ORDER_KEYS = ["K", "R", "G", "B", "L*", "a*", "b*", "Chroma", "Saturation"]
|
|
54
|
+
|
|
55
|
+
def _is_linear_points_norm(pts) -> bool:
|
|
56
|
+
try:
|
|
57
|
+
return (
|
|
58
|
+
isinstance(pts, (list, tuple)) and len(pts) == 2
|
|
59
|
+
and tuple(pts[0]) == (0.0, 0.0)
|
|
60
|
+
and tuple(pts[1]) == (1.0, 1.0)
|
|
61
|
+
)
|
|
62
|
+
except Exception:
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
def _coerce_points_norm(raw) -> List[Tuple[float, float]] | None:
|
|
66
|
+
if not isinstance(raw, (list, tuple)) or len(raw) < 2:
|
|
67
|
+
return None
|
|
68
|
+
out: List[Tuple[float, float]] = []
|
|
69
|
+
for e in raw:
|
|
70
|
+
if isinstance(e, (list, tuple)) and len(e) >= 2:
|
|
71
|
+
x, y = e[0], e[1]
|
|
72
|
+
elif isinstance(e, dict):
|
|
73
|
+
x, y = e.get("x"), e.get("y")
|
|
74
|
+
else:
|
|
75
|
+
continue
|
|
76
|
+
if x is None or y is None:
|
|
77
|
+
continue
|
|
78
|
+
out.append((float(x), float(y)))
|
|
79
|
+
return out if len(out) >= 2 else None
|
|
80
|
+
|
|
81
|
+
def _coerce_modes_dict(raw) -> Dict[str, List[Tuple[float, float]]]:
|
|
82
|
+
"""
|
|
83
|
+
Return {mode_key: points_norm}, filtering junk but keeping backward-compat.
|
|
84
|
+
Accepts keys as internal mode keys ("K") or labels ("K (Brightness)").
|
|
85
|
+
"""
|
|
86
|
+
modes: Dict[str, List[Tuple[float, float]]] = {}
|
|
87
|
+
if not isinstance(raw, dict):
|
|
88
|
+
return modes
|
|
89
|
+
|
|
90
|
+
for k, v in raw.items():
|
|
91
|
+
key = str(k)
|
|
92
|
+
# allow labels or keys
|
|
93
|
+
if key in _MODE_KEY_TO_LABEL:
|
|
94
|
+
mode_key = key
|
|
95
|
+
else:
|
|
96
|
+
mode_label = _norm_mode(key) # converts aliases -> proper label
|
|
97
|
+
mode_key = _LABEL_TO_MODE_KEY.get(mode_label, "K")
|
|
98
|
+
|
|
99
|
+
pts = _coerce_points_norm(v)
|
|
100
|
+
if pts is None:
|
|
101
|
+
continue
|
|
102
|
+
modes[mode_key] = pts
|
|
103
|
+
|
|
104
|
+
return modes
|
|
27
105
|
# ---------------------- shape library (normalized) ----------------------
|
|
28
106
|
def _shape_points_norm(shape: str, amount: float) -> List[Tuple[float, float]]:
|
|
29
107
|
a = float(max(0.0, min(1.0, amount)))
|
|
@@ -141,32 +219,38 @@ def _unwrap_preset_dict(preset: Dict) -> Dict:
|
|
|
141
219
|
"""
|
|
142
220
|
Accept a variety of containers and peel down to the actual curve data.
|
|
143
221
|
|
|
144
|
-
|
|
145
|
-
{"
|
|
146
|
-
{"curves": {...}}
|
|
147
|
-
{"state": {...}}
|
|
222
|
+
Handles:
|
|
223
|
+
- {"preset": {...}}
|
|
224
|
+
- {"curves": {...}}
|
|
225
|
+
- {"state": {...}}
|
|
226
|
+
- multi presets with {"modes": {...}}
|
|
148
227
|
"""
|
|
149
228
|
p = dict(preset or {})
|
|
150
229
|
|
|
151
|
-
|
|
230
|
+
def _looks_like_curve_dict(d: dict) -> bool:
|
|
231
|
+
return (
|
|
232
|
+
isinstance(d, dict)
|
|
233
|
+
and (
|
|
234
|
+
"points_norm" in d
|
|
235
|
+
or "handles" in d
|
|
236
|
+
or "points_scene" in d
|
|
237
|
+
or "scene_points" in d
|
|
238
|
+
or "modes" in d # <-- multi
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
|
|
152
242
|
inner = p.get("preset")
|
|
153
|
-
if
|
|
154
|
-
or "points_scene" in inner or "scene_points" in inner):
|
|
243
|
+
if _looks_like_curve_dict(inner):
|
|
155
244
|
return inner
|
|
156
245
|
|
|
157
|
-
# Case 2: payloads like {"curves": {...}}
|
|
158
246
|
inner = p.get("curves")
|
|
159
|
-
if
|
|
160
|
-
or "points_scene" in inner or "scene_points" in inner):
|
|
247
|
+
if _looks_like_curve_dict(inner):
|
|
161
248
|
return inner
|
|
162
249
|
|
|
163
|
-
# Case 3: {"state": {...}} (if you stored the curve state under that key)
|
|
164
250
|
inner = p.get("state")
|
|
165
|
-
if
|
|
166
|
-
or "points_scene" in inner or "scene_points" in inner):
|
|
251
|
+
if _looks_like_curve_dict(inner):
|
|
167
252
|
return inner
|
|
168
253
|
|
|
169
|
-
# Otherwise assume p already *is* the preset
|
|
170
254
|
return p
|
|
171
255
|
|
|
172
256
|
|
|
@@ -179,7 +263,9 @@ def _settings() -> QSettings | None:
|
|
|
179
263
|
return None
|
|
180
264
|
|
|
181
265
|
def list_custom_presets() -> list[dict]:
|
|
182
|
-
"""
|
|
266
|
+
"""
|
|
267
|
+
Returns a list of preset dicts (v1 single-curve or v2 multi-curve).
|
|
268
|
+
"""
|
|
183
269
|
s = _settings()
|
|
184
270
|
if not s:
|
|
185
271
|
return []
|
|
@@ -187,29 +273,82 @@ def list_custom_presets() -> list[dict]:
|
|
|
187
273
|
try:
|
|
188
274
|
lst = json.loads(raw)
|
|
189
275
|
if isinstance(lst, list):
|
|
190
|
-
|
|
276
|
+
out = [p for p in lst if isinstance(p, dict)]
|
|
277
|
+
# normalize missing name fields (defensive)
|
|
278
|
+
for p in out:
|
|
279
|
+
if "name" not in p:
|
|
280
|
+
p["name"] = "(unnamed)"
|
|
281
|
+
return out
|
|
191
282
|
except Exception:
|
|
192
283
|
pass
|
|
193
284
|
return []
|
|
194
285
|
|
|
195
|
-
def save_custom_preset(name: str,
|
|
196
|
-
"""
|
|
286
|
+
def save_custom_preset(name: str, mode_or_preset, points_norm: list[tuple[float, float]] | None = None) -> bool:
|
|
287
|
+
"""
|
|
288
|
+
Save a custom preset. Supports:
|
|
289
|
+
- legacy: save_custom_preset(name, mode, points_norm)
|
|
290
|
+
- new: save_custom_preset(name, preset_dict)
|
|
291
|
+
"""
|
|
197
292
|
s = _settings()
|
|
198
293
|
if not s:
|
|
199
294
|
return False
|
|
295
|
+
|
|
200
296
|
name = (name or "").strip()
|
|
201
297
|
if not name:
|
|
202
298
|
return False
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
299
|
+
|
|
300
|
+
# --- build preset dict ---
|
|
301
|
+
preset: dict
|
|
302
|
+
|
|
303
|
+
if isinstance(mode_or_preset, dict):
|
|
304
|
+
# v2 (or v1 dict passed directly)
|
|
305
|
+
preset = dict(mode_or_preset)
|
|
306
|
+
preset["name"] = name # enforce
|
|
307
|
+
|
|
308
|
+
# If it's a multi preset, sanitize modes
|
|
309
|
+
if preset.get("kind") == "curves_multi" or isinstance(preset.get("modes"), dict):
|
|
310
|
+
modes = _coerce_modes_dict(preset.get("modes", {}))
|
|
311
|
+
# fill missing keys with linear so UI always has a full set
|
|
312
|
+
full_modes = {}
|
|
313
|
+
for k in _MODE_KEY_TO_LABEL.keys():
|
|
314
|
+
full_modes[k] = modes.get(k, [(0.0, 0.0), (1.0, 1.0)])
|
|
315
|
+
preset = {
|
|
316
|
+
"name": name,
|
|
317
|
+
"kind": "curves_multi",
|
|
318
|
+
"version": 2,
|
|
319
|
+
"active": str(preset.get("active") or "K"),
|
|
320
|
+
"modes": {k: [(float(x), float(y)) for (x, y) in v] for k, v in full_modes.items()},
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
else:
|
|
324
|
+
# treat as v1 single-curve dict
|
|
325
|
+
mode_label = _norm_mode(preset.get("mode"))
|
|
326
|
+
pts = _coerce_points_norm(preset.get("points_norm")) or [(0.0, 0.0), (1.0, 1.0)]
|
|
327
|
+
preset = {
|
|
328
|
+
"name": name,
|
|
329
|
+
"mode": mode_label,
|
|
330
|
+
"shape": str(preset.get("shape", "custom")),
|
|
331
|
+
"amount": float(preset.get("amount", 1.0)),
|
|
332
|
+
"points_norm": [(float(x), float(y)) for (x, y) in pts],
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
else:
|
|
336
|
+
# legacy signature
|
|
337
|
+
mode = _norm_mode(str(mode_or_preset))
|
|
338
|
+
pts = points_norm or [(0.0, 0.0), (1.0, 1.0)]
|
|
339
|
+
preset = {
|
|
340
|
+
"name": name,
|
|
341
|
+
"mode": mode,
|
|
342
|
+
"shape": "custom",
|
|
343
|
+
"amount": 1.0,
|
|
344
|
+
"points_norm": [(float(x), float(y)) for (x, y) in pts],
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
# --- upsert by name (case-insensitive) ---
|
|
210
348
|
lst = list_custom_presets()
|
|
211
|
-
lst = [p for p in lst if (p.get("name","").lower() != name.lower())]
|
|
349
|
+
lst = [p for p in lst if (p.get("name", "").lower() != name.lower())]
|
|
212
350
|
lst.append(preset)
|
|
351
|
+
|
|
213
352
|
s.setValue(_SETTINGS_KEY, json.dumps(lst))
|
|
214
353
|
s.sync()
|
|
215
354
|
return True
|
|
@@ -218,8 +357,11 @@ def delete_custom_preset(name: str) -> bool:
|
|
|
218
357
|
s = _settings()
|
|
219
358
|
if not s:
|
|
220
359
|
return False
|
|
360
|
+
nm = (name or "").strip().lower()
|
|
361
|
+
if not nm:
|
|
362
|
+
return False
|
|
221
363
|
lst = list_custom_presets()
|
|
222
|
-
lst = [p for p in lst if (p.get("name",
|
|
364
|
+
lst = [p for p in lst if (p.get("name", "").strip().lower() != nm)]
|
|
223
365
|
s.setValue(_SETTINGS_KEY, json.dumps(lst))
|
|
224
366
|
s.sync()
|
|
225
367
|
return True
|
|
@@ -228,7 +370,7 @@ def delete_custom_preset(name: str) -> bool:
|
|
|
228
370
|
# ---------------------- headless apply ----------------------
|
|
229
371
|
def apply_curves_via_preset(main_window, doc, preset: Dict):
|
|
230
372
|
import numpy as _np
|
|
231
|
-
from setiastro.saspro.curves_preset import
|
|
373
|
+
from setiastro.saspro.curves_preset import _unwrap_preset_dict # self
|
|
232
374
|
# lazy import to avoid cycle
|
|
233
375
|
from setiastro.saspro.curve_editor_pro import _apply_mode_any
|
|
234
376
|
|
|
@@ -236,8 +378,7 @@ def apply_curves_via_preset(main_window, doc, preset: Dict):
|
|
|
236
378
|
if img is None:
|
|
237
379
|
return
|
|
238
380
|
|
|
239
|
-
|
|
240
|
-
core_preset = _unwrap_preset_dict(preset or {})
|
|
381
|
+
core = _unwrap_preset_dict(preset or {})
|
|
241
382
|
|
|
242
383
|
arr = _np.asarray(img)
|
|
243
384
|
if arr.dtype.kind in "ui":
|
|
@@ -248,18 +389,52 @@ def apply_curves_via_preset(main_window, doc, preset: Dict):
|
|
|
248
389
|
else:
|
|
249
390
|
arr01 = arr.astype(_np.float32)
|
|
250
391
|
|
|
251
|
-
|
|
392
|
+
# -------- MULTI --------
|
|
393
|
+
if core.get("kind") == "curves_multi" or isinstance(core.get("modes"), dict):
|
|
394
|
+
modes = _coerce_modes_dict(core.get("modes", {}))
|
|
395
|
+
|
|
396
|
+
out01 = arr01
|
|
397
|
+
used_any = False
|
|
398
|
+
|
|
399
|
+
for mode_key in _APPLY_ORDER_KEYS:
|
|
400
|
+
pts = modes.get(mode_key)
|
|
401
|
+
if not pts:
|
|
402
|
+
continue
|
|
403
|
+
if _is_linear_points_norm(pts):
|
|
404
|
+
continue
|
|
405
|
+
|
|
406
|
+
pts_scene = _points_norm_to_scene(pts)
|
|
407
|
+
fn = _interpolator_from_scene_points(pts_scene)
|
|
408
|
+
lut01 = build_curve_lut(fn, size=65536)
|
|
409
|
+
|
|
410
|
+
mode_label = _MODE_KEY_TO_LABEL.get(mode_key, "K (Brightness)")
|
|
411
|
+
out01 = _apply_mode_any(out01, mode_label, lut01)
|
|
412
|
+
used_any = True
|
|
413
|
+
|
|
414
|
+
if not used_any:
|
|
415
|
+
return
|
|
416
|
+
|
|
417
|
+
meta = {
|
|
418
|
+
"step_name": "Curves",
|
|
419
|
+
"mode": _MODE_KEY_TO_LABEL.get(str(core.get("active") or "K"), "K (Brightness)"),
|
|
420
|
+
"preset": dict(core),
|
|
421
|
+
}
|
|
422
|
+
doc.apply_edit(out01, metadata=meta, step_name="Curves")
|
|
423
|
+
return
|
|
424
|
+
|
|
425
|
+
# -------- LEGACY SINGLE --------
|
|
426
|
+
lut01, mode = _lut_from_preset(core)
|
|
252
427
|
out01 = _apply_mode_any(arr01, mode, lut01)
|
|
253
428
|
|
|
254
429
|
meta = {
|
|
255
430
|
"step_name": "Curves",
|
|
256
431
|
"mode": mode,
|
|
257
|
-
"preset": dict(
|
|
432
|
+
"preset": dict(core),
|
|
258
433
|
}
|
|
259
434
|
doc.apply_edit(out01, metadata=meta, step_name="Curves")
|
|
260
435
|
|
|
261
436
|
|
|
262
|
-
|
|
437
|
+
|
|
263
438
|
# ---------------------- open UI with preset ----------------------
|
|
264
439
|
def open_curves_with_preset(main_window, preset: Dict | None = None):
|
|
265
440
|
# lazy import UI to avoid cycle
|
|
@@ -274,20 +449,47 @@ def open_curves_with_preset(main_window, preset: Dict | None = None):
|
|
|
274
449
|
|
|
275
450
|
dlg = CurvesDialogPro(main_window, doc)
|
|
276
451
|
|
|
277
|
-
|
|
278
|
-
|
|
452
|
+
core = _unwrap_preset_dict(preset or {})
|
|
453
|
+
|
|
454
|
+
# -------- MULTI --------
|
|
455
|
+
if core.get("kind") == "curves_multi" or isinstance(core.get("modes"), dict):
|
|
456
|
+
modes = _coerce_modes_dict(core.get("modes", {}))
|
|
457
|
+
|
|
458
|
+
# Fill dialog store for every key
|
|
459
|
+
for k in getattr(dlg, "_curves_store", {}).keys():
|
|
460
|
+
dlg._curves_store[k] = modes.get(k, [(0.0, 0.0), (1.0, 1.0)])
|
|
461
|
+
|
|
462
|
+
# Choose active key
|
|
463
|
+
active_key = str(core.get("active") or "K")
|
|
464
|
+
if active_key not in dlg._curves_store:
|
|
465
|
+
active_key = "K"
|
|
466
|
+
dlg._current_mode_key = active_key
|
|
467
|
+
|
|
468
|
+
# Set the radio to match active
|
|
469
|
+
want_label = _MODE_KEY_TO_LABEL.get(active_key, "K (Brightness)")
|
|
470
|
+
for b in dlg.mode_group.buttons():
|
|
471
|
+
if b.text().lower() == want_label.lower():
|
|
472
|
+
b.setChecked(True)
|
|
473
|
+
break
|
|
474
|
+
|
|
475
|
+
# Push active curve into editor
|
|
476
|
+
dlg._editor_set_from_norm(dlg._curves_store.get(active_key, [(0.0, 0.0), (1.0, 1.0)]))
|
|
477
|
+
dlg._refresh_overlays()
|
|
478
|
+
dlg._quick_preview()
|
|
479
|
+
|
|
480
|
+
dlg.show()
|
|
481
|
+
dlg.raise_()
|
|
482
|
+
dlg.activateWindow()
|
|
483
|
+
return
|
|
279
484
|
|
|
280
|
-
#
|
|
281
|
-
want = _norm_mode(
|
|
485
|
+
# -------- LEGACY SINGLE --------
|
|
486
|
+
want = _norm_mode(core.get("mode"))
|
|
282
487
|
for b in dlg.mode_group.buttons():
|
|
283
488
|
if b.text().lower() == want.lower():
|
|
284
489
|
b.setChecked(True)
|
|
285
490
|
break
|
|
286
491
|
|
|
287
|
-
|
|
288
|
-
pts_scene = _scene_points_from_preset(core_preset)
|
|
289
|
-
|
|
290
|
-
# remove exact endpoints if present; editor expects control handles only
|
|
492
|
+
pts_scene = _scene_points_from_preset(core)
|
|
291
493
|
filt = [(x, y) for (x, y) in pts_scene if x > 0.0 + 1e-6 and x < 360.0 - 1e-6]
|
|
292
494
|
dlg.editor.setControlHandles(filt)
|
|
293
495
|
|