Quantifying K412 using NeXLSpectrum and NeXLMatrixCorrection

This document demonstrates the low-level API for filter fitting spectra. It is more flexible than the higher- level API but much more complex. In most situations, the high level API discussed here is more appropriate.

Use the NeXLSpectrum to load, plot, fit and report the quantification of a set of K412 spectra.

Loading NeXLSpectrum also automatically makes NeXLCore and NeXLUncertainties available.

Loading the Gadfly library adds plotting support to NeXLSpectrum.

using NeXLSpectrum              # Provides spectrum reading and fitting tools
using NeXLMatrixCorrection      # Provides `quant` to convert k-ratios to mass fraction.
using Gadfly                    # Plotting
using DataFrames, Latexify      # Tables

Read in the Spectra

path = joinpath(@__DIR__,"K412 spectra")
# Load a single spectrum
fe = loadspectrum(joinpath(path, "Fe std.msa"))
# Create a detector model to match it
det = matching(fe, 132.0, 10)
# Now load all the spectra using this detector
unks = (i->loadspectrum(joinpath(path, "III-E K412[$i][4].msa"),det)).(0:4)
al2o3 = loadspectrum(joinpath(path, "Al2O3 std.msa"),det)
caf2 = loadspectrum(joinpath(path, "CaF2 std.msa"),det)
fe = loadspectrum(joinpath(path, "Fe std.msa"),det)
mgo = loadspectrum(joinpath(path, "MgO std.msa"),det)
sio2 = loadspectrum(joinpath(path, "SiO2 std.msa"),det)
# Add carbon coating
foreach(s->s[:Coating]=Film(pure(n"C"), 30.0e-7), unks)
foreach(s->s[:Coating]=Film(pure(n"C"), 10.0e-7), (al2o3, caf2, mgo, sio2));

Table: The spectra

NameBeamEnergyProbeCurrentLiveTimeRealTimeCoatingIntegralMaterial
III-E K412[0][all]2e+041.114235.5286.330.0 nm of Pure C8.079e+06K412
III-E K412[1][all]2e+041.114235.4286.230.0 nm of Pure C8.077e+06K412
III-E K412[2][all]2e+041.112235.5286.330.0 nm of Pure C8.084e+06K412
III-E K412[3][all]2e+041.11235.4286.330.0 nm of Pure C8.087e+06K412
III-E K412[4][all]2e+041.11235.4286.230.0 nm of Pure C8.081e+06K412
Al2O3 std2e+041.111172149110.0 nm of Pure C4.974e+07Al2O3
CaF2 std2e+041.111176145610.0 nm of Pure C4.406e+07CaF2
Fe std2e+041.1111711529nothing5.445e+07Fe
MgO std2e+041.1061176149610.0 nm of Pure C4.985e+07MgO
SiO2 std2e+041.111173147010.0 nm of Pure C4.665e+07SiO2

Notice that the spectra all have 1) live-time (:LiveTime); 2) probe-current (:ProbeCurrent); 3) take-off angle (:TakeOffAngle); 4) beam energy (:BeamEnergy); and detector (:Detector) properties defined. These properties are necessary for extracting the k-ratios and estimating the composition.

sio2[:LiveTime], sio2[:ProbeCurrent], sio2[:TakeOffAngle], sio2[:BeamEnergy], sio2[:Detector]
(1173.1648, 1.10989, 0.6108652381980153, 20000.0, BasicEDS[4096 chs, 1.6303
2 + 9.99856⋅ch eV, 132.0 eV @ Mn K-L3, 10 ch LLD, [Be,Sc,Ba,Pu]])

The Unknowns

display(plot(unks..., klms=[n"O",n"Mg",n"Al",n"Si",n"Ca",n"Fe"], xmax=8.0e3))

The Reference Spectra

Build a convenient structure so it is easy to appreciate the necessary information and to splat it into filteredReference.

refs = (
  # spectrum, element, composition
  ( al2o3, n"Al", mat"Al2O3" ), #
  ( mgo,   n"Mg", mat"MgO" ),   #
  ( fe,    n"Fe", mat"Fe" ),    #
  ( sio2,  n"Si", mat"SiO2" ),  #
  ( sio2,  n"O",  mat"SiO2" ),  #
  ( caf2,  n"Ca", mat"CaF2" ), )
display(plot(al2o3, caf2, fe, mgo, sio2, klms=collect( ref[2] for ref in refs), xmax=8.0e3))

