Skip to content

Commit

Permalink
Merge pull request #797 from dnjohnstone/NEW_absorption_coeff
Browse files Browse the repository at this point in the history
X-ray Absorption Coefficient Database
  • Loading branch information
francisco-dlp committed Jun 17, 2016
2 parents 1ca0073 + 0b17ee0 commit 3f18359
Show file tree
Hide file tree
Showing 6 changed files with 472 additions and 33 deletions.
2 changes: 2 additions & 0 deletions doc/user_guide/bibliography.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Bibliography
"Three-dimensional chemical analysis of laser-welded NiTi–stainless steel wires
using a dual-beam FIB", Acta Materialia 61 (2013) 3090–3098.
.. [Chantler2015] http://physics.nist.gov/ffast
.. [Egerton2011] Ray Egerton, "Electron Energy-Loss Spectroscopy in the
Electron Microscope", Springer-Verlag, 2011.
Expand Down
18 changes: 18 additions & 0 deletions doc/user_guide/eds.rst
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,24 @@ database:
The lines are returned in order of distance from the specified energy, and can
be limited by additional, optional arguments.

Mass absorption coefficient database
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

A mass absorption coefficient database [Chantler2005]_ is available:

.. code-block:: python
>>> hs.material.mass_absorption_coefficient(
>>> element='Al', energies=['C_Ka','Al_Ka'])
array([ 26330.38933818, 372.02616732])
.. code-block:: python
>>> hs.material.mass_absorption_mixture(
>>> elements=['Al','Zn'], weight_percent=[50,50], energies='Al_Ka')
2587.4161643905127
.. _eds_plot-label:

Plotting
--------
Expand Down
191 changes: 191 additions & 0 deletions hyperspy/misc/eds/ffast_mac.py

Large diffs are not rendered by default.

229 changes: 214 additions & 15 deletions hyperspy/misc/material.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import numpy as np
import numbers
import warnings
import copy

from hyperspy.misc.elements import elements as elements_db
from hyperspy.misc.eds.ffast_mac import ffast_mac_db as ffast_mac
from hyperspy.misc.eds import utils as utils_eds
from hyperspy.misc.utils import stack


Expand Down Expand Up @@ -154,9 +156,9 @@ def atomic_to_weight(atomic_percent, elements='auto'):
return _atomic_to_weight(atomic_percent, elements)


def _density_of_mixture_of_pure_elements(weight_percent,
elements,
mean='harmonic'):
def _density_of_mixture(weight_percent,
elements,
mean='harmonic'):
"""Calculate the density a mixture of elements.
The density of the elements is retrieved from an internal database. The
Expand All @@ -181,8 +183,7 @@ def _density_of_mixture_of_pure_elements(weight_percent,
Examples
--------
Calculate the density of modern bronze given its weight percent:
>>> hs.material.density_of_mixture_of_pure_elements(
(88, 12),("Cu", "Sn"))
>>> hs.material.density_of_mixture([88, 12],['Cu', 'Sn'])
8.6903187973131466
"""
Expand All @@ -209,9 +210,9 @@ def _density_of_mixture_of_pure_elements(weight_percent,
return np.where(sum_weight == 0.0, 0.0, density)


def density_of_mixture_of_pure_elements(weight_percent,
elements='auto',
mean='harmonic'):
def density_of_mixture(weight_percent,
elements='auto',
mean='harmonic'):
"""Calculate the density of a mixture of elements.
The density of the elements is retrieved from an internal database. The
Expand All @@ -238,21 +239,219 @@ def density_of_mixture_of_pure_elements(weight_percent,
Examples
--------
Calculate the density of modern bronze given its weight percent:
>>> hs.material.density_of_mixture_of_pure_elements(
(88, 12),("Cu", "Sn"))
>>> hs.material.density_of_mixture([88, 12],['Cu', 'Sn'])
8.6903187973131466
"""
from hyperspy.signals import BaseSignal
elements = _elements_auto(weight_percent, elements)
if isinstance(weight_percent[0], BaseSignal):
density = weight_percent[0]._deepcopy_with_new_data(
_density_of_mixture_of_pure_elements(
stack(weight_percent).data, elements, mean=mean))
_density_of_mixture(stack(weight_percent).data,
elements, mean=mean))
return density
else:
return _density_of_mixture_of_pure_elements(weight_percent,
elements, mean=mean)
return _density_of_mixture(weight_percent, elements, mean=mean)


def mass_absorption_coefficient(element, energies):
"""
Mass absorption coefficient (mu/rho) of a X-ray absorbed in a pure
material.
The mass absorption is retrieved from the database of Chantler2005
Parameters
----------
element: str
The element symbol of the absorber, e.g. 'Al'.
energies: float or list of float or str or list of str
The energy or energies of the X-ray in keV, or the name of the X-rays,
e.g. 'Al_Ka'.
Return
------
mass absorption coefficient(s) in cm^2/g
Examples
--------
>>> hs.material.mass_absorption_coefficient(
>>> element='Al', energies=['C_Ka','Al_Ka'])
array([ 26330.38933818, 372.02616732])
See also
--------
hs.material.mass_absorption_mixture
Note
----
See http://physics.nist.gov/ffast
Chantler, C.T., Olsen, K., Dragoset, R.A., Kishore, A.R., Kotochigova,
S.A., and Zucker, D.S. (2005), X-Ray Form Factor, Attenuation and
Scattering Tables (version 2.1).
"""
energies_db = np.array(ffast_mac[element].energies_keV)
macs = np.array(ffast_mac[element].mass_absorption_coefficient_cm2g)
energies = copy.copy(energies)
if isinstance(energies, str):
energies = utils_eds._get_energy_xray_line(energies)
elif hasattr(energies, '__iter__'):
for i, energy in enumerate(energies):
if isinstance(energy, str):
energies[i] = utils_eds._get_energy_xray_line(energy)
index = np.searchsorted(energies_db, energies)
mac_res = np.exp(np.log(macs[index - 1]) +
np.log(macs[index] / macs[index - 1]) *
(np.log(energies / energies_db[index - 1]) /
np.log(energies_db[index] / energies_db[index - 1])))
return np.nan_to_num(mac_res)


