itkdb-gtk 0.10.9.dev3__py3-none-any.whl → 0.10.10__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 CHANGED
@@ -2,26 +2,34 @@
2
2
  """Wirebonding GUI for PSB."""
3
3
 
4
4
  import sys
5
+ import re
5
6
  import json
7
+ import copy
8
+ from pathlib import Path
9
+ from collections import namedtuple
6
10
  import gi
7
11
 
8
12
  gi.require_version("Gtk", "3.0")
9
- from gi.repository import Gtk, Gio, GLib
13
+ from gi.repository import Gtk, Gio, Gdk
10
14
 
11
15
  try:
12
16
  import itkdb_gtk
13
17
 
14
18
  except ImportError:
15
- from pathlib import Path
16
19
  cwd = Path(__file__).parent.parent
17
20
  sys.path.append(cwd.as_posix())
18
21
  import itkdb_gtk
19
-
22
+
20
23
  __HELP_LINK__="https://itkdb-gtk.docs.cern.ch/wirebondTest.html"
21
24
 
22
25
  from itkdb_gtk import dbGtkUtils
23
26
  from itkdb_gtk import ITkDBlogin, ITkDButils, UploadTest
24
27
 
28
+
29
+ #valid_channel = re.compile("(^[0-9]+)-([0-9]+)")
30
+ valid_channel = re.compile("^[0-9]+[\\s*\\,\\s,-[0-9]+]*")
31
+
32
+
25
33
  test_parameters = {
26
34
  "Repaired Row 1": "REPAIRED_FRONTEND_ROW1",
27
35
  "Failed Row 1": "FAILED_FRONTEND_ROW1",
@@ -84,21 +92,75 @@ module_param = {
84
92
  ],
85
93
  }
86
94
 
87
- def wire2strip(mod_par, irow, iwire):
88
- """Convert from wirebond index to strip_number."""
89
- for sensor in mod_par:
90
- for hyb in sensor:
91
- rng = hyb[irow]
92
- if iwire>= rng[0] and iwire<=rng[1]:
93
- if irow % 2:
94
- ichan = 2*(iwire-rng[0]) + 1
95
- else:
96
- ichan = 2*(iwire-rng[0])
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.
97
116
 
98
- return ichan
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
99
156
 
100
- return None
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))
101
162
 
163
+ return nitems
102
164
 
103
165
  def find_holes(chan_list, min_chan=0, max_chan=999999):
104
166
  """Find groups of consecutive channels."""
@@ -142,19 +204,41 @@ def find_holes(chan_list, min_chan=0, max_chan=999999):
142
204
  return holes
143
205
 
144
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
+
145
228
  class HybridHoles:
146
- """Holes in hybrid bomds.
229
+ """Holes in hybrid bonds.
147
230
 
148
231
  Holes are defined by a list [first_chan, n_chan].
149
232
  """
150
233
 
151
- def __init__(self, param, hid=0):
234
+ def __init__(self, param, win=None, hid=0):
152
235
  """Initialization.
153
236
 
154
237
  Args:
155
238
  param: Hybrid wirebon parameters.
156
239
 
157
240
  """
241
+ self.win = win
158
242
  self.id = hid
159
243
  self.param = param
160
244
  self.nchan = 0
@@ -163,116 +247,120 @@ class HybridHoles:
163
247
 
164
248
  self.holes = [[] for irow in range(4)]
165
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)
166
263
 
167
264
  def add_channel(self, irow, ichan)->bool:
168
265
  """Add a new channel in row.
169
266
 
170
267
  Args:
171
- irow: rown number
268
+ irow: row number
172
269
  ichan: channel number
173
270
 
174
271
  Returns:
175
272
  True if added, False otherwise.
