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.
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
@@ -16,7 +16,7 @@ from astropy.io import fits
16
16
  from astropy.io.fits import Header
17
17
  from astropy.wcs import WCS
18
18
 
19
- from PyQt6.QtCore import QProcess, QTimer, QEventLoop, Qt
19
+ from PyQt6.QtCore import QProcess, QTimer, QEventLoop, Qt, QCoreApplication
20
20
  from PyQt6.QtGui import QIcon
21
21
  from PyQt6.QtWidgets import (
22
22
  QDialog, QLabel, QPushButton, QVBoxLayout, QHBoxLayout,
@@ -68,7 +68,7 @@ class _SolveStatusPopup(QDialog):
68
68
  def __init__(self, parent=None):
69
69
  super().__init__(parent, Qt.WindowType.Tool)
70
70
  self.setObjectName("plate_solve_status_popup")
71
- self.setWindowTitle("Plate Solving")
71
+ self.setWindowTitle(self.tr("Plate Solving"))
72
72
  self.setWindowModality(Qt.WindowModality.NonModal)
73
73
  self.setWindowFlag(Qt.WindowType.WindowStaysOnTopHint, True)
74
74
  self.setMinimumWidth(420)
@@ -77,7 +77,7 @@ class _SolveStatusPopup(QDialog):
77
77
  lay.setContentsMargins(12, 12, 12, 12)
78
78
  lay.setSpacing(10)
79
79
 
80
- self.label = QLabel("Starting…", self)
80
+ self.label = QLabel(self.tr("Starting…"), self)
81
81
  self.label.setWordWrap(True)
82
82
  lay.addWidget(self.label)
83
83
 
@@ -87,7 +87,7 @@ class _SolveStatusPopup(QDialog):
87
87
 
88
88
  row = QHBoxLayout()
89
89
  row.addStretch(1)
90
- hide_btn = QPushButton("Hide", self)
90
+ hide_btn = QPushButton(self.tr("Hide"), self)
91
91
  hide_btn.clicked.connect(self.hide)
92
92
  row.addWidget(hide_btn)
93
93
  lay.addLayout(row)
@@ -160,7 +160,8 @@ def _set_status_ui(parent, text: str):
160
160
  # Batch log?
161
161
  logw = getattr(parent, "log", None)
162
162
  if logw and hasattr(logw, "append"):
163
- if text and (text.startswith("Status:") or text.startswith("") or text.startswith("✔") or text.startswith("❌")):
163
+ tr_status = QCoreApplication.translate("PlateSolver", "Status:")
164
+ if text and (text.startswith("Status:") or text.startswith(tr_status) or text.startswith("▶") or text.startswith("✔") or text.startswith("❌")):
164
165
  logw.append(text)
165
166
  updated_any = True
166
167
 
@@ -206,11 +207,11 @@ def _wait_process(proc: QProcess, timeout_ms: int, parent=None) -> bool:
206
207
  proc.waitForFinished(2000)
207
208
  except Exception:
208
209
  pass
209
- _set_status_ui(parent, "Status: process timed out.")
210
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: process timed out."))
210
211
  return False
211
212
 
212
213
  if proc.exitStatus() != QProcess.ExitStatus.NormalExit:
213
- _set_status_ui(parent, "Status: process did not exit normally.")
214
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: process did not exit normally."))
214
215
  return False
215
216
 
216
217
  return True
@@ -385,7 +386,8 @@ def _astrometry_api_request(method: str, url: str, *, data=None, files=None,
385
386
  if attempt >= max_retries:
386
387
  break
387
388
  delay = min(8.0, 0.5 * (2 ** (attempt - 1))) + random.random() * 0.2
388
- _set_status_ui(parent, f"Status: {stage or 'request'} retry {attempt}/{max_retries}…")
389
+ msg = QCoreApplication.translate("PlateSolver", "Status: {0} retry {1}/{2}…").format(stage or 'request', attempt, max_retries)
390
+ _set_status_ui(parent, msg)
389
391
  _sleep_ui(int(delay * 1000))
390
392
  return None
391
393
 
@@ -621,7 +623,7 @@ def _astrometry_download_wcs_file(settings, job_id: int, parent=None) -> Header
621
623
  base_site = _get_astrometry_api_url(settings).split("/api/")[0].rstrip("/") + "/"
622
624
  url = base_site + f"wcs_file/{int(job_id)}"
623
625
 
624
- _set_status_ui(parent, "Status: Downloading WCS file (with SIP) from Astrometry.net…")
626
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Downloading WCS file (with SIP) from Astrometry.net…"))
625
627
  try:
626
628
  r = requests.get(url, timeout=(10, 60))
627
629
  if r.status_code != 200 or len(r.content) < 2000:
@@ -903,13 +905,13 @@ def _build_astap_seed(h: Header) -> Tuple[list[str], str]:
903
905
 
904
906
 
905
907
  def _astrometry_login(settings, parent=None) -> str | None:
906
- _set_status_ui(parent, "Status: Logging in to Astrometry.net…")
908
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Logging in to Astrometry.net…"))
907
909
  api_key = _get_astrometry_api_key(settings)
908
910
  if not api_key:
909
911
  from PyQt6.QtWidgets import QInputDialog
910
- key, ok = QInputDialog.getText(None, "Astrometry.net API Key", "Enter your Astrometry.net API key:")
912
+ key, ok = QInputDialog.getText(None, QCoreApplication.translate("PlateSolver", "Astrometry.net API Key"), QCoreApplication.translate("PlateSolver", "Enter your Astrometry.net API key:"))
911
913
  if not ok or not key:
912
- _set_status_ui(parent, "Status: Login canceled (no API key).")
914
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Login canceled (no API key)."))
913
915
  return None
914
916
  _set_astrometry_api_key(settings, key)
915
917
  api_key = key
@@ -921,20 +923,20 @@ def _astrometry_login(settings, parent=None) -> str | None:
921
923
  parent=parent, stage="login"
922
924
  )
923
925
  if resp and resp.get("status") == "success":
924
- _set_status_ui(parent, "Status: Login successful.")
926
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Login successful."))
925
927
  return resp.get("session")
926
- _set_status_ui(parent, "Status: Login failed.")
928
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Login failed."))
927
929
  return None
928
930
 
929
931
  def _astrometry_upload(settings, session: str, image_path: str, parent=None) -> int | None:
930
- _set_status_ui(parent, "Status: Uploading image to Astrometry.net…")
932
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Uploading image to Astrometry.net…"))
931
933
  base = _get_astrometry_api_url(settings)
932
934
 
933
935
  try:
934
936
  sz = os.path.getsize(image_path)
935
937
  if sz < 1024: # fits headers alone are ~2880 bytes
936
938
  print(f"[Astrometry] temp FITS too small ({sz} bytes): {image_path}")
937
- _set_status_ui(parent, "Status: Upload failed (temp FITS empty).")
939
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Upload failed (temp FITS empty)."))
938
940
  return None
939
941
  except Exception:
940
942
  pass
@@ -955,12 +957,12 @@ def _astrometry_upload(settings, session: str, image_path: str, parent=None) ->
955
957
  parent=parent, stage="upload"
956
958
  )
