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,168 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Internationalization (i18n) module for Seti Astro Suite Pro.
4
+
5
+ Handles loading and managing translations using PyQt6's QTranslator.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from typing import Optional, Dict, List
11
+
12
+ from PyQt6.QtCore import QCoreApplication, QTranslator, QLocale, QSettings
13
+
14
+ # Module-level translator instance (kept alive for the app lifetime)
15
+ _translator: Optional[QTranslator] = None
16
+ _current_language: str = "en"
17
+
18
+ # Available languages with display names
19
+ AVAILABLE_LANGUAGES: Dict[str, str] = {
20
+ "en": "English",
21
+ "it": "Italiano",
22
+ "fr": "Français",
23
+ "es": "Español",
24
+ "zh": "简体中文",
25
+ "de": "Deutsch",
26
+ "pt": "Português",
27
+ "ja": "日本語",
28
+ "hi": "हिन्दी",
29
+ "sw": "Kiswahili",
30
+ "uk": "Українська",
31
+ "ru": "Русский",
32
+ "ar": "العربية",
33
+ }
34
+
35
+
36
+ def get_translations_dir() -> str:
37
+ """Get the path to the translations directory."""
38
+ # Source / installed package location
39
+ module_dir = os.path.dirname(os.path.abspath(__file__))
40
+ pkg_dir = os.path.join(module_dir, "translations")
41
+
42
+ # PyInstaller frozen builds
43
+ if hasattr(os.sys, "_MEIPASS"):
44
+ # New bundle layout (preferred)
45
+ frozen_internal = os.path.join(os.sys._MEIPASS, "_internal", "translations")
46
+ if os.path.exists(frozen_internal):
47
+ return frozen_internal
48
+
49
+ # Legacy bundle layout fallback
50
+ frozen_legacy = os.path.join(os.sys._MEIPASS, "translations")
51
+ if os.path.exists(frozen_legacy):
52
+ return frozen_legacy
53
+
54
+ return pkg_dir
55
+
56
+
57
+
58
+ def get_available_languages() -> Dict[str, str]:
59
+ """
60
+ Get available languages.
61
+
62
+ Returns:
63
+ Dict mapping language codes to display names (e.g., {"en": "English"})
64
+ """
65
+ return AVAILABLE_LANGUAGES.copy()
66
+
67
+
68
+ def get_current_language() -> str:
69
+ """Get the currently loaded language code."""
70
+ return _current_language
71
+
72
+
73
+ def get_saved_language() -> str:
74
+ """
75
+ Get the language saved in settings.
76
+
77
+ Returns:
78
+ Language code from settings, defaults to "en"
79
+ """
80
+ settings = QSettings("SetiAstro", "SetiAstroSuitePro")
81
+ return settings.value("ui/language", "en", type=str) or "en"
82
+
83
+
84
+ def save_language(lang_code: str) -> None:
85
+ """
86
+ Save the language preference to settings.
87
+
88
+ Args:
89
+ lang_code: Language code (e.g., "it", "fr", "es")
90
+ """
91
+ settings = QSettings("SetiAstro", "SetiAstroSuitePro")
92
+ settings.setValue("ui/language", lang_code)
93
+ settings.sync()
94
+
95
+
96
+ def load_language(lang_code: str = None, app: QCoreApplication = None) -> bool:
97
+ """
98
+ Load a translation for the specified language.
99
+
100
+ Must be called BEFORE creating any widgets for full effect.
101
+
102
+ Args:
103
+ lang_code: Language code (e.g., "it", "fr", "es").
104
+ If None, reads from settings.
105
+ app: QApplication instance. If None, uses QCoreApplication.instance()
106
+
107
+ Returns:
108
+ True if translation was loaded successfully, False otherwise
109
+ """
110
+ global _translator, _current_language
111
+
112
+ if app is None:
113
+ app = QCoreApplication.instance()
114
+
115
+ if app is None:
116
+ return False
117
+
118
+ # Get language from settings if not specified
119
+ if lang_code is None:
120
+ lang_code = get_saved_language()
121
+
122
+ # English is the base language, no translation needed
123
+ if lang_code == "en":
124
+ # Remove any existing translator
125
+ if _translator is not None:
126
+ app.removeTranslator(_translator)
127
+ _translator = None
128
+ _current_language = "en"
129
+ return True
130
+
131
+ # Find translation file
132
+ translations_dir = get_translations_dir()
133
+ qm_file = os.path.join(translations_dir, f"saspro_{lang_code}.qm")
134
+
135
+ if not os.path.exists(qm_file):
136
+ # Translation file doesn't exist yet
137
+ _current_language = lang_code
138
+ return False
139
+
140
+ # Remove old translator if any
141
+ if _translator is not None:
142
+ app.removeTranslator(_translator)
143
+
144
+ # Load new translator
145
+ _translator = QTranslator()
146
+ if _translator.load(qm_file):
147
+ app.installTranslator(_translator)
148
+ _current_language = lang_code
149
+ return True
150
+ else:
151
+ _translator = None
152
+ return False
153
+
154
+
155
+ def tr(text: str, context: str = "Global") -> str:
156
+ """
157
+ Translate a string outside of a QObject context.
158
+
159
+ For use in module-level code or functions that aren't methods of QObject.
160
+
161
+ Args:
162
+ text: Text to translate
163
+ context: Context for disambiguation (default: "Global")
164
+
165
+ Returns:
166
+ Translated string
167
+ """
168
+ return QCoreApplication.translate(context, text)
@@ -91,6 +91,9 @@ class ImageCombineDialog(QDialog):
91
91
  def __init__(self, main_window):
