petal-qc 0.0.17__py3-none-any.whl → 0.0.24__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.
Files changed (36) hide show
  1. petal_qc/PetalReceptionTests.py +23 -5
  2. petal_qc/__init__.py +6 -1
  3. petal_qc/getPetalCoreTestSummary.py +69 -9
  4. petal_qc/metrology/PetalMetrology.py +23 -11
  5. petal_qc/metrology/analyze_locking_points.py +14 -3
  6. petal_qc/metrology/compare_Cores.py +15 -2
  7. petal_qc/metrology/convert_mitutoyo.py +5 -0
  8. petal_qc/metrology/coreMetrology.py +16 -26
  9. petal_qc/metrology/do_Metrology.py +1 -1
  10. petal_qc/metrology/petal_flatness.py +10 -9
  11. petal_qc/metrology/readAVSdata.py +11 -4
  12. petal_qc/metrology/test_paralelism.py +2 -2
  13. petal_qc/metrology/uploadPetalInformation.py +35 -3
  14. petal_qc/readTemplateTable.py +313 -0
  15. petal_qc/test/analyzeMetrologyTable.py +158 -29
  16. petal_qc/test/checkPipeShipments.py +60 -0
  17. petal_qc/test/getAVStests.py +2 -2
  18. petal_qc/test/reportFromJSon.py +35 -6
  19. petal_qc/test/testMitutoyo.py +10 -0
  20. petal_qc/test/thermalReportFromJSon.py +99 -0
  21. petal_qc/thermal/IRDataGetter.py +2 -0
  22. petal_qc/thermal/IRPetal.py +20 -6
  23. petal_qc/thermal/IRPetalParam.py +12 -3
  24. petal_qc/thermal/Petal_IR_Analysis.py +1 -1
  25. petal_qc/thermal/PipeFit.py +24 -10
  26. petal_qc/thermal/PipeIterFit.py +94 -0
  27. petal_qc/thermal/contours.py +82 -3
  28. petal_qc/thermal/coreThermal.py +2 -2
  29. petal_qc/thermal/create_IRCore.py +11 -6
  30. petal_qc/thermal/create_core_report.py +0 -3
  31. petal_qc/uploadXrays.py +86 -0
  32. {petal_qc-0.0.17.dist-info → petal_qc-0.0.24.dist-info}/METADATA +2 -3
  33. {petal_qc-0.0.17.dist-info → petal_qc-0.0.24.dist-info}/RECORD +36 -30
  34. {petal_qc-0.0.17.dist-info → petal_qc-0.0.24.dist-info}/WHEEL +1 -1
  35. {petal_qc-0.0.17.dist-info → petal_qc-0.0.24.dist-info}/entry_points.txt +1 -0
  36. {petal_qc-0.0.17.dist-info → petal_qc-0.0.24.dist-info}/top_level.txt +0 -0
@@ -6,17 +6,43 @@ from pathlib import Path
6
6
  import json
7
7
 
8
8
 
9
+ try:
10
+ import petal_qc
11
+
12
+ except ImportError:
13
+ cwd = Path(__file__).parent.parent
14
+ sys.path.append(cwd.as_posix())
15
+
16
+ from petal_qc.utils.ArgParserUtils import RangeListAction
17
+ from petal_qc.utils.all_files import all_files
18
+
19
+
9
20
 
10
21
  def main(options):
11
22
  """main entry."""
12
23
  petal_cores = {}
13
- for fnam in options.files:
14
- ifile = Path(fnam).expanduser().resolve()
15
- with open(ifile, "r", encoding="utf-8") as fin:
24
+ for fpath in options.files:
25
+ fnam = str(fpath)
26
+ if "PPC." not in fnam:
27
+ continue
28
+
29
+ ipos = fnam.find("PPC")
30
+ lpos = fnam[ipos:].find("-")
31
+ petal_id = fnam[ipos:ipos+lpos]
32
+ pid = int(petal_id[4:])
33
+
34
+ if len(options.cores)>0 and pid not in options.cores:
35
+ continue
36
+
37
+ with open(fpath, "r", encoding="utf-8") as fin:
16
38
  data = json.load(fin)
17
39
 
18
40
  if not data["passed"]:
