setiastrosuitepro 1.6.2.post1__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 (367) 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/rotateclockwise.png +0 -0
  107. setiastro/images/rotatecounterclockwise.png +0 -0
  108. setiastro/images/satellite.png +0 -0
  109. setiastro/images/script.png +0 -0
  110. setiastro/images/selectivecolor.png +0 -0
  111. setiastro/images/simbad.png +0 -0
  112. setiastro/images/slot0.png +0 -0
  113. setiastro/images/slot1.png +0 -0
  114. setiastro/images/slot2.png +0 -0
  115. setiastro/images/slot3.png +0 -0
  116. setiastro/images/slot4.png +0 -0
  117. setiastro/images/slot5.png +0 -0
  118. setiastro/images/slot6.png +0 -0
  119. setiastro/images/slot7.png +0 -0
  120. setiastro/images/slot8.png +0 -0
  121. setiastro/images/slot9.png +0 -0
  122. setiastro/images/spcc.png +0 -0
  123. setiastro/images/spin_precession_vs_lunar_distance.png +0 -0
  124. setiastro/images/spinner.gif +0 -0
  125. setiastro/images/stacking.png +0 -0
  126. setiastro/images/staradd.png +0 -0
  127. setiastro/images/staralign.png +0 -0
  128. setiastro/images/starnet.png +0 -0
  129. setiastro/images/starregistration.png +0 -0
  130. setiastro/images/starspike.png +0 -0
  131. setiastro/images/starstretch.png +0 -0
  132. setiastro/images/statstretch.png +0 -0
  133. setiastro/images/supernova.png +0 -0
  134. setiastro/images/uhs.png +0 -0
  135. setiastro/images/undoicon.png +0 -0
  136. setiastro/images/upscale.png +0 -0
  137. setiastro/images/viewbundle.png +0 -0
  138. setiastro/images/whitebalance.png +0 -0
  139. setiastro/images/wimi_icon_256x256.png +0 -0
  140. setiastro/images/wimilogo.png +0 -0
  141. setiastro/images/wims.png +0 -0
  142. setiastro/images/wrench_icon.png +0 -0
  143. setiastro/images/xisfliberator.png +0 -0
  144. setiastro/qml/ResourceMonitor.qml +126 -0
  145. setiastro/saspro/__init__.py +20 -0
  146. setiastro/saspro/__main__.py +945 -0
  147. setiastro/saspro/_generated/__init__.py +7 -0
  148. setiastro/saspro/_generated/build_info.py +3 -0
  149. setiastro/saspro/abe.py +1346 -0
  150. setiastro/saspro/abe_preset.py +196 -0
  151. setiastro/saspro/aberration_ai.py +694 -0
  152. setiastro/saspro/aberration_ai_preset.py +224 -0
  153. setiastro/saspro/accel_installer.py +218 -0
  154. setiastro/saspro/accel_workers.py +30 -0
  155. setiastro/saspro/add_stars.py +624 -0
  156. setiastro/saspro/astrobin_exporter.py +1010 -0
  157. setiastro/saspro/astrospike.py +153 -0
  158. setiastro/saspro/astrospike_python.py +1841 -0
  159. setiastro/saspro/autostretch.py +198 -0
  160. setiastro/saspro/backgroundneutral.py +602 -0
  161. setiastro/saspro/batch_convert.py +328 -0
  162. setiastro/saspro/batch_renamer.py +522 -0
  163. setiastro/saspro/blemish_blaster.py +491 -0
  164. setiastro/saspro/blink_comparator_pro.py +2926 -0
  165. setiastro/saspro/bundles.py +61 -0
  166. setiastro/saspro/bundles_dock.py +114 -0
  167. setiastro/saspro/cheat_sheet.py +213 -0
  168. setiastro/saspro/clahe.py +368 -0
  169. setiastro/saspro/comet_stacking.py +1442 -0
  170. setiastro/saspro/common_tr.py +107 -0
  171. setiastro/saspro/config.py +38 -0
  172. setiastro/saspro/config_bootstrap.py +40 -0
  173. setiastro/saspro/config_manager.py +316 -0
  174. setiastro/saspro/continuum_subtract.py +1617 -0
  175. setiastro/saspro/convo.py +1400 -0
  176. setiastro/saspro/convo_preset.py +414 -0
  177. setiastro/saspro/copyastro.py +190 -0
  178. setiastro/saspro/cosmicclarity.py +1589 -0
  179. setiastro/saspro/cosmicclarity_preset.py +407 -0
  180. setiastro/saspro/crop_dialog_pro.py +973 -0
  181. setiastro/saspro/crop_preset.py +189 -0
  182. setiastro/saspro/curve_editor_pro.py +2562 -0
  183. setiastro/saspro/curves_preset.py +375 -0
  184. setiastro/saspro/debayer.py +673 -0
  185. setiastro/saspro/debug_utils.py +29 -0
  186. setiastro/saspro/dnd_mime.py +35 -0
  187. setiastro/saspro/doc_manager.py +2664 -0
  188. setiastro/saspro/exoplanet_detector.py +2166 -0
  189. setiastro/saspro/file_utils.py +284 -0
  190. setiastro/saspro/fitsmodifier.py +748 -0
  191. setiastro/saspro/fix_bom.py +32 -0
  192. setiastro/saspro/free_torch_memory.py +48 -0
  193. setiastro/saspro/frequency_separation.py +1349 -0
  194. setiastro/saspro/function_bundle.py +1596 -0
  195. setiastro/saspro/generate_translations.py +3092 -0
  196. setiastro/saspro/ghs_dialog_pro.py +663 -0
  197. setiastro/saspro/ghs_preset.py +284 -0
  198. setiastro/saspro/graxpert.py +637 -0
  199. setiastro/saspro/graxpert_preset.py +287 -0
  200. setiastro/saspro/gui/__init__.py +0 -0
  201. setiastro/saspro/gui/main_window.py +8810 -0
  202. setiastro/saspro/gui/mixins/__init__.py +33 -0
  203. setiastro/saspro/gui/mixins/dock_mixin.py +362 -0
  204. setiastro/saspro/gui/mixins/file_mixin.py +450 -0
  205. setiastro/saspro/gui/mixins/geometry_mixin.py +403 -0
  206. setiastro/saspro/gui/mixins/header_mixin.py +441 -0
  207. setiastro/saspro/gui/mixins/mask_mixin.py +421 -0
  208. setiastro/saspro/gui/mixins/menu_mixin.py +389 -0
  209. setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
  210. setiastro/saspro/gui/mixins/toolbar_mixin.py +1457 -0
  211. setiastro/saspro/gui/mixins/update_mixin.py +309 -0
  212. setiastro/saspro/gui/mixins/view_mixin.py +435 -0
  213. setiastro/saspro/gui/statistics_dialog.py +47 -0
  214. setiastro/saspro/halobgon.py +488 -0
  215. setiastro/saspro/header_viewer.py +448 -0
  216. setiastro/saspro/headless_utils.py +88 -0
  217. setiastro/saspro/histogram.py +756 -0
  218. setiastro/saspro/history_explorer.py +941 -0
  219. setiastro/saspro/i18n.py +168 -0
  220. setiastro/saspro/image_combine.py +417 -0
  221. setiastro/saspro/image_peeker_pro.py +1604 -0
  222. setiastro/saspro/imageops/__init__.py +37 -0
  223. setiastro/saspro/imageops/mdi_snap.py +292 -0
  224. setiastro/saspro/imageops/scnr.py +36 -0
  225. setiastro/saspro/imageops/starbasedwhitebalance.py +210 -0
  226. setiastro/saspro/imageops/stretch.py +236 -0
  227. setiastro/saspro/isophote.py +1182 -0
  228. setiastro/saspro/layers.py +208 -0
  229. setiastro/saspro/layers_dock.py +714 -0
  230. setiastro/saspro/lazy_imports.py +193 -0
  231. setiastro/saspro/legacy/__init__.py +2 -0
  232. setiastro/saspro/legacy/image_manager.py +2226 -0
  233. setiastro/saspro/legacy/numba_utils.py +3676 -0
  234. setiastro/saspro/legacy/xisf.py +1071 -0
  235. setiastro/saspro/linear_fit.py +537 -0
  236. setiastro/saspro/live_stacking.py +1841 -0
  237. setiastro/saspro/log_bus.py +5 -0
  238. setiastro/saspro/logging_config.py +460 -0
  239. setiastro/saspro/luminancerecombine.py +309 -0
  240. setiastro/saspro/main_helpers.py +201 -0
  241. setiastro/saspro/mask_creation.py +931 -0
  242. setiastro/saspro/masks_core.py +56 -0
  243. setiastro/saspro/mdi_widgets.py +353 -0
  244. setiastro/saspro/memory_utils.py +666 -0
  245. setiastro/saspro/metadata_patcher.py +75 -0
  246. setiastro/saspro/mfdeconv.py +3831 -0
  247. setiastro/saspro/mfdeconv_earlystop.py +71 -0
  248. setiastro/saspro/mfdeconvcudnn.py +3263 -0
  249. setiastro/saspro/mfdeconvsport.py +2382 -0
  250. setiastro/saspro/minorbodycatalog.py +567 -0
  251. setiastro/saspro/morphology.py +407 -0
  252. setiastro/saspro/multiscale_decomp.py +1293 -0
  253. setiastro/saspro/nbtorgb_stars.py +541 -0
  254. setiastro/saspro/numba_utils.py +3145 -0
  255. setiastro/saspro/numba_warmup.py +141 -0
  256. setiastro/saspro/ops/__init__.py +9 -0
  257. setiastro/saspro/ops/command_help_dialog.py +623 -0
  258. setiastro/saspro/ops/command_runner.py +217 -0
  259. setiastro/saspro/ops/commands.py +1594 -0
  260. setiastro/saspro/ops/script_editor.py +1102 -0
  261. setiastro/saspro/ops/scripts.py +1473 -0
  262. setiastro/saspro/ops/settings.py +637 -0
  263. setiastro/saspro/parallel_utils.py +554 -0
  264. setiastro/saspro/pedestal.py +121 -0
  265. setiastro/saspro/perfect_palette_picker.py +1071 -0
  266. setiastro/saspro/pipeline.py +110 -0
  267. setiastro/saspro/pixelmath.py +1604 -0
  268. setiastro/saspro/plate_solver.py +2445 -0
  269. setiastro/saspro/project_io.py +797 -0
  270. setiastro/saspro/psf_utils.py +136 -0
  271. setiastro/saspro/psf_viewer.py +549 -0
  272. setiastro/saspro/pyi_rthook_astroquery.py +95 -0
  273. setiastro/saspro/remove_green.py +331 -0
  274. setiastro/saspro/remove_stars.py +1599 -0
  275. setiastro/saspro/remove_stars_preset.py +404 -0
  276. setiastro/saspro/resources.py +501 -0
  277. setiastro/saspro/rgb_combination.py +208 -0
  278. setiastro/saspro/rgb_extract.py +19 -0
  279. setiastro/saspro/rgbalign.py +723 -0
  280. setiastro/saspro/runtime_imports.py +7 -0
  281. setiastro/saspro/runtime_torch.py +754 -0
  282. setiastro/saspro/save_options.py +73 -0
  283. setiastro/saspro/selective_color.py +1552 -0
  284. setiastro/saspro/sfcc.py +1472 -0
  285. setiastro/saspro/shortcuts.py +3043 -0
  286. setiastro/saspro/signature_insert.py +1102 -0
  287. setiastro/saspro/stacking_suite.py +18470 -0
  288. setiastro/saspro/star_alignment.py +7435 -0
  289. setiastro/saspro/star_alignment_preset.py +329 -0
  290. setiastro/saspro/star_metrics.py +49 -0
  291. setiastro/saspro/star_spikes.py +765 -0
  292. setiastro/saspro/star_stretch.py +507 -0
  293. setiastro/saspro/stat_stretch.py +538 -0
  294. setiastro/saspro/status_log_dock.py +78 -0
  295. setiastro/saspro/subwindow.py +3328 -0
  296. setiastro/saspro/supernovaasteroidhunter.py +1719 -0
  297. setiastro/saspro/swap_manager.py +99 -0
  298. setiastro/saspro/torch_backend.py +89 -0
  299. setiastro/saspro/torch_rejection.py +434 -0
  300. setiastro/saspro/translations/all_source_strings.json +3654 -0
  301. setiastro/saspro/translations/ar_translations.py +3865 -0
  302. setiastro/saspro/translations/de_translations.py +3749 -0
  303. setiastro/saspro/translations/es_translations.py +3939 -0
  304. setiastro/saspro/translations/fr_translations.py +3858 -0
  305. setiastro/saspro/translations/hi_translations.py +3571 -0
  306. setiastro/saspro/translations/integrate_translations.py +270 -0
  307. setiastro/saspro/translations/it_translations.py +3678 -0
  308. setiastro/saspro/translations/ja_translations.py +3601 -0
  309. setiastro/saspro/translations/pt_translations.py +3869 -0
  310. setiastro/saspro/translations/ru_translations.py +2848 -0
  311. setiastro/saspro/translations/saspro_ar.qm +0 -0
  312. setiastro/saspro/translations/saspro_ar.ts +255 -0
  313. setiastro/saspro/translations/saspro_de.qm +0 -0
  314. setiastro/saspro/translations/saspro_de.ts +253 -0
  315. setiastro/saspro/translations/saspro_es.qm +0 -0
  316. setiastro/saspro/translations/saspro_es.ts +12520 -0
  317. setiastro/saspro/translations/saspro_fr.qm +0 -0
  318. setiastro/saspro/translations/saspro_fr.ts +12514 -0
  319. setiastro/saspro/translations/saspro_hi.qm +0 -0
  320. setiastro/saspro/translations/saspro_hi.ts +257 -0
  321. setiastro/saspro/translations/saspro_it.qm +0 -0
  322. setiastro/saspro/translations/saspro_it.ts +12520 -0
  323. setiastro/saspro/translations/saspro_ja.qm +0 -0
  324. setiastro/saspro/translations/saspro_ja.ts +257 -0
  325. setiastro/saspro/translations/saspro_pt.qm +0 -0
  326. setiastro/saspro/translations/saspro_pt.ts +257 -0
  327. setiastro/saspro/translations/saspro_ru.qm +0 -0
  328. setiastro/saspro/translations/saspro_ru.ts +237 -0
  329. setiastro/saspro/translations/saspro_sw.qm +0 -0
  330. setiastro/saspro/translations/saspro_sw.ts +257 -0
  331. setiastro/saspro/translations/saspro_uk.qm +0 -0
  332. setiastro/saspro/translations/saspro_uk.ts +10771 -0
  333. setiastro/saspro/translations/saspro_zh.qm +0 -0
  334. setiastro/saspro/translations/saspro_zh.ts +12520 -0
  335. setiastro/saspro/translations/sw_translations.py +3671 -0
  336. setiastro/saspro/translations/uk_translations.py +3700 -0
  337. setiastro/saspro/translations/zh_translations.py +3675 -0
  338. setiastro/saspro/versioning.py +77 -0
  339. setiastro/saspro/view_bundle.py +1558 -0
  340. setiastro/saspro/wavescale_hdr.py +645 -0
  341. setiastro/saspro/wavescale_hdr_preset.py +101 -0
  342. setiastro/saspro/wavescalede.py +680 -0
  343. setiastro/saspro/wavescalede_preset.py +230 -0
  344. setiastro/saspro/wcs_update.py +374 -0
  345. setiastro/saspro/whitebalance.py +492 -0
  346. setiastro/saspro/widgets/__init__.py +48 -0
  347. setiastro/saspro/widgets/common_utilities.py +306 -0
  348. setiastro/saspro/widgets/graphics_views.py +122 -0
  349. setiastro/saspro/widgets/image_utils.py +518 -0
  350. setiastro/saspro/widgets/minigame/game.js +986 -0
  351. setiastro/saspro/widgets/minigame/index.html +53 -0
  352. setiastro/saspro/widgets/minigame/style.css +241 -0
  353. setiastro/saspro/widgets/preview_dialogs.py +280 -0
  354. setiastro/saspro/widgets/resource_monitor.py +237 -0
  355. setiastro/saspro/widgets/spinboxes.py +275 -0
  356. setiastro/saspro/widgets/themed_buttons.py +13 -0
  357. setiastro/saspro/widgets/wavelet_utils.py +331 -0
  358. setiastro/saspro/wimi.py +7996 -0
  359. setiastro/saspro/wims.py +578 -0
  360. setiastro/saspro/window_shelf.py +185 -0
  361. setiastro/saspro/xisf.py +1123 -0
  362. setiastrosuitepro-1.6.2.post1.dist-info/METADATA +278 -0
  363. setiastrosuitepro-1.6.2.post1.dist-info/RECORD +367 -0
  364. setiastrosuitepro-1.6.2.post1.dist-info/WHEEL +4 -0
  365. setiastrosuitepro-1.6.2.post1.dist-info/entry_points.txt +6 -0
  366. setiastrosuitepro-1.6.2.post1.dist-info/licenses/LICENSE +674 -0
  367. setiastrosuitepro-1.6.2.post1.dist-info/licenses/license.txt +2580 -0
