hdf5c++接口:编写动态二维数组

HDF5 C++ interface: writing dynamic 2D arrays

本文关键字:动态 二维数组 接口 hdf5c++      更新时间:2023-10-16

我使用hdf5c++ API来编写2D数组数据集文件。HDF组有一个从静态定义的数组大小创建HDF5文件的示例,我对其进行了修改,以满足下面的需求。然而,我需要一个动态数组,其中NXNY都是在运行时确定的。我找到了另一种解决方案,使用"new"关键字创建二维数组来帮助创建动态数组。以下是我的文件:

#include "StdAfx.h"
#include "H5Cpp.h"
using namespace H5;
const H5std_string FILE_NAME("C:\SDS.h5");
const H5std_string DATASET_NAME("FloatArray");
const int NX = 5; // dataset dimensions
const int NY = 6;
int main (void)
{
    // Create a 2D array using "new" method
    double **data = new double*[NX];
    for (int j = 0; j < NX; j++)         // 0 1 2 3 4 5
    {                                    // 1 2 3 4 5 6
        data[j] = new double[NY];        // 2 3 4 5 6 7
        for (int i = 0; i < NY; i++)     // 3 4 5 6 7 8
            data[j][i] = (float)(i + j); // 4 5 6 7 8 9
    }
    // Create HDF5 file and dataset
    H5File file(FILE_NAME, H5F_ACC_TRUNC);
    hsize_t dimsf[2] = {NX, NY};
    DataSpace dataspace(2, dimsf);
    DataSet dataset = file.createDataSet(DATASET_NAME, PredType::NATIVE_DOUBLE,
                                            dataspace);
    // Attempt to write data to HDF5 file
    dataset.write(data, PredType::NATIVE_DOUBLE);
    // Clean up
    for(int j = 0; j < NX; j++)
        delete [] data[j];
    delete [] data;
    return 0;
}

结果文件,然而,不是预期的(hdf5dump的输出):

HDF5 "SDS.h5" {
GROUP "/" {
   DATASET "FloatArray" {
      DATATYPE  H5T_IEEE_F64LE
      DATASPACE  SIMPLE { ( 5, 6 ) / ( 5, 6 ) }
      DATA {
      (0,0): 4.76465e-307, 4.76541e-307, -7.84591e+298, -2.53017e-098, 0,
      (0,5): 3.8981e-308,
      (1,0): 4.76454e-307, 0, 2.122e-314, -7.84591e+298, 0, 1,
      (2,0): 2, 3, 4, 5, -2.53017e-098, -2.65698e+303,
      (3,0): 0, 3.89814e-308, 4.76492e-307, 0, 2.122e-314, -7.84591e+298,
      (4,0): 1, 2, 3, 4, 5, 6
      }
   }
}
}

问题源于如何创建2D数组(因为这个例子与静态数组方法一起工作得很好)。我从这封邮件中了解到:

HDF5库期望一个连续的元素数组,而不是指向低维元素的指针

由于我对c++/HDF5相当陌生,我不确定如何在运行时创建一个动态大小的数组,这是一个连续的元素数组。我不想做电子邮件线程中描述的更复杂的"hyperlab"方法,因为这看起来过于复杂。

嗯,我对HDF5一无所知,但是c++中具有连续缓冲区的动态2D数组可以通过使用大小为NX * NY的1D数组来模拟。例如:

分配:

double *data = new double[NX*NY];

元素访问:

 data[j*NY + i]

(代替data[j][i])

下面是如何在HDF5格式中编写N维数组

使用boost multi_array类要好得多。这相当于使用std::vector而不是原始数组:它为您完成所有的内存管理,并且您可以使用熟悉的下标(例如data[12][13] = 46)像原始数组一样有效地访问元素

下面是一个简短的例子:

#include <algorithm>
#include <boost/multi_array.hpp>
using boost::multi_array;
using boost::extents;
// dataset dimensions set at run time
int NX = 5,  NY = 6,  NZ = 7;

// allocate array using the "extents" helper. 
// This makes it easier to see how big the array is
multi_array<double, 3>  float_data(extents[NX][NY][NZ]);
// use resize to change size when necessary
// float_data.resize(extents[NX + 5][NY + 4][NZ + 3]);

// This is how you would fill the entire array with a value (e.g. 3.0)
std::fill_n(float_data.data(), float_data.num_elements(), 3.0)
// initialise the array to some variables
for (int ii = 0; ii != NX; ii++)
    for (int jj = 0; jj != NY; jj++)
        for (int kk = 0; kk != NZ; kk++)
            float_data[ii][jj][kk]  = ii + jj + kk