176
-
273
+
177
274
  """
178
275
  first_chan = self.param[irow][0]
179
276
  last_chan = self.param[irow][1]
277
+ strip_row = int(irow/2)
278
+
279
+
180
280
  if isinstance(ichan, list) or isinstance(ichan, tuple):
181
281
  nadded = 0
182
282
  for ich in ichan:
183
- if ich >= first_chan and ich <= last_chan:
283
+ if first_chan <= ich <= last_chan:
184
284
  self.channels[irow].append(ich)
185
285
  nadded += 1
186
286
 
187
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
+
188
292
  return nadded>0
189
- else:
190
- if ichan >= first_chan and ichan <= last_chan:
191
- self.channels[irow].append(ichan)
192
- return True
193
- else:
194
- return False
195
293
 
196
- def get_n_unconnected(self):
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:
197
303
  """Count number of unconnected channels.
198
304
 
199
305
  Return a list, one item per row.
200
306
  """
201
- nchan = []
202
- for row in self.holes:
203
- nch = 0
204
- for h in row:
205
- nch += h[1]
206
-
207
- nchan.append(nch)
208
-
307
+ nchan = [len(C) for C in self.channels]
209
308
  return nchan
210
309
 
211
- def get_max_consecutive(self):
212
- """Returns the largest 'hole'."""
310
+ def get_max_consecutive_from_list(self, holes):
311
+ """Return max widht of holes."""
213
312
  mx_width = []
313
+ lst_holes = []
314
+ for irow, row in enumerate(holes):
315
+ if len(row) == 0:
316
+ mxW = 0
214
317
 
215
- for row in self.holes:
216
- mxW = -1
217
- for h in row:
218
- if h[1] > mxW:
219
- mxW = h[1]
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])
220
323
 
221
324
  mx_width.append(mxW)
222
325
 
223
- return mx_width
224
-
225
- def get_sensor_holes(self):
226
- """Compute holes in 'sensor' strips.
227
-
228
- Each hybrid has 2 sensor segments corresponding to
229
- rows (1,2) and (3, 4).
230
-
231
- Return a list of [sensor, hybrid, segment, ichan, width]
232
- """
233
- holes = []
234
- channels = [[], []]
235
- for irow, row in enumerate(self.channels):
236
- isegment = int(irow/2)
237
- for ich in row:
238
- rng = self.param[irow]
239
- if irow % 2:
240
- chan = 2*(ich-rng[0]) + 1
241
- else:
242
- chan = 2*(ich-rng[0])
243
-
244
- channels[isegment].append(chan)
245
-
246
- channels[isegment] = sorted(channels[isegment])
326
+ return mx_width, lst_holes
247
327
 
248
- for isegment, S in enumerate(channels):
249
- H = find_holes(S)
250
- if len(H)>0:
251
- out = [ [0, self.id, isegment, chan, width] for chan, width in H ]
252
- holes.extend(out)
253
328
 
254
- return holes
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
255
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
256
338
 
257
339
  class SensorHoles:
258
340
  """Holes in sensor."""
259
341
 
260
- def __init__(self, param, sid=0):
342
+ def __init__(self, param, win=None, sid=0):
261
343
  """Initialization.
262
344
 
263
345
  Args:
264
346
  param: sensor wirebon params
265
347
  """
348
+ self.win = win
266
349
  self.id = sid
267
350
  self.param = param
268
351
  self.nchan = 0
269
352
  self.nhybrid = len(param)
270
353
  self.hybrids = []
271
354
  for i, P in enumerate(param):
272
- H = HybridHoles(P, hid=i)
355
+ H = HybridHoles(P, hid=i, win=win)
273
356
  self.hybrids.append(H)
274
357
  self.nchan += H.nchan
275
358
 
359
+ def ready(self):
360
+ """Call when all channels are in."""
361
+ for H in self.hybrids:
362
+ H.ready()
363
+
276
364
  def get_n_hyb(self):
277
365
  """Return number of hybrids."""
278
366
  return len(self.hybrids)
@@ -286,15 +374,29 @@ class SensorHoles:
286
374
 
287
375
  This is ordered by row.
288
376
  """
289
- mx_width = [0, 0, 0, 0]
377
+ mx_width = [0 for x in range(4)]
290
378
  for hyb in self.hybrids:
291
379
  mxW = hyb.get_max_consecutive()
292
- for j in range(4):
293
- if mxW[j] > mx_width[j]:
294
- mx_width[j] = mxW[j]
380
+ for j, val in enumerate(mxW):
381
+ mx_width[j] = max(mx_width[j] , val)
295
382
 
