setiastrosuitepro 1.6.1__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 (342) hide show
  1. setiastro/__init__.py +2 -0
  2. setiastro/data/SASP_data.fits +0 -0
  3. setiastro/data/catalogs/List_of_Galaxies_with_Distances_Gly.csv +488 -0
  4. setiastro/data/catalogs/astrobin_filters.csv +890 -0
  5. setiastro/data/catalogs/astrobin_filters_page1_local.csv +51 -0
  6. setiastro/data/catalogs/cali2.csv +63 -0
  7. setiastro/data/catalogs/cali2color.csv +65 -0
  8. setiastro/data/catalogs/celestial_catalog - original.csv +16471 -0
  9. setiastro/data/catalogs/celestial_catalog.csv +24031 -0
  10. setiastro/data/catalogs/detected_stars.csv +24784 -0
  11. setiastro/data/catalogs/fits_header_data.csv +46 -0
  12. setiastro/data/catalogs/test.csv +8 -0
  13. setiastro/data/catalogs/updated_celestial_catalog.csv +16471 -0
  14. setiastro/images/Astro_Spikes.png +0 -0
  15. setiastro/images/HRDiagram.png +0 -0
  16. setiastro/images/LExtract.png +0 -0
  17. setiastro/images/LInsert.png +0 -0
  18. setiastro/images/Oxygenation-atm-2.svg.png +0 -0
  19. setiastro/images/RGB080604.png +0 -0
  20. setiastro/images/abeicon.png +0 -0
  21. setiastro/images/aberration.png +0 -0
  22. setiastro/images/andromedatry.png +0 -0
  23. setiastro/images/andromedatry_satellited.png +0 -0
  24. setiastro/images/annotated.png +0 -0
  25. setiastro/images/aperture.png +0 -0
  26. setiastro/images/astrosuite.ico +0 -0
  27. setiastro/images/astrosuite.png +0 -0
  28. setiastro/images/astrosuitepro.icns +0 -0
  29. setiastro/images/astrosuitepro.ico +0 -0
  30. setiastro/images/astrosuitepro.png +0 -0
  31. setiastro/images/background.png +0 -0
  32. setiastro/images/background2.png +0 -0
  33. setiastro/images/benchmark.png +0 -0
  34. setiastro/images/big_moon_stabilizer_timeline.png +0 -0
  35. setiastro/images/big_moon_stabilizer_timeline_clean.png +0 -0
  36. setiastro/images/blaster.png +0 -0
  37. setiastro/images/blink.png +0 -0
  38. setiastro/images/clahe.png +0 -0
  39. setiastro/images/collage.png +0 -0
  40. setiastro/images/colorwheel.png +0 -0
  41. setiastro/images/contsub.png +0 -0
  42. setiastro/images/convo.png +0 -0
  43. setiastro/images/copyslot.png +0 -0
  44. setiastro/images/cosmic.png +0 -0
  45. setiastro/images/cosmicsat.png +0 -0
  46. setiastro/images/crop1.png +0 -0
  47. setiastro/images/cropicon.png +0 -0
  48. setiastro/images/curves.png +0 -0
  49. setiastro/images/cvs.png +0 -0
  50. setiastro/images/debayer.png +0 -0
  51. setiastro/images/denoise_cnn_custom.png +0 -0
  52. setiastro/images/denoise_cnn_graph.png +0 -0
  53. setiastro/images/disk.png +0 -0
  54. setiastro/images/dse.png +0 -0
  55. setiastro/images/exoicon.png +0 -0
  56. setiastro/images/eye.png +0 -0
  57. setiastro/images/fliphorizontal.png +0 -0
  58. setiastro/images/flipvertical.png +0 -0
  59. setiastro/images/font.png +0 -0
  60. setiastro/images/freqsep.png +0 -0
  61. setiastro/images/functionbundle.png +0 -0
  62. setiastro/images/graxpert.png +0 -0
  63. setiastro/images/green.png +0 -0
  64. setiastro/images/gridicon.png +0 -0
  65. setiastro/images/halo.png +0 -0
  66. setiastro/images/hdr.png +0 -0
  67. setiastro/images/histogram.png +0 -0
  68. setiastro/images/hubble.png +0 -0
  69. setiastro/images/imagecombine.png +0 -0
  70. setiastro/images/invert.png +0 -0
  71. setiastro/images/isophote.png +0 -0
  72. setiastro/images/isophote_demo_figure.png +0 -0
  73. setiastro/images/isophote_demo_image.png +0 -0
  74. setiastro/images/isophote_demo_model.png +0 -0
  75. setiastro/images/isophote_demo_residual.png +0 -0
  76. setiastro/images/jwstpupil.png +0 -0
  77. setiastro/images/linearfit.png +0 -0
  78. setiastro/images/livestacking.png +0 -0
  79. setiastro/images/mask.png +0 -0
  80. setiastro/images/maskapply.png +0 -0
  81. setiastro/images/maskcreate.png +0 -0
  82. setiastro/images/maskremove.png +0 -0
  83. setiastro/images/morpho.png +0 -0
  84. setiastro/images/mosaic.png +0 -0
  85. setiastro/images/multiscale_decomp.png +0 -0
  86. setiastro/images/nbtorgb.png +0 -0
  87. setiastro/images/neutral.png +0 -0
  88. setiastro/images/nuke.png +0 -0
  89. setiastro/images/openfile.png +0 -0
  90. setiastro/images/pedestal.png +0 -0
  91. setiastro/images/pen.png +0 -0
  92. setiastro/images/pixelmath.png +0 -0
  93. setiastro/images/platesolve.png +0 -0
  94. setiastro/images/ppp.png +0 -0
  95. setiastro/images/pro.png +0 -0
  96. setiastro/images/project.png +0 -0
  97. setiastro/images/psf.png +0 -0
  98. setiastro/images/redo.png +0 -0
  99. setiastro/images/redoicon.png +0 -0
  100. setiastro/images/rescale.png +0 -0
  101. setiastro/images/rgbalign.png +0 -0
  102. setiastro/images/rgbcombo.png +0 -0
  103. setiastro/images/rgbextract.png +0 -0
  104. setiastro/images/rotate180.png +0 -0
  105. setiastro/images/rotateclockwise.png +0 -0
  106. setiastro/images/rotatecounterclockwise.png +0 -0
  107. setiastro/images/satellite.png +0 -0
  108. setiastro/images/script.png +0 -0
  109. setiastro/images/selectivecolor.png +0 -0
  110. setiastro/images/simbad.png +0 -0
  111. setiastro/images/slot0.png +0 -0
  112. setiastro/images/slot1.png +0 -0
  113. setiastro/images/slot2.png +0 -0
  114. setiastro/images/slot3.png +0 -0
  115. setiastro/images/slot4.png +0 -0
  116. setiastro/images/slot5.png +0 -0
  117. setiastro/images/slot6.png +0 -0
  118. setiastro/images/slot7.png +0 -0
  119. setiastro/images/slot8.png +0 -0
  120. setiastro/images/slot9.png +0 -0
  121. setiastro/images/spcc.png +0 -0
  122. setiastro/images/spin_precession_vs_lunar_distance.png +0 -0
  123. setiastro/images/spinner.gif +0 -0
  124. setiastro/images/stacking.png +0 -0
  125. setiastro/images/staradd.png +0 -0
  126. setiastro/images/staralign.png +0 -0
  127. setiastro/images/starnet.png +0 -0
  128. setiastro/images/starregistration.png +0 -0
  129. setiastro/images/starspike.png +0 -0
  130. setiastro/images/starstretch.png +0 -0
  131. setiastro/images/statstretch.png +0 -0
  132. setiastro/images/supernova.png +0 -0
  133. setiastro/images/uhs.png +0 -0
  134. setiastro/images/undoicon.png +0 -0
  135. setiastro/images/upscale.png +0 -0
  136. setiastro/images/viewbundle.png +0 -0
  137. setiastro/images/whitebalance.png +0 -0
  138. setiastro/images/wimi_icon_256x256.png +0 -0
  139. setiastro/images/wimilogo.png +0 -0
  140. setiastro/images/wims.png +0 -0
  141. setiastro/images/wrench_icon.png +0 -0
  142. setiastro/images/xisfliberator.png +0 -0
  143. setiastro/saspro/__init__.py +20 -0
  144. setiastro/saspro/__main__.py +809 -0
  145. setiastro/saspro/_generated/__init__.py +7 -0
  146. setiastro/saspro/_generated/build_info.py +2 -0
  147. setiastro/saspro/abe.py +1295 -0
  148. setiastro/saspro/abe_preset.py +196 -0
  149. setiastro/saspro/aberration_ai.py +694 -0
  150. setiastro/saspro/aberration_ai_preset.py +224 -0
  151. setiastro/saspro/accel_installer.py +218 -0
  152. setiastro/saspro/accel_workers.py +30 -0
  153. setiastro/saspro/add_stars.py +621 -0
  154. setiastro/saspro/astrobin_exporter.py +1007 -0
  155. setiastro/saspro/astrospike.py +153 -0
  156. setiastro/saspro/astrospike_python.py +1839 -0
  157. setiastro/saspro/autostretch.py +196 -0
  158. setiastro/saspro/backgroundneutral.py +560 -0
  159. setiastro/saspro/batch_convert.py +325 -0
  160. setiastro/saspro/batch_renamer.py +519 -0
  161. setiastro/saspro/blemish_blaster.py +488 -0
  162. setiastro/saspro/blink_comparator_pro.py +2926 -0
  163. setiastro/saspro/bundles.py +61 -0
  164. setiastro/saspro/bundles_dock.py +114 -0
  165. setiastro/saspro/cheat_sheet.py +178 -0
  166. setiastro/saspro/clahe.py +342 -0
  167. setiastro/saspro/comet_stacking.py +1377 -0
  168. setiastro/saspro/common_tr.py +107 -0
  169. setiastro/saspro/config.py +38 -0
  170. setiastro/saspro/config_bootstrap.py +40 -0
  171. setiastro/saspro/config_manager.py +316 -0
  172. setiastro/saspro/continuum_subtract.py +1617 -0
  173. setiastro/saspro/convo.py +1397 -0
  174. setiastro/saspro/convo_preset.py +414 -0
  175. setiastro/saspro/copyastro.py +187 -0
  176. setiastro/saspro/cosmicclarity.py +1564 -0
  177. setiastro/saspro/cosmicclarity_preset.py +407 -0
  178. setiastro/saspro/crop_dialog_pro.py +956 -0
  179. setiastro/saspro/crop_preset.py +189 -0
  180. setiastro/saspro/curve_editor_pro.py +2544 -0
  181. setiastro/saspro/curves_preset.py +375 -0
  182. setiastro/saspro/debayer.py +670 -0
  183. setiastro/saspro/debug_utils.py +29 -0
  184. setiastro/saspro/dnd_mime.py +35 -0
  185. setiastro/saspro/doc_manager.py +2641 -0
  186. setiastro/saspro/exoplanet_detector.py +2166 -0
  187. setiastro/saspro/file_utils.py +284 -0
  188. setiastro/saspro/fitsmodifier.py +745 -0
  189. setiastro/saspro/fix_bom.py +32 -0
  190. setiastro/saspro/free_torch_memory.py +48 -0
  191. setiastro/saspro/frequency_separation.py +1343 -0
  192. setiastro/saspro/function_bundle.py +1594 -0
  193. setiastro/saspro/generate_translations.py +2378 -0
  194. setiastro/saspro/ghs_dialog_pro.py +660 -0
  195. setiastro/saspro/ghs_preset.py +284 -0
  196. setiastro/saspro/graxpert.py +634 -0
  197. setiastro/saspro/graxpert_preset.py +287 -0
  198. setiastro/saspro/gui/__init__.py +0 -0
  199. setiastro/saspro/gui/main_window.py +8567 -0
  200. setiastro/saspro/gui/mixins/__init__.py +33 -0
  201. setiastro/saspro/gui/mixins/dock_mixin.py +263 -0
  202. setiastro/saspro/gui/mixins/file_mixin.py +443 -0
  203. setiastro/saspro/gui/mixins/geometry_mixin.py +403 -0
  204. setiastro/saspro/gui/mixins/header_mixin.py +441 -0
  205. setiastro/saspro/gui/mixins/mask_mixin.py +421 -0
  206. setiastro/saspro/gui/mixins/menu_mixin.py +361 -0
  207. setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
  208. setiastro/saspro/gui/mixins/toolbar_mixin.py +1457 -0
  209. setiastro/saspro/gui/mixins/update_mixin.py +309 -0
  210. setiastro/saspro/gui/mixins/view_mixin.py +435 -0
  211. setiastro/saspro/halobgon.py +462 -0
  212. setiastro/saspro/header_viewer.py +448 -0
  213. setiastro/saspro/headless_utils.py +88 -0
  214. setiastro/saspro/histogram.py +753 -0
  215. setiastro/saspro/history_explorer.py +939 -0
  216. setiastro/saspro/i18n.py +156 -0
  217. setiastro/saspro/image_combine.py +414 -0
  218. setiastro/saspro/image_peeker_pro.py +1601 -0
  219. setiastro/saspro/imageops/__init__.py +37 -0
  220. setiastro/saspro/imageops/mdi_snap.py +292 -0
  221. setiastro/saspro/imageops/scnr.py +36 -0
  222. setiastro/saspro/imageops/starbasedwhitebalance.py +210 -0
  223. setiastro/saspro/imageops/stretch.py +244 -0
  224. setiastro/saspro/isophote.py +1179 -0
  225. setiastro/saspro/layers.py +208 -0
  226. setiastro/saspro/layers_dock.py +714 -0
  227. setiastro/saspro/lazy_imports.py +193 -0
  228. setiastro/saspro/legacy/__init__.py +2 -0
  229. setiastro/saspro/legacy/image_manager.py +2226 -0
  230. setiastro/saspro/legacy/numba_utils.py +3659 -0
  231. setiastro/saspro/legacy/xisf.py +1071 -0
  232. setiastro/saspro/linear_fit.py +534 -0
  233. setiastro/saspro/live_stacking.py +1830 -0
  234. setiastro/saspro/log_bus.py +5 -0
  235. setiastro/saspro/logging_config.py +460 -0
  236. setiastro/saspro/luminancerecombine.py +309 -0
  237. setiastro/saspro/main_helpers.py +201 -0
  238. setiastro/saspro/mask_creation.py +928 -0
  239. setiastro/saspro/masks_core.py +56 -0
  240. setiastro/saspro/mdi_widgets.py +353 -0
  241. setiastro/saspro/memory_utils.py +666 -0
  242. setiastro/saspro/metadata_patcher.py +75 -0
  243. setiastro/saspro/mfdeconv.py +3826 -0
  244. setiastro/saspro/mfdeconv_earlystop.py +71 -0
  245. setiastro/saspro/mfdeconvcudnn.py +3263 -0
  246. setiastro/saspro/mfdeconvsport.py +2382 -0
  247. setiastro/saspro/minorbodycatalog.py +567 -0
  248. setiastro/saspro/morphology.py +382 -0
  249. setiastro/saspro/multiscale_decomp.py +1290 -0
  250. setiastro/saspro/nbtorgb_stars.py +531 -0
  251. setiastro/saspro/numba_utils.py +3044 -0
  252. setiastro/saspro/numba_warmup.py +141 -0
  253. setiastro/saspro/ops/__init__.py +9 -0
  254. setiastro/saspro/ops/command_help_dialog.py +623 -0
  255. setiastro/saspro/ops/command_runner.py +217 -0
  256. setiastro/saspro/ops/commands.py +1594 -0
  257. setiastro/saspro/ops/script_editor.py +1102 -0
  258. setiastro/saspro/ops/scripts.py +1413 -0
  259. setiastro/saspro/ops/settings.py +679 -0
  260. setiastro/saspro/parallel_utils.py +554 -0
  261. setiastro/saspro/pedestal.py +121 -0
  262. setiastro/saspro/perfect_palette_picker.py +1070 -0
  263. setiastro/saspro/pipeline.py +110 -0
  264. setiastro/saspro/pixelmath.py +1600 -0
  265. setiastro/saspro/plate_solver.py +2444 -0
  266. setiastro/saspro/project_io.py +797 -0
  267. setiastro/saspro/psf_utils.py +136 -0
  268. setiastro/saspro/psf_viewer.py +549 -0
  269. setiastro/saspro/pyi_rthook_astroquery.py +95 -0
  270. setiastro/saspro/remove_green.py +314 -0
  271. setiastro/saspro/remove_stars.py +1625 -0
  272. setiastro/saspro/remove_stars_preset.py +404 -0
  273. setiastro/saspro/resources.py +477 -0
  274. setiastro/saspro/rgb_combination.py +207 -0
  275. setiastro/saspro/rgb_extract.py +19 -0
  276. setiastro/saspro/rgbalign.py +723 -0
  277. setiastro/saspro/runtime_imports.py +7 -0
  278. setiastro/saspro/runtime_torch.py +754 -0
  279. setiastro/saspro/save_options.py +72 -0
  280. setiastro/saspro/selective_color.py +1552 -0
  281. setiastro/saspro/sfcc.py +1430 -0
  282. setiastro/saspro/shortcuts.py +3043 -0
  283. setiastro/saspro/signature_insert.py +1099 -0
  284. setiastro/saspro/stacking_suite.py +18181 -0
  285. setiastro/saspro/star_alignment.py +7420 -0
  286. setiastro/saspro/star_alignment_preset.py +329 -0
  287. setiastro/saspro/star_metrics.py +49 -0
  288. setiastro/saspro/star_spikes.py +681 -0
  289. setiastro/saspro/star_stretch.py +470 -0
  290. setiastro/saspro/stat_stretch.py +506 -0
  291. setiastro/saspro/status_log_dock.py +78 -0
  292. setiastro/saspro/subwindow.py +3267 -0
  293. setiastro/saspro/supernovaasteroidhunter.py +1716 -0
  294. setiastro/saspro/swap_manager.py +99 -0
  295. setiastro/saspro/torch_backend.py +89 -0
  296. setiastro/saspro/torch_rejection.py +434 -0
  297. setiastro/saspro/translations/de_translations.py +3733 -0
  298. setiastro/saspro/translations/es_translations.py +3923 -0
  299. setiastro/saspro/translations/fr_translations.py +3842 -0
  300. setiastro/saspro/translations/integrate_translations.py +234 -0
  301. setiastro/saspro/translations/it_translations.py +3662 -0
  302. setiastro/saspro/translations/ja_translations.py +3585 -0
  303. setiastro/saspro/translations/pt_translations.py +3853 -0
  304. setiastro/saspro/translations/saspro_de.qm +0 -0
  305. setiastro/saspro/translations/saspro_de.ts +253 -0
  306. setiastro/saspro/translations/saspro_es.qm +0 -0
  307. setiastro/saspro/translations/saspro_es.ts +12520 -0
  308. setiastro/saspro/translations/saspro_fr.qm +0 -0
  309. setiastro/saspro/translations/saspro_fr.ts +12514 -0
  310. setiastro/saspro/translations/saspro_it.qm +0 -0
  311. setiastro/saspro/translations/saspro_it.ts +12520 -0
  312. setiastro/saspro/translations/saspro_ja.qm +0 -0
  313. setiastro/saspro/translations/saspro_ja.ts +257 -0
  314. setiastro/saspro/translations/saspro_pt.qm +0 -0
  315. setiastro/saspro/translations/saspro_pt.ts +257 -0
  316. setiastro/saspro/translations/saspro_zh.qm +0 -0
  317. setiastro/saspro/translations/saspro_zh.ts +12520 -0
  318. setiastro/saspro/translations/zh_translations.py +3659 -0
  319. setiastro/saspro/versioning.py +71 -0
  320. setiastro/saspro/view_bundle.py +1555 -0
  321. setiastro/saspro/wavescale_hdr.py +624 -0
  322. setiastro/saspro/wavescale_hdr_preset.py +101 -0
  323. setiastro/saspro/wavescalede.py +658 -0
  324. setiastro/saspro/wavescalede_preset.py +230 -0
  325. setiastro/saspro/wcs_update.py +374 -0
  326. setiastro/saspro/whitebalance.py +456 -0
  327. setiastro/saspro/widgets/__init__.py +48 -0
  328. setiastro/saspro/widgets/common_utilities.py +306 -0
  329. setiastro/saspro/widgets/graphics_views.py +122 -0
  330. setiastro/saspro/widgets/image_utils.py +518 -0
  331. setiastro/saspro/widgets/preview_dialogs.py +280 -0
  332. setiastro/saspro/widgets/spinboxes.py +275 -0
  333. setiastro/saspro/widgets/themed_buttons.py +13 -0
  334. setiastro/saspro/widgets/wavelet_utils.py +299 -0
  335. setiastro/saspro/window_shelf.py +185 -0
  336. setiastro/saspro/xisf.py +1123 -0
  337. setiastrosuitepro-1.6.1.dist-info/METADATA +267 -0
  338. setiastrosuitepro-1.6.1.dist-info/RECORD +342 -0
  339. setiastrosuitepro-1.6.1.dist-info/WHEEL +4 -0
  340. setiastrosuitepro-1.6.1.dist-info/entry_points.txt +6 -0
  341. setiastrosuitepro-1.6.1.dist-info/licenses/LICENSE +674 -0
  342. setiastrosuitepro-1.6.1.dist-info/licenses/license.txt +2580 -0