19
- petalId = data["component"]
41
+ if data["component"] is None:
42
+ print("Petal {} has bad Serial number".format(petal_id))
43
+ continue
44
+
45
+ petalId = "{}-{}".format(petal_id, data["component"])
20
46
  if petalId not in petal_cores:
21
47
  petal_cores[petalId] = {"FRONT": [], "BACK": []}
22
48
 
@@ -40,12 +66,15 @@ def main(options):
40
66
  if __name__ == "__main__":
41
67
  parser = argparse.ArgumentParser()
42
68
  parser.add_argument('files', nargs='*', help="Input files")
69
+ parser.add_argument("--cores", dest="cores", action=RangeListAction, default=[],
70
+ help="Create list of cores to analyze. The list is made with numbers or ranges (ch1:ch2 or ch1:ch2:step) ")
43
71
  opts = parser.parse_args()
44
72
 
45
- from petal_qc.utils.all_files import all_files
46
73
 
74
+ #folder = Path("/tmp/petal-metrology/results")
75
+ folder = Path("~/tmp/petal-metrology/Production/Results").expanduser()
47
76
  opts.files = []
48
- for fnam in all_files(Path("~/tmp/petal-metrology/Production/Results").expanduser(), "*.json"):
77
+ for fnam in all_files(folder, "*.json"):
49
78
  opts.files.append(fnam)
50
79
 
51
80
  main(opts)
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env python
2
+ """Test."""
3
+
4
+ from pathlib import Path
5
+ from petal_qc.metrology import DataFile
6
+
7
+ ifile = Path("/tmp/petals/re-measured/test-xx.txt")
8
+
9
+ data = DataFile.read(ifile, r"Punto(-|(Vision-))\d", "Punto")
10
+ print(data)
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env python3
2
+ """List failing cores from JSon files."""
3
+ import sys
4
+ import argparse
5
+ import re
6
+ from pathlib import Path
7
+ import json
8
+
9
+ from itkdb_gtk import ITkDBlogin
10
+ from itkdb_gtk import ITkDButils
11
+
12
+ try:
13
+ import petal_qc
14
+
15
+ except ImportError:
16
+ cwd = Path(__file__).parent.parent
17
+ sys.path.append(cwd.as_posix())
18
+
19
+ from petal_qc.utils.ArgParserUtils import RangeListAction
20
+ from petal_qc.utils.all_files import all_files
21
+
22
+
23
+ match_fnam = re.compile("20US.*Thermal.json", re.DOTALL)
24
+
25
+
26
+ def main(client, options):
27
+ """main entry."""
28
+ petal_cores = {}
29
+ for fpath in options.files:
30
+ fnam = str(fpath.name)
31
+ M = match_fnam.match(fnam)
32
+ if M is None:
33
+ continue
34
+
35
+ # ipos = fnam.find("PPC")
36
+ # lpos = fnam[ipos:].find("-")
37
+ # petal_id = fnam[ipos:ipos+lpos]
38
+ # pid = int(petal_id[4:])
39
+ #
40
+ # if len(options.cores)>0 and pid not in options.cores:
41
+ # continue
42
+
43
+ with open(fpath, "r", encoding="utf-8") as fin:
44
+ data = json.load(fin)
45
+
46
+ if not data["passed"]:
47
+ if data["component"] is None:
48
+ print("Petal {} has bad Serial number".format(petal_id))
49
+ continue
50
+
51
+ core = ITkDButils.get_DB_component(client, data["component"])
52
+ petal_id = core['alternativeIdentifier']
53
+
54
+ petalId = "{}-{}".format(petal_id, data["component"])
55
+ print (petalId)
56
+ for D in data["defects"]:
57
+ print("{}: {} {}".format(D["name"], D["description"], D["properties"]["msg"]))
58
+
59
+
60
+ keys = sorted(petal_cores.keys())
61
+ for petalId in keys:
62
+ print(petalId)
63
+ for side in ["FRONT","BACK"]:
64
+ if len(petal_cores[petalId][side])>0:
65
+ print("+-", side)
66
+ for D in petal_cores[petalId][side]:
67
+ print(" ", D)
68
+
69
+ print("\n")
70
+
71
+
72
+ if __name__ == "__main__":
73
+ parser = argparse.ArgumentParser()
74
+ parser.add_argument('files', nargs='*', help="Input files")
75
+ parser.add_argument("--cores", dest="cores", action=RangeListAction, default=[],
76
+ help="Create list of cores to analyze. The list is made with numbers or ranges (ch1:ch2 or ch1:ch2:step) ")
77
+ opts = parser.parse_args()
78
+
79
+
80
+ dlg = None
81
+ try:
82
+ # We use here the Gtk GUI
83
+ dlg = ITkDBlogin.ITkDBlogin()
84
+ client = dlg.get_client()
85
+
86
+ except Exception:
87
+ # Login with "standard" if the above fails.
88
+ client = ITkDButils.create_client()
89
+
90
+
91
+ #folder = Path("/tmp/petal-metrology/results")
92
+ folder = Path("~/SACO-CSIC/ITk-Strips/Local Supports/thermal/IFIC-thermal/thermal-new-golden/results").expanduser()
93
+ opts.files = []
94
+ for fnam in all_files(folder, "*.json"):
95
+ opts.files.append(fnam)
96
+
97
+ main(client, opts)
98
+ dlg.die()
99
+
@@ -51,6 +51,7 @@ class IRDataGetter(object):
51
51
 
