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,456 @@
1
+ # pro/whitebalance.py
2
+ from __future__ import annotations
3
+
4
+ import numpy as np
5
+ from typing import Dict, Tuple, Optional
6
+
7
+ from PyQt6.QtCore import Qt, QTimer
8
+ from PyQt6.QtGui import QIcon, QImage, QPixmap
9
+ from PyQt6.QtWidgets import (
10
+ QDialog, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QWidget, QGroupBox,
11
+ QGridLayout, QSlider, QCheckBox, QPushButton, QMessageBox, QDoubleSpinBox
12
+ )
13
+
14
+ # imageops
15
+ from setiastro.saspro.imageops.starbasedwhitebalance import apply_star_based_white_balance
16
+ from setiastro.saspro.imageops.stretch import stretch_color_image
17
+
18
+ # Shared utilities
19
+ from setiastro.saspro.widgets.image_utils import (
20
+ to_float01 as _to_float01,
21
+ extract_mask_from_document as _active_mask_array_from_doc
22
+ )
23
+
24
+ from matplotlib import pyplot as plt # NEW
25
+ from matplotlib.patches import Circle # NEW
26
+ from matplotlib.ticker import MaxNLocator # NEW
27
+ # ----------------------------
28
+ # Core WB implementations
29
+ # ----------------------------
30
+ def plot_star_color_ratios_comparison(raw_pixels: np.ndarray, after_pixels: np.ndarray):
31
+ """
32
+ Replicates the SASv2 diagnostic plot: star color ratios before/after WB,
33
+ with an RGB background grid, best-fit line, and neutral markers.
34
+ Expects Nx3 arrays of star RGB samples in [0,1] (or any common scale).
35
+ """
36
+ def compute_ratios(pixels: np.ndarray):
37
+ eps = 1e-8
38
+ rb = pixels[:, 0] / (pixels[:, 2] + eps) # R/B
39
+ gb = pixels[:, 1] / (pixels[:, 2] + eps) # G/B
40
+ return rb, gb
41
+
42
+ rb_before, gb_before = compute_ratios(raw_pixels)
43
+ rb_after, gb_after = compute_ratios(after_pixels)
44
+
45
+ # Optional: keep only finite points
46
+ def _finite(x, y):
47
+ m = np.isfinite(x) & np.isfinite(y)
48
+ return x[m], y[m]
49
+ rb_before, gb_before = _finite(rb_before, gb_before)
50
+ rb_after, gb_after = _finite(rb_after, gb_after)
51
+
52
+ # Plot bounds + background grid
53
+ rmin, rmax = 0.5, 2.0
54
+ gmin, gmax = 0.5, 2.0
55
+ res = 200
56
+
57
+ rb_vals = np.linspace(rmin, rmax, res)
58
+ gb_vals = np.linspace(gmin, gmax, res)
59
+ rb_grid, gb_grid = np.meshgrid(rb_vals, gb_vals)
60
+ rgb_image = np.stack([rb_grid, gb_grid, np.ones_like(rb_grid)], axis=-1)
61
+ rgb_image /= np.maximum(rgb_image.max(axis=2, keepdims=True), 1e-8)
62
+
63
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 6), sharex=True, sharey=True)
64
+
65
+ def plot_panel(ax, rb_data, gb_data, title):
66
+ ax.imshow(rgb_image, extent=(rmin, rmax, gmin, gmax), origin='lower', aspect='auto')
67
+ ax.scatter(rb_data, gb_data, alpha=0.6, edgecolors='k', label="Stars")
68
+
69
+ if rb_data.size >= 2:
70
+ m, b = np.polyfit(rb_data, gb_data, 1)
71
+ xs = np.linspace(rmin, rmax, 100)
72
+ ax.plot(xs, m * xs + b, 'r--', label=f"Best Fit\ny = {m:.2f}x + {b:.2f}")
73
+
74
+ ax.axhline(1.0, color='gray', linestyle=':', linewidth=1)
75
+ ax.axvline(1.0, color='gray', linestyle=':', linewidth=1)
76
+ ax.add_patch(Circle((1.0, 1.0), 0.2, fill=False, edgecolor='blue', linestyle='--', linewidth=1.5))
77
+ ax.text(1.03, 1.17, "Neutral Region", color='blue', fontsize=9)
78
+
79
+ ax.set_xlim(rmin, rmax); ax.set_ylim(gmin, gmax)
80
+ ax.set_title(f"{title} White Balance")
81
+ ax.set_xlabel("Red / Blue Ratio"); ax.set_ylabel("Green / Blue Ratio")
82
+ ax.xaxis.set_major_locator(MaxNLocator(integer=True))
83
+ ax.yaxis.set_major_locator(MaxNLocator(integer=True))
84
+ ax.grid(True); ax.legend()
85
+
86
+ plot_panel(ax1, rb_before, gb_before, "Before")
87
+ plot_panel(ax2, rb_after, gb_after, "After")
88
+
89
+ plt.suptitle("Star Color Ratios with RGB Mapping", fontsize=14)
90
+ plt.tight_layout()
91
+ plt.show()
92
+
93
+ def apply_manual_white_balance(img: np.ndarray, r_gain: float, g_gain: float, b_gain: float) -> np.ndarray:
94
+ """Simple per-channel gain, clipped to [0,1]."""
95
+ if img.ndim != 3 or img.shape[2] != 3:
96
+ raise ValueError("Manual WB requires RGB image.")
97
+ out = _to_float01(img).copy()
98
+ gains = np.array([r_gain, g_gain, b_gain], dtype=np.float32).reshape((1, 1, 3))
99
+ out = np.clip(out * gains, 0.0, 1.0)
100
+ return out.astype(np.float32, copy=False)
101
+
102
+
103
+ def apply_auto_white_balance(img: np.ndarray) -> np.ndarray:
104
+ """
105
+ Gray-world auto WB: scale each channel so its mean equals the overall mean.
106
+ """
107
+ if img.ndim != 3 or img.shape[2] != 3:
108
+ raise ValueError("Auto WB requires RGB image.")
109
+ rgb = _to_float01(img)
110
+ means = np.mean(rgb, axis=(0, 1))
111
+ overall = float(np.mean(means))
112
+ means = np.where(means <= 1e-8, 1e-8, means)
113
+ scale = (overall / means).reshape((1, 1, 3))
114
+ out = np.clip(rgb * scale, 0.0, 1.0)
115
+ return out.astype(np.float32, copy=False)
116
+
117
+
118
+ # --------------------------------
119
+ # Headless entry point for DnD
120
+ # --------------------------------
121
+ def apply_white_balance_to_doc(doc, preset: Optional[Dict] = None):
122
+ """
123
+ Preset schema:
124
+ {
125
+ "mode": "star" | "manual" | "auto", # default "star"
126
+ # star mode:
127
+ "threshold": float (default 50),
128
+ "reuse_cached_sources": bool (default True),
129
+ # manual mode:
130
+ "r_gain": float (default 1.0), "g_gain": float (default 1.0), "b_gain": float (default 1.0)
131
+ }
132
+ """
133
+ import numpy as np
134
+
135
+ p = dict(preset or {})
136
+ mode = (p.get("mode") or "star").lower()
137
+
138
+ base = np.asarray(doc.image).astype(np.float32, copy=False)
139
+ if base.ndim != 3 or base.shape[2] != 3:
140
+ raise ValueError("White Balance requires an RGB image.")
141
+
142
+ base_n = _to_float01(base)
143
+
144
+ try:
145
+ if mode == "manual":
146
+ r = float(p.get("r_gain", 1.0))
147
+ g = float(p.get("g_gain", 1.0))
148
+ b = float(p.get("b_gain", 1.0))
149
+ out = apply_manual_white_balance(base_n, r, g, b)
150
+
151
+ elif mode == "auto":
152
+ out = apply_auto_white_balance(base_n)
153
+
154
+ else: # "star"
155
+ thr = float(p.get("threshold", 50.0))
156
+ reuse = bool(p.get("reuse_cached_sources", True))
157
+ out, _count, _overlay = apply_star_based_white_balance(
158
+ base_n, threshold=thr, autostretch=False,
159
+ reuse_cached_sources=reuse, return_star_colors=False
160
+ )
161
+ except Exception as e:
162
+ # Fallback: if SEP missing or star detection fails, try Auto WB
163
+ if mode == "star":
164
+ try:
165
+ out = apply_auto_white_balance(base_n)
166
+ except Exception:
167
+ raise e
168
+ else:
169
+ raise
170
+
171
+ # Destination-mask blend (if any)
172
+ m = _active_mask_array_from_doc(doc)
173
+ if m is not None:
174
+ if out.ndim == 3:
175
+ m3 = np.repeat(m[..., None], 3, axis=2).astype(np.float32, copy=False)
176
+ else:
177
+ m3 = m.astype(np.float32, copy=False)
178
+ base_for_blend = _to_float01(np.asarray(doc.image).astype(np.float32, copy=False))
179
+ out = base_for_blend * (1.0 - m3) + out * m3
180
+
181
+ doc.apply_edit(
182
+ out.astype(np.float32, copy=False),
183
+ metadata={"step_name": "White Balance", "preset": p},
184
+ step_name="White Balance",
185
+ )
186
+
187
+
188
+ # -------------------------
189
+ # Interactive dialog (UI)
190
+ # -------------------------
191
+ class WhiteBalanceDialog(QDialog):
192
+ def __init__(self, parent, doc, icon: QIcon | None = None):
193
+ super().__init__(parent)
194
+ self.doc = doc
195
+ if icon:
196
+ self.setWindowIcon(icon)
197
+ self.setWindowTitle(self.tr("White Balance"))
198
+ self.resize(900, 600)
199
+
200
+ self._build_ui()
201
+ self._wire_events()
202
+
203
+ # default to Star-Based, like SASv2
204
+ self.type_combo.setCurrentText("Star-Based")
205
+ self._update_mode_widgets()
206
+ # kick off a first detection preview
207
+ QTimer.singleShot(200, self._update_star_preview)
208
+
209
+ # ---- UI construction ------------------------------------------------
210
+ def _build_ui(self):
211
+ self.main_layout = QVBoxLayout(self)
212
+
213
+ # Type selector
214
+ row = QHBoxLayout()
215
+ row.addWidget(QLabel(self.tr("White Balance Type:")))
216
+ self.type_combo = QComboBox()
217
+ self.type_combo.addItems([self.tr("Star-Based"), self.tr("Manual"), self.tr("Auto")])
218
+ row.addWidget(self.type_combo); row.addStretch()
219
+ self.main_layout.addLayout(row)
220
+
221
+ # Manual group
222
+ self.manual_group = QGroupBox(self.tr("Manual Gains"))
223
+ g = QGridLayout(self.manual_group)
224
+ self.r_spin = QDoubleSpinBox(); self._cfg_gain(self.r_spin, 1.0)
225
+ self.g_spin = QDoubleSpinBox(); self._cfg_gain(self.g_spin, 1.0)
226
+ self.b_spin = QDoubleSpinBox(); self._cfg_gain(self.b_spin, 1.0)
227
+ g.addWidget(QLabel(self.tr("Red gain:")), 0, 0); g.addWidget(self.r_spin, 0, 1)
228
+ g.addWidget(QLabel(self.tr("Green gain:")), 1, 0); g.addWidget(self.g_spin, 1, 1)
229
+ g.addWidget(QLabel(self.tr("Blue gain:")), 2, 0); g.addWidget(self.b_spin, 2, 1)
230
+ self.main_layout.addWidget(self.manual_group)
231
+
232
+ # Star-based controls + preview
233
+ self.star_group = QGroupBox(self.tr("Star-Based Settings"))
234
+ sg = QVBoxLayout(self.star_group)
235
+ # threshold slider
236
+ thr_row = QHBoxLayout()
237
+ thr_row.addWidget(QLabel(self.tr("Detection threshold (σ):")))
238
+ self.thr_slider = QSlider(Qt.Orientation.Horizontal)
239
+ self.thr_slider.setMinimum(1); self.thr_slider.setMaximum(100)
240
+ self.thr_slider.setValue(50); self.thr_slider.setTickInterval(10)
241
+ self.thr_slider.setTickPosition(QSlider.TickPosition.TicksBelow)
242
+ self.thr_label = QLabel("50")
243
+ thr_row.addWidget(self.thr_slider); thr_row.addWidget(self.thr_label)
244
+ sg.addLayout(thr_row)
245
+
246
+ self.chk_reuse = QCheckBox(self.tr("Reuse cached star detections")); self.chk_reuse.setChecked(True)
247
+ sg.addWidget(self.chk_reuse)
248
+
249
+ self.chk_autostretch_overlay = QCheckBox(self.tr("Autostretch overlay preview")); self.chk_autostretch_overlay.setChecked(True)
250
+ sg.addWidget(self.chk_autostretch_overlay)
251
+
252
+ # star count + image preview
253
+ self.star_count = QLabel(self.tr("Detecting stars..."))
254
+ sg.addWidget(self.star_count)
255
+
256
+ self.preview = QLabel(); self.preview.setMinimumSize(640, 360)
257
+ self.preview.setStyleSheet("border: 1px solid #333;")
258
+ sg.addWidget(self.preview)
259
+ self.main_layout.addWidget(self.star_group)
260
+
261
+ # Buttons
262
+ btn_row = QHBoxLayout()
263
+ btn_row.addStretch()
264
+ self.btn_apply = QPushButton(self.tr("Apply"))
265
+ self.btn_cancel = QPushButton(self.tr("Cancel"))
266
+ btn_row.addWidget(self.btn_apply)
267
+ btn_row.addWidget(self.btn_cancel)
268
+ self.main_layout.addLayout(btn_row)
269
+
270
+ self.setLayout(self.main_layout)
271
+
272
+ # debounce timer for star preview
273
+ self._debounce = QTimer(self); self._debounce.setSingleShot(True); self._debounce.setInterval(600)
274
+ self._debounce.timeout.connect(self._update_star_preview)
275
+
276
+ def _cfg_gain(self, box: QDoubleSpinBox, val: float):
277
+ box.setRange(0.5, 1.5); box.setDecimals(3); box.setSingleStep(0.01); box.setValue(val)
278
+
279
+ # ---- events ---------------------------------------------------------
280
+ def _wire_events(self):
281
+ self.type_combo.currentTextChanged.connect(self._update_mode_widgets)
282
+ self.btn_cancel.clicked.connect(self.reject)
283
+ self.btn_apply.clicked.connect(self._on_apply)
284
+
285
+ self.thr_slider.valueChanged.connect(lambda v: (self.thr_label.setText(str(v)), self._debounce.start()))
286
+ self.chk_autostretch_overlay.toggled.connect(lambda _=None: self._debounce.start())
287
+ self.chk_reuse.toggled.connect(lambda _=None: self._debounce.start())
288
+
289
+ def _update_mode_widgets(self):
290
+ t = self.type_combo.currentText()
291
+ self.manual_group.setVisible(t == "Manual")
292
+ self.star_group.setVisible(t == "Star-Based")
293
+
294
+ # ---- preview --------------------------------------------------------
295
+ def _update_star_preview(self):
296
+ if self.type_combo.currentText() != "Star-Based":
297
+ return
298
+ try:
299
+ img = _to_float01(np.asarray(self.doc.image))
300
+ thr = float(self.thr_slider.value())
301
+ reuse = bool(self.chk_reuse.isChecked())
302
+ auto = bool(self.chk_autostretch_overlay.isChecked())
303
+
304
+ _, count, overlay = apply_star_based_white_balance(
305
+ img, threshold=thr, autostretch=auto,
306
+ reuse_cached_sources=reuse, return_star_colors=False
307
+ )
308
+ self.star_count.setText(self.tr("Detected {0} stars.").format(count))
309
+ # to pixmap
310
+ h, w, _ = overlay.shape
311
+ qimg = QImage((overlay * 255).astype(np.uint8).data, w, h, 3 * w, QImage.Format.Format_RGB888)
312
+ pm = QPixmap.fromImage(qimg).scaled(self.preview.width(), self.preview.height(),
313
+ Qt.AspectRatioMode.KeepAspectRatio,
314
+ Qt.TransformationMode.SmoothTransformation)
315
+ self.preview.setPixmap(pm)
316
+ except Exception as e:
317
+ self.star_count.setText(self.tr("Detection failed."))
318
+ self.preview.clear()
319
+
320
+ # ---- apply ----------------------------------------------------------
321
+ def _on_apply(self):
322
+ mode = self.type_combo.currentText()
323
+
324
+ # Find the main window that carries the replay machinery
325
+ main = self.parent()
326
+ while main is not None and not hasattr(main, "replay_last_action_on_base"):
327
+ main = main.parent()
328
+
329
+ def _record_preset_for_replay(preset: dict):
330
+ """Helper to stash the WB preset on the main window for Replay Last Action."""
331
+ if main is None:
332
+ return
333
+ try:
334
+ main._last_headless_command = {
335
+ "command_id": "white_balance",
336
+ "preset": preset,
337
+ }
338
+ if hasattr(main, "_log"):
339
+ mode_str = str(preset.get("mode", "star")).lower()
340
+ if mode_str == "manual":
341
+ r = float(preset.get("r_gain", 1.0))
342
+ g = float(preset.get("g_gain", 1.0))
343
+ b = float(preset.get("b_gain", 1.0))
344
+ main._log(
345
+ f"[Replay] Recorded White Balance preset "
346
+ f"(mode=manual, R={r:.3f}, G={g:.3f}, B={b:.3f})"
347
+ )
348
+ elif mode_str == "auto":
349
+ main._log("[Replay] Recorded White Balance preset (mode=auto)")
350
+ else:
351
+ thr = float(preset.get("threshold", 50.0))
352
+ reuse = bool(preset.get("reuse_cached_sources", True))
353
+ main._log(
354
+ f"[Replay] Recorded White Balance preset "
355
+ f"(mode=star, threshold={thr:.1f}, "
356
+ f"reuse={'yes' if reuse else 'no'})"
357
+ )
358
+ except Exception:
359
+ # Logging/recording must never break the dialog
360
+ pass
361
+
362
+ try:
363
+ if mode == "Manual":
364
+ preset = {
365
+ "mode": "manual",
366
+ "r_gain": float(self.r_spin.value()),
367
+ "g_gain": float(self.g_spin.value()),
368
+ "b_gain": float(self.b_spin.value()),
369
+ }
370
+
371
+ # Record for Replay Last Action
372
+ _record_preset_for_replay(preset)
373
+
374
+ # Use the headless helper so doc metadata is consistent
375
+ apply_white_balance_to_doc(self.doc, preset)
376
+ self.accept()
377
+
378
+ elif mode == "Auto":
379
+ preset = {"mode": "auto"}
380
+
381
+ # Record for Replay Last Action
382
+ _record_preset_for_replay(preset)
383
+
384
+ apply_white_balance_to_doc(self.doc, preset)
385
+ self.accept()
386
+
387
+ else: # --- Star-Based: compute here so we can plot like SASv2 ---
388
+ thr = float(self.thr_slider.value())
389
+ reuse = bool(self.chk_reuse.isChecked())
390
+
391
+ preset = {
392
+ "mode": "star",
393
+ "threshold": thr,
394
+ "reuse_cached_sources": reuse,
395
+ }
396
+
397
+ base = _to_float01(
398
+ np.asarray(self.doc.image).astype(np.float32, copy=False)
399
+ )
400
+
401
+ # Ask for star colors so we can plot
402
+ result = apply_star_based_white_balance(
403
+ base,
404
+ threshold=thr,
405
+ autostretch=False,
406
+ reuse_cached_sources=reuse,
407
+ return_star_colors=True,
408
+ )
409
+
410
+ # Expected: (out, count, overlay, raw_colors, after_colors)
411
+ if len(result) < 5:
412
+ raise RuntimeError(
413
+ "Star-based WB did not return star color arrays. "
414
+ "Ensure return_star_colors=True is supported."
415
+ )
416
+
417
+ out, star_count, _overlay, raw_colors, after_colors = result
418
+
419
+ # Optional destination-mask blend, same as headless path
420
+ m = _active_mask_array_from_doc(self.doc)
421
+ if m is not None:
422
+ m3 = np.repeat(m[..., None], 3, axis=2).astype(np.float32, copy=False)
423
+ base_for_blend = _to_float01(
424
+ np.asarray(self.doc.image).astype(np.float32, copy=False)
425
+ )
426
+ out = base_for_blend * (1.0 - m3) + out * m3
427
+
428
+ # Commit to the document, including the preset in metadata
429
+ self.doc.apply_edit(
430
+ out.astype(np.float32, copy=False),
431
+ metadata={"step_name": "White Balance", "preset": preset},
432
+ step_name="White Balance",
433
+ )
434
+
435
+ # Record for Replay Last Action (using the same preset we just stored in metadata)
436
+ _record_preset_for_replay(preset)
437
+
438
+ # 🔬 Show the same diagnostic plot SASv2 shows
439
+ if (
440
+ raw_colors is not None
441
+ and after_colors is not None
442
+ and raw_colors.size
443
+ and after_colors.size
444
+ ):
445
+ plot_star_color_ratios_comparison(raw_colors, after_colors)
446
+
447
+ QMessageBox.information(
448
+ self,
449
+ self.tr("White Balance"),
450
+ self.tr("Star-Based WB applied.\nDetected {0} stars.").format(int(star_count)),
451
+ )
452
+ self.accept()
453
+
454
+ except Exception as e:
455
+ QMessageBox.critical(self, self.tr("White Balance"), self.tr("Failed to apply White Balance:\n{0}").format(e))
456
+
@@ -0,0 +1,48 @@
1
+ # pro/widgets/__init__.py
2
+ """
3
+ Shared UI widgets for Seti Astro Suite Pro.
4
+
5
+ This package contains reusable UI components to avoid code duplication.
6
+ """
7
+
8
+ from setiastro.saspro.widgets.spinboxes import CustomSpinBox, CustomDoubleSpinBox
9
+ from setiastro.saspro.widgets.graphics_views import ZoomableGraphicsView
10
+ from setiastro.saspro.widgets.preview_dialogs import ImagePreviewDialog
11
+ from setiastro.saspro.widgets.image_utils import (
12
+ numpy_to_qimage,
13
+ numpy_to_qpixmap,
14
+ qimage_to_numpy,
15
+ create_preview_image,
16
+ normalize_image
17
+ )
18
+ from setiastro.saspro.widgets.common_utilities import (
19
+ AboutDialog,
20
+ ProjectSaveWorker,
21
+ DECOR_GLYPHS,
22
+ strip_ui_decorations,
23
+ _strip_ui_decorations,
24
+ install_crash_handlers,
25
+ get_version,
26
+ get_build_timestamp,
27
+ )
28
+
29
+ __all__ = [
30
+ 'CustomSpinBox',
31
+ 'CustomDoubleSpinBox',
32
+ 'ZoomableGraphicsView',
33
+ 'ImagePreviewDialog',
34
+ 'numpy_to_qimage',
35
+ 'numpy_to_qpixmap',
36
+ 'qimage_to_numpy',
37
+ 'create_preview_image',
38
+ 'normalize_image',
39
+ # Common utilities
40
+ 'AboutDialog',
41
+ 'ProjectSaveWorker',
42
+ 'DECOR_GLYPHS',
43
+ 'strip_ui_decorations',
44
+ '_strip_ui_decorations',
45
+ 'install_crash_handlers',
46
+ 'get_version',
47
+ 'get_build_timestamp',
48
+ ]