rootloader 1.3.0__tar.gz → 1.4.0__tar.gz

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.
Files changed (39) hide show
  1. rootloader-1.4.0/.claude/settings.local.json +14 -0
  2. rootloader-1.4.0/.coverage +0 -0
  3. rootloader-1.4.0/CLAUDE.md +31 -0
  4. {rootloader-1.3.0 → rootloader-1.4.0}/PKG-INFO +4 -1
  5. {rootloader-1.3.0 → rootloader-1.4.0}/docs/rootloader/tdirectory.md +3 -3
  6. {rootloader-1.3.0 → rootloader-1.4.0}/docs/rootloader/th2.md +1 -1
  7. {rootloader-1.3.0 → rootloader-1.4.0}/docs/rootloader/ttree.md +90 -33
  8. {rootloader-1.3.0 → rootloader-1.4.0}/pyproject.toml +3 -0
  9. {rootloader-1.3.0 → rootloader-1.4.0}/rootloader/tdirectory.py +6 -1
  10. {rootloader-1.3.0 → rootloader-1.4.0}/rootloader/th2.py +3 -2
  11. {rootloader-1.3.0 → rootloader-1.4.0}/rootloader/tleaf.py +1 -1
  12. {rootloader-1.3.0 → rootloader-1.4.0}/rootloader/ttree.py +53 -111
  13. rootloader-1.4.0/rootloader/version.py +1 -0
  14. rootloader-1.4.0/tests/README.md +315 -0
  15. rootloader-1.4.0/tests/conftest.py +319 -0
  16. rootloader-1.4.0/tests/test_attrdict.py +159 -0
  17. rootloader-1.4.0/tests/test_package.py +47 -0
  18. rootloader-1.4.0/tests/test_tdirectory.py +368 -0
  19. rootloader-1.4.0/tests/test_tfile.py +278 -0
  20. rootloader-1.4.0/tests/test_th1.py +341 -0
  21. rootloader-1.4.0/tests/test_th2.py +301 -0
  22. rootloader-1.4.0/tests/test_tleaf.py +128 -0
  23. rootloader-1.4.0/tests/test_ttree.py +743 -0
  24. rootloader-1.3.0/rootloader/version.py +0 -1
  25. {rootloader-1.3.0 → rootloader-1.4.0}/.gitignore +0 -0
  26. {rootloader-1.3.0 → rootloader-1.4.0}/LICENSE +0 -0
  27. {rootloader-1.3.0 → rootloader-1.4.0}/README.md +0 -0
  28. {rootloader-1.3.0 → rootloader-1.4.0}/docs/README.md +0 -0
  29. {rootloader-1.3.0 → rootloader-1.4.0}/docs/rootloader/attrdict.md +0 -0
  30. {rootloader-1.3.0 → rootloader-1.4.0}/docs/rootloader/index.md +0 -0
  31. {rootloader-1.3.0 → rootloader-1.4.0}/docs/rootloader/tfile.md +0 -0
  32. {rootloader-1.3.0 → rootloader-1.4.0}/docs/rootloader/th1.md +0 -0
  33. {rootloader-1.3.0 → rootloader-1.4.0}/docs/rootloader/tleaf.md +0 -0
  34. {rootloader-1.3.0 → rootloader-1.4.0}/docs/rootloader/version.md +0 -0
  35. {rootloader-1.3.0 → rootloader-1.4.0}/gen_documentation.bash +0 -0
  36. {rootloader-1.3.0 → rootloader-1.4.0}/rootloader/__init__.py +0 -0
  37. {rootloader-1.3.0 → rootloader-1.4.0}/rootloader/attrdict.py +0 -0
  38. {rootloader-1.3.0 → rootloader-1.4.0}/rootloader/tfile.py +0 -0
  39. {rootloader-1.3.0 → rootloader-1.4.0}/rootloader/th1.py +0 -0
