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/file.rb
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
module HDF5
|
|
2
2
|
class File
|
|
3
|
+
include Hierarchy
|
|
4
|
+
|
|
3
5
|
H5F_ACC_RDONLY = 0x0000
|
|
4
6
|
H5F_ACC_RDWR = 0x0001
|
|
5
7
|
H5F_ACC_TRUNC = 0x0002
|
|
8
|
+
H5F_ACC_EXCL = 0x0004
|
|
6
9
|
|
|
7
10
|
class << self
|
|
8
11
|
def create(filename, flags = H5F_ACC_TRUNC)
|
|
@@ -17,8 +20,9 @@ module HDF5
|
|
|
17
20
|
end
|
|
18
21
|
end
|
|
19
22
|
|
|
20
|
-
def open(filename, mode =
|
|
21
|
-
|
|
23
|
+
def open(filename, mode = 'r')
|
|
24
|
+
file_id, flags = open_file(filename, mode)
|
|
25
|
+
file = from_id(file_id, filename, flags)
|
|
22
26
|
return file unless block_given?
|
|
23
27
|
|
|
24
28
|
begin
|
|
@@ -30,6 +34,29 @@ module HDF5
|
|
|
30
34
|
|
|
31
35
|
private
|
|
32
36
|
|
|
37
|
+
def open_file(filename, mode)
|
|
38
|
+
return [HDF5::FFI.H5Fopen(filename, mode, HDF5::DEFAULT_PROPERTY_LIST), mode] if mode.is_a?(Integer)
|
|
39
|
+
|
|
40
|
+
case mode
|
|
41
|
+
when 'r'
|
|
42
|
+
[HDF5::FFI.H5Fopen(filename, H5F_ACC_RDONLY, HDF5::DEFAULT_PROPERTY_LIST), H5F_ACC_RDONLY]
|
|
43
|
+
when 'r+'
|
|
44
|
+
[HDF5::FFI.H5Fopen(filename, H5F_ACC_RDWR, HDF5::DEFAULT_PROPERTY_LIST), H5F_ACC_RDWR]
|
|
45
|
+
when 'w'
|
|
46
|
+
[HDF5::FFI.H5Fcreate(filename, H5F_ACC_TRUNC, HDF5::DEFAULT_PROPERTY_LIST, HDF5::DEFAULT_PROPERTY_LIST),
|
|
47
|
+
H5F_ACC_TRUNC]
|
|
48
|
+
when 'x'
|
|
49
|
+
[HDF5::FFI.H5Fcreate(filename, H5F_ACC_EXCL, HDF5::DEFAULT_PROPERTY_LIST, HDF5::DEFAULT_PROPERTY_LIST),
|
|
50
|
+
H5F_ACC_EXCL]
|
|
51
|
+
when 'a'
|
|
52
|
+
file_id = HDF5::FFI.H5Fcreate(filename, H5F_ACC_EXCL, HDF5::DEFAULT_PROPERTY_LIST, HDF5::DEFAULT_PROPERTY_LIST)
|
|
53
|
+
file_id = HDF5::FFI.H5Fopen(filename, H5F_ACC_RDWR, HDF5::DEFAULT_PROPERTY_LIST) if file_id < 0
|
|
54
|
+
[file_id, H5F_ACC_RDWR]
|
|
55
|
+
else
|
|
56
|
+
raise ArgumentError, "Unsupported file mode: #{mode.inspect}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
33
60
|
def from_id(file_id, filename, mode)
|
|
34
61
|
file = allocate
|
|
35
62
|
file.send(:initialize_from_id, file_id, filename, mode)
|
|
@@ -42,21 +69,54 @@ module HDF5
|
|
|
42
69
|
end
|
|
43
70
|
|
|
44
71
|
def close
|
|
45
|
-
return if @file_id.nil?
|
|
72
|
+
return if @file_id.nil? || @context.closed?
|
|
46
73
|
|
|
47
|
-
|
|
74
|
+
@context.close_file
|
|
48
75
|
@file_id = nil
|
|
49
76
|
end
|
|
50
77
|
|
|
78
|
+
def closed?
|
|
79
|
+
@file_id.nil? || @context.closed?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def flush
|
|
83
|
+
ensure_open!
|
|
84
|
+
status = HDF5::FFI.H5Fflush(@file_id, :H5F_SCOPE_GLOBAL)
|
|
85
|
+
raise HDF5::Error, 'Failed to flush file' if status < 0
|
|
86
|
+
|
|
87
|
+
self
|
|
88
|
+
end
|
|
89
|
+
|
|
51
90
|
def create_group(name, &block)
|
|
52
|
-
|
|
91
|
+
group = @context.synchronize do
|
|
92
|
+
ensure_open!
|
|
93
|
+
Group.create(@file_id, name, @context)
|
|
94
|
+
end
|
|
95
|
+
return group unless block
|
|
96
|
+
|
|
97
|
+
begin
|
|
98
|
+
block.call(group)
|
|
99
|
+
ensure
|
|
100
|
+
group.close
|
|
101
|
+
end
|
|
53
102
|
end
|
|
54
103
|
|
|
55
|
-
def create_dataset(name, data, &block)
|
|
56
|
-
|
|
104
|
+
def create_dataset(name, data = nil, **options, &block)
|
|
105
|
+
dataset = @context.synchronize do
|
|
106
|
+
ensure_open!
|
|
107
|
+
Dataset.create(@file_id, name, data, context: @context, **options)
|
|
108
|
+
end
|
|
109
|
+
return dataset unless block
|
|
110
|
+
|
|
111
|
+
begin
|
|
112
|
+
block.call(dataset)
|
|
113
|
+
ensure
|
|
114
|
+
dataset.close
|
|
115
|
+
end
|
|
57
116
|
end
|
|
58
117
|
|
|
59
118
|
def list_entries
|
|
119
|
+
ensure_open!
|
|
60
120
|
list = []
|
|
61
121
|
callback = ::FFI::Function.new(:int, %i[int64_t string pointer pointer]) do |_, name, _, _|
|
|
62
122
|
list << name
|
|
@@ -74,27 +134,41 @@ module HDF5
|
|
|
74
134
|
end
|
|
75
135
|
|
|
76
136
|
def [](name)
|
|
137
|
+
ensure_open!
|
|
77
138
|
if group?(name)
|
|
78
|
-
Group.open(@file_id, name)
|
|
139
|
+
Group.open(@file_id, name, @context)
|
|
79
140
|
elsif dataset?(name)
|
|
80
|
-
Dataset.open(@file_id, name)
|
|
141
|
+
Dataset.open(@file_id, name, context: @context)
|
|
81
142
|
else
|
|
82
143
|
raise HDF5::Error, "Group or dataset not found: #{name}"
|
|
83
144
|
end
|
|
84
145
|
end
|
|
85
146
|
|
|
86
147
|
def attrs
|
|
87
|
-
|
|
148
|
+
ensure_open!
|
|
149
|
+
@attrs ||= AttributeManager.new(@file_id, @context)
|
|
88
150
|
end
|
|
89
151
|
|
|
90
152
|
private
|
|
91
153
|
|
|
154
|
+
def hdf5_id
|
|
155
|
+
ensure_open!
|
|
156
|
+
@file_id
|
|
157
|
+
end
|
|
158
|
+
|
|
92
159
|
def initialize_from_id(file_id, filename, mode)
|
|
93
160
|
raise HDF5::Error, "Failed to open file: #{filename}" if file_id < 0
|
|
94
161
|
|
|
95
162
|
@filename = filename
|
|
96
163
|
@mode = mode
|
|
97
164
|
@file_id = file_id
|
|
165
|
+
@context = FileContext.new(file_id)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def ensure_open!
|
|
169
|
+
raise ClosedError, 'HDF5 file is closed' if @file_id.nil?
|
|
170
|
+
|
|
171
|
+
@context.ensure_open!(@file_id)
|
|
98
172
|
end
|
|
99
173
|
|
|
100
174
|
def group?(name)
|
|
@@ -122,5 +196,7 @@ module HDF5
|
|
|
122
196
|
end
|
|
123
197
|
info[:type] == :H5O_TYPE_DATASET
|
|
124
198
|
end
|
|
199
|
+
|
|
200
|
+
prepend FileContext.guard(:flush, :list_entries, :[], :attrs, :close, :closed?)
|
|
125
201
|
end
|
|
126
202
|
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module HDF5
|
|
2
|
+
class FileContext
|
|
3
|
+
CLOSE_FUNCTIONS = {
|
|
4
|
+
file: :H5Fclose,
|
|
5
|
+
group: :H5Gclose,
|
|
6
|
+
dataset: :H5Dclose
|
|
7
|
+
}.freeze
|
|
8
|
+
|
|
9
|
+
def initialize(file_id)
|
|
10
|
+
@file_id = file_id
|
|
11
|
+
@handles = { file_id => :file }
|
|
12
|
+
@closed = false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def register(id, type)
|
|
16
|
+
raise ClosedError, 'HDF5 file is closed' if closed?
|
|
17
|
+
|
|
18
|
+
@handles[id] = type
|
|
19
|
+
id
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def close(id)
|
|
23
|
+
synchronize do
|
|
24
|
+
type = @handles[id]
|
|
25
|
+
return if type.nil?
|
|
26
|
+
|
|
27
|
+
status = HDF5::FFI.public_send(CLOSE_FUNCTIONS.fetch(type), id)
|
|
28
|
+
raise HDF5::Error, "Failed to close HDF5 #{type}" if status < 0
|
|
29
|
+
|
|
30
|
+
@handles.delete(id)
|
|
31
|
+
@closed = true if type == :file
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def close_file
|
|
37
|
+
synchronize do
|
|
38
|
+
return if closed?
|
|
39
|
+
|
|
40
|
+
children = @handles.keys.reject { |id| id == @file_id }
|
|
41
|
+
children.reverse_each { |id| close(id) }
|
|
42
|
+
close(@file_id)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def ensure_open!(id = nil)
|
|
47
|
+
raise ClosedError, 'HDF5 file is closed' if closed?
|
|
48
|
+
raise ClosedError, 'HDF5 object is closed' if id && !@handles.key?(id)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def closed?
|
|
52
|
+
@closed
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def synchronize(&block)
|
|
56
|
+
HDF5::FFI::CALL_LOCK.synchronize(&block)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.guard(*methods)
|
|
60
|
+
Module.new do
|
|
61
|
+
methods.each do |method|
|
|
62
|
+
define_method(method) do |*args, **kwargs, &block|
|
|
63
|
+
lock = @context || HDF5::FFI::CALL_LOCK
|
|
64
|
+
lock.synchronize { super(*args, **kwargs, &block) }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
data/lib/hdf5/group.rb
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
module HDF5
|
|
2
2
|
class Group
|
|
3
|
+
include Hierarchy
|
|
4
|
+
|
|
3
5
|
class << self
|
|
4
|
-
def create(parent_id, name)
|
|
6
|
+
def create(parent_id, name, context = nil)
|
|
5
7
|
group = from_id(
|
|
6
8
|
HDF5::FFI.H5Gcreate2(parent_id, name, HDF5::DEFAULT_PROPERTY_LIST, HDF5::DEFAULT_PROPERTY_LIST,
|
|
7
|
-
HDF5::DEFAULT_PROPERTY_LIST), name
|
|
9
|
+
HDF5::DEFAULT_PROPERTY_LIST), name, context
|
|
8
10
|
)
|
|
9
11
|
return group unless block_given?
|
|
10
12
|
|
|
@@ -15,8 +17,8 @@ module HDF5
|
|
|
15
17
|
end
|
|
16
18
|
end
|
|
17
19
|
|
|
18
|
-
def open(parent_id, name)
|
|
19
|
-
group = from_id(HDF5::FFI.H5Gopen2(parent_id, name, HDF5::DEFAULT_PROPERTY_LIST), name)
|
|
20
|
+
def open(parent_id, name, context = nil)
|
|
21
|
+
group = from_id(HDF5::FFI.H5Gopen2(parent_id, name, HDF5::DEFAULT_PROPERTY_LIST), name, context)
|
|
20
22
|
return group unless block_given?
|
|
21
23
|
|
|
22
24
|
begin
|
|
@@ -28,33 +30,58 @@ module HDF5
|
|
|
28
30
|
|
|
29
31
|
private
|
|
30
32
|
|
|
31
|
-
def from_id(group_id, name)
|
|
33
|
+
def from_id(group_id, name, context)
|
|
32
34
|
group = allocate
|
|
33
|
-
group.send(:initialize_from_id, group_id, name)
|
|
35
|
+
group.send(:initialize_from_id, group_id, name, context)
|
|
34
36
|
group
|
|
35
37
|
end
|
|
36
38
|
end
|
|
37
39
|
|
|
38
40
|
def initialize(file_id, name)
|
|
39
|
-
initialize_from_id(HDF5::FFI.H5Gopen2(file_id, name, HDF5::DEFAULT_PROPERTY_LIST), name)
|
|
41
|
+
initialize_from_id(HDF5::FFI.H5Gopen2(file_id, name, HDF5::DEFAULT_PROPERTY_LIST), name, nil)
|
|
40
42
|
end
|
|
41
43
|
|
|
42
44
|
def close
|
|
43
45
|
return if @group_id.nil?
|
|
44
46
|
|
|
45
|
-
HDF5::FFI.H5Gclose(@group_id)
|
|
47
|
+
@context ? @context.close(@group_id) : HDF5::FFI.H5Gclose(@group_id)
|
|
46
48
|
@group_id = nil
|
|
47
49
|
end
|
|
48
50
|
|
|
51
|
+
def closed?
|
|
52
|
+
@group_id.nil? || (@context && @context.closed?)
|
|
53
|
+
end
|
|
54
|
+
|
|
49
55
|
def create_group(name, &block)
|
|
50
|
-
|
|
56
|
+
group = HDF5::FFI::CALL_LOCK.synchronize do
|
|
57
|
+
ensure_open!
|
|
58
|
+
self.class.create(@group_id, name, @context)
|
|
59
|
+
end
|
|
60
|
+
return group unless block
|
|
61
|
+
|
|
62
|
+
begin
|
|
63
|
+
block.call(group)
|
|
64
|
+
ensure
|
|
65
|
+
group.close
|
|
66
|
+
end
|
|
51
67
|
end
|
|
52
68
|
|
|
53
|
-
def create_dataset(name, data, &block)
|
|
54
|
-
|
|
69
|
+
def create_dataset(name, data = nil, **options, &block)
|
|
70
|
+
dataset = HDF5::FFI::CALL_LOCK.synchronize do
|
|
71
|
+
ensure_open!
|
|
72
|
+
Dataset.create(@group_id, name, data, context: @context, **options)
|
|
73
|
+
end
|
|
74
|
+
return dataset unless block
|
|
75
|
+
|
|
76
|
+
begin
|
|
77
|
+
block.call(dataset)
|
|
78
|
+
ensure
|
|
79
|
+
dataset.close
|
|
80
|
+
end
|
|
55
81
|
end
|
|
56
82
|
|
|
57
83
|
def list_entries
|
|
84
|
+
ensure_open!
|
|
58
85
|
entries = []
|
|
59
86
|
callback = ::FFI::Function.new(:int, %i[int64_t string pointer pointer]) do |_, name, _, _|
|
|
60
87
|
entries << name
|
|
@@ -76,26 +103,41 @@ module HDF5
|
|
|
76
103
|
end
|
|
77
104
|
|
|
78
105
|
def [](name)
|
|
106
|
+
ensure_open!
|
|
79
107
|
if group?(name)
|
|
80
|
-
self.class.open(@group_id, name)
|
|
108
|
+
self.class.open(@group_id, name, @context)
|
|
81
109
|
elsif dataset?(name)
|
|
82
|
-
Dataset.open(@group_id, name)
|
|
110
|
+
Dataset.open(@group_id, name, context: @context)
|
|
83
111
|
else
|
|
84
112
|
raise HDF5::Error, "Group or dataset not found: #{name}"
|
|
85
113
|
end
|
|
86
114
|
end
|
|
87
115
|
|
|
88
116
|
def attrs
|
|
89
|
-
|
|
117
|
+
ensure_open!
|
|
118
|
+
@attrs ||= AttributeManager.new(@group_id, @context)
|
|
90
119
|
end
|
|
91
120
|
|
|
92
121
|
private
|
|
93
122
|
|
|
94
|
-
def
|
|
123
|
+
def hdf5_id
|
|
124
|
+
ensure_open!
|
|
125
|
+
@group_id
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def initialize_from_id(group_id, name, context)
|
|
95
129
|
raise HDF5::Error, "Failed to open group: #{name}" if group_id < 0
|
|
96
130
|
|
|
97
131
|
@group_id = group_id
|
|
98
132
|
@name = name
|
|
133
|
+
@context = context
|
|
134
|
+
@context.register(group_id, :group) if @context
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def ensure_open!
|
|
138
|
+
raise ClosedError, 'HDF5 group is closed' if @group_id.nil?
|
|
139
|
+
|
|
140
|
+
@context&.ensure_open!(@group_id)
|
|
99
141
|
end
|
|
100
142
|
|
|
101
143
|
def group?(name)
|
|
@@ -123,5 +165,7 @@ module HDF5
|
|
|
123
165
|
end
|
|
124
166
|
info[:type] == :H5O_TYPE_DATASET
|
|
125
167
|
end
|
|
168
|
+
|
|
169
|
+
prepend FileContext.guard(:list_entries, :list_datasets, :[], :attrs, :close, :closed?)
|
|
126
170
|
end
|
|
127
171
|
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
module HDF5
|
|
2
|
+
module Hierarchy
|
|
3
|
+
def keys
|
|
4
|
+
list_entries
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def each_key(&block)
|
|
8
|
+
return enum_for(__method__) unless block
|
|
9
|
+
|
|
10
|
+
keys.each(&block)
|
|
11
|
+
self
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def require_group(path)
|
|
15
|
+
path.split('/').reject(&:empty?).inject(self) do |parent, name|
|
|
16
|
+
if parent.key?(name)
|
|
17
|
+
child = parent[name]
|
|
18
|
+
raise HDF5::Error, "Existing object is not a group: #{name}" unless child.is_a?(Group)
|
|
19
|
+
|
|
20
|
+
child
|
|
21
|
+
else
|
|
22
|
+
parent.create_group(name)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def open_dataset(name)
|
|
28
|
+
object = self[name]
|
|
29
|
+
raise HDF5::Error, "Object is not a dataset: #{name}" unless object.is_a?(Dataset)
|
|
30
|
+
return object unless block_given?
|
|
31
|
+
|
|
32
|
+
begin
|
|
33
|
+
yield object
|
|
34
|
+
ensure
|
|
35
|
+
object.close
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def key?(name)
|
|
40
|
+
exists = HDF5::FFI.H5Lexists(hdf5_id, name, HDF5::DEFAULT_PROPERTY_LIST)
|
|
41
|
+
raise HDF5::Error, "Failed to check link existence: #{name}" if exists.negative?
|
|
42
|
+
|
|
43
|
+
exists.positive?
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def delete(name)
|
|
47
|
+
status = HDF5::FFI.H5Ldelete(hdf5_id, name, HDF5::DEFAULT_PROPERTY_LIST)
|
|
48
|
+
raise HDF5::Error, "Failed to delete link: #{name}" if status < 0
|
|
49
|
+
|
|
50
|
+
self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def create_hard_link(target, name)
|
|
54
|
+
status = HDF5::FFI.H5Lcreate_hard(hdf5_id, target, hdf5_id, name, HDF5::DEFAULT_PROPERTY_LIST,
|
|
55
|
+
HDF5::DEFAULT_PROPERTY_LIST)
|
|
56
|
+
raise HDF5::Error, "Failed to create hard link: #{name}" if status < 0
|
|
57
|
+
|
|
58
|
+
self
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def create_soft_link(target, name)
|
|
62
|
+
status = HDF5::FFI.H5Lcreate_soft(target, hdf5_id, name, HDF5::DEFAULT_PROPERTY_LIST,
|
|
63
|
+
HDF5::DEFAULT_PROPERTY_LIST)
|
|
64
|
+
raise HDF5::Error, "Failed to create soft link: #{name}" if status < 0
|
|
65
|
+
|
|
66
|
+
self
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def link_info(name)
|
|
70
|
+
info = HDF5::FFI::MiV == 10 ? HDF5::FFI::H5LInfoT.new : HDF5::FFI::H5LInfo2T.new
|
|
71
|
+
status = if HDF5::FFI::MiV == 10
|
|
72
|
+
HDF5::FFI.H5Lget_info(hdf5_id, name, info, HDF5::DEFAULT_PROPERTY_LIST)
|
|
73
|
+
else
|
|
74
|
+
HDF5::FFI.H5Lget_info2(hdf5_id, name, info, HDF5::DEFAULT_PROPERTY_LIST)
|
|
75
|
+
end
|
|
76
|
+
raise HDF5::Error, "Failed to get link information: #{name}" if status < 0
|
|
77
|
+
|
|
78
|
+
type = info[:type]
|
|
79
|
+
return { type: :hard } if type == :H5L_TYPE_HARD
|
|
80
|
+
|
|
81
|
+
size = info[:u][:val_size]
|
|
82
|
+
buffer = ::FFI::MemoryPointer.new(:char, size)
|
|
83
|
+
status = HDF5::FFI.H5Lget_val(hdf5_id, name, buffer, size, HDF5::DEFAULT_PROPERTY_LIST)
|
|
84
|
+
raise HDF5::Error, "Failed to get link value: #{name}" if status < 0
|
|
85
|
+
|
|
86
|
+
raw = buffer.read_bytes(size)
|
|
87
|
+
return { type: :soft, target: raw.delete_suffix("\0") } if type == :H5L_TYPE_SOFT
|
|
88
|
+
|
|
89
|
+
if type == :H5L_TYPE_EXTERNAL
|
|
90
|
+
filename, path = raw.byteslice(1..).split("\0", 2)
|
|
91
|
+
return { type: :external, filename:, path: path&.delete_suffix("\0") }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
raise UnsupportedTypeError, "Unsupported link type: #{type}"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def move(source, destination)
|
|
98
|
+
status = HDF5::FFI.H5Lmove(hdf5_id, source, hdf5_id, destination, HDF5::DEFAULT_PROPERTY_LIST,
|
|
99
|
+
HDF5::DEFAULT_PROPERTY_LIST)
|
|
100
|
+
raise HDF5::Error, "Failed to move link: #{source}" if status < 0
|
|
101
|
+
|
|
102
|
+
self
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
prepend FileContext.guard(*(instance_methods(false) - %i[each_key open_dataset]))
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
module HDF5
|
|
2
|
+
class Selection
|
|
3
|
+
attr_reader :start, :stride, :count, :result_shape
|
|
4
|
+
|
|
5
|
+
def self.normalize(selection, shape)
|
|
6
|
+
return new(Array.new(shape.length, true), shape) if selection.nil?
|
|
7
|
+
|
|
8
|
+
selectors = selection.is_a?(Array) ? selection.dup : [selection]
|
|
9
|
+
raise IndexError, 'Too many indices for dataset' if selectors.length > shape.length
|
|
10
|
+
|
|
11
|
+
selectors.concat(Array.new(shape.length - selectors.length, true))
|
|
12
|
+
new(selectors, shape)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(selectors, shape)
|
|
16
|
+
@start = []
|
|
17
|
+
@stride = []
|
|
18
|
+
@count = []
|
|
19
|
+
@result_shape = []
|
|
20
|
+
@result_axes = []
|
|
21
|
+
|
|
22
|
+
selectors.zip(shape).each_with_index do |(selector, dimension), axis|
|
|
23
|
+
@result_axes << axis unless selector.is_a?(Integer)
|
|
24
|
+
normalize_axis(selector, dimension)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def scalar?
|
|
29
|
+
result_shape.empty?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def size
|
|
33
|
+
count.inject(1, :*)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def block(ranges)
|
|
37
|
+
result = dup
|
|
38
|
+
starts = start.dup
|
|
39
|
+
counts = count.dup
|
|
40
|
+
@result_axes.zip(ranges).each do |axis, range|
|
|
41
|
+
starts[axis] += range.begin * stride[axis]
|
|
42
|
+
counts[axis] = range.end - range.begin
|
|
43
|
+
end
|
|
44
|
+
result.instance_variable_set(:@start, starts)
|
|
45
|
+
result.instance_variable_set(:@count, counts)
|
|
46
|
+
result.instance_variable_set(:@result_shape, ranges.map { |range| range.end - range.begin })
|
|
47
|
+
result
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def normalize_axis(selector, dimension)
|
|
53
|
+
case selector
|
|
54
|
+
when true
|
|
55
|
+
@start << 0
|
|
56
|
+
@stride << 1
|
|
57
|
+
@count << dimension
|
|
58
|
+
@result_shape << dimension
|
|
59
|
+
when Integer
|
|
60
|
+
index = selector.negative? ? dimension + selector : selector
|
|
61
|
+
raise IndexError, "Index #{selector} is outside dimension of size #{dimension}" unless index.between?(0,
|
|
62
|
+
dimension - 1)
|
|
63
|
+
|
|
64
|
+
@start << index
|
|
65
|
+
@stride << 1
|
|
66
|
+
@count << 1
|
|
67
|
+
when Range
|
|
68
|
+
first, last = normalize_range(selector, dimension)
|
|
69
|
+
@start << first
|
|
70
|
+
@stride << 1
|
|
71
|
+
@count << last - first
|
|
72
|
+
@result_shape << last - first
|
|
73
|
+
when HDF5::Slice
|
|
74
|
+
first, last = normalize_range(selector.range, dimension)
|
|
75
|
+
count = (last - first).fdiv(selector.step).ceil
|
|
76
|
+
@start << first
|
|
77
|
+
@stride << selector.step
|
|
78
|
+
@count << count
|
|
79
|
+
@result_shape << count
|
|
80
|
+
else
|
|
81
|
+
raise IndexError, "Unsupported index: #{selector.inspect}"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def normalize_range(range, dimension)
|
|
86
|
+
first = range.begin.nil? ? 0 : range.begin
|
|
87
|
+
last = range.end.nil? ? dimension : range.end
|
|
88
|
+
first += dimension if first.negative?
|
|
89
|
+
last += dimension if last.negative?
|
|
90
|
+
last += 1 unless range.exclude_end? || range.end.nil?
|
|
91
|
+
|
|
92
|
+
first = [[first, 0].max, dimension].min
|
|
93
|
+
last = [[last, 0].max, dimension].min
|
|
94
|
+
[first, [last, first].max]
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
module HDF5
|
|
2
|
+
module StringCodec
|
|
3
|
+
module_function
|
|
4
|
+
|
|
5
|
+
UTF8 = 1
|
|
6
|
+
VARIABLE_SIZE = (1 << (::FFI.type_size(:size_t) * 8)) - 1
|
|
7
|
+
|
|
8
|
+
def datatype_id
|
|
9
|
+
type_id = HDF5::FFI.H5Tcopy(HDF5::FFI.H5T_C_S1)
|
|
10
|
+
raise HDF5::Error, 'Failed to create string datatype' if type_id < 0
|
|
11
|
+
|
|
12
|
+
check(HDF5::FFI.H5Tset_size(type_id, VARIABLE_SIZE), 'set variable string size')
|
|
13
|
+
check(HDF5::FFI.H5Tset_cset(type_id, UTF8), 'set UTF-8 string encoding')
|
|
14
|
+
type_id
|
|
15
|
+
rescue StandardError
|
|
16
|
+
HDF5::FFI.H5Tclose(type_id) if type_id && type_id >= 0
|
|
17
|
+
raise
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def buffer_for(value)
|
|
21
|
+
buffer_for_values([normalize(value)])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def buffer_for_values(values, encoding: Encoding::UTF_8)
|
|
25
|
+
pointers = values.map { |value| ::FFI::MemoryPointer.from_string(normalize(value, encoding:)) }
|
|
26
|
+
buffer = ::FFI::MemoryPointer.new(:pointer, pointers.length)
|
|
27
|
+
buffer.write_array_of_pointer(pointers)
|
|
28
|
+
[buffer, pointers]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def read(buffer, encoding: Encoding::UTF_8)
|
|
32
|
+
read_values(buffer, 1, [], encoding:)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def read_values(buffer, count, shape, encoding: Encoding::UTF_8)
|
|
36
|
+
values = buffer.read_array_of_pointer(count).map do |pointer|
|
|
37
|
+
next nil if pointer.null?
|
|
38
|
+
|
|
39
|
+
value = pointer.read_string.force_encoding(encoding)
|
|
40
|
+
raise HDF5::Error, "String data is not valid #{encoding.name}" unless value.valid_encoding?
|
|
41
|
+
|
|
42
|
+
value
|
|
43
|
+
end
|
|
44
|
+
return values.first if shape.empty?
|
|
45
|
+
|
|
46
|
+
Numo::RObject.cast(values).reshape(*shape)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def string_data?(value)
|
|
50
|
+
return true if value.is_a?(String)
|
|
51
|
+
|
|
52
|
+
values = if value.is_a?(Numo::RObject)
|
|
53
|
+
value.to_a.flatten
|
|
54
|
+
elsif value.is_a?(Array)
|
|
55
|
+
value.flatten
|
|
56
|
+
else
|
|
57
|
+
[]
|
|
58
|
+
end
|
|
59
|
+
!values.empty? && values.all? { |item| item.is_a?(String) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def normalize_data(value, encoding: Encoding::UTF_8)
|
|
63
|
+
return [[normalize(value, encoding:)], []] if value.is_a?(String)
|
|
64
|
+
return [value.to_a.flatten.map { |item| normalize(item, encoding:) }, value.shape] if value.is_a?(Numo::RObject)
|
|
65
|
+
|
|
66
|
+
shape = array_shape(value)
|
|
67
|
+
[value.flatten.map { |item| normalize(item, encoding:) }, shape]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def array_shape(value)
|
|
71
|
+
return [] unless value.is_a?(Array)
|
|
72
|
+
raise HDF5::Error, 'String data must not be empty' if value.empty?
|
|
73
|
+
|
|
74
|
+
DataHelpers.array_shape(value)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def variable?(type_id)
|
|
78
|
+
HDF5::FFI.H5Tis_variable_str(type_id).positive?
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def encoding_for(type_id)
|
|
82
|
+
case HDF5::FFI.H5Tget_cset(type_id)
|
|
83
|
+
when :H5T_CSET_ASCII then Encoding::US_ASCII
|
|
84
|
+
when :H5T_CSET_UTF8 then Encoding::UTF_8
|
|
85
|
+
else raise UnsupportedTypeError, 'Unsupported HDF5 string encoding'
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def normalize(value, encoding: Encoding::UTF_8)
|
|
90
|
+
raise HDF5::Error, 'String dataset data must be a String' unless value.is_a?(String)
|
|
91
|
+
raise HDF5::Error, 'String data has an invalid encoding' unless value.valid_encoding?
|
|
92
|
+
raise HDF5::Error, 'Variable-length strings cannot contain NUL bytes' if value.include?("\0")
|
|
93
|
+
|
|
94
|
+
result = value.encode(encoding)
|
|
95
|
+
raise HDF5::Error, "String data is not valid #{encoding.name}" unless result.valid_encoding?
|
|
96
|
+
|
|
97
|
+
result
|
|
98
|
+
rescue EncodingError
|
|
99
|
+
raise HDF5::Error, "String data must be representable as #{encoding.name}"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def check(status, operation)
|
|
103
|
+
raise HDF5::Error, "Failed to #{operation}" if status < 0
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
data/lib/hdf5/version.rb
CHANGED