itkdb-gtk 0.0.16.dev7__py3-none-any.whl → 0.0.16.dev20__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/WireBondGui.py +535 -25
- itkdb_gtk/__init__.py +6 -1
- itkdb_gtk/dashBoard.py +1 -1
- itkdb_gtk/dbGtkUtils.py +62 -18
- {itkdb_gtk-0.0.16.dev7.dist-info → itkdb_gtk-0.0.16.dev20.dist-info}/METADATA +4 -1
- {itkdb_gtk-0.0.16.dev7.dist-info → itkdb_gtk-0.0.16.dev20.dist-info}/RECORD +9 -9
- {itkdb_gtk-0.0.16.dev7.dist-info → itkdb_gtk-0.0.16.dev20.dist-info}/WHEEL +1 -1
- {itkdb_gtk-0.0.16.dev7.dist-info → itkdb_gtk-0.0.16.dev20.dist-info}/entry_points.txt +1 -0
- {itkdb_gtk-0.0.16.dev7.dist-info → itkdb_gtk-0.0.16.dev20.dist-info}/top_level.txt +0 -0
itkdb_gtk/WireBondGui.py
CHANGED
|
@@ -10,7 +10,7 @@ from gi.repository import Gtk, Gio, GLib
|
|
|
10
10
|
|
|
11
11
|
try:
|
|
12
12
|
import itkdb_gtk
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
except ImportError:
|
|
15
15
|
from pathlib import Path
|
|
16
16
|
cwd = Path(sys.argv[0]).parent.parent
|
|
@@ -34,6 +34,380 @@ test_parameters = {
|
|
|
34
34
|
"Failed Module->Frame": "FAILED_MODULE_TO_FRAME"
|
|
35
35
|
}
|
|
36
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
|
+
|
|
37
411
|
|
|
38
412
|
class WireBond(Gtk.Window):
|
|
39
413
|
"""Main window."""
|
|
@@ -44,6 +418,7 @@ class WireBond(Gtk.Window):
|
|
|
44
418
|
self.pdb = None
|
|
45
419
|
self.session = session
|
|
46
420
|
self.models = {}
|
|
421
|
+
self.holes = {}
|
|
47
422
|
self.prepare_window()
|
|
48
423
|
|
|
49
424
|
def prepare_window(self):
|
|
@@ -70,6 +445,15 @@ class WireBond(Gtk.Window):
|
|
|
70
445
|
button.connect("clicked", self.save_test)
|
|
71
446
|
self.hb.pack_end(button)
|
|
72
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
|
+
|
|
73
457
|
# Create the main box and add it to the window
|
|
74
458
|
self.mainBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
75
459
|
self.mainBox.set_property("margin-left", 6)
|
|
@@ -78,7 +462,7 @@ class WireBond(Gtk.Window):
|
|
|
78
462
|
|
|
79
463
|
# Data panel
|
|
80
464
|
grid = Gtk.Grid(column_spacing=5, row_spacing=1)
|
|
81
|
-
for i, tit in enumerate(["Operator", "Bond Machine", "Wire Batch", "SN"]):
|
|
465
|
+
for i, tit in enumerate(["Operator", "Bond Machine", "Wire Batch", "SN", "Date"]):
|
|
82
466
|
lbl = Gtk.Label(label=tit)
|
|
83
467
|
lbl.set_xalign(0)
|
|
84
468
|
grid.attach(lbl, 0, i, 1, 1)
|
|
@@ -87,11 +471,14 @@ class WireBond(Gtk.Window):
|
|
|
87
471
|
self.machine = dbGtkUtils.new_small_text_entry()
|
|
88
472
|
self.batch = dbGtkUtils.new_small_text_entry()
|
|
89
473
|
self.SN = dbGtkUtils.new_small_text_entry()
|
|
474
|
+
self.date = dbGtkUtils.TextEntry(small=True)
|
|
475
|
+
self.date.connect("text_changed", self.new_date)
|
|
90
476
|
|
|
91
|
-
grid.attach(self.operator,
|
|
92
|
-
grid.attach(self.machine,
|
|
93
|
-
grid.attach(self.batch,
|
|
94
|
-
grid.attach(self.SN,
|
|
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)
|
|
95
482
|
|
|
96
483
|
self.mainBox.pack_start(grid, True, True, 0)
|
|
97
484
|
|
|
@@ -118,7 +505,7 @@ class WireBond(Gtk.Window):
|
|
|
118
505
|
#
|
|
119
506
|
self.message_panel = dbGtkUtils.MessagePanel(size=100)
|
|
120
507
|
self.mainBox.pack_start(self.message_panel.frame, True, True, 0)
|
|
121
|
-
self.write_message("wirebond GUI")
|
|
508
|
+
self.write_message("wirebond GUI\n")
|
|
122
509
|
|
|
123
510
|
# The button box
|
|
124
511
|
btnBox = Gtk.ButtonBox(orientation=Gtk.Orientation.HORIZONTAL)
|
|
@@ -212,22 +599,30 @@ class WireBond(Gtk.Window):
|
|
|
212
599
|
"""Handles edition in comment."""
|
|
213
600
|
self.text_edited(1, path, text)
|
|
214
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
|
+
|
|
215
609
|
def button_pressed(self, *args):
|
|
610
|
+
"""Button pressed."""
|
|
216
611
|
pass
|
|
217
612
|
|
|
218
613
|
def add_item(self, *args):
|
|
219
614
|
"""Adds a new item in the current model."""
|
|
220
615
|
out = dbGtkUtils.get_a_list_of_values("Add Item", ["Channel", "Comment"])
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
616
|
+
if len(out) == 2:
|
|
617
|
+
model = self.tree.get_model()
|
|
618
|
+
model[-1] = out
|
|
619
|
+
model.append(["", ""])
|
|
225
620
|
|
|
226
621
|
def quit(self, *args):
|
|
227
622
|
"""Quits the application."""
|
|
228
623
|
if self.pdb:
|
|
229
624
|
self.pdb.die()
|
|
230
|
-
|
|
625
|
+
|
|
231
626
|
self.hide()
|
|
232
627
|
self.destroy()
|
|
233
628
|
|
|
@@ -235,6 +630,116 @@ class WireBond(Gtk.Window):
|
|
|
235
630
|
"""Writes text to Text Viewer."""
|
|
236
631
|
self.message_panel.write_message(text)
|
|
237
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
|
+
|
|
238
743
|
def get_list_of_channels(self, data):
|
|
239
744
|
"""Creates the lists of channels."""
|
|
240
745
|
for key, model in self.models.items():
|
|
@@ -244,11 +749,11 @@ class WireBond(Gtk.Window):
|
|
|
244
749
|
chan, comm = model[iter]
|
|
245
750
|
if len(chan) > 0:
|
|
246
751
|
out[chan] = comm
|
|
247
|
-
|
|
752
|
+
|
|
248
753
|
iter = model.iter_next(iter)
|
|
249
754
|
|
|
250
755
|
data["results"][key] = out
|
|
251
|
-
|
|
756
|
+
|
|
252
757
|
def save_test(self, *args):
|
|
253
758
|
"""Save Test file."""
|
|
254
759
|
dialog = Gtk.FileChooserDialog(
|
|
@@ -264,17 +769,17 @@ class WireBond(Gtk.Window):
|
|
|
264
769
|
filter_text.set_name("JSON files")
|
|
265
770
|
filter_text.add_mime_type("application/json")
|
|
266
771
|
dialog.add_filter(filter_text)
|
|
267
|
-
|
|
772
|
+
|
|
268
773
|
response = dialog.run()
|
|
269
774
|
if response == Gtk.ResponseType.OK:
|
|
270
775
|
ofile = dialog.get_filename()
|
|
271
776
|
data = self.get_test_data()
|
|
272
777
|
with open(ofile, 'w') as of:
|
|
273
778
|
json.dump(data, of, indent=3)
|
|
274
|
-
|
|
779
|
+
|
|
275
780
|
dialog.hide()
|
|
276
781
|
dialog.destroy()
|
|
277
|
-
|
|
782
|
+
|
|
278
783
|
def get_test_data(self):
|
|
279
784
|
"""Get the test data."""
|
|
280
785
|
data = {
|
|
@@ -287,7 +792,7 @@ class WireBond(Gtk.Window):
|
|
|
287
792
|
self.get_test_header(data)
|
|
288
793
|
self.get_list_of_channels(data)
|
|
289
794
|
return data
|
|
290
|
-
|
|
795
|
+
|
|
291
796
|
def get_test_header(self, data):
|
|
292
797
|
"""Get Basic test data."""
|
|
293
798
|
SN = self.SN.get_text()
|
|
@@ -296,7 +801,7 @@ class WireBond(Gtk.Window):
|
|
|
296
801
|
batch = self.batch.get_text()
|
|
297
802
|
|
|
298
803
|
if not SN or len(SN)==0:
|
|
299
|
-
SN = dbGtkUtils.get_a_value("Module Serial Number",
|
|
804
|
+
SN = dbGtkUtils.get_a_value("Module Serial Number",
|
|
300
805
|
"Module serial Number is missing")
|
|
301
806
|
|
|
302
807
|
if len(operator) == 0 or len(machine) == 0 or len(batch) == 0:
|
|
@@ -315,7 +820,7 @@ class WireBond(Gtk.Window):
|
|
|
315
820
|
data["properties"]["BOND_MACHINE"] = machine
|
|
316
821
|
data["properties"]["BONDWIRE_BATCH"] = batch
|
|
317
822
|
|
|
318
|
-
|
|
823
|
+
|
|
319
824
|
def upload_test(self, *args):
|
|
320
825
|
"""Upload test."""
|
|
321
826
|
if self.session is None:
|
|
@@ -327,7 +832,7 @@ class WireBond(Gtk.Window):
|
|
|
327
832
|
|
|
328
833
|
else:
|
|
329
834
|
self.session = client
|
|
330
|
-
|
|
835
|
+
|
|
331
836
|
defaults = {
|
|
332
837
|
"institution": "IFIC",
|
|
333
838
|
"runNumber": "1",
|
|
@@ -335,19 +840,24 @@ class WireBond(Gtk.Window):
|
|
|
335
840
|
}
|
|
336
841
|
skeleton = ITkDButils.get_test_skeleton(self.session, "MODULE", "MODULE_WIRE_BONDING", defaults)
|
|
337
842
|
|
|
338
|
-
self.get_test_header(skeleton)
|
|
843
|
+
self.get_test_header(skeleton)
|
|
339
844
|
self.get_list_of_channels(skeleton)
|
|
845
|
+
self.get_unconnected(skeleton)
|
|
340
846
|
|
|
341
847
|
uploadW = UploadTest.UploadTest(self.session, payload=skeleton)
|
|
342
848
|
# uploadW.run()
|
|
343
849
|
|
|
344
850
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
win = WireBond("WireBond")
|
|
851
|
+
def main():
|
|
852
|
+
"""Main entry."""
|
|
853
|
+
win = WireBond(title="WireBond")
|
|
348
854
|
win.connect("destroy", Gtk.main_quit)
|
|
349
855
|
try:
|
|
350
856
|
Gtk.main()
|
|
351
857
|
|
|
352
858
|
except KeyboardInterrupt:
|
|
353
859
|
print("Arrrgggg!!!")
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
if __name__ == "__main__":
|
|
863
|
+
main()
|
itkdb_gtk/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
__version__ = "0.0.16.
|
|
1
|
+
__version__ = "0.0.16.dev20"
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
def dash_board():
|
|
@@ -45,3 +45,8 @@ def uploadModuleIV():
|
|
|
45
45
|
"""Upload IV files of single and double modules"""
|
|
46
46
|
from .UploadModuleIV import main
|
|
47
47
|
main()
|
|
48
|
+
|
|
49
|
+
def wirebondTest():
|
|
50
|
+
"""Inputs data and eventually upload wirebod test."""
|
|
51
|
+
from .WireBondGui import main
|
|
52
|
+
main()
|
itkdb_gtk/dashBoard.py
CHANGED
|
@@ -180,7 +180,7 @@ class DashWindow(dbGtkUtils.ITkDBWindow):
|
|
|
180
180
|
return
|
|
181
181
|
|
|
182
182
|
self.mask |= bt
|
|
183
|
-
W = WireBondGui.WireBond(session=self.session)
|
|
183
|
+
W = WireBondGui.WireBond(session=self.session, title="Wirebond")
|
|
184
184
|
W.connect("destroy", self.app_closed, bitn)
|
|
185
185
|
|
|
186
186
|
def app_closed(self, *args):
|
itkdb_gtk/dbGtkUtils.py
CHANGED
|
@@ -11,8 +11,26 @@ import numpy as np
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
gi.require_version("Gtk", "3.0")
|
|
14
|
-
from gi.repository import Gtk, Gio, GLib
|
|
14
|
+
from gi.repository import Gtk, GObject, Gio, GLib
|
|
15
15
|
|
|
16
|
+
def parse_date(txt):
|
|
17
|
+
"""Parse a date."""
|
|
18
|
+
try:
|
|
19
|
+
return dateutil.parser.parse(txt, fuzzy=False)
|
|
20
|
+
|
|
21
|
+
except Exception:
|
|
22
|
+
return None
|
|
23
|
+
def parse_date_as_string(txt):
|
|
24
|
+
"""Parse data and return DB compatible string."""
|
|
25
|
+
D = parse_date(txt)
|
|
26
|
+
if D is None:
|
|
27
|
+
return D
|
|
28
|
+
|
|
29
|
+
out = D.isoformat(timespec='milliseconds')
|
|
30
|
+
if out[-1] not in ['zZ']:
|
|
31
|
+
out += 'Z'
|
|
32
|
+
|
|
33
|
+
return out
|
|
16
34
|
|
|
17
35
|
def is_a_date(txt):
|
|
18
36
|
"""check tha the input string is a date."""
|
|
@@ -34,6 +52,16 @@ def new_small_text_entry():
|
|
|
34
52
|
style_context.add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_SETTINGS)
|
|
35
53
|
return entry
|
|
36
54
|
|
|
55
|
+
def set_entry_style(container):
|
|
56
|
+
"""Set max entry."""
|
|
57
|
+
provider = Gtk.CssProvider()
|
|
58
|
+
style_context = container.get_style_context()
|
|
59
|
+
font_size = 2.25*style_context.get_property("font-size", 0)
|
|
60
|
+
css = "{} {{ min-height: {}px; }}".format(container.get_name(), font_size)
|
|
61
|
+
provider.load_from_data(css.encode())
|
|
62
|
+
style_context.add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_SETTINGS)
|
|
63
|
+
return container
|
|
64
|
+
|
|
37
65
|
def set_combo_iter(combo, txt, col=0):
|
|
38
66
|
"""Set scombo active iter to that containing txt in column col."""
|
|
39
67
|
model = combo.get_model()
|
|
@@ -47,17 +75,6 @@ def set_combo_iter(combo, txt, col=0):
|
|
|
47
75
|
iter = model.iter_next(iter)
|
|
48
76
|
|
|
49
77
|
|
|
50
|
-
def set_entry_style(container):
|
|
51
|
-
"""Set max entry."""
|
|
52
|
-
provider = Gtk.CssProvider()
|
|
53
|
-
print(container.get_name())
|
|
54
|
-
style_context = container.get_style_context()
|
|
55
|
-
font_size = 2.25*style_context.get_property("font-size", 0)
|
|
56
|
-
css = "{} {{ min-height: {}px; }}".format(container.get_name(), font_size)
|
|
57
|
-
provider.load_from_data(css.encode())
|
|
58
|
-
style_context.add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_SETTINGS)
|
|
59
|
-
|
|
60
|
-
|
|
61
78
|
def is_iterable(obj):
|
|
62
79
|
"""Tell if an object is iterable. Strings are not considered iterables."""
|
|
63
80
|
if isinstance(obj, Iterable):
|
|
@@ -166,11 +183,16 @@ def ask_for_confirmation(main_title, second_text, parent=None):
|
|
|
166
183
|
return (out == Gtk.ResponseType.OK)
|
|
167
184
|
|
|
168
185
|
|
|
169
|
-
class TextEntry(
|
|
186
|
+
class TextEntry(GObject.GObject):
|
|
170
187
|
"""Create a Gtk text entry/view object."""
|
|
188
|
+
__gsignals__ = {
|
|
189
|
+
"text_changed": (GObject.SIGNAL_RUN_FIRST, None, (str,))
|
|
190
|
+
}
|
|
171
191
|
|
|
172
|
-
def __init__(self, n_lines=1):
|
|
192
|
+
def __init__(self, n_lines=1, small=False):
|
|
173
193
|
"""Init."""
|
|
194
|
+
GObject.GObject.__init__(self)
|
|
195
|
+
self.tmp_txt = ""
|
|
174
196
|
self.nlines = n_lines
|
|
175
197
|
if self.nlines > 1:
|
|
176
198
|
self.widget = Gtk.Frame()
|
|
@@ -184,9 +206,31 @@ class TextEntry(object):
|
|
|
184
206
|
scrolled.add(self.entry)
|
|
185
207
|
|
|
186
208
|
else:
|
|
187
|
-
|
|
209
|
+
if small:
|
|
210
|
+
self.widget = new_small_text_entry()
|
|
211
|
+
else:
|
|
212
|
+
self.widget = Gtk.Entry()
|
|
213
|
+
|
|
214
|
+
self.widget.connect("focus-in-event", self.on_enter)
|
|
215
|
+
self.widget.connect("focus-out-event", self.on_leave)
|
|
216
|
+
|
|
188
217
|
self.entry = self.widget
|
|
189
218
|
|
|
219
|
+
def do_my_signal(self, *args):
|
|
220
|
+
"""Signal handler."""
|
|
221
|
+
pass
|
|
222
|
+
|
|
223
|
+
def on_enter(self, *args):
|
|
224
|
+
"""On enter."""
|
|
225
|
+
self.tmp_txt = self.widget.get_text().strip()
|
|
226
|
+
return False
|
|
227
|
+
|
|
228
|
+
def on_leave(self, *args):
|
|
229
|
+
"""On leave."""
|
|
230
|
+
val = self.widget.get_text().strip()
|
|
231
|
+
if val != self.tmp_txt:
|
|
232
|
+
self.emit("text_changed", val)
|
|
233
|
+
|
|
190
234
|
def get_text(self):
|
|
191
235
|
"""Return the text."""
|
|
192
236
|
if self.nlines > 1:
|
|
@@ -197,7 +241,7 @@ class TextEntry(object):
|
|
|
197
241
|
|
|
198
242
|
else:
|
|
199
243
|
return self.entry.get_text()
|
|
200
|
-
|
|
244
|
+
|
|
201
245
|
def set_text(self, text):
|
|
202
246
|
"""Sets text."""
|
|
203
247
|
if self.nlines > 1:
|
|
@@ -306,7 +350,7 @@ def get_a_list_of_values(main_title, labels, defaults=None, second_text=None, pa
|
|
|
306
350
|
entry.set_text(defaults[i])
|
|
307
351
|
except Exception:
|
|
308
352
|
pass
|
|
309
|
-
|
|
353
|
+
|
|
310
354
|
vbox.pack_start(entry.widget, False, False, 0)
|
|
311
355
|
entries.append(entry)
|
|
312
356
|
|
|
@@ -378,7 +422,7 @@ class MessagePanel(object):
|
|
|
378
422
|
|
|
379
423
|
# A scroll window with the text view
|
|
380
424
|
scrolled = Gtk.ScrolledWindow()
|
|
381
|
-
scrolled.set_policy(Gtk.PolicyType.
|
|
425
|
+
scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
|
|
382
426
|
scrolled.add(self.text_view)
|
|
383
427
|
box.pack_start(scrolled, True, True, 0)
|
|
384
428
|
self.frame = frame
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: itkdb_gtk
|
|
3
|
-
Version: 0.0.16.
|
|
3
|
+
Version: 0.0.16.dev20
|
|
4
4
|
Summary: A collection of Gtk based GUI to access ITkDB.
|
|
5
5
|
Author-email: Carlos Lacasta <carlos.lacasta@cern.ch>
|
|
6
6
|
Project-URL: Homepage, https://gitlab.cern.ch/atlas-itk/sw/db/itk-pdb-gtk-gui-utils
|
|
@@ -10,10 +10,13 @@ Classifier: Operating System :: OS Independent
|
|
|
10
10
|
Requires-Python: >=3.7
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
Requires-Dist: itkdb >=0.4.0
|
|
13
|
+
Requires-Dist: numpy
|
|
14
|
+
Requires-Dist: matplotlib
|
|
13
15
|
Requires-Dist: openpyxl
|
|
14
16
|
Requires-Dist: pyserial
|
|
15
17
|
Requires-Dist: python-dateutil
|
|
16
18
|
Requires-Dist: requests
|
|
19
|
+
Requires-Dist: PyGObject
|
|
17
20
|
|
|
18
21
|
# Interaction with the ITk PDB.
|
|
19
22
|
|
|
@@ -13,15 +13,15 @@ itkdb_gtk/UploadModuleIV.py,sha256=JSvfQSjMxCEy_9KtcydIjpSSCBJ6xJbCwo24-xszabk,2
|
|
|
13
13
|
itkdb_gtk/UploadMultipleTests.py,sha256=hI-LK_WWFCoUI8AQTEfrjoHYdYCbOru9DuBUA-R2id4,19730
|
|
14
14
|
itkdb_gtk/UploadPetalInformation.py,sha256=IsZGa_i6jW8lEUnJOhPFmq-GCemLg_nhlHuQlJQMbjc,22210
|
|
15
15
|
itkdb_gtk/UploadTest.py,sha256=AwMHwuS2PzyuOGsinyoFcuHZ2m_ZsNktHudp2667_Q4,13590
|
|
16
|
-
itkdb_gtk/WireBondGui.py,sha256=
|
|
17
|
-
itkdb_gtk/__init__.py,sha256=
|
|
18
|
-
itkdb_gtk/dashBoard.py,sha256=
|
|
19
|
-
itkdb_gtk/dbGtkUtils.py,sha256=
|
|
16
|
+
itkdb_gtk/WireBondGui.py,sha256=v9oWHIBCU2tbnje2T_3fystPNlks366Dk269pmX9dds,25747
|
|
17
|
+
itkdb_gtk/__init__.py,sha256=uTLh_Z2X49YBX2Bca0JVJxniEw0OiVZY45EEgDDsecM,963
|
|
18
|
+
itkdb_gtk/dashBoard.py,sha256=mguE5QXvCwD4wZgHx5PViRHrY3mgoJh4ijiyz1P2DfA,5760
|
|
19
|
+
itkdb_gtk/dbGtkUtils.py,sha256=xVqRgtg7F7tua41iqHVLFCjgqo4lRVaDD063409i8q0,25120
|
|
20
20
|
itkdb_gtk/readAVSdata.py,sha256=SvHWFoqvlrEDGKeVaQEPGxsjcNY21KItMSVuWE5q05E,19457
|
|
21
21
|
itkdb_gtk/readGoogleSheet.py,sha256=UCSfFCQncc0pWxqfCKTaa0K4HkQmwOI07eErepOMlGU,2682
|
|
22
22
|
itkdb_gtk/untrash_component.py,sha256=fZWanuPww1RLJ-Fduso4EqfLlcBsd5ryJymr1xJZEUM,756
|
|
23
|
-
itkdb_gtk-0.0.16.
|
|
24
|
-
itkdb_gtk-0.0.16.
|
|
25
|
-
itkdb_gtk-0.0.16.
|
|
26
|
-
itkdb_gtk-0.0.16.
|
|
27
|
-
itkdb_gtk-0.0.16.
|
|
23
|
+
itkdb_gtk-0.0.16.dev20.dist-info/METADATA,sha256=1TRkMOqgjFif1VgkAs-zkAVmaK7UYr-CArc4c3c57aw,3154
|
|
24
|
+
itkdb_gtk-0.0.16.dev20.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
25
|
+
itkdb_gtk-0.0.16.dev20.dist-info/entry_points.txt,sha256=FuSmtQZK5jEzNomHiJqdFZAJZoRueejwoWMg53aZSu0,371
|
|
26
|
+
itkdb_gtk-0.0.16.dev20.dist-info/top_level.txt,sha256=KVRrH4OS8ovzNR9bvADE0ABn5bNpSk987tuH0jCfkbU,10
|
|
27
|
+
itkdb_gtk-0.0.16.dev20.dist-info/RECORD,,
|
|
File without changes
|