""" A basic container type to assist the matching of MC events in the offline tree with the events in the online tree. """ # TODO: add tests (need a proper testfile in km3net-testdata) struct MCEventMatcher f::ROOTFile end Base.length(itr::MCEventMatcher) = length(itr.f.online.events) Base.lastindex(itr::MCEventMatcher) = length(itr) function Base.getindex(itr::MCEventMatcher, idx::Integer) event = itr.f.online.events[idx] mc_idx = event.header.trigger_counter + 1 mc_event = itr.f.offline[mc_idx] (event, mc_event) end function Base.iterate(itr::MCEventMatcher, state=1) state > length(itr) && return nothing (itr[state], state + 1) end countevents(tree::OfflineTree) = length(tree) countevents(tree::OnlineTree) = length(tree.events) countevents(tree::OscillationsData) = length(tree) triggercounterof(e::Evt) = e.trigger_counter frameindexof(e::Evt) = e.frame_index triggercounterof(e::DAQEvent) = e.header.trigger_counter frameindexof(e::DAQEvent) = e.header.frame_index """ Retrieves the event with for a given `frame_index` and `trigger_counter`. """ function getevent(tree::T, frame_index, trigger_counter) where T<:Union{OnlineTree, OfflineTree} lookup = tree._frame_index_trigger_counter_lookup_map key = (frame_index, trigger_counter) if haskey(lookup, key) event_idx = lookup[key] return getevent(tree, event_idx) end highest_event_idx = length(lookup) == 0 ? 0 : maximum(values(lookup)) for event_idx in (highest_event_idx+1):countevents(tree) event = getevent(tree, event_idx) fi = frameindexof(event) tc = triggercounterof(event) lookup[(fi, tc)] = event_idx if fi == frame_index && tc == trigger_counter return event end end error("No online event found for frame_index=$(frame_index) and trigger_counter=$(trigger_counter).") end getevent(tree::OfflineTree, idx) = tree[idx] getevent(tree::OnlineTree, idx) = tree.events[idx] getevent(tree::OscillationsData, idx) = tree[idx] """ An iterator which yields a `Vector{Summaryslice}` containing summaryslices of a given `time_interval` (in seconds). Useful when analysing summary data with fixed time intervals. The returned summaryslices are also sorted in time. # Examples ```julia-repl julia> f = ROOTFile("KM3NeT_00000133_00014728.root") ROOTFile{OnlineTree (83509 events, 106969 summaryslices)} julia> sii = SummarysliceIntervalIterator(f, 60) SummarysliceIntervalIterator (10739.7s, 60s intervals, 179 chunks) julia> for summaryslices in sii @show length(summaryslices) @show summaryslices[1].header break end length(summaryslices) = 599 (summaryslices[1]).header = SummarysliceHeader(133, 14728, 134, UTCExtended(1676246413, 400000000, 0)) ``` !!! note Short time intervals (a few tens of seconds) will likely return `Vector{Summaryslice}`s with few entries in the first and last iterations due to a delay in run changes. The number of frames per summaryslice will gradually increase due to the asynchronous nature of the run transition. See the example below with a time inteval of 10s and 100 active optical modules. ```julia-repl julia> sii = SummarysliceIntervalIterator(f, 10) SummarysliceIntervalIterator (106.2s, 10s intervals, 11 chunks) julia> for summaryslices in sii n = length(summaryslices) @show n end n = 73 n = 100 n = 100 n = 100 n = 100 n = 100 n = 100 n = 100 n = 100 n = 96 n = 31 ``` """ struct SummarysliceIntervalIterator sc::SummarysliceContainer first_frame_index::Int time_interval::Int # [s] n_chunks::Int timespan::Float64 indices::Vector{Int} function SummarysliceIntervalIterator(f::ROOTFile, time_interval) ss = f.online.summaryslices sorted_summaryslice_indices = sortperm([sh.frame_index for sh in ss.headers]) first_frame_index = first(f.online.summaryslices).header.frame_index timespan = (ss.headers[sorted_summaryslice_indices[end]].frame_index - ss.headers[first(sorted_summaryslice_indices)].frame_index) / 10 n_chunks = Int(ceil(timespan / time_interval)) new(f.online.summaryslices, first_frame_index, time_interval, n_chunks, timespan, sorted_summaryslice_indices) end end function Base.show(io::IO, sii::SummarysliceIntervalIterator) print(io, "SummarysliceIntervalIterator ($(sii.timespan)s, $(sii.time_interval)s intervals, $(sii.n_chunks) chunks)") end Base.eltype(::Type{SummarysliceIntervalIterator}) = Vector{KM3io.Summaryslice} Base.length(sii::SummarysliceIntervalIterator) = sii.n_chunks function Base.iterate(sii::SummarysliceIntervalIterator, state=(chunk_idx=1, s_idx=1)) state.chunk_idx > sii.n_chunks && return nothing out_size = sii.time_interval * 10 out = empty!(Vector{KM3io.Summaryslice}(undef, out_size)) frame_index_upper = sii.first_frame_index + state.chunk_idx * sii.time_interval * 10 s_idx = state.s_idx while s_idx <= length(sii.indices) idx = sii.indices[s_idx] summaryslice = sii.sc[idx] if summaryslice.header.frame_index < frame_index_upper push!(out, summaryslice) s_idx += 1 else break end end return (out, (chunk_idx=state.chunk_idx+1, s_idx=s_idx)) end