diff --git a/src/external/nexus/README.Cygwin b/src/external/nexus/README.Cygwin deleted file mode 100644 index 858d2760..00000000 --- a/src/external/nexus/README.Cygwin +++ /dev/null @@ -1,69 +0,0 @@ -2011/04/13 -- BMW - -Under Cygwin of all the required libraries for NeXus only HDF5 is available. -The packages and can be installed through the Cygwin setup. -One should also make sure that , and a package containing "/usr/lib/librpc.a" (e.g. = 4.0-3) are installed. - -All other libraries have to be built from the sources: - -* JPEG-6b - URL: http://www.hdfgroup.org/ftp/lib-external/jpeg/src/jpegsrc.v6b.tar.gz - Configure options: --prefix=/usr/local --enable-static - -* MXML 2.5 - URL: http://ftp.easysw.com/pub/mxml/2.5/mxml-2.5.tar.gz - Configure options: --prefix=/usr/local --enable-static - -* HDF 4.2.5 - URL: http://www.hdfgroup.org/ftp/HDF/HDF_Current/src/hdf-4.2.5.tar.gz - Configure options: --prefix=/usr/local --enable-static --disable-fortran --with-jpeg=/usr/local - -* NeXus 4.2.1 - URL: http://download.nexusformat.org/kits/nexus-4.2.1.tar.gz - Configure options: --prefix=/usr/local --with-hdf4=/usr/local --with-hdf5=/usr --with-xml=/usr/local - -The version numbers and source-code locations might of course change with time but should be easily adjustable. - -If one is confident enough that all requirements to build the above packages are fullfilled, one could also try to run the following lines as a script. -However, there is absolutely no warranty that it works. - ---- - -#!/bin/sh - -cd -mkdir nexus -cd nexus -curl http://www.hdfgroup.org/ftp/lib-external/jpeg/src/jpegsrc.v6b.tar.gz -G | tar xz -cd jpeg-6b -./configure --prefix=/usr/local --enable-static -make -make install -cd .. -curl http://ftp.easysw.com/pub/mxml/2.5/mxml-2.5.tar.gz -G | tar xz -cd mxml-2.5 -./configure --prefix=/usr/local --enable-static -make -make install -cd .. -curl http://www.hdfgroup.org/ftp/HDF/HDF_Current/src/hdf-4.2.5.tar.gz -G | tar xz -cd hdf-4.2.5 -./configure --prefix=/usr/local --enable-static --disable-fortran --with-jpeg=/usr/local -make -make install -cd .. -curl http://download.nexusformat.org/kits/nexus-4.2.1.tar.gz -G | tar xz -./configure --prefix=/usr/local --with-hdf4=/usr/local --with-hdf5=/usr --with-xml=/usr/local -make -make install - ---- - -In order to obtain NeXus support in musrfit after installing the above libraries, musrfit has to be configured with the options -"--enable-static --enable-NeXus" - -Further information on how to set up musrfit under Cygwin can be found here: -https://intranet.psi.ch/MUSR/MusrFitSetup#A_4_MS_Windows -http://lmu.web.psi.ch/facilities/software/musrfit/user/intranet.psi.ch/MUSR/MusrFitSetup.html#A_4_MS_Windows - -EOF diff --git a/src/external/nexus/examples/hdf4/CMakeLists.txt b/src/external/nexus/examples/hdf4/CMakeLists.txt new file mode 100644 index 00000000..6bfb7705 --- /dev/null +++ b/src/external/nexus/examples/hdf4/CMakeLists.txt @@ -0,0 +1,149 @@ +# - h4nexus +cmake_minimum_required(VERSION 3.26) + +project(h4nexus VERSION 0.1.0 LANGUAGES CXX) + +#--- set C++ standard --------------------------------------------------------- +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +#--- set a default build type if none was specified --------------------------- +set(default_build_type "Release") + +if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to '${default_build_type}' as none was specified.") + set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE + STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Release" "MinSizeRel" "RelWithDebInfo") +endif () + +#--- check for pkg-config ----------------------------------------------------- +find_package(PkgConfig REQUIRED) + +#--- check for git ------------------------------------------------------------ +find_package(Git 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}) + +#--- check for HDF5 ----------------------------------------------------------- +find_package(HDF5 REQUIRED COMPONENTS CXX) +if(NOT HDF5_FOUND) + message(FATAL_ERROR "HDF5 C++ library not found") +endif() +include_directories(${HDF5_INCLUDE_DIRS}) + +#--- check for ROOT ----------------------------------------------------------- +find_package(ROOT 6.36 REQUIRED COMPONENTS Minuit2) +if (ROOT_miniut2_FOUND) + execute_process(COMMAND root-config --bindir OUTPUT_VARIABLE ROOT_BINDIR) + string(STRIP ${ROOT_BINDIR} ROOT_BINDIR) + execute_process(COMMAND root-config --version OUTPUT_VARIABLE ROOT_VERSION) + string(STRIP ${ROOT_VERSION} ROOT_VERSION) + message("-- Found ROOT: ${ROOT_BINDIR} (found version: ${ROOT_VERSION})") + #---Define useful ROOT functions and macros (e.g. ROOT_GENERATE_DICTIONARY) + include(${ROOT_USE_FILE}) +endif (ROOT_miniut2_FOUND) + +#--- all checks done -> feed config.h ----------------------------------------- +set(HAVE_CONFIG_H 1 CACHE INTERNAL "config.h is available") +configure_file(${CMAKE_SOURCE_DIR}/cmake/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) + +#--- check if project source is a git repo ------------------------------------ +if (EXISTS "${CMAKE_SOURCE_DIR}/.git/HEAD") + message(STATUS "is a git repo") + set(IS_GIT_REPO 1) +else () + message(STATUS "is NOT a git repo") + set(IS_GIT_REPO 0) +endif () + +#--- start create git-revision.h ---------------------------------------------- +if (IS_GIT_REPO) + execute_process(COMMAND sh ${CMAKE_SOURCE_DIR}/git_revision.sh ${CMAKE_BINARY_DIR}) + + set(HAVE_GIT_REV_H "-DHAVE_GIT_REV_H") + set(GIT_REV_H "git-revision.h") +else (IS_GIT_REPO) + set(HAVE_GIT_REV_H "") + set(GIT_REV_H "") +endif (IS_GIT_REPO) + +#--- end create git-revision.h ------------------------------------------------ + +#--- write summary of the installation +cmake_host_system_information(RESULT PROCESSOR QUERY PROCESSOR_DESCRIPTION) + +message("") +message("|-----------------------------------------------------------------------|") +message("| |") +message("| Summary |") +message("| |") +message("|-----------------------------------------------------------------------|") +message("") +message(" System: ${CMAKE_HOST_SYSTEM_NAME} ${CMAKE_SYSTEM_PROCESSOR} - ${CMAKE_HOST_SYSTEM_VERSION}") +message(" Processor: ${PROCESSOR} (${CMAKE_SYSTEM_PROCESSOR})") +message(" ----------") +message("") +message(" h4nexus Version: ${h4nexus_VERSION}") +message(" ----------------") +message("") +message(" Build Type: ${CMAKE_BUILD_TYPE}") +message(" -----------") +message("") +message(" Requirements:") +message(" -------------") +message("") +message(" HDF4 found in ${HDF4_INCLUDE_DIRS}") +message(" ROOT found in ${ROOT_INCLUDE_DIRS}, Version: ${ROOT_VERSION}") +message("") +message(" Installation directories:") +message(" -------------------------") +message("") +message(" Programs : ${CMAKE_INSTALL_PREFIX}/bin") +message("") +message("-------------------------------------------------------------------------") +message("") + +#--- h4nexus executable ------------------------------------------------------- +add_executable(h4nexus + ../../PNeXus.cpp + main.cpp) +target_compile_options(h4nexus BEFORE PRIVATE "-DHAVE_HDF4 -DHAVE_CONFIG_H" ${HAVE_GIT_REV_H}) +target_include_directories(h4nexus + BEFORE PRIVATE + $ + $ + $ +) +target_link_libraries(h4nexus ${HDF4_LIBRARIES} ${HDF5_LIBRARIES} ${ROOT_LIBRARIES}) diff --git a/src/external/nexus/examples/hdf4/cmake/config.h.in b/src/external/nexus/examples/hdf4/cmake/config.h.in new file mode 100644 index 00000000..c4f2a648 --- /dev/null +++ b/src/external/nexus/examples/hdf4/cmake/config.h.in @@ -0,0 +1,7 @@ +/* config.h.in. Generated from CMakeLists.txt */ + +/* Define to 1 if you have the file. */ +#cmakedefine HAVE_CONFIG_H @HAVE_CONFIG_H@ + +/* h4nexus version */ +#define H4NEXUS_VERSION "@h4nexus_VERSION@" diff --git a/src/external/nexus/examples/hdf4/docu/README.md b/src/external/nexus/examples/hdf4/docu/README.md new file mode 100644 index 00000000..1ba03d95 --- /dev/null +++ b/src/external/nexus/examples/hdf4/docu/README.md @@ -0,0 +1,165 @@ +# h4nexus - handle muSR-NeXus files via HDF4 only + +## Contents + +Tests and classes to handle muSR-NeXus files directly via the HDF4 C API. + +This project provides the same API as h5nexus but uses HDF4 instead of HDF5 for handling NeXus files. + +## Features + +- **Read and write NeXus HDF4 files** with a clean C++ API +- **Case-insensitive path lookup** for datasets and groups +- **Type-safe data handling** using template classes +- **Dead time correction calculation** for muon detector data using ROOT Minuit2 +- **Compatible API with h5nexus** for easy migration + +## Key Classes + +- `nxH4::PNeXus` - Main class for reading/writing NeXus HDF4 files +- `nxH4::PNXdata` - Template class for storing dataset content with attributes +- `nxH4::PNeXusDeadTime` - Dead time correction calculator for muon detector data + +## Requirements + +- CMake >= 3.26 +- C++17 compatible compiler +- HDF4 library (libhdf4-dev or hdf-devel) +- ROOT >= 6.36 with Minuit2 component +- pkg-config + +## Building + +```bash +mkdir build +cd build +cmake .. +make +``` + +## Installation + +```bash +make install +``` + +This will install: +- Library: `libh4nexus.so` in `${CMAKE_INSTALL_PREFIX}/lib` +- Executable: `h4nexus` in `${CMAKE_INSTALL_PREFIX}/bin` +- Header: `PNeXus.h` in `${CMAKE_INSTALL_PREFIX}/include/h4nexus` + +## Usage + +### Command Line + +```bash +# Display help +h4nexus --help + +# Read and display a NeXus HDF4 file +h4nexus --fn input.nxs + +# Read with debug output +h4nexus --fn input.nxs --debug + +# Calculate dead time corrections +h4nexus --fn input.nxs --dead_time_estimate + +# Write output file +h4nexus --fn input.nxs --out output.nxs +``` + +### Programmatic Usage + +```cpp +#include + +// Read a NeXus file +nxH4::PNeXus nexus("data.nxs"); + +// Access datasets +auto counts_data = nexus.GetDataset("/raw_data_1/detector_1/counts"); +const auto& counts = counts_data.GetData(); +const auto& dims = counts_data.GetDimensions(); + +// Dump file contents +nexus.Dump(); + +// Write to new file +nexus.WriteNexusFile("output.nxs", 2); // IDF version 2 +``` + +### Creating Files from Scratch + +```cpp +nxH4::PNeXus nxs_out; + +// Add datasets +std::vector counts(16*66000, 0); +nxs_out.AddDataset("/raw_data_1/detector_1/counts", + counts, {1, 16, 66000}, + nxH4::H4DataType::INT32); + +// Add attributes +nxs_out.AddDatasetAttribute("/raw_data_1/detector_1/counts", + "units", std::string("counts")); + +// Add group attributes +nxs_out.AddGroupAttribute("/raw_data_1", "NX_class", std::string("NXentry")); + +// Write file +nxs_out.WriteNexusFile("output.nxs"); +``` + +## API Compatibility with h5nexus + +The h4nexus API is designed to be compatible with h5nexus. The main differences are: + +- Namespace: `nxH4::` instead of `nxH5::` +- Data types: `H4DataType` enum instead of `H5::DataType` +- Dimensions: Uses `uint32_t` instead of `hsize_t` + +Code migration typically requires only: +1. Changing namespace from `nxH5` to `nxH4` +2. Changing `H5::PredType::NATIVE_INT` to `nxH4::H4DataType::INT32` (etc.) +3. Changing dimension types from `hsize_t` to `uint32_t` + +## Supported Data Types + +- `H4DataType::INT32` - 32-bit signed integer +- `H4DataType::FLOAT32` - 32-bit floating point +- `H4DataType::FLOAT64` - 64-bit floating point +- `H4DataType::CHAR8` - 8-bit character/string +- `H4DataType::UINT32` - 32-bit unsigned integer +- `H4DataType::INT16` - 16-bit signed integer +- `H4DataType::UINT16` - 16-bit unsigned integer +- `H4DataType::INT8` - 8-bit signed integer +- `H4DataType::UINT8` - 8-bit unsigned integer + +## Documentation + +Generate Doxygen documentation (if Doxygen is installed): + +```bash +make doc +``` + +Documentation will be generated in the `doc/html` directory. + +## Differences from HDF5 + +HDF4 has some limitations compared to HDF5: +- No true hierarchical groups (simulated using naming conventions) +- Less flexible attribute handling +- Different maximum name lengths +- C API instead of C++ API + +The h4nexus library abstracts these differences to provide a similar interface to h5nexus. + +## License + +GNU General Public License v2 (GPLv2) + +## Contacts + +Andreas Suter diff --git a/src/external/nexus/examples/hdf4/git_revision.sh b/src/external/nexus/examples/hdf4/git_revision.sh new file mode 100755 index 00000000..d24abc0d --- /dev/null +++ b/src/external/nexus/examples/hdf4/git_revision.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Script to create git-revision.h with current git information +# Usage: git_revision.sh [output_directory] + +output_dir="${1:-.}" +output_file="${output_dir}/git-revision.h" + +# Check if we're in a git repository +if ! git rev-parse --git-dir > /dev/null 2>&1; then + echo "Not in a git repository, skipping git-revision.h generation" + exit 0 +fi + +# Get git information +git_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) +git_hash=$(git rev-parse --short HEAD 2>/dev/null) +git_date=$(git log -1 --format=%cd --date=short 2>/dev/null) + +# Create header file +cat > "$output_file" << EOF +// This file is auto-generated by git_revision.sh +// Do not edit manually + +#ifndef GIT_REVISION_H +#define GIT_REVISION_H + +#define GIT_BRANCH "$git_branch" +#define GIT_HASH "$git_hash" +#define GIT_DATE "$git_date" + +#endif // GIT_REVISION_H +EOF + +echo "Generated $output_file" diff --git a/src/external/nexus/examples/hdf4/main.cpp b/src/external/nexus/examples/hdf4/main.cpp new file mode 100644 index 00000000..1d3230a9 --- /dev/null +++ b/src/external/nexus/examples/hdf4/main.cpp @@ -0,0 +1,447 @@ +/*************************************************************************** + + main.cpp + + Author: Andreas Suter + e-mail: andreas.suter@psi.ch + +***************************************************************************/ + +/*************************************************************************** + * Copyright (C) 2007-2026 by Andreas Suter * + * andreas.suter@psi.ch * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +/** + * @file main.cpp + * @brief Command-line interface for the h4nexus NeXus HDF4 file reader/writer + * + * This file contains the main() function and command-line interface for the + * h4nexus program. It provides functionality to: + * - Read and display NeXus HDF4 files + * - Write NeXus HDF4 files using the PNXdata approach + * - Calculate dead time corrections for muon detector data + * + * **Command-Line Options:** + * - --fn : Input NeXus HDF4 file (required) + * - --out : Output NeXus HDF4 file (optional) + * - --debug, -d: Enable debug output + * - --dead_time_estimate, -dt: Calculate dead time corrections + * - --help, -h: Display help message + * - --version, -v: Display version information + * + * @author Andreas Suter + * @date 2007-2026 + * @copyright GNU General Public License v2 + * @version 1.0 + * + * @see nxH4::PNeXus + * @see nxH4::PNeXusDeadTime + */ + +#include +#include +#include +#include +#include + +#include + +#include "PNeXus.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_GIT_REV_H +#include "git-revision.h" +#endif + +//----------------------------------------------------------------------------- +/** + * @brief Display command-line syntax and help information + * + * Prints the usage syntax and available command-line options for the h4nexus + * program to stdout and exits the program. + */ +void h4nexus_syntax() { + std::cout << std::endl; + std::cout << "usage: h4nexus [--help | -h] |" << std::endl; + std::cout << " [--version | -v] |" << std::endl; + std::cout << " --fn [--debug | -d]" << std::endl; + std::cout << " [--dead_time_estimate | -dt]]" << std::endl; + std::cout << " [--out ]" << std::endl; + std::cout << std::endl; + std::cout << "options:" << std::endl; + std::cout << " --help, -h: this help." << std::endl; + std::cout << " --version, -v: version of h4nexus." << std::endl; + std::cout << " --fn : nexus hdf4 input file name ." << std::endl; + std::cout << " --dead_time_estimate, -dt: dead time estimate for the read hdf4 nexus file." << std::endl; + std::cout << " --debug, -d: print additional debug information." << std::endl; + std::cout << " --out : write the required datasets of a nexus hdf4 file to file " << std::endl; + std::cout << " with name . Only makes sense together with the option --fn ." << std::endl; + std::cout << std::endl; + exit(0); +} + +//----------------------------------------------------------------------------- +/** + * @brief Calculate dead time corrections for muon detector data + * + * Estimates dead time corrections for each detector in the NeXus file using + * the PNeXusDeadTime class and ROOT Minuit2 minimization. + * + * @param nxs Pointer to the PNeXus object containing the data + * @param debug If true, print additional debug information + * + * @see nxH4::PNeXusDeadTime + */ +void h4nexus_deadTimeEstimate(const nxH4::PNeXus *nxs, bool debug) +{ + if (debug) { + std::cout << std::endl; + std::cout << std::endl << "+++++++++++++++++++"; + std::cout << std::endl << "in deadTimeEstimate"; + std::cout << std::endl << "+++++++++++++++++++"; + std::cout << std::endl; + } + + nxH4::PNeXusDeadTime ndt(nxs, debug); + auto dims = ndt.GetDimensions(); + for (unsigned int i=0; iGetIdfVersion() == 1) { + std::cerr << "Error: IDF v1 write not yet implemented" << std::endl; + return; + } + + // Write using the read object's data + int result = const_cast(nxs)->WriteNexusFile(outFileName, nxs->GetIdfVersion()); + + if (result == 0) { + std::cout << "Successfully wrote: " << outFileName << std::endl; + } else { + std::cerr << "Failed to write file: " << outFileName << std::endl; + } + + // write data from scratch + + std::unique_ptr nxs_out = std::make_unique(); + + std::vector ival; + std::vector fval; + std::vector sval; + + // ---------- + // raw_data_1 + // ---------- + + // IDF version + ival.push_back(2); + nxs_out->AddDataset("/raw_data_1/IDF_version", ival, {1}, nxH4::H4DataType::INT32); + ival.clear(); + + // add group attribute to '/raw_data_1' + nxs_out->AddGroupAttribute("/raw_data_1", "NX_class", std::string("NXentry")); + + // beamline + sval.push_back("piE3"); + nxs_out->AddDataset("/raw_data_1/beamline", sval, {1}, nxH4::H4DataType::CHAR8); + sval.clear(); + + // definition + sval.push_back("muonTD"); + nxs_out->AddDataset("/raw_data_1/definition", sval, {1}, nxH4::H4DataType::CHAR8); + sval.clear(); + + // run_number + ival.push_back(1234); + nxs_out->AddDataset("/raw_data_1/run_number", ival, {1}, nxH4::H4DataType::INT32); + ival.clear(); + + // title + sval.push_back("this is the run title."); + nxs_out->AddDataset("/raw_data_1/title", sval, {1}, nxH4::H4DataType::CHAR8); + sval.clear(); + + // start time + sval.push_back("2026-01-01T01:02:03"); + nxs_out->AddDataset("/raw_data_1/start_time", sval, {1}, nxH4::H4DataType::CHAR8); + sval.clear(); + + // end time + sval.push_back("2026-01-01T02:03:42"); + nxs_out->AddDataset("/raw_data_1/end_time", sval, {1}, nxH4::H4DataType::CHAR8); + sval.clear(); + + // experiment_identifier - pgroup for PSI + sval.push_back("p18324"); + nxs_out->AddDataset("/raw_data_1/experiment_identifier", sval, {1}, nxH4::H4DataType::CHAR8); + sval.clear(); + + // ------------------- + // detector_1 (NXdata) + // ------------------- + + // add group attribute to /raw_data_1/instrument + nxs_out->AddGroupAttribute("/raw_data_1/detector_1", "NX_class", std::string("NXdata")); + + // counts + std::vector counts(16*66000, 42); // data 16 histos with length 66000 + nxs_out->AddDataset("/raw_data_1/detector_1/counts", counts, {1, 16, 66000}, nxH4::H4DataType::INT32); + + // attributes for counts + nxs_out->AddDatasetAttribute("/raw_data_1/detector_1/counts", "signal", 1); + nxs_out->AddDatasetAttribute("/raw_data_1/detector_1/counts", "axes", std::string("period_index,spectrum_index,raw_time")); + nxs_out->AddDatasetAttribute("/raw_data_1/detector_1/counts", "long_name", std::string("positron_counts")); + nxs_out->AddDatasetAttribute("/raw_data_1/detector_1/counts", "t0_bin", 2741); + nxs_out->AddDatasetAttribute("/raw_data_1/detector_1/counts", "first_good_bin", 2741); + nxs_out->AddDatasetAttribute("/raw_data_1/detector_1/counts", "last_good_bin", 66000); + nxs_out->AddDatasetAttribute("/raw_data_1/detector_1/counts", "units", std::string("counts")); + nxs_out->AddDatasetAttribute("/raw_data_1/detector_1/counts", "target", std::string("/raw_data_1/instrument/detector_1/counts")); + + // raw_time + std::vector raw_time(66000, 0.0); + for (unsigned int i=0; iAddDataset("/raw_data_1/detector_1/raw_time", raw_time, {66000}, nxH4::H4DataType::FLOAT32); + + // attributes raw_time + nxs_out->AddDatasetAttribute("/raw_data_1/detector_1/raw_time", "units", std::string("microseconds")); + nxs_out->AddDatasetAttribute("/raw_data_1/detector_1/raw_time", "target", std::string("/raw_data_1/instrument/detector_1/raw_time")); + + + // ---------- + // instrument + // ---------- + + // add group attribute to /raw_data_1/instrument + nxs_out->AddGroupAttribute("/raw_data_1/instrument", "NX_class", std::string("NXinstrument")); + + // name + sval.push_back("LEM"); + nxs_out->AddDataset("/raw_data_1/instrument/name", sval, {1}, nxH4::H4DataType::CHAR8); + sval.clear(); + + // ------ + // source + // ------ + + // add group attribute to /raw_data_1/instrument/source + nxs_out->AddGroupAttribute("/raw_data_1/instrument/source", "NX_class", std::string("NXsource")); + + // name + sval.push_back("PSI"); + nxs_out->AddDataset("/raw_data_1/instrument/source/name", sval, {1}, nxH4::H4DataType::CHAR8); + sval.clear(); + + // type + sval.push_back("continuous muon source"); + nxs_out->AddDataset("/raw_data_1/instrument/source/types", sval, {1}, nxH4::H4DataType::CHAR8); + sval.clear(); + + // probe + sval.push_back("postive muons"); + nxs_out->AddDataset("/raw_data_1/instrument/source/probe", sval, {1}, nxH4::H4DataType::CHAR8); + sval.clear(); + + // ----------------------- + // detector_1 (NXdetector) + // ----------------------- + + // add group attribute to /raw_data_1/instrument/detector_1 + nxs_out->AddGroupAttribute("/raw_data_1/instrument/detector_1", "NX_class", std::string("NXdetector")); + + // counts + nxs_out->AddDataset("/raw_data_1/instrument/detector_1/counts", counts, {1, 16, 66000}, nxH4::H4DataType::INT32); + + // attributes for counts + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "signal", 1); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "axes", std::string("period_index,spectrum_index,raw_time")); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "long_name", std::string("positron_counts")); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "t0_bin", 2741); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "first_good_bin", 2741); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "last_good_bin", 66000); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "units", std::string("counts")); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "target", std::string("/raw_data_1/instrument/detector_1/counts")); + + // raw_time + nxs_out->AddDataset("/raw_data_1/instrument/detector_1/raw_time", raw_time, {66000}, nxH4::H4DataType::FLOAT32); + + // attributes raw_time + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/raw_time", "units", std::string("microseconds")); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/raw_time", "target", std::string("/raw_data_1/instrument/detector_1/raw_time")); + + // resolution + fval.push_back(195.3125); + nxs_out->AddDataset("/raw_data_1/instrument/detector_1/resolution", fval, {1}, nxH4::H4DataType::FLOAT32); + fval.clear(); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/resolution", + "units", std::string("picoseconds")); + + // spectrum_index + for (unsigned int i=0; i<66000; i++) + ival.push_back(i+1); + nxs_out->AddDataset("/raw_data_1/instrument/detector_1/spectrum_index", ival, {66000}, nxH4::H4DataType::INT32); + ival.clear(); + + // dead_time + std::vector deadTime(66000, 0.0); + nxs_out->AddDataset("/raw_data_1/instrument/detector_1/dead_time", deadTime, {66000}, nxH4::H4DataType::FLOAT32); + + // attributes dead_time + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/dead_time", "available", 0); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/dead_time", "units", std::string("microseconds")); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/dead_time", "target", std::string("/raw_data_1/instrument/detector_1/dead_time")); + + // add root attributes + // file name + nxs_out->AddRootAttribute("file_name", std::string("_test.nxs")); + // date-time + std::time_t time = std::time({}); + char timeString[std::size("yyyy-mm-ddThh:mm:ssZ")]; + std::strftime(std::data(timeString), std::size(timeString), + "%FT%TZ", std::gmtime(&time)); + nxs_out->AddRootAttribute("file_time", std::string(timeString)); + // NeXus version + nxs_out->AddRootAttribute("NeXus_Version", std::string("4.3.0")); + // hdf4 version + nxs_out->AddRootAttribute("HDF4_Version", std::string(nxs->GetHdf4Version())); + // creator + nxs_out->AddRootAttribute("creator", std::string("h4nexus - PSI")); + + nxs_out->WriteNexusFile("_test.nxs"); +} + +//----------------------------------------------------------------------------- +/** + * @brief Main entry point for the h4nexus program + * + * Parses command-line arguments and performs the requested operations: + * - Read and display NeXus HDF4 file information + * - Write NeXus HDF4 files with modified data + * - Calculate dead time corrections + * + * @param argc Number of command-line arguments + * @param argv Array of command-line argument strings + * + * @return 0 on success, non-zero on error + * + * @see h4nexus_syntax() for available command-line options + */ +int main(int argc, char *argv[]) +{ + std::string fileName{""}; + std::string fileNameOut{""}; + bool printDebug{false}; + bool deadTimeEstimate{false}; + + if (argc == 1) + h4nexus_syntax(); + + for (int i=1; i= argc) { + std::cout << std::endl << "**ERROR** found --fn without ." << std::endl; + h4nexus_syntax(); + } + i++; + fileName = argv[i]; + } else if (!strcmp(argv[i], "-dt") || !strcmp(argv[i], "--dead_time_estimate")) { + deadTimeEstimate = true; + } else if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) { + printDebug = true; + } else if (!strcmp(argv[i], "--out")) { + if (i+1 >= argc) { + std::cout << std::endl << "**ERROR** found --out without ." << std::endl; + h4nexus_syntax(); + } + i++; + fileNameOut = argv[i]; + } else { + h4nexus_syntax(); + } + } + + if (fileName.empty()) { + std::cerr << std::endl; + std::cerr << "**ERROR** is missing." << std::endl; + std::cerr << std::endl; + h4nexus_syntax(); + } + + std::unique_ptr nxs = std::make_unique(fileName, printDebug); + + nxs->Dump(); + + if (deadTimeEstimate) { + h4nexus_deadTimeEstimate(nxs.get(), printDebug); + } + + if (!fileNameOut.empty()) { + h4nexus_writeTest(nxs.get(), fileNameOut, printDebug); + } + + return 0; +} diff --git a/src/external/nexus/examples/hdf5/CMakeLists.txt b/src/external/nexus/examples/hdf5/CMakeLists.txt new file mode 100644 index 00000000..53e6e81f --- /dev/null +++ b/src/external/nexus/examples/hdf5/CMakeLists.txt @@ -0,0 +1,119 @@ +# - h5nexus +cmake_minimum_required(VERSION 3.26) + +project(h5nexus VERSION 0.1.0 LANGUAGES CXX) + +#--- set C++ standard --------------------------------------------------------- +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +#--- set a default build type if none was specified --------------------------- +set(default_build_type "Release") + +if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to '${default_build_type}' as none was specified.") + set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE + STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Release" "MinSizeRel" "RelWithDebInfo") +endif () + +#--- check for pkg-config ----------------------------------------------------- +find_package(PkgConfig REQUIRED) + +#--- check for git ------------------------------------------------------------ +find_package(Git REQUIRED) + +#--- check for HDF5 ----------------------------------------------------------- +find_package(HDF5 REQUIRED COMPONENTS CXX) +if(NOT HDF5_FOUND) + message(FATAL_ERROR "HDF5 C++ library not found") +endif() +include_directories(${HDF5_INCLUDE_DIRS}) + +#--- check for ROOT ----------------------------------------------------------- +find_package(ROOT 6.36 REQUIRED COMPONENTS Minuit2) +if (ROOT_miniut2_FOUND) + execute_process(COMMAND root-config --bindir OUTPUT_VARIABLE ROOT_BINDIR) + string(STRIP ${ROOT_BINDIR} ROOT_BINDIR) + execute_process(COMMAND root-config --version OUTPUT_VARIABLE ROOT_VERSION) + string(STRIP ${ROOT_VERSION} ROOT_VERSION) + message("-- Found ROOT: ${ROOT_BINDIR} (found version: ${ROOT_VERSION})") + #---Define useful ROOT functions and macros (e.g. ROOT_GENERATE_DICTIONARY) + include(${ROOT_USE_FILE}) +endif (ROOT_miniut2_FOUND) + +#--- all checks done -> feed config.h ----------------------------------------- +set(HAVE_CONFIG_H 1 CACHE INTERNAL "config.h is available") +configure_file(${CMAKE_SOURCE_DIR}/cmake/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) + +#--- check if project source is a git repo ------------------------------------ +if (EXISTS "${CMAKE_SOURCE_DIR}/.git/HEAD") + message(STATUS "is a git repo") + set(IS_GIT_REPO 1) +else () + message(STATUS "is NOT a git repo") + set(IS_GIT_REPO 0) +endif () + +#--- start create git-revision.h ---------------------------------------------- +if (IS_GIT_REPO) + execute_process(COMMAND sh ${CMAKE_SOURCE_DIR}/src/git_revision.sh) + + set(HAVE_GIT_REV_H "-DHAVE_GIT_REV_H") + set(GIT_REV_H "git-revision.h") +else (IS_GIT_REPO) + set(HAVE_GIT_REV_H "") + set(GIT_REV_H "") +endif (IS_GIT_REPO) + +#--- end create git-revision.h ------------------------------------------------ + +#--- write summary of the installation +cmake_host_system_information(RESULT PROCESSOR QUERY PROCESSOR_DESCRIPTION) + +message("") +message("|-----------------------------------------------------------------------|") +message("| |") +message("| Summary |") +message("| |") +message("|-----------------------------------------------------------------------|") +message("") +message(" System: ${CMAKE_HOST_SYSTEM_NAME} ${CMAKE_SYSTEM_PROCESSOR} - ${CMAKE_HOST_SYSTEM_VERSION}") +message(" Processor: ${PROCESSOR} (${CMAKE_SYSTEM_PROCESSOR})") +message(" ----------") +message("") +message(" h5nexus Version: ${musrfit_VERSION}") +message(" ----------------") +message("") +message(" Build Type: ${CMAKE_BUILD_TYPE}") +message(" -----------") +message("") +message(" Requirements:") +message(" -------------") +message("") +message(" HDF5 found in ${HDF5_INCLUDE_DIRS}, Version: ${HDF5_VERSION}") +message(" ROOT found in ${ROOT_INCLUDE_DIRS}, Version: ${ROOT_VERSION}") +message("") +message(" Installation directories:") +message(" -------------------------") +message("") +message(" Programs : ${CMAKE_INSTALL_PREFIX}/bin") +message("") +message("-------------------------------------------------------------------------") +message("") + +#--- add executable ----------------------------------------------------------- +add_executable(h5nexus + ../../PNeXus.cpp + main.cpp) +target_compile_options(h5nexus BEFORE PRIVATE "-DHAVE_CONFIG_H" "${HAVE_GIT_REV_H}") +target_include_directories(h5nexus + BEFORE PRIVATE + $ + $ + $ +) +target_link_libraries(h5nexus ${HDF5_CXX_LIBRARIES} ${ROOT_LIBRARIES}) diff --git a/src/external/nexus/examples/hdf5/cmake/config.h.in b/src/external/nexus/examples/hdf5/cmake/config.h.in new file mode 100644 index 00000000..3a149ef7 --- /dev/null +++ b/src/external/nexus/examples/hdf5/cmake/config.h.in @@ -0,0 +1,5 @@ +// config.h + +#define PACKAGE_VERSION "@PROJECT_VERSION@" +#define BUILD_TYPE "@CMAKE_BUILD_TYPE@" + diff --git a/src/external/nexus/examples/hdf5/cmake/git-revision.h.in b/src/external/nexus/examples/hdf5/cmake/git-revision.h.in new file mode 100644 index 00000000..5d676b3a --- /dev/null +++ b/src/external/nexus/examples/hdf5/cmake/git-revision.h.in @@ -0,0 +1,8 @@ +#ifndef GIT_VERSION_H +#define GIT_VERSION_H + +#define GIT_BRANCH "@GIT_BRANCH@" +#define GIT_CURRENT_SHA1 @GIT_CURRENT_SHA1@ + +#endif // GIT_VERSION_H + diff --git a/src/external/nexus/examples/hdf5/docu/README.md b/src/external/nexus/examples/hdf5/docu/README.md new file mode 100644 index 00000000..04bf616b --- /dev/null +++ b/src/external/nexus/examples/hdf5/docu/README.md @@ -0,0 +1,198 @@ +# h5nexus Documentation + +This directory contains documentation for the h5nexus project. + +## Available Documentation + +- **Usage.md** - Comprehensive usage guide with detailed examples and workflow patterns +- **mainpage.md** - Main page for Doxygen-generated API documentation + +## Generating API Documentation + +The project uses [Doxygen](https://www.doxygen.nl/) to generate comprehensive API documentation from source code comments. + +### Prerequisites + +- **Doxygen** (required) - Install with: + ```bash + # macOS + brew install doxygen + + # Ubuntu/Debian + sudo apt-get install doxygen + + # Fedora/RHEL + sudo dnf install doxygen + ``` + +- **Graphviz** (optional, recommended) - For generating diagrams: + ```bash + # macOS + brew install graphviz + + # Ubuntu/Debian + sudo apt-get install graphviz + + # Fedora/RHEL + sudo dnf install graphviz + ``` + +### Method 1: Using CMake (Recommended) + +If you've already configured your build with CMake: + +```bash +cd build +make doc +``` + +This will generate HTML documentation in the `doc/html/` directory. + +### Method 2: Using Doxygen Directly + +From the project root directory: + +```bash +doxygen Doxyfile +``` + +This will also generate HTML documentation in the `doc/html/` directory. + +## Viewing the Documentation + +After generation, open the main documentation page in your web browser: + +```bash +# macOS +open doc/html/index.html + +# Linux +xdg-open doc/html/index.html + +# Or manually navigate to: file:///path/to/h5nexus/doc/html/index.html +``` + +## Documentation Structure + +The generated documentation includes: + +- **Main Page** - Overview, quick start guide, and architecture description +- **Classes** - Detailed documentation for all classes: + - `nxH5::PNeXus` - Main NeXus file reader/writer class + - `nxH5::PNXdata` - Template class for dataset storage + - `nxH5::PNeXusDeadTime` - Dead time correction calculator +- **Files** - Source file documentation +- **Namespaces** - Namespace documentation (nxH5) +- **Examples** - Code examples from the documentation +- **Class Hierarchy** - Visual class inheritance diagrams +- **Call Graphs** - Function call graphs (if Graphviz is available) +- **Include Dependency Graphs** - Header file dependency visualization + +## Documentation Features + +The Doxygen configuration includes: + +- **Full source browsing** - Browse annotated source code +- **Search functionality** - Fast search across all documentation +- **Interactive SVG diagrams** - Zoomable class and call graphs +- **Cross-references** - Links between related classes and functions +- **Syntax highlighting** - Colored code examples +- **Responsive layout** - Works on desktop and mobile browsers + +## Configuration + +The Doxygen configuration is stored in `Doxyfile` at the project root. Key settings: + +- **Input files**: `inc/`, `src/`, `docu/mainpage.md` +- **Output directory**: `doc/` +- **Output format**: HTML (LaTeX disabled) +- **Graphs**: Enabled if Graphviz/dot is available +- **Extract all**: Yes (documents all code, not just documented items) +- **Source browser**: Enabled + +## Updating Documentation + +To update the documentation after code changes: + +1. Edit Doxygen comments in header/source files +2. Regenerate documentation: `make doc` or `doxygen Doxyfile` +3. Refresh your browser to see changes + +### Doxygen Comment Style + +The project uses Javadoc-style comments: + +```cpp +/** + * @brief Brief description of the function + * + * Detailed description with more information about what + * the function does, how it works, and any important notes. + * + * @param arg1 Description of first parameter + * @param arg2 Description of second parameter + * @return Description of return value + * @throws ExceptionType Description of when this exception is thrown + * + * @note Important note about usage + * @warning Warning about potential issues + * + * @example + * @code + * // Example usage + * MyClass obj; + * obj.myFunction(42, "test"); + * @endcode + */ +void myFunction(int arg1, const std::string& arg2); +``` + +## Cleaning Generated Documentation + +To remove generated documentation: + +```bash +rm -rf doc/html/ +``` + +## Troubleshooting + +### "Doxygen not found" during CMake configuration + +Install Doxygen (see Prerequisites above), then reconfigure: + +```bash +cd build +cmake .. +``` + +### Graphs/diagrams not generating + +Install Graphviz (see Prerequisites above), then regenerate: + +```bash +make doc +# or +doxygen Doxyfile +``` + +### Broken links in documentation + +Ensure all referenced files exist and paths in Doxyfile are correct. The documentation system expects: +- Source files in `inc/` and `src/` +- Main page at `docu/mainpage.md` + +### Documentation looks outdated + +Clear the output directory and regenerate: + +```bash +rm -rf doc/html/ +make doc +``` + +## Additional Resources + +- [Doxygen Manual](https://www.doxygen.nl/manual/) +- [Doxygen Special Commands](https://www.doxygen.nl/manual/commands.html) +- [Markdown Support in Doxygen](https://www.doxygen.nl/manual/markdown.html) diff --git a/src/external/nexus/examples/hdf5/docu/Usage.md b/src/external/nexus/examples/hdf5/docu/Usage.md new file mode 100644 index 00000000..9fa476dd --- /dev/null +++ b/src/external/nexus/examples/hdf5/docu/Usage.md @@ -0,0 +1,2036 @@ +# PNeXus Usage Documentation + +## Overview + +PNeXus is a C++ library for reading and writing NeXus HDF5 files from ISIS muon experiments. It provides a simple, type-safe interface based on the PNXdata approach for handling multi-dimensional datasets with attributes. + +## Table of Contents + +1. [Command-Line Usage](#command-line-usage) +2. [Reading NeXus Files](#reading-nexus-files) +3. [Writing NeXus Files](#writing-nexus-files) +4. [Working with Group Attributes](#working-with-group-attributes) +5. [PNXdata Data Structure](#pnxdata-data-structure) +6. [Working with Datasets](#working-with-datasets) +7. [API Reference](#api-reference) +8. [Examples](#examples) + +--- + +## Command-Line Usage + +### Basic Syntax + +```bash +h5nexus --fn [options] +``` + +### Options + +| Option | Description | +|--------|-------------| +| `--fn ` | Input NeXus HDF5 file (required) | +| `--out ` | Output NeXus HDF5 file (enables write mode) | +| `-d, --debug` | Enable debug output | +| `-dt, --dead_time_estimate` | Calculate dead time corrections | +| `-h, --help` | Show help message | +| `-v, --version` | Show version information | + +### Examples + +**Read and display a NeXus file:** +```bash +h5nexus --fn EMU00139040.nxs +``` + +**Read with debug output:** +```bash +h5nexus --fn EMU00139040.nxs --debug +``` + +**Read and write to a new file:** +```bash +h5nexus --fn EMU00139040.nxs --out output.nxs +``` + +**Read, calculate dead time, and write:** +```bash +h5nexus --fn EMU00139040.nxs --dead_time_estimate --out corrected.nxs +``` + +--- + +## Reading NeXus Files + +### Basic Reading + +The simplest way to read a NeXus file is to create a `PNeXus` object with the filename: + +```cpp +#include "PNeXus.h" + +// Read a NeXus file +nxH5::PNeXus nexus("EMU00139040.nxs"); + +// With debug output +nxH5::PNeXus nexus_debug("EMU00139040.nxs", true); +``` + +### Accessing File Metadata + +```cpp +// Get basic file information +std::string filename = nexus.GetFileName(); +std::string hdf5_version = nexus.GetHdf5Version(); +std::string nexus_version = nexus.GetNeXusVersion(); +int idf_version = nexus.GetIdfVersion(); // 1 or 2 +``` + +### Accessing the Data Map + +All datasets are stored in a map structure: + +```cpp +// Get read-only access to the data map +const auto& dataMap = nexus.GetDataMap(); + +// Get mutable access (for modifications) +auto& dataMap = nexus.GetDataMap(); + +// Check if a dataset exists +if (dataMap.find("/raw_data_1/detector_1/counts") != dataMap.end()) { + // Dataset exists +} +``` + +### Manipulating the Data Map + +PNeXus provides convenient methods to manipulate the data map without directly accessing it: + +```cpp +// Check if a dataset exists +bool exists = nexus.HasDataset("/raw_data_1/detector_1/counts"); + +// Add or update a dataset +nxH5::PNXdata newData; +newData.SetDimensions({96}); +newData.SetData(std::vector(96, 0)); +nexus.SetDataset("/raw_data_1/custom_data", newData); + +// Get a dataset (returns a copy) +auto counts = nexus.GetDataset("/raw_data_1/detector_1/counts"); + +// Get a mutable reference to a dataset (for in-place modifications) +auto& countsRef = nexus.GetDatasetRef("/raw_data_1/detector_1/counts"); +countsRef.GetData()[0] = 100; // Modify directly in the data map + +// Remove a dataset +bool removed = nexus.RemoveDataset("/raw_data_1/old_data"); + +// Get the total number of datasets +size_t numDatasets = nexus.GetNumDatasets(); +std::cout << "Total datasets: " << numDatasets << std::endl; + +// Clear all datasets from the data map +nexus.ClearDataMap(); +``` + +### Reading Integer Datasets + +```cpp +#include + +// Access integer dataset +auto counts = std::any_cast>( + dataMap.at("/raw_data_1/detector_1/counts") +); + +// Get dimensions +const auto& dims = counts.GetDimensions(); +// Example: dims = [1, 96, 2048] for periods × spectra × time_bins + +// Get data as flattened vector +const auto& data = counts.GetData(); +size_t total_elements = counts.GetNumElements(); // 1 × 96 × 2048 = 196608 + +// Access specific element in 3D array [i, j, k] +size_t index = i * dims[1] * dims[2] + j * dims[2] + k; +int value = data[index]; +``` + +### Reading Float Datasets + +```cpp +// Access float dataset (e.g., dead time corrections) +auto dead_time = std::any_cast>( + dataMap.at("/raw_data_1/detector_1/dead_time") +); + +const auto& dt_data = dead_time.GetData(); +const auto& dt_dims = dead_time.GetDimensions(); + +// Example: 1D array [96] - one value per detector +for (size_t i = 0; i < dt_data.size(); i++) { + std::cout << "Detector " << (i+1) << ": " << dt_data[i] << std::endl; +} +``` + +### Reading String Datasets + +```cpp +// Access string dataset +auto beamline = std::any_cast>( + dataMap.at("/raw_data_1/beamline") +); + +const auto& beamline_data = beamline.GetData(); +std::string beamline_name = beamline_data[0]; // "EMU" +``` + +### Reading Attributes + +```cpp +// 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; +} + +// Get all attributes +const auto& attributes = counts.GetAttributes(); +for (const auto& [name, value] : attributes) { + // Process attributes + try { + 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; + } else if (auto* strVal = std::any_cast(&value)) { + std::cout << name << " (string): " << *strVal << std::endl; + } + } catch (...) { + std::cerr << "Unknown attribute type for: " << name << std::endl; + } +} +``` + +### Case-Insensitive Path Lookup + +PNeXus automatically handles case-insensitive paths: + +```cpp +// All of these will find the same dataset: +// /RAW_DATA_1/IDF_version +// /raw_data_1/IDF_version +// /Raw_Data_1/Idf_Version + +// The actual path in the file is used internally +``` + +--- + +## Writing NeXus Files + +### Basic Writing + +Write the entire data map to a new NeXus file: + +```cpp +// Read a file +nxH5::PNeXus nexus("input.nxs", true); + +// Write to a new file +int result = nexus.WriteNexusFile("output.nxs"); + +if (result == 0) { + std::cout << "Successfully wrote file" << std::endl; +} else { + std::cerr << "Failed to write file" << std::endl; +} +``` + +### Writing with IDF Version + +```cpp +// Specify IDF version (default is 2) +nexus.WriteNexusFile("output.nxs", 2); // IDF version 2 +``` + +### Read-Modify-Write Workflow + +#### Approach 1: Direct Data Map Access + +```cpp +// 1. Read input file +nxH5::PNeXus nexus("input.nxs", true); + +// 2. Get mutable access to data map +auto& dataMap = nexus.GetDataMap(); + +// 3. Modify data +auto counts = std::any_cast>( + dataMap["/raw_data_1/detector_1/counts"] +); + +// Modify the data (e.g., scale by factor) +auto& data = counts.GetData(); +for (auto& value : data) { + value *= 2; // Double all counts +} + +// Update the data map +dataMap["/raw_data_1/detector_1/counts"] = counts; + +// 4. Write to output file +nexus.WriteNexusFile("modified.nxs"); +``` + +#### Approach 2: Using Data Map Manipulation Methods (Recommended) + +```cpp +// 1. Read input file +nxH5::PNeXus nexus("input.nxs", true); + +// 2. Check if dataset exists +if (nexus.HasDataset("/raw_data_1/detector_1/counts")) { + // 3. Get mutable reference and modify in-place + auto& countsRef = nexus.GetDatasetRef("/raw_data_1/detector_1/counts"); + auto& data = countsRef.GetData(); + + for (auto& value : data) { + value *= 2; // Double all counts + } + + // No need to update data map - changes are already applied +} + +// 4. Write to output file +nexus.WriteNexusFile("modified.nxs"); +``` + +**Note:** Approach 2 is recommended because: +- Uses type-safe methods instead of `std::any_cast` +- Modifies data in-place with `GetDatasetRef()` - no need to copy back +- Provides cleaner error handling with `HasDataset()` +- More readable and maintainable code + +### Modifying Attributes + +```cpp +// Get dataset +auto counts = std::any_cast>( + dataMap["/raw_data_1/detector_1/counts"] +); + +// Modify existing attribute +if (counts.HasAttribute("first_good_bin")) { + counts.AddAttribute("first_good_bin", static_cast(25)); +} + +// Add new attribute +counts.AddAttribute("processing_date", std::string("2026-01-08")); +counts.AddAttribute("scale_factor", 2.0f); + +// Update data map +dataMap["/raw_data_1/detector_1/counts"] = counts; + +// Write +nexus.WriteNexusFile("output.nxs"); +``` + +### Creating New Datasets + +```cpp +// Create a new integer dataset +nxH5::PNXdata new_dataset; + +// Set dimensions (e.g., 1D array of 96 elements) +new_dataset.SetDimensions({96}); + +// Set data +std::vector data(96); +for (int i = 0; i < 96; i++) { + data[i] = i + 1; +} +new_dataset.SetData(data); + +// Add attributes +new_dataset.AddAttribute("units", std::string("counts")); +new_dataset.AddAttribute("description", std::string("Custom dataset")); + +// Add to data map +auto& dataMap = nexus.GetDataMap(); +dataMap["/raw_data_1/custom_data"] = new_dataset; + +// Write +nexus.WriteNexusFile("output.nxs"); +``` + +### Creating Multi-Dimensional Datasets + +```cpp +// Create 3D dataset: 1 period × 96 detectors × 2048 time bins +nxH5::PNXdata counts_3d; + +// Set dimensions +counts_3d.SetDimensions({1, 96, 2048}); + +// Create flattened data vector +size_t total = 1 * 96 * 2048; +std::vector data(total, 0); + +// Fill data (example: set value at [0, detector, timebin]) +for (size_t detector = 0; detector < 96; detector++) { + for (size_t timebin = 0; timebin < 2048; timebin++) { + size_t index = 0 * 96 * 2048 + detector * 2048 + timebin; + data[index] = detector * 1000 + timebin; + } +} + +counts_3d.SetData(data); + +// Add attributes +counts_3d.AddAttribute("signal", static_cast(1)); +counts_3d.AddAttribute("axes", std::string("period_index,spectrum_index,raw_time")); + +// Add to data map and write +dataMap["/raw_data_1/detector_1/counts"] = counts_3d; +nexus.WriteNexusFile("output.nxs"); +``` + +--- + +## Working with Group Attributes + +### Overview + +Groups in HDF5/NeXus files can have attributes just like datasets. PNeXus provides methods to add, modify, and manage attributes for HDF5 groups (e.g., `/raw_data_1`, `/raw_data_1/detector_1`). This is particularly useful for: + +- Adding NeXus class identifiers (`NX_class` attributes) +- Adding metadata to group hierarchies +- Documenting experimental parameters at the group level +- Organizing structural information + +Group attributes are stored separately from dataset attributes and are written automatically when creating the NeXus file structure. + +### Adding Group Attributes + +Use `AddGroupAttribute()` to add or update attributes for a group: + +```cpp +#include "PNeXus.h" + +// Create or read a PNeXus object +nxH5::PNeXus nexus("input.nxs"); + +// Add NX_class attribute to identify NeXus group type +nexus.AddGroupAttribute("/raw_data_1", "NX_class", std::string("NXentry")); + +// Add custom attributes (string, int, float) +nexus.AddGroupAttribute("/raw_data_1", "experiment_id", std::string("EXP001")); +nexus.AddGroupAttribute("/raw_data_1", "run_number", static_cast(12345)); +nexus.AddGroupAttribute("/raw_data_1", "temperature", 293.15f); + +// Add attributes to nested groups +nexus.AddGroupAttribute("/raw_data_1/detector_1", "NX_class", std::string("NXdetector")); +nexus.AddGroupAttribute("/raw_data_1/detector_1", "detector_type", std::string("muon_detector")); +nexus.AddGroupAttribute("/raw_data_1/instrument", "NX_class", std::string("NXinstrument")); + +// Attributes are written when calling WriteNexusFile() +nexus.WriteNexusFile("output.nxs"); +``` + +### Checking for Group Attributes + +Check if a group has a specific attribute: + +```cpp +// Check if attribute exists +if (nexus.HasGroupAttribute("/raw_data_1", "experiment_id")) { + std::cout << "Experiment ID attribute exists" << std::endl; +} + +// Check before accessing +if (nexus.HasGroupAttribute("/raw_data_1", "run_number")) { + auto runNum = std::any_cast( + nexus.GetGroupAttribute("/raw_data_1", "run_number") + ); + std::cout << "Run number: " << runNum << std::endl; +} +``` + +### Retrieving Group Attributes + +Get individual or all attributes from a group: + +```cpp +// Get a single attribute +try { + auto expId = std::any_cast( + nexus.GetGroupAttribute("/raw_data_1", "experiment_id") + ); + std::cout << "Experiment ID: " << expId << std::endl; +} catch (const std::out_of_range&) { + std::cerr << "Attribute not found" << std::endl; +} catch (const std::bad_any_cast&) { + std::cerr << "Type mismatch" << std::endl; +} + +// Get all attributes for a group +const auto& attributes = nexus.GetGroupAttributes("/raw_data_1"); +for (const auto& [name, value] : attributes) { + std::cout << "Attribute: " << name << std::endl; + // Cast to appropriate type to access value + if (auto* str = std::any_cast(&value)) { + std::cout << " Value (string): " << *str << std::endl; + } else if (auto* intVal = std::any_cast(&value)) { + std::cout << " Value (int): " << *intVal << std::endl; + } else if (auto* floatVal = std::any_cast(&value)) { + std::cout << " Value (float): " << *floatVal << std::endl; + } +} +``` + +### Removing Group Attributes + +Remove specific attributes or all attributes from a group: + +```cpp +// Remove a specific attribute +bool removed = nexus.RemoveGroupAttribute("/raw_data_1", "temperature"); +if (removed) { + std::cout << "Temperature attribute removed" << std::endl; +} else { + std::cout << "Attribute not found or group doesn't exist" << std::endl; +} + +// Clear all attributes from a group +bool cleared = nexus.ClearGroupAttributes("/raw_data_1/detector_1"); +if (cleared) { + std::cout << "All attributes cleared from detector_1" << std::endl; +} +``` + +### Common NeXus Group Attributes + +NeXus uses the `NX_class` attribute to identify group types: + +| Group Path | NX_class Value | Description | +|------------|----------------|-------------| +| `/raw_data_1` | `NXentry` | Top-level entry for a measurement | +| `/raw_data_1/instrument` | `NXinstrument` | Instrument information | +| `/raw_data_1/detector_1` | `NXdetector` | Detector group | +| `/raw_data_1/sample` | `NXsample` | Sample information | +| `/raw_data_1/user_1` | `NXuser` | User information | + +### Practical Example: Setting up NeXus Structure + +```cpp +#include "PNeXus.h" + +int main() { + nxH5::PNeXus nexus; + + // Add some example data + std::vector counts(96 * 2048, 0); + nexus.AddDataset("/raw_data_1/detector_1/counts", counts, + {1, 96, 2048}, H5::PredType::NATIVE_INT32); + + // Set up proper NeXus group structure with NX_class 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/detector_1", "NX_class", std::string("NXdetector")); + + // Add experimental metadata at group level + nexus.AddGroupAttribute("/raw_data_1", "definition", std::string("muonTD")); + nexus.AddGroupAttribute("/raw_data_1", "experiment_identifier", std::string("EMU2026-001")); + nexus.AddGroupAttribute("/raw_data_1", "collection_time", 3600.0f); // seconds + + // Add instrument-specific metadata + nexus.AddGroupAttribute("/raw_data_1/instrument", "name", std::string("EMU")); + nexus.AddGroupAttribute("/raw_data_1/instrument", "facility", std::string("ISIS")); + + // Add detector-specific metadata + nexus.AddGroupAttribute("/raw_data_1/detector_1", "detector_number", static_cast(1)); + nexus.AddGroupAttribute("/raw_data_1/detector_1", "description", + std::string("Positron detector array")); + + // Write file - group attributes are automatically written + int result = nexus.WriteNexusFile("structured_output.nxs"); + if (result == 0) { + std::cout << "Successfully created NeXus file with group attributes" << std::endl; + } + + return 0; +} +``` + +### Supported Attribute Types + +Group attributes support the same types as dataset attributes: + +- **int32_t**: Integer values +- **float**: Floating-point values +- **std::string**: String values + +Example: +```cpp +nexus.AddGroupAttribute("/raw_data_1", "string_attr", std::string("text")); +nexus.AddGroupAttribute("/raw_data_1", "int_attr", static_cast(42)); +nexus.AddGroupAttribute("/raw_data_1", "float_attr", 3.14f); +``` + +### Notes + +- Group attributes are stored in memory and written when `WriteNexusFile()` is called +- Attributes are automatically written when groups are created during file writing +- Groups don't need to exist beforehand - they're created automatically when datasets are added +- Group attributes persist through read-modify-write workflows +- Use `HasGroupAttribute()` to check for existence before accessing + +--- + +## PNXdata Data Structure + +### Template Class Definition + +`PNXdata` is a template class that stores datasets with metadata: + +```cpp +template +class PNXdata { + // Supported types: int, int32_t, float, std::string +}; +``` + +### Key Methods + +| Method | Description | +|--------|-------------| +| `GetData()` | Get const reference to data vector | +| `GetData()` (mutable) | Get mutable reference to data vector | +| `SetData(vector)` | Set the data vector | +| `GetDimensions()` | Get const reference to dimensions vector | +| `SetDimensions(vector)` | Set the dimensions vector | +| `GetRank()` | Get number of dimensions | +| `GetNumElements()` | Get total number of elements (product of dimensions) | +| `GetDataType()` | Get HDF5 data type | +| `SetDataType(type)` | Set HDF5 data type | +| `AddAttribute(name, value)` | Add an attribute | +| `GetAttribute(name)` | Get an attribute value | +| `HasAttribute(name)` | Check if attribute exists | +| `GetAttributes()` | Get all attributes as map | + +### Data Storage + +- **Data**: Stored as flattened 1D `std::vector` +- **Dimensions**: Stored as `std::vector` +- **Attributes**: Stored as `std::map` + +### Multi-Dimensional Indexing + +For a 3D dataset with dimensions `[dim0, dim1, dim2]`, access element `[i, j, k]`: + +```cpp +size_t index = i * dim1 * dim2 + j * dim2 + k; +T value = data[index]; +``` + +For a 2D dataset with dimensions `[dim0, dim1]`, access element `[i, j]`: + +```cpp +size_t index = i * dim1 + j; +T value = data[index]; +``` + +--- + +## Working with Datasets + +### Common Dataset Paths (IDF v2) + +| Path | Type | Dimensions | Description | +|------|------|------------|-------------| +| `/raw_data_1/IDF_version` | int | [1] | IDF version number | +| `/raw_data_1/beamline` | string | [1] | Beamline name (e.g., "EMU") | +| `/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] | Number of good frames | +| `/raw_data_1/detector_1/counts` | int | [periods, spectra, bins] | Count data (3D) | +| `/raw_data_1/detector_1/raw_time` | float | [time_bins+1] | Time bin edges | +| `/raw_data_1/detector_1/spectrum_index` | int | [spectra] | Spectrum indices | +| `/raw_data_1/detector_1/dead_time` | float | [spectra] | Dead time per detector | +| `/raw_data_1/instrument/name` | string | [1] | Instrument name | +| `/raw_data_1/instrument/source/name` | string | [1] | Source name (e.g., "ISIS") | +| `/raw_data_1/instrument/source/type` | string | [1] | Source type | +| `/raw_data_1/instrument/source/probe` | string | [1] | Probe type | +| `/raw_data_1/instrument/detector_1/resolution` | int | [1] | Time resolution (picoseconds) | + +### Common Attributes + +**Counts Dataset Attributes:** +- `signal` (int32_t): Signal axis indicator (typically 1) +- `axes` (string): Comma-separated axis names +- `long_name` (string): Long description +- `t0_bin` (int32_t): T0 time bin +- `first_good_bin` (int32_t): First good data bin +- `last_good_bin` (int32_t): Last good data bin +- `resolution` (int32_t): Time resolution in picoseconds + +**Time Dataset Attributes:** +- `units` (string): Time units (e.g., "microsecond") + +--- + +## API Reference + +### PNeXus Class + +#### Constructors + +```cpp +// Default constructor +PNeXus(); + +// Read constructor +PNeXus(const std::string& filename, bool printDebug = false); +``` + +#### Public Methods + +```cpp +// File metadata +std::string GetFileName() const; +std::string GetHdf5Version() const; +std::string GetNeXusVersion() const; +int GetIdfVersion() const; + +// Data access +const std::map& GetDataMap() const; +std::map& GetDataMap(); + +// Data map manipulation +bool HasDataset(const std::string& path) const; +template +void SetDataset(const std::string& path, const PNXdata& data); +template +PNXdata GetDataset(const std::string& path) const; +template +PNXdata& GetDatasetRef(const std::string& path); +bool RemoveDataset(const std::string& path); +void ClearDataMap(); +size_t GetNumDatasets() const; + +// Reading +int ReadNexusFile(); + +// Writing +int WriteNexusFile(const std::string& filename, int idfVersion = 2); + +// Group attribute management +bool AddGroupAttribute(const std::string& groupPath, const std::string& attrName, + const std::any& attrValue); +bool RemoveGroupAttribute(const std::string& groupPath, const std::string& attrName); +bool HasGroupAttribute(const std::string& groupPath, const std::string& attrName) const; +std::any GetGroupAttribute(const std::string& groupPath, const std::string& attrName) const; +const std::map& GetGroupAttributes(const std::string& groupPath) const; +bool ClearGroupAttributes(const std::string& groupPath); + +// Display +void Dump(); +``` + +#### Data Map Manipulation Methods + +**HasDataset(const std::string& path)** +- Check if a dataset exists in the data map +- Parameters: `path` - HDF5 path to check +- Returns: `true` if dataset exists, `false` otherwise + +**SetDataset(const std::string& path, const PNXdata& data)** +- Add or update a dataset in the data map +- Parameters: + - `path` - HDF5 path for the dataset + - `data` - PNXdata object to store +- Note: If path already exists, the dataset is updated; otherwise, it's added + +**GetDataset(const std::string& path)** +- Get a copy of a dataset from the data map +- Parameters: `path` - HDF5 path of the dataset +- Returns: PNXdata object (copy of the stored data) +- Throws: `std::out_of_range` if path doesn't exist, `std::bad_any_cast` if type mismatch +- Note: Returns a copy; modifications won't affect the data map + +**GetDatasetRef(const std::string& path)** +- Get a mutable reference to a dataset in the data map +- Parameters: `path` - HDF5 path of the dataset +- Returns: Reference to PNXdata object in the data map +- Throws: `std::out_of_range` if path doesn't exist, `std::bad_any_cast` if type mismatch +- Note: Modifications to the returned reference directly affect the data map + +**RemoveDataset(const std::string& path)** +- Remove a dataset from the data map +- Parameters: `path` - HDF5 path of the dataset to remove +- Returns: `true` if dataset was removed, `false` if path didn't exist + +**ClearDataMap()** +- Clear all datasets from the data map +- Note: After calling this, the data map will be empty + +**GetNumDatasets()** +- Get the number of datasets currently stored in the data map +- Returns: Number of datasets (size_t) + +#### Advanced Data Map Manipulation Methods + +These methods provide convenient ways to modify datasets and their properties in-place: + +**UpdateDatasetData(const std::string& path, const std::vector& newData)** +- Update only the data vector of an existing dataset (dimensions unchanged) +- Parameters: + - `path` - HDF5 path of the dataset + - `newData` - New data vector to replace existing data +- Returns: `true` if update succeeded, `false` if path doesn't exist or type mismatch +- Template parameter: Must match the original dataset type + +**UpdateDatasetDimensions(const std::string& path, const std::vector& newDimensions)** +- Update only the dimensions of an existing dataset (data unchanged) +- Parameters: + - `path` - HDF5 path of the dataset + - `newDimensions` - New dimensions vector +- Returns: `true` if update succeeded, `false` if path doesn't exist or type mismatch +- Note: Ensure data size matches new dimensions before writing to file + +**AddDatasetAttribute(const std::string& path, const std::string& attrName, const std::any& attrValue)** +- Add or update an attribute for a dataset +- Parameters: + - `path` - HDF5 path of the dataset + - `attrName` - Attribute name + - `attrValue` - Attribute value (std::any - can be int32_t, float, std::string, etc.) +- Returns: `true` if attribute was added, `false` if dataset doesn't exist or type mismatch + +**RemoveDatasetAttribute(const std::string& path, const std::string& attrName)** +- Remove an attribute from a dataset +- Parameters: + - `path` - HDF5 path of the dataset + - `attrName` - Attribute name to remove +- Returns: `true` if attribute was removed, `false` if dataset or attribute doesn't exist + +**AddDataset(const std::string& path, const std::vector& data, const std::vector& dimensions, const H5::DataType& dataType)** +- Create and add a new dataset with data and dimensions in one call +- Parameters: + - `path` - HDF5 path for the new dataset + - `data` - Data vector + - `dimensions` - Dimensions vector + - `dataType` - HDF5 DataType (optional, defaults to NATIVE_INT) +- Returns: `true` if dataset was created, `false` if path already exists +- Note: Use appropriate dataType for the template type (e.g., PredType::NATIVE_FLOAT for float) + +**ModifyDataset(const std::string& path, const std::vector& newData, const std::vector& newDimensions)** +- Modify both data and dimensions of an existing dataset in one call +- Parameters: + - `path` - HDF5 path of the dataset + - `newData` - New data vector + - `newDimensions` - New dimensions vector +- Returns: `true` if modification succeeded, `false` if path doesn't exist or type mismatch +- Note: More efficient than separate UpdateDatasetData() and UpdateDatasetDimensions() calls + +#### Group Attribute Management Methods + +**AddGroupAttribute(const std::string& groupPath, const std::string& attrName, const std::any& attrValue)** +- Add or update an attribute for an HDF5 group +- Parameters: + - `groupPath` - HDF5 path of the group (e.g., "/raw_data_1") + - `attrName` - Attribute name + - `attrValue` - Attribute value (std::any - can be int32_t, float, std::string) +- Returns: `true` if attribute was added successfully +- Note: If attribute already exists, it will be updated +- Example: `nexus.AddGroupAttribute("/raw_data_1", "NX_class", std::string("NXentry"))` + +**RemoveGroupAttribute(const std::string& groupPath, const std::string& attrName)** +- Remove an attribute from a group +- Parameters: + - `groupPath` - HDF5 path of the group + - `attrName` - Attribute name to remove +- Returns: `true` if attribute was removed, `false` if group or attribute not found + +**HasGroupAttribute(const std::string& groupPath, const std::string& attrName)** +- Check if a group has a specific attribute +- Parameters: + - `groupPath` - HDF5 path of the group + - `attrName` - Attribute name to check +- Returns: `true` if attribute exists, `false` otherwise + +**GetGroupAttribute(const std::string& groupPath, const std::string& attrName)** +- Get an attribute value from a group +- Parameters: + - `groupPath` - HDF5 path of the group + - `attrName` - Attribute name +- Returns: The attribute value as std::any (must be cast to appropriate type) +- Throws: `std::out_of_range` if group or attribute doesn't exist + +**GetGroupAttributes(const std::string& groupPath)** +- Get all attributes for a group +- Parameters: + - `groupPath` - HDF5 path of the group +- Returns: Map of attribute names to values (std::map&) +- Note: Returns empty map if group has no attributes + +**ClearGroupAttributes(const std::string& groupPath)** +- Clear all attributes from a group +- Parameters: + - `groupPath` - HDF5 path of the group +- Returns: `true` if group was found and attributes cleared, `false` otherwise + +#### Return Values + +- `ReadNexusFile()`: Returns 0 on success, 1 on error +- `WriteNexusFile()`: Returns 0 on success, 1 on error +- `RemoveDataset()`: Returns true if removed, false if path didn't exist +- `HasDataset()`: Returns true if exists, false otherwise + +#### Exceptions + +Methods may throw HDF5 exceptions: +- `H5::FileIException` - File cannot be opened/created +- `H5::GroupIException` - Group operations fail +- `H5::DataSetIException` - Dataset operations fail +- `H5::AttributeIException` - Attribute operations fail + +--- + +## Examples + +### Example 1: Read and Print Detector Counts + +```cpp +#include "PNeXus.h" +#include + +int main() { + try { + // Read file + nxH5::PNeXus nexus("EMU00139040.nxs", true); + + // Get data map + const auto& dataMap = nexus.GetDataMap(); + + // Access counts dataset + auto counts = std::any_cast>( + dataMap.at("/raw_data_1/detector_1/counts") + ); + + const auto& dims = counts.GetDimensions(); + const auto& data = counts.GetData(); + + std::cout << "Counts 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; + + // Print first 10 values from detector 1 + std::cout << "First 10 counts from detector 1:" << std::endl; + for (int i = 0; i < 10; i++) { + size_t index = 0 * dims[1] * dims[2] + 0 * dims[2] + i; + std::cout << " Bin " << i << ": " << data[index] << std::endl; + } + + return 0; + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } +} +``` + +### Example 2: Apply Background Subtraction + +```cpp +#include "PNeXus.h" +#include +#include + +int main() { + try { + // Read input file + nxH5::PNeXus nexus("input.nxs", true); + auto& dataMap = nexus.GetDataMap(); + + // Get counts + auto counts = std::any_cast>( + dataMap.at("/raw_data_1/detector_1/counts") + ); + + const auto& dims = counts.GetDimensions(); + auto& data = counts.GetData(); + + // Calculate background from last 100 bins + size_t bg_start = dims[2] - 100; + + for (size_t detector = 0; detector < dims[1]; detector++) { + // Calculate average background for this detector + double bg_sum = 0.0; + for (size_t bin = bg_start; bin < dims[2]; bin++) { + size_t index = 0 * dims[1] * dims[2] + detector * dims[2] + bin; + bg_sum += data[index]; + } + double bg_avg = bg_sum / 100.0; + + // Subtract background from all bins + for (size_t bin = 0; bin < dims[2]; bin++) { + size_t index = 0 * dims[1] * dims[2] + detector * dims[2] + bin; + int bg_subtracted = data[index] - static_cast(bg_avg); + data[index] = std::max(0, bg_subtracted); // No negative counts + } + } + + // Update data map + dataMap["/raw_data_1/detector_1/counts"] = counts; + + // Write output + int result = nexus.WriteNexusFile("background_subtracted.nxs"); + if (result == 0) { + std::cout << "Successfully wrote background-subtracted file" << std::endl; + } + + return 0; + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } +} +``` + +### Example 3: Extract and Save Single Detector + +```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 input_file = argv[1]; + int detector_num = std::stoi(argv[2]) - 1; // Convert to 0-indexed + std::string output_file = argv[3]; + + try { + // Read file + nxH5::PNeXus nexus(input_file); + const auto& dataMap = nexus.GetDataMap(); + + // Get counts and time + auto counts = std::any_cast>( + dataMap.at("/raw_data_1/detector_1/counts") + ); + auto raw_time = std::any_cast>( + dataMap.at("/raw_data_1/detector_1/raw_time") + ); + + const auto& dims = counts.GetDimensions(); + const auto& count_data = counts.GetData(); + const auto& time_data = raw_time.GetData(); + + // Validate detector number + if (detector_num < 0 || detector_num >= static_cast(dims[1])) { + std::cerr << "Detector number out of range (1-" << dims[1] << ")" + << std::endl; + return 1; + } + + // Write to text file + std::ofstream outfile(output_file); + outfile << "# Detector " << (detector_num + 1) << std::endl; + outfile << "# Time(us)\tCounts" << std::endl; + + for (size_t bin = 0; bin < dims[2]; bin++) { + size_t index = 0 * dims[1] * dims[2] + detector_num * dims[2] + bin; + outfile << time_data[bin] << "\t" << count_data[index] << std::endl; + } + + outfile.close(); + std::cout << "Extracted detector " << (detector_num + 1) + << " to " << output_file << std::endl; + + return 0; + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } +} +``` + +### Example 4: Using Data Map Manipulation Methods + +```cpp +#include "PNeXus.h" +#include + +int main() { + try { + // Read input file + nxH5::PNeXus nexus("input.nxs", true); + + // Check if specific datasets exist + if (nexus.HasDataset("/raw_data_1/detector_1/counts")) { + std::cout << "Counts dataset found" << std::endl; + } + + // Get dataset info + std::cout << "Total datasets: " << nexus.GetNumDatasets() << std::endl; + + // Get a dataset using the new API (returns a copy) + auto counts = nexus.GetDataset("/raw_data_1/detector_1/counts"); + std::cout << "Counts dimensions: "; + for (auto dim : counts.GetDimensions()) { + std::cout << dim << " "; + } + std::cout << std::endl; + + // Modify a dataset using GetDatasetRef (modifies in-place) + auto& countsRef = nexus.GetDatasetRef("/raw_data_1/detector_1/counts"); + auto& data = countsRef.GetData(); + // Scale all counts by 2 + for (auto& val : data) { + val *= 2; + } + std::cout << "Scaled all counts by factor of 2" << std::endl; + + // Create and add a new dataset + nxH5::PNXdata correction; + correction.SetDimensions({96}); + std::vector corrData(96, 1.0f); + correction.SetData(corrData); + correction.AddAttribute("units", std::string("dimensionless")); + correction.AddAttribute("description", std::string("Correction factors")); + nexus.SetDataset("/raw_data_1/detector_1/correction", correction); + std::cout << "Added correction dataset" << std::endl; + + // Verify the new dataset was added + if (nexus.HasDataset("/raw_data_1/detector_1/correction")) { + std::cout << "Correction dataset successfully added" << std::endl; + std::cout << "New total datasets: " << nexus.GetNumDatasets() << std::endl; + } + + // Remove an old dataset if it exists + if (nexus.HasDataset("/raw_data_1/old_data")) { + bool removed = nexus.RemoveDataset("/raw_data_1/old_data"); + if (removed) { + std::cout << "Removed old_data dataset" << std::endl; + } + } + + // Write modified file + int result = nexus.WriteNexusFile("modified_output.nxs"); + if (result == 0) { + std::cout << "Successfully wrote modified file" << std::endl; + } + + return 0; + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } +} +``` + +### Example 5: Using Advanced Data Map Manipulation Methods + +```cpp +#include "PNeXus.h" +#include + +int main() { + try { + // Read input file + nxH5::PNeXus nexus("input.nxs", true); + + // Example 1: Update only the data of an existing dataset + std::vector newCounts(96 * 2048, 100); // New data with same dimensions + bool success = nexus.UpdateDatasetData( + "/raw_data_1/detector_1/counts", + newCounts + ); + if (success) { + std::cout << "Successfully updated counts data" << std::endl; + } + + // Example 2: Update dimensions (e.g., after reshaping) + std::vector newDims = {1, 96, 2048}; + success = nexus.UpdateDatasetDimensions( + "/raw_data_1/detector_1/counts", + newDims + ); + if (success) { + std::cout << "Successfully updated dimensions" << std::endl; + } + + // Example 3: Add/update attributes without accessing the dataset + success = nexus.AddDatasetAttribute( + "/raw_data_1/detector_1/counts", + "processing_date", + std::string("2026-01-09") + ); + + success = nexus.AddDatasetAttribute( + "/raw_data_1/detector_1/counts", + "scale_factor", + 2.5f + ); + + success = nexus.AddDatasetAttribute( + "/raw_data_1/detector_1/counts", + "first_good_bin", + static_cast(100) + ); + + if (success) { + std::cout << "Successfully added attributes" << std::endl; + } + + // Example 4: Remove an attribute + success = nexus.RemoveDatasetAttribute( + "/raw_data_1/detector_1/counts", + "old_attribute" + ); + if (success) { + std::cout << "Successfully removed attribute" << std::endl; + } + + // Example 5: Create and add a new dataset in one call + std::vector correctionFactors(96, 1.05f); + std::vector corrDims = {96}; + + success = nexus.AddDataset( + "/raw_data_1/detector_1/efficiency_correction", + correctionFactors, + corrDims, + H5::PredType::NATIVE_FLOAT + ); + + if (success) { + std::cout << "Successfully created new dataset" << std::endl; + + // Add attributes to the new dataset + nexus.AddDatasetAttribute( + "/raw_data_1/detector_1/efficiency_correction", + "units", + std::string("dimensionless") + ); + + nexus.AddDatasetAttribute( + "/raw_data_1/detector_1/efficiency_correction", + "description", + std::string("Detector efficiency correction factors") + ); + } + + // Example 6: Modify both data and dimensions together + std::vector modifiedData(96 * 1024); // Different size + for (size_t i = 0; i < modifiedData.size(); i++) { + modifiedData[i] = i % 100; + } + std::vector modifiedDims = {1, 96, 1024}; // Changed time bins + + success = nexus.ModifyDataset( + "/raw_data_1/detector_1/counts", + modifiedData, + modifiedDims + ); + + if (success) { + std::cout << "Successfully modified dataset data and dimensions" << std::endl; + } + + // Write output file + int result = nexus.WriteNexusFile("modified_output.nxs"); + if (result == 0) { + std::cout << "Successfully wrote modified file" << std::endl; + } + + return 0; + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } +} +``` + +### Example 6: Practical Workflow - Apply Dead Time Correction + +```cpp +#include "PNeXus.h" +#include +#include + +int main() { + try { + // Read input file + nxH5::PNeXus nexus("input.nxs", true); + + // Check if required datasets exist + if (!nexus.HasDataset("/raw_data_1/detector_1/counts")) { + std::cerr << "Counts dataset not found" << std::endl; + return 1; + } + + if (!nexus.HasDataset("/raw_data_1/detector_1/dead_time")) { + std::cerr << "Dead time dataset not found" << std::endl; + return 1; + } + + // Get the counts dataset + auto counts = nexus.GetDataset("/raw_data_1/detector_1/counts"); + const auto& dims = counts.GetDimensions(); + auto countData = counts.GetData(); // Make a copy to modify + + // Get dead time corrections + auto deadTime = nexus.GetDataset("/raw_data_1/detector_1/dead_time"); + const auto& dtData = deadTime.GetData(); + + // Apply dead time correction: N_corrected = N_raw / (1 - N_raw * tau) + for (size_t period = 0; period < dims[0]; period++) { + for (size_t det = 0; det < dims[1]; det++) { + float tau = dtData[det]; // Dead time for this detector + + for (size_t bin = 0; bin < dims[2]; bin++) { + size_t idx = period * dims[1] * dims[2] + det * dims[2] + bin; + float raw = static_cast(countData[idx]); + + // Apply correction + float corrected = raw / (1.0f - raw * tau); + countData[idx] = static_cast(std::round(corrected)); + } + } + } + + // Update the counts data with corrected values + bool success = nexus.UpdateDatasetData( + "/raw_data_1/detector_1/counts", + countData + ); + + if (!success) { + std::cerr << "Failed to update counts data" << std::endl; + return 1; + } + + // Add attribute to indicate correction was applied + nexus.AddDatasetAttribute( + "/raw_data_1/detector_1/counts", + "dead_time_corrected", + std::string("yes") + ); + + nexus.AddDatasetAttribute( + "/raw_data_1/detector_1/counts", + "correction_date", + std::string("2026-01-09") + ); + + std::cout << "Dead time correction applied successfully" << std::endl; + std::cout << "Total datasets: " << nexus.GetNumDatasets() << std::endl; + + // Write corrected file + int result = nexus.WriteNexusFile("corrected_output.nxs"); + if (result == 0) { + std::cout << "Successfully wrote corrected file" << std::endl; + } + + return 0; + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } +} +``` + +### Example 7: Batch Attribute Management + +```cpp +#include "PNeXus.h" +#include +#include + +int main() { + try { + // Read input file + nxH5::PNeXus nexus("input.nxs", true); + + const std::string countPath = "/raw_data_1/detector_1/counts"; + + // Check if dataset exists + if (!nexus.HasDataset(countPath)) { + std::cerr << "Counts dataset not found" << std::endl; + return 1; + } + + // Define a batch of attributes to add + std::map newAttributes = { + {"processing_software", std::string("PNeXus v1.0")}, + {"processing_date", std::string("2026-01-09")}, + {"background_subtracted", std::string("no")}, + {"dead_time_corrected", std::string("yes")}, + {"normalization_factor", 1.0f}, + {"quality_flag", static_cast(1)} + }; + + // Add all attributes in a loop + for (const auto& [name, value] : newAttributes) { + bool success = nexus.AddDatasetAttribute(countPath, name, value); + if (success) { + std::cout << "Added attribute: " << name << std::endl; + } else { + std::cerr << "Failed to add attribute: " << name << std::endl; + } + } + + // Remove obsolete attributes + std::vector obsoleteAttrs = { + "old_version", + "deprecated_flag", + "temp_attribute" + }; + + for (const auto& attrName : obsoleteAttrs) { + bool removed = nexus.RemoveDatasetAttribute(countPath, attrName); + if (removed) { + std::cout << "Removed attribute: " << attrName << std::endl; + } + } + + // Verify final state + auto counts = nexus.GetDataset(countPath); + const auto& allAttrs = counts.GetAttributes(); + + std::cout << "\nFinal attributes count: " << allAttrs.size() << std::endl; + std::cout << "Attributes:" << std::endl; + for (const auto& [name, value] : allAttrs) { + std::cout << " - " << name << std::endl; + } + + // Write output + int result = nexus.WriteNexusFile("annotated_output.nxs"); + if (result == 0) { + std::cout << "\nSuccessfully wrote annotated file" << std::endl; + } + + return 0; + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } +} +``` + +### Example 8: Create Derivative Datasets + +```cpp +#include "PNeXus.h" +#include +#include + +int main() { + try { + // Read input file + nxH5::PNeXus nexus("input.nxs", true); + + // Get counts dataset + auto counts = nexus.GetDataset("/raw_data_1/detector_1/counts"); + const auto& dims = counts.GetDimensions(); + const auto& countData = counts.GetData(); + + // Create asymmetry dataset from forward/backward detectors + // Assuming first 48 detectors are forward, next 48 are backward + size_t nTimeBins = dims[2]; + std::vector asymmetry(nTimeBins, 0.0f); + + for (size_t bin = 0; bin < nTimeBins; bin++) { + float forward = 0.0f, backward = 0.0f; + + // Sum forward detectors (0-47) + for (size_t det = 0; det < 48; det++) { + size_t idx = det * nTimeBins + bin; + forward += countData[idx]; + } + + // Sum backward detectors (48-95) + for (size_t det = 48; det < 96; det++) { + size_t idx = det * nTimeBins + bin; + backward += countData[idx]; + } + + // Calculate asymmetry: (F - B) / (F + B) + if (forward + backward > 0) { + asymmetry[bin] = (forward - backward) / (forward + backward); + } + } + + // Add asymmetry dataset + bool success = nexus.AddDataset( + "/raw_data_1/asymmetry", + asymmetry, + {nTimeBins}, + H5::PredType::NATIVE_FLOAT + ); + + if (success) { + std::cout << "Created asymmetry dataset" << std::endl; + + // Add descriptive attributes + nexus.AddDatasetAttribute( + "/raw_data_1/asymmetry", + "units", + std::string("dimensionless") + ); + + nexus.AddDatasetAttribute( + "/raw_data_1/asymmetry", + "description", + std::string("Forward-backward asymmetry") + ); + + nexus.AddDatasetAttribute( + "/raw_data_1/asymmetry", + "formula", + std::string("(F-B)/(F+B)") + ); + + nexus.AddDatasetAttribute( + "/raw_data_1/asymmetry", + "forward_detectors", + std::string("0-47") + ); + + nexus.AddDatasetAttribute( + "/raw_data_1/asymmetry", + "backward_detectors", + std::string("48-95") + ); + } + + // Create summed counts dataset (sum over all detectors) + std::vector summedCounts(nTimeBins, 0); + for (size_t bin = 0; bin < nTimeBins; bin++) { + for (size_t det = 0; det < dims[1]; det++) { + size_t idx = det * nTimeBins + bin; + summedCounts[bin] += countData[idx]; + } + } + + success = nexus.AddDataset( + "/raw_data_1/total_counts", + summedCounts, + {nTimeBins}, + H5::PredType::NATIVE_INT + ); + + if (success) { + std::cout << "Created total counts dataset" << std::endl; + + nexus.AddDatasetAttribute( + "/raw_data_1/total_counts", + "description", + std::string("Sum of all detector counts") + ); + } + + std::cout << "Total datasets in file: " << nexus.GetNumDatasets() << std::endl; + + // Write output + int result = nexus.WriteNexusFile("derived_output.nxs"); + if (result == 0) { + std::cout << "Successfully wrote file with derivative datasets" << std::endl; + } + + return 0; + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } +} +``` + +### Example 9: Merge Multiple Runs + +```cpp +#include "PNeXus.h" +#include +#include + +int main(int argc, char* argv[]) { + if (argc < 3) { + std::cerr << "Usage: " << argv[0] + << " ..." << std::endl; + return 1; + } + + std::string output_file = argv[1]; + std::vector input_files; + for (int i = 2; i < argc; i++) { + input_files.push_back(argv[i]); + } + + try { + // Read first file as template + nxH5::PNeXus merged(input_files[0], true); + auto& dataMap = merged.GetDataMap(); + + // Get counts from first file + auto counts = std::any_cast>( + dataMap.at("/raw_data_1/detector_1/counts") + ); + + const auto& dims = counts.GetDimensions(); + auto& merged_data = counts.GetData(); + + // Add counts from remaining files + for (size_t i = 1; i < input_files.size(); i++) { + nxH5::PNeXus nexus(input_files[i]); + const auto& tempMap = nexus.GetDataMap(); + + auto temp_counts = std::any_cast>( + tempMap.at("/raw_data_1/detector_1/counts") + ); + + const auto& temp_data = temp_counts.GetData(); + const auto& temp_dims = temp_counts.GetDimensions(); + + // Verify dimensions match + if (temp_dims != dims) { + std::cerr << "Dimension mismatch in " << input_files[i] << std::endl; + return 1; + } + + // Add counts element-wise + for (size_t j = 0; j < merged_data.size(); j++) { + merged_data[j] += temp_data[j]; + } + } + + // Update data map + dataMap["/raw_data_1/detector_1/counts"] = counts; + + // Update title + auto title = std::any_cast>( + dataMap.at("/raw_data_1/title") + ); + auto& title_data = title.GetData(); + title_data[0] = "Merged: " + std::to_string(input_files.size()) + " runs"; + dataMap["/raw_data_1/title"] = title; + + // Write merged file + int result = merged.WriteNexusFile(output_file); + if (result == 0) { + std::cout << "Successfully merged " << input_files.size() + << " runs to " << output_file << std::endl; + } + + return 0; + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } +} +``` + +### Example 10: Working with Group Attributes + +```cpp +#include "PNeXus.h" +#include + +int main() { + try { + // Create a new NeXus file from scratch + nxH5::PNeXus nexus; + + // Add some detector data + std::vector counts(96 * 2048, 100); + nexus.AddDataset("/raw_data_1/detector_1/counts", counts, + {1, 96, 2048}, H5::PredType::NATIVE_INT32); + + // Add time information + std::vector timeAxis(2049); + for (size_t i = 0; i < timeAxis.size(); i++) { + timeAxis[i] = i * 0.1f; // 0.1 microsecond bins + } + nexus.AddDataset("/raw_data_1/detector_1/raw_time", timeAxis, + {2049}, H5::PredType::NATIVE_FLOAT); + + // ======================================== + // Add group attributes following NeXus convention + // ======================================== + + // Main entry group (required NX_class) + nexus.AddGroupAttribute("/raw_data_1", "NX_class", std::string("NXentry")); + nexus.AddGroupAttribute("/raw_data_1", "definition", std::string("muonTD")); + nexus.AddGroupAttribute("/raw_data_1", "experiment_identifier", + std::string("EMU2026-001")); + nexus.AddGroupAttribute("/raw_data_1", "run_cycle", std::string("2026-1")); + nexus.AddGroupAttribute("/raw_data_1", "collection_time", 7200.0f); + + // Instrument group + nexus.AddGroupAttribute("/raw_data_1/instrument", "NX_class", + std::string("NXinstrument")); + nexus.AddGroupAttribute("/raw_data_1/instrument", "name", std::string("EMU")); + nexus.AddGroupAttribute("/raw_data_1/instrument", "short_name", std::string("EMU")); + + // Detector group + nexus.AddGroupAttribute("/raw_data_1/detector_1", "NX_class", + std::string("NXdetector")); + nexus.AddGroupAttribute("/raw_data_1/detector_1", "description", + std::string("96-element positron detector array")); + nexus.AddGroupAttribute("/raw_data_1/detector_1", "detector_number", + static_cast(1)); + nexus.AddGroupAttribute("/raw_data_1/detector_1", "type", + std::string("scintillator")); + + // ======================================== + // Query and display group attributes + // ======================================== + + std::cout << "=== Group Attributes ===" << std::endl; + + // Check if specific attribute exists + if (nexus.HasGroupAttribute("/raw_data_1", "experiment_identifier")) { + auto expId = std::any_cast( + nexus.GetGroupAttribute("/raw_data_1", "experiment_identifier") + ); + std::cout << "Experiment ID: " << expId << std::endl; + } + + // Display all attributes for a group + const auto& rawDataAttrs = nexus.GetGroupAttributes("/raw_data_1"); + std::cout << "\nAttributes for /raw_data_1:" << std::endl; + for (const auto& [name, value] : rawDataAttrs) { + std::cout << " " << name << ": "; + if (auto* str = std::any_cast(&value)) { + std::cout << *str; + } else if (auto* intVal = std::any_cast(&value)) { + std::cout << *intVal; + } else if (auto* floatVal = std::any_cast(&value)) { + std::cout << *floatVal; + } + std::cout << std::endl; + } + + // ======================================== + // Modify group attributes + // ======================================== + + // Update an existing attribute + nexus.AddGroupAttribute("/raw_data_1", "collection_time", 7500.0f); + std::cout << "\nUpdated collection_time to 7500.0 seconds" << std::endl; + + // Remove an attribute + bool removed = nexus.RemoveGroupAttribute("/raw_data_1", "run_cycle"); + if (removed) { + std::cout << "Removed run_cycle attribute" << std::endl; + } + + // ======================================== + // Validate NeXus structure + // ======================================== + + // Check that all required groups have NX_class attributes + std::vector requiredGroups = { + "/raw_data_1", + "/raw_data_1/instrument", + "/raw_data_1/detector_1" + }; + + std::cout << "\n=== NeXus Structure Validation ===" << std::endl; + for (const auto& groupPath : requiredGroups) { + if (nexus.HasGroupAttribute(groupPath, "NX_class")) { + auto nxClass = std::any_cast( + nexus.GetGroupAttribute(groupPath, "NX_class") + ); + std::cout << groupPath << " -> " << nxClass << " ✓" << std::endl; + } else { + std::cout << groupPath << " -> Missing NX_class! ✗" << std::endl; + } + } + + // ======================================== + // Write the file + // ======================================== + + int result = nexus.WriteNexusFile("group_attributes_example.nxs"); + if (result == 0) { + std::cout << "\n✓ Successfully wrote NeXus file with group attributes" + << std::endl; + std::cout << " File: group_attributes_example.nxs" << std::endl; + } else { + std::cerr << "✗ Failed to write NeXus file" << std::endl; + return 1; + } + + return 0; + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } +} +``` + +--- + +## Quick Reference: Data Map Manipulation Methods + +This section provides a quick reference for choosing the right method for your task. + +### When to Use Each Method + +| Task | Method | Returns | Notes | +|------|--------|---------|-------| +| Check if dataset exists | `HasDataset(path)` | bool | Always check before accessing | +| Get dataset copy | `GetDataset(path)` | PNXdata | Returns copy, modifications don't affect data map | +| Get dataset reference | `GetDatasetRef(path)` | PNXdata& | Direct access, modifications affect data map | +| Add/replace dataset | `SetDataset(path, data)` | void | Overwrites if exists | +| Remove dataset | `RemoveDataset(path)` | bool | True if removed | +| Count datasets | `GetNumDatasets()` | size_t | Total number in data map | +| Clear all datasets | `ClearDataMap()` | void | Empties data map | +| **Update data only** | `UpdateDatasetData(path, newData)` | bool | Preserves dimensions | +| **Update dimensions only** | `UpdateDatasetDimensions(path, newDims)` | bool | Preserves data | +| **Update both** | `ModifyDataset(path, data, dims)` | bool | Changes data and dimensions | +| **Add attribute** | `AddDatasetAttribute(path, name, value)` | bool | Add or update attribute | +| **Remove attribute** | `RemoveDatasetAttribute(path, name)` | bool | Remove specific attribute | +| **Create new dataset** | `AddDataset(path, data, dims, type)` | bool | Fails if path exists | +| **Add group attribute** | `AddGroupAttribute(path, name, value)` | bool | Add or update group attribute | +| **Remove group attribute** | `RemoveGroupAttribute(path, name)` | bool | Remove group attribute | +| **Check group attribute** | `HasGroupAttribute(path, name)` | bool | Check if group attribute exists | +| **Get group attribute** | `GetGroupAttribute(path, name)` | std::any | Get group attribute value | + +### Method Selection Guide + +**To create a new dataset:** +```cpp +// Option 1: Using AddDataset (recommended for new datasets) +nexus.AddDataset("/path", data, dims, H5::PredType::NATIVE_FLOAT); + +// Option 2: Using SetDataset (more flexible, allows manual attribute setup) +PNXdata dataset(H5::PredType::NATIVE_FLOAT); +dataset.SetData(data); +dataset.SetDimensions(dims); +dataset.AddAttribute("units", std::string("microseconds")); +nexus.SetDataset("/path", dataset); +``` + +**To modify existing dataset data:** +```cpp +// Option 1: Update data only (fastest if dimensions unchanged) +nexus.UpdateDatasetData("/path", newData); + +// Option 2: Modify data and dimensions together +nexus.ModifyDataset("/path", newData, newDims); + +// Option 3: Get reference and modify directly (most flexible) +auto& dataset = nexus.GetDatasetRef("/path"); +dataset.GetData()[0] = 42; // Direct modification +``` + +**To work with attributes:** +```cpp +// Add/update dataset attributes without fetching dataset +nexus.AddDatasetAttribute("/path", "units", std::string("counts")); +nexus.AddDatasetAttribute("/path", "scale", 2.5f); + +// Remove specific dataset attribute +nexus.RemoveDatasetAttribute("/path", "old_attr"); + +// Access all dataset attributes (requires fetching dataset) +auto dataset = nexus.GetDataset("/path"); +const auto& attrs = dataset.GetAttributes(); +``` + +**To work with group attributes:** +```cpp +// Add/update group attributes +nexus.AddGroupAttribute("/raw_data_1", "NX_class", std::string("NXentry")); +nexus.AddGroupAttribute("/raw_data_1", "experiment_id", std::string("EXP001")); +nexus.AddGroupAttribute("/raw_data_1", "run_number", static_cast(12345)); + +// Check and retrieve group attributes +if (nexus.HasGroupAttribute("/raw_data_1", "experiment_id")) { + auto expId = std::any_cast( + nexus.GetGroupAttribute("/raw_data_1", "experiment_id") + ); +} + +// Get all attributes for a group +const auto& groupAttrs = nexus.GetGroupAttributes("/raw_data_1"); +for (const auto& [name, value] : groupAttrs) { + // Process attributes +} + +// Remove group attribute +nexus.RemoveGroupAttribute("/raw_data_1", "old_attr"); +``` + +### Best Practices Summary + +1. **Check existence first**: Always use `HasDataset()` before operations +2. **Choose the right access method**: + - Use `GetDataset()` when you need a copy + - Use `GetDatasetRef()` for in-place modifications + - Use `UpdateDatasetData()` for simple data updates +3. **Batch operations**: Use the specialized methods to avoid repeated casts +4. **Type safety**: Always specify the correct template parameter `` +5. **Error handling**: Check return values (bool) for all modification methods +6. **Attribute management**: + - Use `AddDatasetAttribute()` for dataset attributes + - Use `AddGroupAttribute()` for group-level metadata + - Add `NX_class` attributes to groups for NeXus compliance +7. **NeXus structure**: Use group attributes to properly identify NeXus group types + +### Common Patterns + +**Pattern 1: Read-Modify-Write** +```cpp +nxH5::PNeXus nexus("input.nxs"); +auto counts = nexus.GetDataset("/raw_data_1/detector_1/counts"); +auto data = counts.GetData(); +// ... modify data ... +nexus.UpdateDatasetData("/raw_data_1/detector_1/counts", data); +nexus.WriteNexusFile("output.nxs"); +``` + +**Pattern 2: In-Place Modification** +```cpp +nxH5::PNeXus nexus("input.nxs"); +auto& counts = nexus.GetDatasetRef("/raw_data_1/detector_1/counts"); +for (auto& val : counts.GetData()) { + val *= 2; // Directly modifies data map +} +nexus.WriteNexusFile("output.nxs"); +``` + +**Pattern 3: Add New Datasets with Attributes** +```cpp +nxH5::PNeXus nexus("input.nxs"); +std::vector corrections(96, 1.0f); +nexus.AddDataset("/raw_data_1/corrections", corrections, {96}, + H5::PredType::NATIVE_FLOAT); +nexus.AddDatasetAttribute("/raw_data_1/corrections", "units", + std::string("dimensionless")); +nexus.WriteNexusFile("output.nxs"); +``` + +**Pattern 4: Create NeXus-Compliant Structure with Group Attributes** +```cpp +nxH5::PNeXus nexus; +// Add data +nexus.AddDataset("/raw_data_1/detector_1/counts", counts, dims, + H5::PredType::NATIVE_INT32); +// Add NeXus group structure +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/detector_1", "NX_class", std::string("NXdetector")); +// Add metadata +nexus.AddGroupAttribute("/raw_data_1", "experiment_id", std::string("EXP001")); +nexus.WriteNexusFile("output.nxs"); +``` + +--- + +## Notes and Limitations + +### Current Limitations + +1. **IDF Version 1**: Write functionality not yet implemented for IDF version 1 +2. **Data Types**: Limited to int, int32_t, float, and std::string +3. **Compression**: Written files are uncompressed (can be added as future enhancement) + +### Best Practices + +1. **Always use debug mode during development**: `PNeXus nexus(file, true)` +2. **Check return values**: Both `ReadNexusFile()` and `WriteNexusFile()` return status codes +3. **Handle exceptions**: Wrap HDF5 operations in try-catch blocks +4. **Validate dimensions**: Check dimensions match expected values after reading +5. **Close resources**: HDF5 handles are automatically closed by the C++ API +6. **Use const access when possible**: Use `GetDataMap() const` for read-only operations + +### Performance Considerations + +1. **Memory usage**: Entire data map is held in memory +2. **Large files**: Be mindful of memory when working with large datasets +3. **Parallel I/O**: Not currently supported (future enhancement) +4. **Caching**: No caching layer (reads/writes go directly to HDF5) + +--- + +## Troubleshooting + +### Common Errors + +**"Error: No data to write (data map is empty)"** +- Ensure you've successfully read a file or populated the data map 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 flattened data vector size equals the product of all dimensions +- Example: dims=[1,96,2048] requires 196608 elements + +**"Could not find dataset matching case-insensitive path"** +- Path may not exist in the file +- Check available paths using `Dump()` or inspect the data map + +**"H5Gopen2 failed" or "H5::GroupIException"** +- Group creation failed - check file permissions +- Ensure parent directory exists for output file + +### Debug Output + +Enable debug output to see detailed information: + +```cpp +nxH5::PNeXus nexus("file.nxs", true); // Enable debug +``` + +Debug output shows: +- File open/close operations +- Dataset reads/writes with dimensions +- Group creation +- Attribute processing +- Error messages with context + +--- + +## Additional Resources + +- **PNXdata Usage**: See `PNXdata_usage.md` for detailed PNXdata examples +- **HDF5 Documentation**: https://portal.hdfgroup.org/documentation/index.html +- **NeXus Format**: https://www.nexusformat.org/ +- **ISIS NeXus**: https://www.isis.stfc.ac.uk/Pages/NeXus-Muon-Data.aspx + +--- + +## License + +PNeXus is licensed under the GNU General Public License v2. + +Copyright (C) 2007-2026 by Andreas Suter (andreas.suter@psi.ch) diff --git a/src/external/nexus/examples/hdf5/git_revision.sh b/src/external/nexus/examples/hdf5/git_revision.sh new file mode 100755 index 00000000..8f948d2e --- /dev/null +++ b/src/external/nexus/examples/hdf5/git_revision.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +echo "-- Generating header for git hash" +GIT_HEADER="git-revision.h" +[ -d src ] || mkdir src + +GIT_BRANCH=`git rev-parse --abbrev-ref HEAD` + +GIT_VERSION=`git log -n 1 --pretty=format:"%ad - %h"` +if [ "$(grep -ics "$GIT_VERSION" $GIT_HEADER)" = 1 ] +then + echo "-- No need to generate new $GIT_HEADER - git hash is unchanged" + exit 0; +fi + +echo "-- git branch is : " $GIT_BRANCH +echo "-- git version is : " $GIT_VERSION + +echo "#ifndef GIT_VERSION_H" > $GIT_HEADER +echo "#define GIT_VERSION_H" >> $GIT_HEADER +echo "" >> $GIT_HEADER +echo "#define GIT_BRANCH \"$GIT_BRANCH\"" >> $GIT_HEADER +echo "#define GIT_CURRENT_SHA1 \"$GIT_VERSION\"" >> $GIT_HEADER +echo "" >> $GIT_HEADER +echo "#endif //GIT_VERSION_H" >> $GIT_HEADER + +echo "-- file is generated into" $GIT_HEADER diff --git a/src/external/nexus/examples/hdf5/main.cpp b/src/external/nexus/examples/hdf5/main.cpp new file mode 100644 index 00000000..0c216454 --- /dev/null +++ b/src/external/nexus/examples/hdf5/main.cpp @@ -0,0 +1,572 @@ +/*************************************************************************** + + main.cpp + + Author: Andreas Suter + e-mail: andreas.suter@psi.ch + +***************************************************************************/ + +/*************************************************************************** + * Copyright (C) 2007-2026 by Andreas Suter * + * andreas.suter@psi.ch * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +/** + * @file main.cpp + * @brief Command-line interface for the h5nexus NeXus HDF5 file reader/writer + * + * This file contains the main() function and command-line interface for the + * h5nexus program. It provides functionality to: + * - Read and display NeXus HDF5 files + * - Write NeXus HDF5 files using the PNXdata approach + * - Calculate dead time corrections for muon detector data + * + * **Command-Line Options:** + * - --fn : Input NeXus HDF5 file (required) + * - --out : Output NeXus HDF5 file (optional) + * - --debug, -d: Enable debug output + * - --dead_time_estimate, -dt: Calculate dead time corrections + * - --help, -h: Display help message + * - --version, -v: Display version information + * + * @author Andreas Suter + * @date 2007-2026 + * @copyright GNU General Public License v2 + * @version 1.0 + * + * @see nxH5::PNeXus + * @see nxH5::PNeXusDeadTime + */ + +#include +#include +#include +#include +#include +#include + +#include "hdf5.h" + +#include "PNeXus.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_GIT_REV_H +#include "git-revision.h" +#endif + +//----------------------------------------------------------------------------- +/** + * @brief Display command-line syntax and help information + * + * Prints the usage syntax and available command-line options for the h5nexus + * program to stdout and exits the program. + */ +void h5nexus_syntax() { + std::cout << std::endl; + std::cout << "usage: h5nexus [--help | -h] |" << std::endl; + std::cout << " [--version | -v] |" << std::endl; + std::cout << " --fn [--debug | -d]" << std::endl; + std::cout << " [--dead_time_estimate | -dt]]" << std::endl; + std::cout << " [--out ]" << std::endl; + std::cout << " [--data idx ]" << std::endl; + std::cout << std::endl; + std::cout << "options:" << std::endl; + std::cout << " --help, -h: this help." << std::endl; + std::cout << " --version, -v: version of h5nexus." << std::endl; + std::cout << " --fn : nexus hdf5 input file name ." << std::endl; + std::cout << " --dead_time_estimate, -dt: dead time estimate for the read hdf5 nexus file." << std::endl; + std::cout << " --debug, -d: print additional debug information." << std::endl; + std::cout << " --out : write the required datasets of a nexus hdf5 file to file " << std::endl; + std::cout << " with name . Only makes sense together with the option --fn ." << std::endl; + std::cout << " --data idx : write a single ascii data set with idx to ." << std::endl; + std::cout << " Only makes sense together with the option --fn ." << std::endl; + std::cout << std::endl; + exit(0); +} + +//----------------------------------------------------------------------------- +/** + * @brief Calculate dead time corrections for muon detector data + * + * Estimates dead time corrections for each detector in the NeXus file using + * the PNeXusDeadTime class and ROOT Minuit2 minimization. + * + * @param nxs Pointer to the PNeXus object containing the data + * @param debug If true, print additional debug information + * + * @see nxH5::PNeXusDeadTime + */ +std::vector h5nexus_deadTimeEstimate(const nxH5::PNeXus *nxs, bool debug) +{ + if (debug) { + std::cout << std::endl; + std::cout << std::endl << "+++++++++++++++++++"; + std::cout << std::endl << "in deadTimeEstimate"; + std::cout << std::endl << "+++++++++++++++++++"; + std::cout << std::endl; + } + + nxH5::PNeXusDeadTime ndt(nxs, debug); + auto dims = ndt.GetDimensions(); + for (unsigned int i=0; i dte) +{ + std::cout << std::endl << "as35> in h5nexus_writeData: idf version: " << nxs->GetIdfVersion(); + std::vector counts; + std::vector dt; + std::vector dims; + float resolution{0.0}; + int good_frames{0}; + + if (nxs->GetIdfVersion() == 1) { + if (nxs->HasDataset("/run/histogram_data_1/counts")) { + std::cout << std::endl << "as35> found counts in idf version 1"; + } + } else { + auto dataMap = nxs->GetDataMap(); + if (nxs->HasDataset("/raw_data_1/detector_1/counts")) { + std::cout << std::endl << "as35> found counts in idf version 2"; + auto counts_data = std::any_cast>(dataMap["/raw_data_1/detector_1/counts"]); + counts = counts_data.GetData(); + auto dd = counts_data.GetDimensions(); + for (auto i=0; i dims: " << dims.size() << ": "; + for (auto i=0; i= dims[1]) { + std::cerr << std::endl << "**ERROR** idx=" << idx << " is >= number of dataset=" << dims[1] << std::endl; + return; + } + if (nxs->HasDataset("/raw_data_1/detector_1/dead_time")) { + std::cout << std::endl << "as35> found dead_time in idf version 2"; + auto dt_data = std::any_cast>(dataMap["/raw_data_1/detector_1/dead_time"]); + dt = dt_data.GetData(); + } + if (nxs->HasDataset("/raw_data_1/instrument/detector_1/resolution")) { + std::cout << std::endl << "as35> found resolution in idf version 2"; + auto r_data = std::any_cast>(dataMap["/raw_data_1/instrument/detector_1/resolution"]); + auto rr = r_data.GetData(); + resolution = (float)rr[0]; + if (r_data.HasAttribute("units")) { + std::string units = std::any_cast(r_data.GetAttribute("units")); + if (units == "picoseconds") + resolution *= 1.0e-6; + else if (units == "nanoseconds") + resolution *= 1.0e-3; + } + } + if (nxs->HasDataset("/raw_data_1/good_frames")) { + std::cout << std::endl << "as35> found good_frames in idf version 2"; + auto gf_data = std::any_cast>(dataMap["/raw_data_1/good_frames"]); + good_frames = gf_data.GetData()[0]; + } + } + std::cout << std::endl; + + float dtei; + if (dte.size() > idx) + dtei = dte[idx]; + + // write dataset + std::ofstream fout(dataOutFln); + fout << "# NeXus fln: " << fln << std::endl; + fout << "# idx=" << idx << std::endl; + fout << "# resolution : " << resolution << " (us)" << std::endl; + fout << "# good_frames : " << good_frames << std::endl; + fout << "# dead_time : " << dt[idx] << " (us) : from file" << std::endl; + fout << "# dead_time : " << dtei << " (us) : from estimater" << std::endl; + fout << "# ------" << std::endl; + fout << "# raw counts, dead time corrected counts (file), dead time corrected counts (estimated)" << std::endl; + int cc{0}, dtcc{0}, dtecc{0}; + // see https://docs.mantidproject.org/v3.9.0/algorithms/ApplyDeadTimeCorr-v1.html#algm-applydeadtimecorr + for (auto i=0; iGetIdfVersion() == 1) { + std::cerr << "Error: IDF v1 write not yet implemented" << std::endl; + return; + } + + // Write using the read object's data + int result = const_cast(nxs)->WriteNexusFile(outFileName, nxs->GetIdfVersion()); + + if (result == 0) { + std::cout << "Successfully wrote: " << outFileName << std::endl; + } else { + std::cerr << "Failed to write file: " << outFileName << std::endl; + } + + // write data from scratch + + std::unique_ptr nxs_out = std::make_unique(); + + std::vector ival; + std::vector fval; + std::vector sval; + + H5::StrType strType(H5::PredType::C_S1, H5T_VARIABLE); + + // ---------- + // raw_data_1 + // ---------- + + // IDF version + ival.push_back(2); + nxs_out->AddDataset("/raw_data_1/IDF_version", ival, {1}, H5::PredType::NATIVE_INT); + ival.clear(); + + // add group attribute to '/raw_data_1' + nxs_out->AddGroupAttribute("/raw_data_1", "NX_class", std::string("NXentry")); + + // beamline + sval.push_back("piE3"); + nxs_out->AddDataset("/raw_data_1/beamline", sval, {1}, strType); + sval.clear(); + + // definition + sval.push_back("muonTD"); + nxs_out->AddDataset("/raw_data_1/definition", sval, {1}, strType); + sval.clear(); + + // run_number + ival.push_back(1234); + nxs_out->AddDataset("/raw_data_1/run_number", ival, {1}, H5::PredType::NATIVE_INT); + ival.clear(); + + // title + sval.push_back("this is the run title."); + nxs_out->AddDataset("/raw_data_1/title", sval, {1}, strType); + sval.clear(); + + // start time + sval.push_back("2026-01-01T01:02:03"); + nxs_out->AddDataset("/raw_data_1/start_time", sval, {1}, strType); + sval.clear(); + + // end time + sval.push_back("2026-01-01T02:03:42"); + nxs_out->AddDataset("/raw_data_1/end_time", sval, {1}, strType); + sval.clear(); + + // experiment_identifier - pgroup for PSI + sval.push_back("p18324"); + nxs_out->AddDataset("/raw_data_1/experiment_identifier", sval, {1}, strType); + sval.clear(); + + // ------------------- + // detector_1 (NXdata) + // ------------------- + + // add group attribute to /raw_data_1/instrument + nxs_out->AddGroupAttribute("/raw_data_1/detector_1", "NX_class", std::string("NXdata")); + + // counts + std::vector counts(16*66000, 42); // data 16 histos with length 66000 + nxs_out->AddDataset("/raw_data_1/detector_1/counts", counts, {1, 16, 66000}, H5::PredType::NATIVE_INT); + + // attributes for counts + nxs_out->AddDatasetAttribute("/raw_data_1/detector_1/counts", "signal", 1); + nxs_out->AddDatasetAttribute("/raw_data_1/detector_1/counts", "axes", std::string("period_index,spectrum_index,raw_time")); + nxs_out->AddDatasetAttribute("/raw_data_1/detector_1/counts", "long_name", std::string("positron_counts")); + nxs_out->AddDatasetAttribute("/raw_data_1/detector_1/counts", "t0_bin", 2741); + nxs_out->AddDatasetAttribute("/raw_data_1/detector_1/counts", "first_good_bin", 2741); + nxs_out->AddDatasetAttribute("/raw_data_1/detector_1/counts", "last_good_bin", 66000); + nxs_out->AddDatasetAttribute("/raw_data_1/detector_1/counts", "units", std::string("counts")); + nxs_out->AddDatasetAttribute("/raw_data_1/detector_1/counts", "target", std::string("/raw_data_1/instrument/detector_1/counts")); + + // raw_time + std::vector raw_time(66000, 0.0); + for (unsigned int i=0; iAddDataset("/raw_data_1/detector_1/raw_time", raw_time, {66000}, H5::PredType::NATIVE_FLOAT); + + // attributes raw_time + nxs_out->AddDatasetAttribute("/raw_data_1/detector_1/raw_time", "units", std::string("microseconds")); + nxs_out->AddDatasetAttribute("/raw_data_1/detector_1/raw_time", "target", std::string("/raw_data_1/instrument/detector_1/raw_time")); + + + // ---------- + // instrument + // ---------- + + // add group attribute to /raw_data_1/instrument + nxs_out->AddGroupAttribute("/raw_data_1/instrument", "NX_class", std::string("NXinstrument")); + + // name + sval.push_back("LEM"); + nxs_out->AddDataset("/raw_data_1/instrument/name", sval, {1}, strType); + sval.clear(); + + // ------ + // source + // ------ + + // add group attribute to /raw_data_1/instrument/source + nxs_out->AddGroupAttribute("/raw_data_1/instrument/source", "NX_class", std::string("NXsource")); + + // name + sval.push_back("PSI"); + nxs_out->AddDataset("/raw_data_1/instrument/source/name", sval, {1}, strType); + sval.clear(); + + // type + sval.push_back("continuous muon source"); + nxs_out->AddDataset("/raw_data_1/instrument/source/types", sval, {1}, strType); + sval.clear(); + + // probe + sval.push_back("postive muons"); + nxs_out->AddDataset("/raw_data_1/instrument/source/probe", sval, {1}, strType); + sval.clear(); + + // ----------------------- + // detector_1 (NXdetector) + // ----------------------- + + // add group attribute to /raw_data_1/instrument/detector_1 + nxs_out->AddGroupAttribute("/raw_data_1/instrument/detector_1", "NX_class", std::string("NXdetector")); + + // counts + nxs_out->AddDataset("/raw_data_1/instrument/detector_1/counts", counts, {1, 16, 66000}, H5::PredType::NATIVE_INT); + + // attributes for counts + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "signal", 1); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "axes", std::string("period_index,spectrum_index,raw_time")); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "long_name", std::string("positron_counts")); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "t0_bin", 2741); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "first_good_bin", 2741); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "last_good_bin", 66000); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "units", std::string("counts")); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/counts", "target", std::string("/raw_data_1/instrument/detector_1/counts")); + + // raw_time + nxs_out->AddDataset("/raw_data_1/instrument/detector_1/raw_time", raw_time, {66000}, H5::PredType::NATIVE_FLOAT); + + // attributes raw_time + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/raw_time", "units", std::string("microseconds")); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/raw_time", "target", std::string("/raw_data_1/instrument/detector_1/raw_time")); + + // resolution + fval.push_back(195.3125); + nxs_out->AddDataset("/raw_data_1/instrument/detector_1/resolution", fval, {1}, H5::PredType::NATIVE_FLOAT); + fval.clear(); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/resolution", + "units", std::string("picoseconds")); + + // spectrum_index + for (unsigned int i=0; i<66000; i++) + ival.push_back(i+1); + nxs_out->AddDataset("/raw_data_1/instrument/detector_1/spectrum_index", ival, {66000}, H5::PredType::NATIVE_FLOAT); + ival.clear(); + + // dead_time + std::vector deadTime(66000, 0.0); + nxs_out->AddDataset("/raw_data_1/instrument/detector_1/dead_time", deadTime, {66000}, H5::PredType::NATIVE_FLOAT); + + // attributes dead_time + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/dead_time", "available", 0); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/dead_time", "units", std::string("microseconds")); + nxs_out->AddDatasetAttribute("/raw_data_1/instrument/detector_1/dead_time", "target", std::string("/raw_data_1/instrument/detector_1/dead_time")); + + // add root attributes + // file name + nxs_out->AddRootAttribute("file_name", std::string("_test.nxs")); + // date-time + std::time_t time = std::time({}); + char timeString[std::size("yyyy-mm-ddThh:mm:ssZ")]; + std::strftime(std::data(timeString), std::size(timeString), + "%FT%TZ", std::gmtime(&time)); + nxs_out->AddRootAttribute("file_time", std::string(timeString)); + // NeXus version + nxs_out->AddRootAttribute("NeXus_Version", std::string("4.3.0")); + // hdf5 version + nxs_out->AddRootAttribute("HDF5_Version", std::string(nxs->GetHdf5Version())); + // creator + nxs_out->AddRootAttribute("creator", std::string("h5nexus - PSI")); + + nxs_out->WriteNexusFile("_test.nxs"); +} + +//----------------------------------------------------------------------------- +/** + * @brief Main entry point for the h5nexus program + * + * Parses command-line arguments and performs the requested operations: + * - Read and display NeXus HDF5 file information + * - Write NeXus HDF5 files with modified data + * - Calculate dead time corrections + * + * @param argc Number of command-line arguments + * @param argv Array of command-line argument strings + * + * @return 0 on success, non-zero on error + * + * @see h5nexus_syntax() for available command-line options + */ +int main(int argc, char *argv[]) +{ + std::string fileName{""}; + std::string fileNameOut{""}; + std::string dataNameOut{""}; + int idx{-1}; + bool printDebug{false}; + bool deadTimeEstimate{false}; + + if (argc == 1) + h5nexus_syntax(); + + for (int i=1; i= argc) { + std::cout << std::endl << "**ERROR** found --fn without ." << std::endl; + h5nexus_syntax(); + } + i++; + fileName = argv[i]; + } else if (!strcmp(argv[i], "-dt") || !strcmp(argv[i], "--dead_time_estimate")) { + deadTimeEstimate = true; + } else if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) { + printDebug = true; + } else if (!strcmp(argv[i], "--data")) { + if (i+2 >= argc) { + std::cout << std::endl << "**ERROR** in --data idx ." << std::endl; + h5nexus_syntax(); + } + int ii; + try { + ii = std::stoi(argv[i+1]); + } catch (const std::invalid_argument& ia) { + std::cout << std::endl << "**ERROR** couldn't convert idx from arguments given." << std::endl; + h5nexus_syntax(); + } + if (ii < 0) { + std::cout << std::endl << "**ERROR** found idx < 0, namely " << ii << std::endl; + h5nexus_syntax(); + } + idx = ii; + dataNameOut = argv[i+2]; + i += 2; + } else if (!strcmp(argv[i], "--out")) { + if (i+1 >= argc) { + std::cout << std::endl << "**ERROR** found --out without ." << std::endl; + h5nexus_syntax(); + } + i++; + fileNameOut = argv[i]; + } else { + h5nexus_syntax(); + } + } + + if (fileName.empty()) { + std::cerr << std::endl; + std::cerr << "**ERROR** is missing." << std::endl; + std::cerr << std::endl; + h5nexus_syntax(); + } + + if (printDebug) { + std::cout << std::endl; + std::cout << ">> fln = '" << fileName << "'" << std::endl; + std::cout << ">> fout = '" << fileNameOut << "'" << std::endl; + std::cout << ">> dout = '" << dataNameOut << "', idx=" << idx << std::endl; + std::cout << std::endl; + } + + std::unique_ptr nxs = std::make_unique(fileName, printDebug); + + nxs->Dump(); + + std::vector dte; + if (deadTimeEstimate) { + dte = h5nexus_deadTimeEstimate(nxs.get(), printDebug); + } + + if (!fileNameOut.empty()) { + h5nexus_writeTest(nxs.get(), fileNameOut, printDebug); + } + + if (!dataNameOut.empty()) { + h5nexus_writeData(nxs.get(), fileName, dataNameOut, idx, dte); + } + + return 0; +} diff --git a/src/external/nexus/nexus_definitions/NeXus-Def-V2-Rev10.pdf b/src/external/nexus/nexus_definitions/NeXus-Def-V2-Rev10.pdf new file mode 100644 index 00000000..337d7b03 Binary files /dev/null and b/src/external/nexus/nexus_definitions/NeXus-Def-V2-Rev10.pdf differ