@@ -0,0 +1,14 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(python -m py_compile rootloader/th2.py rootloader/tleaf.py rootloader/ttree.py rootloader/tdirectory.py)",
5
+ "Bash(python3 -m py_compile rootloader/th2.py rootloader/tleaf.py rootloader/ttree.py rootloader/tdirectory.py)",
6
+ "Bash(python -m pytest tests/ -x --tb=short -q)",
7
+ "Bash(python3 -m pytest tests/ --tb=short -q)",
8
+ "Bash(python3 -m pytest tests/ --tb=no -q)",
9
+ "Bash(python3 -m pytest tests/test_ttree.py::test_stats_scalar_single_column_float tests/test_ttree.py::test_loc_slice_reduces_size --tb=long -q)",
10
+ "Bash(python3 -m pytest tests/ -v --tb=short -q)",
11
+ "Bash(python3 -m pytest tests/ -v --tb=short)"
12
+ ]
13
+ }
14
+ }
Binary file
@@ -0,0 +1,31 @@
1
+ # CLAUDE.md
2
+
3
+ ## Project Overview
4
+
5
+ `rootloader` is a Python library that wraps PyROOT to load ROOT files into memory as Python-native objects (numpy arrays, pandas DataFrames). It targets physicists at TRIUMF who work with ROOT data but prefer Python workflows.
6
+
7
+ ## Architecture
8
+
9
+ The class hierarchy mirrors ROOT's object model:
10
+
11
+ ```
12
+ attrdict (dict subclass — attribute-style key access)
13
+ └── tdirectory — wraps ROOT.TDirectoryFile or ROOT.TFile; iterates keys and dispatches to typed wrappers
14
+ └── tfile — entry point; opens a ROOT.TFile and delegates to tdirectory
15
+
16
+ ttree — wraps ROOT.TTree via RDataFrame; supports lazy column selection, filters, and conversion to pandas/numpy
17
+ th1 — wraps ROOT.TH1; stores x/y/dy arrays and provides .plot() and .to_dataframe()
18
+ th2 — wraps ROOT.TH2; stores x/y/z/dz arrays and provides .plot() and .to_dataframe()
19
+ tleaf — thin wrapper around ROOT.TLeaf for direct leaf-level access
20
+ ```
21
+
22
+ **Key design decisions:**
23
+
24
+ - `attrdict` makes all dict keys accessible as attributes (`fid.tree1` == `fid['tree1']`). This is the base for `tdirectory`.
25
+ - `tdirectory.__init__` resolves ROOT key cycles (keeps highest cycle number) and dispatches each object to `ttree`, `th1`, `th2`, or a nested `tdirectory`.
26
+ - `ttree` is lazy: it holds a `ROOT.RDataFrame` and only materializes data when `.to_dataframe()`, `.to_dict()`, `.to_array()`, or a stats method (`.min()`, `.max()`, etc.) is called. Column selection via `__getitem__` returns a view (not a copy).
27
+ - Stats methods (`min`, `max`, `mean`, `sum`, `std`) use JIT-compiled C++ templates (`cpp_template` in `ttree.py`) to avoid memory creep from repeated ROOT JIT compilations. Each unique `(function, dtype)` pair is compiled once and cached on the ROOT module.
28
+ - `th1.to_dataframe()` and `th2.to_dataframe()` store reconstruction metadata in `df.attrs` so the original typed object can be restored via `tdirectory.from_dataframe()`.
29
+ - `ROOT.EnableImplicitMT()` is called at import time in `ttree.py` to enable multithreading for RDataFrame operations.
30
+ - `ROOT.gROOT.SetBatch(1)` is set at import in `__init__.py` to suppress graphical output.
31
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rootloader
3
- Version: 1.3.0
3
+ Version: 1.4.0
4
4
  Summary: Read simple root files into memory
5
5
  Project-URL: Homepage, https://github.com/ucn-triumf/rootloader
6
6
  Project-URL: Bug Tracker, https://github.com/ucn-triumf/rootloader/issues
