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,56 @@
1
+ # pro/masks_core.py
2
+ from __future__ import annotations
3
+ from dataclasses import dataclass
4
+ import numpy as np
5
+
6
+ try:
7
+ import cv2
8
+ except Exception:
9
+ cv2 = None
10
+
11
+ @dataclass
12
+ class MaskLayer:
13
+ id: str
14
+ name: str
15
+ data: np.ndarray # HxW float32 in [0..1]
16
+ invert: bool = False
17
+ opacity: float = 1.0 # 0..1
18
+ mode: str = "affect" # "affect" or "protect"
19
+ visible: bool = True
20
+
21
+ def _resize_mask(mask: np.ndarray, shape_hw: tuple[int,int]) -> np.ndarray:
22
+ h, w = shape_hw
23
+ if mask.shape[:2] == (h, w):
24
+ return mask.astype(np.float32, copy=False)
25
+ if cv2 is not None:
26
+ return cv2.resize(mask.astype(np.float32, copy=False), (w, h), interpolation=cv2.INTER_LINEAR)
27
+ # Pure-numpy fallback (nearest)
28
+ y = (np.linspace(0, mask.shape[0]-1, h)).astype(np.int32)
29
+ x = (np.linspace(0, mask.shape[1]-1, w)).astype(np.int32)
30
+ return mask[y][:, x].astype(np.float32, copy=False)
31
+
32
+ def blend_with_mask(original: np.ndarray,
33
+ edited: np.ndarray,
34
+ layer: MaskLayer | None) -> np.ndarray:
35
+ if layer is None:
36
+ return edited
37
+ m = _resize_mask(layer.data, original.shape[:2])
38
+ m = np.clip(m, 0.0, 1.0)
39
+ if layer.mode == "protect":
40
+ m = 1.0 - m
41
+ if layer.invert:
42
+ m = 1.0 - m
43
+ m = m * float(max(0.0, min(1.0, layer.opacity)))
44
+
45
+ # Shape/broadcast safety
46
+ o = original
47
+ e = edited
48
+ if e.ndim == 2 and o.ndim == 3:
49
+ e = np.repeat(e[..., None], o.shape[2], axis=2)
50
+ if o.ndim == 2 and e.ndim == 3:
51
+ o = np.repeat(o[..., None], e.shape[2], axis=2)
52
+ if m.ndim == 2 and e.ndim == 3:
53
+ m = m[..., None]
54
+
55
+ return (e.astype(np.float32, copy=False) * m +
56
+ o.astype(np.float32, copy=False) * (1.0 - m)).astype(e.dtype, copy=False)
@@ -0,0 +1,353 @@
1
+ # pro/mdi_widgets.py
2
+ """
3
+ MDI-related widgets and supporting classes extracted from main file.
4
+
5
+ Contains:
6
+ - MdiArea: Custom QMdiArea with drag-and-drop support
7
+ - ViewLinkController: Synchronizes view transforms across linked windows
8
+ - ConsoleListWidget: QListWidget with context menu for console output
9
+ - QtLogStream: QObject to mirror stdout/stderr to Qt signals
10
+ - _DocProxy: Lightweight proxy for ROI/preview document resolution
11
+ """
12
+
13
+ import json
14
+ import weakref
15
+ from typing import Optional
16
+
17
+ from PyQt6.QtWidgets import QMdiArea, QListWidget, QMenu, QApplication
18
+ from PyQt6.QtCore import Qt, pyqtSignal, QObject
19
+
20
+ from setiastro.saspro.dnd_mime import (
21
+ MIME_VIEWSTATE, MIME_CMD, MIME_MASK, MIME_ASTROMETRY, MIME_LINKVIEW
22
+ )
23
+ from setiastro.saspro.shortcuts import _unpack_cmd_payload
24
+
25
+
26
+ class MdiArea(QMdiArea):
27
+ """
28
+ Custom QMdiArea with support for drag-and-drop of:
29
+ - View states (for duplicating views)
30
+ - Commands with presets (from shortcut toolbar)
31
+ - Masks
32
+ - Astrometry data
33
+ - Link view payloads
34
+ """
35
+ backgroundDoubleClicked = pyqtSignal()
36
+ viewStateDropped = pyqtSignal(dict, object) # (state_dict, target_subwindow or None)
37
+ commandDropped = pyqtSignal(dict, object) # ({"command_id","preset"}, target_subwindow or None)
38
+ maskDropped = pyqtSignal(dict, object) # (payload, target_subwindow or None)
39
+ astrometryDropped = pyqtSignal(dict, object)
40
+ linkViewDropped = pyqtSignal(dict, object)
41
+
42
+ def __init__(self, *args, **kwargs):
43
+ super().__init__(*args, **kwargs)
44
+ self.setAcceptDrops(True)
45
+
46
+ def dragEnterEvent(self, e):
47
+ md = e.mimeData()
48
+ if (md.hasFormat(MIME_VIEWSTATE)
49
+ or md.hasFormat(MIME_CMD)
50
+ or md.hasFormat(MIME_MASK)
51
+ or md.hasFormat(MIME_ASTROMETRY)
52
+ or md.hasFormat(MIME_LINKVIEW)):
53
+ e.acceptProposedAction()
54
+ else:
55
+ super().dragEnterEvent(e)
56
+
57
+ def dropEvent(self, e):
58
+ pos = e.position().toPoint()
59
+
60
+ # Map the event position from the MdiArea into the viewport's coords
61
+ vp = self.viewport()
62
+ vp_pos = vp.mapFrom(self, pos) if vp is not None else pos
63
+
64
+ # Get subwindows in real z-order (back → front)
65
+ try:
66
+ order_enum = getattr(QMdiArea, "WindowOrder", None)
67
+ subwins = self.subWindowList(order_enum.StackingOrder) if order_enum else self.subWindowList()
68
+ except Exception:
69
+ subwins = self.subWindowList()
70
+
71
+ # Pick the visually top-most window under the cursor
72
+ target = None
73
+ for sw in reversed(subwins): # reversed: front-most first
74
+ if sw.isVisible() and sw.geometry().contains(vp_pos):
75
+ target = sw
76
+ break
77
+
78
+ # 1) View-state payload
79
+ if e.mimeData().hasFormat(MIME_VIEWSTATE):
80
+ try:
81
+ raw = bytes(e.mimeData().data(MIME_VIEWSTATE))
82
+ state = json.loads(raw.decode("utf-8"))
83
+ except Exception:
84
+ e.ignore()
85
+ return
86
+ self.viewStateDropped.emit(state, target)
87
+ e.acceptProposedAction()
88
+ return
89
+
90
+ # 2) Command + preset payload (from shortcuts)
91
+ if e.mimeData().hasFormat(MIME_CMD):
92
+ try:
93
+ payload = _unpack_cmd_payload(bytes(e.mimeData().data(MIME_CMD)))
94
+ except Exception:
95
+ e.ignore()
96
+ return
97
+ self.commandDropped.emit(payload, target)
98
+ e.acceptProposedAction()
99
+ return
100
+
101
+ # 3) Mask payload (from subwindow DnD)
102
+ if e.mimeData().hasFormat(MIME_MASK):
103
+ try:
104
+ raw = bytes(e.mimeData().data(MIME_MASK))
105
+ payload = json.loads(raw.decode("utf-8"))
106
+ except Exception:
107
+ e.ignore()
108
+ return
109
+ self.maskDropped.emit(payload, target)
110
+ e.acceptProposedAction()
111
+ return
112
+
113
+ # 4) Astrometric payload (from subwindow DnD)
114
+ if e.mimeData().hasFormat(MIME_ASTROMETRY):
115
+ try:
116
+ raw = bytes(e.mimeData().data(MIME_ASTROMETRY))
117
+ payload = json.loads(raw.decode("utf-8"))
118
+ except Exception:
119
+ e.ignore()
120
+ return
121
+ self.astrometryDropped.emit(payload, target)
122
+ e.acceptProposedAction()
123
+ return
124
+
125
+ # 5) Link view payload
126
+ if e.mimeData().hasFormat(MIME_LINKVIEW):
127
+ try:
128
+ raw = bytes(e.mimeData().data(MIME_LINKVIEW))
129
+ payload = json.loads(raw.decode("utf-8"))
130
+ except Exception:
131
+ e.ignore()
132
+ return
133
+ self.linkViewDropped.emit(payload, target)
134
+ e.acceptProposedAction()
135
+ return
136
+
137
+ # Fallback
138
+ super().dropEvent(e)
139
+
140
+ def mouseDoubleClickEvent(self, event):
141
+ pt = event.position().toPoint() if hasattr(event, "position") else event.pos()
142
+ for sw in self.subWindowList():
143
+ if sw.geometry().contains(pt):
144
+ return super().mouseDoubleClickEvent(event)
145
+ self.backgroundDoubleClicked.emit()
146
+ event.accept()
147
+
148
+
149
+ class ViewLinkController:
150
+ """
151
+ Controller to synchronize view transforms (zoom, scroll) across linked windows.
152
+
153
+ Views can be assigned to named groups. When one view's transform changes,
154
+ all other views in the same group are updated to match.
155
+ """
156
+
157
+ def __init__(self, mdi: QMdiArea):
158
+ self.mdi = mdi
159
+ self.groups: dict[str, set] = {} # name -> set(views)
160
+ self.by_view: dict = {} # view -> group name
161
+ self._slots: dict = {} # view -> callable
162
+ self._broadcasting = False
163
+
164
+ def attach_view(self, view):
165
+ """Connect a view's transform change signal to the controller."""
166
+ if view in self._slots:
167
+ return
168
+ slot = lambda scale, h, v, vref=view: self._on_view_transform_from(vref, scale, h, v)
169
+ view.viewTransformChanged.connect(slot)
170
+ self._slots[view] = slot
171
+
172
+ def detach_view(self, view):
173
+ """Disconnect and remove a view from any group."""
174
+ slot = self._slots.pop(view, None)
175
+ if slot:
176
+ try:
177
+ view.viewTransformChanged.disconnect(slot)
178
+ except Exception:
179
+ pass
180
+ g = self.by_view.pop(view, None)
181
+ if g and g in self.groups:
182
+ self.groups[g].discard(view)
183
+ if not self.groups[g]:
184
+ self.groups.pop(g, None)
185
+
186
+ def set_view_group(self, view, name_or_none: Optional[str]):
187
+ """Assign a view to a named group, or remove from groups if None."""
188
+ old = self.by_view.pop(view, None)
189
+ if old and old in self.groups:
190
+ self.groups[old].discard(view)
191
+ if not self.groups[old]:
192
+ self.groups.pop(old, None)
193
+ if name_or_none:
194
+ self.groups.setdefault(name_or_none, set()).add(view)
195
+ self.by_view[view] = name_or_none
196
+
197
+ def group_of(self, view) -> Optional[str]:
198
+ """Return the group name for a view, or None."""
199
+ return self.by_view.get(view)
200
+
201
+ def _on_view_transform_from(self, src_view, scale: float, hval: float, vval: float):
202
+ """Handle transform change from a source view - broadcast to group."""
203
+ if self._broadcasting:
204
+ return
205
+ g = self.by_view.get(src_view)
206
+ if not g:
207
+ return
208
+
209
+ self._broadcasting = True
210
+ try:
211
+ for tgt in tuple(self.groups.get(g, ())):
212
+ if tgt is src_view:
213
+ continue
214
+ try:
215
+ # Skip deleted / half-torn-down views
216
+ from PyQt6 import sip as _sip
217
+ if _sip.isdeleted(tgt):
218
+ continue
219
+ except Exception:
220
+ pass
221
+
222
+ hb = tgt.scroll.horizontalScrollBar().value()
223
+ vb = tgt.scroll.verticalScrollBar().value()
224
+ if abs(scale - tgt.scale) < 1e-9 and int(hval) == hb and int(vval) == vb:
225
+ continue
226
+
227
+ try:
228
+ tgt.set_view_transform(scale, hval, vval, from_link=True)
229
+ except Exception as ex:
230
+ print("[link] apply failed:", ex)
231
+ finally:
232
+ self._broadcasting = False
233
+
234
+
235
+ class ConsoleListWidget(QListWidget):
236
+ """
237
+ QListWidget with a context menu for console output:
238
+ - Select All
239
+ - Copy Selected
240
+ - Copy All
241
+ - Clear
242
+ """
243
+
244
+ def __init__(self, parent=None):
245
+ super().__init__(parent)
246
+ self.setContextMenuPolicy(Qt.ContextMenuPolicy.DefaultContextMenu)
247
+
248
+ def _selected_lines(self) -> list[str]:
249
+ return [itm.text() for itm in self.selectedItems()]
250
+
251
+ def _all_lines(self) -> list[str]:
252
+ return [self.item(i).text() for i in range(self.count())]
253
+
254
+ def _copy_text(self, lines: list[str]):
255
+ if not lines:
256
+ return
257
+ text = "\n".join(lines)
258
+ cb = QApplication.clipboard()
259
+ cb.setText(text)
260
+
261
+ def contextMenuEvent(self, event):
262
+ menu = QMenu(self)
263
+
264
+ act_select_all = menu.addAction("Select All")
265
+ act_copy_sel = menu.addAction("Copy Selected")
266
+ act_copy_all = menu.addAction("Copy All")
267
+ menu.addSeparator()
268
+ act_clear = menu.addAction("Clear")
269
+
270
+ action = menu.exec(event.globalPos())
271
+ if action is None:
272
+ return
273
+
274
+ if action is act_select_all:
275
+ self.selectAll()
276
+ elif action is act_copy_sel:
277
+ self._copy_text(self._selected_lines())
278
+ elif action is act_copy_all:
279
+ self._copy_text(self._all_lines())
280
+ elif action is act_clear:
281
+ self.clear()
282
+
283
+
284
+ class QtLogStream(QObject):
285
+ """
286
+ QObject that intercepts writes (e.g., from stdout/stderr) and emits
287
+ them as Qt signals, while still forwarding to the original stream.
288
+ """
289
+ text_emitted = pyqtSignal(str)
290
+
291
+ def __init__(self, orig_stream, parent=None):
292
+ super().__init__(parent)
293
+ self._orig = orig_stream
294
+
295
+ def write(self, text: str):
296
+ # Still write to the original stream
297
+ try:
298
+ if self._orig is not None:
299
+ self._orig.write(text)
300
+ except Exception:
301
+ pass
302
+ # Mirror into Qt
303
+ if text:
304
+ self.text_emitted.emit(text)
305
+
306
+ def flush(self):
307
+ try:
308
+ if self._orig is not None:
309
+ self._orig.flush()
310
+ except Exception:
311
+ pass
312
+
313
+
314
+ class _DocProxy:
315
+ """
316
+ Lightweight proxy that always resolves to the current document
317
+ for a view (ROI when a Preview/ROI tab is active, else base doc).
318
+ All attribute gets/sets forward to the currently-active target.
319
+ """
320
+ __slots__ = ("_dm", "_view_ref", "_base_doc")
321
+
322
+ def __init__(self, doc_manager, view, base_doc):
323
+ self._dm = doc_manager
324
+ self._view_ref = weakref.ref(view)
325
+ self._base_doc = base_doc
326
+
327
+ def _target(self):
328
+ view = self._view_ref()
329
+ if view is None:
330
+ return self._base_doc
331
+ doc = self._dm.get_document_for_view(view)
332
+ return doc or self._base_doc
333
+
334
+ def __getattr__(self, name):
335
+ return getattr(self._target(), name)
336
+
337
+ def __setattr__(self, name, value):
338
+ if name in _DocProxy.__slots__:
339
+ object.__setattr__(self, name, value)
340
+ else:
341
+ setattr(self._target(), name, value)
342
+
343
+ def __repr__(self):
344
+ tgt = self._target()
345
+ try:
346
+ dn = tgt.display_name() if hasattr(tgt, "display_name") else "<doc>"
347
+ except Exception:
348
+ dn = "<doc>"
349
+ return f"<DocProxy → {dn}>"
350
+
351
+
352
+ # Role constant for action data
353
+ ROLE_ACTION = Qt.ItemDataRole.UserRole + 1