Skip to content

Commit

Permalink
Fixed SG filter optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
theHenks committed Feb 15, 2024
1 parent 8bbded7 commit e64da51
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 61 deletions.
22 changes: 14 additions & 8 deletions ext/LegendSpecFitsRecipesBaseExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -74,26 +74,32 @@ end
end
end

@recipe function f(report:: NamedTuple{(:wl, :min_sf, :min_sf_err, :a_grid_wl_sg, :sfs, :sfs_err)})
xlabel := "Window Length (ns)"
ylabel := "SEP Surrival Fraction (%)"
@recipe function f(report:: NamedTuple{(:wl, :min_sf, :a_grid_wl_sg, :sfs)})
xlabel := "Window Length ($(unit(first(report.a_grid_wl_sg))))"
ylabel := "SEP Surrival Fraction ($(unit(first(report.sfs))))"
grid := :true
gridcolor := :black
gridalpha := 0.2
gridlinewidth := 0.5
ylims := (0, 30)
# ylims := (0, 30)
@series begin
seriestype := :scatter
label := "SF"
yerror --> report.sfs_err
ustrip.(report.a_grid_wl_sg), report.sfs
ustrip.(report.a_grid_wl_sg), ustrip.(report.sfs)
end
@series begin
seriestype := :hline
label := "Min. SF (WT: $(report.wl))"
label := "Min. SF $(report.min_sf) (WT: $(report.wl))"
color := :red
linewidth := 2.5
[report.min_sf]
[ustrip(Measurements.value(report.min_sf))]
end
@series begin
seriestype := :hspan
label := ""
color := :red
alpha := 0.1
ustrip.([Measurements.value(report.min_sf)-Measurements.uncertainty(report.min_sf), Measurements.value(report.min_sf)+Measurements.uncertainty(report.min_sf)])
end
end

Expand Down
37 changes: 16 additions & 21 deletions src/aoe_calibration.jl
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Prepare an array of uncalibrated DEP energies for parameter extraction and calib
- `result`: Result of the initial fit
- `report`: Report of the initial fit
"""
function prepare_dep_peakhist(e::Array{T}, dep::T,; relative_cut::T=0.5, n_bins_cut::Int=500) where T<:Real
function prepare_dep_peakhist(e::Array{T}, dep::Quantity{T},; relative_cut::T=0.5, n_bins_cut::Int=500, uncertainty::Bool=true) where T<:Real
# get cut window around peak
cuts = cut_single_peak(e, minimum(e), maximum(e); n_bins=n_bins_cut, relative_cut=relative_cut)
# estimate bin width
Expand All @@ -84,7 +84,7 @@ function prepare_dep_peakhist(e::Array{T}, dep::T,; relative_cut::T=0.5, n_bins_
# get peakstats
depstats = estimate_single_peak_stats(dephist)
# initial fit for calibration and parameter extraction
result, report = fit_single_peak_th228(dephist, depstats,; uncertainty=true, low_e_tail=false)
result, report = fit_single_peak_th228(dephist, depstats,; uncertainty=uncertainty, low_e_tail=false)
# get calibration estimate from peak postion
result = merge(result, (m_calib = dep / result.μ, ))
return result, report
Expand All @@ -100,19 +100,14 @@ Get the number of counts after a PSD cut value `psd_cut` for a given `peak` and
- `n`: Number of counts after the cut
- `n_err`: Uncertainty of the number of counts after the cut
"""
function get_n_after_psd_cut(psd_cut::T, aoe::Array{T}, e::Array{T}, peak::T, window::Array{T}, bin_width::T, result_before::NamedTuple, peakstats::NamedTuple; uncertainty::Bool=true, fixed_position::Bool=true) where T<:Real
function get_n_after_psd_cut(psd_cut::Unitful.RealOrRealQuantity, aoe::Vector{<:Unitful.RealOrRealQuantity}, e::Vector{<:T}, peak::T, window::Vector{T}, bin_width::T, result_before::NamedTuple, peakstats::NamedTuple; uncertainty::Bool=true, fixed_position::Bool=true) where T<:Unitful.Energy{<:Real}
# get energy after cut and create histogram
peakhist = fit(Histogram, e[aoe .> psd_cut], peak-first(window):bin_width:peak+last(window))
peakhist = fit(Histogram, ustrip.(e[aoe .> psd_cut]), ustrip(peak-first(window):bin_width:peak+last(window)))
# create pseudo_prior with known peak sigma in signal for more stable fit
pseudo_prior = NamedTupleDist= Normal(result_before.σ, 0.3), )
# fit peak and return number of signal counts
result, _ = fit_single_peak_th228(peakhist, peakstats,; uncertainty=uncertainty, fixed_position=fixed_position, low_e_tail=false, pseudo_prior=pseudo_prior)
if uncertainty
n, n_err = result.n, result.err.n
else
n, n_err = result.n, 0.0
end
return (n = result.n, n_err = n_err)
return result.n
end
export get_n_after_psd_cut

