setiastrosuitepro 1.6.5.post3__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.
Files changed (368) 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/andromedatry.png +0 -0
  24. setiastro/images/andromedatry_satellited.png +0 -0
  25. setiastro/images/annotated.png +0 -0
  26. setiastro/images/aperture.png +0 -0
  27. setiastro/images/astrosuite.ico +0 -0
  28. setiastro/images/astrosuite.png +0 -0
  29. setiastro/images/astrosuitepro.icns +0 -0
  30. setiastro/images/astrosuitepro.ico +0 -0
  31. setiastro/images/astrosuitepro.png +0 -0
  32. setiastro/images/background.png +0 -0
  33. setiastro/images/background2.png +0 -0
  34. setiastro/images/benchmark.png +0 -0
  35. setiastro/images/big_moon_stabilizer_timeline.png +0 -0
  36. setiastro/images/big_moon_stabilizer_timeline_clean.png +0 -0
  37. setiastro/images/blaster.png +0 -0
  38. setiastro/images/blink.png +0 -0
  39. setiastro/images/clahe.png +0 -0
  40. setiastro/images/collage.png +0 -0
  41. setiastro/images/colorwheel.png +0 -0
  42. setiastro/images/contsub.png +0 -0
  43. setiastro/images/convo.png +0 -0
  44. setiastro/images/copyslot.png +0 -0
  45. setiastro/images/cosmic.png +0 -0
  46. setiastro/images/cosmicsat.png +0 -0
  47. setiastro/images/crop1.png +0 -0
  48. setiastro/images/cropicon.png +0 -0
  49. setiastro/images/curves.png +0 -0
  50. setiastro/images/cvs.png +0 -0
  51. setiastro/images/debayer.png +0 -0
  52. setiastro/images/denoise_cnn_custom.png +0 -0
  53. setiastro/images/denoise_cnn_graph.png +0 -0
  54. setiastro/images/disk.png +0 -0
  55. setiastro/images/dse.png +0 -0
  56. setiastro/images/exoicon.png +0 -0
  57. setiastro/images/eye.png +0 -0
  58. setiastro/images/fliphorizontal.png +0 -0
  59. setiastro/images/flipvertical.png +0 -0
  60. setiastro/images/font.png +0 -0
  61. setiastro/images/freqsep.png +0 -0
  62. setiastro/images/functionbundle.png +0 -0
  63. setiastro/images/graxpert.png +0 -0
  64. setiastro/images/green.png +0 -0
  65. setiastro/images/gridicon.png +0 -0
  66. setiastro/images/halo.png +0 -0
  67. setiastro/images/hdr.png +0 -0
  68. setiastro/images/histogram.png +0 -0
  69. setiastro/images/hubble.png +0 -0
  70. setiastro/images/imagecombine.png +0 -0
  71. setiastro/images/invert.png +0 -0
  72. setiastro/images/isophote.png +0 -0
  73. setiastro/images/isophote_demo_figure.png +0 -0
  74. setiastro/images/isophote_demo_image.png +0 -0
  75. setiastro/images/isophote_demo_model.png +0 -0
  76. setiastro/images/isophote_demo_residual.png +0 -0
  77. setiastro/images/jwstpupil.png +0 -0
  78. setiastro/images/linearfit.png +0 -0
  79. setiastro/images/livestacking.png +0 -0
  80. setiastro/images/mask.png +0 -0
  81. setiastro/images/maskapply.png +0 -0
  82. setiastro/images/maskcreate.png +0 -0
  83. setiastro/images/maskremove.png +0 -0
  84. setiastro/images/morpho.png +0 -0
  85. setiastro/images/mosaic.png +0 -0
  86. setiastro/images/multiscale_decomp.png +0 -0
  87. setiastro/images/nbtorgb.png +0 -0
  88. setiastro/images/neutral.png +0 -0
  89. setiastro/images/nuke.png +0 -0
  90. setiastro/images/openfile.png +0 -0
  91. setiastro/images/pedestal.png +0 -0
  92. setiastro/images/pen.png +0 -0
  93. setiastro/images/pixelmath.png +0 -0
  94. setiastro/images/platesolve.png +0 -0
  95. setiastro/images/ppp.png +0 -0
  96. setiastro/images/pro.png +0 -0
  97. setiastro/images/project.png +0 -0
  98. setiastro/images/psf.png +0 -0
  99. setiastro/images/redo.png +0 -0
  100. setiastro/images/redoicon.png +0 -0
  101. setiastro/images/rescale.png +0 -0
  102. setiastro/images/rgbalign.png +0 -0
  103. setiastro/images/rgbcombo.png +0 -0
  104. setiastro/images/rgbextract.png +0 -0
  105. setiastro/images/rotate180.png +0 -0
  106. setiastro/images/rotatearbitrary.png +0 -0
  107. setiastro/images/rotateclockwise.png +0 -0
  108. setiastro/images/rotatecounterclockwise.png +0 -0
  109. setiastro/images/satellite.png +0 -0
  110. setiastro/images/script.png +0 -0
  111. setiastro/images/selectivecolor.png +0 -0
  112. setiastro/images/simbad.png +0 -0
  113. setiastro/images/slot0.png +0 -0
  114. setiastro/images/slot1.png +0 -0
  115. setiastro/images/slot2.png +0 -0
  116. setiastro/images/slot3.png +0 -0
  117. setiastro/images/slot4.png +0 -0
  118. setiastro/images/slot5.png +0 -0
  119. setiastro/images/slot6.png +0 -0
  120. setiastro/images/slot7.png +0 -0
  121. setiastro/images/slot8.png +0 -0
  122. setiastro/images/slot9.png +0 -0
  123. setiastro/images/spcc.png +0 -0
  124. setiastro/images/spin_precession_vs_lunar_distance.png +0 -0
  125. setiastro/images/spinner.gif +0 -0
  126. setiastro/images/stacking.png +0 -0
  127. setiastro/images/staradd.png +0 -0
  128. setiastro/images/staralign.png +0 -0
  129. setiastro/images/starnet.png +0 -0
  130. setiastro/images/starregistration.png +0 -0
  131. setiastro/images/starspike.png +0 -0
  132. setiastro/images/starstretch.png +0 -0
  133. setiastro/images/statstretch.png +0 -0
  134. setiastro/images/supernova.png +0 -0
  135. setiastro/images/uhs.png +0 -0
  136. setiastro/images/undoicon.png +0 -0
  137. setiastro/images/upscale.png +0 -0
  138. setiastro/images/viewbundle.png +0 -0
  139. setiastro/images/whitebalance.png +0 -0
  140. setiastro/images/wimi_icon_256x256.png +0 -0
  141. setiastro/images/wimilogo.png +0 -0
  142. setiastro/images/wims.png +0 -0
  143. setiastro/images/wrench_icon.png +0 -0
  144. setiastro/images/xisfliberator.png +0 -0
  145. setiastro/qml/ResourceMonitor.qml +126 -0
  146. setiastro/saspro/__init__.py +20 -0
  147. setiastro/saspro/__main__.py +958 -0
  148. setiastro/saspro/_generated/__init__.py +7 -0
  149. setiastro/saspro/_generated/build_info.py +3 -0
  150. setiastro/saspro/abe.py +1346 -0
  151. setiastro/saspro/abe_preset.py +196 -0
  152. setiastro/saspro/aberration_ai.py +698 -0
  153. setiastro/saspro/aberration_ai_preset.py +224 -0
  154. setiastro/saspro/accel_installer.py +218 -0
  155. setiastro/saspro/accel_workers.py +30 -0
  156. setiastro/saspro/add_stars.py +624 -0
  157. setiastro/saspro/astrobin_exporter.py +1010 -0
  158. setiastro/saspro/astrospike.py +153 -0
  159. setiastro/saspro/astrospike_python.py +1841 -0
  160. setiastro/saspro/autostretch.py +198 -0
  161. setiastro/saspro/backgroundneutral.py +611 -0
  162. setiastro/saspro/batch_convert.py +328 -0
  163. setiastro/saspro/batch_renamer.py +522 -0
  164. setiastro/saspro/blemish_blaster.py +491 -0
  165. setiastro/saspro/blink_comparator_pro.py +3149 -0
  166. setiastro/saspro/bundles.py +61 -0
  167. setiastro/saspro/bundles_dock.py +114 -0
  168. setiastro/saspro/cheat_sheet.py +213 -0
  169. setiastro/saspro/clahe.py +368 -0
  170. setiastro/saspro/comet_stacking.py +1442 -0
  171. setiastro/saspro/common_tr.py +107 -0
  172. setiastro/saspro/config.py +38 -0
  173. setiastro/saspro/config_bootstrap.py +40 -0
  174. setiastro/saspro/config_manager.py +316 -0
  175. setiastro/saspro/continuum_subtract.py +1617 -0
  176. setiastro/saspro/convo.py +1400 -0
  177. setiastro/saspro/convo_preset.py +414 -0
  178. setiastro/saspro/copyastro.py +190 -0
  179. setiastro/saspro/cosmicclarity.py +1589 -0
  180. setiastro/saspro/cosmicclarity_preset.py +407 -0
  181. setiastro/saspro/crop_dialog_pro.py +983 -0
  182. setiastro/saspro/crop_preset.py +189 -0
  183. setiastro/saspro/curve_editor_pro.py +2562 -0
  184. setiastro/saspro/curves_preset.py +375 -0
  185. setiastro/saspro/debayer.py +673 -0
  186. setiastro/saspro/debug_utils.py +29 -0
  187. setiastro/saspro/dnd_mime.py +35 -0
  188. setiastro/saspro/doc_manager.py +2664 -0
  189. setiastro/saspro/exoplanet_detector.py +2166 -0
  190. setiastro/saspro/file_utils.py +284 -0
  191. setiastro/saspro/fitsmodifier.py +748 -0
  192. setiastro/saspro/fix_bom.py +32 -0
  193. setiastro/saspro/free_torch_memory.py +48 -0
  194. setiastro/saspro/frequency_separation.py +1349 -0
  195. setiastro/saspro/function_bundle.py +1596 -0
  196. setiastro/saspro/generate_translations.py +3092 -0
  197. setiastro/saspro/ghs_dialog_pro.py +663 -0
  198. setiastro/saspro/ghs_preset.py +284 -0
  199. setiastro/saspro/graxpert.py +637 -0
  200. setiastro/saspro/graxpert_preset.py +287 -0
  201. setiastro/saspro/gui/__init__.py +0 -0
  202. setiastro/saspro/gui/main_window.py +8792 -0
  203. setiastro/saspro/gui/mixins/__init__.py +33 -0
  204. setiastro/saspro/gui/mixins/dock_mixin.py +375 -0
  205. setiastro/saspro/gui/mixins/file_mixin.py +450 -0
  206. setiastro/saspro/gui/mixins/geometry_mixin.py +503 -0
  207. setiastro/saspro/gui/mixins/header_mixin.py +441 -0
  208. setiastro/saspro/gui/mixins/mask_mixin.py +421 -0
  209. setiastro/saspro/gui/mixins/menu_mixin.py +390 -0
  210. setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
  211. setiastro/saspro/gui/mixins/toolbar_mixin.py +1619 -0
  212. setiastro/saspro/gui/mixins/update_mixin.py +323 -0
  213. setiastro/saspro/gui/mixins/view_mixin.py +435 -0
  214. setiastro/saspro/gui/statistics_dialog.py +47 -0
  215. setiastro/saspro/halobgon.py +488 -0
  216. setiastro/saspro/header_viewer.py +448 -0
  217. setiastro/saspro/headless_utils.py +88 -0
  218. setiastro/saspro/histogram.py +756 -0
  219. setiastro/saspro/history_explorer.py +941 -0
  220. setiastro/saspro/i18n.py +168 -0
  221. setiastro/saspro/image_combine.py +417 -0
  222. setiastro/saspro/image_peeker_pro.py +1604 -0
  223. setiastro/saspro/imageops/__init__.py +37 -0
  224. setiastro/saspro/imageops/mdi_snap.py +292 -0
  225. setiastro/saspro/imageops/scnr.py +36 -0
  226. setiastro/saspro/imageops/starbasedwhitebalance.py +210 -0
  227. setiastro/saspro/imageops/stretch.py +236 -0
  228. setiastro/saspro/isophote.py +1182 -0
  229. setiastro/saspro/layers.py +208 -0
  230. setiastro/saspro/layers_dock.py +714 -0
  231. setiastro/saspro/lazy_imports.py +193 -0
  232. setiastro/saspro/legacy/__init__.py +2 -0
  233. setiastro/saspro/legacy/image_manager.py +2360 -0
  234. setiastro/saspro/legacy/numba_utils.py +3676 -0
  235. setiastro/saspro/legacy/xisf.py +1213 -0
  236. setiastro/saspro/linear_fit.py +537 -0
  237. setiastro/saspro/live_stacking.py +1854 -0
  238. setiastro/saspro/log_bus.py +5 -0
  239. setiastro/saspro/logging_config.py +460 -0
  240. setiastro/saspro/luminancerecombine.py +510 -0
  241. setiastro/saspro/main_helpers.py +201 -0
  242. setiastro/saspro/mask_creation.py +1086 -0
  243. setiastro/saspro/masks_core.py +56 -0
  244. setiastro/saspro/mdi_widgets.py +353 -0
  245. setiastro/saspro/memory_utils.py +666 -0
  246. setiastro/saspro/metadata_patcher.py +75 -0
  247. setiastro/saspro/mfdeconv.py +3909 -0
  248. setiastro/saspro/mfdeconv_earlystop.py +71 -0
  249. setiastro/saspro/mfdeconvcudnn.py +3312 -0
  250. setiastro/saspro/mfdeconvsport.py +2459 -0
  251. setiastro/saspro/minorbodycatalog.py +567 -0
  252. setiastro/saspro/morphology.py +407 -0
  253. setiastro/saspro/multiscale_decomp.py +1747 -0
  254. setiastro/saspro/nbtorgb_stars.py +541 -0
  255. setiastro/saspro/numba_utils.py +3145 -0
  256. setiastro/saspro/numba_warmup.py +141 -0
  257. setiastro/saspro/ops/__init__.py +9 -0
  258. setiastro/saspro/ops/command_help_dialog.py +623 -0
  259. setiastro/saspro/ops/command_runner.py +217 -0
  260. setiastro/saspro/ops/commands.py +1594 -0
  261. setiastro/saspro/ops/script_editor.py +1105 -0
  262. setiastro/saspro/ops/scripts.py +1476 -0
  263. setiastro/saspro/ops/settings.py +637 -0
  264. setiastro/saspro/parallel_utils.py +554 -0
  265. setiastro/saspro/pedestal.py +121 -0
  266. setiastro/saspro/perfect_palette_picker.py +1105 -0
  267. setiastro/saspro/pipeline.py +110 -0
  268. setiastro/saspro/pixelmath.py +1604 -0
  269. setiastro/saspro/plate_solver.py +2445 -0
  270. setiastro/saspro/project_io.py +797 -0
  271. setiastro/saspro/psf_utils.py +136 -0
  272. setiastro/saspro/psf_viewer.py +549 -0
  273. setiastro/saspro/pyi_rthook_astroquery.py +95 -0
  274. setiastro/saspro/remove_green.py +331 -0
  275. setiastro/saspro/remove_stars.py +1599 -0
  276. setiastro/saspro/remove_stars_preset.py +446 -0
  277. setiastro/saspro/resources.py +503 -0
  278. setiastro/saspro/rgb_combination.py +208 -0
  279. setiastro/saspro/rgb_extract.py +19 -0
  280. setiastro/saspro/rgbalign.py +723 -0
  281. setiastro/saspro/runtime_imports.py +7 -0
  282. setiastro/saspro/runtime_torch.py +754 -0
  283. setiastro/saspro/save_options.py +73 -0
  284. setiastro/saspro/selective_color.py +1611 -0
  285. setiastro/saspro/sfcc.py +1472 -0
  286. setiastro/saspro/shortcuts.py +3116 -0
  287. setiastro/saspro/signature_insert.py +1102 -0
  288. setiastro/saspro/stacking_suite.py +19066 -0
  289. setiastro/saspro/star_alignment.py +7380 -0
  290. setiastro/saspro/star_alignment_preset.py +329 -0
  291. setiastro/saspro/star_metrics.py +49 -0
  292. setiastro/saspro/star_spikes.py +765 -0
  293. setiastro/saspro/star_stretch.py +507 -0
  294. setiastro/saspro/stat_stretch.py +538 -0
  295. setiastro/saspro/status_log_dock.py +78 -0
  296. setiastro/saspro/subwindow.py +3407 -0
  297. setiastro/saspro/supernovaasteroidhunter.py +1719 -0
  298. setiastro/saspro/swap_manager.py +134 -0
  299. setiastro/saspro/torch_backend.py +89 -0
  300. setiastro/saspro/torch_rejection.py +434 -0
  301. setiastro/saspro/translations/all_source_strings.json +4726 -0
  302. setiastro/saspro/translations/ar_translations.py +4096 -0
  303. setiastro/saspro/translations/de_translations.py +3728 -0
  304. setiastro/saspro/translations/es_translations.py +4169 -0
  305. setiastro/saspro/translations/fr_translations.py +4090 -0
  306. setiastro/saspro/translations/hi_translations.py +3803 -0
  307. setiastro/saspro/translations/integrate_translations.py +271 -0
  308. setiastro/saspro/translations/it_translations.py +4728 -0
  309. setiastro/saspro/translations/ja_translations.py +3834 -0
  310. setiastro/saspro/translations/pt_translations.py +3847 -0
  311. setiastro/saspro/translations/ru_translations.py +3082 -0
  312. setiastro/saspro/translations/saspro_ar.qm +0 -0
  313. setiastro/saspro/translations/saspro_ar.ts +16019 -0
  314. setiastro/saspro/translations/saspro_de.qm +0 -0
  315. setiastro/saspro/translations/saspro_de.ts +14548 -0
  316. setiastro/saspro/translations/saspro_es.qm +0 -0
  317. setiastro/saspro/translations/saspro_es.ts +16202 -0
  318. setiastro/saspro/translations/saspro_fr.qm +0 -0
  319. setiastro/saspro/translations/saspro_fr.ts +15870 -0
  320. setiastro/saspro/translations/saspro_hi.qm +0 -0
  321. setiastro/saspro/translations/saspro_hi.ts +14855 -0
  322. setiastro/saspro/translations/saspro_it.qm +0 -0
  323. setiastro/saspro/translations/saspro_it.ts +19046 -0
  324. setiastro/saspro/translations/saspro_ja.qm +0 -0
  325. setiastro/saspro/translations/saspro_ja.ts +14980 -0
  326. setiastro/saspro/translations/saspro_pt.qm +0 -0
  327. setiastro/saspro/translations/saspro_pt.ts +15024 -0
  328. setiastro/saspro/translations/saspro_ru.qm +0 -0
  329. setiastro/saspro/translations/saspro_ru.ts +11835 -0
  330. setiastro/saspro/translations/saspro_sw.qm +0 -0
  331. setiastro/saspro/translations/saspro_sw.ts +15237 -0
  332. setiastro/saspro/translations/saspro_uk.qm +0 -0
  333. setiastro/saspro/translations/saspro_uk.ts +15248 -0
  334. setiastro/saspro/translations/saspro_zh.qm +0 -0
  335. setiastro/saspro/translations/saspro_zh.ts +15289 -0
  336. setiastro/saspro/translations/sw_translations.py +3897 -0
  337. setiastro/saspro/translations/uk_translations.py +3929 -0
  338. setiastro/saspro/translations/zh_translations.py +3910 -0
  339. setiastro/saspro/versioning.py +77 -0
  340. setiastro/saspro/view_bundle.py +1558 -0
  341. setiastro/saspro/wavescale_hdr.py +645 -0
  342. setiastro/saspro/wavescale_hdr_preset.py +101 -0
  343. setiastro/saspro/wavescalede.py +680 -0
  344. setiastro/saspro/wavescalede_preset.py +230 -0
  345. setiastro/saspro/wcs_update.py +374 -0
  346. setiastro/saspro/whitebalance.py +513 -0
  347. setiastro/saspro/widgets/__init__.py +48 -0
  348. setiastro/saspro/widgets/common_utilities.py +306 -0
  349. setiastro/saspro/widgets/graphics_views.py +122 -0
  350. setiastro/saspro/widgets/image_utils.py +518 -0
  351. setiastro/saspro/widgets/minigame/game.js +991 -0
  352. setiastro/saspro/widgets/minigame/index.html +53 -0
  353. setiastro/saspro/widgets/minigame/style.css +241 -0
  354. setiastro/saspro/widgets/preview_dialogs.py +280 -0
  355. setiastro/saspro/widgets/resource_monitor.py +263 -0
  356. setiastro/saspro/widgets/spinboxes.py +290 -0
  357. setiastro/saspro/widgets/themed_buttons.py +13 -0
  358. setiastro/saspro/widgets/wavelet_utils.py +331 -0
  359. setiastro/saspro/wimi.py +7996 -0
  360. setiastro/saspro/wims.py +578 -0
  361. setiastro/saspro/window_shelf.py +185 -0
  362. setiastro/saspro/xisf.py +1213 -0
  363. setiastrosuitepro-1.6.5.post3.dist-info/METADATA +278 -0
  364. setiastrosuitepro-1.6.5.post3.dist-info/RECORD +368 -0
  365. setiastrosuitepro-1.6.5.post3.dist-info/WHEEL +4 -0
  366. setiastrosuitepro-1.6.5.post3.dist-info/entry_points.txt +6 -0
  367. setiastrosuitepro-1.6.5.post3.dist-info/licenses/LICENSE +674 -0
  368. setiastrosuitepro-1.6.5.post3.dist-info/licenses/license.txt +2580 -0
