Skip to content
Snippets Groups Projects
NexusHDF5Descriptor.cpp 6.19 KiB
Newer Older
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright © 2007 ISIS Rutherford Appleton Laboratory UKRI,
//   NScD Oak Ridge National Laboratory, European Spallation Source,
//   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
// SPDX - License - Identifier: GPL - 3.0 +

#include "MantidKernel/NexusHDF5Descriptor.h"
Peterson, Peter's avatar
Peterson, Peter committed
#include "MantidKernel/NexusDescriptor.h"

#include <hdf5.h>

#include <cstdlib>   // malloc, calloc
#include <cstring>   // strcpy
#include <stdexcept> // std::invalid_argument

namespace Mantid::Kernel {

/// hdf5 specific functions, stay in anonymous namespace to make hdf5 linking
/// PRIVATE
namespace {

/**
 * populate a string attribute from HDF5 attribute handler
 * @param attr input HDF5 atttribute handler
 * @param data
 * @return
 */
herr_t readStringAttribute(hid_t attr, char **data) {
  herr_t iRet = 0;
  hsize_t thedims[H5S_MAX_RANK];
  hid_t atype = H5Aget_type(attr);
  hsize_t sdim = H5Tget_size(atype);
  hid_t space = H5Aget_space(attr);
  int ndims = H5Sget_simple_extent_dims(space, thedims, NULL);

  if (ndims == 0) {
    if (H5Tis_variable_str(atype)) {
      hid_t btype = H5Tget_native_type(atype, H5T_DIR_ASCEND);
      iRet = H5Aread(attr, btype, data);
      H5Tclose(btype);
    } else {
      *data = (char *)malloc(sdim + 1);
      iRet = H5Aread(attr, atype, *data);
      (*data)[sdim] = '\0';
    }
  } else if (ndims == 1) {
    unsigned int i;
    char **strings;

    strings = (char **)malloc(thedims[0] * sizeof(char *));

    if (!H5Tis_variable_str(atype)) {
      strings[0] = (char *)malloc(thedims[0] * sdim * sizeof(char));
      for (i = 1; i < thedims[0]; i++) {
        strings[i] = strings[0] + i * sdim;
      }
    }

    iRet = H5Aread(attr, atype, strings[0]);
    *data = (char *)calloc((sdim + 2) * thedims[0], sizeof(char));
    for (i = 0; i < thedims[0]; i++) {
      if (i == 0) {
        strncpy(*data, strings[i], sdim);
      } else {
        strcat(*data, ", ");
        strncat(*data, strings[i], sdim);
      }
    }
    if (H5Tis_variable_str(atype)) {
      H5Dvlen_reclaim(atype, space, H5P_DEFAULT, strings);
    } else {
      free(strings[0]);
    }

    free(strings);
  } else {
    *data = (char *)malloc(33);
    strcpy(*data, " higher dimensional string array");
    *data[32] = '\0';
  }

  H5Tclose(atype);
  H5Sclose(space);
  if (iRet < 0)
    return -1;
  return 0;
}

/**
 * Reads a string attribute of N-dimensions
 * @param attr input HDF5 attribute handler
 * @param data output attribute data
 * @param maxlen
 * @return
 */
herr_t readStringAttributeN(hid_t attr, char *data, int maxlen) {
  herr_t iRet;
  char *vdat = NULL;
  iRet = readStringAttribute(attr, &vdat);
  if (iRet >= 0) {
    strncpy(data, vdat, maxlen);
    free(vdat);
  }
  data[maxlen - 1] = '\0';
  return iRet;
}

void getGroup(hid_t groupID,
              std::map<std::string, std::set<std::string>> &allEntries) {

  /**
   * Return the NX_class attribute associate with objectName group entry
   */
  auto lf_getNxClassAttribute = [&](hid_t groupID,
                                    const char *objectName) -> std::string {
    std::string attribute = "";
    hid_t attributeID = H5Aopen_by_name(groupID, objectName, "NX_class",
                                        H5P_DEFAULT, H5P_DEFAULT);
    if (attributeID < 0) {
      H5Aclose(attributeID);
      return attribute;
    }

    hid_t type = H5T_C_S1;
    hid_t atype = H5Tcopy(type);
    char data[128];
    H5Tset_size(atype, sizeof(data));
    readStringAttributeN(attributeID, data, sizeof(data));
    // already null terminated in readStringAttributeN
    attribute = std::string(data);
    H5Tclose(atype);
    H5Aclose(attributeID);

    return attribute;
  };

  // using HDF5 C API
  constexpr std::size_t maxLength = 1024;
  char groupName[maxLength];
  char memberName[maxLength];
  std::size_t groupNameLength =
      static_cast<std::size_t>(H5Iget_name(groupID, groupName, maxLength));
  hsize_t nObjects = 0;
  H5Gget_num_objs(groupID, &nObjects);

  const std::string groupNameStr(groupName, groupNameLength);
  const std::string nxClass =
      (groupNameStr == "/")
          ? ""
          : lf_getNxClassAttribute(groupID, groupNameStr.c_str());

  if (!nxClass.empty()) {
    allEntries[nxClass].insert(groupNameStr);
  }

  for (unsigned int i = 0; i < nObjects; ++i) {

    const int type = H5Gget_objtype_by_idx(groupID, static_cast<size_t>(i));
    const std::size_t memberNameLength =
        static_cast<std::size_t>(H5Gget_objname_by_idx(
            groupID, static_cast<hsize_t>(i), memberName, maxLength));

    if (type == H5O_TYPE_GROUP) {
      hid_t subGroupID = H5Gopen2(groupID, memberName, H5P_DEFAULT);
      getGroup(subGroupID, allEntries);
      H5Gclose(subGroupID);

    } else if (type == H5O_TYPE_DATASET) {
      const std::string memberNameStr(memberName, memberNameLength);
      const std::string absoluteEntryName = groupNameStr + "/" + memberNameStr;
      allEntries["SDS"].insert(absoluteEntryName);
    }
  }
}

} // namespace

Peterson, Peter's avatar
Peterson, Peter committed
bool NexusHDF5Descriptor::isReadable(const std::string &filename) {
  // use existing function to do the work
  return NexusDescriptor::isReadable(filename,
                                     NexusDescriptor::Version::Version5);
NexusHDF5Descriptor::NexusHDF5Descriptor(const std::string &filename)
    : m_filename(filename), m_allEntries(initAllEntries()) {}

// PUBLIC
std::string NexusHDF5Descriptor::getFilename() const noexcept {
  return m_filename;
}

const std::map<std::string, std::set<std::string>> &
NexusHDF5Descriptor::getAllEntries() const noexcept {
  return m_allEntries;
}

// PRIVATE
std::map<std::string, std::set<std::string>>
NexusHDF5Descriptor::initAllEntries() {

  hid_t fileID = H5Fopen(m_filename.c_str(), H5F_ACC_RDONLY, H5P_DEFAULT);
  if (fileID < 0) {
    throw std::invalid_argument(
        "ERROR: Kernel::NexusHDF5Descriptor couldn't open hdf5 file " +
        m_filename + "\n");
  }

  hid_t groupID = H5Gopen2(fileID, "/", H5P_DEFAULT);

  std::map<std::string, std::set<std::string>> allEntries;
  // scan file recursively starting with root group "/"
  getGroup(groupID, allEntries);
  H5Gclose(groupID);
  H5Fclose(fileID);

  // rely on move semantics
  return allEntries;
}

} // namespace Mantid::Kernel