Expand All @@ -128,11 +123,11 @@ The algorhithm utilizes a root search algorithm to find the cut value with a rel
- `nsf`: Number of counts after the cut
- `err`: Uncertainties
"""
function get_psd_cut(aoe::Array{T}, e::Array{T},; dep::T=1592.53, window::Array{T}=[12.0, 10.0], dep_sf::Float64=0.9, cut_search_interval::Tuple{T,T}=(-25.0, 0.0), rtol::T=0.001, bin_width_window::T=3.0, fixed_position::Bool=true, sigma_high_sided::T=NaN) where T<:Real
function get_psd_cut(aoe::Vector{<:Unitful.RealOrRealQuantity}, e::Vector{<:T},; dep::T=1592.53u"keV", window::Vector{T}=[12.0, 10.0]u"keV", dep_sf::Float64=0.9, cut_search_interval::Tuple{Quantity{<:Real}, Quantity{<:Real}}=(-25.0u"keV^-1", 0.0u"keV^-1"), rtol::Float64=0.001, bin_width_window::T=3.0u"keV", fixed_position::Bool=true, sigma_high_sided::Float64=NaN, uncertainty::Bool=true) where T<:Unitful.Energy{<:Real}
# estimate bin width
bin_width = get_friedman_diaconis_bin_width(e[e .> dep - bin_width_window .&& e .< dep + bin_width_window])
# create histogram
dephist = fit(Histogram, e, dep-first(window):bin_width:dep+last(window))
dephist = fit(Histogram, ustrip.(e), ustrip(dep-first(window):bin_width:dep+last(window)))
# get peakstats
depstats = estimate_single_peak_stats(dephist)
# cut window around peak
Expand All @@ -144,16 +139,16 @@ function get_psd_cut(aoe::Array{T}, e::Array{T},; dep::T=1592.53, window::Array{
aoe = aoe[aoe .< sigma_high_sided]
end
# fit before cut
result_before, _ = fit_single_peak_th228(dephist, depstats,; uncertainty=true, fixed_position=fixed_position, low_e_tail=false)
result_before, _ = fit_single_peak_th228(dephist, depstats,; uncertainty=uncertainty, fixed_position=fixed_position, low_e_tail=false)
# get n0 before cut
nsf = result_before.n * dep_sf
# get psd cut
n_surrival_dep_f = cut -> get_n_after_psd_cut(cut, aoe, e, dep, window, bin_width, result_before, depstats; uncertainty=false, fixed_position=fixed_position).n - nsf
n_surrival_dep_f = cut -> get_n_after_psd_cut(cut, aoe, e, dep, window, bin_width, mvalue(result_before), depstats; uncertainty=false, fixed_position=fixed_position) - nsf
psd_cut = find_zero(n_surrival_dep_f, cut_search_interval, Bisection(), rtol=rtol, maxiters=100)
# return n_surrival_dep_f.(0.25:0.001:0.5)
# get nsf after cut
result_after = get_n_after_psd_cut(psd_cut, aoe, e, dep, window, bin_width, result_before, depstats; uncertainty=true, fixed_position=fixed_position)
return (cut = psd_cut, n0 = result_before.n, nsf = result_after.n, err = (cut = psd_cut * rtol, n0 = result_before.err.n, nsf = result_after.n_err))
nsf = get_n_after_psd_cut(psd_cut, aoe, e, dep, window, bin_width, mvalue(result_before), depstats; uncertainty=uncertainty, fixed_position=fixed_position)
return (cut = measurement(psd_cut, psd_cut * rtol), n0 = result_before.n, nsf = nsf, sf = nsf / result_before.n * 100*u"percent")
end
export get_psd_cut

Expand All @@ -170,11 +165,11 @@ Get the surrival fraction of a peak after a PSD cut value `psd_cut` for a given
- `sf`: Surrival fraction
- `err`: Uncertainties
"""
function get_peak_surrival_fraction(aoe::Array{T}, e::Array{T}, peak::T, window::Array{T}, psd_cut::T,; uncertainty::Bool=true, low_e_tail::Bool=true, bin_width_window::T=2.0, sigma_high_sided::T=NaN) where T<:Real
function get_peak_surrival_fraction(aoe::Vector{<:Unitful.RealOrRealQuantity}, e::Vector{<:T}, peak::T, window::Vector{T}, psd_cut::Unitful.RealOrRealQuantity,; uncertainty::Bool=true, low_e_tail::Bool=true, bin_width_window::T=2.0u"keV", sigma_high_sided::Float64=NaN) where T<:Unitful.Energy{<:Real}
# estimate bin width
bin_width = get_friedman_diaconis_bin_width(e[e .> peak - bin_width_window .&& e .< peak + bin_width_window])
# get energy before cut and create histogram
peakhist = fit(Histogram, e, peak-first(window):bin_width:peak+last(window))
peakhist = fit(Histogram, ustrip.(e), ustrip(peak-first(window):bin_width:peak+last(window)))
# estimate peak stats
peakstats = estimate_single_peak_stats(peakhist)
# fit peak and return number of signal counts
Expand All @@ -189,7 +184,7 @@ function get_peak_surrival_fraction(aoe::Array{T}, e::Array{T}, peak::T, window:
# estimate bin width
bin_width = get_friedman_diaconis_bin_width(e[e .> peak - bin_width_window .&& e .< peak + bin_width_window])
# get energy after cut and create histogram
peakhist = fit(Histogram, e, peak-first(window):bin_width:peak+last(window))
peakhist = fit(Histogram, ustrip(e), ustrip(peak-first(window):bin_width:peak+last(window)))
# create pseudo_prior with known peak sigma in signal for more stable fit
pseudo_prior = NamedTupleDist= ConstValueDist(result_before.μ), σ = Normal(result_before.σ, 0.1))
pseudo_prior = NamedTupleDist= Normal(result_before.σ, 0.1), )
Expand All @@ -199,12 +194,12 @@ function get_peak_surrival_fraction(aoe::Array{T}, e::Array{T}, peak::T, window:
result_after, report_after = fit_single_peak_th228(peakhist, peakstats,; uncertainty=uncertainty, low_e_tail=low_e_tail)
# result_after, report_after = fit_single_peak_th228(peakhist, peakstats,; uncertainty=uncertainty, low_e_tail=low_e_tail, pseudo_prior=pseudo_prior)
# calculate surrival fraction
sf = result_after.n / result_before.n
sf = result_after.n / result_before.n * 100.0 * u"percent"
result = (
peak = peak,
n_before = result_before.n,
n_after = result_after.n,
sf = sf * 100*u"percent"
sf = sf
)
report = (
peak = result.peak,
Expand Down
12 changes: 3 additions & 9 deletions src/cut.jl
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ function get_centered_gaussian_window_cut(x::Vector{T}, min_x::T, max_x::T, n_σ
# get bin width
bin_width = get_friedman_diaconis_bin_width(x[x .> result_fit.μ - 0.5*result_fit.σ .&& x .< result_fit.μ + 0.5*result_fit.σ])
# prepare histogram
h = fit(Histogram, x, result_fit.μ-5*result_fit.σ:bin_width:result_fit.μ+5*result_fit.σ)
h = fit(Histogram, x, mvalue(result_fit.μ-5*result_fit.σ):mvalue(bin_width):mvalue(result_fit.μ+5*result_fit.σ))
# norm fitted distribution for better plotting
# n_fit = length(x[ifelse(left, cuts.low, result_fit.μ) .< x .< ifelse(left, result_fit.μ, cuts.high)])
# n_fit = length(x)
Expand All @@ -85,18 +85,12 @@ function get_centered_gaussian_window_cut(x::Vector{T}, min_x::T, max_x::T, n_σ
σ = result_fit.σ*x_unit,
low_cut_fit = ifelse(left, cuts.low, result_fit.μ),
high_cut_fit = ifelse(left, result_fit.μ, cuts.high),
max_cut_fit = cuts.max,
err = (
low_cut = n_σ*result_fit.σ_err*x_unit,
high_cut = n_σ*result_fit.σ_err*x_unit,
center = result_fit.μ_err*x_unit,
σ = result_fit.σ_err*x_unit
)
max_cut_fit = cuts.max
)
report = (
h = LinearAlgebra.normalize(h, mode=:pdf),
f_fit = t -> report_fit.f_fit(t),
x_fit = ifelse(left, cuts.low:(result_fit.μ-cuts.low)/1000:result_fit.μ, result_fit.μ:(cuts.high-result_fit.μ)/1000:cuts.high),
x_fit = ifelse(left, cuts.low:mvalue(result_fit.μ-cuts.low)/1000:mvalue(result_fit.μ), mvalue(result_fit.μ):mvalue(cuts.high-result_fit.μ)/1000:cuts.high),
low_cut = result.low_cut,
high_cut = result.high_cut,
low_cut_fit = result.low_cut_fit,
Expand Down
44 changes: 31 additions & 13 deletions src/filter_optimization.jl
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,20 @@ function fit_fwhm_ft_fep(e_grid::Matrix, e_grid_ft::StepRangeLen{Quantity{<:T},
end
export fit_fwhm_ft_fep

"""
fit_sg_wl(dep_sep_data, a_grid_wl_sg, optimization_config)
Fit the SG window length for the SEP data and return the optimal window length and the corresponding survival fraction.
# Arguments
- `dep_sep_data`: NamedTuple with the DEP and SEP data
- `a_grid_wl_sg`: range of window lengths to sweep through
- `optimization_config`: configuration dictionary
# Returns
- `result`: optimal window length and corresponding survival fraction
- `report`: report with all window lengths and survival fractions
"""
function fit_sg_wl(dep_sep_data::NamedTuple{(:dep, :sep)}, a_grid_wl_sg::StepRangeLen, optimization_config::PropDict)
# unpack config
dep, dep_window = optimization_config.dep, Float64.(optimization_config.dep_window)
Expand All @@ -163,11 +176,13 @@ function fit_sg_wl(dep_sep_data::NamedTuple{(:dep, :sep)}, a_grid_wl_sg::StepRan
aoe_dep, aoe_sep = dep_sep_data.dep.aoe, dep_sep_data.sep.aoe

# prepare peakhist
result_dep, _ = prepare_dep_peakhist(e_dep, dep; n_bins_cut=optimization_config.nbins_dep_cut, relative_cut=optimization_config.dep_rel_cut)

result_dep, _ = prepare_dep_peakhist(e_dep, dep; n_bins_cut=optimization_config.nbins_dep_cut, relative_cut=optimization_config.dep_rel_cut, uncertainty=false)

yield()

# get calib constant from fit on DEP peak
e_dep_calib = e_dep .* result_dep.m_calib
e_sep_calib = e_sep .* result_dep.m_calib
e_dep_calib = e_dep .* mvalue(result_dep.m_calib)
e_sep_calib = e_sep .* mvalue(result_dep.m_calib)

# create empty arrays for sf and sf_err
sep_sfs = Quantity{Measurement}[]
Expand All @@ -176,43 +191,46 @@ function fit_sg_wl(dep_sep_data::NamedTuple{(:dep, :sep)}, a_grid_wl_sg::StepRan
# for each window lenght, calculate the survival fraction in the SEP
for (i_aoe, wl) in enumerate(a_grid_wl_sg)

aoe_dep_i = aoe_dep[i_aoe, :][isfinite.(aoe_dep[i_aoe, :])] ./ result_dep.m_calib
aoe_dep_i = aoe_dep[i_aoe, :][isfinite.(aoe_dep[i_aoe, :])] ./ mvalue(result_dep.m_calib)
e_dep_i = e_dep_calib[isfinite.(aoe_dep[i_aoe, :])]

# prepare AoE
max_aoe_dep_i = quantile(aoe_dep_i, optimization_config.max_aoe_quantile) + optimization_config.max_aoe_offset
min_aoe_dep_i = quantile(aoe_dep_i, optimization_config.min_aoe_quantile) + optimization_config.min_aoe_offset

try
psd_cut = get_psd_cut(aoe_dep_i, e_dep_i; window=dep_window, cut_search_interval=(min_aoe_dep_i, max_aoe_dep_i))
psd_cut = get_psd_cut(aoe_dep_i, e_dep_i; window=dep_window, cut_search_interval=(min_aoe_dep_i, max_aoe_dep_i), uncertainty=false)

aoe_sep_i = aoe_sep[i_aoe, :][isfinite.(aoe_sep[i_aoe, :])] ./ result_dep.m_calib
e_sep_i = e_sep_calib[isfinite.(aoe_sep[i_aoe, :])]

result_sep, _ = get_peak_surrival_fraction(aoe_sep_i, e_sep_i, sep, sep_window, psd_cut.cut; uncertainty=true, low_e_tail=false)
result_sep, _ = get_peak_surrival_fraction(aoe_sep_i, e_sep_i, sep, sep_window, psd_cut.cut; uncertainty=false, low_e_tail=false)

push!(sep_sfs, result_sep.sf)
push!(wls, wl)
catch e
@warn "Couldn't process window length $wl"
end
yield()
end
# get minimal surrival fraction and window length
sep_sfs_cut = [1.0u"percent" .< sep_sfs .< 100u"percent"]
sep_sfs_cut = 1.0u"percent" .< sep_sfs .< 100u"percent"
if isempty(sep_sfs[sep_sfs_cut])
@warn "No valid SEP SF found, setting to NaN"
min_sf = NaN*u"percent"
min_sf = measurement(NaN, NaN)*u"percent"
@warn "No valid window length found, setting to default"
wl_sg_min_sf = 100u"ns"
wl_sg_min_sf = last(a_grid_wl_sg[a_grid_wl_sg .< 110u"ns"])
else
min_sf = minimum(sep_sfs[sep_sfs_cut])
wl_sg_min_sf = a_grid_wl_sg[sep_sfs_cut][findmin(sep_sfs[sep_sfs_cut])[2]]
min_sf = minimum(sep_sfs[sep_sfs_cut])
wl_sg_min_sf = wls[sep_sfs_cut][findmin(sep_sfs[sep_sfs_cut])[2]]
end

# generate result and report
result = (
wl = measurement(wl_sg_min_sf, step(a_grid_wl_sg)),
sf = min_sf
sf = min_sf,
n_dep = length(e_dep),
n_sep = length(e_sep)
)
report = (
wl = result.wl,
Expand Down
4 changes: 2 additions & 2 deletions src/qc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function qc_sg_optimization(dsp_dep::NamedTuple{(:aoe, :e, :blmean, :blslope, :t
# get half truncated centered cut on blslope for pile-up rejection
result_dep_slope_cut, report_dep_slope_cut = get_centered_gaussian_window_cut(blslope_dep, -0.1u"ns^-1", 0.1u"ns^-1", optimization_config.cuts.dep.blslope_sigma, ; n_bins_cut=optimization_config.cuts.dep.nbins_blslope_cut, relative_cut=optimization_config.cuts.dep.rel_cut_blslope_cut)
# Cut on blslope, energy and t0 for simple QC
qc_cut_dep = blslope_dep .> result_dep_slope_cut.low_cut .&& blslope_dep .< result_dep_slope_cut.high_cut .&& e_dep .> optimization_config.cuts.dep.min_e .&& quantile(e_dep, first(optimization_config.cuts.dep.e_quantile)) .< e_dep .< quantile(e_dep, last(optimization_config.cuts.dep.e_quantile)) .&& first(optimization_config.cuts.dep.t50)u"µs" .< t50_dep .< last(optimization_config.cuts.dep.t50)u"µs"
qc_cut_dep = blslope_dep .> result_dep_slope_cut.low_cut .&& blslope_dep .< result_dep_slope_cut.high_cut .&& e_dep .> optimization_config.cuts.dep.min_e .&& quantile(e_dep, first(optimization_config.cuts.dep.e_quantile)) .< e_dep .< quantile(e_dep, last(optimization_config.cuts.dep.e_quantile)) .&& first(optimization_config.cuts.dep.t50) .< t50_dep .< last(optimization_config.cuts.dep.t50)
aoe_dep, e_dep = aoe_dep[:, qc_cut_dep], e_dep[qc_cut_dep]

### SEP
Expand All @@ -25,7 +25,7 @@ function qc_sg_optimization(dsp_dep::NamedTuple{(:aoe, :e, :blmean, :blslope, :t
result_sep_slope_cut, report_sep_slope_cut = get_centered_gaussian_window_cut(blslope_sep, -0.1u"ns^-1", 0.1u"ns^-1", optimization_config.cuts.sep.blslope_sigma, ; n_bins_cut=optimization_config.cuts.sep.nbins_blslope_cut, relative_cut=optimization_config.cuts.sep.rel_cut_blslope_cut)

# Cut on blslope, energy and t0 for simple QC
qc_cut_sep = blslope_sep .> result_sep_slope_cut.low_cut .&& blslope_sep .< result_sep_slope_cut.high_cut .&& e_sep .> optimization_config.cuts.sep.min_e .&& quantile(e_sep, first(optimization_config.cuts.sep.e_quantile)) .< e_sep .< quantile(e_sep, last(optimization_config.cuts.sep.e_quantile)) .&& first(optimization_config.cuts.sep.t50)u"µs" .< t50_sep .< last(optimization_config.cuts.sep.t50)u"µs"
qc_cut_sep = blslope_sep .> result_sep_slope_cut.low_cut .&& blslope_sep .< result_sep_slope_cut.high_cut .&& e_sep .> optimization_config.cuts.sep.min_e .&& quantile(e_sep, first(optimization_config.cuts.sep.e_quantile)) .< e_sep .< quantile(e_sep, last(optimization_config.cuts.sep.e_quantile)) .&& first(optimization_config.cuts.sep.t50) .< t50_sep .< last(optimization_config.cuts.sep.t50)
aoe_sep, e_sep = aoe_sep[:, qc_cut_sep], e_sep[qc_cut_sep]

return (dep=(aoe=aoe_dep, e=e_dep), sep=(aoe=aoe_sep, e=e_sep))
Expand Down
Loading

0 comments on commit e64da51

Please sign in to comment.