92
92
  super().__init__(main_window)
93
93
  self.setWindowTitle("Image Combine")
94
+ self.setWindowFlag(Qt.WindowType.Window, True)
95
+ self.setWindowModality(Qt.WindowModality.NonModal)
96
+ self.setModal(False)
94
97
  self.mw = main_window
95
98
  self.dm = getattr(main_window, "doc_manager", None) or getattr(main_window, "dm", None)
96
99
  self.zoom = 1.0
@@ -94,14 +94,14 @@ class PreviewPane(QWidget):
94
94
  self.zoom_slider.setValue(100)
95
95
  self.zoom_slider.valueChanged.connect(self.on_zoom_changed)
96
96
 
97
- self.zoom_in_btn = themed_toolbtn("zoom-in", "Zoom In")
98
- self.zoom_out_btn = themed_toolbtn("zoom-out", "Zoom Out")
99
- self.fit_btn = themed_toolbtn("zoom-fit-best", "Fit to Preview")
97
+ self.zoom_in_btn = themed_toolbtn("zoom-in", self.tr("Zoom In"))
98
+ self.zoom_out_btn = themed_toolbtn("zoom-out", self.tr("Zoom Out"))
99
+ self.fit_btn = themed_toolbtn("zoom-fit-best", self.tr("Fit to Preview"))
100
100
  self.zoom_in_btn.clicked .connect(lambda: self.adjust_zoom(10))
101
101
  self.zoom_out_btn.clicked.connect(lambda: self.adjust_zoom(-10))
102
102
  self.fit_btn.clicked .connect(self.fit_to_view)
103
103
 
104
- self.stretch_btn = QPushButton("AutoStretch")
104
+ self.stretch_btn = QPushButton(self.tr("AutoStretch"))
105
105
  self.stretch_btn.clicked.connect(self.toggle_stretch)
106
106
 
107
107
  zl = QHBoxLayout()
@@ -632,14 +632,14 @@ class TiltDialog(QDialog):
632
632
  min_d, max_d = min(corner_deltas), max(corner_deltas)
633
633
 
634
634
  # 2) now build a more meaningful label:
635
- range_label = QLabel(f"Tilt span: {min_d:.1f} µm … {max_d:.1f} µm")
635
+ range_label = QLabel(self.tr("Tilt span: {0:.1f} µm … {1:.1f} µm").format(min_d, max_d))
636
636
  for name, (x, y) in corners.items():
637
637
  # how far above/below the center plane
638
638
  delta = a*(x - cx) + b*(y - cy)
639
639
  rows.append((name, f"{delta:.1f}"))
640
640
 
641
641
  table = QTableWidget(len(rows), 2, self)
