From 1b0d618f16aca79952f67e50544979b1cc2c981f Mon Sep 17 00:00:00 2001 From: Laurent Mercadier <laurent.mercadier@xfel.eu> Date: Fri, 12 Jul 2019 10:24:53 +0200 Subject: [PATCH] adds knife_edge function --- __init__.py | 1 + knife_edge.py | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 knife_edge.py diff --git a/__init__.py b/__init__.py index 23e93fc..654b00f 100644 --- a/__init__.py +++ b/__init__.py @@ -1,3 +1,4 @@ from ToolBox.Load import * from ToolBox.xgm import * from ToolBox.XAS import * +from ToolBox.knife_edge import * diff --git a/knife_edge.py b/knife_edge.py new file mode 100644 index 0000000..811502c --- /dev/null +++ b/knife_edge.py @@ -0,0 +1,66 @@ +""" Toolbox for SCS. + + Various utilities function to quickly process data measured at the SCS instruments. + + Copyright (2019) SCS Team. +""" +import matplotlib.pyplot as plt +import numpy as np +from scipy.special import erfc +from scipy.optimize import curve_fit + +def knife_edge(nrun, axisKey='scannerX', signalKey='FastADC4peaks', p0=None, plot=False): + ''' Calculates the beam radius at 1/e^2 from a knife-edge scan by fitting with erfc + function: f(a, u) = a*erfc(u) or f(a, u) = a*erfc(-u) where u = sqrt(2)*(x-x0)/w0 + with w0 the beam radius at 1/e^2 and x0 the beam center. + Inputs: + nrun: xarray Dataset containing the detector signal and the motor position. + axisKey: string, key of the axis against which the knife-edge is performed. + signalKey: string, key of the detector signal. + p0: list, initial parameters used for the fit: x0, w0, a. If None, a beam + radius of 100 um is assumed. + plot: bool: If True, plots the data and the result of the fit. + Outputs: + ndarray with beam radius at 1/e^2 in mm and standard error from the fit + in mm. + ''' + def integPowerUp(x, x0, w0, a): + return a*erfc(-np.sqrt(2)*(x-x0)/w0) + + def integPowerDown(x, x0, w0, a): + return a*erfc(np.sqrt(2)*(x-x0)/w0) + + #get the number of pulses per train from the signal source: + dim = nrun[signalKey].dims[1] + #duplicate motor position values to match signal shape + #this is much faster than using nrun.stack() + positions = np.repeat(nrun[axisKey].values, + len(nrun[dim])).astype(nrun[signalKey].dtype) + #sort the data to decide which fitting function to use + sortIdx = np.argsort(positions) + positions = positions[sortIdx] + intensities = nrun[signalKey].values.flatten()[sortIdx] + if intensities[0] > intensities[-1]: + func = integPowerDown + else: + func = integPowerUp + + if p0 is None: + p0 = [np.mean(positions), 0.1, np.max(intensities)/2] + popt, pcov = curve_fit(func, positions, intensities, p0=p0) + print('w0 = (%.1f +/- %.1f) um'%(popt[1]*1e3, pcov[1,1]**0.5*1e3)) + + if plot: + xfit = np.linspace(positions.min(), positions.max(), 1000) + yfit = func(xfit, *popt) + plt.figure(figsize=(7,4)) + plt.scatter(positions, intensities, color='C1', label='exp', s=2, alpha=0.01) + plt.plot(xfit, yfit, color='C4', + label=r'fit $\rightarrow$ $w_0=$(%.1f $\pm$ %.1f) $\mu$m'%(popt[1]*1e3, pcov[1,1]**0.5*1e3)) + leg = plt.legend() + for lh in leg.legendHandles: + lh.set_alpha(1) + plt.ylabel(signalKey) + plt.xlabel(axisKey + '-position [mm]') + plt.tight_layout() + return np.array(popt[1], pcov[1,1]**0.5) \ No newline at end of file -- GitLab