访问 HDF5 文件中的 Fletcher-32 校验和

Accessing Fletcher-32 checksum in HDF5 file

假设我想检查一个特定的 H5 文件是否是我认为的那个文件,并且在我没有查看的时候没有更改某些数据集。我已经打开了 Fletcher-32 过滤器。我想知道是否有某种方法可以访问存储在 H5 文件中的校验和

明确地说,我不想重新计算校验和,我假设数据与校验和一致,并且我不希望有任何恶意;我只是想要一种快速查看并列出校验和的方法——然后稍后再查看以确保我的列表没有以某种方式与数据不同步。理想情况下,我想通过 h5py 接口来执行此操作,但 C 接口至少可以让我从某个地方开始。

我的用例基本上是这样的:我有一个 H5 文件的数据库,我想确保 数据集 的 none 在没有数据库了解它。我不在乎——比方说——一个属性是否被更改或添加,这意味着文件大小、修改时间和 MD5 和都没有用。例如,我可能意识到某些缩放比例缩小了 2 倍,进入并更改一个数据集中的那些位而不更改数据集的形状甚至文件中的字节数——但随后无法更新数据库原因或其他。我需要能够检测到这种变化。由于 Fletcher-32 已经由 HDF5 计算我们数据的每次更改,这将非常方便。

基本上,我只是要求可以实现此目的的最高级别 API 调用。


我在缓冲区的 HDF5 源代码 here where it reads the stored checksum — evidently the last 4 bytes 中找到了一处。

根据这个事实,从 HDF5 1.10.2 和 h5py 2.10 开始,似乎有 答案。但它仍然没有我想要的那么快 — 大概是因为它正在读取每个块中的所有字节,可能由于需要不断为所有这些读取分配新缓冲区而加剧。

本质上,我们想要绕过任何过滤器(压缩等),读取原始数据块的最后 4 个字节,并将它们解释为一个无符号的 32 位整数。 h5py中的read_direct_chunk是v 2.10新增的,对应HDF5函数H5D_READ_CHUNK.

这是一些简单的示例代码,假设 test.h5 有一个名为 data 的二维数据集。

import numpy as np
import h5py

with h5py.File('test.h5', 'r') as f:
    ds = f['data']
    n_chunks_0 = int(np.ceil(ds.shape[0] / ds.chunks[0]))
    n_chunks_1 = int(np.ceil(ds.shape[1] / ds.chunks[1]))
    checksums = np.empty((n_chunks_0, n_chunks_1), dtype=np.uint32)
    for i in range(n_chunks_0):
        for j in range(n_chunks_1):
            filter_mask, raw_data_bytes = d.id.read_direct_chunk((i, j))
            checksums[i, j] = np.frombuffer(raw_data_bytes[-4:], dtype=np.uint32)[0]

请注意,可能存在一些我没有考虑的字节顺序问题。

无论如何,问题仍然存在:有什么好的 API 只获取最后 4 位,而不是整个块?

提前致歉;根据我能找到的信息,这是一个不完整的答案。根据我对 HDF5、h5py 和 PyTables 文档的阅读,您无法直接访问校验和值(使用 Python 或任何其他语言)。
这是我对 HDF5 校验和行为的理解:

  • 数据在写入时进行校验和计算。
  • 为每个数据集块计算并存储校验和。
  • 读取数据集(块)时检查数据集是否损坏。
  • 将块保存的校验和与读取时计算的值进行比较。

考虑到这个限制,我看不出你如何能按照你的建议去做。

也就是说,有一种方法可以在对数据进行操作之前查看数据并验证完整性。请参阅下面的代码。它创建一个包含 4 个数据集的文件:2 个有 fletcher32=True,另外 2 个没有。然后它使用 visititems() 递归访问文件中的每个节点(调用 def check_fletcher)。调用的例程检查节点是否为数据集并且 fletcher32=True。如果为真,它会尝试读取数据集。如果读取失败,它将发出错误(您可以捕获)。不幸的是,我不知道如何破坏数据集来测试代码的 except: 部分。也许这会给你一些想法。

import numpy as np
import h5py

def check_fletcher(name, node):
    if isinstance(node, h5py.Dataset) and node.fletcher32:
       print (name,': ', end = '')
       try:
           test = node[:]
           print ('test successful')
       except:
           print ('test failed')

##################################    
data = np.random.rand(20, 20, 20)

with h5py.File('SO_62946682.h5','w') as h5f:

    group = h5f.create_group('data')
    
    ds1 = group.create_dataset('/data1/test1', data=data, fletcher32=True)
    print (ds1.name, ':', ds1.fletcher32)
   
    ds2 = group.create_dataset('/data2/test2', data=data)
    print (ds2.name, ':', ds2.fletcher32)

    ds3 = group.create_dataset('/data3/test3', data=data, fletcher32=True)
    print (ds3.name, ':', ds3.fletcher32)

    ds4 = group.create_dataset('/data4/test4', data=data)
    print (ds4.name, ':', ds4.fletcher32)

    h5f.visititems(check_fletcher)

有一个非常新的界面,可以让我做我想做的事。它是在 HDF5 v1.10.5 and will be in h5py 3.0 中引入的——特别是 (H5D)get_num_chunks 和 (H5D)get_chunk_info 函数。

这是一个简单的示例,展示了如何使用这些函数获取 test.h5data 数据集中的 每个 块的校验和。请注意,我们需要 h5py 功能和 seek/read 功能——这就是为什么我使用这种奇怪的方式打开文件的原因。

import numpy as np
import h5py

with open('test.h5', 'rb') as stream:
    with h5py.File(stream, 'r') as f:
        ds = f['data']
        assert ds.fletcher32, ('Dataset does not have Fletcher-32 checksums')
        checksums = np.zeros((ds.id.get_num_chunks(),), dtype=np.uint32)
        for i in range(checksums.size):
            chunk_info = ds.id.get_chunk_info(i)
            offset = chunk_info.byte_offset + chunk_info.size - 4
            stream.seek(offset, 0)
            checksums[i] = np.frombuffer(stream.read(4), dtype=np.uint32)[0]

此代码适用于 h5py 的当前 master 分支。结果与我在上面的问题中添加的代码的结果一致。但是除了块信息之外,此代码每个块仅读取 4 个字节,因此可能尽可能高效。