All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
ICARUSgeometryChecker.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # Changes:
4 # 20200521 (petrillo@slac.stanford.edu) [v2.0]
5 # updated to Python 3
6 # 20200707 (petrillo@slac.stanford.edu) [v2.1]
7 # added wire plane depth alignment tests
8 #
9 
10 __doc__ = """
11 Performs simple checks on ICARUS geometry.
12 """
13 __version__ = "%(prog)s 2.1"
14 
15 import itertools
16 import logging
17 
18 
19 # ##############################################################################
21  class BadIterator:
22  def __iter__(self): return self
23  def next(self): raise StopIteration
24  # class BadIterator
25 
26  def __init__(self, iterable):
27  self.outerIter = iter(iterable)
29  # __init__()
30 
31  def __iter__(self): return self
32 
33  def next(self):
34  while True:
35  try: return next(self.innerIter)
36  except StopIteration: pass
37  self.innerIter = iter(next(self.outerIter)) # let StopIteration through
38  # while
39  # next()
40 
41 # class NestedIteration
42 
43 
44 # ##############################################################################
45 def GeoPairToString(pair):
46  return " - ".join(map(lambda obj: str(obj.ID()), pair))
47 
48 def BoxToString(box): return "%s -- %s" % (box.Min(), box.Max())
49 
50 
51 # ------------------------------------------------------------------------------
52 def boxID(box, default):
53  try: return box.ID()
54  except AttributeError: return default
55 # boxID()
56 
57 
58 # ------------------------------------------------------------------------------
59 def CheckGeoObjOverlaps(objs, objName = None, extractBox = None):
60  """Returns a list of pairs of overlapping objects (empty if no overlap)."""
61  if objName is None: objName = objs.__class__.__name__
62  overlaps = []
63  for (iObj1, obj1), (iObj2, obj2) in itertools.combinations(enumerate(objs), 2):
64  box1 = obj1 if extractBox is None else extractBox(obj1)
65  box2 = obj2 if extractBox is None else extractBox(obj2)
66  if not box2.Overlaps(box1): continue
67  logging.error("%s %s (%s to %s) and %s (%s to %s) overlap!", objName,
68  boxID(box1, iObj1), box1.Min(), box1.Max(),
69  boxID(box2, iObj2), box2.Min(), box2.Max(),
70  )
71  overlaps.append( ( obj1, obj2, ) )
72  # for boxes
73  return overlaps
74 # CheckGeoObjOverlaps()
75 
76 
77 # ------------------------------------------------------------------------------
78 def checkPlaneAlignment(planes, tolerance = 0.1):
79  """Check alignment in y and z position of planes on the same x.
80 
81  Returns triples (distance, first plane, second plane) for each misaligned
82  pair of planes.
83  """
84  assert tolerance >= 0.0
85  PlanesByX = groupPlanesByX(planes, sortBy = 'z')
86  inconsistentPlanes = {}
87  for planesOnX in PlanesByX:
88  if len(planesOnX) < 2: continue
89  planeIter = iter(planesOnX)
90  refPlane = next(planeIter)
91  for nextPlane in planeIter:
92  refBox = refPlane.BoundingBox()
93  nextBox = nextPlane.BoundingBox()
94  #
95  # 1. check plane alignment on z direction
96  #
97  distance = nextBox.MinZ() - refBox.MaxZ()
98  if abs(distance) > tolerance:
99  logging.error(
100  "Planes on x=%s are misaligned along z: ( %g -- %g ) (%s) vs. ( %g -- %g ) (%s)",
101  refPlane.GetCenter().X(), refBox.MinZ(), refBox.MaxZ(), refPlane.ID(),
102  nextBox.MinZ(), nextBox.MaxZ(), nextPlane.ID(),
103  )
104  inconsistentPlanes.setdefault('zalign', []) \
105  .append( ( distance, refPlane, nextPlane, ) )
106  # if misaligned on z
107 
108  #
109  # 2. check plane alignment on y direction
110  #
111  topDistance = nextBox.MaxY() - refBox.MaxY()
112  if abs(topDistance) > tolerance:
113  logging.error(
114  "Planes on x=%s are misaligned along y: ( %g -- %g ) (%s) vs. ( %g -- %g ) (%s)",
115  refPlane.GetCenter().X(), refBox.MinY(), refBox.MaxY(), refPlane.ID(),
116  nextBox.MinY(), nextBox.MaxY(), nextPlane.ID(),
117  )
118  inconsistentPlanes.setdefault('topYalign', []) \
119  .append( ( topDistance, refPlane, nextPlane, ) )
120  # if misaligned on top y
121  bottomDistance = nextBox.MinY() - refBox.MinY()
122  if abs(bottomDistance) > tolerance:
123  inconsistentPlanes.setdefault('bottomYalign', []) \
124  .append( ( bottomDistance, refPlane, nextPlane, ) )
125  # if misaligned on bottom y
126  if 'topYalign' in inconsistentPlanes or 'bottomYalign' in inconsistentPlanes:
127  logging.error(
128  "Planes on x=%s are misaligned along y: ( %g -- %g ) (%s) vs. ( %g -- %g ) (%s)",
129  refPlane.GetCenter().X(), refBox.MinY(), refBox.MaxY(), refPlane.ID(),
130  nextBox.MinY(), nextBox.MaxY(), nextPlane.ID(),
131  )
132  # if misaligned on bottom y
133 
134  #
135  # 3. check wire view
136  #
137  if refPlane.View() != nextPlane.View():
138  logging.error("Plane %s is on view '%s', %s is on view '%s'.",
139  refPlane.ID(), ROOT.geo.PlaneGeo.ViewName(refPlane.View()),
140  nextPlane.ID(), ROOT.geo.PlaneGeo.ViewName(nextPlane.View()),
141  )
142  inconsistentPlanes.setdefault('view', []) \
143  .append( ( refPlane, nextPlane, ) )
144  # if views do not match
145 
146  #
147  # 4. check wire orientation
148  #
149  if 1.0 - refPlane.GetIncreasingWireDirection().Dot(nextPlane.GetIncreasingWireDirection()) > tolerance:
150  logging.error("Plane %s measures direction %s, %s measures %s.",
151  refPlane.ID(), refPlane.GetIncreasingWireDirection(),
152  nextPlane.ID(), nextPlane.GetIncreasingWireDirection(),
153  )
154  inconsistentPlanes.setdefault('direction', []) \
155  .append( ( refPlane, nextPlane, ) )
156  # if views do not match
157 
158  #
159  # 5. check wire extremes alignment along y and z
160  #
161  # This check is different than the previous one in that it uses the actual
162  # coverage of wires rather than the wire plane box boundaries.
163  #
164  refWireMinY = min(
165  min(wire.GetStart().Y(), wire.GetEnd().Y())
166  for wire in refPlane.IterateWires()
167  )
168  refWireMaxY = max(
169  max(wire.GetStart().Y(), wire.GetEnd().Y())
170  for wire in refPlane.IterateWires()
171  )
172  refWireMaxZ = max(
173  max(wire.GetStart().Z(), wire.GetEnd().Z())
174  for wire in refPlane.IterateWires()
175  )
176  nextWireMinY = min(
177  min(wire.GetStart().Y(), wire.GetEnd().Y())
178  for wire in nextPlane.IterateWires()
179  )
180  nextWireMaxY = max(
181  max(wire.GetStart().Y(), wire.GetEnd().Y())
182  for wire in nextPlane.IterateWires()
183  )
184  nextWireMinZ = min(
185  min(wire.GetStart().Z(), wire.GetEnd().Z())
186  for wire in nextPlane.IterateWires()
187  )
188 
189  if abs(nextWireMinZ - refWireMaxZ) > tolerance:
190  logging.error(
191  "Wires of planes on x=%s are misaligned along z: %s ends at %g cm, %s restarts at %g cm",
192  refPlane.GetCenter().X(), refPlane.ID(), refWireMaxZ, nextPlane.ID(), nextWireMinZ,
193  )
194  inconsistentPlanes.setdefault('wireZalign', []) \
195  .append( ( nextWireMinZ - refWireMaxZ, refPlane, nextPlane, ) )
196  # if misaligned on z
197 
198  if abs(nextWireMaxY - refWireMaxY) > tolerance:
199  logging.error(
200  "Wires of planes on x=%s are misaligned along y: %s tops at %g cm, %s at %g cm",
201  refPlane.GetCenter().X(), refPlane.ID(), refWireMaxY, nextPlane.ID(), nextWireMaxY,
202  )
203  inconsistentPlanes.setdefault('wireTopYalign', []) \
204  .append( ( nextWireMaxY - refWireMaxY, refPlane, nextPlane, ) )
205  # if misaligned on top y
206 
207  if abs(nextWireMinY - refWireMinY) > tolerance:
208  logging.error(
209  "Wires of planes on x=%s are misaligned along y: %s floors at %g cm, %s at %g cm",
210  refPlane.GetCenter().X(), refPlane.ID(), refWireMinY, nextPlane.ID(), nextWireMinY,
211  )
212  inconsistentPlanes.setdefault('wireBottomYalign', []) \
213  .append( ( nextWireMinY - refWireMinY, refPlane, nextPlane, ) )
214  # if misaligned on top y
215 
216 
217  refPlane = nextPlane
218  # for all following planes on the same x
219  # for groups of planes along the same x
220  return inconsistentPlanes
221 # checkPlaneAlignment()
222 
223 
224 # ------------------------------------------------------------------------------
225 def wireEndBorders(end, borderCoords, tolerance):
226  """Returns the borders touched by the specified wire end."""
227 
228  # this is overdoing, since the check only cares whether any border is touched
229 
230  borders = set()
231  for side in ( 'top', 'bottom', ):
232  if abs(end.Y() - borderCoords[side]) <= tolerance: borders.add(side)
233  assert ('top' not in borders) or ('bottom' not in borders)
234 
235  for side in ( 'upstream', 'downstream', ):
236  if abs(end.Z() - borderCoords[side]) <= tolerance: borders.add(side)
237  assert ('upstream' not in borders) or ('downstream' not in borders)
238 
239  return borders
240 
241 # wireEndBorders()
242 
243 
244 
245 def checkWireEndingsInPlane(plane, tolerance = 0.01):
246  """Wires which do not end on any border of the plane are returned.
247 
248  The border is determined by the wires themselves rather than the plane box,
249  which could in principle extend further (but not less).
250 
251  A dictionary is returned with key the ID of the wire not ending on a border,
252  and a list of end labels ('start', 'end') listing which end did not.
253  """
254 
255  assert tolerance >= 0.0
256 
257  #
258  # 1. determine the boundaries (brute force, brute programming)
259  #
260  borderCoords = {
261  'bottom': min(
262  min(wire.GetStart().Y(), wire.GetEnd().Y())
263  for wire in plane.IterateWires()
264  ),
265  'top': max(
266  max(wire.GetStart().Y(), wire.GetEnd().Y())
267  for wire in plane.IterateWires()
268  ),
269  'upstream': min(
270  min(wire.GetStart().Z(), wire.GetEnd().Z())
271  for wire in plane.IterateWires()
272  ),
273  'downstream': max(
274  max(wire.GetStart().Z(), wire.GetEnd().Z())
275  for wire in plane.IterateWires()
276  ),
277  } # borderCoords
278 
279  shorterWires = dict()
280  for iWire, wire in enumerate(plane.IterateWires()):
281 
282  wireID = ROOT.geo.WireID(plane.ID(), iWire)
283 
284  #
285  # 1. check the start
286  #
287  start = wire.GetStart()
288  borders = wireEndBorders(start, borderCoords, tolerance)
289 
290  # there should be at least one border touched by each wire end
291  if len(borders) == 0:
292  logging.error(
293  "Wire %s \"start\" at %s cm does not touch any plane border (%s)",
294  wireID, start, ", ".join("%s: %g" % item for item in borderCoords.items()),
295  )
296  shorterWires.setdefault(wireID, []).append('start')
297  # if error
298 
299  #
300  # 2. check the end
301  #
302  end = wire.GetEnd()
303  borders = wireEndBorders(end, borderCoords, tolerance)
304 
305  # there should be at least one border touched by each wire end
306  if len(borders) == 0:
307  logging.error(
308  "Wire %s \"end\" at %s cm does not touch any plane border (%s)",
309  wireID, end, ", ".join("%s: %g" % item for item in borderCoords.items()),
310  )
311  shorterWires.setdefault(wireID, []).append('end')
312  # if error
313 
314  # for
315 
316  return shorterWires
317 # checkWireEndingsInPlane()
318 
319 
320 # ------------------------------------------------------------------------------
321 def checkPlaneWireAlignment(planeA, planeB, tolerance = 0.01):
322  """
323  Wires in the plane A are verified to have a continuation in plane B as another
324  wire. Only wires that reach the side (extreme z coordinate) are tested.
325 
326  Wires are assumed to have to go top to bottom on y direction.
327 
328  The list of misaligned wires is returned in the 5-uple form:
329 
330  ( shift, left wire ID, left wire, right wire ID, right wire )
331 
332  if the wire on the left plane was matched to one on the right plane, and
333 
334  ( None, left wire ID, left wire, None, None )
335 
336  if the wire on the left plane was not matched to any wire on the right plane.
337  """
338  # throughout this code, left refers to lower _z_, right to higher _z_
339 
340  misalignedWires = []
341 
342  # define a left and a right plane
343  if planeA.GetCenter().Z() < planeB.GetCenter().Z():
344  leftPlane, rightPlane = planeA, planeB
345  else:
346  leftPlane, rightPlane = planeB, planeA
347 
348  # wire pitch should be the same for the two planes
349  rightWirePitch = rightPlane.WirePitch()
350  assert abs(rightWirePitch - leftPlane.WirePitch()) < tolerance
351 
352  #
353  # 1. determine the sides
354  #
355  leftMinY = min(
356  min(wire.GetStart().Y(), wire.GetEnd().Y())
357  for wire in leftPlane.IterateWires()
358  )
359  leftMaxY = max(
360  max(wire.GetStart().Y(), wire.GetEnd().Y())
361  for wire in leftPlane.IterateWires()
362  )
363  leftMaxZ = max(
364  max(wire.GetStart().Z(), wire.GetEnd().Z())
365  for wire in leftPlane.IterateWires()
366  )
367  # RminZ = min( min(wire.GetStart().Z(), wire.GetEnd().Z()) for wire in rightPlane.IterateWires() )
368 
369  #
370  # 2. for each wire in left plane ending close to right side, pick and test
371  # the matching wire of the right plane
372  #
373 
374  leftEndPos = lambda wire: \
375  wire.GetStart() if wire.GetStart().Z() > wire.GetEnd().Z() else wire.GetEnd()
376  rightStartPos = lambda wire: \
377  wire.GetStart() if wire.GetStart().Z() < wire.GetEnd().Z() else wire.GetEnd()
378 
379  stats = StatCollector()
380  wireNoDiff = None # offset in wire number only between matching wires
381  for leftWireNo, leftWire in enumerate(leftPlane.IterateWires()):
382 
383  leftEnd = leftEndPos(leftWire)
384 
385  #
386  # 2.1. if the wire does not end on the right edge, move on;
387  # if the wire ends on a corner, also move on
388  #
389  if abs(leftEnd.Z() - leftMaxZ) > 0.01: continue
390  if abs(leftEnd.Y() - leftMaxY) < 0.01 or abs(leftEnd.Y() - leftMinY) < 0.01:
391  continue
392 
393  #
394  # 2.2. find the closest wire on the left
395  #
396  leftWireID = ROOT.geo.WireID(leftPlane.ID(), leftWireNo)
397  try:
398  rightWireID = rightPlane.NearestWireID(leftEnd)
399  #except ( TypeError, ROOT.geo.InvalidWireError ) as e:
400  except TypeError as e:
401  rightWireID = None
402  except ROOT.geo.InvalidWireError as e:
403  # this branch is a placeholder, since Python 3 is not able to catch
404  # ROOT.geo.InvalidWireError (and if one is thrown, a TypeError will raise)
405  logging.error(
406  "No wire on %s is close enough to %s (closest is %s, but would have been %s)",
407  rightPlane.ID(), leftWireID,
408  (e.suggestedWireID() if e.hasSuggestedWire() else "unknown"),
409  (e.badWireID() if e.hasBadWire() else "unknown"),
410  )
411  rightWireID = e.badWireID() if e.hasBadWire() else None
412  rightWireID.markInvalid()
413  misalignedWires.append( ( None, leftWireID, leftWire, None, None, ) )
414  continue
415  # try ... except no wire matched
416  if not rightWireID:
417  msg = ""
418  if rightWireID is None:
419  msg += "No wire on {} is close enough to {}" \
420  .format(rightPlane.ID(), leftWireID)
421  else: # just invalid
422  msg += "No wire on {} is close enough to {} (would have been {})" \
423  .format(rightPlane.ID(), leftWireID, rightWireID)
424  # if ... else
425 
426  wireCoord = rightPlane.WireCoordinate(leftEnd)
427  msg += "; closest would have been {} W: {}" \
428  .format(rightPlane.ID(), wireCoord)
429 
430  nearestWireID = ROOT.geo.WireID(rightPlane.ID(), int(0.5 + wireCoord)) \
431  if 0.5 + wireCoord >= 0.0 else None
432  nearestWire = None
433  if nearestWireID and rightPlane.HasWire(nearestWireID):
434  nearestWire = rightPlane.Wire(nearestWireID)
435  if nearestWire:
436  msg += "; actual {} ends at: {}" \
437  .format(nearestWireID, rightStartPos(nearestWire))
438 
439  logging.error(msg)
440  misalignedWires.append( ( None, leftWireID, leftWire, None, None, ) )
441  continue
442  #
443 
444  rightWire = rightPlane.Wire(rightWireID)
445 
446  #
447  # 2.3. check the projected distance of that wire from this one
448  #
449  shift = leftWire.DistanceFrom(rightWire)
450  stats.add(shift)
451 
452  #
453  # 2.4. compare wire orientation
454  #
455  leftWire.Direction().Dot(rightWire.Direction())
456  if 1.0 - leftWire.Direction().Dot(rightWire.Direction()) > tolerance:
457  logging.error(
458  "Wire %s has direction %s, the matched %s has %s",
459  leftWireID, leftWire.Direction(),
460  rightWireID, rightWire.Direction(),
461  )
462  # if too far
463 
464  #
465  # 2.5. check that wires touch
466  #
467  d = (leftEndPos(leftWire) - rightStartPos(rightWire)).Mag()
468  if d > tolerance:
469  logging.debug(
470  "Distance of wire %s (%s) from the matched wire %s (%s): %g",
471  leftWireID, leftEndPos(leftWire),
472  rightWireID, rightStartPos(rightWire),
473  d
474  )
475  # find which is the wire physically closest to leftWire
476  closestWireID, d_min = rightWireID, d
477  testWireID = rightWireID
478  while testWireID.Wire > 0:
479  testWireID.Wire -= 1
480  testWire = rightPlane.Wire(testWireID)
481  test_d = (leftEndPos(leftWire) - rightStartPos(testWire)).Mag()
482  logging.debug("Distance from %s (%s): %g", testWireID, rightStartPos(testWire), test_d)
483  if test_d >= d_min: break
484  closestWireID, d_min = testWireID, test_d
485  # while
486  testWireID = rightWireID
487  LastWireNo = rightPlane.Nwires() - 1
488  while testWireID.Wire < LastWireNo:
489  testWireID.Wire += 1
490  testWire = rightPlane.Wire(testWireID)
491  test_d = (leftEndPos(leftWire) - rightStartPos(testWire)).Mag()
492  logging.debug("Distance from %s (%s): %g", testWireID, rightStartPos(testWire), test_d)
493  if test_d >= d_min: break
494  closestWireID, d_min = testWireID, test_d
495  # while
496 
497  logging.error(
498  "Wire %s ends at %s, the matched wire %s starts at %s, %g cm away.",
499  leftWireID, leftEndPos(leftWire),
500  rightWireID, rightStartPos(rightWire),
501  d
502  )
503  if closestWireID != rightWireID:
504  logging.error(
505  " => the closest wire is actually %s starting at %s, %g cm away",
506  closestWireID, rightPlane.Wire(closestWireID), d_min
507  )
508  # if
509 
510  misalignedWires.append( ( d_min, leftWireID, leftWire, rightWireID, rightWire ) )
511 
512  # if too far
513 
514  # for wires in the left plane
515 
516  if stats.entries() > 0:
517  logging.debug("Shift for %d wires between %s and %s: %g +/- %g cm",
518  stats.entries(), leftPlane.ID(), rightPlane.ID(),
519  stats.average(), stats.RMS(),
520  )
521  else:
522  logging.debug("No wire shift statistics collected.")
523  return misalignedWires
524 # checkPlaneWireAlignment()
525 
526 
527 # ------------------------------------------------------------------------------
528 def checkWireAlignment(planes, tolerance = 0.1):
529  """Check alignment in z position of planes on the same x.
530 
531  Returns triples (distance, first plane, second plane) for each misaligned
532  pair of planes.
533  """
534  assert tolerance >= 0.0
535  PlanesByX = groupPlanesByX(planes, sortBy = 'z')
536  misalignedWires = [] # grouped by extended plane
537  for planesOnX in PlanesByX:
538  if len(planesOnX) < 2: continue
539  misalignedWiresOnPlane = []
540  planeIter = iter(planesOnX)
541  leftPlane = next(planeIter)
542  for rightPlane in planeIter:
543  misalignedWiresOnPair \
544  = checkPlaneWireAlignment(leftPlane, rightPlane, tolerance=tolerance)
545  if misalignedWiresOnPair:
546  misalignedWiresOnPlane.extend(misalignedWiresOnPair)
547  leftPlane = rightPlane
548  # for all following planes on the same x
549  if misalignedWiresOnPlane: misalignedWires.append(misalignedWiresOnPlane)
550  # for groups of planes along the same x
551  return misalignedWires
552 # checkWireAlignment()
553 
554 
555 # ------------------------------------------------------------------------------
557  def __init__(self, keyFunc = None, tolerance = None):
558  self.keyFunc = (lambda obj: obj) if keyFunc is None else keyFunc
559  self.tolerance = tolerance
560  # __init__()
561  def __call__(self, objects):
562  groups = []
563  refKey = None
564  for obj in objects:
565  key = self.keyFunc(obj)
566  withPrevious = refKey is not None and self._compareKeys(key, refKey)
567  if not withPrevious:
568  refKey = key
569  groups.append(list())
570  groups[-1].append(obj)
571  # for objects
572  return groups
573  # __call__()
574  def _compareKeys(self, a, b):
575  return (abs(b - a) < self.tolerance) if self.tolerance else (a == b)
576  @staticmethod
577  def cluster(objs, keyFunc = None, tolerance = None):
578  return SimpleProximityClusterer(keyFunc=keyFunc, tolerance=tolerance)(objs)
579 # class SimpleProximityClusterer
580 
581 
582 def groupPlanesByX(planes, tolerance = 0.1, sortBy = None):
583  xPos = lambda plane: plane.GetCenter().X()
584  cluster = SimpleProximityClusterer(xPos, tolerance) # 1 mm
585  groupedByX = cluster(sorted(planes, key=xPos))
586  if sortBy:
587  if sortBy.lower() == 'x':
588  sortKey = xPos
589  elif sortBy.lower() == 'y':
590  sortKey = lambda plane: plane.GetCenter().Y()
591  elif sortBy.lower() == 'z':
592  sortKey = lambda plane: plane.GetCenter().Z()
593  else:
594  raise RuntimeError("Unsupported sorting requested: '%s'" % sortBy)
595  for planes in groupedByX: planes.sort(key=sortKey)
596  # if sorting
597  return groupedByX
598 # groupPlanesByX()
599 
600 
601 # ------------------------------------------------------------------------------
603  def __init__(self): self.reset()
604  def reset(self):
605  self.min_ = None
606  self.max_ = None
607  self.n = 0
608  # reset()
609  def add(self, value):
610  if self.min_ > value or self.min_ is None: self.min_ = value
611  if self.max_ < value: self.max_ = value # None < any number
612  # add()
613  def min(self): return self.min_
614  def max(self): return self.max_
615  def minmax(self): return self.min(), self.max()
616 # class AccumulateExtrema
617 
618 
619 # ------------------------------------------------------------------------------
621  def __init__(self): self.reset()
622  def reset(self):
623  self.n = 0
624  self.w = 0.0
625  self.wx = 0.0
626  self.wx2 = 0.0
627  # reset()
628  def add(self, value, weight = 1.0):
629  self.n += 1
630  self.w += weight
631  self.wx += weight * value
632  self.wx2 += weight * value**2
633  # add()
634  def entries(self): return self.n
635  def weightSum(self): return self.w
636  def sum(self): return self.wx
637  def sumSq(self): return self.wx2
638  def average(self): return self.wx / self.w if self.w else None
639  def averageSq(self): return self.wx2 / self.w if self.w else None
640  def variance(self):
641  return self.averageSq() - self.average()**2 if self.w else None
642  def RMS(self): return self.variance() ** 0.5 if self.w else None
643 # class StatCollector
644 
645 
646 # ##############################################################################
648 
649  import argparse
650 
651  Parser = argparse.ArgumentParser(description=__doc__)
652 
653  Parser.set_defaults()
654 
655  Parser.add_argument("config", help="configuration file [FHiCL]")
656 
657  Parser.add_argument("--servicetable", "-T", default='services',
658  help="name of the FHiCL table where all services are configured ['%(default)s']"
659  )
660 
661  Parser.add_argument("--debug", "-d", action="count", default=0,
662  help="be more verbose")
663 
664  Parser.add_argument("--version", "-V", action="version", version=__version__)
665 
666  #argGroup = Parser.add_argument_group(title="Numerical output format")
667  #argGroup.add_argument("--integer", "-d", "-i", action="store_false",
668  #dest="bFloat", help="sets the sum of integral numbers")
669 
670  args = Parser.parse_args(args=argv[1:])
671 
672  # `logging.DEBUG` is the standard debugging level;
673  # `logging.DEBUG + 1` (args.debug = 0) is a bit less verbose, but still well
674  # within `logging.INFO` level
675  logging.basicConfig(level=logging.DEBUG - (args.debug - 1))
676 
677  from ICARUSservices import ServiceManager
678  import cppUtils
679  import ROOT
680  import ROOTutils
681 
682  # this is for a bug present in LArSoft v08_52_00 (and many other versions);
683  # the header where exception geo::InvalidWireError is defined is not included.
684  cppUtils.SourceCode.loadHeaderFromUPS("larcorealg/Geometry/Exceptions.h")
685 
686  global ROOT
687 
688  ServiceManager.setConfiguration(args.config, args.servicetable)
689 
690  geom = ServiceManager('Geometry')
691 
692  FailureSummary = []
693 
694  Cryostats = list(geom.IterateCryostats())
695  TPCs = list(geom.IterateTPCs())
696  Planes = list(geom.IteratePlanes())
697 
698  #
699  # cryostat overlap
700  #
701  overlappingCryostats = CheckGeoObjOverlaps(Cryostats, "cryostat")
702  if overlappingCryostats:
703  msg = "%s cryostat overlaps detected: %s." % (
704  len(overlappingCryostats),
705  ", ".join(map(GeoPairToString, overlappingCryostats)),
706  )
707  logging.error(msg)
708  FailureSummary.append(msg)
709  else: logging.info("No cryostat overlap detected.")
710 
711  #
712  # TPC overlaps
713  #
714  overlappingTPCs = CheckGeoObjOverlaps(TPCs, "TPC")
715  if overlappingTPCs:
716  msg = "%s TPC overlaps detected: %s." % (
717  len(overlappingTPCs), ", ".join(map(GeoPairToString, overlappingTPCs)),
718  )
719  logging.error(msg)
720  FailureSummary.append(msg)
721  else: logging.info("No TPC overlap detected.")
722 
723  #
724  # TPC active volume overlaps
725  #
726  overlappingActiveVolTPCs = CheckGeoObjOverlaps \
727  (TPCs, "active TPC volume", extractBox=ROOT.geo.TPCGeo.ActiveBoundingBox)
728  if overlappingActiveVolTPCs:
729  msg = "%s TPC active volume overlaps detected: %s." % (
730  len(overlappingActiveVolTPCs),
731  ", ".join(map(GeoPairToString, overlappingActiveVolTPCs)),
732  )
733  logging.error(msg)
734  FailureSummary.append(msg)
735  else: logging.info("No TPC active volume overlap detected.")
736 
737  #
738  # plane box overlaps
739  #
740  overlappingPlanes = CheckGeoObjOverlaps \
741  (Planes, "wire planes", extractBox=ROOT.geo.PlaneGeo.BoundingBox)
742  if overlappingPlanes:
743  logging.error("%s wire plane overlaps detected: %s.",
744  len(overlappingPlanes),
745  ", ".join(map(GeoPairToString, overlappingPlanes))
746  )
747  FailureSummary.append \
748  ("%s wire plane overlaps detected." % len(overlappingPlanes))
749  else: logging.info("No wire plane overlap detected.")
750 
751  #
752  # check plane alignment
753  #
754  inconsistentPlanes = checkPlaneAlignment(Planes, tolerance=0.0001) # 1 um (!!)
755 
756  try: planesWithIssues = inconsistentPlanes['zalign']
757  except KeyError:
758  logging.info("All %d planes are correctly aligned along z.", len(Planes))
759  else:
760  logging.error("%s wire planes present misalignment on z: %s",
761  len(planesWithIssues), ", ".join(
762  "%s vs. %s (%g mm)" % (planeA.ID(), planeB.ID(), distance)
763  for distance, planeA, planeB in planesWithIssues
764  ),
765  )
766  FailureSummary.append \
767  ("%s wire planes present misalignment: " % len(misalignedPlanes))
768  # if error
769 
770  try: planesWithIssues = inconsistentPlanes['topYalign']
771  except KeyError:
772  logging.info("Top of all %d planes is correctly aligned.", len(Planes))
773  else:
774  logging.error("%s wire planes present misalignment of top side: %s",
775  len(planesWithIssues), ", ".join(
776  "%s vs. %s (%g mm)" % (planeA.ID(), planeB.ID(), distance)
777  for distance, planeA, planeB in planesWithIssues
778  ),
779  )
780  FailureSummary.append \
781  ("%s wire planes present misalignment on top side" % len(planesWithIssues))
782  # if error
783 
784  try: planesWithIssues = inconsistentPlanes['bottomYalign']
785  except KeyError:
786  logging.info("Bottom of all %d planes is correctly aligned.", len(Planes))
787  else:
788  logging.error("%s wire planes present misalignment of bottom side: %s",
789  len(planesWithIssues), ", ".join(
790  "%s vs. %s (%g mm)" % (planeA.ID(), planeB.ID(), distance)
791  for distance, planeA, planeB in planesWithIssues
792  ),
793  )
794  FailureSummary.append \
795  ("%s wire planes present misalignment on bottom side" % len(planesWithIssues))
796  # if error
797 
798  try: planesWithIssues = inconsistentPlanes['wireZalign']
799  except KeyError:
800  logging.info(
801  "Inter-plane areas covered by wires on all %d planes are correctly aligned.",
802  len(Planes)
803  )
804  else:
805  logging.error("%s planes have wires inconsistently covering the touching side: %s",
806  len(planesWithIssues), ", ".join(
807  "%s vs. %s (%g mm)" % (planeA.ID(), planeB.ID(), distance)
808  for distance, planeA, planeB in planesWithIssues
809  ),
810  )
811  FailureSummary.append(
812  "%s planes present inconsistent wire coverage on touching side"
813  % len(planesWithIssues)
814  )
815  # if error
816 
817  try: planesWithIssues = inconsistentPlanes['wireBottomYalign']
818  except KeyError:
819  logging.info(
820  "Bottom of areas covered by wires on all %d planes is correctly aligned.",
821  len(Planes)
822  )
823  else:
824  logging.error("%s planes have wires inconsistently covering bottom side: %s",
825  len(planesWithIssues), ", ".join(
826  "%s vs. %s (%g mm)" % (planeA.ID(), planeB.ID(), distance)
827  for distance, planeA, planeB in planesWithIssues
828  ),
829  )
830  FailureSummary.append(
831  "%s planes present inconsistent wire coverage on bottom side: "
832  % len(misalignedPlanes)
833  )
834  # if error
835 
836  try: planesWithIssues = inconsistentPlanes['wireTopYalign']
837  except KeyError:
838  logging.info(
839  "Top of areas covered by wires on all %d planes is correctly aligned.",
840  len(Planes)
841  )
842  else:
843  logging.error("%s planes have wires inconsistently covering top side: %s",
844  len(planesWithIssues), ", ".join(
845  "%s vs. %s (%g mm)" % (planeA.ID(), planeB.ID(), distance)
846  for distance, planeA, planeB in planesWithIssues
847  ),
848  )
849  FailureSummary.append(
850  "%s planes present inconsistent wire coverage on top side: "
851  % len(misalignedPlanes)
852  )
853  # if error
854 
855 
856 
857  try: planesWithIssues = inconsistentPlanes['view']
858  except KeyError:
859  logging.info("All %d planes have matching views.", len(Planes))
860  else:
861  logging.error("%s wire planes present direction inconsistencies:",
862  len(planesWithIssues), ", ".join(
863  "%s vs. %s" % (planeA.ID(), planeB.ID())
864  for planeA, planeB in planesWithIssues
865  ),
866  )
867  FailureSummary.append(
868  "%s wire planes present direction inconsistencies." % len(planesWithIssues)
869  )
870  # if error
871 
872  try: planesWithIssues = inconsistentPlanes['direction']
873  except KeyError:
874  logging.info("All %d planes have consistent directions.", len(Planes))
875  else:
876  logging.error("%s wire planes present view inconsistencies:",
877  len(planesWithIssues), ", ".join(
878  "%s vs. %s" % (planeA.ID(), planeB.ID())
879  for planeA, planeB in planesWithIssues
880  ),
881  )
882  FailureSummary.append(
883  "%s wire planes present view inconsistencies." % len(planesWithIssues)
884  )
885  # if error
886 
887  #
888  # check wire alignment
889  #
890  shorterWires = {}
891  for plane in Planes:
892  shorterWires.update(checkWireEndingsInPlane(plane, tolerance=0.01))
893  if shorterWires:
894  logging.error("Wires not ending on plane frame found (%d):",
895  len(shorterWires),
896  )
897  for wireID, ends in shorterWires.items():
898  logging.error(" %s: %s", wireID, ", ".join(ends))
899  FailureSummary.append \
900  ("%d wires do not end on plane frame" % len(shorterWires))
901  # if error
902 
903 
904  misalignedWires = checkWireAlignment(Planes, tolerance=0.0001) # 1 um required
905  if misalignedWires:
906  logging.error("Misaligned wires found on %d extended planes:",
907  len(misalignedWires),
908  )
909  for misalignedWiresOnPlane in misalignedWires:
910  logging.error(" %d on wires on plane around x=%g cm:",
911  len(misalignedWiresOnPlane),
912  misalignedWiresOnPlane[0][2].GetCenter().X()
913  )
914  for shift, wireLid, wireL, wireRid, wireR in misalignedWiresOnPlane:
915  if shift is None:
916  logging.error(" %s did not match any wire", wireLid)
917  else:
918  logging.error(" %s and %s are misaligned by %g um",
919  wireLid, wireRid, shift * 1.0e4,
920  )
921  # for all misaligned wire pairs
922  # for
923  FailureSummary.append \
924  ("misaligned wires found on %d extended planes" % len(misalignedWires))
925  else:
926  logging.info("No misaligned wires detected.")
927 
928  if FailureSummary:
929  logging.info("%d failure categories detected:\n - %s",
930  len(FailureSummary), "\n - ".join(FailureSummary),
931  )
932  # if failures
933  return 1 if FailureSummary else 0
934 # performGeometryChecks()
935 
936 
937 # ##############################################################################
938 if __name__ == "__main__":
939  import sys
940  sys.exit(performGeometryChecks(sys.argv))
941 # main
942 
943 # ##############################################################################
static std::string format(PyObject *obj, unsigned int pos, unsigned int indent, unsigned int maxlen, unsigned int depth)
Definition: fclmodule.cxx:374
Cluster finding and building.
then echo echo For and will not be changed by echo further linking echo echo B echo The symbol is in the uninitialized data multiple common symbols may appear with the echo same name If the symbol is defined the common echo symbols are treated as undefined references For more echo details on common see the discussion of warn common echo in *Note Linker see the discussion of warn common echo in *Note Linker such as a global int variable echo as opposed to a large global array echo echo I echo The symbol is an indirect reference to another symbol This echo is a GNU extension to the a out object file format which is echo rarely used echo echo N echo The symbol is a debugging symbol echo echo R echo The symbol is in a read only data section echo echo S echo The symbol is in an uninitialized data section for small echo objects echo echo T echo The symbol is in the the normal defined echo symbol is used with no error When a weak undefined symbol echo is linked and the symbol is not the value of the echo weak symbol becomes zero with no error echo echo W echo The symbol is a weak symbol that has not been specifically echo tagged as a weak object symbol When a weak defined symbol echo is linked with a normal defined the normal defined echo symbol is used with no error When a weak undefined symbol echo is linked and the symbol is not the value of the echo weak symbol becomes zero with no error echo echo echo The symbol is a stabs symbol in an a out object file In echo this the next values printed are the stabs other echo the stabs desc and the stab type Stabs symbols are echo used to hold debugging information For more echo see *Note or object file format specific echo echo For Mac OS X
auto enumerate(Iterables &&...iterables)
Range-for loop helper tracking the number of iteration.
Definition: enumerate.h:69
S join(S const &sep, Coll const &s)
Returns a concatenation of strings in s separated by sep.
T abs(T value)
list
Definition: file_to_url.sh:28