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
@@ -176,7 +176,7 @@ class CurveEditor(QGraphicsView):
176
176
  def _on_symmetry_pick(self, u: float, _v: float):
177
177
  # editor already drew the yellow line; now redistribute handles
178
178
  self.redistributeHandlesByPivot(u)
179
- self._set_status(f"Inflection @ K={u:.3f}")
179
+ self._set_status(self.tr("Inflection @ K={0:.3f}").format(u))
180
180
  self._quick_preview()
181
181
 
182
182
  def initGrid(self):
@@ -1015,8 +1015,17 @@ class CurvesDialogPro(QDialog):
1015
1015
  """
1016
1016
  def __init__(self, parent, document):
1017
1017
  super().__init__(parent)
1018
- self.setWindowTitle("Curves Editor")
1018
+ self.setWindowTitle(self.tr("Curves Editor"))
1019
+ self.setWindowFlag(Qt.WindowType.Window, True)
1020
+ self.setWindowModality(Qt.WindowModality.NonModal)
1021
+ self.setModal(False)
1022
+ self._main = parent
1019
1023
  self.doc = document
1024
+
1025
+ # Connect to active document change signal
1026
+ if hasattr(self._main, "currentDocumentChanged"):
1027
+ self._main.currentDocumentChanged.connect(self._on_active_doc_changed)
1028
+
1020
1029
  self._preview_img = None # downsampled float01
1021
1030
  self._full_img = None # full-res float01
1022
1031
  self._pix = None
@@ -1085,12 +1094,12 @@ class CurvesDialogPro(QDialog):
1085
1094
 
1086
1095
  rowp = QHBoxLayout()
1087
1096
  self.btn_presets = QToolButton(self)
1088
- self.btn_presets.setText("Presets")
1097
+ self.btn_presets.setText(self.tr("Presets"))
1089
1098
  self.btn_presets.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
1090
1099
  rowp.addWidget(self.btn_presets)
1091
1100
 
1092
1101
  self.btn_save_preset = QToolButton(self)
1093
- self.btn_save_preset.setText("Save as Preset")
1102
+ self.btn_save_preset.setText(self.tr("Save as Preset..."))
1094
1103
  self.btn_save_preset.clicked.connect(self._save_current_as_preset)
1095
1104
  rowp.addWidget(self.btn_save_preset)
1096
1105
  left.addLayout(rowp)
@@ -1103,10 +1112,10 @@ class CurvesDialogPro(QDialog):
1103
1112
  # buttons
1104
1113
  rowb = QHBoxLayout()
1105
1114
  self.btn_preview = QToolButton(self)
1106
- self.btn_preview.setText("Toggle Preview")
1115
+ self.btn_preview.setText(self.tr("Toggle Preview"))
1107
1116
  self.btn_preview.setCheckable(True) # ⬅️ toggle
1108
- self.btn_apply = QPushButton("Apply to Document")
1109
- self.btn_reset = QToolButton(); self.btn_reset.setText("Reset")
1117
+ self.btn_apply = QPushButton(self.tr("Apply to Document"))
1118
+ self.btn_reset = QToolButton(); self.btn_reset.setText(self.tr("Reset"))
1110
1119
  rowb.addWidget(self.btn_preview); rowb.addWidget(self.btn_apply); rowb.addWidget(self.btn_reset)
1111
1120
  left.addLayout(rowb)
1112
1121
  left.addStretch(1)
@@ -1117,9 +1126,9 @@ class CurvesDialogPro(QDialog):
1117
1126
  zoombar = QHBoxLayout()
1118
1127
  zoombar.addStretch(1)
1119
1128
 
1120
- self.btn_zoom_out = themed_toolbtn("zoom-out", "Zoom Out")
1121
- self.btn_zoom_in = themed_toolbtn("zoom-in", "Zoom In")
1122
- self.btn_zoom_fit = themed_toolbtn("zoom-fit-best", "Fit to Preview")
1129
+ self.btn_zoom_out = themed_toolbtn("zoom-out", self.tr("Zoom Out"))
1130
+ self.btn_zoom_in = themed_toolbtn("zoom-in", self.tr("Zoom In"))
1131
+ self.btn_zoom_fit = themed_toolbtn("zoom-fit-best", self.tr("Fit to Preview"))
1123
1132
 
1124
1133
  zoombar.addWidget(self.btn_zoom_out)
1125
1134
  zoombar.addWidget(self.btn_zoom_in)
@@ -1601,7 +1610,7 @@ class CurvesDialogPro(QDialog):
1601
1610
 
1602
1611
  def _on_symmetry_pick(self, u: float, _v: float):
1603
1612
  self.editor.redistributeHandlesByPivot(u)
1604
- self._set_status(f"Inflection @ K={u:.3f}")
1613
+ self._set_status(self.tr("Inflection @ K={0:.3f}").format(u))
1605
1614
  self._quick_preview()
1606
1615
 
1607
1616
  def _fit_once(self):
@@ -1641,7 +1650,7 @@ class CurvesDialogPro(QDialog):
1641
1650
  v = float(img[iy, ix] if img.ndim == 2 else img[iy, ix, 0])
1642
1651
  v = 0.0 if not np.isfinite(v) else float(np.clip(v, 0.0, 1.0))
1643
1652
  self.editor.updateValueLines(v, 0.0, 0.0, grayscale=True)
1644
- self._set_status(f"Cursor ({ix}, {iy}) K: {v:.3f}")
1653
+ self._set_status(self.tr("Cursor ({0}, {1}) K: {2:.3f}").format(ix, iy, v))
1645
1654
  else:
1646
1655
  C = img.shape[2]
1647
1656
  if C >= 3:
@@ -1656,7 +1665,7 @@ class CurvesDialogPro(QDialog):
1656
1665
  g = 0.0 if not np.isfinite(g) else float(np.clip(g, 0.0, 1.0))
1657
1666
  b = 0.0 if not np.isfinite(b) else float(np.clip(b, 0.0, 1.0))
1658
1667
  self.editor.updateValueLines(r, g, b, grayscale=False)
1659
- self._set_status(f"Cursor ({ix}, {iy}) R: {r:.3f} G: {g:.3f} B: {b:.3f}")
1668
+ self._set_status(self.tr("Cursor ({0}, {1}) R: {2:.3f} G: {3:.3f} B: {4:.3f}").format(ix, iy, r, g, b))
1660
1669
 
1661
1670
 
1662
1671
  # 1) Put this helper inside CurvesDialogPro (near other helpers)
@@ -1728,16 +1737,16 @@ class CurvesDialogPro(QDialog):
1728
1737
 
1729
1738
  def _save_current_as_preset(self):
1730
1739
  # get name
1731
- name, ok = QInputDialog.getText(self, "Save Curves Preset", "Preset name:")
1740
+ name, ok = QInputDialog.getText(self, self.tr("Save Curves Preset"), self.tr("Preset name:"))
1732
1741
  if not ok or not name.strip():
1733
1742
  return
1734
1743
  pts_norm = self._collect_points_norm_from_editor()
1735
1744
  mode = self._current_mode()
1736
1745
  if save_custom_preset(name.strip(), mode, pts_norm):
1737
- self._set_status(f"Saved preset “{name.strip()}”.")
1746
+ self._set_status(self.tr("Saved preset “{0}”.").format(name.strip()))
1738
1747
  self._rebuild_presets_menu()
1739
1748
  else:
1740
- QMessageBox.warning(self, "Save failed", "Could not save preset.")
1749
+ QMessageBox.warning(self, self.tr("Save failed"), self.tr("Could not save preset."))
1741
1750
 
1742
1751
  def _rebuild_presets_menu(self):
1743
1752
  m = QMenu(self)
@@ -1754,7 +1763,7 @@ class CurvesDialogPro(QDialog):
1754
1763
  ("Flatten", {"mode": "K (Brightness)", "shape": "flatten", "amount": 1.0}),
1755
1764
  ]
1756
1765
  if builtins:
1757
- mb = m.addMenu("Built-ins")
1766
+ mb = m.addMenu(self.tr("Built-ins"))
1758
1767
  for label, preset in builtins:
1759
1768
  act = mb.addAction(label)
1760
1769
  act.triggered.connect(lambda _=False, p=preset: self._apply_preset_dict(p))
@@ -1762,15 +1771,15 @@ class CurvesDialogPro(QDialog):
1762
1771
  # Custom presets (from QSettings)
1763
1772
  customs = list_custom_presets()
1764
1773
  if customs:
1765
- mc = m.addMenu("Custom")
1774
+ mc = m.addMenu(self.tr("Custom"))
1766
1775
  for p in sorted(customs, key=lambda d: d.get("name","").lower()):
1767
1776
  act = mc.addAction(p.get("name","(unnamed)"))
1768
1777
  act.triggered.connect(lambda _=False, pp=p: self._apply_preset_dict(pp))
1769
1778
  mc.addSeparator()
1770
- act_manage = mc.addAction("Manage…")
1779
+ act_manage = mc.addAction(self.tr("Manage…"))
1771
1780
  act_manage.triggered.connect(self._open_manage_customs_dialog) # optional (see below)
1772
1781
  else:
1773
- m.addAction("(No custom presets yet)").setEnabled(False)
1782
+ m.addAction(self.tr("(No custom presets yet)")).setEnabled(False)
1774
1783
 
1775
1784
  self.btn_presets.setMenu(m)
1776
1785
 
@@ -1778,21 +1787,30 @@ class CurvesDialogPro(QDialog):
1778
1787
  # optional: quick-and-dirty remover
1779
1788
  customs = list_custom_presets()
1780
1789
  if not customs:
1781
- QMessageBox.information(self, "Manage Presets", "No custom presets.")
1790
+ QMessageBox.information(self, self.tr("Manage Presets"), self.tr("No custom presets."))
1782
1791
  return
1783
1792
  names = [p.get("name","") for p in customs]
1784
- name, ok = QInputDialog.getItem(self, "Delete Preset", "Choose preset to delete:", names, 0, False)
1793
+ name, ok = QInputDialog.getItem(self, self.tr("Delete Preset"), self.tr("Choose preset to delete:"), names, 0, False)
1785
1794
  if ok and name:
1786
1795
  from setiastro.saspro.curves_preset import delete_custom_preset
1787
1796
  if delete_custom_preset(name):
1788
1797
  self._rebuild_presets_menu()
1789
1798
 
1790
1799
 
1800
+ # ----- active document change -----
1801
+ def _on_active_doc_changed(self, doc):
1802
+ """Called when user clicks a different image window."""
1803
+ if doc is None or getattr(doc, "image", None) is None:
1804
+ return
1805
+ self.doc = doc
1806
+ self._load_from_doc()
1807
+ QTimer.singleShot(0, self._fit_after_load)
1808
+
1791
1809
  # ----- data -----
1792
1810
  def _load_from_doc(self):
1793
1811
  img = self.doc.image
1794
1812
  if img is None:
1795
- QMessageBox.information(self, "No image", "Open an image first.")
1813
+ QMessageBox.information(self, self.tr("No image"), self.tr("Open an image first."))
1796
1814
  return
1797
1815
  arr = np.asarray(img)
1798
1816
  # normalize to float01 gently
@@ -1837,7 +1855,7 @@ class CurvesDialogPro(QDialog):
1837
1855
  # Pick which buffer to show (both are downsampled)
1838
1856
  img = self._preview_proc if (self._show_proc and self._preview_proc is not None) else self._preview_orig
1839
1857
  self._update_preview_pix(img)
1840
- self._set_status("Preview ON" if self._show_proc else "Preview OFF")
1858
+ self._set_status(self.tr("Preview ON") if self._show_proc else self.tr("Preview OFF"))
1841
1859
 
1842
1860
 
1843
1861
  # ----- quick (in-UI) preview on downsample -----
@@ -1858,15 +1876,15 @@ class CurvesDialogPro(QDialog):
1858
1876
  rgb = self._clip_counts_rgb_from_thresholds(bt, wt)
1859
1877
  def _fmt(pair):
1860
1878
  cnt_b, cnt_w, fb, fw = pair
1861
- return f"Bk {cnt_b:,} ({fb*100:.2f}%) Wt {cnt_w:,} ({fw*100:.2f}%)"
1879
+ return self.tr("Bk {0:,} ({1:.2f}%) Wt {2:,} ({3:.2f}%)").format(cnt_b, fb*100, cnt_w, fw*100)
1862
1880
  self._set_status(
1863
- f"Clipping — R: {_fmt(rgb['r'])} G: {_fmt(rgb['g'])} B: {_fmt(rgb['b'])}"
1881
+ self.tr("Clipping — R: {0} G: {1} B: {2}").format(_fmt(rgb['r']), _fmt(rgb['g']), _fmt(rgb['b']))
1864
1882
  )
1865
1883
  else:
1866
1884
  # Grayscale/mono → K summary (unchanged behavior)
1867
1885
  below, above, f_below, f_above = self._clip_counts_from_thresholds(bt, wt)
1868
1886
  self._set_status(
1869
- f"Clipping — Bk {below:,} ({f_below*100:.2f}%) Wt {above:,} ({f_above*100:.2f}%)"
1887
+ self.tr("Clipping — Bk {0:,} ({1:.2f}%) Wt {2:,} ({3:.2f}%)").format(below, f_below*100, above, f_above*100)
1870
1888
  )
1871
1889
  except Exception:
1872
1890
  pass
@@ -1887,7 +1905,7 @@ class CurvesDialogPro(QDialog):
1887
1905
  # NOTE: do not push full-res into the label
1888
1906
  out_masked = self._blend_with_mask(out01)
1889
1907
  self._last_preview = out_masked # cache for Apply
1890
- self._set_status("Full-res ready (not shown).")
1908
+ self._set_status(self.tr("Full-res ready (not shown)."))
1891
1909
 
1892
1910
  def _clip_counts_from_thresholds(self, black_t: float | None, white_t: float | None):
1893
1911
  """
