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
@@ -11,6 +11,7 @@ from PyQt6.QtWidgets import (
11
11
  QApplication, QMessageBox
12
12
  )
13
13
  from PyQt6.QtGui import QAction, QShortcut, QKeySequence
14
+ from PyQt6.QtCore import Qt, QCoreApplication
14
15
 
15
16
 
16
17
  def _qs_to_str(seq: QKeySequence) -> str:
@@ -46,27 +47,68 @@ def _seqs_for_action(act: QAction):
46
47
 
47
48
  def _where_for_action(act: QAction) -> str:
48
49
  """Determine where an action is available (Menus/Toolbar or Window)."""
49
- if act.parent():
50
- pn = act.parent().__class__.__name__
51
- if pn.startswith("QMenu") or pn.startswith("QToolBar"):
52
- return "Menus/Toolbar"
53
- return "Window"
50
+ try:
51
+ parent = act.parent()
52
+ if parent is not None:
53
+ pn = parent.__class__.__name__
54
+ if pn.startswith("QMenu") or pn.startswith("QToolBar"):
55
+ return QCoreApplication.translate("CheatSheet", "Menus/Toolbar")
56
+ except Exception:
57
+ pass
58
+ return QCoreApplication.translate("CheatSheet", "Window")
54
59
 
55
60
 
56
61
  def _describe_action(act: QAction) -> str:
57
62
  """Get a human-readable description for an action."""
58
- return _clean_text(act.statusTip() or act.toolTip() or act.text() or act.objectName() or "Action")
63
+ try:
64
+ desc = _clean_text(
65
+ act.statusTip() or act.toolTip() or act.text() or act.objectName() or ""
66
+ )
67
+ except Exception:
68
+ desc = ""
69
+
70
+ if not desc:
71
+ desc = QCoreApplication.translate("CheatSheet", "Action")
72
+ return desc
59
73
 
60
74
 
61
75
  def _describe_shortcut(sc: QShortcut) -> str:
62
76
  """Get a human-readable description for a shortcut."""
63
- return _clean_text(sc.property("hint") or sc.whatsThis() or sc.objectName() or "Shortcut")
77
+ try:
78
+ hint = sc.property("hint")
79
+ desc = _clean_text(hint or sc.whatsThis() or sc.objectName() or "")
80
+ except Exception:
81
+ desc = ""
64
82
 
83
+ if not desc:
84
+ desc = QCoreApplication.translate("CheatSheet", "Shortcut")
85
+ return desc
65
86
 
87
+ def add_extra_shortcuts(rows):
88
+ """
89
+ Add app-level shortcuts that aren't represented by QActions/QShortcuts.
90
+ rows: list of (shortcut_str, action_str, where_str)
91
+ """
92
+ rows.append((
93
+ _clean_text("Ctrl+K"),
94
+ _clean_text("Toggle Show/Hide Mask"),
95
+ QCoreApplication.translate("CheatSheet", "Window"),
96
+ ))
97
+ rows.append((
98
+ _clean_text("Ctrl+Alt+M"),
99
+ _clean_text("SASpro Neon Invaders (Easter Egg)"),
100
+ QCoreApplication.translate("CheatSheet", "Window"),
101
+ ))
102
+
66
103
  def _where_for_shortcut(sc: QShortcut) -> str:
67
104
  """Determine where a shortcut is available."""
68
- par = sc.parent()
69
- return par.__class__.__name__ if par is not None else "Window"
105
+ try:
106
+ par = sc.parent()
107
+ if par is not None:
108
+ return par.__class__.__name__
109
+ except Exception:
110
+ pass
111
+ return QCoreApplication.translate("CheatSheet", "Window")
70
112
 
71
113
 
72
114
  class CheatSheetDialog(QDialog):
@@ -80,7 +122,10 @@ class CheatSheetDialog(QDialog):
80
122
 
81
123
  def __init__(self, parent, keyboard_rows, gesture_rows):
82
124
  super().__init__(parent)
83
- self.setWindowTitle("Keyboard Shortcut Cheat Sheet")
125
+ self.setWindowTitle(self.tr("Keyboard Shortcut Cheat Sheet"))
126
+ self.setWindowFlag(Qt.WindowType.Window, True)
127
+ self.setWindowModality(Qt.WindowModality.NonModal)
128
+ self.setModal(False)
84
129
  self.resize(780, 520)
85
130
 
86
131
  self._keyboard_rows = keyboard_rows
@@ -92,7 +137,7 @@ class CheatSheetDialog(QDialog):
92
137
  pg_keys = QWidget(tabs)
