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
@@ -0,0 +1,77 @@
1
+ # src/setiastro/saspro/versioning.py
2
+ from __future__ import annotations
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ def _read_pyproject_version(start: Path) -> str | None:
8
+ """
9
+ Walk upward from 'start' looking for pyproject.toml,
10
+ return [tool.poetry].version if found.
11
+ """
12
+ # Python 3.11+: tomllib; Python 3.10: tomli
13
+ try:
14
+ import tomllib as _toml # type: ignore
15
+ except Exception:
16
+ try:
17
+ import tomli as _toml # type: ignore
18
+ except Exception:
19
+ _toml = None
20
+
21
+ if _toml is None:
22
+ return None
23
+
24
+ cur = start
25
+ for _ in range(8): # don't walk forever
26
+ pp = cur / "pyproject.toml"
27
+ if pp.exists():
28
+ try:
29
+ data = _toml.loads(pp.read_text(encoding="utf-8"))
30
+ v = (
31
+ data.get("tool", {})
32
+ .get("poetry", {})
33
+ .get("version", None)
34
+ )
35
+ if isinstance(v, str) and v.strip():
36
+ return v.strip()
37
+ except Exception:
38
+ return None
39
+ if cur.parent == cur:
40
+ break
41
+ cur = cur.parent
42
+ return None
43
+
44
+ def get_app_version(dist_name: str = "setiastrosuitepro") -> str:
45
+ """
46
+ Single source of truth for SASpro version.
47
+
48
+ Order:
49
+ 0) build_info.py (best for PyInstaller frozen builds)
50
+ 1) installed distribution metadata (best for pip installs)
51
+ 2) pyproject.toml (best for running from source checkout)
52
+ 3) safe fallback
53
+ """
54
+ # 0) build_info (PyInstaller-friendly)
55
+ try:
56
+ from ._generated.build_info import APP_VERSION
57
+ if isinstance(APP_VERSION, str) and APP_VERSION.strip() and APP_VERSION.strip() != "0.0.0":
58
+ return APP_VERSION.strip()
59
+ except Exception:
60
+ pass
61
+
62
+ # 1) Installed package metadata
63
+ try:
64
+ from importlib.metadata import version as _dist_version
65
+ v = _dist_version(dist_name)
66
+ if v and v != "0.1.0":
67
+ return v
68
+ except Exception:
69
+ pass
70
+
71
+ # 2) Source tree pyproject.toml
72
+ here = Path(__file__).resolve()
73
+ v2 = _read_pyproject_version(here.parent)
74
+ if v2:
75
+ return v2
76
+
77
+ return "0.0.0"
@@ -174,7 +174,7 @@ class BundleChip(QWidget):
174
174
  v.setSpacing(0)
175
175
  self._title = QLabel(self._name)
176
176
  self._title.setObjectName("chipTitle")
177
- self._hint = QLabel("Drag to move · Ctrl+drag to apply · Drop views/shortcuts here")
177
+ self._hint = QLabel(self._panel.tr("Drag to move · Ctrl+drag to apply · Drop views/shortcuts here"))
178
178
  self._hint.setObjectName("chipHint")
179
179
  v.addWidget(self._title, 0, Qt.AlignmentFlag.AlignCenter)
180
180
  v.addWidget(self._hint, 0, Qt.AlignmentFlag.AlignCenter)
@@ -265,7 +265,7 @@ class BundleChip(QWidget):
265
265
 
266
266
  def contextMenuEvent(self, ev):
267
267
  m = QMenu(self)
268
- act_del = m.addAction("Delete Chip")
268
+ act_del = m.addAction(self._panel.tr("Delete Chip"))
269
269
  act = m.exec(ev.globalPos())
270
270
  if act is act_del:
271
271
  try:
@@ -378,16 +378,17 @@ class SelectViewsDialog(QDialog):
378
378
  super().__init__(parent)
379
379
  self.setWindowTitle("Add Views to Bundle")
380
380
  self.setWindowFlag(Qt.WindowType.Window, True)
381
+ self.setWindowModality(Qt.WindowModality.NonModal)
381
382
  self.setModal(False)
382
383
  #self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
383
384
  self._boxes: list[QCheckBox] = []
384
385
 
385
386
  v = QVBoxLayout(self)
386
- v.addWidget(QLabel("Choose views to add:"))
387
+ v.addWidget(QLabel(self.tr("Choose views to add:")))
387
388
  v.setSpacing(6)
388
389
 
389
390
  # NEW: "Select all" checkbox
390
- self._select_all = QCheckBox("Select all open views")
391
+ self._select_all = QCheckBox(self.tr("Select all open views"))
391
392
  self._select_all.toggled.connect(self._on_select_all_toggled)
392
393
  v.addWidget(self._select_all)
393
394
 