@@ -0,0 +1,284 @@
1
+ # pro/ghs_preset.py
2
+ from __future__ import annotations
3
+ from typing import Dict, List, Tuple
4
+ import numpy as np
5
+
6
+ # Reuse LUT + apply engine from Curves editor (with a safe fallback import)
7
+ from setiastro.saspro.curve_editor_pro import build_curve_lut, _apply_mode_any # if your file name lacks the 's'
8
+
9
+ # Optional PCHIP interpolation (nice-to-have; we’ll fall back to np.interp)
10
+ try:
11
+ from scipy.interpolate import PchipInterpolator as _PCHIP
12
+ _HAS_PCHIP = True
13
+ except Exception:
14
+ _HAS_PCHIP = False
15
+
16
+
17
+ # ---------- helpers: points -> scene -> interpolator -> LUT ----------
18
+
19
+ def _points_norm_to_scene(points_norm: List[Tuple[float, float]]) -> List[Tuple[float, float]]:
20
+ """(x:[0..1], y:[0..1 up]) → scene coords (x:[0..360], y:[0..360] down)."""
21
+ pts = []
22
+ for x, y in points_norm:
23
+ x = float(max(0.0, min(1.0, x)))
24
+ y = float(max(0.0, min(1.0, y)))
25
+ xs = 360.0 * x
26
+ ys = 360.0 * (1.0 - y)
27
+ pts.append((xs, ys))
28
+ # Ensure strict endpoints exist
29
+ if not any(abs(px - 0.0) < 1e-6 for px, _ in pts): pts.append((0.0, 360.0))
30
+ if not any(abs(px - 360.0) < 1e-6 for px, _ in pts): pts.append((360.0, 0.0))
31
+ pts = sorted(pts, key=lambda t: t[0])
32
+ # Make X strictly increasing
33
+ out, lastx = [], -1e9
34
+ for x, y in pts:
35
+ if x <= lastx: x = lastx + 1e-3
36
+ out.append((min(360.0, max(0.0, x)), min(360.0, max(0.0, y))))
37
+ lastx = out[-1][0]
38
+ return out
39
+
40
+ def _interpolator_from_scene_points(points_scene: List[Tuple[float, float]]):
41
+ xs = np.array([p[0] for p in points_scene], dtype=np.float64)
42
+ ys = np.array([p[1] for p in points_scene], dtype=np.float64)
43
+ if _HAS_PCHIP and xs.size >= 2:
44
+ return _PCHIP(xs, ys)
45
+ def _lin(x):
46
+ return np.interp(x, xs, ys)
47
+ return _lin
48
+
49
+ def _norm_channel(ch: str | None) -> str:
50
+ m = (ch or "K (Brightness)").strip().lower()
51
+ alias = {"k":"K (Brightness)","brightness":"K (Brightness)","rgb":"K (Brightness)",
52
+ "r":"R","g":"G","b":"B"}
53
+ proper = {"k (brightness)":"K (Brightness)","r":"R","g":"G","b":"B"}
54
+ if m in proper: return proper[m]
55
+ return alias.get(m, "K (Brightness)")
56
+
57
+
58
+ # ---------- GHS parameterization → normalized control curve ----------
59
+
60
+ def _ghs_points_norm(alpha: float, beta: float, gamma: float,
61
+ pivot: float, lp: float, hp: float,
62
+ N: int = 128) -> List[Tuple[float, float]]:
63
+ """
64
+ Returns a monotone set of (x,y) in [0..1] implementing your dialog’s math.
65
+ """
66
+ a = float(np.clip(alpha, 0.02, 10.0)) # sliders 1..500 → /50 gives 0.02..10
67
+ b = float(np.clip(beta, 0.02, 10.0))
68
+ g = float(np.clip(gamma, 0.01, 5.0)) # slider 1..500 → /100 gives 0.01..5
69
+ SP = float(np.clip(pivot, 0.0, 1.0))
70
+ LP = float(np.clip(lp, 0.0, 1.0))
71
+ HP = float(np.clip(hp, 0.0, 1.0))
72
+
73
+ us = np.linspace(0.0, 1.0, int(max(16, N)))
74
+ left = us <= 0.5
75
+ right = ~left
76
+
77
+ # Generalized hyperbolic halves around 0.5
78
+ rawL = us**a / (us**a + b*(1.0-us)**a)
79
+ rawR = us**a / (us**a + (1.0/b)*(1.0-us)**a)
80
+
81
+ midL = (0.5**a) / (0.5**a + b*(0.5)**a)
82
+ midR = (0.5**a) / (0.5**a + (1.0/b)*(0.5)**a)
83
+ eps = 1e-6
84
+
85
+ # Domain remap to pivot SP
86
+ up = np.empty_like(us)
87
+ vp = np.empty_like(us)
88
+
89
+ # Left → [0..SP]
90
+ up[left] = 2.0 * SP * us[left]
91
+ vp[left] = rawL[left] * (SP / max(midL, eps))
92
+
93
+ # Right → [SP..1]
94
+ up[right] = SP + 2.0*(1.0 - SP)*(us[right] - 0.5)
95
+ vp[right] = SP + (rawR[right] - midR) * ((1.0 - SP) / max(1.0 - midR, eps))
96
+
97
+ # LP/HP protection (blend toward identity y=x on each side)
98
+ if LP > 0:
99
+ m = up <= SP
100
+ vp[m] = (1.0 - LP)*vp[m] + LP*up[m]
101
+ if HP > 0:
102
+ m = up >= SP
103
+ vp[m] = (1.0 - HP)*vp[m] + HP*up[m]
104
+
105
+ # Gamma lift
106
+ if abs(g - 1.0) > 1e-6:
107
+ vp = np.clip(vp, 0.0, 1.0) ** (1.0 / g)
108
+
109
+ # Clamp + enforce monotonicity (guards against tiny numerical dips)
110
+ vp = np.clip(vp, 0.0, 1.0)
111
+ vp = np.maximum.accumulate(vp)
112
+
113
+ pts = list(zip(up.tolist(), vp.tolist()))
114
+ # Make sure endpoints exist exactly
115
+ if pts[0][0] != 0.0: pts.insert(0, (0.0, 0.0))
116
+ if pts[-1][0] != 1.0: pts.append((1.0, 1.0))
117
+ return pts
118
+
119
+
120
+ def _lut_from_ghs_preset(preset: Dict) -> tuple[np.ndarray, str, Dict]:
121
+ p = dict(preset or {})
122
+ alpha = float(p.get("alpha", 1.0))
123
+ beta = float(p.get("beta", 1.0))
124
+ gamma = float(p.get("gamma", 1.0))
125
+ pivot = float(p.get("pivot", 0.5))
126
+ lp = float(p.get("lp", 0.0))
127
+ hp = float(p.get("hp", 0.0))
128
+ ch = _norm_channel(p.get("channel", "K (Brightness)"))
129
+
130
+ ptsN = _ghs_points_norm(alpha, beta, gamma, pivot, lp, hp, N=128)
131
+ ptsS = _points_norm_to_scene(ptsN)
132
+ fn = _interpolator_from_scene_points(ptsS)
133
+ lut01 = build_curve_lut(fn, size=65536)
134
+
135
+ # sanitized params for metadata
136
+ params = {"alpha":alpha, "beta":beta, "gamma":gamma, "pivot":pivot, "lp":lp, "hp":hp, "channel":ch}
137
+ return lut01, ch, params
138
+
139
+
140
+ # ---------- optional: mask-aware blend (same semantics as your dialogs) ----------
141
+
142
+ def _active_mask_layer(doc):
143
+ mid = getattr(doc, "active_mask_id", None)
144
+ if not mid: return None, None, None
145
+ layer = getattr(doc, "masks", {}).get(mid)
146
+ if layer is None: return None, None, None
147
+ m = np.asarray(getattr(layer, "data", None))
148
+ if m is None or m.size == 0: return None, None, None
149
+ m = m.astype(np.float32, copy=False)
150
+ if m.dtype.kind in "ui":
151
+ m /= float(np.iinfo(m.dtype).max)
152
+ else:
153
+ mx = float(m.max()) if m.size else 1.0
154
+ if mx > 1.0: m /= mx
155
+ return np.clip(m, 0.0, 1.0), mid, getattr(layer, "name", "Mask")
156
+
157
+ def _resample_mask(mask: np.ndarray, out_hw: tuple[int,int]) -> np.ndarray:
158
+ mh, mw = mask.shape[:2]
159
+ th, tw = out_hw
160
+ if (mh, mw) == (th, tw): return mask
161
+ yi = np.linspace(0, mh - 1, th).astype(np.int32)
162
+ xi = np.linspace(0, mw - 1, tw).astype(np.int32)
163
+ return mask[yi][:, xi]
164
+
165
+ def _blend_with_mask(processed: np.ndarray, src: np.ndarray, doc) -> np.ndarray:
166
+ mask, _, _ = _active_mask_layer(doc)
167
+ if mask is None:
168
+ return processed
169
+ out = processed.astype(np.float32, copy=False)
170
+ m = _resample_mask(mask, out.shape[:2])
171
+ if out.ndim == 3 and out.shape[2] == 3:
172
+ m = m[..., None]
173
+ s = src
174
+ if s.ndim == 2 and out.ndim == 3:
175
+ s = np.stack([s]*3, axis=-1)
176
+ elif s.ndim == 3 and out.ndim == 2:
177
+ s = s[..., 0]
178
+ return (m * out + (1.0 - m) * s).astype(np.float32, copy=False)
179
+
180
+
181
+ # ---------- headless apply ----------
182
+
183
+ def apply_ghs_via_preset(main_window, doc, preset: Dict):
184
+ """
185
+ Headless Universal Hyperbolic Stretch:
186
+ - builds the curve from α/β/γ + pivot + LP/HP
187
+ - applies to K/R/G/B channel
188
+ - blends with active mask if any
189
+ - commits to document with metadata
190
+ """
191
+ img = getattr(doc, "image", None)
192
+ if img is None:
193
+ return
194
+
195
+ arr = np.asarray(img)
196
+
197
+ # normalize to float01
198
+ if arr.dtype.kind in "ui":
199
+ src01 = arr.astype(np.float32) / np.iinfo(arr.dtype).max
200
+ elif arr.dtype.kind == "f":
201
+ mx = float(arr.max()) if arr.size else 1.0
202
+ src01 = (arr / (mx if mx > 1.0 else 1.0)).astype(np.float32)
203
+ else:
204
+ src01 = arr.astype(np.float32)
205
+
206
+ lut01, channel, params = _lut_from_ghs_preset(preset or {})
207
+ out01 = _apply_mode_any(src01, channel, lut01)
208
+
209
+ # 🔁 Remember this as the last headless command for Replay
210
+ try:
211
+ remember = getattr(main_window, "remember_last_headless_command", None)
212
+ if remember is None:
213
+ remember = getattr(main_window, "_remember_last_headless_command", None)
214
+
215
+ if callable(remember):
216
+ # store the sanitized params we just used
217
+ remember("ghs", params, description="Hyperbolic Stretch")
218
+
219
+ # optional debug
220
+ try:
221
+ if hasattr(main_window, "_log"):
222
+ main_window._log(
223
+ f"[Replay] GHS headless stored: command_id='ghs', "
224
+ f"preset_keys={list(params.keys())}"
225
+ )
226
+ except Exception:
227
+ pass
228
+ except Exception:
229
+ # don’t block the stretch if remembering fails
230
+ pass
231
+
232
+ # Mask-aware blend, same semantics as dialog
233
+ mask, mid, mname = _active_mask_layer(doc)
234
+ if mask is not None:
235
+ out01 = _blend_with_mask(out01, src01, doc)
236
+
237
+ meta = {
238
+ "step_name": "Hyperbolic Stretch",
239
+ "ghs": params,
240
+ "masked": bool(mid),
241
+ "mask_id": mid,
242
+ "mask_name": mname,
243
+ "mask_blend": "m*out + (1-m)*src",
244
+ }
245
+
246
+ doc.apply_edit(
247
+ out01.astype(np.float32, copy=False),
248
+ metadata=meta,
249
+ step_name="Hyperbolic Stretch",
250
+ )
251
+
252
+
253
+
254
+
255
+ # ---------- open dialog seeded from preset ----------
256
+
257
+ def open_ghs_with_preset(main_window, preset: Dict | None = None):
258
+ # find active document
259
+ dm = getattr(main_window, "doc_manager", getattr(main_window, "docman", None))
260
+ doc = dm.get_active_document() if (dm and hasattr(dm, "get_active_document")) else getattr(dm, "active_document", None)
261
+ if doc is None:
262
+ return
263
+
264
+ from setiastro.saspro.ghs_dialog_pro import GhsDialogPro
265
+ dlg = GhsDialogPro(main_window, doc)
266
+
267
+ p = dict(preset or {})
268
+ # sliders use integer storage: α: *50, β:*50, γ:*100; LP/HP:*360
269
+ try:
270
+ dlg.sA.setValue(int(np.clip(float(p.get("alpha", 1.0)) * 50.0, 1, 500)))
271
+ dlg.sB.setValue(int(np.clip(float(p.get("beta", 1.0)) * 50.0, 1, 500)))
272
+ dlg.sG.setValue(int(np.clip(float(p.get("gamma", 1.0)) * 100.0, 1, 500)))
273
+ dlg.sLP.setValue(int(np.clip(float(p.get("lp", 0.0)) * 360.0, 0, 360)))
274
+ dlg.sHP.setValue(int(np.clip(float(p.get("hp", 0.0)) * 360.0, 0, 360)))
275
+ ch = _norm_channel(p.get("channel", "K (Brightness)"))
276
+ i = dlg.cmb_ch.findText(ch); dlg.cmb_ch.setCurrentIndex(i if i >= 0 else 0)
277
+ pv = float(np.clip(p.get("pivot", 0.5), 0.0, 1.0))
278
+ dlg._sym_u = pv
279
+ dlg.editor.setSymmetryPoint(pv * 360.0, 0)
280
+ dlg._rebuild_from_params()
281
+ except Exception:
282
+ pass
283
+
284
+ dlg.show(); dlg.raise_(); dlg.activateWindow()