Mantid
Loading...
Searching...
No Matches
SaveNXcanSASBase.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2025 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
7
9
10#include "MantidAPI/Axis.h"
22#include "MantidKernel/Unit.h"
24#include "MantidNexus/H5Util.h"
25
26#include <algorithm>
27
28using namespace Mantid::API;
29using namespace Mantid::DataHandling::NXcanSAS;
30using namespace Mantid::Kernel;
31using namespace Mantid::Nexus;
32
33namespace {
34bool hasUnit(const std::string &unitToCompareWith, const MatrixWorkspace_sptr &ws) {
35 if (ws->axes() == 0) {
36 return false;
37 }
38 const auto unit = ws->getAxis(0)->unit();
39 return (unit && unit->unitID() == unitToCompareWith);
40}
41
42void areAxesNumeric(const MatrixWorkspace_sptr &workspace, const int numberOfDims = 1) {
43 if (numberOfDims == 0) {
44 throw std::invalid_argument("Workspace can only have 1 or 2 dimensions");
45 }
46 std::vector<int> indices(numberOfDims);
47 std::generate(indices.begin(), indices.end(), [i = 0]() mutable { return i++; });
48 const auto itIndex = std::find_if(indices.cbegin(), indices.cend(), [&workspace](const auto &index) {
49 return !workspace->getAxis(index)->isNumeric();
50 });
51 if (itIndex != indices.cend()) {
52 throw std::invalid_argument("Invalid workspace: Axis " + std::to_string(*itIndex) + " is not numeric");
53 }
54}
55
56bool checkValidMatrixWorkspace(const Workspace_sptr &ws) {
57 const auto &ws_input = std::dynamic_pointer_cast<MatrixWorkspace>(ws);
58 return (ws_input && hasUnit("MomentumTransfer", ws_input) && ws_input->isCommonBins());
59}
60
61std::string validateGroupWithProperties(const Workspace_sptr &ws) {
62 if (!ws) {
63 return "Workspace has to be a valid workspace";
64 }
65
66 if (ws->isGroup()) {
67 const auto &groupItems = std::dynamic_pointer_cast<WorkspaceGroup>(ws)->getAllItems();
68 if (std::any_of(groupItems.cbegin(), groupItems.cend(),
69 [](const auto &childWs) { return !checkValidMatrixWorkspace(childWs); })) {
70 return "Workspace must have common bins and Momentum transfer units";
71 }
72 return "";
73 }
74
75 if (!checkValidMatrixWorkspace(ws)) {
76 return "Workspace must have common bins and Momentum transfer units";
77 }
78 return "";
79}
80
81} // namespace
82
83namespace Mantid::DataHandling {
84
86 // Standard NXcanSAS properties
87 auto groupValidator = std::make_shared<Kernel::LambdaValidator<Workspace_sptr>>(validateGroupWithProperties);
89 Kernel::Direction::Input, groupValidator),
90 "The input workspace, which must be in units of Q. Can be a 1D or a 2D workspace.");
91 declareProperty(std::make_unique<FileProperty>(StandardProperties::FILENAME, "", API::FileProperty::Save, ".h5"),
92 "The name of the .h5 file to save");
93
94 std::vector<std::string> radiationSourceOptions{"Spallation Neutron Source",
95 "Pulsed Reactor Neutron Source",
96 "Reactor Neutron Source",
97 "Synchrotron X-ray Source",
98 "Pulsed Muon Source",
99 "Rotating Anode X-ray",
100 "Fixed Tube X-ray",
101 "neutron",
102 "x-ray",
103 "muon",
104 "electron"};
105 declareProperty(StandardProperties::RADIATION_SOURCE, "Spallation Neutron Source",
106 std::make_shared<Kernel::StringListValidator>(radiationSourceOptions), "The type of radiation used.");
108 "Specify in a comma separated list, which detectors to store "
109 "information about; \nwhere each name must match a name "
110 "given for a detector in the [[IDF|instrument definition "
111 "file (IDF)]]. \nIDFs are located in the instrument "
112 "sub-directory of the Mantid install directory.");
115 std::make_shared<API::WorkspaceUnitValidator>("Wavelength")),
116 "The transmission workspace. Optional. If given, will be saved at "
117 "TransmissionSpectrum");
118
121 std::make_shared<API::WorkspaceUnitValidator>("Wavelength")),
122 "The transmission workspace of the Can. Optional. If given, will be "
123 "saved at TransmissionSpectrum");
124
126 "The run number for the sample transmission workspace. Optional.");
128 "The run number for the sample direct workspace. Optional.");
130 "The run number for the can scatter workspace. Optional.");
132 "The run number for the can direct workspace. Optional.");
133
136 "The name of the workspace used in the scaled background subtraction, to be included in the metadata. Optional.");
139 "The scale factor used in the scaled background subtraction, to be included in the metadata. Optional.");
140
141 std::vector<std::string> const geometryOptions{"Cylinder", "FlatPlate", "Flat plate", "Disc", "Unknown"};
143 std::make_shared<Kernel::StringListValidator>(geometryOptions),
144 "The geometry type of the collimation.");
146 "The height of the collimation element in mm. If specified as 0 it will not be recorded.");
148 "The width of the collimation element in mm. If specified as 0 it will not be recorded.");
150 "The thickness of the sample in mm. If specified as 0 it will not be recorded.");
151}
152
154 const auto spinStateValidator = std::make_shared<Kernel::SpinStateValidator>(
155 std::unordered_set<int>{2, 4}, false, SpinStateNXcanSAS::SPIN_PARA, SpinStateNXcanSAS::SPIN_ANTIPARA, true,
157
159 "The order of the spin states in the input group workspace: +1 Polarization parallel to polarizer, "
160 "-1 antiparallel and 0 no polarization");
162 "The name of the Polarizer Component as defined in the IDF. i.e. 'short-polarizer'");
164 "The name of the Analyzer Component as defined in the IDF. i.e. 'helium-analyzer'");
166 "Comma separated list of flipper components as defined in the IDF i.e. 'RF-flipper");
168 "The name of sample logs in which the magnetic field strength is stored");
170 "Direction of the magnetic field on the sample: comma separated vector"
171 "with three values: Polar, Azimuthal and Rotation angles");
172}
173
174std::map<std::string, std::string> SaveNXcanSASBase::validateStandardInputs() const {
175 std::map<std::string, std::string> result;
176
177 /* If input workspace is a group, check that each group members is a valid 2D Workspace,
178 otherwise check that the input is a valid 2D workspace.*/
180 auto valid2DWorkspace = [](const Workspace_sptr &ws) {
181 return ws && std::dynamic_pointer_cast<const Mantid::DataObjects::Workspace2D>(ws);
182 };
183 if (workspace->isGroup()) {
184 const auto &groupItems = std::dynamic_pointer_cast<WorkspaceGroup>(workspace)->getAllItems();
185 if (std::any_of(groupItems.cbegin(), groupItems.cend(),
186 [&](const auto &childWs) { return !valid2DWorkspace(childWs); })) {
187 result.emplace("InputWorkspace",
188 "All input workspaces in the input group must be a Workspace2D with numeric axis.");
189 }
190 } else {
191 if (!valid2DWorkspace(workspace)) {
192 result.emplace("InputWorkspace", "The InputWorkspace must be a Workspace2D with numeric axis.");
193 }
194 }
195
196 // Transmission data should be 1D
199
200 auto checkTransmission = [&result](const MatrixWorkspace_sptr &trans, const std::string &propertyName) {
201 if (trans->getNumberHistograms() != 1) {
202 result.emplace(propertyName, "The input workspaces for transmissions have to be 1D.");
203 }
204 };
205
206 if (transmission) {
207 checkTransmission(transmission, StandardProperties::TRANSMISSION);
208 }
209 if (transmissionCan) {
210 checkTransmission(transmissionCan, StandardProperties::TRANSMISSION_CAN);
211 }
212
213 return result;
214}
215
216std::map<std::string, std::string>
217SaveNXcanSASBase::validatePolarizedInputWorkspace(const std::vector<std::string> &spinVec) const {
218 std::map<std::string, std::string> result;
220
221 if (!workspace->isGroup()) {
223 "Input Workspaces for polarized data can only be workspace groups.");
224 return result;
225 }
226 const auto wsGroup = std::dynamic_pointer_cast<WorkspaceGroup>(workspace);
227 const auto &entries = wsGroup->getNumberOfEntries();
228 if (entries != 2 && entries != 4) {
230 "Input Group Workspace can only contain 2 or 4 workspace members.");
231 }
232
233 if (entries != static_cast<int>(spinVec.size())) {
234 result.emplace(PolProperties::INPUT_SPIN_STATES, "The number of spin states is different than the number of"
235 " member workspaces on the InputWorkspace group");
236 }
237
238 const auto wsVec = wsGroup->getAllItems();
239 // The group has been validated in StandardInputs, so workspaces are safely downcasted to MatrixWorkspaces
240 auto const dim0 = getWorkspaceDimensionality(std::dynamic_pointer_cast<MatrixWorkspace>(wsVec.at(0)));
241 if (std::any_of(wsVec.cbegin(), wsVec.cend(), [&dim0](const Workspace_sptr &ws) {
242 return dim0 != getWorkspaceDimensionality(std::dynamic_pointer_cast<MatrixWorkspace>(ws));
243 })) {
244 result.emplace(StandardProperties::INPUT_WORKSPACE, "All workspaces in group must have the same dimensionality");
245 }
246
247 return result;
248}
249std::map<std::string, std::string>
250SaveNXcanSASBase::validateSpinStateStrings(const std::vector<std::string> &spinVec) const {
251 std::map<std::string, std::string> result;
252
253 // Validating spin strings
254 if (spinVec.size() == 4 && std::any_of(spinVec.cbegin(), spinVec.cend(), [](const std::string &spinPair) {
255 return (spinPair.find(SpinStateNXcanSAS::SPIN_ZERO) != std::string::npos);
256 })) {
257 result.emplace(PolProperties::INPUT_SPIN_STATES, "Full polarized group can't contain spin state 0");
258 }
259
260 if (spinVec.size() == 2) {
261 if (std::any_of(spinVec.cbegin(), spinVec.cend(),
262 [](const std::string &state) { return state.find('1') == std::string::npos; })) {
263 result.emplace(PolProperties::INPUT_SPIN_STATES, "There can't be 00 state");
264 }
265 const auto noPin =
266 spinVec[0].starts_with(SpinStateNXcanSAS::SPIN_ZERO) && spinVec[1].starts_with(SpinStateNXcanSAS::SPIN_ZERO);
267 const auto noPout =
268 spinVec[0].ends_with(SpinStateNXcanSAS::SPIN_ZERO) && spinVec[1].ends_with(SpinStateNXcanSAS::SPIN_ZERO);
269 if (noPin == noPout) {
271 "The 0 polarized state can only be either Pin or Pout for 2 spin configurations");
272 }
273 }
274 return result;
275}
276
277std::map<std::string, std::string> SaveNXcanSASBase::validatePolarizedMetadata() const {
278 std::map<std::string, std::string> result;
279 const std::string magneticFieldDirection = getProperty(PolProperties::MAG_FIELD_DIR);
280 if (!magneticFieldDirection.empty()) {
281 const auto direction = VectorHelper::splitStringIntoVector<std::string>(magneticFieldDirection);
282 try {
283 std::for_each(direction.cbegin(), direction.cend(), [](const std::string &val) { (void)std::stod(val); });
284 } catch (const std::invalid_argument &) {
285 result.emplace(PolProperties::MAG_FIELD_DIR, "Some value of the magnetic field direction vector is not a number");
286 }
287 if (direction.size() != 3) {
288 result.emplace(PolProperties::MAG_FIELD_DIR,
289 "Magnetic Field Direction should contain 3 comma separated values to represent a 3D vector");
290 }
291 }
292 return result;
293}
294
308 const auto &radiationSource = getPropertyValue(StandardProperties::RADIATION_SOURCE);
309 const std::string &geometry = getProperty(StandardProperties::GEOMETRY);
310 const double beamHeight = getProperty(StandardProperties::SAMPLE_HEIGHT);
311 const double beamWidth = getProperty(StandardProperties::SAMPLE_WIDTH);
312 const double sampleThickness = getProperty(StandardProperties::SAMPLE_THICKNESS);
313 const auto &detectorNames = getPropertyValue(StandardProperties::DETECTOR_NAMES);
314
317
318 // Add the instrument information
319 const auto detectors = Kernel::VectorHelper::splitStringIntoVector<std::string>(detectorNames);
320 addInstrument(sasEntry, workspace, radiationSource, geometry, beamHeight, beamWidth, detectors);
321
322 // Add the sample information
323 addSample(sasEntry, sampleThickness);
324
325 // Get additional run numbers
326 const auto &sampleTransmissionRun = getPropertyValue(StandardProperties::SAMPLE_TRANS_RUN_NUMBER);
330
331 // Get scaled background subtraction information
332 const auto &scaledBgSubWorkspace = getPropertyValue(StandardProperties::BKG_SUB_WORKSPACE);
333
334 addProcess(sasEntry, workspace, transmissionCan);
335
336 // Add additional process information
337 auto process = sasEntry.openGroup(sasProcessGroupName);
338
339 if (transmissionCan) {
340 H5Util::write(process, sasProcessTermCanScatter, canScatterRun);
341 H5Util::write(process, sasProcessTermCanDirect, canDirectRun);
342 }
343 if (transmissionSample) {
344 H5Util::write(process, sasProcessTermSampleDirect, sampleDirectRun);
345 H5Util::write(process, sasProcessTermSampleTrans, sampleTransmissionRun);
346 }
347
348 if (!scaledBgSubWorkspace.empty()) {
349 const double scaledBgSubScaleFactor = getProperty(StandardProperties::BKG_SUB_SCALE);
350 H5Util::write(process, sasProcessTermScaledBgSubWorkspace, scaledBgSubWorkspace);
352 {});
353 }
354
355 // Add the transmissions for sample
356 if (transmissionSample) {
357 addTransmission(sasEntry, transmissionSample, sasTransmissionSpectrumNameSampleAttrValue);
358 }
359
360 // Add the transmissions for can
361 if (transmissionCan) {
363 }
364}
365
376
377 for (const auto &[compType, compVec] : createPolarizedComponentMap()) {
378 for (size_t i = 0; i < compVec.size(); i++) {
379 const auto suffix = compVec.size() > 1 ? addDigit(i + 1) : "";
380 addPolarizer(sasEntry, workspace, compVec.at(i), compType, suffix);
381 }
382 }
384 const std::string &magneticFieldDirStr = getProperty(PolProperties::MAG_FIELD_DIR);
385 addSampleEMFields(sasEntry, workspace, sasSampleMagneticField, magneticFieldDirStr);
386}
387
392std::map<std::string, std::vector<std::string>> SaveNXcanSASBase::createPolarizedComponentMap() const {
393 // Assumption here is that we will pass the IDF component names as comma separated lists
394 std::map<std::string, std::vector<std::string>> componentMap;
395 for (const auto &[compType, compName] : PolProperties::POL_COMPONENTS) {
396 std::string const &componentName = getProperty(compName);
397 if (!componentName.empty()) {
398 const auto componentVec = VectorHelper::splitStringIntoVector<std::string>(componentName);
399 componentMap.emplace(std::make_pair(compType, componentVec));
400 }
401 }
402 return componentMap;
403}
404
413 const std::string &suffix) const {
414 const std::string sasEntryName = sasEntryGroupName + suffix;
415 auto sasEntry = H5Util::createGroupCanSAS(file, sasEntryName, nxEntryClassAttr, sasEntryClassAttr);
416
417 // Add version
419
420 // Add definition
422
423 // Add title
424 const auto workspaceTitle = workspace->getTitle();
425 H5Util::write(sasEntry, sasEntryTitle, workspaceTitle);
426
427 // Add run
428 auto runNumber = workspace->getRunNumber();
429 H5Util::write(sasEntry, sasEntryRun, std::to_string(runNumber));
430
431 return sasEntry;
432}
433
443 areAxesNumeric(workspace, static_cast<int>(dim));
444 switch (dim) {
445 case (WorkspaceDimensionality::oneD):
446 addData1D(data, workspace);
447 break;
448 case (WorkspaceDimensionality::twoD):
449 addData2D(data, workspace);
450 break;
451 default:
452 throw std::runtime_error("SaveNXcanSAS: The provided workspace "
453 "dimensionality is not 1D or 2D.");
454 }
455}
456
466
472void SaveNXcanSASBase::savePolarizedGroup(const WorkspaceGroup_sptr &wsGroup, const std::filesystem::path &path) const {
473 auto file = prepareFile(path);
474 // Necessary metdata will be taken from first workspace of the group
475 auto workspace = std::dynamic_pointer_cast<MatrixWorkspace>(wsGroup->getItem(0));
476 areAxesNumeric(workspace, static_cast<int>(getWorkspaceDimensionality(workspace)));
477
478 m_progress->report("Adding a new entry.");
479 auto sasEntry = addSasEntry(file, workspace, sasEntryDefaultSuffix);
480
481 // Add metadata for canSAS file: Instrument, Sample, Process
482 m_progress->report("Adding standard metadata");
484 // Add polarized Metadata
485 m_progress->report("Adding polarized metadata");
487
488 // Add polarized Data
489 m_progress->report("Adding polarized data.");
490 addPolarizedData(sasEntry, wsGroup);
491
492 file.close();
493}
494
501 const std::filesystem::path &path) const {
502 auto file = prepareFile(path);
503
504 m_progress->report("Adding a new entry.");
505 auto sasEntry = addSasEntry(file, workspace, sasEntryDefaultSuffix);
506
507 // Add metadata for canSAS file: Instrument, Sample, Process
508 m_progress->report("Adding standard metadata");
510
511 // Add 1D or 2D data
512 m_progress->report("Adding data.");
513 addData(sasEntry, workspace);
514
515 file.close();
516}
517
518} // namespace Mantid::DataHandling
IPeaksWorkspace_sptr workspace
std::map< DeltaEMode::Type, std::string > index
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
std::string getPropertyValue(const std::string &name) const override
Get the value of a property as a string.
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
@ Save
to specify a file to write to, the file may or may not exist
A property class for workspaces.
std::map< std::string, std::vector< std::string > > createPolarizedComponentMap() const
Creates a component map to access the polarizer component names defined in input properties on the co...
std::map< std::string, std::string > validatePolarizedMetadata() const
std::map< std::string, std::string > validateStandardInputs() const
H5::Group addSasEntry(H5::H5File &file, const Mantid::API::MatrixWorkspace_sptr &workspace, const std::string &suffix) const
Add the sasEntry to the sasroot.
void addPolarizedData(H5::Group &group, const Mantid::API::WorkspaceGroup_sptr &wsGroup) const
Calls out polarized data function from helper library.
std::unique_ptr< API::Progress > m_progress
void addPolarizedMetadata(const Mantid::API::MatrixWorkspace_sptr &workspace, H5::Group &sasEntry) const
Adds polarized metadata to a NXcanSAS file format.
void addStandardMetadata(const Mantid::API::MatrixWorkspace_sptr &workspace, H5::Group &sasEntry) const
Adds standard metadata to a NXcanSAS file format.
void addData(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace) const
Sorts out dimensionality of the data (1D, 2D) and calls helper function to insert data in workspace t...
void savePolarizedGroup(const API::WorkspaceGroup_sptr &wsGroup, const std::filesystem::path &path) const
Saves NXcanSAS data for a group workspace.
void saveSingleWorkspaceFile(const API::MatrixWorkspace_sptr &workspace, const std::filesystem::path &path) const
Saves NXcanSAS data for a matrix workspace.
std::map< std::string, std::string > validateSpinStateStrings(const std::vector< std::string > &spinVec) const
std::map< std::string, std::string > validatePolarizedInputWorkspace(const std::vector< std::string > &spinVec) const
std::shared_ptr< WorkspaceGroup > WorkspaceGroup_sptr
shared pointer to Mantid::API::WorkspaceGroup
std::shared_ptr< Workspace > Workspace_sptr
shared pointer to Mantid::API::Workspace
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
std::map< std::string, std::string > POL_COMPONENTS
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< std::string > &detectorNames)
Add the instrument group to the NXcanSAS file.
const std::string sasProcessTermScaledBgSubScaleFactor
void MANTID_DATAHANDLING_DLL addData2D(H5::Group &data, const Mantid::API::MatrixWorkspace_sptr &workspace)
Stores the 2D signal and Q data in the HDF5 file.
const std::string sasTransmissionSpectrumNameCanAttrValue
void MANTID_DATAHANDLING_DLL addSampleEMFields(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace, const std::string &emFieldStrengthLog, const std::string &emFieldDir)
Adds the direction and strength of either magnetic or electric field on the sample.
void MANTID_DATAHANDLING_DLL addData1D(H5::Group &data, const Mantid::API::MatrixWorkspace_sptr &workspace)
Adds signal and Q data to the data group from 1D reduced SANS data.
void MANTID_DATAHANDLING_DLL addProcess(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace)
std::string MANTID_DATAHANDLING_DLL addDigit(size_t index)
void MANTID_DATAHANDLING_DLL addPolarizer(H5::Group &group, const Mantid::API::MatrixWorkspace_sptr &workspace, const std::string &componentName, const std::string &componentType, const std::string &groupSuffix)
Add the polarizer component information to the instrument cansas group.
const std::string sasTransmissionSpectrumNameSampleAttrValue
WorkspaceDimensionality getWorkspaceDimensionality(const Mantid::API::MatrixWorkspace_sptr &workspace)
Retrieves workspace dimensionality enum value: oneD , twoD, other (error)
void MANTID_DATAHANDLING_DLL addPolarizedData(H5::Group &data, const Mantid::API::WorkspaceGroup_sptr &wsGroup, const std::string &inputSpinStates)
Adds signal, Q and spin data to the data group from 1D or 2D reduced polarized SANS data.
void MANTID_DATAHANDLING_DLL addSample(H5::Group &group, const double &sampleThickness)
Adds sample thickness information to the sas sample group.
H5::H5File MANTID_DATAHANDLING_DLL prepareFile(const std::filesystem::path &path)
Creates and opens a H5 File in the given path.
const std::string sasProcessTermScaledBgSubWorkspace
void MANTID_DATAHANDLING_DLL addTransmission(H5::Group &group, const Mantid::API::MatrixWorkspace_const_sptr &workspace, const std::string &transmissionName)
Add a transmission group to the cansas file, including metadata extracted from the transmission works...
template MANTID_KERNEL_DLL std::vector< std::string > splitStringIntoVector< std::string >(std::string listString, const std::string &separator)
MANTID_NEXUS_DLL void writeStrAttribute(const H5::H5Object &object, const std::string &name, const std::string &value)
Definition H5Util.cpp:208
MANTID_NEXUS_DLL H5::Group createGroupCanSAS(H5::Group &group, const std::string &name, const std::string &nxtype, const std::string &cstype)
MANTID_NEXUS_DLL void write(H5::Group &group, const std::string &name, const std::string &value)
void writeScalarDataSetWithStrAttributes(H5::Group &group, const std::string &name, const T &value, const std::map< std::string, std::string > &attributes)
Definition H5Util.cpp:244
Header for a base Nexus::Exception.
std::string to_string(const wide_integer< Bits, Signed > &n)
@ Input
An input workspace.
Definition Property.h:54