关于C#中读取HDF4文件数据的说明

HDF4也被称为HDF(Hierarchical Data Format),可以存储不同类型的图像和数码数据的文件格式,并且可以在不同类型的机器上传输,同时还有统一处理这种文件格式的函数库。随着技术的发展,现存在两个完全不同的HDF文件版本:HDF4和HDF5,其中HDF4是第一个HDF版本。我们可以通过访问HDF4的官方网站(http://www.hdfgroup.org/products/hdf4/)查看其详细介绍。
csharp-read-hdf4-1
如果只是需要查看HDF4文件数据的话,我们可以从官网上下载并安装HDFView(一个简单的HDF文件查看工具):
csharp-read-hdf4-2
但是如果需要通过编程手段读写HDF4文件数据的话,我们就必须先从官网上下载并安装HDF4的函数库,然后再编程调用该函数库来实现对其的读写。
以下介绍如何通过C#方式读取HDF4文件的属性和数据。

一、安装HDF4函数库:
在HDF4官网下载页(http://www.hdfgroup.org/release4/obtain.html)下载并安装HDF4函数库。官网提供了多个系统平台的编译版本,在这里我们选择“Windows (32-bit)”平台:
csharp-read-hdf4-3
如在安装过程中发现环境变量无法写入,我们可以忽略设置环境变量选项。

二、引用HDF4函数库:
从官网中我们可以发现,现在的HDF4函数库只有提供C++和Fortran访问的版本,要想在C#程序中使用的话,我们需要通过DllImport方式引入。HDF4的函数库主要包含在hdf.dll和mfhdf.dll这两个库中,而常用来读取HDF4中数据的SD接口则在mdhdf.dll库中。

// 定义HDF4 SD接口函数库的文件位置
public const string MFHDF4_DLL = @"C:\Program Files (x86)\HDF_Group\HDF\4.2.11\bin\mfhdf.dll";

// 引入SDstart方法
[DllImport(MFHDF4_DLL, EntryPoint = "SDstart", CallingConvention = CallingConvention.Cdecl)]
public static extern int SDstart(string filename, int access_mode);

// 引入SDfindattr方法
[DllImport(MFHDF4_DLL, EntryPoint = "SDfindattr", CallingConvention = CallingConvention.Cdecl)]
public static extern int SDfindattr(int obj_id, string attr_name);

// 引入SDreadattr方法(字符串类型属性)
[DllImport(MFHDF4_DLL, EntryPoint = "SDreadattr", CallingConvention = CallingConvention.Cdecl)]
public static extern int SDreadattr(int obj_id, int attr_index, StringBuilder attr_buffer);

// 引入SDreadattr方法(单精度浮点类型属性)
[DllImport(MFHDF4_DLL, EntryPoint = "SDreadattr", CallingConvention = CallingConvention.Cdecl)]
public static extern int SDreadattr(int obj_id, int attr_index, float[] attr_buffer);

// 引入SDnametoindex方法
[DllImport(MFHDF4_DLL, EntryPoint = "SDnametoindex", CallingConvention = CallingConvention.Cdecl)]
public static extern int SDnametoindex(int sd_id, string sds_name);

// 引入SDselect方法
[DllImport(MFHDF4_DLL, EntryPoint = "SDselect", CallingConvention = CallingConvention.Cdecl)]
public static extern int SDselect(int sd_id, int sds_index);

// 引入SDgetinfo方法
[DllImport(MFHDF4_DLL, EntryPoint = "SDgetinfo", CallingConvention = CallingConvention.Cdecl)]
public static extern int SDgetinfo(int sds_id, StringBuilder sds_name, int[] rank, int[] dimsizes, int[] ntype, int[] num_attrs);

// 引入SDreaddata方法
[DllImport(MFHDF4_DLL, EntryPoint = "SDreaddata", CallingConvention = CallingConvention.Cdecl)]
public static extern int SDreaddata(int sds_id, int[] start, int[] stride, int[] edge, short[,] buffer);

// 引入SDendaccess方法
[DllImport(MFHDF4_DLL, EntryPoint = "SDendaccess", CallingConvention = CallingConvention.Cdecl)]
public static extern int SDendaccess(int sds_id);

// 引入SDend方法
[DllImport(MFHDF4_DLL, EntryPoint = "SDend", CallingConvention = CallingConvention.Cdecl)]
public static extern int SDend(int sd_id);

以上我们通过DllImport的方式引入读取HDF4文件所需要用到的相关SD接口函数,其中EntryPoint属性为对应的函数名称,CallingConvention属性必须设置为CallingConvention.Cdecl。由于个别函数返回的数据是通过指针引用在函数参数中的,因此我们需要通过StringBuilder来接收字符串数据,通过值类型数组(如float[]、int[]、short[]等)来接收数值型数据和数组型数据(不能通过ref或out参数返回)。

三、读取HDF4属性和数据:

// 要读取的HDF4文件
string hdf4File = "C:/test.hdf";
// 字符串类型属性
string sensor;
// 单精度浮点类型属性
float slope;
// 数据行数
int row;
// 数据列数
int column;
// 数据集数据
short[,] data;
// 函数调用状态
int status;
// 只读方式打开HDF4文件
int sd_id = SDstart(hdf4File, 1);
if (sd_id != -1)
{
    // 查找属性Sensor的索引号
    int attr_index = SDfindattr(sd_id, "Sensor");
    if (attr_index != -1)
    {
        // 读取Sensor属性,通过StringBuilder接收字符串数据
        StringBuilder buffer = new StringBuilder();
        status = SDreadattr(sd_id, attr_index, buffer);
        if (status != -1)
        {
            sensor = buffer.ToString();
        }
    }
    // 查找属性Slope的索引号
    attr_index = SDfindattr(sd_id, "Slope");
    if (attr_index != -1)
    {
        // 读取Slope属性,通过float[]接收单精度浮点数据
        float[] buffer = new float[1];
        status = SDreadattr(sd_id, attr_index, buffer);
        if (status != -1)
        {
            slope = buffer[0];
        }
    }
    // 查找数据集DATA的索引号
    int sds_indes = SDnametoindex(sd_id, "DATA");
    // 选中数据集
    int sds_id = SDselect(sd_id, sds_indes);
    if (sds_id != -1)
    {
        // 数据集名称
        StringBuilder sds_name = new StringBuilder();
        // 秩数
        int[] rank = new int[1];
        // 行列数
        int[] dimsizes = new int[2];
        // 数据类型
        int[] ntype = new int[1];
        // 属性数目
        int[] num_attrs = new int[1];
        // 读取数据集信息
        status = SDgetinfo(sds_id, sds_name, rank, dimsizes, ntype, num_attrs);
        if (status != -1)
        {
            row = dimsizes[0];
            column = dimsizes[1];
            // 读取数据集所有数据
            short[,] buffer = new short[row, column];
            status = SDreaddata(sds_id, new int[] { 0, 0 }, null, new int[] { row, column }, buffer);
            if (status != -1)
            {
                data = buffer;
            }
        }
        // 结束数据集访问
        status = SDendaccess(sds_id);
    }
    // 关闭HDF4文件
    status = SDend(sd_id);
}

从以上方法中可以看出,虽然HDF4官方并没有提供可直接让C#访问的类库,但我们还是可以基于DllImport这种静态函数引入的方式对其进行访问。

更多信息请参阅:The HDF Group – Information, Support, and Software