ruby-hdf5 0.0.3 → 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/lib/hdf5/attribute.rb +25 -10
- data/lib/hdf5/data_helpers.rb +153 -17
- data/lib/hdf5/dataset.rb +125 -64
- data/lib/hdf5/dtype.rb +22 -8
- data/lib/hdf5/ffi.rb +3 -3
- data/lib/hdf5/ffi_10.rb +0 -485
- data/lib/hdf5/ffi_14.rb +0 -479
- data/lib/hdf5/ffi_20.rb +0 -516
- data/lib/hdf5/file.rb +23 -5
- data/lib/hdf5/group.rb +23 -5
- data/lib/hdf5/hierarchy.rb +1 -1
- data/lib/hdf5/selection.rb +17 -1
- data/lib/hdf5/string_codec.rb +31 -18
- data/lib/hdf5/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 85ca4ff79d4a34553188284efb980628cd4f4931aa29d12bd37b09e0a89d3ca8
|
|
4
|
+
data.tar.gz: 1126e3458c88e00b00a6919ed832c35196673a5d785da524a218a9ff0772bd03
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 15f984a33a2c9036364e4bd4cef11acd041a1684f531cd30f05c235e764df5ff5a69cea2e7e727f0c34fd1438272673ed3764661bf1f0e19b0322f0879b4fea0
|
|
7
|
+
data.tar.gz: 9ef886f63b1c207e6c58e9b5a00462207d74239e522cb2aaeb9344237e93e14e6ad29326d3e79d7e9a1ea05b5a3d0215b0f5f5a250c412e9459a9810835f2aeb
|
data/lib/hdf5/attribute.rb
CHANGED
|
@@ -14,6 +14,8 @@ module HDF5
|
|
|
14
14
|
|
|
15
15
|
space_id = HDF5::FFI.H5Aget_space(@attr_id)
|
|
16
16
|
raise HDF5::Error, 'Failed to get attribute dataspace' if space_id < 0
|
|
17
|
+
return HDF5::Empty.new(DType.for_hdf5(type_id)) if HDF5::FFI.H5Sget_simple_extent_type(space_id) == :H5S_NULL
|
|
18
|
+
|
|
17
19
|
return read_string(type_id, space_id) if HDF5::FFI.H5Tget_class(type_id) == :H5T_STRING
|
|
18
20
|
|
|
19
21
|
dtype_object = DType.for_hdf5(type_id)
|
|
@@ -66,7 +68,7 @@ module HDF5
|
|
|
66
68
|
status = HDF5::FFI.H5Aread(@attr_id, type_id, buffer)
|
|
67
69
|
raise HDF5::Error, 'Failed to read string attribute' if status < 0
|
|
68
70
|
|
|
69
|
-
HDF5::StringCodec.read_values(buffer, count, attribute_shape)
|
|
71
|
+
HDF5::StringCodec.read_values(buffer, count, attribute_shape, encoding: HDF5::StringCodec.encoding_for(type_id))
|
|
70
72
|
ensure
|
|
71
73
|
if buffer
|
|
72
74
|
active_error = $ERROR_INFO
|
|
@@ -134,7 +136,7 @@ module HDF5
|
|
|
134
136
|
write(attr_name, value)
|
|
135
137
|
end
|
|
136
138
|
|
|
137
|
-
def modify(attr_name, value)
|
|
139
|
+
def modify(attr_name, value, casting: :safe)
|
|
138
140
|
@context&.ensure_open!(@dataset_id)
|
|
139
141
|
raise HDF5::Error, "Attribute not found: #{attr_name}" unless key?(attr_name)
|
|
140
142
|
|
|
@@ -144,28 +146,34 @@ module HDF5
|
|
|
144
146
|
type_id = HDF5::FFI.H5Aget_type(attr_id)
|
|
145
147
|
space_id = HDF5::FFI.H5Aget_space(attr_id)
|
|
146
148
|
raise HDF5::Error, "Failed to inspect attribute: #{attr_name}" if type_id < 0 || space_id < 0
|
|
149
|
+
if HDF5::FFI.H5Sget_simple_extent_type(space_id) == :H5S_NULL
|
|
150
|
+
unless value.is_a?(HDF5::Empty) && value.dtype.to_sym == DType.for_hdf5(type_id).to_sym
|
|
151
|
+
raise HDF5::ShapeError, 'Cannot assign a value to a Null attribute'
|
|
152
|
+
end
|
|
153
|
+
return value
|
|
154
|
+
end
|
|
147
155
|
|
|
148
156
|
if HDF5::FFI.H5Tget_class(type_id) == :H5T_STRING
|
|
149
157
|
unless HDF5::StringCodec.variable?(type_id)
|
|
150
158
|
raise UnsupportedTypeError, 'Fixed-length string attributes are not yet supported'
|
|
151
159
|
end
|
|
152
160
|
|
|
153
|
-
|
|
161
|
+
encoding = HDF5::StringCodec.encoding_for(type_id)
|
|
162
|
+
string_values, string_shape = HDF5::StringCodec.normalize_data(value, encoding:)
|
|
154
163
|
unless string_shape == attribute_shape(space_id)
|
|
155
164
|
raise HDF5::ShapeError,
|
|
156
165
|
'Attribute shape must not change when modifying'
|
|
157
166
|
end
|
|
158
167
|
|
|
159
|
-
buffer, _string_pointers = HDF5::StringCodec.buffer_for_values(string_values)
|
|
168
|
+
buffer, _string_pointers = HDF5::StringCodec.buffer_for_values(string_values, encoding:)
|
|
160
169
|
status = HDF5::FFI.H5Awrite(attr_id, type_id, buffer)
|
|
161
170
|
else
|
|
162
171
|
dtype_object = DType.for_hdf5(type_id)
|
|
163
|
-
values = HDF5::DataHelpers.normalize_data(value, label: 'Attribute data')
|
|
172
|
+
values = HDF5::DataHelpers.normalize_data(value, label: 'Attribute data', dtype: dtype_object, casting:, convert: false)
|
|
164
173
|
expected_shape = attribute_shape(space_id)
|
|
165
174
|
raise HDF5::Error, 'Attribute shape must not change when modifying' unless values.shape == expected_shape
|
|
166
175
|
|
|
167
|
-
|
|
168
|
-
status = HDF5::FFI.H5Awrite(attr_id, dtype_object.memory_type_id, HDF5::DataHelpers.buffer_for(converted))
|
|
176
|
+
status = HDF5::FFI.H5Awrite(attr_id, DType.for_numo(values).memory_type_id, HDF5::DataHelpers.buffer_for(values))
|
|
169
177
|
end
|
|
170
178
|
raise HDF5::Error, "Failed to modify attribute: #{attr_name}" if status < 0
|
|
171
179
|
|
|
@@ -178,10 +186,14 @@ module HDF5
|
|
|
178
186
|
|
|
179
187
|
def write(attr_name, value)
|
|
180
188
|
@context&.ensure_open!(@dataset_id)
|
|
189
|
+
empty_data = value.is_a?(HDF5::Empty)
|
|
190
|
+
if empty_data && value.dtype.kind == :string
|
|
191
|
+
raise UnsupportedFeatureError, 'Creating Null string attributes is not yet supported'
|
|
192
|
+
end
|
|
181
193
|
string_data = HDF5::StringCodec.string_data?(value)
|
|
182
194
|
string_values, string_shape = HDF5::StringCodec.normalize_data(value) if string_data
|
|
183
|
-
values = HDF5::DataHelpers.normalize_data(value, label: 'Attribute data') unless string_data
|
|
184
|
-
dtype_object = DType.for_numo(values) unless string_data
|
|
195
|
+
values = HDF5::DataHelpers.normalize_data(value, label: 'Attribute data') unless string_data || empty_data
|
|
196
|
+
dtype_object = empty_data ? value.dtype : DType.for_numo(values) unless string_data
|
|
185
197
|
type_id = string_data ? HDF5::StringCodec.datatype_id : dtype_object.storage_type_id
|
|
186
198
|
|
|
187
199
|
exists = HDF5::FFI.H5Aexists(@dataset_id, attr_name)
|
|
@@ -192,7 +204,7 @@ module HDF5
|
|
|
192
204
|
raise HDF5::Error, "Failed to replace attribute: #{attr_name}" if status < 0
|
|
193
205
|
end
|
|
194
206
|
|
|
195
|
-
dataspace_id = create_dataspace(string_data ? string_shape : values.shape)
|
|
207
|
+
dataspace_id = create_dataspace(empty_data ? nil : (string_data ? string_shape : values.shape))
|
|
196
208
|
raise HDF5::Error, 'Failed to create attribute dataspace' if dataspace_id < 0
|
|
197
209
|
|
|
198
210
|
attr_id = HDF5::FFI.H5Acreate2(
|
|
@@ -204,6 +216,7 @@ module HDF5
|
|
|
204
216
|
HDF5::DEFAULT_PROPERTY_LIST
|
|
205
217
|
)
|
|
206
218
|
raise HDF5::Error, "Failed to create attribute: #{attr_name}" if attr_id < 0
|
|
219
|
+
return value if empty_data
|
|
207
220
|
|
|
208
221
|
buffer, _string_pointers = if string_data
|
|
209
222
|
HDF5::StringCodec.buffer_for_values(string_values)
|
|
@@ -236,6 +249,8 @@ module HDF5
|
|
|
236
249
|
end
|
|
237
250
|
|
|
238
251
|
def create_dataspace(shape)
|
|
252
|
+
return HDF5::FFI.H5Screate(:H5S_NULL) if shape.nil?
|
|
253
|
+
|
|
239
254
|
return HDF5::FFI.H5Screate(:H5S_SCALAR) if shape.empty?
|
|
240
255
|
|
|
241
256
|
dimensions = ::FFI::MemoryPointer.new(:ulong_long, shape.length)
|
data/lib/hdf5/data_helpers.rb
CHANGED
|
@@ -2,27 +2,163 @@ module HDF5
|
|
|
2
2
|
module DataHelpers
|
|
3
3
|
module_function
|
|
4
4
|
|
|
5
|
-
def normalize_data(data, label: 'Data')
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
5
|
+
def normalize_data(data, label: 'Data', dtype: nil, casting: :safe, convert: true)
|
|
6
|
+
raise ArgumentError, "Unsupported casting mode: #{casting.inspect}" unless %i[safe unsafe].include?(casting)
|
|
7
|
+
|
|
8
|
+
if data.is_a?(Numo::NArray)
|
|
9
|
+
source = DType.for_numo(data)
|
|
10
|
+
return data unless dtype
|
|
11
|
+
unless source.castable_to?(dtype, casting:)
|
|
12
|
+
raise ConversionError, "Cannot safely cast #{source.to_sym} to #{dtype.to_sym}"
|
|
13
|
+
end
|
|
14
|
+
return data if source.to_sym == dtype.to_sym
|
|
15
|
+
# HDF5 converts numeric widths and matching complex compounds directly,
|
|
16
|
+
# but cannot convert a real numeric datatype to a complex compound.
|
|
17
|
+
native_conversion = dtype.kind != :complex || source.kind == :complex
|
|
18
|
+
return data if !convert && casting == :safe && native_conversion
|
|
19
|
+
|
|
20
|
+
return dtype.numo_class.cast(data)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Homogeneous flat Arrays need no recursive shape traversal or per-value
|
|
24
|
+
# conversion checks: integer extrema cover the range; Ruby Float is float64.
|
|
25
|
+
if dtype.nil? && data.is_a?(Array) && !data.empty?
|
|
26
|
+
homogeneous = normalize_homogeneous_array(data, data, casting:)
|
|
27
|
+
return homogeneous if homogeneous
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
shape = array_shape(data)
|
|
31
|
+
values = data.is_a?(Array) ? (shape.length <= 1 ? data : data.flatten) : [data]
|
|
32
|
+
raise HDF5::Error, "#{label} must not be empty without an explicit dtype" if values.empty? && !dtype
|
|
33
|
+
if dtype.nil? && shape.length > 1
|
|
34
|
+
homogeneous = normalize_homogeneous_array(data, values, casting:)
|
|
35
|
+
return homogeneous if homogeneous
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
dtype ||= inferred_dtype(values, label:)
|
|
39
|
+
validate_values(values, dtype, casting:)
|
|
22
40
|
normalized = dtype.kind == :bool ? normalize_booleans(data) : data
|
|
41
|
+
return dtype.numo_class.new(*shape) if values.empty?
|
|
42
|
+
return Numo::Bit.new.store(normalized) if dtype.kind == :bool && !data.is_a?(Array)
|
|
43
|
+
|
|
23
44
|
dtype.numo_class.cast(normalized)
|
|
24
45
|
end
|
|
25
46
|
|
|
47
|
+
def normalize_homogeneous_array(data, values, casting:)
|
|
48
|
+
if values.all? { |value| value.is_a?(Integer) }
|
|
49
|
+
minimum, maximum = values.minmax
|
|
50
|
+
dtype = DType.for_symbol(minimum >= 0 && maximum >= (1 << 63) ? :uint64 : :int64)
|
|
51
|
+
validate_values([minimum, maximum], dtype, casting:)
|
|
52
|
+
dtype.numo_class.cast(data)
|
|
53
|
+
elsif values.all? { |value| value.is_a?(Float) }
|
|
54
|
+
Numo::DFloat.cast(data)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def inferred_dtype(values, label:)
|
|
59
|
+
kind = nil
|
|
60
|
+
minimum = maximum = 0
|
|
61
|
+
values.each do |value|
|
|
62
|
+
if value.is_a?(Integer)
|
|
63
|
+
current = :int64
|
|
64
|
+
minimum = value if value < minimum
|
|
65
|
+
maximum = value if value > maximum
|
|
66
|
+
elsif value.is_a?(Complex)
|
|
67
|
+
current = :complex128
|
|
68
|
+
elsif value.is_a?(Numeric)
|
|
69
|
+
current = :float64
|
|
70
|
+
elsif value.equal?(true) || value.equal?(false)
|
|
71
|
+
current = :bool
|
|
72
|
+
else
|
|
73
|
+
raise HDF5::Error, "Only numeric #{label.downcase} is supported"
|
|
74
|
+
end
|
|
75
|
+
if kind && (kind == :bool) != (current == :bool)
|
|
76
|
+
raise HDF5::Error, "Only numeric #{label.downcase} is supported"
|
|
77
|
+
end
|
|
78
|
+
kind = current if kind.nil? || current == :complex128 || current == :float64 && kind == :int64
|
|
79
|
+
end
|
|
80
|
+
kind = :uint64 if kind == :int64 && minimum >= 0 && maximum >= (1 << 63)
|
|
81
|
+
DType.for_symbol(kind)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def array_shape(value)
|
|
85
|
+
return [] unless value.is_a?(Array)
|
|
86
|
+
return [0] if value.empty?
|
|
87
|
+
|
|
88
|
+
unless value.first.is_a?(Array)
|
|
89
|
+
raise ShapeError, 'Data must be rectangular' if value.any? { |item| item.is_a?(Array) }
|
|
90
|
+
|
|
91
|
+
return [value.length]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
child_shape = array_shape(value.first)
|
|
95
|
+
index = 1
|
|
96
|
+
while index < value.length
|
|
97
|
+
item = value[index]
|
|
98
|
+
unless item.is_a?(Array) && array_shape(item) == child_shape
|
|
99
|
+
raise ShapeError, 'Data must be rectangular'
|
|
100
|
+
end
|
|
101
|
+
index += 1
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
[value.length, *child_shape]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def scalar?(value)
|
|
108
|
+
value.is_a?(Numeric) || value.equal?(true) || value.equal?(false) ||
|
|
109
|
+
value.is_a?(Numo::NArray) && value.shape.empty?
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def validate_values(values, dtype, casting:)
|
|
113
|
+
if dtype.kind == :bool
|
|
114
|
+
unless values.all? { |value| value.equal?(true) || value.equal?(false) }
|
|
115
|
+
raise ConversionError, 'Bool data must contain true or false'
|
|
116
|
+
end
|
|
117
|
+
return
|
|
118
|
+
end
|
|
119
|
+
if casting == :unsafe
|
|
120
|
+
raise ConversionError, 'Data must contain numeric values' unless values.all? { |value| value.is_a?(Numeric) }
|
|
121
|
+
return
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
case dtype.kind
|
|
125
|
+
when :integer
|
|
126
|
+
bits = dtype.itemsize * 8
|
|
127
|
+
minimum = dtype.unsigned? ? 0 : -(1 << (bits - 1))
|
|
128
|
+
maximum = dtype.unsigned? ? (1 << bits) - 1 : (1 << (bits - 1)) - 1
|
|
129
|
+
values.each do |value|
|
|
130
|
+
unless value.is_a?(Integer) && value.between?(minimum, maximum)
|
|
131
|
+
raise ConversionError, "Value #{value.inspect} cannot safely be represented as #{dtype.to_sym}"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
when :float, :complex
|
|
135
|
+
size = dtype.kind == :complex ? dtype.itemsize / 2 : dtype.itemsize
|
|
136
|
+
values.each do |value|
|
|
137
|
+
raise ConversionError, 'Data must contain numeric values' unless value.is_a?(Numeric)
|
|
138
|
+
if dtype.kind == :complex
|
|
139
|
+
validate_component(value.real, size, dtype)
|
|
140
|
+
validate_component(value.imag, size, dtype)
|
|
141
|
+
else
|
|
142
|
+
validate_component(value, size, dtype)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def validate_component(component, size, dtype)
|
|
149
|
+
return if size == 8 && component.is_a?(Float)
|
|
150
|
+
|
|
151
|
+
raise ConversionError, "Cannot safely cast complex data to #{dtype.to_sym}" if component.is_a?(Complex)
|
|
152
|
+
|
|
153
|
+
converted = component.to_f
|
|
154
|
+
converted = [converted].pack('f').unpack1('f') if size == 4
|
|
155
|
+
return if component.is_a?(Float) && !component.finite? && !converted.finite?
|
|
156
|
+
return if component.is_a?(Float) && component == converted
|
|
157
|
+
return if converted.finite? && converted.to_r == component.to_r
|
|
158
|
+
|
|
159
|
+
raise ConversionError, "Value #{component.inspect} cannot safely be represented as #{dtype.to_sym}"
|
|
160
|
+
end
|
|
161
|
+
|
|
26
162
|
def buffer_for(narray)
|
|
27
163
|
dtype = DType.for_numo(narray)
|
|
28
164
|
binary = if dtype.kind == :bool
|
data/lib/hdf5/dataset.rb
CHANGED
|
@@ -2,13 +2,22 @@ module HDF5
|
|
|
2
2
|
class Dataset
|
|
3
3
|
class << self
|
|
4
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)
|
|
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
|
+
|
|
6
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
|
|
14
|
+
end
|
|
7
15
|
string_data = HDF5::StringCodec.string_data?(data)
|
|
8
16
|
_string_values, string_shape = HDF5::StringCodec.normalize_data(data) if string_data
|
|
9
17
|
unless data.nil? || string_data || empty_data
|
|
10
18
|
narray = HDF5::DataHelpers.normalize_data(data,
|
|
11
|
-
label: 'Dataset data')
|
|
19
|
+
label: 'Dataset data', dtype: dtype && DType.for_symbol(dtype), casting:,
|
|
20
|
+
convert: false)
|
|
12
21
|
end
|
|
13
22
|
unless string_data
|
|
14
23
|
dtype_object = if empty_data
|
|
@@ -19,7 +28,6 @@ module HDF5
|
|
|
19
28
|
end
|
|
20
29
|
type_id = string_data ? HDF5::StringCodec.datatype_id : dtype_object.storage_type_id
|
|
21
30
|
shape = string_data ? string_shape : narray.shape if shape.nil? && !data.nil? && !empty_data
|
|
22
|
-
raise HDF5::Error, 'shape: and dtype: are required when data: is omitted' if data.nil? && (!shape || !dtype)
|
|
23
31
|
raise HDF5::Error, 'Dataset shape must match data shape' if narray && shape != narray.shape
|
|
24
32
|
raise HDF5::ShapeError, 'Dataset shape must match string data shape' if string_data && shape != string_shape
|
|
25
33
|
|
|
@@ -31,7 +39,7 @@ module HDF5
|
|
|
31
39
|
raise HDF5::Error, "Failed to create dataspace for dataset: #{name}" if dataspace_id < 0
|
|
32
40
|
|
|
33
41
|
dcpl_id = create_property_list(shape, dtype_object, chunks:, compression:, compression_opts:, shuffle:, fletcher32:,
|
|
34
|
-
fillvalue:)
|
|
42
|
+
fillvalue:, casting:)
|
|
35
43
|
|
|
36
44
|
dataset = from_id(
|
|
37
45
|
HDF5::FFI.H5Dcreate2(parent_id, name, type_id, dataspace_id, HDF5::DEFAULT_PROPERTY_LIST,
|
|
@@ -39,6 +47,7 @@ module HDF5
|
|
|
39
47
|
)
|
|
40
48
|
dataset.write(data) if string_data
|
|
41
49
|
dataset.write(narray) if narray
|
|
50
|
+
initialized = true
|
|
42
51
|
return dataset unless block_given?
|
|
43
52
|
|
|
44
53
|
begin
|
|
@@ -47,7 +56,7 @@ module HDF5
|
|
|
47
56
|
dataset.close
|
|
48
57
|
end
|
|
49
58
|
rescue StandardError
|
|
50
|
-
if dataset
|
|
59
|
+
if dataset && !initialized
|
|
51
60
|
dataset.close unless dataset.closed?
|
|
52
61
|
HDF5::FFI.H5Ldelete(parent_id, name, HDF5::DEFAULT_PROPERTY_LIST)
|
|
53
62
|
end
|
|
@@ -104,7 +113,7 @@ module HDF5
|
|
|
104
113
|
end
|
|
105
114
|
|
|
106
115
|
def create_property_list(shape, dtype_object, chunks:, compression:, compression_opts:, shuffle:, fletcher32:,
|
|
107
|
-
fillvalue:)
|
|
116
|
+
fillvalue:, casting:)
|
|
108
117
|
chunked = chunks || compression || compression_opts || shuffle || fletcher32
|
|
109
118
|
return unless chunked || !fillvalue.nil?
|
|
110
119
|
raise HDF5::Error, 'Chunked storage is not supported for scalar datasets' if chunked && shape.empty?
|
|
@@ -152,7 +161,7 @@ module HDF5
|
|
|
152
161
|
end
|
|
153
162
|
check_property_status(HDF5::FFI.H5Pset_fletcher32(dcpl_id), 'enable Fletcher32') if fletcher32
|
|
154
163
|
unless fillvalue.nil?
|
|
155
|
-
value =
|
|
164
|
+
value = HDF5::DataHelpers.normalize_data(fillvalue, dtype: dtype_object, casting:, label: 'Fill value')
|
|
156
165
|
raise HDF5::Error, 'fillvalue must be scalar' unless value.shape.empty?
|
|
157
166
|
|
|
158
167
|
check_property_status(HDF5::FFI.H5Pset_fill_value(dcpl_id, dtype_object.memory_type_id, HDF5::DataHelpers.buffer_for(value)),
|
|
@@ -223,43 +232,24 @@ module HDF5
|
|
|
223
232
|
ensure_open!
|
|
224
233
|
return write_string(data, selection:) if HDF5::StringCodec.string_data?(data)
|
|
225
234
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
target_dtype = dtype
|
|
229
|
-
if normalized_selection.scalar?
|
|
230
|
-
target_dtype.numo_class.cast(data)
|
|
231
|
-
else
|
|
232
|
-
target_dtype.numo_class.ones(*normalized_selection.result_shape) * data
|
|
233
|
-
end
|
|
234
|
-
else
|
|
235
|
-
HDF5::DataHelpers.normalize_data(data, label: 'Dataset data')
|
|
236
|
-
end
|
|
237
|
-
raise HDF5::Error, 'Dataset shape must match data shape' unless values.shape == normalized_selection.result_shape
|
|
235
|
+
current_shape = shape
|
|
236
|
+
raise HDF5::Error, 'Cannot write to a Null dataset' if current_shape.nil?
|
|
238
237
|
|
|
239
|
-
|
|
238
|
+
normalized_selection = Selection.normalize(selection, current_shape)
|
|
240
239
|
target_dtype = dtype
|
|
241
|
-
raise ConversionError,
|
|
242
|
-
dtype_object.castable_to?(target_dtype, casting:)
|
|
243
|
-
return data if normalized_selection.size.zero?
|
|
240
|
+
raise ConversionError, 'String datasets require string data' if target_dtype.kind == :string
|
|
244
241
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
raise HDF5::Error, 'Failed to create memory dataspace' if memory_space_id < 0
|
|
252
|
-
raise HDF5::Error, 'File and memory selections have different sizes' unless
|
|
253
|
-
HDF5::FFI.H5Sget_select_npoints(file_space_id) == HDF5::FFI.H5Sget_select_npoints(memory_space_id)
|
|
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
|
|
254
248
|
|
|
255
|
-
|
|
256
|
-
HDF5::DEFAULT_PROPERTY_LIST, buffer)
|
|
257
|
-
raise HDF5::Error, 'Failed to write dataset' if status < 0
|
|
249
|
+
return data if normalized_selection.size.zero?
|
|
258
250
|
|
|
251
|
+
write_numeric_buffer(HDF5::DataHelpers.buffer_for(values), DType.for_numo(values), normalized_selection)
|
|
259
252
|
data
|
|
260
|
-
ensure
|
|
261
|
-
HDF5::FFI.H5Sclose(memory_space_id) if memory_space_id && memory_space_id >= 0
|
|
262
|
-
HDF5::FFI.H5Sclose(file_space_id) if file_space_id && file_space_id >= 0
|
|
263
253
|
end
|
|
264
254
|
|
|
265
255
|
def close
|
|
@@ -344,6 +334,9 @@ module HDF5
|
|
|
344
334
|
def fillvalue
|
|
345
335
|
ensure_open!
|
|
346
336
|
dtype_object = dtype
|
|
337
|
+
if dtype_object.kind == :string
|
|
338
|
+
raise UnsupportedFeatureError, 'fillvalue is not supported for string datasets'
|
|
339
|
+
end
|
|
347
340
|
property_list_id = HDF5::FFI.H5Dget_create_plist(@dataset_id)
|
|
348
341
|
raise HDF5::Error, 'Failed to get dataset creation properties' if property_list_id < 0
|
|
349
342
|
|
|
@@ -351,7 +344,8 @@ module HDF5
|
|
|
351
344
|
status = HDF5::FFI.H5Pget_fill_value(property_list_id, dtype_object.memory_type_id, buffer)
|
|
352
345
|
raise HDF5::Error, 'Failed to get dataset fill value' if status < 0
|
|
353
346
|
|
|
354
|
-
|
|
347
|
+
value = HDF5::DataHelpers.from_binary(dtype_object, buffer.read_bytes(dtype_object.itemsize), []).extract
|
|
348
|
+
dtype_object.kind == :bool ? !value.zero? : value
|
|
355
349
|
ensure
|
|
356
350
|
HDF5::FFI.H5Pclose(property_list_id) if property_list_id && property_list_id >= 0
|
|
357
351
|
end
|
|
@@ -381,7 +375,7 @@ module HDF5
|
|
|
381
375
|
|
|
382
376
|
def append(data, axis: 0)
|
|
383
377
|
ensure_open!
|
|
384
|
-
values = HDF5::DataHelpers.normalize_data(data, label: 'Dataset data')
|
|
378
|
+
values = HDF5::DataHelpers.normalize_data(data, label: 'Dataset data', dtype: dtype, convert: false)
|
|
385
379
|
current_shape = shape
|
|
386
380
|
raise HDF5::Error, 'Cannot append to a Null dataset' if current_shape.nil?
|
|
387
381
|
raise HDF5::Error, 'Cannot append to a scalar dataset' if current_shape.empty?
|
|
@@ -421,6 +415,16 @@ module HDF5
|
|
|
421
415
|
ensure_open!
|
|
422
416
|
type_id = HDF5::FFI.H5Dget_type(@dataset_id)
|
|
423
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)
|
|
427
|
+
end
|
|
424
428
|
if dtype && HDF5::FFI.H5Tget_class(type_id) == :H5T_STRING
|
|
425
429
|
raise ConversionError,
|
|
426
430
|
'dtype is not supported for string datasets'
|
|
@@ -432,11 +436,11 @@ module HDF5
|
|
|
432
436
|
raise ConversionError, "Cannot safely cast #{source_dtype.to_sym} to #{current_dtype.to_sym}" unless
|
|
433
437
|
source_dtype.castable_to?(current_dtype, casting:)
|
|
434
438
|
|
|
435
|
-
current_shape = shape
|
|
436
|
-
return HDF5::Empty.new(current_dtype) if current_shape.nil?
|
|
437
|
-
|
|
438
439
|
normalized_selection = Selection.normalize(selection, current_shape)
|
|
439
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
|
|
440
444
|
|
|
441
445
|
file_space_id = HDF5::FFI.H5Dget_space(@dataset_id)
|
|
442
446
|
raise HDF5::Error, 'Failed to get dataset dataspace' if file_space_id < 0
|
|
@@ -485,12 +489,17 @@ module HDF5
|
|
|
485
489
|
ensure_open!
|
|
486
490
|
raise HDF5::Error, 'read_into destination must be a Numo::NArray' unless destination.is_a?(Numo::NArray)
|
|
487
491
|
|
|
488
|
-
|
|
489
|
-
|
|
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
|
|
490
497
|
raise HDF5::Error,
|
|
491
498
|
'read_into destination shape must match selection shape'
|
|
492
499
|
end
|
|
493
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)
|
|
494
503
|
destination.store(values)
|
|
495
504
|
end
|
|
496
505
|
|
|
@@ -500,10 +509,13 @@ module HDF5
|
|
|
500
509
|
ensure_open!
|
|
501
510
|
raise ArgumentError, 'max_bytes must be a positive integer' unless max_bytes.is_a?(Integer) && max_bytes.positive?
|
|
502
511
|
|
|
503
|
-
current_dtype = dtype
|
|
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
|
|
504
517
|
raise ArgumentError, 'max_bytes is smaller than one dataset element' if max_bytes < current_dtype.itemsize
|
|
505
518
|
|
|
506
|
-
current_shape = shape
|
|
507
519
|
if current_shape.empty?
|
|
508
520
|
yield [], read
|
|
509
521
|
return
|
|
@@ -521,10 +533,9 @@ module HDF5
|
|
|
521
533
|
|
|
522
534
|
ensure_open!
|
|
523
535
|
|
|
524
|
-
chunk_shape = chunks
|
|
536
|
+
current_shape, chunk_shape = HDF5::FFI::CALL_LOCK.synchronize { [shape, chunks] }
|
|
525
537
|
raise HDF5::Error, 'each_chunk requires a chunked dataset' unless chunk_shape
|
|
526
538
|
|
|
527
|
-
current_shape = shape
|
|
528
539
|
return if current_shape.any?(&:zero?)
|
|
529
540
|
|
|
530
541
|
each_block_selection(current_shape, chunk_shape) do |selection|
|
|
@@ -534,25 +545,64 @@ module HDF5
|
|
|
534
545
|
|
|
535
546
|
private
|
|
536
547
|
|
|
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
|
+
|
|
537
576
|
def write_string(data, selection:)
|
|
538
|
-
|
|
539
|
-
|
|
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:)
|
|
540
588
|
unless values_shape == normalized_selection.result_shape
|
|
541
589
|
raise HDF5::ShapeError,
|
|
542
590
|
'Dataset shape must match string data shape'
|
|
543
591
|
end
|
|
544
592
|
return data if normalized_selection.size.zero?
|
|
545
593
|
|
|
546
|
-
type_id = HDF5::FFI.H5Dget_type(@dataset_id)
|
|
547
|
-
raise HDF5::Error, 'Failed to get dataset datatype' if type_id < 0
|
|
548
594
|
unless HDF5::StringCodec.variable?(type_id)
|
|
549
595
|
raise UnsupportedTypeError, 'Fixed-length string datasets are not yet supported'
|
|
550
596
|
end
|
|
551
597
|
|
|
552
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
|
+
|
|
553
601
|
select_hyperslab(file_space_id, normalized_selection)
|
|
554
602
|
memory_space_id = create_memory_dataspace(normalized_selection.result_shape)
|
|
555
|
-
|
|
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:)
|
|
556
606
|
status = HDF5::FFI.H5Dwrite(@dataset_id, type_id, memory_space_id, file_space_id,
|
|
557
607
|
HDF5::DEFAULT_PROPERTY_LIST, buffer)
|
|
558
608
|
raise HDF5::Error, 'Failed to write string dataset' if status < 0
|
|
@@ -572,25 +622,36 @@ module HDF5
|
|
|
572
622
|
return Numo::RObject.new(*normalized_selection.result_shape) if normalized_selection.size.zero?
|
|
573
623
|
|
|
574
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
|
+
|
|
575
627
|
select_hyperslab(file_space_id, normalized_selection)
|
|
576
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
|
+
|
|
577
631
|
buffer = ::FFI::MemoryPointer.new(:pointer, normalized_selection.size)
|
|
578
632
|
status = HDF5::FFI.H5Dread(@dataset_id, type_id, memory_space_id, file_space_id,
|
|
579
633
|
HDF5::DEFAULT_PROPERTY_LIST, buffer)
|
|
580
634
|
raise HDF5::Error, 'Failed to read string dataset' if status < 0
|
|
581
635
|
|
|
582
|
-
HDF5::StringCodec.read_values(buffer, normalized_selection.size, normalized_selection.result_shape
|
|
636
|
+
HDF5::StringCodec.read_values(buffer, normalized_selection.size, normalized_selection.result_shape,
|
|
637
|
+
encoding: HDF5::StringCodec.encoding_for(type_id))
|
|
583
638
|
ensure
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
|
590
653
|
end
|
|
591
654
|
end
|
|
592
|
-
HDF5::FFI.H5Sclose(memory_space_id) if memory_space_id && memory_space_id >= 0
|
|
593
|
-
HDF5::FFI.H5Sclose(file_space_id) if file_space_id && file_space_id >= 0
|
|
594
655
|
end
|
|
595
656
|
|
|
596
657
|
def block_shape_for(dataset_shape, max_elements)
|
|
@@ -653,7 +714,7 @@ module HDF5
|
|
|
653
714
|
|
|
654
715
|
prepend FileContext.guard(
|
|
655
716
|
:attrs, :write, :dtype, :shape, :chunks, :maxshape, :fillvalue, :resize, :append, :read, :read_array,
|
|
656
|
-
:[], :[]=, :read_into, :
|
|
717
|
+
:[], :[]=, :read_into, :close, :closed?
|
|
657
718
|
)
|
|
658
719
|
end
|
|
659
720
|
end
|