Skip to content
Open
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "LegendDataManagement"
uuid = "9feedd95-f0e0-423f-a8dc-de0970eae6b3"
version = "0.6.26"
version = "0.6.27"

[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Expand Down Expand Up @@ -55,7 +55,7 @@ IntervalSets = "0.6, 0.7"
JSON = "1"
LRUCache = "1.5"
LegendDataTypes = "0.1.17"
LegendHDF5IO = "0.1.14"
LegendHDF5IO = "0.2"
LinearAlgebra = "<0.0.1, 1"
MIMEs = "0.1, 1"
Markdown = "<0.0.1, 1"
Expand Down
59 changes: 11 additions & 48 deletions ext/LegendDataManagementLegendHDF5IOExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,6 @@ const RSelLike = Union{AbstractDataSelectorLike, FileKey, Unitful.Time, Abstract
const PossibleDataSelectors = [DataTier, DataCategory, DataPeriod, DataRun, DataPartition, DetectorId]


const dataselector_bytypes = Dict{Type, String}()

LegendHDF5IO.datatype_to_string(::Type{<:T}) where {T <: LegendDataManagement.DataSelector} =
dataselector_bytypes[T]

function LegendHDF5IO._array_type(::Type{Array{T, N}}
) where {T <: LegendDataManagement.DataSelector, N}

AbstractArray{T, N}
end

# write LegendDataManagement.DataSelector
function LegendHDF5IO.create_entry(parent::LHDataStore, name::AbstractString,
data::T; kwargs...) where {T <:LegendDataManagement.DataSelector}
Expand Down Expand Up @@ -146,23 +135,14 @@ function _init_workers(wpool::WorkerPool)
end

function __init__()
function extend_datatype_dict(::Type{T}, key::String
) where {T <: LegendDataManagement.DataSelector}

LegendHDF5IO._datatype_dict[key] = T
dataselector_bytypes[T] = key
for (name, T) in (
"expsetup" => ExpSetup, "datatier" => DataTier, "datarun" => DataRun,
"dataperiod" => DataPeriod, "datacategory" => DataCategory, "timestamp" => Timestamp,
"filekey" => FileKey, "channelid" => ChannelId, "detectorid" => DetectorId,
"datapartition" => DataPartition,
)
LegendHDF5IO.register_datatype!(name, T)
end

(@isdefined ExpSetup) && extend_datatype_dict(ExpSetup, "expsetup")
(@isdefined DataTier) && extend_datatype_dict(DataTier, "datatier")
(@isdefined DataRun) && extend_datatype_dict(DataRun, "datarun")
(@isdefined DataPeriod) && extend_datatype_dict(DataPeriod, "dataperiod")
(@isdefined DataCategory) && extend_datatype_dict(DataCategory, "datacategory")
(@isdefined Timestamp) && extend_datatype_dict(Timestamp, "timestamp")
(@isdefined FileKey) && extend_datatype_dict(FileKey, "filekey")
(@isdefined ChannelId) && extend_datatype_dict(ChannelId, "channelid")
(@isdefined DetectorId) && extend_datatype_dict(DetectorId, "detectorid")
(@isdefined DataPartition) && extend_datatype_dict(DataPartition, "datapartition")
end

# Paths are resolved and stat'ed one at a time: file metadata lookups are expensive on
Expand Down Expand Up @@ -221,29 +201,12 @@ function _nest(paths, vals)
end)
end

# Every column of a group keeps the rows `sel` selects; a single column is unwrapped.
# TODO: pass a mask to the read once `LH5Array` supports `AbstractVector{Bool}` indices.
# Every column of a group keeps the rows `sel` selects; a single column is unwrapped. Rows
# named by index are read as LegendHDF5IO reads index vectors: in one bounding read, a single
# selection or one read per run of rows, whichever costs least for their spacing.
_load_all_keys(nt::NamedTuple, sel=(:)) = length(nt) == 1 ? _load_all_keys(nt[first(keys(nt))], sel) :
NamedTuple{keys(nt)}(map(k -> _load_all_keys(nt[k], sel), keys(nt)))
_load_all_keys(x::Union{AbstractArray, Table}, sel=(:)) = sel === (:) ? x[:] : x[:][sel]
# Rows named by index are read range by range, as an `LH5Array` reads ranges. Every read of a
# column costs tens of microseconds however few rows it takes, so rows of scalars are read
# along with gaps of up to `_bridged_rows` between them rather than one range per row; rows
# holding arrays -- waveforms -- cost their bytes and are read in ranges of adjacent rows.
const _bridged_rows = 256
function _load_all_keys(x::Union{AbstractArray, Table}, sel::AbstractVector{Int})
isempty(sel) && return x[1:0]
maxgap = isbitstype(eltype(x)) ? _bridged_rows : 0
rows = sort(unique(sel))
ranges = [rows[1]:rows[1]]
for r in rows[2:end]
r - last(ranges[end]) - 1 <= maxgap ? (ranges[end] = first(ranges[end]):r) : push!(ranges, r:r)
end
read = fast_flatten([x[rg] for rg in ranges])
offsets = cumsum(length.(ranges)) .- length.(ranges)
pos = map(i -> (k = searchsortedlast(first.(ranges), i); offsets[k] + i - first(ranges[k]) + 1), sel)
pos == 1:length(read) ? read : read[pos]
end
_load_all_keys(x::Union{AbstractArray, Table}, sel=(:)) = x[sel]
_load_all_keys(x, sel=(:)) = x

_nrows(nt::NamedTuple) = _nrows(first(nt))
Expand Down
3 changes: 3 additions & 0 deletions src/legend_data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ tier_data[tier::DataTierLike, category::DataCategoryLike, period::DataPeriodLike
tier_data[tier::DataTierLike, category::DataCategoryLike, period::DataPeriodLike, run::DataRunLike, det::DetectorIdLike]

tier_data[tier::DataTierLike, filekey::FileKeyLike]
tier_data[tier::DataTierLike, filekeys::AbstractVector{<:FileKey}]
tier_data[tier::DataTierLike, filekey::FileKey, ch::ChannelIdLike]
tier_data[tier::DataTierLike, filekey::FileKey, det::DetectorIdLike]
```
Expand Down Expand Up @@ -252,6 +253,8 @@ function _getindex_impl(tier_data::LegendTierData, tier::DataTierLike, filekey::
)
end

_getindex_impl(tier_data::LegendTierData, tier::DataTierLike, filekeys::AbstractVector{<:FileKey}) = [tier_data[tier, filekey] for filekey in filekeys]

# Disambiguation between DataCategory and FileKey:
function _getindex_impl(tier_data::LegendTierData, tier::DataTierLike, s::AbstractString)
if _can_convert_to(DataCategory, s)
Expand Down
4 changes: 2 additions & 2 deletions src/utils/data_utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,10 @@ function load_partition_ch(open_func::Function, flatten_func::Function, data::Le

function get_key_evt_tbl(ds, period, run, k)
if select_random
t = Table(ds[ch, tier, k])[:]
t = ds[ch, tier, k][:]
t[evt_range[(period, run)][k]]
else
Table(ds[ch, tier, k])[evt_range[(period, run)][k]]
ds[ch, tier, k][evt_range[(period, run)][k]]
end
end
# load data with given range
Expand Down
3 changes: 3 additions & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[deps]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
ArraysOfArrays = "65a8f2f4-9b39-5baf-92e2-a9cc46fdf018"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Expand All @@ -12,6 +13,7 @@ ParallelProcessingTools = "8e8a01fc-6193-5ca1-a2f1-20776dae4199"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
PropDicts = "4dc08600-4268-439e-8673-d706fafbb426"
PropertyFunctions = "09e99361-2bb8-48a2-a80f-de58f0739eb4"
RadiationDetectorSignals = "bf2c0563-65cf-5db2-a620-ceb7de82658c"
SolidStateDetectors = "71e43887-2bd9-5f77-aebd-47f656f0a3f0"
StructArrays = "09ab397b-f2b6-538f-b94a-2f83cf4a842a"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Expand All @@ -23,3 +25,4 @@ YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6"
[compat]
Documenter = "1"
LegendTestData = "0.3.7"
RadiationDetectorSignals = "0.3.12"
69 changes: 69 additions & 0 deletions test/test_ext_legendhdf5io.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ using Test

using LegendHDF5IO
using LegendTestData
using ArraysOfArrays
using PropertyFunctions
using RadiationDetectorSignals
using StructArrays
using TypedTables
using Unitful

Expand Down Expand Up @@ -527,4 +530,70 @@ using Distributed
end
end
end

@testset "_load_all_keys" begin
Ext = Base.get_extension(LegendDataManagement, :LegendDataManagementLegendHDF5IOExt)
mktempdir() do dir
fn = joinpath(dir, "load_all_keys.lh5")
n = 200
a = rand(Float32, n)
wf = VectorOfVectors([rand(Float32, 8) for _ in 1:n])
wfs = ArrayOfRDWaveforms((fill(range(0.0u"μs", 1.0u"μs"; length = 8), n), VectorOfSimilarVectors(rand(Float32, 8, n))))
lh5open(fn, "w") do h
h["tbl"] = StructArray((a = a, wf = wf))
h["nested"] = StructArray((t = a, sub = StructArray((a = a, wf = wf))))
h["tbl_rdwf"] = StructArray((a = a, wf = wfs))
end
lh5open(fn, "r") do h
tbl = h["tbl"]
@test tbl isa LegendHDF5IO.LH5LazyTable

# a lazily read table is loaded, not passed through as it is
full = Ext._load_all_keys(tbl)
@test full isa StructArray
@test full.a isa Vector{Float32}
@test full.a == a
@test full.wf == wf

rows = [3, 7, 7, 150, 2]
byrow = Ext._load_all_keys(tbl, rows)
@test byrow.a == a[rows]
@test byrow.wf == wf[rows]

# tables concatenated lazily load like the tables they are made of
twice = vcat(tbl, tbl)
@test Ext._load_all_keys(twice, [3, n + 7, 2]).a == a[[3, 7, 2]]
@test Ext._load_all_keys(twice).wf == vcat(wf, wf)

mask = a .> 0.5f0
bymask = Ext._load_all_keys(tbl, mask)
@test bymask.a == a[mask]
@test bymask.wf == wf[mask]

col = Ext._load_all_keys(tbl.a, rows)
@test col isa Vector{Float32}
@test col == a[rows]

# rows of a table with waveform columns are usable as rows
tbl_rdwf = h["tbl_rdwf"]
rows_wf = Ext._load_all_keys(tbl_rdwf, rows)
@test rows_wf.a == a[rows]
@test rows_wf[1].wf == wfs[rows[1]]
@test Ext._load_all_keys(tbl_rdwf)[1].wf == wfs[1]
@test Ext._load_all_keys(tbl_rdwf, mask)[1].wf == wfs[findfirst(mask)]

# a table nested in a table is loaded along with it
nested = Ext._load_all_keys(h["nested"])
@test nested.sub.a isa Vector{Float32}
@test nested.sub.wf == wf

nt = Ext._load_all_keys((x = tbl.a, y = tbl.wf))
@test nt.x == a
@test nt.y == wf
@test Ext._load_all_keys((x = tbl.a,)) == a

@test Ext._load_all_keys("not an array") == "not an array"
end
end
end
end
3 changes: 3 additions & 0 deletions test/test_legend_data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ include("testing_utils.jl")

@test getproperty(l200, :tier) isa LegendDataManagement.LegendTierData
@test normalize_path(@inferred(l200.tier[:raw, filekey])) == "/some/other/storage/raw_lh5/cal/p02/r006/l200-p02-r006-cal-20221226T200846Z-tier_raw.lh5"
other_filekey = FileKey("l200-p02-r006-cal-20221226T210000Z")
@test @inferred(l200.tier[:raw, [filekey, other_filekey]]) isa Vector{String}
@test l200.tier[:raw, [filekey, other_filekey]] == [l200.tier[:raw, filekey], l200.tier[:raw, other_filekey]]
@test normalize_path(@inferred(l200.tier[:raw, "l200-p02-r006-cal-20221226T200846Z"])) == "/some/other/storage/raw_lh5/cal/p02/r006/l200-p02-r006-cal-20221226T200846Z-tier_raw.lh5"
@test normalize_path(@inferred(l200.tier[:dsp, "l200-p02-r006-cal-20221226T200846Z"])) == normalize_path(joinpath(testdata_dir, "generated", "tier", "dsp", "cal", "p02", "r006", "l200-p02-r006-cal-20221226T200846Z-tier_dsp.lh5"))

Expand Down
Loading