No GUI: data nodes¶
Experimental signals¶
One design goal of ParSeq is the ability of data import into any data node,
not only into the head node(s) of a pipeline. Nonetheless, a few nodes of the
ParSeq-XAS pipeline are considered to be input nodes – those listed in this
section – where experimental signals are loaded. The nodes NodeIF and
NodeIE do the same job and can receive single or multiple fluorescence
or electron yield signals. The idea of the splitting into two separate nodes is
to reduce the need for data format re-definition when switching between
multi-channel and single-channel signals. The node NodeIXES receives
a 2D intensity array of a HERFD scan.
Note
Please see the general data format definitions of ParSeq. Note that any data channel can be defined by a Python expression. Therefore, if ‘eref’ does not exist as an array in an hdf5 file or a column in a column file, it is possible to build it from other arrays or columns as e.g. np.log(Col5/Col4).
- class parseq_XAS.XAS_nodes.NodeIT(widgetClasses=[])¶
- The following three arrays are required:
- eraw: the original energy axis in eV; it will later transform to e,i0: the I₀ signal – intensity upstream of the sample,itr: the transmitted intensity.
An optional array eref can be given to be used for energy calibration. If used, it should contain an absorption spectrum of a foil.
- class parseq_XAS.XAS_nodes.NodeIF(widgetClasses=[])¶
- The following three arrays are required:
- eraw: the original energy axis in eV; it will later transform to e,i0: the I₀ signal – intensity upstream of the sample,ify: the partial fluorescence yield signal.
An optional array eref can be given to be used for energy calibration. If used, it should contain an absorption spectrum of a foil.
Note
Please see the general data format definitions of ParSeq. Multiple fluorescence channels can either be loaded as a sum or a collection of individual spectra. The data format can be defined by a Python list or a list comprehension expression, e.g
[f'Col{n}' for n in range(10, 17)]. One can also insert it into thesum()function.
- class parseq_XAS.XAS_nodes.NodeIE(widgetClasses=[])¶
- The following three arrays are required:
- eraw: the original energy axis in eV; it will later transform to e,i0: the I₀ signal – intensity upstream of the sample,iey: the partial fluorescence yield signal.
An optional array eref can be given to be used for energy calibration. If used, it should contain an absorption spectrum of a foil.
- class parseq_XAS.XAS_nodes.NodeIXES(widgetClasses=[])¶
- The following three arrays are required:
- eraw: the original energy axis in eV; it will later transform to e,i0: the I₀ signal – intensity upstream of the sample,xes2D: the 2D intensity array in (scan axis (DCM energy) vs meridional detector pixel) coordinates. The number of rows of xes2D must be equal to the length of eraw and i0.
An optional array eref can be given to be used for energy calibration. If used, it should contain an absorption spectrum of a foil.
Absorption coefficient µ(E)¶
- class parseq_XAS.XAS_nodes.NodeMu(widgetClasses=[])¶
- The following two arrays are required:
- eraw: the original energy axis in eV; it will transform to e,muraw: the original absorption coefficient; it will transform to mu.
An optional array eref can be given to be used for energy calibration. If used, it should contain an absorption spectrum of a foil.
EXAFS function χ(k)¶
- class parseq_XAS.XAS_nodes.NodeChi(widgetClasses=[])¶
- The following two arrays are required:
- k: the photoelectron wavenumber axis as an equidistant mesh,chi: the EXAFS function multiplied by kʷ.
Fourier-transformed EXAFS function χ(r)¶
- class parseq_XAS.XAS_nodes.NodeFT(widgetClasses=[])¶
- The following four arrays are required:
- r: the uncorrected distance axis as an equidistant mesh,ft: the absolute Fourier-transformed EXAFS function multiplied by kʷ.ftr: the real Fourier-transformed EXAFS function multiplied by kʷ.fti: the imag Fourier-transformed EXAFS function multiplied by kʷ.
Fourier-filtered EXAFS function χ̃(k)¶
- class parseq_XAS.XAS_nodes.NodeBFT(widgetClasses=[])¶
- The following two arrays are required:
- bftk: the photoelectron wavenumber axis as an equidistant mesh,bft: the EXAFS function multiplied by kʷ.
No GUI: data transformations¶
Make absorption coefficient¶
In all cases, the obtained absorption coefficient is unnormalized, i.e. defined down to an unknown multiplicative constant.
- class parseq_XAS.XAS_transforms.MakeTrMu(fromNode, toNode)¶
The transmission absorption coefficient is calculated as \(µ_{tr}(E)=\log(I_0/I_{tr})\).
- class parseq_XAS.XAS_transforms.MakeFYMu(fromNode, toNode)¶
- class parseq_XAS.XAS_transforms.MakeTEYMu(fromNode, toNode)¶
These versions of \(µ(E)\) (in fluorescence or electron yield) are obtained by dividing the measured signal by \(I_0\). In addition, the result is multiplied by \(\max(I_0)\) in order to keep the physical meaning and units of the original signal.
- class parseq_XAS.XAS_transforms.MakeHERFD(fromNode, toNode)¶
The 2D count array xes2D (the scan axis (DCM energy e) vs meridional detector pixel) is summed within a given band ROI horizontally (i.e. the columns are summed) to get a 1D count array of the length of e. This array is divided by \(I_0\) and multiplied by \(\max(I_0)\) to get the HERFD absorption coefficient in count units.
Make EXAFS function χ(k)¶
- class parseq_XAS.XAS_transforms.MakeChi(fromNode, toNode)¶
This transformation is the largest by the number of code lines (but still only ~600 Python lines) and by computation load. It comprises several sub-steps explained below.
Edge position \(E_0\)¶
The absorption coefficient is differentiated and optionally smoothened. \(E_0\) is found within the search interval e0Where (two relative fractions of the spectrum energy range) by one of the three methods selected by e0Method: (0) simple derivative maximum, (1) the derivative maximum of interpolating cubic spline, (2, default) the center of mass of interpolating cubic spline.
Energy calibration¶
The energy axis can be calibrated to match the found \(E_0\) to a tabulated value. The calibration can be done by a Bragg angle offset (default), a lattice parameter offset or as a constant energy offset (eShiftKind = 0, 1, 2). The shift can be applied by moving \(E_0\) to a target value (eCalibrationMethod = 0) or applying a given shift to \(E_0\) (eCalibrationMethod = 1). The latter option is useful when copying a calibration shift of \(E_0\) found for a metal foil to other spectra from the same beam time measurements.
If a calibration foil was measured simultaneously with the sample, two calibration scenarios are possible. (1) The two spectra, of the sample and of the foil, are loaded separately by modifying their format definitions. The foil spectrum is calibrated and then its \(E_0\) shift is copied to the sample spectrum. (2) Only the sample spectrum is loaded and in the \(E_0\) determination the derivative of the reference foil spectrum is used (useERefCurve = True).
Data rebinning¶
If the energy scan was done in a continuous way with a constant slew rate, the resulting spectrum is typically strongly over sampled. This means that several experimental points fall into one \(dk\) interval (the EXAFS function will be defined on a constant \(dk\) mesh, see below). Even more, as the k-space and the E-space are quadratically related, the energy intervals corresponding to \(dk\) become linearly (with k) larger to the end of the spectrum, so more and more experimental points fall into one \(dk\) interval. On converting from E-space to k-space, a kind of interpolation will be used, which only uses the local interpolation polynomial between the matching experimental points. A way of using all experimental data, called rebinning, consists of summing the experimental points belonging to one \(dk\) interval before doing the interpolation. The user provides rebinRegions dictionary that defines regions – pre-edge, edge, post-edge and EXAFS – by setting their borders (splitters) and bin sizes (deltas). The defined bins are fed to
numpy.histogram()to perform the actual rebinning. The number of original bins and the redefined bins per region are reported in nbinOriginal and binDistrNew.Pre-edge background¶
The pre-edge background \(µ_b(E)\) is constructed by polynomial interpolation over the region specified by preedgeWhere. The polynomial law is given by preedgeExps.
For absorption spectra measured in transmission mode, usually a Victoreen polynomial \(aE^{-3}+bE^{-4}\) or a modified Victoreen polynomial \(aE^{-3}+b\) is utilized, where the coefficients are found by the least-squares fit from
numpy.polynomial.Polynomialclass.For absorption spectra measured in fluorescence, background subtraction is frequently not needed. More frequently a constant shift is sufficient (with only power “0”). Sometimes the spectra exhibit a net growth with energy, which can be approximated by a linear law (powers “0” and “1”).
Self-absorption correction¶
See the description of self-absorption correction, including its history, here.
The correction is defined by a dictionary selfAbsorptionCorrectionDict that specifies the following keys: corrChemFormula, corrDataTable – one of (“Henke”, “BrCo”, “Chantler”, “Chantler total”), corrCalibEnergy – energy at which the calibration constant C is calculated, corrFluoEnergy – the fluorescence line energy, corrPhiDeg, corrThetaDeg, corrTauDeg – the observation angles φ, θ and τ in degrees, corrFormula – ‘thick’ or another str, and corrThickness – the edge jump for a ‘thin’ case.
If the specified material has no absorption edge within the spectrum range, this is reported in selfAbsorptionCorrectionDict[‘corrJumpStr’]. Otherwise, it contains a str of the tabulated edge jump.
Post-edge background and edge normalization¶
The post-edge background is needed for defining the edge normalization.
The post-edge background is constructed by polynomial interpolation over the region specified by postedgeWhere. The polynomial law is given by postedgeExps. The arguments for choosing the polynomial are the same as for the pre-edge background.
The obtained edge height is reported in edgeJump.
The post-edge background can also be used to construct a “flat” view of the absorption coefficient, where the post-edge part is seen horizontal. This can be useful for the linear combination fit and the function fit of µ(E).
Atomic-like absorption coefficient µ₀¶
The definition of EXAFS function (see below) includes µ₀ – an artificial absorption coefficient that the material would have without EXAFS wiggles, i.e. when the central atom would be isolated without neighbors. The absorption coefficient of such a single atom gas is most typically not possible to measure, but also the electronic state of the atom in a gas state would differ from that in a solid or liquid, so that µ₀ has to be constructed artificially. µ₀ is believed to be a smooth function of energy and therefore is usually constructed as a spline.
Two methods are offered for the spline creation: ‘through internal k-spaced knots’ and ‘smoothing spline’, mu0method 0 and 1. The method of a spline through knots is generally better, as it contains only low-frequency oscillations, so that χ(k) preserves all the true structural oscillations. This is not the case for the smoothing spline, where the resulting χ(k) partially loses its signal and transfers it toward µ₀, but this method is easier to use, it always “just works”.
Prior to constructing µ₀, a helping curve is built, “µ₀ prior”, that makes the final µ₀ resemble an absorption edge, optionally with a white line. µ₀ prior is first subtracted from µ before constructing a spline and then added again to the resulting spline.
In the first µ₀ method (‘through internal k-spaced knots’), a given number of knots (mu0knots) are equidistantly placed in k-space and an LSQ (Least SQuared) based fitting B-spline is calculated by
scipy.interpolate.LSQUnivariateSpline(). The difference µ₀ – µ₀ prior is optionally weighted with a \(k^w\) factor (\(w\) = mu0kpow). Additionally, a given number of the first knots can be set variable in height to automatically minimize the low-r portion (set by ftMinRange) of the FT EXAFS. The number of the varied knots (ftMinNKnots) is advised to be kept small (much smaller than the total number of knots) to make the minimization stable.The second µ₀ method (‘smoothing spline’) depends on a smoothing parameter (mu0smoothingFactor) that is set by examining the low-r FT. By comparing with the first µ₀ method, one can discover that the 1st FT peak height is always lower with the second method. This signal loss can be tolerated if it is smaller than the fitting error of the first shell coordination number.
k-mesh and χ(k)¶
Finally, the EXAFS function χ(k) is defined as:
\[χ(k) = \frac{µ - µ_b - µ_0}{µ_0 - µ_b}\]where
\[k = \sqrt{2m_e(E-E_0)} / \hbar\]The resulted χ(k) is interpolated onto the equidistant k-mesh defined by krange and dk and weighted by \(k^w\), where \(w\) is defined by kw.
Denoising¶
The curve can optionally be denoised by means of
scipy.signal.butter(). The two parameters, order and lowpass frequency are the first two parameters ofscipy.signal.butter(). The former one is directly it, and the latter is slightly modified: Wn = “lowpass frequency” × kmax. The calculated noise level is a normalized difference between the original and the denoised χ·kᵂ:\[N/S = \left( \sum_k(χ·k^w-χ(denoised)·k^w)^2 / \sum_k(χ(denoised)·k^w)^2 \right)^{1/2}.\]
Make Fourier-transformed EXAFS function χ(r)¶
- class parseq_XAS.XAS_transforms.MakeFT(fromNode, toNode)¶
The reason for setting χ(k) on a uniform grid is the use of the efficient Fast Fourier Transform (FFT) algorithm. ParSeq-XAS utilizes numpy function
fft.rfft()that computes the one-dimensional FFT for real input. Because the EXAFS FT has a doubled exponential power \(-2ikr\), not the usual \(-ikr\), the result is multiplied by \(dk/2\), not the usual \(dk\); the real-space spacing \(dr=π/(N\cdot dk)\). The real-space grid is given by numpy functionfft.rfftfreq(N, dk/π). The number of grid points \(N\) is a class variablenfftand equals here 4096.Before making FT, χ(k) is multiplied by a window function that is one of these choices: ‘none’, ‘box’, ‘linear-tapered’, ‘cosine-tapered’, ‘Gaussian-tapered’, as set by ftWindowKind.
Optionally, the zeroth frequency (here, distance \(r\)) can be removed by nulling the first integral of χ(k); this choice is controlled by forceFT0.
The resulting FT is cut at a selected rmax value, mainly for the plotting purpose.
Make back-Fourier-transformed EXAFS function χ̃(k)¶
- class parseq_XAS.XAS_transforms.MakeBFT(fromNode, toNode)¶
This class applies a window function, as per bftWindowKind, bftWindowRange and bftWindowWidth, and calculates Back Fourier Transform (BFT) by numpy’s
fft.irfft(). The resulted BFT is cut to the k range defined in the previous steps.
No GUI: data fits¶
Linear Combination Fit of µ(E)¶
- class parseq_XAS.XAS_fits.LCF_mu(node=None, widgetClass=None)¶
The Linear Combination Fit (LCF) fits a weighted sum of reference spectra to the spectrum of interest. Here, the array attribute flat (the version of µ that has a horizontal post-edge) of the involved data objects are used as ordinates and the attribute e is used as abscissa. The fit curve resides in a new array fitLCF as an attribute of the data container.
The fit creates an attribute lcf_params of the data container that is a list of dictionaries, a dictionary per reference spectrum, with the following items:
name: str, the alias of the reference spectrum,use: bool, switches the reference on/off,w: float, the weight of the reference,wBounds: 3-list, [min, max, Δ], Δ is only used by the GUI,If xVary is True (see below), these two entries are needed:dx: float, the energy shift of the reference,dxBounds: 3-list, [min, max, Δ] for the energy shift.The reference dictionary may have wtie and dxtie for tie expressions for the weight w and the energy shift dx. Tie expression is a string variable either starting with “fix” or starting with one of the three signs “=><” and followed by a Python expression of other weights accessed as “w[1]”, “w[2]”, “dx[1]” etc. It is possible to add metavariables with a user-defined name to be used in tie expressions. A variable can also be fixed by its min and max bounds: if min >= max, the value will be fixed at the value of min.
After the fit, the items wError and dxError contain the fitting errors.
The class variable xVary determines whether the reference spectra are allowed to vary their energy axis. This feature may be needed if the energy calibration of the involved spectra is not trustful.
One may add a pinhole fraction as a fitting variable, which makes sense only for transmission spectra.
The actual curve fitting is done by
scipy.optimize.curve_fit(). The attribute lcf_result of the data container will get the fit info returned bycurve_fit().
Function Fit of µ(E)¶
- class parseq_XAS.XAS_fits.FunctionFit_mu(node=None, widgetClass=None)¶
The Function Fit fits a Python expression that depends on a set of user variables to data. Here, the array attribute flat (the version of µ that has a horizontal post-edge) of the fitted data object is used as ordinate and the attribute e is used as abscissa. The fit curve resides in a new array fitFunc as an attribute of the data container.
The fit creates a few attributes of the data container:
ffit_formula: str, fit formula,
ffit_params: dictionary of fitting variables as keys,
ffit_xRange: 2-list [min, max] of energy limits.
The dictionary ffit_params is a dictionary of dictionaries of the following keys: value (float, the fit variable value), lim (2-list of min and max), tie (str, a tie expression) and error (float, the fitting error).
The actual curve fitting is done by
scipy.optimize.curve_fit(). The attribute ffit_result of the data container will get the fit info returned bycurve_fit().
EXAFS Fit of χ(k) and χ(r)¶
- class parseq_XAS.XAS_fits.EXAFSFit(node=None, widgetClass=None)¶
The EXAFS Fit fits a sum of EXAFS shells to data. The array attribute bft of the fitted data object is used as ordinate and the attribute bftk is used as abscissa. The fit curve resides in a new array bftfit as an attribute of the data container. Optionally, the fit can be done in the Fourier space; then the fit works with ft vs r and the fit curve is ftfit.
The fit creates a few attributes of the data container:
exafsfit_params: list of shell dictionaries,
exafsfit_aux: list of shell specifications – a path to the corresponding FEFF file and its descriptions,
exafsfit_kRange: 2-list [min, max]
exafsfit_k_use: bool, whether to fit in k-space
exafsfit_rRange: 2-list [min, max]
exafsfit_r_use: bool, whether to fit in r-space
The list exafsfit_params contains shell dictionaries, per shell. Each shell dictionary defines the 4 shell fitting parameters ‘r’, ‘n’, ‘s’, ‘e’ as keys and fitting parameter dictionaries as values. The fitting parameter dictionary defines the pairs ‘value’: float, ‘step’: float, ‘lim’: [min, max] and ‘error’: float. It may additionally have a (‘tie’: expression) entry.
The list exafsfit_params has an extra shell dictionary that contains an ‘s0’ fitting variable (multi-electron damping factor) and optional metavariables.
The actual curve fitting is done by
scipy.optimize.curve_fit(). The attribute exafsfit_result of the data container will get the fit info returned bycurve_fit().