setiastrosuitepro 1.6.0__py3-none-any.whl → 1.6.4.post1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. setiastro/data/SASP_data.fits +0 -0
  2. setiastro/data/catalogs/List_of_Galaxies_with_Distances_Gly.csv +488 -0
  3. setiastro/data/catalogs/astrobin_filters.csv +890 -0
  4. setiastro/data/catalogs/astrobin_filters_page1_local.csv +51 -0
  5. setiastro/data/catalogs/cali2.csv +63 -0
  6. setiastro/data/catalogs/cali2color.csv +65 -0
  7. setiastro/data/catalogs/celestial_catalog - original.csv +16471 -0
  8. setiastro/data/catalogs/celestial_catalog.csv +24031 -0
  9. setiastro/data/catalogs/detected_stars.csv +24784 -0
  10. setiastro/data/catalogs/fits_header_data.csv +46 -0
  11. setiastro/data/catalogs/test.csv +8 -0
  12. setiastro/data/catalogs/updated_celestial_catalog.csv +16471 -0
  13. setiastro/images/Astro_Spikes.png +0 -0
  14. setiastro/images/Background_startup.jpg +0 -0
  15. setiastro/images/HRDiagram.png +0 -0
  16. setiastro/images/LExtract.png +0 -0
  17. setiastro/images/LInsert.png +0 -0
  18. setiastro/images/Oxygenation-atm-2.svg.png +0 -0
  19. setiastro/images/RGB080604.png +0 -0
  20. setiastro/images/abeicon.png +0 -0
  21. setiastro/images/aberration.png +0 -0
  22. setiastro/images/andromedatry.png +0 -0
  23. setiastro/images/andromedatry_satellited.png +0 -0
  24. setiastro/images/annotated.png +0 -0
  25. setiastro/images/aperture.png +0 -0
  26. setiastro/images/astrosuite.ico +0 -0
  27. setiastro/images/astrosuite.png +0 -0
  28. setiastro/images/astrosuitepro.icns +0 -0
  29. setiastro/images/astrosuitepro.ico +0 -0
  30. setiastro/images/astrosuitepro.png +0 -0
  31. setiastro/images/background.png +0 -0
  32. setiastro/images/background2.png +0 -0
  33. setiastro/images/benchmark.png +0 -0
  34. setiastro/images/big_moon_stabilizer_timeline.png +0 -0
  35. setiastro/images/big_moon_stabilizer_timeline_clean.png +0 -0
  36. setiastro/images/blaster.png +0 -0
  37. setiastro/images/blink.png +0 -0
  38. setiastro/images/clahe.png +0 -0
  39. setiastro/images/collage.png +0 -0
  40. setiastro/images/colorwheel.png +0 -0
  41. setiastro/images/contsub.png +0 -0
  42. setiastro/images/convo.png +0 -0
  43. setiastro/images/copyslot.png +0 -0
  44. setiastro/images/cosmic.png +0 -0
  45. setiastro/images/cosmicsat.png +0 -0
  46. setiastro/images/crop1.png +0 -0
  47. setiastro/images/cropicon.png +0 -0
  48. setiastro/images/curves.png +0 -0
  49. setiastro/images/cvs.png +0 -0
  50. setiastro/images/debayer.png +0 -0
  51. setiastro/images/denoise_cnn_custom.png +0 -0
  52. setiastro/images/denoise_cnn_graph.png +0 -0
  53. setiastro/images/disk.png +0 -0
  54. setiastro/images/dse.png +0 -0
  55. setiastro/images/exoicon.png +0 -0
  56. setiastro/images/eye.png +0 -0
  57. setiastro/images/fliphorizontal.png +0 -0
  58. setiastro/images/flipvertical.png +0 -0
  59. setiastro/images/font.png +0 -0
  60. setiastro/images/freqsep.png +0 -0
  61. setiastro/images/functionbundle.png +0 -0
  62. setiastro/images/graxpert.png +0 -0
  63. setiastro/images/green.png +0 -0
  64. setiastro/images/gridicon.png +0 -0
  65. setiastro/images/halo.png +0 -0
  66. setiastro/images/hdr.png +0 -0
  67. setiastro/images/histogram.png +0 -0
  68. setiastro/images/hubble.png +0 -0
  69. setiastro/images/imagecombine.png +0 -0
  70. setiastro/images/invert.png +0 -0
  71. setiastro/images/isophote.png +0 -0
  72. setiastro/images/isophote_demo_figure.png +0 -0
  73. setiastro/images/isophote_demo_image.png +0 -0
  74. setiastro/images/isophote_demo_model.png +0 -0
  75. setiastro/images/isophote_demo_residual.png +0 -0
  76. setiastro/images/jwstpupil.png +0 -0
  77. setiastro/images/linearfit.png +0 -0
  78. setiastro/images/livestacking.png +0 -0
  79. setiastro/images/mask.png +0 -0
  80. setiastro/images/maskapply.png +0 -0
  81. setiastro/images/maskcreate.png +0 -0
  82. setiastro/images/maskremove.png +0 -0
  83. setiastro/images/morpho.png +0 -0
  84. setiastro/images/mosaic.png +0 -0
  85. setiastro/images/multiscale_decomp.png +0 -0
  86. setiastro/images/nbtorgb.png +0 -0
  87. setiastro/images/neutral.png +0 -0
  88. setiastro/images/nuke.png +0 -0
  89. setiastro/images/openfile.png +0 -0
  90. setiastro/images/pedestal.png +0 -0
  91. setiastro/images/pen.png +0 -0
  92. setiastro/images/pixelmath.png +0 -0
  93. setiastro/images/platesolve.png +0 -0
  94. setiastro/images/ppp.png +0 -0
  95. setiastro/images/pro.png +0 -0
  96. setiastro/images/project.png +0 -0
  97. setiastro/images/psf.png +0 -0
  98. setiastro/images/redo.png +0 -0
  99. setiastro/images/redoicon.png +0 -0
  100. setiastro/images/rescale.png +0 -0
  101. setiastro/images/rgbalign.png +0 -0
  102. setiastro/images/rgbcombo.png +0 -0
  103. setiastro/images/rgbextract.png +0 -0
  104. setiastro/images/rotate180.png +0 -0
  105. setiastro/images/rotatearbitrary.png +0 -0
  106. setiastro/images/rotateclockwise.png +0 -0
  107. setiastro/images/rotatecounterclockwise.png +0 -0
  108. setiastro/images/satellite.png +0 -0
  109. setiastro/images/script.png +0 -0
  110. setiastro/images/selectivecolor.png +0 -0
  111. setiastro/images/simbad.png +0 -0
  112. setiastro/images/slot0.png +0 -0
  113. setiastro/images/slot1.png +0 -0
  114. setiastro/images/slot2.png +0 -0
  115. setiastro/images/slot3.png +0 -0
  116. setiastro/images/slot4.png +0 -0
  117. setiastro/images/slot5.png +0 -0
  118. setiastro/images/slot6.png +0 -0
  119. setiastro/images/slot7.png +0 -0
  120. setiastro/images/slot8.png +0 -0
  121. setiastro/images/slot9.png +0 -0
  122. setiastro/images/spcc.png +0 -0
  123. setiastro/images/spin_precession_vs_lunar_distance.png +0 -0
  124. setiastro/images/spinner.gif +0 -0
  125. setiastro/images/stacking.png +0 -0
  126. setiastro/images/staradd.png +0 -0
  127. setiastro/images/staralign.png +0 -0
  128. setiastro/images/starnet.png +0 -0
  129. setiastro/images/starregistration.png +0 -0
  130. setiastro/images/starspike.png +0 -0
  131. setiastro/images/starstretch.png +0 -0
  132. setiastro/images/statstretch.png +0 -0
  133. setiastro/images/supernova.png +0 -0
  134. setiastro/images/uhs.png +0 -0
  135. setiastro/images/undoicon.png +0 -0
  136. setiastro/images/upscale.png +0 -0
  137. setiastro/images/viewbundle.png +0 -0
  138. setiastro/images/whitebalance.png +0 -0
  139. setiastro/images/wimi_icon_256x256.png +0 -0
  140. setiastro/images/wimilogo.png +0 -0
  141. setiastro/images/wims.png +0 -0
  142. setiastro/images/wrench_icon.png +0 -0
  143. setiastro/images/xisfliberator.png +0 -0
  144. setiastro/qml/ResourceMonitor.qml +126 -0
  145. setiastro/saspro/__main__.py +228 -67
  146. setiastro/saspro/_generated/build_info.py +2 -1
  147. setiastro/saspro/abe.py +76 -25
  148. setiastro/saspro/aberration_ai.py +14 -14
  149. setiastro/saspro/add_stars.py +15 -12
  150. setiastro/saspro/astrobin_exporter.py +61 -58
  151. setiastro/saspro/astrospike_python.py +3 -1
  152. setiastro/saspro/autostretch.py +4 -2
  153. setiastro/saspro/backgroundneutral.py +65 -14
  154. setiastro/saspro/batch_convert.py +8 -5
  155. setiastro/saspro/batch_renamer.py +39 -36
  156. setiastro/saspro/blemish_blaster.py +15 -12
  157. setiastro/saspro/blink_comparator_pro.py +605 -379
  158. setiastro/saspro/cheat_sheet.py +62 -17
  159. setiastro/saspro/clahe.py +34 -8
  160. setiastro/saspro/comet_stacking.py +103 -38
  161. setiastro/saspro/common_tr.py +107 -0
  162. setiastro/saspro/continuum_subtract.py +7 -7
  163. setiastro/saspro/convo.py +12 -9
  164. setiastro/saspro/copyastro.py +3 -0
  165. setiastro/saspro/cosmicclarity.py +77 -52
  166. setiastro/saspro/crop_dialog_pro.py +80 -45
  167. setiastro/saspro/curve_editor_pro.py +51 -33
  168. setiastro/saspro/debayer.py +6 -3
  169. setiastro/saspro/doc_manager.py +49 -19
  170. setiastro/saspro/exoplanet_detector.py +11 -11
  171. setiastro/saspro/fitsmodifier.py +48 -44
  172. setiastro/saspro/fix_bom.py +32 -0
  173. setiastro/saspro/frequency_separation.py +18 -12
  174. setiastro/saspro/function_bundle.py +18 -16
  175. setiastro/saspro/generate_translations.py +3092 -0
  176. setiastro/saspro/ghs_dialog_pro.py +19 -16
  177. setiastro/saspro/graxpert.py +3 -0
  178. setiastro/saspro/gui/main_window.py +471 -126
  179. setiastro/saspro/gui/mixins/dock_mixin.py +123 -11
  180. setiastro/saspro/gui/mixins/file_mixin.py +25 -20
  181. setiastro/saspro/gui/mixins/geometry_mixin.py +115 -15
  182. setiastro/saspro/gui/mixins/header_mixin.py +6 -6
  183. setiastro/saspro/gui/mixins/mask_mixin.py +8 -8
  184. setiastro/saspro/gui/mixins/menu_mixin.py +62 -33
  185. setiastro/saspro/gui/mixins/toolbar_mixin.py +382 -226
  186. setiastro/saspro/gui/mixins/update_mixin.py +26 -26
  187. setiastro/saspro/gui/statistics_dialog.py +47 -0
  188. setiastro/saspro/halobgon.py +29 -3
  189. setiastro/saspro/header_viewer.py +21 -18
  190. setiastro/saspro/histogram.py +29 -26
  191. setiastro/saspro/history_explorer.py +2 -0
  192. setiastro/saspro/i18n.py +168 -0
  193. setiastro/saspro/image_combine.py +3 -0
  194. setiastro/saspro/image_peeker_pro.py +52 -44
  195. setiastro/saspro/imageops/stretch.py +5 -13
  196. setiastro/saspro/isophote.py +3 -0
  197. setiastro/saspro/legacy/numba_utils.py +64 -47
  198. setiastro/saspro/linear_fit.py +3 -0
  199. setiastro/saspro/live_stacking.py +13 -2
  200. setiastro/saspro/mask_creation.py +180 -22
  201. setiastro/saspro/mfdeconv.py +5 -0
  202. setiastro/saspro/morphology.py +38 -13
  203. setiastro/saspro/multiscale_decomp.py +713 -256
  204. setiastro/saspro/nbtorgb_stars.py +12 -2
  205. setiastro/saspro/numba_utils.py +149 -48
  206. setiastro/saspro/ops/scripts.py +77 -17
  207. setiastro/saspro/ops/settings.py +177 -100
  208. setiastro/saspro/perfect_palette_picker.py +25 -7
  209. setiastro/saspro/pixelmath.py +114 -110
  210. setiastro/saspro/plate_solver.py +118 -108
  211. setiastro/saspro/remove_green.py +24 -7
  212. setiastro/saspro/remove_stars.py +136 -162
  213. setiastro/saspro/remove_stars_preset.py +55 -13
  214. setiastro/saspro/resources.py +46 -15
  215. setiastro/saspro/rgb_combination.py +19 -18
  216. setiastro/saspro/rgbalign.py +11 -11
  217. setiastro/saspro/save_options.py +5 -4
  218. setiastro/saspro/selective_color.py +84 -25
  219. setiastro/saspro/sfcc.py +119 -72
  220. setiastro/saspro/shortcuts.py +345 -36
  221. setiastro/saspro/signature_insert.py +4 -1
  222. setiastro/saspro/stacking_suite.py +2066 -1119
  223. setiastro/saspro/star_alignment.py +291 -331
  224. setiastro/saspro/star_spikes.py +137 -53
  225. setiastro/saspro/star_stretch.py +47 -10
  226. setiastro/saspro/stat_stretch.py +52 -16
  227. setiastro/saspro/status_log_dock.py +1 -1
  228. setiastro/saspro/subwindow.py +97 -36
  229. setiastro/saspro/supernovaasteroidhunter.py +68 -61
  230. setiastro/saspro/swap_manager.py +77 -42
  231. setiastro/saspro/translations/all_source_strings.json +4726 -0
  232. setiastro/saspro/translations/ar_translations.py +4096 -0
  233. setiastro/saspro/translations/de_translations.py +3728 -0
  234. setiastro/saspro/translations/es_translations.py +4169 -0
  235. setiastro/saspro/translations/fr_translations.py +4090 -0
  236. setiastro/saspro/translations/hi_translations.py +3803 -0
  237. setiastro/saspro/translations/integrate_translations.py +271 -0
  238. setiastro/saspro/translations/it_translations.py +4728 -0
  239. setiastro/saspro/translations/ja_translations.py +3834 -0
  240. setiastro/saspro/translations/pt_translations.py +3847 -0
  241. setiastro/saspro/translations/ru_translations.py +3082 -0
  242. setiastro/saspro/translations/saspro_ar.qm +0 -0
  243. setiastro/saspro/translations/saspro_ar.ts +16019 -0
  244. setiastro/saspro/translations/saspro_de.qm +0 -0
  245. setiastro/saspro/translations/saspro_de.ts +14548 -0
  246. setiastro/saspro/translations/saspro_es.qm +0 -0
  247. setiastro/saspro/translations/saspro_es.ts +16202 -0
  248. setiastro/saspro/translations/saspro_fr.qm +0 -0
  249. setiastro/saspro/translations/saspro_fr.ts +15870 -0
  250. setiastro/saspro/translations/saspro_hi.qm +0 -0
  251. setiastro/saspro/translations/saspro_hi.ts +14855 -0
  252. setiastro/saspro/translations/saspro_it.qm +0 -0
  253. setiastro/saspro/translations/saspro_it.ts +19046 -0
  254. setiastro/saspro/translations/saspro_ja.qm +0 -0
  255. setiastro/saspro/translations/saspro_ja.ts +14980 -0
  256. setiastro/saspro/translations/saspro_pt.qm +0 -0
  257. setiastro/saspro/translations/saspro_pt.ts +15024 -0
  258. setiastro/saspro/translations/saspro_ru.qm +0 -0
  259. setiastro/saspro/translations/saspro_ru.ts +11835 -0
  260. setiastro/saspro/translations/saspro_sw.qm +0 -0
  261. setiastro/saspro/translations/saspro_sw.ts +15237 -0
  262. setiastro/saspro/translations/saspro_uk.qm +0 -0
  263. setiastro/saspro/translations/saspro_uk.ts +15248 -0
  264. setiastro/saspro/translations/saspro_zh.qm +0 -0
  265. setiastro/saspro/translations/saspro_zh.ts +15289 -0
  266. setiastro/saspro/translations/sw_translations.py +3897 -0
  267. setiastro/saspro/translations/uk_translations.py +3929 -0
  268. setiastro/saspro/translations/zh_translations.py +3910 -0
  269. setiastro/saspro/versioning.py +77 -0
  270. setiastro/saspro/view_bundle.py +20 -17
  271. setiastro/saspro/wavescale_hdr.py +54 -33
  272. setiastro/saspro/wavescale_hdr_preset.py +6 -5
  273. setiastro/saspro/wavescalede.py +54 -31
  274. setiastro/saspro/wavescalede_preset.py +9 -7
  275. setiastro/saspro/whitebalance.py +58 -22
  276. setiastro/saspro/widgets/common_utilities.py +12 -11
  277. setiastro/saspro/widgets/minigame/game.js +991 -0
  278. setiastro/saspro/widgets/minigame/index.html +53 -0
  279. setiastro/saspro/widgets/minigame/style.css +241 -0
  280. setiastro/saspro/widgets/preview_dialogs.py +8 -8
  281. setiastro/saspro/widgets/resource_monitor.py +263 -0
  282. setiastro/saspro/widgets/spinboxes.py +18 -0
  283. setiastro/saspro/widgets/wavelet_utils.py +52 -20
  284. setiastro/saspro/wimi.py +7996 -0
  285. setiastro/saspro/wims.py +578 -0
  286. setiastro/saspro/window_shelf.py +2 -2
  287. {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/METADATA +15 -3
  288. setiastrosuitepro-1.6.4.post1.dist-info/RECORD +368 -0
  289. setiastrosuitepro-1.6.0.dist-info/RECORD +0 -174
  290. {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/WHEEL +0 -0
  291. {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/entry_points.txt +0 -0
  292. {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/licenses/LICENSE +0 -0
  293. {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/licenses/license.txt +0 -0
setiastro/saspro/sfcc.py CHANGED
@@ -195,7 +195,7 @@ def compute_gradient_map(sources, delta_flux, shape, method="poly2"):
195
195
  class SaspViewer(QMainWindow):
196
196
  def __init__(self, sasp_data_path: str, user_custom_path: str):
197
197
  super().__init__()
198
- self.setWindowTitle("SASP Viewer (Pickles + RGB Responses)")
198
+ self.setWindowTitle(self.tr("SASP Viewer (Pickles + RGB Responses)"))
199
199
 
200
200
  self.base_hdul = fits.open(sasp_data_path, mode="readonly", memmap=False)
201
201
  self.custom_hdul = fits.open(user_custom_path, mode="readonly", memmap=False)
@@ -220,24 +220,24 @@ class SaspViewer(QMainWindow):
220
220
  vbox = QVBoxLayout(); central.setLayout(vbox)
221
221
 
222
222
  row = QHBoxLayout(); vbox.addLayout(row)
223
- row.addWidget(QLabel("Star Template:"))
223
+ row.addWidget(QLabel(self.tr("Star Template:")))
224
224
  self.star_combo = QComboBox(); self.star_combo.addItems(self.pickles_templates); row.addWidget(self.star_combo)
225
- row.addWidget(QLabel("R-Filter:"))
225
+ row.addWidget(QLabel(self.tr("R-Filter:")))
226
226
  self.r_filter_combo = QComboBox(); self.r_filter_combo.addItems(self.rgb_filter_choices); row.addWidget(self.r_filter_combo)
227
- row.addWidget(QLabel("G-Filter:"))
227
+ row.addWidget(QLabel(self.tr("G-Filter:")))
228
228
  self.g_filter_combo = QComboBox(); self.g_filter_combo.addItems(self.rgb_filter_choices); row.addWidget(self.g_filter_combo)
229
- row.addWidget(QLabel("B-Filter:"))
229
+ row.addWidget(QLabel(self.tr("B-Filter:")))
230
230
  self.b_filter_combo = QComboBox(); self.b_filter_combo.addItems(self.rgb_filter_choices); row.addWidget(self.b_filter_combo)
231
231
 
232
232
  row2 = QHBoxLayout(); vbox.addLayout(row2)
233
- row2.addWidget(QLabel("LP/Cut Filter1:"))
233
+ row2.addWidget(QLabel(self.tr("LP/Cut Filter1:")))
234
234
  self.lp_filter_combo = QComboBox(); self.lp_filter_combo.addItems(self.rgb_filter_choices); row2.addWidget(self.lp_filter_combo)
235
- row2.addWidget(QLabel("LP/Cut Filter2:"))
235
+ row2.addWidget(QLabel(self.tr("LP/Cut Filter2:")))
236
236
  self.lp_filter_combo2 = QComboBox(); self.lp_filter_combo2.addItems(self.rgb_filter_choices); row2.addWidget(self.lp_filter_combo2)
237
- row2.addSpacing(20); row2.addWidget(QLabel("Sensor (QE):"))
237
+ row2.addSpacing(20); row2.addWidget(QLabel(self.tr("Sensor (QE):")))
238
238
  self.sens_combo = QComboBox(); self.sens_combo.addItems(self.sensor_list); row2.addWidget(self.sens_combo)
239
239
 
240
- self.plot_btn = QPushButton("Plot"); self.plot_btn.clicked.connect(self.update_plot); row.addWidget(self.plot_btn)
240
+ self.plot_btn = QPushButton(self.tr("Plot")); self.plot_btn.clicked.connect(self.update_plot); row.addWidget(self.plot_btn)
241
241
 
242
242
  self.figure = Figure(figsize=(9, 6)); self.canvas = FigureCanvas(self.figure); vbox.addWidget(self.canvas)
243
243
  self.update_plot()
@@ -309,22 +309,22 @@ class SaspViewer(QMainWindow):
309
309
  mag_texts.append(f"{color[0].upper()}→{data['filter_name']}: {mag:.2f}")
310
310
  else:
311
311
  mag_texts.append(f"{color[0].upper()}→{data['filter_name']}: N/A")
312
- title_text = " | ".join(mag_texts) if mag_texts else "No channels selected"
312
+ title_text = " | ".join(mag_texts) if mag_texts else self.tr("No channels selected")
313
313
 
314
314
  self.figure.clf()
315
315
  ax1 = self.figure.add_subplot(111)
316
316
  ax1.plot(common_wl, fl_common, color="black", linewidth=1, label=f"{star_ext} SED")
317
317
  for color, data in rgb_data.items():
318
318
  if data is not None:
319
- ax1.plot(common_wl, data["response"], color="gold", linewidth=1.5, label=f"{color.upper()} Response")
320
- ax1.set_xlim(wl_min, wl_max); ax1.set_xlabel("Wavelength (Å)")
321
- ax1.set_ylabel("Flux (erg s⁻¹ cm⁻² Å⁻¹)", color="black"); ax1.tick_params(axis="y", labelcolor="black")
319
+ ax1.plot(common_wl, data["response"], color="gold", linewidth=1.5, label=self.tr("{0} Response").format(color.upper()))
320
+ ax1.set_xlim(wl_min, wl_max); ax1.set_xlabel(self.tr("Wavelength (Å)"))
321
+ ax1.set_ylabel(self.tr("Flux (erg s⁻¹ cm⁻² Å⁻¹)"), color="black"); ax1.tick_params(axis="y", labelcolor="black")
322
322
 
323
323
  ax2 = ax1.twinx()
324
- ax2.set_ylabel("Relative Throughput", color="red"); ax2.tick_params(axis="y", labelcolor="red"); ax2.set_ylim(0.0, 1.0)
325
- if rgb_data["red"] is not None: ax2.plot(common_wl, rgb_data["red"]["T_sys"], color="red", linestyle="--", linewidth=1, label="R filter×QE")
326
- if rgb_data["green"] is not None: ax2.plot(common_wl, rgb_data["green"]["T_sys"], color="green", linestyle="--", linewidth=1, label="G filter×QE")
327
- if rgb_data["blue"] is not None: ax2.plot(common_wl, rgb_data["blue"]["T_sys"], color="blue", linestyle="--", linewidth=1, label="B filter×QE")
324
+ ax2.set_ylabel(self.tr("Relative Throughput"), color="red"); ax2.tick_params(axis="y", labelcolor="red"); ax2.set_ylim(0.0, 1.0)
325
+ if rgb_data["red"] is not None: ax2.plot(common_wl, rgb_data["red"]["T_sys"], color="red", linestyle="--", linewidth=1, label=self.tr("R filter×QE"))
326
+ if rgb_data["green"] is not None: ax2.plot(common_wl, rgb_data["green"]["T_sys"], color="green", linestyle="--", linewidth=1, label=self.tr("G filter×QE"))
327
+ if rgb_data["blue"] is not None: ax2.plot(common_wl, rgb_data["blue"]["T_sys"], color="blue", linestyle="--", linewidth=1, label=self.tr("B filter×QE"))
328
328
 
329
329
  ax1.grid(True, which="both", linestyle="--", alpha=0.3); self.figure.suptitle(title_text, fontsize=10)
330
330
  lines1, labels1 = ax1.get_legend_handles_labels(); lines2, labels2 = ax2.get_legend_handles_labels()
@@ -345,7 +345,10 @@ class SFCCDialog(QDialog):
345
345
  """
346
346
  def __init__(self, doc_manager, sasp_data_path, parent=None):
347
347
  super().__init__(parent)
348
- self.setWindowTitle("Spectral Flux Color Calibration")
348
+ self.setWindowTitle(self.tr("Spectral Flux Color Calibration"))
349
+ self.setWindowFlag(Qt.WindowType.Window, True)
350
+ self.setWindowModality(Qt.WindowModality.NonModal)
351
+ self.setModal(False)
349
352
  self.setMinimumSize(800, 600)
350
353
 
351
354
  self.doc_manager = doc_manager
@@ -353,7 +356,7 @@ class SFCCDialog(QDialog):
353
356
  self.user_custom_path = self._ensure_user_custom_fits()
354
357
  self.current_image = None
355
358
  self.current_header = None
356
- self.orientation_label = QLabel("Orientation: N/A")
359
+ self.orientation_label = QLabel(self.tr("Orientation: N/A"))
357
360
  self.sasp_viewer_window = None
358
361
  self.main_win = parent
359
362
 
@@ -457,19 +460,19 @@ class SFCCDialog(QDialog):
457
460
  layout = QVBoxLayout(self)
458
461
 
459
462
  row1 = QHBoxLayout(); layout.addLayout(row1)
460
- self.fetch_stars_btn = QPushButton("Step 1: Fetch Stars from Current View")
463
+ self.fetch_stars_btn = QPushButton(self.tr("Step 1: Fetch Stars from Current View"))
461
464
  f = self.fetch_stars_btn.font(); f.setBold(True); self.fetch_stars_btn.setFont(f)
462
465
  self.fetch_stars_btn.clicked.connect(self.fetch_stars)
463
466
  row1.addWidget(self.fetch_stars_btn)
464
467
 
465
- self.open_sasp_btn = QPushButton("Open SASP Viewer")
468
+ self.open_sasp_btn = QPushButton(self.tr("Open SASP Viewer"))
466
469
  self.open_sasp_btn.clicked.connect(self.open_sasp_viewer)
467
470
  row1.addWidget(self.open_sasp_btn)
468
471
 
469
472
  row1.addSpacing(20)
470
- row1.addWidget(QLabel("Select White Reference:"))
473
+ row1.addWidget(QLabel(self.tr("Select White Reference:")))
471
474
  self.star_combo = QComboBox()
472
- self.star_combo.addItem("Vega (A0V)", userData="A0V")
475
+ self.star_combo.addItem(self.tr("Vega (A0V)"), userData="A0V")
473
476
  for sed in getattr(self, "sed_list", []):
474
477
  if sed.upper() == "A0V": continue
475
478
  self.star_combo.addItem(sed, userData=sed)
@@ -478,32 +481,32 @@ class SFCCDialog(QDialog):
478
481
  if idx_g2v >= 0: self.star_combo.setCurrentIndex(idx_g2v)
479
482
 
480
483
  row2 = QHBoxLayout(); layout.addLayout(row2)
481
- row2.addWidget(QLabel("R Filter:"))
484
+ row2.addWidget(QLabel(self.tr("R Filter:")))
482
485
  self.r_filter_combo = QComboBox(); self.r_filter_combo.addItem("(None)"); self.r_filter_combo.addItems(self.filter_list); row2.addWidget(self.r_filter_combo)
483
- row2.addSpacing(20); row2.addWidget(QLabel("G Filter:"))
486
+ row2.addSpacing(20); row2.addWidget(QLabel(self.tr("G Filter:")))
484
487
  self.g_filter_combo = QComboBox(); self.g_filter_combo.addItem("(None)"); self.g_filter_combo.addItems(self.filter_list); row2.addWidget(self.g_filter_combo)
485
- row2.addSpacing(20); row2.addWidget(QLabel("B Filter:"))
488
+ row2.addSpacing(20); row2.addWidget(QLabel(self.tr("B Filter:")))
486
489
  self.b_filter_combo = QComboBox(); self.b_filter_combo.addItem("(None)"); self.b_filter_combo.addItems(self.filter_list); row2.addWidget(self.b_filter_combo)
487
490
 
488
491
  row3 = QHBoxLayout(); layout.addLayout(row3)
489
492
  row3.addStretch()
490
- row3.addWidget(QLabel("Sensor (QE):"))
493
+ row3.addWidget(QLabel(self.tr("Sensor (QE):")))
491
494
  self.sens_combo = QComboBox(); self.sens_combo.addItem("(None)"); self.sens_combo.addItems(self.sensor_list); row3.addWidget(self.sens_combo)
492
- row3.addSpacing(20); row3.addWidget(QLabel("LP/Cut Filter1:"))
495
+ row3.addSpacing(20); row3.addWidget(QLabel(self.tr("LP/Cut Filter1:")))
493
496
  self.lp_filter_combo = QComboBox(); self.lp_filter_combo.addItem("(None)"); self.lp_filter_combo.addItems(self.filter_list); row3.addWidget(self.lp_filter_combo)
494
- row3.addSpacing(20); row3.addWidget(QLabel("LP/Cut Filter2:"))
497
+ row3.addSpacing(20); row3.addWidget(QLabel(self.tr("LP/Cut Filter2:")))
495
498
  self.lp_filter_combo2 = QComboBox(); self.lp_filter_combo2.addItem("(None)"); self.lp_filter_combo2.addItems(self.filter_list); row3.addWidget(self.lp_filter_combo2)
496
499
  row3.addStretch()
497
500
 
498
501
  row4 = QHBoxLayout(); layout.addLayout(row4)
499
- self.run_spcc_btn = QPushButton("Step 2: Run Color Calibration")
502
+ self.run_spcc_btn = QPushButton(self.tr("Step 2: Run Color Calibration"))
500
503
  f2 = self.run_spcc_btn.font(); f2.setBold(True); self.run_spcc_btn.setFont(f2)
501
504
  self.run_spcc_btn.clicked.connect(self.run_spcc)
502
505
  row4.addWidget(self.run_spcc_btn)
503
506
 
504
- self.neutralize_chk = QCheckBox("Background Neutralization"); self.neutralize_chk.setChecked(True); row4.addWidget(self.neutralize_chk)
507
+ self.neutralize_chk = QCheckBox(self.tr("Background Neutralization")); self.neutralize_chk.setChecked(True); row4.addWidget(self.neutralize_chk)
505
508
 
506
- self.run_grad_btn = QPushButton("Run Gradient Extraction (Beta)")
509
+ self.run_grad_btn = QPushButton(self.tr("Run Gradient Extraction (Beta)"))
507
510
  f3 = self.run_grad_btn.font(); f3.setBold(True); self.run_grad_btn.setFont(f3)
508
511
  self.run_grad_btn.clicked.connect(self.run_gradient_extraction)
509
512
  row4.addWidget(self.run_grad_btn)
@@ -512,7 +515,7 @@ class SFCCDialog(QDialog):
512
515
  row4.addWidget(self.grad_method_combo)
513
516
 
514
517
  row4.addSpacing(15)
515
- row4.addWidget(QLabel("Star detect σ:"))
518
+ row4.addWidget(QLabel(self.tr("Star detect σ:")))
516
519
  self.sep_thr_spin = QSpinBox()
517
520
  self.sep_thr_spin.setRange(2, 50) # should be enough
518
521
  self.sep_thr_spin.setValue(5) # our current hardcoded value
@@ -520,17 +523,17 @@ class SFCCDialog(QDialog):
520
523
  row4.addWidget(self.sep_thr_spin)
521
524
 
522
525
  row4.addStretch()
523
- self.add_curve_btn = QPushButton("Add Custom Filter/Sensor Curve…")
526
+ self.add_curve_btn = QPushButton(self.tr("Add Custom Filter/Sensor Curve…"))
524
527
  self.add_curve_btn.clicked.connect(self.add_custom_curve); row4.addWidget(self.add_curve_btn)
525
- self.remove_curve_btn = QPushButton("Remove Filter/Sensor Curve…")
528
+ self.remove_curve_btn = QPushButton(self.tr("Remove Filter/Sensor Curve…"))
526
529
  self.remove_curve_btn.clicked.connect(self.remove_custom_curve); row4.addWidget(self.remove_curve_btn)
527
530
  row4.addStretch()
528
- self.close_btn = QPushButton("Close"); self.close_btn.clicked.connect(self.close); row4.addWidget(self.close_btn)
531
+ self.close_btn = QPushButton(self.tr("Close")); self.close_btn.clicked.connect(self.close); row4.addWidget(self.close_btn)
529
532
 
530
533
  self.count_label = QLabel(""); layout.addWidget(self.count_label)
531
534
 
532
535
  self.figure = Figure(figsize=(6, 4)); self.canvas = FigureCanvas(self.figure); self.canvas.setVisible(False); layout.addWidget(self.canvas, stretch=1)
533
- self.reset_btn = QPushButton("Reset View/Close"); self.reset_btn.clicked.connect(self.close); layout.addWidget(self.reset_btn)
536
+ self.reset_btn = QPushButton(self.tr("Reset View/Close")); self.reset_btn.clicked.connect(self.close); layout.addWidget(self.reset_btn)
534
537
 
535
538
  # hide gradient controls by default (enable if you like)
536
539
  self.run_grad_btn.hide(); self.grad_method_combo.hide()
@@ -610,9 +613,9 @@ class SFCCDialog(QDialog):
610
613
 
611
614
  def get_calibration_points(self, rgb_img: np.ndarray):
612
615
  print("\nClick three calibration points: BL (λmin,0), BR (λmax,0), TL (λmin,1)")
613
- fig, ax = plt.subplots(figsize=(8, 5)); ax.imshow(rgb_img); ax.set_title("Click 3 points, then close")
616
+ fig, ax = plt.subplots(figsize=(8, 5)); ax.imshow(rgb_img); ax.set_title(self.tr("Click 3 points, then close"))
614
617
  pts = plt.ginput(3, timeout=-1); plt.close(fig)
615
- if len(pts) != 3: raise RuntimeError("Need exactly three clicks for calibration.")
618
+ if len(pts) != 3: raise RuntimeError(self.tr("Need exactly three clicks for calibration."))
616
619
  return pts[0], pts[1], pts[2]
617
620
 
618
621
  def build_transforms(self, px_bl, py_bl, px_br, py_br, px_tl, py_tl, λ_min, λ_max, resp_min, resp_max):
@@ -644,10 +647,10 @@ class SFCCDialog(QDialog):
644
647
  return df.sort_values("wavelength_nm").reset_index(drop=True)
645
648
 
646
649
  def _query_name_channel(self):
647
- name_str, ok1 = QInputDialog.getText(self, "Curve Name", "Enter curve name (EXTNAME):")
650
+ name_str, ok1 = QInputDialog.getText(self, self.tr("Curve Name"), self.tr("Enter curve name (EXTNAME):"))
648
651
  if not (ok1 and name_str.strip()): return False, None, None
649
652
  extname = name_str.strip().upper().replace(" ", "_")
650
- ch_str, ok2 = QInputDialog.getText(self, "Channel", "Enter channel (R,G,B or Q for sensor):")
653
+ ch_str, ok2 = QInputDialog.getText(self, self.tr("Channel"), self.tr("Enter channel (R,G,B or Q for sensor):"))
651
654
  if not (ok2 and ch_str.strip()): return False, None, None
652
655
  return True, extname, ch_str.strip().upper()
653
656
 
@@ -662,16 +665,16 @@ class SFCCDialog(QDialog):
662
665
  hdul.append(new_hdu); hdul.flush()
663
666
 
664
667
  def add_custom_curve(self):
665
- msg = QMessageBox(self); msg.setWindowTitle("Add Custom Curve"); msg.setText("Choose how to add the curve:")
666
- csv_btn = msg.addButton("Import CSV", QMessageBox.ButtonRole.AcceptRole)
667
- img_btn = msg.addButton("Digitize Image", QMessageBox.ButtonRole.AcceptRole)
668
+ msg = QMessageBox(self); msg.setWindowTitle(self.tr("Add Custom Curve")); msg.setText(self.tr("Choose how to add the curve:"))
669
+ csv_btn = msg.addButton(self.tr("Import CSV"), QMessageBox.ButtonRole.AcceptRole)
670
+ img_btn = msg.addButton(self.tr("Digitize Image"), QMessageBox.ButtonRole.AcceptRole)
668
671
  cancel_btn = msg.addButton(QMessageBox.StandardButton.Cancel)
669
672
  msg.exec()
670
673
  if msg.clickedButton() == csv_btn: self._import_curve_from_csv()
671
674
  elif msg.clickedButton() == img_btn: self._digitize_curve_from_image()
672
675
 
673
676
  def _import_curve_from_csv(self):
674
- csv_path, _ = QFileDialog.getOpenFileName(self, "Select 2-column CSV (λ_nm, response)", "", "CSV Files (*.csv);;All Files (*)")
677
+ csv_path, _ = QFileDialog.getOpenFileName(self, self.tr("Select 2-column CSV (λ_nm, response)"), "", "CSV Files (*.csv);;All Files (*)")
675
678
  if not csv_path: return
676
679
  try:
677
680
  df = (pd.read_csv(csv_path, comment="#", header=None).iloc[:, :2].dropna())
@@ -683,19 +686,19 @@ class SFCCDialog(QDialog):
683
686
  df.columns = ["wavelength_nm","response"]
684
687
  wl_nm = df["wavelength_nm"].astype(float).to_numpy(); tp = df["response"].astype(float).to_numpy()
685
688
  except Exception as e2:
686
- QMessageBox.critical(self, "CSV Error", f"Could not read CSV:\n{e2}"); return
689
+ QMessageBox.critical(self, self.tr("CSV Error"), self.tr("Could not read CSV:\n{0}").format(e2)); return
687
690
  except Exception as e:
688
- QMessageBox.critical(self, "CSV Error", f"Could not read CSV:\n{e}"); return
691
+ QMessageBox.critical(self, self.tr("CSV Error"), self.tr("Could not read CSV:\n{0}").format(e)); return
689
692
 
690
693
  ok, extname_base, channel_val = self._query_name_channel()
691
694
  if not ok: return
692
695
  wl_ang = (wl_nm * 10.0).astype(np.float32); tr_final = tp.astype(np.float32)
693
696
  self._append_curve_hdu(wl_ang, tr_final, extname_base, "SENSOR" if channel_val=="Q" else "FILTER", f"CSV:{os.path.basename(csv_path)}")
694
697
  self._reload_hdu_lists(); self.refresh_filter_sensor_lists()
695
- QMessageBox.information(self, "Done", f"CSV curve '{extname_base}' added.")
698
+ QMessageBox.information(self, self.tr("Done"), self.tr("CSV curve '{0}' added.").format(extname_base))
696
699
 
697
700
  def _digitize_curve_from_image(self):
698
- img_path_str, _ = QFileDialog.getOpenFileName(self, "Select Curve Image to Digitize", "", "Images (*.png *.jpg *.jpeg *.bmp);;All Files (*)")
701
+ img_path_str, _ = QFileDialog.getOpenFileName(self, self.tr("Select Curve Image to Digitize"), "", "Images (*.png *.jpg *.jpeg *.bmp);;All Files (*)")
699
702
  if not img_path_str: return
700
703
  img_filename = os.path.basename(img_path_str)
701
704
  try:
@@ -703,20 +706,20 @@ class SFCCDialog(QDialog):
703
706
  if bgr is None: raise RuntimeError(f"cv2.imread returned None for '{img_path_str}'")
704
707
  rgb_img = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB); gray_img = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
705
708
  except Exception as e:
706
- QMessageBox.critical(self, "Error", f"Could not load image:\n{e}"); return
709
+ QMessageBox.critical(self, self.tr("Error"), self.tr("Could not load image:\n{0}").format(e)); return
707
710
 
708
711
  try:
709
712
  (px_bl, py_bl), (px_br, py_br), (px_tl, py_tl) = self.get_calibration_points(rgb_img)
710
713
  except Exception as e:
711
- QMessageBox.critical(self, "Digitization Error", str(e)); return
714
+ QMessageBox.critical(self, self.tr("Digitization Error"), str(e)); return
712
715
 
713
- λ_min_str, ok1 = QInputDialog.getText(self, "λ_min", "Enter λ_min (in nm):")
714
- λ_max_str, ok2 = QInputDialog.getText(self, "λ_max", "Enter λ_max (in nm):")
716
+ λ_min_str, ok1 = QInputDialog.getText(self, self.tr("λ_min"), self.tr("Enter λ_min (in nm):"))
717
+ λ_max_str, ok2 = QInputDialog.getText(self, self.tr("λ_max"), self.tr("Enter λ_max (in nm):"))
715
718
  if not (ok1 and ok2 and λ_min_str.strip() and λ_max_str.strip()): return
716
719
  try:
717
720
  λ_min = float(λ_min_str); λ_max = float(λ_max_str)
718
721
  except ValueError:
719
- QMessageBox.critical(self, "Input Error", "λ_min and λ_max must be numbers."); return
722
+ QMessageBox.critical(self, self.tr("Input Error"), self.tr("λ_min and λ_max must be numbers.")); return
720
723
 
721
724
  ok, extname_base, channel_val = self._query_name_channel()
722
725
  if not ok: return
@@ -725,7 +728,7 @@ class SFCCDialog(QDialog):
725
728
  try:
726
729
  df_curve = self.extract_curve(gray_img, px_to_λ, py_to_resp, λ_min, λ_max, threshold=50)
727
730
  except Exception as e:
728
- QMessageBox.critical(self, "Extraction Error", str(e)); return
731
+ QMessageBox.critical(self, self.tr("Extraction Error"), str(e)); return
729
732
 
730
733
  df_curve["wl_int"] = df_curve["wavelength_nm"].round().astype(int)
731
734
  grp = (df_curve.groupby("wl_int")["response"].median().reset_index().sort_values("wl_int"))
@@ -734,21 +737,21 @@ class SFCCDialog(QDialog):
734
737
  try:
735
738
  tr_corr, _ = self.interpolate_bad_points(wl, tr)
736
739
  except Exception as e:
737
- QMessageBox.critical(self, "Interpolation Error", str(e)); return
740
+ QMessageBox.critical(self, self.tr("Interpolation Error"), str(e)); return
738
741
 
739
742
  tr_smoothed = self.smooth_curve(tr_corr, window_size=5)
740
743
  wl_ang = (wl.astype(float) * 10.0).astype(np.float32); tr_final = tr_smoothed.astype(np.float32)
741
744
  self._append_curve_hdu(wl_ang, tr_final, extname_base, "SENSOR" if channel_val=="Q" else "FILTER", f"UserDefined:{img_filename}")
742
745
  self._reload_hdu_lists(); self.refresh_filter_sensor_lists()
743
- QMessageBox.information(self, "Done", f"Added curve '{extname_base}'.")
746
+ QMessageBox.information(self, self.tr("Done"), self.tr("Added curve '{0}'.").format(extname_base))
744
747
 
745
748
  def remove_custom_curve(self):
746
749
  all_curves = self.filter_list + self.sensor_list
747
750
  if not all_curves:
748
- QMessageBox.information(self, "Remove Curve", "No custom curves to remove."); return
749
- curve, ok = QInputDialog.getItem(self, "Remove Curve", "Select a FILTER or SENSOR curve to delete:", all_curves, 0, False)
751
+ QMessageBox.information(self, self.tr("Remove Curve"), self.tr("No custom curves to remove.")); return
752
+ curve, ok = QInputDialog.getItem(self, self.tr("Remove Curve"), self.tr("Select a FILTER or SENSOR curve to delete:"), all_curves, 0, False)
750
753
  if not ok or not curve: return
751
- reply = QMessageBox.question(self, "Confirm Deletion", f"Delete '{curve}'?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
754
+ reply = QMessageBox.question(self, self.tr("Confirm Deletion"), self.tr("Delete '{0}'?").format(curve), QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
752
755
  if reply != QMessageBox.StandardButton.Yes: return
753
756
 
754
757
  temp_path = self.user_custom_path + ".tmp"
@@ -768,7 +771,7 @@ class SFCCDialog(QDialog):
768
771
  QMessageBox.critical(self, "Write Error", f"Could not remove curve:\n{e}"); return
769
772
 
770
773
  self._reload_hdu_lists(); self.refresh_filter_sensor_lists()
771
- QMessageBox.information(self, "Removed", f"Deleted curve '{curve}'.")
774
+ QMessageBox.information(self, self.tr("Removed"), self.tr("Deleted curve '{0}'.").format(curve))
772
775
 
773
776
  def refresh_filter_sensor_lists(self):
774
777
  self._reload_hdu_lists()
@@ -1155,18 +1158,56 @@ class SFCCDialog(QDialog):
1155
1158
  diag_meas_BG, diag_exp_BG = [], []
1156
1159
  enriched = []
1157
1160
 
1161
+ # --- Optimization: Pre-calculate integrals for unique templates ---
1162
+ unique_simbad_types = set(m["template"] for m in raw_matches)
1163
+
1164
+ # Map simbad_type -> pickles_template_name
1165
+ simbad_to_pickles = {}
1166
+ pickles_templates_needed = set()
1167
+
1168
+ for sp in unique_simbad_types:
1169
+ cands = pickles_match_for_simbad(sp, getattr(self, "pickles_templates", []))
1170
+ if cands:
1171
+ pickles_name = cands[0]
1172
+ simbad_to_pickles[sp] = pickles_name
1173
+ pickles_templates_needed.add(pickles_name)
1174
+
1175
+ # Pre-calc integrals for each unique Pickles template
1176
+ # Cache structure: template_name -> (S_sr, S_sg, S_sb)
1177
+ template_integrals = {}
1178
+
1179
+ # Cache for load_sed to avoid re-reading even across different calls if desired,
1180
+ # but here we just optimize the loop.
1181
+
1182
+ for pname in pickles_templates_needed:
1183
+ try:
1184
+ wl_s, fl_s = load_sed(pname)
1185
+ fs_i = np.interp(wl_grid, wl_s, fl_s, left=0., right=0.)
1186
+
1187
+ S_sr = np.trapezoid(fs_i * T_sys_R, x=wl_grid)
1188
+ S_sg = np.trapezoid(fs_i * T_sys_G, x=wl_grid)
1189
+ S_sb = np.trapezoid(fs_i * T_sys_B, x=wl_grid)
1190
+
1191
+ template_integrals[pname] = (S_sr, S_sg, S_sb)
1192
+ except Exception as e:
1193
+ print(f"[SFCC] Warning: failed to load/integrate template {pname}: {e}")
1194
+
1195
+ # --- Main Match Loop ---
1158
1196
  for m in raw_matches:
1159
1197
  xi, yi, sp = m["x_pix"], m["y_pix"], m["template"]
1160
1198
  Rm = float(base[yi, xi, 0]); Gm = float(base[yi, xi, 1]); Bm = float(base[yi, xi, 2])
1161
1199
  if Gm <= 0: continue
1162
1200
 
1163
- cands = pickles_match_for_simbad(sp, getattr(self, "pickles_templates", []))
1164
- if not cands: continue
1165
- wl_s, fl_s = load_sed(cands[0])
1166
- fs_i = np.interp(wl_grid, wl_s, fl_s, left=0., right=0.)
1167
- S_sr = np.trapezoid(fs_i * T_sys_R, x=wl_grid)
1168
- S_sg = np.trapezoid(fs_i * T_sys_G, x=wl_grid)
1169
- S_sb = np.trapezoid(fs_i * T_sys_B, x=wl_grid)
1201
+ # 1. Resolve Simbad -> Pickles
1202
+ pname = simbad_to_pickles.get(sp)
1203
+ if not pname: continue
1204
+
1205
+ # 2. Retrieve pre-calced integrals
1206
+ integrals = template_integrals.get(pname)
1207
+ if not integrals: continue
1208
+
1209
+ S_sr, S_sg, S_sb = integrals
1210
+
1170
1211
  if S_sg <= 0: continue
1171
1212
 
1172
1213
  exp_RG = S_sr / S_sg; exp_BG = S_sb / S_sg
@@ -1180,7 +1221,8 @@ class SFCCDialog(QDialog):
1180
1221
  "S_star_R": S_sr, "S_star_G": S_sg, "S_star_B": S_sb,
1181
1222
  "exp_RG": exp_RG, "exp_BG": exp_BG
1182
1223
  })
1183
- self._last_matched = enriched # <-- missing in SASpro
1224
+
1225
+ self._last_matched = enriched
1184
1226
  diag_meas_RG = np.array(diag_meas_RG); diag_exp_RG = np.array(diag_exp_RG)
1185
1227
  diag_meas_BG = np.array(diag_meas_BG); diag_exp_BG = np.array(diag_exp_BG)
1186
1228
  if diag_meas_RG.size == 0 or diag_meas_BG.size == 0:
@@ -1276,7 +1318,10 @@ class SFCCDialog(QDialog):
1276
1318
  })
1277
1319
 
1278
1320
  self.doc_manager.update_active_document(
1279
- calibrated, metadata=new_meta, step_name="SFCC Calibrated"
1321
+ calibrated,
1322
+ metadata=new_meta,
1323
+ step_name="SFCC Calibrated",
1324
+ doc=doc, # 👈 pin to the document we started from
1280
1325
  )
1281
1326
 
1282
1327
  self.count_label.setText(f"Applied SFCC color calibration using {n_stars} stars")
@@ -1384,8 +1429,10 @@ class SFCCDialog(QDialog):
1384
1429
  new_meta["ColourGradRemoved"] = True
1385
1430
 
1386
1431
  self.doc_manager.update_active_document(
1387
- corrected, metadata=new_meta,
1388
- step_name="Colour-Gradient (star spectra, ¼-res fit)"
1432
+ corrected,
1433
+ metadata=new_meta,
1434
+ step_name="Colour-Gradient (star spectra, ¼-res fit)",
1435
+ doc=doc, # 👈 same idea
1389
1436
  )
1390
1437
  self.count_label.setText("Chromatic gradient removed ✓")
1391
1438
  QApplication.processEvents()