93
138
  v1 = QVBoxLayout(pg_keys)
94
139
  tbl_keys = QTableWidget(0, 3, pg_keys)
95
- tbl_keys.setHorizontalHeaderLabels(["Shortcut", "Action", "Where"])
140
+ tbl_keys.setHorizontalHeaderLabels([self.tr("Shortcut"), self.tr("Action"), self.tr("Where")])
96
141
  tbl_keys.verticalHeader().setVisible(False)
97
142
  tbl_keys.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
98
143
  tbl_keys.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
@@ -114,7 +159,7 @@ class CheatSheetDialog(QDialog):
114
159
  pg_mouse = QWidget(tabs)
115
160
  v2 = QVBoxLayout(pg_mouse)
116
161
  tbl_mouse = QTableWidget(0, 3, pg_mouse)
117
- tbl_mouse.setHorizontalHeaderLabels(["Gesture", "Context", "Effect"])
162
+ tbl_mouse.setHorizontalHeaderLabels([self.tr("Gesture"), self.tr("Context"), self.tr("Effect")])
118
163
  tbl_mouse.verticalHeader().setVisible(False)
119
164
  tbl_mouse.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
120
165
  tbl_mouse.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
@@ -132,16 +177,16 @@ class CheatSheetDialog(QDialog):
132
177
  tbl_mouse.setItem(r, 1, QTableWidgetItem(context))
133
178
  tbl_mouse.setItem(r, 2, QTableWidgetItem(effect))
134
179
 
135
- tabs.addTab(pg_keys, "Base Keyboard")
136
- tabs.addTab(pg_mouse, "Additional & Mouse & Drag")
180
+ tabs.addTab(pg_keys, self.tr("Base Keyboard"))
181
+ tabs.addTab(pg_mouse, self.tr("Additional & Mouse & Drag"))
137
182
 
138
183
  # Buttons
139
184
  btns = QHBoxLayout()
140
185
  btns.addStretch(1)
141
186
 
142
- b_copy = QPushButton("Copy")
187
+ b_copy = QPushButton(self.tr("Copy"))
143
188
  b_copy.clicked.connect(self._copy_all)
144
- b_close = QPushButton("Close")
189
+ b_close = QPushButton(self.tr("Close"))
145
190
  b_close.clicked.connect(self.accept)
146
191
  btns.addWidget(b_copy)
147
192
  btns.addWidget(b_close)
@@ -161,7 +206,7 @@ class CheatSheetDialog(QDialog):
161
206
  for g, c, e in self._gesture_rows:
162
207
  lines.append(f"{g:24} {c:18} {e}")
163
208
  QApplication.clipboard().setText("\n".join(lines))
164
- QMessageBox.information(self, "Copied", "Cheat sheet copied to clipboard.")
209
+ QMessageBox.information(self, self.tr("Copied"), self.tr("Cheat sheet copied to clipboard."))
165
210
 
166
211
 
167
212
  # Legacy alias for backward compatibility
setiastro/saspro/clahe.py CHANGED
@@ -113,7 +113,10 @@ def apply_clahe_to_doc(doc, preset: dict | None):
113
113
  class CLAHEDialogPro(QDialog):
114
114
  def __init__(self, parent, doc, icon: QIcon | None = None):
115
115
  super().__init__(parent)
116
- self.setWindowTitle("CLAHE")
116
+ self.setWindowTitle(self.tr("CLAHE"))
117
+ self.setWindowFlag(Qt.WindowType.Window, True)
118
+ self.setWindowModality(Qt.WindowModality.NonModal)
119
+ self.setModal(False)
117
120
  if icon:
118
121
  try: self.setWindowIcon(icon)
119
122
  except Exception as e:
@@ -130,7 +133,7 @@ class CLAHEDialogPro(QDialog):
130
133
  v = QVBoxLayout(self)
131
134
 
132
135
  # ---- Params (unchanged) ----
133
- grp = QGroupBox("CLAHE Parameters"); grid = QGridLayout(grp)
136
+ grp = QGroupBox(self.tr("CLAHE Parameters")); grid = QGridLayout(grp)
134
137
  self.s_clip = QSlider(Qt.Orientation.Horizontal); self.s_clip.setRange(1, 40); self.s_clip.setValue(20)
135
138
  self.lbl_clip = QLabel("2.0")
136
139
  self.s_clip.valueChanged.connect(lambda val: self.lbl_clip.setText(f"{val/10.0:.1f}"))
@@ -150,13 +153,13 @@ class CLAHEDialogPro(QDialog):
150
153
  self.s_tile.valueChanged.connect(lambda v: self.lbl_tile.setText(f"{v} px"))
