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.

Files changed (174) hide show
  1. setiastro/__init__.py +2 -0
  2. setiastro/saspro/__init__.py +20 -0
  3. setiastro/saspro/__main__.py +784 -0
  4. setiastro/saspro/_generated/__init__.py +7 -0
  5. setiastro/saspro/_generated/build_info.py +2 -0
  6. setiastro/saspro/abe.py +1295 -0
  7. setiastro/saspro/abe_preset.py +196 -0
  8. setiastro/saspro/aberration_ai.py +694 -0
  9. setiastro/saspro/aberration_ai_preset.py +224 -0
  10. setiastro/saspro/accel_installer.py +218 -0
  11. setiastro/saspro/accel_workers.py +30 -0
  12. setiastro/saspro/add_stars.py +621 -0
  13. setiastro/saspro/astrobin_exporter.py +1007 -0
  14. setiastro/saspro/astrospike.py +153 -0
  15. setiastro/saspro/astrospike_python.py +1839 -0
  16. setiastro/saspro/autostretch.py +196 -0
  17. setiastro/saspro/backgroundneutral.py +560 -0
  18. setiastro/saspro/batch_convert.py +325 -0
  19. setiastro/saspro/batch_renamer.py +519 -0
  20. setiastro/saspro/blemish_blaster.py +488 -0
  21. setiastro/saspro/blink_comparator_pro.py +2923 -0
  22. setiastro/saspro/bundles.py +61 -0
  23. setiastro/saspro/bundles_dock.py +114 -0
  24. setiastro/saspro/cheat_sheet.py +168 -0
  25. setiastro/saspro/clahe.py +342 -0
  26. setiastro/saspro/comet_stacking.py +1377 -0
  27. setiastro/saspro/config.py +38 -0
  28. setiastro/saspro/config_bootstrap.py +40 -0
  29. setiastro/saspro/config_manager.py +316 -0
  30. setiastro/saspro/continuum_subtract.py +1617 -0
  31. setiastro/saspro/convo.py +1397 -0
  32. setiastro/saspro/convo_preset.py +414 -0
  33. setiastro/saspro/copyastro.py +187 -0
  34. setiastro/saspro/cosmicclarity.py +1564 -0
  35. setiastro/saspro/cosmicclarity_preset.py +407 -0
  36. setiastro/saspro/crop_dialog_pro.py +948 -0
  37. setiastro/saspro/crop_preset.py +189 -0
  38. setiastro/saspro/curve_editor_pro.py +2544 -0
  39. setiastro/saspro/curves_preset.py +375 -0
  40. setiastro/saspro/debayer.py +670 -0
  41. setiastro/saspro/debug_utils.py +29 -0
  42. setiastro/saspro/dnd_mime.py +35 -0
  43. setiastro/saspro/doc_manager.py +2634 -0
  44. setiastro/saspro/exoplanet_detector.py +2166 -0
  45. setiastro/saspro/file_utils.py +284 -0
  46. setiastro/saspro/fitsmodifier.py +744 -0
  47. setiastro/saspro/free_torch_memory.py +48 -0
  48. setiastro/saspro/frequency_separation.py +1343 -0
  49. setiastro/saspro/function_bundle.py +1594 -0
  50. setiastro/saspro/ghs_dialog_pro.py +660 -0
  51. setiastro/saspro/ghs_preset.py +284 -0
  52. setiastro/saspro/graxpert.py +634 -0
  53. setiastro/saspro/graxpert_preset.py +287 -0
  54. setiastro/saspro/gui/__init__.py +0 -0
  55. setiastro/saspro/gui/main_window.py +8494 -0
  56. setiastro/saspro/gui/mixins/__init__.py +33 -0
  57. setiastro/saspro/gui/mixins/dock_mixin.py +263 -0
  58. setiastro/saspro/gui/mixins/file_mixin.py +445 -0
  59. setiastro/saspro/gui/mixins/geometry_mixin.py +403 -0
  60. setiastro/saspro/gui/mixins/header_mixin.py +441 -0
  61. setiastro/saspro/gui/mixins/mask_mixin.py +421 -0
  62. setiastro/saspro/gui/mixins/menu_mixin.py +361 -0
  63. setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
  64. setiastro/saspro/gui/mixins/toolbar_mixin.py +1324 -0
  65. setiastro/saspro/gui/mixins/update_mixin.py +309 -0
  66. setiastro/saspro/gui/mixins/view_mixin.py +435 -0
  67. setiastro/saspro/halobgon.py +462 -0
  68. setiastro/saspro/header_viewer.py +445 -0
  69. setiastro/saspro/headless_utils.py +88 -0
  70. setiastro/saspro/histogram.py +753 -0
  71. setiastro/saspro/history_explorer.py +939 -0
  72. setiastro/saspro/image_combine.py +414 -0
  73. setiastro/saspro/image_peeker_pro.py +1596 -0
  74. setiastro/saspro/imageops/__init__.py +37 -0
  75. setiastro/saspro/imageops/mdi_snap.py +292 -0
  76. setiastro/saspro/imageops/scnr.py +36 -0
  77. setiastro/saspro/imageops/starbasedwhitebalance.py +210 -0
  78. setiastro/saspro/imageops/stretch.py +244 -0
  79. setiastro/saspro/isophote.py +1179 -0
  80. setiastro/saspro/layers.py +208 -0
  81. setiastro/saspro/layers_dock.py +714 -0
  82. setiastro/saspro/lazy_imports.py +193 -0
  83. setiastro/saspro/legacy/__init__.py +2 -0
  84. setiastro/saspro/legacy/image_manager.py +2226 -0
  85. setiastro/saspro/legacy/numba_utils.py +3659 -0
  86. setiastro/saspro/legacy/xisf.py +1071 -0
  87. setiastro/saspro/linear_fit.py +534 -0
  88. setiastro/saspro/live_stacking.py +1830 -0
  89. setiastro/saspro/log_bus.py +5 -0
  90. setiastro/saspro/logging_config.py +460 -0
  91. setiastro/saspro/luminancerecombine.py +309 -0
  92. setiastro/saspro/main_helpers.py +201 -0
  93. setiastro/saspro/mask_creation.py +928 -0
  94. setiastro/saspro/masks_core.py +56 -0
  95. setiastro/saspro/mdi_widgets.py +353 -0
  96. setiastro/saspro/memory_utils.py +666 -0
  97. setiastro/saspro/metadata_patcher.py +75 -0
  98. setiastro/saspro/mfdeconv.py +3826 -0
  99. setiastro/saspro/mfdeconv_earlystop.py +71 -0
  100. setiastro/saspro/mfdeconvcudnn.py +3263 -0
  101. setiastro/saspro/mfdeconvsport.py +2382 -0
  102. setiastro/saspro/minorbodycatalog.py +567 -0
  103. setiastro/saspro/morphology.py +382 -0
  104. setiastro/saspro/multiscale_decomp.py +1290 -0
  105. setiastro/saspro/nbtorgb_stars.py +531 -0
  106. setiastro/saspro/numba_utils.py +3044 -0
  107. setiastro/saspro/numba_warmup.py +141 -0
  108. setiastro/saspro/ops/__init__.py +9 -0
  109. setiastro/saspro/ops/command_help_dialog.py +623 -0
  110. setiastro/saspro/ops/command_runner.py +217 -0
  111. setiastro/saspro/ops/commands.py +1594 -0
  112. setiastro/saspro/ops/script_editor.py +1102 -0
  113. setiastro/saspro/ops/scripts.py +1413 -0
  114. setiastro/saspro/ops/settings.py +560 -0
  115. setiastro/saspro/parallel_utils.py +554 -0
  116. setiastro/saspro/pedestal.py +121 -0
  117. setiastro/saspro/perfect_palette_picker.py +1053 -0
  118. setiastro/saspro/pipeline.py +110 -0
  119. setiastro/saspro/pixelmath.py +1600 -0
  120. setiastro/saspro/plate_solver.py +2435 -0
  121. setiastro/saspro/project_io.py +797 -0
  122. setiastro/saspro/psf_utils.py +136 -0
  123. setiastro/saspro/psf_viewer.py +549 -0
  124. setiastro/saspro/pyi_rthook_astroquery.py +95 -0
  125. setiastro/saspro/remove_green.py +314 -0
  126. setiastro/saspro/remove_stars.py +1625 -0
  127. setiastro/saspro/remove_stars_preset.py +404 -0
  128. setiastro/saspro/resources.py +472 -0
  129. setiastro/saspro/rgb_combination.py +207 -0
  130. setiastro/saspro/rgb_extract.py +19 -0
  131. setiastro/saspro/rgbalign.py +723 -0
  132. setiastro/saspro/runtime_imports.py +7 -0
  133. setiastro/saspro/runtime_torch.py +754 -0
  134. setiastro/saspro/save_options.py +72 -0
  135. setiastro/saspro/selective_color.py +1552 -0
  136. setiastro/saspro/sfcc.py +1425 -0
  137. setiastro/saspro/shortcuts.py +2807 -0
  138. setiastro/saspro/signature_insert.py +1099 -0
  139. setiastro/saspro/stacking_suite.py +17712 -0
  140. setiastro/saspro/star_alignment.py +7420 -0
  141. setiastro/saspro/star_alignment_preset.py +329 -0
  142. setiastro/saspro/star_metrics.py +49 -0
  143. setiastro/saspro/star_spikes.py +681 -0
  144. setiastro/saspro/star_stretch.py +470 -0
  145. setiastro/saspro/stat_stretch.py +502 -0
  146. setiastro/saspro/status_log_dock.py +78 -0
  147. setiastro/saspro/subwindow.py +3267 -0
  148. setiastro/saspro/supernovaasteroidhunter.py +1712 -0
  149. setiastro/saspro/swap_manager.py +99 -0
  150. setiastro/saspro/torch_backend.py +89 -0
  151. setiastro/saspro/torch_rejection.py +434 -0
  152. setiastro/saspro/view_bundle.py +1555 -0
  153. setiastro/saspro/wavescale_hdr.py +624 -0
  154. setiastro/saspro/wavescale_hdr_preset.py +100 -0
  155. setiastro/saspro/wavescalede.py +657 -0
  156. setiastro/saspro/wavescalede_preset.py +228 -0
  157. setiastro/saspro/wcs_update.py +374 -0
  158. setiastro/saspro/whitebalance.py +456 -0
  159. setiastro/saspro/widgets/__init__.py +48 -0
  160. setiastro/saspro/widgets/common_utilities.py +305 -0
  161. setiastro/saspro/widgets/graphics_views.py +122 -0
  162. setiastro/saspro/widgets/image_utils.py +518 -0
  163. setiastro/saspro/widgets/preview_dialogs.py +280 -0
  164. setiastro/saspro/widgets/spinboxes.py +275 -0
  165. setiastro/saspro/widgets/themed_buttons.py +13 -0
  166. setiastro/saspro/widgets/wavelet_utils.py +299 -0
  167. setiastro/saspro/window_shelf.py +185 -0
  168. setiastro/saspro/xisf.py +1123 -0
  169. setiastrosuitepro-1.6.0.dist-info/METADATA +266 -0
  170. setiastrosuitepro-1.6.0.dist-info/RECORD +174 -0
  171. setiastrosuitepro-1.6.0.dist-info/WHEEL +4 -0
  172. setiastrosuitepro-1.6.0.dist-info/entry_points.txt +6 -0
  173. setiastrosuitepro-1.6.0.dist-info/licenses/LICENSE +674 -0
  174. setiastrosuitepro-1.6.0.dist-info/licenses/license.txt +2580 -0
