#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 &param

	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 &param
	
	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 &param

	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 &param
	
	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 &param

	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 &param
	
	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 &param

	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 &param
		
	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


/// 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 &param
	
	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 &param

	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 &param

	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 &param
		
	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
