-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathfittools.py
More file actions
236 lines (200 loc) · 7.58 KB
/
fittools.py
File metadata and controls
236 lines (200 loc) · 7.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
import numpy as np
import numexpr as ne
from scipy.optimize import curve_fit, brentq
from scipy.interpolate import interp1d
class Guess(object):
"""
Container of guesses for fitting, used on initial fit guesses and learning.
"""
def __init__(self, peak_ratio = 0.2, sigma_x0 = 0.01, sigma_y0 = 0.01, sigma_x1 = 1, sigma_y1 = 1, offset_ratio = 0.006, fx = 0.03, fy = 0):
self.peak_ratio = peak_ratio
self.sigma_x0 = sigma_x0
self.sigma_y0 = sigma_y0
self.sigma_x1 = sigma_x1
self.sigma_y1 = sigma_y1
self.offset_ratio = offset_ratio
self.fx = fx
self.fy = fy
def find_nearest(array, value):
"""
Find the index of nearest element in array to value.
"""
idx = (np.abs(array-value)).argmin()
return idx
def gaussian(x, a, mu, sigma, c):
"""
Gaussian function
:math:`f(x)=a e^{-(x - \mu)^2 / (2 \\sigma^2)} + c`
ref: https://en.wikipedia.org/wiki/Gaussian_function
Parameters
----------
x : 1D np.array
coordinate
a : float
the height of the curve's peak
mu : float
the position of the center of the peak
sigma : float
the standard deviation, sometimes called the Gaussian RMS width
c : float
non-zero background
Returns
-------
out : 1D np.array
the Gaussian profile
"""
return ne.evaluate('a * exp(-((x - mu) ** 2) / 2 / sigma ** 2) + c')
def guss_gaussian(x):
"""
Find a set of better starting parameters for Gaussian function fitting
Parameters
----------
x : 1D np.array
1D profile of your data
Returns
-------
out : tuple of float
estimated value of (a, mu, sigma, c)
"""
c_guess = (x[0] + x[-1]) / 2
a_guess = x.max() - c_guess
mu_guess = x.argmax()
x_inter = interp1d(np.arange(len(x)), x)
def _(i):
return x_inter(i) - a_guess / 2 - c_guess
try:
sigma_l_guess = brentq(_, 0, mu_guess)
except:
sigma_l_guess = len(x) / 4
try:
sigma_r_guess = brentq(_, mu_guess, len(x) - 1)
except:
sigma_r_guess = 3 * len(x) / 4
return a_guess, mu_guess, (sigma_r_guess -
sigma_l_guess) / 2.35482, c_guess
def fit_gaussian(x, xmin, xmax):
"""
Fit a Gaussian function to x and return its parameters, with mu in [xmin, xmax]
Parameters
----------
x : 1D np.array
1D profile of your data
Returns
-------
out : tuple of float
(a, mu, sigma, c)
"""
p, q = curve_fit(gaussian, np.arange(x.size), x, p0=guss_gaussian(x), bounds=([-np.inf, xmin, -np.inf, -np.inf], [np.inf, xmax, np.inf, np.inf]))
return p
def find_center_by_gaussian_fit(IM, ymin, ymax):
"""
Find image center by fitting the summation along x and y axis of the data to two 1D Gaussian function
"""
y = np.sum(IM, axis=1)
return fit_gaussian(y, ymin, ymax)[1]
def find_center_by_convolution(IM, ymin, ymax):
""" Center the image by convolution of two projections along each axis.
code from the ``linbasex`` juptyer notebook
Parameter
-------
IM: numpy 2D array
image data
Returns
-------
y-center
"""
# projection along axis=0 of image (rows)
QL_raw0 = IM.sum(axis=1)
# autocorrelate projections
conv_0 = np.convolve(QL_raw0, QL_raw0, mode='full')
#Take the first max, should there be several equal maxima.
# 10May16 - axes swapped - check this
return np.argmax(conv_0[ymin*2:ymax*2])/2 + ymin
def find_symmetry_axis(phase, ymin, ymax):
"""
Find symmetry axis of phase spectrum in range [ymin, ymax]. It will try different methods in the following order:
find_center_by_gaussian_fit
find_center_by_convolution
If none of the methods could find a valid symmetry axis, a RuntimeError will be raised.
Return the y index of the symmetry axis.
"""
try :
center = find_center_by_gaussian_fit(phase, ymin, ymax)
return center
except (RuntimeError, ValueError) :
#find_center_by_gaussian_fit failed, just pass to use next method
pass
#find_center_by_convolution always succeeds
center = find_center_by_convolution(phase, ymin, ymax)
return center
def three_peaks_1d(x, a0, x0, sigma_x0, a1, x1, sigma_x1, offset):
"""
The 1D fitting function for fitting three peaks in projection on x axis.
"""
peak0 = gaussian(x, a0, x0, sigma_x0, 0)
peak1 = gaussian(x, a1, x1, sigma_x1, 0)
peakm1 = gaussian(x, a1, 2*x0-x1, sigma_x1, 0)
return ne.evaluate('peak0 + peak1 + peakm1 + offset')
def find_peaks_1d(x, a0, x0, sigma_x0, a1, x1, sigma_x1, offset):
length_x = x.shape[0]
popt,_ = curve_fit(three_peaks_1d, np.arange(length_x), x, p0 = (a0, x0, sigma_x0, a1, x1, sigma_x1, offset),
bounds = ([-np.inf, 0, 0, -np.inf, length_x//2, 0, -np.inf], [np.inf, length_x, np.inf, np.inf, length_x, max(0.01*length_x, 5), np.inf]))
#needs to limit sigma to avoid unsense results
return popt
def three_peaks(xy_tuple, a0, x0, y0, sigma_x0, sigma_y0, a1, x1, y1, sigma_x1, sigma_y1, offset):
"""
The fitting function of three peaks.
"""
(x, y) = xy_tuple
formula = ('a0*exp((-(x-x0)**2)/(2*sigma_x0**2) + (-(y-y0)**2)/(2*sigma_y0**2))'
'+ a1*exp((-(x-x1)**2)/(2*sigma_x1**2) + (-(y-y1)**2)/(2*sigma_y1**2))'
'+ a1*exp((-(x+x1-2*x0)**2)/(2*sigma_x1**2) + (-(y+y1-2*y0)**2)/(2*sigma_y1**2))'
'+ offset'
)
return ne.evaluate(formula).ravel()
def find_peaks(XYf2d_shifted, guess):
"""
Fit the three peaks in the shifted 2d amplitude spectrum XYf2d_shifted.
Return the phase shift of the secondary peak in x and y direction.
"""
length_x = XYf2d_shifted.shape[1]
length_y = XYf2d_shifted.shape[0]
dXf = 1/length_x
dYf = 1/length_y
a0 = np.max(XYf2d_shifted) #compose initial fit condition from guess
x0 = length_x//2
y0 = length_y//2
a1 = guess.peak_ratio*a0
x1 = x0 + guess.fx/dXf
y1 = y0 + guess.fy/dYf
offset = guess.offset_ratio*a0
initial_guess = (a0, x0, y0, guess.sigma_x0, guess.sigma_y0, a1, x1, y1, guess.sigma_x1, guess.sigma_y1, offset)
x, y = np.meshgrid(np.arange(length_x), np.arange(length_y))
popt,_ = curve_fit(three_peaks, (x, y), XYf2d_shifted.ravel(), p0=initial_guess,
bounds = ([0, 0, 0, 0, 0, 0, length_x//2, 0, 0, 0, 0],
[np.inf, length_x, length_y, np.inf, np.inf, np.inf, length_x, length_y, max(0.01*length_x, 5), max(0.01*length_y, 5), np.inf]))
#needs to limit sigma to avoid unsense results
fx = (popt[6]-popt[1])*dXf
fy = (popt[7]-popt[2])*dYf
newguess = Guess()
newguess.peak_ratio = popt[5]/popt[0] #update guess
newguess.sigma_x0 = popt[3]
newguess.sigma_y0 = popt[4]
newguess.sigma_x1 = popt[8]
newguess.sigma_y1 = popt[9]
newguess.offset_ratio = popt[10]/popt[0]
newguess.fx = fx
newguess.fy = fy
#xband1 = 0.09#100*popt[3]*dXf/0.5 #not used
#xband2 = 0.16#(popt[6]-popt[1]+30*popt[8])*dXf/0.5
#yband = 0.12#80*popt[9]*dYf/0.5
return fx, fy, newguess
def half_image(IM, xcenter):
"""
Generate half of image IM by the image center in the x direction. This function is used to prepare for abel transfrom.
"""
xcenter = int(np.rint(xcenter))
new_width = min(IM.shape[1] - xcenter - 1, xcenter)
left = IM[:, xcenter-new_width:xcenter+1][:, ::-1]
right = IM[:, xcenter:xcenter+new_width+1]
return (left + right) / 2