Comment mieux écrire un std :: vector contenant un ensemble de données HDF5?

StackOverflow https://stackoverflow.com/questions/581209

  •  06-09-2019
  •  | 
  •  

Question

Étant donné un vecteur de chaînes, quelle est la meilleure façon de les écrire à un ensemble de données HDF5? En ce moment je fais quelque chose comme ce qui suit:

  const unsigned int MaxStrLength = 512;

  struct TempContainer {
    char string[MaxStrLength];
  };

  void writeVector (hid_t group, std::vector<std::string> const & v)
  {
    //
    // Firstly copy the contents of the vector into a temporary container
    std::vector<TempContainer> tc;
    for (std::vector<std::string>::const_iterator i = v.begin ()
                                              , end = v.end ()
      ; i != end
      ; ++i)
    {
      TempContainer t;
      strncpy (t.string, i->c_str (), MaxStrLength);
      tc.push_back (t);
    }


    //
    // Write the temporary container to a dataset
    hsize_t     dims[] = { tc.size () } ;
    hid_t dataspace = H5Screate_simple(sizeof(dims)/sizeof(*dims)
                               , dims
                               , NULL);

    hid_t strtype = H5Tcopy (H5T_C_S1);
    H5Tset_size (strtype, MaxStrLength);

    hid_t datatype = H5Tcreate (H5T_COMPOUND, sizeof (TempConainer));
    H5Tinsert (datatype
      , "string"
      , HOFFSET(TempContainer, string)
      , strtype);

    hid_t dataset = H5Dcreate1 (group
                          , "files"
                          , datatype
                          , dataspace
                          , H5P_DEFAULT);

    H5Dwrite (dataset, datatype, H5S_ALL, H5S_ALL, H5P_DEFAULT, &tc[0] );

    H5Dclose (dataset);
    H5Sclose (dataspace);
    H5Tclose (strtype);
    H5Tclose (datatype);
}

Au minimum, je voudrais vraiment changer ce qui précède de telle sorte que:

  1. Il utilise des chaînes de longueur variable
  2. Je ne ai pas besoin d'avoir un conteneur temporaire

Je ne comportent aucune restriction sur la façon dont je stocker les données ainsi, par exemple, il ne doit pas être COMPOSÉ s'il y a datatype une meilleure façon de le faire.

EDIT:. Juste pour circonscrire le problème, je suis relativement au courant de jouer avec les données sur le côté C, il est du côté HDF5 où j'ai besoin de l'aide

Merci pour votre aide.

Était-ce utile?

La solution

[Un grand merci à dirkgently pour son aide pour répondre à ce sujet.]

Pour écrire une chaîne de longueur variable HDF5 utiliser les éléments suivants:

// Create the datatype as follows
hid_t datatype = H5Tcopy (H5T_C_S1);
H5Tset_size (datatype, H5T_VARIABLE);

// 
// Pass the string to be written to H5Dwrite
// using the address of the pointer!
const char * s = v.c_str ();
H5Dwrite (dataset
  , datatype
  , H5S_ALL
  , H5S_ALL
  , H5P_DEFAULT
  , &s );

Une solution pour l'écriture d'un conteneur est d'écrire chaque élément individuellement. Ceci peut être réalisé en utilisant hyperslabs .

Par exemple:

class WriteString
{
public:
  WriteString (hid_t dataset, hid_t datatype
      , hid_t dataspace, hid_t memspace)
    : m_dataset (dataset), m_datatype (datatype)
    , m_dataspace (dataspace), m_memspace (memspace)
    , m_pos () {}

private:
  hid_t m_dataset;
  hid_t m_datatype;
  hid_t m_dataspace;
  hid_t m_memspace;
  int m_pos;

// ...

public:
  void operator ()(std::vector<std::string>::value_type const & v)
  {
    // Select the file position, 1 record at position 'pos'
    hsize_t count[] = { 1 } ;
    hsize_t offset[] = { m_pos++ } ;
    H5Sselect_hyperslab( m_dataspace
      , H5S_SELECT_SET
      , offset
      , NULL
      , count
      , NULL );

    const char * s = v.c_str ();
    H5Dwrite (m_dataset
      , m_datatype
      , m_memspace
      , m_dataspace
      , H5P_DEFAULT
      , &s );
    }    
};

// ...

