ruby-hdf5 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +72 -69
- data/lib/hdf5/attribute.rb +180 -73
- data/lib/hdf5/data_helpers.rb +190 -0
- data/lib/hdf5/dataset.rb +622 -101
- data/lib/hdf5/dtype.rb +214 -0
- data/lib/hdf5/ffi.rb +48 -10
- data/lib/hdf5/ffi_10.rb +0 -485
- data/lib/hdf5/ffi_14.rb +0 -479
- data/lib/hdf5/ffi_20.rb +9697 -0
- data/lib/hdf5/file.rb +86 -10
- data/lib/hdf5/file_context.rb +70 -0
- data/lib/hdf5/group.rb +59 -15
- data/lib/hdf5/hierarchy.rb +107 -0
- data/lib/hdf5/selection.rb +97 -0
- data/lib/hdf5/string_codec.rb +106 -0
- data/lib/hdf5/version.rb +1 -1
- data/lib/hdf5.rb +31 -0
- metadata +24 -3
data/lib/hdf5/dataset.rb
CHANGED
|
@@ -1,72 +1,53 @@
|
|
|
1
1
|
module HDF5
|
|
2
2
|
class Dataset
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def datatype_id_for(data)
|
|
15
|
-
if data.all? { |value| value.is_a?(Integer) }
|
|
16
|
-
validate_native_int_range!(data)
|
|
17
|
-
HDF5::FFI.H5T_NATIVE_INT
|
|
18
|
-
elsif data.all? { |value| value.is_a?(Numeric) }
|
|
19
|
-
HDF5::FFI.H5T_NATIVE_DOUBLE
|
|
20
|
-
else
|
|
21
|
-
raise HDF5::Error, 'Only numeric dataset data is supported'
|
|
3
|
+
class << self
|
|
4
|
+
def create(parent_id, name, data = nil, shape: nil, dtype: nil, maxshape: nil, chunks: nil, compression: nil,
|
|
5
|
+
compression_opts: nil, shuffle: false, fletcher32: false, fillvalue: nil, context: nil, casting: :safe)
|
|
6
|
+
raise HDF5::Error, 'shape: and dtype: are required when data: is omitted' if data.nil? && (!shape || !dtype)
|
|
7
|
+
|
|
8
|
+
empty_data = data.is_a?(HDF5::Empty)
|
|
9
|
+
if empty_data
|
|
10
|
+
raise ShapeError, 'Null datasets cannot have a shape' unless shape.nil?
|
|
11
|
+
if data.dtype.kind == :string
|
|
12
|
+
raise UnsupportedFeatureError, 'Creating Null string datasets is not yet supported'
|
|
13
|
+
end
|
|
22
14
|
end
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
else
|
|
30
|
-
buffer = ::FFI::MemoryPointer.new(:double, data.length)
|
|
31
|
-
buffer.write_array_of_double(data.map(&:to_f))
|
|
15
|
+
string_data = HDF5::StringCodec.string_data?(data)
|
|
16
|
+
_string_values, string_shape = HDF5::StringCodec.normalize_data(data) if string_data
|
|
17
|
+
unless data.nil? || string_data || empty_data
|
|
18
|
+
narray = HDF5::DataHelpers.normalize_data(data,
|
|
19
|
+
label: 'Dataset data', dtype: dtype && DType.for_symbol(dtype), casting:,
|
|
20
|
+
convert: false)
|
|
32
21
|
end
|
|
22
|
+
unless string_data
|
|
23
|
+
dtype_object = if empty_data
|
|
24
|
+
data.dtype
|
|
25
|
+
else
|
|
26
|
+
(dtype ? DType.for_symbol(dtype) : DType.for_numo(narray))
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
type_id = string_data ? HDF5::StringCodec.datatype_id : dtype_object.storage_type_id
|
|
30
|
+
shape = string_data ? string_shape : narray.shape if shape.nil? && !data.nil? && !empty_data
|
|
31
|
+
raise HDF5::Error, 'Dataset shape must match data shape' if narray && shape != narray.shape
|
|
32
|
+
raise HDF5::ShapeError, 'Dataset shape must match string data shape' if string_data && shape != string_shape
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def native_int_bounds
|
|
38
|
-
bits = ::FFI.type_size(:int) * 8
|
|
39
|
-
max = (1 << (bits - 1)) - 1
|
|
40
|
-
min = -(1 << (bits - 1))
|
|
41
|
-
[min, max]
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def validate_native_int_range!(values)
|
|
45
|
-
min, max = native_int_bounds
|
|
46
|
-
out_of_range = values.find { |value| value < min || value > max }
|
|
47
|
-
return unless out_of_range
|
|
48
|
-
|
|
49
|
-
raise HDF5::Error,
|
|
50
|
-
"Integer value #{out_of_range} is outside native int range (#{min}..#{max}). Use a smaller value."
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
private_constant :DataHelpers
|
|
34
|
+
raise HDF5::Error, 'Null datasets cannot have maxshape or chunks' if empty_data && (maxshape || chunks)
|
|
55
35
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
dims = ::FFI::MemoryPointer.new(:ulong_long, 1)
|
|
60
|
-
dims.write_array_of_ulong_long([values.length])
|
|
61
|
-
datatype_id = DataHelpers.datatype_id_for(values)
|
|
62
|
-
dataspace_id = HDF5::FFI.H5Screate_simple(1, dims, nil)
|
|
36
|
+
validate_maxshape(maxshape, shape) if maxshape
|
|
37
|
+
chunks = :auto if maxshape && chunks.nil?
|
|
38
|
+
dataspace_id = create_dataspace(shape, maxshape)
|
|
63
39
|
raise HDF5::Error, "Failed to create dataspace for dataset: #{name}" if dataspace_id < 0
|
|
64
40
|
|
|
41
|
+
dcpl_id = create_property_list(shape, dtype_object, chunks:, compression:, compression_opts:, shuffle:, fletcher32:,
|
|
42
|
+
fillvalue:, casting:)
|
|
43
|
+
|
|
65
44
|
dataset = from_id(
|
|
66
|
-
HDF5::FFI.H5Dcreate2(parent_id, name,
|
|
67
|
-
HDF5::DEFAULT_PROPERTY_LIST, HDF5::DEFAULT_PROPERTY_LIST), name
|
|
45
|
+
HDF5::FFI.H5Dcreate2(parent_id, name, type_id, dataspace_id, HDF5::DEFAULT_PROPERTY_LIST,
|
|
46
|
+
dcpl_id || HDF5::DEFAULT_PROPERTY_LIST, HDF5::DEFAULT_PROPERTY_LIST), name, context
|
|
68
47
|
)
|
|
69
|
-
dataset.write(
|
|
48
|
+
dataset.write(data) if string_data
|
|
49
|
+
dataset.write(narray) if narray
|
|
50
|
+
initialized = true
|
|
70
51
|
return dataset unless block_given?
|
|
71
52
|
|
|
72
53
|
begin
|
|
@@ -74,12 +55,20 @@ module HDF5
|
|
|
74
55
|
ensure
|
|
75
56
|
dataset.close
|
|
76
57
|
end
|
|
58
|
+
rescue StandardError
|
|
59
|
+
if dataset && !initialized
|
|
60
|
+
dataset.close unless dataset.closed?
|
|
61
|
+
HDF5::FFI.H5Ldelete(parent_id, name, HDF5::DEFAULT_PROPERTY_LIST)
|
|
62
|
+
end
|
|
63
|
+
raise
|
|
77
64
|
ensure
|
|
65
|
+
HDF5::FFI.H5Tclose(type_id) if string_data && type_id && type_id >= 0
|
|
66
|
+
HDF5::FFI.H5Pclose(dcpl_id) if dcpl_id && dcpl_id >= 0
|
|
78
67
|
HDF5::FFI.H5Sclose(dataspace_id) if dataspace_id && dataspace_id >= 0
|
|
79
68
|
end
|
|
80
69
|
|
|
81
|
-
def open(parent_id, name)
|
|
82
|
-
dataset = from_id(HDF5::FFI.H5Dopen2(parent_id, name, HDF5::DEFAULT_PROPERTY_LIST), name)
|
|
70
|
+
def open(parent_id, name, context: nil)
|
|
71
|
+
dataset = from_id(HDF5::FFI.H5Dopen2(parent_id, name, HDF5::DEFAULT_PROPERTY_LIST), name, context)
|
|
83
72
|
return dataset unless block_given?
|
|
84
73
|
|
|
85
74
|
begin
|
|
@@ -91,51 +80,204 @@ module HDF5
|
|
|
91
80
|
|
|
92
81
|
private
|
|
93
82
|
|
|
94
|
-
def
|
|
83
|
+
def create_dataspace(shape, maxshape = nil)
|
|
84
|
+
return HDF5::FFI.H5Screate(:H5S_NULL) if shape.nil?
|
|
85
|
+
return HDF5::FFI.H5Screate(:H5S_SCALAR) if shape.empty?
|
|
86
|
+
|
|
87
|
+
dims = ::FFI::MemoryPointer.new(:ulong_long, shape.length)
|
|
88
|
+
dims.write_array_of_ulong_long(shape)
|
|
89
|
+
maxdims = if maxshape
|
|
90
|
+
::FFI::MemoryPointer.new(:ulong_long, maxshape.length).tap do |pointer|
|
|
91
|
+
pointer.write_array_of_ulong_long(maxshape.map do |dimension|
|
|
92
|
+
dimension.nil? ? unlimited_dimension : dimension
|
|
93
|
+
end)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
HDF5::FFI.H5Screate_simple(shape.length, dims, maxdims)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def validate_maxshape(maxshape, shape)
|
|
100
|
+
unless maxshape.is_a?(Array) && maxshape.length == shape.length
|
|
101
|
+
raise HDF5::Error,
|
|
102
|
+
'maxshape must be an Array matching dataset rank'
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
valid = maxshape.zip(shape).all? do |maximum, dimension|
|
|
106
|
+
maximum.nil? || maximum.is_a?(Integer) && maximum >= dimension
|
|
107
|
+
end
|
|
108
|
+
raise HDF5::Error, 'maxshape dimensions must be nil or integers no smaller than shape' unless valid
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def unlimited_dimension
|
|
112
|
+
(1 << (::FFI.type_size(:ulong_long) * 8)) - 1
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def create_property_list(shape, dtype_object, chunks:, compression:, compression_opts:, shuffle:, fletcher32:,
|
|
116
|
+
fillvalue:, casting:)
|
|
117
|
+
chunked = chunks || compression || compression_opts || shuffle || fletcher32
|
|
118
|
+
return unless chunked || !fillvalue.nil?
|
|
119
|
+
raise HDF5::Error, 'Chunked storage is not supported for scalar datasets' if chunked && shape.empty?
|
|
120
|
+
if dtype_object.nil? && !fillvalue.nil?
|
|
121
|
+
raise UnsupportedFeatureError,
|
|
122
|
+
'fillvalue is not supported for string datasets'
|
|
123
|
+
end
|
|
124
|
+
raise HDF5::Error, 'Unsupported compression' unless compression.nil? || compression == :gzip
|
|
125
|
+
raise HDF5::Error, 'compression_opts requires compression: :gzip' if compression_opts && compression != :gzip
|
|
126
|
+
|
|
127
|
+
itemsize = dtype_object ? dtype_object.itemsize : ::FFI.type_size(:pointer)
|
|
128
|
+
if chunked
|
|
129
|
+
chunk_shape = if chunks == :auto || chunks.nil?
|
|
130
|
+
auto_chunk_shape(shape,
|
|
131
|
+
itemsize)
|
|
132
|
+
else
|
|
133
|
+
validate_chunk_shape(chunks,
|
|
134
|
+
shape)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
compression_level = compression_opts || 4
|
|
138
|
+
unless compression.nil? || compression_level.between?(
|
|
139
|
+
0, 9
|
|
140
|
+
)
|
|
141
|
+
raise HDF5::Error,
|
|
142
|
+
'gzip compression_opts must be between 0 and 9'
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
validate_filter_available(1, 'gzip', capability: 1) if compression == :gzip
|
|
146
|
+
validate_filter_available(2, 'shuffle', capability: 1) if shuffle
|
|
147
|
+
validate_filter_available(3, 'Fletcher32', capability: 1) if fletcher32
|
|
148
|
+
|
|
149
|
+
dcpl_id = HDF5::FFI.H5Pcreate(HDF5::FFI.H5P_CLS_DATASET_CREATE_ID_g)
|
|
150
|
+
raise HDF5::Error, 'Failed to create dataset property list' if dcpl_id < 0
|
|
151
|
+
|
|
152
|
+
if chunked
|
|
153
|
+
dims = ::FFI::MemoryPointer.new(:ulong_long, chunk_shape.length)
|
|
154
|
+
dims.write_array_of_ulong_long(chunk_shape)
|
|
155
|
+
check_property_status(HDF5::FFI.H5Pset_chunk(dcpl_id, chunk_shape.length, dims), 'set chunk dimensions')
|
|
156
|
+
end
|
|
157
|
+
check_property_status(HDF5::FFI.H5Pset_shuffle(dcpl_id), 'enable shuffle') if shuffle
|
|
158
|
+
if compression == :gzip
|
|
159
|
+
check_property_status(HDF5::FFI.H5Pset_deflate(dcpl_id, compression_level),
|
|
160
|
+
'enable gzip')
|
|
161
|
+
end
|
|
162
|
+
check_property_status(HDF5::FFI.H5Pset_fletcher32(dcpl_id), 'enable Fletcher32') if fletcher32
|
|
163
|
+
unless fillvalue.nil?
|
|
164
|
+
value = HDF5::DataHelpers.normalize_data(fillvalue, dtype: dtype_object, casting:, label: 'Fill value')
|
|
165
|
+
raise HDF5::Error, 'fillvalue must be scalar' unless value.shape.empty?
|
|
166
|
+
|
|
167
|
+
check_property_status(HDF5::FFI.H5Pset_fill_value(dcpl_id, dtype_object.memory_type_id, HDF5::DataHelpers.buffer_for(value)),
|
|
168
|
+
'set fill value')
|
|
169
|
+
end
|
|
170
|
+
dcpl_id
|
|
171
|
+
rescue StandardError
|
|
172
|
+
HDF5::FFI.H5Pclose(dcpl_id) if dcpl_id && dcpl_id >= 0
|
|
173
|
+
raise
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def auto_chunk_shape(shape, itemsize)
|
|
177
|
+
target_bytes = 256 * 1024
|
|
178
|
+
chunk_shape = shape.map { |dimension| [dimension, 1].max }
|
|
179
|
+
|
|
180
|
+
while chunk_shape.inject(itemsize, :*) > target_bytes
|
|
181
|
+
axis = chunk_shape.each_index.max_by { |index| chunk_shape[index] }
|
|
182
|
+
chunk_shape[axis] = (chunk_shape[axis] / 2.0).ceil
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
chunk_shape
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def validate_chunk_shape(chunks, shape)
|
|
189
|
+
unless chunks.is_a?(Array) && chunks.length == shape.length
|
|
190
|
+
raise HDF5::Error,
|
|
191
|
+
'chunks must be an Array matching dataset rank'
|
|
192
|
+
end
|
|
193
|
+
unless chunks.all? { |dimension| dimension.is_a?(Integer) && dimension.positive? }
|
|
194
|
+
raise HDF5::Error, 'chunk dimensions must be positive integers'
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
chunks
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def check_property_status(status, operation)
|
|
201
|
+
raise HDF5::Error, "Failed to #{operation}" if status < 0
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def validate_filter_available(filter_id, name, capability:)
|
|
205
|
+
unless HDF5::FFI.H5Zfilter_avail(filter_id).positive?
|
|
206
|
+
raise UnsupportedFeatureError, "HDF5 #{name} filter is unavailable"
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
flags = ::FFI::MemoryPointer.new(:uint)
|
|
210
|
+
status = HDF5::FFI.H5Zget_filter_info(filter_id, flags)
|
|
211
|
+
raise HDF5::Error, "Failed to inspect HDF5 #{name} filter" if status < 0
|
|
212
|
+
raise UnsupportedFeatureError, "HDF5 #{name} filter cannot encode data" if (flags.read_uint & capability).zero?
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def from_id(dataset_id, name, context)
|
|
95
216
|
dataset = allocate
|
|
96
|
-
dataset.send(:initialize_from_id, dataset_id, name)
|
|
217
|
+
dataset.send(:initialize_from_id, dataset_id, name, context)
|
|
97
218
|
dataset
|
|
98
219
|
end
|
|
99
220
|
end
|
|
100
221
|
|
|
101
222
|
def initialize(parent_id, name)
|
|
102
|
-
initialize_from_id(HDF5::FFI.H5Dopen2(parent_id, name, HDF5::DEFAULT_PROPERTY_LIST), name)
|
|
223
|
+
initialize_from_id(HDF5::FFI.H5Dopen2(parent_id, name, HDF5::DEFAULT_PROPERTY_LIST), name, nil)
|
|
103
224
|
end
|
|
104
225
|
|
|
105
226
|
def attrs
|
|
106
|
-
|
|
227
|
+
ensure_open!
|
|
228
|
+
@attrs ||= AttributeManager.new(@dataset_id, @context)
|
|
107
229
|
end
|
|
108
230
|
|
|
109
|
-
def write(data)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
231
|
+
def write(data, selection: nil, casting: :safe)
|
|
232
|
+
ensure_open!
|
|
233
|
+
return write_string(data, selection:) if HDF5::StringCodec.string_data?(data)
|
|
234
|
+
|
|
235
|
+
current_shape = shape
|
|
236
|
+
raise HDF5::Error, 'Cannot write to a Null dataset' if current_shape.nil?
|
|
237
|
+
|
|
238
|
+
normalized_selection = Selection.normalize(selection, current_shape)
|
|
239
|
+
target_dtype = dtype
|
|
240
|
+
raise ConversionError, 'String datasets require string data' if target_dtype.kind == :string
|
|
241
|
+
|
|
242
|
+
values = HDF5::DataHelpers.normalize_data(data, label: 'Dataset data', dtype: target_dtype, casting:, convert: false)
|
|
243
|
+
if HDF5::DataHelpers.scalar?(data) && !normalized_selection.scalar?
|
|
244
|
+
write_scalar(values, target_dtype, normalized_selection) unless normalized_selection.size.zero?
|
|
245
|
+
return data
|
|
246
|
+
end
|
|
247
|
+
raise HDF5::Error, 'Dataset shape must match data shape' unless values.shape == normalized_selection.result_shape
|
|
248
|
+
|
|
249
|
+
return data if normalized_selection.size.zero?
|
|
116
250
|
|
|
251
|
+
write_numeric_buffer(HDF5::DataHelpers.buffer_for(values), DType.for_numo(values), normalized_selection)
|
|
117
252
|
data
|
|
118
253
|
end
|
|
119
254
|
|
|
120
255
|
def close
|
|
121
256
|
return if @dataset_id.nil?
|
|
122
257
|
|
|
123
|
-
HDF5::FFI.H5Dclose(@dataset_id)
|
|
258
|
+
@context ? @context.close(@dataset_id) : HDF5::FFI.H5Dclose(@dataset_id)
|
|
124
259
|
@dataset_id = nil
|
|
125
260
|
end
|
|
126
261
|
|
|
262
|
+
def closed?
|
|
263
|
+
@dataset_id.nil? || (@context && @context.closed?)
|
|
264
|
+
end
|
|
265
|
+
|
|
127
266
|
def dtype
|
|
267
|
+
ensure_open!
|
|
128
268
|
datatype_id = HDF5::FFI.H5Dget_type(@dataset_id)
|
|
129
269
|
raise HDF5::Error, 'Failed to get datatype' if datatype_id < 0
|
|
130
270
|
|
|
131
|
-
|
|
271
|
+
DType.for_hdf5(datatype_id)
|
|
132
272
|
ensure
|
|
133
273
|
HDF5::FFI.H5Tclose(datatype_id) if datatype_id && datatype_id >= 0
|
|
134
274
|
end
|
|
135
275
|
|
|
136
276
|
def shape
|
|
277
|
+
ensure_open!
|
|
137
278
|
dataspace_id = HDF5::FFI.H5Dget_space(@dataset_id)
|
|
138
279
|
raise HDF5::Error, 'Failed to get dataspace' if dataspace_id < 0
|
|
280
|
+
return nil if HDF5::FFI.H5Sget_simple_extent_type(dataspace_id) == :H5S_NULL
|
|
139
281
|
|
|
140
282
|
ndims = HDF5::FFI.H5Sget_simple_extent_ndims(dataspace_id)
|
|
141
283
|
raise HDF5::Error, 'Failed to get number of dimensions' if ndims < 0
|
|
@@ -148,52 +290,431 @@ module HDF5
|
|
|
148
290
|
HDF5::FFI.H5Sclose(dataspace_id) if dataspace_id && dataspace_id >= 0
|
|
149
291
|
end
|
|
150
292
|
|
|
151
|
-
def
|
|
152
|
-
|
|
293
|
+
def ndim
|
|
294
|
+
shape&.length
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def size
|
|
298
|
+
shape&.inject(1, :*) || 0
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def chunks
|
|
302
|
+
ensure_open!
|
|
303
|
+
property_list_id = HDF5::FFI.H5Dget_create_plist(@dataset_id)
|
|
304
|
+
raise HDF5::Error, 'Failed to get dataset creation properties' if property_list_id < 0
|
|
305
|
+
return nil unless HDF5::FFI.H5Pget_layout(property_list_id) == :H5D_CHUNKED
|
|
306
|
+
|
|
307
|
+
dimensions = ::FFI::MemoryPointer.new(:ulong_long, shape.length)
|
|
308
|
+
rank = HDF5::FFI.H5Pget_chunk(property_list_id, shape.length, dimensions)
|
|
309
|
+
raise HDF5::Error, 'Failed to get chunk dimensions' if rank < 0
|
|
310
|
+
|
|
311
|
+
dimensions.read_array_of_uint64(rank)
|
|
312
|
+
ensure
|
|
313
|
+
HDF5::FFI.H5Pclose(property_list_id) if property_list_id && property_list_id >= 0
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def maxshape
|
|
317
|
+
ensure_open!
|
|
318
|
+
dataspace_id = HDF5::FFI.H5Dget_space(@dataset_id)
|
|
319
|
+
raise HDF5::Error, 'Failed to get dataset dataspace' if dataspace_id < 0
|
|
320
|
+
|
|
321
|
+
rank = HDF5::FFI.H5Sget_simple_extent_ndims(dataspace_id)
|
|
322
|
+
return [] if rank.zero?
|
|
323
|
+
|
|
324
|
+
maximums = ::FFI::MemoryPointer.new(:ulong_long, rank)
|
|
325
|
+
status = HDF5::FFI.H5Sget_simple_extent_dims(dataspace_id, nil, maximums)
|
|
326
|
+
raise HDF5::Error, 'Failed to get dataset maximum shape' if status < 0
|
|
327
|
+
|
|
328
|
+
unlimited = (1 << (::FFI.type_size(:ulong_long) * 8)) - 1
|
|
329
|
+
maximums.read_array_of_uint64(rank).map { |dimension| dimension == unlimited ? nil : dimension }
|
|
330
|
+
ensure
|
|
331
|
+
HDF5::FFI.H5Sclose(dataspace_id) if dataspace_id && dataspace_id >= 0
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def fillvalue
|
|
335
|
+
ensure_open!
|
|
336
|
+
dtype_object = dtype
|
|
337
|
+
if dtype_object.kind == :string
|
|
338
|
+
raise UnsupportedFeatureError, 'fillvalue is not supported for string datasets'
|
|
339
|
+
end
|
|
340
|
+
property_list_id = HDF5::FFI.H5Dget_create_plist(@dataset_id)
|
|
341
|
+
raise HDF5::Error, 'Failed to get dataset creation properties' if property_list_id < 0
|
|
342
|
+
|
|
343
|
+
buffer = ::FFI::MemoryPointer.new(:char, dtype_object.itemsize)
|
|
344
|
+
status = HDF5::FFI.H5Pget_fill_value(property_list_id, dtype_object.memory_type_id, buffer)
|
|
345
|
+
raise HDF5::Error, 'Failed to get dataset fill value' if status < 0
|
|
346
|
+
|
|
347
|
+
value = HDF5::DataHelpers.from_binary(dtype_object, buffer.read_bytes(dtype_object.itemsize), []).extract
|
|
348
|
+
dtype_object.kind == :bool ? !value.zero? : value
|
|
349
|
+
ensure
|
|
350
|
+
HDF5::FFI.H5Pclose(property_list_id) if property_list_id && property_list_id >= 0
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def resize(new_shape)
|
|
354
|
+
ensure_open!
|
|
355
|
+
raise HDF5::Error, 'Cannot resize a Null dataset' if shape.nil?
|
|
356
|
+
unless new_shape.is_a?(Array) && new_shape.length == shape.length
|
|
357
|
+
raise HDF5::Error,
|
|
358
|
+
'Dataset shape must be an Array matching dataset rank'
|
|
359
|
+
end
|
|
360
|
+
unless new_shape.all? { |dimension| dimension.is_a?(Integer) && dimension >= 0 }
|
|
361
|
+
raise HDF5::Error, 'Dataset dimensions must be non-negative integers'
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
maxshape.zip(new_shape).each do |maximum, dimension|
|
|
365
|
+
raise HDF5::Error, 'Dataset shape exceeds maxshape' if maximum && dimension > maximum
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
dimensions = ::FFI::MemoryPointer.new(:ulong_long, new_shape.length)
|
|
369
|
+
dimensions.write_array_of_ulong_long(new_shape)
|
|
370
|
+
status = HDF5::FFI.H5Dset_extent(@dataset_id, dimensions)
|
|
371
|
+
raise HDF5::Error, 'Failed to resize dataset' if status < 0
|
|
372
|
+
|
|
373
|
+
self
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def append(data, axis: 0)
|
|
377
|
+
ensure_open!
|
|
378
|
+
values = HDF5::DataHelpers.normalize_data(data, label: 'Dataset data', dtype: dtype, convert: false)
|
|
153
379
|
current_shape = shape
|
|
380
|
+
raise HDF5::Error, 'Cannot append to a Null dataset' if current_shape.nil?
|
|
381
|
+
raise HDF5::Error, 'Cannot append to a scalar dataset' if current_shape.empty?
|
|
382
|
+
raise IndexError, "Invalid append axis: #{axis}" unless axis.is_a?(Integer) && axis.between?(0,
|
|
383
|
+
current_shape.length - 1)
|
|
384
|
+
raise HDF5::Error, 'Appended data rank must match dataset rank' unless values.shape.length == current_shape.length
|
|
385
|
+
raise HDF5::Error, 'Appended data shape must match all non-appended dimensions' unless
|
|
386
|
+
values.shape.each_with_index.all? { |dimension, index| index == axis || dimension == current_shape[index] }
|
|
387
|
+
|
|
388
|
+
source_dtype = DType.for_numo(values)
|
|
389
|
+
target_dtype = dtype
|
|
390
|
+
raise ConversionError, "Cannot safely cast #{source_dtype.to_sym} to #{target_dtype.to_sym}" unless
|
|
391
|
+
source_dtype.castable_to?(target_dtype)
|
|
392
|
+
return self if values.shape[axis].zero?
|
|
393
|
+
|
|
394
|
+
new_shape = current_shape.dup
|
|
395
|
+
new_shape[axis] += values.shape[axis]
|
|
396
|
+
resize(new_shape)
|
|
397
|
+
selection = current_shape.each_with_index.map do |dimension, index|
|
|
398
|
+
index == axis ? dimension...new_shape[index] : 0...dimension
|
|
399
|
+
end
|
|
400
|
+
write(values, selection: selection)
|
|
401
|
+
self
|
|
402
|
+
rescue StandardError => e
|
|
403
|
+
raise unless current_shape && new_shape && shape == new_shape
|
|
404
|
+
|
|
405
|
+
begin
|
|
406
|
+
resize(current_shape)
|
|
407
|
+
rescue StandardError => rollback_error
|
|
408
|
+
raise HDF5::Error,
|
|
409
|
+
"Append failed (#{e.message}) and extent rollback failed (#{rollback_error.message}); current shape: #{shape.inspect}"
|
|
410
|
+
end
|
|
411
|
+
raise e
|
|
412
|
+
end
|
|
154
413
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
414
|
+
def read(selection: nil, dtype: nil, casting: :safe)
|
|
415
|
+
ensure_open!
|
|
416
|
+
type_id = HDF5::FFI.H5Dget_type(@dataset_id)
|
|
417
|
+
raise HDF5::Error, 'Failed to get dataset datatype' if type_id < 0
|
|
418
|
+
current_shape = shape
|
|
419
|
+
if current_shape.nil?
|
|
420
|
+
raise HDF5::Error, 'Null datasets cannot be sliced' unless selection.nil?
|
|
421
|
+
|
|
422
|
+
current_dtype = dtype ? DType.for_symbol(dtype) : DType.for_hdf5(type_id)
|
|
423
|
+
unless DType.for_hdf5(type_id).castable_to?(current_dtype, casting:)
|
|
424
|
+
raise ConversionError, 'Cannot safely cast Null dataset dtype'
|
|
425
|
+
end
|
|
426
|
+
return HDF5::Empty.new(current_dtype)
|
|
165
427
|
end
|
|
428
|
+
if dtype && HDF5::FFI.H5Tget_class(type_id) == :H5T_STRING
|
|
429
|
+
raise ConversionError,
|
|
430
|
+
'dtype is not supported for string datasets'
|
|
431
|
+
end
|
|
432
|
+
return read_string(type_id, selection:) if HDF5::FFI.H5Tget_class(type_id) == :H5T_STRING
|
|
433
|
+
|
|
434
|
+
source_dtype = DType.for_hdf5(type_id)
|
|
435
|
+
current_dtype = dtype ? DType.for_symbol(dtype) : source_dtype
|
|
436
|
+
raise ConversionError, "Cannot safely cast #{source_dtype.to_sym} to #{current_dtype.to_sym}" unless
|
|
437
|
+
source_dtype.castable_to?(current_dtype, casting:)
|
|
438
|
+
|
|
439
|
+
normalized_selection = Selection.normalize(selection, current_shape)
|
|
440
|
+
return current_dtype.numo_class.zeros(*normalized_selection.result_shape) if normalized_selection.size.zero?
|
|
441
|
+
if current_dtype.kind == :complex && source_dtype.kind != :complex
|
|
442
|
+
raise ConversionError, 'Reading non-complex data as complex requires an explicit Numo cast'
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
file_space_id = HDF5::FFI.H5Dget_space(@dataset_id)
|
|
446
|
+
raise HDF5::Error, 'Failed to get dataset dataspace' if file_space_id < 0
|
|
447
|
+
|
|
448
|
+
select_hyperslab(file_space_id, normalized_selection)
|
|
449
|
+
memory_space_id = create_memory_dataspace(normalized_selection.result_shape)
|
|
450
|
+
raise HDF5::Error, 'Failed to create memory dataspace' if memory_space_id < 0
|
|
451
|
+
raise HDF5::Error, 'File and memory selections have different sizes' unless
|
|
452
|
+
HDF5::FFI.H5Sget_select_npoints(file_space_id) == HDF5::FFI.H5Sget_select_npoints(memory_space_id)
|
|
453
|
+
|
|
454
|
+
bytesize = normalized_selection.size * current_dtype.itemsize
|
|
455
|
+
buffer = ::FFI::MemoryPointer.new(:char, bytesize)
|
|
456
|
+
status = HDF5::FFI.H5Dread(@dataset_id, current_dtype.memory_type_id, memory_space_id, file_space_id,
|
|
457
|
+
HDF5::DEFAULT_PROPERTY_LIST, buffer)
|
|
458
|
+
raise HDF5::Error, 'Failed to read dataset' if status < 0
|
|
459
|
+
|
|
460
|
+
result = HDF5::DataHelpers.from_binary(current_dtype, buffer.read_bytes(bytesize),
|
|
461
|
+
normalized_selection.result_shape)
|
|
462
|
+
return result unless normalized_selection.scalar?
|
|
463
|
+
|
|
464
|
+
scalar = result.extract
|
|
465
|
+
current_dtype.kind == :bool ? !scalar.zero? : scalar
|
|
466
|
+
ensure
|
|
467
|
+
HDF5::FFI.H5Tclose(type_id) if type_id && type_id >= 0
|
|
468
|
+
HDF5::FFI.H5Sclose(memory_space_id) if memory_space_id && memory_space_id >= 0
|
|
469
|
+
HDF5::FFI.H5Sclose(file_space_id) if file_space_id && file_space_id >= 0
|
|
166
470
|
end
|
|
167
471
|
|
|
168
|
-
def
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
HDF5::DEFAULT_PROPERTY_LIST, HDF5::DEFAULT_PROPERTY_LIST, buffer)
|
|
172
|
-
raise HDF5::Error, 'Failed to read integer dataset' if status < 0
|
|
472
|
+
def read_array(selection: nil, flatten: false, dtype: nil, casting: :safe)
|
|
473
|
+
value = read(selection:, dtype:, casting:)
|
|
474
|
+
return value unless value.is_a?(Numo::NArray)
|
|
173
475
|
|
|
174
|
-
|
|
476
|
+
array = value.to_a
|
|
477
|
+
flatten ? array.flatten : array
|
|
175
478
|
end
|
|
176
479
|
|
|
177
|
-
def
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
HDF5::DEFAULT_PROPERTY_LIST, HDF5::DEFAULT_PROPERTY_LIST, buffer)
|
|
181
|
-
raise HDF5::Error, 'Failed to read float dataset' if status < 0
|
|
480
|
+
def [](*selection)
|
|
481
|
+
read(selection: selection)
|
|
482
|
+
end
|
|
182
483
|
|
|
183
|
-
|
|
484
|
+
def []=(*selection, value)
|
|
485
|
+
write(value, selection: selection)
|
|
184
486
|
end
|
|
185
487
|
|
|
186
|
-
def
|
|
187
|
-
|
|
488
|
+
def read_into(destination, selection: nil, casting: :safe)
|
|
489
|
+
ensure_open!
|
|
490
|
+
raise HDF5::Error, 'read_into destination must be a Numo::NArray' unless destination.is_a?(Numo::NArray)
|
|
491
|
+
|
|
492
|
+
current_shape = shape
|
|
493
|
+
raise HDF5::Error, 'Cannot read a Null dataset into an array' if current_shape.nil?
|
|
494
|
+
|
|
495
|
+
expected_shape = Selection.normalize(selection, current_shape).result_shape
|
|
496
|
+
unless destination.shape == expected_shape
|
|
497
|
+
raise HDF5::Error,
|
|
498
|
+
'read_into destination shape must match selection shape'
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
values = read(selection:, dtype: DType.for_numo(destination).to_sym, casting:)
|
|
502
|
+
values = values ? 1 : 0 if expected_shape.empty? && destination.is_a?(Numo::Bit)
|
|
503
|
+
destination.store(values)
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
def each_block(max_bytes:)
|
|
507
|
+
return enum_for(__method__, max_bytes:) unless block_given?
|
|
508
|
+
|
|
509
|
+
ensure_open!
|
|
510
|
+
raise ArgumentError, 'max_bytes must be a positive integer' unless max_bytes.is_a?(Integer) && max_bytes.positive?
|
|
511
|
+
|
|
512
|
+
current_shape, current_dtype = HDF5::FFI::CALL_LOCK.synchronize { [shape, dtype] }
|
|
513
|
+
raise HDF5::Error, 'Cannot iterate over a Null dataset' if current_shape.nil?
|
|
514
|
+
if current_dtype.kind == :string
|
|
515
|
+
raise UnsupportedFeatureError, 'each_block cannot bound the byte size of variable-length strings'
|
|
516
|
+
end
|
|
517
|
+
raise ArgumentError, 'max_bytes is smaller than one dataset element' if max_bytes < current_dtype.itemsize
|
|
518
|
+
|
|
519
|
+
if current_shape.empty?
|
|
520
|
+
yield [], read
|
|
521
|
+
return
|
|
522
|
+
end
|
|
523
|
+
return if current_shape.any?(&:zero?)
|
|
524
|
+
|
|
525
|
+
block_shape = block_shape_for(current_shape, max_bytes / current_dtype.itemsize)
|
|
526
|
+
each_block_selection(current_shape, block_shape) do |selection|
|
|
527
|
+
yield selection, read(selection: selection)
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
def each_chunk
|
|
532
|
+
return enum_for(__method__) unless block_given?
|
|
533
|
+
|
|
534
|
+
ensure_open!
|
|
535
|
+
|
|
536
|
+
current_shape, chunk_shape = HDF5::FFI::CALL_LOCK.synchronize { [shape, chunks] }
|
|
537
|
+
raise HDF5::Error, 'each_chunk requires a chunked dataset' unless chunk_shape
|
|
538
|
+
|
|
539
|
+
return if current_shape.any?(&:zero?)
|
|
540
|
+
|
|
541
|
+
each_block_selection(current_shape, chunk_shape) do |selection|
|
|
542
|
+
yield selection, read(selection: selection)
|
|
543
|
+
end
|
|
188
544
|
end
|
|
189
545
|
|
|
190
546
|
private
|
|
191
547
|
|
|
192
|
-
def
|
|
548
|
+
def write_numeric_buffer(buffer, dtype_object, normalized_selection)
|
|
549
|
+
file_space_id = HDF5::FFI.H5Dget_space(@dataset_id)
|
|
550
|
+
raise HDF5::Error, 'Failed to get dataset dataspace' if file_space_id < 0
|
|
551
|
+
|
|
552
|
+
select_hyperslab(file_space_id, normalized_selection)
|
|
553
|
+
memory_space_id = create_memory_dataspace(normalized_selection.result_shape)
|
|
554
|
+
raise HDF5::Error, 'Failed to create memory dataspace' if memory_space_id < 0
|
|
555
|
+
raise HDF5::Error, 'File and memory selections have different sizes' unless
|
|
556
|
+
HDF5::FFI.H5Sget_select_npoints(file_space_id) == HDF5::FFI.H5Sget_select_npoints(memory_space_id)
|
|
557
|
+
|
|
558
|
+
status = HDF5::FFI.H5Dwrite(@dataset_id, dtype_object.memory_type_id, memory_space_id, file_space_id,
|
|
559
|
+
HDF5::DEFAULT_PROPERTY_LIST, buffer)
|
|
560
|
+
raise HDF5::Error, 'Failed to write dataset' if status < 0
|
|
561
|
+
ensure
|
|
562
|
+
HDF5::FFI.H5Sclose(memory_space_id) if memory_space_id && memory_space_id >= 0
|
|
563
|
+
HDF5::FFI.H5Sclose(file_space_id) if file_space_id && file_space_id >= 0
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def write_scalar(value, dtype_object, selection)
|
|
567
|
+
max_elements = [256 * 1024 / dtype_object.itemsize, selection.size].min
|
|
568
|
+
constant = dtype_object.numo_class.new(max_elements).fill(value.extract)
|
|
569
|
+
buffer = HDF5::DataHelpers.buffer_for(constant)
|
|
570
|
+
block_shape = block_shape_for(selection.result_shape, max_elements)
|
|
571
|
+
each_block_selection(selection.result_shape, block_shape) do |ranges|
|
|
572
|
+
write_numeric_buffer(buffer, dtype_object, selection.block(ranges))
|
|
573
|
+
end
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
def write_string(data, selection:)
|
|
577
|
+
current_shape = shape
|
|
578
|
+
raise HDF5::Error, 'Cannot write to a Null dataset' if current_shape.nil?
|
|
579
|
+
|
|
580
|
+
normalized_selection = Selection.normalize(selection, current_shape)
|
|
581
|
+
type_id = HDF5::FFI.H5Dget_type(@dataset_id)
|
|
582
|
+
raise HDF5::Error, 'Failed to get dataset datatype' if type_id < 0
|
|
583
|
+
unless HDF5::FFI.H5Tget_class(type_id) == :H5T_STRING
|
|
584
|
+
raise ConversionError, 'Cannot write strings to a numeric dataset'
|
|
585
|
+
end
|
|
586
|
+
encoding = HDF5::StringCodec.encoding_for(type_id)
|
|
587
|
+
values, values_shape = HDF5::StringCodec.normalize_data(data, encoding:)
|
|
588
|
+
unless values_shape == normalized_selection.result_shape
|
|
589
|
+
raise HDF5::ShapeError,
|
|
590
|
+
'Dataset shape must match string data shape'
|
|
591
|
+
end
|
|
592
|
+
return data if normalized_selection.size.zero?
|
|
593
|
+
|
|
594
|
+
unless HDF5::StringCodec.variable?(type_id)
|
|
595
|
+
raise UnsupportedTypeError, 'Fixed-length string datasets are not yet supported'
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
file_space_id = HDF5::FFI.H5Dget_space(@dataset_id)
|
|
599
|
+
raise HDF5::Error, 'Failed to get string dataset dataspace' if file_space_id < 0
|
|
600
|
+
|
|
601
|
+
select_hyperslab(file_space_id, normalized_selection)
|
|
602
|
+
memory_space_id = create_memory_dataspace(normalized_selection.result_shape)
|
|
603
|
+
raise HDF5::Error, 'Failed to create string memory dataspace' if memory_space_id < 0
|
|
604
|
+
|
|
605
|
+
buffer, _string_pointers = HDF5::StringCodec.buffer_for_values(values, encoding:)
|
|
606
|
+
status = HDF5::FFI.H5Dwrite(@dataset_id, type_id, memory_space_id, file_space_id,
|
|
607
|
+
HDF5::DEFAULT_PROPERTY_LIST, buffer)
|
|
608
|
+
raise HDF5::Error, 'Failed to write string dataset' if status < 0
|
|
609
|
+
|
|
610
|
+
data
|
|
611
|
+
ensure
|
|
612
|
+
HDF5::FFI.H5Tclose(type_id) if type_id && type_id >= 0
|
|
613
|
+
HDF5::FFI.H5Sclose(memory_space_id) if memory_space_id && memory_space_id >= 0
|
|
614
|
+
HDF5::FFI.H5Sclose(file_space_id) if file_space_id && file_space_id >= 0
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
def read_string(type_id, selection:)
|
|
618
|
+
normalized_selection = Selection.normalize(selection, shape)
|
|
619
|
+
unless HDF5::StringCodec.variable?(type_id)
|
|
620
|
+
raise UnsupportedTypeError, 'Fixed-length string datasets are not yet supported'
|
|
621
|
+
end
|
|
622
|
+
return Numo::RObject.new(*normalized_selection.result_shape) if normalized_selection.size.zero?
|
|
623
|
+
|
|
624
|
+
file_space_id = HDF5::FFI.H5Dget_space(@dataset_id)
|
|
625
|
+
raise HDF5::Error, 'Failed to get string dataset dataspace' if file_space_id < 0
|
|
626
|
+
|
|
627
|
+
select_hyperslab(file_space_id, normalized_selection)
|
|
628
|
+
memory_space_id = create_memory_dataspace(normalized_selection.result_shape)
|
|
629
|
+
raise HDF5::Error, 'Failed to create string memory dataspace' if memory_space_id < 0
|
|
630
|
+
|
|
631
|
+
buffer = ::FFI::MemoryPointer.new(:pointer, normalized_selection.size)
|
|
632
|
+
status = HDF5::FFI.H5Dread(@dataset_id, type_id, memory_space_id, file_space_id,
|
|
633
|
+
HDF5::DEFAULT_PROPERTY_LIST, buffer)
|
|
634
|
+
raise HDF5::Error, 'Failed to read string dataset' if status < 0
|
|
635
|
+
|
|
636
|
+
HDF5::StringCodec.read_values(buffer, normalized_selection.size, normalized_selection.result_shape,
|
|
637
|
+
encoding: HDF5::StringCodec.encoding_for(type_id))
|
|
638
|
+
ensure
|
|
639
|
+
begin
|
|
640
|
+
if buffer && type_id && memory_space_id
|
|
641
|
+
active_error = $ERROR_INFO
|
|
642
|
+
reclaim_status = HDF5::FFI.H5Dvlen_reclaim(type_id, memory_space_id, HDF5::DEFAULT_PROPERTY_LIST, buffer)
|
|
643
|
+
if reclaim_status.negative? && active_error.nil?
|
|
644
|
+
raise HDF5::Error,
|
|
645
|
+
'Failed to reclaim variable-length string data'
|
|
646
|
+
end
|
|
647
|
+
end
|
|
648
|
+
ensure
|
|
649
|
+
begin
|
|
650
|
+
HDF5::FFI.H5Sclose(memory_space_id) if memory_space_id && memory_space_id >= 0
|
|
651
|
+
ensure
|
|
652
|
+
HDF5::FFI.H5Sclose(file_space_id) if file_space_id && file_space_id >= 0
|
|
653
|
+
end
|
|
654
|
+
end
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
def block_shape_for(dataset_shape, max_elements)
|
|
658
|
+
remaining = max_elements
|
|
659
|
+
dataset_shape.reverse.map do |dimension|
|
|
660
|
+
block_dimension = [dimension, remaining].min
|
|
661
|
+
remaining /= block_dimension
|
|
662
|
+
block_dimension
|
|
663
|
+
end.reverse
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
def each_block_selection(dataset_shape, block_shape, axis = 0, prefix = [], &block)
|
|
667
|
+
if axis == dataset_shape.length
|
|
668
|
+
yield prefix
|
|
669
|
+
return
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
0.step(dataset_shape[axis] - 1, block_shape[axis]) do |start|
|
|
673
|
+
length = [block_shape[axis], dataset_shape[axis] - start].min
|
|
674
|
+
each_block_selection(dataset_shape, block_shape, axis + 1, prefix + [start...(start + length)], &block)
|
|
675
|
+
end
|
|
676
|
+
end
|
|
677
|
+
|
|
678
|
+
def create_memory_dataspace(shape)
|
|
679
|
+
return HDF5::FFI.H5Screate(:H5S_SCALAR) if shape.empty?
|
|
680
|
+
|
|
681
|
+
dims = ::FFI::MemoryPointer.new(:ulong_long, shape.length)
|
|
682
|
+
dims.write_array_of_ulong_long(shape)
|
|
683
|
+
HDF5::FFI.H5Screate_simple(shape.length, dims, nil)
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
def select_hyperslab(dataspace_id, selection)
|
|
687
|
+
rank = selection.start.length
|
|
688
|
+
return if rank.zero?
|
|
689
|
+
|
|
690
|
+
start = ::FFI::MemoryPointer.new(:ulong_long, rank)
|
|
691
|
+
stride = ::FFI::MemoryPointer.new(:ulong_long, rank)
|
|
692
|
+
count = ::FFI::MemoryPointer.new(:ulong_long, rank)
|
|
693
|
+
start.write_array_of_ulong_long(selection.start)
|
|
694
|
+
stride.write_array_of_ulong_long(selection.stride)
|
|
695
|
+
count.write_array_of_ulong_long(selection.count)
|
|
696
|
+
status = HDF5::FFI.H5Sselect_hyperslab(dataspace_id, :H5S_SELECT_SET, start, stride, count, nil)
|
|
697
|
+
raise HDF5::Error, 'Failed to select dataset region' if status < 0
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
def initialize_from_id(dataset_id, name, context)
|
|
193
701
|
raise HDF5::Error, "Failed to open dataset: #{name}" if dataset_id < 0
|
|
194
702
|
|
|
195
703
|
@dataset_id = dataset_id
|
|
196
704
|
@name = name
|
|
705
|
+
@context = context
|
|
706
|
+
@context.register(dataset_id, :dataset) if @context
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
def ensure_open!
|
|
710
|
+
raise ClosedError, 'HDF5 dataset is closed' if @dataset_id.nil?
|
|
711
|
+
|
|
712
|
+
@context&.ensure_open!(@dataset_id)
|
|
197
713
|
end
|
|
714
|
+
|
|
715
|
+
prepend FileContext.guard(
|
|
716
|
+
:attrs, :write, :dtype, :shape, :chunks, :maxshape, :fillvalue, :resize, :append, :read, :read_array,
|
|
717
|
+
:[], :[]=, :read_into, :close, :closed?
|
|
718
|
+
)
|
|
198
719
|
end
|
|
199
720
|
end
|