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,1338 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Analyze ThermaCAM data."""
|
|
3
|
+
import math
|
|
4
|
+
import sys
|
|
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
|
+
from numpy.polynomial import polynomial as Polynomial
|
|
12
|
+
from scipy import ndimage
|
|
13
|
+
from scipy.optimize import minimize
|
|
14
|
+
from skimage import measure
|
|
15
|
+
|
|
16
|
+
from petal_qc.thermal import contours
|
|
17
|
+
from petal_qc.thermal import CSVImage
|
|
18
|
+
from petal_qc.thermal import DebugPlot
|
|
19
|
+
from petal_qc.thermal import IRBFile
|
|
20
|
+
from petal_qc.thermal import PipeFit
|
|
21
|
+
from petal_qc.utils.Geometry import Line
|
|
22
|
+
from petal_qc.utils.Geometry import Point
|
|
23
|
+
from petal_qc.thermal.IRPetalParam import IRPetalParam
|
|
24
|
+
|
|
25
|
+
# Create a global instance of DebugPLot
|
|
26
|
+
debug_plot = DebugPlot.DebugPlot()
|
|
27
|
+
|
|
28
|
+
the_segments = None # global variable to store the last segments found.
|
|
29
|
+
the_contours = None # Global variable to store the last contours found
|
|
30
|
+
the_images = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class DebugTempProfile(object):
|
|
34
|
+
"""Stores Debug data."""
|
|
35
|
+
|
|
36
|
+
def __init__(self):
|
|
37
|
+
"""Initialization."""
|
|
38
|
+
self._x = None # The x along the segment (From A to B)
|
|
39
|
+
self._P = None # The 2D points along the segment
|
|
40
|
+
self._T = None # The temperature values along thre segment
|
|
41
|
+
self._xF = None # X where fit function is evaluated
|
|
42
|
+
self._pF = None # 2D points where fit func is evaluaed
|
|
43
|
+
self._Fn = None # Fit function values at _xF.
|
|
44
|
+
|
|
45
|
+
def set_data(self, data):
|
|
46
|
+
"""Get data returned be create_profile."""
|
|
47
|
+
self._x = np.copy(data[:, 2])
|
|
48
|
+
self._P = np.copy(data[:, 0:2])
|
|
49
|
+
self._T = np.copy(data[:, 3])
|
|
50
|
+
|
|
51
|
+
def set_function(self, x, line, pfunc):
|
|
52
|
+
"""Sets the fit data."""
|
|
53
|
+
self._xF = np.copy(x)
|
|
54
|
+
self._Fn = pfunc(x)
|
|
55
|
+
self._pF = np.zeros([len(x), 2])
|
|
56
|
+
for i, v in enumerate(x):
|
|
57
|
+
P = line.param(v)
|
|
58
|
+
self._pF[i, 0:2] = P
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
T_profile = None # Global to store the temp. profile.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_last_segments():
|
|
65
|
+
"""Return the last segments found."""
|
|
66
|
+
global the_segments
|
|
67
|
+
return the_segments
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_last_contours():
|
|
71
|
+
"""Return last contours."""
|
|
72
|
+
global the_contours
|
|
73
|
+
return the_contours
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_last_images():
|
|
77
|
+
"""Return left and right images."""
|
|
78
|
+
if the_images is None:
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
return the_images
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def set_images(img_list):
|
|
85
|
+
"""Set global list with images."""
|
|
86
|
+
global the_images
|
|
87
|
+
the_images = img_list
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class Segment(object):
|
|
91
|
+
"""Values in a contour segment.
|
|
92
|
+
|
|
93
|
+
A and B are the edges of the slice
|
|
94
|
+
Pmin is the list of points with minimum temperature
|
|
95
|
+
Tmin is the list of minimum temperature
|
|
96
|
+
distance is the distance from the start of the contour.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
the_segmens = None
|
|
100
|
+
|
|
101
|
+
def __init__(self, A, B, Pmin, distance, Tmin, Spread):
|
|
102
|
+
"""Initialization."""
|
|
103
|
+
self.A = A
|
|
104
|
+
self.B = B
|
|
105
|
+
self.Pmin = Pmin
|
|
106
|
+
self.distance = distance
|
|
107
|
+
self.Tmin = Tmin
|
|
108
|
+
self.Spread = Spread
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def get_points_in_list(segments):
|
|
112
|
+
"""Return a list with all the points in the list of segments.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
----
|
|
116
|
+
segments (list[Segment]): The list of segments
|
|
117
|
+
|
|
118
|
+
Returns
|
|
119
|
+
-------
|
|
120
|
+
np.array: array of points.
|
|
121
|
+
|
|
122
|
+
"""
|
|
123
|
+
# Count the number of points
|
|
124
|
+
npts = 0
|
|
125
|
+
for S in segments:
|
|
126
|
+
if S.Pmin is not None:
|
|
127
|
+
npts += len(S.Pmin)
|
|
128
|
+
|
|
129
|
+
points = np.zeros([npts, 2])
|
|
130
|
+
ipoint = 0
|
|
131
|
+
for S in segments:
|
|
132
|
+
if S.Pmin is None:
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
for Pmin in S.Pmin:
|
|
136
|
+
points[ipoint, :] = Pmin
|
|
137
|
+
ipoint += 1
|
|
138
|
+
|
|
139
|
+
return points
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def get_spread_in_list(segments):
|
|
143
|
+
"""Return an array with spread values."""
|
|
144
|
+
# Count the number of points
|
|
145
|
+
npts = 0
|
|
146
|
+
for S in segments:
|
|
147
|
+
if S.Pmin is not None:
|
|
148
|
+
npts += len(S.Pmin)
|
|
149
|
+
|
|
150
|
+
values = np.zeros([npts])
|
|
151
|
+
ipoint = 0
|
|
152
|
+
for S in segments:
|
|
153
|
+
if S.Pmin is None:
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
values[ipoint] = S.Spread
|
|
157
|
+
ipoint += 1
|
|
158
|
+
|
|
159
|
+
return values
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def reorder_pipe_points(in_data, is_front, T=None, image=None, cut=15):
|
|
163
|
+
"""Reorder the pipe points.
|
|
164
|
+
|
|
165
|
+
Points are ordered so that the first point is the minimum at the bottom
|
|
166
|
+
bending of the pipe. The points will run clockwise for thre "front" view
|
|
167
|
+
(EoS at the right) and anti-clockwise to the other view.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
----
|
|
171
|
+
data: The original pipe path is_front (bool): Tells about he petal side.
|
|
172
|
+
is_front: tells whether it is a "front view" (EoS)
|
|
173
|
+
T: the transform to move to petal coordinates (result of fit)
|
|
174
|
+
image: The IR image. This will trigger all the debug activity.
|
|
175
|
+
|
|
176
|
+
Returns
|
|
177
|
+
-------
|
|
178
|
+
out: Contains the points ordered. It will contain an additional point,
|
|
179
|
+
the one at the bottom of the petal pipe bend. This will be the
|
|
180
|
+
very first one
|
|
181
|
+
|
|
182
|
+
"""
|
|
183
|
+
out = np.zeros(in_data.shape)
|
|
184
|
+
npts = len(in_data)
|
|
185
|
+
indx = np.zeros(npts, dtype=int)
|
|
186
|
+
if T is not None:
|
|
187
|
+
data = PipeFit.PipeFit.transform_data(in_data, T)
|
|
188
|
+
|
|
189
|
+
else:
|
|
190
|
+
data = np.copy(in_data)
|
|
191
|
+
|
|
192
|
+
if image is not None:
|
|
193
|
+
fig, ax = plt.subplots(1, 3, tight_layout=True,
|
|
194
|
+
gridspec_kw={'width_ratios': (0.25, 0.25, 0.5)},
|
|
195
|
+
figsize=(7, 4), dpi=300)
|
|
196
|
+
fig.suptitle("pipe {}".format(is_front))
|
|
197
|
+
|
|
198
|
+
# find point of lower Y. This is the bend at tehe bottom of the petal
|
|
199
|
+
ix = np.argmin(data, axis=0)
|
|
200
|
+
i0 = ix[1]
|
|
201
|
+
X = data[i0-3:i0+4, 0]
|
|
202
|
+
Y = data[i0-3:i0+4, 1]
|
|
203
|
+
dbg_XY = np.vstack([X, Y]).T
|
|
204
|
+
c, stats = Polynomial.polyfit(X, Y, 2, full=True)
|
|
205
|
+
pfunc = Polynomial.Polynomial(c)
|
|
206
|
+
fmin = minimize(pfunc, data[i0, 0])
|
|
207
|
+
|
|
208
|
+
X0 = fmin.x[0]
|
|
209
|
+
Y0 = pfunc(X0)
|
|
210
|
+
|
|
211
|
+
# Now insert the minimum into both arrays
|
|
212
|
+
if X0 > data[i0, 0]:
|
|
213
|
+
data = np.insert(data, i0, [X0, Y0], axis=0)
|
|
214
|
+
else:
|
|
215
|
+
data = np.insert(data, i0+1, [X0, Y0], axis=0)
|
|
216
|
+
i0 += 2
|
|
217
|
+
|
|
218
|
+
if image is not None:
|
|
219
|
+
x = np.linspace(X[0], X[-1], 50)
|
|
220
|
+
dbg_XF = np.vstack([x, pfunc(x)]).T
|
|
221
|
+
ax[0].plot(X, Y, 'o-', X0, Y0, 'o', dbg_XF[:, 0], dbg_XF[:, 1], '-')
|
|
222
|
+
ax[1].plot(data[:, 0], data[:, 1], 'o-', dbg_XF[:, 0], dbg_XF[:, 1], '-')
|
|
223
|
+
|
|
224
|
+
# Copy the data into the output array
|
|
225
|
+
n1 = npts-i0
|
|
226
|
+
out[:n1, :] = data[i0:npts, :]
|
|
227
|
+
out[n1:, :] = data[:i0, :]
|
|
228
|
+
|
|
229
|
+
if not is_front:
|
|
230
|
+
out = np.flipud(out)
|
|
231
|
+
indx = np.flipud(indx)
|
|
232
|
+
|
|
233
|
+
if T is not None:
|
|
234
|
+
out = PipeFit.PipeFit.transform_inv(out, T)
|
|
235
|
+
|
|
236
|
+
if image is not None:
|
|
237
|
+
if T is not None:
|
|
238
|
+
dbg_XY = PipeFit.PipeFit.transform_inv(dbg_XY, T)
|
|
239
|
+
dbg_XF = PipeFit.PipeFit.transform_inv(dbg_XF, T)
|
|
240
|
+
|
|
241
|
+
ax[2].imshow(image, origin='lower', cmap='jet')
|
|
242
|
+
ax[2].plot(out[:, 0], out[:, 1], 'o-', dbg_XF[:, 0], dbg_XF[:, 1], '-', out[0:5, 0], out[0:5, 1], 'o')
|
|
243
|
+
ax[2].set_xlim([0.8*np.min(dbg_XY[:, 0]), 1.2*np.max(dbg_XY[:, 0])])
|
|
244
|
+
ax[2].set_ylim([0.8*np.min(dbg_XY[:, 1]), 1.2*np.max(dbg_XY[:, 1])])
|
|
245
|
+
|
|
246
|
+
return out
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def find_reference_image(irbf, T_min):
|
|
250
|
+
"""Find first image in sequence with T < T_min.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
----
|
|
254
|
+
irbf: The sequence of IR images
|
|
255
|
+
T_min: The temperature threshold
|
|
256
|
+
|
|
257
|
+
Returns
|
|
258
|
+
-------
|
|
259
|
+
min_T, i_min, values: The actual temperature of the image,
|
|
260
|
+
the sequence nubmer
|
|
261
|
+
and the array of values
|
|
262
|
+
|
|
263
|
+
"""
|
|
264
|
+
# find the image with smalowestllest temperature. This will be used to define the pipe
|
|
265
|
+
min_T = sys.float_info.max
|
|
266
|
+
i_min = -1
|
|
267
|
+
for i, img in enumerate(irbf.images()):
|
|
268
|
+
temp = np.min(img[0].image)
|
|
269
|
+
print("Image {}. Min temp {:.3f} C".format(i, temp))
|
|
270
|
+
if temp < min_T:
|
|
271
|
+
min_T = temp
|
|
272
|
+
if min_T < T_min:
|
|
273
|
+
i_min = i
|
|
274
|
+
break
|
|
275
|
+
|
|
276
|
+
if i_min < 0:
|
|
277
|
+
raise LookupError("No frame below {} C found. Quitting.".format(T_min))
|
|
278
|
+
|
|
279
|
+
ref_img = irbf.getImage(i_min)
|
|
280
|
+
# values = get_IR_data(ref_img)
|
|
281
|
+
|
|
282
|
+
return min_T, i_min, ref_img
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def extract_pipe_path(image, params):
|
|
286
|
+
"""Extract the "pipe path" in a petal IR image.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
----
|
|
290
|
+
image: The 2D array containing the 2 specular images
|
|
291
|
+
params: IRPetalPam object with options.
|
|
292
|
+
|
|
293
|
+
Returns
|
|
294
|
+
-------
|
|
295
|
+
pipe: the pipe contour or path.
|
|
296
|
+
|
|
297
|
+
"""
|
|
298
|
+
global the_images
|
|
299
|
+
|
|
300
|
+
the_images = [image, ]
|
|
301
|
+
pipe = get_IR_pipe(image, params)
|
|
302
|
+
pipe = contours.contour_smooth(pipe, params.contour_smooth)
|
|
303
|
+
return pipe
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def extract_mirrored_pipe_path(values, params):
|
|
307
|
+
"""Extract the path of the 2 pipes in a 2 petal image.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
----
|
|
311
|
+
values: The 2D array containing the 2 specular images
|
|
312
|
+
params: IRPetalPam object with options.
|
|
313
|
+
|
|
314
|
+
Returns
|
|
315
|
+
-------
|
|
316
|
+
(left_pipe, right_pipe): The 2 paths.
|
|
317
|
+
|
|
318
|
+
"""
|
|
319
|
+
global the_images
|
|
320
|
+
if params.rotate:
|
|
321
|
+
rotated = rotate_full_image(values)
|
|
322
|
+
else:
|
|
323
|
+
rotated = values
|
|
324
|
+
|
|
325
|
+
imgs = split_IR_image(rotated)
|
|
326
|
+
pipes = []
|
|
327
|
+
for img in imgs:
|
|
328
|
+
pipe = extract_pipe_path(img, params)
|
|
329
|
+
pipes.append(pipe)
|
|
330
|
+
|
|
331
|
+
the_images = imgs
|
|
332
|
+
return list(pipes)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def get_mirrored_petal_images(img, params):
|
|
336
|
+
"""Return the images in a mirrored petal image.
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
----
|
|
340
|
+
img (IRBImage): The image of the mirror image
|
|
341
|
+
params: IRBPetalParam object
|
|
342
|
+
|
|
343
|
+
Returns
|
|
344
|
+
-------
|
|
345
|
+
tuple of images
|
|
346
|
+
|
|
347
|
+
"""
|
|
348
|
+
if isinstance(img, IRBFile.IRBImage):
|
|
349
|
+
values = get_IR_data(img, rotate=params.rotate)
|
|
350
|
+
else:
|
|
351
|
+
values = img
|
|
352
|
+
|
|
353
|
+
images = split_IR_image(values)
|
|
354
|
+
return images
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def get_IR_data(ref_img, rotate=False):
|
|
358
|
+
"""Get the data from the image in the proper orientation.
|
|
359
|
+
|
|
360
|
+
Proper orientation means that petals are vertical (in the mirror image).
|
|
361
|
+
It will eventually try to rotate the image to compensate a camera rotation.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
----
|
|
365
|
+
ref_img: IRBimage
|
|
366
|
+
rotate: True to make the rotation compensation.
|
|
367
|
+
|
|
368
|
+
Returns
|
|
369
|
+
-------
|
|
370
|
+
2d array: The 2d array wit the temperature data.
|
|
371
|
+
|
|
372
|
+
"""
|
|
373
|
+
nrow, ncol = ref_img.image.shape
|
|
374
|
+
landscape = (ncol > nrow)
|
|
375
|
+
|
|
376
|
+
if landscape:
|
|
377
|
+
values = ref_img.image.T
|
|
378
|
+
else:
|
|
379
|
+
values = ref_img.image
|
|
380
|
+
|
|
381
|
+
if rotate:
|
|
382
|
+
values = rotate_full_image(values)
|
|
383
|
+
|
|
384
|
+
return values
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def rotate_full_image(data):
|
|
388
|
+
"""Rotates full image.
|
|
389
|
+
|
|
390
|
+
The idea is to make the line produced by the actual petal vertical.
|
|
391
|
+
This should correct rotations of the camera.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
----
|
|
395
|
+
data: The image.
|
|
396
|
+
|
|
397
|
+
Returns
|
|
398
|
+
-------
|
|
399
|
+
The image rotated.
|
|
400
|
+
|
|
401
|
+
"""
|
|
402
|
+
nrow, ncol = data.shape
|
|
403
|
+
delta = 100 * nrow/1280
|
|
404
|
+
npts = int(20 * nrow/1280 + 1)
|
|
405
|
+
Y = np.linspace(nrow, nrow-delta, npts)
|
|
406
|
+
|
|
407
|
+
npX = 20 * ncol/960
|
|
408
|
+
first_row = data[nrow-1, :]
|
|
409
|
+
indx = np.argmin(first_row)
|
|
410
|
+
A = np.zeros([len(Y), 2])
|
|
411
|
+
A[:, 0] = indx - npX # ncol/2 - npX
|
|
412
|
+
A[:, 1] = Y
|
|
413
|
+
|
|
414
|
+
B = np.zeros([len(Y), 2])
|
|
415
|
+
B[:, 0] = indx + npX # ncol/2 + npX
|
|
416
|
+
B[:, 1] = Y
|
|
417
|
+
|
|
418
|
+
# Get segments along vertical
|
|
419
|
+
segments = slice_contours(data, A, B, distance=abs(Y[1]-Y[0]), do_fit=True, show=False)
|
|
420
|
+
points = Segment.get_points_in_list(segments)
|
|
421
|
+
points -= np.array([ncol/2, nrow])
|
|
422
|
+
|
|
423
|
+
# Fit the poitns to a straight line to get the angle.
|
|
424
|
+
c = Polynomial.polyfit(points[:, 0], points[:, 1], 1)
|
|
425
|
+
angle = -math.atan(c[1])
|
|
426
|
+
|
|
427
|
+
# Rotate image
|
|
428
|
+
rotated = ndimage.rotate(data, angle=angle)
|
|
429
|
+
return rotated
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def find_slice_minimum(T, X):
|
|
433
|
+
"""Find the position of the minimum value.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
----
|
|
437
|
+
T: array with values
|
|
438
|
+
X: array with positions
|
|
439
|
+
|
|
440
|
+
Returns
|
|
441
|
+
-------
|
|
442
|
+
Tmin, Pmin: min value and position of minimum.
|
|
443
|
+
|
|
444
|
+
"""
|
|
445
|
+
indx = np.argmin(T)
|
|
446
|
+
i = j = int(indx)
|
|
447
|
+
Tmin = T[indx]
|
|
448
|
+
while i > 0:
|
|
449
|
+
if T[i-1] == Tmin:
|
|
450
|
+
i -= 1
|
|
451
|
+
else:
|
|
452
|
+
break
|
|
453
|
+
|
|
454
|
+
while j < len(T)-1:
|
|
455
|
+
if T[j+1] == Tmin:
|
|
456
|
+
j += 1
|
|
457
|
+
|
|
458
|
+
else:
|
|
459
|
+
break
|
|
460
|
+
|
|
461
|
+
Pmin = (X[i] + X[j])/2
|
|
462
|
+
return Tmin, Pmin
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def get_T_profile(data, A, B, npts=10, do_fit=False, npdim=7, debug=False):
|
|
466
|
+
"""Get the temperature profile between A and B.
|
|
467
|
+
|
|
468
|
+
Args:
|
|
469
|
+
----
|
|
470
|
+
data: the data
|
|
471
|
+
A: First point in segment
|
|
472
|
+
B: Last point in segment
|
|
473
|
+
npts: number of points in segment
|
|
474
|
+
do_fit: True if fit rather that takng minimum value
|
|
475
|
+
npdim: the degree of the polynomialused to fit.
|
|
476
|
+
debug: if True, show debug information.
|
|
477
|
+
|
|
478
|
+
Return:
|
|
479
|
+
------
|
|
480
|
+
Tmin, Pmin, S: Tmin is the minimum temperature.
|
|
481
|
+
Pmin the point of the minimum.
|
|
482
|
+
S the Temperature spread
|
|
483
|
+
In case of prblems both are None.
|
|
484
|
+
|
|
485
|
+
"""
|
|
486
|
+
global debug_plot, T_profile
|
|
487
|
+
if debug:
|
|
488
|
+
debug_plot.setup_debug('TProf')
|
|
489
|
+
|
|
490
|
+
T_profile = DebugTempProfile()
|
|
491
|
+
|
|
492
|
+
mx_dist = A.distance(B)
|
|
493
|
+
L, T, X, _data = create_profile(data, A, B, npts)
|
|
494
|
+
spread = np.sqrt(np.cov(X, aweights=np.abs(T)))
|
|
495
|
+
|
|
496
|
+
T_profile.set_data(_data)
|
|
497
|
+
|
|
498
|
+
spread = np.std(T)
|
|
499
|
+
# If we just want the minimum temperature or we have something close to a flat line
|
|
500
|
+
if not do_fit or spread < 0.35:
|
|
501
|
+
Tmin, Pmin = find_slice_minimum(T, X)
|
|
502
|
+
if debug:
|
|
503
|
+
debug_plot.plot('TProf', X, T, 'o', [Pmin], [Tmin], 'o')
|
|
504
|
+
|
|
505
|
+
return [Tmin], [L.param(Pmin)], spread
|
|
506
|
+
|
|
507
|
+
else:
|
|
508
|
+
ndof = len(X)
|
|
509
|
+
T_min = np.min(T)
|
|
510
|
+
x_min = np.min(X)
|
|
511
|
+
x_max = np.max(X)
|
|
512
|
+
|
|
513
|
+
# TODO: get rid of the IF below...
|
|
514
|
+
if ndof < npdim and ndof > 4:
|
|
515
|
+
npdim = 4
|
|
516
|
+
|
|
517
|
+
# Do the polinomialfit.
|
|
518
|
+
if ndof > npdim:
|
|
519
|
+
c, stats = Polynomial.polyfit(X, T, npdim, full=True)
|
|
520
|
+
pfunc = Polynomial.Polynomial(c)
|
|
521
|
+
|
|
522
|
+
# Get the polinomial derivative roots and curvature.
|
|
523
|
+
# This is done to catch double minimums
|
|
524
|
+
|
|
525
|
+
# valid_roots, valid_values, valid_curv = get_derivatives(T_min, X[1], X[-1], c)
|
|
526
|
+
|
|
527
|
+
#
|
|
528
|
+
# TODO: temporary remove the search for double minima in the segment.
|
|
529
|
+
valid_roots, valid_values, valid_curv = [], [], []
|
|
530
|
+
|
|
531
|
+
if len(valid_roots) > 1:
|
|
532
|
+
print("--- Multi-minimum segment.")
|
|
533
|
+
for r, v, crv in zip(valid_roots, valid_values, valid_curv):
|
|
534
|
+
print("{} -> {} [{}]".format(r, v, crv))
|
|
535
|
+
|
|
536
|
+
# Find the polynomial minimum with `minimize` (redundant, probably)
|
|
537
|
+
X0 = X[np.argmin(T)]
|
|
538
|
+
fmin = minimize(pfunc, X0)
|
|
539
|
+
x = np.linspace(x_min, x_max, 3*npts)
|
|
540
|
+
|
|
541
|
+
if not fmin.success and len(fmin.x):
|
|
542
|
+
for V in valid_roots:
|
|
543
|
+
if abs(fmin.x[0] - V) < 1e-3:
|
|
544
|
+
fmin.success = True
|
|
545
|
+
break
|
|
546
|
+
|
|
547
|
+
# If the re is aclear minimum within the segment
|
|
548
|
+
if fmin.success and fmin.x[0] > 0 and fmin.x[0] < mx_dist:
|
|
549
|
+
# This should no happen. if minimize returns we should have found
|
|
550
|
+
# at least a valid root.
|
|
551
|
+
if len(valid_roots) == 0:
|
|
552
|
+
valid_roots = np.array([fmin.x[0]])
|
|
553
|
+
valid_values = np.array([fmin.fun])
|
|
554
|
+
|
|
555
|
+
# Prepare the resould
|
|
556
|
+
Tmin = valid_values
|
|
557
|
+
Pmin = [L.param(x) for x in valid_roots]
|
|
558
|
+
T_profile.set_function(x, L, pfunc)
|
|
559
|
+
if debug:
|
|
560
|
+
debug_plot.plot('TProf', X, T, 'o', x, pfunc(x), '-', valid_roots, valid_values, 'o')
|
|
561
|
+
|
|
562
|
+
if len(valid_roots) > 1:
|
|
563
|
+
debug_plot.setup_debug('TProfMulti')
|
|
564
|
+
debug_plot.plot('TProfMulti', X, T, 'o', x, pfunc(x), '-', valid_roots, valid_values, 'o')
|
|
565
|
+
debug_plot.set_title('TProfMulti', "Curvature: {}".format(str(valid_curv)))
|
|
566
|
+
|
|
567
|
+
return Tmin, Pmin, spread
|
|
568
|
+
|
|
569
|
+
else:
|
|
570
|
+
# No good minimum
|
|
571
|
+
Tmin, Pmin = find_slice_minimum(T, X)
|
|
572
|
+
if debug:
|
|
573
|
+
debug_plot.plot("TProf", X, T, 'o', [Pmin], [Tmin], 'o')
|
|
574
|
+
debug_plot.set_title('TProf', "Could not find the minimum")
|
|
575
|
+
|
|
576
|
+
return None, None, spread
|
|
577
|
+
|
|
578
|
+
else:
|
|
579
|
+
return None, None, spread
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def create_profile(data, A, B, npts):
|
|
583
|
+
"""Create a T profile along the line connecting A and B.
|
|
584
|
+
|
|
585
|
+
Args:
|
|
586
|
+
----
|
|
587
|
+
data (array): The data array
|
|
588
|
+
A (Point): Start point
|
|
589
|
+
B (Point): The end point
|
|
590
|
+
npts: Number of points
|
|
591
|
+
|
|
592
|
+
Returns
|
|
593
|
+
-------
|
|
594
|
+
L, T, X, _data: Line connecting A and B, T values, distance along line, data.
|
|
595
|
+
data contains information about the points in the profile:
|
|
596
|
+
0:1 Point coordinates
|
|
597
|
+
2: path length along segment,
|
|
598
|
+
3: mean temperature
|
|
599
|
+
|
|
600
|
+
"""
|
|
601
|
+
# Sample the data between the 2 given points, A and B. The data is smeared
|
|
602
|
+
# around the corresponging point by averaing the neighbors.
|
|
603
|
+
dst = A.distance(B)
|
|
604
|
+
step = dst/npts
|
|
605
|
+
L = Line(A, B)
|
|
606
|
+
T = []
|
|
607
|
+
X = []
|
|
608
|
+
_data = np.zeros([npts, 4])
|
|
609
|
+
_i = 0
|
|
610
|
+
for i in range(npts):
|
|
611
|
+
lmd = i * step
|
|
612
|
+
p = L.param(lmd)
|
|
613
|
+
ix = int(p.x)
|
|
614
|
+
iy = int(p.y)
|
|
615
|
+
|
|
616
|
+
# Get the average
|
|
617
|
+
M = data[iy-2:iy+2, ix-2:ix+2]
|
|
618
|
+
indx = np.nonzero(M)
|
|
619
|
+
if not np.any(indx):
|
|
620
|
+
continue
|
|
621
|
+
|
|
622
|
+
S = M[indx].mean()
|
|
623
|
+
|
|
624
|
+
# Store values for fit and debug.
|
|
625
|
+
if S != 0 and not np.isnan(S):
|
|
626
|
+
_data[_i, 0:2] = p
|
|
627
|
+
_data[_i, 2] = lmd
|
|
628
|
+
_data[_i, 3] = S
|
|
629
|
+
_i += 1
|
|
630
|
+
T.append(S)
|
|
631
|
+
X.append(i*step)
|
|
632
|
+
|
|
633
|
+
if _i < npts:
|
|
634
|
+
_data = _data[0:_i, :]
|
|
635
|
+
|
|
636
|
+
return L, T, X, _data
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
def get_derivatives(T_min, x_min, x_max, coeff):
|
|
640
|
+
"""Get valid roots and values of a polinomial in the given range.
|
|
641
|
+
|
|
642
|
+
Args:
|
|
643
|
+
----
|
|
644
|
+
T_min: Min value of Temperature
|
|
645
|
+
x_min: Lower edge of interval
|
|
646
|
+
x_max: Upper edge of interval
|
|
647
|
+
coeff: Polinomial coefficients
|
|
648
|
+
|
|
649
|
+
Returns
|
|
650
|
+
-------
|
|
651
|
+
List: roots, values@roots, curvature@roots
|
|
652
|
+
|
|
653
|
+
"""
|
|
654
|
+
npdim = len(coeff)
|
|
655
|
+
pfunc = Polynomial.Polynomial(coeff)
|
|
656
|
+
fact = np.array([x for x in range(1, npdim)])
|
|
657
|
+
cderiv = fact*coeff[1:]
|
|
658
|
+
cderiv2 = fact[:-1]*cderiv[1:]
|
|
659
|
+
|
|
660
|
+
deriv = Polynomial.Polynomial(cderiv)
|
|
661
|
+
deriv2 = Polynomial.Polynomial(cderiv2)
|
|
662
|
+
roots = Polynomial.polyroots(cderiv)
|
|
663
|
+
|
|
664
|
+
indx = np.where((np.isreal(roots)) & (roots > x_min) &
|
|
665
|
+
(roots < x_max) &
|
|
666
|
+
(pfunc(roots) < 0.5*T_min))
|
|
667
|
+
|
|
668
|
+
valid_roots = np.real(roots[indx])
|
|
669
|
+
valid_values = pfunc(valid_roots)
|
|
670
|
+
valid_curv = deriv2(valid_roots)
|
|
671
|
+
|
|
672
|
+
indx = np.where(valid_curv > 0.1)
|
|
673
|
+
valid_roots = valid_roots[indx]
|
|
674
|
+
valid_values = valid_values[indx]
|
|
675
|
+
valid_curv = valid_curv[indx]
|
|
676
|
+
|
|
677
|
+
return valid_roots, valid_values, valid_curv
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def show_profile(debug_plot):
|
|
681
|
+
"""Plot a profile."""
|
|
682
|
+
debug_plot.setup_debug("Slices", is_3d=True)
|
|
683
|
+
if T_profile:
|
|
684
|
+
if T_profile._x is not None:
|
|
685
|
+
debug_plot.plot("Slices", T_profile._P[:, 0], T_profile._P[:, 1], T_profile._T, 'ro')
|
|
686
|
+
if T_profile._pF is not None:
|
|
687
|
+
debug_plot.plotx("Slices", T_profile._pF[:, 0], T_profile._pF[:, 1], T_profile._Fn, 'b-')
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def slice_contours(data, inner, outer, distance=50, npoints=15, do_fit=False, show=False) -> list[Segment]:
|
|
691
|
+
"""Make slices between contours.
|
|
692
|
+
|
|
693
|
+
Args:
|
|
694
|
+
----
|
|
695
|
+
data: the data
|
|
696
|
+
inner: Inner contour
|
|
697
|
+
outer: Outer contour
|
|
698
|
+
distance: distance between slices
|
|
699
|
+
npoints: number of points in segment
|
|
700
|
+
do_fit: if True do fit values to get the minimum
|
|
701
|
+
show: if True show result of fits
|
|
702
|
+
|
|
703
|
+
Returns
|
|
704
|
+
-------
|
|
705
|
+
A list of Segments.
|
|
706
|
+
|
|
707
|
+
"""
|
|
708
|
+
global debug_plot
|
|
709
|
+
|
|
710
|
+
segments = []
|
|
711
|
+
dist = 0
|
|
712
|
+
npts = len(outer)
|
|
713
|
+
dist = 0
|
|
714
|
+
|
|
715
|
+
# First point in outer contour. Find closest point in inner contour
|
|
716
|
+
# and the minimum along the segment.
|
|
717
|
+
x0, y0 = outer[0, :]
|
|
718
|
+
last_x, last_y = x0, y0
|
|
719
|
+
U = contours.find_closest(x0, y0, inner)
|
|
720
|
+
Tmin, Pmin, spread = get_T_profile(data, Point(x0, y0), Point(U[0], U[1]), npts=npoints, do_fit=do_fit, debug=False)
|
|
721
|
+
if show:
|
|
722
|
+
show_profile(debug_plot)
|
|
723
|
+
|
|
724
|
+
# store results: distance along segment and temperature
|
|
725
|
+
T = []
|
|
726
|
+
D = []
|
|
727
|
+
for val in Tmin:
|
|
728
|
+
T.append(val)
|
|
729
|
+
D.append(dist)
|
|
730
|
+
|
|
731
|
+
# segments.append(((x0, y0), U, Pmin, (dist, Tmin)))
|
|
732
|
+
segments.append(Segment((x0, y0), U, Pmin, dist, Tmin, spread))
|
|
733
|
+
|
|
734
|
+
# Now loop on all the points
|
|
735
|
+
path_length = 0
|
|
736
|
+
for ipt in range(npts):
|
|
737
|
+
x, y = outer[ipt, :]
|
|
738
|
+
point_sep = math.sqrt((x-last_x)**2+(y-last_y)**2)
|
|
739
|
+
dist += point_sep
|
|
740
|
+
path_length += dist
|
|
741
|
+
last_x, last_y = x, y
|
|
742
|
+
if dist >= distance:
|
|
743
|
+
V = contours.find_closest(x, y, inner)
|
|
744
|
+
if V is None:
|
|
745
|
+
break
|
|
746
|
+
|
|
747
|
+
# Store point
|
|
748
|
+
x0, y0 = x, y
|
|
749
|
+
|
|
750
|
+
# Get the minimum on the slice
|
|
751
|
+
Tmin, Pmin, spread = get_T_profile(data, Point(x0, y0), Point(V[0], V[1]),
|
|
752
|
+
npts=npoints, do_fit=do_fit, debug=False)
|
|
753
|
+
if Tmin is not None:
|
|
754
|
+
for val in Tmin:
|
|
755
|
+
T.append(val)
|
|
756
|
+
D.append(path_length)
|
|
757
|
+
|
|
758
|
+
segments.append(Segment((x0, y0), V, Pmin, path_length, Tmin, spread))
|
|
759
|
+
if show:
|
|
760
|
+
show_profile(debug_plot)
|
|
761
|
+
|
|
762
|
+
dist = 0
|
|
763
|
+
|
|
764
|
+
# Now plot the temperature along the pipe.
|
|
765
|
+
if show:
|
|
766
|
+
debug_plot.setup_debug("SlicePipe")
|
|
767
|
+
debug_plot.plot("SlicePipe", D, T, '-o')
|
|
768
|
+
|
|
769
|
+
return segments
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
def find_image_contours(data, params):
|
|
773
|
+
"""Find the image contours to get the pipe position.
|
|
774
|
+
|
|
775
|
+
The idea is to find 2 contours so that the pipe lies between the outer and
|
|
776
|
+
inner contour.
|
|
777
|
+
|
|
778
|
+
The contours are found from the minimum value in the image and adding a
|
|
779
|
+
percentage (params.countour.cut) of the image value range (max-min).
|
|
780
|
+
|
|
781
|
+
The image is blurred before with a median filter.
|
|
782
|
+
|
|
783
|
+
Args:
|
|
784
|
+
----
|
|
785
|
+
data: The IR image.
|
|
786
|
+
params (IRPetalParams): An IRPetalParam object.
|
|
787
|
+
|
|
788
|
+
Returns
|
|
789
|
+
-------
|
|
790
|
+
list of contours. Order is [inner, outer]. None if not doog contours
|
|
791
|
+
found.
|
|
792
|
+
|
|
793
|
+
"""
|
|
794
|
+
# make a gaus filtering to smooth theimage
|
|
795
|
+
filtered = ndimage.median_filter(data, size=params.gauss_size)
|
|
796
|
+
|
|
797
|
+
# Get the actual absolute value for the contours.
|
|
798
|
+
min_T = np.min(filtered)
|
|
799
|
+
max_T = np.max(filtered)
|
|
800
|
+
target_T = min_T + params.contour_cut*(max_T-min_T)
|
|
801
|
+
|
|
802
|
+
# Get the contours
|
|
803
|
+
contour_list = measure.find_contours(filtered, target_T)
|
|
804
|
+
out = []
|
|
805
|
+
|
|
806
|
+
# Filter out the contours.
|
|
807
|
+
for contour in contour_list:
|
|
808
|
+
# Accept only closed contours
|
|
809
|
+
if np.any(contour[0, :] != contour[-1, :]):
|
|
810
|
+
continue
|
|
811
|
+
|
|
812
|
+
tmpC = contours.adjust_contour(contour)
|
|
813
|
+
area = contours.contour_area(tmpC)
|
|
814
|
+
if area < params.min_area:
|
|
815
|
+
continue
|
|
816
|
+
|
|
817
|
+
# Smooth th econtour.
|
|
818
|
+
tmpC = contours.contour_smooth(tmpC, 25)
|
|
819
|
+
out.append(tmpC)
|
|
820
|
+
|
|
821
|
+
if params.debug:
|
|
822
|
+
print("contour area: {:.3f}".format(area))
|
|
823
|
+
|
|
824
|
+
if params.debug:
|
|
825
|
+
colors = ["#ff9b54", "#ff7f51"] # ["#540b0e", "#9e2a2b"]
|
|
826
|
+
print("Number of countours:", len(out))
|
|
827
|
+
|
|
828
|
+
fig, ax = plt.subplots(1, 1, tight_layout=True)
|
|
829
|
+
ax.imshow(data, origin='lower', cmap='jet')
|
|
830
|
+
for i, cnt in enumerate(out):
|
|
831
|
+
ax.plot(cnt[:, 0], cnt[:, 1], linewidth=3, color=colors[i % 2])
|
|
832
|
+
|
|
833
|
+
ax.set_title("Contours")
|
|
834
|
+
|
|
835
|
+
# Order the contours
|
|
836
|
+
if len(out) != 2:
|
|
837
|
+
return None
|
|
838
|
+
|
|
839
|
+
outer_path = mplPath.Path(out[0])
|
|
840
|
+
inner_path = mplPath.Path(out[1])
|
|
841
|
+
if outer_path.contains_path(inner_path):
|
|
842
|
+
inner = out[1]
|
|
843
|
+
outer = out[0]
|
|
844
|
+
else:
|
|
845
|
+
inner = out[0]
|
|
846
|
+
outer = out[1]
|
|
847
|
+
|
|
848
|
+
global the_contours
|
|
849
|
+
the_contours = inner, outer
|
|
850
|
+
return inner, outer
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
def get_segments(data, params) -> list[Segment]:
|
|
854
|
+
"""Compute the pipe segments.
|
|
855
|
+
|
|
856
|
+
Args:
|
|
857
|
+
----
|
|
858
|
+
data: The IR image.
|
|
859
|
+
params (IRPetalParams): An IRPetalParam object.
|
|
860
|
+
|
|
861
|
+
Returns
|
|
862
|
+
-------
|
|
863
|
+
list of segments. See `slice_contours`
|
|
864
|
+
|
|
865
|
+
"""
|
|
866
|
+
cntrs = find_image_contours(data, params)
|
|
867
|
+
if cntrs is None:
|
|
868
|
+
return None
|
|
869
|
+
|
|
870
|
+
global the_segments
|
|
871
|
+
the_segments = slice_contours(data, cntrs[0], cntrs[1],
|
|
872
|
+
distance=params.distance,
|
|
873
|
+
npoints=params.npoints,
|
|
874
|
+
do_fit=params.do_fit,
|
|
875
|
+
show=params.debug)
|
|
876
|
+
|
|
877
|
+
return the_segments
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
def get_IR_pipe(data, params):
|
|
881
|
+
"""Return the pipe path in the IR image.
|
|
882
|
+
|
|
883
|
+
Args:
|
|
884
|
+
----
|
|
885
|
+
data: The IR image.
|
|
886
|
+
params (IRPetalParams): An IRPetalParam object.
|
|
887
|
+
|
|
888
|
+
Returns
|
|
889
|
+
-------
|
|
890
|
+
The contour of the pipe amd the list of segments.
|
|
891
|
+
None in case of problems.
|
|
892
|
+
|
|
893
|
+
"""
|
|
894
|
+
segments = get_segments(data, params)
|
|
895
|
+
if segments is None:
|
|
896
|
+
return None
|
|
897
|
+
|
|
898
|
+
min_path = Segment.get_points_in_list(segments)
|
|
899
|
+
return min_path
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
def remove_outliers(data, cut=2.0, debug=False):
|
|
903
|
+
"""Remove points far away form the rest.
|
|
904
|
+
|
|
905
|
+
Args:
|
|
906
|
+
----
|
|
907
|
+
data : The data
|
|
908
|
+
cut: max allowed distance
|
|
909
|
+
debug: be bverbose if True
|
|
910
|
+
|
|
911
|
+
"""
|
|
912
|
+
# move to the
|
|
913
|
+
d = np.abs(data - np.median(data))
|
|
914
|
+
mdev = np.median(d)
|
|
915
|
+
s = d / (mdev if mdev else 1.)
|
|
916
|
+
return data[s < cut]
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
def get_T_along_path(irpath, data, width, norm=True):
|
|
920
|
+
"""Get the mean T along the IRpath.
|
|
921
|
+
|
|
922
|
+
Temperature is computed as the average in a widthxwidth square
|
|
923
|
+
around the points in the path.
|
|
924
|
+
|
|
925
|
+
Args:
|
|
926
|
+
----
|
|
927
|
+
irpath: coordinates in image reference of the points
|
|
928
|
+
defining the path.
|
|
929
|
+
data: the 2D data matrix
|
|
930
|
+
width: half width of rectangle around point to compute the average.
|
|
931
|
+
norm: if True will normalize teh X coordinates to go fro m0 to 1.
|
|
932
|
+
Otherwise they will represen the path length.
|
|
933
|
+
|
|
934
|
+
Returns
|
|
935
|
+
-------
|
|
936
|
+
values, D: values are the temperature.
|
|
937
|
+
D is the path legnth at this point.
|
|
938
|
+
If norm is True, it will be normalized to a total distance of 1.
|
|
939
|
+
|
|
940
|
+
"""
|
|
941
|
+
npts = len(irpath)
|
|
942
|
+
values = np.zeros(npts)
|
|
943
|
+
shape = data.shape
|
|
944
|
+
D = np.linspace(0, npts-1, npts)
|
|
945
|
+
dst = 0
|
|
946
|
+
X0 = 0
|
|
947
|
+
for i in range(npts):
|
|
948
|
+
X = irpath[i, :]
|
|
949
|
+
ix1 = int(round(max(0, X[0]-width)))
|
|
950
|
+
ix2 = int(round(min(shape[1], X[0]+width)))
|
|
951
|
+
iy1 = int(round(max(0, X[1]-width)))
|
|
952
|
+
iy2 = int(round(min(shape[0], X[1]+width)))
|
|
953
|
+
|
|
954
|
+
the_region = data[iy1:iy2, ix1:ix2]
|
|
955
|
+
values[i] = np.mean(the_region)
|
|
956
|
+
std = np.std(the_region)
|
|
957
|
+
|
|
958
|
+
if std > 1.0:
|
|
959
|
+
# print("Expect problems.")
|
|
960
|
+
# print("--- ({})".format(X))
|
|
961
|
+
# print(the_region)
|
|
962
|
+
# print("avg: {:.4f} std {:.4f} sample {}". format(values[i], std, the_region.size))
|
|
963
|
+
val = remove_outliers(the_region, 1.5)
|
|
964
|
+
values[i] = np.mean(val)
|
|
965
|
+
std = np.std(val)
|
|
966
|
+
# print("avg: {:.4f} std {:.4f} sample {}". format(values[i], std, len(val)))
|
|
967
|
+
|
|
968
|
+
if i:
|
|
969
|
+
dst += np.linalg.norm(X-X0)
|
|
970
|
+
|
|
971
|
+
D[i] = dst
|
|
972
|
+
X0 = X
|
|
973
|
+
|
|
974
|
+
if norm:
|
|
975
|
+
D = D/dst
|
|
976
|
+
|
|
977
|
+
return values, D
|
|
978
|
+
|
|
979
|
+
|
|
980
|
+
def find_edge(data, i0, half, indices):
|
|
981
|
+
"""Find edges."""
|
|
982
|
+
i1 = i0
|
|
983
|
+
last = -1
|
|
984
|
+
for i in indices:
|
|
985
|
+
x = data[i]
|
|
986
|
+
if indices[0] <= indices[-1]:
|
|
987
|
+
if x > half:
|
|
988
|
+
break
|
|
989
|
+
|
|
990
|
+
else:
|
|
991
|
+
if x < half:
|
|
992
|
+
break
|
|
993
|
+
|
|
994
|
+
if last < 0:
|
|
995
|
+
i1 = x
|
|
996
|
+
last = x
|
|
997
|
+
continue
|
|
998
|
+
|
|
999
|
+
else:
|
|
1000
|
+
if abs(x-last) <= 2:
|
|
1001
|
+
i1 = x
|
|
1002
|
+
last = x
|
|
1003
|
+
|
|
1004
|
+
return i1
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
def get_spread_along_path(irpath, data, width, norm=True, debug=False):
|
|
1008
|
+
"""Get the T spread along the IRpath.
|
|
1009
|
+
|
|
1010
|
+
At each point we build the line perpendicular to the path
|
|
1011
|
+
and compute the temperature spread in this line.
|
|
1012
|
+
|
|
1013
|
+
Args:
|
|
1014
|
+
----
|
|
1015
|
+
irpath: coordinates in image reference of the points
|
|
1016
|
+
defining the path.
|
|
1017
|
+
data: the 2D data matrix
|
|
1018
|
+
width: half width of rectangle around point to compute the average.
|
|
1019
|
+
norm: if True will normalize teh X coordinates to go fro m0 to 1.
|
|
1020
|
+
Otherwise they will represen the path length.
|
|
1021
|
+
|
|
1022
|
+
Returns
|
|
1023
|
+
-------
|
|
1024
|
+
values, D: values are the temperature spread.
|
|
1025
|
+
D is the path length at this point.
|
|
1026
|
+
If norm is True, it will be normalized to a total distance of 1.
|
|
1027
|
+
|
|
1028
|
+
"""
|
|
1029
|
+
npts = len(irpath)
|
|
1030
|
+
values = np.zeros(npts)
|
|
1031
|
+
shape = data.shape
|
|
1032
|
+
|
|
1033
|
+
# GEt the path length
|
|
1034
|
+
D = contours.contour_path_length(irpath, norm)
|
|
1035
|
+
|
|
1036
|
+
# Start with the spread calculation
|
|
1037
|
+
if debug:
|
|
1038
|
+
dbg_pts = [None, None]
|
|
1039
|
+
|
|
1040
|
+
for i in range(npts):
|
|
1041
|
+
X = Point(irpath[i, :])
|
|
1042
|
+
|
|
1043
|
+
try:
|
|
1044
|
+
L = Line(X, Point(irpath[i+1, :]))
|
|
1045
|
+
except Exception as E:
|
|
1046
|
+
L = Line(X, Point(irpath[0, :]))
|
|
1047
|
+
|
|
1048
|
+
# Get the line perpendicular.
|
|
1049
|
+
pL = L.line_perpendicular_at_point(X)
|
|
1050
|
+
xp = np.linspace(-width, width, 2*width+1)
|
|
1051
|
+
xt = np.zeros(xp.shape)
|
|
1052
|
+
for j, ix in enumerate(xp):
|
|
1053
|
+
p = X + ix*pL.V
|
|
1054
|
+
if debug:
|
|
1055
|
+
dbg_pts[1 if j else 0] = p
|
|
1056
|
+
|
|
1057
|
+
ix1 = int(round(max(0, p[0]-1)))
|
|
1058
|
+
ix2 = int(round(min(shape[1], p[0]+1)))
|
|
1059
|
+
iy1 = int(round(max(0, p[1]-1)))
|
|
1060
|
+
iy2 = int(round(min(shape[0], p[1]+1)))
|
|
1061
|
+
|
|
1062
|
+
the_region = data[iy1:iy2, ix1:ix2]
|
|
1063
|
+
xt[j] = np.mean(the_region)
|
|
1064
|
+
|
|
1065
|
+
npts = len(xt)
|
|
1066
|
+
xta = np.abs(xt)
|
|
1067
|
+
grd = np.gradient(xta)
|
|
1068
|
+
|
|
1069
|
+
edges = np.where(abs(grd) > 2)[0]
|
|
1070
|
+
nedg = len(edges)
|
|
1071
|
+
half = int(npts/2)
|
|
1072
|
+
|
|
1073
|
+
i1 = 0
|
|
1074
|
+
i2 = npts-1
|
|
1075
|
+
if nedg == 1:
|
|
1076
|
+
if edges[0] < half:
|
|
1077
|
+
i1 = edges[0]
|
|
1078
|
+
i2 = npts-1
|
|
1079
|
+
else:
|
|
1080
|
+
i1 = 0
|
|
1081
|
+
i2 = edges[0]
|
|
1082
|
+
elif nedg:
|
|
1083
|
+
i1 = find_edge(edges, 0, half, [x for x in range(nedg)])
|
|
1084
|
+
i2 = find_edge(edges, npts-1, half, [x for x in range(nedg-1, -1, -1)])
|
|
1085
|
+
|
|
1086
|
+
try:
|
|
1087
|
+
qtl = np.quantile(xta[i1:i2], [0.25, 0.5, 0.75, 1])
|
|
1088
|
+
iqr = qtl[2]-qtl[0]
|
|
1089
|
+
values[i] = iqr
|
|
1090
|
+
|
|
1091
|
+
except Exception as e:
|
|
1092
|
+
print("Problems.\n{}".format(repr(e)))
|
|
1093
|
+
pass
|
|
1094
|
+
|
|
1095
|
+
# Debugging.
|
|
1096
|
+
if debug:
|
|
1097
|
+
xxx = np.get_printoptions()
|
|
1098
|
+
np.set_printoptions(precision=2)
|
|
1099
|
+
print("{} -> {:.2f}".format(qtl, iqr))
|
|
1100
|
+
np.set_printoptions(precision=xxx['precision'])
|
|
1101
|
+
debug_plot.setup_debug("TSpread", 1, 3,
|
|
1102
|
+
fig_kw={"figsize": (10, 5),
|
|
1103
|
+
"gridspec_kw": {'width_ratios': (0.35, 0.35, 0.3)}})
|
|
1104
|
+
ax = debug_plot.get_ax("TSpread")
|
|
1105
|
+
ax[0].clear()
|
|
1106
|
+
ax[0].plot(D[:i+1], values[:i+1], 'o-')
|
|
1107
|
+
|
|
1108
|
+
ax[1].clear()
|
|
1109
|
+
ax[1].plot(xp, xt, 'o-')
|
|
1110
|
+
xx = [xp[i1], xp[i2]]
|
|
1111
|
+
yy = [-qtl[3], -qtl[3]]
|
|
1112
|
+
ax[1].plot(xx, yy, 'o-')
|
|
1113
|
+
|
|
1114
|
+
xl = ax[1].get_xlim()
|
|
1115
|
+
yl = ax[1].get_ylim()
|
|
1116
|
+
ax[1].text(0.5*(xl[0]+xl[1]), yl[1]-0.1*abs(yl[1]-yl[0]),
|
|
1117
|
+
"IQR: {:.2f}".format(iqr),
|
|
1118
|
+
horizontalalignment="center")
|
|
1119
|
+
|
|
1120
|
+
debug_plot.setup_debug("TSpread", 1, 2)
|
|
1121
|
+
ax = debug_plot.get_ax("TSpread")
|
|
1122
|
+
ax[2].clear()
|
|
1123
|
+
ax[2].imshow(data, origin="lower", cmap='jet')
|
|
1124
|
+
ax[2].plot(irpath[:, 0], irpath[:, 1], '-o')
|
|
1125
|
+
ax[2].plot([dbg_pts[0][0], dbg_pts[1][0]], [dbg_pts[0][1], dbg_pts[1][1]], '-', linewidth=2, color="black")
|
|
1126
|
+
plt.draw()
|
|
1127
|
+
plt.pause(0.0001)
|
|
1128
|
+
|
|
1129
|
+
return values, D
|
|
1130
|
+
|
|
1131
|
+
|
|
1132
|
+
def show_data(data, params, fname=None, id=0):
|
|
1133
|
+
"""Show the data histograms.
|
|
1134
|
+
|
|
1135
|
+
Args:
|
|
1136
|
+
----
|
|
1137
|
+
data: The data array
|
|
1138
|
+
params: a IRPetalParam object or equivalent with parameters
|
|
1139
|
+
fname : Name of output file with images. Defaults to None.
|
|
1140
|
+
id: tells whether it is front (0) or back (1)
|
|
1141
|
+
|
|
1142
|
+
"""
|
|
1143
|
+
min_path = get_IR_pipe(data, params)
|
|
1144
|
+
segments = get_last_segments()
|
|
1145
|
+
cntrs = get_last_contours() # find_image_contours(data, params)
|
|
1146
|
+
|
|
1147
|
+
nrow, ncol = data.shape
|
|
1148
|
+
colors = ["#540b0e", "#9e2a2b"]
|
|
1149
|
+
fig, ax = plt.subplots(1, 1, tight_layout=True, dpi=300, figsize=(3, 4.5)) # figsize=(ncol/200, nrow/300))
|
|
1150
|
+
|
|
1151
|
+
ax.set_title("Contours - {}".format(id))
|
|
1152
|
+
pcm = ax.imshow(data, origin='lower', cmap="jet")
|
|
1153
|
+
fig.colorbar(pcm, ax=ax)
|
|
1154
|
+
fig.savefig("single-petal-{}.png".format(id))
|
|
1155
|
+
|
|
1156
|
+
for i, cnt in enumerate(cntrs):
|
|
1157
|
+
ax.plot(cnt[:, 0], cnt[:, 1], linewidth=3, color=colors[i % 2])
|
|
1158
|
+
|
|
1159
|
+
fig.savefig("single-petal-contours-{}.png".format(id))
|
|
1160
|
+
|
|
1161
|
+
for S in segments:
|
|
1162
|
+
ax.plot([S.A[0], S.B[0]], [S.A[1], S.B[1]], 'o-', linewidth=1, markersize=2, color="#788aa3")
|
|
1163
|
+
for P in S.Pmin:
|
|
1164
|
+
ax.plot(P[0], P[1], 'o', linewidth=1, markersize=2, color="#788aa3")
|
|
1165
|
+
|
|
1166
|
+
fig.savefig("single-petal-segments-{}.png".format(id))
|
|
1167
|
+
|
|
1168
|
+
ax.plot(min_path[:, 0], min_path[:, 1], linewidth=1, color="black")
|
|
1169
|
+
fig.savefig("single-petal-all-{}.png".format(id))
|
|
1170
|
+
|
|
1171
|
+
np.savetxt("min_path_{}.csv".format(id), min_path, delimiter=',', fmt='%.4f')
|
|
1172
|
+
if fname:
|
|
1173
|
+
fig.savefig(fname, dpi=300)
|
|
1174
|
+
|
|
1175
|
+
|
|
1176
|
+
def split_IR_image(values, fraction=0.5):
|
|
1177
|
+
"""Split the image in 2 halves.
|
|
1178
|
+
|
|
1179
|
+
Args:
|
|
1180
|
+
----
|
|
1181
|
+
values: origina matrix with image values
|
|
1182
|
+
fraction: Tell fraction of left over right parts of image
|
|
1183
|
+
|
|
1184
|
+
"""
|
|
1185
|
+
nrow, ncol = values.shape
|
|
1186
|
+
half = int(fraction*ncol)
|
|
1187
|
+
|
|
1188
|
+
left = values[:, 0:half]
|
|
1189
|
+
right = values[:, half:]
|
|
1190
|
+
|
|
1191
|
+
return left, right
|
|
1192
|
+
|
|
1193
|
+
|
|
1194
|
+
def get_image_from_irb(irbf, frame, thrs):
|
|
1195
|
+
"""Return image.
|
|
1196
|
+
|
|
1197
|
+
Args:
|
|
1198
|
+
----
|
|
1199
|
+
irbf: the IRBFile object.
|
|
1200
|
+
frame: if >=0, it will return the frame number given here.
|
|
1201
|
+
thrs: if frame is <0, it will return the first image whose
|
|
1202
|
+
the lower temperature is smaller than this value
|
|
1203
|
+
|
|
1204
|
+
Returns
|
|
1205
|
+
-------
|
|
1206
|
+
if the conditions are mot met (frame or threshold) it will
|
|
1207
|
+
raise LookupError.
|
|
1208
|
+
|
|
1209
|
+
img: the image
|
|
1210
|
+
i_min: the index of the image in the sequence.
|
|
1211
|
+
|
|
1212
|
+
"""
|
|
1213
|
+
img = None
|
|
1214
|
+
i_min = frame
|
|
1215
|
+
if irbf.n_images() == 0:
|
|
1216
|
+
print("Input file does not contain images.")
|
|
1217
|
+
|
|
1218
|
+
else:
|
|
1219
|
+
if frame >= 0:
|
|
1220
|
+
img = irbf.getImage(frame)
|
|
1221
|
+
if img is None:
|
|
1222
|
+
raise LookupError("Frame {} not found in [0, {}]".format(frame, irbf.n_images()))
|
|
1223
|
+
|
|
1224
|
+
else:
|
|
1225
|
+
min_T, i_min, *_ = find_reference_image(irbf, thrs)
|
|
1226
|
+
img = irbf.getImage(i_min)
|
|
1227
|
+
|
|
1228
|
+
print("min_T {:.1f}".format(min_T))
|
|
1229
|
+
|
|
1230
|
+
return img, i_min
|
|
1231
|
+
|
|
1232
|
+
|
|
1233
|
+
def read_image(fnam, frame=-1, thrs=-20):
|
|
1234
|
+
"""Open the data file and returns an image.
|
|
1235
|
+
|
|
1236
|
+
Args:
|
|
1237
|
+
----
|
|
1238
|
+
fnam: The file path
|
|
1239
|
+
indx (int, optional): If an IRB file, this is the image index.
|
|
1240
|
+
|
|
1241
|
+
Returns
|
|
1242
|
+
-------
|
|
1243
|
+
An image
|
|
1244
|
+
|
|
1245
|
+
"""
|
|
1246
|
+
ifile = Path(fnam).expanduser().resolve()
|
|
1247
|
+
if not ifile.exists():
|
|
1248
|
+
print("Input file does not exist.")
|
|
1249
|
+
return None
|
|
1250
|
+
|
|
1251
|
+
suffix = ifile.suffix.lower()
|
|
1252
|
+
img = None
|
|
1253
|
+
if suffix == ".csv":
|
|
1254
|
+
img = CSVImage.CSVImage(ifile)
|
|
1255
|
+
|
|
1256
|
+
elif suffix == ".irb":
|
|
1257
|
+
irbf = IRBFile.IRBFile(ifile)
|
|
1258
|
+
img = get_image_from_irb(irbf, frame, thrs)
|
|
1259
|
+
|
|
1260
|
+
else:
|
|
1261
|
+
try:
|
|
1262
|
+
irb = IRBFile.IRBFile.load(ifile)
|
|
1263
|
+
img = get_image_from_irb(irbf, frame, thrs)
|
|
1264
|
+
|
|
1265
|
+
except Exception as eee:
|
|
1266
|
+
print(eee)
|
|
1267
|
+
|
|
1268
|
+
return img
|
|
1269
|
+
|
|
1270
|
+
|
|
1271
|
+
def main(fnam, options):
|
|
1272
|
+
"""Read data and plot edges."""
|
|
1273
|
+
params = IRPetalParam(options)
|
|
1274
|
+
params.debug = False
|
|
1275
|
+
|
|
1276
|
+
print("Open file")
|
|
1277
|
+
img = read_image(fnam, options.frame, options.thrs)
|
|
1278
|
+
if img is None:
|
|
1279
|
+
sys.exit()
|
|
1280
|
+
|
|
1281
|
+
# Show original Image
|
|
1282
|
+
fig, ax = plt.subplots(1, 1, dpi=300)
|
|
1283
|
+
values = get_IR_data(img, False)
|
|
1284
|
+
min_T = np.min(values)
|
|
1285
|
+
fig.suptitle("Original image - Temp. {:.1f}".format(min_T))
|
|
1286
|
+
pcm = ax.imshow(values, origin='lower', cmap="jet")
|
|
1287
|
+
fig.colorbar(pcm, ax=ax)
|
|
1288
|
+
fig.savefig("original-IR-img.png")
|
|
1289
|
+
|
|
1290
|
+
# Show rotated image
|
|
1291
|
+
values = get_IR_data(img, True)
|
|
1292
|
+
min_T = np.min(values)
|
|
1293
|
+
fig, ax = plt.subplots(1, 1, dpi=300)
|
|
1294
|
+
fig.suptitle("Rotated image - Temp. {:.1f}".format(min_T))
|
|
1295
|
+
pcm = ax.imshow(values, origin='lower', cmap="jet")
|
|
1296
|
+
fig.colorbar(pcm, ax=ax)
|
|
1297
|
+
fig.savefig("rotated-IR-image.png")
|
|
1298
|
+
|
|
1299
|
+
# Split image
|
|
1300
|
+
left, right = split_IR_image(values)
|
|
1301
|
+
|
|
1302
|
+
# anad show
|
|
1303
|
+
print("Draw")
|
|
1304
|
+
show_data(left, params, fname="front.png", id=1)
|
|
1305
|
+
show_data(right, params, fname="back.png", id=2)
|
|
1306
|
+
|
|
1307
|
+
plt.show()
|
|
1308
|
+
|
|
1309
|
+
|
|
1310
|
+
if __name__ == "__main__":
|
|
1311
|
+
P = IRPetalParam()
|
|
1312
|
+
parser = ArgumentParser()
|
|
1313
|
+
parser.add_argument('files', nargs='*', help="Input files")
|
|
1314
|
+
parser.add_argument('--frame', default=-1, type=int, help="Frame to analize")
|
|
1315
|
+
parser.add_argument("--thrs", type=float, default=-22, help="Temperature threshold")
|
|
1316
|
+
parser.add_argument("--distance", type=float,
|
|
1317
|
+
default=P.distance, help="Distance in contour beteween slices")
|
|
1318
|
+
parser.add_argument("--npoints", type=int,
|
|
1319
|
+
default=P.npoints, help="Number of points per segment")
|
|
1320
|
+
parser.add_argument("--orig", action="store_true",
|
|
1321
|
+
default=False, help="plot the original image")
|
|
1322
|
+
parser.add_argument("--do_fit", action="store_true",
|
|
1323
|
+
default=P.do_fit,
|
|
1324
|
+
help="Do a fit to find the minimum in a slice rather than looking returning the minimum value.")
|
|
1325
|
+
parser.add_argument("--do_min", dest="do_fit", action="store_false",
|
|
1326
|
+
help="Use the minimum valueo in a slice rather than fitting.")
|
|
1327
|
+
parser.add_argument("--debug", action="store_true",
|
|
1328
|
+
default=False,
|
|
1329
|
+
help="debug")
|
|
1330
|
+
|
|
1331
|
+
options = parser.parse_args()
|
|
1332
|
+
if len(options.files) == 0:
|
|
1333
|
+
print("I need an input file")
|
|
1334
|
+
sys.exit()
|
|
1335
|
+
else:
|
|
1336
|
+
ifile = options.files[0]
|
|
1337
|
+
|
|
1338
|
+
main(ifile, options)
|