itkdb-gtk 0.0.14__py3-none-any.whl → 0.0.16__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 itkdb-gtk might be problematic. Click here for more details.
- itkdb_gtk/{getShipments.py → GetShipments.py} +9 -8
- itkdb_gtk/GlueWeight.py +8 -5
- itkdb_gtk/{groundVITests.py → GroundVITests.py} +10 -8
- itkdb_gtk/ITkDBlogin.py +1 -0
- itkdb_gtk/ITkDButils.py +6 -2
- itkdb_gtk/{sendShipments.py → SendShipments.py} +9 -7
- itkdb_gtk/ShowAttachments.py +136 -8
- itkdb_gtk/ShowComments.py +12 -3
- itkdb_gtk/ShowDefects.py +17 -5
- itkdb_gtk/UploadModuleIV.py +857 -0
- itkdb_gtk/{uploadMultipleTests.py → UploadMultipleTests.py} +13 -15
- itkdb_gtk/{uploadPetalInformation.py → UploadPetalInformation.py} +9 -9
- itkdb_gtk/{uploadTest.py → UploadTest.py} +87 -49
- itkdb_gtk/WireBondGui.py +863 -0
- itkdb_gtk/__init__.py +18 -10
- itkdb_gtk/dashBoard.py +68 -22
- itkdb_gtk/dbGtkUtils.py +165 -41
- itkdb_gtk/readAVSdata.py +8 -4
- itkdb_gtk/readGoogleSheet.py +1 -1
- itkdb_gtk/untrash_component.py +35 -0
- {itkdb_gtk-0.0.14.dist-info → itkdb_gtk-0.0.16.dist-info}/METADATA +13 -8
- itkdb_gtk-0.0.16.dist-info/RECORD +27 -0
- {itkdb_gtk-0.0.14.dist-info → itkdb_gtk-0.0.16.dist-info}/WHEEL +1 -1
- {itkdb_gtk-0.0.14.dist-info → itkdb_gtk-0.0.16.dist-info}/entry_points.txt +2 -0
- itkdb_gtk/checkComponent.py +0 -151
- itkdb_gtk/checkComponentTests.py +0 -116
- itkdb_gtk/checkJSon.py +0 -56
- itkdb_gtk/recover_component.py +0 -0
- itkdb_gtk-0.0.14.dist-info/RECORD +0 -28
- {itkdb_gtk-0.0.14.dist-info → itkdb_gtk-0.0.16.dist-info}/top_level.txt +0 -0
itkdb_gtk/WireBondGui.py
ADDED
|
@@ -0,0 +1,863 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Wirebonding GUI for PSB."""
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
import json
|
|
6
|
+
import gi
|
|
7
|
+
|
|
8
|
+
gi.require_version("Gtk", "3.0")
|
|
9
|
+
from gi.repository import Gtk, Gio, GLib
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
import itkdb_gtk
|
|
13
|
+
|
|
14
|
+
except ImportError:
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
cwd = Path(sys.argv[0]).parent.parent
|
|
17
|
+
sys.path.append(cwd.as_posix())
|
|
18
|
+
|
|
19
|
+
from itkdb_gtk import dbGtkUtils
|
|
20
|
+
from itkdb_gtk import ITkDBlogin, ITkDButils, UploadTest
|
|
21
|
+
|
|
22
|
+
test_parameters = {
|
|
23
|
+
"Repaired Row 1": "REPAIRED_FRONTEND_ROW1",
|
|
24
|
+
"Failed Row 1": "FAILED_FRONTEND_ROW1",
|
|
25
|
+
"Repaired Row 2": "REPAIRED_FRONTEND_ROW2",
|
|
26
|
+
"Failed Row 2": "FAILED_FRONTEND_ROW2",
|
|
27
|
+
"Repaired Row 3": "REPAIRED_FRONTEND_ROW3",
|
|
28
|
+
"Failed Row 3": "FAILED_FRONTEND_ROW3",
|
|
29
|
+
"Repaired Row 4": "REPAIRED_FRONTEND_ROW4",
|
|
30
|
+
"Failed Row 4": "FAILED_FRONTEND_ROW4",
|
|
31
|
+
"Repaired Hyb->PB": "REPAIRED_HYBRID_TO_PB",
|
|
32
|
+
"Failed HyB->PB": "FAILED_HYBRID_TO_PB",
|
|
33
|
+
"Repaired Module->Frame": "REPAIRED_MODULE_TO_FRAME",
|
|
34
|
+
"Failed Module->Frame": "FAILED_MODULE_TO_FRAME"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# module_param[iring][i_sensor][i_hybrid][i_row]
|
|
38
|
+
module_param = {
|
|
39
|
+
0: [
|
|
40
|
+
[
|
|
41
|
+
[[255, 830], [1343, 1918], [2431, 3006], [3519, 4094]],
|
|
42
|
+
[[831, 1342], [1919, 2430], [3007, 3518], [4095, 4606]],
|
|
43
|
+
]
|
|
44
|
+
],
|
|
45
|
+
1: [
|
|
46
|
+
[
|
|
47
|
+
[[271, 974], [1615, 2318], [2959, 3662], [4303, 5006]],
|
|
48
|
+
[[975, 1614], [2319, 2958], [3663, 4302], [5007, 5646]],
|
|
49
|
+
]
|
|
50
|
+
],
|
|
51
|
+
2: [
|
|
52
|
+
[
|
|
53
|
+
[[201, 968], [969, 1736], [1737, 2504], [2505, 3272]]
|
|
54
|
+
]
|
|
55
|
+
],
|
|
56
|
+
3: [
|
|
57
|
+
[
|
|
58
|
+
[[566, 1013], [2358, 2805], [4150, 4597], [5942, 6389]],
|
|
59
|
+
[[1462, 1909], [3254, 3701], [5046, 5493], [6838, 7285]]
|
|
60
|
+
],
|
|
61
|
+
[
|
|
62
|
+
[[1014, 1461], [2806, 3253], [4598, 5045], [6390, 6837]],
|
|
63
|
+
[[1910, 2357], [3702, 4149], [5494, 5941], [7286, 7733]]
|
|
64
|
+
]
|
|
65
|
+
],
|
|
66
|
+
4: [
|
|
67
|
+
[
|
|
68
|
+
[[318, 829], [1342, 1853], [2366, 2877], [3390, 3901]]
|
|
69
|
+
],
|
|
70
|
+
[
|
|
71
|
+
[[830, 1341], [1854, 2365], [2878, 3389], [3902, 4413]]
|
|
72
|
+
]
|
|
73
|
+
],
|
|
74
|
+
5: [
|
|
75
|
+
[
|
|
76
|
+
[[332, 907], [1484, 2059], [2636, 3211], [3788, 4363]]
|
|
77
|
+
],
|
|
78
|
+
[
|
|
79
|
+
[[908, 1483], [2060, 2635], [3212, 3787], [4364, 4939]]
|
|
80
|
+
]
|
|
81
|
+
],
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
def wire2strip(mod_par, irow, iwire):
|
|
85
|
+
"""Convert from wirebond index to strip_number."""
|
|
86
|
+
for sensor in mod_par:
|
|
87
|
+
for hyb in sensor:
|
|
88
|
+
rng = hyb[irow]
|
|
89
|
+
if iwire>= rng[0] and iwire<=rng[1]:
|
|
90
|
+
if irow % 2:
|
|
91
|
+
ichan = 2*(iwire-rng[0]) + 1
|
|
92
|
+
else:
|
|
93
|
+
ichan = 2*(iwire-rng[0])
|
|
94
|
+
|
|
95
|
+
return ichan
|
|
96
|
+
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def find_holes(chan_list, min_chan=0, max_chan=999999):
|
|
101
|
+
"""Find groups of consecutive channels."""
|
|
102
|
+
out = sorted(chan_list)
|
|
103
|
+
nchan = 0
|
|
104
|
+
last_chan = -1
|
|
105
|
+
ichan = -1
|
|
106
|
+
holes = []
|
|
107
|
+
for chan in out:
|
|
108
|
+
if chan < min_chan or chan > max_chan:
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
if last_chan < 0:
|
|
112
|
+
last_chan = chan
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
if chan - last_chan > 1:
|
|
116
|
+
if nchan:
|
|
117
|
+
holes.append([ichan, nchan])
|
|
118
|
+
nchan = 0
|
|
119
|
+
ichan = -1
|
|
120
|
+
else:
|
|
121
|
+
if last_chan < max_chan and chan >= max_chan:
|
|
122
|
+
# WE are in another sensor
|
|
123
|
+
holes.append([ichan, nchan])
|
|
124
|
+
nchan = 0
|
|
125
|
+
ichan = -1
|
|
126
|
+
last_chan = chan
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
nchan += 1
|
|
130
|
+
if ichan < 0:
|
|
131
|
+
ichan = last_chan
|
|
132
|
+
nchan += 1
|
|
133
|
+
|
|
134
|
+
last_chan = chan
|
|
135
|
+
|
|
136
|
+
if nchan:
|
|
137
|
+
holes.append([ichan, nchan])
|
|
138
|
+
|
|
139
|
+
return holes
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class HybridHoles:
|
|
143
|
+
"""Holes in hybrid bomds.
|
|
144
|
+
|
|
145
|
+
Holes are defined by a list [first_chan, n_chan].
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
def __init__(self, param, hid=0):
|
|
149
|
+
"""Initialization.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
param: Hybrid wirebon parameters.
|
|
153
|
+
|
|
154
|
+
"""
|
|
155
|
+
self.id = hid
|
|
156
|
+
self.param = param
|
|
157
|
+
self.nchan = 0
|
|
158
|
+
for min_chan, max_chan in param:
|
|
159
|
+
self.nchan += (max_chan-min_chan+1)
|
|
160
|
+
|
|
161
|
+
self.holes = [[] for irow in range(4)]
|
|
162
|
+
self.channels = [[] for irow in range(4)]
|
|
163
|
+
|
|
164
|
+
def add_channel(self, irow, ichan)->bool:
|
|
165
|
+
"""Add a new channel in row.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
irow: rown number
|
|
169
|
+
ichan: channel number
|
|
170
|
+
|
|
171
|
+
Return:
|
|
172
|
+
True if added, False otherwise.
|
|
173
|
+
"""
|
|
174
|
+
first_chan = self.param[irow][0]
|
|
175
|
+
last_chan = self.param[irow][1]
|
|
176
|
+
if isinstance(ichan, list) or isinstance(ichan, tuple):
|
|
177
|
+
nadded = 0
|
|
178
|
+
for ich in ichan:
|
|
179
|
+
if ich >= first_chan and ich <= last_chan:
|
|
180
|
+
self.channels[irow].append(ich)
|
|
181
|
+
nadded += 1
|
|
182
|
+
|
|
183
|
+
self.channels[irow] = sorted(self.channels[irow])
|
|
184
|
+
return nadded>0
|
|
185
|
+
else:
|
|
186
|
+
if ichan >= first_chan and ichan <= last_chan:
|
|
187
|
+
self.channels[irow].append(ichan)
|
|
188
|
+
return True
|
|
189
|
+
else:
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
def get_n_unconnected(self):
|
|
193
|
+
"""Count number of unconnected channels.
|
|
194
|
+
|
|
195
|
+
Return a list, one item per row.
|
|
196
|
+
"""
|
|
197
|
+
nchan = []
|
|
198
|
+
for row in self.holes:
|
|
199
|
+
nch = 0
|
|
200
|
+
for h in row:
|
|
201
|
+
nch += h[1]
|
|
202
|
+
|
|
203
|
+
nchan.append(nch)
|
|
204
|
+
|
|
205
|
+
return nchan
|
|
206
|
+
|
|
207
|
+
def get_max_consecutive(self):
|
|
208
|
+
"""Returns the largest 'hole'."""
|
|
209
|
+
mx_width = []
|
|
210
|
+
|
|
211
|
+
for row in self.holes:
|
|
212
|
+
mxW = -1
|
|
213
|
+
for h in row:
|
|
214
|
+
if h[1] > mxW:
|
|
215
|
+
mxW = h[1]
|
|
216
|
+
|
|
217
|
+
mx_width.append(mxW)
|
|
218
|
+
|
|
219
|
+
return mx_width
|
|
220
|
+
|
|
221
|
+
def get_sensor_holes(self):
|
|
222
|
+
"""Compute holes in 'sensor' strips.
|
|
223
|
+
|
|
224
|
+
Each hybrid has 2 sensor segments corresponding to
|
|
225
|
+
rows (1,2) and (3, 4).
|
|
226
|
+
|
|
227
|
+
Return a list of [sensor, hybrid, segment, ichan, width]
|
|
228
|
+
"""
|
|
229
|
+
holes = []
|
|
230
|
+
channels = [[], []]
|
|
231
|
+
for irow, row in enumerate(self.channels):
|
|
232
|
+
isegment = int(irow/2)
|
|
233
|
+
for ich in row:
|
|
234
|
+
rng = self.param[irow]
|
|
235
|
+
if irow % 2:
|
|
236
|
+
chan = 2*(ich-rng[0]) + 1
|
|
237
|
+
else:
|
|
238
|
+
chan = 2*(ich-rng[0])
|
|
239
|
+
|
|
240
|
+
channels[isegment].append(chan)
|
|
241
|
+
|
|
242
|
+
channels[isegment] = sorted(channels[isegment])
|
|
243
|
+
|
|
244
|
+
for isegment, S in enumerate(channels):
|
|
245
|
+
H = find_holes(S)
|
|
246
|
+
if len(H)>0:
|
|
247
|
+
out = [ [0, self.id, isegment, chan, width] for chan, width in H ]
|
|
248
|
+
holes.extend(out)
|
|
249
|
+
|
|
250
|
+
return holes
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class SensorHoles:
|
|
254
|
+
"""Holes in sensor."""
|
|
255
|
+
|
|
256
|
+
def __init__(self, param, sid=0):
|
|
257
|
+
"""Initialization.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
param: sensor wirebon params
|
|
261
|
+
"""
|
|
262
|
+
self.id = sid
|
|
263
|
+
self.param = param
|
|
264
|
+
self.nchan = 0
|
|
265
|
+
self.nhybrid = len(param)
|
|
266
|
+
self.hybrids = []
|
|
267
|
+
for i, P in enumerate(param):
|
|
268
|
+
H = HybridHoles(P, hid=i)
|
|
269
|
+
self.hybrids.append(H)
|
|
270
|
+
self.nchan += H.nchan
|
|
271
|
+
|
|
272
|
+
def get_n_hyb(self):
|
|
273
|
+
"""Return number of hybrids."""
|
|
274
|
+
return len(self.hybrids)
|
|
275
|
+
|
|
276
|
+
def get_hybrid(self, i):
|
|
277
|
+
"""Return i-th hybrid."""
|
|
278
|
+
return self.hybrids[i]
|
|
279
|
+
|
|
280
|
+
def get_max_consecutive(self):
|
|
281
|
+
"""Max number of consecutive unconnected channels.
|
|
282
|
+
|
|
283
|
+
This is ordered by row.
|
|
284
|
+
"""
|
|
285
|
+
mx_width = [0, 0, 0, 0]
|
|
286
|
+
for hyb in self.hybrids:
|
|
287
|
+
mxW = hyb.get_max_consecutive()
|
|
288
|
+
for j in range(4):
|
|
289
|
+
if mxW[j] > mx_width[j]:
|
|
290
|
+
mx_width[j] = mxW[j]
|
|
291
|
+
|
|
292
|
+
return mx_width
|
|
293
|
+
|
|
294
|
+
def get_n_unconnected(self):
|
|
295
|
+
"""Count number of unconnected channels.
|
|
296
|
+
|
|
297
|
+
Return a list, one item per row.
|
|
298
|
+
"""
|
|
299
|
+
nchan = [0, 0, 0, 0]
|
|
300
|
+
for hyb in self.hybrids:
|
|
301
|
+
nc = hyb.get_n_unconnected()
|
|
302
|
+
for i, n in enumerate(nc):
|
|
303
|
+
nchan[i] += n
|
|
304
|
+
|
|
305
|
+
return nchan
|
|
306
|
+
|
|
307
|
+
def get_sensor_holes(self):
|
|
308
|
+
"""Return holes sensor.
|
|
309
|
+
|
|
310
|
+
Return a list of [sensor, hybrid, segment, ichan, width]
|
|
311
|
+
"""
|
|
312
|
+
holes = []
|
|
313
|
+
for hyb in self.hybrids:
|
|
314
|
+
H = hyb.get_sensor_holes()
|
|
315
|
+
for _, ih, isegment, ichan, width in H:
|
|
316
|
+
holes.append([self.id, ih, isegment, ichan, width])
|
|
317
|
+
|
|
318
|
+
return holes
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class ModuleHoles:
|
|
322
|
+
"""Holes in Modules."""
|
|
323
|
+
|
|
324
|
+
def __init__(self, param):
|
|
325
|
+
"""Initialization.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
param: module wirebond params
|
|
329
|
+
"""
|
|
330
|
+
self.nsensor = len(param)
|
|
331
|
+
self.nchan = 0
|
|
332
|
+
self.sensors = []
|
|
333
|
+
for i, P in enumerate(param):
|
|
334
|
+
S = SensorHoles(P, sid=i)
|
|
335
|
+
self.sensors.append(S)
|
|
336
|
+
self.nchan += S.nchan
|
|
337
|
+
|
|
338
|
+
def get_max_consecutive(self):
|
|
339
|
+
"""Max number of consecutive unconnected channels.
|
|
340
|
+
|
|
341
|
+
This is ordered by row.
|
|
342
|
+
"""
|
|
343
|
+
mx_width = [-1, -1, -1, -1]
|
|
344
|
+
for S in self.sensors:
|
|
345
|
+
mxW = S.get_max_consecutive()
|
|
346
|
+
for j in range(4):
|
|
347
|
+
if mxW[j] > mx_width[j]:
|
|
348
|
+
mx_width[j] = mxW[j]
|
|
349
|
+
|
|
350
|
+
return mx_width
|
|
351
|
+
|
|
352
|
+
def get_sensor_holes(self) -> list:
|
|
353
|
+
"""Return. holesin sensor strips.
|
|
354
|
+
|
|
355
|
+
Return a list of [sensor, hybrid, segment, ichan, width]
|
|
356
|
+
"""
|
|
357
|
+
holes = []
|
|
358
|
+
for S in self.sensors:
|
|
359
|
+
for _, ihyb, isegment, ichan, width in S.get_sensor_holes():
|
|
360
|
+
holes.append([S.id, ihyb, isegment, ichan, width])
|
|
361
|
+
|
|
362
|
+
return holes
|
|
363
|
+
|
|
364
|
+
def get_n_unconnected(self) -> list:
|
|
365
|
+
"""Count number of unconnected channels.
|
|
366
|
+
|
|
367
|
+
Return a list, one item per row.
|
|
368
|
+
"""
|
|
369
|
+
nchan = [0, 0, 0, 0]
|
|
370
|
+
for S in self.sensors:
|
|
371
|
+
nc = S.get_n_unconnected()
|
|
372
|
+
for i, n in enumerate(nc):
|
|
373
|
+
nchan[i] += n
|
|
374
|
+
|
|
375
|
+
return nchan
|
|
376
|
+
|
|
377
|
+
def get_total_unconnected(self) -> int:
|
|
378
|
+
"""Return total number of unconnected wires."""
|
|
379
|
+
unc = self.get_n_unconnected()
|
|
380
|
+
out = 0
|
|
381
|
+
for v in unc:
|
|
382
|
+
out += v
|
|
383
|
+
|
|
384
|
+
return int(out)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def get_module_param(SN):
|
|
388
|
+
"""Get parameters of module type.
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
SN: Serial Number
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
list: list with bond numbers.
|
|
395
|
+
"""
|
|
396
|
+
if len(SN) != 14 or SN[:3]!="20U":
|
|
397
|
+
raise ValueError("Wrong serial number {}".format(SN))
|
|
398
|
+
|
|
399
|
+
if SN[3:5] != "SE":
|
|
400
|
+
raise ValueError("Cannot handle barrel modules yet.")
|
|
401
|
+
|
|
402
|
+
mod_type = SN[5:7]
|
|
403
|
+
if mod_type[0] != "M":
|
|
404
|
+
raise ValueError("Does not seem to be a RingModule: {}".format(SN))
|
|
405
|
+
|
|
406
|
+
ring = int(mod_type[-1])
|
|
407
|
+
param = module_param[ring]
|
|
408
|
+
|
|
409
|
+
return param
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
class WireBond(Gtk.Window):
|
|
413
|
+
"""Main window."""
|
|
414
|
+
|
|
415
|
+
def __init__(self, session=None, title=""):
|
|
416
|
+
"""Initialization."""
|
|
417
|
+
super().__init__(title=title)
|
|
418
|
+
self.pdb = None
|
|
419
|
+
self.session = session
|
|
420
|
+
self.models = {}
|
|
421
|
+
self.holes = {}
|
|
422
|
+
self.prepare_window()
|
|
423
|
+
|
|
424
|
+
def prepare_window(self):
|
|
425
|
+
"""Creates the GUI."""
|
|
426
|
+
self.hb = Gtk.HeaderBar()
|
|
427
|
+
self.hb.set_show_close_button(True)
|
|
428
|
+
self.hb.props.title = "Wire Bond"
|
|
429
|
+
self.set_titlebar(self.hb)
|
|
430
|
+
|
|
431
|
+
# Button to upload
|
|
432
|
+
button = Gtk.Button()
|
|
433
|
+
icon = Gio.ThemedIcon(name="document-send-symbolic")
|
|
434
|
+
image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
|
|
435
|
+
button.add(image)
|
|
436
|
+
button.set_tooltip_text("Click to upload test")
|
|
437
|
+
button.connect("clicked", self.upload_test)
|
|
438
|
+
self.hb.pack_end(button)
|
|
439
|
+
|
|
440
|
+
button = Gtk.Button()
|
|
441
|
+
icon = Gio.ThemedIcon(name="document-save-symbolic")
|
|
442
|
+
image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
|
|
443
|
+
button.add(image)
|
|
444
|
+
button.set_tooltip_text("Click to save test file")
|
|
445
|
+
button.connect("clicked", self.save_test)
|
|
446
|
+
self.hb.pack_end(button)
|
|
447
|
+
|
|
448
|
+
# Button to upload
|
|
449
|
+
button = Gtk.Button()
|
|
450
|
+
icon = Gio.ThemedIcon(name="document-open-symbolic")
|
|
451
|
+
image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
|
|
452
|
+
button.add(image)
|
|
453
|
+
button.set_tooltip_text("Click to read local data file.")
|
|
454
|
+
button.connect("clicked", self.read_file)
|
|
455
|
+
self.hb.pack_end(button)
|
|
456
|
+
|
|
457
|
+
# Create the main box and add it to the window
|
|
458
|
+
self.mainBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
459
|
+
self.mainBox.set_property("margin-left", 6)
|
|
460
|
+
self.mainBox.set_property("margin-right", 6)
|
|
461
|
+
self.add(self.mainBox)
|
|
462
|
+
|
|
463
|
+
# Data panel
|
|
464
|
+
grid = Gtk.Grid(column_spacing=5, row_spacing=1)
|
|
465
|
+
for i, tit in enumerate(["Operator", "Bond Machine", "Wire Batch", "SN", "Date"]):
|
|
466
|
+
lbl = Gtk.Label(label=tit)
|
|
467
|
+
lbl.set_xalign(0)
|
|
468
|
+
grid.attach(lbl, 0, i, 1, 1)
|
|
469
|
+
|
|
470
|
+
self.operator = dbGtkUtils.new_small_text_entry()
|
|
471
|
+
self.machine = dbGtkUtils.new_small_text_entry()
|
|
472
|
+
self.batch = dbGtkUtils.new_small_text_entry()
|
|
473
|
+
self.SN = dbGtkUtils.new_small_text_entry()
|
|
474
|
+
self.date = dbGtkUtils.TextEntry(small=True)
|
|
475
|
+
self.date.connect("text_changed", self.new_date)
|
|
476
|
+
|
|
477
|
+
grid.attach(self.operator, 1, 0, 1, 1)
|
|
478
|
+
grid.attach(self.machine, 1, 1, 1, 1)
|
|
479
|
+
grid.attach(self.batch, 1, 2, 1, 1)
|
|
480
|
+
grid.attach(self.SN, 1, 3, 1, 1)
|
|
481
|
+
grid.attach(self.date.widget, 1, 4, 1, 1)
|
|
482
|
+
|
|
483
|
+
self.mainBox.pack_start(grid, True, True, 0)
|
|
484
|
+
|
|
485
|
+
# Prepare combo and all the models
|
|
486
|
+
lbl = Gtk.Label(label="Choose section.")
|
|
487
|
+
lbl.set_xalign(0)
|
|
488
|
+
self.mainBox.pack_start(lbl, True, True, 0)
|
|
489
|
+
|
|
490
|
+
combo = self.create_combo()
|
|
491
|
+
self.mainBox.pack_start(combo, True, True, 0)
|
|
492
|
+
|
|
493
|
+
# The tree view
|
|
494
|
+
scrolled = self.create_tree_view()
|
|
495
|
+
self.mainBox.pack_start(scrolled, True, True, 5)
|
|
496
|
+
|
|
497
|
+
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
|
498
|
+
self.mainBox.pack_start(box, False, False, 0)
|
|
499
|
+
dbGtkUtils.add_button_to_container(box, "Add Item",
|
|
500
|
+
"Click to add a new item.",
|
|
501
|
+
self.add_item)
|
|
502
|
+
|
|
503
|
+
#
|
|
504
|
+
# The text view and buffer
|
|
505
|
+
#
|
|
506
|
+
self.message_panel = dbGtkUtils.MessagePanel(size=100)
|
|
507
|
+
self.mainBox.pack_start(self.message_panel.frame, True, True, 0)
|
|
508
|
+
self.write_message("wirebond GUI\n")
|
|
509
|
+
|
|
510
|
+
# The button box
|
|
511
|
+
btnBox = Gtk.ButtonBox(orientation=Gtk.Orientation.HORIZONTAL)
|
|
512
|
+
|
|
513
|
+
btn = Gtk.Button(label="Quit")
|
|
514
|
+
btn.connect("clicked", self.quit)
|
|
515
|
+
btnBox.add(btn)
|
|
516
|
+
|
|
517
|
+
self.mainBox.pack_end(btnBox, False, True, 0)
|
|
518
|
+
self.show_all()
|
|
519
|
+
|
|
520
|
+
def on_name_combo_changed(self, combo):
|
|
521
|
+
"""Change model in TreeView."""
|
|
522
|
+
tree_iter = combo.get_active_iter()
|
|
523
|
+
if tree_iter is not None:
|
|
524
|
+
model = combo.get_model()
|
|
525
|
+
param = model[tree_iter][1]
|
|
526
|
+
view_model = self.models[param]
|
|
527
|
+
self.tree.set_model(view_model)
|
|
528
|
+
else:
|
|
529
|
+
self.write_message("Cannot find model for {}".format(param))
|
|
530
|
+
|
|
531
|
+
def create_combo(self):
|
|
532
|
+
"""Create the combo."""
|
|
533
|
+
model = Gtk.ListStore(str, str)
|
|
534
|
+
for txt, param in test_parameters.items():
|
|
535
|
+
model.append([txt, param])
|
|
536
|
+
|
|
537
|
+
M = Gtk.ListStore(str, str)
|
|
538
|
+
M.append(["", ""])
|
|
539
|
+
self.models[param] = M
|
|
540
|
+
|
|
541
|
+
self.combo = Gtk.ComboBox.new_with_model_and_entry(model)
|
|
542
|
+
self.combo.set_entry_text_column(0)
|
|
543
|
+
self.combo.set_active(0)
|
|
544
|
+
self.combo.connect("changed", self.on_name_combo_changed)
|
|
545
|
+
return self.combo
|
|
546
|
+
|
|
547
|
+
def create_tree_view(self, size=150):
|
|
548
|
+
"""Creates the tree vew with the attachmens."""
|
|
549
|
+
tree_iter = self.combo.get_active_iter()
|
|
550
|
+
combo_model = self.combo.get_model()
|
|
551
|
+
param = combo_model[tree_iter][1]
|
|
552
|
+
model = self.models[param]
|
|
553
|
+
|
|
554
|
+
self.tree = Gtk.TreeView(model=model)
|
|
555
|
+
self.tree.connect("button-press-event", self.button_pressed)
|
|
556
|
+
|
|
557
|
+
scrolled = Gtk.ScrolledWindow()
|
|
558
|
+
scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
|
559
|
+
scrolled.add(self.tree)
|
|
560
|
+
scrolled.set_size_request(-1, size)
|
|
561
|
+
|
|
562
|
+
renderer = Gtk.CellRendererText()
|
|
563
|
+
renderer.set_property("editable", True)
|
|
564
|
+
renderer.connect("edited", self.channel_edited)
|
|
565
|
+
column = Gtk.TreeViewColumn("Channel", renderer, text=0)
|
|
566
|
+
self.tree.append_column(column)
|
|
567
|
+
|
|
568
|
+
renderer = Gtk.CellRendererText()
|
|
569
|
+
renderer.set_property("editable", True)
|
|
570
|
+
renderer.connect("edited", self.failure_edited)
|
|
571
|
+
column = Gtk.TreeViewColumn("Failure", renderer, text=1)
|
|
572
|
+
self.tree.append_column(column)
|
|
573
|
+
|
|
574
|
+
return scrolled
|
|
575
|
+
|
|
576
|
+
def text_edited(self, icol, path, text):
|
|
577
|
+
"""Text has been edited in the TreeView"""
|
|
578
|
+
if len(text) == 0:
|
|
579
|
+
return
|
|
580
|
+
|
|
581
|
+
model = self.tree.get_model()
|
|
582
|
+
|
|
583
|
+
n_child = model.iter_n_children()
|
|
584
|
+
current_child = int(path)
|
|
585
|
+
if n_child-current_child == 1:
|
|
586
|
+
model.append(["", ""])
|
|
587
|
+
|
|
588
|
+
model[path][icol] = text
|
|
589
|
+
|
|
590
|
+
def channel_edited(self, widget, path, text):
|
|
591
|
+
"""Handles edition in channel number cell."""
|
|
592
|
+
if not text.isnumeric():
|
|
593
|
+
dbGtkUtils.complain("Wrong channel number", "Invalid channel number: {}".format(text))
|
|
594
|
+
return
|
|
595
|
+
|
|
596
|
+
self.text_edited(0, path, text)
|
|
597
|
+
|
|
598
|
+
def failure_edited(self, widget, path, text):
|
|
599
|
+
"""Handles edition in comment."""
|
|
600
|
+
self.text_edited(1, path, text)
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def new_date(self, entry, value):
|
|
604
|
+
"""new date given at input."""
|
|
605
|
+
d = dbGtkUtils.parse_date_as_string(value)
|
|
606
|
+
if d is not None:
|
|
607
|
+
self.date.set_text(d)
|
|
608
|
+
|
|
609
|
+
def button_pressed(self, *args):
|
|
610
|
+
"""Button pressed."""
|
|
611
|
+
pass
|
|
612
|
+
|
|
613
|
+
def add_item(self, *args):
|
|
614
|
+
"""Adds a new item in the current model."""
|
|
615
|
+
out = dbGtkUtils.get_a_list_of_values("Add Item", ["Channel", "Comment"])
|
|
616
|
+
if len(out) == 2:
|
|
617
|
+
model = self.tree.get_model()
|
|
618
|
+
model[-1] = out
|
|
619
|
+
model.append(["", ""])
|
|
620
|
+
|
|
621
|
+
def quit(self, *args):
|
|
622
|
+
"""Quits the application."""
|
|
623
|
+
if self.pdb:
|
|
624
|
+
self.pdb.die()
|
|
625
|
+
|
|
626
|
+
self.hide()
|
|
627
|
+
self.destroy()
|
|
628
|
+
|
|
629
|
+
def write_message(self, text):
|
|
630
|
+
"""Writes text to Text Viewer."""
|
|
631
|
+
self.message_panel.write_message(text)
|
|
632
|
+
|
|
633
|
+
def compute_unconnected(self):
|
|
634
|
+
"""Compute number of unconnected."""
|
|
635
|
+
param = get_module_param(self.SN.get_text())
|
|
636
|
+
M = ModuleHoles(param=param)
|
|
637
|
+
|
|
638
|
+
for test in test_parameters.values():
|
|
639
|
+
if test.find("FAILED") < 0:
|
|
640
|
+
continue
|
|
641
|
+
if test.find("ROW") < 0:
|
|
642
|
+
continue
|
|
643
|
+
|
|
644
|
+
irow = int(test[-1]) - 1
|
|
645
|
+
|
|
646
|
+
# Get list of all channels with wirebond notation.
|
|
647
|
+
model = self.models[test]
|
|
648
|
+
it = model.get_iter_first()
|
|
649
|
+
out = []
|
|
650
|
+
while it:
|
|
651
|
+
chan, _ = model[it]
|
|
652
|
+
if len(chan) > 0:
|
|
653
|
+
out.append(int(chan))
|
|
654
|
+
|
|
655
|
+
it = model.iter_next(it)
|
|
656
|
+
|
|
657
|
+
# Translate to sensor, hybrids, etc.
|
|
658
|
+
for S in M.sensors:
|
|
659
|
+
for H in S.hybrids:
|
|
660
|
+
H.add_channel(irow, out)
|
|
661
|
+
H.holes[irow] = find_holes(H.channels[irow])
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
# Now get sensor strips.
|
|
665
|
+
unconnected = M.get_n_unconnected()
|
|
666
|
+
mx_consecutive = M.get_max_consecutive()
|
|
667
|
+
module_holes = M.get_sensor_holes()
|
|
668
|
+
|
|
669
|
+
out = {}
|
|
670
|
+
for irow in range(4):
|
|
671
|
+
key = "MAX_CONT_UNCON_ROW{}".format(irow+1)
|
|
672
|
+
out[key] = mx_consecutive[irow]
|
|
673
|
+
|
|
674
|
+
mxW = 0
|
|
675
|
+
self.write_message("Found {} clusters of unconnected strips in sensor.\n".format(len(module_holes)))
|
|
676
|
+
for H in module_holes:
|
|
677
|
+
self.write_message("{}\n".format(str(H)))
|
|
678
|
+
if H[-1] > mxW:
|
|
679
|
+
mxW = H[-1]
|
|
680
|
+
|
|
681
|
+
if mxW > 0:
|
|
682
|
+
self.write_message("Max width: {}". format(mxW))
|
|
683
|
+
|
|
684
|
+
out["MAX_UNCON_SENSOR_CHAN"] = mxW
|
|
685
|
+
nstrips = 0
|
|
686
|
+
for v in unconnected:
|
|
687
|
+
nstrips += v
|
|
688
|
+
|
|
689
|
+
out["TOTAL_PERC_UNCON_SENSOR_CHAN"] = nstrips/M.nchan
|
|
690
|
+
|
|
691
|
+
return out
|
|
692
|
+
|
|
693
|
+
def get_unconnected(self, skeleton):
|
|
694
|
+
"""Fill the test DTO with unconnected information."""
|
|
695
|
+
out = self.compute_unconnected()
|
|
696
|
+
for key, val in out.items():
|
|
697
|
+
skeleton["results"][key] = val
|
|
698
|
+
|
|
699
|
+
def read_file(self, *args):
|
|
700
|
+
"""Read local data file."""
|
|
701
|
+
dialog = Gtk.FileChooserDialog(
|
|
702
|
+
title="Please choose a file",
|
|
703
|
+
parent=self,
|
|
704
|
+
action=Gtk.FileChooserAction.OPEN
|
|
705
|
+
)
|
|
706
|
+
dialog.add_buttons(
|
|
707
|
+
Gtk.STOCK_CANCEL,
|
|
708
|
+
Gtk.ResponseType.CANCEL,
|
|
709
|
+
Gtk.STOCK_OPEN,
|
|
710
|
+
Gtk.ResponseType.OK,
|
|
711
|
+
)
|
|
712
|
+
filter_text = Gtk.FileFilter()
|
|
713
|
+
filter_text.set_name("JSON files")
|
|
714
|
+
filter_text.add_mime_type("application/json")
|
|
715
|
+
dialog.add_filter(filter_text)
|
|
716
|
+
|
|
717
|
+
response = dialog.run()
|
|
718
|
+
if response == Gtk.ResponseType.OK:
|
|
719
|
+
ofile = dialog.get_filename()
|
|
720
|
+
self.write_message("Loading data from {}\n".format(ofile))
|
|
721
|
+
with open(ofile, 'r', encoding="utf-8") as data_file:
|
|
722
|
+
data = json.load(data_file)
|
|
723
|
+
self.parse(data)
|
|
724
|
+
|
|
725
|
+
dialog.hide()
|
|
726
|
+
dialog.destroy()
|
|
727
|
+
|
|
728
|
+
def parse(self, data):
|
|
729
|
+
"""Parses a JSon dictionary."""
|
|
730
|
+
self.operator.set_text(data["properties"]["OPERATOR"])
|
|
731
|
+
self.machine.set_text(data["properties"]["BOND_MACHINE"])
|
|
732
|
+
self.batch.set_text(data["properties"]["BONDWIRE_BATCH"])
|
|
733
|
+
self.SN.set_text(data["component"])
|
|
734
|
+
self.date.set_text(data["date"])
|
|
735
|
+
for key, val in data["results"].items():
|
|
736
|
+
model = self.models[key]
|
|
737
|
+
model.clear()
|
|
738
|
+
for chan, msg in val.items():
|
|
739
|
+
model.append([chan, msg])
|
|
740
|
+
|
|
741
|
+
model.append(["", ""])
|
|
742
|
+
|
|
743
|
+
def get_list_of_channels(self, data):
|
|
744
|
+
"""Creates the lists of channels."""
|
|
745
|
+
for key, model in self.models.items():
|
|
746
|
+
iter = model.get_iter_first()
|
|
747
|
+
out = {}
|
|
748
|
+
while iter:
|
|
749
|
+
chan, comm = model[iter]
|
|
750
|
+
if len(chan) > 0:
|
|
751
|
+
out[chan] = comm
|
|
752
|
+
|
|
753
|
+
iter = model.iter_next(iter)
|
|
754
|
+
|
|
755
|
+
data["results"][key] = out
|
|
756
|
+
|
|
757
|
+
def save_test(self, *args):
|
|
758
|
+
"""Save Test file."""
|
|
759
|
+
dialog = Gtk.FileChooserDialog(
|
|
760
|
+
title="Please choose a file", parent=self, action=Gtk.FileChooserAction.SAVE
|
|
761
|
+
)
|
|
762
|
+
dialog.add_buttons(
|
|
763
|
+
Gtk.STOCK_CANCEL,
|
|
764
|
+
Gtk.ResponseType.CANCEL,
|
|
765
|
+
Gtk.STOCK_OPEN,
|
|
766
|
+
Gtk.ResponseType.OK,
|
|
767
|
+
)
|
|
768
|
+
filter_text = Gtk.FileFilter()
|
|
769
|
+
filter_text.set_name("JSON files")
|
|
770
|
+
filter_text.add_mime_type("application/json")
|
|
771
|
+
dialog.add_filter(filter_text)
|
|
772
|
+
|
|
773
|
+
response = dialog.run()
|
|
774
|
+
if response == Gtk.ResponseType.OK:
|
|
775
|
+
ofile = dialog.get_filename()
|
|
776
|
+
data = self.get_test_data()
|
|
777
|
+
with open(ofile, 'w') as of:
|
|
778
|
+
json.dump(data, of, indent=3)
|
|
779
|
+
|
|
780
|
+
dialog.hide()
|
|
781
|
+
dialog.destroy()
|
|
782
|
+
|
|
783
|
+
def get_test_data(self):
|
|
784
|
+
"""Get the test data."""
|
|
785
|
+
data = {
|
|
786
|
+
"date": ITkDButils.get_db_date(),
|
|
787
|
+
"properties": {},
|
|
788
|
+
"results": {},
|
|
789
|
+
"comments": [],
|
|
790
|
+
"defects": []
|
|
791
|
+
}
|
|
792
|
+
self.get_test_header(data)
|
|
793
|
+
self.get_list_of_channels(data)
|
|
794
|
+
return data
|
|
795
|
+
|
|
796
|
+
def get_test_header(self, data):
|
|
797
|
+
"""Get Basic test data."""
|
|
798
|
+
SN = self.SN.get_text()
|
|
799
|
+
operator = self.operator.get_text()
|
|
800
|
+
machine = self.machine.get_text()
|
|
801
|
+
batch = self.batch.get_text()
|
|
802
|
+
|
|
803
|
+
if not SN or len(SN)==0:
|
|
804
|
+
SN = dbGtkUtils.get_a_value("Module Serial Number",
|
|
805
|
+
"Module serial Number is missing")
|
|
806
|
+
|
|
807
|
+
if len(operator) == 0 or len(machine) == 0 or len(batch) == 0:
|
|
808
|
+
values = dbGtkUtils.get_a_list_of_values(
|
|
809
|
+
"Missing Values",
|
|
810
|
+
["SN", "Operator", "Wire Bonder", "Wire Batch"],
|
|
811
|
+
defaults=[SN, operator, machine, batch],
|
|
812
|
+
)
|
|
813
|
+
if len(values) == 4:
|
|
814
|
+
SN, operator, machine, batch = values
|
|
815
|
+
else:
|
|
816
|
+
self.write_message("Something went wrong whilerequesting missing information.")
|
|
817
|
+
|
|
818
|
+
data["component"] = SN
|
|
819
|
+
data["properties"]["OPERATOR"] = operator
|
|
820
|
+
data["properties"]["BOND_MACHINE"] = machine
|
|
821
|
+
data["properties"]["BONDWIRE_BATCH"] = batch
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def upload_test(self, *args):
|
|
825
|
+
"""Upload test."""
|
|
826
|
+
if self.session is None:
|
|
827
|
+
self.pdb = ITkDBlogin.ITkDBlogin()
|
|
828
|
+
client = self.pdb.get_client()
|
|
829
|
+
if client is None:
|
|
830
|
+
dbGtkUtils.complain("Could not connect to DB with provided credentials.")
|
|
831
|
+
self.pdb.die()
|
|
832
|
+
|
|
833
|
+
else:
|
|
834
|
+
self.session = client
|
|
835
|
+
|
|
836
|
+
defaults = {
|
|
837
|
+
"institution": "IFIC",
|
|
838
|
+
"runNumber": "1",
|
|
839
|
+
"date": ITkDButils.get_db_date()
|
|
840
|
+
}
|
|
841
|
+
skeleton = ITkDButils.get_test_skeleton(self.session, "MODULE", "MODULE_WIRE_BONDING", defaults)
|
|
842
|
+
|
|
843
|
+
self.get_test_header(skeleton)
|
|
844
|
+
self.get_list_of_channels(skeleton)
|
|
845
|
+
self.get_unconnected(skeleton)
|
|
846
|
+
|
|
847
|
+
uploadW = UploadTest.UploadTest(self.session, payload=skeleton)
|
|
848
|
+
# uploadW.run()
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
def main():
|
|
852
|
+
"""Main entry."""
|
|
853
|
+
win = WireBond(title="WireBond")
|
|
854
|
+
win.connect("destroy", Gtk.main_quit)
|
|
855
|
+
try:
|
|
856
|
+
Gtk.main()
|
|
857
|
+
|
|
858
|
+
except KeyboardInterrupt:
|
|
859
|
+
print("Arrrgggg!!!")
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
if __name__ == "__main__":
|
|
863
|
+
main()
|