Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vacuum mask eels #2183

Merged
merged 16 commits into from Sep 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
68 changes: 31 additions & 37 deletions hyperspy/_signals/eds_tem.py
Expand Up @@ -291,16 +291,16 @@ 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,
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,
**kwargs):
"""
Absorption corrected quantification using Cliff-Lorimer, the zeta-factor
Expand Down Expand Up @@ -342,7 +342,8 @@ def quantification(self,
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.
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.
Expand Down Expand Up @@ -389,9 +390,7 @@ def quantification(self,
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
navigation_mask = self.vacuum_mask(navigation_mask, closing)

xray_lines = [intensity.metadata.Sample.xray_lines[0] for intensity in intensities]
it = 0
Expand Down Expand Up @@ -421,32 +420,30 @@ def quantification(self,

abs_corr_factor = None # initial

quant_kwargs = {"intensities" : int_stack.data,
"absorption_correction" : abs_corr_factor}

if method == 'CL':
quantification_method = utils_eds.quantification_cliff_lorimer
kwargs = {"intensities" : int_stack.data,
"kfactors" : factors,
"absorption_correction" : abs_corr_factor}
quant_kwargs.update({"kfactors" : factors,
"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}
quant_kwargs.update({"zfactors" : factors,
"dose" : self._get_dose(method)})

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}
quant_kwargs.update({"cross_sections" : factors,
"dose" : self._get_dose(method)})

else:
raise ValueError('Please specify method for quantification, '
'as \'CL\', \'zeta\' or \'cross_section\'.')
'as "CL", "zeta" or "cross_section".')

while True:
results = quantification_method(**kwargs)
results = quantification_method(**quant_kwargs)

if method == 'CL':
composition.data = results * 100.
Expand Down Expand Up @@ -477,13 +474,13 @@ def quantification(self,
number_of_atoms.split(),
toa,
probe_area)
kwargs["absorption_correction"] = abs_corr_factor
quant_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
quant_kwargs["absorption_correction"] = abs_corr_factor

res_max = np.max((composition - comp_old).data)
comp_old.data = composition.data
Expand Down Expand Up @@ -557,9 +554,6 @@ def quantification(self,
return composition, mass_thickness
else:
return composition
else:
raise ValueError('Please specify method for quantification, as \
''CL\', \'zeta\' or \'cross_section\'')


def vacuum_mask(self, threshold=1.0, closing=True, opening=False):
Expand All @@ -576,6 +570,11 @@ def vacuum_mask(self, threshold=1.0, closing=True, opening=False):
opnening: bool
If true, applied a morphologic opening to the mask

Returns
-------
mask: signal
The mask of the region

Examples
--------
>>> # Simulate a spectrum image with vacuum region
Expand All @@ -586,11 +585,6 @@ def vacuum_mask(self, threshold=1.0, closing=True, opening=False):
>>> si = hs.stack([s]*3 + [s_vac])
>>> si.vacuum_mask().data
array([False, False, False, True], dtype=bool)

Return
------
mask: signal
The mask of the region
"""
from scipy.ndimage.morphology import binary_dilation, binary_erosion
mask = (self.max(-1) <= threshold)
Expand Down
113 changes: 78 additions & 35 deletions hyperspy/_signals/eels.py
Expand Up @@ -24,8 +24,9 @@
import traits.api as t
from scipy import constants
from prettytable import PrettyTable
import pint

from hyperspy.signal import BaseSetMetadataItems
from hyperspy.signal import BaseSetMetadataItems, BaseSignal
from hyperspy._signals.signal1d import (Signal1D, LazySignal1D)
from hyperspy.signal_tools import EdgesRange
from hyperspy.misc.elements import elements as elements_db
Expand All @@ -48,6 +49,7 @@


_logger = logging.getLogger(__name__)
_ureg = pint.UnitRegistry()


@add_gui_method(toolkey="hyperspy.microscope_parameters_EELS")
Expand Down Expand Up @@ -272,10 +274,10 @@ def estimate_zero_loss_peak_centre(self, mask=None):

