petal-qc 0.0.21__py3-none-any.whl → 0.0.22__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of petal-qc might be problematic. Click here for more details.
- petal_qc/__init__.py +6 -1
- petal_qc/getPetalCoreTestSummary.py +20 -12
- petal_qc/metrology/PetalMetrology.py +21 -9
- petal_qc/metrology/analyze_locking_points.py +14 -3
- petal_qc/metrology/compare_Cores.py +15 -2
- petal_qc/metrology/coreMetrology.py +16 -26
- petal_qc/metrology/test_paralelism.py +2 -2
- petal_qc/metrology/uploadPetalInformation.py +20 -2
- petal_qc/readTemplateTable.py +313 -0
- petal_qc/test/analyzeMetrologyTable.py +158 -29
- petal_qc/test/reportFromJSon.py +2 -2
- petal_qc/test/testMitutoyo.py +10 -0
- petal_qc/thermal/IRPetal.py +20 -6
- petal_qc/thermal/PipeFit.py +11 -8
- petal_qc/thermal/PipeIterFit.py +74 -0
- petal_qc/thermal/create_IRCore.py +8 -3
- petal_qc/uploadXrays.py +86 -0
- {petal_qc-0.0.21.dist-info → petal_qc-0.0.22.dist-info}/METADATA +1 -1
- {petal_qc-0.0.21.dist-info → petal_qc-0.0.22.dist-info}/RECORD +22 -18
- {petal_qc-0.0.21.dist-info → petal_qc-0.0.22.dist-info}/WHEEL +1 -1
- {petal_qc-0.0.21.dist-info → petal_qc-0.0.22.dist-info}/entry_points.txt +1 -0
- {petal_qc-0.0.21.dist-info → petal_qc-0.0.22.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Read Python table with reception tests."""
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
import math
|
|
6
|
+
import argparse
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from itkdb_gtk import ITkDBlogin, ITkDButils
|
|
12
|
+
|
|
13
|
+
def check_value(value):
|
|
14
|
+
"""Check that value is valid."""
|
|
15
|
+
if value is None:
|
|
16
|
+
return False
|
|
17
|
+
|
|
18
|
+
if isinstance(value, float):
|
|
19
|
+
if math.isnan(value):
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
return True
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def string_to_boolean(s):
|
|
26
|
+
"""Converts a string to a boolean."""
|
|
27
|
+
val = s.lower()
|
|
28
|
+
if val=="pass" or val=="true" or val=="1":
|
|
29
|
+
return True
|
|
30
|
+
elif val=="fail" or val=="false" or val=="0":
|
|
31
|
+
return False
|
|
32
|
+
else:
|
|
33
|
+
raise ValueError
|
|
34
|
+
|
|
35
|
+
def parse_value(value):
|
|
36
|
+
"""Parse value in cell."""
|
|
37
|
+
try:
|
|
38
|
+
spassed, comment = value.split(",")
|
|
39
|
+
except ValueError:
|
|
40
|
+
spassed = value
|
|
41
|
+
comment = ""
|
|
42
|
+
|
|
43
|
+
passed = string_to_boolean(spassed)
|
|
44
|
+
if passed is None:
|
|
45
|
+
return None, None
|
|
46
|
+
|
|
47
|
+
return passed, comment
|
|
48
|
+
|
|
49
|
+
def create_dto(session, SN, test_name):
|
|
50
|
+
"""REturns the test DTO."""
|
|
51
|
+
user = ITkDButils.get_db_user(session)
|
|
52
|
+
defaults = {
|
|
53
|
+
"component": SN,
|
|
54
|
+
"institution": user["institutions"][0]["code"],
|
|
55
|
+
"runNumber": "1",
|
|
56
|
+
}
|
|
57
|
+
dto = ITkDButils.get_test_skeleton(session, "CORE_PETAL", test_name, defaults)
|
|
58
|
+
return dto
|
|
59
|
+
|
|
60
|
+
def do_visual_inspection(session, core, value):
|
|
61
|
+
"""Uploads visual inspection."""
|
|
62
|
+
if not check_value(value):
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
SN = core["serialNumber"]
|
|
66
|
+
dto = create_dto(session, SN, "VISUAL_INSPECTION")
|
|
67
|
+
|
|
68
|
+
passed, comment = parse_value(value)
|
|
69
|
+
|
|
70
|
+
if passed is None:
|
|
71
|
+
print("Wrong value fpr PASS/FAIL {} in {} for {}".format(value, "VISUAL_INSPECTION", core["alternativeIdentifier"]) )
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
if passed:
|
|
75
|
+
dto["passed"] = True
|
|
76
|
+
else:
|
|
77
|
+
dto["passed"] = False
|
|
78
|
+
dto["defects"].append({
|
|
79
|
+
"name": "VISUAL",
|
|
80
|
+
"description": comment,
|
|
81
|
+
"properties": {}
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
return dto
|
|
85
|
+
|
|
86
|
+
def do_grounding(session, core, value):
|
|
87
|
+
"""Uploads grounding check"""
|
|
88
|
+
if not check_value(value):
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
SN = core["serialNumber"]
|
|
92
|
+
dto = create_dto(session, SN, "GROUNDING_CHECK")
|
|
93
|
+
|
|
94
|
+
fb, pipes, pipe_gnd = [float(x) for x in value.split(',')]
|
|
95
|
+
if fb > 2.0:
|
|
96
|
+
dto["passed"] = False
|
|
97
|
+
dto["defects"].append({
|
|
98
|
+
"name": "GROUND_FB",
|
|
99
|
+
"description": "resistance front-back is {} > 2 Ohm".format(fb),
|
|
100
|
+
"properties": {}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
if pipes>0 and pipes<20.0e6:
|
|
104
|
+
dto["passed"] = False
|
|
105
|
+
dto["defects"].append({
|
|
106
|
+
"name": "GROUND_PIPES",
|
|
107
|
+
"description": "resistance between pipes is {} < 20 MOhm".format(pipes),
|
|
108
|
+
"properties": {}
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
if pipe_gnd>0 and pipe_gnd<20.0e6:
|
|
112
|
+
dto["passed"] = False
|
|
113
|
+
dto["defects"].append({
|
|
114
|
+
"name": "GROUND_PIPE_GND",
|
|
115
|
+
"description": "resistance between pipes and GNDis {} < 20 MOhm".format(pipe_gnd),
|
|
116
|
+
"properties": {}
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
dto["results"]["RESISTANCE_FB"] = fb
|
|
120
|
+
dto["results"]["RESISTANCE_PIPES"] = pipes
|
|
121
|
+
dto["results"]["RESISTANCE_PIPE_GND"] = pipe_gnd
|
|
122
|
+
|
|
123
|
+
return dto
|
|
124
|
+
|
|
125
|
+
def do_bending(session, core, value):
|
|
126
|
+
"""Uploads bending."""
|
|
127
|
+
if not check_value(value):
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
SN = core["serialNumber"]
|
|
131
|
+
dto = create_dto(session, SN, "BENDING120")
|
|
132
|
+
passed, comment = parse_value(value)
|
|
133
|
+
if passed is None:
|
|
134
|
+
print("Wrong value fpr PASS/FAIL {} in {} for {}".format(value, "BENDING120", core["alternativeIdentifier"]) )
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
if passed:
|
|
138
|
+
dto["passed"] = True
|
|
139
|
+
else:
|
|
140
|
+
dto["passed"] = False
|
|
141
|
+
dto["defects"].append({
|
|
142
|
+
"name": "BENDING120",
|
|
143
|
+
"description": comment,
|
|
144
|
+
"properties": {}
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
return dto
|
|
148
|
+
|
|
149
|
+
def do_weight(session, core, value):
|
|
150
|
+
"""Uploads weight."""
|
|
151
|
+
if not check_value(value):
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
SN = core["serialNumber"]
|
|
155
|
+
dto = create_dto(session, SN, "PETAL_CORE_WEIGHT")
|
|
156
|
+
|
|
157
|
+
weight = float(value)
|
|
158
|
+
dto["results"]["WEIGHT"] = weight
|
|
159
|
+
if abs(weight-250)>25:
|
|
160
|
+
dto["passed"] = False
|
|
161
|
+
dto["defects"].append({
|
|
162
|
+
"name": "WEIGHT",
|
|
163
|
+
"description": "Petal core wights {:.1f} more than 25 gr. beyond 250.".format(weight),
|
|
164
|
+
"properties": {}
|
|
165
|
+
})
|
|
166
|
+
else:
|
|
167
|
+
dto["passed"] = True
|
|
168
|
+
|
|
169
|
+
return dto
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def do_thickness(session, core, value):
|
|
173
|
+
"""Uploads thickness."""
|
|
174
|
+
if not check_value(value):
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
SN = core["serialNumber"]
|
|
178
|
+
dto = create_dto(session, SN, "CORE_THICKNESS")
|
|
179
|
+
thickness = float(value)
|
|
180
|
+
dto["results"]["THICKNESS"] = thickness
|
|
181
|
+
|
|
182
|
+
dto["passed"] = True
|
|
183
|
+
if abs(thickness-5.9)>0.25:
|
|
184
|
+
dto["problems"] = True
|
|
185
|
+
dto["comments"].append("Petal core wights {:.1f} more than 25 gr. beyond 250.".format(thickness))
|
|
186
|
+
|
|
187
|
+
return dto
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def do_metrology_template(session, core, value):
|
|
191
|
+
"""Uploads metrology template."""
|
|
192
|
+
if not check_value(value):
|
|
193
|
+
return None
|
|
194
|
+
|
|
195
|
+
SN = core["serialNumber"]
|
|
196
|
+
dto = create_dto(session, SN, "METROLOGY_TEMPLATE")
|
|
197
|
+
passed, comment = parse_value(value)
|
|
198
|
+
if passed is None:
|
|
199
|
+
print("Wrong value fpr PASS/FAIL {} in {} for {}".format(value, "TEMPLATE", core["alternativeIdentifier"]) )
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
if passed:
|
|
203
|
+
dto["passed"] = True
|
|
204
|
+
else:
|
|
205
|
+
dto["passed"] = False
|
|
206
|
+
dto["defects"].append({
|
|
207
|
+
"name": "TEMPLATE",
|
|
208
|
+
"description": comment,
|
|
209
|
+
"properties": {}
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
return dto
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def do_delamination(session, core, value):
|
|
216
|
+
"""Uploads delamination."""
|
|
217
|
+
if not check_value(value):
|
|
218
|
+
return None
|
|
219
|
+
|
|
220
|
+
SN = core["serialNumber"]
|
|
221
|
+
dto = create_dto(session, SN, "DELAMINATION")
|
|
222
|
+
|
|
223
|
+
passed, comment = parse_value(value)
|
|
224
|
+
if passed is None:
|
|
225
|
+
print("Wrong value fpr PASS/FAIL {} in {} for {}".format(value, "DELAMINATION", core["alternativeIdentifier"]) )
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
if passed:
|
|
229
|
+
dto["passed"] = True
|
|
230
|
+
else:
|
|
231
|
+
dto["passed"] = False
|
|
232
|
+
dto["defects"].append({
|
|
233
|
+
"name": "DELAMINATION",
|
|
234
|
+
"description": comment,
|
|
235
|
+
"properties": {}
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
return dto
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def readTemplateTable(session, options):
|
|
242
|
+
"""Main entry.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
session (itkdb.Session): The PDB client.
|
|
246
|
+
options: program options
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
core_tests = {
|
|
250
|
+
"VISUAL_INSPECTION": do_visual_inspection,
|
|
251
|
+
"GROUNDING_CHECK": do_grounding,
|
|
252
|
+
"BENDING120": do_bending,
|
|
253
|
+
"PETAL_CORE_WEIGHT": do_weight,
|
|
254
|
+
"CORE_THICKNESS": do_thickness,
|
|
255
|
+
"METROLOGY_TEMPLATE": do_metrology_template,
|
|
256
|
+
"DELAMINATION": do_delamination
|
|
257
|
+
}
|
|
258
|
+
try:
|
|
259
|
+
sheet = int(options.sheet)
|
|
260
|
+
except ValueError:
|
|
261
|
+
sheet = options.sheet
|
|
262
|
+
|
|
263
|
+
df = pd.read_excel(options.files[0], sheet_name=sheet)
|
|
264
|
+
#print(df)
|
|
265
|
+
|
|
266
|
+
for row in df.itertuples():
|
|
267
|
+
petal_id = row.CORE_ID
|
|
268
|
+
if not check_value(petal_id):
|
|
269
|
+
break
|
|
270
|
+
|
|
271
|
+
print("\n\n### {}".format(petal_id))
|
|
272
|
+
core = ITkDButils.get_DB_component(session, petal_id)
|
|
273
|
+
for test, func in core_tests.items():
|
|
274
|
+
data = func(session, core, getattr(row, test))
|
|
275
|
+
if data is None:
|
|
276
|
+
continue
|
|
277
|
+
|
|
278
|
+
data["properties"]["OPERATOR"] = options.operator
|
|
279
|
+
|
|
280
|
+
#print(json.dumps(data, indent=3))
|
|
281
|
+
rc = ITkDButils.upload_test(session, data, check_runNumber=True)
|
|
282
|
+
if rc:
|
|
283
|
+
print("\n*** Could not upload test {} for {}".format(test, petal_id))
|
|
284
|
+
print(rc)
|
|
285
|
+
print()
|
|
286
|
+
|
|
287
|
+
def main():
|
|
288
|
+
"""Entry point."""
|
|
289
|
+
parser = argparse.ArgumentParser()
|
|
290
|
+
parser.add_argument("files", nargs='*', help="The template spreadsheet")
|
|
291
|
+
parser.add_argument("--sheet", default="0", help="Sheet to read from excel file.")
|
|
292
|
+
parser.add_argument("--operator", default="Oihan Elesgaray", help="Name of operator.")
|
|
293
|
+
|
|
294
|
+
args = parser.parse_args()
|
|
295
|
+
if len(args.files) == 0:
|
|
296
|
+
print("I need an input file")
|
|
297
|
+
sys.exit()
|
|
298
|
+
|
|
299
|
+
if not Path(args.files[0]).exists():
|
|
300
|
+
print("Input file does not exist.")
|
|
301
|
+
sys.exit()
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
# ITk_PB authentication
|
|
305
|
+
dlg = ITkDBlogin.ITkDBlogin()
|
|
306
|
+
pdb_session = dlg.get_client()
|
|
307
|
+
|
|
308
|
+
readTemplateTable(pdb_session, args)
|
|
309
|
+
|
|
310
|
+
dlg.die()
|
|
311
|
+
|
|
312
|
+
if __name__ == "__main__":
|
|
313
|
+
main()
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Analyze the table generated by createMetrologyTable.
|
|
2
|
+
"""Analyze the table generated by createMetrologyTable.
|
|
3
|
+
|
|
4
|
+
It should also work with the table created by analyzeMetrologyTable, which is
|
|
5
|
+
the new default.
|
|
6
|
+
"""
|
|
3
7
|
|
|
4
8
|
import sys
|
|
5
9
|
from pathlib import Path
|
|
@@ -13,7 +17,22 @@ from lmfit.models import LinearModel
|
|
|
13
17
|
|
|
14
18
|
from petal_qc.utils.fit_utils import draw_best_fit
|
|
15
19
|
|
|
20
|
+
fig_width = 12.0
|
|
21
|
+
fig_height = 1.2*fig_width/3.0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
16
25
|
def distance(P1, P2, P3):
|
|
26
|
+
"""Distance of P3 to line defined by P1-P2.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
P1: Point 1 of the line
|
|
30
|
+
P2: Point 2 of the line
|
|
31
|
+
P3: Actual point
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
float: distance
|
|
35
|
+
"""
|
|
17
36
|
C = np.cross(P2-P1, P3-P1)
|
|
18
37
|
D = C/norm(P2-P1)
|
|
19
38
|
return D
|
|
@@ -38,14 +57,41 @@ def remove_outliers_indx(data, cut=2.0, debug=False):
|
|
|
38
57
|
indx = np.where(s < cut)[0]
|
|
39
58
|
return indx
|
|
40
59
|
|
|
60
|
+
def draw_delta_scatter(ax, x, y, title, x_lim=None, y_lim=None, radius=None):
|
|
41
61
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
fig_width = 12.0
|
|
45
|
-
fig_height = 1.2*fig_width/3.0
|
|
62
|
+
if x_lim is None:
|
|
63
|
+
x_lim=(-150, 150)
|
|
46
64
|
|
|
47
|
-
|
|
65
|
+
if y_lim is None:
|
|
66
|
+
y_lim=(-150, 150)
|
|
67
|
+
|
|
68
|
+
if radius is None:
|
|
69
|
+
radius = (25, 100)
|
|
70
|
+
|
|
71
|
+
ax.set_title(title)
|
|
72
|
+
ax.set_aspect('equal', adjustable='box')
|
|
73
|
+
ax.set_xlim(x_lim)
|
|
74
|
+
ax.set_ylim(y_lim)
|
|
75
|
+
circle = plt.Circle((0,0), radius[1], color="red", alpha=0.25)
|
|
76
|
+
ax.add_patch(circle)
|
|
77
|
+
circle = plt.Circle((0,0), radius[0], color="green", alpha=0.25)
|
|
78
|
+
ax.add_patch(circle)
|
|
48
79
|
|
|
80
|
+
ax.set_xlabel("X (µm)")
|
|
81
|
+
ax.set_ylabel("Y (µm)")
|
|
82
|
+
ax.grid()
|
|
83
|
+
|
|
84
|
+
ax.scatter(x, y, marker='.')
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def analyze_fiducials(options, T):
|
|
88
|
+
"""Analyze relative position of locator fiducials.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
options (_type_): Program options
|
|
92
|
+
T (DataFrame): Pandas data frame.
|
|
93
|
+
"""
|
|
94
|
+
side = T["side"].values[0]
|
|
49
95
|
if options.mould > 0:
|
|
50
96
|
x = 1000*T.loc[T['mould'] == options.mould, 'fd_dx'].values
|
|
51
97
|
y = 1000*T.loc[T['mould'] == options.mould, 'fd_dy'].values
|
|
@@ -54,22 +100,23 @@ def main(options):
|
|
|
54
100
|
x = 1000*T['fd_dx'].values
|
|
55
101
|
y = 1000*T['fd_dy'].values
|
|
56
102
|
|
|
57
|
-
fig, ax = plt.subplots(nrows=1, ncols=
|
|
58
|
-
fig.suptitle("Relative Position FD01-FD02")
|
|
59
|
-
ax[0]
|
|
60
|
-
ax[0].
|
|
61
|
-
ax[0].
|
|
62
|
-
ax[0].
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
ax[0].
|
|
70
|
-
ax[0].
|
|
71
|
-
|
|
72
|
-
|
|
103
|
+
fig, ax = plt.subplots(nrows=1, ncols=2, tight_layout=True, figsize=(fig_width, fig_height))
|
|
104
|
+
fig.suptitle("Relative Position FD01-FD02 [{}]".format(side))
|
|
105
|
+
draw_delta_scatter(ax[0], x, y, "FD01-FD02")
|
|
106
|
+
# ax[0].set_title("FD01-FD02")
|
|
107
|
+
# ax[0].set_aspect('equal', adjustable='box')
|
|
108
|
+
# ax[0].set_xlim(-150, 150)
|
|
109
|
+
# ax[0].set_ylim(-150, 150)
|
|
110
|
+
# circle = plt.Circle((0,0), 100, color="red", alpha=0.25)
|
|
111
|
+
# ax[0].add_patch(circle)
|
|
112
|
+
# circle = plt.Circle((0,0), 25, color="green", alpha=0.25)
|
|
113
|
+
# ax[0].add_patch(circle)
|
|
114
|
+
#
|
|
115
|
+
# ax[0].set_xlabel("X (µm)")
|
|
116
|
+
# ax[0].set_ylabel("Y (µm)")
|
|
117
|
+
# ax[0].grid()
|
|
118
|
+
#
|
|
119
|
+
# ax[0].scatter(x, y, marker='.')
|
|
73
120
|
|
|
74
121
|
model = LinearModel()
|
|
75
122
|
params = model.guess(y, x=x)
|
|
@@ -94,15 +141,94 @@ def main(options):
|
|
|
94
141
|
angle = 180*math.atan( result.best_values['slope'])/math.pi
|
|
95
142
|
print("angle {:.5f} deg.".format(angle))
|
|
96
143
|
|
|
97
|
-
ax[1].set_xlim(-
|
|
98
|
-
ax[1].set_xlabel("
|
|
144
|
+
ax[1].set_xlim(-50, 50)
|
|
145
|
+
ax[1].set_xlabel("distance to line (µm)")
|
|
99
146
|
ax[1].grid()
|
|
100
|
-
ax[1].hist(
|
|
147
|
+
ax[1].hist(values)
|
|
148
|
+
|
|
149
|
+
# ax[2].set_xlim(-75, 75)
|
|
150
|
+
# ax[2].set_xlabel("delta_X (µm)")
|
|
151
|
+
# ax[2].grid()
|
|
152
|
+
# ax[2].hist(x)
|
|
153
|
+
|
|
154
|
+
# ax[3].set_xlim(-150, 150)
|
|
155
|
+
# ax[3].set_xlabel("delta_Y (µm)")
|
|
156
|
+
# ax[3].grid()
|
|
157
|
+
# ax[3].hist(y)
|
|
158
|
+
|
|
159
|
+
def analyze_delta_pos(options, T):
|
|
160
|
+
""" Study dependency of delta pos in PL01-FD01 with the other locators.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
options (_type_): Program options
|
|
164
|
+
T (DataFrame): Pandas data frame.
|
|
165
|
+
"""
|
|
166
|
+
side = T["side"].values[0]
|
|
167
|
+
x = T["pl01_dx"].values
|
|
168
|
+
y = T["pl01_dy"].values
|
|
169
|
+
delta_pl01 = 1000*np.column_stack((x, y))
|
|
170
|
+
norm = np.array([ np.linalg.norm(P) for P in delta_pl01])
|
|
171
|
+
indx = np.where(norm<75.0)
|
|
172
|
+
|
|
173
|
+
delta_pl02 = 1000*np.column_stack((T["pl02_dx"].values, T["pl02_dy"].values))
|
|
174
|
+
delta_pl03 = 1000*np.column_stack((T["pl03_dx"].values, T["pl03_dy"].values))
|
|
175
|
+
|
|
176
|
+
fig, ax = plt.subplots(nrows=1, ncols=2, tight_layout=True, figsize=(fig_width, fig_height))
|
|
177
|
+
fig.suptitle("Delta from PL01 [{}]".format(side))
|
|
178
|
+
draw_delta_scatter(ax[0], delta_pl02[indx, 0], delta_pl02[indx, 1], "PL02")
|
|
179
|
+
draw_delta_scatter(ax[1], delta_pl03[indx, 0], delta_pl03[indx, 1], "PL02")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def analyze_parallelism(options, T):
|
|
183
|
+
"""Analyze parallelism.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
options (_type_): Program options
|
|
187
|
+
T (DataFrame): Pandas data frame.
|
|
188
|
+
"""
|
|
189
|
+
if options.institute is None:
|
|
190
|
+
df = T
|
|
191
|
+
else:
|
|
192
|
+
df = T.loc[T["institute"] == options.institute]
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
side = df["side"].values[0]
|
|
196
|
+
labels = []
|
|
197
|
+
for L in df["petal_id"].values:
|
|
198
|
+
ival = int(L[5:])
|
|
199
|
+
labels.append(ival)
|
|
200
|
+
|
|
201
|
+
x = 1000*df["flatness"].values
|
|
202
|
+
y = 1000*df["parallelism"].values
|
|
203
|
+
|
|
204
|
+
fig, ax = plt.subplots(nrows=1, ncols=1, tight_layout=True)
|
|
205
|
+
fig.suptitle("Parallelism .vs. flatness [{}]".format(side))
|
|
206
|
+
|
|
207
|
+
ax.scatter(x, y, marker='.')
|
|
208
|
+
for xp, yp, lbl in zip(x, y, labels):
|
|
209
|
+
ax.text(xp, yp, "{}".format(lbl))
|
|
210
|
+
|
|
211
|
+
ax.set_xlabel("flatness (µm)")
|
|
212
|
+
ax.set_ylabel("parallelism (µm)")
|
|
213
|
+
ax.grid()
|
|
214
|
+
|
|
215
|
+
fig, ax = plt.subplots(nrows=1, ncols=1, tight_layout=True)
|
|
216
|
+
fig.suptitle("Offset [{}]".format(side))
|
|
217
|
+
ax.hist(1000*T["offset"].values)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def main(options):
|
|
221
|
+
"""main entry."""
|
|
222
|
+
T = pd.read_csv(options.files[0])
|
|
223
|
+
|
|
224
|
+
# analyze fiducials
|
|
225
|
+
analyze_fiducials(options, T)
|
|
226
|
+
|
|
227
|
+
# Understand deviation from nominal.
|
|
228
|
+
analyze_delta_pos(options, T)
|
|
101
229
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
ax[2].grid()
|
|
105
|
-
ax[2].hist(y)
|
|
230
|
+
# Correlate planarity y parallelism
|
|
231
|
+
analyze_parallelism(options, T)
|
|
106
232
|
|
|
107
233
|
plt.show()
|
|
108
234
|
|
|
@@ -110,6 +236,9 @@ if __name__ == "__main__":
|
|
|
110
236
|
parser = argparse.ArgumentParser()
|
|
111
237
|
parser.add_argument('files', nargs='*', help="Input files")
|
|
112
238
|
parser.add_argument('--mould', default=-1, type=int, help="mould index")
|
|
239
|
+
parser.add_argument("--institute",
|
|
240
|
+
default=None,
|
|
241
|
+
help="Either IFIC or DESY to treat the different files")
|
|
113
242
|
|
|
114
243
|
opts = parser.parse_args()
|
|
115
244
|
if len(opts.files) == 0:
|
petal_qc/test/reportFromJSon.py
CHANGED
|
@@ -71,8 +71,8 @@ if __name__ == "__main__":
|
|
|
71
71
|
opts = parser.parse_args()
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
folder = Path("/tmp/petal-metrology/results")
|
|
75
|
-
|
|
74
|
+
#folder = Path("/tmp/petal-metrology/results")
|
|
75
|
+
folder = Path("~/tmp/petal-metrology/Production/Results").expanduser()
|
|
76
76
|
opts.files = []
|
|
77
77
|
for fnam in all_files(folder, "*.json"):
|
|
78
78
|
opts.files.append(fnam)
|
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):
|