setiastrosuitepro 1.6.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (394) hide show
  1. setiastro/__init__.py +2 -0
  2. setiastro/data/SASP_data.fits +0 -0
  3. setiastro/data/catalogs/List_of_Galaxies_with_Distances_Gly.csv +488 -0
  4. setiastro/data/catalogs/astrobin_filters.csv +890 -0
  5. setiastro/data/catalogs/astrobin_filters_page1_local.csv +51 -0
  6. setiastro/data/catalogs/cali2.csv +63 -0
  7. setiastro/data/catalogs/cali2color.csv +65 -0
  8. setiastro/data/catalogs/celestial_catalog - original.csv +16471 -0
  9. setiastro/data/catalogs/celestial_catalog.csv +24031 -0
  10. setiastro/data/catalogs/detected_stars.csv +24784 -0
  11. setiastro/data/catalogs/fits_header_data.csv +46 -0
  12. setiastro/data/catalogs/test.csv +8 -0
  13. setiastro/data/catalogs/updated_celestial_catalog.csv +16471 -0
  14. setiastro/images/Astro_Spikes.png +0 -0
  15. setiastro/images/Background_startup.jpg +0 -0
  16. setiastro/images/HRDiagram.png +0 -0
  17. setiastro/images/LExtract.png +0 -0
  18. setiastro/images/LInsert.png +0 -0
  19. setiastro/images/Oxygenation-atm-2.svg.png +0 -0
  20. setiastro/images/RGB080604.png +0 -0
  21. setiastro/images/abeicon.png +0 -0
  22. setiastro/images/aberration.png +0 -0
  23. setiastro/images/acv_icon.png +0 -0
  24. setiastro/images/andromedatry.png +0 -0
  25. setiastro/images/andromedatry_satellited.png +0 -0
  26. setiastro/images/annotated.png +0 -0
  27. setiastro/images/aperture.png +0 -0
  28. setiastro/images/astrosuite.ico +0 -0
  29. setiastro/images/astrosuite.png +0 -0
  30. setiastro/images/astrosuitepro.icns +0 -0
  31. setiastro/images/astrosuitepro.ico +0 -0
  32. setiastro/images/astrosuitepro.png +0 -0
  33. setiastro/images/background.png +0 -0
  34. setiastro/images/background2.png +0 -0
  35. setiastro/images/benchmark.png +0 -0
  36. setiastro/images/big_moon_stabilizer_timeline.png +0 -0
  37. setiastro/images/big_moon_stabilizer_timeline_clean.png +0 -0
  38. setiastro/images/blaster.png +0 -0
  39. setiastro/images/blink.png +0 -0
  40. setiastro/images/clahe.png +0 -0
  41. setiastro/images/collage.png +0 -0
  42. setiastro/images/colorwheel.png +0 -0
  43. setiastro/images/contsub.png +0 -0
  44. setiastro/images/convo.png +0 -0
  45. setiastro/images/copyslot.png +0 -0
  46. setiastro/images/cosmic.png +0 -0
  47. setiastro/images/cosmicsat.png +0 -0
  48. setiastro/images/crop1.png +0 -0
  49. setiastro/images/cropicon.png +0 -0
  50. setiastro/images/curves.png +0 -0
  51. setiastro/images/cvs.png +0 -0
  52. setiastro/images/debayer.png +0 -0
  53. setiastro/images/denoise_cnn_custom.png +0 -0
  54. setiastro/images/denoise_cnn_graph.png +0 -0
  55. setiastro/images/disk.png +0 -0
  56. setiastro/images/dse.png +0 -0
  57. setiastro/images/exoicon.png +0 -0
  58. setiastro/images/eye.png +0 -0
  59. setiastro/images/first_quarter.png +0 -0
  60. setiastro/images/fliphorizontal.png +0 -0
  61. setiastro/images/flipvertical.png +0 -0
  62. setiastro/images/font.png +0 -0
  63. setiastro/images/freqsep.png +0 -0
  64. setiastro/images/full_moon.png +0 -0
  65. setiastro/images/functionbundle.png +0 -0
  66. setiastro/images/graxpert.png +0 -0
  67. setiastro/images/green.png +0 -0
  68. setiastro/images/gridicon.png +0 -0
  69. setiastro/images/halo.png +0 -0
  70. setiastro/images/hdr.png +0 -0
  71. setiastro/images/histogram.png +0 -0
  72. setiastro/images/hubble.png +0 -0
  73. setiastro/images/imagecombine.png +0 -0
  74. setiastro/images/invert.png +0 -0
  75. setiastro/images/isophote.png +0 -0
  76. setiastro/images/isophote_demo_figure.png +0 -0
  77. setiastro/images/isophote_demo_image.png +0 -0
  78. setiastro/images/isophote_demo_model.png +0 -0
  79. setiastro/images/isophote_demo_residual.png +0 -0
  80. setiastro/images/jwstpupil.png +0 -0
  81. setiastro/images/last_quarter.png +0 -0
  82. setiastro/images/linearfit.png +0 -0
  83. setiastro/images/livestacking.png +0 -0
  84. setiastro/images/mask.png +0 -0
  85. setiastro/images/maskapply.png +0 -0
  86. setiastro/images/maskcreate.png +0 -0
  87. setiastro/images/maskremove.png +0 -0
  88. setiastro/images/morpho.png +0 -0
  89. setiastro/images/mosaic.png +0 -0
  90. setiastro/images/multiscale_decomp.png +0 -0
  91. setiastro/images/nbtorgb.png +0 -0
  92. setiastro/images/neutral.png +0 -0
  93. setiastro/images/new_moon.png +0 -0
  94. setiastro/images/nuke.png +0 -0
  95. setiastro/images/openfile.png +0 -0
  96. setiastro/images/pedestal.png +0 -0
  97. setiastro/images/pen.png +0 -0
  98. setiastro/images/pixelmath.png +0 -0
  99. setiastro/images/platesolve.png +0 -0
  100. setiastro/images/ppp.png +0 -0
  101. setiastro/images/pro.png +0 -0
  102. setiastro/images/project.png +0 -0
  103. setiastro/images/psf.png +0 -0
  104. setiastro/images/redo.png +0 -0
  105. setiastro/images/redoicon.png +0 -0
  106. setiastro/images/rescale.png +0 -0
  107. setiastro/images/rgbalign.png +0 -0
  108. setiastro/images/rgbcombo.png +0 -0
  109. setiastro/images/rgbextract.png +0 -0
  110. setiastro/images/rotate180.png +0 -0
  111. setiastro/images/rotatearbitrary.png +0 -0
  112. setiastro/images/rotateclockwise.png +0 -0
  113. setiastro/images/rotatecounterclockwise.png +0 -0
  114. setiastro/images/satellite.png +0 -0
  115. setiastro/images/script.png +0 -0
  116. setiastro/images/selectivecolor.png +0 -0
  117. setiastro/images/simbad.png +0 -0
  118. setiastro/images/slot0.png +0 -0
  119. setiastro/images/slot1.png +0 -0
  120. setiastro/images/slot2.png +0 -0
  121. setiastro/images/slot3.png +0 -0
  122. setiastro/images/slot4.png +0 -0
  123. setiastro/images/slot5.png +0 -0
  124. setiastro/images/slot6.png +0 -0
  125. setiastro/images/slot7.png +0 -0
  126. setiastro/images/slot8.png +0 -0
  127. setiastro/images/slot9.png +0 -0
  128. setiastro/images/spcc.png +0 -0
  129. setiastro/images/spin_precession_vs_lunar_distance.png +0 -0
  130. setiastro/images/spinner.gif +0 -0
  131. setiastro/images/stacking.png +0 -0
  132. setiastro/images/staradd.png +0 -0
  133. setiastro/images/staralign.png +0 -0
  134. setiastro/images/starnet.png +0 -0
  135. setiastro/images/starregistration.png +0 -0
  136. setiastro/images/starspike.png +0 -0
  137. setiastro/images/starstretch.png +0 -0
  138. setiastro/images/statstretch.png +0 -0
  139. setiastro/images/supernova.png +0 -0
  140. setiastro/images/uhs.png +0 -0
  141. setiastro/images/undoicon.png +0 -0
  142. setiastro/images/upscale.png +0 -0
  143. setiastro/images/viewbundle.png +0 -0
  144. setiastro/images/waning_crescent_1.png +0 -0
  145. setiastro/images/waning_crescent_2.png +0 -0
  146. setiastro/images/waning_crescent_3.png +0 -0
  147. setiastro/images/waning_crescent_4.png +0 -0
  148. setiastro/images/waning_crescent_5.png +0 -0
  149. setiastro/images/waning_gibbous_1.png +0 -0
  150. setiastro/images/waning_gibbous_2.png +0 -0
  151. setiastro/images/waning_gibbous_3.png +0 -0
  152. setiastro/images/waning_gibbous_4.png +0 -0
  153. setiastro/images/waning_gibbous_5.png +0 -0
  154. setiastro/images/waxing_crescent_1.png +0 -0
  155. setiastro/images/waxing_crescent_2.png +0 -0
  156. setiastro/images/waxing_crescent_3.png +0 -0
  157. setiastro/images/waxing_crescent_4.png +0 -0
  158. setiastro/images/waxing_crescent_5.png +0 -0
  159. setiastro/images/waxing_gibbous_1.png +0 -0
  160. setiastro/images/waxing_gibbous_2.png +0 -0
  161. setiastro/images/waxing_gibbous_3.png +0 -0
  162. setiastro/images/waxing_gibbous_4.png +0 -0
  163. setiastro/images/waxing_gibbous_5.png +0 -0
  164. setiastro/images/whitebalance.png +0 -0
  165. setiastro/images/wimi_icon_256x256.png +0 -0
  166. setiastro/images/wimilogo.png +0 -0
  167. setiastro/images/wims.png +0 -0
  168. setiastro/images/wrench_icon.png +0 -0
  169. setiastro/images/xisfliberator.png +0 -0
  170. setiastro/qml/ResourceMonitor.qml +128 -0
  171. setiastro/saspro/__init__.py +20 -0
  172. setiastro/saspro/__main__.py +964 -0
  173. setiastro/saspro/_generated/__init__.py +7 -0
  174. setiastro/saspro/_generated/build_info.py +3 -0
  175. setiastro/saspro/abe.py +1379 -0
  176. setiastro/saspro/abe_preset.py +196 -0
  177. setiastro/saspro/aberration_ai.py +910 -0
  178. setiastro/saspro/aberration_ai_preset.py +224 -0
  179. setiastro/saspro/accel_installer.py +218 -0
  180. setiastro/saspro/accel_workers.py +30 -0
  181. setiastro/saspro/acv_exporter.py +379 -0
  182. setiastro/saspro/add_stars.py +627 -0
  183. setiastro/saspro/astrobin_exporter.py +1010 -0
  184. setiastro/saspro/astrospike.py +153 -0
  185. setiastro/saspro/astrospike_python.py +1841 -0
  186. setiastro/saspro/autostretch.py +198 -0
  187. setiastro/saspro/backgroundneutral.py +639 -0
  188. setiastro/saspro/batch_convert.py +328 -0
  189. setiastro/saspro/batch_renamer.py +522 -0
  190. setiastro/saspro/blemish_blaster.py +494 -0
  191. setiastro/saspro/blink_comparator_pro.py +3149 -0
  192. setiastro/saspro/bundles.py +61 -0
  193. setiastro/saspro/bundles_dock.py +114 -0
  194. setiastro/saspro/cheat_sheet.py +213 -0
  195. setiastro/saspro/clahe.py +371 -0
  196. setiastro/saspro/comet_stacking.py +1442 -0
  197. setiastro/saspro/common_tr.py +107 -0
  198. setiastro/saspro/config.py +38 -0
  199. setiastro/saspro/config_bootstrap.py +40 -0
  200. setiastro/saspro/config_manager.py +316 -0
  201. setiastro/saspro/continuum_subtract.py +1620 -0
  202. setiastro/saspro/convo.py +1403 -0
  203. setiastro/saspro/convo_preset.py +414 -0
  204. setiastro/saspro/copyastro.py +190 -0
  205. setiastro/saspro/cosmicclarity.py +1593 -0
  206. setiastro/saspro/cosmicclarity_preset.py +407 -0
  207. setiastro/saspro/crop_dialog_pro.py +1005 -0
  208. setiastro/saspro/crop_preset.py +189 -0
  209. setiastro/saspro/curve_editor_pro.py +2608 -0
  210. setiastro/saspro/curves_preset.py +375 -0
  211. setiastro/saspro/debayer.py +673 -0
  212. setiastro/saspro/debug_utils.py +29 -0
  213. setiastro/saspro/dnd_mime.py +35 -0
  214. setiastro/saspro/doc_manager.py +2727 -0
  215. setiastro/saspro/exoplanet_detector.py +2258 -0
  216. setiastro/saspro/file_utils.py +284 -0
  217. setiastro/saspro/fitsmodifier.py +748 -0
  218. setiastro/saspro/fix_bom.py +32 -0
  219. setiastro/saspro/free_torch_memory.py +48 -0
  220. setiastro/saspro/frequency_separation.py +1352 -0
  221. setiastro/saspro/function_bundle.py +1596 -0
  222. setiastro/saspro/generate_translations.py +3092 -0
  223. setiastro/saspro/ghs_dialog_pro.py +728 -0
  224. setiastro/saspro/ghs_preset.py +284 -0
  225. setiastro/saspro/graxpert.py +638 -0
  226. setiastro/saspro/graxpert_preset.py +287 -0
  227. setiastro/saspro/gui/__init__.py +0 -0
  228. setiastro/saspro/gui/main_window.py +8928 -0
  229. setiastro/saspro/gui/mixins/__init__.py +33 -0
  230. setiastro/saspro/gui/mixins/dock_mixin.py +375 -0
  231. setiastro/saspro/gui/mixins/file_mixin.py +450 -0
  232. setiastro/saspro/gui/mixins/geometry_mixin.py +503 -0
  233. setiastro/saspro/gui/mixins/header_mixin.py +441 -0
  234. setiastro/saspro/gui/mixins/mask_mixin.py +421 -0
  235. setiastro/saspro/gui/mixins/menu_mixin.py +391 -0
  236. setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
  237. setiastro/saspro/gui/mixins/toolbar_mixin.py +1824 -0
  238. setiastro/saspro/gui/mixins/update_mixin.py +323 -0
  239. setiastro/saspro/gui/mixins/view_mixin.py +477 -0
  240. setiastro/saspro/gui/statistics_dialog.py +47 -0
  241. setiastro/saspro/halobgon.py +492 -0
  242. setiastro/saspro/header_viewer.py +448 -0
  243. setiastro/saspro/headless_utils.py +88 -0
  244. setiastro/saspro/histogram.py +760 -0
  245. setiastro/saspro/history_explorer.py +941 -0
  246. setiastro/saspro/i18n.py +168 -0
  247. setiastro/saspro/image_combine.py +421 -0
  248. setiastro/saspro/image_peeker_pro.py +1608 -0
  249. setiastro/saspro/imageops/__init__.py +37 -0
  250. setiastro/saspro/imageops/mdi_snap.py +292 -0
  251. setiastro/saspro/imageops/scnr.py +36 -0
  252. setiastro/saspro/imageops/starbasedwhitebalance.py +210 -0
  253. setiastro/saspro/imageops/stretch.py +236 -0
  254. setiastro/saspro/isophote.py +1186 -0
  255. setiastro/saspro/layers.py +208 -0
  256. setiastro/saspro/layers_dock.py +714 -0
  257. setiastro/saspro/lazy_imports.py +193 -0
  258. setiastro/saspro/legacy/__init__.py +2 -0
  259. setiastro/saspro/legacy/image_manager.py +2360 -0
  260. setiastro/saspro/legacy/numba_utils.py +3676 -0
  261. setiastro/saspro/legacy/xisf.py +1213 -0
  262. setiastro/saspro/linear_fit.py +537 -0
  263. setiastro/saspro/live_stacking.py +1854 -0
  264. setiastro/saspro/log_bus.py +5 -0
  265. setiastro/saspro/logging_config.py +460 -0
  266. setiastro/saspro/luminancerecombine.py +510 -0
  267. setiastro/saspro/main_helpers.py +201 -0
  268. setiastro/saspro/mask_creation.py +1090 -0
  269. setiastro/saspro/masks_core.py +56 -0
  270. setiastro/saspro/mdi_widgets.py +353 -0
  271. setiastro/saspro/memory_utils.py +666 -0
  272. setiastro/saspro/metadata_patcher.py +75 -0
  273. setiastro/saspro/mfdeconv.py +3909 -0
  274. setiastro/saspro/mfdeconv_earlystop.py +71 -0
  275. setiastro/saspro/mfdeconvcudnn.py +3312 -0
  276. setiastro/saspro/mfdeconvsport.py +2459 -0
  277. setiastro/saspro/minorbodycatalog.py +567 -0
  278. setiastro/saspro/morphology.py +411 -0
  279. setiastro/saspro/multiscale_decomp.py +1751 -0
  280. setiastro/saspro/nbtorgb_stars.py +541 -0
  281. setiastro/saspro/numba_utils.py +3145 -0
  282. setiastro/saspro/numba_warmup.py +141 -0
  283. setiastro/saspro/ops/__init__.py +9 -0
  284. setiastro/saspro/ops/command_help_dialog.py +623 -0
  285. setiastro/saspro/ops/command_runner.py +217 -0
  286. setiastro/saspro/ops/commands.py +1594 -0
  287. setiastro/saspro/ops/script_editor.py +1105 -0
  288. setiastro/saspro/ops/scripts.py +1476 -0
  289. setiastro/saspro/ops/settings.py +637 -0
  290. setiastro/saspro/parallel_utils.py +554 -0
  291. setiastro/saspro/pedestal.py +121 -0
  292. setiastro/saspro/perfect_palette_picker.py +1105 -0
  293. setiastro/saspro/pipeline.py +110 -0
  294. setiastro/saspro/pixelmath.py +1604 -0
  295. setiastro/saspro/plate_solver.py +2480 -0
  296. setiastro/saspro/project_io.py +797 -0
  297. setiastro/saspro/psf_utils.py +136 -0
  298. setiastro/saspro/psf_viewer.py +631 -0
  299. setiastro/saspro/pyi_rthook_astroquery.py +95 -0
  300. setiastro/saspro/remove_green.py +331 -0
  301. setiastro/saspro/remove_stars.py +1599 -0
  302. setiastro/saspro/remove_stars_preset.py +446 -0
  303. setiastro/saspro/resources.py +570 -0
  304. setiastro/saspro/rgb_combination.py +208 -0
  305. setiastro/saspro/rgb_extract.py +19 -0
  306. setiastro/saspro/rgbalign.py +727 -0
  307. setiastro/saspro/runtime_imports.py +7 -0
  308. setiastro/saspro/runtime_torch.py +754 -0
  309. setiastro/saspro/save_options.py +73 -0
  310. setiastro/saspro/selective_color.py +1614 -0
  311. setiastro/saspro/sfcc.py +1530 -0
  312. setiastro/saspro/shortcuts.py +3125 -0
  313. setiastro/saspro/signature_insert.py +1106 -0
  314. setiastro/saspro/stacking_suite.py +19069 -0
  315. setiastro/saspro/star_alignment.py +7383 -0
  316. setiastro/saspro/star_alignment_preset.py +329 -0
  317. setiastro/saspro/star_metrics.py +49 -0
  318. setiastro/saspro/star_spikes.py +769 -0
  319. setiastro/saspro/star_stretch.py +542 -0
  320. setiastro/saspro/stat_stretch.py +554 -0
  321. setiastro/saspro/status_log_dock.py +78 -0
  322. setiastro/saspro/subwindow.py +3523 -0
  323. setiastro/saspro/supernovaasteroidhunter.py +1719 -0
  324. setiastro/saspro/swap_manager.py +134 -0
  325. setiastro/saspro/torch_backend.py +89 -0
  326. setiastro/saspro/torch_rejection.py +434 -0
  327. setiastro/saspro/translations/all_source_strings.json +4726 -0
  328. setiastro/saspro/translations/ar_translations.py +4096 -0
  329. setiastro/saspro/translations/de_translations.py +3728 -0
  330. setiastro/saspro/translations/es_translations.py +4169 -0
  331. setiastro/saspro/translations/fr_translations.py +4090 -0
  332. setiastro/saspro/translations/hi_translations.py +3803 -0
  333. setiastro/saspro/translations/integrate_translations.py +271 -0
  334. setiastro/saspro/translations/it_translations.py +4728 -0
  335. setiastro/saspro/translations/ja_translations.py +3834 -0
  336. setiastro/saspro/translations/pt_translations.py +3847 -0
  337. setiastro/saspro/translations/ru_translations.py +3082 -0
  338. setiastro/saspro/translations/saspro_ar.qm +0 -0
  339. setiastro/saspro/translations/saspro_ar.ts +16019 -0
  340. setiastro/saspro/translations/saspro_de.qm +0 -0
  341. setiastro/saspro/translations/saspro_de.ts +14548 -0
  342. setiastro/saspro/translations/saspro_es.qm +0 -0
  343. setiastro/saspro/translations/saspro_es.ts +16202 -0
  344. setiastro/saspro/translations/saspro_fr.qm +0 -0
  345. setiastro/saspro/translations/saspro_fr.ts +15870 -0
  346. setiastro/saspro/translations/saspro_hi.qm +0 -0
  347. setiastro/saspro/translations/saspro_hi.ts +14855 -0
  348. setiastro/saspro/translations/saspro_it.qm +0 -0
  349. setiastro/saspro/translations/saspro_it.ts +19046 -0
  350. setiastro/saspro/translations/saspro_ja.qm +0 -0
  351. setiastro/saspro/translations/saspro_ja.ts +14980 -0
  352. setiastro/saspro/translations/saspro_pt.qm +0 -0
  353. setiastro/saspro/translations/saspro_pt.ts +15024 -0
  354. setiastro/saspro/translations/saspro_ru.qm +0 -0
  355. setiastro/saspro/translations/saspro_ru.ts +11835 -0
  356. setiastro/saspro/translations/saspro_sw.qm +0 -0
  357. setiastro/saspro/translations/saspro_sw.ts +15237 -0
  358. setiastro/saspro/translations/saspro_uk.qm +0 -0
  359. setiastro/saspro/translations/saspro_uk.ts +15248 -0
  360. setiastro/saspro/translations/saspro_zh.qm +0 -0
  361. setiastro/saspro/translations/saspro_zh.ts +15289 -0
  362. setiastro/saspro/translations/sw_translations.py +3897 -0
  363. setiastro/saspro/translations/uk_translations.py +3929 -0
  364. setiastro/saspro/translations/zh_translations.py +3910 -0
  365. setiastro/saspro/versioning.py +77 -0
  366. setiastro/saspro/view_bundle.py +1558 -0
  367. setiastro/saspro/wavescale_hdr.py +648 -0
  368. setiastro/saspro/wavescale_hdr_preset.py +101 -0
  369. setiastro/saspro/wavescalede.py +683 -0
  370. setiastro/saspro/wavescalede_preset.py +230 -0
  371. setiastro/saspro/wcs_update.py +374 -0
  372. setiastro/saspro/whitebalance.py +540 -0
  373. setiastro/saspro/widgets/__init__.py +48 -0
  374. setiastro/saspro/widgets/common_utilities.py +306 -0
  375. setiastro/saspro/widgets/graphics_views.py +122 -0
  376. setiastro/saspro/widgets/image_utils.py +518 -0
  377. setiastro/saspro/widgets/minigame/game.js +991 -0
  378. setiastro/saspro/widgets/minigame/index.html +53 -0
  379. setiastro/saspro/widgets/minigame/style.css +241 -0
  380. setiastro/saspro/widgets/preview_dialogs.py +280 -0
  381. setiastro/saspro/widgets/resource_monitor.py +313 -0
  382. setiastro/saspro/widgets/spinboxes.py +290 -0
  383. setiastro/saspro/widgets/themed_buttons.py +13 -0
  384. setiastro/saspro/widgets/wavelet_utils.py +331 -0
  385. setiastro/saspro/wimi.py +7367 -0
  386. setiastro/saspro/wims.py +588 -0
  387. setiastro/saspro/window_shelf.py +185 -0
  388. setiastro/saspro/xisf.py +1213 -0
  389. setiastrosuitepro-1.6.7.dist-info/METADATA +279 -0
  390. setiastrosuitepro-1.6.7.dist-info/RECORD +394 -0
  391. setiastrosuitepro-1.6.7.dist-info/WHEEL +4 -0
  392. setiastrosuitepro-1.6.7.dist-info/entry_points.txt +6 -0
  393. setiastrosuitepro-1.6.7.dist-info/licenses/LICENSE +674 -0
  394. setiastrosuitepro-1.6.7.dist-info/licenses/license.txt +2580 -0
