setiastrosuitepro 1.6.1__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 (342) 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/HRDiagram.png +0 -0
  16. setiastro/images/LExtract.png +0 -0
  17. setiastro/images/LInsert.png +0 -0
  18. setiastro/images/Oxygenation-atm-2.svg.png +0 -0
  19. setiastro/images/RGB080604.png +0 -0
  20. setiastro/images/abeicon.png +0 -0
  21. setiastro/images/aberration.png +0 -0
  22. setiastro/images/andromedatry.png +0 -0
  23. setiastro/images/andromedatry_satellited.png +0 -0
  24. setiastro/images/annotated.png +0 -0
  25. setiastro/images/aperture.png +0 -0
  26. setiastro/images/astrosuite.ico +0 -0
  27. setiastro/images/astrosuite.png +0 -0
  28. setiastro/images/astrosuitepro.icns +0 -0
  29. setiastro/images/astrosuitepro.ico +0 -0
  30. setiastro/images/astrosuitepro.png +0 -0
  31. setiastro/images/background.png +0 -0
  32. setiastro/images/background2.png +0 -0
  33. setiastro/images/benchmark.png +0 -0
  34. setiastro/images/big_moon_stabilizer_timeline.png +0 -0
  35. setiastro/images/big_moon_stabilizer_timeline_clean.png +0 -0
  36. setiastro/images/blaster.png +0 -0
  37. setiastro/images/blink.png +0 -0
  38. setiastro/images/clahe.png +0 -0
  39. setiastro/images/collage.png +0 -0
  40. setiastro/images/colorwheel.png +0 -0
  41. setiastro/images/contsub.png +0 -0
  42. setiastro/images/convo.png +0 -0
  43. setiastro/images/copyslot.png +0 -0
  44. setiastro/images/cosmic.png +0 -0
  45. setiastro/images/cosmicsat.png +0 -0
  46. setiastro/images/crop1.png +0 -0
  47. setiastro/images/cropicon.png +0 -0
  48. setiastro/images/curves.png +0 -0
  49. setiastro/images/cvs.png +0 -0
  50. setiastro/images/debayer.png +0 -0
  51. setiastro/images/denoise_cnn_custom.png +0 -0
  52. setiastro/images/denoise_cnn_graph.png +0 -0
  53. setiastro/images/disk.png +0 -0
  54. setiastro/images/dse.png +0 -0
  55. setiastro/images/exoicon.png +0 -0
  56. setiastro/images/eye.png +0 -0
  57. setiastro/images/fliphorizontal.png +0 -0
  58. setiastro/images/flipvertical.png +0 -0
  59. setiastro/images/font.png +0 -0
  60. setiastro/images/freqsep.png +0 -0
  61. setiastro/images/functionbundle.png +0 -0
  62. setiastro/images/graxpert.png +0 -0
  63. setiastro/images/green.png +0 -0
  64. setiastro/images/gridicon.png +0 -0
  65. setiastro/images/halo.png +0 -0
  66. setiastro/images/hdr.png +0 -0
  67. setiastro/images/histogram.png +0 -0
  68. setiastro/images/hubble.png +0 -0
  69. setiastro/images/imagecombine.png +0 -0
  70. setiastro/images/invert.png +0 -0
  71. setiastro/images/isophote.png +0 -0
  72. setiastro/images/isophote_demo_figure.png +0 -0
  73. setiastro/images/isophote_demo_image.png +0 -0
  74. setiastro/images/isophote_demo_model.png +0 -0
  75. setiastro/images/isophote_demo_residual.png +0 -0
  76. setiastro/images/jwstpupil.png +0 -0
  77. setiastro/images/linearfit.png +0 -0
  78. setiastro/images/livestacking.png +0 -0
  79. setiastro/images/mask.png +0 -0
  80. setiastro/images/maskapply.png +0 -0
  81. setiastro/images/maskcreate.png +0 -0
  82. setiastro/images/maskremove.png +0 -0
  83. setiastro/images/morpho.png +0 -0
  84. setiastro/images/mosaic.png +0 -0
  85. setiastro/images/multiscale_decomp.png +0 -0
  86. setiastro/images/nbtorgb.png +0 -0
  87. setiastro/images/neutral.png +0 -0
  88. setiastro/images/nuke.png +0 -0
  89. setiastro/images/openfile.png +0 -0
  90. setiastro/images/pedestal.png +0 -0
  91. setiastro/images/pen.png +0 -0
  92. setiastro/images/pixelmath.png +0 -0
  93. setiastro/images/platesolve.png +0 -0
  94. setiastro/images/ppp.png +0 -0
  95. setiastro/images/pro.png +0 -0
  96. setiastro/images/project.png +0 -0
  97. setiastro/images/psf.png +0 -0
  98. setiastro/images/redo.png +0 -0
  99. setiastro/images/redoicon.png +0 -0
  100. setiastro/images/rescale.png +0 -0
  101. setiastro/images/rgbalign.png +0 -0
  102. setiastro/images/rgbcombo.png +0 -0
  103. setiastro/images/rgbextract.png +0 -0
  104. setiastro/images/rotate180.png +0 -0
  105. setiastro/images/rotateclockwise.png +0 -0
  106. setiastro/images/rotatecounterclockwise.png +0 -0
  107. setiastro/images/satellite.png +0 -0
  108. setiastro/images/script.png +0 -0
  109. setiastro/images/selectivecolor.png +0 -0
  110. setiastro/images/simbad.png +0 -0
  111. setiastro/images/slot0.png +0 -0
  112. setiastro/images/slot1.png +0 -0
  113. setiastro/images/slot2.png +0 -0
  114. setiastro/images/slot3.png +0 -0
  115. setiastro/images/slot4.png +0 -0
  116. setiastro/images/slot5.png +0 -0
  117. setiastro/images/slot6.png +0 -0
  118. setiastro/images/slot7.png +0 -0
  119. setiastro/images/slot8.png +0 -0
  120. setiastro/images/slot9.png +0 -0
  121. setiastro/images/spcc.png +0 -0
  122. setiastro/images/spin_precession_vs_lunar_distance.png +0 -0
  123. setiastro/images/spinner.gif +0 -0
  124. setiastro/images/stacking.png +0 -0
  125. setiastro/images/staradd.png +0 -0
  126. setiastro/images/staralign.png +0 -0
  127. setiastro/images/starnet.png +0 -0
  128. setiastro/images/starregistration.png +0 -0
  129. setiastro/images/starspike.png +0 -0
  130. setiastro/images/starstretch.png +0 -0
  131. setiastro/images/statstretch.png +0 -0
  132. setiastro/images/supernova.png +0 -0
  133. setiastro/images/uhs.png +0 -0
  134. setiastro/images/undoicon.png +0 -0
  135. setiastro/images/upscale.png +0 -0
  136. setiastro/images/viewbundle.png +0 -0
  137. setiastro/images/whitebalance.png +0 -0
  138. setiastro/images/wimi_icon_256x256.png +0 -0
  139. setiastro/images/wimilogo.png +0 -0
  140. setiastro/images/wims.png +0 -0
  141. setiastro/images/wrench_icon.png +0 -0
  142. setiastro/images/xisfliberator.png +0 -0
  143. setiastro/saspro/__init__.py +20 -0
  144. setiastro/saspro/__main__.py +809 -0
  145. setiastro/saspro/_generated/__init__.py +7 -0
  146. setiastro/saspro/_generated/build_info.py +2 -0
  147. setiastro/saspro/abe.py +1295 -0
  148. setiastro/saspro/abe_preset.py +196 -0
  149. setiastro/saspro/aberration_ai.py +694 -0
  150. setiastro/saspro/aberration_ai_preset.py +224 -0
  151. setiastro/saspro/accel_installer.py +218 -0
  152. setiastro/saspro/accel_workers.py +30 -0
  153. setiastro/saspro/add_stars.py +621 -0
  154. setiastro/saspro/astrobin_exporter.py +1007 -0
  155. setiastro/saspro/astrospike.py +153 -0
  156. setiastro/saspro/astrospike_python.py +1839 -0
  157. setiastro/saspro/autostretch.py +196 -0
  158. setiastro/saspro/backgroundneutral.py +560 -0
  159. setiastro/saspro/batch_convert.py +325 -0
  160. setiastro/saspro/batch_renamer.py +519 -0
  161. setiastro/saspro/blemish_blaster.py +488 -0
  162. setiastro/saspro/blink_comparator_pro.py +2926 -0
  163. setiastro/saspro/bundles.py +61 -0
  164. setiastro/saspro/bundles_dock.py +114 -0
  165. setiastro/saspro/cheat_sheet.py +178 -0
  166. setiastro/saspro/clahe.py +342 -0
  167. setiastro/saspro/comet_stacking.py +1377 -0
  168. setiastro/saspro/common_tr.py +107 -0
  169. setiastro/saspro/config.py +38 -0
  170. setiastro/saspro/config_bootstrap.py +40 -0
  171. setiastro/saspro/config_manager.py +316 -0
  172. setiastro/saspro/continuum_subtract.py +1617 -0
  173. setiastro/saspro/convo.py +1397 -0
  174. setiastro/saspro/convo_preset.py +414 -0
  175. setiastro/saspro/copyastro.py +187 -0
  176. setiastro/saspro/cosmicclarity.py +1564 -0
  177. setiastro/saspro/cosmicclarity_preset.py +407 -0
  178. setiastro/saspro/crop_dialog_pro.py +956 -0
  179. setiastro/saspro/crop_preset.py +189 -0
  180. setiastro/saspro/curve_editor_pro.py +2544 -0
  181. setiastro/saspro/curves_preset.py +375 -0
  182. setiastro/saspro/debayer.py +670 -0
  183. setiastro/saspro/debug_utils.py +29 -0
  184. setiastro/saspro/dnd_mime.py +35 -0
  185. setiastro/saspro/doc_manager.py +2641 -0
  186. setiastro/saspro/exoplanet_detector.py +2166 -0
  187. setiastro/saspro/file_utils.py +284 -0
  188. setiastro/saspro/fitsmodifier.py +745 -0
  189. setiastro/saspro/fix_bom.py +32 -0
  190. setiastro/saspro/free_torch_memory.py +48 -0
  191. setiastro/saspro/frequency_separation.py +1343 -0
  192. setiastro/saspro/function_bundle.py +1594 -0
  193. setiastro/saspro/generate_translations.py +2378 -0
  194. setiastro/saspro/ghs_dialog_pro.py +660 -0
  195. setiastro/saspro/ghs_preset.py +284 -0
  196. setiastro/saspro/graxpert.py +634 -0
  197. setiastro/saspro/graxpert_preset.py +287 -0
  198. setiastro/saspro/gui/__init__.py +0 -0
  199. setiastro/saspro/gui/main_window.py +8567 -0
  200. setiastro/saspro/gui/mixins/__init__.py +33 -0
  201. setiastro/saspro/gui/mixins/dock_mixin.py +263 -0
  202. setiastro/saspro/gui/mixins/file_mixin.py +443 -0
  203. setiastro/saspro/gui/mixins/geometry_mixin.py +403 -0
  204. setiastro/saspro/gui/mixins/header_mixin.py +441 -0
  205. setiastro/saspro/gui/mixins/mask_mixin.py +421 -0
  206. setiastro/saspro/gui/mixins/menu_mixin.py +361 -0
  207. setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
  208. setiastro/saspro/gui/mixins/toolbar_mixin.py +1457 -0
  209. setiastro/saspro/gui/mixins/update_mixin.py +309 -0
  210. setiastro/saspro/gui/mixins/view_mixin.py +435 -0
  211. setiastro/saspro/halobgon.py +462 -0
  212. setiastro/saspro/header_viewer.py +448 -0
  213. setiastro/saspro/headless_utils.py +88 -0
  214. setiastro/saspro/histogram.py +753 -0
  215. setiastro/saspro/history_explorer.py +939 -0
  216. setiastro/saspro/i18n.py +156 -0
  217. setiastro/saspro/image_combine.py +414 -0
  218. setiastro/saspro/image_peeker_pro.py +1601 -0
  219. setiastro/saspro/imageops/__init__.py +37 -0
  220. setiastro/saspro/imageops/mdi_snap.py +292 -0
  221. setiastro/saspro/imageops/scnr.py +36 -0
  222. setiastro/saspro/imageops/starbasedwhitebalance.py +210 -0
  223. setiastro/saspro/imageops/stretch.py +244 -0
  224. setiastro/saspro/isophote.py +1179 -0
  225. setiastro/saspro/layers.py +208 -0
  226. setiastro/saspro/layers_dock.py +714 -0
  227. setiastro/saspro/lazy_imports.py +193 -0
  228. setiastro/saspro/legacy/__init__.py +2 -0
  229. setiastro/saspro/legacy/image_manager.py +2226 -0
  230. setiastro/saspro/legacy/numba_utils.py +3659 -0
  231. setiastro/saspro/legacy/xisf.py +1071 -0
  232. setiastro/saspro/linear_fit.py +534 -0
  233. setiastro/saspro/live_stacking.py +1830 -0
  234. setiastro/saspro/log_bus.py +5 -0
  235. setiastro/saspro/logging_config.py +460 -0
  236. setiastro/saspro/luminancerecombine.py +309 -0
  237. setiastro/saspro/main_helpers.py +201 -0
  238. setiastro/saspro/mask_creation.py +928 -0
  239. setiastro/saspro/masks_core.py +56 -0
  240. setiastro/saspro/mdi_widgets.py +353 -0
  241. setiastro/saspro/memory_utils.py +666 -0
  242. setiastro/saspro/metadata_patcher.py +75 -0
  243. setiastro/saspro/mfdeconv.py +3826 -0
  244. setiastro/saspro/mfdeconv_earlystop.py +71 -0
  245. setiastro/saspro/mfdeconvcudnn.py +3263 -0
  246. setiastro/saspro/mfdeconvsport.py +2382 -0
  247. setiastro/saspro/minorbodycatalog.py +567 -0
  248. setiastro/saspro/morphology.py +382 -0
  249. setiastro/saspro/multiscale_decomp.py +1290 -0
  250. setiastro/saspro/nbtorgb_stars.py +531 -0
  251. setiastro/saspro/numba_utils.py +3044 -0
  252. setiastro/saspro/numba_warmup.py +141 -0
  253. setiastro/saspro/ops/__init__.py +9 -0
  254. setiastro/saspro/ops/command_help_dialog.py +623 -0
  255. setiastro/saspro/ops/command_runner.py +217 -0
  256. setiastro/saspro/ops/commands.py +1594 -0
  257. setiastro/saspro/ops/script_editor.py +1102 -0
  258. setiastro/saspro/ops/scripts.py +1413 -0
  259. setiastro/saspro/ops/settings.py +679 -0
  260. setiastro/saspro/parallel_utils.py +554 -0
  261. setiastro/saspro/pedestal.py +121 -0
  262. setiastro/saspro/perfect_palette_picker.py +1070 -0
  263. setiastro/saspro/pipeline.py +110 -0
  264. setiastro/saspro/pixelmath.py +1600 -0
  265. setiastro/saspro/plate_solver.py +2444 -0
  266. setiastro/saspro/project_io.py +797 -0
  267. setiastro/saspro/psf_utils.py +136 -0
  268. setiastro/saspro/psf_viewer.py +549 -0
  269. setiastro/saspro/pyi_rthook_astroquery.py +95 -0
  270. setiastro/saspro/remove_green.py +314 -0
  271. setiastro/saspro/remove_stars.py +1625 -0
  272. setiastro/saspro/remove_stars_preset.py +404 -0
  273. setiastro/saspro/resources.py +477 -0
  274. setiastro/saspro/rgb_combination.py +207 -0
  275. setiastro/saspro/rgb_extract.py +19 -0
  276. setiastro/saspro/rgbalign.py +723 -0
  277. setiastro/saspro/runtime_imports.py +7 -0
  278. setiastro/saspro/runtime_torch.py +754 -0
  279. setiastro/saspro/save_options.py +72 -0
  280. setiastro/saspro/selective_color.py +1552 -0
  281. setiastro/saspro/sfcc.py +1430 -0
  282. setiastro/saspro/shortcuts.py +3043 -0
  283. setiastro/saspro/signature_insert.py +1099 -0
  284. setiastro/saspro/stacking_suite.py +18181 -0
  285. setiastro/saspro/star_alignment.py +7420 -0
  286. setiastro/saspro/star_alignment_preset.py +329 -0
  287. setiastro/saspro/star_metrics.py +49 -0
  288. setiastro/saspro/star_spikes.py +681 -0
  289. setiastro/saspro/star_stretch.py +470 -0
  290. setiastro/saspro/stat_stretch.py +506 -0
  291. setiastro/saspro/status_log_dock.py +78 -0
  292. setiastro/saspro/subwindow.py +3267 -0
  293. setiastro/saspro/supernovaasteroidhunter.py +1716 -0
  294. setiastro/saspro/swap_manager.py +99 -0
  295. setiastro/saspro/torch_backend.py +89 -0
  296. setiastro/saspro/torch_rejection.py +434 -0
  297. setiastro/saspro/translations/de_translations.py +3733 -0
  298. setiastro/saspro/translations/es_translations.py +3923 -0
  299. setiastro/saspro/translations/fr_translations.py +3842 -0
  300. setiastro/saspro/translations/integrate_translations.py +234 -0
  301. setiastro/saspro/translations/it_translations.py +3662 -0
  302. setiastro/saspro/translations/ja_translations.py +3585 -0
  303. setiastro/saspro/translations/pt_translations.py +3853 -0
  304. setiastro/saspro/translations/saspro_de.qm +0 -0
  305. setiastro/saspro/translations/saspro_de.ts +253 -0
  306. setiastro/saspro/translations/saspro_es.qm +0 -0
  307. setiastro/saspro/translations/saspro_es.ts +12520 -0
  308. setiastro/saspro/translations/saspro_fr.qm +0 -0
  309. setiastro/saspro/translations/saspro_fr.ts +12514 -0
  310. setiastro/saspro/translations/saspro_it.qm +0 -0
  311. setiastro/saspro/translations/saspro_it.ts +12520 -0
  312. setiastro/saspro/translations/saspro_ja.qm +0 -0
  313. setiastro/saspro/translations/saspro_ja.ts +257 -0
  314. setiastro/saspro/translations/saspro_pt.qm +0 -0
  315. setiastro/saspro/translations/saspro_pt.ts +257 -0
  316. setiastro/saspro/translations/saspro_zh.qm +0 -0
  317. setiastro/saspro/translations/saspro_zh.ts +12520 -0
  318. setiastro/saspro/translations/zh_translations.py +3659 -0
  319. setiastro/saspro/versioning.py +71 -0
  320. setiastro/saspro/view_bundle.py +1555 -0
  321. setiastro/saspro/wavescale_hdr.py +624 -0
  322. setiastro/saspro/wavescale_hdr_preset.py +101 -0
  323. setiastro/saspro/wavescalede.py +658 -0
  324. setiastro/saspro/wavescalede_preset.py +230 -0
  325. setiastro/saspro/wcs_update.py +374 -0
  326. setiastro/saspro/whitebalance.py +456 -0
  327. setiastro/saspro/widgets/__init__.py +48 -0
  328. setiastro/saspro/widgets/common_utilities.py +306 -0
  329. setiastro/saspro/widgets/graphics_views.py +122 -0
  330. setiastro/saspro/widgets/image_utils.py +518 -0
  331. setiastro/saspro/widgets/preview_dialogs.py +280 -0
  332. setiastro/saspro/widgets/spinboxes.py +275 -0
  333. setiastro/saspro/widgets/themed_buttons.py +13 -0
  334. setiastro/saspro/widgets/wavelet_utils.py +299 -0
  335. setiastro/saspro/window_shelf.py +185 -0
  336. setiastro/saspro/xisf.py +1123 -0
  337. setiastrosuitepro-1.6.1.dist-info/METADATA +267 -0
  338. setiastrosuitepro-1.6.1.dist-info/RECORD +342 -0
  339. setiastrosuitepro-1.6.1.dist-info/WHEEL +4 -0
  340. setiastrosuitepro-1.6.1.dist-info/entry_points.txt +6 -0
  341. setiastrosuitepro-1.6.1.dist-info/licenses/LICENSE +674 -0
  342. setiastrosuitepro-1.6.1.dist-info/licenses/license.txt +2580 -0