@@ -509,7 +510,9 @@ class ViewBundleDialog(QDialog):
509
510
  def __init__(self, parent: QWidget | None = None):
510
511
  super().__init__(parent)
511
512
  _pin_on_top_mac(self)
512
- self.setWindowTitle("View Bundles")
513
+ self.setWindowTitle(self.tr("View Bundles"))
514
+ self.setWindowFlag(Qt.WindowType.Window, True)
515
+ self.setWindowModality(Qt.WindowModality.NonModal)
513
516
  self.setModal(False)
514
517
  self.resize(900, 540)
515
518
  self.setAcceptDrops(True)
@@ -535,28 +538,28 @@ class ViewBundleDialog(QDialog):
535
538
  self.docs.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
536
539
  self.docs.customContextMenuRequested.connect(self._docs_context_menu)
537
540
  self.docs.itemDoubleClicked.connect(self._docs_item_activated)
538
- self.btn_new = QPushButton("New Bundle")
539
- self.btn_dup = QPushButton("Duplicate")
540
- self.btn_del = QPushButton("Delete")
541
- self.btn_clear = QPushButton("Clear Views")
542
- self.btn_remove_sel = QPushButton("Remove Selected")
543
- self.btn_add_from_open = QPushButton("Add from Open…")
544
- self.btn_add_files = QPushButton("Add Files…")
545
- self.btn_add_dir = QPushButton("Add Directory (Recursive)…")
546
- self.btn_compress = QPushButton("Compress to Chip")
547
- self.drop_hint = QLabel("Drop views here to add • Drop shortcuts here to apply to THIS bundle")
541
+ self.btn_new = QPushButton(self.tr("New Bundle"))
542
+ self.btn_dup = QPushButton(self.tr("Duplicate"))
543
+ self.btn_del = QPushButton(self.tr("Delete"))
544
+ self.btn_clear = QPushButton(self.tr("Clear Views"))
545
+ self.btn_remove_sel = QPushButton(self.tr("Remove Selected"))
546
+ self.btn_add_from_open = QPushButton(self.tr("Add from Open…"))
547
+ self.btn_add_files = QPushButton(self.tr("Add Files…"))
548
+ self.btn_add_dir = QPushButton(self.tr("Add Directory (Recursive)…"))
549
+ self.btn_compress = QPushButton(self.tr("Compress to Chip"))
550
+ self.drop_hint = QLabel(self.tr("Drop views here to add • Drop shortcuts here to apply to THIS bundle"))
548
551
  self.drop_hint.setAlignment(Qt.AlignmentFlag.AlignCenter)
549
552
  self.drop_hint.setStyleSheet("color:#aaa; padding:6px; border:1px dashed #666; border-radius:6px;")
550
553
 
551
554
  left = QVBoxLayout()
552
- left.addWidget(QLabel("Bundles"))
555
+ left.addWidget(QLabel(self.tr("Bundles")))
553
556
  left.addWidget(self.list, 1)
554
557
  row = QHBoxLayout()
555
558
  row.addWidget(self.btn_new); row.addWidget(self.btn_dup); row.addWidget(self.btn_del)
556
559
  left.addLayout(row)
557
560
 
558
561
  right = QVBoxLayout()
559
- right.addWidget(QLabel("Views in Selected Bundle"))
562
+ right.addWidget(QLabel(self.tr("Views in Selected Bundle")))
560
563
  right.addWidget(self.docs, 1)
561
564
 
562
565
  rrow = QHBoxLayout()
@@ -132,14 +132,14 @@ class HDRWorker(QObject):
132
132
 
133
133
  def run(self):
134
134
  try:
135
- self.progress_update.emit("Converting to Lab color space…", 10)
135
+ self.progress_update.emit(self.tr("Converting to Lab color space…"), 10)
136
136
  # progress checkpoints inline here are cosmetic
137
- self.progress_update.emit("Decomposing luminance with starlet…", 20)
137
+ self.progress_update.emit(self.tr("Decomposing luminance with starlet…"), 20)
138
138
  # full compute
139
139
  transformed, mask = compute_wavescale_hdr(
140
140
  self.rgb_image, self.n_scales, self.compression_factor, self.mask_gamma, self.base_kernel
141
141
  )
142
- self.progress_update.emit("Finalizing…", 95)
142
+ self.progress_update.emit(self.tr("Finalizing…"), 95)
143
143
  self.finished.emit(transformed, mask)
144
144
  except Exception as e:
145
145
  print("WaveScale HDR error:", e)
@@ -152,7 +152,7 @@ class HDRWorker(QObject):
152
152
  class MaskDisplayWindow(QDialog):
153
153
  def __init__(self, parent=None):
154
154
  super().__init__(parent)
155
- self.setWindowTitle("HDR Mask (L-based)")
155
+ self.setWindowTitle(self.tr("HDR Mask (L-based)"))
156
156
  self.lbl = QLabel(alignment=Qt.AlignmentFlag.AlignCenter)