Parameters
----------
mask : Signal1D of bool data type.
mask : Signal1D of bool data type or bool array
It must have signal_dimension = 0 and navigation_shape equal to the
current signal. Where mask is True the shift is not computed
and set to nan.
navigation shape of the current signal. Where mask is True the
shift is not computed and set to nan.

Returns
-------
Expand All @@ -296,12 +298,14 @@ def estimate_zero_loss_peak_centre(self, mask=None):
"""
self._check_signal_dimension_equals_one()
self._check_navigation_mask(mask)
if isinstance(mask, BaseSignal):
mask = mask.data
zlpc = self.valuemax(-1)
if mask is not None:
if zlpc._lazy:
zlpc.data = da.where(mask.data, np.nan, zlpc.data)
zlpc.data = da.where(mask, np.nan, zlpc.data)
else:
zlpc.data[mask.data] = np.nan
zlpc.data[mask] = np.nan
zlpc.set_signal_type("")
title = self.metadata.General.title
zlpc.metadata.General.title = "ZLP(%s)" % title
Expand Down Expand Up @@ -341,10 +345,10 @@ def align_zero_loss_peak(
subpixel : bool
If True, perform the alignment with subpixel accuracy
using cross-correlation.
mask : Signal1D of bool data type.
It must have signal_dimension = 0 and navigation_shape equal to the
current signal. Where mask is True the shift is not computed
and set to nan.
mask : Signal1D of bool data type or bool array.
It must have signal_dimension = 0 and navigation_shape equal to
the shape of the current signal. Where mask is True the shift is
not computed and set to nan.
signal_range : tuple of integers, tuple of floats. Optional
Will only search for the ZLP within the signal_range. If given
in integers, the range will be in index values. If given floats,
Expand Down Expand Up @@ -1003,21 +1007,21 @@ def fourier_ratio_deconvolution(self, ll,
'after_fourier_ratio_deconvolution')
return cl

def richardson_lucy_deconvolution(self, psf, iterations=15, mask=None,
def richardson_lucy_deconvolution(self, psf, iterations=15,
ericpre marked this conversation as resolved.
Show resolved Hide resolved
show_progressbar=None,
parallel=None, max_workers=None):
"""1D Richardson-Lucy Poissonian deconvolution of
the spectrum by the given kernel.