642
- table.setHorizontalHeaderLabels(["Corner", "Δ µm"])
642
+ table.setHorizontalHeaderLabels([self.tr("Corner"), self.tr("Δ µm")])
643
643
  # hide the vertical header
644
644
  table.verticalHeader().setVisible(False)
645
645
  for i, (name, val) in enumerate(rows):
@@ -653,7 +653,7 @@ class TiltDialog(QDialog):
653
653
  layout.addWidget(range_label, 0) # stretch = 0
654
654
  if table:
655
655
  layout.addWidget(table, 0)
656
- close_btn = QPushButton("Close", self)
656
+ close_btn = QPushButton(self.tr("Close"), self)
657
657
  close_btn.clicked.connect(self.accept)
658
658
  layout.addWidget(close_btn, 0)
659
659
 
@@ -897,9 +897,9 @@ class SurfaceDialog(QDialog):
897
897
  h.addWidget(view, 1)
898
898
  h.addWidget(lbl_cb, 0)
899
899
 
900
- btn = QPushButton("Close")
900
+ btn = QPushButton(self.tr("Close"))
901
901
  btn.clicked.connect(self.accept)
902
- lbl_span = QLabel(f"Span: {vmin:.2f} … {vmax:.2f} {units}")
902
+ lbl_span = QLabel(self.tr("Span: {0:.2f} … {1:.2f} {2}").format(vmin, vmax, units))
903
903
 
904
904
  v = QVBoxLayout(self)
905
905
  v.addLayout(h)
@@ -1057,7 +1057,7 @@ class DistortionGridDialog(QDialog):
1057
1057
  amplify: float = 20.0,
1058
1058
  parent=None):
1059
1059
  super().__init__(parent)
1060
- self.setWindowTitle("Astrometric Distortion & Histogram")
1060
+ self.setWindowTitle(self.tr("Astrometric Distortion & Histogram"))
1061
1061
 
1062
1062
  # — 1) detect stars —
1063
1063
  gray = img.mean(-1).astype(np.float32) if img.ndim==3 else img.astype(np.float32)
@@ -1065,7 +1065,7 @@ class DistortionGridDialog(QDialog):
1065
1065
  bkg = sep.Background(data)
1066
1066
  stars = sep.extract(data - bkg.back(), thresh=5.0, err=bkg.globalrms)
1067
1067
  if stars is None or len(stars) < 10:
1068
- QMessageBox.warning(self, "Distortion", "Not enough stars found.")
1068
+ QMessageBox.warning(self, self.tr("Distortion"), self.tr("Not enough stars found."))
1069
1069
  self.reject()
1070
1070
  return
1071
1071
 
@@ -1098,7 +1098,7 @@ class DistortionGridDialog(QDialog):
1098
1098
  label_font = QFont("Arial", 12, QFont.Weight.Bold)
1099
1099
 
1100
1100
  # title above the grid
1101
- title = QLabel("Astrometric Distortion Grid")
1101
+ title = QLabel(self.tr("Astrometric Distortion Grid"))
1102
1102
  title.setFont(QFont("Arial", 16, QFont.Weight.Bold))
1103
1103
  title.setAlignment(Qt.AlignmentFlag.AlignCenter)
1104
1104
  title.setStyleSheet("color: white;")
@@ -1163,9 +1163,9 @@ class DistortionGridDialog(QDialog):
1163
1163
  canvas = FigureCanvas(fig)
1164
1164
  ax = fig.add_subplot(111)
1165
1165
  ax.hist(disp_star_arcsec, bins=30, edgecolor='black')
1166
- ax.set_xlabel("Distortion (″)")
1167
- ax.set_ylabel("Number of stars")
1168
- ax.set_title("Residual histogram")
1166
+ ax.set_xlabel(self.tr("Distortion (″)"))
1167
+ ax.set_ylabel(self.tr("Number of stars"))
1168
+ ax.set_title(self.tr("Residual histogram"))
1169
1169
  fig.tight_layout()
1170
1170
 
1171
1171
  # side-by-side layout
@@ -1174,7 +1174,7 @@ class DistortionGridDialog(QDialog):
1174
1174
  hl.addWidget(canvas, 1)
