Mantid
Loading...
Searching...
No Matches
LoadTOFRawNexus.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#include "MantidAPI/Axis.h"
12#include "MantidAPI/Run.h"
19#include <nexus/NeXusFile.hpp>
20
21#include <boost/algorithm/string/detail/classification.hpp>
22#include <boost/algorithm/string/split.hpp>
23#include <utility>
24
25namespace Mantid::DataHandling {
26
28
29using namespace Kernel;
30using namespace API;
31using namespace DataObjects;
32using HistogramData::BinEdges;
33using HistogramData::Counts;
34using HistogramData::CountStandardDeviations;
35
37 : m_numPixels(0), m_signalNo(0), pulseTimes(0), m_numBins(0), m_spec_min(0), m_spec_max(0), m_dataField(""),
38 m_axisField(""), m_xUnits(""), m_fileMutex(), m_assumeOldFile(false) {}
39
42 declareProperty(std::make_unique<FileProperty>("Filename", "", FileProperty::Load, ".nxs"),
43 "The name of the NeXus file to load");
44 declareProperty(std::make_unique<WorkspaceProperty<MatrixWorkspace>>("OutputWorkspace", "", Direction::Output),
45 "The name of the Workspace2D to create.");
46 declareProperty("Signal", 1,
47 "Number of the signal to load from the file. Default is 1 = "
48 "time_of_flight.\n"
49 "Some NXS files have multiple data fields giving binning in "
50 "other units (e.g. d-spacing or momentum).\n"
51 "Enter the right signal number for your desired field.");
52 auto mustBePositive = std::make_shared<BoundedValidator<int>>();
53 mustBePositive->setLower(1);
54 declareProperty(std::make_unique<PropertyWithValue<specnum_t>>("SpectrumMin", 1, mustBePositive),
55 "The index number of the first spectrum to read. Only used if\n"
56 "spectrum_max is set.");
57 declareProperty(std::make_unique<PropertyWithValue<specnum_t>>("SpectrumMax", Mantid::EMPTY_INT(), mustBePositive),
58 "The number of the last spectrum to read. Only used if explicitly\n"
59 "set.");
60}
61
69 int confidence(0);
70 if (descriptor.pathOfTypeExists("/entry", "NXentry") || descriptor.pathOfTypeExists("/entry-state0", "NXentry")) {
71 const bool hasEventData = descriptor.classTypeExists("NXevent_data");
72 const bool hasData = descriptor.classTypeExists("NXdata");
73 if (hasData && hasEventData)
74 // Event data = this is event NXS
75 confidence = 20;
76 else if (hasData && !hasEventData)
77 // No event data = this is the one
78 confidence = 80;
79 else
80 // No data ?
81 confidence = 10;
82 }
83 return confidence;
84}
85
93void LoadTOFRawNexus::countPixels(const std::string &nexusfilename, const std::string &entry_name,
94 std::vector<std::string> &bankNames) {
95 m_numPixels = 0;
96 m_numBins = 0;
97 m_dataField = "";
98 m_axisField = "";
99 bankNames.clear();
100
101 // Create the root Nexus class
102 auto file = ::NeXus::File(nexusfilename);
103
104 // Open the default data group 'entry'
105 file.openGroup(entry_name, "NXentry");
106 // Also pop into the instrument
107 file.openGroup("instrument", "NXinstrument");
108
109 // Look for all the banks
110 std::map<std::string, std::string> entries = file.getEntries();
111 std::map<std::string, std::string>::iterator it;
112 for (it = entries.begin(); it != entries.end(); ++it) {
113 std::string name = it->first;
114 if (name.size() > 4) {
115 if (name.substr(0, 4) == "bank") {
116 // OK, this is some bank data
117 file.openGroup(name, it->second);
118
119 // -------------- Find the data field name ----------------------------
120 if (m_dataField.empty()) {
121 std::map<std::string, std::string> dataEntries = file.getEntries();
122 for (auto dataEntryIt = dataEntries.begin(); dataEntryIt != dataEntries.end(); ++dataEntryIt) {
123 if (dataEntryIt->second == "SDS") {
124 file.openData(dataEntryIt->first);
125 if (file.hasAttr("signal")) {
126 int signal = 0;
127 file.getAttr("signal", signal);
128 if (signal == m_signalNo) {
129 // That's the right signal!
130 m_dataField = dataEntryIt->first;
131 // Find the corresponding X axis
132 std::string axes;
133 m_assumeOldFile = false;
134 if (!file.hasAttr("axes")) {
135 if (1 != m_signalNo) {
136 throw std::runtime_error("Your chosen signal number, " + Strings::toString(m_signalNo) +
137 ", corresponds to the data field '" + m_dataField +
138 "' has no 'axes' attribute specifying.");
139 } else {
140 m_assumeOldFile = true;
141 axes = "x_pixel_offset,y_pixel_offset,time_of_flight";
142 }
143 }
144
145 if (!m_assumeOldFile) {
146 file.getAttr("axes", axes);
147 }
148
149 std::vector<std::string> allAxes;
150 boost::split(allAxes, axes, boost::algorithm::detail::is_any_ofF<char>(","));
151 if (allAxes.size() != 3)
152 throw std::runtime_error("Your chosen signal number, " + Strings::toString(m_signalNo) +
153 ", corresponds to the data field '" + m_dataField + "' which has only " +
154 Strings::toString(allAxes.size()) + " dimension. Expected 3 dimensions.");
155
156 m_axisField = allAxes.back();
157 g_log.information() << "Loading signal " << m_signalNo << ", " << m_dataField << " with axis "
158 << m_axisField << '\n';
159 file.closeData();
160 break;
161 } // Data has a 'signal' attribute
162 } // Yes, it is a data field
163 file.closeData();
164 } // each entry in the group
165 }
166 }
167 file.closeGroup();
168 } // bankX name
169 }
170 } // each entry
171
172 if (m_dataField.empty())
173 throw std::runtime_error("Your chosen signal number, " + Strings::toString(m_signalNo) +
174 ", was not found in any of the data fields of any "
175 "'bankX' group. Cannot load file.");
176
177 for (it = entries.begin(); it != entries.end(); ++it) {
178 std::string name = it->first;
179 if (name.size() > 4) {
180 if (name.substr(0, 4) == "bank") {
181 // OK, this is some bank data
182 file.openGroup(name, it->second);
183 const auto bankEntries = file.getEntries();
184
185 if (bankEntries.find("pixel_id") != bankEntries.end()) {
186 bankNames.emplace_back(name);
187
188 // Count how many pixels in the bank
189 file.openData("pixel_id");
190 std::vector<int64_t> dims = file.getInfo().dims;
191 file.closeData();
192
193 if (!dims.empty()) {
194 const size_t newPixels = std::accumulate(dims.cbegin(), dims.cend(), static_cast<size_t>(1),
195 [](size_t product, auto dim) { return product * dim; });
196 m_numPixels += newPixels;
197 }
198 } else {
199 bankNames.emplace_back(name);
200
201 // Get the number of pixels from the offsets arrays
202 file.openData("x_pixel_offset");
203 std::vector<int64_t> xdim = file.getInfo().dims;
204 file.closeData();
205
206 file.openData("y_pixel_offset");
207 std::vector<int64_t> ydim = file.getInfo().dims;
208 file.closeData();
209
210 if (!xdim.empty() && !ydim.empty()) {
211 m_numPixels += (xdim[0] * ydim[0]);
212 }
213 }
214
215 if (bankEntries.find(m_axisField) != bankEntries.end()) {
216 // Get the size of the X vector
217 file.openData(m_axisField);
218 std::vector<int64_t> dims = file.getInfo().dims;
219 // Find the units, if available
220 if (file.hasAttr("units"))
221 file.getAttr("units", m_xUnits);
222 else
223 m_xUnits = "microsecond"; // use default
224 file.closeData();
225 if (!dims.empty())
226 m_numBins = dims[0] - 1;
227 }
228
229 file.closeGroup();
230 } // bankX name
231 }
232 } // each entry
233 file.close();
234}
235
236/*for (std::vector<uint32_t>::iterator it = pixel_id.begin(); it !=
237pixel_id.end();)
238{
239 detid_t pixelID = *it;
240 specnum_t wi = static_cast<specnum_t>((*id_to_wi)[pixelID]);
241 // spectrum is just wi+1
242 if (wi+1 < m_spec_min || wi+1 > m_spec_max) pixel_id.erase(it);
243 else ++it;
244}*/
245// Function object for remove_if STL algorithm
246namespace {
247// Check the numbers supplied are not in the range and erase the ones that are
248struct range_check {
249 range_check(specnum_t min, specnum_t max, detid2index_map id_to_wi)
250 : m_min(min), m_max(max), m_id_to_wi(std::move(id_to_wi)) {}
251
252 bool operator()(specnum_t x) {
253 auto wi = static_cast<specnum_t>((m_id_to_wi)[x]);
254 return (wi + 1 < m_min || wi + 1 > m_max);
255 }
256
257private:
261};
262} // namespace
263
272void LoadTOFRawNexus::loadBank(const std::string &nexusfilename, const std::string &entry_name,
273 const std::string &bankName, const API::MatrixWorkspace_sptr &WS,
274 const detid2index_map &id_to_wi) {
275 g_log.debug() << "Loading bank " << bankName << '\n';
276 // To avoid segfaults on RHEL5/6 and Fedora
277 m_fileMutex.lock();
278
279 // Navigate to the point in the file
280 auto file = ::NeXus::File(nexusfilename);
281 file.openGroup(entry_name, "NXentry");
282 file.openGroup("instrument", "NXinstrument");
283 file.openGroup(bankName, "NXdetector");
284
285 size_t m_numPixels = 0;
286 std::vector<uint32_t> pixel_id;
287
288 if (!m_assumeOldFile) {
289 // Load the pixel IDs
290 file.readData("pixel_id", pixel_id);
291 m_numPixels = pixel_id.size();
292 if (m_numPixels == 0) {
293 file.close();
294 m_fileMutex.unlock();
295 g_log.warning() << "Invalid pixel_id data in " << bankName << '\n';
296 return;
297 }
298 } else {
299 // Load the x and y pixel offsets
300 std::vector<float> xoffsets;
301 std::vector<float> yoffsets;
302 file.readData("x_pixel_offset", xoffsets);
303 file.readData("y_pixel_offset", yoffsets);
304
305 m_numPixels = xoffsets.size() * yoffsets.size();
306 if (0 == m_numPixels) {
307 file.close();
308 m_fileMutex.unlock();
309 g_log.warning() << "Invalid (x,y) offsets in " << bankName << '\n';
310 return;
311 }
312
313 size_t bankNum = 0;
314 if (bankName.size() > 4) {
315 if (bankName.substr(0, 4) == "bank") {
316 bankNum = boost::lexical_cast<size_t>(bankName.substr(4));
317 bankNum--;
318 } else {
319 file.close();
320 m_fileMutex.unlock();
321 g_log.warning() << "Invalid bank number for " << bankName << '\n';
322 return;
323 }
324 }
325
326 // All good, so construct the pixel ID listing
327 size_t numX = xoffsets.size();
328 size_t numY = yoffsets.size();
329
330 for (size_t i = 0; i < numX; i++) {
331 for (size_t j = 0; j < numY; j++) {
332 pixel_id.emplace_back(static_cast<uint32_t>(j + numY * (i + numX * bankNum)));
333 }
334 }
335 }
336
337 size_t iPart = 0;
338 if (m_spec_max != Mantid::EMPTY_INT()) {
339 uint32_t ifirst = pixel_id[0];
340 range_check out_range(m_spec_min, m_spec_max, id_to_wi);
341 auto newEnd = std::remove_if(pixel_id.begin(), pixel_id.end(), out_range);
342 pixel_id.erase(newEnd, pixel_id.end());
343 // check if beginning or end of array was erased
344 if (ifirst != pixel_id[0])
345 iPart = m_numPixels - pixel_id.size();
346 m_numPixels = pixel_id.size();
347 if (m_numPixels == 0) {
348 file.close();
349 m_fileMutex.unlock();
350 g_log.warning() << "No pixels from " << bankName << '\n';
351 return;
352 };
353 }
354 // Load the TOF vector
355 std::vector<float> tof;
356 file.readData(m_axisField, tof);
357 size_t m_numBins = tof.size() - 1;
358 if (tof.size() <= 1) {
359 file.close();
360 m_fileMutex.unlock();
361 g_log.warning() << "Invalid " << m_axisField << " data in " << bankName << '\n';
362 return;
363 }
364
365 BinEdges X(tof.begin(), tof.end());
366
367 // Load the data. Coerce ints into double.
368 std::string errorsField;
369 std::vector<double> data;
370 file.openData(m_dataField);
371 file.getDataCoerce(data);
372 if (file.hasAttr("errors"))
373 file.getAttr("errors", errorsField);
374 file.closeData();
375
376 // Load the errors
377 bool hasErrors = !errorsField.empty();
378 std::vector<double> errors;
379 if (hasErrors) {
380 try {
381 file.openData(errorsField);
382 file.getDataCoerce(errors);
383 file.closeData();
384 } catch (...) {
385 g_log.information() << "Error loading the errors field, '" << errorsField << "' for bank " << bankName
386 << ". Will use sqrt(counts). \n";
387 hasErrors = false;
388 }
389 }
390
391 // Have all the data I need
392 m_fileMutex.unlock();
393 file.close();
394
395 for (size_t i = iPart; i < iPart + m_numPixels; i++) {
396 // Find the workspace index for this detector
397 detid_t pixelID = pixel_id[i - iPart];
398 size_t wi = id_to_wi.find(pixelID)->second;
399
400 // Set the basic info of that spectrum
401 auto &spec = WS->getSpectrum(wi);
402 spec.setSpectrumNo(specnum_t(wi + 1));
403 spec.setDetectorID(pixel_id[i - iPart]);
404 auto from = data.begin() + i * m_numBins;
405 auto to = from + m_numBins;
406
407 if (hasErrors) {
408 auto eFrom = errors.begin() + i * m_numBins;
409 auto eTo = eFrom + m_numBins;
410 spec.setHistogram(X, Counts(from, to), CountStandardDeviations(eFrom, eTo));
411 } else {
412 spec.setHistogram(X, Counts(from, to));
413 }
414 }
415
416 // Done!
417}
418
420std::string LoadTOFRawNexus::getEntryName(const std::string &filename) {
421 std::string entry_name = "entry";
422 auto file = ::NeXus::File(filename);
423 std::map<std::string, std::string> entries = file.getEntries();
424 file.close();
425
426 if (entries.empty())
427 throw std::runtime_error("No entries in the NXS file!");
428
429 // name "entry" is normal, but "entry-state0" is the name of the real state
430 // for live nexus files.
431 if (entries.find(entry_name) == entries.end())
432 entry_name = "entry-state0";
433 // If that doesn't exist, just take the first entry.
434 if (entries.find(entry_name) == entries.end())
435 entry_name = entries.begin()->first;
436 // // Tell the user
437 // if (entries.size() > 1)
438 // g_log.notice() << "There are " << entries.size() << " NXentry's in the
439 // file. Loading entry '" << entry_name << "' only.\n";
440
441 return entry_name;
442}
443
452 // The input properties
453 std::string filename = getPropertyValue("Filename");
454 m_signalNo = getProperty("Signal");
455 m_spec_min = getProperty("SpectrumMin");
456 m_spec_max = getProperty("SpectrumMax");
457
458 // Find the entry name we want.
459 std::string entry_name = LoadTOFRawNexus::getEntryName(filename);
460
461 // Count pixels and other setup
462 auto prog = std::make_unique<Progress>(this, 0.0, 1.0, 10);
463 prog->doReport("Counting pixels");
464 std::vector<std::string> bankNames;
465 countPixels(filename, entry_name, bankNames);
466 g_log.debug() << "Workspace found to have " << m_numPixels << " pixels and " << m_numBins << " bins\n";
467
468 prog->setNumSteps(bankNames.size() + 5);
469
470 prog->doReport("Creating workspace");
471 // Start with a dummy WS just to hold the logs and load the instrument
473
474 // Load the logs
475 prog->doReport("Loading DAS logs");
476 g_log.debug() << "Loading DAS logs\n";
477
478 int nPeriods = 1; // Unused
479 auto periodLog = std::make_unique<const TimeSeriesProperty<int>>("period_log"); // Unused
480 LoadEventNexus::runLoadNexusLogs<MatrixWorkspace_sptr>(filename, WS, *this, false, nPeriods, periodLog);
481
482 // Load the instrument
483 prog->report("Loading instrument");
484 g_log.debug() << "Loading instrument\n";
485 LoadEventNexus::runLoadInstrument<MatrixWorkspace_sptr>(filename, WS, entry_name, this);
486
487 // Load the meta data, but don't stop on errors
488 prog->report("Loading metadata");
489 g_log.debug() << "Loading metadata\n";
490 Kernel::NexusHDF5Descriptor descriptor(filename);
491
492 try {
493 LoadEventNexus::loadEntryMetadata(filename, WS, entry_name, descriptor);
494 } catch (std::exception &e) {
495 g_log.warning() << "Error while loading meta data: " << e.what() << '\n';
496 }
497
498 // Set the spectrum number/detector ID at each spectrum. This is consistent
499 // with LoadEventNexus for non-ISIS files.
500 prog->report("Building Spectra Mapping");
501 g_log.debug() << "Building Spectra Mapping\n";
502 WS->rebuildSpectraMapping(false);
503 // And map ID to WI
504 g_log.debug() << "Mapping ID to WI\n";
505 const auto id_to_wi = WS->getDetectorIDToWorkspaceIndexMap();
506
507 // Load each bank sequentially
508 for (const auto &bankName : bankNames) {
509 prog->report("Loading bank " + bankName);
510 g_log.debug() << "Loading bank " << bankName << '\n';
511 loadBank(filename, entry_name, bankName, WS, id_to_wi);
512 }
513
514 // Set some units
515 if (m_xUnits == "Ang")
516 WS->getAxis(0)->setUnit("dSpacing");
517 else if (m_xUnits == "invAng")
518 WS->getAxis(0)->setUnit("MomentumTransfer");
519 else
520 // Default to TOF for any other string
521 WS->getAxis(0)->setUnit("TOF");
522 WS->setYUnit("Counts");
523
524 // Set to the output
525 setProperty("OutputWorkspace", WS);
526}
527
528} // namespace Mantid::DataHandling
specnum_t m_min
detid2index_map m_id_to_wi
specnum_t m_max
#define DECLARE_NEXUS_FILELOADER_ALGORITHM(classname)
DECLARE_NEXUS_FILELOADER_ALGORITHM should be used in place of the standard DECLARE_ALGORITHM macro wh...
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
@ Load
allowed here which will be passed to the algorithm
Definition: FileProperty.h:52
A property class for workspaces.
static void loadEntryMetadata(const std::string &nexusfilename, T WS, const std::string &entry_name, const Kernel::NexusHDF5Descriptor &descriptor)
Load the run number and other meta data from the given bank.
std::string m_dataField
Name of the 'data' field to load (depending on Signal)
int m_signalNo
Signal # to load. Default 1.
bool m_assumeOldFile
Flag for whether or not to assume the data is old SNS raw files;.
int confidence(Kernel::NexusDescriptor &descriptor) const override
Returns a confidence value that this algorithm can load a file.
static std::string getEntryName(const std::string &filename)
std::mutex m_fileMutex
Mutex to avoid simultaneous file access.
void countPixels(const std::string &nexusfilename, const std::string &entry_name, std::vector< std::string > &bankNames)
Goes thoguh a histogram NXS file and counts the number of pixels.
void exec() override
Executes the algorithm.
const std::string name() const override
Algorithm's name for identification overriding a virtual method.
specnum_t m_spec_min
Interval of chunk.
size_t m_numPixels
Number of pixels.
void loadBank(const std::string &nexusfilename, const std::string &entry_name, const std::string &bankName, const API::MatrixWorkspace_sptr &WS, const detid2index_map &id_to_wi)
Load a single bank into the workspace.
void init() override
Initialisation method.
std::string m_axisField
Name of the 'axis' field to load (depending on Signal)
std::string m_xUnits
Units of the X axis found.
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
void debug(const std::string &msg)
Logs at debug level.
Definition: Logger.cpp:114
void warning(const std::string &msg)
Logs at warning level.
Definition: Logger.cpp:86
void information(const std::string &msg)
Logs at information level.
Definition: Logger.cpp:105
Defines a wrapper around a file whose internal structure can be accessed using the NeXus API.
bool classTypeExists(const std::string &classType) const
Query if a given type exists somewhere in the file.
bool pathOfTypeExists(const std::string &path, const std::string &type) const
Query if a path exists of a given type.
The concrete, templated class for properties.
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
Kernel::Logger g_log("ExperimentInfo")
static logger object
std::shared_ptr< MatrixWorkspace > MatrixWorkspace_sptr
shared pointer to the matrix workspace base class
std::string toString(const T &value)
Convert a number to a string.
Definition: Strings.cpp:703
constexpr int EMPTY_INT() noexcept
Returns what we consider an "empty" integer within a property.
Definition: EmptyValues.h:25
int32_t detid_t
Typedef for a detector ID.
Definition: SpectrumInfo.h:21
std::unordered_map< detid_t, size_t > detid2index_map
Map with key = detector ID, value = workspace index.
int32_t specnum_t
Typedef for a spectrum Number.
Definition: IDTypes.h:16
STL namespace.
@ Output
An output workspace.
Definition: Property.h:54