151
154
  self.s_tile.valueChanged.connect(self._debounce_preview)
152
155
 
153
- grid.addWidget(QLabel("Tile Size (px):"), 1, 0)
156
+ grid.addWidget(QLabel(self.tr("Tile Size (px):")), 1, 0)
154
157
  grid.addWidget(self.s_tile, 1, 1)
155
158
  grid.addWidget(self.lbl_tile, 1, 2)
156
159
 
157
160
 
158
161
 
159
- grid.addWidget(QLabel("Clip Limit:"), 0, 0); grid.addWidget(self.s_clip, 0, 1); grid.addWidget(self.lbl_clip, 0, 2)
162
+ grid.addWidget(QLabel(self.tr("Clip Limit:")), 0, 0); grid.addWidget(self.s_clip, 0, 1); grid.addWidget(self.lbl_clip, 0, 2)
160
163
 
161
164
  v.addWidget(grp)
162
165
 
@@ -190,9 +193,9 @@ class CLAHEDialogPro(QDialog):
190
193
 
191
194
  # ---- Buttons (unchanged) ----
192
195
  row = QHBoxLayout()
193
- self.btn_apply = QPushButton("Apply"); self.btn_apply.clicked.connect(self._apply)
194
- self.btn_reset = QPushButton("Reset"); self.btn_reset.clicked.connect(self._reset)
195
- self.btn_close = QPushButton("Cancel"); self.btn_close.clicked.connect(self.reject)
196
+ self.btn_apply = QPushButton(self.tr("Apply")); self.btn_apply.clicked.connect(self._apply)
197
+ self.btn_reset = QPushButton(self.tr("Reset")); self.btn_reset.clicked.connect(self._reset)
198
+ self.btn_close = QPushButton(self.tr("Cancel")); self.btn_close.clicked.connect(self.reject)
196
199
  row.addStretch(1); row.addWidget(self.btn_apply); row.addWidget(self.btn_reset); row.addWidget(self.btn_close)
197
200
  v.addLayout(row)
198
201
 
@@ -319,11 +322,34 @@ class CLAHEDialogPro(QDialog):
319
322
  pass
320
323
  # ─────────────────────────────────────────────────────────────
321
324
 
322
- self.accept()
325
+ # Dialog stays open so user can apply to other images
326
+ # Refresh document reference for next operation
327
+ self._refresh_document_from_active()
323
328
 
324
329
  except Exception as e:
325
330
  QMessageBox.critical(self, "CLAHE", f"Failed to apply:\n{e}")
326
331
 
332
+ def _refresh_document_from_active(self):
333
+ """
334
+ Refresh the dialog's document reference to the currently active document.
335
+ This allows reusing the same dialog on different images.
336
+ """
337
+ try:
338
+ main = self.parent()
339
+ if main and hasattr(main, "_active_doc"):
340
+ new_doc = main._active_doc()
341
+ if new_doc is not None and new_doc is not self.doc:
342
+ self.doc = new_doc
343
+ # Refresh original image and preview for new document
344
+ self.orig = np.clip(np.asarray(new_doc.image, dtype=np.float32), 0.0, 1.0)
345
+ disp = self.orig
346
+ if disp.ndim == 2: disp = disp[..., None].repeat(3, axis=2)
347
+ elif disp.ndim == 3 and disp.shape[2] == 1: disp = disp.repeat(3, axis=2)
348
+ self._disp_base = disp
349
+ self._update_preview()
350
+ except Exception:
351
+ pass
352
+
327
353
  def _tile_grid_from_px(self, tile_px: int, hw: tuple[int, int]) -> tuple[int, int]:
328
354
  """
329
355
  Convert desired tile size (pixels) into OpenCV tileGridSize=(n,n)
@@ -90,28 +90,48 @@ def starnet_starless_pair_from_array(
90
90
  # -------- normalize & shape: float32 [0..1], keep note if mono ----------
91
91
  x = np.asarray(src_rgb01, dtype=np.float32)
92
92
  was_mono = (x.ndim == 2) or (x.ndim == 3 and x.shape[2] == 1)
93
- if x.ndim == 2:
94
- x3 = np.stack([x]*3, axis=-1)
95
- elif x.ndim == 3 and x.shape[2] == 1:
96
- x3 = np.repeat(x, 3, axis=2)
97
- else:
98
- x3 = x
99
- x3 = np.nan_to_num(x3, nan=0.0, posinf=0.0, neginf=0.0)
100
-
101
- # -------- pre-StarNet stretch (per channel), only if the data are linear ----------
93
+
94
+ # DELAY expansion: work with 'x' (mono or rgb) directly where possible
95
+ x_input = x
96
+ if x_input.ndim == 3 and x_input.shape[2] == 1:
97
+ x_input = x_input[..., 0] # collapse to 2D for processing if needed, or keep 2D
98
+
99
+ # For StarNet save, we need 3 channels usually, but check if we can save mono?
100
+ # Actually StarNet usually expects RGB Tiff. So we might need to expand just for saving.
101
+ # But let's avoid `x3 = np.repeat` globally.
102
+
103
+ # Optimization: Create x3 ON DEMAND or virtually using broadcasting only when needed.
104
+ # But `save_image` might handle mono TIFs. If StarNet accepts Mono TIF, we save huge RAM.
105
+ # Standard StarNet typically wants RGB. We will enable "is_mono" flag in `save_image` if it is mono,
106
+ # but StarNet is finicky. Let's stick to RGB for StarNet input but avoid `np.repeat` for the WHOLE array
107
+ # if we can just broadcast or slice.
108
+ # Actually, `stretch_color_image` handles broadcasting? No.
109
+ # Let's simple optimize:
110
+
102
111
  if is_linear:
103
- # channel-wise stretch to avoid red cast; your funcs expect [0..1]
104
- pre = stretch_color_image(x3, 0.25, False, False, False)
105
-
112
+ # stretch; if mono use mono stretch
113
+ if was_mono:
114
+ if x.ndim == 3: x = x[..., 0]
115
+ pre = stretch_mono_image(x, 0.25, False, False)
116
+ # expand ONLY for save
117
+ pre_to_save = np.dstack([pre, pre, pre])
118
+ else:
119
+ pre = stretch_color_image(x, 0.25, False, False, False)
120
+ pre_to_save = pre
106
121
  else:
107
- pre = x3 # already non-linear; pass through
122
+ pre = x # floating point 0..1
123
+ if was_mono:
124
+ if pre.ndim == 3: pre = pre[..., 0]
125
+ pre_to_save = np.dstack([pre, pre, pre])
126
+ else:
127
+ pre_to_save = pre
108
128
 
109
129
  # -------- StarNet I/O (write float->16b TIFF; read back float) ----------
110
130
  starnet_dir = os.path.dirname(exe) or os.getcwd()
111
131
  in_path = os.path.join(starnet_dir, "imagetoremovestars.tif")
112
132
  out_path = os.path.join(starnet_dir, "starless.tif")
113
133
 
114
- save_image(pre, in_path, original_format="tif", bit_depth="16-bit",
134
+ save_image(pre_to_save, in_path, original_format="tif", bit_depth="16-bit",
115
135
  original_header=None, is_mono=False, image_meta=None, file_meta=None)
116
136
 
117
137
  exe_name = os.path.basename(exe).lower()
@@ -134,32 +154,61 @@ def starnet_starless_pair_from_array(
134
154
  except Exception:
135
155
  pass
136
156
 
137
- if starless_pre.ndim == 2:
138
- starless_pre = np.stack([starless_pre]*3, axis=-1)
139
- elif starless_pre.ndim == 3 and starless_pre.shape[2] == 1:
140
- starless_pre = np.repeat(starless_pre, 3, axis=2)
157
+ # Don't expand starless_pre yet if we don't need to.
141
158
  starless_pre = starless_pre.astype(np.float32, copy=False)
142
-
159
+ if was_mono and starless_pre.ndim == 3:
160
+ # StarNet output is usually RGB even for mono input. Convert back to mono?
161
+ # Or just use one channel.
162
+ starless_pre = starless_pre[..., 0]
163
+
164
+ # Maintain `pre` as the stretched input (mono or rgb)
165
+
143
166
  # ---- mask-protect in the SAME (stretched) domain as pre/starless_pre ----
144
167
  if core_mask is not None:
145
168
  m = np.clip(core_mask.astype(np.float32), 0.0, 1.0)
146
- m3 = np.repeat(m[..., None], 3, axis=2)
147
- protected_stretched = starless_pre * (1.0 - m3) + pre * m3
169
+ # broadcast mask
170
+ if not was_mono:
171
+ if m.ndim == 2: m = m[..., None]
172
+
173
+ protected_stretched = starless_pre * (1.0 - m) + pre * m
148
174
  else:
149
175
  protected_stretched = starless_pre
176
+
177
+ # Return to 3-channel ONLY if requested by the caller's context?
178
+ # The signature `starnet_starless_pair_from_array` implies it might return what it got.
179
+ # The original returned `protected_unstretch`.
180
+ pass # logic flow continues below...
150
181
 
151
182
  # -------- “unstretch” → shared pseudo-linear space (once, after blend) ----------
152
183
  if is_linear:
153
- protected_unstretch = stretch_color_image(
154
- protected_stretched, 0.05, False, False, False
155
- )
184
+ # choose stretcher based on channels
185
+ if was_mono:
186
+ # ensure 2d
187
+ if protected_stretched.ndim == 3 and protected_stretched.shape[2] == 1:
188
+ protected_stretched = protected_stretched[..., 0]
189
+ elif protected_stretched.ndim == 3:
190
+ # collapse rgb to mono if needed? likely StarNet gave RGB.
191
+ # Keep RGB if StarNet created color artifacts we want to keep?
192
+ # Usually for mono data we want to kill color.
193
+ protected_stretched = protected_stretched.mean(axis=2)
194
+
195
+ protected_unstretch = stretch_mono_image(
196
+ protected_stretched, 0.05, False, False
197
+ )
198
+ # Expand finally for return constraint?
199
+ # The older function returned RGB-like.
200
+ # Let's expand here at the VERY END.
201
+ protected_unstretch = np.dstack([protected_unstretch]*3)
202
+ else:
203
+ protected_unstretch = stretch_color_image(
204
+ protected_stretched, 0.05, False, False, False
205
+ )
156
206
  else:
157
207
  protected_unstretch = protected_stretched
208
+ if was_mono and protected_unstretch.ndim == 2:
209
+ protected_unstretch = np.dstack([protected_unstretch]*3)
158
210
 
159
- protected_unstretch = np.clip(
160
- protected_unstretch.astype(np.float32, copy=False), 0.0, 1.0
161
- )
162
- return protected_unstretch, protected_unstretch
211
+ return np.clip(protected_unstretch, 0.0, 1.0), np.clip(protected_unstretch, 0.0, 1.0)
163
212
 
164
213
 
165
214
 
@@ -170,8 +219,17 @@ def darkstar_starless_from_array(src_rgb01: np.ndarray, settings, **_ignored) ->
170
219
  """