// write to HDF5 format
H5::H5File file("SDS.h5", H5F_ACC_TRUNC);
write_hdf5(file, "doubleArray", float_data );

最后一行调用一个函数,该函数可以写任意维数和任意标准数字类型(intscharsfloats等)的multi_array s。

这是write_hdf5()的代码。

首先,我们必须将c++类型映射到HDF5类型(从H5 c++ api):

#include <cstdint>
//!_______________________________________________________________________________________
//!     
//!     map types to HDF5 types
//!         
//!     
//!     author lg (04 March 2013)
//!_______________________________________________________________________________________ 
template<typename T> struct get_hdf5_data_type
{   static H5::PredType type()  
    {   
        //static_assert(false, "Unknown HDF5 data type"); 
        return H5::PredType::NATIVE_DOUBLE; 
    }
};
template<> struct get_hdf5_data_type<char>                  {   H5::IntType type    {   H5::PredType::NATIVE_CHAR       };  };
//template<> struct get_hdf5_data_type<unsigned char>       {   H5::IntType type    {   H5::PredType::NATIVE_UCHAR      };  };
//template<> struct get_hdf5_data_type<short>               {   H5::IntType type    {   H5::PredType::NATIVE_SHORT      };  };
//template<> struct get_hdf5_data_type<unsigned short>      {   H5::IntType type    {   H5::PredType::NATIVE_USHORT     };  };
//template<> struct get_hdf5_data_type<int>                 {   H5::IntType type    {   H5::PredType::NATIVE_INT        };  };
//template<> struct get_hdf5_data_type<unsigned int>        {   H5::IntType type    {   H5::PredType::NATIVE_UINT       };  };
//template<> struct get_hdf5_data_type<long>                {   H5::IntType type    {   H5::PredType::NATIVE_LONG       };  };
//template<> struct get_hdf5_data_type<unsigned long>       {   H5::IntType type    {   H5::PredType::NATIVE_ULONG      };  };
template<> struct get_hdf5_data_type<long long>             {   H5::IntType type    {   H5::PredType::NATIVE_LLONG      };  };
template<> struct get_hdf5_data_type<unsigned long long>    {   H5::IntType type    {   H5::PredType::NATIVE_ULLONG     };  };
template<> struct get_hdf5_data_type<int8_t>                {   H5::IntType type    {   H5::PredType::NATIVE_INT8       };  };
template<> struct get_hdf5_data_type<uint8_t>               {   H5::IntType type    {   H5::PredType::NATIVE_UINT8      };  };
template<> struct get_hdf5_data_type<int16_t>               {   H5::IntType type    {   H5::PredType::NATIVE_INT16      };  };
template<> struct get_hdf5_data_type<uint16_t>              {   H5::IntType type    {   H5::PredType::NATIVE_UINT16     };  };
template<> struct get_hdf5_data_type<int32_t>               {   H5::IntType type    {   H5::PredType::NATIVE_INT32      };  };
template<> struct get_hdf5_data_type<uint32_t>              {   H5::IntType type    {   H5::PredType::NATIVE_UINT32     };  };
template<> struct get_hdf5_data_type<int64_t>               {   H5::IntType type    {   H5::PredType::NATIVE_INT64      };  };
template<> struct get_hdf5_data_type<uint64_t>              {   H5::IntType type    {   H5::PredType::NATIVE_UINT64     };  };
template<> struct get_hdf5_data_type<float>                 {   H5::FloatType type  {   H5::PredType::NATIVE_FLOAT      };  };
template<> struct get_hdf5_data_type<double>                {   H5::FloatType type  {   H5::PredType::NATIVE_DOUBLE     };  };
template<> struct get_hdf5_data_type<long double>           {   H5::FloatType type  {   H5::PredType::NATIVE_LDOUBLE    };  };

然后,我们可以使用一点模板转发魔法来创建一个正确类型的函数来输出数据。由于这是模板代码,如果您打算从程序中的多个源文件输出HDF5数组,则需要将其保存在头文件中:

//!_______________________________________________________________________________________
//!     
//!     write_hdf5 multi_array
//!         
//!     author leo Goodstadt (04 March 2013)
//!     
//!_______________________________________________________________________________________
template<typename T, std::size_t DIMENSIONS, typename hdf5_data_type>
void do_write_hdf5(H5::H5File file, const std::string& data_set_name, const boost::multi_array<T, DIMENSIONS>& data, hdf5_data_type& datatype)
{
    // Little endian for x86
    //FloatType datatype(get_hdf5_data_type<T>::type());
    datatype.setOrder(H5T_ORDER_LE);
    vector<hsize_t> dimensions(data.shape(), data.shape() + DIMENSIONS);
    H5::DataSpace dataspace(DIMENSIONS, dimensions.data());
    H5::DataSet dataset = file.createDataSet(data_set_name, datatype, dataspace);
    dataset.write(data.data(), datatype);
}
template<typename T, std::size_t DIMENSIONS>
void write_hdf5(H5::H5File file, const std::string& data_set_name, const boost::multi_array<T, DIMENSIONS>& data )
{
    get_hdf5_data_type<T> hdf_data_type;
    do_write_hdf5(file, data_set_name, data, hdf_data_type.type);
}

