Интерфейс HDF5 C ++: написание динамических 2D-массивов - PullRequest
10 голосов
/ 14 сентября 2011

Я использую HDF5 C ++ API для записи файлов набора данных 2D-массива. Группа HDF имеет пример для создания файла HDF5 из статически определенного размера массива, который я изменил для соответствия моим потребностям ниже. Однако мне нужен динамический массив, в котором во время выполнения определяются и NX, и NY. Я нашел другое решение для создания двумерных массивов с помощью ключевого слова "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, я не уверен, как создать динамически изменяемый массив во время выполнения, представляющий собой непрерывный массив элементов. Я не хочу делать более сложный метод «гиперсписки», описанный в ветке электронной почты, так как это выглядит слишком сложным. Любая помощь приветствуется.

Ответы [ 4 ]

8 голосов
/ 14 сентября 2011

Ну, я ничего не знаю о HDF5, но динамические двумерные массивы в C ++ с непрерывным буфером можно моделировать с использованием одномерного массива размером NX * NY. Например:

Распределение:

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

Элемент доступа:

 data[j*NY + i]

(вместо data[j][i])

6 голосов
/ 05 марта 2013

Здесь описано, как записать N размерных массивов в формате HDF5

Гораздо лучше использовать класс 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 );

В последней строке вызывается функция, которая может записать multi_array s любого измерения и любого стандартного числового типа (ints, chars, floats и т. д.).

Вот код для 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);
}
2 голосов
/ 14 сентября 2011

В научном программировании принято представлять многомерные массивы в виде большого одномерного массива, а затем вычислять соответствующее смещение из многомерных индексов, например, как видно из ответа Дока Брауна.

В качестве альтернативы вы можете перегрузитьоператор нижнего индекса (operator[]()) для обеспечения интерфейса, который позволяет использовать многомерные индексы, подкрепленные массивом 1D.Или еще лучше, используйте библиотеку, которая делает это, например Boost multi_array .Или, если ваши двумерные массивы являются матрицами, вы можете использовать хорошую библиотеку линейной алгебры C ++, такую ​​как Eigen .

0 голосов
/ 14 ноября 2018

Я тоже некоторое время боролся с подобным вопросом. По некоторым причинам мне нужно обработать поток данных в C ++, но в конечном итоге я хотел бы проанализировать полученный HDF в python, используя преимущества numpy и matplotlib. Решение проще, чем ожидалось. Сначала я объявляю пространство данных той формы, которая мне действительно нужна.

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);

Полученный массив правильно интерпретируется как 2D, если вы позже прочитаете файл HDF5.

...