157
157
  self.lbl.setFixedSize(400, 400) # keep it small
158
158
  lay = QVBoxLayout(self)
@@ -184,7 +184,7 @@ class WaveScaleHDRDialogPro(QDialog):
184
184
 
185
185
  def __init__(self, parent, doc, icon_path: str | None = None, *, headless: bool=False, bypass_guard: bool=False):
186
186
  super().__init__(parent)
187
- self.setWindowTitle("WaveScale HDR")
187
+ self.setWindowTitle(self.tr("WaveScale HDR"))
188
188
  self._headless = bool(headless)
189
189
  self._bypass_guard = bool(bypass_guard)
190
190
  if self._headless:
@@ -199,6 +199,9 @@ class WaveScaleHDRDialogPro(QDialog):
199
199
  import logging
200
200
  logging.debug(f"Exception suppressed: {type(e).__name__}: {e}")
201
201
  self.resize(980, 700)
202
+ self.setWindowFlag(Qt.WindowType.Window, True)
203
+ self.setWindowModality(Qt.WindowModality.NonModal)
204
+ self.setModal(False)
202
205
 
203
206
  self._doc = doc
204
207
  base = getattr(doc, "image", None)
@@ -241,45 +244,45 @@ class WaveScaleHDRDialogPro(QDialog):
241
244
  self.scroll.setWidget(self.view)
242
245
 
243
246
  # controls (add zoom row)
244
- self.grp = QGroupBox("HDR Controls")
247
+ self.grp = QGroupBox(self.tr("HDR Controls"))
245
248
  form = QFormLayout(self.grp)
246
249
 
247
250
  self.s_scales = QSlider(Qt.Orientation.Horizontal); self.s_scales.setRange(2, 10); self.s_scales.setValue(5)
248
251
  self.s_comp = QSlider(Qt.Orientation.Horizontal); self.s_comp.setRange(10, 500); self.s_comp.setValue(150)
249
252
  self.s_gamma = QSlider(Qt.Orientation.Horizontal); self.s_gamma.setRange(10, 1000); self.s_gamma.setValue(500)
250
253
 
251
- form.addRow("Number of Scales:", self.s_scales)
252
- form.addRow("Coarse Compression:", self.s_comp)
253
- form.addRow("Mask Gamma:", self.s_gamma)
254
+ form.addRow(self.tr("Number of Scales:"), self.s_scales)
255
+ form.addRow(self.tr("Coarse Compression:"), self.s_comp)
256
+ form.addRow(self.tr("Mask Gamma:"), self.s_gamma)
254
257
 
255
258
  row = QHBoxLayout()
256
- self.btn_preview = QPushButton("Preview")
257
- self.btn_toggle = QPushButton("Show Original"); self.btn_toggle.setCheckable(True)
259
+ self.btn_preview = QPushButton(self.tr("Preview"))
260
+ self.btn_toggle = QPushButton(self.tr("Show Original")); self.btn_toggle.setCheckable(True)
258
261
  row.addWidget(self.btn_preview); row.addWidget(self.btn_toggle)
259
262
  form.addRow(row)
260
263
 
261
264
  # ↓ NEW: zoom controls
262
265
  zoom_row = QHBoxLayout()
263
- self.btn_zoom_in = QPushButton("Zoom In")
264
- self.btn_zoom_out = QPushButton("Zoom Out")
265
- self.btn_fit = QPushButton("Fit to Preview")
266
+ self.btn_zoom_in = QPushButton(self.tr("Zoom In"))
267
+ self.btn_zoom_out = QPushButton(self.tr("Zoom Out"))
268
+ self.btn_fit = QPushButton(self.tr("Fit to Preview"))
266
269
  zoom_row.addWidget(self.btn_zoom_in)
267
270
  zoom_row.addWidget(self.btn_zoom_out)
268
271
  zoom_row.addWidget(self.btn_fit)
269
272
  form.addRow(zoom_row)
270
273
 
271
274
  # progress group (unchanged)
272
- self.prog_grp = QGroupBox("Processing Progress")
275
+ self.prog_grp = QGroupBox(self.tr("Processing Progress"))
273
276
  vprog = QVBoxLayout(self.prog_grp)
274
- self.lbl_step = QLabel("Idle")
277
+ self.lbl_step = QLabel(self.tr("Idle"))
275
278
  self.bar = QProgressBar(); self.bar.setRange(0, 100); self.bar.setValue(0)
276
279
  vprog.addWidget(self.lbl_step); vprog.addWidget(self.bar)
277
280
 
278
281
  # bottom buttons (unchanged)
279
282
  bot = QHBoxLayout()
