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.
- petal_qc/PetalReceptionTests.py +23 -5
- petal_qc/__init__.py +6 -1
- petal_qc/getPetalCoreTestSummary.py +69 -9
- petal_qc/metrology/PetalMetrology.py +23 -11
- petal_qc/metrology/analyze_locking_points.py +14 -3
- petal_qc/metrology/compare_Cores.py +15 -2
- petal_qc/metrology/convert_mitutoyo.py +5 -0
- petal_qc/metrology/coreMetrology.py +16 -26
- petal_qc/metrology/do_Metrology.py +1 -1
- petal_qc/metrology/petal_flatness.py +10 -9
- petal_qc/metrology/readAVSdata.py +11 -4
- petal_qc/metrology/test_paralelism.py +2 -2
- petal_qc/metrology/uploadPetalInformation.py +35 -3
- petal_qc/readTemplateTable.py +313 -0
- petal_qc/test/analyzeMetrologyTable.py +158 -29
- petal_qc/test/checkPipeShipments.py +60 -0
- petal_qc/test/getAVStests.py +2 -2
- petal_qc/test/reportFromJSon.py +35 -6
- petal_qc/test/testMitutoyo.py +10 -0
- petal_qc/test/thermalReportFromJSon.py +99 -0
- petal_qc/thermal/IRDataGetter.py +2 -0
- petal_qc/thermal/IRPetal.py +20 -6
- petal_qc/thermal/IRPetalParam.py +12 -3
- petal_qc/thermal/Petal_IR_Analysis.py +1 -1
- petal_qc/thermal/PipeFit.py +24 -10
- petal_qc/thermal/PipeIterFit.py +94 -0
- petal_qc/thermal/contours.py +82 -3
- petal_qc/thermal/coreThermal.py +2 -2
- petal_qc/thermal/create_IRCore.py +11 -6
- petal_qc/thermal/create_core_report.py +0 -3
- petal_qc/uploadXrays.py +86 -0
- {petal_qc-0.0.17.dist-info → petal_qc-0.0.24.dist-info}/METADATA +2 -3
- {petal_qc-0.0.17.dist-info → petal_qc-0.0.24.dist-info}/RECORD +36 -30
- {petal_qc-0.0.17.dist-info → petal_qc-0.0.24.dist-info}/WHEEL +1 -1
- {petal_qc-0.0.17.dist-info → petal_qc-0.0.24.dist-info}/entry_points.txt +1 -0
- {petal_qc-0.0.17.dist-info → petal_qc-0.0.24.dist-info}/top_level.txt +0 -0
petal_qc/test/reportFromJSon.py
CHANGED
|
@@ -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
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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(
|
|
77
|
+
for fnam in all_files(folder, "*.json"):
|
|
49
78
|
opts.files.append(fnam)
|
|
50
79
|
|
|
51
80
|
main(opts)
|
|
@@ -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
|
+
|
petal_qc/thermal/IRDataGetter.py
CHANGED
|
@@ -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.
|
petal_qc/thermal/IRPetal.py
CHANGED
|
@@ -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=
|
|
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
|
-
|
|
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
|
-
|
|
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 >
|
|
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
|
-
|
|
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):
|
petal_qc/thermal/IRPetalParam.py
CHANGED
|
@@ -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 = -
|
|
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
|
|
petal_qc/thermal/PipeFit.py
CHANGED
|
@@ -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 +
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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"))
|
petal_qc/thermal/contours.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
petal_qc/thermal/coreThermal.py
CHANGED
|
@@ -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
|
|