@@ -2066,12 +2084,12 @@ class CurvesDialogPro(QDialog):
2066
2084
 
2067
2085
  self._refresh_overlays()
2068
2086
  self._quick_preview()
2069
- self._set_status("Applied. Image reloaded. All curves reset — keep tweaking.")
2087
+ self._set_status(self.tr("Applied. Image reloaded. All curves reset — keep tweaking."))
2070
2088
 
2071
2089
 
2072
2090
 
2073
2091
  except Exception as e:
2074
- QMessageBox.critical(self, "Apply failed", str(e))
2092
+ QMessageBox.critical(self, self.tr("Apply failed"), str(e))
2075
2093
 
2076
2094
 
2077
2095
  # ----- helpers -----
@@ -2288,7 +2306,7 @@ class CurvesDialogPro(QDialog):
2288
2306
  break
2289
2307
 
2290
2308
  self.editor.addControlPoint(x, y)
2291
- self._set_status(f"Added point at x={v:.3f}")
2309
+ self._set_status(self.tr("Added point at x={0:.3f}").format(v))
2292
2310
  ev.accept()
2293
2311
  return True
2294
2312
 
@@ -2304,7 +2322,7 @@ class CurvesDialogPro(QDialog):
2304
2322
  # 3) refresh overlays & preview
