itkdb-gtk 0.0.3__py3-none-any.whl → 0.20.1__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.
- itkdb_gtk/{sendShipments.py → CreateShipments.py} +74 -78
- itkdb_gtk/{getShipments.py → GetShipments.py} +99 -106
- itkdb_gtk/GlueWeight.py +45 -66
- itkdb_gtk/ITkDB.desktop +8 -0
- itkdb_gtk/ITkDB.svg +380 -0
- itkdb_gtk/ITkDBlogin.py +10 -6
- itkdb_gtk/ITkDButils.py +295 -57
- itkdb_gtk/PanelVisualInspection.py +590 -0
- itkdb_gtk/QRScanner.py +120 -0
- itkdb_gtk/SensorUtils.py +492 -0
- itkdb_gtk/ShowAttachments.py +267 -0
- itkdb_gtk/ShowComments.py +94 -0
- itkdb_gtk/ShowDefects.py +103 -0
- itkdb_gtk/UploadModuleIV.py +566 -0
- itkdb_gtk/UploadMultipleTests.py +746 -0
- itkdb_gtk/UploadTest.py +509 -0
- itkdb_gtk/VisualInspection.py +297 -0
- itkdb_gtk/WireBondGui.py +1304 -0
- itkdb_gtk/__init__.py +38 -12
- itkdb_gtk/dashBoard.py +292 -33
- itkdb_gtk/dbGtkUtils.py +356 -75
- itkdb_gtk/findComponent.py +242 -0
- itkdb_gtk/findVTRx.py +36 -0
- itkdb_gtk/readGoogleSheet.py +1 -2
- itkdb_gtk/untrash_component.py +35 -0
- {itkdb_gtk-0.0.3.dist-info → itkdb_gtk-0.20.1.dist-info}/METADATA +21 -12
- itkdb_gtk-0.20.1.dist-info/RECORD +30 -0
- {itkdb_gtk-0.0.3.dist-info → itkdb_gtk-0.20.1.dist-info}/WHEEL +1 -1
- itkdb_gtk-0.20.1.dist-info/entry_points.txt +12 -0
- itkdb_gtk/checkComponent.py +0 -131
- itkdb_gtk/groundingTest.py +0 -225
- itkdb_gtk/readAVSdata.py +0 -565
- itkdb_gtk/uploadPetalInformation.py +0 -604
- itkdb_gtk/uploadTest.py +0 -384
- itkdb_gtk-0.0.3.dist-info/RECORD +0 -19
- itkdb_gtk-0.0.3.dist-info/entry_points.txt +0 -7
- {itkdb_gtk-0.0.3.dist-info → itkdb_gtk-0.20.1.dist-info}/top_level.txt +0 -0
itkdb_gtk/WireBondGui.py
ADDED
|
@@ -0,0 +1,1304 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Wirebonding GUI for PSB."""
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
import re
|
|
6
|
+
import json
|
|
7
|
+
import copy
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from collections import namedtuple
|
|
10
|
+
import gi
|
|
11
|
+
|
|
12
|
+
gi.require_version("Gtk", "3.0")
|
|
13
|
+
from gi.repository import Gtk, Gio, Gdk
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
import itkdb_gtk
|
|
17
|
+
|
|
18
|
+
except ImportError:
|
|
19
|
+
cwd = Path(__file__).parent.parent
|
|
20
|
+
sys.path.append(cwd.as_posix())
|
|
21
|
+
import itkdb_gtk
|
|
22
|
+
|
|
23
|
+
__HELP_LINK__="https://itkdb-gtk.docs.cern.ch/wirebondTest.html"
|
|
24
|
+
|
|
25
|
+
from itkdb_gtk import dbGtkUtils
|
|
26
|
+
from itkdb_gtk import ITkDBlogin, ITkDButils, UploadTest
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
#valid_channel = re.compile("(^[0-9]+)-([0-9]+)")
|
|
30
|
+
valid_channel = re.compile("^[0-9]+[\\s*\\,\\s,-[0-9]+]*")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
test_parameters = {
|
|
34
|
+
"Repaired Row 1": "REPAIRED_FRONTEND_ROW1",
|
|
35
|
+
"Failed Row 1": "FAILED_FRONTEND_ROW1",
|
|
36
|
+
"Repaired Row 2": "REPAIRED_FRONTEND_ROW2",
|
|
37
|
+
"Failed Row 2": "FAILED_FRONTEND_ROW2",
|
|
38
|
+
"Repaired Row 3": "REPAIRED_FRONTEND_ROW3",
|
|
39
|
+
"Failed Row 3": "FAILED_FRONTEND_ROW3",
|
|
40
|
+
"Repaired Row 4": "REPAIRED_FRONTEND_ROW4",
|
|
41
|
+
"Failed Row 4": "FAILED_FRONTEND_ROW4",
|
|
42
|
+
"Repaired Hyb->PB": "REPAIRED_HYBRID_TO_PB",
|
|
43
|
+
"Failed HyB->PB": "FAILED_HYBRID_TO_PB",
|
|
44
|
+
"Repaired Module->Frame": "REPAIRED_MODULE_TO_FRAME",
|
|
45
|
+
"Failed Module->Frame": "FAILED_MODULE_TO_FRAME"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# module_param[iring][i_sensor][i_hybrid][i_row]
|
|
49
|
+
module_param = {
|
|
50
|
+
0: [
|
|
51
|
+
[
|
|
52
|
+
[[255, 830], [1343, 1918], [2431, 3006], [3519, 4094]],
|
|
53
|
+
[[831, 1342], [1919, 2430], [3007, 3518], [4095, 4606]],
|
|
54
|
+
]
|
|
55
|
+
],
|
|
56
|
+
1: [
|
|
57
|
+
[
|
|
58
|
+
[[271, 974], [1615, 2318], [2959, 3662], [4303, 5006]],
|
|
59
|
+
[[975, 1614], [2319, 2958], [3663, 4302], [5007, 5646]],
|
|
60
|
+
]
|
|
61
|
+
],
|
|
62
|
+
2: [
|
|
63
|
+
[
|
|
64
|
+
[[201, 968], [969, 1736], [1737, 2504], [2505, 3272]]
|
|
65
|
+
]
|
|
66
|
+
],
|
|
67
|
+
3: [
|
|
68
|
+
[
|
|
69
|
+
[[566, 1013], [2358, 2805], [4150, 4597], [5942, 6389]],
|
|
70
|
+
[[1462, 1909], [3254, 3701], [5046, 5493], [6838, 7285]]
|
|
71
|
+
],
|
|
72
|
+
[
|
|
73
|
+
[[1014, 1461], [2806, 3253], [4598, 5045], [6390, 6837]],
|
|
74
|
+
[[1910, 2357], [3702, 4149], [5494, 5941], [7286, 7733]]
|
|
75
|
+
]
|
|
76
|
+
],
|
|
77
|
+
4: [
|
|
78
|
+
[
|
|
79
|
+
[[318, 829], [1342, 1853], [2366, 2877], [3390, 3901]]
|
|
80
|
+
],
|
|
81
|
+
[
|
|
82
|
+
[[830, 1341], [1854, 2365], [2878, 3389], [3902, 4413]]
|
|
83
|
+
]
|
|
84
|
+
],
|
|
85
|
+
5: [
|
|
86
|
+
[
|
|
87
|
+
[[332, 907], [1484, 2059], [2636, 3211], [3788, 4363]]
|
|
88
|
+
],
|
|
89
|
+
[
|
|
90
|
+
[[908, 1483], [2060, 2635], [3212, 3787], [4364, 4939]]
|
|
91
|
+
]
|
|
92
|
+
],
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module_to_tf = {
|
|
96
|
+
0: [],
|
|
97
|
+
1: [],
|
|
98
|
+
2: [],
|
|
99
|
+
3: [],
|
|
100
|
+
4: [],
|
|
101
|
+
5: []
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
hyb_to_pb = {
|
|
105
|
+
0: [],
|
|
106
|
+
1: [],
|
|
107
|
+
2: [],
|
|
108
|
+
3: [],
|
|
109
|
+
4: [],
|
|
110
|
+
5: []
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def range_to_list(V):
|
|
115
|
+
"""Convert a range to a list.
|
|
116
|
+
|
|
117
|
+
ch1-ch2 or ch1:ch2 -> [ch1, ch1+1, ch1+2, ..., ch2] or
|
|
118
|
+
ch1:step:ch2 -> [ch1, ch1+sep, ch1+2*step, ..., ch2]
|
|
119
|
+
"""
|
|
120
|
+
nfound = 0
|
|
121
|
+
for c in "-:,":
|
|
122
|
+
if c in V:
|
|
123
|
+
nfound += 1
|
|
124
|
+
break
|
|
125
|
+
|
|
126
|
+
if nfound == 0:
|
|
127
|
+
return [V]
|
|
128
|
+
|
|
129
|
+
out = []
|
|
130
|
+
values = V.split(',')
|
|
131
|
+
for V in values:
|
|
132
|
+
if '-' in V:
|
|
133
|
+
endpoints = list(map(int, V.split('-')))
|
|
134
|
+
endpoints.sort()
|
|
135
|
+
for i in range(endpoints[0], endpoints[1]+1):
|
|
136
|
+
out.append(str(i))
|
|
137
|
+
elif ':' in V:
|
|
138
|
+
endpoints = list(map(int, V.split(':')))
|
|
139
|
+
if len(endpoints) == 2:
|
|
140
|
+
endpoints.sort()
|
|
141
|
+
for i in range(endpoints[0], endpoints[1]+1):
|
|
142
|
+
out.append(str(i))
|
|
143
|
+
|
|
144
|
+
elif len(endpoints) == 3:
|
|
145
|
+
for i in range(endpoints[0], endpoints[2]+1, endpoints[1]):
|
|
146
|
+
out.append(str(i))
|
|
147
|
+
|
|
148
|
+
else:
|
|
149
|
+
print("Wring range specification. {}".format(V))
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
else:
|
|
153
|
+
out.append(V)
|
|
154
|
+
|
|
155
|
+
return out
|
|
156
|
+
|
|
157
|
+
def count_items(items):
|
|
158
|
+
"""Count number of channels from results."""
|
|
159
|
+
nitems = 0
|
|
160
|
+
for key in items.keys():
|
|
161
|
+
nitems += len(range_to_list(key))
|
|
162
|
+
|
|
163
|
+
return nitems
|
|
164
|
+
|
|
165
|
+
def find_holes(chan_list, min_chan=0, max_chan=999999):
|
|
166
|
+
"""Find groups of consecutive channels."""
|
|
167
|
+
out = sorted(chan_list)
|
|
168
|
+
nchan = 0
|
|
169
|
+
last_chan = -1
|
|
170
|
+
ichan = -1
|
|
171
|
+
holes = []
|
|
172
|
+
for chan in out:
|
|
173
|
+
if chan < min_chan or chan > max_chan:
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
if last_chan < 0:
|
|
177
|
+
last_chan = chan
|
|
178
|
+
continue
|
|
179
|
+
|
|
180
|
+
if chan - last_chan > 1:
|
|
181
|
+
if nchan:
|
|
182
|
+
holes.append([ichan, nchan])
|
|
183
|
+
nchan = 0
|
|
184
|
+
ichan = -1
|
|
185
|
+
else:
|
|
186
|
+
if last_chan < max_chan and chan >= max_chan:
|
|
187
|
+
# WE are in another sensor
|
|
188
|
+
holes.append([ichan, nchan])
|
|
189
|
+
nchan = 0
|
|
190
|
+
ichan = -1
|
|
191
|
+
last_chan = chan
|
|
192
|
+
continue
|
|
193
|
+
|
|
194
|
+
nchan += 1
|
|
195
|
+
if ichan < 0:
|
|
196
|
+
ichan = last_chan
|
|
197
|
+
nchan += 1
|
|
198
|
+
|
|
199
|
+
last_chan = chan
|
|
200
|
+
|
|
201
|
+
if nchan:
|
|
202
|
+
holes.append([ichan, nchan])
|
|
203
|
+
|
|
204
|
+
return holes
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def wire2strip(iwire, irow, first_chan):
|
|
208
|
+
"""From bond to strip number."""
|
|
209
|
+
if irow % 2:
|
|
210
|
+
istrip = 2*(iwire-first_chan) + 1
|
|
211
|
+
else:
|
|
212
|
+
istrip = 2*(iwire-first_chan)
|
|
213
|
+
|
|
214
|
+
return istrip
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class Hole:
|
|
218
|
+
"""A range of consecutive unconnected channels."""
|
|
219
|
+
def __init__(self, *args):
|
|
220
|
+
for i, name in enumerate(['sensor', 'hybrid', 'row', 'chan', 'width']):
|
|
221
|
+
setattr(self, name, args[i])
|
|
222
|
+
|
|
223
|
+
def __repr__(self):
|
|
224
|
+
return "sensor: {} hyb: {} row: {} chan: {} width: {}".format(
|
|
225
|
+
self.sensor, self.hybrid, self.row, self.chan, self.width
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
class HybridHoles:
|
|
229
|
+
"""Holes in hybrid bonds.
|
|
230
|
+
|
|
231
|
+
Holes are defined by a list [first_chan, n_chan].
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
def __init__(self, param, win=None, hid=0):
|
|
235
|
+
"""Initialization.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
param: Hybrid wirebon parameters.
|
|
239
|
+
|
|
240
|
+
"""
|
|
241
|
+
self.win = win
|
|
242
|
+
self.id = hid
|
|
243
|
+
self.param = param
|
|
244
|
+
self.nchan = 0
|
|
245
|
+
for min_chan, max_chan in param:
|
|
246
|
+
self.nchan += (max_chan-min_chan+1)
|
|
247
|
+
|
|
248
|
+
self.holes = [[] for irow in range(4)]
|
|
249
|
+
self.channels = [[] for irow in range(4)]
|
|
250
|
+
# Sensor strips for each of the strip rows "served" by a hybrid.
|
|
251
|
+
self.sensor_channels = [[], []]
|
|
252
|
+
self.sensor_holes = [[], []]
|
|
253
|
+
|
|
254
|
+
def ready(self):
|
|
255
|
+
"""Call when all channels are in."""
|
|
256
|
+
for irow, C in enumerate(self.channels):
|
|
257
|
+
C.sort()
|
|
258
|
+
self.holes[irow] = find_holes(C)
|
|
259
|
+
|
|
260
|
+
for irow, S in enumerate(self.sensor_channels):
|
|
261
|
+
S.sort()
|
|
262
|
+
self.sensor_holes[irow] = find_holes(S)
|
|
263
|
+
|
|
264
|
+
def add_channel(self, irow, ichan)->bool:
|
|
265
|
+
"""Add a new channel in row.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
irow: row number
|
|
269
|
+
ichan: channel number
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
True if added, False otherwise.
|
|
273
|
+
|
|
274
|
+
"""
|
|
275
|
+
first_chan = self.param[irow][0]
|
|
276
|
+
last_chan = self.param[irow][1]
|
|
277
|
+
strip_row = int(irow/2)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
if isinstance(ichan, list) or isinstance(ichan, tuple):
|
|
281
|
+
nadded = 0
|
|
282
|
+
for ich in ichan:
|
|
283
|
+
if first_chan <= ich <= last_chan:
|
|
284
|
+
self.channels[irow].append(ich)
|
|
285
|
+
nadded += 1
|
|
286
|
+
|
|
287
|
+
self.channels[irow] = sorted(self.channels[irow])
|
|
288
|
+
for iwire in self.channels[irow]:
|
|
289
|
+
istrip = wire2strip(iwire, irow, first_chan)
|
|
290
|
+
self.sensor_channels[strip_row].append(istrip)
|
|
291
|
+
|
|
292
|
+
return nadded>0
|
|
293
|
+
|
|
294
|
+
if first_chan <= ichan <= last_chan:
|
|
295
|
+
self.channels[irow].append(ichan)
|
|
296
|
+
istrip = wire2strip(ichan, irow, first_chan)
|
|
297
|
+
self.sensor_channels[strip_row].append(istrip)
|
|
298
|
+
return True
|
|
299
|
+
|
|
300
|
+
return False
|
|
301
|
+
|
|
302
|
+
def get_n_unconnected(self) -> list:
|
|
303
|
+
"""Count number of unconnected channels.
|
|
304
|
+
|
|
305
|
+
Return a list, one item per row.
|
|
306
|
+
"""
|
|
307
|
+
nchan = [len(C) for C in self.channels]
|
|
308
|
+
return nchan
|
|
309
|
+
|
|
310
|
+
def get_max_consecutive_from_list(self, holes):
|
|
311
|
+
"""Return max widht of holes."""
|
|
312
|
+
mx_width = []
|
|
313
|
+
lst_holes = []
|
|
314
|
+
for irow, row in enumerate(holes):
|
|
315
|
+
if len(row) == 0:
|
|
316
|
+
mxW = 0
|
|
317
|
+
|
|
318
|
+
else:
|
|
319
|
+
mxW = -1
|
|
320
|
+
for h in row:
|
|
321
|
+
lst_holes.append(Hole(0, self.id, irow, h[0], h[1]))
|
|
322
|
+
mxW = max(mxW, h[1])
|
|
323
|
+
|
|
324
|
+
mx_width.append(mxW)
|
|
325
|
+
|
|
326
|
+
return mx_width, lst_holes
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def get_max_consecutive(self):
|
|
330
|
+
"""Returns the largest 'hole'."""
|
|
331
|
+
mx_width, _ = self.get_max_consecutive_from_list(self.holes)
|
|
332
|
+
return mx_width
|
|
333
|
+
|
|
334
|
+
def get_max_sensor_consecutive(self):
|
|
335
|
+
"""Return largest hole in sensor."""
|
|
336
|
+
mx_width, holes = self.get_max_consecutive_from_list(self.sensor_holes)
|
|
337
|
+
return mx_width, holes
|
|
338
|
+
|
|
339
|
+
class SensorHoles:
|
|
340
|
+
"""Holes in sensor."""
|
|
341
|
+
|
|
342
|
+
def __init__(self, param, win=None, sid=0):
|
|
343
|
+
"""Initialization.
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
param: sensor wirebon params
|
|
347
|
+
"""
|
|
348
|
+
self.win = win
|
|
349
|
+
self.id = sid
|
|
350
|
+
self.param = param
|
|
351
|
+
self.nchan = 0
|
|
352
|
+
self.nhybrid = len(param)
|
|
353
|
+
self.hybrids = []
|
|
354
|
+
for i, P in enumerate(param):
|
|
355
|
+
H = HybridHoles(P, hid=i, win=win)
|
|
356
|
+
self.hybrids.append(H)
|
|
357
|
+
self.nchan += H.nchan
|
|
358
|
+
|
|
359
|
+
def ready(self):
|
|
360
|
+
"""Call when all channels are in."""
|
|
361
|
+
for H in self.hybrids:
|
|
362
|
+
H.ready()
|
|
363
|
+
|
|
364
|
+
def get_n_hyb(self):
|
|
365
|
+
"""Return number of hybrids."""
|
|
366
|
+
return len(self.hybrids)
|
|
367
|
+
|
|
368
|
+
def get_hybrid(self, i):
|
|
369
|
+
"""Return i-th hybrid."""
|
|
370
|
+
return self.hybrids[i]
|
|
371
|
+
|
|
372
|
+
def get_max_consecutive(self):
|
|
373
|
+
"""Max number of consecutive unconnected channels.
|
|
374
|
+
|
|
375
|
+
This is ordered by row.
|
|
376
|
+
"""
|
|
377
|
+
mx_width = [0 for x in range(4)]
|
|
378
|
+
for hyb in self.hybrids:
|
|
379
|
+
mxW = hyb.get_max_consecutive()
|
|
380
|
+
for j, val in enumerate(mxW):
|
|
381
|
+
mx_width[j] = max(mx_width[j] , val)
|
|
382
|
+
|
|
383
|
+
return mx_width
|
|
384
|
+
|
|
385
|
+
def get_max_sensor_consecutive(self):
|
|
386
|
+
"""MAx widht of holes in sensor."""
|
|
387
|
+
mx_width = -1
|
|
388
|
+
holes = []
|
|
389
|
+
for hyb in self.hybrids:
|
|
390
|
+
mxW, hyb_holes = hyb.get_max_sensor_consecutive()
|
|
391
|
+
for H in hyb_holes:
|
|
392
|
+
H.sensor = self.id
|
|
393
|
+
|
|
394
|
+
holes.extend(hyb_holes)
|
|
395
|
+
for v in mxW:
|
|
396
|
+
mx_width = max(mx_width, v)
|
|
397
|
+
|
|
398
|
+
return mx_width, holes
|
|
399
|
+
|
|
400
|
+
def get_n_unconnected(self):
|
|
401
|
+
"""Count number of unconnected channels.
|
|
402
|
+
|
|
403
|
+
Return a list, one item per row.
|
|
404
|
+
"""
|
|
405
|
+
nchan = [0, 0, 0, 0]
|
|
406
|
+
for hyb in self.hybrids:
|
|
407
|
+
nc = hyb.get_n_unconnected()
|
|
408
|
+
for i, n in enumerate(nc):
|
|
409
|
+
nchan[i] += n
|
|
410
|
+
|
|
411
|
+
return nchan
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
class ModuleHoles:
|
|
415
|
+
"""Holes in Modules."""
|
|
416
|
+
|
|
417
|
+
def __init__(self, param, win=None):
|
|
418
|
+
"""Initialization.
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
param: module wirebond params
|
|
422
|
+
"""
|
|
423
|
+
self.win = win
|
|
424
|
+
self.nsensor = len(param)
|
|
425
|
+
self.nchan = 0
|
|
426
|
+
self.sensors = []
|
|
427
|
+
for i, P in enumerate(param):
|
|
428
|
+
S = SensorHoles(P, sid=i, win=win)
|
|
429
|
+
self.sensors.append(S)
|
|
430
|
+
self.nchan += S.nchan
|
|
431
|
+
|
|
432
|
+
def ready(self):
|
|
433
|
+
"""Call when all channels are in."""
|
|
434
|
+
for S in self.sensors:
|
|
435
|
+
S.ready()
|
|
436
|
+
|
|
437
|
+
def get_max_consecutive(self):
|
|
438
|
+
"""Max number of consecutive unconnected channels.
|
|
439
|
+
|
|
440
|
+
This is ordered by row.
|
|
441
|
+
"""
|
|
442
|
+
mx_width = [-1, -1, -1, -1]
|
|
443
|
+
for S in self.sensors:
|
|
444
|
+
mxW = S.get_max_consecutive()
|
|
445
|
+
for j in range(4):
|
|
446
|
+
mx_width[j] = max(mx_width[j], mxW[j])
|
|
447
|
+
|
|
448
|
+
return mx_width
|
|
449
|
+
|
|
450
|
+
def get_max_sensor_consecutive(self):
|
|
451
|
+
"""The maximum number of consecutive channels per strip row."""
|
|
452
|
+
mx_width = -1
|
|
453
|
+
module_holes = []
|
|
454
|
+
for S in self.sensors:
|
|
455
|
+
width, holes = S.get_max_sensor_consecutive()
|
|
456
|
+
module_holes.extend(holes)
|
|
457
|
+
mx_width = max(mx_width, width)
|
|
458
|
+
|
|
459
|
+
return mx_width, module_holes
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def get_n_unconnected(self) -> list:
|
|
463
|
+
"""Count number of unconnected channels.
|
|
464
|
+
|
|
465
|
+
Return a list, one item per row.
|
|
466
|
+
"""
|
|
467
|
+
nchan = [0, 0, 0, 0]
|
|
468
|
+
for S in self.sensors:
|
|
469
|
+
nc = S.get_n_unconnected()
|
|
470
|
+
for i, n in enumerate(nc):
|
|
471
|
+
nchan[i] += n
|
|
472
|
+
|
|
473
|
+
return nchan
|
|
474
|
+
|
|
475
|
+
def get_total_unconnected(self) -> int:
|
|
476
|
+
"""Return total number of unconnected wires."""
|
|
477
|
+
unc = self.get_n_unconnected()
|
|
478
|
+
out = 0
|
|
479
|
+
for v in unc:
|
|
480
|
+
out += v
|
|
481
|
+
|
|
482
|
+
return int(out)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def get_module_param(SN):
|
|
486
|
+
"""Get parameters of module type.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
SN: Serial Number
|
|
490
|
+
|
|
491
|
+
Returns:
|
|
492
|
+
list: list with bond numbers.
|
|
493
|
+
"""
|
|
494
|
+
if len(SN) != 14 or SN[:3]!="20U":
|
|
495
|
+
raise ValueError("Wrong serial number {}".format(SN))
|
|
496
|
+
|
|
497
|
+
if SN[3:5] != "SE":
|
|
498
|
+
raise ValueError("Cannot handle barrel modules yet.")
|
|
499
|
+
|
|
500
|
+
mod_type = SN[5:7]
|
|
501
|
+
if mod_type[0] != "M":
|
|
502
|
+
raise ValueError("Does not seem to be a RingModule: {}".format(SN))
|
|
503
|
+
|
|
504
|
+
ring = int(mod_type[-1])
|
|
505
|
+
param = module_param[ring]
|
|
506
|
+
|
|
507
|
+
return param
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
class WireBond(dbGtkUtils.ITkDBWindow):
|
|
511
|
+
"""Main window."""
|
|
512
|
+
|
|
513
|
+
def __init__(self, session, title="", help_link=__HELP_LINK__):
|
|
514
|
+
"""Initialization."""
|
|
515
|
+
super().__init__(title=title, session=session, help_link=help_link)
|
|
516
|
+
self.pdb = None
|
|
517
|
+
self.models = {}
|
|
518
|
+
self.holes = {}
|
|
519
|
+
self.institute = self.pdb_user["institutions"][0]["code"]
|
|
520
|
+
self.inst_combo = None
|
|
521
|
+
self.module_SN = None
|
|
522
|
+
self.alternativeID = None
|
|
523
|
+
self.combo = None
|
|
524
|
+
self.tree = None
|
|
525
|
+
self.lut = {}
|
|
526
|
+
self.module_type = None
|
|
527
|
+
self.init_window()
|
|
528
|
+
|
|
529
|
+
def init_window(self):
|
|
530
|
+
"""Creates the GUI."""
|
|
531
|
+
self.hb.props.title = "Wire Bond"
|
|
532
|
+
|
|
533
|
+
# Button to upload
|
|
534
|
+
button = Gtk.Button()
|
|
535
|
+
icon = Gio.ThemedIcon(name="document-send-symbolic")
|
|
536
|
+
image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
|
|
537
|
+
button.add(image)
|
|
538
|
+
button.set_tooltip_text("Click to upload test")
|
|
539
|
+
button.connect("clicked", self.upload_test)
|
|
540
|
+
self.hb.pack_end(button)
|
|
541
|
+
|
|
542
|
+
button = Gtk.Button()
|
|
543
|
+
icon = Gio.ThemedIcon(name="document-save-symbolic")
|
|
544
|
+
image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
|
|
545
|
+
button.add(image)
|
|
546
|
+
button.set_tooltip_text("Click to save test file")
|
|
547
|
+
button.connect("clicked", self.save_test)
|
|
548
|
+
self.hb.pack_end(button)
|
|
549
|
+
|
|
550
|
+
# Button to upload
|
|
551
|
+
button = Gtk.Button()
|
|
552
|
+
icon = Gio.ThemedIcon(name="document-open-symbolic")
|
|
553
|
+
image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
|
|
554
|
+
button.add(image)
|
|
555
|
+
button.set_tooltip_text("Click to read local data file.")
|
|
556
|
+
button.connect("clicked", self.read_file)
|
|
557
|
+
self.hb.pack_end(button)
|
|
558
|
+
|
|
559
|
+
# Data panel
|
|
560
|
+
grid = Gtk.Grid(column_spacing=5, row_spacing=1)
|
|
561
|
+
|
|
562
|
+
# The shipment receiver
|
|
563
|
+
institute = self.create_institute_combo(True)
|
|
564
|
+
institute.connect("changed", self.on_institute)
|
|
565
|
+
institute.set_tooltip_text("Select the Institute.")
|
|
566
|
+
dbGtkUtils.set_combo_iter(institute, self.institute)
|
|
567
|
+
|
|
568
|
+
lbl = Gtk.Label(label="Institute")
|
|
569
|
+
lbl.set_xalign(0)
|
|
570
|
+
grid.attach(lbl, 0, 0, 1, 1)
|
|
571
|
+
grid.attach(institute, 1, 0, 1, 1)
|
|
572
|
+
self.inst_combo = institute
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
# The lookup table
|
|
577
|
+
lbl = Gtk.Label(label="Lookup Table")
|
|
578
|
+
lbl.set_xalign(0)
|
|
579
|
+
grid.attach(lbl, 2, 0, 1, 1)
|
|
580
|
+
|
|
581
|
+
self.testF = Gtk.FileChooserButton()
|
|
582
|
+
self.testF.set_tooltip_text("Click to select Lookup table.")
|
|
583
|
+
|
|
584
|
+
grid.attach(self.testF, 3, 0, 1, 1)
|
|
585
|
+
self.testF.connect("file-set", self.on_lut)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
for i, tit in enumerate(["Operator", "Bond Machine", "Wire Batch", "SN", "Date"]):
|
|
589
|
+
lbl = Gtk.Label(label=tit)
|
|
590
|
+
lbl.set_xalign(0)
|
|
591
|
+
grid.attach(lbl, 0, i+1, 1, 1)
|
|
592
|
+
|
|
593
|
+
self.operator = dbGtkUtils.new_small_text_entry()
|
|
594
|
+
self.machine = dbGtkUtils.new_small_text_entry()
|
|
595
|
+
self.batch = dbGtkUtils.new_small_text_entry()
|
|
596
|
+
#self.SN = dbGtkUtils.new_small_text_entry()
|
|
597
|
+
|
|
598
|
+
self.SN = itkdb_gtk.dbGtkUtils.TextEntry(small=True)
|
|
599
|
+
self.SN.connect("text-changed", self.on_SN_changed)
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
self.date = dbGtkUtils.TextEntry(small=True)
|
|
603
|
+
self.date.entry.set_text(ITkDButils.get_db_date())
|
|
604
|
+
self.date.connect("text_changed", self.new_date)
|
|
605
|
+
|
|
606
|
+
grid.attach(self.operator, 1, 1, 1, 1)
|
|
607
|
+
grid.attach(self.machine, 1, 2, 1, 1)
|
|
608
|
+
grid.attach(self.batch, 1, 3, 1, 1)
|
|
609
|
+
grid.attach(self.SN.entry, 1, 4, 1, 1)
|
|
610
|
+
grid.attach(self.date.widget, 1, 5, 1, 1)
|
|
611
|
+
|
|
612
|
+
self.mainBox.pack_start(grid, True, True, 0)
|
|
613
|
+
|
|
614
|
+
# Prepare combo and all the models
|
|
615
|
+
lbl = Gtk.Label(label="Choose section.")
|
|
616
|
+
lbl.set_xalign(0)
|
|
617
|
+
self.mainBox.pack_start(lbl, True, True, 0)
|
|
618
|
+
|
|
619
|
+
combo = self.create_combo()
|
|
620
|
+
self.mainBox.pack_start(combo, True, True, 0)
|
|
621
|
+
|
|
622
|
+
# The tree view
|
|
623
|
+
scrolled = self.create_tree_view()
|
|
624
|
+
self.mainBox.pack_start(scrolled, True, True, 5)
|
|
625
|
+
|
|
626
|
+
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
|
627
|
+
self.mainBox.pack_start(box, False, False, 0)
|
|
628
|
+
dbGtkUtils.add_button_to_container(box, "Add Bond",
|
|
629
|
+
"Click to add a new bond or bond range.",
|
|
630
|
+
self.add_item)
|
|
631
|
+
|
|
632
|
+
dbGtkUtils.add_button_to_container(box, "Remove Bond",
|
|
633
|
+
"Click to remove selected bond.",
|
|
634
|
+
self.remove_item)
|
|
635
|
+
|
|
636
|
+
#
|
|
637
|
+
# The text view and buffer
|
|
638
|
+
#
|
|
639
|
+
self.mainBox.pack_start(self.message_panel.frame, True, True, 0)
|
|
640
|
+
self.write_message("wirebond GUI\n")
|
|
641
|
+
|
|
642
|
+
def on_lut(self, fdlg):
|
|
643
|
+
"""Get look-up table."""
|
|
644
|
+
fnam = Path(fdlg.get_filename())
|
|
645
|
+
if not fnam.exists():
|
|
646
|
+
dbGtkUtils.complain("Cannot open Luukup Table.",
|
|
647
|
+
"File {} does not exist.".format(fnam))
|
|
648
|
+
return
|
|
649
|
+
|
|
650
|
+
lut = {}
|
|
651
|
+
module_map = {}
|
|
652
|
+
section = None
|
|
653
|
+
i_local = 0
|
|
654
|
+
i_std = 1
|
|
655
|
+
indx = ["local", "standard"]
|
|
656
|
+
found_format = False
|
|
657
|
+
with open(fnam, 'r', encoding="UTF-8") as fin:
|
|
658
|
+
for line in fin:
|
|
659
|
+
line = line.strip()
|
|
660
|
+
|
|
661
|
+
# Remove comments.
|
|
662
|
+
ipos = line.find('#')
|
|
663
|
+
jpos = line.find("#!")
|
|
664
|
+
if jpos>=0 and ipos==jpos:
|
|
665
|
+
if found_format:
|
|
666
|
+
dbGtkUtils.complain("A second format line was found.",
|
|
667
|
+
"Onely one is allowed. stopr map parsing.")
|
|
668
|
+
return
|
|
669
|
+
|
|
670
|
+
indx = [x.lower() for x in line[ipos+2:].split()]
|
|
671
|
+
try:
|
|
672
|
+
i_local = indx.index("local")
|
|
673
|
+
i_std = indx.index("standard")
|
|
674
|
+
found_format = True
|
|
675
|
+
except ValueError:
|
|
676
|
+
dbGtkUtils.complain("Wrong format desciption string.",
|
|
677
|
+
"The words 'local' and 'standard' should be there.\n{}".format(line))
|
|
678
|
+
return
|
|
679
|
+
|
|
680
|
+
continue
|
|
681
|
+
|
|
682
|
+
if ipos >= 0:
|
|
683
|
+
line = line[:ipos].strip()
|
|
684
|
+
|
|
685
|
+
if len(line) == 0:
|
|
686
|
+
continue
|
|
687
|
+
|
|
688
|
+
# Check for new section
|
|
689
|
+
ipos = line.find(':')
|
|
690
|
+
if ipos >= 0:
|
|
691
|
+
section = line[:ipos].strip().upper()
|
|
692
|
+
if section in module_map:
|
|
693
|
+
dbGtkUtils.complain("Section {} already in map.".format(section), "Stop parsing bond Lookup table.")
|
|
694
|
+
return
|
|
695
|
+
|
|
696
|
+
lut = {}
|
|
697
|
+
module_map[section] = lut
|
|
698
|
+
continue
|
|
699
|
+
|
|
700
|
+
if section is None:
|
|
701
|
+
continue
|
|
702
|
+
|
|
703
|
+
values = line.split()
|
|
704
|
+
if len(values)!=len(indx):
|
|
705
|
+
dbGtkUtils.complain("Cannot read Lookup table.", "Wrong line format: {}".format(line))
|
|
706
|
+
return
|
|
707
|
+
|
|
708
|
+
v_local = range_to_list(values[i_local])
|
|
709
|
+
v_std = range_to_list(values[i_std])
|
|
710
|
+
|
|
711
|
+
if len(v_local) != len(v_std):
|
|
712
|
+
dbGtkUtils.complain("Wrong Lookup table.",
|
|
713
|
+
"Ranges have different length: {}".format(line))
|
|
714
|
+
return
|
|
715
|
+
|
|
716
|
+
for L, S in zip(v_local, v_std):
|
|
717
|
+
lut[L] = S
|
|
718
|
+
|
|
719
|
+
self.lut = module_map
|
|
720
|
+
|
|
721
|
+
def convert_channel(self, C):
|
|
722
|
+
"""Convert channel according to LUT
|
|
723
|
+
|
|
724
|
+
Args:
|
|
725
|
+
C (str): channel number
|
|
726
|
+
|
|
727
|
+
"""
|
|
728
|
+
try:
|
|
729
|
+
return self.lut[self.module_type][C]
|
|
730
|
+
|
|
731
|
+
except KeyError:
|
|
732
|
+
return C
|
|
733
|
+
|
|
734
|
+
def on_institute(self, combo):
|
|
735
|
+
"""Institute changed."""
|
|
736
|
+
name = self.get_institute_from_combo(combo)
|
|
737
|
+
if name:
|
|
738
|
+
self.institute = name
|
|
739
|
+
|
|
740
|
+
def on_SN_changed(self, entry, value):
|
|
741
|
+
"""New SN given. Ask in PDB,"""
|
|
742
|
+
if len(value) <= 0:
|
|
743
|
+
return
|
|
744
|
+
|
|
745
|
+
obj = itkdb_gtk.ITkDButils.get_DB_component(self.session, value)
|
|
746
|
+
if obj is not None and obj["serialNumber"] is not None:
|
|
747
|
+
entry.set_text(obj["serialNumber"])
|
|
748
|
+
alternativeID = obj["alternativeIdentifier"]
|
|
749
|
+
module_SN = obj["serialNumber"]
|
|
750
|
+
if len(module_SN) == 14 and module_SN.startswith("20USEM"):
|
|
751
|
+
self.module_SN = module_SN
|
|
752
|
+
self.alternativeID = alternativeID
|
|
753
|
+
self.module_type = module_SN[5:7]
|
|
754
|
+
|
|
755
|
+
else:
|
|
756
|
+
itkdb_gtk.dbGtkUtils.complain("Invalid SN: {}".format(module_SN), "Not a Ring module")
|
|
757
|
+
|
|
758
|
+
else:
|
|
759
|
+
itkdb_gtk.dbGtkUtils.complain("Invalid SN", value)
|
|
760
|
+
|
|
761
|
+
def on_name_combo_changed(self, combo):
|
|
762
|
+
"""Change model in TreeView."""
|
|
763
|
+
tree_iter = combo.get_active_iter()
|
|
764
|
+
if tree_iter is not None:
|
|
765
|
+
model = combo.get_model()
|
|
766
|
+
param = model[tree_iter][1]
|
|
767
|
+
view_model = self.models[param]
|
|
768
|
+
self.tree.set_model(view_model)
|
|
769
|
+
else:
|
|
770
|
+
self.write_message("Cannot find model for {}\n".format(param))
|
|
771
|
+
|
|
772
|
+
def create_combo(self):
|
|
773
|
+
"""Create the combo."""
|
|
774
|
+
model = Gtk.ListStore(str, str)
|
|
775
|
+
for txt, param in test_parameters.items():
|
|
776
|
+
model.append([txt, param])
|
|
777
|
+
|
|
778
|
+
M = Gtk.ListStore(str, str)
|
|
779
|
+
M.append(["", ""])
|
|
780
|
+
self.models[param] = M
|
|
781
|
+
|
|
782
|
+
self.combo = Gtk.ComboBox.new_with_model_and_entry(model)
|
|
783
|
+
self.combo.set_entry_text_column(0)
|
|
784
|
+
self.combo.set_active(0)
|
|
785
|
+
self.combo.connect("changed", self.on_name_combo_changed)
|
|
786
|
+
return self.combo
|
|
787
|
+
|
|
788
|
+
def create_tree_view(self, size=150):
|
|
789
|
+
"""Creates the tree vew with the attachmens."""
|
|
790
|
+
tree_iter = self.combo.get_active_iter()
|
|
791
|
+
combo_model = self.combo.get_model()
|
|
792
|
+
param = combo_model[tree_iter][1]
|
|
793
|
+
model = self.models[param]
|
|
794
|
+
|
|
795
|
+
self.tree = Gtk.TreeView(model=model)
|
|
796
|
+
self.tree.connect("button-press-event", self.button_pressed)
|
|
797
|
+
|
|
798
|
+
scrolled = Gtk.ScrolledWindow()
|
|
799
|
+
scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
|
800
|
+
scrolled.add(self.tree)
|
|
801
|
+
scrolled.set_size_request(-1, size)
|
|
802
|
+
|
|
803
|
+
renderer = Gtk.CellRendererText()
|
|
804
|
+
renderer.set_property("editable", True)
|
|
805
|
+
renderer.connect("edited", self.channel_edited)
|
|
806
|
+
column = Gtk.TreeViewColumn("Channel", renderer, text=0)
|
|
807
|
+
self.tree.append_column(column)
|
|
808
|
+
|
|
809
|
+
renderer = Gtk.CellRendererText()
|
|
810
|
+
renderer.set_property("editable", True)
|
|
811
|
+
renderer.connect("edited", self.failure_edited)
|
|
812
|
+
column = Gtk.TreeViewColumn("Failure", renderer, text=1)
|
|
813
|
+
self.tree.append_column(column)
|
|
814
|
+
|
|
815
|
+
return scrolled
|
|
816
|
+
|
|
817
|
+
def text_edited(self, icol, path, text):
|
|
818
|
+
"""Text has been edited in the TreeView"""
|
|
819
|
+
if len(text) == 0:
|
|
820
|
+
return
|
|
821
|
+
|
|
822
|
+
model = self.tree.get_model()
|
|
823
|
+
|
|
824
|
+
n_child = model.iter_n_children()
|
|
825
|
+
current_child = int(path)
|
|
826
|
+
if n_child-current_child == 1:
|
|
827
|
+
model.append(["", ""])
|
|
828
|
+
|
|
829
|
+
model[path][icol] = text
|
|
830
|
+
|
|
831
|
+
def channel_edited(self, widget, path, text):
|
|
832
|
+
"""Handles edition in channel number cell."""
|
|
833
|
+
if not text.isnumeric():
|
|
834
|
+
if valid_channel.match(text) is None:
|
|
835
|
+
dbGtkUtils.complain("Wrong channel number", "Invalid channel number: {}".format(text))
|
|
836
|
+
return
|
|
837
|
+
|
|
838
|
+
self.text_edited(0, path, text)
|
|
839
|
+
|
|
840
|
+
def failure_edited(self, widget, path, text):
|
|
841
|
+
"""Handles edition in comment."""
|
|
842
|
+
self.text_edited(1, path, text)
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
def new_date(self, entry, value):
|
|
846
|
+
"""new date given at input."""
|
|
847
|
+
d = dbGtkUtils.parse_date_as_string(value)
|
|
848
|
+
if d is not None:
|
|
849
|
+
self.date.set_text(d)
|
|
850
|
+
|
|
851
|
+
def button_pressed(self, tree, event):
|
|
852
|
+
"""Button pressed."""
|
|
853
|
+
# double click shows attachments
|
|
854
|
+
if event.button == 1 and event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS:
|
|
855
|
+
#self.write_message("This is a double click.\n")
|
|
856
|
+
return
|
|
857
|
+
|
|
858
|
+
if event.button != 3:
|
|
859
|
+
return
|
|
860
|
+
|
|
861
|
+
# Create popup menu
|
|
862
|
+
select = self.tree.get_selection()
|
|
863
|
+
model, lv_iter = select.get_selected()
|
|
864
|
+
values = None
|
|
865
|
+
if lv_iter:
|
|
866
|
+
values = model[lv_iter]
|
|
867
|
+
|
|
868
|
+
else:
|
|
869
|
+
P = tree.get_path_at_pos(event.x, event.y)
|
|
870
|
+
if P:
|
|
871
|
+
lv_iter = model.get_iter(P[0])
|
|
872
|
+
values = model[lv_iter]
|
|
873
|
+
|
|
874
|
+
if not values:
|
|
875
|
+
return
|
|
876
|
+
|
|
877
|
+
menu = Gtk.Menu()
|
|
878
|
+
|
|
879
|
+
item_show = Gtk.MenuItem(label="Delete")
|
|
880
|
+
item_show.connect("activate", self.on_delete_item, (model, lv_iter, values))
|
|
881
|
+
menu.append(item_show)
|
|
882
|
+
|
|
883
|
+
menu.show_all()
|
|
884
|
+
menu.popup_at_pointer(event)
|
|
885
|
+
|
|
886
|
+
def on_delete_item(self, item, data):
|
|
887
|
+
"""Delete bond in list view."""
|
|
888
|
+
model, lv_iter, _ = data
|
|
889
|
+
model.remove(lv_iter)
|
|
890
|
+
|
|
891
|
+
def remove_item(self, *args):
|
|
892
|
+
"""REmoves selected bond."""
|
|
893
|
+
select = self.tree.get_selection()
|
|
894
|
+
model, lv_iter = select.get_selected()
|
|
895
|
+
if lv_iter:
|
|
896
|
+
model.remove(lv_iter)
|
|
897
|
+
|
|
898
|
+
def add_item(self, *args):
|
|
899
|
+
"""Adds a new item in the current model."""
|
|
900
|
+
out = dbGtkUtils.get_a_list_of_values("Add Item", ["Channel", "Comment"])
|
|
901
|
+
if len(out) == 2:
|
|
902
|
+
model = self.tree.get_model()
|
|
903
|
+
model[-1] = out
|
|
904
|
+
model.append(["", ""])
|
|
905
|
+
|
|
906
|
+
def quit(self, *args):
|
|
907
|
+
"""Quits the application."""
|
|
908
|
+
if self.pdb:
|
|
909
|
+
self.pdb.die()
|
|
910
|
+
|
|
911
|
+
self.hide()
|
|
912
|
+
self.destroy()
|
|
913
|
+
|
|
914
|
+
def compute_repaired(self, skeleton):
|
|
915
|
+
"""Compute number of repaired."""
|
|
916
|
+
nrepaired = 0
|
|
917
|
+
for key, values in skeleton["results"].items():
|
|
918
|
+
if key.find("REPAIRED")<0 or key.find("ROW")<0:
|
|
919
|
+
continue
|
|
920
|
+
|
|
921
|
+
nrepaired += len(values)
|
|
922
|
+
|
|
923
|
+
if nrepaired>0:
|
|
924
|
+
skeleton["problems"] = True
|
|
925
|
+
skeleton["comments"].append("Number of repaired FE bonds: {}".format(nrepaired))
|
|
926
|
+
|
|
927
|
+
return nrepaired
|
|
928
|
+
|
|
929
|
+
def compute_hybrid_to_pb(self, skeleton):
|
|
930
|
+
"""Compute number of failures and repairs."""
|
|
931
|
+
n = count_items(skeleton["results"]["REPAIRED_HYBRID_TO_PB"])
|
|
932
|
+
n = count_items(skeleton["results"]["FAILED_HYBRID_TO_PB"])
|
|
933
|
+
if n:
|
|
934
|
+
msg = "Hybrid to PB: {} failing bonds.".format(n)
|
|
935
|
+
skeleton["comments"].append(msg)
|
|
936
|
+
skeleton["passed"] = False
|
|
937
|
+
self.write_message("{}\n".format(msg))
|
|
938
|
+
for key in skeleton["results"]["FAILED_HYBRID_TO_PB"]:
|
|
939
|
+
defect = {
|
|
940
|
+
"description": "Unbonded: Hybrid to PB",
|
|
941
|
+
"name": "UNBODED",
|
|
942
|
+
"properties": {
|
|
943
|
+
"wire_number": range_to_list(key)
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
skeleton["defects"].append(defect)
|
|
947
|
+
|
|
948
|
+
|
|
949
|
+
def compute_module_to_frame(self, skeleton):
|
|
950
|
+
"""Compute number of failures and repairs."""
|
|
951
|
+
n = count_items(skeleton["results"]["REPAIRED_MODULE_TO_FRAME"])
|
|
952
|
+
n = count_items(skeleton["results"]["FAILED_MODULE_TO_FRAME"])
|
|
953
|
+
if n:
|
|
954
|
+
msg = "Module to test frame: {} failing bonds.".format(n)
|
|
955
|
+
skeleton["comments"].append(msg)
|
|
956
|
+
skeleton["passed"] = False
|
|
957
|
+
self.write_message("{}\n".format(msg))
|
|
958
|
+
for key in skeleton["results"]["FAILED_MODULE_TO_FRAME"]:
|
|
959
|
+
defect = {
|
|
960
|
+
"description": "Unbonded: Module to Frame",
|
|
961
|
+
"name": "UNBODED",
|
|
962
|
+
"properties": {
|
|
963
|
+
"wire_number": range_to_list(key)
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
skeleton["defects"].append(defect)
|
|
967
|
+
|
|
968
|
+
def compute_unconnected(self, results):
|
|
969
|
+
"""Compute number of unconnected."""
|
|
970
|
+
try:
|
|
971
|
+
param = get_module_param(self.SN.get_text())
|
|
972
|
+
except ValueError as E:
|
|
973
|
+
dbGtkUtils.complain("Wrong SN number", str(E))
|
|
974
|
+
return None
|
|
975
|
+
|
|
976
|
+
M = ModuleHoles(param=param, win=self)
|
|
977
|
+
|
|
978
|
+
defects = []
|
|
979
|
+
for test, values in results.items():
|
|
980
|
+
if test.find("FAILED") < 0:
|
|
981
|
+
continue
|
|
982
|
+
if test.find("ROW") < 0:
|
|
983
|
+
continue
|
|
984
|
+
|
|
985
|
+
irow = int(test[-1]) - 1
|
|
986
|
+
|
|
987
|
+
# Get list of all channels with wirebond notation.
|
|
988
|
+
out = [int(x) for x in values.keys()]
|
|
989
|
+
# Translate to sensor, hybrids, etc.
|
|
990
|
+
for S in M.sensors:
|
|
991
|
+
for H in S.hybrids:
|
|
992
|
+
H.add_channel(irow, out)
|
|
993
|
+
H.holes[irow] = find_holes(H.channels[irow])
|
|
994
|
+
|
|
995
|
+
# add defects
|
|
996
|
+
defects.append(
|
|
997
|
+
{
|
|
998
|
+
"description": "Unbonded Channel",
|
|
999
|
+
"name": "UNBONDED",
|
|
1000
|
+
"properties": {
|
|
1001
|
+
"front_end_row": irow,
|
|
1002
|
+
"wire_number": out
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
)
|
|
1006
|
+
|
|
1007
|
+
# Now get sensor strips.
|
|
1008
|
+
M.ready()
|
|
1009
|
+
unconnected = M.get_n_unconnected()
|
|
1010
|
+
mx_consecutive = M.get_max_consecutive()
|
|
1011
|
+
mx_sensor_width, module_holes = M.get_max_sensor_consecutive()
|
|
1012
|
+
|
|
1013
|
+
out = {}
|
|
1014
|
+
out["comments"] = []
|
|
1015
|
+
out["defects"] = defects
|
|
1016
|
+
for irow in range(4):
|
|
1017
|
+
key = "MAX_CONT_UNCON_ROW{}".format(irow+1)
|
|
1018
|
+
out[key] = mx_consecutive[irow]
|
|
1019
|
+
|
|
1020
|
+
self.write_message("Found {} clusters of unconnected strips in sensor.\n".format(len(module_holes)))
|
|
1021
|
+
for H in module_holes:
|
|
1022
|
+
self.write_message("{}\n".format(H))
|
|
1023
|
+
|
|
1024
|
+
if mx_sensor_width > 0:
|
|
1025
|
+
self.write_message("Max width of consecutive unconnected strips: {}\n". format(mx_sensor_width))
|
|
1026
|
+
|
|
1027
|
+
out["MAX_UNCON_SENSOR_CHAN"] = mx_sensor_width
|
|
1028
|
+
if mx_sensor_width > 8:
|
|
1029
|
+
out["passed"] = False
|
|
1030
|
+
out["comments"].append("Too many consecutive sensor strips unconnected: {}".format(mx_sensor_width))
|
|
1031
|
+
|
|
1032
|
+
nstrips = 0
|
|
1033
|
+
for v in unconnected:
|
|
1034
|
+
nstrips += v
|
|
1035
|
+
|
|
1036
|
+
percent = 100*nstrips/M.nchan
|
|
1037
|
+
out["TOTAL_PERC_UNCON_SENSOR_CHAN"] = percent
|
|
1038
|
+
if out["TOTAL_PERC_UNCON_SENSOR_CHAN"] > 1.0:
|
|
1039
|
+
out["passed"] = False
|
|
1040
|
+
out["comments"].append("More than 1%% of channels unconnected: {:.1f}%%".format(percent))
|
|
1041
|
+
|
|
1042
|
+
return out
|
|
1043
|
+
|
|
1044
|
+
def get_unconnected(self, skeleton):
|
|
1045
|
+
"""Fill the test DTO with unconnected information."""
|
|
1046
|
+
out = self.compute_unconnected(skeleton["results"])
|
|
1047
|
+
if out is None:
|
|
1048
|
+
raise ValueError("Wrong SN")
|
|
1049
|
+
|
|
1050
|
+
for key, val in out.items():
|
|
1051
|
+
if key in ["passed", "problems"]:
|
|
1052
|
+
skeleton[key] = out[key]
|
|
1053
|
+
continue
|
|
1054
|
+
|
|
1055
|
+
if key == "comments":
|
|
1056
|
+
for C in out[key]:
|
|
1057
|
+
skeleton[key].append(C)
|
|
1058
|
+
|
|
1059
|
+
continue
|
|
1060
|
+
|
|
1061
|
+
if key == "defects":
|
|
1062
|
+
for D in out[key]:
|
|
1063
|
+
skeleton[key].append(D)
|
|
1064
|
+
|
|
1065
|
+
continue
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
skeleton["results"][key] = val
|
|
1069
|
+
|
|
1070
|
+
def read_file(self, *args):
|
|
1071
|
+
"""Read local data file."""
|
|
1072
|
+
dialog = Gtk.FileChooserDialog(
|
|
1073
|
+
title="Please choose a file",
|
|
1074
|
+
parent=self,
|
|
1075
|
+
action=Gtk.FileChooserAction.OPEN
|
|
1076
|
+
)
|
|
1077
|
+
dialog.add_buttons(
|
|
1078
|
+
Gtk.STOCK_CANCEL,
|
|
1079
|
+
Gtk.ResponseType.CANCEL,
|
|
1080
|
+
Gtk.STOCK_OPEN,
|
|
1081
|
+
Gtk.ResponseType.OK,
|
|
1082
|
+
)
|
|
1083
|
+
filter_text = Gtk.FileFilter()
|
|
1084
|
+
filter_text.set_name("JSON files")
|
|
1085
|
+
filter_text.add_mime_type("application/json")
|
|
1086
|
+
dialog.add_filter(filter_text)
|
|
1087
|
+
|
|
1088
|
+
response = dialog.run()
|
|
1089
|
+
if response == Gtk.ResponseType.OK:
|
|
1090
|
+
ofile = dialog.get_filename()
|
|
1091
|
+
self.write_message("Loading data from {}\n".format(ofile))
|
|
1092
|
+
with open(ofile, 'r', encoding="utf-8") as data_file:
|
|
1093
|
+
data = json.load(data_file)
|
|
1094
|
+
self.parse(data)
|
|
1095
|
+
|
|
1096
|
+
dialog.hide()
|
|
1097
|
+
dialog.destroy()
|
|
1098
|
+
|
|
1099
|
+
def parse(self, data):
|
|
1100
|
+
"""Parses a JSon dictionary."""
|
|
1101
|
+
try:
|
|
1102
|
+
dbGtkUtils.set_combo_iter(self.inst_combo, data["institution"])
|
|
1103
|
+
except KeyError:
|
|
1104
|
+
self.write_message("institution value is not in the loaded file\n.")
|
|
1105
|
+
|
|
1106
|
+
self.operator.set_text(data["properties"]["OPERATOR"])
|
|
1107
|
+
self.machine.set_text(data["properties"]["BOND_MACHINE"])
|
|
1108
|
+
self.batch.set_text(data["properties"]["BONDWIRE_BATCH"])
|
|
1109
|
+
self.SN.set_text(data["component"])
|
|
1110
|
+
self.date.set_text(data["date"])
|
|
1111
|
+
for key, val in data["results"].items():
|
|
1112
|
+
model = self.models[key]
|
|
1113
|
+
model.clear()
|
|
1114
|
+
for chan, msg in val.items():
|
|
1115
|
+
model.append([chan, msg])
|
|
1116
|
+
|
|
1117
|
+
model.append(["", ""])
|
|
1118
|
+
|
|
1119
|
+
def get_list_of_channels(self, data):
|
|
1120
|
+
"""Creates the lists of channels."""
|
|
1121
|
+
for key, model in self.models.items():
|
|
1122
|
+
lv_iter = model.get_iter_first()
|
|
1123
|
+
out = {}
|
|
1124
|
+
while lv_iter:
|
|
1125
|
+
chan, comm = model[lv_iter]
|
|
1126
|
+
if len(chan) > 0:
|
|
1127
|
+
out[chan] = comm
|
|
1128
|
+
|
|
1129
|
+
lv_iter = model.iter_next(lv_iter)
|
|
1130
|
+
|
|
1131
|
+
data["results"][key] = out
|
|
1132
|
+
|
|
1133
|
+
def fix_list_of_channels(self, data):
|
|
1134
|
+
"""Expand ranges and, eventually, apply LUT.
|
|
1135
|
+
|
|
1136
|
+
Args:
|
|
1137
|
+
data: The test payload.
|
|
1138
|
+
"""
|
|
1139
|
+
for tit, section in data["results"].items():
|
|
1140
|
+
if not isinstance(section, dict):
|
|
1141
|
+
continue
|
|
1142
|
+
|
|
1143
|
+
range_items = []
|
|
1144
|
+
added_items = []
|
|
1145
|
+
for key, comment in section.items():
|
|
1146
|
+
values = list(map(str.strip, key.split(',')))
|
|
1147
|
+
if ',' in key or '-' in key:
|
|
1148
|
+
range_items.append(key)
|
|
1149
|
+
|
|
1150
|
+
else:
|
|
1151
|
+
continue
|
|
1152
|
+
|
|
1153
|
+
for V in values:
|
|
1154
|
+
for i in range_to_list(V):
|
|
1155
|
+
added_items.append((str(i), comment))
|
|
1156
|
+
|
|
1157
|
+
for key in range_items:
|
|
1158
|
+
section.pop(key)
|
|
1159
|
+
|
|
1160
|
+
for key, comm in added_items:
|
|
1161
|
+
section[key] = comm
|
|
1162
|
+
|
|
1163
|
+
if len(self.lut)>0 and len(section)>0:
|
|
1164
|
+
tmp = copy.deepcopy(section)
|
|
1165
|
+
section.clear()
|
|
1166
|
+
for key, val in tmp.items():
|
|
1167
|
+
section[self.convert_channel(key)] = val
|
|
1168
|
+
|
|
1169
|
+
|
|
1170
|
+
def save_test(self, *args):
|
|
1171
|
+
"""Save Test file."""
|
|
1172
|
+
dialog = Gtk.FileChooserDialog(
|
|
1173
|
+
title="Please choose a file", parent=self, action=Gtk.FileChooserAction.SAVE
|
|
1174
|
+
)
|
|
1175
|
+
dialog.add_buttons(
|
|
1176
|
+
Gtk.STOCK_CANCEL,
|
|
1177
|
+
Gtk.ResponseType.CANCEL,
|
|
1178
|
+
Gtk.STOCK_OPEN,
|
|
1179
|
+
Gtk.ResponseType.OK,
|
|
1180
|
+
)
|
|
1181
|
+
filter_text = Gtk.FileFilter()
|
|
1182
|
+
filter_text.set_name("JSON files")
|
|
1183
|
+
filter_text.add_mime_type("application/json")
|
|
1184
|
+
dialog.add_filter(filter_text)
|
|
1185
|
+
|
|
1186
|
+
response = dialog.run()
|
|
1187
|
+
if response == Gtk.ResponseType.OK:
|
|
1188
|
+
ofile = dialog.get_filename()
|
|
1189
|
+
data = self.get_test_data()
|
|
1190
|
+
with open(ofile, 'w', encoding="UTF-8") as of:
|
|
1191
|
+
json.dump(data, of, indent=3)
|
|
1192
|
+
|
|
1193
|
+
dialog.hide()
|
|
1194
|
+
dialog.destroy()
|
|
1195
|
+
|
|
1196
|
+
def get_test_data(self):
|
|
1197
|
+
"""Get the test data."""
|
|
1198
|
+
data = {
|
|
1199
|
+
"institution": self.institute,
|
|
1200
|
+
"date": ITkDButils.get_db_date(),
|
|
1201
|
+
"runNumber": "1",
|
|
1202
|
+
"properties": {},
|
|
1203
|
+
"results": {},
|
|
1204
|
+
"comments": [],
|
|
1205
|
+
"defects": []
|
|
1206
|
+
}
|
|
1207
|
+
self.get_test_header(data)
|
|
1208
|
+
self.get_list_of_channels(data)
|
|
1209
|
+
return data
|
|
1210
|
+
|
|
1211
|
+
def get_test_header(self, data):
|
|
1212
|
+
"""Get Basic test data."""
|
|
1213
|
+
SN = self.SN.get_text()
|
|
1214
|
+
operator = self.operator.get_text()
|
|
1215
|
+
machine = self.machine.get_text()
|
|
1216
|
+
batch = self.batch.get_text()
|
|
1217
|
+
|
|
1218
|
+
if not SN or len(SN)==0:
|
|
1219
|
+
SN = dbGtkUtils.get_a_value("Module Serial Number",
|
|
1220
|
+
"Module serial Number is missing")
|
|
1221
|
+
|
|
1222
|
+
if len(operator) == 0 or len(machine) == 0:
|
|
1223
|
+
values = dbGtkUtils.get_a_list_of_values(
|
|
1224
|
+
"Missing Values",
|
|
1225
|
+
["SN", "Operator", "Wire Bonder"],
|
|
1226
|
+
defaults=[SN, operator, machine],
|
|
1227
|
+
)
|
|
1228
|
+
if len(values) == 4:
|
|
1229
|
+
SN, operator, machine = values
|
|
1230
|
+
else:
|
|
1231
|
+
self.write_message("Something went wrong while requesting missing information.\n")
|
|
1232
|
+
|
|
1233
|
+
data["component"] = SN
|
|
1234
|
+
data["properties"]["OPERATOR"] = operator
|
|
1235
|
+
data["properties"]["BOND_MACHINE"] = machine
|
|
1236
|
+
data["properties"]["BONDWIRE_BATCH"] = batch
|
|
1237
|
+
data["institution"] = self.institute
|
|
1238
|
+
if data["runNumber"] == "-1":
|
|
1239
|
+
data["runNumber"] = "1"
|
|
1240
|
+
|
|
1241
|
+
data["date"] = self.date.get_text()
|
|
1242
|
+
|
|
1243
|
+
|
|
1244
|
+
|
|
1245
|
+
def upload_test(self, *args):
|
|
1246
|
+
"""Upload test."""
|
|
1247
|
+
if self.session is None:
|
|
1248
|
+
self.pdb = ITkDBlogin.ITkDBlogin()
|
|
1249
|
+
client = self.pdb.get_client()
|
|
1250
|
+
if client is None:
|
|
1251
|
+
dbGtkUtils.complain("Could not connect to DB with provided credentials.")
|
|
1252
|
+
self.pdb.die()
|
|
1253
|
+
|
|
1254
|
+
else:
|
|
1255
|
+
self.session = client
|
|
1256
|
+
|
|
1257
|
+
defaults = {
|
|
1258
|
+
"institution": self.institute,
|
|
1259
|
+
"runNumber": "1",
|
|
1260
|
+
"date": ITkDButils.get_db_date()
|
|
1261
|
+
}
|
|
1262
|
+
skeleton = ITkDButils.get_test_skeleton(self.session, "MODULE", "MODULE_WIRE_BONDING", defaults)
|
|
1263
|
+
|
|
1264
|
+
self.get_test_header(skeleton)
|
|
1265
|
+
self.get_list_of_channels(skeleton)
|
|
1266
|
+
self.fix_list_of_channels(skeleton)
|
|
1267
|
+
try:
|
|
1268
|
+
self.get_unconnected(skeleton)
|
|
1269
|
+
self.compute_repaired(skeleton)
|
|
1270
|
+
self.compute_hybrid_to_pb(skeleton)
|
|
1271
|
+
self.compute_module_to_frame(skeleton)
|
|
1272
|
+
|
|
1273
|
+
except ValueError:
|
|
1274
|
+
return
|
|
1275
|
+
|
|
1276
|
+
uploadW = UploadTest.UploadTest(self.session, payload=skeleton)
|
|
1277
|
+
# uploadW.run()
|
|
1278
|
+
|
|
1279
|
+
|
|
1280
|
+
def main():
|
|
1281
|
+
"""Main entry."""
|
|
1282
|
+
dlg = ITkDBlogin.ITkDBlogin()
|
|
1283
|
+
client = dlg.get_client()
|
|
1284
|
+
if client is None:
|
|
1285
|
+
print("Could not connect to DB with provided credentials.")
|
|
1286
|
+
dlg.die()
|
|
1287
|
+
sys.exit()
|
|
1288
|
+
|
|
1289
|
+
client.user_gui = dlg
|
|
1290
|
+
|
|
1291
|
+
|
|
1292
|
+
win = WireBond(client, title="WireBond")
|
|
1293
|
+
win.connect("destroy", Gtk.main_quit)
|
|
1294
|
+
win.show_all()
|
|
1295
|
+
try:
|
|
1296
|
+
Gtk.main()
|
|
1297
|
+
|
|
1298
|
+
except KeyboardInterrupt:
|
|
1299
|
+
print("Arrrgggg!!!")
|
|
1300
|
+
|
|
1301
|
+
dlg.die()
|
|
1302
|
+
|
|
1303
|
+
if __name__ == "__main__":
|
|
1304
|
+
main()
|