グルーピングによるデータ階層化
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
}
}
}
}
}