我有一个由 matlab 创建的结构体数组,并存储在 v7.3 格式的 mat 文件中:
struArray = struct('name', {'one', 'two', 'three'},
'id', {1,2,3},
'data', {[1:10], [3:9], [0]})
save('test.mat', 'struArray', '-v7.3')
现在我想使用 h5py 通过 python 读取这个文件:
data = h5py.File('test.mat')
struArray = data['/struArray']
我不知道如何从
struArray
中一一获取结构体数据:
for index in range(<the size of struArray>):
elem = <the index th struct in struArray>
name = <the name of elem>
id = <the id of elem>
data = <the data of elem>
Matlab 7.3 文件格式不太容易与 h5py 一起使用。它依赖于 HDF5 参考,参见。 h5py 参考文档。
>>> import h5py
>>> f = h5py.File('test.mat')
>>> list(f.keys())
['#refs#', 'struArray']
>>> struArray = f['struArray']
>>> struArray['name'][0, 0] # this is the HDF5 reference
<HDF5 object reference>
>>> f[struArray['name'][0, 0]].value # this is the actual data
array([[111],
[110],
[101]], dtype=uint16)
阅读
struArray(i).id
:
>>> f[struArray['id'][0, 0]][0, 0]
1.0
>>> f[struArray['id'][1, 0]][0, 0]
2.0
>>> f[struArray['id'][2, 0]][0, 0]
3.0
请注意,Matlab 将数字存储为大小为 (1, 1) 的数组,因此最终
[0, 0]
来获取该数字。
阅读
struArray(i).data
:
>>> f[struArray['data'][0, 0]].value
array([[ 1.],
[ 2.],
[ 3.],
[ 4.],
[ 5.],
[ 6.],
[ 7.],
[ 8.],
[ 9.],
[ 10.]])
要读取
struArray(i).name
,需要将整数数组转换为字符串:
>>> f[struArray['name'][0, 0]].value.tobytes()[::2].decode()
'one'
>>> f[struArray['name'][1, 0]].value.tobytes()[::2].decode()
'two'
>>> f[struArray['name'][2, 0]].value.tobytes()[::2].decode()
'three'
visit
或 visititems
是查看 h5py
文件整体结构的快速方法:
fs['struArray'].visititems(lambda n,o:print(n, o))
当我在 Octave 生成的文件上运行此命令时
save -hdf5
我得到:
type <HDF5 dataset "type": shape (), type "|S7">
value <HDF5 group "/struArray/value" (3 members)>
value/data <HDF5 group "/struArray/value/data" (2 members)>
value/data/type <HDF5 dataset "type": shape (), type "|S5">
value/data/value <HDF5 group "/struArray/value/data/value" (4 members)>
value/data/value/_0 <HDF5 group "/struArray/value/data/value/_0" (2 members)>
value/data/value/_0/type <HDF5 dataset "type": shape (), type "|S7">
value/data/value/_0/value <HDF5 dataset "value": shape (10, 1), type "<f8">
value/data/value/_1 <HDF5 group "/struArray/value/data/value/_1" (2 members)>
...
value/data/value/dims <HDF5 dataset "dims": shape (2,), type "<i4">
value/id <HDF5 group "/struArray/value/id" (2 members)>
value/id/type <HDF5 dataset "type": shape (), type "|S5">
value/id/value <HDF5 group "/struArray/value/id/value" (4 members)>
value/id/value/_0 <HDF5 group "/struArray/value/id/value/_0" (2 members)>
...
value/id/value/_2/value <HDF5 dataset "value": shape (), type "<f8">
value/id/value/dims <HDF5 dataset "dims": shape (2,), type "<i4">
value/name <HDF5 group "/struArray/value/name" (2 members)>
...
value/name/value/dims <HDF5 dataset "dims": shape (2,), type "<i4">
这可能与 MATLAB 7.3 生成的内容不同,但它给出了结构复杂性的想法。
更精细的回调可以显示值,并且可以作为重新创建 Python 对象(字典、列表等)的起点。
def callback(name, obj):
if name.endswith('type'):
print('type:', obj.value)
elif name.endswith('value'):
if type(obj).__name__=='Dataset':
print(obj.value.T) # http://stackoverflow.com/questions/21624653
elif name.endswith('dims'):
print('dims:', obj.value)
else:
print('name:', name)
fs.visititems(callback)
产生:
name: struArray
type: b'struct'
name: struArray/value/data
type: b'cell'
name: struArray/value/data/value/_0
type: b'matrix'
[[ 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.]]
name: struArray/value/data/value/_1
type: b'matrix'
[[ 3. 4. 5. 6. 7. 8. 9.]]
name: struArray/value/data/value/_2
type: b'scalar'
0.0
dims: [3 1]
name: struArray/value/id
type: b'cell'
name: struArray/value/id/value/_0
type: b'scalar'
1.0
...
dims: [3 1]
name: struArray/value/name
type: b'cell'
name: struArray/value/name/value/_0
type: b'sq_string'
[[111 110 101]]
...
dims: [3 1]
我很抱歉,但我认为从 Matlab 之外获取细胞/结构的内容将是相当具有挑战性的。如果您查看生成的文件(例如使用 HDFView),您会发现有很多交叉引用,并且没有明显的方法可以继续。
如果您坚持使用简单的数字数组,它就可以正常工作。如果您有包含数值数组的小型元胞数组,您可以将它们转换为单独的变量(即 cellcontents1、cellcontents2 等),这些变量通常只有几行,并允许直接保存和加载它们。因此,在您的示例中,我将使用 vars
name1, name2, name3, id1, id2, id3 ...
等保存文件。
编辑:您在问题中指定了 h5py,这就是我的回答,但值得一提的是,使用
scipy.io.loadmat
您应该能够将原始变量转换为 numpy 等效项(例如对象数组)。
我首先启动解释器并在
help
上运行 struarray
。它应该为您提供足够的信息来帮助您入门。如果做不到这一点,您可以通过 print
ing __dict__
属性来转储任何 Python 对象的属性。
我知道两种解决方案(其中一种是我制作的,如果
*.mat
文件非常大或非常深,效果会更好),它可以抽象出您与 h5py
库的直接交互。
hdf5storage
包,维护良好,旨在帮助将 v7.3 保存的 matfile 加载到 Python 中0.2.0
的最新版本(hdf5storage
)也可以加载大型(~500Mb)和/或深数组(我实际上不确定哪个有两个原因导致问题)假设您已将这两个包下载到可以将它们加载到 Python 中的位置,您可以看到它们为您的示例生成类似的输出
'test.mat'
:
In [1]: pyInMine = LoadMatFile('test.mat')
In [2]: pyInHdf5 = hdf5.loadmat('test.mat')
In [3]: pyInMine()
Out[3]: dict_keys(['struArray'])
In [4]: pyInMine['struArray'].keys()
Out[4]: dict_keys(['data', 'id', 'name'])
In [5]: pyInHdf5.keys()
Out[5]: dict_keys(['struArray'])
In [6]: pyInHdf5['struArray'].dtype
Out[6]: dtype([('name', 'O'), ('id', '<f8', (1, 1)), ('data', 'O')])
In [7]: pyInHdf5['struArray']['data']
Out[7 ]:
array([[array([[ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.]]),
array([[3., 4., 5., 6., 7., 8., 9.]]), array([[0.]])]],
dtype=object)
In [8]: pyInMine['struArray']['data']
Out[8]:
array([[array([[ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.]]),
array([[3., 4., 5., 6., 7., 8., 9.]]), array([[0.]])]],
dtype=object)
最大的区别是我的库将 Matlab 中的结构体数组转换为 Python 字典,其键是结构体的字段,而
hdf5storage
将它们转换为 numpy
具有存储字段的各种数据类型的对象数组。
我还注意到,数组的索引行为与您期望的 Matlab 方法不同。具体来说,在 Matlab 中,为了获取第二个结构的
name
字段,您需要对 struct 进行索引:
[Matlab] >> struArray(2).name`
[Matlab] >> 'two'
在我的包中,您必须首先抓住字段,然后然后索引:
In [9]: pyInMine['struArray'].shape
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-64-a2f85945642b> in <module>
----> 1 pyInMine['struArray'].shape
AttributeError: 'dict' object has no attribute 'shape'
In [10]: pyInMine['struArray']['name'].shape
Out[10]: (1, 3)
In [11]: pyInMine['struArray']['name'][0,1]
Out[11]: 'two'
hdf5storage
包更好一点,可以让您索引结构然后抓取字段,反之亦然,因为结构化numpy
对象数组的工作方式:
In [12]: pyInHdf5['struArray'].shape
Out[12]: (1, 3)
In [13]: pyInHdf5['struArray'][0,1]['name']
Out[13]: array([['two']], dtype='<U3')
In [14]: pyInHdf5['struArray']['name'].shape
Out[14]: (1, 3)
In [15]: pyInHdf5['struArray']['name'][0,1]
Out[15]: array([['two']], dtype='<U3')
同样,这两个包处理最终输出的方式略有不同,但总的来说,它们都非常擅长读取 v7.3 matfiles。最后的想法是,在大约 500MB+ 文件的情况下,我发现
hdf5storage
包在加载时挂起,而我的包则没有(尽管仍然需要大约 1.5 分钟才能完成加载)。
我使用了mat73包,参见mat73 github。它可以通过 pip 安装,并负责正确加载 .mat 文件,类似于 scipy.io 过去的做法。
data_dict = mat73.loadmat('data.mat', use_attrdict=True)
返回一个数据字典,该字典正确返回.mat文件的结构。
确实是Matlab 7.3和h5py的问题。 我的技巧是将
h5py._hl.dataset.Dataset
类型转换为 numpy
数组。
例如,
np.array(data['data'])
将通过
'data'
字段解决您的问题。