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,421 @@
1
+ # pro/gui/mixins/mask_mixin.py
2
+ """
3
+ Mask management mixin for AstroSuiteProMainWindow.
4
+
5
+ This mixin contains all functionality for creating, managing, and
6
+ manipulating masks on document images.
7
+ """
8
+ from __future__ import annotations
9
+ from typing import TYPE_CHECKING
10
+
11
+ import numpy as np
12
+ from PyQt6.QtWidgets import QMessageBox
13
+
14
+ if TYPE_CHECKING:
15
+ pass
16
+
17
+
18
+ class MaskMixin:
19
+ """
20
+ Mixin for mask management.
21
+
22
+ Provides methods for creating, applying, inverting, and removing masks
23
+ from document images.
24
+ """
25
+
26
+ def _show_mask_overlay(self):
27
+ """Show the mask overlay on the active view."""
28
+ vw = self._active_view()
29
+ if not vw:
30
+ return
31
+ # require an active mask on this doc
32
+ doc = getattr(vw, "document", None)
33
+ has_mask = bool(doc and getattr(doc, "active_mask_id", None))
34
+ if not has_mask:
35
+ QMessageBox.information(self, self.tr("Mask Overlay"), self.tr("No active mask on this image."))
36
+ return
37
+ vw.show_mask_overlay = True
38
+ # ensure visuals are up-to-date immediately
39
+ try:
40
+ vw._set_mask_highlight(True)
41
+ except Exception:
42
+ pass
43
+ vw._render(rebuild=True)
44
+ self._refresh_mask_action_states()
45
+
46
+ def _hide_mask_overlay(self):
47
+ """Hide the mask overlay on the active view."""
48
+ vw = self._active_view()
49
+ if not vw:
50
+ return
51
+ vw.show_mask_overlay = False
52
+ vw._render(rebuild=True)
53
+ self._refresh_mask_action_states()
54
+
55
+ def _invert_mask(self):
56
+ """Invert the active mask on the current document."""
57
+ doc = self._active_doc()
58
+ if not doc:
59
+ return
60
+ mid = getattr(doc, "active_mask_id", None)
61
+ if not mid:
62
+ return
63
+ layer = (getattr(doc, "masks", {}) or {}).get(mid)
64
+ if layer is None or getattr(layer, "data", None) is None:
65
+ return
66
+
67
+ m = np.asarray(layer.data)
68
+ if m.size == 0:
69
+ return
70
+
71
+ # invert (preserve dtype)
72
+ if m.dtype.kind in "ui":
73
+ maxv = np.iinfo(m.dtype).max
74
+ layer.data = (maxv - m).astype(m.dtype, copy=False)
75
+ else:
76
+ layer.data = (1.0 - m.astype(np.float32, copy=False)).clip(0.0, 1.0)
77
+
78
+ # notify listeners (triggers ImageSubWindow.render via your existing hookup)
79
+ if hasattr(doc, "changed"):
80
+ doc.changed.emit()
81
+
82
+ # and explicitly refresh the active view overlay right now
83
+ vw = self._active_view()
84
+ if vw and hasattr(vw, "refresh_mask_overlay"):
85
+ vw.refresh_mask_overlay()
86
+
87
+ # keep menu states tidy
88
+ if hasattr(self, "_refresh_mask_action_states"):
89
+ self._refresh_mask_action_states()
90
+
91
+ def _action_create_mask(self):
92
+ """Create a new mask from the current document."""
93
+ from setiastro.saspro.masks_core import create_mask_and_attach
94
+
95
+ doc = self._current_document()
96
+ if doc is None or getattr(doc, "image", None) is None:
97
+ QMessageBox.information(self, self.tr("No image"), self.tr("Open an image first."))
98
+ return
99
+ created = create_mask_and_attach(self, doc)
100
+ # Optional toast/log
101
+ if created and hasattr(self, "_log"):
102
+ self._log("Mask created and set active.")
103
+
104
+ def _list_candidate_mask_sources(self, exclude_doc=None):
105
+ """Return list of open documents that can serve as mask sources."""
106
+ return [d for d in self._list_open_docs() if d is not exclude_doc]
107
+
108
+ def _prepare_mask_array(self, src_img, target_hw, invert=False, feather_px=0.0):
109
+ """
110
+ Prepare a mask array from source image.
111
+
112
+ Args:
113
+ src_img: Source image array
114
+ target_hw: Target (height, width) tuple
115
+ invert: Whether to invert the mask
116
+ feather_px: Feather radius in pixels
117
+
118
+ Returns:
119
+ Prepared mask as float32 array
120
+ """
121
+ a = np.asarray(src_img)
122
+ if a.ndim == 3:
123
+ a = (0.2126 * a[..., 0] + 0.7152 * a[..., 1] + 0.0722 * a[..., 2])
124
+ elif a.ndim == 3 and a.shape[2] == 1:
125
+ a = a[..., 0]
126
+ a = a.astype(np.float32, copy=False)
127
+ if a.dtype.kind in "ui":
128
+ a /= float(np.iinfo(a.dtype).max)
129
+ else:
130
+ mx = float(a.max()) if a.size else 1.0
131
+ if mx > 1.0:
132
+ a /= mx
133
+ a = np.clip(a, 0.0, 1.0)
134
+
135
+ th, tw = target_hw
136
+ sh, sw = a.shape[:2]
137
+ if (sh, sw) != (th, tw):
138
+ yi = (np.linspace(0, sh - 1, th)).astype(np.int32)
139
+ xi = (np.linspace(0, sw - 1, tw)).astype(np.int32)
140
+ a = a[yi][:, xi]
141
+ if invert:
142
+ a = 1.0 - a
143
+ if feather_px and feather_px > 0.5:
144
+ k = max(1, min(int(round(feather_px)), 64))
145
+ w = np.ones((k,), dtype=np.float32) / float(k)
146
+ a = np.apply_along_axis(lambda r: np.convolve(r, w, mode='same'), 1, a)
147
+ a = np.apply_along_axis(lambda c: np.convolve(c, w, mode='same'), 0, a)
148
+ a = np.clip(a, 0.0, 1.0)
149
+ return a.astype(np.float32, copy=False)
150
+
151
+ def _attach_mask_to_document(self, target_doc, mask_doc, *, name="Mask", mode="replace", invert=False, feather=0.0):
152
+ """
153
+ Attach a mask from mask_doc to target_doc.
154
+
155
+ Args:
156
+ target_doc: Document to attach mask to
157
+ mask_doc: Document to use as mask source
158
+ name: Name for the mask layer
159
+ mode: Mask blend mode
160
+ invert: Whether to invert the mask
161
+ feather: Feather radius in pixels
162
+
163
+ Returns:
164
+ True if successful, False otherwise
165
+ """
166
+ if getattr(target_doc, "image", None) is None or getattr(mask_doc, "image", None) is None:
167
+ return False
168
+ th, tw = target_doc.image.shape[:2]
169
+ mask_arr = self._prepare_mask_array(mask_doc.image, (th, tw), invert=invert, feather_px=feather)
170
+
171
+ try:
172
+ from setiastro.saspro.masks_core import MaskLayer
173
+ except Exception:
174
+ from uuid import uuid4
175
+
176
+ class MaskLayer:
177
+ def __init__(self, name, data, mode="replace", opacity=1.0):
178
+ self.id = f"mask-{uuid4().hex[:8]}"
179
+ self.name = name
180
+ self.data = data
181
+ self.mode = mode
182
+ self.opacity = opacity
183
+
184
+ layer = MaskLayer(id=name, name=name, data=mask_arr, mode=mode, opacity=1.0)
185
+ try:
186
+ target_doc.add_mask(layer, make_active=True)
187
+ except Exception:
188
+ if not hasattr(target_doc, "masks"):
189
+ target_doc.masks = {}
190
+ target_doc.masks[layer.id] = layer
191
+ target_doc.active_mask_id = layer.id
192
+ target_doc.changed.emit()
193
+
194
+ md = target_doc.metadata.setdefault("masks_meta", {})
195
+ md[layer.id] = {"name": name, "mode": mode, "invert": bool(invert), "feather": float(feather)}
196
+ target_doc.changed.emit()
197
+ return True
198
+
199
+ def _apply_mask_menu(self):
200
+ """Show dialog to apply a mask from another document."""
201
+ target_doc = self._active_doc()
202
+ if not target_doc:
203
+ QMessageBox.information(self, self.tr("Mask"), self.tr("No active document."))
204
+ return
205
+
206
+ candidates = self._list_candidate_mask_sources(exclude_doc=target_doc)
207
+ if not candidates:
208
+ QMessageBox.information(self, self.tr("Mask"), self.tr("Open another image to use as a mask."))
209
+ return
210
+
211
+ # If there are multiple, ask which one to use
212
+ mask_doc = None
213
+ if len(candidates) == 1:
214
+ mask_doc = candidates[0]
215
+ else:
216
+ from PyQt6.QtWidgets import QInputDialog
217
+ names = [f"{i + 1}. {d.display_name()}" for i, d in enumerate(candidates)]
218
+ choice, ok = QInputDialog.getItem(self, self.tr("Choose Mask Image"),
219
+ self.tr("Use this image as mask:"), names, 0, False)
220
+ if not ok:
221
+ return
222
+ idx = names.index(choice)
223
+ mask_doc = candidates[idx]
224
+
225
+ name = mask_doc.display_name() or "Mask"
226
+ ok = self._attach_mask_to_document(target_doc, mask_doc,
227
+ name=name, mode="replace",
228
+ invert=False, feather=0.0)
229
+ if ok and hasattr(self, "_log"):
230
+ self._log(f"Mask '{name}' applied to '{target_doc.display_name()}'")
231
+
232
+ # Force views to update title/overlay immediately
233
+ if ok:
234
+ try:
235
+ target_doc.changed.emit()
236
+ except Exception:
237
+ pass
238
+
239
+ self._refresh_mask_action_states()
240
+
241
+ def _resolve_mask_source_doc_from_payload(self, payload: dict):
242
+ """
243
+ Robustly resolve the source document for a mask drop using any of:
244
+ - doc_ptr / mask_doc_ptr (legacy pointer)
245
+ - doc_uid / base_doc_uid
246
+ - file_path
247
+ """
248
+ # 1) Try pointer first, using the existing helper if present
249
+ ptr = payload.get("doc_ptr") or payload.get("mask_doc_ptr")
250
+ if ptr and hasattr(self, "_doc_by_ptr"):
251
+ try:
252
+ doc = self._doc_by_ptr(ptr)
253
+ if doc is not None:
254
+ return doc
255
+ except Exception:
256
+ pass
257
+
258
+ # 2) Fall back to uid-based matching
259
+ uid = payload.get("doc_uid") or payload.get("base_doc_uid")
260
+ file_path = payload.get("file_path")
261
+
262
+ try:
263
+ open_docs = self._list_open_docs()
264
+ except Exception:
265
+ open_docs = []
266
+
267
+ for d in open_docs:
268
+ # A) uid match
269
+ if uid and getattr(d, "uid", None) == uid:
270
+ return d
271
+
272
+ if file_path:
273
+ # B) file_path match as last resort
274
+ for d in open_docs:
275
+ meta = getattr(d, "metadata", {}) or {}
276
+ if meta.get("file_path") == file_path:
277
+ return d
278
+
279
+ return None
280
+
281
+
282
+ def _remove_mask_menu(self):
283
+ """Remove the active mask from the current document."""
284
+ doc = self._active_doc()
285
+ if not doc:
286
+ return
287
+ mid = getattr(doc, "active_mask_id", None)
288
+ if not mid:
289
+ QMessageBox.information(self, self.tr("Mask"), self.tr("No active mask to remove."))
290
+ return
291
+ try:
292
+ doc.remove_mask(mid)
293
+ doc.changed.emit()
294
+ if hasattr(self, "_log"):
295
+ self._log(f"Removed active mask from '{doc.display_name()}'")
296
+ except Exception:
297
+ ...
298
+ # If overlay was on, hide it now
299
+ vw = self._active_view()
300
+ if vw and getattr(vw, "show_mask_overlay", False):
301
+ vw.show_mask_overlay = False
302
+ vw._render(rebuild=True)
303
+
304
+ self._refresh_mask_action_states()
305
+
306
+ def _handle_mask_drop(self, payload: dict, target_sw):
307
+ print("[MainWindow] _handle_mask_drop payload:", payload)
308
+ """
309
+ Handle mask drag-and-drop from one document to another.
310
+
311
+ Args:
312
+ payload: Dict with source document info
313
+ target_sw: Target QMdiSubWindow
314
+ """
315
+ from PyQt6.QtCore import Qt, QTimer
316
+ from PyQt6.QtWidgets import QMessageBox
317
+ from setiastro.saspro.subwindow import ImageSubWindow
318
+
319
+ if target_sw is None:
320
+ # applying a mask requires a target view
321
+ print("[MainWindow] _handle_mask_drop: target_sw is None")
322
+ return
323
+
324
+ # --- 1) Resolve source doc pointer ---------------------------------
325
+ src_ptr = payload.get("mask_doc_ptr") or payload.get("doc_ptr")
326
+ if not src_ptr:
327
+ print("[MainWindow] _handle_mask_drop: missing mask_doc_ptr/doc_ptr")
328
+ return
329
+
330
+ src_doc = None
331
+ src_sw = None
332
+
333
+ # Prefer a doc-manager helper if you ever add one
334
+ dm = getattr(self, "docman", None)
335
+ if dm is not None and hasattr(dm, "doc_for_ptr"):
336
+ try:
337
+ src_doc = dm.doc_for_ptr(src_ptr)
338
+ except Exception:
339
+ src_doc = None
340
+
341
+ # Legacy resolver: walk MDI subwindows and compare id(document)
342
+ if src_doc is None and hasattr(self, "_find_doc_by_id"):
343
+ try:
344
+ src_doc, src_sw = self._find_doc_by_id(src_ptr)
345
+ except Exception:
346
+ src_doc, src_sw = None, None
347
+
348
+ if src_doc is None:
349
+ print(f"[MainWindow] _handle_mask_drop: no src_doc for ptr={src_ptr}")
350
+ QMessageBox.warning(self, self.tr("Mask"), self.tr("Could not resolve mask document."))
351
+ return
352
+
353
+ # --- 2) Resolve target view / doc ----------------------------------
354
+ target_view = target_sw.widget()
355
+ if not isinstance(target_view, ImageSubWindow):
356
+ # In case there’s a wrapper widget
357
+ tv = target_sw.widget()
358
+ if tv is not None:
359
+ target_view = tv.findChild(ImageSubWindow)
360
+ else:
361
+ target_view = None
362
+
363
+ if target_view is None:
364
+ print("[MainWindow] _handle_mask_drop: no target_view resolved")
365
+ return
366
+
367
+ target_doc = getattr(target_view, "document", None)
368
+ if target_doc is None:
369
+ print("[MainWindow] _handle_mask_drop: target_view has no document")
370
+ return
371
+
372
+ # Allow DocProxy, but unwrap if it exposes base_document
373
+ real_target = getattr(target_doc, "base_document", None) or target_doc
374
+
375
+ mode = str(payload.get("mode", "replace"))
376
+ invert = bool(payload.get("invert", False))
377
+ feather = float(payload.get("feather", 0.0))
378
+ name = payload.get("name") or src_doc.display_name() or "Mask"
379
+
380
+ print(f"[MainWindow] _handle_mask_drop: src_doc={src_doc}, target_doc={real_target}, "
381
+ f"mode={mode}, invert={invert}, feather={feather}, name={name!r}")
382
+
383
+ # --- 3) Attach mask using the shared helper -------------------------
384
+ ok = self._attach_mask_to_document(
385
+ real_target,
386
+ src_doc,
387
+ name=name,
388
+ mode=mode,
389
+ invert=invert,
390
+ feather=feather,
391
+ )
392
+
393
+ if not ok:
394
+ print("[MainWindow] _handle_mask_drop: _attach_mask_to_document() returned False")
395
+ return
396
+
397
+ if hasattr(self, "_log"):
398
+ try:
399
+ self._log(f"Mask '{name}' applied to '{real_target.display_name()}'")
400
+ except Exception:
401
+ pass
402
+
403
+ if hasattr(real_target, "changed"):
404
+ try:
405
+ real_target.changed.emit()
406
+ except Exception:
407
+ pass
408
+
409
+ # Make the drop target the active subwindow immediately (like before)
410
+ def _activate():
411
+ try:
412
+ self.mdi.setActiveSubWindow(target_sw)
413
+ target_sw.activateWindow()
414
+ target_sw.raise_()
415
+ target_sw.widget().setFocus(Qt.FocusReason.MouseFocusReason)
416
+ except Exception:
417
+ pass
418
+ self._refresh_mask_action_states()
419
+
420
+ _activate()
421
+ QTimer.singleShot(0, _activate)