diff --git a/src/external/nexus/PNeXus.cpp b/src/external/nexus/PNeXus.cpp index 7678aad2..9e71cb98 100644 --- a/src/external/nexus/PNeXus.cpp +++ b/src/external/nexus/PNeXus.cpp @@ -1971,48 +1971,105 @@ void nxH4::PNeXus::Dump() //============================================================================= 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); if (sd_id == FAIL) { std::cerr << "**ERROR** Failed to create HDF4 file: " << filename << std::endl; return 1; } - // Write file attributes + // Write file-level attributes (NeXus_version, HDF4_version, etc.) WriteFileAttributes(sd_id); - // Write datasets based on IDF version - 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>(data_any); - WriteIntDataset(sd_id, path, data); - continue; - } catch (...) {} - - // Try to write as float dataset - try { - auto data = std::any_cast>(data_any); - WriteFloatDataset(sd_id, path, data); - continue; - } catch (...) {} - - // Try to write as string dataset - try { - auto data = std::any_cast>(data_any); - WriteStringDataset(sd_id, path, data); - continue; - } catch (...) {} - } - } else { + if ((idfVersion != 1) && (idfVersion != 2)) { std::cerr << "**ERROR** Unsupported IDF version for writing: " << idfVersion << std::endl; SDend(sd_id); return 1; } + // Write all SDS datasets and collect their references keyed by path + std::map sdsRefMap; + + for (const auto& [path, data_any] : fDataMap) { + int32 sds_ref = -1; + + // Try to write as int dataset + try { + auto data = std::any_cast>(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>(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>(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); + + // 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 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; } @@ -2064,7 +2121,7 @@ void nxH4::PNeXus::WriteFileAttributes(int32 sd_id) //============================================================================= // 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& data) { // Extract dataset name from path @@ -2101,13 +2158,18 @@ void nxH4::PNeXus::WriteIntDataset(int32 sd_id, const std::string& path, // Write attributes WriteDatasetAttributes(sds_id, data); + // Get the SDS reference for Vgroup linking + int32 sds_ref = SDidtoref(sds_id); + SDendaccess(sds_id); + + return sds_ref; } //============================================================================= // 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& data) { // Extract dataset name from path @@ -2144,13 +2206,18 @@ void nxH4::PNeXus::WriteFloatDataset(int32 sd_id, const std::string& path, // Write attributes WriteDatasetAttributes(sds_id, data); + // Get the SDS reference for Vgroup linking + int32 sds_ref = SDidtoref(sds_id); + SDendaccess(sds_id); + + return sds_ref; } //============================================================================= // 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& data) { // 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(); if (string_data.empty()) { - return; + return -1; } std::string str = string_data[0]; @@ -2185,7 +2252,12 @@ void nxH4::PNeXus::WriteStringDataset(int32 sd_id, const std::string& path, // Write attributes WriteDatasetAttributes(sds_id, data); + // Get the SDS reference for Vgroup linking + int32 sds_ref = SDidtoref(sds_id); + SDendaccess(sds_id); + + return sds_ref; } //============================================================================= @@ -2238,6 +2310,126 @@ void nxH4::PNeXus::WriteDatasetAttributes(int32 sds_id, const PNXdata& data) } } +//============================================================================= +// nxH4::PNeXus::WriteVGroupAttributes +//============================================================================= +void nxH4::PNeXus::WriteVGroupAttributes(int32 vgroup_id, + const std::map& attributes) +{ + for (const auto& [attr_name, attr_value] : attributes) { + // Try string + try { + std::string str = std::any_cast(attr_value); + Vsetattr(vgroup_id, attr_name.c_str(), DFNT_CHAR8, + static_cast(str.length()), str.c_str()); + continue; + } catch (...) {} + + // Try scalar int + try { + int32 value = std::any_cast(attr_value); + Vsetattr(vgroup_id, attr_name.c_str(), DFNT_INT32, 1, &value); + continue; + } catch (...) {} + + // Try scalar float + try { + float32 value = std::any_cast(attr_value); + Vsetattr(vgroup_id, attr_name.c_str(), DFNT_FLOAT32, 1, &value); + continue; + } catch (...) {} + + // Try vector + try { + std::vector int_vec = std::any_cast>(attr_value); + std::vector data32(int_vec.begin(), int_vec.end()); + Vsetattr(vgroup_id, attr_name.c_str(), DFNT_INT32, + static_cast(data32.size()), data32.data()); + continue; + } catch (...) {} + + // Try vector + try { + std::vector float_vec = std::any_cast>(attr_value); + std::vector data32(float_vec.begin(), float_vec.end()); + Vsetattr(vgroup_id, attr_name.c_str(), DFNT_FLOAT32, + static_cast(data32.size()), data32.data()); + continue; + } catch (...) {} + } +} + +//============================================================================= +// nxH4::PNeXus::CreateVGroupHierarchy +//============================================================================= +int32 nxH4::PNeXus::CreateVGroupHierarchy(int32 file_id, const std::string& path, + std::map& vgroupCache) +{ + std::vector 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(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 //============================================================================= diff --git a/src/external/nexus/PNeXus.h b/src/external/nexus/PNeXus.h index 6fbc4d95..54041911 100644 --- a/src/external/nexus/PNeXus.h +++ b/src/external/nexus/PNeXus.h @@ -944,43 +944,60 @@ private: void WriteDatasetAttributes(int32 sds_id, const PNXdata& data); /** - * @brief Write attributes to a group - * @param sd_id HDF4 SD interface ID - * @param groupPath Group path + * @brief Write attributes to a Vgroup + * @param vgroup_id HDF4 Vgroup ID to write attributes to * @param attributes Map of attribute names to values */ - void WriteGroupAttributes(int32 sd_id, const std::string& groupPath, - const std::map& attributes); + void WriteVGroupAttributes(int32 vgroup_id, + const std::map& attributes); /** - * @brief Write an integer dataset with attributes - * @param sd_id HDF4 SD interface ID - * @param path Dataset path - * @param data PNXdata object containing data, dimensions, and attributes - * @throws std::runtime_error if writing fails + * @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 */ - void WriteIntDataset(int32 sd_id, const std::string& path, - const PNXdata& data); + int32 CreateVGroupHierarchy(int32 file_id, const std::string& path, + std::map& vgroupCache); /** - * @brief Write a float dataset with attributes + * @brief Write an integer dataset with attributes and return its SDS reference * @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 + * @return SDS reference number for linking into Vgroups * @throws std::runtime_error if writing fails */ - void WriteFloatDataset(int32 sd_id, const std::string& path, - const PNXdata& data); + int32 WriteIntDataset(int32 sd_id, const std::string& path, + const PNXdata& data); /** - * @brief Write a string dataset with attributes + * @brief Write a float dataset with attributes and return its SDS reference * @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 + * @return SDS reference number for linking into Vgroups * @throws std::runtime_error if writing fails */ - void WriteStringDataset(int32 sd_id, const std::string& path, - const PNXdata& data); + int32 WriteFloatDataset(int32 sd_id, const std::string& path, + const PNXdata& data); + + /** + * @brief Write a string dataset with attributes and return its SDS reference + * @param sd_id HDF4 SD interface ID + * @param path Dataset path (leaf name used for SDS name) + * @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 + */ + int32 WriteStringDataset(int32 sd_id, const std::string& path, + const PNXdata& data); /** * @brief Write root-level file attributes