52
52
  def fine_tune_params(self, param):
53
53
  """Set default values for the parameters."""
54
+ param.sensor_max = -5
54
55
  return
55
56
 
56
57
  def get_IR_data(self, image, **kargs):
@@ -298,6 +299,7 @@ class IRDataDESY(IRDataGetter):
298
299
  """Set default values for the parameters."""
299
300
  param.distance = 16
300
301
  param.width = 16
302
+ param.sensor_max = -5
301
303
 
302
304
  def get_IR_data(self, image, **kargs):
303
305
  """Get the data from the image in the proper orientation.
@@ -515,7 +515,7 @@ def find_slice_minimum(T, X):
515
515
  return Tmin, Pmin
516
516
 
517
517
 
518
- def get_T_profile(data, A, B, npts=10, do_fit=False, npdim=7, debug=False):
518
+ def get_T_profile(data, A, B, npts=10, do_fit=False, npdim=6, debug=False):
519
519
  """Get the temperature profile between A and B.
520
520
 
521
521
  Args:
@@ -569,13 +569,16 @@ def get_T_profile(data, A, B, npts=10, do_fit=False, npdim=7, debug=False):
569
569
 
570
570
  # Do the polinomialfit.
571
571
  if ndof > npdim:
572
- c, stats = Polynomial.polyfit(X, T, npdim, full=True)
572
+ indx = remove_outliers(np.array(T), 6, indx=True)
573
+ TT = np.array(T)
574
+ XX = np.array(X)
575
+ c, stats = Polynomial.polyfit(XX[indx], TT[indx], npdim, full=True)
573
576
  pfunc = Polynomial.Polynomial(c)
574
577
 
575
578
  # Get the polinomial derivative roots and curvature.
576
579
  # This is done to catch double minimums
577
580
 
578
- # valid_roots, valid_values, valid_curv = get_derivatives(T_min, X[1], X[-1], c)
581
+ valid_roots, valid_values, valid_curv = get_derivatives(T_min, X[1], X[-1], c)
579
582
 
580
583
  #
581
584
  # TODO: temporary remove the search for double minima in the segment.
@@ -585,6 +588,13 @@ def get_T_profile(data, A, B, npts=10, do_fit=False, npdim=7, debug=False):
585
588
  print("--- Multi-minimum segment.")
586
589
  for r, v, crv in zip(valid_roots, valid_values, valid_curv):
587
590
  print("{} -> {} [{}]".format(r, v, crv))
591
+
592
+ x = np.linspace(x_min, x_max, 3*npts)
593
+ debug_plot.plot('TProf', X, T, 'o', x, pfunc(x), '-', valid_roots, valid_values, 'o')
594
+ if len(valid_roots) > 1:
595
+ debug_plot.setup_debug('TProfMulti')
596
+ debug_plot.plot('TProfMulti', X, T, 'o', x, pfunc(x), '-', valid_roots, valid_values, 'o')
597
+ debug_plot.set_title('TProfMulti', "Curvature: {}".format(str(valid_curv)))
588
598
 
589
599
  # Find the polynomial minimum with `minimize` (redundant, probably)
590
600
  X0 = X[np.argmin(T)]
@@ -722,7 +732,7 @@ def get_derivatives(T_min, x_min, x_max, coeff):
722
732
  valid_values = pfunc(valid_roots)