1175
1175
 
1176
1176
  # close button
1177
- btn = QPushButton("Close")
1177
+ btn = QPushButton(self.tr("Close"))
1178
1178
  btn.clicked.connect(self.accept)
1179
1179
 
1180
1180
  # final
@@ -1313,7 +1313,10 @@ def _arcsec_per_pix_from_header(hdr: fits.Header, fallback_px_um: float|None=Non
1313
1313
  class ImagePeekerDialogPro(QDialog):
1314
1314
  def __init__(self, parent, document, settings):
1315
1315
  super().__init__(parent)
1316
- self.setWindowTitle("Image Peeker")
1316
+ self.setWindowTitle(self.tr("Image Peeker"))
1317
+ self.setWindowFlag(Qt.WindowType.Window, True)
1318
+ self.setWindowModality(Qt.WindowModality.NonModal)
1319
+ self.setModal(False)
1317
1320
  self.document = self._coerce_doc(document) # <- ensure we hold a real doc
1318
1321
  self.settings = settings
1319
1322
  # status / progress line
@@ -1321,7 +1324,7 @@ class ImagePeekerDialogPro(QDialog):
1321
1324
  self.status_lbl.setStyleSheet("color:#bbb;")
1322
1325
 
1323
1326
 
1324
- self.params = QGroupBox("Grid parameters")
1327
+ self.params = QGroupBox(self.tr("Grid parameters"))
1325
1328
  self.params.setMinimumWidth(180)
1326
1329
  self.params.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
1327
1330
  gl = QGridLayout(self.params)
@@ -1343,28 +1346,31 @@ class ImagePeekerDialogPro(QDialog):
1343
1346
  self.pixel_size_input.setValue(px); self.focal_length_input.setValue(fl); self.aperture_input.setValue(ap)
1344
1347
 
1345
1348
  row = 0
1346
- gl.addWidget(QLabel("Grid size:"), row, 0); gl.addWidget(self.grid_spin, row, 1); row += 1
1347
- gl.addWidget(QLabel("Panel size:"), row, 0)
1349
+ gl.addWidget(QLabel(self.tr("Grid size:")), row, 0); gl.addWidget(self.grid_spin, row, 1); row += 1
1350
+ gl.addWidget(QLabel(self.tr("Panel size:")), row, 0)
1348
1351
  pr = QHBoxLayout(); pr.addWidget(self.panel_slider, 1); pr.addWidget(self.panel_value_label)
1349
1352
  gl.addLayout(pr, row, 1); row += 1
1350
- gl.addWidget(QLabel("Separation:"), row, 0)
1353
+ gl.addWidget(QLabel(self.tr("Separation:")), row, 0)
1351
1354
  sr = QHBoxLayout(); sr.addWidget(self.sep_slider, 1); sr.addWidget(self.sep_value_label)
1352
1355
  gl.addLayout(sr, row, 1); row += 1
1353
- gl.addWidget(QLabel("Pixel size (µm):"), row, 0); gl.addWidget(self.pixel_size_input, row, 1); row += 1
1354
- gl.addWidget(QLabel("Focal length (mm):"), row, 0); gl.addWidget(self.focal_length_input, row, 1); row += 1
1355
- gl.addWidget(QLabel("Aperture (mm):"), row, 0); gl.addWidget(self.aperture_input, row, 1); row += 1
1356
+ gl.addWidget(QLabel(self.tr("Pixel size (µm):")), row, 0); gl.addWidget(self.pixel_size_input, row, 1); row += 1
1357
+ gl.addWidget(QLabel(self.tr("Focal length (mm):")), row, 0); gl.addWidget(self.focal_length_input, row, 1); row += 1
1358
+ gl.addWidget(QLabel(self.tr("Aperture (mm):")), row, 0); gl.addWidget(self.aperture_input, row, 1); row += 1
1356
1359
 
1357
1360
  # Right side
1358
1361
  from PyQt6.QtWidgets import QTabWidget
1359
1362
  self.preview_pane = PreviewPane()
1360
1363
  analysis_row = QHBoxLayout()
1361
- analysis_row.addWidget(QLabel("Analysis:"))
1364
+ analysis_row.addWidget(QLabel(self.tr("Analysis:")))
1362
1365
  self.analysis_combo = QComboBox()
1363
- self.analysis_combo.addItems(["None", "Tilt Analysis", "Focal Plane Analysis", "Astrometric Distortion Analysis"])
1366
+ self.analysis_combo.addItem(self.tr("None"), "None")
1367
+ self.analysis_combo.addItem(self.tr("Tilt Analysis"), "Tilt Analysis")
1368
+ self.analysis_combo.addItem(self.tr("Focal Plane Analysis"), "Focal Plane Analysis")
1369
+ self.analysis_combo.addItem(self.tr("Astrometric Distortion Analysis"), "Astrometric Distortion Analysis")
1364
1370
  analysis_row.addWidget(self.analysis_combo); analysis_row.addStretch(1)
1365
1371
 
1366
1372
  btns = QHBoxLayout(); btns.addStretch(1)
1367
- ok_btn = QPushButton("Save Settings && Exit"); cancel_btn = QPushButton("Exit without Saving")
1373
+ ok_btn = QPushButton(self.tr("Save Settings && Exit")); cancel_btn = QPushButton(self.tr("Exit without Saving"))
1368
1374
  btns.addWidget(ok_btn); btns.addWidget(cancel_btn)
1369
1375
 
1370
1376
  main = QHBoxLayout(self)
@@ -1381,7 +1387,8 @@ class ImagePeekerDialogPro(QDialog):
1381
1387
 
1382
1388
  QTimer.singleShot(0, self._refresh_mosaic)
1383
1389
 
1384
- def _set_busy(self, on: bool, text: str = "Processing…"):
1390
+ def _set_busy(self, on: bool, text: str = ""):
1391
+ if not text: text = self.tr("Processing…")
1385
1392
  self.status_lbl.setText(text if on else "")
1386
1393
  for w in (self.params, self.analysis_combo):
1387
1394
  w.setEnabled(not on)
@@ -1404,13 +1411,14 @@ class ImagePeekerDialogPro(QDialog):
1404
1411
 
1405
1412
 
1406
1413
  def _run_analysis(self, *_):
1407
- mode = self.analysis_combo.currentText()
1408
- if mode == "None":
1414
+ mode_key = self.analysis_combo.currentData()
1415
+ mode_disp = self.analysis_combo.currentText()
1416
+ if mode_key == "None":
1409
1417
  self._set_busy(False, "")
1410
1418
  self._refresh_mosaic()
1411
1419
  return
1412
- self._set_busy(True, f"Running {mode}…")
1413
- QTimer.singleShot(0, lambda: self._run_analysis_dispatch(mode))
1420
+ self._set_busy(True, self.tr("Running {0}…").format(mode_disp))
1421
+ QTimer.singleShot(0, lambda: self._run_analysis_dispatch(mode_key))
1414
1422
 
1415
1423
  def _run_analysis_dispatch(self, mode: str):
1416
1424
  try:
@@ -1427,15 +1435,15 @@ class ImagePeekerDialogPro(QDialog):
1427
1435
  arr, pixel_size_um=ps_um, focal_length_mm=fl_mm, aperture_mm=ap_mm,
1428
1436
  sigma_clip=2.5, thresh_sigma=snr_th
1429
1437
  )