280
- self.btn_apply = QPushButton("Apply to Document"); self.btn_apply.setEnabled(False)
281
- self.btn_reset = QPushButton("Reset")
282
- self.btn_close = QPushButton("Close")
283
+ self.btn_apply = QPushButton(self.tr("Apply to Document")); self.btn_apply.setEnabled(False)
284
+ self.btn_reset = QPushButton(self.tr("Reset"))
285
+ self.btn_close = QPushButton(self.tr("Close"))
283
286
  bot.addStretch(1); bot.addWidget(self.btn_apply); bot.addWidget(self.btn_reset); bot.addWidget(self.btn_close)
284
287
 
285
288
  # layout (unchanged)
@@ -381,9 +384,9 @@ class WaveScaleHDRDialogPro(QDialog):
381
384
  # Soft warning instead of rejecting the dialog
382
385
  try:
383
386
  QMessageBox.information(
384
- self, "WaveScale HDR",
385
- "A headless HDR run appears to be in progress. "
386
- "This window will remain open; you can still preview safely."
387
+ self, self.tr("WaveScale HDR"),
388
+ self.tr("A headless HDR run appears to be in progress. "
389
+ "This window will remain open; you can still preview safely.")
387
390
  )
388
391
  except Exception:
389
392
  pass
@@ -480,10 +483,10 @@ class WaveScaleHDRDialogPro(QDialog):
480
483
 
481
484
  def _toggle(self):
482
485
  if self.btn_toggle.isChecked():
483
- self.btn_toggle.setText("Show Preview")
486
+ self.btn_toggle.setText(self.tr("Show Preview"))
484
487
  self._set_pix(self.original_rgb)
485
488
  else:
486
- self.btn_toggle.setText("Show Original")
489
+ self.btn_toggle.setText(self.tr("Show Original"))
487
490
  self._set_pix(self.preview_rgb)
488
491
 
489
492
  def _reset(self):
@@ -492,9 +495,9 @@ class WaveScaleHDRDialogPro(QDialog):
492
495
  self.s_gamma.setValue(500)
493
496
  self.preview_rgb = self.original_rgb.copy()
494
497
  self._set_pix(self.preview_rgb)
495
- self.lbl_step.setText("Idle"); self.bar.setValue(0)
498
+ self.lbl_step.setText(self.tr("Idle")); self.bar.setValue(0)
496
499
  self.btn_apply.setEnabled(False)
497
- self.btn_toggle.setChecked(False); self.btn_toggle.setText("Show Original")
500
+ self.btn_toggle.setChecked(False); self.btn_toggle.setText(self.tr("Show Original"))
498
501
 
499
502
  def _start_preview(self):
500
503
  self.btn_preview.setEnabled(False); self.btn_apply.setEnabled(False)
@@ -519,7 +522,7 @@ class WaveScaleHDRDialogPro(QDialog):
519
522
  def _on_finished(self, transformed_rgb: np.ndarray, mask: np.ndarray):
520
523
  self.btn_preview.setEnabled(True)
521
524
  if transformed_rgb is None:
522
- QMessageBox.critical(self, "WaveScale HDR", "Processing failed.")
525
+ QMessageBox.critical(self, self.tr("WaveScale HDR"), self.tr("Processing failed."))
523
526
  return
524
527
 
525
528
  # ← NEW: combine HDR's luminance mask with the doc's active mask (if present)
@@ -532,13 +535,13 @@ class WaveScaleHDRDialogPro(QDialog):
532
535
 
533
536
  # show the *combined* mask in the little window
534
537
  self.mask_win.setWindowTitle(
535
- "HDR Mask (L × Active Mask)" if self._get_doc_active_mask_2d() is not None else "HDR Mask (L-based)"
538
+ self.tr("HDR Mask (L × Active Mask)") if self._get_doc_active_mask_2d() is not None else self.tr("HDR Mask (L-based)")
536
539
  )
537
540
  self.mask_win.update_mask(mask_comb)
538
541
 
539
542
  self.btn_apply.setEnabled(True)
540
- self.btn_toggle.setChecked(False); self.btn_toggle.setText("Show Original")
541
- self.lbl_step.setText("Preview ready"); self.bar.setValue(100)
543
+ self.btn_toggle.setChecked(False); self.btn_toggle.setText(self.tr("Show Original"))
544
+ self.lbl_step.setText(self.tr("Preview ready")); self.bar.setValue(100)
542
545
  # Headless: apply immediately (exactly like clicking "Apply to Document")
543
546
  if self._headless:
544
547
  QTimer.singleShot(0, self._apply_to_doc)
@@ -561,7 +564,7 @@ class WaveScaleHDRDialogPro(QDialog):
561
564
  else:
562
565
  self._doc.image = out
563
566
  except Exception as e:
564
- QMessageBox.critical(self, "WaveScale HDR", f"Failed to write to document:\n{e}")
567
+ QMessageBox.critical(self, self.tr("WaveScale HDR"), self.tr("Failed to write to document:\n{0}").format(e))
565
568
  return