723
733
  valid_curv = deriv2(valid_roots)
724
734
 
725
- indx = np.where(valid_curv > 0.1)
735
+ indx = np.where(valid_curv > 1e-3)
726
736
  valid_roots = valid_roots[indx]
727
737
  valid_values = valid_values[indx]
728
738
  valid_curv = valid_curv[indx]
@@ -983,13 +993,14 @@ def get_IR_pipe(data, params):
983
993
  return min_path
984
994
 
985
995
 
986
- def remove_outliers(data, cut=2.0, debug=False):
996
+ def remove_outliers(data, cut=2.0, indx=False, debug=False):
987
997
  """Remove points far away form the rest.
988
998
 
989
999
  Args:
990
1000
  ----
991
1001
  data : The data
992
1002
  cut: max allowed distance
1003
+ indx: if True return indices rather than a new array
993
1004
  debug: be bverbose if True
994
1005
 
995
1006
  """
@@ -997,7 +1008,10 @@ def remove_outliers(data, cut=2.0, debug=False):
997
1008
  d = np.abs(data - np.median(data))
998
1009
  mdev = np.median(d)
999
1010
  s = d / (mdev if mdev else 1.)
1000
- return data[s < cut]
1011
+ if indx:
1012
+ return np.where(s < cut)[0]
1013
+ else:
1014
+ return data[s < cut]
1001
1015
 
1002
1016
 
1003
1017
  def get_T_along_path(irpath, data, width, norm=True):
@@ -1,5 +1,5 @@
1
1
  """Parameters needed for the thermal analysis of the petal."""
2
-
2
+ import sys
3
3
 
4
4
  class IRPetalParam(object):
5
5
  """Default values for IR image handling."""
@@ -14,7 +14,7 @@ class IRPetalParam(object):
14
14
  """
15
15
  self.institute = None # Either IFIC or DESY to treat the different files
16
16
  self.thrs = -22.0 # the threshold
17
- self.tco2 = -35.0 # Inlet temperature
17
+ self.tco2 = -32.0 # Inlet temperature
18
18
  self.gauss_size = 15 # Radius of gausian filtering
19
19
  self.grad_sigma = 2.5 # Sigma of grading calculation
20
20
  self.distance = 5 # Distance in contour between slices
@@ -26,8 +26,13 @@ class IRPetalParam(object):
26
26
  self.do_fit = True # True to fit the segment points.
27
27
  self.rotate = True # Rotate to have a vertical petal in mirror image
28
28
  self.debug = False # To debug
29
- self.report = False #
29
+ self.report = False #
30
30
  self.graphana = None # Graphana server
31
+ self.save_pipes = False # If true save pipe path
32
+ self.legend = True # if false do not plot legend
33
+ self.sensor_min = -sys.float_info.max # cut on lower temp for sensor contour average
34
+ self.sensor_max = sys.float_info.max # cut on higher temp for sensor contour average
35
+
31
36
 
32
37
  if values is not None:
33
38
  self.set_values(values)
@@ -75,3 +80,7 @@ class IRPetalParam(object):
75
80
  help="Value to smooth contour")
76
81
  parser.add_argument("--debug", action="store_true", default=False, help="Show additional information.")
77
82
  parser.add_argument("--report", action="store_true", default=False, help="True if figures kept for the report.")
83
+ parser.add_argument("--save_pipes", default=False, action="store_true", help="SAve pipe path. Output is alias_pipe-i.txt")
84
+ parser.add_argument("--no-legend", dest="legend", action="store_false", default=True, help="Do not show the legend in plots.")
85
+ parser.add_argument("--sensor_min", dest="sensor_min", default=P.sensor_min, help="cut on lower temp for sensor contour average")
86
+ parser.add_argument("--sensor_max", dest="sensor_max", default=P.sensor_max, help="cut on higher temp for sensor contour average")
@@ -88,7 +88,7 @@ def analyze_IR_image(img, pipe, sensors, iside, params) -> AnalysisResult:
88
88
  Savg = []
89
89
  Sstd = []
90
90
  for S in sensors:
91
- avg, std = contours.get_average_in_contour(img, S)
91
+ avg, std = contours.get_average_in_contour(img, S, tmin=params.sensor_min, tmax=params.sensor_max)
92
92
  Savg.append(avg)
93
93
  Sstd.append(std)
94
94
 
@@ -44,6 +44,8 @@ class PipeFit(object):
44
44
 
45
45
  self.core_center = None
46
46
  self.core_band = None
47
+ self.sample_indx = None
48
+ self.curv = []
47
49
 
48
50
  def set_front(self, is_front=True):
49
51
  """Sets for a front image.
