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
|
@@ -521,32 +521,6 @@ class CurveEditor(QGraphicsView):
|
|
|
521
521
|
if ln is not None:
|
|
522
522
|
ln.setVisible(False)
|
|
523
523
|
|
|
524
|
-
def _scene_to_norm_points(self, pts_scene: list[tuple[float,float]]) -> list[tuple[float,float]]:
|
|
525
|
-
"""(x:[0..360], y:[0..360] down) → (x,y in [0..1] up). Ensures endpoints present & strictly increasing x."""
|
|
526
|
-
out = []
|
|
527
|
-
lastx = -1e9
|
|
528
|
-
for (x, y) in sorted(pts_scene, key=lambda t: t[0]):
|
|
529
|
-
x = float(np.clip(x, 0.0, 360.0))
|
|
530
|
-
y = float(np.clip(y, 0.0, 360.0))
|
|
531
|
-
# strictly increasing X
|
|
532
|
-
if x <= lastx:
|
|
533
|
-
x = lastx + 1e-3
|
|
534
|
-
lastx = x
|
|
535
|
-
out.append((x / 360.0, 1.0 - (y / 360.0)))
|
|
536
|
-
# ensure endpoints
|
|
537
|
-
if not any(abs(px - 0.0) < 1e-6 for px, _ in out): out.insert(0, (0.0, 0.0))
|
|
538
|
-
if not any(abs(px - 1.0) < 1e-6 for px, _ in out): out.append((1.0, 1.0))
|
|
539
|
-
# clamp
|
|
540
|
-
return [(float(np.clip(x,0,1)), float(np.clip(y,0,1))) for (x,y) in out]
|
|
541
|
-
|
|
542
|
-
def _collect_points_norm_from_editor(self) -> list[tuple[float,float]]:
|
|
543
|
-
"""Take endpoints+handles from editor => normalized points."""
|
|
544
|
-
pts_scene = []
|
|
545
|
-
for p in (self.editor.end_points + self.editor.control_points):
|
|
546
|
-
pos = p.scenePos()
|
|
547
|
-
pts_scene.append((float(pos.x()), float(pos.y())))
|
|
548
|
-
return self._scene_to_norm_points(pts_scene)
|
|
549
|
-
|
|
550
524
|
|
|
551
525
|
def redistributeHandlesByPivot(self, u: float):
|
|
552
526
|
"""
|
|
@@ -1022,10 +996,18 @@ class CurvesDialogPro(QDialog):
|
|
|
1022
996
|
self._main = parent
|
|
1023
997
|
self.doc = document
|
|
1024
998
|
|
|
1025
|
-
|
|
999
|
+
self._follow_conn = False
|
|
1026
1000
|
if hasattr(self._main, "currentDocumentChanged"):
|
|
1027
|
-
|
|
1028
|
-
|
|
1001
|
+
try:
|
|
1002
|
+
self._main.currentDocumentChanged.connect(self._on_active_doc_changed)
|
|
1003
|
+
self._follow_conn = True
|
|
1004
|
+
except Exception:
|
|
1005
|
+
self._follow_conn = False
|
|
1006
|
+
try:
|
|
1007
|
+
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
|
|
1008
|
+
except Exception:
|
|
1009
|
+
pass # older PyQt6 versions
|
|
1010
|
+
self.finished.connect(self._cleanup_connections)
|
|
1029
1011
|
self._preview_img = None # downsampled float01
|
|
1030
1012
|
self._full_img = None # full-res float01
|
|
1031
1013
|
self._pix = None
|
|
@@ -1040,7 +1022,14 @@ class CurvesDialogPro(QDialog):
|
|
|
1040
1022
|
self._cdf = None
|
|
1041
1023
|
self._cdf_bins = 1024
|
|
1042
1024
|
self._cdf_total = 0
|
|
1043
|
-
|
|
1025
|
+
# Debounce: coalesce rapid curve edits into one rebuild
|
|
1026
|
+
self._curve_debounce_ms = 120 # tweak: 80–200ms feels good
|
|
1027
|
+
self._curve_debounce = QTimer(self)
|
|
1028
|
+
self._curve_debounce.setSingleShot(True)
|
|
1029
|
+
self._curve_debounce.timeout.connect(self._rebuild_preview_from_curve_debounced)
|
|
1030
|
+
|
|
1031
|
+
# Optional: generation counter so stale results can't “win”
|
|
1032
|
+
self._curve_gen = 0
|
|
1044
1033
|
self._clip_scale = 1.0 # preview→full multiplier
|
|
1045
1034
|
self._cdf_total_full = 0 # total pixels in full image (H*W)
|
|
1046
1035
|
self._cdf_total_preview = 0 # total pixels in preview (H*W)
|
|
@@ -1212,18 +1201,34 @@ class CurvesDialogPro(QDialog):
|
|
|
1212
1201
|
|
|
1213
1202
|
def _on_editor_curve_changed(self, _lut8=None):
|
|
1214
1203
|
"""
|
|
1215
|
-
Called on every editor redraw/drag. Persist
|
|
1216
|
-
|
|
1204
|
+
Called on every editor redraw/drag. Persist points and refresh overlays.
|
|
1205
|
+
Preview rebuild is DEBOUNCED to avoid spamming.
|
|
1217
1206
|
"""
|
|
1218
1207
|
try:
|
|
1219
1208
|
self._curves_store[self._current_mode_key] = self._editor_points_norm()
|
|
1220
1209
|
except Exception:
|
|
1221
1210
|
pass
|
|
1222
|
-
|
|
1211
|
+
|
|
1212
|
+
# cheap: overlay redraw is fine every move (or you can debounce this too)
|
|
1223
1213
|
self._refresh_overlays()
|
|
1224
|
-
# now build from *all* current curves (including the just-edited one)
|
|
1225
|
-
self._quick_preview()
|
|
1226
1214
|
|
|
1215
|
+
# expensive: debounce the preview rebuild
|
|
1216
|
+
self._curve_gen += 1
|
|
1217
|
+
self._curve_debounce.start(self._curve_debounce_ms)
|
|
1218
|
+
|
|
1219
|
+
def _rebuild_preview_from_curve_debounced(self):
|
|
1220
|
+
"""
|
|
1221
|
+
Runs after the user pauses dragging for _curve_debounce_ms.
|
|
1222
|
+
Only rebuild if we have images loaded.
|
|
1223
|
+
"""
|
|
1224
|
+
if self._preview_orig is None and self._preview_img is None:
|
|
1225
|
+
return
|
|
1226
|
+
# If your preview toggle is off, you may want to skip:
|
|
1227
|
+
if not getattr(self, "btn_preview", None) or not self.btn_preview.isChecked():
|
|
1228
|
+
return
|
|
1229
|
+
|
|
1230
|
+
# Do the real work (what you were doing before)
|
|
1231
|
+
self._quick_preview()
|
|
1227
1232
|
|
|
1228
1233
|
def _active_mode_key(self) -> str:
|
|
1229
1234
|
for b in self.mode_group.buttons():
|
|
@@ -1670,29 +1675,53 @@ class CurvesDialogPro(QDialog):
|
|
|
1670
1675
|
|
|
1671
1676
|
# 1) Put this helper inside CurvesDialogPro (near other helpers)
|
|
1672
1677
|
def _map_label_xy_to_image_ij(self, x: float, y: float):
|
|
1673
|
-
"""
|
|
1678
|
+
"""
|
|
1679
|
+
Map label-local coords (x,y) to _preview_img pixel (ix, iy).
|
|
1680
|
+
Correct even when the pixmap is centered inside a larger label.
|
|
1681
|
+
Returns None if cursor is outside the displayed pixmap area.
|
|
1682
|
+
"""
|
|
1674
1683
|
if self._pix is None:
|
|
1675
1684
|
return None
|
|
1685
|
+
|
|
1676
1686
|
pm_disp = self.label.pixmap()
|
|
1677
1687
|
if pm_disp is None or pm_disp.isNull():
|
|
1678
1688
|
return None
|
|
1679
1689
|
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
disp_w = pm_disp.width() # size of the *displayed* pixmap on the label
|
|
1690
|
+
# Displayed pixmap size (after zoom)
|
|
1691
|
+
disp_w = pm_disp.width()
|
|
1683
1692
|
disp_h = pm_disp.height()
|
|
1684
|
-
|
|
1693
|
+
|
|
1694
|
+
# Label may be bigger -> pixmap is centered with margins
|
|
1695
|
+
lbl_w = self.label.width()
|
|
1696
|
+
lbl_h = self.label.height()
|
|
1697
|
+
|
|
1698
|
+
off_x = max(0, (lbl_w - disp_w) // 2)
|
|
1699
|
+
off_y = max(0, (lbl_h - disp_h) // 2)
|
|
1700
|
+
|
|
1701
|
+
# Remove margins: label-local -> pixmap-local
|
|
1702
|
+
px = float(x) - float(off_x)
|
|
1703
|
+
py = float(y) - float(off_y)
|
|
1704
|
+
|
|
1705
|
+
if px < 0 or py < 0 or px >= disp_w or py >= disp_h:
|
|
1706
|
+
return None # outside actual image area
|
|
1707
|
+
|
|
1708
|
+
# Now convert displayed pixmap pixel -> source preview pixel
|
|
1709
|
+
src_w = self._pix.width()
|
|
1710
|
+
src_h = self._pix.height()
|
|
1711
|
+
if src_w <= 0 or src_h <= 0:
|
|
1685
1712
|
return None
|
|
1686
1713
|
|
|
1687
1714
|
sx = disp_w / float(src_w)
|
|
1688
1715
|
sy = disp_h / float(src_h)
|
|
1689
1716
|
|
|
1690
|
-
ix = int(
|
|
1691
|
-
iy = int(
|
|
1717
|
+
ix = int(px / sx)
|
|
1718
|
+
iy = int(py / sy)
|
|
1719
|
+
|
|
1692
1720
|
if ix < 0 or iy < 0 or ix >= src_w or iy >= src_h:
|
|
1693
1721
|
return None
|
|
1694
1722
|
return ix, iy
|
|
1695
1723
|
|
|
1724
|
+
|
|
1696
1725
|
def _scene_to_norm_points(self, pts_scene: list[tuple[float,float]]) -> list[tuple[float,float]]:
|
|
1697
1726
|
"""(x:[0..360], y:[0..360] down) → (x,y in [0..1] up). Ensures endpoints present & strictly increasing x."""
|
|
1698
1727
|
out = []
|
|
@@ -1740,14 +1769,38 @@ class CurvesDialogPro(QDialog):
|
|
|
1740
1769
|
name, ok = QInputDialog.getText(self, self.tr("Save Curves Preset"), self.tr("Preset name:"))
|
|
1741
1770
|
if not ok or not name.strip():
|
|
1742
1771
|
return
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1772
|
+
name = name.strip()
|
|
1773
|
+
|
|
1774
|
+
# 0) flush active editor -> store (CRITICAL)
|
|
1775
|
+
try:
|
|
1776
|
+
self._curves_store[self._current_mode_key] = self._editor_points_norm()
|
|
1777
|
+
except Exception:
|
|
1778
|
+
pass
|
|
1779
|
+
|
|
1780
|
+
# 1) build a full multi-curve payload
|
|
1781
|
+
modes = {}
|
|
1782
|
+
for k, pts in self._curves_store.items():
|
|
1783
|
+
if not isinstance(pts, (list, tuple)) or len(pts) < 2:
|
|
1784
|
+
continue
|
|
1785
|
+
# ensure floats (QSettings/JSON safety)
|
|
1786
|
+
modes[k] = [(float(x), float(y)) for (x, y) in pts]
|
|
1787
|
+
|
|
1788
|
+
preset = {
|
|
1789
|
+
"name": name,
|
|
1790
|
+
"version": 2,
|
|
1791
|
+
"kind": "curves_multi",
|
|
1792
|
+
"active": self._current_mode_key, # "K", "R", ...
|
|
1793
|
+
"modes": modes,
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
# 2) save via your existing persistence
|
|
1797
|
+
if save_custom_preset(name, preset): # <-- change signature (see section 3)
|
|
1798
|
+
self._set_status(self.tr("Saved preset “{0}”.").format(name))
|
|
1747
1799
|
self._rebuild_presets_menu()
|
|
1748
1800
|
else:
|
|
1749
1801
|
QMessageBox.warning(self, self.tr("Save failed"), self.tr("Could not save preset."))
|
|
1750
1802
|
|
|
1803
|
+
|
|
1751
1804
|
def _rebuild_presets_menu(self):
|
|
1752
1805
|
m = QMenu(self)
|
|
1753
1806
|
# Built-in shapes under K (Brightness)
|
|
@@ -2178,6 +2231,44 @@ class CurvesDialogPro(QDialog):
|
|
|
2178
2231
|
|
|
2179
2232
|
return (m * out + (1.0 - m) * src).astype(np.float32, copy=False)
|
|
2180
2233
|
|
|
2234
|
+
def closeEvent(self, ev):
|
|
2235
|
+
self._cleanup_connections()
|
|
2236
|
+
super().closeEvent(ev)
|
|
2237
|
+
|
|
2238
|
+
def _cleanup_connections(self):
|
|
2239
|
+
# disconnect the "follow active doc" hook
|
|
2240
|
+
try:
|
|
2241
|
+
if self._follow_conn and hasattr(self._main, "currentDocumentChanged"):
|
|
2242
|
+
self._main.currentDocumentChanged.disconnect(self._on_active_doc_changed)
|
|
2243
|
+
except Exception:
|
|
2244
|
+
pass
|
|
2245
|
+
self._follow_conn = False
|
|
2246
|
+
|
|
2247
|
+
# stop/kill any running worker thread(s)
|
|
2248
|
+
try:
|
|
2249
|
+
thr = getattr(self, "_thr", None)
|
|
2250
|
+
if thr is not None:
|
|
2251
|
+
try:
|
|
2252
|
+
thr.requestInterruption()
|
|
2253
|
+
except Exception:
|
|
2254
|
+
pass
|
|
2255
|
+
try:
|
|
2256
|
+
thr.quit()
|
|
2257
|
+
except Exception:
|
|
2258
|
+
pass
|
|
2259
|
+
try:
|
|
2260
|
+
thr.wait(250)
|
|
2261
|
+
except Exception:
|
|
2262
|
+
pass
|
|
2263
|
+
except Exception:
|
|
2264
|
+
pass
|
|
2265
|
+
|
|
2266
|
+
# optional: drop refs that can keep things alive
|
|
2267
|
+
try:
|
|
2268
|
+
self._thr = None
|
|
2269
|
+
except Exception:
|
|
2270
|
+
pass
|
|
2271
|
+
|
|
2181
2272
|
|
|
2182
2273
|
# zoom/pan
|
|
2183
2274
|
def _apply_zoom(self):
|
|
@@ -2333,43 +2424,69 @@ class CurvesDialogPro(QDialog):
|
|
|
2333
2424
|
def _apply_preset_dict(self, preset: dict):
|
|
2334
2425
|
preset = preset or {}
|
|
2335
2426
|
|
|
2336
|
-
#
|
|
2427
|
+
# -------- MULTI-CURVE (v2) --------
|
|
2428
|
+
if preset.get("kind") == "curves_multi" or ("modes" in preset and isinstance(preset.get("modes"), dict)):
|
|
2429
|
+
modes = preset.get("modes", {}) or {}
|
|
2430
|
+
|
|
2431
|
+
# 0) load all curves into store (fill missing keys with linear)
|
|
2432
|
+
for k in self._curves_store.keys():
|
|
2433
|
+
pts = modes.get(k)
|
|
2434
|
+
if isinstance(pts, (list, tuple)) and len(pts) >= 2:
|
|
2435
|
+
self._curves_store[k] = [(float(x), float(y)) for (x, y) in pts]
|
|
2436
|
+
else:
|
|
2437
|
+
self._curves_store[k] = [(0.0, 0.0), (1.0, 1.0)]
|
|
2438
|
+
|
|
2439
|
+
# 1) choose active key (default to K)
|
|
2440
|
+
active = str(preset.get("active") or "K")
|
|
2441
|
+
if active not in self._curves_store:
|
|
2442
|
+
active = "K"
|
|
2443
|
+
self._current_mode_key = active
|
|
2444
|
+
|
|
2445
|
+
# 2) set radio button that corresponds to active key
|
|
2446
|
+
# map internal key -> radio label
|
|
2447
|
+
key_to_label = {v: k for (k, v) in self._mode_key_map.items()} # "K"->"K (Brightness)"
|
|
2448
|
+
want_label = key_to_label.get(active, "K (Brightness)")
|
|
2449
|
+
for b in self.mode_group.buttons():
|
|
2450
|
+
if b.text() == want_label:
|
|
2451
|
+
b.setChecked(True)
|
|
2452
|
+
break
|
|
2453
|
+
|
|
2454
|
+
# 3) push active curve into editor
|
|
2455
|
+
self._editor_set_from_norm(self._curves_store[active])
|
|
2456
|
+
|
|
2457
|
+
# 4) refresh overlays + preview
|
|
2458
|
+
self._refresh_overlays()
|
|
2459
|
+
self._quick_preview()
|
|
2460
|
+
|
|
2461
|
+
self._set_status(self.tr("Preset: {0} [multi]").format(preset.get("name", self.tr("(built-in)"))))
|
|
2462
|
+
return
|
|
2463
|
+
|
|
2464
|
+
# -------- LEGACY SINGLE-CURVE --------
|
|
2465
|
+
# your existing single-curve behavior (slightly adjusted: store it too)
|
|
2337
2466
|
want = _norm_mode(preset.get("mode"))
|
|
2338
2467
|
for b in self.mode_group.buttons():
|
|
2339
2468
|
if b.text().lower() == want.lower():
|
|
2340
2469
|
b.setChecked(True)
|
|
2341
2470
|
break
|
|
2342
2471
|
|
|
2343
|
-
# 2) get points_norm — if absent, build from shape/amount (built-ins)
|
|
2344
2472
|
ptsN = preset.get("points_norm")
|
|
2345
|
-
shape = preset.get("shape")
|
|
2473
|
+
shape = preset.get("shape")
|
|
2346
2474
|
amount = float(preset.get("amount", 1.0))
|
|
2347
2475
|
|
|
2348
2476
|
if not (isinstance(ptsN, (list, tuple)) and len(ptsN) >= 2):
|
|
2349
2477
|
try:
|
|
2350
|
-
# build from a named shape (built-ins); default to linear
|
|
2351
2478
|
ptsN = _shape_points_norm(str(shape or "linear"), amount)
|
|
2352
2479
|
except Exception:
|
|
2353
|
-
ptsN = [(0.0, 0.0), (1.0, 1.0)]
|
|
2480
|
+
ptsN = [(0.0, 0.0), (1.0, 1.0)]
|
|
2354
2481
|
|
|
2355
|
-
|
|
2356
|
-
pts_scene = _points_norm_to_scene(ptsN)
|
|
2357
|
-
filt = [(x, y) for (x, y) in pts_scene if 1e-6 < x < 360.0 - 1e-6]
|
|
2358
|
-
|
|
2359
|
-
if hasattr(self.editor, "clearSymmetryLine"):
|
|
2360
|
-
self.editor.clearSymmetryLine()
|
|
2361
|
-
|
|
2362
|
-
self.editor.setControlHandles(filt)
|
|
2363
|
-
self.editor.updateCurve() # ensure redraw
|
|
2364
|
-
|
|
2365
|
-
# persist into store & refresh
|
|
2482
|
+
self._editor_set_from_norm(ptsN)
|
|
2366
2483
|
self._curves_store[self._current_mode_key] = self._editor_points_norm()
|
|
2367
2484
|
self._refresh_overlays()
|
|
2368
2485
|
self._quick_preview()
|
|
2369
2486
|
|
|
2370
|
-
# 4) status: don’t assume shape exists
|
|
2371
2487
|
shape_tag = f"[{shape}]" if shape else "[custom]"
|
|
2372
|
-
self._set_status(self.tr("Preset: {0} {1}").format(preset.get(
|
|
2488
|
+
self._set_status(self.tr("Preset: {0} {1}").format(preset.get("name", self.tr("(built-in)")), shape_tag))
|
|
2489
|
+
|
|
2373
2490
|
|
|
2374
2491
|
|
|
2375
2492
|
def apply_curves_ops(doc, op: dict):
|