@@ -687,6 +687,9 @@ Requires-Dist: matplotlib
687
687
  Requires-Dist: numpy
688
688
  Requires-Dist: pandas
689
689
  Requires-Dist: tqdm
690
+ Provides-Extra: test
691
+ Requires-Dist: pytest; extra == 'test'
692
+ Requires-Dist: pytest-cov; extra == 'test'
690
693
  Description-Content-Type: text/markdown
691
694
 
692
695
  # ROOTLOADER
@@ -41,7 +41,7 @@ class tdirectory(attrdict):
41
41
 
42
42
  ### tdirectory.copy
43
43
 
44
- [Show source in tdirectory.py:122](../../rootloader/tdirectory.py#L122)
44
+ [Show source in tdirectory.py:127](../../rootloader/tdirectory.py#L127)
45
45
 
46
46
  Make a copy of this object
47
47
 
@@ -53,7 +53,7 @@ def copy(self): ...
53
53
 
54
54
  ### tdirectory.from_dataframe
55
55
 
56
- [Show source in tdirectory.py:131](../../rootloader/tdirectory.py#L131)
56
+ [Show source in tdirectory.py:136](../../rootloader/tdirectory.py#L136)
57
57
 
58
58
  Convert all elements contained in self to original objects
59
59
 
@@ -65,7 +65,7 @@ def from_dataframe(self): ...
65
65
 
66
66
  ### tdirectory.to_dataframe
67
67
 
68
- [Show source in tdirectory.py:142](../../rootloader/tdirectory.py#L142)
68
+ [Show source in tdirectory.py:147](../../rootloader/tdirectory.py#L147)
69
69
 
70
70
  Convert all objects possible (th1, th2, and ttree) into pandas dataframes
71
71
 
@@ -75,7 +75,7 @@ def plot(self, ax=None, flat=True, **kwargs): ...
75
75
 
76
76
  ### th2.to_dataframe
77
77
 
78
- [Show source in th2.py:159](../../rootloader/th2.py#L159)
78
+ [Show source in th2.py:160](../../rootloader/th2.py#L160)
79
79
 
80
80
  Convert tree to pandas dataframe
81
81
 
@@ -4,6 +4,12 @@
4
4
 
5
5
  > Auto-generated documentation for [rootloader.ttree](../../rootloader/ttree.py) module.
6
6
 
7
+ #### Attributes
8
+
9
+ - `cpp_template` - template to pre-compile stats functions to avoid memory creep during JIT compilations
10
+ used in ttree._get_stat: '\n{TYPE2} RDF_{FNNAME}_{TYPE2}({DTYPE} df, string col){\n return df.{FNNAME}<{TYPE2}>(col).GetValue.\n}\n'
11
+
12
+
7
13
  - [ttree](#ttree)
8
14
  - [ttree](#ttree-1)
9
15
  - [ttree.__getitem__](#ttree__getitem__)
@@ -19,17 +25,19 @@
19
25
  - [ttree.min](#ttreemin)
20
26
  - [ttree.reset](#ttreereset)
21
27
  - [ttree.reset_columns](#ttreereset_columns)
22
- - [ttree.rms](#ttreerms)
23
28
  - [ttree.set_filter](#ttreeset_filter)
24
29
  - [ttree.set_index](#ttreeset_index)
25
30
  - [ttree.size](#ttreesize)
26
31
  - [ttree.std](#ttreestd)
32
+ - [ttree.sum](#ttreesum)
33
+ - [ttree.to_array](#ttreeto_array)
27
34
  - [ttree.to_dataframe](#ttreeto_dataframe)
28
35
  - [ttree.to_dict](#ttreeto_dict)
36
+ - [ttree.values](#ttreevalues)
29
37
 
30
38
  ## ttree
31
39
 
32
- [Show source in ttree.py:14](../../rootloader/ttree.py#L14)
40
+ [Show source in ttree.py:22](../../rootloader/ttree.py#L22)
33
41
 
34
42
  Extract ROOT.TTree with lazy operation. Looks like a dataframe in most ways
35
43
 
@@ -43,12 +51,12 @@ Extract ROOT.TTree with lazy operation. Looks like a dataframe in most ways
43
51
 
44
52
  ```python
45
53
  class ttree(object):
46
- def __init__(self, tree): ...
54
+ def __init__(self, tree, filter_string=None, columns=None): ...
47
55
  ```
48
56
 
49
57
  ### ttree.__getitem__
50
58
 
51
- [Show source in ttree.py:79](../../rootloader/ttree.py#L79)
59
+ [Show source in ttree.py:90](../../rootloader/ttree.py#L90)
52
60
 
53
61
  Fetch a new dataframe with fewer 'columns', as a memory view
54
62
 
@@ -60,7 +68,9 @@ def __getitem__(self, key): ...
60
68
 
61
69
  ### ttree.columns
62
70
 
63
- [Show source in ttree.py:318](../../rootloader/ttree.py#L318)
71
+ [Show source in ttree.py:345](../../rootloader/ttree.py#L345)
72
+
73
+ Return list of column (branch) names
64
74
 
65
75
  #### Signature
66
76
 
@@ -71,7 +81,9 @@ def columns(self): ...
71
81
 
72
82
  ### ttree.filters
73
83
 
74
- [Show source in ttree.py:321](../../rootloader/ttree.py#L321)
84
+ [Show source in ttree.py:349](../../rootloader/ttree.py#L349)
85
+
86
+ Return list of RDataFrame filters
75
87
 
76
88
  #### Signature
77
89
 
@@ -82,7 +94,7 @@ def filters(self): ...
82
94
 
83
95
  ### ttree.hist1d
84
96
 
85
- [Show source in ttree.py:129](../../rootloader/ttree.py#L129)
97
+ [Show source in ttree.py:139](../../rootloader/ttree.py#L139)
86
98
 
87
99
  Return histogram of column
88
100
 
@@ -107,7 +119,7 @@ def hist1d(self, column=None, nbins=None, step=None, edges=None): ...
107
119
 
108
120
  ### ttree.hist2d
109
121
 
110
- [Show source in ttree.py:182](../../rootloader/ttree.py#L182)
122
+ [Show source in ttree.py:192](../../rootloader/ttree.py#L192)
111
123
 
112
124
  Return histogram of two columns
113
125
 
@@ -131,7 +143,9 @@ def hist2d(
131
143
 
132
144
  ### ttree.index
133
145
 
134
- [Show source in ttree.py:324](../../rootloader/ttree.py#L324)
146
+ [Show source in ttree.py:353](../../rootloader/ttree.py#L353)
147
+
148
+ Return ttree of just the index data
135
149
 
136
150
  #### Signature
137
151
 
@@ -142,7 +156,9 @@ def index(self): ...
142
156
 
143
157
  ### ttree.index_name
144
158
 
145
- [Show source in ttree.py:327](../../rootloader/ttree.py#L327)
159
+ [Show source in ttree.py:357](../../rootloader/ttree.py#L357)
160
+
161
+ Return string of the name of the index branch
146
162
 
147
163
  #### Signature
148
164
 
@@ -153,7 +169,9 @@ def index_name(self): ...
153
169
 
154
170
  ### ttree.loc
155
171
 
156
- [Show source in ttree.py:330](../../rootloader/ttree.py#L330)
172
+ [Show source in ttree.py:361](../../rootloader/ttree.py#L361)
173
+
174
+ Return a ttree that can be indexed like a pandas dataframe
157
175
 
158
176
  #### Signature
159
177
 
@@ -164,7 +182,9 @@ def loc(self): ...
164
182
 
165
183
  ### ttree.max
166
184
 
167
- [Show source in ttree.py:354](../../rootloader/ttree.py#L354)
185
+ [Show source in ttree.py:402](../../rootloader/ttree.py#L402)
186
+
187
+ Return the max value of the tree, for each branch
168
188
 
169
189
  #### Signature
170
190
 
@@ -174,7 +194,9 @@ def max(self): ...
174
194
 
175
195
  ### ttree.mean
176
196
 
177
- [Show source in ttree.py:359](../../rootloader/ttree.py#L359)
197
+ [Show source in ttree.py:405](../../rootloader/ttree.py#L405)
198
+
199
+ Return the mean value of the tree, for each branch
178
200
 
179
201
  #### Signature
180
202
 
@@ -184,7 +206,9 @@ def mean(self): ...
184
206
 
185
207
  ### ttree.min
186
208
 
187
- [Show source in ttree.py:349](../../rootloader/ttree.py#L349)
209
+ [Show source in ttree.py:399](../../rootloader/ttree.py#L399)
210
+
211
+ Return the min value of the tree, for each branch
188
212
 
189
213
  #### Signature
190
214
 
@@ -194,7 +218,7 @@ def min(self): ...
194
218
 
195
219
  ### ttree.reset
196
220
 
197
- [Show source in ttree.py:254](../../rootloader/ttree.py#L254)
221
+ [Show source in ttree.py:264](../../rootloader/ttree.py#L264)
198
222
 
199
223
  Make a new tree
200
224
 
@@ -206,7 +230,7 @@ def reset(self): ...
206
230
 
207
231
  ### ttree.reset_columns
208
232
 
209
- [Show source in ttree.py:258](../../rootloader/ttree.py#L258)
233
+ [Show source in ttree.py:268](../../rootloader/ttree.py#L268)
210
234
 
211
235
  Include all columns again
212
236
 
@@ -216,19 +240,9 @@ Include all columns again
216
240
  def reset_columns(self): ...
217
241
  ```
218
242
 
219
- ### ttree.rms
220
-
221
- [Show source in ttree.py:364](../../rootloader/ttree.py#L364)
222
-
223
- #### Signature
224
-
225
- ```python
226
- def rms(self): ...
227
- ```
228
-
229
243
  ### ttree.set_filter
230
244
 
231
- [Show source in ttree.py:268](../../rootloader/ttree.py#L268)
245
+ [Show source in ttree.py:278](../../rootloader/ttree.py#L278)
232
246
 
233
247
  Set a filter on the dataframe to select a subset of the data
234
248
 
@@ -240,7 +254,7 @@ def set_filter(self, expression, inplace=False): ...
240
254
 
241
255
  ### ttree.set_index
242
256
 
243
- [Show source in ttree.py:262](../../rootloader/ttree.py#L262)
257
+ [Show source in ttree.py:272](../../rootloader/ttree.py#L272)
244
258
 
245
259
  Set the index column name
246
260
 
@@ -252,7 +266,9 @@ def set_index(self, column): ...
252
266
 
253
267
  ### ttree.size
254
268
 
255
- [Show source in ttree.py:333](../../rootloader/ttree.py#L333)
269
+ [Show source in ttree.py:365](../../rootloader/ttree.py#L365)
270
+
271
+ Return the number of rows in the ttree
256
272
 
257
273
  #### Signature
258
274
 
@@ -263,7 +279,9 @@ def size(self): ...
263
279
 
264
280
  ### ttree.std
265
281
 
266
- [Show source in ttree.py:369](../../rootloader/ttree.py#L369)
282
+ [Show source in ttree.py:411](../../rootloader/ttree.py#L411)
283
+
284
+ Return the standard deviationif the of values the tree, for each branch
267
285
 
268
286
  #### Signature
269
287
 
@@ -271,11 +289,35 @@ def size(self): ...
271
289
  def std(self): ...
272
290
  ```
273
291
 
292
+ ### ttree.sum
293
+
294
+ [Show source in ttree.py:408](../../rootloader/ttree.py#L408)
295
+
296
+ Return the sum of the values of the tree, for each branch
297
+
298
+ #### Signature
299
+
300
+ ```python
301
+ def sum(self): ...
302
+ ```
303
+
304
+ ### ttree.to_array
305
+
306
+ [Show source in ttree.py:289](../../rootloader/ttree.py#L289)
307
+
308
+ Return ttree data as 1D or 2D numpy array (depending on number of columns)
309
+
310
+ #### Signature
311
+
312
+ ```python
313
+ def to_array(self): ...
314
+ ```
315
+
274
316
  ### ttree.to_dataframe
275
317
 
276
- [Show source in ttree.py:280](../../rootloader/ttree.py#L280)
318
+ [Show source in ttree.py:300](../../rootloader/ttree.py#L300)
277
319
 
278
- Return pandas dataframe of the data
320
+ Return ttree data as pandas dataframe
279
321
 
280
322
  #### Signature
281
323
 
@@ -285,10 +327,25 @@ def to_dataframe(self): ...
285
327
 
286
328
  ### ttree.to_dict
287
329
 
288
- [Show source in ttree.py:307](../../rootloader/ttree.py#L307)
330
+ [Show source in ttree.py:334](../../rootloader/ttree.py#L334)
331
+
332
+ Return ttree data as dict of numpy arrays
289
333
 
290
334
  #### Signature
291
335
 
292
336
  ```python
293
337
  def to_dict(self): ...
338
+ ```
339
+
340
+ ### ttree.values
341
+
342
+ [Show source in ttree.py:369](../../rootloader/ttree.py#L369)
343
+
344
+ Convert ttree 1D or 2D numpy array (depending on number of columns)
345
+
346
+ #### Signature
347
+
348
+ ```python
349
+ @property
350
+ def values(self): ...
294
351
  ```
@@ -22,6 +22,9 @@ dynamic = ["version"]
22
22
  "Homepage" = "https://github.com/ucn-triumf/rootloader"
23
23
  "Bug Tracker" = "https://github.com/ucn-triumf/rootloader/issues"
24
24
 
25
+ [project.optional-dependencies]
26
+ test = ["pytest", "pytest-cov"]
27
+
25
28
  # set version
26
29
  [tool.hatch.version]
27
30
  path = "rootloader/version.py"
@@ -65,7 +65,12 @@ class tdirectory(attrdict):
65
65
  # TTree
66
66
  if 'TTree' == classname:
67
67
  if empty_ok or obj.GetEntries() > 0:
68
- self[name] = ttree(obj)
68
+ if name in tree_filter:
69
+ filter_string, columns = tree_filter[name]
70
+ self[name] = ttree(obj, filter_string=filter_string,
71
+ columns=columns)
72
+ else:
73
+ self[name] = ttree(obj)
69
74
  elif not quiet:
70
75
  tqdm.write(f'Skipped "{name}" due to lack of entries')
71
76
 
@@ -71,7 +71,7 @@ class th2(object):
71
71
  self.dz = self.dz.transpose()
72
72
 
73
73
  def __len__(self):
74
- return self.nbins
74
+ return self.nbinsx * self.nbinsy
75
75
 
76
76
  def __repr__(self):
77
77
  return f'{self.base_class}: "{self.name}", {self.entries} entries, sum = {self.sum}'
@@ -143,7 +143,8 @@ class th2(object):
143
143
 
144
144
  if ax is None:
145
145
  plt.figure()
146
- ax = plt.gca().add_subplot(projection='3d')
146
+ # add_subplot is a Figure method, not an Axes method
147
+ ax = plt.gcf().add_subplot(111, projection='3d')
147
148
 
148
149
  ax.plot_surface(xx, yy, self.z, **kwargs)
149
150
 
@@ -16,7 +16,7 @@ class tleaf(object):
16
16
 
17
17
  def copy(self):
18
18
  """Make a copy of this object"""
19
- return tleaf(self._leaf.copy())
19
+ return tleaf(self._leaf)
20
20
 
21
21
  def get_entry(self, i):
22
22
  self._leaf.GetBranch().GetEntry(i)