@@ -0,0 +1,441 @@
1
+ # pro/gui/mixins/header_mixin.py
2
+ """
3
+ Header management mixin for AstroSuiteProMainWindow.
4
+
5
+ This mixin contains all functionality for viewing and managing FITS headers,
6
+ metadata, and WCS information.
7
+ """
8
+ from __future__ import annotations
9
+ import re
10
+ from typing import TYPE_CHECKING
11
+
12
+ from PyQt6.QtCore import Qt
13
+
14
+ if TYPE_CHECKING:
15
+ pass
16
+
17
+
18
+ class HeaderMixin:
19
+ """
20
+ Mixin for header/metadata management.
21
+
22
+ Provides methods for viewing, extracting, and manipulating document headers
23
+ and WCS (World Coordinate System) information.
24
+ """
25
+
26
+ # Exact keys we always consider WCS
27
+ _WCS_KEY_SET = {
28
+ "WCSAXES", "CTYPE1", "CTYPE2", "CUNIT1", "CUNIT2",
29
+ "CRPIX1", "CRPIX2", "CRVAL1", "CRVAL2",
30
+ "CD1_1", "CD1_2", "CD2_1", "CD2_2",
31
+ "PC1_1", "PC1_2", "PC2_1", "PC2_2",
32
+ "CDELT1", "CDELT2",
33
+ "LONPOLE", "LATPOLE",
34
+ "RADESYS", "RADECSYS", "EQUINOX", "EPOCH",
35
+ "NAXIS1", "NAXIS2" # useful context for UIs/solvers
36
+ }
37
+
38
+ def _ensure_header_map(self, doc):
39
+ """Ensure doc has a header dictionary in metadata, return it."""
40
+ meta = getattr(doc, "metadata", None)
41
+ if meta is None:
42
+ return None
43
+ hdr = meta.get("original_header")
44
+ if not isinstance(hdr, dict):
45
+ hdr = {}
46
+ meta["original_header"] = hdr
47
+ return hdr
48
+
49
+ def _coerce_wcs_numbers(self, d: dict) -> dict:
50
+ """Convert common WCS/SIP values to int/float where appropriate."""
51
+ numeric = {
52
+ "CRPIX1", "CRPIX2", "CRVAL1", "CRVAL2", "CDELT1", "CDELT2",
53
+ "CD1_1", "CD1_2", "CD2_1", "CD2_2", "PC1_1", "PC1_2", "PC2_1", "PC2_2",
54
+ "CROTA1", "CROTA2", "EQUINOX", "WCSAXES", "A_ORDER", "B_ORDER", "AP_ORDER", "BP_ORDER",
55
+ "LONPOLE", "LATPOLE"
56
+ }
57
+ out = {}
58
+ for k, v in d.items():
59
+ K = str(k).upper()
60
+ try:
61
+ if K in numeric or re.match(r"^(A|B|AP|BP)_\d+_\d+$", K or ""):
62
+ if isinstance(v, str):
63
+ s = v.strip()
64
+ # int if clean integer, else float
65
+ out[K] = int(s) if re.fullmatch(r"[+-]?\d+", s) else float(s)
66
+ else:
67
+ out[K] = v
68
+ else:
69
+ out[K] = v
70
+ except Exception:
71
+ out[K] = v
72
+ return out
73
+
74
+ def _extract_wcs_dict(self, doc) -> dict:
75
+ """Collect a complete WCS/SIP dict from the doc's header/meta."""
76
+ if doc is None:
77
+ return {}
78
+ src = (getattr(doc, "metadata", {}) or {}).get("original_header")
79
+
80
+ wcs = {}
81
+ if src is None:
82
+ pass
83
+ else:
84
+ try:
85
+ for k, v in dict(src).items():
86
+ K = str(k).upper()
87
+ if (K.startswith(("CRPIX", "CRVAL", "CDELT", "CD", "PC", "CROTA", "CTYPE", "CUNIT",
88
+ "WCSAXES", "LONPOLE", "LATPOLE", "EQUINOX", "PV")) or
89
+ K in {"RADECSYS", "RADESYS", "NAXIS1", "NAXIS2"} or
90
+ K.startswith(("A_", "B_", "AP_", "BP_"))):
91
+ wcs[K] = v
92
+ except Exception:
93
+ pass
94
+
95
+ # Also accept any mirror you previously stored
96
+ meta = getattr(doc, "metadata", {}) or {}
97
+ imgmeta = meta.get("image_meta") or meta.get("WCS") or {}
98
+ if isinstance(imgmeta, dict):
99
+ sub = imgmeta.get("WCS", imgmeta)
100
+ if isinstance(sub, dict):
101
+ for k, v in sub.items():
102
+ K = str(k).upper()
103
+ if (K.startswith(("CRPIX", "CRVAL", "CDELT", "CD", "PC", "CROTA", "CTYPE", "CUNIT",
104
+ "WCSAXES", "LONPOLE", "LATPOLE", "EQUINOX", "PV")) or
105
+ K in {"RADECSYS", "RADESYS", "NAXIS1", "NAXIS2"} or
106
+ K.startswith(("A_", "B_", "AP_", "BP_"))):
107
+ wcs.setdefault(K, v)
108
+
109
+ # sensible defaults/parity
110
+ if any(k.startswith(("A_", "B_", "AP_", "BP_")) for k in wcs):
111
+ wcs.setdefault("CUNIT1", "deg")
112
+ wcs.setdefault("CUNIT2", "deg")
113
+ # TAN-SIP labels if SIP present
114
+ c1 = str(wcs.get("CTYPE1", "RA---TAN"))
115
+ c2 = str(wcs.get("CTYPE2", "DEC--TAN"))
116
+ if not c1.endswith("-SIP"):
117
+ wcs["CTYPE1"] = "RA---TAN-SIP"
118
+ if not c2.endswith("-SIP"):
119
+ wcs["CTYPE2"] = "DEC--TAN-SIP"
120
+
121
+ if "RADECSYS" in wcs and "RADESYS" not in wcs:
122
+ wcs["RADESYS"] = wcs["RADECSYS"]
123
+ if "WCSAXES" not in wcs and {"CTYPE1", "CTYPE2"} <= wcs.keys():
124
+ wcs["WCSAXES"] = 2
125
+
126
+ return self._coerce_wcs_numbers(wcs)
127
+
128
+ def _ensure_header_for_doc(self, doc):
129
+ """Return an astropy Header for doc.metadata['original_header'] (creating one if needed)."""
130
+ from astropy.io.fits import Header
131
+ import numpy as np
132
+
133
+ meta = getattr(doc, "metadata", None)
134
+ if not isinstance(meta, dict):
135
+ setattr(doc, "metadata", {})
136
+ meta = doc.metadata
137
+
138
+ hdr_like = meta.get("original_header")
139
+
140
+ # Already a Header?
141
+ if isinstance(hdr_like, Header):
142
+ hdr = hdr_like
143
+ elif isinstance(hdr_like, dict):
144
+ # coerce dict -> Header
145
+ hdr = Header()
146
+ int_keys = {"A_ORDER", "B_ORDER", "AP_ORDER", "BP_ORDER", "WCSAXES", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3"}
147
+ for k, v in dict(hdr_like).items():
148
+ K = str(k).upper()
149
+ try:
150
+ if K in int_keys:
151
+ hdr[K] = int(float(str(v).strip().split()[0]))
152
+ elif re.match(r"^(?:A|B|AP|BP)_\d+_\d+$", K) or \
153
+ re.match(r"^(?:CRPIX|CRVAL|CDELT|CD|PC|CROTA|LATPOLE|LONPOLE|EQUINOX)\d?_?\d*$", K):
154
+ hdr[K] = float(str(v).strip().split()[0])
155
+ else:
156
+ hdr[K] = v
157
+ except Exception:
158
+ pass
159
+ else:
160
+ hdr = Header()
161
+
162
+ # Ensure basic axis cards exist (needed for non-FITS sources)
163
+ try:
164
+ img = getattr(doc, "image", None)
165
+ if img is not None:
166
+ a = np.asarray(img)
167
+ H = int(a.shape[0]) if a.ndim >= 2 else 1
168
+ W = int(a.shape[1]) if a.ndim >= 2 else 1
169
+ C = int(a.shape[2]) if a.ndim == 3 else 1
170
+ # Only set when missing (don't clobber real FITS headers)
171
+ if "NAXIS" not in hdr:
172
+ hdr["NAXIS"] = 2 if a.ndim != 3 else 3
173
+ if "NAXIS1" not in hdr:
174
+ hdr["NAXIS1"] = W
175
+ if "NAXIS2" not in hdr:
176
+ hdr["NAXIS2"] = H
177
+ if a.ndim == 3 and "NAXIS3" not in hdr:
178
+ hdr["NAXIS3"] = C
179
+ except Exception:
180
+ pass
181
+
182
+ meta["original_header"] = hdr
183
+ return hdr
184
+
185
+ def _refresh_header_viewer(self, doc=None):
186
+ """Rebuild the header dock from the given (or active) doc -- never raises."""
187
+ try:
188
+ doc = doc or self._active_doc()
189
+ hv = getattr(self, "header_viewer", None)
190
+
191
+ # If your dock widget has a native API, try it but don't trust it.
192
+ if hv and hasattr(hv, "set_document"):
193
+ try:
194
+ hv.set_document(doc)
195
+ return
196
+ except Exception as e:
197
+ print("[header] set_document suppressed:", e)
198
+
199
+ # Fallback path: extract -> populate, all guarded.
200
+ rows = self._extract_header_pairs(doc)
201
+ if not rows:
202
+ self._clear_header_viewer(self.tr("No header") if doc else self.tr("No image"))
203
+ else:
204
+ self._populate_header_viewer(rows)
205
+ except Exception as e:
206
+ print("[header] refresh suppressed:", e)
207
+ self._clear_header_viewer("")
208
+
209
+ def _extract_header_pairs(self, doc):
210
+ """
211
+ Return list[(key, value, comment)].
212
+ Prefers a JSON-safe snapshot if present, otherwise best-effort parsing.
213
+ Never raises.
214
+ """
215
+ try:
216
+ if not doc:
217
+ return []
218
+
219
+ meta = getattr(doc, "metadata", {}) or {}
220
+
221
+ # 1) Prefer a snapshot if any writer/loader provided it.
222
+ snap = meta.get("__header_snapshot__")
223
+ if isinstance(snap, dict):
224
+ fmt = snap.get("format")
225
+ if fmt == "fits-cards":
226
+ cards = snap.get("cards") or []
227
+ out = []
228
+ for it in cards:
229
+ try:
230
+ k, v, c = it
231
+ except Exception:
232
+ # tolerate weird shapes
233
+ k, v, c = (str(it[0]) if it else "",
234
+ "" if len(it) < 2 else str(it[1]),
235
+ "" if len(it) < 3 else str(it[2]))
236
+ out.append((str(k), str(v), str(c)))
237
+ return out
238
+ if fmt == "dict":
239
+ items = snap.get("items") or {}
240
+ out = []
241
+ for k, v in items.items():
242
+ if isinstance(v, dict):
243
+ out.append((str(k), str(v.get("value", "")), str(v.get("comment", ""))))
244
+ else:
245
+ out.append((str(k), str(v), ""))
246
+ return out
247
+ if fmt == "repr":
248
+ return [(self.tr("Header"), str(snap.get("text", "")), "")]
249
+
250
+ # 2) Live header object(s) (can be astropy, dict, or random).
251
+ hdr = (meta.get("original_header")
252
+ or meta.get("fits_header")
253
+ or meta.get("header"))
254
+
255
+ if hdr is None:
256
+ return []
257
+
258
+ # astropy.io.fits.Header (optional; no hard dependency)
259
+ try:
260
+ from astropy.io.fits import Header
261
+ except Exception:
262
+ Header = None
263
+
264
+ if Header is not None:
265
+ try:
266
+ if isinstance(hdr, Header):
267
+ out = []
268
+ for k in hdr.keys():
269
+ try:
270
+ val = hdr[k]
271
+ except Exception:
272
+ val = ""
273
+ try:
274
+ cmt = hdr.comments[k]
275
+ except Exception:
276
+ cmt = ""
277
+ out.append((str(k), str(val), str(cmt)))
278
+ return out
279
+ except Exception as e:
280
+ print("[header] astropy parse suppressed:", e)
281
+
282
+ # dict-ish header (e.g., XISF-like)
283
+ if isinstance(hdr, dict):
284
+ out = []
285
+ for k, v in hdr.items():
286
+ if isinstance(v, dict):
287
+ out.append((str(k), str(v.get("value", "")), str(v.get("comment", ""))))
288
+ else:
289
+ # avoid huge array dumps
290
+ try:
291
+ import numpy as _np
292
+ if isinstance(v, _np.ndarray):
293
+ v = f"ndarray{tuple(v.shape)}"
294
+ except Exception:
295
+ pass
296
+ out.append((str(k), str(v), ""))
297
+ return out
298
+
299
+ # Fallback: string repr
300
+ return [(self.tr("Header"), str(hdr), "")]
301
+ except Exception as e:
302
+ print("[header] extract suppressed:", e)
303
+ return []
304
+
305
+ def _populate_header_viewer(self, rows):
306
+ """Render rows into whatever widget you expose; never raises."""
307
+ try:
308
+ w = self._header_widget()
309
+ except Exception as e:
310
+ print("[header] _header_widget suppressed:", e)
311
+ return
312
+ if w is None:
313
+ return
314
+
315
+ # Table widget path
316
+ try:
317
+ from PyQt6.QtWidgets import QTableWidget, QTableWidgetItem
318
+ if isinstance(w, QTableWidget):
319
+ try:
320
+ w.setRowCount(0)
321
+ w.setColumnCount(3)
322
+ w.setHorizontalHeaderLabels([self.tr("Key"), self.tr("Value"), self.tr("Comment")])
323
+ for r, (k, v, c) in enumerate(rows):
324
+ w.insertRow(r)
325
+ w.setItem(r, 0, QTableWidgetItem(k))
326
+ w.setItem(r, 1, QTableWidgetItem(v))
327
+ w.setItem(r, 2, QTableWidgetItem(c))
328
+ return
329
+ except Exception as e:
330
+ print("[header] table populate suppressed:", e)
331
+ except Exception:
332
+ pass
333
+
334
+ # List widget path
335
+ try:
336
+ from PyQt6.QtWidgets import QListWidget
337
+ if isinstance(w, QListWidget):
338
+ try:
339
+ w.clear()
340
+ for k, v, c in rows:
341
+ w.addItem(f"{k} = {v}" + (f" / {c}" if c else ""))
342
+ return
343
+ except Exception as e:
344
+ print("[header] list populate suppressed:", e)
345
+ except Exception:
346
+ pass
347
+
348
+ # Plain text-ish
349
+ try:
350
+ if hasattr(w, "setPlainText"):
351
+ w.setPlainText("\n".join(
352
+ f"{k} = {v}" + (f" / {c}" if c else "") for (k, v, c) in rows
353
+ ))
354
+ return
355
+ if hasattr(w, "setText"):
356
+ w.setText("\n".join(
357
+ f"{k} = {v}" + (f" / {c}" if c else "") for (k, v, c) in rows
358
+ ))
359
+ return
360
+ except Exception as e:
361
+ print("[header] text populate suppressed:", e)
362
+
363
+ def _clear_header_viewer(self, message: str = ""):
364
+ """Clear header viewer content quietly--no dialogs."""
365
+ w = self._header_widget()
366
+ if w is None:
367
+ return
368
+ try:
369
+ from PyQt6.QtWidgets import QTableWidget
370
+ if isinstance(w, QTableWidget):
371
+ w.setRowCount(0)
372
+ w.setColumnCount(3)
373
+ w.setHorizontalHeaderLabels([self.tr("Key"), self.tr("Value"), self.tr("Comment")])
374
+ return
375
+ except Exception:
376
+ pass
377
+ try:
378
+ from PyQt6.QtWidgets import QListWidget
379
+ if isinstance(w, QListWidget):
380
+ w.clear()
381
+ if message:
382
+ w.addItem(message)
383
+ return
384
+ except Exception:
385
+ pass
386
+ # QTextEdit-like
387
+ if hasattr(w, "setPlainText"):
388
+ try:
389
+ w.setPlainText(message or "")
390
+ except Exception:
391
+ pass
392
+
393
+ def _header_widget(self):
394
+ """
395
+ Find the concrete widget used to display header text/table.
396
+ Never raises; returns None if nothing sensible is found.
397
+ """
398
+ hv = getattr(self, "header_viewer", None) or getattr(self, "metadata_dock", None)
399
+ if hv is None:
400
+ return None
401
+
402
+ # If it's a dock widget (QDockWidget-like), pull its child widget
403
+ try:
404
+ if hasattr(hv, "widget") and callable(hv.widget):
405
+ inner = hv.widget()
406
+ if inner is not None:
407
+ return inner
408
+ except Exception:
409
+ pass
410
+
411
+ # It might already be the actual widget
412
+ return hv
413
+
414
+ def _on_doc_added_for_header_sync(self, doc):
415
+ """Update header when the *active* doc changes."""
416
+ try:
417
+ doc.changed.connect(self._maybe_refresh_header_on_doc_change)
418
+ except Exception:
419
+ pass
420
+
421
+ def _on_doc_removed_for_header_sync(self, doc):
422
+ """If the removed doc was the active one, clear header."""
423
+ if doc is self._active_doc():
424
+ self._clear_header_viewer(self.tr("No image"))
425
+ hv = getattr(self, "header_viewer", None)
426
+ if hv and hasattr(hv, "set_document"):
427
+ try:
428
+ hv.set_document(None)
429
+ except Exception:
430
+ pass
431
+
432
+ # If there are no more subwindows, force a global clear too
433
+ if not self.mdi.subWindowList():
434
+ self.currentDocumentChanged.emit(None)
435
+ self._hdr_refresh_timer.start(0)
436
+
437
+ def _maybe_refresh_header_on_doc_change(self):
438
+ """Refresh header if sender is the active doc."""
439
+ sender = self.sender()
440
+ if sender is self._active_doc():
441
+ self._hdr_refresh_timer.start(0)