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 // compute time filter bounds
241 if (filter_time_start_sec != EMPTY_DBL()) {
242 filter_time_start = runstart + filter_time_start_sec;
243 } else {
244 filter_time_start = runstart;
245 }
246 if (filter_time_stop_sec != EMPTY_DBL()) {
247 filter_time_stop = runstart + filter_time_stop_sec;
248 } else {
249 filter_time_stop = endtime;
250 }
251
252 // vector to stored to integrated counts by detector ID
253 std::vector<uint32_t> Y(max_detid - min_detid + 1, 0);
254
255 Nexus::File h5file(filename);
256
257 h5file.openAddress("/");
258 h5file.openGroup("entry", "NXentry");
259
260 // Now we want to go through all the bankN_event entries
261 prog->doReport("Reading and integrating data");
262
263 std::set<std::string> const classEntries = h5file.getEntriesByClass("NXevent_data");
264 if (!classEntries.empty()) {
265 const std::regex classRegex("(/entry/)([^/]*)");
266 std::smatch groups;
267 for (const std::string &classEntry : classEntries) {
268
269 if (std::regex_match(classEntry, groups, classRegex)) {
270 const std::string entry_name(groups[2].str());
271 // skip entries with junk data
272 if (entry_name == "bank_error_events" || entry_name == "bank_unmapped_events")
273 continue;
274 g_log.debug() << "Loading bank " << entry_name << '\n';
275 h5file.openGroup(entry_name, "NXevent_data");
276
277 std::vector<uint32_t> event_ids;
278
279 if (descriptor.isEntry("/entry/" + entry_name + "/event_id", "SDS"))
280 event_ids = Nexus::IOHelper::readNexusVector<uint32_t>(h5file, "event_id");
281 else
282 event_ids = Nexus::IOHelper::readNexusVector<uint32_t>(h5file, "event_pixel_id");
283
284 // closeGroup and skip this bank if there is no event
285 if (event_ids.size() <= 1) {
286 h5file.closeGroup();
287 continue;
288 }
289
290 // Load "h5file" into BankPulseTimes using a shared ptr
291 const auto bankPulseTimes = std::make_shared<BankPulseTimes>(boost::ref(h5file), std::vector<int>());
292
293 // Get event_index from the "h5file"
294 const auto event_index =
295 std::make_shared<std::vector<uint64_t>>(Nexus::IOHelper::readNexusVector<uint64_t>(h5file, "event_index"));
296
297 // Use filter_time_start time as starting reference in time and create a pulseROI using bankPulseTimes
298 const auto pulseROI = bankPulseTimes->getPulseIndices(filter_time_start, filter_time_stop);
299
300 // set up PulseIndexer and give previous pulseROI to pulseIndexer
301 const PulseIndexer pulseIndexer(event_index, event_index->at(0), event_ids.size(), entry_name, pulseROI);
302
303 std::vector<float> event_times;
304 if (tof_filtering) {
305 if (descriptor.isEntry("/entry/" + entry_name + "/event_time_offset", "SDS"))
306 event_times = Nexus::IOHelper::readNexusVector<float>(h5file, "event_time_offset");
307 else
308 event_times = Nexus::IOHelper::readNexusVector<float>(h5file, "event_time_of_flight");
309 }
310
311 // Nested loop to loop through all the relevant pulses and relevant event_ids.
312 // For someone new to this, every pulse creates an entry in event_index, event_index.size() = # of pulses,
313 // the value of event_index[i] points to the index of event_ids. In short, event_ids[event_index[i]] is the
314 // detector id from the i-th pulse. See NXevent_data description for more details.
315 for (const auto &pulse : pulseIndexer) {
316 for (size_t i = pulse.eventIndexStart; i < pulse.eventIndexStop; i++) {
317 // for (size_t i = 0; i < event_ids.size(); i++) {
318 if (tof_filtering) {
319 const auto tof = event_times[i];
320 if (tof < tof_min || tof > tof_max)
321 continue;
322 }
323 const detid_t det_id = event_ids[i];
324 if (det_id < min_detid || det_id > max_detid)
325 continue;
326 Y[det_id - min_detid]++;
327 }
328 }
329 h5file.closeGroup();
330 }
331 }
332 }
333
334 h5file.closeGroup();
335 h5file.close();
336
337 // filter the logs the same way FilterByTime does
338 const bool is_time_filtered = (filter_time_start_sec != EMPTY_DBL() || filter_time_stop_sec != EMPTY_DBL());
339 if (is_time_filtered) {
340 Kernel::TimeROI timeroi(filter_time_start, filter_time_stop);
341 outWS->mutableRun().setTimeROI(timeroi);
342 outWS->mutableRun().removeDataOutsideTimeROI();
343 }
344
345 // determine x values
346 const auto xBins = {center - width / 2, center + width / 2};
347
348 prog->doReport("Setting data to workspace");
349 // set the data on the workspace
350 const auto histX = Mantid::Kernel::make_cow<HistogramX>(xBins);
351 for (detid_t detid = min_detid; detid <= max_detid; detid++) {
352 const auto id_wi = id_to_wi.find(detid);
353 if (id_wi == id_to_wi.end())
354 continue;
355
356 const auto wi = id_wi->second;
357 const auto y = Y[detid - min_detid];
358
359 outWS->mutableY(wi) = y;
360 outWS->mutableE(wi) = sqrt(y);
361 outWS->setSharedX(wi, histX);
362 }
363
364 // set units
365 outWS->getAxis(0)->setUnit(getPropertyValue("Units"));
366 outWS->setYUnit("Counts");
367
368 // add filename
369 outWS->mutableRun().addProperty("Filename", filename);
370 setProperty("OutputWorkspace", outWS);
371}
372
373} // namespace DataHandling
374} // 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