void writeVector (hid_t group, std::vector<std::string> const & v)
{
  hsize_t     dims[] = { m_files.size ()  } ;
  hid_t dataspace = H5Screate_simple(sizeof(dims)/sizeof(*dims)
                                    , dims, NULL);

  dims[0] = 1;
  hid_t memspace = H5Screate_simple(sizeof(dims)/sizeof(*dims)
                                    , dims, NULL);

  hid_t datatype = H5Tcopy (H5T_C_S1);
  H5Tset_size (datatype, H5T_VARIABLE);

  hid_t dataset = H5Dcreate1 (group, "files", datatype
                             , dataspace, H5P_DEFAULT);

  // 
  // Select the "memory" to be written out - just 1 record.
  hsize_t offset[] = { 0 } ;
  hsize_t count[] = { 1 } ;
  H5Sselect_hyperslab( memspace, H5S_SELECT_SET, offset
                     , NULL, count, NULL );

  std::for_each (v.begin ()
      , v.end ()
      , WriteStrings (dataset, datatype, dataspace, memspace));

  H5Dclose (dataset);
  H5Sclose (dataspace);
  H5Sclose (memspace);
  H5Tclose (datatype);
}      

Autres conseils

Voici un code de travail pour écrire un vecteur de chaînes de longueur variable en utilisant la HDF5 C ++ API.

J'intègre quelques-unes des suggestions dans les autres postes:

  1. utilisez H5T_C_S1 et H5T_VARIABLE
  2. utiliser string::c_str() pour obtenir des pointeurs vers les chaînes
  3. placer les pointeurs dans un vector de char* et passer à l'API HDF5

Il est ne sont pas nécessaires pour créer des copies coûteuses de la chaîne (par exemple avec strdup()). c_str() renvoie un pointeur vers la valeur nulle terminée données de la chaîne sous-jacente. Ceci est précisément ce que la fonction est destinée. Bien sûr, les chaînes avec des valeurs nulles intégrées ne fonctionnent pas avec cette ...

std::vector est garantie à un stockage contigu sous-jacente, donc l'utilisation vector et vector::data() est le même que l'utilisation de tableaux bruts, mais bien sûr beaucoup plus propre et plus sûr que la façon maladroite, ancienne c de faire les choses.

#include "H5Cpp.h"
void write_hdf5(H5::H5File file, const std::string& data_set_name,
                const std::vector<std::string>& strings )
{
    H5::Exception::dontPrint();

    try
    {
        // HDF5 only understands vector of char* :-(
        std::vector<const char*> arr_c_str;
        for (unsigned ii = 0; ii < strings.size(); ++ii) 
            arr_c_str.push_back(strings[ii].c_str());

        //
        //  one dimension
        // 
        hsize_t     str_dimsf[1] {arr_c_str.size()};
        H5::DataSpace   dataspace(1, str_dimsf);

        // Variable length string
        H5::StrType datatype(H5::PredType::C_S1, H5T_VARIABLE); 
        H5::DataSet str_dataset = file.createDataSet(data_set_name, datatype, dataspace);

        str_dataset.write(arr_c_str.data(), datatype);
    }
    catch (H5::Exception& err)
    {
        throw std::runtime_error(string("HDF5 Error in " ) 
                                    + err.getFuncName()
                                    + ": "
                                    + err.getDetailMsg());


    }
}

Si vous regardez un code plus propre: Je vous suggère de créer un foncteur qui va prendre une corde et l'enregistrer dans le conteneur HDF5 (dans un mode désiré). Richard, je le mauvais algorithme, s'il vous plaît vérifier à nouveau!

std::for_each(v.begin(), v.end(), write_hdf5);

struct hdf5 : public std::unary_function<std::string, void> {
    hdf5() : _dataset(...) {} // initialize the HDF5 db
    ~hdf5() : _dataset(...) {} // close the the HDF5 db
    void operator(std::string& s) {
            // append 
            // use s.c_str() ?
    }
};

Est-ce que aider à démarrer?

J'ai eu un problème similaire, avec la mise en garde que je voulais un vecteur de chaînes stockées comme un attribut . La chose la plus délicate avec des attributs est que nous ne pouvons pas utiliser les fonctions de DataSpace fantaisie comme hyperslabs (au moins avec l'API C ++).

Mais dans les deux cas, il peut être utile d'entrer dans un vecteur de chaînes en une seule entrée dans un ensemble de données (si, par exemple, vous vous attendez toujours à les lire ensemble). Dans ce cas, toute la magie est livré avec type , pas avec le DataSpace lui-même.