@@ -0,0 +1,542 @@
1
+ # pro/star_stretch.py
2
+ from __future__ import annotations
3
+ import os
4
+ import numpy as np
5
+
6
+ from PyQt6.QtCore import Qt, QThread, pyqtSignal, QEvent, QPointF
7
+ from PyQt6.QtWidgets import (
8
+ QDialog, QVBoxLayout, QHBoxLayout, QLabel, QSlider, QCheckBox,
9
+ QPushButton, QScrollArea, QWidget, QMessageBox
10
+ )
11
+ from PyQt6.QtGui import QPixmap, QImage, QMovie
12
+ from setiastro.saspro.widgets.themed_buttons import themed_toolbtn
13
+
14
+ # Shared utilities
15
+ from setiastro.saspro.widgets.image_utils import to_float01 as _to_float01
16
+
17
+ # --- use your Numba kernels; fall back to pure numpy SCNR if needed ----
18
+ try:
19
+ from setiastro.saspro.legacy.numba_utils import applyPixelMath_numba, applySCNR_numba
20
+ _HAS_NUMBA = True
21
+ except Exception:
22
+ _HAS_NUMBA = False
23
+ # Fallback SCNR (Average Neutral) if legacy.numba_utils is unavailable
24
+ def applySCNR_numba(image_array: np.ndarray) -> np.ndarray:
25
+ img = image_array.astype(np.float32, copy=False)
26
+ if img.ndim != 3 or img.shape[2] != 3:
27
+ return img
28
+ r = img[..., 0]; g = img[..., 1]; b = img[..., 2]
29
+ g2 = np.minimum(g, 0.5 * (r + b))
30
+ out = img.copy()
31
+ out[..., 1] = g2
32
+ return np.clip(out, 0.0, 1.0)
33
+
34
+ # ---- small helpers --------------------------------------------------------
35
+
36
+ def _as_qimage_rgb8(float01: np.ndarray) -> QImage:
37
+ f = np.asarray(float01, dtype=np.float32)
38
+
39
+ # Ensure 3-channel RGB for preview
40
+ if f.ndim == 2:
41
+ f = np.stack([f]*3, axis=-1)
42
+ elif f.ndim == 3 and f.shape[2] == 1:
43
+ f = np.repeat(f, 3, axis=2)
44
+
45
+ # [0,1] -> uint8 and force C-contiguous
46
+ buf8 = (np.clip(f, 0.0, 1.0) * 255.0).astype(np.uint8, copy=False)
47
+ buf8 = np.ascontiguousarray(buf8)
48
+ h, w, _ = buf8.shape
49
+ bpl = int(buf8.strides[0])
50
+
51
+ # Prefer zero-copy via sip pointer if available; fall back to bytes
52
+ try:
53
+ from PyQt6 import sip
54
+ qimg = QImage(sip.voidptr(buf8.ctypes.data), w, h, bpl, QImage.Format.Format_RGB888)
55
+ qimg._keepalive = buf8 # keep numpy alive while qimg exists
56
+ return qimg.copy() # detach so Qt owns the pixels (safe for QPixmap.fromImage)
57
+ except Exception:
58
+ data = buf8.tobytes()
59
+ qimg = QImage(data, w, h, bpl, QImage.Format.Format_RGB888)
60
+ return qimg.copy() # detach to avoid lifetime issues
61
+
62
+ def _saturation_boost(rgb01: np.ndarray, amount: float) -> np.ndarray:
63
+ """
64
+ Fast saturation-like boost without HSV dependency:
65
+ C' = mean + (C - mean) * amount
66
+ """
67
+ if rgb01.ndim != 3 or rgb01.shape[2] != 3:
68
+ return rgb01
69
+ mean = rgb01.mean(axis=2, keepdims=True)
70
+ out = mean + (rgb01 - mean) * float(amount)
71
+ return np.clip(out, 0.0, 1.0)
72
+
73
+ # ---- background thread ----------------------------------------------------
74
+
75
+ class _StarStretchWorker(QThread):
76
+ preview_ready = pyqtSignal(object) # np.ndarray float32 0..1
77
+
78
+ def __init__(self, image: np.ndarray, stretch_factor: float, sat_amount: float, do_scnr: bool):
79
+ super().__init__()
80
+ self.image = image
81
+ self.stretch_factor = float(stretch_factor) # this is the "amount" for your pixel math
82
+ self.sat_amount = float(sat_amount)
83
+ self.do_scnr = bool(do_scnr)
84
+
85
+ def run(self):
86
+ imgf = _to_float01(self.image)
87
+ if imgf is None:
88
+ return
89
+
90
+ # If grayscale, make it 3-channel to keep the kernels happy, then restore shape
91
+ orig_ndim = imgf.ndim
92
+ need_collapse = False
93
+ if imgf.ndim == 2:
94
+ imgf = np.stack([imgf]*3, axis=-1)
95
+ need_collapse = True
96
+ elif imgf.ndim == 3 and imgf.shape[2] == 1:
97
+ imgf = np.repeat(imgf, 3, axis=2)
98
+ need_collapse = True
99
+
100
+ # --- Star Stretch: your Numba pixel math ---
101
+ # amount maps to the SASv2 slider (0..8); kernel uses: f=3**amount
102
+ out = applyPixelMath_numba(imgf.astype(np.float32, copy=False), self.stretch_factor)
103
+
104
+ # --- Optional saturation (RGB only) ---
105
+ if out.ndim == 3 and out.shape[2] == 3 and abs(self.sat_amount - 1.0) > 1e-6:
106
+ out = _saturation_boost(out, self.sat_amount)
107
+
108
+ # --- Optional SCNR (Average Neutral via your Numba kernel) ---
109
+ if self.do_scnr and out.ndim == 3 and out.shape[2] == 3:
110
+ out = applySCNR_numba(out.astype(np.float32, copy=False))
111
+
112
+ # collapse back to mono if we expanded earlier
113
+ if need_collapse:
114
+ out = out[..., 0]
115
+
116
+ self.preview_ready.emit(out.astype(np.float32, copy=False))
117
+
118
+ # ---- dialog ---------------------------------------------------------------
119
+
120
+ class StarStretchDialog(QDialog):
121
+ """
122
+ Star Stretch for SASpro.
123
+ - Works on active ImageDocument (passed in).
124
+ - Preview is computed in background thread.
125
+ - 'Apply to Document' records history via doc.apply_edit(..., step_name="Star Stretch").
126
+ """
127
+ def __init__(self, parent, document):
128
+ super().__init__(parent)
129
+ self.setWindowTitle(self.tr("Star Stretch"))
130
+ self.setWindowFlag(Qt.WindowType.Window, True)
131
+ self.setWindowModality(Qt.WindowModality.NonModal)
132
+ self.setModal(False)
133
+ self._main = parent
134
+ self.doc = document
135
+ self._preview: np.ndarray | None = None
136
+ try:
137
+ self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
138
+ except Exception:
139
+ pass # older PyQt6 versions
140
+ # Connect to active document change signal
141
+ self._connected_current_doc_changed = False
142
+ if hasattr(self._main, "currentDocumentChanged"):
143
+ try:
144
+ self._main.currentDocumentChanged.connect(self._on_active_doc_changed)
145
+ self._connected_current_doc_changed = True
146
+ except Exception:
147
+ self._connected_current_doc_changed = False
148
+
149
+ self._pix: QPixmap | None = None
150
+ self._zoom = 0.25
151
+ self._panning = False
152
+ self._pan_start = QPointF()
153
+ self._apply_when_ready = False
154
+
155
+ # UI
156
+ main = QHBoxLayout(self)
157
+
158
+ # Left column (controls)
159
+ left = QVBoxLayout()
160
+ info = QLabel(
161
+ "Instructions:\n"
162
+ "1) Adjust stretch and options.\n"
163
+ "2) Preview the result.\n"
164
+ "3) Apply to the current document."
165
+ )
166
+ info.setWordWrap(True)
167
+ left.addWidget(info)
168
+
169
+ # Stretch slider (0..8.00)
170
+ self.lbl_st = QLabel(self.tr("Stretch Amount:") + " 5.00")
171
+ self.sld_st = QSlider(Qt.Orientation.Horizontal)
172
+ self.sld_st.setRange(0, 800)
173
+ self.sld_st.setValue(500)
174
+ self.sld_st.valueChanged.connect(self._on_stretch_changed)
175
+ left.addWidget(self.lbl_st)
176
+ left.addWidget(self.sld_st)
177
+
178
+ # Saturation slider (0..2.00)
179
+ self.lbl_sat = QLabel(self.tr("Color Boost:") + " 1.00")
180
+ self.sld_sat = QSlider(Qt.Orientation.Horizontal)
181
+ self.sld_sat.setRange(0, 200)
182
+ self.sld_sat.setValue(100)
183
+ self.sld_sat.valueChanged.connect(self._on_sat_changed)
184
+ left.addWidget(self.lbl_sat)
185
+ left.addWidget(self.sld_sat)
186
+
187
+ # SCNR checkbox
188
+ self.chk_scnr = QCheckBox(self.tr("Remove Green via SCNR (Optional)"))
189
+ left.addWidget(self.chk_scnr)
190
+
191
+ # Buttons row
192
+ rowb = QHBoxLayout()
193
+ self.btn_preview = QPushButton(self.tr("Preview"))
194
+ self.btn_apply = QPushButton(self.tr("Apply to Document"))
195
+ rowb.addWidget(self.btn_preview)
196
+ rowb.addWidget(self.btn_apply)
197
+ left.addLayout(rowb)
198
+
199
+ # Spinner
200
+ self.lbl_spin = QLabel()
201
+ self.lbl_spin.setAlignment(Qt.AlignmentFlag.AlignCenter)
202
+ self.lbl_spin.hide()
203
+ spinner_gif = _guess_spinner_path()
204
+ if spinner_gif and os.path.exists(spinner_gif):
205
+ mv = QMovie(spinner_gif)
206
+ self.lbl_spin.setMovie(mv)
207
+ self._spinner = mv
208
+ else:
209
+ self._spinner = None
210
+ left.addWidget(self.lbl_spin)
211
+
212
+ left.addStretch(1)
213
+ main.addLayout(left, 0)
214
+
215
+ # Right column (preview with zoom/pan)
216
+ right = QVBoxLayout()
217
+ zoombar = QHBoxLayout()
218
+ b_out = QPushButton(self.tr("Zoom Out"))
219
+ b_in = QPushButton(self.tr("Zoom In"))
220
+ b_fit = QPushButton(self.tr("Fit to Preview"))
221
+ b_out.clicked.connect(self._zoom_out)
222
+ b_in.clicked.connect(self._zoom_in)
223
+ b_fit.clicked.connect(self._fit)
224
+ zoombar.addWidget(b_out); zoombar.addWidget(b_in); zoombar.addWidget(b_fit)
225
+ right.addLayout(zoombar)
226
+
227
+ self.scroll = QScrollArea()
228
+ self.scroll.setWidgetResizable(True)
229
+ self.scroll.setAlignment(Qt.AlignmentFlag.AlignCenter)
230
+ self.scroll.viewport().installEventFilter(self)
231
+
232
+ self.label = QLabel(alignment=Qt.AlignmentFlag.AlignCenter)
233
+ self.scroll.setWidget(self.label)
234
+
235
+ right.addWidget(self.scroll, 1)
236
+ main.addLayout(right, 1)
237
+
238
+ # signals
239
+ self.btn_preview.clicked.connect(self._run_preview)
240
+ self.btn_apply.clicked.connect(self._apply_to_doc)
241
+
242
+ # initialize preview with current doc image
243
+ self._update_preview_pix(self.doc.image)
244
+
245
+ # --- active document change ---
246
+ def _on_active_doc_changed(self, doc):
247
+ """Called when user clicks a different image window."""
248
+ if doc is None or getattr(doc, "image", None) is None:
249
+ return
250
+ self.doc = doc
251
+ self._preview = None
252
+ self._update_preview_pix(self.doc.image)
253
+
254
+ # --- UI change handlers ---
255
+ def _on_stretch_changed(self, v: int):
256
+ self.lbl_st.setText(f"Stretch Amount: {v/100.0:.2f}")
257
+
258
+ def _on_sat_changed(self, v: int):
259
+ self.lbl_sat.setText(f"Color Boost: {v/100.0:.2f}")
260
+
261
+ # --- preview / processing ---
262
+ def _run_preview(self):
263
+ img = self.doc.image
264
+ if img is None:
265
+ QMessageBox.information(self, "No image", "Open an image first.")
266
+ return
267
+ self._show_spinner(True)
268
+ self.btn_preview.setEnabled(False)
269
+ self.btn_apply.setEnabled(False)
270
+
271
+ self._thr = _StarStretchWorker(
272
+ image=img,
273
+ stretch_factor=self.sld_st.value()/100.0,
274
+ sat_amount=self.sld_sat.value()/100.0,
275
+ do_scnr=self.chk_scnr.isChecked()
276
+ )
277
+ self._thr.preview_ready.connect(self._on_preview_ready)
278
+ self._thr.finished.connect(lambda: self._show_spinner(False))
279
+ self._thr.start()
280
+
281
+ def _on_preview_ready(self, out: np.ndarray):
282
+ out_masked = self._blend_with_mask(out)
283
+ self._preview = out_masked
284
+ self.btn_preview.setEnabled(True)
285
+ self.btn_apply.setEnabled(True)
286
+ self._update_preview_pix(out_masked)
287
+
288
+ mw = self._find_main_window()
289
+ if mw and hasattr(mw, "_log"):
290
+ mw._log("Star Stretch: preview generated.")
291
+
292
+ # NEW: if Apply was pressed before preview completed, finish now.
293
+ if self._apply_when_ready:
294
+ self._apply_when_ready = False
295
+ self._finish_apply()
296
+
297
+ def _apply_to_doc(self):
298
+ # If we don't have a preview yet, compute it and auto-apply when ready.
299
+ if self._preview is None:
300
+ if getattr(self, "_thr", None) and self._thr.isRunning():
301
+ # already computing; just mark to apply when it lands
302
+ self._apply_when_ready = True
303
+ return
304
+ self._apply_when_ready = True
305
+ self._run_preview()
306
+ return
307
+
308
+ # We do have a preview → finish immediately
309
+ self._finish_apply()
310
+
311
+ def _finish_apply(self):
312
+ try:
313
+ _marr, mid, mname = self._active_mask_layer()
314
+ meta = {
315
+ "step_name": "Star Stretch",
316
+ "star_stretch": {
317
+ "stretch_factor": self.sld_st.value()/100.0,
318
+ "color_boost": self.sld_sat.value()/100.0,
319
+ "scnr_green": self.chk_scnr.isChecked(),
320
+ "numba": _HAS_NUMBA,
321
+ },
322
+ # ✅ mask bookkeeping
323
+ "masked": bool(mid),
324
+ "mask_id": mid,
325
+ "mask_name": mname,
326
+ "mask_blend": "m*out + (1-m)*src",
327
+ }
328
+ self.doc.apply_edit(self._preview.copy(), metadata=meta, step_name="Star Stretch")
329
+
330
+ mw = self._find_main_window()
331
+ if mw and hasattr(mw, "_log"):
332
+ mw._log("Star Stretch: applied to document.")
333
+
334
+ # 🔁 Record as last headless-style command for Replay
335
+ try:
336
+ if mw and hasattr(mw, "_remember_last_headless_command"):
337
+ preset = {
338
+ "stretch_factor": self.sld_st.value()/100.0,
339
+ "color_boost": self.sld_sat.value()/100.0,
340
+ "scnr_green": self.chk_scnr.isChecked(),
341
+ }
342
+ mw._remember_last_headless_command(
343
+ "star_stretch",
344
+ preset,
345
+ description="Star Stretch",
346
+ )
347
+ except Exception:
348
+ # Don't let replay bookkeeping break the dialog
349
+ pass
350
+
351
+ except Exception as e:
352
+ QMessageBox.critical(self, "Apply failed", str(e))
353
+ return
354
+
355
+ # Dialog stays open so user can apply to other images
356
+ # Refresh document reference for next operation
357
+ self.close()
358
+ return
359
+
360
+ def _refresh_document_from_active(self):
361
+ """
362
+ Refresh the dialog's document reference to the currently active document.
363
+ This allows reusing the same dialog on different images.
364
+ """
365
+ try:
366
+ main = self._find_main_window()
367
+ if main and hasattr(main, "_active_doc"):
368
+ new_doc = main._active_doc()
369
+ if new_doc is not None and new_doc is not self.doc:
370
+ self.doc = new_doc
371
+ # Reset preview for new document
372
+ self._preview = None
373
+ self._compute_and_show_preview()
374
+ except Exception:
375
+ pass
376
+
377
+
378
+ # --- preview rendering ---
379
+ def _update_preview_pix(self, img: np.ndarray | None):
380
+ if img is None:
381
+ self.label.clear(); self._pix = None; return
382
+ qimg = _as_qimage_rgb8(_to_float01(img))
383
+ pm = QPixmap.fromImage(qimg)
384
+ self._pix = pm
385
+ self._apply_zoom()
386
+
387
+ def _apply_zoom(self):
388
+ if self._pix is None:
389
+ return
390
+ scaled = self._pix.scaled(self._pix.size()*self._zoom,
391
+ Qt.AspectRatioMode.KeepAspectRatio,
392
+ Qt.TransformationMode.SmoothTransformation)
393
+ self.label.setPixmap(scaled)
394
+ self.label.resize(scaled.size())
395
+
396
+ # --- zoom/pan ---
397
+ def _zoom_in(self): self._set_zoom(self._zoom * 1.25)
398
+ def _zoom_out(self): self._set_zoom(self._zoom / 1.25)
399
+ def _fit(self):
400
+ if self._pix is None: return
401
+ vp = self.scroll.viewport().size()
402
+ if self._pix.width()==0 or self._pix.height()==0: return
403
+ s = min(vp.width()/self._pix.width(), vp.height()/self._pix.height())
404
+ self._set_zoom(max(0.05, s))
405
+
406
+ def _set_zoom(self, z: float):
407
+ self._zoom = float(max(0.05, min(z, 8.0)))
408
+ self._apply_zoom()
409
+
410
+ # --- spinner ---
411
+ def _show_spinner(self, on: bool):
412
+ if self._spinner is None:
413
+ self.lbl_spin.setVisible(on)
414
+ return
415
+ if on:
416
+ self.lbl_spin.show(); self._spinner.start()
417
+ else:
418
+ self._spinner.stop(); self.lbl_spin.hide()
419
+
420
+ # --- event filter (wheel zoom + panning) ---
421
+ def eventFilter(self, obj, ev):
422
+ if obj is self.scroll.viewport():
423
+ if ev.type() == QEvent.Type.Wheel and (ev.modifiers() & Qt.KeyboardModifier.ControlModifier):
424
+ self._set_zoom(self._zoom * (1.25 if ev.angleDelta().y() > 0 else 0.8))
425
+ ev.accept(); return True
426
+ if ev.type() == QEvent.Type.MouseButtonPress and ev.button() == Qt.MouseButton.LeftButton:
427
+ self._panning = True; self._pan_start = ev.position()
428
+ self.scroll.viewport().setCursor(Qt.CursorShape.ClosedHandCursor)
429
+ ev.accept(); return True
430
+ if ev.type() == QEvent.Type.MouseMove and self._panning:
431
+ d = ev.position() - self._pan_start
432
+ h = self.scroll.horizontalScrollBar(); v = self.scroll.verticalScrollBar()
433
+ h.setValue(h.value() - int(d.x())); v.setValue(v.value() - int(d.y()))
434
+ self._pan_start = ev.position()
435
+ ev.accept(); return True
436
+ if ev.type() == QEvent.Type.MouseButtonRelease and ev.button() == Qt.MouseButton.LeftButton:
437
+ self._panning = False
438
+ self.scroll.viewport().setCursor(Qt.CursorShape.ArrowCursor)
439
+ ev.accept(); return True
440
+ return super().eventFilter(obj, ev)
441
+
442
+ # --- helper ---
443
+ def _find_main_window(self):
444
+ p = self.parent()
445
+ while p is not None and not hasattr(p, "docman"):
446
+ p = p.parent()
447
+ return p
448
+
449
+ # --- mask helpers ---------------------------------------------------
450
+ def _active_mask_layer(self):
451
+ """Return (mask_array_float01, mask_id, mask_name) or (None, None, None)."""
452
+ doc = self.doc
453
+ mid = getattr(doc, "active_mask_id", None)
454
+ if not mid:
455
+ return None, None, None
456
+ layer = getattr(doc, "masks", {}).get(mid)
457
+ if layer is None:
458
+ return None, None, None
459
+ m = np.asarray(getattr(layer, "data", None), dtype=np.float32)
460
+ if m is None or m.size == 0:
461
+ return None, None, None
462
+ # ensure [0..1]
463
+ if m.dtype.kind in "ui":
464
+ m = m / float(np.iinfo(m.dtype).max)
465
+ else:
466
+ mx = float(m.max()) if m.size else 1.0
467
+ if mx > 1.0:
468
+ m = m / mx
469
+ m = np.clip(m, 0.0, 1.0)
470
+ return m, mid, getattr(layer, "name", "Mask")
471
+
472
+ def _resample_mask_if_needed(self, mask: np.ndarray, out_hw: tuple[int,int]) -> np.ndarray:
473
+ """Nearest-neighbor resize using integer indexing (fast, dependency-free)."""
474
+ mh, mw = mask.shape[:2]
475
+ th, tw = out_hw
476
+ if (mh, mw) == (th, tw):
477
+ return mask
478
+ yi = np.linspace(0, mh - 1, th).astype(np.int32)
479
+ xi = np.linspace(0, mw - 1, tw).astype(np.int32)
480
+ return mask[yi][:, xi]
481
+
482
+ def _blend_with_mask(self, stretched: np.ndarray) -> np.ndarray:
483
+ """Blend preview/apply with original using active mask if present."""
484
+ mask, _mid, _name = self._active_mask_layer()
485
+ if mask is None:
486
+ return stretched
487
+ src = _to_float01(self.doc.image)
488
+ out = stretched.astype(np.float32, copy=False)
489
+
490
+ # Make sure spatial size matches mask
491
+ th, tw = out.shape[:2]
492
+ m = self._resample_mask_if_needed(mask, (th, tw))
493
+
494
+ # Broadcast mask to 3ch when needed
495
+ if out.ndim == 3 and out.shape[2] == 3:
496
+ m = m[..., None]
497
+
498
+ # If preview changed mono↔RGB shape, match src first
499
+ if src.ndim == 2 and out.ndim == 3 and out.shape[2] == 3:
500
+ src = np.stack([src]*3, axis=-1)
501
+ elif src.ndim == 3 and src.shape[2] == 3 and out.ndim == 2:
502
+ src = src[..., 0] # collapse to mono
503
+
504
+ return (m * out + (1.0 - m) * src).astype(np.float32, copy=False)
505
+
506
+ def closeEvent(self, ev):
507
+ # 1) Disconnect active-doc tracking (Fabio hook)
508
+ try:
509
+ if self._connected_current_doc_changed and hasattr(self._main, "currentDocumentChanged"):
510
+ self._main.currentDocumentChanged.disconnect(self._on_active_doc_changed)
511
+ except Exception:
512
+ pass
513
+ self._connected_current_doc_changed = False
514
+
515
+ # 2) Stop any background preview worker/thread if you have one
516
+ # (names may differ in your file; keep what matches your implementation)
517
+ try:
518
+ if getattr(self, "_worker", None) is not None:
519
+ try:
520
+ self._worker.requestInterruption()
521
+ except Exception:
522
+ pass
523
+ if getattr(self, "_thread", None) is not None:
524
+ self._thread.quit()
525
+ self._thread.wait(500)
526
+ except Exception:
527
+ pass
528
+
529
+ super().closeEvent(ev)
530
+
531
+
532
+ def _guess_spinner_path() -> str | None:
533
+ here = os.path.dirname(__file__)
534
+ cands = [
535
+ os.path.join(here, "spinner.gif"),
536
+ os.path.join(os.path.dirname(here), "spinner.gif"),
537
+ os.path.join(os.getcwd(), "spinner.gif"),
538
+ ]
539
+ for c in cands:
540
+ if os.path.exists(c):
541
+ return c
542
+ return None