setiastrosuitepro 1.7.1.post2__py3-none-any.whl → 1.7.4__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.
- setiastro/images/3dplanet.png +0 -0
- setiastro/saspro/__init__.py +20 -8
- setiastro/saspro/__main__.py +349 -290
- setiastro/saspro/_generated/build_info.py +2 -2
- setiastro/saspro/abe.py +4 -4
- setiastro/saspro/autostretch.py +29 -18
- setiastro/saspro/doc_manager.py +4 -1
- setiastro/saspro/gui/main_window.py +46 -7
- setiastro/saspro/gui/mixins/file_mixin.py +6 -2
- setiastro/saspro/gui/mixins/menu_mixin.py +1 -0
- setiastro/saspro/gui/mixins/toolbar_mixin.py +9 -2
- setiastro/saspro/imageops/serloader.py +101 -17
- setiastro/saspro/layers.py +186 -10
- setiastro/saspro/layers_dock.py +198 -5
- setiastro/saspro/legacy/image_manager.py +10 -4
- setiastro/saspro/legacy/numba_utils.py +301 -119
- setiastro/saspro/numba_utils.py +998 -270
- setiastro/saspro/ops/settings.py +6 -6
- setiastro/saspro/pixelmath.py +1 -1
- setiastro/saspro/planetprojection.py +4059 -0
- setiastro/saspro/resources.py +2 -0
- setiastro/saspro/save_options.py +45 -13
- setiastro/saspro/ser_stack_config.py +21 -1
- setiastro/saspro/ser_stacker.py +8 -2
- setiastro/saspro/ser_stacker_dialog.py +37 -10
- setiastro/saspro/ser_tracking.py +57 -35
- setiastro/saspro/serviewer.py +164 -16
- setiastro/saspro/sfcc.py +14 -8
- setiastro/saspro/stacking_suite.py +292 -111
- setiastro/saspro/subwindow.py +64 -36
- setiastro/saspro/translations/all_source_strings.json +2 -2
- setiastro/saspro/translations/ar_translations.py +3 -3
- setiastro/saspro/translations/de_translations.py +2 -2
- setiastro/saspro/translations/es_translations.py +2 -2
- setiastro/saspro/translations/fr_translations.py +2 -2
- setiastro/saspro/translations/hi_translations.py +2 -2
- setiastro/saspro/translations/it_translations.py +2 -2
- setiastro/saspro/translations/ja_translations.py +2 -2
- setiastro/saspro/translations/pt_translations.py +2 -2
- setiastro/saspro/translations/ru_translations.py +2 -2
- setiastro/saspro/translations/saspro_ar.ts +2 -2
- setiastro/saspro/translations/saspro_de.ts +4 -4
- setiastro/saspro/translations/saspro_es.ts +2 -2
- setiastro/saspro/translations/saspro_fr.ts +2 -2
- setiastro/saspro/translations/saspro_hi.ts +2 -2
- setiastro/saspro/translations/saspro_it.ts +4 -4
- setiastro/saspro/translations/saspro_ja.ts +2 -2
- setiastro/saspro/translations/saspro_pt.ts +2 -2
- setiastro/saspro/translations/saspro_ru.ts +2 -2
- setiastro/saspro/translations/saspro_sw.ts +2 -2
- setiastro/saspro/translations/saspro_uk.ts +2 -2
- setiastro/saspro/translations/saspro_zh.ts +2 -2
- setiastro/saspro/translations/sw_translations.py +2 -2
- setiastro/saspro/translations/uk_translations.py +2 -2
- setiastro/saspro/translations/zh_translations.py +2 -2
- setiastro/saspro/window_shelf.py +62 -1
- {setiastrosuitepro-1.7.1.post2.dist-info → setiastrosuitepro-1.7.4.dist-info}/METADATA +1 -1
- {setiastrosuitepro-1.7.1.post2.dist-info → setiastrosuitepro-1.7.4.dist-info}/RECORD +62 -60
- {setiastrosuitepro-1.7.1.post2.dist-info → setiastrosuitepro-1.7.4.dist-info}/entry_points.txt +1 -1
- {setiastrosuitepro-1.7.1.post2.dist-info → setiastrosuitepro-1.7.4.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.7.1.post2.dist-info → setiastrosuitepro-1.7.4.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.7.1.post2.dist-info → setiastrosuitepro-1.7.4.dist-info}/licenses/license.txt +0 -0
setiastro/saspro/serviewer.py
CHANGED
|
@@ -9,7 +9,7 @@ from PyQt6.QtGui import QImage, QPixmap, QPainter, QPen, QColor
|
|
|
9
9
|
from PyQt6.QtWidgets import (
|
|
10
10
|
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QFileDialog,
|
|
11
11
|
QScrollArea, QSlider, QCheckBox, QGroupBox, QFormLayout, QSpinBox,
|
|
12
|
-
QMessageBox, QRubberBand, QComboBox, QDoubleSpinBox
|
|
12
|
+
QMessageBox, QRubberBand, QComboBox, QDoubleSpinBox, QWidget
|
|
13
13
|
)
|
|
14
14
|
|
|
15
15
|
from setiastro.saspro.imageops.serloader import open_planetary_source, PlanetaryFrameSource
|
|
@@ -323,9 +323,80 @@ class SERViewer(QDialog):
|
|
|
323
323
|
|
|
324
324
|
self.btn_stack = QPushButton("Open Stacker…", self)
|
|
325
325
|
self.btn_stack.setEnabled(False) # enabled once SER loaded
|
|
326
|
+
self.chk_planet_norm = QCheckBox("Normalize for planetary centroid detect")
|
|
327
|
+
self.chk_planet_norm.setChecked(True)
|
|
328
|
+
self.chk_planet_norm.setToolTip("Detection-only normalization (does not change stacking pixels). Helps dim planets.")
|
|
329
|
+
|
|
330
|
+
self.spin_planet_thresh = QDoubleSpinBox()
|
|
331
|
+
self.spin_planet_thresh.setRange(50.0, 99.9)
|
|
332
|
+
self.spin_planet_thresh.setDecimals(1)
|
|
333
|
+
self.spin_planet_thresh.setSingleStep(0.5)
|
|
334
|
+
self.spin_planet_thresh.setValue(92.0)
|
|
335
|
+
self.spin_planet_thresh.setToolTip("Percentile used to threshold the blob for centroid detection.")
|
|
336
|
+
self.spin_planet_min = QDoubleSpinBox()
|
|
337
|
+
self.spin_planet_min.setRange(0.0, 0.5) # abs floor in [0..1]
|
|
338
|
+
self.spin_planet_min.setDecimals(3)
|
|
339
|
+
self.spin_planet_min.setSingleStep(0.005)
|
|
340
|
+
self.spin_planet_min.setValue(0.02)
|
|
341
|
+
self.spin_planet_min.setToolTip(
|
|
342
|
+
"Minimum normalized intensity (0..1) allowed for detection thresholding.\n"
|
|
343
|
+
"If percentile threshold is too low on dim planets, this prevents the mask from vanishing.\n"
|
|
344
|
+
"Typical: 0.01–0.05."
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
self.spin_planet_smooth = QDoubleSpinBox()
|
|
348
|
+
self.spin_planet_smooth.setRange(0.0, 10.0)
|
|
349
|
+
self.spin_planet_smooth.setDecimals(2)
|
|
350
|
+
self.spin_planet_smooth.setSingleStep(0.25)
|
|
351
|
+
self.spin_planet_smooth.setValue(1.5)
|
|
352
|
+
self.spin_planet_smooth.setToolTip("Gaussian blur sigma used before thresholding. 1.0–2.0 is typical.")
|
|
353
|
+
|
|
354
|
+
self.spin_norm_lo = QDoubleSpinBox(); self.spin_norm_lo.setRange(0.0, 20.0); self.spin_norm_lo.setValue(1.0)
|
|
355
|
+
self.spin_norm_hi = QDoubleSpinBox(); self.spin_norm_hi.setRange(80.0, 100.0); self.spin_norm_hi.setValue(99.5)
|
|
356
|
+
|
|
357
|
+
# -----------------------------
|
|
358
|
+
# Advanced detection settings
|
|
359
|
+
# -----------------------------
|
|
360
|
+
adv = QGroupBox("Advanced detection settings", self)
|
|
361
|
+
adv.setCheckable(True)
|
|
362
|
+
adv.setChecked(False)
|
|
363
|
+
|
|
364
|
+
adv_body = QWidget(adv) # <- content container
|
|
365
|
+
adv_form = QFormLayout(adv_body)
|
|
366
|
+
adv_form.setContentsMargins(8, 8, 8, 8)
|
|
367
|
+
adv_form.setVerticalSpacing(6)
|
|
368
|
+
adv_form.setHorizontalSpacing(10)
|
|
369
|
+
|
|
370
|
+
adv_form.addRow("", self.chk_planet_norm)
|
|
371
|
+
adv_form.addRow("Planet detect thresh (%)", self.spin_planet_thresh)
|
|
372
|
+
adv_form.addRow("Norm low pct", self.spin_norm_lo)
|
|
373
|
+
adv_form.addRow("Norm high pct", self.spin_norm_hi)
|
|
374
|
+
adv_form.addRow("Planet min val", self.spin_planet_min)
|
|
375
|
+
adv_form.addRow("Planet smooth σ", self.spin_planet_smooth)
|
|
376
|
+
|
|
377
|
+
# Put the body into the groupbox layout
|
|
378
|
+
adv_layout = QVBoxLayout(adv)
|
|
379
|
+
adv_layout.setContentsMargins(8, 8, 8, 8)
|
|
380
|
+
adv_layout.addWidget(adv_body)
|
|
381
|
+
|
|
382
|
+
# show/hide only the body
|
|
383
|
+
adv_body.setVisible(False)
|
|
384
|
+
adv.toggled.connect(adv_body.setVisible)
|
|
326
385
|
|
|
327
386
|
sform.addRow("Tracking", self.cmb_track)
|
|
328
387
|
sform.addRow("Keep %", self.spin_keep)
|
|
388
|
+
|
|
389
|
+
# instead of adding the detection rows directly:
|
|
390
|
+
# sform.addRow("", self.chk_planet_norm)
|
|
391
|
+
# sform.addRow("Planet detect thresh (%)", self.spin_planet_thresh)
|
|
392
|
+
# sform.addRow("Norm low pct", self.spin_norm_lo)
|
|
393
|
+
# sform.addRow("Norm high pct", self.spin_norm_hi)
|
|
394
|
+
# sform.addRow("Planet min val", self.spin_planet_min)
|
|
395
|
+
# sform.addRow("Planet smooth σ", self.spin_planet_smooth)
|
|
396
|
+
|
|
397
|
+
# add the advanced groupbox as a single row spanning the form
|
|
398
|
+
sform.addRow(adv)
|
|
399
|
+
|
|
329
400
|
sform.addRow("", self.lbl_anchor)
|
|
330
401
|
sform.addRow("", self.btn_stack)
|
|
331
402
|
|
|
@@ -354,7 +425,8 @@ class SERViewer(QDialog):
|
|
|
354
425
|
w.toggled.connect(self._refresh)
|
|
355
426
|
if hasattr(w, "valueChanged"):
|
|
356
427
|
w.valueChanged.connect(self._refresh)
|
|
357
|
-
|
|
428
|
+
for s in (self.spin_x, self.spin_y, self.spin_w, self.spin_h):
|
|
429
|
+
s.valueChanged.connect(self._sanitize_roi_controls)
|
|
358
430
|
self.cmb_track.currentIndexChanged.connect(self._on_track_mode_changed)
|
|
359
431
|
self.btn_stack.clicked.connect(self._open_stacker_clicked)
|
|
360
432
|
self.cmb_bayer.currentIndexChanged.connect(self._refresh)
|
|
@@ -746,16 +818,23 @@ class SERViewer(QDialog):
|
|
|
746
818
|
x, y, w, h = rect_disp
|
|
747
819
|
x_full = int(rx + x)
|
|
748
820
|
y_full = int(ry + y)
|
|
821
|
+
w = int(w)
|
|
822
|
+
h = int(h)
|
|
823
|
+
|
|
824
|
+
x_full, y_full, w, h = self._even_roi(x_full, y_full, w, h)
|
|
825
|
+
|
|
749
826
|
self.spin_x.setValue(x_full)
|
|
750
827
|
self.spin_y.setValue(y_full)
|
|
751
|
-
self.spin_w.setValue(
|
|
752
|
-
self.spin_h.setValue(
|
|
828
|
+
self.spin_w.setValue(w)
|
|
829
|
+
self.spin_h.setValue(h)
|
|
753
830
|
else:
|
|
754
831
|
x, y, w, h = rect_disp
|
|
755
|
-
self.
|
|
756
|
-
|
|
757
|
-
self.
|
|
758
|
-
self.
|
|
832
|
+
x, y, w, h = self._even_roi(int(x), int(y), int(w), int(h))
|
|
833
|
+
|
|
834
|
+
self.spin_x.setValue(x)
|
|
835
|
+
self.spin_y.setValue(y)
|
|
836
|
+
self.spin_w.setValue(w)
|
|
837
|
+
self.spin_h.setValue(h)
|
|
759
838
|
|
|
760
839
|
self.chk_roi.setChecked(True)
|
|
761
840
|
self._refresh()
|
|
@@ -844,10 +923,16 @@ class SERViewer(QDialog):
|
|
|
844
923
|
track_mode=self._track_mode_value(),
|
|
845
924
|
surface_anchor=anchor,
|
|
846
925
|
debayer=debayer,
|
|
847
|
-
bayer_pattern=bp,
|
|
926
|
+
bayer_pattern=bp,
|
|
848
927
|
keep_percent=float(self.spin_keep.value()),
|
|
849
|
-
)
|
|
850
928
|
|
|
929
|
+
# ✅ planetary detect knobs
|
|
930
|
+
planet_min_val=float(self.spin_planet_min.value()),
|
|
931
|
+
planet_use_norm=bool(self.chk_planet_norm.isChecked()),
|
|
932
|
+
planet_norm_hi_pct=float(self.spin_norm_hi.value()), # <-- you already have this
|
|
933
|
+
planet_thresh_pct=float(self.spin_planet_thresh.value()),
|
|
934
|
+
planet_smooth_sigma=float(self.spin_planet_smooth.value()),
|
|
935
|
+
)
|
|
851
936
|
|
|
852
937
|
dlg.stackProduced.connect(self._on_stacker_produced)
|
|
853
938
|
dlg.show()
|
|
@@ -1090,10 +1175,10 @@ class SERViewer(QDialog):
|
|
|
1090
1175
|
dlg.setDirectory(start_dir)
|
|
1091
1176
|
dlg.setFileMode(QFileDialog.FileMode.ExistingFiles)
|
|
1092
1177
|
dlg.setNameFilters([
|
|
1093
|
-
"Planetary Sources (*.ser *.avi *.mp4 *.mov *.mkv *.png *.tif *.tiff *.jpg *.jpeg *.bmp *.webp)",
|
|
1178
|
+
"Planetary Sources (*.ser *.avi *.mp4 *.mov *.mkv *.png *.tif *.tiff *.jpg *.jpeg *.bmp *.webp *.fit *.fits)",
|
|
1094
1179
|
"SER Videos (*.ser)",
|
|
1095
1180
|
"AVI/Video (*.avi *.mp4 *.mov *.mkv)",
|
|
1096
|
-
"Images (*.png *.tif *.tiff *.jpg *.jpeg *.bmp *.webp)",
|
|
1181
|
+
"Images (*.png *.tif *.tiff *.jpg *.jpeg *.bmp *.webp *.fit *.fits)",
|
|
1097
1182
|
"All Files (*)",
|
|
1098
1183
|
])
|
|
1099
1184
|
|
|
@@ -1217,12 +1302,65 @@ class SERViewer(QDialog):
|
|
|
1217
1302
|
self._refresh()
|
|
1218
1303
|
|
|
1219
1304
|
# ---------------- rendering ----------------
|
|
1305
|
+
def _even_roi(self, x: int, y: int, w: int, h: int):
|
|
1306
|
+
"""Force ROI x,y,w,h to even numbers (preserves Bayer phase)."""
|
|
1307
|
+
if self.reader is None:
|
|
1308
|
+
return x, y, w, h
|
|
1309
|
+
|
|
1310
|
+
m = self.reader.meta
|
|
1311
|
+
W = int(m.width)
|
|
1312
|
+
H = int(m.height)
|
|
1313
|
+
|
|
1314
|
+
# Clamp first
|
|
1315
|
+
x = max(0, min(W - 1, int(x)))
|
|
1316
|
+
y = max(0, min(H - 1, int(y)))
|
|
1317
|
+
w = max(1, int(w))
|
|
1318
|
+
h = max(1, int(h))
|
|
1319
|
+
|
|
1320
|
+
# Make origin even (keep Bayer phase)
|
|
1321
|
+
x &= ~1
|
|
1322
|
+
y &= ~1
|
|
1323
|
+
|
|
1324
|
+
# Make size even
|
|
1325
|
+
w &= ~1
|
|
1326
|
+
h &= ~1
|
|
1327
|
+
if w < 2: w = 2
|
|
1328
|
+
if h < 2: h = 2
|
|
1329
|
+
|
|
1330
|
+
# Fit inside image (keep evenness)
|
|
1331
|
+
if x + w > W:
|
|
1332
|
+
w = (W - x) & ~1
|
|
1333
|
+
if w < 2:
|
|
1334
|
+
x = max(0, (W - 2) & ~1)
|
|
1335
|
+
w = 2
|
|
1336
|
+
if y + h > H:
|
|
1337
|
+
h = (H - y) & ~1
|
|
1338
|
+
if h < 2:
|
|
1339
|
+
y = max(0, (H - 2) & ~1)
|
|
1340
|
+
h = 2
|
|
1341
|
+
|
|
1342
|
+
return int(x), int(y), int(w), int(h)
|
|
1343
|
+
|
|
1344
|
+
def _sanitize_roi_controls(self):
|
|
1345
|
+
if self.reader is None:
|
|
1346
|
+
return
|
|
1347
|
+
x = int(self.spin_x.value()); y = int(self.spin_y.value())
|
|
1348
|
+
w = int(self.spin_w.value()); h = int(self.spin_h.value())
|
|
1349
|
+
ex, ey, ew, eh = self._even_roi(x, y, w, h)
|
|
1350
|
+
if (ex, ey, ew, eh) != (x, y, w, h):
|
|
1351
|
+
self.spin_x.blockSignals(True); self.spin_y.blockSignals(True)
|
|
1352
|
+
self.spin_w.blockSignals(True); self.spin_h.blockSignals(True)
|
|
1353
|
+
self.spin_x.setValue(ex); self.spin_y.setValue(ey)
|
|
1354
|
+
self.spin_w.setValue(ew); self.spin_h.setValue(eh)
|
|
1355
|
+
self.spin_x.blockSignals(False); self.spin_y.blockSignals(False)
|
|
1356
|
+
self.spin_w.blockSignals(False); self.spin_h.blockSignals(False)
|
|
1220
1357
|
|
|
1221
1358
|
def _roi_tuple(self):
|
|
1222
1359
|
if not self.chk_roi.isChecked():
|
|
1223
1360
|
return None
|
|
1224
|
-
|
|
1225
|
-
|
|
1361
|
+
x, y, w, h = (int(self.spin_x.value()), int(self.spin_y.value()),
|
|
1362
|
+
int(self.spin_w.value()), int(self.spin_h.value()))
|
|
1363
|
+
return self._even_roi(x, y, w, h)
|
|
1226
1364
|
|
|
1227
1365
|
def _on_trim_changed(self):
|
|
1228
1366
|
if self.reader is None:
|
|
@@ -1542,18 +1680,28 @@ class SERViewer(QDialog):
|
|
|
1542
1680
|
|
|
1543
1681
|
def _to_qimage(self, arr: np.ndarray) -> QImage:
|
|
1544
1682
|
a = np.clip(arr, 0.0, 1.0)
|
|
1683
|
+
|
|
1545
1684
|
if a.ndim == 2:
|
|
1546
|
-
u = (a * 255.0).astype(np.uint8)
|
|
1685
|
+
u = (a * 255.0).astype(np.uint8, copy=False)
|
|
1686
|
+
# ✅ FITS/memmap correction: ensure C-contiguous rows
|
|
1687
|
+
if not u.flags["C_CONTIGUOUS"]:
|
|
1688
|
+
u = np.ascontiguousarray(u)
|
|
1689
|
+
|
|
1547
1690
|
h, w = u.shape
|
|
1548
1691
|
return QImage(u.data, w, h, w, QImage.Format.Format_Grayscale8).copy()
|
|
1549
1692
|
|
|
1550
1693
|
if a.ndim == 3 and a.shape[2] >= 3:
|
|
1551
|
-
u = (a[..., :3] * 255.0).astype(np.uint8)
|
|
1694
|
+
u = (a[..., :3] * 255.0).astype(np.uint8, copy=False)
|
|
1695
|
+
# ✅ FITS/memmap correction: ensure C-contiguous packed RGB
|
|
1696
|
+
if not u.flags["C_CONTIGUOUS"]:
|
|
1697
|
+
u = np.ascontiguousarray(u)
|
|
1698
|
+
|
|
1552
1699
|
h, w, _ = u.shape
|
|
1553
1700
|
return QImage(u.data, w, h, w * 3, QImage.Format.Format_RGB888).copy()
|
|
1554
1701
|
|
|
1555
1702
|
raise ValueError(f"Unexpected image shape: {a.shape}")
|
|
1556
1703
|
|
|
1704
|
+
|
|
1557
1705
|
def _roi_bounds(self):
|
|
1558
1706
|
"""
|
|
1559
1707
|
Returns (rx, ry, rw, rh) in full-frame coords if ROI enabled,
|
setiastro/saspro/sfcc.py
CHANGED
|
@@ -20,6 +20,12 @@ from datetime import datetime
|
|
|
20
20
|
from typing import List, Tuple, Optional
|
|
21
21
|
|
|
22
22
|
import numpy as np
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
_trapz = np.trapezoid
|
|
26
|
+
except AttributeError:
|
|
27
|
+
_trapz = np.trapz
|
|
28
|
+
|
|
23
29
|
import numpy.ma as ma
|
|
24
30
|
import pandas as pd
|
|
25
31
|
|
|
@@ -313,8 +319,8 @@ class SaspViewer(QMainWindow):
|
|
|
313
319
|
for color in ("red","green","blue"):
|
|
314
320
|
data = rgb_data[color]
|
|
315
321
|
if data is not None:
|
|
316
|
-
S_star =
|
|
317
|
-
S_veg =
|
|
322
|
+
S_star = _trapz(data["response"], x=common_wl)
|
|
323
|
+
S_veg = _trapz(fl_veg_c * data["T_sys"], x=common_wl)
|
|
318
324
|
if S_veg>0 and S_star>0:
|
|
319
325
|
mag = -2.5 * np.log10(S_star / S_veg)
|
|
320
326
|
mag_texts.append(f"{color[0].upper()}→{data['filter_name']}: {mag:.2f}")
|
|
@@ -1485,9 +1491,9 @@ class SFCCDialog(QDialog):
|
|
|
1485
1491
|
wl_ref, fl_ref = load_sed(ref_sed_name)
|
|
1486
1492
|
fr_i = np.interp(wl_grid, wl_ref, fl_ref, left=0.0, right=0.0)
|
|
1487
1493
|
|
|
1488
|
-
S_ref_R =
|
|
1489
|
-
S_ref_G =
|
|
1490
|
-
S_ref_B =
|
|
1494
|
+
S_ref_R = _trapz(fr_i * T_sys_R, x=wl_grid)
|
|
1495
|
+
S_ref_G = _trapz(fr_i * T_sys_G, x=wl_grid)
|
|
1496
|
+
S_ref_B = _trapz(fr_i * T_sys_B, x=wl_grid)
|
|
1491
1497
|
|
|
1492
1498
|
diag_meas_RG, diag_exp_RG = [], []
|
|
1493
1499
|
diag_meas_BG, diag_exp_BG = [], []
|
|
@@ -1510,9 +1516,9 @@ class SFCCDialog(QDialog):
|
|
|
1510
1516
|
try:
|
|
1511
1517
|
wl_s, fl_s = load_sed(pname)
|
|
1512
1518
|
fs_i = np.interp(wl_grid, wl_s, fl_s, left=0.0, right=0.0)
|
|
1513
|
-
S_sr =
|
|
1514
|
-
S_sg =
|
|
1515
|
-
S_sb =
|
|
1519
|
+
S_sr = _trapz(fs_i * T_sys_R, x=wl_grid)
|
|
1520
|
+
S_sg = _trapz(fs_i * T_sys_G, x=wl_grid)
|
|
1521
|
+
S_sb = _trapz(fs_i * T_sys_B, x=wl_grid)
|
|
1516
1522
|
template_integrals[pname] = (S_sr, S_sg, S_sb)
|
|
1517
1523
|
except Exception as e:
|
|
1518
1524
|
print(f"[SFCC] Warning: failed to load/integrate template {pname}: {e}")
|