Skip to content

Commit f4a1b58

Browse files
committed
Fixed Run_SDD_Calibration script
1 parent 2ebbac0 commit f4a1b58

1 file changed

Lines changed: 124 additions & 86 deletions

File tree

autoemx/scripts/Microscope calibration/Run_SDD_Calibration.py

Lines changed: 124 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
Automated SDD Detector Calibration
55
66
This script automates the collection and fitting of X-ray spectra from experimental standards
7-
to generate an SDD calibration file for AutoEMXsp.
7+
to generate an SDD calibration file for AutoEMX.
88
99
Requirements:
10-
- Proper instrument calibration files and instrument driver for the selected microscope
10+
- Proper instrument calibration files and instrument driver for the selected microscope
1111
1212
Typical Usage:
1313
- Edit the 'std_list' list to define your standards.
@@ -16,30 +16,38 @@
1616
- ref_el: Element in the composition whose peak should be taken as reference to calibrate the SDD.
1717
- ref_peak: Characteristic X-ray to take as reference to calibrate the SDD (e.g., Ka1, La1, Ma1, Mz1).
1818
- pos: Position of standard in the microscope stage.
19-
- sample_type: 'bulk' or 'powder'. Check autoemx.tools.config_classes.SampleConfig for updated support.
19+
- sample_type: 'bulk' or 'powder'. Check autoemx.config.runtime_configs.SampleConfig for updated support.
2020
- is_manual_meas: Set to True to manually select spots to measure.
2121
2222
- Suggestions:
2323
- Preferably use bulk standards. Powder standards are also acceptable if bulk standards are not available.
2424
- Choose peaks above 2 keV, well-distanced between themselves.
25+
- Exactly two standards are required (two-point energy calibration).
26+
27+
- Set reuse_session to None to acquire new spectra, or to an existing session folder
28+
name under SDD calibrations/ to skip acquisition and reuse prior ledgers.
2529
2630
- Run the script to collect and fit spectra, with automated generation of SDD calibration file.
27-
31+
2832
Potential improvements:
29-
- At the moment the script runs the spectral fitting twice, which is inefficient, but not really important given
30-
that this code is not run very often.
33+
- At the moment the script may run spectral fitting twice when fit_during_collection is True
34+
and centers are still re-read from the ledger, which is inefficient but acceptable given
35+
that this code is not run very often.
3136
3237
Created on Fri Aug 20 09:34:34 2025
3338
3439
@author: Andrea
3540
"""
3641
import os
3742
from datetime import datetime
38-
import pandas as pd
43+
44+
import numpy as np
3945

4046
from autoemx.runners.batch_acquire_experimental_stds import batch_acquire_experimental_stds
4147
from autoemx.runners.batch_fit_spectra import batch_fit_spectra
4248
from autoemx.data.Xray_lines import get_el_xray_lines
49+
from autoemx.config.ledger_io import load_sample_ledger
50+
from autoemx.config.runtime_configs import SampleConfig
4351
import autoemx.calibrations as calibs
4452
import autoemx.utils.constants as cnst
4553
from autoemx.utils import print_double_separator
@@ -61,21 +69,30 @@
6169
now_formatted = now.strftime("%Y%m%d_%Hh%Mm")
6270
output_filename_suffix = f'_{now_formatted}_50kcnts'
6371

72+
# Set to an existing session folder name under SDD calibrations/ to skip acquisition
73+
# (e.g. '20260521_09h22m'). None acquires new spectra into a timestamped session folder.
74+
reuse_session = None
75+
6476
# =============================================================================
6577
# Sample Definitions - Add two pure elements to measure
6678
# =============================================================================
67-
Cu_center = (37.863,38.195)
79+
Cu_center = (37.863, 38.195)
6880
std_list = [
69-
{'ID': 'Cu','formula': 'Cu', 'ref_el' : 'Cu', 'ref_peak': 'Ka1', 'pos': Cu_center, 'sample_type': 'bulk', 'is_manual_meas' : False},
70-
{'ID': 'Al','formula': 'Al', 'ref_el' : 'Al', 'ref_peak': 'Ka1', 'pos': tuple(a + b for a, b in zip(Cu_center, (5, 0))), 'sample_type': 'bulk', 'is_manual_meas' : False}, # CAlibration standard center
81+
{'ID': 'Cu', 'formula': 'Cu', 'ref_el': 'Cu', 'ref_peak': 'Ka1', 'pos': Cu_center, 'sample_type': 'bulk', 'is_manual_meas': False},
82+
{'ID': 'Al', 'formula': 'Al', 'ref_el': 'Al', 'ref_peak': 'Ka1', 'pos': tuple(a + b for a, b in zip(Cu_center, (5, 0))), 'sample_type': 'bulk', 'is_manual_meas': False},
7183
]
7284

85+
if len(std_list) != 2:
86+
raise ValueError("SDD energy calibration requires exactly two standards in std_list.")
87+
7388
# =============================================================================
7489
# Acquisition Options and Sample description
75-
working_distance = 5.5 #mm
90+
working_distance = 5.5 # mm
7691
is_auto_substrate_detection = False
7792

78-
fit_during_collection= False
93+
is_manual_meas = False # Default navigation mode; per-sample 'is_manual_meas' overrides this.
94+
95+
fit_during_collection = False
7996
update_std_library = False
8097

8198
sample_substrate_type = 'None'
@@ -86,10 +103,10 @@
86103
beam_energy = 15 # keV
87104

88105
auto_adjust_brightness_contrast = True
89-
contrast = None # 4.3877 # Used if auto_adjust_brightness_contrast = False
90-
brightness = None # 0.4504 # Used if auto_adjust_brightness_contrast = False
91-
saved_images_extension = 'png' # Default lightweight output. Set 'tif' for higher-resolution/larger files.
92-
save_raw_images = False # Default saves only the annotated image. Set True to also save raw images.
106+
contrast = None # 4.3877 # Used if auto_adjust_brightness_contrast = False
107+
brightness = None # 0.4504 # Used if auto_adjust_brightness_contrast = False
108+
saved_images_extension = 'png' # Default lightweight output. Set 'tif' for higher-resolution/larger files.
109+
save_raw_images = False # Default saves only the annotated image. Set True to also save raw images.
93110

94111
n_target_spectra = 5
95112
max_n_spectra = 10
@@ -105,7 +122,8 @@
105122
# =============================================================================
106123
powder_meas_cfg_kwargs = dict(
107124
par_selection_mode='auto',
108-
is_known_powder_mixture_meas = True,
125+
is_known_powder_mixture_meas=True,
126+
img_shift_tracking=True,
109127
max_n_par_per_frame=30,
110128
max_spectra_per_par=3,
111129
max_area_par=10000.0,
@@ -114,82 +132,97 @@
114132
xsp_spots_distance_um=1.0,
115133
par_brightness_thresh=100,
116134
par_xy_spots_thresh=100,
117-
par_feature_selection = 'peaks',
118-
par_spot_spacing = 'random'
135+
par_feature_selection='peaks',
136+
par_spot_spacing='random',
119137
)
120138

121139
# =============================================================================
122140
# Bulk options
123141
# =============================================================================
124142
bulk_meas_cfg_kwargs = dict(
125-
grid_spot_spacing_um = 10.0, # µm
126-
min_xsp_spots_distance_um = 2.5, # µm
127-
randomize_frames = False,
128-
exclude_sample_margin = False
143+
grid_spot_spacing_um=10.0, # µm
144+
min_xsp_spots_distance_um=2.5, # µm
145+
image_frame_width_um=None, # µm
146+
randomize_frames=False,
147+
exclude_sample_margin=False,
129148
)
130149

131150
# =============================================================================
132151
# Options for experimental standard collection
133152
# =============================================================================
134153
exp_stds_meas_cfg_kwargs = dict(
135-
min_acceptable_PB_ratio = 10,
136-
quant_flags_accepted = [0],
137-
use_for_mean_PB_calc = False
154+
min_acceptable_PB_ratio=10,
155+
quant_flags_accepted=[0],
156+
els_to_use_for_mean_PB_calc=["none"],
157+
generate_separate_std_dict=False,
138158
)
139159

140160
# =============================================================================
141161
# Run
142162
# =============================================================================
143163
# Load microscope calibrations for this instrument and mode
144164
calibs.load_microscope_calibrations(microscope_ID, measurement_mode, load_detector_channel_params=True)
145-
eds_calibration_path = os.path.join(calibs.calibration_files_dir, cnst.SDD_CALIBS_MEAS_DIR, now_formatted)
146-
147-
# # --- Acquire and save spectra
148-
# analyzers = batch_acquire_experimental_stds(
149-
# stds=std_list,
150-
# microscope_ID=microscope_ID,
151-
# microscope_type=microscope_type,
152-
# measurement_type=measurement_type,
153-
# measurement_mode=measurement_mode,
154-
# sample_halfwidth=sample_halfwidth,
155-
# sample_substrate_type=sample_substrate_type,
156-
# sample_substrate_shape=sample_substrate_shape,
157-
# working_distance = working_distance,
158-
# beam_energy=beam_energy,
159-
# spectrum_lims=spectrum_lims,
160-
# use_instrument_background=use_instrument_background,
161-
# min_bckgrnd_cnts=min_bckgrnd_cnts,
162-
# fit_during_collection= fit_during_collection,
163-
# update_std_library = update_std_library,
164-
# is_auto_substrate_detection=is_auto_substrate_detection,
165-
# auto_adjust_brightness_contrast=auto_adjust_brightness_contrast,
166-
# contrast=contrast,
167-
# brightness=brightness,
168-
# saved_images_extension=saved_images_extension,
169-
# save_raw_images=save_raw_images,
170-
# min_n_spectra=n_target_spectra,
171-
# max_n_spectra=max_n_spectra,
172-
# target_Xsp_counts=target_Xsp_counts,
173-
# max_XSp_acquisition_time=max_XSp_acquisition_time,
174-
# els_substrate=els_substrate,
175-
# powder_meas_cfg_kwargs=powder_meas_cfg_kwargs,
176-
# bulk_meas_cfg_kwargs=bulk_meas_cfg_kwargs,
177-
# exp_stds_meas_cfg_kwargs=exp_stds_meas_cfg_kwargs,
178-
# output_filename_suffix=output_filename_suffix,
179-
# development_mode=False,
180-
# verbose=True,
181-
# exp_std_dir = eds_calibration_path
182-
# )
183-
184-
# std_paths = [an.sample_result_dir for an in analyzers]
185-
std_paths = [
186-
'/Users/Andrea_1/Desktop/Work/Codes/Repositories/AutoEMXSp/autoemx/calibrations/PhenomXL/Detector_channel_params_calibs/SDD calibrations/20260521_09h22m/Cu',
187-
'/Users/Andrea_1/Desktop/Work/Codes/Repositories/AutoEMXSp/autoemx/calibrations/PhenomXL/Detector_channel_params_calibs/SDD calibrations/20260521_09h22m/Al'
188-
]
189165

190-
# --- Ensure fit results exist in ledgers, launch fit if missing
191-
from autoemx.config.ledger_io import load_sample_ledger
192-
import numpy as np
166+
session_name = reuse_session if reuse_session else now_formatted
167+
eds_calibration_path = os.path.join(
168+
calibs.calibration_files_dir,
169+
cnst.SDD_CALIBS_MEAS_DIR,
170+
session_name,
171+
)
172+
173+
if reuse_session:
174+
print_double_separator()
175+
print(f"Reusing existing SDD calibration session: {eds_calibration_path}")
176+
std_paths = [os.path.join(eds_calibration_path, std['ID']) for std in std_list]
177+
missing = [path for path in std_paths if not os.path.isdir(path)]
178+
if missing:
179+
raise FileNotFoundError(
180+
"reuse_session is set but the following standard folders are missing:\n"
181+
+ "\n".join(missing)
182+
)
183+
else:
184+
# --- Acquire and save spectra
185+
analyzers = batch_acquire_experimental_stds(
186+
stds=std_list,
187+
microscope_ID=microscope_ID,
188+
microscope_type=microscope_type,
189+
measurement_type=measurement_type,
190+
measurement_mode=measurement_mode,
191+
sample_halfwidth=sample_halfwidth,
192+
sample_substrate_type=sample_substrate_type,
193+
sample_substrate_shape=sample_substrate_shape,
194+
working_distance=working_distance,
195+
beam_energy=beam_energy,
196+
spectrum_lims=spectrum_lims,
197+
use_instrument_background=use_instrument_background,
198+
min_bckgrnd_cnts=min_bckgrnd_cnts,
199+
is_manual_meas=is_manual_meas,
200+
fit_during_collection=fit_during_collection,
201+
update_std_library=update_std_library,
202+
is_auto_substrate_detection=is_auto_substrate_detection,
203+
auto_adjust_brightness_contrast=auto_adjust_brightness_contrast,
204+
contrast=contrast,
205+
brightness=brightness,
206+
saved_images_extension=saved_images_extension,
207+
save_raw_images=save_raw_images,
208+
min_n_spectra=n_target_spectra,
209+
max_n_spectra=max_n_spectra,
210+
target_Xsp_counts=target_Xsp_counts,
211+
max_XSp_acquisition_time=max_XSp_acquisition_time,
212+
els_substrate=els_substrate,
213+
powder_meas_cfg_kwargs=powder_meas_cfg_kwargs,
214+
bulk_meas_cfg_kwargs=bulk_meas_cfg_kwargs,
215+
exp_stds_meas_cfg_kwargs=exp_stds_meas_cfg_kwargs,
216+
output_filename_suffix=output_filename_suffix,
217+
development_mode=False,
218+
verbose=True,
219+
exp_std_dir=eds_calibration_path,
220+
)
221+
if any(an is None for an in analyzers):
222+
failed = [std['ID'] for std, an in zip(std_list, analyzers) if an is None]
223+
raise RuntimeError(f"Acquisition failed for standard(s): {failed}")
224+
std_paths = [an.sample_result_dir for an in analyzers]
225+
193226

194227
def ledger_has_fit_results(ledger, ref_peak):
195228
for spectrum in getattr(ledger, 'spectra', []):
@@ -199,37 +232,42 @@ def ledger_has_fit_results(ledger, ref_peak):
199232
return True
200233
return False
201234

235+
202236
extracted_par_vals = {}
203-
sample_IDs = [std_d['ID'] for std_d in std_list]
204237
fit_params_vals_to_extract = [f"{std_d['ref_el']}_{std_d['ref_peak']}_center" for std_d in std_list]
205238

206239
for std, sample_path in zip(std_list, std_paths):
207240
sample_ID = std['ID']
208241
ref_peak = f"{std['ref_el']}_{std['ref_peak']}"
242+
is_particle = std['sample_type'] in SampleConfig.POWDER_SAMPLES_TYPES
209243
ledger_path = os.path.join(sample_path, cnst.LEDGER_FILENAME + cnst.LEDGER_FILEEXT)
244+
if not os.path.exists(ledger_path):
245+
raise FileNotFoundError(f"Ledger not found for standard '{sample_ID}': {ledger_path}")
210246
ledger = load_sample_ledger(ledger_path)
211247
if not ledger_has_fit_results(ledger, ref_peak):
212248
batch_fit_spectra(
213249
[sample_ID],
214250
'all',
215251
is_standard=True,
216252
fit_params_vals_to_extract=fit_params_vals_to_extract,
253+
spectrum_lims=spectrum_lims,
217254
samples_path=eds_calibration_path,
218-
use_instrument_background=False,
255+
use_instrument_background=use_instrument_background,
219256
plot_signal=False,
220257
zoom_plot=False,
221258
line_to_plot='',
222259
els_substrate=els_substrate,
223260
fit_tol=1e-4,
224-
is_particle=True,
261+
is_particle=is_particle,
225262
max_undetectable_w_fr=0,
226263
force_single_iteration=False,
227264
interrupt_fits_bad_spectra=False,
228265
print_results=False,
229266
quant_verbose=True,
230-
fitting_verbose=False
267+
fitting_verbose=False,
231268
)
232269
ledger = load_sample_ledger(ledger_path)
270+
233271
# Collect fit parameter values for this sample, only if quant_flag == 0
234272
centers = []
235273
for spectrum in getattr(ledger, 'spectra', []):
@@ -264,13 +302,16 @@ def ledger_has_fit_results(ledger, ref_peak):
264302
raise RuntimeError(f"No valid fitted center values found for standard '{std['ID']}' ({param_name})")
265303
measured_means[param_name] = meas_mean
266304

267-
# Assign to calibration variables
268-
x_measured_en = measured_means["Cu_Ka1_center"]
269-
y_measured_en = measured_means["Al_Ka1_center"]
305+
# Two-point calibration from std_list order (first = x, second = y)
306+
std_x, std_y = std_list
307+
x_param = f"{std_x['ref_el']}_{std_x['ref_peak']}_center"
308+
y_param = f"{std_y['ref_el']}_{std_y['ref_peak']}_center"
309+
x_measured_en = measured_means[x_param]
310+
y_measured_en = measured_means[y_param]
270311

271312
# Theoretical energies
272-
x_th_en = get_el_xray_lines("Cu")["Ka1"]["energy (keV)"]
273-
y_th_en = get_el_xray_lines("Al")["Ka1"]["energy (keV)"]
313+
x_th_en = get_el_xray_lines(std_x['ref_el'])[std_x['ref_peak']]["energy (keV)"]
314+
y_th_en = get_el_xray_lines(std_y['ref_el'])[std_y['ref_peak']]["energy (keV)"]
274315

275316
# Calculate new calibration
276317
i_x = (x_measured_en - current_energy_zero) / current_bin_width
@@ -282,14 +323,11 @@ def ledger_has_fit_results(ledger, ref_peak):
282323
new_scale = (Dx - Dy + x_measured_en - y_measured_en) / (i_x - i_y)
283324
new_offset = Dy + y_measured_en - i_y * new_scale
284325

285-
print_double_separator
326+
print_double_separator()
286327
print(f"Current scale: {current_bin_width:.6f}")
287328
print(f"Current offset: {current_energy_zero:.6f}")
288329
print(f"New scale: {new_scale:.6f}")
289330
print(f"New offset: {new_offset:.6f}")
290331

291332
# Add calibration file
292333
calibs.update_detector_channel_params(measurement_mode, new_offset, new_scale)
293-
294-
295-

0 commit comments

Comments
 (0)