1623 lines
52 KiB
Igor
1623 lines
52 KiB
Igor
#pragma TextEncoding = "UTF-8"
|
|
#pragma rtGlobals=3 // Use modern global access method and strict wave access.
|
|
#pragma IgorVersion = 6.1
|
|
#pragma ModuleName = PearlScientaPreprocess
|
|
#include "pearl-fitfuncs"
|
|
#include "pearl-area-import"
|
|
|
|
// Copyright (c) 2013-18 Paul Scherrer Institut
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
/// @file
|
|
/// @brief preprocessing functions for Scienta detector images.
|
|
/// @ingroup ArpesPackage
|
|
///
|
|
/// this procedure contains functions for data reduction
|
|
/// and instrument-specific normalization.
|
|
///
|
|
/// @author matthias muntwiler, matthias.muntwiler@psi.ch
|
|
///
|
|
/// @copyright 2013-18 Paul Scherrer Institut @n
|
|
/// Licensed under the Apache License, Version 2.0 (the "License"); @n
|
|
/// you may not use this file except in compliance with the License. @n
|
|
/// You may obtain a copy of the License at
|
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
/// @namespace PearlScientaPreprocess
|
|
/// @brief preprocessing functions for Scienta detector images.
|
|
///
|
|
/// PearlScientaPreprocess is declared in @ref pearl-scienta-preprocess.ipf.
|
|
|
|
/// prompt the user for integrate on linear background reduction parameters.
|
|
///
|
|
function prompt_int_linbg_reduction(param)
|
|
string ¶m
|
|
|
|
variable Lcrop = NumberByKey("Lcrop", param, "=", ";")
|
|
variable Lsize = NumberByKey("Lsize", param, "=", ";")
|
|
variable Hcrop = NumberByKey("Hcrop", param, "=", ";")
|
|
variable Hsize = NumberByKey("Hsize", param, "=", ";")
|
|
variable Cpos = NumberByKey("Cpos", param, "=", ";")
|
|
variable Csize = NumberByKey("Csize", param, "=", ";")
|
|
|
|
prompt Lcrop, "Lower cropping region"
|
|
prompt Hcrop, "Upper cropping region"
|
|
prompt Lsize, "Lower background region"
|
|
prompt Hsize, "Upper background region"
|
|
prompt Cpos, "Center position"
|
|
prompt Csize, "Center integration region"
|
|
|
|
doprompt "int_linbg_reduction Parameters", lcrop, hcrop, lsize, hsize, cpos, csize
|
|
if (v_flag == 0)
|
|
param = ReplaceNumberByKey("Lcrop", param, Lcrop, "=", ";")
|
|
param = ReplaceNumberByKey("Lsize", param, Lsize, "=", ";")
|
|
param = ReplaceNumberByKey("Hcrop", param, Hcrop, "=", ";")
|
|
param = ReplaceNumberByKey("Hsize", param, Hsize, "=", ";")
|
|
param = ReplaceNumberByKey("Cpos", param, Cpos, "=", ";")
|
|
param = ReplaceNumberByKey("Csize", param, Csize, "=", ";")
|
|
endif
|
|
|
|
return v_flag
|
|
end
|
|
|
|
/// capture linear background reduction parameters from cursors in a graph.
|
|
///
|
|
/// PRELIMINARY - function arguments may change
|
|
///
|
|
/// sets reduction parameters from cursors in a graph.
|
|
/// the resulting parameters are copied to the global `s_reduction_params` string
|
|
/// used by the data explorer.
|
|
///
|
|
/// an even number of cursors (2 or more) must be set on the image.
|
|
/// cursor names and order do not matter,
|
|
/// except that the alphabetically first cursor which is attached to an image selects the image.
|
|
/// the cursors mark the following positions, from innermost to outermost pair:
|
|
///
|
|
/// 1. low and high limits of peak region.
|
|
/// if no cursors are present, the limits are set at 40% and 60% of the x-scale.
|
|
/// 2. peak-side boundary of lower and upper background region.
|
|
/// if two or less cursors are present, the default background region applies,
|
|
/// which extends from the peak limits up to the default cropping region.
|
|
/// the background region extends up to the cropping region defined by the third pair.
|
|
/// 3. lower and upper cropping region.
|
|
/// if four or less cursors are present, the default cropping region applies,
|
|
/// which is 11% on either side of the image in fixed mode, and 0% otherwise.
|
|
/// fixed mode is detected by the number of pixels (>= 992).
|
|
///
|
|
/// @note on profile graphs, the necessary cursors can be configured easily
|
|
/// by calling the ad_profile_cursor_mode() function, e.g.
|
|
/// `ad_profiles_cursor_mode(root:packages:pearl_explorer:preview_image, 1)`.
|
|
///
|
|
function /s capture_int_linbg_cursors()
|
|
string param = csr_int_linbg_reduction("")
|
|
svar /z global_params = root:packages:pearl_explorer:s_reduction_params
|
|
if (svar_exists(global_params))
|
|
global_params = param
|
|
endif
|
|
return param
|
|
end
|
|
|
|
/// calculate linear background reduction parameters from cursors in a graph.
|
|
///
|
|
/// PRELIMINARY - function arguments may change
|
|
///
|
|
/// calculates reduction parameters from cursors in a graph.
|
|
/// the resulting parameters are returned in a string.
|
|
///
|
|
/// an even number of cursors (2 or more) must be set on the image.
|
|
/// cursor names and order do not matter,
|
|
/// except that the alphabetically first cursor which is attached to an image selects the image.
|
|
/// the cursors mark the following positions, from innermost to outermost pair:
|
|
///
|
|
/// 1. low and high limits of peak region.
|
|
/// if no cursors are present, the limits are set at 40% and 60% of the x-scale.
|
|
/// 2. peak-side boundary of lower and upper background region.
|
|
/// if two or less cursors are present, the default background region applies,
|
|
/// which extends from the peak limits up to the default cropping region.
|
|
/// the background region extends up to the cropping region defined by the third pair.
|
|
/// 3. lower and upper cropping region.
|
|
/// if four or less cursors are present, the default cropping region applies,
|
|
/// which is 11% on either side of the image in fixed mode, and 0% otherwise.
|
|
/// fixed mode is detected by the number of pixels (>= 992).
|
|
///
|
|
/// @note on profile graphs, the necessary cursors can be configured easily
|
|
/// by calling the ad_profile_cursor_mode() function, e.g.
|
|
/// `ad_profiles_cursor_mode(root:packages:pearl_explorer:preview_image, 1)`.
|
|
///
|
|
/// @param win graph window name or empty string for top window.
|
|
///
|
|
/// @return parameter string for linear background subtraction
|
|
///
|
|
function /s csr_int_linbg_reduction(win)
|
|
string win
|
|
|
|
// read all cursor positions
|
|
variable ic
|
|
string sc
|
|
variable nc = 10
|
|
make /n=(nc) /free positions
|
|
variable np = 0
|
|
wave /z image = $""
|
|
string imagename = ""
|
|
string tracename = ""
|
|
string info
|
|
|
|
for (ic = 0; ic < nc; ic += 1)
|
|
sc = num2char(char2num("A") + ic)
|
|
wave /z wc = CsrWaveRef($sc, win)
|
|
info = CsrInfo($sc, win)
|
|
tracename = StringByKey("TNAME", info, ":", ";")
|
|
if (waveexists(wc) && (wavedims(wc) == 2))
|
|
if (!waveexists(image))
|
|
wave image = wc
|
|
imagename = tracename
|
|
endif
|
|
if (cmpstr(tracename, imagename) == 0)
|
|
positions[np] = pcsr($sc, win)
|
|
np += 1
|
|
endif
|
|
endif
|
|
endfor
|
|
|
|
np = floor(np / 2) * 2 // ignore odd cursor
|
|
redimension /n=(np) positions
|
|
sort positions, positions
|
|
// shift upper positions by one so that the rightmost pixel becomes 1.0
|
|
positions = p >= np / 2 ? positions + 1 : positions
|
|
positions = positions / dimsize(image, 0)
|
|
|
|
// map innermost cursor pair to peak center and size
|
|
variable ip2 = np / 2
|
|
variable ip1 = ip2 - 1
|
|
variable Cpos
|
|
variable Csize
|
|
if (ip1 >= 0)
|
|
Cpos = (positions[ip1] + positions[ip2]) / 2
|
|
Csize = positions[ip2] - positions[ip1]
|
|
else
|
|
// default: a small region in the center
|
|
Cpos = 0.5
|
|
Csize = 0.2
|
|
endif
|
|
|
|
// background region
|
|
ip1 -= 1
|
|
ip2 += 1
|
|
variable Lsize
|
|
variable Hsize
|
|
if (ip1 >= 0)
|
|
Lsize = positions[ip1]
|
|
Hsize = 1 - positions[ip2]
|
|
else
|
|
// default: everything outside the peak region
|
|
Lsize = Cpos - Csize / 2
|
|
Hsize = 1 - (Cpos + Csize / 2)
|
|
endif
|
|
|
|
// crop region
|
|
ip1 -= 1
|
|
ip2 += 1
|
|
variable Lcrop = 0
|
|
variable Hcrop = 0
|
|
if (ip1 >= 0)
|
|
Lcrop = positions[ip1]
|
|
Hcrop = 1 - positions[ip2]
|
|
else
|
|
// default: in fixed mode: dark corners of the EW4000 at PEARL, 0 otherwise
|
|
if (dimsize(image, 0) >= 992)
|
|
Lcrop = 0.11
|
|
Hcrop = 0.11
|
|
endif
|
|
endif
|
|
Lsize = max(Lsize - Lcrop, 0)
|
|
Hsize = max(Hsize - Hcrop, 0)
|
|
|
|
string param = ""
|
|
param = ReplaceNumberByKey("Lcrop", param, Lcrop, "=", ";")
|
|
param = ReplaceNumberByKey("Lsize", param, Lsize, "=", ";")
|
|
param = ReplaceNumberByKey("Hcrop", param, Hcrop, "=", ";")
|
|
param = ReplaceNumberByKey("Hsize", param, Hsize, "=", ";")
|
|
param = ReplaceNumberByKey("Cpos", param, Cpos, "=", ";")
|
|
param = ReplaceNumberByKey("Csize", param, Csize, "=", ";")
|
|
|
|
return param
|
|
end
|
|
|
|
/// linear-background subtracted integration reduction function.
|
|
///
|
|
/// data reduction function for adh5_load_reduced_detector.
|
|
/// cf. @ref adh5_default_reduction for an explanation of reduction functions.
|
|
///
|
|
/// this function calculates the average pixel value of each angular slice
|
|
/// in one center and two background intervals.
|
|
/// a background value is calculated at the center position
|
|
/// by linear interpolation from the two background values.
|
|
/// returns the center minus linear background in dest1.
|
|
/// returns the Poisson one-sigma error in dest2.
|
|
///
|
|
/// typical values (peak centered on detector, FWHM ~ 20 % of image)
|
|
/// Lcrop=0.11;Hcrop=0.11;Lsize=0.2;Hsize=0.2;Cpos=0.5;Csize=0.2
|
|
///
|
|
/// @param source scienta detector image, energy axis along X, angle axis along Y.
|
|
/// two-dimensional intensity distribution (image).
|
|
/// the scales are carried over to the result waves.
|
|
///
|
|
/// @param param parameters in a key1=value1;key2=value2;... list.
|
|
/// all region parameters are relative to the image size (0...1).
|
|
/// @arg Lcrop = size of the lower cropping region
|
|
/// @arg Hcrop = size of the upper cropping region
|
|
/// @arg Lsize = size of the lower background integration region
|
|
/// @arg Hsize = size of the upper background integration region
|
|
/// @arg Cpos = center position of the of the peak integration region
|
|
/// @arg Csize = size of the peak integration region
|
|
///
|
|
/// @return free wave containing references of the two result waves.
|
|
/// the first wave is the integral minus linear background.
|
|
/// the second wave is the Poisson one-sigma error.
|
|
///
|
|
threadsafe function /wave int_linbg_reduction(source, param)
|
|
wave source
|
|
string ¶m
|
|
|
|
variable nx = dimsize(source, 0)
|
|
variable ny = dimsize(source, 1)
|
|
|
|
// read parameters
|
|
variable lcrop = NumberByKey("Lcrop", param, "=", ";")
|
|
variable lsize = NumberByKey("Lsize", param, "=", ";")
|
|
variable hcrop = NumberByKey("Hcrop", param, "=", ";")
|
|
variable hsize = NumberByKey("Hsize", param, "=", ";")
|
|
variable cpos = NumberByKey("Cpos", param, "=", ";")
|
|
variable csize = NumberByKey("Csize", param, "=", ";")
|
|
|
|
make /wave /free /n=2 result_waves
|
|
make /free /n=0 dest1, dest2
|
|
result_waves[0] = dest1
|
|
result_waves[1] = dest2
|
|
adh5_setup_profile(source, dest1, 1)
|
|
adh5_setup_profile(source, dest2, 1)
|
|
|
|
// validate parameters
|
|
// background parameters are optional, center parameter is required.
|
|
if (numtype(lcrop) != 0)
|
|
lcrop = 0
|
|
endif
|
|
if (numtype(lsize) != 0)
|
|
lsize = 0
|
|
endif
|
|
if (numtype(hcrop) != 0)
|
|
hcrop = 0
|
|
endif
|
|
if (numtype(hsize) != 0)
|
|
hsize = 0
|
|
endif
|
|
if (numtype(Cpos) != 0)
|
|
redimension /n=0 result_waves
|
|
return result_waves // Cpos parameter missing
|
|
endif
|
|
if (numtype(Csize) != 0)
|
|
redimension /n=0 result_waves
|
|
return result_waves // Csize parameter missing
|
|
endif
|
|
|
|
variable lpos = lcrop + lsize / 2
|
|
variable hpos = 1 - (hcrop + hsize / 2)
|
|
|
|
variable p0
|
|
variable p1
|
|
|
|
duplicate /free dest1, lbg, hbg
|
|
if (lsize > 0)
|
|
p0 = round(lcrop * nx)
|
|
p1 = round((lcrop + lsize) * nx)
|
|
ad_profile_y_w(source, p0, p1, lbg)
|
|
else
|
|
lbg = 0
|
|
endif
|
|
if (hsize > 0)
|
|
p0 = round((1 - hcrop - hsize) * nx)
|
|
p1 = round((1 - hcrop) * nx)
|
|
ad_profile_y_w(source, p0, p1, hbg)
|
|
else
|
|
hbg = 0
|
|
endif
|
|
if (csize > 0)
|
|
p0 = round((cpos - csize/2) * nx)
|
|
p1 = round((cpos + csize/2) * nx)
|
|
ad_profile_y_w(source, p0, p1, dest1)
|
|
else
|
|
dest1 = 0
|
|
endif
|
|
|
|
variable scale = (cpos - lpos) / (hpos - lpos)
|
|
dest2 = dest1
|
|
dest1 -= scale * (hbg - lbg) + lbg
|
|
dest2 = sqrt(dest2 + scale^2 * (hbg + lbg))
|
|
|
|
string s_note1
|
|
string s_note2
|
|
sprintf s_note1, "AxisLabelD=peak integral"
|
|
sprintf s_note2, "KineticEnergy=%.3f", cpos * nx * dimdelta(source, 0) + dimoffset(source, 0)
|
|
Note dest1, s_note1
|
|
Note dest1, s_note2
|
|
Note dest2, s_note1
|
|
Note dest2, s_note2
|
|
|
|
return result_waves
|
|
end
|
|
|
|
function prompt_int_quadbg_reduction(param)
|
|
string ¶m
|
|
|
|
variable Lcrop = NumberByKey("Lcrop", param, "=", ";")
|
|
variable Lsize = NumberByKey("Lsize", param, "=", ";")
|
|
variable Hcrop = NumberByKey("Hcrop", param, "=", ";")
|
|
variable Hsize = NumberByKey("Hsize", param, "=", ";")
|
|
variable Cpos = NumberByKey("Cpos", param, "=", ";")
|
|
variable Csize = NumberByKey("Csize", param, "=", ";")
|
|
|
|
prompt Lcrop, "Lower cropping region"
|
|
prompt Hcrop, "Upper cropping region"
|
|
prompt Lsize, "Lower background region"
|
|
prompt Hsize, "Upper background region"
|
|
prompt Cpos, "Center position"
|
|
prompt Csize, "Center integration region"
|
|
|
|
doprompt "int_quadbg_reduction Parameters", lcrop, hcrop, lsize, hsize, cpos, csize
|
|
if (v_flag == 0)
|
|
param = ReplaceNumberByKey("Lcrop", param, Lcrop, "=", ";")
|
|
param = ReplaceNumberByKey("Lsize", param, Lsize, "=", ";")
|
|
param = ReplaceNumberByKey("Hcrop", param, Hcrop, "=", ";")
|
|
param = ReplaceNumberByKey("Hsize", param, Hsize, "=", ";")
|
|
param = ReplaceNumberByKey("Cpos", param, Cpos, "=", ";")
|
|
param = ReplaceNumberByKey("Csize", param, Csize, "=", ";")
|
|
endif
|
|
|
|
return v_flag
|
|
end
|
|
|
|
/// integrate peak area minus a quadratic background
|
|
///
|
|
/// data reduction function for adh5_load_reduced_detector.
|
|
/// cf. @ref adh5_default_reduction for an explanation of reduction functions.
|
|
///
|
|
/// this function calculates the average pixel value of each angular slice
|
|
/// in one center and two background intervals.
|
|
/// a background value is calculated at the center position
|
|
/// by linear interpolation from the two background values.
|
|
/// returns the center minus linear background in dest1.
|
|
/// returns the Poisson one-sigma error in dest2.
|
|
///
|
|
/// typical values (peak centered on detector, FWHM ~ 20 % of image)
|
|
/// Lcrop=0.11;Hcrop=0.11;Lsize=0.2;Hsize=0.2;Cpos=0.5;Csize=0.2
|
|
///
|
|
/// @param source scienta detector image, energy axis along X, angle axis along Y.
|
|
/// two-dimensional intensity distribution (image).
|
|
/// the scales are carried over to the result waves.
|
|
///
|
|
/// @param param parameters in a key1=value1;key2=value2;... list.
|
|
/// all region parameters are relative to the image size (0...1).
|
|
/// @arg Lcrop = size of the lower cropping region
|
|
/// @arg Hcrop = size of the upper cropping region
|
|
/// @arg Lsize = size of the lower background integration region
|
|
/// @arg Hsize = size of the upper background integration region
|
|
/// @arg Cpos = center position of the of the peak integration region
|
|
/// @arg Csize = size of the peak integration region
|
|
///
|
|
/// @return free wave containing references of the two result waves.
|
|
/// the first wave is the integral minus linear background.
|
|
/// the second wave is the Poisson one-sigma error.
|
|
///
|
|
threadsafe function /wave int_quadbg_reduction(source, param)
|
|
wave source
|
|
string ¶m
|
|
|
|
variable nx = dimsize(source, 0)
|
|
variable ny = dimsize(source, 1)
|
|
|
|
// read parameters
|
|
variable lcrop = NumberByKey("Lcrop", param, "=", ";")
|
|
variable lsize = NumberByKey("Lsize", param, "=", ";")
|
|
variable hcrop = NumberByKey("Hcrop", param, "=", ";")
|
|
variable hsize = NumberByKey("Hsize", param, "=", ";")
|
|
variable cpos = NumberByKey("Cpos", param, "=", ";")
|
|
variable csize = NumberByKey("Csize", param, "=", ";")
|
|
|
|
make /wave /free /n=2 result_waves
|
|
make /free /n=0 dest1, dest2
|
|
result_waves[0] = dest1
|
|
result_waves[1] = dest2
|
|
adh5_setup_profile(source, dest1, 1)
|
|
adh5_setup_profile(source, dest2, 1)
|
|
|
|
// validate parameters
|
|
// background parameters are optional, center parameter is required.
|
|
if (numtype(lcrop) != 0)
|
|
lcrop = 0
|
|
endif
|
|
if (numtype(lsize) != 0)
|
|
lsize = 0
|
|
endif
|
|
if (numtype(hcrop) != 0)
|
|
hcrop = 0
|
|
endif
|
|
if (numtype(hsize) != 0)
|
|
hsize = 0
|
|
endif
|
|
if (numtype(Cpos) != 0)
|
|
redimension /n=0 result_waves
|
|
return result_waves // Cpos parameter missing
|
|
endif
|
|
if (numtype(Csize) != 0)
|
|
redimension /n=0 result_waves
|
|
return result_waves // Csize parameter missing
|
|
endif
|
|
|
|
// crop boundaries
|
|
variable pcl = round(lcrop * nx)
|
|
variable pch = round((1 - hcrop) * nx)
|
|
// fit boundaries
|
|
variable pfl = round((lcrop + lsize) * nx)
|
|
variable pfh = round((1 - hcrop - hsize) * nx)
|
|
// integration boundaries
|
|
variable pil = round((cpos - csize/2) * nx)
|
|
variable pih = round((cpos + csize/2) * nx)
|
|
|
|
// prepare intermediate data buffer
|
|
make /n=(nx) /d /free profile, mask, fit
|
|
setscale /p x dimoffset(source,0), dimdelta(source,0), waveunits(source,0), profile, mask, fit
|
|
mask = ((p >= pcl) && (p < pfl)) || ((p >= pfh) && (p < pch))
|
|
|
|
variable qq
|
|
variable sp, sf
|
|
variable xil = x2pnt(profile, pil)
|
|
variable xih = x2pnt(profile, pih)
|
|
|
|
make /n=3 /free /d w_coef
|
|
for (qq = 0; qq < ny; qq += 1)
|
|
profile = source[p][qq]
|
|
curvefit /Q /NTHR=1 /W=2 poly 3, kwCWave=w_coef, profile /M=mask
|
|
fit = poly(w_coef, x)
|
|
sp = sum(profile, xil, xih)
|
|
sf = sum(fit, xil, xih)
|
|
dest1[qq] = sp - sf
|
|
dest2[qq] = sqrt(sp)
|
|
endfor
|
|
|
|
string s_note1
|
|
string s_note2
|
|
sprintf s_note1, "AxisLabelD=peak integral"
|
|
sprintf s_note2, "KineticEnergy=%.3f", cpos * nx * dimdelta(source, 0) + dimoffset(source, 0)
|
|
Note dest1, s_note1
|
|
Note dest1, s_note2
|
|
Note dest2, s_note1
|
|
Note dest2, s_note2
|
|
|
|
return result_waves
|
|
end
|
|
|
|
/// parameter dialog for the redim_linbg_reduction() function
|
|
///
|
|
/// @param param parameter string in a key1=value1;key2=value2;... list.
|
|
/// the parameter string is passed by reference.
|
|
/// see redim_linbg_reduction() for a description of parameters.
|
|
///
|
|
/// @return zero if the user clicked OK, non-zero if the user clicked Cancel.
|
|
///
|
|
function prompt_redim_linbg_reduction(param)
|
|
string ¶m
|
|
|
|
variable Lcrop = NumberByKey("Lcrop", param, "=", ";")
|
|
variable Lsize = NumberByKey("Lsize", param, "=", ";")
|
|
variable Hcrop = NumberByKey("Hcrop", param, "=", ";")
|
|
variable Hsize = NumberByKey("Hsize", param, "=", ";")
|
|
variable Cpos = NumberByKey("Cpos", param, "=", ";")
|
|
variable Csize = NumberByKey("Csize", param, "=", ";")
|
|
|
|
prompt Lcrop, "Lower cropping region"
|
|
prompt Hcrop, "Upper cropping region"
|
|
prompt Lsize, "Lower background region"
|
|
prompt Hsize, "Upper background region"
|
|
prompt Cpos, "Center position"
|
|
prompt Csize, "Center integration region"
|
|
|
|
doprompt "redim_linbg_reduction Parameters", lcrop, hcrop, lsize, hsize, cpos, csize
|
|
if (v_flag == 0)
|
|
param = ReplaceNumberByKey("Lcrop", param, Lcrop, "=", ";")
|
|
param = ReplaceNumberByKey("Lsize", param, Lsize, "=", ";")
|
|
param = ReplaceNumberByKey("Hcrop", param, Hcrop, "=", ";")
|
|
param = ReplaceNumberByKey("Hsize", param, Hsize, "=", ";")
|
|
param = ReplaceNumberByKey("Cpos", param, Cpos, "=", ";")
|
|
param = ReplaceNumberByKey("Csize", param, Csize, "=", ";")
|
|
endif
|
|
|
|
return v_flag
|
|
end
|
|
|
|
/// linear background reduction function for incorrectly dimensioned scienta image
|
|
///
|
|
/// if the energy step size does not divide the energy range to an integer number,
|
|
/// the scienta image is exported with the wrong array size.
|
|
/// this can be fixed by redimensioning the array.
|
|
///
|
|
/// the current implementation works in the case where dimension 0 needs to be incremented.
|
|
/// the function may be generalized to dimension 1 and/or decrementing by additional parameters.
|
|
/// it is not known yet whether a generalization is needed or whether it can cover all cases.
|
|
///
|
|
/// background subtraction and peak integration is the same as by the int_linbg_reduction() function.
|
|
///
|
|
/// @param source source wave
|
|
/// Scienta detector image, energy axis along X, angle axis along Y
|
|
///
|
|
/// @param param parameter string in a key1=value1;key2=value2;... list.
|
|
/// the parameter string is passed by reference.
|
|
///
|
|
/// all region parameters are relative to the image size (0...1).
|
|
/// @arg Lcrop size of the lower cropping region
|
|
/// @arg Hcrop size of the upper cropping region
|
|
/// @arg Lsize size of the lower background integration region
|
|
/// @arg Hsize size of the upper background integration region
|
|
/// @arg Cpos center position of the of the peak integration region
|
|
/// @arg Csize size of the peak integration region
|
|
///
|
|
/// typical values (peak centered on detector, FWHM ~ 20 % of image)
|
|
/// Lcrop=0.11;Hcrop=0.11;Lsize=0.2;Hsize=0.2;Cpos=0.5;Csize=0.2
|
|
///
|
|
/// @return free wave containing references of the two result waves.
|
|
/// the first wave is the integral minus linear background.
|
|
/// the second wave is the Poisson one-sigma error.
|
|
///
|
|
threadsafe function /wave redim_linbg_reduction(source, param)
|
|
wave source
|
|
string ¶m
|
|
|
|
variable nx = dimsize(source, 0)
|
|
variable ny = dimsize(source, 1)
|
|
|
|
duplicate /free source, source_redim
|
|
redimension /n=(nx * ny) source_redim
|
|
nx += 1
|
|
redimension /n=(nx, ny) source_redim
|
|
|
|
return int_linbg_reduction(source_redim, param)
|
|
end
|
|
|
|
/// apply the gauss4_reduction function to a single image
|
|
///
|
|
/// useful for testing or manual processing.
|
|
/// to debug, (temporarily) remove the threadsafe attribute from the gauss2_reduction function.
|
|
///
|
|
function test_gauss4_reduction(image)
|
|
wave image
|
|
|
|
string param = ""
|
|
|
|
param = ReplaceNumberByKey("rngl", param, -inf, "=", ";")
|
|
param = ReplaceNumberByKey("rngh", param, inf, "=", ";")
|
|
param = ReplaceNumberByKey("npeaks", param, 4, "=", ";")
|
|
param = ReplaceNumberByKey("ybox", param, 1, "=", ";")
|
|
param = ReplaceNumberByKey("pos1", param, 11, "=", ";")
|
|
param = ReplaceNumberByKey("wid1", param, 0.1, "=", ";")
|
|
param = ReplaceNumberByKey("pos2", param, 12, "=", ";")
|
|
param = ReplaceNumberByKey("wid2", param, 0.2, "=", ";")
|
|
param = ReplaceNumberByKey("pos3", param, 13, "=", ";")
|
|
param = ReplaceNumberByKey("wid3", param, 0.3, "=", ";")
|
|
param = ReplaceNumberByKey("pos4", param, 14, "=", ";")
|
|
param = ReplaceNumberByKey("wid4", param, 0.4, "=", ";")
|
|
|
|
wave /wave results = gauss4_reduction(image, param)
|
|
|
|
variable npk = numpnts(results) / 2
|
|
variable ipk
|
|
string sw
|
|
for (ipk = 0; ipk < npk; ipk += 1)
|
|
sw = "test_int_" + num2str(ipk + 1)
|
|
duplicate /o results[ipk], $sw
|
|
sw = "test_sig_" + num2str(ipk + 1)
|
|
duplicate /o results[ipk + npk], $sw
|
|
endfor
|
|
end
|
|
|
|
/// prompt for the gauss4_reduction parameters
|
|
///
|
|
///
|
|
function prompt_gauss4_reduction(param)
|
|
string ¶m
|
|
|
|
variable rngl = NumberByKey("rngl", param, "=", ";")
|
|
variable rngh = NumberByKey("rngh", param, "=", ";")
|
|
variable pos1 = NumberByKey("pos1", param, "=", ";")
|
|
variable wid1 = NumberByKey("wid1", param, "=", ";")
|
|
variable pos2 = NumberByKey("pos2", param, "=", ";")
|
|
variable wid2 = NumberByKey("wid2", param, "=", ";")
|
|
variable pos3 = NumberByKey("pos3", param, "=", ";")
|
|
variable wid3 = NumberByKey("wid3", param, "=", ";")
|
|
variable pos4 = NumberByKey("pos4", param, "=", ";")
|
|
variable wid4 = NumberByKey("wid4", param, "=", ";")
|
|
variable npeaks = NumberByKey("npeaks", param, "=", ";")
|
|
variable ybox = NumberByKey("ybox", param, "=", ";")
|
|
|
|
prompt rngl, "range low"
|
|
prompt rngh, "range high"
|
|
prompt pos1, "position 1"
|
|
prompt wid1, "width 1"
|
|
prompt pos2, "position 2"
|
|
prompt wid2, "width 2"
|
|
prompt pos3, "position 3"
|
|
prompt wid3, "width 3"
|
|
prompt pos4, "position 4"
|
|
prompt wid4, "width 4"
|
|
prompt npeaks, "number of peaks"
|
|
prompt ybox, "ybox (1 or 3)"
|
|
|
|
doprompt "gauss4_reduction reduction parameters (1/2)", rngl, rngh, npeaks, ybox
|
|
if (v_flag == 0)
|
|
param = ReplaceNumberByKey("rngl", param, rngl, "=", ";")
|
|
param = ReplaceNumberByKey("rngh", param, rngh, "=", ";")
|
|
param = ReplaceNumberByKey("npeaks", param, npeaks, "=", ";")
|
|
param = ReplaceNumberByKey("ybox", param, ybox, "=", ";")
|
|
|
|
doprompt "gauss4_reduction reduction parameters (2/2)", pos1, wid1, pos2, wid2, pos3, wid3, pos4, wid4
|
|
if (v_flag == 0)
|
|
param = ReplaceNumberByKey("pos1", param, pos1, "=", ";")
|
|
param = ReplaceNumberByKey("wid1", param, wid1, "=", ";")
|
|
param = ReplaceNumberByKey("pos2", param, pos2, "=", ";")
|
|
param = ReplaceNumberByKey("wid2", param, wid2, "=", ";")
|
|
param = ReplaceNumberByKey("pos3", param, pos3, "=", ";")
|
|
param = ReplaceNumberByKey("wid3", param, wid3, "=", ";")
|
|
param = ReplaceNumberByKey("pos4", param, pos4, "=", ";")
|
|
param = ReplaceNumberByKey("wid4", param, wid4, "=", ";")
|
|
endif
|
|
endif
|
|
|
|
return v_flag
|
|
end
|
|
|
|
/// fit horizontal cuts of an image with up to four gaussian peaks on a linear background
|
|
///
|
|
/// the function fits each horizontal profile (EDC) with four gaussian peaks on a linear background.
|
|
/// the position and width of the peaks is kept fixed according to input parameters.
|
|
/// the peak amplitude is constrained to positive value.
|
|
///
|
|
/// the width parameter is defined as in Igor's gauss curve fit function
|
|
/// (standard deviation divided by the square root of two).
|
|
/// the return value in dest1 is the integrated peak of one of the peaks.
|
|
/// dest2 returns the corresponding error estimate.
|
|
///
|
|
/// @param source source wave.
|
|
/// two-dimensional distribution of counts.
|
|
/// for correct weighting and error estimation it is important
|
|
/// that the source wave contains actual counts (Poisson statistics).
|
|
///
|
|
/// @param param (in, out) semicolon-separated key=value list of processing parameters.
|
|
/// this is a pass-by-reference argument.
|
|
/// the following parameters are required.
|
|
/// position, width and limit parameters are on the x (energy) scale.
|
|
/// @arg rngl low limit of fit interval
|
|
/// @arg rngh high limit of fit interval
|
|
/// @arg pos1 position of peak 1
|
|
/// @arg wid1 width of peak 1
|
|
/// @arg pos2 position of peak 2
|
|
/// @arg wid2 width of peak 2
|
|
/// @arg pos3 position of peak 3
|
|
/// @arg wid3 width of peak 3
|
|
/// @arg pos4 position of peak 4
|
|
/// @arg wid4 width of peak 4
|
|
/// @arg npeaks number of peaks to fit: 1...4
|
|
/// the others are held at amplitude 0.
|
|
/// @arg ybox box size of averaging in y direction, must be 1 or 3.
|
|
/// other values lead to corrupt data.
|
|
///
|
|
/// @return free wave containing references of the result waves.
|
|
/// the number of waves is two times the number of peaks that are fit.
|
|
/// the first npeaks waves contain the peak integrals,
|
|
/// the second npeaks waves the corresponding error estimates.
|
|
///
|
|
threadsafe function /wave gauss4_reduction(source, param)
|
|
wave source
|
|
string ¶m
|
|
|
|
variable nx = dimsize(source, 0)
|
|
variable ny = dimsize(source, 1)
|
|
|
|
// read parameters
|
|
variable rngl = NumberByKey("rngl", param, "=", ";")
|
|
variable rngh = NumberByKey("rngh", param, "=", ";")
|
|
variable pos1 = NumberByKey("pos1", param, "=", ";")
|
|
variable wid1 = NumberByKey("wid1", param, "=", ";")
|
|
variable pos2 = NumberByKey("pos2", param, "=", ";")
|
|
variable wid2 = NumberByKey("wid2", param, "=", ";")
|
|
variable pos3 = NumberByKey("pos3", param, "=", ";")
|
|
variable wid3 = NumberByKey("wid3", param, "=", ";")
|
|
variable pos4 = NumberByKey("pos4", param, "=", ";")
|
|
variable wid4 = NumberByKey("wid4", param, "=", ";")
|
|
variable npeaks = NumberByKey("npeaks", param, "=", ";")
|
|
variable ybox = NumberByKey("ybox", param, "=", ";")
|
|
|
|
// prepare curve fit
|
|
variable ipk
|
|
make /free xprof
|
|
adh5_setup_profile(source, xprof, 0)
|
|
duplicate /free xprof, xprof_sig
|
|
variable pl = max(x2pnt(xprof, rngl), 0)
|
|
variable ph = min(x2pnt(xprof, rngh), numpnts(xprof) - 1)
|
|
|
|
make /free /n=(npeaks) peak_coef
|
|
peak_coef = p * 3 + 2
|
|
variable n_coef = npeaks * 3 + 2
|
|
make /free /d /n=14 w_coef, W_sigma
|
|
w_coef[0] = {0, 0, 1, pos1, wid1, 1, pos2, wid2, 1, pos3, wid3, 1, pos4, wid4}
|
|
redimension /n=(n_coef) w_coef, w_sigma
|
|
|
|
// text constraints cannot be used in threadsafe functions.
|
|
// the following matrix-vector forumlation is equivalent to:
|
|
// make /free /T /N=6 constraints
|
|
// constraints[0] = {"K2 >= 0", "K5 >= 0", "K8 >= 0", "K11 >= 0", "K1 <= 0", "K0 => 0"}
|
|
make /free /n=(npeaks + 2, numpnts(w_coef)) cmat
|
|
make /free /n=(npeaks + 2) cvec
|
|
cmat = 0
|
|
cmat[0][0] = -1
|
|
cmat[1][1] = 1
|
|
cvec = 0
|
|
|
|
string hold = "00"
|
|
for (ipk=0; ipk < npeaks; ipk += 1)
|
|
hold += "011"
|
|
cmat[2 + ipk][2 + ipk*3] = -1
|
|
endfor
|
|
|
|
// prepare output
|
|
make /free /n=(npeaks * 2) /wave result_waves
|
|
string s_note
|
|
for (ipk = 0; ipk < npeaks; ipk += 1)
|
|
make /free /n=0 pk_int
|
|
adh5_setup_profile(source, pk_int, 1)
|
|
pk_int = nan
|
|
sprintf s_note, "AxisLabelD=peak %u integral", ipk+1
|
|
Note pk_int, s_note
|
|
sprintf s_note, "KineticEnergy=%.3f", w_coef[3 + ipk * 3]
|
|
Note pk_int, s_note
|
|
result_waves[ipk] = pk_int
|
|
|
|
make /free /n=0 pk_sig
|
|
adh5_setup_profile(source, pk_sig, 1)
|
|
pk_sig = nan
|
|
sprintf s_note, "AxisLabelD=peak %u sigma", ipk+1
|
|
Note pk_sig, s_note
|
|
sprintf s_note, "KineticEnergy=%.3f", w_coef[3 + ipk * 3]
|
|
Note pk_sig, s_note
|
|
result_waves[ipk + npeaks] = pk_sig
|
|
|
|
waveclear pk_int, pk_sig
|
|
endfor
|
|
|
|
// loop over angle scale
|
|
variable p0 = 0
|
|
variable p1 = dimsize(source, 1) - 1
|
|
variable pp
|
|
variable wmin
|
|
variable wmax
|
|
if (ybox > 1)
|
|
p0 += ceil((ybox - 1) / 2)
|
|
p1 -= ceil((ybox - 1) / 2)
|
|
endif
|
|
variable V_FitNumIters
|
|
variable V_FitError
|
|
|
|
for (pp = p0; pp <= p1; pp += 1)
|
|
// box average
|
|
xprof = source[p][pp]
|
|
if (ybox > 1)
|
|
xprof += source[p][pp-1] + source[p][pp+1]
|
|
endif
|
|
xprof_sig = max(sqrt(xprof), 1)
|
|
xprof /= ybox
|
|
xprof_sig /= ybox
|
|
|
|
// generate guess
|
|
wmin = wavemin(xprof)
|
|
wmax = wavemax(xprof)
|
|
w_coef[0] = wmin
|
|
w_coef[1] = 0
|
|
for (ipk=0; ipk < npeaks; ipk += 1)
|
|
w_coef[2 + ipk*3] = wmax - wmin
|
|
endfor
|
|
|
|
V_FitError = 0
|
|
FuncFit /H=hold /Q /NTHR=1 /N /W=2 MultiGaussLinBG_AO w_coef xprof[pl,ph] /C={cmat, cvec} /I=1 /W=xprof_sig[pl,ph]
|
|
wave w_sigma
|
|
|
|
// retrieve results, leave them at nan if the fit did not converge
|
|
if (V_FitNumIters < 40)
|
|
for (ipk = 0; ipk < npeaks; ipk += 1)
|
|
wave val = result_waves[ipk]
|
|
wave sig = result_waves[ipk + npeaks]
|
|
val[pp] = max(w_coef[peak_coef[ipk]], 0)
|
|
sig[pp] = max(w_sigma[peak_coef[ipk]], 0)
|
|
endfor
|
|
endif
|
|
endfor
|
|
|
|
// calculate integral
|
|
for (ipk = 0; ipk < npeaks; ipk += 1)
|
|
wave val = result_waves[ipk]
|
|
wave sig = result_waves[ipk + npeaks]
|
|
val *= w_coef[peak_coef[ipk] + 2] * sqrt(pi)
|
|
sig *= w_coef[peak_coef[ipk] + 2] * sqrt(pi)
|
|
endfor
|
|
|
|
return result_waves
|
|
end
|
|
|
|
threadsafe function /wave gauss6_reduction(source, param)
|
|
wave source
|
|
string ¶m
|
|
|
|
variable nx = dimsize(source, 0)
|
|
variable ny = dimsize(source, 1)
|
|
|
|
// read parameters
|
|
variable rngl = NumberByKey("rngl", param, "=", ";")
|
|
variable rngh = NumberByKey("rngh", param, "=", ";")
|
|
variable pos1 = NumberByKey("pos1", param, "=", ";")
|
|
variable wid1 = NumberByKey("wid1", param, "=", ";")
|
|
variable pos2 = NumberByKey("pos2", param, "=", ";")
|
|
variable wid2 = NumberByKey("wid2", param, "=", ";")
|
|
variable pos3 = NumberByKey("pos3", param, "=", ";")
|
|
variable wid3 = NumberByKey("wid3", param, "=", ";")
|
|
variable pos4 = NumberByKey("pos4", param, "=", ";")
|
|
variable wid4 = NumberByKey("wid4", param, "=", ";")
|
|
variable pos5 = NumberByKey("pos5", param, "=", ";")
|
|
variable wid5 = NumberByKey("wid5", param, "=", ";")
|
|
variable pos6 = NumberByKey("pos6", param, "=", ";")
|
|
variable wid6 = NumberByKey("wid6", param, "=", ";")
|
|
variable npeaks = NumberByKey("npeaks", param, "=", ";")
|
|
variable ybox = NumberByKey("ybox", param, "=", ";")
|
|
|
|
// prepare curve fit
|
|
variable ipk
|
|
make /free xprof
|
|
adh5_setup_profile(source, xprof, 0)
|
|
duplicate /free xprof, xprof_sig
|
|
variable pl = max(x2pnt(xprof, rngl), 0)
|
|
variable ph = min(x2pnt(xprof, rngh), numpnts(xprof) - 1)
|
|
|
|
make /free /n=(npeaks) peak_coef
|
|
peak_coef = p * 3 + 2
|
|
variable n_coef = npeaks * 3 + 2
|
|
make /free /d /n=(n_coef) w_coef, W_sigma
|
|
w_coef[0] = {0, 0, 1, pos1, wid1, 1, pos2, wid2, 1, pos3, wid3, 1, pos4, wid4, 1, pos5, wid5, 1, pos6, wid6}
|
|
redimension /n=(n_coef) w_coef, w_sigma
|
|
|
|
// text constraints cannot be used in threadsafe functions.
|
|
// the following matrix-vector forumlation is equivalent to:
|
|
// make /free /T /N=6 constraints
|
|
// constraints[0] = {"K2 >= 0", "K5 >= 0", "K8 >= 0", "K11 >= 0", "K1 <= 0", "K0 => 0"}
|
|
make /free /n=(npeaks + 2, numpnts(w_coef)) cmat
|
|
make /free /n=(npeaks + 2) cvec
|
|
cmat = 0
|
|
cmat[0][0] = -1
|
|
cmat[1][1] = 1
|
|
cvec = 0
|
|
|
|
string hold = "00"
|
|
for (ipk=0; ipk < npeaks; ipk += 1)
|
|
hold += "011"
|
|
cmat[2 + ipk][2 + ipk*3] = -1
|
|
endfor
|
|
|
|
// prepare output
|
|
make /free /n=(npeaks * 2) /wave result_waves
|
|
string s_note
|
|
for (ipk = 0; ipk < npeaks; ipk += 1)
|
|
make /free /n=0 pk_int
|
|
adh5_setup_profile(source, pk_int, 1)
|
|
pk_int = nan
|
|
sprintf s_note, "AxisLabelD=peak %u integral", ipk+1
|
|
Note pk_int, s_note
|
|
sprintf s_note, "KineticEnergy=%.3f", w_coef[3 + ipk * 3]
|
|
Note pk_int, s_note
|
|
result_waves[ipk] = pk_int
|
|
|
|
make /free /n=0 pk_sig
|
|
adh5_setup_profile(source, pk_sig, 1)
|
|
pk_sig = nan
|
|
sprintf s_note, "AxisLabelD=peak %u sigma", ipk+1
|
|
Note pk_sig, s_note
|
|
sprintf s_note, "KineticEnergy=%.3f", w_coef[3 + ipk * 3]
|
|
Note pk_sig, s_note
|
|
result_waves[ipk + npeaks] = pk_sig
|
|
|
|
waveclear pk_int, pk_sig
|
|
endfor
|
|
|
|
// loop over angle scale
|
|
variable p0 = 0
|
|
variable p1 = dimsize(source, 1) - 1
|
|
variable pp
|
|
variable wmin
|
|
variable wmax
|
|
if (ybox > 1)
|
|
p0 += ceil((ybox - 1) / 2)
|
|
p1 -= ceil((ybox - 1) / 2)
|
|
endif
|
|
variable V_FitNumIters
|
|
variable V_FitError
|
|
|
|
for (pp = p0; pp <= p1; pp += 1)
|
|
// box average
|
|
xprof = source[p][pp]
|
|
if (ybox > 1)
|
|
xprof += source[p][pp-1] + source[p][pp+1]
|
|
endif
|
|
xprof_sig = max(sqrt(xprof), 1)
|
|
xprof /= ybox
|
|
xprof_sig /= ybox
|
|
|
|
// generate guess
|
|
wmin = wavemin(xprof)
|
|
wmax = wavemax(xprof)
|
|
w_coef[0] = wmin
|
|
w_coef[1] = 0
|
|
for (ipk=0; ipk < npeaks; ipk += 1)
|
|
w_coef[2 + ipk*3] = wmax - wmin
|
|
endfor
|
|
|
|
V_FitError = 0
|
|
FuncFit /H=hold /Q /NTHR=1 /N /W=2 MultiGaussLinBG_AO w_coef xprof[pl,ph] /C={cmat, cvec} /I=1 /W=xprof_sig[pl,ph]
|
|
wave w_sigma
|
|
|
|
// retrieve results, leave them at nan if the fit did not converge
|
|
if (V_FitNumIters < 40)
|
|
for (ipk = 0; ipk < npeaks; ipk += 1)
|
|
wave val = result_waves[ipk]
|
|
wave sig = result_waves[ipk + npeaks]
|
|
val[pp] = max(w_coef[peak_coef[ipk]], 0)
|
|
sig[pp] = max(w_sigma[peak_coef[ipk]], 0)
|
|
endfor
|
|
endif
|
|
endfor
|
|
|
|
// calculate integral
|
|
for (ipk = 0; ipk < npeaks; ipk += 1)
|
|
wave val = result_waves[ipk]
|
|
wave sig = result_waves[ipk + npeaks]
|
|
val *= w_coef[peak_coef[ipk] + 2] * sqrt(pi)
|
|
sig *= w_coef[peak_coef[ipk] + 2] * sqrt(pi)
|
|
endfor
|
|
|
|
return result_waves
|
|
end
|
|
|
|
|
|
|
|
|
|
/// find peak positions for the gauss-fit reduction function
|
|
///
|
|
/// the function fits a reference spectrum with up to four gaussian peaks on a linear background.
|
|
/// and returns the results in a parameter string for the @ref gauss4_reduction function.
|
|
///
|
|
/// @param spectrum reference spectrum (EDC),
|
|
/// e.g. a ScientaImage integrated over the scan axes:
|
|
/// @code ad_extract_rod(ScientaImage, nan, nan, -inf, inf, -inf, inf, "ScientaSpectrum").
|
|
///
|
|
/// @param peakpos initial guess of the peak position.
|
|
/// the length of this wave determines the number of peaks to fit.
|
|
///
|
|
/// @return semicolon-separated key=value list of processing parameters
|
|
/// for use with the reduction functions.
|
|
/// position, width and limit parameters are on the x (energy) scale.
|
|
/// @arg rngl low limit of fit interval
|
|
/// @arg rngh high limit of fit interval
|
|
/// @arg pos1 position of peak 1
|
|
/// @arg wid1 width of peak 1
|
|
/// @arg pos2 position of peak 2
|
|
/// @arg wid2 width of peak 2
|
|
/// @arg pos3 position of peak 3
|
|
/// @arg wid3 width of peak 3
|
|
/// @arg pos4 position of peak 4
|
|
/// @arg wid4 width of peak 4
|
|
/// @arg return select result to return, "amp1" ... "amp4"
|
|
/// @arg npeaks number of peaks to fit: 1...4
|
|
/// the others are held at amplitude 0.
|
|
/// @arg ybox box size of averaging in y direction, must be 1 or 3.
|
|
/// other values lead to corrupt data.
|
|
///
|
|
function /s find_gauss4_reduction_params(spectrum, peakpos)
|
|
wave spectrum
|
|
wave peakpos
|
|
string param = ""
|
|
|
|
variable wmin = wavemin(spectrum)
|
|
variable wmax = wavemax(spectrum)
|
|
|
|
// read parameters
|
|
variable rngl = dimoffset(spectrum, 0)
|
|
variable rngh = dimoffset(spectrum, 0) + dimdelta(spectrum, 0) * (dimsize(spectrum, 0) - 1)
|
|
make /n=4 /free positions, widths
|
|
variable npeaks = numpnts(peakpos)
|
|
variable ybox = 1
|
|
positions = 0
|
|
positions[0, npeaks-1] = peakpos[p]
|
|
widths = 0.2
|
|
|
|
variable n_coef = npeaks * 3 + 2
|
|
make /free /d /n=(n_coef) w_coef
|
|
w_coef = 0
|
|
w_coef[0] = wmin
|
|
w_coef[1] = 0
|
|
|
|
make /free /n=(2+npeaks, numpnts(w_coef)) cmat
|
|
make /free /n=(2+npeaks) cvec
|
|
cmat = 0
|
|
cmat[0][0] = -1
|
|
cmat[1][1] = 1
|
|
cvec = 0
|
|
|
|
variable ip
|
|
for (ip=0; ip < npeaks; ip += 1)
|
|
cmat[2 + ip][2 + ip*3] = -1
|
|
w_coef[2 + ip*3] = wmax - wmin
|
|
w_coef[3 + ip*3] = peakpos[ip]
|
|
w_coef[4 + ip*3] = widths[ip]
|
|
endfor
|
|
|
|
variable V_FitNumIters
|
|
FuncFit /Q /NTHR=1 /N MultiGaussLinBG w_coef spectrum /C={cmat, cvec}
|
|
|
|
for (ip=0; ip < npeaks; ip += 1)
|
|
positions[ip] = w_coef[3 + ip * 3]
|
|
widths[ip ] = abs(w_coef[4 + ip * 3])
|
|
endfor
|
|
for (ip=npeaks; ip < 4; ip += 1)
|
|
positions[ip] = 0
|
|
widths[ip] = 0.2
|
|
endfor
|
|
|
|
param = ReplaceNumberByKey("rngl", param, rngl, "=", ";")
|
|
param = ReplaceNumberByKey("rngh", param, rngh, "=", ";")
|
|
param = ReplaceNumberByKey("npeaks", param, npeaks, "=", ";")
|
|
param = ReplaceNumberByKey("ybox", param, ybox, "=", ";")
|
|
param = ReplaceNumberByKey("pos1", param, positions[0], "=", ";")
|
|
param = ReplaceNumberByKey("wid1", param, widths[0], "=", ";")
|
|
param = ReplaceNumberByKey("pos2", param, positions[1], "=", ";")
|
|
param = ReplaceNumberByKey("wid2", param, widths[1], "=", ";")
|
|
param = ReplaceNumberByKey("pos3", param, positions[2], "=", ";")
|
|
param = ReplaceNumberByKey("wid3", param, widths[2], "=", ";")
|
|
param = ReplaceNumberByKey("pos4", param, positions[3], "=", ";")
|
|
param = ReplaceNumberByKey("wid4", param, widths[3], "=", ";")
|
|
|
|
return param
|
|
end
|
|
|
|
/// fit horizontal cuts of an image with up to four voigtian peaks on a linear background
|
|
///
|
|
/// the function fits each horizontal profile (EDC) with four voigtian peaks on a linear background.
|
|
/// the position, width and shape of the peaks is kept fixed according to input parameters.
|
|
/// the peak amplitude is constrained to positive value.
|
|
///
|
|
/// the width and shape parameters are defined as in Igor's VoigtFunc:
|
|
/// width is the width of the gaussian component,
|
|
/// shape * width is the width of the lorentzian component.
|
|
///
|
|
/// @param source source wave.
|
|
/// two-dimensional distribution of counts.
|
|
/// for correct weighting and error estimation it is important
|
|
/// that the source wave contains actual counts (Poisson statistics).
|
|
///
|
|
/// @param param (in, out) semicolon-separated key=value list of processing parameters.
|
|
/// this is a pass-by-reference argument.
|
|
/// the following parameters are required.
|
|
/// position, width and limit parameters are on the x (energy) scale.
|
|
/// @arg rngl low limit of fit interval
|
|
/// @arg rngh high limit of fit interval
|
|
/// @arg pos1 position of peak 1
|
|
/// @arg wid1 width of peak 1
|
|
/// @arg shp1 shape of peak 1
|
|
/// @arg pos2 position of peak 2
|
|
/// @arg wid2 width of peak 2
|
|
/// @arg shp2 shape of peak 2
|
|
/// @arg pos3 position of peak 3
|
|
/// @arg wid3 width of peak 3
|
|
/// @arg shp3 shape of peak 3
|
|
/// @arg pos4 position of peak 4
|
|
/// @arg wid4 width of peak 4
|
|
/// @arg shp4 shape of peak 4
|
|
/// @arg npeaks number of peaks to fit: 1...4
|
|
/// the others are held at amplitude 0.
|
|
///
|
|
/// @return free wave containing references of the result waves.
|
|
/// the number of waves is two times the number of peaks that are fit.
|
|
/// the first npeaks waves contain the peak amplitudes,
|
|
/// the second npeaks waves the corresponding error estimates.
|
|
///
|
|
threadsafe function /wave voigt4_reduction(source, param)
|
|
wave source
|
|
string ¶m
|
|
|
|
dfref orig_dfr = GetDataFolderDFR()
|
|
|
|
variable nx = dimsize(source, 0)
|
|
variable ny = dimsize(source, 1)
|
|
|
|
// read parameters
|
|
variable rngl = NumberByKey("rngl", param, "=", ";")
|
|
variable rngh = NumberByKey("rngh", param, "=", ";")
|
|
variable pos1 = NumberByKey("pos1", param, "=", ";")
|
|
variable wid1 = NumberByKey("wid1", param, "=", ";")
|
|
variable shp1 = NumberByKey("shp1", param, "=", ";")
|
|
variable pos2 = NumberByKey("pos2", param, "=", ";")
|
|
variable wid2 = NumberByKey("wid2", param, "=", ";")
|
|
variable shp2 = NumberByKey("shp2", param, "=", ";")
|
|
variable pos3 = NumberByKey("pos3", param, "=", ";")
|
|
variable wid3 = NumberByKey("wid3", param, "=", ";")
|
|
variable shp3 = NumberByKey("shp3", param, "=", ";")
|
|
variable pos4 = NumberByKey("pos4", param, "=", ";")
|
|
variable wid4 = NumberByKey("wid4", param, "=", ";")
|
|
variable shp4 = NumberByKey("shp4", param, "=", ";")
|
|
variable npeaks = NumberByKey("npeaks", param, "=", ";")
|
|
|
|
// prepare curve fit
|
|
variable ipk
|
|
make /free xprof
|
|
adh5_setup_profile(source, xprof, 0)
|
|
duplicate /free xprof, xprof_sig
|
|
variable pl = max(x2pnt(xprof, rngl), 0)
|
|
variable ph = min(x2pnt(xprof, rngh), numpnts(xprof) - 1)
|
|
|
|
make /free /n=(npeaks) peak_coef
|
|
variable coef_per_peak = 4
|
|
peak_coef = p * coef_per_peak + 2
|
|
variable n_coef = npeaks * coef_per_peak + 2
|
|
make /free /d /n=18 w_coef, W_sigma
|
|
w_coef[0] = {0, 0, 1, pos1, wid1, shp1, 1, pos2, wid2, shp2, 1, pos3, wid3, shp3, 1, pos4, wid4, shp4}
|
|
redimension /n=(n_coef) w_coef, w_sigma
|
|
|
|
// text constraints cannot be used in threadsafe functions.
|
|
// the following matrix-vector formulation enforces all peak amplitudes to be positive.
|
|
make /free /n=(npeaks, numpnts(w_coef)) cmat
|
|
make /free /n=(npeaks) cvec
|
|
cmat = 0
|
|
cvec = 0
|
|
|
|
string hold = "00"
|
|
for (ipk=0; ipk < npeaks; ipk += 1)
|
|
hold += "0111"
|
|
cmat[ipk][2 + ipk * coef_per_peak] = -1
|
|
endfor
|
|
|
|
// prepare output
|
|
make /free /n=(npeaks * 2) /wave result_waves
|
|
string s_note
|
|
for (ipk = 0; ipk < npeaks; ipk += 1)
|
|
make /free /n=0 pk_int
|
|
adh5_setup_profile(source, pk_int, 1)
|
|
pk_int = nan
|
|
sprintf s_note, "AxisLabelD=peak %u integral", ipk+1
|
|
Note pk_int, s_note
|
|
sprintf s_note, "KineticEnergy=%.3f", w_coef[3 + ipk * coef_per_peak]
|
|
Note pk_int, s_note
|
|
result_waves[ipk] = pk_int
|
|
|
|
make /free /n=0 pk_sig
|
|
adh5_setup_profile(source, pk_sig, 1)
|
|
pk_sig = nan
|
|
sprintf s_note, "AxisLabelD=peak %u sigma", ipk+1
|
|
Note pk_sig, s_note
|
|
sprintf s_note, "KineticEnergy=%.3f", w_coef[3 + ipk * coef_per_peak]
|
|
Note pk_sig, s_note
|
|
result_waves[ipk + npeaks] = pk_sig
|
|
|
|
waveclear pk_int, pk_sig
|
|
endfor
|
|
|
|
// loop over angle scale
|
|
variable p0 = 0
|
|
variable p1 = dimsize(source, 1) - 1
|
|
variable pp
|
|
variable wmin
|
|
variable wmax
|
|
variable V_FitNumIters
|
|
variable V_FitError
|
|
|
|
for (pp = p0; pp <= p1; pp += 1)
|
|
xprof = source[p][pp]
|
|
xprof_sig = max(sqrt(xprof), 1)
|
|
|
|
// generate guess
|
|
wmin = wavemin(xprof)
|
|
wmax = wavemax(xprof)
|
|
w_coef[0] = wmin
|
|
w_coef[1] = 0
|
|
|
|
for (ipk=0; ipk < npeaks; ipk += 1)
|
|
w_coef[2 + ipk * coef_per_peak] = wmax - wmin
|
|
endfor
|
|
|
|
V_FitError = 0
|
|
FuncFit /H=hold /Q /N /W=2 MultiVoigtLinBG_AO w_coef xprof[pl,ph] /C={cmat, cvec} /I=1 /W=xprof_sig[pl,ph]
|
|
wave w_sigma
|
|
|
|
// retrieve results, leave them at nan if the fit did not converge
|
|
if (V_FitNumIters < 40)
|
|
for (ipk = 0; ipk < npeaks; ipk += 1)
|
|
wave val = result_waves[ipk]
|
|
wave sig = result_waves[ipk + npeaks]
|
|
val[pp] = max(w_coef[peak_coef[ipk]], 0)
|
|
sig[pp] = max(w_sigma[peak_coef[ipk]], 0)
|
|
endfor
|
|
endif
|
|
endfor
|
|
|
|
SetDataFolder orig_dfr
|
|
return result_waves
|
|
end
|
|
|
|
|
|
/// prompt for the voigt4_reduction parameters
|
|
///
|
|
///
|
|
function prompt_voigt4_reduction(param)
|
|
string ¶m
|
|
|
|
variable rngl = NumberByKey("rngl", param, "=", ";")
|
|
variable rngh = NumberByKey("rngh", param, "=", ";")
|
|
variable pos1 = NumberByKey("pos1", param, "=", ";")
|
|
variable wid1 = NumberByKey("wid1", param, "=", ";")
|
|
variable shp1 = NumberByKey("shp1", param, "=", ";")
|
|
variable pos2 = NumberByKey("pos2", param, "=", ";")
|
|
variable wid2 = NumberByKey("wid2", param, "=", ";")
|
|
variable shp2 = NumberByKey("shp2", param, "=", ";")
|
|
variable pos3 = NumberByKey("pos3", param, "=", ";")
|
|
variable wid3 = NumberByKey("wid3", param, "=", ";")
|
|
variable shp3 = NumberByKey("shp3", param, "=", ";")
|
|
variable pos4 = NumberByKey("pos4", param, "=", ";")
|
|
variable wid4 = NumberByKey("wid4", param, "=", ";")
|
|
variable shp4 = NumberByKey("shp4", param, "=", ";")
|
|
variable npeaks = NumberByKey("npeaks", param, "=", ";")
|
|
variable dummy = nan
|
|
|
|
prompt rngl, "range low"
|
|
prompt rngh, "range high"
|
|
prompt pos1, "position 1"
|
|
prompt wid1, "width 1"
|
|
prompt shp1, "shape 1"
|
|
prompt pos2, "position 2"
|
|
prompt wid2, "width 2"
|
|
prompt shp2, "shape 2"
|
|
prompt pos3, "position 3"
|
|
prompt wid3, "width 3"
|
|
prompt shp3, "shape 3"
|
|
prompt pos4, "position 4"
|
|
prompt wid4, "width 4"
|
|
prompt shp4, "shape 4"
|
|
prompt npeaks, "number of peaks"
|
|
prompt dummy, "(not used)"
|
|
|
|
doprompt "voigt4_reduction reduction parameters (1/2)", rngl, rngh, npeaks, dummy, pos1, pos2, pos3, pos4
|
|
if (v_flag == 0)
|
|
param = ReplaceNumberByKey("rngl", param, rngl, "=", ";")
|
|
param = ReplaceNumberByKey("rngh", param, rngh, "=", ";")
|
|
param = ReplaceNumberByKey("npeaks", param, npeaks, "=", ";")
|
|
param = ReplaceNumberByKey("pos1", param, pos1, "=", ";")
|
|
param = ReplaceNumberByKey("pos2", param, pos2, "=", ";")
|
|
param = ReplaceNumberByKey("pos3", param, pos3, "=", ";")
|
|
param = ReplaceNumberByKey("pos4", param, pos4, "=", ";")
|
|
|
|
doprompt "voigt4_reduction reduction parameters (2/2)", wid1, shp1, wid2, shp2, wid3, shp3, wid4, shp4
|
|
if (v_flag == 0)
|
|
param = ReplaceNumberByKey("wid1", param, wid1, "=", ";")
|
|
param = ReplaceNumberByKey("shp1", param, shp1, "=", ";")
|
|
param = ReplaceNumberByKey("wid2", param, wid2, "=", ";")
|
|
param = ReplaceNumberByKey("shp2", param, shp2, "=", ";")
|
|
param = ReplaceNumberByKey("wid3", param, wid3, "=", ";")
|
|
param = ReplaceNumberByKey("shp3", param, shp3, "=", ";")
|
|
param = ReplaceNumberByKey("wid4", param, wid4, "=", ";")
|
|
param = ReplaceNumberByKey("shp4", param, shp4, "=", ";")
|
|
endif
|
|
endif
|
|
|
|
return v_flag
|
|
end
|
|
|
|
|
|
/// apply the Shockley_anglefit function to a single image
|
|
///
|
|
/// useful for testing or manual processing
|
|
/// since the Shockley_anglefit function cannot be called from the command line directly.
|
|
///
|
|
/// @param branch -1 or +1: select negative (positive) angles for the fit interval, respectively
|
|
///
|
|
function test_shockley_anglefit(image, branch)
|
|
wave image
|
|
variable branch
|
|
|
|
string param = ""
|
|
param = ReplaceStringByKey("branch", param, num2str(branch), "=", ";")
|
|
|
|
string s_branch
|
|
if (branch >= 0)
|
|
s_branch = "p"
|
|
else
|
|
s_branch = "n"
|
|
endif
|
|
string pkpos_name = "saf_pkpos_" + s_branch
|
|
string pkwid_name = "saf_pkwid_" + s_branch
|
|
|
|
wave /wave results = shockley_anglefit(image, param)
|
|
duplicate results[0], $pkpos_name
|
|
duplicate results[1], $pkwid_name
|
|
end
|
|
|
|
function prompt_Shockley_anglefit(param)
|
|
string ¶m
|
|
|
|
variable branch = NumberByKey("branch", param, "=", ";")
|
|
|
|
prompt branch, "Branch (-1 or +1)"
|
|
|
|
doprompt "Shockley_anglefit_reduction Parameters", branch
|
|
if (v_flag == 0)
|
|
param = ReplaceNumberByKey("branch", param, branch, "=", ";")
|
|
endif
|
|
|
|
return v_flag
|
|
end
|
|
|
|
/// data reduction function for analysing the Cu(111) Shockley surface state.
|
|
///
|
|
/// do curve fitting of one branch of the surface state.
|
|
/// the result is peak position versus energy.
|
|
///
|
|
/// TODO: this function contains hard-coded parameters. please generalize as necessary.
|
|
///
|
|
/// @param source scienta detector image, energy axis along X, angle axis along Y.
|
|
/// two-dimensional intensity distribution (image).
|
|
/// the scales are carried over to the result waves.
|
|
/// for correct weighting and error estimation it is important
|
|
/// that the source wave contains actual counts (Poisson statistics).
|
|
///
|
|
/// @param param (in, out) semicolon-separated key=value list of processing parameters.
|
|
/// this is a pass-by-reference argument.
|
|
/// the following parameters are required.
|
|
/// @arg branch -1 or +1: select negative (positive) angles for the fit interval, respectively
|
|
///
|
|
/// @return free wave containing references of the result waves.
|
|
/// @arg result[0] peak position
|
|
/// @arg result[1] peak width (sigma)
|
|
///
|
|
threadsafe function /wave Shockley_anglefit(source, param)
|
|
wave source
|
|
string ¶m
|
|
|
|
variable nx = dimsize(source, 0)
|
|
variable ny = dimsize(source, 1)
|
|
|
|
// read parameters
|
|
variable branch = NumberByKey("branch", param, "=", ";")
|
|
|
|
// validate parameters
|
|
if (numtype(branch) != 0)
|
|
branch = +1
|
|
endif
|
|
|
|
// prepare output
|
|
make /wave /free /n=2 result_waves
|
|
make /free /n=0 dest1, dest2
|
|
result_waves[0] = dest1
|
|
result_waves[1] = dest2
|
|
adh5_setup_profile(source, dest1, 0)
|
|
adh5_setup_profile(source, dest2, 0)
|
|
dest1 = nan
|
|
dest2 = nan
|
|
|
|
// select angle range
|
|
// hard-coded for a particular measurement series
|
|
variable y0
|
|
variable y1
|
|
if (branch < 0)
|
|
y0 = -5
|
|
y1 = 0
|
|
else
|
|
y0 = 0
|
|
y1 = 5
|
|
endif
|
|
|
|
// select energy range
|
|
// start at the point of highest intensity and go up 0.45 eV
|
|
variable p0
|
|
variable p1
|
|
variable q0
|
|
variable q1
|
|
duplicate /free dest1, center
|
|
q0 = round((y0 - dimoffset(source, 1)) / dimdelta(source, 1))
|
|
q1 = round((y1 - dimoffset(source, 1)) / dimdelta(source, 1))
|
|
ad_profile_x_w(source, q0, q1, center)
|
|
wavestats /q/m=1 center
|
|
p0 = round((v_maxloc - dimoffset(source, 0)) / dimdelta(source, 0))
|
|
p1 = round((v_maxloc + 0.4 - dimoffset(source, 0)) / dimdelta(source, 0))
|
|
|
|
// prepare intermediate data buffer
|
|
make /n=(ny)/d/free profile
|
|
setscale /p x dimoffset(source,1), dimdelta(source,1), waveunits(source,1), profile
|
|
|
|
variable pp
|
|
for (pp = p0; pp <= p1; pp += 1)
|
|
profile = source[pp][p]
|
|
curvefit /Q /NTHR=1 /W=2 gauss profile(y0,y1)
|
|
wave w_coef
|
|
dest1[pp] = w_coef[2]
|
|
dest2[pp] = w_coef[3]
|
|
endfor
|
|
|
|
return result_waves
|
|
end
|
|
|
|
function scienta_norm(w, x): fitfunc
|
|
wave w
|
|
variable x
|
|
|
|
return w[0] * (x^2 - w[1]^2)
|
|
end
|
|
|
|
function /wave fit_scienta_ang_transm(data, params)
|
|
wave data // measured angular distribution (1D)
|
|
wave /z params
|
|
|
|
if (!waveexists(params))
|
|
make /n=12 /o params
|
|
endif
|
|
redimension /n=12/d params
|
|
|
|
variable h = wavemax(data) - wavemin(data)
|
|
params[0] = h / 2
|
|
params[1] = 0
|
|
params[2] = 7
|
|
params[3] = h / 4
|
|
params[4] = -23
|
|
params[5] = 4
|
|
params[6] = h / 4
|
|
params[7] = +23
|
|
params[8] = 4
|
|
params[9] = h / 2
|
|
params[10] = 0
|
|
params[11] = -0.001
|
|
FuncFit /NTHR=0 /q scienta_ang_transm params data
|
|
|
|
return params
|
|
end
|
|
|
|
threadsafe function scienta_ang_transm(w, x): fitfunc
|
|
// parameterized angular transmission function of the analyser
|
|
wave w // coefficients
|
|
// w[0] = amplitude gauss 1
|
|
// w[1] = position gauss 1
|
|
// w[2] = width gauss 1
|
|
// w[3] = amplitude gauss 2
|
|
// w[4] = position gauss 2
|
|
// w[5] = width gauss 2
|
|
// w[6] = amplitude gauss 3
|
|
// w[7] = position gauss 3
|
|
// w[8] = width gauss 3
|
|
// w[9] = constant background
|
|
// w[10] = linear background
|
|
// w[11] = quadratic background
|
|
variable x
|
|
|
|
make /free /n=4 /d w_int
|
|
w_int[0] = 0
|
|
w_int[1,] = w[p - 1]
|
|
variable pk1 = gauss1d(w_int, x)
|
|
w_int[1,] = w[p + 2]
|
|
variable pk2 = gauss1d(w_int, x)
|
|
w_int[1,] = w[p + 5]
|
|
variable pk3 = gauss1d(w_int, x)
|
|
w_int[0,2] = w[9 + p]
|
|
w_int[3] = 0
|
|
variable bg = poly(w_int, x)
|
|
|
|
return bg + pk1 + pk2 + pk3
|
|
end
|
|
|
|
function /wave fit_scienta_poly_bg(data, params, bgterms)
|
|
wave data // measured distribution (2D)
|
|
wave /z params // wave, will be redimensioned for the correct size
|
|
variable bgterms // number of terms in the polynomial background: 2, 3, or 4
|
|
|
|
if (!waveexists(params))
|
|
make /n=15 /o params
|
|
endif
|
|
redimension /n=15 /d params
|
|
|
|
variable wmax = wavemax(data)
|
|
variable wmin = wavemin(data)
|
|
params[0] = 0
|
|
params[1] = 7
|
|
params[2] = 1 / 2
|
|
params[3] = -23
|
|
params[4] = 4
|
|
params[5] = 1 / 2
|
|
params[6] = +23
|
|
params[7] = 4
|
|
params[8] = 1
|
|
params[9] = 0
|
|
params[10] = -0.001
|
|
params[11] = wmin
|
|
params[12] = (wmax - wmin) / dimdelta(data,1) / dimsize(data,1)
|
|
params[13] = 0
|
|
params[14] = 0
|
|
|
|
string h = "0000000000000"
|
|
if (bgterms < 3)
|
|
h = h + "1"
|
|
else
|
|
h = h + "0"
|
|
endif
|
|
if (bgterms < 4)
|
|
h = h + "1"
|
|
else
|
|
h = h + "0"
|
|
endif
|
|
FuncFitMD /NTHR=1 /q /h=h scienta_poly_bg params data
|
|
|
|
return params
|
|
end
|
|
|
|
function scienta_poly_bg(w, e, a): fitfunc
|
|
// polynomial background with
|
|
// parameterized angular transmission function of the analyser
|
|
wave w // coefficients
|
|
// angular transmission, varies with a
|
|
// amplitude of gauss 1 = 1 constant
|
|
// other peak amplitudes and linear terms are relative to gauss 1
|
|
// w[0] = position gauss 1
|
|
// w[1] = width gauss 1
|
|
// w[2] = amplitude gauss 2, relative to gauss 1
|
|
// w[3] = position gauss 2
|
|
// w[4] = width gauss 2
|
|
// w[5] = amplitude gauss 3, relative to gauss 1
|
|
// w[6] = position gauss 3
|
|
// w[7] = width gauss 3
|
|
// w[8] = constant term
|
|
// w[9] = linear term
|
|
// w[10] = quadratic term
|
|
// spectral background, varies with e
|
|
// w[11] = constant term
|
|
// w[12] = linear term
|
|
// w[13] = quadratic term
|
|
// w[14] = cubic term
|
|
variable a // detection angle
|
|
variable e // electron energy
|
|
|
|
make /free /n=4 /d w_int
|
|
variable p0 = 0
|
|
|
|
w_int[0] = 0
|
|
w_int[1] = 1
|
|
w_int[2,] = w[p0 + p - 2]
|
|
variable pk1 = gauss1d(w_int, a)
|
|
p0 += 2
|
|
|
|
w_int[1,] = w[p0 + p - 1]
|
|
variable pk2 = gauss1d(w_int, a)
|
|
p0 += 3
|
|
|
|
w_int[1,] = w[p0 + p - 1]
|
|
variable pk3 = gauss1d(w_int, a)
|
|
p0 += 3
|
|
|
|
w_int[0,2] = w[p0 + p]
|
|
w_int[3] = 0
|
|
variable base = poly(w_int, a)
|
|
p0 += 3
|
|
|
|
w_int[0,3] = w[p0 + p]
|
|
variable bg = poly(w_int, e)
|
|
|
|
return bg * (base + pk1 + pk2 + pk3)
|
|
end
|