########################
# FFT AUTO PHASE METHODS
########################
[docs]def autops(data, fn, p0=0.0, p1=0.0):
"""
Automated phase correction from NMRglue by https://github.com/jjhelmus
These functions provide support for automatic phasing of NMR data.
Automatic linear phase correction
Parameters
data : ndarray
Array of NMR data.
fn : str or function
Algorithm to use for phase scoring. Built in functions can be
specified by one of the following strings: "acme", "peak_minima"
p0 : float
Initial zero order phase in degrees.
p1 : float
Initial first order phase in degrees.
Returns
ndata : ndarray
Phased NMR data.
"""
import numpy as np
import scipy.optimize
if not callable(fn):
fn = {
'peak_minima': _ps_peak_minima_score,
'acme': _ps_acme_score,
}[fn]
opt = [p0, p1]
opt = scipy.optimize.fmin(fn, x0=opt, args=(data, ))
return ps(data, p0=opt[0], p1=opt[1]), opt[0], opt[1]
[docs]def _ps_acme_score(ph, data):
"""
Phase correction using ACME algorithm by Chen Li et al.
Journal of Magnetic Resonance 158 (2002) 164-168
Parameters
* pd : tuple, current p0 and p1 values
* data : ndarray, array of NMR data.
Returns
* score : float, value of the objective function (phase score)
"""
import numpy as np
stepsize = 1
phc0, phc1 = ph
s0 = ps(data, p0=phc0, p1=phc1)
data = np.real(s0)
# Calculation of first derivatives
ds1 = np.abs((data[1:]-data[:-1]) / (stepsize*2))
p1 = ds1 / np.sum(ds1)
# Calculation of entropy
p1[p1 == 0] = 1
h1 = -p1 * np.log(p1)
h1s = np.sum(h1)
# Calculation of penalty
pfun = 0.0
as_ = data - np.abs(data)
sumas = np.sum(as_)
if sumas < 0:
pfun = pfun + np.sum((as_/2) ** 2)
p = 1000 * pfun
return h1s + p
[docs]def _ps_peak_minima_score(ph, data):
"""
Phase correction using simple minima-minimisation around highest peak
This is a naive approach but is quick and often achieves reasonable
results. The optimisation is performed by finding the highest peak in the
spectra (e.g. TMSP) and then attempting to reduce minima surrounding it.
Parameters
* pd : tuple, current p0 and p1 values
* data : ndarray, array of NMR data.
Returns
* score : float, value of the objective function (phase score)
"""
phc0, phc1 = ph
s0 = ps(data, p0=phc0, p1=phc1)
data = np.real(s0)
i = np.argmax(data)
mina = np.min(data[i-100:i])
minb = np.min(data[i:i+100])
return np.abs(mina - minb)
[docs]def ps(data, p0=0.0, p1=0.0, inv=False):
"""
Linear phase correction
Parameters
data : ndarray
Array of NMR data.
p0 : float
Zero order phase in degrees.
p1 : float
First order phase in degrees.
inv : bool, optional
True for inverse phase correction
Returns
ndata : ndarray
Phased NMR data.
"""
import numpy as np
p0 = p0 * np.pi / 180. # convert to radians
p1 = p1 * np.pi / 180.
size = data.shape[-1]
apod = np.exp(1.0j * (p0 + (p1 * np.arange(size) / size))).astype(data.dtype)
if inv:
apod = 1 / apod
return apod * data
##############
# MUGUI AUX
##############
[docs]def derange(string,vmax,type='int'):
'''
derange(string)
reads string
assuming 2, 3, 4 or 5 csv values,
or space separated values, either int (default) or floats (specify type='float')::
5: start, stop, packe, last, packl
4: start, stop, last, packl (packe is 1)
3: start, stop, pack
2: start, stop
returns 2, 3, 4 or 5 floats or int, or
two negative values, if fails either::
validity check (stop>start and bin <stop-start)
check for sanity (last value less then a vmax)
'''
# print('In derange')
try:
try:
if type=='float':
values = [float(x) for x in string.split(',')]
else:
values = [int(x) for x in string.split(',')]
except:
if type=='float':
values = [float(x) for x in string.split('')]
else:
values = [int(x) for x in string.split('')]
if len(values)==5: # start, stop, packe, last, packl
if values[3]<values[1] or values[4]>values[3]-value[1] or values[2]>values[1]-value[0] or values[1]<values[0] or sum(n<0 for n in values)>0 or value[3]>vmax:
return -1,-1
return values[0],values[1],values[2],values[3],values[4] # start, stop, packe, last, packl
if len(values)==4: # start, stop, last, packl
if values[2]<values[1] or value[3]>value[2]-value[1] or values[1]<values[0] or sum(n<0 for n in values)>0 or value[2]>vmax:
return -1,-1
return values[0],values[1],1,values[2],values[3] # start, stop, packe, last, packl
elif len(values)==3: # start, stop, packe
if values[2]>values[1] or values[1]<values[0] or sum(n<0 for n in values)>0 or value[1]>vmax:
return -1,-1
return values[0],values[1],values[2] # start, stop, pack
elif len(values)==2: # start, stop
if values[1]<values[0] or sum(n<0 for n in values)>0 or value[1]>vmax:
return -1,-1
return values[0],values[1] # start, stop
except:
return -1,-1
[docs]def derange_int(string,vmax): # dismiss, derage takes both fload and int
'''
derange(string)
reads string
assuming 2, 3 or 4 csv values, integers,
or 2, 3 or 4 space separated values, integers or floats
returns 2, 3 or 4 int, or
two negative values, if the string does not
contain commas or spaces
'''
# print('In derange_int')
try:
try:
values = [int(x) for x in string.split(',')]
except:
if len(values)==5: # start, stop, packe, last, packl
if values[3]<values[1] or values[4]>values[3]-value[1] or values[2]>values[1]-value[0] or values[1]<values[0] or sum(n<0 for n in values)>0 or value[3]>vmax:
return -1,-1
return values[0],values[1],values[2],values[3],values[4] # start, stop, packe, last, packl
if len(values)==4: # start, stop, last, packl
if values[2]<values[1] or value[3]>value[2]-value[1] or values[1]<values[0] or sum(n<0 for n in values)>0 or value[2]>vmax:
return -1,-1
return values[0],values[1],1,values[2],values[3] # start, stop, packe, last, packl
elif len(values)==3: # start, stop, packe
if values[2]>values[1] or values[1]<values[0] or sum(n<0 for n in values)>0 or value[1]>vmax:
return -1,-1
return values[0],values[1],values[2] # start, stop, pack
elif len(values)==2: # start, stop
if values[1]<values[0] or sum(n<0 for n in values)>0 or value[1]>vmax:
return -1,-1
return values[0],values[1] # start, stop
except:
return -1,-1,
[docs]def derun(string):
'''
parses string, producing a list of runs;
expects comma separated items
looks for 'l','l:m','l+n+m'
where l, m, n are integers
rejects all other characters
returns a list of lists of integer
'''
s = []
try:
# systematic str(int(b[])) to check that b[] ARE integers
for b in string.split(','): # csv
kcolon = b.find(':') # ':' and '+' are mutually exclusive
kplus = b.find('+')
if kcolon>0: # value might be a range
if int(b[:kcolon])>int(b[kcolon+1:]):
errmsg='typo!'
for j in range(int(b[:kcolon]),int(b[kcolon+1:])+1):
s.append([str(j)]) # strings
elif kplus>0:
ss = []
k0 = 0
while kplus>0: # str(int(b[]))
ss.append(int(b[k0:kplus]))
k0 = kplus+1
kplus = b.find('+',k0)
ss.append(int(b[k0:]))
s.append([str(q) for q in ss])
else:# value should be an integer
int(b) # produces an Error if b is not an integer
s.append([b]) # added as a string
except Exception as e:
s = []
errmsg = e
if 'errmsg' not in locals():
errmsg = None
return s, errmsg
[docs]def findall(p, s):
'''Yields all the positions of
the pattern p in the string s.
Used by translate.
'''
i = s.find(p)
while i != -1:
yield i
i = s.find(p, i+1)
[docs]def find_nth(haystack, needle, n):
'''
Finds nth needle in haystack
Returns its first occurrence (0 if not present)
Used by ?
'''
start = haystack.rfind(needle)
while start >= 0 and n > 1:
start = haystack.rfind(needle, 1, start-1)
n -= 1
return start
[docs]def get_grouping(groupcsv):
"""
name = 'forward' or 'backward'
* grouping(name) is an np.array wth detector indices
* group.value[k] for k=0,1 is a shorthand csv like '1:3,5' or '1,3,5' etc.
* index is present mugui.mainwindow.selected_index
* out is mugui._output_ for error messages
returns
* grouping, group, index
group and index are changed only in case of errors
"""
import numpy as np
# two shorthands: either a list, comma separated, such as 1,3,5,6
# or a pair of integers, separated by a colon, such as 1:3 = 1,2,3
# only one column is allowed, but 1, 3, 5 , 7:9 = 1, 3, 5, 7, 8, 9
# or 1:3,5,7 = 1,2,3,5,7 are also valid
# get the shorthand from the gui Text
groupcsv = groupcsv.replace('.',',') # can only be a mistake: '.' means ','
try:
if groupcsv.find(':')==-1:
# colon not found, csv only
grouping = np.array([int(s) for s in groupcsv.split(',')])
else:
# colon found
if groupcsv.find(',')+groupcsv.find(':')==-2:
grouping = np.array([int(groupcsv)])
elif groupcsv.find(',')+1: # True if found, False if not found (not found yields -1)
firstcomma = groupcsv.index(',')
lastcomma = groupcsv.rindex(',')
if firstcomma < groupcsv.find(':'): # first read csv then range
partial = np.array([int(s) for s in groupcsv[:lastcomma].split(',')])
fst = int(groupcsv[lastcomma:grouping.find(':')])
lst = int(groupcsv[groupcsv.find(':')+1:])
grouping[name] = np.concatenate((partial,arange(fst,lst+1,dtype=int)))
else: # first read range then csv
partial = np.array([int(s) for s in groupcsv[:lastcomma].split(',')])
fst = int(groupcsv[:groupcsv.find(':')])
lst = int(groupcsv[groupcsv.find(':')+1:firstcomma])
grouping = np.concatenate((np.arange(fst,lst+1,dtype=int),partial))
else: # only range
fst = int(groupcsv[:groupcsv.find(':')])
lst = int(groupcsv[groupcsv.find(':')+1:])
grouping = np.arange(fst,lst+1,dtype=int)
grouping -=1 # python starts at 0
except:
grouping = np.array([-1])
return grouping
[docs]def get_title(run):
'''
form standard psi title
'''
return '{} {} {} {}'.format(run.get_sample(),run.get_field(),run.get_orient(),run.get_temp())
[docs]def muvalid(string,out,index):
'''
parse function
CHECK WITH MUCOMPONENT, THAT USES A DIFFERENT SCHEME
accepted functions are RHS of agebraic expressions of parameters p[i], i=0...ntot
'''
import re
pattern = re.compile(r"\p\[(\d+)\]") # find all patterns p[*] where * is digits
test = pattern.sub(r"a",string) # substitute "a" to "p[*]" in s
# strindices = pattern.findall(string)
# indices = [int(strindices[k]) for k in range(len(strindices))] # in internal parameter list
# mindices = ... # produce the equivalent minuit indices
valid = True
message = ''
try:
safetry(test) # should select only safe use (although such a thing does not exist!)
except Exception as e:
with out:
print('function: {}. Tested: {}. Wrong or not allowed syntax: {}'.format(string,test,e))
index = 3
valid = False
return valid
[docs]def muvaluid(string):
'''
Run suite fits: muvaluid returns True/False
* checks the syntax for string function
corresponding to flag='l'. Meant for pars
displaying large changes across the run suite,
requiring different migrad start guesses::
# string syntax: e.g. "0.2*3,2.*4,20."
# means that for the first 3 runs value = 0.2,
# for the next 4 runs value = 2.0
# from the 8th run on value = 20.0
'''
try:
value_times_list = string.split(',')
last = value_times_list.pop()
for value_times in value_times_list:
value,times = value_times.split('*')
dum, dum = float(value),int(times)
dum = float(last)
return True
except:
return False
[docs]def muvalue(lrun,string):
'''
Run suite fits:
muvalue returns the value
for the nint-th parameter of the lrun-th run
according to string (corresponding flag='l').
Large parameter change across the run suite
requires different migrad start guesses.
Probably broken!
'''
# string syntax: e.g. "0.2*3,2.*4,20."
# means that for the first 3 runs value = 0.2,
# for the next 4 runs value = 2.0
# from the 8th run on value = 20.0
value = []
for value_times in string.split(','):
try: # if value_times contains a '*'
value,times = value_times.split('*')
for k in range(int(times)):
value.append(float(value))
except: # if value_times is a single value
for k in range(len(value),lrun):
value.append(float(value_times))
# cannot work! doesn't check for syntax, can be broken; this returns a list that doesn't know about lrun
return value[lrun]
[docs]def muzeropad(runs,out):
'''
runs is a string containing the run number, out in mugui is _output_, Textarea for printing error messages
Utility of the suite tab, not a method.
Future:
1) determine how many leading zeros for padding
read a file from data dir
check number of digits before '.'
count number of digits in run
zero pad
now:
0) zeroth version pads a fixed number of zeros to 4 digits
'''
zeros='0000'
if len(runs)<len(zeros):
return zeros[:len(zeros)-len(runs)]+runs
elif len(runs)==len(zeros):
return runs
else:
with out:
print('Too long run number!')
return []
[docs]def norun_msg(out):
'''
Prints error message when trying to process without any data loaded
'''
with out:
print('No run loaded yet! Load one first (select suite tab).')
[docs]def plotile(x,xdim=0,offset=0):
'''
Produces a tiled plot e.g.
::
x.shape = (1,1000)
y.shape = (4,1000)
xt = plotile(x,4)
yt = plotile(y,offset=0.1)
'''
# x is an array(x.shape[0],x.shape[1])
# xoffset is a step offset
# xdim = x.shape[0] if xdim == 0 else xdim
# each row is shifted by xoffset*n, where n is the index of the row
#
#
from copy import deepcopy
from numpy import tile, arange
xt = deepcopy(x)
if xdim != 0: # x is a 1D array, must be tiled to xdim
xt = tile(xt,(int(xdim),1))
if offset != 0:
xt += tile(offset*arange(xt.shape[0]),(x.shape[1],1)).transpose()
return xt
[docs]def rebin(x,y,strstp,pack,e=None):
'''
* x,y[,e] are 2D arrays to be rebinned
* pack is the rebinning factor, e.g it returns::
xb = array([x[0:pack].sum()/pack])
* strstp = [start,stop] is a list of indices
rebinning is done on slices of x,y[,e]
such as x[start:stop]
use either::
xb,yb = rebin(x,y,strstp,pack)
or::
xb,yb,eyb = rebin(x,y,strstp,pack,ey) # the 5th is y error
Works also with pack = 1
Works for 1d array x and 2D ndarrays y, ey returning 2D arrays
xb, yb, eb, e.g.::
xb.shape = (1,25000),
yb.shape = (nruns,25000)
'''
from numpy import floor, sqrt,empty, array
start,stop = strstp
if pack==1: # no rebinning, just a slice of 2D arrays
xx = x[:,start:stop]
yy = y[:,start:stop]
# print('in rebin: shape xx {}, yy {}'.format(xx.shape,yy.shape))
if e is None:
return xx,yy
else:
ee = e[:,start:stop]
return xx,yy,ee
else:
m = int(floor((stop-start)/pack)) # length of rebinned xb
mn = m*pack # length of x slice
xx =x[:,start:start+mn] # slice of the first 2D array
xx = xx.reshape(m,pack) # temporaty 2d array
xb = array([xx.sum(1)/pack]) # rebinned first ndarray
nruns = y.shape[0] # number of runs
yb = empty((nruns,m))
if e is not None:
eb = empty((nruns,m))
for k in range(nruns): # each row is a run
yy = y[k][start:start+mn] # slice row
yy = yy.reshape(m,pack) # temporaty 2d
yb[k] = yy.sum(1)/pack # rebinned row
if e is not None:
ey = e[k][start:start+mn] # slSice row
ey = ey.reshape(m,pack) # temporaty 2d
eb[k] = sqrt((ey**2).sum(1))/pack # rebinned row
if e is not None:
return xb,yb,eb
else:
return xb,yb
[docs]def safetry(string):
'''
Used by muvalid
'''
from math import acos,asin,atan,atan2,ceil,cos,cosh,degrees,e,exp,floor,log,log10,pi,pow,radians,sin,sinh,sqrt,tan,tanh
safe_list = ['a','acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh', 'degrees', 'e',
'exp', 'floor', 'log', 'log10', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh']
# use the list to filter the local namespace
a = 0.3
safe_dict={}
for k in safe_list:
safe_dict[k]=locals().get(k)
# print(safe_dict[k])
return eval(string,{"__builtins__":None},safe_dict)
[docs]def set_bar(n,b):
'''
service to animate histograms
e.g. in the fit tab
extracted from matplotlib animate
histogram example
'''
from numpy import array, zeros, ones
import matplotlib.path as path
# get the corners of the rectangles for the histogram
left = array(b[:-1])
right = array(b[1:])
bottom = zeros(len(left))
top = bottom + n
nrects = len(left)
# here comes the tricky part -- we have to set up the vertex and path
# codes arrays using moveto, lineto and closepoly
# for each rect: 1 for the MOVETO, 3 for the LINETO, 1 for the
# CLOSEPOLY; the vert for the closepoly is ignored but we still need
# it to keep the codes aligned with the vertices
nverts = nrects*(1 + 3 + 1)
verts = zeros((nverts, 2))
codes = ones(nverts, int) * path.Path.LINETO
codes[0::5] = path.Path.MOVETO
codes[4::5] = path.Path.CLOSEPOLY
verts[0::5, 0] = left
verts[0::5, 1] = bottom
verts[1::5, 0] = left
verts[1::5, 1] = top
verts[2::5, 0] = right
verts[2::5, 1] = top
verts[3::5, 0] = right
verts[3::5, 1] = bottom
xlim = [left[0], right[-1]]
return verts, codes, bottom, xlim
[docs]def tlog_exists(path,run,out):
'''
check if tlog exists under various known filenames types
'''
import os
filename_psibulk = 'run_'+muzeropad(run,out)+'.mon' # add definitions for e.g. filename_isis
ok = os.path.exists(os.path.join(path,filename_psibulk)) # or os.path.exists(os.path.join(paths,filename_isis))
return ok
[docs]def translate(nint,lmin,function):
'''
Used in int2_int and min2int to parse parameters contained in function[nint].value e.g.
::
p[4]*2+p[7]
and translate the internal parameter indices 4 and 7 (written according to the gui parameter list order)
into the corresponding minuit parameter list indices, that skips shared and fixed parameters.
e.g. if parameter 6 is shared with parameter 4 and parameter 2 is fixed, the minuit parameter indices
will be 3 instead of 4 (skipping internal index 2) and 5 instead of 7 (skipping both 2 and 6)
'''
string = function[nint].value
# search for integers between '[' and ']'
start = [i+1 for i in findall('[',string)]
# finds index of number after all occurencies of '['
stop = [i for i in findall(']',string)]
# same for ']'
nints = [string[i:j] for (i,j) in zip(start,stop)]
# this is a list of strings with the numbers
nmins = [lmin[int(string[i:j])] for (i,j) in zip(start,stop)]
for lstr,m in zip(nints,nmins):
string = string.replace(lstr,str(m))
return string
[docs]def value_error(value,error):
'''
value_error(v,e)
returns a string of the format v(e)
'''
from numpy import floor, log10
exponent = int(floor(log10(error)))
most_significant = int(round(error/10**exponent))
if most_significant>9:
exponent += 1
most_significant=1
exponent = -exponent if exponent<0 else 0
form = '"{:.'
form += '{}'.format(exponent)
form += 'f}({})".format(value,most_significant)'
return eval(form)