Parameters
----------
iterations: int
Number of iterations of the deconvolution. Note that
increasing the value will increase the noise amplification.
psf: EELSSpectrum
psf : EELSSpectrum
It must have the same signal dimension as the current
spectrum and a spatial dimension of 0 or the same as the
current spectrum.
iterations : int
Number of iterations of the deconvolution. Note that
increasing the value will increase the noise amplification.
%s
%s
%s
Expand All @@ -1034,8 +1038,6 @@ def richardson_lucy_deconvolution(self, psf, iterations=15, mask=None,
show_progressbar = preferences.General.show_progressbar
self._check_signal_dimension_equals_one()
psf_size = psf.axes_manager.signal_axes[0].size
kernel = psf()
imax = kernel.argmax()
maxval = self.axes_manager.navigation_size
show_progressbar = show_progressbar and (maxval > 0)

Expand All @@ -1049,6 +1051,7 @@ def deconv_function(signal, kernel=None,
result *= np.convolve(kernel[::-1], signal /
first)[mimax:mimax + psf_size]
return result

ds = self.map(deconv_function, kernel=psf, iterations=iterations,
psf_size=psf_size, show_progressbar=show_progressbar,
parallel=parallel, max_workers=max_workers,
Expand Down Expand Up @@ -1108,29 +1111,30 @@ def set_microscope_parameters(self,
"Acquisition_instrument.TEM.Detector.EELS.collection_angle",
collection_angle)
set_microscope_parameters.__doc__ = \
"""
Set the microscope parameters that are necessary to calculate
the GOS.

If not all of them are defined, in interactive mode
raises an UI item to fill the values

beam_energy: float
The energy of the electron beam in keV
convengence_angle : float
The microscope convergence semi-angle in mrad.
collection_angle : float
The collection semi-angle in mrad.
{}
{}
""".format(TOOLKIT_DT, DISPLAY_DT)
"""
Set the microscope parameters that are necessary to calculate
the GOS. If not all of them are defined, in interactive mode
raises an UI item to fill the values.

Parameters
----------

beam_energy: float
The energy of the electron beam in keV.
convengence_angle : float
The microscope convergence semi-angle in mrad.
collection_angle : float
The collection semi-angle in mrad.
{}
{}
""".format(TOOLKIT_DT, DISPLAY_DT)

def power_law_extrapolation(self,
window_size=20,
extrapolation_size=1024,
add_noise=False,
fix_neg_r=False):
"""Extrapolate the spectrum to the right using a powerlaw
"""Extrapolate the spectrum to the right using a powerlaw.


Parameters
Expand Down Expand Up @@ -1609,7 +1613,7 @@ def plot_edges_label(self, edges, vertical_line_marker=None,
vertical_line_marker, text_marker = slp.get_markers(edges)
# the object is needed to connect replot method when axes_manager
# indices changed
er = EdgesRange(self, active=list(edges.keys()))
_ = EdgesRange(self, active=list(edges.keys()))
if len(vertical_line_marker) != len(text_marker) or \
len(edges) != len(vertical_line_marker):
raise ValueError('The size of edges, vertical_line_marker and '
Expand Down Expand Up @@ -1788,6 +1792,45 @@ def rebin(self, new_shape=None, scale=None, crop=True, out=None):
return m
rebin.__doc__ = hyperspy.signal.BaseSignal.rebin.__doc__

def vacuum_mask(self, threshold=10.0, start_energy=None,
closing=True, opening=False):
"""
Generate mask of the vacuum region

Parameters
----------
threshold: float
For a given navigation coordinate, mean value in the energy axis
below which the pixel is considered as vacuum.
start_energy: float, None
Minimum energy included in the calculation of the mean intensity.
If None, consider only the last quarter of the spectrum to
calculate the mask.
closing: bool
If True, a morphological closing is applied to the mask.
opening: bool
If True, a morphological opening is applied to the mask.

Returns
-------
mask: signal
The mask of the region.
"""
signal_axis = self.axes_manager.signal_axes[0]
if start_energy is None:
start_energy = 0.75 * signal_axis.high_value

mask = (self.isig[start_energy:].mean(-1) <= threshold)

from scipy.ndimage.morphology import binary_dilation, binary_erosion
if closing:
mask.data = binary_dilation(mask.data, border_value=0)
mask.data = binary_erosion(mask.data, border_value=1)
if opening:
mask.data = binary_erosion(mask.data, border_value=1)
mask.data = binary_dilation(mask.data, border_value=0)
return mask


class EELSSpectrum(EELSSpectrum_mixin, Signal1D):

Expand Down
5 changes: 1 addition & 4 deletions hyperspy/_signals/signal1d.py
Expand Up @@ -562,10 +562,7 @@ def _check_navigation_mask(self, mask):
elif mask.axes_manager.signal_dimension not in (0, 1):
raise ValueError("mask must be a BaseSignal "
"with signal_dimension equal to 1")
elif (mask.axes_manager.navigation_dimension !=
self.axes_manager.navigation_dimension):
raise ValueError("mask must be a BaseSignal with the same "
"navigation_dimension as the current signal.")
super()._check_navigation_mask(mask)

def estimate_shift1D(
self,
Expand Down
6 changes: 5 additions & 1 deletion hyperspy/learn/mva.py
Expand Up @@ -232,6 +232,8 @@ def decomposition(
* :py:meth:`~._signals.lazy.LazySignal.decomposition` for lazy signals

"""
from hyperspy.signal import BaseSignal

# Check data is suitable for decomposition
if self.data.dtype.char not in np.typecodes["AllFloat"]:
raise TypeError(
Expand Down Expand Up @@ -358,7 +360,9 @@ def decomposition(
self._unfolded4decomposition = self.unfold()
try:
_logger.info("Performing decomposition analysis")

if isinstance(navigation_mask, BaseSignal):
self._check_navigation_mask(mask=navigation_mask)
navigation_mask = navigation_mask.data
tjof2 marked this conversation as resolved.
Show resolved Hide resolved
if hasattr(navigation_mask, "ravel"):
navigation_mask = navigation_mask.ravel()

Expand Down