Skip to content

Commit a23eb33

Browse files
committed
create a own CAL product "level"
1 parent b1a7447 commit a23eb33

14 files changed

Lines changed: 561 additions & 535 deletions

File tree

.github/workflows/end2end_run.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
python -m pip install -e .[dev]
4444
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
4545
- name: show environment
46-
run: python -m pip freeze
46+
run: python -m pip freeze
4747
- name: end2end test with pytest
4848
run: |
4949
pytest -p no:warnings --doctest-rst -m "end2end" --pyargs stixcore --remote-data=any
Lines changed: 100 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
"""
2-
Author: O. Limousin, CEA
3-
Date: Oct 23, 2024
4-
This script to:
5-
6-
#%%%%%%%%%%%%%% FOR fit of ECC_ULTRA_FINE %%%%%%%%%%%%%%
7-
../00_CALIBRATION_MONITORING_ULTRA_FINE/02_MONITOR_ECC_SPECTRA ==> XX_erg.fits
8-
9-
../00_CALIBRATION_MONITORING_ULTRA_FINE/01_MONITOR_ECC_PARA/ ==> ECC_para_XXXX.fits
10-
11-
+ read the log (.xlsx file)
12-
+ open the ECC calibrated spectra XX_erg.fits
13-
+ fit 31 and 81 keV lines (Fit_Ba_robust) (fit right hand side, and baseline)
14-
+ register the results in a Pandas Dataframe
15-
+ date, Run Number, Fit Goodness flags,DET, PIX,
16-
P31, err_P31, dE31, err_dE31, H31
17-
P81, err_P81, dE81, err_dE81, H81
18-
+ compute gain/offset corrections
19-
+ fill the dataframe with new values
20-
+ include errors
21-
+ include a Flag (Flag31 and Flag81) which is True if fit was OK
22-
if was not OK, the ECC value in NOT corrected
23-
+ store the results in pkl
24-
+ generate update ECC_para files to recalibrate uncalibrated files
2+
Author: O. Limousin, CEA
3+
Date: Oct 23, 2024
4+
This script to:
5+
6+
#%%%%%%%%%%%%%% FOR fit of ECC_ULTRA_FINE %%%%%%%%%%%%%%
7+
../00_CALIBRATION_MONITORING_ULTRA_FINE/02_MONITOR_ECC_SPECTRA ==> XX_erg.fits
8+
9+
../00_CALIBRATION_MONITORING_ULTRA_FINE/01_MONITOR_ECC_PARA/ ==> ECC_para_XXXX.fits
10+
11+
+ read the log (.xlsx file)
12+
+ open the ECC calibrated spectra XX_erg.fits
13+
+ fit 31 and 81 keV lines (Fit_Ba_robust) (fit right hand side, and baseline)
14+
+ register the results in a Pandas Dataframe
15+
+ date, Run Number, Fit Goodness flags,DET, PIX,
16+
P31, err_P31, dE31, err_dE31, H31
17+
P81, err_P81, dE81, err_dE81, H81
18+
+ compute gain/offset corrections
19+
+ fill the dataframe with new values
20+
+ include errors
21+
+ include a Flag (Flag31 and Flag81) which is True if fit was OK
22+
if was not OK, the ECC value in NOT corrected
23+
+ store the results in pkl
24+
+ generate update ECC_para files to recalibrate uncalibrated files
2525
2626
"""
2727

@@ -49,43 +49,42 @@ def open_fits_tables(fits_path):
4949
data_names = [data_i.columns.names for data_i in data]
5050
data_format = [data_i.columns.formats for data_i in data]
5151
data_unit = [data_i.columns.units for data_i in data]
52-
data_list = [[list(data[i][data_names[i][j]]) for j in range(len(data_names[i]))] for i in
53-
range(len(data))]
52+
data_list = [[list(data[i][data_names[i][j]]) for j in range(len(data_names[i]))] for i in range(len(data))]
5453
return header, data, data_list, data_names, data_format, data_unit
5554

5655

