added HDF4 ISIS NeXus class handling.

This commit is contained in:
2026-01-25 10:51:23 +01:00
parent 3eea73f07a
commit 909fa6519d
7 changed files with 3071 additions and 178 deletions

View File

@@ -213,7 +213,35 @@ endif (qt_based_tools)
if (nexus) if (nexus)
find_package(HDF5 COMPONENTS CXX REQUIRED) find_package(HDF5 COMPONENTS CXX REQUIRED)
if (HAVE_HDF4) if (HAVE_HDF4)
find_package(HDF4 REQUIRED) #--- check for HDF4 -----------------------------------------------------------
# Find HDF4 manually (pkg-config often doesn't have hdf4)
find_path(HDF4_INCLUDE_DIR
NAMES mfhdf.h
PATHS /usr/include /usr/local/include
PATH_SUFFIXES hdf
)
find_library(HDF4_DF_LIBRARY
NAMES df libdf
PATHS /usr/lib64 /usr/lib /usr/local/lib64 /usr/local/lib
)
find_library(HDF4_MFHDF_LIBRARY
NAMES mfhdf libmfhdf
PATHS /usr/lib64 /usr/lib /usr/local/lib64 /usr/local/lib
)
if (HDF4_INCLUDE_DIR AND HDF4_DF_LIBRARY AND HDF4_MFHDF_LIBRARY)
set(HDF4_FOUND TRUE)
set(HDF4_INCLUDE_DIRS ${HDF4_INCLUDE_DIR})
set(HDF4_LIBRARIES ${HDF4_MFHDF_LIBRARY} ${HDF4_DF_LIBRARY})
message(STATUS "Found HDF4: ${HDF4_INCLUDE_DIR}")
message(STATUS " HDF4 libraries: ${HDF4_LIBRARIES}")
else()
message(FATAL_ERROR "HDF4 library not found. Please install libhdf4-dev or hdf-devel")
endif()
include_directories(${HDF4_INCLUDE_DIRS})
add_definitions(-DHAVE_HDF4) add_definitions(-DHAVE_HDF4)
endif (HAVE_HDF4) endif (HAVE_HDF4)
add_definitions(-DPNEXUS_ENABLED) add_definitions(-DPNEXUS_ENABLED)

View File

@@ -1,97 +0,0 @@
## Process this file with cmake
#=============================================================================
# NeXus - Neutron & X-ray Common Data Format
#
# CMakeLists for building the NeXus library and applications.
#
# Copyright (C) 2011 Stephen Rankin
#
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
# for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# For further information, see <http://www.nexusformat.org>
#
#
#=============================================================================
#------------------------------------------------------------------------------
# find the runtime binaries of the HDF4 library
#------------------------------------------------------------------------------
find_library(HDF4_DF_LIBRARY NAMES df hdf
HINTS ENV HDF4_ROOT
PATH_SUFFIXES hdf)
if(HDF4_DF_LIBRARY MATCHES HDF4_DF_LIBRARY-NOTFOUND)
message(FATAL_ERROR "Could not find HDF4 DF library!")
else()
get_filename_component(HDF4_LIBRARY_DIRS ${HDF4_DF_LIBRARY} PATH)
message(STATUS "Found HDF4 DF library: ${HDF4_DF_LIBRARY}")
message(STATUS "HDF4 libary path: ${HDF4_LIBRARY_DIRS}")
endif()
find_library(HDF4_MFHDF_LIBRARY NAMES mfhdf
HINTS ENV HDF4_ROOT
PATH_SUFFIXES hdf)
if(HDF4_MFHDF_LIBRARY MATCHES HDF4_MFHDF_LIBRARY-NOTFOUND)
message(FATAL_ERROR "Could not find HDF5 MFHDF library!")
else()
message(STATUS "Found HDF4 MFHDF library: ${HDF4_MFHDF_LIBRARY}")
endif()
#------------------------------------------------------------------------------
# find the HDF4 header file
#------------------------------------------------------------------------------
find_path(HDF4_INCLUDE_DIRS mfhdf.h
HINTS ENV HDF4_ROOT
PATH_SUFFIXES hdf)
if(HDF4_INCLUDE_DIRS MATCHES HDF4_INCLUDE_DIRS-NOTFOUND)
message(FATAL_ERROR "Could not find HDF4 header files")
else()
message(STATUS "Found HDF4 header files in: ${HDF4_INCLUDE_DIRS}")
endif()
#------------------------------------------------------------------------------
# search for additional packages required to link against HDF4
#------------------------------------------------------------------------------
find_package(JPEG REQUIRED)
#------------------------------------------------------------------------------
# add libraries to the link list for NAPI
#------------------------------------------------------------------------------
get_filename_component(LIB_EXT ${HDF4_DF_LIBRARY} EXT)
if(LIB_EXT MATCHES .a)
message(STATUS "HDF4 DF library is static")
list(APPEND NAPI_LINK_LIBS "-Wl,-whole-archive" ${HDF4_DF_LIBRARY} "-Wl,-no-whole-archive")
else()
list(APPEND NAPI_LINK_LIBS ${HDF4_DF_LIBRARY})
endif()
get_filename_component(LIB_EXT ${HDF4_MFHDF_LIBRARY} EXT)
if(LIB_EXT MATCHES .a)
message(STATUS "HDF4 MFHDF library is static")
list(APPEND NAPI_LINK_LIBS "-Wl,-whole-archive" ${HDF4_MFHDF_LIBRARY} "-Wl,-no-whole-archive")
else()
list(APPEND NAPI_LINK_LIBS ${HDF4_MFHDF_LIBRARY})
endif()
list(APPEND NAPI_LINK_LIBS jpeg)
include_directories ( SYSTEM ${HDF4_INCLUDE_DIRS} )
link_directories(${HDF4_LIBRARY_DIRS})