Pre-filter the Reference Spectra

# Build a top-hat filter
filt = buildfilter(NeXLSpectrum.GaussianFilter,det)
# Filter all the reference spectra
frs = mapreduce(ref->filterreference(filt, ref..., withEsc=true), append!, refs)
# frs is now a FilteredReference[] used to fit the unknowns.
12-element Vector{FilteredReference{Float64}}:
 Reference[k[Al K-L3 + 3 others, Al2O3]]
 Reference[k[Mg K-L3 + 1 other, MgO]]
 Reference[k[Fe L3-M5 + 13 others, Fe]]
 Reference[k[Fe K-L3 + 1 other, Fe]]
 Reference[k[Fe K-M3 + 3 others, Fe]]
 Reference[Ecs[Fe K-L3 + 1 other]]
 Reference[Ecs[Fe K-M3 + 3 others]]
 Reference[k[Si K-L3 + 3 others, SiO2]]
 Reference[k[O K-L3 + 1 other, SiO2]]
 Reference[k[Ca K-L3 + 3 others, CaF2]]
 Reference[Ecs[Ca K-L3 + 1 other]]
 Reference[Ecs[Ca K-M3 + 1 other]]

Fit the Pre-Filtered References to the Unknowns

res= [ fit_spectrum(unk,filt,frs,false) for unk in unks ]
5-element Vector{FilterFitResult{Float64}}:
 FitResult(III-E K412[0][all])
 FitResult(III-E K412[1][all])
 FitResult(III-E K412[2][all])
 FitResult(III-E K412[3][all])
 FitResult(III-E K412[4][all])
Spectrak[O K-L3 + 1 other, SiO2]k[Fe L3-M5 + 13 others, Fe]k[Mg K-L3 + 1 other, MgO]k[Al K-L3 + 3 others, Al2O3]k[Si K-L3 + 3 others, SiO2]k[Ca K-L3 + 3 others, CaF2]k[Fe K-L3 + 1 other, Fe]k[Fe K-M3 + 3 others, Fe]
III-E K412[0][all]0.64220.039290.14690.067160.35060.19250.067140.06744
III-E K412[1][all]0.64490.039210.14680.066660.34960.19180.067180.06544
III-E K412[2][all]0.64510.039690.14720.0670.35050.19250.066870.06622
III-E K412[3][all]0.64970.038460.14770.067050.3510.19290.067060.06702
III-E K412[4][all]0.64820.038540.14760.067220.35170.19220.066960.06577

Let's take a look at a residual spectrum by plotting one of the FilterFitResult objects.

plot(res[1])

Quantify the k-ratios by Matrix Correction

quant = quantify.(res)
5-element Vector{IterationResult}:
 Converged to III-E K412[0][all][O=0.4745,Mg=0.1170,Al=0.0488,Si=0.2081,Ca=
0.1090,Fe=0.0810] in 8 steps.
 Converged to III-E K412[1][all][O=0.4754,Mg=0.1170,Al=0.0484,Si=0.2075,Ca=
0.1086,Fe=0.0811] in 8 steps.
 Converged to III-E K412[2][all][O=0.4761,Mg=0.1172,Al=0.0486,Si=0.2080,Ca=
0.1090,Fe=0.0807] in 8 steps.
 Converged to III-E K412[3][all][O=0.4788,Mg=0.1176,Al=0.0487,Si=0.2083,Ca=
0.1093,Fe=0.0809] in 9 steps.
 Converged to III-E K412[4][all][O=0.4776,Mg=0.1175,Al=0.0488,Si=0.2087,Ca=
0.1089,Fe=0.0808] in 9 steps.
MaterialOMgAlSiCaFeTotal
III-E K412[0][all]0.47450.1170.048760.20810.1090.0811.038
III-E K412[1][all]0.47540.1170.048420.20750.10860.081061.038
III-E K412[2][all]0.47610.11720.048650.2080.1090.080691.04
III-E K412[3][all]0.47880.11760.04870.20830.10930.080931.044
III-E K412[4][all]0.47760.11750.048810.20870.10890.08081.042

Finally plot the results as mass fractions.

plot(quant, known=unks[1][:Composition])

Plot the difference from the SRM value.

plot(quant, known=unks[1][:Composition], delta=true)

Plot the difference from the mean value for each element.

plot(quant, delta=true)