Source code for espm.cross_sections_utils

import json
import re
import numpy as np
from pathlib import Path
from espm.conf import DB_PATH, SYMBOLS_PERIODIC_TABLE, SIEGBAHN_TO_IUPAC

[docs] def modify_cross_sections(energy : int | str, input_type : str = 'new_values', lines_new_values : dict[str,float] = None, lines_scaling_factors : dict[str,float] = None, k_factors : dict[str,float] = None, reference_line : str = None, output_filename : str = None) -> None: r""" Function allowing the user to modify the X-ray emission cross-sections of the input database. The function can be with three diffrent input types: new values, scaling factors and k-factors with a reference line. Parameters ---------- energy : int The electron energy in keV for which the cross-sections are to be modified. input_type : str The type of input to be used. Can be one of the following: 'new_values', 'scaling_factors', 'k_factors'. lines_new_values : dict [For the 'new_values' input type] A dictionary containing the new values for the cross-sections. The keys of the dictionary should be the element symbol and the X-ray line in Siegbahn notation, separated by an underscore. The correspoding values should be the new cross-section values. For example: {'Cu_Ka1' : 6.3e-23}. lines_scaling_factors : dict [For the 'scaling_factors' input type] A dictionary containing the scaling factors for the cross-sections. The keys of the dictionary should be the element symbol and the X-ray line family in Siegbahn notation, separated by an underscore. The corresponding values should be the scaling factors. For example: {'Cu_Ka' : 1.2}. k_factors : dict [For the 'k_factors' input type] A dictionary containing the k-factors for the cross-sections. The keys of the dictionary should be the element symbol and the X-ray line family in Siegbahn notation, separated by an underscore. The corresponding values should be the Cliff-Lorimer k-factors. For example: {'Cu_Ka' : 1.75}. reference_line : str [For the 'k_factors' input type] The reference line for the k-factors. Should be the element symbol and the X-ray line family in Siegbahn notation, separated by an underscore. For example: 'Si_Ka'. output_filename : str The name of the output file. If None, the default name is the same as the input file with '_modified' appended. Returns ------- None """ assert not(lines_new_values is None and lines_scaling_factors is None and k_factors is None), f"You need to input a dict of lines to modify with type : {input_type}." if energy == 100: cross_sections_json = DB_PATH / '100keV_xrays.json' elif energy == 200: cross_sections_json = DB_PATH / '200keV_xrays.json' elif energy == 300: cross_sections_json = DB_PATH / '300keV_xrays.json' with open(cross_sections_json, 'r') as cross_sections_file: cross_sections_data = json.load(cross_sections_file) with open(SYMBOLS_PERIODIC_TABLE, 'r') as atomic_symbols_file: atomic_symbols_data = json.load(atomic_symbols_file) with open(SIEGBAHN_TO_IUPAC, 'r') as siegbahn_to_iupac_file: siegbahn_to_iupac_data = json.load(siegbahn_to_iupac_file) iupac_to_siegbahn = { iupac: siegbahn for siegbahn, iupac_list in siegbahn_to_iupac_data.items() for iupac in iupac_list } if input_type == 'new_values': for el_line, value in lines_new_values.items(): line_match = re.match(r"([A-Z][a-z]?)_(.*)", el_line) element = line_match.group(1) line_name = line_match.group(2) atomic_number = str(atomic_symbols_data['table'][element]['number']) line_iupac = siegbahn_to_iupac_data.get(line_name, [line_name])[0] if atomic_number in cross_sections_data['table'] and line_iupac in cross_sections_data['table'][atomic_number]: cross_sections_data['table'][atomic_number][line_iupac]['cs'] = value line_siegbahn = iupac_to_siegbahn.get(line_iupac, line_iupac) print(f"Set new cross-section for Element {element}, Line {line_siegbahn} to {value:.2e}") else: print(f"Warning: Element {element} or line {line_iupac} not found in the JSON data.") elif input_type == 'scaling_factors': for el_line, factor in lines_scaling_factors.items(): line_match = re.match(r"([A-Z][a-z]?)_(.*)", el_line) element = line_match.group(1) line_group = line_match.group(2) atomic_number = str(atomic_symbols_data['table'][element]['number']) line_iupac_list = siegbahn_to_iupac_data.get(line_group, []) for line_iupac in line_iupac_list: if atomic_number in cross_sections_data['table'] and line_iupac in cross_sections_data['table'][atomic_number]: current_cs = cross_sections_data['table'][atomic_number][line_iupac]['cs'] cross_sections_data['table'][atomic_number][line_iupac]['cs'] = current_cs * factor line_siegbahn = iupac_to_siegbahn.get(line_iupac, line_iupac) print(f"Applied scaling factor {factor} to cross-section for element {element}, line {line_siegbahn}") else: print(f"Skipped element {element}, line {line_iupac}: No matching data in database.") elif input_type == 'k_factors' and reference_line is not None: ref_match = re.match(r"([A-Z][a-z]?)_(.*)", reference_line) ref_element = ref_match.group(1) ref_line_family = ref_match.group(2) ref_at_num = str(atomic_symbols_data["table"][ref_element]['number']) ref_lines_iupac = siegbahn_to_iupac_data.get(ref_line_family, []) if not ref_lines_iupac: raise ValueError(f"Reference line family {ref_line_family} not found in Siegbahn to IUPAC mapping.") valid_ref_lines = { line: cross_sections_data['table'][ref_at_num][line]['cs'] for line in ref_lines_iupac if line in cross_sections_data['table'][ref_at_num] } if not valid_ref_lines: raise ValueError(f"No valid reference lines found for {ref_element}_{ref_line_family} in the cross-section data.") for el_line, factor in k_factors.items(): line_match = re.match(r"([A-Z][a-z]?)_(.*)", el_line) element = line_match.group(1) line_family = line_match.group(2) atomic_number = str(atomic_symbols_data['table'][element]['number']) target_lines_iupac = siegbahn_to_iupac_data.get(line_family, []) if not target_lines_iupac: raise ValueError(f"Target line family {line_family} not found in Siegbahn to IUPAC mapping.") for ref_line, target_line in zip(ref_lines_iupac, target_lines_iupac): if ref_line in valid_ref_lines and target_line in cross_sections_data['table'][atomic_number]: ref_cs = valid_ref_lines[ref_line] new_value = ref_cs * factor cross_sections_data['table'][atomic_number][target_line]['cs'] = new_value ref_line_siegbahn = iupac_to_siegbahn.get(ref_line, ref_line) target_line_siegbahn = iupac_to_siegbahn.get(target_line, target_line) print(f"Set new cross-section for element {element}, line {target_line_siegbahn} " f"based on k-factor {factor} using {ref_element}, line {ref_line_siegbahn} as reference.") else: print(f"Skipped element {element}, line {target_line}: No matching data in reference or target.") original_files_list = [ 'SDD_efficiency.txt', '200keV_xrays.json', '__init__.py', 'default_xrays.json', 'periodic_table_symbols.json', 'siegbahn_to_iupac.json', '300keV_xrays.json', 'periodic_table_number.json', '100keV_xrays.json' ] if output_filename is None: input_filename = cross_sections_json.name output_filename = input_filename.replace('.json', '_modified.json') if output_filename in original_files_list: raise ValueError("The output filename cannot be the same as one of the original files.") with open(DB_PATH / output_filename, 'w') as file: json.dump(cross_sections_data, file, indent = 4)
[docs] def load_table (db_name : str) -> tuple[dict, dict] : r""" Load the table and metadata of a json table generated by emtables. Parameters ---------- db_name : str The file name of the table to load. Returns ------- table : dict The table of the cross sections. metadata : dict The metadata of the table. Notes ----- Call espm.conf.DB_PATH to get the folder of the tables. """ db_path = DB_PATH / Path(db_name) with open(db_path,"r") as f : json_dict = json.load(f) return json_dict["table"], json_dict["metadata"]
[docs] def import_k_factors(table : dict, mdata : dict, k_factors_names : list[str], k_factors_values : list[int], ref_name : str) -> tuple[dict,dict] : r""" Modify the X-ray emission cross-sections of the input table using the k-factors input, i.e. imposing cross-sections ratios to correspond to the k-factors. The metadata are modified too to keep track of the modifications. Parameters ---------- table : dict The table of the X-ray emission cross sections. mdata : dict The metadata of the table. k_factors_names : list The list of the names of the k-factors to import. It has to correspond to the nomenclature of the hyperspy X-ray lines. k_factors_values : list The list of the values of the k-factors to import. It has to have the same length and ordering as k_factors_names. ref_name : str The name of the X-ray line to use as a reference for the k-factors. It has to correspond to the nomenclature of the hyperspy X-ray lines. Returns ------- new_table : dict The modified table of the X-ray emission cross sections. new_mdata : dict The modified metadata of the table. """ with open(SYMBOLS_PERIODIC_TABLE,"r") as f : SPT = json.load(f)["table"] with open(SIEGBAHN_TO_IUPAC,"r") as f : STI = json.load(f) for i,name in enumerate(k_factors_names) : if name == ref_name : mr = re.match(r"([A-Z][a-z]?)_(.*)",name) ref_at_num = SPT[mr.group(1)]["number"] ref_lines = STI[mr.group(2)] ref_sig_vals = [] for l in ref_lines : if l in table[str(ref_at_num)] : ref_sig_vals.append(table[str(ref_at_num)][l]["cs"]) ref_sig_val = np.mean(ref_sig_vals) ref_k_val = k_factors_values[i] for i,name in enumerate(k_factors_names) : m0 = re.match(r"([A-Z][a-z]?)_(.*)",name) if m0 : at_num = SPT[m0.group(1)]["number"] lines = STI[m0.group(2)] for line in lines : new_k = k_factors_values[i]/ref_k_val if line in table[str(at_num)] : sig_val = table[str(at_num)][line]["cs"] new_value = ref_sig_val*new_k/sig_val new_table, new_mdata = modify_table_lines(table,mdata,[at_num],line,new_value) return new_table,new_mdata
[docs] def modify_table_lines (table : dict, mdata : dict, elements : list[str], line : str, coeff : float) -> tuple[dict,dict] : r""" Modify the cross section of the lines of the selected elements in the input table. Parameters ---------- table : dict The table of the X-ray emission cross sections. mdata : dict The metadata of the table. elements : list The list of the atomic numbers of the elements to modify. line : str The regex of the line to modify. It has to correspond to IUPAC notation. coeff : float The coefficient to multiply the cross section of the selected lines. Returns ------- new_table : dict The modified table of the X-ray emission cross sections. new_mdata : dict The modified metadata of the table. Notes ----- X-ray line regex examples : input "L" will modify all the L lines, input "L3" will modifiy all the L3 lines, input "L3M2" will modify the "L3M2" line. """ if mdata["lines"] : for elt in elements : for key in table[str(elt)].keys() : if re.match(r"^{}".format(line),key) : table[str(elt)][key]["cs"] *=coeff if "modifications" in mdata : mdata["modifications"][str(elt) + "_" + key] = coeff else : mdata["modifications"] = {} mdata["modifications"][str(elt) + "_" + key] = coeff else : print("You need to enable line notation") return table, mdata
[docs] def save_table (filename : str, table : dict, mdata : dict) -> None : r""" Saves a table and its metadata in a json file. The structure of the json file is compliant with espm. """ d = {} d["table"] = table d["metadata"] = mdata with open(filename,"w") as f : json.dump(d,f,indent = 4)
[docs] def get_k_factor (table : dict, mdata : dict, element : int, line : str, range : float = 0.5, ref_elt : str = "14", ref_line : str = "KL3", ref_range : float = 0.5) : r""" Obtain the k-factor of a line from an emtables, X-ray emission cross section table. Parameters ---------- table : dict The table of the X-ray emission cross sections. mdata : dict The metadata of the table. element : int The atomic number of the element to use. line : str The regex of the line to use. It has to correspond to IUPAC notation. range : float The energy range to use for the integration of the cross section of the line. For example, if range = 0.5, the integration will be done between the energy of the line - 0.5 and the energy of the line + 0.5. We do so that when you select the "KL3" line, it integrates around it and make it correspond to the K-alpha bunch of lines. ref_elt : str The atomic number of the element to use as a reference for the k-factor. The default reference line is Si "KL3" with an integration range of 0.5. ref_line : str The regex of the line to use as a reference for the k-factor. It has to correspond to IUPAC notation. ref_range : float The energy range to use for the integration of the cross section of the reference line. Returns ------- k_factor : float The k-factor of the line. It does not take into account the absorption correction. """ ref_cs = 0.0 cs = 0.0 if mdata["lines"] : ref_en = table[str(ref_elt)][ref_line]["energy"] for key in table[str(ref_elt)].keys() : en = table[str(ref_elt)][key]["energy"] if (en < ref_en + ref_range) and (en > ref_en - ref_range) : ref_cs += table[str(ref_elt)][key]["cs"] elt_en = table[str(element)][line]["energy"] for key in table[str(element)].keys() : en = table[str(element)][key]["energy"] if (en < elt_en + range) and (en > elt_en - range) : cs += table[str(element)][key]["cs"] else : print("You need to enable line notation") return cs/ref_cs