From 97666f8026bb5abcdc2b3bd5d1f599e7bdca8cf8 Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Fri, 10 Sep 2021 15:24:15 -0700 Subject: [PATCH 1/5] start --- src/root.jl | 121 ++++++++++++++++++++++++----------------------- test/runtests.jl | 1 + 2 files changed, 64 insertions(+), 58 deletions(-) diff --git a/src/root.jl b/src/root.jl index 8e4d26bc..b4c397ca 100644 --- a/src/root.jl +++ b/src/root.jl @@ -193,67 +193,72 @@ on type `T` and jagg type `J`. In order to retrieve data from custom branches, user should defined more speialized method of this function with specific `T` and `J`. See `TLorentzVector` example. """ -function interped_data(rawdata, rawoffsets, ::Type{T}, ::Type{J}) where {T, J<:JaggType} - # there are two possibility, one is the leaf is just normal leaf but the title has "[...]" in it - # magic offsets, seems to be common for a lot of types, see auto.py in uproot3 - # only needs when the jaggedness comes from TLeafElements, not needed when - # the jaggedness comes from having "[]" in TLeaf's title - # the other is where we need to auto detector T bsaed on class name - # we want the fundamental type as `reinterpret` will create vector - if J == Nojagg - return ntoh.(reinterpret(T, rawdata)) - elseif J == Offsetjaggjagg # the branch is doubly jagged - jagg_offset = 10 - subT = eltype(eltype(T)) - out = VectorOfVectors(T(), Int32[1]) - @views for i in 1:(length(rawoffsets)-1) - flat = rawdata[(rawoffsets[i]+1+jagg_offset:rawoffsets[i+1])] - row = VectorOfVectors{subT}() - cursor = 1 - while cursor < length(flat) - n = ntoh(reinterpret(Int32, flat[cursor:cursor+sizeof(Int32)-1])[1]) - cursor += sizeof(Int32) - b = ntoh.(reinterpret(subT, flat[cursor:cursor+n*sizeof(subT)-1])) - cursor += n*sizeof(subT) - push!(row, b) - end - push!(out, row) - end - return out - else # the branch is singly jagged - # for each "event", the index range is `offsets[i] + jagg_offset + 1` to `offsets[i+1]` - # this is why we need to append `rawoffsets` in the `readbranchraw()` call - # when you use this range to index `rawdata`, you will get raw bytes belong to each event - # Say your real data is Int32 and you see 8 bytes after indexing, then this event has [num1, num2] as real data - _size = sizeof(eltype(T)) - if J === Offsetjagg - jagg_offset = 10 - dp = 0 # book keeping for copy_to! - lr = length(rawoffsets) - offset = Vector{Int32}(undef, lr) - offset[1] = 0 - @views @inbounds for i in 1:lr-1 - start = rawoffsets[i]+jagg_offset+1 - stop = rawoffsets[i+1] - l = stop-start+1 - if l > 0 - unsafe_copyto!(rawdata, dp+1, rawdata, start, l) - dp += l - offset[i+1] = offset[i] + l - else - # when we have an empty [] in jagged basket - offset[i+1] = offset[i] - end - end - resize!(rawdata, dp) +function interped_data(rawdata, rawoffsets, ::Type{T}, ::Type{Nojagg}) where T + return ntoh.(reinterpret(T, rawdata)) +end +function interped_data(rawdata, rawoffsets, ::Type{T}, ::Type{Nojagg}) where {T<:Bool} + return map(ntoh,reinterpret(T, rawdata)) +end +# there are two possibility, one is the leaf is just normal leaf but the title has "[...]" in it +# magic offsets, seems to be common for a lot of types, see auto.py in uproot3 +# only needs when the jaggedness comes from TLeafElements, not needed when +# the jaggedness comes from having "[]" in TLeaf's title +# the other is where we need to auto detector T bsaed on class name +# we want the fundamental type as `reinterpret` will create vector +function interped_data(rawdata, rawoffsets, ::Type{T}, ::Type{Nooffsetjagg}) where T + real_data = ntoh.(reinterpret(T, rawdata)) + rawoffsets .÷= sizeof(eltype(T)) + rawoffsets .+= 1 + return VectorOfVectors(real_data, rawoffsets) +end +function interped_data(rawdata, rawoffsets, ::Type{T}, ::Type{Offsetjagg}) where T + # for each "event", the index range is `offsets[i] + jagg_offset + 1` to `offsets[i+1]` + # this is why we need to append `rawoffsets` in the `readbranchraw()` call + # when you use this range to index `rawdata`, you will get raw bytes belong to each event + # Say your real data is Int32 and you see 8 bytes after indexing, then this event has [num1, num2] as real data + _size = sizeof(eltype(T)) + jagg_offset = 10 + dp = 0 # book keeping for copy_to! + lr = length(rawoffsets) + offset = Vector{Int32}(undef, lr) + offset[1] = 0 + @views @inbounds for i in 1:lr-1 + start = rawoffsets[i]+jagg_offset+1 + stop = rawoffsets[i+1] + l = stop-start+1 + if l > 0 + unsafe_copyto!(rawdata, dp+1, rawdata, start, l) + dp += l + offset[i+1] = offset[i] + l else - offset = rawoffsets + # when we have an empty [] in jagged basket + offset[i+1] = offset[i] + end + end + resize!(rawdata, dp) + real_data = ntoh.(reinterpret(T, rawdata)) + offset .÷= sizeof(eltype(T)) + offset .+= 1 + return VectorOfVectors(real_data, offset) +end +function interped_data(rawdata, rawoffsets, ::Type{T}, ::Type{Offsetjaggjagg}) where T + jagg_offset = 10 + subT = eltype(eltype(T)) + out = VectorOfVectors(T(), Int32[1]) + @views for i in 1:(length(rawoffsets)-1) + flat = rawdata[(rawoffsets[i]+1+jagg_offset:rawoffsets[i+1])] + row = VectorOfVectors{subT}() + cursor = 1 + while cursor < length(flat) + n = ntoh(reinterpret(Int32, flat[cursor:cursor+sizeof(Int32)-1])[1]) + cursor += sizeof(Int32) + b = ntoh.(reinterpret(subT, flat[cursor:cursor+n*sizeof(subT)-1])) + cursor += n*sizeof(subT) + push!(row, b) end - real_data = ntoh.(reinterpret(T, rawdata)) - offset .÷= _size - offset .+= 1 - return VectorOfVectors(real_data, offset) + push!(out, row) end + return out end function _normalize_ftype(fType) diff --git a/test/runtests.jl b/test/runtests.jl index 6184d52e..b4e03e4f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -312,6 +312,7 @@ end tree = LazyTree(rootfile, "Events", r"Muon_(pt|eta)$") @test sort(propertynames(tree)) == sort([:Muon_pt, :Muon_eta]) @test occursin("LazyEvent", repr(first(iterate(tree)))) + @test sum(rootfile["Events/HLT_Mu3_PFJet40"]) == 443 close(rootfile) end From 04bcbe2334fc0fd25a72078e088fa50fe11e53f1 Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Fri, 10 Sep 2021 16:37:54 -0700 Subject: [PATCH 2/5] make methods in custom.jl more specific than root.jl --- src/custom.jl | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/custom.jl b/src/custom.jl index c99f9496..db01273f 100644 --- a/src/custom.jl +++ b/src/custom.jl @@ -73,7 +73,7 @@ function interped_data(rawdata, rawoffsets, ::Type{Vector{LVF64}}, ::Type{Offset offset .+= 1 VectorOfVectors(real_data, offset) end -function interped_data(rawdata, rawoffsets, ::Type{LVF64}, ::Type{J}) where {T, J <: JaggType} +function interped_data(rawdata, rawoffsets, ::Type{LVF64}, ::Type{Nojagg}) # even with rawoffsets, we know each TLV is destinied to be 64 bytes [ reinterpret(LVF64, x) for x in Base.Iterators.partition(rawdata, 64) @@ -91,7 +91,10 @@ end function readtype(io::IO, T::Type{KM3NETDAQHit}) T(readtype(io, Int32), read(io, UInt8), read(io, Int32), read(io, UInt8)) end -function interped_data(rawdata, rawoffsets, ::Type{Vector{KM3NETDAQHit}}, ::Type{J}) where {T, J <: UnROOT.JaggType} +function interped_data(rawdata, rawoffsets, ::Type{Vector{KM3NETDAQHit}}, ::Type{Nojagg}) + UnROOT.splitup(rawdata, rawoffsets, KM3NETDAQHit, skipbytes=10) +end +function interped_data(rawdata, rawoffsets, ::Type{Vector{KM3NETDAQHit}}, ::Type{Offsetjagg}) UnROOT.splitup(rawdata, rawoffsets, KM3NETDAQHit, skipbytes=10) end @@ -133,7 +136,10 @@ function readtype(io::IO, T::Type{KM3NETDAQTriggeredHit}) T(dom_id, channel_id, tdc, tot, trigger_mask) end -function UnROOT.interped_data(rawdata, rawoffsets, ::Type{Vector{KM3NETDAQTriggeredHit}}, ::Type{J}) where {T, J <: UnROOT.JaggType} +function UnROOT.interped_data(rawdata, rawoffsets, ::Type{Vector{KM3NETDAQTriggeredHit}}, ::Type{Nojagg}) + UnROOT.splitup(rawdata, rawoffsets, KM3NETDAQTriggeredHit, skipbytes=10) +end +function UnROOT.interped_data(rawdata, rawoffsets, ::Type{Vector{KM3NETDAQTriggeredHit}}, ::Type{Offsetjagg}) UnROOT.splitup(rawdata, rawoffsets, KM3NETDAQTriggeredHit, skipbytes=10) end @@ -165,6 +171,6 @@ function readtype(io::IO, T::Type{KM3NETDAQEventHeader}) T(detector_id, run, frame_index, UTC_seconds, UTC_16nanosecondcycles, trigger_counter, trigger_mask, overlays) end -function UnROOT.interped_data(rawdata, rawoffsets, ::Type{KM3NETDAQEventHeader}, ::Type{J}) where {T, J <: UnROOT.JaggType} +function UnROOT.interped_data(rawdata, rawoffsets, ::Type{KM3NETDAQEventHeader}, ::Type{Nojagg}) UnROOT.splitup(rawdata, rawoffsets, KM3NETDAQEventHeader, jagged=false) end From bb2424c828510f9b1b7bcaf3759be2975c3d08dd Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Wed, 15 Sep 2021 13:04:19 -0700 Subject: [PATCH 3/5] master --- src/UnROOT.jl | 2 +- src/custom.jl | 55 +++++++++++++-------------------------- src/displays.jl | 43 ++++++++++++++++++++++--------- src/iteration.jl | 45 +++++++++++++------------------- src/root.jl | 18 ++++++------- src/utils.jl | 4 +++ test/runtests.jl | 67 +++++++++++++++++++++++++++++++++++++++++------- 7 files changed, 139 insertions(+), 95 deletions(-) diff --git a/src/UnROOT.jl b/src/UnROOT.jl index bf32aab7..2e58975c 100644 --- a/src/UnROOT.jl +++ b/src/UnROOT.jl @@ -11,7 +11,7 @@ import AbstractTrees: children, printnode, print_tree using CodecZlib, CodecLz4, CodecXz, CodecZstd, StaticArrays, LorentzVectors, ArraysOfArrays using Mixers, Parameters, Memoization, LRUCache -import Tables, TypedTables, PrettyTables, DataFrames +import Tables, TypedTables, PrettyTables @static if VERSION < v"1.6" Base.first(a::AbstractVector{S}, n::Integer) where S<: AbstractString = a[1:(length(a) > n ? n : end)] diff --git a/src/custom.jl b/src/custom.jl index db01273f..7d1c6490 100644 --- a/src/custom.jl +++ b/src/custom.jl @@ -82,51 +82,32 @@ end # TLorentzVector ends # KM3NeT -struct KM3NETDAQHit <: CustomROOTStruct +struct _KM3NETDAQHit <: CustomROOTStruct dom_id::Int32 channel_id::UInt8 tdc::Int32 tot::UInt8 end -function readtype(io::IO, T::Type{KM3NETDAQHit}) +function readtype(io::IO, T::Type{_KM3NETDAQHit}) T(readtype(io, Int32), read(io, UInt8), read(io, Int32), read(io, UInt8)) end -function interped_data(rawdata, rawoffsets, ::Type{Vector{KM3NETDAQHit}}, ::Type{Nojagg}) - UnROOT.splitup(rawdata, rawoffsets, KM3NETDAQHit, skipbytes=10) +function interped_data(rawdata, rawoffsets, ::Type{Vector{_KM3NETDAQHit}}, ::Type{Nojagg}) + UnROOT.splitup(rawdata, rawoffsets, _KM3NETDAQHit, skipbytes=10) end -function interped_data(rawdata, rawoffsets, ::Type{Vector{KM3NETDAQHit}}, ::Type{Offsetjagg}) - UnROOT.splitup(rawdata, rawoffsets, KM3NETDAQHit, skipbytes=10) +function interped_data(rawdata, rawoffsets, ::Type{Vector{_KM3NETDAQHit}}, ::Type{Offsetjagg}) + UnROOT.splitup(rawdata, rawoffsets, _KM3NETDAQHit, skipbytes=10) end -# Experimental implementation for maximum performance (using reinterpret) -primitive type DAQHit 80 end -function Base.getproperty(hit::DAQHit, s::Symbol) - r = Ref(hit) - GC.@preserve r begin - if s === :dom_id - return ntoh(unsafe_load(Ptr{Int32}(Base.unsafe_convert(Ptr{Cvoid}, r)))) - elseif s === :channel_id - return unsafe_load(Ptr{UInt8}(Base.unsafe_convert(Ptr{Cvoid}, r)+4)) - elseif s === :tdc - return unsafe_load(Ptr{UInt32}(Base.unsafe_convert(Ptr{Cvoid}, r)+5)) - elseif s === :tot - return unsafe_load(Ptr{UInt8}(Base.unsafe_convert(Ptr{Cvoid}, r)+9)) - end - end - error("unknown field $s of type $(typeof(hit))") -end -Base.show(io::IO, h::DAQHit) = print(io, "DAQHit(", h.dom_id, ',', h.channel_id, ',', h.tdc, ',', h.tot, ')') - - -struct KM3NETDAQTriggeredHit +struct _KM3NETDAQTriggeredHit dom_id::Int32 channel_id::UInt8 tdc::Int32 tot::UInt8 trigger_mask::UInt64 end -function readtype(io::IO, T::Type{KM3NETDAQTriggeredHit}) +packedsizeof(::Type{_KM3NETDAQTriggeredHit}) = 24 # incl. cnt and vers +function readtype(io::IO, T::Type{_KM3NETDAQTriggeredHit}) dom_id = readtype(io, Int32) channel_id = read(io, UInt8) tdc = read(io, Int32) @@ -136,14 +117,14 @@ function readtype(io::IO, T::Type{KM3NETDAQTriggeredHit}) T(dom_id, channel_id, tdc, tot, trigger_mask) end -function UnROOT.interped_data(rawdata, rawoffsets, ::Type{Vector{KM3NETDAQTriggeredHit}}, ::Type{Nojagg}) - UnROOT.splitup(rawdata, rawoffsets, KM3NETDAQTriggeredHit, skipbytes=10) +function UnROOT.interped_data(rawdata, rawoffsets, ::Type{Vector{_KM3NETDAQTriggeredHit}}, ::Type{Nojagg}) + UnROOT.splitup(rawdata, rawoffsets, _KM3NETDAQTriggeredHit, skipbytes=10) end -function UnROOT.interped_data(rawdata, rawoffsets, ::Type{Vector{KM3NETDAQTriggeredHit}}, ::Type{Offsetjagg}) - UnROOT.splitup(rawdata, rawoffsets, KM3NETDAQTriggeredHit, skipbytes=10) +function UnROOT.interped_data(rawdata, rawoffsets, ::Type{Vector{_KM3NETDAQTriggeredHit}}, ::Type{Offsetjagg}) + UnROOT.splitup(rawdata, rawoffsets, _KM3NETDAQTriggeredHit, skipbytes=10) end -struct KM3NETDAQEventHeader +struct _KM3NETDAQEventHeader detector_id::Int32 run::Int32 frame_index::Int32 @@ -153,9 +134,9 @@ struct KM3NETDAQEventHeader trigger_mask::UInt64 overlays::UInt32 end -packedsizeof(::Type{KM3NETDAQEventHeader}) = 76 +packedsizeof(::Type{_KM3NETDAQEventHeader}) = 76 -function readtype(io::IO, T::Type{KM3NETDAQEventHeader}) +function readtype(io::IO, T::Type{_KM3NETDAQEventHeader}) skip(io, 18) detector_id = readtype(io, Int32) run = readtype(io, Int32) @@ -171,6 +152,6 @@ function readtype(io::IO, T::Type{KM3NETDAQEventHeader}) T(detector_id, run, frame_index, UTC_seconds, UTC_16nanosecondcycles, trigger_counter, trigger_mask, overlays) end -function UnROOT.interped_data(rawdata, rawoffsets, ::Type{KM3NETDAQEventHeader}, ::Type{Nojagg}) - UnROOT.splitup(rawdata, rawoffsets, KM3NETDAQEventHeader, jagged=false) +function UnROOT.interped_data(rawdata, rawoffsets, ::Type{_KM3NETDAQEventHeader}, ::Type{Nojagg}) + UnROOT.splitup(rawdata, rawoffsets, _KM3NETDAQEventHeader, jagged=false) end diff --git a/src/displays.jl b/src/displays.jl index 4eff9ac2..e410cbe7 100644 --- a/src/displays.jl +++ b/src/displays.jl @@ -3,17 +3,34 @@ These functions are used to display a ROOTFile is a tree-like fashion by using `AbstractTrees` printing functions. We customize what the children of ROOTFile and a TTree is, and how to print the final `node`. =# +struct TKeyNode + name::AbstractString + classname::AbstractString +end function children(f::ROOTFile) - ch = Vector{TTree}() + # display TTrees recursively + # subsequent TTrees with duplicate fName will be skipped + # since TKey cycle number is guaranteed to be decreasing + # then all TKeys in the file which are not for a TTree + seen = Set{String}() + ch = Vector{Union{TTree,TKeyNode}}() + lock(f) for k in keys(f) - lock(f.fobj) try - push!(ch, f[k]) + obj = f[k] + obj isa TTree || continue + obj.fName ∈ seen && continue + push!(ch, obj) + push!(seen, obj.fName) catch - finally - unlock(f.fobj) end end + for tkey in f.directory.keys + kn = TKeyNode(tkey.fName, tkey.fClassName) + kn.classname == "TTree" && continue + push!(ch, kn) + end + unlock(f) ch end function children(t::TTree) @@ -26,15 +43,16 @@ function children(t::TTree) return ks end end -printnode(io::IO, t::TTree) = print(io, t.fName) +printnode(io::IO, t::TTree) = print(io, "$(t.fName) (TTree)") printnode(io::IO, f::ROOTFile) = print(io, f.filename) +printnode(io::IO, k::TKeyNode) = print(io, "$(k.name) ($(k.classname))") function Base.show(io::IO, tree::LazyTree) _hs = _make_header(tree) _ds = displaysize(io) PrettyTables.pretty_table( io, - tree; + innertable(tree); header=_hs, alignment=:l, vlines=[1], @@ -44,7 +62,7 @@ function Base.show(io::IO, tree::LazyTree) row_number_column_title="Row", show_row_number=true, compact_printing=false, - formatters=(v, i, j) -> _treeformat(v, _ds[2] ÷ min(5, length(_hs[1]))), + formatters=(v, i, j) -> _treeformat(v, _ds[2] ÷ min(8, length(_hs[1]))), display_size=(min(_ds[1], 40), min(_ds[2], 160)), ) end @@ -52,14 +70,15 @@ _symtup2str(symtup, trunc=15) = collect(first.(string.(symtup), trunc)) function _make_header(t) pn = propertynames(t) header = _symtup2str(pn) - subheader = _symtup2str(Tables.columntype.(Ref(t), pn)) + subheader = _symtup2str(Tables.columntype.(Ref(innertable(t)), pn)) (header, subheader) end function _treeformat(val, trunc) - s = if val isa Vector{T} where T<:Integer + s = if val isa AbstractArray{T} where T<:Integer string(Int.(val)) - elseif val isa Vector{T} where T<:AbstractFloat - string(round.(Float64.(val); sigdigits=3)) + elseif val isa AbstractArray{T} where T<:AbstractFloat + T = eltype(val) + replace(string(round.(T.(val); sigdigits=3)), string(T)=>"") else string(val) end diff --git a/src/iteration.jl b/src/iteration.jl index 874420b0..1cfee31b 100644 --- a/src/iteration.jl +++ b/src/iteration.jl @@ -5,7 +5,7 @@ Reads all branches from a tree. """ function arrays(f::ROOTFile, treename) names = keys(f[treename]) - res = Vector{Any}(undef, length(names)) + res = Vector{Vector}(undef, length(names)) Threads.@threads for i in eachindex(names) res[i] = array(f, "$treename/$(names[i])") end @@ -122,12 +122,12 @@ Base.eltype(ba::LazyBranch{T,J,B}) where {T,J,B} = T function Base.show(io::IO, lb::LazyBranch) summary(io, lb) - println(":") - println(" File: $(lb.f.filename)") - println(" Branch: $(lb.b.fName)") - println(" Description: $(lb.b.fTitle)") - println(" NumEntry: $(lb.L)") - print(" Entry Type: $(eltype(lb))") + println(io, ":") + println(io, " File: $(lb.f.filename)") + println(io, " Branch: $(lb.b.fName)") + println(io, " Description: $(lb.b.fTitle)") + println(io, " NumEntry: $(lb.L)") + print(io, " Entry Type: $(eltype(lb))") nothing end @@ -166,27 +166,24 @@ function Base.iterate(ba::LazyBranch{T,J,B}, idx=1) where {T,J,B} return (ba[idx], idx + 1) end -const _LazyTreeType = - TypedTables.Table{<:NamedTuple,1,NamedTuple{S,N}} where {S,N<:Tuple{Vararg{LazyBranch}}} - -struct LazyTree{T} <: DataFrames.AbstractDataFrame +struct LazyTree{T} treetable::T - colidx::DataFrames.Index end + @inline innertable(t::LazyTree) = Core.getfield(t, :treetable) +Base.propertynames(lt::LazyTree) = propertynames(innertable(lt)) +Base.getproperty(lt::LazyTree, s::Symbol) = getproperty(innertable(lt), s) + # a specific branch Base.getindex(lt::LazyTree, row::Int) = innertable(lt)[row] function Base.getindex(lt::LazyTree, rang::UnitRange) - return LazyTree(innertable(lt)[rang], Core.getfield(lt, :colidx)) + return LazyTree(innertable(lt)[rang]) end Base.getindex(lt::LazyTree, ::typeof(!), s::Symbol) = lt[:, s] -Base.getindex(lt::LazyTree, ::Colon, i::Int) = lt[:, propertynames(lt)[i]] -Base.getindex(lt::LazyTree, ::typeof(!), i::Int) = lt[:, propertynames(lt)[i]] Base.getindex(lt::LazyTree, ::Colon, s::Symbol) = getproperty(innertable(lt), s) # the real deal # a specific event -Base.getindex(lt::LazyTree, row::Int, col::Int) = lt[:, col][row] Base.getindex(lt::LazyTree, row::Int, col::Symbol) = lt[:, col][row] Base.getindex(lt::LazyTree, rows::UnitRange, col::Symbol) = lt[:, col][rows] Base.getindex(lt::LazyTree, ::Colon) = lt[1:end] @@ -200,13 +197,9 @@ Base.lastindex(e::Iterators.Enumerate{LazyTree{T}}) where T = lastindex(e.itr) Base.eachindex(e::Iterators.Enumerate{LazyTree{T}}) where T = eachindex(e.itr) Base.getindex(e::Iterators.Enumerate{LazyTree{T}}, row::Int) where T = (row, first(iterate(e.itr, row))) -# interfacing AbstractDataFrame -DataFrames._check_consistency(lt::LazyTree) = nothing #we're read-only +# interfacing Table Base.names(lt::LazyTree) = collect(String.(propertynames(innertable(lt)))) -DataFrames.index(lt::LazyTree) = Core.getfield(lt, :colidx) -DataFrames.ncol(lt::LazyTree) = length(DataFrames.index(lt)) Base.length(lt::LazyTree) = length(innertable(lt)) -DataFrames.nrow(lt::LazyTree) = length(lt) function getbranchnamesrecursive(obj) out = Vector{String}() @@ -223,7 +216,7 @@ end LazyTree(f::ROOTFile, s::AbstractString, branche::Union{AbstractString, Regex}) LazyTree(f::ROOTFile, s::AbstractString, branches::Vector{Union{AbstractString, Regex}}) -Constructor for `LazyTree`, which is close to an `AbstractDataFrame` (interface wise), +Constructor for `LazyTree`, which is close to an `DataFrame` (interface wise), and a lazy `TypedTables.Table` (speed wise). Looping over a `LazyTree` is fast and type stable. Internally, `LazyTree` contains a typed table whose branch are [`LazyBranch`](@ref). This means that at any given time only `N` baskets are cached, where `N` is the number of branches. @@ -251,16 +244,14 @@ function LazyTree(f::ROOTFile, s::AbstractString, branches) @warn "Your tree is quite wide, with $(length(branches)) branches, this will take compiler a moment." end d = Dict{Symbol,LazyBranch}() - d_colidx = Dict{Symbol,Int}() _m(s::AbstractString) = isequal(s) _m(r::Regex) = Base.Fix1(occursin, r) branches = mapreduce(b -> filter(_m(b), getbranchnamesrecursive(tree)), ∪, branches) SB = Symbol.(branches) - for (i, b) in enumerate(SB) + for b in SB d[b] = f["$s/$b"] - d_colidx[b] = i end - return LazyTree(TypedTables.Table(d), DataFrames.Index(d_colidx, SB)) + return LazyTree(TypedTables.Table(d)) end function LazyTree(f::ROOTFile, s::AbstractString) @@ -285,7 +276,7 @@ end function Base.getproperty(evt::LazyEvent, s::Symbol) @inbounds getproperty(Core.getfield(evt, :tree), s)[Core.getfield(evt, :idx)] end -Base.collect(evt::LazyEvent) = Core.getfield(evt, :tree)[Core.getfield(evt, :idx)] +Base.collect(evt::LazyEvent) = @inbounds Core.getfield(evt, :tree)[Core.getfield(evt, :idx)] function Base.iterate(tree::T, idx=1) where {T<:LazyTree} idx > length(tree) && return nothing diff --git a/src/root.jl b/src/root.jl index b4c397ca..1335e04a 100644 --- a/src/root.jl +++ b/src/root.jl @@ -193,12 +193,13 @@ on type `T` and jagg type `J`. In order to retrieve data from custom branches, user should defined more speialized method of this function with specific `T` and `J`. See `TLorentzVector` example. """ +function interped_data(rawdata, rawoffsets, ::Type{Bool}, ::Type{Nojagg}) + # specialized case to get Vector{Bool} instead of BitVector + return map(ntoh,reinterpret(Bool, rawdata)) +end function interped_data(rawdata, rawoffsets, ::Type{T}, ::Type{Nojagg}) where T return ntoh.(reinterpret(T, rawdata)) end -function interped_data(rawdata, rawoffsets, ::Type{T}, ::Type{Nojagg}) where {T<:Bool} - return map(ntoh,reinterpret(T, rawdata)) -end # there are two possibility, one is the leaf is just normal leaf but the title has "[...]" in it # magic offsets, seems to be common for a lot of types, see auto.py in uproot3 # only needs when the jaggedness comes from TLeafElements, not needed when @@ -206,10 +207,10 @@ end # the other is where we need to auto detector T bsaed on class name # we want the fundamental type as `reinterpret` will create vector function interped_data(rawdata, rawoffsets, ::Type{T}, ::Type{Nooffsetjagg}) where T + _size = sizeof(eltype(T)) real_data = ntoh.(reinterpret(T, rawdata)) - rawoffsets .÷= sizeof(eltype(T)) - rawoffsets .+= 1 - return VectorOfVectors(real_data, rawoffsets) + rawoffsets .= (rawoffsets .÷ _size) .+ 1 + return VectorOfVectors(real_data, rawoffsets, ArraysOfArrays.no_consistency_checks) end function interped_data(rawdata, rawoffsets, ::Type{T}, ::Type{Offsetjagg}) where T # for each "event", the index range is `offsets[i] + jagg_offset + 1` to `offsets[i+1]` @@ -237,9 +238,8 @@ function interped_data(rawdata, rawoffsets, ::Type{T}, ::Type{Offsetjagg}) where end resize!(rawdata, dp) real_data = ntoh.(reinterpret(T, rawdata)) - offset .÷= sizeof(eltype(T)) - offset .+= 1 - return VectorOfVectors(real_data, offset) + offset .= (offset .÷ _size) .+ 1 + return VectorOfVectors(real_data, offset, ArraysOfArrays.no_consistency_checks) end function interped_data(rawdata, rawoffsets, ::Type{T}, ::Type{Offsetjaggjagg}) where T jagg_offset = 10 diff --git a/src/utils.jl b/src/utils.jl index 594c3c31..8c63d395 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -99,3 +99,7 @@ function parseTH(th::Dict{Symbol, Any}) end return counts, edges, sumw2 end + +function samplefile(filename::AbstractString) + return ROOTFile(normpath(joinpath(@__DIR__, "../test/samples", filename))) +end diff --git a/test/runtests.jl b/test/runtests.jl index b4e03e4f..a7438143 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -215,6 +215,12 @@ end rootfile = ROOTFile(joinpath(SAMPLES_DIR, "tree_with_large_array.root")) branch = rootfile["t1"]["int32_array"] arr = UnROOT.array(rootfile, branch) + arr2 = UnROOT.arrays(rootfile, "t1")[1] + + @test hash(branch) == hash(rootfile["t1"]["int32_array"]) + @test hash(branch) != hash(rootfile["t1"]["float_array"]) + @test arr == arr2 + table = LazyTree(rootfile, "t1") BA = LazyBranch(rootfile, branch) @test length(arr) == length(BA) @@ -223,6 +229,10 @@ end @test BA[20:30] == arr[20:30] @test BA[1:end] == arr @test table.int32_array[20:30] == BA[20:30] + @test table[:, :int32_array][20:30] == BA[20:30] + @test table[23, :int32_array] == BA[23] + @test table[20:30, :int32_array] == BA[20:30] + @test table[:].int32_array[20:30] == BA[20:30] @test [row.int32_array for row in table[20:30]] == BA[20:30] @test sum(table.int32_array) == sum(row.int32_array for row in table) @test [row.int32_array for row in table] == BA @@ -308,9 +318,9 @@ end @test eltype(HLT_Mu3_PFJet40) == Bool @test HLT_Mu3_PFJet40[1:3] == [false, true, false] tree = LazyTree(rootfile, "Events", [r"Muon_(pt|eta|phi)$", "Muon_charge", "Muon_pt"]) - @test sort(propertynames(tree)) == sort([:Muon_pt, :Muon_eta, :Muon_phi, :Muon_charge]) + @test sort(propertynames(tree) |> collect) == sort([:Muon_pt, :Muon_eta, :Muon_phi, :Muon_charge]) tree = LazyTree(rootfile, "Events", r"Muon_(pt|eta)$") - @test sort(propertynames(tree)) == sort([:Muon_pt, :Muon_eta]) + @test sort(propertynames(tree) |> collect) == sort([:Muon_pt, :Muon_eta]) @test occursin("LazyEvent", repr(first(iterate(tree)))) @test sum(rootfile["Events/HLT_Mu3_PFJet40"]) == 443 close(rootfile) @@ -329,15 +339,36 @@ end @test filter_branches(["Muon.pt"]) == Set(["Muon.pt"]) end -@testset "Displaying" begin - files = filter(endswith(".root"), readdir(SAMPLES_DIR)) +@testset "Displaying files" begin + files = filter(x->endswith(x, ".root"), readdir(SAMPLES_DIR)) _io = IOBuffer() for f in files r = ROOTFile(joinpath(SAMPLES_DIR, f)) show(_io, r) close(r) end + + # test that duplicate trees (but different cycle numbers) + # are only displayed once, and that histograms show up + f = UnROOT.samplefile("tree_cycles_hist.root") + @test length(collect(eachmatch(r"Events", repr(f)))) == 1 + @test length(collect(eachmatch(r"myTH2F", repr(f)))) == 1 + close(f) +end + +@testset "Displaying trees" begin + f = UnROOT.samplefile("NanoAODv5_sample.root") + t = LazyTree(f, "Events", ["nMuon","MET_pt","Muon_pt"]) + _io = IOBuffer() + show(_io, t) + show(_io, t[1:10]) + show(_io, t.Muon_pt) + show(_io, t.Muon_pt[1:10]) + s = repr(t[1:10]) + @test length(collect(eachmatch(r"Float32\[", s))) == 0 + close(f) end + # Custom bootstrap things @testset "custom boostrapping" begin @@ -345,18 +376,21 @@ end f_manual = ROOTFile(joinpath(SAMPLES_DIR, "km3net_online.root")) data, offsets = UnROOT.array(f_manual, "KM3NET_EVENT/KM3NET_EVENT/KM3NETDAQ::JDAQEventHeader"; raw=true) - headers_manual = UnROOT.splitup(data, offsets, UnROOT.KM3NETDAQEventHeader; jagged=false) + headers_manual = UnROOT.splitup(data, offsets, UnROOT._KM3NETDAQEventHeader; jagged=false) data, offsets = UnROOT.array(f_manual, "KM3NET_EVENT/KM3NET_EVENT/snapshotHits"; raw=true) - event_hits_manual = UnROOT.splitup(data, offsets, UnROOT.KM3NETDAQHit; skipbytes=10) + event_hits_manual = UnROOT.splitup(data, offsets, UnROOT._KM3NETDAQHit; skipbytes=10) + + data, offsets = UnROOT.array(f_manual, "KM3NET_EVENT/KM3NET_EVENT/triggeredHits"; raw=true) + event_thits_manual = UnROOT.splitup(data, offsets, UnROOT._KM3NETDAQTriggeredHit; skipbytes=10) close(f_manual) # we can close, everything is in memory # automatic interpretation customstructs = Dict( - "KM3NETDAQ::JDAQEvent.snapshotHits" => Vector{UnROOT.KM3NETDAQHit}, - "KM3NETDAQ::JDAQEvent.triggeredHits" => Vector{UnROOT.KM3NETDAQTriggeredHit}, - "KM3NETDAQ::JDAQEvent.KM3NETDAQ::JDAQEventHeader" => UnROOT.KM3NETDAQEventHeader + "KM3NETDAQ::JDAQEvent.snapshotHits" => Vector{UnROOT._KM3NETDAQHit}, + "KM3NETDAQ::JDAQEvent.triggeredHits" => Vector{UnROOT._KM3NETDAQTriggeredHit}, + "KM3NETDAQ::JDAQEvent.KM3NETDAQ::JDAQEventHeader" => UnROOT._KM3NETDAQEventHeader ) f_auto = UnROOT.ROOTFile(joinpath(SAMPLES_DIR, "km3net_online.root"), customstructs=customstructs) headers_auto = f_auto["KM3NET_EVENT/KM3NET_EVENT/KM3NETDAQ::JDAQEventHeader"] @@ -377,6 +411,20 @@ end @test event_hits[3][end].dom_id == 809544061 @test event_hits[3][end].tdc == 63512892 end + for event_thits ∈ [event_thits_manual, event_thits_auto] + @test length(event_thits) == 3 + @test length(event_thits[1]) == 18 + @test length(event_thits[2]) == 53 + @test length(event_thits[3]) == 9 + @test event_thits[1][1].dom_id == 806451572 + @test event_thits[1][1].tdc == 30733918 + @test event_thits[1][end].dom_id == 808972598 + @test event_thits[1][end].tdc == 30733192 + @test event_thits[3][1].dom_id == 808447186 + @test event_thits[3][1].tdc == 63511558 + @test event_thits[3][end].dom_id == 809526097 + @test event_thits[3][end].tdc == 63511708 + end for headers ∈ [headers_manual, headers_auto] @test length(headers) == 3 @@ -531,6 +579,7 @@ end @testset "Parallel and enumerate interface" begin t = LazyTree(ROOTFile(joinpath(SAMPLES_DIR, "NanoAODv5_sample.root")), "Events", ["Muon_pt"]) + @test eachindex(enumerate(t)) == eachindex(t) nmu = 0 for evt in t nmu += length(evt.Muon_pt) From b40f96477a435922b0c6ef07ebf0729ee2c2a03a Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Wed, 15 Sep 2021 13:05:29 -0700 Subject: [PATCH 4/5] master --- test/tree_cycles_hist.py | 35 +++++++++++++++++++++++++++++++++++ test/tree_cycles_hist.root | Bin 0 -> 9869 bytes 2 files changed, 35 insertions(+) create mode 100644 test/tree_cycles_hist.py create mode 100644 test/tree_cycles_hist.root diff --git a/test/tree_cycles_hist.py b/test/tree_cycles_hist.py new file mode 100644 index 00000000..4fe7c178 --- /dev/null +++ b/test/tree_cycles_hist.py @@ -0,0 +1,35 @@ +import ROOT as r + +f = r.TFile("tree_cycles_hist.root", "recreate") +t = r.TTree("Events", "") + +obj = r.vector("float")() +t.Branch("Jet_pt", obj) +rows = [[], [27.324586868286133, 24.88954734802246, 20.853023529052734], [], [20.330659866333008], [], []] +for i,row in enumerate(rows): + obj.clear() + for x in row: + obj.push_back(x) + t.Fill() + if i == 3: + t.Write() + + +th1f = r.TH1F("myTH1F", "", 2, -2, 2) +th1d = r.TH1D("myTH1D", "", 2, -2, 2) +th2f = r.TH2F("myTH2F", "", 2, -2, 2, 4, -2, 2) +th2d = r.TH2D("myTH2D", "", 2, -2, 2, 4, -2, 2) + +for x,y,w in [ + [-1.5, -1.5, 20.0], + [+1.5, +1.5, 1.0], + [-1.5, +1.5, 20.0], + [+1.5, -1.5, 1.0], + ]: + th1f.Fill(x, w) + th1d.Fill(x, w) + th2f.Fill(x, y, w) + th2d.Fill(x, y, w) + +f.Write() +f.Close() diff --git a/test/tree_cycles_hist.root b/test/tree_cycles_hist.root new file mode 100644 index 0000000000000000000000000000000000000000..0c8f432681a68e1267c3081393c90a274b0bf585 GIT binary patch literal 9869 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 T{_TIdri5J6(ZqXlrvd&CuJ2gK literal 0 HcmV?d00001 From 2862c3fc6eb0a24a6892fbdd5290ee3bbe30a559 Mon Sep 17 00:00:00 2001 From: Nick Amin Date: Wed, 15 Sep 2021 13:06:58 -0700 Subject: [PATCH 5/5] rename --- test/{ => samples}/tree_cycles_hist.py | 0 test/{ => samples}/tree_cycles_hist.root | Bin 2 files changed, 0 insertions(+), 0 deletions(-) rename test/{ => samples}/tree_cycles_hist.py (100%) rename test/{ => samples}/tree_cycles_hist.root (100%) diff --git a/test/tree_cycles_hist.py b/test/samples/tree_cycles_hist.py similarity index 100% rename from test/tree_cycles_hist.py rename to test/samples/tree_cycles_hist.py diff --git a/test/tree_cycles_hist.root b/test/samples/tree_cycles_hist.root similarity index 100% rename from test/tree_cycles_hist.root rename to test/samples/tree_cycles_hist.root