5756
def Read_fits_STIX_One_Pixel(data, PIX=0, DETECTOR_ID=1, Nbin=2024, NRebin=1, NSigma=1):
5857
data = data[0]
5958
Pix = PIX
60-
Pix = Pix + (DETECTOR_ID-1)*12
59+
Pix = Pix + (DETECTOR_ID - 1) * 12
6160
obs = [0] * Nbin
6261
erg_c = data.ERG_center # data.field(0)
63-
obs = data.field(3+Pix)
62+
obs = data.field(3 + Pix)
6463
nbin = Nbin
65-
Nbin = [Nbin/NRebin]
64+
Nbin = [Nbin / NRebin]
6665
# TODO: check if we really need to rebin with congrid here
6766
# erg_c = congrid(erg_c[:nbin], Nbin)
6867
# obs = congrid(obs[:nbin], Nbin)
6968
erg_c = erg_c[:nbin]
7069
obs = obs[:nbin]
7170

72-
yerr = NSigma*np.sqrt(obs)
71+
yerr = NSigma * np.sqrt(obs)
7372
return erg_c, obs, yerr
7473

7574

7675
def line(x, slope, intercept):
7776
"""a line"""
78-
return (slope*x + intercept)
77+
return slope * x + intercept
7978

8079

8180
def poly(x, degree, slope, intercept):
8281
"""a line"""
83-
return (degree*x*x + slope*x + intercept)
82+
return degree * x * x + slope * x + intercept
8483

8584

8685
def gaussian(x, amp, cen, wid):
8786
# """1-d gaussian: gaussian(x, amp, cen, wid)"""
88-
return (amp*np.exp(-(x-cen)**2 / (2*wid**2)))
87+
return amp * np.exp(-((x - cen) ** 2) / (2 * wid**2))
8988

9089