171
220
  # normalize channels
172
221
  img = src_rgb01.astype(np.float32, copy=False)
173
- if img.ndim == 2: img = np.stack([img]*3, axis=-1)
174
- if img.ndim == 3 and img.shape[2] == 1: img = np.repeat(img, 3, axis=2)
222
+ # Delay expansion: if it's 2D/Mono, send it as-is if DarkStar supports it,
223
+ # but DarkStar expects 3-channel TIF usually.
224
+ # We'll just expand for the save call, not "in place" if possible.
225
+ # Actually DarkStar runner saves `img` directly.
226
+ # So we'll expand just for that save to avoid holding 2 copies in memory.
227
+ if img.ndim == 2:
228
+ img_to_save = np.stack([img]*3, axis=-1)
229
+ elif img.ndim == 3 and img.shape[2] == 1:
230
+ img_to_save = np.repeat(img, 3, axis=2)
231
+ else:
232
+ img_to_save = img
175
233
 
176
234
  # resolve exe and base folder
177
235
  exe, base = _resolve_darkstar_exe(type("Dummy", (), {"settings": settings})())
@@ -192,7 +250,8 @@ def darkstar_starless_from_array(src_rgb01: np.ndarray, settings, **_ignored) ->
192
250
  out_path = os.path.join(output_dir, "imagetoremovestars_starless.tif")
193
251
 
194
252
  # save input as float32 TIFF
