Skip to content

Commit de7368f

Browse files
authored
Add size, ndims, length, etc methods to Dataspace (#758)
* Also `copy`, `==`, `hash`, `isempty,` and `isnull`. * Merge `size`-like methods for `Dataspace` and `Union{Dataset,Attribute}` * Start systematically testing `Dataspace` methods * Close temporary dataspace in get_dims
1 parent e816572 commit de7368f

File tree

7 files changed

+222
-22
lines changed

7 files changed

+222
-22
lines changed

docs/src/api_bindings.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ h5s_combine_select(space1_id::hid_t, op::Cint, space2_id::hid_t)
193193
h5s_copy(space_id::hid_t)
194194
h5s_create(class::Cint)
195195
h5s_create_simple(rank::Cint, current_dims::Ptr{hsize_t}, maximum_dims::Ptr{hsize_t})
196+
h5s_extent_equal(space1_id::hid_t, space2_id::hid_t)
196197
h5s_get_regular_hyperslab(space_id::hid_t, start::Ptr{hsize_t}, stride::Ptr{hsize_t}, count::Ptr{hsize_t}, block::Ptr{hsize_t})
197198
h5s_get_select_hyper_nblocks(space_id::hid_t)
198199
h5s_get_select_npoints(space_id::hid_t)

gen/api_defs.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@
203203
@bind h5s_copy(space_id::hid_t)::hid_t "Error copying dataspace"
204204
@bind h5s_create(class::Cint)::hid_t "Error creating dataspace"
205205
@bind h5s_create_simple(rank::Cint, current_dims::Ptr{hsize_t}, maximum_dims::Ptr{hsize_t})::hid_t "Error creating simple dataspace"
206+
@bind h5s_extent_equal(space1_id::hid_t, space2_id::hid_t)::htri_t "Error comparing dataspaces"
206207
@bind h5s_get_regular_hyperslab(space_id::hid_t, start::Ptr{hsize_t}, stride::Ptr{hsize_t}, count::Ptr{hsize_t}, block::Ptr{hsize_t})::herr_t "Error getting regular hyperslab selection"
207208
@bind h5s_get_simple_extent_dims(space_id::hid_t, dims::Ptr{hsize_t}, maxdims::Ptr{hsize_t})::Cint "Error getting the dimensions for a dataspace"
208209
@bind h5s_get_simple_extent_ndims(space_id::hid_t)::Cint "Error getting the number of dimensions for a dataspace"

src/HDF5.jl

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,9 @@ mutable struct Dataspace
264264
end
265265
end
266266
Base.cconvert(::Type{hid_t}, dspace::Dataspace) = dspace.id
267+
Base.:(==)(dspace1::Dataspace, dspace2::Dataspace) = h5s_extent_equal(checkvalid(dspace1), checkvalid(dspace2))
268+
Base.hash(dspace::Dataspace, h::UInt) = hash(dspace.id, hash(Dataspace, h))
269+
Base.copy(dspace::Dataspace) = Dataspace(h5s_copy(checkvalid(dspace)))
267270

268271
mutable struct Attribute
269272
id::hid_t
@@ -963,23 +966,8 @@ object_info(obj::Union{File,Object}) = h5o_get_info(checkvalid(obj))
963966
Base.length(obj::Union{Group,File}) = h5g_get_num_objs(checkvalid(obj))
964967
Base.length(x::Attributes) = object_info(x.parent).num_attrs
965968

966-
Base.isempty(x::Union{Dataset,Group,File}) = length(x) == 0
967-
function Base.size(obj::Union{Dataset,Attribute})
968-
dspace = dataspace(obj)
969-
dims, maxdims = get_dims(dspace)
970-
close(dspace)
971-
return dims
972-
end
973-
Base.size(dset::Union{Dataset,Attribute}, d) = d > ndims(dset) ? 1 : size(dset)[d]
974-
Base.length(dset::Union{Dataset,Attribute}) = prod(size(dset))
975-
Base.ndims(dset::Union{Dataset,Attribute}) = length(size(dset))
969+
Base.isempty(x::Union{Group,File}) = length(x) == 0
976970
Base.eltype(dset::Union{Dataset,Attribute}) = get_jl_type(dset)
977-
function isnull(obj::Union{Dataset,Attribute})
978-
dspace = dataspace(obj)
979-
ret = h5s_get_simple_extent_type(dspace) == H5S_NULL
980-
close(dspace)
981-
ret
982-
end
983971

984972
# filename and name
985973
filename(obj::Union{File,Group,Dataset,Attribute,Datatype}) = h5f_get_name(checkvalid(obj))
@@ -1094,6 +1082,61 @@ dataspace(sz::Dims{N}; max_dims::Union{Dims{N},Tuple{}}=()) where {N} = _dataspa
10941082
dataspace(sz1::Int, sz2::Int, sz3::Int...; max_dims::Union{Dims,Tuple{}}=()) = _dataspace(tuple(sz1, sz2, sz3...), max_dims)
10951083

10961084

1085+
function Base.ndims(obj::Union{Dataspace,Dataset,Attribute})
1086+
dspace = obj isa Dataspace ? checkvalid(obj) : dataspace(obj)
1087+
ret = Int(h5s_get_simple_extent_ndims(dspace))
1088+
obj isa Dataspace || close(dspace)
1089+
return ret
1090+
end
1091+
function Base.size(obj::Union{Dataspace,Dataset,Attribute})
1092+
dspace = obj isa Dataspace ? checkvalid(obj) : dataspace(obj)
1093+
h5_dims = h5s_get_simple_extent_dims(dspace, nothing)
1094+
N = length(h5_dims)
1095+
ret = ntuple(i -> @inbounds(Int(h5_dims[N-i+1])), N)
1096+
obj isa Dataspace || close(dspace)
1097+
return ret
1098+
end
1099+
function Base.size(obj::Union{Dataspace,Dataset,Attribute}, d::Integer)
1100+
d > 0 || throw(ArgumentError("invalid dimension d; must be positive integer"))
1101+
N = ndims(obj)
1102+
d > N && return 1
1103+
dspace = obj isa Dataspace ? obj : dataspace(obj)
1104+
h5_dims = h5s_get_simple_extent_dims(dspace, nothing)
1105+
ret = @inbounds Int(h5_dims[N - d + 1])
1106+
obj isa Dataspace || close(dspace)
1107+
return ret
1108+
end
1109+
function Base.length(obj::Union{Dataspace,Dataset,Attribute})
1110+
isnull(obj) && return 0
1111+
dspace = obj isa Dataspace ? obj : dataspace(obj)
1112+
h5_dims = h5s_get_simple_extent_dims(dspace, nothing)
1113+
ret = Int(prod(h5_dims))
1114+
obj isa Dataspace || close(dspace)
1115+
return ret
1116+
end
1117+
Base.isempty(dspace::Union{Dataspace,Dataset,Attribute}) = length(dspace) == 0
1118+
1119+
"""
1120+
isnull(dspace::Union{Dataspace, Dataset, Attribute})
1121+
1122+
Determines whether the given object has no size (consistent with the `H5S_NULL` dataspace).
1123+
1124+
# Examples
1125+
```julia-repl
1126+
julia> HDF5.isnull(dataspace(HDF5.EmptyArray{Float64}()))
1127+
true
1128+
1129+
julia> HDF5.isnull(dataspace((0,)))
1130+
false
1131+
```
1132+
"""
1133+
function isnull(obj::Union{Dataspace,Dataset,Attribute})
1134+
dspace = obj isa Dataspace ? checkvalid(obj) : dataspace(obj)
1135+
ret = h5s_get_simple_extent_type(dspace) == H5S_NULL
1136+
obj isa Dataspace || close(dspace)
1137+
return ret
1138+
end
1139+
10971140
function get_dims(dspace::Dataspace)
10981141
h5_dims, h5_maxdims = h5s_get_simple_extent_dims(dspace)
10991142
# reverse dimensions since hdf5 uses C-style order
@@ -1111,11 +1154,16 @@ function get_regular_hyperslab(dspace::Dataspace)
11111154
end
11121155

11131156
"""
1114-
get_dims(dset::HDF5.Dataset)
1157+
HDF5.get_dims(obj::Union{HDF5.Dataset, HDF5.Attribute})
11151158
1116-
Get the array dimensions from a dataset and return a tuple of dims and maxdims.
1159+
Get the array dimensions from a dataset or attribute and return a tuple of dims and maxdims.
11171160
"""
1118-
get_dims(dset::Dataset) = get_dims(dataspace(checkvalid(dset)))
1161+
function get_dims(dset::Union{Dataset,Attribute})
1162+
dspace = dataspace(dset)
1163+
ret = get_dims(dspace)
1164+
close(dspace)
1165+
return ret
1166+
end
11191167

11201168
"""
11211169
set_dims!(dset::HDF5.Dataset, new_dims::Dims)
@@ -1222,7 +1270,7 @@ function Base.read(obj::DatasetOrAttribute, ::Type{T}, I...) where T
12221270
sz = (1,)
12231271
scalar = true
12241272
elseif isempty(I)
1225-
sz, _ = get_dims(dspace)
1273+
sz = size(dspace)
12261274
else
12271275
sz = map(length, filter(i -> !isa(i, Int), indices))
12281276
if isempty(sz)
@@ -1376,7 +1424,7 @@ function readmmap(obj::Dataset, ::Type{T}) where {T}
13761424
dspace = dataspace(obj)
13771425
stype = h5s_get_simple_extent_type(dspace)
13781426
(stype != H5S_SIMPLE) && error("can only mmap simple dataspaces")
1379-
dims, _ = get_dims(dspace)
1427+
dims = size(dspace)
13801428

13811429
if isempty(dims)
13821430
return T[]
@@ -1596,7 +1644,7 @@ end
15961644
function hyperslab(dspace::Dataspace, I::Union{AbstractRange{Int},Int}...)
15971645
local dsel_id
15981646
try
1599-
dims, maxdims = get_dims(dspace)
1647+
dims = size(dspace)
16001648
n_dims = length(dims)
16011649
if length(I) != n_dims
16021650
error("Wrong number of indices supplied, supplied length $(length(I)) but expected $(n_dims).")

src/api.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,12 @@ function h5s_create_simple(rank, current_dims, maximum_dims)
782782
return var"#status#"
783783
end
784784

785+
function h5s_extent_equal(space1_id, space2_id)
786+
var"#status#" = ccall((:H5Sextent_equal, libhdf5), htri_t, (hid_t, hid_t), space1_id, space2_id)
787+
var"#status#" < 0 && error("Error comparing dataspaces")
788+
return var"#status#" > 0
789+
end
790+
785791
function h5s_get_regular_hyperslab(space_id, start, stride, count, block)
786792
var"#status#" = ccall((:H5Sget_regular_hyperslab, libhdf5), herr_t, (hid_t, Ptr{hsize_t}, Ptr{hsize_t}, Ptr{hsize_t}, Ptr{hsize_t}), space_id, start, stride, count, block)
787793
var"#status#" < 0 && error("Error getting regular hyperslab selection")

src/api_helpers.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,12 @@ function h5s_get_simple_extent_dims(space_id)
254254
h5s_get_simple_extent_dims(space_id, dims, maxdims)
255255
return dims, maxdims
256256
end
257+
function h5s_get_simple_extent_dims(space_id, ::Nothing)
258+
n = h5s_get_simple_extent_ndims(space_id)
259+
dims = Vector{hsize_t}(undef, n)
260+
h5s_get_simple_extent_dims(space_id, dims, C_NULL)
261+
return dims
262+
end
257263

258264

259265
###

test/dataspace.jl

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
@testset "Dataspaces" begin
2+
hsize_t = HDF5.hsize_t
3+
# Reference objects without using high-level API
4+
ds_null = HDF5.Dataspace(HDF5.h5s_create(HDF5.H5S_NULL))
5+
ds_scalar = HDF5.Dataspace(HDF5.h5s_create(HDF5.H5S_SCALAR))
6+
ds_zerosz = HDF5.Dataspace(HDF5.h5s_create_simple(1, hsize_t[0], hsize_t[0]))
7+
ds_vector = HDF5.Dataspace(HDF5.h5s_create_simple(1, hsize_t[5], hsize_t[5]))
8+
ds_matrix = HDF5.Dataspace(HDF5.h5s_create_simple(2, hsize_t[7, 5], hsize_t[7, 5]))
9+
ds_maxdim = HDF5.Dataspace(HDF5.h5s_create_simple(2, hsize_t[7, 5], hsize_t[20, 20]))
10+
ds_unlim = HDF5.Dataspace(HDF5.h5s_create_simple(1, hsize_t[1], [HDF5.H5S_UNLIMITED]))
11+
12+
# Testing basic property accessors of dataspaces
13+
14+
@test isvalid(ds_scalar)
15+
16+
@test ndims(ds_null) === 0
17+
@test ndims(ds_scalar) === 0
18+
@test ndims(ds_zerosz) === 1
19+
@test ndims(ds_vector) === 1
20+
@test ndims(ds_matrix) === 2
21+
22+
# Test that properties of existing datasets can be extracted.
23+
# Note: Julia reverses the order of dimensions when using the high-level API versus
24+
# the dimensions used above to create the reference objects.
25+
@test size(ds_null) === ()
26+
@test size(ds_scalar) === ()
27+
@test size(ds_zerosz) === (0,)
28+
@test size(ds_vector) === (5,)
29+
@test size(ds_matrix) === (5, 7)
30+
@test size(ds_maxdim) === (5, 7)
31+
32+
@test size(ds_null, 5) === 1
33+
@test size(ds_scalar, 5) === 1
34+
@test size(ds_zerosz, 5) === 1
35+
@test size(ds_vector, 5) === 1
36+
@test size(ds_matrix, 5) === 1
37+
@test size(ds_maxdim, 5) === 1
38+
@test_throws ArgumentError("invalid dimension d; must be positive integer") size(ds_null, 0)
39+
@test_throws ArgumentError("invalid dimension d; must be positive integer") size(ds_scalar, -1)
40+
41+
@test length(ds_null) === 0
42+
@test length(ds_scalar) === 1
43+
@test length(ds_zerosz) === 0
44+
@test length(ds_vector) === 5
45+
@test length(ds_matrix) === 35
46+
@test length(ds_maxdim) === 35
47+
48+
@test isempty(ds_null)
49+
@test !isempty(ds_scalar)
50+
@test isempty(ds_zerosz)
51+
@test !isempty(ds_vector)
52+
53+
@test HDF5.isnull(ds_null)
54+
@test !HDF5.isnull(ds_scalar)
55+
@test !HDF5.isnull(ds_zerosz)
56+
@test !HDF5.isnull(ds_vector)
57+
58+
@test HDF5.get_dims(ds_null) === ((), ())
59+
@test HDF5.get_dims(ds_scalar) === ((), ())
60+
@test HDF5.get_dims(ds_zerosz) === ((0,), (0,))
61+
@test HDF5.get_dims(ds_vector) === ((5,), (5,))
62+
@test HDF5.get_dims(ds_matrix) === ((5, 7), (5, 7))
63+
@test HDF5.get_dims(ds_maxdim) === ((5, 7), (20, 20))
64+
@test HDF5.get_dims(ds_unlim) === ((1,), (-1,))
65+
66+
# Can create new copies
67+
ds_tmp = copy(ds_maxdim)
68+
ds_tmp2 = HDF5.Dataspace(ds_tmp.id) # copy of ID, but new Julia object
69+
@test ds_tmp.id == ds_tmp2.id != ds_maxdim.id
70+
# Equality and hashing
71+
@test ds_tmp == ds_maxdim
72+
@test ds_tmp !== ds_maxdim
73+
@test hash(ds_tmp) != hash(ds_maxdim)
74+
@test ds_tmp == ds_tmp2
75+
@test ds_tmp !== ds_tmp2
76+
@test hash(ds_tmp) == hash(ds_tmp2)
77+
78+
# Behavior of closing dataspace objects
79+
close(ds_tmp)
80+
@test ds_tmp.id == -1
81+
@test !isvalid(ds_tmp)
82+
@test !isvalid(ds_tmp2)
83+
84+
# Validity checking in high-level operations
85+
@test_throws ErrorException("File or object has been closed") copy(ds_tmp)
86+
@test_throws ErrorException("File or object has been closed") ndims(ds_tmp)
87+
@test_throws ErrorException("File or object has been closed") size(ds_tmp)
88+
@test_throws ErrorException("File or object has been closed") size(ds_tmp, 1)
89+
@test_throws ErrorException("File or object has been closed") length(ds_tmp)
90+
@test_throws ErrorException("File or object has been closed") ds_tmp == ds_tmp2
91+
@test close(ds_tmp) === nothing # no error
92+
93+
# Test ability to create explicitly-sized dataspaces
94+
95+
@test dataspace(()) == ds_scalar
96+
@test dataspace((5,)) == ds_vector
97+
@test dataspace((5, 7)) == ds_matrix != ds_maxdim
98+
@test dataspace((5, 7), max_dims = (20, 20)) == ds_maxdim != ds_matrix
99+
@test dataspace((1,), max_dims = (-1,)) == ds_unlim
100+
# for ≥ 2 numbers, same as single tuple argument
101+
@test dataspace(5, 7) == ds_matrix
102+
@test dataspace(5, 7, 1) == dataspace((5, 7, 1))
103+
104+
# Test dataspaces derived from data
105+
106+
@test dataspace(nothing) == ds_null
107+
@test dataspace(HDF5.EmptyArray{Bool}()) == ds_null
108+
109+
@test dataspace(fill(1.0)) == ds_scalar
110+
@test dataspace(1) == ds_scalar
111+
@test dataspace(1 + 1im) == ds_scalar
112+
@test dataspace("string") == ds_scalar
113+
114+
@test dataspace(zeros(0)) == ds_zerosz
115+
@test dataspace(zeros(0, 0)) != ds_zerosz
116+
@test dataspace(zeros(5, 7)) == ds_matrix
117+
@test dataspace(HDF5.VLen([[1]])) == dataspace((1,))
118+
@test dataspace(HDF5.VLen([[1], [2]])) == dataspace((2,))
119+
120+
# Constructing dataspace for/from HDF5 dataset or attribute
121+
122+
mktemp() do path, io
123+
close(io)
124+
h5open(path, "w") do hid
125+
dset = create_dataset(hid, "dset", datatype(Int), ds_matrix)
126+
attr = create_attribute(dset, "attr", datatype(Bool), ds_vector)
127+
@test dataspace(dset) == ds_matrix
128+
@test dataspace(dset) !== ds_matrix
129+
@test dataspace(attr) == ds_vector
130+
@test dataspace(attr) !== ds_vector
131+
close(dset)
132+
close(attr)
133+
@test_throws ErrorException("File or object has been closed") dataspace(dset)
134+
@test_throws ErrorException("File or object has been closed") dataspace(attr)
135+
end
136+
end
137+
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ include("plain.jl")
1010
include("compound.jl")
1111
include("custom.jl")
1212
include("reference.jl")
13+
include("dataspace.jl")
1314
include("hyperslab.jl")
1415
include("readremote.jl")
1516
include("extend_test.jl")

0 commit comments

Comments
 (0)