Mantid
Loading...
Searching...
No Matches
SaveNXSPE.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 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 +
8
12#include "MantidAPI/Run.h"
15
18
21
22#include <Poco/File.h>
23#include <Poco/Path.h>
24#include <boost/scoped_array.hpp>
25#include <limits>
26#include <nexus/NeXusFile.hpp>
27
28namespace Mantid::DataHandling {
29
30// Register the algorithm into the algorithm factory
31DECLARE_ALGORITHM(SaveNXSPE)
32
33using namespace Kernel;
34using namespace API;
35
36const double SaveNXSPE::MASK_FLAG = std::numeric_limits<double>::quiet_NaN();
37const double SaveNXSPE::MASK_ERROR = 0.0;
38// works fine but there were cases that some compilers crush on this (VS2008 in
39// mixed .net environment ?)
40const std::string SaveNXSPE::NXSPE_VER = "1.2";
41// 4MB chunk size
42const size_t SaveNXSPE::MAX_CHUNK_SIZE = 4194304;
43
45
50 auto wsValidator = std::make_shared<CompositeValidator>();
51 wsValidator->add(std::make_shared<API::WorkspaceUnitValidator>("DeltaE"));
52 wsValidator->add<API::CommonBinsValidator>();
53 wsValidator->add<API::HistogramValidator>();
54
56 std::make_unique<WorkspaceProperty<MatrixWorkspace>>("InputWorkspace", "", Direction::Input, wsValidator),
57 "The name of the workspace to save.");
58
60 std::make_unique<API::FileProperty>("Filename", "", FileProperty::Save, std::vector<std::string>(1, ".nxspe")),
61 "The name of the NXSPE file to write, as a full or relative path");
62
63 declareProperty("Efixed", EMPTY_DBL(), "Value of the fixed energy to write into NXSPE file.");
64 declareProperty("Psi", EMPTY_DBL(), "Value of PSI to write into NXSPE file.");
65 declareProperty("KiOverKfScaling", true, "Flags in the file whether Ki/Kf scaling has been done or not.");
66
67 // optional par or phx file
68 std::vector<std::string> fileExts{".par", ".phx"};
70 std::make_unique<FileProperty>("ParFile", "not_used.par", FileProperty::OptionalLoad, fileExts),
71 "If provided, will replace detectors parameters in resulting nxspe file with the values taken from the file. \n\
72 Should be used only if the parameters, calculated by the [[FindDetectorsPar]] algorithm are not suitable for some reason. \n\
73 See [[FindDetectorsPar]] description for the details.");
74}
75
80 // Retrieve the input workspace
81 MatrixWorkspace_sptr inputWS = getProperty("InputWorkspace");
82
83 // Number of spectra
84 const auto nHist = static_cast<int64_t>(inputWS->getNumberHistograms());
85 // Number of energy bins
86 const auto nBins = static_cast<int64_t>(inputWS->blocksize());
87
88 // Retrieve the filename from the properties
89 std::string filename = getPropertyValue("Filename");
90
91 // Create the file.
92 ::NeXus::File nxFile(filename, NXACC_CREATE5);
93
94 // Make the top level entry (and open it)
95 std::string entryName = getPropertyValue("InputWorkspace");
96 if (entryName.empty()) {
97 entryName = "mantid_workspace";
98 }
99 nxFile.makeGroup(entryName, "NXentry", true);
100
101 // Definition name and version
102 nxFile.writeData("definition", "NXSPE");
103 nxFile.openData("definition");
104 nxFile.putAttr("version", NXSPE_VER);
105 nxFile.closeData();
106
107 // Program name and version
108 nxFile.writeData("program_name", "mantid");
109 nxFile.openData("program_name");
110 nxFile.putAttr("version", Mantid::Kernel::MantidVersion::version());
111 nxFile.closeData();
112
113 // Create NXSPE_info
114 nxFile.makeGroup("NXSPE_info", "NXcollection", true);
115
116 // Get the value out of the property first
117 double efixed = getProperty("Efixed");
118 if (isEmpty(efixed))
120 // Now lets check to see if the workspace knows better.
121 // TODO: Check that this is the way round we want to do it.
122 const API::Run &run = inputWS->run();
123 if (run.hasProperty("Ei")) {
124 efixed = run.getPropertyValueAsType<double>("Ei");
125 }
126 nxFile.writeData("fixed_energy", efixed);
127 nxFile.openData("fixed_energy");
128 nxFile.putAttr("units", "meV");
129 nxFile.closeData();
130
131 double psi = getProperty("Psi");
132 if (isEmpty(psi))
133 psi = MASK_FLAG;
134 nxFile.writeData("psi", psi);
135 nxFile.openData("psi");
136 nxFile.putAttr("units", "degrees");
137 nxFile.closeData();
138
139 bool kikfScaling = getProperty("KiOverKfScaling");
140 if (kikfScaling) {
141 nxFile.writeData("ki_over_kf_scaling", 1);
142 } else {
143 nxFile.writeData("ki_over_kf_scaling", 0);
144 }
145
146 nxFile.closeGroup(); // NXSPE_info
147
148 // NXinstrument
149 nxFile.makeGroup("instrument", "NXinstrument", true);
150 // Write the instrument name
151 nxFile.writeData("name", inputWS->getInstrument()->getName());
152 // and the short name
153 nxFile.openData("name");
154 // TODO: Get the instrument short name
155 nxFile.putAttr("short_name", inputWS->getInstrument()->getName());
156 nxFile.closeData();
157
158 // NXfermi_chopper
159 nxFile.makeGroup("fermi", "NXfermi_chopper", true);
160
161 nxFile.writeData("energy", efixed);
162 nxFile.closeGroup(); // NXfermi_chopper
163
164 nxFile.closeGroup(); // NXinstrument
165
166 // NXsample
167 nxFile.makeGroup("sample", "NXsample", true);
168 // TODO: Write sample info
169 // nxFile.writeData("rotation_angle", 0.0);
170 // nxFile.writeData("seblock", "NONE");
171 // nxFile.writeData("temperature", 300.0);
172
173 nxFile.closeGroup(); // NXsample
174
175 // Make the NXdata group
176 nxFile.makeGroup("data", "NXdata", true);
177
178 // Energy bins
179 // Get the Energy Axis (X) of the first spectra (they are all the same -
180 // checked above)
181 const auto &X = inputWS->x(0);
182 nxFile.writeData("energy", X.rawData());
183 nxFile.openData("energy");
184 nxFile.putAttr("units", "meV");
185 nxFile.closeData();
186
187 // let's create some blank arrays in the nexus file
188 using Dimensions = std::vector<int64_t>;
189 Dimensions arrayDims(2);
190 arrayDims[0] = nHist;
191 arrayDims[1] = nBins;
192 nxFile.makeData("data", ::NeXus::FLOAT64, arrayDims, false);
193 nxFile.makeData("error", ::NeXus::FLOAT64, arrayDims, false);
194
195 // Add the axes attributes to the data
196 nxFile.openData("data");
197 nxFile.putAttr("signal", 1);
198 nxFile.putAttr("axes", "polar:energy");
199 nxFile.closeData();
200
201 // What size slabs are we going to write...
202 // Use an intermediate in-memory buffer to reduce the number
203 // of calls to putslab, i.e disk writes but still write whole rows
204 Dimensions slabStart(2, 0), slabSize(2, 0);
205 auto chunkRows = static_cast<Dimensions::value_type>(MAX_CHUNK_SIZE / 8 / nBins);
206 if (nHist < chunkRows) {
207 chunkRows = nHist;
208 }
209 // slab size for all but final write
210 slabSize[0] = chunkRows;
211 slabSize[1] = nBins;
212
213 // Allocate the temporary buffers for the signal and errors
214 using Buffer = boost::scoped_array<double>;
215 const size_t bufferSize(slabSize[0] * slabSize[1]);
216 Buffer signalBuffer(new double[bufferSize]);
217 Buffer errorBuffer(new double[bufferSize]);
218
219 // Write the data
220 Progress progress(this, 0.0, 1.0, nHist);
221 int64_t bufferCounter(0);
222 const auto &spectrumInfo = inputWS->spectrumInfo();
223 for (int64_t i = 0; i < nHist; ++i) {
224 progress.report();
225
226 double *signalBufferStart = signalBuffer.get() + bufferCounter * nBins;
227 double *errorBufferStart = errorBuffer.get() + bufferCounter * nBins;
228 if (spectrumInfo.hasDetectors(i) && !spectrumInfo.isMonitor(i)) {
229 // a detector but not a monitor
230 if (!spectrumInfo.isMasked(i)) {
231 std::copy(inputWS->y(i).cbegin(), inputWS->y(i).cend(), signalBufferStart);
232 std::copy(inputWS->e(i).cbegin(), inputWS->e(i).cend(), errorBufferStart);
233 } else {
234 std::fill_n(signalBufferStart, nBins, MASK_FLAG);
235 std::fill_n(errorBufferStart, nBins, MASK_ERROR);
236 }
237 } else {
238 // no detector gets zeroes.
239 std::fill_n(signalBufferStart, nBins, 0.0);
240 std::fill_n(errorBufferStart, nBins, 0.0);
241 }
242 ++bufferCounter;
243
244 // Do we need to flush the buffer. Either we have filled the buffer
245 // or we have reached the end of the workspace and not completely filled
246 // the buffer
247 if (bufferCounter == chunkRows || i == nHist - 1) {
248 // techincally only need to update for the very last slab but
249 // this is always correct and avoids an if statement
250 slabSize[0] = bufferCounter;
251 nxFile.openData("data");
252 nxFile.putSlab(signalBuffer.get(), slabStart, slabSize);
253 nxFile.closeData();
254
255 // Open the error
256 nxFile.openData("error");
257 nxFile.putSlab(errorBuffer.get(), slabStart, slabSize);
258 nxFile.closeData();
259
260 // Reset counters for next time
261 slabStart[0] += bufferCounter;
262 bufferCounter = 0;
263 }
264 }
265
266 // execute the algorithm to calculate the detector's parameters;
267 auto spCalcDetPar = createChildAlgorithm("FindDetectorsPar", 0, 1, true, 1);
268
269 spCalcDetPar->initialize();
270 spCalcDetPar->setProperty("InputWorkspace", inputWS);
271 std::string parFileName = this->getPropertyValue("ParFile");
272 if (!(parFileName.empty() || parFileName == "not_used.par")) {
273 spCalcDetPar->setPropertyValue("ParFile", parFileName);
274 }
275 spCalcDetPar->execute();
276
277 //
278 auto *pCalcDetPar = dynamic_cast<FindDetectorsPar *>(spCalcDetPar.get());
279 if (!pCalcDetPar) { // "can not get pointer to FindDetectorsPar algorithm"
280 throw(std::bad_cast());
281 }
282 const std::vector<double> &azimuthal = pCalcDetPar->getAzimuthal();
283 const std::vector<double> &polar = pCalcDetPar->getPolar();
284 const std::vector<double> &azimuthal_width = pCalcDetPar->getAzimWidth();
285 const std::vector<double> &polar_width = pCalcDetPar->getPolarWidth();
286 const std::vector<double> &secondary_flightpath = pCalcDetPar->getFlightPath();
287
288 // Write the Polar (2Theta) angles
289 nxFile.writeData("polar", polar);
290
291 // Write the Azimuthal (phi) angles
292 nxFile.writeData("azimuthal", azimuthal);
293
294 // Now the widths...
295 nxFile.writeData("polar_width", polar_width);
296 nxFile.writeData("azimuthal_width", azimuthal_width);
297
298 // Secondary flight path
299 nxFile.writeData("distance", secondary_flightpath);
300
301 nxFile.closeGroup(); // NXdata
302
303 nxFile.closeGroup(); // Top level NXentry
304}
305
306} // namespace Mantid::DataHandling
#define DECLARE_ALGORITHM(classname)
Definition: Algorithm.h:576
Base class from which all concrete algorithm classes should be derived.
Definition: Algorithm.h:85
void declareProperty(std::unique_ptr< Kernel::Property > p, const std::string &doc="") override
Add a property to the list of managed properties.
Definition: Algorithm.cpp:1913
std::string getPropertyValue(const std::string &name) const override
Get the value of a property as a string.
Definition: Algorithm.cpp:2026
TypedValue getProperty(const std::string &name) const override
Get the value of a property.
Definition: Algorithm.cpp:2076
virtual std::shared_ptr< Algorithm > createChildAlgorithm(const std::string &name, const double startProgress=-1., const double endProgress=-1., const bool enableLogging=true, const int &version=-1)
Create a Child Algorithm.
Definition: Algorithm.cpp:842
void progress(double p, const std::string &msg="", double estimatedTime=0.0, int progressPrecision=0)
Sends ProgressNotification.
Definition: Algorithm.cpp:231
static bool isEmpty(const NumT toCheck)
checks that the value was not set by users, uses the value in empty double/int.
A validator which provides a TENTATIVE check that a workspace contains common bins in each spectrum.
@ OptionalLoad
to specify a file to read but the file doesn't have to exist
Definition: FileProperty.h:53
@ Save
to specify a file to write to, the file may or may not exist
Definition: FileProperty.h:49
A validator which checks that a workspace contains histogram data (the default) or point data as requ...
bool hasProperty(const std::string &name) const
Does the property exist on the object.
Definition: LogManager.cpp:265
HeldType getPropertyValueAsType(const std::string &name) const
Get the value of a property as the given TYPE.
Definition: LogManager.cpp:332
Helper class for reporting progress from algorithms.
Definition: Progress.h:25
This class stores information regarding an experimental run as a series of log entries.
Definition: Run.h:38
A property class for workspaces.
std::vector< double > const & getAzimuthal() const
the accessors, used to return algorithm results when called as Child Algorithm, without setting the p...
static const size_t MAX_CHUNK_SIZE
The size in bytes of a chunk to accumulate to write to the file at once.
Definition: SaveNXSPE.h:73
void init() override
Initialisation code.
Definition: SaveNXSPE.cpp:49
static const std::string NXSPE_VER
file format version
Definition: SaveNXSPE.h:71
void exec() override
Execution code.
Definition: SaveNXSPE.cpp:79
static const double MASK_FLAG
Value for data if pixel is masked.
Definition: SaveNXSPE.h:67
static const double MASK_ERROR
Value for error if pixel is masked.
Definition: SaveNXSPE.h:69
static const char * version()
The full version number.
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
constexpr double EMPTY_DBL() noexcept
Returns what we consider an "empty" double within a property.
Definition: EmptyValues.h:43
@ Input
An input workspace.
Definition: Property.h:53