UnROOT.jl is a reader for the CERN ROOT file format written entirely in Julia, without any dependence on ROOT or Python.
XRootD and HTTP are now available via extensions
UnROOT.jl supports opening files remotely via XRootD which requires
XRootD.jl
and via HTTP using HTTP.jl seamlessly
up to version 0.10.38.
Starting with v0.11, this behaviour has changed to reduce default dependencies, since
not everyone uses these features. To continue opening remote
files via XRootD or HTTP, the corresponding Julia package (XRootD.jl, respectively HTTP.jl)
now needs to be installed and loaded.
UnROOT.jl provides extensions for both
which are loaded automatically so ROOTFile(url) will work just like before.
Long story short, when passing a url to ROOTFile(...), make sure to load
appropriate package:
using UnROOT
using XRootD # this is now required for XRootD URLs
ROOTFile("xroot://...")
or
using UnROOT
using HTTP # this is now required for HTTP/HTTPS URLs
ROOTFile("https://...")
See PR396 for more details.
`getindex` now behaves differently
We decided to alter the behaviour of getindex(f::ROOTfile, s::AbstractString) which is essentially
the method called called when f["foo/bar"] is used. Before v0.9.0, UnROOT tried to do a best guess
and return a tree/branch or even fully parsed data. This lead to two bigger issues.
- Errors prevented any further exploration once
UnROOTbumped into something it could not interpret, although it might not even be requested by the user (e.g. the interpretation of a single branch in a tree, while others would work fine) - Unpredictable behaviour (type instability): the path dictates which type of data is returned.
Starting from v0.9.0 we introduce an interface where f["..."] always returns genuine ROOT datatypes (or custom ones if you provide interpretations) and only performs the actual parsing when explicitly requested by the user via helper methods like LazyBranch(f, "...").
Long story short, the following pattern can be used to fix your code when upgrading to v0.9.0:
f("foo/bar") => LazyBranch(f, "foo/bar")
The f["foo/bar"] accessor should now work on almost all files and is a handy utility to explore the ROOT data structures.
See PR199 for more details.
- Download the latest Julia release
- Open up Julia REPL (hit
]once to enter Pkg mode, hit backspace to exit it)
julia>]
(v1.8) pkg> add UnROOTQuick Start (see docs for more)
julia> using UnROOT
julia> f = ROOTFile("test/samples/NanoAODv5_sample.root")
ROOTFile with 2 entries and 21 streamers.
test/samples/NanoAODv5_sample.root
├─ Events (TTree)
│ ├─ "run"
│ ├─ "luminosityBlock"
│ ├─ "event"
│ ├─ "⋮"
│ ├─ "L1_UnpairedBunchBptxPlus"
│ ├─ "L1_ZeroBias"
│ └─ "L1_ZeroBias_copy"
└─ untagged (TObjString)
julia> mytree = LazyTree(f, "Events", ["Electron_dxy", "nMuon", r"Muon_(pt|eta)$"])
Row │ Electron_dxy nMuon Muon_pt Muon_eta
│ SubArray{Float3 UInt32 SubArray{Float3 SubArray{Float3
─────┼────────────────────────────────────────────────────────────────────────────
1 │ [0.000371] 0 [] []
2 │ [-0.00982] 2 [19.9, 15.3] [0.53, 0.229]
3 │ [] 0 [] []
4 │ [-0.00157] 0 [] []
5 │ [] 0 [] []
6 │ [-0.00126] 0 [] []
7 │ [0.0612, 0.000642] 2 [22.2, 4.43] [-1.13, 1.98]
8 │ [0.00587, 0.000549, -0.00617] 0 [] []
⋮ │ ⋮ ⋮ ⋮ ⋮
992 rows omittedClick to expand example for RNTuple
julia> using UnROOT
julia> f = ROOTFile("./test/samples/RNTuple/test_ntuple_stl_containers.root");
julia> f["ntuple"]
UnROOT.RNTuple with 5 rows, 13 fields, and metadata:
header:
name: "ntuple"
ntuple_description: ""
writer_identifier: "ROOT v6.29/01"
schema:
RNTupleSchema with 13 top fields
├─ :lorentz_vector ⇒ Struct
├─ :vector_tuple_int32_string ⇒ Vector
├─ :string ⇒ String
├─ :vector_string ⇒ Vector
├─ :vector_vector_int32 ⇒ Vector
├─ :vector_variant_int64_string ⇒ Vector
├─ :vector_vector_string ⇒ Vector
├─ :variant_int32_string ⇒ Union
├─ :array_float ⇒ StdArray{3}
├─ :tuple_int32_string ⇒ Struct
├─ :array_lv ⇒ StdArray{3}
├─ :pair_int32_string ⇒ Struct
└─ :vector_int32 ⇒ Vector
footer:
cluster_summaries: UnROOT.ClusterSummary[ClusterSummary(num_first_entry=0, num_entries=5)]
julia> LazyTree(f, "ntuple")
Row │ string vector_int32 array_float vector_vector_i vector_string vector_vector_s variant_int32_s vector_variant_ ⋯
│ String Vector{Int32} StaticArraysCor Vector{Vector{I Vector{String} Vector{Vector{S Union{Int32, St Vector{Union{In ⋯
─────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ one [1] [1.0, 1.0, 1.0] Vector{Int32}[Int3 ["one"] [["one"]] 1 Union{Int64, Strin ⋯
2 │ two [1, 2] [2.0, 2.0, 2.0] Vector{Int32}[Int3 ["one", "two"] [["one"], ["two"]] two Union{Int64, Strin ⋯
3 │ three [1, 2, 3] [3.0, 3.0, 3.0] Vector{Int32}[Int3 ["one", "two", "th [["one"], ["two"], three Union{Int64, Strin ⋯
4 │ four [1, 2, 3, 4] [4.0, 4.0, 4.0] Vector{Int32}[Int3 ["one", "two", "th [["one"], ["two"], 4 Union{Int64, Strin ⋯
5 │ five [1, 2, 3, 4, 5] [5.0, 5.0, 5.0] Vector{Int32}[Int3 ["one", "two", "th [["one"], ["two"], 5 Union{Int64, Strin ⋯
5 columns omittedYou can iterate through a LazyTree:
julia> for event in mytree
@show event.Electron_dxy
break
end
event.Electron_dxy = Float32[0.00037050247]
julia> Threads.@threads :static for event in mytree # multi-threading
...
endOnly one basket per branch will be cached so you don't have to worry about running out of RAM.
At the same time, event inside the for-loop is not materialized until a field is accessed. This means you should avoid double-access,
see performance tips
XRootD is also supported via extensions,
depending on the protocol, either using XRootD or using HTTP:
- the "url" has to start with
http://orhttps://: - (1.6+ only) or the "url" has to start with
root://and have another//to separate server and file path
julia> r = ROOTFile("https://scikit-hep.org/uproot3/examples/Zmumu.root")
ROOTFile with 1 entry and 18 streamers.
https://scikit-hep.org/uproot3/examples/Zmumu.root
└─ events (TTree)
├─ "Type"
├─ "Run"
├─ "Event"
├─ "⋮"
├─ "phi2"
├─ "Q2"
└─ "M"
julia> r = ROOTFile("root://eospublic.cern.ch//eos/root-eos/cms_opendata_2012_nanoaod/Run2012B_DoubleMuParked.root")
ROOTFile with 1 entry and 19 streamers.
root://eospublic.cern.ch//eos/root-eos/cms_opendata_2012_nanoaod/Run2012B_DoubleMuParked.root
└─ Events (TTree)
├─ "run"
├─ "luminosityBlock"
├─ "event"
├─ "⋮"
├─ "Electron_dxyErr"
├─ "Electron_dz"
└─ "Electron_dzErr"
We provide an experimental interface for hooking up UnROOT with your custom types
that only takes 2 steps, as explained in the docs.
As a show case for this functionality, the TLorentzVector support in UnROOT is implemented
with the said plug-in system.
- Use Github issues for any bug reporting or feature request; feel free to make PRs, bug fixing, feature tuning, quality of life, docs, examples etc.
- See
CONTRIBUTING.mdfor more information and recommended workflows in contributing to this package.
Special thanks to Jim Pivarski (@jpivarski) from the Scikit-HEP project, who is the main author of uproot, a native Python library to read and write ROOT files, which was and is a great source of inspiration and information for reverse engineering the ROOT binary structures.
Thanks goes to these wonderful people (emoji key):
Tamas Gal 💻 📖 🚇 🔣 |
Jerry Ling 💻 |
Johannes Schumann 💻 |
Nick Amin 💻 |
Mosè Giordano 🚇 |
Oliver Schulz 🤔 |
Misha Mikhasenko 🔣 |
Yuan-Ru Lin |
This project follows the all-contributors specification. Contributions of any kind welcome!