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 "MantidNexus/NexusFile.h"
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
62namespace {
63// constants for the strings to look for
64const std::string ENTRY_NAME("/entry");
65const std::string ENTRY_STATE_NAME("/entry-state0");
66const std::string NXENTRY("NXentry");
67const std::string NXEVENT_DATA("NXevent_data");
68const std::string NX_DATA("NXdata");
69} // namespace
70
78 int confidence(0);
79 if (descriptor.isEntry(ENTRY_NAME, NXENTRY) || descriptor.isEntry(ENTRY_STATE_NAME, NXENTRY)) {
80 const bool hasEventData = descriptor.classTypeExistsChild(ENTRY_NAME, NXEVENT_DATA) ||
81 descriptor.classTypeExistsChild(ENTRY_STATE_NAME, NXEVENT_DATA);
82 const bool hasData = descriptor.classTypeExistsChild(ENTRY_NAME, NX_DATA) ||
83 descriptor.classTypeExistsChild(ENTRY_STATE_NAME, NX_DATA);
84
85 if (hasData && hasEventData)
86 // Event data = this is event NXS
87 confidence = 20;
88 else if (hasData && !hasEventData)
89 // No event data = this is the one
90 confidence = 80;
91 else
92 // No data ?
93 confidence = 10;
94 }
95 return confidence;
96}
97
105void LoadTOFRawNexus::countPixels(const std::string &nexusfilename, const std::string &entry_name,
106 std::vector<std::string> &bankNames) {
107 m_numPixels = 0;
108 m_numBins = 0;
109 m_dataField = "";
110 m_axisField = "";
111 bankNames.clear();
112
113 // Create the root Nexus class
114 auto file = Nexus::File(nexusfilename);
115
116 // Open the default data group 'entry'
117 file.openGroup(entry_name, "NXentry");
118 // Also pop into the instrument
119 file.openGroup("instrument", "NXinstrument");
120
121 // Look for all the banks
122 std::map<std::string, std::string> entries = file.getEntries();
123 std::map<std::string, std::string>::iterator it;
124 for (it = entries.begin(); it != entries.end(); ++it) {
125 std::string entryName = it->first;
126 if (entryName.size() > 4) {
127 if (entryName.substr(0, 4) == "bank") {
128 // OK, this is some bank data
129 file.openGroup(entryName, it->second);
130
131 // -------------- Find the data field name ----------------------------
132 if (m_dataField.empty()) {
133 std::map<std::string, std::string> dataEntries = file.getEntries();
134 for (auto dataEntryIt = dataEntries.begin(); dataEntryIt != dataEntries.end(); ++dataEntryIt) {
135 if (dataEntryIt->second == "SDS") {
136 file.openData(dataEntryIt->first);
137 if (file.hasAttr("signal")) {
138 int signal = 0;
139 file.getAttr("signal", signal);
140 if (signal == m_signalNo) {
141 // That's the right signal!
142 m_dataField = dataEntryIt->first;
143 // Find the corresponding X axis
144 std::string axes;
145 m_assumeOldFile = false;
146 if (!file.hasAttr("axes")) {
147 if (1 != m_signalNo) {
148 throw std::runtime_error("Your chosen signal number, " + Strings::toString(m_signalNo) +
149 ", corresponds to the data field '" + m_dataField +
150 "' has no 'axes' attribute specifying.");
151 } else {
152 m_assumeOldFile = true;
153 axes = "x_pixel_offset,y_pixel_offset,time_of_flight";
154 }
155 }
156
157 if (!m_assumeOldFile) {
158 file.getAttr("axes", axes);
159 }
160
161 std::vector<std::string> allAxes;
162 boost::split(allAxes, axes, boost::algorithm::detail::is_any_ofF<char>(","));
163 if (allAxes.size() != 3)
164 throw std::runtime_error("Your chosen signal number, " + Strings::toString(m_signalNo) +
165 ", corresponds to the data field '" + m_dataField + "' which has only " +
166 Strings::toString(allAxes.size()) + " dimension. Expected 3 dimensions.");
167
168 m_axisField = allAxes.back();
169 g_log.information() << "Loading signal " << m_signalNo << ", " << m_dataField << " with axis "
170 << m_axisField << '\n';
171 file.closeData();
172 break;
173 } // Data has a 'signal' attribute
174 } // Yes, it is a data field
175 file.closeData();
176 } // each entry in the group
177 }
178 }
179 file.closeGroup();
180 } // bankX name
181 }
182 } // each entry
183
184 if (m_dataField.empty())
185 throw std::runtime_error("Your chosen signal number, " + Strings::toString(m_signalNo) +
186 ", was not found in any of the data fields of any "
187 "'bankX' group. Cannot load file.");
188
189 for (it = entries.begin(); it != entries.end(); ++it) {
190 std::string entryName = it->first;
191 if (entryName.size() > 4) {
192 if (entryName.substr(0, 4) == "bank") {
193 // OK, this is some bank data
194 file.openGroup(entryName, it->second);
195 const auto bankEntries = file.getEntries();
196
197 if (bankEntries.find("pixel_id") != bankEntries.end()) {
198 bankNames.emplace_back(entryName);
199
200 // Count how many pixels in the bank
201 file.openData("pixel_id");
202 Nexus::DimVector const dims = file.getInfo().dims;
203 file.closeData();
204
205 if (!dims.empty()) {
206 const size_t newPixels = std::accumulate(dims.cbegin(), dims.cend(), static_cast<size_t>(1),
207 [](size_t product, auto dim) { return product * dim; });
208 m_numPixels += newPixels;
209 }
210 } else {
211 bankNames.emplace_back(entryName);
212
213 // Get the number of pixels from the offsets arrays
214 file.openData("x_pixel_offset");
215 Nexus::DimVector const xdim = file.getInfo().dims;
216 file.closeData();
217
218 file.openData("y_pixel_offset");
219 Nexus::DimVector const ydim = file.getInfo().dims;
220 file.closeData();
221
222 if (!xdim.empty() && !ydim.empty()) {
223 m_numPixels += (xdim[0] * ydim[0]);
224 }
225 }
226
227 if (bankEntries.find(m_axisField) != bankEntries.end()) {
228 // Get the size of the X vector
229 file.openData(m_axisField);
230 Nexus::DimVector const dims = file.getInfo().dims;
231 // Find the units, if available
232 if (file.hasAttr("units"))
233 file.getAttr("units", m_xUnits);
234 else
235 m_xUnits = "microsecond"; // use default
236 file.closeData();
237 if (!dims.empty())
238 m_numBins = dims[0] - 1;
239 }
240
241 file.closeGroup();
242 } // bankX name
243 }
244 } // each entry
245 file.close();
246}
247
248/*for (std::vector<uint32_t>::iterator it = pixel_id.begin(); it !=
249pixel_id.end();)
250{
251 detid_t pixelID = *it;
252 specnum_t wi = static_cast<specnum_t>((*id_to_wi)[pixelID]);
253 // spectrum is just wi+1
254 if (wi+1 < m_spec_min || wi+1 > m_spec_max) pixel_id.erase(it);
255 else ++it;
256}*/
257// Function object for remove_if STL algorithm
258namespace {
259// Check the numbers supplied are not in the range and erase the ones that are
260struct range_check {
261 range_check(specnum_t min, specnum_t max, detid2index_map id_to_wi)
262 : m_min(min), m_max(max), m_id_to_wi(std::move(id_to_wi)) {}
263
264 bool operator()(specnum_t x) {
265 auto wi = static_cast<specnum_t>((m_id_to_wi)[x]);
266 return (wi + 1 < m_min || wi + 1 > m_max);
267 }
268
269private:
273};
274} // namespace
275
284void LoadTOFRawNexus::loadBank(const std::string &nexusfilename, const std::string &entry_name,
285 const std::string &bankName, const API::MatrixWorkspace_sptr &WS,
286 const detid2index_map &id_to_wi) {
287 g_log.debug() << "Loading bank " << bankName << '\n';
288 // To avoid segfaults on RHEL5/6 and Fedora
289 m_fileMutex.lock();
290
291 // Navigate to the point in the file
292 auto file = Nexus::File(nexusfilename);
293 file.openGroup(entry_name, "NXentry");
294 file.openGroup("instrument", "NXinstrument");
295 file.openGroup(bankName, "NXdetector");
296
297 m_numPixels = 0;
298 std::vector<uint32_t> pixel_id;
299
300 if (!m_assumeOldFile) {
301 // Load the pixel IDs
302 file.readData("pixel_id", pixel_id);
303 m_numPixels = pixel_id.size();
304 if (m_numPixels == 0) {
305 file.close();
306 m_fileMutex.unlock();
307 g_log.warning() << "Invalid pixel_id data in " << bankName << '\n';
308 return;
309 }
310 } else {
311 // Load the x and y pixel offsets
312 std::vector<float> xoffsets;
313 std::vector<float> yoffsets;
314 file.readData("x_pixel_offset", xoffsets);
315 file.readData("y_pixel_offset", yoffsets);
316
317 m_numPixels = xoffsets.size() * yoffsets.size();
318 if (0 == m_numPixels) {
319 file.close();
320 m_fileMutex.unlock();
321 g_log.warning() << "Invalid (x,y) offsets in " << bankName << '\n';
322 return;
323 }
324
325 size_t bankNum = 0;
326 if (bankName.size() > 4) {
327 if (bankName.substr(0, 4) == "bank") {
328 bankNum = boost::lexical_cast<size_t>(bankName.substr(4));
329 bankNum--;
330 } else {
331 file.close();
332 m_fileMutex.unlock();
333 g_log.warning() << "Invalid bank number for " << bankName << '\n';
334 return;
335 }
336 }
337
338 // All good, so construct the pixel ID listing
339 size_t numX = xoffsets.size();
340 size_t numY = yoffsets.size();
341
342 for (size_t i = 0; i < numX; i++) {
343 for (size_t j = 0; j < numY; j++) {
344 pixel_id.emplace_back(static_cast<uint32_t>(j + numY * (i + numX * bankNum)));
345 }
346 }
347 }
348
349 size_t iPart = 0;
350 if (m_spec_max != Mantid::EMPTY_INT()) {
351 uint32_t ifirst = pixel_id[0];
352 range_check out_range(m_spec_min, m_spec_max, id_to_wi);
353 auto newEnd = std::remove_if(pixel_id.begin(), pixel_id.end(), out_range);
354 pixel_id.erase(newEnd, pixel_id.end());
355 // check if beginning or end of array was erased
356 if (ifirst != pixel_id[0])
357 iPart = m_numPixels - pixel_id.size();
358 m_numPixels = pixel_id.size();
359 if (m_numPixels == 0) {
360 file.close();
361 m_fileMutex.unlock();
362 g_log.warning() << "No pixels from " << bankName << '\n';
363 return;
364 };
365 }
366 // Load the TOF vector
367 std::vector<float> tof;
368 file.readData(m_axisField, tof);
369 m_numBins = tof.size() - 1;
370 if (tof.size() <= 1) {
371 file.close();
372 m_fileMutex.unlock();
373 g_log.warning() << "Invalid " << m_axisField << " data in " << bankName << '\n';
374 return;
375 }
376
377 BinEdges X(tof.begin(), tof.end());
378
379 // Load the data. Coerce ints into double.
380 std::string errorsField;
381 std::vector<double> data;
382 file.openData(m_dataField);
383 file.getDataCoerce(data);
384 if (file.hasAttr("errors"))
385 file.getAttr("errors", errorsField);
386 file.closeData();
387
388 // Load the errors
389 bool hasErrors = !errorsField.empty();
390 std::vector<double> errors;
391 if (hasErrors) {
392 try {
393 file.openData(errorsField);
394 file.getDataCoerce(errors);
395 file.closeData();
396 } catch (...) {
397 g_log.information() << "Error loading the errors field, '" << errorsField << "' for bank " << bankName
398 << ". Will use sqrt(counts). \n";
399 hasErrors = false;
400 }
401 }
402
403 // Have all the data I need
404 m_fileMutex.unlock();
405 file.close();
406
407 for (size_t i = iPart; i < iPart + m_numPixels; i++) {
408 // Find the workspace index for this detector
409 detid_t pixelID = pixel_id[i - iPart];
410 size_t wi = id_to_wi.find(pixelID)->second;
411
412 // Set the basic info of that spectrum
413 auto &spec = WS->getSpectrum(wi);
414 spec.setSpectrumNo(specnum_t(wi + 1));
415 spec.setDetectorID(pixel_id[i - iPart]);
416 auto from = data.begin() + i * m_numBins;
417 auto to = from + m_numBins;
418
419 if (hasErrors) {
420 auto eFrom = errors.begin() + i * m_numBins;
421 auto eTo = eFrom + m_numBins;
422 spec.setHistogram(X, Counts(from, to), CountStandardDeviations(eFrom, eTo));
423 } else {
424 spec.setHistogram(X, Counts(from, to));
425 }
426 }
427
428 // Done!
429}
430
432std::string LoadTOFRawNexus::getEntryName(const std::string &filename) {
433 std::string entry_name = "entry";
434 auto file = Nexus::File(filename);
435 std::map<std::string, std::string> entries = file.getEntries();
436 file.close();
437
438 if (entries.empty())
439 throw std::runtime_error("No entries in the NXS file!");
440
441 // name "entry" is normal, but "entry-state0" is the name of the real state
442 // for live nexus files.
443 if (entries.find(entry_name) == entries.end())
444 entry_name = "entry-state0";
445 // If that doesn't exist, just take the first entry.
446 if (entries.find(entry_name) == entries.end())
447 entry_name = entries.begin()->first;
448 // // Tell the user
449 // if (entries.size() > 1)
450 // g_log.notice() << "There are " << entries.size() << " NXentry's in the
451 // file. Loading entry '" << entry_name << "' only.\n";
452
453 return entry_name;
454}
455
464 // The input properties
465 std::string filename = getPropertyValue("Filename");
466 m_signalNo = getProperty("Signal");
467 m_spec_min = getProperty("SpectrumMin");
468 m_spec_max = getProperty("SpectrumMax");
469
470 // Find the entry name we want.
471 std::string entry_name = LoadTOFRawNexus::getEntryName(filename);
472
473 // Count pixels and other setup
474 auto prog = std::make_unique<Progress>(this, 0.0, 1.0, 10);
475 prog->doReport("Counting pixels");
476 std::vector<std::string> bankNames;
477 countPixels(filename, entry_name, bankNames);
478 g_log.debug() << "Workspace found to have " << m_numPixels << " pixels and " << m_numBins << " bins\n";
479
480 prog->setNumSteps(bankNames.size() + 5);
481
482 prog->doReport("Creating workspace");
483 // Start with a dummy WS just to hold the logs and load the instrument
485
486 // Load the logs
487 prog->doReport("Loading DAS logs");
488 g_log.debug() << "Loading DAS logs\n";
489
490 int nPeriods = 1; // Unused
491 auto periodLog = std::make_unique<const TimeSeriesProperty<int>>("period_log"); // Unused
492 LoadEventNexus::runLoadNexusLogs<MatrixWorkspace_sptr>(filename, WS, *this, false, nPeriods, periodLog);
493
494 // Load the instrument
495 prog->report("Loading instrument");
496 g_log.debug() << "Loading instrument\n";
497 LoadEventNexus::runLoadInstrument<MatrixWorkspace_sptr>(filename, WS, entry_name, this);
498
499 // Load the meta data, but don't stop on errors
500 prog->report("Loading metadata");
501 g_log.debug() << "Loading metadata\n";
502
503 try {
504 LoadEventNexus::loadEntryMetadata(filename, WS, entry_name);
505 } catch (std::exception &e) {
506 g_log.warning() << "Error while loading meta data: " << e.what() << '\n';
507 }
508
509 // Set the spectrum number/detector ID at each spectrum. This is consistent
510 // with LoadEventNexus for non-ISIS files.
511 prog->report("Building Spectra Mapping");
512 g_log.debug() << "Building Spectra Mapping\n";
513 WS->rebuildSpectraMapping(false);
514 // And map ID to WI
515 g_log.debug() << "Mapping ID to WI\n";
516 const auto id_to_wi = WS->getDetectorIDToWorkspaceIndexMap();
517
518 // Load each bank sequentially
519 for (const auto &bankName : bankNames) {
520 prog->report("Loading bank " + bankName);
521 g_log.debug() << "Loading bank " << bankName << '\n';
522 loadBank(filename, entry_name, bankName, WS, id_to_wi);
523 }
524
525 // Set some units
526 if (m_xUnits == "Ang")
527 WS->getAxis(0)->setUnit("dSpacing");
528 else if (m_xUnits == "invAng")
529 WS->getAxis(0)->setUnit("MomentumTransfer");
530 else
531 // Default to TOF for any other string
532 WS->getAxis(0)->setUnit("TOF");
533 WS->setYUnit("Counts");
534
535 // Set to the output
536 setProperty("OutputWorkspace", WS);
537}
538
539} // namespace Mantid::DataHandling
specnum_t m_min
detid2index_map m_id_to_wi
specnum_t m_max
#define DECLARE_NEXUS_LAZY_FILELOADER_ALGORITHM(classname)
DECLARE_NEXUS_LAZY_FILELOADER_ALGORITHM should be used in place of the standard DECLARE_ALGORITHM mac...
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.
Kernel::Logger & g_log
Definition Algorithm.h:422
@ Load
allowed here which will be passed to the algorithm
A property class for workspaces.
static void loadEntryMetadata(const std::string &nexusfilename, T WS, const std::string &entry_name)
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;.
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.
int confidence(Nexus::NexusDescriptorLazy &descriptor) const override
Returns a confidence value that this algorithm can load a file.
specnum_t m_spec_min
Interval of chunk.
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:145
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
void information(const std::string &msg)
Logs at information level.
Definition Logger.cpp:136
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...
bool classTypeExistsChild(const std::string &parentPath, const std::string &classType) const
Query if a given type exists as a decendant of the supplied parentPath.
bool isEntry(std::string const &entryName, std::string const &groupClass) const
Checks if a full-address entry exists for a particular groupClass in a Nexus dataset.
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:734
std::vector< dimsize_t > DimVector
constexpr int EMPTY_INT() noexcept
Returns what we consider an "empty" integer within a property.
Definition EmptyValues.h:24
int32_t detid_t
Typedef for a detector ID.
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:14
STL namespace.
@ Output
An output workspace.
Definition Property.h:54