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