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.

Files changed (51) hide show
  1. petal_qc/BTreport/CheckBTtests.py +321 -0
  2. petal_qc/BTreport/__init__.py +0 -0
  3. petal_qc/BTreport/bustapeReport.py +144 -0
  4. petal_qc/__init__.py +14 -0
  5. petal_qc/metrology/Cluster.py +90 -0
  6. petal_qc/metrology/DataFile.py +47 -0
  7. petal_qc/metrology/PetalMetrology.py +327 -0
  8. petal_qc/metrology/__init__.py +0 -0
  9. petal_qc/metrology/all2csv.py +57 -0
  10. petal_qc/metrology/analyze_locking_points.py +597 -0
  11. petal_qc/metrology/cold_noise.py +106 -0
  12. petal_qc/metrology/comparisonTable.py +59 -0
  13. petal_qc/metrology/convert_mitutoyo.py +175 -0
  14. petal_qc/metrology/convert_smartscope.py +145 -0
  15. petal_qc/metrology/coreMetrology.py +402 -0
  16. petal_qc/metrology/data2csv.py +63 -0
  17. petal_qc/metrology/do_Metrology.py +117 -0
  18. petal_qc/metrology/flatness4nigel.py +157 -0
  19. petal_qc/metrology/gtkutils.py +120 -0
  20. petal_qc/metrology/petal_flatness.py +353 -0
  21. petal_qc/metrology/show_data_file.py +118 -0
  22. petal_qc/metrology/testSummary.py +37 -0
  23. petal_qc/metrology/test_paralelism.py +71 -0
  24. petal_qc/thermal/CSVImage.py +69 -0
  25. petal_qc/thermal/DebugPlot.py +76 -0
  26. petal_qc/thermal/IRBFile.py +768 -0
  27. petal_qc/thermal/IRCore.py +110 -0
  28. petal_qc/thermal/IRDataGetter.py +359 -0
  29. petal_qc/thermal/IRPetal.py +1338 -0
  30. petal_qc/thermal/IRPetalParam.py +71 -0
  31. petal_qc/thermal/PetalColorMaps.py +62 -0
  32. petal_qc/thermal/Petal_IR_Analysis.py +142 -0
  33. petal_qc/thermal/PipeFit.py +598 -0
  34. petal_qc/thermal/__init__.py +0 -0
  35. petal_qc/thermal/analyze_IRCore.py +417 -0
  36. petal_qc/thermal/contours.py +378 -0
  37. petal_qc/thermal/create_IRCore.py +185 -0
  38. petal_qc/thermal/pipe_read.py +182 -0
  39. petal_qc/thermal/show_IR_petal.py +420 -0
  40. petal_qc/utils/Geometry.py +756 -0
  41. petal_qc/utils/Progress.py +182 -0
  42. petal_qc/utils/__init__.py +0 -0
  43. petal_qc/utils/all_files.py +35 -0
  44. petal_qc/utils/docx_utils.py +186 -0
  45. petal_qc/utils/fit_utils.py +188 -0
  46. petal_qc/utils/utils.py +180 -0
  47. petal_qc-0.0.0.dist-info/METADATA +29 -0
  48. petal_qc-0.0.0.dist-info/RECORD +51 -0
  49. petal_qc-0.0.0.dist-info/WHEEL +5 -0
  50. petal_qc-0.0.0.dist-info/entry_points.txt +3 -0
  51. 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)