@@ -0,0 +1,1594 @@
1
+ # ops/commands.py
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass, field
5
+ from typing import Any, Dict, List, Optional
6
+
7
+
8
+ # -----------------------------------------------------------------------------
9
+ # Preset / Command metadata models
10
+ # -----------------------------------------------------------------------------
11
+
12
+ @dataclass
13
+ class PresetSpec:
14
+ """
15
+ Describes one preset key for a command.
16
+ """
17
+ key: str
18
+ type: str # "float" | "int" | "bool" | "str" | "enum" | "dict"
19
+ default: Any = None
20
+ min: float | None = None
21
+ max: float | None = None
22
+ enum: list[str] | None = None
23
+
24
+ # prefer desc, but accept help= for backward-compat
25
+ desc: str = "" # human description
26
+ help: str = "" # DEPRECATED alias for desc
27
+
28
+ optional: bool = True # if False, UI/docs treat as required
29
+
30
+ def __post_init__(self):
31
+ if (not self.desc) and self.help:
32
+ self.desc = self.help
33
+
34
+
35
+ @dataclass
36
+ class CommandSpec:
37
+ """
38
+ One command in SASpro.
39
+ """
40
+ id: str
41
+
42
+ # prefer name/notes, but accept title/summary
43
+ name: str = ""
44
+ notes: str = ""
45
+
46
+ title: str = "" # DEPRECATED alias for name
47
+ summary: str = "" # DEPRECATED alias for notes
48
+
49
+ group: str = "General"
50
+
51
+ # how scripts should call it (preferred)
52
+ call_style: str = "ctx.run_command" # or "direct_import"
53
+
54
+ # Optional: lazy callable import for headless apply
55
+ # Use either import_path+callable_name OR ui_method/headless_method names.
56
+ import_path: str | None = None # e.g. "pro.whitebalance"
57
+ callable_name: str | None = None # e.g. "apply_white_balance_to_doc"
58
+
59
+ # If command is handled by AstroSuiteProMainWindow methods:
60
+ ui_method: str | None = None # e.g. "_open_wavescale_hdr"
61
+ headless_method: str | None = None # e.g. "_apply_star_stretch_preset_to_doc"
62
+
63
+ replay_apply_name: str | None = None
64
+
65
+ presets: list[PresetSpec] = field(default_factory=list)
66
+
67
+ examples: list[str] = field(default_factory=list)
68
+ aliases: list[str] = field(default_factory=list)
69
+
70
+ supports_mono: bool = True
71
+ supports_rgb: bool = True
72
+ supports_linear: bool = True
73
+ supports_nonlinear: bool = True
74
+
75
+ def __post_init__(self):
76
+ # allow old style title/summary usage
77
+ if not self.name and self.title:
78
+ self.name = self.title
79
+ if not self.notes and self.summary:
80
+ self.notes = self.summary
81
+
82
+
83
+ # -----------------------------------------------------------------------------
84
+ # Registry container + helpers
85
+ # -----------------------------------------------------------------------------
86
+
87
+ COMMAND_REGISTRY: Dict[str, CommandSpec] = {}
88
+
89
+
90
+ def register(spec: CommandSpec) -> CommandSpec:
91
+ COMMAND_REGISTRY[spec.id] = spec
92
+ return spec
93
+
94
+
95
+ # -----------------------------------------------------------------------------
96
+ # CID normalization + aliases (lifted from _cid_norm)
97
+ # -----------------------------------------------------------------------------
98
+
99
+ ALIASES: Dict[str, str] = {
100
+ # geometry short ↔ long ids
101
+ "flip_horizontal": "geom_flip_horizontal",
102
+ "geom_flip_h": "geom_flip_horizontal",
103
+ "geom_flip_horizontal": "geom_flip_horizontal",
104
+
105
+ "flip_vertical": "geom_flip_vertical",
106
+ "geom_flip_v": "geom_flip_vertical",
107
+ "geom_rotate_clockwise": "geom_rotate_clockwise",
108
+ "rotate_clockwise": "geom_rotate_clockwise",
109
+ "geom_rot_cw": "geom_rotate_clockwise",
110
+
111
+ "rotate_counterclockwise": "geom_rotate_counterclockwise",
112
+ "geom_rot_ccw": "geom_rotate_counterclockwise",
113
+ "geom_rotate_counterclockwise": "geom_rotate_counterclockwise",
114
+
115
+ "rotate_180": "geom_rotate_180",
116
+ "geom_rotate_180": "geom_rotate_180",
117
+
118
+ "invert": "geom_invert",
119
+ "geom_invert": "geom_invert",
120
+
121
+ "rescale": "geom_rescale",
122
+ "geom_rescale": "geom_rescale",
123
+
124
+ # stretches / gradients
125
+ "ghs": "ghs",
126
+ "hyperbolic_stretch": "ghs",
127
+ "universal_hyperbolic_stretch": "ghs",
128
+
129
+ "abe": "abe",
130
+ "automatic_background_extraction": "abe",
131
+
132
+ "graxpert": "graxpert",
133
+ "grax": "graxpert",
134
+ "remove_gradient_graxpert": "graxpert",
135
+
136
+ # star removal
137
+ "remove_stars": "remove_stars",
138
+ "star_removal": "remove_stars",
139
+ "starnet": "remove_stars",
140
+ "darkstar": "remove_stars",
141
+
142
+ # AI tools ✅ FIXED
143
+ "aberrationai": "aberration_ai",
144
+ "aberration_ai": "aberration_ai", # explicit canonical alias
145
+ "aberration": "aberration_ai",
146
+ "ai_aberration": "aberration_ai",
147
+
148
+ "cosmic": "cosmic_clarity",
149
+ "cosmicclarity": "cosmic_clarity",
150
+ "cosmic_clarity": "cosmic_clarity",
151
+
152
+ # misc
153
+ "crop": "crop",
154
+ "geom_crop": "crop",
155
+
156
+ "wavescale_hdr": "wavescale_hdr",
157
+ "wavescalehdr": "wavescale_hdr",
158
+ "wavescale": "wavescale_hdr",
159
+
160
+ "wavescale_dark_enhance": "wavescale_dark_enhance",
161
+ "wavescale_dark_enhancer": "wavescale_dark_enhance",
162
+ "wsde": "wavescale_dark_enhance",
163
+ "dark_enhancer": "wavescale_dark_enhance",
164
+
165
+ "star_alignment": "star_align",
166
+ "align_stars": "star_align",
167
+ "align": "star_align",
168
+
169
+ "convo": "convo",
170
+ "convolution": "convo",
171
+ "deconvolution": "convo",
172
+ "convo_deconvo": "convo",
173
+ }
174
+
175
+
176
+ def normalize_cid(cid: str | None) -> str:
177
+ c = (cid or "").strip().lower()
178
+ return ALIASES.get(c, c)
179
+
180
+
181
+ def get_spec(cid: str | None) -> CommandSpec | None:
182
+ return COMMAND_REGISTRY.get(normalize_cid(cid))
183
+
184
+
185
+ def list_commands() -> Dict[str, str]:
186
+ return {cid: spec.name for cid, spec in COMMAND_REGISTRY.items()}
187
+
188
+
189
+ # -----------------------------------------------------------------------------
190
+ # Registry population (starter set)
191
+ # -----------------------------------------------------------------------------
192
+
193
+ # Bundles
194
+ register(CommandSpec(
195
+ id="function_bundle",
196
+ title="Function Bundle",
197
+ group="Bundles",
198
+ summary=(
199
+ "Runs a sequence of steps from a saved Function Bundle or an inline "
200
+ "steps list. "
201
+ "Config: name/bundle_name='Bundle Name' OR steps=[...], "
202
+ "optional targets, inherit_target."
203
+ ),
204
+ call_style="ctx.run_command",
205
+ import_path="pro.function_bundle", # <── important
206
+ callable_name="run_function_bundle_command",# <── important
207
+ notes=(
208
+ "Use this command from scripts to run a saved Function Bundle or an "
209
+ "inline list of steps.\n\n"
210
+ "- For saved bundles, specify `bundle_name` (or `name`).\n"
211
+ "- `inherit_target=True` forwards the current target (active view / ROI) "
212
+ "into each step.\n"
213
+ "- This is the same mechanism used by the bundled 'Run Function Bundle…' script."
214
+ ),
215
+ presets=[
216
+ PresetSpec(
217
+ key="bundle_name",
218
+ type="str",
219
+ desc="Name of the saved Function Bundle to run (same as shown in the Function Bundles dialog).",
220
+ optional=True,
221
+ ),
222
+ PresetSpec(
223
+ key="name",
224
+ type="str",
225
+ desc="Alias of `bundle_name` for backward compatibility.",
226
+ optional=True,
227
+ ),
228
+ PresetSpec(
229
+ key="steps",
230
+ type="list",
231
+ desc="Inline steps list (advanced). If given, this is used instead of a saved bundle.",
232
+ optional=True,
233
+ ),
234
+ PresetSpec(
235
+ key="inherit_target",
236
+ type="bool",
237
+ default=True,
238
+ desc="If True, each step runs on the same target (active view / ROI) as the bundle call.",
239
+ optional=True,
240
+ ),
241
+ PresetSpec(
242
+ key="target",
243
+ type="dict",
244
+ desc="Optional explicit target override (advanced; normally you just let inherit_target=True).",
245
+ optional=True,
246
+ ),
247
+ ],
248
+ examples=[
249
+ # Mirrors your run_function_bundle.py config, but simplified
250
+ "def run(ctx):\n"
251
+ " cfg = {\n"
252
+ " 'bundle_name': 'PreProcess', # name from Function Bundles dialog\n"
253
+ " 'inherit_target': True,\n"
254
+ " }\n"
255
+ " ctx.run_command('function_bundle', cfg)\n",
256
+
257
+ # Inline steps example (for power users)
258
+ "def run(ctx):\n"
259
+ " steps = [\n"
260
+ " {'id': 'stat_stretch', 'preset': {'target_median': 0.25}},\n"
261
+ " {'id': 'remove_green', 'preset': {'amount': 0.6}},\n"
262
+ " ]\n"
263
+ " ctx.run_command('function_bundle', {\n"
264
+ " 'steps': steps,\n"
265
+ " 'inherit_target': True,\n"
266
+ " })\n",
267
+ ],
268
+ ))
269
+
270
+
271
+ register(CommandSpec(
272
+ id="bundle",
273
+ title="Bundle Exec",
274
+ group="Bundles",
275
+ summary="Internal bundle runner. steps=[...], targets='all_open'|[doc_ptrs], stop_on_error.",
276
+ call_style="ctx.run_command",
277
+ import_path="pro.function_bundle",
278
+ callable_name="run_function_bundle_command",
279
+ ))
280
+
281
+
282
+ # ---------------- Stretches / tone ----------------
283
+
284
+ register(CommandSpec(
285
+ id="stat_stretch",
286
+ title="Statistical Stretch",
287
+ group="Stretch",
288
+ summary=(
289
+ "Stretch linear data to a target median using Statistical Stretch. "
290
+ "Supports linked/unlinked color stretch, optional normalization, "
291
+ "and optional curves boost."
292
+ ),
293
+ headless_method="_apply_stat_stretch_preset_to_doc",
294
+ ui_method="_open_statistical_stretch_with_preset",
295
+ presets=[
296
+ PresetSpec(
297
+ key="target_median",
298
+ type="float",
299
+ default=0.25,
300
+ min=0.0, max=1.0,
301
+ help="Target median after stretch. Typical values 0.20–0.30."
302
+ ),
303
+ PresetSpec(
304
+ key="linked",
305
+ type="bool",
306
+ default=True,
307
+ help="If True, stretch RGB channels together (linked). If False, unlinked."
308
+ ),
309
+ PresetSpec(
310
+ key="normalize",
311
+ type="bool",
312
+ default=True,
313
+ help="If True, normalize channels/whitepoint after stretch."
314
+ ),
315
+ PresetSpec(
316
+ key="apply_curves",
317
+ type="bool",
318
+ default=False,
319
+ help="If True, apply the optional curves adjustment pass."
320
+ ),
321
+ PresetSpec(
322
+ key="curves_boost",
323
+ type="float",
324
+ default=0.0,
325
+ min=0.0, max=1.0,
326
+ help="Curves boost strength in [0,1]. Only used if apply_curves=True."
327
+ ),
328
+ ],
329
+ examples=[
330
+ "ctx.run_command('stat_stretch', {'target_median': 0.25, 'linked': True})",
331
+ "ctx.run_command('stat_stretch', {'target_median': 0.22, 'linked': False, "
332
+ "'normalize': False, 'apply_curves': True, 'curves_boost': 0.15})",
333
+ ],
334
+ aliases=["statistical_stretch", "statstretch"],
335
+ ))
336
+
337
+ register(CommandSpec(
338
+ id="star_stretch",
339
+ name="Star Stretch",
340
+ group="Stretch",
341
+ ui_method="_open_star_stretch_with_preset",
342
+ headless_method="_apply_star_stretch_preset_to_doc",
343
+ summary=(
344
+ "Stretches stars on linear data using your Star Stretch kernel. "
345
+ "Supports optional color boost and SCNR green neutralization."
346
+ ),
347
+ presets=[
348
+ PresetSpec(
349
+ key="stretch_factor",
350
+ type="float",
351
+ default=5.0,
352
+ min=0.0,
353
+ max=8.0,
354
+ desc="Star stretch strength. Typical range: 3–6. (Aliases: stretch_amount, amount)"
355
+ ),
356
+ PresetSpec(
357
+ key="color_boost",
358
+ type="float",
359
+ default=1.0,
360
+ min=0.0,
361
+ max=2.0,
362
+ desc="Color saturation boost multiplier. 1.0 = no change. (Alias: saturation)"
363
+ ),
364
+ PresetSpec(
365
+ key="scnr_green",
366
+ type="bool",
367
+ default=False,
368
+ desc="If True, apply SCNR-like green neutralization. (Alias: scnr)"
369
+ ),
370
+ ],
371
+ aliases=[
372
+ "starstretch",
373
+ "stretch_stars",
374
+ ],
375
+ examples=[
376
+ "ctx.run_command('star_stretch', {'stretch_factor': 5.0})",
377
+ "ctx.run_command('star_stretch', {'stretch_factor': 4.2, 'color_boost': 1.3})",
378
+ "ctx.run_command('star_stretch', {'amount': 5.5, 'saturation': 1.15, 'scnr': True})",
379
+ ],
380
+ supports_mono=True, # your headless path supports mono via temp RGB + collapse
381
+ supports_rgb=True,
382
+ supports_linear=True,
383
+ supports_nonlinear=False, # star stretch is intended for star images / linear use
384
+ ))
385
+
386
+
387
+ register(CommandSpec(
388
+ id="ghs",
389
+ name="Generalized Hyperbolic Stretch",
390
+ group="Stretch",
391
+ import_path="pro.ghs_preset",
392
+ callable_name="apply_ghs_via_preset",
393
+ ui_method="open_ghs_with_preset",
394
+ summary=(
395
+ "Universal / Generalized Hyperbolic Stretch. Builds a monotone control curve from "
396
+ "alpha/beta/gamma with a pivot symmetry point and optional LP/HP protection. "
397
+ "Applies to K (brightness) or individual RGB channels, with active-mask blending."
398
+ ),
399
+ presets=[
400
+ PresetSpec(
401
+ key="alpha",
402
+ type="float",
403
+ default=1.0,
404
+ min=0.02,
405
+ max=10.0,
406
+ desc=(
407
+ "Hyperbolic alpha (controls left/right curve rolloff). "
408
+ "Dialog slider stores alpha*50. Range ≈0.02–10."
409
+ )
410
+ ),
411
+ PresetSpec(
412
+ key="beta",
413
+ type="float",
414
+ default=1.0,
415
+ min=0.02,
416
+ max=10.0,
417
+ desc=(
418
+ "Hyperbolic beta (asymmetry between shadows/highlights). "
419
+ "Dialog slider stores beta*50. Range ≈0.02–10."
420
+ )
421
+ ),
422
+ PresetSpec(
423
+ key="gamma",
424
+ type="float",
425
+ default=1.0,
426
+ min=0.01,
427
+ max=5.0,
428
+ desc=(
429
+ "Gamma lift after hyperbolic mapping. "
430
+ "Dialog slider stores gamma*100. Range ≈0.01–5."
431
+ )
432
+ ),
433
+ PresetSpec(
434
+ key="pivot",
435
+ type="float",
436
+ default=0.5,
437
+ min=0.0,
438
+ max=1.0,
439
+ desc=(
440
+ "Symmetry / pivot point in normalized domain. "
441
+ "0.5 is neutral midtone pivot."
442
+ )
443
+ ),
444
+ PresetSpec(
445
+ key="lp",
446
+ type="float",
447
+ default=0.0,
448
+ min=0.0,
449
+ max=1.0,
450
+ desc=(
451
+ "Low-protect (LP). Blends left side toward identity y=x. "
452
+ "Dialog slider stores lp*360."
453
+ )
454
+ ),
455
+ PresetSpec(
456
+ key="hp",
457
+ type="float",
458
+ default=0.0,
459
+ min=0.0,
460
+ max=1.0,
461
+ desc=(
462
+ "High-protect (HP). Blends right side toward identity y=x. "
463
+ "Dialog slider stores hp*360."
464
+ )
465
+ ),
466
+ PresetSpec(
467
+ key="channel",
468
+ type="enum",
469
+ default="K (Brightness)",
470
+ enum=["K (Brightness)", "R", "G", "B"],
471
+ desc=(
472
+ "Target channel. 'K (Brightness)' applies to luminance/brightness. "
473
+ "R/G/B apply to individual color channels. "
474
+ "Aliases accepted: k, brightness, rgb, r, g, b."
475
+ )
476
+ ),
477
+ ],
478
+ aliases=[
479
+ "hyperbolic_stretch",
480
+ "universal_hyperbolic_stretch",
481
+ "uhs",
482
+ ],
483
+ examples=[
484
+ # gentle linked luminance stretch
485
+ "ctx.run_command('ghs', {'alpha': 1.0, 'beta': 1.0, 'gamma': 1.0, 'pivot': 0.5})",
486
+ # stronger contrast with protection
487
+ "ctx.run_command('ghs', {'alpha': 2.2, 'beta': 1.4, 'gamma': 1.1, 'pivot': 0.45, 'lp': 0.10, 'hp': 0.20})",
488
+ # per-channel tweak
489
+ "ctx.run_command('ghs', {'alpha': 1.6, 'beta': 1.0, 'gamma': 0.95, 'pivot': 0.5, 'channel': 'R'})",
490
+ ],
491
+ supports_mono=True, # K works on mono; RGB channels are ignored/treated safely by _apply_mode_any
492
+ supports_rgb=True,
493
+ supports_linear=True,
494
+ supports_nonlinear=True,
495
+ ))
496
+
497
+
498
+ register(CommandSpec(
499
+ id="curves",
500
+ title="Curves",
501
+ group="Stretch",
502
+ import_path="pro.curves_preset",
503
+ callable_name="apply_curves_via_preset",
504
+ ui_method="open_curves_with_preset",
505
+ summary=(
506
+ "Preset schema: {mode, shape, amount, points_norm?}. "
507
+ "mode applies to K/R/G/B or Lab/LCh-style channels. "
508
+ "shape selects a built-in curve unless shape='custom'. "
509
+ "amount is 0..1 intensity for non-custom shapes. "
510
+ "custom uses points_norm (normalized 0..1). "
511
+ "Advanced: preset may also include points_scene/handles/control_points."
512
+ ),
513
+ presets=[
514
+ PresetSpec(
515
+ key="mode",
516
+ type="enum",
517
+ default="K (Brightness)",
518
+ enum=[
519
+ "K (Brightness)",
520
+ "R", "G", "B",
521
+ "L*", "a*", "b*",
522
+ "Chroma",
523
+ "Saturation",
524
+ ],
525
+ help=(
526
+ "Channel/mode to apply curve to. "
527
+ "Aliases accepted: k, brightness, rgb -> K (Brightness); "
528
+ "lum/l/lab_l -> L*; lab_a -> a*; lab_b -> b*; "
529
+ "chroma -> Chroma; sat/s -> Saturation."
530
+ ),
531
+ ),
532
+ PresetSpec(
533
+ key="shape",
534
+ type="enum",
535
+ default="linear",
536
+ enum=[
537
+ "linear",
538
+ "s_mild",
539
+ "s_med",
540
+ "s_strong",
541
+ "lift_shadows",
542
+ "crush_shadows",
543
+ "fade_blacks",
544
+ "rolloff_highlights",
545
+ "flatten",
546
+ "custom",
547
+ ],
548
+ help=(
549
+ "Built-in curve shape. "
550
+ "If 'custom', provide points_norm (or points_scene/handles)."
551
+ ),
552
+ ),
553
+ PresetSpec(
554
+ key="amount",
555
+ type="float",
556
+ default=0.5,
557
+ min=0.0,
558
+ max=1.0,
559
+ help=(
560
+ "Intensity for built-in shapes (0..1). "
561
+ "Ignored when shape='custom'."
562
+ ),
563
+ ),
564
+ PresetSpec(
565
+ key="points_norm",
566
+ type="dict",
567
+ default=None,
568
+ optional=True,
569
+ help=(
570
+ "For shape='custom': normalized control points "
571
+ "as [[x,y], ...] with x,y in [0..1]. "
572
+ "Example: [[0,0],[0.25,0.2],[0.5,0.5],[0.75,0.85],[1,1]]."
573
+ ),
574
+ ),
575
+ PresetSpec(
576
+ key="points_scene",
577
+ type="dict",
578
+ default=None,
579
+ optional=True,
580
+ help=(
581
+ "Advanced custom curve in scene coords [[x,y],...], "
582
+ "x,y in [0..360]. Synonyms accepted by engine: "
583
+ "scene_points, handles, control_points."
584
+ ),
585
+ ),
586
+ ],
587
+ supports_mono=True,
588
+ supports_rgb=True,
589
+ supports_linear=True,
590
+ supports_nonlinear=True,
591
+ ))
592
+
593
+
594
+ # ---------------- Gradient / background ----------------
595
+
596
+ register(CommandSpec(
597
+ id="abe",
598
+ title="Automatic Background Extraction",
599
+ group="Background",
600
+ import_path="pro.abe_preset",
601
+ callable_name="apply_abe_via_preset",
602
+ ui_method="open_abe_with_preset", # ✅ matches your pro/abe_preset.py
603
+ summary=(
604
+ "Automatic Background Extraction (headless). "
605
+ "Runs abe_run() with polynomial degree + RBF option. "
606
+ "Headless mode does NOT support exclusion polygons (exclusion_mask=None). "
607
+ "If an active mask is present, blends result as m*out + (1-m)*src. "
608
+ "Optional: create a separate background document."
609
+ ),
610
+ presets=[
611
+ PresetSpec(
612
+ key="degree",
613
+ type="int",
614
+ default=2,
615
+ min=0, max=6,
616
+ help=(
617
+ "Polynomial degree for background model. "
618
+ "0 = RBF-only (allowed in headless). "
619
+ "Dialog range is 0–6."
620
+ ),
621
+ ),
622
+ PresetSpec(
623
+ key="samples",
624
+ type="int",
625
+ default=120,
626
+ min=20, max=100000,
627
+ help="Number of sample points used for background fitting.",
628
+ ),
629
+ PresetSpec(
630
+ key="downsample",
631
+ type="int",
632
+ default=6,
633
+ min=1, max=64,
634
+ help="Downsample factor for analysis grid (higher = faster, coarser).",
635
+ ),
636
+ PresetSpec(
637
+ key="patch",
638
+ type="int",
639
+ default=15,
640
+ min=5, max=151,
641
+ help="Patch size (px) for local background sampling.",
642
+ ),
643
+ PresetSpec(
644
+ key="rbf",
645
+ type="bool",
646
+ default=True,
647
+ help="If True, include RBF smoothing component in the model.",
648
+ ),
649
+ PresetSpec(
650
+ key="rbf_smooth",
651
+ type="float",
652
+ default=1.0,
653
+ min=0.0, max=1000.0,
654
+ help=(
655
+ "RBF smoothness. Dialog stores rbf_smooth*100, "
656
+ "so 1.0 here corresponds to slider=100."
657
+ ),
658
+ ),
659
+ PresetSpec(
660
+ key="make_background_doc",
661
+ type="bool",
662
+ default=False,
663
+ help="If True, also open a background-only document.",
664
+ ),
665
+ ],
666
+ aliases=[
667
+ "automatic_background_extraction",
668
+ "background_extraction",
669
+ ],
670
+ examples=[
671
+ "ctx.run_command('abe', {'degree': 2, 'samples': 120, 'downsample': 6, 'patch': 15})",
672
+ "ctx.run_command('abe', {'degree': 0, 'rbf': True, 'rbf_smooth': 0.8})",
673
+ "ctx.run_command('abe', {'degree': 3, 'samples': 250, 'make_background_doc': True})",
674
+ ],
675
+ supports_mono=True,
676
+ supports_rgb=True,
677
+ supports_linear=True,
678
+ supports_nonlinear=True,
679
+ ))
680
+
681
+
682
+ register(CommandSpec(
683
+ id="graxpert",
684
+ title="GraXpert Gradient / Denoise",
685
+ group="Background",
686
+ import_path="pro.graxpert_preset",
687
+ callable_name="run_graxpert_via_preset",
688
+ # no ui_method here unless you want to open your optional preset dialog from drops
689
+ # ui_method="open_graxpert_with_preset", # (only if/when you add one)
690
+ summary=(
691
+ "GraXpert headless runner. "
692
+ "op='background' runs background-extraction with smoothing. "
693
+ "op='denoise' runs denoising with strength, optional ai_version, and batch_size. "
694
+ "Uses GPU by default, but honors preset['gpu'] or saved QSettings graxpert/use_gpu. "
695
+ "Writes temporary float32 TIFF and runs GraXpert CLI like SASv2."
696
+ ),
697
+ presets=[
698
+ PresetSpec(
699
+ key="op",
700
+ type="enum",
701
+ default="background",
702
+ enum=["background", "denoise"],
703
+ help=(
704
+ "Operation mode. "
705
+ "'background' = gradient removal (background-extraction). "
706
+ "'denoise' = GraXpert denoising."
707
+ ),
708
+ ),
709
+
710
+ # --- background mode ---
711
+ PresetSpec(
712
+ key="smoothing",
713
+ type="float",
714
+ default=0.10,
715
+ min=0.0,
716
+ max=1.0,
717
+ optional=True,
718
+ help=(
719
+ "Background mode only: smoothing value (0..1). "
720
+ "Ignored when op='denoise'."
721
+ ),
722
+ ),
723
+
724
+ # --- denoise mode ---
725
+ PresetSpec(
726
+ key="strength",
727
+ type="float",
728
+ default=0.50,
729
+ min=0.0,
730
+ max=1.0,
731
+ optional=True,
732
+ help=(
733
+ "Denoise mode only: denoise strength (0..1). "
734
+ "Ignored when op='background'."
735
+ ),
736
+ ),
737
+ PresetSpec(
738
+ key="ai_version",
739
+ type="str",
740
+ default="",
741
+ optional=True,
742
+ help=(
743
+ "Denoise mode only: explicit GraXpert AI model version "
744
+ "(e.g. '3.0.2'). Blank/omitted uses latest/auto."
745
+ ),
746
+ ),
747
+ PresetSpec(
748
+ key="batch_size",
749
+ type="int",
750
+ default=None,
751
+ min=1,
752
+ max=64,
753
+ optional=True,
754
+ help=(
755
+ "Denoise mode only: CLI batch size. "
756
+ "If omitted, runner uses 4 when gpu=True else 1."
757
+ ),
758
+ ),
759
+
760
+ # --- shared ---
761
+ PresetSpec(
762
+ key="gpu",
763
+ type="bool",
764
+ default=True,
765
+ optional=True,
766
+ help=(
767
+ "Use GPU if available. "
768
+ "If omitted, defaults from QSettings 'graxpert/use_gpu' "
769
+ "(falls back to True)."
770
+ ),
771
+ ),
772
+
773
+ # --- advanced / power-user ---
774
+ PresetSpec(
775
+ key="exe",
776
+ type="str",
777
+ default="",
778
+ optional=True,
779
+ help=(
780
+ "Optional explicit path to GraXpert executable. "
781
+ "If omitted, _resolve_graxpert_exec() is used."
782
+ ),
783
+ ),
784
+ ],
785
+ aliases=[
786
+ "grax",
787
+ "remove_gradient_graxpert",
788
+ "graxpert_denoise",
789
+ "graxpert_background",
790
+ ],
791
+ examples=[
792
+ # gradient removal
793
+ "ctx.run_command('graxpert', {'op': 'background', 'smoothing': 0.12})",
794
+ # denoise auto-model, GPU
795
+ "ctx.run_command('graxpert', {'op': 'denoise', 'strength': 0.45, 'gpu': True})",
796
+ # denoise specific model, CPU, explicit batch size
797
+ "ctx.run_command('graxpert', {'op': 'denoise', 'strength': 0.6, 'ai_version': '3.0.2', 'gpu': False, 'batch_size': 1})",
798
+ ],
799
+ supports_mono=True,
800
+ supports_rgb=True,
801
+ supports_linear=True,
802
+ supports_nonlinear=True,
803
+ ))
804
+
805
+
806
+ register(CommandSpec(
807
+ id="background_neutral",
808
+ name="Background Neutralization",
809
+ group="Background",
810
+ import_path="pro.backgroundneutral",
811
+ callable_name="run_background_neutral_via_preset",
812
+ summary=(
813
+ "Neutralizes RGB background either automatically or using a user-specified "
814
+ "normalized rectangle. Headless mode blends with active destination mask "
815
+ "before committing."
816
+ ),
817
+ presets=[
818
+ PresetSpec(
819
+ key="mode",
820
+ type="enum",
821
+ default="auto",
822
+ enum=["auto", "rect"],
823
+ help=(
824
+ "Neutralization mode. "
825
+ "'auto' picks an automatic 50x50-ish dark background rect. "
826
+ "'rect' uses rect_norm=[x,y,w,h] in normalized 0..1 coords."
827
+ ),
828
+ optional=True,
829
+ ),
830
+ PresetSpec(
831
+ key="rect_norm",
832
+ type="dict",
833
+ default=None,
834
+ optional=True,
835
+ help=(
836
+ "Required only if mode='rect'. "
837
+ "Normalized rectangle [x0, y0, w, h], each in 0..1. "
838
+ "Example: [0.10, 0.12, 0.20, 0.18]."
839
+ ),
840
+ ),
841
+ ],
842
+ aliases=[
843
+ "background_neutralization",
844
+ "neutralize_background",
845
+ "bn",
846
+ ],
847
+ examples=[
848
+ # auto (default)
849
+ "ctx.run_command('background_neutral', {'mode': 'auto'})",
850
+ # rect mode with normalized ROI
851
+ "ctx.run_command('background_neutral', {'mode': 'rect', 'rect_norm': [0.1, 0.1, 0.2, 0.2]})",
852
+ # mode omitted -> auto
853
+ "ctx.run_command('background_neutral', {})",
854
+ ],
855
+ supports_mono=False, # your headless code raises on non-RGB
856
+ supports_rgb=True,
857
+ supports_linear=True,
858
+ supports_nonlinear=True,
859
+ ))
860
+
861
+
862
+ # ---------------- Color / WB ----------------
863
+
864
+ register(CommandSpec(
865
+ id="remove_green",
866
+ name="Remove Green (SCNR)",
867
+ group="Color",
868
+ import_path="pro.remove_green",
869
+ callable_name="apply_remove_green_preset_to_doc",
870
+ ui_method="open_remove_green_dialog",
871
+ summary=(
872
+ "Suppresses excess green using an SCNR-style operation. "
873
+ "Neutral comparator is derived from R/B using avg/max/min. "
874
+ "Optionally preserves perceived lightness (Rec.709 luma) "
875
+ "and blends with the active destination mask."
876
+ ),
877
+ presets=[
878
+ PresetSpec(
879
+ key="amount",
880
+ type="float",
881
+ default=1.0,
882
+ min=0.0,
883
+ max=1.0,
884
+ desc=(
885
+ "Strength of green suppression (0..1). "
886
+ "Aliases accepted: strength, value."
887
+ ),
888
+ ),
889
+ PresetSpec(
890
+ key="mode",
891
+ type="enum",
892
+ default="avg",
893
+ enum=["avg", "max", "min"],
894
+ desc=(
895
+ "Neutral mode for comparing green against R/B: "
896
+ "avg = Average(R,B), max = Max(R,B), min = Min(R,B). "
897
+ "Unknown values fall back to 'avg'."
898
+ ),
899
+ ),
900
+ PresetSpec(
901
+ key="preserve_lightness",
902
+ type="bool",
903
+ default=True,
904
+ desc=(
905
+ "If True, rescales all channels to preserve perceived "
906
+ "lightness after green suppression (Rec.709 luma), "
907
+ "with highlight safety."
908
+ ),
909
+ ),
910
+ ],
911
+ aliases=[
912
+ "scnr",
913
+ "remove_green_cast",
914
+ "green_neutralize",
915
+ ],
916
+ examples=[
917
+ "ctx.run_command('remove_green', {'amount': 1.0, 'mode': 'avg'})",
918
+ "ctx.run_command('remove_green', {'amount': 0.7, 'mode': 'max'})",
919
+ "ctx.run_command('remove_green', {'strength': 0.5, 'mode': 'min', 'preserve_lightness': False})",
920
+ ],
921
+ supports_mono=False, # _ensure_rgb returns None on mono; headless becomes no-op
922
+ supports_rgb=True,
923
+ supports_linear=True,
924
+ supports_nonlinear=True,
925
+ ))
926
+
927
+
928
+ register(CommandSpec(
929
+ id="white_balance",
930
+ name="White Balance",
931
+ group="Color",
932
+ headless_method="_apply_white_balance_preset_to_doc",
933
+ summary=(
934
+ "White balance for RGB images. Modes: "
935
+ "star (SEP-based), auto (grid/brightest region), manual (per-channel gains). "
936
+ "Star mode falls back to Auto if detection fails."
937
+ ),
938
+ presets=[
939
+ PresetSpec(
940
+ key="mode",
941
+ type="enum",
942
+ default="star",
943
+ enum=["star", "auto", "manual"],
944
+ desc=(
945
+ "White balance mode. "
946
+ "'star' uses SEP star colors; "
947
+ "'auto' uses headless auto WB; "
948
+ "'manual' uses r/g/b gains."
949
+ ),
950
+ ),
951
+ PresetSpec(
952
+ key="threshold",
953
+ type="float",
954
+ default=50.0,
955
+ min=1.0,
956
+ max=100.0,
957
+ optional=True,
958
+ desc=(
959
+ "SEP star threshold (sigma) for star mode. "
960
+ "Higher = fewer/brighter stars."
961
+ ),
962
+ ),
963
+ PresetSpec(
964
+ key="reuse_cached_sources",
965
+ type="bool",
966
+ default=True,
967
+ optional=True,
968
+ desc=(
969
+ "Reuse cached SEP detections for speed (star mode)."
970
+ ),
971
+ ),
972
+ PresetSpec(
973
+ key="r_gain",
974
+ type="float",
975
+ default=1.0,
976
+ min=0.5,
977
+ max=2.0,
978
+ optional=True,
979
+ desc="Manual red gain (manual mode).",
980
+ ),
981
+ PresetSpec(
982
+ key="g_gain",
983
+ type="float",
984
+ default=1.0,
985
+ min=0.5,
986
+ max=2.0,
987
+ optional=True,
988
+ desc="Manual green gain (manual mode).",
989
+ ),
990
+ PresetSpec(
991
+ key="b_gain",
992
+ type="float",
993
+ default=1.0,
994
+ min=0.5,
995
+ max=2.0,
996
+ optional=True,
997
+ desc="Manual blue gain (manual mode).",
998
+ ),
999
+ ],
1000
+ examples=[
1001
+ "ctx.run_command('white_balance', {'mode': 'star', 'threshold': 45})",
1002
+ "ctx.run_command('white_balance', {'mode': 'auto'})",
1003
+ "ctx.run_command('white_balance', {'mode': 'manual', 'r_gain': 1.12, 'g_gain': 1.0, 'b_gain': 0.94})",
1004
+ ],
1005
+ supports_mono=False,
1006
+ supports_rgb=True,
1007
+ supports_linear=True,
1008
+ supports_nonlinear=True,
1009
+ ))
1010
+
1011
+
1012
+ # ---------------- Luminance tools ----------------
1013
+
1014
+ register(CommandSpec(
1015
+ id="extract_luminance",
1016
+ name="Extract Luminance",
1017
+ group="Luminance",
1018
+ ui_method="_extract_luminance", # now accepts preset optionally
1019
+ headless_method="_apply_extract_luminance_preset_to_doc",
1020
+ summary=(
1021
+ "Create a new mono luminance document from RGB using selectable methods "
1022
+ "(Rec.709/601/2020, max, median, equal, or SNR-weighted)."
1023
+ ),
1024
+ presets=[
1025
+ PresetSpec(
1026
+ key="mode",
1027
+ type="enum",
1028
+ default="rec709",
1029
+ enum=["rec709", "rec601", "rec2020", "max", "snr", "equal", "median"],
1030
+ desc=(
1031
+ "Luminance extraction method. "
1032
+ "Aliases accepted: method, luma_method, nb_max→max, snr_unequal→snr."
1033
+ ),
1034
+ ),
1035
+ ],
1036
+ supports_mono=False,
1037
+ supports_rgb=True,
1038
+ supports_linear=True,
1039
+ supports_nonlinear=True,
1040
+ ))
1041
+
1042
+
1043
+ register(CommandSpec(
1044
+ id="recombine_luminance",
1045
+ name="Recombine Luminance",
1046
+ group="Luminance",
1047
+ import_path="pro.luminancerecombine",
1048
+ callable_name="run_recombine_luminance_via_preset",
1049
+ ui_method="_recombine_luminance_ui",
1050
+ notes=(
1051
+ "Replaces target RGB luminance using another open view (mono or RGB). "
1052
+ "Headless preset may specify the luminance source by title or doc_ptr. "
1053
+ "If omitted, first eligible non-target open doc is used."
1054
+ ),
1055
+ presets=[
1056
+ # ---- luminance source selection ----
1057
+ PresetSpec(
1058
+ key="source_title",
1059
+ type="str",
1060
+ default=None,
1061
+ optional=True,
1062
+ desc=(
1063
+ "Title/name of the open document to use as luminance source. "
1064
+ "Matches subwindow title or doc.display_name()."
1065
+ ),
1066
+ ),
1067
+ PresetSpec(
1068
+ key="source_doc_ptr",
1069
+ type="int",
1070
+ default=None,
1071
+ optional=True,
1072
+ desc=(
1073
+ "Doc identity pointer (id(doc)) for luminance source. "
1074
+ "Useful for replay or scripts that stored doc_ptr."
1075
+ ),
1076
+ ),
1077
+
1078
+ # ---- luminance compute method ----
1079
+ PresetSpec(
1080
+ key="method",
1081
+ type="enum",
1082
+ default="rec709",
1083
+ enum=["rec709", "rec601", "rec2020", "max", "snr", "equal", "median"],
1084
+ desc=(
1085
+ "How to compute luminance if the source is RGB. "
1086
+ "rec709/601/2020 use standard weights; "
1087
+ "max uses channel max (narrowband style); "
1088
+ "snr uses noise-weighted mean; "
1089
+ "equal is average RGB; "
1090
+ "median is per-pixel median."
1091
+ ),
1092
+ ),
1093
+
1094
+ # ---- optional explicit weights ----
1095
+ PresetSpec(
1096
+ key="weights",
1097
+ type="dict",
1098
+ default=None,
1099
+ optional=True,
1100
+ desc=(
1101
+ "Optional custom RGB weights as [wr,wg,wb]. "
1102
+ "Overrides method weights when provided."
1103
+ ),
1104
+ ),
1105
+
1106
+ # ---- blend / protection ----
1107
+ PresetSpec(
1108
+ key="blend",
1109
+ type="float",
1110
+ default=1.0,
1111
+ min=0.0, max=1.0,
1112
+ desc="Blend factor. 1.0 = full replace, 0.0 = no change.",
1113
+ ),
1114
+ PresetSpec(
1115
+ key="soft_knee",
1116
+ type="float",
1117
+ default=0.0,
1118
+ min=0.0, max=1.0,
1119
+ desc=(
1120
+ "Highlight protection strength. "
1121
+ "0 = off, higher compresses extreme up-scaling."
1122
+ ),
1123
+ ),
1124
+ ],
1125
+ examples=[
1126
+ # simplest: auto-pick first eligible luminance view
1127
+ "ctx.run_command('recombine_luminance', {'method': 'rec709'})",
1128
+ # specify a source by title
1129
+ "ctx.run_command('recombine_luminance', {'source_title':'M33 — Luminance', 'method':'rec709'})",
1130
+ # narrowband / max-style source
1131
+ "ctx.run_command('recombine_luminance', {'source_title':'NB Stars', 'method':'max', 'blend':1.0})",
1132
+ # custom weights + mild highlight protection
1133
+ "ctx.run_command('recombine_luminance', {'weights':[0.2,0.7,0.1], 'soft_knee':0.15})",
1134
+ ],
1135
+ supports_mono=False,
1136
+ supports_rgb=True,
1137
+ supports_linear=True,
1138
+ supports_nonlinear=True,
1139
+ ))
1140
+
1141
+ # ---------------- WaveScale family ----------------
1142
+
1143
+ register(CommandSpec(
1144
+ id="wavescale_hdr",
1145
+ name="WaveScale HDR",
1146
+ group="Contrast",
1147
+ import_path="pro.wavescale_hdr_preset",
1148
+ callable_name="run_wavescale_hdr_via_preset",
1149
+ ui_method="_open_wavescale_hdr", # or whatever your main window uses
1150
+ summary=(
1151
+ "Wavelet-based HDR compression. Builds a luminance mask and compresses "
1152
+ "coarse scales to recover dynamic range. Mask gamma controls protection."
1153
+ ),
1154
+ presets=[
1155
+ PresetSpec(
1156
+ key="n_scales",
1157
+ type="int",
1158
+ default=5,
1159
+ min=2, max=10,
1160
+ desc="Number of wavelet scales. Typical 4–6."
1161
+ ),
1162
+ PresetSpec(
1163
+ key="compression_factor",
1164
+ type="float",
1165
+ default=1.5,
1166
+ min=0.10, max=5.00,
1167
+ desc="Coarse-scale compression strength. Higher = more HDR."
1168
+ ),
1169
+ PresetSpec(
1170
+ key="mask_gamma",
1171
+ type="float",
1172
+ default=5.0,
1173
+ min=0.10, max=10.00,
1174
+ desc="Gamma shaping for the luminance protection mask."
1175
+ ),
1176
+ ],
1177
+ examples=[
1178
+ "ctx.run_command('wavescale_hdr', {'n_scales': 5, 'compression_factor': 1.5, 'mask_gamma': 5.0})",
1179
+ "ctx.run_command('wavescale_hdr', {'n_scales': 4, 'compression_factor': 2.2})",
1180
+ ],
1181
+ supports_mono=True,
1182
+ supports_rgb=True,
1183
+ supports_linear=True,
1184
+ supports_nonlinear=True,
1185
+ ))
1186
+
1187
+ register(CommandSpec(
1188
+ id="wavescale_dark_enhance",
1189
+ name="WaveScale Dark Enhance",
1190
+ group="Contrast",
1191
+ import_path="pro.wavescalede_preset",
1192
+ callable_name="run_wavescalede_via_preset",
1193
+ ui_method="_open_wavescale_dark_enhance", # adjust if your main window uses a different name
1194
+ summary=(
1195
+ "Wavelet-based enhancement of dark / low-contrast structures. "
1196
+ "Builds a luminance mask, boosts dark-scale content, and optionally iterates."
1197
+ ),
1198
+ presets=[
1199
+ PresetSpec(
1200
+ key="n_scales",
1201
+ type="int",
1202
+ default=6,
1203
+ min=2, max=10,
1204
+ desc="Number of wavelet scales. Typical 5–7."
1205
+ ),
1206
+ PresetSpec(
1207
+ key="boost_factor",
1208
+ type="float",
1209
+ default=5.0,
1210
+ min=0.1, max=10.0, # ✅ match preset dialog
1211
+ desc="Boost factor for dark structures."
1212
+ ),
1213
+ PresetSpec(
1214
+ key="mask_gamma",
1215
+ type="float",
1216
+ default=1.0,
1217
+ min=0.1, max=10.0,
1218
+ desc="Gamma shaping for the protection mask."
1219
+ ),
1220
+ PresetSpec(
1221
+ key="iterations",
1222
+ type="int",
1223
+ default=2,
1224
+ min=1, max=10,
1225
+ desc="Extra enhancement passes."
1226
+ ),
1227
+ ],
1228
+ examples=[
1229
+ "ctx.run_command('wavescale_dark_enhance', {'n_scales': 6, 'boost_factor': 5.0})",
1230
+ "ctx.run_command('wavescale_dark_enhance', {'n_scales': 7, 'boost_factor': 6.5, 'mask_gamma': 1.3, 'iterations': 3})",
1231
+ ],
1232
+ supports_mono=True,
1233
+ supports_rgb=True,
1234
+ supports_linear=True,
1235
+ supports_nonlinear=True,
1236
+ ))
1237
+
1238
+
1239
+ # ---------------- Geometry (headless) ----------------
1240
+
1241
+ register(CommandSpec(
1242
+ id="geom_invert",
1243
+ title="Invert",
1244
+ group="Geometry",
1245
+ headless_method="_apply_geom_invert_to_doc",
1246
+ ))
1247
+
1248
+ register(CommandSpec(
1249
+ id="geom_flip_horizontal",
1250
+ title="Flip Horizontal",
1251
+ group="Geometry",
1252
+ headless_method="_apply_geom_flip_h_to_doc",
1253
+ ))
1254
+
1255
+ register(CommandSpec(
1256
+ id="geom_flip_vertical",
1257
+ title="Flip Vertical",
1258
+ group="Geometry",
1259
+ headless_method="_apply_geom_flip_v_to_doc",
1260
+ ))
1261
+
1262
+ register(CommandSpec(
1263
+ id="geom_rotate_clockwise",
1264
+ title="Rotate 90° CW",
1265
+ group="Geometry",
1266
+ headless_method="_apply_geom_rot_cw_to_doc",
1267
+ ))
1268
+
1269
+ register(CommandSpec(
1270
+ id="geom_rotate_counterclockwise",
1271
+ title="Rotate 90° CCW",
1272
+ group="Geometry",
1273
+ headless_method="_apply_geom_rot_ccw_to_doc",
1274
+ ))
1275
+
1276
+ register(CommandSpec(
1277
+ id="geom_rotate_180",
1278
+ title="Rotate 180°",
1279
+ group="Geometry",
1280
+ headless_method="_apply_geom_rot_180_to_doc",
1281
+ ))
1282
+
1283
+ register(CommandSpec(
1284
+ id="geom_rescale",
1285
+ title="Rescale",
1286
+ group="Geometry",
1287
+ headless_method="_apply_geom_rescale_preset_to_doc",
1288
+ presets=[
1289
+ PresetSpec("factor", "float", default=1.0, min=0.05, max=20.0,
1290
+ desc="Scale multiplier."),
1291
+ ],
1292
+ ))
1293
+
1294
+ register(CommandSpec(
1295
+ id="aberration_ai",
1296
+ title="Aberration AI",
1297
+ group="Optics",
1298
+ import_path="pro.aberration_ai_preset",
1299
+ callable_name="run_aberration_ai_via_preset",
1300
+ # ui_method="open_aberration_ai_dialog", # if you have one; otherwise omit
1301
+ presets=[
1302
+ PresetSpec(
1303
+ "model", "path", default="",
1304
+ desc="Path to .onnx model. If empty, uses QSettings('AberrationAI/model_path')."
1305
+ ),
1306
+ PresetSpec(
1307
+ "patch", "int", default=512, min=128, max=2048,
1308
+ desc="Patch size for tiled inference. CoreML is clamped to 128."
1309
+ ),
1310
+ PresetSpec(
1311
+ "overlap", "int", default=64, min=16, max=512,
1312
+ desc="Overlap between patches."
1313
+ ),
1314
+ PresetSpec(
1315
+ "border_px", "int", default=10, min=0, max=64,
1316
+ desc="Border in pixels to preserve from the original."
1317
+ ),
1318
+ PresetSpec(
1319
+ "auto_gpu", "bool", default=True,
1320
+ desc="Auto-pick best GPU provider if available (forced off on Apple Silicon)."
1321
+ ),
1322
+ PresetSpec(
1323
+ "provider", "enum", default="CPUExecutionProvider",
1324
+ enum=[
1325
+ "CPUExecutionProvider",
1326
+ "DmlExecutionProvider",
1327
+ "CUDAExecutionProvider",
1328
+ "CoreMLExecutionProvider",
1329
+ ],
1330
+ desc="Explicit provider when auto_gpu=False."
1331
+ ),
1332
+ ],
1333
+ supports_mono=True,
1334
+ supports_rgb=True,
1335
+ ))
1336
+
1337
+ register(CommandSpec(
1338
+ id="convo",
1339
+ title="Convolution / Deconvolution",
1340
+ group="Blur & Sharpen",
1341
+ import_path="pro.convo_preset",
1342
+ callable_name="run_convo_via_preset",
1343
+ aliases=[
1344
+ "convolution",
1345
+ "deconvolution",
1346
+ "convo_deconvo", # keep backward compat
1347
+ ],
1348
+ presets=[
1349
+ PresetSpec("op", "enum", default="convolution",
1350
+ enum=["convolution", "deconvolution", "tv"],
1351
+ desc="Operation type."),
1352
+
1353
+ # shared strength (used by all ops)
1354
+ PresetSpec("strength", "float", default=1.0, min=0.0, max=1.0,
1355
+ desc="Blend strength."),
1356
+
1357
+ # --- convolution ---
1358
+ PresetSpec("radius", "float", default=5.0, min=0.1, max=200.0,
1359
+ desc="Convolution PSF radius (px)."),
1360
+ PresetSpec("kurtosis", "float", default=2.0, min=0.1, max=10.0,
1361
+ desc="Convolution PSF kurtosis (σ)."),
1362
+ PresetSpec("aspect", "float", default=1.0, min=0.1, max=10.0,
1363
+ desc="Convolution PSF aspect ratio."),
1364
+ PresetSpec("rotation", "float", default=0.0, min=0.0, max=360.0,
1365
+ desc="Convolution PSF rotation (deg)."),
1366
+
1367
+ # --- deconvolution general ---
1368
+ PresetSpec("algo", "enum", default="Richardson-Lucy",
1369
+ enum=["Richardson-Lucy", "Wiener", "Larson-Sekanina", "Van Cittert"],
1370
+ desc="Deconvolution algorithm."),
1371
+
1372
+ # RL/Wiener PSF
1373
+ PresetSpec("psf_radius", "float", default=3.0, min=0.1, max=100.0,
1374
+ desc="Deconvolution PSF radius (px)."),
1375
+ PresetSpec("psf_kurtosis", "float", default=2.0, min=0.1, max=10.0,
1376
+ desc="Deconvolution PSF kurtosis (σ)."),
1377
+ PresetSpec("psf_aspect", "float", default=1.0, min=0.1, max=10.0,
1378
+ desc="Deconvolution PSF aspect ratio."),
1379
+ PresetSpec("psf_rotation", "float", default=0.0, min=0.0, max=360.0,
1380
+ desc="Deconvolution PSF rotation (deg)."),
1381
+
1382
+ # RL options
1383
+ PresetSpec("rl_iter", "int", default=30, min=1, max=200,
1384
+ desc="Richardson–Lucy iterations."),
1385
+ PresetSpec("rl_reg", "enum", default="None (Plain R–L)",
1386
+ enum=["None (Plain R–L)", "Tikhonov (L2)", "Total Variation (TV)"],
1387
+ desc="RL regularization."),
1388
+ PresetSpec("rl_dering", "bool", default=True,
1389
+ desc="RL de-ring bilateral pass."),
1390
+ PresetSpec("luminance_only", "bool", default=True,
1391
+ desc="Run RL/Wiener on L* only."),
1392
+
1393
+ # Wiener options
1394
+ PresetSpec("wiener_nsr", "float", default=0.01, min=0.0, max=1.0,
1395
+ desc="Wiener NSR."),
1396
+ PresetSpec("wiener_reg", "enum", default="None (Classical Wiener)",
1397
+ enum=["None (Classical Wiener)", "Tikhonov (L2)"],
1398
+ desc="Wiener regularization."),
1399
+ PresetSpec("wiener_dering", "bool", default=True,
1400
+ desc="Wiener de-ring pass."),
1401
+
1402
+ # Larson–Sekanina
1403
+ PresetSpec("ls_rstep", "float", default=0.0, min=0.0, max=50.0,
1404
+ desc="LS radial step (px)."),
1405
+ PresetSpec("ls_astep", "float", default=1.0, min=0.1, max=360.0,
1406
+ desc="LS angular step (deg)."),
1407
+ PresetSpec("ls_operator", "enum", default="Divide",
1408
+ enum=["Divide", "Subtract"],
1409
+ desc="LS operator."),
1410
+ PresetSpec("ls_blend", "enum", default="SoftLight",
1411
+ enum=["SoftLight", "Screen"],
1412
+ desc="LS blend mode."),
1413
+
1414
+ # Van Cittert
1415
+ PresetSpec("vc_iter", "int", default=10, min=1, max=1000,
1416
+ desc="Van Cittert iterations."),
1417
+ PresetSpec("vc_relax", "float", default=0.0, min=0.0, max=1.0,
1418
+ desc="Van Cittert relaxation."),
1419
+
1420
+ # --- TV ---
1421
+ PresetSpec("tv_weight", "float", default=0.10, min=0.0, max=1.0,
1422
+ desc="TV denoise weight."),
1423
+ PresetSpec("tv_iter", "int", default=10, min=1, max=100,
1424
+ desc="TV iterations."),
1425
+ PresetSpec("tv_multichannel", "bool", default=True,
1426
+ desc="TV multi-channel."),
1427
+ ],
1428
+ supports_mono=True,
1429
+ supports_rgb=True,
1430
+ ))
1431
+
1432
+ register(CommandSpec(
1433
+ id="cosmic_clarity",
1434
+ title="Cosmic Clarity",
1435
+ group="AI",
1436
+ import_path="pro.cosmicclarity_preset",
1437
+ callable_name="run_cosmicclarity_via_preset",
1438
+ presets=[
1439
+ PresetSpec("mode", "enum", default="sharpen",
1440
+ enum=["sharpen", "denoise", "both", "superres"],
1441
+ desc="Which CC pipeline to run."),
1442
+
1443
+ PresetSpec("gpu", "bool", default=True,
1444
+ desc="Use GPU acceleration when available."),
1445
+
1446
+ PresetSpec("create_new_view", "bool", default=False,
1447
+ desc="Create new view instead of overwriting active."),
1448
+
1449
+ # --- Sharpen presets ---
1450
+ PresetSpec("sharpening_mode", "enum", default="Both",
1451
+ enum=["Both", "Stellar Only", "Non-Stellar Only"],
1452
+ desc="Sharpening mode."),
1453
+ PresetSpec("auto_psf", "bool", default=True,
1454
+ desc="Auto-detect PSF for non-stellar sharpening."),
1455
+ PresetSpec("nonstellar_psf", "float", default=3.0, min=1.0, max=8.0,
1456
+ desc="Non-stellar PSF strength (1–8)."),
1457
+ PresetSpec("stellar_amount", "float", default=0.50, min=0.0, max=1.0,
1458
+ desc="Stellar sharpening amount."),
1459
+ PresetSpec("nonstellar_amount", "float", default=0.50, min=0.0, max=1.0,
1460
+ desc="Non-stellar sharpening amount."),
1461
+ PresetSpec("sharpen_channels_separately", "bool", default=False,
1462
+ desc="Sharpen R/G/B separately (RGB only)."),
1463
+
1464
+ # --- Denoise presets ---
1465
+ PresetSpec("denoise_luma", "float", default=0.50, min=0.0, max=1.0,
1466
+ desc="Luminance denoise strength."),
1467
+ PresetSpec("denoise_color", "float", default=0.50, min=0.0, max=1.0,
1468
+ desc="Color denoise strength."),
1469
+ PresetSpec("denoise_mode", "enum", default="full",
1470
+ enum=["full", "luminance"],
1471
+ desc="Denoise mode."),
1472
+ PresetSpec("separate_channels", "bool", default=False,
1473
+ desc="Denoise RGB channels separately."),
1474
+
1475
+ # --- SuperRes presets ---
1476
+ PresetSpec("scale", "int", default=2, min=2, max=4,
1477
+ desc="Super-resolution scale factor."),
1478
+ ],
1479
+ supports_mono=True,
1480
+ supports_rgb=True,
1481
+ ))
1482
+
1483
+ register(CommandSpec(
1484
+ id="debayer",
1485
+ title="Debayer",
1486
+ group="Color / CFA",
1487
+ import_path="pro.debayer",
1488
+ callable_name="run_debayer_via_preset",
1489
+ presets=[
1490
+ PresetSpec(
1491
+ "pattern", "enum", default="auto",
1492
+ enum=["auto", "RGGB", "BGGR", "GRBG", "GBRG"],
1493
+ desc="Bayer pattern to use. 'auto' tries header then scoring."
1494
+ ),
1495
+ PresetSpec(
1496
+ "method", "enum", default="auto",
1497
+ enum=["auto", "edge", "bilinear", "AHD", "DHT"],
1498
+ desc="Debayer method. Edge/Bilinear for Bayer; AHD/DHT for X-Trans."
1499
+ ),
1500
+ ],
1501
+ supports_mono=True, # mosaic is mono input
1502
+ supports_rgb=False, # reject RGB in apply_debayer_preset_to_doc
1503
+ ))
1504
+
1505
+ register(CommandSpec(
1506
+ id="linear_fit",
1507
+ title="Linear Fit",
1508
+ group="Calibration",
1509
+ import_path="pro.linear_fit",
1510
+ callable_name="run_linear_fit_via_preset",
1511
+ presets=[
1512
+ PresetSpec(
1513
+ "rgb_mode_idx", "int", default=0, min=0, max=4,
1514
+ desc="RGB target strategy: 0 highest median, 1 lowest, 2 R, 3 G, 4 B."
1515
+ ),
1516
+ PresetSpec(
1517
+ "rescale_mode_idx", "int", default=1, min=0, max=2,
1518
+ desc="Out-of-range handling: 0 clip, 1 normalize if needed, 2 leave as-is."
1519
+ ),
1520
+ ],
1521
+ supports_mono=True,
1522
+ supports_rgb=True,
1523
+ replay_apply_name="apply_linear_fit_to_doc", # if your CommandSpec supports this hook
1524
+ ))
1525
+
1526
+ register(CommandSpec(
1527
+ id="morphology",
1528
+ title="Morphology",
1529
+ group="Masks & Morphology",
1530
+ import_path="pro.morphology",
1531
+ callable_name="apply_morphology_to_doc",
1532
+ presets=[
1533
+ PresetSpec(
1534
+ "operation", "enum",
1535
+ default="erosion",
1536
+ enum=["erosion", "dilation", "opening", "closing"],
1537
+ desc="Morphological operation."
1538
+ ),
1539
+ PresetSpec(
1540
+ "kernel", "int",
1541
+ default=3, min=1, max=31,
1542
+ desc="Kernel diameter (odd)."
1543
+ ),
1544
+ PresetSpec(
1545
+ "iterations", "int",
1546
+ default=1, min=1, max=10,
1547
+ desc="Number of iterations."
1548
+ ),
1549
+ ],
1550
+ supports_mono=True,
1551
+ supports_rgb=True,
1552
+ replay_apply_name="apply_morphology_to_doc", # if your CommandSpec supports it
1553
+ ))
1554
+
1555
+ register(CommandSpec(
1556
+ id="remove_stars",
1557
+ title="Remove Stars",
1558
+ group="Star Tools",
1559
+ import_path="pro.remove_stars_preset",
1560
+ callable_name="run_remove_stars_via_preset",
1561
+ replay_apply_name="apply_remove_stars_to_doc",
1562
+ presets=[
1563
+ PresetSpec("tool", "enum", default="starnet",
1564
+ enum=["starnet", "darkstar"],
1565
+ desc="Which star removal engine to use."),
1566
+
1567
+ # StarNet
1568
+ PresetSpec("linear", "bool", default=True,
1569
+ desc="Temporary stretch before StarNet then unstretch."),
1570
+ PresetSpec("starnet_exe", "path", default="",
1571
+ desc="Optional StarNet exe override; else uses QSettings."),
1572
+
1573
+ # DarkStar
1574
+ PresetSpec("disable_gpu", "bool", default=False,
1575
+ desc="Disable GPU for DarkStar."),
1576
+ PresetSpec("mode", "enum", default="unscreen",
1577
+ enum=["unscreen", "additive"],
1578
+ desc="DarkStar blending mode."),
1579
+ PresetSpec("show_extracted_stars", "bool", default=True,
1580
+ desc="If DarkStar produced stars-only, open it as a new view."),
1581
+ PresetSpec("stride", "int", default=512, min=64, max=1024,
1582
+ desc="Chunk/stride size for DarkStar."),
1583
+ PresetSpec("darkstar_exe", "path", default="",
1584
+ desc="Optional DarkStar exe override; else uses CosmicClarity root."),
1585
+ ],
1586
+ supports_mono=True,
1587
+ supports_rgb=True,
1588
+ ))
1589
+
1590
+
1591
+
1592
+ # -----------------------------------------------------------------------------
1593
+ # End of file
1594
+ # -----------------------------------------------------------------------------