2305
2323
  self._refresh_overlays()
2306
2324
  self._quick_preview()
2307
- self._set_status("All curves reset.")
2325
+ self._set_status(self.tr("All curves reset."))
2308
2326
 
2309
2327
  def _find_main_window(self):
2310
2328
  p = self.parent()
@@ -2351,7 +2369,7 @@ class CurvesDialogPro(QDialog):
2351
2369
 
2352
2370
  # 4) status: don’t assume shape exists
2353
2371
  shape_tag = f"[{shape}]" if shape else "[custom]"
2354
- self._set_status(f"Preset: {preset.get('name', '(built-in)')} {shape_tag}")
2372
+ self._set_status(self.tr("Preset: {0} {1}").format(preset.get('name', self.tr('(built-in)')), shape_tag))
2355
2373
 
2356
2374
 
2357
2375
  def apply_curves_ops(doc, op: dict):
@@ -401,7 +401,10 @@ class DebayerDialog(QDialog):
401
401
  """
402
402
  def __init__(self, parent, doc_manager, active_doc):
403
403
  super().__init__(parent)
404
- self.setWindowTitle("Debayer")
404
+ self.setWindowTitle(self.tr("Debayer"))
405
+ self.setWindowFlag(Qt.WindowType.Window, True)
406
+ self.setWindowModality(Qt.WindowModality.NonModal)
407
+ self.setModal(False)
405
408
  self.dm = doc_manager
406
409
  self.doc = active_doc
407
410
  self.worker: Optional[_DebayerWorker] = None
@@ -435,7 +438,7 @@ class DebayerDialog(QDialog):
435
438
  detected = _detect_bayer_from_header(active_doc)
436
439
  self._detected_pattern = detected # store for later
437
440
 
438
- gb = QGroupBox("Bayer pattern", self)
441
+ gb = QGroupBox(self.tr("Bayer pattern"), self)
439
442
  h = QHBoxLayout(gb)
440
443
  self.combo_pattern = QComboBox(self)
441
444
  self.combo_pattern.addItems([
@@ -455,7 +458,7 @@ class DebayerDialog(QDialog):
455
458
  norm = _normalize_bayer_token(self._detected_pattern or "")
456
459
  self.lbl_detect.setText(f"Detected: {norm or '(unknown)'}")
457
460
 
458
- self.method_group = QGroupBox("Method", self)
461
+ self.method_group = QGroupBox(self.tr("Method"), self)
459
462
  hm = QHBoxLayout(self.method_group)
460
463
  self.combo_method = QComboBox(self)
461
464
 
@@ -1,4 +1,4 @@
1
- # pro/doc_manager.py
1
+ # saspro/doc_manager.py
2
2
  from __future__ import annotations
3
3
  from PyQt6.QtCore import QObject, pyqtSignal, Qt, QTimer
4
4
  from PyQt6.QtWidgets import QApplication, QMessageBox
@@ -2472,23 +2472,29 @@ class DocManager(QObject):
2472
2472
  If a Preview tab is selected on the active ImageSubWindow, return a cached
2473
2473
  _RoiViewDocument so tools and the Preview tab share the same instance.
2474
2474
  Otherwise return the real ImageDocument.
2475
+
2476
+ IMPORTANT: Always check the currently active MDI subwindow first,
2477
+ as that's what the user expects to be the "active" document.
2475
2478
  """