566
569
 
567
570
  # ── Build preset from current sliders ─────────────────────────
@@ -606,8 +609,26 @@ class WaveScaleHDRDialogPro(QDialog):
606
609
  except Exception:
607
610
  pass
608
611
 
609
- self.accept()
612
+ # Dialog stays open so user can apply to other images
613
+ # Refresh document reference for next operation
614
+ self._refresh_document_from_active()
610
615
 
616
+ def _refresh_document_from_active(self):
617
+ """
618
+ Refresh the dialog's document reference to the currently active document.
619
+ This allows reusing the same dialog on different images.
620
+ """
621
+ try:
622
+ main = self.parent()
623
+ if main and hasattr(main, "_active_doc"):
624
+ new_doc = main._active_doc()
625
+ if new_doc is not None and new_doc is not self._doc:
626
+ self._doc = new_doc
627
+ # Reset L channel and refresh preview for new document
628
+ self._L_original = None
629
+ self._last_preview = None
630
+ except Exception:
631
+ pass
611
632
 
612
633
 
613
634
  def _schedule_mask_refresh(self, _value):
@@ -619,6 +640,6 @@ class WaveScaleHDRDialogPro(QDialog):
619
640
  hdr_mask = _mask_from_L(self._L_original, gamma=gamma)
620
641
  mask_comb = self._combine_with_doc_mask(hdr_mask)
621
642
  self.mask_win.setWindowTitle(
622
- "HDR Mask (L × Active Mask)" if self._get_doc_active_mask_2d() is not None else "HDR Mask (L-based)"
643
+ self.tr("HDR Mask (L × Active Mask)") if self._get_doc_active_mask_2d() is not None else self.tr("HDR Mask (L-based)")
623
644
  )
624
645
  self.mask_win.update_mask(mask_comb)
@@ -6,7 +6,7 @@ from .wavescale_hdr import WaveScaleHDRDialogPro
6
6
  class WaveScaleHDRPresetDialog(QDialog):
7
7
  def __init__(self, parent=None, initial: dict | None = None):
8
8
  super().__init__(parent)
9
- self.setWindowTitle("WaveScale HDR — Preset")
9
+ self.setWindowTitle(self.tr("WaveScale HDR — Preset"))
10
10
  p = dict(initial or {})
11
11
  f = QFormLayout(self)
12
12
 
@@ -26,9 +26,9 @@ class WaveScaleHDRPresetDialog(QDialog):
26
26
  self.gamma.setDecimals(2)
27
27
  self.gamma.setValue(float(p.get("mask_gamma", 5.0)))
28
28
 
29
- f.addRow("Number of Scales:", self.n_scales)
30
- f.addRow("Coarse Compression:", self.comp)
31
- f.addRow("Mask Gamma:", self.gamma)
29
+ f.addRow(self.tr("Number of Scales:"), self.n_scales)
30
+ f.addRow(self.tr("Coarse Compression:"), self.comp)
31
+ f.addRow(self.tr("Mask Gamma:"), self.gamma)
32
32
 
33
33
  btns = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, parent=self)
34
34
  btns.accepted.connect(self.accept); btns.rejected.connect(self.reject)
