diff --git a/Citation.bib b/Citation.bib new file mode 100644 index 0000000..b341bba --- /dev/null +++ b/Citation.bib @@ -0,0 +1,11 @@ +@article{ritchie_2022, + title={Reproducible Spectrum and Hyperspectrum Data Analysis Using {NeXL}}, + volume={28}, + DOI={10.1017/S143192762200023X}, + number={2}, + journal={Microscopy and Microanalysis}, + publisher={Cambridge University Press}, + author={Ritchie, Nicholas W. M.}, + year={2022}, + pages={478–495} +} \ No newline at end of file diff --git a/Project.toml b/Project.toml index 0bbd24f..3f3a081 100644 --- a/Project.toml +++ b/Project.toml @@ -2,12 +2,13 @@ name = "NeXLSpectrum" uuid = "6c578565-ca7f-4012-afc4-b8412d85af92" authors = ["Nicholas W.M. Ritchie "] repo-url = "https://github.com/usnistgov/NeXLSpectrum.jl" -version = "0.3.2" +version = "0.3.3" [deps] CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" Compose = "a81c6b42-2e10-5240-aca2-a61377ecd94b" +CubicSplines = "9c784101-8907-5a6d-9be6-98f00873c89b" DataAPI = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" DataDeps = "124859b0-ceae-595e-8997-d05f6a7a8dfe" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" @@ -27,7 +28,6 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" LoopVectorization = "bdcacae8-1622-11e9-2a5c-532679323890" LsqFit = "2fda8390-95c7-5789-9bda-21331edee243" Mmap = "a63ad114-7e13-5084-954f-fe012c677804" -MultivariateStats = "6f286f6a-111f-5878-ab1e-185364afe411" NeXLCore = "93a0284f-3680-435e-8d39-dd6e52a1dbc8" NeXLMatrixCorrection = "3ab34fdf-2fe8-473b-9b20-30f5809db337" NeXLUncertainties = "7127f3d6-1721-46bc-bb38-8fc703dd438e" @@ -35,6 +35,7 @@ OnlineStats = "a15396b6-48d5-5d58-9928-6d29437db91e" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Polynomials = "f27b6e38-b328-58d1-80ce-0feddd5e7a45" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" +Procrastinate = "3362a694-cdb4-48f5-aa9a-ff559879651c" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" Requires = "ae029012-a4dd-5104-9daa-d747884805df" @@ -49,6 +50,7 @@ CSV = "0.9, 0.10" Cairo = "1.0.3" Colors = "0.12" Compose = "0.9" +CubicSplines = "0.2" DataAPI = "1.6" DataDeps = "0.7" DataFrames = "1" @@ -56,21 +58,21 @@ Distributions = "0.25" EzXML = "1.1" FileIO = "1.10" Formatting = "0.4" -FourierTools = "0.2, 0.3" +FourierTools = "0.3.3" Gadfly = "1.3" HDF5 = "0.15, 0.16" ImageAxes = "0.6" ImageIO = "0.5, 0.6" Images = "0.25" -Interpolations = "0.13" +Interpolations = "0.13, 0.14" LoopVectorization = "0.12" LsqFit = "0.12" -MultivariateStats = "0.8, 0.9" -NeXLCore = "0.3.2" -NeXLMatrixCorrection = "0.3.2" -NeXLUncertainties = "0.2.9" +NeXLCore = "0.3.5" +NeXLMatrixCorrection = "0.3.3" +NeXLUncertainties = "0.2.10" OnlineStats = "1.5" -Polynomials = "2" +Polynomials = "3" +Procrastinate = "0.1" Reexport = "1" Requires = "1" ThreadsX = "0.1.8" diff --git a/docs/make.jl b/docs/make.jl index 1b61d41..16c39c3 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -20,6 +20,7 @@ end pages = [ "Home" => "index.md", "Spectrum Methods" => "spectrum.md", + "HyperSpectrum Methods" => "hyperspectrum.md", "Fitting K412 (simple API)" => "k412refs.md", "Fitting K412 (flexible API)" => "K412fit.md", "Fitting K412 (quick fit)" => "K412quick.md", diff --git a/docs/src/continuummodel.html b/docs/src/continuummodel.html new file mode 100644 index 0000000..aab6b0e --- /dev/null +++ b/docs/src/continuummodel.html @@ -0,0 +1,786 @@ + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + +
+ +

Modeling the Continuum

+

Nicholas W. M. Ritchie 9-Jun-2020

+

Modeling the continuum (Bremsstrahlung) is a necessary component of being able to perform an straight (unfiltered) spectrum fit. These models can either be used to handle the continuum for linear or non-linear spectrum fits.

+
using NeXLSpectrum
+using NeXLMatrixCorrection
+using Gadfly
+using Colors
+

The ContinuumModel handles the generation of Bremsstrahlung within the sample, the absorption of the generated X-rays as they exit the sample and the efficiency of the detector collecting the X-rays.

+
e0 = 15.0e3
+eff = SDDEfficiency(TabulatedWindow(MoxtekAP33()); thickness=0.0370, deadlayer=30.0e-7, entrance=Film(pure(n"Al"), 10.0e-7))
+#eff = SDDEfficiency(NoWindow(); thickness=100.0, deadlayer=0.0e-7, entrance=Film(pure(n"Al"), 0.0e-7))
+cmod = ContinuumModel(mat"0.8*Fe+0.15*Cr+0.05*Ni", e0, deg2rad(40.0))
+plot([ea->emitted(cmod, ea),ea->generated(cmod, ea)],100.0,e0)
+
Error: UndefVarError: AP33Tabulation not defined
+
e0 = 5.0e3
+cmod = ContinuumModel(mat"SiO2", e0, deg2rad(40.0))
+plot([ea->emitted(cmod, ea),ea->generated(cmod, ea)],100.0,e0)
+

+
det = simpleEDS(4096, 5.0, 0.0, 132.0, 10)
+e0 = 5.0e3
+cmod = ContinuumModel(mat"0.8*Fe+0.15*Cr+0.05*Ni", e0, deg2rad(40.0))
+resp = NeXLSpectrum.detectorresponse(det, eff)
+emt = map(ch->ch>=lld(det) ? emitted(cmod,energy(ch,det)) : 0.0,1:channelcount(det))
+meas =  resp*emt
+lyrs = [ layer(x=energyscale(det), y=emt, Geom.line, Theme(default_color="red")), layer(x=energyscale(det), y=meas, Geom.line) ]
+plot(lyrs..., Coord.cartesian(xmin=0, xmax=e0))
+
Error: UndefVarError: eff not defined
+

Models!!!

+
model, mc = Castellano2004a, Riveros1993
+eff = SDDEfficiency(AP33Tabulation(); thickness=0.0370, deadlayer=30.0e-7, entrance=Film(pure(n"Al"), 10.0e-7))
+
+spec = loadspectrum(joinpath(@__DIR__, "K412 spectra","III-E K412[0][4].msa"))
+det=matching(spec, 132.0, 10)
+roi = channel(4.5e3,det):channel(6.0e3,det)
+
+spec = loadspectrum(joinpath(@__DIR__, "K412 spectra","III-E K412[0][4].msa"))
+cmod = ContinuumModel(spec[:Composition], spec[:BeamEnergy], spec[:TakeOffAngle], bremsstrahlung=model)
+emt = map(ch->ch>=lld(det) ? emitted(cmod,energy(ch,det)) : 0.0,1:channelcount(det))
+resp = NeXLSpectrum.detectorresponse(det, eff)
+meas = resp*emt
+
+plot(layer(x=eachindex(emt),y=emt, Geom.line),layer(x=eachindex(meas),y=meas, Geom.point))
+
Error: UndefVarError: AP33Tabulation not defined
+
spec = loadspectrum(joinpath(@__DIR__, "K412 spectra","III-E K412[0][4].msa"))
+brem = fitcontinuum(spec, resp, [roi], brem=model, mc=mc)
+println("k = $(brem[:K]/dose(spec))")
+
+plot(spec, brem, yscale=0.05, xmax=12.0e3)
+
Error: UndefVarError: roi not defined
+
spec = loadspectrum(joinpath(@__DIR__, "K412 spectra","Al2O3 std.msa"))
+brem = fitcontinuum(spec, resp, [roi], brem=model, mc=mc)
+println("k = $(brem[:K]/dose(spec))")
+plot(spec, brem, yscale=0.01, xmax=8.0e3)
+
Error: UndefVarError: roi not defined
+
spec = loadspectrum(joinpath(@__DIR__, "K412 spectra","SiO2 std.msa"))
+brem = fitcontinuum(spec, resp, [roi], brem=model, mc=mc)
+println("k = $(brem[:K]/dose(spec))")
+plot(spec, brem, yscale=0.01, xmax=8.0e3)
+
Error: UndefVarError: roi not defined
+
spec = loadspectrum(joinpath(@__DIR__, "K412 spectra","MgO std.msa"))
+brem = fitcontinuum(spec, resp, [roi], brem=model, mc=mc)
+println("k = $(brem[:K]/dose(spec))")
+plot(spec, brem, yscale=0.01, xmax=8.0e3)
+
Error: UndefVarError: roi not defined
+
spec = loadspectrum(joinpath(@__DIR__, "K412 spectra","CaF2 std.msa"))
+brem = fitcontinuum(spec, resp, [roi], brem=model, mc=mc)
+println("k = $(brem[:K]/dose(spec))")
+plot(spec, brem, yscale=0.05, xmax=8.0e3)
+
Error: UndefVarError: roi not defined
+
spec = loadspectrum(joinpath(@__DIR__, "K412 spectra","Fe std.msa"))
+brem = fitcontinuum(spec, resp, [ channel(2.0e3,det):channel(4.0e3,det)], brem=model, mc=mc)
+println("k = $(brem[:K]/dose(spec))")
+plot(spec, brem, yscale=0.05, xmax=8.0e3)
+
Error: UndefVarError: resp not defined
+
path = joinpath(@__DIR__,"K412 spectra")
+spec = loadspectrum(joinpath(path,"III-E K412[0][4].msa"))
+display(plot(spec, subtractcontinuum(spec, det, resp), yscale=0.05, xmax=8.0e3))
+
+spec = loadspectrum(joinpath(path,"Al2O3 std.msa"))
+display(plot(spec, subtractcontinuum(spec, det, resp), yscale=0.05, xmax=8.0e3))
+
+spec = loadspectrum(joinpath(path,"SiO2 std.msa"))
+display(plot(spec, subtractcontinuum(spec, det, resp), yscale=0.05, xmax=8.0e3))
+
+spec = loadspectrum(joinpath(path,"MgO std.msa"))
+display(plot(spec, subtractcontinuum(spec, det, resp), yscale=0.05, xmax=8.0e3))
+
+spec = loadspectrum(joinpath(path,"CaF2 std.msa"))
+display(plot(spec, subtractcontinuum(spec, det, resp), yscale=0.05, xmax=8.0e3))
+
+spec = loadspectrum(joinpath(path,"Fe std.msa"))
+display(plot(spec, subtractcontinuum(spec, det, resp), yscale=0.05, xmax=8.0e3))
+
+spec = loadspectrum(joinpath(@__DIR__, "spectra","Ag std.msa"))
+display(plot(spec, fittedcontinuum(spec, det, resp, mode=:Global), fittedcontinuum(spec, det, resp, mode=:Local), yscale=0.05))
+
+spec = loadspectrum(joinpath(@__DIR__, "spectra","Au std.msa"))
+display(plot(spec, fittedcontinuum(spec, det, resp, mode=:Global), fittedcontinuum(spec, det, resp, mode=:Local), yscale=0.05))
+
+spec = loadspectrum(joinpath(@__DIR__, "spectra","B std.msa"))
+display(plot(spec, fittedcontinuum(spec, det, resp, mode=:Global), fittedcontinuum(spec, det, resp, mode=:Local), yscale=0.5))
+
+spec = loadspectrum(joinpath(@__DIR__, "spectra","Bi std.msa"))
+display(plot(spec, fittedcontinuum(spec, det, resp, mode=:Global), fittedcontinuum(spec, det, resp, mode=:Local), yscale=0.05))
+
+spec = loadspectrum(joinpath(@__DIR__, "spectra","BN std.msa"))
+display(plot(spec, fittedcontinuum(spec, det, resp, mode=:Global), fittedcontinuum(spec, det, resp, mode=:Local), yscale=0.20))
+
+spec = loadspectrum(joinpath(@__DIR__, "spectra","C std.msa"))
+display(plot(spec, fittedcontinuum(spec, det, resp, mode=:Global), fittedcontinuum(spec, det, resp, mode=:Local), yscale=0.05))
+
Error: UndefVarError: resp not defined
+ + +
+ +
+
+
+ + + diff --git a/docs/src/hyperspectrum.md b/docs/src/hyperspectrum.md new file mode 100644 index 0000000..b88ee7a --- /dev/null +++ b/docs/src/hyperspectrum.md @@ -0,0 +1,127 @@ +# ![](NeXL_sm.png)Spectrum +## [Working with HyperSpectrum objects](@id hyperspectrum_methods) + +`HyperSpectrum{T<:Real, N, NP} <: AbstractArray{Spectrum{T}, N}` represents an N-dimensional array of `Spectrum{T}` +which share common properties. N = 1 represents a line scan, N = 2 represents a spectrum image, N = 3 can represent +a slice-and-view type hyperspectrum cube or a time series of spectrum images. Higher N are also possible. + +To construct an empty `HyperSpectrum` use +```julia +hs = HyperSpectrum( + energy::EnergyScale, + props::Dict{Symbol,Any}, + dims::Tuple, + depth::Int, + type::Type{<:Real}; + axisnames = ( "Y", "X", "Z", "A", "B", "C" ), + fov = ( 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ), + offset = ( 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ), # + stagemap::Type{<:StageMapping}=DefaultStageMapping +) +``` + +To construct a `HyperSpectrum` from an existing array of (N+1) dimensions. The first dimension is the channel data like (ch, Y, X, ...) +```julia +hs = HyperSpectrum( + energy::EnergyScale, + props::Dict{Symbol,Any}, + arr::Array{<:Real}; + axisnames = ( "Y", "X", "Z", "A", "B", "C" ), # + fov = [ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ], # + offset = zeros(length(fov)), # + stagemap::Type{<:StageMapping}=DefaultStageMapping, # + livetime=fill(get(props, :LiveTime, 1.0), size(arr)[2:end]...) +) +``` + +We will assume a 2D hyperspectrum ("spectrum image") for these examples. + +The `Spectrum` representing individual pixels in a `HyperSpectrum` are accessed using +```julia +hs[10,20] # access the 10th row, 20th column +``` +To index energy planes within the `HyperSpectrum` you can use +```julia +plane( + hss::HyperSpectrum{T, N, NP}, + chs::AbstractUnitRange{<:Integer}, + normalize = false, +) +``` +which sums over `chs` and optionally normalizes to the brightest pixel. +Alternatively, +```julia +plane( + hss::HyperSpectrum{T, N, NP}, + cxr::CharXRay, + normalize = false, +) +``` +will extract one FWHM centered on `cxr`. Like `plane(hss,n"Fe K-L3")`. + +To extract `HyperSpectrum` data items representing regions from the `HyperSpectrum` +```julia +hs[10:20,20:30] # extract a HyperSpectrum representing the 10th to 20th row and 20th to 30th columns. +hs[10:2:20,20:2:30] # extract a HyperSpectrum representing every other pixel in the 10th to 20th row and 20th to 30th columns. +``` + +To extract a linescan from within a 2D `HyperSpectrum` +```julia +linescan(hss::HyperSpectrum{T,2,3}, ci1::CartesianIndex{2}, ci2::CartesianIndex{2}, width::Int=1) +``` + +Mostly, a `HyperSpectrum` acts like an `Array{Spectrum}` and you can view and process the +individual spectra this way. However, this is often inefficient as a new `Spectrum` datum +must be allocated each coordinate. There are functions designed to operate more efficiently +on the channel data. + +```julia +sum(hs) # Produce a sum spectrum +maxpixel(hs) # produce a Bright's max-pixel spectrum +``` +To find out which pixel contains the max-pixel for each channel in the `HyperSpectrum` +```julia +indexofmaxpixel(hss::HyperSpectrum) # An array of CartesianIndices +indexofmaxpixel(hss::HyperSpectrum, ch::Int) # A CartesianIndices for channel `ch` +``` + +To visualize planes within the `HyperSpectrum` as images +```julia +hss[n"Fe K-L3"] # A FWHM ROI around the Fe K-L3 transition +roiimage(hss::HyperSpectrum, chs::AbstractUnitRange{<:Integer}) +``` +For an RGB image of up to three lines +```julia +colorize(hss::HyperSpectrum, cxrs::AbstractVector{CharXRay}, normalize=:All) +colorize(hs, [ n"Fe K-L3", n"Si K-L3", n"Al K-L3" ]) +``` + +Quantifying `HyperSpectra` is like quantifying spectra. There is a specialization +of `fit_spectrum(...)` and `quantify(...)` optimized for hyper-spectra. + +```julia +fit_spectra( + hs::HyperSpectrum, + ffp::FilterFitPacket{S, T}; + mode::Symbol = :Fast[|:Intermediate|:Full] + zero = x -> max(Base.zero(T), x), + sigma = Base.zero(T) +)::Array{KRatios} +``` +`:Fast` uses a highly optimized but less precise algorithm that works for up to +approximately 15 to 20 elements. `:Intermediate` and `:Full` perform the slower +weighted filtered least-square fit. `:Intermediate` sets k-ratios less than zero +to zero but doesn't refit, while `:Full` removes k-ratios that are less than zero +and refits. + +To convert the `Array{KRatios}` from `fit_spectra` to `Array{Material}` +```julia +NeXLMatrixCorrection.quantify( + measured::AbstractVector{<:KRatios}; + mc::Type{<:MatrixCorrection} = XPP, + fc::Type{<:FluorescenceCorrection} = NullFluorescence, + cc::Type{<:CoatingCorrection} = NullCoating, + kro::KRatioOptimizer = SimpleKRatioOptimizer(1.5), + coating::Union{Nothing, Pair{CharXRay, <:Material}}=nothing +) +``` \ No newline at end of file diff --git a/notebook/background corrected llsq fit.ipynb b/notebook/background corrected llsq fit.ipynb index e174591..2ec9925 100644 --- a/notebook/background corrected llsq fit.ipynb +++ b/notebook/background corrected llsq fit.ipynb @@ -2,27 +2,21 @@ "cells": [ { "cell_type": "markdown", - "source": [ - "# LLSQ Fit\n", - "Use background corrected reference spectra to fit an unknown. " - ], "metadata": { "nteract": { "transient": { "deleting": false } } - } + }, + "source": [ + "# LLSQ Fit\n", + "Use background corrected reference spectra to fit an unknown. " + ] }, { "cell_type": "code", "execution_count": null, - "source": [ - "using NeXLSpectrum\r\n", - "using DataFrames, Gadfly, CSV\r\n", - "using LinearAlgebra" - ], - "outputs": [], "metadata": { "collapsed": false, "jupyter": { @@ -34,40 +28,30 @@ "deleting": false } } - } + }, + "outputs": [], + "source": [ + "using NeXLSpectrum\n", + "using DataFrames, Gadfly, CSV\n", + "using LinearAlgebra" + ] }, { "cell_type": "markdown", - "source": [ - "Load the unknown spectrum and the reference spectra. Plot them." - ], "metadata": { "nteract": { "transient": { "deleting": false } } - } + }, + "source": [ + "Load the unknown spectrum and the reference spectra. Plot them." + ] }, { "cell_type": "code", "execution_count": null, - "source": [ - "path = joinpath(@__DIR__,\"K309\")\r\n", - "unk = loadspectrum(joinpath(path, \"K309.msa\"))\r\n", - "unk[:Composition]=parse(Material, \"0.3872*O+0.0794*Al+0.1870*Si+0.1072*Ca+0.1049*Fe+0.1343*Ba\", name=\"K309\")\r\n", - "unk[:Coating]=Film(parse(Material,\"C\",density=1.9),10.0e-7)\r\n", - "stds = map((\"Al2O3\", \"BaF2\", \"CaF2\", \"Fe\", \"Si\", \"Ti\" )) do fn \r\n", - " s = loadspectrum(joinpath(path,\"$fn std.msa\"))\r\n", - " s[:Composition]=parse(Material,fn)\r\n", - " s[:Coating]=Film(parse(Material,\"C\",density=1.9),10.0e-7)\r\n", - " s\r\n", - "end\r\n", - "elems = collect(elms(unk, true))\r\n", - "set_default_plot_size(8inch,4inch)\r\n", - "plot(unk,stds...,klms=elems,xmax=8.0e3)" - ], - "outputs": [], "metadata": { "collapsed": false, "jupyter": { @@ -79,19 +63,27 @@ "deleting": false } } - } + }, + "outputs": [], + "source": [ + "path = joinpath(@__DIR__,\"K309\")\n", + "unk = loadspectrum(joinpath(path, \"K309.msa\"))\n", + "unk[:Composition]=parse(Material, \"0.3872*O+0.0794*Al+0.1870*Si+0.1072*Ca+0.1049*Fe+0.1343*Ba\", name=\"K309\")\n", + "unk[:Coating]=Film(parse(Material,\"C\",density=1.9),10.0e-7)\n", + "stds = map((\"Al2O3\", \"BaF2\", \"CaF2\", \"Fe\", \"Si\", \"Ti\" )) do fn \n", + " s = loadspectrum(joinpath(path,\"$fn std.msa\"))\n", + " s[:Composition]=parse(Material,fn)\n", + " s[:Coating]=Film(parse(Material,\"C\",density=1.9),10.0e-7)\n", + " s\n", + "end\n", + "elems = collect(elms(unk, true))\n", + "set_default_plot_size(8inch,4inch)\n", + "plot(unk,stds...,klms=elems,xmax=8.0e3)" + ] }, { "cell_type": "code", "execution_count": null, - "source": [ - "det = matching(unk,132.0, 110)\r\n", - "resp = detectorresponse(det,SDDEfficiency(AP33Model()))\r\n", - "\r\n", - "unkb = fittedcontinuum(unk, det, resp)\r\n", - "stdsb = map(s->fittedcontinuum(s, det, resp), stds);" - ], - "outputs": [], "metadata": { "collapsed": false, "jupyter": { @@ -103,24 +95,28 @@ "deleting": false } } - } + }, + "outputs": [], + "source": [ + "det = matching(unk,132.0, 110)\n", + "resp = detectorresponse(det,SDDEfficiency(ModeledWindow(MoxtekAP33())))\n", + "\n", + "unkb = fittedcontinuum(unk, det, resp)\n", + "stdsb = map(s->fittedcontinuum(s, det, resp), stds);" + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "plot(Iterators.flatten(zip(stdsb, stds))..., xmax=8.0e3, yscale=0.008)" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, - "source": [ - "plot(unkb, unk, autoklms=true,xmax=8.0e3, yscale=0.04) " - ], - "outputs": [], "metadata": { "collapsed": false, "jupyter": { @@ -132,17 +128,15 @@ "deleting": false } } - } + }, + "outputs": [], + "source": [ + "plot(unkb, unk, autoklms=true,xmax=8.0e3, yscale=0.04) " + ] }, { "cell_type": "code", "execution_count": null, - "source": [ - "unkc = subtractcontinuum(unk, det, resp)\r\n", - "stdsc = map(s->subtractcontinuum(s, det, resp), stds)\r\n", - "plot(unkc, stdsc..., autoklms=true, xmax=8.0e3, yscale=0.01)" - ], - "outputs": [], "metadata": { "collapsed": false, "jupyter": { @@ -154,15 +148,17 @@ "deleting": false } } - } + }, + "outputs": [], + "source": [ + "unkc = subtractcontinuum(unk, det, resp)\n", + "stdsc = map(s->subtractcontinuum(s, det, resp), stds)\n", + "plot(unkc, stdsc..., autoklms=true, xmax=8.0e3, yscale=0.01)" + ] }, { "cell_type": "code", "execution_count": null, - "source": [ - "plot(stdsc[4], autoklms=true, xmax=8.0e3, yscale=0.01)" - ], - "outputs": [], "metadata": { "collapsed": false, "jupyter": { @@ -174,79 +170,22 @@ "deleting": false } } - } + }, + "outputs": [], + "source": [ + "plot(stdsc[4], autoklms=true, xmax=8.0e3, yscale=0.01)" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Seems to work - at least the basic concept." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, - "source": [ - "function fit_spectrum(\r\n", - " spec::Spectrum, \r\n", - " refs::Dict{Element, <:Spectrum}, \r\n", - " det::EDSDetector,\r\n", - " resp::AbstractArray{<:Real,2};\r\n", - " brem::Type{<:NeXLBremsstrahlung} = Castellano2004a,\r\n", - " mc::Type{<:MatrixCorrection} = Riveros1993,\r\n", - " )# ::Vector{KRatio}\r\n", - " cuds = []\r\n", - " for (elm, ref) in refs\r\n", - " cmod = ContinuumModel(\r\n", - " ref[:Composition],\r\n", - " ref[:BeamEnergy],\r\n", - " ref[:TakeOffAngle],\r\n", - " matrixcorrection = mc,\r\n", - " bremsstrahlung = brem)\r\n", - " model = resp * map(e -> e > 50.0 ? emitted(cmod, e) : 0.0, energyscale(spec))\r\n", - " cud = map(NeXLSpectrum.labeledextents(characteristic(elm, alltransitions, 0.001), det, 0.001)) do le\r\n", - " cxrs, ur = le \r\n", - " ( cxrs, ur, ref[ur]-model[ur], ref )\r\n", - " end\r\n", - " append!(cuds, cud) \r\n", - " end\r\n", - " ascontiguous = let\r\n", - " scuds = sort(cuds, lt=(c1,c2)->first(c1[2]) 0\r\n", - " res[end] = min(first(roi), first(res[end])):max(last(roi), last(res[end]))\r\n", - " else\r\n", - " push!(res, roi)\r\n", - " end\r\n", - " end\r\n", - " res\r\n", - " end\r\n", - " cmod = ContinuumModel(\r\n", - " unk[:Composition],\r\n", - " unk[:BeamEnergy],\r\n", - " unk[:TakeOffAngle],\r\n", - " matrixcorrection = mc,\r\n", - " bremsstrahlung = brem)\r\n", - " model = resp * map(e -> e > 50.0 ? emitted(cmod, e) : 0.0, energyscale(spec))\r\n", - " y = counts(unk) - model\r\n", - " res = UncertainValues[]\r\n", - " for roi in ascontiguous\r\n", - " fcuds = filter(cud->length(intersect(cud[2], roi))>0, cuds)\r\n", - " a = zeros(Float64, (length(roi), length(fcuds)))\r\n", - " labels, scales = Label[], Float64[]\r\n", - " for (i, cud) in enumerate(fcuds)\r\n", - " (cxrs, croi, cdata, cref) = cud\r\n", - " a[first(croi)-first(roi)+1:last(croi)-first(roi)+1,i] = cdata\r\n", - " push!(labels, CharXRayLabel(spec, croi, cxrs))\r\n", - " push!(scales, dose(cref) / dose(unk))\r\n", - " end\r\n", - " push!(res, diagm(scales)*NeXLSpectrum.olspinv(y[roi], a, 1.0, labels))\r\n", - " end\r\n", - " return cat(res...)\r\n", - "end" - ], - "outputs": [], "metadata": { "collapsed": false, "jupyter": { @@ -258,16 +197,72 @@ "deleting": false } } - } + }, + "outputs": [], + "source": [ + "function fit_spectrum(\n", + " spec::Spectrum, \n", + " refs::Dict{Element, <:Spectrum}, \n", + " det::EDSDetector,\n", + " resp::AbstractArray{<:Real,2};\n", + " brem::Type{<:NeXLBremsstrahlung} = Castellano2004a,\n", + " mc::Type{<:MatrixCorrection} = Riveros1993,\n", + " )# ::Vector{KRatio}\n", + " cuds = []\n", + " for (elm, ref) in refs\n", + " cmod = ContinuumModel(\n", + " ref[:Composition],\n", + " ref[:BeamEnergy],\n", + " ref[:TakeOffAngle],\n", + " matrixcorrection = mc,\n", + " bremsstrahlung = brem)\n", + " model = resp * map(e -> e > 50.0 ? emitted(cmod, e) : 0.0, energyscale(spec))\n", + " cud = map(NeXLSpectrum.labeledextents(characteristic(elm, alltransitions, 0.001), det, 0.001)) do le\n", + " cxrs, ur = le \n", + " ( cxrs, ur, ref[ur]-model[ur], ref )\n", + " end\n", + " append!(cuds, cud) \n", + " end\n", + " ascontiguous = let\n", + " scuds = sort(cuds, lt=(c1,c2)->first(c1[2]) 0\n", + " res[end] = min(first(roi), first(res[end])):max(last(roi), last(res[end]))\n", + " else\n", + " push!(res, roi)\n", + " end\n", + " end\n", + " res\n", + " end\n", + " cmod = ContinuumModel(\n", + " unk[:Composition],\n", + " unk[:BeamEnergy],\n", + " unk[:TakeOffAngle],\n", + " matrixcorrection = mc,\n", + " bremsstrahlung = brem)\n", + " model = resp * map(e -> e > 50.0 ? emitted(cmod, e) : 0.0, energyscale(spec))\n", + " y = counts(unk) - model\n", + " res = UncertainValues[]\n", + " for roi in ascontiguous\n", + " fcuds = filter(cud->length(intersect(cud[2], roi))>0, cuds)\n", + " a = zeros(Float64, (length(roi), length(fcuds)))\n", + " labels, scales = Label[], Float64[]\n", + " for (i, cud) in enumerate(fcuds)\n", + " (cxrs, croi, cdata, cref) = cud\n", + " a[first(croi)-first(roi)+1:last(croi)-first(roi)+1,i] = cdata\n", + " push!(labels, CharXRayLabel(spec, croi, cxrs))\n", + " push!(scales, dose(cref) / dose(unk))\n", + " end\n", + " push!(res, diagm(scales)*NeXLSpectrum.olspinv(y[roi], a, 1.0, labels))\n", + " end\n", + " return cat(res...)\n", + "end" + ] }, { "cell_type": "code", "execution_count": null, - "source": [ - "refs = Dict(n\"O\"=>stds[1],n\"Ba\"=>stds[2],n\"Ca\"=>stds[3],n\"Fe\"=>stds[4],n\"Si\"=>stds[5],n\"Ti\"=>stds[6])\r\n", - "fit_spectrum(unk, refs, det, resp)" - ], - "outputs": [], "metadata": { "collapsed": false, "jupyter": { @@ -280,43 +275,48 @@ } }, "tags": [] - } + }, + "outputs": [], + "source": [ + "refs = Dict(n\"O\"=>stds[1],n\"Ba\"=>stds[2],n\"Ca\"=>stds[3],n\"Fe\"=>stds[4],n\"Si\"=>stds[5],n\"Ti\"=>stds[6])\n", + "fit_spectrum(unk, refs, det, resp)" + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ "Let's compare this to the filter-fit result." - ], - "metadata": {} + ] }, { "cell_type": "code", "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ - "ffrs = references( [\r\n", - " reference(n\"O\", stds[1]),\r\n", - " reference(n\"Ba\", stds[2]),\r\n", - " reference(n\"Ca\", stds[3]),\r\n", - " reference(n\"Fe\", stds[4]),\r\n", - " reference(n\"Si\", stds[5]),\r\n", - " reference(n\"Ti\", stds[6])\r\n", - " ], det)\r\n", - "ffres=NeXLSpectrum.fit_spectrum(unk, ffrs)\r\n", + "ffrs = references( [\n", + " reference(n\"O\", stds[1]),\n", + " reference(n\"Ba\", stds[2]),\n", + " reference(n\"Ca\", stds[3]),\n", + " reference(n\"Fe\", stds[4]),\n", + " reference(n\"Si\", stds[5]),\n", + " reference(n\"Ti\", stds[6])\n", + " ], det)\n", + "ffres=NeXLSpectrum.fit_spectrum(unk, ffrs)\n", "asa(DataFrame, ffres)" - ], - "outputs": [], - "metadata": {} + ] }, { "cell_type": "markdown", + "metadata": {}, "source": [ - "Clearly, there is some similarity between the numbers but the agreement is not what I'd like.\r\n", - "\r\n", - "Who do you trust? Well, I trust the filter-fit results. I'm very confident that the filter-fit algorithm produces accurate k-ratios. Less so for the background corrected fit.\r\n", - "\r\n", + "Clearly, there is some similarity between the numbers but the agreement is not what I'd like.\n", + "\n", + "Who do you trust? Well, I trust the filter-fit results. I'm very confident that the filter-fit algorithm produces accurate k-ratios. Less so for the background corrected fit.\n", + "\n", "NWMR" - ], - "metadata": {} + ] } ], "metadata": { @@ -330,8 +330,8 @@ }, "language_info": { "file_extension": ".jl", - "name": "julia", "mimetype": "application/julia", + "name": "julia", "version": "1.6.2" }, "nteract": { @@ -340,4 +340,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/notebook/continuum.jl b/notebook/continuum.jl index b948eac..3285359 100644 --- a/notebook/continuum.jl +++ b/notebook/continuum.jl @@ -48,7 +48,7 @@ X-ray. The detector response function (implemented as a matrix) is a function th """ # ╔═╡ 458d65a0-687c-11eb-18aa-29a72e160f55 -resp = detectorresponse(det,SDDEfficiency(AP33Model())) +resp = detectorresponse(det,SDDEfficiency(ModeledWindow(MoxtekAP33()))) # ╔═╡ 26bf4360-6885-11eb-316f-618f54f568b9 md""" diff --git a/notebook/spectrum_comparison2.ipynb b/notebook/spectrum_comparison2.ipynb new file mode 100644 index 0000000..1ebc881 --- /dev/null +++ b/notebook/spectrum_comparison2.ipynb @@ -0,0 +1,7501 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Comparing Spectra\n", + "\n", + "This notebooks demonstrates how you can use the χ² metric to compare spectra." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "using NeXLSpectrum\n", + "using DataFrames, Gadfly, InvertedIndices" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5-element Vector{Spectrum{Float64}}:\n", + " Spectrum{Float64}[IIIE K412[0][all], 2.58389 + 9.99864⋅ch eV, 4096 ch, 20.0 keV, K412, 6.44e6 counts]\n", + " Spectrum{Float64}[IIIE K412[1][all], 2.58389 + 9.99864⋅ch eV, 4096 ch, 20.0 keV, K412, 6.44e6 counts]\n", + " Spectrum{Float64}[IIIE K412[2][all], 2.58389 + 9.99864⋅ch eV, 4096 ch, 20.0 keV, K412, 6.45e6 counts]\n", + " Spectrum{Float64}[IIIE K412[3][all], 2.58389 + 9.99864⋅ch eV, 4096 ch, 20.0 keV, K412, 6.45e6 counts]\n", + " Spectrum{Float64}[IIIE K412[4][all], 2.58389 + 9.99864⋅ch eV, 4096 ch, 20.0 keV, K412, 6.45e6 counts]" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "specs = [ loadspectrum(joinpath(raw\"C:\\Users\\nritchie\\Documents\\FY2022\\DODOA EDS QC\\10-Jan-2022\\Manual\",\"IIIE K412[$i][3].msa\")) for i in 0:4 ]" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "BasicEDS[4096 chs, 2.58389 + 9.99864⋅ch eV, 132.0 eV @ Mn K-L3, 1 ch LLD, [Be,Sc,Ba,Pu]]" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "det = matching(specs[1], 132.0)" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "7-element Vector{Element}:\n", + " Element(Carbon)\n", + " Element(Oxygen)\n", + " Element(Magnesium)\n", + " Element(Aluminium)\n", + " Element(Silicon)\n", + " Element(Calcium)\n", + " Element(Iron)" + ] + }, + "execution_count": 24, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "set_default_plot_size(8inch, 4inch)\n", + "elms = [ n\"C\",n\"O\",n\"Mg\",n\"Al\",n\"Si\",n\"Ca\",n\"Fe\" ]\n", + "# plot(specs..., xmax=12.0e3, klms=elms)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "I'll present two different ways to compare spectra.\n", + " * Direct spectrum to spectrum comparison (`χ²(...)`)\n", + " * Comparing a spectrum to the sum of the other spectra (`similarity(...)`)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, `χ²`. This metric will be approximately unity when the spectra differ only by count statistics. \n", + "\n", + "Overall, the spectra compare very well one to another. The largest `χ²` metric is 1.10 when comparing one spectrum to another over large ROI." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5×5 Matrix{Float64}:\n", + " 0.0 1020.88 995.206 997.722 1088.22\n", + " 1020.88 0.0 979.742 998.507 1013.8\n", + " 995.206 979.742 0.0 961.053 976.453\n", + " 997.722 998.507 961.053 0.0 937.162\n", + " 1088.22 1013.8 976.453 937.162 0.0" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fullroi = channel(100.0, det):channel(10.0e3, det)\n", + "χ²(specs, fullroi)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, individual peaks can compare less well." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5×5 Matrix{Float64}:\n", + " 0.0 6.78062 6.79688 9.54306 5.37196\n", + " 6.78062 0.0 10.2712 16.9399 10.5116\n", + " 6.79688 10.2712 0.0 10.2049 0.98205\n", + " 9.54306 16.9399 10.2049 0.0 8.49895\n", + " 5.37196 10.5116 0.98205 8.49895 0.0" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "χ²(specs, NeXLSpectrum.fwhmroi(specs[1], n\"Si K-L3\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5×5 Matrix{Float64}:\n", + " 0.0 5.0342 8.54218 11.8589 13.0329\n", + " 5.0342 0.0 10.0157 7.45117 9.54829\n", + " 8.54218 10.0157 0.0 13.8535 10.2126\n", + " 11.8589 7.45117 13.8535 0.0 15.7439\n", + " 13.0329 9.54829 10.2126 15.7439 0.0" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "χ²(specs, NeXLSpectrum.fwhmroi(specs[1], n\"Fe K-L3\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5×5 Matrix{Float64}:\n", + " 0.0 4.01239 9.69215 17.7303 28.7199\n", + " 4.01239 0.0 7.79901 11.8719 25.8071\n", + " 9.69215 7.79901 0.0 3.34689 6.66464\n", + " 17.7303 11.8719 3.34689 0.0 6.22052\n", + " 28.7199 25.8071 6.66464 6.22052 0.0" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "χ²(specs, NeXLSpectrum.fwhmroi(specs[1], n\"O K-L3\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5×5 Matrix{Float64}:\n", + " 0.0 8.22987 9.94782 2.72169 5.35568\n", + " 8.22987 0.0 11.4878 9.39366 8.36689\n", + " 9.94782 11.4878 0.0 12.1918 13.5127\n", + " 2.72169 9.39366 12.1918 0.0 2.2342\n", + " 5.35568 8.36689 13.5127 2.2342 0.0" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "χ²(specs, NeXLSpectrum.fwhmroi(specs[1], n\"Mg K-L3\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, the `χ²` matrices can be hard to interpret. Which spectrum is the \"problem child\"? What we really want to know is how each spectrum compares with the mean of the others.\n", + "\n", + "We want to retain the spectra that are most similar to the mean. That is what `similarity(...)` is used for." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5-element Vector{Float64}:\n", + " 1.510312985973718\n", + " 1.2388275573468728\n", + " 0.6653228173105655\n", + " 1.3338255027184582\n", + " 2.06757098843039" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "using Statistics\n", + "NeXLSpectrum.similarity(specs, det, n\"O\")" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "

5 rows × 10 columns

SpectrumCOMgAlSiCaFeAllMean
SubStrin…Float64Float64Float64Float64Float64Float64Float64Float64Float64
1IIIE K412[0][all]0.7529441.510311.150271.233541.177511.478570.9891811.042681.18462
2IIIE K412[1][all]0.9918441.238831.020991.150491.811481.0310.9822831.006921.17527
3IIIE K412[2][all]0.6157580.6653231.111391.007691.245611.117641.022641.003390.969437
4IIIE K412[3][all]1.022721.333830.5569860.9519591.626991.194240.9105470.9952851.08533
5IIIE K412[4][all]0.7758552.067570.9515940.8981250.997170.8412831.135821.002521.09535
" + ], + "text/latex": [ + "\\begin{tabular}{r|cccccccccc}\n", + "\t& Spectrum & C & O & Mg & Al & Si & Ca & Fe & All & Mean\\\\\n", + "\t\\hline\n", + "\t& SubStrin… & Float64 & Float64 & Float64 & Float64 & Float64 & Float64 & Float64 & Float64 & Float64\\\\\n", + "\t\\hline\n", + "\t1 & IIIE K412[0][all] & 0.752944 & 1.51031 & 1.15027 & 1.23354 & 1.17751 & 1.47857 & 0.989181 & 1.04268 & 1.18462 \\\\\n", + "\t2 & IIIE K412[1][all] & 0.991844 & 1.23883 & 1.02099 & 1.15049 & 1.81148 & 1.031 & 0.982283 & 1.00692 & 1.17527 \\\\\n", + "\t3 & IIIE K412[2][all] & 0.615758 & 0.665323 & 1.11139 & 1.00769 & 1.24561 & 1.11764 & 1.02264 & 1.00339 & 0.969437 \\\\\n", + "\t4 & IIIE K412[3][all] & 1.02272 & 1.33383 & 0.556986 & 0.951959 & 1.62699 & 1.19424 & 0.910547 & 0.995285 & 1.08533 \\\\\n", + "\t5 & IIIE K412[4][all] & 0.775855 & 2.06757 & 0.951594 & 0.898125 & 0.99717 & 0.841283 & 1.13582 & 1.00252 & 1.09535 \\\\\n", + "\\end{tabular}\n" + ], + "text/plain": [ + "\u001b[1m5×10 DataFrame\u001b[0m\n", + "\u001b[1m Row \u001b[0m│\u001b[1m Spectrum \u001b[0m\u001b[1m C \u001b[0m\u001b[1m O \u001b[0m\u001b[1m Mg \u001b[0m\u001b[1m Al \u001b[0m\u001b[1m Si \u001b[0m\u001b[1m Ca \u001b[0m\u001b[1m Fe \u001b[0m\u001b[1m All \u001b[0m\u001b[1m Mean \u001b[0m\n", + "\u001b[1m \u001b[0m│\u001b[90m SubStrin… \u001b[0m\u001b[90m Float64 \u001b[0m\u001b[90m Float64 \u001b[0m\u001b[90m Float64 \u001b[0m\u001b[90m Float64 \u001b[0m\u001b[90m Float64 \u001b[0m\u001b[90m Float64 \u001b[0m\u001b[90m Float64 \u001b[0m\u001b[90m Float64 \u001b[0m\u001b[90m Float64 \u001b[0m\n", + "─────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────\n", + " 1 │ IIIE K412[0][all] 0.752944 1.51031 1.15027 1.23354 1.17751 1.47857 0.989181 1.04268 1.18462\n", + " 2 │ IIIE K412[1][all] 0.991844 1.23883 1.02099 1.15049 1.81148 1.031 0.982283 1.00692 1.17527\n", + " 3 │ IIIE K412[2][all] 0.615758 0.665323 1.11139 1.00769 1.24561 1.11764 1.02264 1.00339 0.969437\n", + " 4 │ IIIE K412[3][all] 1.02272 1.33383 0.556986 0.951959 1.62699 1.19424 0.910547 0.995285 1.08533\n", + " 5 │ IIIE K412[4][all] 0.775855 2.06757 0.951594 0.898125 0.99717 0.841283 1.13582 1.00252 1.09535" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ENV[\"columns\"]=200\n", + "df=DataFrame( \n", + " :Spectrum=>name.(specs), \n", + " map(elm->Symbol(elm.symbol)=>NeXLSpectrum.similarity(specs, det, elm), elms)...,\n", + " :All => NeXLSpectrum.similarity(specs)\n", + ")\n", + "insertcols!(df, :Mean=>map(r->mean(r[2:end-1]), eachrow(df)))\n", + "using PrettyTables\n", + "open(joinpath(homedir(),\"Desktop\", \"k412compare.tex\"),\"w\") do io\n", + " pretty_table(io, df, backend = Val(:latex), table_type=:tabular, nosubheader=true, \n", + " formatters = ft_printf(\"%6.4f\"), alignment = [ :l, :l, :l, :l, :l, :l, :l, :l, :l, :l ],\n", + " title = raw\"\"\"\n", + " Comparing 5 spectra collected from K412 glass using the \\metric metric over ranges of channels corresponding to the elements in the column headers. The All\n", + " and Mean columns reflect the \\metric metric over \\SI{100}{\\eV} to $E_0$ and the mean of all element metrics, respectively.\"\"\",\n", + " label = \"tbl:k412compare\"\n", + " )\n", + " end\n", + " df" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We expect a bit of variation in O since the soft X-ray is quite susceptible to absorption and topography. \n", + "\n", + "Let's remove spectra 1 and 4 and see what happens." + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(0.8318253422459458, 0.1718201270876415)\n", + "(1.3631719703560008, 0.5052585090220634)\n", + "(0.9582440564960193, 0.2373362361928849)\n", + "(1.0483615688070889, 0.1398559685664196)\n", + "(1.3717523420924753, 0.3363250417770381)\n", + "(1.1325482433073382, 0.23393694358833322)\n", + "(1.0080957263973978, 0.08225871599218175)\n" + ] + } + ], + "source": [ + "using Statistics\n", + "for elm in elms\n", + " println( ( mean(NeXLSpectrum.similarity(specs, det, elm)), std(NeXLSpectrum.similarity(specs, det, elm))) )\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we increase the X-ray energy, the variability decreases." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's try applying these functions to a spectrum that we know should compare well since they represent sub-samplings of the same source.\n", + "\n", + " * `subdivide(...)` takes a single spectrum and distributes the counts at random among N spectra creating N spectra that sums to the original spectrum.\n", + " * `subsample(...)` takes a single spectrum and emulates taking a fraction of the same live-time. The results won't necessarily sum to the original." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "

7 rows × 5 columns

variablemeanstdmaxmin
SymbolFloat64Float64Float64Float64
1C0.8648260.2939481.808620.302374
2O0.8548320.2761741.856210.460516
3Mg0.8535060.2000031.277820.394415
4Al0.914620.2551551.499240.297589
5Si0.9286370.2623361.502860.544608
6Ca0.8649070.1574211.118470.572802
7Fe0.9170720.1164291.188630.746547
" + ], + "text/latex": [ + "\\begin{tabular}{r|ccccc}\n", + "\t& variable & mean & std & max & min\\\\\n", + "\t\\hline\n", + "\t& Symbol & Float64 & Float64 & Float64 & Float64\\\\\n", + "\t\\hline\n", + "\t1 & C & 0.864826 & 0.293948 & 1.80862 & 0.302374 \\\\\n", + "\t2 & O & 0.854832 & 0.276174 & 1.85621 & 0.460516 \\\\\n", + "\t3 & Mg & 0.853506 & 0.200003 & 1.27782 & 0.394415 \\\\\n", + "\t4 & Al & 0.91462 & 0.255155 & 1.49924 & 0.297589 \\\\\n", + "\t5 & Si & 0.928637 & 0.262336 & 1.50286 & 0.544608 \\\\\n", + "\t6 & Ca & 0.864907 & 0.157421 & 1.11847 & 0.572802 \\\\\n", + "\t7 & Fe & 0.917072 & 0.116429 & 1.18863 & 0.746547 \\\\\n", + "\\end{tabular}\n" + ], + "text/plain": [ + "\u001b[1m7×5 DataFrame\u001b[0m\n", + "\u001b[1m Row \u001b[0m│\u001b[1m variable \u001b[0m\u001b[1m mean \u001b[0m\u001b[1m std \u001b[0m\u001b[1m max \u001b[0m\u001b[1m min \u001b[0m\n", + "\u001b[1m \u001b[0m│\u001b[90m Symbol \u001b[0m\u001b[90m Float64 \u001b[0m\u001b[90m Float64 \u001b[0m\u001b[90m Float64 \u001b[0m\u001b[90m Float64 \u001b[0m\n", + "─────┼─────────────────────────────────────────────────\n", + " 1 │ C 0.864826 0.293948 1.80862 0.302374\n", + " 2 │ O 0.854832 0.276174 1.85621 0.460516\n", + " 3 │ Mg 0.853506 0.200003 1.27782 0.394415\n", + " 4 │ Al 0.91462 0.255155 1.49924 0.297589\n", + " 5 │ Si 0.928637 0.262336 1.50286 0.544608\n", + " 6 │ Ca 0.864907 0.157421 1.11847 0.572802\n", + " 7 │ Fe 0.917072 0.116429 1.18863 0.746547" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sd=mapreduce(_->subdivide(specs[1], 8), append!, 1:6)\n", + "describe(DataFrame(\n", + " :Spectrum=>eachindex(sd),\n", + " [ Symbol(symbol(elm))=>NeXLSpectrum.similarity(sd, det, elm) for elm in elms]...\n", + ")[:,2:end], :mean, :std, :max, :min)" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "

7 rows × 5 columns

variablemeanstdmaxmin
SymbolFloat64Float64Float64Float64
1C0.8919880.2827761.528410.262456
2O0.8908370.2861792.019250.371919
3Mg0.8586330.2439941.438750.428418
4Al0.925030.2291241.57710.52459
5Si0.942460.245821.792960.5111
6Ca0.911320.1657081.376740.61856
7Fe0.8961380.121141.171040.618218
" + ], + "text/latex": [ + "\\begin{tabular}{r|ccccc}\n", + "\t& variable & mean & std & max & min\\\\\n", + "\t\\hline\n", + "\t& Symbol & Float64 & Float64 & Float64 & Float64\\\\\n", + "\t\\hline\n", + "\t1 & C & 0.891988 & 0.282776 & 1.52841 & 0.262456 \\\\\n", + "\t2 & O & 0.890837 & 0.286179 & 2.01925 & 0.371919 \\\\\n", + "\t3 & Mg & 0.858633 & 0.243994 & 1.43875 & 0.428418 \\\\\n", + "\t4 & Al & 0.92503 & 0.229124 & 1.5771 & 0.52459 \\\\\n", + "\t5 & Si & 0.94246 & 0.24582 & 1.79296 & 0.5111 \\\\\n", + "\t6 & Ca & 0.91132 & 0.165708 & 1.37674 & 0.61856 \\\\\n", + "\t7 & Fe & 0.896138 & 0.12114 & 1.17104 & 0.618218 \\\\\n", + "\\end{tabular}\n" + ], + "text/plain": [ + "\u001b[1m7×5 DataFrame\u001b[0m\n", + "\u001b[1m Row \u001b[0m│\u001b[1m variable \u001b[0m\u001b[1m mean \u001b[0m\u001b[1m std \u001b[0m\u001b[1m max \u001b[0m\u001b[1m min \u001b[0m\n", + "\u001b[1m \u001b[0m│\u001b[90m Symbol \u001b[0m\u001b[90m Float64 \u001b[0m\u001b[90m Float64 \u001b[0m\u001b[90m Float64 \u001b[0m\u001b[90m Float64 \u001b[0m\n", + "─────┼─────────────────────────────────────────────────\n", + " 1 │ C 0.891988 0.282776 1.52841 0.262456\n", + " 2 │ O 0.890837 0.286179 2.01925 0.371919\n", + " 3 │ Mg 0.858633 0.243994 1.43875 0.428418\n", + " 4 │ Al 0.92503 0.229124 1.5771 0.52459\n", + " 5 │ Si 0.94246 0.24582 1.79296 0.5111\n", + " 6 │ Ca 0.91132 0.165708 1.37674 0.61856\n", + " 7 │ Fe 0.896138 0.12114 1.17104 0.618218" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sd2=mapreduce(_->map(i->subsample(specs[1], 0.1),1:8),append!,1:10)\n", + "describe(DataFrame(\n", + " :Spectrum=>eachindex(sd2),\n", + " [ Symbol(symbol(elm))=>NeXLSpectrum.similarity(sd2, det, elm) for elm in elms]...\n", + ")[:,2:end], :mean, :std, :max, :min)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Interestingly, these are consistently slightly less than unity? Why?" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [], + "source": [ + "using Distributions" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.015166609872030761" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "σ=10.0\n", + "n=Normal(0.0,σ)\n", + "mean(mean((rand(n,15).^2))-σ^2 for i in 1:100000)" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1.4935791914593541" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p1, p2 = Dict(:ProbeCurrent=>1.0, :LiveTime=>10.0),Dict(:ProbeCurrent=>1.0, :LiveTime=>0.99*40.0)\n", + "r = rand(1:10000, 2048)\n", + "d1, d2 = Poisson.(r), Poisson.(4r)\n", + "s1 = Spectrum(det.scale, [ rand(d) for d in d1], p1)\n", + "s2 = Spectrum(det.scale, [ rand(d) for d in d2], p2)\n", + "NeXLSpectrum.similarity(s1,s2,1:2048)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": "\n\n\n \n \n \n\n\n \n \n \n Energy (eV)\n \n \n \n \n \n \n 0\n \n \n \n \n 5000\n \n \n \n \n 10000\n \n \n \n \n 15000\n \n \n \n \n 20000\n \n \n \n \n \n \n \n Spectrum[1193]\n \n \n \n \n Spectrum[1194]\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n Spectra\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n 0\n \n \n \n \n 10000\n \n \n \n \n 20000\n \n \n \n \n 30000\n \n \n \n \n 40000\n \n \n \n \n \n \n Counts\n \n \n \n\n\n \n \n \n\n\n", + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " Energy (eV)\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " -30000\n", + " \n", + " \n", + " \n", + " \n", + " -25000\n", + " \n", + " \n", + " \n", + " \n", + " -20000\n", + " \n", + " \n", + " \n", + " \n", + " -15000\n", + " \n", + " \n", + " \n", + " \n", + " -10000\n", + " \n", + " \n", + " \n", + " \n", + " -5000\n", + " \n", + " \n", + " \n", + " \n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " 5000\n", + " \n", + " \n", + " \n", + " \n", + " 10000\n", + " \n", + " \n", + " \n", + " \n", + " 15000\n", + " \n", + " \n", + " \n", + " \n", + " 20000\n", + " \n", + " \n", + " \n", + " \n", + " 25000\n", + " \n", + " \n", + " \n", + " \n", + " 30000\n", + " \n", + " \n", + " \n", + " \n", + " 35000\n", + " \n", + " \n", + " \n", + " \n", + " 40000\n", + " \n", + " \n", + " \n", + " \n", + " 45000\n", + " \n", + " \n", + " \n", + " \n", + " 50000\n", + " \n", + " \n", + " \n", + " \n", + " 55000\n", + " \n", + " \n", + " \n", + " \n", + " -22000\n", + " \n", + " \n", + " \n", + " \n", + " -20000\n", + " \n", + " \n", + " \n", + " \n", + " -18000\n", + " \n", + " \n", + " \n", + " \n", + " -16000\n", + " \n", + " \n", + " \n", + " \n", + " -14000\n", + " \n", + " \n", + " \n", + " \n", + " -12000\n", + " \n", + " \n", + " \n", + " \n", + " -10000\n", + " \n", + " \n", + " \n", + " \n", + " -8000\n", + " \n", + " \n", + " \n", + " \n", + " -6000\n", + " \n", + " \n", + " \n", + " \n", + " -4000\n", + " \n", + " \n", + " \n", + " \n", + " -2000\n", + " \n", + " \n", + " \n", + " \n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " 2000\n", + " \n", + " \n", + " \n", + " \n", + " 4000\n", + " \n", + " \n", + " \n", + " \n", + " 6000\n", + " \n", + " \n", + " \n", + " \n", + " 8000\n", + " \n", + " \n", + " \n", + " \n", + " 10000\n", + " \n", + " \n", + " \n", + " \n", + " 12000\n", + " \n", + " \n", + " \n", + " \n", + " 14000\n", + " \n", + " \n", + " \n", + " \n", + " 16000\n", + " \n", + " \n", + " \n", + " \n", + " 18000\n", + " \n", + " \n", + " \n", + " \n", + " 20000\n", + " \n", + " \n", + " \n", + " \n", + " 22000\n", + " \n", + " \n", + " \n", + " \n", + " 24000\n", + " \n", + " \n", + " \n", + " \n", + " 26000\n", + " \n", + " \n", + " \n", + " \n", + " 28000\n", + " \n", + " \n", + " \n", + " \n", + " 30000\n", + " \n", + " \n", + " \n", + " \n", + " 32000\n", + " \n", + " \n", + " \n", + " \n", + " 34000\n", + " \n", + " \n", + " \n", + " \n", + " 36000\n", + " \n", + " \n", + " \n", + " \n", + " 38000\n", + " \n", + " \n", + " \n", + " \n", + " 40000\n", + " \n", + " \n", + " \n", + " \n", + " 42000\n", + " \n", + " \n", + " \n", + " \n", + " -25000\n", + " \n", + " \n", + " \n", + " \n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " 25000\n", + " \n", + " \n", + " \n", + " \n", + " 50000\n", + " \n", + " \n", + " \n", + " \n", + " -21000\n", + " \n", + " \n", + " \n", + " \n", + " -20000\n", + " \n", + " \n", + " \n", + " \n", + " -19000\n", + " \n", + " \n", + " \n", + " \n", + " -18000\n", + " \n", + " \n", + " \n", + " \n", + " -17000\n", + " \n", + " \n", + " \n", + " \n", + " -16000\n", + " \n", + " \n", + " \n", + " \n", + " -15000\n", + " \n", + " \n", + " \n", + " \n", + " -14000\n", + " \n", + " \n", + " \n", + " \n", + " -13000\n", + " \n", + " \n", + " \n", + " \n", + " -12000\n", + " \n", + " \n", + " \n", + " \n", + " -11000\n", + " \n", + " \n", + " \n", + " \n", + " -10000\n", + " \n", + " \n", + " \n", + " \n", + " -9000\n", + " \n", + " \n", + " \n", + " \n", + " -8000\n", + " \n", + " \n", + " \n", + " \n", + " -7000\n", + " \n", + " \n", + " \n", + " \n", + " -6000\n", + " \n", + " \n", + " \n", + " \n", + " -5000\n", + " \n", + " \n", + " \n", + " \n", + " -4000\n", + " \n", + " \n", + " \n", + " \n", + " -3000\n", + " \n", + " \n", + " \n", + " \n", + " -2000\n", + " \n", + " \n", + " \n", + " \n", + " -1000\n", + " \n", + " \n", + " \n", + " \n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " 1000\n", + " \n", + " \n", + " \n", + " \n", + " 2000\n", + " \n", + " \n", + " \n", + " \n", + " 3000\n", + " \n", + " \n", + " \n", + " \n", + " 4000\n", + " \n", + " \n", + " \n", + " \n", + " 5000\n", + " \n", + " \n", + " \n", + " \n", + " 6000\n", + " \n", + " \n", + " \n", + " \n", + " 7000\n", + " \n", + " \n", + " \n", + " \n", + " 8000\n", + " \n", + " \n", + " \n", + " \n", + " 9000\n", + " \n", + " \n", + " \n", + " \n", + " 10000\n", + " \n", + " \n", + " \n", + " \n", + " 11000\n", + " \n", + " \n", + " \n", + " \n", + " 12000\n", + " \n", + " \n", + " \n", + " \n", + " 13000\n", + " \n", + " \n", + " \n", + " \n", + " 14000\n", + " \n", + " \n", + " \n", + " \n", + " 15000\n", + " \n", + " \n", + " \n", + " \n", + " 16000\n", + " \n", + " \n", + " \n", + " \n", + " 17000\n", + " \n", + " \n", + " \n", + " \n", + " 18000\n", + " \n", + " \n", + " \n", + " \n", + " 19000\n", + " \n", + " \n", + " \n", + " \n", + " 20000\n", + " \n", + " \n", + " \n", + " \n", + " 21000\n", + " \n", + " \n", + " \n", + " \n", + " 22000\n", + " \n", + " \n", + " \n", + " \n", + " 23000\n", + " \n", + " \n", + " \n", + " \n", + " 24000\n", + " \n", + " \n", + " \n", + " \n", + " 25000\n", + " \n", + " \n", + " \n", + " \n", + " 26000\n", + " \n", + " \n", + " \n", + " \n", + " 27000\n", + " \n", + " \n", + " \n", + " \n", + " 28000\n", + " \n", + " \n", + " \n", + " \n", + " 29000\n", + " \n", + " \n", + " \n", + " \n", + " 30000\n", + " \n", + " \n", + " \n", + " \n", + " 31000\n", + " \n", + " \n", + " \n", + " \n", + " 32000\n", + " \n", + " \n", + " \n", + " \n", + " 33000\n", + " \n", + " \n", + " \n", + " \n", + " 34000\n", + " \n", + " \n", + " \n", + " \n", + " 35000\n", + " \n", + " \n", + " \n", + " \n", + " 36000\n", + " \n", + " \n", + " \n", + " \n", + " 37000\n", + " \n", + " \n", + " \n", + " \n", + " 38000\n", + " \n", + " \n", + " \n", + " \n", + " 39000\n", + " \n", + " \n", + " \n", + " \n", + " 40000\n", + " \n", + " \n", + " \n", + " \n", + " 41000\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " Spectrum[1193]\n", + " \n", + " \n", + " \n", + " \n", + " Spectrum[1194]\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " Spectra\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " h,j,k,l,arrows,drag to pan\n", + " \n", + " \n", + " \n", + " \n", + " i,o,+,-,scroll,shift-drag to zoom\n", + " \n", + " \n", + " \n", + " \n", + " r,dbl-click to reset\n", + " \n", + " \n", + " \n", + " \n", + " c for coordinates\n", + " \n", + " \n", + " \n", + " \n", + " ? for help\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " ?\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " -60000\n", + " \n", + " \n", + " \n", + " \n", + " -50000\n", + " \n", + " \n", + " \n", + " \n", + " -40000\n", + " \n", + " \n", + " \n", + " \n", + " -30000\n", + " \n", + " \n", + " \n", + " \n", + " -20000\n", + " \n", + " \n", + " \n", + " \n", + " -10000\n", + " \n", + " \n", + " \n", + " \n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " 10000\n", + " \n", + " \n", + " \n", + " \n", + " 20000\n", + " \n", + " \n", + " \n", + " \n", + " 30000\n", + " \n", + " \n", + " \n", + " \n", + " 40000\n", + " \n", + " \n", + " \n", + " \n", + " 50000\n", + " \n", + " \n", + " \n", + " \n", + " 60000\n", + " \n", + " \n", + " \n", + " \n", + " 70000\n", + " \n", + " \n", + " \n", + " \n", + " 80000\n", + " \n", + " \n", + " \n", + " \n", + " 90000\n", + " \n", + " \n", + " \n", + " \n", + " 100000\n", + " \n", + " \n", + " \n", + " \n", + " 110000\n", + " \n", + " \n", + " \n", + " \n", + " -45000\n", + " \n", + " \n", + " \n", + " \n", + " -40000\n", + " \n", + " \n", + " \n", + " \n", + " -35000\n", + " \n", + " \n", + " \n", + " \n", + " -30000\n", + " \n", + " \n", + " \n", + " \n", + " -25000\n", + " \n", + " \n", + " \n", + " \n", + " -20000\n", + " \n", + " \n", + " \n", + " \n", + " -15000\n", + " \n", + " \n", + " \n", + " \n", + " -10000\n", + " \n", + " \n", + " \n", + " \n", + " -5000\n", + " \n", + " \n", + " \n", + " \n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " 5000\n", + " \n", + " \n", + " \n", + " \n", + " 10000\n", + " \n", + " \n", + " \n", + " \n", + " 15000\n", + " \n", + " \n", + " \n", + " \n", + " 20000\n", + " \n", + " \n", + " \n", + " \n", + " 25000\n", + " \n", + " \n", + " \n", + " \n", + " 30000\n", + " \n", + " \n", + " \n", + " \n", + " 35000\n", + " \n", + " \n", + " \n", + " \n", + " 40000\n", + " \n", + " \n", + " \n", + " \n", + " 45000\n", + " \n", + " \n", + " \n", + " \n", + " 50000\n", + " \n", + " \n", + " \n", + " \n", + " 55000\n", + " \n", + " \n", + " \n", + " \n", + " 60000\n", + " \n", + " \n", + " \n", + " \n", + " 65000\n", + " \n", + " \n", + " \n", + " \n", + " 70000\n", + " \n", + " \n", + " \n", + " \n", + " 75000\n", + " \n", + " \n", + " \n", + " \n", + " 80000\n", + " \n", + " \n", + " \n", + " \n", + " 85000\n", + " \n", + " \n", + " \n", + " \n", + " -50000\n", + " \n", + " \n", + " \n", + " \n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " 50000\n", + " \n", + " \n", + " \n", + " \n", + " 100000\n", + " \n", + " \n", + " \n", + " \n", + " -44000\n", + " \n", + " \n", + " \n", + " \n", + " -42000\n", + " \n", + " \n", + " \n", + " \n", + " -40000\n", + " \n", + " \n", + " \n", + " \n", + " -38000\n", + " \n", + " \n", + " \n", + " \n", + " -36000\n", + " \n", + " \n", + " \n", + " \n", + " -34000\n", + " \n", + " \n", + " \n", + " \n", + " -32000\n", + " \n", + " \n", + " \n", + " \n", + " -30000\n", + " \n", + " \n", + " \n", + " \n", + " -28000\n", + " \n", + " \n", + " \n", + " \n", + " -26000\n", + " \n", + " \n", + " \n", + " \n", + " -24000\n", + " \n", + " \n", + " \n", + " \n", + " -22000\n", + " \n", + " \n", + " \n", + " \n", + " -20000\n", + " \n", + " \n", + " \n", + " \n", + " -18000\n", + " \n", + " \n", + " \n", + " \n", + " -16000\n", + " \n", + " \n", + " \n", + " \n", + " -14000\n", + " \n", + " \n", + " \n", + " \n", + " -12000\n", + " \n", + " \n", + " \n", + " \n", + " -10000\n", + " \n", + " \n", + " \n", + " \n", + " -8000\n", + " \n", + " \n", + " \n", + " \n", + " -6000\n", + " \n", + " \n", + " \n", + " \n", + " -4000\n", + " \n", + " \n", + " \n", + " \n", + " -2000\n", + " \n", + " \n", + " \n", + " \n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " 2000\n", + " \n", + " \n", + " \n", + " \n", + " 4000\n", + " \n", + " \n", + " \n", + " \n", + " 6000\n", + " \n", + " \n", + " \n", + " \n", + " 8000\n", + " \n", + " \n", + " \n", + " \n", + " 10000\n", + " \n", + " \n", + " \n", + " \n", + " 12000\n", + " \n", + " \n", + " \n", + " \n", + " 14000\n", + " \n", + " \n", + " \n", + " \n", + " 16000\n", + " \n", + " \n", + " \n", + " \n", + " 18000\n", + " \n", + " \n", + " \n", + " \n", + " 20000\n", + " \n", + " \n", + " \n", + " \n", + " 22000\n", + " \n", + " \n", + " \n", + " \n", + " 24000\n", + " \n", + " \n", + " \n", + " \n", + " 26000\n", + " \n", + " \n", + " \n", + " \n", + " 28000\n", + " \n", + " \n", + " \n", + " \n", + " 30000\n", + " \n", + " \n", + " \n", + " \n", + " 32000\n", + " \n", + " \n", + " \n", + " \n", + " 34000\n", + " \n", + " \n", + " \n", + " \n", + " 36000\n", + " \n", + " \n", + " \n", + " \n", + " 38000\n", + " \n", + " \n", + " \n", + " \n", + " 40000\n", + " \n", + " \n", + " \n", + " \n", + " 42000\n", + " \n", + " \n", + " \n", + " \n", + " 44000\n", + " \n", + " \n", + " \n", + " \n", + " 46000\n", + " \n", + " \n", + " \n", + " \n", + " 48000\n", + " \n", + " \n", + " \n", + " \n", + " 50000\n", + " \n", + " \n", + " \n", + " \n", + " 52000\n", + " \n", + " \n", + " \n", + " \n", + " 54000\n", + " \n", + " \n", + " \n", + " \n", + " 56000\n", + " \n", + " \n", + " \n", + " \n", + " 58000\n", + " \n", + " \n", + " \n", + " \n", + " 60000\n", + " \n", + " \n", + " \n", + " \n", + " 62000\n", + " \n", + " \n", + " \n", + " \n", + " 64000\n", + " \n", + " \n", + " \n", + " \n", + " 66000\n", + " \n", + " \n", + " \n", + " \n", + " 68000\n", + " \n", + " \n", + " \n", + " \n", + " 70000\n", + " \n", + " \n", + " \n", + " \n", + " 72000\n", + " \n", + " \n", + " \n", + " \n", + " 74000\n", + " \n", + " \n", + " \n", + " \n", + " 76000\n", + " \n", + " \n", + " \n", + " \n", + " 78000\n", + " \n", + " \n", + " \n", + " \n", + " 80000\n", + " \n", + " \n", + " \n", + " \n", + " 82000\n", + " \n", + " \n", + " \n", + " \n", + " 84000\n", + " \n", + " \n", + " \n", + " \n", + " 86000\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " Counts\n", + " \n", + " \n", + " \n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "Plot(...)" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "plot(s1,s2)" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1.0039067304532736" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p1, p2 = Dict(:ProbeCurrent=>1.0, :LiveTime=>10.0),Dict(:ProbeCurrent=>1.0, :LiveTime=>40.0)\n", + "mean(map(1:1000) do i\n", + " r = rand(1:100, 2048)\n", + " d1, d2 = Poisson.(r), Poisson.(4r)\n", + " s1 = Spectrum(det.scale, [ rand(d) for d in d1], p1)\n", + " s2 = Spectrum(det.scale, [ rand(d) for d in d2], p2)\n", + " NeXLSpectrum.similarity(s1, s2, 10:20)\n", + "end)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": "\n\n\n \n \n \n\n\n \n \n \n Energy (eV)\n \n \n \n \n \n \n 17000\n \n \n \n \n 18000\n \n \n \n \n 19000\n \n \n \n \n 20000\n \n \n \n \n \n \n \n IIIE K412[0][all]\n \n \n \n \n \n \n \n \n \n \n \n \n \n Spectrum\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n 0\n \n \n \n \n 10\n \n \n \n \n 20\n \n \n \n \n 30\n \n \n \n \n 40\n \n \n \n \n 50\n \n \n \n \n \n \n Counts\n \n \n \n\n\n \n \n \n\n\n", + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " Energy (eV)\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " 13000\n", + " \n", + " \n", + " \n", + " \n", + " 14000\n", + " \n", + " \n", + " \n", + " \n", + " 15000\n", + " \n", + " \n", + " \n", + " \n", + " 16000\n", + " \n", + " \n", + " \n", + " \n", + " 17000\n", + " \n", + " \n", + " \n", + " \n", + " 18000\n", + " \n", + " \n", + " \n", + " \n", + " 19000\n", + " \n", + " \n", + " \n", + " \n", + " 20000\n", + " \n", + " \n", + " \n", + " \n", + " 21000\n", + " \n", + " \n", + " \n", + " \n", + " 22000\n", + " \n", + " \n", + " \n", + " \n", + " 23000\n", + " \n", + " \n", + " \n", + " \n", + " 24000\n", + " \n", + " \n", + " \n", + " \n", + " 14000\n", + " \n", + " \n", + " \n", + " \n", + " 14200\n", + " \n", + " \n", + " \n", + " \n", + " 14400\n", + " \n", + " \n", + " \n", + " \n", + " 14600\n", + " \n", + " \n", + " \n", + " \n", + " 14800\n", + " \n", + " \n", + " \n", + " \n", + " 15000\n", + " \n", + " \n", + " \n", + " \n", + " 15200\n", + " \n", + " \n", + " \n", + " \n", + " 15400\n", + " \n", + " \n", + " \n", + " \n", + " 15600\n", + " \n", + " \n", + " \n", + " \n", + " 15800\n", + " \n", + " \n", + " \n", + " \n", + " 16000\n", + " \n", + " \n", + " \n", + " \n", + " 16200\n", + " \n", + " \n", + " \n", + " \n", + " 16400\n", + " \n", + " \n", + " \n", + " \n", + " 16600\n", + " \n", + " \n", + " \n", + " \n", + " 16800\n", + " \n", + " \n", + " \n", + " \n", + " 17000\n", + " \n", + " \n", + " \n", + " \n", + " 17200\n", + " \n", + " \n", + " \n", + " \n", + " 17400\n", + " \n", + " \n", + " \n", + " \n", + " 17600\n", + " \n", + " \n", + " \n", + " \n", + " 17800\n", + " \n", + " \n", + " \n", + " \n", + " 18000\n", + " \n", + " \n", + " \n", + " \n", + " 18200\n", + " \n", + " \n", + " \n", + " \n", + " 18400\n", + " \n", + " \n", + " \n", + " \n", + " 18600\n", + " \n", + " \n", + " \n", + " \n", + " 18800\n", + " \n", + " \n", + " \n", + " \n", + " 19000\n", + " \n", + " \n", + " \n", + " \n", + " 19200\n", + " \n", + " \n", + " \n", + " \n", + " 19400\n", + " \n", + " \n", + " \n", + " \n", + " 19600\n", + " \n", + " \n", + " \n", + " \n", + " 19800\n", + " \n", + " \n", + " \n", + " \n", + " 20000\n", + " \n", + " \n", + " \n", + " \n", + " 20200\n", + " \n", + " \n", + " \n", + " \n", + " 20400\n", + " \n", + " \n", + " \n", + " \n", + " 20600\n", + " \n", + " \n", + " \n", + " \n", + " 20800\n", + " \n", + " \n", + " \n", + " \n", + " 21000\n", + " \n", + " \n", + " \n", + " \n", + " 21200\n", + " \n", + " \n", + " \n", + " \n", + " 21400\n", + " \n", + " \n", + " \n", + " \n", + " 21600\n", + " \n", + " \n", + " \n", + " \n", + " 21800\n", + " \n", + " \n", + " \n", + " \n", + " 22000\n", + " \n", + " \n", + " \n", + " \n", + " 22200\n", + " \n", + " \n", + " \n", + " \n", + " 22400\n", + " \n", + " \n", + " \n", + " \n", + " 22600\n", + " \n", + " \n", + " \n", + " \n", + " 22800\n", + " \n", + " \n", + " \n", + " \n", + " 23000\n", + " \n", + " \n", + " \n", + " \n", + " 10000\n", + " \n", + " \n", + " \n", + " \n", + " 15000\n", + " \n", + " \n", + " \n", + " \n", + " 20000\n", + " \n", + " \n", + " \n", + " \n", + " 25000\n", + " \n", + " \n", + " \n", + " \n", + " 14000\n", + " \n", + " \n", + " \n", + " \n", + " 14100\n", + " \n", + " \n", + " \n", + " \n", + " 14200\n", + " \n", + " \n", + " \n", + " \n", + " 14300\n", + " \n", + " \n", + " \n", + " \n", + " 14400\n", + " \n", + " \n", + " \n", + " \n", + " 14500\n", + " \n", + " \n", + " \n", + " \n", + " 14600\n", + " \n", + " \n", + " \n", + " \n", + " 14700\n", + " \n", + " \n", + " \n", + " \n", + " 14800\n", + " \n", + " \n", + " \n", + " \n", + " 14900\n", + " \n", + " \n", + " \n", + " \n", + " 15000\n", + " \n", + " \n", + " \n", + " \n", + " 15100\n", + " \n", + " \n", + " \n", + " \n", + " 15200\n", + " \n", + " \n", + " \n", + " \n", + " 15300\n", + " \n", + " \n", + " \n", + " \n", + " 15400\n", + " \n", + " \n", + " \n", + " \n", + " 15500\n", + " \n", + " \n", + " \n", + " \n", + " 15600\n", + " \n", + " \n", + " \n", + " \n", + " 15700\n", + " \n", + " \n", + " \n", + " \n", + " 15800\n", + " \n", + " \n", + " \n", + " \n", + " 15900\n", + " \n", + " \n", + " \n", + " \n", + " 16000\n", + " \n", + " \n", + " \n", + " \n", + " 16100\n", + " \n", + " \n", + " \n", + " \n", + " 16200\n", + " \n", + " \n", + " \n", + " \n", + " 16300\n", + " \n", + " \n", + " \n", + " \n", + " 16400\n", + " \n", + " \n", + " \n", + " \n", + " 16500\n", + " \n", + " \n", + " \n", + " \n", + " 16600\n", + " \n", + " \n", + " \n", + " \n", + " 16700\n", + " \n", + " \n", + " \n", + " \n", + " 16800\n", + " \n", + " \n", + " \n", + " \n", + " 16900\n", + " \n", + " \n", + " \n", + " \n", + " 17000\n", + " \n", + " \n", + " \n", + " \n", + " 17100\n", + " \n", + " \n", + " \n", + " \n", + " 17200\n", + " \n", + " \n", + " \n", + " \n", + " 17300\n", + " \n", + " \n", + " \n", + " \n", + " 17400\n", + " \n", + " \n", + " \n", + " \n", + " 17500\n", + " \n", + " \n", + " \n", + " \n", + " 17600\n", + " \n", + " \n", + " \n", + " \n", + " 17700\n", + " \n", + " \n", + " \n", + " \n", + " 17800\n", + " \n", + " \n", + " \n", + " \n", + " 17900\n", + " \n", + " \n", + " \n", + " \n", + " 18000\n", + " \n", + " \n", + " \n", + " \n", + " 18100\n", + " \n", + " \n", + " \n", + " \n", + " 18200\n", + " \n", + " \n", + " \n", + " \n", + " 18300\n", + " \n", + " \n", + " \n", + " \n", + " 18400\n", + " \n", + " \n", + " \n", + " \n", + " 18500\n", + " \n", + " \n", + " \n", + " \n", + " 18600\n", + " \n", + " \n", + " \n", + " \n", + " 18700\n", + " \n", + " \n", + " \n", + " \n", + " 18800\n", + " \n", + " \n", + " \n", + " \n", + " 18900\n", + " \n", + " \n", + " \n", + " \n", + " 19000\n", + " \n", + " \n", + " \n", + " \n", + " 19100\n", + " \n", + " \n", + " \n", + " \n", + " 19200\n", + " \n", + " \n", + " \n", + " \n", + " 19300\n", + " \n", + " \n", + " \n", + " \n", + " 19400\n", + " \n", + " \n", + " \n", + " \n", + " 19500\n", + " \n", + " \n", + " \n", + " \n", + " 19600\n", + " \n", + " \n", + " \n", + " \n", + " 19700\n", + " \n", + " \n", + " \n", + " \n", + " 19800\n", + " \n", + " \n", + " \n", + " \n", + " 19900\n", + " \n", + " \n", + " \n", + " \n", + " 20000\n", + " \n", + " \n", + " \n", + " \n", + " 20100\n", + " \n", + " \n", + " \n", + " \n", + " 20200\n", + " \n", + " \n", + " \n", + " \n", + " 20300\n", + " \n", + " \n", + " \n", + " \n", + " 20400\n", + " \n", + " \n", + " \n", + " \n", + " 20500\n", + " \n", + " \n", + " \n", + " \n", + " 20600\n", + " \n", + " \n", + " \n", + " \n", + " 20700\n", + " \n", + " \n", + " \n", + " \n", + " 20800\n", + " \n", + " \n", + " \n", + " \n", + " 20900\n", + " \n", + " \n", + " \n", + " \n", + " 21000\n", + " \n", + " \n", + " \n", + " \n", + " 21100\n", + " \n", + " \n", + " \n", + " \n", + " 21200\n", + " \n", + " \n", + " \n", + " \n", + " 21300\n", + " \n", + " \n", + " \n", + " \n", + " 21400\n", + " \n", + " \n", + " \n", + " \n", + " 21500\n", + " \n", + " \n", + " \n", + " \n", + " 21600\n", + " \n", + " \n", + " \n", + " \n", + " 21700\n", + " \n", + " \n", + " \n", + " \n", + " 21800\n", + " \n", + " \n", + " \n", + " \n", + " 21900\n", + " \n", + " \n", + " \n", + " \n", + " 22000\n", + " \n", + " \n", + " \n", + " \n", + " 22100\n", + " \n", + " \n", + " \n", + " \n", + " 22200\n", + " \n", + " \n", + " \n", + " \n", + " 22300\n", + " \n", + " \n", + " \n", + " \n", + " 22400\n", + " \n", + " \n", + " \n", + " \n", + " 22500\n", + " \n", + " \n", + " \n", + " \n", + " 22600\n", + " \n", + " \n", + " \n", + " \n", + " 22700\n", + " \n", + " \n", + " \n", + " \n", + " 22800\n", + " \n", + " \n", + " \n", + " \n", + " 22900\n", + " \n", + " \n", + " \n", + " \n", + " 23000\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " IIIE K412[0][all]\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " Spectrum\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " h,j,k,l,arrows,drag to pan\n", + " \n", + " \n", + " \n", + " \n", + " i,o,+,-,scroll,shift-drag to zoom\n", + " \n", + " \n", + " \n", + " \n", + " r,dbl-click to reset\n", + " \n", + " \n", + " \n", + " \n", + " c for coordinates\n", + " \n", + " \n", + " \n", + " \n", + " ? for help\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " ?\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " -70\n", + " \n", + " \n", + " \n", + " \n", + " -60\n", + " \n", + " \n", + " \n", + " \n", + " -50\n", + " \n", + " \n", + " \n", + " \n", + " -40\n", + " \n", + " \n", + " \n", + " \n", + " -30\n", + " \n", + " \n", + " \n", + " \n", + " -20\n", + " \n", + " \n", + " \n", + " \n", + " -10\n", + " \n", + " \n", + " \n", + " \n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " 10\n", + " \n", + " \n", + " \n", + " \n", + " 20\n", + " \n", + " \n", + " \n", + " \n", + " 30\n", + " \n", + " \n", + " \n", + " \n", + " 40\n", + " \n", + " \n", + " \n", + " \n", + " 50\n", + " \n", + " \n", + " \n", + " \n", + " 60\n", + " \n", + " \n", + " \n", + " \n", + " 70\n", + " \n", + " \n", + " \n", + " \n", + " 80\n", + " \n", + " \n", + " \n", + " \n", + " 90\n", + " \n", + " \n", + " \n", + " \n", + " 100\n", + " \n", + " \n", + " \n", + " \n", + " 110\n", + " \n", + " \n", + " \n", + " \n", + " 120\n", + " \n", + " \n", + " \n", + " \n", + " 130\n", + " \n", + " \n", + " \n", + " \n", + " -55\n", + " \n", + " \n", + " \n", + " \n", + " -50\n", + " \n", + " \n", + " \n", + " \n", + " -45\n", + " \n", + " \n", + " \n", + " \n", + " -40\n", + " \n", + " \n", + " \n", + " \n", + " -35\n", + " \n", + " \n", + " \n", + " \n", + " -30\n", + " \n", + " \n", + " \n", + " \n", + " -25\n", + " \n", + " \n", + " \n", + " \n", + " -20\n", + " \n", + " \n", + " \n", + " \n", + " -15\n", + " \n", + " \n", + " \n", + " \n", + " -10\n", + " \n", + " \n", + " \n", + " \n", + " -5\n", + " \n", + " \n", + " \n", + " \n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " 5\n", + " \n", + " \n", + " \n", + " \n", + " 10\n", + " \n", + " \n", + " \n", + " \n", + " 15\n", + " \n", + " \n", + " \n", + " \n", + " 20\n", + " \n", + " \n", + " \n", + " \n", + " 25\n", + " \n", + " \n", + " \n", + " \n", + " 30\n", + " \n", + " \n", + " \n", + " \n", + " 35\n", + " \n", + " \n", + " \n", + " \n", + " 40\n", + " \n", + " \n", + " \n", + " \n", + " 45\n", + " \n", + " \n", + " \n", + " \n", + " 50\n", + " \n", + " \n", + " \n", + " \n", + " 55\n", + " \n", + " \n", + " \n", + " \n", + " 60\n", + " \n", + " \n", + " \n", + " \n", + " 65\n", + " \n", + " \n", + " \n", + " \n", + " 70\n", + " \n", + " \n", + " \n", + " \n", + " 75\n", + " \n", + " \n", + " \n", + " \n", + " 80\n", + " \n", + " \n", + " \n", + " \n", + " 85\n", + " \n", + " \n", + " \n", + " \n", + " 90\n", + " \n", + " \n", + " \n", + " \n", + " 95\n", + " \n", + " \n", + " \n", + " \n", + " 100\n", + " \n", + " \n", + " \n", + " \n", + " 105\n", + " \n", + " \n", + " \n", + " \n", + " 110\n", + " \n", + " \n", + " \n", + " \n", + " -100\n", + " \n", + " \n", + " \n", + " \n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " 100\n", + " \n", + " \n", + " \n", + " \n", + " 200\n", + " \n", + " \n", + " \n", + " \n", + " -56\n", + " \n", + " \n", + " \n", + " \n", + " -54\n", + " \n", + " \n", + " \n", + " \n", + " -52\n", + " \n", + " \n", + " \n", + " \n", + " -50\n", + " \n", + " \n", + " \n", + " \n", + " -48\n", + " \n", + " \n", + " \n", + " \n", + " -46\n", + " \n", + " \n", + " \n", + " \n", + " -44\n", + " \n", + " \n", + " \n", + " \n", + " -42\n", + " \n", + " \n", + " \n", + " \n", + " -40\n", + " \n", + " \n", + " \n", + " \n", + " -38\n", + " \n", + " \n", + " \n", + " \n", + " -36\n", + " \n", + " \n", + " \n", + " \n", + " -34\n", + " \n", + " \n", + " \n", + " \n", + " -32\n", + " \n", + " \n", + " \n", + " \n", + " -30\n", + " \n", + " \n", + " \n", + " \n", + " -28\n", + " \n", + " \n", + " \n", + " \n", + " -26\n", + " \n", + " \n", + " \n", + " \n", + " -24\n", + " \n", + " \n", + " \n", + " \n", + " -22\n", + " \n", + " \n", + " \n", + " \n", + " -20\n", + " \n", + " \n", + " \n", + " \n", + " -18\n", + " \n", + " \n", + " \n", + " \n", + " -16\n", + " \n", + " \n", + " \n", + " \n", + " -14\n", + " \n", + " \n", + " \n", + " \n", + " -12\n", + " \n", + " \n", + " \n", + " \n", + " -10\n", + " \n", + " \n", + " \n", + " \n", + " -8\n", + " \n", + " \n", + " \n", + " \n", + " -6\n", + " \n", + " \n", + " \n", + " \n", + " -4\n", + " \n", + " \n", + " \n", + " \n", + " -2\n", + " \n", + " \n", + " \n", + " \n", + " 0\n", + " \n", + " \n", + " \n", + " \n", + " 2\n", + " \n", + " \n", + " \n", + " \n", + " 4\n", + " \n", + " \n", + " \n", + " \n", + " 6\n", + " \n", + " \n", + " \n", + " \n", + " 8\n", + " \n", + " \n", + " \n", + " \n", + " 10\n", + " \n", + " \n", + " \n", + " \n", + " 12\n", + " \n", + " \n", + " \n", + " \n", + " 14\n", + " \n", + " \n", + " \n", + " \n", + " 16\n", + " \n", + " \n", + " \n", + " \n", + " 18\n", + " \n", + " \n", + " \n", + " \n", + " 20\n", + " \n", + " \n", + " \n", + " \n", + " 22\n", + " \n", + " \n", + " \n", + " \n", + " 24\n", + " \n", + " \n", + " \n", + " \n", + " 26\n", + " \n", + " \n", + " \n", + " \n", + " 28\n", + " \n", + " \n", + " \n", + " \n", + " 30\n", + " \n", + " \n", + " \n", + " \n", + " 32\n", + " \n", + " \n", + " \n", + " \n", + " 34\n", + " \n", + " \n", + " \n", + " \n", + " 36\n", + " \n", + " \n", + " \n", + " \n", + " 38\n", + " \n", + " \n", + " \n", + " \n", + " 40\n", + " \n", + " \n", + " \n", + " \n", + " 42\n", + " \n", + " \n", + " \n", + " \n", + " 44\n", + " \n", + " \n", + " \n", + " \n", + " 46\n", + " \n", + " \n", + " \n", + " \n", + " 48\n", + " \n", + " \n", + " \n", + " \n", + " 50\n", + " \n", + " \n", + " \n", + " \n", + " 52\n", + " \n", + " \n", + " \n", + " \n", + " 54\n", + " \n", + " \n", + " \n", + " \n", + " 56\n", + " \n", + " \n", + " \n", + " \n", + " 58\n", + " \n", + " \n", + " \n", + " \n", + " 60\n", + " \n", + " \n", + " \n", + " \n", + " 62\n", + " \n", + " \n", + " \n", + " \n", + " 64\n", + " \n", + " \n", + " \n", + " \n", + " 66\n", + " \n", + " \n", + " \n", + " \n", + " 68\n", + " \n", + " \n", + " \n", + " \n", + " 70\n", + " \n", + " \n", + " \n", + " \n", + " 72\n", + " \n", + " \n", + " \n", + " \n", + " 74\n", + " \n", + " \n", + " \n", + " \n", + " 76\n", + " \n", + " \n", + " \n", + " \n", + " 78\n", + " \n", + " \n", + " \n", + " \n", + " 80\n", + " \n", + " \n", + " \n", + " \n", + " 82\n", + " \n", + " \n", + " \n", + " \n", + " 84\n", + " \n", + " \n", + " \n", + " \n", + " 86\n", + " \n", + " \n", + " \n", + " \n", + " 88\n", + " \n", + " \n", + " \n", + " \n", + " 90\n", + " \n", + " \n", + " \n", + " \n", + " 92\n", + " \n", + " \n", + " \n", + " \n", + " 94\n", + " \n", + " \n", + " \n", + " \n", + " 96\n", + " \n", + " \n", + " \n", + " \n", + " 98\n", + " \n", + " \n", + " \n", + " \n", + " 100\n", + " \n", + " \n", + " \n", + " \n", + " 102\n", + " \n", + " \n", + " \n", + " \n", + " 104\n", + " \n", + " \n", + " \n", + " \n", + " 106\n", + " \n", + " \n", + " \n", + " \n", + " 108\n", + " \n", + " \n", + " \n", + " \n", + " 110\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " Counts\n", + " \n", + " \n", + " \n", + "\n", + "\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "Plot(...)" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "p=plot(specs[1],duanehunt=true, xmin=17000.0)\n", + "#p |> SVG(raw\"C:\\users\\nritchie\\Desktop\\duane_hunt.svg\", 6inch, 4inch)\n", + "p" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "19972.88154579493" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "duane_hunt(specs[1])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Julia (4 threads) 1.7.1", + "language": "julia", + "name": "julia-(4-threads)-1.7" + }, + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.7.1" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/src/NeXLSpectrum.jl b/src/NeXLSpectrum.jl index 07d7934..fa0f360 100644 --- a/src/NeXLSpectrum.jl +++ b/src/NeXLSpectrum.jl @@ -13,7 +13,7 @@ using Unitful: mm using FileIO using Mmap using EzXML: readxml -using Polynomials: ImmutablePolynomial, fit, printpoly, roots, derivative, coeffs +using Polynomials: ImmutablePolynomial, fit, printpoly, roots, derivative, coeffs, Polynomials using LinearAlgebra using LoopVectorization using Statistics @@ -55,24 +55,22 @@ export extents # Determine the energy extents of many x-ray lines on a detector export matching # Build a detector to match a spectrum export matches # Do the spectrum and detector match export lld # Low-level discriminator in eV -export detectorresponse # Build a matrix that describes the detectors response to X-rays export gaussianwidth, fwhm # X-ray window models include("window.jl") # primary function transmission(wnd, energy, angle) -export AbstractWindow, LayerWindow, TabulatedWindow -export AP33Model, AP5Model, AP33Tabulation, AP5Tabulation # Moxtek windows -export Beryllium # Classic windows -export AmptekC1, AmptekC2 # Amptek windows -export NoWindow # 100% transmission +export AbstractWindow, ModeledWindow, TabulatedWindow, NoWindow +export WindowType, MoxtekAP33, MoxtekAP5, AmetekC1, AmetekC2, AmptekC1, AmptekC2, BerylliumWindow include("detefficiency.jl") export DetectorEfficiency export efficiency export SDDEfficiency, SiLiEfficiency # Helpers to build DetectorEfficiency -export buildresponse # Builds a detector response matrix +export detectorresponse # Build a matrix that describes the detectors response to X-rays +export detect # Detect Dict(CharXRay=>Intensity) +export simulate # Simulate a spectrum as measured on a detector # Items defined in NeXL/spectrum.jl include("spectrum.jl") @@ -103,18 +101,23 @@ export maxspectrum # Dave Bright's max spectra derived spectrum export suitablefor # Which ROIs is a set of elements suitable for as reference for the specified element? export missingReferences # A Vector with missing ROIs in a FilterFitPacket export dosenormalize # Rescale the channel data to correspond to a different electron dose. +export offset # Offset the counts data by Δcounts export maxproperty, minproperty # Min value of a property over a vector of spectra export sameproperty # Returns the property value if all the spectra share the same value, errors otherwise export textplot # A quick way to visualize a spectrum + +include("compare.jl") export findsimilar # Find the spectra that are most similar to each other export χ² # Compare spectra -export duane_hunt # Estimate the Duane-Hunt limit export sigma # Computes the channel-by-channel dose corrected difference from the mean. +include("recalibrate.jl") export recalibrate # Change the LinearEnergyScale against which a spectrum is calibrated export shift # Shift the channels in a spectrum by a specified energy amount. -export offset # Offset the counts data by Δcounts + +include("duanehunt.jl") +export duane_hunt # Estimate the Duane-Hunt limit include("hyperspectrum.jl") export HyperSpectrum # The wrapper that makes an Array{<:Real,N} look like an Array{Spectrum,N-1} @@ -192,7 +195,8 @@ export FitResult export BasicFitResult export FilterFitResult export findlabel -export fit_spectrum +export fit_spectrum # Preferred for individual spectra +export fit_spectra # Preferred for multiple spectra export heterogeneity export peaktobackground export characteristiccounts @@ -242,6 +246,12 @@ export suitability # Tabulates material suitability for use as a fitting referen include("qquant.jl") export VectorQuant +include("direct.jl") +export DirectReference +export DirectReferences +export DirectFitResult +export direct + include("labeled.jl") export labeledimage # Displays an image and caption. export labeledimages # Displays a grid of images and captions. diff --git a/src/brukerspx.jl b/src/brukerspx.jl index 627604e..1a50259 100644 --- a/src/brukerspx.jl +++ b/src/brukerspx.jl @@ -151,8 +151,10 @@ function readbrukerspx(io::IO)::Spectrum props[:TakeOffAngle] = deg2rad(parse(Float64, findfirst("ElevationAngle", item).content)) ) - props[:WorkingDistance] = - 0.1 * parse(Float64, findfirst("WorkingDistance", item).content) + wd = findfirst("WorkingDistance", item) + if !isnothing(wd) + props[:WorkingDistance] = 0.1 * parse(Float64, wd.content) + end end #item = findfirst("//TRTSpectrum/ClassInstance/TRTHeaderedClass/ClassInstance[@Type='TRTXrfFPModelHeader']",xml) #item = findfirst("//TRTSpectrum/ClassInstance/TRTHeaderedClass/ClassInstance[@Type='TRTQuantitatorConfig']",xml) diff --git a/src/compare.jl b/src/compare.jl new file mode 100644 index 0000000..4f2e0d4 --- /dev/null +++ b/src/compare.jl @@ -0,0 +1,243 @@ +""" + χ²(s1::Spectrum{T}, s2::Spectrum{U}, chs)::T where {T<:Real, U <: Real} + χ²(specs::AbstractArray{Spectrum{T}}, chs)::Matrix{T} + +Computes the dose corrected reduced χ² metric between `s1` and `s2` over the channels in `chs`. + +The second form computes a matrix of χ² comparing each spectrum in the array to the others. +""" +function χ²(s1::Spectrum{T}, s2::Spectrum{U}, chs)::Float64 where {T<:Real,U<:Real} + k1, k2 = 1.0 / dose(s1), 1.0 / dose(s2) + return sum(chs) do ch + s1c, s2c = get(s1, ch, 0.0), get(s2, ch, 0.0) + (k1 * s1c - k2 * s2c)^2 / (k1 * k1 * max(one(T), s1c) + k2 * k2 * max(one(U), s2c)) + end +end +function χ²(specs::AbstractVector{<:Spectrum}, chs)::Matrix{Float64} + χ2s = zeros(Float64, (length(specs), length(specs))) + for i in eachindex(specs), j in i+1:length(specs) + χ2s[i, j] = χ²(specs[i], specs[j], chs) + χ2s[j, i] = χ2s[i, j] + end + return χ2s +end + +""" + similarity(s1::Spectrum{T}, s2::Spectrum{T}, chs)::Float64 where {T<:Real} + similarity(specs::AbstractArray{Spectrum{T}}, chs)::Vector{Float64} + similarity(specs::AbstractArray{Spectrum}, minE::Float64=100.0)::Vector{Float64} + similarity(specs::AbstractArray{<:Spectrum}, det::Detector, elm::Element)::Vector{Float64} + similarity(specs::AbstractArray{Spectrum{T}}, det::Detector, mat::Material)::Vector{Float64} + +Returns a vector of similarity metrics which measure how similar the i-th `Spectrum` is to the other spectra. +The mean reduced χ² statistic metric is such that if `s1` and `s2` differ by only count statistics +then the metric will be approximately unity. If `s1` and `s2` vary due to probe current drift, sample +inhomogeneity, surface roughness or other non-count statistics related reasons then the metric will be +larger than one. + +The first version covers all the channels between minE and the nominal beam energy. The third and fourth versions +considers those channels representing peaks in a spectrum from the `Material` or `Element` on the `Detector`. +""" +similarity(s1::Spectrum, s2::Spectrum, chs)::Float64 = χ²(s1, s2, chs) / length(chs) +function similarity( + specs::AbstractArray{<:Spectrum}, + chs +)::Vector{Float64} + return [similarity(spec, sum(filter(s -> !(s === spec), specs)), chs) for spec in specs] +end + +function similarity( + specs::AbstractArray{<:Spectrum}, + minE::Float64=100.0, +)::Vector{Float64} + e0 = maximum(spec[:BeamEnergy] for spec in specs) + chs = + minimum( + channel(minE, spec) for spec in specs + ):maximum(channel(e0, spec) for spec in specs) + return similarity(specs, chs) +end +function similarity( + specs::AbstractArray{<:Spectrum}, + det::Detector, + elm::Element, +)::Vector{Float64} + e0 = maximum(spec[:BeamEnergy] for spec in specs) + rois = extents(characteristic(elm, alltransitions, 0.01, e0), det, 0.001) + chs = mapreduce(collect, append!, rois) + return similarity(specs, chs) +end +function similarity( + specs::AbstractArray{<:Spectrum}, + det::Detector, + mat::Material, +)::Vector{Float64} + function mrg(inp::Vector{UnitRange{Int}})::Vector{UnitRange{Int}} + simp = sort(inp) + st, res = simp[1], UnitRange{Int}[] + for r in simp[2:end] + if isempty(intersect(st, r)) + push!(res, st) + st = r + else + st = first(st):max(last(r), last(st)) + end + end + push!(res, st) + return res + end + e0 = maximum(spec[:BeamEnergy] for spec in specs) + # Figure out the contiguous ROIs and the channels in the ROIs + rois = mrg( + mapreduce( + elm -> extents(characteristic(elm, alltransitions, 0.01, e0), det, 0.001), + append!, + keys(mat), + ), + ) + chs = mapreduce(collect, append!, rois) + return similarity(specs, chs) +end + + +""" + findsimilar( + specs::AbstractArray{Spectrum{T}}; + atol = nothing, + rtol=1.5, + minspecs=3 + )::Vector{Spectrum{T}} + findsimilar( + specs::AbstractArray{Spectrum{T}}, + det::Detector, + elm::Element; + atol = nothing, + rtol=1.5, + minspecs = 3 + )::Vector{Spectrum{T}} + + +Filters a collection of spectra for the ones most similar to the average by +removing the least similar spectrum sequentially until all the remaining spectra are either: + + * less than atol (if atol != nothing) + * less than rtol * median(others) (if rtol != nothing) + +when applying the 'similarity(...)` function to the spectrum and the sum of the other spectra. + +This is useful for finding which of a set of replicate spectra are sufficiently similar +to each other. +""" +function findsimilar( + specs::AbstractArray{Spectrum{T}}; + atol=nothing, + rtol=1.5, + minspecs=3 +)::Vector{Spectrum{T}} where {T<:Real} + function keep(fs, meds) + return (isnothing(atol) || (fs < atol)) && # + (isnothing(rtol) || (fs < rtol * meds)) + end + if length(specs) >= minspecs + σs = similarity(specs) + (fmσ, fmi) = findmax(σs) + # Now perform this recursively until all are within tol or we hit minspecs + if !keep(fmσ, median(filter(σ -> σ ≠ fmσ, σs))) + rem = filter(s -> !(s === specs[fmi]), specs) + return findsimilar(rem, atol=atol, rtol=rtol, minspecs=minspecs) + else + return specs + end + end + error("There are not $minspecs spectra which are sufficiently similar to the mean spectrum.") +end +function findsimilar( + specs::AbstractArray{Spectrum{T}}, + det::Detector, + elm::Element; + atol=nothing, + rtol=1.5, + minspecs=3 +)::Vector{Spectrum{T}} where {T<:Real} + function keep(fs, meds) + return (isnothing(atol) || (fs < atol)) && # + (isnothing(rtol) || (fs < rtol * meds)) + end + if length(specs) >= minspecs + σs = NeXLSpectrum.similarity(specs, det, elm) + (fmσ, fmi) = findmax(σs) + # Now perform this recursively until all are within tol or we hit minspecs + if !keep(fmσ, median(filter(σ -> σ ≠ fmσ, σs))) + rem = filter(s -> !(s === specs[fmi]), specs) + return findsimilar(rem, det, elm, atol=atol, rtol=rtol, minspecs=minspecs) + else + return specs + end + end + error("There are not $minspecs spectra which are sufficiently similar to the mean spectrum.") +end + + +""" + sigma(spec::Spectrum, specs::AbstractArray{<:Spectrum}, chs::AbstractRange{<:Integer})::Vector{Float64} + +Computes on a channel-by-channel basis how much `spec` spectrum deviates from the mean of the +other spectra in `specs`. The result is expressed in terms of the standard deviation expected +from count statistics alone. Assuming `spec` varies only by count statistics we expect +the result values have a mean 0.0 and a standard deviation of 1.0. +""" +function sigma(spec::Spectrum, specs::AbstractArray{<:Spectrum}, chs::AbstractRange{<:Integer})::Vector{Float64} + function doseaverage(specs, chs) + t = [uv(spec, chs) / dose(spec) for spec in specs] + return map(j -> mean(collect(t[i][j] for i in eachindex(t))), eachindex(t[1])) + end + function delta(spec, specs, chs) + minus(uv1, uv2) = uv(value(uv1) - value(uv2), sqrt(variance(uv1) + variance(uv2))) + return minus.(uv(spec, chs), dose(spec) * doseaverage(filter(s -> s != spec, specs), chs)) + end + return map(v -> value(v) / σ(v), delta(spec, specs, chs)) +end + +""" + dosenormalize(spectrum::Spectrum{T}, dose=60.0)::Spectrum{T} where { T <: Real } + dosenormalize(spectrum::Spectrum{T}, dose=60.0)::Spectrum{Float64} where { T <: Integer } + +Compute a spectrum which is `spectrum` rescaled to a live time times probe current equal to `dose`. +Useful for setting spectra on an equivalent acquisition duration scale. +""" +function dosenormalize(spectrum::Spectrum{T}, dose=60.0)::Spectrum{T} where {T<:AbstractFloat} + res = copy(spectrum) + scale = dose / NeXLSpectrum.dose(res) + res.counts .*= scale + res[:LiveTime] *= scale + res[:Name] = "N[$(spectrum[:Name]), $dose nA⋅s]" + return res +end +function dosenormalize(spectrum::Spectrum{T}, dose=60.0)::Spectrum{Float64} where {T<:Integer} + scale = dose / NeXLSpectrum.dose(spectrum) + newProps = copy(spectrum.properties) + newProps[:LiveTime] *= scale + newProps[:Name] = "N[$(spectrum[:Name]), $dose nA⋅s]" + return Spectrum(spectrum.energy, Float64.(spectrum.counts) * scale, newProps) +end + + +""" + shannon_entropy(spec::Spectrum) + +Computes a measure of the information content in a spectrum. As there become more and more +distinct values in a spectrum, this value approaches log2(nchannels(spec)). This number +reflects the number of bits necessary to encode the spectrum data with maximum efficiency. + +This is inspired by John Colby's FLAME software which did something similar. Although, to +be honest, I don't know how his algorithm was implemented. +""" +function shannon_entropy(spec::Spectrum) + d = Dict{Int,Float64}() + rr(c::AbstractFloat) = round(Int, c, RoundNearestTiesUp) + rr(c::Integer) = Int(c) + foreach(rr.(counts(spec))) do ci + d[ci] = get(d, ci, 0.0) + 1.0 / length(spec) + end + return -sum(cx -> cx * log2(cx), values(d)) +end \ No newline at end of file diff --git a/src/continuum.jl b/src/continuum.jl index dab132d..3b95b22 100644 --- a/src/continuum.jl +++ b/src/continuum.jl @@ -1,3 +1,4 @@ +using CubicSplines # Model and fit the continuum struct ContinuumModel @@ -16,8 +17,8 @@ struct ContinuumModel material::Material, e0::Float64, takeoff::Float64; - matrixcorrection::Type{<:MatrixCorrection} = Riveros1993, - bremsstrahlung::Type{<:NeXLBremsstrahlung} = Castellano2004b, + matrixcorrection::Type{<:MatrixCorrection}=Riveros1993, + bremsstrahlung::Type{<:NeXLBremsstrahlung}=Castellano2004b ) = new(material, e0, takeoff, matrixcorrection, bremsstrahlung) end @@ -64,8 +65,8 @@ function fitcontinuum( spec::Spectrum, resp::AbstractArray{<:Real,2}, rois::AbstractArray{<:UnitRange,1}; - brem::Type{<:NeXLBremsstrahlung} = Castellano2004a, - mc::Type{<:MatrixCorrection} = Riveros1993, + brem::Type{<:NeXLBremsstrahlung}=Castellano2004a, + mc::Type{<:MatrixCorrection}=Riveros1993 ) @assert haskey(spec, :Composition) "The fitcontinuum(...) function requires the spec[:Composition] property." @assert haskey(spec, :BeamEnergy) "The fitcontinuum(...) function requires the spec[:BeamEnergy] property." @@ -74,8 +75,8 @@ function fitcontinuum( spec[:Composition], spec[:BeamEnergy], spec[:TakeOffAngle], - matrixcorrection = mc, - bremsstrahlung = brem, + matrixcorrection=mc, + bremsstrahlung=brem, ) minEmod = max(50.0, energy(lld(spec), spec)) # Ensures model = resp * map(e -> e > minEmod ? emitted(cmod, e) : 0.0, energyscale(spec)) @@ -122,7 +123,7 @@ function continuumrois( end extend(roi, n) = roi.start-n:roi.stop+n # Average channel width between minE and maxE. - avgwidth = let + avgwidth = let minC, maxC = channel(minE, det), channel(maxE, det) (energy(maxC, det) - energy(minC, det)) / (maxC - minC) end @@ -144,8 +145,6 @@ function continuumrois( return invert(ascontiguous, channel(minE, det):channel(maxE, det)) end - - """ fitcontinuum( spec::Spectrum, @@ -159,32 +158,17 @@ end Fit the continuum from ROIs determined from the data within the spectrum (:Composition, :BeamEnergy & :TakeOffAngle). The ROIs are computed using `continuumrois(...)` and each roi is fit seperately. - - - fittedcontinuum( - spec::Spectrum, - det::EDSDetector, - resp::AbstractArray{<:Real,2}; # - mode = :Global [ | :Local ] # Fit to all ROIs simultaneously (:Global) or to each roi independently (:Local) - minE::Float64 = 1.5e3, - maxE::Float64 = 0.95 * spec[:BeamEnergy], - brem::Type{<:NeXLBremsstrahlung} = Castellano2004a, - mc::Type{<:MatrixCorrection} = Riveros1993, - )::Spectrum - -Fit the continuum under the characteristic peaks by fitting the closest continuum ROIs. The low energy peaks are -fit using the continuum immediately higher in energy and the high energy peaks are fit using the continuum on both -sides. """ function fitcontinuum( spec::Spectrum, det::EDSDetector, resp::AbstractArray{<:Real,2}; # - minE::Float64 = 1.5e3, - maxE::Float64 = 0.95 * spec[:BeamEnergy], - brem::Type{<:NeXLBremsstrahlung} = Castellano2004a, - mc::Type{<:MatrixCorrection} = Riveros1993, -)::Vector{Float64} + minE::Float64=1.5e3, + maxE::Float64=0.90 * spec[:BeamEnergy], + width::Int=20, + brem::Type{<:NeXLBremsstrahlung}=Castellano2004a, + mc::Type{<:MatrixCorrection}=Riveros1993 +) @assert haskey(spec, :Composition) "The fitcontinuum(...) function requires the spec[:Composition] property." @assert haskey(spec, :BeamEnergy) "The fitcontinuum(...) function requires the spec[:BeamEnergy] property." @assert haskey(spec, :TakeOffAngle) "The fitcontinuum(...) function requires the spec[:TakeOffAngle] property." @@ -192,54 +176,143 @@ function fitcontinuum( spec[:Composition], spec[:BeamEnergy], spec[:TakeOffAngle], - matrixcorrection = mc, - bremsstrahlung = brem, + matrixcorrection=mc, + bremsstrahlung=brem, ) # meas is the raw continuum shape. It needs to be scaled to the unknown. minEmod = max(50.0, energy(lld(spec), spec)) model = resp * map(e -> e > minEmod ? emitted(cmod, e) : 0.0, energyscale(spec)) - prevroi, brem = missing, counts(spec, Float64) + chlow, k0 = 1, 0.0 + chdata, result = counts(spec, Float64), zeros(Float64, length(spec)) for roi in continuumrois(elms(spec, true), det, minE, maxE) - currrois = ismissing(prevroi) ? (roi,) : (prevroi, roi) - k = - sum(sum(counts(spec, roi, Float64)) for roi in currrois) / - sum(sum(model[roi]) for roi in currrois) - peak = (ismissing(prevroi) ? lld(det) : prevroi.stop):roi.start - brem[peak] = k * model[peak] - prevroi = roi + # Begining of continuum ROI + roi1 = roi.start:(roi.start+min(width, length(roi))-1) + k1 = sum(@view chdata[roi1]) / sum(@view model[roi1]) + # End of continuum ROI + roi2 = (roi.stop-min(width, length(roi))+1):roi.stop + k2 = sum(@view chdata[roi2]) / sum(@view model[roi2]) + # Model between roi1 and roi2 + k1_2(ch) = (k2 - k1) * (ch - roi.start) / length(roi) + k1 + result[roi] = k1_2.(roi) .* model[roi] + k0 = chlow == 1 ? k1 : k0 + # Model between prev roi and roi + roi0_1 = chlow:(roi.start-1) + k0_1(ch) = (k1 - k0) * (ch - roi0_1.start) / length(roi0_1) + k0 + result[roi0_1] = k0_1.(roi0_1) .* model[roi0_1] + chlow, k0 = roi.stop + 1, k2 end - return brem + # Fill the final patch + result[chlow:length(result)] = k0 * model[chlow:length(result)] + props = Dict{Symbol,Any}( + :TakeOffAngle => spec[:TakeOffAngle], + :BeamEnergy => spec[:BeamEnergy], + :Composition => spec[:Composition], + :Name => "Brem[Local][$(spec[:Name])]", + ) + return Spectrum(spec.energy, result, props) end -function fittedcontinuum( +""" +fittedcontinuum( spec::Spectrum, det::EDSDetector, resp::AbstractArray{<:Real,2}; # - mode::Symbol = :Global, + mode = :Global [ | :Local ] # Fit to all ROIs simultaneously (:Global) or to each roi independently (:Local) minE::Float64 = 1.5e3, maxE::Float64 = 0.95 * spec[:BeamEnergy], + width::Int = 20, # Width of ROI at each end of each patch of continuum that is matched brem::Type{<:NeXLBremsstrahlung} = Castellano2004a, mc::Type{<:MatrixCorrection} = Riveros1993, -)::Spectrum - function localfittedcontinuum(spec, det, resp, minE, maxE, brem, mc)::Spectrum - res = Spectrum( - spec.energy, - fitcontinuum(spec, det, resp, minE = minE, maxE = maxE, brem = brem, mc = mc), - copy(spec.properties), - ) - res[:Name] = "Brem[Local][$(spec[:Name])]" - return res + )::Spectrum + +Fit the continuum under the characteristic peaks by fitting the closest continuum ROIs. The low energy peaks are +fit using the continuum immediately higher in energy and the high energy peaks are fit using the continuum on both +sides. + + * mode = :Global [ | :Local ] Global fits the model to the data using a single scale factor. :Local tweaks the + global model at ROIs above and below the characteristic peaks. + + Typically, :Global produces the overall best fit but :Local fits better around the characteristic peaks and is + better for modeling the continuum under the peaks. +""" +function fittedcontinuum( + spec::Spectrum, + det::EDSDetector, + resp::AbstractArray{<:Real,2}; # + mode::Symbol=:Global, + minE::Float64=1.5e3, + maxE::Float64=0.95 * spec[:BeamEnergy], + brem::Type{<:NeXLBremsstrahlung}=Castellano2004a, + mc::Type{<:MatrixCorrection}=Riveros1993 +) + @assert (mode == :Global) || (mode == :Local) "mode must equal :Global | :Local in fitted continuum" + crois = continuumrois(elms(spec, true), det, minE, maxE) + gl = fitcontinuum(spec, resp, crois, brem=brem, mc=mc) + return mode == :Global ? gl : tweakcontinuum(spec, gl, crois) +end + + + +""" + tweakcontinuum( + meas::Spectrum{T}, + cont::Spectrum{U}, + crois::Vector{UnitRange{Int}}; + nch=10, + maxSc=1.5 + ) where { T<: Real, U <: Real } + +Takes a measured spectrum and the associated modeled continuum spectrum and tweaks the continuum spectrum to produce +a better fit to the measured. It focuses on the ends of the continuum ROIs (in `crois`) to match the continuum at +these channels. + + * `maxSc` limits how large a tweak is acceptable. + * `nch` determines how many channels to match at the end of each ROI in `crois` +""" +function tweakcontinuum(meas::Spectrum{T}, cont::Spectrum{U}, crois::Vector{UnitRange{Int}}; nch=10, maxSc=1.5) where {T<:Real,U<:Real} + function LinearSpline(x, y) + @assert length(x)==length(y) + function f(xx) + i = findfirst(v -> xx <= v, x) + return if !isnothing(i) + i > firstindex(x) ? ((y[i] - y[i-1]) / (x[i] - x[i-1])) * (xx - x[i-1]) + y[i-1] : y[i] + else + y[lastindex(y)] + end + end + return f + end + x, y, chmax = Float64[1.0], Float64[1.0], channel(0.9*meas[:BeamEnergy], meas) + for croi in crois + if (length(croi) > (3 * nch) / 2) && (croi.start < chmax) + # Add a pivot beginning and end of croi + stroi = croi.start:croi.start+nch-1 + push!(x, stroi.start) + push!(y, Base.clamp(integrate(meas, stroi) / max(integrate(cont, stroi),1.0), 1.0 / maxSc, maxSc)) + if croi.stop < chmax + endroi = croi.stop-nch+1:min(length(cont), croi.stop) + push!(x, endroi.stop) + push!(y, clamp(integrate(meas, endroi) / max(integrate(cont, endroi), 1.0), 1.0 / maxSc, maxSc)) + else + push!(x, chmax) + push!(y, 1.0) + end + end end - globalfittedcontinuum(spec, det, resp, minE, maxE, brem, mc)::Spectrum = fitcontinuum( - spec, - resp, - continuumrois(elms(spec, true), det, minE, maxE), - brem = brem, - mc = mc, + if x[end] < length(cont) + push!(x, length(cont)) + push!(y, 1.0) + end + # In case there are too few points in the spline for a cubic spline + spline = length(x) >= 5 ? CubicSpline(x, y) : LinearSpline(x, y) + scale = map(x -> spline(x), eachindex(cont.counts)) + props = Dict( + cont.properties..., + :CScale => scale, + :Parent => cont, + :Name => "Tweaked[$(cont[:Name])]" ) - @assert (mode == :Global) || (mode == :Local) "mode must equal :Global | :Local in fitted continuum" - return mode == :Global ? globalfittedcontinuum(spec, det, resp, minE, maxE, brem, mc) : # - localfittedcontinuum(spec, det, resp, minE, maxE, brem, mc) + return Spectrum(cont.energy, scale .* cont.counts, props) end """ @@ -253,21 +326,21 @@ end mc::Type{<:MatrixCorrection} = Riveros1993, )::Spectrum -Computes the characteristic-only spectrum by subtracting the . +Computes the characteristic-only spectrum by subtracting the continuum. """ function subtractcontinuum( spec::Spectrum, det::EDSDetector, resp::AbstractArray{<:Real,2}; # - minE::Float64 = 1.5e3, - maxE::Float64 = 0.95 * spec[:BeamEnergy], - brem::Type{<:NeXLBremsstrahlung} = Castellano2004a, - mc::Type{<:MatrixCorrection} = Riveros1993, + minE::Float64=1.5e3, + maxE::Float64=0.95 * spec[:BeamEnergy], + brem::Type{<:NeXLBremsstrahlung}=Castellano2004a, + mc::Type{<:MatrixCorrection}=Riveros1993 )::Spectrum res = Spectrum( spec.energy, counts(spec, Float64) - - fitcontinuum(spec, det, resp, minE = minE, maxE = maxE, brem = brem, mc = mc), + fitcontinuum(spec, det, resp, minE=minE, maxE=maxE, brem=brem, mc=mc), copy(spec.properties), ) res[:Name] = "CharOnly[$(res[:Name])]" diff --git a/src/data/AMETEK Si3N4 C1.csv b/src/data/AMETEK Si3N4 C1.csv new file mode 100644 index 0000000..944e657 --- /dev/null +++ b/src/data/AMETEK Si3N4 C1.csv @@ -0,0 +1,84 @@ +# Si3N4 window transparency extracted from a plot on the AMETEK website., +keV,C1 +0,0 +0.0359,0 +0.04488,0.00672 +0.05834,0.03024 +0.0718,0.06159 +0.07629,0.0224 +0.08078,0 +0.20194,0 +0.22438,0.0056 +0.23785,0.0112 +0.26028,0.02128 +0.28272,0.03807 +0.29619,0.05487 +0.30965,0.06831 +0.31862,0.08847 +0.33657,0.10526 +0.34106,0.1243 +0.35901,0.14446 +0.37248,0.17357 +0.39491,0.19597 +0.3994,0.2206 +0.41286,0.1355 +0.43082,0.16237 +0.44877,0.17917 +0.45325,0.19597 +0.4712,0.21389 +0.48018,0.2318 +0.48915,0.24524 +0.50262,0.2598 +0.51159,0.27324 +0.52506,0.29115 +0.53403,0.30795 +0.55198,0.32139 +0.57891,0.34602 +0.58788,0.3617 +0.60135,0.37514 +0.61481,0.39194 +0.63276,0.40761 +0.64174,0.42329 +0.65969,0.43449 +0.67764,0.45465 +0.6911,0.4692 +0.71354,0.486 +0.73598,0.5028 +0.75841,0.5196 +0.77636,0.53303 +0.80329,0.55095 +0.84817,0.57671 +0.88407,0.59574 +0.9469,0.6215 +1.03665,0.65174 +1.13538,0.67973 +1.22513,0.70213 +1.31488,0.71892 +1.43156,0.74356 +1.55722,0.62038 +1.56171,0.58343 +1.638,0.61142 +1.70531,0.63382 +1.77263,0.65622 +1.83545,0.67637 +1.84443,0.58231 +1.95213,0.6047 +2.06881,0.62486 +2.23934,0.6495 +2.40987,0.66965 +2.56694,0.68421 +2.7285,0.69877 +2.88108,0.71109 +3.12341,0.72788 +3.36126,0.74468 +3.54525,0.75812 +3.77861,0.77492 +4.06582,0.79619 +4.27674,0.81075 +4.57292,0.83091 +4.82423,0.84546 +5.07554,0.86002 +5.25954,0.86898 +5.51533,0.88018 +5.77113,0.8925 +6,0.90146 diff --git a/src/data/AMETEK Si3N4 C2.csv b/src/data/AMETEK Si3N4 C2.csv new file mode 100644 index 0000000..5b0c36a --- /dev/null +++ b/src/data/AMETEK Si3N4 C2.csv @@ -0,0 +1,96 @@ +# Si3N4 window transparency extracted from a plot on the AMETEK website., +keV,C2 +0,0 +0.00898,0.00336 +0.02244,0.0056 +0.02693,0.04367 +0.03141,0.07279 +0.04039,0.10974 +0.04488,0.17021 +0.05385,0.22732 +0.05834,0.29227 +0.06283,0.33595 +0.0718,0.38074 +0.07629,0.32363 +0.08078,0.23068 +0.08527,0.22284 +0.09424,0.21725 +0.10322,0.18813 +0.11219,0.14446 +0.12117,0.10302 +0.13014,0.0963 +0.13912,0.11758 +0.14809,0.1243 +0.15707,0.14446 +0.17502,0.16685 +0.18399,0.18589 +0.18848,0.21389 +0.20194,0.23852 +0.21092,0.25868 +0.21541,0.28108 +0.22887,0.30459 +0.23785,0.32699 +0.24682,0.35274 +0.2558,0.37178 +0.26477,0.38634 +0.26926,0.4009 +0.28272,0.42441 +0.2917,0.45465 +0.31414,0.48264 +0.31862,0.4916 +0.3276,0.50504 +0.34106,0.53975 +0.37248,0.56215 +0.37696,0.57447 +0.39043,0.58679 +0.40389,0.60358 +0.41735,0.51736 +0.4353,0.54311 +0.45325,0.55543 +0.46223,0.56999 +0.47569,0.58119 +0.48915,0.59239 +0.50711,0.60358 +0.51608,0.61254 +0.52954,0.62374 +0.54749,0.62822 +0.5834,0.64614 +0.62827,0.66965 +0.67315,0.68757 +0.74046,0.70437 +0.82124,0.7234 +0.88856,0.73348 +1.0187,0.74916 +1.1264,0.76036 +1.21616,0.76932 +1.29693,0.77828 +1.39117,0.78947 +1.49439,0.80291 +1.55722,0.81187 +1.56171,0.78723 +1.65146,0.80291 +1.71429,0.81411 +1.83545,0.83203 +1.84443,0.79731 +1.84892,0.75028 +1.8534,0.73796 +1.99252,0.74468 +2.1451,0.7514 +2.29768,0.75588 +2.4727,0.76036 +2.68811,0.76708 +2.86761,0.77156 +3.0561,0.77828 +3.25804,0.78611 +3.41511,0.79283 +3.69334,0.80627 +3.97607,0.82195 +4.13313,0.83091 +4.45625,0.84882 +4.65819,0.8589 +4.8736,0.86898 +5.07105,0.87906 +5.24159,0.88802 +5.47046,0.89586 +5.76215,0.90594 +5.99551,0.91377 \ No newline at end of file diff --git a/src/AP3_3_mod.csv b/src/data/AP3_3_mod.csv similarity index 100% rename from src/AP3_3_mod.csv rename to src/data/AP3_3_mod.csv diff --git a/src/AP5.csv b/src/data/AP5.csv similarity index 100% rename from src/AP5.csv rename to src/data/AP5.csv diff --git a/src/detector.jl b/src/detector.jl index d113448..e7faf5d 100644 --- a/src/detector.jl +++ b/src/detector.jl @@ -74,7 +74,7 @@ Example: energy(101,pes) == 3.0 + 10.0*101 + 0.001*101^2 """ NeXLCore.energy(ch::Int, sc::LinearEnergyScale)::Float64 = (ch - 1) * sc.width + sc.offset - +Polynomials.derivative(_::Int, sc::LinearEnergyScale)::Float64 = sc.width """ PolyEnergyScale @@ -108,6 +108,7 @@ end NeXLCore.energy(ch::Integer, sc::PolyEnergyScale)::Float64 = sc.poly(convert(Float64, ch - 1)) +Polynomials.derivative(ch::Int, sc::PolyEnergyScale)::Float64 = derivative(sc.poly,1)(ch-1) """ @@ -302,13 +303,14 @@ energyscale(det::EDSDetector) = energyscale(det.scale, eachindex(det)) Low level detection limit in channels. Channels at or below this value will be zeroed when the lld is applied. """ -lld(det::EDSDetector) = det.lld +lld(det::EDSDetector)::Int = det.lld resolution(eV::Float64, det::EDSDetector) = resolution(eV, det.resolution) resolution(det::Detector) = resolution(enx"Mn K-L3", det) NeXLCore.energy(ch::Int, det::EDSDetector) = energy(ch, det.scale) +Polynomials.derivative(ch::Int, det::EDSDetector) = derivative(ch, det.scale) channel(eV::Float64, det::EDSDetector) = channel(eV, det.scale) channel(sf::SpectrumFeature, det::EDSDetector) = channel(energy(sf), det.scale) @@ -426,7 +428,10 @@ end function escapeextents( cxrs::AbstractVector{T}, det::Detector, - ampl::Float64 + ampl::Float64, + maxE::Float64, + escape::CharXRay = n"Si K-L3", + minweight::Float64 = 0.5 )::Vector{Tuple{Vector{T},UnitRange{Int}}} where T <: SpectrumFeature Creates a vector containing pairs containing a vector of T <: SpectrumFeature and an interval. The interval represents a @@ -439,7 +444,7 @@ function escapeextents( ampl::Float64, maxE::Float64, escape::CharXRay = n"Si K-L3", - minweight::Float64 = 0.5, + minweight::Float64 = 0.5 )::Vector{Tuple{Vector{EscapeArtifact},UnitRange{Int}}} where {T<:SpectrumFeature} escs = map( tr -> EscapeArtifact(tr, escape), diff --git a/src/detefficiency.jl b/src/detefficiency.jl index e4c4ae9..a47d11f 100644 --- a/src/detefficiency.jl +++ b/src/detefficiency.jl @@ -1,3 +1,5 @@ +using Distributions: Poisson + # Model detector efficiency (Window + Detector Crystal) struct DetectorEfficiency @@ -11,9 +13,9 @@ Base.show(io::IO, de::DetectorEfficiency) = print(io, "$(de.name)[$(de.window)]" SDDEfficiency( window::AbstractWindow; - thickness = 0.0370, - deadlayer = 30.0e-7, - entrance = Film(pure(n"Al"), 10.0e-7), + thickness=0.0370, + deadlayer=30.0e-7, + entrance=Film(pure(n"Al"), 10.0e-7) ) = DetectorEfficiency( "SDD", window, @@ -23,9 +25,9 @@ SDDEfficiency( SiLiEfficiency( window::AbstractWindow; - thickness = 0.250, - deadlayer = 30.0e-7, - entrance = Film(pure(n"Al"), 10.0e-7), + thickness=0.250, + deadlayer=30.0e-7, + entrance=Film(pure(n"Al"), 10.0e-7) ) = DetectorEfficiency( "Si(Li)", window, @@ -33,10 +35,13 @@ SiLiEfficiency( Film(pure(n"Si"), thickness), ) -efficiency(aa::DetectorEfficiency, energy::Float64, angle::Float64 = π / 2) = - transmission(aa.window, energy, angle) * - (1.0 - transmission(aa.active, energy, angle)) * - mapreduce(lyr -> transmission(lyr, energy, angle), *, aa.surface) +function efficiency(aa::DetectorEfficiency, energy::Float64, angle::Float64=π / 2) + return energy > 20.0 ? # + transmission(aa.window, energy, angle) * + (1.0 - transmission(aa.active, energy, angle)) * + mapreduce(lyr -> transmission(lyr, energy, angle), *, aa.surface) : # + 0.0 +end """ detectorresponse(det::EDSDetector, eff::DetectorEfficiency, incidence::Float64=π/2)::AbstractMatrix @@ -46,6 +51,9 @@ escape peaks. All the warts that can be modeled within a linear model but not t are non-linear. This function can (!should!) be specialized for more sophisticated detector models that include more warts. +It also dicretizes the input energies on the same scale as the `EDSDetector` (thus it is square.) This is reasonable +when the detector channel width is much less than the resolution. + Example: genint = computegeneratedintensity(....) # Either characteristic or Bremsstrahlung... @@ -58,31 +66,77 @@ Example: function detectorresponse( det::EDSDetector, eff::DetectorEfficiency, - incidence::Float64 = π / 2, + incidence::Float64=π / 2, ) res = zeros(Float64, (channelcount(det), channelcount(det))) # An x-ray with energy in ch will be dispersed among a range of channels about ch for ch in max(channel(10.0, det), lld(det)):channelcount(det) # X-ray energy by channel el, eh = energy(ch, det), energy(ch + 1, det) # X-ray energy + @assert el > 0.0 fwhm = resolution(0.5 * (eh + el), det) + @assert !isnan(fwhm) # Full width of detectable X-rays - roc2 = channel(el - 3.0 * fwhm, det):channel(el + 3.0 * fwhm, det) + full_roc = channel(el - 3.0 * fwhm, det):channel(el + 3.0 * fwhm, det) # Range of available channels - roc = max(lld(det), roc2.start):min(channelcount(det), roc2.stop) + in_roc = max(lld(det), full_roc.start):min(channelcount(det), full_roc.stop) + # Nominal efficiency for an energy(ch) X-ray effic = 0.5 * (efficiency(eff, eh, incidence) + efficiency(eff, el, incidence)) - tmp = map(ch2 -> profile(ch2, 0.5 * (eh + el), det), roc2) - # This code handles the X-rays that come in below the LLD or above the last channel but are - # broadened to be detected in an existing channel. - pre, post = 1, length(tmp) - if roc.start > roc2.start - pre = roc.start - roc2.start + 1 - tmp[pre] = sum(@view tmp[1:pre]) - end - if roc2.stop > roc.stop - post = length(tmp) - (roc2.stop-roc.stop) - tmp[post] = sum(@view tmp[post:end]) - end - res[roc, ch] = effic * @view tmp[pre:post] + @assert !isnan(effic) + prof = map(ch2 -> profile(ch2, 0.5 * (eh + el), det), full_roc) + pre = (in_roc.start - full_roc.start) + 1 + post = length(prof) - (full_roc.stop - in_roc.stop) + res[in_roc, ch] = effic * @view prof[pre:post] end return res end + +""" + detect(emitted::Dict{<:XRay,<:Real}, det::EDSDetector, response::Matrix{Float64}; noise=false) + +Returns a Spectrum as though the intensities in `emitted` were detected on the specified detector. +`noise=true` will add Poisson noise to the resulting measured `Spectrum`. +""" +function detect(emitted::Dict{<:XRay, <:Real}, det::EDSDetector, response::Matrix{Float64}; noise=false) + data = zeros(Float64, channelcount(det)) + for (xr, i) in emitted + ch = channel(energy(xr), det) + if checkbounds(Bool, data, ch) + @inbounds data[ch] += i + end + end + meas = response * data # As a Float64[] + if noise + meas = map(c->rand(Poisson(c)), meas) # Returns an Int[] + end + return Spectrum(det.scale, meas, Dict{Symbol,Any}(:Detector=>det)) +end + +"""" + simulate(comp::Material, dose::Float64, e0::Float64, θtoa::Float64, Ω::Float64, det::Detector, resp::Matrix{Float64}; noise=false, vargs...) + +Compute a simulated X-ray spectrum for the specified composition material. + +Arguments: + + * comp: The Material + * dose: The electron dose in nA⋅s + * e0: The beam energy in eV + * θtoa: The take-off angle in radians + * Ω: The detector solid angle in steradians + * det: The detector model + * resp: The detector response + +Returns a `Spectrum` struct. +""" +function simulate(comp::Material, dose::Float64, e0::Float64, θtoa::Float64, Ω::Float64, det::Detector, resp::Matrix{<:AbstractFloat}; noise=false, vargs...) + ei = emitted_intensities(comp, dose, e0, θtoa, Ω; vargs...) + sp = detect(ei, det, resp, noise=noise) + sp[:TakeOffAngle] = θtoa + sp[:SolidAngle] = Ω + sp[:ProbeCurrent] = 1.0 + sp[:LiveTime] = dose + sp[:Composition] = comp + sp[:BeamEnergy] = e0 + sp[:Name] = "Simulated $(name(comp))" + return sp +end diff --git a/src/direct.jl b/src/direct.jl new file mode 100644 index 0000000..bf0fb45 --- /dev/null +++ b/src/direct.jl @@ -0,0 +1,149 @@ +# Implements a "direct fit" of reference spectra to an unknown spectrum +# This involves background modeling each spectrum - unknown and references - to remove the +# signal from the continuum. Then the remaining signal is assumed to be characteristic only +# so we can perform a direct linear least squares fit to determine the k-ratios. + +""" +A `DirectReference` represents the data extracted from a reference spectrum necessary to perform a +"direct" (background-eliminated direct fit).""" +struct DirectReference + label::CharXRayLabel + scale::Float64 + roi::UnitRange{Int} + data::Vector{Float64} +end + +Base.show(io::IO, dr::DirectReference) = print(io, "DirectReference($(dr.label))") + +""" +A `DirectReferences` is a packet of `DirectReference` and detector information. It can be used to +fit the corresponding peaks in an "unknown" spectrum. +""" +struct DirectReferences + references::Vector{DirectReference} + detector::Detector + response::Matrix{Float64} +end + +struct DirectRefInit + element::Element + spectrum::Spectrum + material::Material +end + +direct(elm::Element, spec::Spectrum, mat::Material) = DirectRefInit(elm, spec, mat) + +""" + references(refs::Vector{DirectRefInit}, det::Detector, resp::Matrix{Float64}; minE=0.5e3 ) + +Use along with `direct(...)` to build a collection of direct fitting references. + +Example: + + > detu = matching(unk, 132.0, 110) + > resp = detectorresponse(detu, SDDEfficiency(ModeledWindow(MoxtekAP33()))) + + > drefs = references([ + direct(n"O", stds[1], mat"Al2O3"), + direct(n"Al", stds[1], mat"Al2O3"), + direct(n"Ba", stds[2], mat"BaF2"), + direct(n"Ca", stds[3], mat"CaF2"), + direct(n"Fe", stds[4], mat"Fe"), + direct(n"Si", stds[5], mat"Si") ], + detu, resp + ) +""" +function references(refs::Vector{DirectRefInit}, det::Detector, resp::Matrix{Float64}; minE=0.5e3 ) + function direct(elm::Element, spec::Spectrum, mat::Material) + allElms = collect(elms(mat)) + @assert elm in allElms "$elm is not contained in $mat." + @assert haskey(spec, :BeamEnergy) "The spectrum must define the :BeamEnergy property." + lbls = NeXLSpectrum.charXRayLabels(spec, elm, allElms, det, spec[:BeamEnergy]) + map(lbls) do lbl + charOnly = counts(lbl.spectrum, lbl.roi) - counts(fittedcontinuum(lbl.spectrum, det, resp, mode=:Local, minE=minE), lbl.roi) + DirectReference(lbl, dose(lbl.spectrum), lbl.roi, charOnly) + end + end + drefs = mapreduce(append!, refs) do ref + direct(ref.element, ref.spectrum, ref.material) + end + return DirectReferences(drefs, det, resp) +end + + +Base.show(io::IO, drs::DirectReferences) = print(io, "DirectReferences(\n"*join( ("\t$(dr.label)" for dr in drs.references), ",\n")*"\n)") + +""" +`DirectFitResult` contains the result of a direct fit of a `DirectReferences` to an unknown spectrum. +""" +struct DirectFitResult{T <: AbstractFloat} <: FitResult + label::UnknownLabel + kratios::UncertainValues + residual::Spectrum{T} + continuum::Spectrum{T} + peakback::Dict{ReferenceLabel,NTuple{3,T}} +end + +Base.show(io::IO, dfr::DirectFitResult) = print(io, "DirectFitResult($(dfr.label))") + +""" + fit_spectrum(unk::Spectrum{T}, drefs::DirectReferences)::DirectFitResult{T} where { T <: Real } + +Fits the direct references to the unknown spectrum returning a `DirectFitResult` struct containing +k-ratios and other output quantities from the fit. + +Surprisingly, this function requires that `unk[:Composition]` is defined as a `Material` with an +estimate of the composition of the material from which `unk` was collected. This information is +necessary to model the continuum background. Yes, this seems circular (and it is.) One option +is to perform a filter-fit first, quantify the filter fit and then use this as your estimate. +""" +function fit_spectrum(unk::Spectrum{T}, drefs::DirectReferences) where { T <: Real } + @assert haskey(unk.properties, :Composition) "Please define the :Composition property for `unk` with a `Material`." + # Create blocks of contiguous peaks to fit in a group + function contiguous(srefs) + res = Vector{DirectReference}[ ] + for (i, sref) in enumerate(sort(srefs.references, lt=(a,b)->a.roi.start < b.roi.start)) + if (i==1) || (sref.roi.start > res[end][end].roi.stop) + push!(res, [ sref ]) # start new grouping + else + push!(res[end], sref) # append to last grouping + end + end + return res + end + continuum = fittedcontinuum(unk, drefs.detector, drefs.response, mode=:Local, minE=0.5e3) + charonly = unk - continuum + du = dose(unk) + # Perform the fit (returns `UncertainValues`) + ks = cat(map(contiguous(drefs)) do block + # Determine the fit ROI + full = minimum(dr.roi.start for dr in block):maximum(dr.roi.stop for dr in block) + # Extract these channels from charonly and unk and scale to 1 nA⋅s + y, cov = counts(charonly, full) / du, counts(unk, full) / (du^2) + # Build the model matrix + a = zeros(Float64, length(y), length(block)) + for (i,dr) in enumerate(block) + a[dr.roi.start-full.start+1:dr.roi.stop-full.start+1, i] = dr.data / dr.scale + end + # Perform a weighed least squares fit using the pseudo-inverse + w = Diagonal([sqrt(1.0 / cv) for cv in cov]) + genInv = pinv(w * a, rtol = 1.0e-6) + return uvs(getproperty.(block, :label), genInv * w * y, genInv * transpose(genInv)) + end) + # Compute the residual spectrum + function residual(unk, drefs, ks) + res, du, rprops = copy(unk.counts), dose(unk), copy(unk.properties) + for dr in drefs.references + res[dr.roi] -= (value(ks, dr.label) * du/dr.scale) * dr.data + end + rprops[:Name] = "Residual[$(unk[:Name])]" + return Spectrum(unk.energy, res, rprops) + end + DirectFitResult( + UnknownLabel(unk), + ks, + residual(unk, drefs, ks), + continuum, + Dict{ReferenceLabel,NTuple{3,T}}() + ) +end \ No newline at end of file diff --git a/src/duanehunt.jl b/src/duanehunt.jl new file mode 100644 index 0000000..e833555 --- /dev/null +++ b/src/duanehunt.jl @@ -0,0 +1,39 @@ +# Duane-Hunt related functions + +_duane_hunt_func(es, p) = map(ee-> p[1]*bremsstrahlung(Small1987, ee, p[2], n"H"), es) + +function _duane_hunt_impl(spec::Spectrum) + if channel(spec[:BeamEnergy], spec) < length(spec) + cdata = counts(spec) + este0, estCh = let # Sometimes the D-H can be much below `spec[:BeamEnergy]` + # this handles pulse pile-up above the D-H + lim = min(10.0, max(1.0, 1.0e-5 * maximum(cdata))) + len = min(10, max(5, length(spec) ÷ 400)) + chlast = findlast(i -> mean(@view cdata[i-len:i]) > lim, len+1:length(cdata)) + energy(chlast, spec), chlast + end + xdata, ydata = let + # Range of channels above/below `este0` + chs = max(1,(9*estCh)÷10):min((21*estCh)÷20,length(spec)) + energyscale(spec, chs), cdata[chs] + end + esti0 = sum(ydata) / sum(_duane_hunt_func(xdata, (1.0, este0))) + e0 = spec[:BeamEnergy] + # Apply constraint functions to keep DH between 0.1 E0 and 1.1 E0. + transform(v) = asin(2*(v-0.1*e0)/(1.1*e0-0.1*e0)-1) + inv_transform(tv) = 0.1*e0+(sin(tv)+1)*(1.1*e0-0.1*e0)/2 + # Fit Small's continuum model to the data (ignore detector efficiency) + model(xs, p) = _duane_hunt_func(xs, (p[1], inv_transform(p[2]))) + res = curve_fit(model, xdata, ydata, [esti0, transform(este0)]) + return (res.param[1], inv_transform(res.param[2])) + else + error("Unable to estimate the Duane-Hunt on $(spec[:Name]) because the counts data does not extend to $(spec[:BeamEnergy]) keV.") + end +end + +""" + duane_hunt(spec::Spectrum) + +Estimates the Duane-Hunt limit (the energy at which the continuum goes to effectively zero.) +""" +duane_hunt(spec::Spectrum) = _duane_hunt_impl(spec)[2] \ No newline at end of file diff --git a/src/emsa.jl b/src/emsa.jl index 1b47cb8..ed88edf 100644 --- a/src/emsa.jl +++ b/src/emsa.jl @@ -1,35 +1,35 @@ function isemsa(io::IO) - for (lx, line) in enumerate(eachline(io)) - tmp = split_emsa_header_item(line) - if isnothing(tmp) || # - (lx==1 && !(tmp[1]=="FORMAT" && uppercase(tmp[2])=="EMSA/MAS SPECTRAL DATA FILE")) || # - (lx==2 && !(tmp[1]=="VERSION" && tmp[2]=="1.0")) - return false - end - if lx>=2 - return true - end - end + for (lx, line) in enumerate(eachline(io)) + tmp = split_emsa_header_item(line) + if isnothing(tmp) || # + (lx == 1 && !(tmp[1] == "FORMAT" && startswith(uppercase(tmp[2]), "EMSA/MAS"))) || # + (lx == 2 && !(tmp[1] == "VERSION" && tmp[2] == "1.0")) + return false + end + if lx >= 2 + return true + end + end end isemsa(filename::AbstractString) = - open(filename,"r") do f - isemsa(f) - end + open(filename, "r") do f + isemsa(f) + end function split_emsa_header_item(line::AbstractString) - p = findfirst(":",line) + p = findfirst(":", line) if p ≠ nothing - tmp = uppercase(strip(SubString(line,2:p.start-1))) - pp = findfirst("-",tmp) + tmp = uppercase(strip(SubString(line, 2:p.start-1))) + pp = findfirst("-", tmp) if pp ≠ nothing - key, mod = strip(SubString(tmp,1:pp.start-1)), strip(SubString(tmp,pp.stop+1)) + key, mod = strip(SubString(tmp, 1:pp.start-1)), strip(SubString(tmp, pp.stop + 1)) else key, mod = tmp, nothing end - value = strip(SubString(line,p.stop+1)) + value = strip(SubString(line, p.stop + 1)) # println(key," = ",value) return (key, value, mod) else @@ -38,16 +38,16 @@ function split_emsa_header_item(line::AbstractString) end function parsecoating(value::AbstractString)::Film - i=findfirst(" nm of ",value) - if !isnothing(i) - try - thk = parse(Float64, value[1:i.start-1])*1.0e-7 # cm - mat = parsedtsa2comp(value[i.stop+1:end]) - return Film(mat, thk) - catch err - @warn "Error parsing $(value) as a coating." - end - end + i = findfirst(" nm of ", value) + if !isnothing(i) + try + thk = parse(Float64, value[1:i.start-1]) * 1.0e-7 # cm + mat = parsedtsa2comp(value[i.stop+1:end]) + return Film(mat, thk) + catch err + @warn "Error parsing $(value) as a coating." + end + end return Film() end @@ -58,26 +58,26 @@ Read an ISO/EMSA format spectrum from a disk file at the specified path. T is the type of the channel data elements. """ function readEMSA(filename::AbstractString, T::Type{<:Real}=Float64)::Spectrum - open(filename,"r") do f - res = readEMSA(f, T) - _setfilename(res, filename) - return res - end + open(filename, "r") do f + res = readEMSA(f, T) + _setfilename(res, filename) + return res + end end function _setfilename(spec::Spectrum, filename::String) - spec[:Filename]=filename - if startswith( spec[:Name], "Bruker") - if haskey(spec, :Composition) - spec[:Name]=name(spec[:Composition]) - else - path = splitpath(filename) - spec[:Name]=path[end] - end - end - if endswith(spec[:Name],".txt") || endswith(spec[:Name],".msa") - spec[:Name] = spec[:Name][1:end-4] - end + spec[:Filename] = filename + if startswith(spec[:Name], "Bruker") + if haskey(spec, :Composition) + spec[:Name] = name(spec[:Composition]) + else + path = splitpath(filename) + spec[:Name] = path[end] + end + end + if endswith(spec[:Name], ".txt") || endswith(spec[:Name], ".msa") + spec[:Name] = spec[:Name][1:end-4] + end end @@ -88,142 +88,144 @@ Read an ISO/EMSA format spectrum from a disk file at the specified path. T is the type of the channel data elements. """ function readEMSA(f::IO, T::Type{<:Real}=Float64)::Spectrum - energy, counts, eaxis = LinearEnergyScale(0.0,10.0), T[], Float64[] + energy, counts, eaxis = LinearEnergyScale(0.0, 10.0), T[], Float64[] props = Dict{Symbol,Any}() - props[:Filename]=filename + props[:Filename] = filename inData, xpcscale = 0, 1.0 stgpos = Dict{Symbol,Float64}() - date, time, datatype = missing, missing, :Y + date, time, datatype = missing, missing, :Y for (lx, line) in enumerate(eachline(f)) if (lx ≤ 2) res = split_emsa_header_item(line) - if isnothing(res) || ((lx==1) && (res[1]!="FORMAT" || uppercase(res[2])!="EMSA/MAS SPECTRAL DATA FILE")) + if isnothing(res) || ((lx == 1) && (res[1] != "FORMAT" || !startswith(uppercase(res[2]), "EMSA/MAS"))) error("This file does not have the correct header to be an EMSA/MAS spectrum file.") end - if isnothing(res) || ((lx==2) && (res[1]!="VERSION" || res[2]!="1.0")) + if isnothing(res) || ((lx == 2) && (res[1] != "VERSION" || res[2] != "1.0")) error("This file is not a VERSION=1.0 EMSA/MAS spectrum file.") end - elseif inData>0 + elseif inData > 0 if startswith(line, "#ENDOFDATA") break else - if datatype == :Y - for cc in filter(s->length(s)>0, strip.(split(line, ","))) - try - append!(counts, parse(T, cc)) - catch - @warn "Unparsable counts while reading in \"$cc\" from \"$line\"." - end - inData+=1 - end - else - @assert datatype==:XY - try - items = filter(s->length(s)>0, strip.(split(line, ","))) - for i in 1:2:length(items) - append!(eaxis, parse(Float64, items[i])) - append!(counts, parse(T, items[i+1])) - inData+=1 - end - catch - @warn "Unparsable counts while reading \"$line\"." - end + if datatype == :Y + for cc in filter(s -> length(s) > 0, strip.(split(line, ","))) + try + append!(counts, parse(T, cc)) + catch + @warn "Unparsable counts while reading in \"$cc\" from \"$line\"." + end + inData += 1 + end + else + @assert datatype == :XY + try + items = filter(s -> length(s) > 0, strip.(split(line, ","))) + for i in 1:2:length(items) + append!(eaxis, parse(Float64, items[i])) + append!(counts, parse(T, items[i+1])) + inData += 1 + end + catch + @warn "Unparsable counts while reading \"$line\"." + end - end - end - elseif startswith(line,"#") - try - res = split_emsa_header_item(line) - if res ≠ nothing - key, value, mod = res - if key == "SPECTRUM" - inData = 1 - elseif key == "BEAMKV" - props[:BeamEnergy]=1000.0*parse(Float64,value) # in eV - elseif key == "XPERCHAN" - xperch=parse(Float64,value) - xpcscale = isnothing(mod) ? (xperch < 0.1 ? 1000.0 : 1.0) : # Guess likely eV or keV - (isequal(mod,"KEV") ? 1000.0 : 1.0) - energy = LinearEnergyScale(xpcscale*energy.offset, xpcscale*xperch) - elseif key == "LIVETIME" - props[:LiveTime]=parse(Float64,value) - elseif key == "REALTIME" - props[:RealTime]=parse(Float64,value) - elseif key == "PROBECUR" - props[:ProbeCurrent]=parse(Float64,value) - elseif key == "OFFSET" - energy = LinearEnergyScale(xpcscale*parse(Float64,value), energy.width) - elseif key == "DATE" - try - date = Date(parse(DateTime, value, DateFormat("d-u-y","english"))) - catch - date = Date(parse(DateTime, value, DateFormat("d-m-y","english"))) - end - elseif key == "TIME" - time = Time(DateTime(value, count(c->c==':',value) == 2 ? "H:M:S" : "H:M")) - elseif key == "TITLE" - props[:Name]=value - props[:Title]=value - elseif key == "OWNER" - props[:Owner]=value - elseif key == "ELEVANGLE" - props[:TakeOffAngle] = (props[:Elevation] = deg2rad(parse(Float64, value))) - elseif key == "XPOSITION" - stgpos[:X] = parse(Float64, value) - elseif key == "YPOSITION" - stgpos[:Y] = parse(Float64, value) - elseif key == "ZPOSITION" - stgpos[:Z] = parse(Float64, value) - elseif key == "COMMENT" - prev = getindex(props,:Comment,missing) - props[:Comment] = prev ≠ missing ? prev*"\n"*value : value - elseif key == "DATATYPE" - datatype = uppercase(value)=="XY" ? :XY : :Y - elseif key == "#D2STDCMP" - props[:Composition] = parsedtsa2comp(value) - elseif key == "#CONDCOATING" - props[:Coating] = parsecoating(value) - elseif key =="#RTDET" # Bruker - props[:DetectorModel] = value - elseif key == "#TDEADLYR" # Bruker - @assert isnothing(mod) || (!isequal(mod,"CM")) "Unexpected scale -$mod" - props[:DeadLayer] = parse(Float64, value) - elseif key == "#FANO" # Bruker - props[:Fano] = parse(Float64, value) - elseif key == "#WORKING" - props[:WorkingDistance] = 0.1*parse(Float64, split(value)[1]) - elseif key == "#MNFWHM" # Bruker - sc = (isnothing(mod) ? 1000.0 : (isequal(mod,"KEV") ? 1000.0 : 1.0)) - props[:FWHMMnKa] = sc*parse(Float64, value) - elseif key == "#IDENT" # Bruker - elm = nothing - try - props[:XRFAnode] = parse(Element, value) - catch - # Just ignore it - end - elseif key== "#OXINSTELEMS" - try - props[:Elements] = map(v->parse(Element, v), strip.(split(value,","))) - catch - # Just ignore it - end - end - end - catch - println(stderr,"Error parsing: $line") - end + end + end + elseif startswith(line, "#") + try + res = split_emsa_header_item(line) + if !isnothing(res) + key, value, mod = res + if key == "SPECTRUM" + inData = 1 + elseif key == "BEAMKV" + props[:BeamEnergy] = 1000.0 * parse(Float64, value) # in eV + elseif key == "XPERCHAN" + xperch = parse(Float64, value) + xpcscale = isnothing(mod) ? (xperch < 0.1 ? 1000.0 : 1.0) : # Guess likely eV or keV + (isequal(mod, "KEV") ? 1000.0 : 1.0) + energy = LinearEnergyScale(xpcscale * energy.offset, xpcscale * xperch) + elseif key == "LIVETIME" + props[:LiveTime] = parse(Float64, value) + elseif key == "REALTIME" + props[:RealTime] = parse(Float64, value) + elseif key == "PROBECUR" + props[:ProbeCurrent] = parse(Float64, value) + elseif key == "OFFSET" + energy = LinearEnergyScale(xpcscale * parse(Float64, value), energy.width) + elseif key == "DATE" + try + date = Date(parse(DateTime, value, DateFormat("d-u-y", "english"))) + catch + date = Date(parse(DateTime, value, DateFormat("d-m-y", "english"))) + end + elseif key == "TIME" + time = Time(DateTime(value, count(c -> c == ':', value) == 2 ? "H:M:S" : "H:M")) + elseif key == "TITLE" + props[:Name] = value + props[:Title] = value + elseif key == "OWNER" + props[:Owner] = value + elseif key == "ELEVANGLE" + props[:TakeOffAngle] = (props[:Elevation] = deg2rad(parse(Float64, value))) + elseif key == "XPOSITION" + stgpos[:X] = parse(Float64, value) + elseif key == "YPOSITION" + stgpos[:Y] = parse(Float64, value) + elseif key == "ZPOSITION" + stgpos[:Z] = parse(Float64, value) + elseif key == "COMMENT" + if length(value) > 0 + prev = get(props, :Comment, missing) + props[:Comment] = ismissing(prev) ? value : "$prev\n$value" + end + elseif key == "DATATYPE" + datatype = uppercase(value) == "XY" ? :XY : :Y + elseif key == "#D2STDCMP" + props[:Composition] = parsedtsa2comp(value) + elseif key == "#CONDCOATING" + props[:Coating] = parsecoating(value) + elseif key == "#RTDET" # Bruker + props[:DetectorModel] = value + elseif key == "#TDEADLYR" # Bruker + @assert isnothing(mod) || (!isequal(mod, "CM")) "Unexpected scale -$mod" + props[:DeadLayer] = parse(Float64, value) + elseif key == "#FANO" # Bruker + props[:Fano] = parse(Float64, value) + elseif key == "#WORKING" + props[:WorkingDistance] = 0.1 * parse(Float64, split(value)[1]) + elseif key == "#MNFWHM" # Bruker + sc = (isnothing(mod) ? 1000.0 : (isequal(mod, "KEV") ? 1000.0 : 1.0)) + props[:FWHMMnKa] = sc * parse(Float64, value) + elseif key == "#IDENT" # Bruker + elm = nothing + try + props[:XRFAnode] = parse(Element, value) + catch + # Just ignore it + end + elseif key == "#OXINSTELEMS" + try + props[:Elements] = map(v -> parse(Element, v), strip.(split(value, ","))) + catch + # Just ignore it + end + end + end + catch + println(stderr, "Error parsing: $line") + end end end - if !(ismissing(date) || ismissing(time)) - props[:AcquisitionTime] = DateTime(date, time) - end - if startswith( props[:Name], "Bruker") && haskey(props, :Composition) - props[:Name]=name(props[:Composition]) + if !(ismissing(date) || ismissing(time)) + props[:AcquisitionTime] = DateTime(date, time) + end + if startswith(props[:Name], "Bruker") && haskey(props, :Composition) + props[:Name] = name(props[:Composition]) + end + if !isempty(stgpos) + props[:StagePosition] = stgpos end - if !isempty(stgpos) - props[:StagePosition] = stgpos - end return Spectrum(energy, counts, props) end @@ -234,117 +236,90 @@ Read an EMSA file and apply the specified detector. If force is false and the d read calibration don't match then the function errors. """ function readEMSA(filename::AbstractString, det::Detector, force::Bool=false, T::Type{<:Real}=Float64)::Spectrum - spec = readEMSA(filename, T) - if !force - if abs(channel(energy(1, det),spec)-1)>1 - error("Spectrum and detector calibrations don't match - Offset.") - end - test = det.channelcount÷2 - if abs(channel(energy(test,det),spec)-test)>1 - error("Spectrum and detector calibrations don't match - Gain.") - end - end - return apply(spec, det) + spec = readEMSA(filename, T) + if !force + if abs(channel(energy(1, det), spec) - 1) > 1 + error("Spectrum and detector calibrations don't match - Offset.") + end + test = det.channelcount ÷ 2 + if abs(channel(energy(test, det), spec) - test) > 1 + error("Spectrum and detector calibrations don't match - Gain.") + end + end + return apply(spec, det) end function writeEMSA(filename::AbstractString, spec::Spectrum) - open(filename,"w") do ios - writeEMSA(ios, spec) - end + open(filename, "w") do ios + writeEMSA(ios, spec) + end end function writeEMSA(io::IOStream, spec::Spectrum) - forceascii(ss) = map(c-> Int(c)&0x80==0 ? c : '?', ss) - # "#FORMAT : EMSA/MAS Spectral Data File" - writeline(io, tag, data, extra="") = - println(io,"#$(tag)$(repeat(' ',12-(length(tag)+length(extra))))$(extra): $(forceascii(data))") - writeline(io, "FORMAT","EMSA/MAS Spectral Data File") - writeline(io, "VERSION","1.0") - writeline(io, "TITLE",spec[:Name]) - if haskey(spec,:AcquisitionTime) - dt = spec[:AcquisitionTime] - writeline(io, "DATE",uppercase(Dates.format(dt,"d-u-yyyy",locale="english"))) # 25-FEB-2022 - writeline(io, "TIME",Dates.format(dt,"HH:MM:SS")) # 15:32:23 - end - if haskey(spec,:Owner) - writeline(io, "OWNER",spec[:Owner]) - end - writeline(io, "NPOINTS","$(length(spec))") - writeline(io, "NCOLUMNS","1") - writeline(io, "XUNITS","eV") - writeline(io, "YUNITS","counts") - if spec.energy isa LinearEnergyScale - writeline(io, "DATATYPE","Y") - writeline(io, "XPERCHAN","$(spec.energy.width)") - writeline(io, "OFFSET", "$(spec.energy.offset)") - else - writeline(io, "DATATYPE","XY") - dx = (energy(length(spec),spec)-energy(1,spec))/(length(spec)-1.0) # Mean width - writeline(io, "XPERCHAN","$(dx)") - writeline(io, "OFFSET", "$(energy(spec,1))") # energy of first channel - end - writeline(io, "SIGNALTYPE","EDS") - if haskey(spec,:LiveTime) - writeline(io, "LIVETIME","$(spec[:LiveTime])") - end - if haskey(spec,:RealTime) - writeline(io, "REALTIME","$(spec[:RealTime])") - end - if haskey(spec,:BeamEnergy) - writeline(io, "BEAMKV","$(0.001*spec[:BeamEnergy])") - end - if haskey(spec,:ProbeCurrent) - writeline(io,"PROBECUR","$(spec[:ProbeCurrent])") - end - if haskey(spec,:TakeOffAngle) - writeline(io, "ELEVANGLE","$(rad2deg(spec[:TakeOffAngle]))") - end - if haskey(spec,:Azimuthal) - writeline(io, "AZIMANGLE","$(rad2deg(spec[:Azimuthal]))") - end - if haskey(spec,:StagePosition) - sp=spec[:StagePosition] - for (tag, sym) in ( ("XPOSITION", :X ), ("YPOSITION", :Y ), ("ZPOSITION", :Z ) ) - if haskey(sp, sym) - writeline(io, tag, "$(10.0*sp[sym])","mm") - end - end - end - if haskey(spec,:Composition) - writeline(io, "#D2STDCMP",todtsa2comp(spec[:Composition])) - end - if haskey(spec,:Specimen) - writeline(io,"#SPECIMEN","$(spec[:Specimen])") - end - if haskey(spec,:Coating) - cc = props[:Coating] - writeline(io, "#CONDCOATING", "$(1.0e7*cc.thickness) nm of $(todtsa2comp(cc.material))") - end - if haskey(spec,:DetectorModel) - writeline(io, "#RTDET",spec[:DetectorModel]) # Bruker - end - if haskey(spec,:DeadLayer) - writeline(io, "#TDEADLYR", spec[:DeadLayer]) # Bruker - end - if haskey(spec,:Fano) - writeline(io, "#FANO",spec[:Fano]) # Bruker - end - if haskey(spec, :FWHMMnKa) - writeline(io, "#MNFWHM", spec[:FWHMMnKa], "EV") # Bruker - end - if haskey(spec, :XRFAnode) - writeline(io, "#IDENT", spec[:XRFAnode]) # Bruker - end - if spec.energy isa LinearEnergyScale - writeline(io, "SPECTRUM","Spectrum data follows in counts") - for i in eachindex(spec) - println(io, spec.counts[i], ",") - end - else - writeline(io, "SPECTRUM","Spectrum data follows in energy, counts") - for i in eachindex(spec) - println(io, energy(i,spec), ", ", spec.counts[i], ",") - end - end - writeline(io, "ENDOFDATA","") + forceascii(ss) = map(c -> Int(c) & 0x80 == 0 ? c : '?', ss) + # "#FORMAT : EMSA/MAS Spectral Data File" + writeline(io, tag, data, extra="") = + println(io, "#$(tag)$(repeat(' ',12-(length(tag)+length(extra))))$(extra): $(forceascii(data))") + writeline(io, "FORMAT", "EMSA/MAS Spectral Data File") + writeline(io, "VERSION", "1.0") + writeline(io, "TITLE", spec[:Name]) + if haskey(spec, :AcquisitionTime) + dt = spec[:AcquisitionTime] + writeline(io, "DATE", uppercase(Dates.format(dt, "d-u-yyyy", locale="english"))) # 25-FEB-2022 + writeline(io, "TIME", Dates.format(dt, "HH:MM:SS")) # 15:32:23 + end + haskey(spec, :Owner) && writeline(io, "OWNER", spec[:Owner]) + writeline(io, "NPOINTS", "$(length(spec))") + writeline(io, "NCOLUMNS", "1") + writeline(io, "XUNITS", "eV") + writeline(io, "YUNITS", "counts") + if spec.energy isa LinearEnergyScale + writeline(io, "DATATYPE", "Y") + writeline(io, "XPERCHAN", "$(spec.energy.width)") + writeline(io, "OFFSET", "$(spec.energy.offset)") + else + writeline(io, "DATATYPE", "XY") + dx = (energy(length(spec), spec) - energy(1, spec)) / (length(spec) - 1.0) # Mean width + writeline(io, "XPERCHAN", "$(dx)") + writeline(io, "OFFSET", "$(energy(spec,1))") # energy of first channel + end + writeline(io, "SIGNALTYPE", "EDS") + haskey(spec, :LiveTime) && writeline(io, "LIVETIME", "$(spec[:LiveTime])") + haskey(spec, :RealTime) && writeline(io, "REALTIME", "$(spec[:RealTime])") + haskey(spec, :BeamEnergy) && writeline(io, "BEAMKV", "$(0.001*spec[:BeamEnergy])") + haskey(spec, :ProbeCurrent) && writeline(io, "PROBECUR", "$(spec[:ProbeCurrent])") + haskey(spec, :TakeOffAngle) && writeline(io, "ELEVANGLE", "$(rad2deg(spec[:TakeOffAngle]))") + haskey(spec, :Azimuthal) && writeline(io, "AZIMANGLE", "$(rad2deg(spec[:Azimuthal]))") + if haskey(spec, :StagePosition) + sp = spec[:StagePosition] + for (tag, sym) in (("XPOSITION", :X), ("YPOSITION", :Y), ("ZPOSITION", :Z)) + if haskey(sp, sym) + writeline(io, tag, "$(10.0*sp[sym])", "mm") + end + end + end + haskey(spec, :Composition) && writeline(io, "#D2STDCMP", todtsa2comp(spec[:Composition])) + haskey(spec, :Specimen) && writeline(io, "#SPECIMEN", "$(spec[:Specimen])") + if haskey(spec, :Coating) + cc = props[:Coating] + writeline(io, "#CONDCOATING", "$(1.0e7*cc.thickness) nm of $(todtsa2comp(cc.material))") + end + haskey(spec, :DetectorModel) && writeline(io, "#RTDET", spec[:DetectorModel]) # Bruker + haskey(spec, :DeadLayer) && writeline(io, "#TDEADLYR", spec[:DeadLayer]) # Bruker + haskey(spec, :Fano) && writeline(io, "#FANO", spec[:Fano]) # Bruker + haskey(spec, :FWHMMnKa) && writeline(io, "#MNFWHM", spec[:FWHMMnKa], "EV") # Bruker + haskey(spec, :XRFAnode) && writeline(io, "#IDENT", spec[:XRFAnode]) # Bruker + # Write channel data + if spec.energy isa LinearEnergyScale + writeline(io, "SPECTRUM", "Spectrum data follows in counts") + for i in eachindex(spec) + println(io, spec.counts[i], ",") + end + else + writeline(io, "SPECTRUM", "Spectrum data follows in energy, counts") + for i in eachindex(spec) + println(io, energy(i, spec), ", ", spec.counts[i], ",") + end + end + writeline(io, "ENDOFDATA", "") end diff --git a/src/filter.jl b/src/filter.jl index acfc9ae..423ce8e 100644 --- a/src/filter.jl +++ b/src/filter.jl @@ -627,7 +627,7 @@ Base.show(io::IO, fd::FilteredUnknown) = print(io, fd.label) """ extract(fd::FilteredReference{T}, roi::UnitRange{Int})::Vector{T} where { T <: AbstractFloat } - extract(fd::FilteredUnknown, roi::UnitRange{Int})::AbstractVector{T} where { T <: AbstractFloat } + extract(fd::FilteredUnknown, roi::UnitRange{Int})::Vector{T} where { T <: AbstractFloat } Extract the filtered data representing the specified range. `roi` must fully encompass the filtered data in `fd`. @@ -643,37 +643,24 @@ end NeXLUncertainties.extract( fd::FilteredUnknown, roi::UnitRange{Int}, -) = view(fd.filtered, roi) +) = fd.filtered[roi] _buildlabels(ffs::AbstractVector{<:FilteredReference}) = collect(ff.label for ff in ffs) _buildscale(unk::FilteredUnknown, ffs::AbstractVector{FilteredReference{T}}) where { T<: AbstractFloat } = - Diagonal([convert(T, unk.scale / ff.scale) for ff in ffs]) - -# Internal: Computes the residual spectrum based on the fit k-ratios -function _computeResidual( - unk::FilteredUnknown, - ffs::AbstractVector{FilteredReference{T}}, - kr::UncertainValues, -) where { T <: AbstractFloat } - res = copy(unk.data) - for ff in ffs - res[ff.roi] -= (value(kr, ff.label) * ff.scale / unk.scale) * ff.charonly - end - return res -end + Diagonal([Float64(unk.scale / ff.scale) for ff in ffs]) # Internal: Computes the total peak (counts), background (counts) and reference count (c/nAs) based on the fit k-ratios function _computecounts( # unk::FilteredUnknown, # ffs::AbstractVector{FilteredReference{T}}, # - kr::UncertainValues, # + krs::UncertainValues, # ) where { T <: AbstractFloat } res = Dict{ReferenceLabel,NTuple{3,T}}() for ff in ffs su, sco = sum(unk.data[ff.roi]), sum(ff.charonly) res[ff.label] = ( su, - su - value(kr, ff.label) * sco * ff.scale / unk.scale, + su - value(krs, ff.label) * sco * ff.scale / unk.scale, sco * ff.scale ) end @@ -695,7 +682,8 @@ function fitcontiguouso( chs::UnitRange{Int}, )::UncertainValues where { T <: AbstractFloat } x, lbls, scale = _buildmodel(ffs, chs), _buildlabels(ffs), _buildscale(unk, ffs) - return scale * olspinv(extract(unk, chs), x, 1.0, lbls) + genInv = pinv(x, rtol = 1.0e-6) + return scale * uvs(lbls, genInv * extract(unk, chs), genInv * transpose(genInv)) end """ @@ -713,7 +701,7 @@ function selectBestReferences( for ref in filter(ref -> element(ref.label) == elm, refs) comp = composition(ref.label) # Pick the reference with the largest charonly value but prefer pure elements over compounds - mrf = sum(ref.charonly) * (ismissing(comp) ? 0.01 : normalized(comp, elm)^2) + mrf = sum(ref.charonly) * (ismissing(comp) ? 0.01 : Float64(normalized(comp, elm))^2) if (!haskey(rois, ref.roi)) || (mrf > rois[ref.roi][2]) rois[ref.roi] = (ref, mrf) end diff --git a/src/filterfit_wls.jl b/src/filterfit_wls.jl index 58f3e8b..a9ffd05 100644 --- a/src/filterfit_wls.jl +++ b/src/filterfit_wls.jl @@ -7,7 +7,7 @@ optimistic resulting covariance matrix. """ struct FilteredUnknownW{T} <: FilteredUnknown where { T<:AbstractFloat} label::UnknownLabel # A way of identifying this filtered datum - scale::Float64 # A dose or other scale correction factor + scale::T # A dose or other scale correction factor roi::UnitRange{Int} # ROI for the raw data (always 1:...) data::Vector{T} # Spectrum data over ffroi filtered::Vector{T} # Filtered spectrum data over ffroi @@ -32,17 +32,6 @@ function _buildmodel( return x end -""" - covariance(fd::FilteredUnknownW, roi::UnitRange{Int}) - -Like extract(fd,roi) except extracts the covariance diagnonal elements over the specified range of channels. -`roi` must be fully contained within the data in `fd`. -""" -NeXLUncertainties.covariance( - fd::FilteredUnknownW, - roi::UnitRange{Int} -) = view(fd.covariance, roi) - """ Weighted least squares for FilteredUnknownW """ @@ -52,15 +41,21 @@ function fitcontiguousww( chs::UnitRange{Int}, )::UncertainValues where { T <: AbstractFloat } x, lbls, scale = _buildmodel(ffs, chs), _buildlabels(ffs), _buildscale(unk, ffs) - covscales = T[ ff.covscale for ff in ffs ] - return scale * wlspinv2(extract(unk, chs), x, covariance(unk, chs), covscales, lbls) + # dcs is a factor that accounts for the heteroskedasciscity introduced by the filter + dcs = Diagonal([ T(ff.covscale) for ff in ffs ]) + w = Diagonal([sqrt(one(T) / T(cv)) for cv in view(unk.covariance, chs)]) + genInv = pinv(w * x, rtol = 1.0e-6) # 60% of allocation here + ext = extract(unk, chs) + # @assert all(eltype.((genInv, w, dcs, ext)).==T) + # @assert eltype(scale)==Float64 + return scale * uvs(lbls, genInv * w * ext, dcs * (genInv * transpose(genInv)) * dcs) end -function ascontiguous(rois::AbstractArray{UnitRange{Int}}) +function ascontiguous(rois::AbstractArray{UnitRange{Int}})::Vector{UnitRange{Int}} # Join the UnitRanges into contiguous UnitRanges join(roi1, roi2) = min(roi1.start, roi2.start):max(roi1.stop, roi2.stop) srois = sort(rois) - res = [srois[1]] + res = UnitRange{Int}[srois[1]] for roi in srois[2:end] if length(intersect(res[end], roi)) > 0 res[end] = join(roi, res[end]) # Join UnitRanges @@ -76,7 +71,7 @@ end For filtering the unknown spectrum. Defaults to the weighted fitting model. """ -tophatfilter(spec::Spectrum, filt::TopHatFilter{T}, scale::Float64 = 1.0) where { T <: AbstractFloat } = # +tophatfilter(spec::Spectrum, filt::TopHatFilter{T}, scale::T = one(T)) where { T <: AbstractFloat } = # tophatfilter(FilteredUnknownW{T}, spec, filt, scale) """ @@ -89,7 +84,7 @@ function tophatfilter( ::Type{FilteredUnknownW{T}}, spec::Spectrum, thf::TopHatFilter{T}, - scale::Float64 = 1.0, + scale::T = one(T), ) where { T<: AbstractFloat } data = counts(spec, 1:length(thf), T, true) filtered = T[filtereddatum(thf, data, i) for i in eachindex(data)] @@ -97,7 +92,7 @@ function tophatfilter( covar = T[filteredcovar(thf, dp, i, i) for i in eachindex(data)] return FilteredUnknownW{T}( UnknownLabel(spec), - scale, + T(scale), 1:length(data), data, filtered, @@ -109,32 +104,30 @@ end function _filterfit( unk::FilteredUnknownW{T}, ffs::AbstractVector{FilteredReference{T}}, - forcezeros, -) where { T <: AbstractFloat } - trimmed, refit, removed = copy(ffs), true, UncertainValues[] # start with all the FilteredReference - while refit - refit = false - fitrois = ascontiguous(map(fd -> fd.ffroi, trimmed)) - retained = map(fitrois) do fr + forcezeros::Bool, +)::UncertainValues where { T <: AbstractFloat } + trimmed, removed = copy(ffs), UncertainValues[] # start with all the FilteredReference + while true + retained = map(ascontiguous(map(fd -> fd.ffroi, trimmed))) do fr # `fitcontiguousww(..) performs the fit fitcontiguousww(unk, filter(ff -> length(intersect(fr, ff.ffroi)) > 0, trimmed), fr) end - kr = cat(retained) if forcezeros - for lbl in keys(kr) - if NeXLUncertainties.value(kr, lbl) < 0.0 - splice!(trimmed, findfirst(ff -> ff.label == lbl, trimmed)) - push!(removed, uvs([lbl], [0.0], reshape([σ(kr, lbl)], (1, 1)))) - refit = true - end + vals = Dict{Label, UncertainValue}() + for kr in retained, lbl in filter(l->value(kr,l) < 0.0, keys(kr)) + splice!(trimmed, findfirst(ff -> ff.label == lbl, trimmed)) + vals[lbl] = uv(0.0, σ(kr, lbl)) end - end - if !refit - return cat(append!(retained, removed)) + if isempty(vals) + return cat(append!(retained, removed)) + end + push!(removed, uvs(vals)) + else + return cat(retained) end end # while @assert false - return removed # To maintain type + return cat(removed) # To maintain type end """ @@ -154,10 +147,31 @@ would bias the result positive. function filterfit( unk::FilteredUnknownW{T}, ffs::AbstractVector{FilteredReference{T}}, - forcezeros = true, -)::FilterFitResult where { T <: AbstractFloat } + forcezeros::Bool = true, +) where { T <: AbstractFloat } krs = _filterfit(unk, ffs, forcezeros) - resid, pb = _computeResidual(unk, ffs, krs), _computecounts(unk, ffs, krs) + resid = Deferred() do + sp = unk.label.spectrum + props = copy(properties(sp)) + props[:Name] = "Residual[$(props[:Name])]" + res = copy(sp.counts) + for ff in ffs + res[ff.roi] -= (value(krs, ff.label) * ff.scale / unk.scale) * ff.charonly + end + return Spectrum(sp.energy, res, props) + end + pb = Deferred() do + res = Dict{ReferenceLabel,NTuple{3,T}}() + for ff in ffs + su, sco = sum(unk.data[ff.roi]), sum(ff.charonly) + res[ff.label] = ( + su, + su - value(krs, ff.label) * sco * ff.scale / unk.scale, + sco * ff.scale + ) + end + return res + end return FilterFitResult(unk.label, krs, unk.roi, unk.data, resid, pb) end @@ -166,13 +180,13 @@ function fit_spectrum( unk::Spectrum, filt::TopHatFilter, refs::AbstractVector{FilteredReference{T}}, - forcezeros = true, + forcezeros::Bool = true, ) where { T <: AbstractFloat } bestRefs = selectBestReferences(refs) - return filterfit(tophatfilter(ty, unk, filt, 1.0 / dose(unk)), bestRefs, forcezeros) + return filterfit(tophatfilter(ty, unk, filt, one(T) / T(dose(unk))), bestRefs, forcezeros) end -function fit_spectrum( +function fit_spectra( ty::Type{FilteredUnknownW{T}}, unks::AbstractVector{<:Spectrum}, filt::TopHatFilter{T}, @@ -181,7 +195,7 @@ function fit_spectrum( ) where { T <: AbstractFloat } bestRefs = selectBestReferences(refs) return map(unks) do unk - fu = tophatfilter(ty, unk, filt, 1.0 / dose(unk)) + fu = tophatfilter(ty, unk, filt, one(T) / T(dose(unk))) filterfit(fu, bestRefs, forcezeros) end end @@ -194,10 +208,16 @@ fit_spectrum( ) where { T <: AbstractFloat } = # fit_spectrum(FilteredUnknownW{T}, unk, filt, refs, forcezeros) -fit_spectrum( +fit_spectra( unks::AbstractVector{Spectrum}, filt::TopHatFilter{T}, refs::AbstractVector{FilteredReference{T}}, forcezeros = true, ) where { T <: AbstractFloat } = # - fit_spectrum(FilteredUnknownW{T}, unks, filt, refs, forcezeros) + fit_spectra(FilteredUnknownW{T}, unks, filt, refs, forcezeros) + +fit_spectrum( + unks::AbstractVector{Spectrum}, + filt::TopHatFilter{T}, + refs::AbstractVector{FilteredReference{T}}, + forcezeros = true) where { T<: AbstractFloat }= fit_spectra(unks, filt, refs, forcezeros) \ No newline at end of file diff --git a/src/fitlabels.jl b/src/fitlabels.jl index da7c8b3..c072908 100644 --- a/src/fitlabels.jl +++ b/src/fitlabels.jl @@ -34,12 +34,12 @@ Base.isequal(c1::ReferenceLabel, c2::ReferenceLabel) = isequal(c1.roi, c2.roi) && isequal(c1.xrays, c2.xrays) && isequal(c1.spectrum, c2.spectrum) -xrays(cl::ReferenceLabel) = cl.xrays +xrays(cl::ReferenceLabel)::Vector{CharXRay} = cl.xrays spectrum(cl::ReferenceLabel)::Spectrum = spectrum(cl.spectrum) -properties(cl::ReferenceLabel) = properties(cl.spectrum) -hasspectrum(cl::ReferenceLabel) = hasspectrum(cl.spectrum) +properties(cl::ReferenceLabel)::Dict{Symbol,Any} = properties(cl.spectrum) +hasspectrum(cl::ReferenceLabel)::Bool = hasspectrum(cl.spectrum) composition(cl::ReferenceLabel) = get(properties(cl), :Composition, missing) -NeXLCore.element(cl::ReferenceLabel) = element(cl.xrays[1]) +NeXLCore.element(cl::ReferenceLabel)::Element = element(cl.xrays[1]) Base.isless(rl1::ReferenceLabel, rl2::ReferenceLabel) = return isequal(rl1.roi, rl2.roi) ? isless(rl1.spectrum[:Name], rl2.spectrum[:Name]) : ( @@ -67,7 +67,7 @@ end function Base.show(io::IO, cl::CharXRayLabel) comp = composition(cl) - compname = isnothing(comp) ? "Unspecified" : name(comp) + compname = ismissing(comp) ? "Unspecified" : name(comp) print(io,"k[$(name(cl.xrays)), $compname]") end diff --git a/src/fitresult.jl b/src/fitresult.jl index bc7bb22..2569bb2 100644 --- a/src/fitresult.jl +++ b/src/fitresult.jl @@ -1,5 +1,6 @@ using Statistics using DataAPI +using Procrastinate abstract type FitResult end """ @@ -18,7 +19,7 @@ function findlabel(ffr::FitResult, cxr::CharXRay) return lbls[fa[1]] end -Base.show(io::IO, ffr::FitResult) = print(io, "$(ffr.label)") +Base.show(io::IO, ffr::FitResult) = print(io, "FitResult($(ffr.label))") NeXLUncertainties.NeXLUncertainties.value(ffr::FitResult, label::ReferenceLabel) = NeXLUncertainties.value(ffr.kratios, label) NeXLUncertainties.σ(ffr::FitResult, label::ReferenceLabel) = σ(ffr.kratios, label) @@ -181,7 +182,8 @@ Summarize the ROI and k-ratio data within a `FitResult` structure as a `DataFram function NeXLUncertainties.asa(::Type{DataFrame}, fr::FitResult; withUnc = false) rois = labels(fr) res = DataFrame( - ROI = rois, + Spectrum = fill(fr.label, length(rois)), + Feature = rois, K = map(l -> value(fr.kratios, l), rois), ) withUnc && insertcols!(res, :dK => map(l -> σ(fr.kratios, l), rois) ) @@ -209,9 +211,9 @@ Struct elements label::UnknownLabel # Identifies the unknown kratios::UncertainValues # Labeled with ReferenceLabel objects roi::UnitRange{Int} # Range of channels fit - raw::Vector{Float64} # Raw spectrum data - residual::Vector{Float64} # Residual spectrum - peakback::Dict{ReferenceLabel,NTuple{3,Float64}} # peak counts, background counts and counts/(nAs) + raw::Vector{T} # Raw spectrum data + residual::Deferred # Residual spectrum + peakback::Deferred # {Dict{ReferenceLabel,NTuple{3,Float64}}} with peak counts, background counts and counts/(nAs) Use asa(DataFrame, ffr::FilterFitResult) to summarize in tabular form. @@ -221,21 +223,20 @@ struct FilterFitResult{T <: AbstractFloat} <: FitResult kratios::UncertainValues roi::UnitRange{Int} raw::Vector{T} - residual::Vector{T} - peakback::Dict{ReferenceLabel,NTuple{3,T}} + residual::Deferred + peakback::Deferred end +Base.show(io::IO, ffr::FilterFitResult) = print(io, "FitResult($(ffr.label))") + + """ residual(ffr::FilterFitResult)::Spectrum A Spectrum containing the histogram representing the unknown spectrum minus the fitted characteristic peaks shapes times the best fit coefficient. """ -function residual(ffr::FilterFitResult) - props = copy(properties(ffr.label.spectrum)) - props[:Name] = "Residual[$(props[:Name])]" - return Spectrum(ffr.label.spectrum.energy, ffr.residual, props) -end +residual(ffr::FilterFitResult) = ffr.residual() """ spectrum(ffr::FilterFitResult)::Spectrum @@ -250,7 +251,7 @@ spectrum(ffr::FilterFitResult) = ffr.label.spectrum Number of spectrum counts that were accounted for by the fitted elements with the `strip` Element(s) removed. """ characteristiccounts(ffr::FilterFitResult, strip) = - sum(element(ref) in strip ? 0.0 : v[1] - v[2] for (ref, v) in ffr.peakback) # sum(ffr.raw[ffr.roi]-ffr.residual[ffr.roi]) + sum(element(ref) in strip ? 0.0 : v[1] - v[2] for (ref, v) in ffr.peakback()) """ peaktobackground(ffr::FilterFitResult, backwidth::Float64=10.0)::Float64 @@ -263,8 +264,8 @@ function peaktobackground( klabel::ReferenceLabel, backwidth::T = convert(T, 10.0), ) where { T <: AbstractFloat } - unk = spectrum(unknown(ffr)) - peak, back, _ = ffr.peakback[klabel] + unk, pb = spectrum(unknown(ffr)), ffr.peakback() + peak, back, _ = pb[klabel] return (peak * (energy(klabel.roi.stop, unk) - energy(klabel.roi.start, unk))) / (backwidth * back) end @@ -277,7 +278,7 @@ end charOnly::Bool=true, material=nothing, mc = XPP, fc=ReedFluorescence, - columns = () # Selected from ( :roi, :peakback, :counts, :dose ) + columns = ( :counts, ) # Selected from ( :roi, :peakback, :counts, :dose ) )::DataFrame Tabulate details about each region-of-interest in the 'FilterFitResult' in a 'DataFrame'. @@ -294,7 +295,7 @@ Columns: * dK : Float64 - The 1σ uncertainty in :K * :Start : Int - Start index for fit channels (:roi ∈ columns) * :Stop : Int - Stop index for fit channels (:roi ∈ columns) - * :Peak : Float64 - Total counts in characteristic peak (:peakback ∈ columns) + * :Counts : Float64 - Total counts in characteristic peak (peak-back) (:peakback || :counts ∈ columns) * :Back : Float64 - Total counts in background under the characteristic peak (:peakback ∈ columns) * :PtoB : Float64 - Peak-to-Background assuming 10 eV/channel (:peakback ∈ columns) * :KCalc : Float64 - Computed k-ratio assuming a composition. (Requires `material` argument to be specified.) @@ -302,7 +303,6 @@ Columns: * :LiveTime : Float64 - Acquisiton live time (s) (:dose ∈ columns) * :ProbeCurrent : Float64 - Probe current (nA) (:dose ∈ columns) * :DeadPct : Float64 - Dead time in ProbeCurrent (:dose ∈ columns) - * :Counts : Float64 - Total counts in characteristic peak (:counts ∈ columns) * :RefCountsPernAs : Float64 - Estimated counts in :Reference in :Feature per unit dose. (:counts ∈ columns) * :CountsPernAs : Float64 - Estimated counts in :Spectrum in :Feature per unit dose. (:counts ∈ columns) """ @@ -311,7 +311,7 @@ function NeXLUncertainties.asa( ffr::FilterFitResult; charOnly::Bool = true, material::Union{Material,Nothing} = nothing, - columns::Tuple = (), # ( :roi, :peakback, :counts, :dose) + columns::Tuple = ( :counts, ), # ( :roi, :peakback, :counts, :dose) mc = XPP, fc=ReedFluorescence, )::DataFrame sl = charOnly ? # @@ -332,8 +332,9 @@ function NeXLUncertainties.asa( insertcols!(res, 5, :Stop => [ lbl.roi.stop for lbl in sl]) end if :peakback ∈ columns - insertcols!(res, :Peak => [ (ffr.peakback[lbl][1]-ffr.peakback[lbl][2]) for lbl in sl] ) - insertcols!(res, :Back => [ ffr.peakback[lbl][2] for lbl in sl] ) + pb = ffr.peakback() + insertcols!(res, :Counts => [ (pb[lbl][1]-pb[lbl][2]) for lbl in sl] ) + insertcols!(res, :Back => [ pb[lbl][2] for lbl in sl] ) insertcols!(res, :PtoB => [ peaktobackground(ffr, lbl) for lbl in sl] ) end if material isa Material @@ -354,16 +355,17 @@ function NeXLUncertainties.asa( insertcols!(res, :KoKcalc => [ r[:K]/r[:KCalc] for r in eachrow(res) ]) end if :dose ∈ columns - dt(spec) = 100.0*(spec[:RealTime]-spec[:LiveTime])/spec[:RealTime] insertcols!(res, 2, :LiveTime => [ get(unkspec, :LiveTime, missing) for _ in sl ]) - insertcols!(res, 3, :ProbeCurrent => [ get(unkspec, :ProbeCurrent, missing) for _ in sl ]) - insertcols!(res, 4, :DeadPct => [ dt(unkspec) for lbl in sl ]) - + insertcols!(res, 3, :RealTime => [ get(unkspec, :RealTime, missing) for _ in sl ]) + insertcols!(res, 4, :ProbeCurrent => [ get(unkspec, :ProbeCurrent, missing) for _ in sl ]) end if :counts ∈ columns - insertcols!(res, :Counts => [ ffr.peakback[lbl][1]-ffr.peakback[lbl][2] for lbl in sl] ) - insertcols!(res, :RefCountsPernAs => [ ffr.peakback[lbl][3] for lbl in sl] ) - insertcols!(res, :CountsPernAs => [ (ffr.peakback[lbl][1]-ffr.peakback[lbl][2]) / dose(unkspec) for lbl in sl ]) + pb = ffr.peakback() + if !(:peakback ∈ columns) + insertcols!(res, :Counts => [ pb[lbl][1]-pb[lbl][2] for lbl in sl] ) + end + insertcols!(res, :RefCountsPernAs => [ pb[lbl][3] for lbl in sl] ) + insertcols!(res, :CountsPernAs => [ (pb[lbl][1]-pb[lbl][2]) / dose(unkspec) for lbl in sl ]) end return res end diff --git a/src/gadflysupport.jl b/src/gadflysupport.jl index dce4707..acc4f89 100644 --- a/src/gadflysupport.jl +++ b/src/gadflysupport.jl @@ -7,17 +7,18 @@ applied to EDS spectra using the Gadfly.plot(...) functions implemented in `NeXLSpectrum`. """ const NeXLSpectrumStyle = style( - background_color = nothing, - panel_fill = RGB(253 / 255, 253 / 255, 241 / 255), - grid_color = RGB(255 / 255, 223 / 255, 223 / 255), - grid_color_focused = RGB(255 / 255, 200 / 255, 200 / 255), - grid_line_style = :solid, - major_label_color = RGB(32 / 255, 32 / 255, 32 / 255), - major_label_font_size = 9pt, - panel_stroke = RGB(32 / 255, 32 / 255, 32 / 255), - plot_padding = [2pt], - key_title_font_size = 9pt, - key_position = :right, # :bottom + background_color=nothing, + panel_fill=RGB(253 / 255, 253 / 255, 241 / 255), + grid_color=RGB(255 / 255, 223 / 255, 223 / 255), + grid_color_focused=RGB(255 / 255, 200 / 255, 200 / 255), + grid_line_style=:solid, + major_label_color=RGB(32 / 255, 32 / 255, 32 / 255), + major_label_font_size=9pt, + panel_stroke=RGB(32 / 255, 32 / 255, 32 / 255), + plot_padding=[2pt], + key_title_font_size=9pt, + key_position=:right, # :bottom + colorkey_swatch_shape=:square ) @@ -62,53 +63,53 @@ Plot a multiple spectra on a single plot using Gadfly. """ Gadfly.plot( # specs::AbstractVector{Spectrum{<:Real}}; - klms = Union{Element,CharXRay}[], - edges = AtomicSubShell[], - escapes = CharXRay[], - coincidences = CharXRay[], - autoklms = false, - xmin = 0.0, - xmax = missing, - norm = :None, - yscale = 1.05, - ytransform = identity, - style = NeXLSpectrumStyle, - palette = NeXLPalette, + klms=Union{Element,CharXRay}[], + edges=AtomicSubShell[], + escapes=CharXRay[], + coincidences=CharXRay[], + autoklms=false, + xmin=0.0, + xmax=missing, + norm=:None, + yscale=1.05, + ytransform=identity, + style=NeXLSpectrumStyle, + palette=NeXLPalette )::Plot = plot( # specs..., - klms = klms, - edges = edges, - escapes = escapes, - coincidences = coincidences, - autoklms = autoklms, - xmin = xmin, - xmax = xmax, - norm = norm, - yscale = yscale, - ytransform = ytransform, - style = style, - palette = palette, + klms=klms, + edges=edges, + escapes=escapes, + coincidences=coincidences, + autoklms=autoklms, + xmin=xmin, + xmax=xmax, + norm=norm, + yscale=yscale, + ytransform=ytransform, + style=style, + palette=palette, ) function Gadfly.plot( specs::Spectrum{<:Real}...; - klms = Union{Element,CharXRay}[], - edges = AtomicSubShell[], - escapes = CharXRay[], - coincidences = CharXRay[], - autoklms = false, - xmin = 0.0, - xmax = missing, - legend = true, - norm = NoScaling(), - yscale = 1.05, - ytransform = identity, - style = NeXLSpectrumStyle, - palette = NeXLPalette, - customlayers = Gadfly.Layer[], - duanehunt = false, - title = nothing, - minklmweight = 1.0e-3 + klms=Union{Element,CharXRay}[], + edges::AbstractArray{AtomicSubShell}=AtomicSubShell[], + escapes::Union{AbstractVector{Element},AbstractVector{CharXRay}}=CharXRay[], + coincidences::AbstractArray{CharXRay}=CharXRay[], + autoklms=false, + xmin=0.0, + xmax=missing, + legend=true, + norm=NoScaling(), + yscale=1.05, + ytransform=identity, + style=NeXLSpectrumStyle, + palette=NeXLPalette, + customlayers=Gadfly.Layer[], + duanehunt=false, + title=nothing, + minklmweight=1.0e-3 )::Plot function klmLayer(specdata, cxrs::AbstractArray{CharXRay}) d = Dict{Any,Vector{CharXRay}}() @@ -130,15 +131,15 @@ function Gadfly.plot( end end end - return layer( - x = x, - y = y, - label = label, + return length(x) > 0 ? layer( + x=x, + y=y, + label=label, Geom.hair, Geom.point, - Geom.label(position = :above), - Theme(default_color = colorant"antiquewhite"), - ) + Geom.label(position=:above), + Theme(default_color=colorant"gray55"), + ) : nothing end function edgeLayer(maxI, ashs::AbstractArray{AtomicSubShell}) d = Dict{Any,Vector{AtomicSubShell}}() @@ -155,36 +156,43 @@ function Gadfly.plot( push!(label, "$(ash)") end end - return layer( - x = x, - y = y, - label = label, + return length(x) > 0 ? layer( + x=x, + y=y, + label=label, Geom.hair, - Geom.label(position = :right), - Theme(default_color = colorant"lightgray"), - ) + Geom.label(position=:right), + Theme(default_color=colorant"lightgray") + ) : nothing end - function siEscapeLayer(crxs) + function siEscapeLayer(cxrs::AbstractVector{CharXRay}, maxE) x, y, label = [], [], [] - for xrs in crxs + for xrs in cxrs eesc = energy(xrs) - energy(n"Si K-L3") - if eesc > 0.0 - ich = maximum( - get(specdata[i], channel(eesc, specs[i]), 0.0) for i in eachindex(specs) - ) - push!(x, eesc) - push!(y, ytransform(ich)) - push!(label, "$(element(xrs).symbol)\nesc") - end + ich = maximum( + get(specdata[i], channel(eesc, specs[i]), 0.0) for i in eachindex(specs) + ) + push!(x, eesc) + push!(y, ytransform(ich)) + push!(label, "$(element(xrs).symbol)\nesc") end - return layer( - x = x, - y = y, - label = label, + return length(x) > 0 ? layer( + x=x, + y=y, + label=label, Geom.hair, - Geom.label(position = :above), - Theme(default_color = colorant"black"), - ) + Geom.label(position=:above), + Theme(default_color=colorant"black"), + ) : nothing + end + function siEscapeLayer(els::AbstractVector{Element}, maxE) + cxrs = mapreduce(append!, els) do el + mapreduce(append!, (ktransitions, ltransitions, mtransitions), init=CharXRay[]) do trs + cx = characteristic(el, trs) + (length(cx) > 0) && (50.0 < energy(brightest(cx)) - enx"Si K-L3" < maxE) ? [brightest(cx)] : CharXRay[] + end + end + siEscapeLayer(cxrs, maxE) end function sumPeaks(cxrs) x, y, label = [], [], [] @@ -201,14 +209,14 @@ function Gadfly.plot( end end end - return layer( - x = x, - y = y, - label = label, + return length(x) > 0 ? layer( + x=x, + y=y, + label=label, Geom.hair, - Geom.label(position = :above), - Theme(default_color = colorant"gray"), - ) + Geom.label(position=:above), + Theme(default_color=colorant"gray"), + ) : nothing end @assert length(specs) <= length(palette) "The palette must specify at least as many colors as spectra." specdata = [scaledcounts(norm, s) for s in specs] @@ -216,9 +224,9 @@ function Gadfly.plot( maxI, maxE, maxE0 = 16, 1.0e3, 1.0e3 names, layers = String[], Layer[] append!(layers, customlayers) - if duanehunt - if length(specs)==1 - try + if duanehunt + if length(specs) == 1 + try p = _duane_hunt_impl(specs[1]) es = (0.9*p[2]):10.0:(1.05*p[2]) _duane_hunt_func(es, p) @@ -227,10 +235,10 @@ function Gadfly.plot( @warn err.msg end else - dhx, dhy = Float64[], Float64[] + dhx, dhy = Float64[], Float64[] for i in eachindex(specs) append!(dhx, duane_hunt(specs[i])) - append!(dhy, 0.9*maxI*yscale) + append!(dhy, 0.9 * maxI * yscale) end append!(layers, layer(x=dhx, y=dhy, color=palette[eachindex(specs)], Geom.hair(orientation=:vertical), Geom.point)) end @@ -252,22 +260,24 @@ function Gadfly.plot( maxE0 = ismissing(mE0) ? maxE : max(maxE, mE0) push!(names, spec[:Name]) ly = Gadfly.layer( - x = energyscale(spec, chs), - y = ytransform.(specdata[i][chs]), # + x=energyscale(spec, chs), + y=ytransform.(specdata[i][chs]), # Geom.step, - Theme(default_color = palette[i]), + Theme(default_color=palette[i]), ) append!(layers, ly) end autoklms && append!(klms, mapreduce(s -> elms(s, true), union!, specs)) if length(klms) > 0 - tr(elm::Element) = filter(characteristic(elm, alltransitions, minklmweight, maxE0)) do cxr - energy(cxr) > min(200.0, maxE0/25) - end + tr(elm::Element) = + filter(characteristic(elm, alltransitions, minklmweight, maxE0)) do cxr + energy(cxr) > min(200.0, maxE0 / 25) + end tr(cxr::CharXRay) = [cxr] pklms = mapreduce(klm -> tr(klm), append!, klms) if length(pklms) > 0 - append!(layers, klmLayer(specdata, pklms)) + l = klmLayer(specdata, pklms) + (!isnothing(l)) && append!(layers, l) end end if length(edges) > 0 @@ -275,14 +285,17 @@ function Gadfly.plot( shs(ash::AtomicSubShell) = [ash] pedges = mapreduce(ash -> shs(ash), append!, edges) if length(pedges) > 0 - append!(layers, edgeLayer(0.5 * maxI, pedges)) + l = edgeLayer(0.5 * maxI, pedges) + (!isnothing(l)) && append!(layers, l) end end if length(escapes) > 0 - append!(layers, siEscapeLayer(escapes)) + l = siEscapeLayer(escapes, maxE) + (!isnothing(l)) && append!(layers, l) end if length(coincidences) > 0 - append!(layers, sumPeaks(coincidences)) + l = sumPeaks(coincidences) + (!isnothing(l)) && append!(layers, l) end Gadfly.with_theme(style) do leg = @@ -292,6 +305,7 @@ function Gadfly.plot( length(specs) > 1 ? "Spectra" : "Spectrum", names, palette[1:length(specs)], + pos=[0.8w, 0.0h] # 80# over, centered ), ) : tuple() try @@ -299,13 +313,13 @@ function Gadfly.plot( layers..., Guide.XLabel("Energy (eV)"), Guide.YLabel(ylbl), - Scale.x_continuous(format = :plain), - Scale.y_continuous(format = :plain), + Scale.x_continuous(format=:plain), + Scale.y_continuous(format=:plain), Coord.Cartesian( - ymin = 0, - ymax = ytransform(yscale * maxI), - xmin = convert(Float64, xmin), - xmax = maxE, + ymin=0, + ymax=ytransform(yscale * maxI), + xmin=convert(Float64, xmin), + xmax=maxE, ), Guide.title(title), leg..., @@ -315,13 +329,13 @@ function Gadfly.plot( layers..., Guide.XLabel("Energy (eV)"), Guide.YLabel(ylbl), - Scale.x_continuous(format = :plain), - Scale.y_continuous(format = :plain), + Scale.x_continuous(format=:plain), + Scale.y_continuous(format=:plain), Coord.Cartesian( - ymin = 0, - ymax = ytransform(yscale * maxI), - xmin = convert(Float64, xmin), - xmax = maxE, + ymin=0, + ymax=ytransform(yscale * maxI), + xmin=convert(Float64, xmin), + xmax=maxE, ), Guide.title(title), leg..., @@ -348,41 +362,43 @@ Plot the sample spectrum, the residual and fit regions-of-interests and the asso """ function Gadfly.plot( ffr::FilterFitResult, - roi::Union{Nothing,AbstractUnitRange{<:Integer}} = nothing; - palette = NeXLPalette, - style = NeXLSpectrumStyle, - xmax::Union{AbstractFloat, Nothing} = nothing, - comp::Union{Material, Nothing} = nothing, - det::Union{EDSDetector, Nothing} = nothing, - resp::Union{AbstractArray{<:AbstractFloat,2},Nothing} = nothing, - yscale = 1.0 + roi::Union{Nothing,AbstractUnitRange{<:Integer}}=nothing; + palette=NeXLPalette, + style=NeXLSpectrumStyle, + xmax::Union{AbstractFloat,Nothing}=nothing, + comp::Union{Material,Nothing}=nothing, + det::Union{EDSDetector,Nothing}=nothing, + resp::Union{AbstractArray{<:AbstractFloat,2},Nothing}=nothing, + yscale=1.0 ) + fspec = spectrum(ffr) function defroi(ffrr) # Compute a reasonable default display ROI tmp = minimum( lbl.roi[1] for lbl in keys(ffrr.kratios) ):maximum(lbl.roi[end] for lbl in keys(ffrr.kratios)) return max( - lld(ffr.label.spectrum), + lld(fspec), tmp[1] - length(ffrr.roi) ÷ 40, ):min(tmp[end] + length(ffrr.roi) ÷ 10, ffrr.roi[end]) end roilt(l1, l2) = isless(l1.roi[1], l2.roi[1]) - roi = something(roi, defroi(ffr)) + roi, resid = something(roi, defroi(ffr)), residual(ffr).counts layers = [ - layer(x = roi, y = ffr.residual[roi], Geom.step, Theme(default_color = palette[2])), - layer(x = roi, y = ffr.raw[roi], Geom.step, Theme(default_color = palette[1])), + layer(x=roi, y=resid[roi], Geom.step, Theme(default_color=palette[2])), + layer(x=roi, y=ffr.raw[roi], Geom.step, Theme(default_color=palette[1])), ] # If the information is available,also model the continuum - comp = isnothing(comp) ? get(spectrum(ffr), :Composition, nothing) : comp - det = isnothing(det) ? get(spectrum(ffr), :Detector, nothing) : det + comp = isnothing(comp) ? get(fspec, :Composition, nothing) : comp + det = isnothing(det) ? get(fspec, :Detector, nothing) : det if !any(isnothing.((comp, resp, det))) - cc = fitcontinuum(spectrum(ffr), det, resp) + cc = fitcontinuum(fspec, det, resp) push!(layers, layer(x=roi, y=cc[roi], Geom.line, Theme(default_color=palette[2]))) end + scroi = min(channel(100.0, fspec), length(fspec)):roi.stop miny, maxy, prev, i = - minimum(ffr.residual[roi]), 3.0 * yscale * maximum(ffr.residual[roi]), -1000, -1 - for lbl in sort(collect(keys(ffr.kratios)), lt = roilt) + minimum(resid[scroi]), 3.0 * yscale * maximum(resid[scroi]), -1000, -1 + for lbl in sort(collect(keys(ffr.kratios)), lt=roilt) if NeXLUncertainties.value(ffr, lbl) > 0.0 # This logic keeps the labels on different lines (mostly...) i, prev = @@ -393,24 +409,24 @@ function Gadfly.plot( push!( layers, layer( - x = [lbl.roi[1], lbl.roi[end]], - y = maxy * [0.4 + 0.1 * i, 0.4 + 0.1 * i], - label = labels, + x=[lbl.roi[1], lbl.roi[end]], + y=maxy * [0.4 + 0.1 * i, 0.4 + 0.1 * i], + label=labels, Geom.line, Geom.point, - Geom.label(position = :right), - Theme(default_color = "gray"), + Geom.label(position=:right), + Theme(default_color="gray"), ), ) # Plot the k-ratio as a label above ROI push!( layers, layer( - x = [0.5 * (lbl.roi[1] + lbl.roi[end])], - y = maxy * [0.4 + 0.1 * i], - label = [@sprintf("%1.4f", NeXLUncertainties.value(ffr, lbl))], - Geom.label(position = :above), - Theme(default_color = "gray"), + x=[0.5 * (lbl.roi[1] + lbl.roi[end])], + y=maxy * [0.4 + 0.1 * i], + label=[@sprintf("%1.4f", NeXLUncertainties.value(ffr, lbl))], + Geom.label(position=:above), + Theme(default_color="gray"), ), ) end @@ -419,10 +435,10 @@ function Gadfly.plot( plot( layers..., Coord.cartesian( - xmin = roi[1], - xmax = something(xmax, roi[end]), - ymin = min(1.1 * miny, 0.0), - ymax = maxy, + xmin=roi[1], + xmax=something(xmax, roi[end]), + ymin=min(1.1 * miny, 0.0), + ymax=maxy, ), Guide.XLabel("Channels"), Guide.YLabel("Counts"), @@ -436,25 +452,25 @@ end Plot a filtered reference spectrum. """ -function Gadfly.plot(fr::FilteredReference; palette = NeXLPalette) +function Gadfly.plot(fr::FilteredReference; palette=NeXLPalette) roicolors = Colorant[RGB(0.9, 1.0, 0.9), RGB(0.95, 0.95, 1.0)] layers = [ - layer(x = fr.ffroi, y = fr.data, Theme(default_color = palette[1]), Geom.step), - layer(x = fr.ffroi, y = fr.filtered, Theme(default_color = palette[2]), Geom.step), - layer(x = fr.roi, y = fr.charonly, Theme(default_color = palette[3]), Geom.step), + layer(x=fr.ffroi, y=fr.data, Theme(default_color=palette[1]), Geom.step), + layer(x=fr.ffroi, y=fr.filtered, Theme(default_color=palette[2]), Geom.step), + layer(x=fr.roi, y=fr.charonly, Theme(default_color=palette[3]), Geom.step), layer( - xmin = [fr.ffroi[1], fr.roi[1]], - xmax = [fr.ffroi[end], fr.roi[end]], + xmin=[fr.ffroi[1], fr.roi[1]], + xmax=[fr.ffroi[end], fr.roi[end]], Geom.vband, - color = roicolors, + color=roicolors, ), ] try plot( layers..., Coord.cartesian( - xmin = fr.ffroi[1] - length(fr.ffroi) ÷ 10, - xmax = fr.ffroi[end] + length(fr.ffroi) ÷ 10, + xmin=fr.ffroi[1] - length(fr.ffroi) ÷ 10, + xmax=fr.ffroi[end] + length(fr.ffroi) ÷ 10, ), Guide.xlabel("Channel"), Guide.ylabel("Counts"), @@ -469,8 +485,8 @@ function Gadfly.plot(fr::FilteredReference; palette = NeXLPalette) plot( layers..., Coord.cartesian( - xmin = fr.ffroi[1] - length(fr.ffroi) ÷ 10, - xmax = fr.ffroi[end] + length(fr.ffroi) ÷ 10, + xmin=fr.ffroi[1] - length(fr.ffroi) ÷ 10, + xmax=fr.ffroi[end] + length(fr.ffroi) ÷ 10, ), Guide.xlabel("Channel"), Guide.ylabel("Counts"), @@ -504,13 +520,13 @@ function Gadfly.plot(vq::VectorQuant, chs::UnitRange) RGB(0, 0, 0), RGB(0 / 255, 168 / 255, 45 / 255), ], - transform = deuteranopic, + transform=deuteranopic, )[3:end] lyrs = mapreduce( i -> layer( - x = chs, - y = vq.vectors[i, chs], - Theme(default_color = colors[i]), + x=chs, + y=vq.vectors[i, chs], + Theme(default_color=colors[i]), Geom.line, ), append!, @@ -524,7 +540,7 @@ function Gadfly.plot(vq::VectorQuant, chs::UnitRange) Guide.manual_color_key( "Vector", [repr(r[1]) for r in vq.references], - color = Colorant[colors...], + color=Colorant[colors...], ), ) catch @@ -542,13 +558,13 @@ end Plots the detector efficiency function assuming the detector is perpendicular to the incident X-rays. """ -function Gadfly.plot(deteff::DetectorEfficiency, emax = 20.0e3) +function Gadfly.plot(deteff::DetectorEfficiency, emax=20.0e3) eff(ee) = efficiency(deteff, ee, π / 2) plot(eff, 100.0, emax) end function plotandimage(plot::Gadfly.Plot, image::Array) - io = IOBuffer(maxsize = 10 * 1024 * 1024) + io = IOBuffer(maxsize=10 * 1024 * 1024) save(Stream(format"PNG", io), image) pix = max(size(image, 1), size(image, 2)) scaleX, scaleY = size(image, 1) / pix, size(image, 2) / pix @@ -575,7 +591,7 @@ end Plots the reference spectra which were used to construct a `FilterFitPacket`. """ Gadfly.plot(ffp::FilterFitPacket; kwargs...) = - plot(unique(spectra(ffp))...; klms = collect(elms(ffp)), kwargs...) + plot(unique(spectra(ffp))...; klms=collect(elms(ffp)), kwargs...) """ plot_compare(specs::AbstractArray{<:Spectrum}, mode=:Plot; xmin=100.0, xmax=1.0, palette = NeXLPalette) @@ -585,23 +601,23 @@ mean of the other spectra. Count statistics are taken into account so if the sp statistics we expect a mean of 0.0 and a standard deviation of 1.0 over all channels. Note: xmax is relative to the :BeamEnergy. """ -function plot_compare(specs::AbstractArray{<:Spectrum}, mode=:Plot; xmin=100.0, xmax=1.0, palette = NeXLPalette) - channels(spec) = channel(100.0, spec):channel(xmax*get(spec,:BeamEnergy,20.0e3),spec) - if mode==:Plot +function plot_compare(specs::AbstractArray{<:Spectrum}, mode=:Plot; xmin=100.0, xmax=1.0, palette=NeXLPalette) + channels(spec) = channel(100.0, spec):channel(xmax * get(spec, :BeamEnergy, 20.0e3), spec) + if mode == :Plot layers = [ - layer(x=energyscale(specs[i],channels(specs[i])), y = sigma(specs[i], specs, channels(specs[i])), - Theme(default_color = palette[i], alphas=[0.4])) - for i in eachindex(specs) + layer(x=energyscale(specs[i], channels(specs[i])), y=sigma(specs[i], specs, channels(specs[i])), + Theme(default_color=palette[i], alphas=[0.4])) + for i in eachindex(specs) ] plot(layers..., Guide.xlabel("Energy (eV)"), Guide.ylabel("σ"), - Guide.manual_color_key("Spectra", [String(spec[:Name]) for spec in specs], palette[eachindex(specs)]), - Coord.cartesian(xmin=100.0, xmax=xmax*maximum(get(spec, :BeamEnergy, 20.0e3) for spec in specs)) + Guide.manual_color_key("Spectra", [String(spec[:Name]) for spec in specs], palette[eachindex(specs)]), + Coord.cartesian(xmin=100.0, xmax=xmax * maximum(get(spec, :BeamEnergy, 20.0e3) for spec in specs)) ) - elseif mode==:Histogram + elseif mode == :Histogram layers = [ - layer(x = sigma(specs[i], specs, channels(specs[i])), Geom.histogram(), - Theme(default_color = palette[i], alphas=[0.2])) - for i in eachindex(specs) + layer(x=sigma(specs[i], specs, channels(specs[i])), Geom.histogram(), + Theme(default_color=palette[i], alphas=[0.2])) + for i in eachindex(specs) ] plot(layers..., Guide.xlabel("σ"), Guide.manual_color_key("Spectra", [String(spec[:Name]) for spec in specs], palette[eachindex(specs)])) else @@ -614,15 +630,163 @@ end Compare spectra collected simultaneously on multiple detectors in a single acquisition. """ -function plot_multicompare(specs::AbstractArray{Spectrum{T}}; minE=200.0, maxE=0.5*specs[1][:BeamEnergy]) where { T<: Real} +function plot_multicompare(specs::AbstractArray{Spectrum{T}}; minE=200.0, maxE=0.5 * specs[1][:BeamEnergy]) where {T<:Real} s, mcs = specs[1], multicompare(specs) - chs = max(1,channel(minE, s)): min(channel(maxE, s), length(s)) - xx = map(i->energy(i,s), chs) + chs = max(1, channel(minE, s)):min(channel(maxE, s), length(s)) + xx = map(i -> energy(i, s), chs) plot( - (layer(x=xx, y=view(mc,chs), Geom.line, Theme(default_color=c)) for (c,mc) in zip(NeXLPalette[1:length(mcs)], mcs))..., + (layer(x=xx, y=view(mc, chs), Geom.line, Theme(default_color=c)) for (c, mc) in zip(NeXLPalette[1:length(mcs)], mcs))..., Guide.xlabel("Energy (eV)"), Guide.ylabel("Ratio") ) end +""" + plot(wind::Union{AbstractWindow, AbstractArray{<:AbstractWindow}}; xmax=20.0e3, angle=π/2, style=NeXLSpectrumStyle) + + +Plot the window transmission function. +""" +function Gadfly.plot(winds::AbstractArray{<:AbstractWindow}; xmin=0.0, xmax=20.0e3, angle=π / 2, style=NeXLSpectrumStyle) + Gadfly.with_theme(style) do + es = max(xmin, 10.0):10.0:xmax + lyr(w, c) = layer(x=es, y=map(e -> transmission(w, e, angle), es), Theme(default_color=c), Geom.line) + plot( + (lyr(w, c) for (w, c) in zip(winds, NeXLPalette[eachindex(winds)]))..., + Coord.cartesian( + xmin=xmin, + xmax=xmax, + ymin=0.0, + ymax=1.0, + ), + Guide.xlabel("Energy (eV)"), + Guide.ylabel("Transmission"), + Guide.manual_color_key("Window", [name(w) for w in winds], NeXLPalette[eachindex(winds)]) + ) + end +end +Gadfly.plot(wind::AbstractWindow; xmin=0.0, xmax=20.0e3, angle=π / 2, style=NeXLSpectrumStyle) = # + plot([wind], xmin=xmin, xmax=xmax, angle=angle, style=style) + +""" + Gadfly.plot( + dfr::DirectFitResult, + roi::Union{Nothing,AbstractUnitRange{<:Integer}} = nothing; + palette = NeXLPalette, + style = NeXLSpectrumStyle, + xmax::Union{AbstractFloat, Nothing} = nothing, + comp::Union{Material, Nothing} = nothing, + det::Union{EDSDetector, Nothing} = nothing, + resp::Union{AbstractArray{<:AbstractFloat,2},Nothing} = nothing, + yscale = 1.0 + ) + +Plot the sample spectrum, the residual and fit regions-of-interests and the associated k-ratios. +""" +function Gadfly.plot( + dfr::DirectFitResult, + roi::Union{Nothing,AbstractUnitRange{<:Integer}}=nothing; + palette=NeXLPalette, + style=NeXLSpectrum.NeXLSpectrumStyle, + xmax::Union{AbstractFloat,Nothing}=nothing, + comp::Union{Material,Nothing}=nothing, + det::Union{EDSDetector,Nothing}=nothing, + resp::Union{AbstractArray{<:AbstractFloat,2},Nothing}=nothing, + yscale=1.0 +) + dspec = dfr.label.spectrum + function defroi(ddffrr) # Compute a reasonable default display ROI + raw = ddffrr.label.spectrum.counts + res = ddffrr.residual().counts + mx = findlast(i -> raw[i] != res[i], eachindex(raw)) + mx = min(max(mx + mx ÷ 5, 100), length(raw)) + mn = channel(0.0, ddffrr.label.spectrum) + return mn:mx + end + roilt(l1, l2) = isless(l1.roi[1], l2.roi[1]) + roi, resid = something(roi, defroi(dfr)), residual(dfr).counts + layers = [ + layer(x=roi, y=counts(dfr.continuum, roi), Geom.step, Theme(default_color=palette[3])), + layer(x=roi, y=resid[roi], Geom.step, Theme(default_color=palette[2])), + layer(x=roi, y=counts(dspec, roi), Geom.step, Theme(default_color=palette[1])), + ] + # If the information is available,also model the continuum + comp = isnothing(comp) ? get(dspec, :Composition, nothing) : comp + det = isnothing(det) ? get(dspec, :Detector, nothing) : det + if !any(isnothing.((comp, resp, det))) + cc = fitcontinuum(dspec, det, resp) + push!(layers, layer(x=roi, y=cc[roi], Geom.line, Theme(default_color=palette[2]))) + end + scroi = min(channel(100.0, dspec), length(dspec)):roi.stop + miny, maxy, prev, i = + minimum(resid[scroi]), 3.0 * yscale * maximum(resid[scroi]), -1000, -1 + for lbl in sort(collect(keys(dfr.kratios)), lt=roilt) + if NeXLUncertainties.value(dfr.kratios, lbl) > 0.0 + # This logic keeps the labels on different lines (mostly...) + i, prev = + (lbl.roi[1] > prev + length(roi) ÷ 10) || (i == 6) ? (0, lbl.roi[end]) : + (i + 1, prev) + labels = ["", name(lbl.xrays)] + # Plot the ROI + push!( + layers, + layer( + x=[lbl.roi[1], lbl.roi[end]], + y=maxy * [0.4 + 0.1 * i, 0.4 + 0.1 * i], + label=labels, + Geom.line, + Geom.point, + Geom.label(position=:right), + Theme(default_color="gray"), + ), + ) + # Plot the k-ratio as a label above ROI + push!( + layers, + layer( + x=[0.5 * (lbl.roi[1] + lbl.roi[end])], + y=maxy * [0.4 + 0.1 * i], + label=[@sprintf("%1.4f", NeXLUncertainties.value(dfr, lbl))], + Geom.label(position=:above), + Theme(default_color="gray"), + ), + ) + end + end + Gadfly.with_theme(style) do + plot( + layers..., + Coord.cartesian( + xmin=roi[1], + xmax=something(xmax, roi[end]), + ymin=min(1.1 * miny, 0.0), + ymax=maxy, + ), + Guide.XLabel("Channels"), + Guide.YLabel("Counts"), + Guide.title("$(dfr.label)"), + ) + end +end + +function Gadfly.plot(dr::DirectReference) + sp = dr.label.spectrum + bc = copy(sp.counts) + bc[dr.roi] -= dr.data + back = Spectrum(sp.energy, bc, copy(sp.properties)) + extroi = max(1, dr.roi.start - length(dr.roi) ÷ 3):min(length(sp), dr.roi.stop + length(dr.roi) ÷ 3) + plot( + layer(x=extroi, y=sp.counts[extroi], Geom.step, Theme(default_color=NeXLPalette[1])), + layer(x=extroi, y=back.counts[extroi], Geom.step, Theme(default_color=NeXLPalette[2])), + Guide.title("$(dr.label)"), Coord.cartesian(xmin=extroi.start, xmax=extroi.stop), + Guide.xlabel("Channel"), Guide.ylabel("Counts") + ) +end + +function Gadfly.plot(drs::DirectReferences; cols=3) + plts = [plot(ref) for ref in drs.references] + foreach(_ -> push!(plts, plot()), 1:((cols-length(drs.references)%cols)%cols)) + gridstack(reshape(plts, length(plts) ÷ cols, cols)) +end + @info "Loading Gadfly support into NeXLSpectrum." diff --git a/src/hyperspectrum.jl b/src/hyperspectrum.jl index 72c7680..e64babc 100644 --- a/src/hyperspectrum.jl +++ b/src/hyperspectrum.jl @@ -51,7 +51,7 @@ struct HyperSpectrum{T<:Real, N, NP} <: AbstractArray{Spectrum{T}, N} function HyperSpectrum(energy::EnergyScale, props::Dict{Symbol,Any}, arr::Array{<:Real}; axisnames = ( "Y", "X", "Z", "A", "B", "C" ), # fov = [ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ], # - offset = 0.0 * collect(fov), # + offset = zeros(length(fov)), # stagemap::Type{<:StageMapping}=DefaultStageMapping, # livetime=fill(get(props, :LiveTime, 1.0), size(arr)[2:end]...) ) diff --git a/src/llsq.jl b/src/llsq.jl index 55e3f8c..e88065b 100644 --- a/src/llsq.jl +++ b/src/llsq.jl @@ -1,181 +1,3 @@ -using MultivariateStats: cov_whitening -using NeXLUncertainties - -""" - olssvd(y::AbstractVector{N}, a::AbstractMatrix{N}, sigma::N, xLabels::Vector{<:Label}, tol::N=convert(N,1.0e-10))::UncertainValues where N <: AbstractFloat - -Solves the ordinary least squares problem a⋅x = y for x using singular value decomposition for AbstractFloat-based types. -""" -function olssvd( - y::AbstractVector{N}, - a::AbstractMatrix{N}, - sigma::N, - xLabels::Vector{<:Label}, - tol::N = convert(N, 1.0e-10) -) where {N<:AbstractFloat} - f = svd(a) - mins = tol * maximum(f.S) - fs = [s > mins ? one(N) / s : zero(N) for s in f.S] - # Note: w*a = f.U * Diagonal(f.S) * f.Vt - genInv = f.V * Diagonal(fs) * transpose(f.U) - #cov = sigma^2*f.V*(Diagonal(fs)*Diagonal(fs))*Transpose(f.V) - cov = sigma^2 * genInv * transpose(genInv) - return uvs(xLabels, genInv * y, cov) -end - -""" - olspinv(y::AbstractVector{N}, a::AbstractMatrix{N}, v::Matrix{N}, xlabels::Vector{<:Label}, tol::N=convert(N,1.0e-10))::UncertainValues - -Solves the ordinary least squares problem a⋅x = y for x using the pseudo-inverse for AbstractFloat-based types. -""" -function olspinv( - y::AbstractVector{N}, - a::AbstractMatrix{N}, - sigma::N, - xLabels::Vector{<:Label}, - ::N = convert(N, 1.0e-10), -) where {N<:AbstractFloat} - genInv = pinv(a, rtol = 1.0e-6) - return uvs(xLabels, genInv * y, sigma * genInv * transpose(genInv)) -end - -""" - glspinv(y::AbstractVector{N}, a::AbstractMatrix{N}, v::Matrix{N}, xlabels::Vector{<:Label}, tol::N=convert(N,1.0e-10))::UncertainValues - -Solve the generalized least squares problem y = x β + ϵ for β where ϵ ~ v σ using the pseudo-inverse. - -β = (xᵀv⁻¹x)⁻¹xᵀv⁻¹y where a⁻¹ => pinv(a) - -cov[β] = (xᵀv⁻¹x)⁻¹ -""" -function glspinv( - y::AbstractVector{N}, - x::AbstractMatrix{N}, - v::AbstractMatrix{N}, - xLabels::Vector{<:Label}, - ::N = convert(N, 1.0e-10), -) where {N<:AbstractFloat} - txiv = transpose(x) * pinv(v) - yp, ixp = txiv * y, pinv(txiv * x) - return uvs(xLabels, ixp * yp, ixp) -end - -""" - glsinv(y::AbstractVector{N}, a::AbstractMatrix{N}, v::Matrix{N}, xlabels::Vector{<:Label}, tol::N=convert(N,1.0e-10))::UncertainValues - -Solve the generalized least squares problem y = x β + ϵ for β where ϵ ~ v σ using the inverse. -""" -function glsinv( - y::AbstractVector{N}, - x::AbstractMatrix{N}, - v::AbstractMatrix{N}, - xLabels::Vector{<:Label}, - ::N = convert(N, 1.0e-10), -) where {N<:AbstractFloat} - txiv = transpose(x) * inv(v) - yp, ixp = txiv * y, inv(txiv * x) - return uvs(xLabels, ixp * yp, ixp) -end - - -""" - glssvd(y::AbstractVector{N}, a::AbstractMatrix{N}, cov::Matrix{N}, xlabels::Vector{<:Label}, tol::N=convert(N,1.0e-10))::UncertainValues - -Solves the generalized least squares problem y = x β + ϵ for β using covariance whitening and the ordinary least squares pseudo-inverse. -""" -function glssvd( - y::AbstractVector{N}, - x::AbstractMatrix{N}, - cov::AbstractMatrix{N}, - xLabels::Vector{<:Label}, - tol::N = convert(N, 1.0e-10), -) where {N<:AbstractFloat} - checkcovariance!(cov) - w = cov_whitening(Matrix(cov)) - #olssvd(w*y, w*a, one(N), xLabels, tol) - return olspinv(w * y, w * x, one(N), xLabels, tol) -end - -""" - glschol(y::AbstractVector{N}, a::AbstractMatrix{N}, cov::Matrix{N}, xlabels::Vector{<:Label}, tol::N=convert(N,1.0e-10))::UncertainValues - -Solves the generalized least squares problem y = x β + ϵ for β using covariance whitening and the ordinary least squares pseudo-inverse. -""" -function glschol( - y::AbstractVector{N}, - x::AbstractMatrix{N}, - cov::AbstractMatrix{N}, - xLabels::Vector{<:Label}, - tol::N = convert(N, 1.0e-10), -) where {N<:AbstractFloat} - checkcovariance!(cov) - w = inv(cholesky(cov).L) - return olspinv(w * y, w * x, one(N), xLabels, tol) -end - - -""" - wlssvd(y::AbstractVector{N}, a::AbstractMatrix{N}, cov::AbstractVector{N}, xlabels::Vector{<:Label}, tol::N=convert(N,1.0e-10))::UncertainValues - -Solves the weighted least squares problem y = x β + ϵ for β using singular value decomposition for AbstractFloat-based types. -""" -function wlssvd( - y::AbstractVector{N}, - x::AbstractMatrix{N}, - cov::AbstractVector{N}, - xLabels::Vector{<:Label}, - tol::N = convert(N, 1.0e-10), -) where {N<:AbstractFloat} - w = Diagonal([sqrt(one(N) / cv) for cv in cov]) - return olssvd(w * y, w * x, one(N), xLabels, tol) -end - -""" - wlspinv(y::AbstractVector{N}, a::AbstractMatrix{N}, cov::AbstractVector{N}, xlabels::Vector{<:Label}, tol::N=convert(N,1.0e-10))::UncertainValues - -Solves the weighted least squares problem a⋅x = y for x using singular value decomposition for AbstractFloat-based types. -""" -function wlspinv( - y::AbstractVector{N}, - a::AbstractMatrix{N}, - cov::AbstractVector{N}, - xLabels::Vector{<:Label}, - tol::N = convert(N, 1.0e-10), -) where {N<:AbstractFloat} - w = Diagonal([sqrt(one(N) / cv) for cv in cov]) - return olspinv(w * y, w * a, one(N), xLabels, tol) -end - -""" - wlspinv2(y::AbstractVector{N}, a::AbstractMatrix{N}, cov::AbstractVector{N}, covscales::AbstractVector{N}, xLabels::Vector{<:Label}, tol::N=convert(N,1.0e-10))::UncertainValues where N <: AbstractFloat - -Solves the weighted least squares problem a⋅x = y for x using singular value decomposition for AbstractFloat-based types. Rescales the rescaleCovariances -according to `covscales`. -""" -function wlspinv2( - y::AbstractVector{N}, - a::AbstractMatrix{N}, - cov::AbstractVector{N}, - covscales::AbstractVector{N}, - xLabels::Vector{<:Label}, - tol::N = convert(N, 1.0e-10), -) where {N<:AbstractFloat} - function rescaleCovariances( - uvs::UncertainValues, - covscales::AbstractVector{N}, - )::UncertainValues - cov = copy(uvs.covariance) - # rwgts = map(sqrt,wgts) - a = Base.axes(cov) - for r in a[1], c in a[2] - cov[r, c] *= covscales[r] * covscales[c] - end - return UncertainValues(uvs.labels, uvs.values, cov) - end - w = Diagonal([sqrt(one(N) / cv) for cv in cov]) - return rescaleCovariances(olspinv(w * y, w * a, one(N), xLabels, tol), covscales) -end - """ simple_linear_regression(x::AbstractVector{<:Real}, y::AbstractVector{<:Real})::Tuple{Real, Real} diff --git a/src/qquant.jl b/src/qquant.jl index fb4ef68..057a809 100644 --- a/src/qquant.jl +++ b/src/qquant.jl @@ -72,16 +72,26 @@ function fit_spectrum( raw = counts(spec, 1:size(vq.vectors, 2), T, true) krs = zero.(vq.vectors * raw) spsc = T(dose(spec)) - residual = copy(raw) - for (i, vqr) in enumerate(vq.references) - residual[vqr.roi] -= krs[i] * vqr.charonly + residual = Deferred() do + props = copy(properties(spec)) + props[:Name] = "Residual[$(props[:Name])]" + res = copy(counts(spec, 1:size(vq.vectors, 2), T, true)) + for (i, vqr) in enumerate(vq.references) + res[vqr.roi] -= krs[i] * vqr.charonly + end + return Spectrum(spec.energy, res, props) + end + peakback = Deferred() do + res, resid = Dict{ReferenceLabel,NTuple{3,T}}(), residual() + for (i, vqr) in enumerate(vq.references) + ii, bb = krs[i] * vqr.sumchar, sum(resid[vqr.roi]) + res[vqr.label] = (ii, bb, bb / spsc) + end + return res end - peakback = Dict{ReferenceLabel,NTuple{3,T}}() dkrs = zeros(T, length(vq.references)) for (i, vqr) in enumerate(vq.references) - ii, bb = krs[i] * vqr.sumchar, sum(residual[vqr.roi]) - peakback[vqr.label] = (ii, bb, bb / spsc) - dkrs[i] = sqrt(max(Base.zero(T), ii + bb)) / vqr.sumchar + dkrs[i] = sqrt(max(Base.zero(T), krs[i] * vqr.sumchar)) / vqr.sumchar end kratios = uvs( map(ref -> ref.label, vq.references), # @@ -97,7 +107,7 @@ function fit_spectrum( peakback ) end -function fit_spectrum( +function fit_spectra( hs::HyperSpectrum, vq::VectorQuant{T}, zero = x -> max(Base.zero(T), x), @@ -119,3 +129,9 @@ function fit_spectrum( end return res end + +fit_spectrum( + hs::HyperSpectrum, + vq::VectorQuant{T}, + zero = x -> max(Base.zero(T), x), +) where { T<: AbstractFloat } = fit_spectra(hs, vq, zero) \ No newline at end of file diff --git a/src/quantify.jl b/src/quantify.jl index 8dfcae4..75c1f2e 100644 --- a/src/quantify.jl +++ b/src/quantify.jl @@ -41,10 +41,9 @@ function NeXLMatrixCorrection.quantify( )::IterationResult return quantify(fit_spectrum(spec, ffp); kwargs...) end -function NeXLMatrixCorrection.quantify( +NeXLMatrixCorrection.quantify( specs::AbstractVector{<:Spectrum}, ffp::FilterFitPacket; kwargs..., -)::Vector{IterationResult} - return map(spec->quantify(fit_spectrum(spec, ffp); kwargs...), specs) -end \ No newline at end of file +)::Vector{IterationResult} = # + map(spec->quantify(fit_spectrum(spec, ffp); kwargs...), specs) \ No newline at end of file diff --git a/src/recalibrate.jl b/src/recalibrate.jl new file mode 100644 index 0000000..a9b1d41 --- /dev/null +++ b/src/recalibrate.jl @@ -0,0 +1,38 @@ +import FourierTools + +""" + recalibrate(s::Spectrum{T}, es::LinearEnergyScale) + +Allows changing the energy scale on a spectrum from one LinearEnergyScale to another as though the spectrum were +measured on a different detector. The algorith uses a FFT-base scheme to rescale and shift the spectral data. +Ths scheme allows for fractional shifts of offset and fractional changes in the width. It is limited in that +the change in width must produce an integral number of channels in the resulting spectrum. The algorithm +maintains the total spectrum integral so the new spectrum can be used for quantitative purposes. +Plotting one spectrum over the other should maintain peak position but is likely to change the channel counts. +""" +function recalibrate(s::Spectrum{T}, es::LinearEnergyScale) where { T <: Real } + (!(s.energy isa LinearEnergyScale)) && error("The recalibrate(...) function requires that the spectrum has a LinearEnergyScale.") + # Resample the spectrum onto a new channel width (offset remains same) + cxs, oldsum = counts(s), sum(s.counts) + oldlen, newlen = length(cxs), round(Int, (s.energy.width/es.width)*length(cxs)) + if newlen ≠ length(cxs) + cxs = FourierTools.resample(cxs, newlen) + end + newWidth = s.energy.width*(oldlen/newlen) + # Shift the first channel from s.energy.offset to es.offset + shft = (s.energy.offset - es.offset) / newWidth + if abs(shft) > 0.01 + cxs = FourierTools.shift(cxs, shft) + end + return Spectrum(es, cxs*(oldsum/sum(cxs)), copy(s.properties)) +end + +""" + shift(s::Spectrum, ev::AbstractFloat)::Spectrum + +Shift the entire spectrum along the energy axis by a specified number of ev by modifying the +counts data. This function can shift by a fractional number of channels. +""" +function shift(s::Spectrum, ev::AbstractFloat)::Spectrum + return Spectrum(s.energy, FourierTools.shift(counts(s), ev/s.energy.width), copy(s.properties)) +end \ No newline at end of file diff --git a/src/reference.jl b/src/reference.jl index d1864a4..872d8c2 100644 --- a/src/reference.jl +++ b/src/reference.jl @@ -193,18 +193,21 @@ function references( end return FilterFitPacket(det, ff, frefs) end -references(refs::AbstractVector{ReferencePacket}, fwhm::Float64; ftype::Type{<:AbstractFloat}=Float64) = - references(refs, matching(first(refs).spectrum, fwhm), ftype=ftype) +references(refs::AbstractVector{ReferencePacket}, fwhm::Real; ftype::Type{<:AbstractFloat}=Float64) = + references(refs, matching(first(refs).spectrum, Float64(fwhm)), ftype=ftype) fit_spectrum(spec::Spectrum, ffp::FilterFitPacket{S, T}) where { S<:Detector, T<: AbstractFloat } = fit_spectrum(FilteredUnknownW{T}, spec, ffp.filter, ffp.references) -fit_spectrum(specs::AbstractVector{<:Spectrum}, ffp::FilterFitPacket{S, T}) where { S<:Detector, T<: AbstractFloat } = +fit_spectra(specs::AbstractVector{<:Spectrum}, ffp::FilterFitPacket{S, T}) where { S<:Detector, T<: AbstractFloat } = ThreadsX.map(spec -> fit_spectrum(FilteredUnknownW{T}, spec, ffp.filter, ffp.references), specs) +fit_spectrum(specs::AbstractVector{<:Spectrum}, ffp::FilterFitPacket{S, T}) where { S<:Detector, T<: AbstractFloat } = # + fit_spectra(specs, ffp) """ fit_spectrum(spec::Spectrum, ffp::FilterFitPacket)::FilterFitResult fit_spectrum(specs::AbstractVector{<:Spectrum}, ffp::FilterFitPacket)::Vector{FilterFitResult} + fit_spectra(specs::AbstractVector{<:Spectrum}, ffp::FilterFitPacket)::Vector{FilterFitResult} Fit a `Spectrum` or a vector of `Spectrum` using the specified `FilterFitPacket`. The result is a `FilterFitResult` structure which contains k-ratios, residuals, etc. @@ -240,7 +243,7 @@ the memory and take about 4/5 the time.) | :Intermediate | 1 | 1064.6 | 13.1 M | 364.4 | 0.9% | | :Full | 1 | 2186.2 | 6.2 G | 862.1 | 2.8% | """ -function fit_spectrum( +function fit_spectra( hs::HyperSpectrum, ffp::FilterFitPacket{S, T}; mode::Symbol = :Fast, @@ -261,6 +264,14 @@ function fit_spectrum( return Array{KRatio}[] end +fit_spectrum( + hs::HyperSpectrum, + ffp::FilterFitPacket{S, T}; + kwargs... +) where { S <: Detector, T <: AbstractFloat } = # + fit_spectra(hs, ffp; kwargs...) + + function fit_spectrum_int( hs::HyperSpectrum, ffp::FilterFitPacket{S, T}, diff --git a/src/semantics_image.jl b/src/semantics_image.jl index 699f93b..0ea839a 100644 --- a/src/semantics_image.jl +++ b/src/semantics_image.jl @@ -13,7 +13,8 @@ function readSEManticsImage(fn::AbstractString) if isfile(img_txt) imgs = CSV.File(img_txt, delim = "\t") |> DataFrame (nm, _) = splitext(sp[end]) - rg = Regex(replace(nm[1:end-3], "_" => "[.\\\\:]", "["=>"\\[", "]"=>"\\]")) + # Single call to replace fails in 1.6 + rg = Regex(reduce((a,b)->replace(a,b), ( "["=>"\\[", "]"=>"\\]", "_" => "[.\\\\:_]"); init=string(nm[1:end-3]))) mimgs = filter(r -> !isnothing(match(rg, r[:Name])), imgs) if nrow(mimgs) > 0 # Since an image can be written more than once, take the last... @@ -26,10 +27,10 @@ function readSEManticsImage(fn::AbstractString) ) else - @info "There is no meta-data in image.txt for images matching $rg." + @info "There is no meta-data in images.txt for images matching $rg for $fn." end else - @info "There is no meta-data file images.txt for images matching $rg." + @info "There is no meta-data file images.txt for images matching $rg for $fn." end return img end diff --git a/src/spectrum.jl b/src/spectrum.jl index 4848c33..e90e17c 100644 --- a/src/spectrum.jl +++ b/src/spectrum.jl @@ -1,4 +1,3 @@ -import FourierTools # Keeps track of the number of spectra in this session. @@ -96,7 +95,6 @@ to associate other data items with a `Spectrum`. :AcquisitionTime # Date and time of acquisition (`DateTime` struct) :Signature # Dict{Element,Real} with the "particle signature" :SolidAngle # Detector solid angle is steradians (area/dist²) - :Detector # A detector model property like `EDSDetector` Spectrum Image items: @@ -200,6 +198,8 @@ Base.setindex!(spec::Spectrum, val::Real, sym::Symbol) = setindex!(spec.properties, val, sym) Base.setindex!(spec::Spectrum, val::Any, sym::Symbol) = setindex!(spec.properties, val, sym) + +Base.checkbounds(::Type{Bool}, spec::Spectrum, ch::Int) = checkbounds(Bool, spec.counts, ch) Base.copy(spec::Spectrum) = Spectrum(spec.energy, copy(spec.counts), copy(spec.properties)) Base.merge!(spec::Spectrum, props::Dict{Symbol,Any}) = merge!(spec.properties, props) @@ -216,6 +216,9 @@ end function Base.similar(spec::Spectrum{T}, ::Type{U}) where { T <: Real, U <: Real } return Spectrum(spec.energy, similar(spec.counts, U), copy(spec.properties)) end +function Base.similar(spec::Spectrum{T}) where { T <: Real } + return Spectrum(spec.energy, similar(spec.counts, T), copy(spec.properties)) +end # Spectrum math simply performs the operation on the channel data but doesn't change any properties except the name. Base.:*(a::Real, s::Spectrum) = property!(Spectrum(s.energy, a*s.counts, copy(s.properties)), :Name, "$a⋅$(s[:Name])") @@ -364,15 +367,13 @@ end Build an EDSDetector to match the channel count and energy scale in this spectrum. """ -matching(spec::Spectrum, resMnKa::Float64, lld::Int = 1)::BasicEDS = - BasicEDS(length(spec), spec.energy, MnKaResolution(resMnKa), lld) - matching( spec::Spectrum, resMnKa::Float64, - lld::Int, - minByFam::Dict{Shell,Element}, -)::BasicEDS = BasicEDS(length(spec), spec.energy, MnKaResolution(resMnKa), lld, minByFam) + lld::Int = 1, + minByFam::Dict{Shell,Element} = Dict{Shell,Element}(), +)::BasicEDS = + BasicEDS(length(spec), spec.energy, MnKaResolution(resMnKa), max(lld, channel(10.0, spec.energy)), minByFam) """ matches(spec::Spectrum, det::Detector, tol::Float64 = 1.0)::Bool matches(spec1::Spectrum, spec2::Spectrum, tol::Float64 = 1.0)::Bool @@ -439,13 +440,15 @@ function dose(props::Dict{Symbol, Any}, def::Union{Float64,Missing})::Union{Floa res = get(props, :LiveTime, missing) * get(props, :ProbeCurrent, missing) return isequal(res, missing) ? def : res end -dose(spec::Spectrum, def::Union{Float64,Missing})::Union{Float64,Missing} = dose(spec.properties, def) -function dose(props::Dict{Symbol, Any}) +dose(spec::Spectrum, def::Union{Float64,Missing})::Union{Float64,Missing} = + get(spec.properties, :LiveTime, missing) * get(spec.properties, :ProbeCurrent, missing) + +function dose(props::Dict{Symbol, Any})::Float64 res = get(props, :LiveTime, missing) * get(props, :ProbeCurrent, missing) ismissing(res) && @error "One or more of the properties necessary to calculate the dose (:ProbeCurrent and :LiveTime) is not available." return res end -dose(spec::Spectrum) = dose(spec.properties) +dose(spec::Spectrum)::Float64 = dose(spec.properties) """ NeXLCore.energy(ch::Int, spec::Spectrum)::Float64 @@ -499,11 +502,11 @@ cannot be converted without loss to the type `T`. function counts( spec::Spectrum, ::Type{T} = Float64, - applyLLD = false, -)::Array{T} where {T<:Number} + applyLLD = false +)::Vector{T} where {T<:Number} res = T.(spec.counts) if applyLLD && haskey(spec, :Detector) - fill!(view(res, 1:lld(spec[:Detector])), zero(T)) + res[1:lld(spec[:Detector])] .= zero(T) end return res end @@ -512,16 +515,17 @@ function counts( channels::AbstractRange{<:Integer}, ::Type{T} = Float64, applyLLD = false, -)::Array{T} where {T<:Real} +)::Vector{T} where {T<:Real} if (first(channels) >= 1) && (last(channels) <= length(spec)) - res = T.(spec.counts[channels]) + res = map(x->T(x), view(spec.counts,channels)) else res = zeros(T, length(channels)) r = max(first(channels), 1):min(last(channels), length(spec)) - res[first(r)-first(channels)+1:last(r)-first(channels)+1] = spec.counts[r] + res[first(r)-first(channels)+1:last(r)-first(channels)+1] .= view(spec.counts, r) end - if applyLLD && haskey(spec, :Detector) && (lld(spec) <= first(channels)) - fill!(view(res, 1:lld(spec)-first(channels)+1), zero(T)) + lldv = lld(spec) + if applyLLD && haskey(spec, :Detector) && (lldv <= first(channels)) + res[1:lldv-first(channels)+1] .= zero(T) end return res end @@ -530,7 +534,7 @@ function counts( channels::AbstractVector{<:Integer}, ::Type{T} = Float64, applyLLD = false, -)::Array{T} where {T<:Real} +)::Vector{T} where {T<:Real} res = T.(spec.counts[channels]) if applyLLD && haskey(spec, :Detector) && (lld(spec) <= first(channels)) fill!(view(res, 1:lld(spec)-first(channels)+1), zero(T)) @@ -543,7 +547,7 @@ end Gets the low-level discriminator associated with this spectrum if there is one. """ -lld(spec::Spectrum) = +lld(spec::Spectrum)::Int = haskey(spec.properties, :Detector) ? lld(spec.properties[:Detector]) : 1 """ @@ -1073,13 +1077,19 @@ end """ - Base.sum(specs::AbstractArray{Spectrum{T}}; restype::Type{<:Real}=T, applylld=false, name=Nothing|String)::Spectrum{T} + Base.sum( + specs::AbstractArray{Spectrum{T}}; + restype::Type{<:Real}=T, + applylld=false, + name=Nothing|String + properties=Nothing|Dict{Symbol, Any} + )::Spectrum{T} Computes the sum spectrum over an `AbstractArray{Spectrum}` where the :ProbeCurrent and :LiveTime will be maintained in a way that maintains the sum of the individual doses. This function assumes (but does not check) that the energy scales are equivalent for all the spectra. The resultant energy scale is the scale of the first spectrum. Other than :ProbeCurrent, :LiveTime and :RealTime which are computed to maintain the total sum dose, only those properties that the -spectra hold in common will be maintained. +spectra hold in common will be maintained. The items in `properties` is merged with the resulting properties. This function behaves differently from `reduce(+, specs)` which checks whether the energy scales match and fails if they don't. @@ -1089,6 +1099,7 @@ function Base.sum( restype::Type{<:Real} = T; applylld = false, name = nothing, + properties = nothing, )::Spectrum where {T<:Real} cxs = zeros(restype, maximum(length.(specs))) for spec in specs @@ -1100,209 +1111,12 @@ function Base.sum( rt = sum(get(sp, :RealTime, NaN64) for sp in specs) (!isnan(rt)) && (props[:RealTime] = rt) props[:Name] = something(name, "Sum[$(length(specs)) spectra]") - return Spectrum(specs[1].energy, cxs, props) -end - -""" - χ²(s1::Spectrum{T}, s2::Spectrum{U}, chs)::T where {T<:Real, U <: Real} - χ²(specs::AbstractArray{Spectrum{T}}, chs)::Matrix{T} - -Computes the dose corrected reduced χ² metric between `s1` and `s2` over the channels in `chs`. - -The second form computes a matrix of χ² comparing each spectrum in the array to the others. -""" -function χ²(s1::Spectrum{T}, s2::Spectrum{U}, chs)::Float64 where {T<:Real, U<:Real} - k1, k2 = 1.0 / dose(s1), 1.0 / dose(s2) - return sum(ch->(k1 * s1[ch] - k2 * s2[ch])^2 / (k1*k1*max(one(T), s1[ch]) + k2*k2*max(one(U), s2[ch])), chs) -end -function χ²(specs::AbstractVector{<:Spectrum}, chs)::Matrix{Float64} - χ2s = zeros(Float64, (length(specs), length(specs))) - for i in eachindex(specs), j in i+1:length(specs) - χ2s[i, j] = χ²(specs[i], specs[j], chs) - χ2s[j, i] = χ2s[i, j] - end - return χ2s -end - -""" - similarity(s1::Spectrum{T}, s2::Spectrum{T}, chs)::Float64 where {T<:Real} - similarity(specs::AbstractArray{Spectrum{T}}, chs)::Vector{Float64} - similarity(specs::AbstractArray{Spectrum}, minE::Float64=100.0)::Vector{Float64} - similarity(specs::AbstractArray{<:Spectrum}, det::Detector, elm::Element)::Vector{Float64} - similarity(specs::AbstractArray{Spectrum{T}}, det::Detector, mat::Material)::Vector{Float64} - -Returns a vector of similarity metrics which measure how similar the i-th `Spectrum` is to the other spectra. -The mean reduced χ² statistic metric is such that if `s1` and `s2` differ by only count statistics -then the metric will be approximately unity. If `s1` and `s2` vary due to probe current drift, sample -inhomogeneity, surface roughness or other non-count statistics related reasons then the metric will be -larger than one. - -The first version covers all the channels between minE and the nominal beam energy. The third and fourth versions -considers those channels representing peaks in a spectrum from the `Material` or `Element` on the `Detector`. -""" -function similarity(s1::Spectrum{T}, s2::Spectrum{T}, chs)::Float64 where {T<:Real} - k1, k2 = 1.0 / dose(s1), 1.0 / dose(s2) - return χ²(s1, s2, chs)/length(chs) -end -function similarity( - specs::AbstractArray{<:Spectrum}, - chs -)::Vector{Float64} - return [similarity(spec, sum(filter(s->!(s===spec), specs)), chs) for spec in specs] -end - -function similarity( - specs::AbstractArray{<:Spectrum}, - minE::Float64 = 100.0, -)::Vector{Float64} - e0 = maximum(spec[:BeamEnergy] for spec in specs) - chs = - minimum( - channel(minE, spec) for spec in specs - ):maximum(channel(e0, spec) for spec in specs) - return similarity(specs, chs) -end -function similarity( - specs::AbstractArray{<:Spectrum}, - det::Detector, - elm::Element, -)::Vector{Float64} - e0 = maximum(spec[:BeamEnergy] for spec in specs) - rois = extents(characteristic(elm, alltransitions, 0.01, e0), det, 0.001) - chs = mapreduce(collect, append!, rois) - return similarity(specs, chs) -end -function similarity( - specs::AbstractArray{<:Spectrum}, - det::Detector, - mat::Material, -)::Vector{Float64} - function mrg(inp::Vector{UnitRange{Int}})::Vector{UnitRange{Int}} - simp = sort(inp) - st, res = simp[1], UnitRange{Int}[] - for r in simp[2:end] - if isempty(intersect(st, r)) - push!(res, st) - st = r - else - st = first(st):max(last(r), last(st)) - end - end - push!(res, st) - return res - end - e0 = maximum(spec[:BeamEnergy] for spec in specs) - # Figure out the contiguous ROIs and the channels in the ROIs - rois = mrg( - mapreduce( - elm -> extents(characteristic(elm, alltransitions, 0.01, e0), det, 0.001), - append!, - keys(mat), - ), - ) - chs = mapreduce(collect, append!, rois) - return similarity(specs, chs) -end - - -""" - findsimilar(specs::AbstractArray{Spectrum{T}}; atol = 4.0, rtol=1.5, minspecs=3)::Vector{Spectrum{T}} - findsimilar(specs::AbstractArray{Spectrum{T}},det::Detector,elm::Element; atol = 4.0, rtol=1.5, minspecs = 3)::Vector{Spectrum{T}} - - -Filters a collection of spectra for the ones most similar to the average by -removing the least similar spectrum sequentially until all the remaining spectra are within either: - - * atol of the mean - * rtol * max(others) of the mean - -when applying the 'similarity(...)` function to the spectrum and the sum of the other spectra. - -This is useful for finding which of a set of replicate spectra are sufficiently similar -to each other. -""" -function findsimilar( - specs::AbstractArray{Spectrum{T}}; - atol = 4.0, - rtol = 1.5, - minspecs = 3, -)::Vector{Spectrum{T}} where {T<:Real} - if length(specs) >= minspecs - σs = abs.(similarity(specs)) - (fmσ, fmi) = findmax(σs) - # Now perform this recursively until all are within tol or we hit minspecs - maxσ = maximum(filter(σ -> σ ≠ fmσ, σs)) - if (fmσ > atol + maxσ) || (fmσ > rtol*maxσ) - rem = filter(s -> !(s === specs[fmi]), specs) - return findsimilar(rem, atol = atol, rtol=rtol, minspecs = minspecs) - else - return specs - end - end - error("There are not $minspecs spectra which are sufficiently similar to the mean spectrum.") -end -function findsimilar( - specs::AbstractArray{Spectrum{T}}, - det::Detector, - elm::Element; - atol = 4.0, - rtol = 1.5, - minspecs = 3, -)::Vector{Spectrum{T}} where {T<:Real} - if length(specs) >= minspecs - σs = abs.(NeXLSpectrum.similarity(specs, det, elm)) - (fmσ, fmi) = findmax(σs) - # Now perform this recursively until all are within tol or we hit minspecs - maxσ = maximum(filter(σ -> σ ≠ fmσ, σs)) - if (fmσ > atol) || (fmσ > rtol*maxσ) - rem = filter(s -> !(s === specs[fmi]), specs) - return findsimilar(rem, det, elm, atol = atol, rtol=rtol, minspecs = minspecs) - else - return specs - end - end - error("There are not $minspecs spectra which are sufficiently similar to the mean spectrum.") -end - -# Duane-Hunt related functions - -_duane_hunt_func(es, p) = map(ee-> p[1]*bremsstrahlung(Small1987, ee, p[2], n"H"), es) - -function _duane_hunt_impl(spec::Spectrum) - if channel(spec[:BeamEnergy], spec) < length(spec) - cdata = counts(spec) - este0, estCh = let # Sometimes the D-H can be much below `spec[:BeamEnergy]` - # this handles pulse pile-up above the D-H - lim = min(10.0, max(1.0, 1.0e-5 * maximum(cdata))) - len = min(10, max(5, length(spec) ÷ 400)) - chlast = findlast(i -> mean(@view cdata[i-len:i]) > lim, len+1:length(cdata)) - energy(chlast, spec), chlast - end - xdata, ydata = let - # Range of channels above/below `este0` - chs = max(1,(9*estCh)÷10):min((21*estCh)÷20,length(spec)) - energyscale(spec, chs), cdata[chs] - end - esti0 = sum(ydata) / sum(_duane_hunt_func(xdata, (1.0, este0))) - e0 = spec[:BeamEnergy] - # Apply constraint functions to keep DH between 0.1 E0 and 1.1 E0. - transform(v) = asin(2*(v-0.1*e0)/(1.1*e0-0.1*e0)-1) - inv_transform(tv) = 0.1*e0+(sin(tv)+1)*(1.1*e0-0.1*e0)/2 - # Fit Small's continuum model to the data (ignore detector efficiency) - model(xs, p) = _duane_hunt_func(xs, (p[1], inv_transform(p[2]))) - res = curve_fit(model, xdata, ydata, [esti0, transform(este0)]) - return (res.param[1], inv_transform(res.param[2])) - else - error("Unable to estimate the Duane-Hunt on $(spec[:Name]) because the counts data does not extend to $(spec[:BeamEnergy]) keV.") + if !isnothing(properties) + merge!(props, properties) end + return Spectrum(specs[1].energy, cxs, props) end -""" - duane_hunt(spec::Spectrum) - -Estimates the Duane-Hunt limit (the energy at which the continuum goes to effectively zero.) -""" -duane_hunt(spec::Spectrum) = _duane_hunt_impl(spec)[2] """ uv(spec::Spectrum, chs::AbstractRange{<:Integer}=eachindex(spec))::Vector{UncertainValue} @@ -1315,99 +1129,5 @@ function NeXLUncertainties.uv(spec::Spectrum, chs::AbstractRange{<:Integer}=each return uv.(c, sqrt.(max.(c,one(eltype(spec))))) end -""" - sigma(spec::Spectrum, specs::AbstractArray{<:Spectrum}, chs::AbstractRange{<:Integer})::Vector{Float64} - -Computes on a channel-by-channel basis how much `spec` spectrum deviates from the mean of the -other spectra in `specs`. The result is expressed in terms of the standard deviation expected -from count statistics alone. Assuming `spec` varies only by count statistics we expect -the result values have a mean 0.0 and a standard deviation of 1.0. -""" -function sigma(spec::Spectrum, specs::AbstractArray{<:Spectrum}, chs::AbstractRange{<:Integer})::Vector{Float64} - function doseaverage(specs, chs) - t = [ uv(spec, chs)/dose(spec) for spec in specs ] - return map(j->mean(collect(t[i][j] for i in eachindex(t))), eachindex(t[1])) - end - function delta(spec, specs, chs) - minus(uv1,uv2) = uv(value(uv1)-value(uv2),sqrt(variance(uv1)+variance(uv2))) - return minus.(uv(spec, chs), dose(spec)*doseaverage(filter(s->s!=spec, specs), chs)) - end - return map(v->value(v)/σ(v), delta(spec, specs, chs)) -end - -""" - dosenormalize(spectrum::Spectrum{T}, dose=60.0)::Spectrum{T} where { T <: Real } - dosenormalize(spectrum::Spectrum{T}, dose=60.0)::Spectrum{Float64} where { T <: Integer } - -Compute a spectrum which is `spectrum` rescaled to a live time times probe current equal to `dose`. -Useful for setting spectra on an equivalent acquisition duration scale. -""" -function dosenormalize(spectrum::Spectrum{T}, dose=60.0)::Spectrum{T} where { T <: AbstractFloat } - res = copy(spectrum) - scale = dose / NeXLSpectrum.dose(res) - res.counts .*= scale - res[:LiveTime] *= scale - res[:Name] = "N[$(spectrum[:Name]), $dose nA⋅s]" - return res -end -function dosenormalize(spectrum::Spectrum{T}, dose=60.0)::Spectrum{Float64} where { T <: Integer } - scale = dose / NeXLSpectrum.dose(spectrum) - newProps = copy(spectrum.properties) - newProps[:LiveTime] *= scale - newProps[:Name] = "N[$(spectrum[:Name]), $dose nA⋅s]" - return Spectrum(spectrum.energy, Float64.(spectrum.counts) * scale, newProps) -end - -""" - recalibrate(s::Spectrum{T}, es::LinearEnergyScale) - -Allows changing the energy scale on a spectrum from one LinearEnergyScale to another as though the spectrum were -measured on a different detector. The algorith uses a FFT-base scheme to rescale and shift the spectral data. -Ths scheme allows for fractional shifts of offset and fractional changes in the width. It is limited in that -the change in width must produce an integral number of channels in the resulting spectrum. The algorithm -maintains the total spectrum integral so the new spectrum can be used for quantitative purposes. -Plotting one spectrum over the other should maintain peak position but is likely to change the channel counts. -""" -function recalibrate(s::Spectrum{T}, es::LinearEnergyScale) where { T <: Real } - (!(s.energy isa LinearEnergyScale)) && error("The rescale(...) function requires that the spectrum has a LinearEnergyScale.") - # Resample the spectrum onto a new channel width (offset remains same) - cxs, oldsum = counts(s), sum(s.counts) - oldlen, newlen = length(cxs), round(Int, (s.energy.width/es.width)*length(cxs)) - if newlen ≠ length(cxs) - cxs = FourierTools.resample(cxs, newlen) - end - newWidth = s.energy.width*(oldlen/newlen) - # Shift the first channel from s.energy.offset to es.offset - shft = (s.energy.offset - es.offset) / newWidth - if abs(shft) > 0.01 - cxs = FourierTools.shift(cxs, shft) - end - return Spectrum(es, cxs*(oldsum/sum(cxs)), copy(s.properties)) -end - -""" - shift(s::Spectrum, ev::AbstractFloat)::Spectrum - -Shift the entire spectrum along the energy axis by a specified number of ev by modifying the -counts data. This function can shift by a fractional number of channels. -""" -function shift(s::Spectrum, ev::AbstractFloat)::Spectrum - return Spectrum(s.energy, FourierTools.shift(counts(s), ev/s.energy.width), copy(s.properties)) -end - -""" - shannon_entropy(spec::Spectrum) - -Computes a measure of the information content in a spectrum. As there become more and more -distinct values in a spectrum, this value approaches log2(nchannels(spec)). This number -reflects the number of bits necessary to encode the spectrum data with maximum efficiency. - -This is inspired by John Colby's FLAME software which did something similar. Although, to -be honest, I don't know how his algorithm was implemented. -""" -function shannon_entropy(spec::Spectrum) - d = Dict{Int,Float64}() - foreach(c->d[c] = get(d, c, 0.0) + 1.0 / length(spec), counts(spec)) - f(c) = c<=0 ? 0.0 : log2(c) - return -sum(cx -> cx*f(cx), values(d)) -end \ No newline at end of file +simulate(sp::Spectrum, Ω::AbstractFloat, det::Detector, resp::Matrix{<:AbstractFloat}; vargs...) = + simulate(nonneg(sp[:Composition]), dose(sp), sp[:BeamEnergy], sp[:TakeOffAngle], Ω, det, resp; vargs...) \ No newline at end of file diff --git a/src/standardize.jl b/src/standardize.jl index 0dc4e30..1ad2730 100644 --- a/src/standardize.jl +++ b/src/standardize.jl @@ -96,22 +96,6 @@ function NeXLUncertainties.compute( return (LabeledValues(outputs, results), jac) end -function __remap_peaktoback(pbs::Dict{<:ReferenceLabel,NTuple{3,T}}, sm::StandardizeModel) where { T<: AbstractFloat } - res = Dict{ReferenceLabel,NTuple{3,T}}() - for (meas, pb) in pbs - i = findfirst(std->matches(meas, std), sm.standards) - if !isnothing(i) - std = sm.standards[i] - sp = copy(properties(std)) - sp[:Composition] = std.standard - res[CharXRayLabel(sp, meas.roi, meas.xrays)] = pb - else - res[meas] = pb - end - end - return res -end - """ NeXLCore.standardize(ffr::FilterFitResult{T}, standard::FilterFitResult{T}, material::Material, els=elms(material))::FilterFitResult{T} NeXLCore.standardize(ffrs::Vector{FilterFitResult{T}}, standard::FilterFitResult{T}, material::Material, els=elms(material))::Vector{FilterFitResult{T}} @@ -129,13 +113,28 @@ function NeXLCore.standardize(ffr::FilterFitResult{T}, standard::FilterFitResult # Problem here with equivalent CharXRayLabel(s) for unknown and standard... inp = cat(ffr.kratios, rext) stdize = StandardizeModel(Vector{CharXRayLabel}(labels(ffr.kratios)), Vector{StandardLabel}(labels(rext))) + ptob = Deferred() do + pbs, res = ffr.peakback, Dict{ReferenceLabel,NTuple{3,T}}() + for (meas, pb) in pbs() + i = findfirst(std->matches(meas, std), sm.standards) + if !isnothing(i) + std = sm.standards[i] + sp = copy(properties(std)) + sp[:Composition] = std.standard + res[CharXRayLabel(sp, meas.roi, meas.xrays)] = pb + else + res[meas] = pb + end + end + return res + end return FilterFitResult{T}( ffr.label, # Same stdize(inp), # remapped ffr.roi, # Same ffr.raw, # Same ffr.residual, # Same - residual is due to references - __remap_peaktoback(ffr.peakback, stdize) # Remapped to standard labels + ptob ) end @@ -155,13 +154,28 @@ function NeXLCore.standardize(ffr::FilterFitResult{T}, standards::AbstractArray{ return if length(stds)>0 stdize = StandardizeModel(Vector{CharXRayLabel}(labels(ffr.kratios)), Vector{StandardLabel}(labels(stds))) inp = cat(ffr.kratios, stds) + ptob = Deferred() do + pbs, res = ffr.peakback, Dict{ReferenceLabel,NTuple{3,T}}() + for (meas, pb) in pbs() + i = findfirst(std->matches(meas, std), sm.standards) + if !isnothing(i) + std = sm.standards[i] + sp = copy(properties(std)) + sp[:Composition] = std.standard + res[CharXRayLabel(sp, meas.roi, meas.xrays)] = pb + else + res[meas] = pb + end + end + return res + end FilterFitResult{T}( ffr.label, stdize(inp), ffr.roi, ffr.raw, ffr.residual, - __remap_peaktoback(ffr.peakback, stdize) + ptob ) else @warn "No suitable standards were provided to standardize this measurement." diff --git a/src/window.jl b/src/window.jl index de83460..7cc49ec 100644 --- a/src/window.jl +++ b/src/window.jl @@ -1,123 +1,191 @@ -# X-ray Window +""" +`AbstractWindow` is the base type for `TabulatedWindow`, `ModeledWindow` and `NoWindow`. +You can view the window transmission function using `Gadfly` and the method: + + plot(wind::Union{AbstractWindow, AbstractArray{<:AbstractWindow}}; xmax=20.0e3, angle=π/2, style=NeXLSpectrumStyle) + +Window types are identified by types based on `WindowType` like `MoxtekAP33`, `MoxtekAP5`, `AmptekC1`, `AmptekC2`, `BerylliumWindow`. + +An implementation of an `AbstractWindow` is instantiate using code like `TabulatedWindow(MoxtekAP33())` or `ModeledWindow(AmptekC1())`. +In addition, there is a `NoWindow()` type that implements a 100% transparent window. +`AbstractWindow` types primarily implement `NeXLCore.transmission(wnd::AbstractWindow, energy::Float64, angle::Float64 = π / 2)` +""" abstract type AbstractWindow end +Base.show(io::IO, wnd::AbstractWindow) = print(io, name(wnd)) + +""" + NoWindow + +A 100% transparent window. +""" +struct NoWindow <: AbstractWindow end +NeXLCore.name(::NoWindow) = "No window" +NeXLCore.transmission(wnd::NoWindow, energy::Float64, angle::Float64=π / 2) = 1.0 + + +""" +`WindowType` is the abstract type for models or types of X-ray windows. These types distinguish between +the window type and the implementation. Often, there are both tabulated transmission functions from +the vendor and calculated transmission functions based on the construction of the window. +Use the `TabulatedWindow` or `ModeledWindow` to instantiate `AbstractWindow` which implements the +`transmission(wnd::AbstractWindow, energy::Float64, angle::Float64 = π / 2)` method. + +Predefined `WindowType`s are `MoxtekAP33`, `MoxtekAP5`, `AmptekC1`, `AmptekC2`, `BerylliumWindow`. +""" +abstract type WindowType end +struct MoxtekAP33 <: WindowType end +NeXLCore.name(::MoxtekAP33) = "Moxtek AP3.3" +struct MoxtekAP5 <: WindowType end +NeXLCore.name(::MoxtekAP5) = "Moxtek AP5" -Base.show(io::IO, wnd::AbstractWindow) = print(io, wnd.name) +""" +Create modeled windows for the Ametek C1 Si₃N₄ windows according to the specifications here: + https://www.amptek.com/products/accessories-for-xrf-eds/c-series-low-energy-x-ray-windows#Specifications + +Light-tight (solar-blind) window. Often used for environmental XRF units. +""" +struct AmptekC1 <: WindowType end +NeXLCore.name(::AmptekC1) = "AMPTEK C1 Si₃N₄" + +""" +Create modeled windows for the Ametek C2 Si₃N₄ windows according to the specifications here: + https://www.amptek.com/products/accessories-for-xrf-eds/c-series-low-energy-x-ray-windows#Specifications + +Light transparent window often used for SEM detectors. +""" +struct AmptekC2 <: WindowType end +NeXLCore.name(::AmptekC2) = "AMPTEK C2 Si₃N₄" + +# Aliases: Amptek was bought by Ametek (confusing???) +const AmetekC1 = AmptekC1; +const AmetekC2 = AmptekC2; + + +""" + BerylliumWindow(thickness) + +Create a window of pure Be of the specified thickness (in cm) +""" +struct BerylliumWindow <: WindowType + thickness::Float64 -struct NoWindow <: AbstractWindow - name::String - NoWindow() = new("No window") + BerylliumWindow(thickness=5.0e-4) = new(thickness) end +NeXLCore.name(bw::BerylliumWindow) = "$(bw.thickness*1.0e4) μm Beryllium" -NeXLCore.transmission(wnd::NoWindow, energy::Float64, angle::Float64 = π / 2) = 1.0 -struct LayerWindow <: AbstractWindow - name::String +""" + ModeledWindow(wt::WindowType) + +This type models a window using the materials and thicknesses provided by the vendor. +If accomodates grids by a simplistic mechanism of assuming an open area. +""" +struct ModeledWindow <: AbstractWindow + type::WindowType layers::Vector{Film} support::Film openfraction::Float64 end +NeXLCore.name(mw::ModeledWindow) = "$(name(mw.type)) - Modeled" -NeXLCore.transmission(wnd::LayerWindow, energy::Float64, angle::Float64 = π / 2) = - ( - length(wnd.layers) > 0 ? - mapreduce(lyr -> NeXLCore.transmission(lyr, energy, angle), *, wnd.layers) : 1.0 - ) * ( - wnd.openfraction + - (1.0 - wnd.openfraction) * NeXLCore.transmission(wnd.support, energy, angle) - ) - -""" - AP33Model() +function NeXLCore.transmission( + wnd::ModeledWindow, + energy::Float64, + angle::Float64=π / 2 +) + lt = mapreduce(lyr -> NeXLCore.transmission(lyr, energy, angle), *, wnd.layers, init=1.0) + lt * (wnd.openfraction + (1.0 - wnd.openfraction) * NeXLCore.transmission(wnd.support, energy, angle)) +end +NeXLCore.transmission(wnd::AbstractWindow, cxr::CharXRay, angle::Float64=π / 2) = transmission(wnd, energy(cxr), angle) -Construct a modeled window for the Moxtek AP3.3 window. -""" -function AP33Model() +function ModeledWindow(wt::MoxtekAP33) support, openarea = Film(pure(n"Si"), 0.038), 0.77 - paralene = Film(parse(Material, "C10H8O4N", density = 1.39), 3.0e-5) + paralene = Film(parse(Material, "C10H8O4N", density=1.39), 3.0e-5) aluminum = Film(pure(n"Al"), 4.0e-6) - return LayerWindow("Moxtek AP3.3 model", [aluminum, paralene], support, openarea) + ModeledWindow(wt, [aluminum, paralene], support, openarea) end -function AP5Model() +function ModeledWindow(wt::MoxtekAP5) support, openarea = Film(pure(n"C"), 0.0265), 0.78 - paralene = Film(parse(Material, "C10H8O4N", density = 1.39), 3.0e-5) + paralene = Film(parse(Material, "C10H8O4N", density=1.39), 3.0e-5) aluminum = Film(pure(n"Al"), 4.0e-6) - return LayerWindow("Moxtek AP5 model", [aluminum, paralene], support, openarea) + ModeledWindow(wt, [aluminum, paralene], support, openarea) end -""" - Beryllium(thickness=5.0e-4) - -Construct a beryllium window. -""" -function Beryllium(thickness = 5.0e-4) - support, openarea = Film(pure(n"Be"), thickness), 0.0 - return LayerWindow("$(thickness*1.0e4) μm Be window", [], support, openarea) +function ModeledWindow(wt::BerylliumWindow) + support, openarea = Film(pure(n"Be"), wt.thickness), 0.0 + ModeledWindow(wt, [], support, openarea) end -""" - function AmptekC1() - function AmptekC2() - -Create modeled windows for the Amptek C1 or C2 Si₃N₄ windows according to the specifications here: -https://www.amptek.com/products/accessories-for-xrf-eds/c-series-low-energy-x-ray-windows#Specifications -""" - -function AmptekC1() - si3n4 = Film(parse(Material, "Si3N4", density = 3.44), 150.0e-7) - al = Film(pure(n"Al"), 250.0e-7) - support, openarea = Film(pure(n"Si"), 1.50e-4), 0.80 - return LayerWindow("Amptek C1", [si3n4, al], support, openarea) +function ModeledWindow(wt::AmptekC1, openarea=0.80) + si3n4 = Film(parse(Material, "Si3N4", density=3.44), 150.0e-7) # 150 nm of Si₃N₄ + al = Film(pure(n"Al"), 250.0e-7) # 250 nm of Al + support = Film(pure(n"Si"), 1.50e-3) # 80% open area, 15 μm Si thickness support + ModeledWindow(wt, [si3n4, al], support, openarea) end -function AmptekC2() - si3n4 = Film(parse(Material, "Si3N4", density = 3.44), 40.0e-7) - al = Film(pure(n"Al"), 30.0e-7) - support, openarea = Film(pure(n"Si"), 1.50e-4), 0.80 - return LayerWindow("Amptek C2", [si3n4, al], support, openarea) +function ModeledWindow(wt::AmptekC2, openarea=0.80) + si3n4 = Film(parse(Material, "Si3N4", density=3.44), 40.0e-7) # 40 nm of Si₃N₄ + al = Film(pure(n"Al"), 30.0e-7) # 30 nm of Al + support = Film(pure(n"Si"), 1.50e-3) # 15 μm thickness Si support + ModeledWindow(wt, [si3n4, al], support, openarea) end +""" + TabulatedWindow(wt::WindowType) + +Construct a model of the window transmission based on vendor-supplied tabulations of transparency. At the +end of the user supplied data, the transmission function is extended by matching the ModeledWindow +with the tabulation. +""" struct TabulatedWindow <: AbstractWindow - name::String + type::WindowType interpolation::AbstractInterpolation - extrapolation::LayerWindow - match::Float64 + extrapolation::ModeledWindow + match::Float64 # Used to match tabulated and modeled transmission functions. end +NeXLCore.name(mw::TabulatedWindow) = "$(name(mw.type)) - Tabulated" function NeXLCore.transmission( wnd::TabulatedWindow, energy::Float64, - angle::Float64 = π / 2, + angle::Float64=π / 2, ) bnds = bounds(parent(wnd.interpolation))[1] return (energy >= bnds[1]) && (energy < bnds[2]) ? wnd.interpolation(energy) : # wnd.match * transmission(wnd.extrapolation, energy, angle) end -""" - AP33Tabulation() - AP5Tabulation() +function TabulatedWindow(wt::MoxtekAP33) + data = CSV.read(joinpath(@__DIR__, "data", "AP3_3_mod.csv"), DataFrame, header=3, comment="//") + inter = LinearInterpolation(data[:, 1], data[:, 2]) + extra = ModeledWindow(wt) + match = inter(data[end, 1]) / transmission(extra, data[end, 1], π / 2) + TabulatedWindow(wt, inter, extra, match) +end -Construct tabulated window models for the Moxtek AP3.3 and AP5 windows. -""" -function AP33Tabulation() - data = CSV.read( - joinpath(dirname(pathof(@__MODULE__)), "AP3_3_mod.csv"), - DataFrame, - comment = "//" - ) +function TabulatedWindow(wt::MoxtekAP5) + data = CSV.read(joinpath(@__DIR__, "data", "AP5.csv"), DataFrame) inter = LinearInterpolation(data[:, 1], data[:, 2]) - extra = AP33Model() - return TabulatedWindow("Moxtek AP3.3", inter, extra, 1.01582935271) + extra = ModeledWindow(wt) + match = inter(data[end, 1]) / transmission(extra, data[end, 1], π / 2) + TabulatedWindow(wt, inter, extra, match) end +function TabulatedWindow(wt::AmptekC1) + data = CSV.read(joinpath(@__DIR__, "data", "AMETEK Si3N4 C1.csv"), DataFrame, header=2) + inter = LinearInterpolation(1000.0 * data[:, 1], data[:, 2]) + extra = ModeledWindow(wt) + match = inter(1000.0 * data[end, 1]) / transmission(extra, 1000.0 * data[end, 1], π / 2) + TabulatedWindow(wt, inter, extra, match) +end -function AP5Tabulation() - data = CSV.read(joinpath(dirname(pathof(@__MODULE__)), "AP5.csv"), DataFrame) - inter = LinearInterpolation(data[:, 1], data[:, 2]) - extra = AP5Model() - return TabulatedWindow("Moxtek AP5", inter, extra, 1.0) +function TabulatedWindow(wt::AmptekC2) + data = CSV.read(joinpath(@__DIR__, "data", "AMETEK Si3N4 C2.csv"), DataFrame, header=2) + inter = LinearInterpolation(1000.0 * data[:, 1], data[:, 2]) + extra = ModeledWindow(wt) + match = inter(1000.0 * data[end, 1]) / transmission(extra, 1000.0 * data[end, 1], π / 2) + TabulatedWindow(wt, inter, extra, match) end -"" diff --git a/test/Other/Spc(Calcite(2))_2.msa b/test/Other/Spc(Calcite(2))_2.msa new file mode 100644 index 0000000..12e1ab2 --- /dev/null +++ b/test/Other/Spc(Calcite(2))_2.msa @@ -0,0 +1,629 @@ +#FORMAT : EMSA/MAS Spectral Data File +#VERSION : 1.0 +#TITLE : Spc(Calcite(2))_2 +#DATE : 26-Apr-2022 +#TIME : 03:26 +#OWNER : +#NPOINTS : 3000 +#NCOLUMNS : 5 +#XUNITS : eV +#YUNITS : counts +#DATATYPE : Y +#XPERCHAN : 10.00000000 +#OFFSET : 0.00000000 +##SIMULATED : 0 +##COATING : C +#BEAMKV : 25.000 +##PROFILE : Rate +##DETECTOR1 : "SDD" 35.00 0.00 25.00 "EDAX, SiN 2016" 147.74 33.38 1.000 4 11 +##DETECTOR2 : "SDD" 35.00 0.00 25.00 "EDAX, SiN 2016" 149.16 33.31 1.000 4 11 +##DETECTOR3 : "SDD" 35.00 0.00 25.00 "EDAX, SiN 2016" 149.79 33.66 1.000 5 11 +##DETECTOR4 : "SDD" 35.00 0.00 25.00 "EDAX, SiN 2016" 149.56 33.30 1.000 4 11 +##BSELEVEL : 18.46799420 +##SAMPLE : KR21_1_126.97 TS 1 +##ORIGIN : TESCAN TIMA 2.5.1 Online +##XSAMPLE -m: 2.73000000e-03 +##YSAMPLE -m: -7.00000000e-04 +#SPECTRUM : +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +1.0, 1.0, 4.0, 8.0, 16.0, +32.0, 51.0, 91.0, 131.0, 190.0, +285.0, 340.0, 446.0, 474.0, 563.0, +557.0, 589.0, 548.0, 492.0, 455.0, +372.0, 302.0, 238.0, 197.0, 127.0, +124.0, 120.0, 125.0, 135.0, 170.0, +227.0, 272.0, 328.0, 473.0, 519.0, +728.0, 843.0, 1038.0, 1217.0, 1325.0, +1416.0, 1491.0, 1399.0, 1296.0, 1279.0, +1034.0, 838.0, 623.0, 477.0, 297.0, +254.0, 163.0, 112.0, 83.0, 70.0, +76.0, 91.0, 61.0, 74.0, 58.0, +67.0, 66.0, 69.0, 55.0, 67.0, +59.0, 68.0, 52.0, 61.0, 45.0, +57.0, 48.0, 47.0, 57.0, 63.0, +51.0, 52.0, 55.0, 54.0, 50.0, +51.0, 64.0, 68.0, 59.0, 79.0, +68.0, 66.0, 69.0, 77.0, 66.0, +81.0, 69.0, 103.0, 92.0, 96.0, +103.0, 88.0, 94.0, 97.0, 102.0, +114.0, 131.0, 141.0, 152.0, 170.0, +233.0, 310.0, 387.0, 508.0, 651.0, +803.0, 975.0, 1142.0, 1338.0, 1376.0, +1446.0, 1405.0, 1395.0, 1169.0, 1067.0, +908.0, 697.0, 565.0, 380.0, 306.0, +214.0, 167.0, 145.0, 112.0, 89.0, +102.0, 101.0, 95.0, 78.0, 83.0, +93.0, 97.0, 90.0, 90.0, 103.0, +105.0, 97.0, 89.0, 98.0, 95.0, +92.0, 98.0, 106.0, 108.0, 94.0, +100.0, 117.0, 111.0, 105.0, 101.0, +116.0, 98.0, 100.0, 119.0, 134.0, +144.0, 145.0, 146.0, 148.0, 166.0, +178.0, 169.0, 173.0, 164.0, 171.0, +175.0, 181.0, 170.0, 196.0, 154.0, +172.0, 146.0, 160.0, 163.0, 149.0, +179.0, 150.0, 146.0, 127.0, 152.0, +149.0, 153.0, 148.0, 131.0, 125.0, +120.0, 132.0, 122.0, 116.0, 119.0, +105.0, 110.0, 116.0, 103.0, 89.0, +100.0, 79.0, 110.0, 104.0, 93.0, +111.0, 106.0, 94.0, 108.0, 84.0, +115.0, 116.0, 90.0, 99.0, 115.0, +115.0, 113.0, 100.0, 98.0, 113.0, +103.0, 119.0, 114.0, 112.0, 103.0, +126.0, 103.0, 105.0, 125.0, 84.0, +92.0, 86.0, 113.0, 108.0, 114.0, +103.0, 114.0, 104.0, 107.0, 103.0, +113.0, 115.0, 109.0, 106.0, 109.0, +90.0, 105.0, 110.0, 110.0, 106.0, +119.0, 109.0, 110.0, 120.0, 135.0, +114.0, 131.0, 110.0, 78.0, 100.0, +96.0, 98.0, 101.0, 95.0, 91.0, +106.0, 76.0, 83.0, 89.0, 80.0, +89.0, 86.0, 90.0, 103.0, 100.0, +102.0, 106.0, 103.0, 101.0, 89.0, +94.0, 86.0, 88.0, 81.0, 104.0, +93.0, 100.0, 87.0, 83.0, 89.0, +102.0, 104.0, 91.0, 96.0, 98.0, +84.0, 92.0, 90.0, 98.0, 85.0, +94.0, 95.0, 113.0, 89.0, 86.0, +78.0, 93.0, 101.0, 88.0, 89.0, +93.0, 91.0, 92.0, 61.0, 89.0, +90.0, 86.0, 102.0, 81.0, 78.0, +109.0, 95.0, 86.0, 105.0, 93.0, +113.0, 88.0, 92.0, 96.0, 109.0, +91.0, 110.0, 110.0, 124.0, 114.0, +113.0, 104.0, 117.0, 132.0, 138.0, +134.0, 133.0, 179.0, 172.0, 218.0, +264.0, 349.0, 437.0, 592.0, 708.0, +972.0, 1264.0, 1555.0, 1851.0, 2197.0, +2504.0, 2896.0, 2917.0, 3106.0, 3121.0, +3122.0, 2992.0, 2647.0, 2476.0, 2097.0, +1799.0, 1455.0, 1195.0, 883.0, 721.0, +535.0, 392.0, 300.0, 234.0, 183.0, +155.0, 135.0, 112.0, 155.0, 143.0, +134.0, 174.0, 213.0, 293.0, 244.0, +301.0, 360.0, 367.0, 416.0, 447.0, +452.0, 463.0, 467.0, 435.0, 428.0, +381.0, 341.0, 322.0, 260.0, 199.0, +205.0, 173.0, 150.0, 104.0, 105.0, +94.0, 115.0, 97.0, 76.0, 86.0, +81.0, 86.0, 92.0, 74.0, 75.0, +67.0, 73.0, 58.0, 69.0, 72.0, +71.0, 71.0, 70.0, 68.0, 54.0, +68.0, 58.0, 51.0, 73.0, 62.0, +56.0, 62.0, 63.0, 71.0, 54.0, +52.0, 61.0, 59.0, 79.0, 79.0, +66.0, 66.0, 63.0, 65.0, 57.0, +66.0, 70.0, 61.0, 59.0, 63.0, +57.0, 60.0, 65.0, 52.0, 62.0, +60.0, 52.0, 68.0, 60.0, 64.0, +60.0, 51.0, 53.0, 63.0, 59.0, +63.0, 58.0, 58.0, 75.0, 73.0, +63.0, 58.0, 62.0, 73.0, 59.0, +57.0, 57.0, 72.0, 64.0, 73.0, +69.0, 71.0, 75.0, 86.0, 69.0, +71.0, 83.0, 75.0, 87.0, 70.0, +60.0, 64.0, 66.0, 58.0, 57.0, +74.0, 61.0, 60.0, 61.0, 51.0, +45.0, 64.0, 64.0, 42.0, 47.0, +51.0, 50.0, 42.0, 48.0, 53.0, +39.0, 50.0, 61.0, 50.0, 61.0, +63.0, 41.0, 48.0, 43.0, 55.0, +47.0, 44.0, 45.0, 63.0, 45.0, +48.0, 40.0, 53.0, 53.0, 43.0, +49.0, 48.0, 44.0, 64.0, 44.0, +52.0, 49.0, 48.0, 42.0, 44.0, +41.0, 38.0, 55.0, 53.0, 63.0, +38.0, 51.0, 64.0, 42.0, 57.0, +42.0, 49.0, 35.0, 44.0, 48.0, +62.0, 53.0, 56.0, 44.0, 48.0, +53.0, 51.0, 46.0, 52.0, 54.0, +52.0, 65.0, 55.0, 93.0, 86.0, +90.0, 91.0, 123.0, 125.0, 105.0, +131.0, 146.0, 155.0, 146.0, 152.0, +122.0, 142.0, 146.0, 136.0, 119.0, +108.0, 87.0, 114.0, 82.0, 66.0, +60.0, 65.0, 53.0, 52.0, 54.0, +41.0, 39.0, 52.0, 54.0, 47.0, +41.0, 35.0, 43.0, 38.0, 49.0, +48.0, 62.0, 44.0, 32.0, 36.0, +57.0, 41.0, 44.0, 48.0, 58.0, +60.0, 60.0, 85.0, 83.0, 110.0, +114.0, 133.0, 144.0, 187.0, 183.0, +201.0, 227.0, 271.0, 221.0, 254.0, +250.0, 257.0, 243.0, 235.0, 177.0, +191.0, 173.0, 177.0, 146.0, 116.0, +106.0, 75.0, 99.0, 73.0, 75.0, +70.0, 55.0, 47.0, 42.0, 34.0, +51.0, 48.0, 35.0, 45.0, 36.0, +39.0, 35.0, 37.0, 36.0, 41.0, +37.0, 42.0, 39.0, 36.0, 42.0, +36.0, 44.0, 37.0, 48.0, 41.0, +38.0, 35.0, 35.0, 41.0, 33.0, +29.0, 40.0, 43.0, 34.0, 38.0, +45.0, 49.0, 47.0, 52.0, 43.0, +31.0, 50.0, 54.0, 59.0, 62.0, +58.0, 54.0, 61.0, 65.0, 83.0, +64.0, 62.0, 57.0, 61.0, 74.0, +61.0, 62.0, 43.0, 64.0, 27.0, +40.0, 50.0, 39.0, 43.0, 49.0, +37.0, 30.0, 49.0, 40.0, 35.0, +44.0, 36.0, 43.0, 35.0, 54.0, +51.0, 56.0, 46.0, 53.0, 43.0, +67.0, 52.0, 66.0, 68.0, 58.0, +59.0, 53.0, 50.0, 52.0, 52.0, +41.0, 40.0, 40.0, 36.0, 39.0, +34.0, 38.0, 38.0, 22.0, 37.0, +28.0, 40.0, 41.0, 41.0, 38.0, +48.0, 44.0, 26.0, 49.0, 42.0, +46.0, 39.0, 48.0, 30.0, 37.0, +50.0, 47.0, 40.0, 34.0, 30.0, +41.0, 30.0, 35.0, 29.0, 38.0, +28.0, 30.0, 32.0, 29.0, 31.0, +31.0, 30.0, 34.0, 29.0, 36.0, +25.0, 21.0, 22.0, 27.0, 36.0, +28.0, 34.0, 31.0, 26.0, 27.0, +23.0, 30.0, 30.0, 23.0, 29.0, +31.0, 25.0, 24.0, 26.0, 28.0, +21.0, 28.0, 37.0, 36.0, 27.0, +26.0, 22.0, 30.0, 31.0, 28.0, +30.0, 30.0, 39.0, 31.0, 23.0, +19.0, 35.0, 27.0, 28.0, 28.0, +20.0, 20.0, 33.0, 22.0, 28.0, +26.0, 19.0, 26.0, 31.0, 28.0, +23.0, 27.0, 26.0, 18.0, 31.0, +27.0, 24.0, 22.0, 22.0, 27.0, +22.0, 29.0, 34.0, 24.0, 20.0, +29.0, 24.0, 34.0, 15.0, 28.0, +32.0, 34.0, 22.0, 19.0, 21.0, +26.0, 28.0, 27.0, 22.0, 33.0, +26.0, 33.0, 31.0, 33.0, 19.0, +23.0, 25.0, 18.0, 24.0, 27.0, +28.0, 31.0, 36.0, 22.0, 17.0, +26.0, 26.0, 21.0, 22.0, 26.0, +25.0, 20.0, 21.0, 18.0, 23.0, +23.0, 22.0, 18.0, 17.0, 26.0, +14.0, 25.0, 19.0, 23.0, 28.0, +29.0, 27.0, 21.0, 25.0, 21.0, +29.0, 22.0, 24.0, 25.0, 31.0, +18.0, 24.0, 28.0, 14.0, 27.0, +16.0, 23.0, 13.0, 23.0, 22.0, +21.0, 25.0, 16.0, 14.0, 27.0, +33.0, 22.0, 12.0, 18.0, 24.0, +23.0, 18.0, 22.0, 16.0, 18.0, +28.0, 25.0, 23.0, 25.0, 29.0, +26.0, 23.0, 35.0, 15.0, 23.0, +25.0, 23.0, 26.0, 27.0, 23.0, +20.0, 18.0, 29.0, 22.0, 28.0, +24.0, 23.0, 22.0, 20.0, 27.0, +21.0, 20.0, 17.0, 28.0, 18.0, +31.0, 18.0, 21.0, 27.0, 20.0, +25.0, 21.0, 21.0, 17.0, 15.0, +13.0, 14.0, 16.0, 19.0, 16.0, +7.0, 22.0, 20.0, 18.0, 17.0, +23.0, 22.0, 17.0, 16.0, 19.0, +14.0, 24.0, 26.0, 16.0, 30.0, +19.0, 16.0, 14.0, 18.0, 22.0, +24.0, 28.0, 17.0, 32.0, 26.0, +19.0, 18.0, 20.0, 17.0, 26.0, +26.0, 20.0, 13.0, 22.0, 25.0, +19.0, 19.0, 22.0, 16.0, 15.0, +18.0, 18.0, 13.0, 26.0, 17.0, +13.0, 20.0, 22.0, 12.0, 20.0, +27.0, 16.0, 29.0, 19.0, 22.0, +19.0, 18.0, 21.0, 22.0, 21.0, +22.0, 22.0, 21.0, 15.0, 17.0, +19.0, 17.0, 13.0, 16.0, 19.0, +17.0, 18.0, 13.0, 10.0, 13.0, +14.0, 19.0, 15.0, 13.0, 20.0, +17.0, 14.0, 12.0, 16.0, 11.0, +16.0, 15.0, 15.0, 8.0, 16.0, +12.0, 18.0, 14.0, 17.0, 15.0, +14.0, 17.0, 16.0, 15.0, 17.0, +14.0, 18.0, 21.0, 17.0, 19.0, +14.0, 22.0, 8.0, 9.0, 11.0, +16.0, 20.0, 17.0, 14.0, 21.0, +17.0, 12.0, 12.0, 8.0, 16.0, +17.0, 8.0, 16.0, 21.0, 11.0, +15.0, 13.0, 17.0, 11.0, 14.0, +21.0, 10.0, 15.0, 15.0, 13.0, +18.0, 11.0, 18.0, 16.0, 14.0, +13.0, 13.0, 21.0, 12.0, 15.0, +15.0, 12.0, 7.0, 9.0, 9.0, +15.0, 15.0, 11.0, 8.0, 14.0, +6.0, 9.0, 16.0, 16.0, 11.0, +13.0, 15.0, 14.0, 9.0, 14.0, +11.0, 21.0, 22.0, 15.0, 9.0, +16.0, 19.0, 13.0, 9.0, 15.0, +18.0, 12.0, 14.0, 14.0, 15.0, +21.0, 9.0, 14.0, 12.0, 13.0, +11.0, 13.0, 13.0, 7.0, 16.0, +18.0, 8.0, 18.0, 8.0, 23.0, +12.0, 17.0, 12.0, 8.0, 9.0, +13.0, 8.0, 17.0, 14.0, 12.0, +8.0, 15.0, 13.0, 11.0, 12.0, +13.0, 10.0, 8.0, 13.0, 12.0, +13.0, 15.0, 13.0, 14.0, 14.0, +13.0, 10.0, 10.0, 7.0, 12.0, +17.0, 14.0, 12.0, 11.0, 10.0, +14.0, 18.0, 15.0, 10.0, 14.0, +8.0, 14.0, 12.0, 18.0, 13.0, +5.0, 9.0, 12.0, 15.0, 12.0, +8.0, 5.0, 9.0, 8.0, 3.0, +9.0, 11.0, 13.0, 8.0, 7.0, +8.0, 10.0, 9.0, 15.0, 14.0, +9.0, 6.0, 11.0, 9.0, 13.0, +10.0, 10.0, 13.0, 12.0, 11.0, +10.0, 9.0, 12.0, 13.0, 12.0, +5.0, 13.0, 6.0, 5.0, 9.0, +16.0, 9.0, 12.0, 8.0, 10.0, +10.0, 5.0, 11.0, 8.0, 13.0, +10.0, 13.0, 8.0, 11.0, 11.0, +11.0, 6.0, 7.0, 11.0, 14.0, +7.0, 10.0, 6.0, 10.0, 13.0, +9.0, 11.0, 8.0, 8.0, 6.0, +10.0, 7.0, 9.0, 11.0, 9.0, +9.0, 11.0, 11.0, 14.0, 11.0, +9.0, 13.0, 11.0, 8.0, 14.0, +9.0, 5.0, 6.0, 8.0, 8.0, +6.0, 11.0, 5.0, 11.0, 9.0, +13.0, 16.0, 5.0, 11.0, 8.0, +6.0, 4.0, 11.0, 7.0, 6.0, +8.0, 11.0, 5.0, 9.0, 9.0, +8.0, 8.0, 8.0, 12.0, 8.0, +7.0, 7.0, 9.0, 11.0, 6.0, +5.0, 8.0, 10.0, 9.0, 8.0, +9.0, 6.0, 7.0, 6.0, 13.0, +4.0, 7.0, 4.0, 8.0, 7.0, +8.0, 9.0, 9.0, 6.0, 13.0, +7.0, 7.0, 5.0, 12.0, 11.0, +9.0, 7.0, 7.0, 2.0, 5.0, +7.0, 12.0, 9.0, 8.0, 9.0, +13.0, 4.0, 5.0, 11.0, 4.0, +12.0, 11.0, 7.0, 7.0, 10.0, +4.0, 5.0, 10.0, 9.0, 12.0, +3.0, 7.0, 10.0, 6.0, 9.0, +8.0, 14.0, 6.0, 9.0, 6.0, +11.0, 6.0, 9.0, 7.0, 10.0, +11.0, 13.0, 14.0, 9.0, 10.0, +13.0, 7.0, 6.0, 7.0, 11.0, +7.0, 11.0, 3.0, 7.0, 9.0, +6.0, 3.0, 8.0, 6.0, 8.0, +8.0, 7.0, 6.0, 6.0, 8.0, +9.0, 10.0, 4.0, 8.0, 5.0, +6.0, 7.0, 2.0, 3.0, 8.0, +2.0, 7.0, 5.0, 3.0, 5.0, +6.0, 6.0, 2.0, 7.0, 3.0, +8.0, 6.0, 4.0, 2.0, 6.0, +12.0, 4.0, 6.0, 4.0, 6.0, +8.0, 4.0, 9.0, 3.0, 6.0, +3.0, 7.0, 8.0, 3.0, 8.0, +5.0, 3.0, 6.0, 6.0, 6.0, +4.0, 4.0, 2.0, 9.0, 5.0, +3.0, 4.0, 2.0, 2.0, 6.0, +7.0, 7.0, 3.0, 3.0, 2.0, +9.0, 9.0, 7.0, 6.0, 2.0, +3.0, 13.0, 8.0, 3.0, 10.0, +6.0, 3.0, 7.0, 4.0, 6.0, +8.0, 10.0, 6.0, 3.0, 7.0, +10.0, 5.0, 5.0, 9.0, 8.0, +8.0, 6.0, 5.0, 5.0, 4.0, +8.0, 10.0, 5.0, 5.0, 8.0, +4.0, 6.0, 3.0, 3.0, 1.0, +6.0, 5.0, 5.0, 5.0, 4.0, +2.0, 5.0, 5.0, 7.0, 2.0, +4.0, 9.0, 4.0, 5.0, 4.0, +6.0, 5.0, 2.0, 3.0, 1.0, +9.0, 3.0, 4.0, 4.0, 4.0, +9.0, 6.0, 4.0, 1.0, 7.0, +4.0, 6.0, 1.0, 2.0, 4.0, +5.0, 3.0, 6.0, 3.0, 5.0, +2.0, 5.0, 7.0, 2.0, 9.0, +5.0, 2.0, 2.0, 1.0, 1.0, +4.0, 2.0, 4.0, 5.0, 2.0, +3.0, 3.0, 8.0, 1.0, 4.0, +6.0, 2.0, 3.0, 6.0, 6.0, +1.0, 4.0, 3.0, 1.0, 2.0, +4.0, 5.0, 4.0, 3.0, 6.0, +3.0, 2.0, 2.0, 6.0, 6.0, +5.0, 9.0, 2.0, 2.0, 6.0, +1.0, 3.0, 3.0, 5.0, 4.0, +1.0, 7.0, 5.0, 2.0, 0.0, +1.0, 3.0, 5.0, 3.0, 6.0, +7.0, 5.0, 2.0, 7.0, 3.0, +5.0, 1.0, 4.0, 2.0, 6.0, +3.0, 3.0, 5.0, 3.0, 2.0, +2.0, 4.0, 10.0, 7.0, 5.0, +1.0, 4.0, 4.0, 1.0, 7.0, +1.0, 1.0, 1.0, 3.0, 5.0, +2.0, 2.0, 5.0, 2.0, 4.0, +2.0, 3.0, 3.0, 3.0, 5.0, +1.0, 7.0, 2.0, 1.0, 4.0, +3.0, 5.0, 3.0, 5.0, 4.0, +2.0, 3.0, 3.0, 0.0, 4.0, +2.0, 0.0, 1.0, 0.0, 4.0, +1.0, 4.0, 3.0, 3.0, 2.0, +3.0, 5.0, 2.0, 1.0, 2.0, +4.0, 3.0, 3.0, 1.0, 2.0, +0.0, 2.0, 3.0, 4.0, 3.0, +5.0, 1.0, 4.0, 3.0, 4.0, +6.0, 2.0, 3.0, 3.0, 4.0, +2.0, 2.0, 4.0, 3.0, 4.0, +5.0, 4.0, 1.0, 6.0, 1.0, +2.0, 2.0, 8.0, 0.0, 1.0, +1.0, 3.0, 4.0, 2.0, 2.0, +3.0, 3.0, 5.0, 2.0, 7.0, +2.0, 4.0, 1.0, 4.0, 4.0, +4.0, 1.0, 2.0, 1.0, 2.0, +3.0, 2.0, 2.0, 2.0, 2.0, +2.0, 2.0, 3.0, 3.0, 3.0, +4.0, 1.0, 3.0, 2.0, 1.0, +1.0, 3.0, 2.0, 2.0, 4.0, +3.0, 4.0, 0.0, 4.0, 2.0, +1.0, 0.0, 0.0, 2.0, 6.0, +3.0, 3.0, 1.0, 1.0, 3.0, +1.0, 2.0, 2.0, 3.0, 2.0, +2.0, 1.0, 5.0, 6.0, 2.0, +0.0, 2.0, 0.0, 5.0, 7.0, +1.0, 2.0, 3.0, 3.0, 0.0, +2.0, 2.0, 1.0, 3.0, 3.0, +3.0, 1.0, 1.0, 4.0, 2.0, +2.0, 1.0, 0.0, 3.0, 2.0, +2.0, 2.0, 2.0, 1.0, 3.0, +4.0, 4.0, 0.0, 3.0, 2.0, +2.0, 3.0, 4.0, 0.0, 1.0, +0.0, 3.0, 1.0, 1.0, 1.0, +3.0, 1.0, 2.0, 3.0, 0.0, +0.0, 2.0, 4.0, 3.0, 0.0, +0.0, 0.0, 2.0, 5.0, 1.0, +3.0, 2.0, 0.0, 1.0, 0.0, +2.0, 3.0, 2.0, 1.0, 1.0, +3.0, 2.0, 2.0, 1.0, 0.0, +4.0, 0.0, 4.0, 2.0, 3.0, +1.0, 2.0, 0.0, 1.0, 1.0, +3.0, 1.0, 0.0, 0.0, 0.0, +1.0, 4.0, 2.0, 1.0, 1.0, +3.0, 3.0, 0.0, 1.0, 1.0, +3.0, 2.0, 1.0, 1.0, 1.0, +2.0, 1.0, 4.0, 2.0, 1.0, +0.0, 2.0, 3.0, 2.0, 1.0, +0.0, 1.0, 0.0, 2.0, 2.0, +2.0, 1.0, 0.0, 1.0, 1.0, +2.0, 2.0, 0.0, 3.0, 1.0, +3.0, 0.0, 1.0, 0.0, 0.0, +0.0, 4.0, 0.0, 3.0, 2.0, +3.0, 1.0, 3.0, 4.0, 1.0, +0.0, 0.0, 2.0, 0.0, 2.0, +2.0, 2.0, 0.0, 0.0, 0.0, +1.0, 0.0, 0.0, 1.0, 1.0, +0.0, 0.0, 3.0, 0.0, 0.0, +1.0, 1.0, 0.0, 2.0, 1.0, +0.0, 4.0, 3.0, 2.0, 0.0, +0.0, 0.0, 2.0, 2.0, 2.0, +1.0, 1.0, 1.0, 2.0, 0.0, +0.0, 0.0, 1.0, 0.0, 0.0, +1.0, 1.0, 2.0, 0.0, 0.0, +1.0, 0.0, 3.0, 0.0, 2.0, +0.0, 2.0, 3.0, 1.0, 1.0, +0.0, 0.0, 2.0, 2.0, 0.0, +2.0, 0.0, 2.0, 1.0, 2.0, +0.0, 0.0, 1.0, 1.0, 2.0, +1.0, 1.0, 0.0, 0.0, 1.0, +1.0, 2.0, 2.0, 0.0, 1.0, +1.0, 1.0, 2.0, 0.0, 1.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +1.0, 0.0, 2.0, 3.0, 1.0, +0.0, 1.0, 2.0, 0.0, 0.0, +1.0, 0.0, 1.0, 0.0, 0.0, +2.0, 0.0, 0.0, 1.0, 1.0, +0.0, 1.0, 0.0, 0.0, 1.0, +1.0, 1.0, 0.0, 2.0, 1.0, +0.0, 0.0, 1.0, 3.0, 0.0, +0.0, 1.0, 1.0, 0.0, 0.0, +0.0, 1.0, 1.0, 0.0, 0.0, +1.0, 3.0, 0.0, 0.0, 0.0, +1.0, 0.0, 1.0, 0.0, 2.0, +0.0, 2.0, 0.0, 1.0, 0.0, +2.0, 0.0, 1.0, 0.0, 0.0, +0.0, 2.0, 1.0, 0.0, 4.0, +1.0, 0.0, 1.0, 0.0, 0.0, +1.0, 0.0, 0.0, 0.0, 0.0, +3.0, 1.0, 1.0, 1.0, 1.0, +1.0, 0.0, 2.0, 0.0, 0.0, +1.0, 1.0, 0.0, 1.0, 0.0, +1.0, 0.0, 0.0, 2.0, 0.0, +0.0, 1.0, 0.0, 1.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 1.0, 2.0, 1.0, +0.0, 1.0, 0.0, 3.0, 0.0, +2.0, 0.0, 1.0, 1.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +1.0, 1.0, 0.0, 0.0, 1.0, +0.0, 0.0, 1.0, 1.0, 0.0, +1.0, 0.0, 0.0, 1.0, 0.0, +0.0, 0.0, 0.0, 0.0, 1.0, +0.0, 0.0, 1.0, 1.0, 1.0, +1.0, 1.0, 0.0, 1.0, 0.0, +1.0, 2.0, 0.0, 0.0, 0.0, +0.0, 1.0, 1.0, 2.0, 0.0, +0.0, 1.0, 0.0, 0.0, 0.0, +0.0, 0.0, 1.0, 0.0, 1.0, +0.0, 0.0, 0.0, 0.0, 1.0, +0.0, 1.0, 0.0, 0.0, 1.0, +0.0, 0.0, 0.0, 0.0, 1.0, +0.0, 1.0, 0.0, 0.0, 0.0, +0.0, 0.0, 2.0, 0.0, 1.0, +0.0, 1.0, 0.0, 0.0, 0.0, +1.0, 1.0, 2.0, 0.0, 1.0, +0.0, 0.0, 0.0, 0.0, 0.0, +1.0, 1.0, 0.0, 1.0, 0.0, +0.0, 0.0, 0.0, 1.0, 0.0, +0.0, 0.0, 0.0, 1.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 1.0, 1.0, 0.0, +0.0, 0.0, 1.0, 0.0, 1.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 2.0, 1.0, 0.0, 0.0, +0.0, 0.0, 2.0, 1.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +2.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 1.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 1.0, +1.0, 0.0, 0.0, 0.0, 1.0, +0.0, 0.0, 1.0, 0.0, 0.0, +1.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 2.0, +1.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 1.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 2.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 1.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 1.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 1.0, 0.0, +0.0, 0.0, 0.0, 0.0, 1.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 1.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 1.0, 1.0, +0.0, 0.0, 1.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 1.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 1.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 1.0, +0.0, 1.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +1.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 1.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 1.0, 1.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 1.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +1.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 1.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 1.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 1.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, +0.0, 0.0, 0.0, 0.0, 0.0, + +#ENDOFDATA : diff --git a/test/Other/chevkinite-1.spx b/test/Other/chevkinite-1.spx new file mode 100644 index 0000000..716214f --- /dev/null +++ b/test/Other/chevkinite-1.spx @@ -0,0 +1,416 @@ + + + + + + + + RTHardware + 137 + + 63440 + 60227 + 5 + 95 + 2666 + 16722 + 2E4 + 60000 + + + + RTDetector + 5 + 9932 + + SD3pr + 8273/74 + XFlash 6|10 + 0.45 + 0.029 + eJyzcUkt8UmsTC0qtrMB0wYKjiX5ubZKhsZKCiEZmcnZeanFxbZKpq66xkr6UDWGUDXmKEos9ICKjOCKjKCKTFEUmSGbYwxVYoZbiQlUiQWqErhV+gj3AwCpRT07 + slew AP3.3 + + + + + + + + + + + + Internal + 1 + + + Internal + 8E-2 + 1E1 + 1 + 1 + + + Internal + 8E-2 + 5.55E-1 + 1 + + + + 3 + 21 + 5 + -3 + 0 + 1 + False + 1 + 1 + 3.975E-1 + -9.82E-1 + 0.07,0.0058,0.183,0.0078,0.277,0.0058,0.555,0,1.1,0,3.293,0.0064,5.89,0,0,0,0,0, + 0,0.01,0.000801,0.01,0.00298,0.01,0.008902,0.01,0.025,0.010046,0.041098,0.013475,0.04702,0.017302,0.049199,0.019237,0.05,0.02, + 0,0.026417,0.003686,0.026417,0.013709,0.026417,0.04095,0.026417,0.115,0.026585,0.18905,0.039072,0.216292,0.053009,0.226314,0.060056,0.23,0.062835, + 0,0.03,0.005362,0.03,0.019937,0.03,0.059556,0.03,0.16725,0.030229,0.274944,0.047376,0.314563,0.066511,0.329139,0.076186,0.3345,0.08, + 0,0.025,0.007349,0.025,0.027328,0.025,0.081633,0.025,0.22925,0.025114,0.376867,0.033706,0.431172,0.043293,0.451151,0.048139,0.4585,0.05005, + 0,0.025,0.009626,0.025,0.035791,0.025,0.106915,0.025,0.30025,0.025,0.493585,0.025035,0.564709,0.025073,0.590874,0.025092,0.6005,0.0251, + 0,0.025,0.013761,0.025,0.051168,0.025,0.15285,0.025,0.42925,0.025,0.70565,0.025035,0.807332,0.025073,0.844738,0.025092,0.8585,0.0251, + 0,0.025,0.018394,0.025,0.068393,0.025,0.204305,0.025,0.57375,0.025,0.943195,0.025035,1.079108,0.025073,1.129106,0.025092,1.1475,0.0251, + 0,0.025,0.021968,0.025,0.081684,0.025,0.244008,0.025,0.68525,0.025,1.126492,0.025035,1.288816,0.025073,1.348531,0.025092,1.3705,0.0251, + 0,0.025,0.025864,0.025,0.096167,0.025,0.287273,0.025,0.80675,0.025,1.326227,0.025035,1.517333,0.025073,1.587636,0.025092,1.6135,0.0251, + 0,0.015,0.029334,0.015,0.109071,0.015,0.325818,0.015,0.915,0.015,1.504182,0.015035,1.720929,0.015073,1.800667,0.015092,1.83,0.0151, + 0,0.015,0.03153,0.015,0.117236,0.015,0.35021,0.015,0.9835,0.015,1.61679,0.015035,1.849764,0.015073,1.935471,0.015092,1.967,0.0151, + 0,0.015,0.03464,0.015,0.128798,0.015,0.38475,0.015,1.0805,0.015,1.776249,0.015035,2.032202,0.015073,2.12636,0.015092,2.161,0.0151, + 0,0,0.048088,0,0.178804,0,0.534129,0,1.5,0,2.465871,0.000035,2.821196,0.000073,2.951912,0.000092,3,0.0001, + 0,0,0.069279,0,0.257598,0,0.769501,0,2.161,0,3.552499,0.000035,4.064403,0.000073,4.252721,0.000092,4.322,0.0001, + 0,0,0.099623,0,0.370422,0,1.106536,0,3.1075,0,5.108463,0.000035,5.844577,0.000073,6.115378,0.000092,6.215,0.0001, + 0,0,0.134085,0,0.498566,0,1.489329,0,4.1825,0,6.875671,0.000035,7.866434,0.000073,8.230915,0.000092,8.365,0.0001, + 0,0,0.162313,0,0.603525,0,1.802863,0,5.063,0,8.323137,0.000035,9.522476,0.000073,9.963688,0.000092,10.125999,0.0001, + 0,0,0.192351,0,0.715217,0,2.136515,0,6,0,9.863485,0.000035,11.284782,0.000073,11.807649,0.000092,12,0.0001, + 0,0,0.24044,0,0.894022,0,2.670643,0,7.5,0,12.329357,0,14.105978,0,14.759561,0,15,0, + 0,0,0.320586,0,1.192029,0,3.560857,0,10,0,16.439142,0,18.80797,0,19.679415,0,20,0, + 0,0,1.60293,0,5.960146,0,17.804287,0,50,0,82.195709,0,94.039856,0,98.397072,0,100,0, + + + + RTESMA + 662 + + 2E1 + -1 + -1 + 1.326969701E-3 + 3.5E1 + 9E1 + 2 + + + + + 82 + 29.3.2021 + + 4096 + -4.7715E-1 + 5E-3 + 1.44877381E-4 + 4.311038316E-4 + + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,1,5,11,61,209,659,1888,4647,9272,15622,21949,25992,26049,21807,15368,9109,4742,2076,789,278,103,29,12,6,11,13,4,5,6,2,3,1,1,2,0,0,133,377,317,311,337,351,344,308,345,375,372,374,356,364,321,370,349,353,364,362,358,434,408,415,402,463,505,513,499,514,521,483,505,451,402,390,294,312,340,318,297,290,309,321,341,338,328,325,332,329,336,356,331,387,342,372,368,388,403,394,374,446,419,394,468,495,517,513,557,569,642,723,842,885,1175,1463,1812,2057,2374,2653,2783,2707,2556,2238,1843,1460,1085,754,521,360,319,237,212,211,195,179,232,241,233,254,270,277,275,289,287,320,330,317,350,349,360,441,474,473,494,521,568,582,538,517,550,502,444,412,359,306,281,243,270,252,233,259,239,261,258,276,243,261,271,278,276,295,321,326,338,346,372,347,402,384,407,436,460,415,465,437,461,364,429,375,381,333,370,335,342,343,331,316,329,366,346,357,414,383,399,390,406,410,411,378,339,347,307,370,310,331,316,373,341,326,351,356,328,358,328,352,322,382,338,329,320,320,316,373,345,352,327,360,347,348,392,327,352,342,338,343,345,346,334,317,339,360,394,404,357,359,361,395,389,343,368,390,390,360,358,336,376,311,375,321,304,293,352,340,320,319,354,329,295,330,345,330,284,325,338,316,389,347,338,317,331,360,350,342,361,345,365,374,368,378,360,384,363,416,417,430,383,405,378,361,400,381,446,394,422,408,416,398,396,456,416,412,418,391,448,433,378,446,440,398,440,415,402,444,423,434,442,522,532,652,700,813,983,1195,1461,1793,2307,2602,3019,3554,3870,4538,4625,5039,5093,5061,4948,4529,4299,3797,3486,3069,2633,2183,1782,1540,1307,1130,958,791,817,758,761,658,574,599,597,500,498,490,461,473,425,386,379,406,413,395,384,371,366,350,379,361,388,366,393,366,381,371,389,390,394,368,352,426,402,355,401,379,372,348,395,403,405,358,401,365,381,389,400,376,371,370,409,396,409,392,393,405,402,420,395,422,418,432,422,419,416,467,495,463,416,431,484,455,431,420,440,453,430,355,443,412,427,424,393,446,407,409,379,426,404,420,410,418,408,424,389,388,412,431,404,426,386,425,410,378,415,413,399,422,394,375,405,419,374,393,325,378,349,395,397,383,387,410,410,390,418,422,377,440,384,393,384,391,396,402,387,382,408,395,435,400,412,369,422,412,415,397,408,392,402,374,396,409,386,395,422,399,382,430,429,392,424,376,415,450,422,393,393,398,436,379,371,412,390,446,429,439,402,434,412,435,394,423,425,433,407,405,449,442,450,427,399,431,417,375,391,399,390,371,427,385,424,395,440,378,360,416,415,403,430,373,417,399,421,403,418,408,416,420,421,453,406,442,406,436,448,453,482,457,473,430,527,499,500,476,471,465,426,495,455,451,455,493,454,447,425,416,446,379,450,412,445,478,450,391,446,470,453,447,440,483,438,451,443,435,442,439,413,458,459,436,405,403,436,407,444,401,425,390,411,386,415,386,366,394,388,421,386,437,408,355,392,362,356,400,402,402,378,376,389,396,413,391,389,365,388,407,403,354,350,395,365,399,370,374,419,402,353,369,384,331,385,369,379,373,374,399,384,343,349,393,370,370,384,392,371,379,389,343,371,361,385,389,354,359,355,347,412,379,386,411,407,416,389,434,424,428,397,493,497,559,555,588,621,633,695,770,740,815,758,795,819,904,792,799,783,782,712,674,716,610,648,612,566,532,527,506,460,418,393,390,371,352,329,378,344,364,360,357,338,398,364,349,367,372,348,337,336,319,359,332,377,304,350,354,364,328,368,375,327,343,372,335,364,361,407,359,389,414,364,395,370,389,378,387,419,393,405,394,415,393,404,403,331,389,409,400,398,400,376,382,392,403,398,354,385,369,438,347,386,371,387,356,344,372,369,343,385,374,345,347,379,353,351,388,424,368,360,379,386,380,377,392,385,364,387,445,412,426,412,418,421,422,404,409,412,433,385,425,374,403,386,385,411,388,412,422,403,446,428,432,464,470,561,578,642,745,792,878,1017,1262,1287,1479,1656,1868,2053,2340,2490,2786,2895,3173,3263,3440,3557,3645,3592,3772,3527,3554,3358,3271,3171,2922,2703,2446,2433,2235,2000,1863,1689,1616,1527,1468,1411,1412,1337,1315,1402,1408,1413,1435,1391,1468,1425,1483,1388,1396,1258,1291,1209,1168,1050,1115,1008,964,911,851,786,802,777,724,798,852,856,940,984,1055,1224,1277,1393,1539,1711,1815,1866,2063,2201,2316,2434,2505,2559,2598,2622,2604,2624,2580,2562,2414,2371,2241,2161,1922,1985,1811,1693,1655,1481,1402,1340,1192,1212,1140,1106,1018,1012,962,982,867,905,908,916,921,987,947,932,960,1017,995,1027,1006,1001,982,989,1039,1036,960,874,888,908,886,870,855,744,724,676,645,642,649,628,591,593,622,601,647,712,718,777,857,916,969,1011,1069,1154,1307,1314,1442,1521,1530,1727,1832,1812,1845,1833,1932,1961,1859,1900,1830,1815,1781,1753,1744,1605,1519,1374,1356,1263,1188,1136,1105,994,981,936,893,844,853,714,759,695,689,703,685,598,665,592,558,548,561,579,540,523,479,435,459,447,524,454,430,417,452,447,406,408,389,423,396,434,415,426,412,420,442,438,445,481,485,527,525,538,556,615,599,635,666,632,697,665,687,694,744,735,703,713,690,741,691,700,670,693,651,681,659,624,627,655,647,598,672,621,611,653,628,609,563,591,556,555,523,530,536,518,543,497,485,443,428,460,505,475,436,491,466,450,420,502,490,457,476,506,430,419,451,421,435,413,397,420,402,387,409,319,329,340,344,307,337,307,292,317,297,317,358,337,342,351,384,397,393,380,426,435,489,408,446,459,487,470,461,475,485,486,416,459,454,440,440,422,388,373,350,337,349,300,294,309,320,291,252,252,246,290,274,263,256,246,237,243,255,242,235,236,256,271,286,273,301,289,305,306,357,347,423,463,493,524,562,638,720,748,741,862,883,1014,1056,1157,1224,1251,1376,1353,1485,1501,1519,1554,1550,1565,1497,1515,1429,1473,1403,1336,1253,1213,1112,1073,951,897,807,762,698,678,603,507,528,414,422,394,358,328,330,299,275,299,287,257,253,266,230,234,263,268,214,259,214,226,192,236,212,235,219,207,210,202,205,200,219,200,171,185,180,154,165,174,196,183,194,183,172,158,206,171,144,161,153,178,185,179,168,173,185,168,160,168,145,171,183,161,174,158,176,157,167,191,168,182,151,180,176,188,179,169,156,184,188,184,159,173,170,178,201,183,170,175,188,211,213,193,211,213,230,236,216,247,267,275,259,305,301,268,296,310,314,296,319,323,309,324,287,318,332,318,279,311,281,264,278,248,252,246,217,220,212,192,208,209,185,196,171,189,195,148,177,142,154,166,162,139,131,147,155,140,152,127,138,132,154,141,153,132,160,147,150,145,123,135,131,133,135,135,136,154,144,141,145,143,136,153,126,140,146,139,130,124,140,131,134,142,140,168,133,137,133,126,139,137,128,124,153,132,137,147,128,130,137,122,128,135,147,152,141,148,137,145,126,114,125,102,132,111,136,140,127,120,138,139,136,126,134,126,127,138,136,108,137,120,134,134,129,130,115,133,125,150,129,138,129,124,135,126,144,112,103,127,114,140,130,119,131,125,122,132,117,148,112,125,116,152,132,117,126,139,126,134,114,111,131,108,109,101,118,141,121,120,112,135,98,108,125,112,124,112,131,129,143,136,144,119,140,123,131,117,123,109,110,131,125,129,118,116,122,102,119,112,106,120,123,110,125,138,118,105,103,111,108,132,127,119,120,117,95,141,123,110,118,119,129,118,125,105,141,111,119,107,122,113,108,116,114,103,139,121,116,117,112,112,118,131,109,113,130,116,131,111,123,144,124,95,119,116,108,98,104,124,110,116,110,105,117,98,110,120,115,123,131,117,96,115,115,103,125,106,107,109,116,112,114,111,127,107,121,116,100,109,104,102,112,92,106,115,104,115,106,113,113,103,98,107,128,87,110,113,123,131,96,97,111,115,112,92,103,105,114,122,122,127,131,104,94,94,109,106,128,82,112,112,105,111,105,111,106,123,102,114,131,98,95,100,98,95,111,106,116,89,108,125,106,91,91,102,124,111,89,106,107,113,100,112,90,98,109,85,119,88,111,107,113,110,99,103,114,125,109,107,103,112,102,97,97,112,114,117,123,94,94,87,95,101,94,102,88,110,110,126,98,103,98,119,105,88,111,94,96,113,109,89,118,108,108,113,106,102,93,100,116,97,100,101,98,87,88,95,111,98,81,79,100,103,109,106,101,102,110,113,89,91,97,88,85,103,85,115,114,105,91,85,85,93,105,117,96,97,95,86,105,95,92,83,101,78,108,95,93,77,91,91,80,89,92,98,97,88,118,98,79,90,90,79,83,88,88,105,79,98,96,89,104,97,115,101,93,101,108,81,91,111,92,76,85,97,74,105,89,95,84,88,89,107,96,92,82,107,97,108,80,106,95,71,93,77,84,81,73,70,77,93,71,95,88,80,93,108,88,76,86,67,104,83,94,103,97,78,86,94,91,94,87,89,89,91,96,91,86,73,87,89,80,82,83,73,73,83,90,90,96,91,96,108,94,76,98,87,88,84,72,103,91,83,78,83,73,97,89,75,76,71,80,86,67,73,80,94,72,97,89,83,83,87,70,85,75,76,83,81,57,77,79,83,81,74,71,75,84,72,103,95,89,67,92,80,83,69,86,78,70,81,75,81,73,86,86,91,93,77,80,67,76,80,75,82,76,84,85,78,75,83,70,88,85,88,76,72,90,83,72,79,79,68,67,75,87,89,75,81,83,89,87,87,75,79,91,83,83,82,73,86,79,57,79,79,70,58,87,61,76,59,74,64,78,74,71,64,87,61,82,68,63,73,67,78,66,69,75,77,68,82,82,75,88,73,62,75,76,74,81,73,84,58,83,65,68,66,80,84,58,68,82,58,75,79,75,81,72,57,62,69,63,65,74,73,72,65,65,75,90,82,84,75,59,85,72,74,72,57,82,79,83,72,72,82,67,57,74,73,69,74,65,60,80,61,67,76,53,76,69,73,62,62,57,76,76,61,61,71,75,64,65,69,74,69,66,63,63,66,74,61,63,63,68,74,57,60,57,67,70,64,63,60,67,55,54,59,61,55,66,57,79,64,65,59,57,73,62,63,58,70,72,79,59,63,65,74,59,62,54,68,52,64,65,65,72,76,71,62,45,66,67,64,69,57,67,72,52,81,57,56,62,72,51,63,70,54,54,69,58,57,57,60,56,52,55,52,53,66,58,53,46,52,64,64,61,68,56,68,56,44,54,62,63,48,44,65,65,63,66,58,40,67,51,55,51,52,61,63,52,60,54,59,45,65,57,62,53,54,54,56,63,49,62,44,51,49,44,56,63,51,69,60,58,54,57,56,60,60,64,58,55,47,51,45,63,61,54,67,44,66,61,54,54,45,65,53,49,65,57,64,41,47,61,65,58,66,45,62,54,55,54,49,64,55,56,56,40,55,40,48,56,63,39,54,45,47,52,50,49,60,42,59,49,57,44,41,51,55,41,63,34,46,54,58,55,53,42,39,63,56,42,59,53,52,54,51,64,55,46,52,53,51,48,58,46,48,35,64,55,47,50,39,57,49,35,43,39,45,65,46,43,64,51,54,60,47,35,55,49,53,44,36,45,58,53,44,55,58,50,55,44,57,39,39,59,40,64,46,42,49,41,47,36,56,50,42,55,49,47,47,50,60,49,61,37,47,35,59,42,49,52,35,48,41,43,39,48,48,43,33,48,43,58,45,36,49,57,32,50,48,39,36,56,50,50,48,55,38,45,48,39,39,51,50,41,35,30,45,38,47,43,45,51,41,37,53,43,60,47,45,57,51,42,40,40,42,40,44,39,37,52,45,49,42,42,47,50,47,39,41,48,51,37,38,47,41,48,35,40,44,46,43,51,44,47,32,45,47,40,63,47,44,47,45,35,31,50,35,36,40,43,37,41,37,40,42,26,34,49,34,49,39,35,41,28,47,35,36,38,41,33,46,46,41,25,39,32,42,28,35,38,31,30,46,37,42,35,38,38,43,46,36,37,25,40,43,45,29,26,43,40,44,36,39,37,36,45,34,38,37,24,38,39,42,38,31,40,37,33,39,34,29,41,46,34,42,35,33,32,28,41,41,42,49,34,40,37,38,40,34,31,30,28,27,38,41,43,24,40,29,38,41,28,39,32,38,32,38,32,36,42,32,47,37,35,35,38,45,38,36,33,33,41,30,41,34,39,29,32,28,38,28,44,33,44,31,26,34,35,36,33,34,36,27,36,37,37,24,29,33,32,32,29,36,31,36,25,33,36,34,34,35,24,39,35,35,35,31,34,26,37,29,29,39,23,27,29,30,30,37,25,34,34,31,49,35,36,19,23,27,33,24,38,38,22,27,28,42,33,25,32,34,37,35,30,29,31,25,26,33,31,29,44,24,25,28,37,33,30,24,40,27,29,31,30,32,31,20,31,37,18,26,37,28,24,25,31,27,26,34,30,34,28,27,26,28,26,24,27,24,27,21,30,29,29,26,30,27,18,25,24,24,28,27,29,37,29,28,32,23,18,30,19,29,34,31,23,21,25,33,24,29,37,24,30,29,33,29,23,25,19,22,26,18,30,20,26,28,33,17,26,18,31,15,24,30,27,24,34,25,26,13,26,27,23,23,18,25,21,27,23,18,32,26,18,26,22,20,28,32,22,19,23,33,35,29,19,11,20,26,23,28,24,21,29,17,22,29,23,17,23,11,20,26,20,23,18,24,25,19,30,32,25,31,19,29,15,19,22,21,15,23,22,25,22,26,21,20,20,24,29,19,24,36,21,26,31,16,26,24,19,14,18,19,32,24,24,16,20,24,19,32,21,20,24,26,21,25,27,29,19,30,21,21,20,20,25,24,27,36,19,20,22,27,19,27,21,22,24,19,21,20,12,30,16,12,27,23,18,21,19,16,24,21,18,22,13,14,15,20,19,23,22,16,15,22,23,23,17,17,16,25,16,25,21,22,15,22,19,21,21,20,23,14,17,22,23,19,17,22,16,18,18,29,22,22,27,22,15,19,17,19,22,22,14,19,22,19,22,16,13,19,18,16,21,18,17,9,22,23,16,14,16,23,17,24,18,13,23,22,15,17,21,23,19,13,12,26,19,20,11,22,12,8,18,22,23,18,18,17,15,13,21,12,24,16,20,21,11,18,15,15,12,19,14,20,17,20,22,18,12,15,18,13,13,16,14,17,12,24,15,16,22,21,17,18,14,15,8,15,17,9,13,13,12,16,8,17,9,19,12,18,15,12,17,20,14,13,18,19,12,12,9,16,13,23,13,11,17,14,15,13,16,19,17,11,20,14,17,18,22,13,11,17,14,13,11,11,9,13,17,17,14,19,14,13,17,9,19,15,22,12,12,14,16,16,15,21,13,20,17,13,14,14,23,16,27,12,21,13,15,16,16,10,11,11,15,16,9,13,11,4,8,18,19,11,19,18,7,10,21,15,21,7,11,14,15,10,21,16,14,9,14,12,13,13,17,10,14,20,12,8,9,12,15,16,17,10,13,9,15,12,13,19,20,10,14,6,10,7,14,14,11,15,11,14,11,13,8,15,16,17,8,6,11,16,13,11,18,12,12,16,8,17,9,8,13,8,10,15,15,16,11,16,15,17,14,6,9,11,12,17,11,17,8,9,5,9,17,10,7,8,13,14,7,15,13,12,10,14,9,11,12,12,9,12,9,13,7,5,8,6,8,13,15,8,11,9,13,12,11,9,8,11,9,8,11,11,11,9,6,14,14,3,13,15,9,11,17,8,15,9,13,11,11,15,7,9,4,14,12,9,7,14,9,8,12,8,5,2,6,11,11,7,15,9,11,10,9,5,11,10,14,6,10,6,14,8,7,8,7,8,7,4,9,12,12,10,11,7,9,11,7,11,9,6,7,10,11,9,10,2,9,8,8,10,12,8,14,8,11,11,8,9,8,8,8,8,7,5,4,4,6,9,9,4,6,9,5,11,6,6,9,4,5,10,8,11,7,12,6,2,8,11,8,11,8,6,5,9,7,9,4,4,9,5,8,7,9,3,8,5,6,4,6,7,10,4,13,8,6,6,3,10,9,0,4,7,6,7,9,3,8,7,7,5,6,7,9,5,8,3,8,7,7,6,5,9,5,4,6,10,4,8,7,8,5,6,4,7,7,5,6,6,8,8,8,3,6,12,6,9,5,4,3,5,6,6,3,7,2,10,6,6,6,15,5,9,10,6,5,6,3,7,4,9,9,4,4,3,8,5,4,6,3,6,10,5,6,8,2,8,4,7,5,3,6,5,3,4,3,6,4,4,10,5,3,7,5,7,2,5,10,2,0,5,3,9,3,10,7,4,3,1,6,5,5,1,4,4,5,7,3,1,2,3,6,4,2,5,4,0,2,5,2,9,4,8,6,1,6,4,5,4,9,4,2,4,2,4,3,4,5,4,4,4,4,3,6,3,5,3,5,3,4,3,4,3,2,1,8,7,4,1,4,5,4,4,4,4,2,2,3,3,3,3,2,5,2,3,3,5,3,5,5,5,2,5,3,2,3,1,4,1,1,4,5,6,4,1,3,2,3,1,8,2,1,1,4,1,0,3,5,1,1,1,1,2,3,2,1,2,2,1,3,2,0,1,2,3,4,3,2,1,4,0,0,3,3,2,3,0,3,1,2,4,5,1,3,5,3,0,3,1,1,5,2,3,3,2,3,3,0,0,1,2,1,5,4,2,2,2,2,1,1,0,1,5,2,2,5,1,2,3,4,1,1,0,0,1,3,2,4,2,1,2,1,1,1,2,2,0,1,2,1,1,0,0,0,0,0,1,0,0,2,1,1,0,0,0,1,1,0,1,0,0,1,3,3,1,0,1,0,0,0,0,0,1,1,0,0,3,2,0,2,1,1,0,1,0,2,1,0,0,0,0,0,0,1,2,0,1,0,1,1,0,0,0,2,0,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0 + + + 8 + K-Serie + 4.41288156E-1 + 9.702750774E-2 + 24858 + 881 + 1.279468088E-1 + 12 + + + 14 + K-Serie + 1.631660487E-1 + 6.297683998E-2 + 72808 + 1780 + 4.685651417E-2 + + + 20 + K-Serie + 2.040153455E-2 + 1.123667408E-2 + 11250 + 1365 + 5.346735623E-2 + + + 22 + K-Serie + 9.091308535E-2 + 5.982041852E-2 + 45898 + 1167 + 3.231770004E-2 + + + 24 + K-Serie + 2.12927132E-2 + 1.521492612E-2 + 9850 + 908 + 4.559701006E-2 + + + 26 + K-Serie + 1.164834562E-1 + 8.939907094E-2 + 42688 + 721 + 2.98090491E-2 + + + 55 + L-Serie + 1.099223812E-2 + 2.007694658E-2 + 12033 + 1218 + 4.198033121E-2 + + + 56 + L-Serie + 8.174891657E-2 + 1.542823816E-1 + 87635 + 1177 + 2.920990544E-2 + + + 57 + L-Serie + 5.371385132E-2 + 1.02535592E-1 + 56535 + 1138 + 2.997690873E-2 + + + 8 + O + O-K + K + 5.249E-1 + 6.875969713E-2 + 26960 + 25705 + + + 14 + Si + Si-KA + KA + 1.7396 + 1.067628997E-1 + 74883 + 69363 + + + 22 + Ti + Ti-KA + KA + 4.510059792 + 1.631320293E-1 + 84768 + 75175 + + + 26 + Fe + Fe-KA + KA + 6.400794531 + 1.923404964E-1 + 43018 + 35784 + + + 56 + Ba + Ba-LA + LA + 4.464977951 + 1.62371475E-1 + 71368 + 61723 + + + 20 + Ca + Ca-KA + KA + 3.691130226 + 1.487110538E-1 + 20326 + 10956 + + + 24 + Cr + Cr-KA + KA + 5.41164707 + 1.776599227E-1 + 23321 + 14358 + + + 55 + Cs + Cs-LA + LA + 4.284007623 + 1.592818661E-1 + 13124 + 3572 + + + 57 + La + La-LA + LA + 4.645297284 + 1.653925756E-1 + 46928 + 37503 + + + + + + 8 + 16711680 + 1 + 1 + + + 14 + 16711680 + 1 + + + 22 + 16776960 + 1 + + + 26 + 16711935 + 1 + + + 56 + 65535 + 1 + + + 20 + 65407 + 1 + + + 24 + 65280 + 1 + + + 55 + 8388352 + 1 + + + 57 + 255 + 1 + + + + + + + 0,08035 + 20,00035 + 0 + 14,7031033989744 + 0 + 0 + 0 + 0 + Lines + 223,223,223 + 5 + 5 + 0 + + Verdana + 11 + 8,0,0 +