在科学编程中,通常将多维数组表示为一个大的1D数组,然后从多维索引中计算相应的偏移量,例如Doc Brown的答案。

或者,您可以重载下标操作符(operator[]()),以便提供允许使用由1D数组支持的多维索引的接口。或者更好的是,使用一个库来做这件事,比如Boost multi_array。或者如果你的二维数组是矩阵,你可以使用一个很好的c++线性代数库,比如Eigen.

实际上,这个"方法实现起来并不复杂。您只需要修改"write"部分:

数据集。写(数据,PredType:: NATIVE_DOUBLE);

在输出前选择数据空间中的hyperlab:

#include "H5Cpp.h"
using namespace H5;
const H5std_string FILE_NAME("SDS.h5");
const H5std_string DATASET_NAME("FloatArray");
const int NX = 5; // dataset dimensions
const int NY = 6;
int main ()
{
    // Create a 2D array using "new" method
    double **data = new double*[NX];
    for (int j = 0; j < NX; j++)         // 0 1 2 3 4 5
    {                                    // 1 2 3 4 5 6
        data[j] = new double[NY];        // 2 3 4 5 6 7
        for (int i = 0; i < NY; i++)     // 3 4 5 6 7 8
            data[j][i] = (float)(i + j); // 4 5 6 7 8 9
    }
    // Create HDF5 file and dataset
    H5File file(FILE_NAME, H5F_ACC_TRUNC);
    hsize_t dimsf[2] = {NX, NY};
    DataSpace dataspace(2, dimsf);
    DataSet dataset = file.createDataSet(DATASET_NAME, PredType::NATIVE_DOUBLE,
                                             dataspace);
    
    // The above codes are the same.    
    hsize_t start[2]={0, 0}, count[2]={1, NY};
    // Create memory space for one line
    DataSpace memspace(2, count);
    for(int k=0; k<NX; k++)
    {
        start[0] = k;
        // select the hyperslab for one line
        dataspace.selectHyperslab(H5S_SELECT_SET, count, start, NULL, NULL);
        // Attempt to write data to HDF5 file
        dataset.write(data[k], PredType::NATIVE_DOUBLE, memspace, dataspace);
        /*
        * memspace: dataspace specifying the size of the memory that needs to be written
        * dataspace: dataspace sepcifying the portion of the dataset that needs to be written
        */
        // Reset the selection for the dataspace.
        dataspace.selectNone();
    }
    // Clean up
    for(int j = 0; j < NX; j++)
        delete [] data[j];
    delete [] data;
    return 0;
}
结果文件是正确的:
HDF5 "SDS.h5" {
GROUP "/" {
   DATASET "FloatArray" {
      DATATYPE  H5T_IEEE_F64LE
      DATASPACE  SIMPLE { ( 5, 6 ) / ( 5, 6 ) }
      DATA {
      (0,0): 0, 1, 2, 3, 4, 5,
      (1,0): 1, 2, 3, 4, 5, 6,
      (2,0): 2, 3, 4, 5, 6, 7,
      (3,0): 3, 4, 5, 6, 7, 8,
      (4,0): 4, 5, 6, 7, 8, 9
      }
   }
}
}

我也一直在纠结一个类似的问题。由于某些原因,我需要在c++中处理数据流,但最终我想使用numpy和matplotlib的优点,在python中分析生成的HDF。解决方案比预期的要简单。首先,我声明了我真正需要的任何形状的数据空间。

hsize_t dims[2] = {rows, cols};         
dataspace = new DataSpace(2, dims);
dataset = new DataSet(group->createDataSet("data", PredType::STD_U16LE, *dataspace));

接下来我使用1D动态数组并填充它记住元素[I][j]位于位置[I * cols + j]

unsigned short* hits = new unsigned short[cols * rows]; (...) hits[i * cols + j] = foo; (...) 现在是有趣的部分。因为DataSet.write需要void*,所以它不关心你通过了什么。它只接受连续的元素数组,形状由DataSpace定义解释。由于我们的动态数组是连续的,具有正确的总体大小和元素顺序,因此您可以简单地将其写入

dataset->write(hits, PredType::STD_U16LE);

如果您稍后读取HDF5文件,则结果数组将被正确解释为2D。