added docu by Claude for PNeXus.

This commit is contained in:
2026-02-06 08:45:58 +01:00
parent d5dbc12175
commit dd2f743b3a
3 changed files with 1636 additions and 18 deletions

View File

@@ -80,6 +80,20 @@
#include "PNeXus.h" #include "PNeXus.h"
//=============================================================================
// nxs::checkHDFType - Determine HDF file format from magic bytes
//=============================================================================
/**
* @brief Determine the HDF format type of a file by reading its header
*
* Opens the file in binary mode and reads the first 8 bytes to compare
* against known HDF4 and HDF5 magic byte signatures. This allows automatic
* format detection before attempting to open the file with the appropriate
* library.
*
* @param filename Path to the file to check
* @return nxs::HDFType indicating the format (HDF4, HDF5, or Unknown)
*/
nxs::HDFType nxs::checkHDFType(const std::string& filename) { nxs::HDFType nxs::checkHDFType(const std::string& filename) {
std::ifstream file(filename, std::ios::binary); std::ifstream file(filename, std::ios::binary);
@@ -1996,8 +2010,21 @@ void nxH4::PNeXus::WriteDatasetAttributes(int32 sds_id, const PNXdata<T>& data)
} }
//============================================================================= //=============================================================================
// Group attribute methods // Group attribute methods - manage attributes associated with HDF4 groups
//============================================================================= //=============================================================================
/**
* @brief Add or update an attribute for a group
*
* Stores an attribute value in the internal group attributes map. These
* attributes will be written when WriteNexusFile() is called. The root
* group "/" can be used for file-level attributes.
*
* @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
*/
bool nxH4::PNeXus::AddGroupAttribute(const std::string& groupPath, bool nxH4::PNeXus::AddGroupAttribute(const std::string& groupPath,
const std::string& attrName, const std::string& attrName,
const std::any& attrValue) const std::any& attrValue)
@@ -2282,6 +2309,17 @@ int32 nxH4::PNeXus::findDatasetRefByPath(const std::string& path)
return result_ref; return result_ref;
} }
/**
* @brief Convert HDF4 numeric type constant to H4DataType enum
*
* Maps HDF4 DFNT_* type constants to the library's H4DataType enum for
* type-safe handling of dataset types.
*
* @param hdf4_type HDF4 numeric type constant (e.g., DFNT_INT32)
* @return Corresponding H4DataType enum value
*
* @note Unknown types default to H4DataType::INT32
*/
nxH4::H4DataType nxH4::PNeXus::convertHdf4Type(int32 hdf4_type) nxH4::H4DataType nxH4::PNeXus::convertHdf4Type(int32 hdf4_type)
{ {
switch (hdf4_type) { switch (hdf4_type) {
@@ -2298,6 +2336,17 @@ nxH4::H4DataType nxH4::PNeXus::convertHdf4Type(int32 hdf4_type)
} }
} }
/**
* @brief Convert H4DataType enum to HDF4 numeric type constant
*
* Maps the library's H4DataType enum to HDF4 DFNT_* type constants
* for writing datasets to HDF4 files.
*
* @param dataType H4DataType enum value
* @return Corresponding HDF4 numeric type constant (e.g., DFNT_INT32)
*
* @note Unknown types default to DFNT_INT32
*/
int32 nxH4::PNeXus::convertToHdf4Type(H4DataType dataType) int32 nxH4::PNeXus::convertToHdf4Type(H4DataType dataType)
{ {
switch (dataType) { switch (dataType) {
@@ -2426,9 +2475,26 @@ nxH5::PNeXusDeadTime::PNeXusDeadTime(const PNeXus *nxs, bool debug) : fDebug(deb
// nxH5::PNeXusDeadTime::operator() // nxH5::PNeXusDeadTime::operator()
//============================================================================= //=============================================================================
/** /**
* @brief Calculates the negative log-likelihood with dead time correction parameter dtc. * @brief Calculate the negative log-likelihood for dead time correction
* @param par *
* @return * This function implements the objective function for ROOT Minuit2 minimization.
* It calculates the negative log-likelihood comparing observed counts to
* the expected count rate model with dead time correction.
*
* **Model:**
* The expected count rate includes:
* - Exponential muon decay: N(t) = N0 * exp(-t/tau_mu) + N_bkg
* - Dead time correction: N_corrected = N(t) / (1 + N(t) * dtc)
*
* **Parameters:**
* - par[0] (dtc): Dead time correction parameter
* - par[1] (N0): Initial count rate amplitude
* - par[2] (N_bkg): Background count rate
*
* @param par Vector of parameters [dtc, N0, N_bkg]
* @return Negative log-likelihood value to be minimized
*
* @note Uses the muon lifetime tau_mu = 2.1969811 microseconds
*/ */
double nxH5::PNeXusDeadTime::operator()(const std::vector<double> &par) const double nxH5::PNeXusDeadTime::operator()(const std::vector<double> &par) const
{ {
@@ -2452,7 +2518,23 @@ double nxH5::PNeXusDeadTime::operator()(const std::vector<double> &par) const
// nxH5::PNeXusDeadTime::minimize() // nxH5::PNeXusDeadTime::minimize()
//============================================================================= //=============================================================================
/** /**
* @brief PNeXusDeadTime::minimize * @brief Minimize dead time for a specific detector spectrum
*
* Performs ROOT Minuit2 minimization to find the optimal dead time parameter
* and count rate model parameters for the specified spectrum index. Uses
* MnMinimize with strategy 2 (high precision) and stores the result.
*
* **Minimization Parameters:**
* - dtc: Dead time correction (initial: 0.01, step: 0.01)
* - N0: Initial count rate (initial: estimated from data)
* - N_bkg: Background rate (initial: 0.1)
*
* @param i Spectrum index to minimize (0-based, must be < dims[1])
*
* @note Results are stored in fDeadTimeEstimated[i] and optionally printed
* to stdout if debug mode is enabled
*
* @see GetDeadTimeEstimated() to retrieve results after minimization
*/ */
void nxH5::PNeXusDeadTime::minimize(const int i) void nxH5::PNeXusDeadTime::minimize(const int i)
{ {

View File

@@ -27,6 +27,77 @@
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/ ***************************************************************************/
/**
* @file PNeXus.h
* @brief NeXus HDF4/HDF5 file reader and writer for muon spin rotation data
*
* This header file defines the PNeXus library for reading and writing NeXus
* files in both HDF4 and HDF5 formats. The library is specifically designed
* for ISIS muon NeXus files but can be adapted for other NeXus data.
*
* The library provides:
* - **Dual format support**: Both HDF4 (nxH4 namespace) and HDF5 (nxH5 namespace)
* - **Case-insensitive path lookup**: Handles varying path casings in NeXus files
* - **Type-safe data handling**: Template-based PNXdata class with type preservation
* - **IDF version support**: Handles both IDF version 1 and version 2 file structures
* - **Dead time correction**: Built-in muon spectrometry dead time analysis
*
* ## Architecture
*
* The library is organized into three namespaces:
* - **nxs**: Common utilities (HDFType enum, checkHDFType function)
* - **nxH4**: HDF4-specific implementation (conditional, requires HAVE_HDF4)
* - **nxH5**: HDF5-specific implementation (always available)
*
* ## Data Organization
*
* NeXus files follow the ISIS muon format with structure:
* @verbatim
* /raw_data_1/ (NXentry)
* ├── IDF_version
* ├── beamline, definition, run_number, title
* ├── start_time, end_time, good_frames
* ├── detector_1/ (NXdata)
* │ ├── counts (multi-dimensional)
* │ ├── raw_time
* │ └── attributes (signal, axes, units, etc.)
* └── instrument/ (NXinstrument)
* ├── name, source/
* └── detector_1/ (NXdetector)
* ├── counts, raw_time
* ├── resolution, spectrum_index
* └── dead_time
* @endverbatim
*
* ## Usage Example
*
* @code
* #include "PNeXus.h"
*
* // Check file type
* nxs::HDFType type = nxs::checkHDFType("file.nxs");
*
* if (type == nxs::HDFType::HDF5) {
* // Read HDF5 NeXus file
* nxH5::PNeXus nexus("file.nxs");
* nexus.Dump();
*
* // Access data
* auto counts = nexus.GetDataset<int>("/raw_data_1/detector_1/counts");
* auto dims = counts.GetDimensions();
* }
* @endcode
*
* @author Andreas Suter
* @date 2007-2026
* @copyright GNU General Public License v2
* @version 1.0
*
* @see nxs::checkHDFType()
* @see nxH4::PNeXus
* @see nxH5::PNeXus
*/
#ifndef _PNEXUS_H_ #ifndef _PNEXUS_H_
#define _PNEXUS_H_ #define _PNEXUS_H_
@@ -47,19 +118,94 @@
#include <H5Cpp.h> #include <H5Cpp.h>
/**
* @namespace nxs
* @brief Common utilities for NeXus file handling
*
* The nxs namespace provides utility functions that are common to both
* HDF4 and HDF5 implementations. This includes file type detection and
* shared enumerations.
*
* @see nxH4 for HDF4-specific implementation
* @see nxH5 for HDF5-specific implementation
*/
namespace nxs { namespace nxs {
/**
* @enum HDFType
* @brief Enumeration of supported HDF file types
*
* Used to identify whether a file is HDF4, HDF5, or of unknown format.
* This is determined by reading the file's magic bytes header.
*/
enum class HDFType { enum class HDFType {
HDF4, HDF4, ///< HDF4 format (magic: 0x0e 0x03 0x13 0x01)
HDF5, HDF5, ///< HDF5 format (magic: 0x89 'H' 'D' 'F' 0x0d 0x0a 0x1a 0x0a)
Unknown Unknown ///< Unrecognized file format
}; };
/**
* @brief Determine the HDF format type of a file by reading its header
*
* Reads the first 8 bytes of the file and compares them against known
* HDF4 and HDF5 magic byte signatures to determine the file format.
*
* **Magic Signatures:**
* - HDF5: `0x89 'H' 'D' 'F' 0x0d 0x0a 0x1a 0x0a` (8 bytes)
* - HDF4: `0x0e 0x03 0x13 0x01` (4 bytes)
*
* @param filename Path to the file to check
* @return HDFType indicating the file format (HDF4, HDF5, or Unknown)
*
* @note Returns Unknown if the file cannot be opened or is too small
*
* @example
* @code
* std::string file = "experiment.nxs";
* nxs::HDFType type = nxs::checkHDFType(file);
*
* switch (type) {
* case nxs::HDFType::HDF4:
* // Use nxH4::PNeXus
* break;
* case nxs::HDFType::HDF5:
* // Use nxH5::PNeXus
* break;
* default:
* std::cerr << "Unknown file format" << std::endl;
* }
* @endcode
*
* @see nxH4::PNeXus for HDF4 file handling
* @see nxH5::PNeXus for HDF5 file handling
*/
HDFType checkHDFType(const std::string& filename); HDFType checkHDFType(const std::string& filename);
} // end namespace nxs } // end namespace nxs
#ifdef HAVE_HDF4 #ifdef HAVE_HDF4
/**
* @namespace nxH4
* @brief HDF4 implementation of the NeXus file reader/writer
*
* The nxH4 namespace provides classes and functions for reading and writing
* NeXus files in HDF4 format. This implementation uses the HDF4 C API
* (mfhdf.h, hdf.h) and provides case-insensitive path lookup.
*
* **Key Classes:**
* - PNeXus: Main file reader/writer class
* - PNXdata: Template class for storing dataset content
* - PNeXusDeadTime: Dead time correction calculator
*
* **Requirements:**
* - HDF4 library (libmfhdf, libhdf)
* - HAVE_HDF4 must be defined at compile time
*
* @note This namespace is only available if compiled with HAVE_HDF4 defined
*
* @see nxH5 for the HDF5 implementation
* @see nxs::checkHDFType() to determine file format before reading
*/
namespace nxH4 { namespace nxH4 {
class PNeXus; class PNeXus;
@@ -362,6 +508,20 @@ private:
*/ */
class PNeXus { class PNeXus {
public: public:
/**
* @brief Default constructor - creates an empty PNeXus object
*
* Creates a PNeXus object without opening any file. Use this constructor
* when you want to build a NeXus file from scratch by adding datasets
* programmatically.
*
* @example
* @code
* nxH4::PNeXus nexus;
* nexus.AddDataset<int>("/raw_data_1/run_number", {12345}, {1}, nxH4::H4DataType::INT32);
* nexus.WriteNexusFile("output.nxs");
* @endcode
*/
PNeXus(); PNeXus();
/** /**
@@ -616,7 +776,20 @@ public:
} }
/** /**
* @brief Dump the read hdf4-NeXus file content which was read * @brief Print a human-readable dump of the NeXus file contents
*
* Outputs the contents of the loaded NeXus file to stdout in a
* hierarchical format. The output includes:
* - File metadata (HDF4 version, NeXus version, file name, file time)
* - IDF version
* - Run information (run number, title, start/stop time)
* - Sample information (name, temperature, magnetic field)
* - Instrument information
* - Detector counts with dimensions and first few values
*
* The output format differs based on IDF version (1 or 2).
*
* @note This method is primarily useful for debugging and verification
*/ */
void Dump(); void Dump();
@@ -713,14 +886,32 @@ private:
std::map<std::string, std::map<std::string, std::any>> fGroupAttributes; ///< Map of group paths to their attributes std::map<std::string, std::map<std::string, std::any>> fGroupAttributes; ///< Map of group paths to their attributes
/** /**
* @brief HandleIdfV1 * @brief Read datasets for IDF version 1 file structure
*
* Reads datasets specific to the IDF version 1 NeXus format, which uses
* a "/run/" group hierarchy. Datasets are organized under paths like:
* - /run/IDF_version, /run/number, /run/title
* - /run/sample/temperature, /run/sample/magnetic_field
* - /run/histogram_data_1/counts, /run/histogram_data_1/raw_time
*
* @param sd_id HDF4 SD interface ID * @param sd_id HDF4 SD interface ID
*
* @note IDF v1 is an older format primarily used by ISIS facilities
*/ */
void HandleIdfV1(int32 sd_id); void HandleIdfV1(int32 sd_id);
/** /**
* @brief HandleIdfV2 * @brief Read datasets for IDF version 2 file structure
*
* Reads datasets specific to the IDF version 2 NeXus format, which uses
* a "/raw_data_1/" group hierarchy. Datasets are organized under paths like:
* - /raw_data_1/IDF_version, /raw_data_1/run_number, /raw_data_1/title
* - /raw_data_1/instrument/detector_1/counts
* - /raw_data_1/instrument/detector_1/resolution
*
* @param sd_id HDF4 SD interface ID * @param sd_id HDF4 SD interface ID
*
* @note IDF v2 is the current standard format for ISIS muon NeXus files
*/ */
void HandleIdfV2(int32 sd_id); void HandleIdfV2(int32 sd_id);
@@ -894,6 +1085,55 @@ private:
} }
#endif // HAVE_HDF4 #endif // HAVE_HDF4
/**
* @namespace nxH5
* @brief HDF5 implementation of the NeXus file reader/writer
*
* The nxH5 namespace provides classes and functions for reading and writing
* NeXus files in HDF5 format. This implementation uses the HDF5 C++ API
* (H5Cpp.h) and provides case-insensitive path lookup.
*
* **Key Classes:**
* - PNeXus: Main file reader/writer class
* - PNXdata: Template class for storing dataset content with HDF5 DataTypes
* - PNeXusDeadTime: Dead time correction calculator
*
* **Requirements:**
* - HDF5 C++ library (libhdf5_cpp)
*
* **Key Features:**
* - Case-insensitive path resolution for datasets and groups
* - Multi-dimensional array support with dimension tracking
* - Attribute management for datasets and groups
* - Type-safe operations using PNXdata template class
* - IDF version 1 and 2 support
*
* @example
* @code
* // Read an HDF5 NeXus file
* nxH5::PNeXus nexus("experiment.nxs");
*
* // Access data with type safety
* if (nexus.HasDataset("/raw_data_1/detector_1/counts")) {
* auto counts = nexus.GetDataset<int>("/raw_data_1/detector_1/counts");
* auto dims = counts.GetDimensions(); // [periods, spectra, bins]
* auto data = counts.GetData(); // flattened data vector
* }
*
* // Calculate dead time corrections
* nxH5::PNeXusDeadTime dtCalc(&nexus, true);
* if (dtCalc.IsValid()) {
* for (size_t i = 0; i < dtCalc.GetDimensions()[1]; i++) {
* dtCalc.minimize(i);
* }
* }
* @endcode
*
* @note This is the primary implementation and is always available
*
* @see nxH4 for the HDF4 implementation
* @see nxs::checkHDFType() to determine file format before reading
*/
namespace nxH5 { namespace nxH5 {
class PNeXus; class PNeXus;
@@ -985,6 +1225,21 @@ public:
*/ */
const std::vector<hsize_t>& GetDimensions() const { return fDims; } const std::vector<hsize_t>& GetDimensions() const { return fDims; }
/**
* @brief Get the estimated dead time values from minimization
*
* Returns the dead time values calculated by the minimize() function
* for each spectrum. These values represent the dead time parameter
* that best fits the observed count data when accounting for dead
* time losses.
*
* @return Vector of estimated dead time values (in microseconds) per spectrum
*
* @note Call minimize() for each spectrum before calling this method
* to populate the estimated values
*
* @see minimize() for the calculation method
*/
std::vector<float> GetDeadTimeEstimated() { return fDeadTimeEstimated; } std::vector<float> GetDeadTimeEstimated() { return fDeadTimeEstimated; }
private: private:
@@ -1182,14 +1437,48 @@ private:
*/ */
class PNeXus { class PNeXus {
public: public:
/**
* @brief Default constructor - creates an empty PNeXus object
*
* Creates a PNeXus object without opening any file. Use this constructor
* when you want to build a NeXus file from scratch by adding datasets
* programmatically.
*
* @example
* @code
* nxH5::PNeXus nexus;
*
* // Add datasets programmatically
* std::vector<int> counts(16 * 66000, 0);
* nexus.AddDataset<int>("/raw_data_1/detector_1/counts", counts,
* {1, 16, 66000}, H5::PredType::NATIVE_INT);
*
* // Add attributes
* nexus.AddGroupAttribute("/raw_data_1", "NX_class", std::string("NXentry"));
*
* // Write to file
* nexus.WriteNexusFile("output.nxs");
* @endcode
*/
PNeXus(); PNeXus();
/** /**
* @brief Constructor - creates PNeXus object and reads the NeXus file * @brief Constructor - creates PNeXus object and reads the NeXus file
* @param fln Filename of the NeXus HDF5 file to read * @param fln Filename of the NeXus HDF5 file to read
* @param printDebug Enable debug output during reading (default: false)
* @throws H5::FileIException if file cannot be opened * @throws H5::FileIException if file cannot be opened
* @throws H5::AttributeIException if required attributes are missing * @throws H5::AttributeIException if required attributes are missing
* @throws H5::DataSetIException if required datasets are missing * @throws H5::DataSetIException if required datasets are missing
*
* @example
* @code
* try {
* nxH5::PNeXus nexus("experiment.nxs", true); // Enable debug output
* nexus.Dump(); // Print file contents
* } catch (const H5::Exception& e) {
* std::cerr << "HDF5 error: " << e.getDetailMsg() << std::endl;
* }
* @endcode
*/ */
PNeXus(const std::string fln, const bool printDebug=false); PNeXus(const std::string fln, const bool printDebug=false);
@@ -1200,8 +1489,8 @@ public:
std::string GetFileName() const { return fFileName; } std::string GetFileName() const { return fFileName; }
/** /**
* @brief Get the hdf5 version of the NeXus file * @brief Get the HDF5 version string from the file
* @return The hdf5 version as a string * @return The HDF5 version as a string (e.g., "1.10.4")
*/ */
std::string GetHdf5Version() const { return fHdf5Version; } std::string GetHdf5Version() const { return fHdf5Version; }
@@ -1434,18 +1723,51 @@ public:
} }
/** /**
* @brief Dump the read hdf5-NeXus file content which was read * @brief Print a human-readable dump of the NeXus file contents
*
* Outputs the contents of the loaded NeXus file to stdout in a
* hierarchical format. The output includes:
* - File metadata (HDF5 version, NeXus version, file name, file time)
* - IDF version
* - Run information (run number, title, start/end time, good frames)
* - Instrument and source information
* - Detector counts with dimensions, attributes, and first few values
* - Raw time and spectrum index data
*
* The output format differs based on IDF version (1 or 2).
*
* @note This method is primarily useful for debugging and verification
*
* @example
* @code
* nxH5::PNeXus nexus("experiment.nxs");
* nexus.Dump(); // Prints formatted output to stdout
* @endcode
*/ */
void Dump(); void Dump();
/** /**
* @brief Write the data map contents to a NeXus HDF5 file * @brief Write the data map contents to a NeXus HDF5 file
*
* Creates a new HDF5 file and writes all datasets from the internal
* data map along with their attributes. Group hierarchy is created
* automatically based on dataset paths.
*
* @param filename Path to the output NeXus HDF5 file * @param filename Path to the output NeXus HDF5 file
* @param idfVersion IDF version to write (default: 2) * @param idfVersion IDF version to write (default: 2)
* @return 0 on success, 1 on error * @return 0 on success, 1 on error
* @throws H5::FileIException if file cannot be created * @throws H5::FileIException if file cannot be created
* @throws H5::GroupIException if group creation fails * @throws H5::GroupIException if group creation fails
* @throws H5::DataSetIException if dataset writing fails * @throws H5::DataSetIException if dataset writing fails
*
* @note Currently only IDF version 2 is fully supported for writing
*
* @example
* @code
* nxH5::PNeXus nexus("input.nxs");
* // Modify data...
* nexus.WriteNexusFile("output.nxs", 2);
* @endcode
*/ */
int WriteNexusFile(const std::string& filename, int idfVersion = 2); int WriteNexusFile(const std::string& filename, int idfVersion = 2);
@@ -1531,14 +1853,32 @@ private:
std::map<std::string, std::map<std::string, std::any>> fGroupAttributes; ///< Map of group paths to their attributes std::map<std::string, std::map<std::string, std::any>> fGroupAttributes; ///< Map of group paths to their attributes
/** /**
* @brief HandleIdfV1 * @brief Read datasets for IDF version 1 file structure
* @param file *
* Reads datasets specific to the IDF version 1 NeXus format, which uses
* a "/run/" group hierarchy. Datasets are organized under paths like:
* - /run/IDF_version, /run/number, /run/title
* - /run/sample/temperature, /run/sample/magnetic_field
* - /run/histogram_data_1/counts, /run/histogram_data_1/raw_time
*
* @param file HDF5 file object to read from
*
* @note IDF v1 is an older format primarily used by ISIS facilities
*/ */
void HandleIdfV1(H5::H5File &file); void HandleIdfV1(H5::H5File &file);
/** /**
* @brief HandleIdfV2 * @brief Read datasets for IDF version 2 file structure
* @param file *
* Reads datasets specific to the IDF version 2 NeXus format, which uses
* a "/raw_data_1/" group hierarchy. Datasets are organized under paths like:
* - /raw_data_1/IDF_version, /raw_data_1/run_number, /raw_data_1/title
* - /raw_data_1/instrument/detector_1/counts
* - /raw_data_1/instrument/detector_1/resolution
*
* @param file HDF5 file object to read from
*
* @note IDF v2 is the current standard format for ISIS muon NeXus files
*/ */
void HandleIdfV2(H5::H5File &file); void HandleIdfV2(H5::H5File &file);

1196
src/external/nexus/Usage.md vendored Normal file

File diff suppressed because it is too large Load Diff