setiastrosuitepro 1.6.7__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 (394) 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/Background_startup.jpg +0 -0
  16. setiastro/images/HRDiagram.png +0 -0
  17. setiastro/images/LExtract.png +0 -0
  18. setiastro/images/LInsert.png +0 -0
  19. setiastro/images/Oxygenation-atm-2.svg.png +0 -0
  20. setiastro/images/RGB080604.png +0 -0
  21. setiastro/images/abeicon.png +0 -0
  22. setiastro/images/aberration.png +0 -0
  23. setiastro/images/acv_icon.png +0 -0
  24. setiastro/images/andromedatry.png +0 -0
  25. setiastro/images/andromedatry_satellited.png +0 -0
  26. setiastro/images/annotated.png +0 -0
  27. setiastro/images/aperture.png +0 -0
  28. setiastro/images/astrosuite.ico +0 -0
  29. setiastro/images/astrosuite.png +0 -0
  30. setiastro/images/astrosuitepro.icns +0 -0
  31. setiastro/images/astrosuitepro.ico +0 -0
  32. setiastro/images/astrosuitepro.png +0 -0
  33. setiastro/images/background.png +0 -0
  34. setiastro/images/background2.png +0 -0
  35. setiastro/images/benchmark.png +0 -0
  36. setiastro/images/big_moon_stabilizer_timeline.png +0 -0
  37. setiastro/images/big_moon_stabilizer_timeline_clean.png +0 -0
  38. setiastro/images/blaster.png +0 -0
  39. setiastro/images/blink.png +0 -0
  40. setiastro/images/clahe.png +0 -0
  41. setiastro/images/collage.png +0 -0
  42. setiastro/images/colorwheel.png +0 -0
  43. setiastro/images/contsub.png +0 -0
  44. setiastro/images/convo.png +0 -0
  45. setiastro/images/copyslot.png +0 -0
  46. setiastro/images/cosmic.png +0 -0
  47. setiastro/images/cosmicsat.png +0 -0
  48. setiastro/images/crop1.png +0 -0
  49. setiastro/images/cropicon.png +0 -0
  50. setiastro/images/curves.png +0 -0
  51. setiastro/images/cvs.png +0 -0
  52. setiastro/images/debayer.png +0 -0
  53. setiastro/images/denoise_cnn_custom.png +0 -0
  54. setiastro/images/denoise_cnn_graph.png +0 -0
  55. setiastro/images/disk.png +0 -0
  56. setiastro/images/dse.png +0 -0
  57. setiastro/images/exoicon.png +0 -0
  58. setiastro/images/eye.png +0 -0
  59. setiastro/images/first_quarter.png +0 -0
  60. setiastro/images/fliphorizontal.png +0 -0
  61. setiastro/images/flipvertical.png +0 -0
  62. setiastro/images/font.png +0 -0
  63. setiastro/images/freqsep.png +0 -0
  64. setiastro/images/full_moon.png +0 -0
  65. setiastro/images/functionbundle.png +0 -0
  66. setiastro/images/graxpert.png +0 -0
  67. setiastro/images/green.png +0 -0
  68. setiastro/images/gridicon.png +0 -0
  69. setiastro/images/halo.png +0 -0
  70. setiastro/images/hdr.png +0 -0
  71. setiastro/images/histogram.png +0 -0
  72. setiastro/images/hubble.png +0 -0
  73. setiastro/images/imagecombine.png +0 -0
  74. setiastro/images/invert.png +0 -0
  75. setiastro/images/isophote.png +0 -0
  76. setiastro/images/isophote_demo_figure.png +0 -0
  77. setiastro/images/isophote_demo_image.png +0 -0
  78. setiastro/images/isophote_demo_model.png +0 -0
  79. setiastro/images/isophote_demo_residual.png +0 -0
  80. setiastro/images/jwstpupil.png +0 -0
  81. setiastro/images/last_quarter.png +0 -0
  82. setiastro/images/linearfit.png +0 -0
  83. setiastro/images/livestacking.png +0 -0
  84. setiastro/images/mask.png +0 -0
  85. setiastro/images/maskapply.png +0 -0
  86. setiastro/images/maskcreate.png +0 -0
  87. setiastro/images/maskremove.png +0 -0
  88. setiastro/images/morpho.png +0 -0
  89. setiastro/images/mosaic.png +0 -0
  90. setiastro/images/multiscale_decomp.png +0 -0
  91. setiastro/images/nbtorgb.png +0 -0
  92. setiastro/images/neutral.png +0 -0
  93. setiastro/images/new_moon.png +0 -0
  94. setiastro/images/nuke.png +0 -0
  95. setiastro/images/openfile.png +0 -0
  96. setiastro/images/pedestal.png +0 -0
  97. setiastro/images/pen.png +0 -0
  98. setiastro/images/pixelmath.png +0 -0
  99. setiastro/images/platesolve.png +0 -0
  100. setiastro/images/ppp.png +0 -0
  101. setiastro/images/pro.png +0 -0
  102. setiastro/images/project.png +0 -0
  103. setiastro/images/psf.png +0 -0
  104. setiastro/images/redo.png +0 -0
  105. setiastro/images/redoicon.png +0 -0
  106. setiastro/images/rescale.png +0 -0
  107. setiastro/images/rgbalign.png +0 -0
  108. setiastro/images/rgbcombo.png +0 -0
  109. setiastro/images/rgbextract.png +0 -0
  110. setiastro/images/rotate180.png +0 -0
  111. setiastro/images/rotatearbitrary.png +0 -0
  112. setiastro/images/rotateclockwise.png +0 -0
  113. setiastro/images/rotatecounterclockwise.png +0 -0
  114. setiastro/images/satellite.png +0 -0
  115. setiastro/images/script.png +0 -0
  116. setiastro/images/selectivecolor.png +0 -0
  117. setiastro/images/simbad.png +0 -0
  118. setiastro/images/slot0.png +0 -0
  119. setiastro/images/slot1.png +0 -0
  120. setiastro/images/slot2.png +0 -0
  121. setiastro/images/slot3.png +0 -0
  122. setiastro/images/slot4.png +0 -0
  123. setiastro/images/slot5.png +0 -0
  124. setiastro/images/slot6.png +0 -0
  125. setiastro/images/slot7.png +0 -0
  126. setiastro/images/slot8.png +0 -0
  127. setiastro/images/slot9.png +0 -0
  128. setiastro/images/spcc.png +0 -0
  129. setiastro/images/spin_precession_vs_lunar_distance.png +0 -0
  130. setiastro/images/spinner.gif +0 -0
  131. setiastro/images/stacking.png +0 -0
  132. setiastro/images/staradd.png +0 -0
  133. setiastro/images/staralign.png +0 -0
  134. setiastro/images/starnet.png +0 -0
  135. setiastro/images/starregistration.png +0 -0
  136. setiastro/images/starspike.png +0 -0
  137. setiastro/images/starstretch.png +0 -0
  138. setiastro/images/statstretch.png +0 -0
  139. setiastro/images/supernova.png +0 -0
  140. setiastro/images/uhs.png +0 -0
  141. setiastro/images/undoicon.png +0 -0
  142. setiastro/images/upscale.png +0 -0
  143. setiastro/images/viewbundle.png +0 -0
  144. setiastro/images/waning_crescent_1.png +0 -0
  145. setiastro/images/waning_crescent_2.png +0 -0
  146. setiastro/images/waning_crescent_3.png +0 -0
  147. setiastro/images/waning_crescent_4.png +0 -0
  148. setiastro/images/waning_crescent_5.png +0 -0
  149. setiastro/images/waning_gibbous_1.png +0 -0
  150. setiastro/images/waning_gibbous_2.png +0 -0
  151. setiastro/images/waning_gibbous_3.png +0 -0
  152. setiastro/images/waning_gibbous_4.png +0 -0
  153. setiastro/images/waning_gibbous_5.png +0 -0
  154. setiastro/images/waxing_crescent_1.png +0 -0
  155. setiastro/images/waxing_crescent_2.png +0 -0
  156. setiastro/images/waxing_crescent_3.png +0 -0
  157. setiastro/images/waxing_crescent_4.png +0 -0
  158. setiastro/images/waxing_crescent_5.png +0 -0
  159. setiastro/images/waxing_gibbous_1.png +0 -0
  160. setiastro/images/waxing_gibbous_2.png +0 -0
  161. setiastro/images/waxing_gibbous_3.png +0 -0
  162. setiastro/images/waxing_gibbous_4.png +0 -0
  163. setiastro/images/waxing_gibbous_5.png +0 -0
  164. setiastro/images/whitebalance.png +0 -0
  165. setiastro/images/wimi_icon_256x256.png +0 -0
  166. setiastro/images/wimilogo.png +0 -0
  167. setiastro/images/wims.png +0 -0
  168. setiastro/images/wrench_icon.png +0 -0
  169. setiastro/images/xisfliberator.png +0 -0
  170. setiastro/qml/ResourceMonitor.qml +128 -0
  171. setiastro/saspro/__init__.py +20 -0
  172. setiastro/saspro/__main__.py +964 -0
  173. setiastro/saspro/_generated/__init__.py +7 -0
  174. setiastro/saspro/_generated/build_info.py +3 -0
  175. setiastro/saspro/abe.py +1379 -0
  176. setiastro/saspro/abe_preset.py +196 -0
  177. setiastro/saspro/aberration_ai.py +910 -0
  178. setiastro/saspro/aberration_ai_preset.py +224 -0
  179. setiastro/saspro/accel_installer.py +218 -0
  180. setiastro/saspro/accel_workers.py +30 -0
  181. setiastro/saspro/acv_exporter.py +379 -0
  182. setiastro/saspro/add_stars.py +627 -0
  183. setiastro/saspro/astrobin_exporter.py +1010 -0
  184. setiastro/saspro/astrospike.py +153 -0
  185. setiastro/saspro/astrospike_python.py +1841 -0
  186. setiastro/saspro/autostretch.py +198 -0
  187. setiastro/saspro/backgroundneutral.py +639 -0
  188. setiastro/saspro/batch_convert.py +328 -0
  189. setiastro/saspro/batch_renamer.py +522 -0
  190. setiastro/saspro/blemish_blaster.py +494 -0
  191. setiastro/saspro/blink_comparator_pro.py +3149 -0
  192. setiastro/saspro/bundles.py +61 -0
  193. setiastro/saspro/bundles_dock.py +114 -0
  194. setiastro/saspro/cheat_sheet.py +213 -0
  195. setiastro/saspro/clahe.py +371 -0
  196. setiastro/saspro/comet_stacking.py +1442 -0
  197. setiastro/saspro/common_tr.py +107 -0
  198. setiastro/saspro/config.py +38 -0
  199. setiastro/saspro/config_bootstrap.py +40 -0
  200. setiastro/saspro/config_manager.py +316 -0
  201. setiastro/saspro/continuum_subtract.py +1620 -0
  202. setiastro/saspro/convo.py +1403 -0
  203. setiastro/saspro/convo_preset.py +414 -0
  204. setiastro/saspro/copyastro.py +190 -0
  205. setiastro/saspro/cosmicclarity.py +1593 -0
  206. setiastro/saspro/cosmicclarity_preset.py +407 -0
  207. setiastro/saspro/crop_dialog_pro.py +1005 -0
  208. setiastro/saspro/crop_preset.py +189 -0
  209. setiastro/saspro/curve_editor_pro.py +2608 -0
  210. setiastro/saspro/curves_preset.py +375 -0
  211. setiastro/saspro/debayer.py +673 -0
  212. setiastro/saspro/debug_utils.py +29 -0
  213. setiastro/saspro/dnd_mime.py +35 -0
  214. setiastro/saspro/doc_manager.py +2727 -0
  215. setiastro/saspro/exoplanet_detector.py +2258 -0
  216. setiastro/saspro/file_utils.py +284 -0
  217. setiastro/saspro/fitsmodifier.py +748 -0
  218. setiastro/saspro/fix_bom.py +32 -0
  219. setiastro/saspro/free_torch_memory.py +48 -0
  220. setiastro/saspro/frequency_separation.py +1352 -0
  221. setiastro/saspro/function_bundle.py +1596 -0
  222. setiastro/saspro/generate_translations.py +3092 -0
  223. setiastro/saspro/ghs_dialog_pro.py +728 -0
  224. setiastro/saspro/ghs_preset.py +284 -0
  225. setiastro/saspro/graxpert.py +638 -0
  226. setiastro/saspro/graxpert_preset.py +287 -0
  227. setiastro/saspro/gui/__init__.py +0 -0
  228. setiastro/saspro/gui/main_window.py +8928 -0
  229. setiastro/saspro/gui/mixins/__init__.py +33 -0
  230. setiastro/saspro/gui/mixins/dock_mixin.py +375 -0
  231. setiastro/saspro/gui/mixins/file_mixin.py +450 -0
  232. setiastro/saspro/gui/mixins/geometry_mixin.py +503 -0
  233. setiastro/saspro/gui/mixins/header_mixin.py +441 -0
  234. setiastro/saspro/gui/mixins/mask_mixin.py +421 -0
  235. setiastro/saspro/gui/mixins/menu_mixin.py +391 -0
  236. setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
  237. setiastro/saspro/gui/mixins/toolbar_mixin.py +1824 -0
  238. setiastro/saspro/gui/mixins/update_mixin.py +323 -0
  239. setiastro/saspro/gui/mixins/view_mixin.py +477 -0
  240. setiastro/saspro/gui/statistics_dialog.py +47 -0
  241. setiastro/saspro/halobgon.py +492 -0
  242. setiastro/saspro/header_viewer.py +448 -0
  243. setiastro/saspro/headless_utils.py +88 -0
  244. setiastro/saspro/histogram.py +760 -0
  245. setiastro/saspro/history_explorer.py +941 -0
  246. setiastro/saspro/i18n.py +168 -0
  247. setiastro/saspro/image_combine.py +421 -0
  248. setiastro/saspro/image_peeker_pro.py +1608 -0
  249. setiastro/saspro/imageops/__init__.py +37 -0
  250. setiastro/saspro/imageops/mdi_snap.py +292 -0
  251. setiastro/saspro/imageops/scnr.py +36 -0
  252. setiastro/saspro/imageops/starbasedwhitebalance.py +210 -0
  253. setiastro/saspro/imageops/stretch.py +236 -0
  254. setiastro/saspro/isophote.py +1186 -0
  255. setiastro/saspro/layers.py +208 -0
  256. setiastro/saspro/layers_dock.py +714 -0
  257. setiastro/saspro/lazy_imports.py +193 -0
  258. setiastro/saspro/legacy/__init__.py +2 -0
  259. setiastro/saspro/legacy/image_manager.py +2360 -0
  260. setiastro/saspro/legacy/numba_utils.py +3676 -0
  261. setiastro/saspro/legacy/xisf.py +1213 -0
  262. setiastro/saspro/linear_fit.py +537 -0
  263. setiastro/saspro/live_stacking.py +1854 -0
  264. setiastro/saspro/log_bus.py +5 -0
  265. setiastro/saspro/logging_config.py +460 -0
  266. setiastro/saspro/luminancerecombine.py +510 -0
  267. setiastro/saspro/main_helpers.py +201 -0
  268. setiastro/saspro/mask_creation.py +1090 -0
  269. setiastro/saspro/masks_core.py +56 -0
  270. setiastro/saspro/mdi_widgets.py +353 -0
  271. setiastro/saspro/memory_utils.py +666 -0
  272. setiastro/saspro/metadata_patcher.py +75 -0
  273. setiastro/saspro/mfdeconv.py +3909 -0
  274. setiastro/saspro/mfdeconv_earlystop.py +71 -0
  275. setiastro/saspro/mfdeconvcudnn.py +3312 -0
  276. setiastro/saspro/mfdeconvsport.py +2459 -0
  277. setiastro/saspro/minorbodycatalog.py +567 -0
  278. setiastro/saspro/morphology.py +411 -0
  279. setiastro/saspro/multiscale_decomp.py +1751 -0
  280. setiastro/saspro/nbtorgb_stars.py +541 -0
  281. setiastro/saspro/numba_utils.py +3145 -0
  282. setiastro/saspro/numba_warmup.py +141 -0
  283. setiastro/saspro/ops/__init__.py +9 -0
  284. setiastro/saspro/ops/command_help_dialog.py +623 -0
  285. setiastro/saspro/ops/command_runner.py +217 -0
  286. setiastro/saspro/ops/commands.py +1594 -0
  287. setiastro/saspro/ops/script_editor.py +1105 -0
  288. setiastro/saspro/ops/scripts.py +1476 -0
  289. setiastro/saspro/ops/settings.py +637 -0
  290. setiastro/saspro/parallel_utils.py +554 -0
  291. setiastro/saspro/pedestal.py +121 -0
  292. setiastro/saspro/perfect_palette_picker.py +1105 -0
  293. setiastro/saspro/pipeline.py +110 -0
  294. setiastro/saspro/pixelmath.py +1604 -0
  295. setiastro/saspro/plate_solver.py +2480 -0
  296. setiastro/saspro/project_io.py +797 -0
  297. setiastro/saspro/psf_utils.py +136 -0
  298. setiastro/saspro/psf_viewer.py +631 -0
  299. setiastro/saspro/pyi_rthook_astroquery.py +95 -0
  300. setiastro/saspro/remove_green.py +331 -0
  301. setiastro/saspro/remove_stars.py +1599 -0
  302. setiastro/saspro/remove_stars_preset.py +446 -0
  303. setiastro/saspro/resources.py +570 -0
  304. setiastro/saspro/rgb_combination.py +208 -0
  305. setiastro/saspro/rgb_extract.py +19 -0
  306. setiastro/saspro/rgbalign.py +727 -0
  307. setiastro/saspro/runtime_imports.py +7 -0
  308. setiastro/saspro/runtime_torch.py +754 -0
  309. setiastro/saspro/save_options.py +73 -0
  310. setiastro/saspro/selective_color.py +1614 -0
  311. setiastro/saspro/sfcc.py +1530 -0
  312. setiastro/saspro/shortcuts.py +3125 -0
  313. setiastro/saspro/signature_insert.py +1106 -0
  314. setiastro/saspro/stacking_suite.py +19069 -0
  315. setiastro/saspro/star_alignment.py +7383 -0
  316. setiastro/saspro/star_alignment_preset.py +329 -0
  317. setiastro/saspro/star_metrics.py +49 -0
  318. setiastro/saspro/star_spikes.py +769 -0
  319. setiastro/saspro/star_stretch.py +542 -0
  320. setiastro/saspro/stat_stretch.py +554 -0
  321. setiastro/saspro/status_log_dock.py +78 -0
  322. setiastro/saspro/subwindow.py +3523 -0
  323. setiastro/saspro/supernovaasteroidhunter.py +1719 -0
  324. setiastro/saspro/swap_manager.py +134 -0
  325. setiastro/saspro/torch_backend.py +89 -0
  326. setiastro/saspro/torch_rejection.py +434 -0
  327. setiastro/saspro/translations/all_source_strings.json +4726 -0
  328. setiastro/saspro/translations/ar_translations.py +4096 -0
  329. setiastro/saspro/translations/de_translations.py +3728 -0
  330. setiastro/saspro/translations/es_translations.py +4169 -0
  331. setiastro/saspro/translations/fr_translations.py +4090 -0
  332. setiastro/saspro/translations/hi_translations.py +3803 -0
  333. setiastro/saspro/translations/integrate_translations.py +271 -0
  334. setiastro/saspro/translations/it_translations.py +4728 -0
  335. setiastro/saspro/translations/ja_translations.py +3834 -0
  336. setiastro/saspro/translations/pt_translations.py +3847 -0
  337. setiastro/saspro/translations/ru_translations.py +3082 -0
  338. setiastro/saspro/translations/saspro_ar.qm +0 -0
  339. setiastro/saspro/translations/saspro_ar.ts +16019 -0
  340. setiastro/saspro/translations/saspro_de.qm +0 -0
  341. setiastro/saspro/translations/saspro_de.ts +14548 -0
  342. setiastro/saspro/translations/saspro_es.qm +0 -0
  343. setiastro/saspro/translations/saspro_es.ts +16202 -0
  344. setiastro/saspro/translations/saspro_fr.qm +0 -0
  345. setiastro/saspro/translations/saspro_fr.ts +15870 -0
  346. setiastro/saspro/translations/saspro_hi.qm +0 -0
  347. setiastro/saspro/translations/saspro_hi.ts +14855 -0
  348. setiastro/saspro/translations/saspro_it.qm +0 -0
  349. setiastro/saspro/translations/saspro_it.ts +19046 -0
  350. setiastro/saspro/translations/saspro_ja.qm +0 -0
  351. setiastro/saspro/translations/saspro_ja.ts +14980 -0
  352. setiastro/saspro/translations/saspro_pt.qm +0 -0
  353. setiastro/saspro/translations/saspro_pt.ts +15024 -0
  354. setiastro/saspro/translations/saspro_ru.qm +0 -0
  355. setiastro/saspro/translations/saspro_ru.ts +11835 -0
  356. setiastro/saspro/translations/saspro_sw.qm +0 -0
  357. setiastro/saspro/translations/saspro_sw.ts +15237 -0
  358. setiastro/saspro/translations/saspro_uk.qm +0 -0
  359. setiastro/saspro/translations/saspro_uk.ts +15248 -0
  360. setiastro/saspro/translations/saspro_zh.qm +0 -0
  361. setiastro/saspro/translations/saspro_zh.ts +15289 -0
  362. setiastro/saspro/translations/sw_translations.py +3897 -0
  363. setiastro/saspro/translations/uk_translations.py +3929 -0
  364. setiastro/saspro/translations/zh_translations.py +3910 -0
  365. setiastro/saspro/versioning.py +77 -0
  366. setiastro/saspro/view_bundle.py +1558 -0
  367. setiastro/saspro/wavescale_hdr.py +648 -0
  368. setiastro/saspro/wavescale_hdr_preset.py +101 -0
  369. setiastro/saspro/wavescalede.py +683 -0
  370. setiastro/saspro/wavescalede_preset.py +230 -0
  371. setiastro/saspro/wcs_update.py +374 -0
  372. setiastro/saspro/whitebalance.py +540 -0
  373. setiastro/saspro/widgets/__init__.py +48 -0
  374. setiastro/saspro/widgets/common_utilities.py +306 -0
  375. setiastro/saspro/widgets/graphics_views.py +122 -0
  376. setiastro/saspro/widgets/image_utils.py +518 -0
  377. setiastro/saspro/widgets/minigame/game.js +991 -0
  378. setiastro/saspro/widgets/minigame/index.html +53 -0
  379. setiastro/saspro/widgets/minigame/style.css +241 -0
  380. setiastro/saspro/widgets/preview_dialogs.py +280 -0
  381. setiastro/saspro/widgets/resource_monitor.py +313 -0
  382. setiastro/saspro/widgets/spinboxes.py +290 -0
  383. setiastro/saspro/widgets/themed_buttons.py +13 -0
  384. setiastro/saspro/widgets/wavelet_utils.py +331 -0
  385. setiastro/saspro/wimi.py +7367 -0
  386. setiastro/saspro/wims.py +588 -0
  387. setiastro/saspro/window_shelf.py +185 -0
  388. setiastro/saspro/xisf.py +1213 -0
  389. setiastrosuitepro-1.6.7.dist-info/METADATA +279 -0
  390. setiastrosuitepro-1.6.7.dist-info/RECORD +394 -0
  391. setiastrosuitepro-1.6.7.dist-info/WHEEL +4 -0
  392. setiastrosuitepro-1.6.7.dist-info/entry_points.txt +6 -0
  393. setiastrosuitepro-1.6.7.dist-info/licenses/LICENSE +674 -0
  394. setiastrosuitepro-1.6.7.dist-info/licenses/license.txt +2580 -0
