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.

Potentially problematic release.


This version of setiastrosuitepro might be problematic. Click here for more details.

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,638 @@
1
+ # pro/graxpert.py
2
+ from __future__ import annotations
3
+ import os
4
+ import platform
5
+ import shutil
6
+ import tempfile
7
+ import stat
8
+ import glob
9
+ import subprocess
10
+ import numpy as np
11
+
12
+ from PyQt6.QtCore import QThread, pyqtSignal, Qt
13
+ from PyQt6.QtWidgets import (
14
+ QDialog, QVBoxLayout, QTextEdit, QPushButton, QFileDialog,
15
+ QMessageBox, QInputDialog, QFormLayout, QDialogButtonBox, QDoubleSpinBox,
16
+ QRadioButton, QLabel, QComboBox, QCheckBox, QWidget
17
+ )
18
+ from setiastro.saspro.config import Config
19
+
20
+ # Prefer the exact loader you used in SASv2
21
+ try:
22
+ # adjust this import path if your loader lives elsewhere
23
+ from setiastro.saspro.legacy.image_manager import load_image as _legacy_load_image
24
+ except Exception:
25
+ _legacy_load_image = None
26
+
27
+
28
+ class GraXpertOperationDialog(QDialog):
29
+ """Choose operation + parameter (smoothing or strength) + (optional) denoise model."""
30
+ def __init__(self, parent=None):
31
+ super().__init__(parent)
32
+
33
+ self.setWindowTitle("GraXpert")
34
+ self.setWindowFlag(Qt.WindowType.Window, True)
35
+ self.setWindowModality(Qt.WindowModality.NonModal)
36
+ self.setModal(False)
37
+
38
+ root = QVBoxLayout(self)
39
+
40
+ # radios
41
+ self.rb_bg = QRadioButton("Remove gradient")
42
+ self.rb_dn = QRadioButton("Denoise")
43
+ self.rb_bg.setChecked(True)
44
+
45
+ # param widgets
46
+ self.spin = QDoubleSpinBox()
47
+ self.spin.setRange(0.0, 1.0)
48
+ self.spin.setDecimals(2)
49
+ self.spin.setSingleStep(0.01)
50
+ self.spin.setValue(0.10) # default for smoothing
51
+
52
+ # dynamic label
53
+ self.param_label = QLabel("Smoothing (0–1):")
54
+
55
+ # denoise model (optional)
56
+ self.model_label = QLabel("Denoise model:")
57
+ self.model_combo = QComboBox()
58
+ # Index 0 = auto/latest (empty payload → omit flag)
59
+ self.model_combo.addItem("Latest (auto)", "") # omit -ai_version
60
+ for v in ["3.0.2", "3.0.1", "3.0.0", "2.0.0", "1.1.0", "1.0.0"]:
61
+ self.model_combo.addItem(v, v)
62
+
63
+ # GPU toggle (persists via QSettings if available)
64
+ self.cb_gpu = QCheckBox("Use GPU acceleration")
65
+ use_gpu_default = True
66
+ try:
67
+ settings = getattr(parent, "settings", None)
68
+ if settings is not None:
69
+ use_gpu_default = settings.value("graxpert/use_gpu", True, type=bool)
70
+ except Exception:
71
+ pass
72
+ self.cb_gpu.setChecked(bool(use_gpu_default))
73
+
74
+
75
+ # layout
76
+ form = QFormLayout()
77
+ form.addRow(self.rb_bg)
78
+ form.addRow(self.rb_dn)
79
+ form.addRow(self.param_label, self.spin)
80
+ form.addRow(self.model_label, self.model_combo)
81
+ form.addRow(self.cb_gpu)
82
+ root.addLayout(form)
83
+
84
+ # switch label/defaults and enable/disable model picker
85
+ def _to_bg():
86
+ self.param_label.setText("Smoothing (0–1):")
87
+ # If param was the denoise default, flip back to smoothing default
88
+ self.spin.setValue(0.10 if abs(self.spin.value() - 0.50) < 1e-6 else self.spin.value())
89
+ self.model_label.setEnabled(False)
90
+ self.model_combo.setEnabled(False)
91
+
92
+ def _to_dn():
93
+ self.param_label.setText("Strength (0–1):")
94
+ # If param was the smoothing default, flip to denoise default
95
+ self.spin.setValue(0.50 if abs(self.spin.value() - 0.10) < 1e-6 else self.spin.value())
96
+ self.model_label.setEnabled(True)
97
+ self.model_combo.setEnabled(True)
98
+
99
+ self.rb_bg.toggled.connect(lambda checked: _to_bg() if checked else None)
100
+ self.rb_dn.toggled.connect(lambda checked: _to_dn() if checked else None)
101
+
102
+ # initialize state
103
+ _to_bg()
104
+
105
+ btns = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
106
+ btns.accepted.connect(self.accept)
107
+ btns.rejected.connect(self.reject)
108
+ root.addWidget(btns)
109
+
110
+ def result(self):
111
+ op = "background" if self.rb_bg.isChecked() else "denoise"
112
+ val = float(self.spin.value())
113
+ ai_version = self.model_combo.currentData() if not self.rb_bg.isChecked() else ""
114
+ use_gpu = self.cb_gpu.isChecked()
115
+ return op, val, (ai_version or None), use_gpu
116
+
117
+ def _build_graxpert_cmd(
118
+ exe: str,
119
+ operation: str,
120
+ input_path: str,
121
+ *,
122
+ smoothing: float | None = None,
123
+ strength: float | None = None,
124
+ ai_version: str | None = None,
125
+ gpu: bool = True,
126
+ batch_size: int | None = None
127
+ ) -> list[str]:
128
+ op = "denoising" if operation == "denoise" else "background-extraction"
129
+ cmd = [exe, "-cmd", op, input_path, "-cli", "-gpu", "true" if gpu else "false"]
130
+ if op == "denoising":
131
+ if strength is not None:
132
+ cmd += ["-strength", f"{strength:.2f}"]
133
+ if batch_size is not None:
134
+ cmd += ["-batch_size", str(int(batch_size))]
135
+ # Only include if user chose a specific model
136
+ if ai_version:
137
+ cmd += ["-ai_version", ai_version]
138
+ else:
139
+ if smoothing is not None:
140
+ cmd += ["-smoothing", f"{smoothing:.2f}"]
141
+ return cmd
142
+
143
+ # ---------- Public entry point (call this from your main window) ----------
144
+ def remove_gradient_with_graxpert(main_window, target_doc=None):
145
+ """
146
+ Exactly mirror SASv2 flow:
147
+ - write input_image.tif
148
+ - run GraXpert
149
+ - read input_image_GraXpert.{fits|tif|tiff|png} using legacy loader
150
+ - apply to target document
151
+ """
152
+ if getattr(main_window, "_graxpert_headless_running", False):
153
+ return
154
+ if getattr(main_window, "_graxpert_guard", False): # cool-down guard
155
+ return
156
+
157
+ # 1) pick the document: explicit > fallback
158
+ doc = target_doc
159
+
160
+ if doc is None:
161
+ # Backwards compatibility: fall back to _active_doc
162
+ doc = getattr(main_window, "_active_doc", None)
163
+ if callable(doc):
164
+ doc = doc()
165
+
166
+ if doc is None and hasattr(main_window, "mdi"):
167
+ # Extra fallback: resolve from active subwindow if possible
168
+ try:
169
+ sw = main_window.mdi.activeSubWindow()
170
+ if sw is not None:
171
+ view = sw.widget()
172
+ doc = getattr(view, "document", None)
173
+ except Exception:
174
+ pass
175
+
176
+ if doc is None or getattr(doc, "image", None) is None:
177
+ QMessageBox.warning(
178
+ main_window,
179
+ "No Image",
180
+ "Please load an image before removing the gradient."
181
+ )
182
+ return
183
+
184
+ # 2) smoothing/denoise prompt
185
+ op_dlg = GraXpertOperationDialog(main_window)
186
+ if op_dlg.exec() != QDialog.DialogCode.Accepted:
187
+ return
188
+ operation, param, ai_version, use_gpu = op_dlg.result()
189
+
190
+ # 3) resolve GraXpert executable
191
+ exe = _resolve_graxpert_exec(main_window)
192
+ if not exe:
193
+ return
194
+
195
+ # Persist the checkbox choice for next time
196
+ try:
197
+ if hasattr(main_window, "settings"):
198
+ main_window.settings.setValue("graxpert/use_gpu", bool(use_gpu))
199
+ except Exception:
200
+ pass
201
+
202
+ # 🔁 NEW: record this as a replayable headless-style command
203
+ try:
204
+ remember = getattr(main_window, "remember_last_headless_command", None)
205
+ if remember is None:
206
+ remember = getattr(main_window, "_remember_last_headless_command", None)
207
+
208
+ if callable(remember):
209
+ preset = {
210
+ "op": operation, # "background" or "denoise"
211
+ "gpu": bool(use_gpu),
212
+ }
213
+ if operation == "background":
214
+ preset["smoothing"] = float(param)
215
+ desc = "GraXpert Gradient Removal"
216
+ else:
217
+ preset["strength"] = float(param)
218
+ if ai_version:
219
+ preset["ai_version"] = ai_version
220
+ desc = "GraXpert Denoise"
221
+
222
+ remember("graxpert", preset, description=desc)
223
+
224
+ # Optional log entry, if you want:
225
+ if hasattr(main_window, "_log"):
226
+ try:
227
+ main_window._log(
228
+ f"[Replay] GraXpert preset stored from dialog: "
229
+ f"op={operation}, keys={list(preset.keys())}"
230
+ )
231
+ except Exception:
232
+ pass
233
+ except Exception:
234
+ # Don't let replay bookkeeping break GraXpert itself
235
+ pass
236
+
237
+ # 4) write input to a temp working dir but KEEP THE SAME BASENAMES as v2
238
+ workdir = tempfile.mkdtemp(prefix="saspro_graxpert_")
239
+ input_basename = "input_image"
240
+ input_path = os.path.join(workdir, f"{input_basename}.tif")
241
+ try:
242
+ _write_tiff_float32(doc.image, input_path)
243
+ except Exception as e:
244
+ QMessageBox.critical(main_window, "GraXpert", f"Failed to write temporary input:\n{e}")
245
+ shutil.rmtree(workdir, ignore_errors=True)
246
+ return
247
+
248
+ # 5) build the exact v2 command (now with optional ai_version for denoise)
249
+ command = _build_graxpert_cmd(
250
+ exe,
251
+ operation,
252
+ input_path,
253
+ smoothing=param if operation == "background" else None,
254
+ strength=param if operation == "denoise" else None,
255
+ ai_version=ai_version if operation == "denoise" else None,
256
+ gpu=bool(use_gpu),
257
+ batch_size=(4 if use_gpu else 1)
258
+ )
259
+
260
+ # Label + metadata for history/undo
261
+ op_label = "GraXpert Denoise" if operation == "denoise" else "GraXpert Gradient Removal"
262
+ meta_extras = {
263
+ "graxpert_operation": operation, # "denoise" | "background"
264
+ "graxpert_param": float(param),
265
+ "graxpert_ai_version": (ai_version or "latest") if operation == "denoise" else None,
266
+ "graxpert_gpu": bool(use_gpu),
267
+ }
268
+
269
+ # 6) run and wait with a small log dialog
270
+ output_basename = f"{input_basename}_GraXpert"
271
+ _run_graxpert_command(
272
+ main_window,
273
+ command,
274
+ output_basename,
275
+ workdir,
276
+ target_doc=doc,
277
+ op_label=op_label,
278
+ meta_extras=meta_extras,
279
+ )
280
+
281
+
282
+ # ---------- helpers ----------
283
+ def _resolve_graxpert_exec(main_window) -> str | None:
284
+ # prefer QSettings if available (all OS)
285
+ path = None
286
+ if hasattr(main_window, "settings"):
287
+ try:
288
+ path = main_window.settings.value("paths/graxpert", type=str)
289
+ except Exception:
290
+ path = None
291
+ if path and os.path.exists(path):
292
+ _ensure_exec_bit(path)
293
+ return path
294
+
295
+ sysname = platform.system()
296
+ default = Config.get_graxpert_default_path()
297
+
298
+ if sysname == "Windows":
299
+ # rely on PATH (like v2) or default
300
+ return default if default else "GraXpert.exe"
301
+
302
+ if sysname == "Darwin":
303
+ if default and os.path.exists(default):
304
+ _ensure_exec_bit(default)
305
+ if hasattr(main_window, "settings"):
306
+ main_window.settings.setValue("paths/graxpert", default)
307
+ return default
308
+ return _pick_graxpert_path_and_store(main_window)
309
+
310
+ if sysname == "Linux":
311
+ # in v2 you asked user and saved; do the same
312
+ return _pick_graxpert_path_and_store(main_window)
313
+
314
+ QMessageBox.critical(main_window, "GraXpert", f"Unsupported operating system: {sysname}")
315
+ return None
316
+
317
+ def _pick_graxpert_path_and_store(main_window) -> str | None:
318
+ path, _ = QFileDialog.getOpenFileName(main_window, "Select GraXpert Executable")
319
+ if not path:
320
+ QMessageBox.warning(main_window, "Cancelled", "GraXpert path selection was cancelled.")
321
+ return None
322
+ try:
323
+ _ensure_exec_bit(path)
324
+ except Exception as e:
325
+ QMessageBox.critical(main_window, "GraXpert", f"Failed to set execute permissions:\n{e}")
326
+ return None
327
+ if hasattr(main_window, "settings"):
328
+ main_window.settings.setValue("paths/graxpert", path)
329
+ return path
330
+
331
+
332
+ def _ensure_exec_bit(path: str) -> None:
333
+ if platform.system() == "Windows":
334
+ return
335
+ try:
336
+ st = os.stat(path)
337
+ os.chmod(path, st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
338
+ except Exception:
339
+ pass
340
+
341
+
342
+ def _write_tiff_float32(image, path: str, *, clip01: bool = True):
343
+ """
344
+ Always write a 32-bit floating-point TIFF for GraXpert.
345
+ - Mono stays 2D; RGB stays HxWx3.
346
+ - Values are clipped to [0,1] by default to avoid weird HDR ranges.
347
+ """
348
+ import numpy as np
349
+
350
+ arr = np.asarray(image)
351
+ if arr.ndim == 3 and arr.shape[2] == 1:
352
+ arr = arr[..., 0]
353
+
354
+ # Convert to float32 in [0,1]
355
+ if np.issubdtype(arr.dtype, np.floating):
356
+ a32 = arr.astype(np.float32, copy=False)
357
+ if clip01:
358
+ a32 = np.clip(a32, 0.0, 1.0)
359
+ elif np.issubdtype(arr.dtype, np.integer):
360
+ # Scale integers to [0,1] float32
361
+ maxv = np.float32(np.iinfo(arr.dtype).max)
362
+ a32 = (arr.astype(np.float32) / maxv)
363
+ else:
364
+ a32 = arr.astype(np.float32)
365
+
366
+ if clip01:
367
+ a32 = np.clip(a32, 0.0, 1.0)
368
+
369
+ # Prefer tifffile to guarantee float32 TIFFs
370
+ try:
371
+ import tifffile as tiff
372
+ # Write a plain, contiguous, uncompressed float32 TIFF
373
+ # (GraXpert doesn't need ImageJ tags; photometric=minisblack is fine)
374
+ tiff.imwrite(
375
+ path,
376
+ a32,
377
+ dtype=np.float32,
378
+ photometric='minisblack' if a32.ndim == 2 else None,
379
+ planarconfig='contig',
380
+ compression=None,
381
+ imagej=False,
382
+ )
383
+ return
384
+ except Exception as e1:
385
+ pass
386
+
387
+ # Fallback: imageio (uses tifffile under the hood in many installs)
388
+ try:
389
+ import imageio.v3 as iio
390
+ iio.imwrite(path, a32.astype(np.float32))
391
+ return
392
+ except Exception as e2:
393
+ raise RuntimeError(
394
+ "Could not write 32-bit TIFF for GraXpert. "
395
+ "Please install 'tifffile' or 'imageio'.\n"
396
+ f"tifffile error: {e1}\nimageio error: {e2}"
397
+ )
398
+
399
+
400
+
401
+ # ---------- runner + dialog ----------
402
+ class _GraXpertThread(QThread):
403
+ stdout_signal = pyqtSignal(str)
404
+ finished_signal = pyqtSignal(int)
405
+
406
+ def __init__(self, command: list[str], cwd: str | None = None, parent=None):
407
+ super().__init__(parent)
408
+ self.command = command
409
+ self.cwd = cwd
410
+
411
+ def run(self):
412
+ env = os.environ.copy()
413
+ for k in ("PYTHONHOME", "PYTHONPATH", "DYLD_LIBRARY_PATH",
414
+ "DYLD_FALLBACK_LIBRARY_PATH", "PYTHONEXECUTABLE"):
415
+ env.pop(k, None)
416
+ try:
417
+ p = subprocess.Popen(
418
+ self.command,
419
+ cwd=self.cwd,
420
+ stdout=subprocess.PIPE,
421
+ stderr=subprocess.STDOUT, # merge; avoids ResourceWarning + deadlocks
422
+ text=True,
423
+ universal_newlines=True,
424
+ env=env,
425
+ start_new_session=True
426
+ )
427
+ for line in iter(p.stdout.readline, ""):
428
+ if not line:
429
+ break
430
+ self.stdout_signal.emit(line.rstrip())
431
+ try:
432
+ p.stdout.close()
433
+ except Exception:
434
+ pass
435
+ rc = p.wait()
436
+ except Exception as e:
437
+ self.stdout_signal.emit(str(e))
438
+ rc = -1
439
+ self.finished_signal.emit(rc)
440
+
441
+
442
+ def _run_graxpert_command(parent, command: list[str], output_basename: str,
443
+ working_dir: str, target_doc,
444
+ op_label: str | None = None,
445
+ meta_extras: dict | None = None):
446
+ dlg = QDialog(parent)
447
+ dlg.setWindowTitle("GraXpert Progress")
448
+ dlg.setMinimumSize(600, 420)
449
+ lay = QVBoxLayout(dlg)
450
+ log = QTextEdit(readOnly=True)
451
+ lay.addWidget(log)
452
+ btn_cancel = QPushButton("Cancel")
453
+ lay.addWidget(btn_cancel)
454
+
455
+ thr = _GraXpertThread(command, cwd=working_dir)
456
+ thr.stdout_signal.connect(lambda s: log.append(s))
457
+ thr.finished_signal.connect(
458
+ lambda code: _on_graxpert_finished(
459
+ parent,
460
+ code,
461
+ output_basename,
462
+ working_dir,
463
+ target_doc,
464
+ dlg,
465
+ op_label,
466
+ meta_extras,
467
+ )
468
+ )
469
+ btn_cancel.clicked.connect(thr.terminate)
470
+
471
+ thr.start()
472
+ dlg.exec()
473
+
474
+
475
+
476
+ # ---------- finish: import EXACT base like v2, via legacy loader ----------
477
+ def _persist_output_file(src_path: str) -> str | None:
478
+ """Optional: move/copy GraXpert output to an app cache we control."""
479
+ try:
480
+ from PyQt6.QtCore import QStandardPaths
481
+ cache_root = QStandardPaths.writableLocation(QStandardPaths.StandardLocation.CacheLocation)
482
+ except Exception:
483
+ cache_root = None
484
+ try:
485
+ base = os.path.join(cache_root or os.path.expanduser("~/.saspro_cache"), "graxpert")
486
+ os.makedirs(base, exist_ok=True)
487
+ dst = os.path.join(base, os.path.basename(src_path))
488
+ # prefer move (cheaper); fall back to copy if cross-device issues
489
+ try:
490
+ shutil.move(src_path, dst)
491
+ except Exception:
492
+ shutil.copy2(src_path, dst)
493
+ return dst
494
+ except Exception:
495
+ return None
496
+
497
+
498
+ def _on_graxpert_finished(parent,
499
+ return_code: int,
500
+ output_basename: str,
501
+ working_dir: str,
502
+ target_doc,
503
+ dlg,
504
+ op_label: str | None = None,
505
+ meta_extras: dict | None = None):
506
+ try:
507
+ dlg.close()
508
+ except Exception:
509
+ pass
510
+
511
+ if return_code != 0:
512
+ QMessageBox.critical(parent, "GraXpert", "GraXpert process failed.")
513
+ shutil.rmtree(working_dir, ignore_errors=True)
514
+ return
515
+
516
+ # 1) find output file in the temp working dir
517
+ output_file = _pick_exact_output(working_dir, output_basename)
518
+ if not output_file:
519
+ QMessageBox.critical(parent, "GraXpert", "GraXpert output file not found.")
520
+ shutil.rmtree(working_dir, ignore_errors=True)
521
+ return
522
+
523
+ # 2) read pixels (we *do not* want its header to replace ours)
524
+ arr, header = None, None
525
+ if _legacy_load_image is not None:
526
+ try:
527
+ out = _legacy_load_image(output_file, return_metadata=True)
528
+ if out and len(out) == 5:
529
+ a, h, bit_depth, is_mono, out_meta = out
530
+ else:
531
+ a, h, bit_depth, is_mono = out
532
+ out_meta = {}
533
+ arr, header = a, h
534
+ except Exception:
535
+ arr = None
536
+ header = None
537
+ bit_depth = "32-bit floating point"
538
+ is_mono = None
539
+ out_meta = {}
540
+ else:
541
+ out_meta = {}
542
+ bit_depth = "32-bit floating point"
543
+ is_mono = None
544
+ # Decide how it appears in history/undo
545
+ step_label = op_label or "GraXpert Gradient Removal"
546
+
547
+ # 3) base metadata: START FROM EXISTING DOC METADATA
548
+ base_meta = dict(getattr(target_doc, "metadata", {}) or {})
549
+
550
+ # Keep original_header / wcs_header from the doc.
551
+ # If you want to keep GraXpert's header for debugging, store separately:
552
+ from astropy.io import fits as _fits_mod
553
+ if header is not None and isinstance(header, _fits_mod.Header):
554
+ base_meta.setdefault("graxpert_header", header)
555
+
556
+ # Basic fields we do want to update
557
+ base_meta["step_name"] = step_label
558
+ base_meta["description"] = step_label
559
+ base_meta["bit_depth"] = "32-bit floating point"
560
+ if is_mono is not None:
561
+ base_meta["is_mono"] = bool(is_mono)
562
+
563
+ # Copy over any interesting fields from GraXpert's own metadata that are SAFE
564
+ # but do NOT overwrite original_header / wcs_header.
565
+ for k, v in (out_meta or {}).items():
566
+ if k in ("original_header", "fits_header", "wcs_header"):
567
+ continue
568
+ base_meta.setdefault(k, v)
569
+
570
+ # Operation-specific extras
571
+ if meta_extras:
572
+ # these are non-header fields like graxpert_operation, etc.
573
+ base_meta.update(meta_extras)
574
+
575
+ # 4) apply to the target doc
576
+ try:
577
+ target_doc.apply_edit(
578
+ arr.astype(np.float32, copy=False),
579
+ metadata=base_meta,
580
+ step_name=step_label,
581
+ )
582
+ except Exception as e:
583
+ QMessageBox.critical(parent, "GraXpert", f"Failed to apply result:\n{e}")
584
+ finally:
585
+ shutil.rmtree(working_dir, ignore_errors=True)
586
+
587
+
588
+ def _pick_exact_output(folder: str, base: str) -> str | None:
589
+ # exact filenames only, like v2 did
590
+ exts = ("fits", "tif", "tiff", "png")
591
+ for ext in exts:
592
+ p = os.path.join(folder, f"{base}.{ext}")
593
+ if os.path.exists(p):
594
+ return p
595
+ # also try case-variants just in case
596
+ for q in glob.glob(os.path.join(folder, f"{base}.*")):
597
+ if q.lower().endswith("." + ext):
598
+ return q
599
+ return None
600
+
601
+
602
+ def _fallback_read_float01(path: str) -> np.ndarray | None:
603
+ """Basic loader: return float32 in [0,1], mono or RGB, without being too clever."""
604
+ try:
605
+ import imageio.v3 as iio
606
+ arr = iio.imread(path)
607
+ except Exception:
608
+ try:
609
+ import tifffile as tiff
610
+ arr = tiff.imread(path)
611
+ except Exception:
612
+ try:
613
+ from astropy.io import fits
614
+ with fits.open(path, memmap=False) as hdul:
615
+ arr = hdul[0].data
616
+ except Exception:
617
+ try:
618
+ import cv2
619
+ arr = cv2.imread(path, cv2.IMREAD_UNCHANGED)
620
+ if arr is not None and arr.ndim == 3:
621
+ arr = arr[..., ::-1] # BGR->RGB
622
+ except Exception:
623
+ arr = None
624
+ if arr is None:
625
+ return None
626
+
627
+ arr = np.asarray(arr)
628
+ if arr.ndim == 3 and arr.shape[2] == 1:
629
+ arr = arr[..., 0]
630
+ if arr.dtype.kind in "ui":
631
+ scale = 65535.0 if arr.dtype.itemsize >= 2 else 255.0
632
+ arr = arr.astype(np.float32) / scale
633
+ else:
634
+ arr = arr.astype(np.float32, copy=False)
635
+ mx = float(arr.max()) if arr.size else 1.0
636
+ if mx > 5.0:
637
+ arr = arr / mx
638
+ return arr