Hyperspy Tutorial

EELS analysis of perovskite oxides

This tutorial shows the various functionalities in HyperSpy which is used to analyse Electron Energy Loss Spectroscopy data, using EELS datasets from perovskite oxide heterostructure.

It assumes some knowledge on how to use HyperSpy, like loading datasets and how the basic signals work.

This notebook requires:

HyperSpy 1.1

Author

7/6/2016 Magnus Nord - Developed for HyperSpy workshop at Scandem conference 2016

Changes

  • 3/8/2016 Updated for HyperSpy 1.1. Added note about Gatan Digital Micrograph GOS.

Table of contents

This notebook was used for the HyperSpy workshop at the Norwegian University of Science and Technology for the Scandem 2016 conference, 7 June 2016.

The data was acquired on a Jeol ARM200cF using a Gatan Quantum ER with DualEELS capabilities.

The data itself is from La0.7Sr0.3MnO3 thin films deposited on SrTiO3. In the Fine Structure example parts of the film has been exposed to a very long electron beam exposure, inducing oxygen vacancies.

The datasets has been binned to reduce the file size and processing time.

Firstly we use some IPython magic to import the right plotting libraries, then import HyperSpy

In [1]:
%matplotlib nbagg
In [2]:
import hyperspy.api as hs
WARNING:hyperspy_gui_traitsui:The nbAgg matplotlib backend is not supported by the installed traitsui version and the ETS toolkit has been set to null. To set the ETS toolkit independently from the matplotlib backend, set it before importing matplotlib.
WARNING:hyperspy_gui_traitsui:The traitsui GUI elements are not available.

First we take a look at an EELS line scan across an La0.7Sr0.3MnO3/SrTiO3 thin film. The core loss data has several peaks: Ti-L23, O-K, Mn-L23 and La-M54. We can navigate the line scan using the navigation window, and by moving the red line.

In [110]:
s = hs.load("datasets/LSMO_STO_linescan.hdf5")
In [111]:
s.plot()

Now we can quantifiy the first edge (Ti-L23, 460 eV, 0-10 nm). Firstly by removing the background, then integrating the Ti-L23 edge. The remove_background box can disappear (known bug) when using the qt4 toolkit. If it does run the s.remove_background() command again of use ipywidgets GUI instead.

In [112]:
s.remove_background()

To integrate the Ti-L32 edge interactively we can use a region of interest:

In [20]:
roi = hs.roi.SpanROI(left=450, right=600)
s.plot()
roi.add_widget(s, axes=["Energy loss"])
Out[20]:
<hyperspy.drawing._widgets.range.RangeWidget at 0x7f905525a2e8>

Finally, to integrate the signal in the selected ROI:

In [22]:
s_ti = s.isig[roi].integrate1D(axis="Energy loss")
In [25]:
s_ti.plot()

Notice that we can also perform the same operations in one single line if interactivity is not required:

In [26]:
s = hs.load("datasets/LSMO_STO_linescan.hdf5")
In [27]:
s_ti = s.remove_background(signal_range=(405.,448.)).isig[448.:480.].integrate1D(axis="Energy loss")
In [28]:
s_ti.plot()

Now, lets do some more advanced quantification using HyperSpy's extensive modelling framework. Firstly we load the low loss and core loss spectra.

Firstly we'll have to tell HyperSpy where to find the Digital Micrograph Hartree-Slater cross section files, since they are not included in HyperSpy. Go to the "EELS" tab, then set "GOS directory" to the "H-S GOS Tables" folder. Note that unfortunately this requires a license of Gatan Digital Micrograph.

If you don't have Digital Micrograph you can still perform curve fitting of K and L edges using Ray Egerton's Hydrogenic cross-sections which are included in HyperSpy

In [29]:
hs.preferences.gui()
In [30]:
s_ll = hs.load("datasets/LSMO_STO_linescan_low_loss.hdf5")
In [31]:
s = hs.load("datasets/LSMO_STO_linescan.hdf5")

Here, the metadata has been populated with some of the experimental parameters:

In [32]:
s.metadata
Out[32]:
├── Acquisition_instrument
│   └── TEM
│       ├── Detector
│       │   └── EELS
│       │       └── collection_angle = 33.100000000000001
│       ├── beam_energy = 200.0
│       ├── convergence_angle = 27.100000000000001
│       └── dwell_time = 0.49990557338919173
├── General
│   ├── original_filename = LSMO_STO_linescan.dm3
│   └── title = EELS Spectrum Image (high-loss)
└── Signal
    ├── binned = True
    ├── signal_origin = 
    └── signal_type = EELS