@@ -73,7 +73,8 @@ def run_wavescale_hdr_via_preset(main, preset: dict | None = None, target_doc=No
73
73
 
74
74
  main, doc, _dm = normalize_headless_main(main, target_doc)
75
75
  if doc is None or getattr(doc, "image", None) is None:
76
- QMessageBox.warning(main or None, "...", "Load an image first.")
76
+ from PyQt6.QtCore import QCoreApplication
77
+ QMessageBox.warning(main or None, "...", QCoreApplication.translate("WaveScaleHDRPresetDialog", "Load an image first."))
77
78
  return
78
79
 
79
80
  # Build headless dialog and apply preset
@@ -149,13 +149,13 @@ class DSEWorker(QObject):
149
149
 
150
150
  def run(self):
151
151
  try:
152
- self.progress_update.emit("Analyzing dark structure…", 20)
152
+ self.progress_update.emit(self.tr("Analyzing dark structure…"), 20)
153
153
  out, mask = compute_wavescale_dse(
154
154
  self.image, self.n_scales, self.boost, self.gamma,
155
155
  self.iterations, self.base_kernel,
156
156
  external_mask=self.external_mask # ← NEW
157
157
  )
158
- self.progress_update.emit("Finalizing…", 95)
158
+ self.progress_update.emit(self.tr("Finalizing…"), 95)
159
159
  self.finished.emit(out, mask)
160
160
  except Exception as e:
161
161
  print("WaveScale DSE error:", e)
@@ -167,7 +167,7 @@ class DSEWorker(QObject):
167
167
  class _MaskWindow(QDialog):
168
168
  def __init__(self, parent=None):
169
169
  super().__init__(parent)
170
- self.setWindowTitle("Dark Mask")
170
+ self.setWindowTitle(self.tr("Dark Mask"))
171
171
  self.setMinimumSize(300, 300)
172
172
  self.resize(400, 400)
173
173
  v = QVBoxLayout(self)
@@ -194,13 +194,16 @@ class _MaskWindow(QDialog):
194
194
  class WaveScaleDarkEnhancerDialogPro(QDialog):
195
195
  def __init__(self, parent, doc, icon_path: str | None = None):
196
196
  super().__init__(parent)
197
- self.setWindowTitle("WaveScale Dark Enhancer")
197
+ self.setWindowTitle(self.tr("WaveScale Dark Enhancer"))
198
198
  if icon_path:
199
199
  try: self.setWindowIcon(QIcon(icon_path))
200
200
  except Exception as e:
201
201
  import logging
202
202
  logging.debug(f"Exception suppressed: {type(e).__name__}: {e}")
203
203
  self.resize(980, 700)
204
+ self.setWindowFlag(Qt.WindowType.Window, True)
205
+ self.setWindowModality(Qt.WindowModality.NonModal)
206
+ self.setModal(False)
204
207
 
205
208
  self._doc = doc
206
209
  base = getattr(doc, "image", None)
@@ -243,7 +246,7 @@ class WaveScaleDarkEnhancerDialogPro(QDialog):
243
246
  self.zoom_max = 5.0
244
247
 
245
248
  # controls
246
- self.grp = QGroupBox("Dark Enhancer Controls")
249
+ self.grp = QGroupBox(self.tr("Dark Enhancer Controls"))
247
250
  form = QFormLayout(self.grp)
248
251
 
249
252
  self.s_scales = QSlider(Qt.Orientation.Horizontal); self.s_scales.setRange(2, 10); self.s_scales.setValue(6)
@@ -251,40 +254,40 @@ class WaveScaleDarkEnhancerDialogPro(QDialog):
251
254
  self.s_gamma = QSlider(Qt.Orientation.Horizontal); self.s_gamma.setRange(10, 1000); self.s_gamma.setValue(100) # 0.10..10.00
252
255
  self.s_iters = QSlider(Qt.Orientation.Horizontal); self.s_iters.setRange(1, 10); self.s_iters.setValue(2)
253
256
 
254
- form.addRow("Number of Scales:", self.s_scales)
255
- form.addRow("Boost Factor:", self.s_boost)
256
- form.addRow("Mask Gamma:", self.s_gamma)
257
- form.addRow("Iterations:", self.s_iters)
257
+ form.addRow(self.tr("Number of Scales:"), self.s_scales)
258
+ form.addRow(self.tr("Boost Factor:"), self.s_boost)
259
+ form.addRow(self.tr("Mask Gamma:"), self.s_gamma)
260
+ form.addRow(self.tr("Iterations:"), self.s_iters)
258
261
 
259
262
  row = QHBoxLayout()
260
- self.btn_preview = QPushButton("Preview")
261
- self.btn_toggle = QPushButton("Show Original"); self.btn_toggle.setCheckable(True)
263
+ self.btn_preview = QPushButton(self.tr("Preview"))
264
+ self.btn_toggle = QPushButton(self.tr("Show Original")); self.btn_toggle.setCheckable(True)
262
265
  row.addWidget(self.btn_preview); row.addWidget(self.btn_toggle)
263
266
  form.addRow(row)
264
267
 
265
268
  # progress
266
- self.prog_grp = QGroupBox("Progress")
269
+ self.prog_grp = QGroupBox(self.tr("Progress"))
267
270
  vprog = QVBoxLayout(self.prog_grp)
268
- self.lbl_step = QLabel("Idle")
271
+ self.lbl_step = QLabel(self.tr("Idle"))
269
272
  self.bar = QProgressBar(); self.bar.setRange(0, 100); self.bar.setValue(0)
270
273
  vprog.addWidget(self.lbl_step); vprog.addWidget(self.bar)
271
274
 
272
275
  # bottom
273
276
  bot = QHBoxLayout()
274
- self.btn_apply = QPushButton("Apply to Document"); self.btn_apply.setEnabled(False)
275
- self.btn_reset = QPushButton("Reset")
276
- self.btn_close = QPushButton("Close")
277
+ self.btn_apply = QPushButton(self.tr("Apply to Document")); self.btn_apply.setEnabled(False)
278
+ self.btn_reset = QPushButton(self.tr("Reset"))
279
+ self.btn_close = QPushButton(self.tr("Close"))
277
280
  bot.addStretch(1); bot.addWidget(self.btn_apply); bot.addWidget(self.btn_reset); bot.addWidget(self.btn_close)
278
281
 
279
282
  # layout
280
283
  main = QVBoxLayout(self)
281
284
  main.addWidget(self.scroll)
282
285
 
283
- zoom_box = QGroupBox("Zoom Controls")
286
+ zoom_box = QGroupBox(self.tr("Zoom Controls"))
284
287
  zr = QHBoxLayout(zoom_box)
285
- self.btn_zin = QPushButton("Zoom In")
286
- self.btn_zout = QPushButton("Zoom Out")
287
- self.btn_fit = QPushButton("Fit to Preview")
288
+ self.btn_zin = QPushButton(self.tr("Zoom In"))
289
+ self.btn_zout = QPushButton(self.tr("Zoom Out"))
290
+ self.btn_fit = QPushButton(self.tr("Fit to Preview"))
288
291
  zr.addWidget(self.btn_zin); zr.addWidget(self.btn_zout); zr.addWidget(self.btn_fit)
289
292
  main.addWidget(zoom_box)
290
293
 
@@ -408,10 +411,10 @@ class WaveScaleDarkEnhancerDialogPro(QDialog):
408
411
  # --- toggle ---
409
412
  def _toggle(self):
410
413
  if self.btn_toggle.isChecked():
411
- self.btn_toggle.setText("Show Preview")
414
+ self.btn_toggle.setText(self.tr("Show Preview"))
412
415
  self._set_pix(self.original)
413
416
  else:
414
- self.btn_toggle.setText("Show Original")
417
+ self.btn_toggle.setText(self.tr("Show Original"))
415
418
  self._set_pix(self.preview)
416
419
 
417
420
  # --- reset ---
@@ -422,9 +425,9 @@ class WaveScaleDarkEnhancerDialogPro(QDialog):
422
425
  self.s_iters.setValue(2)
423
426
  self.preview = self.original.copy()
424
427
  self._set_pix(self.preview)
425
- self.lbl_step.setText("Idle"); self.bar.setValue(0)
428
+ self.lbl_step.setText(self.tr("Idle")); self.bar.setValue(0)
426
429
  self.btn_apply.setEnabled(False)
427
- self.btn_toggle.setChecked(False); self.btn_toggle.setText("Show Original")
430
+ self.btn_toggle.setChecked(False); self.btn_toggle.setText(self.tr("Show Original"))
428
431
  self._update_mask_only()
429
432
 
430
433
  # --- zoom + Ctrl+Wheel ---
@@ -467,7 +470,7 @@ class WaveScaleDarkEnhancerDialogPro(QDialog):
467
470
  self.base_kernel, mgamma)
