LoginSignup
2
0

More than 1 year has passed since last update.

HDF5をC++で使う:(その2)グルーピング

Last updated at Posted at 2023-01-16

グルーピングによるデータ階層化

HDF5のHは" Hierarchical (階層的な)"の意味ですが、階層構造を実現しているのがグルーピングという仕組みです。

例えば(その1)で作成した書き出しプログラムを実行し、作成されたtest.h5の中身をh5dumpで出力すると、次のような出力が得られました。

HDF5 "test.h5" {
GROUP "/" {
   DATASET "dataset" {
      DATATYPE  H5T_STD_I32LE
      DATASPACE  SIMPLE { ( 4, 6 ) / ( 4, 6 ) }
      DATA {
      (0,0): 0, 1, 2, 3, 4, 5,
      (1,0): 6, 7, 8, 9, 10, 11,
      (2,0): 12, 13, 14, 15, 16, 17,
      (3,0): 18, 19, 20, 21, 22, 23
      }
   }
}
}

この最初の{}の中にGROUP "/"とあるのに気付いた方もいると思います。データセット作成時に特に指定しなかった場合、それは"/"という名前のグループに含まれることになっています。

グループはディレクトリに似ており、どんどん階層化することができます。グループの中にグループを持たせることができるという意味です。この仕組みによって、関連性の高いデータセットを整理したり、元データを保存したディレクトリ構造をそのままデータ構造に反映させたりできます。

グループを用いたデータ書き出し

サンプルコードを載せます。

#include <iostream>
#include <H5Cpp.h>

#define GROUP1_NAME "/group1"
#define GROUP2_NAME "/group2"
#define GROUP1_SUB_NAME "/group1/subgroup"
#define DATASET1_NAME "/dataset1"
#define DATASET2_NAME "/dataset2"

bool create_dataset(H5::DataSet &dataset, H5::DataType &datatype, int offset)
{
  H5::DataSpace dataspace = dataset.getSpace();
  hsize_t dims[dataspace.getSimpleExtentNdims()];
  dataspace.getSimpleExtentDims( dims );

  int i, j, *dset_data = new int [dims[0]*dims[1]];
  if( !dset_data ) return false;
  for( i=0; i<static_cast<int>(dims[0]); i++ ){
     for( j=0; j<static_cast<int>(dims[1]); j++ )
        dset_data[i*dims[1]+j] = i * dims[1] + j + offset;
  }
  dataset.write( dset_data, datatype );
  delete dset_data;
  return true;
}

bool open_group_dataset(H5::H5File &file, const char *groupname, const char *datasetname, hsize_t dim1, hsize_t dim2, int offset)
{
  H5::Group group;
  try{
    group = file.createGroup( groupname );
  } catch( ... ){
    group = file.openGroup( groupname );
  }
  hsize_t dims[] = { dim1, dim2 };
  H5::DataSpace dataspace;
  dataspace.setExtentSimple( sizeof(dims) / sizeof(hsize_t), dims );

  std::string groupnamestr = groupname;
  std::string datasetnamestr = datasetname;
  H5::DataType datatype = H5::PredType::STD_I32LE;
  H5::DataSet dataset = group.createDataSet( groupnamestr + datasetnamestr, datatype, dataspace );
  create_dataset( dataset, datatype, offset );
  dataset.close();
  group.close();
  return true;
}

int main(int argc, char *argv[])
{
  if( argc < 2 ){
    std::cerr << "input file name." << std::endl;
    return 1;
  }
  H5::Exception::dontPrint(); // make this progam silent against exceptions.
  H5::H5File file( argv[1], H5F_ACC_TRUNC );
  open_group_dataset( file, GROUP1_NAME, DATASET1_NAME, 1, 6, 0 );
  open_group_dataset( file, GROUP1_NAME, DATASET2_NAME, 2, 4, 10 );
  open_group_dataset( file, GROUP1_SUB_NAME, DATASET1_NAME, 2, 6, -10 );
  open_group_dataset( file, GROUP2_NAME, DATASET1_NAME, 3, 3, 20 );
  open_group_dataset( file, GROUP2_NAME, DATASET2_NAME, 4, 6, 30 );
  file.close();
  return 0;
}

ちょっと長くなってしまいましたが、main()関数から順を追っていきましょう。

  if( argc < 2 ){
    std::cerr << "input file name." << std::endl;
    return 1;
  }
  H5::Exception::dontPrint(); // make this progam silent against exceptions.
  H5::H5File file( argv[1], H5F_ACC_TRUNC );

作成するHDF5ファイルの名前をコマンドライン第1引数で指定するようにしました。
H5::Exception::dontPrint()というメソッドが出てきましたが、これは無くても構いません。コメントに書いている通り、例外発生時のエラーメッセージを出力しないようにする効果を持つものです。

次にopen_group_dataset()関数に入ります。

  H5::Group group;
  try{
    group = file.createGroup( groupname );
  } catch( ... ){
    group = file.openGroup( groupname );
  }

H5::Groupがグループを表すクラスです。新規にグループ作成する場合にはH5FileクラスのcreateGroup()メソッドを、既存グループを開く場合にはopenGroup()メソッドをそれぞれ用います。引数はどちらもグループ名(文字列)を与えます。