1430
- TiltDialog("Sensor Tilt (µm)", norm_plane, (a,b,c), (H,W), ps_um, parent=self).show()
1438
+ TiltDialog(self.tr("Sensor Tilt (µm)"), norm_plane, (a,b,c), (H,W), ps_um, parent=self).show()
1431
1439
 
1432
1440
  elif mode == "Focal Plane Analysis":
1433
1441
  fwhm_heat, (mn_f, mx_f) = compute_fwhm_surface(arr, ps_um, thresh_sigma=snr_th, deg=3)
1434
- SurfaceDialog("FWHM Heatmap", fwhm_heat, mn_f, mx_f, "µm", "viridis", parent=self).show()
1442
+ SurfaceDialog(self.tr("FWHM Heatmap"), fwhm_heat, mn_f, mx_f, "µm", "viridis", parent=self).show()
1435
1443
  ecc_heat, (mn_e, mx_e) = compute_eccentricity_surface(arr, ps_um, thresh_sigma=snr_th, deg=3)
1436
- SurfaceDialog("Eccentricity Map", ecc_heat, mn_e, mx_e, "e = 1−b/a", "magma", parent=self).show()
1444
+ SurfaceDialog(self.tr("Eccentricity Map"), ecc_heat, mn_e, mx_e, "e = 1−b/a", "magma", parent=self).show()
1437
1445
  ori_heat, (mn_o, mx_o) = compute_orientation_surface(arr, thresh_sigma=snr_th, deg=3)