468
471
  mask_comb = self._combine_with_doc_mask(algo_mask)
469
472
  self.mask_win.setWindowTitle(
470
- "Dark Mask (Algo × Active Mask)" if self._get_doc_active_mask_2d() is not None else "Dark Mask"
473
+ self.tr("Dark Mask (Algo × Active Mask)") if self._get_doc_active_mask_2d() is not None else self.tr("Dark Mask")
471
474
  )
472
475
  self.mask_win.set_mask(mask_comb)
473
476
  # --- threaded preview ---
@@ -498,7 +501,7 @@ class WaveScaleDarkEnhancerDialogPro(QDialog):
498
501
  def _on_finished(self, out: np.ndarray, mask: np.ndarray):
499
502
  self.btn_preview.setEnabled(True)
500
503
  if out is None:
501
- QMessageBox.critical(self, "WaveScale Dark Enhancer", "Processing failed.")
504
+ QMessageBox.critical(self, self.tr("WaveScale Dark Enhancer"), self.tr("Processing failed."))
502
505
  return
503
506
 
504
507
  # Respect the document mask
@@ -520,8 +523,8 @@ class WaveScaleDarkEnhancerDialogPro(QDialog):
520
523
  self._set_pix(self.preview)
521
524
  self.mask_win.set_mask(mask)
522
525
  self.btn_apply.setEnabled(True)
523
- self.btn_toggle.setChecked(False); self.btn_toggle.setText("Show Original")
524
- self.lbl_step.setText("Preview ready"); self.bar.setValue(100)
526
+ self.btn_toggle.setChecked(False); self.btn_toggle.setText(self.tr("Show Original"))
527
+ self.lbl_step.setText(self.tr("Preview ready")); self.bar.setValue(100)
525
528
 
526
529
  # --- apply back to doc ---
527
530
  # --- apply back to doc ---
@@ -541,7 +544,7 @@ class WaveScaleDarkEnhancerDialogPro(QDialog):
541
544
  else:
542
545
  self._doc.image = out
543
546
  except Exception as e:
544
- QMessageBox.critical(self, "WaveScale Dark Enhancer", f"Failed to write to document:\n{e}")
547
+ QMessageBox.critical(self, self.tr("WaveScale Dark Enhancer"), self.tr("Failed to write to document:\n{0}").format(e))
545
548
  return
546
549
 
547
550
  # ── Build preset from current sliders ─────────────────────────
