Mantid
Loading...
Searching...
No Matches
LoadEventAsWorkspace2D.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2024 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#include "MantidAPI/Axis.h"
12#include "MantidAPI/Run.h"
13#include "MantidAPI/Sample.h"
24#include <regex>
25
26namespace Mantid {
27namespace DataHandling {
28using namespace API;
30using Mantid::HistogramData::HistogramX;
36// using Mantid::Kernel::TimeROI;
37// Register the algorithm into the AlgorithmFactory
38DECLARE_ALGORITHM(LoadEventAsWorkspace2D)
39
40//----------------------------------------------------------------------------------------------
41
42
43const std::string LoadEventAsWorkspace2D::name() const { return "LoadEventAsWorkspace2D"; }
44
46int LoadEventAsWorkspace2D::version() const { return 1; }
47
49const std::string LoadEventAsWorkspace2D::category() const { return "DataHandling\\Nexus"; }
50
52const std::string LoadEventAsWorkspace2D::summary() const {
53 return "Load event data, integrating the events during loading. Also set the X-axis based on log data.";
54}
55
56//----------------------------------------------------------------------------------------------
60 const std::vector<std::string> exts{".nxs.h5", ".nxs", "_event.nxs"};
61 this->declareProperty(std::make_unique<FileProperty>("Filename", "", FileProperty::Load, exts),
62 "The name of the Event NeXus file to read, including its full or "
63 "relative path. ");
64 declareProperty(std::make_unique<PropertyWithValue<double>>("FilterByTofMin", EMPTY_DBL(), Direction::Input),
65 "To exclude events that do not fall within a range "
66 "of times-of-flight. "
67 "This is the minimum accepted value in microseconds. Keep "
68 "blank to load all events.");
69 declareProperty(std::make_unique<PropertyWithValue<double>>("FilterByTofMax", EMPTY_DBL(), Direction::Input),
70 "To exclude events that do not fall within a range "
71 "of times-of-flight. "
72 "This is the maximum accepted value in microseconds. Keep "
73 "blank to load all events.");
75 std::make_unique<PropertyWithValue<double>>("FilterByTimeStart", EMPTY_DBL(), Direction::Input),
76 "Optional: To only include events after the provided start "
77 "time, in seconds (relative to the start of the run). If left empty, it will take start time as default.");
78 declareProperty(std::make_unique<PropertyWithValue<double>>("FilterByTimeStop", EMPTY_DBL(), Direction::Input),
79 "Optional: To only include events before the provided stop "
80 "time, in seconds (relative to the start of the run). if left empty, it will take run end time or "
81 "last pulse time as default.");
82 declareProperty(std::make_unique<PropertyWithValue<std::vector<std::string>>>(
83 "LogAllowList", std::vector<std::string>(), Direction::Input),
84 "If specified, only these logs will be loaded from the file (each "
85 "separated by a space).");
86 declareProperty(std::make_unique<PropertyWithValue<std::vector<std::string>>>(
87 "LogBlockList", std::vector<std::string>(), Direction::Input),
88 "If specified, these logs will NOT be loaded from the file (each "
89 "separated by a space).");
90 declareProperty(std::make_unique<PropertyWithValue<std::string>>("XCenterLog", "wavelength", Direction::Input),
91 "Name of log to take to use as the X-bin center");
92 declareProperty(std::make_unique<PropertyWithValue<std::string>>("XWidthLog", "wavelength_spread", Direction::Input),
93 "Name of log to take to use as the X-bin width");
95 "Value to set X-bin center to which overrides XCenterLog");
97 "Value to set X-bin width to which overrides XWidthLog");
98 declareProperty("Units", "Wavelength",
99 std::make_shared<StringListValidator>(UnitFactory::Instance().getConvertibleUnits()),
100 "The name of the units to convert to (must be one of those registered in the Unit Factory)");
101 declareProperty(std::make_unique<WorkspaceProperty<Workspace2D>>("OutputWorkspace", "", Direction::Output),
102 "An output workspace.");
103 declareProperty("LoadNexusInstrumentXML", true,
104 "If true, load the instrument definition file (IDF) from the input NeXus file. "
105 "If false, Mantid will load the most appropriate IDF from the instrument repository.");
106}
107
108std::map<std::string, std::string> LoadEventAsWorkspace2D::validateInputs() {
109 std::map<std::string, std::string> results;
110
111 const std::vector<std::string> allow_list = getProperty("LogAllowList");
112 const std::vector<std::string> block_list = getProperty("LogBlockList");
113
114 if (!allow_list.empty() && !block_list.empty()) {
115 results["LogAllowList"] =
116 "LogBlockList and LogAllowList are mutually exclusive. Please only enter values for one of these fields.";
117 results["LogBlockList"] =
118 "LogBlockList and LogAllowList are mutually exclusive. Please only enter values for one of these fields.";
119 }
120
121 const double tofMin = getProperty("FilterByTofMin");
122 const double tofMax = getProperty("FilterByTofMax");
123
124 if (tofMin != EMPTY_DBL() && tofMax != EMPTY_DBL()) {
125 if (tofMin == EMPTY_DBL() || tofMax == EMPTY_DBL()) {
126 results["FilterByTofMin"] = "You must specify both min & max or neither TOF filters";
127 results["FilterByTofMax"] = "You must specify both min & max or neither TOF filters";
128 } else if (tofMin >= tofMax) {
129 results["FilterByTofMin"] = "FilterByTofMin must be less than FilterByTofMax";
130 results["FilterByTofMax"] = "FilterByTofMax must be greater than FilterByTofMin";
131 }
132 }
133
134 return results;
135}
136
137//----------------------------------------------------------------------------------------------
141 std::string filename = getPropertyValue("Filename");
142
143 auto prog = std::make_unique<Progress>(this, 0.0, 1.0, 6);
144
145 // temporary workspace to load instrument and metadata
146 MatrixWorkspace_sptr WS = WorkspaceFactory::Instance().create("Workspace2D", 1, 1, 1);
147 // Load the logs
148 prog->doReport("Loading logs");
149 int nPeriods = 1; // Unused
150 auto periodLog = std::make_unique<const TimeSeriesProperty<int>>("period_log"); // Unused
151 LoadEventNexus::runLoadNexusLogs<MatrixWorkspace_sptr>(filename, WS, *this, false, nPeriods, periodLog,
152 getProperty("LogAllowList"), getProperty("LogBlockList"));
153
154 if (nPeriods != 1)
155 g_log.warning("This algorithm does not correctly handle period data");
156
157 // set center and width parameters, do it before we try to load the data so if the log doesn't exist we fail fast
158 double center = getProperty("XCenter");
159 if (center == EMPTY_DBL())
160 center = WS->run().getStatistics(getPropertyValue("XCenterLog")).mean;
161
162 double width = getProperty("XWidth");
163 if (width == EMPTY_DBL())
164 width = WS->run().getStatistics(getPropertyValue("XWidthLog")).mean;
165 width *= center;
166
167 if (width == 0.) {
168 std::string errmsg(
169 "Width was calculated to be 0 (XCenter*XWidth). This will result in a invalid bin with zero width");
170 g_log.error(errmsg);
171 throw std::runtime_error(errmsg);
172 }
173
174 const Nexus::NexusDescriptor descriptor(filename);
175
176 // Load the instrument
177 prog->doReport("Loading instrument");
178 LoadEventNexus::loadInstrument<MatrixWorkspace_sptr>(filename, WS, "entry", this, &descriptor);
179
180 // load run metadata
181 prog->doReport("Loading metadata");
182 try {
183 LoadEventNexus::loadEntryMetadata(filename, WS, "entry");
184 } catch (std::exception &e) {
185 g_log.warning() << "Error while loading meta data: " << e.what() << '\n';
186 }
187
188 // create IndexInfo
189 prog->doReport("Creating IndexInfo");
190 const std::vector<int32_t> range;
191 LoadEventNexusIndexSetup indexSetup(WS, EMPTY_INT(), EMPTY_INT(), range);
192 auto indexInfo = indexSetup.makeIndexInfo();
193 const size_t numHist = indexInfo.size();
194
195 // make output workspace with correct number of histograms
196 MatrixWorkspace_sptr outWS = WorkspaceFactory::Instance().create(WS, numHist, 2, 1);
197 // set spectrum index information
198 outWS->setIndexInfo(indexInfo);
199
200 // now load the data
201 const auto id_to_wi = outWS->getDetectorIDToWorkspaceIndexMap();
202 detid_t min_detid = std::numeric_limits<detid_t>::max();
203 detid_t max_detid = std::numeric_limits<detid_t>::min();
204
205 for (const auto &entry : id_to_wi) {
206 min_detid = std::min(min_detid, entry.first);
207 max_detid = std::max(max_detid, entry.first);
208 }
209
210 const double tof_min = getProperty("FilterByTofMin");
211 const double tof_max = getProperty("FilterByTofMax");
212 const bool tof_filtering = (tof_min != EMPTY_DBL() && tof_max != EMPTY_DBL());
213
214 double filter_time_start_sec, filter_time_stop_sec;
215 filter_time_start_sec = getProperty("FilterByTimeStart");
216 filter_time_stop_sec = getProperty("FilterByTimeStop");
217
218 // get the start time
219 if (!WS->run().hasProperty("start_time")) {
220 g_log.warning("This NXS file does not have a start time, first pulse time is used instead.");
221 }
222
223 Types::Core::DateAndTime runstart(WS->run().hasProperty("start_time") ? WS->run().getProperty("start_time")->value()
224 : WS->run().getFirstPulseTime());
225
226 // get end time, to account for some older DAS issue where last pulse time can be slightly greater than end time, use
227 // last pulse time in this case and check if WS has "proton_charge" property otherwise last pulse time doesn't exist.
228 Types::Core::DateAndTime endtime;
229 if (WS->run().hasProperty("proton_charge") &&
230 WS->run().getLastPulseTime() > WS->run().getProperty("end_time")->value()) {
231 endtime = WS->run().getLastPulseTime();
232 g_log.warning("Last pulse time is used because the last pulse time is greater than the end time!");
233 } else {
234 endtime = WS->run().getProperty("end_time")->value();
235 }
236
237 Types::Core::DateAndTime filter_time_start;
238 Types::Core::DateAndTime filter_time_stop;
239
240 // vector to stored to integrated counts by detector ID
241 std::vector<uint32_t> Y(max_detid - min_detid + 1, 0);
242
243 Nexus::File h5file(filename);
244
245 h5file.openAddress("/");
246 h5file.openGroup("entry", "NXentry");
247
248 // Now we want to go through all the bankN_event entrie
249 prog->doReport("Reading and integrating data");
250
251 std::set<std::string> const classEntries = h5file.getEntriesByClass("NXevent_data");
252 if (!classEntries.empty()) {
253 const std::regex classRegex("(/entry/)([^/]*)");
254 std::smatch groups;
255 for (const std::string &classEntry : classEntries) {
256
257 if (std::regex_match(classEntry, groups, classRegex)) {
258 const std::string entry_name(groups[2].str());
259 // skip entries with junk data
260 if (entry_name == "bank_error_events" || entry_name == "bank_unmapped_events")
261 continue;
262 g_log.debug() << "Loading bank " << entry_name << '\n';
263 h5file.openGroup(entry_name, "NXevent_data");
264
265 std::vector<uint32_t> event_ids;
266
267 if (descriptor.isEntry("/entry/" + entry_name + "/event_id", "SDS"))
268 event_ids = Nexus::IOHelper::readNexusVector<uint32_t>(h5file, "event_id");
269 else
270 event_ids = Nexus::IOHelper::readNexusVector<uint32_t>(h5file, "event_pixel_id");
271
272 // closeGroup and skip this bank if there is no event
273 if (event_ids.size() <= 1) {
274 h5file.closeGroup();
275 continue;
276 }
277
278 // Load "h5file" into BankPulseTimes using a shared ptr
279 const auto bankPulseTimes = std::make_shared<BankPulseTimes>(boost::ref(h5file), std::vector<int>());
280
281 // Get event_index from the "h5file"
282 const auto event_index =
283 std::make_shared<std::vector<uint64_t>>(Nexus::IOHelper::readNexusVector<uint64_t>(h5file, "event_index"));
284
285 // if "filterTimeStart" is empty, use run start time as default
286 if (filter_time_start_sec != EMPTY_DBL()) {
287 filter_time_start = runstart + filter_time_start_sec;
288 } else {
289 filter_time_start = runstart;
290 }
291
292 // if "filterTimeStop" is empty, use end time as default
293 if (filter_time_stop_sec != EMPTY_DBL()) {
294 filter_time_stop = runstart + filter_time_stop_sec;
295 } else {
296 filter_time_stop = endtime;
297 }
298
299 // Use filter_time_start time as starting reference in time and create a TimeROI using bankPulseTimes
300 const auto TimeROI = bankPulseTimes->getPulseIndices(filter_time_start, filter_time_stop);
301
302 // set up PulseIndexer and give previous TimeROI to pulseIndexer
303 const PulseIndexer pulseIndexer(event_index, event_index->at(0), event_ids.size(), entry_name, TimeROI);
304
305 std::vector<float> event_times;
306 if (tof_filtering) {
307 if (descriptor.isEntry("/entry/" + entry_name + "/event_time_offset", "SDS"))
308 event_times = Nexus::IOHelper::readNexusVector<float>(h5file, "event_time_offset");
309 else
310 event_times = Nexus::IOHelper::readNexusVector<float>(h5file, "event_time_of_flight");
311 }
312
313 // Nested loop to loop through all the relavent pulses and relavent event_ids.
314 // For someone new to this, every pulse creates an entry in event_index, event_index.size() = # of pulses,
315 // the value of event_index[i] points to the index of event_ids. In short, event_ids[event_index[i]] is the
316 // detector id from the i-th pulse. See NXevent_data description for more details.
317 for (const auto &pulse : pulseIndexer) {
318 for (size_t i = pulse.eventIndexStart; i < pulse.eventIndexStop; i++) {
319 // for (size_t i = 0; i < event_ids.size(); i++) {
320 if (tof_filtering) {
321 const auto tof = event_times[i];
322 if (tof < tof_min || tof > tof_max)
323 continue;
324 }
325 const detid_t det_id = event_ids[i];
326 if (det_id < min_detid || det_id > max_detid)
327 continue;
328 Y[det_id - min_detid]++;
329 }
330 }
331 h5file.closeGroup();
332 }
333 }
334 }
335
336 h5file.closeGroup();
337 h5file.close();
338
339 // determine x values
340 const auto xBins = {center - width / 2, center + width / 2};
341
342 prog->doReport("Setting data to workspace");
343 // set the data on the workspace
344 const auto histX = Mantid::Kernel::make_cow<HistogramX>(xBins);
345 for (detid_t detid = min_detid; detid <= max_detid; detid++) {
346 const auto id_wi = id_to_wi.find(detid);
347 if (id_wi == id_to_wi.end())
348 continue;
349
350 const auto wi = id_wi->second;
351 const auto y = Y[detid - min_detid];
352
353 outWS->mutableY(wi) = y;
354 outWS->mutableE(wi) = sqrt(y);
355 outWS->setSharedX(wi, histX);
356 }
357
358 // set units
359 outWS->getAxis(0)->setUnit(getPropertyValue("Units"));
360 outWS->setYUnit("Counts");
361
362 // add filename
363 outWS->mutableRun().addProperty("Filename", filename);
364 setProperty("OutputWorkspace", outWS);
365}
366
367} // namespace DataHandling
368} // namespace Mantid
std::string name
Definition Run.cpp:60
#define DECLARE_ALGORITHM(classname)
Definition Algorithm.h:538
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.
LoadEventAsWorkspace2D : Load event data, integrating the events during loading.
int version() const override
Algorithm's version for identification.
const std::string category() const override
Algorithm's category for identification.
std::map< std::string, std::string > validateInputs() override
Perform validation of ALL the input properties of the algorithm.
void init() override
Initialize the algorithms properties.
const std::string summary() const override
Algorithm's summary for use in the GUI and help.
Helper for LoadEventNexus dealing with setting up indices (spectrum numbers an detector ID mapping) f...
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.
PulseIndexer contains information for mapping from pulse index/number to event index.
Concrete workspace implementation.
Definition Workspace2D.h:29
IPropertyManager * setProperty(const std::string &name, const T &value)
Templated method to set the value of a PropertyWithValue.
ListValidator is a validator that requires the value of a property to be one of a defined list of pos...
void debug(const std::string &msg)
Logs at debug level.
Definition Logger.cpp:145
void error(const std::string &msg)
Logs at error level.
Definition Logger.cpp:108
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
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...
TimeROI : Object that holds information about when the time measurement was active.
Definition TimeROI.h:18
A specialised Property class for holding a series of time-value pairs.
bool isEntry(const std::string &entryName, const std::string &groupClass) const noexcept
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
SingletonHolder< UnitFactoryImpl > UnitFactory
Definition UnitFactory.h:79
Helper class which provides the Collimation Length for SANS instruments.
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.
constexpr double EMPTY_DBL() noexcept
Returns what we consider an "empty" double within a property.
Definition EmptyValues.h:42
STL namespace.
Describes the direction (within an algorithm) of a Property.
Definition Property.h:50
@ Input
An input workspace.
Definition Property.h:53
@ Output
An output workspace.
Definition Property.h:54