/*************************************************************************** PNeXus.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 PNeXus.cpp * @brief Implementation of the PNeXus class - NeXus HDF4/HDF5 file reader and writer * * This file contains the implementation of the PNeXus class for reading and * writing ISIS muon NeXus HDF5 files. It provides case-insensitive path * lookup functionality using the HDF4 C / HDF5 C++ API. * * The implementation includes: * * **Read Functionality:** * - String utilities for case-insensitive comparison * - Path parsing for HDF4 / HDF5 hierarchical paths * - Recursive path traversal with case-insensitive matching * - Attribute, group, and dataset lookup functions * - Support for IDF version 1 and 2 * * **Write Functionality:** * - Group hierarchy creation * - Dataset writing for int, float, and string types * - Attribute preservation * - Multi-dimensional array support * - Type-safe operations using PNXdata template class * * **Error Handling:** * - Comprehensive error handling with descriptive exceptions * - Validation of data consistency * - HDF4 error propagation / HDF5 exception propagation * * @author Andreas Suter * @date 2007-2026 * @copyright GNU General Public License v2 * @version 1.0 * * @see nxH4::PNeXus * @see nxH4::PNXdata * @see nxH5::PNeXus * @see nxH5::PNXdata */ #include #include #include #include #include #include #include "Minuit2/MnStrategy.h" #include "Minuit2/MnMinimize.h" #include "Minuit2/FunctionMinimum.h" #include "PNeXus.h" //============================================================================= // nxs::checkHDFType - Determine HDF file format from magic bytes //============================================================================= /** * @brief Determine the HDF format type of a file by reading its header * * Opens the file in binary mode and reads the first 8 bytes to compare * against known HDF4 and HDF5 magic byte signatures. This allows automatic * format detection before attempting to open the file with the appropriate * library. * * @param filename Path to the file to check * @return nxs::HDFType indicating the format (HDF4, HDF5, or Unknown) */ nxs::HDFType nxs::checkHDFType(const std::string& filename) { std::ifstream file(filename, std::ios::binary); if (!file.is_open()) { std::cerr << "Error: Cannot open file '" << filename << "'" << std::endl; return nxs::HDFType::Unknown; } // Read first 8 bytes (enough for both signatures) unsigned char header[8]; file.read(reinterpret_cast(header), 8); if (file.gcount() < 4) { std::cerr << "**Error**: File too small to be HDF4 or HDF5" << std::endl; return nxs::HDFType::Unknown; } // HDF5 signature: 0x89 'H' 'D' 'F' 0x0d 0x0a 0x1a 0x0a const unsigned char hdf5_sig[8] = {0x89, 0x48, 0x44, 0x46, 0x0d, 0x0a, 0x1a, 0x0a}; // HDF4 signature: 0x0e 0x03 0x13 0x01 const unsigned char hdf4_sig[4] = {0x0e, 0x03, 0x13, 0x01}; // Check HDF5 first (needs 8 bytes) if (file.gcount() >= 8 && std::memcmp(header, hdf5_sig, 8) == 0) { return nxs::HDFType::HDF5; } // Check HDF4 (needs 4 bytes) if (std::memcmp(header, hdf4_sig, 4) == 0) { return nxs::HDFType::HDF4; } return nxs::HDFType::Unknown; } #ifdef HAVE_HDF4 //============================================================================= // nxH4::PNeXusDeadTime Constructor //============================================================================= nxH4::PNeXusDeadTime::PNeXusDeadTime(const nxH4::PNeXus *nxs, bool debug) : fDebug(debug) { std::map dataMap = nxs->GetDataMap(); int idfVersion = nxs->GetIdfVersion(); if ((idfVersion != 1) && (idfVersion != 2)) { std::stringstream err; err << "**ERROR** Found unsupported IDF version '" << idfVersion << "'"; std::cout << err.str() << std::endl; fValid = false; return; } if (idfVersion == 1) { // not yet implemented } else { // idfVersion == 2 // get counts if (dataMap.find("/raw_data_1/instrument/detector_1/counts") != dataMap.end()) { auto counts_data = std::any_cast>(dataMap["/raw_data_1/instrument/detector_1/counts"]); const auto& counts = counts_data.GetData(); fCounts = counts; auto dims = counts_data.GetDimensions(); if (fDebug) { std::cout << " counts dimensions: " << dims[0] << " x " << dims[1] << " x " << dims[2] << std::endl; } fDims = dims; } else { std::cout << "**ERROR** Couldn't find 'counts' dataset" << std::endl; fValid = false; return; } // get time_resolution if (dataMap.find("/raw_data_1/instrument/detector_1/time_resolution") != dataMap.end()) { auto time_res_data = std::any_cast>(dataMap["/raw_data_1/instrument/detector_1/time_resolution"]); const auto& time_res = time_res_data.GetData(); if (time_res.size() > 0) { fTimeResolution = time_res[0]; if (fDebug) { std::cout << " time resolution: " << fTimeResolution << " ps" << std::endl; } } } else { std::cout << "**ERROR** Couldn't find 'time_resolution' dataset" << std::endl; fValid = false; return; } // get good_frames if (dataMap.find("/raw_data_1/good_frames") != dataMap.end()) { auto good_frames_data = std::any_cast>(dataMap["/raw_data_1/good_frames"]); const auto& good_frames = good_frames_data.GetData(); if (good_frames.size() > 0) { fGoodFrames = good_frames[0]; if (fDebug) { std::cout << " good frames: " << fGoodFrames << std::endl; } } } else { std::cout << "**ERROR** Couldn't find 'good_frames' dataset" << std::endl; fValid = false; return; } // get t0_bin, first_good_bin, last_good_bin if (dataMap.find("/raw_data_1/instrument/detector_1/spectrum_index") != dataMap.end()) { auto spec_idx_data = std::any_cast>(dataMap["/raw_data_1/instrument/detector_1/spectrum_index"]); if (spec_idx_data.HasAttribute("t0_bin")) { try { fT0Bin = std::any_cast(spec_idx_data.GetAttribute("t0_bin")); } catch (...) { std::cout << "**WARNING** Couldn't read t0_bin attribute" << std::endl; } } if (spec_idx_data.HasAttribute("first_good_bin")) { try { fFgbBin = std::any_cast(spec_idx_data.GetAttribute("first_good_bin")); } catch (...) { std::cout << "**WARNING** Couldn't read first_good_bin attribute" << std::endl; } } if (spec_idx_data.HasAttribute("last_good_bin")) { try { fLgbBin = std::any_cast(spec_idx_data.GetAttribute("last_good_bin")); } catch (...) { std::cout << "**WARNING** Couldn't read last_good_bin attribute" << std::endl; } } } if (fDebug) { std::cout << " t0_bin: " << fT0Bin << ", first_good_bin: " << fFgbBin << ", last_good_bin: " << fLgbBin << std::endl; } } fValid = true; } //============================================================================= // PNeXusDeadTime::operator() //============================================================================= double nxH4::PNeXusDeadTime::operator()(const std::vector &par) const { double chisq = 0.0; double tau = par[0]; // dead time in microseconds // Convert time resolution from ps to microseconds double dt = fTimeResolution * 1.0e-6; // ps -> us // Calculate chi-square for the specified spectrum int period = 0; // assuming single period for (unsigned int bin = fFgbBin; bin <= static_cast(fLgbBin); bin++) { int idx = period * fDims[1] * fDims[2] + fIdx * fDims[2] + bin; double n_obs = static_cast(fCounts[idx]); if (n_obs > 0) { // Dead time correction: n_true = n_obs / (1 - n_obs * tau / (good_frames * dt)) double correction = 1.0 - (n_obs * tau) / (fGoodFrames * dt); if (correction > 0) { double n_expected = n_obs / correction; double diff = n_obs - n_expected; chisq += (diff * diff) / n_expected; } } } return chisq; } //============================================================================= // PNeXusDeadTime::minimize //============================================================================= void nxH4::PNeXusDeadTime::minimize(const int i) { if (i < 0 || i >= static_cast(fDims[1])) { std::cerr << "**ERROR** Invalid spectrum index: " << i << std::endl; return; } fIdx = static_cast(i); ROOT::Minuit2::MnUserParameters upar; upar.Add("dead_time", 0.1, 0.01); // initial value, step size upar.SetLimits("dead_time", 0.0, 10.0); // limits in microseconds ROOT::Minuit2::MnMinimize minimize(*this, upar); ROOT::Minuit2::FunctionMinimum min = minimize(); std::cout << "Spectrum " << i << ": "; if (min.IsValid()) { double dt = min.UserState().Value("dead_time"); double dt_err = min.UserState().Error("dead_time"); std::cout << "Dead time = " << dt << " +/- " << dt_err << " us" << std::endl; } else { std::cout << "Minimization failed" << std::endl; } } //============================================================================= // PNeXus Default Constructor //============================================================================= nxH4::PNeXus::PNeXus() : fSdId(-1), fFileId(-1) { } //============================================================================= // PNeXus Constructor with filename //============================================================================= nxH4::PNeXus::PNeXus(const std::string fln, const bool printDebug) : fPrintDebug(printDebug), fFileName(fln), fSdId(-1), fFileId(-1) { if (ReadNexusFile() != 0) { throw std::runtime_error("Failed to read NeXus file: " + fln); } } //============================================================================= // PNeXus Destructor //============================================================================= nxH4::PNeXus::~PNeXus() { if (fSdId != -1) { SDend(fSdId); } if (fFileId != -1) { Hclose(fFileId); } } //============================================================================= // PNeXus::ReadNexusFile //============================================================================= int nxH4::PNeXus::ReadNexusFile() { // Open the HDF4 file fSdId = SDstart(fFileName.c_str(), DFACC_READ); if (fSdId == FAIL) { std::cerr << "**ERROR** Failed to open HDF4 file: " << fFileName << std::endl; return 1; } fFileId = Hopen(fFileName.c_str(), DFACC_READ, 0); if (fFileId == FAIL) { std::cerr << "**ERROR** Failed to open HDF4 file (H interface): " << fFileName << std::endl; SDend(fSdId); fSdId = -1; return 1; } // Read file-level attributes int32 n_datasets, n_file_attrs; if (SDfileinfo(fSdId, &n_datasets, &n_file_attrs) == FAIL) { std::cerr << "**ERROR** Failed to get file info" << std::endl; return 1; } if (fPrintDebug) { std::cout << "Number of datasets: " << n_datasets << std::endl; std::cout << "Number of file attributes: " << n_file_attrs << std::endl; } // Read HDF4 version and NeXus version from attributes char attr_name[H4_MAX_NC_NAME]; int32 attr_type, attr_count; for (int32 i = 0; i < n_file_attrs; i++) { if (SDattrinfo(fSdId, i, attr_name, &attr_type, &attr_count) == FAIL) { continue; } if (caseInsensitiveEquals(attr_name, "HDF_version") || caseInsensitiveEquals(attr_name, "HDF4_version")) { std::vector buffer(attr_count + 1, '\0'); if (SDreadattr(fSdId, i, buffer.data()) != FAIL) { fHdf4Version = std::string(buffer.data()); } } else if (caseInsensitiveEquals(attr_name, "NeXus_version")) { std::vector buffer(attr_count + 1, '\0'); if (SDreadattr(fSdId, i, buffer.data()) != FAIL) { fNeXusVersion = std::string(buffer.data()); } } else if (caseInsensitiveEquals(attr_name, "file_name")) { std::vector buffer(attr_count + 1, '\0'); if (SDreadattr(fSdId, i, buffer.data()) != FAIL) { fFileNameNxs = std::string(buffer.data()); } } else if (caseInsensitiveEquals(attr_name, "file_time")) { std::vector buffer(attr_count + 1, '\0'); if (SDreadattr(fSdId, i, buffer.data()) != FAIL) { fFileTimeNxs = std::string(buffer.data()); } } } // Determine IDF version // Try to find IDF_version dataset - check multiple possible locations // First try simple root-level name int32 idf_idx = SDnametoindex(fSdId, "IDF_version"); if (idf_idx != FAIL) { int32 sds_id = SDselect(fSdId, idf_idx); if (sds_id != FAIL) { int32 rank, dim_sizes[H4_MAX_VAR_DIMS], data_type, n_attrs; char name[H4_MAX_NC_NAME]; if (SDgetinfo(sds_id, name, &rank, dim_sizes, &data_type, &n_attrs) != FAIL) { if (data_type == DFNT_INT32) { int32 idf_val; int32 start[H4_MAX_VAR_DIMS] = {0}; int32 edges[H4_MAX_VAR_DIMS] = {1}; if (SDreaddata(sds_id, start, nullptr, edges, &idf_val) != FAIL) { fIdfVersion = idf_val; } } } SDendaccess(sds_id); } } // If not found, try /run/IDF_version (IDF version 1 location) if (fIdfVersion == -1) { try { int32 sds_ref = findDatasetRefByPath("/run/IDF_version"); if (sds_ref != -1) { int32 sds_idx = SDreftoindex(fSdId, sds_ref); if (sds_idx != FAIL) { int32 sds_id = SDselect(fSdId, sds_idx); if (sds_id != FAIL) { int32 rank, dim_sizes[H4_MAX_VAR_DIMS], data_type, n_attrs; char name[H4_MAX_NC_NAME]; if (SDgetinfo(sds_id, name, &rank, dim_sizes, &data_type, &n_attrs) != FAIL) { if (data_type == DFNT_INT32) { int32 idf_val; int32 start[H4_MAX_VAR_DIMS] = {0}; int32 edges[H4_MAX_VAR_DIMS] = {1}; if (SDreaddata(sds_id, start, nullptr, edges, &idf_val) != FAIL) { fIdfVersion = idf_val; } } } SDendaccess(sds_id); } } } } catch (...) { // Path not found, continue to next attempt } } // If still not found, try /raw_data_1/idf_version (IDF version 2 location) if (fIdfVersion == -1) { try { int32 sds_ref = findDatasetRefByPath("/raw_data_1/idf_version"); if (sds_ref != -1) { int32 sds_idx = SDreftoindex(fSdId, sds_ref); if (sds_idx != FAIL) { int32 sds_id = SDselect(fSdId, sds_idx); if (sds_id != FAIL) { int32 rank, dim_sizes[H4_MAX_VAR_DIMS], data_type, n_attrs; char name[H4_MAX_NC_NAME]; if (SDgetinfo(sds_id, name, &rank, dim_sizes, &data_type, &n_attrs) != FAIL) { if (data_type == DFNT_INT32) { int32 idf_val; int32 start[H4_MAX_VAR_DIMS] = {0}; int32 edges[H4_MAX_VAR_DIMS] = {1}; if (SDreaddata(sds_id, start, nullptr, edges, &idf_val) != FAIL) { fIdfVersion = idf_val; } } } SDendaccess(sds_id); } } } } catch (...) { // Path not found, will use default } } if (fIdfVersion == -1) { // Try alternate approach - check for raw_data_1 structure // If we find raw_data_1/detector_1, assume IDF v2 // Otherwise assume IDF v1 fIdfVersion = 2; // default to version 2 } if (fPrintDebug) { std::cout << "HDF4 Version: " << fHdf4Version << std::endl; std::cout << "NeXus Version: " << fNeXusVersion << std::endl; std::cout << "IDF Version: " << fIdfVersion << std::endl; } // Read datasets based on IDF version if (fIdfVersion == 1) { HandleIdfV1(fSdId); } else if (fIdfVersion == 2) { HandleIdfV2(fSdId); } else { std::cerr << "**ERROR** Unsupported IDF version: " << fIdfVersion << std::endl; return 1; } return 0; } //============================================================================= // nxH4::PNeXus::HandleIdfV1 //============================================================================= void nxH4::PNeXus::HandleIdfV1(int32 sd_id) { // IDF version 1 handling - not yet implemented if (fPrintDebug) { std::cout << "IDF version 1 handling ..." << std::endl; } std::vector int_datasets = { "/run/IDF_version", "/run/number", "/run/switching_states", "/run/instrument/detector/number", "/run/instrument/beam/frames_good", "/run/histogram_data_1/counts", "/run/histogram_data_1/resolution", "/run/histogram_data_1/grouping" }; std::vector float_datasets = { "/run/sample/temperature", "/run/sample/magnetic_field", "/run/sample/magnetic_field_vector", "/run/instrument/detector/deadtimes", "/run/histogram_data_1/time_zero", "/run/histogram_data_1/raw_time", "/run/histogram_data_1/corrected_time", "/run/histogram_data_1/alpha" }; std::vector string_datasets = { "/run/program_name", "/run/title", "/run/notes", "/run/analysis", "/run/lab", "/run/beamline", "/run/start_time", "/run/stop_time", "/run/user/name", "/run/user/experiment_number", "/run/sample/name", "/run/sample/magnetic_field_state", "/run/sample/environment", "/run/instrument/name", "/run/instrument/collimator/type" }; // Read integer datasets for (const auto& path : int_datasets) { try { ReadIntDataset(sd_id, path); } catch (const std::exception& e) { if (fPrintDebug) { std::cout << "Note: Could not read " << path << ": " << e.what() << std::endl; } } } // Read float datasets for (const auto& path : float_datasets) { try { ReadFloatDataset(sd_id, path); } catch (const std::exception& e) { if (fPrintDebug) { std::cout << "Note: Could not read " << path << ": " << e.what() << std::endl; } } } // Read string datasets for (const auto& path : string_datasets) { try { ReadStringDataset(sd_id, path); } catch (const std::exception& e) { if (fPrintDebug) { std::cout << "Note: Could not read " << path << ": " << e.what() << std::endl; } } } } //============================================================================= // nxH4::PNeXus::HandleIdfV2 //============================================================================= void nxH4::PNeXus::HandleIdfV2(int32 sd_id) { // Read common datasets for IDF version 2 // These are typical datasets found in muon NeXus files std::vector int_datasets = { "/raw_data_1/instrument/detector_1/counts", "/raw_data_1/instrument/detector_1/spectrum_index", "/raw_data_1/good_frames", "/raw_data_1/instrument/detector_1/raw_time", "/raw_data_1/run_number" }; std::vector float_datasets = { "/raw_data_1/instrument/detector_1/resolution", "/raw_data_1/instrument/detector_1/time_zero" }; std::vector string_datasets = { "/raw_data_1/name", "/raw_data_1/title", "/raw_data_1/start_time", "/raw_data_1/end_time" }; // Read integer datasets for (const auto& path : int_datasets) { try { ReadIntDataset(sd_id, path); } catch (const std::exception& e) { if (fPrintDebug) { std::cout << "Note: Could not read " << path << ": " << e.what() << std::endl; } } } // Read float datasets for (const auto& path : float_datasets) { try { ReadFloatDataset(sd_id, path); } catch (const std::exception& e) { if (fPrintDebug) { std::cout << "Note: Could not read " << path << ": " << e.what() << std::endl; } } } // Read string datasets for (const auto& path : string_datasets) { try { ReadStringDataset(sd_id, path); } catch (const std::exception& e) { if (fPrintDebug) { std::cout << "Note: Could not read " << path << ": " << e.what() << std::endl; } } } } //============================================================================= // nxH4::PNeXus::ReadIntDataset //============================================================================= void nxH4::PNeXus::ReadIntDataset(int32 sd_id, const std::string& path) { // Extract dataset name from path std::vector components = splitPath(path); if (components.empty()) { throw std::runtime_error("Invalid path: " + path); } std::string dataset_name = components.back(); // Use VGroup navigation to find the correct dataset reference int32 sds_ref = -1; if (components.size() > 1) { // Try to resolve via VGroup hierarchy sds_ref = findDatasetRefByPath(path); } int32 sds_idx = -1; if (sds_ref != -1) { // Find SDS index from reference sds_idx = SDreftoindex(sd_id, sds_ref); } // Fallback to name-based search if VGroup navigation failed if (sds_idx == FAIL || sds_idx == -1) { sds_idx = findDatasetIndex(sd_id, dataset_name); } int32 sds_id = SDselect(sd_id, sds_idx); if (sds_id == FAIL) { throw std::runtime_error("Failed to select dataset: " + dataset_name); } // Get dataset info int32 rank, dim_sizes[H4_MAX_VAR_DIMS], data_type, n_attrs; char name[H4_MAX_NC_NAME]; if (SDgetinfo(sds_id, name, &rank, dim_sizes, &data_type, &n_attrs) == FAIL) { SDendaccess(sds_id); throw std::runtime_error("Failed to get dataset info: " + dataset_name); } // Calculate total number of elements int32 n_elements = 1; std::vector dimensions; for (int32 i = 0; i < rank; i++) { n_elements *= dim_sizes[i]; dimensions.push_back(static_cast(dim_sizes[i])); } // Read data std::vector data(n_elements); int32 start[H4_MAX_VAR_DIMS] = {0}; if (SDreaddata(sds_id, start, nullptr, dim_sizes, data.data()) == FAIL) { SDendaccess(sds_id); throw std::runtime_error("Failed to read dataset: " + dataset_name); } // Convert to int vector std::vector int_data(data.begin(), data.end()); // Create PNXdata object PNXdata pnx_data(H4DataType::INT32); pnx_data.SetData(int_data); pnx_data.SetDimensions(dimensions); // Read attributes ReadDatasetAttributes(sds_id, pnx_data); // Store in data map fDataMap[path] = pnx_data; SDendaccess(sds_id); } //============================================================================= // nxH4::PNeXus::ReadFloatDataset //============================================================================= void nxH4::PNeXus::ReadFloatDataset(int32 sd_id, const std::string& path) { // Extract dataset name from path std::vector components = splitPath(path); if (components.empty()) { throw std::runtime_error("Invalid path: " + path); } std::string dataset_name = components.back(); // Use VGroup navigation to find the correct dataset reference int32 sds_ref = -1; if (components.size() > 1) { // Try to resolve via VGroup hierarchy sds_ref = findDatasetRefByPath(path); } int32 sds_idx = -1; if (sds_ref != -1) { // Find SDS index from reference sds_idx = SDreftoindex(sd_id, sds_ref); } // Fallback to name-based search if VGroup navigation failed if (sds_idx == FAIL || sds_idx == -1) { sds_idx = findDatasetIndex(sd_id, dataset_name); } int32 sds_id = SDselect(sd_id, sds_idx); if (sds_id == FAIL) { throw std::runtime_error("Failed to select dataset: " + dataset_name); } // Get dataset info int32 rank, dim_sizes[H4_MAX_VAR_DIMS], data_type, n_attrs; char name[H4_MAX_NC_NAME]; if (SDgetinfo(sds_id, name, &rank, dim_sizes, &data_type, &n_attrs) == FAIL) { SDendaccess(sds_id); throw std::runtime_error("Failed to get dataset info: " + dataset_name); } // Calculate total number of elements int32 n_elements = 1; std::vector dimensions; for (int32 i = 0; i < rank; i++) { n_elements *= dim_sizes[i]; dimensions.push_back(static_cast(dim_sizes[i])); } // Read data std::vector data(n_elements); int32 start[H4_MAX_VAR_DIMS] = {0}; if (SDreaddata(sds_id, start, nullptr, dim_sizes, data.data()) == FAIL) { SDendaccess(sds_id); throw std::runtime_error("Failed to read dataset: " + dataset_name); } // Convert to float vector std::vector float_data(data.begin(), data.end()); // Create PNXdata object PNXdata pnx_data(H4DataType::FLOAT32); pnx_data.SetData(float_data); pnx_data.SetDimensions(dimensions); // Read attributes ReadDatasetAttributes(sds_id, pnx_data); // Store in data map fDataMap[path] = pnx_data; SDendaccess(sds_id); } //============================================================================= // nxH4::PNeXus::ReadStringDataset //============================================================================= void nxH4::PNeXus::ReadStringDataset(int32 sd_id, const std::string& path) { // Extract dataset name from path std::vector components = splitPath(path); if (components.empty()) { throw std::runtime_error("Invalid path: " + path); } std::string dataset_name = components.back(); // Use VGroup navigation to find the correct dataset reference int32 sds_ref = -1; if (components.size() > 1) { // Try to resolve via VGroup hierarchy sds_ref = findDatasetRefByPath(path); } int32 sds_idx = -1; if (sds_ref != -1) { // Find SDS index from reference sds_idx = SDreftoindex(sd_id, sds_ref); } // Fallback to name-based search if VGroup navigation failed if (sds_idx == FAIL || sds_idx == -1) { sds_idx = findDatasetIndex(sd_id, dataset_name); } int32 sds_id = SDselect(sd_id, sds_idx); if (sds_id == FAIL) { throw std::runtime_error("Failed to select dataset: " + dataset_name); } // Get dataset info int32 rank, dim_sizes[H4_MAX_VAR_DIMS], data_type, n_attrs; char name[H4_MAX_NC_NAME]; if (SDgetinfo(sds_id, name, &rank, dim_sizes, &data_type, &n_attrs) == FAIL) { SDendaccess(sds_id); throw std::runtime_error("Failed to get dataset info: " + dataset_name); } // Calculate total size for string int32 n_elements = 1; std::vector dimensions; for (int32 i = 0; i < rank; i++) { n_elements *= dim_sizes[i]; dimensions.push_back(static_cast(dim_sizes[i])); } // Read data as char array std::vector data(n_elements + 1, '\0'); int32 start[H4_MAX_VAR_DIMS] = {0}; if (SDreaddata(sds_id, start, nullptr, dim_sizes, data.data()) == FAIL) { SDendaccess(sds_id); throw std::runtime_error("Failed to read dataset: " + dataset_name); } // Create string std::vector string_data; string_data.push_back(std::string(data.data())); // Create PNXdata object PNXdata pnx_data(H4DataType::CHAR8); pnx_data.SetData(string_data); pnx_data.SetDimensions(dimensions); // Read attributes ReadDatasetAttributes(sds_id, pnx_data); // Store in data map fDataMap[path] = pnx_data; SDendaccess(sds_id); } //============================================================================= // nxH4::PNeXus::ReadDatasetAttributes (template specializations) //============================================================================= template void nxH4::PNeXus::ReadDatasetAttributes(int32 sds_id, PNXdata& data) { int32 rank, dim_sizes[H4_MAX_VAR_DIMS], data_type, n_attrs; char name[H4_MAX_NC_NAME]; if (SDgetinfo(sds_id, name, &rank, dim_sizes, &data_type, &n_attrs) == FAIL) { return; } for (int32 i = 0; i < n_attrs; i++) { char attr_name[H4_MAX_NC_NAME]; int32 attr_type, attr_count; if (SDattrinfo(sds_id, i, attr_name, &attr_type, &attr_count) == FAIL) { continue; } // Read attribute based on type - store as primitives like h5nexus if (attr_type == DFNT_INT32) { if (attr_count == 1) { // Single value - store as int directly int32 value; if (SDreadattr(sds_id, i, &value) != FAIL) { data.AddAttribute(attr_name, static_cast(value)); } } else { // Multiple values - store as vector std::vector attr_data(attr_count); if (SDreadattr(sds_id, i, attr_data.data()) != FAIL) { std::vector int_data(attr_data.begin(), attr_data.end()); data.AddAttribute(attr_name, int_data); } } } else if (attr_type == DFNT_FLOAT32) { if (attr_count == 1) { // Single value - store as float directly float32 value; if (SDreadattr(sds_id, i, &value) != FAIL) { data.AddAttribute(attr_name, static_cast(value)); } } else { // Multiple values - store as vector std::vector attr_data(attr_count); if (SDreadattr(sds_id, i, attr_data.data()) != FAIL) { std::vector float_data(attr_data.begin(), attr_data.end()); data.AddAttribute(attr_name, float_data); } } } else if (attr_type == DFNT_CHAR8 || attr_type == DFNT_UCHAR8) { // String attributes - always store as string std::vector attr_data(attr_count + 1, '\0'); if (SDreadattr(sds_id, i, attr_data.data()) != FAIL) { data.AddAttribute(attr_name, std::string(attr_data.data())); } } } } //============================================================================= // nxH4::PNeXus::Dump //============================================================================= void nxH4::PNeXus::Dump() { int first_good_bin{0}; if (fIdfVersion == 1) { std::cout << std::endl; std::cout << "========================================" << std::endl; std::cout << "NeXus File Dump" << std::endl; std::cout << "========================================" << std::endl; std::cout << "Filename: " << fFileName << std::endl; std::cout << "HDF4 Version: " << fHdf4Version << std::endl; std::cout << "NeXus Version: " << fNeXusVersion << std::endl; std::cout << "IDF Version: " << fIdfVersion << std::endl; std::cout << "----------------------------------------" << std::endl; std::cout << std::endl << "++++"; std::cout << std::endl << "run"; std::cout << std::endl << "----"; std::cout << std::endl << " IDF_version: " << fIdfVersion; if (fDataMap.find("/run/program_name") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/program_name"]); std::cout << std::endl << " program_name : " << str_data.GetData()[0]; // Check for attributes if (str_data.HasAttribute("version")) { try { auto version = std::any_cast(str_data.GetAttribute("version")); std::cout << " version : " << version; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast version attribute" << std::endl; } } } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast program_name data" << std::endl; } } else { std::cout << std::endl << " program_name : n/a"; } if (fDataMap.find("/run/number") != fDataMap.end()) { try { auto number = std::any_cast>(fDataMap["/run/number"]); std::cout << std::endl << " number : " << number.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast number data" << std::endl; } } else { std::cout << std::endl << " number : n/a"; } if (fDataMap.find("/run/title") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/title"]); std::cout << std::endl << " title : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast title data" << std::endl; } } else { std::cout << std::endl << " title : n/a"; } if (fDataMap.find("/run/notes") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/notes"]); std::cout << std::endl << " notes : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast notes data" << std::endl; } } else { std::cout << std::endl << " notes : n/a"; } if (fDataMap.find("/run/analysis") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/analysis"]); std::cout << std::endl << " analysis : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast analysis data" << std::endl; } } else { std::cout << std::endl << " analysis : n/a"; } if (fDataMap.find("/run/lab") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/lab"]); std::cout << std::endl << " lab : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast lab data" << std::endl; } } else { std::cout << std::endl << " lab : n/a"; } if (fDataMap.find("/run/beamline") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/beamline"]); std::cout << std::endl << " beamline : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast beamline data" << std::endl; } } else { std::cout << std::endl << " beamline : n/a"; } if (fDataMap.find("/run/start_time") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/start_time"]); std::cout << std::endl << " start_time : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast start_time data" << std::endl; } } else { std::cout << std::endl << " start_time : n/a"; } if (fDataMap.find("/run/stop_time") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/stop_time"]); std::cout << std::endl << " stop_time : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast stop_time data" << std::endl; } } else { std::cout << std::endl << " stop_time : n/a"; } if (fDataMap.find("/run/switching_state") != fDataMap.end()) { try { auto int_data = std::any_cast>(fDataMap["/run/switching_state"]); std::cout << std::endl << " switching_state : " << int_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast switching_state data" << std::endl; } } else { std::cout << std::endl << " switching_state : n/a"; } std::cout << std::endl << "----"; std::cout << std::endl << " user"; std::cout << std::endl << "----"; if (fDataMap.find("/run/user/name") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/user/name"]); std::cout << std::endl << " name : '" << str_data.GetData()[0] << "'"; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast name data" << std::endl; } } else { std::cout << std::endl << " name : n/a"; } if (fDataMap.find("/run/user/experiment_number") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/user/experiment_number"]); std::cout << std::endl << " experiment_number : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast experiment_number data" << std::endl; } } else { std::cout << std::endl << " experiment_number : n/a"; } std::cout << std::endl << "----"; std::cout << std::endl << " sample"; std::cout << std::endl << "----"; if (fDataMap.find("/run/sample/name") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/sample/name"]); std::cout << std::endl << " name : '" << str_data.GetData()[0] << "'"; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast name data" << std::endl; } } else { std::cout << std::endl << " name : n/a"; } if (fDataMap.find("/run/sample/temperature") != fDataMap.end()) { try { auto float_data = std::any_cast>(fDataMap["/run/sample/temperature"]); std::cout << std::endl << " temperature : " << float_data.GetData()[0]; // Check for attributes if (float_data.HasAttribute("units")) { try { auto units = std::any_cast(float_data.GetAttribute("units")); std::cout << " units : " << units; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast units attribute" << std::endl; } } else { std::cout << " units : n/a"; } } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast temperature data" << std::endl; } } else { std::cout << std::endl << " temperature : n/a"; } if (fDataMap.find("/run/sample/magnetic_field") != fDataMap.end()) { try { auto float_data = std::any_cast>(fDataMap["/run/sample/magnetic_field"]); std::cout << std::endl << " magnetic_field : " << float_data.GetData()[0]; // Check for attributes if (float_data.HasAttribute("units")) { try { auto units = std::any_cast(float_data.GetAttribute("units")); std::cout << " units : " << units; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast units attribute" << std::endl; } } else { std::cout << " units : n/a"; } } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast magnetic_field data" << std::endl; } } else { std::cout << std::endl << " magnetic_field : n/a"; } if (fDataMap.find("/run/sample/magnetic_field_vector") != fDataMap.end()) { try { auto float_data = std::any_cast>(fDataMap["/run/sample/magnetic_field_vector"]); std::cout << std::endl << " magnetic_field_vector : " << float_data.GetData()[0]; // Check for attributes if (float_data.HasAttribute("coordinate_system")) { try { auto coordinate_system = std::any_cast(float_data.GetAttribute("coordinate_system")); std::cout << std::endl << " coordinate_system : " << coordinate_system << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast coordinate_system attribute" << std::endl; } } else { std::cout << std::endl << " coordinate_system : n/a" << std::endl; } if (float_data.HasAttribute("units")) { try { auto units = std::any_cast(float_data.GetAttribute("units")); std::cout << " units : " << units << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast units attribute" << std::endl; } } else { std::cout << " units : n/a" << std::endl; } if (float_data.HasAttribute("available")) { try { auto available = std::any_cast(float_data.GetAttribute("available")); std::cout << " available : " << available; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast available attribute" << std::endl; } } else { std::cout << " available : n/a"; } } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast magnetic_field_vector data" << std::endl; } } else { std::cout << std::endl << " magnetic_field_vector : n/a"; } if (fDataMap.find("/run/sample/environment") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/sample/environment"]); std::cout << std::endl << " environment : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast environment data" << std::endl; } } else { std::cout << std::endl << " environment : n/a"; } std::cout << std::endl << "----"; std::cout << std::endl << " instrument"; std::cout << std::endl << "----"; if (fDataMap.find("/run/instrument/name") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/instrument/name"]); std::cout << std::endl << " name : '" << str_data.GetData()[0] << "'"; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast name data" << std::endl; } } else { std::cout << std::endl << " name : n/a"; } std::cout << std::endl << "----"; std::cout << std::endl << " detector"; std::cout << std::endl << "----"; if (fDataMap.find("/run/instrument/detector/number") != fDataMap.end()) { try { auto int_data = std::any_cast>(fDataMap["/run/instrument/detector/number"]); std::cout << std::endl << " number : " << int_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast number data" << std::endl; } } else { std::cout << std::endl << " number : n/a"; } if (fDataMap.find("/run/instrument/detector/deadtimes") != fDataMap.end()) { try { auto float_data = std::any_cast>(fDataMap["/run/instrument/detector/deadtimes"]); auto data = float_data.GetData(); unsigned int end{15}; if (data.size() < end) end = data.size(); std::cout << std::endl << " deadtimes: "; for (unsigned int i=0; i>(fDataMap["/run/instrument/beam/frames_good"]); std::cout << std::endl << " good_frames : " << fg.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast good_frames data" << std::endl; } } else { std::cout << std::endl << " good_frames : n/a"; } std::cout << std::endl << "----"; std::cout << std::endl << " collimator"; std::cout << std::endl << "----"; if (fDataMap.find("/run/instrument/collimator/type") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/instrument/collimator/type"]); std::cout << std::endl << " type : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast type data" << std::endl; } } else { std::cout << std::endl << " type : n/a"; } std::cout << std::endl << "----"; std::cout << std::endl << " histogram_data_1"; std::cout << std::endl << "----"; std::cout << std::endl << " counts:"; std::cout << std::endl; try { auto counts_data = std::any_cast>(fDataMap["/run/histogram_data_1/counts"]); // Check for attributes if (counts_data.HasAttribute("units")) { try { auto units = std::any_cast(counts_data.GetAttribute("units")); std::cout << " units : " << units << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast units attribute" << std::endl; } } else { std::cout << " units : n/a" << std::endl; } if (counts_data.HasAttribute("signal")) { try { auto signal = std::any_cast(counts_data.GetAttribute("signal")); std::cout << " signal : " << signal << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast signal attribute" << std::endl; } } else { std::cout << " signal : n/a" << std::endl; } int noOfHistos{0}; if (counts_data.HasAttribute("number")) { try { noOfHistos = std::any_cast(counts_data.GetAttribute("number")); std::cout << " number : " << noOfHistos << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast number attribute" << std::endl; } } else { std::cout << " number : n/a" << std::endl; } int histoLength{0}; if (counts_data.HasAttribute("length")) { try { histoLength = std::any_cast(counts_data.GetAttribute("length")); std::cout << " length : " << histoLength << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast length attribute" << std::endl; } } else { std::cout << " length : n/a" << std::endl; } if (counts_data.HasAttribute("t0_bin")) { try { auto t0_bin = std::any_cast(counts_data.GetAttribute("t0_bin")); std::cout << " t0_bin : " << t0_bin << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast t0_bin attribute" << std::endl; } } else { std::cout << " t0_bin : n/a" << std::endl; } if (counts_data.HasAttribute("first_good_bin")) { try { first_good_bin = std::any_cast(counts_data.GetAttribute("first_good_bin")); std::cout << " first_good_bin : " << first_good_bin << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast first_good_bin attribute" << std::endl; } } else { std::cout << " first_good_bin : n/a" << std::endl; } if (counts_data.HasAttribute("last_good_bin")) { try { auto last_good_bin = std::any_cast(counts_data.GetAttribute("last_good_bin")); std::cout << " last_good_bin : " << last_good_bin << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast last_good_bin attribute" << std::endl; } } else { std::cout << " last_good_bin : n/a" << std::endl; } if (counts_data.HasAttribute("offset")) { try { auto offset = std::any_cast(counts_data.GetAttribute("offset")); std::cout << " offset : " << offset << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast offset attribute" << std::endl; } } else { std::cout << " offset : n/a" << std::endl; } // dump the first couple of counts of each detector const auto& data = counts_data.GetData(); std::cout << std::endl << " first couple of counts of each detector:"; for (unsigned int i=0; i>(fDataMap["/run/histogram_data_1/resolution"]); std::cout << std::endl << " resolution : " << int_data.GetData()[0]; if (int_data.HasAttribute("units")) { try { auto units = std::any_cast(int_data.GetAttribute("units")); std::cout << " units : " << units; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast units attribute" << std::endl; } } else { std::cout << std::endl << " units : n/a"; } } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast resolution data" << std::endl; } } else { std::cout << std::endl << " resolution : n/a"; } if (fDataMap.find("/run/histogram_data_1/time_zero") != fDataMap.end()) { try { auto float_data = std::any_cast>(fDataMap["/run/histogram_data_1/time_zero"]); std::cout << std::endl << " time_zero : " << float_data.GetData()[0]; if (float_data.HasAttribute("units")) { try { auto units = std::any_cast(float_data.GetAttribute("units")); std::cout << std::endl << " units : " << units; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast units attribute" << std::endl; } } else { std::cout << std::endl << " units : n/a"; } if (float_data.HasAttribute("available")) { try { auto available = std::any_cast(float_data.GetAttribute("available")); std::cout << std::endl << " available : " << available; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast available attribute"; } } else { std::cout << std::endl << " available : n/a"; } } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast time_zero data" << std::endl; } } else { std::cout << std::endl << " time_zero : n/a"; } if (fDataMap.find("/run/histogram_data_1/raw_time") != fDataMap.end()) { try { auto float_data = std::any_cast>(fDataMap["/run/histogram_data_1/raw_time"]); std::cout << std::endl << " raw_time : " << float_data.GetData()[0]; if (float_data.HasAttribute("axis")) { try { auto axis = std::any_cast(float_data.GetAttribute("axis")); std::cout << std::endl << " axis : " << axis; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast axis attribute" << std::endl; } } else { std::cout << std::endl << " axis : n/a"; } if (float_data.HasAttribute("primary")) { try { auto primary = std::any_cast(float_data.GetAttribute("primary")); std::cout << std::endl << " primary : " << primary; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast primary attribute" << std::endl; } } else { std::cout << std::endl << " primary : n/a"; } if (float_data.HasAttribute("units")) { try { auto units = std::any_cast(float_data.GetAttribute("units")); std::cout << std::endl << " units : " << units; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast units attribute" << std::endl; } } else { std::cout << std::endl << " units : n/a"; } } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast raw_time data" << std::endl; } } else { std::cout << std::endl << " raw_time : n/a"; } if (fDataMap.find("/run/histogram_data_1/corrected_time") != fDataMap.end()) { try { auto float_data = std::any_cast>(fDataMap["/run/histogram_data_1/corrected_time"]); std::cout << std::endl << " corrected_time : " << float_data.GetData()[0]; if (float_data.HasAttribute("axis")) { try { auto axis = std::any_cast(float_data.GetAttribute("axis")); std::cout << std::endl << " axis : " << axis; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast axis attribute" << std::endl; } } else { std::cout << std::endl << " axis : n/a"; } if (float_data.HasAttribute("units")) { try { auto units = std::any_cast(float_data.GetAttribute("units")); std::cout << std::endl << " units : " << units; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast units attribute" << std::endl; } } else { std::cout << std::endl << " units : n/a"; } } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast raw_time data" << std::endl; } } else { std::cout << std::endl << " corrected_time : n/a"; } if (fDataMap.find("/run/histogram_data_1/grouping") != fDataMap.end()) { try { auto int_data = std::any_cast>(fDataMap["/run/histogram_data_1/grouping"]); auto data = int_data.GetData(); unsigned int end{15}; if (data.size() < end) end = data.size(); std::cout << std::endl << " grouping : "; for (unsigned int i=0; i(int_data.GetAttribute("available")); std::cout << std::endl << " available : " << available; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast available attribute" << std::endl; } } else { std::cout << std::endl << " available : n/a"; } } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast grouping data" << std::endl; } } else { std::cout << std::endl << " grouping : n/a"; } if (fDataMap.find("/run/histogram_data_1/alpha") != fDataMap.end()) { try { auto float_data = std::any_cast>(fDataMap["/run/histogram_data_1/alpha"]); auto data = float_data.GetData(); unsigned int end{15}; if (data.size() < end) end = data.size(); std::cout << std::endl << " alpha : "; for (unsigned int i=0; i(float_data.GetAttribute("available")); std::cout << std::endl << " available : " << available; } catch (const std::bad_any_cast& e) { std::cerr << "**ERROR**: Failed to cast available attribute" << std::endl; } } else { std::cout << std::endl << " alpha : n/a"; } } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "**ERROR**: Failed to cast alpha data" << std::endl; } } else { std::cout << std::endl << " available : n/a"; } std::cout << std::endl; std::cout << "========================================" << std::endl; std::cout << std::endl; } else { // IDF Version 2 std::cout << std::endl; std::cout << std::endl << "hdf5-NeXus file content of file:' " << fFileName << "'"; std::cout << std::endl << "****"; std::cout << std::endl << "Top Level Attributes:"; std::cout << std::endl << " HDF4 Version : " << fHdf4Version; std::cout << std::endl << " NeXus Version: " << fNeXusVersion; std::cout << std::endl << " file_name : " << fFileNameNxs; std::cout << std::endl << " file_time : " << fFileTimeNxs; std::cout << std::endl << "++++"; std::cout << std::endl << "raw_data_1"; std::cout << std::endl << "----"; std::cout << std::endl << " IDF_version: " << fIdfVersion; if (fDataMap.find("/raw_data_1/beamline") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/beamline"]); std::cout << std::endl << " beamline : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast beamline data" << std::endl; } } else { std::cout << std::endl << " beamline : n/a"; } if (fDataMap.find("/raw_data_1/definition") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/definition"]); std::cout << std::endl << " definition : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast definition data" << std::endl; } } else { std::cout << std::endl << " definition : n/a"; } if (fDataMap.find("/raw_data_1/run_number") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/run_number"]); std::cout << std::endl << " run_number : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast run_number data" << std::endl; } } else { std::cout << std::endl << " run_number : n/a"; } if (fDataMap.find("/raw_data_1/title") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/title"]); std::cout << std::endl << " title : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast title data" << std::endl; } } else { std::cout << std::endl << " title : n/a"; } if (fDataMap.find("/raw_data_1/start_time") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/start_time"]); std::cout << std::endl << " start_time : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast start_time data" << std::endl; } } else { std::cout << std::endl << " start_time : n/a"; } if (fDataMap.find("/raw_data_1/end_time") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/end_time"]); std::cout << std::endl << " end_time : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast end_time data" << std::endl; } } else { std::cout << std::endl << " end_time : n/a"; } if (fDataMap.find("/raw_data_1/good_frames") != fDataMap.end()) { try { auto good_frames = std::any_cast>(fDataMap["/raw_data_1/good_frames"]); std::cout << std::endl << " good_frames: " << good_frames.GetData()[0]; } catch(const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast good_frames data" << std::endl; } } else { std::cout << std::endl << " good_frames: n/a"; } if (fDataMap.find("/raw_data_1/experiment_identifier") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/experiment_identifier"]); std::cout << std::endl << " experiment_identifier: " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast experiment_identifier data" << std::endl; } } else { std::cout << std::endl << " experiment_identifier: n/a"; } std::cout << std::endl << "----"; std::cout << std::endl << " instrument"; if (fDataMap.find("/raw_data_1/instrument/name") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/instrument/name"]); std::cout << std::endl << " name : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast instrument/name data" << std::endl; } } else { std::cout << std::endl << " name : n/a"; } std::cout << std::endl << "----"; std::cout << std::endl << " source"; if (fDataMap.find("/raw_data_1/instrument/source/name") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/instrument/source/name"]); std::cout << std::endl << " name : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast instrument/source/name data" << std::endl; } } else { std::cout << std::endl << " name : n/a"; } if (fDataMap.find("/raw_data_1/instrument/source/type") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/instrument/source/type"]); std::cout << std::endl << " type : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast instrument/source/type data" << std::endl; } } else { std::cout << std::endl << " type : n/a"; } if (fDataMap.find("/raw_data_1/instrument/source/probe") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/instrument/source/probe"]); std::cout << std::endl << " probe : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast instrument/source/probe data" << std::endl; } } else { std::cout << std::endl << " probe : n/a"; } std::cout << std::endl << "----"; std::cout << std::endl << " detector_1"; std::cout << std::endl << " counts:"; std::cout << std::endl; try { auto counts_data = std::any_cast>(fDataMap["/raw_data_1/instrument/detector_1/counts"]); auto dims = counts_data.GetDimensions(); std::cout << " counts dimensions: " << dims[0] << " x " << dims[1] << " x " << dims[2] << std::endl; std::cout << " total elements: " << counts_data.GetNumElements() << std::endl; // Check for attributes if (counts_data.HasAttribute("signal")) { try { auto signal = std::any_cast(counts_data.GetAttribute("signal")); std::cout << " signal : " << signal << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast signal attribute" << std::endl; } } else { std::cout << std::endl << " signal : n/a"; } if (counts_data.HasAttribute("axes")) { try { auto axes = std::any_cast(counts_data.GetAttribute("axes")); std::cout << " axes : " << axes << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast axes attribute" << std::endl; } } else { std::cout << std::endl << " axes : n/a"; } if (counts_data.HasAttribute("long_name")) { try { auto long_name = std::any_cast(counts_data.GetAttribute("long_name")); std::cout << " long_name : " << long_name << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast long_name attribute" << std::endl; } } else { std::cout << std::endl << " long_name : n/a"; } if (counts_data.HasAttribute("t0_bin")) { try { auto t0_bin = std::any_cast(counts_data.GetAttribute("t0_bin")); std::cout << " t0_bin : " << t0_bin << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast t0_bin attribute" << std::endl; } } else { std::cout << std::endl << " t0_bin : n/a"; } if (counts_data.HasAttribute("first_good_bin")) { try { first_good_bin = std::any_cast(counts_data.GetAttribute("first_good_bin")); std::cout << " first_good_bin : " << first_good_bin << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast first_good_bin attribute" << std::endl; } } else { std::cout << std::endl << " first_good_bin : n/a"; } if (counts_data.HasAttribute("last_good_bin")) { try { auto last_good_bin = std::any_cast(counts_data.GetAttribute("last_good_bin")); std::cout << " last_good_bin : " << last_good_bin << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast last_good_bin attribute" << std::endl; } } else { std::cout << std::endl << " last_good_bin : n/a"; } if (fDataMap.find("/raw_data_1/instrument/detector_1/resolution") != fDataMap.end()) { try { auto ivalData = std::any_cast>(fDataMap["/raw_data_1/instrument/detector_1/resolution"]); std::cout << " resolution : " << ivalData.GetData()[0]; if (ivalData.HasAttribute("units")) { try { auto units = std::any_cast(ivalData.GetAttribute("units")); std::cout << " " << units << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast units attribute" << std::endl; } } else { std::cout << std::endl << " unit : n/a" << std::endl; } } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast resolution attribute" << std::endl; } } else { std::cout << std::endl << " resolution : n/a"; } // dump the first couple of counts of each detector const auto& data = counts_data.GetData(); std::cout << std::endl << " first couple of counts of each detector:"; for (unsigned int i=0; i>(fDataMap["/raw_data_1/instrument/detector_1/raw_time"]); const auto& data = raw_time.GetData(); // Check for attributes if (raw_time.HasAttribute("units")) { try { auto units = std::any_cast(raw_time.GetAttribute("units")); std::cout << " units : " << units << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast units attribute" << std::endl; } } else { std::cout << std::endl << " units : n/a" << std::endl; } // dump the first couple of raw_times std::cout << " "; for (unsigned int i=0; i>(fDataMap["/raw_data_1/instrument/detector_1/spectrum_index"]); const auto& data = spectrum_index.GetData(); // dump the first couple of raw_times std::cout << " "; for (unsigned int i=0; i>(fDataMap["/raw_data_1/instrument/detector_1/dead_time"]); const auto& data = dead_time.GetData(); // dump the first couple of raw_times std::cout << " "; for (unsigned int i=0; i(attr_value); SDsetattr(sd_id, attr_name.c_str(), DFNT_CHAR8, str_attr.length(), str_attr.c_str()); } catch (...) { // Try other types if needed } } } } //============================================================================= // nxH4::PNeXus::WriteIdfV2 //============================================================================= void nxH4::PNeXus::WriteIdfV2(int32 sd_id) { // Write all datasets from data map for (const auto& [path, data_any] : fDataMap) { // Try to write as int dataset try { auto data = std::any_cast>(data_any); WriteIntDataset(sd_id, path, data); continue; } catch (...) {} // Try to write as float dataset try { auto data = std::any_cast>(data_any); WriteFloatDataset(sd_id, path, data); continue; } catch (...) {} // Try to write as string dataset try { auto data = std::any_cast>(data_any); WriteStringDataset(sd_id, path, data); continue; } catch (...) {} } } //============================================================================= // nxH4::PNeXus::WriteIntDataset //============================================================================= void nxH4::PNeXus::WriteIntDataset(int32 sd_id, const std::string& path, const PNXdata& data) { // Extract dataset name from path std::vector components = splitPath(path); if (components.empty()) { throw std::runtime_error("Invalid path: " + path); } std::string dataset_name = components.back(); const auto& dims = data.GetDimensions(); int32 rank = static_cast(dims.size()); std::vector dim_sizes(rank); for (int32 i = 0; i < rank; i++) { dim_sizes[i] = static_cast(dims[i]); } // Create dataset int32 sds_id = SDcreate(sd_id, dataset_name.c_str(), DFNT_INT32, rank, dim_sizes.data()); if (sds_id == FAIL) { throw std::runtime_error("Failed to create dataset: " + dataset_name); } // Convert data to int32 const auto& int_data = data.GetData(); std::vector data32(int_data.begin(), int_data.end()); // Write data int32 start[H4_MAX_VAR_DIMS] = {0}; if (SDwritedata(sds_id, start, nullptr, dim_sizes.data(), data32.data()) == FAIL) { SDendaccess(sds_id); throw std::runtime_error("Failed to write dataset: " + dataset_name); } // Write attributes WriteDatasetAttributes(sds_id, data); SDendaccess(sds_id); } //============================================================================= // nxH4::PNeXus::WriteFloatDataset //============================================================================= void nxH4::PNeXus::WriteFloatDataset(int32 sd_id, const std::string& path, const PNXdata& data) { // Extract dataset name from path std::vector components = splitPath(path); if (components.empty()) { throw std::runtime_error("Invalid path: " + path); } std::string dataset_name = components.back(); const auto& dims = data.GetDimensions(); int32 rank = static_cast(dims.size()); std::vector dim_sizes(rank); for (int32 i = 0; i < rank; i++) { dim_sizes[i] = static_cast(dims[i]); } // Create dataset int32 sds_id = SDcreate(sd_id, dataset_name.c_str(), DFNT_FLOAT32, rank, dim_sizes.data()); if (sds_id == FAIL) { throw std::runtime_error("Failed to create dataset: " + dataset_name); } // Convert data to float32 const auto& float_data = data.GetData(); std::vector data32(float_data.begin(), float_data.end()); // Write data int32 start[H4_MAX_VAR_DIMS] = {0}; if (SDwritedata(sds_id, start, nullptr, dim_sizes.data(), data32.data()) == FAIL) { SDendaccess(sds_id); throw std::runtime_error("Failed to write dataset: " + dataset_name); } // Write attributes WriteDatasetAttributes(sds_id, data); SDendaccess(sds_id); } //============================================================================= // nxH4::PNeXus::WriteStringDataset //============================================================================= void nxH4::PNeXus::WriteStringDataset(int32 sd_id, const std::string& path, const PNXdata& data) { // Extract dataset name from path std::vector components = splitPath(path); if (components.empty()) { throw std::runtime_error("Invalid path: " + path); } std::string dataset_name = components.back(); const auto& string_data = data.GetData(); if (string_data.empty()) { return; } std::string str = string_data[0]; int32 dims[1] = {static_cast(str.length())}; // Create dataset int32 sds_id = SDcreate(sd_id, dataset_name.c_str(), DFNT_CHAR8, 1, dims); if (sds_id == FAIL) { throw std::runtime_error("Failed to create dataset: " + dataset_name); } // Write data - HDF4 requires non-const pointer std::vector str_buffer(str.begin(), str.end()); int32 start[1] = {0}; if (SDwritedata(sds_id, start, nullptr, dims, str_buffer.data()) == FAIL) { SDendaccess(sds_id); throw std::runtime_error("Failed to write dataset: " + dataset_name); } // Write attributes WriteDatasetAttributes(sds_id, data); SDendaccess(sds_id); } //============================================================================= // nxH4::PNeXus::WriteDatasetAttributes (template specializations) //============================================================================= template void nxH4::PNeXus::WriteDatasetAttributes(int32 sds_id, const PNXdata& data) { const auto& attributes = data.GetAttributes(); for (const auto& [attr_name, attr_value] : attributes) { // Try different attribute types - primitives first (like h5nexus) // Try scalar int try { int32 value = std::any_cast(attr_value); SDsetattr(sds_id, attr_name.c_str(), DFNT_INT32, 1, &value); continue; } catch (...) {} // Try scalar float try { float32 value = std::any_cast(attr_value); SDsetattr(sds_id, attr_name.c_str(), DFNT_FLOAT32, 1, &value); continue; } catch (...) {} // Try string try { std::string str = std::any_cast(attr_value); SDsetattr(sds_id, attr_name.c_str(), DFNT_CHAR8, str.length(), str.c_str()); continue; } catch (...) {} // Try vector for multi-element attributes try { std::vector int_vec = std::any_cast>(attr_value); std::vector data32(int_vec.begin(), int_vec.end()); SDsetattr(sds_id, attr_name.c_str(), DFNT_INT32, data32.size(), data32.data()); continue; } catch (...) {} // Try vector for multi-element attributes try { std::vector float_vec = std::any_cast>(attr_value); std::vector data32(float_vec.begin(), float_vec.end()); SDsetattr(sds_id, attr_name.c_str(), DFNT_FLOAT32, data32.size(), data32.data()); continue; } catch (...) {} } } //============================================================================= // Group attribute methods - manage attributes associated with HDF4 groups //============================================================================= /** * @brief Add or update an attribute for a group * * Stores an attribute value in the internal group attributes map. These * attributes will be written when WriteNexusFile() is called. The root * group "/" can be used for file-level attributes. * * @param groupPath HDF4 path of the group (e.g., "/raw_data_1") * @param attrName Attribute name * @param attrValue Attribute value (stored as std::any) * @return true if attribute was added successfully */ bool nxH4::PNeXus::AddGroupAttribute(const std::string& groupPath, const std::string& attrName, const std::any& attrValue) { fGroupAttributes[groupPath][attrName] = attrValue; return true; } bool nxH4::PNeXus::RemoveGroupAttribute(const std::string& groupPath, const std::string& attrName) { auto it = fGroupAttributes.find(groupPath); if (it != fGroupAttributes.end()) { auto attr_it = it->second.find(attrName); if (attr_it != it->second.end()) { it->second.erase(attr_it); return true; } } return false; } bool nxH4::PNeXus::HasGroupAttribute(const std::string& groupPath, const std::string& attrName) const { auto it = fGroupAttributes.find(groupPath); if (it != fGroupAttributes.end()) { return it->second.find(attrName) != it->second.end(); } return false; } std::any nxH4::PNeXus::GetGroupAttribute(const std::string& groupPath, const std::string& attrName) const { return fGroupAttributes.at(groupPath).at(attrName); } const std::map& nxH4::PNeXus::GetGroupAttributes( const std::string& groupPath) const { static std::map empty_map; auto it = fGroupAttributes.find(groupPath); if (it != fGroupAttributes.end()) { return it->second; } return empty_map; } bool nxH4::PNeXus::ClearGroupAttributes(const std::string& groupPath) { auto it = fGroupAttributes.find(groupPath); if (it != fGroupAttributes.end()) { it->second.clear(); return true; } return false; } bool nxH4::PNeXus::AddRootAttribute(const std::string& attrName, const std::any& attrValue) { return AddGroupAttribute("/", attrName, attrValue); } //============================================================================= // Helper methods //============================================================================= bool nxH4::PNeXus::caseInsensitiveEquals(const std::string& a, const std::string& b) { if (a.length() != b.length()) { return false; } return std::equal(a.begin(), a.end(), b.begin(), [](char a, char b) { return std::tolower(static_cast(a)) == std::tolower(static_cast(b)); }); } std::vector nxH4::PNeXus::splitPath(const std::string& path) { std::vector components; std::string current; for (char c : path) { if (c == '/') { if (!current.empty()) { components.push_back(current); current.clear(); } } else { current += c; } } if (!current.empty()) { components.push_back(current); } return components; } std::string nxH4::PNeXus::findAttributeName(int32 sd_id, const std::string& requestedName) { int32 n_datasets, n_attrs; if (SDfileinfo(sd_id, &n_datasets, &n_attrs) == FAIL) { throw std::runtime_error("Failed to get file info"); } char attr_name[H4_MAX_NC_NAME]; int32 attr_type, attr_count; for (int32 i = 0; i < n_attrs; i++) { if (SDattrinfo(sd_id, i, attr_name, &attr_type, &attr_count) != FAIL) { if (caseInsensitiveEquals(attr_name, requestedName)) { return std::string(attr_name); } } } throw std::runtime_error("Attribute not found: " + requestedName); } int32 nxH4::PNeXus::findDatasetIndex(int32 sd_id, const std::string& requestedName) { // First try exact match int32 idx = SDnametoindex(sd_id, requestedName.c_str()); if (idx != FAIL) { return idx; } // Try case-insensitive search int32 n_datasets, n_attrs; if (SDfileinfo(sd_id, &n_datasets, &n_attrs) == FAIL) { throw std::runtime_error("Failed to get file info"); } for (int32 i = 0; i < n_datasets; i++) { int32 sds_id = SDselect(sd_id, i); if (sds_id != FAIL) { char name[H4_MAX_NC_NAME]; int32 rank, dim_sizes[H4_MAX_VAR_DIMS], data_type, n_ds_attrs; if (SDgetinfo(sds_id, name, &rank, dim_sizes, &data_type, &n_ds_attrs) != FAIL) { if (caseInsensitiveEquals(name, requestedName)) { SDendaccess(sds_id); return i; } } SDendaccess(sds_id); } } throw std::runtime_error("Dataset not found: " + requestedName); } int32 nxH4::PNeXus::findDatasetRefByPath(const std::string& path) { // Navigate VGroup hierarchy to find the dataset reference // Path format: /group1/group2/.../dataset_name std::vector components = splitPath(path); if (components.empty()) { return -1; } // Open file with V interface if (Vstart(fFileId) == FAIL) { return -1; } int32 result_ref = -1; int32 current_vg_ref = -1; // Find root VGroup (first component after /) // For IDF version 1, root is typically "run" if (components.size() >= 1) { // Get all lone vgroups int32 n_vgroups = Vlone(fFileId, nullptr, 0); if (n_vgroups > 0) { std::vector vgroup_refs(n_vgroups); Vlone(fFileId, vgroup_refs.data(), n_vgroups); // Find the root VGroup for (int32 i = 0; i < n_vgroups; i++) { int32 vg_id = Vattach(fFileId, vgroup_refs[i], "r"); if (vg_id != FAIL) { char vg_name[VGNAMELENMAX]; if (Vgetname(vg_id, vg_name) != FAIL) { if (caseInsensitiveEquals(vg_name, components[0])) { current_vg_ref = vgroup_refs[i]; Vdetach(vg_id); break; } } Vdetach(vg_id); } } } } // Navigate through intermediate VGroups for (size_t comp_idx = 1; comp_idx < components.size() - 1; comp_idx++) { if (current_vg_ref == -1) break; int32 vg_id = Vattach(fFileId, current_vg_ref, "r"); if (vg_id == FAIL) { current_vg_ref = -1; break; } int32 n_entries = Vntagrefs(vg_id); bool found = false; for (int32 i = 0; i < n_entries; i++) { int32 tag, ref; if (Vgettagref(vg_id, i, &tag, &ref) != FAIL) { // Check if it's a VGroup if (tag == DFTAG_VG) { int32 child_vg_id = Vattach(fFileId, ref, "r"); if (child_vg_id != FAIL) { char child_name[VGNAMELENMAX]; if (Vgetname(child_vg_id, child_name) != FAIL) { if (caseInsensitiveEquals(child_name, components[comp_idx])) { current_vg_ref = ref; found = true; Vdetach(child_vg_id); break; } } Vdetach(child_vg_id); } } } } Vdetach(vg_id); if (!found) { current_vg_ref = -1; break; } } // Find the dataset in the final VGroup if (current_vg_ref != -1 && components.size() >= 1) { int32 vg_id = Vattach(fFileId, current_vg_ref, "r"); if (vg_id != FAIL) { int32 n_entries = Vntagrefs(vg_id); std::string target_name = components.back(); for (int32 i = 0; i < n_entries; i++) { int32 tag, ref; if (Vgettagref(vg_id, i, &tag, &ref) != FAIL) { // Check if it's a Numeric Data (SDS) if (tag == DFTAG_NDG) { // Try to get the name by converting ref to index int32 sds_idx = SDreftoindex(fSdId, ref); if (sds_idx != FAIL) { int32 sds_id = SDselect(fSdId, sds_idx); if (sds_id != FAIL) { char ds_name[H4_MAX_NC_NAME]; int32 rank, dim_sizes[H4_MAX_VAR_DIMS], data_type, n_attrs; if (SDgetinfo(sds_id, ds_name, &rank, dim_sizes, &data_type, &n_attrs) != FAIL) { if (caseInsensitiveEquals(ds_name, target_name)) { result_ref = ref; SDendaccess(sds_id); break; } } SDendaccess(sds_id); } } } } } Vdetach(vg_id); } } Vend(fFileId); return result_ref; } /** * @brief Convert HDF4 numeric type constant to H4DataType enum * * Maps HDF4 DFNT_* type constants to the library's H4DataType enum for * type-safe handling of dataset types. * * @param hdf4_type HDF4 numeric type constant (e.g., DFNT_INT32) * @return Corresponding H4DataType enum value * * @note Unknown types default to H4DataType::INT32 */ nxH4::H4DataType nxH4::PNeXus::convertHdf4Type(int32 hdf4_type) { switch (hdf4_type) { case DFNT_INT32: return H4DataType::INT32; case DFNT_FLOAT32: return H4DataType::FLOAT32; case DFNT_FLOAT64: return H4DataType::FLOAT64; case DFNT_CHAR8: return H4DataType::CHAR8; case DFNT_UINT32: return H4DataType::UINT32; case DFNT_INT16: return H4DataType::INT16; case DFNT_UINT16: return H4DataType::UINT16; case DFNT_INT8: return H4DataType::INT8; case DFNT_UINT8: return H4DataType::UINT8; default: return H4DataType::INT32; } } /** * @brief Convert H4DataType enum to HDF4 numeric type constant * * Maps the library's H4DataType enum to HDF4 DFNT_* type constants * for writing datasets to HDF4 files. * * @param dataType H4DataType enum value * @return Corresponding HDF4 numeric type constant (e.g., DFNT_INT32) * * @note Unknown types default to DFNT_INT32 */ int32 nxH4::PNeXus::convertToHdf4Type(H4DataType dataType) { switch (dataType) { case H4DataType::INT32: return DFNT_INT32; case H4DataType::FLOAT32: return DFNT_FLOAT32; case H4DataType::FLOAT64: return DFNT_FLOAT64; case H4DataType::CHAR8: return DFNT_CHAR8; case H4DataType::UINT32: return DFNT_UINT32; case H4DataType::INT16: return DFNT_INT16; case H4DataType::UINT16: return DFNT_UINT16; case H4DataType::INT8: return DFNT_INT8; case H4DataType::UINT8: return DFNT_UINT8; default: return DFNT_INT32; } } #endif // HAVE_HDF4 // ** HDF5 ******************************************************************** //============================================================================= // nxH5::PNeXusDeadTime Constructor //============================================================================= nxH5::PNeXusDeadTime::PNeXusDeadTime(const PNeXus *nxs, bool debug) : fDebug(debug) { std::map dataMap = nxs->GetDataMap(); int idfVersion = nxs->GetIdfVersion(); if ((idfVersion != 1) && (idfVersion != 2)) { std::stringstream err; err << "**ERROR** Found unsupported IDF version '" << idfVersion << "'"; std::cout << err.str() << std::endl; fValid = false; return; } if (idfVersion == 1) { // not yet implemented } else { // idfVersion == 2 // get counts if (dataMap.find("/raw_data_1/instrument/detector_1/counts") != dataMap.end()) { auto counts_data = std::any_cast>(dataMap["/raw_data_1/instrument/detector_1/counts"]); const auto& counts = counts_data.GetData(); fCounts = counts; auto dims = counts_data.GetDimensions(); if (fDebug) { std::cout << " counts dimensions: " << dims[0] << " x " << dims[1] << " x " << dims[2] << std::endl; } fDims = dims; // get t0 if (counts_data.HasAttribute("t0_bin")) { try { auto t0_bin = std::any_cast(counts_data.GetAttribute("t0_bin")); fT0Bin = t0_bin; if (fDebug) std::cout << " fT0Bin: " << fT0Bin << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast t0_bin attribute" << std::endl; } } // get fgb if (counts_data.HasAttribute("first_good_bin")) { try { auto first_good_bin = std::any_cast(counts_data.GetAttribute("first_good_bin")); fFgbBin = first_good_bin; if (fDebug) std::cout << " fFgbBin: " << fFgbBin << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast first_good_bin attribute" << std::endl; } } // get lgb if (counts_data.HasAttribute("last_good_bin")) { try { auto last_good_bin = std::any_cast(counts_data.GetAttribute("last_good_bin")); fLgbBin = last_good_bin; if (fDebug) std::cout << " fLgbBin: " << fLgbBin << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast last_good_bin attribute" << std::endl; } } } // get dead time parameters if (dataMap.find("/raw_data_1/instrument/detector_1/dead_time") != dataMap.end()) { auto dead_time = std::any_cast>(dataMap["/raw_data_1/instrument/detector_1/dead_time"]); const auto& dt_data = dead_time.GetData(); fDeadTime = dt_data; } fDeadTimeEstimated.resize(fDeadTime.size()); // get good_frames if (dataMap.find("/raw_data_1/good_frames") != dataMap.end()) { auto intData = std::any_cast>(dataMap["/raw_data_1/good_frames"]); const auto& ival = intData.GetData(); fGoodFrames = ival[0]; if (fDebug) std::cout << " good_frames: " << fGoodFrames << std::endl; } // get resolution with units if (dataMap.find("/raw_data_1/instrument/detector_1/resolution") != dataMap.end()) { auto int_data = std::any_cast>(dataMap["/raw_data_1/instrument/detector_1/resolution"]); const auto& ival = int_data.GetData(); float scale{1.0}; if (int_data.HasAttribute("units")) { try { auto units = std::any_cast(int_data.GetAttribute("units")); if (units == "picoseconds") scale = 1.0e-6; else if (units == "nanoseconds") scale = 1.0e-3; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast units attribute" << std::endl; } } fTimeResolution = ival[0] * scale; if (fDebug) std::cout << " time_resolution: " << fTimeResolution << " (us)" << std::endl; } } } //============================================================================= // nxH5::PNeXusDeadTime::operator() //============================================================================= /** * @brief Calculate the negative log-likelihood for dead time correction * * This function implements the objective function for ROOT Minuit2 minimization. * It calculates the negative log-likelihood comparing observed counts to * the expected count rate model with dead time correction. * * **Model:** * The expected count rate includes: * - Exponential muon decay: N(t) = N0 * exp(-t/tau_mu) + N_bkg * - Dead time correction: N_corrected = N(t) / (1 + N(t) * dtc) * * **Parameters:** * - par[0] (dtc): Dead time correction parameter * - par[1] (N0): Initial count rate amplitude * - par[2] (N_bkg): Background count rate * * @param par Vector of parameters [dtc, N0, N_bkg] * @return Negative log-likelihood value to be minimized * * @note Uses the muon lifetime tau_mu = 2.1969811 microseconds */ double nxH5::PNeXusDeadTime::operator()(const std::vector &par) const { // par[0]: dtc, par[1]: N0, par[2]: N_bkg double nt=0, nd=0, ll=0; const double tau_mu=2.1969811; for (unsigned int i=fFgbBin; i::max(); double tolerance = 0.1; ROOT::Minuit2::FunctionMinimum min = minimize(maxfcn, tolerance); std::unique_ptr fcnMin; ///< Minuit2 function minimum result // keep FunctionMinimum object fcnMin.reset(new ROOT::Minuit2::FunctionMinimum(min)); // keep user parameters if (fcnMin) params = fcnMin->UserParameters(); fDeadTimeEstimated[i] = params.Value(0); if (fDebug) std::cout << "debug> " << i << ": dtc fitted=" << params.Value(0) << ", dtc from file=" << fDeadTime[i] << std::endl; } //============================================================================= // Case-insensitive string comparison helper //============================================================================= /** * Compares two strings character-by-character using case-insensitive matching. * The function first checks if the lengths match, then converts each character * to lowercase using std::tolower before comparison. * * @param a First string to compare * @param b Second string to compare * @return true if strings are equal (ignoring case), false otherwise * * @note Uses static_cast to unsigned char for std::tolower to avoid undefined * behavior with negative char values * * @example * @code * caseInsensitiveEquals("NeXus", "nexus") // returns true * caseInsensitiveEquals("IDF_VERSION", "idf_version") // returns true * @endcode */ bool nxH5::PNeXus::caseInsensitiveEquals(const std::string& a, const std::string& b) { if (a.length() != b.length()) { return false; } for (size_t i = 0; i < a.length(); ++i) { if (std::tolower(static_cast(a[i])) != std::tolower(static_cast(b[i]))) { return false; } } return true; } //============================================================================= // Split HDF5 path into components //============================================================================= /** * Splits an HDF5 path string into individual components using '/' as delimiter. * The function preserves the information about absolute vs relative paths by * keeping an empty first component for absolute paths. * * @param path HDF5 path string to split * @return Vector of path components * * @note Empty components are filtered out except for the first component * which indicates an absolute path when empty * * @example * @code * splitPath("/raw_data_1/IDF_version") // returns ["", "raw_data_1", "IDF_version"] * splitPath("detector_1/counts") // returns ["detector_1", "counts"] * splitPath("/") // returns [""] * @endcode */ std::vector nxH5::PNeXus::splitPath(const std::string& path) { std::vector components; std::string component; std::istringstream stream(path); while (std::getline(stream, component, '/')) { // Keep empty first component to indicate absolute path if (!component.empty() || components.empty()) { components.push_back(component); } } return components; } //============================================================================= // Find attribute with case-insensitive name matching //============================================================================= /** * Searches for an attribute by name using case-insensitive matching. * Lists all attributes on the given HDF5 object and compares each name * case-insensitively against the requested name. * * @param obj HDF5 File object to search for attributes * @param requestedName Attribute name to find (any case) * @return The correctly-cased attribute name as it exists in the file * @throws H5::AttributeIException if no matching attribute is found * * @note Useful for handling NeXus files where attribute names may vary in case * (e.g., "NeXus_version" vs "nexus_version") * * @example * @code * std::string actualName = findAttributeName(file, "nexus_VERSION"); * // actualName might be "NeXus_version" if that's how it's stored * @endcode */ std::string nxH5::PNeXus::findAttributeName(H5::H5File& obj, const std::string& requestedName) { int numAttrs = obj.getNumAttrs(); for (int i = 0; i < numAttrs; i++) { H5::Attribute attr = obj.openAttribute(i); std::string attrName = attr.getName(); attr.close(); if (caseInsensitiveEquals(attrName, requestedName)) { return attrName; } } // Not found - throw exception std::ostringstream msg; msg << "Could not find attribute matching case-insensitive name: '" << requestedName << "'"; throw H5::AttributeIException("findAttributeName", msg.str()); } //============================================================================= // Find group path with case-insensitive matching (File overload) //============================================================================= /** * Finds an HDF5 group path using case-insensitive matching, starting from * the file root. Traverses the group hierarchy by splitting the path into * components and matching each component case-insensitively. * * @param parent HDF5 File object (file root) * @param requestedPath Group path to find (any case, absolute or relative) * @return The correctly-cased group path as it exists in the file * @throws H5::GroupIException if group not found or path component is not a group * * @note Each path component is verified to be a Group using getObjectType() * * @example * @code * std::string path = findGroupPath(file, "/RAW_DATA_1/detector_1"); * // path might be "/raw_data_1/detector_1" if that's the actual casing * @endcode */ std::string nxH5::PNeXus::findGroupPath(H5::H5File& parent, const std::string& requestedPath) { std::vector components = splitPath(requestedPath); // Handle empty path if (components.empty()) { return "/"; } // Check if absolute path bool isAbsolute = (components[0].empty()); std::string currentPath = isAbsolute ? "" : ""; // Start from index 1 if absolute path (skip empty component) size_t startIdx = isAbsolute ? 1 : 0; // Traverse path components for (size_t i = startIdx; i < components.size(); ++i) { const std::string& requestedComponent = components[i]; // Build the parent path for querying std::string parentPath = (currentPath.empty() || currentPath == "") ? "/" : currentPath; H5::Group currentGroup = (parentPath == "/") ? parent.openGroup("/") : parent.openGroup(parentPath); // List object names using iteration hsize_t numObjs = currentGroup.getNumObjs(); std::vector objectNames; for (hsize_t j = 0; j < numObjs; j++) { objectNames.push_back(currentGroup.getObjnameByIdx(j)); } // Find case-insensitive match std::string matchedName; bool found = false; for (const auto& objName : objectNames) { if (caseInsensitiveEquals(objName, requestedComponent)) { matchedName = objName; found = true; break; } } if (!found) { std::ostringstream msg; msg << "Could not find group component matching case-insensitive path: '" << requestedPath << "' (failed at component: '" << requestedComponent << "')"; throw H5::GroupIException("findGroupPath", msg.str()); } // Build current path currentPath += "/" + matchedName; // Verify it's a group H5O_type_t objType = currentGroup.childObjType(matchedName); if (objType != H5O_TYPE_GROUP) { std::ostringstream msg; msg << "Path component '" << matchedName << "' in '" << requestedPath << "' is not a group"; throw H5::GroupIException("findGroupPath", msg.str()); } } return currentPath; } //----------------------------------------------------------------------------- // Find group path with case-insensitive matching (Group overload) //----------------------------------------------------------------------------- std::string nxH5::PNeXus::findGroupPath(H5::Group& parent, const std::string& requestedPath) { std::vector components = splitPath(requestedPath); // Handle empty path if (components.empty()) { return "/"; } // Check if absolute path bool isAbsolute = (components[0].empty()); std::string currentPath = isAbsolute ? "" : ""; H5::Group currentGroup = parent; // Start from index 1 if absolute path (skip empty component) size_t startIdx = isAbsolute ? 1 : 0; // Traverse path components for (size_t i = startIdx; i < components.size(); ++i) { const std::string& requestedComponent = components[i]; // List object names using iteration hsize_t numObjs = currentGroup.getNumObjs(); std::vector objectNames; for (hsize_t j = 0; j < numObjs; j++) { objectNames.push_back(currentGroup.getObjnameByIdx(j)); } // Find case-insensitive match std::string matchedName; bool found = false; for (const auto& objName : objectNames) { if (caseInsensitiveEquals(objName, requestedComponent)) { matchedName = objName; found = true; break; } } if (!found) { std::ostringstream msg; msg << "Could not find group component matching case-insensitive path: '" << requestedPath << "' (failed at component: '" << requestedComponent << "')"; throw H5::GroupIException("findGroupPath", msg.str()); } // Build current path currentPath += "/" + matchedName; // Verify it's a group (unless it's the last component, which is checked by caller) H5O_type_t objType = currentGroup.childObjType(matchedName); if (objType != H5O_TYPE_GROUP) { std::ostringstream msg; msg << "Path component '" << matchedName << "' in '" << requestedPath << "' is not a group"; throw H5::GroupIException("findGroupPath", msg.str()); } // Open the group for next iteration if (i < components.size() - 1) { currentGroup = currentGroup.openGroup(matchedName); } } return currentPath; } //============================================================================= // Find dataset path with case-insensitive matching (File overload) //============================================================================= /** * Finds an HDF5 dataset path using case-insensitive matching, starting from * the file root. Traverses the group hierarchy and verifies that the final * path component is actually a dataset. * * @param parent HDF5 File object (file root) * @param requestedPath Dataset path to find (any case, absolute or relative) * @return The correctly-cased dataset path as it exists in the file * @throws H5::DataSetIException if dataset not found, path invalid, or * final component is not a dataset * * @note Intermediate path components must be groups, and the final component * must be a dataset, otherwise an exception is thrown * * @example * @code * std::string path = findDatasetPath(file, "/RAW_DATA_1/idf_VERSION"); * // path might be "/raw_data_1/IDF_version" if that's the actual casing * std::int data = H5Easy::load(file, path); * @endcode */ std::string nxH5::PNeXus::findDatasetPath(H5::H5File& parent, const std::string& requestedPath) { std::vector components = splitPath(requestedPath); // Handle empty path if (components.empty()) { throw H5::DataSetIException("findDatasetPath", "Empty dataset path provided"); } // Check if absolute path bool isAbsolute = (components[0].empty()); std::string currentPath = isAbsolute ? "" : ""; // Start from index 1 if absolute path (skip empty component) size_t startIdx = isAbsolute ? 1 : 0; // Traverse path components for (size_t i = startIdx; i < components.size(); ++i) { const std::string& requestedComponent = components[i]; // Build the parent path for querying std::string parentPath = (currentPath.empty() || currentPath == "") ? "/" : currentPath; H5::Group currentGroup = (parentPath == "/") ? parent.openGroup("/") : parent.openGroup(parentPath); // List object names using iteration hsize_t numObjs = currentGroup.getNumObjs(); std::vector objectNames; for (hsize_t j = 0; j < numObjs; j++) { objectNames.push_back(currentGroup.getObjnameByIdx(j)); } // Find case-insensitive match std::string matchedName; bool found = false; for (const auto& objName : objectNames) { if (caseInsensitiveEquals(objName, requestedComponent)) { matchedName = objName; found = true; break; } } if (!found) { std::ostringstream msg; msg << "Could not find dataset matching case-insensitive path: '" << requestedPath << "' (failed at component: '" << requestedComponent << "')"; throw H5::DataSetIException("findDatasetPath", msg.str()); } // Build current path currentPath += "/" + matchedName; // Check if this is the last component (should be a dataset) if (i == components.size() - 1) { H5O_type_t objType = currentGroup.childObjType(matchedName); if (objType != H5O_TYPE_DATASET) { std::ostringstream msg; msg << "Path '" << requestedPath << "' resolves to '" << currentPath << "' which is not a dataset"; throw H5::DataSetIException("findDatasetPath", msg.str()); } } else { // Intermediate component - should be a group H5O_type_t objType = currentGroup.childObjType(matchedName); if (objType != H5O_TYPE_GROUP) { std::ostringstream msg; msg << "Path component '" << matchedName << "' in '" << requestedPath << "' is not a group"; throw H5::DataSetIException("findDatasetPath", msg.str()); } } } return currentPath; } //----------------------------------------------------------------------------- // Find dataset path with case-insensitive matching (Group overload) //----------------------------------------------------------------------------- std::string nxH5::PNeXus::findDatasetPath(H5::Group& parent, const std::string& requestedPath) { std::vector components = splitPath(requestedPath); // Handle empty path if (components.empty()) { throw H5::DataSetIException("findDatasetPath", "Empty dataset path provided"); } // Check if absolute path bool isAbsolute = (components[0].empty()); std::string currentPath = isAbsolute ? "" : ""; H5::Group currentGroup = parent; // Start from index 1 if absolute path (skip empty component) size_t startIdx = isAbsolute ? 1 : 0; // Traverse path components for (size_t i = startIdx; i < components.size(); ++i) { const std::string& requestedComponent = components[i]; // List object names using iteration hsize_t numObjs = currentGroup.getNumObjs(); std::vector objectNames; for (hsize_t j = 0; j < numObjs; j++) { objectNames.push_back(currentGroup.getObjnameByIdx(j)); } // Find case-insensitive match std::string matchedName; bool found = false; for (const auto& objName : objectNames) { if (caseInsensitiveEquals(objName, requestedComponent)) { matchedName = objName; found = true; break; } } if (!found) { std::ostringstream msg; msg << "Could not find dataset matching case-insensitive path: '" << requestedPath << "' (failed at component: '" << requestedComponent << "')"; throw H5::DataSetIException("findDatasetPath", msg.str()); } // Build current path currentPath += "/" + matchedName; // Check if this is the last component (should be a dataset) if (i == components.size() - 1) { H5O_type_t objType = currentGroup.childObjType(matchedName); if (objType != H5O_TYPE_DATASET) { std::ostringstream msg; msg << "Path '" << requestedPath << "' resolves to '" << currentPath << "' which is not a dataset"; throw H5::DataSetIException("findDatasetPath", msg.str()); } } else { // Intermediate component - should be a group H5O_type_t objType = currentGroup.childObjType(matchedName); if (objType != H5O_TYPE_GROUP) { std::ostringstream msg; msg << "Path component '" << matchedName << "' in '" << requestedPath << "' is not a group"; throw H5::DataSetIException("findDatasetPath", msg.str()); } // Open the group for next iteration currentGroup = currentGroup.openGroup(matchedName); } } return currentPath; } //============================================================================= // nxH5::PNeXus Constructor //============================================================================= nxH5::PNeXus::PNeXus() { // empty } //============================================================================= // nxH5::PNeXus Constructor //============================================================================= /** * Constructs a PNeXus object and immediately reads the specified NeXus file. * The constructor initializes the filename member and calls ReadNexusFile() * to open and parse the HDF5 file. * * @param fln Path to the NeXus HDF5 file to read * @throws H5::FileIException if the file cannot be opened * @throws H5::AttributeIException if required attributes are missing * @throws H5::DataSetIException if required datasets are missing * * @note The file is read immediately upon construction. If reading fails, * an exception is thrown and the object is not fully constructed. * * @example * @code * try { * PNeXus nexus("data/emu00139040.nxs"); * std::cout << "Opened: " << nexus.GetFileName() << std::endl; * } catch (const H5::Exception& e) { * std::cerr << "Failed to open file: " << e.getDetailMsg() << std::endl; * } * @endcode */ nxH5::PNeXus::PNeXus(const std::string fln, const bool printDebug) : fFileName(fln), fPrintDebug(printDebug) { ReadNexusFile(); } //============================================================================= // Read NeXus File //============================================================================= /** * Reads and parses the NeXus HDF5 file. Opens the file in read-only mode, * loads the NeXus version attribute, and reads the IDF version dataset. * Uses case-insensitive path lookup to handle varying path casings. * * @return 0 on success, 1 on error * @throws H5::FileIException if file cannot be opened * @throws H5::AttributeIException if NeXus_version attribute is missing * @throws H5::DataSetIException if IDF_version dataset is missing * * @note This method is called automatically by the constructor. It performs * case-insensitive lookups for both attributes and datasets to handle * NeXus files with varying naming conventions. * * @internal * Current implementation reads: * - Attribute: NeXus_version (from root "/") * - Dataset: /raw_data_1/IDF_version * * Both paths use case-insensitive lookup, so variations like * "/RAW_DATA_1/idf_VERSION" will work correctly. */ int nxH5::PNeXus::ReadNexusFile() { try { // Open the HDF5/NeXus file H5::H5File file(fFileName, H5F_ACC_RDONLY); // Turn off the auto-printing when failure occurs so that we can // handle the errors appropriately H5::Exception::dontPrint(); // Load NeXus version attribute (with case-insensitive lookup) std::string version; try { std::string versionNeXusAttr = findAttributeName(file, "NeXus_version"); H5::Attribute attr = file.openAttribute(versionNeXusAttr); H5::DataType dtype = attr.getDataType(); attr.read(dtype, version); attr.close(); fNeXusVersion = version; if (fPrintDebug) std::cout << "debug> NeXus version=" << fNeXusVersion << std::endl; } catch (const H5::AttributeIException& err) { std::cerr << "Error: Failed to read NeXus_version attribute: " << err.getDetailMsg() << std::endl; return 1; } // Load IDF version from dataset (with case-insensitive lookup) int idf_vers{-1}; try { std::string idfPath = findDatasetPath(file, "/run/IDF_version"); H5::DataSet dataset = file.openDataSet(idfPath); dataset.read(&idf_vers, H5::PredType::NATIVE_INT); dataset.close(); fIdfVersion = idf_vers; } catch (const H5::DataSetIException& err) { if (fPrintDebug) std::cout << "Info: not IDF version 1, will check for IDF version 2" << std::endl; } if (fIdfVersion == -1) { try { std::string idfPath = findDatasetPath(file, "/raw_data_1/IDF_version"); H5::DataSet dataset = file.openDataSet(idfPath); dataset.read(&idf_vers, H5::PredType::NATIVE_INT); dataset.close(); fIdfVersion = idf_vers; } catch (const H5::DataSetIException& err) { std::cerr << "Error: Failed to read IDF_version dataset: " << err.getDetailMsg() << std::endl; return 1; } try { std::string versionHdf5Attr = findAttributeName(file, "HDF5_version"); H5::Attribute attr = file.openAttribute(versionHdf5Attr); H5::DataType dtype = attr.getDataType(); attr.read(dtype, version); attr.close(); fHdf5Version = version; if (fPrintDebug) std::cout << "debug> HDF5 version=" << fHdf5Version << std::endl; } catch (const H5::AttributeIException& err) { std::cerr << "Error: Failed to read HDF5_version attribute: " << err.getDetailMsg() << std::endl; return 1; } } if (fPrintDebug) std::cout << "debug> IDF_version=" << fIdfVersion << std::endl; if (fIdfVersion == 1) { try { HandleIdfV1(file); } catch (...) { } } else { // fIdfVersion == 2 try { HandleIdfV2(file); } catch (...) { } } return 0; } catch (const H5::FileIException& err) { std::cerr << "Error: Failed to open file '" << fFileName << "': " << err.getDetailMsg() << std::endl; return 1; } catch (const H5::Exception& err) { std::cerr << "Error: HDF5 exception occurred: " << err.getDetailMsg() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: Unexpected exception: " << err.what() << std::endl; return 1; } } //============================================================================= // Handle NeXus IDF version 1 file //============================================================================= void nxH5::PNeXus::HandleIdfV1(H5::H5File &file) { if (fPrintDebug) std::cout << "debug> in HandleIdfV1 ..." << std::endl; std::string attrStr{""}, attrStrName{""}; // get file_name attribute try { attrStrName = findAttributeName(file, "user"); H5::Attribute attr = file.openAttribute(attrStrName); H5::DataType dtype = attr.getDataType(); attr.read(dtype, attrStr); attr.close(); fUserV1 = attrStr; if (fPrintDebug) std::cout << "debug> user =" << fUserV1 << std::endl; } catch (const H5::AttributeIException& err) { std::cerr << "Error: Failed to read user attribute: " << err.getDetailMsg() << std::endl; } // Read the mandatory key datasets and store them in the data map try { ReadStringDataset(file, "/run/program_name"); ReadIntDataset(file, "/run/number"); ReadStringDataset(file, "/run/title"); ReadStringDataset(file, "/run/notes"); ReadStringDataset(file, "/run/analysis"); ReadStringDataset(file, "/run/lab"); ReadStringDataset(file, "/run/beamline"); ReadStringDataset(file, "/run/start_time"); ReadStringDataset(file, "/run/stop_time"); ReadIntDataset(file, "/run/switching_states"); ReadStringDataset(file, "/run/user/name"); ReadStringDataset(file, "/run/user/experiment_number"); ReadStringDataset(file, "/run/sample/name"); ReadFloatDataset(file, "/run/sample/temperature"); ReadFloatDataset(file, "/run/sample/magnetic_field"); ReadStringDataset(file, "/run/sample/magnetic_field_state"); ReadFloatDataset(file, "/run/sample/magnetic_field_vector"); ReadStringDataset(file, "/run/sample/environment"); ReadStringDataset(file, "/run/instrument/name"); ReadIntDataset(file, "/run/instrument/detector/number"); ReadFloatDataset(file, "/run/instrument/detector/deadtimes"); ReadStringDataset(file, "/run/instrument/collimator/type"); ReadStringDataset(file, "/run/instrument/beam/beamline"); ReadIntDataset(file, "/run/instrument/beam/frames_good"); ReadIntDataset(file, "/run/histogram_data_1/counts"); ReadIntDataset(file, "/run/histogram_data_1/resolution"); ReadIntDataset(file, "/run/histogram_data_1/time_zero"); ReadFloatDataset(file, "/run/histogram_data_1/raw_time"); ReadFloatDataset(file, "/run/histogram_data_1/corrected_time"); ReadIntDataset(file, "/run/histogram_data_1/grouping"); ReadFloatDataset(file, "/run/histogram_data_1/alpha"); } catch (const H5::Exception& err) { std::cerr << "Error in HandleIdfV1: " << err.getDetailMsg() << std::endl; throw; } } //============================================================================= // Handle NeXus IDF version 2 file //============================================================================= void nxH5::PNeXus::HandleIdfV2(H5::H5File &file) { if (fPrintDebug) std::cout << "debug> in HandleIdfV2 ..." << std::endl; std::string attrStr{""}, attrStrName{""}; // get file_name attribute try { attrStrName = findAttributeName(file, "file_name"); H5::Attribute attr = file.openAttribute(attrStrName); H5::DataType dtype = attr.getDataType(); attr.read(dtype, attrStr); attr.close(); fFileNameNxs = attrStr; if (fPrintDebug) std::cout << "debug> NXS file_name =" << fFileNameNxs << std::endl; } catch (const H5::AttributeIException& err) { std::cerr << "Error: Failed to read file_name attribute: " << err.getDetailMsg() << std::endl; } // get file_time attribute attrStr=""; try { attrStrName = findAttributeName(file, "file_time"); H5::Attribute attr = file.openAttribute(attrStrName); H5::DataType dtype = attr.getDataType(); attr.read(dtype, attrStr); attr.close(); fFileTimeNxs = attrStr; if (fPrintDebug) std::cout << "debug> NXS file_time =" << fFileNameNxs << std::endl; } catch (const H5::AttributeIException& err) { std::cerr << "Error: Failed to read file_time attribute: " << err.getDetailMsg() << std::endl; } // Read the mandatory key datasets and store them in the data map try { ReadIntDataset(file, "/raw_data_1/IDF_version"); ReadStringDataset(file, "/raw_data_1/beamline"); ReadStringDataset(file, "/raw_data_1/definition"); ReadIntDataset(file, "/raw_data_1/good_frames"); ReadIntDataset(file, "/raw_data_1/run_number"); ReadStringDataset(file, "/raw_data_1/title"); ReadStringDataset(file, "/raw_data_1/start_time"); ReadStringDataset(file, "/raw_data_1/end_time"); ReadStringDataset(file, "/raw_data_1/experiment_identifier"); ReadStringDataset(file, "/raw_data_1/instrument/name"); ReadStringDataset(file, "/raw_data_1/instrument/source/name"); ReadStringDataset(file, "/raw_data_1/instrument/source/type"); ReadStringDataset(file, "/raw_data_1/instrument/source/probe"); ReadIntDataset(file, "/raw_data_1/instrument/detector_1/resolution"); ReadIntDataset(file, "/raw_data_1/instrument/detector_1/counts"); ReadFloatDataset(file, "/raw_data_1/instrument/detector_1/raw_time"); ReadIntDataset(file, "/raw_data_1/instrument/detector_1/spectrum_index"); ReadFloatDataset(file, "/raw_data_1/detector_1/dead_time"); ReadStringDataset(file, "/raw_data_1/sample/name"); ReadFloatDataset(file, "/raw_data_1/sample/temperature"); ReadFloatDataset(file, "/raw_data_1/sample/magnetic_field"); ReadStringDataset(file, "/raw_data_1/sample/shape"); } catch (const H5::Exception& err) { std::cerr << "Error in HandleIdfV2: " << err.getDetailMsg() << std::endl; throw; } } //============================================================================= // Dump hdf5-NeXus file content which was read //============================================================================= void nxH5::PNeXus::Dump() { int32_t first_good_bin{0}; if (fIdfVersion == 1) { std::cout << std::endl; std::cout << std::endl << "hdf5-NeXus file content of file:' " << fFileName << "'"; std::cout << std::endl << "****"; std::cout << std::endl << "****"; std::cout << std::endl << "Top Level Attributes:"; std::cout << std::endl << " NeXus Version: " << fNeXusVersion; std::cout << std::endl << " user: " << fUserV1; std::cout << std::endl << "++++"; std::cout << std::endl << "run"; std::cout << std::endl << "----"; std::cout << std::endl << " IDF_version: " << fIdfVersion; if (fDataMap.find("/run/program_name") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/program_name"]); std::cout << std::endl << " program_name : " << str_data.GetData()[0]; // Check for attributes if (str_data.HasAttribute("version")) { try { auto version = std::any_cast(str_data.GetAttribute("version")); std::cout << " version : " << version << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast version attribute" << std::endl; } } } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast program_name data" << std::endl; } } else { std::cout << std::endl << " program_name : n/a"; } if (fDataMap.find("/run/number") != fDataMap.end()) { try { auto number = std::any_cast>(fDataMap["/run/number"]); std::cout << std::endl << " number : " << number.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast number data" << std::endl; } } else { std::cout << std::endl << " number : n/a"; } if (fDataMap.find("/run/title") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/title"]); std::cout << std::endl << " title : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast title data" << std::endl; } } else { std::cout << std::endl << " title : n/a"; } if (fDataMap.find("/run/notes") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/notes"]); std::cout << std::endl << " notes : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast notes data" << std::endl; } } else { std::cout << std::endl << " notes : n/a"; } if (fDataMap.find("/run/analysis") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/analysis"]); std::cout << std::endl << " analysis : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast analysis data" << std::endl; } } else { std::cout << std::endl << " analysis : n/a"; } if (fDataMap.find("/run/lab") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/lab"]); std::cout << std::endl << " lab : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast lab data" << std::endl; } } else { std::cout << std::endl << " lab : n/a"; } if (fDataMap.find("/run/beamline") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/beamline"]); std::cout << std::endl << " beamline : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast beamline data" << std::endl; } } else { std::cout << std::endl << " beamline : n/a"; } if (fDataMap.find("/run/start_time") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/start_time"]); std::cout << std::endl << " start_time : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast start_time data" << std::endl; } } else { std::cout << std::endl << " start_time : n/a"; } if (fDataMap.find("/run/end_time") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/end_time"]); std::cout << std::endl << " end_time : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast end_time data" << std::endl; } } else { std::cout << std::endl << " end_time : n/a"; } if (fDataMap.find("/run/switching_state") != fDataMap.end()) { try { auto int_data = std::any_cast>(fDataMap["/run/switching_state"]); std::cout << std::endl << " switching_state : " << int_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast switching_state data" << std::endl; } } else { std::cout << std::endl << " switching_state : n/a"; } std::cout << std::endl << " user"; std::cout << std::endl << "----"; if (fDataMap.find("/run/usr/name") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/user/name"]); std::cout << std::endl << " name : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast name data" << std::endl; } } else { std::cout << std::endl << " name : n/a"; } if (fDataMap.find("/run/usr/experiment_number") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/user/experiment_number"]); std::cout << std::endl << " experiment_number : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast experiment_number data" << std::endl; } } else { std::cout << std::endl << " experiment_number : n/a"; } std::cout << std::endl << " sample"; std::cout << std::endl << "----"; if (fDataMap.find("/run/sample/name") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/sample/name"]); std::cout << std::endl << " name : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast name data" << std::endl; } } else { std::cout << std::endl << " name : n/a"; } if (fDataMap.find("/run/sample/temperature") != fDataMap.end()) { try { auto float_data = std::any_cast>(fDataMap["/run/sample/temperature"]); std::cout << std::endl << " temperature : " << float_data.GetData()[0]; // Check for attributes if (float_data.HasAttribute("units")) { try { auto units = std::any_cast(float_data.GetAttribute("units")); std::cout << " units : " << units << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast units attribute" << std::endl; } } } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast temperature data" << std::endl; } } else { std::cout << std::endl << " temperature : n/a"; } if (fDataMap.find("/run/sample/magnetic_field") != fDataMap.end()) { try { auto float_data = std::any_cast>(fDataMap["/run/sample/magnetic_field"]); std::cout << std::endl << " magnetic_field : " << float_data.GetData()[0]; // Check for attributes if (float_data.HasAttribute("units")) { try { auto units = std::any_cast(float_data.GetAttribute("units")); std::cout << " units : " << units << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast units attribute" << std::endl; } } } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast magnetic_field data" << std::endl; } } else { std::cout << std::endl << " magnetic_field : n/a"; } if (fDataMap.find("/run/sample/magnetic_field_vector") != fDataMap.end()) { try { auto float_data = std::any_cast>(fDataMap["/run/sample/magnetic_field_vector"]); std::cout << std::endl << " magnetic_field_vector : " << float_data.GetData()[0]; // Check for attributes if (float_data.HasAttribute("coordinate_system")) { try { auto coordinate_system = std::any_cast(float_data.GetAttribute("coordinate_system")); std::cout << " coordinate_system : " << coordinate_system << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast coordinate_system attribute" << std::endl; } } if (float_data.HasAttribute("units")) { try { auto units = std::any_cast(float_data.GetAttribute("units")); std::cout << " units : " << units << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast units attribute" << std::endl; } } if (float_data.HasAttribute("available")) { try { auto available = std::any_cast(float_data.GetAttribute("available")); std::cout << " available : " << available << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast available attribute" << std::endl; } } } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast magnetic_field_vector data" << std::endl; } } else { std::cout << std::endl << " magnetic_field_vector : n/a"; } if (fDataMap.find("/run/sample/environment") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/sample/environment"]); std::cout << std::endl << " environment : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast environment data" << std::endl; } } else { std::cout << std::endl << " environment : n/a"; } std::cout << std::endl << " instrument"; std::cout << std::endl << "----"; if (fDataMap.find("/run/instrument/name") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/instrument/name"]); std::cout << std::endl << " name : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast name data" << std::endl; } } else { std::cout << std::endl << " name : n/a"; } std::cout << std::endl << " detector"; std::cout << std::endl << "----"; if (fDataMap.find("/run/instrument/detector/number") != fDataMap.end()) { try { auto int_data = std::any_cast>(fDataMap["/run/instrument/detector/number"]); std::cout << std::endl << " number : " << int_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast number data" << std::endl; } } else { std::cout << std::endl << " number : n/a"; } if (fDataMap.find("/run/instrument/detector/deadtimes") != fDataMap.end()) { try { const auto& dead_times = std::any_cast>("/run/instrument/detector/deadtimes").GetData(); std::cout << std::endl << " deadtimes: "; for (unsigned int i=0; i<10; i++) std::cout << dead_times[i] << ", "; std::cout << "..."; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast dead time data" << std::endl; } } else { std::cout << std::endl << " deadtimes: n/a"; } std::cout << std::endl << " collimator"; std::cout << std::endl << "----"; if (fDataMap.find("/run/instrument/collimator/type") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/instrument/collimator/type"]); std::cout << std::endl << " type : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast type data" << std::endl; } } else { std::cout << std::endl << " type : n/a"; } std::cout << std::endl << " beam"; std::cout << std::endl << "----"; if (fDataMap.find("/run/instrument/beam/beamline") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/instrument/beam/beamline"]); std::cout << std::endl << " beamline : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast beamline data" << std::endl; } } else { std::cout << std::endl << " beamline : n/a"; } if (fDataMap.find("/run/instrument/beam/frames_good") != fDataMap.end()) { try { auto str_data = std::any_cast>(fDataMap["/run/instrument/beam/frames_good"]); std::cout << std::endl << " frames_good : " << str_data.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast frames_good data" << std::endl; } } else { std::cout << std::endl << " frames_good : n/a"; } std::cout << std::endl << " histogram_data_1"; std::cout << std::endl << "----"; std::cout << std::endl << " counts:"; std::cout << std::endl; try { auto counts_data = std::any_cast>(fDataMap["/run/histogram_data_1/counts"]); // Check for attributes if (counts_data.HasAttribute("units")) { try { auto units = std::any_cast(counts_data.GetAttribute("units")); std::cout << " units : " << units << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast units attribute" << std::endl; } } else { std::cout << " units : n/a" << std::endl; } if (counts_data.HasAttribute("signal")) { try { auto signal = std::any_cast(counts_data.GetAttribute("signal")); std::cout << " signal : " << signal << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast signal attribute" << std::endl; } } else { std::cout << " signal : n/a" << std::endl; } int noOfHistos{0}; if (counts_data.HasAttribute("number")) { try { noOfHistos = std::any_cast(counts_data.GetAttribute("number")); std::cout << " number : " << noOfHistos << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast number attribute" << std::endl; } } else { std::cout << " number : n/a" << std::endl; } int histoLength{0}; if (counts_data.HasAttribute("length")) { try { histoLength = std::any_cast(counts_data.GetAttribute("length")); std::cout << " length : " << noOfHistos << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast length attribute" << std::endl; } } else { std::cout << " length : n/a" << std::endl; } if (counts_data.HasAttribute("t0_bin")) { try { auto t0_bin = std::any_cast(counts_data.GetAttribute("t0_bin")); std::cout << " t0_bin : " << t0_bin << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast t0_bin attribute" << std::endl; } } else { std::cout << " t0_bin : n/a" << std::endl; } if (counts_data.HasAttribute("first_good_bin")) { try { first_good_bin = std::any_cast(counts_data.GetAttribute("first_good_bin")); std::cout << " first_good_bin : " << first_good_bin << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast first_good_bin attribute" << std::endl; } } else { std::cout << " first_good_bin : n/a" << std::endl; } if (counts_data.HasAttribute("last_good_bin")) { try { auto last_good_bin = std::any_cast(counts_data.GetAttribute("last_good_bin")); std::cout << " last_good_bin : " << last_good_bin << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast last_good_bin attribute" << std::endl; } } else { std::cout << " last_good_bin : n/a" << std::endl; } if (counts_data.HasAttribute("offset")) { try { auto offset = std::any_cast(counts_data.GetAttribute("offset")); std::cout << " offset : " << offset << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast offset attribute" << std::endl; } } else { std::cout << " offset : n/a" << std::endl; } // dump the first couple of counts of each detector const auto& data = counts_data.GetData(); std::cout << std::endl << " first couple of counts of each detector:"; for (unsigned int i=0; i>(fDataMap["/run/histogram_data_1/resolution"]); std::cout << std::endl << " resolution : " << int_data.GetData()[0]; if (int_data.HasAttribute("units")) { try { auto units = std::any_cast(int_data.GetAttribute("units")); std::cout << " units : " << units << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast units attribute" << std::endl; } } else { std::cout << " units : n/a" << std::endl; } } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast resolution data" << std::endl; } } else { std::cout << std::endl << " resolution : n/a"; } if (fDataMap.find("/run/histogram_data_1/time_zero") != fDataMap.end()) { try { auto int_data = std::any_cast>(fDataMap["/run/histogram_data_1/time_zero"]); std::cout << std::endl << " time_zero : " << int_data.GetData()[0]; if (int_data.HasAttribute("units")) { try { auto units = std::any_cast(int_data.GetAttribute("units")); std::cout << " units : " << units << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast units attribute" << std::endl; } } else { std::cout << " units : n/a" << std::endl; } if (int_data.HasAttribute("available")) { try { auto available = std::any_cast(int_data.GetAttribute("available")); std::cout << " available : " << available << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast available attribute" << std::endl; } } else { std::cout << " available : n/a" << std::endl; } } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast time_zero data" << std::endl; } } else { std::cout << std::endl << " time_zero : n/a"; } if (fDataMap.find("/run/histogram_data_1/raw_time") != fDataMap.end()) { try { auto float_data = std::any_cast>(fDataMap["/run/histogram_data_1/raw_time"]); std::cout << std::endl << " raw_time : " << float_data.GetData()[0]; if (float_data.HasAttribute("axis")) { try { auto axis = std::any_cast(float_data.GetAttribute("axis")); std::cout << " axis : " << axis << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast axis attribute" << std::endl; } } else { std::cout << " axis : n/a" << std::endl; } if (float_data.HasAttribute("primary")) { try { auto primary = std::any_cast(float_data.GetAttribute("primary")); std::cout << " primary : " << primary << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast primary attribute" << std::endl; } } else { std::cout << " primary : n/a" << std::endl; } if (float_data.HasAttribute("units")) { try { auto units = std::any_cast(float_data.GetAttribute("units")); std::cout << " units : " << units << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast units attribute" << std::endl; } } else { std::cout << " units : n/a" << std::endl; } } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast raw_time data" << std::endl; } } else { std::cout << std::endl << " raw_time : n/a"; } if (fDataMap.find("/run/histogram_data_1/corrected_time") != fDataMap.end()) { try { auto float_data = std::any_cast>(fDataMap["/run/histogram_data_1/corrected_time"]); std::cout << std::endl << " corrected_time : " << float_data.GetData()[0]; if (float_data.HasAttribute("axis")) { try { auto axis = std::any_cast(float_data.GetAttribute("axis")); std::cout << " axis : " << axis << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast axis attribute" << std::endl; } } else { std::cout << " axis : n/a" << std::endl; } if (float_data.HasAttribute("units")) { try { auto units = std::any_cast(float_data.GetAttribute("units")); std::cout << " units : " << units << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast units attribute" << std::endl; } } else { std::cout << " units : n/a" << std::endl; } } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast raw_time data" << std::endl; } } else { std::cout << std::endl << " corrected_time : n/a"; } if (fDataMap.find("/run/histogram_data_1/grouping") != fDataMap.end()) { try { auto int_data = std::any_cast>(fDataMap["/run/histogram_data_1/grouping"]); std::cout << std::endl << " grouping : " << int_data.GetData()[0]; if (int_data.HasAttribute("available")) { try { auto available = std::any_cast(int_data.GetAttribute("available")); std::cout << " available : " << available << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast available attribute" << std::endl; } } else { std::cout << std::endl << " available : n/a"; } } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast grouping data" << std::endl; } } else { std::cout << std::endl << " grouping : n/a"; } std::cout << std::endl; } else { // IDF Version 2 std::cout << std::endl; std::cout << std::endl << "hdf5-NeXus file content of file:' " << fFileName << "'"; std::cout << std::endl << "****"; std::cout << std::endl << "Top Level Attributes:"; std::cout << std::endl << " HDF5 Version : " << fHdf5Version; std::cout << std::endl << " NeXus Version: " << fNeXusVersion; std::cout << std::endl << " file_name : " << fFileNameNxs; std::cout << std::endl << " file_time : " << fFileTimeNxs; std::cout << std::endl << "++++"; std::cout << std::endl << "raw_data_1"; std::cout << std::endl << "----"; std::cout << std::endl << " IDF_version: " << fIdfVersion; if (fDataMap.find("/raw_data_1/beamline") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/beamline"]); std::cout << std::endl << " beamline : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast beamline data" << std::endl; } } else { std::cout << std::endl << " beamline : n/a"; } if (fDataMap.find("/raw_data_1/definition") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/definition"]); std::cout << std::endl << " definition : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast definition data" << std::endl; } } else { std::cout << std::endl << " definition : n/a"; } if (fDataMap.find("/raw_data_1/run_number") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/run_number"]); std::cout << std::endl << " run_number : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast run_number data" << std::endl; } } else { std::cout << std::endl << " run_number : n/a"; } if (fDataMap.find("/raw_data_1/title") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/title"]); std::cout << std::endl << " title : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast title data" << std::endl; } } else { std::cout << std::endl << " title : n/a"; } if (fDataMap.find("/raw_data_1/start_time") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/start_time"]); std::cout << std::endl << " start_time : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast start_time data" << std::endl; } } else { std::cout << std::endl << " start_time : n/a"; } if (fDataMap.find("/raw_data_1/end_time") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/end_time"]); std::cout << std::endl << " end_time : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast end_time data" << std::endl; } } else { std::cout << std::endl << " end_time : n/a"; } if (fDataMap.find("/raw_data_1/good_frames") != fDataMap.end()) { try { auto good_frames = std::any_cast>(fDataMap["/raw_data_1/good_frames"]); std::cout << std::endl << " good_frames: " << good_frames.GetData()[0]; } catch(const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast good_frames data" << std::endl; } } else { std::cout << std::endl << " good_frames: n/a"; } if (fDataMap.find("/raw_data_1/experiment_identifier") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/experiment_identifier"]); std::cout << std::endl << " experiment_identifier: " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast experiment_identifier data" << std::endl; } } else { std::cout << std::endl << " experiment_identifier: n/a"; } std::cout << std::endl << "----"; std::cout << std::endl << " instrument"; if (fDataMap.find("/raw_data_1/instrument/name") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/instrument/name"]); std::cout << std::endl << " name : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast instrument/name data" << std::endl; } } else { std::cout << std::endl << " name : n/a"; } std::cout << std::endl << "----"; std::cout << std::endl << " source"; if (fDataMap.find("/raw_data_1/instrument/source/name") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/instrument/source/name"]); std::cout << std::endl << " name : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast instrument/source/name data" << std::endl; } } else { std::cout << std::endl << " name : n/a"; } if (fDataMap.find("/raw_data_1/instrument/source/type") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/instrument/source/type"]); std::cout << std::endl << " type : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast instrument/source/type data" << std::endl; } } else { std::cout << std::endl << " type : n/a"; } if (fDataMap.find("/raw_data_1/instrument/source/probe") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/instrument/source/probe"]); std::cout << std::endl << " probe : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast instrument/source/probe data" << std::endl; } } else { std::cout << std::endl << " probe : n/a"; } std::cout << std::endl << "----"; std::cout << std::endl << " sample"; if (fDataMap.find("/raw_data_1/sample/name") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/sample/name"]); std::cout << std::endl << " name : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast sample/name data" << std::endl; } } else { std::cout << std::endl << " name : n/a"; } if (fDataMap.find("/raw_data_1/sample/temperature") != fDataMap.end()) { try { auto temp_ds = std::any_cast>(fDataMap["/raw_data_1/sample/temperature"]); float temp = temp_ds.GetData()[0]; if (temp_ds.HasAttribute("units")) { try { auto units = std::any_cast(temp_ds.GetAttribute("units")); if (units == "Celsius") temp += 273.16; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast units attribute" << std::endl; } } std::cout << std::endl << " temperature: " << temp << " K"; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast sample/temperature data" << std::endl; } } else { std::cout << std::endl << " temperature: n/a"; } if (fDataMap.find("/raw_data_1/sample/magnetic_field") != fDataMap.end()) { try { auto mag_field_ds = std::any_cast>(fDataMap["/raw_data_1/sample/magnetic_field"]); float mag_field = mag_field_ds.GetData()[0]; if (mag_field_ds.HasAttribute("units")) { try { auto units = std::any_cast(mag_field_ds.GetAttribute("units")); if (units == "Tesla") mag_field *= 1e4; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast units attribute" << std::endl; } } std::cout << std::endl << " mag. field: " << mag_field << " G"; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast sample/magnetic_field data" << std::endl; } } else { std::cout << std::endl << " temperature: n/a"; } if (fDataMap.find("/raw_data_1/sample/shape") != fDataMap.end()) { try { auto str = std::any_cast>(fDataMap["/raw_data_1/sample/shape"]); std::cout << std::endl << " shape : " << str.GetData()[0]; } catch (const std::bad_any_cast& e) { std::cerr << std::endl << "Error: Failed to cast sample/shape data" << std::endl; } } else { std::cout << std::endl << " shape : n/a"; } std::cout << std::endl << "----"; std::cout << std::endl << " detector_1"; std::cout << std::endl << " counts:"; std::cout << std::endl; try { auto counts_data = std::any_cast>(fDataMap["/raw_data_1/instrument/detector_1/counts"]); auto dims = counts_data.GetDimensions(); std::cout << " counts dimensions: " << dims[0] << " x " << dims[1] << " x " << dims[2] << std::endl; std::cout << " total elements: " << counts_data.GetNumElements() << std::endl; // Check for attributes if (counts_data.HasAttribute("signal")) { try { auto signal = std::any_cast(counts_data.GetAttribute("signal")); std::cout << " signal : " << signal << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast signal attribute" << std::endl; } } else { std::cout << " signal : n/a" << std::endl; } if (counts_data.HasAttribute("axes")) { try { auto axes = std::any_cast(counts_data.GetAttribute("axes")); std::cout << " axes : " << axes << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast axes attribute" << std::endl; } } else { std::cout << " axes : n/a" << std::endl; } if (counts_data.HasAttribute("long_name")) { try { auto long_name = std::any_cast(counts_data.GetAttribute("long_name")); std::cout << " long_name : " << long_name << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast long_name attribute" << std::endl; } } else { std::cout << " long_name : n/a" << std::endl; } if (counts_data.HasAttribute("t0_bin")) { try { auto t0_bin = std::any_cast(counts_data.GetAttribute("t0_bin")); std::cout << " t0_bin : " << t0_bin << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast t0_bin attribute" << std::endl; } } else { std::cout << " t0_bin : n/a" << std::endl; } if (counts_data.HasAttribute("first_good_bin")) { try { first_good_bin = std::any_cast(counts_data.GetAttribute("first_good_bin")); std::cout << " first_good_bin : " << first_good_bin << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast first_good_bin attribute" << std::endl; } } else { std::cout << " first_good_bin : n/a" << std::endl; } if (counts_data.HasAttribute("last_good_bin")) { try { auto last_good_bin = std::any_cast(counts_data.GetAttribute("last_good_bin")); std::cout << " last_good_bin : " << last_good_bin << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast last_good_bin attribute" << std::endl; } } else { std::cout << " last_good_bin : n/a" << std::endl; } if (fDataMap.find("/raw_data_1/instrument/detector_1/resolution") != fDataMap.end()) { try { auto ivalData = std::any_cast>(fDataMap["/raw_data_1/instrument/detector_1/resolution"]); std::cout << " resolution : " << ivalData.GetData()[0]; if (ivalData.HasAttribute("units")) { try { auto units = std::any_cast(ivalData.GetAttribute("units")); std::cout << " " << units << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast units attribute" << std::endl; } } } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast resolution attribute" << std::endl; } } else { std::cout << " resolution : n/a"; } // dump the first couple of counts of each detector const auto& data = counts_data.GetData(); std::cout << std::endl << " first couple of counts of each detector:"; for (unsigned int i=0; i>(fDataMap["/raw_data_1/instrument/detector_1/raw_time"]); const auto& data = raw_time.GetData(); // Check for attributes if (raw_time.HasAttribute("units")) { try { auto units = std::any_cast(raw_time.GetAttribute("units")); std::cout << " units : " << units << std::endl; } catch (const std::bad_any_cast& e) { std::cerr << "Error: Failed to cast units attribute" << std::endl; } } else { std::cout << " units : n/a" << std::endl; } // dump the first couple of raw_times std::cout << " "; for (unsigned int i=0; i>(fDataMap["/raw_data_1/instrument/detector_1/spectrum_index"]); const auto& data = spectrum_index.GetData(); // dump the first couple of raw_times std::cout << " "; for (unsigned int i=0; i>(fDataMap["/raw_data_1/detector_1/dead_time"]); const auto& data = dead_time.GetData(); // dump the first couple of raw_times std::cout << " "; for (unsigned int i=0; i dims(rank); dataspace.getSimpleExtentDims(dims.data(), nullptr); // Calculate total number of elements hsize_t numElements = 1; for (int i = 0; i < rank; i++) { numElements *= dims[i]; } // Create PNXdata object PNXdata data(datatype); data.SetDimensions(dims); // Read data std::vector buffer(numElements); dataset.read(buffer.data(), H5::PredType::NATIVE_INT); data.SetData(buffer); // Read attributes ReadDatasetAttributes(dataset, data); // Store in map fDataMap[actualPath] = data; dataset.close(); if (fPrintDebug) { std::cout << "debug> Read integer dataset: " << actualPath << " (dims: "; 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; } } catch (const H5::Exception& err) { std::cerr << "Error reading integer dataset " << path << ": " << err.getDetailMsg() << std::endl; throw; } } //============================================================================= // Read float dataset and store in data map //============================================================================= /** * Reads a float dataset from the HDF5 file and stores it in the data map. * Handles multi-dimensional datasets by flattening them into a vector. * * @param file HDF5 file object * @param path Path to the dataset * @throws H5::Exception if reading fails */ void nxH5::PNeXus::ReadFloatDataset(H5::H5File& file, const std::string& path) { try { // Find the actual path (case-insensitive) std::string actualPath = findDatasetPath(file, path); // Open the dataset H5::DataSet dataset = file.openDataSet(actualPath); H5::DataType datatype = dataset.getDataType(); H5::DataSpace dataspace = dataset.getSpace(); // Get dimensions int rank = dataspace.getSimpleExtentNdims(); std::vector dims(rank); dataspace.getSimpleExtentDims(dims.data(), nullptr); // Calculate total number of elements hsize_t numElements = 1; for (int i = 0; i < rank; i++) { numElements *= dims[i]; } // Create PNXdata object PNXdata data(datatype); data.SetDimensions(dims); // Read data std::vector buffer(numElements); dataset.read(buffer.data(), H5::PredType::NATIVE_FLOAT); data.SetData(buffer); // Read attributes ReadDatasetAttributes(dataset, data); // Store in map fDataMap[actualPath] = data; dataset.close(); if (fPrintDebug) { std::cout << "debug> Read float dataset: " << actualPath << " (dims: "; 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; } } catch (const H5::Exception& err) { std::cerr << "Error reading float dataset " << path << ": " << err.getDetailMsg() << std::endl; throw; } } //============================================================================= // Read string dataset and store in data map //============================================================================= /** * Reads a string dataset from the HDF5 file and stores it in the data map. * * @param file HDF5 file object * @param path Path to the dataset * @throws H5::Exception if reading fails */ void nxH5::PNeXus::ReadStringDataset(H5::H5File& file, const std::string& path) { try { // Find the actual path (case-insensitive) std::string actualPath = findDatasetPath(file, path); // Open the dataset H5::DataSet dataset = file.openDataSet(actualPath); H5::DataType datatype = dataset.getDataType(); H5::DataSpace dataspace = dataset.getSpace(); // Get dimensions int rank = dataspace.getSimpleExtentNdims(); std::vector dims(rank); dataspace.getSimpleExtentDims(dims.data(), nullptr); // Calculate total number of elements hsize_t numElements = 1; for (int i = 0; i < rank; i++) { numElements *= dims[i]; } // Create PNXdata object PNXdata data(datatype); data.SetDimensions(dims); // Read data std::vector buffer(numElements); if (numElements == 1) { // Single string std::string value; dataset.read(value, datatype); buffer[0] = value; } else { // Multiple strings (if needed) for (hsize_t i = 0; i < numElements; i++) { dataset.read(buffer[i], datatype); } } data.SetData(buffer); // Read attributes ReadDatasetAttributes(dataset, data); // Store in map fDataMap[actualPath] = data; dataset.close(); if (fPrintDebug) { std::cout << "debug> Read string dataset: " << actualPath << std::endl; } } catch (const H5::Exception& err) { std::cerr << "Error reading string dataset " << path << ": " << err.getDetailMsg() << std::endl; throw; } } //============================================================================= // Read dataset attributes (template implementation) //============================================================================= /** * Reads all attributes from an HDF5 dataset and adds them to a PNXdata object. * Attributes can be integers, floats, or strings. * * @tparam T The data type of the PNXdata object * @param dataset HDF5 dataset object * @param data PNXdata object to add attributes to */ template void nxH5::PNeXus::ReadDatasetAttributes(H5::DataSet& dataset, PNXdata& data) { int numAttrs = dataset.getNumAttrs(); for (int i = 0; i < numAttrs; i++) { H5::Attribute attr = dataset.openAttribute(i); std::string attrName = attr.getName(); H5::DataType attrType = attr.getDataType(); H5T_class_t typeClass = attrType.getClass(); try { if (typeClass == H5T_INTEGER) { int32_t value; attr.read(H5::PredType::NATIVE_INT32, &value); data.AddAttribute(attrName, value); } else if (typeClass == H5T_FLOAT) { float value; attr.read(H5::PredType::NATIVE_FLOAT, &value); data.AddAttribute(attrName, value); } else if (typeClass == H5T_STRING) { std::string value; attr.read(attrType, value); data.AddAttribute(attrName, value); } } catch (const H5::Exception& err) { if (fPrintDebug) { std::cerr << "Warning: Could not read attribute " << attrName << ": " << err.getDetailMsg() << std::endl; } } attr.close(); } } // Explicit template instantiations template void nxH5::PNeXus::ReadDatasetAttributes(H5::DataSet&, PNXdata&); template void nxH5::PNeXus::ReadDatasetAttributes(H5::DataSet&, PNXdata&); template void nxH5::PNeXus::ReadDatasetAttributes(H5::DataSet&, PNXdata&); //============================================================================= // WRITE METHODS //============================================================================= //============================================================================= // Create group hierarchy for a given path //============================================================================= /** * Create nested group hierarchy for a given path. Groups are created recursively * if they don't exist. If a group already exists, it is opened and returned. * * @param file HDF5 file object * @param path Full path (e.g., "/raw_data_1/detector_1") * @return H5::Group handle to the deepest created/opened group * @throws H5::GroupIException if group creation fails */ H5::Group nxH5::PNeXus::CreateGroupHierarchy(H5::H5File& file, const std::string& path) { std::vector components = splitPath(path); std::string currentPath = ""; H5::Group currentGroup; for (const auto& component : components) { if (component.empty()) continue; // Skip empty (root indicator) currentPath += "/" + component; // Check if group exists bool exists = false; try { H5::Group testGroup = file.openGroup(currentPath); testGroup.close(); exists = true; } catch (const H5::Exception&) { exists = false; } // Create if doesn't exist if (!exists) { if (fPrintDebug) { std::cout << "debug> Creating group: " << currentPath << std::endl; } currentGroup = file.createGroup(currentPath); // Write group attributes if any exist for this path auto attrIt = fGroupAttributes.find(currentPath); if (attrIt != fGroupAttributes.end()) { if (fPrintDebug) { std::cout << "debug> Writing " << attrIt->second.size() << " attributes to group: " << currentPath << std::endl; } WriteGroupAttributes(currentGroup, attrIt->second); } currentGroup.close(); if (fPrintDebug) { std::cout << "debug> Created group: " << currentPath << std::endl; } } } // Return an invalid group handle (we just needed to create the hierarchy) return currentGroup; } //============================================================================= // Write dataset attributes from PNXdata object //============================================================================= /** * Writes all attributes from a PNXdata object to an HDF5 dataset. * Supports int32_t, float, and string attribute types. * * @tparam T The data type of the PNXdata object * @param dataset HDF5 dataset object to write attributes to * @param data PNXdata object containing attributes */ template void nxH5::PNeXus::WriteDatasetAttributes(H5::DataSet& dataset, const PNXdata& data) { const auto& attributes = data.GetAttributes(); for (const auto& [attrName, attrValue] : attributes) { try { // Try int32_t if (auto* intVal = std::any_cast(&attrValue)) { H5::DataSpace attrSpace(H5S_SCALAR); H5::Attribute attr = dataset.createAttribute( attrName, H5::PredType::NATIVE_INT32, attrSpace ); attr.write(H5::PredType::NATIVE_INT32, intVal); attr.close(); attrSpace.close(); } // Try float else if (auto* floatVal = std::any_cast(&attrValue)) { H5::DataSpace attrSpace(H5S_SCALAR); H5::Attribute attr = dataset.createAttribute( attrName, H5::PredType::NATIVE_FLOAT, attrSpace ); attr.write(H5::PredType::NATIVE_FLOAT, floatVal); attr.close(); attrSpace.close(); } // Try string else if (auto* strVal = std::any_cast(&attrValue)) { H5::StrType strType(H5::PredType::C_S1, H5T_VARIABLE); H5::DataSpace attrSpace(H5S_SCALAR); H5::Attribute attr = dataset.createAttribute( attrName, strType, attrSpace ); attr.write(strType, *strVal); attr.close(); attrSpace.close(); } else { if (fPrintDebug) { std::cerr << "Warning: Unsupported attribute type for " << attrName << std::endl; } } } catch (const H5::Exception& err) { if (fPrintDebug) { std::cerr << "Warning: Could not write attribute " << attrName << ": " << err.getDetailMsg() << std::endl; } } } } //============================================================================= // Write integer dataset with attributes //============================================================================= /** * Writes an integer dataset to the HDF5 file with all its attributes. * The parent group hierarchy is created automatically if it doesn't exist. * * @param file HDF5 file object * @param path Dataset path (groups created automatically) * @param data PNXdata object containing data, dimensions, and attributes * @throws H5::Exception if writing fails */ void nxH5::PNeXus::WriteIntDataset(H5::H5File& file, const std::string& path, const PNXdata& data) { try { // Extract parent path and dataset name size_t lastSlash = path.find_last_of('/'); std::string parentPath = (lastSlash == 0) ? "/" : path.substr(0, lastSlash); std::string datasetName = path.substr(lastSlash + 1); // Create parent group hierarchy if (!parentPath.empty() && parentPath != "/") { CreateGroupHierarchy(file, parentPath); } // Get dimensions and data const auto& dims = data.GetDimensions(); const auto& buffer = data.GetData(); // Validate data consistency if (buffer.size() != data.GetNumElements()) { throw std::runtime_error("Data size mismatch with dimensions"); } // Create dataspace H5::DataSpace dataspace(dims.size(), dims.data()); // Create dataset H5::DataSet dataset = file.createDataSet( path, H5::PredType::NATIVE_INT, dataspace ); // Write data dataset.write(buffer.data(), H5::PredType::NATIVE_INT); // Write attributes WriteDatasetAttributes(dataset, data); // Close resources dataset.close(); dataspace.close(); if (fPrintDebug) { std::cout << "debug> Wrote integer dataset: " << path << " (dims: "; 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; } } catch (const H5::Exception& err) { std::cerr << "Error writing integer dataset " << path << ": " << err.getDetailMsg() << std::endl; throw; } } //============================================================================= // Write float dataset with attributes //============================================================================= /** * Writes a float dataset to the HDF5 file with all its attributes. * The parent group hierarchy is created automatically if it doesn't exist. * * @param file HDF5 file object * @param path Dataset path (groups created automatically) * @param data PNXdata object containing data, dimensions, and attributes * @throws H5::Exception if writing fails */ void nxH5::PNeXus::WriteFloatDataset(H5::H5File& file, const std::string& path, const PNXdata& data) { try { // Extract parent path and dataset name size_t lastSlash = path.find_last_of('/'); std::string parentPath = (lastSlash == 0) ? "/" : path.substr(0, lastSlash); std::string datasetName = path.substr(lastSlash + 1); // Create parent group hierarchy if (!parentPath.empty() && parentPath != "/") { CreateGroupHierarchy(file, parentPath); } // Get dimensions and data const auto& dims = data.GetDimensions(); const auto& buffer = data.GetData(); // Validate data consistency if (buffer.size() != data.GetNumElements()) { throw std::runtime_error("Data size mismatch with dimensions"); } // Create dataspace H5::DataSpace dataspace(dims.size(), dims.data()); // Create dataset H5::DataSet dataset = file.createDataSet( path, H5::PredType::NATIVE_FLOAT, dataspace ); // Write data dataset.write(buffer.data(), H5::PredType::NATIVE_FLOAT); // Write attributes WriteDatasetAttributes(dataset, data); // Close resources dataset.close(); dataspace.close(); if (fPrintDebug) { std::cout << "debug> Wrote float dataset: " << path << " (dims: "; 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; } } catch (const H5::Exception& err) { std::cerr << "Error writing float dataset " << path << ": " << err.getDetailMsg() << std::endl; throw; } } //============================================================================= // Write string dataset with attributes //============================================================================= /** * Writes a string dataset to the HDF5 file with all its attributes. * The parent group hierarchy is created automatically if it doesn't exist. * Handles both scalar strings and arrays of strings. * * @param file HDF5 file object * @param path Dataset path (groups created automatically) * @param data PNXdata object containing data, dimensions, and attributes * @throws H5::Exception if writing fails */ void nxH5::PNeXus::WriteStringDataset(H5::H5File& file, const std::string& path, const PNXdata& data) { try { // Extract parent path and dataset name size_t lastSlash = path.find_last_of('/'); std::string parentPath = (lastSlash == 0) ? "/" : path.substr(0, lastSlash); std::string datasetName = path.substr(lastSlash + 1); // Create parent group hierarchy if (!parentPath.empty() && parentPath != "/") { CreateGroupHierarchy(file, parentPath); } // Get data const auto& buffer = data.GetData(); const auto& dims = data.GetDimensions(); /* //as35 // Handle single string vs array of strings if (buffer.size() == 1 && dims.size() == 1 && dims[0] == 1) { // Single string - use variable-length string type H5::StrType strType(H5::PredType::C_S1, H5T_VARIABLE); H5::DataSpace dataspace(H5S_SCALAR); H5::DataSet dataset = file.createDataSet(path, strType, dataspace); dataset.write(buffer[0], strType); WriteDatasetAttributes(dataset, data); dataset.close(); dataspace.close(); } else { */ //as35 // Array of strings H5::StrType strType(H5::PredType::C_S1, H5T_VARIABLE); H5::DataSpace dataspace(dims.size(), dims.data()); H5::DataSet dataset = file.createDataSet(path, strType, dataspace); // For arrays, need to create array of C strings std::vector cStrings; for (const auto& str : buffer) { cStrings.push_back(str.c_str()); } dataset.write(cStrings.data(), strType); WriteDatasetAttributes(dataset, data); dataset.close(); dataspace.close(); /* //as35 } */ //as35 if (fPrintDebug) { std::cout << "debug> Wrote string dataset: " << path << std::endl; } } catch (const H5::Exception& err) { std::cerr << "Error writing string dataset " << path << ": " << err.getDetailMsg() << std::endl; throw; } } //============================================================================= // Write group attributes //============================================================================= /** * Writes attributes to an HDF5 group object. Supports int32_t, float, and * string attribute types. * * @param group HDF5 group object to write attributes to * @param attributes Map of attribute names to values (stored as std::any) */ void nxH5::PNeXus::WriteGroupAttributes(H5::Group& group, const std::map& attributes) { for (const auto& [attrName, attrValue] : attributes) { try { // Try int32_t if (auto* intVal = std::any_cast(&attrValue)) { H5::DataSpace attrSpace(H5S_SCALAR); H5::Attribute attr = group.createAttribute( attrName, H5::PredType::NATIVE_INT32, attrSpace ); attr.write(H5::PredType::NATIVE_INT32, intVal); attr.close(); attrSpace.close(); } // Try float else if (auto* floatVal = std::any_cast(&attrValue)) { H5::DataSpace attrSpace(H5S_SCALAR); H5::Attribute attr = group.createAttribute( attrName, H5::PredType::NATIVE_FLOAT, attrSpace ); attr.write(H5::PredType::NATIVE_FLOAT, floatVal); attr.close(); attrSpace.close(); } // Try string else if (auto* strVal = std::any_cast(&attrValue)) { H5::StrType strType(H5::PredType::C_S1, H5T_VARIABLE); H5::DataSpace attrSpace(H5S_SCALAR); H5::Attribute attr = group.createAttribute( attrName, strType, attrSpace ); attr.write(strType, *strVal); attr.close(); attrSpace.close(); } else { if (fPrintDebug) { std::cerr << "Warning: Unsupported attribute type for " << attrName << std::endl; } } } catch (const H5::Exception& err) { if (fPrintDebug) { std::cerr << "Warning: Failed to write group attribute " << attrName << ": " << err.getDetailMsg() << std::endl; } } } } //============================================================================= // Write root-level file attributes //============================================================================= /** * Writes root-level file attributes such as NeXus_version, HDF5_version, * file_name, and file_time to the HDF5 file. * * @param file HDF5 file object * @throws H5::Exception if writing fails */ void nxH5::PNeXus::WriteFileAttributes(H5::H5File& file) { try { H5::StrType strType(H5::PredType::C_S1, H5T_VARIABLE); H5::DataSpace attrSpace(H5S_SCALAR); // Check if custom root-level attributes exist auto rootAttrIt = fGroupAttributes.find("/"); bool hasCustomAttrs = (rootAttrIt != fGroupAttributes.end()); // NeXus_version attribute if (!fNeXusVersion.empty() && (!hasCustomAttrs || rootAttrIt->second.find("NeXus_version") == rootAttrIt->second.end())) { H5::Attribute attr = file.createAttribute( "NeXus_version", strType, attrSpace ); attr.write(strType, fNeXusVersion); attr.close(); } // HDF5_version attribute if (!fHdf5Version.empty() && (!hasCustomAttrs || rootAttrIt->second.find("HDF5_version") == rootAttrIt->second.end())) { H5::Attribute attr = file.createAttribute( "HDF5_version", strType, attrSpace ); attr.write(strType, fHdf5Version); attr.close(); } // file_name attribute if (!fFileNameNxs.empty() && (!hasCustomAttrs || rootAttrIt->second.find("file_name") == rootAttrIt->second.end())) { H5::Attribute attr = file.createAttribute( "file_name", strType, attrSpace ); attr.write(strType, fFileNameNxs); attr.close(); } // file_time attribute if (!fFileTimeNxs.empty() && (!hasCustomAttrs || rootAttrIt->second.find("file_time") == rootAttrIt->second.end())) { H5::Attribute attr = file.createAttribute( "file_time", strType, attrSpace ); attr.write(strType, fFileTimeNxs); attr.close(); } attrSpace.close(); // Write any custom root-level attributes from fGroupAttributes["/"] if (hasCustomAttrs) { if (fPrintDebug) { std::cout << "debug> Writing " << rootAttrIt->second.size() << " custom attributes to root level" << std::endl; } for (const auto& [attrName, attrValue] : rootAttrIt->second) { try { // Try int32_t if (auto* intVal = std::any_cast(&attrValue)) { H5::DataSpace scalarSpace(H5S_SCALAR); H5::Attribute attr = file.createAttribute( attrName, H5::PredType::NATIVE_INT32, scalarSpace ); attr.write(H5::PredType::NATIVE_INT32, intVal); attr.close(); scalarSpace.close(); } // Try float else if (auto* floatVal = std::any_cast(&attrValue)) { H5::DataSpace scalarSpace(H5S_SCALAR); H5::Attribute attr = file.createAttribute( attrName, H5::PredType::NATIVE_FLOAT, scalarSpace ); attr.write(H5::PredType::NATIVE_FLOAT, floatVal); attr.close(); scalarSpace.close(); } // Try string else if (auto* strVal = std::any_cast(&attrValue)) { H5::StrType varStrType(H5::PredType::C_S1, H5T_VARIABLE); H5::DataSpace scalarSpace(H5S_SCALAR); H5::Attribute attr = file.createAttribute( attrName, varStrType, scalarSpace ); attr.write(varStrType, *strVal); attr.close(); scalarSpace.close(); } else { if (fPrintDebug) { std::cerr << "Warning: Unsupported attribute type for root-level " << attrName << std::endl; } } } catch (const H5::Exception& err) { if (fPrintDebug) { std::cerr << "Warning: Failed to write root-level attribute " << attrName << ": " << err.getDetailMsg() << std::endl; } } } } } catch (const H5::Exception& err) { std::cerr << "Error writing file attributes: " << err.getDetailMsg() << std::endl; throw; } } //============================================================================= // Write IDF version 2 structure //============================================================================= /** * Writes all datasets from the data map to the HDF5 file following the * IDF version 2 structure. Iterates through fDataMap and writes each * dataset based on its type. * * @param file HDF5 file object * @throws H5::Exception if writing fails */ void nxH5::PNeXus::WriteIdfV2(H5::H5File& file) { if (fPrintDebug) { std::cout << "debug> Writing IDF v2 structure..." << std::endl; } // Iterate through all datasets in fDataMap for (const auto& [path, anyData] : fDataMap) { try { // Try PNXdata try { auto intData = std::any_cast>(anyData); WriteIntDataset(file, path, intData); continue; } catch (const std::bad_any_cast&) {} // Try PNXdata - convert to int try { auto int32Data = std::any_cast>(anyData); // Convert PNXdata to PNXdata PNXdata intData(int32Data.GetDataType()); intData.SetDimensions(int32Data.GetDimensions()); std::vector convertedData; for (auto val : int32Data.GetData()) { convertedData.push_back(static_cast(val)); } intData.SetData(convertedData); // Copy attributes for (const auto& [name, value] : int32Data.GetAttributes()) { intData.AddAttribute(name, value); } WriteIntDataset(file, path, intData); continue; } catch (const std::bad_any_cast&) {} // Try PNXdata try { auto floatData = std::any_cast>(anyData); WriteFloatDataset(file, path, floatData); continue; } catch (const std::bad_any_cast&) {} // Try PNXdata try { auto strData = std::any_cast>(anyData); WriteStringDataset(file, path, strData); continue; } catch (const std::bad_any_cast&) {} // Unknown type if (fPrintDebug) { std::cerr << "Warning: Unknown data type for path " << path << std::endl; } } catch (const H5::Exception& err) { std::cerr << "Error writing dataset " << path << ": " << err.getDetailMsg() << std::endl; throw; } } } //============================================================================= // Group attribute management methods //============================================================================= /** * Add or update an attribute for a group. * * @param groupPath HDF5 path of the group (e.g., "/raw_data_1") * @param attrName Attribute name * @param attrValue Attribute value (stored as std::any) * @return true if attribute was added successfully */ bool nxH5::PNeXus::AddGroupAttribute(const std::string& groupPath, const std::string& attrName, const std::any& attrValue) { fGroupAttributes[groupPath][attrName] = attrValue; return true; } /** * Remove an attribute from a group. * * @param groupPath HDF5 path of the group * @param attrName Attribute name to remove * @return true if attribute was removed, false if group or attribute not found */ bool nxH5::PNeXus::RemoveGroupAttribute(const std::string& groupPath, const std::string& attrName) { auto groupIt = fGroupAttributes.find(groupPath); if (groupIt == fGroupAttributes.end()) { return false; } auto attrIt = groupIt->second.find(attrName); if (attrIt == groupIt->second.end()) { return false; } groupIt->second.erase(attrIt); // Clean up empty group entry if (groupIt->second.empty()) { fGroupAttributes.erase(groupIt); } return true; } /** * Check if a group has a specific attribute. * * @param groupPath HDF5 path of the group * @param attrName Attribute name to check * @return true if attribute exists, false otherwise */ bool nxH5::PNeXus::HasGroupAttribute(const std::string& groupPath, const std::string& attrName) const { auto groupIt = fGroupAttributes.find(groupPath); if (groupIt == fGroupAttributes.end()) { return false; } return groupIt->second.find(attrName) != groupIt->second.end(); } /** * Get an attribute value from a group. * * @param groupPath HDF5 path of the group * @param attrName Attribute name * @return The attribute value as std::any * @throws std::out_of_range if group or attribute doesn't exist */ std::any nxH5::PNeXus::GetGroupAttribute(const std::string& groupPath, const std::string& attrName) const { return fGroupAttributes.at(groupPath).at(attrName); } /** * Get all attributes for a group. * * @param groupPath HDF5 path of the group * @return Map of attribute names to values */ const std::map& nxH5::PNeXus::GetGroupAttributes(const std::string& groupPath) const { static const std::map emptyMap; auto it = fGroupAttributes.find(groupPath); if (it == fGroupAttributes.end()) { return emptyMap; } return it->second; } /** * Clear all attributes from a group. * * @param groupPath HDF5 path of the group * @return true if group was found and attributes cleared, false otherwise */ bool nxH5::PNeXus::ClearGroupAttributes(const std::string& groupPath) { auto it = fGroupAttributes.find(groupPath); if (it == fGroupAttributes.end()) { return false; } fGroupAttributes.erase(it); return true; } /** * Add or update an attribute at the root level "/". * This is a convenience method that calls AddGroupAttribute("/", attrName, attrValue). * * @param attrName Attribute name * @param attrValue Attribute value (stored as std::any) * @return true if attribute was added successfully */ bool nxH5::PNeXus::AddRootAttribute(const std::string& attrName, const std::any& attrValue) { return AddGroupAttribute("/", attrName, attrValue); } //============================================================================= // Write NeXus file (main entry point) //============================================================================= /** * Writes the data map contents to a new NeXus HDF5 file. * This is the main public API for writing NeXus files. * * @param filename Path to the output NeXus HDF5 file * @param idfVersion IDF version to write (default: 2) * @return 0 on success, 1 on error * @throws H5::FileIException if file cannot be created * @throws H5::GroupIException if group creation fails * @throws H5::DataSetIException if dataset writing fails */ int nxH5::PNeXus::WriteNexusFile(const std::string& filename, int idfVersion) { try { if (fPrintDebug) { std::cout << std::endl; std::cout << "debug> Creating NeXus file: " << filename << std::endl; } // Validate that we have data to write if (fDataMap.empty()) { std::cerr << "Error: No data to write (data map is empty)" << std::endl; return 1; } // Create new HDF5 file (truncate if exists) H5::H5File file(filename, H5F_ACC_TRUNC); // Turn off auto-printing for exceptions H5::Exception::dontPrint(); // Write root-level file attributes WriteFileAttributes(file); // Write structure based on IDF version if (idfVersion == 1) { std::cerr << "Error: IDF version 1 writing not yet implemented" << std::endl; file.close(); return 1; } else if (idfVersion == 2) { WriteIdfV2(file); } else { std::cerr << "Error: Unsupported IDF version " << idfVersion << std::endl; file.close(); return 1; } // Close file file.close(); if (fPrintDebug) { std::cout << "debug> Successfully wrote NeXus file: " << filename << std::endl; } return 0; } catch (const H5::FileIException& err) { std::cerr << "Error: Failed to create file '" << filename << "': " << err.getDetailMsg() << std::endl; return 1; } catch (const H5::Exception& err) { std::cerr << "Error: HDF5 exception occurred: " << err.getDetailMsg() << std::endl; return 1; } catch (const std::exception& err) { std::cerr << "Error: Unexpected exception: " << err.what() << std::endl; return 1; } } // Explicit template instantiations for write methods template void nxH5::PNeXus::WriteDatasetAttributes(H5::DataSet&, const PNXdata&); template void nxH5::PNeXus::WriteDatasetAttributes(H5::DataSet&, const PNXdata&); template void nxH5::PNeXus::WriteDatasetAttributes(H5::DataSet&, const PNXdata&);