Files
musrfit/src/external/nexus/PNeXus.cpp

5594 lines
200 KiB
C++

/***************************************************************************
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 <iostream>
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <cstring>
#include <cmath>
#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<char*>(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<std::string, std::any> 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<PNXdata<int>>(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<PNXdata<float>>(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<PNXdata<int>>(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<PNXdata<int>>(dataMap["/raw_data_1/instrument/detector_1/spectrum_index"]);
if (spec_idx_data.HasAttribute("t0_bin")) {
try {
fT0Bin = std::any_cast<int>(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<int>(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<int>(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<double> &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<unsigned int>(fLgbBin); bin++) {
int idx = period * fDims[1] * fDims[2] + fIdx * fDims[2] + bin;
double n_obs = static_cast<double>(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<int>(fDims[1])) {
std::cerr << "**ERROR** Invalid spectrum index: " << i << std::endl;
return;
}
fIdx = static_cast<unsigned int>(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<char> 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<char> 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<char> 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<char> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> float_datasets = {
"/raw_data_1/instrument/detector_1/resolution",
"/raw_data_1/instrument/detector_1/time_zero"
};
std::vector<std::string> 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<std::string> 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<uint32_t> dimensions;
for (int32 i = 0; i < rank; i++) {
n_elements *= dim_sizes[i];
dimensions.push_back(static_cast<uint32_t>(dim_sizes[i]));
}
// Read data
std::vector<int32> 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> int_data(data.begin(), data.end());
// Create PNXdata object
PNXdata<int> 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<std::string> 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<uint32_t> dimensions;
for (int32 i = 0; i < rank; i++) {
n_elements *= dim_sizes[i];
dimensions.push_back(static_cast<uint32_t>(dim_sizes[i]));
}
// Read data
std::vector<float32> 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> float_data(data.begin(), data.end());
// Create PNXdata object
PNXdata<float> 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<std::string> 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<uint32_t> dimensions;
for (int32 i = 0; i < rank; i++) {
n_elements *= dim_sizes[i];
dimensions.push_back(static_cast<uint32_t>(dim_sizes[i]));
}
// Read data as char array
std::vector<char> 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<std::string> string_data;
string_data.push_back(std::string(data.data()));
// Create PNXdata object
PNXdata<std::string> 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 <typename T>
void nxH4::PNeXus::ReadDatasetAttributes(int32 sds_id, PNXdata<T>& 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<int>(value));
}
} else {
// Multiple values - store as vector
std::vector<int32> attr_data(attr_count);
if (SDreadattr(sds_id, i, attr_data.data()) != FAIL) {
std::vector<int> 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<float>(value));
}
} else {
// Multiple values - store as vector
std::vector<float32> attr_data(attr_count);
if (SDreadattr(sds_id, i, attr_data.data()) != FAIL) {
std::vector<float> 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<char> 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<PNXdata<std::string>>(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<std::string>(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<PNXdata<int>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<float>>(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<std::string>(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<PNXdata<float>>(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<std::string>(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<PNXdata<float>>(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<std::string>(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<std::string>(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<int>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<int>>(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<PNXdata<float>>(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<end; i++)
std::cout << data[i] << ", ";
std::cout << "...";
} catch (const std::bad_any_cast& e) {
std::cerr << std::endl << "**ERROR**: Failed to cast deadtimes data" << std::endl;
}
} else {
std::cout << std::endl << " deadtimes: n/a";
}
std::cout << std::endl << "----";
std::cout << std::endl << " beam";
std::cout << std::endl << "----";
if (fDataMap.find("/run/instrument/beam/frames_good") != fDataMap.end()) {
try {
auto fg = std::any_cast<PNXdata<int>>(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<PNXdata<std::string>>(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<PNXdata<int>>(fDataMap["/run/histogram_data_1/counts"]);
// Check for attributes
if (counts_data.HasAttribute("units")) {
try {
auto units = std::any_cast<std::string>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<float>(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<noOfHistos; i++) {
if (i<9)
std::cout << std::endl << " " << i+1 << ": ";
else
std::cout << std::endl << " " << i+1 << ": ";
for (unsigned int j=0; j<first_good_bin+5; j++)
std::cout << data[i*histoLength+j] << ", ";
std::cout << "...";
}
} catch (const std::bad_any_cast& e) {
std::cerr << "**ERROR**: Failed to cast counts data" << std::endl;
}
if (fDataMap.find("/run/histogram_data_1/resolution") != fDataMap.end()) {
try {
auto int_data = std::any_cast<PNXdata<int>>(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<std::string>(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<PNXdata<float>>(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<std::string>(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<int>(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<PNXdata<float>>(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<int>(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<int>(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<std::string>(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<PNXdata<float>>(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<int>(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<std::string>(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<PNXdata<int>>(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<end; i++)
std::cout << data[i] << ", ";
std::cout << "...";
if (int_data.HasAttribute("available")) {
try {
auto available = std::any_cast<int>(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<PNXdata<float>>(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<end; i++)
std::cout << data[i] << ", ";
std::cout << "...";
if (float_data.HasAttribute("available")) {
try {
auto available = std::any_cast<int>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<int>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<int>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<int>>(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<int>(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<std::string>(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<std::string>(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<int32_t>(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<int32_t>(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<int32_t>(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<PNXdata<int32_t>>(fDataMap["/raw_data_1/instrument/detector_1/resolution"]);
std::cout << " resolution : " << ivalData.GetData()[0];
if (ivalData.HasAttribute("units")) {
try {
auto units = std::any_cast<std::string>(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<dims[1]; i++) {
if (i<9)
std::cout << std::endl << " " << i+1 << ": ";
else
std::cout << std::endl << " " << i+1 << ": ";
for (unsigned int j=0; j<first_good_bin+5; j++)
std::cout << data[i*dims[2]+j] << ", ";
std::cout << "...";
}
} catch (const std::bad_any_cast& e) {
std::cerr << "Error: Failed to cast counts data" << std::endl;
}
std::cout << std::endl;
std::cout << std::endl << " raw_time:";
std::cout << std::endl;
try {
auto raw_time = std::any_cast<PNXdata<float>>(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<std::string>(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<first_good_bin+5; i++)
std::cout << data[i] << ", ";
std::cout << "...";
} catch (const std::bad_any_cast& e) {
std::cerr << "Error: Failed to cast raw_time data" << std::endl;
}
std::cout << std::endl;
std::cout << std::endl << " spectrum_index:";
std::cout << std::endl;
try {
auto spectrum_index = std::any_cast<PNXdata<int>>(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<first_good_bin+5; i++)
std::cout << data[i] << ", ";
std::cout << "...";
} catch (const std::bad_any_cast& e) {
std::cerr << "Error: Failed to cast spectrum_index data" << std::endl;
}
std::cout << std::endl;
std::cout << std::endl << " dead_time:";
std::cout << std::endl;
try {
auto dead_time = std::any_cast<PNXdata<float>>(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<first_good_bin+5; i++)
std::cout << data[i] << ", ";
std::cout << "...";
} catch (const std::bad_any_cast& e) {
std::cerr << "Error: Failed to cast spectrum_index data" << std::endl;
}
std::cout << std::endl;
}
}
//=============================================================================
// nxH4::PNeXus::WriteNexusFile
//=============================================================================
int nxH4::PNeXus::WriteNexusFile(const std::string& filename, int idfVersion)
{
// Create new HDF4 file
int32 sd_id = SDstart(filename.c_str(), DFACC_CREATE);
if (sd_id == FAIL) {
std::cerr << "**ERROR** Failed to create HDF4 file: " << filename << std::endl;
return 1;
}
// Write file attributes
WriteFileAttributes(sd_id);
// Write datasets based on IDF version
if (idfVersion == 2) {
WriteIdfV2(sd_id);
} else {
std::cerr << "**ERROR** Unsupported IDF version for writing: " << idfVersion << std::endl;
SDend(sd_id);
return 1;
}
SDend(sd_id);
return 0;
}
//=============================================================================
// nxH4::PNeXus::WriteFileAttributes
//=============================================================================
void nxH4::PNeXus::WriteFileAttributes(int32 sd_id)
{
// Write HDF4 version
if (!fHdf4Version.empty()) {
SDsetattr(sd_id, "HDF4_version", DFNT_CHAR8,
fHdf4Version.length(), fHdf4Version.c_str());
}
// Write NeXus version
if (!fNeXusVersion.empty()) {
SDsetattr(sd_id, "NeXus_version", DFNT_CHAR8,
fNeXusVersion.length(), fNeXusVersion.c_str());
}
// Write file name
if (!fFileNameNxs.empty()) {
SDsetattr(sd_id, "file_name", DFNT_CHAR8,
fFileNameNxs.length(), fFileNameNxs.c_str());
}
// Write file time
if (!fFileTimeNxs.empty()) {
SDsetattr(sd_id, "file_time", DFNT_CHAR8,
fFileTimeNxs.length(), fFileTimeNxs.c_str());
}
// Write root level attributes from fGroupAttributes
if (fGroupAttributes.find("/") != fGroupAttributes.end()) {
const auto& attrs = fGroupAttributes.at("/");
for (const auto& [attr_name, attr_value] : attrs) {
// Handle different attribute types
try {
auto str_attr = std::any_cast<std::string>(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<PNXdata<int>>(data_any);
WriteIntDataset(sd_id, path, data);
continue;
} catch (...) {}
// Try to write as float dataset
try {
auto data = std::any_cast<PNXdata<float>>(data_any);
WriteFloatDataset(sd_id, path, data);
continue;
} catch (...) {}
// Try to write as string dataset
try {
auto data = std::any_cast<PNXdata<std::string>>(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<int>& data)
{
// Extract dataset name from path
std::vector<std::string> 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<int32>(dims.size());
std::vector<int32> dim_sizes(rank);
for (int32 i = 0; i < rank; i++) {
dim_sizes[i] = static_cast<int32>(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<int32> 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<float>& data)
{
// Extract dataset name from path
std::vector<std::string> 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<int32>(dims.size());
std::vector<int32> dim_sizes(rank);
for (int32 i = 0; i < rank; i++) {
dim_sizes[i] = static_cast<int32>(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<float32> 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<std::string>& data)
{
// Extract dataset name from path
std::vector<std::string> 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<int32>(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<char> 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 <typename T>
void nxH4::PNeXus::WriteDatasetAttributes(int32 sds_id, const PNXdata<T>& 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<int>(attr_value);
SDsetattr(sds_id, attr_name.c_str(), DFNT_INT32, 1, &value);
continue;
} catch (...) {}
// Try scalar float
try {
float32 value = std::any_cast<float>(attr_value);
SDsetattr(sds_id, attr_name.c_str(), DFNT_FLOAT32, 1, &value);
continue;
} catch (...) {}
// Try string
try {
std::string str = std::any_cast<std::string>(attr_value);
SDsetattr(sds_id, attr_name.c_str(), DFNT_CHAR8, str.length(), str.c_str());
continue;
} catch (...) {}
// Try vector<int> for multi-element attributes
try {
std::vector<int> int_vec = std::any_cast<std::vector<int>>(attr_value);
std::vector<int32> data32(int_vec.begin(), int_vec.end());
SDsetattr(sds_id, attr_name.c_str(), DFNT_INT32, data32.size(), data32.data());
continue;
} catch (...) {}
// Try vector<float> for multi-element attributes
try {
std::vector<float> float_vec = std::any_cast<std::vector<float>>(attr_value);
std::vector<float32> 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<std::string, std::any>& nxH4::PNeXus::GetGroupAttributes(
const std::string& groupPath) const
{
static std::map<std::string, std::any> 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<unsigned char>(a)) ==
std::tolower(static_cast<unsigned char>(b));
});
}
std::vector<std::string> nxH4::PNeXus::splitPath(const std::string& path)
{
std::vector<std::string> 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<std::string> 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<int32> 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<std::string, std::any> 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<PNXdata<int>>(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<int32_t>(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<int32_t>(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<int32_t>(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<PNXdata<float>>(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<PNXdata<int>>(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<PNXdata<int>>(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<std::string>(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<double> &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<fLgbBin; i++) {
nt = par[1]*exp(-(((double)i-(double)fT0Bin+0.5)*fTimeResolution)/tau_mu)+par[2]; // theory
if (nt <= 0) nt=1.0e-6;
nt = nt/(1.0+nt*par[0]); // dead time corrected
if (nt <= 1.0e-12) nt=1.0e-12;
nd = (double)fCounts[i+fIdx*fDims[2]]; // data
ll += nt - nd/(fGoodFrames*fTimeResolution) * log(nt);
}
return ll;
}
//=============================================================================
// nxH5::PNeXusDeadTime::minimize()
//=============================================================================
/**
* @brief Minimize dead time for a specific detector spectrum
*
* Performs ROOT Minuit2 minimization to find the optimal dead time parameter
* and count rate model parameters for the specified spectrum index. Uses
* MnMinimize with strategy 2 (high precision) and stores the result.
*
* **Minimization Parameters:**
* - dtc: Dead time correction (initial: 0.01, step: 0.01)
* - N0: Initial count rate (initial: estimated from data)
* - N_bkg: Background rate (initial: 0.1)
*
* @param i Spectrum index to minimize (0-based, must be < dims[1])
*
* @note Results are stored in fDeadTimeEstimated[i] and optionally printed
* to stdout if debug mode is enabled
*
* @see GetDeadTimeEstimated() to retrieve results after minimization
*/
void nxH5::PNeXusDeadTime::minimize(const int i)
{
fIdx = i;
double n0 = 1.07*(double)fCounts[fFgbBin+fIdx*fDims[2]];
ROOT::Minuit2::MnUserParameters params;
params.Add("dtc", 0.01, 0.01);
params.Add("N0", n0/fDims[2], 1.0);
params.Add("N_bkg", 0.1/fDims[2], 0.1);
// create minimizer object
ROOT::Minuit2::MnMinimize minimize(*this, params, ROOT::Minuit2::MnStrategy{2});
// minimize
// maxfcn is MINUIT2 Default maxfcn
unsigned int maxfcn = std::numeric_limits<unsigned int>::max();
double tolerance = 0.1;
ROOT::Minuit2::FunctionMinimum min = minimize(maxfcn, tolerance);
std::unique_ptr<ROOT::Minuit2::FunctionMinimum> 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<unsigned char>(a[i])) !=
std::tolower(static_cast<unsigned char>(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<std::string> nxH5::PNeXus::splitPath(const std::string& path)
{
std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<int>(file, path);
* @endcode
*/
std::string nxH5::PNeXus::findDatasetPath(H5::H5File& parent, const std::string& requestedPath)
{
std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> 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<PNXdata<std::string>>(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<std::string>(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<PNXdata<int>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<float>>(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<std::string>(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<PNXdata<float>>(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<std::string>(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<PNXdata<float>>(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<std::string>(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<std::string>(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<int>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<int>>(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<PNXdata<float>>("/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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<int>>(fDataMap["/run/histogram_data_1/counts"]);
// Check for attributes
if (counts_data.HasAttribute("units")) {
try {
auto units = std::any_cast<std::string>(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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<float>(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<noOfHistos; i++) {
if (i<9)
std::cout << std::endl << " " << i+1 << ": ";
else
std::cout << std::endl << " " << i+1 << ": ";
for (unsigned int j=0; j<first_good_bin+5; j++)
std::cout << data[i*histoLength+j] << ", ";
std::cout << "...";
}
} catch (const std::bad_any_cast& e) {
std::cerr << "Error: Failed to cast counts data" << std::endl;
}
if (fDataMap.find("/run/histogram_data_1/resolution") != fDataMap.end()) {
try {
auto int_data = std::any_cast<PNXdata<int>>(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<std::string>(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<PNXdata<int>>(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<std::string>(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>(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<PNXdata<float>>(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<int>(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<int>(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<std::string>(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<PNXdata<float>>(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<int>(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<std::string>(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<PNXdata<int>>(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>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<int>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<int>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<std::string>>(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<PNXdata<float>>(fDataMap["/raw_data_1/sample/temperature"]);
float temp = temp_ds.GetData()[0];
if (temp_ds.HasAttribute("units")) {
try {
auto units = std::any_cast<std::string>(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<PNXdata<float>>(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<std::string>(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<PNXdata<std::string>>(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<PNXdata<int>>(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<int>(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<std::string>(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<std::string>(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<int32_t>(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<int32_t>(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<int32_t>(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<PNXdata<int32_t>>(fDataMap["/raw_data_1/instrument/detector_1/resolution"]);
std::cout << " resolution : " << ivalData.GetData()[0];
if (ivalData.HasAttribute("units")) {
try {
auto units = std::any_cast<std::string>(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<dims[1]; i++) {
if (i<9)
std::cout << std::endl << " " << i+1 << ": ";
else
std::cout << std::endl << " " << i+1 << ": ";
for (unsigned int j=0; j<first_good_bin+5; j++)
std::cout << data[i*dims[2]+j] << ", ";
std::cout << "...";
}
} catch (const std::bad_any_cast& e) {
std::cerr << "Error: Failed to cast counts data" << std::endl;
}
std::cout << std::endl;
std::cout << std::endl << " raw_time:";
std::cout << std::endl;
try {
auto raw_time = std::any_cast<PNXdata<float>>(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<std::string>(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<first_good_bin+5; i++)
std::cout << data[i] << ", ";
std::cout << "...";
} catch (const std::bad_any_cast& e) {
std::cerr << "Error: Failed to cast raw_time data" << std::endl;
}
std::cout << std::endl;
std::cout << std::endl << " spectrum_index:";
std::cout << std::endl;
try {
auto spectrum_index = std::any_cast<PNXdata<int>>(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<first_good_bin+5; i++)
std::cout << data[i] << ", ";
std::cout << "...";
} catch (const std::bad_any_cast& e) {
std::cerr << "Error: Failed to cast spectrum_index data" << std::endl;
}
std::cout << std::endl;
std::cout << std::endl << " dead_time:";
std::cout << std::endl;
try {
auto dead_time = std::any_cast<PNXdata<float>>(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<first_good_bin+5; i++)
std::cout << data[i] << ", ";
std::cout << "...";
} catch (const std::bad_any_cast& e) {
std::cerr << "Error: Failed to cast spectrum_index data" << std::endl;
}
std::cout << std::endl;
}
}
//=============================================================================
// Read integer dataset and store in data map
//=============================================================================
/**
* Reads an integer 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::ReadIntDataset(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<hsize_t> 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<int> data(datatype);
data.SetDimensions(dims);
// Read data
std::vector<int> 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<hsize_t> 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<float> data(datatype);
data.SetDimensions(dims);
// Read data
std::vector<float> 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<hsize_t> 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<std::string> data(datatype);
data.SetDimensions(dims);
// Read data
std::vector<std::string> 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 <typename T>
void nxH5::PNeXus::ReadDatasetAttributes(H5::DataSet& dataset, PNXdata<T>& 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<int>&);
template void nxH5::PNeXus::ReadDatasetAttributes(H5::DataSet&, PNXdata<float>&);
template void nxH5::PNeXus::ReadDatasetAttributes(H5::DataSet&, PNXdata<std::string>&);
//=============================================================================
// 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<std::string> 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 <typename T>
void nxH5::PNeXus::WriteDatasetAttributes(H5::DataSet& dataset, const PNXdata<T>& data)
{
const auto& attributes = data.GetAttributes();
for (const auto& [attrName, attrValue] : attributes) {
try {
// Try int32_t
if (auto* intVal = std::any_cast<int32_t>(&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<float>(&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<std::string>(&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<int>& 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<float>& 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<std::string>& 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<const char*> 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<std::string, std::any>& attributes)
{
for (const auto& [attrName, attrValue] : attributes) {
try {
// Try int32_t
if (auto* intVal = std::any_cast<int32_t>(&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<float>(&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<std::string>(&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<int32_t>(&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<float>(&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<std::string>(&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<int>
try {
auto intData = std::any_cast<PNXdata<int>>(anyData);
WriteIntDataset(file, path, intData);
continue;
} catch (const std::bad_any_cast&) {}
// Try PNXdata<int32_t> - convert to int
try {
auto int32Data = std::any_cast<PNXdata<int32_t>>(anyData);
// Convert PNXdata<int32_t> to PNXdata<int>
PNXdata<int> intData(int32Data.GetDataType());
intData.SetDimensions(int32Data.GetDimensions());
std::vector<int> convertedData;
for (auto val : int32Data.GetData()) {
convertedData.push_back(static_cast<int>(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<float>
try {
auto floatData = std::any_cast<PNXdata<float>>(anyData);
WriteFloatDataset(file, path, floatData);
continue;
} catch (const std::bad_any_cast&) {}
// Try PNXdata<std::string>
try {
auto strData = std::any_cast<PNXdata<std::string>>(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<std::string, std::any>& nxH5::PNeXus::GetGroupAttributes(const std::string& groupPath) const
{
static const std::map<std::string, std::any> 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<int>&);
template void nxH5::PNeXus::WriteDatasetAttributes(H5::DataSet&, const PNXdata<float>&);
template void nxH5::PNeXus::WriteDatasetAttributes(H5::DataSet&, const PNXdata<std::string>&);