createGroup()メソッドは、グループが既に存在していた場合例外を発生します。これを利用して、与えられたグループ名が既にあるか無いかに応じて処理を分岐させています。

続く

  hsize_t dims[] = { dim1, dim2 };
  H5::DataSpace dataspace;
  dataspace.setExtentSimple( sizeof(dims) / sizeof(hsize_t), dims );

でデータスペースを定義しています。ここは前回と同様です。

この後の

  std::string groupnamestr = groupname;
  std::string datasetnamestr = datasetname;

は、グループ名とデータセット名を個別に与えるようにしている都合で用意しました。文字列の連結を+演算子でやりたかったからで、C言語風にsprintf()やstrcat()を使ってももちろん構いません。この事例から分かるように、文字列はcharポインタとstd::stringインスタンスどちらでも受け付けてくれます。

前回の例ではデータセットはH5::H5FileのcreateDataSet()メソッドを使って作成しましたが、H5::Groupインスタンスを陽に作る場合には、そちらのcreateDataSet()メソッドを用います。

  H5::DataType datatype = H5::PredType::STD_I32LE;
  H5::DataSet dataset = group.createDataSet( groupnamestr + datasetnamestr, datatype, dataspace );

H5::DataTypeインスタンスを作っているのは、この後に自作create_dataset()関数の引数に与えるためです。

そのcreate_dataset()関数に入りましょう。

  H5::DataSpace dataspace = dataset.getSpace();
  hsize_t dims[dataspace.getSimpleExtentNdims()];
  dataspace.getSimpleExtentDims( dims );

ここは前回作成した読み込みプログラムでデータスペース取得した方法と同じです。open_group_dataset()関数に与えた引数dim1, dim2をcreate_dataset()にも与えれば不要になるのですが、データセットが既にこれらの情報を持っているということで、冗長性を避けてこのようにしてみました。

続いて2重forループで適当にデータを作成してから、

  dataset.write( dset_data, datatype );

としてデータを書き出しています。これも前回の書き出しプログラムで説明しました。

あとは、open_group_dataset()関数に返って

  dataset.close();
  group.close();

としてデータセットとグループをともに閉じてから、main()関数に戻ります。最後に

  file.close();

としてファイルを閉じて、終了です。

階層化されたデータの例

上記プログラムのmain()関数では

  open_group_dataset( file, GROUP1_NAME, DATASET1_NAME, 1, 6, 0 );
  open_group_dataset( file, GROUP1_NAME, DATASET2_NAME, 2, 4, 10 );
  open_group_dataset( file, GROUP1_SUB_NAME, DATASET1_NAME, 2, 6, -10 );
  open_group_dataset( file, GROUP2_NAME, DATASET1_NAME, 3, 3, 20 );
  open_group_dataset( file, GROUP2_NAME, DATASET2_NAME, 4, 6, 30 );

としていますので、作成されるHDF5ファイルは

/
/group1/dataset1
/group1/dataset2
/group1/subgroup/dataset1
/group2/dataset1
/group2/dataset2

という階層構造を持っていることが期待されます。実際にプログラムを走らせてファイルtest.h5を作り、h5dumpで中身を出力すると次のようになり、予想通りであることが分かります。

HDF5 "test.h5" {
GROUP "/" {
   GROUP "group1" {
      DATASET "dataset1" {
         DATATYPE  H5T_STD_I32LE
         DATASPACE  SIMPLE { ( 1, 6 ) / ( 1, 6 ) }
         DATA {
         (0,0): 0, 1, 2, 3, 4, 5
         }
      }
      DATASET "dataset2" {
         DATATYPE  H5T_STD_I32LE
         DATASPACE  SIMPLE { ( 2, 4 ) / ( 2, 4 ) }
         DATA {
         (0,0): 10, 11, 12, 13,
         (1,0): 14, 15, 16, 17
         }
      }
      GROUP "subgroup" {
         DATASET "dataset1" {
            DATATYPE  H5T_STD_I32LE
            DATASPACE  SIMPLE { ( 2, 6 ) / ( 2, 6 ) }
            DATA {
            (0,0): -10, -9, -8, -7, -6, -5,
            (1,0): -4, -3, -2, -1, 0, 1
            }
         }
      }
   }
   GROUP "group2" {
      DATASET "dataset1" {
         DATATYPE  H5T_STD_I32LE
         DATASPACE  SIMPLE { ( 3, 3 ) / ( 3, 3 ) }
         DATA {
         (0,0): 20, 21, 22,
         (1,0): 23, 24, 25,
         (2,0): 26, 27, 28
         }
      }
      DATASET "dataset2" {
         DATATYPE  H5T_STD_I32LE
         DATASPACE  SIMPLE { ( 4, 6 ) / ( 4, 6 ) }
         DATA {
         (0,0): 30, 31, 32, 33, 34, 35,
         (1,0): 36, 37, 38, 39, 40, 41,
         (2,0): 42, 43, 44, 45, 46, 47,
         (3,0): 48, 49, 50, 51, 52, 53
         }
      }
   }
}
}
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0