28#include <boost/algorithm/string/trim.hpp>
29#include <boost/regex.hpp>
47enum class StoreType { Qx, Qy, I, Idev, Other };
49bool isCanSASCompliant(
bool isStrict,
const std::string &input) {
50 auto baseRegex = isStrict ? boost::regex(
"[a-z_][a-z0-9_]*") :
boost::regex(
"[A-Za-z_][\\w_]*");
51 return boost::regex_match(input, baseRegex);
54void removeSpecialCharacters(std::string &input) {
55 boost::regex toReplace(
"[-\\.]");
56 std::string replaceWith(
"_");
57 input = boost::regex_replace(input, toReplace, replaceWith);
60std::string makeCompliantName(
const std::string &input,
bool isStrict,
61 const std::function<
void(std::string &)> &captializeStrategy) {
64 if (!isCanSASCompliant(isStrict, output)) {
65 removeSpecialCharacters(output);
66 captializeStrategy(output);
68 if (!isCanSASCompliant(isStrict, output)) {
69 std::string message =
"SaveNXcanSAS: The input " + input +
"is not compliant with the NXcanSAS format.";
70 throw std::runtime_error(message);
76template <
typename NumT>
77void writeArray1DWithStrAttributes(H5::Group &group,
const std::string &dataSetName,
const std::vector<NumT> &values,
78 const std::map<std::string, std::string> &attributes) {
80 auto dataSet = group.openDataSet(dataSetName);
81 for (
const auto &attribute : attributes) {
86H5::DSetCreatPropList setCompression2D(
const hsize_t *chunkDims,
const int deflateLevel = 6) {
87 H5::DSetCreatPropList propList;
89 propList.setChunk(rank, chunkDims);
90 propList.setDeflate(deflateLevel);
94template <
typename Functor>
96 Functor func,
const std::map<std::string, std::string> &attributes) {
100 const size_t dimension0 =
workspace->getNumberHistograms();
101 const size_t dimension1 =
workspace->y(0).size();
102 const hsize_t rank = 2;
103 hsize_t dimensionArray[rank] = {
static_cast<hsize_t
>(dimension0),
static_cast<hsize_t
>(dimension1)};
106 hsize_t start[rank] = {0, 0};
109 hsize_t sizeOfSingleSlab[rank] = {1, dimensionArray[1]};
112 auto fileSpace = H5::DataSpace(rank, dimensionArray);
113 H5::DataType dataType(getType<double>());
116 H5::DSetCreatPropList propList = setCompression2D(sizeOfSingleSlab);
119 auto dataSet = group.createDataSet(dataSetName, dataType, fileSpace, propList);
122 hsize_t memSpaceDimension[1] = {dimension1};
123 H5::DataSpace memSpace(1, memSpaceDimension);
128 fileSpace.selectHyperslab(H5S_SELECT_SET, sizeOfSingleSlab, start);
131 dataSet.write(func(
workspace,
index), dataType, memSpace, fileSpace);
137 for (
const auto &attribute : attributes) {
142std::vector<std::string> splitDetectorNames(std::string detectorNames) {
143 const std::string delimiter =
",";
144 std::vector<std::string> detectors;
146 std::string detectorName;
147 while ((pos = detectorNames.find(delimiter)) != std::string::npos) {
148 detectorName = detectorNames.substr(0, pos);
149 boost::algorithm::trim(detectorName);
150 detectors.emplace_back(detectorName);
151 detectorNames.erase(0, pos + delimiter.length());
154 boost::algorithm::trim(detectorNames);
155 detectors.emplace_back(detectorNames);
179 auto workspaceTitle =
workspace->getTitle();
183 const auto runNumber =
workspace->getRunNumber();
191 auto instrument =
workspace->getInstrument();
192 return instrument->getFullName();
196 auto date =
workspace->getWorkspaceStartDate();
197 auto instrumentName = getInstrumentName(
workspace);
202 const std::vector<std::string> &detectorNames) {
204 if (!detectorNames.empty()) {
205 for (
const auto &detectorName : detectorNames) {
206 if (detectorName.empty()) {
213 auto instrument =
workspace->getInstrument();
214 auto component = instrument->getComponentByName(detectorName);
217 const auto sample = instrument->getSample();
218 const auto distance = component->getDistance(*sample);
219 std::map<std::string, std::string> sddAttributes;
240 const std::string &radiationSource,
const std::vector<std::string> &detectorNames) {
245 auto instrumentName = getInstrumentName(
workspace);
249 addDetectors(instrument,
workspace, detectorNames);
264std::string getDate() {
268 strftime(temp, 25,
"%Y-%m-%dT%H:%M:%S", localtime(&rawtime));
269 std::string sasDate(temp);
280void addPropertyFromRunIfExists(
Run const &run, std::string
const &propertyName, H5::Group &sasGroup,
281 std::string
const &sasTerm) {
304 auto date = getDate();
334 auto date = getDate();
346 const auto canRun = canWorkspace->getRunNumber();
354void createNote(H5::Group &group) {
370void addNoteToProcess(H5::Group &group,
const std::string &firstEntryName,
const std::string &firstEntryValue,
371 const std::string &secondEntryName,
const std::string &secondEntryValue) {
381 auto numberOfHistograms =
workspace->getNumberHistograms();
383 if (numberOfHistograms == 1) {
384 dimensionality = WorkspaceDimensionality::oneD;
385 }
else if (numberOfHistograms > 1) {
386 dimensionality = WorkspaceDimensionality::twoD;
388 return dimensionality;
393std::string getIntensityUnitLabel(std::string intensityUnitLabel) {
394 if (intensityUnitLabel ==
"I(q) (cm-1)") {
397 return intensityUnitLabel;
409std::string getMomentumTransferLabel(std::string momentumTransferLabel) {
410 if (momentumTransferLabel ==
"Angstrom^-1") {
413 return momentumTransferLabel;
418 const auto unitLabel = dimension->getMDUnits().getUnitLabel();
419 return unitLabel.ascii();
437 const auto &qValue =
workspace->points(0);
438 std::map<std::string, std::string> qAttributes;
439 auto qUnit = getUnitFromMDDimension(
workspace->getDimension(0));
440 qUnit = getMomentumTransferLabel(qUnit);
447 writeArray1DWithStrAttributes(data,
sasDataQ, qValue.rawData(), qAttributes);
452 std::map<std::string, std::string> iAttributes;
453 auto iUnit = getIntensityUnit(
workspace);
454 iUnit = getIntensityUnitLabel(iUnit);
459 writeArray1DWithStrAttributes(data,
sasDataI, intensity.rawData(), iAttributes);
463 const auto &intensityUncertainty =
workspace->e(0);
464 std::map<std::string, std::string> eAttributes;
465 eAttributes.insert(std::make_pair(
sasUnitAttr, iUnit));
467 writeArray1DWithStrAttributes(data,
sasDataIdev, intensityUncertainty.rawData(), eAttributes);
472 const auto qResolution =
workspace->pointStandardDeviations(0);
473 std::map<std::string, std::string> xUncertaintyAttributes;
474 xUncertaintyAttributes.emplace(
sasUnitAttr, qUnit);
476 writeArray1DWithStrAttributes(data,
sasDataQdev, qResolution.rawData(), xUncertaintyAttributes);
481 const unsigned indices[] = {0, 1};
482 for (
const auto index : indices) {
484 if (!axis->isNumeric()) {
491class SpectrumAxisValueProvider {
494 setSpectrumAxisValues();
498 auto isPointData = m_workspace->getNumberHistograms() == m_spectrumAxisValues.size();
503 value = (m_spectrumAxisValues[
index + 1] + m_spectrumAxisValues[
index]) / 2.0;
507 m_currentAxisValues.swap(tempVec);
508 return m_currentAxisValues.data();
512 void setSpectrumAxisValues() {
513 auto sAxis = m_workspace->getAxis(1);
515 m_spectrumAxisValues.emplace_back((*sAxis)(
index));
527template <
typename T>
class QxExtractor {
530 if (ws->isHistogramData()) {
533 return qxPointData.data();
535 return ws->dataX(
index).data();
539 std::vector<T> qxPointData;
584 std::invalid_argument(
"SaveNXcanSAS: The provided 2D workspace needs "
585 "to have 2 numeric axes.");
597 std::map<std::string, std::string> qxAttributes;
598 auto qxUnit = getUnitFromMDDimension(
workspace->getXDimension());
599 qxUnit = getMomentumTransferLabel(qxUnit);
601 QxExtractor<double> qxExtractor;
605 std::map<std::string, std::string> qyAttributes;
606 auto qyUnit = getUnitFromMDDimension(
workspace->getDimension(1));
607 qyUnit = getMomentumTransferLabel(qyUnit);
610 SpectrumAxisValueProvider spectrumAxisValueProvider(
workspace);
611 write2DWorkspace(data,
workspace,
sasDataQy, spectrumAxisValueProvider, qyAttributes);
614 std::map<std::string, std::string> iAttributes;
615 auto iUnit = getIntensityUnit(
workspace);
616 iUnit = getIntensityUnitLabel(iUnit);
625 std::map<std::string, std::string> eAttributes;
626 eAttributes.insert(std::make_pair(
sasUnitAttr, iUnit));
636 auto workspaceDimensionality = getWorkspaceDimensionality(
workspace);
637 switch (workspaceDimensionality) {
638 case (WorkspaceDimensionality::oneD):
641 case (WorkspaceDimensionality::twoD):
645 throw std::runtime_error(
"SaveNXcanSAS: The provided workspace "
646 "dimensionality is not 1D or 2D.");
652 const std::string &transmissionName) {
669 auto date = getDate();
674 const auto transmissionData =
workspace->y(0);
675 std::map<std::string, std::string> transmissionAttributes;
683 transmissionAttributes);
687 const auto &transmissionErrors =
workspace->e(0);
688 std::map<std::string, std::string> transmissionErrorAttributes;
689 transmissionErrorAttributes.emplace(
sasUnitAttr, unit);
692 transmissionErrorAttributes);
697 std::map<std::string, std::string> lambdaAttributes;
698 auto lambdaUnit = getUnitFromMDDimension(
workspace->getDimension(0));
699 if (lambdaUnit.empty() || lambdaUnit ==
"Angstrom") {
716 auto inputWSValidator = std::make_shared<Kernel::CompositeValidator>();
721 "The input workspace, which must be in units of Q");
723 "The name of the .h5 file to save");
725 std::vector<std::string> radiation_source{
"Spallation Neutron Source",
726 "Pulsed Reactor Neutron Source",
727 "Reactor Neutron Source",
728 "Synchrotron X-ray Source",
729 "Pulsed Muon Source",
730 "Rotating Anode X-ray",
737 std::make_shared<Kernel::StringListValidator>(radiation_source),
"The type of radiation used.");
739 "Specify in a comma separated list, which detectors to store "
740 "information about; \nwhere each name must match a name "
741 "given for a detector in the [[IDF|instrument definition "
742 "file (IDF)]]. \nIDFs are located in the instrument "
743 "sub-directory of the MantidPlot install directory.");
747 std::make_shared<API::WorkspaceUnitValidator>(
"Wavelength")),
748 "The transmission workspace. Optional. If given, will be saved at "
749 "TransmissionSpectrum");
753 std::make_shared<API::WorkspaceUnitValidator>(
"Wavelength")),
754 "The transmission workspace of the Can. Optional. If given, will be "
755 "saved at TransmissionSpectrum");
757 declareProperty(
"SampleTransmissionRunNumber",
"",
"The run number for the sample transmission workspace. Optional.");
758 declareProperty(
"SampleDirectRunNumber",
"",
"The run number for the sample direct workspace. Optional.");
759 declareProperty(
"CanScatterRunNumber",
"",
"The run number for the can scatter workspace. Optional.");
760 declareProperty(
"CanDirectRunNumber",
"",
"The run number for the can direct workspace. Optional.");
766 std::map<std::string, std::string> result;
767 if (!
workspace || !std::dynamic_pointer_cast<const Mantid::DataObjects::Workspace2D>(
workspace)) {
768 result.emplace(
"InputWorkspace",
"The InputWorkspace must be a Workspace2D.");
776 if (trans->getNumberHistograms() != 1) {
777 result.emplace(propertyName,
"The input workspaces for transmissions have to be 1D.");
782 checkTransmission(transmission,
"Trasmission");
785 if (transmissionCan) {
786 checkTransmission(transmissionCan,
"TransmissionCan");
803 if (Poco::File(filename).
exists()) {
804 Poco::File(filename).remove();
807 H5::H5File file(filename, H5F_ACC_EXCL);
809 const std::string suffix(
"01");
812 int numberOfSteps = 4;
813 if (transmissionSample) {
817 if (transmissionCan) {
824 progress.report(
"Adding a new entry.");
825 auto sasEntry = addSasEntry(file,
workspace, suffix);
828 progress.report(
"Adding instrument information.");
829 const auto detectors = splitDetectorNames(detectorNames);
830 addInstrument(sasEntry,
workspace, radiationSource, detectors);
833 const auto sampleTransmissionRun =
getPropertyValue(
"SampleTransmissionRunNumber");
839 progress.report(
"Adding process information.");
840 if (transmissionCan) {
841 addProcess(sasEntry,
workspace, transmissionCan);
846 if (transmissionCan || transmissionSample) {
847 createNote(sasEntry);
850 if (transmissionSample)
856 if (transmissionSample) {
857 progress.report(
"Adding sample transmission information.");
862 if (transmissionCan) {
863 progress.report(
"Adding can transmission information.");
880 bool isStrict =
false;
881 auto emptyCapitalizationStrategy = [](std::string &) {};
882 return makeCompliantName(input, isStrict, emptyCapitalizationStrategy);
#define DECLARE_ALGORITHM(classname)
const std::vector< double > * lambda
double value
The value of the point.
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.
void progress(double p, const std::string &msg="", double estimatedTime=0.0, int progressPrecision=0)
Sends ProgressNotification.
A validator which provides a TENTATIVE check that a workspace contains common bins in each spectrum.
@ Save
to specify a file to write to, the file may or may not exist
static std::string getInstrumentFilename(const std::string &instrumentName, const std::string &date="")
Get the IDF using the instrument name and date.
bool hasProperty(const std::string &name) const
Does the property exist on the object.
Kernel::Property * getProperty(const std::string &name) const
Returns the named property as a pointer.
Helper class for reporting progress from algorithms.
This class stores information regarding an experimental run as a series of log entries.
A property class for workspaces.
A validator which checks that the unit of the workspace referred to by a WorkspaceProperty is the exp...
SaveNXcanSAS : Saves a reduced workspace in the NXcanSAS format.
void init() override
Initialisation code.
void exec() override
Execution code.
std::map< std::string, std::string > validateInputs() override
Perform validation of ALL the input properties of the algorithm.
SaveNXcanSAS()
Constructor.
static const char * version()
The full version number.
std::shared_ptr< const MatrixWorkspace > MatrixWorkspace_const_sptr
shared pointer to the matrix workspace base class (const version)
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
MANTID_DATAHANDLING_DLL H5::Group createGroupCanSAS(H5::Group &group, const std::string &name, const std::string &nxtype, const std::string &cstype)
void writeNumAttribute(LocationType &location, const std::string &name, const NumT &value)
void writeArray1D(H5::Group &group, const std::string &name, const std::vector< NumT > &values)
MANTID_DATAHANDLING_DLL void write(H5::Group &group, const std::string &name, const std::string &value)
void writeStrAttribute(LocationType &location, 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)
const std::string sasInstrumentIDF
const std::string sasTransmissionSpectrumNameAttr
const std::string sasProcessDate
const std::string sasDataIdev
const std::string sasProcessGroupName
const std::string sasInstrumentName
const std::string sasDataQx
const std::string sasEntryRun
const std::string sasTransmissionSpectrumNameCanAttrValue
const std::string nxEntryClassAttr
const std::string sasInstrumentClassAttr
const std::string sasEntryVersionAttr
const std::string sasSignal
const std::string nxProcessClassAttr
const std::string sasProcessTermUserFile
const std::string sasDataGroupName
const std::string sasInstrumentSourceRadiation
const std::string sasDataQUncertaintiesAttr
const std::string sasTransmissionSpectrumTdev
const std::string sasDataIAxesAttr
const std::string sasTransmissionSpectrumT
const std::string sasDataQy
const std::string nxNoteClassAttr
const std::string nxTransmissionSpectrumClassAttr
const std::string sasDataQIndicesAttr
const std::string sasProcessTermCanDirect
const std::string sasEntryClassAttr
const std::string sasTransmissionSpectrumTIndices
const std::string sasDataQdev
const std::string sasUncertaintyAttr
Standards state that "uncertainties" should be used, however different facilities interpret the stand...
const std::string sasMomentumTransfer
const std::string sasInstrumentDetectorSdd
const std::string sasTransmissionSpectrumTUncertainties
const std::string sasNone
const std::string sasProcessTermSampleDirect
const std::string sasProcessNameValue
const std::string sasTransmissionSpectrumLambda
const std::string sasDataQ
const std::string sasNoteClassAttr
const std::string sasDataClassAttr
const std::string sasDataI
const std::string sasProcessTermCan
const std::string sasInstrumentDetectorClassAttr
const std::string sasInstrumentSourceGroupName
const std::string sasNoteGroupName
const std::string sasUncertaintiesAttr
const std::string sasEntryDefinition
const std::string nxInstrumentSourceClassAttr
const std::string sasProcessTermCanScatter
const std::string sasUnitAttr
const std::string nxDataClassAttr
const std::string sasTransmissionSpectrumNameSampleAttrValue
const std::string sasInstrumentDetectorSddUnitAttrValue
const std::string sasProcessName
const std::string sasDataQUncertaintyAttr
const std::string sasProcessUserFileInLogs
const std::string sasTransmissionSpectrumTUncertainty
const std::string sasIntensity
const std::string sasInstrumentSourceClassAttr
const std::string nxInstrumentDetectorClassAttr
const std::string sasEntryVersionAttrValue
const std::string sasEntryDefinitionFormat
const std::string sasDataIUncertaintiesAttr
const std::string sasProcessTermSvn
const std::string sasProcessBatchFileInLogs
const std::string sasSeparator
const std::string sasTransmissionSpectrumTimeStampAttr
const std::string sasAngstrom
const std::string nxInstrumentClassAttr
const std::string sasInstrumentDetectorName
const std::string sasTransmissionSpectrumGroupName
const std::string sasEntryGroupName
const std::string sasProcessClassAttr
const std::string sasProcessTermBatchFile
const std::string sasInstrumentDetectorGroupName
const std::string sasEntryTitle
const std::string sasTransmissionSpectrumClassAttr
const std::string sasDataIUncertaintyAttr
const std::string sasProcessTermSampleTrans
const std::string sasInstrumentGroupName
bool exists(::NeXus::File &file, const std::string &name)
Based on the current group in the file, does the named sub-entry exist?
std::string MANTID_DATAHANDLING_DLL makeCanSASRelaxedName(const std::string &input)
This makes out of an input a relaxed name, something conforming to "[A-Za-z_][\w_]*" For now "-" is c...
std::shared_ptr< const IMDDimension > IMDDimension_const_sptr
Shared Pointer to const IMDDimension.
void MANTID_KERNEL_DLL convertToBinCentre(const std::vector< double > &bin_edges, std::vector< double > &bin_centres)
Convert an array of bin boundaries to bin center values.
std::vector< double > MantidVec
typedef for the data storage used in Mantid matrix workspaces
std::string to_string(const wide_integer< Bits, Signed > &n)
@ Input
An input workspace.