All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
sbndcode/sbndcode/gallery/python/galleryUtils.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 
4 __doc__ = """
5 Collection of utilities to interface gallery with python.
6 
7 This module requires ROOT.
8 """
9 
10 __all__ = [
11  'readHeader', # imported from `cppUtils`
12  'SourceCode', # imported from `cppUtils`
13  'make_getValidHandle',
14  'makeFileList',
15  'forEach',
16  'eventLoop',
17  'findFHiCL',
18  'loadConfiguration',
19  'ConfigurationClass',
20  'startMessageFacility',
21  'ServiceRegistryClass',
22  'ConfigurationHelper',
23  ]
24 
25 import sys, os
26 import uuid
27 from ROOTutils import ROOT
28 import cppUtils
29 import warnings
30 
31 
32 ################################################################################
33 ### Pass-through
34 ###
35 SourceCode = cppUtils.SourceCode
36 readHeader = cppUtils.readHeader
37 
38 
39 ################################################################################
40 ### gallery
41 ################################################################################
42 
43 class HandleMaker:
44  """Make the ROOT C++ jit compiler instantiate the
45  Event::getValidHandle member template for template parameter klass.
46 
47  Needs to keep track of what was done already, because Cling will protest if
48  the same class is asked twice.
49  """
50  AlreadyMade = set()
51 
52  def __call__(self, klass):
53  if klass in HandleMaker.AlreadyMade: return
54  res = HandleMaker.make(klass)
55  if res != ROOT.TInterpreter.kNoError:
56  raise RuntimeError(
57  "Could not create `ROOT.gallery.Event.getValidHandle` for '%s' (code: %d)"
58  % (klass, res)
59  )
60  # if
61  HandleMaker.AlreadyMade.add(klass)
62  # if already there
63  return event.getValidHandle[klass] if event \
64  else ROOT.gallery.Event.getValidHandle[klass]
65  # __call__()
66 
67  @staticmethod
68  def make(klass):
69  ROOT.gROOT.ProcessLine('template gallery::ValidHandle<%(name)s> gallery::Event::getValidHandle<%(name)s>(art::InputTag const&) const;' % {'name' : klass})
70 
71 # class HandleMaker
72 make_getValidHandle = HandleMaker()
73 
74 
75 
76 def makeFileList(*filePaths):
77  """Creates a file list suitable for `gallery::Event`."""
78  files = ROOT.vector(ROOT.string)()
79  for path in filePaths: files.push_back(path)
80  return files
81 # makeFileList()
82 
83 
84 def forEach(event):
85  """
86  Simplifies sequential event looping.
87 
88  This function handles the state of `event`, moving the current event around.
89  Therefore, it has side effects.
90  The function returns `event` itself, after it has set to the next available
91  event.
92 
93  This function is actually a generator.
94 
95  Example of loop:
96 
97  for iEvent, event in enumerate(forEach(event)):
98  ...
99 
100 
101  """
102  while (not event.atEnd()):
103  yield event
104  event.next()
105  # while
106 # forEach()
107 
108 
109 class EventIterator:
110  """
111  Iterator for an event sequence.
112 
113  It can be used directly as:
114 
115  for event in EventIterator(event): ...
116 
117  or it can be plugged in directly in the gallery.Event class, making the latter
118  be iterable:
119 
120  for event in event: ...
121 
122  (or `for evt in event: ...`).
123  """
124  def __init__(self, event):
125  self._event = event
126  self._index = None
127  def __iter__(self):
128  self._index = None
129  return self
130  def next(self):
131  if self._index is not None:
132  self._event.next()
133  self._index += 1
134  if self._event.atEnd(): raise StopIteration
135  return self._event
136  # __next__
137 
138 # class EventIterator
139 
140 
141 def eventLoop(inputFiles,
142  process,
143  options = {},
144  ):
145  """
146  Applies the `process` function to each and every event from the specified
147  input files, in sequence.
148 
149  The `inputFiles` list may be a single file, or a list (or any iterable object)
150  of files, or a `std::vector<std::string>` object.
151 
152  The `process` callable is executed with two arguments: the number of argument
153  in the loop, and the event itself. No information on which file the event is
154  taken from is provided. If a call returns exactly `False`, it is considered
155  to have failed and an error counter is incremented. Exceptions raised in
156  `process` are not handled.
157 
158  The error counter is returned at the end of the execution.
159 
160  Options:
161  - 'nEvents': number of events to be processed (does not include skipped ones)
162  - 'nSkip': number of events from the beginning of the sample to be skipped
163  """
164 
165  # option reading
166  nSkip = options.get('nSkip', 0)
167  nEvents = options.get('nEvents', None)
168 
169  # make sure the input file list is in the right format
170  if not isinstance(inputFiles, ROOT.vector(ROOT.string)):
171  if isinstance(inputFiles, str): inputFiles = [ inputFiles, ]
172  inputFiles = makeFileList(*inputFiles)
173  # if
174 
175  event = ROOT.gallery.Event(inputFiles)
176 
177  # ROOT.gStyle.SetOptStat(0)
178 
179  iEvent = 0
180  nProcessedEvents = 0
181  nErrors = 0
182 
183  iFile = None
184  for iEvent, event in enumerate(forEach(event)):
185 
186  if iFile != event.fileEntry():
187  iFile = event.fileEntry()
188  print("Opening: '%s'" % inputFiles[iFile])
189  # if new file
190 
191  # event flow control
192  if iEvent < nSkip: continue
193  if (nEvents is not None) and (nProcessedEvents >= nEvents): break
194  nProcessedEvents += 1
195 
196  ###
197  ###
198  ###
199  res = process(event, iEvent)
200  if isinstance(res, bool) and not res: nErrors += 1
201 
202  ###
203  ### all done
204  ###
205 
206  # for
207  if nErrors > 0:
208  print >>sys.stderr, "Encountered %d/%d errors." % (nErrors, nProcessedEvents)
209  return nErrors
210 # eventLoop()
211 
212 
213 
214 # this does not really work...
215 # ROOT.gallery.Event.__iter__ = lambda self: EventIterator(self)
216 
217 
218 ################################################################################
219 ### Infrastructure
220 ################################################################################
221 
222 def findFHiCL(configRelPath, extraDirs = []):
223 
224  if os.path.isfile(configRelPath):
225  return os.path.join(os.getcwd(), configRelPath)
226  for path in extraDirs + os.environ.get('FHICL_FILE_PATH', "").split(':'):
227  candidate = os.path.join(path, configRelPath)
228  if os.path.isfile(candidate): return candidate
229  else: return None
230 
231 # findFHiCL()
232 
233 
234 ################################################################################
235 class ConfigurationHelper:
236  """
237  Provides more user-friendly interface for the configuration access.
238 
239  While FHiCL C++ interface is friendly enough, some of that is lost in Python
240  bindings.
241 
242  This helper class keeps a reference to the FHiCL configuration object, and
243  provides some `get()` overloads to make the interface easier.
244  """
245  def __init__(self, config):
246  self.config = config
247  def get(self, key, default=None, klass=None):
248  """
249  Returns the value associated with the specified key.
250 
251  Parameters
252  -----------
253 
254  key _(string)_
255  the key of the configuration parameter to be read
256  default
257  the value to provide if no key is present; if default is `None`,
258  no default is present and if key is missing a `KeyError` exception will
259  be raised
260  klass _(class type)_
261  type of object returned (if `None`, it's taken from `default` if
262  specified)
263 
264  Returns
265  --------
266 
267  An object of type `klass`, initialised with the value associated to `key`
268  or with `default`.
269 
270  Raises
271  -------
272 
273  `KeyError` if the `key` is not present in the configuration and `default` is
274  specified as `None`.
275 
276  """
277  if klass is None:
278  assert default is not None
279  klass = default.__class__
280  # first try to directly return the key
281  try: return self.config.get[klass](key)
282  except Exception: pass
283  # if that failed, act depending on the default value
284  if default is None: raise KeyError(key)
285  return klass(default)
286  # get()
287 
288  def has(self, key):
289  """
290  Returns whether there is a value associated with the specified key.
291 
292  Parameters
293  -----------
294 
295  key _(string)_
296  the key of the configuration parameter to be read
297 
298  """
299  return self.config.has(klass)
300  # has()
301 
302  __call__ = get # alias
303 
304  def pset(self): return self.config
305 
306 # class ConfigurationHelper
307 
309  def __init__(self, data = None):
310  with warnings.catch_warnings():
311  # os.tempnam() is a potential security risk: ACK
312  warnings.filterwarnings("ignore", ".*tempnam .*", RuntimeWarning)
313  self._file = open(str(uuid.uuid4()), "w+")
314  self.name = self._file.name
315  if data is not None:
316  self._file.write(str(data))
317  self._file.flush() # we are not going to close this file...
318  #
319  # __init__()
320  def __del__(self):
321  if not self._file: return
322  del self._file
323  os.remove(self.name)
324  # __del__()
325  def file_(self): return self._file
326  def __str__(self): return self.name
327 # class TemporaryFile
328 
329 
330 def loadConfiguration(configSpec):
331  # this utility actually relies on generic utilities that while not LArSoft
332  # specific, are nevertheless distributed with LArSoft (`larcorealg`).
333  SourceCode.loadHeaderFromUPS("larcorealg/Geometry/StandaloneBasicSetup.h")
334 
335  if isinstance(configSpec, ConfigurationString):
336  configFile = TemporaryFile(configSpec)
337  configPath = configFile.name
338  else:
339  configFile = None
340  configPath = configSpec
341  # if
342 
343  fullPath = findFHiCL(configPath)
344  if not fullPath:
345  raise RuntimeError("Couldn't find configuration file '%s'" % configPath)
346  return ROOT.lar.standalone.ParseConfiguration(fullPath)
347 # loadConfiguration()
348 
349 
350 class ConfigurationString:
351  """Wrapper to a string that should be considered configuration text."""
352  def __init__(self, config): self.config = config
353  def __str__(self): return self.config
354 # class ConfigurationString
355 
356 
357 class ConfigurationClass:
358  def __init__(self, configPath):
359  self.config = loadConfiguration(configPath)
360  def paramsFor(self, FHiCLpath):
361  return self.config.get[ROOT.fhicl.ParameterSet](FHiCLpath)
362  def service(self, serviceName):
363  return self.paramsFor("services." + serviceName)
364  def producer(self, moduleName):
365  return self.paramsFor("physics.producers." + moduleName)
366 # class ConfigurationClass
367 
368 
369 ################################################################################
371  def __init__(self, config):
372  self.fullConfig = config if isinstance(config, ConfigurationClass) else ConfigurationClass(config)
373  self.services = {}
374  # __init__(self)
375 
376  def config(self, serviceName): return self.fullConfig.service(serviceName)
377 
378  def has(self, serviceName): return serviceName in self.services
379 
380  def register(self, serviceName, service):
381  self.services[serviceName] = service
382  return service
383 
384  def registeredServiceNames(self): return self.services.keys()
385 
386  def registry(self): return self # behave like a manager (LArSoftUtils concept)
387 
388  def get(self, serviceName): return self.services[serviceName]
389  def __call__(self, serviceName): return self.get(serviceName)
390 
391  def create(self, serviceName, serviceClass, *otherServiceConstructorArgs):
392  serviceConfig = self.config(serviceName)
393  if not serviceConfig:
394  raise RuntimeError("Couldn't find the configuration for service '%s'" % serviceName)
395  service = serviceClass(serviceConfig, *otherServiceConstructorArgs)
396  if not service:
397  raise RuntimeError("Failed to create service '%s' (type: %s)" % (serviceName, serviceClass.__class__.__name__))
398  return self.register(serviceName, service)
399  # create()
400 
401 # class ServiceRegistryClass
402 
403 
404 ################################################################################
406  """Use it as a function: `startMessageFacility(config, applName)`.
407 
408  `config` either is `ConfigurationClass` object, or it is a parameter set
409  with the configuration for the service.
410  """
411  Init = False
412  def __init__(self, config, applName = None):
413  if not startMessageFacility.Init: self.init(config, applName)
414  def init(self, config, applName):
415  if not applName: applName = os.path.basename(sys.argv[0])
416  if isinstance(config, ConfigurationClass): config = config.service("message")
417  print("Starting message facility for %s..." % applName)
418  ROOT.mf.StartMessageFacility(config, applName)
419  startMessageFacility.Init = True
420  # init()
421 # class startMessageFacility
422 
423 
424 ################################################################################
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
open(RACETRACK) or die("Could not open file $RACETRACK for writing")