# Name: rebin-alg.py # Purpose: An algorithm to perform XAFS data reduction. # Author: Ken McIvor # # Copyright 2003 Illinois Institute of Technology # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL ILLINOIS INSTITUTE OF TECHNOLOGY BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # # Except as contained in this notice, the name of Illinois Institute # of Technology shall not be used in advertising or otherwise to promote # the sale, use or other dealings in this Software without prior written # authorization from Illinois Institute of Technology. import math import bisect import Numeric def grid(start, stop, stepSize): """Create an array which can be used as an abscissa for interpolation. Returns an array of regular values, increasing by 'stepSize', which go from 'start' to 'stop' inclusively. """ r = Numeric.arange(start, stop + stepSize, stepSize) while r[-1] > stop: r = r[:-1] if r[-1] < stop: r = Numeric.resize(r, (r.shape[0]+1, ) ) r[-1] = stop return r def rebin(data, eColumn, Eo, after, kstep, file='', msgStream=None): """XAFS data reduction. 'data' is a Numerical Python matrix (rank 2 array) which contains the XAFS data to be reduced. 'eColumn' is the column index of the energy values of each point. 'Eo' is the edge energy of the data. All points for which the energy is greater than 'Eo'+'after' are reduced to a new set of points in which the energy varies in equally-spaced steps in k-space, defined by 'kstep'. 'file' is the file name corresponding to this data set, and if specified will be used in any exceptions raised by this functions. If 'msgStream' is specified it must be an output stream (an object implementing a write() method), which will used to print status information. """ # if file is specified, reformat it for use in error messages if file: file = '[%s] ' % file # acquire the energy vector of the data and find its minimum and maximum E = data[:,eColumn] Min, Max = min(E), max(E) if Max <= Eo: raise RebinError, ( '%sno energy values larger than Eo (incomplete data set?)' % file) # start rebinning points at Eo+after start = Eo + after if Max <= start: raise RebinError, '%sthe value of "after" is too large' % file # find the index of the energy vector which corresponds to the start point startPoint = bisect.bisect_left(E, start) # slice the energy vector and data matrix to get the points for rebinning eRange = E[startPoint:] dataRange = data[startPoint:, :] if msgStream: msgStream.write('%s%d points in E from %.4f to %.4f (Eo = %.4f)\n' % (file, eRange.shape[0], start, Max, Eo)) def k(E, Eo=Eo): """Calculate K for some energy level E, using the edge energy argument passed to rebin(). """ return 0.512 * math.sqrt(E - Eo) # Generate the regular k-space abscissa that will be rebined to. # grid(start, end, step) returns a vector of values from 'start' to 'end' # by 'step', inclusive. kgrid = grid(k(start), k(Max), kstep) if msgStream: msgStream.write( '%srebinning to %d points in k-space from %.4f to %.4f\n' % (file, kgrid.shape[0], kgrid[0], kgrid[-1])) # newData is matrix which is BINCOUNT-1 rows long and contains the same # number of columns as the data matrix. It accumulates the sums of the # points which fall in each bin newData = Numeric.zeros((kgrid.shape[0]-1, data.shape[1]), data.typecode()) # binCounts is a vector of BINCOUNT elements, which accumulates the number # of points that fall in each bin binCounts = Numeric.zeros( (newData.shape[0],) , data.typecode()) for i in range(0, eRange.shape[0]): # locate the index of the bin in which the current energy level falls where = bisect.bisect_right(kgrid, k(eRange[i])) - 1 if where == newData.shape[0]: where = where - 1 # bin the data newData[where] += dataRange[i] binCounts[where] += 1 # check that all of the bins contain data for i in range(0, newData.shape[0]): if binCounts[i] == 0: raise RebinError, ('%sbin %d (%.4f < k < %.4f) is empty' % (file, i, kgrid[i], kgrid[i+1])) # average the binned points for i in range(0, newData.shape[0]): newData[i,:] /= binCounts[i] # The length of E[0:startPoint] is the number of points which were not # rebinned. The result matrix will contain that many rows plus one row for # each bin. Len = E[0:startPoint].shape[0] + newData.shape[0] # create a new matrix to hold the final result result = Numeric.zeros((Len, data.shape[1]), data.typecode()) # copy unrebinned and rebinned data into the result matrix result[0:startPoint, :] = data[0:startPoint, :] result[startPoint:, :] = newData return result