1438
- SurfaceDialog("Orientation Map", ori_heat, mn_o, mx_o, "rad", "hsv", parent=self).show()
1446
+ SurfaceDialog(self.tr("Orientation Map"), ori_heat, mn_o, mx_o, "rad", "hsv", parent=self).show()
1439
1447
 
1440
1448
  elif mode == "Astrometric Distortion Analysis":
1441
1449
  hdr = _header_from_meta(meta)
@@ -1446,7 +1454,7 @@ class ImagePeekerDialogPro(QDialog):
1446
1454
  parent=self, doc=self._coerce_doc(self.document), settings=self.settings
1447
1455
  )
1448
1456
  if not ok:
1449
- QMessageBox.warning(self, "Plate Solve", f"ASTAP/Astrometry failed:\n{hdr_or_err}")
1457
+ QMessageBox.warning(self, self.tr("Plate Solve"), self.tr("ASTAP/Astrometry failed:\n{0}").format(hdr_or_err))
1450
1458
  return
1451
1459
 
1452
1460
  # IMPORTANT: if solver returned a Header, store it
@@ -1465,22 +1473,22 @@ class ImagePeekerDialogPro(QDialog):
1465
1473
 
1466
1474
  # Now WCS exists, but do we have SIP?
1467
1475
  if hdr is None:
1468
- QMessageBox.critical(self, "WCS Error", "Plate solve did not produce a readable WCS header.")
1476
+ QMessageBox.critical(self, self.tr("WCS Error"), self.tr("Plate solve did not produce a readable WCS header."))
1469
1477
  return
1470
1478
 
1471
1479
  has_sip = any(k.startswith("A_") for k in hdr.keys()) and any(k.startswith("B_") for k in hdr.keys())
1472
1480
  if not has_sip:
1473
1481
  QMessageBox.warning(
1474
- self, "No Distortion Model",
1475
- "This image has a valid WCS, but no SIP distortion terms (A_*, B_*).\n"
1482
+ self, self.tr("No Distortion Model"),
1483
+ self.tr("This image has a valid WCS, but no SIP distortion terms (A_*, B_*).\n"
1476
1484
  "Astrometric distortion analysis requires a SIP-enabled solve.\n\n"
1477
- "Re-solve with distortion fitting enabled in ASTAP."
1485
+ "Re-solve with distortion fitting enabled in ASTAP.")
1478
1486
  )
1479
1487
  return
1480
1488
 
1481
1489
  asp = _arcsec_per_pix_from_header(hdr, fallback_px_um=ps_um, fallback_fl_mm=fl_mm)
1482
1490
  if asp is None:
1483
- QMessageBox.critical(self, "WCS Error", "Cannot determine pixel scale.")
1491
+ QMessageBox.critical(self, self.tr("WCS Error"), self.tr("Cannot determine pixel scale."))
1484
1492
  return
1485
1493
 
