mcutils/xray/dataReduction.py

228 lines
7.9 KiB
Python
Raw Normal View History

from __future__ import print_function,division
import logging as log
log.basicConfig(level=log.INFO)
import numpy as np
np.seterr(all='ignore')
from . import utils
import os
def subtractReferences(i,idx_ref, useRatio = False):
""" given data in i (first index is shot num) and the indeces of the
references (idx_ref, array of integers) it interpolates the closest
reference data for each shot and subtracts it (or divides it, depending
on useRatio = [True|False]; """
iref=np.empty_like(i)
idx_ref = np.squeeze(idx_ref)
idx_ref = np.atleast_1d(idx_ref)
if idx_ref.shape[0] == 1:
return i-i[idx_ref]
# references before first ref are "first ref"
iref[:idx_ref[0]] = i[idx_ref[0]]
# references after last ref are "last ref"
iref[idx_ref[-1]:] = i[idx_ref[-1]]
_ref = 0
for _i in range(idx_ref[0],idx_ref[-1]):
idx_ref_before = idx_ref[_ref]
idx_ref_after = idx_ref[_ref+1]
ref_before = i[idx_ref_before]
ref_after = i[idx_ref_after]
weight_before = float(_i-idx_ref_before)/(idx_ref_after-idx_ref_before)
weight_after = 1-weight_before
if weight_after == 1:
iref[_i] = ref_before
elif weight_before == 1:
iref[_i] = ref_after
else:
# normal reference for an on chi, the weighted average
iref[_i] = weight_before*ref_before + weight_after*ref_after
if _i>=idx_ref_after: _ref += 1
log.debug("For image %d : %d-%d"%(_i,idx_ref_before,idx_ref_after))
if useRatio:
i /= iref
else:
i -= iref
return i
def averageScanPoints(scan,data,isRef=None,lpower=None,useRatio=False,\
funcForEveraging=np.nanmean):
""" given scanpoints in 'scan' and corresponding data in 'data'
average all data corresponding the exactly the same scanpoint.
If the values in scan are coming from a readback, rounding might be
necessary.
No normalization is done inside this function
if isRef is provided must be a boolean array of the same shape as 'scan'
is there is at least one scanpoint marked as True, the data are
subtracted/divided by the interpolated reference
if lpower is provided the data is divided by it (how it is done depends
if one uses the ratio or not
funcForEveraging: is usually np.nanmean or np.nanmedian. it can be any
function that support axis=0 as keyword argument
"""
data = data.astype(np.float)
if isRef is None: isRef = np.zeros( data.shape[0], dtype=bool )
assert data.shape[0] == isRef.shape[0]
# subtract reference only is there is at least one
if isRef.sum()>0:
data = subtractReferences(data,np.argwhere(isRef), useRatio=useRatio)
else:
data = data.copy(); # create local copy
# normalize signal for laser intensity if provided
if lpower is not None:
lpower = utils.reshapeToBroadcast(lpower,data)
if useRatio is False:
data /= lpower
else:
data = (data-1)/lpower+1
scan_pos = np.unique(scan)
shape_out = [len(scan_pos),] + list(data.shape[1:])
ret = np.empty(shape_out)
err = np.empty(shape_out)
dataInScanPoint = []
chi2_0 = []
for i,t in enumerate(scan_pos):
shot_idx = (scan == t)
# select data for the scan point
data_for_scan = data[shot_idx]
dataInScanPoint.append( data_for_scan )
# calculate average
ret[i] = funcForEveraging(data_for_scan,axis=0)
# calculate std
noise = np.nanstd(data[shot_idx], axis = 0)
# calculate chi2 of different repetitions
chi2 = np.power( (data_for_scan - ret[i])/noise,2)
# sum over all axis but first
for _ in range(data_for_scan.ndim-1):
chi2 = np.nansum( chi2, axis=-1 )
# store chi2_0
chi2_0.append( chi2/ret[i].size )
# store error of mean
err[i] = noise/np.sqrt(shot_idx.sum())
ret = dict(scan=scan_pos,data=ret,err=err,chi2_0=chi2_0,
dataInScanPoint=dataInScanPoint)
ret = utils.data_storage(ret)
return ret
def calcTimeResolvedSignal(scan,data,reference="min",monitor=None,q=None,
saveTxt=True,folder="./",**kw):
"""
reference: can be 'min', 'max', a float|integer or an array of booleans
q : is needed if monitor is a tuple|list
monitor : normalization vector (if it is interpreted a list it is
interpreted as q-range normalization)
saveTxt : will save txt outputfiles (diff_av_*) in folder
other keywords are passed to averageScanPoints
"""
if reference == "min":
isRef = (scan == scan.min())
elif reference == "max":
isRef = (scan == scan.max())
elif isinstance(reference,(float,int)):
isRef = (scan == reference)
else:
isRef = reference
# normalize if needed
if monitor is not None:
if isinstance(monitor,(tuple,list)):
assert q is not None
assert data.ndim == 2
idx = (q>= monitor[0]) & (q<= monitor[1])
monitor = np.nanmedian(data[:,idx],axis=1)
monitor = utils.reshapeToBroadcast(monitor,data)
data = data/monitor
ret = averageScanPoints(scan,data,isRef=isRef,**kw)
if q is not None: ret["q"] = q
return ret
def errorFilter(data,threshold=4):
""" Very simple but effective filter for zinger like noise
The noise is in general lower when using nanmean instead than
nanmedian but nanmean does not filter out 'spikes'.
This filter mitigate this effect by using nanmedian for the q points
that have an higher than usual error (proxy for spikes ...)
tested with mi1245/dec2016/tiox/tiox1/run3
"""
assert data.data.ndim == 2
for iscan in range(len(data.dataInScanPoint)):
median_err = np.nanmedian(data.err[iscan])
idx = data.err[iscan] > threshold*median_err
data.data[iscan][idx] = \
np.nanmedian( data.dataInScanPoint[iscan][:,idx],axis=0 )
data.err[iscan][idx] = median_err
return data
def saveTxt(folder,data,delayToStr=True,info="",**kw):
""" data must be a data_storage instance """
q = data.q if "q" in data else np.arange(data.data.shape[-1])
# save one file with all average diffs
fname = "%s/diff_av_matrix.txt" % (folder)
utils.saveTxt(fname,q,data.data,headerv=data.scan,**kw)
for iscan,scan in enumerate(data.scan):
scan = utils.timeToStr(scan) if delayToStr else "%+10.5e" % scan
# try retreiving info on chi2
try:
chi2_0 = data.chi2_0[iscan]
info_delay = [ "# rep_num : chi2_0 ", ]
for irep,value in enumerate(chi2_0):
info_delay.append( "# %d : %.3f" % (irep,value))
info_delay = "\n".join(info_delay)
if info != '': info_delay = "%s\n%s" % (info,info_delay)
except AttributeError:
info_delay = info
# save one file per timedelay with average diff (and err)
fname = "%s/diff_av_%s.txt" % (folder,scan)
utils.saveTxt(fname,q,data.data[iscan],e=data.err[iscan],
info=info_delay,**kw)
# save one file per timedelay with all diffs for given delay
fname = "%s/diffs_%s.txt" % (folder,scan)
utils.saveTxt(fname,q,data.dataInScanPoint[iscan],info=info_delay,**kw)
def read_diff_av(folder,plot2D=False,save=None):
print("Never tested !!!")
basename = folder+"/"+"diff_av*"
files = glob.glob(basename)
files.sort()
if len(files) == 0:
print("No file found (basename %s)" % basename)
return None
temp = [os.path.basename(f[:-4]) for f in files]
delays = [f.split("_")[-1] for f in temp ]
diffav = collections.OrderedDict()
diffs = collections.OrderedDict()
for d,f in zip(delays,files):
data = np.loadtxt(f)
diffav[d]=data[:,1]
diffs[d] = np.loadtxt(folder+"/diffs_%s.dat"%d)[:,1:]
q =data[:,0]
t = np.asarray( [mc.strToTime(ti) for ti in delays] )
if plot2D:
idx = t>0
i = np.asarray( diffav.values() )
plt.pcolor(np.log10(t[idx]),q,i[idx].T)
plt.xlabel(r"$\log_{10}(t)$")
plt.ylabel(r"q ($\AA^{-1}$)")
it=np.asarray(diffav.values())
if save:
tosave = np.vstack( (q,it) )
header = np.hstack( (len(it),t) )
tosave = np.vstack( (header,tosave.T) )
np.savetxt(folder + "/all_diffs_av_matrix.txt",tosave)
return q,it,diffs,t