def _mass_absorption_mixture(weight_percent,
elements,
energies):
"""Calculate the mass absorption coefficient for X-ray absorbed in a
mixture of elements.
The mass absorption coefficient is calculated as a weighted mean of the
weight percent and is retrieved from the database of Chantler2005.
Parameters
----------
weight_percent: np.array
The composition of the absorber(s) in weight percent. The first
dimension of the matrix corresponds to the elements.
elements: list of str
The list of element symbol of the absorber, e.g. ['Al','Zn'].
energies: float or list of float or str or list of str
The energy or energies of the X-ray in keV, or the name of the X-rays,
e.g. 'Al_Ka'.
Examples
--------
>>> hs.material.mass_absorption_mixture(
>>> elements=['Al','Zn'], weight_percent=[50,50], energies='Al_Ka')
2587.4161643905127
Return
------
float or array of float
mass absorption coefficient(s) in cm^2/g
See also
--------
hs.material.mass_absorption
Note
----
See http://physics.nist.gov/ffast
Chantler, C.T., Olsen, K., Dragoset, R.A., Kishore, A.R., Kotochigova,
S.A., and Zucker, D.S. (2005), X-Ray Form Factor, Attenuation and
Scattering Tables (version 2.1).
"""
if len(elements) != len(weight_percent):
raise ValueError(
"Elements and weight_fraction should have the same length")
if hasattr(weight_percent[0], '__iter__'):
weight_fraction = np.array(weight_percent)
weight_fraction /= np.sum(weight_fraction, 0)
mac_res = np.zeros([len(energies)]+list(weight_fraction.shape[1:]))
for element, weight in zip(elements, weight_fraction):
mac_re = mass_absorption_coefficient(element, energies)
mac_res += np.array([weight * ma for ma in mac_re])
return mac_res
else:
mac_res = np.array([mass_absorption_coefficient(
el, energies) for el in elements])
mac_res = np.dot(weight_percent, mac_res) / np.sum(weight_percent, 0)
return mac_res


def mass_absorption_mixture(weight_percent,
elements='auto',
energies='auto'):
"""Calculate the mass absorption coefficient for X-ray absorbed in a
mixture of elements.
The mass absorption coefficient is calculated as a weighted mean of the
weight percent and is retrieved from the database of Chantler2005.
Parameters
----------
weight_percent: list of float or list of signals
The composition of the absorber(s) in weight percent. The first
dimension of the matrix corresponds to the elements.
elements: list of str or 'auto'
The list of element symbol of the absorber, e.g. ['Al','Zn']. If
elements is 'auto', take the elements in each signal metadata of the
weight_percent list.
energies: list of float or list of str or 'auto'
The energy or energies of the X-ray in keV, or the name of the X-rays,
e.g. 'Al_Ka'. If 'auto', take the lines in each signal metadata of the
weight_percent list.
Examples
--------
>>> hs.material.mass_absorption_mixture(
>>> elements=['Al','Zn'], weight_percent=[50,50], energies='Al_Ka')
2587.41616439
Return
------
float or array of float
mass absorption coefficient(s) in cm^2/g
See also
--------
hs.material.mass_absorption_coefficient
Note
----
See http://physics.nist.gov/ffast
Chantler, C.T., Olsen, K., Dragoset, R.A., Kishore, A.R., Kotochigova,
S.A., and Zucker, D.S. (2005), X-Ray Form Factor, Attenuation and
Scattering Tables (version 2.1).
"""
from hyperspy.signals import BaseSignal
elements = _elements_auto(weight_percent, elements)
energies = _lines_auto(weight_percent, energies)
if isinstance(weight_percent[0], BaseSignal):
weight_per = np.array([wt.data for wt in weight_percent])
mac_res = stack([weight_percent[0].deepcopy()]*len(energies))
mac_res.data = \
_mass_absorption_mixture(weight_per, elements, energies)
mac_res = mac_res.split()
for i, energy in enumerate(energies):
mac_res[i].metadata.set_item("Sample.xray_lines", ([energy]))
mac_res[i].metadata.General.set_item(
"title", "Absoprtion coeff of"
" %s in %s" % (energy, mac_res[i].metadata.General.title))
if mac_res[i].metadata.has_item("Sample.elements"):
del mac_res[i].metadata.Sample.elements
return mac_res
else:
return _mass_absorption_mixture(weight_percent, elements, energies)


def _lines_auto(composition, xray_lines):
if isinstance(composition[0], numbers.Number):
if isinstance(xray_lines, str):
if xray_lines == 'auto':
raise ValueError("The X-ray lines needs to be provided.")
else:
if isinstance(xray_lines, str):
if xray_lines == 'auto':
xray_lines = []
for compo in composition:
if len(compo.metadata.Sample.xray_lines) > 1:
raise ValueError(
"The signal %s contains more than one X-ray lines "
"but this function requires only one X-ray lines "
"per signal." % compo.metadata.General.title)
else:
xray_lines.append(compo.metadata.Sample.xray_lines[0])
return xray_lines


def _elements_auto(composition, elements):
Expand Down

0 comments on commit 3f18359

Please sign in to comment.