All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
triggeredEventList.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 
3 import time
4 
5 __doc__ = """Prints the list of events passing the specified trigger."""
6 __author__ = "Gianluca Petrillo (petrillo@slac.stanford.edu)"
7 __date__ = time.strptime("July 11, 2021", "%B %d, %Y")
8 __version__ = "1.1"
9 
10 import sys
11 import galleryUtils
12 import ROOT
13 import logging
14 
15 DefaultTriggerModuleLabel = "trigger" # may be overridden by command line
16 
17 TriggerDataProductClass = 'std::vector<raw::Trigger>'
18 
19 # ------------------------------------------------------------------------------
21  def __init__(self,
22  run=False, subRun=False, event=True, sourceFile=False,
23  human=False, short=False,
24  trigTime=False,
25  separator=" ",
26  ):
27  self.eventTag = None
28  self.run = run
29  self.subrun = subRun
30  self.event = event
31  self.trigTime = trigTime
32  self.sourceFile = sourceFile
33  self.human = human
34  self.short = short
35  self.separator = separator
36  # __init__()
37 
38  def prepareFor(self, event):
39  self.eventObj = event
40  self.eventTag = None
41 
42  def __call__(self, trigInfo, event = None):
43  if self.eventTag is None or event is not self.eventObj:
44  self.eventTag = self._makeTag(event)
45  return self.addTriggerInfo(self.eventTag, trigInfo)
46  # __call__()
47 
48  def addTriggerInfo(self, tag, trigInfo):
49  info = [ tag, ]
50  if self.trigTime: info.append(f"{trigInfo['trigTime']:.3f}") # to nanosecond
51  return self.separator.join(info)
52  # addTriggerInfo()
53 
54  def _makeTag(self, event=None):
55  if event is not None: self.eventObj = event
56  assert self.eventObj, "Needs to prepareFor(event) this object first!"
57  eventAux = self.eventObj.eventAuxiliary()
58  eventID = []
59  if self.run:
60  run = eventAux.run()
61  if self.human: eventID.append(f"run {run}")
62  elif self.short: eventID.append(f"R:{run}")
63  else: eventID.append(str(run))
64  # if print run
65  if self.subrun:
66  subrun = eventAux.subRun()
67  if self.human: eventID.append(f"subrun {subrun}")
68  elif self.short: eventID.append(f"S:{subrun}")
69  else: eventID.append(str(subrun))
70  # if print subrun
71  if self.event:
72  evt = eventAux.event()
73  if self.human: eventID.append(f"event {evt}")
74  elif self.short: eventID.append(f"E:{evt}")
75  else: eventID.append(str(evt))
76  # if print event
77  if self.sourceFile:
78  sourceFile = self.eventObj.getTFile()
79  fileName = sourceFile.GetName() if sourceFile else "<unknown>"
80  eventInFile = self.eventObj.eventEntry()
81  if self.human: eventID.append(f"from file {fileName} entry {eventInFile}")
82  elif self.short: eventID.append(f"F:{fileName!r}@{eventInFile}")
83  else:
84  eventID.append(repr(fileName))
85  eventID.append(str(eventInFile))
86  # if print event
87  return self.separator.join(eventID)
88  # _makeTag()
89 
90 # class EventTagCache
91 
92 
94  """Simple numeric range class (usual [ lower, upper [ interval).
95 
96  Boundaries set to `None` are ignored.
97  """
98 
99  def __init__(self, lower, upper):
100  self.lower = lower
101  self.upper = upper
102  def contains(self, value):
103  if self.lower is not None and value < self.lower: return False
104  if self.upper is not None and value >= self.upper: return False
105  return True
106  def __contains__(self, value): return self.contains(value)
107  def __nonzero__(self): return self.lower is not None or self.upper is not None
108  def __str__(self):
109  if self.lower is None:
110  if self.upper is None: return "(any)"
111  else: return f" < {self.upper}"
112  else:
113  if self.upper is None: return f">= {self.lower}"
114  else: return f"{self.lower} -- {self.upper}"
115  # __str__()
116 # class IntervalClass
117 
118 
119 # ------------------------------------------------------------------------------
120 def analyzeTrigger(eventTag, triggers, trigSpec, args):
121  if len(triggers) == 0: return False
122 
123  # if this is expensive (which should not be since the trigger data product
124  # has already been read) it can be made conditional based on `args.timeRange`.
125  trigTime = triggers[0].TriggerTime()
126  if trigTime not in args.timeRange: return False
127 
128  trigSpec['count'] += 1
129 
130  if args.noList: return True # if no printing is requested, skip the rest
131 
132  trigInfo = { 'trigTime': trigTime, }
133  print(eventTag(trigInfo=trigInfo), file=trigSpec['dest'])
134 
135  return True
136 # analyzeTrigger()
137 
138 
139 def findTriggeredEvents(sampleEvents, triggers, args):
140 
141  # digest the trigger info options
142  trigSpecs = triggers.copy()
143  for trigSpec in trigSpecs:
144  tag = trigSpec['spec']
145  if not isinstance(tag, ROOT.art.InputTag):
146  if ':' not in tag: tag = DefaultTriggerModuleLabel + ':' + tag
147  tag = ROOT.art.InputTag(tag)
148  # if
149  trigSpec.update({
150  'tag': tag,
151  'dest': (open(trigSpec['destList'], 'w')
152  if trigSpec['destList'] else sys.stdout),
153  'count': 0,
154  })
155  # for
156 
157  try: # using a context manager here is more complicate...
158 
159  getTrigger \
160  = galleryUtils.make_getValidHandle(TriggerDataProductClass, sampleEvents)
161 
162  eventTag = EventTagCache(
163  run=args.run,
164  subRun=args.subrun,
165  event=args.event,
166  sourceFile=args.sourcefile,
167  human=args.human,
168  short=args.short,
169  trigTime=args.trigtime,
170  separator=args.sep,
171  ) if args.doList else None
172 
173  for iEvent, event in enumerate(galleryUtils.forEach(sampleEvents)):
174 
175  if args.maxEvents and iEvent >= args.maxEvents:
176  logging.warning(f"Limit of {iEvent} events reached: stopping now.")
177  break
178 
179  if eventTag: eventTag.prepareFor(event)
180 
181  for trigSpec in trigSpecs:
182  triggers = getTrigger(trigSpec['tag']).product()
183  analyzeTrigger(eventTag, triggers, trigSpec, args)
184  # for
185 
186  # for all events
187  else: iEvent += 1
188 
189  finally:
190  for trigSpec in trigSpecs:
191  if trigSpec['dest'] is not sys.stdout: trigSpec['dest'].close()
192  # for
193  # try ... finally
194 
195  for trigSpec in trigSpecs:
196  print(
197  f"{trigSpec['tag'].encode()}:"
198  f" {trigSpec['count']}/{iEvent} events triggered"
199  f" ({trigSpec['count']/iEvent*100:g}%)",
200  end="",
201  )
202  if (trigSpec['dest'] is not sys.stdout):
203  print(f" (->'{trigSpec['dest'].name}')", end="")
204  print()
205  # for
206 
207  return 0
208 # findTriggeredEvents()
209 
210 
211 def getAllTriggerTags(sampleEvents):
212 
213  return sorted(
214  sampleEvents.getInputTags[TriggerDataProductClass](),
215  key=ROOT.art.InputTag.encode
216  )
217 
218 # getAllTriggerTags()
219 
220 
221 def listTriggerDataProducts(sampleEvents):
222  # sorted() also converts a returned `std::vector` into a Python list
223  inputTags = getAllTriggerTags(sampleEvents)
224  nProducts = len(inputTags)
225  print(f"{nProducts} '{TriggerDataProductClass}' data products found:")
226  PadLength=len(str(nProducts))
227  for iProduct, inputTag in enumerate(inputTags):
228  print(f"[#{iProduct+1:0>{PadLength}}] {inputTag.encode()}")
229  # for
230  return 0
231 # listTriggerDataProducts()
232 
233 
235  if isinstance(spec, ROOT.art.InputTag): spec = spec.encode()
236  return f"triggeredEvents_{spec.replace(':', '_')}.log"
237 # buildDefaultOutputFile()
238 
239 
240 def parseTimeRange(rangeSpec, sep = "/"):
241 
242  if not rangeSpec: return IntervalClass(None, None)
243  try:
244  lower, upper = rangeSpec.split(sep)
245  except ValueError:
246  raise RuntimeError(f"Invalid format for time range: {rangeSpec}")
247 
248  if lower:
249  try: lower = float(lower)
250  except ValueError:
251  RuntimeError(
252  "Lower bound of time range '{rangeSpec}' ('f{lower}')"
253  " is not a valid time."
254  )
255  else: lower = None
256  if upper:
257  try: upper = float(upper)
258  except ValueError:
259  RuntimeError(
260  "Upper bound of time range '{rangeSpec}' ('f{upper}')"
261  " is not a valid time."
262  )
263  else: upper = None
264  return IntervalClass(lower, upper)
265 # parseTimeRange()
266 
267 
268 if __name__ == "__main__":
269 
270  # --- BEGIN -- argument parsing ---------------------------------------------
271  import argparse
272 
273  parser = argparse.ArgumentParser(description=__doc__)
274 
275  # positional
276  parser.add_argument("InputFile", help="art/ROOT input file or file list")
277  parser.add_argument("TriggerSpec", nargs="*",
278  help=f"trigger to check: data product name (`{DefaultTriggerModuleLabel}:inst`)"
279  f" or just instance name (`M5S10`, becoming `{DefaultTriggerModuleLabel}:M5S10`)"
280  )
281 
282  # input options
283  inputGroup = parser.add_argument_group("Input options")
284  inputGroup.add_argument("--maxEvents", "-n", type=int,
285  help="do not process more that this number of events")
286  inputGroup.add_argument("--deflabel", type=str, default=DefaultTriggerModuleLabel,
287  help="trigger module label used in TriggerSpec by default [%(default)s]")
288  inputGroup.add_argument("--all", action="store_true",
289  help="processes all available trigger data products")
290  inputGroup.add_argument("--all-to-files", dest="allToFiles",
291  action="store_true",
292  help="like `--all`, redirecting each trigger into its default-named file"
293  )
294 
295  # processing options
296  filterGroup = parser.add_argument_group("Filtering options")
297  filterGroup.add_argument("--accept-time", "-T", dest="acceptTime", type=str,
298  help="only consider triggers in the specified range of time: `<min>:<max>`;"
299  " time is in electronics time scale, microseconds;"
300  " either `<min>` or `<max>` may be omitted"
301  )
302 
303  # output options
304  outputFormatGroup = parser.add_argument_group("Output format")
305  outputFormatGroup.add_argument("--run", "-r", action="store_true",
306  help="print the run number for triggering events [%(default)s]")
307  outputFormatGroup.add_argument("--subrun", "-a", action="store_true",
308  help="print the subrun number for triggering events [%(default)s]")
309  outputFormatGroup.add_argument("--event", "-e", action="store_false",
310  help="print the event number for triggering events [%(default)s]")
311  outputFormatGroup.add_argument("--trigtime", "-t", action="store_true",
312  help="print the relative time of first trigger [%(default)s]")
313  outputFormatGroup.add_argument("--sourcefile", "--file", "-f",
314  action="store_true",
315  help="print the file where triggering events are stored [%(default)s]"
316  )
317  outputFormatGroup.add_argument("--human", "--hr", "-H", action="store_true",
318  help="print the event ID with extended labels [%(default)s]")
319  outputFormatGroup.add_argument("--short", "-S", action="store_true",
320  help="print the event ID with short labels [%(default)s]")
321  outputFormatGroup.add_argument("--nolist", "-N", dest="noList",
322  action="store_true", help="do not print any event ID (only summary)")
323  outputFormatGroup.add_argument("--separator", dest="sep", default=" ",
324  help="use this string as separator [%(default)r]")
325 
326  # "modal" options
327  generalOptGroup = parser.add_argument_group("General options")
328  generalOptGroup.add_argument("--list", "-l", dest="doList", action="store_true",
329  help="prints the available trigger data product tags, and exits")
330  generalOptGroup.add_argument("--debug", action="store_true",
331  help="enable additional diagnostic output")
332  generalOptGroup.add_argument("--version", "-V", action="version",
333  version=f"%(prog)s v{__version__} ({time.asctime(__date__)})",
334  help="prints the version number"
335  )
336 
337  args = parser.parse_args()
338 
339  logging.basicConfig(level=(logging.DEBUG if args.debug else logging.INFO))
340 
341  DefaultTriggerModuleLabel = args.deflabel
342  # --- END ---- argument parsing ---------------------------------------------
343 
344  sampleEvents = galleryUtils.makeEvent(args.InputFile)
345 
346  hasTriggerSpecs = args.TriggerSpec or args.all or args.allToFiles
347 
348  if not args.doList and not hasTriggerSpecs:
349  listTriggerDataProducts(sampleEvents)
350  logging.error("No trigger tag specified!")
351  sys.exit(1)
352 
353  if args.doList: sys.exit(listTriggerDataProducts(sampleEvents))
354 
355  args.timeRange = parseTimeRange(args.acceptTime)
356 
357  # process trigger specifications
358  triggerSpecs = []
359  if args.all or args.allToFiles:
360  for tag in getAllTriggerTags(sampleEvents):
361  triggerSpecs.append({
362  'spec': tag,
363  'destList': (buildDefaultOutputFile(tag) if args.allToFiles else None),
364  })
365  # for
366  else:
367  for spec in args.TriggerSpec:
368  spec, sep, destList = spec.partition('->')
369  if sep and not destList: destList = buildDefaultOutputFile(spec)
370  triggerSpecs.append({ 'spec': spec, 'destList': destList, })
371  # for
372  # if all ... else
373 
374  sys.exit(findTriggeredEvents(sampleEvents, triggerSpecs, args))
375 
376 # main
do one_file $F done echo for F in find $TOP name CMakeLists txt print
auto enumerate(Iterables &&...iterables)
Range-for loop helper tracking the number of iteration.
Definition: enumerate.h:69
print OUTPUT<< EOF;< setup name="Default"version="1.0">< worldref="volWorld"/></setup ></gdml > EOF close(OUTPUT)
open(RACETRACK) or die("Could not open file $RACETRACK for writing")