View File

@@ -1,34 +0,0 @@
# - find MXML
# find the MXML lib and includes
# This module defines
# LIBMXML_INCLUDE_DIR, where to find mxml.h
# LIBMXML_LIBRARY, library to link against
# LIBMXML_FOUND, if false, do not try to use the MXML lib
find_path(LIBMXML_INCLUDE_DIR mxml.h
HINT "/usr/include"
)
# find position of mxml.h from the end
string(FIND "${LIBMXML_INCLUDE_DIR}" "/mxml.h" pos REVERSE)
# truncate the string
string(SUBSTRING "${LIBMXML_INCLUDE_DIR}" 0 ${pos} substr)
set(LIBMXML_INCLUDE_DIR ${substr})
unset(substr)
find_library(LIBMXML_LIBRARY mxml)
# get version string
# currently do not know from where to get it automatically
# handle the QUIETLY and REQUIRED arguments and set LIBMXML_FOUND to TRUE if
# all listed variables are TRUE
include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(MXML
REQUIRED_VARS LIBMXML_LIBRARY LIBMXML_INCLUDE_DIR)
if (NOT LIBMXML_FOUND)
unset(LIBMXML_LIBRARY)
endif()
mark_as_advanced(LIBMXML_INCLUDE_DIR LIBMXML_LIBRARY)

View File

