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,756 @@
|
|
|
1
|
+
"""A collection of methods t ocompute flatness or fit points to a plame."""
|
|
2
|
+
import math
|
|
3
|
+
from inspect import isfunction
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import numpy.linalg as linalg
|
|
7
|
+
from scipy.spatial import Delaunay, ConvexHull
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def vector_angle(v1, v2):
|
|
11
|
+
"""Copmpute angle between given vectors."""
|
|
12
|
+
u1 = v1 / linalg.norm(v1)
|
|
13
|
+
u2 = v2 / linalg.norm(v2)
|
|
14
|
+
|
|
15
|
+
y = u1 - u2
|
|
16
|
+
x = u1 + u2
|
|
17
|
+
|
|
18
|
+
a0 = 2 * np.arctan(linalg.norm(y) / linalg.norm(x))
|
|
19
|
+
|
|
20
|
+
if (not np.signbit(a0)) or np.signbit(np.pi - a0):
|
|
21
|
+
return a0
|
|
22
|
+
elif np.signbit(a0):
|
|
23
|
+
return 0.0
|
|
24
|
+
else:
|
|
25
|
+
return np.pi
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def flatness_LSPL(M):
|
|
29
|
+
"""Compute flatness according to least squares reference plane method.
|
|
30
|
+
|
|
31
|
+
ISO/TS 12781-1
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
M (np.ndarray): The data nx3 array
|
|
35
|
+
|
|
36
|
+
Return
|
|
37
|
+
(float) - the computed flatness
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
# calculate the center of mass and translate all points
|
|
41
|
+
com = np.sum(M, axis=0) / len(M)
|
|
42
|
+
q = M - com
|
|
43
|
+
|
|
44
|
+
# calculate 3x3 matrix. The inner product returns total sum of 3x3 matrix
|
|
45
|
+
Q = np.dot(q.T, q)
|
|
46
|
+
|
|
47
|
+
# Calculate eigenvalues and eigenvectors
|
|
48
|
+
la, vectors = np.linalg.eig(Q)
|
|
49
|
+
|
|
50
|
+
# Extract the eigenvector of the minimum eigenvalue
|
|
51
|
+
n = vectors.T[np.argmin(la)]
|
|
52
|
+
|
|
53
|
+
e = np.dot(q, n)
|
|
54
|
+
|
|
55
|
+
eplus = abs(np.amax(e[np.where(e > 0)]))
|
|
56
|
+
eminus = abs(np.amin(e[np.where(e < 0)]))
|
|
57
|
+
|
|
58
|
+
flatness = eplus + eminus
|
|
59
|
+
return flatness
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def flatness_conhull(M):
|
|
63
|
+
"""Compute (MZPL) flatness by convex hull algorithm.
|
|
64
|
+
|
|
65
|
+
Robust Convex Hull-based Algoritm for Straightness and Flatness
|
|
66
|
+
Determination in Coordinate Measuring (Gyula Hermann)
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
M: point array of size (npoints, ndim)
|
|
70
|
+
|
|
71
|
+
Returns
|
|
72
|
+
-------
|
|
73
|
+
flatness - the computed flatness
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
X = M[:, 0]
|
|
77
|
+
Y = M[:, 1]
|
|
78
|
+
Z = M[:, 2]
|
|
79
|
+
max_dis_local = []
|
|
80
|
+
hull = ConvexHull(M, incremental=False, qhull_options=None)
|
|
81
|
+
for plane in hull.equations:
|
|
82
|
+
dis = np.abs(plane[0] * X[:] + plane[1] * Y[:] + plane[2] * Z[:] + plane[3])
|
|
83
|
+
max_dis_local.append(np.max(dis))
|
|
84
|
+
|
|
85
|
+
return np.min(max_dis_local)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def flatness_conhull_old(M):
|
|
89
|
+
"""Compute flatness by convex hull algorithm.
|
|
90
|
+
|
|
91
|
+
Robust Convex Hull-based Algoritm for Straightness and Flatness
|
|
92
|
+
Determination in Coordinate Measuring (Gyula Hermann)
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
M: point array of size (npoints, ndim)
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
flatness - the computed flatness
|
|
100
|
+
|
|
101
|
+
"""
|
|
102
|
+
X = M[:, 0]
|
|
103
|
+
Y = M[:, 1]
|
|
104
|
+
Z = M[:, 2]
|
|
105
|
+
ch = Delaunay(M).convex_hull
|
|
106
|
+
|
|
107
|
+
N = ch.shape[0]
|
|
108
|
+
max_dis_local = np.zeros([N, 1])
|
|
109
|
+
for i in range(0, N):
|
|
110
|
+
P1 = np.array([X[ch[i, 0]], Y[ch[i, 0]], Z[ch[i, 0]]])
|
|
111
|
+
P2 = np.array([X[ch[i, 1]], Y[ch[i, 1]], Z[ch[i, 1]]])
|
|
112
|
+
P3 = np.array([X[ch[i, 2]], Y[ch[i, 2]], Z[ch[i, 2]]])
|
|
113
|
+
|
|
114
|
+
normal = np.cross(P1-P2, P1-P3)
|
|
115
|
+
|
|
116
|
+
D = -normal[0] * P3[0] - normal[1] * P3[1] - normal[2] * P3[2]
|
|
117
|
+
|
|
118
|
+
plano_0 = np.array([normal[0], normal[1], normal[2], D])
|
|
119
|
+
plano = plano_0 / np.sqrt(np.sum(plano_0**2))
|
|
120
|
+
|
|
121
|
+
dis = np.abs(plano[0] * X[:] + plano[1] * Y[:] + plano[2] * Z[:] +
|
|
122
|
+
plano[3]) / np.sqrt(plano[0]**2 + plano[1]**2 + plano[2]**2)
|
|
123
|
+
|
|
124
|
+
max_dis_local[i] = np.max(dis)
|
|
125
|
+
|
|
126
|
+
# planos(i, :) = plano(:);
|
|
127
|
+
|
|
128
|
+
return np.min(max_dis_local)
|
|
129
|
+
# plano_opt = planos(find(max_dis_local == flatness), :);
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def project_to_plane(data, V, M=None):
|
|
133
|
+
"""Project data points to a plane.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
----
|
|
137
|
+
data (array): Data array
|
|
138
|
+
V (matrix): The transformation matrix
|
|
139
|
+
M (vector): The mean value of the data
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
array: the data projectoed onto the plane.
|
|
144
|
+
|
|
145
|
+
"""
|
|
146
|
+
npts = data.shape[0]
|
|
147
|
+
out = np.zeros(data.shape)
|
|
148
|
+
|
|
149
|
+
if M is not None:
|
|
150
|
+
for i in range(0, npts):
|
|
151
|
+
out[i, :] = np.dot(data[i, :] - M, V)
|
|
152
|
+
|
|
153
|
+
else:
|
|
154
|
+
for i in range(0, npts):
|
|
155
|
+
out[i, :] = np.dot(data[i, :], V)
|
|
156
|
+
|
|
157
|
+
return out
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def fit_plane(data, use_average=7):
|
|
161
|
+
"""Fit plane where Z dispersion is smaller.
|
|
162
|
+
|
|
163
|
+
This is the plane defined by the eigenvector with smaller eigenvalue
|
|
164
|
+
of the covariance matrix of the data.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
-----
|
|
168
|
+
data: The data
|
|
169
|
+
use_average: bitted word telling which components of average
|
|
170
|
+
should be used.
|
|
171
|
+
|
|
172
|
+
Returns
|
|
173
|
+
-------
|
|
174
|
+
val: the data in the new reference
|
|
175
|
+
V: the eigenvectors
|
|
176
|
+
M: the "center of gravity" of the plane
|
|
177
|
+
L: the eigenvalues
|
|
178
|
+
|
|
179
|
+
"""
|
|
180
|
+
M = np.mean(data, 0)
|
|
181
|
+
cmtx = np.cov(np.transpose(data))
|
|
182
|
+
L, V = linalg.eig(cmtx)
|
|
183
|
+
|
|
184
|
+
# We assume the rotation is not too big and unit vectors are
|
|
185
|
+
# close to the original ux, uy and uz
|
|
186
|
+
idx = []
|
|
187
|
+
for i in range(0, V.shape[0]):
|
|
188
|
+
v = np.abs(V[:, i])
|
|
189
|
+
mx = np.amax(v)
|
|
190
|
+
ix = np.where(v == mx)[0][0]
|
|
191
|
+
idx.append(ix)
|
|
192
|
+
|
|
193
|
+
NV = np.zeros(V.shape)
|
|
194
|
+
NL = np.zeros(L.shape)
|
|
195
|
+
for i, j in enumerate(idx):
|
|
196
|
+
NV[:, j] = V[:, i]
|
|
197
|
+
NL[j] = L[i]
|
|
198
|
+
|
|
199
|
+
for i in range(0, 3):
|
|
200
|
+
if NV[i, i] < 0:
|
|
201
|
+
NV[:, i] = -NV[:, i]
|
|
202
|
+
|
|
203
|
+
avg = np.array([0., 0., 0.])
|
|
204
|
+
for i in range(0, 3):
|
|
205
|
+
if (1 << i & use_average):
|
|
206
|
+
avg[i] = M[i]
|
|
207
|
+
|
|
208
|
+
return project_to_plane(data, NV, avg), NV, M, NL
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def remove_outliers_indx(data, cut=2.0, debug=False):
|
|
212
|
+
"""Remove points far away form the rest.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
----
|
|
216
|
+
data : The data
|
|
217
|
+
cut: max allowed distance
|
|
218
|
+
debug: be verbose if True.
|
|
219
|
+
|
|
220
|
+
Returns
|
|
221
|
+
-------
|
|
222
|
+
index of valid pints in data array.
|
|
223
|
+
|
|
224
|
+
"""
|
|
225
|
+
d = np.abs(data - np.median(data))
|
|
226
|
+
mdev = np.median(d)
|
|
227
|
+
s = d / (mdev if mdev else 1.)
|
|
228
|
+
indx = np.where(s < cut)[0]
|
|
229
|
+
return indx
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def remove_outliers(data, cut, zlimit=1e25):
|
|
233
|
+
"""Remove points far away form the rest.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
----
|
|
237
|
+
data : The data
|
|
238
|
+
cut: max allowed distance
|
|
239
|
+
zlimit: limit for Z Defaults to 1e25.
|
|
240
|
+
|
|
241
|
+
"""
|
|
242
|
+
# move to the
|
|
243
|
+
val, V, M, L = fit_plane(data)
|
|
244
|
+
npts = val.shape[0]
|
|
245
|
+
ipoint = 0
|
|
246
|
+
vout = np.zeros([npts, 3])
|
|
247
|
+
rms = math.sqrt(L[0])
|
|
248
|
+
for i in range(0, npts):
|
|
249
|
+
sn = abs(val[i, 2]-M[2])/rms
|
|
250
|
+
if sn < cut:
|
|
251
|
+
vout[ipoint, :] = data[i, :]
|
|
252
|
+
ipoint = ipoint + 1
|
|
253
|
+
|
|
254
|
+
return vout[0:ipoint, :]
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def __remove_outliers(data, cut=2.0, debug=False):
|
|
258
|
+
"""Remove points far away form the rest.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
----
|
|
262
|
+
data : The data
|
|
263
|
+
cut: max allowed distance
|
|
264
|
+
debug: be bverbose if True
|
|
265
|
+
|
|
266
|
+
"""
|
|
267
|
+
# move to the
|
|
268
|
+
val, V, M, L = fit_plane(data)
|
|
269
|
+
Z = val[:, 2]
|
|
270
|
+
d = np.abs(Z - np.median(Z))
|
|
271
|
+
mdev = np.median(d)
|
|
272
|
+
s = d / (mdev if mdev else 1.)
|
|
273
|
+
return data[s < cut, :]
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class Point(object):
|
|
277
|
+
"""Represents a point in a 2D space."""
|
|
278
|
+
|
|
279
|
+
def __init__(self, x=None, y=None, name=None):
|
|
280
|
+
"""Initialization of a Point object.
|
|
281
|
+
|
|
282
|
+
Arguments are coordinates and optionally a name.
|
|
283
|
+
It can be initialized with individual values of
|
|
284
|
+
X and Y, with tuplles or arrays or with Point objects.
|
|
285
|
+
|
|
286
|
+
"""
|
|
287
|
+
self.x = None
|
|
288
|
+
self.y = None
|
|
289
|
+
|
|
290
|
+
if name:
|
|
291
|
+
self.name = name
|
|
292
|
+
else:
|
|
293
|
+
self.name = "Point"
|
|
294
|
+
|
|
295
|
+
if x is not None:
|
|
296
|
+
if isinstance(x, Point):
|
|
297
|
+
self.x = x.x
|
|
298
|
+
self.y = x.y
|
|
299
|
+
else:
|
|
300
|
+
try:
|
|
301
|
+
self.x = float(x[0])
|
|
302
|
+
self.y = float(x[1])
|
|
303
|
+
except (TypeError, IndexError):
|
|
304
|
+
try:
|
|
305
|
+
self.x = float(x)
|
|
306
|
+
except ValueError:
|
|
307
|
+
self.x = None
|
|
308
|
+
|
|
309
|
+
try:
|
|
310
|
+
self.y = float(y)
|
|
311
|
+
except ValueError:
|
|
312
|
+
self.y = None
|
|
313
|
+
|
|
314
|
+
def __sub__(self, other):
|
|
315
|
+
"""Defference between 2 Point objects."""
|
|
316
|
+
if isinstance(other, Point):
|
|
317
|
+
return Point(self.x - other.x, self.y - other.y)
|
|
318
|
+
else:
|
|
319
|
+
return NotImplemented
|
|
320
|
+
|
|
321
|
+
def __add__(self, other):
|
|
322
|
+
"""Addtion of 2 Point objects."""
|
|
323
|
+
if isinstance(other, Point):
|
|
324
|
+
return Point(self.x + other.x, self.y + other.y)
|
|
325
|
+
else:
|
|
326
|
+
return NotImplemented
|
|
327
|
+
|
|
328
|
+
def __mul__(self, other):
|
|
329
|
+
"""Scalar product of 2 Point objects."""
|
|
330
|
+
if isinstance(other, Point):
|
|
331
|
+
return self.x * other.x + self.y * other.y
|
|
332
|
+
elif isinstance(other, float) or isinstance(other, int):
|
|
333
|
+
return Point(self.x * other, self.y * other)
|
|
334
|
+
else:
|
|
335
|
+
return NotImplemented
|
|
336
|
+
|
|
337
|
+
def __rmul__(self, other):
|
|
338
|
+
"""Multiplication by float or int."""
|
|
339
|
+
if isinstance(other, float) or isinstance(other, int):
|
|
340
|
+
return Point(self.x * other, self.y * other)
|
|
341
|
+
else:
|
|
342
|
+
return NotImplemented
|
|
343
|
+
|
|
344
|
+
def __eq__(self, other):
|
|
345
|
+
"""Checks for equality."""
|
|
346
|
+
if isinstance(other, Point):
|
|
347
|
+
return self.x == other.x and self.y == other.y
|
|
348
|
+
else:
|
|
349
|
+
return NotImplemented
|
|
350
|
+
|
|
351
|
+
def __ne__(self, other):
|
|
352
|
+
"""Checks for non equality."""
|
|
353
|
+
if isinstance(other, Point):
|
|
354
|
+
return self.x != other.x or self.y != other.y
|
|
355
|
+
else:
|
|
356
|
+
return NotImplemented
|
|
357
|
+
|
|
358
|
+
def __lt__(self, other):
|
|
359
|
+
"""Lees than operator.
|
|
360
|
+
|
|
361
|
+
A point is smaller than other if its magnitude is smaller.
|
|
362
|
+
"""
|
|
363
|
+
if isinstance(other, Point):
|
|
364
|
+
return self.mag() < other.mag()
|
|
365
|
+
else:
|
|
366
|
+
return NotImplemented
|
|
367
|
+
|
|
368
|
+
def __gt__(self, other):
|
|
369
|
+
"""Greater than operator.
|
|
370
|
+
|
|
371
|
+
A point is greater than other if its magnitude is greater.
|
|
372
|
+
"""
|
|
373
|
+
if isinstance(other, Point):
|
|
374
|
+
return self.mag() > other.mag()
|
|
375
|
+
else:
|
|
376
|
+
return NotImplemented
|
|
377
|
+
|
|
378
|
+
def __le__(self, other):
|
|
379
|
+
"""Lees or equal.
|
|
380
|
+
|
|
381
|
+
Here equality refers to magnitude.
|
|
382
|
+
"""
|
|
383
|
+
if isinstance(other, Point):
|
|
384
|
+
return self.mag() <= other.mag()
|
|
385
|
+
else:
|
|
386
|
+
return NotImplemented
|
|
387
|
+
|
|
388
|
+
def __ge__(self, other):
|
|
389
|
+
"""Greater or equal.
|
|
390
|
+
|
|
391
|
+
Here equality refers to magnitude.
|
|
392
|
+
"""
|
|
393
|
+
if isinstance(other, Point):
|
|
394
|
+
return self.mag() >= other.mag()
|
|
395
|
+
else:
|
|
396
|
+
return NotImplemented
|
|
397
|
+
|
|
398
|
+
def __neg__(self):
|
|
399
|
+
"""Unary minus."""
|
|
400
|
+
return Point(-self.x, -self.y)
|
|
401
|
+
|
|
402
|
+
def mag(self):
|
|
403
|
+
"""Return the length or magnitude."""
|
|
404
|
+
return math.sqrt(self.x*self.x + self.y*self.y)
|
|
405
|
+
|
|
406
|
+
def mag2(self):
|
|
407
|
+
"""The squared of the magnitud."""
|
|
408
|
+
return self.x*self.x + self.y*self.y
|
|
409
|
+
|
|
410
|
+
def norm(self):
|
|
411
|
+
"""Return unit vector."""
|
|
412
|
+
v = self.mag()
|
|
413
|
+
return Point(self.x/v, self.y/v)
|
|
414
|
+
|
|
415
|
+
def angle(self, P):
|
|
416
|
+
"""Return angle with given point."""
|
|
417
|
+
return math.atan2(P.cross(self), P.dot(self))
|
|
418
|
+
|
|
419
|
+
def cw(self):
|
|
420
|
+
"""Return a point like this rotated +90 degrees."""
|
|
421
|
+
return Point(-self.y, self.x)
|
|
422
|
+
|
|
423
|
+
def ccw(self):
|
|
424
|
+
"""Return a point like this rotated -90 degress."""
|
|
425
|
+
return Point(self.y, -self.x)
|
|
426
|
+
|
|
427
|
+
def dot(self, a):
|
|
428
|
+
"""Dot product with given vector."""
|
|
429
|
+
return self.x * a.x + self.y * a.y
|
|
430
|
+
|
|
431
|
+
def cross(self, b):
|
|
432
|
+
"""Cross product with given vector."""
|
|
433
|
+
return self.dot(b.cw())
|
|
434
|
+
|
|
435
|
+
def phi(self):
|
|
436
|
+
"""Phi or azimutal angle of vector."""
|
|
437
|
+
return math.atan2(self.y, self.x)
|
|
438
|
+
|
|
439
|
+
def valid(self):
|
|
440
|
+
"""Tells if the point has valid values."""
|
|
441
|
+
if self.x is None or self.y is None:
|
|
442
|
+
return False
|
|
443
|
+
else:
|
|
444
|
+
return True
|
|
445
|
+
|
|
446
|
+
def distance(self, other):
|
|
447
|
+
"""Distance to a Point or Line."""
|
|
448
|
+
if isinstance(other, Point):
|
|
449
|
+
dd = (self-other).mag()
|
|
450
|
+
return dd
|
|
451
|
+
elif isinstance(other, Line):
|
|
452
|
+
ff = math.sqrt(other.A()**2 + other.B()**2)
|
|
453
|
+
dd = other.A()*self[0] + other.B()*self[1] + other.C()
|
|
454
|
+
return dd/ff
|
|
455
|
+
else:
|
|
456
|
+
raise ValueError
|
|
457
|
+
|
|
458
|
+
def unit(self):
|
|
459
|
+
"""Returns a unit vector from this one."""
|
|
460
|
+
return (1.0/self.mag())*self
|
|
461
|
+
|
|
462
|
+
def __getitem__(self, key):
|
|
463
|
+
"""Implement the getitem interface."""
|
|
464
|
+
if key < 0 or key > 1:
|
|
465
|
+
raise IndexError
|
|
466
|
+
elif key == 0:
|
|
467
|
+
return self.x
|
|
468
|
+
else:
|
|
469
|
+
return self.y
|
|
470
|
+
|
|
471
|
+
def __setitem__(self, key, val):
|
|
472
|
+
"""Implement the setitem interface."""
|
|
473
|
+
if key < 0 or key > 2:
|
|
474
|
+
raise IndexError
|
|
475
|
+
elif key == 0:
|
|
476
|
+
self.x = val
|
|
477
|
+
else:
|
|
478
|
+
self.y = val
|
|
479
|
+
|
|
480
|
+
def __len__(self):
|
|
481
|
+
"""Return length."""
|
|
482
|
+
return 2
|
|
483
|
+
|
|
484
|
+
def __str__(self):
|
|
485
|
+
"""String representation."""
|
|
486
|
+
return "%f,%f" % (self.x, self.y)
|
|
487
|
+
|
|
488
|
+
def __repr__(self):
|
|
489
|
+
"""String representation."""
|
|
490
|
+
return "Point(%f, %f)" % (self.x, self.y)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def dot(a, b):
|
|
494
|
+
"""Dot product."""
|
|
495
|
+
return a.x*b.x + a.y*b.y
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def cross(a, b):
|
|
499
|
+
"""Cross product."""
|
|
500
|
+
return dot(a, b.cw())
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
class Line(object):
|
|
504
|
+
"""Represents a line.
|
|
505
|
+
|
|
506
|
+
We store the slope (m) and the intercept (b)
|
|
507
|
+
|
|
508
|
+
"""
|
|
509
|
+
|
|
510
|
+
def __init__(self, m=None, n=None):
|
|
511
|
+
"""Line creation.
|
|
512
|
+
|
|
513
|
+
We create the line in various forms:
|
|
514
|
+
1) m and n are floats: the slope intercept form
|
|
515
|
+
2) m is a float and n is a Point: line with that slope passing
|
|
516
|
+
through the given point
|
|
517
|
+
3) m and n are points: line passing through 2 points
|
|
518
|
+
V = n - m
|
|
519
|
+
O = m
|
|
520
|
+
"""
|
|
521
|
+
self.P1 = None
|
|
522
|
+
self.P2 = None
|
|
523
|
+
if isinstance(m, Point):
|
|
524
|
+
self.P1 = m
|
|
525
|
+
if isinstance(n, Point):
|
|
526
|
+
self.P2 = n
|
|
527
|
+
delta_x = (n.x - m.x)
|
|
528
|
+
delta_y = (n.y - m.y)
|
|
529
|
+
if delta_x == 0.0: # vertical line
|
|
530
|
+
self.O = Point(n.x, n.y)
|
|
531
|
+
self.V = Point(0.0, 1.0)
|
|
532
|
+
self.m = None
|
|
533
|
+
self.b = None
|
|
534
|
+
return
|
|
535
|
+
|
|
536
|
+
self.m = delta_y/delta_x
|
|
537
|
+
self.b = - self.m * n.x + n.y
|
|
538
|
+
self.O = Point(m.x, m.y)
|
|
539
|
+
self.delta = Point(delta_x, delta_y)
|
|
540
|
+
self.V = self.delta.norm()
|
|
541
|
+
|
|
542
|
+
else: # n has to be a number
|
|
543
|
+
self.m = n
|
|
544
|
+
self.b = - self.m * m.x + m.y
|
|
545
|
+
alpha = math.atan(n)
|
|
546
|
+
self.O = m
|
|
547
|
+
self.V = Point(math.cos(alpha), math.sin(alpha))
|
|
548
|
+
self.delta = self.V
|
|
549
|
+
|
|
550
|
+
else: # m has to be a number
|
|
551
|
+
if isinstance(n, Point):
|
|
552
|
+
self.m = m
|
|
553
|
+
self.b = - self.m * n.x + n.y
|
|
554
|
+
alpha = math.atan(m)
|
|
555
|
+
self.O = n
|
|
556
|
+
self.V = Point(math.cos(alpha), math.sin(alpha))
|
|
557
|
+
self.delta = self.V
|
|
558
|
+
else:
|
|
559
|
+
self.m = m
|
|
560
|
+
self.b = n
|
|
561
|
+
alpha = math.atan(m)
|
|
562
|
+
self.O = Point(0., n)
|
|
563
|
+
self.V = Point(math.cos(alpha), math.sin(alpha))
|
|
564
|
+
self.delta = self.V
|
|
565
|
+
|
|
566
|
+
def __str__(self):
|
|
567
|
+
"""Stringrerpresentation."""
|
|
568
|
+
return "Line(%f, %f)" % (self.m, self.b)
|
|
569
|
+
|
|
570
|
+
def A(self):
|
|
571
|
+
"""A coeff."""
|
|
572
|
+
return self.m
|
|
573
|
+
|
|
574
|
+
def B(self):
|
|
575
|
+
"""B coeff."""
|
|
576
|
+
return -1
|
|
577
|
+
|
|
578
|
+
def C(self):
|
|
579
|
+
"""C coeff."""
|
|
580
|
+
return self.b
|
|
581
|
+
|
|
582
|
+
def eval(self, x):
|
|
583
|
+
"""Evaluates the line."""
|
|
584
|
+
if self.m:
|
|
585
|
+
return self.m * x + self.b
|
|
586
|
+
|
|
587
|
+
else:
|
|
588
|
+
return self.b
|
|
589
|
+
|
|
590
|
+
def param(self, t):
|
|
591
|
+
"""Return point corresponding to given parameter."""
|
|
592
|
+
out = self.P1 + t*self.V
|
|
593
|
+
if not isinstance(out, Point):
|
|
594
|
+
out = Point(out)
|
|
595
|
+
|
|
596
|
+
return out
|
|
597
|
+
|
|
598
|
+
def __call__(self, x):
|
|
599
|
+
"""Line evaluation.
|
|
600
|
+
|
|
601
|
+
Evaluates the line in parametric form x=0 gives P0, and x=1 gives P1
|
|
602
|
+
"""
|
|
603
|
+
out = self.O + x*self.delta
|
|
604
|
+
return out
|
|
605
|
+
|
|
606
|
+
def line_perpendicular_at_point(self, P):
|
|
607
|
+
"""Return the line perpendicular passing by point."""
|
|
608
|
+
L = Line(-1.0/self.m, P)
|
|
609
|
+
return L
|
|
610
|
+
|
|
611
|
+
def line_parallel_at_distance(self, d):
|
|
612
|
+
"""Returns the line parallel to this one which is at a distance d."""
|
|
613
|
+
if not self.m:
|
|
614
|
+
P = Point(self.O.x + d, 0)
|
|
615
|
+
V = Point(self.O.x + d, 1)
|
|
616
|
+
return Line(P, V)
|
|
617
|
+
|
|
618
|
+
else:
|
|
619
|
+
new_m = self.m
|
|
620
|
+
new_b = self.b + d * math.sqrt(1 + self.m*self.m)
|
|
621
|
+
return Line(new_m, new_b)
|
|
622
|
+
|
|
623
|
+
def line_at_angle(self, angle, center=None):
|
|
624
|
+
"""Returns a line which forms an angle w.r.t center."""
|
|
625
|
+
if not center:
|
|
626
|
+
center = self.O
|
|
627
|
+
else:
|
|
628
|
+
res = center.y - (self.m * center.x + self.b)
|
|
629
|
+
if abs(res) > 1e-10:
|
|
630
|
+
raise Exception("Line.line_at_angle: center does not belong to line")
|
|
631
|
+
|
|
632
|
+
ca = math.cos(angle)
|
|
633
|
+
sa = math.sin(angle)
|
|
634
|
+
bx = self.V.x * ca - self.V.y * sa
|
|
635
|
+
by2 = 1.0-bx*bx
|
|
636
|
+
if by2 < 0:
|
|
637
|
+
bx = self.V.x * ca + self.V.y * sa
|
|
638
|
+
by2 = 1.0 - bx*bx
|
|
639
|
+
|
|
640
|
+
by = math.sqrt(by2)
|
|
641
|
+
b = Point(bx, by)
|
|
642
|
+
b1 = center + b
|
|
643
|
+
return Line(center, b1)
|
|
644
|
+
|
|
645
|
+
def line_angle(self, P=None):
|
|
646
|
+
"""Returns the angle with a given direction."""
|
|
647
|
+
if not P:
|
|
648
|
+
P = Point(1.0, 0.0)
|
|
649
|
+
|
|
650
|
+
return P.angle(self.V)
|
|
651
|
+
|
|
652
|
+
def angle(self, other):
|
|
653
|
+
"""Angle between 2 lines."""
|
|
654
|
+
angle = math.atan((other.m-self.m)/(1+self.m*other.m))
|
|
655
|
+
return angle
|
|
656
|
+
|
|
657
|
+
def cross_point(self, other):
|
|
658
|
+
"""Intersection point."""
|
|
659
|
+
if self.m is None:
|
|
660
|
+
# vertical line
|
|
661
|
+
if other.m is None:
|
|
662
|
+
raise Exception("Parallel Lines")
|
|
663
|
+
else:
|
|
664
|
+
x = self.O.x
|
|
665
|
+
y = other.m*x + other.b
|
|
666
|
+
return Point(x, y)
|
|
667
|
+
|
|
668
|
+
if other.m is None:
|
|
669
|
+
return other.cross_point(self)
|
|
670
|
+
|
|
671
|
+
D = other.m - self.m
|
|
672
|
+
if D == 0.:
|
|
673
|
+
raise Exception("Parallel Lines")
|
|
674
|
+
|
|
675
|
+
x = (self.b - other.b)/D
|
|
676
|
+
y = (-self.m*other.b + other.m*self.b)/D
|
|
677
|
+
|
|
678
|
+
return Point(x, y)
|
|
679
|
+
|
|
680
|
+
def is_parallel(self, other):
|
|
681
|
+
"""True if given line is parallel."""
|
|
682
|
+
return self.m == other.m
|
|
683
|
+
|
|
684
|
+
def is_perpendicular(self, other):
|
|
685
|
+
"""True if given line is perpendicular."""
|
|
686
|
+
return self.m*other.m == -1.0
|
|
687
|
+
|
|
688
|
+
def cross_circle(self, C, R, tmin, tmax):
|
|
689
|
+
"""Computes the crossing point with a circle.
|
|
690
|
+
|
|
691
|
+
C: the center of the circle
|
|
692
|
+
R: the radius
|
|
693
|
+
tmin, tmax : angles limiting the crossing point or
|
|
694
|
+
tmin = function to accept a point
|
|
695
|
+
tmax ignored
|
|
696
|
+
"""
|
|
697
|
+
a = self.m
|
|
698
|
+
b = self.b
|
|
699
|
+
c = C.x
|
|
700
|
+
d = C.y
|
|
701
|
+
func = None
|
|
702
|
+
|
|
703
|
+
if isfunction(tmin):
|
|
704
|
+
func = tmin
|
|
705
|
+
|
|
706
|
+
else:
|
|
707
|
+
if tmin < 0:
|
|
708
|
+
tmin += 2*math.pi
|
|
709
|
+
|
|
710
|
+
if tmax < 0:
|
|
711
|
+
tmax += 2*math.pi
|
|
712
|
+
|
|
713
|
+
if tmin > tmax:
|
|
714
|
+
tmp = tmax
|
|
715
|
+
tmax = tmin
|
|
716
|
+
tmin = tmp
|
|
717
|
+
|
|
718
|
+
xx = a*a+1.0
|
|
719
|
+
det = xx*R*R - d*d + 2.0*d*(a*c+b) - a*a*c*c - 2.0*a*b*c-b*b
|
|
720
|
+
if det < 0:
|
|
721
|
+
return None
|
|
722
|
+
|
|
723
|
+
det = math.sqrt(det)
|
|
724
|
+
yy = a*(d-b)+c
|
|
725
|
+
x1 = (det-yy)/xx
|
|
726
|
+
x2 = (det+yy)/xx
|
|
727
|
+
|
|
728
|
+
p1 = Point(x1, a*x1+b)
|
|
729
|
+
p2 = Point(x2, a*x2+b)
|
|
730
|
+
|
|
731
|
+
if func:
|
|
732
|
+
if func(p1, R, C):
|
|
733
|
+
return p1
|
|
734
|
+
elif func(p2, R, C):
|
|
735
|
+
return p2
|
|
736
|
+
else:
|
|
737
|
+
return None
|
|
738
|
+
|
|
739
|
+
else:
|
|
740
|
+
V = (p1-C)
|
|
741
|
+
dd = V.phi()
|
|
742
|
+
if dd < 0.0:
|
|
743
|
+
dd += 2*math.pi
|
|
744
|
+
|
|
745
|
+
if tmin < dd and dd < tmax:
|
|
746
|
+
return p1
|
|
747
|
+
|
|
748
|
+
V = (p2-C)
|
|
749
|
+
dd = V.phi()
|
|
750
|
+
if dd < 0.0:
|
|
751
|
+
dd += 2*math.pi
|
|
752
|
+
|
|
753
|
+
if tmin < dd and dd < tmax:
|
|
754
|
+
return p2
|
|
755
|
+
|
|
756
|
+
return None
|