@@ -357,7 +359,8 @@ class PipeFit(object):
357
359
  dst, P = contours.find_closest_point(X[0], X[1], self.cpipe)
358
360
  D[i, :] = P - X
359
361
  ddd[i] = dst
360
- W = 1 + 1.6*(y_max - X[1])/height
362
+ W = 1 + 2*(y_max - X[1])/height
363
+ #W = self.curv[i]
361
364
  weights[i] = W
362
365
  sum_weights += W
363
366
 
@@ -369,7 +372,7 @@ class PipeFit(object):
369
372
  res = np.dot(ddd, weights)/sum_weights
370
373
 
371
374
  if self.debug:
372
- dbg_ax. clear()
375
+ dbg_ax.clear()
373
376
  dbg_ax.plot(self.cpipe[:, 0], self.cpipe[:, 1])
374
377
  dbg_ax.plot(out[:, 0], out[:, 1])
375
378
  dbg_ax.set_title("area {:3f} dist {:.3f}".format(real_area, np.sum(ddd)/npts))
@@ -420,7 +423,7 @@ class PipeFit(object):
420
423
 
421
424
  return res
422
425
 
423
- def fit(self, data, M0=None, factor=1.0):
426
+ def fit(self, data, M0=None, factor=1.0, simplify=True):
424
427
  """Do the fit.
425
428
 
426
429
  Args:
@@ -451,12 +454,19 @@ class PipeFit(object):
451
454
  print("\n** Initial guess")
452
455
  self.print_transform(M)
453
456
  else:
454
- M = M0
457
+ M = M0[0:5]
455
458
 
456
459
  self.core_center, self.core_band = self.get_data_center(data)
457
460
 
458
-
459
- self.data = contours.contour_simplify(data, 1.25*factor)
461
+ if simplify:
462
+ self.sample_indx = contours.contour_simplify(data, 1.25, return_mask=True)
463
+ self.data = np.array(data[self.sample_indx, :])
464
+
465
+ else:
466
+ self.data = np.array(data)
467
+
468
+ self.curv = contours.contour_curvature(self.data)
469
+
460
470
  # self.data = data
461
471
  verbose = 0
462
472
  if self.debug:
@@ -482,9 +492,9 @@ class PipeFit(object):
482
492
  self.R = M
483
493
  return M
484
494
 
485
- def fit_ex(self, data, limit=5e6, factor=1):
495
+ def fit_ex(self, data, M0=None, limit=5e6, factor=1, simplify=True):
486
496
  """Does the regular fit and tries to correct."""
487
- R = self.fit(data, factor=factor)
497
+ R = self.fit(data, factor=factor, M0=M0, simplify=simplify)
488
498
 
489
499
  # Check for an offset...
490
500
  if self.res.cost > limit:
@@ -535,9 +545,13 @@ class PipeFit(object):
535
545
 
536
546
  return X, Y
537
547
 
538
- def plot(self, show_fig=True):
548
+ def plot(self, show_fig=True, ax=None):
539
549
  """Plot result of fit and data."""
540
- fig, ax = plt.subplots(1, 1, tight_layout=True)
550
+ if ax is None:
551
+ fig, ax = plt.subplots(1, 1, tight_layout=True)
552
+ else:
553
+ fig = ax.get_figure()
554
+
541
555
  ax.plot(self.pipe[:, 0], self.pipe[:, 1], label="Pipe")
542
556
  if self.R is not None:
543
557
  out = self.transform_data(self.data, self.R)
@@ -0,0 +1,94 @@
1
+ import sys
2
+ from pathlib import Path
3
+ import numpy as np
4
+ import matplotlib.pyplot as plt
5
+
6
+
7
+
8
+ cwd = Path(__file__).parent
9
+ if cwd.exists():
10
+ sys.path.insert(0, cwd.as_posix())
11
+
12
+ from petal_qc.thermal import PipeFit, contours, DebugPlot
13
+
14
+ # create a global instance of DebugPlot
15
+ debug_plot = DebugPlot.DebugPlot()
16
+
17
+
18
+ class PipeIterFit:
19
+ """makes an iterative fit removing outliers in each iteration."""
20
+
21
+ def __init__(self, data):
22
+ """Initialize class."""
23
+ self.data = data
24
+ ptype = PipeFit.PipeFit.guess_pipe_type(data)
25
+ self.PF = PipeFit.PipeFit(ptype)
26
+
27
+ def remove_outsiders(self, data, thrs):
28
+ """Removes points which are further than thrs from the fit."""
29
+ D = np.zeros(len(data))
30
+ out = self.PF.transform_data(data, self.PF.R)
31
+ i = 0
32
+ for x, y in out:
33
+ dst, P = contours.find_closest_point(x, y, self.PF.pipe)
34
+ D[i] = dst
35
+ i += 1
36
+
37
+ indx = np.where(D < thrs)[0]
38
+ return np.array(data[indx, :])
39
+
40
+ def fit(self, threshold=20, factor=1.0, debug=False):
41
+ """Do the fit."""
42
+ global debug_plot
43
+ if debug:
44
+ debug_plot.setup_debug('IterFit')
45
+
46
+
47
+ total_data = self.data
48
+ data_size = len(total_data)
49
+
50
+ M0 = self.PF.fit_ex(total_data, factor=factor, simplify=True)
51
+ sample_data = self.remove_outsiders(self.PF.data, threshold)
52
+ last_size = len(sample_data)
53
+ if debug:
54
+ ax = debug_plot.get_ax("IterFit")
55
+ ax.clear()
56
+ ax.plot(total_data[:, 0], total_data[:, 1], 'o')
57
+ ax.plot(sample_data[:, 0], sample_data[:, 1], 'P')
58
+ plt.draw()
59
+ plt.pause(0.0001)
60
+
61
+ # Adaptively determining the number of iterations
62
+ while True:
63
+ M0 = self.PF.fit_ex(sample_data, M0=M0, factor=factor, simplify=False)
64
+
65
+ out = self.PF.transform_data(self.PF.data, M0)
66
+ D = []
67
+ for x, y in out:
68
+ dst, P = contours.find_closest_point(x, y, self.PF.pipe)
69
+ D.append(dst)
70
+
71
+ if debug:
72
+ ax = debug_plot.get_ax("IterFit")
73
+ ax.clear()
74
+ ax.hist(D)
75
+ plt.draw()
76
+ plt.pause(0.0001)
77
+ self.PF.plot(ax=ax)
78
+
79
+ sample_data = self.remove_outsiders(self.PF.data, 20)
80
+ sample_size = len(sample_data)
81
+ if sample_size == last_size:
82
+ break
83
+
84
+ last_size = sample_size
85
+
86
+ if debug:
87
+ self.PF.plot(ax=ax)
88
+
89
+ return M0
90
+
91
+ def plot(self):
92
+ """Plot this fit."""
93
+ global debug_plot
94
+ self.PF.plot(ax=debug_plot.get_ax("IterFit"))
@@ -8,6 +8,8 @@ import random
8
8
  import sys
9
9
 
10
10
  import matplotlib.path as mplPath
11
+ import matplotlib.pyplot as plt
12
+
11
13
  import numpy as np
12
14
 
13
15
  from petal_qc.utils.Geometry import Point, remove_outliers_indx
@@ -258,8 +260,46 @@ def in_contour(x, y, C):
258
260
  path = mplPath.Path(C)
259
261
  return path.contains_point(x, y, C)
260
262
 
263
+ def show_contour_values(img, C):
264
+ """Creates an image of the contour values.
265
+
266
+ Args:
267
+ ----
268
+ img: The image
269
+ C: The contour
261
270
 