957
959
  if resp and resp.get("status") == "success":
958
- _set_status_ui(parent, "Status: Upload complete.")
960
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Upload complete."))
959
961
  return int(resp["subid"])
960
962
  except Exception as e:
961
963
  print("Upload error:", e)
962
964
 
963
- _set_status_ui(parent, "Status: Upload failed.")
965
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Upload failed."))
964
966
  return None
965
967
 
966
968
 
@@ -968,7 +970,7 @@ def _astrometry_upload(settings, session: str, image_path: str, parent=None) ->
968
970
  def _solve_with_local_solvefield(parent, settings, tmp_fit_path: str) -> tuple[bool, Header | str]:
969
971
  solvefield = _get_solvefield_exe(settings)
970
972
  if not solvefield or not os.path.exists(solvefield):
971
- return False, "solve-field not configured."
973
+ return False, QCoreApplication.translate("PlateSolver", "solve-field not configured.")
972
974
 
973
975
  args = [
974
976
  "--overwrite",
@@ -978,22 +980,22 @@ def _solve_with_local_solvefield(parent, settings, tmp_fit_path: str) -> tuple[b
978
980
  "--write-wcs", "wcs",
979
981
  tmp_fit_path
980
982
  ]
981
- _set_status_ui(parent, "Status: Running local solve-field…")
983
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Running local solve-field…"))
982
984
  print("Running solve-field:", solvefield, " ".join(args))
983
985
  p = QProcess(parent)
984
986
  p.start(solvefield, args)
985
987
  if not p.waitForStarted(5000):
986
- _set_status_ui(parent, "Status: solve-field failed to start.")
988
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: solve-field failed to start."))
987
989
  return False, f"Failed to start solve-field: {p.errorString()}"
988
990
 
989
991
  if not _wait_process(p, 300000, parent=parent):
990
- _set_status_ui(parent, "Status: solve-field timed out.")
992
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: solve-field timed out."))
991
993
  return False, "solve-field timed out."
992
994
 
993
995
  if p.exitCode() != 0:
994
996
  out = bytes(p.readAllStandardOutput()).decode(errors="ignore")
995
997
  err = bytes(p.readAllStandardError()).decode(errors="ignore")
996
- _set_status_ui(parent, "Status: solve-field failed.")
998
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: solve-field failed."))
997
999
  print("solve-field failed.\nSTDOUT:\n", out, "\nSTDERR:\n", err)
998
1000
  return False, "solve-field returned non-zero exit."
999
1001
 
@@ -1021,7 +1023,7 @@ def _solve_with_local_solvefield(parent, settings, tmp_fit_path: str) -> tuple[b
1021
1023
 
1022
1024
 
1023
1025
  def _astrometry_poll_job(settings, subid: int, *, max_wait_s=900, parent=None) -> int | None:
1024
- _set_status_ui(parent, "Status: Waiting for job assignment…")
1026
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Waiting for job assignment…"))
1025
1027
  base = _get_astrometry_api_url(settings)
1026
1028
  t0 = time.time()
1027
1029
  while time.time() - t0 < max_wait_s:
@@ -1030,7 +1032,7 @@ def _astrometry_poll_job(settings, subid: int, *, max_wait_s=900, parent=None) -
1030
1032
  if resp:
1031
1033
  jobs = resp.get("jobs", [])
1032
1034
  if jobs and jobs[0] is not None:
1033
- _set_status_ui(parent, f"Status: Job assigned (ID {jobs[0]}).")
1035
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Job assigned (ID {0}).").format(jobs[0]))
1034
1036
  try: return int(jobs[0])
1035
1037
  except Exception as e:
1036
1038
  import logging
@@ -1039,14 +1041,14 @@ def _astrometry_poll_job(settings, subid: int, *, max_wait_s=900, parent=None) -
1039
1041
  return None
1040
1042
 
1041
1043
  def _astrometry_poll_calib(settings, job_id: int, *, max_wait_s=900, parent=None) -> dict | None:
1042
- _set_status_ui(parent, "Status: Waiting for solution…")
1044
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Waiting for solution…"))
1043
1045
  base = _get_astrometry_api_url(settings)
1044
1046
  t0 = time.time()
1045
1047
  while time.time() - t0 < max_wait_s:
1046
1048
  resp = _astrometry_api_request("GET", base + f"jobs/{job_id}/calibration/",
1047
1049
  parent=parent, stage="poll calib")
1048
1050
  if resp and all(k in resp for k in ("ra","dec","pixscale")):
1049
- _set_status_ui(parent, "Status: Solution received.")
1051
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Solution received."))
1050
1052
  return resp
1051
1053
  _sleep_ui(1500)
1052
1054
  return None
@@ -1403,13 +1405,13 @@ def _solve_numpy_with_astrometry(
1403
1405
  import logging
1404
1406
  logging.debug(f"Exception suppressed: {type(e).__name__}: {e}")
1405
1407
  return True, hh
1406
- return False, "solve-field returned no header."
1408
+ return False, QCoreApplication.translate("PlateSolver", "solve-field returned no header.")
1407
1409
 
1408
1410
  # 2) web API fallback (full-res, 16-bit upload)
1409
1411
  if requests is None:
1410
- return False, "requests not available for astrometry.net API."
1412
+ return False, QCoreApplication.translate("PlateSolver", "requests not available for astrometry.net API.")
1411
1413
 
1412
- _set_status_ui(parent, "Status: Preparing full-res 16-bit FITS for web solve…")
1414
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Preparing full-res 16-bit FITS for web solve…"))
1413
1415
 
1414
1416
  tmp_fit_web = _write_temp_fit_web_16bit(gray_full)
1415
1417
 
@@ -1417,21 +1419,21 @@ def _solve_numpy_with_astrometry(
1417
1419
  try:
1418
1420
  sz = os.path.getsize(tmp_fit_web)
1419
1421
  if sz < 3000:
1420
- return False, f"Temp FITS for web upload is empty/tiny ({sz} bytes)."
1422
+ return False, QCoreApplication.translate("PlateSolver", "Temp FITS for web upload is empty/tiny ({0} bytes).").format(sz)
1421
1423
  except Exception:
1422
1424
  pass
1423
1425
 
1424
1426
  session = _astrometry_login(settings, parent=parent)
1425
1427
  if not session:
1426
- return False, "Astrometry.net login failed."
1428
+ return False, QCoreApplication.translate("PlateSolver", "Astrometry.net login failed.")
1427
1429
 
1428
1430
  subid = _astrometry_upload(settings, session, tmp_fit_web, parent=parent)
1429
1431
  if not subid:
1430
- return False, "Astrometry.net upload failed."
1432
+ return False, QCoreApplication.translate("PlateSolver", "Astrometry.net upload failed.")
1431
1433
 
1432
1434
  job_id = _astrometry_poll_job(settings, subid, parent=parent)
1433
1435
  if not job_id:
1434
- return False, "Astrometry.net job ID not received in time."
1436
+ return False, QCoreApplication.translate("PlateSolver", "Astrometry.net job ID not received in time.")
1435
1437
 
1436
1438
  # Prefer full WCS file (includes SIP)
1437
1439
  hdr_wcs = _astrometry_download_wcs_file(settings, job_id, parent=parent)
@@ -1440,9 +1442,9 @@ def _solve_numpy_with_astrometry(
1440
1442
  # fallback to calibration (no SIP)
1441
1443
  calib = _astrometry_poll_calib(settings, job_id, parent=parent)
1442
1444
  if not calib:
1443
- return False, "Astrometry.net calibration not received in time."
1445
+ return False, QCoreApplication.translate("PlateSolver", "Astrometry.net calibration not received in time.")
1444
1446
 
1445
- _set_status_ui(parent, "Status: Building WCS header from calibration…")
1447
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Building WCS header from calibration…"))
1446
1448
  hdr_wcs = _wcs_header_from_astrometry_calib(calib, (Hfull, Wfull))
1447
1449
 
1448
1450
  # Coerce & ensure TAN-SIP if SIP terms exist
@@ -1487,24 +1489,24 @@ def _solve_numpy_with_astrometry(
1487
1489
 
1488
1490
  def _solve_numpy_with_fallback(parent, settings, image: np.ndarray, seed_header: Header | None) -> tuple[bool, Header | str]:
1489
1491
  # Try ASTAP first
1490
- _set_status_ui(parent, "Status: Solving with ASTAP…")
1492
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Solving with ASTAP…"))
1491
1493
  ok, res = _solve_numpy_with_astap(parent, settings, image, seed_header)
1492
1494
  if ok:
1493
- _set_status_ui(parent, "Status: Solved with ASTAP.")
1495
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Solved with ASTAP."))
1494
1496
  return True, res
1495
1497
 
1496
1498
  # ASTAP failed → tell the user and fall back
1497
1499
  err_msg = str(res) if res is not None else "unknown error"
1498
1500
  print("ASTAP failed:", err_msg)
1499
- _set_status_ui(parent, f"Status: ASTAP failed ({err_msg}). Falling back to Astrometry.net…")
1501
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: ASTAP failed ({0}). Falling back to Astrometry.net…").format(err_msg))
1500
1502
  QApplication.processEvents()
1501
1503
 
1502
1504
  # Fallback: astrometry.net (local solve-field first, then web API inside)
1503
1505
  ok2, res2 = _solve_numpy_with_astrometry(parent, settings, image, seed_header)
1504
1506
  if ok2:
1505
- _set_status_ui(parent, "Status: Solved via Astrometry.net.")
1507
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Solved via Astrometry.net."))
1506
1508
  else:
1507
- _set_status_ui(parent, f"Status: Astrometry.net failed ({res2}).")
1509
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Astrometry.net failed ({0}).").format(res2))
1508
1510
 
1509
1511
  return ok2, res2
1510
1512
 
@@ -1626,7 +1628,7 @@ def _solve_numpy_with_astap(parent, settings, image: np.ndarray, seed_header: He
1626
1628
  """
1627
1629
  astap_exe = _get_astap_exe(settings)
1628
1630
  if not astap_exe or not os.path.exists(astap_exe):
1629
- return False, "ASTAP path is not set (see Preferences) or file not found."
1631
+ return False, QCoreApplication.translate("PlateSolver", "ASTAP path is not set (see Preferences) or file not found.")
1630
1632
 
1631
1633
  # normalize and force 2-D luminance in [0,1]
1632
1634
  norm = _normalize_for_astap(image)
@@ -1696,13 +1698,13 @@ def _solve_numpy_with_astap(parent, settings, image: np.ndarray, seed_header: He
1696
1698
  proc = QProcess(parent)
1697
1699
  proc.start(astap_exe, args)
1698
1700
  if not proc.waitForStarted(5000):
1699
- _set_status_ui(parent, "Status: ASTAP failed to start.")
1700
- return False, f"Failed to start ASTAP: {proc.errorString()}"
1701
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: ASTAP failed to start."))
1702
+ return False, QCoreApplication.translate("PlateSolver", "Failed to start ASTAP: {0}").format(proc.errorString())
1701
1703
 
1702
- _set_status_ui(parent, "Status: ASTAP solving…")
1704
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: ASTAP solving…"))
1703
1705
  if not _wait_process(proc, 300000, parent=parent):
1704
- _set_status_ui(parent, "Status: ASTAP timed out.")
1705
- return False, "ASTAP timed out."
1706
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: ASTAP timed out."))
1707
+ return False, QCoreApplication.translate("PlateSolver", "ASTAP timed out.")
1706
1708
 
1707
1709
  if proc.exitCode() != 0:
1708
1710
  out = bytes(proc.readAllStandardOutput()).decode(errors="ignore")
@@ -1717,7 +1719,7 @@ def _solve_numpy_with_astap(parent, settings, image: np.ndarray, seed_header: He
1717
1719
  except Exception as e:
1718
1720
  import logging
1719
1721
  logging.debug(f"Exception suppressed: {type(e).__name__}: {e}")
1720
- return False, "ASTAP returned a non-zero exit code."
1722
+ return False, QCoreApplication.translate("PlateSolver", "ASTAP returned a non-zero exit code.")
1721
1723
 
1722
1724
  # >>> THIS is the key change: read the header **directly** from the FITS ASTAP wrote
1723
1725
  try:
@@ -1777,7 +1779,7 @@ def _debug_dump_meta(label: str, meta: dict):
1777
1779
  def plate_solve_doc_inplace(parent, doc, settings) -> Tuple[bool, Header | str]:
1778
1780
  img = getattr(doc, "image", None)
1779
1781
  if img is None:
1780
- return False, "Active document has no image data."
1782
+ return False, QCoreApplication.translate("PlateSolver", "Active document has no image data.")
1781
1783
 
1782
1784
  # Make sure metadata is a dict we can mutate
1783
1785
  meta = getattr(doc, "metadata", {}) or {}
@@ -1829,7 +1831,7 @@ def plate_solve_doc_inplace(parent, doc, settings) -> Tuple[bool, Header | str]:
1829
1831
  (hasattr(parent, "findChild") and parent.findChild(QLabel, "status_label") is not None)
1830
1832
  )
1831
1833
  if headless:
1832
- _status_popup_open(parent, "Status: Preparing plate solve…")
1834
+ _status_popup_open(parent, QCoreApplication.translate("PlateSolver", "Status: Preparing plate solve…"))
1833
1835
 
1834
1836
  try:
1835
1837
  ok, res = _solve_numpy_with_fallback(parent, settings, img, seed_h)
@@ -1884,7 +1886,7 @@ def plate_solve_doc_inplace(parent, doc, settings) -> Tuple[bool, Header | str]:
1884
1886
  if hasattr(parent, "currentDocumentChanged"):
1885
1887
  QTimer.singleShot(0, lambda: parent.currentDocumentChanged.emit(doc))
1886
1888
 
1887
- _set_status_ui(parent, "Status: Plate solve completed.")
1889
+ _set_status_ui(parent, QCoreApplication.translate("PlateSolver", "Status: Plate solve completed."))
1888
1890
  _status_popup_close()
1889
1891
  return True, hdr
1890
1892
  finally:
@@ -2035,7 +2037,7 @@ def plate_solve_active_document(parent, settings) -> tuple[bool, Header | str]:
2035
2037
  """
2036
2038
  doc = _active_doc_from_parent(parent)
2037
2039
  if doc is None:
2038
- return False, "No active document to plate-solve."
2040
+ return False, QCoreApplication.translate("PlateSolver", "No active document to plate-solve.")
2039
2041
 
2040
2042
  return plate_solve_doc_inplace(parent, doc, settings)
2041
2043
 
@@ -2054,9 +2056,10 @@ class PlateSolverDialog(QDialog):
2054
2056
  def __init__(self, settings, parent=None, icon: QIcon | None = None):
2055
2057
  super().__init__(parent)
2056
2058
  self.settings = settings
2057
- self.setWindowTitle("Plate Solver")
2059
+ self.setWindowTitle(self.tr("Plate Solver"))
2058
2060
  self.setMinimumWidth(560)
2059
2061
  self.setWindowFlag(Qt.WindowType.Window, True)
2062
+ self.setWindowModality(Qt.WindowModality.NonModal)
2060
2063
  self.setModal(False)
2061
2064
  #self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
2062
2065
 
@@ -2067,16 +2070,18 @@ class PlateSolverDialog(QDialog):
2067
2070
 
2068
2071
  # ---- Top row: Mode selector ----
2069
2072
  top = QHBoxLayout()
2070
- top.addWidget(QLabel("Mode:", self))
2073
+ top.addWidget(QLabel(self.tr("Mode:"), self))
2071
2074
  self.mode_combo = QComboBox(self)
2072
- self.mode_combo.addItems(["Active View", "File", "Batch"])
2075
+ self.mode_combo.addItem(self.tr("Active View"), "Active View")
2076
+ self.mode_combo.addItem(self.tr("File"), "File")
2077
+ self.mode_combo.addItem(self.tr("Batch"), "Batch")
2073
2078
  top.addWidget(self.mode_combo, 1)
2074
2079
  top.addStretch(1)
2075
2080
  main.addLayout(top)
2076
2081
 
2077
2082
  # ---- Seeding group (shared) ----
2078
2083
  from PyQt6.QtWidgets import QGroupBox, QFormLayout
2079
- seed_box = QGroupBox("Seeding & Constraints", self)
2084
+ seed_box = QGroupBox(self.tr("Seeding & Constraints"), self)
2080
2085
  seed_form = QFormLayout(seed_box)
2081
2086
  seed_form.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow)
2082
2087
  seed_form.setHorizontalSpacing(8)
@@ -2084,46 +2089,51 @@ class PlateSolverDialog(QDialog):
2084
2089
 
2085
2090
  # Seed mode
2086
2091
  self.cb_seed_mode = QComboBox(seed_box)
2087
- self.cb_seed_mode.addItems(["Auto (from header)", "Manual", "None (blind)"])
2088
- seed_form.addRow("Seed mode:", self.cb_seed_mode)
2092
+ self.cb_seed_mode.addItem(self.tr("Auto (from header)"), "Auto (from header)")
2093
+ self.cb_seed_mode.addItem(self.tr("Manual"), "Manual")
2094
+ self.cb_seed_mode.addItem(self.tr("None (blind)"), "None (blind)")
2095
+ seed_form.addRow(self.tr("Seed mode:"), self.cb_seed_mode)
2089
2096
 
2090
2097
  # Manual RA/Dec/Scale row
2091
2098
  manual_row = QHBoxLayout()
2092
- self.le_ra = QLineEdit(seed_box); self.le_ra.setPlaceholderText("RA (e.g. 22:32:14 or 338.1385)")
2093
- self.le_dec = QLineEdit(seed_box); self.le_dec.setPlaceholderText("Dec (e.g. +40:42:43 or 40.7123)")
2094
- self.le_scale = QLineEdit(seed_box); self.le_scale.setPlaceholderText('Scale [" / px] (e.g. 1.46)')
2099
+ self.le_ra = QLineEdit(seed_box); self.le_ra.setPlaceholderText(self.tr("RA (e.g. 22:32:14 or 338.1385)"))
2100
+ self.le_dec = QLineEdit(seed_box); self.le_dec.setPlaceholderText(self.tr("Dec (e.g. +40:42:43 or 40.7123)"))
2101
+ self.le_scale = QLineEdit(seed_box); self.le_scale.setPlaceholderText(self.tr('Scale [" / px] (e.g. 1.46)'))
2095
2102
  manual_row.addWidget(self.le_ra, 1)
2096
2103
  manual_row.addWidget(self.le_dec, 1)
2097
2104
  manual_row.addWidget(self.le_scale, 1)
2098
- seed_form.addRow("Manual RA/Dec/Scale:", manual_row)
2105
+ seed_form.addRow(self.tr("Manual RA/Dec/Scale:"), manual_row)
2099
2106
 
2100
2107
  # Search radius (-r)
2101
2108
  rad_row = QHBoxLayout()
2102
2109
  self.cb_radius_mode = QComboBox(seed_box)
2103
- self.cb_radius_mode.addItems(["Auto (-r 0)", "Value (deg)"])
2104
- self.le_radius_val = QLineEdit(seed_box); self.le_radius_val.setPlaceholderText("e.g. 5.0")
2110
+ self.cb_radius_mode.addItem(self.tr("Auto (-r 0)"), "Auto (-r 0)")
2111
+ self.cb_radius_mode.addItem(self.tr("Value (deg)"), "Value (deg)")
2112
+ self.le_radius_val = QLineEdit(seed_box); self.le_radius_val.setPlaceholderText(self.tr("e.g. 5.0"))
2105
2113
  self.le_radius_val.setFixedWidth(120)
2106
2114
  rad_row.addWidget(self.cb_radius_mode)
2107
2115
  rad_row.addWidget(self.le_radius_val)
2108
2116
  rad_row.addStretch(1)
2109
- seed_form.addRow("Search radius:", rad_row)
2117
+ seed_form.addRow(self.tr("Search radius:"), rad_row)
2110
2118
 
2111
2119
  # FOV (-fov)
2112
2120
  fov_row = QHBoxLayout()
2113
2121
  self.cb_fov_mode = QComboBox(seed_box)
2114
- self.cb_fov_mode.addItems(["Compute from scale", "Auto (-fov 0)", "Value (deg)"])
2115
- self.le_fov_val = QLineEdit(seed_box); self.le_fov_val.setPlaceholderText("e.g. 1.80")
2122
+ self.cb_fov_mode.addItem(self.tr("Compute from scale"), "Compute from scale")
2123
+ self.cb_fov_mode.addItem(self.tr("Auto (-fov 0)"), "Auto (-fov 0)")
2124
+ self.cb_fov_mode.addItem(self.tr("Value (deg)"), "Value (deg)")
2125
+ self.le_fov_val = QLineEdit(seed_box); self.le_fov_val.setPlaceholderText(self.tr("e.g. 1.80"))
2116
2126
  self.le_fov_val.setFixedWidth(120)
2117
2127
  fov_row.addWidget(self.cb_fov_mode)
2118
2128
  fov_row.addWidget(self.le_fov_val)
2119
2129
  fov_row.addStretch(1)
2120
- seed_form.addRow("FOV:", fov_row)
2130
+ seed_form.addRow(self.tr("FOV:"), fov_row)
2121
2131
 
2122
2132
  # Tooltips
2123
- self.cb_seed_mode.setToolTip("Use FITS header, your manual RA/Dec/scale, or blind solve.")
2124
- self.le_scale.setToolTip('Pixel scale in arcseconds/pixel (e.g., 1.46).')
2125
- self.cb_radius_mode.setToolTip("ASTAP -r. Auto lets ASTAP choose; Value forces a cone radius.")
2126
- self.cb_fov_mode.setToolTip("ASTAP -fov. Compute uses image height × scale; Auto lets ASTAP infer.")
2133
+ self.cb_seed_mode.setToolTip(self.tr("Use FITS header, your manual RA/Dec/scale, or blind solve."))
2134
+ self.le_scale.setToolTip(self.tr('Pixel scale in arcseconds/pixel (e.g., 1.46).'))
2135
+ self.cb_radius_mode.setToolTip(self.tr("ASTAP -r. Auto lets ASTAP choose; Value forces a cone radius."))
2136
+ self.cb_fov_mode.setToolTip(self.tr("ASTAP -fov. Compute uses image height × scale; Auto lets ASTAP infer."))
2127
2137
 
2128
2138
  main.addWidget(seed_box)
2129
2139
 
@@ -2133,15 +2143,15 @@ class PlateSolverDialog(QDialog):
2133
2143
 
2134
2144
  # Page 0: Active View
2135
2145
  p0 = QWidget(self); l0 = QVBoxLayout(p0)
2136
- l0.addWidget(QLabel("Solve the currently active image view.", p0))
2146
+ l0.addWidget(QLabel(self.tr("Solve the currently active image view."), p0))
2137
2147
  l0.addStretch(1)
2138
2148
  self.stack.addWidget(p0)
2139
2149
 
2140
2150
  # Page 1: File picker
2141
2151
  p1 = QWidget(self); l1 = QVBoxLayout(p1)
2142
2152
  file_row = QHBoxLayout()
2143
- self.le_path = QLineEdit(p1); self.le_path.setPlaceholderText("Choose an image…")
2144
- btn_browse = QPushButton("Browse…", p1)
2153
+ self.le_path = QLineEdit(p1); self.le_path.setPlaceholderText(self.tr("Choose an image…"))
2154
+ btn_browse = QPushButton(self.tr("Browse…"), p1)
2145
2155
  file_row.addWidget(self.le_path, 1); file_row.addWidget(btn_browse)
2146
2156
  l1.addLayout(file_row); l1.addStretch(1)
2147
2157
  self.stack.addWidget(p1)
@@ -2149,14 +2159,14 @@ class PlateSolverDialog(QDialog):
2149
2159
  # Page 2: Batch
2150
2160
  p2 = QWidget(self); l2 = QVBoxLayout(p2)
2151
2161
  in_row = QHBoxLayout(); out_row = QHBoxLayout()
2152
- self.le_in = QLineEdit(p2); self.le_in.setPlaceholderText("Input directory")
2153
- self.le_out = QLineEdit(p2); self.le_out.setPlaceholderText("Output directory")
2154
- b_in = QPushButton("Browse Input…", p2)
2155
- b_out = QPushButton("Browse Output…", p2)
2162
+ self.le_in = QLineEdit(p2); self.le_in.setPlaceholderText(self.tr("Input directory"))
2163
+ self.le_out = QLineEdit(p2); self.le_out.setPlaceholderText(self.tr("Output directory"))
2164
+ b_in = QPushButton(self.tr("Browse Input…"), p2)
2165
+ b_out = QPushButton(self.tr("Browse Output…"), p2)
2156
2166
  in_row.addWidget(self.le_in, 1); in_row.addWidget(b_in)
2157
2167
  out_row.addWidget(self.le_out, 1); out_row.addWidget(b_out)
2158
2168
  self.log = QTextEdit(p2); self.log.setReadOnly(True); self.log.setMinimumHeight(160)
2159
- l2.addLayout(in_row); l2.addLayout(out_row); l2.addWidget(QLabel("Status:", p2)); l2.addWidget(self.log, 1)
2169
+ l2.addLayout(in_row); l2.addLayout(out_row); l2.addWidget(QLabel(self.tr("Status:"), p2)); l2.addWidget(self.log, 1)
2160
2170
  self.stack.addWidget(p2)
2161
2171
 
2162
2172
  # ---------------- Status + buttons ----------------
@@ -2166,8 +2176,8 @@ class PlateSolverDialog(QDialog):
2166
2176
 
2167
2177
  btn_row = QHBoxLayout()
2168
2178
  btn_row.addStretch(1)
2169
- self.btn_go = QPushButton("Start", self)
2170
- self.btn_close = QPushButton("Close", self)
2179
+ self.btn_go = QPushButton(self.tr("Start"), self)
2180
+ self.btn_close = QPushButton(self.tr("Close"), self)
2171
2181
  btn_row.addWidget(self.btn_go)
2172
2182
  btn_row.addWidget(self.btn_close)
2173
2183
  main.addLayout(btn_row)
@@ -2218,26 +2228,26 @@ class PlateSolverDialog(QDialog):
2218
2228
  # ---------- file/batch pickers ----------
2219
2229
  def _browse_file(self):
2220
2230
  f, _ = QFileDialog.getOpenFileName(
2221
- self, "Choose Image",
2222
- "", "Images (*.fits *.fit *.xisf *.tif *.tiff *.png *.jpg *.jpeg);;All files (*)"
2231
+ self, self.tr("Choose Image"),
2232
+ "", self.tr("Images (*.fits *.fit *.xisf *.tif *.tiff *.png *.jpg *.jpeg);;All files (*)")
2223
2233
  )
2224
2234
  if f:
2225
2235
  self.le_path.setText(f)
2226
2236
 
2227
2237
  def _browse_in(self):
2228
- d = QFileDialog.getExistingDirectory(self, "Choose input directory")
2238
+ d = QFileDialog.getExistingDirectory(self, self.tr("Choose input directory"))
2229
2239
  if d: self.le_in.setText(d)
2230
2240
 
2231
2241
  def _browse_out(self):
2232
- d = QFileDialog.getExistingDirectory(self, "Choose output directory")
2242
+ d = QFileDialog.getExistingDirectory(self, self.tr("Choose output directory"))
2233
2243
  if d: self.le_out.setText(d)
2234
2244
 
2235
2245
  # ---------- actions ----------
2236
2246
  def _run(self):
2237
2247
  astap_exe = _get_astap_exe(self.settings)
2238
2248
  if not astap_exe or not os.path.exists(astap_exe):
2239
- self.status.setText("ASTAP path missing. Set Preferences → ASTAP executable.")
2240
- QMessageBox.warning(self, "Plate Solver", "ASTAP path missing.\nSet it in Preferences → ASTAP executable.")
2249
+ self.status.setText(self.tr("ASTAP path missing. Set Preferences → ASTAP executable."))
2250
+ QMessageBox.warning(self, self.tr("Plate Solver"), self.tr("ASTAP path missing.\nSet it in Preferences → ASTAP executable."))
2241
2251
  return
2242
2252
 
2243
2253
  idx = self.cb_seed_mode.currentIndex()
@@ -2267,11 +2277,11 @@ class PlateSolverDialog(QDialog):
2267
2277
  # Active view
2268
2278
  doc = _active_doc_from_parent(self.parent())
2269
2279
  if not doc:
2270
- QMessageBox.information(self, "Plate Solver", "No active image view.")
2280
+ QMessageBox.information(self, self.tr("Plate Solver"), self.tr("No active image view."))
2271
2281
  return
2272
2282
  ok, res = plate_solve_doc_inplace(self, doc, self.settings)
2273
2283
  if ok:
2274
- self.status.setText("Solved with ASTAP (WCS + SIP applied to active doc).")
2284
+ self.status.setText(self.tr("Solved with ASTAP (WCS + SIP applied to active doc)."))
2275
2285
  QTimer.singleShot(0, self.accept) # close when done
2276
2286
  else:
2277
2287
  self.status.setText(str(res))
@@ -2279,10 +2289,10 @@ class PlateSolverDialog(QDialog):
2279
2289
  # Single file
2280
2290
  path = self.le_path.text().strip()
2281
2291
  if not path:
2282
- QMessageBox.information(self, "Plate Solver", "Choose a file to solve.")
2292
+ QMessageBox.information(self, self.tr("Plate Solver"), self.tr("Choose a file to solve."))
2283
2293
  return
2284
2294
  if not os.path.exists(path):
2285
- QMessageBox.warning(self, "Plate Solver", "Selected file does not exist.")
2295
+ QMessageBox.warning(self, self.tr("Plate Solver"), self.tr("Selected file does not exist."))
2286
2296
  return
2287
2297
  self._solve_file(path)
2288
2298
  else:
@@ -2293,10 +2303,10 @@ class PlateSolverDialog(QDialog):
2293
2303
  try:
2294
2304
  image_data, original_header, bit_depth, is_mono = load_image(path)
2295
2305
  except Exception as e:
2296
- QMessageBox.warning(self, "Plate Solver", f"Cannot read image:\n{e}")
2306
+ QMessageBox.warning(self, self.tr("Plate Solver"), self.tr("Cannot read image:\n{0}").format(e))
2297
2307
  return
2298
2308
  if image_data is None:
2299
- QMessageBox.warning(self, "Plate Solver", "Unsupported or unreadable image.")
2309
+ QMessageBox.warning(self, self.tr("Plate Solver"), self.tr("Unsupported or unreadable image."))
2300
2310
  return
2301
2311
 
2302
2312
  # Seed header from original_header
@@ -2323,9 +2333,9 @@ class PlateSolverDialog(QDialog):
2323
2333
  # Save-as using legacy.save_image() with ORIGINAL pixels (not normalized)
2324
2334
  save_path, _ = QFileDialog.getSaveFileName(
2325
2335
  self,
2326
- "Save Plate-Solved FITS",
2336
+ self.tr("Save Plate-Solved FITS"),
2327
2337
  "",
2328
- "FITS files (*.fits *.fit)"
2338
+ self.tr("FITS files (*.fits *.fit)")
2329
2339
  )
2330
2340
  if save_path:
2331
2341
  try:
@@ -2343,22 +2353,22 @@ class PlateSolverDialog(QDialog):
2343
2353
  original_header=h2,
2344
2354
  is_mono=is_mono
2345
2355
  )
2346
- self.status.setText(f"Solved FITS saved:\n{save_path}")
2356
+ self.status.setText(self.tr("Solved FITS saved:\n{0}").format(save_path))
2347
2357
  QTimer.singleShot(0, self.accept)
2348
2358
  except Exception as e:
2349
- QMessageBox.critical(self, "Save Error", f"Failed to save: {e}")
2359
+ QMessageBox.critical(self, self.tr("Save Error"), self.tr("Failed to save: {0}").format(e))
2350
2360
  else:
2351
- self.status.setText("Solved (not saved).")
2361
+ self.status.setText(self.tr("Solved (not saved)."))
2352
2362
 
2353
2363
 
2354
2364
  def _run_batch(self):
2355
2365
  in_dir = self.le_in.text().strip()
2356
2366
  out_dir = self.le_out.text().strip()
2357
2367
  if not in_dir or not os.path.isdir(in_dir):
2358
- QMessageBox.warning(self, "Batch", "Please choose a valid input directory.")
2368
+ QMessageBox.warning(self, self.tr("Batch"), self.tr("Please choose a valid input directory."))
2359
2369
  return
2360
2370
  if not out_dir or not os.path.isdir(out_dir):
2361
- QMessageBox.warning(self, "Batch", "Please choose a valid output directory.")
2371
+ QMessageBox.warning(self, self.tr("Batch"), self.tr("Please choose a valid output directory."))
2362
2372
  return
2363
2373
 
2364
2374
  exts = {".xisf", ".fits", ".fit", ".tif", ".tiff", ".png", ".jpg", ".jpeg"}
@@ -2368,24 +2378,24 @@ class PlateSolverDialog(QDialog):
2368
2378
  if os.path.splitext(f)[1].lower() in exts
2369
2379
  ]
2370
2380
  if not files:
2371
- QMessageBox.information(self, "Batch", "No acceptable image files found.")
2381
+ QMessageBox.information(self, self.tr("Batch"), self.tr("No acceptable image files found."))
2372
2382
  return
2373
2383
 
2374
2384
  self.log.clear()
2375
- self.log.append(f"Found {len(files)} files. Starting batch…")
2385
+ self.log.append(self.tr("Found {0} files. Starting batch…").format(len(files)))
2376
2386
  QApplication.processEvents()
2377
2387
 
2378
2388
  for path in files:
2379
2389
  base = os.path.splitext(os.path.basename(path))[0]
2380
2390
  out = os.path.join(out_dir, base + "_plate_solved.fits")
2381
- self.log.append(f"▶ {path}")
2391
+ self.log.append(f"▶ {path}") # Symbol, no need to translate
2382
2392
  QApplication.processEvents()
2383
2393
 
2384
2394
  try:
2385
2395
  # Load using legacy.load_image()
2386
2396
  image_data, original_header, bit_depth, is_mono = load_image(path)
2387
2397
  if image_data is None:
2388
- self.log.append(" ❌ Failed to load")
2398
+ self.log.append(self.tr(" ❌ Failed to load"))
2389
2399
  continue
2390
2400
 
2391
2401
  # Seed header from original_header
@@ -2424,12 +2434,12 @@ class PlateSolverDialog(QDialog):
2424
2434
  original_header=h2,
2425
2435
  is_mono=is_mono
2426
2436
  )
2427
- self.log.append(" ✔ saved: " + out)
2437
+ self.log.append(self.tr(" ✔ saved: ") + out)
2428
2438
 
2429
2439
  except Exception as e:
2430
- self.log.append(" ❌ error: " + str(e))
2440
+ self.log.append(self.tr(" ❌ error: ") + str(e))
2431
2441
 
2432
2442
  QApplication.processEvents()
2433
2443
 
2434
- self.log.append("Batch plate solving completed.")
2444
+ self.log.append(self.tr("Batch plate solving completed."))
2435
2445