From dd2f743b3a127816a1b1e4e03a6ecb1f98f8a64b Mon Sep 17 00:00:00 2001 From: Andreas Suter Date: Fri, 6 Feb 2026 08:45:58 +0100 Subject: [PATCH] added docu by Claude for PNeXus. --- src/external/nexus/PNeXus.cpp | 92 ++- src/external/nexus/PNeXus.h | 366 +++++++++- src/external/nexus/Usage.md | 1196 +++++++++++++++++++++++++++++++++ 3 files changed, 1636 insertions(+), 18 deletions(-) create mode 100644 src/external/nexus/Usage.md diff --git a/src/external/nexus/PNeXus.cpp b/src/external/nexus/PNeXus.cpp index 14b705a3..6e8622a4 100644 --- a/src/external/nexus/PNeXus.cpp +++ b/src/external/nexus/PNeXus.cpp @@ -80,6 +80,20 @@ #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) { std::ifstream file(filename, std::ios::binary); @@ -1996,8 +2010,21 @@ void nxH4::PNeXus::WriteDatasetAttributes(int32 sds_id, const PNXdata& 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, const std::string& attrName, const std::any& attrValue) @@ -2282,6 +2309,17 @@ int32 nxH4::PNeXus::findDatasetRefByPath(const std::string& path) 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) { 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) { switch (dataType) { @@ -2426,9 +2475,26 @@ nxH5::PNeXusDeadTime::PNeXusDeadTime(const PNeXus *nxs, bool debug) : fDebug(deb // nxH5::PNeXusDeadTime::operator() //============================================================================= /** - * @brief Calculates the negative log-likelihood with dead time correction parameter dtc. - * @param par - * @return + * @brief Calculate the negative log-likelihood for dead time correction + * + * 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 &par) const { @@ -2452,7 +2518,23 @@ double nxH5::PNeXusDeadTime::operator()(const std::vector &par) const // 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) { diff --git a/src/external/nexus/PNeXus.h b/src/external/nexus/PNeXus.h index 3de538fb..d0de0c0e 100644 --- a/src/external/nexus/PNeXus.h +++ b/src/external/nexus/PNeXus.h @@ -27,6 +27,77 @@ * 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("/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_ #define _PNEXUS_H_ @@ -47,19 +118,94 @@ #include +/** + * @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 { +/** + * @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 { - HDF4, - HDF5, - Unknown + HDF4, ///< HDF4 format (magic: 0x0e 0x03 0x13 0x01) + HDF5, ///< HDF5 format (magic: 0x89 'H' 'D' 'F' 0x0d 0x0a 0x1a 0x0a) + 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); } // end namespace nxs #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 { class PNeXus; @@ -362,6 +508,20 @@ private: */ class PNeXus { 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("/raw_data_1/run_number", {12345}, {1}, nxH4::H4DataType::INT32); + * nexus.WriteNexusFile("output.nxs"); + * @endcode + */ 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(); @@ -713,14 +886,32 @@ private: std::map> 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 + * + * @note IDF v1 is an older format primarily used by ISIS facilities */ 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 + * + * @note IDF v2 is the current standard format for ISIS muon NeXus files */ void HandleIdfV2(int32 sd_id); @@ -894,6 +1085,55 @@ private: } #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("/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 { class PNeXus; @@ -985,6 +1225,21 @@ public: */ const std::vector& 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 GetDeadTimeEstimated() { return fDeadTimeEstimated; } private: @@ -1182,14 +1437,48 @@ private: */ class PNeXus { 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 counts(16 * 66000, 0); + * nexus.AddDataset("/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(); /** * @brief Constructor - creates PNeXus object and reads the NeXus file * @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::AttributeIException if required attributes 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); @@ -1200,8 +1489,8 @@ public: std::string GetFileName() const { return fFileName; } /** - * @brief Get the hdf5 version of the NeXus file - * @return The hdf5 version as a string + * @brief Get the HDF5 version string from the file + * @return The HDF5 version as a string (e.g., "1.10.4") */ 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(); /** * @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 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 + * + * @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); @@ -1531,14 +1853,32 @@ private: std::map> fGroupAttributes; ///< Map of group paths to their attributes /** - * @brief HandleIdfV1 - * @param file + * @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 file HDF5 file object to read from + * + * @note IDF v1 is an older format primarily used by ISIS facilities */ void HandleIdfV1(H5::H5File &file); /** - * @brief HandleIdfV2 - * @param file + * @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 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); diff --git a/src/external/nexus/Usage.md b/src/external/nexus/Usage.md new file mode 100644 index 00000000..1d81c66f --- /dev/null +++ b/src/external/nexus/Usage.md @@ -0,0 +1,1196 @@ +# PNeXus Library Usage Documentation + +## Overview + +PNeXus is a C++ library for reading and writing NeXus files containing muon spin rotation (muSR) data. The library provides support for both HDF4 and HDF5 file formats with a unified, type-safe API based on the `PNXdata` template class. + +**Key Features:** +- **Dual Format Support**: Read/write both HDF4 (nxH4) and HDF5 (nxH5) files +- **Case-Insensitive Paths**: Automatic path resolution regardless of casing +- **Type-Safe Data Handling**: Template-based dataset storage with dimension tracking +- **IDF Version Support**: Handles both IDF v1 and v2 file structures +- **Dead Time Correction**: Built-in muon detector dead time analysis using ROOT Minuit2 +- **Group Attributes**: Full support for NeXus group hierarchy attributes + +## Table of Contents + +1. [Architecture Overview](#architecture-overview) +2. [File Type Detection](#file-type-detection) +3. [Reading NeXus Files](#reading-nexus-files) + - [HDF5 Files](#reading-hdf5-files) + - [HDF4 Files](#reading-hdf4-files) +4. [Writing NeXus Files](#writing-nexus-files) +5. [Working with the Data Map](#working-with-the-data-map) +6. [Working with Datasets](#working-with-datasets) +7. [Working with Group Attributes](#working-with-group-attributes) +8. [Dead Time Correction](#dead-time-correction) +9. [PNXdata Class Reference](#pnxdata-class-reference) +10. [PNeXus Class Reference](#pnexus-class-reference) +11. [Common Dataset Paths](#common-dataset-paths) +12. [Complete Examples](#complete-examples) +13. [Troubleshooting](#troubleshooting) +14. [Additional Resources](#additional-resources) + +--- + +## Architecture Overview + +The PNeXus library is organized into three namespaces: + +| Namespace | Description | Availability | +|-----------|-------------|--------------| +| `nxs` | Common utilities (file type detection, HDFType enum) | Always available | +| `nxH4` | HDF4 implementation | Requires `HAVE_HDF4` compile flag | +| `nxH5` | HDF5 implementation | Always available | + +### Data Organization + +NeXus files follow the ISIS muon format with this general structure: + +``` +/raw_data_1/ (NXentry) +├── IDF_version +├── beamline, definition, run_number, title +├── start_time, end_time, good_frames +├── detector_1/ (NXdata) - IDF v2 +│ ├── 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 +``` + +--- + +## File Type Detection + +Before reading a NeXus file, determine its format using `nxs::checkHDFType()`: + +```cpp +#include "PNeXus.h" + +std::string filename = "experiment.nxs"; +nxs::HDFType type = nxs::checkHDFType(filename); + +switch (type) { + case nxs::HDFType::HDF4: + std::cout << "HDF4 file detected" << std::endl; + // Use nxH4::PNeXus (if HAVE_HDF4 is defined) + break; + case nxs::HDFType::HDF5: + std::cout << "HDF5 file detected" << std::endl; + // Use nxH5::PNeXus + break; + case nxs::HDFType::Unknown: + std::cerr << "Unknown file format" << std::endl; + break; +} +``` + +### HDFType Enumeration + +| Value | Description | Magic Bytes | +|-------|-------------|-------------| +| `HDF4` | HDF4 format | `0x0e 0x03 0x13 0x01` | +| `HDF5` | HDF5 format | `0x89 'H' 'D' 'F' 0x0d 0x0a 0x1a 0x0a` | +| `Unknown` | Unrecognized format | - | + +--- + +## Reading NeXus Files + +### Reading HDF5 Files + +The HDF5 implementation (`nxH5::PNeXus`) is always available: + +```cpp +#include "PNeXus.h" +#include + +int main() { + try { + // Basic reading + nxH5::PNeXus nexus("EMU00139040.nxs"); + + // Or with debug output + nxH5::PNeXus nexus_debug("EMU00139040.nxs", true); + + // Access file metadata + std::cout << "File: " << nexus.GetFileName() << std::endl; + std::cout << "HDF5 version: " << nexus.GetHdf5Version() << std::endl; + std::cout << "NeXus version: " << nexus.GetNeXusVersion() << std::endl; + std::cout << "IDF version: " << nexus.GetIdfVersion() << std::endl; + + // Dump file contents + nexus.Dump(); + + } catch (const H5::Exception& e) { + std::cerr << "HDF5 error: " << e.getDetailMsg() << std::endl; + return 1; + } + return 0; +} +``` + +### Reading HDF4 Files + +The HDF4 implementation (`nxH4::PNeXus`) requires `HAVE_HDF4` to be defined: + +```cpp +#include "PNeXus.h" +#include + +int main() { +#ifdef HAVE_HDF4 + try { + // Basic reading + nxH4::PNeXus nexus("old_data.nxs"); + + // Or with debug output + nxH4::PNeXus nexus_debug("old_data.nxs", true); + + // Access file metadata + std::cout << "File: " << nexus.GetFileName() << std::endl; + std::cout << "HDF4 version: " << nexus.GetHdf4Version() << std::endl; + std::cout << "NeXus version: " << nexus.GetNeXusVersion() << std::endl; + std::cout << "IDF version: " << nexus.GetIdfVersion() << std::endl; + + // Dump file contents + nexus.Dump(); + + } catch (const std::runtime_error& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } +#else + std::cerr << "HDF4 support not compiled" << std::endl; +#endif + return 0; +} +``` + +### Unified Format Handling + +For code that needs to handle both formats: + +```cpp +#include "PNeXus.h" +#include +#include +#include + +int main(int argc, char* argv[]) { + if (argc < 2) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return 1; + } + + std::string filename = argv[1]; + nxs::HDFType type = nxs::checkHDFType(filename); + + try { + if (type == nxs::HDFType::HDF5) { + nxH5::PNeXus nexus(filename, true); + std::cout << "HDF5 file loaded successfully" << std::endl; + std::cout << "IDF version: " << nexus.GetIdfVersion() << std::endl; + std::cout << "Datasets: " << nexus.GetNumDatasets() << std::endl; + nexus.Dump(); + } +#ifdef HAVE_HDF4 + else if (type == nxs::HDFType::HDF4) { + nxH4::PNeXus nexus(filename, true); + std::cout << "HDF4 file loaded successfully" << std::endl; + std::cout << "IDF version: " << nexus.GetIdfVersion() << std::endl; + std::cout << "Datasets: " << nexus.GetNumDatasets() << std::endl; + nexus.Dump(); + } +#endif + else { + std::cerr << "Unknown or unsupported file format" << std::endl; + return 1; + } + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } + + return 0; +} +``` + +--- + +## Writing NeXus Files + +### Writing HDF5 Files + +```cpp +#include "PNeXus.h" +#include + +int main() { + try { + // Read input file + nxH5::PNeXus nexus("input.nxs", true); + + // Modify data as needed (see Data Map section) + + // Write to new file + int result = nexus.WriteNexusFile("output.nxs"); + + if (result == 0) { + std::cout << "Successfully wrote output.nxs" << std::endl; + } else { + std::cerr << "Failed to write file" << std::endl; + } + + } catch (const H5::Exception& e) { + std::cerr << "HDF5 error: " << e.getDetailMsg() << std::endl; + return 1; + } + return 0; +} +``` + +### Creating a New NeXus File from Scratch + +```cpp +#include "PNeXus.h" +#include + +int main() { + try { + // Create empty PNeXus object + nxH5::PNeXus nexus; + + // Create detector counts (1 period x 96 detectors x 2048 time bins) + std::vector counts(1 * 96 * 2048, 0); + for (size_t det = 0; det < 96; det++) { + for (size_t bin = 0; bin < 2048; bin++) { + size_t idx = det * 2048 + bin; + counts[idx] = 100 + bin; // Example data + } + } + + // Add counts dataset + nexus.AddDataset("/raw_data_1/instrument/detector_1/counts", + counts, {1, 96, 2048}, H5::PredType::NATIVE_INT32); + + // Add attributes to counts dataset + nexus.AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", + "signal", static_cast(1)); + nexus.AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", + "axes", std::string("period_index,spectrum_index,raw_time")); + + // Create time axis (bin edges) + std::vector rawTime(2049); + for (size_t i = 0; i < rawTime.size(); i++) { + rawTime[i] = i * 0.016f; // 16 ns bins in microseconds + } + nexus.AddDataset("/raw_data_1/instrument/detector_1/raw_time", + rawTime, {2049}, H5::PredType::NATIVE_FLOAT); + nexus.AddDatasetAttribute("/raw_data_1/instrument/detector_1/raw_time", + "units", std::string("microsecond")); + + // Add metadata datasets + nexus.AddDataset("/raw_data_1/run_number", + {12345}, {1}, H5::PredType::NATIVE_INT32); + nexus.AddDataset("/raw_data_1/title", + {"Test measurement"}, {1}, H5::StrType(H5::PredType::C_S1, H5T_VARIABLE)); + + // Add NeXus group attributes + nexus.AddGroupAttribute("/raw_data_1", "NX_class", std::string("NXentry")); + nexus.AddGroupAttribute("/raw_data_1/instrument", "NX_class", std::string("NXinstrument")); + nexus.AddGroupAttribute("/raw_data_1/instrument/detector_1", "NX_class", std::string("NXdetector")); + + // Write the file + int result = nexus.WriteNexusFile("new_file.nxs", 2); // IDF version 2 + + if (result == 0) { + std::cout << "Successfully created new_file.nxs" << std::endl; + } + + } catch (const H5::Exception& e) { + std::cerr << "HDF5 error: " << e.getDetailMsg() << std::endl; + return 1; + } + return 0; +} +``` + +### Writing HDF4 Files + +```cpp +#include "PNeXus.h" +#include + +int main() { +#ifdef HAVE_HDF4 + try { + // Read input file + nxH4::PNeXus nexus("input.nxs", true); + + // Write to new file + int result = nexus.WriteNexusFile("output.nxs", 2); + + if (result == 0) { + std::cout << "Successfully wrote output.nxs" << std::endl; + } + + } catch (const std::runtime_error& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } +#else + std::cerr << "HDF4 support not compiled" << std::endl; +#endif + return 0; +} +``` + +--- + +## Working with the Data Map + +All datasets are stored in a `std::map` where keys are HDF paths and values are `PNXdata` objects wrapped in `std::any`. + +### Accessing the Data Map + +```cpp +nxH5::PNeXus nexus("file.nxs"); + +// Read-only access +const auto& dataMap = nexus.GetDataMap(); + +// Mutable access +auto& dataMap = nexus.GetDataMap(); + +// Check number of datasets +std::cout << "Total datasets: " << nexus.GetNumDatasets() << std::endl; +``` + +### Checking Dataset Existence + +```cpp +if (nexus.HasDataset("/raw_data_1/detector_1/counts")) { + std::cout << "Counts dataset found" << std::endl; +} +``` + +### Getting Datasets + +```cpp +// Method 1: Get a copy (safe, but less efficient for modification) +auto counts = nexus.GetDataset("/raw_data_1/detector_1/counts"); + +// Method 2: Get a reference (efficient for in-place modification) +auto& countsRef = nexus.GetDatasetRef("/raw_data_1/detector_1/counts"); + +// Method 3: Direct std::any_cast (traditional approach) +auto counts = std::any_cast>( + dataMap.at("/raw_data_1/detector_1/counts") +); +``` + +### Adding/Updating Datasets + +```cpp +// Method 1: Using SetDataset (replaces if exists) +nxH5::PNXdata newData; +newData.SetDimensions({96}); +newData.SetData(std::vector(96, 1.0f)); +nexus.SetDataset("/raw_data_1/corrections", newData); + +// Method 2: Using AddDataset (fails if exists) +bool success = nexus.AddDataset("/raw_data_1/new_data", + dataVector, {96}, H5::PredType::NATIVE_FLOAT); + +// Method 3: Update existing dataset data only +success = nexus.UpdateDatasetData("/raw_data_1/detector_1/counts", newCounts); + +// Method 4: Modify data and dimensions together +success = nexus.ModifyDataset("/raw_data_1/detector_1/counts", + newCounts, {1, 96, 1024}); +``` + +### Removing Datasets + +```cpp +bool removed = nexus.RemoveDataset("/raw_data_1/old_data"); +if (removed) { + std::cout << "Dataset removed" << std::endl; +} + +// Clear all datasets +nexus.ClearDataMap(); +``` + +--- + +## Working with Datasets + +### Reading Dataset Properties + +```cpp +auto counts = nexus.GetDataset("/raw_data_1/detector_1/counts"); + +// Get dimensions +const auto& dims = counts.GetDimensions(); // e.g., {1, 96, 2048} +size_t rank = counts.GetRank(); // e.g., 3 +size_t numElements = counts.GetNumElements(); // e.g., 196608 + +// Get data +const auto& data = counts.GetData(); // std::vector + +// Get HDF5 data type +H5::DataType dtype = counts.GetDataType(); +``` + +### Multi-Dimensional Indexing + +Data is stored as flattened 1D vectors. For 3D data with dimensions `[dim0, dim1, dim2]`: + +```cpp +// Access element at [i, j, k] +size_t index = i * dims[1] * dims[2] + j * dims[2] + k; +int value = data[index]; +``` + +For 2D data with dimensions `[dim0, dim1]`: + +```cpp +// Access element at [i, j] +size_t index = i * dims[1] + j; +int value = data[index]; +``` + +### Working with Attributes + +```cpp +auto counts = nexus.GetDataset("/raw_data_1/detector_1/counts"); + +// Check if attribute exists +if (counts.HasAttribute("first_good_bin")) { + // Get attribute value + auto fgb = std::any_cast(counts.GetAttribute("first_good_bin")); + std::cout << "First good bin: " << fgb << std::endl; +} + +// Add or update attribute +counts.AddAttribute("processing_date", std::string("2026-02-06")); +counts.AddAttribute("scale_factor", 2.0f); + +// Get all attributes +const auto& attrs = counts.GetAttributes(); +for (const auto& [name, value] : attrs) { + std::cout << "Attribute: " << name << std::endl; +} + +// Update the data map after modifying +nexus.SetDataset("/raw_data_1/detector_1/counts", counts); +``` + +### Using Convenience Methods for Attributes + +```cpp +// Add attribute directly (without fetching dataset first) +nexus.AddDatasetAttribute("/raw_data_1/detector_1/counts", + "quality_flag", static_cast(1)); + +// Remove attribute +nexus.RemoveDatasetAttribute("/raw_data_1/detector_1/counts", + "old_attribute"); +``` + +### Supported Data Types + +| C++ Type | HDF5 Type | HDF4 Type | Notes | +|----------|-----------|-----------|-------| +| `int` / `int32_t` | `NATIVE_INT32` | `DFNT_INT32` | Most common for count data | +| `float` | `NATIVE_FLOAT` | `DFNT_FLOAT32` | Time axes, corrections | +| `std::string` | `StrType` | `DFNT_CHAR8` | Metadata strings | + +--- + +## Working with Group Attributes + +Groups in NeXus files use the `NX_class` attribute to identify their type. + +### Adding Group Attributes + +```cpp +nxH5::PNeXus nexus("file.nxs"); + +// Add NeXus class identifiers +nexus.AddGroupAttribute("/raw_data_1", "NX_class", std::string("NXentry")); +nexus.AddGroupAttribute("/raw_data_1/instrument", "NX_class", std::string("NXinstrument")); +nexus.AddGroupAttribute("/raw_data_1/instrument/detector_1", "NX_class", std::string("NXdetector")); + +// Add custom metadata +nexus.AddGroupAttribute("/raw_data_1", "experiment_id", std::string("EXP001")); +nexus.AddGroupAttribute("/raw_data_1", "temperature", 293.15f); +nexus.AddGroupAttribute("/raw_data_1", "run_number", static_cast(12345)); + +// Add root-level attribute (shorthand) +nexus.AddRootAttribute("file_creator", std::string("PNeXus Library")); +``` + +### Checking and Retrieving Group Attributes + +```cpp +// Check if attribute exists +if (nexus.HasGroupAttribute("/raw_data_1", "NX_class")) { + // Get single attribute + auto nxClass = std::any_cast( + nexus.GetGroupAttribute("/raw_data_1", "NX_class") + ); + std::cout << "NX_class: " << nxClass << std::endl; +} + +// Get all attributes for a group +const auto& attrs = nexus.GetGroupAttributes("/raw_data_1"); +for (const auto& [name, value] : attrs) { + if (auto* str = std::any_cast(&value)) { + std::cout << name << " (string): " << *str << std::endl; + } else if (auto* intVal = std::any_cast(&value)) { + std::cout << name << " (int): " << *intVal << std::endl; + } else if (auto* floatVal = std::any_cast(&value)) { + std::cout << name << " (float): " << *floatVal << std::endl; + } +} +``` + +### Removing Group Attributes + +```cpp +// Remove single attribute +bool removed = nexus.RemoveGroupAttribute("/raw_data_1", "old_attribute"); + +// Clear all attributes from group +bool cleared = nexus.ClearGroupAttributes("/raw_data_1/detector_1"); +``` + +### Common NeXus Group Classes + +| Group Path | NX_class | Description | +|------------|----------|-------------| +| `/raw_data_1` | `NXentry` | Top-level measurement entry | +| `/raw_data_1/instrument` | `NXinstrument` | Instrument information | +| `/raw_data_1/instrument/detector_1` | `NXdetector` | Detector group | +| `/raw_data_1/detector_1` | `NXdata` | Data group (IDF v2) | +| `/raw_data_1/sample` | `NXsample` | Sample information | +| `/raw_data_1/user_1` | `NXuser` | User information | + +--- + +## Dead Time Correction + +The `PNeXusDeadTime` class calculates dead time corrections for muon detector data using ROOT Minuit2 minimization. + +### Basic Usage + +```cpp +#include "PNeXus.h" +#include + +int main() { + try { + // Read NeXus file + nxH5::PNeXus nexus("EMU00139040.nxs", true); + + // Create dead time calculator + nxH5::PNeXusDeadTime dtCalc(&nexus, true); // true = debug output + + if (!dtCalc.IsValid()) { + std::cerr << "Dead time calculation not possible" << std::endl; + return 1; + } + + // Get dimensions + const auto& dims = dtCalc.GetDimensions(); + std::cout << "Minimizing for " << dims[1] << " spectra" << std::endl; + + // Minimize for each spectrum + for (unsigned int i = 0; i < dims[1]; i++) { + dtCalc.minimize(i); + } + + // Get estimated dead times + auto estimatedDT = dtCalc.GetDeadTimeEstimated(); + for (size_t i = 0; i < estimatedDT.size(); i++) { + std::cout << "Detector " << i << ": " << estimatedDT[i] << " us" << std::endl; + } + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } + return 0; +} +``` + +### Dead Time Model + +The dead time correction uses the non-paralyzable model: + +``` +N_corrected = N_raw / (1 - N_raw * tau) +``` + +Where: +- `N_raw` is the measured count rate +- `tau` is the dead time parameter (microseconds) +- `N_corrected` is the true count rate + +### HDF4 Dead Time Calculation + +```cpp +#ifdef HAVE_HDF4 +nxH4::PNeXus nexus("old_file.nxs", true); +nxH4::PNeXusDeadTime dtCalc(&nexus, true); + +if (dtCalc.IsValid()) { + const auto& dims = dtCalc.GetDimensions(); + for (unsigned int i = 0; i < dims[1]; i++) { + dtCalc.minimize(i); + } +} +#endif +``` + +--- + +## PNXdata Class Reference + +`PNXdata` is the core template class for storing dataset content. + +### Constructors + +```cpp +// Default constructor +PNXdata(); + +// Constructor with data type (HDF5) +PNXdata(const H5::DataType& dataType); + +// Constructor with data type (HDF4) +PNXdata(const H4DataType& dataType); +``` + +### Methods + +| Method | Description | +|--------|-------------| +| `GetData()` | Get const reference to data vector | +| `GetData()` (non-const) | Get mutable reference to data vector | +| `SetData(vector)` | Set the data vector | +| `GetDimensions()` | Get const reference to dimensions | +| `SetDimensions(vector)` | Set the dimensions | +| `GetRank()` | Get number of dimensions | +| `GetNumElements()` | Get total element count | +| `GetDataType()` | Get HDF data type | +| `SetDataType(type)` | Set HDF data type | +| `AddAttribute(name, value)` | Add or update attribute | +| `GetAttribute(name)` | Get attribute value | +| `HasAttribute(name)` | Check if attribute exists | +| `GetAttributes()` | Get all attributes as map | + +--- + +## PNeXus Class Reference + +### Constructors + +```cpp +// Default constructor (creates empty object) +PNeXus(); + +// Read constructor +PNeXus(const std::string& filename, bool printDebug = false); +``` + +### File Information Methods + +| Method | Description | +|--------|-------------| +| `GetFileName()` | Get the NeXus filename | +| `GetHdf5Version()` / `GetHdf4Version()` | Get HDF library version | +| `GetNeXusVersion()` | Get NeXus format version | +| `GetIdfVersion()` | Get IDF version (1 or 2) | + +### Data Map Methods + +| Method | Description | +|--------|-------------| +| `GetDataMap()` | Get const/mutable reference to data map | +| `HasDataset(path)` | Check if dataset exists | +| `GetDataset(path)` | Get dataset copy | +| `GetDatasetRef(path)` | Get dataset reference | +| `SetDataset(path, data)` | Add/update dataset | +| `RemoveDataset(path)` | Remove dataset | +| `ClearDataMap()` | Clear all datasets | +| `GetNumDatasets()` | Get dataset count | +| `UpdateDatasetData(path, data)` | Update data only | +| `UpdateDatasetDimensions(path, dims)` | Update dimensions only | +| `ModifyDataset(path, data, dims)` | Update both | +| `AddDataset(path, data, dims, type)` | Create new dataset | +| `AddDatasetAttribute(path, name, value)` | Add dataset attribute | +| `RemoveDatasetAttribute(path, name)` | Remove dataset attribute | + +### Group Attribute Methods + +| Method | Description | +|--------|-------------| +| `AddGroupAttribute(path, name, value)` | Add/update group attribute | +| `RemoveGroupAttribute(path, name)` | Remove group attribute | +| `HasGroupAttribute(path, name)` | Check if group attribute exists | +| `GetGroupAttribute(path, name)` | Get group attribute value | +| `GetGroupAttributes(path)` | Get all attributes for group | +| `ClearGroupAttributes(path)` | Clear all group attributes | +| `AddRootAttribute(name, value)` | Add root-level attribute | + +### I/O Methods + +| Method | Description | +|--------|-------------| +| `ReadNexusFile()` | Read/parse the NeXus file | +| `WriteNexusFile(filename, idfVersion)` | Write data map to file | +| `Dump()` | Print human-readable dump | + +--- + +## Common Dataset Paths + +### IDF Version 2 Paths + +| Path | Type | Dimensions | Description | +|------|------|------------|-------------| +| `/raw_data_1/IDF_version` | int | [1] | IDF version number | +| `/raw_data_1/beamline` | string | [1] | Beamline name | +| `/raw_data_1/run_number` | int | [1] | Run number | +| `/raw_data_1/title` | string | [1] | Experiment title | +| `/raw_data_1/start_time` | string | [1] | Start timestamp | +| `/raw_data_1/end_time` | string | [1] | End timestamp | +| `/raw_data_1/good_frames` | int | [1] | Good frame count | +| `/raw_data_1/detector_1/counts` | int | [P,S,B] | Count data | +| `/raw_data_1/detector_1/raw_time` | float | [B+1] | Time bin edges | +| `/raw_data_1/instrument/detector_1/counts` | int | [P,S,B] | Count data | +| `/raw_data_1/instrument/detector_1/resolution` | int | [1] | Time resolution (ps) | +| `/raw_data_1/instrument/detector_1/dead_time` | float | [S] | Dead time per detector | +| `/raw_data_1/instrument/detector_1/spectrum_index` | int | [S] | Spectrum indices | + +Where: P = periods, S = spectra, B = time bins + +### Common Attributes + +**Counts Dataset:** +- `signal` (int32_t): Signal axis indicator +- `axes` (string): Comma-separated axis names +- `t0_bin` (int32_t): T0 time bin +- `first_good_bin` (int32_t): First good bin +- `last_good_bin` (int32_t): Last good bin +- `resolution` (int32_t): Time resolution (ps) + +**Time Dataset:** +- `units` (string): Time units (e.g., "microsecond") + +--- + +## Complete Examples + +### Example 1: Read and Display File Contents + +```cpp +#include "PNeXus.h" +#include + +int main(int argc, char* argv[]) { + if (argc < 2) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return 1; + } + + std::string filename = argv[1]; + nxs::HDFType type = nxs::checkHDFType(filename); + + try { + if (type == nxs::HDFType::HDF5) { + nxH5::PNeXus nexus(filename, true); + + std::cout << "=== File Information ===" << std::endl; + std::cout << "Filename: " << nexus.GetFileName() << std::endl; + std::cout << "HDF5 version: " << nexus.GetHdf5Version() << std::endl; + std::cout << "IDF version: " << nexus.GetIdfVersion() << std::endl; + std::cout << "Datasets: " << nexus.GetNumDatasets() << std::endl; + + // Print all dataset paths + std::cout << "\n=== Dataset Paths ===" << std::endl; + const auto& dataMap = nexus.GetDataMap(); + for (const auto& [path, value] : dataMap) { + std::cout << " " << path << std::endl; + } + + // Get counts info + if (nexus.HasDataset("/raw_data_1/detector_1/counts")) { + auto counts = nexus.GetDataset("/raw_data_1/detector_1/counts"); + const auto& dims = counts.GetDimensions(); + + std::cout << "\n=== Counts Dataset ===" << std::endl; + std::cout << "Dimensions: "; + for (size_t i = 0; i < dims.size(); i++) { + std::cout << dims[i]; + if (i < dims.size() - 1) std::cout << " x "; + } + std::cout << std::endl; + std::cout << "Total elements: " << counts.GetNumElements() << std::endl; + + // Print attributes + std::cout << "Attributes:" << std::endl; + for (const auto& [name, value] : counts.GetAttributes()) { + std::cout << " " << name << std::endl; + } + } + } +#ifdef HAVE_HDF4 + else if (type == nxs::HDFType::HDF4) { + nxH4::PNeXus nexus(filename, true); + nexus.Dump(); + } +#endif + else { + std::cerr << "Unknown file format" << std::endl; + return 1; + } + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } + + return 0; +} +``` + +### Example 2: Read-Modify-Write Workflow + +```cpp +#include "PNeXus.h" +#include +#include + +int main() { + try { + // Read input file + nxH5::PNeXus nexus("input.nxs", true); + + // Check for required dataset + if (!nexus.HasDataset("/raw_data_1/detector_1/counts")) { + std::cerr << "Counts dataset not found" << std::endl; + return 1; + } + + // Get mutable reference to counts + auto& counts = nexus.GetDatasetRef("/raw_data_1/detector_1/counts"); + const auto& dims = counts.GetDimensions(); + auto& data = counts.GetData(); + + std::cout << "Processing " << dims[1] << " detectors, " + << dims[2] << " time bins" << std::endl; + + // Apply scaling factor + double scaleFactor = 1.5; + for (auto& val : data) { + val = static_cast(std::round(val * scaleFactor)); + } + + // Add processing attribute + counts.AddAttribute("scale_factor", static_cast(scaleFactor)); + counts.AddAttribute("processing_date", std::string("2026-02-06")); + + // Write to new file + int result = nexus.WriteNexusFile("scaled_output.nxs"); + if (result == 0) { + std::cout << "Successfully wrote scaled_output.nxs" << std::endl; + } + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } + return 0; +} +``` + +### Example 3: Extract Single Detector to Text File + +```cpp +#include "PNeXus.h" +#include +#include + +int main(int argc, char* argv[]) { + if (argc < 4) { + std::cerr << "Usage: " << argv[0] + << " " << std::endl; + return 1; + } + + std::string inputFile = argv[1]; + int detectorNum = std::stoi(argv[2]) - 1; // Convert to 0-indexed + std::string outputFile = argv[3]; + + try { + nxH5::PNeXus nexus(inputFile); + + // Get counts + auto counts = nexus.GetDataset("/raw_data_1/detector_1/counts"); + const auto& dims = counts.GetDimensions(); + const auto& countData = counts.GetData(); + + // Validate detector number + if (detectorNum < 0 || detectorNum >= static_cast(dims[1])) { + std::cerr << "Detector must be between 1 and " << dims[1] << std::endl; + return 1; + } + + // Get time axis + auto rawTime = nexus.GetDataset("/raw_data_1/detector_1/raw_time"); + const auto& timeData = rawTime.GetData(); + + // Write to text file + std::ofstream outfile(outputFile); + outfile << "# Detector " << (detectorNum + 1) << std::endl; + outfile << "# Time(us)\tCounts" << std::endl; + + for (size_t bin = 0; bin < dims[2]; bin++) { + size_t idx = detectorNum * dims[2] + bin; + float timeMidpoint = (timeData[bin] + timeData[bin + 1]) / 2.0f; + outfile << timeMidpoint << "\t" << countData[idx] << std::endl; + } + + std::cout << "Extracted detector " << (detectorNum + 1) + << " to " << outputFile << std::endl; + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } + return 0; +} +``` + +### Example 4: Apply Dead Time Correction and Save + +```cpp +#include "PNeXus.h" +#include +#include + +int main(int argc, char* argv[]) { + if (argc < 3) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return 1; + } + + try { + // Read input file + nxH5::PNeXus nexus(argv[1], true); + + // Get dead time calculator + nxH5::PNeXusDeadTime dtCalc(&nexus, false); + + if (!dtCalc.IsValid()) { + std::cerr << "Cannot calculate dead time" << std::endl; + return 1; + } + + // Run minimization for all spectra + const auto& dims = dtCalc.GetDimensions(); + std::cout << "Calculating dead time for " << dims[1] << " spectra..." << std::endl; + + for (unsigned int i = 0; i < dims[1]; i++) { + dtCalc.minimize(i); + } + + // Get estimated dead times + auto estimatedDT = dtCalc.GetDeadTimeEstimated(); + + // Create new dead_time dataset + nexus.AddDataset("/raw_data_1/detector_1/dead_time_estimated", + estimatedDT, {estimatedDT.size()}, + H5::PredType::NATIVE_FLOAT); + nexus.AddDatasetAttribute("/raw_data_1/detector_1/dead_time_estimated", + "units", std::string("microsecond")); + nexus.AddDatasetAttribute("/raw_data_1/detector_1/dead_time_estimated", + "description", + std::string("Estimated dead time from chi-square fit")); + + // Apply correction to counts + auto& counts = nexus.GetDatasetRef("/raw_data_1/detector_1/counts"); + auto& countData = counts.GetData(); + const auto& countDims = counts.GetDimensions(); + + for (size_t det = 0; det < countDims[1]; det++) { + float tau = estimatedDT[det]; + for (size_t bin = 0; bin < countDims[2]; bin++) { + size_t idx = det * countDims[2] + bin; + float raw = static_cast(countData[idx]); + if (raw > 0 && (1.0f - raw * tau) > 0) { + float corrected = raw / (1.0f - raw * tau); + countData[idx] = static_cast(std::round(corrected)); + } + } + } + + // Add processing attributes + counts.AddAttribute("dead_time_corrected", std::string("yes")); + counts.AddAttribute("correction_method", std::string("non-paralyzable")); + + // Write output + int result = nexus.WriteNexusFile(argv[2]); + if (result == 0) { + std::cout << "Successfully wrote " << argv[2] << std::endl; + } + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } + return 0; +} +``` + +### Example 5: Merge Multiple Runs + +```cpp +#include "PNeXus.h" +#include +#include + +int main(int argc, char* argv[]) { + if (argc < 4) { + std::cerr << "Usage: " << argv[0] + << " ..." << std::endl; + return 1; + } + + std::string outputFile = argv[1]; + std::vector inputFiles; + for (int i = 2; i < argc; i++) { + inputFiles.push_back(argv[i]); + } + + try { + // Read first file as template + nxH5::PNeXus merged(inputFiles[0], true); + + // Get counts from first file + auto& counts = merged.GetDatasetRef("/raw_data_1/detector_1/counts"); + const auto& dims = counts.GetDimensions(); + auto& mergedData = counts.GetData(); + + // Add counts from remaining files + for (size_t i = 1; i < inputFiles.size(); i++) { + std::cout << "Adding " << inputFiles[i] << "..." << std::endl; + + nxH5::PNeXus nexus(inputFiles[i]); + auto tempCounts = nexus.GetDataset("/raw_data_1/detector_1/counts"); + const auto& tempDims = tempCounts.GetDimensions(); + const auto& tempData = tempCounts.GetData(); + + // Verify dimensions match + if (tempDims != dims) { + std::cerr << "Dimension mismatch in " << inputFiles[i] << std::endl; + return 1; + } + + // Add counts + for (size_t j = 0; j < mergedData.size(); j++) { + mergedData[j] += tempData[j]; + } + } + + // Update title + if (merged.HasDataset("/raw_data_1/title")) { + auto& title = merged.GetDatasetRef("/raw_data_1/title"); + title.GetData()[0] = "Merged: " + std::to_string(inputFiles.size()) + " runs"; + } + + // Add merge information + counts.AddAttribute("merged_runs", static_cast(inputFiles.size())); + + // Write merged file + int result = merged.WriteNexusFile(outputFile); + if (result == 0) { + std::cout << "Merged " << inputFiles.size() + << " runs to " << outputFile << std::endl; + } + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } + return 0; +} +``` + +--- + +## Troubleshooting + +### Common Errors + +**"Error: No data to write (data map is empty)"** +- Ensure you've read a file or added datasets before writing + +**"Error: IDF version 1 writing not yet implemented"** +- Only IDF version 2 is currently supported for writing +- Check `GetIdfVersion()` before writing + +**"Data size mismatch with dimensions"** +- Ensure data vector size equals product of all dimensions +- Example: dims=[1,96,2048] requires 196608 elements + +**"std::bad_any_cast"** +- Type mismatch when accessing dataset +- Verify the correct template type is used + +**"H5::FileIException" or similar HDF5 errors** +- File not found, insufficient permissions, or corrupted file +- Check file exists and is readable + +### Debug Output + +Enable debug output to see detailed information: + +```cpp +nxH5::PNeXus nexus("file.nxs", true); // true = enable debug +``` + +Debug output shows: +- File open/close operations +- Dataset reads with dimensions +- Group creation during writing +- Attribute processing +- Error messages with context + +--- + +## Additional Resources + +- **HDF5 Documentation**: https://portal.hdfgroup.org/documentation/ +- **HDF4 Documentation**: https://portal.hdfgroup.org/documentation/HDF4.html +- **NeXus Format**: https://www.nexusformat.org/ +- **ISIS NeXus**: https://www.isis.stfc.ac.uk/Pages/NeXus-Muon-Data.aspx +- **ROOT Minuit2**: https://root.cern/doc/master/group__Minuit.html + +--- + +## License + +PNeXus is licensed under the GNU General Public License v2. + +Copyright (C) 2007-2026 by Andreas Suter (andreas.suter@psi.ch)