如何通过h5py读取v7.3 mat文件?

19

我有一个由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>

你找到了这个问题的实际解决方案吗? - Pastafarian
我有一个类似的问题,并且有部分解决方案:https://dev59.com/r4rda4cB1Zd3GeqPHxvw#29856030 - CodyF
6个回答

16

使用h5py处理Matlab 7.3文件格式并不是非常容易的,它依赖于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'

4

visitvisititems是快速查看文件总体结构的方法:

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)  # https://dev59.com/O2Ei5IYBdhLWcg3wIJFH
    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]

0

我知道两种解决方案(其中一种是我自己制作的,如果*.mat文件非常大或非常深,则效果更好),可以抽象出您与h5py库的直接交互。

  • hdf5storage包,它得到了很好的维护,并旨在帮助将v7.3保存的mat文件加载到Python中
  • 我的自己的mat文件加载器,我编写它来克服即使最新版本(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字典,其键是结构的字段,而将它们转换为具有各种数据类型存储字段的numpy对象数组。

我还注意到数组的索引行为与您从Matlab方法中期望的不同。具体来说,在Matlab中,为了获取第二个结构的name字段,您将对structure进行索引:

[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 mat文件。最后需要指出的是,在处理大于500MB的文件时,我发现hdf5storage包在加载时会卡住,而我的包则不会(尽管仍需花费约1.5分钟完成加载)。


0
我会先启动解释器并在struarray上运行help。这应该会给你足够的信息来开始工作。如果失败了,你可以通过print __dict__属性来转储任何Python对象的属性。

0

非常抱歉,我认为从Matlab外部获取单元格/结构的内容将会是相当具有挑战性的。如果您查看生成的文件(例如使用HDFView),您将会看到有许多交叉引用,而且没有明显的方法可以继续进行。

如果您坚持使用简单的数字数组,那么它可以正常工作。如果您有包含数字数组的小型单元格数组,您可以将它们转换为单独的变量(即cellcontents1、cellcontents2等),这通常只需要几行代码,并允许它们直接保存和加载。因此,在您的示例中,我会保存一个带有变量name1、name2、name3、id1、id2、id3 ...等的文件。

编辑:您在问题中指定了h5py,所以这就是我回答的内容,但值得一提的是,使用scipy.io.loadmat,您应该能够将原始变量转换为numpy等效变量(例如对象数组)。


5
谢谢!我已经为这个问题努力了几天。我总是得到像 <HDF5 object reference> 这样的东西,而不是真正的值。然而,scipy.io.loadmat 在 v7.3 格式的 mat 文件上无法使用。 - Eastsun

-1

这真的是Matlab 7.3和h5py的一个问题。 我的技巧是将类型转换为numpy数组。 例如,

np.array(data['data'])

将解决您在'data'字段方面的问题。


不起作用。只是在现有的数组层上添加了另一个数组层。例如:array([[<HDF5 object reference>, <HDF5 object reference>, <HDF5 object reference>]], dtype=object) 而现有数据的类型是 h5py._hl.dataset.Dataset - Pastafarian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接