@@ -0,0 +1,325 @@
1
+ from __future__ import annotations
2
+ import os
3
+ import glob
4
+ from pathlib import Path
5
+
6
+ from PyQt6.QtCore import Qt, QThread, pyqtSignal
7
+ from PyQt6.QtWidgets import (
8
+ QDialog, QVBoxLayout, QLabel, QLineEdit, QPushButton, QComboBox, QCheckBox,
9
+ QFileDialog, QHBoxLayout, QProgressBar, QMessageBox
10
+ )
11
+
12
+ from setiastro.saspro.legacy.image_manager import load_image as legacy_load_image, save_image as legacy_save_image
13
+
14
+
15
+ # --- helpers ---------------------------------------------------------------
16
+
17
+ _ALL_INPUT_PATTERNS = [
18
+ # science formats
19
+ "*.fit", "*.fits", "*.fts", "*.fz", "*.xisf",
20
+ # tiff/png/jpeg
21
+ "*.tif", "*.tiff", "*.png", "*.jpg", "*.jpeg",
22
+ # common RAWs
23
+ "*.cr2", "*.nef", "*.arw", "*.dng", "*.orf", "*.rw2", "*.pef",
24
+ ]
25
+
26
+ # Allowed bit-depths per output format (labels match your legacy.save_image)
27
+ _ALLOWED_DEPTHS = {
28
+ "png": {"8-bit"},
29
+ "jpg": {"8-bit"},
30
+ "jpeg": {"8-bit"},
31
+ "fits": {"32-bit floating point"},
32
+ "fit": {"32-bit floating point"},
33
+ "tiff": {"8-bit", "16-bit", "32-bit unsigned", "32-bit floating point"},
34
+ "tif": {"8-bit", "16-bit", "32-bit unsigned", "32-bit floating point"},
35
+ "xisf": {"16-bit", "32-bit unsigned", "32-bit floating point"},
36
+ }
37
+
38
+ from setiastro.saspro.file_utils import _normalize_ext
39
+
40
+ def _format_token_for_save(ext: str) -> str:
41
+ """
42
+ Map UI extension to save_image's `original_format` token.
43
+ """
44
+ e = _normalize_ext(ext)
45
+ if e == "tif": return "tiff"
46
+ if e == "jpg": return "jpg"
47
+ if e == "png": return "png"
48
+ if e in ("fit", "fits"): return e
49
+ if e == "xisf": return "xisf"
50
+ # default to fits if somehow unknown
51
+ return "fits"
52
+
53
+
54
+ # --- worker ----------------------------------------------------------------
55
+
56
+ class _BatchWorker(QThread):
57
+ progress = pyqtSignal(int, str) # (percent, message)
58
+ finished = pyqtSignal()
59
+ failed = pyqtSignal(str)
60
+
61
+ def __init__(self, in_dir: str, out_dir: str, out_ext: str,
62
+ recurse: bool, skip_existing: bool, bit_depth_choice: str):
63
+ super().__init__()
64
+ self.in_dir = in_dir
65
+ self.out_dir = out_dir
66
+ self.out_ext = out_ext # like ".fits"
67
+ self.recurse = recurse
68
+ self.skip_existing = skip_existing
69
+ self.bit_depth_choice = bit_depth_choice # "Auto" or one of _ALLOWED_DEPTHS per format
70
+ self._cancel = False
71
+
72
+ def cancel(self):
73
+ self._cancel = True
74
+
75
+ def _collect_files(self) -> list[str]:
76
+ files = []
77
+ pats = _ALL_INPUT_PATTERNS
78
+ if self.recurse:
79
+ for pat in pats:
80
+ files.extend(glob.glob(os.path.join(self.in_dir, "**", pat), recursive=True))
81
+ else:
82
+ for pat in pats:
83
+ files.extend(glob.glob(os.path.join(self.in_dir, pat)))
84
+ # unique + sorted
85
+ files = sorted(set(files))
86
+ return files
87
+
88
+ def run(self):
89
+ try:
90
+ files = self._collect_files()
91
+ n = len(files)
92
+ if not n:
93
+ self.failed.emit("No matching files in input directory.")
94
+ return
95
+
96
+ Path(self.out_dir).mkdir(parents=True, exist_ok=True)
97
+ out_token = _format_token_for_save(self.out_ext)
98
+ allowed = _ALLOWED_DEPTHS.get(_normalize_ext(self.out_ext), set())
99
+
100
+ for i, src in enumerate(files, start=1):
101
+ if self._cancel:
102
+ break
103
+
104
+ base = Path(src).stem
105
+ dst = os.path.join(self.out_dir, f"{base}{self.out_ext}")
106
+
107
+ if self.skip_existing and os.path.exists(dst):
108
+ self.progress.emit(int(i * 100 / n), f"Skipping (exists): {os.path.basename(dst)}")
109
+ continue
110
+
111
+ self.progress.emit(int((i - 1) * 100 / n), f"Loading {os.path.basename(src)}")
112
+ try:
113
+ img, header, src_bit_depth, is_mono = legacy_load_image(src)
114
+ except Exception as e:
115
+ self.progress.emit(int(i * 100 / n), f"Skipping (load failed): {os.path.basename(src)}")
116
+ continue
117
+
118
+ if img is None:
119
+ self.progress.emit(int(i * 100 / n), f"Skipping (unreadable): {os.path.basename(src)}")
120
+ continue
121
+
122
+ # Decide bit depth to use
123
+ if self.bit_depth_choice == "Auto":
124
+ # Prefer source bit depth if valid for target, else pick a sane default from allowed
125
+ if src_bit_depth in allowed:
126
+ bit_depth = src_bit_depth
127
+ else:
128
+ # fallbacks by format
129
+ if out_token in ("png", "jpg"):
130
+ bit_depth = "8-bit"
131
+ elif out_token in ("tiff", "xisf"):
132
+ # prefer float if available, else first allowed
133
+ bit_depth = "32-bit floating point" if "32-bit floating point" in allowed else next(iter(allowed)) if allowed else None
134
+ else: # fits/fit
135
+ bit_depth = "32-bit floating point"
136
+ else:
137
+ bit_depth = self.bit_depth_choice
138
+ if allowed and bit_depth not in allowed:
139
+ # shouldn't happen because UI filters, but guard anyway
140
+ bit_depth = next(iter(allowed))
141
+
142
+ # Write
143
+ self.progress.emit(int((i - 1) * 100 / n), f"Saving {os.path.basename(dst)}")
144
+ try:
145
+ legacy_save_image(
146
+ img_array=img,
147
+ filename=dst,
148
+ original_format=out_token,
149
+ bit_depth=bit_depth,
150
+ original_header=header, # preserves FITS keywords when saving to FITS
151
+ is_mono=is_mono,
152
+ image_meta=None,
153
+ file_meta=None,
154
+ )
155
+ except Exception as e:
156
+ self.progress.emit(int(i * 100 / n), f"ERROR: {os.path.basename(base)} → {e}")
157
+ continue
158
+
159
+ self.progress.emit(int(i * 100 / n), f"Saved {os.path.basename(dst)}")
160
+
161
+ self.finished.emit()
162
+ except Exception as e:
163
+ self.failed.emit(str(e))
164
+
165
+
166
+ # --- dialog ----------------------------------------------------------------
167
+
168
+ class BatchConvertDialog(QDialog):
169
+ def __init__(self, parent=None):
170
+ super().__init__(parent)
171
+ self.setWindowTitle(self.tr("Batch Convert"))
172
+ self.setMinimumWidth(560)
173
+ self.worker: _BatchWorker | None = None
174
+
175
+ lay = QVBoxLayout(self)
176
+
177
+ # in dir
178
+ self.in_edit = QLineEdit()
179
+ in_row = self._row("Input folder:", self.in_edit, self._browse_in)
180
+ lay.addLayout(in_row)
181
+
182
+ # out dir
183
+ self.out_edit = QLineEdit()
184
+ out_row = self._row("Output folder:", self.out_edit, self._browse_out)
185
+ lay.addLayout(out_row)
186
+
187
+ # options row
188
+ opt_row = QHBoxLayout()
189
+
190
+ self.recurse_cb = QCheckBox("Recurse subfolders")
191
+ self.recurse_cb.setChecked(True)
192
+ opt_row.addWidget(self.recurse_cb)
193
+
194
+ self.skip_cb = QCheckBox("Skip existing")
195
+ self.skip_cb.setChecked(True)
196
+ opt_row.addWidget(self.skip_cb)
197
+
198
+ opt_row.addStretch(1)
199
+ lay.addLayout(opt_row)
200
+
201
+ # output format + bit depth
202
+ fmt_row = QHBoxLayout()
203
+ self.fmt = QComboBox()
204
+ self.fmt.addItems([".fits", ".fit", ".tif", ".tiff", ".png", ".jpg", ".jpeg", ".xisf"])
205
+ self.fmt.currentIndexChanged.connect(self._refresh_depth_choices)
206
+
207
+ self.depth = QComboBox()
208
+ self.depth.addItem("Auto") # always first
209
+ # choices will be populated based on fmt
210
+
211
+ fmt_row.addWidget(QLabel(self.tr("Output format:")))
212
+ fmt_row.addWidget(self.fmt)
213
+ fmt_row.addSpacing(16)
214
+ fmt_row.addWidget(QLabel(self.tr("Bit depth:")))
215
+ fmt_row.addWidget(self.depth)
216
+ fmt_row.addStretch(1)
217
+ lay.addLayout(fmt_row)
218
+
219
+ # status/progress
220
+ self.status = QLabel("")
221
+ self.bar = QProgressBar()
222
+ self.bar.setRange(0, 100)
223
+
224
+ # buttons
225
+ self.start_btn = QPushButton(self.tr("Start"))
226
+ self.cancel_btn = QPushButton(self.tr("Cancel"))
227
+ self.cancel_btn.setEnabled(False)
228
+ btns = QHBoxLayout()
229
+ btns.addStretch(1)
230
+ btns.addWidget(self.start_btn)
231
+ btns.addWidget(self.cancel_btn)
232
+
233
+ lay.addSpacing(8)
234
+ lay.addWidget(self.status)
235
+ lay.addWidget(self.bar)
236
+ lay.addLayout(btns)
237
+
238
+ self.start_btn.clicked.connect(self._start)
239
+ self.cancel_btn.clicked.connect(self._cancel)
240
+
241
+ # initialize bit-depth choices
242
+ self._refresh_depth_choices()
243
+
244
+ def _row(self, label: str, line: QLineEdit, browse_fn):
245
+ hb = QHBoxLayout()
246
+ hb.addWidget(QLabel(label))
247
+ hb.addWidget(line, 1)
248
+ b = QPushButton("Browse…")
249
+ b.clicked.connect(browse_fn)
250
+ hb.addWidget(b)
251
+ return hb
252
+
253
+ def _browse_in(self):
254
+ d = QFileDialog.getExistingDirectory(self, "Choose Input Folder", self.in_edit.text().strip() or "")
255
+ if d:
256
+ self.in_edit.setText(d)
257
+
258
+ def _browse_out(self):
259
+ d = QFileDialog.getExistingDirectory(self, "Choose Output Folder", self.out_edit.text().strip() or "")
260
+ if d:
261
+ self.out_edit.setText(d)
262
+
263
+ def _refresh_depth_choices(self):
264
+ self.depth.blockSignals(True)
265
+ cur_fmt = self.fmt.currentText().lstrip(".").lower()
266
+ self.depth.clear()
267
+ self.depth.addItem("Auto")
268
+ # Map .tif/.tiff etc.
269
+ key = _normalize_ext(cur_fmt)
270
+ allowed = _ALLOWED_DEPTHS.get(key if key != "tif" else "tiff", set())
271
+ for d in sorted(allowed):
272
+ self.depth.addItem(d)
273
+ # pick a sensible default
274
+ self.depth.setCurrentIndex(0)
275
+ self.depth.blockSignals(False)
276
+
277
+ def _start(self):
278
+ in_dir = self.in_edit.text().strip()
279
+ out_dir = self.out_edit.text().strip()
280
+ if not in_dir or not out_dir:
281
+ QMessageBox.warning(self, "Batch Convert", "Pick input and output folders.")
282
+ return
283
+
284
+ if os.path.abspath(in_dir) == os.path.abspath(out_dir):
285
+ # you *can* convert in place, but warn if target ext might overwrite sources
286
+ QMessageBox.information(
287
+ self, "In-place Notice",
288
+ "Input and output folders are the same. Existing files with the same base name and extension may be overwritten."
289
+ )
290
+
291
+ out_ext = self.fmt.currentText()
292
+ recurse = self.recurse_cb.isChecked()
293
+ skip_existing = self.skip_cb.isChecked()
294
+ bit_depth_choice = self.depth.currentText() # "Auto" or explicit
295
+
296
+ self.worker = _BatchWorker(in_dir, out_dir, out_ext, recurse, skip_existing, bit_depth_choice)
297
+ self.worker.progress.connect(self._on_progress)
298
+ self.worker.finished.connect(self._on_finished)
299
+ self.worker.failed.connect(self._on_failed)
300
+
301
+ self.start_btn.setEnabled(False)
302
+ self.cancel_btn.setEnabled(True)
303
+ self.status.setText("Starting…")
304
+ self.bar.setValue(0)
305
+ self.worker.start()
306
+
307
+ def _cancel(self):
308
+ if self.worker:
309
+ self.worker.cancel()
310
+ self.cancel_btn.setEnabled(False)
311
+
312
+ def _on_progress(self, pct: int, msg: str):
313
+ self.bar.setValue(pct)
314
+ self.status.setText(msg)
315
+
316
+ def _on_finished(self):
317
+ self.start_btn.setEnabled(True)
318
+ self.cancel_btn.setEnabled(False)
319
+ self.status.setText("Done.")
320
+
321
+ def _on_failed(self, err: str):
322
+ self.start_btn.setEnabled(True)
323
+ self.cancel_btn.setEnabled(False)
324
+ self.status.setText("Failed.")
325
+ QMessageBox.critical(self, "Batch Convert", err)