2476
- # Prefer cached (if set and still valid)
2477
- if self._active_doc is not None and self._active_doc in self._docs:
2478
- base_doc = self._active_doc
2479
- else:
2480
- base_doc = None
2481
- try:
2482
- if self._mdi is not None:
2483
- sw = self._mdi.activeSubWindow()
2484
- if sw is not None:
2485
- w = sw.widget()
2486
- base_doc = getattr(w, "document", None) or getattr(sw, "document", None)
2487
- if base_doc is not None:
2488
- self._active_doc = base_doc
2489
- except Exception:
2490
- pass
2491
- if base_doc is None:
2479
+ base_doc = None
2480
+
2481
+ # ALWAYS check the MDI active subwindow first - this is the source of truth
2482
+ try:
2483
+ if self._mdi is not None:
2484
+ sw = self._mdi.activeSubWindow()
2485
+ if sw is not None:
2486
+ w = sw.widget()
2487
+ base_doc = getattr(w, "document", None) or getattr(sw, "document", None)
2488
+ if base_doc is not None:
2489
+ self._active_doc = base_doc
2490
+ except Exception:
2491
+ pass
2492
+
2493
+ # Fallback to cached value only if MDI lookup failed
2494
+ if base_doc is None:
2495
+ if self._active_doc is not None and self._active_doc in self._docs:
2496
+ base_doc = self._active_doc
2497
+ else:
2492
2498
  base_doc = self._docs[-1] if self._docs else None