1486
1494
  DistortionGridDialog(
@@ -1526,7 +1534,7 @@ class ImagePeekerDialogPro(QDialog):
1526
1534
  self.sep_color_btn.setIcon(QIcon(pix))
1527
1535
 
1528
1536
  def _choose_sep_color(self):
1529
- col = QColorDialog.getColor(self._sep_color, self, "Choose separation color")
1537
+ col = QColorDialog.getColor(self._sep_color, self, self.tr("Choose separation color"))
1530
1538
  if col.isValid():
1531
1539
  self._sep_color = col
1532
1540
  self._update_sep_color_button()
@@ -1554,7 +1562,7 @@ class ImagePeekerDialogPro(QDialog):
1554
1562
  # fetch the currently loaded image (you’ll adapt to your image_manager API)
1555
1563
  img = self.image_manager.current_qimage()
1556
1564
  if img is None:
1557
- QMessageBox.warning(self, "No image", "No image loaded to peek at!")
1565
+ QMessageBox.warning(self, self.tr("No image"), self.tr("No image loaded to peek at!"))
1558
1566
  return
1559
1567
 
1560
1568
  mosaic = self._build_mosaic(img, n, panel_sz, sep, sep_col)
@@ -95,19 +95,11 @@ def apply_curves_adjustment(image: np.ndarray,
95
95
  xvals, yvals = _calculate_curve_points(target_median, curves_boost)
96
96
 
97
97
  # Apply the 1D LUT per channel using np.interp (piecewise linear)
98
- if img.ndim == 2:
99
- flat = img.ravel()
100
- out = np.interp(flat, xvals, yvals).reshape(img.shape).astype(np.float32)
101
- elif img.ndim == 3 and img.shape[2] in (3, 4):
102
- h, w, c = img.shape
103
- out = np.empty_like(img, dtype=np.float32)
104
- # Apply same curve to each color channel
105
- for ch in range(c):
106
- flat = img[..., ch].ravel()
107
- out[..., ch] = np.interp(flat, xvals, yvals).reshape(h, w)
108
- else:
109
- # Fallback: just return clamped image
110
- out = img
98
+ # Apply the 1D LUT per channel using np.interp (piecewise linear)
99
+ # Optimization: np.interp can handle N-D 'x' array directly.
100
+ # No need to loop over channels or flatten/reshape if we pass the whole array.
101
+
102
+ out = np.interp(img, xvals, yvals).astype(np.float32, copy=False)
111
103
 
112
104
  return np.clip(out, 0.0, 1.0)
113
105
 
@@ -360,6 +360,9 @@ class IsophoteModelerDialog(QDialog):
360
360
  def __init__(self, mono_image: np.ndarray, parent: Optional[QWidget] = None,
361
361
  title_hint: Optional[str] = None, image_manager=None, doc_manager=None):
362
362
  super().__init__(parent)
363
+ self.setWindowFlag(Qt.WindowType.Window, True)
364
+ self.setWindowModality(Qt.WindowModality.NonModal)
365
+ self.setModal(False)
363
366
  self.image_manager = image_manager
364
367
  self.doc_manager = doc_manager
365
368
 
@@ -802,39 +802,46 @@ def kappa_sigma_clip_weighted_3d(stack, weights, kappa=2.5, iterations=3):
802
802
  pixel_weights = weights[:]
803
803
  else:
804
804
  pixel_weights = weights[:, i, j].copy()
805
- # Initialize tracking of indices
806
- current_idx = np.empty(num_frames, dtype=np.int64)
807
- for f in range(num_frames):
808
- current_idx[f] = f
809
- current_vals = pixel_values
810
- current_w = pixel_weights
811
- current_indices = current_idx
805
+
806
+ valid_mask = pixel_values != 0
807
+
812
808
  med = 0.0
813
809
  for _ in range(iterations):
814
- if current_vals.size == 0:
810
+ count = 0
811
+ for k in range(num_frames):
812
+ if valid_mask[k]:
813
+ count += 1
814
+
815
+ if count == 0:
815
816
  break
817
+
818
+ current_vals = pixel_values[valid_mask]
819
+
816
820
  med = np.median(current_vals)
817
821
  std = np.std(current_vals)
818
822
  lower_bound = med - kappa * std
819
823
  upper_bound = med + kappa * std
820
- valid = (current_vals != 0) & (current_vals >= lower_bound) & (current_vals <= upper_bound)
821
- current_vals = current_vals[valid]
822
- current_w = current_w[valid]
823
- current_indices = current_indices[valid]
824
- # Mark rejected: frames not in current_indices are rejected.
824
+
825
+ for k in range(num_frames):
826
+ if valid_mask[k]:
827
+ val = pixel_values[k]
828
+ if val < lower_bound or val > upper_bound:
829
+ valid_mask[k] = False
830
+
825
831
  for f in range(num_frames):
826
- # Check if f is in current_indices
827
- found = False
828
- for k in range(current_indices.size):
829
- if current_indices[k] == f:
830
- found = True
831
- break
832
- if not found:
833
- rej_mask[f, i, j] = True
834
- else:
835
- rej_mask[f, i, j] = False
836
- if current_w.size > 0 and current_w.sum() > 0:
837
- clipped[i, j] = np.sum(current_vals * current_w) / current_w.sum()
832
+ rej_mask[f, i, j] = not valid_mask[f]
833
+
834
+ wsum = 0.0
835
+ vsum = 0.0
836
+ for k in range(num_frames):
837
+ if valid_mask[k]:
838
+ w = pixel_weights[k]
839
+ v = pixel_values[k]
840
+ wsum += w
841
+ vsum += v * w
842
+
843
+ if wsum > 0:
844
+ clipped[i, j] = vsum / wsum
838
845
  else:
839
846
  clipped[i, j] = med
840
847
  return clipped, rej_mask
@@ -859,36 +866,46 @@ def kappa_sigma_clip_weighted_4d(stack, weights, kappa=2.5, iterations=3):
859
866
  pixel_weights = weights[:]
860
867
  else:
861
868
  pixel_weights = weights[:, i, j, c].copy()
862
- current_idx = np.empty(num_frames, dtype=np.int64)
863
- for f in range(num_frames):
864
- current_idx[f] = f
865
- current_vals = pixel_values
866
- current_w = pixel_weights
867
- current_indices = current_idx
869
+
870
+ valid_mask = pixel_values != 0
871
+
868
872
  med = 0.0
869
873
  for _ in range(iterations):
870
- if current_vals.size == 0:
874
+ count = 0
875
+ for k in range(num_frames):
876
+ if valid_mask[k]:
877
+ count += 1
878
+
879
+ if count == 0:
871
880
  break
881
+
882
+ current_vals = pixel_values[valid_mask]
883
+
872
884
  med = np.median(current_vals)
873
885
  std = np.std(current_vals)
874
886
  lower_bound = med - kappa * std
875
887
  upper_bound = med + kappa * std
876
- valid = (current_vals != 0) & (current_vals >= lower_bound) & (current_vals <= upper_bound)
877
- current_vals = current_vals[valid]
878
- current_w = current_w[valid]
879
- current_indices = current_indices[valid]
888
+
889
+ for k in range(num_frames):
890
+ if valid_mask[k]:
891
+ val = pixel_values[k]
892
+ if val < lower_bound or val > upper_bound:
893
+ valid_mask[k] = False
894
+
880
895
  for f in range(num_frames):
881
- found = False
882
- for k in range(current_indices.size):
883
- if current_indices[k] == f:
884
- found = True
885
- break
886
- if not found:
887
- rej_mask[f, i, j, c] = True
888
- else:
889
- rej_mask[f, i, j, c] = False
890
- if current_w.size > 0 and current_w.sum() > 0:
891
- clipped[i, j, c] = np.sum(current_vals * current_w) / current_w.sum()
896
+ rej_mask[f, i, j, c] = not valid_mask[f]
897
+
898
+ wsum = 0.0
899
+ vsum = 0.0
900
+ for k in range(num_frames):
901
+ if valid_mask[k]:
902
+ w = pixel_weights[k]
903
+ v = pixel_values[k]
904
+ wsum += w
905
+ vsum += v * w
906
+
907
+ if wsum > 0:
908
+ clipped[i, j, c] = vsum / wsum
892
909
  else:
893
910
  clipped[i, j, c] = med
894
911
  return clipped, rej_mask
@@ -224,6 +224,9 @@ class LinearFitDialog(QDialog):
224
224
  def __init__(self, parent, doc_manager, active_doc):
225
225
  super().__init__(parent)
226
226
  self.setWindowTitle("Linear Fit")
227
+ self.setWindowFlag(Qt.WindowType.Window, True)
228
+ self.setWindowModality(Qt.WindowModality.NonModal)
229
+ self.setModal(False)
227
230
  self.dm = doc_manager
228
231
  self.doc = active_doc
229
232
  self.worker: Optional[_LinearFitWorker] = None