#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Automated X-Ray Experimental Standard Acquisition and Analysis
This module configures and runs automated collection and fitting
of EDS/WDS spectra from experimental standards (i.e., sampels of known composition)
to generate reference values of peak-to-background ratios.
Import this module in your own Python code and call the
`batch_acquire_experimental_stds()` function, passing your desired configuration
and sample list as arguments. This enables integration into larger
automation workflows or pipelines.
Workflow includes:
- Configuration of microscope, measurement, substrate, and fitting parameters
- Sample setup (elements, position, reference formulae)
- Calibration and quantification options
- Automated or manual navigation and acquisition modes
Requirements:
- Proper instrument calibration files and instrument driver for the selected microscope
Typical usage:
- Edit the 'std_list' list to define your standards
- Adjust configuration parameters as needed, either directly in the script or by passing them to `batch_acquire_experimental_stds`
- Run the script, or import and call `batch_acquire_experimental_stds()` to collect experimental standards for one or multiple samples at a time
Parameters
----------
stds : list of dict
List of experimental standard definitions.
Each dictionary must contain:
- 'ID' (str): Identifier for the standard sample (SampleConfig.ID).
- 'formula' (str): Chemical formula of the standard (ExpStandardsConfig.formula).
- 'pos' (tuple of float): (x, y) stage coordinates in mm (SampleConfig.center_pos).
- 'sample_type' (str): Sample type ('powder', 'bulk', etc.) (SampleConfig.type).
- 'is_manual_meas' (bool): If True, navigation to positions is manual (MeasurementConfig.is_manual_navigation).
microscope_ID : str, optional
Identifier for the microscope hardware.
Must correspond to a calibration folder in `./XSp_calibs/Microscopes/<ID>` (MicroscopeConfig.ID).
Default is `'PhenomXL'`.
microscope_type : str, optional
Type of microscope. Allowed: `'SEM'` (implemented), `'STEM'` (not implemented).
Default is `'SEM'` (MicroscopeConfig.type).
measurement_type : str, optional
Measurement type. Allowed: `'EDS'` (implemented), `'WDS'` (not implemented).
Default is `'EDS'` (MeasurementConfig.type).
measurement_mode : str, optional
Acquisition mode (e.g., `'point'`, `'map'`), defining beam/detector calibration settings.
Default is `'point'` (MeasurementConfig.mode).
sample_halfwidth : float, optional
Half-width of the sample area in mm for mapping/acquisition.
Default is `3.0` (SampleConfig.half_width_mm).
sample_substrate_type : str, optional
Type of sample substrate. Allowed: `'Ctape'`, `'None'`.
Default is `'Ctape'` (SampleSubstrateConfig.type).
sample_substrate_shape : str, optional
Shape of the substrate. Allowed: `'circle'`, `'square'`.
Default is `'circle'` (SampleSubstrateConfig.shape).
working_distance : float, optional
Working distance in mm for acquisition. If None, taken from microscope driver.
Default is `5.0` (MeasurementConfig.working_distance).
beam_energy : float, optional
Electron beam energy in keV.
Default is `15.0` (MeasurementConfig.beam_energy_keV).
spectrum_lims : tuple of float, optional
Lower and upper energy limits for spectrum fitting in eV.
Default is `(14, 1100)` (QuantConfig.spectrum_lims).
use_instrument_background : bool, optional
Whether to use instrument background files during fitting.
If False, background is computed during fitting.
Default is `False` (QuantConfig.use_instrument_background).
min_bckgrnd_cnts : float, optional
Minimum background counts required for a spectrum not to be filtered out.
Default is `5` (QuantConfig.min_bckgrnd_cnts).
fit_during_collection : bool, optional
If True, fit spectra during acquisition; otherwise fit later.
Default is `True`.
update_std_library : bool, optional
If True, update the stored experimental standards library after acquisition.
Default is `False`.
is_auto_substrate_detection : bool, optional
If True, substrate elements are detected automatically.
Implemented only for `'Ctape'` substrates.
Default is `False` (SampleSubstrateConfig.auto_detection).
auto_adjust_brightness_contrast : bool, optional
If True, brightness/contrast are set automatically.
Default is `True` (MicroscopeConfig.is_auto_BC).
contrast : float, optional
Manual contrast setting (required if auto_adjust_brightness_contrast is False).
Default is `4.3877` (MicroscopeConfig.contrast).
brightness : float, optional
Manual brightness setting (required if auto_adjust_brightness_contrast is False).
Default is `0.4504` (MicroscopeConfig.brightness).
min_n_spectra : int, optional
Minimum number of spectra to acquire.
Default is `50` (MeasurementConfig.min_n_spectra).
max_n_spectra : int, optional
Maximum number of spectra to acquire.
Default is `100` (MeasurementConfig.max_n_spectra).
target_Xsp_counts : int, optional
Target counts for spectrum acquisition.
Default is `250000` (MeasurementConfig.target_acquisition_counts).
max_XSp_acquisition_time : float, optional
Maximum acquisition time in seconds. If None, estimated from target counts.
Default is `None` (MeasurementConfig.max_acquisition_time).
els_substrate : list of str, optional
List of substrate element symbols.
Default is `['C', 'O', 'Al']` (SampleSubstrateConfig.elements).
powder_meas_cfg_kwargs : dict, optional
Additional keyword arguments for PowderMeasurementConfig.
bulk_meas_cfg_kwargs : dict, optional
Additional keyword arguments for BulkMeasurementConfig.
exp_stds_meas_cfg_kwargs : dict, optional
Additional keyword arguments for ExpStandardsConfig.
Used to customize experimental standard acquisition and PB ratio filtering.
output_filename_suffix : str, optional
String appended to output filenames.
Default is `''`.
development_mode : bool, optional
If True, enables development/debug features.
Default is `False`.
verbose : bool, optional
If True, print verbose output.
Default is `True`.
exp_std_dir : str, optional
Directory where experimental standard results are saved.
Default is `None`.
Created on Fri Aug 20 09:34:34 2025
@author: Andrea
"""
import logging
from typing import List, Dict, Tuple, Any
from autoemxsp.core.EMXSp_composition_analyser import EMXSp_Composition_Analyzer
import autoemxsp.XSp_calibs as calibs
from autoemxsp.utils import print_double_separator
from autoemxsp.config import (
MicroscopeConfig,
SampleConfig,
MeasurementConfig,
SampleSubstrateConfig,
QuantConfig,
ClusteringConfig,
PowderMeasurementConfig,
BulkMeasurementConfig,
ExpStandardsConfig
)
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
[docs]
def batch_acquire_experimental_stds(
stds: List[Dict[str, Any]],
microscope_ID: str = 'PhenomXL',
microscope_type: str = 'SEM',
measurement_type: str = 'EDS',
measurement_mode: str = 'point',
sample_halfwidth: float = 3.0,
sample_substrate_type: str = 'Ctape',
sample_substrate_shape: str = 'circle',
working_distance: float = 5, #mm
working_distance_tolerance: float = 1, #mm
beam_energy: float = 15.0,
spectrum_lims: Tuple[float, float] = (14, 1100),
use_instrument_background: bool = False,
min_bckgrnd_cnts: float = 5,
fit_during_collection= True,
update_std_library = False,
is_auto_substrate_detection: bool = False,
auto_adjust_brightness_contrast: bool = True,
contrast: float = 4.3877,
brightness: float = 0.4504,
min_n_spectra: int = 50,
max_n_spectra: int = 100,
target_Xsp_counts: int = 250000,
max_XSp_acquisition_time: float = None,
els_substrate: List[str] = None,
powder_meas_cfg_kwargs: Dict[str, Any] = None,
bulk_meas_cfg_kwargs: Dict[str, Any] = None,
exp_stds_meas_cfg_kwargs: Dict[str, Any] = None,
output_filename_suffix: str = '',
development_mode: bool = False,
verbose: bool = True,
exp_std_dir: str = None,
) -> None:
"""
Batch acquisition (and optional quantification) of X-ray spectra for a list of powder samples.
Parameters
----------
stds : list of dict
List of experimental standard definitions.
Each dictionary must contain:
- 'ID' (str): Identifier for the standard sample (SampleConfig.ID).
- 'formula' (str): Chemical formula of the standard (ExpStandardsConfig.formula).
- 'pos' (tuple of float): (x, y) stage coordinates in mm (SampleConfig.center_pos).
- 'sample_type' (str): Sample type ('powder', 'bulk', etc.) (SampleConfig.type).
- 'is_manual_meas' (bool): If True, navigation to positions is manual (MeasurementConfig.is_manual_navigation).
microscope_ID : str, optional
Identifier for the microscope hardware.
Must correspond to a calibration folder in `./XSp_calibs/Microscopes/<ID>` (MicroscopeConfig.ID).
Default is `'PhenomXL'`.
microscope_type : str, optional
Type of microscope. Allowed: `'SEM'` (implemented), `'STEM'` (not implemented).
Default is `'SEM'` (MicroscopeConfig.type).
measurement_type : str, optional
Measurement type. Allowed: `'EDS'` (implemented), `'WDS'` (not implemented).
Default is `'EDS'` (MeasurementConfig.type).
measurement_mode : str, optional
Acquisition mode (e.g., `'point'`, `'map'`), defining beam/detector calibration settings.
Default is `'point'` (MeasurementConfig.mode).
sample_halfwidth : float, optional
Half-width of the sample area in mm for mapping/acquisition.
Default is `3.0` (SampleConfig.half_width_mm).
sample_substrate_type : str, optional
Type of sample substrate. Allowed: `'Ctape'`, `'None'`.
Default is `'Ctape'` (SampleSubstrateConfig.type).
sample_substrate_shape : str, optional
Shape of the substrate. Allowed: `'circle'`, `'square'`.
Default is `'circle'` (SampleSubstrateConfig.shape).
working_distance : float, optional
Working distance in mm for acquisition. If None, taken from microscope driver.
Default is `5.0` (MeasurementConfig.working_distance).
working_distance_tolerance : float, optional
Defines maximum accepted deviation of working distance from its typical value, in mm.
Used to prevent gross mistakes from EM autofocus. Default: 1 mm.
beam_energy : float, optional
Electron beam energy in keV.
Default is `15.0` (MeasurementConfig.beam_energy_keV).
spectrum_lims : tuple of float, optional
Lower and upper energy limits for spectrum fitting in eV.
Default is `(14, 1100)` (QuantConfig.spectrum_lims).
use_instrument_background : bool, optional
Whether to use instrument background files during fitting.
If False, background is computed during fitting.
Default is `False` (QuantConfig.use_instrument_background).
min_bckgrnd_cnts : float, optional
Minimum background counts required for a spectrum not to be filtered out.
Default is `5` (QuantConfig.min_bckgrnd_cnts).
fit_during_collection : bool, optional
If True, fit spectra during acquisition; otherwise fit later.
Default is `True`.
update_std_library : bool, optional
If True, update the stored experimental standards library after acquisition.
Default is `False`.
is_auto_substrate_detection : bool, optional
If True, substrate elements are detected automatically.
Implemented only for `'Ctape'` substrates.
Default is `False` (SampleSubstrateConfig.auto_detection).
auto_adjust_brightness_contrast : bool, optional
If True, brightness/contrast are set automatically.
Default is `True` (MicroscopeConfig.is_auto_BC).
contrast : float, optional
Manual contrast setting (required if auto_adjust_brightness_contrast is False).
Default is `4.3877` (MicroscopeConfig.contrast).
brightness : float, optional
Manual brightness setting (required if auto_adjust_brightness_contrast is False).
Default is `0.4504` (MicroscopeConfig.brightness).
min_n_spectra : int, optional
Minimum number of spectra to acquire.
Default is `50` (MeasurementConfig.min_n_spectra).
max_n_spectra : int, optional
Maximum number of spectra to acquire.
Default is `100` (MeasurementConfig.max_n_spectra).
target_Xsp_counts : int, optional
Target counts for spectrum acquisition.
Default is `250000` (MeasurementConfig.target_acquisition_counts).
max_XSp_acquisition_time : float, optional
Maximum acquisition time in seconds. If None, estimated from target counts.
Default is `None` (MeasurementConfig.max_acquisition_time).
els_substrate : list of str, optional
List of substrate element symbols.
Default is `['C', 'O', 'Al']` (SampleSubstrateConfig.elements).
powder_meas_cfg_kwargs : dict, optional
Additional keyword arguments for PowderMeasurementConfig.
bulk_meas_cfg_kwargs : dict, optional
Additional keyword arguments for BulkMeasurementConfig.
exp_stds_meas_cfg_kwargs : dict, optional
Additional keyword arguments for ExpStandardsConfig.
Used to customize experimental standard acquisition and PB ratio filtering.
output_filename_suffix : str, optional
String appended to output filenames.
Default is `''`.
development_mode : bool, optional
If True, enables development/debug features.
Default is `False`.
verbose : bool, optional
If True, print verbose output.
Default is `True`.
exp_std_dir : str, optional
Directory where experimental standard results are saved.
Default is `None`.
Returns
-------
results : list(EMXSp_Composition_Analyzer)
A list of the composition analysis objects (one per sample) containing the results and methods for further analysis.
"""
if max_XSp_acquisition_time is None:
max_XSp_acquisition_time = target_Xsp_counts / 10000 * 5
if els_substrate is None:
els_substrate = ['C', 'O', 'Al']
# --- Configuration objects
microscope_cfg = MicroscopeConfig(
ID=microscope_ID,
type=microscope_type,
is_auto_BC=auto_adjust_brightness_contrast,
brightness=brightness,
contrast=contrast
)
# Load microscope calibrations for this instrument and mode
calibs.load_microscope_calibrations(microscope_ID, measurement_mode)
quant_cfg = QuantConfig(
spectrum_lims=spectrum_lims,
use_instrument_background=use_instrument_background,
min_bckgrnd_cnts=min_bckgrnd_cnts
)
if powder_meas_cfg_kwargs:
powder_meas_cfg = PowderMeasurementConfig(**powder_meas_cfg_kwargs)
else:
powder_meas_cfg = PowderMeasurementConfig()
if bulk_meas_cfg_kwargs:
bulk_meas_cfg = BulkMeasurementConfig(**bulk_meas_cfg_kwargs)
else:
bulk_meas_cfg = BulkMeasurementConfig()
sample_substrate_cfg = SampleSubstrateConfig(
elements=els_substrate,
type=sample_substrate_type,
shape=sample_substrate_shape,
auto_detection=is_auto_substrate_detection
)
results = []
for std_sample in stds:
# --- Sample configuration
sample_ID = std_sample['ID']
formula = std_sample['formula']
center_pos = std_sample['pos']
sample_type = std_sample['sample_type']
is_manual_meas = std_sample['is_manual_meas']
print_double_separator()
logging.info(f"Sample '{sample_ID}'")
if exp_stds_meas_cfg_kwargs:
exp_stds_cfg = ExpStandardsConfig(is_exp_std_measurement = True, formula = formula, **exp_stds_meas_cfg_kwargs)
else:
exp_stds_cfg = ExpStandardsConfig(is_exp_std_measurement = True, formula = formula)
measurement_cfg = MeasurementConfig(
type=measurement_type,
mode=measurement_mode,
working_distance = working_distance,
working_distance_tolerance = working_distance_tolerance,
beam_energy_keV=beam_energy,
is_manual_navigation=is_manual_meas,
max_acquisition_time=max_XSp_acquisition_time,
target_acquisition_counts=target_Xsp_counts,
min_n_spectra=min_n_spectra,
max_n_spectra=max_n_spectra
)
elements = list(exp_stds_cfg.w_frs.keys())
sample_cfg = SampleConfig(
ID=sample_ID,
elements=elements,
type=sample_type,
center_pos=center_pos,
half_width_mm=sample_halfwidth
)
# # --- Template: Customizing Parameters Per Sample
# # For any parameter you wish to override on a per-sample basis, add a key:value
# # pair to the sample's dictionary (e.g., 'n_spectra', 'beam_energy', etc.).
# # In the main loop, check if the key is present; if not, use the default value.
# #
# # Example: To override 'n_spectra' for specific samples,
# # add 'n_spectra': <value> in the sample dictionary.
# # Then, after loading the sample parameters, use the following pattern:
# # -- Inside your main sample loop:
# if sample.get('max_n_spectra') is not None:
# max_n_spectra_val = sample['max_n_spectra']
# else:
# max_n_spectra_val = max_n_spectra # or your chosen default
# # -- Repeat similarly for other parameters you wish to customize per sample:
# if sample.get('min_n_spectra') is not None:
# min_n_spectra_val = sample['min_n_spectra']
# else:
# min_n_spectra_val = min_n_spectra # or your chosen default
# # -- Make sure to place the configuration object instantiation *after*
# # these assignments so that each sample's settings are correctly applied.
# # For example:
# measurement_cfg = MeasurementConfig(
# min_n_spectra=min_n_spectra,
# max_n_spectra=max_n_spectra
# # ... other parameters ...
# )
# --- Run Composition Analyzer
comp_analyzer = EMXSp_Composition_Analyzer(
microscope_cfg=microscope_cfg,
sample_cfg=sample_cfg,
measurement_cfg=measurement_cfg,
sample_substrate_cfg=sample_substrate_cfg,
quant_cfg=quant_cfg,
clustering_cfg=ClusteringConfig(),
powder_meas_cfg=powder_meas_cfg,
bulk_meas_cfg=bulk_meas_cfg,
exp_stds_cfg=exp_stds_cfg,
is_acquisition=True,
development_mode=development_mode,
output_filename_suffix=output_filename_suffix,
verbose=verbose,
results_dir=exp_std_dir
)
try:
comp_analyzer.run_exp_std_collection(fit_during_collection= fit_during_collection, update_std_library = update_std_library)
results.append(comp_analyzer)
except Exception as e:
results.append(None)
logging.exception(f"Sample '{sample_ID}': acquisition/fitting failed: {e}")
continue
# Put microscope in standby after completion
if not development_mode and len(stds) > 1:
try:
comp_analyzer.EM_controller.standby()
except Exception as e:
logging.warning(f"Could not put microscope in standby: {e}")
return results