@@ -1,45 +0,0 @@
# - Find NeXus library
# Find the native NEXUS includes and library
# This module defines
# NEXUS_INCLUDE_DIR, where to find NeXus.h, etc.
# NEXUS_LIBRARY, library to link against to use NEXUS
# NEXUS_FOUND, if false, do not try to use NEXUS.
find_path(NEXUS_INCLUDE_DIR napi.h
HINTS "/usr/local/include" "/opt/nexus/include" "/usr/local/include/nexus"
)
# find position of napi.h from the end
string(FIND "${NEXUS_INCLUDE_DIR}" "/napi.h" pos REVERSE)
# truncate the string
string(SUBSTRING "${NEXUS_INCLUDE_DIR}" 0 ${pos} substr)
set(NEXUS_INCLUDE_DIR ${substr})
unset(substr)
find_library(NEXUS_LIBRARY NeXus
HINTS "/usr/lib" "/usr/lib64" "/usr/local/lib" "/usr/local/lib64" "/opt/nexus/lib")
# get version string
if (NEXUS_INCLUDE_DIR AND EXISTS ${NEXUS_INCLUDE_DIR}/napi.h)
file(STRINGS "${NEXUS_INCLUDE_DIR}/napi.h" NEXUS_version_str
REGEX "^#define[\t ]+NEXUS_VERSION[\t ].*")
string(REGEX REPLACE "^#define[\t ]+NEXUS_VERSION[\t ]+\"([^\"]*).*"
"\\1" NEXUS_VERSION_STRING "${NEXUS_version_str}")
unset(NEXUS_version_str)
endif()
# handle the QUIETLY and REQUIRED arguments and set NEXUS_FOUND to TRUE if
# all listed variables are TRUE
include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(NEXUS
REQUIRED_VARS NEXUS_LIBRARY NEXUS_INCLUDE_DIR
VERSION_VAR NEXUS_VERSION_STRING)
if (NOT NEXUS_FOUND)
unset(NEXUS_LIBRARY)
endif()
mark_as_advanced(NEXUS_INCLUDE_DIR NEXUS_LIBRARY)

View File

