From d39fdf9d9dd439d95a6629a2d7aea2b0a2105f9f Mon Sep 17 00:00:00 2001 From: adriazalvarez Date: Tue, 7 Jan 2025 11:02:18 +0000 Subject: [PATCH 1/9] Remove helper and main exec structure out of inherited saveNXCanSass, inherit from saveNXcanSASBase --- Framework/DataHandling/CMakeLists.txt | 2 + .../inc/MantidDataHandling/SaveNXcanSAS.h | 5 +- Framework/DataHandling/src/SaveNXcanSAS.cpp | 838 +----------------- 3 files changed, 11 insertions(+), 834 deletions(-) diff --git a/Framework/DataHandling/CMakeLists.txt b/Framework/DataHandling/CMakeLists.txt index 7915bf588518..52b65ec306cd 100644 --- a/Framework/DataHandling/CMakeLists.txt +++ b/Framework/DataHandling/CMakeLists.txt @@ -184,6 +184,7 @@ set(SRC_FILES src/SaveNXSPE.cpp src/SaveNXTomo.cpp src/SaveNXcanSAS.cpp + src/SaveNXCanSASBase.cpp src/SaveNexus.cpp src/SaveNexusESS.cpp src/SaveNexusGeometry.cpp @@ -401,6 +402,7 @@ set(INC_FILES inc/MantidDataHandling/SaveNXSPE.h inc/MantidDataHandling/SaveNXTomo.h inc/MantidDataHandling/SaveNXcanSAS.h + inc/MantidDataHandling/SaveNXcanSASBase.h inc/MantidDataHandling/SaveNexus.h inc/MantidDataHandling/SaveNexusESS.h inc/MantidDataHandling/SaveNexusGeometry.h diff --git a/Framework/DataHandling/inc/MantidDataHandling/SaveNXcanSAS.h b/Framework/DataHandling/inc/MantidDataHandling/SaveNXcanSAS.h index 8512c4c74a55..55e79ff28e63 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/SaveNXcanSAS.h +++ b/Framework/DataHandling/inc/MantidDataHandling/SaveNXcanSAS.h @@ -8,6 +8,7 @@ #include "MantidAPI/Algorithm.h" #include "MantidDataHandling/DllConfig.h" +#include "MantidDataHandling/SaveNXcanSASBase.h" namespace Mantid { namespace DataHandling { @@ -15,7 +16,7 @@ namespace DataHandling { /** SaveNXcanSAS : Saves a reduced workspace in the NXcanSAS format. Currently * only MatrixWorkspaces resulting from 1D and 2D reductions are supported. */ -class MANTID_DATAHANDLING_DLL SaveNXcanSAS final : public API::Algorithm { +class MANTID_DATAHANDLING_DLL SaveNXcanSAS final : public SaveNXcanSASBase { public: /// Constructor SaveNXcanSAS(); @@ -42,7 +43,5 @@ class MANTID_DATAHANDLING_DLL SaveNXcanSAS final : public API::Algorithm { void exec() override; }; -std::string MANTID_DATAHANDLING_DLL makeCanSASRelaxedName(const std::string &input); - } // namespace DataHandling } // namespace Mantid diff --git a/Framework/DataHandling/src/SaveNXcanSAS.cpp b/Framework/DataHandling/src/SaveNXcanSAS.cpp index 847d1260d2ab..410509d181a3 100644 --- a/Framework/DataHandling/src/SaveNXcanSAS.cpp +++ b/Framework/DataHandling/src/SaveNXcanSAS.cpp @@ -43,679 +43,6 @@ using namespace Mantid::Geometry; using namespace Mantid::API; using namespace Mantid::DataHandling::NXcanSAS; -namespace { - -enum class StoreType { Qx, Qy, I, Idev, Other }; - -bool isCanSASCompliant(bool isStrict, const std::string &input) { - auto baseRegex = isStrict ? boost::regex("[a-z_][a-z0-9_]*") : boost::regex("[A-Za-z_][\\w_]*"); - return boost::regex_match(input, baseRegex); -} - -void removeSpecialCharacters(std::string &input) { - boost::regex toReplace("[-\\.]"); - std::string replaceWith("_"); - input = boost::regex_replace(input, toReplace, replaceWith); -} - -std::string makeCompliantName(const std::string &input, bool isStrict, - const std::function &captializeStrategy) { - auto output = input; - // Check if input is compliant - if (!isCanSASCompliant(isStrict, output)) { - removeSpecialCharacters(output); - captializeStrategy(output); - // Check if the changes have made it compliant - if (!isCanSASCompliant(isStrict, output)) { - std::string message = "SaveNXcanSAS: The input " + input + "is not compliant with the NXcanSAS format."; - throw std::runtime_error(message); - } - } - return output; -} - -template -void writeArray1DWithStrAttributes(H5::Group &group, const std::string &dataSetName, const std::vector &values, - const std::map &attributes) { - Mantid::NeXus::H5Util::writeArray1D(group, dataSetName, values); - auto dataSet = group.openDataSet(dataSetName); - for (const auto &attribute : attributes) { - Mantid::NeXus::H5Util::writeStrAttribute(dataSet, attribute.first, attribute.second); - } -} - -H5::DSetCreatPropList setCompression2D(const hsize_t *chunkDims, const int deflateLevel = 6) { - H5::DSetCreatPropList propList; - const int rank = 2; - propList.setChunk(rank, chunkDims); - propList.setDeflate(deflateLevel); - return propList; -} - -template -void write2DWorkspace(H5::Group &group, Mantid::API::MatrixWorkspace_sptr workspace, const std::string &dataSetName, - Functor func, const std::map &attributes) { - using namespace Mantid::NeXus::H5Util; - - // Set the dimension - const size_t dimension0 = workspace->getNumberHistograms(); - const size_t dimension1 = workspace->y(0).size(); - const hsize_t rank = 2; - hsize_t dimensionArray[rank] = {static_cast(dimension0), static_cast(dimension1)}; - - // Start position in the 2D data (indexed) data structure - hsize_t start[rank] = {0, 0}; - - // Size of a slab - hsize_t sizeOfSingleSlab[rank] = {1, dimensionArray[1]}; - - // Get the Data Space definition for the 2D Data Set in the file - auto fileSpace = H5::DataSpace(rank, dimensionArray); - H5::DataType dataType(getType()); - - // Get the proplist with compression settings - H5::DSetCreatPropList propList = setCompression2D(sizeOfSingleSlab); - - // Create the data set - auto dataSet = group.createDataSet(dataSetName, dataType, fileSpace, propList); - - // Create Data Spae for 1D entry for each row in memory - hsize_t memSpaceDimension[1] = {dimension1}; - H5::DataSpace memSpace(1, memSpaceDimension); - - // Insert each row of the workspace as a slab - for (unsigned int index = 0; index < dimension0; ++index) { - // Need the data space - fileSpace.selectHyperslab(H5S_SELECT_SET, sizeOfSingleSlab, start); - - // Write the correct data set to file - dataSet.write(func(workspace, index), dataType, memSpace, fileSpace); - // Step up the write position - ++start[0]; - } - - // Add attributes to data set - for (const auto &attribute : attributes) { - writeStrAttribute(dataSet, attribute.first, attribute.second); - } -} - -std::vector splitDetectorNames(std::string detectorNames) { - const std::string delimiter = ","; - std::vector detectors; - size_t pos(0); - - while ((pos = detectorNames.find(delimiter)) != std::string::npos) { - std::string detectorName = detectorNames.substr(0, pos); - boost::algorithm::trim(detectorName); - detectors.emplace_back(detectorName); - detectorNames.erase(0, pos + delimiter.length()); - } - // Push remaining element - boost::algorithm::trim(detectorNames); - detectors.emplace_back(detectorNames); - return detectors; -} - -//------- SASentry -/** - * Add the sasEntry to the sasroot. - * @param file: Handle to the NXcanSAS file - * @param workspace: the workspace to store - * @return the sasEntry - */ -H5::Group addSasEntry(H5::H5File &file, const Mantid::API::MatrixWorkspace_sptr &workspace, const std::string &suffix) { - using namespace Mantid::DataHandling::NXcanSAS; - const std::string sasEntryName = sasEntryGroupName + suffix; - auto sasEntry = Mantid::NeXus::H5Util::createGroupCanSAS(file, sasEntryName, nxEntryClassAttr, sasEntryClassAttr); - - // Add version - Mantid::NeXus::H5Util::writeStrAttribute(sasEntry, sasEntryVersionAttr, sasEntryVersionAttrValue); - - // Add definition - Mantid::NeXus::H5Util::write(sasEntry, sasEntryDefinition, sasEntryDefinitionFormat); - - // Add title - auto workspaceTitle = workspace->getTitle(); - Mantid::NeXus::H5Util::write(sasEntry, sasEntryTitle, workspaceTitle); - - // Add run - const auto runNumber = workspace->getRunNumber(); - Mantid::NeXus::H5Util::write(sasEntry, sasEntryRun, std::to_string(runNumber)); - - return sasEntry; -} - -//------- SASinstrument -std::string getInstrumentName(const Mantid::API::MatrixWorkspace_sptr &workspace) { - auto instrument = workspace->getInstrument(); - return instrument->getFullName(); -} - -std::string getIDF(const Mantid::API::MatrixWorkspace_sptr &workspace) { - auto date = workspace->getWorkspaceStartDate(); - auto instrumentName = getInstrumentName(workspace); - return InstrumentFileFinder::getInstrumentFilename(instrumentName, date); -} - -void addDetectors(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace, - const std::vector &detectorNames) { - // If the group is empty then don't add anything - if (!detectorNames.empty()) { - for (const auto &detectorName : detectorNames) { - if (detectorName.empty()) { - continue; - } - - std::string sasDetectorName = sasInstrumentDetectorGroupName + detectorName; - sasDetectorName = Mantid::DataHandling::makeCanSASRelaxedName(sasDetectorName); - - auto instrument = workspace->getInstrument(); - auto component = instrument->getComponentByName(detectorName); - - if (component) { - const auto sample = instrument->getSample(); - const auto distance = component->getDistance(*sample); - std::map sddAttributes; - sddAttributes.insert(std::make_pair(sasUnitAttr, sasInstrumentDetectorSddUnitAttrValue)); - auto detector = Mantid::NeXus::H5Util::createGroupCanSAS(group, sasDetectorName, nxInstrumentDetectorClassAttr, - sasInstrumentDetectorClassAttr); - Mantid::NeXus::H5Util::write(detector, sasInstrumentDetectorName, detectorName); - Mantid::NeXus::H5Util::writeScalarDataSetWithStrAttributes(detector, sasInstrumentDetectorSdd, distance, - sddAttributes); - } - } - } -} - -/** - * Add the instrument group to the NXcanSAS file. This adds the - * instrument name and the IDF - * @param group: the sasEntry - * @param workspace: the workspace which is being stored - * @param radiationSource: the selcted radiation source - * @param detectorNames: the names of the detectors to store - */ -void addInstrument(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace, - const std::string &radiationSource, const std::string &geometry, double beamHeight, double beamWidth, - const std::vector &detectorNames) { - // Setup instrument - const std::string sasInstrumentNameForGroup = sasInstrumentGroupName; - auto instrument = Mantid::NeXus::H5Util::createGroupCanSAS(group, sasInstrumentNameForGroup, nxInstrumentClassAttr, - sasInstrumentClassAttr); - auto instrumentName = getInstrumentName(workspace); - Mantid::NeXus::H5Util::write(instrument, sasInstrumentName, instrumentName); - - // Setup the detector - addDetectors(instrument, workspace, detectorNames); - - // Setup source - const std::string sasSourceName = sasInstrumentSourceGroupName; - auto source = Mantid::NeXus::H5Util::createGroupCanSAS(instrument, sasSourceName, nxInstrumentSourceClassAttr, - sasInstrumentSourceClassAttr); - Mantid::NeXus::H5Util::write(source, sasInstrumentSourceRadiation, radiationSource); - - // Setup Aperture - const std::string sasApertureName = sasInstrumentApertureGroupName; - auto aperture = Mantid::NeXus::H5Util::createGroupCanSAS(instrument, sasApertureName, nxInstrumentApertureClassAttr, - sasInstrumentApertureClassAttr); - - Mantid::NeXus::H5Util::write(aperture, sasInstrumentApertureShape, geometry); - - std::map beamSizeAttrs; - beamSizeAttrs.insert(std::make_pair(sasUnitAttr, sasBeamAndSampleSizeUnitAttrValue)); - if (beamHeight != 0) { - Mantid::NeXus::H5Util::writeScalarDataSetWithStrAttributes(aperture, sasInstrumentApertureGapHeight, beamHeight, - beamSizeAttrs); - } - if (beamWidth != 0) { - Mantid::NeXus::H5Util::writeScalarDataSetWithStrAttributes(aperture, sasInstrumentApertureGapWidth, beamWidth, - beamSizeAttrs); - } - - // Add IDF information - auto idf = getIDF(workspace); - Mantid::NeXus::H5Util::write(instrument, sasInstrumentIDF, idf); -} - -//------- SASsample - -void addSample(H5::Group &group, const double &sampleThickness) { - if (sampleThickness == 0) { - return; - } - std::string const sasSampleNameForGroup = sasInstrumentSampleGroupAttr; - - auto sample = Mantid::NeXus::H5Util::createGroupCanSAS(group, sasSampleNameForGroup, nxInstrumentSampleClassAttr, - sasInstrumentSampleClassAttr); - - std::map sampleThicknessAttrs; - sampleThicknessAttrs.insert(std::make_pair(sasUnitAttr, sasBeamAndSampleSizeUnitAttrValue)); - Mantid::NeXus::H5Util::writeScalarDataSetWithStrAttributes(sample, sasInstrumentSampleThickness, sampleThickness, - sampleThicknessAttrs); -} - -//------- SASprocess - -std::string getDate() { - time_t rawtime; - time(&rawtime); - char temp[25]; - strftime(temp, 25, "%Y-%m-%dT%H:%M:%S", localtime(&rawtime)); - std::string sasDate(temp); - return sasDate; -} - -/** Write a property value to the H5 file if the property exists in the run - * - * @param run : the run to look for the property in - * @param propertyName : the name of the property to find - * @param sasGroup : the group to add the term into in the output file - * @param sasTerm : the name of the term to add - */ -void addPropertyFromRunIfExists(Run const &run, std::string const &propertyName, H5::Group &sasGroup, - std::string const &sasTerm) { - if (run.hasProperty(propertyName)) { - const auto *property = run.getProperty(propertyName); - Mantid::NeXus::H5Util::write(sasGroup, sasTerm, property->value()); - } -} - -/** - * Add the process information to the NXcanSAS file. This information - * about the run number, the Mantid version and the user file (if available) - * @param group: the sasEntry - * @param workspace: the workspace which is being stored - */ -void addProcess(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace) { - // Setup process - const std::string sasProcessNameForGroup = sasProcessGroupName; - auto process = - Mantid::NeXus::H5Util::createGroupCanSAS(group, sasProcessNameForGroup, nxProcessClassAttr, sasProcessClassAttr); - - // Add name - Mantid::NeXus::H5Util::write(process, sasProcessName, sasProcessNameValue); - - // Add creation date of the file - auto date = getDate(); - Mantid::NeXus::H5Util::write(process, sasProcessDate, date); - - // Add Mantid version - const auto version = std::string(MantidVersion::version()); - Mantid::NeXus::H5Util::write(process, sasProcessTermSvn, version); - - // Add log values - const auto run = workspace->run(); - addPropertyFromRunIfExists(run, sasProcessUserFileInLogs, process, sasProcessTermUserFile); - addPropertyFromRunIfExists(run, sasProcessBatchFileInLogs, process, sasProcessTermBatchFile); -} - -/** - * Add the process information to the NXcanSAS file. This information - * about the run number, the Mantid version and the user file (if available) - * @param group: the sasEntry - * @param workspace: the workspace which is being stored - */ -void addProcess(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace, - const Mantid::API::MatrixWorkspace_sptr &canWorkspace) { - // Setup process - const std::string sasProcessNameForGroup = sasProcessGroupName; - auto process = - Mantid::NeXus::H5Util::createGroupCanSAS(group, sasProcessNameForGroup, nxProcessClassAttr, sasProcessClassAttr); - - // Add name - Mantid::NeXus::H5Util::write(process, sasProcessName, sasProcessNameValue); - - // Add creation date of the file - auto date = getDate(); - Mantid::NeXus::H5Util::write(process, sasProcessDate, date); - - // Add Mantid version - const auto version = std::string(MantidVersion::version()); - Mantid::NeXus::H5Util::write(process, sasProcessTermSvn, version); - - const auto run = workspace->run(); - addPropertyFromRunIfExists(run, sasProcessUserFileInLogs, process, sasProcessTermUserFile); - addPropertyFromRunIfExists(run, sasProcessBatchFileInLogs, process, sasProcessTermBatchFile); - - // Add can run number - const auto canRun = canWorkspace->getRunNumber(); - Mantid::NeXus::H5Util::write(process, sasProcessTermCan, std::to_string(canRun)); -} - -/** - * Add an entry to the process group. - * @param group: the sasEntry - * @param entryName: string containing the name of the value to save - * @param entryValue: string containing the value to save - */ -void addProcessEntry(H5::Group &group, const std::string &entryName, const std::string &entryValue) { - auto process = group.openGroup(sasProcessGroupName); - // Populate process entry - Mantid::NeXus::H5Util::write(process, entryName, entryValue); -} - -WorkspaceDimensionality getWorkspaceDimensionality(const Mantid::API::MatrixWorkspace_sptr &workspace) { - auto numberOfHistograms = workspace->getNumberHistograms(); - WorkspaceDimensionality dimensionality(WorkspaceDimensionality::other); - if (numberOfHistograms == 1) { - dimensionality = WorkspaceDimensionality::oneD; - } else if (numberOfHistograms > 1) { - dimensionality = WorkspaceDimensionality::twoD; - } - return dimensionality; -} - -//------- SASdata - -std::string getIntensityUnitLabel(const std::string &intensityUnitLabel) { - if (intensityUnitLabel == "I(q) (cm-1)") { - return sasIntensity; - } else { - return intensityUnitLabel; - } -} - -std::string getIntensityUnit(const Mantid::API::MatrixWorkspace_sptr &workspace) { - auto iUnit = workspace->YUnit(); - if (iUnit.empty()) { - iUnit = workspace->YUnitLabel(); - } - return iUnit; -} - -std::string getMomentumTransferLabel(const std::string &momentumTransferLabel) { - if (momentumTransferLabel == "Angstrom^-1") { - return sasMomentumTransfer; - } else { - return momentumTransferLabel; - } -} - -std::string getUnitFromMDDimension(const Mantid::Geometry::IMDDimension_const_sptr &dimension) { - const auto unitLabel = dimension->getMDUnits().getUnitLabel(); - return unitLabel.ascii(); -} - -void addData1D(H5::Group &data, const Mantid::API::MatrixWorkspace_sptr &workspace) { - // Add attributes for @signal, @I_axes, @Q_indices, - Mantid::NeXus::H5Util::writeStrAttribute(data, sasSignal, sasDataI); - Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIAxesAttr, sasDataQ); - Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIUncertaintyAttr, sasDataIdev); - Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIUncertaintiesAttr, sasDataIdev); - Mantid::NeXus::H5Util::writeNumAttribute(data, sasDataQIndicesAttr, std::vector{0}); - - if (workspace->hasDx(0)) { - Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataQUncertaintyAttr, sasDataQdev); - Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataQUncertaintiesAttr, sasDataQdev); - } - - //----------------------------------------- - // Add Q with units + uncertainty definition - const auto &qValue = workspace->points(0); - std::map qAttributes; - auto qUnit = getUnitFromMDDimension(workspace->getDimension(0)); - qUnit = getMomentumTransferLabel(qUnit); - qAttributes.emplace(sasUnitAttr, qUnit); - if (workspace->hasDx(0)) { - qAttributes.emplace(sasUncertaintyAttr, sasDataQdev); - qAttributes.emplace(sasUncertaintiesAttr, sasDataQdev); - } - - writeArray1DWithStrAttributes(data, sasDataQ, qValue.rawData(), qAttributes); - - //----------------------------------------- - // Add I with units + uncertainty definition - const auto &intensity = workspace->y(0); - std::map iAttributes; - auto iUnit = getIntensityUnit(workspace); - iUnit = getIntensityUnitLabel(iUnit); - iAttributes.emplace(sasUnitAttr, iUnit); - iAttributes.emplace(sasUncertaintyAttr, sasDataIdev); - iAttributes.emplace(sasUncertaintiesAttr, sasDataIdev); - - writeArray1DWithStrAttributes(data, sasDataI, intensity.rawData(), iAttributes); - - //----------------------------------------- - // Add Idev with units - const auto &intensityUncertainty = workspace->e(0); - std::map eAttributes; - eAttributes.insert(std::make_pair(sasUnitAttr, iUnit)); // same units as intensity - - writeArray1DWithStrAttributes(data, sasDataIdev, intensityUncertainty.rawData(), eAttributes); - - //----------------------------------------- - // Add Qdev with units if available - if (workspace->hasDx(0)) { - const auto qResolution = workspace->pointStandardDeviations(0); - std::map xUncertaintyAttributes; - xUncertaintyAttributes.emplace(sasUnitAttr, qUnit); - - writeArray1DWithStrAttributes(data, sasDataQdev, qResolution.rawData(), xUncertaintyAttributes); - } -} - -bool areAxesNumeric(const Mantid::API::MatrixWorkspace_sptr &workspace) { - const std::array indices = {0, 1}; - return std::all_of(indices.cbegin(), indices.cend(), - [workspace](auto const &index) { return workspace->getAxis(index)->isNumeric(); }); -} - -class SpectrumAxisValueProvider { -public: - explicit SpectrumAxisValueProvider(Mantid::API::MatrixWorkspace_sptr workspace) : m_workspace(std::move(workspace)) { - setSpectrumAxisValues(); - } - - Mantid::MantidVec::value_type *operator()(const Mantid::API::MatrixWorkspace_sptr & /*unused*/, int index) { - auto isPointData = m_workspace->getNumberHistograms() == m_spectrumAxisValues.size(); - double value = 0; - if (isPointData) { - value = m_spectrumAxisValues[index]; - } else { - value = (m_spectrumAxisValues[index + 1] + m_spectrumAxisValues[index]) / 2.0; - } - - Mantid::MantidVec tempVec(m_workspace->dataY(index).size(), value); - m_currentAxisValues.swap(tempVec); - return m_currentAxisValues.data(); - } - -private: - void setSpectrumAxisValues() { - auto sAxis = m_workspace->getAxis(1); - for (size_t index = 0; index < sAxis->length(); ++index) { - m_spectrumAxisValues.emplace_back((*sAxis)(index)); - } - } - - Mantid::API::MatrixWorkspace_sptr m_workspace; - Mantid::MantidVec m_spectrumAxisValues; - Mantid::MantidVec m_currentAxisValues; -}; - -/** - * QxExtractor functor which allows us to convert 2D Qx data into point data. - */ -template class QxExtractor { -public: - T *operator()(const Mantid::API::MatrixWorkspace_sptr &ws, int index) { - if (ws->isHistogramData()) { - qxPointData.clear(); - Mantid::Kernel::VectorHelper::convertToBinCentre(ws->dataX(index), qxPointData); - return qxPointData.data(); - } else { - return ws->dataX(index).data(); - } - } - - std::vector qxPointData; -}; - -/** - * Stores the 2D data in the HDF5 file. Qx and Qy values need to be stored as a - *meshgrid. - * They should be stored as point data. - * @param data: the hdf5 group - * @param workspace: the workspace to store - * - * Workspace looks like this in Mantid Matrix - * (Qx) 0 1 2 ... M (first dimension) - * (QY) - * 0 IQx0Qy0 IQx1Qy0 IQx2Qy0 ... IQxMQy0 - * 1 IQx0Qy1 IQx1Qy1 IQx2Qy1 ... IQxMQy1 - * 2 IQx0Qy2 IQx1Qy2 IQx2Qy2 ... IQxMQy2 - * 3 IQx0Qy3 IQx1Qy3 IQx2Qy3 ... IQxMQy3 - * . - * . - * N IQx0QyN IQx1QyN IQx2QyN ... IQxMQyN - * (second dimension) - * - * The layout below is how it would look like in the HDFView, ie vertical axis - * is first dimension. We map the Mantid Matrix layout 1-to-1. Note that this - * will swap the matrix indices, but this is how it is done in the other - *2Dloaders - * - * In HDF5 the Qx would need to be stored as: - * Qx1 Qx2 ... QxM - * Qx1 Qx2 ... QxM - * Qx1 Qx2 ... QxM - * . - * . - * Qx1 Qx2 ... QxM - * - * In HDF5 the Qy would need to be stored as: - * Qy1 Qy1 ... Qy1 - * Qy2 Qy2 ... Qy2 - * Qy3 Qy3 ... Qy3 - * . - * . - * QxN QxN ... QxN - */ -void addData2D(H5::Group &data, const Mantid::API::MatrixWorkspace_sptr &workspace) { - if (!areAxesNumeric(workspace)) { - throw std::invalid_argument("SaveNXcanSAS: The provided 2D workspace needs to have 2 numeric axes."); - } - // Add attributes for @signal, @I_axes, @Q_indices, - Mantid::NeXus::H5Util::writeStrAttribute(data, sasSignal, sasDataI); - const std::string sasDataIAxesAttr2D = sasDataQ + sasSeparator + sasDataQ; - Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIAxesAttr, sasDataIAxesAttr2D); - Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIUncertaintyAttr, sasDataIdev); - Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIUncertaintiesAttr, sasDataIdev); - // Write the Q Indices as Int Array - Mantid::NeXus::H5Util::writeNumAttribute(data, sasDataQIndicesAttr, std::vector{0, 1}); - - // Store the 2D Qx data + units - std::map qxAttributes; - auto qxUnit = getUnitFromMDDimension(workspace->getXDimension()); - qxUnit = getMomentumTransferLabel(qxUnit); - qxAttributes.emplace(sasUnitAttr, qxUnit); - QxExtractor qxExtractor; - write2DWorkspace(data, workspace, sasDataQx, qxExtractor, qxAttributes); - - // Get 2D Qy data and store it - std::map qyAttributes; - auto qyUnit = getUnitFromMDDimension(workspace->getDimension(1)); - qyUnit = getMomentumTransferLabel(qyUnit); - qyAttributes.emplace(sasUnitAttr, qyUnit); - - SpectrumAxisValueProvider spectrumAxisValueProvider(workspace); - write2DWorkspace(data, workspace, sasDataQy, spectrumAxisValueProvider, qyAttributes); - - // Get 2D I data and store it - std::map iAttributes; - auto iUnit = getIntensityUnit(workspace); - iUnit = getIntensityUnitLabel(iUnit); - iAttributes.emplace(sasUnitAttr, iUnit); - iAttributes.emplace(sasUncertaintyAttr, sasDataIdev); - iAttributes.emplace(sasUncertaintiesAttr, sasDataIdev); - - auto iExtractor = [](const Mantid::API::MatrixWorkspace_sptr &ws, int index) { return ws->dataY(index).data(); }; - write2DWorkspace(data, workspace, sasDataI, iExtractor, iAttributes); - - // Get 2D Idev data and store it - std::map eAttributes; - eAttributes.insert(std::make_pair(sasUnitAttr, iUnit)); // same units as intensity - - auto iDevExtractor = [](const Mantid::API::MatrixWorkspace_sptr &ws, int index) { return ws->dataE(index).data(); }; - write2DWorkspace(data, workspace, sasDataIdev, iDevExtractor, eAttributes); -} - -void addData(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace) { - const std::string sasDataName = sasDataGroupName; - auto data = Mantid::NeXus::H5Util::createGroupCanSAS(group, sasDataName, nxDataClassAttr, sasDataClassAttr); - - auto workspaceDimensionality = getWorkspaceDimensionality(workspace); - switch (workspaceDimensionality) { - case (WorkspaceDimensionality::oneD): - addData1D(data, workspace); - break; - case (WorkspaceDimensionality::twoD): - addData2D(data, workspace); - break; - default: - throw std::runtime_error("SaveNXcanSAS: The provided workspace " - "dimensionality is not 1D or 2D."); - } -} - -//------- SAStransmission_spectrum -void addTransmission(H5::Group &group, const Mantid::API::MatrixWorkspace_const_sptr &workspace, - const std::string &transmissionName) { - // Setup process - const std::string sasTransmissionName = sasTransmissionSpectrumGroupName + "_" + transmissionName; - auto transmission = Mantid::NeXus::H5Util::createGroupCanSAS( - group, sasTransmissionName, nxTransmissionSpectrumClassAttr, sasTransmissionSpectrumClassAttr); - - // Add attributes for @signal, @T_axes, @T_indices, @T_uncertainty, - // @T_uncertainties, @name, @timestamp - Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasSignal, sasTransmissionSpectrumT); - Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasTransmissionSpectrumTIndices, sasTransmissionSpectrumT); - Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasTransmissionSpectrumTUncertainty, - sasTransmissionSpectrumTdev); - Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasTransmissionSpectrumTUncertainties, - sasTransmissionSpectrumTdev); - Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasTransmissionSpectrumNameAttr, transmissionName); - - auto date = getDate(); - Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasTransmissionSpectrumTimeStampAttr, date); - - //----------------------------------------- - // Add T with units + uncertainty definition - const auto transmissionData = workspace->y(0); - std::map transmissionAttributes; - std::string unit = sasNone; - - transmissionAttributes.emplace(sasUnitAttr, unit); - transmissionAttributes.emplace(sasUncertaintyAttr, sasTransmissionSpectrumTdev); - transmissionAttributes.emplace(sasUncertaintiesAttr, sasTransmissionSpectrumTdev); - - writeArray1DWithStrAttributes(transmission, sasTransmissionSpectrumT, transmissionData.rawData(), - transmissionAttributes); - - //----------------------------------------- - // Add Tdev with units - const auto &transmissionErrors = workspace->e(0); - std::map transmissionErrorAttributes; - transmissionErrorAttributes.emplace(sasUnitAttr, unit); - - writeArray1DWithStrAttributes(transmission, sasTransmissionSpectrumTdev, transmissionErrors.rawData(), - transmissionErrorAttributes); - - //----------------------------------------- - // Add lambda with units - const auto lambda = workspace->points(0); - std::map lambdaAttributes; - auto lambdaUnit = getUnitFromMDDimension(workspace->getDimension(0)); - if (lambdaUnit.empty() || lambdaUnit == "Angstrom") { - lambdaUnit = sasAngstrom; - } - lambdaAttributes.emplace(sasUnitAttr, lambdaUnit); - - writeArray1DWithStrAttributes(transmission, sasTransmissionSpectrumLambda, lambda.rawData(), lambdaAttributes); -} -} // namespace - namespace Mantid::DataHandling { // Register the algorithm into the AlgorithmFactory DECLARE_ALGORITHM(SaveNXcanSAS) @@ -723,69 +50,7 @@ DECLARE_ALGORITHM(SaveNXcanSAS) /// constructor SaveNXcanSAS::SaveNXcanSAS() = default; -void SaveNXcanSAS::init() { - auto inputWSValidator = std::make_shared(); - inputWSValidator->add("MomentumTransfer"); - inputWSValidator->add(); - declareProperty(std::make_unique>("InputWorkspace", "", Kernel::Direction::Input, - inputWSValidator), - "The input workspace, which must be in units of Q. Can be a 1D or a 2D workspace."); - declareProperty(std::make_unique("Filename", "", API::FileProperty::Save, ".h5"), - "The name of the .h5 file to save"); - - std::vector radiationSourceOptions{"Spallation Neutron Source", - "Pulsed Reactor Neutron Source", - "Reactor Neutron Source", - "Synchrotron X-ray Source", - "Pulsed Muon Source", - "Rotating Anode X-ray", - "Fixed Tube X-ray", - "neutron", - "x-ray", - "muon", - "electron"}; - declareProperty("RadiationSource", "Spallation Neutron Source", - std::make_shared(radiationSourceOptions), "The type of radiation used."); - declareProperty("DetectorNames", "", - "Specify in a comma separated list, which detectors to store " - "information about; \nwhere each name must match a name " - "given for a detector in the [[IDF|instrument definition " - "file (IDF)]]. \nIDFs are located in the instrument " - "sub-directory of the Mantid install directory."); - declareProperty( - std::make_unique>("Transmission", "", Kernel::Direction::Input, PropertyMode::Optional, - std::make_shared("Wavelength")), - "The transmission workspace. Optional. If given, will be saved at " - "TransmissionSpectrum"); - - declareProperty(std::make_unique>( - "TransmissionCan", "", Kernel::Direction::Input, PropertyMode::Optional, - std::make_shared("Wavelength")), - "The transmission workspace of the Can. Optional. If given, will be " - "saved at TransmissionSpectrum"); - - declareProperty("SampleTransmissionRunNumber", "", "The run number for the sample transmission workspace. Optional."); - declareProperty("SampleDirectRunNumber", "", "The run number for the sample direct workspace. Optional."); - declareProperty("CanScatterRunNumber", "", "The run number for the can scatter workspace. Optional."); - declareProperty("CanDirectRunNumber", "", "The run number for the can direct workspace. Optional."); - - declareProperty( - "BackgroundSubtractionWorkspace", "", - "The name of the workspace used in the scaled background subtraction, to be included in the metadata. Optional."); - declareProperty( - "BackgroundSubtractionScaleFactor", 0.0, - "The scale factor used in the scaled background subtraction, to be included in the metadata. Optional."); - - std::vector const geometryOptions{"Cylinder", "FlatPlate", "Flat plate", "Disc", "Unknown"}; - declareProperty("Geometry", "Unknown", std::make_shared(geometryOptions), - "The geometry type of the collimation."); - declareProperty("SampleHeight", 0.0, - "The height of the collimation element in mm. If specified as 0 it will not be recorded."); - declareProperty("SampleWidth", 0.0, - "The width of the collimation element in mm. If specified as 0 it will not be recorded."); - declareProperty("SampleThickness", 0.0, - "The thickness of the sample in mm. If specified as 0 it will not be recorded."); -} +void SaveNXcanSAS::init() { initStandardProperties(); } std::map SaveNXcanSAS::validateInputs() { // The input should be a Workspace2D @@ -806,7 +71,7 @@ std::map SaveNXcanSAS::validateInputs() { }; if (transmission) { - checkTransmission(transmission, "Trasmission"); + checkTransmission(transmission, "Transmission"); } if (transmissionCan) { @@ -819,113 +84,24 @@ std::map SaveNXcanSAS::validateInputs() { void SaveNXcanSAS::exec() { Mantid::API::MatrixWorkspace_sptr &&workspace = getProperty("InputWorkspace"); std::string &&filename = getPropertyValue("Filename"); - - std::string &&radiationSource = getPropertyValue("RadiationSource"); - std::string &&geometry = getProperty("Geometry"); - double &&beamHeight = getProperty("SampleHeight"); - double &&beamWidth = getProperty("SampleWidth"); - double &&sampleThickness = getProperty("SampleThickness"); - std::string &&detectorNames = getPropertyValue("DetectorNames"); - - Mantid::API::MatrixWorkspace_sptr &&transmissionSample = getProperty("Transmission"); - Mantid::API::MatrixWorkspace_sptr &&transmissionCan = getProperty("TransmissionCan"); - // Remove the file if it already exists if (Poco::File(filename).exists()) { Poco::File(filename).remove(); } H5::H5File file(filename, H5F_ACC_EXCL); - - const std::string suffix("01"); - - // Setup progress bar - int numberOfSteps = 4; - if (transmissionSample) { - ++numberOfSteps; - } - - if (transmissionCan) { - ++numberOfSteps; - } - + auto numberOfSteps = 4; Progress progress(this, 0.1, 1.0, numberOfSteps); - - // Add a new entry progress.report("Adding a new entry."); + // add sas entry + const std::string suffix("01"); auto sasEntry = addSasEntry(file, workspace, suffix); - - // Add the instrument information - progress.report("Adding instrument information."); - const auto detectors = splitDetectorNames(detectorNames); - addInstrument(sasEntry, workspace, radiationSource, geometry, beamHeight, beamWidth, detectors); - - // Add the sample information - progress.report("Adding sample information."); - addSample(sasEntry, sampleThickness); - - // Get additional run numbers - const auto sampleTransmissionRun = getPropertyValue("SampleTransmissionRunNumber"); - const auto sampleDirectRun = getPropertyValue("SampleDirectRunNumber"); - const auto canScatterRun = getPropertyValue("CanScatterRunNumber"); - const auto canDirectRun = getPropertyValue("CanDirectRunNumber"); - - // Get scaled background subtraction information - - const auto scaledBgSubWorkspace = getPropertyValue("BackgroundSubtractionWorkspace"); - const auto scaledBgSubScaleFactor = getPropertyValue("BackgroundSubtractionScaleFactor"); - - // Add the process information - progress.report("Adding process information."); - if (transmissionCan) { - addProcess(sasEntry, workspace, transmissionCan); - } else { - addProcess(sasEntry, workspace); - } - - if (transmissionCan) { - addProcessEntry(sasEntry, sasProcessTermCanScatter, canScatterRun); - addProcessEntry(sasEntry, sasProcessTermCanDirect, canDirectRun); - } - if (transmissionSample) { - addProcessEntry(sasEntry, sasProcessTermSampleTrans, sampleTransmissionRun); - addProcessEntry(sasEntry, sasProcessTermSampleDirect, sampleDirectRun); - } - - if (!scaledBgSubWorkspace.empty()) { - progress.report("Adding scaled background subtraction information."); - addProcessEntry(sasEntry, sasProcessTermScaledBgSubWorkspace, scaledBgSubWorkspace); - addProcessEntry(sasEntry, sasProcessTermScaledBgSubScaleFactor, scaledBgSubScaleFactor); - } - - // Add the transmissions for sample - if (transmissionSample) { - progress.report("Adding sample transmission information."); - addTransmission(sasEntry, transmissionSample, sasTransmissionSpectrumNameSampleAttrValue); - } - - // Add the transmissions for can - if (transmissionCan) { - progress.report("Adding can transmission information."); - addTransmission(sasEntry, transmissionCan, sasTransmissionSpectrumNameCanAttrValue); - } - + // Add metadata for canSAS file: Instrument, Sample, Process + addStandardMetadata(workspace, sasEntry, &progress); // Add the data progress.report("Adding data."); addData(sasEntry, workspace); file.close(); } - -/** - * This makes out of an input a relaxed name, something conforming to - * "[A-Za-z_][\w_]*" - * For now "-" is converted to "_", "." is converted to "_", else we throw - */ -std::string makeCanSASRelaxedName(const std::string &input) { - bool isStrict = false; - auto emptyCapitalizationStrategy = [](std::string &) {}; - return makeCompliantName(input, isStrict, emptyCapitalizationStrategy); -} - } // namespace Mantid::DataHandling From ea9bb892835eeac840e706955ad8968dcd882eee Mon Sep 17 00:00:00 2001 From: adriazalvarez Date: Tue, 7 Jan 2025 11:03:37 +0000 Subject: [PATCH 2/9] Add parent SaveNXcanSasBase class with helper functionsin anonymous namespace --- .../inc/MantidDataHandling/SaveNXcanSASBase.h | 32 + .../DataHandling/src/SaveNXcanSASBase.cpp | 872 ++++++++++++++++++ 2 files changed, 904 insertions(+) create mode 100644 Framework/DataHandling/inc/MantidDataHandling/SaveNXcanSASBase.h create mode 100644 Framework/DataHandling/src/SaveNXcanSASBase.cpp diff --git a/Framework/DataHandling/inc/MantidDataHandling/SaveNXcanSASBase.h b/Framework/DataHandling/inc/MantidDataHandling/SaveNXcanSASBase.h new file mode 100644 index 000000000000..307341b9e5b8 --- /dev/null +++ b/Framework/DataHandling/inc/MantidDataHandling/SaveNXcanSASBase.h @@ -0,0 +1,32 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2016 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#pragma once + +#include "MantidAPI/Algorithm.h" +#include "MantidAPI/MatrixWorkspace_fwd.h" +#include "MantidDataHandling/DllConfig.h" +#include "MantidGeometry/Instrument_fwd.h" +#include + +namespace Mantid { +namespace DataHandling { + +/** SaveNXcanSAS : Saves a reduced workspace in the NXcanSAS format. Currently * only MatrixWorkspaces resulting from + * 1D and 2D reductions are supported. */ +class MANTID_DATAHANDLING_DLL SaveNXcanSASBase : public API::Algorithm { +protected: + void initStandardProperties(); + void addStandardMetadata(Mantid::API::MatrixWorkspace_sptr &workspace, H5::Group &sasEntry, + Mantid::API::Progress *progress); + void addData(H5::Group &data, const Mantid::API::MatrixWorkspace_sptr &workspace); + H5::Group addSasEntry(H5::H5File &file, const Mantid::API::MatrixWorkspace_sptr &workspace, + const std::string &suffix); +}; + +std::string MANTID_DATAHANDLING_DLL makeCanSASRelaxedName(const std::string &input); +} // namespace DataHandling +} // namespace Mantid diff --git a/Framework/DataHandling/src/SaveNXcanSASBase.cpp b/Framework/DataHandling/src/SaveNXcanSASBase.cpp new file mode 100644 index 000000000000..5c7b0c4e66ff --- /dev/null +++ b/Framework/DataHandling/src/SaveNXcanSASBase.cpp @@ -0,0 +1,872 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidAPI/Axis.h" +#include "MantidAPI/CommonBinsValidator.h" +#include "MantidAPI/ExperimentInfo.h" +#include "MantidAPI/FileProperty.h" +#include "MantidAPI/InstrumentFileFinder.h" +#include "MantidAPI/Progress.h" +#include "MantidAPI/Run.h" +#include "MantidAPI/WorkspaceUnitValidator.h" +#include "MantidDataHandling/NXcanSASDefinitions.h" +#include "MantidDataHandling/SaveNXcanSAS.h" +#include "MantidDataObjects/Workspace2D.h" +#include "MantidGeometry/Instrument.h" +#include "MantidGeometry/MDGeometry/IMDDimension.h" +#include "MantidKernel/BoundedValidator.h" +#include "MantidKernel/CompositeValidator.h" +#include "MantidKernel/ListValidator.h" +#include "MantidKernel/MDUnit.h" +#include "MantidKernel/MantidVersion.h" +#include "MantidKernel/VectorHelper.h" +#include "MantidNexus/H5Util.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace Mantid::Kernel; +using namespace Mantid::Geometry; +using namespace Mantid::API; +using namespace Mantid::DataHandling::NXcanSAS; + +namespace { + +enum class StoreType { Qx, Qy, I, Idev, Other }; + +bool isCanSASCompliant(bool isStrict, const std::string &input) { + auto baseRegex = isStrict ? boost::regex("[a-z_][a-z0-9_]*") : boost::regex("[A-Za-z_][\\w_]*"); + return boost::regex_match(input, baseRegex); +} + +void removeSpecialCharacters(std::string &input) { + boost::regex toReplace("[-\\.]"); + std::string replaceWith("_"); + input = boost::regex_replace(input, toReplace, replaceWith); +} + +std::string makeCompliantName(const std::string &input, bool isStrict, + const std::function &capitalizeStrategy) { + auto output = input; + // Check if input is compliant + if (!isCanSASCompliant(isStrict, output)) { + removeSpecialCharacters(output); + capitalizeStrategy(output); + // Check if the changes have made it compliant + if (!isCanSASCompliant(isStrict, output)) { + std::string message = "SaveNXcanSAS: The input " + input + "is not compliant with the NXcanSAS format."; + throw std::runtime_error(message); + } + } + return output; +} + +template +void writeArray1DWithStrAttributes(H5::Group &group, const std::string &dataSetName, const std::vector &values, + const std::map &attributes) { + Mantid::NeXus::H5Util::writeArray1D(group, dataSetName, values); + auto dataSet = group.openDataSet(dataSetName); + for (const auto &attribute : attributes) { + Mantid::NeXus::H5Util::writeStrAttribute(dataSet, attribute.first, attribute.second); + } +} + +H5::DSetCreatPropList setCompression2D(const hsize_t *chunkDims, const int deflateLevel = 6) { + H5::DSetCreatPropList propList; + const int rank = 2; + propList.setChunk(rank, chunkDims); + propList.setDeflate(deflateLevel); + return propList; +} + +template +void write2DWorkspace(H5::Group &group, Mantid::API::MatrixWorkspace_sptr workspace, const std::string &dataSetName, + Functor func, const std::map &attributes) { + using namespace Mantid::NeXus::H5Util; + + // Set the dimension + const size_t dimension0 = workspace->getNumberHistograms(); + const size_t dimension1 = workspace->y(0).size(); + const hsize_t rank = 2; + hsize_t dimensionArray[rank] = {static_cast(dimension0), static_cast(dimension1)}; + + // Start position in the 2D data (indexed) data structure + hsize_t start[rank] = {0, 0}; + + // Size of a slab + hsize_t sizeOfSingleSlab[rank] = {1, dimensionArray[1]}; + + // Get the Data Space definition for the 2D Data Set in the file + auto fileSpace = H5::DataSpace(rank, dimensionArray); + H5::DataType dataType(getType()); + + // Get the proplist with compression settings + H5::DSetCreatPropList propList = setCompression2D(sizeOfSingleSlab); + + // Create the data set + auto dataSet = group.createDataSet(dataSetName, dataType, fileSpace, propList); + + // Create Data Spae for 1D entry for each row in memory + hsize_t memSpaceDimension[1] = {dimension1}; + H5::DataSpace memSpace(1, memSpaceDimension); + + // Insert each row of the workspace as a slab + for (unsigned int index = 0; index < dimension0; ++index) { + // Need the data space + fileSpace.selectHyperslab(H5S_SELECT_SET, sizeOfSingleSlab, start); + + // Write the correct data set to file + dataSet.write(func(workspace, index), dataType, memSpace, fileSpace); + // Step up the write position + ++start[0]; + } + + // Add attributes to data set + for (const auto &attribute : attributes) { + writeStrAttribute(dataSet, attribute.first, attribute.second); + } +} + +std::vector splitDetectorNames(std::string detectorNames) { + const std::string delimiter = ","; + std::vector detectors; + size_t pos(0); + + while ((pos = detectorNames.find(delimiter)) != std::string::npos) { + std::string detectorName = detectorNames.substr(0, pos); + boost::algorithm::trim(detectorName); + detectors.emplace_back(detectorName); + detectorNames.erase(0, pos + delimiter.length()); + } + // Push remaining element + boost::algorithm::trim(detectorNames); + detectors.emplace_back(detectorNames); + return detectors; +} + +//------- SASinstrument +std::string getInstrumentName(const Mantid::API::MatrixWorkspace_sptr &workspace) { + auto instrument = workspace->getInstrument(); + return instrument->getFullName(); +} + +std::string getIDF(const Mantid::API::MatrixWorkspace_sptr &workspace) { + auto date = workspace->getWorkspaceStartDate(); + auto instrumentName = getInstrumentName(workspace); + return InstrumentFileFinder::getInstrumentFilename(instrumentName, date); +} + +void addDetectors(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace, + const std::vector &detectorNames) { + // If the group is empty then don't add anything + if (!detectorNames.empty()) { + for (const auto &detectorName : detectorNames) { + if (detectorName.empty()) { + continue; + } + + std::string sasDetectorName = sasInstrumentDetectorGroupName + detectorName; + sasDetectorName = Mantid::DataHandling::makeCanSASRelaxedName(sasDetectorName); + + auto instrument = workspace->getInstrument(); + auto component = instrument->getComponentByName(detectorName); + + if (component) { + const auto sample = instrument->getSample(); + const auto distance = component->getDistance(*sample); + std::map sddAttributes; + sddAttributes.insert(std::make_pair(sasUnitAttr, sasInstrumentDetectorSddUnitAttrValue)); + auto detector = Mantid::NeXus::H5Util::createGroupCanSAS(group, sasDetectorName, nxInstrumentDetectorClassAttr, + sasInstrumentDetectorClassAttr); + Mantid::NeXus::H5Util::write(detector, sasInstrumentDetectorName, detectorName); + Mantid::NeXus::H5Util::writeScalarDataSetWithStrAttributes(detector, sasInstrumentDetectorSdd, distance, + sddAttributes); + } + } + } +} + +/** + * Add the instrument group to the NXcanSAS file. This adds the + * instrument name and the IDF + * @param group: the sasEntry + * @param workspace: the workspace which is being stored + * @param radiationSource: the selcted radiation source + * @param detectorNames: the names of the detectors to store + */ +void addInstrument(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace, + const std::string &radiationSource, const std::string &geometry, double beamHeight, double beamWidth, + const std::vector &detectorNames) { + // Setup instrument + const std::string sasInstrumentNameForGroup = sasInstrumentGroupName; + auto instrument = Mantid::NeXus::H5Util::createGroupCanSAS(group, sasInstrumentNameForGroup, nxInstrumentClassAttr, + sasInstrumentClassAttr); + auto instrumentName = getInstrumentName(workspace); + Mantid::NeXus::H5Util::write(instrument, sasInstrumentName, instrumentName); + + // Setup the detector + addDetectors(instrument, workspace, detectorNames); + + // Setup source + const std::string sasSourceName = sasInstrumentSourceGroupName; + auto source = Mantid::NeXus::H5Util::createGroupCanSAS(instrument, sasSourceName, nxInstrumentSourceClassAttr, + sasInstrumentSourceClassAttr); + Mantid::NeXus::H5Util::write(source, sasInstrumentSourceRadiation, radiationSource); + + // Setup Aperture + const std::string sasApertureName = sasInstrumentApertureGroupName; + auto aperture = Mantid::NeXus::H5Util::createGroupCanSAS(instrument, sasApertureName, nxInstrumentApertureClassAttr, + sasInstrumentApertureClassAttr); + + Mantid::NeXus::H5Util::write(aperture, sasInstrumentApertureShape, geometry); + + std::map beamSizeAttrs; + beamSizeAttrs.insert(std::make_pair(sasUnitAttr, sasBeamAndSampleSizeUnitAttrValue)); + if (beamHeight != 0) { + Mantid::NeXus::H5Util::writeScalarDataSetWithStrAttributes(aperture, sasInstrumentApertureGapHeight, beamHeight, + beamSizeAttrs); + } + if (beamWidth != 0) { + Mantid::NeXus::H5Util::writeScalarDataSetWithStrAttributes(aperture, sasInstrumentApertureGapWidth, beamWidth, + beamSizeAttrs); + } + + // Add IDF information + auto idf = getIDF(workspace); + Mantid::NeXus::H5Util::write(instrument, sasInstrumentIDF, idf); +} + +//------- SASsample + +void addSample(H5::Group &group, const double &sampleThickness) { + if (sampleThickness == 0) { + return; + } + std::string const sasSampleNameForGroup = sasInstrumentSampleGroupAttr; + + auto sample = Mantid::NeXus::H5Util::createGroupCanSAS(group, sasSampleNameForGroup, nxInstrumentSampleClassAttr, + sasInstrumentSampleClassAttr); + + std::map sampleThicknessAttrs; + sampleThicknessAttrs.insert(std::make_pair(sasUnitAttr, sasBeamAndSampleSizeUnitAttrValue)); + Mantid::NeXus::H5Util::writeScalarDataSetWithStrAttributes(sample, sasInstrumentSampleThickness, sampleThickness, + sampleThicknessAttrs); +} + +//------- SASprocess + +std::string getDate() { + time_t rawtime; + time(&rawtime); + char temp[25]; + strftime(temp, 25, "%Y-%m-%dT%H:%M:%S", localtime(&rawtime)); + std::string sasDate(temp); + return sasDate; +} + +/** Write a property value to the H5 file if the property exists in the run + * + * @param run : the run to look for the property in + * @param propertyName : the name of the property to find + * @param sasGroup : the group to add the term into in the output file + * @param sasTerm : the name of the term to add + */ +void addPropertyFromRunIfExists(Run const &run, std::string const &propertyName, H5::Group &sasGroup, + std::string const &sasTerm) { + if (run.hasProperty(propertyName)) { + const auto *property = run.getProperty(propertyName); + Mantid::NeXus::H5Util::write(sasGroup, sasTerm, property->value()); + } +} + +/** + * Add the process information to the NXcanSAS file. This information + * about the run number, the Mantid version and the user file (if available) + * @param group: the sasEntry + * @param workspace: the workspace which is being stored + */ +void addProcess(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace) { + // Setup process + const std::string sasProcessNameForGroup = sasProcessGroupName; + auto process = + Mantid::NeXus::H5Util::createGroupCanSAS(group, sasProcessNameForGroup, nxProcessClassAttr, sasProcessClassAttr); + + // Add name + Mantid::NeXus::H5Util::write(process, sasProcessName, sasProcessNameValue); + + // Add creation date of the file + auto date = getDate(); + Mantid::NeXus::H5Util::write(process, sasProcessDate, date); + + // Add Mantid version + const auto version = std::string(MantidVersion::version()); + Mantid::NeXus::H5Util::write(process, sasProcessTermSvn, version); + + // Add log values + const auto run = workspace->run(); + addPropertyFromRunIfExists(run, sasProcessUserFileInLogs, process, sasProcessTermUserFile); + addPropertyFromRunIfExists(run, sasProcessBatchFileInLogs, process, sasProcessTermBatchFile); +} + +/** + * Add the process information to the NXcanSAS file. This information + * about the run number, the Mantid version and the user file (if available) + * @param group: the sasEntry + * @param workspace: the workspace which is being stored + */ +void addProcess(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace, + const Mantid::API::MatrixWorkspace_sptr &canWorkspace) { + // Setup process + const std::string sasProcessNameForGroup = sasProcessGroupName; + auto process = + Mantid::NeXus::H5Util::createGroupCanSAS(group, sasProcessNameForGroup, nxProcessClassAttr, sasProcessClassAttr); + + // Add name + Mantid::NeXus::H5Util::write(process, sasProcessName, sasProcessNameValue); + + // Add creation date of the file + auto date = getDate(); + Mantid::NeXus::H5Util::write(process, sasProcessDate, date); + + // Add Mantid version + const auto version = std::string(MantidVersion::version()); + Mantid::NeXus::H5Util::write(process, sasProcessTermSvn, version); + + const auto run = workspace->run(); + addPropertyFromRunIfExists(run, sasProcessUserFileInLogs, process, sasProcessTermUserFile); + addPropertyFromRunIfExists(run, sasProcessBatchFileInLogs, process, sasProcessTermBatchFile); + + // Add can run number + const auto canRun = canWorkspace->getRunNumber(); + Mantid::NeXus::H5Util::write(process, sasProcessTermCan, std::to_string(canRun)); +} + +/** + * Add an entry to the process group. + * @param group: the sasEntry + * @param entryName: string containing the name of the value to save + * @param entryValue: string containing the value to save + */ +void addProcessEntry(H5::Group &group, const std::string &entryName, const std::string &entryValue) { + auto process = group.openGroup(sasProcessGroupName); + // Populate process entry + Mantid::NeXus::H5Util::write(process, entryName, entryValue); +} + +WorkspaceDimensionality getWorkspaceDimensionality(const Mantid::API::MatrixWorkspace_sptr &workspace) { + auto numberOfHistograms = workspace->getNumberHistograms(); + WorkspaceDimensionality dimensionality(WorkspaceDimensionality::other); + if (numberOfHistograms == 1) { + dimensionality = WorkspaceDimensionality::oneD; + } else if (numberOfHistograms > 1) { + dimensionality = WorkspaceDimensionality::twoD; + } + return dimensionality; +} + +//------- SASdata + +std::string getIntensityUnitLabel(const std::string &intensityUnitLabel) { + if (intensityUnitLabel == "I(q) (cm-1)") { + return sasIntensity; + } else { + return intensityUnitLabel; + } +} + +std::string getIntensityUnit(const Mantid::API::MatrixWorkspace_sptr &workspace) { + auto iUnit = workspace->YUnit(); + if (iUnit.empty()) { + iUnit = workspace->YUnitLabel(); + } + return iUnit; +} + +std::string getMomentumTransferLabel(const std::string &momentumTransferLabel) { + if (momentumTransferLabel == "Angstrom^-1") { + return sasMomentumTransfer; + } else { + return momentumTransferLabel; + } +} + +std::string getUnitFromMDDimension(const Mantid::Geometry::IMDDimension_const_sptr &dimension) { + const auto unitLabel = dimension->getMDUnits().getUnitLabel(); + return unitLabel.ascii(); +} + +bool areAxesNumeric(const Mantid::API::MatrixWorkspace_sptr &workspace) { + const std::array indices = {0, 1}; + return std::all_of(indices.cbegin(), indices.cend(), + [workspace](auto const &index) { return workspace->getAxis(index)->isNumeric(); }); +} + +class SpectrumAxisValueProvider { +public: + explicit SpectrumAxisValueProvider(Mantid::API::MatrixWorkspace_sptr workspace) : m_workspace(std::move(workspace)) { + setSpectrumAxisValues(); + } + + Mantid::MantidVec::value_type *operator()(const Mantid::API::MatrixWorkspace_sptr & /*unused*/, int index) { + auto isPointData = m_workspace->getNumberHistograms() == m_spectrumAxisValues.size(); + double value = 0; + if (isPointData) { + value = m_spectrumAxisValues[index]; + } else { + value = (m_spectrumAxisValues[index + 1] + m_spectrumAxisValues[index]) / 2.0; + } + + Mantid::MantidVec tempVec(m_workspace->dataY(index).size(), value); + m_currentAxisValues.swap(tempVec); + return m_currentAxisValues.data(); + } + +private: + void setSpectrumAxisValues() { + auto sAxis = m_workspace->getAxis(1); + for (size_t index = 0; index < sAxis->length(); ++index) { + m_spectrumAxisValues.emplace_back((*sAxis)(index)); + } + } + + Mantid::API::MatrixWorkspace_sptr m_workspace; + Mantid::MantidVec m_spectrumAxisValues; + Mantid::MantidVec m_currentAxisValues; +}; + +/** + * QxExtractor functor which allows us to convert 2D Qx data into point data. + */ +template class QxExtractor { +public: + T *operator()(const Mantid::API::MatrixWorkspace_sptr &ws, int index) { + if (ws->isHistogramData()) { + qxPointData.clear(); + Mantid::Kernel::VectorHelper::convertToBinCentre(ws->dataX(index), qxPointData); + return qxPointData.data(); + } else { + return ws->dataX(index).data(); + } + } + + std::vector qxPointData; +}; + +/** + * Stores the 2D data in the HDF5 file. Qx and Qy values need to be stored as a + *meshgrid. + * They should be stored as point data. + * @param data: the hdf5 group + * @param workspace: the workspace to store + * + * Workspace looks like this in Mantid Matrix + * (Qx) 0 1 2 ... M (first dimension) + * (QY) + * 0 IQx0Qy0 IQx1Qy0 IQx2Qy0 ... IQxMQy0 + * 1 IQx0Qy1 IQx1Qy1 IQx2Qy1 ... IQxMQy1 + * 2 IQx0Qy2 IQx1Qy2 IQx2Qy2 ... IQxMQy2 + * 3 IQx0Qy3 IQx1Qy3 IQx2Qy3 ... IQxMQy3 + * . + * . + * N IQx0QyN IQx1QyN IQx2QyN ... IQxMQyN + * (second dimension) + * + * The layout below is how it would look like in the HDFView, ie vertical axis + * is first dimension. We map the Mantid Matrix layout 1-to-1. Note that this + * will swap the matrix indices, but this is how it is done in the other + *2Dloaders + * + * In HDF5 the Qx would need to be stored as: + * Qx1 Qx2 ... QxM + * Qx1 Qx2 ... QxM + * Qx1 Qx2 ... QxM + * . + * . + * Qx1 Qx2 ... QxM + * + * In HDF5 the Qy would need to be stored as: + * Qy1 Qy1 ... Qy1 + * Qy2 Qy2 ... Qy2 + * Qy3 Qy3 ... Qy3 + * . + * . + * QxN QxN ... QxN + */ +void addData2D(H5::Group &data, const Mantid::API::MatrixWorkspace_sptr &workspace) { + if (!areAxesNumeric(workspace)) { + throw std::invalid_argument("SaveNXcanSAS: The provided 2D workspace needs to have 2 numeric axes."); + } + // Add attributes for @signal, @I_axes, @Q_indices, + Mantid::NeXus::H5Util::writeStrAttribute(data, sasSignal, sasDataI); + const std::string sasDataIAxesAttr2D = sasDataQ + sasSeparator + sasDataQ; + Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIAxesAttr, sasDataIAxesAttr2D); + Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIUncertaintyAttr, sasDataIdev); + Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIUncertaintiesAttr, sasDataIdev); + // Write the Q Indices as Int Array + Mantid::NeXus::H5Util::writeNumAttribute(data, sasDataQIndicesAttr, std::vector{0, 1}); + + // Store the 2D Qx data + units + std::map qxAttributes; + auto qxUnit = getUnitFromMDDimension(workspace->getXDimension()); + qxUnit = getMomentumTransferLabel(qxUnit); + qxAttributes.emplace(sasUnitAttr, qxUnit); + QxExtractor qxExtractor; + write2DWorkspace(data, workspace, sasDataQx, qxExtractor, qxAttributes); + + // Get 2D Qy data and store it + std::map qyAttributes; + auto qyUnit = getUnitFromMDDimension(workspace->getDimension(1)); + qyUnit = getMomentumTransferLabel(qyUnit); + qyAttributes.emplace(sasUnitAttr, qyUnit); + + SpectrumAxisValueProvider spectrumAxisValueProvider(workspace); + write2DWorkspace(data, workspace, sasDataQy, spectrumAxisValueProvider, qyAttributes); + + // Get 2D I data and store it + std::map iAttributes; + auto iUnit = getIntensityUnit(workspace); + iUnit = getIntensityUnitLabel(iUnit); + iAttributes.emplace(sasUnitAttr, iUnit); + iAttributes.emplace(sasUncertaintyAttr, sasDataIdev); + iAttributes.emplace(sasUncertaintiesAttr, sasDataIdev); + + auto iExtractor = [](const Mantid::API::MatrixWorkspace_sptr &ws, int index) { return ws->dataY(index).data(); }; + write2DWorkspace(data, workspace, sasDataI, iExtractor, iAttributes); + + // Get 2D Idev data and store it + std::map eAttributes; + eAttributes.insert(std::make_pair(sasUnitAttr, iUnit)); // same units as intensity + + auto iDevExtractor = [](const Mantid::API::MatrixWorkspace_sptr &ws, int index) { return ws->dataE(index).data(); }; + write2DWorkspace(data, workspace, sasDataIdev, iDevExtractor, eAttributes); +} + +void addData1D(H5::Group &data, const Mantid::API::MatrixWorkspace_sptr &workspace) { + // Add attributes for @signal, @I_axes, @Q_indices, + Mantid::NeXus::H5Util::writeStrAttribute(data, sasSignal, sasDataI); + Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIAxesAttr, sasDataQ); + Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIUncertaintyAttr, sasDataIdev); + Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIUncertaintiesAttr, sasDataIdev); + Mantid::NeXus::H5Util::writeNumAttribute(data, sasDataQIndicesAttr, std::vector{0}); + + if (workspace->hasDx(0)) { + Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataQUncertaintyAttr, sasDataQdev); + Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataQUncertaintiesAttr, sasDataQdev); + } + + //----------------------------------------- + // Add Q with units + uncertainty definition + const auto &qValue = workspace->points(0); + std::map qAttributes; + auto qUnit = getUnitFromMDDimension(workspace->getDimension(0)); + qUnit = getMomentumTransferLabel(qUnit); + qAttributes.emplace(sasUnitAttr, qUnit); + if (workspace->hasDx(0)) { + qAttributes.emplace(sasUncertaintyAttr, sasDataQdev); + qAttributes.emplace(sasUncertaintiesAttr, sasDataQdev); + } + + writeArray1DWithStrAttributes(data, sasDataQ, qValue.rawData(), qAttributes); + + //----------------------------------------- + // Add I with units + uncertainty definition + const auto &intensity = workspace->y(0); + std::map iAttributes; + auto iUnit = getIntensityUnit(workspace); + iUnit = getIntensityUnitLabel(iUnit); + iAttributes.emplace(sasUnitAttr, iUnit); + iAttributes.emplace(sasUncertaintyAttr, sasDataIdev); + iAttributes.emplace(sasUncertaintiesAttr, sasDataIdev); + + writeArray1DWithStrAttributes(data, sasDataI, intensity.rawData(), iAttributes); + + //----------------------------------------- + // Add Idev with units + const auto &intensityUncertainty = workspace->e(0); + std::map eAttributes; + eAttributes.insert(std::make_pair(sasUnitAttr, iUnit)); // same units as intensity + + writeArray1DWithStrAttributes(data, sasDataIdev, intensityUncertainty.rawData(), eAttributes); + + //----------------------------------------- + // Add Qdev with units if available + if (workspace->hasDx(0)) { + const auto qResolution = workspace->pointStandardDeviations(0); + std::map xUncertaintyAttributes; + xUncertaintyAttributes.emplace(sasUnitAttr, qUnit); + + writeArray1DWithStrAttributes(data, sasDataQdev, qResolution.rawData(), xUncertaintyAttributes); + } +} + +//------- SAStransmission_spectrum +void addTransmission(H5::Group &group, const Mantid::API::MatrixWorkspace_const_sptr &workspace, + const std::string &transmissionName) { + // Setup process + const std::string sasTransmissionName = sasTransmissionSpectrumGroupName + "_" + transmissionName; + auto transmission = Mantid::NeXus::H5Util::createGroupCanSAS( + group, sasTransmissionName, nxTransmissionSpectrumClassAttr, sasTransmissionSpectrumClassAttr); + + // Add attributes for @signal, @T_axes, @T_indices, @T_uncertainty, + // @T_uncertainties, @name, @timestamp + Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasSignal, sasTransmissionSpectrumT); + Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasTransmissionSpectrumTIndices, sasTransmissionSpectrumT); + Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasTransmissionSpectrumTUncertainty, + sasTransmissionSpectrumTdev); + Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasTransmissionSpectrumTUncertainties, + sasTransmissionSpectrumTdev); + Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasTransmissionSpectrumNameAttr, transmissionName); + + auto date = getDate(); + Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasTransmissionSpectrumTimeStampAttr, date); + + //----------------------------------------- + // Add T with units + uncertainty definition + const auto transmissionData = workspace->y(0); + std::map transmissionAttributes; + std::string unit = sasNone; + + transmissionAttributes.emplace(sasUnitAttr, unit); + transmissionAttributes.emplace(sasUncertaintyAttr, sasTransmissionSpectrumTdev); + transmissionAttributes.emplace(sasUncertaintiesAttr, sasTransmissionSpectrumTdev); + + writeArray1DWithStrAttributes(transmission, sasTransmissionSpectrumT, transmissionData.rawData(), + transmissionAttributes); + + //----------------------------------------- + // Add Tdev with units + const auto &transmissionErrors = workspace->e(0); + std::map transmissionErrorAttributes; + transmissionErrorAttributes.emplace(sasUnitAttr, unit); + + writeArray1DWithStrAttributes(transmission, sasTransmissionSpectrumTdev, transmissionErrors.rawData(), + transmissionErrorAttributes); + + //----------------------------------------- + // Add lambda with units + const auto lambda = workspace->points(0); + std::map lambdaAttributes; + auto lambdaUnit = getUnitFromMDDimension(workspace->getDimension(0)); + if (lambdaUnit.empty() || lambdaUnit == "Angstrom") { + lambdaUnit = sasAngstrom; + } + lambdaAttributes.emplace(sasUnitAttr, lambdaUnit); + + writeArray1DWithStrAttributes(transmission, sasTransmissionSpectrumLambda, lambda.rawData(), lambdaAttributes); +} +} // namespace + +namespace Mantid::DataHandling { + +void SaveNXcanSASBase::initStandardProperties() { + auto inputWSValidator = std::make_shared(); + inputWSValidator->add("MomentumTransfer"); + inputWSValidator->add(); + declareProperty(std::make_unique>("InputWorkspace", "", Kernel::Direction::Input, + inputWSValidator), + "The input workspace, which must be in units of Q. Can be a 1D or a 2D workspace."); + declareProperty(std::make_unique("Filename", "", API::FileProperty::Save, ".h5"), + "The name of the .h5 file to save"); + + std::vector radiationSourceOptions{"Spallation Neutron Source", + "Pulsed Reactor Neutron Source", + "Reactor Neutron Source", + "Synchrotron X-ray Source", + "Pulsed Muon Source", + "Rotating Anode X-ray", + "Fixed Tube X-ray", + "neutron", + "x-ray", + "muon", + "electron"}; + declareProperty("RadiationSource", "Spallation Neutron Source", + std::make_shared(radiationSourceOptions), "The type of radiation used."); + declareProperty("DetectorNames", "", + "Specify in a comma separated list, which detectors to store " + "information about; \nwhere each name must match a name " + "given for a detector in the [[IDF|instrument definition " + "file (IDF)]]. \nIDFs are located in the instrument " + "sub-directory of the Mantid install directory."); + declareProperty( + std::make_unique>("Transmission", "", Kernel::Direction::Input, PropertyMode::Optional, + std::make_shared("Wavelength")), + "The transmission workspace. Optional. If given, will be saved at " + "TransmissionSpectrum"); + + declareProperty(std::make_unique>( + "TransmissionCan", "", Kernel::Direction::Input, PropertyMode::Optional, + std::make_shared("Wavelength")), + "The transmission workspace of the Can. Optional. If given, will be " + "saved at TransmissionSpectrum"); + + declareProperty("SampleTransmissionRunNumber", "", "The run number for the sample transmission workspace. Optional."); + declareProperty("SampleDirectRunNumber", "", "The run number for the sample direct workspace. Optional."); + declareProperty("CanScatterRunNumber", "", "The run number for the can scatter workspace. Optional."); + declareProperty("CanDirectRunNumber", "", "The run number for the can direct workspace. Optional."); + + declareProperty( + "BackgroundSubtractionWorkspace", "", + "The name of the workspace used in the scaled background subtraction, to be included in the metadata. Optional."); + declareProperty( + "BackgroundSubtractionScaleFactor", 0.0, + "The scale factor used in the scaled background subtraction, to be included in the metadata. Optional."); + + std::vector const geometryOptions{"Cylinder", "FlatPlate", "Flat plate", "Disc", "Unknown"}; + declareProperty("Geometry", "Unknown", std::make_shared(geometryOptions), + "The geometry type of the collimation."); + declareProperty("SampleHeight", 0.0, + "The height of the collimation element in mm. If specified as 0 it will not be recorded."); + declareProperty("SampleWidth", 0.0, + "The width of the collimation element in mm. If specified as 0 it will not be recorded."); + declareProperty("SampleThickness", 0.0, + "The thickness of the sample in mm. If specified as 0 it will not be recorded."); +} + +void SaveNXcanSASBase::addStandardMetadata(MatrixWorkspace_sptr &workspace, H5::Group &sasEntry, Progress *progress) { + std::string &&radiationSource = getPropertyValue("RadiationSource"); + std::string &&geometry = getProperty("Geometry"); + double &&beamHeight = getProperty("SampleHeight"); + double &&beamWidth = getProperty("SampleWidth"); + double &&sampleThickness = getProperty("SampleThickness"); + std::string &&detectorNames = getPropertyValue("DetectorNames"); + Mantid::API::MatrixWorkspace_sptr &&transmissionSample = getProperty("Transmission"); + Mantid::API::MatrixWorkspace_sptr &&transmissionCan = getProperty("TransmissionCan"); + + // Setup progress bar + int numberOfSteps = 4; + if (transmissionSample) { + ++numberOfSteps; + } + + if (transmissionCan) { + ++numberOfSteps; + } + + // Add the instrument information + progress->report("Adding instrument information."); + const auto detectors = splitDetectorNames(detectorNames); + addInstrument(sasEntry, workspace, radiationSource, geometry, beamHeight, beamWidth, detectors); + + // Add the sample information + progress->report("Adding sample information."); + addSample(sasEntry, sampleThickness); + + // Get additional run numbers + const auto sampleTransmissionRun = getPropertyValue("SampleTransmissionRunNumber"); + const auto sampleDirectRun = getPropertyValue("SampleDirectRunNumber"); + const auto canScatterRun = getPropertyValue("CanScatterRunNumber"); + const auto canDirectRun = getPropertyValue("CanDirectRunNumber"); + + // Get scaled background subtraction information + + const auto scaledBgSubWorkspace = getPropertyValue("BackgroundSubtractionWorkspace"); + const auto scaledBgSubScaleFactor = getPropertyValue("BackgroundSubtractionScaleFactor"); + + // Add the process information + progress->report("Adding process information."); + if (transmissionCan) { + addProcess(sasEntry, workspace, transmissionCan); + } else { + addProcess(sasEntry, workspace); + } + + if (transmissionCan) { + addProcessEntry(sasEntry, sasProcessTermCanScatter, canScatterRun); + addProcessEntry(sasEntry, sasProcessTermCanDirect, canDirectRun); + } + if (transmissionSample) { + addProcessEntry(sasEntry, sasProcessTermSampleTrans, sampleTransmissionRun); + addProcessEntry(sasEntry, sasProcessTermSampleDirect, sampleDirectRun); + } + + if (!scaledBgSubWorkspace.empty()) { + progress->report("Adding scaled background subtraction information."); + addProcessEntry(sasEntry, sasProcessTermScaledBgSubWorkspace, scaledBgSubWorkspace); + addProcessEntry(sasEntry, sasProcessTermScaledBgSubScaleFactor, scaledBgSubScaleFactor); + } + + // Add the transmissions for sample + if (transmissionSample) { + progress->report("Adding sample transmission information."); + addTransmission(sasEntry, transmissionSample, sasTransmissionSpectrumNameSampleAttrValue); + } + + // Add the transmissions for can + if (transmissionCan) { + progress->report("Adding can transmission information."); + addTransmission(sasEntry, transmissionCan, sasTransmissionSpectrumNameCanAttrValue); + } +} + +/** + * Add the sasEntry to the sasroot. + * @param file: Handle to the NXcanSAS file + * @param workspace: the workspace to store + * @return the sasEntry + */ +H5::Group SaveNXcanSASBase::addSasEntry(H5::H5File &file, const Mantid::API::MatrixWorkspace_sptr &workspace, + const std::string &suffix) { + using namespace Mantid::DataHandling::NXcanSAS; + const std::string sasEntryName = sasEntryGroupName + suffix; + auto sasEntry = Mantid::NeXus::H5Util::createGroupCanSAS(file, sasEntryName, nxEntryClassAttr, sasEntryClassAttr); + + // Add version + Mantid::NeXus::H5Util::writeStrAttribute(sasEntry, sasEntryVersionAttr, sasEntryVersionAttrValue); + + // Add definition + Mantid::NeXus::H5Util::write(sasEntry, sasEntryDefinition, sasEntryDefinitionFormat); + + // Add title + auto workspaceTitle = workspace->getTitle(); + Mantid::NeXus::H5Util::write(sasEntry, sasEntryTitle, workspaceTitle); + + // Add run + const auto runNumber = workspace->getRunNumber(); + Mantid::NeXus::H5Util::write(sasEntry, sasEntryRun, std::to_string(runNumber)); + + return sasEntry; +} + +void SaveNXcanSASBase::addData(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace) { + const std::string sasDataName = sasDataGroupName; + auto data = Mantid::NeXus::H5Util::createGroupCanSAS(group, sasDataName, nxDataClassAttr, sasDataClassAttr); + + auto workspaceDimensionality = getWorkspaceDimensionality(workspace); + switch (workspaceDimensionality) { + case (WorkspaceDimensionality::oneD): + addData1D(data, workspace); + break; + case (WorkspaceDimensionality::twoD): + addData2D(data, workspace); + break; + default: + throw std::runtime_error("SaveNXcanSAS: The provided workspace " + "dimensionality is not 1D or 2D."); + } +} + +/** + * This makes out of an input a relaxed name, something conforming to + * "[A-Za-z_][\w_]*" + * For now "-" is converted to "_", "." is converted to "_", else we throw + */ +std::string makeCanSASRelaxedName(const std::string &input) { + bool isStrict = false; + auto emptyCapitalizationStrategy = [](std::string &) {}; + return makeCompliantName(input, isStrict, emptyCapitalizationStrategy); +} + +} // namespace Mantid::DataHandling From 868a9f434079ec21770d3ed5f52d555a73041764 Mon Sep 17 00:00:00 2001 From: adriazalvarez Date: Thu, 9 Jan 2025 13:16:51 +0000 Subject: [PATCH 3/9] Add file for NXcanSAS helper functions --- Framework/DataHandling/CMakeLists.txt | 2 + .../inc/MantidDataHandling/NXcanSASHelper.h | 46 ++ Framework/DataHandling/src/NXcanSASHelper.cpp | 670 ++++++++++++++++++ 3 files changed, 718 insertions(+) create mode 100644 Framework/DataHandling/inc/MantidDataHandling/NXcanSASHelper.h create mode 100644 Framework/DataHandling/src/NXcanSASHelper.cpp diff --git a/Framework/DataHandling/CMakeLists.txt b/Framework/DataHandling/CMakeLists.txt index 52b65ec306cd..4e32e4c0fbba 100644 --- a/Framework/DataHandling/CMakeLists.txt +++ b/Framework/DataHandling/CMakeLists.txt @@ -142,6 +142,7 @@ set(SRC_FILES src/ModifyDetectorDotDatFile.cpp src/MoveInstrumentComponent.cpp src/MultiPeriodLoadMuonStrategy.cpp + src/NXcanSASHelper.cpp src/NexusTester.cpp src/ORNLDataArchive.cpp src/PDLoadCharacterizations.cpp @@ -360,6 +361,7 @@ set(INC_FILES inc/MantidDataHandling/MoveInstrumentComponent.h inc/MantidDataHandling/MultiPeriodLoadMuonStrategy.h inc/MantidDataHandling/NXcanSASDefinitions.h + inc/MantidDataHandling/NXcanSASHelper.h inc/MantidDataHandling/NexusTester.h inc/MantidDataHandling/ORNLDataArchive.h inc/MantidDataHandling/PDLoadCharacterizations.h diff --git a/Framework/DataHandling/inc/MantidDataHandling/NXcanSASHelper.h b/Framework/DataHandling/inc/MantidDataHandling/NXcanSASHelper.h new file mode 100644 index 000000000000..d1673d5688c6 --- /dev/null +++ b/Framework/DataHandling/inc/MantidDataHandling/NXcanSASHelper.h @@ -0,0 +1,46 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2016 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#pragma once + +#include "MantidAPI/Algorithm.h" +#include "MantidAPI/MatrixWorkspace_fwd.h" +#include "MantidDataHandling/DllConfig.h" +#include + +namespace Mantid { +namespace DataHandling { +namespace NXcanSAS { +// Helper functions for algorithms saving in NXcanSAS format. +enum class WorkspaceDimensionality; + +std::string MANTID_DATAHANDLING_DLL makeCanSASRelaxedName(const std::string &input); + +void MANTID_DATAHANDLING_DLL addDetectors(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace, + const std::vector &detectorNames); +void MANTID_DATAHANDLING_DLL addInstrument(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace, + const std::string &radiationSource, const std::string &geometry, + double beamHeight, double beamWidth, + const std::vector &detectorNames); +void MANTID_DATAHANDLING_DLL addSample(H5::Group &group, const double &sampleThickness); +void MANTID_DATAHANDLING_DLL addProcess(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace); +void MANTID_DATAHANDLING_DLL addProcess(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace, + const Mantid::API::MatrixWorkspace_sptr &canWorkspace); +void MANTID_DATAHANDLING_DLL addProcessEntry(H5::Group &group, const std::string &entryName, + const std::string &entryValue); +void MANTID_DATAHANDLING_DLL addTransmission(H5::Group &group, const Mantid::API::MatrixWorkspace_const_sptr &workspace, + const std::string &transmissionName); + +void MANTID_DATAHANDLING_DLL addData1D(H5::Group &data, const Mantid::API::MatrixWorkspace_sptr &workspace); +void MANTID_DATAHANDLING_DLL addData2D(H5::Group &data, const Mantid::API::MatrixWorkspace_sptr &workspace); + +WorkspaceDimensionality MANTID_DATAHANDLING_DLL +getWorkspaceDimensionality(const Mantid::API::MatrixWorkspace_sptr &workspace); +std::vector MANTID_DATAHANDLING_DLL splitDetectorNames(std::string detectorNames); + +} // namespace NXcanSAS +} // namespace DataHandling +} // namespace Mantid diff --git a/Framework/DataHandling/src/NXcanSASHelper.cpp b/Framework/DataHandling/src/NXcanSASHelper.cpp new file mode 100644 index 000000000000..dbe8074c711d --- /dev/null +++ b/Framework/DataHandling/src/NXcanSASHelper.cpp @@ -0,0 +1,670 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2025 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidDataHandling/NXcanSASHelper.h" +#include "MantidAPI/Axis.h" +#include "MantidAPI/ExperimentInfo.h" +#include "MantidAPI/InstrumentFileFinder.h" +#include "MantidAPI/Run.h" +#include "MantidDataHandling/NXcanSASDefinitions.h" +#include "MantidDataObjects/Workspace2D.h" +#include "MantidGeometry/Instrument.h" +#include "MantidGeometry/MDGeometry/IMDDimension.h" +#include "MantidKernel/MDUnit.h" +#include "MantidKernel/MantidVersion.h" +#include "MantidKernel/VectorHelper.h" +#include "MantidNexus/H5Util.h" + +#include +#include +#include +#include +#include + +using namespace Mantid::Kernel; +using namespace Mantid::Geometry; +using namespace Mantid::API; +using namespace Mantid::DataHandling::NXcanSAS; + +namespace { + +//------- SASFileName + +bool isCanSASCompliant(bool isStrict, const std::string &input) { + auto baseRegex = isStrict ? boost::regex("[a-z_][a-z0-9_]*") : boost::regex("[A-Za-z_][\\w_]*"); + return boost::regex_match(input, baseRegex); +} + +void removeSpecialCharacters(std::string &input) { + boost::regex toReplace("[-\\.]"); + std::string replaceWith("_"); + input = boost::regex_replace(input, toReplace, replaceWith); +} + +std::string makeCompliantName(const std::string &input, bool isStrict, + const std::function &capitalizeStrategy) { + auto output = input; + // Check if input is compliant + if (!isCanSASCompliant(isStrict, output)) { + removeSpecialCharacters(output); + capitalizeStrategy(output); + // Check if the changes have made it compliant + if (!isCanSASCompliant(isStrict, output)) { + std::string message = "SaveNXcanSAS: The input " + input + "is not compliant with the NXcanSAS format."; + throw std::runtime_error(message); + } + } + return output; +} + +//------- SASinstrument + +std::string getInstrumentName(const Mantid::API::MatrixWorkspace_sptr &workspace) { + return workspace->getInstrument()->getFullName(); +} + +std::string getIDF(const Mantid::API::MatrixWorkspace_sptr &workspace) { + auto date = workspace->getWorkspaceStartDate(); + auto instrumentName = getInstrumentName(workspace); + return InstrumentFileFinder::getInstrumentFilename(instrumentName, date); +} + +//------- SASprocess + +std::string getDate() { + time_t rawtime; + time(&rawtime); + char temp[25]; + strftime(temp, 25, "%Y-%m-%dT%H:%M:%S", localtime(&rawtime)); + std::string sasDate(temp); + return sasDate; +} + +/** Write a property value to the H5 file if the property exists in the run + * + * @param run : the run to look for the property in + * @param propertyName : the name of the property to find + * @param sasGroup : the group to add the term into in the output file + * @param sasTerm : the name of the term to add + */ +void addPropertyFromRunIfExists(Run const &run, std::string const &propertyName, H5::Group &sasGroup, + std::string const &sasTerm) { + if (run.hasProperty(propertyName)) { + const auto *property = run.getProperty(propertyName); + Mantid::NeXus::H5Util::write(sasGroup, sasTerm, property->value()); + } +} + +//------- SASData + +std::string getIntensityUnitLabel(const std::string &intensityUnitLabel) { + if (intensityUnitLabel == "I(q) (cm-1)") { + return sasIntensity; + } + return intensityUnitLabel; +} + +std::string getIntensityUnit(const Mantid::API::MatrixWorkspace_sptr &workspace) { + auto iUnit = workspace->YUnit(); + if (iUnit.empty()) { + iUnit = workspace->YUnitLabel(); + } + return iUnit; +} + +std::string getMomentumTransferLabel(const std::string &momentumTransferLabel) { + if (momentumTransferLabel == "Angstrom^-1") { + return sasMomentumTransfer; + } + return momentumTransferLabel; +} + +std::string getUnitFromMDDimension(const Mantid::Geometry::IMDDimension_const_sptr &dimension) { + const auto unitLabel = dimension->getMDUnits().getUnitLabel(); + return unitLabel.ascii(); +} + +bool areAxesNumeric(const Mantid::API::MatrixWorkspace_sptr &workspace) { + const std::array indices = {0, 1}; + return std::all_of(indices.cbegin(), indices.cend(), + [workspace](auto const &index) { return workspace->getAxis(index)->isNumeric(); }); +} + +template +void writeArray1DWithStrAttributes(H5::Group &group, const std::string &dataSetName, const std::vector &values, + const std::map &attributes) { + Mantid::NeXus::H5Util::writeArray1D(group, dataSetName, values); + auto dataSet = group.openDataSet(dataSetName); + for (const auto &attribute : attributes) { + Mantid::NeXus::H5Util::writeStrAttribute(dataSet, attribute.first, attribute.second); + } +} + +H5::DSetCreatPropList setCompression2D(const hsize_t *chunkDims, const int deflateLevel = 6) { + H5::DSetCreatPropList propList; + constexpr int rank = 2; + propList.setChunk(rank, chunkDims); + propList.setDeflate(deflateLevel); + return propList; +} + +template +void write2DWorkspace(H5::Group &group, Mantid::API::MatrixWorkspace_sptr workspace, const std::string &dataSetName, + Functor func, const std::map &attributes) { + using namespace Mantid::NeXus::H5Util; + + // Set the dimension + const size_t dimension0 = workspace->getNumberHistograms(); + const size_t dimension1 = workspace->y(0).size(); + constexpr hsize_t rank = 2; + hsize_t dimensionArray[rank] = {static_cast(dimension0), static_cast(dimension1)}; + + // Start position in the 2D data (indexed) data structure + hsize_t start[rank] = {0, 0}; + + // Size of a slab + hsize_t sizeOfSingleSlab[rank] = {1, dimensionArray[1]}; + + // Get the Data Space definition for the 2D Data Set in the file + auto fileSpace = H5::DataSpace(rank, dimensionArray); + H5::DataType dataType(getType()); + + // Get the proplist with compression settings + H5::DSetCreatPropList propList = setCompression2D(sizeOfSingleSlab); + + // Create the data set + auto dataSet = group.createDataSet(dataSetName, dataType, fileSpace, propList); + + // Create Data Spae for 1D entry for each row in memory + hsize_t memSpaceDimension[1] = {dimension1}; + H5::DataSpace memSpace(1, memSpaceDimension); + + // Insert each row of the workspace as a slab + for (unsigned int index = 0; index < dimension0; ++index) { + // Need the data space + fileSpace.selectHyperslab(H5S_SELECT_SET, sizeOfSingleSlab, start); + + // Write the correct data set to file + dataSet.write(func(workspace, index), dataType, memSpace, fileSpace); + // Step up the write position + ++start[0]; + } + + // Add attributes to data set + for (const auto &[nameAttr, valueAttr] : attributes) { + writeStrAttribute(dataSet, nameAttr, valueAttr); + } +} + +class SpectrumAxisValueProvider { +public: + explicit SpectrumAxisValueProvider(Mantid::API::MatrixWorkspace_sptr workspace) : m_workspace(std::move(workspace)) { + setSpectrumAxisValues(); + } + + Mantid::MantidVec::value_type *operator()(const Mantid::API::MatrixWorkspace_sptr & /*unused*/, int index) { + auto isPointData = m_workspace->getNumberHistograms() == m_spectrumAxisValues.size(); + double value = 0; + if (isPointData) { + value = m_spectrumAxisValues[index]; + } else { + value = (m_spectrumAxisValues[index + 1] + m_spectrumAxisValues[index]) / 2.0; + } + + Mantid::MantidVec tempVec(m_workspace->dataY(index).size(), value); + m_currentAxisValues.swap(tempVec); + return m_currentAxisValues.data(); + } + +private: + void setSpectrumAxisValues() { + auto sAxis = m_workspace->getAxis(1); + for (size_t index = 0; index < sAxis->length(); ++index) { + m_spectrumAxisValues.emplace_back((*sAxis)(index)); + } + } + + Mantid::API::MatrixWorkspace_sptr m_workspace; + Mantid::MantidVec m_spectrumAxisValues; + Mantid::MantidVec m_currentAxisValues; +}; + +/** + * QxExtractor functor which allows us to convert 2D Qx data into point data. + */ +template class QxExtractor { +public: + T *operator()(const Mantid::API::MatrixWorkspace_sptr &ws, int index) { + if (ws->isHistogramData()) { + qxPointData.clear(); + Mantid::Kernel::VectorHelper::convertToBinCentre(ws->dataX(index), qxPointData); + return qxPointData.data(); + } else { + return ws->dataX(index).data(); + } + } + + std::vector qxPointData; +}; + +} // namespace + +namespace Mantid::DataHandling::NXcanSAS { +/** + * This makes out of an input a relaxed name, something conforming to + * "[A-Za-z_][\w_]*" + * For now "-" is converted to "_", "." is converted to "_", else we throw + */ +std::string makeCanSASRelaxedName(const std::string &input) { + bool isStrict = false; + auto emptyCapitalizationStrategy = [](std::string &) {}; + return makeCompliantName(input, isStrict, emptyCapitalizationStrategy); +} + +void addDetectors(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace, + const std::vector &detectorNames) { + // If the group is empty then don't add anything + if (!detectorNames.empty()) { + for (const auto &detectorName : detectorNames) { + if (detectorName.empty()) { + continue; + } + + std::string sasDetectorName = sasInstrumentDetectorGroupName + detectorName; + sasDetectorName = makeCanSASRelaxedName(sasDetectorName); + + auto instrument = workspace->getInstrument(); + auto component = instrument->getComponentByName(detectorName); + + if (component) { + const auto sample = instrument->getSample(); + const auto distance = component->getDistance(*sample); + std::map sddAttributes; + sddAttributes.insert(std::make_pair(sasUnitAttr, sasInstrumentDetectorSddUnitAttrValue)); + auto detector = Mantid::NeXus::H5Util::createGroupCanSAS(group, sasDetectorName, nxInstrumentDetectorClassAttr, + sasInstrumentDetectorClassAttr); + Mantid::NeXus::H5Util::write(detector, sasInstrumentDetectorName, detectorName); + Mantid::NeXus::H5Util::writeScalarDataSetWithStrAttributes(detector, sasInstrumentDetectorSdd, distance, + sddAttributes); + } + } + } +} + +/** + * Add the instrument group to the NXcanSAS file. This adds the + * instrument name and the IDF + * @param group: the sasEntry + * @param workspace: the workspace which is being stored + * @param radiationSource: the selcted radiation source + * @param detectorNames: the names of the detectors to store + */ +void addInstrument(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace, + const std::string &radiationSource, const std::string &geometry, double beamHeight, double beamWidth, + const std::vector &detectorNames) { + // Setup instrument + const std::string sasInstrumentNameForGroup = sasInstrumentGroupName; + auto instrument = Mantid::NeXus::H5Util::createGroupCanSAS(group, sasInstrumentNameForGroup, nxInstrumentClassAttr, + sasInstrumentClassAttr); + auto instrumentName = getInstrumentName(workspace); + Mantid::NeXus::H5Util::write(instrument, sasInstrumentName, instrumentName); + + // Setup the detector + addDetectors(instrument, workspace, detectorNames); + + // Setup source + const std::string sasSourceName = sasInstrumentSourceGroupName; + auto source = Mantid::NeXus::H5Util::createGroupCanSAS(instrument, sasSourceName, nxInstrumentSourceClassAttr, + sasInstrumentSourceClassAttr); + Mantid::NeXus::H5Util::write(source, sasInstrumentSourceRadiation, radiationSource); + + // Setup Aperture + const std::string sasApertureName = sasInstrumentApertureGroupName; + auto aperture = Mantid::NeXus::H5Util::createGroupCanSAS(instrument, sasApertureName, nxInstrumentApertureClassAttr, + sasInstrumentApertureClassAttr); + + Mantid::NeXus::H5Util::write(aperture, sasInstrumentApertureShape, geometry); + + std::map beamSizeAttrs; + beamSizeAttrs.insert(std::make_pair(sasUnitAttr, sasBeamAndSampleSizeUnitAttrValue)); + if (beamHeight != 0) { + Mantid::NeXus::H5Util::writeScalarDataSetWithStrAttributes(aperture, sasInstrumentApertureGapHeight, beamHeight, + beamSizeAttrs); + } + if (beamWidth != 0) { + Mantid::NeXus::H5Util::writeScalarDataSetWithStrAttributes(aperture, sasInstrumentApertureGapWidth, beamWidth, + beamSizeAttrs); + } + + // Add IDF information + auto idf = getIDF(workspace); + Mantid::NeXus::H5Util::write(instrument, sasInstrumentIDF, idf); +} + +//------- SASsample + +void addSample(H5::Group &group, const double &sampleThickness) { + if (sampleThickness == 0) { + return; + } + std::string const sasSampleNameForGroup = sasInstrumentSampleGroupAttr; + + auto sample = Mantid::NeXus::H5Util::createGroupCanSAS(group, sasSampleNameForGroup, nxInstrumentSampleClassAttr, + sasInstrumentSampleClassAttr); + + std::map sampleThicknessAttrs; + sampleThicknessAttrs.insert(std::make_pair(sasUnitAttr, sasBeamAndSampleSizeUnitAttrValue)); + Mantid::NeXus::H5Util::writeScalarDataSetWithStrAttributes(sample, sasInstrumentSampleThickness, sampleThickness, + sampleThicknessAttrs); +} + +/** + * Add the process information to the NXcanSAS file. This information + * about the run number, the Mantid version and the user file (if available) + * @param group: the sasEntry + * @param workspace: the workspace which is being stored + */ +void addProcess(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace) { + // Setup process + const std::string sasProcessNameForGroup = sasProcessGroupName; + auto process = + Mantid::NeXus::H5Util::createGroupCanSAS(group, sasProcessNameForGroup, nxProcessClassAttr, sasProcessClassAttr); + + // Add name + Mantid::NeXus::H5Util::write(process, sasProcessName, sasProcessNameValue); + + // Add creation date of the file + auto date = getDate(); + Mantid::NeXus::H5Util::write(process, sasProcessDate, date); + + // Add Mantid version + const auto version = std::string(MantidVersion::version()); + Mantid::NeXus::H5Util::write(process, sasProcessTermSvn, version); + + // Add log values + const auto run = workspace->run(); + addPropertyFromRunIfExists(run, sasProcessUserFileInLogs, process, sasProcessTermUserFile); + addPropertyFromRunIfExists(run, sasProcessBatchFileInLogs, process, sasProcessTermBatchFile); +} + +/** + * Add the process information to the NXcanSAS file. This information + * about the run number, the Mantid version and the user file (if available) + * @param group: the sasEntry + * @param workspace: the workspace which is being stored + * @param canWorkspace: workspace for the can run + */ +void addProcess(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace, + const Mantid::API::MatrixWorkspace_sptr &canWorkspace) { + // Setup process + const std::string sasProcessNameForGroup = sasProcessGroupName; + auto process = + Mantid::NeXus::H5Util::createGroupCanSAS(group, sasProcessNameForGroup, nxProcessClassAttr, sasProcessClassAttr); + + // Add name + Mantid::NeXus::H5Util::write(process, sasProcessName, sasProcessNameValue); + + // Add creation date of the file + auto date = getDate(); + Mantid::NeXus::H5Util::write(process, sasProcessDate, date); + + // Add Mantid version + const auto version = std::string(MantidVersion::version()); + Mantid::NeXus::H5Util::write(process, sasProcessTermSvn, version); + + const auto run = workspace->run(); + addPropertyFromRunIfExists(run, sasProcessUserFileInLogs, process, sasProcessTermUserFile); + addPropertyFromRunIfExists(run, sasProcessBatchFileInLogs, process, sasProcessTermBatchFile); + + // Add can run number + const auto canRun = canWorkspace->getRunNumber(); + Mantid::NeXus::H5Util::write(process, sasProcessTermCan, std::to_string(canRun)); +} + +/** + * Add an entry to the process group. + * @param group: the sasEntry + * @param entryName: string containing the name of the value to save + * @param entryValue: string containing the value to save + */ +void addProcessEntry(H5::Group &group, const std::string &entryName, const std::string &entryValue) { + auto process = group.openGroup(sasProcessGroupName); + // Populate process entry + Mantid::NeXus::H5Util::write(process, entryName, entryValue); +} + +//------- SAStransmission_spectrum +void addTransmission(H5::Group &group, const Mantid::API::MatrixWorkspace_const_sptr &workspace, + const std::string &transmissionName) { + // Setup process + const std::string sasTransmissionName = sasTransmissionSpectrumGroupName + "_" + transmissionName; + auto transmission = Mantid::NeXus::H5Util::createGroupCanSAS( + group, sasTransmissionName, nxTransmissionSpectrumClassAttr, sasTransmissionSpectrumClassAttr); + + // Add attributes for @signal, @T_axes, @T_indices, @T_uncertainty, + // @T_uncertainties, @name, @timestamp + Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasSignal, sasTransmissionSpectrumT); + Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasTransmissionSpectrumTIndices, sasTransmissionSpectrumT); + Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasTransmissionSpectrumTUncertainty, + sasTransmissionSpectrumTdev); + Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasTransmissionSpectrumTUncertainties, + sasTransmissionSpectrumTdev); + Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasTransmissionSpectrumNameAttr, transmissionName); + + auto date = getDate(); + Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasTransmissionSpectrumTimeStampAttr, date); + + //----------------------------------------- + // Add T with units + uncertainty definition + const auto transmissionData = workspace->y(0); + std::map transmissionAttributes; + std::string unit = sasNone; + + transmissionAttributes.emplace(sasUnitAttr, unit); + transmissionAttributes.emplace(sasUncertaintyAttr, sasTransmissionSpectrumTdev); + transmissionAttributes.emplace(sasUncertaintiesAttr, sasTransmissionSpectrumTdev); + + writeArray1DWithStrAttributes(transmission, sasTransmissionSpectrumT, transmissionData.rawData(), + transmissionAttributes); + + //----------------------------------------- + // Add Tdev with units + const auto &transmissionErrors = workspace->e(0); + std::map transmissionErrorAttributes; + transmissionErrorAttributes.emplace(sasUnitAttr, unit); + + writeArray1DWithStrAttributes(transmission, sasTransmissionSpectrumTdev, transmissionErrors.rawData(), + transmissionErrorAttributes); + + //----------------------------------------- + // Add lambda with units + const auto lambda = workspace->points(0); + std::map lambdaAttributes; + auto lambdaUnit = getUnitFromMDDimension(workspace->getDimension(0)); + if (lambdaUnit.empty() || lambdaUnit == "Angstrom") { + lambdaUnit = sasAngstrom; + } + lambdaAttributes.emplace(sasUnitAttr, lambdaUnit); + + writeArray1DWithStrAttributes(transmission, sasTransmissionSpectrumLambda, lambda.rawData(), lambdaAttributes); +} + +void addData1D(H5::Group &data, const Mantid::API::MatrixWorkspace_sptr &workspace) { + // Add attributes for @signal, @I_axes, @Q_indices, + Mantid::NeXus::H5Util::writeStrAttribute(data, sasSignal, sasDataI); + Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIAxesAttr, sasDataQ); + Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIUncertaintyAttr, sasDataIdev); + Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIUncertaintiesAttr, sasDataIdev); + Mantid::NeXus::H5Util::writeNumAttribute(data, sasDataQIndicesAttr, std::vector{0}); + + if (workspace->hasDx(0)) { + Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataQUncertaintyAttr, sasDataQdev); + Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataQUncertaintiesAttr, sasDataQdev); + } + + //----------------------------------------- + // Add Q with units + uncertainty definition + const auto &qValue = workspace->points(0); + std::map qAttributes; + auto qUnit = getUnitFromMDDimension(workspace->getDimension(0)); + qUnit = getMomentumTransferLabel(qUnit); + qAttributes.emplace(sasUnitAttr, qUnit); + if (workspace->hasDx(0)) { + qAttributes.emplace(sasUncertaintyAttr, sasDataQdev); + qAttributes.emplace(sasUncertaintiesAttr, sasDataQdev); + } + + writeArray1DWithStrAttributes(data, sasDataQ, qValue.rawData(), qAttributes); + + //----------------------------------------- + // Add I with units + uncertainty definition + const auto &intensity = workspace->y(0); + std::map iAttributes; + auto iUnit = getIntensityUnit(workspace); + iUnit = getIntensityUnitLabel(iUnit); + iAttributes.emplace(sasUnitAttr, iUnit); + iAttributes.emplace(sasUncertaintyAttr, sasDataIdev); + iAttributes.emplace(sasUncertaintiesAttr, sasDataIdev); + + writeArray1DWithStrAttributes(data, sasDataI, intensity.rawData(), iAttributes); + + //----------------------------------------- + // Add Idev with units + const auto &intensityUncertainty = workspace->e(0); + std::map eAttributes; + eAttributes.insert(std::make_pair(sasUnitAttr, iUnit)); // same units as intensity + + writeArray1DWithStrAttributes(data, sasDataIdev, intensityUncertainty.rawData(), eAttributes); + + //----------------------------------------- + // Add Qdev with units if available + if (workspace->hasDx(0)) { + const auto qResolution = workspace->pointStandardDeviations(0); + std::map xUncertaintyAttributes; + xUncertaintyAttributes.emplace(sasUnitAttr, qUnit); + + writeArray1DWithStrAttributes(data, sasDataQdev, qResolution.rawData(), xUncertaintyAttributes); + } +} + +/** + * Stores the 2D data in the HDF5 file. Qx and Qy values need to be stored as a + *meshgrid. + * They should be stored as point data. + * @param data: the hdf5 group + * @param workspace: the workspace to store + * + * Workspace looks like this in Mantid Matrix + * (Qx) 0 1 2 ... M (first dimension) + * (QY) + * 0 IQx0Qy0 IQx1Qy0 IQx2Qy0 ... IQxMQy0 + * 1 IQx0Qy1 IQx1Qy1 IQx2Qy1 ... IQxMQy1 + * 2 IQx0Qy2 IQx1Qy2 IQx2Qy2 ... IQxMQy2 + * 3 IQx0Qy3 IQx1Qy3 IQx2Qy3 ... IQxMQy3 + * . + * . + * N IQx0QyN IQx1QyN IQx2QyN ... IQxMQyN + * (second dimension) + * + * The layout below is how it would look like in the HDFView, ie vertical axis + * is first dimension. We map the Mantid Matrix layout 1-to-1. Note that this + * will swap the matrix indices, but this is how it is done in the other + *2Dloaders + * + * In HDF5 the Qx would need to be stored as: + * Qx1 Qx2 ... QxM + * Qx1 Qx2 ... QxM + * Qx1 Qx2 ... QxM + * . + * . + * Qx1 Qx2 ... QxM + * + * In HDF5 the Qy would need to be stored as: + * Qy1 Qy1 ... Qy1 + * Qy2 Qy2 ... Qy2 + * Qy3 Qy3 ... Qy3 + * . + * . + * QxN QxN ... QxN + */ +void addData2D(H5::Group &data, const Mantid::API::MatrixWorkspace_sptr &workspace) { + if (!areAxesNumeric(workspace)) { + throw std::invalid_argument("SaveNXcanSAS: The provided 2D workspace needs to have 2 numeric axes."); + } + // Add attributes for @signal, @I_axes, @Q_indices, + Mantid::NeXus::H5Util::writeStrAttribute(data, sasSignal, sasDataI); + const std::string sasDataIAxesAttr2D = sasDataQ + sasSeparator + sasDataQ; + Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIAxesAttr, sasDataIAxesAttr2D); + Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIUncertaintyAttr, sasDataIdev); + Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIUncertaintiesAttr, sasDataIdev); + // Write the Q Indices as Int Array + Mantid::NeXus::H5Util::writeNumAttribute(data, sasDataQIndicesAttr, std::vector{0, 1}); + + // Store the 2D Qx data + units + std::map qxAttributes; + auto qxUnit = getUnitFromMDDimension(workspace->getXDimension()); + qxUnit = getMomentumTransferLabel(qxUnit); + qxAttributes.emplace(sasUnitAttr, qxUnit); + QxExtractor qxExtractor; + write2DWorkspace(data, workspace, sasDataQx, qxExtractor, qxAttributes); + + // Get 2D Qy data and store it + std::map qyAttributes; + auto qyUnit = getUnitFromMDDimension(workspace->getDimension(1)); + qyUnit = getMomentumTransferLabel(qyUnit); + qyAttributes.emplace(sasUnitAttr, qyUnit); + + SpectrumAxisValueProvider spectrumAxisValueProvider(workspace); + write2DWorkspace(data, workspace, sasDataQy, spectrumAxisValueProvider, qyAttributes); + + // Get 2D I data and store it + std::map iAttributes; + auto iUnit = getIntensityUnit(workspace); + iUnit = getIntensityUnitLabel(iUnit); + iAttributes.emplace(sasUnitAttr, iUnit); + iAttributes.emplace(sasUncertaintyAttr, sasDataIdev); + iAttributes.emplace(sasUncertaintiesAttr, sasDataIdev); + + auto iExtractor = [](const Mantid::API::MatrixWorkspace_sptr &ws, int index) { return ws->dataY(index).data(); }; + write2DWorkspace(data, workspace, sasDataI, iExtractor, iAttributes); + + // Get 2D Idev data and store it + std::map eAttributes; + eAttributes.insert(std::make_pair(sasUnitAttr, iUnit)); // same units as intensity + + auto iDevExtractor = [](const Mantid::API::MatrixWorkspace_sptr &ws, int index) { return ws->dataE(index).data(); }; + write2DWorkspace(data, workspace, sasDataIdev, iDevExtractor, eAttributes); +} + +WorkspaceDimensionality getWorkspaceDimensionality(const Mantid::API::MatrixWorkspace_sptr &workspace) { + auto numberOfHistograms = workspace->getNumberHistograms(); + WorkspaceDimensionality dimensionality(WorkspaceDimensionality::other); + if (numberOfHistograms == 1) { + dimensionality = WorkspaceDimensionality::oneD; + } else if (numberOfHistograms > 1) { + dimensionality = WorkspaceDimensionality::twoD; + } + return dimensionality; +} + +std::vector splitDetectorNames(std::string detectorNames) { + const std::string delimiter = ","; + std::vector detectors; + size_t pos(0); + + while ((pos = detectorNames.find(delimiter)) != std::string::npos) { + std::string detectorName = detectorNames.substr(0, pos); + boost::algorithm::trim(detectorName); + detectors.emplace_back(detectorName); + detectorNames.erase(0, pos + delimiter.length()); + } + // Push remaining element + boost::algorithm::trim(detectorNames); + detectors.emplace_back(detectorNames); + return detectors; +} + +} // namespace Mantid::DataHandling::NXcanSAS From c8898d09d9b2eb517b9692d75f777f26756cbd83 Mon Sep 17 00:00:00 2001 From: adriazalvarez Date: Thu, 9 Jan 2025 13:17:36 +0000 Subject: [PATCH 4/9] Referene to helper file in tests --- Framework/DataHandling/test/SaveNXcanSASTest.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Framework/DataHandling/test/SaveNXcanSASTest.h b/Framework/DataHandling/test/SaveNXcanSASTest.h index 97253dfefe51..45ca02951f61 100644 --- a/Framework/DataHandling/test/SaveNXcanSASTest.h +++ b/Framework/DataHandling/test/SaveNXcanSASTest.h @@ -12,6 +12,7 @@ #include "MantidAPI/Axis.h" #include "MantidAPI/MatrixWorkspace.h" #include "MantidDataHandling/NXcanSASDefinitions.h" +#include "MantidDataHandling/NXcanSASHelper.h" #include "MantidDataHandling/SaveNXcanSAS.h" #include "MantidFrameworkTestHelpers/WorkspaceCreationHelper.h" #include "MantidGeometry/Instrument.h" @@ -512,7 +513,7 @@ class SaveNXcanSASTest : public CxxTest::TestSuite { void do_assert_detector(H5::Group &instrument, const std::vector &detectors) { for (auto &detector : detectors) { std::string detectorName = sasInstrumentDetectorGroupName + detector; - auto detectorNameSanitized = Mantid::DataHandling::makeCanSASRelaxedName(detectorName); + auto detectorNameSanitized = Mantid::DataHandling::NXcanSAS::makeCanSASRelaxedName(detectorName); auto detectorGroup = instrument.openGroup(detectorNameSanitized); auto numAttributes = detectorGroup.getNumAttrs(); From 52b5e3ac712fe25760a20e2a841047a8d4861acc Mon Sep 17 00:00:00 2001 From: adriazalvarez Date: Thu, 9 Jan 2025 13:18:25 +0000 Subject: [PATCH 5/9] Organize code, add some descriptions and refactor some individual lines --- .../inc/MantidDataHandling/SaveNXcanSAS.h | 1 - .../inc/MantidDataHandling/SaveNXcanSASBase.h | 64 +- Framework/DataHandling/src/SaveNXcanSAS.cpp | 75 +- .../DataHandling/src/SaveNXcanSASBase.cpp | 746 ++---------------- 4 files changed, 101 insertions(+), 785 deletions(-) diff --git a/Framework/DataHandling/inc/MantidDataHandling/SaveNXcanSAS.h b/Framework/DataHandling/inc/MantidDataHandling/SaveNXcanSAS.h index 55e79ff28e63..25d4ee5bd1a8 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/SaveNXcanSAS.h +++ b/Framework/DataHandling/inc/MantidDataHandling/SaveNXcanSAS.h @@ -6,7 +6,6 @@ // SPDX - License - Identifier: GPL - 3.0 + #pragma once -#include "MantidAPI/Algorithm.h" #include "MantidDataHandling/DllConfig.h" #include "MantidDataHandling/SaveNXcanSASBase.h" diff --git a/Framework/DataHandling/inc/MantidDataHandling/SaveNXcanSASBase.h b/Framework/DataHandling/inc/MantidDataHandling/SaveNXcanSASBase.h index 307341b9e5b8..3ddfe31c1c1b 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/SaveNXcanSASBase.h +++ b/Framework/DataHandling/inc/MantidDataHandling/SaveNXcanSASBase.h @@ -1,32 +1,32 @@ -// Mantid Repository : https://github.com/mantidproject/mantid -// -// Copyright © 2016 ISIS Rutherford Appleton Laboratory UKRI, -// NScD Oak Ridge National Laboratory, European Spallation Source, -// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS -// SPDX - License - Identifier: GPL - 3.0 + -#pragma once - -#include "MantidAPI/Algorithm.h" -#include "MantidAPI/MatrixWorkspace_fwd.h" -#include "MantidDataHandling/DllConfig.h" -#include "MantidGeometry/Instrument_fwd.h" -#include - -namespace Mantid { -namespace DataHandling { - -/** SaveNXcanSAS : Saves a reduced workspace in the NXcanSAS format. Currently * only MatrixWorkspaces resulting from - * 1D and 2D reductions are supported. */ -class MANTID_DATAHANDLING_DLL SaveNXcanSASBase : public API::Algorithm { -protected: - void initStandardProperties(); - void addStandardMetadata(Mantid::API::MatrixWorkspace_sptr &workspace, H5::Group &sasEntry, - Mantid::API::Progress *progress); - void addData(H5::Group &data, const Mantid::API::MatrixWorkspace_sptr &workspace); - H5::Group addSasEntry(H5::H5File &file, const Mantid::API::MatrixWorkspace_sptr &workspace, - const std::string &suffix); -}; - -std::string MANTID_DATAHANDLING_DLL makeCanSASRelaxedName(const std::string &input); -} // namespace DataHandling -} // namespace Mantid +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2025 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#pragma once + +#include "MantidAPI/Algorithm.h" +#include "MantidAPI/MatrixWorkspace_fwd.h" +#include "MantidDataHandling/DllConfig.h" +#include + +namespace Mantid { +namespace DataHandling { + +/** SaveNXcanSASBase : Base class to save a reduced workspace in the NXcanSAS format. Currently + * only MatrixWorkspaces resulting from + * 1D and 2D reductions are supported. + */ +class MANTID_DATAHANDLING_DLL SaveNXcanSASBase : public API::Algorithm { +protected: + void addStandardMetadata(Mantid::API::MatrixWorkspace_sptr &workspace, H5::Group &sasEntry); + void addData(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace); + H5::Group addSasEntry(H5::H5File &file, const Mantid::API::MatrixWorkspace_sptr &workspace, + const std::string &suffix); + void initStandardProperties(); + std::map validateStandardInputs(); +}; + +} // namespace DataHandling +} // namespace Mantid diff --git a/Framework/DataHandling/src/SaveNXcanSAS.cpp b/Framework/DataHandling/src/SaveNXcanSAS.cpp index 410509d181a3..65ea5691b1fd 100644 --- a/Framework/DataHandling/src/SaveNXcanSAS.cpp +++ b/Framework/DataHandling/src/SaveNXcanSAS.cpp @@ -4,44 +4,13 @@ // NScD Oak Ridge National Laboratory, European Spallation Source, // Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS // SPDX - License - Identifier: GPL - 3.0 + + #include "MantidDataHandling/SaveNXcanSAS.h" -#include "MantidAPI/Axis.h" -#include "MantidAPI/CommonBinsValidator.h" -#include "MantidAPI/ExperimentInfo.h" -#include "MantidAPI/FileProperty.h" -#include "MantidAPI/InstrumentFileFinder.h" #include "MantidAPI/Progress.h" -#include "MantidAPI/Run.h" -#include "MantidAPI/WorkspaceUnitValidator.h" -#include "MantidDataHandling/NXcanSASDefinitions.h" -#include "MantidDataObjects/Workspace2D.h" -#include "MantidGeometry/Instrument.h" -#include "MantidGeometry/MDGeometry/IMDDimension.h" -#include "MantidKernel/BoundedValidator.h" -#include "MantidKernel/CompositeValidator.h" -#include "MantidKernel/ListValidator.h" -#include "MantidKernel/MDUnit.h" -#include "MantidKernel/MantidVersion.h" -#include "MantidKernel/VectorHelper.h" -#include "MantidNexus/H5Util.h" - -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -using namespace Mantid::Kernel; -using namespace Mantid::Geometry; using namespace Mantid::API; -using namespace Mantid::DataHandling::NXcanSAS; namespace Mantid::DataHandling { // Register the algorithm into the AlgorithmFactory @@ -52,37 +21,10 @@ SaveNXcanSAS::SaveNXcanSAS() = default; void SaveNXcanSAS::init() { initStandardProperties(); } -std::map SaveNXcanSAS::validateInputs() { - // The input should be a Workspace2D - Mantid::API::MatrixWorkspace_sptr workspace = getProperty("InputWorkspace"); - std::map result; - if (!workspace || !std::dynamic_pointer_cast(workspace)) { - result.emplace("InputWorkspace", "The InputWorkspace must be a Workspace2D."); - } - - // Transmission data should be 1D - Mantid::API::MatrixWorkspace_sptr transmission = getProperty("Transmission"); - Mantid::API::MatrixWorkspace_sptr transmissionCan = getProperty("TransmissionCan"); - - auto checkTransmission = [&result](const Mantid::API::MatrixWorkspace_sptr &trans, const std::string &propertyName) { - if (trans->getNumberHistograms() != 1) { - result.emplace(propertyName, "The input workspaces for transmissions have to be 1D."); - } - }; - - if (transmission) { - checkTransmission(transmission, "Transmission"); - } - - if (transmissionCan) { - checkTransmission(transmissionCan, "TransmissionCan"); - } - - return result; -} +std::map SaveNXcanSAS::validateInputs() { return validateStandardInputs(); } void SaveNXcanSAS::exec() { - Mantid::API::MatrixWorkspace_sptr &&workspace = getProperty("InputWorkspace"); + std::string &&filename = getPropertyValue("Filename"); // Remove the file if it already exists if (Poco::File(filename).exists()) { @@ -90,14 +32,19 @@ void SaveNXcanSAS::exec() { } H5::H5File file(filename, H5F_ACC_EXCL); - auto numberOfSteps = 4; - Progress progress(this, 0.1, 1.0, numberOfSteps); + + Progress progress(this, 0.1, 1.0, 3); progress.report("Adding a new entry."); + + Mantid::API::MatrixWorkspace_sptr &&workspace = getProperty("InputWorkspace"); // add sas entry const std::string suffix("01"); auto sasEntry = addSasEntry(file, workspace, suffix); + // Add metadata for canSAS file: Instrument, Sample, Process - addStandardMetadata(workspace, sasEntry, &progress); + progress.report("Adding standard metadata"); + addStandardMetadata(workspace, sasEntry); + // Add the data progress.report("Adding data."); addData(sasEntry, workspace); diff --git a/Framework/DataHandling/src/SaveNXcanSASBase.cpp b/Framework/DataHandling/src/SaveNXcanSASBase.cpp index 5c7b0c4e66ff..c2114a40eba3 100644 --- a/Framework/DataHandling/src/SaveNXcanSASBase.cpp +++ b/Framework/DataHandling/src/SaveNXcanSASBase.cpp @@ -1,677 +1,28 @@ // Mantid Repository : https://github.com/mantidproject/mantid // -// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// Copyright © 2025 ISIS Rutherford Appleton Laboratory UKRI, // NScD Oak Ridge National Laboratory, European Spallation Source, // Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS // SPDX - License - Identifier: GPL - 3.0 + -#include "MantidAPI/Axis.h" + +#include "MantidDataHandling/SaveNXcanSASBase.h" #include "MantidAPI/CommonBinsValidator.h" -#include "MantidAPI/ExperimentInfo.h" #include "MantidAPI/FileProperty.h" -#include "MantidAPI/InstrumentFileFinder.h" -#include "MantidAPI/Progress.h" -#include "MantidAPI/Run.h" #include "MantidAPI/WorkspaceUnitValidator.h" #include "MantidDataHandling/NXcanSASDefinitions.h" -#include "MantidDataHandling/SaveNXcanSAS.h" +#include "MantidDataHandling/NXcanSASHelper.h" #include "MantidDataObjects/Workspace2D.h" -#include "MantidGeometry/Instrument.h" -#include "MantidGeometry/MDGeometry/IMDDimension.h" -#include "MantidKernel/BoundedValidator.h" #include "MantidKernel/CompositeValidator.h" #include "MantidKernel/ListValidator.h" -#include "MantidKernel/MDUnit.h" -#include "MantidKernel/MantidVersion.h" -#include "MantidKernel/VectorHelper.h" #include "MantidNexus/H5Util.h" -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -using namespace Mantid::Kernel; -using namespace Mantid::Geometry; using namespace Mantid::API; using namespace Mantid::DataHandling::NXcanSAS; -namespace { - -enum class StoreType { Qx, Qy, I, Idev, Other }; - -bool isCanSASCompliant(bool isStrict, const std::string &input) { - auto baseRegex = isStrict ? boost::regex("[a-z_][a-z0-9_]*") : boost::regex("[A-Za-z_][\\w_]*"); - return boost::regex_match(input, baseRegex); -} - -void removeSpecialCharacters(std::string &input) { - boost::regex toReplace("[-\\.]"); - std::string replaceWith("_"); - input = boost::regex_replace(input, toReplace, replaceWith); -} - -std::string makeCompliantName(const std::string &input, bool isStrict, - const std::function &capitalizeStrategy) { - auto output = input; - // Check if input is compliant - if (!isCanSASCompliant(isStrict, output)) { - removeSpecialCharacters(output); - capitalizeStrategy(output); - // Check if the changes have made it compliant - if (!isCanSASCompliant(isStrict, output)) { - std::string message = "SaveNXcanSAS: The input " + input + "is not compliant with the NXcanSAS format."; - throw std::runtime_error(message); - } - } - return output; -} - -template -void writeArray1DWithStrAttributes(H5::Group &group, const std::string &dataSetName, const std::vector &values, - const std::map &attributes) { - Mantid::NeXus::H5Util::writeArray1D(group, dataSetName, values); - auto dataSet = group.openDataSet(dataSetName); - for (const auto &attribute : attributes) { - Mantid::NeXus::H5Util::writeStrAttribute(dataSet, attribute.first, attribute.second); - } -} - -H5::DSetCreatPropList setCompression2D(const hsize_t *chunkDims, const int deflateLevel = 6) { - H5::DSetCreatPropList propList; - const int rank = 2; - propList.setChunk(rank, chunkDims); - propList.setDeflate(deflateLevel); - return propList; -} - -template -void write2DWorkspace(H5::Group &group, Mantid::API::MatrixWorkspace_sptr workspace, const std::string &dataSetName, - Functor func, const std::map &attributes) { - using namespace Mantid::NeXus::H5Util; - - // Set the dimension - const size_t dimension0 = workspace->getNumberHistograms(); - const size_t dimension1 = workspace->y(0).size(); - const hsize_t rank = 2; - hsize_t dimensionArray[rank] = {static_cast(dimension0), static_cast(dimension1)}; - - // Start position in the 2D data (indexed) data structure - hsize_t start[rank] = {0, 0}; - - // Size of a slab - hsize_t sizeOfSingleSlab[rank] = {1, dimensionArray[1]}; - - // Get the Data Space definition for the 2D Data Set in the file - auto fileSpace = H5::DataSpace(rank, dimensionArray); - H5::DataType dataType(getType()); - - // Get the proplist with compression settings - H5::DSetCreatPropList propList = setCompression2D(sizeOfSingleSlab); - - // Create the data set - auto dataSet = group.createDataSet(dataSetName, dataType, fileSpace, propList); - - // Create Data Spae for 1D entry for each row in memory - hsize_t memSpaceDimension[1] = {dimension1}; - H5::DataSpace memSpace(1, memSpaceDimension); - - // Insert each row of the workspace as a slab - for (unsigned int index = 0; index < dimension0; ++index) { - // Need the data space - fileSpace.selectHyperslab(H5S_SELECT_SET, sizeOfSingleSlab, start); - - // Write the correct data set to file - dataSet.write(func(workspace, index), dataType, memSpace, fileSpace); - // Step up the write position - ++start[0]; - } - - // Add attributes to data set - for (const auto &attribute : attributes) { - writeStrAttribute(dataSet, attribute.first, attribute.second); - } -} - -std::vector splitDetectorNames(std::string detectorNames) { - const std::string delimiter = ","; - std::vector detectors; - size_t pos(0); - - while ((pos = detectorNames.find(delimiter)) != std::string::npos) { - std::string detectorName = detectorNames.substr(0, pos); - boost::algorithm::trim(detectorName); - detectors.emplace_back(detectorName); - detectorNames.erase(0, pos + delimiter.length()); - } - // Push remaining element - boost::algorithm::trim(detectorNames); - detectors.emplace_back(detectorNames); - return detectors; -} - -//------- SASinstrument -std::string getInstrumentName(const Mantid::API::MatrixWorkspace_sptr &workspace) { - auto instrument = workspace->getInstrument(); - return instrument->getFullName(); -} - -std::string getIDF(const Mantid::API::MatrixWorkspace_sptr &workspace) { - auto date = workspace->getWorkspaceStartDate(); - auto instrumentName = getInstrumentName(workspace); - return InstrumentFileFinder::getInstrumentFilename(instrumentName, date); -} - -void addDetectors(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace, - const std::vector &detectorNames) { - // If the group is empty then don't add anything - if (!detectorNames.empty()) { - for (const auto &detectorName : detectorNames) { - if (detectorName.empty()) { - continue; - } - - std::string sasDetectorName = sasInstrumentDetectorGroupName + detectorName; - sasDetectorName = Mantid::DataHandling::makeCanSASRelaxedName(sasDetectorName); - - auto instrument = workspace->getInstrument(); - auto component = instrument->getComponentByName(detectorName); - - if (component) { - const auto sample = instrument->getSample(); - const auto distance = component->getDistance(*sample); - std::map sddAttributes; - sddAttributes.insert(std::make_pair(sasUnitAttr, sasInstrumentDetectorSddUnitAttrValue)); - auto detector = Mantid::NeXus::H5Util::createGroupCanSAS(group, sasDetectorName, nxInstrumentDetectorClassAttr, - sasInstrumentDetectorClassAttr); - Mantid::NeXus::H5Util::write(detector, sasInstrumentDetectorName, detectorName); - Mantid::NeXus::H5Util::writeScalarDataSetWithStrAttributes(detector, sasInstrumentDetectorSdd, distance, - sddAttributes); - } - } - } -} - -/** - * Add the instrument group to the NXcanSAS file. This adds the - * instrument name and the IDF - * @param group: the sasEntry - * @param workspace: the workspace which is being stored - * @param radiationSource: the selcted radiation source - * @param detectorNames: the names of the detectors to store - */ -void addInstrument(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace, - const std::string &radiationSource, const std::string &geometry, double beamHeight, double beamWidth, - const std::vector &detectorNames) { - // Setup instrument - const std::string sasInstrumentNameForGroup = sasInstrumentGroupName; - auto instrument = Mantid::NeXus::H5Util::createGroupCanSAS(group, sasInstrumentNameForGroup, nxInstrumentClassAttr, - sasInstrumentClassAttr); - auto instrumentName = getInstrumentName(workspace); - Mantid::NeXus::H5Util::write(instrument, sasInstrumentName, instrumentName); - - // Setup the detector - addDetectors(instrument, workspace, detectorNames); - - // Setup source - const std::string sasSourceName = sasInstrumentSourceGroupName; - auto source = Mantid::NeXus::H5Util::createGroupCanSAS(instrument, sasSourceName, nxInstrumentSourceClassAttr, - sasInstrumentSourceClassAttr); - Mantid::NeXus::H5Util::write(source, sasInstrumentSourceRadiation, radiationSource); - - // Setup Aperture - const std::string sasApertureName = sasInstrumentApertureGroupName; - auto aperture = Mantid::NeXus::H5Util::createGroupCanSAS(instrument, sasApertureName, nxInstrumentApertureClassAttr, - sasInstrumentApertureClassAttr); - - Mantid::NeXus::H5Util::write(aperture, sasInstrumentApertureShape, geometry); - - std::map beamSizeAttrs; - beamSizeAttrs.insert(std::make_pair(sasUnitAttr, sasBeamAndSampleSizeUnitAttrValue)); - if (beamHeight != 0) { - Mantid::NeXus::H5Util::writeScalarDataSetWithStrAttributes(aperture, sasInstrumentApertureGapHeight, beamHeight, - beamSizeAttrs); - } - if (beamWidth != 0) { - Mantid::NeXus::H5Util::writeScalarDataSetWithStrAttributes(aperture, sasInstrumentApertureGapWidth, beamWidth, - beamSizeAttrs); - } - - // Add IDF information - auto idf = getIDF(workspace); - Mantid::NeXus::H5Util::write(instrument, sasInstrumentIDF, idf); -} - -//------- SASsample - -void addSample(H5::Group &group, const double &sampleThickness) { - if (sampleThickness == 0) { - return; - } - std::string const sasSampleNameForGroup = sasInstrumentSampleGroupAttr; - - auto sample = Mantid::NeXus::H5Util::createGroupCanSAS(group, sasSampleNameForGroup, nxInstrumentSampleClassAttr, - sasInstrumentSampleClassAttr); - - std::map sampleThicknessAttrs; - sampleThicknessAttrs.insert(std::make_pair(sasUnitAttr, sasBeamAndSampleSizeUnitAttrValue)); - Mantid::NeXus::H5Util::writeScalarDataSetWithStrAttributes(sample, sasInstrumentSampleThickness, sampleThickness, - sampleThicknessAttrs); -} - -//------- SASprocess - -std::string getDate() { - time_t rawtime; - time(&rawtime); - char temp[25]; - strftime(temp, 25, "%Y-%m-%dT%H:%M:%S", localtime(&rawtime)); - std::string sasDate(temp); - return sasDate; -} - -/** Write a property value to the H5 file if the property exists in the run - * - * @param run : the run to look for the property in - * @param propertyName : the name of the property to find - * @param sasGroup : the group to add the term into in the output file - * @param sasTerm : the name of the term to add - */ -void addPropertyFromRunIfExists(Run const &run, std::string const &propertyName, H5::Group &sasGroup, - std::string const &sasTerm) { - if (run.hasProperty(propertyName)) { - const auto *property = run.getProperty(propertyName); - Mantid::NeXus::H5Util::write(sasGroup, sasTerm, property->value()); - } -} - -/** - * Add the process information to the NXcanSAS file. This information - * about the run number, the Mantid version and the user file (if available) - * @param group: the sasEntry - * @param workspace: the workspace which is being stored - */ -void addProcess(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace) { - // Setup process - const std::string sasProcessNameForGroup = sasProcessGroupName; - auto process = - Mantid::NeXus::H5Util::createGroupCanSAS(group, sasProcessNameForGroup, nxProcessClassAttr, sasProcessClassAttr); - - // Add name - Mantid::NeXus::H5Util::write(process, sasProcessName, sasProcessNameValue); - - // Add creation date of the file - auto date = getDate(); - Mantid::NeXus::H5Util::write(process, sasProcessDate, date); - - // Add Mantid version - const auto version = std::string(MantidVersion::version()); - Mantid::NeXus::H5Util::write(process, sasProcessTermSvn, version); - - // Add log values - const auto run = workspace->run(); - addPropertyFromRunIfExists(run, sasProcessUserFileInLogs, process, sasProcessTermUserFile); - addPropertyFromRunIfExists(run, sasProcessBatchFileInLogs, process, sasProcessTermBatchFile); -} - -/** - * Add the process information to the NXcanSAS file. This information - * about the run number, the Mantid version and the user file (if available) - * @param group: the sasEntry - * @param workspace: the workspace which is being stored - */ -void addProcess(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace, - const Mantid::API::MatrixWorkspace_sptr &canWorkspace) { - // Setup process - const std::string sasProcessNameForGroup = sasProcessGroupName; - auto process = - Mantid::NeXus::H5Util::createGroupCanSAS(group, sasProcessNameForGroup, nxProcessClassAttr, sasProcessClassAttr); - - // Add name - Mantid::NeXus::H5Util::write(process, sasProcessName, sasProcessNameValue); - - // Add creation date of the file - auto date = getDate(); - Mantid::NeXus::H5Util::write(process, sasProcessDate, date); - - // Add Mantid version - const auto version = std::string(MantidVersion::version()); - Mantid::NeXus::H5Util::write(process, sasProcessTermSvn, version); - - const auto run = workspace->run(); - addPropertyFromRunIfExists(run, sasProcessUserFileInLogs, process, sasProcessTermUserFile); - addPropertyFromRunIfExists(run, sasProcessBatchFileInLogs, process, sasProcessTermBatchFile); - - // Add can run number - const auto canRun = canWorkspace->getRunNumber(); - Mantid::NeXus::H5Util::write(process, sasProcessTermCan, std::to_string(canRun)); -} - -/** - * Add an entry to the process group. - * @param group: the sasEntry - * @param entryName: string containing the name of the value to save - * @param entryValue: string containing the value to save - */ -void addProcessEntry(H5::Group &group, const std::string &entryName, const std::string &entryValue) { - auto process = group.openGroup(sasProcessGroupName); - // Populate process entry - Mantid::NeXus::H5Util::write(process, entryName, entryValue); -} - -WorkspaceDimensionality getWorkspaceDimensionality(const Mantid::API::MatrixWorkspace_sptr &workspace) { - auto numberOfHistograms = workspace->getNumberHistograms(); - WorkspaceDimensionality dimensionality(WorkspaceDimensionality::other); - if (numberOfHistograms == 1) { - dimensionality = WorkspaceDimensionality::oneD; - } else if (numberOfHistograms > 1) { - dimensionality = WorkspaceDimensionality::twoD; - } - return dimensionality; -} - -//------- SASdata - -std::string getIntensityUnitLabel(const std::string &intensityUnitLabel) { - if (intensityUnitLabel == "I(q) (cm-1)") { - return sasIntensity; - } else { - return intensityUnitLabel; - } -} - -std::string getIntensityUnit(const Mantid::API::MatrixWorkspace_sptr &workspace) { - auto iUnit = workspace->YUnit(); - if (iUnit.empty()) { - iUnit = workspace->YUnitLabel(); - } - return iUnit; -} - -std::string getMomentumTransferLabel(const std::string &momentumTransferLabel) { - if (momentumTransferLabel == "Angstrom^-1") { - return sasMomentumTransfer; - } else { - return momentumTransferLabel; - } -} - -std::string getUnitFromMDDimension(const Mantid::Geometry::IMDDimension_const_sptr &dimension) { - const auto unitLabel = dimension->getMDUnits().getUnitLabel(); - return unitLabel.ascii(); -} - -bool areAxesNumeric(const Mantid::API::MatrixWorkspace_sptr &workspace) { - const std::array indices = {0, 1}; - return std::all_of(indices.cbegin(), indices.cend(), - [workspace](auto const &index) { return workspace->getAxis(index)->isNumeric(); }); -} - -class SpectrumAxisValueProvider { -public: - explicit SpectrumAxisValueProvider(Mantid::API::MatrixWorkspace_sptr workspace) : m_workspace(std::move(workspace)) { - setSpectrumAxisValues(); - } - - Mantid::MantidVec::value_type *operator()(const Mantid::API::MatrixWorkspace_sptr & /*unused*/, int index) { - auto isPointData = m_workspace->getNumberHistograms() == m_spectrumAxisValues.size(); - double value = 0; - if (isPointData) { - value = m_spectrumAxisValues[index]; - } else { - value = (m_spectrumAxisValues[index + 1] + m_spectrumAxisValues[index]) / 2.0; - } - - Mantid::MantidVec tempVec(m_workspace->dataY(index).size(), value); - m_currentAxisValues.swap(tempVec); - return m_currentAxisValues.data(); - } - -private: - void setSpectrumAxisValues() { - auto sAxis = m_workspace->getAxis(1); - for (size_t index = 0; index < sAxis->length(); ++index) { - m_spectrumAxisValues.emplace_back((*sAxis)(index)); - } - } - - Mantid::API::MatrixWorkspace_sptr m_workspace; - Mantid::MantidVec m_spectrumAxisValues; - Mantid::MantidVec m_currentAxisValues; -}; - -/** - * QxExtractor functor which allows us to convert 2D Qx data into point data. - */ -template class QxExtractor { -public: - T *operator()(const Mantid::API::MatrixWorkspace_sptr &ws, int index) { - if (ws->isHistogramData()) { - qxPointData.clear(); - Mantid::Kernel::VectorHelper::convertToBinCentre(ws->dataX(index), qxPointData); - return qxPointData.data(); - } else { - return ws->dataX(index).data(); - } - } - - std::vector qxPointData; -}; - -/** - * Stores the 2D data in the HDF5 file. Qx and Qy values need to be stored as a - *meshgrid. - * They should be stored as point data. - * @param data: the hdf5 group - * @param workspace: the workspace to store - * - * Workspace looks like this in Mantid Matrix - * (Qx) 0 1 2 ... M (first dimension) - * (QY) - * 0 IQx0Qy0 IQx1Qy0 IQx2Qy0 ... IQxMQy0 - * 1 IQx0Qy1 IQx1Qy1 IQx2Qy1 ... IQxMQy1 - * 2 IQx0Qy2 IQx1Qy2 IQx2Qy2 ... IQxMQy2 - * 3 IQx0Qy3 IQx1Qy3 IQx2Qy3 ... IQxMQy3 - * . - * . - * N IQx0QyN IQx1QyN IQx2QyN ... IQxMQyN - * (second dimension) - * - * The layout below is how it would look like in the HDFView, ie vertical axis - * is first dimension. We map the Mantid Matrix layout 1-to-1. Note that this - * will swap the matrix indices, but this is how it is done in the other - *2Dloaders - * - * In HDF5 the Qx would need to be stored as: - * Qx1 Qx2 ... QxM - * Qx1 Qx2 ... QxM - * Qx1 Qx2 ... QxM - * . - * . - * Qx1 Qx2 ... QxM - * - * In HDF5 the Qy would need to be stored as: - * Qy1 Qy1 ... Qy1 - * Qy2 Qy2 ... Qy2 - * Qy3 Qy3 ... Qy3 - * . - * . - * QxN QxN ... QxN - */ -void addData2D(H5::Group &data, const Mantid::API::MatrixWorkspace_sptr &workspace) { - if (!areAxesNumeric(workspace)) { - throw std::invalid_argument("SaveNXcanSAS: The provided 2D workspace needs to have 2 numeric axes."); - } - // Add attributes for @signal, @I_axes, @Q_indices, - Mantid::NeXus::H5Util::writeStrAttribute(data, sasSignal, sasDataI); - const std::string sasDataIAxesAttr2D = sasDataQ + sasSeparator + sasDataQ; - Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIAxesAttr, sasDataIAxesAttr2D); - Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIUncertaintyAttr, sasDataIdev); - Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIUncertaintiesAttr, sasDataIdev); - // Write the Q Indices as Int Array - Mantid::NeXus::H5Util::writeNumAttribute(data, sasDataQIndicesAttr, std::vector{0, 1}); - - // Store the 2D Qx data + units - std::map qxAttributes; - auto qxUnit = getUnitFromMDDimension(workspace->getXDimension()); - qxUnit = getMomentumTransferLabel(qxUnit); - qxAttributes.emplace(sasUnitAttr, qxUnit); - QxExtractor qxExtractor; - write2DWorkspace(data, workspace, sasDataQx, qxExtractor, qxAttributes); - - // Get 2D Qy data and store it - std::map qyAttributes; - auto qyUnit = getUnitFromMDDimension(workspace->getDimension(1)); - qyUnit = getMomentumTransferLabel(qyUnit); - qyAttributes.emplace(sasUnitAttr, qyUnit); - - SpectrumAxisValueProvider spectrumAxisValueProvider(workspace); - write2DWorkspace(data, workspace, sasDataQy, spectrumAxisValueProvider, qyAttributes); - - // Get 2D I data and store it - std::map iAttributes; - auto iUnit = getIntensityUnit(workspace); - iUnit = getIntensityUnitLabel(iUnit); - iAttributes.emplace(sasUnitAttr, iUnit); - iAttributes.emplace(sasUncertaintyAttr, sasDataIdev); - iAttributes.emplace(sasUncertaintiesAttr, sasDataIdev); - - auto iExtractor = [](const Mantid::API::MatrixWorkspace_sptr &ws, int index) { return ws->dataY(index).data(); }; - write2DWorkspace(data, workspace, sasDataI, iExtractor, iAttributes); - - // Get 2D Idev data and store it - std::map eAttributes; - eAttributes.insert(std::make_pair(sasUnitAttr, iUnit)); // same units as intensity - - auto iDevExtractor = [](const Mantid::API::MatrixWorkspace_sptr &ws, int index) { return ws->dataE(index).data(); }; - write2DWorkspace(data, workspace, sasDataIdev, iDevExtractor, eAttributes); -} - -void addData1D(H5::Group &data, const Mantid::API::MatrixWorkspace_sptr &workspace) { - // Add attributes for @signal, @I_axes, @Q_indices, - Mantid::NeXus::H5Util::writeStrAttribute(data, sasSignal, sasDataI); - Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIAxesAttr, sasDataQ); - Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIUncertaintyAttr, sasDataIdev); - Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataIUncertaintiesAttr, sasDataIdev); - Mantid::NeXus::H5Util::writeNumAttribute(data, sasDataQIndicesAttr, std::vector{0}); - - if (workspace->hasDx(0)) { - Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataQUncertaintyAttr, sasDataQdev); - Mantid::NeXus::H5Util::writeStrAttribute(data, sasDataQUncertaintiesAttr, sasDataQdev); - } - - //----------------------------------------- - // Add Q with units + uncertainty definition - const auto &qValue = workspace->points(0); - std::map qAttributes; - auto qUnit = getUnitFromMDDimension(workspace->getDimension(0)); - qUnit = getMomentumTransferLabel(qUnit); - qAttributes.emplace(sasUnitAttr, qUnit); - if (workspace->hasDx(0)) { - qAttributes.emplace(sasUncertaintyAttr, sasDataQdev); - qAttributes.emplace(sasUncertaintiesAttr, sasDataQdev); - } - - writeArray1DWithStrAttributes(data, sasDataQ, qValue.rawData(), qAttributes); - - //----------------------------------------- - // Add I with units + uncertainty definition - const auto &intensity = workspace->y(0); - std::map iAttributes; - auto iUnit = getIntensityUnit(workspace); - iUnit = getIntensityUnitLabel(iUnit); - iAttributes.emplace(sasUnitAttr, iUnit); - iAttributes.emplace(sasUncertaintyAttr, sasDataIdev); - iAttributes.emplace(sasUncertaintiesAttr, sasDataIdev); - - writeArray1DWithStrAttributes(data, sasDataI, intensity.rawData(), iAttributes); - - //----------------------------------------- - // Add Idev with units - const auto &intensityUncertainty = workspace->e(0); - std::map eAttributes; - eAttributes.insert(std::make_pair(sasUnitAttr, iUnit)); // same units as intensity - - writeArray1DWithStrAttributes(data, sasDataIdev, intensityUncertainty.rawData(), eAttributes); - - //----------------------------------------- - // Add Qdev with units if available - if (workspace->hasDx(0)) { - const auto qResolution = workspace->pointStandardDeviations(0); - std::map xUncertaintyAttributes; - xUncertaintyAttributes.emplace(sasUnitAttr, qUnit); - - writeArray1DWithStrAttributes(data, sasDataQdev, qResolution.rawData(), xUncertaintyAttributes); - } -} - -//------- SAStransmission_spectrum -void addTransmission(H5::Group &group, const Mantid::API::MatrixWorkspace_const_sptr &workspace, - const std::string &transmissionName) { - // Setup process - const std::string sasTransmissionName = sasTransmissionSpectrumGroupName + "_" + transmissionName; - auto transmission = Mantid::NeXus::H5Util::createGroupCanSAS( - group, sasTransmissionName, nxTransmissionSpectrumClassAttr, sasTransmissionSpectrumClassAttr); - - // Add attributes for @signal, @T_axes, @T_indices, @T_uncertainty, - // @T_uncertainties, @name, @timestamp - Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasSignal, sasTransmissionSpectrumT); - Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasTransmissionSpectrumTIndices, sasTransmissionSpectrumT); - Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasTransmissionSpectrumTUncertainty, - sasTransmissionSpectrumTdev); - Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasTransmissionSpectrumTUncertainties, - sasTransmissionSpectrumTdev); - Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasTransmissionSpectrumNameAttr, transmissionName); - - auto date = getDate(); - Mantid::NeXus::H5Util::writeStrAttribute(transmission, sasTransmissionSpectrumTimeStampAttr, date); - - //----------------------------------------- - // Add T with units + uncertainty definition - const auto transmissionData = workspace->y(0); - std::map transmissionAttributes; - std::string unit = sasNone; - - transmissionAttributes.emplace(sasUnitAttr, unit); - transmissionAttributes.emplace(sasUncertaintyAttr, sasTransmissionSpectrumTdev); - transmissionAttributes.emplace(sasUncertaintiesAttr, sasTransmissionSpectrumTdev); - - writeArray1DWithStrAttributes(transmission, sasTransmissionSpectrumT, transmissionData.rawData(), - transmissionAttributes); - - //----------------------------------------- - // Add Tdev with units - const auto &transmissionErrors = workspace->e(0); - std::map transmissionErrorAttributes; - transmissionErrorAttributes.emplace(sasUnitAttr, unit); - - writeArray1DWithStrAttributes(transmission, sasTransmissionSpectrumTdev, transmissionErrors.rawData(), - transmissionErrorAttributes); - - //----------------------------------------- - // Add lambda with units - const auto lambda = workspace->points(0); - std::map lambdaAttributes; - auto lambdaUnit = getUnitFromMDDimension(workspace->getDimension(0)); - if (lambdaUnit.empty() || lambdaUnit == "Angstrom") { - lambdaUnit = sasAngstrom; - } - lambdaAttributes.emplace(sasUnitAttr, lambdaUnit); - - writeArray1DWithStrAttributes(transmission, sasTransmissionSpectrumLambda, lambda.rawData(), lambdaAttributes); -} -} // namespace - namespace Mantid::DataHandling { void SaveNXcanSASBase::initStandardProperties() { + // Standard NXcanSAS properties auto inputWSValidator = std::make_shared(); inputWSValidator->add("MomentumTransfer"); inputWSValidator->add(); @@ -735,33 +86,63 @@ void SaveNXcanSASBase::initStandardProperties() { "The thickness of the sample in mm. If specified as 0 it will not be recorded."); } -void SaveNXcanSASBase::addStandardMetadata(MatrixWorkspace_sptr &workspace, H5::Group &sasEntry, Progress *progress) { +std::map SaveNXcanSASBase::validateStandardInputs() { + // The input should be a Workspace2D + Mantid::API::MatrixWorkspace_sptr workspace = getProperty("InputWorkspace"); + std::map result; + if (!workspace || !std::dynamic_pointer_cast(workspace)) { + result.emplace("InputWorkspace", "The InputWorkspace must be a Workspace2D."); + } + + // Transmission data should be 1D + Mantid::API::MatrixWorkspace_sptr transmission = getProperty("Transmission"); + Mantid::API::MatrixWorkspace_sptr transmissionCan = getProperty("TransmissionCan"); + + auto checkTransmission = [&result](const Mantid::API::MatrixWorkspace_sptr &trans, const std::string &propertyName) { + if (trans->getNumberHistograms() != 1) { + result.emplace(propertyName, "The input workspaces for transmissions have to be 1D."); + } + }; + + if (transmission) { + checkTransmission(transmission, "TransmissionCan"); + } + if (transmissionCan) { + checkTransmission(transmissionCan, "TransmissionCan"); + } + + return result; +} + +/** + * Adds standard metadata to a NXcanSAS file format. + * 1. Adds instrument metata: Detectors, source and aperture. + * 2. Adds sample metadata + * 3. Adds process metadata: Run number, version info. + * 4. If there's transmission or transmission can. Adds process entry for the + * workspaces and transmission/transmission can data in a new group. + * 5. If there's information for background subtraction workspace, adds new entry + * with the scale and workspace name. + * @param workspace: Workspace to add instrument from + * @param sasEntry: sas group in which to store metadata + */ + +void SaveNXcanSASBase::addStandardMetadata(MatrixWorkspace_sptr &workspace, H5::Group &sasEntry) { std::string &&radiationSource = getPropertyValue("RadiationSource"); std::string &&geometry = getProperty("Geometry"); double &&beamHeight = getProperty("SampleHeight"); double &&beamWidth = getProperty("SampleWidth"); double &&sampleThickness = getProperty("SampleThickness"); std::string &&detectorNames = getPropertyValue("DetectorNames"); + Mantid::API::MatrixWorkspace_sptr &&transmissionSample = getProperty("Transmission"); Mantid::API::MatrixWorkspace_sptr &&transmissionCan = getProperty("TransmissionCan"); - // Setup progress bar - int numberOfSteps = 4; - if (transmissionSample) { - ++numberOfSteps; - } - - if (transmissionCan) { - ++numberOfSteps; - } - // Add the instrument information - progress->report("Adding instrument information."); const auto detectors = splitDetectorNames(detectorNames); addInstrument(sasEntry, workspace, radiationSource, geometry, beamHeight, beamWidth, detectors); // Add the sample information - progress->report("Adding sample information."); addSample(sasEntry, sampleThickness); // Get additional run numbers @@ -776,7 +157,6 @@ void SaveNXcanSASBase::addStandardMetadata(MatrixWorkspace_sptr &workspace, H5:: const auto scaledBgSubScaleFactor = getPropertyValue("BackgroundSubtractionScaleFactor"); // Add the process information - progress->report("Adding process information."); if (transmissionCan) { addProcess(sasEntry, workspace, transmissionCan); } else { @@ -793,20 +173,17 @@ void SaveNXcanSASBase::addStandardMetadata(MatrixWorkspace_sptr &workspace, H5:: } if (!scaledBgSubWorkspace.empty()) { - progress->report("Adding scaled background subtraction information."); addProcessEntry(sasEntry, sasProcessTermScaledBgSubWorkspace, scaledBgSubWorkspace); addProcessEntry(sasEntry, sasProcessTermScaledBgSubScaleFactor, scaledBgSubScaleFactor); } // Add the transmissions for sample if (transmissionSample) { - progress->report("Adding sample transmission information."); addTransmission(sasEntry, transmissionSample, sasTransmissionSpectrumNameSampleAttrValue); } // Add the transmissions for can if (transmissionCan) { - progress->report("Adding can transmission information."); addTransmission(sasEntry, transmissionCan, sasTransmissionSpectrumNameCanAttrValue); } } @@ -815,11 +192,11 @@ void SaveNXcanSASBase::addStandardMetadata(MatrixWorkspace_sptr &workspace, H5:: * Add the sasEntry to the sasroot. * @param file: Handle to the NXcanSAS file * @param workspace: the workspace to store - * @return the sasEntry + * @param suffix: suffix for sas entry group name. Default is "01" + * @return sasEntry group object */ H5::Group SaveNXcanSASBase::addSasEntry(H5::H5File &file, const Mantid::API::MatrixWorkspace_sptr &workspace, const std::string &suffix) { - using namespace Mantid::DataHandling::NXcanSAS; const std::string sasEntryName = sasEntryGroupName + suffix; auto sasEntry = Mantid::NeXus::H5Util::createGroupCanSAS(file, sasEntryName, nxEntryClassAttr, sasEntryClassAttr); @@ -840,12 +217,16 @@ H5::Group SaveNXcanSASBase::addSasEntry(H5::H5File &file, const Mantid::API::Mat return sasEntry; } +/** + * Sorts out dimensionality of the data (1D, 2D) and calls helper + * function to insert data in workspace to the given sas group. + * @param group: Handle to the NXcanSAS group + * @param workspace: the workspace to store data from + */ void SaveNXcanSASBase::addData(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace) { - const std::string sasDataName = sasDataGroupName; - auto data = Mantid::NeXus::H5Util::createGroupCanSAS(group, sasDataName, nxDataClassAttr, sasDataClassAttr); + auto data = Mantid::NeXus::H5Util::createGroupCanSAS(group, sasDataGroupName, nxDataClassAttr, sasDataClassAttr); - auto workspaceDimensionality = getWorkspaceDimensionality(workspace); - switch (workspaceDimensionality) { + switch (getWorkspaceDimensionality(workspace)) { case (WorkspaceDimensionality::oneD): addData1D(data, workspace); break; @@ -858,15 +239,4 @@ void SaveNXcanSASBase::addData(H5::Group &group, const Mantid::API::MatrixWorksp } } -/** - * This makes out of an input a relaxed name, something conforming to - * "[A-Za-z_][\w_]*" - * For now "-" is converted to "_", "." is converted to "_", else we throw - */ -std::string makeCanSASRelaxedName(const std::string &input) { - bool isStrict = false; - auto emptyCapitalizationStrategy = [](std::string &) {}; - return makeCompliantName(input, isStrict, emptyCapitalizationStrategy); -} - } // namespace Mantid::DataHandling From e98b1be5a4dd1df439566fc8594a99fdc4552dbd Mon Sep 17 00:00:00 2001 From: adriazalvarez Date: Thu, 9 Jan 2025 16:20:15 +0000 Subject: [PATCH 6/9] Fix typo in include path on cmakelist --- Framework/DataHandling/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Framework/DataHandling/CMakeLists.txt b/Framework/DataHandling/CMakeLists.txt index 4e32e4c0fbba..d430383d9a8c 100644 --- a/Framework/DataHandling/CMakeLists.txt +++ b/Framework/DataHandling/CMakeLists.txt @@ -185,7 +185,7 @@ set(SRC_FILES src/SaveNXSPE.cpp src/SaveNXTomo.cpp src/SaveNXcanSAS.cpp - src/SaveNXCanSASBase.cpp + src/SaveNXcanSASBase.cpp src/SaveNexus.cpp src/SaveNexusESS.cpp src/SaveNexusGeometry.cpp From 38fef13bbb34923f50b77e8b8aa82b99e6c079dd Mon Sep 17 00:00:00 2001 From: adriazalvarez Date: Thu, 9 Jan 2025 16:31:43 +0000 Subject: [PATCH 7/9] Fix cppcheck style warning: --- .../DataHandling/inc/MantidDataHandling/SaveNXcanSASBase.h | 2 +- Framework/DataHandling/src/SaveNXcanSASBase.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Framework/DataHandling/inc/MantidDataHandling/SaveNXcanSASBase.h b/Framework/DataHandling/inc/MantidDataHandling/SaveNXcanSASBase.h index 3ddfe31c1c1b..4cd03ce35a4e 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/SaveNXcanSASBase.h +++ b/Framework/DataHandling/inc/MantidDataHandling/SaveNXcanSASBase.h @@ -20,7 +20,7 @@ namespace DataHandling { */ class MANTID_DATAHANDLING_DLL SaveNXcanSASBase : public API::Algorithm { protected: - void addStandardMetadata(Mantid::API::MatrixWorkspace_sptr &workspace, H5::Group &sasEntry); + void addStandardMetadata(const Mantid::API::MatrixWorkspace_sptr &workspace, H5::Group &sasEntry); void addData(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace); H5::Group addSasEntry(H5::H5File &file, const Mantid::API::MatrixWorkspace_sptr &workspace, const std::string &suffix); diff --git a/Framework/DataHandling/src/SaveNXcanSASBase.cpp b/Framework/DataHandling/src/SaveNXcanSASBase.cpp index c2114a40eba3..e9d6a023da17 100644 --- a/Framework/DataHandling/src/SaveNXcanSASBase.cpp +++ b/Framework/DataHandling/src/SaveNXcanSASBase.cpp @@ -127,7 +127,7 @@ std::map SaveNXcanSASBase::validateStandardInputs() { * @param sasEntry: sas group in which to store metadata */ -void SaveNXcanSASBase::addStandardMetadata(MatrixWorkspace_sptr &workspace, H5::Group &sasEntry) { +void SaveNXcanSASBase::addStandardMetadata(const MatrixWorkspace_sptr &workspace, H5::Group &sasEntry) { std::string &&radiationSource = getPropertyValue("RadiationSource"); std::string &&geometry = getProperty("Geometry"); double &&beamHeight = getProperty("SampleHeight"); From 5b5fca5f0914c78ac9e2b0a90a6a9bc993dd2577 Mon Sep 17 00:00:00 2001 From: adriazalvarez Date: Thu, 9 Jan 2025 19:17:17 +0000 Subject: [PATCH 8/9] Fix doxygen documentation warnings: 1. There were doxygen lines in add2DData function that needed to be escaped with \. to avoid a warning, this was not detected before as doxygen does not look in anonymous namespaces for mantid. 2. There was a doxygen comment missing input parameters to the function addInstrument. --- Framework/DataHandling/src/NXcanSASHelper.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Framework/DataHandling/src/NXcanSASHelper.cpp b/Framework/DataHandling/src/NXcanSASHelper.cpp index dbe8074c711d..b34fd1f2dc8c 100644 --- a/Framework/DataHandling/src/NXcanSASHelper.cpp +++ b/Framework/DataHandling/src/NXcanSASHelper.cpp @@ -299,8 +299,11 @@ void addDetectors(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &wor * instrument name and the IDF * @param group: the sasEntry * @param workspace: the workspace which is being stored - * @param radiationSource: the selcted radiation source + * @param radiationSource: the selected radiation source * @param detectorNames: the names of the detectors to store + * @param geometry: Geometry type of collimation + * @param beamHeight: Height of collimation element in mm + * @param beamWidth: Width of collimation element in mm */ void addInstrument(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace, const std::string &radiationSource, const std::string &geometry, double beamHeight, double beamWidth, @@ -564,8 +567,8 @@ void addData1D(H5::Group &data, const Mantid::API::MatrixWorkspace_sptr &workspa * 1 IQx0Qy1 IQx1Qy1 IQx2Qy1 ... IQxMQy1 * 2 IQx0Qy2 IQx1Qy2 IQx2Qy2 ... IQxMQy2 * 3 IQx0Qy3 IQx1Qy3 IQx2Qy3 ... IQxMQy3 - * . - * . + * \. + * \. * N IQx0QyN IQx1QyN IQx2QyN ... IQxMQyN * (second dimension) * @@ -578,16 +581,16 @@ void addData1D(H5::Group &data, const Mantid::API::MatrixWorkspace_sptr &workspa * Qx1 Qx2 ... QxM * Qx1 Qx2 ... QxM * Qx1 Qx2 ... QxM - * . - * . + * \. + * \. * Qx1 Qx2 ... QxM * * In HDF5 the Qy would need to be stored as: * Qy1 Qy1 ... Qy1 * Qy2 Qy2 ... Qy2 * Qy3 Qy3 ... Qy3 - * . - * . + * \. + * \. * QxN QxN ... QxN */ void addData2D(H5::Group &data, const Mantid::API::MatrixWorkspace_sptr &workspace) { From f5cc1c8afbba68e923f8be5f4ee64e73395f2fe9 Mon Sep 17 00:00:00 2001 From: adriazalvarez Date: Fri, 10 Jan 2025 09:59:52 +0000 Subject: [PATCH 9/9] Update header date on new file --- Framework/DataHandling/inc/MantidDataHandling/NXcanSASHelper.h | 2 +- Framework/DataHandling/src/NXcanSASHelper.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Framework/DataHandling/inc/MantidDataHandling/NXcanSASHelper.h b/Framework/DataHandling/inc/MantidDataHandling/NXcanSASHelper.h index d1673d5688c6..cf9d946e87f0 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/NXcanSASHelper.h +++ b/Framework/DataHandling/inc/MantidDataHandling/NXcanSASHelper.h @@ -1,6 +1,6 @@ // Mantid Repository : https://github.com/mantidproject/mantid // -// Copyright © 2016 ISIS Rutherford Appleton Laboratory UKRI, +// Copyright © 2025 ISIS Rutherford Appleton Laboratory UKRI, // NScD Oak Ridge National Laboratory, European Spallation Source, // Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS // SPDX - License - Identifier: GPL - 3.0 + diff --git a/Framework/DataHandling/src/NXcanSASHelper.cpp b/Framework/DataHandling/src/NXcanSASHelper.cpp index b34fd1f2dc8c..1978d75f3595 100644 --- a/Framework/DataHandling/src/NXcanSASHelper.cpp +++ b/Framework/DataHandling/src/NXcanSASHelper.cpp @@ -394,7 +394,7 @@ void addProcess(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &works } /** - * Add the process information to the NXcanSAS file. This information + * Add the process information to the NXcanSAS file. It contains information * about the run number, the Mantid version and the user file (if available) * @param group: the sasEntry * @param workspace: the workspace which is being stored