262
- def get_average_in_contour(img, C, remove_outliers=None):
271
+
272
+ """
273
+ values = []
274
+ path = mplPath.Path(C)
275
+ my, mx = img.shape
276
+ min_x, min_y, max_x, max_y = contour_bounds(C)
277
+ imin_x = int(min_x)
278
+ imin_y = int(min_y)
279
+ chst = np.zeros([int(max_y-min_y+2), int(max_x-min_x+2)])
280
+ for ix in range(int(min_x), int(max_x+1)):
281
+ if ix < 0 or ix >= mx:
282
+ continue
283
+
284
+ for iy in range(int(min_y), int(max_y)+1):
285
+ if iy < 0 or iy >= my:
286
+ continue
287
+
288
+ if path.contains_point([ix, iy]):
289
+ chst[iy-imin_y, ix-imin_x] = img[iy, ix]
290
+ values.append(img[iy, ix])
291
+
292
+ values = np.array(values)
293
+ mean = np.mean(values)
294
+ std = np.std(values)
295
+
296
+ fig, ax = plt.subplots(nrows=1,ncols=2)
297
+ ax[0].hist(values, 25, range=(mean-3*std, mean+3*std))
298
+ pcm = ax[1].imshow(chst, origin='lower', vmin=mean-3*std, vmax=mean+3*std)
299
+ fig.colorbar(pcm, ax=ax[1])
300
+
301
+
302
+ def get_average_in_contour(img, C, tmin=sys.float_info.min, tmax=sys.float_info.max, remove_outliers=None):
263
303
  """Gets average and std of points within contour.