296
383
  return mx_width
297
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
+
298
400
  def get_n_unconnected(self):
299
401
  """Count number of unconnected channels.
300
402
 
@@ -308,37 +410,30 @@ class SensorHoles:
308
410
 
309
411
  return nchan
310
412
 
311
- def get_sensor_holes(self):
312
- """Return holes sensor.
313
-
314
- Return a list of [sensor, hybrid, segment, ichan, width]
315
- """
316
- holes = []
317
- for hyb in self.hybrids:
318
- H = hyb.get_sensor_holes()
319
- for _, ih, isegment, ichan, width in H:
320
- holes.append([self.id, ih, isegment, ichan, width])
321
-
322
- return holes
323
-
324
413
 
325
414
  class ModuleHoles:
326
415
  """Holes in Modules."""
327
416
 
328
- def __init__(self, param):
417
+ def __init__(self, param, win=None):
329
418
  """Initialization.
330
419
 
331
420
  Args:
332
421
  param: module wirebond params
333
422
  """
423
+ self.win = win
334
424
  self.nsensor = len(param)
335
425
  self.nchan = 0
336
426
  self.sensors = []
337
427
  for i, P in enumerate(param):
338
- S = SensorHoles(P, sid=i)
428
+ S = SensorHoles(P, sid=i, win=win)
339
429
  self.sensors.append(S)
340
430
  self.nchan += S.nchan
341
431
 
432
+ def ready(self):
433
+ """Call when all channels are in."""
434
+ for S in self.sensors:
435
+ S.ready()
436
+
342
437
  def get_max_consecutive(self):
343
438
  """Max number of consecutive unconnected channels.
344
439
 
@@ -348,22 +443,21 @@ class ModuleHoles:
348
443
  for S in self.sensors:
349
444
  mxW = S.get_max_consecutive()
350
445
  for j in range(4):
351
- if mxW[j] > mx_width[j]:
352
- mx_width[j] = mxW[j]
446
+ mx_width[j] = max(mx_width[j], mxW[j])
353
447
 
354
448
  return mx_width
355
449
 
356
- def get_sensor_holes(self) -> list:
357
- """Return. holesin sensor strips.
358
-
359
- Return a list of [sensor, hybrid, segment, ichan, width]
360
- """
361
- holes = []
450
+ def get_max_sensor_consecutive(self):
451
+ """The maximum number of consecutive channels per strip row."""
452
+ mx_width = -1
453
+ module_holes = []
362
454
  for S in self.sensors:
363
- for _, ihyb, isegment, ichan, width in S.get_sensor_holes():
364
- holes.append([S.id, ihyb, isegment, ichan, width])
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
365
460
 
366
- return holes
367
461
 
368
462
  def get_n_unconnected(self) -> list:
369
463
  """Count number of unconnected channels.
@@ -416,16 +510,20 @@ def get_module_param(SN):
416
510
  class WireBond(dbGtkUtils.ITkDBWindow):
417
511
  """Main window."""
418
512
 
419
- def __init__(self, session, title="", help=__HELP_LINK__):
513
+ def __init__(self, session, title="", help_link=__HELP_LINK__):
420
514
  """Initialization."""
421
- super().__init__(title=title, session=session, help=help)
515
+ super().__init__(title=title, session=session, help_link=help_link)
422
516
  self.pdb = None
423
517
  self.models = {}
424
518
  self.holes = {}
425
- self.institute = "IFIC"
519
+ self.institute = self.pdb_user["institutions"][0]["code"]
426
520
  self.inst_combo = None
427
521
  self.module_SN = None
428
522
  self.alternativeID = None
523
+ self.combo = None
524
+ self.tree = None
525
+ self.lut = {}
526
+ self.module_type = None
429
527
  self.init_window()
430
528
 
431
529
  def init_window(self):
@@ -460,17 +558,33 @@ class WireBond(dbGtkUtils.ITkDBWindow):
460
558
 
461
559
  # Data panel
462
560
  grid = Gtk.Grid(column_spacing=5, row_spacing=1)
463
-
561
+
464
562
  # The shipment receiver
465
- institute = self.create_institute_combo()
563
+ institute = self.create_institute_combo(True)
466
564
  institute.connect("changed", self.on_institute)
467
565
  institute.set_tooltip_text("Select the Institute.")
468
566
  dbGtkUtils.set_combo_iter(institute, self.institute)
469
- grid.attach(Gtk.Label(label="Institute"), 0, 0, 1, 1)
567
+
568
+ lbl = Gtk.Label(label="Institute")
569
+ lbl.set_xalign(0)
570
+ grid.attach(lbl, 0, 0, 1, 1)
470
571
  grid.attach(institute, 1, 0, 1, 1)
471
572
  self.inst_combo = institute
472
-
473
-
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
+
474
588
  for i, tit in enumerate(["Operator", "Bond Machine", "Wire Batch", "SN", "Date"]):
475
589
  lbl = Gtk.Label(label=tit)
476
590
  lbl.set_xalign(0)
@@ -480,11 +594,11 @@ class WireBond(dbGtkUtils.ITkDBWindow):
480
594
  self.machine = dbGtkUtils.new_small_text_entry()
481
595
  self.batch = dbGtkUtils.new_small_text_entry()
482
596
  #self.SN = dbGtkUtils.new_small_text_entry()
483
-
597
+
484
598
  self.SN = itkdb_gtk.dbGtkUtils.TextEntry(small=True)
485
599
  self.SN.connect("text-changed", self.on_SN_changed)
486
600
 
487
-
601
+
488
602
  self.date = dbGtkUtils.TextEntry(small=True)
489
603
  self.date.entry.set_text(ITkDButils.get_db_date())
490
604
  self.date.connect("text_changed", self.new_date)
@@ -511,32 +625,135 @@ class WireBond(dbGtkUtils.ITkDBWindow):
511
625
 
512
626
  box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
513
627
  self.mainBox.pack_start(box, False, False, 0)
514
- dbGtkUtils.add_button_to_container(box, "Add Item",
515
- "Click to add a new item.",
628
+ dbGtkUtils.add_button_to_container(box, "Add Bond",
629
+ "Click to add a new bond or bond range.",
516
630
  self.add_item)
517
631
 
632
+ dbGtkUtils.add_button_to_container(box, "Remove Bond",
633
+ "Click to remove selected bond.",
634
+ self.remove_item)
635
+
518
636
  #
519
637
  # The text view and buffer
520
638
  #
521
639
  self.mainBox.pack_start(self.message_panel.frame, True, True, 0)
522
640
  self.write_message("wirebond GUI\n")
523
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
+
524
734
  def on_institute(self, combo):
525
735
  """Institute changed."""
526
736
  name = self.get_institute_from_combo(combo)
527
737
  if name:
528
738
  self.institute = name
529
-
739
+
530
740
  def on_SN_changed(self, entry, value):
531
741
  """New SN given. Ask in PDB,"""
532
742
  if len(value) <= 0:
533
- return None
534
-
743
+ return
535
744
 
536
745
  obj = itkdb_gtk.ITkDButils.get_DB_component(self.session, value)
537
- if obj is not None:
746
+ if obj is not None and obj["serialNumber"] is not None:
538
747
  entry.set_text(obj["serialNumber"])
539
- self.alternativeID = obj["alternativeIdentifier"]
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")
540
757
 
541
758
  else:
542
759
  itkdb_gtk.dbGtkUtils.complain("Invalid SN", value)
@@ -550,7 +767,7 @@ class WireBond(dbGtkUtils.ITkDBWindow):
550
767
  view_model = self.models[param]
551
768
  self.tree.set_model(view_model)
552
769
  else:
553
- self.write_message("Cannot find model for {}".format(param))
770
+ self.write_message("Cannot find model for {}\n".format(param))
554
771
 
555
772
  def create_combo(self):
556
773
  """Create the combo."""
@@ -614,8 +831,9 @@ class WireBond(dbGtkUtils.ITkDBWindow):
614
831
  def channel_edited(self, widget, path, text):
615
832
  """Handles edition in channel number cell."""
616
833
  if not text.isnumeric():
617
- dbGtkUtils.complain("Wrong channel number", "Invalid channel number: {}".format(text))
618
- return
834
+ if valid_channel.match(text) is None:
835
+ dbGtkUtils.complain("Wrong channel number", "Invalid channel number: {}".format(text))
836
+ return
619
837
 
620
838
  self.text_edited(0, path, text)
621
839
 
@@ -630,9 +848,52 @@ class WireBond(dbGtkUtils.ITkDBWindow):
630
848
  if d is not None:
631
849
  self.date.set_text(d)
632
850
 
633
- def button_pressed(self, *args):
851
+ def button_pressed(self, tree, event):
634
852
  """Button pressed."""
635
- pass
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)
636
897
 
637
898
  def add_item(self, *args):
638
899
  """Adds a new item in the current model."""
@@ -650,16 +911,72 @@ class WireBond(dbGtkUtils.ITkDBWindow):
650
911
  self.hide()
651
912
  self.destroy()
652
913
 
653
- def compute_unconnected(self):
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):
654
969
  """Compute number of unconnected."""
655
970
  try:
656
971
  param = get_module_param(self.SN.get_text())
657
972
  except ValueError as E:
658
973
  dbGtkUtils.complain("Wrong SN number", str(E))
659
-
660
- M = ModuleHoles(param=param)
974
+ return None
661
975
 
662
- for test in test_parameters.values():
976
+ M = ModuleHoles(param=param, win=self)
977
+
978
+ defects = []
979
+ for test, values in results.items():
663
980
  if test.find("FAILED") < 0:
664
981
  continue
665
982
  if test.find("ROW") < 0:
@@ -668,56 +985,86 @@ class WireBond(dbGtkUtils.ITkDBWindow):
668
985
  irow = int(test[-1]) - 1
669
986
 
670
987
  # Get list of all channels with wirebond notation.
671
- model = self.models[test]
672
- it = model.get_iter_first()
673
- out = []
674
- while it:
675
- chan, _ = model[it]
676
- if len(chan) > 0:
677
- out.append(int(chan))
678
-
679
- it = model.iter_next(it)
680
-
988
+ out = [int(x) for x in values.keys()]
681
989
  # Translate to sensor, hybrids, etc.
682
990
  for S in M.sensors:
683
991
  for H in S.hybrids:
684
992
  H.add_channel(irow, out)
685
993
  H.holes[irow] = find_holes(H.channels[irow])
686
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
+ )
687
1006
 
688
1007
  # Now get sensor strips.
1008
+ M.ready()
689
1009
  unconnected = M.get_n_unconnected()
690
1010
  mx_consecutive = M.get_max_consecutive()
691
- module_holes = M.get_sensor_holes()
1011
+ mx_sensor_width, module_holes = M.get_max_sensor_consecutive()
692
1012
 
693
1013
  out = {}
1014
+ out["comments"] = []
1015
+ out["defects"] = defects
694
1016
  for irow in range(4):
695
1017
  key = "MAX_CONT_UNCON_ROW{}".format(irow+1)
696
1018
  out[key] = mx_consecutive[irow]
697
1019
 
698
- mxW = 0
699
1020
  self.write_message("Found {} clusters of unconnected strips in sensor.\n".format(len(module_holes)))
700
1021
  for H in module_holes:
701
- self.write_message("{}\n".format(str(H)))
702
- if H[-1] > mxW:
703
- mxW = H[-1]
704
-
705
- if mxW > 0:
706
- self.write_message("Max width: {}". format(mxW))
707
-
708
- out["MAX_UNCON_SENSOR_CHAN"] = mxW
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
+
709
1032
  nstrips = 0
710
1033
  for v in unconnected:
711
1034
  nstrips += v
712
1035
 
713
- out["TOTAL_PERC_UNCON_SENSOR_CHAN"] = nstrips/M.nchan
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))
714
1041
 
715
1042
  return out
716
1043
 
717
1044
  def get_unconnected(self, skeleton):
718
1045
  """Fill the test DTO with unconnected information."""
719
- out = self.compute_unconnected()
1046
+ out = self.compute_unconnected(skeleton["results"])
1047
+ if out is None:
1048
+ raise ValueError("Wrong SN")
1049
+
720
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
+
721
1068
  skeleton["results"][key] = val
722
1069
 
723
1070
  def read_file(self, *args):
@@ -754,8 +1101,8 @@ class WireBond(dbGtkUtils.ITkDBWindow):
754
1101
  try:
755
1102
  dbGtkUtils.set_combo_iter(self.inst_combo, data["institution"])
756
1103
  except KeyError:
757
- self.write_message("institution value is not in the loaded file.")
758
-
1104
+ self.write_message("institution value is not in the loaded file\n.")
1105
+
759
1106
  self.operator.set_text(data["properties"]["OPERATOR"])
760
1107
  self.machine.set_text(data["properties"]["BOND_MACHINE"])
761
1108
  self.batch.set_text(data["properties"]["BONDWIRE_BATCH"])
@@ -772,17 +1119,54 @@ class WireBond(dbGtkUtils.ITkDBWindow):
772
1119
  def get_list_of_channels(self, data):
773
1120
  """Creates the lists of channels."""
774
1121
  for key, model in self.models.items():
775
- iter = model.get_iter_first()
1122
+ lv_iter = model.get_iter_first()
776
1123
  out = {}
777
- while iter:
778
- chan, comm = model[iter]
1124
+ while lv_iter:
1125
+ chan, comm = model[lv_iter]
779
1126
  if len(chan) > 0:
780
1127
  out[chan] = comm
781
1128
 
782
- iter = model.iter_next(iter)
1129
+ lv_iter = model.iter_next(lv_iter)
783
1130
 
784
1131
  data["results"][key] = out
785
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
+
786
1170
  def save_test(self, *args):
787
1171
  """Save Test file."""
788
1172
  dialog = Gtk.FileChooserDialog(
@@ -814,6 +1198,7 @@ class WireBond(dbGtkUtils.ITkDBWindow):
814
1198
  data = {
815
1199
  "institution": self.institute,
816
1200
  "date": ITkDButils.get_db_date(),
1201
+ "runNumber": "1",
817
1202
  "properties": {},
818
1203
  "results": {},
819
1204
  "comments": [],
@@ -843,7 +1228,7 @@ class WireBond(dbGtkUtils.ITkDBWindow):
843
1228
  if len(values) == 4:
844
1229
  SN, operator, machine = values
845
1230
  else:
846
- self.write_message("Something went wrong while requesting missing information.")
1231
+ self.write_message("Something went wrong while requesting missing information.\n")
847
1232
 
848
1233
  data["component"] = SN
849
1234
  data["properties"]["OPERATOR"] = operator
@@ -852,7 +1237,7 @@ class WireBond(dbGtkUtils.ITkDBWindow):
852
1237
  data["institution"] = self.institute
853
1238
  if data["runNumber"] == "-1":
854
1239
  data["runNumber"] = "1"
855
-
1240
+
856
1241
  data["date"] = self.date.get_text()
857
1242
 
858
1243
 
@@ -878,7 +1263,15 @@ class WireBond(dbGtkUtils.ITkDBWindow):
878
1263
 
879
1264
  self.get_test_header(skeleton)
880
1265
  self.get_list_of_channels(skeleton)
881
- self.get_unconnected(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
882
1275
 
883
1276
  uploadW = UploadTest.UploadTest(self.session, payload=skeleton)
884
1277
  # uploadW.run()
@@ -895,7 +1288,7 @@ def main():
895
1288
 
896
1289
  client.user_gui = dlg
897
1290
 
898
-
1291
+
899
1292
  win = WireBond(client, title="WireBond")
900
1293
  win.connect("destroy", Gtk.main_quit)
901
1294
  win.show_all()
@@ -906,6 +1299,6 @@ def main():
906
1299
  print("Arrrgggg!!!")
907
1300
 
908
1301
  dlg.die()
909
-
1302
+
910
1303
  if __name__ == "__main__":
911
1304
  main()