setiastrosuitepro 1.6.0__py3-none-any.whl → 1.6.4.post1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (293) hide show
  1. setiastro/data/SASP_data.fits +0 -0
  2. setiastro/data/catalogs/List_of_Galaxies_with_Distances_Gly.csv +488 -0
  3. setiastro/data/catalogs/astrobin_filters.csv +890 -0
  4. setiastro/data/catalogs/astrobin_filters_page1_local.csv +51 -0
  5. setiastro/data/catalogs/cali2.csv +63 -0
  6. setiastro/data/catalogs/cali2color.csv +65 -0
  7. setiastro/data/catalogs/celestial_catalog - original.csv +16471 -0
  8. setiastro/data/catalogs/celestial_catalog.csv +24031 -0
  9. setiastro/data/catalogs/detected_stars.csv +24784 -0
  10. setiastro/data/catalogs/fits_header_data.csv +46 -0
  11. setiastro/data/catalogs/test.csv +8 -0
  12. setiastro/data/catalogs/updated_celestial_catalog.csv +16471 -0
  13. setiastro/images/Astro_Spikes.png +0 -0
  14. setiastro/images/Background_startup.jpg +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/rotatearbitrary.png +0 -0
  106. setiastro/images/rotateclockwise.png +0 -0
  107. setiastro/images/rotatecounterclockwise.png +0 -0
  108. setiastro/images/satellite.png +0 -0
  109. setiastro/images/script.png +0 -0
  110. setiastro/images/selectivecolor.png +0 -0
  111. setiastro/images/simbad.png +0 -0
  112. setiastro/images/slot0.png +0 -0
  113. setiastro/images/slot1.png +0 -0
  114. setiastro/images/slot2.png +0 -0
  115. setiastro/images/slot3.png +0 -0
  116. setiastro/images/slot4.png +0 -0
  117. setiastro/images/slot5.png +0 -0
  118. setiastro/images/slot6.png +0 -0
  119. setiastro/images/slot7.png +0 -0
  120. setiastro/images/slot8.png +0 -0
  121. setiastro/images/slot9.png +0 -0
  122. setiastro/images/spcc.png +0 -0
  123. setiastro/images/spin_precession_vs_lunar_distance.png +0 -0
  124. setiastro/images/spinner.gif +0 -0
  125. setiastro/images/stacking.png +0 -0
  126. setiastro/images/staradd.png +0 -0
  127. setiastro/images/staralign.png +0 -0
  128. setiastro/images/starnet.png +0 -0
  129. setiastro/images/starregistration.png +0 -0
  130. setiastro/images/starspike.png +0 -0
  131. setiastro/images/starstretch.png +0 -0
  132. setiastro/images/statstretch.png +0 -0
  133. setiastro/images/supernova.png +0 -0
  134. setiastro/images/uhs.png +0 -0
  135. setiastro/images/undoicon.png +0 -0
  136. setiastro/images/upscale.png +0 -0
  137. setiastro/images/viewbundle.png +0 -0
  138. setiastro/images/whitebalance.png +0 -0
  139. setiastro/images/wimi_icon_256x256.png +0 -0
  140. setiastro/images/wimilogo.png +0 -0
  141. setiastro/images/wims.png +0 -0
  142. setiastro/images/wrench_icon.png +0 -0
  143. setiastro/images/xisfliberator.png +0 -0
  144. setiastro/qml/ResourceMonitor.qml +126 -0
  145. setiastro/saspro/__main__.py +228 -67
  146. setiastro/saspro/_generated/build_info.py +2 -1
  147. setiastro/saspro/abe.py +76 -25
  148. setiastro/saspro/aberration_ai.py +14 -14
  149. setiastro/saspro/add_stars.py +15 -12
  150. setiastro/saspro/astrobin_exporter.py +61 -58
  151. setiastro/saspro/astrospike_python.py +3 -1
  152. setiastro/saspro/autostretch.py +4 -2
  153. setiastro/saspro/backgroundneutral.py +65 -14
  154. setiastro/saspro/batch_convert.py +8 -5
  155. setiastro/saspro/batch_renamer.py +39 -36
  156. setiastro/saspro/blemish_blaster.py +15 -12
  157. setiastro/saspro/blink_comparator_pro.py +605 -379
  158. setiastro/saspro/cheat_sheet.py +62 -17
  159. setiastro/saspro/clahe.py +34 -8
  160. setiastro/saspro/comet_stacking.py +103 -38
  161. setiastro/saspro/common_tr.py +107 -0
  162. setiastro/saspro/continuum_subtract.py +7 -7
  163. setiastro/saspro/convo.py +12 -9
  164. setiastro/saspro/copyastro.py +3 -0
  165. setiastro/saspro/cosmicclarity.py +77 -52
  166. setiastro/saspro/crop_dialog_pro.py +80 -45
  167. setiastro/saspro/curve_editor_pro.py +51 -33
  168. setiastro/saspro/debayer.py +6 -3
  169. setiastro/saspro/doc_manager.py +49 -19
  170. setiastro/saspro/exoplanet_detector.py +11 -11
  171. setiastro/saspro/fitsmodifier.py +48 -44
  172. setiastro/saspro/fix_bom.py +32 -0
  173. setiastro/saspro/frequency_separation.py +18 -12
  174. setiastro/saspro/function_bundle.py +18 -16
  175. setiastro/saspro/generate_translations.py +3092 -0
  176. setiastro/saspro/ghs_dialog_pro.py +19 -16
  177. setiastro/saspro/graxpert.py +3 -0
  178. setiastro/saspro/gui/main_window.py +471 -126
  179. setiastro/saspro/gui/mixins/dock_mixin.py +123 -11
  180. setiastro/saspro/gui/mixins/file_mixin.py +25 -20
  181. setiastro/saspro/gui/mixins/geometry_mixin.py +115 -15
  182. setiastro/saspro/gui/mixins/header_mixin.py +6 -6
  183. setiastro/saspro/gui/mixins/mask_mixin.py +8 -8
  184. setiastro/saspro/gui/mixins/menu_mixin.py +62 -33
  185. setiastro/saspro/gui/mixins/toolbar_mixin.py +382 -226
  186. setiastro/saspro/gui/mixins/update_mixin.py +26 -26
  187. setiastro/saspro/gui/statistics_dialog.py +47 -0
  188. setiastro/saspro/halobgon.py +29 -3
  189. setiastro/saspro/header_viewer.py +21 -18
  190. setiastro/saspro/histogram.py +29 -26
  191. setiastro/saspro/history_explorer.py +2 -0
  192. setiastro/saspro/i18n.py +168 -0
  193. setiastro/saspro/image_combine.py +3 -0
  194. setiastro/saspro/image_peeker_pro.py +52 -44
  195. setiastro/saspro/imageops/stretch.py +5 -13
  196. setiastro/saspro/isophote.py +3 -0
  197. setiastro/saspro/legacy/numba_utils.py +64 -47
  198. setiastro/saspro/linear_fit.py +3 -0
  199. setiastro/saspro/live_stacking.py +13 -2
  200. setiastro/saspro/mask_creation.py +180 -22
  201. setiastro/saspro/mfdeconv.py +5 -0
  202. setiastro/saspro/morphology.py +38 -13
  203. setiastro/saspro/multiscale_decomp.py +713 -256
  204. setiastro/saspro/nbtorgb_stars.py +12 -2
  205. setiastro/saspro/numba_utils.py +149 -48
  206. setiastro/saspro/ops/scripts.py +77 -17
  207. setiastro/saspro/ops/settings.py +177 -100
  208. setiastro/saspro/perfect_palette_picker.py +25 -7
  209. setiastro/saspro/pixelmath.py +114 -110
  210. setiastro/saspro/plate_solver.py +118 -108
  211. setiastro/saspro/remove_green.py +24 -7
  212. setiastro/saspro/remove_stars.py +136 -162
  213. setiastro/saspro/remove_stars_preset.py +55 -13
  214. setiastro/saspro/resources.py +46 -15
  215. setiastro/saspro/rgb_combination.py +19 -18
  216. setiastro/saspro/rgbalign.py +11 -11
  217. setiastro/saspro/save_options.py +5 -4
  218. setiastro/saspro/selective_color.py +84 -25
  219. setiastro/saspro/sfcc.py +119 -72
  220. setiastro/saspro/shortcuts.py +345 -36
  221. setiastro/saspro/signature_insert.py +4 -1
  222. setiastro/saspro/stacking_suite.py +2066 -1119
  223. setiastro/saspro/star_alignment.py +291 -331
  224. setiastro/saspro/star_spikes.py +137 -53
  225. setiastro/saspro/star_stretch.py +47 -10
  226. setiastro/saspro/stat_stretch.py +52 -16
  227. setiastro/saspro/status_log_dock.py +1 -1
  228. setiastro/saspro/subwindow.py +97 -36
  229. setiastro/saspro/supernovaasteroidhunter.py +68 -61
  230. setiastro/saspro/swap_manager.py +77 -42
  231. setiastro/saspro/translations/all_source_strings.json +4726 -0
  232. setiastro/saspro/translations/ar_translations.py +4096 -0
  233. setiastro/saspro/translations/de_translations.py +3728 -0
  234. setiastro/saspro/translations/es_translations.py +4169 -0
  235. setiastro/saspro/translations/fr_translations.py +4090 -0
  236. setiastro/saspro/translations/hi_translations.py +3803 -0
  237. setiastro/saspro/translations/integrate_translations.py +271 -0
  238. setiastro/saspro/translations/it_translations.py +4728 -0
  239. setiastro/saspro/translations/ja_translations.py +3834 -0
  240. setiastro/saspro/translations/pt_translations.py +3847 -0
  241. setiastro/saspro/translations/ru_translations.py +3082 -0
  242. setiastro/saspro/translations/saspro_ar.qm +0 -0
  243. setiastro/saspro/translations/saspro_ar.ts +16019 -0
  244. setiastro/saspro/translations/saspro_de.qm +0 -0
  245. setiastro/saspro/translations/saspro_de.ts +14548 -0
  246. setiastro/saspro/translations/saspro_es.qm +0 -0
  247. setiastro/saspro/translations/saspro_es.ts +16202 -0
  248. setiastro/saspro/translations/saspro_fr.qm +0 -0
  249. setiastro/saspro/translations/saspro_fr.ts +15870 -0
  250. setiastro/saspro/translations/saspro_hi.qm +0 -0
  251. setiastro/saspro/translations/saspro_hi.ts +14855 -0
  252. setiastro/saspro/translations/saspro_it.qm +0 -0
  253. setiastro/saspro/translations/saspro_it.ts +19046 -0
  254. setiastro/saspro/translations/saspro_ja.qm +0 -0
  255. setiastro/saspro/translations/saspro_ja.ts +14980 -0
  256. setiastro/saspro/translations/saspro_pt.qm +0 -0
  257. setiastro/saspro/translations/saspro_pt.ts +15024 -0
  258. setiastro/saspro/translations/saspro_ru.qm +0 -0
  259. setiastro/saspro/translations/saspro_ru.ts +11835 -0
  260. setiastro/saspro/translations/saspro_sw.qm +0 -0
  261. setiastro/saspro/translations/saspro_sw.ts +15237 -0
  262. setiastro/saspro/translations/saspro_uk.qm +0 -0
  263. setiastro/saspro/translations/saspro_uk.ts +15248 -0
  264. setiastro/saspro/translations/saspro_zh.qm +0 -0
  265. setiastro/saspro/translations/saspro_zh.ts +15289 -0
  266. setiastro/saspro/translations/sw_translations.py +3897 -0
  267. setiastro/saspro/translations/uk_translations.py +3929 -0
  268. setiastro/saspro/translations/zh_translations.py +3910 -0
  269. setiastro/saspro/versioning.py +77 -0
  270. setiastro/saspro/view_bundle.py +20 -17
  271. setiastro/saspro/wavescale_hdr.py +54 -33
  272. setiastro/saspro/wavescale_hdr_preset.py +6 -5
  273. setiastro/saspro/wavescalede.py +54 -31
  274. setiastro/saspro/wavescalede_preset.py +9 -7
  275. setiastro/saspro/whitebalance.py +58 -22
  276. setiastro/saspro/widgets/common_utilities.py +12 -11
  277. setiastro/saspro/widgets/minigame/game.js +991 -0
  278. setiastro/saspro/widgets/minigame/index.html +53 -0
  279. setiastro/saspro/widgets/minigame/style.css +241 -0
  280. setiastro/saspro/widgets/preview_dialogs.py +8 -8
  281. setiastro/saspro/widgets/resource_monitor.py +263 -0
  282. setiastro/saspro/widgets/spinboxes.py +18 -0
  283. setiastro/saspro/widgets/wavelet_utils.py +52 -20
  284. setiastro/saspro/wimi.py +7996 -0
  285. setiastro/saspro/wims.py +578 -0
  286. setiastro/saspro/window_shelf.py +2 -2
  287. {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/METADATA +15 -3
  288. setiastrosuitepro-1.6.4.post1.dist-info/RECORD +368 -0
  289. setiastrosuitepro-1.6.0.dist-info/RECORD +0 -174
  290. {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/WHEEL +0 -0
  291. {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/entry_points.txt +0 -0
  292. {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/licenses/LICENSE +0 -0
  293. {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/licenses/license.txt +0 -0
@@ -30,7 +30,7 @@ class FITSModifier(QDialog):
30
30
  doc_manager=None, active_document=None, # <— rename param
31
31
  parent=None):
32
32
  super().__init__(parent)
33
- self.setWindowTitle("FITS Header Editor")
33
+ self.setWindowTitle(self.tr("FITS Header Editor"))
34
34
  self.resize(800, 600)
35
35
 
36
36
  self._doc_manager = doc_manager
@@ -48,17 +48,17 @@ class FITSModifier(QDialog):
48
48
 
49
49
  # UI
50
50
  top = QHBoxLayout()
51
- self.path_label = QLabel(self.file_path or "(no file)")
52
- self.open_btn = QPushButton("Open FITS…")
53
- self.reload_btn = QPushButton("Reload")
51
+ self.path_label = QLabel(self.file_path or self.tr("(no file)"))
52
+ self.open_btn = QPushButton(self.tr("Open FITS…"))
53
+ self.reload_btn = QPushButton(self.tr("Reload"))
54
54
  self.hdu_combo = QComboBox()
55
- self.save_btn = QPushButton("Save")
56
- self.saveas_btn = QPushButton("Save a Copy As…")
55
+ self.save_btn = QPushButton(self.tr("Save"))
56
+ self.saveas_btn = QPushButton(self.tr("Save a Copy As…"))
57
57
  # self.apply_to_slot_btn = QPushButton("Apply to Slot Metadata") # optional
58
58
 
59
- top.addWidget(QLabel("File:"))
59
+ top.addWidget(QLabel(self.tr("File:")))
60
60
  top.addWidget(self.path_label, 1)
61
- top.addWidget(QLabel("HDU:"))
61
+ top.addWidget(QLabel(self.tr("HDU:")))
62
62
  top.addWidget(self.hdu_combo)
63
63
  top.addWidget(self.open_btn)
64
64
  top.addWidget(self.reload_btn)
@@ -67,14 +67,14 @@ class FITSModifier(QDialog):
67
67
  # top.addWidget(self.apply_to_slot_btn)
68
68
 
69
69
  batch = QHBoxLayout()
70
- self.batch_btn = QPushButton("Batch Modify...")
70
+ self.batch_btn = QPushButton(self.tr("Batch Modify..."))
71
71
  batch.addStretch()
72
72
  batch.addWidget(self.batch_btn)
73
73
  batch.addStretch()
74
74
 
75
75
  self.tree = QTreeWidget()
76
76
  self.tree.setColumnCount(3)
77
- self.tree.setHeaderLabels(["Keyword", "Value", "Comment"])
77
+ self.tree.setHeaderLabels([self.tr("Keyword"), self.tr("Value"), self.tr("Comment")])
78
78
  self.tree.setAlternatingRowColors(True)
79
79
  self.tree.setRootIsDecorated(False)
80
80
  self.tree.setEditTriggers(QTreeWidget.EditTrigger.DoubleClicked | QTreeWidget.EditTrigger.SelectedClicked)
@@ -89,12 +89,12 @@ class FITSModifier(QDialog):
89
89
  """)
90
90
 
91
91
  bottom = QHBoxLayout()
92
- self.add_key_edit = QLineEdit(); self.add_key_edit.setPlaceholderText("KEYWORD")
93
- self.add_val_edit = QLineEdit(); self.add_val_edit.setPlaceholderText("Value")
94
- self.add_com_edit = QLineEdit(); self.add_com_edit.setPlaceholderText("Comment (optional)")
95
- self.add_btn = QPushButton("Add/Update")
96
- self.del_btn = QPushButton("Delete Selected")
97
- self.all_hdus_chk = QCheckBox("Apply add/update/delete to all HDUs")
92
+ self.add_key_edit = QLineEdit(); self.add_key_edit.setPlaceholderText(self.tr("KEYWORD"))
93
+ self.add_val_edit = QLineEdit(); self.add_val_edit.setPlaceholderText(self.tr("Value"))
94
+ self.add_com_edit = QLineEdit(); self.add_com_edit.setPlaceholderText(self.tr("Comment (optional)"))
95
+ self.add_btn = QPushButton(self.tr("Add/Update"))
96
+ self.del_btn = QPushButton(self.tr("Delete Selected"))
97
+ self.all_hdus_chk = QCheckBox(self.tr("Apply add/update/delete to all HDUs"))
98
98
  bottom.addWidget(self.add_key_edit)
99
99
  bottom.addWidget(self.add_val_edit)
100
100
  bottom.addWidget(self.add_com_edit)
@@ -210,7 +210,7 @@ class FITSModifier(QDialog):
210
210
 
211
211
  def _set_dirty(self, dirty=True):
212
212
  self._dirty = dirty
213
- self.setWindowTitle("FITS Header Editor" + (" *" if dirty else ""))
213
+ self.setWindowTitle(self.tr("FITS Header Editor") + (" *" if dirty else ""))
214
214
 
215
215
  def _sync_tree_to_header(self):
216
216
  if not self.hdul:
@@ -219,7 +219,7 @@ class FITSModifier(QDialog):
219
219
  self._collect_tree_into_header(hdr)
220
220
 
221
221
  def _choose_file(self):
222
- fn, _ = QFileDialog.getOpenFileName(self, "Open FITS", self._last_dir(), "FITS files (*.fits *.fit *.fts *.fz)")
222
+ fn, _ = QFileDialog.getOpenFileName(self, self.tr("Open FITS"), self._last_dir(), self.tr("FITS files (*.fits *.fit *.fts *.fz)"))
223
223
  if not fn:
224
224
  return
225
225
  self._load_file(fn)
@@ -233,12 +233,12 @@ class FITSModifier(QDialog):
233
233
  try:
234
234
  self.hdul = fits.open(path, mode='update', memmap=False)
235
235
  except Exception as e:
236
- QMessageBox.warning(self, "Invalid FITS",
237
- f"This file does not appear to be a valid FITS:\n\n{path}\n\n{e}\n\n"
238
- "Tip: Choose a FITS file via 'Open FITS…' or edit an in-memory header.")
236
+ QMessageBox.warning(self, self.tr("Invalid FITS"),
237
+ self.tr("This file does not appear to be a valid FITS:\n\n{0}\n\n{1}\n\n"
238
+ "Tip: Choose a FITS file via 'Open FITS…' or edit an in-memory header.").format(path, e))
239
239
  self.hdul = None
240
240
  self.file_path = None
241
- self.path_label.setText("(no file)")
241
+ self.path_label.setText(self.tr("(no file)"))
242
242
  self.hdu_combo.clear()
243
243
  self._update_multi_hdu_ui()
244
244
  return False
@@ -373,7 +373,7 @@ class FITSModifier(QDialog):
373
373
  """
374
374
  doc = self._active_doc()
375
375
  if doc is None or doc.image is None:
376
- QMessageBox.warning(self, "No Image", "No active image/document to save.")
376
+ QMessageBox.warning(self, self.tr("No Image"), self.tr("No active image/document to save."))
377
377
  return False
378
378
 
379
379
  edited_hdr = self._edited_primary_header()
@@ -401,7 +401,7 @@ class FITSModifier(QDialog):
401
401
  file_meta=file_meta,
402
402
  )
403
403
  except Exception as e:
404
- QMessageBox.critical(self, "Save Error", f"Could not save:\n{e}")
404
+ QMessageBox.critical(self, self.tr("Save Error"), self.tr("Could not save:\n{0}").format(e))
405
405
  return False
406
406
 
407
407
  if update_doc_metadata:
@@ -436,9 +436,9 @@ class FITSModifier(QDialog):
436
436
  self._sync_tree_to_header()
437
437
  last = self._settings().value("fits_modifier/last_dir", "", type=str) or ""
438
438
  path, _ = QFileDialog.getSaveFileName(
439
- self, "Save Image As",
439
+ self, self.tr("Save Image As"),
440
440
  last,
441
- "FITS (*.fits *.fit);;TIFF (*.tif *.tiff);;PNG (*.png);;JPEG (*.jpg *.jpeg);;XISF (*.xisf)"
441
+ self.tr("FITS (*.fits *.fit);;TIFF (*.tif *.tiff);;PNG (*.png);;JPEG (*.jpg *.jpeg);;XISF (*.xisf)")
442
442
  )
443
443
  if not path:
444
444
  return
@@ -446,7 +446,7 @@ class FITSModifier(QDialog):
446
446
  if ok:
447
447
  self._save_last_dir(os.path.dirname(path))
448
448
  # Optional: toast confirmation only
449
- QMessageBox.information(self, "Saved Copy", f"Saved a copy to:\n{path}")
449
+ QMessageBox.information(self, self.tr("Saved Copy"), self.tr("Saved a copy to:\n{0}").format(path))
450
450
 
451
451
 
452
452
  def _save_to_path(self, path: str) -> bool:
@@ -605,27 +605,31 @@ class FITSModifier(QDialog):
605
605
  class BatchFITSHeaderDialog(QDialog):
606
606
  def __init__(self, parent=None, preset_keyword: str = "", preset_value: str = "", preset_comment: str = ""):
607
607
  super().__init__(parent)
608
- self.setWindowTitle("Batch Modify FITS Headers")
608
+ self.setWindowTitle(self.tr("Batch Modify FITS Headers"))
609
+ self.setWindowFlag(Qt.WindowType.Window, True)
610
+ self.setWindowModality(Qt.WindowModality.NonModal)
611
+ self.setModal(False)
609
612
  self.resize(520, 220)
610
613
 
611
614
  v = QVBoxLayout(self)
612
615
 
613
616
  row1 = QHBoxLayout()
614
- self.files_edit = QLineEdit(); self.files_edit.setPlaceholderText("No files selected")
615
- self.pick_btn = QPushButton("Choose FITS Files…")
617
+ self.files_edit = QLineEdit(); self.files_edit.setPlaceholderText(self.tr("No files selected"))
618
+ self.pick_btn = QPushButton(self.tr("Choose FITS Files…"))
616
619
  row1.addWidget(self.files_edit, 1); row1.addWidget(self.pick_btn)
617
620
 
618
621
  row2 = QHBoxLayout()
619
- self.key_edit = QLineEdit(); self.key_edit.setPlaceholderText("KEYWORD")
620
- self.val_edit = QLineEdit(); self.val_edit.setPlaceholderText("Value (leave blank for delete)")
621
- self.com_edit = QLineEdit(); self.com_edit.setPlaceholderText("Comment (optional)")
622
+ self.key_edit = QLineEdit(); self.key_edit.setPlaceholderText(self.tr("KEYWORD"))
623
+ self.val_edit = QLineEdit(); self.val_edit.setPlaceholderText(self.tr("Value (leave blank for delete)"))
624
+ self.com_edit = QLineEdit(); self.com_edit.setPlaceholderText(self.tr("Comment (optional)"))
622
625
  row2.addWidget(self.key_edit); row2.addWidget(self.val_edit); row2.addWidget(self.com_edit)
623
626
 
624
627
  row3 = QHBoxLayout()
625
628
  self.mode_combo = QComboBox()
626
- self.mode_combo.addItems(["Add/Update", "Delete"])
627
- self.all_hdus_chk = QCheckBox("Apply to all HDUs")
628
- self.add_if_missing_chk = QCheckBox("Add if missing (for Add/Update)")
629
+ self.mode_combo.addItem(self.tr("Add/Update"), "Add/Update")
630
+ self.mode_combo.addItem(self.tr("Delete"), "Delete")
631
+ self.all_hdus_chk = QCheckBox(self.tr("Apply to all HDUs"))
632
+ self.add_if_missing_chk = QCheckBox(self.tr("Add if missing (for Add/Update)"))
629
633
  self.add_if_missing_chk.setChecked(True)
630
634
  row3.addWidget(self.mode_combo)
631
635
  row3.addWidget(self.all_hdus_chk)
@@ -633,8 +637,8 @@ class BatchFITSHeaderDialog(QDialog):
633
637
  row3.addStretch()
634
638
 
635
639
  row4 = QHBoxLayout()
636
- self.run_btn = QPushButton("Run")
637
- self.close_btn = QPushButton("Close")
640
+ self.run_btn = QPushButton(self.tr("Run"))
641
+ self.close_btn = QPushButton(self.tr("Close"))
638
642
  row4.addStretch(); row4.addWidget(self.run_btn); row4.addWidget(self.close_btn)
639
643
 
640
644
  v.addLayout(row1)
@@ -660,11 +664,11 @@ class BatchFITSHeaderDialog(QDialog):
660
664
 
661
665
  def _pick_files(self):
662
666
  last = self._settings().value("fits_modifier/batch_dir", "", type=str) or ""
663
- files, _ = QFileDialog.getOpenFileNames(self, "Select FITS files", last, "FITS files (*.fits *.fit *.fts *.fz)")
667
+ files, _ = QFileDialog.getOpenFileNames(self, self.tr("Select FITS files"), last, self.tr("FITS files (*.fits *.fit *.fts *.fz)"))
664
668
  if not files:
665
669
  return
666
670
  self.files = files
667
- self.files_edit.setText(f"{len(files)} files selected")
671
+ self.files_edit.setText(self.tr("{0} files selected").format(len(files)))
668
672
  self._settings().setValue("fits_modifier/batch_dir", os.path.dirname(files[0]))
669
673
 
670
674
  def _parse_val(self, s: str):
@@ -689,14 +693,14 @@ class BatchFITSHeaderDialog(QDialog):
689
693
 
690
694
  def _run(self):
691
695
  if not self.files:
692
- QMessageBox.warning(self, "No files", "Please choose one or more FITS files.")
696
+ QMessageBox.warning(self, self.tr("No files"), self.tr("Please choose one or more FITS files."))
693
697
  return
694
698
  key = self.key_edit.text().strip()
695
699
  if not key:
696
- QMessageBox.warning(self, "Missing keyword", "Please enter a FITS keyword.")
700
+ QMessageBox.warning(self, self.tr("Missing keyword"), self.tr("Please enter a FITS keyword."))
697
701
  return
698
702
 
699
- mode = self.mode_combo.currentText()
703
+ mode = self.mode_combo.currentData()
700
704
  apply_all_hdus = self.all_hdus_chk.isChecked()
701
705
  add_if_missing = self.add_if_missing_chk.isChecked()
702
706
  com = self.com_edit.text().strip()
@@ -741,4 +745,4 @@ class BatchFITSHeaderDialog(QDialog):
741
745
  print(f"[Batch FITS] Error on {fp}: {e}")
742
746
  n_err += 1
743
747
 
744
- QMessageBox.information(self, "Batch Complete", f"Updated {n_ok} file(s); {n_err} error(s).")
748
+ QMessageBox.information(self, self.tr("Batch Complete"), self.tr("Updated {0} file(s); {1} error(s).").format(n_ok, n_err))
@@ -0,0 +1,32 @@
1
+
2
+ import os
3
+ import codecs
4
+
5
+ def remove_bom(base_dir):
6
+ count = 0
7
+ for root, dirs, files in os.walk(base_dir):
8
+ for name in files:
9
+ if name.endswith(".py"):
10
+ path = os.path.join(root, name)
11
+ try:
12
+ with open(path, 'rb') as f:
13
+ raw = f.read(3)
14
+
15
+ if raw.startswith(codecs.BOM_UTF8):
16
+ print(f"Removing BOM from {path}")
17
+ with open(path, 'rb') as f:
18
+ content = f.read()
19
+
20
+ # strip BOM
21
+ content = content[3:]
22
+
23
+ with open(path, 'wb') as f:
24
+ f.write(content)
25
+ count += 1
26
+ except Exception as e:
27
+ print(f"Error processing {path}: {e}")
28
+ print(f"Removed BOM from {count} files.")
29
+
30
+ if __name__ == "__main__":
31
+ base_dir = os.path.dirname(os.path.abspath(__file__))
32
+ remove_bom(base_dir)
@@ -237,7 +237,7 @@ class FrequencySeperationTab(QWidget):
237
237
  left.addWidget(self.fileLabel)
238
238
 
239
239
  # Method
240
- left.addWidget(QLabel("Method:", self))
240
+ left.addWidget(QLabel(self.tr("Method:"), self))
241
241
  self.method_combo = QComboBox(self)
242
242
  self.method_combo.addItems(['Gaussian', 'Median', 'Bilateral'])
243
243
  self.method_combo.currentTextChanged.connect(self._on_method_changed)
@@ -259,14 +259,14 @@ class FrequencySeperationTab(QWidget):
259
259
  self._toggle_tol_enabled(False)
260
260
 
261
261
  # Apply separation
262
- btn_apply = QPushButton("Apply Split HF & LF", self)
262
+ btn_apply = QPushButton(self.tr("Apply - Split HF & LF"), self)
263
263
  btn_apply.clicked.connect(self._apply_separation)
264
264
  left.addWidget(btn_apply)
265
265
 
266
- left.addWidget(QLabel("<b>HF Enhancements</b>", self))
266
+ left.addWidget(QLabel(self.tr("<b>HF Enhancements</b>"), self))
267
267
 
268
268
  # Sharpen scale
269
- self.cb_scale = QCheckBox("Enable Sharpen Scale", self)
269
+ self.cb_scale = QCheckBox(self.tr("Enable Sharpen Scale"), self)
270
270
  self.cb_scale.setChecked(True); left.addWidget(self.cb_scale)
271
271
  self.scale_label = QLabel("Sharpen Scale: 1.00", self); left.addWidget(self.scale_label)
272
272
  self.scale_slider = QSlider(Qt.Orientation.Horizontal, self)
@@ -275,7 +275,7 @@ class FrequencySeperationTab(QWidget):
275
275
  left.addWidget(self.scale_slider)
276
276
 
277
277
  # Wavelet
278
- self.cb_wavelet = QCheckBox("Enable Wavelet Sharpening", self)
278
+ self.cb_wavelet = QCheckBox(self.tr("Enable Wavelet Sharpening"), self)
279
279
  self.cb_wavelet.setChecked(True); left.addWidget(self.cb_wavelet)
280
280
  self.wavelet_level_label = QLabel("Wavelet Level: 2", self); left.addWidget(self.wavelet_level_label)
281
281
  self.wavelet_level_slider = QSlider(Qt.Orientation.Horizontal, self)
@@ -290,7 +290,7 @@ class FrequencySeperationTab(QWidget):
290
290
  left.addWidget(self.wavelet_boost_slider)
291
291
 
292
292
  # Denoise
293
- self.cb_denoise = QCheckBox("Enable HF Denoise", self)
293
+ self.cb_denoise = QCheckBox(self.tr("Enable HF Denoise"), self)
294
294
  self.cb_denoise.setChecked(False); left.addWidget(self.cb_denoise)
295
295
  self.denoise_label = QLabel("Denoise Strength: 3.00", self); left.addWidget(self.denoise_label)
296
296
  self.denoise_slider = QSlider(Qt.Orientation.Horizontal, self)
@@ -300,7 +300,7 @@ class FrequencySeperationTab(QWidget):
300
300
 
301
301
  # HF actions row
302
302
  row = QHBoxLayout()
303
- self.btn_apply_hf = QPushButton("Apply HF Enhancements", self)
303
+ self.btn_apply_hf = QPushButton(self.tr("Apply HF Enhancements"), self)
304
304
  self.btn_apply_hf.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_DialogApplyButton))
305
305
  self.btn_apply_hf.clicked.connect(self._apply_hf_enhancements)
306
306
  row.addWidget(self.btn_apply_hf)
@@ -315,8 +315,8 @@ class FrequencySeperationTab(QWidget):
315
315
 
316
316
  # Push buttons
317
317
  push_row = QHBoxLayout()
318
- self.btn_push_lf = QPushButton("Push LF", self); self.btn_push_lf.clicked.connect(lambda: self._push_array(self.low_freq_image, "LF"))
319
- self.btn_push_hf = QPushButton("Push HF", self); self.btn_push_hf.clicked.connect(lambda: self._push_array(self._hf_display_for_push(), "HF"))
318
+ self.btn_push_lf = QPushButton(self.tr("Push LF"), self); self.btn_push_lf.clicked.connect(lambda: self._push_array(self.low_freq_image, "LF"))
319
+ self.btn_push_hf = QPushButton(self.tr("Push HF"), self); self.btn_push_hf.clicked.connect(lambda: self._push_array(self._hf_display_for_push(), "HF"))
320
320
  push_row.addWidget(self.btn_push_lf); push_row.addWidget(self.btn_push_hf)
321
321
  left.addLayout(push_row)
322
322
 
@@ -342,7 +342,7 @@ class FrequencySeperationTab(QWidget):
342
342
 
343
343
  left.addLayout(load_row)
344
344
 
345
- btn_combine_push = QPushButton("Combine HF+LF Push", self)
345
+ btn_combine_push = QPushButton(self.tr("Combine HF+LF -> Push"), self)
346
346
  btn_combine_push.clicked.connect(self._combine_and_push)
347
347
  left.addWidget(btn_combine_push)
348
348
 
@@ -708,10 +708,16 @@ class FrequencySeperationTab(QWidget):
708
708
 
709
709
  # channel reconcile
710
710
  if rch == 1 and ch == 3:
711
- # convert RGB→mono (luma or average; we’ll use average)
711
+ # convert RGB→mono (use weighted luma for consistency, or mean if desired. Original was mean)
712
712
  a = a.mean(axis=2).astype(np.float32)
713
713
  elif rch == 3 and ch == 1:
714
- a = np.repeat(a[..., None], 3, axis=2).astype(np.float32)
714
+ # Broadcast mono to 3 channels without copying
715
+ # (H,W,1) -> (H,W,3) via broadcasted view if consumer allows,
716
+ # but usually downstream (like subtraction) handles broadcasting fine.
717
+ # If explicit physical layout is needed, we must check usage.
718
+ # Here: used for subtraction (OK) and preview (OK).
719
+ # We return a view using broadcast_to or striding tricks.
720
+ a = np.broadcast_to(a, (ah, aw, 3))
715
721
 
716
722
  return a
717
723
 
@@ -202,7 +202,7 @@ class FunctionBundleChip(QWidget):
202
202
  from PyQt6.QtWidgets import QMenu # already imported at top, but safe
203
203
 
204
204
  m = QMenu(self)
205
- act_del = m.addAction("Delete Chip")
205
+ act_del = m.addAction(self._panel.tr("Delete Chip"))
206
206
  act = m.exec(ev.globalPos())
207
207
  if act is act_del:
208
208
  try:
@@ -333,7 +333,9 @@ class FunctionBundleDialog(QDialog):
333
333
  def __init__(self, parent: QWidget | None = None):
334
334
  super().__init__(parent)
335
335
  _pin_on_top_mac(self)
336
- self.setWindowTitle("Function Bundles")
336
+ self.setWindowTitle(self.tr("Function Bundles"))
337
+ self.setWindowFlag(Qt.WindowType.Window, True)
338
+ self.setWindowModality(Qt.WindowModality.NonModal)
337
339
  self.setModal(False)
338
340
  self.resize(920, 560)
339
341
  self.setAcceptDrops(True)
@@ -348,9 +350,9 @@ class FunctionBundleDialog(QDialog):
348
350
  self.list.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
349
351
  self.list.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
350
352
 
351
- self.btn_new = QPushButton("New")
352
- self.btn_dup = QPushButton("Duplicate")
353
- self.btn_del = QPushButton("Delete")
353
+ self.btn_new = QPushButton(self.tr("New"))
354
+ self.btn_dup = QPushButton(self.tr("Duplicate"))
355
+ self.btn_del = QPushButton(self.tr("Delete"))
354
356
 
355
357
  # right: steps
356
358
  self.steps = QListWidget()
@@ -366,26 +368,26 @@ class FunctionBundleDialog(QDialog):
366
368
  self.steps.setResizeMode(QListView.ResizeMode.Adjust) # recompute item layout on width change
367
369
  self.steps.setUniformItemSizes(False)
368
370
 
369
- self.add_hint = QLabel("Drop shortcuts here to add steps")
371
+ self.add_hint = QLabel(self.tr("Drop shortcuts here to add steps"))
370
372
  self.add_hint.setAlignment(Qt.AlignmentFlag.AlignCenter)
371
373
  self.add_hint.setStyleSheet("color:#aaa; padding:6px; border:1px dashed #666; border-radius:6px;")
372
374
 
373
- self.btn_edit_preset = QPushButton("Edit Preset…")
375
+ self.btn_edit_preset = QPushButton(self.tr("Edit Preset…"))
374
376
  self.btn_edit_preset.setEnabled(False) # enabled when exactly one step is selected
375
377
 
376
- self.btn_remove = QPushButton("Remove Selected")
377
- self.btn_clear = QPushButton("Clear Steps")
378
- self.btn_up = QPushButton("▲ Move Up")
379
- self.btn_down = QPushButton("▼ Move Down")
378
+ self.btn_remove = QPushButton(self.tr("Remove Selected"))
379
+ self.btn_clear = QPushButton(self.tr("Clear Steps"))
380
+ self.btn_up = QPushButton(self.tr("▲ Move Up"))
381
+ self.btn_down = QPushButton(self.tr("▼ Move Down"))
380
382
 
381
- self.btn_drag_bundle = QPushButton("Drag Bundle")
382
- self.btn_run_active = QPushButton("Apply to Active View")
383
- self.btn_apply_to_vbundle = QPushButton("Apply to View Bundle…")
384
- self.btn_chip = QPushButton("Compress to Chip")
383
+ self.btn_drag_bundle = QPushButton(self.tr("Drag Bundle"))
384
+ self.btn_run_active = QPushButton(self.tr("Apply to Active View"))
385
+ self.btn_apply_to_vbundle = QPushButton(self.tr("Apply to View Bundle…"))
386
+ self.btn_chip = QPushButton(self.tr("Compress to Chip"))
385
387
 
386
388
  # layout
387
389
  left = QVBoxLayout()
388
- left.addWidget(QLabel("Function Bundles"))
390
+ left.addWidget(QLabel(self.tr("Function Bundles")))
389
391
  left.addWidget(self.list, 1)
390
392
  row = QHBoxLayout()
391
393
  row.addWidget(self.btn_new); row.addWidget(self.btn_dup); row.addWidget(self.btn_del)