adopt HDF4 support for proper hierarchical writing of the data.

This commit is contained in:
2026-02-07 21:26:35 +01:00
parent 86ecfc84af
commit a6f5b956bf
2 changed files with 261 additions and 52 deletions

View File

@@ -1971,48 +1971,105 @@ void nxH4::PNeXus::Dump()
//============================================================================= //=============================================================================
int nxH4::PNeXus::WriteNexusFile(const std::string& filename, int idfVersion) int nxH4::PNeXus::WriteNexusFile(const std::string& filename, int idfVersion)
{ {
// Create new HDF4 file // Create new HDF4 file using SD interface (for datasets)
int32 sd_id = SDstart(filename.c_str(), DFACC_CREATE); int32 sd_id = SDstart(filename.c_str(), DFACC_CREATE);
if (sd_id == FAIL) { if (sd_id == FAIL) {
std::cerr << "**ERROR** Failed to create HDF4 file: " << filename << std::endl; std::cerr << "**ERROR** Failed to create HDF4 file: " << filename << std::endl;
return 1; return 1;
} }
// Write file attributes // Write file-level attributes (NeXus_version, HDF4_version, etc.)
WriteFileAttributes(sd_id); WriteFileAttributes(sd_id);
// Write datasets based on IDF version if ((idfVersion != 1) && (idfVersion != 2)) {
if ((idfVersion == 1) || (idfVersion == 2)) {
// 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 (...) {}
}
} else {
std::cerr << "**ERROR** Unsupported IDF version for writing: " << idfVersion << std::endl; std::cerr << "**ERROR** Unsupported IDF version for writing: " << idfVersion << std::endl;
SDend(sd_id); SDend(sd_id);
return 1; return 1;
} }
// Write all SDS datasets and collect their references keyed by path
std::map<std::string, int32> sdsRefMap;
for (const auto& [path, data_any] : fDataMap) {
int32 sds_ref = -1;
// Try to write as int dataset
try {
auto data = std::any_cast<PNXdata<int>>(data_any);
sds_ref = WriteIntDataset(sd_id, path, data);
} catch (...) {}
if (sds_ref == -1) {
// Try to write as float dataset
try {
auto data = std::any_cast<PNXdata<float>>(data_any);
sds_ref = WriteFloatDataset(sd_id, path, data);
} catch (...) {}
}
if (sds_ref == -1) {
// Try to write as string dataset
try {
auto data = std::any_cast<PNXdata<std::string>>(data_any);
sds_ref = WriteStringDataset(sd_id, path, data);
} catch (...) {}
}
if (sds_ref != -1) {
sdsRefMap[path] = sds_ref;
}
}
// Close SD interface before building Vgroup hierarchy
SDend(sd_id); SDend(sd_id);
// Now open the file with Hopen/Vstart to build the Vgroup hierarchy
int32 file_id = Hopen(filename.c_str(), DFACC_WRITE, 0);
if (file_id == FAIL) {
std::cerr << "**ERROR** Failed to reopen HDF4 file for Vgroup creation: " << filename << std::endl;
return 1;
}
if (Vstart(file_id) == FAIL) {
std::cerr << "**ERROR** Failed to initialize Vgroup interface" << std::endl;
Hclose(file_id);
return 1;
}
// Build Vgroup hierarchy and link SDS datasets into the correct groups
std::map<std::string, int32> vgroupCache; // path -> vgroup ref
for (const auto& [path, sds_ref] : sdsRefMap) {
// Extract parent path
size_t lastSlash = path.find_last_of('/');
if (lastSlash == std::string::npos || lastSlash == 0) {
// Dataset is at root level, no Vgroup needed
continue;
}
std::string parentPath = path.substr(0, lastSlash);
// Create the Vgroup hierarchy for the parent path
int32 parent_vg_ref = CreateVGroupHierarchy(file_id, parentPath, vgroupCache);
if (parent_vg_ref == -1) {
std::cerr << "**ERROR** Failed to create Vgroup hierarchy for: " << parentPath << std::endl;
continue;
}
// Attach to parent Vgroup and add the SDS dataset
int32 parent_vg_id = Vattach(file_id, parent_vg_ref, "w");
if (parent_vg_id != FAIL) {
Vaddtagref(parent_vg_id, DFTAG_NDG, sds_ref);
Vdetach(parent_vg_id);
}
}
// Detach all cached Vgroups (they were left attached for reuse)
// No — Vgroups were detached after creation in CreateVGroupHierarchy.
// We only re-attach briefly above to add tag/refs.
Vend(file_id);
Hclose(file_id);
return 0; return 0;
} }
@@ -2064,7 +2121,7 @@ void nxH4::PNeXus::WriteFileAttributes(int32 sd_id)
//============================================================================= //=============================================================================
// nxH4::PNeXus::WriteIntDataset // nxH4::PNeXus::WriteIntDataset
//============================================================================= //=============================================================================
void nxH4::PNeXus::WriteIntDataset(int32 sd_id, const std::string& path, int32 nxH4::PNeXus::WriteIntDataset(int32 sd_id, const std::string& path,
const PNXdata<int>& data) const PNXdata<int>& data)
{ {
// Extract dataset name from path // Extract dataset name from path
@@ -2101,13 +2158,18 @@ void nxH4::PNeXus::WriteIntDataset(int32 sd_id, const std::string& path,
// Write attributes // Write attributes
WriteDatasetAttributes(sds_id, data); WriteDatasetAttributes(sds_id, data);
// Get the SDS reference for Vgroup linking
int32 sds_ref = SDidtoref(sds_id);
SDendaccess(sds_id); SDendaccess(sds_id);
return sds_ref;
} }
//============================================================================= //=============================================================================
// nxH4::PNeXus::WriteFloatDataset // nxH4::PNeXus::WriteFloatDataset
//============================================================================= //=============================================================================
void nxH4::PNeXus::WriteFloatDataset(int32 sd_id, const std::string& path, int32 nxH4::PNeXus::WriteFloatDataset(int32 sd_id, const std::string& path,
const PNXdata<float>& data) const PNXdata<float>& data)
{ {
// Extract dataset name from path // Extract dataset name from path
@@ -2144,13 +2206,18 @@ void nxH4::PNeXus::WriteFloatDataset(int32 sd_id, const std::string& path,
// Write attributes // Write attributes
WriteDatasetAttributes(sds_id, data); WriteDatasetAttributes(sds_id, data);
// Get the SDS reference for Vgroup linking
int32 sds_ref = SDidtoref(sds_id);
SDendaccess(sds_id); SDendaccess(sds_id);
return sds_ref;
} }
//============================================================================= //=============================================================================
// nxH4::PNeXus::WriteStringDataset // nxH4::PNeXus::WriteStringDataset
//============================================================================= //=============================================================================
void nxH4::PNeXus::WriteStringDataset(int32 sd_id, const std::string& path, int32 nxH4::PNeXus::WriteStringDataset(int32 sd_id, const std::string& path,
const PNXdata<std::string>& data) const PNXdata<std::string>& data)
{ {
// Extract dataset name from path // Extract dataset name from path
@@ -2162,7 +2229,7 @@ void nxH4::PNeXus::WriteStringDataset(int32 sd_id, const std::string& path,
const auto& string_data = data.GetData(); const auto& string_data = data.GetData();
if (string_data.empty()) { if (string_data.empty()) {
return; return -1;
} }
std::string str = string_data[0]; std::string str = string_data[0];
@@ -2185,7 +2252,12 @@ void nxH4::PNeXus::WriteStringDataset(int32 sd_id, const std::string& path,
// Write attributes // Write attributes
WriteDatasetAttributes(sds_id, data); WriteDatasetAttributes(sds_id, data);
// Get the SDS reference for Vgroup linking
int32 sds_ref = SDidtoref(sds_id);
SDendaccess(sds_id); SDendaccess(sds_id);
return sds_ref;
} }
//============================================================================= //=============================================================================
@@ -2238,6 +2310,126 @@ void nxH4::PNeXus::WriteDatasetAttributes(int32 sds_id, const PNXdata<T>& data)
} }
} }
//=============================================================================
// nxH4::PNeXus::WriteVGroupAttributes
//=============================================================================
void nxH4::PNeXus::WriteVGroupAttributes(int32 vgroup_id,
const std::map<std::string, std::any>& attributes)
{
for (const auto& [attr_name, attr_value] : attributes) {
// Try string
try {
std::string str = std::any_cast<std::string>(attr_value);
Vsetattr(vgroup_id, attr_name.c_str(), DFNT_CHAR8,
static_cast<int32>(str.length()), str.c_str());
continue;
} catch (...) {}
// Try scalar int
try {
int32 value = std::any_cast<int>(attr_value);
Vsetattr(vgroup_id, attr_name.c_str(), DFNT_INT32, 1, &value);
continue;
} catch (...) {}
// Try scalar float
try {
float32 value = std::any_cast<float>(attr_value);
Vsetattr(vgroup_id, attr_name.c_str(), DFNT_FLOAT32, 1, &value);
continue;
} catch (...) {}
// Try vector<int>
try {
std::vector<int> int_vec = std::any_cast<std::vector<int>>(attr_value);
std::vector<int32> data32(int_vec.begin(), int_vec.end());
Vsetattr(vgroup_id, attr_name.c_str(), DFNT_INT32,
static_cast<int32>(data32.size()), data32.data());
continue;
} catch (...) {}
// Try vector<float>
try {
std::vector<float> float_vec = std::any_cast<std::vector<float>>(attr_value);
std::vector<float32> data32(float_vec.begin(), float_vec.end());
Vsetattr(vgroup_id, attr_name.c_str(), DFNT_FLOAT32,
static_cast<int32>(data32.size()), data32.data());
continue;
} catch (...) {}
}
}
//=============================================================================
// nxH4::PNeXus::CreateVGroupHierarchy
//=============================================================================
int32 nxH4::PNeXus::CreateVGroupHierarchy(int32 file_id, const std::string& path,
std::map<std::string, int32>& vgroupCache)
{
std::vector<std::string> components = splitPath(path);
if (components.empty()) {
return -1;
}
std::string currentPath;
int32 parent_vg_ref = -1;
for (const auto& component : components) {
if (component.empty()) continue;
currentPath += "/" + component;
// Check if this Vgroup was already created
auto it = vgroupCache.find(currentPath);
if (it != vgroupCache.end()) {
parent_vg_ref = it->second;
continue;
}
// Create a new Vgroup
int32 vg_id = Vattach(file_id, -1, "w");
if (vg_id == FAIL) {
std::cerr << "**ERROR** Failed to create Vgroup for: " << currentPath << std::endl;
return -1;
}
Vsetname(vg_id, component.c_str());
// Write group attributes if any exist for this path
auto attrIt = fGroupAttributes.find(currentPath);
if (attrIt != fGroupAttributes.end()) {
// Check for NX_class attribute and set it as Vgroup class
auto classIt = attrIt->second.find("NX_class");
if (classIt != attrIt->second.end()) {
try {
std::string nxClass = std::any_cast<std::string>(classIt->second);
Vsetclass(vg_id, nxClass.c_str());
} catch (...) {}
}
WriteVGroupAttributes(vg_id, attrIt->second);
}
// Get the reference of the newly created Vgroup
int32 vg_ref = VQueryref(vg_id);
// If there is a parent Vgroup, add this one as a child
if (parent_vg_ref != -1) {
int32 parent_vg_id = Vattach(file_id, parent_vg_ref, "w");
if (parent_vg_id != FAIL) {
Vinsert(parent_vg_id, vg_id);
Vdetach(parent_vg_id);
}
}
Vdetach(vg_id);
// Cache the reference for this path
vgroupCache[currentPath] = vg_ref;
parent_vg_ref = vg_ref;
}
return parent_vg_ref;
}
//============================================================================= //=============================================================================
// Group attribute methods - manage attributes associated with HDF4 groups // Group attribute methods - manage attributes associated with HDF4 groups
//============================================================================= //=============================================================================

