added HDF5 ISIS NeXus class handling.

This commit is contained in:
2026-01-25 11:15:49 +01:00
parent 909fa6519d
commit c3e9c03920
3 changed files with 3562 additions and 2 deletions

View File

@@ -28,7 +28,13 @@ target_include_directories(
) )
#--- add library dependencies ------------------------------------------------- #--- add library dependencies -------------------------------------------------
target_link_libraries(PNeXus ${HDF4_LIBRARIES} ${ROOT_LIBRARIES}) if (HAVE_HDF4)
set(HDF_LIBS ${HDF4_LIBRARIES} ${HDF5_LIBRARIES})
else (HAVE_HDF4)
set(HDF_LIBS ${HDF5_LIBRARIES})
endif (HAVE_HDF4)
message(STATUS "++>> HDF_LIBS: ${HDF_LIBS}")
target_link_libraries(PNeXus ${HDF_LIBS} ${ROOT_LIBRARIES})
#--- install PNeXus solib ----------------------------------------------------- #--- install PNeXus solib -----------------------------------------------------
install(TARGETS PNeXus DESTINATION lib) install(TARGETS PNeXus DESTINATION lib)

File diff suppressed because it is too large Load Diff

View File

@@ -45,6 +45,8 @@
#include <hdf.h> #include <hdf.h>
#endif // HAVE_HDF4 #endif // HAVE_HDF4
#include <H5Cpp.h>
namespace nxs { namespace nxs {
enum class HDFType { enum class HDFType {
@@ -892,4 +894,837 @@ private:
} }
#endif // HAVE_HDF4 #endif // HAVE_HDF4
namespace nxH5 {
class PNeXus;
/**
* @class PNeXusDeadTime
* @brief Dead time correction calculator for muon detector data
*
* The PNeXusDeadTime class calculates dead time corrections for muon detector
* count data using ROOT Minuit2 minimization. It inherits from ROOT::Minuit2::FCNBase
* to implement a chi-square minimization function.
*
* Dead time is the period after detecting an event during which the detector
* cannot register another event. This class estimates the dead time parameter
* for each detector spectrum by minimizing the deviation from expected count rates.
*
* **Typical Usage:**
* @code
* nxH5::PNeXus nexus("file.nxs");
* nxH5::PNeXusDeadTime ndt(&nexus, false); // false = no debug output
*
* if (ndt.IsValid()) {
* auto dims = ndt.GetDimensions();
* // Minimize for each spectrum
* for (unsigned int i = 0; i < dims[1]; i++) {
* ndt.minimize(i);
* }
* }
* @endcode
*
* @note Requires a valid PNeXus object with count data
* @note Uses ROOT Minuit2 for minimization
*
* @see ROOT::Minuit2::FCNBase
* @see nxH5::PNeXus
*/
class PNeXusDeadTime : public ROOT::Minuit2::FCNBase
{
public:
/**
* @brief Constructor - initializes dead time calculator from NeXus data
* * Reads count data, time resolution, and bin information from the NeXus file
* to set up the dead time calculation.
* * @param nxs Pointer to PNeXus object containing the detector data
* @param debug If true, print additional debug information during calculations
* * @note After construction, check IsValid() to ensure data was loaded successfully
*/
PNeXusDeadTime(const PNeXus *nxs, bool debug=false);
/**
* @brief Check if the dead time calculator was initialized successfully
* * @return true if required datasets were found and loaded, false otherwise
*/
bool IsValid() { return fValid; }
/**
* @brief Get the error definition for the minimizer (FCNBase requirement)
* * Returns the UP parameter which defines how the minimizer estimates errors.
* For chi-square minimization, UP = 0.5 corresponds to 1-sigma errors.
* * @return Error definition parameter (0.5 for chi-square)
*/
double Up() const { return fUp; }
/**
* @brief Function call operator - calculates chi-square for given parameters
* * This is the objective function that ROOT::Minuit2 minimizes. It calculates
* the chi-square deviation between observed counts and expected counts
* corrected for dead time.
* * @param par Vector of parameters (dead time values in microseconds)
* @return Chi-square value for the given parameters
*/
double operator()(const std::vector<double> &par) const;
/**
* @brief Minimize dead time for a specific detector spectrum
* * Performs Minuit2 minimization to find the optimal dead time parameter
* for the specified spectrum index. Results are printed to stdout.
* * @param i Spectrum index to minimize (0-based index into second dimension)
* * @note Prints minimization results including dead time estimate and errors
* @note The index i must be less than dims[1] (number of spectra)
*/
void minimize(const int i);
/**
* @brief Get the dimensions of the count dataset
* * Returns the dimensions [periods, spectra, bins] of the count data
* being used for dead time calculation.
* * @return Vector of dimension sizes (typically 3D: [periods, spectra, bins])
*/
const std::vector<hsize_t>& GetDimensions() const { return fDims; }
std::vector<float> GetDeadTimeEstimated() { return fDeadTimeEstimated; }
private:
bool fDebug{false}; ///< Debug flag - if true, print additional diagnostic information
bool fValid; ///< Validity flag - true if required data was loaded successfully
int fGoodFrames{-1}; ///< Number of good time frames for analysis
float fTimeResolution{0.0}; ///< Time resolution in picoseconds
std::vector<hsize_t>fDims = {0, 0, 0}; ///< Dimensions of count data [periods, spectra, bins]
int fT0Bin{-1}; ///< T0 bin number (time zero reference)
int fFgbBin{-1}; ///< First good bin for analysis
int fLgbBin{-1}; ///< Last good bin for analysis
std::vector<float> fDeadTime; ///< Dead time values per detector (microseconds) from the file
std::vector<float> fDeadTimeEstimated; ///< Dead time values per detector (microseconds) as estimated from the data
std::vector<int> fCounts; ///< Count data for minimization
double fUp{0.5}; ///< UP parameter for Minuit2 (0.5 for chi-square)
unsigned int fIdx{0}; ///< Current spectrum index being minimized
};
/**
* @class PNXdata
* @brief Template class for storing HDF5 dataset content with attributes
*
* The PNXdata class stores data read from an HDF5 dataset along with metadata
* such as dimensions, datatype, and attributes. It is designed to be stored in
* std::map<std::string, std::any> where the key is the HDF5 path.
*
* @tparam T The element type of the dataset (int, float, std::string, etc.)
*
* Key features:
* - Stores multi-dimensional data as flattened std::vector<T>
* - Preserves dimension information (shape)
* - Stores HDF5 DataType for type safety
* - Supports attributes as nested PNXdata objects
* - Compatible with std::any for type-erased storage
*
* @example
* @code
* // Create PNXdata for a 3D integer dataset (1 x 96 x 2048)
* PNXdata<int> counts;
* counts.SetDimensions({1, 96, 2048});
* counts.SetData(dataVector);
*
* // Store in map
* std::map<std::string, std::any> dataMap;
* dataMap["/raw_data_1/detector_1/counts"] = counts;
* @endcode
*/
template <typename T> class PNXdata {
public:
/**
* @brief Default constructor
*/
PNXdata() : fDataType(H5::PredType::NATIVE_INT) {}
/**
* @brief Constructor with datatype
* @param dataType HDF5 datatype for this dataset
*/
PNXdata(const H5::DataType& dataType) : fDataType(dataType) {}
/**
* @brief Get the HDF5 DataType
* @return The HDF5 DataType for this dataset
*/
H5::DataType GetDataType() const { return fDataType; }
/**
* @brief Set the HDF5 DataType
* @param dataType The HDF5 DataType to set
*/
void SetDataType(const H5::DataType& dataType) { fDataType = dataType; }
/**
* @brief Get the data as a vector
* @return Reference to the data vector
*/
const std::vector<T>& GetData() const { return fData; }
/**
* @brief Get mutable reference to the data vector
* @return Reference to the data vector
*/
std::vector<T>& GetData() { return fData; }
/**
* @brief Set the data vector
* @param data Vector of data to store
*/
void SetData(const std::vector<T>& data) { fData = data; }
/**
* @brief Get the dimensions of the dataset
* @return Vector of dimension sizes
*/
const std::vector<hsize_t>& GetDimensions() const { return fDimensions; }
/**
* @brief Set the dimensions of the dataset
* @param dims Vector of dimension sizes
*/
void SetDimensions(const std::vector<hsize_t>& dims) { fDimensions = dims; }
/**
* @brief Get the number of dimensions
* @return Number of dimensions (rank)
*/
size_t GetRank() const { return fDimensions.size(); }
/**
* @brief Get total number of elements
* @return Product of all dimensions
*/
size_t GetNumElements() const {
if (fDimensions.empty()) return 0;
size_t total = 1;
for (auto dim : fDimensions) {
total *= dim;
}
return total;
}
/**
* @brief Add an attribute to this dataset
* @param name Attribute name
* @param value Attribute value (stored as std::any to support different types)
*/
void AddAttribute(const std::string& name, const std::any& value) {
fAttributes[name] = value;
}
/**
* @brief Get an attribute by name
* @param name Attribute name
* @return The attribute value as std::any
* @throws std::out_of_range if attribute doesn't exist
*/
std::any GetAttribute(const std::string& name) const {
return fAttributes.at(name);
}
/**
* @brief Check if an attribute exists
* @param name Attribute name
* @return true if attribute exists, false otherwise
*/
bool HasAttribute(const std::string& name) const {
return fAttributes.find(name) != fAttributes.end();
}
/**
* @brief Get all attributes
* @return Map of attribute names to values
*/
const std::map<std::string, std::any>& GetAttributes() const {
return fAttributes;
}
/**
* @brief Get mutable reference to all attributes
* @return Mutable map of attribute names to values
*/
std::map<std::string, std::any>& GetAttributes() {
return fAttributes;
}
private:
H5::DataType fDataType; ///< HDF5 datatype of the dataset
std::vector<T> fData; ///< Data storage (flattened multi-dimensional array)
std::vector<hsize_t> fDimensions; ///< Dimensions of the dataset
std::map<std::string, std::any> fAttributes; ///< Attributes associated with this dataset
};
/**
* @class PNeXus
* @brief NeXus HDF5 file reader with case-insensitive path lookup
*
* The PNeXus class provides functionality for reading ISIS muon NeXus HDF5 files
* using the HDF5 C++ API. It implements case-insensitive path lookup for
* datasets, groups, and attributes to handle files with varying path casings.
*
* Key features:
* - Automatic file reading upon construction
* - Case-insensitive path resolution for datasets and groups
* - Case-insensitive attribute name matching
* - Comprehensive exception handling with HDF5 C++ exceptions
*
* @example
* @code
* PNeXus nexus("data/experiment.nxs");
* // Case-insensitive paths work: "/RAW_DATA_1/IDF_version" -> "/raw_data_1/IDF_version"
* @endcode
*
* @note All path lookups throw appropriate HDF5 C++ exceptions on failure
*/
class PNeXus {
public:
PNeXus();
/**
* @brief Constructor - creates PNeXus object and reads the NeXus file
* @param fln Filename of the NeXus HDF5 file to read
* @throws H5::FileIException if file cannot be opened
* @throws H5::AttributeIException if required attributes are missing
* @throws H5::DataSetIException if required datasets are missing
*/
PNeXus(const std::string fln, const bool printDebug=false);
/**
* @brief Get the filename of the NeXus file
* @return The filename as a string
*/
std::string GetFileName() const { return fFileName; }
/**
* @brief Get the hdf5 version of the NeXus file
* @return The hdf5 version as a string
*/
std::string GetHdf5Version() const { return fHdf5Version; }
/**
* @brief Get the NeXus version of the file
* @return The NeXus version as a string
*/
std::string GetNeXusVersion() const { return fNeXusVersion; }
/**
* @brief Get the Idf version of the file
* @return The Idf version tag
*/
int GetIdfVersion() const { return fIdfVersion; }
/**
* @brief Read and parse the NeXus HDF5 file
* @return 0 on success, 1 on error
* @throws H5::FileIException if file cannot be opened
* @throws H5::AttributeIException if required attributes are missing
* @throws H5::DataSetIException if required datasets are missing
*/
int ReadNexusFile();
/**
* @brief Get the data map containing all datasets
* @return Reference to the data map (path -> PNXdata stored in std::any)
*/
const std::map<std::string, std::any>& GetDataMap() const { return fDataMap; }
/**
* @brief Get mutable reference to the data map
* @return Reference to the data map
*/
std::map<std::string, std::any>& GetDataMap() { return fDataMap; }
/**
* @brief Check if a dataset path exists in the data map
* @param path HDF5 path to check
* @return true if path exists, false otherwise
*/
bool HasDataset(const std::string& path) const { return fDataMap.find(path) != fDataMap.end(); }
/**
* @brief Add or update a dataset in the data map
* @tparam T Data type of the PNXdata object
* @param path HDF5 path for the dataset
* @param data PNXdata object to store
*/
template <typename T>
void SetDataset(const std::string& path, const PNXdata<T>& data) {
fDataMap[path] = data;
}
/**
* @brief Get a dataset from the data map
* @tparam T Data type of the PNXdata object
* @param path HDF5 path of the dataset
* @return PNXdata object
* @throws std::out_of_range if path doesn't exist
* @throws std::bad_any_cast if type T doesn't match stored type
*/
template <typename T>
PNXdata<T> GetDataset(const std::string& path) const {
return std::any_cast<PNXdata<T>>(fDataMap.at(path));
}
/**
* @brief Get a mutable reference to a dataset in the data map
* @tparam T Data type of the PNXdata object
* @param path HDF5 path of the dataset
* @return Reference to PNXdata object
* @throws std::out_of_range if path doesn't exist
* @throws std::bad_any_cast if type T doesn't match stored type
*/
template <typename T>
PNXdata<T>& GetDatasetRef(const std::string& path) {
return std::any_cast<PNXdata<T>&>(fDataMap.at(path));
}
/**
* @brief Remove a dataset from the data map
* @param path HDF5 path of the dataset to remove
* @return true if dataset was removed, false if path didn't exist
*/
bool RemoveDataset(const std::string& path) {
auto it = fDataMap.find(path);
if (it != fDataMap.end()) {
fDataMap.erase(it);
return true;
}
return false;
}
/**
* @brief Clear all datasets from the data map
*/
void ClearDataMap() { fDataMap.clear(); }
/**
* @brief Get the number of datasets in the data map
* @return Number of datasets stored
*/
size_t GetNumDatasets() const { return fDataMap.size(); }
/**
* @brief Update the data of an existing dataset
* @tparam T Data type of the PNXdata object
* @param path HDF5 path of the dataset
* @param newData New data vector to set
* @return true if update succeeded, false if path doesn't exist
*/
template <typename T>
bool UpdateDatasetData(const std::string& path, const std::vector<T>& newData) {
if (!HasDataset(path)) return false;
try {
auto& dataset = std::any_cast<PNXdata<T>&>(fDataMap.at(path));
dataset.SetData(newData);
return true;
} catch (const std::bad_any_cast&) {
return false;
}
}
/**
* @brief Update the dimensions of an existing dataset
* @tparam T Data type of the PNXdata object
* @param path HDF5 path of the dataset
* @param newDimensions New dimensions vector to set
* @return true if update succeeded, false if path doesn't exist
*/
template <typename T>
bool UpdateDatasetDimensions(const std::string& path, const std::vector<hsize_t>& newDimensions) {
if (!HasDataset(path)) return false;
try {
auto& dataset = std::any_cast<PNXdata<T>&>(fDataMap.at(path));
dataset.SetDimensions(newDimensions);
return true;
} catch (const std::bad_any_cast&) {
return false;
}
}
/**
* @brief Add or update an attribute for a dataset
* @tparam T Data type of the PNXdata object
* @param path HDF5 path of the dataset
* @param attrName Attribute name
* @param attrValue Attribute value (stored as std::any)
* @return true if attribute was added, false if dataset doesn't exist
*/
template <typename T>
bool AddDatasetAttribute(const std::string& path, const std::string& attrName,
const std::any& attrValue) {
if (!HasDataset(path)) return false;
try {
auto& dataset = std::any_cast<PNXdata<T>&>(fDataMap.at(path));
dataset.AddAttribute(attrName, attrValue);
return true;
} catch (const std::bad_any_cast&) {
return false;
}
}
/**
* @brief Remove an attribute from a dataset
* @tparam T Data type of the PNXdata object
* @param path HDF5 path of the dataset
* @param attrName Attribute name to remove
* @return true if attribute was removed, false if dataset doesn't exist or attribute not found
*/
template <typename T>
bool RemoveDatasetAttribute(const std::string& path, const std::string& attrName) {
if (!HasDataset(path)) return false;
try {
auto& dataset = std::any_cast<PNXdata<T>&>(fDataMap.at(path));
auto& attrs = dataset.GetAttributes();
auto it = attrs.find(attrName);
if (it != attrs.end()) {
attrs.erase(it);
return true;
}
return false;
} catch (const std::bad_any_cast&) {
return false;
}
}
/**
* @brief Create and add a new dataset with data, dimensions, and optional attributes
* @tparam T Data type of the PNXdata object
* @param path HDF5 path for the new dataset
* @param data Data vector
* @param dimensions Dimensions vector
* @param dataType HDF5 DataType (optional, uses default if not provided)
* @return true if dataset was created, false if path already exists
*/
template <typename T>
bool AddDataset(const std::string& path, const std::vector<T>& data,
const std::vector<hsize_t>& dimensions,
const H5::DataType& dataType = H5::PredType::NATIVE_INT) {
if (HasDataset(path)) return false;
PNXdata<T> dataset(dataType);
dataset.SetData(data);
dataset.SetDimensions(dimensions);
fDataMap[path] = dataset;
return true;
}
/**
* @brief Modify an existing dataset's data and dimensions
* @tparam T Data type of the PNXdata object
* @param path HDF5 path of the dataset
* @param newData New data vector
* @param newDimensions New dimensions vector
* @return true if modification succeeded, false if path doesn't exist
*/
template <typename T>
bool ModifyDataset(const std::string& path, const std::vector<T>& newData,
const std::vector<hsize_t>& newDimensions) {
if (!HasDataset(path)) return false;
try {
auto& dataset = std::any_cast<PNXdata<T>&>(fDataMap.at(path));
dataset.SetData(newData);
dataset.SetDimensions(newDimensions);
return true;
} catch (const std::bad_any_cast&) {
return false;
}
}
/**
* @brief Dump the read hdf5-NeXus file content which was read
*/
void Dump();
/**
* @brief Write the data map contents to a NeXus HDF5 file
* @param filename Path to the output NeXus HDF5 file
* @param idfVersion IDF version to write (default: 2)
* @return 0 on success, 1 on error
* @throws H5::FileIException if file cannot be created
* @throws H5::GroupIException if group creation fails
* @throws H5::DataSetIException if dataset writing fails
*/
int WriteNexusFile(const std::string& filename, int idfVersion = 2);
/**
* @brief Add or update an attribute for a group
* @param groupPath HDF5 path of the group (e.g., "/raw_data_1")
* @param attrName Attribute name
* @param attrValue Attribute value (stored as std::any)
* @return true if attribute was added successfully
* @example
* @code
* nexus.AddGroupAttribute("/raw_data_1", "NX_class", std::string("NXentry"));
* @endcode
*/
bool AddGroupAttribute(const std::string& groupPath, const std::string& attrName,
const std::any& attrValue);
/**
* @brief Remove an attribute from a group
* @param groupPath HDF5 path of the group
* @param attrName Attribute name to remove
* @return true if attribute was removed, false if group or attribute not found
*/
bool RemoveGroupAttribute(const std::string& groupPath, const std::string& attrName);
/**
* @brief Check if a group has a specific attribute
* @param groupPath HDF5 path of the group
* @param attrName Attribute name to check
* @return true if attribute exists, false otherwise
*/
bool HasGroupAttribute(const std::string& groupPath, const std::string& attrName) const;
/**
* @brief Get an attribute value from a group
* @param groupPath HDF5 path of the group
* @param attrName Attribute name
* @return The attribute value as std::any
* @throws std::out_of_range if group or attribute doesn't exist
*/
std::any GetGroupAttribute(const std::string& groupPath, const std::string& attrName) const;
/**
* @brief Get all attributes for a group
* @param groupPath HDF5 path of the group
* @return Map of attribute names to values
*/
const std::map<std::string, std::any>& GetGroupAttributes(const std::string& groupPath) const;
/**
* @brief Clear all attributes from a group
* @param groupPath HDF5 path of the group
* @return true if group was found and attributes cleared, false otherwise
*/
bool ClearGroupAttributes(const std::string& groupPath);
/**
* @brief Add or update an attribute at the root level "/"
* @param attrName Attribute name
* @param attrValue Attribute value (stored as std::any)
* @return true if attribute was added successfully
* @note This is a convenience method that calls AddGroupAttribute("/", attrName, attrValue)
* @example
* @code
* nexus.AddRootAttribute("experiment_id", std::string("EXP-2024-001"));
* nexus.AddRootAttribute("temperature", 293.15f);
* nexus.AddRootAttribute("scan_number", static_cast<int32_t>(42));
* @endcode
*/
bool AddRootAttribute(const std::string& attrName, const std::any& attrValue);
private:
bool fPrintDebug{false}; ///< if true print additional debug information
std::string fFileName{""}; ///< NeXus HDF5 filename
int fIdfVersion{-1}; ///< IDF version of the NeXus file
std::string fHdf5Version{""}; ///< HDF5 version of the file
std::string fNeXusVersion{""}; ///< NeXus version of the file
std::string fFileNameNxs{""};
std::string fFileTimeNxs{""};
std::string fCreatorNxs{""};
std::string fUserV1{""};
std::map<std::string, std::any> fDataMap; ///< Map of HDF5 paths to PNXdata objects
std::map<std::string, std::map<std::string, std::any>> fGroupAttributes; ///< Map of group paths to their attributes
/**
* @brief HandleIdfV1
* @param file
*/
void HandleIdfV1(H5::H5File &file);
/**
* @brief HandleIdfV2
* @param file
*/
void HandleIdfV2(H5::H5File &file);
// ========================================================================
// Write methods for HDF5 file creation
// ========================================================================
/**
* @brief Create nested group hierarchy for a given path
* @param file HDF5 file object
* @param path Full path (e.g., "/raw_data_1/detector_1")
* @return H5::Group handle to the deepest created/opened group
* @throws H5::GroupIException if group creation fails
*/
H5::Group CreateGroupHierarchy(H5::H5File& file, const std::string& path);
/**
* @brief Write dataset attributes from PNXdata object
* @tparam T The data type of the PNXdata object
* @param dataset HDF5 dataset object to write attributes to
* @param data PNXdata object containing attributes
*/
template <typename T>
void WriteDatasetAttributes(H5::DataSet& dataset, const PNXdata<T>& data);
/**
* @brief Write attributes to a group
* @param group HDF5 group object to write attributes to
* @param attributes Map of attribute names to values
*/
void WriteGroupAttributes(H5::Group& group, const std::map<std::string, std::any>& attributes);
/**
* @brief Write an integer dataset with attributes
* @param file HDF5 file object
* @param path Dataset path (groups created automatically)
* @param data PNXdata object containing data, dimensions, and attributes
* @throws H5::Exception if writing fails
*/
void WriteIntDataset(H5::H5File& file, const std::string& path,
const PNXdata<int>& data);
/**
* @brief Write a float dataset with attributes
* @param file HDF5 file object
* @param path Dataset path (groups created automatically)
* @param data PNXdata object containing data, dimensions, and attributes
* @throws H5::Exception if writing fails
*/
void WriteFloatDataset(H5::H5File& file, const std::string& path,
const PNXdata<float>& data);
/**
* @brief Write a string dataset with attributes
* @param file HDF5 file object
* @param path Dataset path (groups created automatically)
* @param data PNXdata object containing data, dimensions, and attributes
* @throws H5::Exception if writing fails
*/
void WriteStringDataset(H5::H5File& file, const std::string& path,
const PNXdata<std::string>& data);
/**
* @brief Write root-level file attributes
* @param file HDF5 file object
* @throws H5::Exception if writing fails
*/
void WriteFileAttributes(H5::H5File& file);
/**
* @brief Write IDF version 2 structure
* @param file HDF5 file object
* @throws H5::Exception if writing fails
*/
void WriteIdfV2(H5::H5File& file);
// ========================================================================
// Case-insensitive lookup helper methods
// ========================================================================
/**
* @brief Compare two strings case-insensitively
* @param a First string to compare
* @param b Second string to compare
* @return true if strings are equal (ignoring case), false otherwise
* @note Uses std::tolower for character-by-character comparison
*/
static bool caseInsensitiveEquals(const std::string& a, const std::string& b);
/**
* @brief Split an HDF5 path into components
* @param path HDF5 path string (e.g., "/raw_data_1/IDF_version")
* @return Vector of path components
* @note Empty first component indicates absolute path
* @example "/raw_data_1/IDF_version" -> ["", "raw_data_1", "IDF_version"]
*/
static std::vector<std::string> splitPath(const std::string& path);
/**
* @brief Find attribute name with case-insensitive matching
* @param obj HDF5 File object to search
* @param requestedName Requested attribute name (any case)
* @return Correctly-cased attribute name as it exists in the file
* @throws H5::AttributeIException if attribute not found
* @example "nexus_version" might resolve to "NeXus_version"
*/
std::string findAttributeName(H5::H5File& obj, const std::string& requestedName);
/**
* @brief Find group path with case-insensitive matching (File version)
* @param parent HDF5 File object (root)
* @param requestedPath Requested group path (any case)
* @return Correctly-cased group path as it exists in the file
* @throws H5::GroupIException if group not found or path invalid
* @example "/RAW_DATA_1" might resolve to "/raw_data_1"
*/
std::string findGroupPath(H5::H5File& parent, const std::string& requestedPath);
/**
* @brief Find group path with case-insensitive matching (Group version)
* @param parent HDF5 Group object to start search from
* @param requestedPath Requested group path (any case)
* @return Correctly-cased group path as it exists in the file
* @throws H5::GroupIException if group not found or path invalid
*/
std::string findGroupPath(H5::Group& parent, const std::string& requestedPath);
/**
* @brief Find dataset path with case-insensitive matching (File version)
* @param parent HDF5 File object (root)
* @param requestedPath Requested dataset path (any case)
* @return Correctly-cased dataset path as it exists in the file
* @throws H5::DataSetIException if dataset not found or path invalid
* @example "/RAW_DATA_1/idf_VERSION" might resolve to "/raw_data_1/IDF_version"
* @note Validates that final path component is actually a dataset
*/
std::string findDatasetPath(H5::H5File& parent, const std::string& requestedPath);
/**
* @brief Find dataset path with case-insensitive matching (Group version)
* @param parent HDF5 Group object to start search from
* @param requestedPath Requested dataset path (any case)
* @return Correctly-cased dataset path as it exists in the file
* @throws H5::DataSetIException if dataset not found or path invalid
* @note Validates that final path component is actually a dataset
*/
std::string findDatasetPath(H5::Group& parent, const std::string& requestedPath);
// ========================================================================
// Dataset reading helper methods
// ========================================================================
/**
* @brief Read an integer dataset and store in data map
* @param file HDF5 file object
* @param path Path to the dataset
* @throws H5::Exception if reading fails
*/
void ReadIntDataset(H5::H5File& file, const std::string& path);
/**
* @brief Read a float dataset and store in data map
* @param file HDF5 file object
* @param path Path to the dataset
* @throws H5::Exception if reading fails
*/
void ReadFloatDataset(H5::H5File& file, const std::string& path);
/**
* @brief Read a string dataset and store in data map
* @param file HDF5 file object
* @param path Path to the dataset
* @throws H5::Exception if reading fails
*/
void ReadStringDataset(H5::H5File& file, const std::string& path);
/**
* @brief Read dataset attributes and add to PNXdata object
* @tparam T The data type of the PNXdata object
* @param dataset HDF5 dataset object
* @param data PNXdata object to add attributes to
*/
template <typename T>
void ReadDatasetAttributes(H5::DataSet& dataset, PNXdata<T>& data);
};
}
#endif // _PNEXUS_H_ #endif // _PNEXUS_H_