ruby-hdf5 0.0.1 → 0.0.2
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 +7 -2
- data/lib/hdf5/attribute.rb +108 -12
- data/lib/hdf5/dataset.rb +60 -37
- data/lib/hdf5/file.rb +1 -1
- data/lib/hdf5/group.rb +10 -6
- 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: 4591bafb2585ec00df81fc3831cc7037cc02dee81f15919ec7cf1b5d2f08a4c2
|
|
4
|
+
data.tar.gz: beb661f3c825adaab6b132cee69a402c1319d05143a0bec1516f2e0dec1180b2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bf13704291dfbdb2d0d61cf03504574a553e3d16e6db980355df5408ee425e340cfa1bc7b3850c4f877aa066958f20ab4cb5cbb07935f6302eb403771c6b4458
|
|
7
|
+
data.tar.gz: ac8511ab873fea84933c38c65c15c899cfb3dbc7a63d2a062f41d78722bcc87288d84997e5f296c38691deb2660e065046e7fdf13e2c611127ef8fc84b1ce2ea
|
data/README.md
CHANGED
|
@@ -11,14 +11,19 @@ This gem currently provides practical high-level wrappers for:
|
|
|
11
11
|
- opening and creating files
|
|
12
12
|
- creating groups
|
|
13
13
|
- creating, writing, and reading one-dimensional numeric datasets
|
|
14
|
-
- reading attributes
|
|
14
|
+
- reading and writing numeric attributes
|
|
15
15
|
|
|
16
16
|
Unsupported at this stage:
|
|
17
17
|
|
|
18
18
|
- string dataset read/write
|
|
19
|
-
- attribute write
|
|
20
19
|
- multidimensional array write
|
|
21
20
|
|
|
21
|
+
Integer datasets and integer attributes currently use native C `int` under the hood.
|
|
22
|
+
Values outside that range are rejected with `HDF5::Error` to avoid silent overflow.
|
|
23
|
+
|
|
24
|
+
`Group#list_datasets` filters datasets from group entries by checking object type per entry.
|
|
25
|
+
For very large groups, this may be slower than `Group#list_entries`.
|
|
26
|
+
|
|
22
27
|
## Supported HDF5 Versions
|
|
23
28
|
|
|
24
29
|
- HDF5 1.10
|
data/lib/hdf5/attribute.rb
CHANGED
|
@@ -3,18 +3,18 @@ module HDF5
|
|
|
3
3
|
def initialize(dataset_id, attr_name)
|
|
4
4
|
@dataset_id = dataset_id
|
|
5
5
|
@attr_name = attr_name
|
|
6
|
-
@attr_id = FFI.H5Aopen(@dataset_id, @attr_name,
|
|
7
|
-
raise 'Failed to open attribute' if @attr_id < 0
|
|
6
|
+
@attr_id = HDF5::FFI.H5Aopen(@dataset_id, @attr_name, HDF5::DEFAULT_PROPERTY_LIST)
|
|
7
|
+
raise HDF5::Error, 'Failed to open attribute' if @attr_id < 0
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def read
|
|
11
|
-
type_id = FFI.H5Aget_type(@attr_id)
|
|
12
|
-
space_id = FFI.H5Aget_space(@attr_id)
|
|
11
|
+
type_id = HDF5::FFI.H5Aget_type(@attr_id)
|
|
12
|
+
space_id = HDF5::FFI.H5Aget_space(@attr_id)
|
|
13
13
|
|
|
14
|
-
size = FFI.H5Sget_simple_extent_npoints(space_id)
|
|
14
|
+
size = HDF5::FFI.H5Sget_simple_extent_npoints(space_id)
|
|
15
15
|
|
|
16
16
|
buffer = \
|
|
17
|
-
case FFI.H5Tget_class(type_id)
|
|
17
|
+
case HDF5::FFI.H5Tget_class(type_id)
|
|
18
18
|
when :H5T_INTEGER
|
|
19
19
|
::FFI::MemoryPointer.new(:int, size)
|
|
20
20
|
when :H5T_FLOAT
|
|
@@ -22,13 +22,13 @@ module HDF5
|
|
|
22
22
|
when :H5T_STRING
|
|
23
23
|
::FFI::MemoryPointer.new(:pointer, size)
|
|
24
24
|
else
|
|
25
|
-
raise 'Unsupported data type'
|
|
25
|
+
raise HDF5::Error, 'Unsupported data type'
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
status = FFI.H5Aread(@attr_id, type_id, buffer)
|
|
29
|
-
raise 'Failed to read attribute' if status < 0
|
|
28
|
+
status = HDF5::FFI.H5Aread(@attr_id, type_id, buffer)
|
|
29
|
+
raise HDF5::Error, 'Failed to read attribute' if status < 0
|
|
30
30
|
|
|
31
|
-
case FFI.H5Tget_class(type_id)
|
|
31
|
+
case HDF5::FFI.H5Tget_class(type_id)
|
|
32
32
|
when :H5T_INTEGER
|
|
33
33
|
buffer.read_array_of_int(size)
|
|
34
34
|
when :H5T_FLOAT
|
|
@@ -36,12 +36,18 @@ module HDF5
|
|
|
36
36
|
when :H5T_STRING
|
|
37
37
|
buffer.read_pointer.read_string
|
|
38
38
|
else
|
|
39
|
-
raise 'Unsupported data type'
|
|
39
|
+
raise HDF5::Error, 'Unsupported data type'
|
|
40
40
|
end
|
|
41
|
+
ensure
|
|
42
|
+
HDF5::FFI.H5Tclose(type_id) if type_id && type_id >= 0
|
|
43
|
+
HDF5::FFI.H5Sclose(space_id) if space_id && space_id >= 0
|
|
41
44
|
end
|
|
42
45
|
|
|
43
46
|
def close
|
|
44
|
-
|
|
47
|
+
return if @attr_id.nil?
|
|
48
|
+
|
|
49
|
+
HDF5::FFI.H5Aclose(@attr_id)
|
|
50
|
+
@attr_id = nil
|
|
45
51
|
end
|
|
46
52
|
end
|
|
47
53
|
|
|
@@ -56,5 +62,95 @@ module HDF5
|
|
|
56
62
|
ensure
|
|
57
63
|
attr.close if attr
|
|
58
64
|
end
|
|
65
|
+
|
|
66
|
+
def []=(attr_name, value)
|
|
67
|
+
write(attr_name, value)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def write(attr_name, value)
|
|
71
|
+
values = normalize_data(value)
|
|
72
|
+
datatype_id = datatype_id_for(values)
|
|
73
|
+
|
|
74
|
+
exists = HDF5::FFI.H5Aexists(@dataset_id, attr_name)
|
|
75
|
+
raise HDF5::Error, "Failed to check attribute existence: #{attr_name}" if exists.negative?
|
|
76
|
+
|
|
77
|
+
if exists.positive?
|
|
78
|
+
status = HDF5::FFI.H5Adelete(@dataset_id, attr_name)
|
|
79
|
+
raise HDF5::Error, "Failed to replace attribute: #{attr_name}" if status < 0
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
dims = ::FFI::MemoryPointer.new(:ulong_long, 1)
|
|
83
|
+
dims.write_array_of_ulong_long([values.length])
|
|
84
|
+
dataspace_id = HDF5::FFI.H5Screate_simple(1, dims, nil)
|
|
85
|
+
raise HDF5::Error, 'Failed to create attribute dataspace' if dataspace_id < 0
|
|
86
|
+
|
|
87
|
+
attr_id = HDF5::FFI.H5Acreate2(
|
|
88
|
+
@dataset_id,
|
|
89
|
+
attr_name,
|
|
90
|
+
datatype_id,
|
|
91
|
+
dataspace_id,
|
|
92
|
+
HDF5::DEFAULT_PROPERTY_LIST,
|
|
93
|
+
HDF5::DEFAULT_PROPERTY_LIST
|
|
94
|
+
)
|
|
95
|
+
raise HDF5::Error, "Failed to create attribute: #{attr_name}" if attr_id < 0
|
|
96
|
+
|
|
97
|
+
buffer = buffer_for(values)
|
|
98
|
+
status = HDF5::FFI.H5Awrite(attr_id, datatype_id, buffer)
|
|
99
|
+
raise HDF5::Error, "Failed to write attribute: #{attr_name}" if status < 0
|
|
100
|
+
|
|
101
|
+
value
|
|
102
|
+
ensure
|
|
103
|
+
HDF5::FFI.H5Aclose(attr_id) if attr_id && attr_id >= 0
|
|
104
|
+
HDF5::FFI.H5Sclose(dataspace_id) if dataspace_id && dataspace_id >= 0
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def normalize_data(value)
|
|
110
|
+
values = value.is_a?(Array) ? value : [value]
|
|
111
|
+
raise HDF5::Error, 'Attribute data must not be empty' if values.empty?
|
|
112
|
+
raise HDF5::Error, 'Nested arrays are not supported for attributes' if values.any? { |item| item.is_a?(Array) }
|
|
113
|
+
|
|
114
|
+
values
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def datatype_id_for(values)
|
|
118
|
+
if values.all? { |item| item.is_a?(Integer) }
|
|
119
|
+
validate_native_int_range!(values)
|
|
120
|
+
HDF5::FFI.H5T_NATIVE_INT
|
|
121
|
+
elsif values.all? { |item| item.is_a?(Numeric) }
|
|
122
|
+
HDF5::FFI.H5T_NATIVE_DOUBLE
|
|
123
|
+
else
|
|
124
|
+
raise HDF5::Error, 'Only numeric attribute data is supported'
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def buffer_for(values)
|
|
129
|
+
if values.all? { |item| item.is_a?(Integer) }
|
|
130
|
+
buffer = ::FFI::MemoryPointer.new(:int, values.length)
|
|
131
|
+
buffer.write_array_of_int(values)
|
|
132
|
+
else
|
|
133
|
+
buffer = ::FFI::MemoryPointer.new(:double, values.length)
|
|
134
|
+
buffer.write_array_of_double(values.map(&:to_f))
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
buffer
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def native_int_bounds
|
|
141
|
+
bits = ::FFI.type_size(:int) * 8
|
|
142
|
+
max = (1 << (bits - 1)) - 1
|
|
143
|
+
min = -(1 << (bits - 1))
|
|
144
|
+
[min, max]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def validate_native_int_range!(values)
|
|
148
|
+
min, max = native_int_bounds
|
|
149
|
+
out_of_range = values.find { |value| value < min || value > max }
|
|
150
|
+
return unless out_of_range
|
|
151
|
+
|
|
152
|
+
raise HDF5::Error,
|
|
153
|
+
"Integer value #{out_of_range} is outside native int range (#{min}..#{max}). Use a smaller value."
|
|
154
|
+
end
|
|
59
155
|
end
|
|
60
156
|
end
|
data/lib/hdf5/dataset.rb
CHANGED
|
@@ -1,11 +1,64 @@
|
|
|
1
1
|
module HDF5
|
|
2
2
|
class Dataset
|
|
3
|
+
module DataHelpers
|
|
4
|
+
module_function
|
|
5
|
+
|
|
6
|
+
def normalize_data(data)
|
|
7
|
+
values = data.is_a?(Array) ? data : [data]
|
|
8
|
+
raise HDF5::Error, 'Dataset data must not be empty' if values.empty?
|
|
9
|
+
raise HDF5::Error, 'Nested arrays are not supported' if values.any? { |value| value.is_a?(Array) }
|
|
10
|
+
|
|
11
|
+
values
|
|
12
|
+
end
|
|
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'
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def buffer_for(data)
|
|
26
|
+
if data.all? { |value| value.is_a?(Integer) }
|
|
27
|
+
buffer = ::FFI::MemoryPointer.new(:int, data.length)
|
|
28
|
+
buffer.write_array_of_int(data)
|
|
29
|
+
else
|
|
30
|
+
buffer = ::FFI::MemoryPointer.new(:double, data.length)
|
|
31
|
+
buffer.write_array_of_double(data.map(&:to_f))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
buffer
|
|
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
|
|
55
|
+
|
|
3
56
|
class << self
|
|
4
57
|
def create(parent_id, name, data)
|
|
5
|
-
values = normalize_data(data)
|
|
58
|
+
values = DataHelpers.normalize_data(data)
|
|
6
59
|
dims = ::FFI::MemoryPointer.new(:ulong_long, 1)
|
|
7
60
|
dims.write_array_of_ulong_long([values.length])
|
|
8
|
-
datatype_id = datatype_id_for(values)
|
|
61
|
+
datatype_id = DataHelpers.datatype_id_for(values)
|
|
9
62
|
dataspace_id = HDF5::FFI.H5Screate_simple(1, dims, nil)
|
|
10
63
|
raise HDF5::Error, "Failed to create dataspace for dataset: #{name}" if dataspace_id < 0
|
|
11
64
|
|
|
@@ -36,36 +89,6 @@ module HDF5
|
|
|
36
89
|
end
|
|
37
90
|
end
|
|
38
91
|
|
|
39
|
-
def normalize_data(data)
|
|
40
|
-
values = data.is_a?(Array) ? data : [data]
|
|
41
|
-
raise HDF5::Error, 'Dataset data must not be empty' if values.empty?
|
|
42
|
-
raise HDF5::Error, 'Nested arrays are not supported' if values.any? { |value| value.is_a?(Array) }
|
|
43
|
-
|
|
44
|
-
values
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def datatype_id_for(data)
|
|
48
|
-
if data.all? { |value| value.is_a?(Integer) }
|
|
49
|
-
HDF5::FFI.H5T_NATIVE_INT
|
|
50
|
-
elsif data.all? { |value| value.is_a?(Numeric) }
|
|
51
|
-
HDF5::FFI.H5T_NATIVE_DOUBLE
|
|
52
|
-
else
|
|
53
|
-
raise HDF5::Error, 'Only numeric dataset data is supported'
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def buffer_for(data)
|
|
58
|
-
if data.all? { |value| value.is_a?(Integer) }
|
|
59
|
-
buffer = ::FFI::MemoryPointer.new(:int, data.length)
|
|
60
|
-
buffer.write_array_of_int(data)
|
|
61
|
-
else
|
|
62
|
-
buffer = ::FFI::MemoryPointer.new(:double, data.length)
|
|
63
|
-
buffer.write_array_of_double(data.map(&:to_f))
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
buffer
|
|
67
|
-
end
|
|
68
|
-
|
|
69
92
|
private
|
|
70
93
|
|
|
71
94
|
def from_id(dataset_id, name)
|
|
@@ -84,9 +107,9 @@ module HDF5
|
|
|
84
107
|
end
|
|
85
108
|
|
|
86
109
|
def write(data)
|
|
87
|
-
values =
|
|
88
|
-
|
|
89
|
-
|
|
110
|
+
values = DataHelpers.normalize_data(data)
|
|
111
|
+
mem_type_id = DataHelpers.datatype_id_for(values)
|
|
112
|
+
buffer = DataHelpers.buffer_for(values)
|
|
90
113
|
status = HDF5::FFI.H5Dwrite(@dataset_id, mem_type_id, HDF5::DEFAULT_PROPERTY_LIST, HDF5::DEFAULT_PROPERTY_LIST,
|
|
91
114
|
HDF5::DEFAULT_PROPERTY_LIST, buffer)
|
|
92
115
|
raise HDF5::Error, 'Failed to write dataset' if status < 0
|
|
@@ -160,8 +183,8 @@ module HDF5
|
|
|
160
183
|
buffer.read_array_of_double(total_elements)
|
|
161
184
|
end
|
|
162
185
|
|
|
163
|
-
def read_string_data(
|
|
164
|
-
raise
|
|
186
|
+
def read_string_data(_total_elements)
|
|
187
|
+
raise HDF5::Error, 'String dataset reading is not supported yet'
|
|
165
188
|
end
|
|
166
189
|
|
|
167
190
|
private
|
data/lib/hdf5/file.rb
CHANGED
data/lib/hdf5/group.rb
CHANGED
|
@@ -54,10 +54,10 @@ module HDF5
|
|
|
54
54
|
Dataset.create(@group_id, name, data, &block)
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
-
def
|
|
58
|
-
|
|
57
|
+
def list_entries
|
|
58
|
+
entries = []
|
|
59
59
|
callback = ::FFI::Function.new(:int, %i[int64_t string pointer pointer]) do |_, name, _, _|
|
|
60
|
-
|
|
60
|
+
entries << name
|
|
61
61
|
0 # continue
|
|
62
62
|
end
|
|
63
63
|
|
|
@@ -66,9 +66,13 @@ module HDF5
|
|
|
66
66
|
else
|
|
67
67
|
HDF5::FFI.H5Literate2(@group_id, :H5_INDEX_NAME, :H5_ITER_NATIVE, nil, callback, nil)
|
|
68
68
|
end).negative? &&
|
|
69
|
-
raise('Failed to list
|
|
69
|
+
raise(HDF5::Error, 'Failed to list entries')
|
|
70
|
+
|
|
71
|
+
entries
|
|
72
|
+
end
|
|
70
73
|
|
|
71
|
-
|
|
74
|
+
def list_datasets
|
|
75
|
+
list_entries.select { |name| dataset?(name) }
|
|
72
76
|
end
|
|
73
77
|
|
|
74
78
|
def [](name)
|
|
@@ -77,7 +81,7 @@ module HDF5
|
|
|
77
81
|
elsif dataset?(name)
|
|
78
82
|
Dataset.open(@group_id, name)
|
|
79
83
|
else
|
|
80
|
-
raise HDF5::Error,
|
|
84
|
+
raise HDF5::Error, "Group or dataset not found: #{name}"
|
|
81
85
|
end
|
|
82
86
|
end
|
|
83
87
|
|
data/lib/hdf5/version.rb
CHANGED