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,407 @@
1
+ # pro/cosmicclarity_preset.py
2
+ from __future__ import annotations
3
+ import os
4
+ import sys
5
+ import time
6
+ import glob
7
+ import shutil
8
+ import subprocess
9
+ import numpy as np
10
+
11
+ from PyQt6.QtCore import QThread, pyqtSignal, Qt, QTimer, QSettings, QLockFile
12
+ from PyQt6.QtWidgets import QDialog, QFormLayout, QDialogButtonBox, QDoubleSpinBox, QComboBox, QCheckBox, QMessageBox, QApplication, QHBoxLayout, QVBoxLayout, QLabel, QPushButton
13
+ from PyQt6.QtCore import QThread, pyqtSignal, Qt, QTimer, QSettings, QLockFile, QEventLoop
14
+
15
+
16
+ # reuse your legacy IO + helpers
17
+ from setiastro.saspro.legacy.image_manager import load_image, save_image
18
+
19
+ from .remove_stars import _ProcThread, _ProcDialog
20
+ from .cosmicclarity import CosmicClarityDialogPro
21
+
22
+ # pull tiny helpers from the main CC module (or re-declare)
23
+ def _platform_exe_names(mode: str) -> str:
24
+ is_win = os.name == "nt"
25
+ is_mac = sys.platform == "darwin"
26
+ if mode == "sharpen":
27
+ return "SetiAstroCosmicClarity.exe" if is_win else ("SetiAstroCosmicClaritymac" if is_mac else "SetiAstroCosmicClarity")
28
+ elif mode == "denoise":
29
+ return "SetiAstroCosmicClarity_denoise.exe" if is_win else ("SetiAstroCosmicClarity_denoisemac" if is_mac else "SetiAstroCosmicClarity_denoise")
30
+ elif mode == "superres":
31
+ return "setiastrocosmicclarity_superres.exe" if is_win else "setiastrocosmicclarity_superres"
32
+ return ""
33
+
34
+ def _cosmic_root(main) -> str:
35
+ s = getattr(main, "settings", None)
36
+ if not s:
37
+ return ""
38
+ try:
39
+ return s.value("paths/cosmic_clarity", "", type=str) or ""
40
+ except Exception:
41
+ return s.value("paths/cosmic_clarity", "") or ""
42
+
43
+ def _base_from_doc(doc) -> str:
44
+ fp = getattr(doc, "file_path", None)
45
+ if isinstance(fp, str) and fp:
46
+ return os.path.splitext(os.path.basename(fp))[0]
47
+ name = getattr(doc, "display_name", None)
48
+ if callable(name):
49
+ try:
50
+ n = name() or ""
51
+ if n:
52
+ return "".join(ch if ch.isalnum() or ch in "-_" else "_" for ch in n).strip("_") or "image"
53
+ except Exception:
54
+ pass
55
+ return "image"
56
+
57
+
58
+ # ---------------- Worker (no UI) ----------------
59
+ class _CCHeadlessWorker(QThread):
60
+ finished_ok = pyqtSignal(np.ndarray)
61
+ failed = pyqtSignal(str)
62
+ log = pyqtSignal(str)
63
+ progress = pyqtSignal(int) # 0..100
64
+ step_changed= pyqtSignal(str) # "sharpen" | "denoise" | "superres"
65
+
66
+ def __init__(self, cosmic_root: str, doc, ops: list[tuple[str, str]], params: dict, create_new: bool):
67
+ super().__init__()
68
+ self.root = cosmic_root
69
+ self.doc = doc
70
+ self.ops = ops
71
+ self.p = params
72
+ self.create_new = create_new
73
+ self._stop = False
74
+ self._proc = None
75
+
76
+ def cancel(self):
77
+ self._stop = True
78
+ try:
79
+ if self._proc and self._proc.poll() is None:
80
+ self._proc.terminate()
81
+ try: self._proc.wait(timeout=5)
82
+ except Exception: self._proc.kill()
83
+ except Exception:
84
+ pass
85
+
86
+ def _emit_progress_line(self, line: str):
87
+ s = line.strip()
88
+ if not s:
89
+ return
90
+ self.log.emit(s)
91
+ # Parse both formats used by your tools
92
+ if s.startswith("Progress:"):
93
+ try: self.progress.emit(int(float(s.split()[1].replace("%",""))))
94
+ except Exception as e:
95
+ import logging
96
+ logging.debug(f"Exception suppressed: {type(e).__name__}: {e}")
97
+ elif s.startswith("PROGRESS:"):
98
+ try: self.progress.emit(int(s.split(":",1)[1].strip().replace("%","")))
99
+ except Exception as e:
100
+ import logging
101
+ logging.debug(f"Exception suppressed: {type(e).__name__}: {e}")
102
+
103
+ def run(self):
104
+ try:
105
+ print("starting cc headless worker")
106
+ in_dir = os.path.join(self.root, "input")
107
+ out_dir = os.path.join(self.root, "output")
108
+ os.makedirs(in_dir, exist_ok=True)
109
+ os.makedirs(out_dir, exist_ok=True)
110
+
111
+ base = _base_from_doc(self.doc)
112
+ in_path = os.path.join(in_dir, f"{base}.tif")
113
+
114
+ # Stage current image
115
+ img = np.clip(np.asarray(self.doc.image), 0.0, 1.0).astype(np.float32, copy=False)
116
+ save_image(img, in_path, "tiff", "32-bit floating point",
117
+ getattr(self.doc, "original_header", None),
118
+ getattr(self.doc, "is_mono", False))
119
+
120
+ staged_input = in_path
121
+ result = None
122
+ print("entering cc headless run")
123
+ for (mode, suffix) in self.ops:
124
+ if self._stop: raise RuntimeError("Cancelled")
125
+ self.step_changed.emit(mode)
126
+
127
+ exe = os.path.join(self.root, _platform_exe_names(mode))
128
+ if not os.path.exists(exe):
129
+ raise RuntimeError(f"Cosmic Clarity executable not found:\n{exe}")
130
+
131
+ # Build args
132
+ args = []
133
+ if mode == "sharpen":
134
+ if not self.p.get("gpu", True):
135
+ args.append("--disable_gpu")
136
+ if self.p.get("auto_psf", True):
137
+ args.append("--auto_detect_psf")
138
+ args += [
139
+ "--sharpening_mode", self.p.get("sharpening_mode", "Both"),
140
+ "--stellar_amount", f"{float(self.p.get('stellar_amount', 0.50)):.2f}",
141
+ "--nonstellar_strength", f"{float(self.p.get('nonstellar_psf', 3.0)):.1f}",
142
+ "--nonstellar_amount", f"{float(self.p.get('nonstellar_amount', 0.50)):.2f}",
143
+ ]
144
+ # NEW: per-channel sharpen flag from preset
145
+ if self.p.get("sharpen_channels_separately", False):
146
+ args.append("--sharpen_channels_separately")
147
+ elif mode == "denoise":
148
+ if not self.p.get("gpu", True): args.append("--disable_gpu")
149
+ if self.p.get("separate_channels", False): args.append("--separate_channels")
150
+ args += [
151
+ "--denoise_strength", f"{float(self.p.get('denoise_luma', 0.50)):.2f}",
152
+ "--color_denoise_strength", f"{float(self.p.get('denoise_color', 0.50)):.2f}",
153
+ "--denoise_mode", self.p.get("denoise_mode", "full"),
154
+ ]
155
+ elif mode == "superres":
156
+ scale = int(self.p.get("scale", 2))
157
+ args = ["--input", staged_input, "--output_dir", out_dir,
158
+ "--scale", str(scale), "--model_dir", self.root]
159
+ else:
160
+ raise RuntimeError(f"Unknown mode: {mode}")
161
+
162
+ self.log.emit(f"Launching: {os.path.basename(exe)} {' '.join(args)}")
163
+ self.progress.emit(0)
164
+
165
+ # Run process and stream stdout
166
+ self._proc = subprocess.Popen([exe] + args, cwd=self.root,
167
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
168
+ text=True, universal_newlines=True)
169
+ try:
170
+ for line in self._proc.stdout:
171
+ if self._stop: break
172
+ self._emit_progress_line(line)
173
+ except Exception:
174
+ pass
175
+ rc = self._proc.wait()
176
+ self._proc = None
177
+ if self._stop: raise RuntimeError("Cancelled")
178
+ if rc != 0:
179
+ raise RuntimeError(f"{os.path.basename(exe)} exited with code {rc}")
180
+
181
+ # Wait for produced file
182
+ if mode == "superres":
183
+ scale = int(self.p.get("scale", 2))
184
+ pat = os.path.join(out_dir, f"{base}_upscaled{scale}.*")
185
+ else:
186
+ pat = os.path.join(out_dir, f"{base}{suffix}.*")
187
+ self.log.emit("Waiting for output file…")
188
+ out_path = self._wait_for_file(pat, timeout=1800)
189
+ if not out_path:
190
+ raise RuntimeError("Output file not found.")
191
+
192
+ arr, _, _, _ = load_image(out_path)
193
+ if arr is None:
194
+ raise RuntimeError("Failed to load output image.")
195
+
196
+ result = np.asarray(arr).astype(np.float32, copy=False)
197
+ self.progress.emit(100)
198
+
199
+ # Stage as next input if more to do
200
+ if (mode, suffix) != self.ops[-1]:
201
+ save_image(result, in_path, "tiff", "32-bit floating point",
202
+ getattr(self.doc, "original_header", None),
203
+ getattr(self.doc, "is_mono", False))
204
+ staged_input = in_path
205
+
206
+ # Cleanup produced out file
207
+ try:
208
+ if os.path.exists(out_path): os.remove(out_path)
209
+ except Exception:
210
+ pass
211
+
212
+ # Cleanup input
213
+ try:
214
+ if os.path.exists(in_path): os.remove(in_path)
215
+ except Exception:
216
+ pass
217
+
218
+ if result is None:
219
+ raise RuntimeError("No result produced.")
220
+ self.finished_ok.emit(result)
221
+
222
+ except Exception as e:
223
+ self.failed.emit(str(e))
224
+
225
+ def _wait_for_file(self, pattern: str, timeout: float = 1800.0, poll: float = 0.25):
226
+ """
227
+ Wait for a file matching glob `pattern`. Returns most recent match or "".
228
+ """
229
+ t0 = time.time()
230
+ last = ""
231
+ while time.time() - t0 < timeout:
232
+ matches = glob.glob(pattern)
233
+ if matches:
234
+ try:
235
+ # pick newest file (mtime)
236
+ matches.sort(key=lambda p: os.path.getmtime(p), reverse=True)
237
+ except Exception:
238
+ matches.sort()
239
+ last = matches[0]
240
+ # make sure it’s non-zero and stable-ish
241
+ try:
242
+ if os.path.getsize(last) > 0:
243
+ return last
244
+ except Exception:
245
+ return last
246
+ time.sleep(poll)
247
+ return ""
248
+
249
+
250
+ # ---------------- Public entry ----------------
251
+ def run_cosmicclarity_via_preset(main, preset: dict | None = None, *, doc=None):
252
+ """Run CC headlessly by driving the same pipeline as the Execute button."""
253
+ p = dict(preset or {})
254
+
255
+ # ---- Record for Replay Last Action ----
256
+ try:
257
+ remember = getattr(main, "remember_last_headless_command", None)
258
+ if remember is None:
259
+ remember = getattr(main, "_remember_last_headless_command", None)
260
+ if callable(remember):
261
+ remember("cosmic_clarity", p, description="Cosmic Clarity")
262
+ else:
263
+ setattr(main, "_last_headless_command", {
264
+ "command_id": "cosmic_clarity",
265
+ "preset": dict(p),
266
+ })
267
+ except Exception:
268
+ pass
269
+ # --------------------------------------
270
+
271
+ # Guard so users can’t open another CC panel while this runs
272
+ setattr(main, "_cosmicclarity_headless_running", True)
273
+ setattr(main, "_cosmicclarity_guard", True)
274
+ s = QSettings()
275
+ try:
276
+ s.setValue("cc/headless_in_progress", True); s.sync()
277
+ except Exception:
278
+ pass
279
+
280
+ try:
281
+ # Prefer the explicit doc (from target_sw); otherwise fall back to active
282
+ if doc is None:
283
+ doc = getattr(main, "_active_doc", None)
284
+ if callable(doc):
285
+ doc = doc()
286
+
287
+ if doc is None or getattr(doc, "image", None) is None:
288
+ QMessageBox.warning(main, "Cosmic Clarity", "Load an image first.")
289
+ return
290
+
291
+ dlg = CosmicClarityDialogPro(main, doc, headless=True, bypass_guard=True)
292
+ if getattr(dlg, "_headless", False) is not True:
293
+ return
294
+
295
+ try:
296
+ dlg.apply_preset(p)
297
+ except Exception:
298
+ mode = str(p.get("mode","sharpen")).lower()
299
+ dlg.cmb_mode.setCurrentIndex({"sharpen":0,"denoise":1,"both":2,"superres":3}.get(mode,0))
300
+ dlg.cmb_gpu.setCurrentIndex(0 if p.get("gpu", True) else 1)
301
+ dlg.cmb_target.setCurrentIndex(1 if p.get("create_new_view", False) else 0)
302
+
303
+ dlg._run_main()
304
+
305
+ loop = QEventLoop()
306
+ dlg.finished.connect(loop.quit)
307
+ loop.exec_() if hasattr(loop, "exec_") else loop.exec()
308
+
309
+ finally:
310
+ try:
311
+ s.setValue("cc/headless_in_progress", False); s.sync()
312
+ except Exception:
313
+ pass
314
+ for name in ("_cosmicclarity_headless_running", "_cosmicclarity_guard"):
315
+ try:
316
+ delattr(main, name)
317
+ except Exception:
318
+ setattr(main, name, False)
319
+
320
+
321
+
322
+ # ---------------- Optional: tiny preset editor for the shortcut button ----------------
323
+ class _CosmicClarityPresetDialog(QDialog):
324
+ """
325
+ Minimal preset editor for the shortcut:
326
+ Mode, GPU, create_new_view plus a few key params per mode.
327
+ """
328
+ def __init__(self, parent=None, initial: dict | None = None):
329
+ super().__init__(parent)
330
+ self.setWindowTitle("Cosmic Clarity — Preset")
331
+ p = dict(initial or {})
332
+ f = QFormLayout(self)
333
+
334
+ self.mode = QComboBox(); self.mode.addItems(["sharpen", "denoise", "both", "superres"])
335
+ self.mode.setCurrentText(str(p.get("mode", "sharpen")))
336
+ f.addRow("Mode:", self.mode)
337
+
338
+ self.gpu = QCheckBox("Use GPU"); self.gpu.setChecked(bool(p.get("gpu", True)))
339
+ f.addRow(self.gpu)
340
+
341
+ self.newview = QCheckBox("Create new view"); self.newview.setChecked(bool(p.get("create_new_view", False)))
342
+ f.addRow(self.newview)
343
+
344
+ # Sharpen
345
+ self.sh_mode = QComboBox(); self.sh_mode.addItems(["Both", "Stellar Only", "Non-Stellar Only"])
346
+ self.sh_mode.setCurrentText(p.get("sharpening_mode", "Both"))
347
+ self.auto_psf = QCheckBox("Auto PSF"); self.auto_psf.setChecked(bool(p.get("auto_psf", True)))
348
+ self.psf = QDoubleSpinBox(); self.psf.setRange(1.0, 8.0); self.psf.setSingleStep(0.1); self.psf.setValue(float(p.get("nonstellar_psf", 3.0)))
349
+ self.st_amt = QDoubleSpinBox(); self.st_amt.setRange(0.0, 1.0); self.st_amt.setSingleStep(0.05); self.st_amt.setValue(float(p.get("stellar_amount", 0.50)))
350
+ self.nst_amt= QDoubleSpinBox(); self.nst_amt.setRange(0.0, 1.0); self.nst_amt.setSingleStep(0.05); self.nst_amt.setValue(float(p.get("nonstellar_amount", 0.50)))
351
+ f.addRow("Sharpening Mode:", self.sh_mode)
352
+ f.addRow(self.auto_psf)
353
+ f.addRow("Non-stellar PSF:", self.psf)
354
+ f.addRow("Stellar Amount:", self.st_amt)
355
+ f.addRow("Non-stellar Amount:", self.nst_amt)
356
+
357
+ # NEW: Sharpen RGB channels separately
358
+ self.sh_sep = QCheckBox("Sharpen RGB channels separately")
359
+ self.sh_sep.setChecked(bool(p.get("sharpen_channels_separately", False)))
360
+ f.addRow(self.sh_sep)
361
+ # Denoise
362
+ self.dn_lum = QDoubleSpinBox(); self.dn_lum.setRange(0.0, 1.0); self.dn_lum.setSingleStep(0.05); self.dn_lum.setValue(float(p.get("denoise_luma", 0.50)))
363
+ self.dn_col = QDoubleSpinBox(); self.dn_col.setRange(0.0, 1.0); self.dn_col.setSingleStep(0.05); self.dn_col.setValue(float(p.get("denoise_color", 0.50)))
364
+ self.dn_mode= QComboBox(); self.dn_mode.addItems(["full","luminance"]); self.dn_mode.setCurrentText(p.get("denoise_mode", "full"))
365
+ self.dn_sep = QCheckBox("Separate RGB channels"); self.dn_sep.setChecked(bool(p.get("separate_channels", False)))
366
+ f.addRow("Denoise Luma:", self.dn_lum)
367
+ f.addRow("Denoise Color:", self.dn_col)
368
+ f.addRow("Denoise Mode:", self.dn_mode)
369
+ f.addRow(self.dn_sep)
370
+
371
+ # Super-res
372
+ self.scale = QComboBox(); self.scale.addItems(["2","3","4"]); self.scale.setCurrentText(str(int(p.get("scale", 2))))
373
+ f.addRow("Super-Res Scale:", self.scale)
374
+
375
+ # OK/Cancel
376
+ btns = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, parent=self)
377
+ btns.accepted.connect(self.accept); btns.rejected.connect(self.reject)
378
+ f.addRow(btns)
379
+
380
+
381
+ def result_dict(self) -> dict:
382
+ m = self.mode.currentText()
383
+ out = {
384
+ "mode": m,
385
+ "gpu": bool(self.gpu.isChecked()),
386
+ "create_new_view": bool(self.newview.isChecked()),
387
+ }
388
+ if m in ("sharpen","both"):
389
+ out.update({
390
+ "sharpening_mode": self.sh_mode.currentText(),
391
+ "auto_psf": bool(self.auto_psf.isChecked()),
392
+ "nonstellar_psf": float(self.psf.value()),
393
+ "stellar_amount": float(self.st_amt.value()),
394
+ "nonstellar_amount": float(self.nst_amt.value()),
395
+ # NEW: propagate sharpen-separate into preset dict
396
+ "sharpen_channels_separately": bool(self.sh_sep.isChecked()),
397
+ })
398
+ if m in ("denoise","both"):
399
+ out.update({
400
+ "denoise_luma": float(self.dn_lum.value()),
401
+ "denoise_color": float(self.dn_col.value()),
402
+ "denoise_mode": self.dn_mode.currentText(),
403
+ "separate_channels": bool(self.dn_sep.isChecked()),
404
+ })
405
+ if m == "superres":
406
+ out["scale"] = int(self.scale.currentText())
407
+ return out