264
304
 
265
305
  We are assuming here that coordinates are integers, ie,
@@ -269,7 +309,8 @@ def get_average_in_contour(img, C, remove_outliers=None):
269
309
  ----
270
310
  img: The image
271
311
  C: The contour
272
- remove_outliers: If an int,
312
+ tmin, tmax: only values in range [tmin, tmax] will be considered
313
+ remove_outliers: If an int,
273
314
 
274
315
  Returns
275
316
  -------
@@ -289,7 +330,9 @@ def get_average_in_contour(img, C, remove_outliers=None):
289
330
  continue
290
331
 
291
332
  if path.contains_point([ix, iy]):
292
- values.append(img[iy, ix])
333
+ val = img[iy, ix]
334
+ if val > tmin and val < tmax:
335
+ values.append(val)
293
336
 
294
337
  values = np.array(values)
295
338
  if remove_outliers is not None and isinstance(remove_outliers, (int, float)):
@@ -365,6 +408,42 @@ def contour_eval(C, x):
365
408
  return value
366
409
 
367
410
 
411
+
412
+ def contour_curvature(contour, stride=1):
413
+ """Computes contour curvature at each point.
414
+
415
+ Args:
416
+ contour: Input contour
417
+ stride (int, optional): stride to compute curvature. Defaults to 1.
418
+
419
+ Returns:
420
+ curvature: aray of curvatures.
421
+
422
+ """
423
+ curvature = []
424
+ assert stride < len(contour), "stride must be shorther than length of contour"
425
+
426
+ npts = len(contour)
427
+ for i in range(npts):
428
+ before = i - stride + npts if i - stride < 0 else i - stride
429
+ after = i + stride - npts if i + stride >= npts else i + stride
430
+
431
+ f1x, f1y = (contour[after] - contour[before]) / stride
432
+ f2x, f2y = (contour[after] - 2 * contour[i] + contour[before]) / stride**2
433
+ denominator = (f1x**2 + f1y**2) ** 3 + 1e-11
434
+
435
+ curvature_at_i = (
436
+ np.sqrt(4 * (f2y * f1x - f2x * f1y) ** 2 / denominator)
437
+ if denominator > 1e-12
438
+ else -1
439
+ )
440
+
441
+ curvature.append(curvature_at_i)
442
+
443
+
444
+ return curvature
445
+
446
+
368
447
  if __name__ == "__main__":
369
448
  cntr = np.loadtxt("long_contour.csv")
370
449
 
@@ -258,7 +258,7 @@ class CoreThermal(itkdb_gtk.dbGtkUtils.ITkDBWindow):
258
258
  if upper:
259
259
  self.dataBox.remove(self.btnData)
260
260
  self.dataBox.add(self.desyData)
261
-
261
+
262
262
  self.param.distance = 16
263
263
  self.param.width = 16
264
264
 
@@ -268,7 +268,7 @@ class CoreThermal(itkdb_gtk.dbGtkUtils.ITkDBWindow):
268
268
  if upper:
269
269
  self.dataBox.remove(self.desyData)
270
270
  self.dataBox.add(self.btnData)
271
-
271
+
272
272
  self.param.distance = 5
273
273
  self.param.width = 2
274
274