Quick Quantifying K412 using NeXLSpectrum VectorQuant

Fred Schamber taught me this trick for quantifying spectrum extremely quickly. It works reasonably well for a moderate number of ROIs, particularly when few of the ROIs interfere.

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 the unknowns 
unks = map(0:4) do i
  loadspectrum(joinpath(path, "III-E K412[$i][4].msa"))
end
# Create a detector model to match the unknown spectra
det = matching(unks[1], 132.0, 10)
BasicEDS[4096 chs, 1.63032 + 9.99856⋅ch eV, 132.0 eV @ Mn K-L3, 10 ch LLD, 
[Be,Sc,Ba,Pu]]
NameBeamEnergyProbeCurrentLiveTimeRealTimeCoatingIntegralMaterial
III-E K412[0][all]2e+041.114235.5286.3nothing8.08e+06K412
III-E K412[1][all]2e+041.114235.4286.2nothing8.077e+06K412
III-E K412[2][all]2e+041.112235.5286.3nothing8.084e+06K412
III-E K412[3][all]2e+041.11235.4286.3nothing8.087e+06K412
III-E K412[4][all]2e+041.11235.4286.2nothing8.081e+06K412

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.

unks[1][:LiveTime], unks[1][:ProbeCurrent], unks[1][:TakeOffAngle], unks[1][:BeamEnergy]
(235.48403, 1.11355, 0.6108652381980153, 20000.0)

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.

ffrs = references( [
  reference(n"Al", joinpath(path, "Al2O3 std.msa"), mat"Al2O3" ), #
  reference(n"Mg", joinpath(path, "MgO std.msa"), mat"MgO" ),   #
  reference(n"Fe", joinpath(path, "Fe std.msa"), mat"Fe" ),    #
  reference(n"Si", joinpath(path, "SiO2 std.msa"), mat"SiO2" ),  #
  reference(n"O", joinpath(path, "SiO2 std.msa"), mat"SiO2" ),  #
  reference(n"Ca", joinpath(path, "CaF2 std.msa"), mat"CaF2" ) 
], det)
display(plot( spectra(ffrs)..., klms= [n"O",n"Mg",n"Al",n"Si",n"Ca",n"Fe"], xmax=8.0e3))

Filter the Reference Spectra and Compute the VectorQuant Structure

vq = VectorQuant(ffrs)
plot(vq, 1:800)

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

res = map(unks) do unk
  fit_spectrum(unk, vq)
end
plot(res[1])

Compare this with the weighted fit

resfull = map(unks) do unk
  fit_spectrum(unk, ffrs)
end
plot(resfull[1])

Now the full data set...

Spectrak[O K-L3 + 1 other, SiO2]Δk[O K-L3 + 1 other, SiO2]k[Fe L3-M5 + 13 others, Fe]Δk[Fe L3-M5 + 13 others, Fe]k[Mg K-L3 + 1 other, MgO]Δk[Mg K-L3 + 1 other, MgO]k[Al K-L3 + 3 others, Al2O3]Δk[Al K-L3 + 3 others, Al2O3]k[Si K-L3 + 3 others, SiO2]Δk[Si K-L3 + 3 others, SiO2]k[Ca K-L3 + 3 others, CaF2]Δk[Ca K-L3 + 3 others, CaF2]k[Fe K-L3 + 1 other, Fe]Δk[Fe K-L3 + 1 other, Fe]k[Fe K-M3 + 3 others, Fe]Δk[Fe K-M3 + 3 others, Fe]
III-E K412[0][all]0.6460.00070250.042470.00016090.14730.00015130.06690.0001030.35080.00024650.19220.00019640.066830.00012820.066660.0003453
III-E K412[1][all]0.6480.00070370.042150.00016030.14720.00015130.06670.00010290.34990.00024620.19160.00019610.067010.00012840.067140.0003466
III-E K412[2][all]0.64850.00070450.042430.0001610.14770.00015160.067010.00010320.3510.00024680.19220.00019660.066830.00012830.066830.000346
III-E K412[3][all]0.6530.00070750.041920.00016010.14790.00015190.067080.00010330.35180.00024730.19260.0001970.066840.00012840.067590.0003483
III-E K412[4][all]0.65140.00070670.04150.00015930.14790.00015190.067220.00010350.35180.00024730.1920.00019670.066870.00012850.066340.0003451
Spectrak[O K-L3 + 1 other, SiO2]Δk[O K-L3 + 1 other, SiO2]k[Fe L3-M5 + 13 others, Fe]Δk[Fe L3-M5 + 13 others, Fe]k[Mg K-L3 + 1 other, MgO]Δk[Mg K-L3 + 1 other, MgO]k[Al K-L3 + 3 others, Al2O3]Δk[Al K-L3 + 3 others, Al2O3]k[Si K-L3 + 3 others, SiO2]Δk[Si K-L3 + 3 others, SiO2]k[Ca K-L3 + 3 others, CaF2]Δk[Ca K-L3 + 3 others, CaF2]k[Fe K-L3 + 1 other, Fe]Δk[Fe K-L3 + 1 other, Fe]k[Fe K-M3 + 3 others, Fe]Δk[Fe K-M3 + 3 others, Fe]
III-E K412[0][all]0.65360.00081010.041910.00043820.14760.00018340.066990.00015890.35070.00028890.19220.00023260.066830.00015930.066840.0006722
III-E K412[1][all]0.65540.00081080.041560.00043720.14750.00018350.066750.0001590.34990.00028880.19160.00023250.067080.00015950.067380.0006721
III-E K412[2][all]0.6560.00081240.041910.00043810.14790.00018380.067090.00015940.35110.00028960.19220.00023290.066880.00015960.067040.0006737
III-E K412[3][all]0.66040.00081550.041460.0004380.14810.00018410.067160.00015950.35190.000290.19250.00023330.066820.00015980.06780.0006746
III-E K412[4][all]0.65880.00081490.040810.00043830.14820.00018410.067280.00015970.35180.000290.19220.00023330.066940.00015980.066480.000674

Compare the timings (full then fast)

using BenchmarkTools
@btime map(unk->fit_spectrum(unk, ffrs), unks)
@btime map(unk->fit_spectrum(unk, vq), unks)
1.422 ms (2847 allocations: 1.28 MiB)
  404.500 μs (667 allocations: 191.23 KiB)
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])