petal-qc 0.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of petal-qc might be problematic. Click here for more details.
- petal_qc/BTreport/CheckBTtests.py +321 -0
- petal_qc/BTreport/__init__.py +0 -0
- petal_qc/BTreport/bustapeReport.py +144 -0
- petal_qc/__init__.py +14 -0
- petal_qc/metrology/Cluster.py +90 -0
- petal_qc/metrology/DataFile.py +47 -0
- petal_qc/metrology/PetalMetrology.py +327 -0
- petal_qc/metrology/__init__.py +0 -0
- petal_qc/metrology/all2csv.py +57 -0
- petal_qc/metrology/analyze_locking_points.py +597 -0
- petal_qc/metrology/cold_noise.py +106 -0
- petal_qc/metrology/comparisonTable.py +59 -0
- petal_qc/metrology/convert_mitutoyo.py +175 -0
- petal_qc/metrology/convert_smartscope.py +145 -0
- petal_qc/metrology/coreMetrology.py +402 -0
- petal_qc/metrology/data2csv.py +63 -0
- petal_qc/metrology/do_Metrology.py +117 -0
- petal_qc/metrology/flatness4nigel.py +157 -0
- petal_qc/metrology/gtkutils.py +120 -0
- petal_qc/metrology/petal_flatness.py +353 -0
- petal_qc/metrology/show_data_file.py +118 -0
- petal_qc/metrology/testSummary.py +37 -0
- petal_qc/metrology/test_paralelism.py +71 -0
- petal_qc/thermal/CSVImage.py +69 -0
- petal_qc/thermal/DebugPlot.py +76 -0
- petal_qc/thermal/IRBFile.py +768 -0
- petal_qc/thermal/IRCore.py +110 -0
- petal_qc/thermal/IRDataGetter.py +359 -0
- petal_qc/thermal/IRPetal.py +1338 -0
- petal_qc/thermal/IRPetalParam.py +71 -0
- petal_qc/thermal/PetalColorMaps.py +62 -0
- petal_qc/thermal/Petal_IR_Analysis.py +142 -0
- petal_qc/thermal/PipeFit.py +598 -0
- petal_qc/thermal/__init__.py +0 -0
- petal_qc/thermal/analyze_IRCore.py +417 -0
- petal_qc/thermal/contours.py +378 -0
- petal_qc/thermal/create_IRCore.py +185 -0
- petal_qc/thermal/pipe_read.py +182 -0
- petal_qc/thermal/show_IR_petal.py +420 -0
- petal_qc/utils/Geometry.py +756 -0
- petal_qc/utils/Progress.py +182 -0
- petal_qc/utils/__init__.py +0 -0
- petal_qc/utils/all_files.py +35 -0
- petal_qc/utils/docx_utils.py +186 -0
- petal_qc/utils/fit_utils.py +188 -0
- petal_qc/utils/utils.py +180 -0
- petal_qc-0.0.0.dist-info/METADATA +29 -0
- petal_qc-0.0.0.dist-info/RECORD +51 -0
- petal_qc-0.0.0.dist-info/WHEEL +5 -0
- petal_qc-0.0.0.dist-info/entry_points.txt +3 -0
- petal_qc-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Fit the thermal path to the petal pipe."""
|
|
3
|
+
import math
|
|
4
|
+
import random
|
|
5
|
+
from argparse import ArgumentParser
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import matplotlib.path as mplPath
|
|
9
|
+
import matplotlib.pyplot as plt
|
|
10
|
+
import numpy as np
|
|
11
|
+
import numpy.linalg as linalg
|
|
12
|
+
from scipy.optimize import least_squares
|
|
13
|
+
|
|
14
|
+
from petal_qc.thermal import contours
|
|
15
|
+
|
|
16
|
+
# This is an ugly hack to see the fits
|
|
17
|
+
dbg_ax = None
|
|
18
|
+
dbg_fig = None
|
|
19
|
+
n_fig = 0
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PipeFit(object):
|
|
23
|
+
"""Align Pipe from CAD and thermal pipe."""
|
|
24
|
+
|
|
25
|
+
__center = []
|
|
26
|
+
__pipe = []
|
|
27
|
+
__sensors = []
|
|
28
|
+
__cpipe = []
|
|
29
|
+
__insiders = []
|
|
30
|
+
__path = []
|
|
31
|
+
__bb = []
|
|
32
|
+
|
|
33
|
+
def __init__(self, is_front, debug=False):
|
|
34
|
+
"""Initialization."""
|
|
35
|
+
if len(PipeFit.__center) == 0:
|
|
36
|
+
PipeFit.prepare_data()
|
|
37
|
+
|
|
38
|
+
self.data = None
|
|
39
|
+
self.R = None
|
|
40
|
+
self.set_front(is_front)
|
|
41
|
+
self.set_debug(debug)
|
|
42
|
+
self.par_names = ["theta", "Sx", "Sy", "Tx", "Ty"]
|
|
43
|
+
self.bounds = [[-math.pi, 0.8, 0.8, -np.inf, -np.inf],
|
|
44
|
+
[math.pi, 3.0, 3.0, np.inf, np.inf]]
|
|
45
|
+
|
|
46
|
+
def set_front(self, is_front=True):
|
|
47
|
+
"""Sets for a front image.
|
|
48
|
+
|
|
49
|
+
TODO: this needs optimization. Probably implementing a cache with the
|
|
50
|
+
sides already defined.
|
|
51
|
+
|
|
52
|
+
"""
|
|
53
|
+
self.front = is_front
|
|
54
|
+
|
|
55
|
+
iside = 0 if not is_front else 1
|
|
56
|
+
self.pipe = PipeFit.__pipe[iside]
|
|
57
|
+
self.sensors = PipeFit.__sensors[iside]
|
|
58
|
+
self.center = PipeFit.__center[iside]
|
|
59
|
+
self.cpipe = PipeFit.__cpipe[iside]
|
|
60
|
+
self.insiders = PipeFit.__insiders[iside]
|
|
61
|
+
self.pipe_path = PipeFit.__path[iside]
|
|
62
|
+
self.pipe_bb = PipeFit.__bb[iside]
|
|
63
|
+
|
|
64
|
+
def set_debug(self, debug):
|
|
65
|
+
"""Sets debug."""
|
|
66
|
+
global dbg_fig, dbg_ax, n_fig
|
|
67
|
+
if debug and dbg_ax is None:
|
|
68
|
+
self.debug = True
|
|
69
|
+
dbg_fig, dbg_ax = plt.subplots(1, 1, tight_layout=True)
|
|
70
|
+
n_fig = dbg_fig.number
|
|
71
|
+
|
|
72
|
+
self.debug = debug
|
|
73
|
+
|
|
74
|
+
if debug:
|
|
75
|
+
plt.figure(dbg_fig.number)
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def prepare_data():
|
|
79
|
+
"""Generate the global data."""
|
|
80
|
+
script_path = Path(__file__).absolute().parent
|
|
81
|
+
for iside in range(2):
|
|
82
|
+
if iside:
|
|
83
|
+
pipe_file = script_path.joinpath("pipe_front.npz")
|
|
84
|
+
else:
|
|
85
|
+
pipe_file = script_path.joinpath("pipe_back.npz")
|
|
86
|
+
|
|
87
|
+
pipe, sensors = PipeFit.read_pipe_data(pipe_file)
|
|
88
|
+
PipeFit.__pipe.append(pipe)
|
|
89
|
+
PipeFit.__sensors.append(sensors)
|
|
90
|
+
|
|
91
|
+
if iside:
|
|
92
|
+
center = np.mean(pipe[pipe[:, 0] < -100], axis=0)
|
|
93
|
+
else: # back
|
|
94
|
+
center = np.mean(pipe[pipe[:, 0] > 100], axis=0)
|
|
95
|
+
|
|
96
|
+
PipeFit.__center.append(center)
|
|
97
|
+
|
|
98
|
+
cpipe = pipe - center
|
|
99
|
+
PipeFit.__cpipe.append(cpipe)
|
|
100
|
+
|
|
101
|
+
insiders = contours.generate_points_inside_contour(cpipe, 1000)
|
|
102
|
+
PipeFit.__insiders.append(insiders)
|
|
103
|
+
PipeFit.__path.append(mplPath.Path(pipe))
|
|
104
|
+
PipeFit.__bb.append(contours.contour_bounds(pipe))
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def read_pipe_data(pipe_file):
|
|
108
|
+
"""Reads pipe data from given file.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
----
|
|
112
|
+
pipe_file: the path of the file wit hpipe data
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
pipe : the path of the pipe
|
|
117
|
+
sensors: the path of the 9 sensors on a petal side.
|
|
118
|
+
|
|
119
|
+
"""
|
|
120
|
+
DB = np.load(pipe_file)
|
|
121
|
+
sensors = []
|
|
122
|
+
for key, val in DB.items():
|
|
123
|
+
if key == "pipe":
|
|
124
|
+
pipe = val
|
|
125
|
+
else:
|
|
126
|
+
sensors.append(val)
|
|
127
|
+
|
|
128
|
+
return pipe, sensors
|
|
129
|
+
|
|
130
|
+
@staticmethod
|
|
131
|
+
def transform_data(data, M):
|
|
132
|
+
"""Apply the transform to the data.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
----
|
|
136
|
+
data: The data. It is a Nx2 array.
|
|
137
|
+
M: The transform. (theta, Sx, Sy, Tx, Ty, Ox, Oy)
|
|
138
|
+
theta is the rotation
|
|
139
|
+
Sx, Sy the scale
|
|
140
|
+
Tx, Ty the translation
|
|
141
|
+
Ox, Ox the final offset. This is not used in the fit.
|
|
142
|
+
|
|
143
|
+
Returns
|
|
144
|
+
-------
|
|
145
|
+
_type_: _description_
|
|
146
|
+
|
|
147
|
+
"""
|
|
148
|
+
# The rotation
|
|
149
|
+
ct = math.cos(M[0])
|
|
150
|
+
st = math.sin(M[0])
|
|
151
|
+
offset = np.array([M[3], M[4]])
|
|
152
|
+
# T = [(1.0-ct)*offset[0] + st*offset[1], -st*offset[0] + (1.0-ct)*offset[1]]
|
|
153
|
+
D = np.zeros(data.shape)
|
|
154
|
+
for i in range(len(data)):
|
|
155
|
+
X = data[i, :] + offset
|
|
156
|
+
|
|
157
|
+
P = np.array([M[1]*(ct * X[0] - st * X[1]),
|
|
158
|
+
M[2]*(st * X[0] + ct * X[1])])
|
|
159
|
+
|
|
160
|
+
# The perspective transform
|
|
161
|
+
# delta = (P[0]*M[5]-1)*(P[1]*M[6]-1) - P[0]*P[1]*M[5]*M[6]
|
|
162
|
+
# P = P/delta
|
|
163
|
+
D[i, :] = P
|
|
164
|
+
|
|
165
|
+
if len(M) > 5:
|
|
166
|
+
D += np.array([M[5], M[6]])
|
|
167
|
+
|
|
168
|
+
return D
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
def transform_inv(data, M):
|
|
172
|
+
"""Inverse transform.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
----
|
|
176
|
+
data: The data. It is a Nx2 array.
|
|
177
|
+
M: The original transform.
|
|
178
|
+
|
|
179
|
+
Returns
|
|
180
|
+
-------
|
|
181
|
+
data transformed by the inverse of M.
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
"""
|
|
185
|
+
out = np.zeros(data.shape)
|
|
186
|
+
O = np.array([M[5], M[6]])
|
|
187
|
+
|
|
188
|
+
ct = math.cos(-M[0])
|
|
189
|
+
st = math.sin(-M[0])
|
|
190
|
+
offset = np.array([M[3], M[4]])
|
|
191
|
+
scale = np.array([M[1], M[2]])
|
|
192
|
+
for i in range(len(data)):
|
|
193
|
+
X = data[i, :] - O
|
|
194
|
+
X = X/scale
|
|
195
|
+
|
|
196
|
+
P = np.array([(ct * X[0] - st * X[1]),
|
|
197
|
+
(st * X[0] + ct * X[1])])
|
|
198
|
+
|
|
199
|
+
P -= offset
|
|
200
|
+
|
|
201
|
+
out[i, :] = P
|
|
202
|
+
|
|
203
|
+
return out
|
|
204
|
+
|
|
205
|
+
@staticmethod
|
|
206
|
+
def print_transform(M):
|
|
207
|
+
"""Print transform."""
|
|
208
|
+
print("Rotation {:.4f} rad".format(M[0]))
|
|
209
|
+
print("Scale X {:.3f} Y {:.3f}".format(M[1], M[2]))
|
|
210
|
+
print("Trans. {:5.3f}, {:5.3f}".format(M[3], M[4]))
|
|
211
|
+
|
|
212
|
+
if len(M) > 5:
|
|
213
|
+
print("Center {:5.3f}, {:5.3f}".format(M[5], M[6]))
|
|
214
|
+
|
|
215
|
+
@staticmethod
|
|
216
|
+
def get_data_center(data, fraction=0.5, y0=None):
|
|
217
|
+
"""Compute the data center.
|
|
218
|
+
|
|
219
|
+
Assumes it is in the half height, and the X is the average of the points
|
|
220
|
+
in a stripe around that value of Y.
|
|
221
|
+
"""
|
|
222
|
+
if y0 is None:
|
|
223
|
+
bounding_box = contours.contour_bounds(data)
|
|
224
|
+
y0 = fraction*(bounding_box[1]+bounding_box[3])
|
|
225
|
+
|
|
226
|
+
window = 10
|
|
227
|
+
while True:
|
|
228
|
+
stripe = data[np.abs(data[:, 1] - y0) < window]
|
|
229
|
+
if len(stripe) > 5:
|
|
230
|
+
break
|
|
231
|
+
|
|
232
|
+
window += 1
|
|
233
|
+
|
|
234
|
+
m0 = np.mean(stripe, axis=0)
|
|
235
|
+
center = np.array([m0[0], y0])
|
|
236
|
+
return center
|
|
237
|
+
|
|
238
|
+
@staticmethod
|
|
239
|
+
def guess_pipe_type(data, fraction=0.25) -> int:
|
|
240
|
+
"""Make a guess about the pipe type.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
----
|
|
244
|
+
data (np.ndarray): The data
|
|
245
|
+
fraction: The hight in the pipe where to get the average.
|
|
246
|
+
Defaults to 0.25.
|
|
247
|
+
|
|
248
|
+
Returns
|
|
249
|
+
-------
|
|
250
|
+
pipe type: =0 if looks like back
|
|
251
|
+
=1 if looks like front
|
|
252
|
+
|
|
253
|
+
"""
|
|
254
|
+
ix = np.argmin(data[:, 1])
|
|
255
|
+
P0 = data[ix, :]
|
|
256
|
+
|
|
257
|
+
D = data[:, 0:2] - P0
|
|
258
|
+
m0 = np.mean(D, axis=0)
|
|
259
|
+
center = PipeFit.get_data_center(D, y0=m0[1])
|
|
260
|
+
# fig, ax = plt.subplots(1,1)
|
|
261
|
+
# ax.plot(D[:,0], D[:, 1], 'o', label="Data")
|
|
262
|
+
# ax.plot(center[0], center[1], '*', label="Center")
|
|
263
|
+
# ax.plot(m0[0], m0[1], 's', label="Mean")
|
|
264
|
+
# ax.legend()
|
|
265
|
+
# plt.draw()
|
|
266
|
+
# plt.pause(0.00001)
|
|
267
|
+
|
|
268
|
+
if m0[0] > center[0]:
|
|
269
|
+
return 1
|
|
270
|
+
else:
|
|
271
|
+
return 0
|
|
272
|
+
|
|
273
|
+
@staticmethod
|
|
274
|
+
def guess_pipe_angle(data, center):
|
|
275
|
+
"""Get an estimation of th epipe angle."""
|
|
276
|
+
c1 = PipeFit.get_data_center(data, 0.6)
|
|
277
|
+
delta = c1 - center
|
|
278
|
+
angle = math.atan(delta[0]/delta[1])
|
|
279
|
+
return angle
|
|
280
|
+
|
|
281
|
+
@staticmethod
|
|
282
|
+
def get_pipe_bottom(data):
|
|
283
|
+
"""Try to guess the bottom of pipe."""
|
|
284
|
+
bb = contours.contour_bounds(data)
|
|
285
|
+
stripe = data[np.abs(data[:, 1] - bb[3]) < 10]
|
|
286
|
+
m0 = np.mean(stripe, axis=0)
|
|
287
|
+
bottom = np.array([m0[0], bb[3]])
|
|
288
|
+
return bottom
|
|
289
|
+
|
|
290
|
+
def initial_guess(self, data):
|
|
291
|
+
"""Make a first guess of the transform."""
|
|
292
|
+
Mdata = self.get_data_center(data)
|
|
293
|
+
theta = self.guess_pipe_angle(data, Mdata)
|
|
294
|
+
T = -Mdata
|
|
295
|
+
dxd = np.amax(data, axis=0) - np.amin(data, axis=0)
|
|
296
|
+
dxp = np.amax(self.cpipe, axis=0) - np.amin(self.cpipe, axis=0)
|
|
297
|
+
scale = dxp/dxd
|
|
298
|
+
|
|
299
|
+
M = np.array([theta, scale[0], scale[1], T[0], T[1]])
|
|
300
|
+
|
|
301
|
+
if self.debug:
|
|
302
|
+
self.print_transform(M)
|
|
303
|
+
out = self.transform_data(data, M)
|
|
304
|
+
dbg_ax.clear()
|
|
305
|
+
dbg_ax.plot(self.cpipe[:, 0], self.cpipe[:, 1])
|
|
306
|
+
dbg_ax.plot(out[:, 0], out[:, 1])
|
|
307
|
+
dbg_ax.set_title("Initial guess")
|
|
308
|
+
plt.draw()
|
|
309
|
+
plt.pause(0.0001)
|
|
310
|
+
|
|
311
|
+
return M
|
|
312
|
+
|
|
313
|
+
def get_residuals(self, M):
|
|
314
|
+
"""Compute intersecting area."""
|
|
315
|
+
out = self.transform_data(self.data, M)
|
|
316
|
+
use_area = True
|
|
317
|
+
if use_area:
|
|
318
|
+
path = mplPath.Path(out)
|
|
319
|
+
ngood = 0.0
|
|
320
|
+
ntot = float(len(self.insiders))
|
|
321
|
+
for i in range(len(self.insiders)):
|
|
322
|
+
P = self.insiders[i, :]
|
|
323
|
+
if path.contains_point(P):
|
|
324
|
+
ngood += 1
|
|
325
|
+
|
|
326
|
+
area = 300*(ntot - ngood)/ntot
|
|
327
|
+
real_area = 100*ngood/ntot
|
|
328
|
+
|
|
329
|
+
else:
|
|
330
|
+
area = 0.0
|
|
331
|
+
real_area = 0
|
|
332
|
+
|
|
333
|
+
npts = len(self.data)
|
|
334
|
+
D = np.zeros([npts, 2])
|
|
335
|
+
ddd = np.zeros(npts)
|
|
336
|
+
for i in range(npts):
|
|
337
|
+
X = out[i, :]
|
|
338
|
+
dst, P = contours.find_closest_point(X[0], X[1], self.cpipe)
|
|
339
|
+
D[i, :] = P - X
|
|
340
|
+
ddd[i] = dst
|
|
341
|
+
|
|
342
|
+
# return value
|
|
343
|
+
res = D.flatten()*area
|
|
344
|
+
|
|
345
|
+
if self.debug:
|
|
346
|
+
dbg_ax. clear()
|
|
347
|
+
dbg_ax.plot(self.cpipe[:, 0], self.cpipe[:, 1])
|
|
348
|
+
dbg_ax.plot(out[:, 0], out[:, 1])
|
|
349
|
+
dbg_ax.set_title("area {:3f} dist {:.3f}".format(real_area, np.sum(ddd)/npts))
|
|
350
|
+
plt.draw()
|
|
351
|
+
plt.pause(0.0001)
|
|
352
|
+
|
|
353
|
+
return res
|
|
354
|
+
|
|
355
|
+
def get_jaccard_distance(self, M):
|
|
356
|
+
"""Get Jaccard distance."""
|
|
357
|
+
out = self.transform_data(self.data, M)
|
|
358
|
+
out_bb = contours.contour_bounds(out)
|
|
359
|
+
|
|
360
|
+
min_x = min(self.pipe_bb[0], out_bb[0])
|
|
361
|
+
min_y = min(self.pipe_bb[1], out_bb[1])
|
|
362
|
+
max_x = max(self.pipe_bb[2], out_bb[2])
|
|
363
|
+
max_y = max(self.pipe_bb[3], out_bb[3])
|
|
364
|
+
|
|
365
|
+
npts = 0
|
|
366
|
+
n_int = 0.0
|
|
367
|
+
n_union = 0.0
|
|
368
|
+
out_path = mplPath.Path(out)
|
|
369
|
+
while npts < 1000:
|
|
370
|
+
P = [random.uniform(min_x, max_x), random.uniform(min_y, max_y)]
|
|
371
|
+
in_pipe = self.pipe_path.contains_point(P)
|
|
372
|
+
in_data = out_path.contains_point(P)
|
|
373
|
+
|
|
374
|
+
valid_point = True
|
|
375
|
+
if in_pipe and in_data:
|
|
376
|
+
n_int += 1.0
|
|
377
|
+
elif in_pipe or in_data:
|
|
378
|
+
n_union += 1.0
|
|
379
|
+
else:
|
|
380
|
+
valid_point = False
|
|
381
|
+
|
|
382
|
+
if valid_point:
|
|
383
|
+
npts += 1
|
|
384
|
+
|
|
385
|
+
res = 10*(1.0-n_int/(n_int + n_union))
|
|
386
|
+
|
|
387
|
+
if self.debug:
|
|
388
|
+
dbg_ax. clear()
|
|
389
|
+
dbg_ax.plot(self.cpipe[:, 0], self.cpipe[:, 1])
|
|
390
|
+
dbg_ax.plot(out[:, 0], out[:, 1])
|
|
391
|
+
dbg_ax.set_title("res {:3f}".format(res))
|
|
392
|
+
plt.draw()
|
|
393
|
+
plt.pause(0.0001)
|
|
394
|
+
|
|
395
|
+
return res
|
|
396
|
+
|
|
397
|
+
def fit(self, data, M0=None, factor=1.0):
|
|
398
|
+
"""Do the fit.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
----
|
|
402
|
+
data: the data to fit to the pipe contour.
|
|
403
|
+
M0: if given, the initial values
|
|
404
|
+
|
|
405
|
+
Returns
|
|
406
|
+
-------
|
|
407
|
+
return the thransform (theta, Sx, Sy, Tx, Ty, Ox, Oy)
|
|
408
|
+
theta is the rotation
|
|
409
|
+
Sx, Sy the scale
|
|
410
|
+
Tx, Ty the translation
|
|
411
|
+
Ox, Oy the final offset. This is not used in the fit.
|
|
412
|
+
|
|
413
|
+
"""
|
|
414
|
+
if M0 is None:
|
|
415
|
+
M = self.initial_guess(data)
|
|
416
|
+
if self.debug:
|
|
417
|
+
print("\n** Initial guess")
|
|
418
|
+
self.print_transform(M)
|
|
419
|
+
else:
|
|
420
|
+
M = M0
|
|
421
|
+
|
|
422
|
+
self.data = contours.contour_simplify(data, 1.5*factor)
|
|
423
|
+
# self.data = data
|
|
424
|
+
verbose = 0
|
|
425
|
+
if self.debug:
|
|
426
|
+
verbose = 2
|
|
427
|
+
|
|
428
|
+
res = least_squares(self.get_residuals, M,
|
|
429
|
+
method='trf',
|
|
430
|
+
# ftol=5e-16,
|
|
431
|
+
# xtol=None,
|
|
432
|
+
# max_nfev=10000,
|
|
433
|
+
# diff_step=[0.05, 0.02, 0.02, 50, 50],
|
|
434
|
+
bounds=self.bounds,
|
|
435
|
+
verbose=verbose)
|
|
436
|
+
self.res = res
|
|
437
|
+
M = np.zeros(7)
|
|
438
|
+
M[0:5] = res.x
|
|
439
|
+
M[5] = self.center[0]
|
|
440
|
+
M[6] = self.center[1]
|
|
441
|
+
|
|
442
|
+
if self.debug:
|
|
443
|
+
self.print_transform(M)
|
|
444
|
+
|
|
445
|
+
self.R = M
|
|
446
|
+
return M
|
|
447
|
+
|
|
448
|
+
def fit_ex(self, data, limit=5e6, factor=1):
|
|
449
|
+
"""Does the regular fit and tries to correct."""
|
|
450
|
+
R = self.fit(data, factor=factor)
|
|
451
|
+
|
|
452
|
+
# Check for an offset...
|
|
453
|
+
if self.res.cost > limit:
|
|
454
|
+
delta = []
|
|
455
|
+
for i in [1, 3, 4]:
|
|
456
|
+
x0 = max(self.bounds[0][i], 0.75*R[i])
|
|
457
|
+
x1 = min(self.bounds[1][i], 1.25*R[i])
|
|
458
|
+
X, Y = self.scan(i, x0, x1, 10, R)
|
|
459
|
+
iax = np.argmin(Y)
|
|
460
|
+
delta.append(X[iax])
|
|
461
|
+
|
|
462
|
+
R[1] = delta[0]
|
|
463
|
+
R[3] = delta[1]
|
|
464
|
+
R[4] = delta[2]
|
|
465
|
+
R = self.fit(data, R[0:5])
|
|
466
|
+
|
|
467
|
+
if self.debug:
|
|
468
|
+
print("\n** Result")
|
|
469
|
+
self.print_transform(R)
|
|
470
|
+
|
|
471
|
+
self.R = R
|
|
472
|
+
return R
|
|
473
|
+
|
|
474
|
+
def scan(self, ipar, vmin, vmax, npts, M):
|
|
475
|
+
"""Scan a parameter value.
|
|
476
|
+
|
|
477
|
+
Args:
|
|
478
|
+
----
|
|
479
|
+
ipar: The parameter index
|
|
480
|
+
vmin: Min value
|
|
481
|
+
vmax: Max value of parameter
|
|
482
|
+
npts: Number of points
|
|
483
|
+
M: transform matrix
|
|
484
|
+
|
|
485
|
+
Returns
|
|
486
|
+
-------
|
|
487
|
+
X, Y: values of parameter and residuals
|
|
488
|
+
|
|
489
|
+
"""
|
|
490
|
+
X = np.linspace(vmin, vmax, num=npts)
|
|
491
|
+
Y = np.zeros(npts)
|
|
492
|
+
M0 = np.array([x for x in M[0:5]])
|
|
493
|
+
for i in range(npts):
|
|
494
|
+
M0[ipar] = X[i]
|
|
495
|
+
out = self.get_residuals(M0)
|
|
496
|
+
out = np.atleast_1d(out)
|
|
497
|
+
Y[i] = 0.5 * np.dot(out, out)
|
|
498
|
+
|
|
499
|
+
return X, Y
|
|
500
|
+
|
|
501
|
+
def plot(self):
|
|
502
|
+
"""Plot result of fit and data."""
|
|
503
|
+
fig, ax = plt.subplots(1, 1, tight_layout=True)
|
|
504
|
+
ax.plot(self.pipe[:, 0], self.pipe[:, 1], label="Pipe")
|
|
505
|
+
if self.R is not None:
|
|
506
|
+
out = self.transform_data(self.data, self.R)
|
|
507
|
+
else:
|
|
508
|
+
out = self.data
|
|
509
|
+
|
|
510
|
+
ax.plot(out[:, 0], out[:, 1], 'o', label="Data")
|
|
511
|
+
ax.legend()
|
|
512
|
+
plt.draw()
|
|
513
|
+
plt.pause(0.0001)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def main(data_file, opts):
|
|
517
|
+
"""The main entry.
|
|
518
|
+
|
|
519
|
+
Args:
|
|
520
|
+
----
|
|
521
|
+
pipe_file: The path of the pipe data file
|
|
522
|
+
data_file: The data file contaning the IR petal path
|
|
523
|
+
opts: Options from command line
|
|
524
|
+
|
|
525
|
+
"""
|
|
526
|
+
# Create fitting class
|
|
527
|
+
PF = PipeFit(opts.front)
|
|
528
|
+
|
|
529
|
+
data = np.loadtxt(data_file, delimiter=',', unpack=False)
|
|
530
|
+
data = contours.contour_smooth(data, 25)
|
|
531
|
+
|
|
532
|
+
# Do the fit
|
|
533
|
+
PF.set_debug(opts.debug)
|
|
534
|
+
R = PF.fit_ex(data)
|
|
535
|
+
PF.set_debug(False)
|
|
536
|
+
|
|
537
|
+
# PLot results
|
|
538
|
+
fig, ax = plt.subplots(1, 3, tight_layout=True)
|
|
539
|
+
fign = plt.gcf().number
|
|
540
|
+
ax[0].plot(PF.pipe[:, 0], PF.pipe[:, 1])
|
|
541
|
+
ax[0].plot(PF.center[0], PF.center[1], 'o')
|
|
542
|
+
ax[0].set_title("Pipe")
|
|
543
|
+
|
|
544
|
+
center = PF.get_data_center(data)
|
|
545
|
+
ax[1].plot(data[:, 0], data[:, 1])
|
|
546
|
+
ax[1].plot(center[0], center[1], 'o')
|
|
547
|
+
ax[1].set_title("Data on IR")
|
|
548
|
+
|
|
549
|
+
# Show the result of the fit
|
|
550
|
+
plt.figure(fign)
|
|
551
|
+
out = PF.transform_data(PF.data, R)
|
|
552
|
+
aout = PF.transform_data(data, R)
|
|
553
|
+
center = PF.get_data_center(aout)
|
|
554
|
+
ax[2].plot(PF.pipe[:, 0], PF.pipe[:, 1])
|
|
555
|
+
ax[2].plot(out[:, 0], out[:, 1], 'o')
|
|
556
|
+
ax[2].plot(center[0], center[1], 'o')
|
|
557
|
+
ax[2].plot(aout[0, 0], aout[0, 1], 'o')
|
|
558
|
+
np.savez_compressed("last_pipe.npz", pipe=aout)
|
|
559
|
+
|
|
560
|
+
ax[2].set_title("pipe - Data (dots)")
|
|
561
|
+
fig.show()
|
|
562
|
+
|
|
563
|
+
if opts.debug:
|
|
564
|
+
F, A = plt.subplots(1, 5, tight_layout=True)
|
|
565
|
+
for i in range(5):
|
|
566
|
+
A[i].set_title("scan {}".format(PF.par_names[i]))
|
|
567
|
+
print("Scanning {}".format(i))
|
|
568
|
+
X, Y = PF.scan(i, 0.75*R[i], 1.25*R[i], 50, R)
|
|
569
|
+
print("done")
|
|
570
|
+
A[i].plot(X-R[i], Y)
|
|
571
|
+
|
|
572
|
+
plt.show()
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
if __name__ == "__main__":
|
|
576
|
+
parser = ArgumentParser(description="PipeContour")
|
|
577
|
+
parser = ArgumentParser()
|
|
578
|
+
parser.add_argument("--front", action="store_true",
|
|
579
|
+
default=True, dest='front',
|
|
580
|
+
help="Front side")
|
|
581
|
+
parser.add_argument("--back", action="store_false",
|
|
582
|
+
dest='front',
|
|
583
|
+
help="Back side")
|
|
584
|
+
parser.add_argument("--debug", action="store_true",
|
|
585
|
+
default=False,
|
|
586
|
+
help="Set to debug")
|
|
587
|
+
|
|
588
|
+
opts = parser.parse_args()
|
|
589
|
+
front = opts.front
|
|
590
|
+
script_path = Path(__file__).absolute().parent
|
|
591
|
+
|
|
592
|
+
if front:
|
|
593
|
+
data_file = script_path.joinpath("min_path_front.csv")
|
|
594
|
+
|
|
595
|
+
else:
|
|
596
|
+
data_file = script_path.joinpath("min_path_back.csv")
|
|
597
|
+
|
|
598
|
+
main(data_file, opts)
|
|
File without changes
|