@@ -28,7 +28,7 @@ target_include_directories(
) )
#--- add library dependencies ------------------------------------------------- #--- add library dependencies -------------------------------------------------
target_link_libraries(PNeXus ${NEXUS_LIBRARY}) target_link_libraries(PNeXus ${HDF4_LIBRARIES} ${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

@@ -31,6 +31,19 @@
#define _PNEXUS_H_ #define _PNEXUS_H_
#include <string> #include <string>
#include <vector>
#include <map>
#include <cctype>
#include <algorithm>
#include <any>
#include <cstdint>
#include "Minuit2/FCNBase.h"
#ifdef HAVE_HDF4
#include <mfhdf.h>
#include <hdf.h>
#endif // HAVE_HDF4
namespace nxs { namespace nxs {
@@ -44,4 +57,839 @@ HDFType checkHDFType(const std::string& filename);
} // end namespace nxs } // end namespace nxs
#ifdef HAVE_HDF4
namespace nxH4 {
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
* nxH4::PNeXus nexus("file.nxs");
* nxH4::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 nxH4::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<uint32_t>& GetDimensions() const { return fDims; }
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<uint32_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)
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
};
/**
* @brief HDF4 data type enumeration
*
* This enum maps to HDF4 numeric types (DFNT_*) for type-safe dataset handling
*/
enum class H4DataType {
INT32, ///< 32-bit signed integer (DFNT_INT32)
FLOAT32, ///< 32-bit floating point (DFNT_FLOAT32)
FLOAT64, ///< 64-bit floating point (DFNT_FLOAT64)
CHAR8, ///< 8-bit character (DFNT_CHAR8)
UINT32, ///< 32-bit unsigned integer (DFNT_UINT32)
INT16, ///< 16-bit signed integer (DFNT_INT16)
UINT16, ///< 16-bit unsigned integer (DFNT_UINT16)
INT8, ///< 8-bit signed integer (DFNT_INT8)
UINT8 ///< 8-bit unsigned integer (DFNT_UINT8)
};
/**
* @class PNXdata
* @brief Template class for storing HDF4 dataset content with attributes
*
* The PNXdata class stores data read from an HDF4 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 HDF4 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 HDF4 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(H4DataType::INT32) {}
/**
* @brief Constructor with datatype
* @param dataType HDF4 datatype for this dataset
*/
PNXdata(const H4DataType& dataType) : fDataType(dataType) {}
/**
* @brief Get the HDF4 DataType
* @return The HDF4 DataType for this dataset
*/
H4DataType GetDataType() const { return fDataType; }
/**
* @brief Set the HDF4 DataType
* @param dataType The HDF4 DataType to set
*/
void SetDataType(const H4DataType& 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<uint32_t>& GetDimensions() const { return fDimensions; }
/**
* @brief Set the dimensions of the dataset
* @param dims Vector of dimension sizes
*/
void SetDimensions(const std::vector<uint32_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:
H4DataType fDataType; ///< HDF4 datatype of the dataset
std::vector<T> fData; ///< Data storage (flattened multi-dimensional array)
std::vector<uint32_t> fDimensions; ///< Dimensions of the dataset
std::map<std::string, std::any> fAttributes; ///< Attributes associated with this dataset
};
/**
* @class PNeXus
* @brief NeXus HDF4 file reader with case-insensitive path lookup
*
* The PNeXus class provides functionality for reading ISIS muon NeXus HDF4 files
* using the HDF4 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
*
* @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 exceptions on failure
*/
class PNeXus {
public:
PNeXus();
/**
* @brief Constructor - creates PNeXus object and reads the NeXus file
* @param fln Filename of the NeXus HDF4 file to read
* @param printDebug Enable debug output
* @throws std::runtime_error if file cannot be opened or read
*/
PNeXus(const std::string fln, const bool printDebug=false);
/**
* @brief Destructor - closes HDF4 file if open
*/
~PNeXus();
/**
* @brief Get the filename of the NeXus file
* @return The filename as a string
*/
std::string GetFileName() const { return fFileName; }
/**
* @brief Get the hdf4 version of the NeXus file
* @return The hdf4 version as a string
*/
std::string GetHdf4Version() const { return fHdf4Version; }
/**
* @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 HDF4 file
* @return 0 on success, 1 on error
* @throws std::runtime_error if file cannot be opened or read
*/
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 HDF4 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 HDF4 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 HDF4 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 HDF4 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 HDF4 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 HDF4 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 HDF4 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<uint32_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 HDF4 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 HDF4 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 HDF4 path for the new dataset
* @param data Data vector
* @param dimensions Dimensions vector
* @param dataType HDF4 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<uint32_t>& dimensions,
const H4DataType& dataType = H4DataType::INT32) {
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 HDF4 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<uint32_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 hdf4-NeXus file content which was read
*/
void Dump();
/**
* @brief Write the data map contents to a NeXus HDF4 file
* @param filename Path to the output NeXus HDF4 file
* @param idfVersion IDF version to write (default: 2)
* @return 0 on success, 1 on error
* @throws std::runtime_error if file cannot be created or written
*/
int WriteNexusFile(const std::string& filename, int idfVersion = 2);
/**
* @brief Add or update an attribute for a group
* @param groupPath HDF4 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 HDF4 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 HDF4 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 HDF4 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 HDF4 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 HDF4 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 HDF4 filename
int fIdfVersion{-1}; ///< IDF version of the NeXus file
std::string fHdf4Version{""}; ///< HDF4 version of the file
std::string fNeXusVersion{""}; ///< NeXus version of the file
std::string fFileNameNxs{""};
std::string fFileTimeNxs{""};
std::string fCreatorNxs{""};
std::string fUserV1{""};
int32 fSdId{-1}; ///< HDF4 SD interface identifier
int32 fFileId{-1}; ///< HDF4 file identifier
std::map<std::string, std::any> fDataMap; ///< Map of HDF4 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 sd_id HDF4 SD interface ID
*/
void HandleIdfV1(int32 sd_id);
/**
* @brief HandleIdfV2
* @param sd_id HDF4 SD interface ID
*/
void HandleIdfV2(int32 sd_id);
// ========================================================================
// Write methods for HDF4 file creation
// ========================================================================
/**
* @brief Write dataset attributes from PNXdata object
* @tparam T The data type of the PNXdata object
* @param sds_id HDF4 dataset ID to write attributes to
* @param data PNXdata object containing attributes
*/
template <typename T>
void WriteDatasetAttributes(int32 sds_id, const PNXdata<T>& data);
/**
* @brief Write attributes to a group
* @param sd_id HDF4 SD interface ID
* @param groupPath Group path
* @param attributes Map of attribute names to values
*/
void WriteGroupAttributes(int32 sd_id, const std::string& groupPath,
const std::map<std::string, std::any>& attributes);
/**
* @brief Write an integer dataset with attributes
* @param sd_id HDF4 SD interface ID
* @param path Dataset path
* @param data PNXdata object containing data, dimensions, and attributes
* @throws std::runtime_error if writing fails
*/
void WriteIntDataset(int32 sd_id, const std::string& path,
const PNXdata<int>& data);
/**
* @brief Write a float dataset with attributes
* @param sd_id HDF4 SD interface ID
* @param path Dataset path
* @param data PNXdata object containing data, dimensions, and attributes
* @throws std::runtime_error if writing fails
*/
void WriteFloatDataset(int32 sd_id, const std::string& path,
const PNXdata<float>& data);
/**
* @brief Write a string dataset with attributes
* @param sd_id HDF4 SD interface ID
* @param path Dataset path
* @param data PNXdata object containing data, dimensions, and attributes
* @throws std::runtime_error if writing fails
*/
void WriteStringDataset(int32 sd_id, const std::string& path,
const PNXdata<std::string>& data);
/**
* @brief Write root-level file attributes
* @param sd_id HDF4 SD interface ID
* @throws std::runtime_error if writing fails
*/
void WriteFileAttributes(int32 sd_id);
/**
* @brief Write IDF version 2 structure
* @param sd_id HDF4 SD interface ID
* @throws std::runtime_error if writing fails
*/
void WriteIdfV2(int32 sd_id);
// ========================================================================
// 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 HDF4 path into components
* @param path HDF4 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 sd_id HDF4 SD interface ID
* @param requestedName Requested attribute name (any case)
* @return Correctly-cased attribute name as it exists in the file
* @throws std::runtime_error if attribute not found
* @example "nexus_version" might resolve to "NeXus_version"
*/
std::string findAttributeName(int32 sd_id, const std::string& requestedName);
/**
* @brief Find dataset index with case-insensitive matching
* @param sd_id HDF4 SD interface ID
* @param requestedName Requested dataset name (any case)
* @return Dataset index
* @throws std::runtime_error if dataset not found
*/
int32 findDatasetIndex(int32 sd_id, const std::string& requestedName);
/**
* @brief Find dataset reference by navigating VGroup hierarchy
* @param path Full hierarchical path (e.g., "/run/sample/name")
* @return Dataset reference number, or -1 if not found
* @note Uses VGroup navigation to resolve paths in HDF4 files with duplicate dataset names
*/
int32 findDatasetRefByPath(const std::string& path);
// ========================================================================
// Dataset reading helper methods
// ========================================================================
/**
* @brief Read an integer dataset and store in data map
* @param sd_id HDF4 SD interface ID
* @param path Path to the dataset
* @throws std::runtime_error if reading fails
*/
void ReadIntDataset(int32 sd_id, const std::string& path);
/**
* @brief Read a float dataset and store in data map
* @param sd_id HDF4 SD interface ID
* @param path Path to the dataset
* @throws std::runtime_error if reading fails
*/
void ReadFloatDataset(int32 sd_id, const std::string& path);
/**
* @brief Read a string dataset and store in data map
* @param sd_id HDF4 SD interface ID
* @param path Path to the dataset
* @throws std::runtime_error if reading fails
*/
void ReadStringDataset(int32 sd_id, const std::string& path);
/**
* @brief Read dataset attributes and add to PNXdata object
* @tparam T The data type of the PNXdata object
* @param sds_id HDF4 dataset ID
* @param data PNXdata object to add attributes to
*/
template <typename T>
void ReadDatasetAttributes(int32 sds_id, PNXdata<T>& data);
/**
* @brief Convert HDF4 data type to H4DataType enum
* @param hdf4_type HDF4 numeric type constant (DFNT_*)
* @return H4DataType enum value
*/
static H4DataType convertHdf4Type(int32 hdf4_type);
/**
* @brief Convert H4DataType enum to HDF4 data type
* @param dataType H4DataType enum value
* @return HDF4 numeric type constant (DFNT_*)
*/
static int32 convertToHdf4Type(H4DataType dataType);
};
}
#endif // HAVE_HDF4
#endif // _PNEXUS_H_ #endif // _PNEXUS_H_