View File

@@ -944,42 +944,59 @@ private:
void WriteDatasetAttributes(int32 sds_id, const PNXdata<T>& data); void WriteDatasetAttributes(int32 sds_id, const PNXdata<T>& data);
/** /**
* @brief Write attributes to a group * @brief Write attributes to a Vgroup
* @param sd_id HDF4 SD interface ID * @param vgroup_id HDF4 Vgroup ID to write attributes to
* @param groupPath Group path
* @param attributes Map of attribute names to values * @param attributes Map of attribute names to values
*/ */
void WriteGroupAttributes(int32 sd_id, const std::string& groupPath, void WriteVGroupAttributes(int32 vgroup_id,
const std::map<std::string, std::any>& attributes); const std::map<std::string, std::any>& attributes);
/** /**
* @brief Write an integer dataset with attributes * @brief Create the Vgroup hierarchy for a given path
*
* Given a path like "/run/sample", creates Vgroups "run" and "sample"
* (nested), writing any associated group attributes from fGroupAttributes.
* Uses a cache to avoid creating duplicate Vgroups.
*
* @param file_id HDF4 file ID (from Hopen)
* @param path The group path to create (e.g., "/run/sample")
* @param vgroupCache Map of already-created group paths to their Vgroup references
* @return The Vgroup reference of the deepest group in the path
*/
int32 CreateVGroupHierarchy(int32 file_id, const std::string& path,
std::map<std::string, int32>& vgroupCache);
/**
* @brief Write an integer dataset with attributes and return its SDS reference
* @param sd_id HDF4 SD interface ID * @param sd_id HDF4 SD interface ID
* @param path Dataset path * @param path Dataset path (leaf name used for SDS name)
* @param data PNXdata object containing data, dimensions, and attributes * @param data PNXdata object containing data, dimensions, and attributes
* @return SDS reference number for linking into Vgroups
* @throws std::runtime_error if writing fails * @throws std::runtime_error if writing fails
*/ */
void WriteIntDataset(int32 sd_id, const std::string& path, int32 WriteIntDataset(int32 sd_id, const std::string& path,
const PNXdata<int>& data); const PNXdata<int>& data);
/** /**
* @brief Write a float dataset with attributes * @brief Write a float dataset with attributes and return its SDS reference
* @param sd_id HDF4 SD interface ID * @param sd_id HDF4 SD interface ID
* @param path Dataset path * @param path Dataset path (leaf name used for SDS name)
* @param data PNXdata object containing data, dimensions, and attributes * @param data PNXdata object containing data, dimensions, and attributes
* @return SDS reference number for linking into Vgroups
* @throws std::runtime_error if writing fails * @throws std::runtime_error if writing fails
*/ */
void WriteFloatDataset(int32 sd_id, const std::string& path, int32 WriteFloatDataset(int32 sd_id, const std::string& path,
const PNXdata<float>& data); const PNXdata<float>& data);
/** /**
* @brief Write a string dataset with attributes * @brief Write a string dataset with attributes and return its SDS reference
* @param sd_id HDF4 SD interface ID * @param sd_id HDF4 SD interface ID
* @param path Dataset path * @param path Dataset path (leaf name used for SDS name)
* @param data PNXdata object containing data, dimensions, and attributes * @param data PNXdata object containing data, dimensions, and attributes
* @return SDS reference number for linking into Vgroups, or -1 for empty strings
* @throws std::runtime_error if writing fails * @throws std::runtime_error if writing fails
*/ */
void WriteStringDataset(int32 sd_id, const std::string& path, int32 WriteStringDataset(int32 sd_id, const std::string& path,
const PNXdata<std::string>& data); const PNXdata<std::string>& data);
/** /**