# -*- coding: utf-8 -*-
#GSASII image calculations: Image calibration, masking & integration routines.
########### SVN repository information ###################
# $Date: 2023-06-23 19:12:24 +0000 (Fri, 23 Jun 2023) $
# $Author: toby $
# $Revision: 5622 $
# $URL: https://subversion.xray.aps.anl.gov/pyGSAS/trunk/GSASIIimage.py $
# $Id: GSASIIimage.py 5622 2023-06-23 19:12:24Z toby $
########### SVN repository information ###################
'''
Classes and routines defined in :mod:`GSASIIimage` follow.
'''
from __future__ import division, print_function
import math
import time
import copy
import sys
import numpy as np
import numpy.linalg as nl
import numpy.ma as ma
from scipy.optimize import leastsq
import scipy.interpolate as scint
import scipy.special as sc
import GSASIIpath
GSASIIpath.SetVersionNumber("$Revision: 5622 $")
try:
import GSASIIplot as G2plt
except ImportError: # expected in scriptable w/o matplotlib and/or wx
pass
import GSASIIlattice as G2lat
import GSASIIpwd as G2pwd
import GSASIIspc as G2spc
#import GSASIImath as G2mth
import GSASIIfiles as G2fil
import ImageCalibrants as calFile
# trig functions in degrees
sind = lambda x: math.sin(x*math.pi/180.)
asind = lambda x: 180.*math.asin(x)/math.pi
tand = lambda x: math.tan(x*math.pi/180.)
atand = lambda x: 180.*math.atan(x)/math.pi
atan2d = lambda y,x: 180.*math.atan2(y,x)/math.pi
cosd = lambda x: math.cos(x*math.pi/180.)
acosd = lambda x: 180.*math.acos(x)/math.pi
rdsq2d = lambda x,p: round(1.0/math.sqrt(x),p)
#numpy versions
npsind = lambda x: np.sin(x*np.pi/180.)
npasind = lambda x: 180.*np.arcsin(x)/np.pi
npcosd = lambda x: np.cos(x*np.pi/180.)
npacosd = lambda x: 180.*np.arccos(x)/np.pi
nptand = lambda x: np.tan(x*np.pi/180.)
npatand = lambda x: 180.*np.arctan(x)/np.pi
npatan2d = lambda y,x: 180.*np.arctan2(y,x)/np.pi
nxs = np.newaxis
debug = False
[docs]def pointInPolygon(pXY,xy):
'Needs a doc string'
#pXY - assumed closed 1st & last points are duplicates
Inside = False
N = len(pXY)
p1x,p1y = pXY[0]
for i in range(N+1):
p2x,p2y = pXY[i%N]
if (max(p1y,p2y) >= xy[1] > min(p1y,p2y)) and (xy[0] <= max(p1x,p2x)):
if p1y != p2y:
xinters = (xy[1]-p1y)*(p2x-p1x)/(p2y-p1y)+p1x
if p1x == p2x or xy[0] <= xinters:
Inside = not Inside
p1x,p1y = p2x,p2y
return Inside
[docs]def peneCorr(tth,dep,dist):
'Needs a doc string'
return dep*(1.-npcosd(tth))*dist**2/1000. #best one
[docs]def makeMat(Angle,Axis):
'''Make rotation matrix from Angle and Axis
:param float Angle: in degrees
:param int Axis: 0 for rotation about x, 1 for about y, etc.
'''
cs = npcosd(Angle)
ss = npsind(Angle)
M = np.array(([1.,0.,0.],[0.,cs,-ss],[0.,ss,cs]),dtype=np.float32)
return np.roll(np.roll(M,Axis,axis=0),Axis,axis=1)
def FitEllipse(xy):
def ellipse_center(p):
''' gives ellipse center coordinates
'''
b,c,d,f,a = p[1]/2., p[2], p[3]/2., p[4]/2., p[0]
num = b*b-a*c
x0=(c*d-b*f)/num
y0=(a*f-b*d)/num
return np.array([x0,y0])
def ellipse_angle_of_rotation( p ):
''' gives rotation of ellipse major axis from x-axis
range will be -90 to 90 deg
'''
b,c,a = p[1]/2., p[2], p[0]
return 0.5*npatand(2*b/(a-c))
def ellipse_axis_length( p ):
''' gives ellipse radii in [minor,major] order
'''
b,c,d,f,g,a = p[1]/2., p[2], p[3]/2., p[4]/2, p[5], p[0]
up = 2*(a*f*f+c*d*d+g*b*b-2*b*d*f-a*c*g)
down1=(b*b-a*c)*( (c-a)*np.sqrt(1+4*b*b/((a-c)*(a-c)))-(c+a))
down2=(b*b-a*c)*( (a-c)*np.sqrt(1+4*b*b/((a-c)*(a-c)))-(c+a))
res1=np.sqrt(up/down1)
res2=np.sqrt(up/down2)
return np.array([ res2,res1])
xy = np.array(xy)
x = np.asarray(xy.T[0])[:,np.newaxis]
y = np.asarray(xy.T[1])[:,np.newaxis]
D = np.hstack((x*x, x*y, y*y, x, y, np.ones_like(x)))
S = np.dot(D.T,D)
C = np.zeros([6,6])
C[0,2] = C[2,0] = 2; C[1,1] = -1
E, V = nl.eig(np.dot(nl.inv(S), C))
n = np.argmax(np.abs(E))
a = V[:,n]
cent = ellipse_center(a)
phi = ellipse_angle_of_rotation(a)
radii = ellipse_axis_length(a)
phi += 90.
if radii[0] > radii[1]:
radii = [radii[1],radii[0]]
phi -= 90.
return cent,phi,radii
[docs]def FitDetector(rings,varyList,parmDict,Print=True,covar=False):
'''Fit detector calibration parameters
:param np.array rings: vector of ring positions
:param list varyList: calibration parameters to be refined
:param dict parmDict: all calibration parameters
:param bool Print: set to True (default) to print the results
:param bool covar: set to True to return the covariance matrix (default is False)
:returns: [chisq,vals,sigList] unless covar is True, then
[chisq,vals,sigList,coVarMatrix] is returned
'''
def CalibPrint(ValSig,chisq,Npts):
print ('Image Parameters: chi**2: %12.3g, Np: %d'%(chisq,Npts))
ptlbls = 'names :'
ptstr = 'values:'
sigstr = 'esds :'
for name,value,sig in ValSig:
ptlbls += "%s" % (name.rjust(12))
if name == 'phi':
ptstr += Fmt[name] % (value%360.)
else:
ptstr += Fmt[name] % (value)
if sig:
sigstr += Fmt[name] % (sig)
else:
sigstr += 12*' '
print (ptlbls)
print (ptstr)
print (sigstr)
def ellipseCalcD(B,xyd,varyList,parmDict):
x,y,dsp = xyd
varyDict = dict(zip(varyList,B))
parms = {}
for parm in parmDict:
if parm in varyList:
parms[parm] = varyDict[parm]
else:
parms[parm] = parmDict[parm]
phi = parms['phi']-90. #get rotation of major axis from tilt axis
tth = 2.0*npasind(parms['wave']/(2.*dsp))
phi0 = npatan2d(y-parms['det-Y'],x-parms['det-X'])
dxy = peneCorr(tth,parms['dep'],parms['dist'])
stth = npsind(tth)
cosb = npcosd(parms['tilt'])
tanb = nptand(parms['tilt'])
tbm = nptand((tth-parms['tilt'])/2.)
tbp = nptand((tth+parms['tilt'])/2.)
d = parms['dist']+dxy
fplus = d*tanb*stth/(cosb+stth)
fminus = d*tanb*stth/(cosb-stth)
vplus = d*(tanb+(1+tbm)/(1-tbm))*stth/(cosb+stth)
vminus = d*(tanb+(1-tbp)/(1+tbp))*stth/(cosb-stth)
R0 = np.sqrt((vplus+vminus)**2-(fplus+fminus)**2)/2. #+minor axis
R1 = (vplus+vminus)/2. #major axis
zdis = (fplus-fminus)/2.
Robs = np.sqrt((x-parms['det-X'])**2+(y-parms['det-Y'])**2)
rsqplus = R0**2+R1**2
rsqminus = R0**2-R1**2
R = rsqminus*npcosd(2.*phi0-2.*phi)+rsqplus
Q = np.sqrt(2.)*R0*R1*np.sqrt(R-2.*zdis**2*npsind(phi0-phi)**2)
P = 2.*R0**2*zdis*npcosd(phi0-phi)
Rcalc = (P+Q)/R
M = (Robs-Rcalc)*25. #why 25? does make "chi**2" more reasonable
return M
names = ['dist','det-X','det-Y','tilt','phi','dep','wave']
fmt = ['%12.3f','%12.3f','%12.3f','%12.3f','%12.3f','%12.4f','%12.6f']
Fmt = dict(zip(names,fmt))
p0 = [parmDict[key] for key in varyList]
result = leastsq(ellipseCalcD,p0,args=(rings.T,varyList,parmDict),full_output=True,ftol=1.e-8)
chisq = np.sum(result[2]['fvec']**2)/(rings.shape[0]-len(p0)) #reduced chi^2 = M/(Nobs-Nvar)
parmDict.update(zip(varyList,result[0]))
vals = list(result[0])
if not len(vals):
sig = []
ValSig = []
sigList = []
else:
sig = list(np.sqrt(chisq*np.diag(result[1])))
sigList = np.zeros(7)
for i,name in enumerate(varyList):
sigList[i] = sig[varyList.index(name)]
ValSig = zip(varyList,vals,sig)
if Print:
if len(sig):
CalibPrint(ValSig,chisq,rings.shape[0])
else:
print(' Nothing refined')
if covar:
return [chisq,vals,sigList,result[1]]
else:
return [chisq,vals,sigList]
[docs]def FitMultiDist(rings,varyList,parmDict,Print=True,covar=False):
'''Fit detector calibration parameters with multi-distance data
:param np.array rings: vector of ring positions (x,y,dist,d-space)
:param list varyList: calibration parameters to be refined
:param dict parmDict: calibration parameters
:param bool Print: set to True (default) to print the results
:param bool covar: set to True to return the covariance matrix (default is False)
:returns: [chisq,vals,sigDict] unless covar is True, then
[chisq,vals,sigDict,coVarMatrix] is returned
'''
def CalibPrint(parmDict,sigDict,chisq,Npts):
ptlbls = 'names :'
ptstr = 'values:'
sigstr = 'esds :'
for d in sorted(set([i[5:] for i in parmDict.keys() if 'det-X' in i]),key=lambda x:int(x)):
fmt = '%12.3f'
for key in 'det-X','det-Y','delta':
name = key+d
if name not in parmDict: continue
ptlbls += "%12s" % name
ptstr += fmt % (parmDict[name])
if name in sigDict:
sigstr += fmt % (sigDict[name])
else:
sigstr += 12*' '
if len(ptlbls) > 68:
print()
print (ptlbls)
print (ptstr)
print (sigstr)
ptlbls = 'names :'
ptstr = 'values:'
sigstr = 'esds :'
if len(ptlbls) > 8:
print()
print (ptlbls)
print (ptstr)
print (sigstr)
print ('\nImage Parameters: chi**2: %12.3g, Np: %d'%(chisq,Npts))
ptlbls = 'names :'
ptstr = 'values:'
sigstr = 'esds :'
names = ['wavelength', 'dep', 'phi', 'tilt']
if 'deltaDist' in parmDict:
names += ['deltaDist']
for name in names:
if name == 'wavelength':
fmt = '%12.6f'
elif name == 'dep':
fmt = '%12.4f'
else:
fmt = '%12.3f'
ptlbls += "%s" % (name.rjust(12))
if name == 'phi':
ptstr += fmt % (parmDict[name]%360.)
else:
ptstr += fmt % (parmDict[name])
if name in sigDict:
sigstr += fmt % (sigDict[name])
else:
sigstr += 12*' '
print (ptlbls)
print (ptstr)
print (sigstr)
print()
def ellipseCalcD(B,xyd,varyList,parmDict):
x,y,dist,dsp = xyd
varyDict = dict(zip(varyList,B))
parms = {}
for parm in parmDict:
if parm in varyList:
parms[parm] = varyDict[parm]
else:
parms[parm] = parmDict[parm]
# create arrays with detector center values
detX = np.array([parms['det-X'+str(int(d))] for d in dist])
detY = np.array([parms['det-Y'+str(int(d))] for d in dist])
if 'deltaDist' in parms:
deltaDist = parms['deltaDist']
else:
deltaDist = np.array([parms['delta'+str(int(d))] for d in dist])
phi = parms['phi']-90. #get rotation of major axis from tilt axis
tth = 2.0*npasind(parms['wavelength']/(2.*dsp))
phi0 = npatan2d(y-detY,x-detX)
dxy = peneCorr(tth,parms['dep'],dist-deltaDist)
stth = npsind(tth)
cosb = npcosd(parms['tilt'])
tanb = nptand(parms['tilt'])
tbm = nptand((tth-parms['tilt'])/2.)
tbp = nptand((tth+parms['tilt'])/2.)
d = (dist-deltaDist)+dxy
fplus = d*tanb*stth/(cosb+stth)
fminus = d*tanb*stth/(cosb-stth)
vplus = d*(tanb+(1+tbm)/(1-tbm))*stth/(cosb+stth)
vminus = d*(tanb+(1-tbp)/(1+tbp))*stth/(cosb-stth)
R0 = np.sqrt((vplus+vminus)**2-(fplus+fminus)**2)/2. #+minor axis
R1 = (vplus+vminus)/2. #major axis
zdis = (fplus-fminus)/2.
Robs = np.sqrt((x-detX)**2+(y-detY)**2)
rsqplus = R0**2+R1**2
rsqminus = R0**2-R1**2
R = rsqminus*npcosd(2.*phi0-2.*phi)+rsqplus
Q = np.sqrt(2.)*R0*R1*np.sqrt(R-2.*zdis**2*npsind(phi0-phi)**2)
P = 2.*R0**2*zdis*npcosd(phi0-phi)
Rcalc = (P+Q)/R
return (Robs-Rcalc)*25. #why 25? does make "chi**2" more reasonable
p0 = [parmDict[key] for key in varyList]
result = leastsq(ellipseCalcD,p0,args=(rings.T,varyList,parmDict),full_output=True,ftol=1.e-8)
chisq = np.sum(result[2]['fvec']**2)/(rings.shape[0]-len(p0)) #reduced chi^2 = M/(Nobs-Nvar)
parmDict.update(zip(varyList,result[0]))
vals = list(result[0])
if chisq > 1:
sig = list(np.sqrt(chisq*np.diag(result[1])))
else:
sig = list(np.sqrt(np.diag(result[1])))
sigDict = {name:s for name,s in zip(varyList,sig)}
if Print:
CalibPrint(parmDict,sigDict,chisq,rings.shape[0])
if covar:
return [chisq,vals,sigDict,result[1]]
else:
return [chisq,vals,sigDict]
[docs]def ImageLocalMax(image,w,Xpix,Ypix):
'Needs a doc string'
w2 = w*2
sizey,sizex = image.shape
xpix = int(Xpix) #get reference corner of pixel chosen
ypix = int(Ypix)
if not w:
ZMax = np.sum(image[ypix-2:ypix+2,xpix-2:xpix+2])
return xpix,ypix,ZMax,0.0001
if (w2 < xpix < sizex-w2) and (w2 < ypix < sizey-w2) and image[ypix,xpix]:
ZMax = image[ypix-w:ypix+w,xpix-w:xpix+w]
Zmax = np.argmax(ZMax)
ZMin = image[ypix-w2:ypix+w2,xpix-w2:xpix+w2]
Zmin = np.argmin(ZMin)
xpix += Zmax%w2-w
ypix += Zmax//w2-w
return xpix,ypix,np.ravel(ZMax)[Zmax],max(0.0001,np.ravel(ZMin)[Zmin]) #avoid neg/zero minimum
else:
return 0,0,0,0
[docs]def makeRing(dsp,ellipse,pix,reject,scalex,scaley,image,mul=1):
'Needs a doc string'
def ellipseC():
'compute estimate of ellipse circumference'
if radii[0] < 0: #hyperbola
# theta = npacosd(1./np.sqrt(1.+(radii[0]/radii[1])**2))
# print (theta)
return 0
apb = radii[1]+radii[0]
amb = radii[1]-radii[0]
return np.pi*apb*(1+3*(amb/apb)**2/(10+np.sqrt(4-3*(amb/apb)**2)))
cent,phi,radii = ellipse
cphi = cosd(phi-90.) #convert to major axis rotation
sphi = sind(phi-90.)
ring = []
C = int(ellipseC())*mul #ring circumference in mm
azm = []
for i in range(0,C,1): #step around ring in 1mm increments
a = 360.*i/C
x = radii[1]*cosd(a-phi+90.) #major axis
y = radii[0]*sind(a-phi+90.)
X = (cphi*x-sphi*y+cent[0])*scalex #convert mm to pixels
Y = (sphi*x+cphi*y+cent[1])*scaley
X,Y,I,J = ImageLocalMax(image,pix,X,Y)
if I and J and float(I)/J > reject:
X += .5 #set to center of pixel
Y += .5
X /= scalex #convert back to mm
Y /= scaley
if [X,Y,dsp] not in ring: #no duplicates!
ring.append([X,Y,dsp])
azm.append(a)
if len(ring) < 10:
ring = []
azm = []
return ring,azm
[docs]def GetEllipse2(tth,dxy,dist,cent,tilt,phi):
'''uses Dandelin spheres to find ellipse or hyperbola parameters from detector geometry
on output
radii[0] (b-minor axis) set < 0. for hyperbola
'''
radii = [0,0]
stth = sind(tth)
cosb = cosd(tilt)
tanb = tand(tilt)
tbm = tand((tth-tilt)/2.)
tbp = tand((tth+tilt)/2.)
sinb = sind(tilt)
d = dist+dxy
if tth+abs(tilt) < 90.: #ellipse
fplus = d*tanb*stth/(cosb+stth)
fminus = d*tanb*stth/(cosb-stth)
vplus = d*(tanb+(1+tbm)/(1-tbm))*stth/(cosb+stth)
vminus = d*(tanb+(1-tbp)/(1+tbp))*stth/(cosb-stth)
radii[0] = np.sqrt((vplus+vminus)**2-(fplus+fminus)**2)/2. #+minor axis
radii[1] = (vplus+vminus)/2. #major axis
zdis = (fplus-fminus)/2.
else: #hyperbola!
f = d*abs(tanb)*stth/(cosb+stth)
v = d*(abs(tanb)+tand(tth-abs(tilt)))
delt = d*stth*(1.+stth*cosb)/(abs(sinb)*cosb*(stth+cosb))
eps = (v-f)/(delt-v)
radii[0] = -eps*(delt-f)/np.sqrt(eps**2-1.) #-minor axis
radii[1] = eps*(delt-f)/(eps**2-1.) #major axis
if tilt > 0:
zdis = f+radii[1]*eps
else:
zdis = -f
#NB: zdis is || to major axis & phi is rotation of minor axis
#thus shift from beam to ellipse center is [Z*sin(phi),-Z*cos(phi)]
elcent = [cent[0]+zdis*sind(phi),cent[1]-zdis*cosd(phi)]
return elcent,phi,radii
[docs]def GetEllipse(dsp,data):
'''uses Dandelin spheres to find ellipse or hyperbola parameters from detector geometry
as given in image controls dictionary (data) and a d-spacing (dsp)
'''
cent = data['center']
tilt = data['tilt']
phi = data['rotation']
dep = data.get('DetDepth',0.0)
tth = 2.0*asind(data['wavelength']/(2.*dsp))
dist = data['distance']
dxy = peneCorr(tth,dep,dist)
return GetEllipse2(tth,dxy,dist,cent,tilt,phi)
[docs]def GetDetectorXY(dsp,azm,data):
'''Get detector x,y position from d-spacing (dsp), azimuth (azm,deg)
& image controls dictionary (data) - new version
it seems to be only used in plotting
'''
def LinePlaneCollision(planeNormal, planePoint, rayDirection, rayPoint, epsilon=1e-6):
ndotu = planeNormal.dot(rayDirection)
if ndotu < epsilon:
return None
w = rayPoint - planePoint
si = -planeNormal.dot(w) / ndotu
Psi = w + si * rayDirection + planePoint
return Psi
dist = data['distance']
cent = data['center']
T = makeMat(data['tilt'],0)
R = makeMat(data['rotation'],2)
MN = np.inner(R,np.inner(R,T))
iMN= nl.inv(MN)
tth = 2.0*npasind(data['wavelength']/(2.*dsp))
vect = np.array([npsind(tth)*npcosd(azm),npsind(tth)*npsind(azm),npcosd(tth)])
dxyz0 = np.inner(np.array([0.,0.,1.0]),MN) #tilt detector normal
dxyz0 += np.array([0.,0.,dist]) #translate to distance
dxyz0 = np.inner(dxyz0,makeMat(data['det2theta'],1).T) #rotate on 2-theta
dxyz1 = np.inner(np.array([cent[0],cent[1],0.]),MN) #tilt detector cent
dxyz1 += np.array([0.,0.,dist]) #translate to distance
dxyz1 = np.inner(dxyz1,makeMat(data['det2theta'],1).T) #rotate on 2-theta
xyz = LinePlaneCollision(dxyz0,dxyz1,vect,2.*dist*vect)
if xyz is None:
return np.zeros(2)
# return None
xyz = np.inner(xyz,makeMat(-data['det2theta'],1).T)
xyz -= np.array([0.,0.,dist]) #translate back
xyz = np.inner(xyz,iMN)
return np.squeeze(xyz)[:2]+cent
[docs]def GetDetectorXY2(dsp,azm,data):
'''Get detector x,y position from d-spacing (dsp), azimuth (azm,deg)
& image controls dictionary (data)
it seems to be only used in plotting
'''
elcent,phi,radii = GetEllipse(dsp,data)
phi = data['rotation']-90. #to give rotation of major axis
tilt = data['tilt']
dist = data['distance']
cent = data['center']
tth = 2.0*asind(data['wavelength']/(2.*dsp))
stth = sind(tth)
cosb = cosd(tilt)
if radii[0] > 0.:
sinb = sind(tilt)
tanb = tand(tilt)
fplus = dist*tanb*stth/(cosb+stth)
fminus = dist*tanb*stth/(cosb-stth)
zdis = (fplus-fminus)/2.
rsqplus = radii[0]**2+radii[1]**2
rsqminus = radii[0]**2-radii[1]**2
R = rsqminus*cosd(2.*azm-2.*phi)+rsqplus
Q = np.sqrt(2.)*radii[0]*radii[1]*np.sqrt(R-2.*zdis**2*sind(azm-phi)**2)
P = 2.*radii[0]**2*zdis*cosd(azm-phi)
radius = (P+Q)/R
xy = np.array([radius*cosd(azm),radius*sind(azm)])
xy += cent
else: #hyperbola - both branches (one is way off screen!)
sinb = abs(sind(tilt))
tanb = abs(tand(tilt))
f = dist*tanb*stth/(cosb+stth)
v = dist*(tanb+tand(tth-abs(tilt)))
delt = dist*stth*(1+stth*cosb)/(sinb*cosb*(stth+cosb))
ecc = (v-f)/(delt-v)
R = radii[1]*(ecc**2-1)/(1-ecc*cosd(azm))
if tilt > 0.:
offset = 2.*radii[1]*ecc+f #select other branch
xy = [-R*cosd(azm)-offset,-R*sind(azm)]
else:
offset = -f
xy = [-R*cosd(azm)-offset,R*sind(azm)]
xy = -np.array([xy[0]*cosd(phi)+xy[1]*sind(phi),xy[0]*sind(phi)-xy[1]*cosd(phi)])
xy += cent
if data['det2theta']:
xy[0] += dist*nptand(data['det2theta']+data['tilt']*npsind(data['rotation']))
return xy
[docs]def GetDetXYfromThAzm(Th,Azm,data):
'''Computes a detector position from a 2theta angle and an azimultal
angle (both in degrees) - apparently not used!
'''
dsp = data['wavelength']/(2.0*npsind(Th))
return GetDetectorXY(dsp,Azm,data)
[docs]def GetTthAzmDsp2(x,y,data): #expensive
'''Computes a 2theta, etc. from a detector position and calibration constants - checked
OK for ellipses & hyperbola.
Use only for detector 2-theta = 0
:returns: np.array(tth,azm,G,dsp) where tth is 2theta, azm is the azimutal angle,
G is ? and dsp is the d-space
'''
wave = data['wavelength']
cent = data['center']
tilt = data['tilt']
dist = data['distance']/cosd(tilt)
x0 = dist*tand(tilt)
phi = data['rotation']
dep = data.get('DetDepth',0.)
azmthoff = data['azmthOff']
dx = np.array(x-cent[0],dtype=np.float32)
dy = np.array(y-cent[1],dtype=np.float32)
D = ((dx-x0)**2+dy**2+dist**2) #sample to pixel distance
X = np.array(([dx,dy,np.zeros_like(dx)]),dtype=np.float32).T
X = np.dot(X,makeMat(phi,2))
Z = np.dot(X,makeMat(tilt,0)).T[2]
tth = npatand(np.sqrt(dx**2+dy**2-Z**2)/(dist-Z))
dxy = peneCorr(tth,dep,dist)
DX = dist-Z+dxy
DY = np.sqrt(dx**2+dy**2-Z**2)
tth = npatan2d(DY,DX)
dsp = wave/(2.*npsind(tth/2.))
azm = (npatan2d(dy,dx)+azmthoff+720.)%360.
G = D/dist**2 #for geometric correction = 1/cos(2theta)^2 if tilt=0.
return np.array([tth,azm,G,dsp])
[docs]def GetTthAzmDsp(x,y,data): #expensive
'''Computes a 2theta, etc. from a detector position and calibration constants - checked
OK for ellipses & hyperbola.
Use for detector 2-theta != 0.
:returns: np.array(tth,azm,G,dsp) where tth is 2theta, azm is the azimutal angle,
G is ? and dsp is the d-space
'''
def costth(xyz):
u = xyz/nl.norm(xyz,axis=-1)[:,:,nxs]
return np.dot(u,np.array([0.,0.,1.]))
#zero detector 2-theta: tested with tilted images - perfect integrations
wave = data['wavelength']
dx = x-data['center'][0]
dy = y-data['center'][1]
tilt = data['tilt']
dist = data['distance']/npcosd(tilt) #sample-beam intersection point
T = makeMat(tilt,0)
R = makeMat(data['rotation'],2)
MN = np.inner(R,np.inner(R,T))
dxyz0 = np.inner(np.dstack([dx,dy,np.zeros_like(dx)]),MN) #correct for 45 deg tilt
dxyz0 += np.array([0.,0.,dist])
if data['DetDepth']:
ctth0 = costth(dxyz0)
tth0 = npacosd(ctth0)
dzp = peneCorr(tth0,data['DetDepth'],dist)
dxyz0[:,:,2] += dzp
#non zero detector 2-theta:
if data['det2theta']:
tthMat = makeMat(data['det2theta'],1)
dxyz = np.inner(dxyz0,tthMat.T)
else:
dxyz = dxyz0
ctth = costth(dxyz)
tth = npacosd(ctth)
dsp = wave/(2.*npsind(tth/2.))
azm = (npatan2d(dxyz[:,:,1],dxyz[:,:,0])+data['azmthOff']+720.)%360.
# G-calculation
x0 = data['distance']*nptand(tilt)
x0x = x0*npcosd(data['rotation'])
x0y = x0*npsind(data['rotation'])
distsq = data['distance']**2
G = ((dx-x0x)**2+(dy-x0y)**2+distsq)/distsq #for geometric correction = 1/cos(2theta)^2 if tilt=0.
return [tth,azm,G,dsp]
[docs]def GetTth(x,y,data):
'Give 2-theta value for detector x,y position; calibration info in data'
if data['det2theta']:
return GetTthAzmDsp(x,y,data)[0]
else:
return GetTthAzmDsp2(x,y,data)[0]
[docs]def GetTthAzm(x,y,data):
'Give 2-theta, azimuth values for detector x,y position; calibration info in data'
if data['det2theta']:
return GetTthAzmDsp(x,y,data)[0:2]
else:
return GetTthAzmDsp2(x,y,data)[0:2]
[docs]def GetTthAzmG2(x,y,data):
'''Give 2-theta, azimuth & geometric corr. values for detector x,y position;
calibration info in data - only used in integration for detector 2-theta = 0
'''
tilt = data['tilt']
dist = data['distance']/npcosd(tilt)
MN = -np.inner(makeMat(data['rotation'],2),makeMat(tilt,0))
dx = x-data['center'][0]
dy = y-data['center'][1]
dz = np.dot(np.dstack([dx.T,dy.T,np.zeros_like(dx.T)]),MN).T[2]
xyZ = dx**2+dy**2-dz**2
tth0 = npatand(np.sqrt(xyZ)/(dist-dz))
dzp = peneCorr(tth0,data['DetDepth'],dist)
tth = npatan2d(np.sqrt(xyZ),dist-dz+dzp)
azm = (npatan2d(dy,dx)+data['azmthOff']+720.)%360.
distsq = data['distance']**2
x0 = data['distance']*nptand(tilt)
x0x = x0*npcosd(data['rotation'])
x0y = x0*npsind(data['rotation'])
G = ((dx-x0x)**2+(dy-x0y)**2+distsq)/distsq #for geometric correction = 1/cos(2theta)^2 if tilt=0.
return tth,azm,G
[docs]def GetTthAzmG(x,y,data):
'''Give 2-theta, azimuth & geometric corr. values for detector x,y position;
calibration info in data - only used in integration for detector 2-theta != 0.
checked OK for ellipses & hyperbola
This is the slow step in image integration
'''
def costth(xyz):
u = xyz/nl.norm(xyz,axis=-1)[:,:,nxs]
return np.dot(u,np.array([0.,0.,1.]))
#zero detector 2-theta: tested with tilted images - perfect integrations
dx = x-data['center'][0]
dy = y-data['center'][1]
tilt = data['tilt']
dist = data['distance']/npcosd(tilt) #sample-beam intersection point
T = makeMat(tilt,0)
R = makeMat(data['rotation'],2)
MN = np.inner(R,np.inner(R,T))
dxyz0 = np.inner(np.dstack([dx,dy,np.zeros_like(dx)]),MN) #correct for 45 deg tilt
dxyz0 += np.array([0.,0.,dist])
if data['DetDepth']:
ctth0 = costth(dxyz0)
tth0 = npacosd(ctth0)
dzp = peneCorr(tth0,data['DetDepth'],dist)
dxyz0[:,:,2] += dzp
#non zero detector 2-theta:
if data.get('det2theta',0):
tthMat = makeMat(data['det2theta'],1)
dxyz = np.inner(dxyz0,tthMat.T)
else:
dxyz = dxyz0
ctth = costth(dxyz)
tth = npacosd(ctth)
azm = (npatan2d(dxyz[:,:,1],dxyz[:,:,0])+data['azmthOff']+720.)%360.
# G-calculation
x0 = data['distance']*nptand(tilt)
x0x = x0*npcosd(data['rotation'])
x0y = x0*npsind(data['rotation'])
distsq = data['distance']**2
G = ((dx-x0x)**2+(dy-x0y)**2+distsq)/distsq #for geometric correction = 1/cos(2theta)^2 if tilt=0.
return tth,azm,G
[docs]def GetDsp(x,y,data):
'Give d-spacing value for detector x,y position; calibration info in data'
if data['det2theta']:
return GetTthAzmDsp(x,y,data)[3]
else:
return GetTthAzmDsp2(x,y,data)[3]
[docs]def GetAzm(x,y,data):
'Give azimuth value for detector x,y position; calibration info in data'
if data['det2theta']:
return GetTthAzmDsp(x,y,data)[1]
else:
return GetTthAzmDsp2(x,y,data)[1]
def meanAzm(a,b):
AZM = lambda a,b: npacosd(0.5*(npsind(2.*b)-npsind(2.*a))/(np.pi*(b-a)/180.))/2.
azm = AZM(a,b)
# quad = int((a+b)/180.)
# if quad == 1:
# azm = 180.-azm
# elif quad == 2:
# azm += 180.
# elif quad == 3:
# azm = 360-azm
return azm
[docs]def ImageCompress(image,scale):
''' Reduces size of image by selecting every n'th point
param: image array: original image
param: scale int: intervsl between selected points
returns: array: reduced size image
'''
if scale == 1:
return image
else:
return image[::scale,::scale]
[docs]def checkEllipse(Zsum,distSum,xSum,ySum,dist,x,y):
'Needs a doc string'
avg = np.array([distSum/Zsum,xSum/Zsum,ySum/Zsum])
curr = np.array([dist,x,y])
return abs(avg-curr)/avg < .02
def GetLineScan(image,data):
Nx,Ny = data['size']
pixelSize = data['pixelSize']
scalex = 1000./pixelSize[0] #microns --> 1/mm
scaley = 1000./pixelSize[1]
wave = data['wavelength']
numChans = data['outChannels']
LUtth = np.array(data['IOtth'],dtype=np.float)
azm = data['linescan'][1]-data['azmthOff']
Tx = np.array([tth for tth in np.linspace(LUtth[0],LUtth[1],numChans+1)])
Ty = np.zeros_like(Tx)
dsp = wave/(2.0*npsind(Tx/2.0))
xy = [GetDetectorXY(d,azm,data) for d in dsp]
xy = np.array(xy).T
xy[1] *= scalex
xy[0] *= scaley
xy = np.array(xy,dtype=int)
Xpix = ma.masked_outside(xy[1],0,Ny-1)
Ypix = ma.masked_outside(xy[0],0,Nx-1)
xpix = Xpix[~(Xpix.mask+Ypix.mask)].compressed()
ypix = Ypix[~(Xpix.mask+Ypix.mask)].compressed()
Ty = image[xpix,ypix]
Tx = ma.array(Tx,mask=Xpix.mask+Ypix.mask).compressed()
return [Tx,Ty]
[docs]def EdgeFinder(image,data):
'''this makes list of all x,y where I>edgeMin suitable for an ellipse search?
Not currently used but might be useful in future?
'''
import numpy.ma as ma
Nx,Ny = data['size']
pixelSize = data['pixelSize']
edgemin = data['edgemin']
scalex = pixelSize[0]/1000.
scaley = pixelSize[1]/1000.
tay,tax = np.mgrid[0:Nx,0:Ny]
tax = np.asfarray(tax*scalex,dtype=np.float32)
tay = np.asfarray(tay*scaley,dtype=np.float32)
tam = ma.getmask(ma.masked_less(image.flatten(),edgemin))
tax = ma.compressed(ma.array(tax.flatten(),mask=tam))
tay = ma.compressed(ma.array(tay.flatten(),mask=tam))
return zip(tax,tay)
[docs]def MakeFrameMask(data,frame):
'''Assemble a Frame mask for a image, according to the input supplied.
Note that this requires use of the Fortran polymask routine that is limited
to 1024x1024 arrays, so this computation is done in blocks (fixed at 512)
and the master image is assembled from that.
:param dict data: Controls for an image. Used to find the image size
and the pixel dimensions.
:param list frame: Frame parameters, typically taken from ``Masks['Frames']``.
:returns: a mask array with dimensions matching the image Controls.
'''
import polymask as pm
pixelSize = data['pixelSize']
scalex = pixelSize[0]/1000.
scaley = pixelSize[1]/1000.
blkSize = 512
Nx,Ny = data['size']
nXBlks = (Nx-1)//blkSize+1
nYBlks = (Ny-1)//blkSize+1
tam = ma.make_mask_none(data['size'])
for iBlk in range(nXBlks):
iBeg = iBlk*blkSize
iFin = min(iBeg+blkSize,Nx)
for jBlk in range(nYBlks):
jBeg = jBlk*blkSize
jFin = min(jBeg+blkSize,Ny)
nI = iFin-iBeg
nJ = jFin-jBeg
tax,tay = np.mgrid[iBeg+0.5:iFin+.5,jBeg+.5:jFin+.5] #bin centers not corners
tax = np.asfarray(tax*scalex,dtype=np.float32)
tay = np.asfarray(tay*scaley,dtype=np.float32)
tamp = ma.make_mask_none((1024*1024))
tamp = ma.make_mask(pm.polymask(nI*nJ,tax.flatten(),
tay.flatten(),len(frame),frame,tamp)[:nI*nJ])^True #switch to exclude around frame
if tamp.shape:
tamp = np.reshape(tamp[:nI*nJ],(nI,nJ))
tam[iBeg:iFin,jBeg:jFin] = ma.mask_or(tamp[0:nI,0:nJ],tam[iBeg:iFin,jBeg:jFin])
else:
tam[iBeg:iFin,jBeg:jFin] = True
return tam.T
def CalcRings(G2frame,ImageZ,data,masks):
pixelSize = data['pixelSize']
scalex = 1000./pixelSize[0]
scaley = 1000./pixelSize[1]
data['rings'] = []
data['ellipses'] = []
if not data['calibrant']:
G2fil.G2Print ('warning: no calibration material selected')
return
skip = data['calibskip']
dmin = data['calibdmin']
if data['calibrant'] not in calFile.Calibrants:
G2fil.G2Print('Warning: %s not in local copy of image calibrants file'%data['calibrant'])
return
calibrant = calFile.Calibrants[data['calibrant']]
Bravais,SGs,Cells = calibrant[:3]
HKL = []
for bravais,sg,cell in zip(Bravais,SGs,Cells):
A = G2lat.cell2A(cell)
if sg:
SGData = G2spc.SpcGroup(sg)[1]
hkl = G2pwd.getHKLpeak(dmin,SGData,A,Inst=None,nodup=True)
HKL += list(hkl)
else:
hkl = G2lat.GenHBravais(dmin,bravais,A)
HKL += list(hkl)
if len(calibrant) > 5:
absent = calibrant[5]
else:
absent = ()
HKL = G2lat.sortHKLd(HKL,True,False)
wave = data['wavelength']
frame = masks['Frames']
tam = ma.make_mask_none(ImageZ.shape)
if frame:
tam = ma.mask_or(tam,MakeFrameMask(data,frame))
for iH,H in enumerate(HKL):
if debug: print (H)
dsp = H[3]
tth = 2.0*asind(wave/(2.*dsp))
if tth+abs(data['tilt']) > 90.:
G2fil.G2Print ('next line is a hyperbola - search stopped')
break
ellipse = GetEllipse(dsp,data)
if iH not in absent and iH >= skip:
Ring = makeRing(dsp,ellipse,0,-1.,scalex,scaley,ma.array(ImageZ,mask=tam))[0]
else:
Ring = makeRing(dsp,ellipse,0,-1.,scalex,scaley,ma.array(ImageZ,mask=tam))[0]
if Ring:
if iH not in absent and iH >= skip:
data['rings'].append(np.array(Ring))
data['ellipses'].append(copy.deepcopy(ellipse+('r',)))
[docs]def ImageRecalibrate(G2frame,ImageZ,data,masks,getRingsOnly=False):
'''Called to repeat the calibration on an image, usually called after
calibration is done initially to improve the fit, but also
can be used after reading approximate calibration parameters,
if they are close enough that the first ring can be found.
:param G2frame: The top-level GSAS-II frame or None, to skip plotting
:param np.Array ImageZ: the image to calibrate
:param dict data: the Controls dict for the image
:param dict masks: a dict with masks
:returns: a list containing vals,varyList,sigList,parmDict,covar or rings
(with an array of x, y, and d-space values) if getRingsOnly is True
or an empty list, in case of an error
'''
if not getRingsOnly:
G2fil.G2Print ('Image recalibration:')
time0 = time.time()
pixelSize = data['pixelSize']
scalex = 1000./pixelSize[0]
scaley = 1000./pixelSize[1]
pixLimit = data['pixLimit']
cutoff = data['cutoff']
data['rings'] = []
data['ellipses'] = []
if data['DetDepth'] > 0.5: #patch - redefine DetDepth
data['DetDepth'] /= data['distance']
if not data['calibrant']:
G2fil.G2Print ('warning: no calibration material selected')
return []
skip = data['calibskip']
dmin = data['calibdmin']
if data['calibrant'] not in calFile.Calibrants:
G2fil.G2Print('Warning: %s not in local copy of image calibrants file'%data['calibrant'])
return []
calibrant = calFile.Calibrants[data['calibrant']]
Bravais,SGs,Cells = calibrant[:3]
HKL = []
for bravais,sg,cell in zip(Bravais,SGs,Cells):
A = G2lat.cell2A(cell)
if sg:
SGData = G2spc.SpcGroup(sg)[1]
hkl = G2pwd.getHKLpeak(dmin,SGData,A,Inst=None,nodup=True)
HKL += list(hkl)
else:
hkl = G2lat.GenHBravais(dmin,bravais,A)
HKL += list(hkl)
if len(calibrant) > 5:
absent = calibrant[5]
else:
absent = ()
HKL = G2lat.sortHKLd(HKL,True,False)
varyList = [item for item in data['varyList'] if data['varyList'][item]]
parmDict = {'dist':data['distance'],'det-X':data['center'][0],'det-Y':data['center'][1],
'setdist':data.get('setdist',data['distance']),
'tilt':data['tilt'],'phi':data['rotation'],'wave':data['wavelength'],'dep':data['DetDepth']}
Found = False
wave = data['wavelength']
frame = masks['Frames']
tam = ma.make_mask_none(ImageZ.shape)
if frame:
tam = ma.mask_or(tam,MakeFrameMask(data,frame))
for iH,H in enumerate(HKL):
if debug: print (H)
dsp = H[3]
tth = 2.0*asind(wave/(2.*dsp))
if tth+abs(data['tilt']) > 90.:
G2fil.G2Print ('next line is a hyperbola - search stopped')
break
ellipse = GetEllipse(dsp,data)
if iH not in absent and iH >= skip:
Ring = makeRing(dsp,ellipse,pixLimit,cutoff,scalex,scaley,ma.array(ImageZ,mask=tam))[0]
else:
Ring = makeRing(dsp,ellipse,pixLimit,1000.0,scalex,scaley,ma.array(ImageZ,mask=tam))[0]
if Ring:
if iH not in absent and iH >= skip:
data['rings'].append(np.array(Ring))
data['ellipses'].append(copy.deepcopy(ellipse+('r',)))
Found = True
elif not Found: #skipping inner rings, keep looking until ring found
continue
else: #no more rings beyond edge of detector
data['ellipses'].append([])
continue
if not data['rings']:
G2fil.G2Print ('no rings found; try lower Min ring I/Ib',mode='warn')
return []
rings = np.concatenate((data['rings']),axis=0)
if getRingsOnly:
return rings,HKL
[chisq,vals,sigList,covar] = FitDetector(rings,varyList,parmDict,True,True)
data['wavelength'] = parmDict['wave']
data['distance'] = parmDict['dist']
data['center'] = [parmDict['det-X'],parmDict['det-Y']]
data['rotation'] = np.mod(parmDict['phi'],360.0)
data['tilt'] = parmDict['tilt']
data['DetDepth'] = parmDict['dep']
data['chisq'] = chisq
N = len(data['ellipses'])
data['ellipses'] = [] #clear away individual ellipse fits
for H in HKL[:N]:
ellipse = GetEllipse(H[3],data)
data['ellipses'].append(copy.deepcopy(ellipse+('b',)))
G2fil.G2Print ('calibration time = %.3f'%(time.time()-time0))
if G2frame:
G2plt.PlotImage(G2frame,newImage=True)
return [vals,varyList,sigList,parmDict,covar]
[docs]def ImageCalibrate(G2frame,data):
'''Called to perform an initial image calibration after points have been
selected for the inner ring.
'''
G2fil.G2Print ('Image calibration:')
time0 = time.time()
ring = data['ring']
pixelSize = data['pixelSize']
scalex = 1000./pixelSize[0]
scaley = 1000./pixelSize[1]
pixLimit = data['pixLimit']
cutoff = data['cutoff']
varyDict = data['varyList']
if varyDict['dist'] and varyDict['wave']:
G2fil.G2Print ('ERROR - you can not simultaneously calibrate distance and wavelength')
return False
if len(ring) < 5:
G2fil.G2Print ('ERROR - not enough inner ring points for ellipse')
return False
#fit start points on inner ring
data['ellipses'] = []
data['rings'] = []
outE = FitEllipse(ring)
fmt = '%s X: %.3f, Y: %.3f, phi: %.3f, R1: %.3f, R2: %.3f'
fmt2 = '%s X: %.3f, Y: %.3f, phi: %.3f, R1: %.3f, R2: %.3f, chi**2: %.3f, Np: %d'
if outE:
G2fil.G2Print (fmt%('start ellipse: ',outE[0][0],outE[0][1],outE[1],outE[2][0],outE[2][1]))
ellipse = outE
else:
return False
#setup 360 points on that ring for "good" fit
data['ellipses'].append(ellipse[:]+('g',))
Ring = makeRing(1.0,ellipse,pixLimit,cutoff,scalex,scaley,G2frame.ImageZ)[0]
if Ring:
ellipse = FitEllipse(Ring)
Ring = makeRing(1.0,ellipse,pixLimit,cutoff,scalex,scaley,G2frame.ImageZ)[0] #do again
ellipse = FitEllipse(Ring)
else:
G2fil.G2Print ('1st ring not sufficiently complete to proceed',mode='warn')
return False
if debug:
G2fil.G2Print (fmt2%('inner ring: ',ellipse[0][0],ellipse[0][1],ellipse[1],
ellipse[2][0],ellipse[2][1],0.,len(Ring))) #cent,phi,radii
data['ellipses'].append(ellipse[:]+('r',))
data['rings'].append(np.array(Ring))
G2plt.PlotImage(G2frame,newImage=True)
#setup for calibration
data['rings'] = []
if not data['calibrant']:
G2fil.G2Print ('Warning: no calibration material selected')
return True
skip = data['calibskip']
dmin = data['calibdmin']
#generate reflection set
calibrant = calFile.Calibrants[data['calibrant']]
Bravais,SGs,Cells = calibrant[:3]
HKL = []
for bravais,sg,cell in zip(Bravais,SGs,Cells):
A = G2lat.cell2A(cell)
if sg:
SGData = G2spc.SpcGroup(sg)[1]
hkl = G2pwd.getHKLpeak(dmin,SGData,A,Inst=None,nodup=True)
HKL += list(hkl)
else:
hkl = G2lat.GenHBravais(dmin,bravais,A)
HKL += list(hkl)
HKL = G2lat.sortHKLd(HKL,True,False)[skip:]
#set up 1st ring
elcent,phi,radii = ellipse #from fit of 1st ring
dsp = HKL[0][3]
G2fil.G2Print ('1st ring: try %.4f'%(dsp))
if varyDict['dist']:
wave = data['wavelength']
tth = 2.0*asind(wave/(2.*dsp))
else: #varyDict['wave']!
dist = data['distance']
tth = npatan2d(radii[0],dist)
data['wavelength'] = wave = 2.0*dsp*sind(tth/2.0)
Ring0 = makeRing(dsp,ellipse,3,cutoff,scalex,scaley,G2frame.ImageZ)[0]
ttth = nptand(tth)
ctth = npcosd(tth)
#1st estimate of tilt; assume ellipse - don't know sign though
if varyDict['tilt']:
tilt = npasind(np.sqrt(max(0.,1.-(radii[0]/radii[1])**2))*ctth)
if not tilt:
G2fil.G2Print ('WARNING - selected ring was fitted as a circle')
G2fil.G2Print (' - if detector was tilted we suggest you skip this ring - WARNING')
else:
tilt = data['tilt']
#1st estimate of dist: sample to detector normal to plane
if varyDict['dist']:
data['distance'] = dist = radii[0]**2/(ttth*radii[1])
else:
dist = data['distance']
if varyDict['tilt']:
#ellipse to cone axis (x-ray beam); 2 choices depending on sign of tilt
zdisp = radii[1]*ttth*tand(tilt)
zdism = radii[1]*ttth*tand(-tilt)
#cone axis position; 2 choices. Which is right?
#NB: zdisp is || to major axis & phi is rotation of minor axis
#thus shift from beam to ellipse center is [Z*sin(phi),-Z*cos(phi)]
centp = [elcent[0]+zdisp*sind(phi),elcent[1]-zdisp*cosd(phi)]
centm = [elcent[0]+zdism*sind(phi),elcent[1]-zdism*cosd(phi)]
#check get same ellipse parms either way
#now do next ring; estimate either way & do a FitDetector each way; best fit is correct one
fail = True
i2 = 1
while fail:
dsp = HKL[i2][3]
G2fil.G2Print ('2nd ring: try %.4f'%(dsp))
tth = 2.0*asind(wave/(2.*dsp))
ellipsep = GetEllipse2(tth,0.,dist,centp,tilt,phi)
G2fil.G2Print (fmt%('plus ellipse :',ellipsep[0][0],ellipsep[0][1],ellipsep[1],ellipsep[2][0],ellipsep[2][1]))
Ringp = makeRing(dsp,ellipsep,3,cutoff,scalex,scaley,G2frame.ImageZ)[0]
parmDict = {'dist':dist,'det-X':centp[0],'det-Y':centp[1],
'tilt':tilt,'phi':phi,'wave':wave,'dep':0.0}
varyList = [item for item in varyDict if varyDict[item]]
if len(Ringp) > 10:
chip = FitDetector(np.array(Ring0+Ringp),varyList,parmDict,True)[0]
tiltp = parmDict['tilt']
phip = parmDict['phi']
centp = [parmDict['det-X'],parmDict['det-Y']]
fail = False
else:
chip = 1e6
ellipsem = GetEllipse2(tth,0.,dist,centm,-tilt,phi)
G2fil.G2Print (fmt%('minus ellipse:',ellipsem[0][0],ellipsem[0][1],ellipsem[1],ellipsem[2][0],ellipsem[2][1]))
Ringm = makeRing(dsp,ellipsem,3,cutoff,scalex,scaley,G2frame.ImageZ)[0]
if len(Ringm) > 10:
parmDict['tilt'] *= -1
chim = FitDetector(np.array(Ring0+Ringm),varyList,parmDict,True)[0]
tiltm = parmDict['tilt']
phim = parmDict['phi']
centm = [parmDict['det-X'],parmDict['det-Y']]
fail = False
else:
chim = 1e6
if fail:
i2 += 1
if chip < chim:
data['tilt'] = tiltp
data['center'] = centp
data['rotation'] = phip
else:
data['tilt'] = tiltm
data['center'] = centm
data['rotation'] = phim
data['ellipses'].append(ellipsep[:]+('b',))
data['rings'].append(np.array(Ringp))
data['ellipses'].append(ellipsem[:]+('r',))
data['rings'].append(np.array(Ringm))
G2plt.PlotImage(G2frame,newImage=True)
if data['DetDepth'] > 0.5: #patch - redefine DetDepth
data['DetDepth'] /= data['distance']
parmDict = {'dist':data['distance'],'det-X':data['center'][0],'det-Y':data['center'][1],
'tilt':data['tilt'],'phi':data['rotation'],'wave':data['wavelength'],'dep':data['DetDepth']}
varyList = [item for item in varyDict if varyDict[item]]
data['rings'] = []
data['ellipses'] = []
for i,H in enumerate(HKL):
dsp = H[3]
tth = 2.0*asind(wave/(2.*dsp))
if tth+abs(data['tilt']) > 90.:
G2fil.G2Print ('next line is a hyperbola - search stopped')
break
if debug: print ('HKLD:'+str(H[:4])+'2-theta: %.4f'%(tth))
elcent,phi,radii = ellipse = GetEllipse(dsp,data)
data['ellipses'].append(copy.deepcopy(ellipse+('g',)))
if debug: print (fmt%('predicted ellipse:',elcent[0],elcent[1],phi,radii[0],radii[1]))
Ring = makeRing(dsp,ellipse,pixLimit,cutoff,scalex,scaley,G2frame.ImageZ)[0]
if Ring:
data['rings'].append(np.array(Ring))
rings = np.concatenate((data['rings']),axis=0)
if i:
chisq = FitDetector(rings,varyList,parmDict,False)[0]
data['distance'] = parmDict['dist']
data['center'] = [parmDict['det-X'],parmDict['det-Y']]
data['rotation'] = parmDict['phi']
data['tilt'] = parmDict['tilt']
data['DetDepth'] = parmDict['dep']
data['chisq'] = chisq
elcent,phi,radii = ellipse = GetEllipse(dsp,data)
if debug: print (fmt2%('fitted ellipse: ',elcent[0],elcent[1],phi,radii[0],radii[1],chisq,len(rings)))
data['ellipses'].append(copy.deepcopy(ellipse+('r',)))
# G2plt.PlotImage(G2frame,newImage=True)
else:
if debug: print ('insufficient number of points in this ellipse to fit')
# break
G2plt.PlotImage(G2frame,newImage=True)
fullSize = len(G2frame.ImageZ)/scalex
if 2*radii[1] < .9*fullSize:
G2fil.G2Print ('Are all usable rings (>25% visible) used? Try reducing Min ring I/Ib')
N = len(data['ellipses'])
if N > 2:
FitDetector(rings,varyList,parmDict)[0]
data['wavelength'] = parmDict['wave']
data['distance'] = parmDict['dist']
data['center'] = [parmDict['det-X'],parmDict['det-Y']]
data['rotation'] = parmDict['phi']
data['tilt'] = parmDict['tilt']
data['DetDepth'] = parmDict['dep']
for H in HKL[:N]:
ellipse = GetEllipse(H[3],data)
data['ellipses'].append(copy.deepcopy(ellipse+('b',)))
G2fil.G2Print ('calibration time = %.3f'%(time.time()-time0))
G2plt.PlotImage(G2frame,newImage=True)
return True
[docs]def Make2ThetaAzimuthMap(data,iLim,jLim): #most expensive part of integration!
'''Makes a set of matrices that provide the 2-theta, azimuth and geometric
correction values for each pixel in an image taking into account the
detector orientation. Can be used for the entire image or a rectangular
section of an image (determined by iLim and jLim).
This is used in two ways. For image integration, the computation is done
over blocks of fixed size (typically 128 or 256 pixels) but for pixel mask
generation, the two-theta matrix for all pixels is computed. Note that
for integration, this routine will be called to generate sections as needed
or may be called by :func:`MakeUseTA`, which creates all sections at
once, so they can be reused multiple times.
:param dict data: GSAS-II image data object (describes the image)
:param list iLim: boundary along x-pixels
:param list jLim: boundary along y-pixels
:returns: TA, array[4,nI,nJ]: 2-theta, azimuth, 2 geometric corrections
'''
pixelSize = data['pixelSize']
scalex = pixelSize[0]/1000.
scaley = pixelSize[1]/1000.
tay,tax = np.mgrid[iLim[0]+0.5:iLim[1]+.5,jLim[0]+.5:jLim[1]+.5] #bin centers not corners
tax = np.asfarray(tax*scalex,dtype=np.float32).flatten()
tay = np.asfarray(tay*scaley,dtype=np.float32).flatten()
nI = iLim[1]-iLim[0]
nJ = jLim[1]-jLim[0]
TA = np.empty((4,nI,nJ))
if data['det2theta']:
TA[:3] = np.array(GetTthAzmG(np.reshape(tax,(nI,nJ)),np.reshape(tay,(nI,nJ)),data)) #includes geom. corr. as dist**2/d0**2 - most expensive step
else:
TA[:3] = np.array(GetTthAzmG2(np.reshape(tax,(nI,nJ)),np.reshape(tay,(nI,nJ)),data)) #includes geom. corr. as dist**2/d0**2 - most expensive step
TA[1] = np.where(TA[1]<0,TA[1]+360,TA[1])
TA[3] = G2pwd.Polarization(data['PolaVal'][0],TA[0],TA[1]-90.)[0]
return TA #2-theta, azimuth & geom. corr. arrays
[docs]def MakeMaskMap(data,masks,iLim,jLim,tamp):
'''Makes a mask array from masking parameters that are not determined by
image calibration parameters or the image intensities. Thus this uses
mask Frames, Polygons and Lines settings (but not Thresholds, Rings or
Arcs). Used on a rectangular section of an image (must be 1024x1024 or
smaller, as dictated by module polymask) where the size is determined
by iLim and jLim.
:param dict data: GSAS-II image data object (describes the image)
:param list iLim: boundary along x-pixels
:param list jLim: boundary along y-pixels
:param np.array tamp: all-zero pixel mask array used in Polymask
:returns: array[nI,nJ] TA: 2-theta, azimuth, 2 geometric corrections
'''
import polymask as pm
pixelSize = data['pixelSize']
scalex = pixelSize[0]/1000.
scaley = pixelSize[1]/1000.
tay,tax = np.mgrid[iLim[0]+0.5:iLim[1]+.5,jLim[0]+.5:jLim[1]+.5] #bin centers not corners
tax = np.asfarray(tax*scalex,dtype=np.float32).flatten()
tay = np.asfarray(tay*scaley,dtype=np.float32).flatten()
nI = iLim[1]-iLim[0]
nJ = jLim[1]-jLim[0]
#make position masks here
frame = masks['Frames']
tam = ma.make_mask_none((nI*nJ))
if frame:
tam = ma.mask_or(tam,ma.make_mask(pm.polymask(nI*nJ,tax,
tay,len(frame),frame,tamp)[:nI*nJ])^True)
polygons = masks['Polygons']
for polygon in polygons:
if polygon:
tam = ma.mask_or(tam,ma.make_mask(pm.polymask(nI*nJ,tax,
tay,len(polygon),polygon,tamp)[:nI*nJ]))
for X,Y,rsq in masks['Points'].T:
tam = ma.mask_or(tam,ma.getmask(ma.masked_less((tax-X)**2+(tay-Y)**2,rsq)))
if tam.shape:
tam = np.reshape(tam,(nI,nJ))
else:
tam = ma.make_mask_none((nI,nJ))
for xline in masks.get('Xlines',[]): #a y pixel position
if iLim[0] <= xline <= iLim[1]:
tam[xline-iLim[0],:] = True
for yline in masks.get('Ylines',[]): #a x pixel position
if jLim[0] <= yline <= jLim[1]:
tam[:,yline-jLim[0]] = True
return tam #position mask
[docs]def Fill2ThetaAzimuthMap(masks,TAr,tam,image):
'''Makes masked intensity correction arrays that depend on image
intensity, 2theta and azimuth. Masking is generated from the
combination of the following:
an array previously generated by :func:`MakeMaskMap` combined with
Thresholds, Rings and Arcs mask input.
These correction arrays are generated for a rectangular section
of an image (must be 1024x1024 or smaller) where the size is
determined the input arrays.
Note that older, less optimized, code has been left commented out below
in case there are future problems or questions.
:param dict masks: GSAS-II mask settings
:param np.array TAr: 2theta/azimuth/correction arrays, reshaped
:param np.array tam: mask array from :func:`MakeMaskMap`
:param np.array image: image array
:returns: a list of 4 masked arrays with values for: azimuth, 2-theta,
intensity/polarization, dist**2/d0**2
'''
tax,tay,tad,pol = TAr #azimuth, 2-theta, dist**2/d0**2, pol
if ma.is_masked(image):
# get prev. masks
mask = ma.getmask(image) # Pixel mask (N.B. mask is True if pixel is masked)
mask |= tam.reshape(image.shape)
image = image.data
else:
mask = tam.reshape(image.shape)
# apply Ring & Arc masks. Note that this could be done in advance
# and be cached (like tam & TAr) but it is not clear this is needed
# often or that a lot of time is saved.
for tth,thick in masks['Rings']:
#tam = ma.mask_or(tam.flatten(),ma.getmask(ma.masked_inside(tay.flatten(),max(0.01,tth-thick/2.),tth+thick/2.)))
mask |= ((tay > max(0.01,tth-thick/2.)) & (tay < (tth+thick/2.)))
for tth,azm,thick in masks['Arcs']:
# tamt = ma.getmask(ma.masked_inside(tay.flatten(),max(0.01,tth-thick/2.),tth+thick/2.))
# tama = ma.getmask(ma.masked_inside(tax.flatten(),azm[0],azm[1]))
# tam = ma.mask_or(tam.flatten(),tamt*tama)
mask |= ((tay > max(0.01,tth-thick/2.)) & (tay < (tth+thick/2.))
& (tax > azm[0]) & (tax < azm[1]))
# apply threshold masks
Zlim = masks['Thresholds'][1]
#taz = ma.masked_outside(image.flatten(),int(Zlim[0]),Zlim[1])
mask |= (image < Zlim[0]) | (image > Zlim[1])
#tam = ma.mask_or(tam.flatten(),ma.getmask(taz))
#tax = ma.compressed(ma.array(tax.flatten(),mask=tam)) #azimuth
#tay = ma.compressed(ma.array(tay.flatten(),mask=tam)) #2-theta
#taz = ma.compressed(ma.array(taz.flatten(),mask=tam)) #intensity
#tad = ma.compressed(ma.array(tad.flatten(),mask=tam)) #dist**2/d0**2
#tabs = ma.compressed(ma.array(tabs.flatten(),mask=tam)) #ones - later used for absorption corr.
#pol = ma.compressed(ma.array(pol.flatten(),mask=tam)) #polarization
#return tax,tay,taz/pol,tad,tabs
mask = ~mask
return tax[mask],tay[mask],image[mask]/pol[mask],tad[mask]
[docs]def MakeUseTA(data,blkSize=128):
'''Precomputes the set of blocked arrays for 2theta-azimuth mapping from
the controls settings of the current image for image integration.
This computation is done optionally, but provides speed as the results
from this can be cached to avoid recomputation for a series of images
with the same calibration parameters.
:param np.array data: specifies parameters for an image
:param int blkSize: a blocksize that is selected for speed
:returns: a list of TA blocks
'''
Nx,Ny = data['size']
nXBlks = (Nx-1)//blkSize+1
nYBlks = (Ny-1)//blkSize+1
useTA = []
for iBlk in range(nYBlks):
iBeg = iBlk*blkSize
iFin = min(iBeg+blkSize,Ny)
useTAj = []
for jBlk in range(nXBlks):
jBeg = jBlk*blkSize
jFin = min(jBeg+blkSize,Nx)
TA = Make2ThetaAzimuthMap(data,(iBeg,iFin),(jBeg,jFin)) #2-theta & azimuth arrays & create position mask
TA = np.dstack((ma.getdata(TA[1]),ma.getdata(TA[0]),ma.getdata(TA[2]),ma.getdata(TA[3]))) #azimuth, 2-theta, dist, pol
TAr = [i.squeeze() for i in np.dsplit(TA,4)] #azimuth, 2-theta, dist**2/d0**2, pol
useTAj.append(TAr)
useTA.append(useTAj)
return useTA
[docs]def MakeUseMask(data,masks,blkSize=128):
'''Precomputes a set of blocked mask arrays for the mask elements
that do not depend on the instrument controls (see :func:`MakeMaskMap`).
This computation is done optionally, but provides speed as the results
from this can be cached to avoid recomputation for a series of images
with the same mask parameters.
:param np.array data: specifies mask parameters for an image
:param int blkSize: a blocksize that is selected for speed
:returns: a list of TA blocks
'''
Masks = copy.deepcopy(masks)
Masks['Points'] = np.array(Masks['Points']).T #get spots as X,Y,R arrays
if np.any(masks['Points']):
Masks['Points'][2] = np.square(Masks['Points'][2]/2.)
Nx,Ny = data['size']
nXBlks = (Nx-1)//blkSize+1
nYBlks = (Ny-1)//blkSize+1
useMask = []
tamp = ma.make_mask_none((1024*1024)) #NB: this array size used in the fortran polymask module
for iBlk in range(nYBlks):
iBeg = iBlk*blkSize
iFin = min(iBeg+blkSize,Ny)
useMaskj = []
for jBlk in range(nXBlks):
jBeg = jBlk*blkSize
jFin = min(jBeg+blkSize,Nx)
mask = MakeMaskMap(data,Masks,(iBeg,iFin),(jBeg,jFin),tamp) #2-theta & azimuth arrays & create position mask
useMaskj.append(mask)
useMask.append(useMaskj)
return useMask
def MakeGainMap(image,Ix,Iy,data,blkSize=128):
import scipy.ndimage.filters as sdif
Iy *= npcosd(Ix[:-1])**3 #undo parallax
Iy *= (1000./data['distance'])**2 #undo r^2 effect
Iy /= np.array(G2pwd.Polarization(data['PolaVal'][0],Ix[:-1],0.)[0]) #undo polarization
if data['Oblique'][1]:
Iy *= G2pwd.Oblique(data['Oblique'][0],Ix[:-1]) #undo penetration
IyInt = scint.interp1d(Ix[:-1],Iy[0],bounds_error=False)
GainMap = np.zeros_like(image,dtype=float)
#do interpolation on all points - fills in the empty bins; leaves others the same
Nx,Ny = data['size']
nXBlks = (Nx-1)//blkSize+1
nYBlks = (Ny-1)//blkSize+1
for iBlk in range(nYBlks):
iBeg = iBlk*blkSize
iFin = min(iBeg+blkSize,Ny)
for jBlk in range(nXBlks):
jBeg = jBlk*blkSize
jFin = min(jBeg+blkSize,Nx)
TA = Make2ThetaAzimuthMap(data,(iBeg,iFin),(jBeg,jFin)) #2-theta & azimuth arrays & create position mask
Ipix = IyInt(TA[0])
GainMap[iBeg:iFin,jBeg:jFin] = image[iBeg:iFin,jBeg:jFin]/(Ipix*TA[3])
GainMap = np.nan_to_num(GainMap,1.0)
GainMap = sdif.gaussian_filter(GainMap,3.,order=0)
return 1./GainMap
[docs]def ImageIntegrate(image,data,masks,blkSize=128,returnN=False,useTA=None,useMask=None):
'''Integrate an image; called from ``OnIntegrate()`` and
``OnIntegrateAll()`` inside :func:`GSASIIimgGUI.UpdateImageControls`
as well as :meth:`GSASIIscriptable.G2Image.Integrate`.
:param np.array image: contains the 2-D image
:param np.array data: specifies controls/calibration parameters for an image
:param np.array masks: specifies masks parameters for an image
:param int blkSize: a blocksize that is selected for speed
:param bool returnN: If True, causes an extra matrix (NST) to be
returned. The default is False.
:param np.array useTA: contains a cached set of blocked
2theta/azimuth/correction matrices (see :func:`MakeUseTA`) for the
current image.
The default, None, causes this to be computed as needed.
:param np.array useMask: contains a cached set of blocked masks (see
:func:`MakeUseMask`) for the current image.
The default, None, causes this to be computed as needed.
:returns: list ints, azms, Xvals, cancel (or ints, azms, Xvals,
NST, cancel if returnN is True), where azms is a list of ``M`` azimuth
values that were requested for integration, ints is a list ``M`` arrays
of diffraction intensities (where each array of diffraction data is
length ``N``), Xvals is an array of "x" values, 2theta, Q, log(q)
(determined by data['binType']), also of length ``N``. Variable cancel
will always be False, since a status window is no longer supported.
'''
#for q, log(q) bins need data['binType']
import histogram2d as h2d
G2fil.G2Print ('Beginning image integration; image range: %d %d'%(np.min(image),np.max(image)))
CancelPressed = False
LUtth = np.array(data['IOtth'])
LRazm = np.array(data['LRazimuth'],dtype=np.float64)
numAzms = data['outAzimuths']
numChans = (data['outChannels']//4)*4
Dazm = (LRazm[1]-LRazm[0])/numAzms
if '2-theta' in data.get('binType','2-theta'):
lutth = LUtth
elif 'log(q)' in data['binType']:
lutth = np.log(4.*np.pi*npsind(LUtth/2.)/data['wavelength'])
elif 'q' == data['binType'].lower():
lutth = 4.*np.pi*npsind(LUtth/2.)/data['wavelength']
dtth = (lutth[1]-lutth[0])/numChans
muT = data.get('SampleAbs',[0.0,''])[0]
if data['DetDepth'] > 0.5: #patch - redefine DetDepth
data['DetDepth'] /= data['distance']
if 'SASD' in data['type']:
muT = -np.log(muT)/2. #Transmission to 1/2 thickness muT
Masks = copy.deepcopy(masks)
Masks['Points'] = np.array(Masks['Points']).T #get spots as X,Y,R arrays
if np.any(masks['Points']):
Masks['Points'][2] = np.square(Masks['Points'][2]/2.)
NST = np.zeros(shape=(numAzms,numChans),order='F',dtype=np.float32)
H0 = np.zeros(shape=(numAzms,numChans),order='F',dtype=np.float32)
H2 = np.linspace(lutth[0],lutth[1],numChans+1)
Nx,Ny = data['size']
nXBlks = (Nx-1)//blkSize+1
nYBlks = (Ny-1)//blkSize+1
tbeg = time.time()
times = [0,0,0,0,0]
tamp = ma.make_mask_none((1024*1024)) #NB: this array size used in the fortran histogram2d
for iBlk in range(nYBlks):
iBeg = iBlk*blkSize
iFin = min(iBeg+blkSize,Ny)
for jBlk in range(nXBlks):
jBeg = jBlk*blkSize
jFin = min(jBeg+blkSize,Nx)
# next is most expensive step!
t0 = time.time()
if useTA:
TAr = useTA[iBlk][jBlk]
else:
TA = Make2ThetaAzimuthMap(data,(iBeg,iFin),(jBeg,jFin)) #2-theta & azimuth arrays & create position mask
TA = np.dstack((ma.getdata(TA[1]),ma.getdata(TA[0]),ma.getdata(TA[2]),ma.getdata(TA[3]))) #azimuth, 2-theta, dist, pol
TAr = [i.squeeze() for i in np.dsplit(TA,4)] #azimuth, 2-theta, dist**2/d0**2, pol
times[1] += time.time()-t0 #xy->th,azm
t0 = time.time()
if useMask:
tam = useMask[iBlk][jBlk]
else:
tam = MakeMaskMap(data,Masks,(iBeg,iFin),(jBeg,jFin),tamp)
Block = image[iBeg:iFin,jBeg:jFin] # image Pixel mask has been applied here
tax,tay,taz,tad = Fill2ThetaAzimuthMap(Masks,TAr,tam,Block) #applies remaining masks
times[0] += time.time()-t0 # time mask application
t0 = time.time()
tax = np.where(tax > LRazm[1],tax-360.,tax) #put azm inside limits if possible
tax = np.where(tax < LRazm[0],tax+360.,tax)
if data.get('SampleAbs',[0.0,''])[1]:
if 'Cylind' in data['SampleShape']:
muR = muT*(1.+npsind(tax)**2/2.)/(npcosd(tay)) #adjust for additional thickness off sample normal
tabs = G2pwd.Absorb(data['SampleShape'],muR,tay)
elif 'Fixed' in data['SampleShape']: #assumes flat plate sample normal to beam
tabs = G2pwd.Absorb('Fixed',muT,tay)
else:
tabs = np.ones_like(taz)
else:
tabs = np.ones_like(taz)
if 'log(q)' in data.get('binType',''):
tay = np.log(4.*np.pi*npsind(tay/2.)/data['wavelength'])
elif 'q' == data.get('binType','').lower():
tay = 4.*np.pi*npsind(tay/2.)/data['wavelength']
times[2] += time.time()-t0 #fill map
t0 = time.time()
taz = np.array((taz*tad/tabs),dtype='float32')
if any([tax.shape[0],tay.shape[0],taz.shape[0]]):
NST,H0 = h2d.histogram2d(len(tax),tax,tay,taz,
numAzms,numChans,LRazm,lutth,Dazm,dtth,NST,H0)
times[3] += time.time()-t0 #binning
G2fil.G2Print('End integration loops')
t0 = time.time()
#prepare masked arrays of bins with pixels for interpolation setup
H2msk = [ma.array(H2[:-1],mask=np.logical_not(nst)) for nst in NST]
# H0msk = [ma.array(np.divide(h0,nst),mask=np.logical_not(nst)) for nst,h0 in zip(NST,H0)]
H0msk = [ma.array(h0/nst,mask=np.logical_not(nst)) for nst,h0 in zip(NST,H0)]
#make linear interpolators; outside limits give NaN
H0 = []
problemEntries = []
for i,(h0msk,h2msk) in enumerate(zip(H0msk,H2msk)):
try:
h0int = scint.interp1d(h2msk.compressed(),h0msk.compressed(),bounds_error=False)
#do interpolation on all points - fills in the empty bins; leaves others the same
h0 = h0int(H2[:-1])
H0.append(h0)
except ValueError:
problemEntries.append(i)
H0.append(np.zeros(numChans,order='F',dtype=np.float32))
H0 = np.nan_to_num(np.array(H0))
if 'log(q)' in data.get('binType',''):
H2 = 2.*npasind(np.exp(H2)*data['wavelength']/(4.*np.pi))
elif 'q' == data.get('binType','').lower():
H2 = 2.*npasind(H2*data['wavelength']/(4.*np.pi))
if Dazm:
H1 = np.array([azm for azm in np.linspace(LRazm[0],LRazm[1],numAzms+1)])
else:
H1 = LRazm
if 'SASD' not in data['type']:
H0 *= np.array(G2pwd.Polarization(data['PolaVal'][0],H2[:-1],0.)[0])
H0 /= np.abs(npcosd(H2[:-1]-np.abs(data['det2theta']))) #parallax correction
H0 *= (data['distance']/1000.)**2 #remove r^2 effect
if 'SASD' in data['type']:
H0 /= npcosd(H2[:-1]) #one more for small angle scattering data?
if data['Oblique'][1]:
H0 /= G2pwd.Oblique(data['Oblique'][0],H2[:-1])
times[4] += time.time()-t0 #cleanup
G2fil.G2Print ('Step times: \n apply masks %8.3fs xy->th,azm %8.3fs fill map %8.3fs \
\n binning %8.3fs cleanup %8.3fs'%(times[0],times[1],times[2],times[3],times[4]))
G2fil.G2Print ("Integration complete. Elapsed time:","%8.3fs"%(time.time()-tbeg))
if problemEntries:
msg = ""
for i in problemEntries:
if msg: msg += ', '
msg += "{:.2f}".format((H1[i+1]+H1[i])/2.)
print('\nWarning, integrations have no pixels at these azimuth(s)\n\t',msg,'\n')
if returnN: #As requested by Steven Weigand
return H0,H1,H2,NST,CancelPressed
else:
return H0,H1,H2,CancelPressed
def MakeStrStaRing(ring,Image,Controls):
ellipse = GetEllipse(ring['Dset'],Controls)
pixSize = Controls['pixelSize']
scalex = 1000./pixSize[0]
scaley = 1000./pixSize[1]
Ring = np.array(makeRing(ring['Dset'],ellipse,ring['pixLimit'],ring['cutoff'],scalex,scaley,Image)[0]).T #returns x,y,dsp for each point in ring
if len(Ring):
ring['ImxyObs'] = copy.copy(Ring[:2])
TA = GetTthAzm(Ring[0],Ring[1],Controls) #convert x,y to tth,azm
TA[0] = Controls['wavelength']/(2.*npsind(TA[0]/2.)) #convert 2th to d
ring['ImtaObs'] = TA
ring['ImtaCalc'] = np.zeros_like(ring['ImtaObs'])
Ring[0] = TA[0]
Ring[1] = TA[1]
return Ring,ring
else:
ring['ImxyObs'] = [[],[]]
ring['ImtaObs'] = [[],[]]
ring['ImtaCalc'] = [[],[]]
return [],[] #bad ring; no points found
[docs]def FitStrSta(Image,StrSta,Controls):
'Needs a doc string'
StaControls = copy.deepcopy(Controls)
phi = StrSta['Sample phi']
wave = Controls['wavelength']
pixelSize = Controls['pixelSize']
scalex = 1000./pixelSize[0]
scaley = 1000./pixelSize[1]
StaType = StrSta['Type']
StaControls['distance'] += StrSta['Sample z']*cosd(phi)
for ring in StrSta['d-zero']: #get observed x,y,d points for the d-zeros
dset = ring['Dset']
Ring,R = MakeStrStaRing(ring,Image,StaControls)
if len(Ring):
ring.update(R)
p0 = ring['Emat']
val,esd,covMat = FitStrain(Ring,p0,dset,wave,phi,StaType)
ring['Emat'] = val
ring['Esig'] = esd
ellipse = FitEllipse(R['ImxyObs'].T)
ringxy,ringazm = makeRing(ring['Dcalc'],ellipse,0,0.,scalex,scaley,Image)
ring['ImxyCalc'] = np.array(ringxy).T[:2]
ringint = np.array([float(Image[int(x*scalex),int(y*scaley)]) for y,x in np.array(ringxy)[:,:2]])
ringint /= np.mean(ringint)
ring['Ivar'] = np.var(ringint)
ring['covMat'] = covMat
G2fil.G2Print ('Variance in normalized ring intensity: %.3f'%(ring['Ivar']))
CalcStrSta(StrSta,Controls)
def IntStrSta(Image,StrSta,Controls):
StaControls = copy.deepcopy(Controls)
pixelSize = Controls['pixelSize']
scalex = 1000./pixelSize[0]
scaley = 1000./pixelSize[1]
phi = StrSta['Sample phi']
StaControls['distance'] += StrSta['Sample z']*cosd(phi)
RingsAI = []
for ring in StrSta['d-zero']: #get observed x,y,d points for the d-zeros
Ring,R = MakeStrStaRing(ring,Image,StaControls)
if len(Ring):
ellipse = FitEllipse(R['ImxyObs'].T)
ringxy,ringazm = makeRing(ring['Dcalc'],ellipse,0,0.,scalex,scaley,Image,5)
XY = np.array(ringxy).T
Th,Azm = GetTthAzm(XY[0],XY[1],Controls)
pola = G2pwd.Polarization(Controls['PolaVal'][0],Th,Azm-90.)[0] #get pola not dpola
ring['ImxyCalc'] = np.array(ringxy).T[:2]
ringint = np.array([float(Image[int(x*scalex),int(y*scaley)]) for y,x in np.array(ringxy)[:,:2]])
ringint /= np.mean(ringint)
ringint /= pola[0] #just 1st column
G2fil.G2Print (' %s %.3f %s %.3f %s %d'%('d-spacing',ring['Dcalc'],'sig(MRD):',np.sqrt(np.var(ringint)),'# points:',len(ringint)))
RingsAI.append(np.array(list(zip(ringazm,ringint))).T)
return RingsAI
def CalcStrSta(StrSta,Controls):
wave = Controls['wavelength']
phi = StrSta['Sample phi']
StaType = StrSta['Type']
for ring in StrSta['d-zero']:
Eij = ring['Emat']
E = [[Eij[0],Eij[1],0],[Eij[1],Eij[2],0],[0,0,0]]
th,azm = ring['ImtaObs']
th0 = np.ones_like(azm)*npasind(wave/(2.*ring['Dset']))
V = -np.sum(np.sum(E*calcFij(90.,phi,azm,th0).T/1.e6,axis=2),axis=1)
if StaType == 'True':
ring['ImtaCalc'] = np.array([np.exp(V)*ring['Dset'],azm])
else:
ring['ImtaCalc'] = np.array([(V+1.)*ring['Dset'],azm])
dmin = np.min(ring['ImtaCalc'][0])
dmax = np.max(ring['ImtaCalc'][0])
if ring.get('fixDset',True):
if abs(Eij[0]) < abs(Eij[2]): #tension
ring['Dcalc'] = dmin+(dmax-dmin)/4.
else: #compression
ring['Dcalc'] = dmin+3.*(dmax-dmin)/4.
else:
ring['Dcalc'] = np.mean(ring['ImtaCalc'][0])
[docs]def calcFij(omg,phi,azm,th):
''' Uses parameters as defined by Bob He & Kingsley Smith, Adv. in X-Ray Anal. 41, 501 (1997)
:param omg: his omega = sample omega rotation; 0 when incident beam || sample surface,
90 when perp. to sample surface
:param phi: his phi = sample phi rotation; usually = 0, axis rotates with omg.
:param azm: his chi = azimuth around incident beam
:param th: his theta = theta
'''
a = npsind(th)*npcosd(omg)+npsind(azm)*npcosd(th)*npsind(omg)
b = -npcosd(azm)*npcosd(th)
c = npsind(th)*npsind(omg)-npsind(azm)*npcosd(th)*npcosd(omg)
d = a*npsind(phi)+b*npcosd(phi)
e = a*npcosd(phi)-b*npsind(phi)
Fij = np.array([
[d**2,d*e,c*d],
[d*e,e**2,c*e],
[c*d,c*e,c**2]])
return -Fij
[docs]def FitStrain(rings,p0,dset,wave,phi,StaType):
'Fits external strain tensor from distortion of Bragg rings in images'
def StrainPrint(ValSig,dset):
print ('Strain tensor for Dset: %.6f'%(dset))
ptlbls = 'names :'
ptstr = 'values:'
sigstr = 'esds :'
for name,fmt,value,sig in ValSig:
ptlbls += "%s" % (name.rjust(12))
ptstr += fmt % (value)
if sig:
sigstr += fmt % (sig)
else:
sigstr += 12*' '
print (ptlbls)
print (ptstr)
print (sigstr)
def strainCalc(p,xyd,dset,wave,phi,StaType):
E = np.array([[p[0],p[1],0],[p[1],p[2],0],[0,0,0]])
dspo,azm,dsp = xyd
th = npasind(wave/(2.0*dspo))
V = -np.sum(np.sum(E*calcFij(90.,phi,azm,th).T/1.e6,axis=2),axis=1)
if StaType == 'True':
dspc = dset*np.exp(V)
else:
dspc = dset*(V+1.)
return dspo-dspc
names = ['e11','e12','e22']
fmt = ['%12.2f','%12.2f','%12.2f']
result = leastsq(strainCalc,p0,args=(rings,dset,wave,phi,StaType),full_output=True)
vals = list(result[0])
chisq = np.sum(result[2]['fvec']**2)/(rings.shape[1]-3) #reduced chi^2 = M/(Nobs-Nvar)
covM = result[1]
covMatrix = covM*chisq
sig = list(np.sqrt(chisq*np.diag(result[1])))
ValSig = zip(names,fmt,vals,sig)
StrainPrint(ValSig,dset)
return vals,sig,covMatrix
[docs]def FitImageSpots(Image,ImMax,ind,pixSize,nxy,spotSize=1.0):
'''Used with "s" key in image plots to search for spot masks
'''
def calcMean(nxy,pixSize,img):
gdx,gdy = np.mgrid[0:nxy,0:nxy]
gdx = ma.array((gdx-nxy//2)*pixSize[0]/1000.,mask=~ma.getmaskarray(ImBox))
gdy = ma.array((gdy-nxy//2)*pixSize[1]/1000.,mask=~ma.getmaskarray(ImBox))
posx = ma.sum(gdx)/ma.count(gdx)
posy = ma.sum(gdy)/ma.count(gdy)
return posx,posy
def calcPeak(values,nxy,pixSize,img):
back,mag,px,py,sig = values
peak = np.zeros([nxy,nxy])+back
nor = 1./(2.*np.pi*sig**2)
gdx,gdy = np.mgrid[0:nxy,0:nxy]
gdx = (gdx-nxy//2)*pixSize[0]/1000.
gdy = (gdy-nxy//2)*pixSize[1]/1000.
arg = (gdx-px)**2+(gdy-py)**2
peak += mag*nor*np.exp(-arg/(2.*sig**2))
return ma.compressed(img-peak)/np.sqrt(ma.compressed(img))
def calc2Peak(values,nxy,pixSize,img):
back,mag,px,py,sigx,sigy,rho = values
peak = np.zeros([nxy,nxy])+back
nor = 1./(2.*np.pi*sigx*sigy*np.sqrt(1.-rho**2))
gdx,gdy = np.mgrid[0:nxy,0:nxy]
gdx = (gdx-nxy//2)*pixSize[0]/1000.
gdy = (gdy-nxy//2)*pixSize[1]/1000.
argnor = -1./(2.*(1.-rho**2))
arg = (gdx-px)**2/sigx**2+(gdy-py)**2/sigy**2-2.*rho*(gdx-px)*(gdy-py)/(sigx*sigy)
peak += mag*nor*np.exp(argnor*arg)
return ma.compressed(img-peak)/np.sqrt(ma.compressed(img))
nxy2 = nxy//2
ImBox = Image[ind[1]-nxy2:ind[1]+nxy2+1,ind[0]-nxy2:ind[0]+nxy2+1]
back = np.min(ImBox)
mag = np.sum(ImBox-back)
vals = [back,mag,0.,0.,0.2,0.2,0.]
ImBox = ma.array(ImBox,dtype=float,mask=ImBox>0.75*ImMax)
px = (ind[0]+.5)*pixSize[0]/1000.
py = (ind[1]+.5)*pixSize[1]/1000.
if ma.any(ma.getmaskarray(ImBox)):
vals = calcMean(nxy,pixSize,ImBox)
posx,posy = [px+vals[0],py+vals[1]]
return [posx,posy,spotSize]
else:
result = leastsq(calc2Peak,vals,args=(nxy,pixSize,ImBox),full_output=True)
vals = result[0]
ratio = vals[4]/vals[5]
if 0.5 < ratio < 2.0 and vals[2] < 2. and vals[3] < 2.:
posx,posy = [px+vals[2],py+vals[3]]
return [posx,posy,min(6.*vals[4],spotSize)]
else:
return None
# Original version
# def AutoPixelMask(Image,Masks,Controls,numChans,dlg=None):
# '''Find "bad" regions on an image and remove them with a pixel mask.
# This works by masking pixels that are well outside the range of the
# radial average.
# Original version from RBVD, takes 1-5 min per image. No longer in use.
# '''
# #if GSASIIpath.GetConfigValue('debug'): print('original AutoPixelMask starting')
# frame = Masks['Frames']
# tam = ma.make_mask_none(Image.shape)
# if frame:
# tam = ma.mask_or(tam,MakeFrameMask(Controls,frame))
# LUtth = np.array(Controls['IOtth'])
# dtth = (LUtth[1]-LUtth[0])/numChans
# esdMul = Masks['SpotMask']['esdMul']
# prob = 100.*sc.erf(esdMul/np.sqrt(2.))
# print(' Spots greater than %.2f of band intensity are masked'%prob)
# mask = ma.make_mask_none(Image.shape)
# band = ma.array(Image,mask=ma.nomask)
# TA = Make2ThetaAzimuthMap(Controls,(0,Image.shape[0]),(0,Image.shape[1]))[0] #2-theta array
# TThs = np.linspace(LUtth[0],LUtth[1],numChans,False)
# for it,TTh in enumerate(TThs):
# band.mask = ma.masked_outside(TA,TTh,TTh+dtth).mask+tam
# pcmax = np.percentile(band.compressed(),[prob,50.])
# mband = ma.masked_greater(band,pcmax[0])
# std = ma.std(mband)
# anom = ma.masked_greater((band-pcmax[1])/std,esdMul)
# mask ^= (anom.mask^band.mask)
# if not dlg is None:
# GoOn = dlg.Update(it,newmsg='Processed 2-theta rings = %d'%(it))
# if not GoOn[0]:
# break
# return mask
[docs]def TestFastPixelMask():
'''Test if the fast (C) version of Auto Pixel Masking is available.
:returns: True if the airxd.mask package can be imported; False otherwise.
'''
try:
import fmask
except ModuleNotFoundError:
if GSASIIpath.GetConfigValue('debug'): print('fmask not found')
return False
return True
[docs]def FastAutoPixelMask(Image, Masks, Controls, numChans, dlg=None):
'''Find "bad" regions on an image and create a pixel mask to remove them.
This works by masking pixels that are m*sigma outside the range of the
median at that radial distance using the using the fmask C module (based on the
AIRXD C++ code https://github.com/AdvancedPhotonSource/AIRXD-ML-PUB, developed
by Howard Yanxon, Wenqian Xu and James Weng.)
This is much faster than AutoPixelMask, which does pretty much the
same computation, but uses pure Python/numpy code.
Called from GSASIIimgGUI.UpdateMasks.OnFindPixelMask (single image)
and GSASIIimgGUI.UpdateMasks.OnAutoFindPixelMask (multiple images)
[see :func:`GSASIIimgGUI.UpdateMasks`]
:param np.array Image: 2D data structure describing a diffaction image
:param dict Masks: contents of Masks data tree
:param dict Controls: diffraction & calibration parameters for image from
IMG data tree entry
:param int numChans: number of channels in eventual 2theta pattern
after integration
:returns: a bool mask array with the same shape as Image
'''
try:
import fmask
if GSASIIpath.GetConfigValue('debug'): print('Loaded fmask from',fmask.__file__)
except:
return None
frame = Masks['Frames']
tam = ma.make_mask_none(Image.shape)
if frame:
tam = ma.mask_or(tam,MakeFrameMask(Controls,frame))
ttmin = float(Masks['SpotMask'].get('SearchMin',0.0))
ttmax = float(Masks['SpotMask'].get('SearchMax',180.0))
esdMul = float(Masks['SpotMask']['esdMul'])
TA = Make2ThetaAzimuthMap(Controls, (0, Image.shape[0]), (0, Image.shape[1]))[0]
LUtth = np.array(Controls['IOtth'])
TThs = np.linspace(LUtth[0], LUtth[1], numChans, False)
# fp = open('/tmp/maskdump.pickle','wb') # make external test file
# import pickle
# pickle.dump(tam,fp) # frame mask
# pickle.dump(TA,fp) # 2theta values
# pickle.dump(Image,fp) # image values
# pickle.dump(TThs,fp) # 2theta bins
# fp.close()
print(' Fast mask: Spots greater or less than %.1f of median abs deviation are masked'%esdMul)
outMask = np.zeros_like(tam,dtype=bool).ravel()
if dlg: dlg.Update(10,"Fast scan in progress")
try:
masked = fmask.mask(esdMul, tam.ravel(), TA.ravel(), Image.ravel(), TThs, outMask, ttmin, ttmax)
except Exception as msg:
print('Exception in fmask.mask\n\t',msg)
return outMask.reshape(Image.shape)
[docs]def AutoPixelMask(Image, Masks, Controls, numChans, dlg=None):
'''Find "bad" regions on an image and creata a pixel mask to remove them.
This works by masking pixels that are well outside the range of the
median at that radial distance.
This is ~4x faster than the original version from RBVD.
Developed by Howard Yanxon, Wenqian Xu and James Weng.
Called from GSASIIimgGUI.UpdateMasks.OnFindPixelMask (single image)
and GSASIIimgGUI.UpdateMasks.OnAutoFindPixelMask (multiple images)
[see :func:`GSASIIimgGUI.UpdateMasks`]
:param np.array Image: 2D data structure describing a diffaction image
:param dict Masks: contents of Masks data tree
:param dict Controls: diffraction & calibration parameters for image from
IMG data tree entry
:param int numChans: number of channels in eventual 2theta pattern
after integration
:param wx.Dialog dlg: a widget that can be used to show the status of
the pixel mask scan and can optionally be used to cancel the scan. If
dlg=None then this is ignored (for non-GUI use).
:returns: a mask array with the same shape as Image or None if the
the scan is cancelled from the dlg Dialog.
'''
#if GSASIIpath.GetConfigValue('debug'): print('faster all-Python AutoPixelMask')
try:
from scipy.stats import median_abs_deviation as newMAD # new in 1.5.0
def MAD(args,**kwargs):
kwargs['scale'] = 1.4826
return newMAD(args,**kwargs)
except ImportError:
try:
from scipy.stats import median_absolute_deviation as MAD
if GSASIIpath.GetConfigValue('debug'):
print('Using deprecated scipy.stats.median_absolute_deviation routine')
except:
print('Unable to load scipy.stats.median_abs_deviation')
return None
frame = Masks['Frames']
tam = ma.make_mask_none(Image.shape)
if frame:
tam = ma.mask_or(tam,MakeFrameMask(Controls,frame))
LUtth = np.array(Controls['IOtth'])
dtth = (LUtth[1]-LUtth[0])/numChans
esdMul = Masks['SpotMask']['esdMul']
print(' Spots greater or less than %.1f of median abs deviation are masked'%esdMul)
band = np.array(Image)
TA = Make2ThetaAzimuthMap(Controls, (0, Image.shape[0]), (0, Image.shape[1]))[0]
TThs = np.linspace(LUtth[0], LUtth[1], numChans, False)
mask = (TA >= LUtth[1]) | (TA < LUtth[0]) | tam
for it in range(len(TThs)):
masker = (TA >= TThs[it]) & (TA < TThs[it]+dtth) & ~tam
if (TThs[it] < Masks['SpotMask'].get('SearchMin',0.0) or
TThs[it] > Masks['SpotMask'].get('SearchMax',180.0)):
mask |= masker
continue
bin = band[masker]
if np.all(np.isnan(bin)):
continue
if bin.size < 1:
continue
median = np.nanmedian(bin)
mad = MAD(bin, nan_policy='omit')
anom = np.abs(band-median)/mad <= esdMul
mask |= (anom & masker)
#print(TThs[it],sum(sum(masker))- sum(sum(anom & masker)))
if not dlg is None:
GoOn = dlg.Update(it,newmsg='Processed 2-theta rings = %d'%(it))
if not GoOn[0]:
return None
#break
return ~mask
[docs]def DoPolaCalib(ImageZ,imageData,arcTth):
''' Determine image polarization by successive integrations with & without preset arc mask.
After initial search, does a set of five with offset azimuth to get mean(std) result.
'''
from scipy.optimize import minimize_scalar
print(' Polarization fit for mask at %.1f deg 2-theta'%arcTth)
data = copy.deepcopy(imageData)
data['IOtth'] = [arcTth-2.,arcTth+2.]
data['fullIntegrate'] = True
data['LRazimuth'] = [0.,360.]
data['outChannels'] = 200
data['outAzimuths'] = 1
Arc = [arcTth,[85.,95.],2.0]
print(' Integration 2-theta test range %.1f - %.1f in 200 steps'%(data['IOtth'][0],data['IOtth'][1]))
print(' Mask azimuth range: %.1f - %.1f'%(Arc[1][0],Arc[1][1]))
Masks = {'Points':[],'Rings':[],'Arcs':[],'Polygons':[],'Frames':[],
'Thresholds':imageData['range'],'SpotMask':{'esdMul':3.,'spotMask':None}}
AMasks = {'Points':[],'Rings':[],'Arcs':[Arc,],'Polygons':[],'Frames':[],
'Thresholds':imageData['range'],'SpotMask':{'esdMul':3.,'spotMask':None}}
def func(p):
p = min(1.0,max(p,0.0))
data['PolaVal'][0] = p
H0 = ImageIntegrate(ImageZ,data,Masks)[0]
H0A = ImageIntegrate(ImageZ,data,AMasks)[0]
M = np.sum(H0)-np.sum(H0A)
print(' Polarization %.4f, fxn: %.1f'%(p,M))
return M**2
time0 = time.time()
res = minimize_scalar(func,bracket=[1.,.999],tol=.0001)
print(res)
pola = min(1.0,max(res.x,.0))
Pola = []
for arc in [75,80,85,90,95]:
Arc = [arcTth,[arc,arc+10.],2.0]
AMasks = {'Points':[],'Rings':[],'Arcs':[Arc,],'Polygons':[],'Frames':[],
'Thresholds':imageData['range'],'SpotMask':{'esdMul':3.,'spotMask':None}}
res = minimize_scalar(func,bracket=[pola-.001,pola],tol=.0001)
print(' Mask azimuth range: %.1f - %.1f'%(Arc[1][0],Arc[1][1]))
print(' pola: %.5f'%res.x)
Pola.append(res.x)
Pola = np.array(Pola)
mean = np.mean(Pola)
std = int(10000*np.std(Pola))
print(' Polarization: %.4f(%d)'%(mean,std))
print(' time: %.2fs'%(time.time()-time0))
imageData['PolaVal'][0] = mean