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"
23#include "MantidNexus/H5Util.h"
24
25#include <algorithm>
26
27using namespace Mantid::API;
28using namespace Mantid::DataHandling::NXcanSAS;
29using namespace Mantid::Kernel;
30using namespace Mantid::Nexus;
31
32namespace {
33bool hasUnit(const std::string &unitToCompareWith, const MatrixWorkspace_sptr &ws) {
34 if (ws->axes() == 0) {
35 return false;
36 }
37 const auto unit = ws->getAxis(0)->unit();
38 return (unit && unit->unitID() == unitToCompareWith);
39}
40
41void areAxesNumeric(const MatrixWorkspace_sptr &workspace, const int numberOfDims = 1) {
42 if (numberOfDims == 0) {
43 throw std::invalid_argument("Workspace can only have 1 or 2 dimensions");
44 }
45 std::vector<int> indices(numberOfDims);
46 std::generate(indices.begin(), indices.end(), [i = 0]() mutable { return i++; });
47 const auto itIndex = std::find_if(indices.cbegin(), indices.cend(), [&workspace](const auto &index) {
48 return !workspace->getAxis(index)->isNumeric();
49 });
50 if (itIndex != indices.cend()) {
51 throw std::invalid_argument("Invalid workspace: Axis " + std::to_string(*itIndex) + " is not numeric");
52 }
53}
54
55bool checkValidMatrixWorkspace(const Workspace_sptr &ws) {
56 const auto &ws_input = std::dynamic_pointer_cast<MatrixWorkspace>(ws);
57 return (ws_input && hasUnit("MomentumTransfer", ws_input) && ws_input->isCommonBins());
58}
59
60std::string validateGroupWithProperties(const Workspace_sptr &ws) {
61 if (!ws) {
62 return "Workspace has to be a valid workspace";
63 }
64
65 if (ws->isGroup()) {
66 const auto &groupItems = std::dynamic_pointer_cast<WorkspaceGroup>(ws)->getAllItems();
67 if (std::any_of(groupItems.cbegin(), groupItems.cend(),
68 [](const auto &childWs) { return !checkValidMatrixWorkspace(childWs); })) {
69 return "Workspace must have common bins and Momentum transfer units";
70 }
71 return "";
72 }
73
74 if (!checkValidMatrixWorkspace(ws)) {
75 return "Workspace must have common bins and Momentum transfer units";
76 }
77 return "";
78}
79
80} // namespace
81
82namespace Mantid::DataHandling {
83
85 // Standard NXcanSAS properties
86 auto groupValidator = std::make_shared<Kernel::LambdaValidator<Workspace_sptr>>(validateGroupWithProperties);
88 Kernel::Direction::Input, groupValidator),
89 "The input workspace, which must be in units of Q. Can be a 1D or a 2D workspace.");
90 declareProperty(std::make_unique<FileProperty>(StandardProperties::FILENAME, "", API::FileProperty::Save, ".h5"),
91 "The name of the .h5 file to save");
92
93 std::vector<std::string> radiationSourceOptions{"Spallation Neutron Source",
94 "Pulsed Reactor Neutron Source",
95 "Reactor Neutron Source",
96 "Synchrotron X-ray Source",
97 "Pulsed Muon Source",
98 "Rotating Anode X-ray",
99 "Fixed Tube X-ray",
100 "neutron",
101 "x-ray",
102 "muon",
103 "electron"};
104 declareProperty(StandardProperties::RADIATION_SOURCE, "Spallation Neutron Source",
105 std::make_shared<Kernel::StringListValidator>(radiationSourceOptions), "The type of radiation used.");
107 "Specify in a comma separated list, which detectors to store "
108 "information about; \nwhere each name must match a name "
109 "given for a detector in the [[IDF|instrument definition "
110 "file (IDF)]]. \nIDFs are located in the instrument "
111 "sub-directory of the Mantid install directory.");
114 std::make_shared<API::WorkspaceUnitValidator>("Wavelength")),
115 "The transmission workspace. Optional. If given, will be saved at "
116 "TransmissionSpectrum");
117
120 std::make_shared<API::WorkspaceUnitValidator>("Wavelength")),
121 "The transmission workspace of the Can. Optional. If given, will be "
122 "saved at TransmissionSpectrum");
123
125 "The run number for the sample transmission workspace. Optional.");
127 "The run number for the sample direct workspace. Optional.");
129 "The run number for the can scatter workspace. Optional.");
131 "The run number for the can direct workspace. Optional.");
132
135 "The name of the workspace used in the scaled background subtraction, to be included in the metadata. Optional.");
138 "The scale factor used in the scaled background subtraction, to be included in the metadata. Optional.");
139
140 std::vector<std::string> const geometryOptions{"Cylinder", "FlatPlate", "Flat plate", "Disc", "Unknown"};
142 std::make_shared<Kernel::StringListValidator>(geometryOptions),
143 "The geometry type of the collimation.");
145 "The height of the collimation element in mm. If specified as 0 it will not be recorded.");
147 "The width of the collimation element in mm. If specified as 0 it will not be recorded.");
149 "The thickness of the sample in mm. If specified as 0 it will not be recorded.");
150}
151
153 const auto spinStateValidator = std::make_shared<Kernel::SpinStateValidator>(
154 std::unordered_set<int>{2, 4}, false, SpinStateNXcanSAS::SPIN_PARA, SpinStateNXcanSAS::SPIN_ANTIPARA, true,
156
158 "The order of the spin states in the input group workspace: +1 Polarization parallel to polarizer, "
159 "-1 antiparallel and 0 no polarization");
161 "The name of the Polarizer Component as defined in the IDF. i.e. 'short-polarizer'");
163 "The name of the Analyzer Component as defined in the IDF. i.e. 'helium-analyzer'");
165 "Comma separated list of flipper components as defined in the IDF i.e. 'RF-flipper");
167 "The name of sample logs in which the magnetic field strength is stored");
169 "Direction of the magnetic field on the sample: comma separated vector"
170 "with three values: Polar, Azimuthal and Rotation angles");
171}
172
173std::map<std::string, std::string> SaveNXcanSASBase::validateStandardInputs() const {
174 std::map<std::string, std::string> result;
175
176 /* If input workspace is a group, check that each group members is a valid 2D Workspace,
177 otherwise check that the input is a valid 2D workspace.*/
179 auto valid2DWorkspace = [](const Workspace_sptr &ws) {
180 return ws && std::dynamic_pointer_cast<const Mantid::DataObjects::Workspace2D>(ws);
181 };
182 if (workspace->isGroup()) {
183 const auto &groupItems = std::dynamic_pointer_cast<WorkspaceGroup>(workspace)->getAllItems();
184 if (std::any_of(groupItems.cbegin(), groupItems.cend(),
185 [&](const auto &childWs) { return !valid2DWorkspace(childWs); })) {
186 result.emplace("InputWorkspace",
187 "All input workspaces in the input group must be a Workspace2D with numeric axis.");
188 }
189 } else {
190 if (!valid2DWorkspace(workspace)) {
191 result.emplace("InputWorkspace", "The InputWorkspace must be a Workspace2D with numeric axis.");
192 }
193 }
194
195 // Transmission data should be 1D
198
199 auto checkTransmission = [&result](const MatrixWorkspace_sptr &trans, const std::string &propertyName) {
200 if (trans->getNumberHistograms() != 1) {
201 result.emplace(propertyName, "The input workspaces for transmissions have to be 1D.");
202 }
203 };
204
205 if (transmission) {
206 checkTransmission(transmission, StandardProperties::TRANSMISSION);
207 }
208 if (transmissionCan) {
209 checkTransmission(transmissionCan, StandardProperties::TRANSMISSION_CAN);
210 }
211
212 return result;
213}
214
215std::map<std::string, std::string>
216SaveNXcanSASBase::validatePolarizedInputWorkspace(const std::vector<std::string> &spinVec) const {
217 std::map<std::string, std::string> result;
219
220 if (!workspace->isGroup()) {
222 "Input Workspaces for polarized data can only be workspace groups.");
223 return result;
224 }
225 const auto wsGroup = std::dynamic_pointer_cast<WorkspaceGroup>(workspace);
226 const auto &entries = wsGroup->getNumberOfEntries();
227 if (entries != 2 && entries != 4) {
229 "Input Group Workspace can only contain 2 or 4 workspace members.");
230 }
231
232 if (entries != static_cast<int>(spinVec.size())) {
233 result.emplace(PolProperties::INPUT_SPIN_STATES, "The number of spin states is different than the number of"
234 " member workspaces on the InputWorkspace group");
235 }
236
237 const auto wsVec = wsGroup->getAllItems();
238 // The group has been validated in StandardInputs, so workspaces are safely downcasted to MatrixWorkspaces
239 auto const dim0 = getWorkspaceDimensionality(std::dynamic_pointer_cast<MatrixWorkspace>(wsVec.at(0)));
240 if (std::any_of(wsVec.cbegin(), wsVec.cend(), [&dim0](const Workspace_sptr &ws) {
241 return dim0 != getWorkspaceDimensionality(std::dynamic_pointer_cast<MatrixWorkspace>(ws));
242 })) {
243 result.emplace(StandardProperties::INPUT_WORKSPACE, "All workspaces in group must have the same dimensionality");
244 }
245
246 return result;
247}
248std::map<std::string, std::string>
249SaveNXcanSASBase::validateSpinStateStrings(const std::vector<std::string> &spinVec) const {
250 std::map<std::string, std::string> result;
251
252 // Validating spin strings
253 if (spinVec.size() == 4 && std::any_of(spinVec.cbegin(), spinVec.cend(), [](const std::string &spinPair) {
254 return (spinPair.find(SpinStateNXcanSAS::SPIN_ZERO) != std::string::npos);
255 })) {
256 result.emplace(PolProperties::INPUT_SPIN_STATES, "Full polarized group can't contain spin state 0");
257 }
258
259 if (spinVec.size() == 2) {
260 if (std::any_of(spinVec.cbegin(), spinVec.cend(),
261 [](const std::string &state) { return state.find('1') == std::string::npos; })) {
262 result.emplace(PolProperties::INPUT_SPIN_STATES, "There can't be 00 state");
263 }
264 const auto noPin =
265 spinVec[0].starts_with(SpinStateNXcanSAS::SPIN_ZERO) && spinVec[1].starts_with(SpinStateNXcanSAS::SPIN_ZERO);
266 const auto noPout =
267 spinVec[0].ends_with(SpinStateNXcanSAS::SPIN_ZERO) && spinVec[1].ends_with(SpinStateNXcanSAS::SPIN_ZERO);
268 if (noPin == noPout) {
270 "The 0 polarized state can only be either Pin or Pout for 2 spin configurations");
271 }
272 }
273 return result;
274}
275
276std::map<std::string, std::string> SaveNXcanSASBase::validatePolarizedMetadata() const {
277 std::map<std::string, std::string> result;
278 const std::string magneticFieldDirection = getProperty(PolProperties::MAG_FIELD_DIR);
279 if (!magneticFieldDirection.empty()) {
280 const auto direction = VectorHelper::splitStringIntoVector<std::string>(magneticFieldDirection);
281 try {
282 std::for_each(direction.cbegin(), direction.cend(), [](const std::string &val) { (void)std::stod(val); });
283 } catch (const std::invalid_argument &) {
284 result.emplace(PolProperties::MAG_FIELD_DIR, "Some value of the magnetic field direction vector is not a number");
285 }
286 if (direction.size() != 3) {
287 result.emplace(PolProperties::MAG_FIELD_DIR,
288 "Magnetic Field Direction should contain 3 comma separated values to represent a 3D vector");
289 }
290 }
291 return result;
292}
293
307 const auto &radiationSource = getPropertyValue(StandardProperties::RADIATION_SOURCE);
308 const std::string &geometry = getProperty(StandardProperties::GEOMETRY);
309 const double beamHeight = getProperty(StandardProperties::SAMPLE_HEIGHT);
310 const double beamWidth = getProperty(StandardProperties::SAMPLE_WIDTH);
311 const double sampleThickness = getProperty(StandardProperties::SAMPLE_THICKNESS);
312 const auto &detectorNames = getPropertyValue(StandardProperties::DETECTOR_NAMES);
313
316
317 // Add the instrument information
318 const auto detectors = Kernel::VectorHelper::splitStringIntoVector<std::string>(detectorNames);
319 addInstrument(sasEntry, workspace, radiationSource, geometry, beamHeight, beamWidth, detectors);
320
321 // Add the sample information
322 addSample(sasEntry, sampleThickness);
323
324 // Get additional run numbers
325 const auto &sampleTransmissionRun = getPropertyValue(StandardProperties::SAMPLE_TRANS_RUN_NUMBER);
329
330 // Get scaled background subtraction information
331 const auto &scaledBgSubWorkspace = getPropertyValue(StandardProperties::BKG_SUB_WORKSPACE);
332
333 addProcess(sasEntry, workspace, transmissionCan);
334
335 // Add additional process information
336 auto process = sasEntry.openGroup(sasProcessGroupName);
337
338 if (transmissionCan) {
339 H5Util::write(process, sasProcessTermCanScatter, canScatterRun);
340 H5Util::write(process, sasProcessTermCanDirect, canDirectRun);
341 }
342 if (transmissionSample) {
343 H5Util::write(process, sasProcessTermSampleDirect, sampleDirectRun);
344 H5Util::write(process, sasProcessTermSampleTrans, sampleTransmissionRun);
345 }
346
347 if (!scaledBgSubWorkspace.empty()) {
348 const double scaledBgSubScaleFactor = getProperty(StandardProperties::BKG_SUB_SCALE);
349 H5Util::write(process, sasProcessTermScaledBgSubWorkspace, scaledBgSubWorkspace);
351 {});
352 }
353
354 // Add the transmissions for sample
355 if (transmissionSample) {
356 addTransmission(sasEntry, transmissionSample, sasTransmissionSpectrumNameSampleAttrValue);
357 }
358
359 // Add the transmissions for can
360 if (transmissionCan) {
362 }
363}
364
375
376 for (const auto &[compType, compVec] : createPolarizedComponentMap()) {
377 for (size_t i = 0; i < compVec.size(); i++) {
378 const auto suffix = compVec.size() > 1 ? addDigit(i + 1) : "";
379 addPolarizer(sasEntry, workspace, compVec.at(i), compType, suffix);
380 }
381 }
383 const std::string &magneticFieldDirStr = getProperty(PolProperties::MAG_FIELD_DIR);
384 addSampleEMFields(sasEntry, workspace, sasSampleMagneticField, magneticFieldDirStr);
385}
386
391std::map<std::string, std::vector<std::string>> SaveNXcanSASBase::createPolarizedComponentMap() const {
392 // Assumption here is that we will pass the IDF component names as comma separated lists
393 std::map<std::string, std::vector<std::string>> componentMap;
394 for (const auto &[compType, compName] : PolProperties::POL_COMPONENTS) {
395 std::string const &componentName = getProperty(compName);
396 if (!componentName.empty()) {
397 const auto componentVec = VectorHelper::splitStringIntoVector<std::string>(componentName);
398 componentMap.emplace(std::make_pair(compType, componentVec));
399 }
400 }
401 return componentMap;
402}
403
412 const std::string &suffix) const {
413 const std::string sasEntryName = sasEntryGroupName + suffix;
414 auto sasEntry = H5Util::createGroupCanSAS(file, sasEntryName, nxEntryClassAttr, sasEntryClassAttr);
415
416 // Add version
418
419 // Add definition
421
422 // Add title
423 const auto workspaceTitle = workspace->getTitle();
424 H5Util::write(sasEntry, sasEntryTitle, workspaceTitle);
425
426 // Add run
427 auto runNumber = workspace->getRunNumber();
428 H5Util::write(sasEntry, sasEntryRun, std::to_string(runNumber));
429
430 return sasEntry;
431}
432
442 areAxesNumeric(workspace, static_cast<int>(dim));
443 switch (dim) {
444 case (WorkspaceDimensionality::oneD):
445 addData1D(data, workspace);
446 break;
447 case (WorkspaceDimensionality::twoD):
448 addData2D(data, workspace);
449 break;
450 default:
451 throw std::runtime_error("SaveNXcanSAS: The provided workspace "
452 "dimensionality is not 1D or 2D.");
453 }
454}
455
465
471void SaveNXcanSASBase::savePolarizedGroup(const WorkspaceGroup_sptr &wsGroup, const std::filesystem::path &path) const {
472 auto file = prepareFile(path);
473 // Necessary metdata will be taken from first workspace of the group
474 auto workspace = std::dynamic_pointer_cast<MatrixWorkspace>(wsGroup->getItem(0));
475 areAxesNumeric(workspace, static_cast<int>(getWorkspaceDimensionality(workspace)));
476
477 m_progress->report("Adding a new entry.");
478 auto sasEntry = addSasEntry(file, workspace, sasEntryDefaultSuffix);
479
480 // Add metadata for canSAS file: Instrument, Sample, Process
481 m_progress->report("Adding standard metadata");
483 // Add polarized Metadata
484 m_progress->report("Adding polarized metadata");
486
487 // Add polarized Data
488 m_progress->report("Adding polarized data.");
489 addPolarizedData(sasEntry, wsGroup);
490
491 file.close();
492}
493
500 const std::filesystem::path &path) const {
501 auto file = prepareFile(path);
502
503 m_progress->report("Adding a new entry.");
504 auto sasEntry = addSasEntry(file, workspace, sasEntryDefaultSuffix);
505
506 // Add metadata for canSAS file: Instrument, Sample, Process
507 m_progress->report("Adding standard metadata");
509
510 // Add 1D or 2D data
511 m_progress->report("Adding data.");
512 addData(sasEntry, workspace);
513
514 file.close();
515}
516
517} // 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 DLLExport 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:53