Il y a essentiellement 4 étapes:

  1. Faire une vector<const char*> qui pointe vers les chaînes.
  2. Créer une structure hvl_t qui pointe vers le vecteur et contient sa longueur.
  3. Créez le type de données. Ceci est un H5::VarLenType envelopper une (longueur variable) H5::StrType.
  4. Ecrire le type de hvl_t à un ensemble de données.

La partie vraiment agréable de cette méthode est que vous farcir toute entrée dans ce HDF5 considère une valeur scalaire. Cela signifie que ce qui en fait un attribut (au lieu d'un jeu de données) est insignifiante.

Si vous choisissez cette solution ou celle avec chaque chaîne dans sa propre entrée de jeu de données est sans doute aussi une question de la performance souhaitée: si vous êtes à la recherche d'un accès aléatoire à des chaînes spécifiques, il est probablement préférable d'écrire les chaînes dans un ensemble de données afin qu'ils puissent être indexés. Si vous allez toujours les lire tous sortir ensemble cette solution peut fonctionner tout aussi bien.

Voici un court exemple de la façon de le faire, en utilisant l'API C ++ et un ensemble de données scalaire simple:

#include <vector>
#include <string>
#include "H5Cpp.h"

int main(int argc, char* argv[]) {
  // Part 0: make up some data
  std::vector<std::string> strings;
  for (int iii = 0; iii < 10; iii++) {
    strings.push_back("this is " + std::to_string(iii));
  }

  // Part 1: grab pointers to the chars
  std::vector<const char*> chars;
  for (const auto& str: strings) {
    chars.push_back(str.data());
  }

  // Part 2: create the variable length type
  hvl_t hdf_buffer;
  hdf_buffer.p = chars.data();
  hdf_buffer.len = chars.size();

  // Part 3: create the type
  auto s_type = H5::StrType(H5::PredType::C_S1, H5T_VARIABLE);
  s_type.setCset(H5T_CSET_UTF8); // just for fun, you don't need this
  auto svec_type = H5::VarLenType(&s_type);

  // Part 4: write the output to a scalar dataset
  H5::H5File out_file("vtest.h5", H5F_ACC_EXCL);
  H5::DataSet dataset(
    out_file.createDataSet("the_ds", svec_type, H5S_SCALAR));
  dataset.write(&hdf_buffer, svec_type);

  return 0;
}

Au lieu d'un TempContainer, vous pouvez utiliser un simple std :: vecteur (vous pouvez aussi le faire correspondre sans canevas T -> basic_string. Quelque chose comme ceci:

#include <algorithm>
#include <vector>
#include <string>
#include <functional>

class StringToVector
  : std::unary_function<std::vector<char>, std::string> {
public:
  std::vector<char> operator()(const std::string &s) const {
    // assumes you want a NUL-terminated string
    const char* str = s.c_str();
    std::size_t size = 1 + std::strlen(str);
    // s.size() != strlen(s.c_str())
    std::vector<char> buf(&str[0], &str[size]);
    return buf;
  }
};

void conv(const std::vector<std::string> &vi,
          std::vector<std::vector<char> > &vo)
{
  // assert vo.size() == vi.size()
  std::transform(vi.begin(), vi.end(),
                 vo.begin(),
                 StringToVector());
}

Dans l'intérêt d'avoir la capacité de lire std::vector<std::string> je poste ma solution, basée sur les conseils de Leo ici https://stackoverflow.com/a/15220532/364818 .

J'ai mélangé C et C ++ API. S'il vous plaît ne hésitez pas à modifier cela et le rendre plus simple.

Notez que l'API HDF5 retourne une liste de char*pointers lorsque vous appelez lu. Ces pointeurs char* doivent être libérés après utilisation, sinon il y a une fuite de mémoire.

Exemple d'utilisation

H5::Attribute Foo = file.openAttribute("Foo");
std::vector<std::string> foos
Foo >> foos;

Voici le code

  const H5::Attribute& operator>>(const H5::Attribute& attr0, std::vector<std::string>& array)
  {
      H5::Exception::dontPrint();

      try
      {
          hid_t attr = attr0.getId();

          hid_t atype = H5Aget_type(attr);
          hid_t aspace = H5Aget_space(attr);
          int rank = H5Sget_simple_extent_ndims(aspace);
          if (rank != 1) throw PBException("Attribute " + attr0.getName() + " is not a string array");

          hsize_t sdim[1];
          herr_t ret = H5Sget_simple_extent_dims(aspace, sdim, NULL);
          size_t size = H5Tget_size (atype);
          if (size != sizeof(void*))
          {
              throw PBException("Internal inconsistency. Expected pointer size element");
          }

          // HDF5 only understands vector of char* :-(
          std::vector<char*> arr_c_str(sdim[0]);

          H5::StrType stringType(H5::PredType::C_S1, H5T_VARIABLE);
          attr0.read(stringType, arr_c_str.data());
          array.resize(sdim[0]);
          for(int i=0;i<sdim[0];i++)
          {
              // std::cout << i << "=" << arr_c_str[i] << std::endl;
              array[i] = arr_c_str[i];
              free(arr_c_str[i]);
          }

      }
      catch (H5::Exception& err)
      {
          throw std::runtime_error(string("HDF5 Error in " )
                                    + err.getFuncName()
                                    + ": "
                                    + err.getDetailMsg());


      }

      return attr0;
  }

Je suis en retard à la fête mais je l'ai modifié la réponse de Leo Goodstadt basé sur les commentaires concernant segfaults. Je suis sur linux, mais je n'ai pas de tels problèmes. J'ai écrit 2 fonctions, l'un pour écrire un vecteur de std :: string à un ensemble de données d'un nom donné dans un H5File ouvert, et un autre de collationner les ensembles de données résultant en un vecteur de std :: string. Remarque il peut copier inutile entre les types quelques fois qui peuvent être plus optimisés. Voici le code de travail pour l'écriture et la lecture:

void write_varnames( const std::string& dsetname, const std::vector<std::string>& strings, H5::H5File& f)
  {
    H5::Exception::dontPrint();

    try
      {
        // HDF5 only understands vector of char* :-(
        std::vector<const char*> arr_c_str;
        for (size_t ii = 0; ii < strings.size(); ++ii)
      {
        arr_c_str.push_back(strings[ii].c_str());
      }

        //
        //  one dimension
        // 
        hsize_t     str_dimsf[1] {arr_c_str.size()};
        H5::DataSpace   dataspace(1, str_dimsf);

        // Variable length string
        H5::StrType datatype(H5::PredType::C_S1, H5T_VARIABLE); 
        H5::DataSet str_dataset = f.createDataSet(dsetname, datatype, dataspace);

        str_dataset.write(arr_c_str.data(), datatype);
      }
    catch (H5::Exception& err)
      {
        throw std::runtime_error(std::string("HDF5 Error in ")  
                 + err.getFuncName()
                 + ": "
                 + err.getDetailMsg());


      }
  }

Et comme suit:

std::vector<std::string> read_string_dset( const std::string& dsname, H5::H5File& f )
  {
    H5::DataSet cdataset = f.openDataSet( dsname );


    H5::DataSpace space = cdataset.getSpace();

    int rank = space.getSimpleExtentNdims();

    hsize_t dims_out[1];

    int ndims = space.getSimpleExtentDims( dims_out, NULL);

    size_t length = dims_out[0];

    std::vector<const char*> tmpvect( length, NULL );

    fprintf(stdout, "In read STRING dataset, got number of strings: [%ld]\n", length );

    std::vector<std::string> strs(length);
    H5::StrType datatype(H5::PredType::C_S1, H5T_VARIABLE); 
    cdataset.read( tmpvect.data(), datatype);

    for(size_t x=0; x<tmpvect.size(); ++x)
      {
        fprintf(stdout, "GOT STRING [%s]\n", tmpvect[x] );
        strs[x] = tmpvect[x];
      }

    return strs;
  }

Je ne sais pas HDF5, mais vous pouvez utiliser

struct TempContainer {
    char* string;
};

et puis copiez les chaînes de cette façon:

TempContainer t;
t.string = strdup(i->c_str());
tc.push_back (t);

attribuera une chaîne avec la taille exacte, et améliore aussi beaucoup lors de l'insertion ou la lecture du conteneur (dans votre exemple, il y a un tableau copié, dans ce cas, seul un pointeur). Vous pouvez également utiliser std :: vecteur:

std::vector<char *> tc;
...
tc.push_back(strdup(i->c_str());
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top