SpecProc 1.0.0__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.
- specproc/__init__.py +9 -0
- specproc/calib_data/linelists/FeAr.dat +375 -0
- specproc/calib_data/linelists/README +79 -0
- specproc/calib_data/linelists/airvac.cl +61 -0
- specproc/calib_data/linelists/argon.dat +161 -0
- specproc/calib_data/linelists/ctiohear.dat +106 -0
- specproc/calib_data/linelists/ctiohenear.dat +146 -0
- specproc/calib_data/linelists/cuar.dat +459 -0
- specproc/calib_data/linelists/henear.dat +93 -0
- specproc/calib_data/linelists/henearhres.dat +300 -0
- specproc/calib_data/linelists/idhenear.dat +127 -0
- specproc/calib_data/linelists/krypton.dat +109 -0
- specproc/calib_data/linelists/lowskylines.dat +577 -0
- specproc/calib_data/linelists/ohlines.dat +268 -0
- specproc/calib_data/linelists/skylines.dat +1385 -0
- specproc/calib_data/linelists/thar-noao.dat +4169 -0
- specproc/calib_data/linelists/thar.dat +3057 -0
- specproc/calib_data/linelists/thorium.dat +3057 -0
- specproc/calib_data/linelists/vacidhenear.dat +127 -0
- specproc/calib_data/linelists/vacthar.dat +3057 -0
- specproc/calib_data/linelists/vacthorium.dat +3057 -0
- specproc/calib_data/linelists/xenon.dat +89 -0
- specproc/calib_data/telescopes/lijiang24hrs/lijiang24_hrs_thar_lines.csv +550 -0
- specproc/calib_data/telescopes/xinglong216bfosc/BFOSC/345/256/232/346/240/207/347/201/257/350/260/261/350/256/244/350/257/201/346/211/213/345/206/214v171118.pdf +0 -0
- specproc/calib_data/telescopes/xinglong216bfosc//346/212/245/345/221/212/345/233/276.docx +0 -0
- specproc/calib_data/telescopes/xinglong216hrs/216HRS /347/201/257/350/260/261/350/257/201/350/256/244/346/226/207/344/273/266.pdf +0 -0
- specproc/calib_data/telescopes/xinglong216hrs/wlcalib_20211123011_A.png +0 -0
- specproc/calib_data/telescopes/xinglong216hrs/xinglong216_thar_lines.csv +517 -0
- specproc/config/__init__.py +1 -0
- specproc/config/config_manager.py +431 -0
- specproc/core/__init__.py +1 -0
- specproc/core/basic_reduction.py +615 -0
- specproc/core/data_structures.py +525 -0
- specproc/core/de_blazing.py +210 -0
- specproc/core/extraction.py +375 -0
- specproc/core/flat_correction.py +1255 -0
- specproc/core/line_identification.py +302 -0
- specproc/core/order_stitching.py +187 -0
- specproc/core/order_tracing.py +1587 -0
- specproc/core/processing_pipeline.py +1183 -0
- specproc/core/scattered_light.py +654 -0
- specproc/core/slit_pipeline.py +762 -0
- specproc/core/wave_calibration.py +961 -0
- specproc/gui/__init__.py +1 -0
- specproc/gui/line_identifier.py +731 -0
- specproc/gui/main_window.py +2315 -0
- specproc/gui/settings_dialog.py +946 -0
- specproc/main.py +388 -0
- specproc/plotting/__init__.py +1 -0
- specproc/plotting/spectra_plotter.py +483 -0
- specproc/utils/__init__.py +1 -0
- specproc/utils/airvac.py +123 -0
- specproc/utils/fits_io.py +269 -0
- specproc/utils/image_processing.py +426 -0
- specproc/utils/overscan.py +600 -0
- specproc-1.0.0.dist-info/METADATA +668 -0
- specproc-1.0.0.dist-info/RECORD +61 -0
- specproc-1.0.0.dist-info/WHEEL +5 -0
- specproc-1.0.0.dist-info/entry_points.txt +5 -0
- specproc-1.0.0.dist-info/licenses/LICENSE +674 -0
- specproc-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,946 @@
|
|
|
1
|
+
|
|
2
|
+
"""
|
|
3
|
+
Settings dialog for SpecProc configuration.
|
|
4
|
+
|
|
5
|
+
Provides a GUI for editing configuration parameters organized by tabs.
|
|
6
|
+
"""
|
|
7
|
+
# 提供按类别编辑处理配置的设置界面。
|
|
8
|
+
|
|
9
|
+
from PyQt5.QtWidgets import (
|
|
10
|
+
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit,
|
|
11
|
+
QSpinBox, QDoubleSpinBox, QComboBox, QCheckBox,
|
|
12
|
+
QPushButton, QTabWidget, QWidget, QGroupBox, QFormLayout, QScrollArea,
|
|
13
|
+
QFileDialog
|
|
14
|
+
)
|
|
15
|
+
from PyQt5.QtCore import Qt
|
|
16
|
+
|
|
17
|
+
from specproc.config.config_manager import ConfigManager
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# 提供处理配置的分项编辑界面。
|
|
21
|
+
class SettingsDialog(QDialog):
|
|
22
|
+
"""Settings dialog for configuration editing."""
|
|
23
|
+
|
|
24
|
+
# 初始化设置对话框。
|
|
25
|
+
def __init__(self, config: ConfigManager, parent=None, initial_tab: int = 0):
|
|
26
|
+
"""
|
|
27
|
+
Initialize settings dialog.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
config: Configuration manager instance
|
|
31
|
+
parent: Parent widget
|
|
32
|
+
initial_tab: Initial tab index to show (0: Data & Instruments, 1: Data Reduction, etc.)
|
|
33
|
+
"""
|
|
34
|
+
super().__init__(parent)
|
|
35
|
+
self.config = config
|
|
36
|
+
self.setWindowTitle("SpecProc Settings")
|
|
37
|
+
self.setModal(True)
|
|
38
|
+
self.resize(800, 600)
|
|
39
|
+
|
|
40
|
+
self.initial_tab = initial_tab
|
|
41
|
+
self.tab_widget = None
|
|
42
|
+
|
|
43
|
+
self.init_ui()
|
|
44
|
+
self.load_current_values()
|
|
45
|
+
|
|
46
|
+
# Set initial tab
|
|
47
|
+
if self.tab_widget:
|
|
48
|
+
self.tab_widget.setCurrentIndex(initial_tab)
|
|
49
|
+
|
|
50
|
+
# 构建设置界面及选项卡。
|
|
51
|
+
def init_ui(self):
|
|
52
|
+
"""Initialize user interface."""
|
|
53
|
+
layout = QVBoxLayout()
|
|
54
|
+
|
|
55
|
+
# Tab widget with different configuration categories
|
|
56
|
+
self.tab_widget = QTabWidget()
|
|
57
|
+
|
|
58
|
+
# Create tabs following data reduction pipeline order
|
|
59
|
+
self.tab_widget.addTab(self._create_data_tab(), "Data & Instruments")
|
|
60
|
+
self.tab_widget.addTab(self._create_reduction_tab(), "Data Reduction")
|
|
61
|
+
self.tab_widget.addTab(self._create_extraction_tab(), "Apertures Tracing")
|
|
62
|
+
self.tab_widget.addTab(self._create_wlcalib_tab(), "Wavelength Calibration")
|
|
63
|
+
self.tab_widget.addTab(self._create_processing_tab(), "Processing & Output")
|
|
64
|
+
|
|
65
|
+
layout.addWidget(self.tab_widget)
|
|
66
|
+
|
|
67
|
+
# Buttons
|
|
68
|
+
button_layout = QHBoxLayout()
|
|
69
|
+
save_btn = QPushButton("Save")
|
|
70
|
+
save_btn.clicked.connect(self._save_settings)
|
|
71
|
+
cancel_btn = QPushButton("Cancel")
|
|
72
|
+
cancel_btn.clicked.connect(self.reject)
|
|
73
|
+
button_layout.addStretch()
|
|
74
|
+
button_layout.addWidget(save_btn)
|
|
75
|
+
button_layout.addWidget(cancel_btn)
|
|
76
|
+
layout.addLayout(button_layout)
|
|
77
|
+
|
|
78
|
+
self.setLayout(layout)
|
|
79
|
+
|
|
80
|
+
# 创建数据输入与探测器设置页。
|
|
81
|
+
def _create_data_tab(self) -> QWidget:
|
|
82
|
+
"""Create data input and detector settings tab."""
|
|
83
|
+
scroll = QScrollArea()
|
|
84
|
+
scroll.setWidgetResizable(True)
|
|
85
|
+
widget = QWidget()
|
|
86
|
+
layout = QFormLayout()
|
|
87
|
+
|
|
88
|
+
# Data input group
|
|
89
|
+
data_group = QGroupBox("Data Input")
|
|
90
|
+
data_layout = QFormLayout()
|
|
91
|
+
|
|
92
|
+
self.rawdata_path_edit = QLineEdit()
|
|
93
|
+
data_layout.addRow("Raw Data Path:", self.rawdata_path_edit)
|
|
94
|
+
|
|
95
|
+
self.statime_key_edit = QLineEdit()
|
|
96
|
+
data_layout.addRow("Start Time Key (DATE-OBS):", self.statime_key_edit)
|
|
97
|
+
|
|
98
|
+
self.exptime_key_edit = QLineEdit()
|
|
99
|
+
data_layout.addRow("Exposure Time Key (EXPTIME):", self.exptime_key_edit)
|
|
100
|
+
|
|
101
|
+
self.direction_combo = QComboBox()
|
|
102
|
+
self.direction_combo.addItems(['xrb', 'xlb', 'yrb', 'ylb'])
|
|
103
|
+
data_layout.addRow("Dispersion Direction:", self.direction_combo)
|
|
104
|
+
|
|
105
|
+
self.output_path_edit = QLineEdit()
|
|
106
|
+
data_layout.addRow("Output Path:", self.output_path_edit)
|
|
107
|
+
|
|
108
|
+
data_group.setLayout(data_layout)
|
|
109
|
+
layout.addRow(data_group)
|
|
110
|
+
|
|
111
|
+
# Detector parameters
|
|
112
|
+
detector_group = QGroupBox("Detector Parameters")
|
|
113
|
+
detector_layout = QFormLayout()
|
|
114
|
+
|
|
115
|
+
self.detector_gain_spin = QDoubleSpinBox()
|
|
116
|
+
self.detector_gain_spin.setRange(0.1, 100.0)
|
|
117
|
+
self.detector_gain_spin.setSingleStep(0.1)
|
|
118
|
+
detector_layout.addRow("CCD Gain:", self.detector_gain_spin)
|
|
119
|
+
|
|
120
|
+
self.detector_readnoise_spin = QDoubleSpinBox()
|
|
121
|
+
self.detector_readnoise_spin.setRange(0.0, 100.0)
|
|
122
|
+
self.detector_readnoise_spin.setSingleStep(0.5)
|
|
123
|
+
detector_layout.addRow("Read Noise:", self.detector_readnoise_spin)
|
|
124
|
+
|
|
125
|
+
detector_group.setLayout(detector_layout)
|
|
126
|
+
layout.addRow(detector_group)
|
|
127
|
+
|
|
128
|
+
widget.setLayout(layout)
|
|
129
|
+
widget.setMinimumWidth(400)
|
|
130
|
+
scroll.setWidget(widget)
|
|
131
|
+
return scroll
|
|
132
|
+
|
|
133
|
+
# 创建数据预处理设置页。
|
|
134
|
+
def _create_reduction_tab(self) -> QWidget:
|
|
135
|
+
"""Create bias, flat, background and cosmic ray correction settings tab."""
|
|
136
|
+
scroll = QScrollArea()
|
|
137
|
+
scroll.setWidgetResizable(True)
|
|
138
|
+
widget = QWidget()
|
|
139
|
+
layout = QFormLayout()
|
|
140
|
+
|
|
141
|
+
# Overscan configuration
|
|
142
|
+
overscan_group = QGroupBox("Overscan Correction")
|
|
143
|
+
overscan_layout = QFormLayout()
|
|
144
|
+
|
|
145
|
+
self.overscan_start_column_spin = QSpinBox()
|
|
146
|
+
self.overscan_start_column_spin.setRange(-1, 10000)
|
|
147
|
+
self.overscan_start_column_spin.setSpecialValueText("None")
|
|
148
|
+
overscan_layout.addRow("Overscan Start Column (1-based):", self.overscan_start_column_spin)
|
|
149
|
+
|
|
150
|
+
self.overscan_method_combo = QComboBox()
|
|
151
|
+
self.overscan_method_combo.addItems(['mean_only', 'mean_savgol', 'mean_polynomial'])
|
|
152
|
+
self.overscan_method_combo.setToolTip(
|
|
153
|
+
"mean_only: Sigma-clipped mean only, no smoothing\n"
|
|
154
|
+
"mean_savgol: Sigma-clipped mean with Savitzky-Golay smoothing\n"
|
|
155
|
+
"mean_polynomial: Sigma-clipped mean with polynomial fitting (IRAF style)"
|
|
156
|
+
)
|
|
157
|
+
overscan_layout.addRow("Overscan Method:", self.overscan_method_combo)
|
|
158
|
+
|
|
159
|
+
self.overscan_smooth_window_spin = QSpinBox()
|
|
160
|
+
self.overscan_smooth_window_spin.setRange(-1, 10000)
|
|
161
|
+
self.overscan_smooth_window_spin.setSpecialValueText("Auto (1/5 height)")
|
|
162
|
+
self.overscan_smooth_window_spin.setToolTip("Savitzky-Golay smoothing window size. -1 for auto (1/5 of image height)")
|
|
163
|
+
overscan_layout.addRow("Smooth Window:", self.overscan_smooth_window_spin)
|
|
164
|
+
|
|
165
|
+
self.overscan_poly_type_combo = QComboBox()
|
|
166
|
+
self.overscan_poly_type_combo.addItems(['legendre', 'chebyshev', 'polynomial'])
|
|
167
|
+
self.overscan_poly_type_combo.setToolTip(
|
|
168
|
+
"Polynomial type for mean_polynomial method:\n"
|
|
169
|
+
"legendre: Legendre polynomial (IRAF default)\n"
|
|
170
|
+
"chebyshev: Chebyshev polynomial\n"
|
|
171
|
+
"polynomial: Standard power series"
|
|
172
|
+
)
|
|
173
|
+
overscan_layout.addRow("Poly Type:", self.overscan_poly_type_combo)
|
|
174
|
+
|
|
175
|
+
self.overscan_poly_order_spin = QSpinBox()
|
|
176
|
+
self.overscan_poly_order_spin.setRange(1, 10)
|
|
177
|
+
self.overscan_poly_order_spin.setValue(3)
|
|
178
|
+
self.overscan_poly_order_spin.setToolTip("Polynomial order (degree) for mean_polynomial method")
|
|
179
|
+
overscan_layout.addRow("Poly Order:", self.overscan_poly_order_spin)
|
|
180
|
+
|
|
181
|
+
self.detector_split_row_spin = QSpinBox()
|
|
182
|
+
self.detector_split_row_spin.setRange(-1, 20000)
|
|
183
|
+
self.detector_split_row_spin.setSpecialValueText("Auto (rows/2)")
|
|
184
|
+
self.detector_split_row_spin.setToolTip(
|
|
185
|
+
"Detector amplifier split row (1-based).\n"
|
|
186
|
+
"Divides the CCD into lower and upper readout halves.\n"
|
|
187
|
+
"Used by overscan correction and step 3 background fitting.\n"
|
|
188
|
+
"For Xinglong 216 HRS 4136-row CCD: 2068. Set -1 for auto."
|
|
189
|
+
)
|
|
190
|
+
overscan_layout.addRow("Amplifier Split Row:", self.detector_split_row_spin)
|
|
191
|
+
|
|
192
|
+
overscan_group.setLayout(overscan_layout)
|
|
193
|
+
layout.addRow(overscan_group)
|
|
194
|
+
|
|
195
|
+
# Connect overscan method change to update parameter states
|
|
196
|
+
self.overscan_method_combo.currentTextChanged.connect(self._update_overscan_params_enabled)
|
|
197
|
+
|
|
198
|
+
# Bias correction
|
|
199
|
+
bias_group = QGroupBox("Bias Correction")
|
|
200
|
+
bias_layout = QFormLayout()
|
|
201
|
+
|
|
202
|
+
self.bias_combine_method_combo = QComboBox()
|
|
203
|
+
self.bias_combine_method_combo.addItems(['mean', 'median', 'sigma_clip'])
|
|
204
|
+
bias_layout.addRow("Combine Method:", self.bias_combine_method_combo)
|
|
205
|
+
|
|
206
|
+
self.bias_combine_sigma_spin = QDoubleSpinBox()
|
|
207
|
+
self.bias_combine_sigma_spin.setRange(1.0, 10.0)
|
|
208
|
+
self.bias_combine_sigma_spin.setSingleStep(0.1)
|
|
209
|
+
bias_layout.addRow("Sigma Clipping Threshold:", self.bias_combine_sigma_spin)
|
|
210
|
+
|
|
211
|
+
bias_group.setLayout(bias_layout)
|
|
212
|
+
layout.addRow(bias_group)
|
|
213
|
+
|
|
214
|
+
# Cosmic ray correction (Step 1)
|
|
215
|
+
cosmic_group = QGroupBox("Cosmic Ray Correction (L.A.Cosmic)")
|
|
216
|
+
cosmic_layout = QFormLayout()
|
|
217
|
+
|
|
218
|
+
self.cosmic_enabled_check = QCheckBox("Enable cosmic ray correction")
|
|
219
|
+
cosmic_layout.addRow(self.cosmic_enabled_check)
|
|
220
|
+
|
|
221
|
+
self.cosmic_sigclip_spin = QDoubleSpinBox()
|
|
222
|
+
self.cosmic_sigclip_spin.setRange(1.0, 20.0)
|
|
223
|
+
self.cosmic_sigclip_spin.setSingleStep(0.1)
|
|
224
|
+
self.cosmic_sigclip_spin.setToolTip("Detection threshold (sigma). Higher values are more conservative.")
|
|
225
|
+
cosmic_layout.addRow("Sigma Clip:", self.cosmic_sigclip_spin)
|
|
226
|
+
|
|
227
|
+
self.cosmic_objlim_spin = QDoubleSpinBox()
|
|
228
|
+
self.cosmic_objlim_spin.setRange(1.0, 20.0)
|
|
229
|
+
self.cosmic_objlim_spin.setSingleStep(0.1)
|
|
230
|
+
self.cosmic_objlim_spin.setToolTip("Contrast limit between cosmic ray and underlying object.")
|
|
231
|
+
cosmic_layout.addRow("Object Limit:", self.cosmic_objlim_spin)
|
|
232
|
+
|
|
233
|
+
self.cosmic_gain_spin = QDoubleSpinBox()
|
|
234
|
+
self.cosmic_gain_spin.setRange(0.0, 10.0)
|
|
235
|
+
self.cosmic_gain_spin.setSingleStep(0.1)
|
|
236
|
+
self.cosmic_gain_spin.setToolTip("Detector gain. Set to 0.0 to auto-detect from config.")
|
|
237
|
+
cosmic_layout.addRow("Gain:", self.cosmic_gain_spin)
|
|
238
|
+
|
|
239
|
+
cosmic_group.setLayout(cosmic_layout)
|
|
240
|
+
layout.addRow(cosmic_group)
|
|
241
|
+
|
|
242
|
+
# Background subtraction (Step 3)
|
|
243
|
+
bg_group = QGroupBox("Background Subtraction")
|
|
244
|
+
bg_layout = QFormLayout()
|
|
245
|
+
|
|
246
|
+
self.bg_method_combo = QComboBox()
|
|
247
|
+
self.bg_method_combo.addItems(['convolution', 'column_spline', 'bspline'])
|
|
248
|
+
self.bg_method_combo.setToolTip(
|
|
249
|
+
"'convolution': astropy NaN-aware 2D Gaussian convolution (recommended,\n"
|
|
250
|
+
" reproduces local ripples/fringes);\n"
|
|
251
|
+
"'column_spline': per-column 1D smoothing spline (follows ripple peaks/troughs,\n"
|
|
252
|
+
" through inter-aperture pixels then lightly smooths horizontally);\n"
|
|
253
|
+
"'bspline': bivariate spline fit.")
|
|
254
|
+
bg_layout.addRow("Background Method:", self.bg_method_combo)
|
|
255
|
+
self.bg_method_combo.currentTextChanged.connect(self._update_bg_params_enabled)
|
|
256
|
+
|
|
257
|
+
self.bg_kernel_sigma_x_spin = QDoubleSpinBox()
|
|
258
|
+
self.bg_kernel_sigma_x_spin.setRange(5.0, 100.0)
|
|
259
|
+
self.bg_kernel_sigma_x_spin.setSingleStep(1.0)
|
|
260
|
+
self.bg_kernel_sigma_x_spin.setValue(13.0)
|
|
261
|
+
self.bg_kernel_sigma_x_spin.setToolTip(
|
|
262
|
+
"Gaussian kernel σ_x (色散方向, 水平, X) for convolution method.\n"
|
|
263
|
+
"建议 10–20 px。\n"
|
|
264
|
+
"σ_x 应极细以还原干涉波峰(如 60-110 px),较小的值(13)能完美刻画横向条纹。")
|
|
265
|
+
bg_layout.addRow("Kernel σ_x (px):", self.bg_kernel_sigma_x_spin)
|
|
266
|
+
|
|
267
|
+
self.bg_kernel_sigma_y_spin = QDoubleSpinBox()
|
|
268
|
+
self.bg_kernel_sigma_y_spin.setRange(5.0, 80.0)
|
|
269
|
+
self.bg_kernel_sigma_y_spin.setSingleStep(1.0)
|
|
270
|
+
self.bg_kernel_sigma_y_spin.setValue(13.0)
|
|
271
|
+
self.bg_kernel_sigma_y_spin.setToolTip(
|
|
272
|
+
"Gaussian kernel σ_y (交叉色散方向, 垂直, Y) for convolution method.\n"
|
|
273
|
+
"建议 12–20 px。\n"
|
|
274
|
+
"σ_y (13) 刚好能跨越 mask 宽度(10-12 px),同时保留纵向高频变化。")
|
|
275
|
+
bg_layout.addRow("Kernel σ_y (px):", self.bg_kernel_sigma_y_spin)
|
|
276
|
+
|
|
277
|
+
self.bg_spline_smooth_factor_spin = QDoubleSpinBox()
|
|
278
|
+
self.bg_spline_smooth_factor_spin.setRange(0.01, 10.0)
|
|
279
|
+
self.bg_spline_smooth_factor_spin.setSingleStep(0.05)
|
|
280
|
+
self.bg_spline_smooth_factor_spin.setDecimals(3)
|
|
281
|
+
self.bg_spline_smooth_factor_spin.setToolTip(
|
|
282
|
+
"Smoothing factor multiplier for column_spline method.\n"
|
|
283
|
+
"s = factor × N_bg_pixels. Smaller values let the spline follow\n"
|
|
284
|
+
"ripple peaks/troughs more closely. Typical 0.1–1.0.")
|
|
285
|
+
bg_layout.addRow("Spline Smooth Factor:", self.bg_spline_smooth_factor_spin)
|
|
286
|
+
|
|
287
|
+
self.bg_spline_post_smooth_x_spin = QDoubleSpinBox()
|
|
288
|
+
self.bg_spline_post_smooth_x_spin.setRange(0.0, 50.0)
|
|
289
|
+
self.bg_spline_post_smooth_x_spin.setSingleStep(1.0)
|
|
290
|
+
self.bg_spline_post_smooth_x_spin.setToolTip(
|
|
291
|
+
"Gaussian σ (pixels) for light horizontal smoothing after column-wise\n"
|
|
292
|
+
"spline assembly. Removes column-to-column discontinuities.\n"
|
|
293
|
+
"Set to 0 to disable. Typical 3–10 px.")
|
|
294
|
+
bg_layout.addRow("Spline Post-smooth σ_x:", self.bg_spline_post_smooth_x_spin)
|
|
295
|
+
|
|
296
|
+
self.bg_poly_order_spin = QSpinBox()
|
|
297
|
+
self.bg_poly_order_spin.setRange(1, 10)
|
|
298
|
+
bg_layout.addRow("Fit Order:", self.bg_poly_order_spin)
|
|
299
|
+
|
|
300
|
+
self.bg_sigma_clip_spin = QDoubleSpinBox()
|
|
301
|
+
self.bg_sigma_clip_spin.setRange(0.0, 10.0)
|
|
302
|
+
self.bg_sigma_clip_spin.setSingleStep(0.1)
|
|
303
|
+
bg_layout.addRow("Sigma Clip:", self.bg_sigma_clip_spin)
|
|
304
|
+
|
|
305
|
+
self.bg_sigma_clip_iter_spin = QSpinBox()
|
|
306
|
+
self.bg_sigma_clip_iter_spin.setRange(1, 20)
|
|
307
|
+
bg_layout.addRow("Clip Iterations:", self.bg_sigma_clip_iter_spin)
|
|
308
|
+
|
|
309
|
+
self.bg_clip_mode_combo = QComboBox()
|
|
310
|
+
self.bg_clip_mode_combo.addItems(['upper', 'both', 'lower'])
|
|
311
|
+
self.bg_clip_mode_combo.setToolTip(
|
|
312
|
+
"'upper': reject only bright outliers (recommended for flats with "
|
|
313
|
+
"unmasked faint apertures);\n"
|
|
314
|
+
"'both': symmetric rejection;\n"
|
|
315
|
+
"'lower': reject only faint outliers.")
|
|
316
|
+
bg_layout.addRow("Clip Mode:", self.bg_clip_mode_combo)
|
|
317
|
+
|
|
318
|
+
self.bg_mask_margin_spin = QSpinBox()
|
|
319
|
+
self.bg_mask_margin_spin.setRange(0, 20)
|
|
320
|
+
bg_layout.addRow("Mask Margin (px):", self.bg_mask_margin_spin)
|
|
321
|
+
|
|
322
|
+
self.bg_bspline_smooth_spin = QDoubleSpinBox()
|
|
323
|
+
self.bg_bspline_smooth_spin.setRange(0.0, 100.0)
|
|
324
|
+
self.bg_bspline_smooth_spin.setSingleStep(0.1)
|
|
325
|
+
bg_layout.addRow("B-spline Smooth:", self.bg_bspline_smooth_spin)
|
|
326
|
+
|
|
327
|
+
bg_group.setLayout(bg_layout)
|
|
328
|
+
layout.addRow(bg_group)
|
|
329
|
+
|
|
330
|
+
# Flat fielding (Step 4)
|
|
331
|
+
flat_group = QGroupBox("Flat Fielding")
|
|
332
|
+
flat_layout = QFormLayout()
|
|
333
|
+
|
|
334
|
+
self.flat_combine_method_combo = QComboBox()
|
|
335
|
+
self.flat_combine_method_combo.addItems(['mean', 'median'])
|
|
336
|
+
flat_layout.addRow("Combine Method:", self.flat_combine_method_combo)
|
|
337
|
+
|
|
338
|
+
self.flat_q_threshold_spin = QDoubleSpinBox()
|
|
339
|
+
self.flat_q_threshold_spin.setRange(0.0, 1.0)
|
|
340
|
+
self.flat_q_threshold_spin.setSingleStep(0.05)
|
|
341
|
+
flat_layout.addRow("Quality Threshold:", self.flat_q_threshold_spin)
|
|
342
|
+
|
|
343
|
+
self.flat_mosaic_maxcount_spin = QDoubleSpinBox()
|
|
344
|
+
self.flat_mosaic_maxcount_spin.setRange(0.0, 100000.0)
|
|
345
|
+
flat_layout.addRow("Mosaic Max Count:", self.flat_mosaic_maxcount_spin)
|
|
346
|
+
|
|
347
|
+
self.flat_blaze_smooth_factor_spin = QDoubleSpinBox()
|
|
348
|
+
self.flat_blaze_smooth_factor_spin.setRange(0.1, 100.0)
|
|
349
|
+
self.flat_blaze_smooth_factor_spin.setSingleStep(0.1)
|
|
350
|
+
self.flat_blaze_smooth_factor_spin.setToolTip(
|
|
351
|
+
"Smoothing factor for automated B-spline blaze fitting in blue/dark apertures.\n"
|
|
352
|
+
"s = factor * N * variance. Typical values: 1.0 (soft) to 20.0 (rigid).\n"
|
|
353
|
+
"Larger = more rigid curve, Smaller = softer fit.")
|
|
354
|
+
flat_layout.addRow("Blaze Smooth Factor:", self.flat_blaze_smooth_factor_spin)
|
|
355
|
+
|
|
356
|
+
self.flat_fringe_orders_spin = QSpinBox()
|
|
357
|
+
self.flat_fringe_orders_spin.setRange(0, 150)
|
|
358
|
+
self.flat_fringe_orders_spin.setToolTip(
|
|
359
|
+
"Number of red-end apertures to apply Upper Envelope Chebyshev fitting for fringe removal.\n"
|
|
360
|
+
"Default is 20. Set to 0 to disable and use standard B-spline for all apertures.")
|
|
361
|
+
flat_layout.addRow("Fringe Apertures (Red end):", self.flat_fringe_orders_spin)
|
|
362
|
+
|
|
363
|
+
self.flat_width_smooth_window_spin = QSpinBox()
|
|
364
|
+
self.flat_width_smooth_window_spin.setRange(5, 401)
|
|
365
|
+
self.flat_width_smooth_window_spin.setSingleStep(2)
|
|
366
|
+
flat_layout.addRow("Width Smooth Window:", self.flat_width_smooth_window_spin)
|
|
367
|
+
|
|
368
|
+
self.flat_n_profile_segments_spin = QSpinBox()
|
|
369
|
+
self.flat_n_profile_segments_spin.setRange(1, 200)
|
|
370
|
+
self.flat_n_profile_segments_spin.setSingleStep(10)
|
|
371
|
+
self.flat_n_profile_segments_spin.setToolTip(
|
|
372
|
+
"Number of segments along X (dispersion) for the\n"
|
|
373
|
+
"cross-dispersion profile. Each segment builds a median\n"
|
|
374
|
+
"binned profile, then all segments are Gaussian-smoothed\n"
|
|
375
|
+
"along X to produce a continuously varying P(y_norm; x).\n"
|
|
376
|
+
"50-100 recommended for 4k detectors.")
|
|
377
|
+
flat_layout.addRow("Profile Segments:", self.flat_n_profile_segments_spin)
|
|
378
|
+
|
|
379
|
+
self.flat_profile_smooth_sigma_spin = QDoubleSpinBox()
|
|
380
|
+
self.flat_profile_smooth_sigma_spin.setRange(0.0, 50.0)
|
|
381
|
+
self.flat_profile_smooth_sigma_spin.setSingleStep(1.0)
|
|
382
|
+
self.flat_profile_smooth_sigma_spin.setToolTip(
|
|
383
|
+
"Gaussian sigma (in segment units) for smoothing the\n"
|
|
384
|
+
"cross-dispersion profiles along the dispersion direction.\n"
|
|
385
|
+
"0 = no smoothing. 4-8 typical for 100 segments.")
|
|
386
|
+
flat_layout.addRow("Profile Smooth Sigma:", self.flat_profile_smooth_sigma_spin)
|
|
387
|
+
|
|
388
|
+
self.flat_profile_bin_step_spin = QDoubleSpinBox()
|
|
389
|
+
self.flat_profile_bin_step_spin.setRange(0.002, 0.2)
|
|
390
|
+
self.flat_profile_bin_step_spin.setDecimals(3)
|
|
391
|
+
self.flat_profile_bin_step_spin.setSingleStep(0.001)
|
|
392
|
+
flat_layout.addRow("y_norm Bin Step:", self.flat_profile_bin_step_spin)
|
|
393
|
+
|
|
394
|
+
self.flat_pixel_min_spin = QDoubleSpinBox()
|
|
395
|
+
self.flat_pixel_min_spin.setRange(0.01, 2.0)
|
|
396
|
+
self.flat_pixel_min_spin.setSingleStep(0.01)
|
|
397
|
+
flat_layout.addRow("Pixel Flat Min:", self.flat_pixel_min_spin)
|
|
398
|
+
|
|
399
|
+
self.flat_pixel_max_spin = QDoubleSpinBox()
|
|
400
|
+
self.flat_pixel_max_spin.setRange(0.1, 5.0)
|
|
401
|
+
self.flat_pixel_max_spin.setSingleStep(0.01)
|
|
402
|
+
flat_layout.addRow("Pixel Flat Max:", self.flat_pixel_max_spin)
|
|
403
|
+
|
|
404
|
+
flat_group.setLayout(flat_layout)
|
|
405
|
+
layout.addRow(flat_group)
|
|
406
|
+
|
|
407
|
+
widget.setLayout(layout)
|
|
408
|
+
scroll.setWidget(widget)
|
|
409
|
+
return scroll
|
|
410
|
+
|
|
411
|
+
# 按过扫描方法启用对应参数。
|
|
412
|
+
def _update_overscan_params_enabled(self):
|
|
413
|
+
"""Enable/disable overscan parameters based on selected method."""
|
|
414
|
+
method = self.overscan_method_combo.currentText()
|
|
415
|
+
|
|
416
|
+
if method == 'mean_only':
|
|
417
|
+
# No additional parameters needed
|
|
418
|
+
self.overscan_smooth_window_spin.setEnabled(False)
|
|
419
|
+
self.overscan_poly_type_combo.setEnabled(False)
|
|
420
|
+
self.overscan_poly_order_spin.setEnabled(False)
|
|
421
|
+
elif method == 'mean_savgol':
|
|
422
|
+
# Only smooth window needed
|
|
423
|
+
self.overscan_smooth_window_spin.setEnabled(True)
|
|
424
|
+
self.overscan_poly_type_combo.setEnabled(False)
|
|
425
|
+
self.overscan_poly_order_spin.setEnabled(False)
|
|
426
|
+
elif method == 'mean_polynomial':
|
|
427
|
+
# Poly type and order needed
|
|
428
|
+
self.overscan_smooth_window_spin.setEnabled(False)
|
|
429
|
+
self.overscan_poly_type_combo.setEnabled(True)
|
|
430
|
+
self.overscan_poly_order_spin.setEnabled(True)
|
|
431
|
+
|
|
432
|
+
# 按背景方法启用对应参数。
|
|
433
|
+
def _update_bg_params_enabled(self):
|
|
434
|
+
"""Enable/disable background parameters based on selected method."""
|
|
435
|
+
method = self.bg_method_combo.currentText()
|
|
436
|
+
|
|
437
|
+
# Method-specific params
|
|
438
|
+
is_conv = (method == 'convolution')
|
|
439
|
+
is_spline = (method == 'column_spline')
|
|
440
|
+
is_bspline = (method == 'bspline')
|
|
441
|
+
|
|
442
|
+
self.bg_kernel_sigma_x_spin.setEnabled(is_conv)
|
|
443
|
+
self.bg_kernel_sigma_y_spin.setEnabled(is_conv)
|
|
444
|
+
self.bg_spline_smooth_factor_spin.setEnabled(is_spline)
|
|
445
|
+
self.bg_spline_post_smooth_x_spin.setEnabled(is_spline)
|
|
446
|
+
self.bg_poly_order_spin.setEnabled(is_bspline)
|
|
447
|
+
self.bg_bspline_smooth_spin.setEnabled(is_bspline)
|
|
448
|
+
|
|
449
|
+
# Common params always enabled: sigma_clip, clip_iterations, clip_mode, mask_margin
|
|
450
|
+
|
|
451
|
+
# 创建波长标定设置页。
|
|
452
|
+
def _create_wlcalib_tab(self) -> QWidget:
|
|
453
|
+
"""Create wavelength calibration settings tab."""
|
|
454
|
+
scroll = QScrollArea()
|
|
455
|
+
scroll.setWidgetResizable(True)
|
|
456
|
+
widget = QWidget()
|
|
457
|
+
layout = QFormLayout()
|
|
458
|
+
|
|
459
|
+
# Calibration lamp type and files
|
|
460
|
+
calib_group = QGroupBox("Calibration Files")
|
|
461
|
+
calib_layout = QFormLayout()
|
|
462
|
+
|
|
463
|
+
self.linelist_combo = QComboBox()
|
|
464
|
+
self.linelist_combo.addItems(['ThAr', 'Ar', 'Ne', 'He', 'Fe', 'custom'])
|
|
465
|
+
calib_layout.addRow("Calibration Lamp Type:", self.linelist_combo)
|
|
466
|
+
|
|
467
|
+
# --- Anchor File ---
|
|
468
|
+
self.anchor_file_edit = QLineEdit()
|
|
469
|
+
anchor_file_layout = QHBoxLayout()
|
|
470
|
+
anchor_file_layout.setContentsMargins(0, 0, 0, 0)
|
|
471
|
+
anchor_file_layout.addWidget(self.anchor_file_edit)
|
|
472
|
+
anchor_browse_btn = QPushButton("...")
|
|
473
|
+
anchor_browse_btn.setFixedWidth(30)
|
|
474
|
+
anchor_browse_btn.clicked.connect(self._select_anchor_file)
|
|
475
|
+
anchor_file_layout.addWidget(anchor_browse_btn)
|
|
476
|
+
calib_layout.addRow("Anchor File (Identified Lines):", anchor_file_layout)
|
|
477
|
+
|
|
478
|
+
# --- Full Linelist ---
|
|
479
|
+
self.full_linelist_edit = QLineEdit()
|
|
480
|
+
full_linelist_layout = QHBoxLayout()
|
|
481
|
+
full_linelist_layout.setContentsMargins(0, 0, 0, 0)
|
|
482
|
+
full_linelist_layout.addWidget(self.full_linelist_edit)
|
|
483
|
+
full_linelist_browse_btn = QPushButton("...")
|
|
484
|
+
full_linelist_browse_btn.setFixedWidth(30)
|
|
485
|
+
full_linelist_browse_btn.clicked.connect(self._select_full_linelist_file)
|
|
486
|
+
full_linelist_layout.addWidget(full_linelist_browse_btn)
|
|
487
|
+
calib_layout.addRow("Full Linelist Catalog:", full_linelist_layout)
|
|
488
|
+
|
|
489
|
+
self.use_precomputed_calib_check = QCheckBox("Use pre-identified calibration files")
|
|
490
|
+
calib_layout.addRow(self.use_precomputed_calib_check)
|
|
491
|
+
|
|
492
|
+
# --- Precomputed Calibration File ---
|
|
493
|
+
self.precomputed_calib_file_edit = QLineEdit()
|
|
494
|
+
precomputed_calib_layout = QHBoxLayout()
|
|
495
|
+
precomputed_calib_layout.setContentsMargins(0, 0, 0, 0)
|
|
496
|
+
precomputed_calib_layout.addWidget(self.precomputed_calib_file_edit)
|
|
497
|
+
precomputed_browse_btn = QPushButton("...")
|
|
498
|
+
precomputed_browse_btn.setFixedWidth(30)
|
|
499
|
+
precomputed_browse_btn.clicked.connect(self._select_precomputed_calib_file)
|
|
500
|
+
precomputed_calib_layout.addWidget(precomputed_browse_btn)
|
|
501
|
+
calib_layout.addRow("Precomputed Calibration File:", precomputed_calib_layout)
|
|
502
|
+
|
|
503
|
+
calib_group.setLayout(calib_layout)
|
|
504
|
+
layout.addRow(calib_group)
|
|
505
|
+
|
|
506
|
+
# Polynomial solution
|
|
507
|
+
poly_group = QGroupBox("Polynomial Solution")
|
|
508
|
+
poly_layout = QFormLayout()
|
|
509
|
+
|
|
510
|
+
self.wlcalib_xorder_spin = QSpinBox()
|
|
511
|
+
self.wlcalib_xorder_spin.setRange(1, 10)
|
|
512
|
+
poly_layout.addRow("X Order (dispersion):", self.wlcalib_xorder_spin)
|
|
513
|
+
|
|
514
|
+
self.wlcalib_yorder_spin = QSpinBox()
|
|
515
|
+
self.wlcalib_yorder_spin.setRange(1, 10)
|
|
516
|
+
poly_layout.addRow("Y Order (spatial):", self.wlcalib_yorder_spin)
|
|
517
|
+
|
|
518
|
+
poly_group.setLayout(poly_layout)
|
|
519
|
+
layout.addRow(poly_group)
|
|
520
|
+
|
|
521
|
+
# Quality control
|
|
522
|
+
quality_group = QGroupBox("Quality Control")
|
|
523
|
+
quality_layout = QFormLayout()
|
|
524
|
+
|
|
525
|
+
self.wlcalib_match_tol_spin = QDoubleSpinBox()
|
|
526
|
+
self.wlcalib_match_tol_spin.setRange(0.1, 10.0)
|
|
527
|
+
self.wlcalib_match_tol_spin.setSingleStep(0.1)
|
|
528
|
+
self.wlcalib_match_tol_spin.setToolTip("Maximum allowed distance (in Angstroms) to match a detected peak to a catalog line.")
|
|
529
|
+
quality_layout.addRow("Catalog Match Tolerance (Å):", self.wlcalib_match_tol_spin)
|
|
530
|
+
|
|
531
|
+
self.wlcalib_rms_threshold_spin = QDoubleSpinBox()
|
|
532
|
+
self.wlcalib_rms_threshold_spin.setRange(0.01, 5.0)
|
|
533
|
+
self.wlcalib_rms_threshold_spin.setSingleStep(0.05)
|
|
534
|
+
self.wlcalib_rms_threshold_spin.setToolTip("Absolute maximum residual (in Angstroms) for a line to be kept in the final 2D polynomial fit.")
|
|
535
|
+
quality_layout.addRow("Max Residual Limit (Å):", self.wlcalib_rms_threshold_spin)
|
|
536
|
+
|
|
537
|
+
quality_group.setLayout(quality_layout)
|
|
538
|
+
layout.addRow(quality_group)
|
|
539
|
+
|
|
540
|
+
# Advanced parameters
|
|
541
|
+
adv_group = QGroupBox("Advanced Parameters")
|
|
542
|
+
adv_layout = QFormLayout()
|
|
543
|
+
|
|
544
|
+
self.wlcalib_discard_highest_spin = QSpinBox()
|
|
545
|
+
self.wlcalib_discard_highest_spin.setRange(0, 50)
|
|
546
|
+
self.wlcalib_discard_highest_spin.setToolTip("Number of apertures to discard at the highest aperture IDs during anchor matching.")
|
|
547
|
+
adv_layout.addRow("Discard Highest Aperture IDs:", self.wlcalib_discard_highest_spin)
|
|
548
|
+
|
|
549
|
+
self.wlcalib_discard_lowest_spin = QSpinBox()
|
|
550
|
+
self.wlcalib_discard_lowest_spin.setRange(0, 50)
|
|
551
|
+
self.wlcalib_discard_lowest_spin.setToolTip("Number of apertures to discard at the lowest aperture IDs during anchor matching.")
|
|
552
|
+
adv_layout.addRow("Discard Lowest Aperture IDs:", self.wlcalib_discard_lowest_spin)
|
|
553
|
+
|
|
554
|
+
adv_group.setLayout(adv_layout)
|
|
555
|
+
layout.addRow(adv_group)
|
|
556
|
+
|
|
557
|
+
widget.setLayout(layout)
|
|
558
|
+
scroll.setWidget(widget)
|
|
559
|
+
return scroll
|
|
560
|
+
|
|
561
|
+
# 创建孔径追踪与光谱提取设置页。
|
|
562
|
+
def _create_extraction_tab(self) -> QWidget:
|
|
563
|
+
"""Create aperture tracing and spectrum extraction settings tab."""
|
|
564
|
+
scroll = QScrollArea()
|
|
565
|
+
scroll.setWidgetResizable(True)
|
|
566
|
+
widget = QWidget()
|
|
567
|
+
layout = QFormLayout()
|
|
568
|
+
|
|
569
|
+
# Aperture tracing
|
|
570
|
+
trace_group = QGroupBox("Apertures Tracing")
|
|
571
|
+
trace_layout = QFormLayout()
|
|
572
|
+
|
|
573
|
+
self.trace_snr_threshold_spin = QDoubleSpinBox()
|
|
574
|
+
self.trace_snr_threshold_spin.setRange(1.0, 100000.0)
|
|
575
|
+
self.trace_snr_threshold_spin.setSingleStep(0.5)
|
|
576
|
+
self.trace_snr_threshold_spin.setToolTip(
|
|
577
|
+
"Aperture detection SNR threshold (multiples of sigma above background).\n"
|
|
578
|
+
"Typical: 3.0–10.0. Lower to detect very faint apertures.")
|
|
579
|
+
trace_layout.addRow("Detection SNR Threshold:", self.trace_snr_threshold_spin)
|
|
580
|
+
|
|
581
|
+
self.trace_aperture_boundary_snr_spin = QDoubleSpinBox()
|
|
582
|
+
self.trace_aperture_boundary_snr_spin.setRange(1.0, 20.0)
|
|
583
|
+
self.trace_aperture_boundary_snr_spin.setSingleStep(0.5)
|
|
584
|
+
self.trace_aperture_boundary_snr_spin.setToolTip(
|
|
585
|
+
"Aperture boundary SNR threshold: the aperture edge is where the signal\n"
|
|
586
|
+
"drops to N × σ above background. Lower = wider apertures. Typical 2.0-5.0.")
|
|
587
|
+
trace_layout.addRow("Aperture Boundary SNR:", self.trace_aperture_boundary_snr_spin)
|
|
588
|
+
|
|
589
|
+
self.trace_min_coverage_spin = QDoubleSpinBox()
|
|
590
|
+
self.trace_min_coverage_spin.setRange(0.05, 0.80)
|
|
591
|
+
self.trace_min_coverage_spin.setSingleStep(0.05)
|
|
592
|
+
self.trace_min_coverage_spin.setDecimals(2)
|
|
593
|
+
self.trace_min_coverage_spin.setToolTip(
|
|
594
|
+
"Minimum fraction of detector width an aperture must span.\n"
|
|
595
|
+
"Lower to accept faint partial apertures; raise to reject short false traces.")
|
|
596
|
+
trace_layout.addRow("Min Trace Coverage:", self.trace_min_coverage_spin)
|
|
597
|
+
|
|
598
|
+
self.trace_degree_spin = QSpinBox()
|
|
599
|
+
self.trace_degree_spin.setRange(1, 10)
|
|
600
|
+
self.trace_degree_spin.setToolTip(
|
|
601
|
+
"Polynomial degree for Chebyshev fit of the aperture trace (applies to both center and edges).")
|
|
602
|
+
trace_layout.addRow("Trace Degree:", self.trace_degree_spin)
|
|
603
|
+
|
|
604
|
+
self.trace_width_cheb_degree_spin = QSpinBox()
|
|
605
|
+
self.trace_width_cheb_degree_spin.setRange(1, 10)
|
|
606
|
+
self.trace_width_cheb_degree_spin.setToolTip("Chebyshev polynomial degree for fitting the aperture width.")
|
|
607
|
+
trace_layout.addRow("Width Cheb Degree:", self.trace_width_cheb_degree_spin)
|
|
608
|
+
|
|
609
|
+
# --- Gap-fill parameters (only relevant when checkbox is checked) ---
|
|
610
|
+
|
|
611
|
+
self.trace_gap_fill_factor_spin = QDoubleSpinBox()
|
|
612
|
+
self.trace_gap_fill_factor_spin.setRange(1.0, 4.0)
|
|
613
|
+
self.trace_gap_fill_factor_spin.setSingleStep(0.1)
|
|
614
|
+
trace_layout.addRow("Gap Fill Factor:", self.trace_gap_fill_factor_spin)
|
|
615
|
+
|
|
616
|
+
self.trace_gap_fill_factor_interp_spin = QDoubleSpinBox()
|
|
617
|
+
self.trace_gap_fill_factor_interp_spin.setRange(1.0, 4.0)
|
|
618
|
+
self.trace_gap_fill_factor_interp_spin.setSingleStep(0.05)
|
|
619
|
+
self.trace_gap_fill_factor_interp_spin.setToolTip("Gap factor to trigger missing-aperture insertion during interpolation.")
|
|
620
|
+
trace_layout.addRow("Gap Fill Factor (Interp):", self.trace_gap_fill_factor_interp_spin)
|
|
621
|
+
|
|
622
|
+
self.trace_n_extend_below_spin = QSpinBox()
|
|
623
|
+
self.trace_n_extend_below_spin.setRange(0, 10)
|
|
624
|
+
trace_layout.addRow("Extend Below (Step2):", self.trace_n_extend_below_spin)
|
|
625
|
+
|
|
626
|
+
self.trace_n_extend_above_spin = QSpinBox()
|
|
627
|
+
self.trace_n_extend_above_spin.setRange(0, 10)
|
|
628
|
+
trace_layout.addRow("Extend Above (Step2):", self.trace_n_extend_above_spin)
|
|
629
|
+
|
|
630
|
+
self.trace_n_mask_below_spin = QSpinBox()
|
|
631
|
+
self.trace_n_mask_below_spin.setRange(0, 10)
|
|
632
|
+
trace_layout.addRow("Mask Below (Step3):", self.trace_n_mask_below_spin)
|
|
633
|
+
|
|
634
|
+
self.trace_n_mask_above_spin = QSpinBox()
|
|
635
|
+
self.trace_n_mask_above_spin.setRange(0, 10)
|
|
636
|
+
trace_layout.addRow("Mask Above (Step3):", self.trace_n_mask_above_spin)
|
|
637
|
+
|
|
638
|
+
trace_group.setLayout(trace_layout)
|
|
639
|
+
layout.addRow(trace_group)
|
|
640
|
+
|
|
641
|
+
widget.setLayout(layout)
|
|
642
|
+
scroll.setWidget(widget)
|
|
643
|
+
# Initialize UI state
|
|
644
|
+
self._update_trace_param_interactions()
|
|
645
|
+
return scroll
|
|
646
|
+
|
|
647
|
+
# 更新追踪参数间的启用状态。
|
|
648
|
+
def _update_trace_param_interactions(self):
|
|
649
|
+
"""Enable/disable parameters (Placeholder, logic simplified)."""
|
|
650
|
+
pass
|
|
651
|
+
|
|
652
|
+
# 创建处理与输出设置页。
|
|
653
|
+
def _create_processing_tab(self) -> QWidget:
|
|
654
|
+
"""Create general processing and output settings tab."""
|
|
655
|
+
scroll = QScrollArea()
|
|
656
|
+
scroll.setWidgetResizable(True)
|
|
657
|
+
widget = QWidget()
|
|
658
|
+
layout = QFormLayout()
|
|
659
|
+
|
|
660
|
+
# Processing mode
|
|
661
|
+
self.mode_combo = QComboBox()
|
|
662
|
+
self.mode_combo.addItems(['normal', 'debug'])
|
|
663
|
+
layout.addRow("Processing Mode:", self.mode_combo)
|
|
664
|
+
|
|
665
|
+
# Figure format
|
|
666
|
+
self.fig_format_combo = QComboBox()
|
|
667
|
+
self.fig_format_combo.addItems(['png', 'pdf', 'jpg'])
|
|
668
|
+
layout.addRow("Figure Format:", self.fig_format_combo)
|
|
669
|
+
|
|
670
|
+
# Output suffix
|
|
671
|
+
self.oned_suffix_edit = QLineEdit()
|
|
672
|
+
layout.addRow("Output Suffix:", self.oned_suffix_edit)
|
|
673
|
+
|
|
674
|
+
# Auto process
|
|
675
|
+
self.auto_process_check = QCheckBox("Auto process all science images without confirmation")
|
|
676
|
+
layout.addRow(self.auto_process_check)
|
|
677
|
+
|
|
678
|
+
# Output options
|
|
679
|
+
output_group = QGroupBox("Output Options")
|
|
680
|
+
output_layout = QFormLayout()
|
|
681
|
+
|
|
682
|
+
self.save_plots_check = QCheckBox("Save diagnostic plots")
|
|
683
|
+
output_layout.addRow(self.save_plots_check)
|
|
684
|
+
|
|
685
|
+
self.save_overscan_check = QCheckBox("Save overscan correction results")
|
|
686
|
+
output_layout.addRow(self.save_overscan_check)
|
|
687
|
+
|
|
688
|
+
self.save_bias_check = QCheckBox("Save bias correction results")
|
|
689
|
+
output_layout.addRow(self.save_bias_check)
|
|
690
|
+
|
|
691
|
+
self.save_flat_check = QCheckBox("Save flat fielding results")
|
|
692
|
+
output_layout.addRow(self.save_flat_check)
|
|
693
|
+
|
|
694
|
+
self.save_background_check = QCheckBox("Save background subtraction results")
|
|
695
|
+
output_layout.addRow(self.save_background_check)
|
|
696
|
+
|
|
697
|
+
self.save_cosmic_check = QCheckBox("Save cosmic ray correction results")
|
|
698
|
+
output_layout.addRow(self.save_cosmic_check)
|
|
699
|
+
|
|
700
|
+
self.save_extraction_check = QCheckBox("Save spectrum extraction results")
|
|
701
|
+
output_layout.addRow(self.save_extraction_check)
|
|
702
|
+
|
|
703
|
+
self.save_wlcalib_check = QCheckBox("Save wavelength calibration results")
|
|
704
|
+
output_layout.addRow(self.save_wlcalib_check)
|
|
705
|
+
|
|
706
|
+
self.save_deblaze_check = QCheckBox("Save de-blazing results")
|
|
707
|
+
output_layout.addRow(self.save_deblaze_check)
|
|
708
|
+
|
|
709
|
+
output_group.setLayout(output_layout)
|
|
710
|
+
layout.addRow(output_group)
|
|
711
|
+
|
|
712
|
+
widget.setLayout(layout)
|
|
713
|
+
scroll.setWidget(widget)
|
|
714
|
+
return scroll
|
|
715
|
+
|
|
716
|
+
# 选择已标识谱线的锚点文件。
|
|
717
|
+
def _select_anchor_file(self):
|
|
718
|
+
"""Open file dialog to select anchor file."""
|
|
719
|
+
file_path, _ = QFileDialog.getOpenFileName(
|
|
720
|
+
self, "Select Anchor File", "", "CSV Files (*.csv);;Text Files (*.txt *.dat);;All Files (*)"
|
|
721
|
+
)
|
|
722
|
+
if file_path:
|
|
723
|
+
self.anchor_file_edit.setText(file_path)
|
|
724
|
+
|
|
725
|
+
# 选择完整谱线目录文件。
|
|
726
|
+
def _select_full_linelist_file(self):
|
|
727
|
+
"""Open file dialog to select full linelist catalog."""
|
|
728
|
+
file_path, _ = QFileDialog.getOpenFileName(
|
|
729
|
+
self, "Select Full Linelist Catalog", "", "Data Files (*.dat *.txt);;All Files (*)"
|
|
730
|
+
)
|
|
731
|
+
if file_path:
|
|
732
|
+
self.full_linelist_edit.setText(file_path)
|
|
733
|
+
|
|
734
|
+
# 选择预先标定结果文件。
|
|
735
|
+
def _select_precomputed_calib_file(self):
|
|
736
|
+
"""Open file dialog to select precomputed calibration FITS file."""
|
|
737
|
+
file_path, _ = QFileDialog.getOpenFileName(
|
|
738
|
+
self, "Select Precomputed Calibration File", "", "FITS Files (*.fits *.fit);;All Files (*)"
|
|
739
|
+
)
|
|
740
|
+
if file_path:
|
|
741
|
+
self.precomputed_calib_file_edit.setText(file_path)
|
|
742
|
+
|
|
743
|
+
# 将当前配置载入界面控件。
|
|
744
|
+
def load_current_values(self):
|
|
745
|
+
"""Load current configuration values into UI."""
|
|
746
|
+
# Data & Instruments tab
|
|
747
|
+
self.rawdata_path_edit.setText(self.config.get('data', 'rawdata_path', './rawdata'))
|
|
748
|
+
self.statime_key_edit.setText(self.config.get('data', 'statime_key', 'DATE-OBS'))
|
|
749
|
+
self.exptime_key_edit.setText(self.config.get('data', 'exptime_key', 'EXPTIME'))
|
|
750
|
+
self.direction_combo.setCurrentText(self.config.get('data', 'direction', 'xrb'))
|
|
751
|
+
self.output_path_edit.setText(self.config.get('reduce', 'output_path', 'output'))
|
|
752
|
+
self.detector_gain_spin.setValue(self.config.get_float('telescope.detector', 'gain', 1.0))
|
|
753
|
+
self.detector_readnoise_spin.setValue(self.config.get_float('telescope.detector', 'readnoise', 5.0))
|
|
754
|
+
|
|
755
|
+
# Data Reduction tab
|
|
756
|
+
overscan_val = self.config.get_int('data', 'overscan_start_column', -1)
|
|
757
|
+
self.overscan_start_column_spin.setValue(overscan_val)
|
|
758
|
+
self.overscan_method_combo.setCurrentText(self.config.get('data', 'overscan_method', 'mean_only'))
|
|
759
|
+
self.overscan_smooth_window_spin.setValue(self.config.get_int('data', 'overscan_smooth_window', -1))
|
|
760
|
+
self.overscan_poly_type_combo.setCurrentText(self.config.get('data', 'overscan_poly_type', 'legendre'))
|
|
761
|
+
self.overscan_poly_order_spin.setValue(self.config.get_int('data', 'overscan_poly_order', 3))
|
|
762
|
+
self.detector_split_row_spin.setValue(self.config.get_int('data', 'detector_split_row', 2068))
|
|
763
|
+
# Update enabled state of overscan params based on selected method
|
|
764
|
+
self._update_overscan_params_enabled()
|
|
765
|
+
self.bias_combine_method_combo.setCurrentText(self.config.get('reduce.bias', 'combine_method', 'median'))
|
|
766
|
+
self.bias_combine_sigma_spin.setValue(self.config.get_float('reduce.bias', 'combine_sigma', 3.0))
|
|
767
|
+
self.flat_combine_method_combo.setCurrentText(self.config.get('reduce.flat', 'combine_method', 'median'))
|
|
768
|
+
self.flat_q_threshold_spin.setValue(self.config.get_float('reduce.flat', 'q_threshold', 0.5))
|
|
769
|
+
self.flat_mosaic_maxcount_spin.setValue(self.config.get_float('reduce.flat', 'mosaic_maxcount', 65535.0))
|
|
770
|
+
self.flat_blaze_smooth_factor_spin.setValue(self.config.get_float('reduce.flat', 'blaze_smooth_factor', 1.0))
|
|
771
|
+
self.flat_fringe_orders_spin.setValue(self.config.get_int('reduce.flat', 'fringe_orders', 20))
|
|
772
|
+
self.flat_width_smooth_window_spin.setValue(self.config.get_int('reduce.flat', 'width_smooth_window', 41))
|
|
773
|
+
self.flat_n_profile_segments_spin.setValue(self.config.get_int('reduce.flat', 'n_profile_segments', 100))
|
|
774
|
+
self.flat_profile_smooth_sigma_spin.setValue(self.config.get_float('reduce.flat', 'profile_smooth_sigma', 6.0))
|
|
775
|
+
self.flat_profile_bin_step_spin.setValue(self.config.get_float('reduce.flat', 'profile_bin_step', 0.01))
|
|
776
|
+
self.flat_pixel_min_spin.setValue(self.config.get_float('reduce.flat', 'pixel_flat_min', 0.5))
|
|
777
|
+
self.flat_pixel_max_spin.setValue(self.config.get_float('reduce.flat', 'pixel_flat_max', 1.5))
|
|
778
|
+
bg_method = self.config.get('reduce.background', 'method', 'convolution')
|
|
779
|
+
if bg_method not in ['convolution', 'column_spline', 'bspline']:
|
|
780
|
+
bg_method = 'convolution'
|
|
781
|
+
self.bg_method_combo.setCurrentText(bg_method)
|
|
782
|
+
self.bg_kernel_sigma_x_spin.setValue(self.config.get_float('reduce.background', 'kernel_sigma_x', 13.0))
|
|
783
|
+
self.bg_kernel_sigma_y_spin.setValue(self.config.get_float('reduce.background', 'kernel_sigma_y', 13.0))
|
|
784
|
+
self.bg_spline_smooth_factor_spin.setValue(self.config.get_float('reduce.background', 'spline_smooth_factor', 1.0))
|
|
785
|
+
self.bg_spline_post_smooth_x_spin.setValue(self.config.get_float('reduce.background', 'spline_post_smooth_x', 5.0))
|
|
786
|
+
self.bg_poly_order_spin.setValue(self.config.get_int('reduce.background', 'poly_order', 3))
|
|
787
|
+
self.bg_sigma_clip_spin.setValue(self.config.get_float('reduce.background', 'sigma_clip', 3.0))
|
|
788
|
+
self.bg_sigma_clip_iter_spin.setValue(self.config.get_int('reduce.background', 'sigma_clip_maxiters', 4))
|
|
789
|
+
clip_mode = self.config.get('reduce.background', 'sigma_clip_mode', 'upper')
|
|
790
|
+
self.bg_clip_mode_combo.setCurrentText(clip_mode)
|
|
791
|
+
self.bg_mask_margin_spin.setValue(self.config.get_int('reduce.background', 'mask_margin_pixels', 1))
|
|
792
|
+
self.bg_bspline_smooth_spin.setValue(self.config.get_float('reduce.background', 'bspline_smooth', 1.0))
|
|
793
|
+
self._update_bg_params_enabled()
|
|
794
|
+
self.cosmic_enabled_check.setChecked(self.config.get_bool('reduce', 'cosmic_enabled', 'yes'))
|
|
795
|
+
self.cosmic_sigclip_spin.setValue(self.config.get_float('reduce', 'cosmic_sigclip', 5.0))
|
|
796
|
+
self.cosmic_objlim_spin.setValue(self.config.get_float('reduce', 'cosmic_objlim', 5.0))
|
|
797
|
+
self.cosmic_gain_spin.setValue(self.config.get_float('reduce', 'cosmic_gain', 1.0))
|
|
798
|
+
|
|
799
|
+
# Wavelength Calibration tab
|
|
800
|
+
self.linelist_combo.setCurrentText(self.config.get('telescope.linelist', 'linelist_type', 'ThAr'))
|
|
801
|
+
self.anchor_file_edit.setText(self.config.get('telescope.linelist', 'anchor_file', 'calib_data/telescopes/xinglong216hrs/xinglong_thar_lines.csv'))
|
|
802
|
+
self.full_linelist_edit.setText(self.config.get('telescope.linelist', 'full_linelist', 'calib_data/linelists/thar-noao.dat'))
|
|
803
|
+
self.use_precomputed_calib_check.setChecked(self.config.get_bool('telescope.linelist', 'use_precomputed_calibration', False))
|
|
804
|
+
self.precomputed_calib_file_edit.setText(self.config.get('telescope.linelist', 'precomputed_calib_file', 'calib_data/telescopes/xinglong216hrs/wlcalib_20211123011_A.fits'))
|
|
805
|
+
self.wlcalib_xorder_spin.setValue(self.config.get_int('reduce.wlcalib', 'xorder', 4))
|
|
806
|
+
self.wlcalib_yorder_spin.setValue(self.config.get_int('reduce.wlcalib', 'yorder', 4))
|
|
807
|
+
self.wlcalib_match_tol_spin.setValue(self.config.get_float('reduce.wlcalib', 'match_tolerance', 2.0))
|
|
808
|
+
self.wlcalib_rms_threshold_spin.setValue(self.config.get_float('reduce.wlcalib', 'rms_threshold', 0.5))
|
|
809
|
+
self.wlcalib_discard_highest_spin.setValue(self.config.get_int('reduce.wlcalib', 'discard_highest_apertures', 20))
|
|
810
|
+
self.wlcalib_discard_lowest_spin.setValue(self.config.get_int('reduce.wlcalib', 'discard_lowest_apertures', 10))
|
|
811
|
+
|
|
812
|
+
# Orders Tracing tab
|
|
813
|
+
self.trace_snr_threshold_spin.setValue(self.config.get_float('reduce.trace', 'snr_threshold', 5.0))
|
|
814
|
+
self.trace_aperture_boundary_snr_spin.setValue(self.config.get_float('reduce.trace', 'aperture_boundary_snr', 3.0))
|
|
815
|
+
self.trace_gap_fill_factor_spin.setValue(self.config.get_float('reduce.trace', 'gap_fill_factor', 1.6))
|
|
816
|
+
self.trace_gap_fill_factor_interp_spin.setValue(self.config.get_float('reduce.trace', 'gap_fill_factor_interp', 1.35))
|
|
817
|
+
self.trace_min_coverage_spin.setValue(self.config.get_float('reduce.trace', 'min_trace_coverage', 0.20))
|
|
818
|
+
self.trace_degree_spin.setValue(self.config.get_int('reduce.trace', 'degree', 4))
|
|
819
|
+
self.trace_width_cheb_degree_spin.setValue(self.config.get_int('reduce.trace', 'width_cheb_degree', 3))
|
|
820
|
+
self.trace_n_extend_below_spin.setValue(self.config.get_int('reduce.trace', 'n_extend_below', 0))
|
|
821
|
+
self.trace_n_extend_above_spin.setValue(self.config.get_int('reduce.trace', 'n_extend_above', 0))
|
|
822
|
+
self.trace_n_mask_below_spin.setValue(self.config.get_int('reduce.trace', 'n_mask_below', 4))
|
|
823
|
+
self.trace_n_mask_above_spin.setValue(self.config.get_int('reduce.trace', 'n_mask_above', 4))
|
|
824
|
+
|
|
825
|
+
# Processing & Output tab
|
|
826
|
+
self.mode_combo.setCurrentText(self.config.get('reduce', 'mode', 'normal'))
|
|
827
|
+
self.fig_format_combo.setCurrentText(self.config.get('reduce', 'fig_format', 'png'))
|
|
828
|
+
self.oned_suffix_edit.setText(self.config.get('reduce', 'oned_suffix', '_ods'))
|
|
829
|
+
self.auto_process_check.setChecked(self.config.get_bool('reduce', 'auto_process_all_images', False))
|
|
830
|
+
self.save_plots_check.setChecked(self.config.get_bool('reduce', 'save_plots', True))
|
|
831
|
+
self.save_overscan_check.setChecked(self.config.get_bool('reduce.save_intermediate', 'save_overscan', True))
|
|
832
|
+
self.save_bias_check.setChecked(self.config.get_bool('reduce.save_intermediate', 'save_bias', True))
|
|
833
|
+
self.save_flat_check.setChecked(self.config.get_bool('reduce.save_intermediate', 'save_flat', True))
|
|
834
|
+
self.save_background_check.setChecked(self.config.get_bool('reduce.save_intermediate', 'save_background', True))
|
|
835
|
+
self.save_cosmic_check.setChecked(self.config.get_bool('reduce.save_intermediate', 'save_cosmic', True))
|
|
836
|
+
self.save_extraction_check.setChecked(self.config.get_bool('reduce.save_intermediate', 'save_extraction', True))
|
|
837
|
+
self.save_wlcalib_check.setChecked(self.config.get_bool('reduce.save_intermediate', 'save_wlcalib', True))
|
|
838
|
+
self.save_deblaze_check.setChecked(self.config.get_bool('reduce.save_intermediate', 'save_deblaze', True))
|
|
839
|
+
|
|
840
|
+
# 保存界面中的配置项。
|
|
841
|
+
def _save_settings(self):
|
|
842
|
+
"""Save settings to configuration."""
|
|
843
|
+
try:
|
|
844
|
+
# Data & Instruments tab
|
|
845
|
+
self.config.set('data', 'rawdata_path', self.rawdata_path_edit.text())
|
|
846
|
+
self.config.set('data', 'statime_key', self.statime_key_edit.text())
|
|
847
|
+
self.config.set('data', 'exptime_key', self.exptime_key_edit.text())
|
|
848
|
+
self.config.set('data', 'direction', self.direction_combo.currentText())
|
|
849
|
+
self.config.set('reduce', 'output_path', self.output_path_edit.text())
|
|
850
|
+
self.config.set('telescope.detector', 'gain', str(self.detector_gain_spin.value()))
|
|
851
|
+
self.config.set('telescope.detector', 'readnoise', str(self.detector_readnoise_spin.value()))
|
|
852
|
+
|
|
853
|
+
# Data Reduction tab
|
|
854
|
+
self.config.set('data', 'overscan_start_column', str(self.overscan_start_column_spin.value()))
|
|
855
|
+
self.config.set('data', 'overscan_method', self.overscan_method_combo.currentText())
|
|
856
|
+
self.config.set('data', 'overscan_smooth_window', str(self.overscan_smooth_window_spin.value()))
|
|
857
|
+
self.config.set('data', 'overscan_poly_type', self.overscan_poly_type_combo.currentText())
|
|
858
|
+
self.config.set('data', 'overscan_poly_order', str(self.overscan_poly_order_spin.value()))
|
|
859
|
+
self.config.set('data', 'detector_split_row', str(self.detector_split_row_spin.value()))
|
|
860
|
+
self.config.set('reduce.bias', 'combine_method', self.bias_combine_method_combo.currentText())
|
|
861
|
+
self.config.set('reduce.bias', 'combine_sigma', str(self.bias_combine_sigma_spin.value()))
|
|
862
|
+
self.config.set('reduce.flat', 'combine_method', self.flat_combine_method_combo.currentText())
|
|
863
|
+
self.config.set('reduce.flat', 'q_threshold', str(self.flat_q_threshold_spin.value()))
|
|
864
|
+
self.config.set('reduce.flat', 'mosaic_maxcount', str(self.flat_mosaic_maxcount_spin.value()))
|
|
865
|
+
self.config.set('reduce.flat', 'blaze_smooth_factor', str(self.flat_blaze_smooth_factor_spin.value()))
|
|
866
|
+
self.config.set('reduce.flat', 'fringe_orders', str(self.flat_fringe_orders_spin.value()))
|
|
867
|
+
self.config.set('reduce.flat', 'width_smooth_window', str(self.flat_width_smooth_window_spin.value()))
|
|
868
|
+
self.config.set('reduce.flat', 'n_profile_segments', str(self.flat_n_profile_segments_spin.value()))
|
|
869
|
+
self.config.set('reduce.flat', 'profile_smooth_sigma', str(self.flat_profile_smooth_sigma_spin.value()))
|
|
870
|
+
self.config.set('reduce.flat', 'profile_bin_step', str(self.flat_profile_bin_step_spin.value()))
|
|
871
|
+
self.config.set('reduce.flat', 'pixel_flat_min', str(self.flat_pixel_min_spin.value()))
|
|
872
|
+
self.config.set('reduce.flat', 'pixel_flat_max', str(self.flat_pixel_max_spin.value()))
|
|
873
|
+
self.config.set('reduce.background', 'method', self.bg_method_combo.currentText())
|
|
874
|
+
self.config.set('reduce.background', 'kernel_sigma_x', str(self.bg_kernel_sigma_x_spin.value()))
|
|
875
|
+
self.config.set('reduce.background', 'kernel_sigma_y', str(self.bg_kernel_sigma_y_spin.value()))
|
|
876
|
+
self.config.set('reduce.background', 'spline_smooth_factor', str(self.bg_spline_smooth_factor_spin.value()))
|
|
877
|
+
self.config.set('reduce.background', 'spline_post_smooth_x', str(self.bg_spline_post_smooth_x_spin.value()))
|
|
878
|
+
self.config.set('reduce.background', 'poly_order', str(self.bg_poly_order_spin.value()))
|
|
879
|
+
self.config.set('reduce.background', 'sigma_clip', str(self.bg_sigma_clip_spin.value()))
|
|
880
|
+
self.config.set('reduce.background', 'sigma_clip_maxiters', str(self.bg_sigma_clip_iter_spin.value()))
|
|
881
|
+
self.config.set('reduce.background', 'sigma_clip_mode', self.bg_clip_mode_combo.currentText())
|
|
882
|
+
self.config.set('reduce.background', 'mask_margin_pixels', str(self.bg_mask_margin_spin.value()))
|
|
883
|
+
self.config.set('reduce.background', 'bspline_smooth', str(self.bg_bspline_smooth_spin.value()))
|
|
884
|
+
self.config.set('reduce', 'cosmic_enabled', 'yes' if self.cosmic_enabled_check.isChecked() else 'no')
|
|
885
|
+
self.config.set('reduce', 'cosmic_sigclip', str(self.cosmic_sigclip_spin.value()))
|
|
886
|
+
self.config.set('reduce', 'cosmic_objlim', str(self.cosmic_objlim_spin.value()))
|
|
887
|
+
self.config.set('reduce', 'cosmic_gain', str(self.cosmic_gain_spin.value()))
|
|
888
|
+
|
|
889
|
+
# Wavelength Calibration tab
|
|
890
|
+
self.config.set('telescope.linelist', 'linelist_type', self.linelist_combo.currentText())
|
|
891
|
+
self.config.set('telescope.linelist', 'anchor_file', self.anchor_file_edit.text())
|
|
892
|
+
self.config.set('telescope.linelist', 'full_linelist', self.full_linelist_edit.text())
|
|
893
|
+
self.config.set('telescope.linelist', 'use_precomputed_calibration',
|
|
894
|
+
'yes' if self.use_precomputed_calib_check.isChecked() else 'no')
|
|
895
|
+
self.config.set('telescope.linelist', 'precomputed_calib_file', self.precomputed_calib_file_edit.text())
|
|
896
|
+
self.config.set('reduce.wlcalib', 'xorder', str(self.wlcalib_xorder_spin.value()))
|
|
897
|
+
self.config.set('reduce.wlcalib', 'yorder', str(self.wlcalib_yorder_spin.value()))
|
|
898
|
+
self.config.set('reduce.wlcalib', 'match_tolerance', str(self.wlcalib_match_tol_spin.value()))
|
|
899
|
+
self.config.set('reduce.wlcalib', 'rms_threshold', str(self.wlcalib_rms_threshold_spin.value()))
|
|
900
|
+
self.config.set('reduce.wlcalib', 'discard_highest_apertures', str(self.wlcalib_discard_highest_spin.value()))
|
|
901
|
+
self.config.set('reduce.wlcalib', 'discard_lowest_apertures', str(self.wlcalib_discard_lowest_spin.value()))
|
|
902
|
+
|
|
903
|
+
# Orders Tracing tab
|
|
904
|
+
self.config.set('reduce.trace', 'snr_threshold', str(self.trace_snr_threshold_spin.value()))
|
|
905
|
+
self.config.set('reduce.trace', 'aperture_boundary_snr', str(self.trace_aperture_boundary_snr_spin.value()))
|
|
906
|
+
self.config.set('reduce.trace', 'gap_fill_factor', str(self.trace_gap_fill_factor_spin.value()))
|
|
907
|
+
self.config.set('reduce.trace', 'gap_fill_factor_interp', str(self.trace_gap_fill_factor_interp_spin.value()))
|
|
908
|
+
self.config.set('reduce.trace', 'min_trace_coverage', str(self.trace_min_coverage_spin.value()))
|
|
909
|
+
self.config.set('reduce.trace', 'degree', str(self.trace_degree_spin.value()))
|
|
910
|
+
self.config.set('reduce.trace', 'width_cheb_degree', str(self.trace_width_cheb_degree_spin.value()))
|
|
911
|
+
self.config.set('reduce.trace', 'n_extend_below', str(self.trace_n_extend_below_spin.value()))
|
|
912
|
+
self.config.set('reduce.trace', 'n_extend_above', str(self.trace_n_extend_above_spin.value()))
|
|
913
|
+
self.config.set('reduce.trace', 'n_mask_below', str(self.trace_n_mask_below_spin.value()))
|
|
914
|
+
self.config.set('reduce.trace', 'n_mask_above', str(self.trace_n_mask_above_spin.value()))
|
|
915
|
+
|
|
916
|
+
# Processing & Output tab
|
|
917
|
+
self.config.set('reduce', 'mode', self.mode_combo.currentText())
|
|
918
|
+
self.config.set('reduce', 'fig_format', self.fig_format_combo.currentText())
|
|
919
|
+
self.config.set('reduce', 'oned_suffix', self.oned_suffix_edit.text())
|
|
920
|
+
self.config.set('reduce', 'auto_process_all_images', 'yes' if self.auto_process_check.isChecked() else 'no')
|
|
921
|
+
self.config.set('reduce', 'save_plots', 'yes' if self.save_plots_check.isChecked() else 'no')
|
|
922
|
+
self.config.set('reduce.save_intermediate', 'save_overscan',
|
|
923
|
+
'yes' if self.save_overscan_check.isChecked() else 'no')
|
|
924
|
+
self.config.set('reduce.save_intermediate', 'save_bias',
|
|
925
|
+
'yes' if self.save_bias_check.isChecked() else 'no')
|
|
926
|
+
self.config.set('reduce.save_intermediate', 'save_flat',
|
|
927
|
+
'yes' if self.save_flat_check.isChecked() else 'no')
|
|
928
|
+
self.config.set('reduce.save_intermediate', 'save_background',
|
|
929
|
+
'yes' if self.save_background_check.isChecked() else 'no')
|
|
930
|
+
self.config.set('reduce.save_intermediate', 'save_cosmic',
|
|
931
|
+
'yes' if self.save_cosmic_check.isChecked() else 'no')
|
|
932
|
+
self.config.set('reduce.save_intermediate', 'save_extraction',
|
|
933
|
+
'yes' if self.save_extraction_check.isChecked() else 'no')
|
|
934
|
+
self.config.set('reduce.save_intermediate', 'save_wlcalib',
|
|
935
|
+
'yes' if self.save_wlcalib_check.isChecked() else 'no')
|
|
936
|
+
self.config.set('reduce.save_intermediate', 'save_deblaze',
|
|
937
|
+
'yes' if self.save_deblaze_check.isChecked() else 'no')
|
|
938
|
+
|
|
939
|
+
# Save to file
|
|
940
|
+
self.config.save()
|
|
941
|
+
|
|
942
|
+
self.accept()
|
|
943
|
+
|
|
944
|
+
except Exception as e:
|
|
945
|
+
from PyQt5.QtWidgets import QMessageBox
|
|
946
|
+
QMessageBox.critical(self, "Error", f"Failed to save settings: {e}")
|