@@ -0,0 +1,673 @@
1
+ # pro/debayer.py
2
+ from __future__ import annotations
3
+ import numpy as np
4
+ from typing import Optional, Tuple
5
+ import cv2
6
+ import os
7
+
8
+ from PyQt6.QtCore import Qt, QThread, pyqtSignal
9
+ from PyQt6.QtWidgets import (
10
+ QDialog, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QDialogButtonBox,
11
+ QGroupBox, QMessageBox, QProgressBar
12
+ )
13
+
14
+ # fast kernels you already have
15
+ try:
16
+ from setiastro.saspro.legacy.numba_utils import debayer_fits_fast
17
+ except Exception as e: # very unlikely in your env
18
+ debayer_fits_fast = None
19
+
20
+
21
+ _RAW_EXTS = (".raf", ".raw", ".rw2", ".arw", ".nef", ".cr2", ".cr3", ".dng", ".orf", ".pef")
22
+
23
+ def _find_raw_sibling(path: Optional[str]) -> Optional[str]:
24
+ """
25
+ If we only have a derived FITS/XISF path, try to locate a plausible RAW
26
+ with the same stem in the same folder.
27
+ """
28
+ if not path:
29
+ return None
30
+ base, _ = os.path.splitext(os.path.basename(path))
31
+ folder = os.path.dirname(path)
32
+ if not folder or not base:
33
+ return None
34
+ try:
35
+ for ext in _RAW_EXTS:
36
+ cand = os.path.join(folder, base + ext)
37
+ if os.path.exists(cand):
38
+ return cand
39
+ except Exception:
40
+ pass
41
+ return None
42
+
43
+ # -------- helpers ------------------------------------------------------------
44
+ _BAYER_METHODS = [
45
+ ("Edge-aware (Numba)", "edge"),
46
+ ("Bilinear (Numba)", "bilinear"),
47
+ ]
48
+ _XTRANS_METHODS = [
49
+ ("AHD (rawpy)", "AHD"),
50
+ ("DHT (rawpy)", "DHT"),
51
+ ]
52
+ _VALID = {"RGGB", "BGGR", "GRBG", "GBRG"}
53
+
54
+ def _normalize_bayer_token(s: str) -> Optional[str]:
55
+ if not s:
56
+ return None
57
+ s = s.upper().replace(",", "").replace(" ", "").replace("/", "").replace("|", "")
58
+ if len(s) == 4 and set(s) <= {"R", "G", "B"}:
59
+ if s.count("R") == 1 and s.count("G") == 2 and s.count("B") == 1:
60
+ return s if s in _VALID else None
61
+ return None
62
+
63
+ def _detect_bayer_from_header(doc) -> Optional[str]:
64
+ """
65
+ Best-effort read of a Bayer pattern from the document header/metadata.
66
+ Returns 'RGGB'/'BGGR'/'GRBG'/'GBRG' or None if not found.
67
+ """
68
+ hdr, meta, _ = _extract_doc_info(doc)
69
+
70
+ probe = {}
71
+ # FITS-like header (astropy Header behaves like a dict, case-insensitive)
72
+ if hdr is not None:
73
+ try:
74
+ keys = list(hdr.keys()) if hasattr(hdr, "keys") else []
75
+ for k in keys:
76
+ try:
77
+ v = hdr.get(k) if hasattr(hdr, "get") else hdr[k]
78
+ except Exception:
79
+ v = None
80
+ probe[str(k).upper()] = "" if v is None else str(v)
81
+ except Exception:
82
+ pass
83
+
84
+ # Merge doc metadata (strings only)
85
+ if isinstance(meta, dict):
86
+ for k, v in list(meta.items()):
87
+ try:
88
+ probe[str(k).upper()] = "" if v is None else str(v)
89
+ except Exception:
90
+ continue
91
+
92
+ keys = [
93
+ "BAYERPAT", "BAYERPATN", "BAYER_PATTERN", "BAYERPATTERN",
94
+ "CFAPATTERN", "CFA_PATTERN", "PATTERN", "COLORTYPE", "COLORFILTERARRAY"
95
+ ]
96
+ for k in keys:
97
+ raw = probe.get(k)
98
+ if not raw:
99
+ continue
100
+ s = str(raw).upper()
101
+ for pat in _VALID:
102
+ if pat in s:
103
+ return pat
104
+ norm = _normalize_bayer_token(s)
105
+ if norm:
106
+ return norm
107
+ return None
108
+
109
+
110
+ def _detect_cfa_family(doc) -> Optional[str]:
111
+ """
112
+ Returns 'BAYER', 'XTRANS', or None.
113
+ Uses header/meta hints; if a RAW path exists, asks rawpy for ground truth.
114
+ """
115
+ hdr, meta, src_path = _extract_doc_info(doc)
116
+
117
+ # Build a searchable blob of header + meta
118
+ def _safe_blob(x) -> str:
119
+ try:
120
+ return str(x)
121
+ except Exception:
122
+ return ""
123
+ blob = (_safe_blob(hdr) + " " + _safe_blob(meta)).upper()
124
+
125
+ # Direct tokens first
126
+ if any(k in blob for k in ("X-TRANS", "XTRANS", "FUJIFILM X-TRANS", "X TRANS")):
127
+ return "XTRANS"
128
+ if any(k in blob for k in ("BAYER", "RGGB", "BGGR", "GRBG", "GBRG")):
129
+ return "BAYER"
130
+
131
+ # Camera model hint
132
+ model = (str(meta.get("MODEL")
133
+ or meta.get("CameraModel")
134
+ or (hdr.get("MODEL") if hasattr(hdr, "get") else None)
135
+ or "")).upper()
136
+ make = (str(meta.get("MAKE")
137
+ or (hdr.get("MAKE") if hasattr(hdr, "get") else None)
138
+ or "")).upper()
139
+ if "FUJIFILM" in make and any(tag in model for tag in (
140
+ "X-T1","X-T2","X-T3","X-T4","X-T5",
141
+ "X-E2","X-E2S","X-E3","X-PRO1","X-PRO2","X-PRO3",
142
+ "X-H1","X-H2S","X-S10","X-S20","X100","X70","X30"
143
+ )):
144
+ return "XTRANS"
145
+
146
+ # If we have a source path, ask rawpy
147
+ if src_path:
148
+ try:
149
+ import rawpy # type: ignore
150
+ with rawpy.imread(src_path) as rp:
151
+ if getattr(rp, "xtrans_pattern", None) is not None:
152
+ return "XTRANS"
153
+ pat = getattr(rp, "raw_pattern", None)
154
+ if pat is not None:
155
+ pat = np.array(pat)
156
+ if getattr(pat, "shape", None) == (2, 2):
157
+ return "BAYER"
158
+ except Exception:
159
+ pass
160
+
161
+ return None
162
+
163
+
164
+ def _score_rgb(rgb: np.ndarray) -> float:
165
+ r, g, b = rgb[..., 0], rgb[..., 1], rgb[..., 2]
166
+ def grad_energy(x):
167
+ gx = cv2.Sobel(x, cv2.CV_32F, 1, 0, ksize=3)
168
+ gy = cv2.Sobel(x, cv2.CV_32F, 0, 1, ksize=3)
169
+ return float(np.mean(gx*gx + gy*gy))
170
+ e_rg = grad_energy(r - g)
171
+ e_bg = grad_energy(b - g)
172
+ return e_rg + e_bg
173
+
174
+ def _autodetect_bayer_by_scoring(mono: np.ndarray) -> str:
175
+ candidates = ["RGGB", "BGGR", "GRBG", "GBRG"]
176
+ best_pat, best_score = None, float("inf")
177
+ for pat in candidates:
178
+ try:
179
+ rgb = debayer_fits_fast(mono, pat, cfa_drizzle=False)
180
+ rgb32 = rgb.astype(np.float32, copy=False)
181
+ s = _score_rgb(rgb32)
182
+ if s < best_score:
183
+ best_score, best_pat = s, pat
184
+ except Exception:
185
+ continue
186
+ return best_pat or "RGGB"
187
+
188
+ def _debayer_xtrans_via_rawpy(src_path: str,
189
+ use_cam_wb: bool = True,
190
+ output_bps: int = 16,
191
+ alg: str = "AHD") -> np.ndarray:
192
+ """
193
+ X-Trans demosaic via rawpy with selectable algorithm: 'AHD' or 'DHT'.
194
+ Returns float32 RGB in [0,1].
195
+ """
196
+ import rawpy # type: ignore
197
+ alg_map = {
198
+ "AHD": rawpy.DemosaicAlgorithm.AHD,
199
+ "DHT": rawpy.DemosaicAlgorithm.DHT,
200
+ }
201
+ dem = alg_map.get((alg or "AHD").upper(), rawpy.DemosaicAlgorithm.AHD)
202
+ with rawpy.imread(src_path) as rp:
203
+ rgb16 = rp.postprocess(
204
+ demosaic_algorithm=dem,
205
+ no_auto_bright=True,
206
+ gamma=(1.0, 1.0),
207
+ output_bps=output_bps,
208
+ use_camera_wb=use_cam_wb,
209
+ fbdd_noise_reduction=rawpy.FBDDNoiseReductionMode.Off,
210
+ four_color_rgb=False,
211
+ half_size=False,
212
+ bright=1.0,
213
+ highlight_mode=rawpy.HighlightMode.Clip
214
+ )
215
+ return (rgb16.astype(np.float32) / (65535.0 if output_bps == 16 else 255.0))
216
+
217
+ def _doc_is_managed(dm, doc) -> bool:
218
+ """Best-effort: is this document tracked by DocManager?"""
219
+ if dm is None or doc is None:
220
+ return False
221
+ # Explicit API if you have it
222
+ fn = getattr(dm, "is_managed_document", None)
223
+ if callable(fn):
224
+ try:
225
+ return bool(fn(doc))
226
+ except Exception:
227
+ pass
228
+ # Common internal collections
229
+ for attr in ("_docs", "documents", "open_documents", "all_docs"):
230
+ coll = getattr(dm, attr, None)
231
+ if isinstance(coll, (list, tuple)):
232
+ try:
233
+ return any(d is doc for d in coll)
234
+ except Exception:
235
+ continue
236
+ return False
237
+
238
+ def _apply_result_to_doc(dm, doc, rgb: np.ndarray, step_name: str = "Debayer"):
239
+ """
240
+ Always apply to the specific 'doc' (never assume 'active' view).
241
+ """
242
+
243
+ # --- A) Best: the document can commit its own undoable edit ---
244
+ if hasattr(doc, "apply_edit") and callable(doc.apply_edit):
245
+ meta = dict(getattr(doc, "metadata", {}) or {})
246
+ meta["is_mono"] = False
247
+ meta.setdefault("bit_depth", "32-bit floating point")
248
+ try:
249
+ doc.apply_edit(rgb.copy(), metadata=meta, step_name=step_name)
250
+ except TypeError:
251
+ # older signature: (image, metadata) or (image,)
252
+ try: doc.apply_edit(rgb.copy(), metadata=meta)
253
+ except Exception: doc.apply_edit(rgb.copy())
254
+ _refresh_view_for_doc(dm, doc)
255
+ return
256
+
257
+ # --- B) Next best: mutate the doc directly, then refresh its view ---
258
+ try:
259
+ doc.image = rgb
260
+ meta = getattr(doc, "metadata", None)
261
+ if isinstance(meta, dict):
262
+ meta["is_mono"] = False
263
+ meta.setdefault("bit_depth", "32-bit floating point")
264
+ except Exception:
265
+ pass
266
+ _refresh_view_for_doc(dm, doc)
267
+ return
268
+
269
+ def _refresh_view_for_doc(dm, doc):
270
+ """
271
+ Find the subwindow that displays 'doc' and ask it to repaint,
272
+ without switching the active view.
273
+ """
274
+ try:
275
+ # Try a dedicated API if your app exposes it
276
+ fn = getattr(dm, "refresh_subwindow_for_document", None)
277
+ if callable(fn):
278
+ fn(doc); return
279
+ except Exception:
280
+ pass
281
+
282
+ # Generic fallback: walk MDI subwindows and update the one bound to 'doc'
283
+ try:
284
+ mw = getattr(dm, "main_window", None) or getattr(dm, "mw", None)
285
+ if mw is None: return
286
+ mdi = getattr(mw, "mdi", None)
287
+ if mdi is None: return
288
+ for sw in mdi.subWindowList():
289
+ w = getattr(sw, "widget", lambda: None)()
290
+ if getattr(w, "document", None) is doc:
291
+ # Common update hooks
292
+ upd = getattr(w, "refresh_pixmap_from_document", None) \
293
+ or getattr(w, "refresh_view", None) \
294
+ or getattr(w, "update_from_doc", None)
295
+ if callable(upd):
296
+ upd(); return
297
+ # Absolute fallback: force a repaint
298
+ try:
299
+ w.update(); sw.update()
300
+ except Exception:
301
+ pass
302
+ return
303
+ except Exception:
304
+ pass
305
+
306
+
307
+ # -------- worker -------------------------------------------------------------
308
+
309
+ class _DebayerWorker(QThread):
310
+ progress = pyqtSignal(int, str)
311
+ failed = pyqtSignal(str)
312
+ finished = pyqtSignal(np.ndarray, str) # (rgb, used_pattern)
313
+
314
+ def __init__(self, mono: np.ndarray, pattern: str, method: str = "edge"):
315
+ super().__init__()
316
+ self.mono = mono
317
+ self.pattern = pattern
318
+ self.method = (method or "edge")
319
+
320
+ def run(self):
321
+ try:
322
+ if debayer_fits_fast is None:
323
+ raise RuntimeError("Numba debayer kernels not available.")
324
+
325
+ # enforce kernel-friendly layout/dtype
326
+ img = _mono_as_float32_contig(self.mono)
327
+
328
+ if img.ndim != 2:
329
+ raise ValueError("Debayer expects a single-channel (mosaic) image.")
330
+ if self.pattern not in _VALID:
331
+ raise ValueError(f"Unsupported pattern: {self.pattern}")
332
+
333
+ self.progress.emit(5, f"Debayering ({self.pattern}, {self.method}) …")
334
+ rgb = debayer_fits_fast(img, self.pattern, cfa_drizzle=False, method=self.method)
335
+ self.progress.emit(96, "Finalizing …")
336
+ self.finished.emit(rgb, self.pattern)
337
+ except Exception as e:
338
+ self.failed.emit(str(e))
339
+
340
+ def _extract_doc_info(doc) -> tuple[dict | None, dict, Optional[str]]:
341
+ meta = getattr(doc, "metadata", {}) or {}
342
+ hdr = (meta.get("original_header")
343
+ or meta.get("fits_header")
344
+ or meta.get("header")
345
+ or getattr(doc, "header", None))
346
+
347
+ # try multiple fields for a source path
348
+ def _first_nonempty(*vals):
349
+ for v in vals:
350
+ if v:
351
+ return v
352
+ return None
353
+
354
+ # header cards that might store original RAW path
355
+ hdr_raw = None
356
+ try:
357
+ if hdr is not None:
358
+ for k in ("RAW_PATH","RAWFILE","ORIGFILE","ORIGINAL","ORIGPATH","RAWORIG","SOURCE","SRCFILE"):
359
+ v = hdr.get(k) if hasattr(hdr, "get") else hdr[k] # may raise → caught
360
+ if v:
361
+ hdr_raw = str(v)
362
+ break
363
+ except Exception:
364
+ pass
365
+
366
+ path = _first_nonempty(
367
+ meta.get("raw_source_path"),
368
+ hdr_raw,
369
+ meta.get("file_path"),
370
+ getattr(doc, "path", None),
371
+ getattr(doc, "file_path", None),
372
+ )
373
+ return hdr, meta, path
374
+
375
+ def _mono_as_float32_contig(arr: np.ndarray) -> np.ndarray:
376
+ """
377
+ Ensure mono mosaic is 2D, C-contiguous, float32 in [0,1] for numba kernels.
378
+ Scales integer inputs by their max (8/16/32 bits).
379
+ """
380
+ a = np.asarray(arr)
381
+ if a.ndim != 2:
382
+ raise RuntimeError("Debayer expects a single-channel (mosaic) image.")
383
+ if np.issubdtype(a.dtype, np.integer):
384
+ # pick a sensible scale based on dtype
385
+ info = np.iinfo(a.dtype)
386
+ a = a.astype(np.float32, copy=False) / float(info.max if info.max > 0 else 1.0)
387
+ else:
388
+ a = a.astype(np.float32, copy=False)
389
+ # if it looks like 0..65535 in float, normalize too
390
+ if a.max() > 2.0:
391
+ a = a / 65535.0
392
+ return np.ascontiguousarray(a)
393
+
394
+
395
+ # -------- dialog -------------------------------------------------------------
396
+
397
+ class DebayerDialog(QDialog):
398
+ """
399
+ One-shot debayer UI for the active view. Uses your numba kernels.
400
+ If the image is already RGB, will warn and exit.
401
+ """
402
+ def __init__(self, parent, doc_manager, active_doc):
403
+ super().__init__(parent)
404
+ self.setWindowTitle(self.tr("Debayer"))
405
+ self.setWindowFlag(Qt.WindowType.Window, True)
406
+ self.setWindowModality(Qt.WindowModality.NonModal)
407
+ self.setModal(False)
408
+ self.dm = doc_manager
409
+ self.doc = active_doc
410
+ self.worker: Optional[_DebayerWorker] = None
411
+
412
+ img = getattr(active_doc, "image", None)
413
+ if img is None:
414
+ raise RuntimeError("No image in active document.")
415
+ arr = np.asarray(img)
416
+
417
+ # Reject non-mosaic early
418
+ if arr.ndim == 3 and arr.shape[2] >= 3:
419
+ QMessageBox.information(self, "Debayer", "Image already has 3 channels.")
420
+ self.setEnabled(False)
421
+ self.close()
422
+ return
423
+ if arr.ndim != 2:
424
+ QMessageBox.warning(self, "Debayer", "Only single-channel mosaics can be debayered.")
425
+ self.setEnabled(False)
426
+ self.close()
427
+ return
428
+
429
+ # ✅ normalize for numba kernels
430
+ self._src = _mono_as_float32_contig(arr)
431
+
432
+ # detect CFA family (BAYER/XTRANS/None)
433
+ self._cfa_family = _detect_cfa_family(active_doc)
434
+
435
+ v = QVBoxLayout(self)
436
+
437
+ # pattern selection
438
+ detected = _detect_bayer_from_header(active_doc)
439
+ self._detected_pattern = detected # store for later
440
+
441
+ gb = QGroupBox(self.tr("Bayer pattern"), self)
442
+ h = QHBoxLayout(gb)
443
+ self.combo_pattern = QComboBox(self)
444
+ self.combo_pattern.addItems([
445
+ "Auto (from header)",
446
+ "RGGB", "BGGR", "GRBG", "GBRG",
447
+ ])
448
+ self.combo_pattern.setCurrentIndex(0)
449
+ self.lbl_detect = QLabel(f"Detected: {detected or '(unknown)'}")
450
+ h.addWidget(self.combo_pattern, 1)
451
+ h.addWidget(self.lbl_detect)
452
+ v.addWidget(gb)
453
+
454
+ if self._cfa_family == 'XTRANS':
455
+ self.combo_pattern.setEnabled(False)
456
+ self.lbl_detect.setText("Detected: X-Trans (rawpy)")
457
+ else:
458
+ norm = _normalize_bayer_token(self._detected_pattern or "")
459
+ self.lbl_detect.setText(f"Detected: {norm or '(unknown)'}")
460
+
461
+ self.method_group = QGroupBox(self.tr("Method"), self)
462
+ hm = QHBoxLayout(self.method_group)
463
+ self.combo_method = QComboBox(self)
464
+
465
+ if self._cfa_family == 'XTRANS':
466
+ for label, _tok in _XTRANS_METHODS:
467
+ self.combo_method.addItem(label)
468
+ else:
469
+ for label, _tok in _BAYER_METHODS:
470
+ self.combo_method.addItem(label)
471
+
472
+ print(f"[Debayer] CFA family auto-detect → {self._cfa_family} "
473
+ f"path={_extract_doc_info(active_doc)[2]} "
474
+ f"model={getattr(active_doc, 'metadata', {}).get('MODEL')}")
475
+
476
+ self.combo_method.setCurrentIndex(0)
477
+ hm.addWidget(self.combo_method)
478
+ hm.addStretch(1)
479
+ v.addWidget(self.method_group)
480
+
481
+ # progress + buttons
482
+ self.status = QLabel("")
483
+ self.bar = QProgressBar(self); self.bar.setRange(0, 100)
484
+ v.addWidget(self.status)
485
+ v.addWidget(self.bar)
486
+
487
+ btns = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, parent=self)
488
+ btns.accepted.connect(self._go)
489
+ btns.rejected.connect(self.reject)
490
+ v.addWidget(btns)
491
+
492
+
493
+ def _chosen_pattern(self) -> str:
494
+ if self._cfa_family == 'XTRANS':
495
+ return "XTRANS"
496
+ txt = self.combo_pattern.currentText()
497
+ if txt.startswith("Auto"):
498
+ norm = _normalize_bayer_token(self._detected_pattern or "")
499
+ return norm or _autodetect_bayer_by_scoring(self._src)
500
+ return txt
501
+
502
+ def _go(self):
503
+ pat = self._chosen_pattern()
504
+ method_label = self.combo_method.currentText()
505
+
506
+ # X-Trans path
507
+ if pat == "XTRANS":
508
+ src_path = (getattr(self.doc, "file_path", None) or getattr(self.doc, "path", None)
509
+ or (getattr(self.doc, "metadata", {}) or {}).get("file_path"))
510
+ if not src_path or not str(src_path).lower().endswith(_RAW_EXTS):
511
+ # try sibling RAW next to this file
512
+ sib = _find_raw_sibling(getattr(self.doc, "file_path", None) or (getattr(self.doc, "metadata", {}) or {}).get("file_path"))
513
+ if sib:
514
+ src_path = sib
515
+ else:
516
+ QMessageBox.warning(self, "Debayer",
517
+ "X-Trans detected, but original RAW path was not found.\n"
518
+ "Open the RAF directly, or embed RAW_PATH in the header, or place the RAW next to the file.")
519
+ return
520
+ # map label → rawpy alg token
521
+ alg = next((tok for (label, tok) in _XTRANS_METHODS if label == method_label), "AHD")
522
+ try:
523
+ self.status.setText(f"Demosaicing X-Trans via rawpy ({alg}) …")
524
+ self.bar.setValue(10)
525
+ rgb = _debayer_xtrans_via_rawpy(src_path, use_cam_wb=True, output_bps=16, alg=alg)
526
+ self.bar.setValue(96)
527
+ _apply_result_to_doc(self.dm, self.doc, rgb.astype(np.float32, copy=False),
528
+ step_name=f"Debayer (X-Trans/{alg})")
529
+ self.status.setText("Done.")
530
+ self.accept()
531
+ except Exception as e:
532
+ QMessageBox.critical(self, "Debayer", f"X-Trans demosaic failed:\n{e}")
533
+ self.status.setText("Failed.")
534
+ return
535
+
536
+ # Bayer path (Numba) with method
537
+ if pat not in _VALID:
538
+ QMessageBox.warning(self, "Debayer", "Unknown pattern (auto-detect failed). Choose a pattern explicitly.")
539
+ return
540
+
541
+ bayer_method = next((tok for (label, tok) in _BAYER_METHODS if label == method_label), "edge")
542
+ self.status.setText(f"Debayering as {pat} ({bayer_method}) …")
543
+ self.bar.setValue(0)
544
+ self.worker = _DebayerWorker(self._src, pat, method=bayer_method)
545
+ self.worker.progress.connect(self._on_prog)
546
+ self.worker.failed.connect(self._on_fail)
547
+ self.worker.finished.connect(self._on_done)
548
+ self.worker.start()
549
+
550
+
551
+ def _on_prog(self, p: int, msg: str):
552
+ self.bar.setValue(p); self.status.setText(msg)
553
+
554
+ def _on_fail(self, err: str):
555
+ QMessageBox.critical(self, "Debayer", err)
556
+ self.status.setText("Failed.")
557
+
558
+ def _on_done(self, rgb: np.ndarray, used_pattern: str):
559
+ # Hand back to doc manager with an undo step name
560
+ _apply_result_to_doc(self.dm, self.doc, rgb, step_name=f"Debayer ({used_pattern})")
561
+ self.status.setText("Done.")
562
+ self.accept()
563
+
564
+
565
+ # -------- headless (shortcut / DnD) -----------------------------------------
566
+
567
+ def apply_debayer_preset_to_doc(dm, doc, preset: dict) -> Tuple[str, np.ndarray]:
568
+ """
569
+ preset = {
570
+ "pattern": "auto|RGGB|BGGR|GRBG|GBRG",
571
+ "method": "auto|edge|bilinear|AHD|DHT"
572
+ }
573
+ Returns (used_pattern, rgb_array).
574
+ """
575
+ if getattr(doc, "image", None) is None:
576
+ raise RuntimeError("No image in document.")
577
+
578
+ # ✅ normalize for numba kernels & ensure 2D
579
+ mono_in = np.asarray(doc.image)
580
+ if mono_in.ndim != 2:
581
+ raise RuntimeError("Debayer expects a single-channel (mosaic) image.")
582
+ mono = _mono_as_float32_contig(mono_in)
583
+
584
+ family = _detect_cfa_family(doc)
585
+ want_method = str(preset.get("method", "auto"))
586
+
587
+ # X-Trans → rawpy path
588
+ if family == "XTRANS":
589
+ src_path = (getattr(doc, "file_path", None) or getattr(doc, "path", None)
590
+ or (getattr(doc, "metadata", {}) or {}).get("file_path"))
591
+
592
+ if not src_path or not str(src_path).lower().endswith(_RAW_EXTS):
593
+ hdr, meta, _ = _extract_doc_info(doc)
594
+ src_path = (meta.get("raw_source_path") or
595
+ (hdr.get("RAW_PATH") if hasattr(hdr, "get") else None) or
596
+ _find_raw_sibling(meta.get("file_path") or getattr(doc, "file_path", None)))
597
+ if not src_path or not str(src_path).lower().endswith(_RAW_EXTS):
598
+ raise RuntimeError("X-Trans detected, but no RAW found. "
599
+ "Embed RAW_PATH/raw_source_path or place the RAW next to the file.")
600
+ alg = want_method if want_method in ("AHD", "DHT") else "AHD"
601
+ rgb = _debayer_xtrans_via_rawpy(src_path, use_cam_wb=True, output_bps=16, alg=alg)
602
+ _apply_result_to_doc(dm, doc, rgb, step_name=f"Debayer (X-Trans/{alg})")
603
+ return "XTRANS", rgb
604
+
605
+ # Bayer → Numba path
606
+ want = str(preset.get("pattern", "auto")).upper()
607
+ if want == "AUTO":
608
+ pat = _normalize_bayer_token(_detect_bayer_from_header(doc) or "")
609
+ if pat not in _VALID:
610
+ pat = _autodetect_bayer_by_scoring(mono)
611
+ else:
612
+ pat = want
613
+ if pat not in _VALID:
614
+ raise ValueError(f"Unsupported Bayer pattern: {pat}")
615
+
616
+ method_tok = (want_method.lower() if want_method.lower() in ("edge", "bilinear") else "edge")
617
+
618
+ if debayer_fits_fast is None:
619
+ raise RuntimeError("Numba debayer kernels not available.")
620
+
621
+ rgb = debayer_fits_fast(mono, pat, cfa_drizzle=False, method=method_tok)
622
+ _apply_result_to_doc(dm, doc, rgb, step_name=f"Debayer ({pat}/{method_tok})")
623
+ return pat, rgb
624
+
625
+
626
+ # -------- headless command runner (Scripts / Presets / Replay) ---------------
627
+
628
+ def run_debayer_via_preset(main, preset: dict | None = None, *, doc=None):
629
+ """
630
+ Headless Debayer runner.
631
+
632
+ preset keys:
633
+ - pattern: "auto" | "RGGB" | "BGGR" | "GRBG" | "GBRG"
634
+ - method: "auto" | "edge" | "bilinear" | "AHD" | "DHT"
635
+ """
636
+ p = dict(preset or {})
637
+
638
+ # ---- Register for Replay Last Action ----
639
+ try:
640
+ remember = getattr(main, "remember_last_headless_command", None) \
641
+ or getattr(main, "_remember_last_headless_command", None)
642
+ if callable(remember):
643
+ remember("debayer", p, description="Debayer")
644
+ else:
645
+ setattr(main, "_last_headless_command", {
646
+ "command_id": "debayer",
647
+ "preset": dict(p),
648
+ })
649
+ except Exception:
650
+ pass
651
+ # ----------------------------------------
652
+
653
+ dm = getattr(main, "doc_manager", None) or getattr(main, "dm", None)
654
+ if dm is None:
655
+ QMessageBox.warning(main, "Debayer", "DocManager not available.")
656
+ return
657
+
658
+ if doc is None:
659
+ d = getattr(main, "_active_doc", None)
660
+ doc = d() if callable(d) else d
661
+
662
+ if doc is None or getattr(doc, "image", None) is None:
663
+ QMessageBox.warning(main, "Debayer", "Load an image first.")
664
+ return
665
+
666
+ try:
667
+ used_pat, _rgb = apply_debayer_preset_to_doc(dm, doc, p)
668
+ if hasattr(main, "_log"):
669
+ main._log(f"✅ Debayer (headless) pattern={used_pat}, preset={p}")
670
+ except Exception as e:
671
+ QMessageBox.critical(main, "Debayer", str(e))
672
+ if hasattr(main, "_log"):
673
+ main._log(f"❌ Debayer failed: {e}")
@@ -0,0 +1,29 @@
1
+ # pro/debug_utils.py (or near save_document)
2
+
3
+ from astropy.io import fits
4
+ import logging
5
+
6
+ log = logging.getLogger(__name__)
7
+
8
+ def debug_dump_metadata(meta: dict, context: str = ""):
9
+ """
10
+ Dump all metadata keys and highlight any fits.Header objects.
11
+ """
12
+ if not isinstance(meta, dict):
13
+ log.debug("[MetaDump %s] metadata is not a dict: %r", context, type(meta))
14
+ return
15
+
16
+ log.debug("===== METADATA DUMP (%s) =====", context)
17
+ log.debug("keys: %s", ", ".join(sorted(meta.keys())))
18
+
19
+ for key, value in meta.items():
20
+ if isinstance(value, fits.Header):
21
+ log.debug("[MetaDump %s] %s -> fits.Header with %d cards", context, key, len(value))
22
+ # If you want the *full* header:
23
+ for card in value.cards:
24
+ log.debug("[MetaDump %s] %-10s = %r", context, card.keyword, card.value)
25
+ else:
26
+ log.debug("[MetaDump %s] %s -> %r (%s)",
27
+ context, key, value, type(value).__name__)
28
+
29
+ log.debug("===== END METADATA DUMP (%s) =====", context)