Repository navigation
Expand file tree
/
Copy path_ttree.py
More file actions
677 lines (515 loc) · 21.9 KB
/
Copy path_ttree.py
File metadata and controls
677 lines (515 loc) · 21.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
# Author: Enric Tejedor CERN 06/2018
################################################################################
# Copyright (C) 1995-2018, Rene Brun and Fons Rademakers. #
# All rights reserved. #
# #
# For the licensing terms see $ROOTSYS/LICENSE. #
# For the list of contributors see $ROOTSYS/README/CREDITS. #
################################################################################
r"""
\pythondoc TTree
The TTree class has several additions for its use from Python, which are also
available in its subclasses e.g. TChain and TNtuple.
First, TTree instances are iterable in Python. Therefore, assuming `t` is
a TTree instance, we can do:
\code{.py}
for entry in t:
x = entry.branch_name
...
\endcode
At each iteration, a new entry of the tree will be read. In the code above,
`entry` allows to access the branch values for the current entry. This can be
done with the syntax `entry.branch_name` or, if the branch name is incompatible
with Python naming rules, with e.g. "getattr(entry, '1_branch_name')".
<em>Please note</em> that iterating in Python can be slow, so only iterate over
a tree as described above if performance is not an issue or when dealing with
a small dataset. To read and process the entries of a tree in a much faster
way, please use ROOT::RDataFrame.
Two methods of TTree have been pythonized to facilitate their: TTree::Branch and
TTree::SetBranchAddress.
### Pythonization of TTree::Branch
The following example shows how we can create different types of branches of a TTree.
`Branch` links the new branch with a given Python object. It is therefore possible to
fill such object with the desired content before calling TTree::Fill.
\code{.py}
from array import array
import numpy as np
import ROOT
# We create the file and the tree
with ROOT.TFile("outfile.root", "RECREATE") as ofile:
t = ROOT.TTree("mytree", "mytree")
# Basic type branch (float) - use array of length 1
n = array('f', [ 1.5 ])
t.Branch('floatb', n, 'floatb/F')
# Array branch - use array of length N
N = 10
a = array('d', N*[ 0. ])
t.Branch('arrayb', a, 'arrayb[' + str(N) + ']/D')
# Array branch - use NumPy array of length N
npa = np.array(N*[ 0. ])
t.Branch('nparrayb', npa, 'nparrayb[' + str(N) + ']/D')
# std::vector branch
v = ROOT.std.vector('double')(N*[ 0. ])
t.Branch('vectorb0', v)
# Class branch / struct in single branch
cb = ROOT.TH1D("myHisto", "myHisto", 64, -4, 4)
# This could have been any class known to ROOT, also custom
#cb = ROOT.MyCustomClass()
t.Branch('classb', cb)
# Struct as leaflist. This is interpreted on the fly,
# but could be known to ROOT by other means, such as
# header inclusion or dictionary load.
ROOT.gInterpreter.Declare('''
struct MyStruct {
int myint;
float myfloat;
};
''')
ms = ROOT.MyStruct()
t.Branch('structll', ms, 'myint/I:myfloat/F')
# Store struct members individually
ms = ROOT.MyStruct()
# Use the `addressof` function in the ROOT module
# to get the address of the struct members
t.Branch('myintb', ROOT.addressof(ms, 'myint'), 'myint/I')
t.Branch('myfloatb', ROOT.addressof(ms, 'myfloat'), 'myfloat/F')
# Let's write one entry in our tree
t.Fill()
# Finally flush the content of the tree to the file
t.Write()
\endcode
### Pythonization of TTree::SetBranchAddress
This section is to be considered for advanced users. Simple event
loops reading tree entries in Python can be performed as shown above.
Below an example is shown of reading different types tree branches.
Note that `SetBranchAddress` will just link a given branch with a
certain Python object; after that, in order to read the content of such
branch for a given TTree entry `x`, TTree::GetEntry(x) must be
invoked.
\code{.py}
from array import array
import numpy as np
import ROOT
with ROOT.TFile('outfile.root') as infile:
t = infile['mytree']
# Basic type branch (float) - use array of length 1
n = array('f', [ 0. ])
t.SetBranchAddress('floatb', n)
# Array branch - use array of length N
N = 10
a = array('d', N*[ 0. ])
t.SetBranchAddress('arrayb', a)
# Array branch - use NumPy array of length N
npa = np.array(N*[ 0. ])
t.SetBranchAddress('nparrayb', a)
# std::vector branch
v = ROOT.std.vector('double')()
t.SetBranchAddress('vectorb', v)
# Class branch
cb = ROOT.TH1D()
# Any other class known to ROOT would have worked
#cb = ROOT.MyClass()
t.SetBranchAddress('classb', cb)
# Struct as leaflist. This is interpreted on the fly,
# but could be known to ROOT by other means, such as
# header inclusion or dictionary load.
ROOT.gInterpreter.Declare('''
struct MyStruct {
int myint;
float myfloat;
};
''')
ms = ROOT.MyStruct()
t.SetBranchAddress('structll', ms)
t.GetEntry(0)
\endcode
\endpythondoc
"""
from . import pythonization
from ._memory_utils import (
_constructor_releasing_ownership,
_SetDirectory_SetOwnership,
_should_give_up_ownership,
)
from ._rvec import _get_cpp_type_from_numpy_type
_branch_lookups = None
class _BranchLookups(object):
"""The entities that reading a branch needs, resolved once.
Every resolution through the ROOT module goes through the facade's
__getattr__, which dominated the cost of `tree.branch` when done inline;
none of these can change over the lifetime of a session.
"""
__slots__ = ("helpers", "branch_element", "branch_object", "leaf_element", "leaf_object", "ll")
def _lookups():
"""Return the cached branch lookups, resolving them on first use.
The helpers in ROOT::Internal::TreeUtils exist because
TBranch::GetAddress() and TBranchElement::GetObject() return char* and so
reach Python as str, with the pointer value gone. Two threads racing here
resolve the same entities, so no lock is needed.
"""
global _branch_lookups
if _branch_lookups is None:
import ROOT
from cppyy import ll
lookups = _BranchLookups()
lookups.helpers = ROOT.Internal.TreeUtils
lookups.branch_element = ROOT.TBranchElement.Class()
lookups.branch_object = ROOT.TBranchObject.Class()
lookups.leaf_element = ROOT.TLeafElement.Class()
lookups.leaf_object = ROOT.TLeafObject.Class()
lookups.ll = ll
_branch_lookups = lookups
return _branch_lookups
# TTree iterator
def _TTree__iter__(self):
i = 0
bytes_read = self.GetEntry(i)
while 0 < bytes_read:
yield self
i += 1
bytes_read = self.GetEntry(i)
if bytes_read == -1:
raise RuntimeError("TTree I/O error")
def _pythonize_branch_addr(branch, addr_orig):
"""Helper for the SetBranchAddress pythonization, extracting the relevant
address from a Python object if possible.
"""
import ctypes
import ROOT
is_leaf_list = branch.IsA() is ROOT.TBranch.Class()
if is_leaf_list:
# If the branch is a leaf list, SetBranchAddress expects the
# address of the object that has the corresponding data members.
return ctypes.c_void_p(ROOT._cppyy.addressof(instance=addr_orig, byref=False))
# Otherwise, SetBranchAddress is expecting a pointer to the address of
# the object, and the pointer needs to stay alive. Therefore, we create
# a container for the pointer and cache it in the original cppyy proxy.
addr_view = ROOT.array["std::intptr_t", 1]([ROOT._cppyy.addressof(instance=addr_orig, byref=False)])
if not hasattr(addr_orig, "_set_branch_cached_pointers"):
addr_orig._set_branch_cached_pointers = []
addr_orig._set_branch_cached_pointers.append(addr_view)
# Finally, we have to return the address of the container
return ctypes.c_void_p(ROOT._cppyy.addressof(instance=addr_view, byref=False))
def _get_cpp_type_from_array_typecode(typecode):
# Complete list from https://docs.python.org/3/library/array.html
c_type_names = {
"b": "signed char",
"B": "unsigned char",
"u": "wchar_t",
"h": "signed short",
"H": "unsigned short",
"i": "signed int",
"I": "unsigned int",
"l": "signed long",
"L": "unsigned long",
"q": "signed long long",
"Q": "unsigned long long",
"f": "float",
"d": "double",
}
return c_type_names[typecode]
def _determine_data_type(addr):
"""Figure out data_type in case addr is a numpy.ndarray or array.array."""
# For NumPy arrays
if hasattr(addr, "__array_interface__"):
return _get_cpp_type_from_numpy_type(addr.__array_interface__["typestr"][1:])
# For the builtin array library
if hasattr(addr, "buffer_info"):
return _get_cpp_type_from_array_typecode(addr.typecode)
return None
def _SetBranchAddress(self, bname, addr, *args, **kwargs):
"""
Pythonization for TTree::SetBranchAddress.
Modify the behaviour of SetBranchAddress so that proxy references can be passed
as arguments from the Python side, more precisely in cases where the C++
implementation of the method expects the address of a pointer.
For example:
```
v = ROOT.std.vector('int')()
t.SetBranchAddress("my_vector_branch", v)
```
"""
import cppyy
import ROOT
branch = self.GetBranch(bname)
# Pythonization for cppyy proxies (of type CPPInstance)
if isinstance(addr, ROOT._cppyy.types.Instance):
addr = _pythonize_branch_addr(branch, addr)
# Figure out data_type in case addr is a numpy.ndarray or array.array
data_type = _determine_data_type(addr)
if data_type is None:
return self._OriginalSetBranchAddress(bname, addr, *args, **kwargs)
# In the case the data_type is available, we would like to call the
# template overload of SetBranchAddress instantiatied for that type.
# However, there are two such overloads candidates:
#
# template <class T> int TTree::SetBranchAddress(const char *bname, T **add, ...);
# template <class T> int TTree::SetBranchAddress(const char *bname, T *add, ...);
#
# The cppyy bindings can't make a meaningful selection here as Python is
# lacking pointer semantics, so it considers both overloads as valid
# choices. In the past, we just happened to be lucky that it tried the T *
# overload first, which is the one we need. But as cppyy becomes more
# strict about overload resolution ambiguity errors, this won't work
# anymore. That's why we re-implement what happens in the template overload
# on the Python side.
cl = ROOT.TClass.GetClass[data_type]()
tp = ROOT.kOther_t
if not cl:
tp = ROOT.TDataType.GetType(cppyy.typeid(getattr(ROOT, data_type)))
# Extract the TBranch ptr argument if available
tbranch_ptr = ROOT.nullptr
if len(args) > 0:
tbranch_ptr = args[0]
elif "ptr" in kwargs:
tbranch_ptr = kwargs["ptr"]
return self._OriginalSetBranchAddress(bname, addr, ptr=tbranch_ptr, realClass=cl, datatype=tp, isptr=False)
def _get_address_of(obj):
"""Return the address of the buffer or proxied object `obj` points at.
Returns None for anything that does not carry an address, and deliberately
also for text-like objects, which do but are never meant as a branch buffer.
"""
import ROOT
if isinstance(obj, ROOT._cppyy.types.Instance):
return ROOT._cppyy.addressof(instance=obj, byref=False)
if isinstance(obj, (str, bytes)):
return None
# A plain integer address is left to the original overloads. It must not
# reach ll.addressof, which converts it to a C long: that overflows where
# long is 32 bits (Windows) and raises OverflowError instead of TypeError.
if isinstance(obj, int):
return None
try:
return ROOT._cppyy.ll.addressof(obj)
except TypeError:
return None
def _try_branch_leaf_list_overload(self, args):
"""Try to match TTree::Branch(const char*, void*, const char*, Int_t = 32000)."""
if not (3 <= len(args) <= 4):
return None
name, address, leaflist = args[0], args[1], args[2]
if not isinstance(name, str) or not isinstance(leaflist, str):
return None
if len(args) == 4 and not isinstance(args[3], int):
return None
import ctypes
buf = _get_address_of(address)
if not buf:
return None
return self._OriginalBranch(name, ctypes.c_void_p(buf), leaflist, *args[3:])
def _try_branch_ptr_to_ptr_overloads(self, args):
"""Try to match one of the TTree::Branch overloads taking a T**:
- ( const char*, const char*, T**, Int_t = 32000, Int_t = 99 )
- ( const char*, T**, Int_t = 32000, Int_t = 99 )
"""
import ROOT
if len(args) < 2 or not isinstance(args[0], str):
return None
name = args[0]
if isinstance(args[1], str):
# the class name is given explicitly
class_name, address, rest = args[1], args[2] if len(args) > 2 else None, args[3:]
else:
class_name, address, rest = None, args[1], args[2:]
if address is None or any(not isinstance(arg, int) for arg in rest) or len(rest) > 2:
return None
if isinstance(address, ROOT._cppyy.types.Instance):
# Hand the proxy to a helper taking a T** and let cppyy work out which
# address that is: where it lives (the proxy itself, or the caller's
# pointer behind a reference proxy) is a rule only the bindings know.
# T is the proxied type, not class_name, which the caller is free to
# give as a base or equivalent spelling of it.
proxy_type = type(address).__cpp_name__
brancher = _lookups().helpers.BranchPtrToPtr[proxy_type]
return brancher(self, name, class_name or proxy_type, address, *rest)
buf = _get_address_of(address)
if not buf or not class_name:
return None
import ctypes
return self._OriginalBranch(name, class_name, ctypes.c_void_p(buf), *rest)
def _Branch(self, *args):
"""
Pythonization for TTree::Branch.
Modify the behaviour of Branch so that proxy references can be passed as
arguments from the Python side, more precisely in cases where the C++
implementation of the method expects the address of a pointer.
For example:
```
v = ROOT.std.vector('int')()
t.Branch('my_vector_branch', v)
```
The following signatures are treated in this pythonization:
- ( const char*, void*, const char*, Int_t = 32000 )
- ( const char*, const char*, T**, Int_t = 32000, Int_t = 99 )
- ( const char*, T**, Int_t = 32000, Int_t = 99 )
"""
if len(args) >= 2:
res = _try_branch_leaf_list_overload(self, args)
if res is not None:
return res
res = _try_branch_ptr_to_ptr_overloads(self, args)
if res is not None:
return res
# Fall back to the original implementation for the rest of overloads
return self._OriginalBranch(*args)
def _search_for_branch(tree, name):
branch = tree.GetBranch(name)
if not branch:
# for benefit of naming of sub-branches, the actual name may have a
# trailing '.'
branch = tree.GetBranch(name + ".")
return branch
def _has_single_leaf(branch):
leaves = branch.GetListOfLeaves()
# i.e. if unambiguously only this one
return bool(leaves.GetSize()) and leaves.First() == leaves.Last()
def _search_for_leaf(tree, name, branch):
leaf = tree.GetLeaf(name)
if branch and not leaf:
leaf = branch.GetLeaf(name)
if not leaf and _has_single_leaf(branch):
leaf = branch.GetListOfLeaves().At(0)
return leaf
def _resolve_branch(tree, name, branch):
"""Return the address and type name of the object held by a branch.
(None, "") means the branch holds no object (the caller should look for a
leaf instead); address 0 with a non-empty type name means failure, reported
to the user as a typed null object.
"""
lookups = _lookups()
helpers = lookups.helpers
# for partial return of a split object
if branch.InheritsFrom(lookups.branch_element):
current_class = branch.GetCurrentClass()
if current_class and current_class != branch.GetTargetClass() and branch.GetID() >= 0:
offset = branch.GetInfo().GetElements().At(branch.GetID()).GetOffset()
return helpers.GetBranchElementObject(branch) + offset, current_class.GetName()
# for return of a full object
if branch.IsA() in (lookups.branch_element, lookups.branch_object):
address = helpers.GetBranchAddress(branch)
if address:
# the branch address is that of the pointer to the object
return lookups.ll.bind_value("intptr_t", address), branch.GetClassName()
# try leaf, otherwise indicate failure by returning a typed null object
if not tree.GetLeaf(name) and not _has_single_leaf(branch):
return 0, branch.GetClassName()
return None, ""
def _get_multi_dims(title):
"""Extract the static dimensions from the title of a TLeaf.
The title carries them as `name[dim1][dim2]...`; TLeaf offers no other
way to get at them.
"""
import re
return [int(dim) for dim in re.findall(r"\[([^\]]*)\]", title) if dim]
def _wrap_leaf(leaf):
"""Read the value of a leaf for the entry the tree is currently on."""
lookups = _lookups()
ll = lookups.ll
if leaf.GetLenStatic() > 1 or leaf.GetLeafCount():
# array types
is_static = leaf.GetLenStatic() > 1
type_name = leaf.GetTypeName()
dims = [leaf.GetNdata()]
title = leaf.GetTitle()
if title.count("[") >= 2:
# multidimensional array case
dims = _get_multi_dims(title)
address = 0
branch = leaf.GetBranch()
if branch:
address = lookups.helpers.GetBranchAddress(branch)
if not address:
address = ll.addressof(leaf.GetValuePointer())
return ll.bind_value(type_name + ("[]" if is_static else "*"), address, dims)
value_pointer = leaf.GetValuePointer()
if value_pointer:
# value types
address = ll.addressof(value_pointer)
if leaf.IsA() in (lookups.leaf_element, lookups.leaf_object):
# the leaf holds a pointer to the value, rather than the value
address = ll.bind_value("intptr_t", address)
return ll.bind_value(leaf.GetTypeName(), address)
return None
def _TTree__getattr__(self, key):
"""
Allow branches to be accessed as attributes of a tree.
Allow access to branches/leaves as if they were Python data attributes of
the tree (e.g. mytree.branch).
Parameters:
self (TTree): The instance of the TTree object from which the attribute is being retrieved.
key (str): The name of the branch to retrieve from the TTree object.
"""
ll = _lookups().ll
# deal with possible aliasing
name = self.GetAlias(key) or key
# search for branch first (typical for objects)
branch = _search_for_branch(self, name)
if branch:
# found a branched object, wrap its address for the object it represents
address, type_name = _resolve_branch(self, name, branch)
if type_name:
return ll.cast[type_name + "*"](address)
# if not, try leaf
leaf = _search_for_leaf(self, name, branch)
if leaf:
# found a leaf, extract value and wrap with a Python object
# according to its type
value = _wrap_leaf(leaf)
if value is not None:
return value
# confused
raise AttributeError("'{}' object has no attribute '{}'".format(self.IsA().GetName(), name))
def _TTree_CloneTree(self, *args, **kwargs):
"""
Forward the arguments to the C++ function and give up ownership if the
TTree is attached to a TFile, which is the owner in that case.
"""
import ROOT
out_tree = self._CloneTree(*args, **kwargs)
if _should_give_up_ownership(out_tree):
ROOT.SetOwnership(out_tree, False)
return out_tree
@pythonization("TTree")
def pythonize_ttree(klass, name):
# Parameters:
# klass: class to be pythonized
# name: string containing the name of the class
# Functions that need to drop the ownership if the current directory is a TFile
klass._cpp_constructor = klass.__init__
klass.__init__ = _constructor_releasing_ownership
klass._CloneTree = klass.CloneTree
klass.CloneTree = _TTree_CloneTree
# Pythonizations that are common to TTree and its subclasses.
# To avoid duplicating the same logic in the pythonizors of
# the subclasses, inject the pythonizations for all the target
# classes here.
# Pythonic iterator
klass.__iter__ = _TTree__iter__
# tree.branch syntax
klass.__getattr__ = _TTree__getattr__
# SetBranchAddress
klass._OriginalSetBranchAddress = klass.SetBranchAddress
klass.SetBranchAddress = _SetBranchAddress
# Branch
klass._OriginalBranch = klass.Branch
klass.Branch = _Branch
klass._Original_SetDirectory = klass.SetDirectory
klass.SetDirectory = _SetDirectory_SetOwnership
@pythonization("TChain")
def pythonize_tchain(klass):
# Parameters:
# klass: class to be pythonized
# TChain needs to be explicitly pythonized because it redefines
# SetBranchAddress in C++. As a consequence, TChain does not
# inherit TTree's pythonization for SetBranchAddress, which
# needs to be injected to TChain too. This is not the case for
# other classes like TNtuple, which will inherit all the
# pythonizations added here for TTree.
# SetBranchAddress
klass._OriginalSetBranchAddress = klass.SetBranchAddress
klass.SetBranchAddress = _SetBranchAddress
@pythonization("TNtuple")
def pythonize_tntuple(klass):
# The constructor needs to be explicitly pythonized for derived classes.
klass._cpp_constructor = klass.__init__
klass.__init__ = _constructor_releasing_ownership