2493
2499
 
2494
2500
  # Non-image docs just pass through
@@ -2514,9 +2520,33 @@ class DocManager(QObject):
2514
2520
 
2515
2521
 
2516
2522
 
2517
- def update_active_document(self, updated_image, metadata=None, step_name: str = "Edit"):
2523
+ def update_active_document(
2524
+ self,
2525
+ updated_image,
2526
+ metadata=None,
2527
+ step_name: str = "Edit",
2528
+ doc=None, # 👈 NEW optional parameter
2529
+ ):
2530
+
2531
+ # Prefer explicit doc if given; otherwise fall back to "active"
2532
+ view_doc = doc or self.get_active_document()
2533
+
2534
+ # DEBUG: Trace why LinearFit might fail
2535
+ # print(f"[DocManager] update_active_document target: {view_doc}, type: {type(view_doc).__name__}")
2536
+
2537
+ # NEW: Unwrap proxy objects (_DocProxy / LiveViewDocument)
2538
+ tname = type(view_doc).__name__
2539
+ if "LiveViewDocument" in tname:
2540
+ try:
2541
+ view_doc = view_doc._current()
2542
+ except Exception:
2543
+ pass
2544
+ elif "_DocProxy" in tname:
2545
+ try:
2546
+ view_doc = view_doc._target()
2547
+ except Exception:
2548
+ pass
2518
2549
 