9190
def Fit_Ba_Lines_Robust(erg_c, obs, PLOT_VERBOSE=1, LogScale=1):
@@ -97,66 +96,68 @@ def Fit_Ba_Lines_Robust(erg_c, obs, PLOT_VERBOSE=1, LogScale=1):
9796
+ flag to say if the fit was successful or not
9897
"""
9998
# Select Energy range for 81 keV Ba-133 line
100-
pipo = ((erg_c > 80.5) & (erg_c < 90.)).nonzero()
101-
y = (obs[pipo])
102-
x = (erg_c[pipo])
103-
x = np.array(x, dtype='float64')
104-
y = np.array(y, dtype='float64')
99+
pipo = ((erg_c > 80.5) & (erg_c < 90.0)).nonzero()
100+
y = obs[pipo]
101+
x = erg_c[pipo]
102+
x = np.array(x, dtype="float64")
103+
y = np.array(y, dtype="float64")
105104
mod = Model(gaussian) + Model(line)
106105
pars = mod.make_params(amp=10, cen=81, wid=0.5, slope=0, intercept=0)
107106
result = mod.fit(y, pars, x=x)
108107

109-
if ((result.params['wid'].stderr is not None) &
110-
(result.params['cen'].stderr is not None) &
111-
(np.abs(result.best_values['cen'] - 81.) < 1.)):
112-
dE81 = result.best_values['wid']*2.35
113-
err_dE81 = result.params['wid'].stderr*2.35
114-
P81 = result.best_values['cen']
115-
err_P81 = result.params['cen'].stderr
116-
H81 = result.best_values['amp']
108+
if (
109+
(result.params["wid"].stderr is not None)
110+
& (result.params["cen"].stderr is not None)
111+
& (np.abs(result.best_values["cen"] - 81.0) < 1.0)
112+
):
113+
dE81 = result.best_values["wid"] * 2.35
114+
err_dE81 = result.params["wid"].stderr * 2.35
115+
P81 = result.best_values["cen"]
116+
err_P81 = result.params["cen"].stderr
117+
H81 = result.best_values["amp"]
117118
Goodness_Flag_81 = True
118119
else:
119120
dE81, err_dE81, P81, err_P81, H81 = 0, 0, 0, 0, 0
120121
Goodness_Flag_81 = False
121122

122123
# Select Energy range for 81 keV Ba-133 line
123-
# THE FOLLWING SECTION IS QUIET ROBUST BUT MIGHT OVERESTIMATE THE ENERGY RESOLUTION AT 32 keV
124-
# THIS ALLOWS TO FORCE THE BASE LINE TO ADJSUT in 40-45 keV range while a simple Gaussian
124+
# THE FOLLOWING SECTION IS QUIET ROBUST BUT MIGHT OVERESTIMATE THE ENERGY RESOLUTION AT 32 keV
125+
# THIS ALLOWS TO FORCE THE BASE LINE TO ADJUST in 40-45 keV range while a simple Gaussian
125126
# is used to fit the right hand side of the 32 keV Line
126127
pipo = (((erg_c > 30.2) & (erg_c < 33.0)) | ((erg_c > 40) & (erg_c < 45))).nonzero()
127128
y = obs[pipo]
128129
x = erg_c[pipo]
129-
x = np.array(x, dtype='float64')
130-
y = np.array(y, dtype='float64')
130+
x = np.array(x, dtype="float64")
131+
y = np.array(y, dtype="float64")
131132

132-
mod = Model(gaussian, prefix='g1_') + Model(poly)
133-
pars = mod.make_params(g1_amp=10, g1_cen=30.6, g1_wid=0.4, degree=0., slope=0, intercept=0.)
133+
mod = Model(gaussian, prefix="g1_") + Model(poly)
134+
pars = mod.make_params(g1_amp=10, g1_cen=30.6, g1_wid=0.4, degree=0.0, slope=0, intercept=0.0)
134135

135136
result = mod.fit(y, pars, x=x)
136137

137-
if ((result.params['g1_wid'].stderr is not None) &
138-
(result.params['g1_cen'].stderr is not None) &
139-
((np.abs(result.best_values['g1_cen'] - 31.) < 1.) & (Goodness_Flag_81))):
140-
dE31 = result.best_values['g1_wid']*2.35
141-
err_dE31 = result.params['g1_wid'].stderr*2.35
142-
P31 = result.best_values['g1_cen']
143-
err_P31 = result.params['g1_cen'].stderr
144-
H31 = result.best_values['g1_amp']
138+
if (
139+
(result.params["g1_wid"].stderr is not None)
140+
& (result.params["g1_cen"].stderr is not None)
141+
& ((np.abs(result.best_values["g1_cen"] - 31.0) < 1.0) & (Goodness_Flag_81))
142+
):
143+
dE31 = result.best_values["g1_wid"] * 2.35
144+
err_dE31 = result.params["g1_wid"].stderr * 2.35
145+
P31 = result.best_values["g1_cen"]
146+
err_P31 = result.params["g1_cen"].stderr
147+
H31 = result.best_values["g1_amp"]
145148
Goodness_Flag_31 = True
146149
else:
147150
dE31, err_dE31, P31, err_P31, H31 = 0, 0, 0, 0, 0
148151
Goodness_Flag_31 = False
149152

150-
return P31, P81, dE31, dE81, err_P31, err_P81, err_dE31, err_dE81, \
151-
H31, H81, Goodness_Flag_31, Goodness_Flag_81
153+
return P31, P81, dE31, dE81, err_P31, err_P81, err_dE31, err_dE81, H31, H81, Goodness_Flag_31, Goodness_Flag_81
152154

153155

154156
def ecc_post_fit(erg_file, para_file, livetime):
155-
156-
DETs = np.arange(32)+1
157+
DETs = np.arange(32) + 1
157158
LARGEs = [0, 1, 2, 3, 4, 5, 6, 7]
158159
SMALLs = [8, 9, 10, 11]
159-
PIXELs = LARGEs+SMALLs
160+
PIXELs = LARGEs + SMALLs
160161

161162
# Prep Pandas DataFrame
162163
df = pd.DataFrame() # dict will be define later during concactenation with appropriated keys
@@ -166,63 +167,63 @@ def ecc_post_fit(erg_file, para_file, livetime):
166167
data = open_fits_tables(erg_file)[1] # only need data
167168
for DET in DETs:
168169
for PIX in PIXELs: # or in LARGEs, SMALLs
169-
erg_c, obs, yerr = Read_fits_STIX_One_Pixel(data, PIX=PIX, DETECTOR_ID=DET,
170-
Nbin=2024, NRebin=1, NSigma=1)
171-
172-
P31, P81, dE31, dE81, err_P31, err_P81, err_dE31, err_dE81, H31, H81, Goodness_Flag31, \
173-
Goodness_Flag81 = Fit_Ba_Lines_Robust(erg_c, obs/livetime,
174-
PLOT_VERBOSE=0, LogScale=0)
175-
176-
dict = {'DET': [DET],
177-
'PIX': [PIX],
178-
'P31': [P31],
179-
'err_P31': [err_P31],
180-
'dE31': [dE31],
181-
'err_dE31': [err_dE31],
182-
'Flag31': [Goodness_Flag31],
183-
'P81': [P81],
184-
'err_P81': [err_P81],
185-
'dE81': [dE81],
186-
'err_dE81': [err_dE81],
187-
'Flag81': [Goodness_Flag81]}
170+
erg_c, obs, yerr = Read_fits_STIX_One_Pixel(data, PIX=PIX, DETECTOR_ID=DET, Nbin=2024, NRebin=1, NSigma=1)
171+
172+
P31, P81, dE31, dE81, err_P31, err_P81, err_dE31, err_dE81, H31, H81, Goodness_Flag31, Goodness_Flag81 = (
173+
Fit_Ba_Lines_Robust(erg_c, obs / livetime, PLOT_VERBOSE=0, LogScale=0)
174+
)
175+
176+
dict = {
177+
"DET": [DET],
178+
"PIX": [PIX],
179+
"P31": [P31],
180+
"err_P31": [err_P31],
181+
"dE31": [dE31],
182+
"err_dE31": [err_dE31],
183+
"Flag31": [Goodness_Flag31],
184+
"P81": [P81],
185+
"err_P81": [err_P81],
186+
"dE81": [dE81],
187+
"err_dE81": [err_dE81],
188+
"Flag81": [Goodness_Flag81],
189+
}
188190
logger.debug(dict)
189191

190192
# NB: this is faster to append a list of dict and create DataFrame at the end
191-
# than concatening the DataFrame row by row, this slows down dramatically progressively
193+
# than concatenating the DataFrame row by row, this slows down dramatically progressively
192194
accumulator.append(pd.DataFrame(dict))
193195

194196
df = pd.concat(accumulator)
195197
df = df.reset_index(drop=True)
196198

197199
# 3- gain and offset correction factors of ECC pre-calibrated data
198-
G_prime = (df['P81']-df['P31'])/(80.9979-(30.6254*33.8 + 30.9731 * 62.4)/(62.4+33.8))
199-
O_prime = df['P31'] - G_prime*(30.6254*33.8 + 30.9731 * 62.4)/(62.4+33.8)
200+
G_prime = (df["P81"] - df["P31"]) / (80.9979 - (30.6254 * 33.8 + 30.9731 * 62.4) / (62.4 + 33.8))
201+
O_prime = df["P31"] - G_prime * (30.6254 * 33.8 + 30.9731 * 62.4) / (62.4 + 33.8)
200202

201203
# 4- add correction factors to the DataFrame
202-
df['Gain_Prime'] = G_prime
203-
df['Offset_Prime'] = O_prime
204+
df["Gain_Prime"] = G_prime
205+
df["Offset_Prime"] = O_prime
204206

205207
# check, Run number and pixel number prior to assign Gain and Offset for further correction
206208
data_para = open_fits_tables(para_file)[1] # only need data
207209

208-
df['Gain_ECC'] = np.float32(data_para[0].gain)
209-
df['Offset_ECC'] = np.float32(data_para[0].off)
210-
df['goc'] = np.float32(data_para[0].goc)
210+
df["Gain_ECC"] = np.float32(data_para[0].gain)
211+
df["Offset_ECC"] = np.float32(data_para[0].off)
212+
df["goc"] = np.float32(data_para[0].goc)
211213

212214
# 7 - Compute corrected Gain and Offset and fill df
213215
# 7.2 - Now assign the corrected Gain and Offset values
214216
# except when ECC works better
215-
df['Gain_Cor'] = 0.0
216-
df['Offset_Cor'] = 0.0
217+
df["Gain_Cor"] = 0.0
218+
df["Offset_Cor"] = 0.0
217219

218220
# apply correction to gain and offset when fit is ok
219-
idx = df['Flag31'] & df['Flag81']
220-
df.loc[idx, 'Gain_Cor'] = df['Gain_ECC'][idx] * df['Gain_Prime'][idx]
221-
df.loc[idx, 'Offset_Cor'] = df['Gain_ECC'][idx] * df['Offset_Prime'][idx] +\
222-
df['Offset_ECC'][idx]
221+
idx = df["Flag31"] & df["Flag81"]
222+
df.loc[idx, "Gain_Cor"] = df["Gain_ECC"][idx] * df["Gain_Prime"][idx]
223+
df.loc[idx, "Offset_Cor"] = df["Gain_ECC"][idx] * df["Offset_Prime"][idx] + df["Offset_ECC"][idx]
223224
# otherwise keep uncorrected ECC Values
224225
idx = ~idx
225-
df.loc[idx, 'Gain_Cor'] = df['Gain_ECC'][idx]
226-
df.loc[idx, 'Offset_Cor'] = df['Offset_ECC'][idx]
226+
df.loc[idx, "Gain_Cor"] = df["Gain_ECC"][idx]
227+
df.loc[idx, "Offset_Cor"] = df["Offset_ECC"][idx]
227228

228229
return df, idx

stixcore/calibration/elut_manager.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from stixcore.util.logging import get_logger
1111
from stixcore.util.singleton import Singleton
1212

13-
__all__ = ['ELUTManager']
13+
__all__ = ["ELUTManager"]
1414

1515
ELUT_DATA_DIR = Path(__file__).parent.parent / "config" / "data" / "common" / "elut"
1616

@@ -42,10 +42,10 @@ def _load_index(self):
4242
logger.info(f"Loaded {len(self.elut_index)} ELUT entries from index")
4343

4444
except FileNotFoundError:
45-
logger.warning(f'No ELUT index found at: {self.elut_index_file}')
45+
logger.warning(f"No ELUT index found at: {self.elut_index_file}")
4646
self.elut_index = []
4747
except Exception as e:
48-
logger.error(f'Error loading ELUT index: {e}')
48+
logger.error(f"Error loading ELUT index: {e}")
4949
self.elut_index = []
5050

5151
def _find_elut_file(self, date):
@@ -173,7 +173,7 @@ def cache_size(self):
173173

174174

175175
# Create singleton instance
176-
if 'pytest' in sys.modules:
177-
ELUTManager.instance = ELUTManager(test_data.elut if hasattr(test_data, 'elut') else None)
176+
if "pytest" in sys.modules:
177+
ELUTManager.instance = ELUTManager(test_data.elut if hasattr(test_data, "elut") else None)
178178
else:
179179
ELUTManager.instance = ELUTManager()

stixcore/calibration/tests/test_elut_manager.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def test_elut_index_loading(self):
3434
"""Test that ELUT index is loaded correctly."""
3535
manager = ELUTManager()
3636

37-
assert hasattr(manager, 'elut_index')
37+
assert hasattr(manager, "elut_index")
3838
assert len(manager.elut_index) > 0
3939

4040
# Check that index contains expected structure
@@ -64,7 +64,7 @@ def test_find_elut_file_known_date(self):
6464

6565
assert elut_file is not None
6666
assert isinstance(elut_file, Path)
67-
assert elut_file.suffix == '.csv'
67+
assert elut_file.suffix == ".csv"
6868

6969
# Verify the file exists
7070
elut_path = manager.data_root / elut_file
@@ -178,12 +178,13 @@ def test_error_handling_nonexistent_file(self):
178178
manager = ELUTManager()
179179

180180
from stixpy.calibration.detector import get_sci_channels
181+
181182
sci_channels = get_sci_channels(datetime(2022, 8, 1))
182183

183184
with pytest.raises(FileNotFoundError, match="ELUT file not found"):
184185
manager.read_elut("nonexistent_file.csv", sci_channels)
185186

186187
def test_instance_attribute_exists(self):
187188
"""Test that singleton instance is accessible."""
188-
assert hasattr(ELUTManager, 'instance')
189+
assert hasattr(ELUTManager, "instance")
189190
assert isinstance(ELUTManager.instance, ELUTManager)

stixcore/ecc/manager.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ def create_context(self, date=None):
208208
Ignore_Min_Gain_Det_Pix_List=json.loads(
209209
config.get("DEFAULT", "Ignore_Min_Gain_Det_Pix_List", fallback="[]")
210210
),
211+
Name=configuration_name,
211212
)
212213

213214
logger.info(f"Read config from in: {temp_dir / 'post_ecc.ini'}")

0 commit comments

Comments
 (0)