Firstly, we make sure the energy scale is properly aligned by using the zero loss peak in the low loss spectrum. The subpixel argument interpolates the data, so we get sub-pixel alignment. Using the also_align argument, we can also apply the alignment on a another signal. For example when using dualEELS, where both the low loss and core loss is acquired quasi-simultaneously. Note the other signals must have the same navigation shape as the low loss signals.

In [33]:
s_ll.align_zero_loss_peak(subpixel=True, also_align=[s])
Initial ZLP position statistics
-------------------------------
Summary statistics
------------------
mean:	-1.000
std:	0.000

min:	-1.000
Q1:	-1.000
median:	-1.000
Q3:	-1.000
max:	-1.000

We have to add the elements which is present in the sample to s

In [34]:
s.add_elements(('Mn','O','Ti','La'))

Then we make a model out of the core loss spectrum. The low loss spectrum is convolved with the model, which means plural scattering is automatically taken into account. In addition this leads to better fits.

NOTE: creating this model requires using the GOS files from Gatan Digital Micrograph. If you don't have these files only K and L edges can be created using Hydrogenic cross sections. If you do have them but HyperSpy can't find them in the default location, you can specify the location using hs.preferences.gui().

In [35]:
m = s.create_model(ll=s_ll)

The model new consist of many different EELSCLEdge components, including a component for the plasmon background

In [36]:
m.components
Out[36]:
   # |      Attribute Name |      Component Name |      Component Type
---- | ------------------- | ------------------- | -------------------
   0 |            PowerLaw |            PowerLaw |            PowerLaw
   1 |               Ti_L3 |               Ti_L3 |          EELSCLEdge
   2 |               Ti_L2 |               Ti_L2 |          EELSCLEdge
   3 |               Ti_L1 |               Ti_L1 |          EELSCLEdge
   4 |                 O_K |                 O_K |          EELSCLEdge
   5 |               Mn_L3 |               Mn_L3 |          EELSCLEdge
   6 |               Mn_L2 |               Mn_L2 |          EELSCLEdge
   7 |               Mn_L1 |               Mn_L1 |          EELSCLEdge
   8 |               La_M5 |               La_M5 |          EELSCLEdge
   9 |               La_M4 |               La_M4 |          EELSCLEdge

We can fit the model to the experimental data by using the multifit function, with the smart fitting. Which is fits in a way optimized for EELS data, by fitting from the lowest to the highest energy losses.

In [37]:
m.multifit(kind='smart')
In [38]:
m.plot()

We can check the error of the fitting

In [39]:
edges = ("Ti_L3", "La_M5", "Mn_L3","O_K")
In [40]:
hs.plot.plot_spectra([m[edge].intensity.as_signal("std") for edge in edges], legend=edges)
Out[40]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f90554e7dd8>

This fitted mostly ok, but it is still not very good. Firstly we can move the Hartree-Slater onsets interactively

In [41]:
m.plot()
m.enable_adjust_position()

Or manually, by directly changing the parameters within the Hartree-Slater edges. The parameter is called onset_energy

In [42]:
m.components.O_K.onset_energy.value = 528

However, to change it for all the probe positions we have to use assign_current_value_to_all()

In [43]:
m.components.O_K.onset_energy.assign_current_value_to_all()

We repeat this for the Manganese edges. Since this is an L-edge, there are 3 different ones. However, we only have to set the Mn-L3: the L2 and L1 is a set to an energy relative to the L3.

In [44]:
m.components.Mn_L3.onset_energy.value
Out[44]:
640.0
In [45]:
m.components.Mn_L2.onset_energy.value
Out[45]:
651.0
In [46]:
m.components.Mn_L3.onset_energy.value = 638.5
In [47]:
m.components.Mn_L2.onset_energy.value 
Out[47]:
649.5
In [48]:
m.components.Mn_L3.onset_energy.assign_current_value_to_all()

This is due to the fine structure not currently taken into account by the model. To get a good fit, we can either not fit to the fine structure regions, or model them somehow. The easiest way is defining certain regions as fine structure:

In [49]:
m.enable_fine_structure()

This will produce a much better fit, but will be much slower (~2 minutes).

In [50]:
m.multifit(kind='smart')
/home/fjd29/Python/hyperspy3/hyperspy/model.py:1094: RuntimeWarning: invalid value encountered in sqrt
  self.p_std = np.sqrt(np.diag(pcov))

Now the fit is much better, due to the model taking into account the fine structure.

In [51]:
m.plot()

Now we can can have a look at the relative intensity from the individual EELS-edges using plot_spectra

In [52]:
edges = ("Ti_L3", "La_M5", "Mn_L3","O_K")
In [53]:
hs.plot.plot_spectra([m[edge].intensity.as_signal() for edge in edges], legend=edges)