2519
- view_doc = self.get_active_document()
2520
2550
  if view_doc is None:
2521
2551
  raise RuntimeError("No active document")
2522
2552
 
@@ -253,7 +253,7 @@ class ClickableEllipseItem(QGraphicsEllipseItem):
253
253
  class ReferenceOverlayDialog(QDialog):
254
254
  def __init__(self, plane: np.ndarray, positions: List[Tuple], target_median: float, parent=None):
255
255
  super().__init__(parent)
256
- self.setWindowTitle("Reference Frame: Stars Overlay")
256
+ self.setWindowTitle(self.tr("Reference Frame: Stars Overlay"))
257
257
  self.plane = plane.astype(np.float32)
258
258
  self.positions = positions
259
259
  self.target_median = target_median
@@ -388,7 +388,7 @@ class ReferenceOverlayDialog(QDialog):
388
388
  class ExoPlanetWindow(QDialog):
389
389
  def __init__(self, parent=None, wrench_path=None):
390
390
  super().__init__(parent)
391
- self.setWindowTitle("Exoplanet Transit Detector")
391
+ self.setWindowTitle(self.tr("Exoplanet Transit Detector"))
392
392
 
393
393
  self.resize(900, 600)
394
394
  self.wrench_path = wrench_path
@@ -430,9 +430,9 @@ class ExoPlanetWindow(QDialog):
430
430
 
431
431
  # — Mode selector —
432
432
  mode_layout = QHBoxLayout()
433
- mode_layout.addWidget(QLabel("Mode:"))
434
- self.aligned_mode_rb = QRadioButton("Aligned Subs")
435
- self.raw_mode_rb = QRadioButton("Raw Subs")
433
+ mode_layout.addWidget(QLabel(self.tr("Mode:")))
434
+ self.aligned_mode_rb = QRadioButton(self.tr("Aligned Subs"))
435
+ self.raw_mode_rb = QRadioButton(self.tr("Raw Subs"))
436
436
  self.aligned_mode_rb.setChecked(True)
437
437
  mg = QButtonGroup(self)
438
438
  mg.addButton(self.aligned_mode_rb); mg.addButton(self.raw_mode_rb)
@@ -459,8 +459,8 @@ class ExoPlanetWindow(QDialog):
459
459
 
460
460
  # — Calibration controls (hidden in Aligned) —
461
461
  cal_layout = QHBoxLayout()
462
- self.load_darks_btn = QPushButton("Load Master Dark…")
463
- self.load_flats_btn = QPushButton("Load Master Flat…")
462
+ self.load_darks_btn = QPushButton(self.tr("Load Master Dark…"))
463
+ self.load_flats_btn = QPushButton(self.tr("Load Master Flat…"))
464
464
  for w in (self.load_darks_btn, self.load_flats_btn):
465
465
  w.clicked.connect(self.load_masters)
466
466
  w.hide()
@@ -478,10 +478,10 @@ class ExoPlanetWindow(QDialog):
478
478
 
479
479
  # — Top controls —
480
480
  top_layout = QHBoxLayout()
481
- self.load_raw_btn = QPushButton("1: Load Raw Subs…")
482
- self.load_aligned_btn = QPushButton("Load, Measure && Photometry…")
483
- self.calibrate_btn = QPushButton("1a: Calibrate && Align Subs")
484
- self.measure_btn = QPushButton("2: Measure && Photometry")
481
+ self.load_raw_btn = QPushButton(self.tr("1: Load Raw Subs…"))
482
+ self.load_aligned_btn = QPushButton(self.tr("Load, Measure && Photometry…"))
483
+ self.calibrate_btn = QPushButton(self.tr("1a: Calibrate && Align Subs"))
484
+ self.measure_btn = QPushButton(self.tr("2: Measure && Photometry"))
485
485
  self.load_raw_btn. clicked.connect(self.load_raw_subs)
486
486
  self.load_aligned_btn.clicked.connect(self.load_and_measure_subs)
487
487
  self.calibrate_btn.clicked.connect(self.calibrate_and_align)