@@ -0,0 +1,329 @@
1
+ # pro/star_alignment_preset.py
2
+ from __future__ import annotations
3
+ import os
4
+ import json
5
+ import re
6
+ import numpy as np
7
+ import cv2
8
+ import astroalign
9
+ from PyQt6.QtWidgets import (
10
+ QDialog, QFormLayout, QDialogButtonBox, QComboBox, QLineEdit, QCheckBox,
11
+ QSpinBox, QPushButton, QFileDialog, QWidget, QMessageBox
12
+ )
13
+ from PyQt6.QtCore import Qt
14
+
15
+ def _norm_name(s: str) -> str:
16
+ if not isinstance(s, str):
17
+ return ""
18
+ s = s.strip()
19
+ # strip common UI decorations
20
+ s = re.sub(r"^[■●◆▲▪▫•◼◻◾◽]\s*", "", s)
21
+ s = re.sub(r"^Active View:\s*", "", s, flags=re.I)
22
+ # ignore extensions when users type a short name
23
+ base = os.path.splitext(s)[0]
24
+ return base.casefold()
25
+
26
+ def _iter_views_with_titles(app):
27
+ """Yield (visible_title, doc, subwin). Visible title honors per-view overrides."""
28
+ mdi = getattr(app, "mdi", None)
29
+ if mdi is None:
30
+ return
31
+ for sw in mdi.subWindowList() or []:
32
+ try:
33
+ w = sw.widget()
34
+ doc = getattr(w, "document", None)
35
+ if doc is None:
36
+ continue
37
+ if hasattr(w, "_effective_title"):
38
+ title = str(w._effective_title())
39
+ else:
40
+ title = str(sw.windowTitle() or doc.display_name())
41
+ yield (title, doc, sw)
42
+ except Exception:
43
+ continue
44
+
45
+ def _resolve_ref_doc_by_view_name(app, name: str):
46
+ """Return (doc or None, debug_list)."""
47
+ wanted = _norm_name(name)
48
+ if not wanted:
49
+ return None, []
50
+
51
+ views = list(_iter_views_with_titles(app))
52
+ seen = [t for (t, _, __) in views]
53
+
54
+ # 1) exact title or exact doc name
55
+ for t, d, _ in views:
56
+ if _norm_name(t) == wanted or _norm_name(getattr(d, "display_name")()) == wanted:
57
+ return d, seen
58
+
59
+ # 2) prefix match
60
+ for t, d, _ in views:
61
+ if _norm_name(t).startswith(wanted) or _norm_name(getattr(d, "display_name")()).startswith(wanted):
62
+ return d, seen
63
+
64
+ # 3) substring
65
+ for t, d, _ in views:
66
+ if wanted in _norm_name(t) or wanted in _norm_name(getattr(d, "display_name")()):
67
+ return d, seen
68
+
69
+ return None, seen
70
+
71
+ # ---------------------------
72
+ # Preset dialog (like others)
73
+ # ---------------------------
74
+ class StarAlignmentPresetDialog(QDialog):
75
+ """
76
+ Preset UI for headless Star Alignment:
77
+ ref_mode: active | view_name | file
78
+ ref_name: (string, used when ref_mode=view_name)
79
+ ref_file: (path, used when ref_mode=file)
80
+ overwrite: bool
81
+ downsample:int (>=1)
82
+ """
83
+ def __init__(self, parent: QWidget | None = None, initial: dict | None = None):
84
+ super().__init__(parent)
85
+ self.setWindowTitle("Star Alignment — Preset")
86
+ init = dict(initial or {})
87
+
88
+ self.cmb_mode = QComboBox()
89
+ self.cmb_mode.addItems(["active", "view_name", "file"])
90
+ self.cmb_mode.setCurrentText(str(init.get("ref_mode", "active")).lower())
91
+
92
+ self.edt_name = QLineEdit()
93
+ self.edt_name.setPlaceholderText("Exact view title (case-insensitive)")
94
+ self.edt_name.setText(str(init.get("ref_name", "")))
95
+
96
+ self.edt_file = QLineEdit()
97
+ self.edt_file.setPlaceholderText("/absolute/path/to/reference.fits|fit|xisf|tif")
98
+ self.edt_file.setText(str(init.get("ref_file", "")))
99
+ self.btn_browse = QPushButton("Browse…")
100
+ def _pick_file():
101
+ p, _ = QFileDialog.getOpenFileName(self, "Choose reference file", "",
102
+ "Images (*.fits *.fit *.xisf *.tif *.tiff *.png *.jpg);;All Files (*)")
103
+ if p:
104
+ self.edt_file.setText(p)
105
+ self.btn_browse.clicked.connect(_pick_file)
106
+
107
+ self.chk_overwrite = QCheckBox("Overwrite target view")
108
+ self.chk_overwrite.setChecked(bool(init.get("overwrite", False)))
109
+
110
+ self.spin_down = QSpinBox()
111
+ self.spin_down.setRange(1, 8)
112
+ self.spin_down.setValue(int(init.get("downsample", 2)))
113
+ self.spin_down.setToolTip("Downsample factor during transform solve (speed vs precision)")
114
+
115
+ lay = QFormLayout(self)
116
+ lay.addRow("Reference mode:", self.cmb_mode)
117
+ lay.addRow("View name (if view_name):", self.edt_name)
118
+ rowf = QWidget(); rf = QFormLayout(rowf); rf.setContentsMargins(0,0,0,0)
119
+ rf.addRow(self.edt_file, self.btn_browse)
120
+ lay.addRow("File (if file):", rowf)
121
+ lay.addRow(self.chk_overwrite)
122
+ lay.addRow("Downsample factor:", self.spin_down)
123
+
124
+ btns = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, self)
125
+ btns.accepted.connect(self.accept); btns.rejected.connect(self.reject)
126
+ lay.addRow(btns)
127
+
128
+ # enable/disable inputs per mode
129
+ def _upd():
130
+ m = self.cmb_mode.currentText()
131
+ self.edt_name.setEnabled(m == "view_name")
132
+ self.edt_file.setEnabled(m == "file")
133
+ self.btn_browse.setEnabled(m == "file")
134
+ self.cmb_mode.currentTextChanged.connect(_upd); _upd()
135
+
136
+ def result_dict(self) -> dict:
137
+ return {
138
+ "ref_mode": self.cmb_mode.currentText(),
139
+ "ref_name": self.edt_name.text().strip(),
140
+ "ref_file": self.edt_file.text().strip(),
141
+ "overwrite": bool(self.chk_overwrite.isChecked()),
142
+ "downsample": int(self.spin_down.value()),
143
+ }
144
+
145
+
146
+ # ---------------------------
147
+ # Headless runner
148
+ # ---------------------------
149
+ def run_star_alignment_via_preset(app, preset: dict, target_doc) -> None:
150
+ """
151
+ app: main window (has mdi, docman/doc_manager, _log)
152
+ target_doc: document object for the drop target
153
+ Behavior mirrors other *_via_preset helpers: overwrite or create new view.
154
+ """
155
+ from setiastro.saspro.legacy.image_manager import load_image # for file ref
156
+
157
+ # ---- resolve target image ----
158
+ if target_doc is None or getattr(target_doc, "image", None) is None:
159
+ raise RuntimeError("Target view has no image.")
160
+ tgt = np.asarray(target_doc.image, dtype=np.float32)
161
+ if tgt.ndim not in (2, 3):
162
+ raise RuntimeError("Unsupported target image shape.")
163
+
164
+ # ---- resolve reference image per preset ----
165
+ pp = dict(preset or {})
166
+ mode = str(pp.get("ref_mode", "active")).lower()
167
+
168
+ def _doc_display_name(d):
169
+ try:
170
+ if callable(getattr(d, "display_name", None)):
171
+ return d.display_name() or ""
172
+ nm = getattr(d, "title", None)
173
+ return nm or ""
174
+ except Exception:
175
+ return ""
176
+
177
+ ref = None
178
+ ref_name = "Reference"
179
+
180
+ if mode == "file":
181
+ path = pp.get("ref_file") or ""
182
+ if not path or not os.path.exists(path):
183
+ raise RuntimeError("Reference file does not exist.")
184
+ ref, _, _, _ = load_image(path)
185
+ if ref is None:
186
+ raise RuntimeError("Failed to load reference file.")
187
+ ref = np.asarray(ref, dtype=np.float32)
188
+ ref_name = os.path.basename(path)
189
+
190
+ elif mode == "view_name":
191
+ # NEW: resolve against visible titles (per-view overrides) w/ normalization
192
+ raw_name = (pp.get("ref_name") or "").strip()
193
+ if not raw_name:
194
+ raise RuntimeError("Reference view name is empty.")
195
+
196
+ ref_doc, seen_titles = _resolve_ref_doc_by_view_name(app, raw_name)
197
+ if ref_doc is None or getattr(ref_doc, "image", None) is None:
198
+ # helpful error includes visible open view titles (no glyphs/exts)
199
+ opts = ", ".join(os.path.splitext(t)[0] for t in seen_titles) if seen_titles else "(no open views)"
200
+ raise RuntimeError(f"Reference view '{raw_name}' not found.\nOpen views: {opts}")
201
+
202
+ ref = np.asarray(ref_doc.image, dtype=np.float32)
203
+
204
+ # Prefer the visible title of the matched subwindow for the label
205
+ ref_name = None
206
+ for t, d, _ in _iter_views_with_titles(app):
207
+ if d is ref_doc:
208
+ ref_name = os.path.splitext(t)[0]
209
+ break
210
+ ref_name = ref_name or (_doc_display_name(ref_doc) or "Reference")
211
+
212
+ else: # "active" (default) → use the app's active subwindow doc
213
+ sw = getattr(app, "mdi", None).activeSubWindow() if getattr(app, "mdi", None) else None
214
+ if sw is None:
215
+ raise RuntimeError("No active view to use as reference.")
216
+ ref_doc = getattr(sw.widget(), "document", None)
217
+ if ref_doc is None or getattr(ref_doc, "image", None) is None:
218
+ raise RuntimeError("Active view has no image to use as reference.")
219
+ ref = np.asarray(ref_doc.image, dtype=np.float32)
220
+ # Prefer visible title if available
221
+ try:
222
+ for t, d, _ in _iter_views_with_titles(app):
223
+ if d is ref_doc:
224
+ ref_name = os.path.splitext(t)[0]
225
+ break
226
+ if ref_name == "Reference":
227
+ ref_name = _doc_display_name(ref_doc) or "Reference"
228
+ except Exception:
229
+ ref_name = _doc_display_name(ref_doc) or "Reference"
230
+
231
+ # ---- grayscale + optional downsample for solve ----
232
+ def _to_gray(a):
233
+ return np.mean(a, axis=2) if a.ndim == 3 else a
234
+
235
+ ds = int(max(1, pp.get("downsample", 2)))
236
+ ref_g = _to_gray(ref); tgt_g = _to_gray(tgt)
237
+ if ds > 1:
238
+ wR, hR = ref_g.shape[1] // ds, ref_g.shape[0] // ds
239
+ wT, hT = tgt_g.shape[1] // ds, tgt_g.shape[0] // ds
240
+ ref_s = cv2.resize(ref_g, (max(1, wR), max(1, hR)), interpolation=cv2.INTER_AREA)
241
+ tgt_s = cv2.resize(tgt_g, (max(1, wT), max(1, hT)), interpolation=cv2.INTER_AREA)
242
+ else:
243
+ ref_s, tgt_s = ref_g, tgt_g
244
+
245
+ # ---- find transform (map target → reference) with gentle backoff ----
246
+ tries = [
247
+ dict(detection_sigma=5, min_area=7, max_control_points=75),
248
+ dict(detection_sigma=12, min_area=9, max_control_points=75),
249
+ dict(detection_sigma=20, min_area=9, max_control_points=75),
250
+ dict(detection_sigma=30, min_area=11, max_control_points=75),
251
+ ]
252
+ last = None
253
+ M = None
254
+ for kw in tries:
255
+ try:
256
+ T, _ = astroalign.find_transform(tgt_s.astype(np.float32), ref_s.astype(np.float32), **kw)
257
+ M = T.params[0:2, :].astype(np.float32)
258
+ break
259
+ except Exception as e:
260
+ last = e
261
+ # bump SEP pixstack if that was the issue
262
+ try:
263
+ import sep
264
+ if "pixel buffer full" in str(e).lower():
265
+ sep.set_extract_pixstack(int(sep.get_extract_pixstack() * 2))
266
+ except Exception:
267
+ pass
268
+ if M is None:
269
+ raise RuntimeError(f"Astroalign failed: {last}")
270
+
271
+ if ds > 1: # scale the translation back up
272
+ M = M.copy()
273
+ M[0, 2] *= ds
274
+ M[1, 2] *= ds
275
+
276
+ # ---- warp target to reference geometry ----
277
+ H, W = ref_g.shape[:2]
278
+ if tgt.ndim == 2:
279
+ aligned = cv2.warpAffine(
280
+ tgt, M, (W, H),
281
+ flags=cv2.INTER_LANCZOS4,
282
+ borderMode=cv2.BORDER_CONSTANT, borderValue=0
283
+ )
284
+ else:
285
+ planes = [
286
+ cv2.warpAffine(
287
+ tgt[..., i], M, (W, H),
288
+ flags=cv2.INTER_LANCZOS4,
289
+ borderMode=cv2.BORDER_CONSTANT, borderValue=0
290
+ ) for i in range(tgt.shape[2])
291
+ ]
292
+ aligned = np.stack(planes, axis=2)
293
+ aligned = aligned.astype(np.float32, copy=False)
294
+
295
+ # ---- write back (overwrite or new view) ----
296
+ overwrite = bool(pp.get("overwrite", False))
297
+ if overwrite:
298
+ if hasattr(target_doc, "set_image"):
299
+ target_doc.set_image(aligned, step_name=f"Star Alignment → {ref_name}")
300
+ elif hasattr(target_doc, "apply_numpy"):
301
+ target_doc.apply_numpy(aligned, step_name=f"Star Alignment → {ref_name}")
302
+ else:
303
+ target_doc.image = aligned
304
+ try:
305
+ if hasattr(target_doc, "changed"):
306
+ target_doc.changed.emit()
307
+ except Exception:
308
+ pass
309
+ else:
310
+ dm = getattr(app, "docman", None) or getattr(app, "doc_manager", None)
311
+ if dm is None:
312
+ raise RuntimeError("Document manager not available.")
313
+ # title
314
+ base = ""
315
+ try:
316
+ base = target_doc.display_name() if callable(getattr(target_doc, "display_name", None)) \
317
+ else (getattr(target_doc, "title", None) or "")
318
+ except Exception:
319
+ pass
320
+ base = base or "Image"
321
+ title = f"{base} [Aligned → {ref_name}]"
322
+ meta = {
323
+ "step_name": "Star Alignment",
324
+ "description": f"Aligned to {ref_name}",
325
+ "is_mono": bool(aligned.ndim == 2 or (aligned.ndim == 3 and aligned.shape[2] == 1)),
326
+ }
327
+ newdoc = dm.open_array(aligned, metadata=meta, title=title)
328
+ if hasattr(app, "_spawn_subwindow_for"):
329
+ app._spawn_subwindow_for(newdoc)
@@ -0,0 +1,49 @@
1
+ # pro/star_metrics.py
2
+ from __future__ import annotations
3
+ import numpy as np
4
+ import sep
5
+
6
+ def measure_stars_sep(
7
+ img_norm_mono: np.ndarray,
8
+ *,
9
+ thresh_sigma: float = 7.0,
10
+ minarea: int = 16,
11
+ deblend_nthresh: int = 32,
12
+ aggregate: str = "median",
13
+ ) -> tuple[int, float, float]:
14
+ """
15
+ img_norm_mono: float32 [0..1] 2D array.
16
+ Returns (star_count, fwhm, ecc) using SEP:
17
+ - FWHM = 2.3548 * sqrt(a*b) (a,b are RMS ellipse axes)
18
+ - ecc = sqrt(1 - (b/a)^2)
19
+ """
20
+ try:
21
+ # background & noise (consistent for both callers)
22
+ bkg = sep.Background(img_norm_mono)
23
+ back = bkg.back()
24
+ grms = bkg.globalrms
25
+
26
+ cat = sep.extract(
27
+ img_norm_mono - back,
28
+ thresh_sigma,
29
+ err=grms,
30
+ minarea=minarea,
31
+ clean=True,
32
+ deblend_nthresh=deblend_nthresh,
33
+ )
34
+ if cat is None or len(cat) == 0:
35
+ return 0, 0.0, 0.0
36
+
37
+ a = np.clip(cat["a"].astype(np.float32), 1e-3, None)
38
+ b = np.clip(cat["b"].astype(np.float32), 1e-3, None)
39
+
40
+ fwhm_vals = 2.3548 * np.sqrt(a * b)
41
+ ratios = np.clip(b / a, 0.0, 1.0)
42
+ ecc_vals = np.sqrt(1.0 - ratios * ratios)
43
+
44
+ agg = np.nanmedian if aggregate == "median" else np.nanmean
45
+ return len(cat), float(agg(fwhm_vals)), float(agg(ecc_vals))
46
+
47
+ except Exception:
48
+ # identical failure shape for both callers
49
+ return 0, 0.0, 0.0