@@ -581,7 +584,26 @@ class WaveScaleDarkEnhancerDialogPro(QDialog):
581
584
  # Never let replay wiring break the apply
582
585
  pass
583
586
 
584
- self.accept()
587
+ # Dialog stays open so user can apply to other images
588
+ # Refresh document reference for next operation
589
+ self._refresh_document_from_active()
590
+
591
+ def _refresh_document_from_active(self):
592
+ """
593
+ Refresh the dialog's document reference to the currently active document.
594
+ This allows reusing the same dialog on different images.
595
+ """
596
+ try:
597
+ main = self.parent()
598
+ if main and hasattr(main, "_active_doc"):
599
+ new_doc = main._active_doc()
600
+ if new_doc is not None and new_doc is not self._doc:
601
+ self._doc = new_doc
602
+ # Reset state and refresh for new document
603
+ self._L_original = None
604
+ self._last_preview = None
605
+ except Exception:
606
+ pass
585
607
 
586
608
 
587
609
  # ─────────────────────────────────────────────────────────────────────────────
@@ -615,7 +637,8 @@ def install_wavescale_dark_enhancer(main_window: QMainWindow,
615
637
  if docman and hasattr(docman, "current_document"):
616
638
  doc = docman.current_document()
617
639
  if doc is None or getattr(doc, "image", None) is None:
618
- QMessageBox.warning(main_window, "WaveScale Dark Enhancer", "No active image.")
640
+ from PyQt6.QtCore import QCoreApplication
641
+ QMessageBox.warning(main_window, QCoreApplication.translate("WaveScaleDarkEnhancerDialogPro", "WaveScale Dark Enhancer"), QCoreApplication.translate("WaveScaleDarkEnhancerDialogPro", "No active image."))
619
642
  return
620
643
  dlg = WaveScaleDarkEnhancerDialogPro(main_window, doc, icon_path=dse_icon_path)
621
644
  dlg.exec()
@@ -9,7 +9,7 @@ from .wavescalede import compute_wavescale_dse
9
9
  class WaveScaleDSEPresetDialog(QDialog):
10
10
  def __init__(self, parent=None, initial: dict | None = None):
11
11
  super().__init__(parent)
12
- self.setWindowTitle("WaveScale Dark Enhancer — Preset")
12
+ self.setWindowTitle(self.tr("WaveScale Dark Enhancer — Preset"))
13
13
  p = dict(initial or {})
14
14
  f = QFormLayout(self)
15
15
 
@@ -33,10 +33,10 @@ class WaveScaleDSEPresetDialog(QDialog):
33
33
  self.iters.setRange(1, 10)
34
34
  self.iters.setValue(int(p.get("iterations", 2)))
35
35
 
36
- f.addRow("Number of Scales:", self.n_scales)
37
- f.addRow("Boost Factor:", self.boost)
38
- f.addRow("Mask Gamma:", self.gamma)
39
- f.addRow("Iterations:", self.iters)
36
+ f.addRow(self.tr("Number of Scales:"), self.n_scales)
37
+ f.addRow(self.tr("Boost Factor:"), self.boost)
38
+ f.addRow(self.tr("Mask Gamma:"), self.gamma)
39
+ f.addRow(self.tr("Iterations:"), self.iters)
40
40
 
41
41
  btns = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, parent=self)
42
42
  btns.accepted.connect(self.accept); btns.rejected.connect(self.reject)
@@ -101,7 +101,8 @@ def run_wavescalede_via_preset(main, preset: dict | None = None, target_doc=None
101
101
 
102
102
  main, doc, _dm = normalize_headless_main(main, target_doc)
103
103
  if doc is None or getattr(doc, "image", None) is None:
104
- QMessageBox.warning(main or None, "...", "Load an image first.")
104
+ from PyQt6.QtCore import QCoreApplication
105
+ QMessageBox.warning(main or None, QCoreApplication.translate("WaveScaleDSEPresetDialog", "WaveScale Dark Enhancer"), QCoreApplication.translate("WaveScaleDSEPresetDialog", "Load an image first."))
105
106
  return
106
107
 
107
108
  # pull & normalize image like the dialog
@@ -174,7 +175,8 @@ def run_wavescalede_via_preset(main, preset: dict | None = None, target_doc=None
174
175
  else:
175
176
  doc.image = result
176
177
  except Exception as e:
177
- QMessageBox.critical(main, "WaveScale Dark Enhancer", f"Failed to write to document:\n{e}")
178
+ from PyQt6.QtCore import QCoreApplication
179
+ QMessageBox.critical(main, QCoreApplication.translate("WaveScaleDSEPresetDialog", "WaveScale Dark Enhancer"), QCoreApplication.translate("WaveScaleDSEPresetDialog", "Failed to write to document:\n{0}").format(e))
178
180
  return
179
181
 
180
182