195
- save_image(img, in_path, original_format="tif", bit_depth="32-bit floating point",
253
+ # save input as float32 TIFF
254
+ save_image(img_to_save, in_path, original_format="tif", bit_depth="32-bit floating point",
196
255
  original_header=None, is_mono=False, image_meta=None, file_meta=None)
197
256
 
198
257
  # build command (SASv2 parity): default unscreen, show extracted stars off, stride 512
@@ -218,8 +277,12 @@ def darkstar_starless_from_array(src_rgb01: np.ndarray, settings, **_ignored) ->
218
277
  if starless is None:
219
278
  raise RuntimeError("DarkStar produced no output.")
220
279
 
221
- if starless.ndim == 2: starless = np.stack([starless]*3, axis=-1)
222
- if starless.shape[2] == 1: starless = np.repeat(starless, 3, axis=2)
280
+ # Delayed expansion
281
+ if starless.ndim == 2:
282
+ starless = np.stack([starless]*3, axis=-1)
283
+ elif starless.ndim == 3 and starless.shape[2] == 1:
284
+ starless = np.repeat(starless, 3, axis=2)
285
+
223
286
  return np.clip(starless.astype(np.float32, copy=False), 0.0, 1.0)
224
287
 
225
288
  # ---------- small helpers ----------
@@ -239,6 +302,10 @@ def _inv_affine_2x3(M: np.ndarray) -> np.ndarray:
239
302
  def _to_luma(img: np.ndarray) -> np.ndarray:
240
303
  if img.ndim == 2: return img.astype(np.float32, copy=False)
241
304
  if img.ndim == 3 and img.shape[-1] == 3:
305
+ try:
306
+ return cv2.cvtColor(img, cv2.COLOR_RGB2GRAY).astype(np.float32, copy=False)
307
+ except Exception:
308
+ pass
242
309
  r,g,b = img[...,0], img[...,1], img[...,2]
243
310
  return (0.2126*r + 0.7152*g + 0.0722*b).astype(np.float32, copy=False)
244
311
  if img.ndim == 3 and img.shape[-1] == 1:
@@ -748,11 +815,9 @@ def _shift_to_comet(img: np.ndarray, xy: Tuple[float,float], ref_xy: Tuple[float
748
815
  M = np.array([[1.0, 0.0, dx], [0.0, 1.0, dy]], dtype=np.float32)
749
816
  H, W = img.shape[:2]
750
817
  interp = cv2.INTER_LANCZOS4
751
- if img.ndim == 2:
752
- return cv2.warpAffine(img, M, (W, H), flags=interp, borderMode=cv2.BORDER_REFLECT)
753
- # 3-channel
754
- ch = [cv2.warpAffine(img[...,c], M, (W, H), flags=interp, borderMode=cv2.BORDER_REFLECT) for c in range(img.shape[-1])]
755
- return np.stack(ch, axis=-1)
818
+
819
+ # Vectorized warp for both 2D (mono) and 3D (RGB)
820
+ return cv2.warpAffine(img, M, (W, H), flags=interp, borderMode=cv2.BORDER_REFLECT)
756
821
 
757
822
  def stack_comet_aligned(file_list: List[str],
758
823
  comet_xy: Dict[str, Tuple[float,float]],
@@ -0,0 +1,107 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Common UI strings translation module.
4
+
5
+ This module provides centralized translation for common button labels,
6
+ dialog titles, and messages used throughout the application.
7
+ """
8
+ from PyQt6.QtCore import QCoreApplication
9
+
10
+ def _tr(text: str, context: str = "Common") -> str:
11
+ """Translate common UI strings."""
12
+ return QCoreApplication.translate(context, text)
13
+
14
+ # ============================================================================
15
+ # Common Button Labels
16
+ # ============================================================================
17
+ class Buttons:
18
+ @staticmethod
19
+ def apply(): return _tr("Apply")
20
+ @staticmethod
21
+ def cancel(): return _tr("Cancel")
22
+ @staticmethod
23
+ def close(): return _tr("Close")
24
+ @staticmethod
25
+ def ok(): return _tr("OK")
26
+ @staticmethod
27
+ def yes(): return _tr("Yes")
28
+ @staticmethod
29
+ def no(): return _tr("No")
30
+ @staticmethod
31
+ def save(): return _tr("Save")
32
+ @staticmethod
33
+ def save_as(): return _tr("Save As...")
34
+ @staticmethod
35
+ def open(): return _tr("Open...")
36
+ @staticmethod
37
+ def browse(): return _tr("Browse...")
38
+ @staticmethod
39
+ def reset(): return _tr("Reset")
40
+ @staticmethod
41
+ def clear(): return _tr("Clear")
42
+ @staticmethod
43
+ def delete(): return _tr("Delete")
44
+ @staticmethod
45
+ def add(): return _tr("Add")
46
+ @staticmethod
47
+ def remove(): return _tr("Remove")
48
+ @staticmethod
49
+ def preview(): return _tr("Preview")
50
+ @staticmethod
51
+ def apply_to_document(): return _tr("Apply to Document")
52
+ @staticmethod
53
+ def zoom_in(): return _tr("Zoom In")
54
+ @staticmethod
55
+ def zoom_out(): return _tr("Zoom Out")
56
+ @staticmethod
57
+ def fit_to_preview(): return _tr("Fit to Preview")
58
+ @staticmethod
59
+ def show_original(): return _tr("Show Original")
60
+ @staticmethod
61
+ def run(): return _tr("Run")
62
+ @staticmethod
63
+ def stop(): return _tr("Stop")
64
+ @staticmethod
65
+ def export(): return _tr("Export")
66
+ @staticmethod
67
+ def import_(): return _tr("Import")
68
+
69
+ # ============================================================================
70
+ # Common Dialog Titles
71
+ # ============================================================================
72
+ class Titles:
73
+ @staticmethod
74
+ def error(): return _tr("Error")
75
+ @staticmethod
76
+ def warning(): return _tr("Warning")
77
+ @staticmethod
78
+ def info(): return _tr("Information")
79
+ @staticmethod
80
+ def confirm(): return _tr("Confirm")
81
+ @staticmethod
82
+ def success(): return _tr("Success")
83
+ @staticmethod
84
+ def processing(): return _tr("Processing...")
85
+ @staticmethod
86
+ def please_wait(): return _tr("Please wait")
87
+
88
+ # ============================================================================
89
+ # Common Messages
90
+ # ============================================================================
91
+ class Messages:
92
+ @staticmethod
93
+ def no_image(): return _tr("No image loaded.")
94
+ @staticmethod
95
+ def no_active_image(): return _tr("No active image.")
96
+ @staticmethod
97
+ def load_image_first(): return _tr("Load an image first.")
98
+ @staticmethod
99
+ def processing_failed(): return _tr("Processing failed.")
100
+ @staticmethod
101
+ def operation_complete(): return _tr("Operation complete.")
102
+ @staticmethod
103
+ def are_you_sure(): return _tr("Are you sure?")
104
+ @staticmethod
105
+ def unsaved_changes(): return _tr("You have unsaved changes.")
106
+ @staticmethod
107
+ def exit_confirm(): return _tr("Do you really want to exit?")
@@ -129,7 +129,7 @@ class ContinuumSubtractTab(QWidget):
129
129
  columns_layout = QHBoxLayout() # holds the three groups
130
130
 
131
131
  # — NB group —
132
- nb_group = QGroupBox("Narrowband Filters")
132
+ nb_group = QGroupBox(self.tr("Narrowband Filters"))
133
133
  nb_l = QVBoxLayout()
134
134
  for name, attr in [("Ha","ha"), ("SII","sii"), ("OIII","oiii")]:
135
135
  # starry
@@ -166,9 +166,9 @@ class ContinuumSubtractTab(QWidget):
166
166
  nb_l.addWidget(btn_sl); nb_l.addWidget(lbl_sl)
167
167
 
168
168
  # user controls
169
- self.linear_output_checkbox = QCheckBox("Output Linear Image Only")
169
+ self.linear_output_checkbox = QCheckBox(self.tr("Output Linear Image Only"))
170
170
  nb_l.addWidget(self.linear_output_checkbox)
171
- self.denoise_checkbox = QCheckBox("Denoise continuum result with Cosmic Clarity (0.9)") # NEW
171
+ self.denoise_checkbox = QCheckBox(self.tr("Denoise continuum result with Cosmic Clarity (0.9)")) # NEW
172
172
  self.denoise_checkbox.setToolTip(
173
173
  "Runs Cosmic Clarity denoise on the linear continuum-subtracted image "
174
174
  "before any non-linear stretch."
@@ -221,13 +221,13 @@ class ContinuumSubtractTab(QWidget):
221
221
 
222
222
  nb_l.addStretch(1)
223
223
 
224
- self.clear_button = QPushButton("Clear Loaded Images")
224
+ self.clear_button = QPushButton(self.tr("Clear Loaded Images"))
225
225
  self.clear_button.clicked.connect(self.clear_loaded_images)
226
226
  nb_l.addWidget(self.clear_button)
227
227
  nb_group.setLayout(nb_l)
228
228
 
229
229
  # — Continuum group —
230
- cont_group = QGroupBox("Continuum Sources")
230
+ cont_group = QGroupBox(self.tr("Continuum Sources"))
231
231
  cont_l = QVBoxLayout()
232
232
  for name, attr in [("Red","red"), ("Green","green"), ("OSC","osc")]:
233
233
  btn = QPushButton(f"Load {name}")
@@ -248,7 +248,7 @@ class ContinuumSubtractTab(QWidget):
248
248
  cont_group.setLayout(cont_l)
249
249
 
250
250
  # — White balance diagnostics —
251
- wb_group = QGroupBox("Star-Based WB")
251
+ wb_group = QGroupBox(self.tr("Star-Based WB"))
252
252
  self.wb_l = QVBoxLayout()
253
253
  self.wb_l.setAlignment(Qt.AlignmentFlag.AlignTop)
254
254
  wb_group.setLayout(self.wb_l)
@@ -267,7 +267,7 @@ class ContinuumSubtractTab(QWidget):
267
267
 
268
268
  # — Bottom row: Execute & status —
269
269
  bottom_layout = QHBoxLayout()
270
- self.execute_button = QPushButton("Execute")
270
+ self.execute_button = QPushButton(self.tr("Execute"))
271
271
  self.execute_button.clicked.connect(self.startContinuumSubtraction)
272
272
  bottom_layout.addWidget(self.execute_button, stretch=1)
273
273
  bottom_layout.addWidget(self.spinnerLabel, stretch=1)
setiastro/saspro/convo.py CHANGED
@@ -152,7 +152,10 @@ class ConvoDeconvoDialog(QDialog):
152
152
  if hasattr(self._main, "currentDocumentChanged") and self._doc_override is None:
153
153
  self._main.currentDocumentChanged.connect(self._on_active_doc_changed)
154
154
 
155
- self.setWindowTitle("Convolution / Deconvolution")
155
+ self.setWindowTitle(self.tr("Convolution / Deconvolution"))
156
+ self.setWindowFlag(Qt.WindowType.Window, True)
157
+ self.setWindowModality(Qt.WindowModality.NonModal)
158
+ self.setModal(False)
156
159
  self.resize(1000, 650)
157
160
  self._use_custom_psf = False
158
161
  self._custom_psf: Optional[np.ndarray] = None
@@ -190,14 +193,14 @@ class ConvoDeconvoDialog(QDialog):
190
193
 
191
194
  # Buttons
192
195
  row1 = QHBoxLayout()
193
- self.preview_btn = QPushButton("Preview")
194
- self.undo_btn = QPushButton("Undo")
195
- self.close_btn = QPushButton("Close")
196
+ self.preview_btn = QPushButton(self.tr("Preview"))
197
+ self.undo_btn = QPushButton(self.tr("Undo"))
198
+ self.close_btn = QPushButton(self.tr("Close"))
196
199
  row1.addWidget(self.preview_btn); row1.addWidget(self.undo_btn)
197
200
  left_layout.addLayout(row1)
198
201
 
199
202
  row2 = QHBoxLayout()
200
- self.push_btn = QPushButton("Push")
203
+ self.push_btn = QPushButton(self.tr("Push"))
201
204
  row2.addWidget(self.push_btn); row2.addWidget(self.close_btn)
202
205
  left_layout.addLayout(row2)
203
206
 
@@ -333,7 +336,7 @@ class ConvoDeconvoDialog(QDialog):
333
336
  form.addRow("Rotation:", self.conv_rotation_slider)
334
337
 
335
338
  layout.addLayout(form); layout.addStretch()
336
- self.tabs.addTab(conv_tab, "Convolution")
339
+ self.tabs.addTab(conv_tab, self.tr("Convolution"))
337
340
 
338
341
  def _build_deconvolution_tab(self):
339
342
  deconv_tab = QWidget()
@@ -445,7 +448,7 @@ class ConvoDeconvoDialog(QDialog):
445
448
 
446
449
  outer_layout.addWidget(self.deconv_stack_container)
447
450
  outer_layout.addStretch()
448
- self.tabs.addTab(deconv_tab, "Deconvolution")
451
+ self.tabs.addTab(deconv_tab, self.tr("Deconvolution"))
449
452
 
450
453
  # Clear “custom PSF” if sliders change
451
454
  for s in (self.rl_psf_radius_slider, self.rl_psf_shape_slider, self.rl_psf_aspect_slider, self.rl_psf_rotation_slider):
@@ -486,7 +489,7 @@ class ConvoDeconvoDialog(QDialog):
486
489
  layout.addWidget(self.sep_psf_preview, alignment=Qt.AlignmentFlag.AlignHCenter)
487
490
 
488
491
  layout.addStretch()
489
- self.tabs.addTab(psf_tab, "PSF Estimator")
492
+ self.tabs.addTab(psf_tab, self.tr("PSF Estimator"))
490
493
 
491
494
  def _build_tv_denoise_tab(self):
492
495
  tvd_tab = QWidget(); layout = QVBoxLayout(tvd_tab)
@@ -499,7 +502,7 @@ class ConvoDeconvoDialog(QDialog):
499
502
  self.tv_multichannel_checkbox.setToolTip("If checked and the image is color, run TV on all channels jointly")
500
503
  form.addRow("", self.tv_multichannel_checkbox)
501
504
  layout.addLayout(form); layout.addStretch()
502
- self.tabs.addTab(tvd_tab, "TV Denoise")
505
+ self.tabs.addTab(tvd_tab, self.tr("TV Denoise"))
503
506
 
504
507
  # ---------------- UI reactions ----------------
505
508
  def _on_deconv_algo_changed(self, selected: str):