def add_xray_lines_markers(self, xray_lines): """ Add marker on a spec.plot() with the name of the selected X-ray lines Parameters ---------- xray_lines: list of string A valid list of X-ray lines """ line_energy = [] intensity = [] for xray_line in xray_lines: element, line = utils_eds._get_element_and_line(xray_line) line_energy.append(self._get_line_energy(xray_line)) relative_factor = elements_db[element][ 'Atomic_properties']['Xray_lines'][line]['weight'] a_eng = self._get_line_energy(element + '_' + line[0] + 'a') intensity.append(self.isig[a_eng].data * relative_factor) for i in range(len(line_energy)): line = markers.vertical_line_segment( x=line_energy[i], y1=None, y2=intensity[i] * 0.8) self.add_marker(line, render_figure=False) string = (r'$\mathrm{%s}_{\mathrm{%s}}$' % utils_eds._get_element_and_line(xray_lines[i])) text = markers.text( x=line_energy[i], y=intensity[i] * 1.1, text=string, rotation=90) self.add_marker(text, render_figure=False) self._xray_markers[xray_lines[i]] = [line, text] line.events.closed.connect(self._xray_marker_closed) text.events.closed.connect(self._xray_marker_closed) self._render_figure(plot=['signal_plot'])
def _add_xray_lines_markers(self, xray_lines): """ Add marker on a spec.plot() with the name of the selected X-ray lines Parameters ---------- xray_lines: list of string A valid list of X-ray lines """ line_energy = [] intensity = [] for xray_line in xray_lines: element, line = utils_eds._get_element_and_line(xray_line) line_energy.append(self._get_line_energy(xray_line)) relative_factor = elements_db[element][ 'Atomic_properties']['Xray_lines'][line]['weight'] a_eng = self._get_line_energy(element + '_' + line[0] + 'a') intensity.append(self.isig[a_eng].data * relative_factor) for i in range(len(line_energy)): line = markers.vertical_line_segment( x=line_energy[i], y1=None, y2=intensity[i] * 0.8) self.add_marker(line) text = markers.text( x=line_energy[i], y=intensity[i] * 1.1, text=xray_lines[i], rotation=90) self.add_marker(text) self._xray_markers[xray_lines[i]] = (line, text)
def _make_position_adjuster(self, component, fix_it, show_label): # Override to ensure formatting of labels of xray lines super(EDSModel, self)._make_position_adjuster(component, fix_it, show_label) if show_label and component in (self.xray_lines + self.family_lines): label = self._position_widgets[component._position][1] label.string = (r"$\mathrm{%s}_{\mathrm{%s}}$" % _get_element_and_line(component.name))
def _make_position_adjuster(self, component, fix_it, show_label): # Override to ensure formatting of labels of xray lines super(EDSModel, self)._make_position_adjuster( component, fix_it, show_label) if show_label and component in (self.xray_lines + self.family_lines): label = self._position_widgets[component._position][1] label.string = (r"$\mathrm{%s}_{\mathrm{%s}}$" % _get_element_and_line(component.name))
def add_xray_lines_markers(self, xray_lines, render_figure=True): """ Add marker on a spec.plot() with the name of the selected X-ray lines Parameters ---------- xray_lines: list of string A valid list of X-ray lines """ line_energy = [] intensity = [] for xray_line in xray_lines: element, line = utils_eds._get_element_and_line(xray_line) line_energy.append(self._get_line_energy(xray_line)) relative_factor = elements_db[element]['Atomic_properties'][ 'Xray_lines'][line]['weight'] a_eng = self._get_line_energy(f'{element}_{line[0]}a') idx = self.axes_manager.signal_axes[0].value2index(a_eng) intensity.append(self.data[..., idx] * relative_factor) for i in range(len(line_energy)): line = markers.vertical_line_segment(x=line_energy[i], y1=None, y2=intensity[i] * 0.8) self.add_marker(line, render_figure=False) string = (r'$\mathrm{%s}_{\mathrm{%s}}$' % utils_eds._get_element_and_line(xray_lines[i])) text = markers.text(x=line_energy[i], y=intensity[i] * 1.1, text=string, rotation=90) self.add_marker(text, render_figure=False) self._xray_markers[xray_lines[i]] = [line, text] line.events.closed.connect(self._xray_marker_closed) text.events.closed.connect(self._xray_marker_closed) if render_figure: self._render_figure(plot=['signal_plot'])
def estimate_integration_windows(self, windows_width=2., xray_lines=None): """ Estimate a window of integration for each X-ray line. Parameters ---------- windows_width: float The width of the integration windows is the 'windows_width' times the calculated FWHM of the line. xray_lines: None or list of string If None, use 'metadata.Sample.elements.xray_lines'. Else, provide an iterable containing a list of valid X-ray lines symbols. Return ------ integration_windows: 2D array of float The positions of the windows in energy. Each row corresponds to a X-ray line. Each row contains the left and right value of the window. Examples -------- >>> s = utils.example_signals.EDS_TEM_Spectrum() >>> s.add_lines() >>> iw = s.estimate_integration_windows() >>> s.plot(integration_windows=iw) >>> s.get_lines_intensity(integration_windows=iw, plot_result=True) Fe_Ka at 6.4039 keV : Intensity = 3710.00 Pt_La at 9.4421 keV : Intensity = 15872.00 See also -------- plot, get_lines_intensity """ if xray_lines is None: xray_lines = self.metadata.Sample.xray_lines integration_windows = [] for Xray_line in xray_lines: line_energy, line_FWHM = self._get_line_energy(Xray_line, FWHM_MnKa='auto') element, line = utils_eds._get_element_and_line(Xray_line) det = windows_width * line_FWHM / 2. integration_windows.append([line_energy - det, line_energy + det]) return integration_windows
def estimate_integration_windows(self, windows_width=2., xray_lines=None): """ Estimate a window of integration for each X-ray line. Parameters ---------- windows_width: float The width of the integration windows is the 'windows_width' times the calculated FWHM of the line. xray_lines: None or list of string If None, use 'metadata.Sample.elements.xray_lines'. Else, provide an iterable containing a list of valid X-ray lines symbols. Return ------ integration_windows: 2D array of float The positions of the windows in energy. Each row corresponds to a X-ray line. Each row contains the left and right value of the window. Examples -------- >>> s = hs.datasets.example_signals.EDS_TEM_Spectrum() >>> s.add_lines() >>> iw = s.estimate_integration_windows() >>> s.plot(integration_windows=iw) >>> s.get_lines_intensity(integration_windows=iw, plot_result=True) Fe_Ka at 6.4039 keV : Intensity = 3710.00 Pt_La at 9.4421 keV : Intensity = 15872.00 See also -------- plot, get_lines_intensity """ xray_lines = self._get_xray_lines(xray_lines) integration_windows = [] for Xray_line in xray_lines: line_energy, line_FWHM = self._get_line_energy(Xray_line, FWHM_MnKa='auto') element, line = utils_eds._get_element_and_line(Xray_line) det = windows_width * line_FWHM / 2. integration_windows.append([line_energy - det, line_energy + det]) return integration_windows
def fix_twin(component): component.A.bmin = 0.0 component.A.bmax = None element, line = utils_eds._get_element_and_line(component.name) for li in elements_db[element]["Atomic_properties"]["Xray_lines"]: if line[0] in li and line != li: xray_sub = element + "_" + li if xray_sub in self: component_sub = self[xray_sub] component_sub.A.bmin = 1e-10 component_sub.A.bmax = None weight_line = component_sub.A.value / component.A.value component_sub.A.twin_function = _get_weight(element, li, weight_line) component_sub.A.twin_inverse_function = _get_iweight(element, li, weight_line) component_sub.A.twin = component.A else: warnings.warn("The X-ray line expected to be in the " "model was not found")
def fix_twin(component): component.A.bmin = 0.0 component.A.bmax = None element, line = utils_eds._get_element_and_line(component.name) for li in elements_db[element]['Atomic_properties']['Xray_lines']: if line[0] in li and line != li: xray_sub = element + '_' + li if xray_sub in self: component_sub = self[xray_sub] component_sub.A.bmin = 1e-10 component_sub.A.bmax = None weight_line = component_sub.A.value / component.A.value component_sub.A.twin_function_expr = _get_weight( element, li, weight_line) component_sub.A.twin = component.A else: warnings.warn("The X-ray line expected to be in the " "model was not found")
def add_family_lines(self, xray_lines='from_elements'): """Create the Xray-lines instances and configure them appropiately If a X-ray line is given, all the the lines of the familiy is added. For instance if Zn Ka is given, Zn Kb is added too. The main lines (alpha) is added to self.xray_lines Parameters ----------- xray_lines: {None, 'from_elements', list of string} If None, if `metadata` contains `xray_lines` list of lines use those. If 'from_elements', add all lines from the elements contains in `metadata`. Alternatively, provide an iterable containing a list of valid X-ray lines symbols. (eg. ('Al_Ka','Zn_Ka')). """ only_one = False only_lines = ("Ka", "La", "Ma") if xray_lines is None or xray_lines == 'from_elements': if 'Sample.xray_lines' in self.signal.metadata \ and xray_lines != 'from_elements': xray_lines = self.signal.metadata.Sample.xray_lines elif 'Sample.elements' in self.signal.metadata: xray_lines = self.signal._get_lines_from_elements( self.signal.metadata.Sample.elements, only_one=only_one, only_lines=only_lines) else: raise ValueError( "No elements defined, set them with `add_elements`") components_names = [xr.name for xr in self.xray_lines] xray_lines = filter(lambda x: x not in components_names, xray_lines) xray_lines, xray_not_here = self.signal.\ _get_xray_lines_in_spectral_range(xray_lines) for xray in xray_not_here: warnings.warn("%s is not in the data energy range." % (xray)) for xray_line in xray_lines: element, line = utils_eds._get_element_and_line(xray_line) line_energy, line_FWHM = self.signal._get_line_energy( xray_line, FWHM_MnKa='auto') component = create_component.Gaussian() component.centre.value = line_energy component.fwhm = line_FWHM component.centre.free = False component.sigma.free = False component.name = xray_line self.append(component) self.xray_lines.append(component) self[xray_line].A.map[ 'values'] = self.signal.isig[line_energy].data * \ line_FWHM / self.signal.axes_manager[-1].scale self[xray_line].A.map['is_set'] = ( np.ones(self.signal.isig[line_energy].data.shape) == 1) component.A.ext_force_positive = True for li in elements_db[element]['Atomic_properties']['Xray_lines']: if line[0] in li and line != li: xray_sub = element + '_' + li if self.signal.\ _get_xray_lines_in_spectral_range( [xray_sub])[0] != []: line_energy, line_FWHM = self.signal.\ _get_line_energy( xray_sub, FWHM_MnKa='auto') component_sub = create_component.Gaussian() component_sub.centre.value = line_energy component_sub.fwhm = line_FWHM component_sub.centre.free = False component_sub.sigma.free = False component_sub.name = xray_sub component_sub.A.twin_function = _get_weight( element, li) component_sub.A.twin_inverse_function = _get_iweight( element, li) component_sub.A.twin = component.A self.append(component_sub) self.family_lines.append(component_sub) self.fetch_stored_values()
def quantification(self, intensities, method, factors, composition_units='atomic', absorption_correction=False, take_off_angle='auto', thickness='auto', convergence_criterion=0.5, navigation_mask=1.0, closing=True, plot_result=False, probe_area='auto', max_iterations=30, show_progressbar=None, **kwargs): """ Absorption corrected quantification using Cliff-Lorimer, the zeta-factor method, or ionization cross sections. The function iterates through quantification function until two successive interations don't change the final composition by a defined percentage critera (0.5% by default). Parameters ---------- intensities: list of signal the intensitiy for each X-ray lines. method: {'CL', 'zeta', 'cross_section'} Set the quantification method: Cliff-Lorimer, zeta-factor, or ionization cross sections. factors: list of float The list of kfactors, zeta-factors or cross sections in same order as intensities. Note that intensities provided by Hyperspy are sorted by the alphabetical order of the X-ray lines. eg. factors =[0.982, 1.32, 1.60] for ['Al_Ka', 'Cr_Ka', 'Ni_Ka']. composition_units: {'atomic', 'weight'} The quantification returns the composition in 'atomic' percent by default, but can also return weight percent if specified. absorption_correction: bool Specify whether or not an absorption correction should be applied. 'False' by default so absorption will not be applied unless specfied. take_off_angle : {'auto'} The angle between the sample surface and the vector along which X-rays travel to reach the centre of the detector. thickness: {'auto'} thickness in nm (can be a single value or have the same navigation dimension as the signal). NB: Must be specified for 'CL' method. For 'zeta' or 'cross_section' methods, first quantification step provides a mass_thickness internally during quantification. convergence_criterion: The convergence criterium defined as the percentage difference between 2 successive iterations. 0.5% by default. navigation_mask : None or float or signal The navigation locations marked as True are not used in the quantification. If float is given the vacuum_mask method is used to generate a mask with the float value as threhsold. Else provides a signal with the navigation shape. Only for the 'Cliff-Lorimer' method. closing: bool If true, applied a morphologic closing to the mask obtained by vacuum_mask. plot_result : bool If True, plot the calculated composition. If the current object is a single spectrum it prints the result instead. probe_area = {'auto'} This allows the user to specify the probe_area for interaction with the sample needed specifically for the cross_section method of quantification. When left as 'auto' the pixel area is used, calculated from the navigation axes information. max_iterations : int An upper limit to the number of calculations for absorption correction. kwargs The extra keyword arguments are passed to plot. Returns ------- A list of quantified elemental maps (signal) giving the composition of the sample in weight or atomic percent with absorption correciton taken into account based on the sample thickness estimate provided. If the method is 'zeta' this function also returns the mass thickness profile for the data. If the method is 'cross_section' this function also returns the atom counts for each element. Examples -------- >>> s = hs.datasets.example_signals.EDS_TEM_Spectrum() >>> s.add_lines() >>> kfactors = [1.450226, 5.075602] #For Fe Ka and Pt La >>> bw = s.estimate_background_windows(line_width=[5.0, 2.0]) >>> s.plot(background_windows=bw) >>> intensities = s.get_lines_intensity(background_windows=bw) >>> res = s.quantification(intensities, kfactors, plot_result=True, >>> composition_units='atomic') Fe (Fe_Ka): Composition = 15.41 atomic percent Pt (Pt_La): Composition = 84.59 atomic percent See also -------- vacuum_mask """ if isinstance(navigation_mask, float): if self.axes_manager.navigation_dimension > 0: navigation_mask = self.vacuum_mask(navigation_mask, closing) else: navigation_mask = None xray_lines = [ intensity.metadata.Sample.xray_lines[0] for intensity in intensities ] it = 0 if absorption_correction: if show_progressbar is None: # pragma: no cover show_progressbar = preferences.General.show_progressbar if show_progressbar: pbar = progressbar(total=None, desc='Absorption correction calculation') composition = utils.stack(intensities, lazy=False, show_progressbar=False) if take_off_angle == 'auto': toa = self.get_take_off_angle() else: toa = take_off_angle #determining illumination area for cross sections quantification. if method == 'cross_section': if probe_area == 'auto': parameters = self.metadata.Acquisition_instrument.TEM if probe_area in parameters: probe_area = parameters.TEM.probe_area else: probe_area = self.get_probe_area( navigation_axes=self.axes_manager.navigation_axes) int_stack = utils.stack(intensities, lazy=False, show_progressbar=False) comp_old = np.zeros_like(int_stack.data) abs_corr_factor = None # initial if method == 'CL': quantification_method = utils_eds.quantification_cliff_lorimer kwargs = { "intensities": int_stack.data, "kfactors": factors, "absorption_correction": abs_corr_factor, "mask": navigation_mask } elif method == 'zeta': quantification_method = utils_eds.quantification_zeta_factor kwargs = { "intensities": int_stack.data, "zfactors": factors, "dose": self._get_dose(method), "absorption_correction": abs_corr_factor } elif method == 'cross_section': quantification_method = utils_eds.quantification_cross_section kwargs = { "intensities": int_stack.data, "cross_sections": factors, "dose": self._get_dose(method, **kwargs), "absorption_correction": abs_corr_factor } else: raise ValueError('Please specify method for quantification, ' 'as "CL", "zeta" or "cross_section".') while True: results = quantification_method(**kwargs) if method == 'CL': composition.data = results * 100. if absorption_correction: if thickness is not None: mass_thickness = intensities[0].deepcopy() mass_thickness.data = self.CL_get_mass_thickness( composition.split(), thickness) mass_thickness.metadata.General.title = 'Mass thickness' else: raise ValueError( 'Thickness is required for absorption correction ' 'with k-factor method. Results will contain no ' 'correction for absorption.') elif method == 'zeta': composition.data = results[0] * 100 mass_thickness = intensities[0].deepcopy() mass_thickness.data = results[1] else: composition.data = results[0] * 100. number_of_atoms = composition._deepcopy_with_new_data( results[1]) if method == 'cross_section': if absorption_correction: abs_corr_factor = utils_eds.get_abs_corr_cross_section( composition.split(), number_of_atoms.split(), toa, probe_area) kwargs["absorption_correction"] = abs_corr_factor else: if absorption_correction: abs_corr_factor = utils_eds.get_abs_corr_zeta( composition.split(), mass_thickness, toa) kwargs["absorption_correction"] = abs_corr_factor res_max = np.max(composition.data - comp_old) comp_old = composition.data if absorption_correction and show_progressbar: pbar.update(1) it += 1 if not absorption_correction or abs( res_max) < convergence_criterion: break elif it >= max_iterations: raise Exception('Absorption correction failed as solution ' f'did not converge after {max_iterations} ' 'iterations') if method == 'cross_section': number_of_atoms = composition._deepcopy_with_new_data(results[1]) number_of_atoms = number_of_atoms.split() composition = composition.split() else: composition = composition.split() #convert ouput units to selection as required. if composition_units == 'atomic': if method != 'cross_section': composition = utils.material.weight_to_atomic(composition) else: if method == 'cross_section': composition = utils.material.atomic_to_weight(composition) #Label each of the elemental maps in the image stacks for composition. for i, xray_line in enumerate(xray_lines): element, line = utils_eds._get_element_and_line(xray_line) composition[i].metadata.General.title = composition_units + \ ' percent of ' + element composition[i].metadata.set_item("Sample.elements", ([element])) composition[i].metadata.set_item("Sample.xray_lines", ([xray_line])) if plot_result and composition[i].axes_manager.navigation_size == 1: c = np.float(composition[i].data) print( f"{element} ({xray_line}): Composition = {c:.2f} percent") #For the cross section method this is repeated for the number of atom maps if method == 'cross_section': for i, xray_line in enumerate(xray_lines): element, line = utils_eds._get_element_and_line(xray_line) number_of_atoms[i].metadata.General.title = \ 'atom counts of ' + element number_of_atoms[i].metadata.set_item("Sample.elements", ([element])) number_of_atoms[i].metadata.set_item("Sample.xray_lines", ([xray_line])) if plot_result and composition[i].axes_manager.navigation_size != 1: utils.plot.plot_signals(composition, **kwargs) if absorption_correction: _logger.info(f'Conversion found after {it} interations.') if method == 'zeta': mass_thickness.metadata.General.title = 'Mass thickness' self.metadata.set_item("Sample.mass_thickness", mass_thickness) return composition, mass_thickness elif method == 'cross_section': return composition, number_of_atoms elif method == 'CL': if absorption_correction: mass_thickness.metadata.General.title = 'Mass thickness' return composition, mass_thickness else: return composition else: raise ValueError('Please specify method for quantification, as ' '"CL", "zeta" or "cross_section"')
def get_lines_intensity(self, Xray_lines=None, plot_result=False, integration_window_factor=2., only_one=True, only_lines=("Ka", "La", "Ma"),): """Return the intensity map of selected Xray lines. The intensity maps are computed by integrating the spectrum over the different X-ray lines. The integration window width is calculated from the energy resolution of the detector defined as defined in `self.mapped_parameters.SEM.EDS.energy_resolution_MnKa` or `self.mapped_parameters.SEM.EDS.energy_resolution_MnKa`. Parameters ---------- Xray_lines: {None, "best", list of string} If None, if `mapped.parameters.Sample.elements.Xray_lines` contains a list of lines use those. If `mapped.parameters.Sample.elements.Xray_lines` is undefined or empty but `mapped.parameters.Sample.elements` is defined, use the same syntax as `add_line` to select a subset of lines for the operation. Alternatively, provide an iterable containing a list of valid X-ray lines symbols. plot_result : bool If True, plot the calculated line intensities. If the current object is a single spectrum it prints the result instead. integration_window_factor: Float The integration window is centered at the center of the X-ray line and its width is defined by this factor (2 by default) times the calculated FWHM of the line. only_one : bool If False, use all the lines of each element in the data spectral range. If True use only the line at the highest energy above an overvoltage of 2 (< beam energy / 2). only_lines : {None, list of strings} If not None, use only the given lines. Returns ------- intensities : list A list containing the intensities as Signal subclasses. Examples -------- >>> specImg.plot_intensity_map(["C_Ka", "Ta_Ma"]) See also -------- set_elements, add_elements. """ if Xray_lines is None: if 'Sample.Xray_lines' in self.mapped_parameters: Xray_lines = self.mapped_parameters.Sample.Xray_lines elif 'Sample.elements' in self.mapped_parameters: Xray_lines = self._get_lines_from_elements( self.mapped_parameters.Sample.elements, only_one=only_one, only_lines=only_lines) else: raise ValueError( "Not X-ray line, set them with `add_elements`") if self.mapped_parameters.signal_type == 'EDS_SEM': FWHM_MnKa = self.mapped_parameters.SEM.EDS.energy_resolution_MnKa elif self.mapped_parameters.signal_type == 'EDS_TEM': FWHM_MnKa = self.mapped_parameters.TEM.EDS.energy_resolution_MnKa else: raise NotImplementedError( "This method only works for EDS_TEM or EDS_SEM signals. " "You can use `set_signal_type(\"EDS_TEM\")` or" "`set_signal_type(\"EDS_SEM\")` to convert to one of these" "signal types.") intensities = [] #test 1D Spectrum (0D problem) #signal_to_index = self.axes_manager.navigation_dimension - 2 for Xray_line in Xray_lines: element, line = utils_eds._get_element_and_line(Xray_line) line_energy = elements_db[element]['Xray_energy'][line] line_FWHM = FWHM_eds(FWHM_MnKa,line_energy) det = integration_window_factor * line_FWHM / 2. img = self[...,line_energy - det:line_energy + det ].integrate_simpson(-1) img.mapped_parameters.title = ( 'Intensity of %s at %.2f %s from %s' % (Xray_line, line_energy, self.axes_manager.signal_axes[0].units, self.mapped_parameters.title)) if img.axes_manager.navigation_dimension >= 2: img = img.as_image([0,1]) elif img.axes_manager.navigation_dimension == 1: img.axes_manager.set_signal_dimension(1) if plot_result: if img.axes_manager.signal_dimension != 0: img.plot() else: print("%s at %s %s : Intensity = %.2f" % (Xray_line, line_energy, self.axes_manager.signal_axes[0].units, img.data)) intensities.append(img) return intensities
def quantification(self, intensities, kfactors, composition_units='weight', navigation_mask=1.0, closing=True, plot_result=False, **kwargs): """ Quantification of intensities to return elemental composition Method: Cliff-Lorimer Parameters ---------- intensities: list of signal the intensitiy for each X-ray lines. kfactors: list of float The list of kfactor in same order as intensities. Note that intensities provided by hyperspy are sorted by the aplhabetical order of the X-ray lines. eg. kfactors =[0.982, 1.32, 1.60] for ['Al_Ka','Cr_Ka', 'Ni_Ka']. composition_units: 'weight' or 'atomic' Quantification returns weight percent. By choosing 'atomic', the return composition is in atomic percent. navigation_mask : None or float or signal The navigation locations marked as True are not used in the quantification. If int is given the vacuum_mask method is used to generate a mask with the int value as threhsold. Else provides a signal with the navigation shape. closing: bool If true, applied a morphologic closing to the mask obtained by vacuum_mask. plot_result : bool If True, plot the calculated composition. If the current object is a single spectrum it prints the result instead. kwargs The extra keyword arguments are passed to plot. Return ------ A list of quantified elemental maps (signal) giving the composition of the sample in weight or atomic percent. Examples -------- >>> s = utils.example_signals.EDS_TEM_Spectrum() >>> s.add_lines() >>> kfactors = [1.450226, 5.075602] #For Fe Ka and Pt La >>> bw = s.estimate_background_windows(line_width=[5.0, 2.0]) >>> s.plot(background_windows=bw) >>> intensities = s.get_lines_intensity(background_windows=bw) >>> res = s.quantification(intensities, kfactors, plot_result=True, >>> composition_units='atomic') Fe (Fe_Ka): Composition = 15.41 atomic percent Pt (Pt_La): Composition = 84.59 atomic percent See also -------- vacuum_mask """ if isinstance(navigation_mask, float): navigation_mask = self.vacuum_mask(navigation_mask, closing).data elif navigation_mask is not None: navigation_mask = navigation_mask.data xray_lines = self.metadata.Sample.xray_lines composition = utils.stack(intensities) composition.data = utils_eds.quantification_cliff_lorimer( composition.data, kfactors=kfactors, mask=navigation_mask) * 100. composition = composition.split() if composition_units == 'atomic': composition = utils.material.weight_to_atomic(composition) for i, xray_line in enumerate(xray_lines): element, line = utils_eds._get_element_and_line(xray_line) composition[i].metadata.General.title = composition_units + \ ' percent of ' + element composition[i].metadata.set_item("Sample.elements", ([element])) composition[i].metadata.set_item( "Sample.xray_lines", ([xray_line])) if plot_result and \ composition[i].axes_manager.signal_dimension == 0: print("%s (%s): Composition = %.2f %s percent" % (element, xray_line, composition[i].data, composition_units)) if plot_result and composition[i].axes_manager.signal_dimension != 0: utils.plot.plot_signals(composition, **kwargs) return composition
def get_lines_intensity(self, xray_lines=None, integration_windows=2., background_windows=None, plot_result=False, only_one=True, only_lines=("a",), **kwargs): """Return the intensity map of selected Xray lines. The intensities, the number of X-ray counts, are computed by suming the spectrum over the different X-ray lines. The sum window width is calculated from the energy resolution of the detector as defined in 'energy_resolution_MnKa' of the metadata. Backgrounds average in provided windows can be subtracted from the intensities. Parameters ---------- xray_lines: {None, "best", list of string} If None, if `metadata.Sample.elements.xray_lines` contains a list of lines use those. If `metadata.Sample.elements.xray_lines` is undefined or empty but `metadata.Sample.elements` is defined, use the same syntax as `add_line` to select a subset of lines for the operation. Alternatively, provide an iterable containing a list of valid X-ray lines symbols. integration_windows: Float or array If float, the width of the integration windows is the 'integration_windows_width' times the calculated FWHM of the line. Else provide an array for which each row corresponds to a X-ray line. Each row contains the left and right value of the window. background_windows: None or 2D array of float If None, no background subtraction. Else, the backgrounds average in the windows are subtracted from the return intensities. 'background_windows' provides the position of the windows in energy. Each line corresponds to a X-ray line. In a line, the two first values correspond to the limits of the left window and the two last values correspond to the limits of the right window. plot_result : bool If True, plot the calculated line intensities. If the current object is a single spectrum it prints the result instead. only_one : bool If False, use all the lines of each element in the data spectral range. If True use only the line at the highest energy above an overvoltage of 2 (< beam energy / 2). only_lines : {None, list of strings} If not None, use only the given lines. kwargs The extra keyword arguments for plotting. See `utils.plot.plot_signals` Returns ------- intensities : list A list containing the intensities as Signal subclasses. Examples -------- >>> s = hs.datasets.example_signals.EDS_SEM_Spectrum() >>> s.get_lines_intensity(['Mn_Ka'], plot_result=True) Mn_La at 0.63316 keV : Intensity = 96700.00 >>> s = hs.datasets.example_signals.EDS_SEM_Spectrum() >>> s.plot(['Mn_Ka'], integration_windows=2.1) >>> s.get_lines_intensity(['Mn_Ka'], >>> integration_windows=2.1, plot_result=True) Mn_Ka at 5.8987 keV : Intensity = 53597.00 >>> s = hs.datasets.example_signals.EDS_SEM_Spectrum() >>> s.set_elements(['Mn']) >>> s.set_lines(['Mn_Ka']) >>> bw = s.estimate_background_windows() >>> s.plot(background_windows=bw) >>> s.get_lines_intensity(background_windows=bw, plot_result=True) Mn_Ka at 5.8987 keV : Intensity = 46716.00 See also -------- set_elements, add_elements, estimate_background_windows, plot """ only_lines = utils_eds._parse_only_lines(only_lines) xray_lines = self._get_xray_lines(xray_lines, only_one=only_one, only_lines=only_lines) xray_lines, xray_not_here = self._get_xray_lines_in_spectral_range( xray_lines) for xray in xray_not_here: warnings.warn("%s is not in the data energy range." % xray + "You can remove it with" + "s.metadata.Sample.xray_lines.remove('%s')" % xray) if hasattr(integration_windows, '__iter__') is False: integration_windows = self.estimate_integration_windows( windows_width=integration_windows, xray_lines=xray_lines) intensities = [] ax = self.axes_manager.signal_axes[0] # test 1D Spectrum (0D problem) # signal_to_index = self.axes_manager.navigation_dimension - 2 for i, (Xray_line, window) in enumerate( zip(xray_lines, integration_windows)): line_energy, line_FWHM = self._get_line_energy(Xray_line, FWHM_MnKa='auto') element, line = utils_eds._get_element_and_line(Xray_line) img = self.isig[window[0]:window[1]].integrate1D(-1) if background_windows is not None: bw = background_windows[i] # TODO: test to prevent slicing bug. To be reomved when fixed indexes = [float(ax.value2index(de)) for de in list(bw) + window] if indexes[0] == indexes[1]: bck1 = self.isig[bw[0]] else: bck1 = self.isig[bw[0]:bw[1]].integrate1D(-1) if indexes[2] == indexes[3]: bck2 = self.isig[bw[2]] else: bck2 = self.isig[bw[2]:bw[3]].integrate1D(-1) corr_factor = (indexes[5] - indexes[4]) / ( (indexes[1] - indexes[0]) + (indexes[3] - indexes[2])) img -= (bck1 + bck2) * corr_factor img.metadata.General.title = ( 'X-ray line intensity of %s: %s at %.2f %s' % (self.metadata.General.title, Xray_line, line_energy, self.axes_manager.signal_axes[0].units, )) if img.axes_manager.navigation_dimension >= 2: img = img.as_image([0, 1]) elif img.axes_manager.navigation_dimension == 1: img.axes_manager.set_signal_dimension(1) if plot_result and img.axes_manager.signal_dimension == 0: print("%s at %s %s : Intensity = %.2f" % (Xray_line, line_energy, ax.units, img.data)) img.metadata.set_item("Sample.elements", ([element])) img.metadata.set_item("Sample.xray_lines", ([Xray_line])) intensities.append(img) if plot_result and img.axes_manager.signal_dimension != 0: utils.plot.plot_signals(intensities, **kwargs) return intensities
def test_get_element_and_line(): assert _get_element_and_line('Mn_Ka') == ('Mn', 'Ka') with pytest.raises(ValueError): _get_element_and_line('MnKa') == -1
def _get_line_energy(self, Xray_line, FWHM_MnKa=None): """ Get the line energy and the energy resolution of a Xray line. The return values are in the same units than the signal axis Parameters ---------- Xray_line : strings Valid element X-ray lines e.g. Fe_Kb. FWHM_MnKa: {None, float, 'auto'} The energy resolution of the detector in eV if 'auto', used the one in 'self.metadata.Acquisition_instrument.SEM.Detector.EDS.energy_resolution_MnKa' Returns ------ float: the line energy, if FWHM_MnKa is None (float,float): the line energy and the energy resolution, if FWHM_MnKa is not None """ units_name = self.axes_manager.signal_axes[0].units element, line = utils_eds._get_element_and_line(Xray_line) if FWHM_MnKa == 'auto': if self.metadata.Signal.signal_type == 'EDS_SEM': FWHM_MnKa = self.metadata.Acquisition_instrument.SEM.Detector.EDS.energy_resolution_MnKa elif self.metadata.Signal.signal_type == 'EDS_TEM': FWHM_MnKa = self.metadata.Acquisition_instrument.TEM.Detector.EDS.energy_resolution_MnKa else: raise NotImplementedError( "This method only works for EDS_TEM or EDS_SEM signals. " "You can use `set_signal_type(\"EDS_TEM\")` or" "`set_signal_type(\"EDS_SEM\")` to convert to one of these" "signal types.") if units_name == 'eV': line_energy = elements_db[element]['Atomic_properties']['Xray_lines'][ line]['energy (keV)'] * 1000 if FWHM_MnKa is not None: line_FWHM = utils_eds.get_FWHM_at_Energy(FWHM_MnKa, line_energy / 1000) * 1000 elif units_name == 'keV': line_energy = elements_db[element]['Atomic_properties']['Xray_lines'][ line]['energy (keV)'] if FWHM_MnKa is not None: line_FWHM = utils_eds.get_FWHM_at_Energy(FWHM_MnKa, line_energy) else: raise ValueError( "%s is not a valid units for the energy axis. " "Only `eV` and `keV` are supported. " "If `s` is the variable containing this EDS spectrum:\n " ">>> s.axes_manager.signal_axes[0].units = \'keV\' \n" % (units_name)) if FWHM_MnKa is None: return line_energy else: return line_energy, line_FWHM
def _get_line_energy(self, Xray_line, FWHM_MnKa=None): """ Get the line energy and the energy resolution of a Xray line. The return values are in the same units than the signal axis Parameters ---------- Xray_line : strings Valid element X-ray lines e.g. Fe_Kb. FWHM_MnKa: {None, float, 'auto'} The energy resolution of the detector in eV if 'auto', used the one in 'self.metadata.Acquisition_instrument.SEM.Detector.EDS.energy_resolution_MnKa' Returns ------ float: the line energy, if FWHM_MnKa is None (float,float): the line energy and the energy resolution, if FWHM_MnKa is not None """ units_name = self.axes_manager.signal_axes[0].units element, line = utils_eds._get_element_and_line(Xray_line) if FWHM_MnKa == 'auto': if self.metadata.Signal.signal_type == 'EDS_SEM': FWHM_MnKa = self.metadata.Acquisition_instrument.SEM.Detector.EDS.energy_resolution_MnKa elif self.metadata.Signal.signal_type == 'EDS_TEM': FWHM_MnKa = self.metadata.Acquisition_instrument.TEM.Detector.EDS.energy_resolution_MnKa else: raise NotImplementedError( "This method only works for EDS_TEM or EDS_SEM signals. " "You can use `set_signal_type(\"EDS_TEM\")` or" "`set_signal_type(\"EDS_SEM\")` to convert to one of these" "signal types.") if units_name == 'eV': line_energy = elements_db[element]['Atomic_properties'][ 'Xray_lines'][line]['energy (keV)'] * 1000 if FWHM_MnKa is not None: line_FWHM = utils_eds.get_FWHM_at_Energy( FWHM_MnKa, line_energy / 1000) * 1000 elif units_name == 'keV': line_energy = elements_db[element]['Atomic_properties'][ 'Xray_lines'][line]['energy (keV)'] if FWHM_MnKa is not None: line_FWHM = utils_eds.get_FWHM_at_Energy( FWHM_MnKa, line_energy) else: raise ValueError( "%s is not a valid units for the energy axis. " "Only `eV` and `keV` are supported. " "If `s` is the variable containing this EDS spectrum:\n " ">>> s.axes_manager.signal_axes[0].units = \'keV\' \n" % (units_name)) if FWHM_MnKa is None: return line_energy else: return line_energy, line_FWHM
def add_family_lines(self, xray_lines='from_elements'): """Create the Xray-lines instances and configure them appropiately If a X-ray line is given, all the the lines of the familiy is added. For instance if Zn Ka is given, Zn Kb is added too. The main lines (alpha) is added to self.xray_lines Parameters ----------- xray_lines: {None, 'from_elements', list of string} If None, if `metadata` contains `xray_lines` list of lines use those. If 'from_elements', add all lines from the elements contains in `metadata`. Alternatively, provide an iterable containing a list of valid X-ray lines symbols. (eg. ('Al_Ka','Zn_Ka')). """ only_one = False only_lines = ("Ka", "La", "Ma") if xray_lines is None or xray_lines == 'from_elements': if 'Sample.xray_lines' in self.signal.metadata \ and xray_lines != 'from_elements': xray_lines = self.signal.metadata.Sample.xray_lines elif 'Sample.elements' in self.signal.metadata: xray_lines = self.signal._get_lines_from_elements( self.signal.metadata.Sample.elements, only_one=only_one, only_lines=only_lines) else: raise ValueError( "No elements defined, set them with `add_elements`") components_names = [xr.name for xr in self.xray_lines] xray_lines = filter(lambda x: x not in components_names, xray_lines) xray_lines, xray_not_here = self.signal.\ _get_xray_lines_in_spectral_range(xray_lines) for xray in xray_not_here: warnings.warn("%s is not in the data energy range." % (xray)) for xray_line in xray_lines: element, line = utils_eds._get_element_and_line(xray_line) line_energy, line_FWHM = self.signal._get_line_energy( xray_line, FWHM_MnKa='auto') component = create_component.Gaussian() component.centre.value = line_energy component.fwhm = line_FWHM component.centre.free = False component.sigma.free = False component.name = xray_line self.append(component) self.xray_lines.append(component) self[xray_line].A.map[ 'values'] = self.signal.isig[line_energy].data * \ line_FWHM / self.signal.axes_manager[-1].scale self[xray_line].A.map['is_set'] = (np.ones( self.signal.isig[line_energy].data.shape) == 1) component.A.ext_force_positive = True for li in elements_db[element]['Atomic_properties']['Xray_lines']: if line[0] in li and line != li: xray_sub = element + '_' + li if self.signal.\ _get_xray_lines_in_spectral_range( [xray_sub])[0] != []: line_energy, line_FWHM = self.signal.\ _get_line_energy( xray_sub, FWHM_MnKa='auto') component_sub = create_component.Gaussian() component_sub.centre.value = line_energy component_sub.fwhm = line_FWHM component_sub.centre.free = False component_sub.sigma.free = False component_sub.name = xray_sub component_sub.A.twin_function = _get_weight( element, li) component_sub.A.twin_inverse_function = _get_iweight( element, li) component_sub.A.twin = component.A self.append(component_sub) self.fetch_stored_values()
def get_lines_intensity(self, xray_lines=None, plot_result=False, **kwargs): """ Return the fitted intensity of the X-ray lines. Return the area under the gaussian corresping to the X-ray lines Parameters ---------- xray_lines: list of str or None or 'from_metadata' If None, all main X-ray lines (alpha) If 'from_metadata', take the Xray_lines stored in the `metadata` of the spectrum. Alternatively, provide an iterable containing a list of valid X-ray lines symbols. plot_result : bool If True, plot the calculated line intensities. If the current object is a single spectrum it prints the result instead. kwargs The extra keyword arguments for plotting. See `utils.plot.plot_signals` Returns ------- intensities : list A list containing the intensities as Signal subclasses. Examples -------- >>> m.multifit() >>> m.get_lines_intensity(["C_Ka", "Ta_Ma"]) """ from hyperspy import utils intensities = [] if xray_lines is None: xray_lines = [component.name for component in self.xray_lines] else: if xray_lines == 'from_metadata': xray_lines = self.signal.metadata.Sample.xray_lines xray_lines = filter(lambda x: x in [a.name for a in self], xray_lines) if not xray_lines: raise ValueError("These X-ray lines are not part of the model.") for xray_line in xray_lines: element, line = utils_eds._get_element_and_line(xray_line) line_energy = self.signal._get_line_energy(xray_line) data_res = self[xray_line].A.map['values'] if self.axes_manager.navigation_dimension == 0: data_res = data_res[0] img = self.signal.isig[0:1].integrate1D(-1) img.data = data_res img.metadata.General.title = ( 'Intensity of %s at %.2f %s from %s' % (xray_line, line_energy, self.signal.axes_manager.signal_axes[0].units, self.signal.metadata.General.title)) if img.axes_manager.navigation_dimension >= 2: img = img.as_signal2D([0, 1]) elif img.axes_manager.navigation_dimension == 1: img.axes_manager.set_signal_dimension(1) if plot_result and img.axes_manager.signal_dimension == 0: print("%s at %s %s : Intensity = %.2f" % (xray_line, line_energy, self.signal.axes_manager.signal_axes[0].units, img.data)) img.metadata.set_item("Sample.elements", ([element])) img.metadata.set_item("Sample.xray_lines", ([xray_line])) intensities.append(img) if plot_result and img.axes_manager.signal_dimension != 0: utils.plot.plot_signals(intensities, **kwargs) return intensities
def get_lines_intensity( self, Xray_lines=None, plot_result=False, integration_window_factor=2., only_one=True, only_lines=("Ka", "La", "Ma"), ): """Return the intensity map of selected Xray lines. The intensity maps are computed by integrating the spectrum over the different X-ray lines. The integration window width is calculated from the energy resolution of the detector defined as defined in `self.mapped_parameters.SEM.EDS.energy_resolution_MnKa` or `self.mapped_parameters.SEM.EDS.energy_resolution_MnKa`. Parameters ---------- Xray_lines: {None, "best", list of string} If None, if `mapped.parameters.Sample.elements.Xray_lines` contains a list of lines use those. If `mapped.parameters.Sample.elements.Xray_lines` is undefined or empty but `mapped.parameters.Sample.elements` is defined, use the same syntax as `add_line` to select a subset of lines for the operation. Alternatively, provide an iterable containing a list of valid X-ray lines symbols. plot_result : bool If True, plot the calculated line intensities. If the current object is a single spectrum it prints the result instead. integration_window_factor: Float The integration window is centered at the center of the X-ray line and its width is defined by this factor (2 by default) times the calculated FWHM of the line. only_one : bool If False, use all the lines of each element in the data spectral range. If True use only the line at the highest energy above an overvoltage of 2 (< beam energy / 2). only_lines : {None, list of strings} If not None, use only the given lines. Returns ------- intensities : list A list containing the intensities as Signal subclasses. Examples -------- >>> specImg.plot_intensity_map(["C_Ka", "Ta_Ma"]) See also -------- set_elements, add_elements. """ if Xray_lines is None: if 'Sample.Xray_lines' in self.mapped_parameters: Xray_lines = self.mapped_parameters.Sample.Xray_lines elif 'Sample.elements' in self.mapped_parameters: Xray_lines = self._get_lines_from_elements( self.mapped_parameters.Sample.elements, only_one=only_one, only_lines=only_lines) else: raise ValueError( "Not X-ray line, set them with `add_elements`") if self.mapped_parameters.signal_type == 'EDS_SEM': FWHM_MnKa = self.mapped_parameters.SEM.EDS.energy_resolution_MnKa elif self.mapped_parameters.signal_type == 'EDS_TEM': FWHM_MnKa = self.mapped_parameters.TEM.EDS.energy_resolution_MnKa else: raise NotImplementedError( "This method only works for EDS_TEM or EDS_SEM signals. " "You can use `set_signal_type(\"EDS_TEM\")` or" "`set_signal_type(\"EDS_SEM\")` to convert to one of these" "signal types.") intensities = [] #test 1D Spectrum (0D problem) #signal_to_index = self.axes_manager.navigation_dimension - 2 for Xray_line in Xray_lines: element, line = utils_eds._get_element_and_line(Xray_line) line_energy = elements_db[element]['Xray_energy'][line] line_FWHM = FWHM_eds(FWHM_MnKa, line_energy) det = integration_window_factor * line_FWHM / 2. img = self[..., line_energy - det:line_energy + det].integrate_simpson(-1) img.mapped_parameters.title = ( 'Intensity of %s at %.2f %s from %s' % (Xray_line, line_energy, self.axes_manager.signal_axes[0].units, self.mapped_parameters.title)) if img.axes_manager.navigation_dimension >= 2: img = img.as_image([0, 1]) elif img.axes_manager.navigation_dimension == 1: img.axes_manager.set_signal_dimension(1) if plot_result: if img.axes_manager.signal_dimension != 0: img.plot() else: print("%s at %s %s : Intensity = %.2f" % (Xray_line, line_energy, self.axes_manager.signal_axes[0].units, img.data)) intensities.append(img) return intensities
def get_lines_intensity(self, xray_lines=None, plot_result=False, only_one=True, only_lines=("a", ), **kwargs): """ Return the fitted intensity of the X-ray lines. Return the area under the gaussian corresping to the X-ray lines Parameters ---------- xray_lines: {None, list of string} If None, if `metadata.Sample.elements.xray_lines` contains a list of lines use those. If `metadata.Sample.elements.xray_lines` is undefined or empty but `metadata.Sample.elements` is defined, use the same syntax as `add_line` to select a subset of lines for the operation. Alternatively, provide an iterable containing a list of valid X-ray lines symbols. plot_result : bool If True, plot the calculated line intensities. If the current object is a single spectrum it prints the result instead. only_one : bool If False, use all the lines of each element in the data spectral range. If True use only the line at the highest energy above an overvoltage of 2 (< beam energy / 2). only_lines : {None, list of strings} If not None, use only the given lines. kwargs The extra keyword arguments for plotting. See `utils.plot.plot_signals` Returns ------- intensities : list A list containing the intensities as Signal subclasses. Examples -------- >>> m.multifit() >>> m.get_lines_intensity(["C_Ka", "Ta_Ma"]) """ from hyperspy import utils intensities = [] if xray_lines is None: xray_lines = [component.name for component in self.xray_lines] else: xray_lines = self.signal._parse_xray_lines(xray_lines, only_one, only_lines) xray_lines = list( filter(lambda x: x in [a.name for a in self], xray_lines)) if len(xray_lines) == 0: raise ValueError("These X-ray lines are not part of the model.") for xray_line in xray_lines: element, line = utils_eds._get_element_and_line(xray_line) line_energy = self.signal._get_line_energy(xray_line) data_res = self[xray_line].A.map['values'] if self.axes_manager.navigation_dimension == 0: data_res = data_res[0] img = self.signal.isig[0:1].integrate1D(-1) img.data = data_res img.metadata.General.title = ( 'Intensity of %s at %.2f %s from %s' % (xray_line, line_energy, self.signal.axes_manager.signal_axes[0].units, self.signal.metadata.General.title)) img.axes_manager.set_signal_dimension(0) if plot_result and img.axes_manager.signal_dimension == 0: print( "%s at %s %s : Intensity = %.2f" % (xray_line, line_energy, self.signal.axes_manager.signal_axes[0].units, img.data)) img.metadata.set_item("Sample.elements", ([element])) img.metadata.set_item("Sample.xray_lines", ([xray_line])) intensities.append(img) if plot_result and img.axes_manager.signal_dimension != 0: utils.plot.plot_signals(intensities, **kwargs) return intensities
def quantification( self, intensities, kfactors, composition_units="weight", navigation_mask=1.0, closing=True, plot_result=False, **kwargs ): """ Quantification of intensities to return elemental composition Method: Cliff-Lorimer Parameters ---------- intensities: list of signal the intensitiy for each X-ray lines. kfactors: list of float The list of kfactor in same order as intensities. Note that intensities provided by hyperspy are sorted by the aplhabetical order of the X-ray lines. eg. kfactors =[0.982, 1.32, 1.60] for ['Al_Ka','Cr_Ka', 'Ni_Ka']. composition_units: 'weight' or 'atomic' Quantification returns weight percent. By choosing 'atomic', the return composition is in atomic percent. navigation_mask : None or float or signal The navigation locations marked as True are not used in the quantification. If int is given the vacuum_mask method is used to generate a mask with the int value as threhsold. Else provides a signal with the navigation shape. closing: bool If true, applied a morphologic closing to the mask obtained by vacuum_mask. plot_result : bool If True, plot the calculated composition. If the current object is a single spectrum it prints the result instead. kwargs The extra keyword arguments are passed to plot. Return ------ A list of quantified elemental maps (signal) giving the composition of the sample in weight or atomic percent. Examples -------- >>> s = utils.example_signals.EDS_TEM_Spectrum() >>> s.add_lines() >>> kfactors = [1.450226, 5.075602] #For Fe Ka and Pt La >>> bw = s.estimate_background_windows(line_width=[5.0, 2.0]) >>> s.plot(background_windows=bw) >>> intensities = s.get_lines_intensity(background_windows=bw) >>> res = s.quantification(intensities, kfactors, plot_result=True, >>> composition_units='atomic') Fe (Fe_Ka): Composition = 15.41 atomic percent Pt (Pt_La): Composition = 84.59 atomic percent See also -------- vacuum_mask """ if isinstance(navigation_mask, float): navigation_mask = self.vacuum_mask(navigation_mask, closing).data elif navigation_mask is not None: navigation_mask = navigation_mask.data xray_lines = self.metadata.Sample.xray_lines composition = utils.stack(intensities) composition.data = ( utils_eds.quantification_cliff_lorimer(composition.data, kfactors=kfactors, mask=navigation_mask) * 100.0 ) composition = composition.split() if composition_units == "atomic": composition = utils.material.weight_to_atomic(composition) for i, xray_line in enumerate(xray_lines): element, line = utils_eds._get_element_and_line(xray_line) composition[i].metadata.General.title = composition_units + " percent of " + element composition[i].metadata.set_item("Sample.elements", ([element])) composition[i].metadata.set_item("Sample.xray_lines", ([xray_line])) if plot_result and composition[i].axes_manager.signal_dimension == 0: print( "%s (%s): Composition = %.2f %s percent" % (element, xray_line, composition[i].data, composition_units) ) if plot_result and composition[i].axes_manager.signal_dimension != 0: utils.plot.plot_signals(composition, **kwargs) return composition
def get_lines_intensity(self, xray_lines=None, plot_result=False, **kwargs): """ Return the fitted intensity of the X-ray lines. Return the area under the gaussian corresping to the X-ray lines Parameters ---------- xray_lines: list of str or None or 'from_metadata' If None, all main X-ray lines (alpha) If 'from_metadata', take the Xray_lines stored in the `metadata` of the spectrum. Alternatively, provide an iterable containing a list of valid X-ray lines symbols. plot_result : bool If True, plot the calculated line intensities. If the current object is a single spectrum it prints the result instead. kwargs The extra keyword arguments for plotting. See `utils.plot.plot_signals` Returns ------- intensities : list A list containing the intensities as Signal subclasses. Examples -------- >>> m.multifit() >>> m.get_lines_intensity(["C_Ka", "Ta_Ma"]) """ from hyperspy import utils intensities = [] if xray_lines is None: xray_lines = [component.name for component in self.xray_lines] else: if xray_lines == 'from_metadata': xray_lines = self.signal.metadata.Sample.xray_lines xray_lines = filter(lambda x: x in [a.name for a in self], xray_lines) if not xray_lines: raise ValueError("These X-ray lines are not part of the model.") for xray_line in xray_lines: element, line = utils_eds._get_element_and_line(xray_line) line_energy = self.signal._get_line_energy(xray_line) data_res = self[xray_line].A.map['values'] if self.axes_manager.navigation_dimension == 0: data_res = data_res[0] img = self.signal.isig[0:1].integrate1D(-1) img.data = data_res img.metadata.General.title = ( 'Intensity of %s at %.2f %s from %s' % (xray_line, line_energy, self.signal.axes_manager.signal_axes[0].units, self.signal.metadata.General.title)) if img.axes_manager.navigation_dimension >= 2: img = img.as_signal2D([0, 1]) elif img.axes_manager.navigation_dimension == 1: img.axes_manager.set_signal_dimension(1) if plot_result and img.axes_manager.signal_dimension == 0: print( "%s at %s %s : Intensity = %.2f" % (xray_line, line_energy, self.signal.axes_manager.signal_axes[0].units, img.data)) img.metadata.set_item("Sample.elements", ([element])) img.metadata.set_item("Sample.xray_lines", ([xray_line])) intensities.append(img) if plot_result and img.axes_manager.signal_dimension != 0: utils.plot.plot_signals(intensities, **kwargs) return intensities
def on_pick_line(self, line): el, _ = _get_element_and_line(line) self.picked.emit(el)
def get_lines_intensity(self, xray_lines=None, integration_windows=2., background_windows=None, plot_result=False, only_one=True, only_lines=("a", ), **kwargs): """Return the intensity map of selected Xray lines. The intensities, the number of X-ray counts, are computed by suming the spectrum over the different X-ray lines. The sum window width is calculated from the energy resolution of the detector as defined in 'energy_resolution_MnKa' of the metadata. Backgrounds average in provided windows can be subtracted from the intensities. Parameters ---------- xray_lines: {None, "best", list of string} If None, if `metadata.Sample.elements.xray_lines` contains a list of lines use those. If `metadata.Sample.elements.xray_lines` is undefined or empty but `metadata.Sample.elements` is defined, use the same syntax as `add_line` to select a subset of lines for the operation. Alternatively, provide an iterable containing a list of valid X-ray lines symbols. integration_windows: Float or array If float, the width of the integration windows is the 'integration_windows_width' times the calculated FWHM of the line. Else provide an array for which each row corresponds to a X-ray line. Each row contains the left and right value of the window. background_windows: None or 2D array of float If None, no background subtraction. Else, the backgrounds average in the windows are subtracted from the return intensities. 'background_windows' provides the position of the windows in energy. Each line corresponds to a X-ray line. In a line, the two first values correspond to the limits of the left window and the two last values correspond to the limits of the right window. plot_result : bool If True, plot the calculated line intensities. If the current object is a single spectrum it prints the result instead. only_one : bool If False, use all the lines of each element in the data spectral range. If True use only the line at the highest energy above an overvoltage of 2 (< beam energy / 2). only_lines : {None, list of strings} If not None, use only the given lines. kwargs The extra keyword arguments for plotting. See `utils.plot.plot_signals` Returns ------- intensities : list A list containing the intensities as BaseSignal subclasses. Examples -------- >>> s = hs.datasets.example_signals.EDS_SEM_Spectrum() >>> s.get_lines_intensity(['Mn_Ka'], plot_result=True) Mn_La at 0.63316 keV : Intensity = 96700.00 >>> s = hs.datasets.example_signals.EDS_SEM_Spectrum() >>> s.plot(['Mn_Ka'], integration_windows=2.1) >>> s.get_lines_intensity(['Mn_Ka'], >>> integration_windows=2.1, plot_result=True) Mn_Ka at 5.8987 keV : Intensity = 53597.00 >>> s = hs.datasets.example_signals.EDS_SEM_Spectrum() >>> s.set_elements(['Mn']) >>> s.set_lines(['Mn_Ka']) >>> bw = s.estimate_background_windows() >>> s.plot(background_windows=bw) >>> s.get_lines_intensity(background_windows=bw, plot_result=True) Mn_Ka at 5.8987 keV : Intensity = 46716.00 See also -------- set_elements, add_elements, estimate_background_windows, plot """ only_lines = utils_eds._parse_only_lines(only_lines) xray_lines = self._get_xray_lines(xray_lines, only_one=only_one, only_lines=only_lines) xray_lines, xray_not_here = self._get_xray_lines_in_spectral_range( xray_lines) for xray in xray_not_here: warnings.warn("%s is not in the data energy range." % xray + "You can remove it with" + "s.metadata.Sample.xray_lines.remove('%s')" % xray) if hasattr(integration_windows, '__iter__') is False: integration_windows = self.estimate_integration_windows( windows_width=integration_windows, xray_lines=xray_lines) intensities = [] ax = self.axes_manager.signal_axes[0] # test Signal1D (0D problem) # signal_to_index = self.axes_manager.navigation_dimension - 2 for i, (Xray_line, window) in enumerate(zip(xray_lines, integration_windows)): line_energy, line_FWHM = self._get_line_energy(Xray_line, FWHM_MnKa='auto') element, line = utils_eds._get_element_and_line(Xray_line) img = self.isig[window[0]:window[1]].integrate1D(-1) if np.issubdtype(img.data.dtype, np.integer): # The operations below require a float dtype with the default # numpy casting rule ('same_kind') img.change_dtype("float") if background_windows is not None: bw = background_windows[i] # TODO: test to prevent slicing bug. To be reomved when fixed indexes = [ float(ax.value2index(de)) for de in list(bw) + window ] if indexes[0] == indexes[1]: bck1 = self.isig[bw[0]] else: bck1 = self.isig[bw[0]:bw[1]].integrate1D(-1) if indexes[2] == indexes[3]: bck2 = self.isig[bw[2]] else: bck2 = self.isig[bw[2]:bw[3]].integrate1D(-1) corr_factor = (indexes[5] - indexes[4]) / ( (indexes[1] - indexes[0]) + (indexes[3] - indexes[2])) img = img - (bck1 + bck2) * corr_factor img.metadata.General.title = ( 'X-ray line intensity of %s: %s at %.2f %s' % ( self.metadata.General.title, Xray_line, line_energy, self.axes_manager.signal_axes[0].units, )) img.axes_manager.set_signal_dimension(0) if plot_result and img.axes_manager.navigation_size == 1: print("%s at %s %s : Intensity = %.2f" % (Xray_line, line_energy, ax.units, img.data)) img.metadata.set_item("Sample.elements", ([element])) img.metadata.set_item("Sample.xray_lines", ([Xray_line])) intensities.append(img) if plot_result and img.axes_manager.navigation_size != 1: utils.plot.plot_signals(intensities, **kwargs) return intensities
def quantification(self, intensities, method, factors='auto', composition_units='atomic', navigation_mask=1.0, closing=True, plot_result=False, **kwargs): """ Quantification using Cliff-Lorimer, the zeta-factor method, or ionization cross sections. Parameters ---------- intensities: list of signal the intensitiy for each X-ray lines. method: 'CL' or 'zeta' or 'cross_section' Set the quantification method: Cliff-Lorimer, zeta-factor, or ionization cross sections. factors: list of float The list of kfactors, zeta-factors or cross sections in same order as intensities. Note that intensities provided by Hyperspy are sorted by the alphabetical order of the X-ray lines. eg. factors =[0.982, 1.32, 1.60] for ['Al_Ka', 'Cr_Ka', 'Ni_Ka']. composition_units: 'weight' or 'atomic' The quantification returns the composition in atomic percent by default, but can also return weight percent if specified. navigation_mask : None or float or signal The navigation locations marked as True are not used in the quantification. If int is given the vacuum_mask method is used to generate a mask with the int value as threhsold. Else provides a signal with the navigation shape. closing: bool If true, applied a morphologic closing to the mask obtained by vacuum_mask. plot_result : bool If True, plot the calculated composition. If the current object is a single spectrum it prints the result instead. kwargs The extra keyword arguments are passed to plot. Returns ------ A list of quantified elemental maps (signal) giving the composition of the sample in weight or atomic percent. If the method is 'zeta' this function also returns the mass thickness profile for the data. If the method is 'cross_section' this function also returns the atom counts for each element. Examples -------- >>> s = hs.datasets.example_signals.EDS_TEM_Spectrum() >>> s.add_lines() >>> kfactors = [1.450226, 5.075602] #For Fe Ka and Pt La >>> bw = s.estimate_background_windows(line_width=[5.0, 2.0]) >>> s.plot(background_windows=bw) >>> intensities = s.get_lines_intensity(background_windows=bw) >>> res = s.quantification(intensities, kfactors, plot_result=True, >>> composition_units='atomic') Fe (Fe_Ka): Composition = 15.41 atomic percent Pt (Pt_La): Composition = 84.59 atomic percent See also -------- vacuum_mask """ if isinstance(navigation_mask, float): navigation_mask = self.vacuum_mask(navigation_mask, closing).data elif navigation_mask is not None: navigation_mask = navigation_mask.data xray_lines = self.metadata.Sample.xray_lines composition = utils.stack(intensities) if method == 'CL': composition.data = utils_eds.quantification_cliff_lorimer( composition.data, kfactors=factors, mask=navigation_mask) * 100. elif method == 'zeta': results = utils_eds.quantification_zeta_factor( composition.data, zfactors=factors, dose=self._get_dose(method)) composition.data = results[0] * 100. mass_thickness = intensities[0].deepcopy() mass_thickness.data = results[1] mass_thickness.metadata.General.title = 'Mass thickness' elif method == 'cross_section': results = utils_eds.quantification_cross_section(composition.data, cross_sections=factors, dose=self._get_dose(method)) composition.data = results[0] * 100 number_of_atoms = utils.stack(intensities) number_of_atoms.data = results[1] number_of_atoms = number_of_atoms.split() else: raise ValueError ('Please specify method for quantification, as \'CL\', \'zeta\' or \'cross_section\'') composition = composition.split() if composition_units == 'atomic': if method != 'cross_section': composition = utils.material.weight_to_atomic(composition) else: if method == 'cross_section': composition = utils.material.atomic_to_weight(composition) for i, xray_line in enumerate(xray_lines): element, line = utils_eds._get_element_and_line(xray_line) composition[i].metadata.General.title = composition_units + \ ' percent of ' + element composition[i].metadata.set_item("Sample.elements", ([element])) composition[i].metadata.set_item( "Sample.xray_lines", ([xray_line])) if plot_result and \ composition[i].axes_manager.signal_dimension == 0: print("%s (%s): Composition = %.2f %s percent" % (element, xray_line, composition[i].data, composition_units)) if method=='cross_section': for i, xray_line in enumerate(xray_lines): element, line = utils_eds._get_element_and_line(xray_line) number_of_atoms[i].metadata.General.title = 'atom counts of ' +\ element number_of_atoms[i].metadata.set_item("Sample.elements", ([element])) number_of_atoms[i].metadata.set_item( "Sample.xray_lines", ([xray_line])) if plot_result and composition[i].axes_manager.signal_dimension != 0: utils.plot.plot_signals(composition, **kwargs) if method=='zeta': self.metadata.set_item("Sample.mass_thickness", mass_thickness) return composition, mass_thickness elif method == 'cross_section': return composition, number_of_atoms elif method == 'CL': return composition else: raise ValueError ('Please specify method for quantification, as \'CL\', \'zeta\' or \'cross_section\'')
def quantification(self, intensities, method, factors, composition_units='atomic', navigation_mask=1.0, closing=True, plot_result=False, **kwargs): """ Quantification using Cliff-Lorimer, the zeta-factor method, or ionization cross sections. Parameters ---------- intensities: list of signal the intensitiy for each X-ray lines. method: 'CL' or 'zeta' or 'cross_section' Set the quantification method: Cliff-Lorimer, zeta-factor, or ionization cross sections. factors: list of float The list of kfactors, zeta-factors or cross sections in same order as intensities. Note that intensities provided by Hyperspy are sorted by the alphabetical order of the X-ray lines. eg. factors =[0.982, 1.32, 1.60] for ['Al_Ka', 'Cr_Ka', 'Ni_Ka']. composition_units: 'weight' or 'atomic' The quantification returns the composition in atomic percent by default, but can also return weight percent if specified. navigation_mask : None or float or signal The navigation locations marked as True are not used in the quantification. If int is given the vacuum_mask method is used to generate a mask with the int value as threhsold. Else provides a signal with the navigation shape. closing: bool If true, applied a morphologic closing to the mask obtained by vacuum_mask. plot_result : bool If True, plot the calculated composition. If the current object is a single spectrum it prints the result instead. kwargs The extra keyword arguments are passed to plot. Returns ------ A list of quantified elemental maps (signal) giving the composition of the sample in weight or atomic percent. If the method is 'zeta' this function also returns the mass thickness profile for the data. If the method is 'cross_section' this function also returns the atom counts for each element. Examples -------- >>> s = hs.datasets.example_signals.EDS_TEM_Spectrum() >>> s.add_lines() >>> kfactors = [1.450226, 5.075602] #For Fe Ka and Pt La >>> bw = s.estimate_background_windows(line_width=[5.0, 2.0]) >>> s.plot(background_windows=bw) >>> intensities = s.get_lines_intensity(background_windows=bw) >>> res = s.quantification(intensities, kfactors, plot_result=True, >>> composition_units='atomic') Fe (Fe_Ka): Composition = 15.41 atomic percent Pt (Pt_La): Composition = 84.59 atomic percent See also -------- vacuum_mask """ if self.axes_manager.navigation_size == 0: navigation_mask = None elif isinstance(navigation_mask, float): navigation_mask = self.vacuum_mask(navigation_mask, closing).data elif navigation_mask is not None: navigation_mask = navigation_mask.data composition = utils.stack(intensities, lazy=False) if method == 'CL': composition.data = utils_eds.quantification_cliff_lorimer( composition.data, kfactors=factors, mask=navigation_mask) * 100. elif method == 'zeta': results = utils_eds.quantification_zeta_factor(composition.data, zfactors=factors, dose=self._get_dose( method, **kwargs)) composition.data = results[0] * 100. mass_thickness = intensities[0].deepcopy() mass_thickness.data = results[1] mass_thickness.metadata.General.title = 'Mass thickness' elif method == 'cross_section': results = utils_eds.quantification_cross_section( composition.data, cross_sections=factors, dose=self._get_dose(method, **kwargs)) composition.data = results[0] * 100 number_of_atoms = composition._deepcopy_with_new_data(results[1]) number_of_atoms = number_of_atoms.split() else: raise ValueError("Please specify method for quantification, " "as 'CL', 'zeta' or 'cross_section'.") composition = composition.split() if composition_units == 'atomic': if method != 'cross_section': composition = utils.material.weight_to_atomic(composition) else: if method == 'cross_section': composition = utils.material.atomic_to_weight(composition) for i, intensity in enumerate(intensities): xray_line = intensity.metadata.Sample.xray_lines[0] element, line = utils_eds._get_element_and_line(xray_line) composition[i].metadata.General.title = composition_units + \ ' percent of ' + element composition[i].metadata.set_item("Sample.elements", ([element])) composition[i].metadata.set_item("Sample.xray_lines", ([xray_line])) if plot_result and \ composition[i].axes_manager.navigation_size == 1: print("%s (%s): Composition = %.2f %s percent" % (element, xray_line, composition[i].data, composition_units)) if method == 'cross_section': number_of_atoms[i].metadata.General.title = \ 'atom counts of ' + element number_of_atoms[i].metadata.set_item("Sample.elements", ([element])) number_of_atoms[i].metadata.set_item("Sample.xray_lines", ([xray_line])) if plot_result and composition[i].axes_manager.navigation_size != 1: utils.plot.plot_signals(composition, **kwargs) if method == 'zeta': self.metadata.set_item("Sample.mass_thickness", mass_thickness) return composition, mass_thickness elif method == 'cross_section': return composition, number_of_atoms elif method == 'CL': return composition else: raise ValueError("Please specify method for quantification, as " "'CL', 'zeta' or 'cross_section'.")