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/dtype.rb
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
module HDF5
|
|
2
|
+
class DType
|
|
3
|
+
TYPES = {
|
|
4
|
+
int8: [Numo::Int8, :H5T_NATIVE_INT8_g, :H5T_STD_I8LE_g, :integer, 1],
|
|
5
|
+
uint8: [Numo::UInt8, :H5T_NATIVE_UINT8_g, :H5T_STD_U8LE_g, :integer, 1],
|
|
6
|
+
int16: [Numo::Int16, :H5T_NATIVE_INT16_g, :H5T_STD_I16LE_g, :integer, 2],
|
|
7
|
+
uint16: [Numo::UInt16, :H5T_NATIVE_UINT16_g, :H5T_STD_U16LE_g, :integer, 2],
|
|
8
|
+
int32: [Numo::Int32, :H5T_NATIVE_INT32_g, :H5T_STD_I32LE_g, :integer, 4],
|
|
9
|
+
uint32: [Numo::UInt32, :H5T_NATIVE_UINT32_g, :H5T_STD_U32LE_g, :integer, 4],
|
|
10
|
+
int64: [Numo::Int64, :H5T_NATIVE_INT64_g, :H5T_STD_I64LE_g, :integer, 8],
|
|
11
|
+
uint64: [Numo::UInt64, :H5T_NATIVE_UINT64_g, :H5T_STD_U64LE_g, :integer, 8],
|
|
12
|
+
float32: [Numo::SFloat, :H5T_NATIVE_FLOAT, :H5T_IEEE_F32LE_g, :float, 4],
|
|
13
|
+
float64: [Numo::DFloat, :H5T_NATIVE_DOUBLE, :H5T_IEEE_F64LE_g, :float, 8],
|
|
14
|
+
bool: [Numo::Bit, nil, nil, :bool, 1],
|
|
15
|
+
complex64: [Numo::SComplex, nil, nil, :complex, 8],
|
|
16
|
+
complex128: [Numo::DComplex, nil, nil, :complex, 16]
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
attr_reader :numo_class, :memory_type_name, :storage_type_name, :kind, :itemsize, :byteorder, :precision, :offset,
|
|
20
|
+
:hdf5_class, :encoding
|
|
21
|
+
|
|
22
|
+
def self.for_numo(value)
|
|
23
|
+
type = TYPES.values.find { |numo_class,| value.is_a?(numo_class) }
|
|
24
|
+
raise HDF5::Error, "Unsupported Numo type: #{value.class}" unless type
|
|
25
|
+
|
|
26
|
+
symbol = TYPES.key(type)
|
|
27
|
+
new(symbol, *type)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.for_symbol(symbol)
|
|
31
|
+
type = TYPES.fetch(symbol) { raise HDF5::Error, "Unsupported dtype: #{symbol.inspect}" }
|
|
32
|
+
new(symbol, *type)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.for_hdf5(type_id)
|
|
36
|
+
type_class = HDF5::FFI.H5Tget_class(type_id)
|
|
37
|
+
itemsize = HDF5::FFI.H5Tget_size(type_id)
|
|
38
|
+
if type_class == :H5T_STRING
|
|
39
|
+
return new(:string, Numo::RObject, nil, nil, :string, itemsize, byteorder: :none,
|
|
40
|
+
hdf5_class: type_class, encoding: StringCodec.encoding_for(type_id))
|
|
41
|
+
end
|
|
42
|
+
return for_bool_hdf5(type_id, itemsize) if type_class == :H5T_ENUM
|
|
43
|
+
return for_complex_hdf5(type_id, itemsize) if type_class == :H5T_COMPOUND
|
|
44
|
+
|
|
45
|
+
symbol = case type_class
|
|
46
|
+
when :H5T_INTEGER
|
|
47
|
+
prefix = HDF5::FFI.H5Tget_sign(type_id) == :H5T_SGN_NONE ? 'uint' : 'int'
|
|
48
|
+
"#{prefix}#{itemsize * 8}".to_sym
|
|
49
|
+
when :H5T_FLOAT
|
|
50
|
+
"float#{itemsize * 8}".to_sym
|
|
51
|
+
else
|
|
52
|
+
raise UnsupportedTypeError, "Unsupported HDF5 datatype: #{type_class}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
precision = HDF5::FFI.H5Tget_precision(type_id)
|
|
56
|
+
offset = HDF5::FFI.H5Tget_offset(type_id)
|
|
57
|
+
unless precision == itemsize * 8
|
|
58
|
+
raise UnsupportedTypeError,
|
|
59
|
+
"Unsupported #{precision}-bit datatype in #{itemsize * 8}-bit storage"
|
|
60
|
+
end
|
|
61
|
+
raise UnsupportedTypeError, "Unsupported datatype bit offset: #{offset}" unless offset.zero?
|
|
62
|
+
|
|
63
|
+
order = HDF5::FFI.H5Tget_order(type_id)
|
|
64
|
+
byteorder = { H5T_ORDER_LE: :little, H5T_ORDER_BE: :big, H5T_ORDER_NONE: :none }.fetch(order) do
|
|
65
|
+
raise UnsupportedTypeError, "Unsupported datatype byte order: #{order}"
|
|
66
|
+
end
|
|
67
|
+
new(symbol, *TYPES.fetch(symbol), byteorder:, precision:, offset:, hdf5_class: type_class)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.for_bool_hdf5(type_id, itemsize)
|
|
71
|
+
false_value = ::FFI::MemoryPointer.new(:int8)
|
|
72
|
+
true_value = ::FFI::MemoryPointer.new(:int8)
|
|
73
|
+
valid = itemsize == 1 && HDF5::FFI.H5Tget_nmembers(type_id) == 2 &&
|
|
74
|
+
HDF5::FFI.H5Tenum_valueof(type_id, 'FALSE', false_value) >= 0 &&
|
|
75
|
+
HDF5::FFI.H5Tenum_valueof(type_id, 'TRUE', true_value) >= 0 &&
|
|
76
|
+
false_value.read_int8.zero? && true_value.read_int8 == 1
|
|
77
|
+
raise UnsupportedTypeError, 'Unsupported HDF5 enum datatype' unless valid
|
|
78
|
+
|
|
79
|
+
new(:bool, *TYPES.fetch(:bool), byteorder: :none, hdf5_class: :H5T_ENUM)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def self.for_complex_hdf5(type_id, itemsize)
|
|
83
|
+
component_size = itemsize / 2
|
|
84
|
+
real_index = HDF5::FFI.H5Tget_member_index(type_id, 'r')
|
|
85
|
+
imaginary_index = HDF5::FFI.H5Tget_member_index(type_id, 'i')
|
|
86
|
+
valid = [8, 16].include?(itemsize) && real_index >= 0 && imaginary_index >= 0 &&
|
|
87
|
+
HDF5::FFI.H5Tget_nmembers(type_id) == 2 &&
|
|
88
|
+
HDF5::FFI.H5Tget_member_offset(type_id, real_index) == 0 &&
|
|
89
|
+
HDF5::FFI.H5Tget_member_offset(type_id, imaginary_index) == component_size
|
|
90
|
+
byteorders = []
|
|
91
|
+
[real_index, imaginary_index].each do |index|
|
|
92
|
+
next unless index >= 0
|
|
93
|
+
|
|
94
|
+
member_type_id = HDF5::FFI.H5Tget_member_type(type_id, index)
|
|
95
|
+
begin
|
|
96
|
+
member_dtype = member_type_id >= 0 ? for_hdf5(member_type_id) : nil
|
|
97
|
+
valid &&= member_dtype && member_dtype.kind == :float && member_dtype.itemsize == component_size
|
|
98
|
+
byteorders << member_dtype.byteorder if member_dtype
|
|
99
|
+
ensure
|
|
100
|
+
HDF5::FFI.H5Tclose(member_type_id) if member_type_id >= 0
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
valid &&= byteorders.length == 2 && byteorders.uniq.length == 1
|
|
104
|
+
raise UnsupportedTypeError, 'Unsupported HDF5 compound datatype' unless valid
|
|
105
|
+
|
|
106
|
+
symbol = itemsize == 8 ? :complex64 : :complex128
|
|
107
|
+
new(symbol, *TYPES.fetch(symbol), byteorder: byteorders.first, hdf5_class: :H5T_COMPOUND)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def initialize(symbol, numo_class, memory_type_name, storage_type_name, kind, itemsize, byteorder: :little,
|
|
111
|
+
precision: itemsize * 8, offset: 0, hdf5_class: nil, encoding: nil)
|
|
112
|
+
@symbol = symbol
|
|
113
|
+
@numo_class = numo_class
|
|
114
|
+
@memory_type_name = memory_type_name
|
|
115
|
+
@storage_type_name = storage_type_name
|
|
116
|
+
@kind = kind
|
|
117
|
+
@itemsize = itemsize
|
|
118
|
+
@byteorder = byteorder
|
|
119
|
+
@precision = precision
|
|
120
|
+
@offset = offset
|
|
121
|
+
@encoding = encoding
|
|
122
|
+
@hdf5_class = hdf5_class || (kind == :integer ? :H5T_INTEGER : :H5T_FLOAT)
|
|
123
|
+
freeze
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def memory_type_id
|
|
127
|
+
return self.class.bool_type_id(native: true) if kind == :bool
|
|
128
|
+
return self.class.complex_type_id(itemsize, native: true) if kind == :complex
|
|
129
|
+
|
|
130
|
+
HDF5::FFI.public_send(memory_type_name)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def storage_type_id
|
|
134
|
+
return self.class.bool_type_id(native: false) if kind == :bool
|
|
135
|
+
return self.class.complex_type_id(itemsize, native: false) if kind == :complex
|
|
136
|
+
|
|
137
|
+
HDF5::FFI.public_send(storage_type_name)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def to_sym
|
|
141
|
+
@symbol
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def castable_to?(target, casting: :safe)
|
|
145
|
+
raise ArgumentError, "Unsupported casting mode: #{casting.inspect}" unless %i[safe unsafe].include?(casting)
|
|
146
|
+
return true if casting == :unsafe || to_sym == target.to_sym
|
|
147
|
+
|
|
148
|
+
if kind == :integer && target.kind == :integer
|
|
149
|
+
return itemsize <= target.itemsize if unsigned? == target.unsigned?
|
|
150
|
+
return false unless unsigned? && !target.unsigned?
|
|
151
|
+
|
|
152
|
+
return itemsize < target.itemsize
|
|
153
|
+
end
|
|
154
|
+
return itemsize <= target.itemsize if kind == :float && target.kind == :float
|
|
155
|
+
return itemsize <= target.itemsize if kind == :complex && target.kind == :complex
|
|
156
|
+
return itemsize <= target.itemsize / 2 if kind == :float && target.kind == :complex
|
|
157
|
+
|
|
158
|
+
if kind == :integer && %i[float complex].include?(target.kind)
|
|
159
|
+
significant_bits = unsigned? ? itemsize * 8 : itemsize * 8 - 1
|
|
160
|
+
size = target.kind == :complex ? target.itemsize / 2 : target.itemsize
|
|
161
|
+
mantissa_bits = size == 4 ? 24 : 53
|
|
162
|
+
return significant_bits <= mantissa_bits
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
false
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def unsigned?
|
|
169
|
+
@symbol.to_s.start_with?('uint')
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
class << self
|
|
173
|
+
def bool_type_id(native:)
|
|
174
|
+
@bool_type_ids ||= {}
|
|
175
|
+
@bool_type_ids[native] ||= begin
|
|
176
|
+
base_id = HDF5::FFI.public_send(native ? :H5T_NATIVE_INT8_g : :H5T_STD_I8LE_g)
|
|
177
|
+
type_id = HDF5::FFI.H5Tenum_create(base_id)
|
|
178
|
+
raise HDF5::Error, 'Failed to create bool datatype' if type_id < 0
|
|
179
|
+
|
|
180
|
+
false_value = ::FFI::MemoryPointer.new(:int8).tap { |pointer| pointer.write_int8(0) }
|
|
181
|
+
true_value = ::FFI::MemoryPointer.new(:int8).tap { |pointer| pointer.write_int8(1) }
|
|
182
|
+
if HDF5::FFI.H5Tenum_insert(type_id, 'FALSE', false_value) < 0 ||
|
|
183
|
+
HDF5::FFI.H5Tenum_insert(type_id, 'TRUE', true_value) < 0
|
|
184
|
+
HDF5::FFI.H5Tclose(type_id)
|
|
185
|
+
raise HDF5::Error, 'Failed to define bool datatype'
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
type_id
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def complex_type_id(size, native:)
|
|
193
|
+
@complex_type_ids ||= {}
|
|
194
|
+
@complex_type_ids[[size, native]] ||= begin
|
|
195
|
+
component_id = if native
|
|
196
|
+
HDF5::FFI.public_send(size == 8 ? :H5T_NATIVE_FLOAT : :H5T_NATIVE_DOUBLE)
|
|
197
|
+
else
|
|
198
|
+
HDF5::FFI.public_send(size == 8 ? :H5T_IEEE_F32LE_g : :H5T_IEEE_F64LE_g)
|
|
199
|
+
end
|
|
200
|
+
type_id = HDF5::FFI.H5Tcreate(:H5T_COMPOUND, size)
|
|
201
|
+
raise HDF5::Error, 'Failed to create complex datatype' if type_id < 0
|
|
202
|
+
|
|
203
|
+
if HDF5::FFI.H5Tinsert(type_id, 'r', 0, component_id) < 0 ||
|
|
204
|
+
HDF5::FFI.H5Tinsert(type_id, 'i', size / 2, component_id) < 0
|
|
205
|
+
HDF5::FFI.H5Tclose(type_id)
|
|
206
|
+
raise HDF5::Error, 'Failed to define complex datatype'
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
type_id
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
data/lib/hdf5/ffi.rb
CHANGED
|
@@ -2,6 +2,8 @@ module HDF5
|
|
|
2
2
|
module FFI
|
|
3
3
|
extend ::FFI::Library
|
|
4
4
|
|
|
5
|
+
require 'monitor'
|
|
6
|
+
|
|
5
7
|
class << self
|
|
6
8
|
attr_reader :backend
|
|
7
9
|
end
|
|
@@ -28,6 +30,20 @@ module HDF5
|
|
|
28
30
|
warn e.message # if $VERBOSE
|
|
29
31
|
end
|
|
30
32
|
|
|
33
|
+
def self.backend_for_version(major, minor, release)
|
|
34
|
+
case [major, minor]
|
|
35
|
+
in [1, 10..13]
|
|
36
|
+
'ffi_10'
|
|
37
|
+
in [1, 14]
|
|
38
|
+
'ffi_14'
|
|
39
|
+
in [2, 0..2]
|
|
40
|
+
'ffi_20'
|
|
41
|
+
else
|
|
42
|
+
raise "Unsupported HDF5 version #{major}.#{minor}.#{release}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
private_class_method :backend_for_version
|
|
46
|
+
|
|
31
47
|
attach_function 'H5get_libversion', %i[
|
|
32
48
|
pointer
|
|
33
49
|
pointer
|
|
@@ -43,17 +59,39 @@ module HDF5
|
|
|
43
59
|
minor = minor_ptr.read_uint
|
|
44
60
|
release = release_ptr.read_uint
|
|
45
61
|
|
|
46
|
-
@backend =
|
|
47
|
-
in [1, 10..13]
|
|
48
|
-
'ffi_10'
|
|
49
|
-
in [1, 14..]
|
|
50
|
-
'ffi_14'
|
|
51
|
-
in [2.., _]
|
|
52
|
-
'ffi_14'
|
|
53
|
-
else
|
|
54
|
-
raise "Unsupported HDF5 version #{major}.#{minor}.#{release}"
|
|
55
|
-
end
|
|
62
|
+
@backend = backend_for_version(major, minor, release)
|
|
56
63
|
|
|
57
64
|
require_relative @backend
|
|
65
|
+
|
|
66
|
+
REQUIRED_FUNCTIONS = %i[
|
|
67
|
+
H5Fopen H5Fcreate H5Fclose H5Fflush
|
|
68
|
+
H5Gopen2 H5Gcreate2 H5Gclose
|
|
69
|
+
H5Dopen2 H5Dcreate2 H5Dclose H5Dread H5Dwrite H5Dget_type H5Dget_space H5Dset_extent H5Dget_create_plist
|
|
70
|
+
H5Aopen H5Acreate2 H5Aclose H5Aread H5Awrite H5Aexists H5Adelete H5Aiterate2 H5Aget_type H5Aget_space
|
|
71
|
+
H5Screate H5Screate_simple H5Sclose H5Sget_simple_extent_type H5Sget_simple_extent_ndims
|
|
72
|
+
H5Sget_simple_extent_dims H5Sselect_hyperslab H5Sget_select_npoints
|
|
73
|
+
H5Tcopy H5Tclose H5Tget_class H5Tget_size H5Tget_sign H5Tget_order H5Tget_precision H5Tget_offset
|
|
74
|
+
H5Tset_size H5Tset_cset H5Tget_cset H5Tis_variable_str H5Tcreate H5Tinsert H5Tenum_create H5Tenum_insert
|
|
75
|
+
H5Tenum_valueof H5Tget_nmembers H5Tget_member_index H5Tget_member_offset H5Tget_member_type H5Tget_super
|
|
76
|
+
H5Pcreate H5Pclose H5Pset_chunk H5Pget_chunk H5Pget_layout H5Pset_deflate H5Pset_shuffle H5Pset_fletcher32
|
|
77
|
+
H5Pset_fill_value H5Pget_fill_value H5Zfilter_avail H5Zget_filter_info
|
|
78
|
+
H5Lexists H5Ldelete H5Lmove H5Lcreate_hard H5Lcreate_soft H5Lget_val H5Dvlen_reclaim
|
|
79
|
+
].freeze
|
|
80
|
+
version_functions = if MiV == 10
|
|
81
|
+
%i[H5Literate H5Lget_info H5Oget_info_by_name]
|
|
82
|
+
else
|
|
83
|
+
%i[H5Literate2 H5Lget_info2 H5Oget_info_by_name1]
|
|
84
|
+
end
|
|
85
|
+
missing = (REQUIRED_FUNCTIONS + version_functions).reject { |name| respond_to?(name) }
|
|
86
|
+
raise LoadError, "Loaded HDF5 library is missing required APIs: #{missing.join(', ')}" unless missing.empty?
|
|
87
|
+
|
|
88
|
+
CALL_LOCK = Monitor.new
|
|
89
|
+
synchronized_calls = Module.new
|
|
90
|
+
singleton_methods.grep(/^H5/).each do |name|
|
|
91
|
+
synchronized_calls.define_method(name) do |*args, **kwargs, &block|
|
|
92
|
+
CALL_LOCK.synchronize { super(*args, **kwargs, &block) }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
singleton_class.prepend(synchronized_calls)
|
|
58
96
|
end
|
|
59
97
|
end
|