Skip to content

Commit c3ac465

Browse files
authored
Merge pull request mantidproject#36671 from rosswhitfield/new_hfir_event_loader
New algorithm LoadEventAsWorkspace2D
2 parents c4a30d6 + dba36d8 commit c3ac465

File tree

8 files changed

+577
-1
lines changed

8 files changed

+577
-1
lines changed

Framework/DataHandling/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ set(SRC_FILES
5252
src/LoadDspacemap.cpp
5353
src/LoadEMU.cpp
5454
src/LoadEmptyInstrument.cpp
55+
src/LoadEventAsWorkspace2D.cpp
5556
src/LoadEventNexus.cpp
5657
src/LoadEventNexusIndexSetup.cpp
5758
src/LoadEventPreNexus2.cpp
@@ -271,6 +272,7 @@ set(INC_FILES
271272
inc/MantidDataHandling/LoadDspacemap.h
272273
inc/MantidDataHandling/LoadEMU.h
273274
inc/MantidDataHandling/LoadEmptyInstrument.h
275+
inc/MantidDataHandling/LoadEventAsWorkspace2D.h
274276
inc/MantidDataHandling/LoadEventNexus.h
275277
inc/MantidDataHandling/LoadEventNexusIndexSetup.h
276278
inc/MantidDataHandling/LoadEventPreNexus2.h
@@ -483,6 +485,7 @@ set(TEST_FILES
483485
LoadDspacemapTest.h
484486
LoadEMUauTest.h
485487
LoadEmptyInstrumentTest.h
488+
LoadEventAsWorkspace2DTest.h
486489
LoadEventNexusIndexSetupTest.h
487490
LoadEventNexusTest.h
488491
LoadEventPreNexus2Test.h
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
#pragma once
8+
9+
#include "MantidAPI/Algorithm.h"
10+
#include "MantidAPI/SpectraDetectorTypes.h"
11+
#include "MantidDataHandling/DllConfig.h"
12+
13+
namespace Mantid {
14+
namespace DataHandling {
15+
16+
/** LoadEventAsWorkspace2D : Load event data, integrating the events during loading
17+
*/
18+
class MANTID_DATAHANDLING_DLL LoadEventAsWorkspace2D : public API::Algorithm {
19+
public:
20+
const std::string name() const override;
21+
int version() const override;
22+
const std::string category() const override;
23+
const std::string summary() const override;
24+
const std::vector<std::string> seeAlso() const override { return {"LoadEventNexus", "HFIRSANS2Wavelength"}; }
25+
26+
private:
27+
void init() override;
28+
void exec() override;
29+
std::map<std::string, std::string> validateInputs() override;
30+
};
31+
32+
} // namespace DataHandling
33+
} // namespace Mantid
Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
// Mantid Repository : https://github.com/mantidproject/mantid
2+
//
3+
// Copyright &copy; 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+
8+
#include "MantidDataHandling/LoadEventAsWorkspace2D.h"
9+
#include "MantidAPI/Axis.h"
10+
#include "MantidAPI/FileProperty.h"
11+
#include "MantidAPI/MatrixWorkspace.h"
12+
#include "MantidAPI/Run.h"
13+
#include "MantidAPI/Sample.h"
14+
#include "MantidAPI/WorkspaceFactory.h"
15+
#include "MantidDataHandling/LoadEventNexus.h"
16+
#include "MantidDataHandling/LoadEventNexusIndexSetup.h"
17+
#include "MantidDataObjects/Workspace2D.h"
18+
#include "MantidKernel/ListValidator.h"
19+
#include "MantidKernel/TimeSeriesProperty.h"
20+
#include "MantidKernel/UnitFactory.h"
21+
#include "MantidNexus/NexusIOHelper.h"
22+
23+
#include <regex>
24+
25+
namespace Mantid {
26+
namespace DataHandling {
27+
using namespace API;
28+
using Mantid::DataObjects::Workspace2D;
29+
using Mantid::HistogramData::HistogramX;
30+
using Mantid::Kernel::Direction;
31+
using Mantid::Kernel::PropertyWithValue;
32+
using Mantid::Kernel::StringListValidator;
33+
using Mantid::Kernel::TimeSeriesProperty;
34+
using Mantid::Kernel::UnitFactory;
35+
// Register the algorithm into the AlgorithmFactory
36+
DECLARE_ALGORITHM(LoadEventAsWorkspace2D)
37+
38+
//----------------------------------------------------------------------------------------------
39+
40+
/// Algorithms name for identification. @see Algorithm::name
41+
const std::string LoadEventAsWorkspace2D::name() const { return "LoadEventAsWorkspace2D"; }
42+
43+
/// Algorithm's version for identification. @see Algorithm::version
44+
int LoadEventAsWorkspace2D::version() const { return 1; }
45+
46+
/// Algorithm's category for identification. @see Algorithm::category
47+
const std::string LoadEventAsWorkspace2D::category() const { return "DataHandling\\Nexus"; }
48+
49+
/// Algorithm's summary for use in the GUI and help. @see Algorithm::summary
50+
const std::string LoadEventAsWorkspace2D::summary() const {
51+
return "Load event data, integrating the events during loading. Also set the X-axis based on log data.";
52+
}
53+
54+
//----------------------------------------------------------------------------------------------
55+
/** Initialize the algorithms properties.
56+
*/
57+
void LoadEventAsWorkspace2D::init() {
58+
const std::vector<std::string> exts{".nxs.h5", ".nxs", "_event.nxs"};
59+
this->declareProperty(std::make_unique<FileProperty>("Filename", "", FileProperty::Load, exts),
60+
"The name of the Event NeXus file to read, including its full or "
61+
"relative path. ");
62+
declareProperty(std::make_unique<PropertyWithValue<double>>("FilterByTofMin", EMPTY_DBL(), Direction::Input),
63+
"To exclude events that do not fall within a range "
64+
"of times-of-flight. "
65+
"This is the minimum accepted value in microseconds. Keep "
66+
"blank to load all events.");
67+
declareProperty(std::make_unique<PropertyWithValue<double>>("FilterByTofMax", EMPTY_DBL(), Direction::Input),
68+
"To exclude events that do not fall within a range "
69+
"of times-of-flight. "
70+
"This is the maximum accepted value in microseconds. Keep "
71+
"blank to load all events.");
72+
declareProperty(std::make_unique<PropertyWithValue<std::vector<std::string>>>(
73+
"LogAllowList", std::vector<std::string>(), Direction::Input),
74+
"If specified, only these logs will be loaded from the file (each "
75+
"separated by a space).");
76+
declareProperty(std::make_unique<PropertyWithValue<std::vector<std::string>>>(
77+
"LogBlockList", std::vector<std::string>(), Direction::Input),
78+
"If specified, these logs will NOT be loaded from the file (each "
79+
"separated by a space).");
80+
declareProperty(std::make_unique<PropertyWithValue<std::string>>("XCenterLog", "wavelength", Direction::Input),
81+
"Name of log to take to use as the X-bin center");
82+
declareProperty(std::make_unique<PropertyWithValue<std::string>>("XWidthLog", "wavelength_spread", Direction::Input),
83+
"Name of log to take to use as the X-bin width");
84+
declareProperty(std::make_unique<PropertyWithValue<double>>("XCenter", EMPTY_DBL(), Direction::Input),
85+
"Value to set X-bin center to which overrides XCenterLog");
86+
declareProperty(std::make_unique<PropertyWithValue<double>>("XWidth", EMPTY_DBL(), Direction::Input),
87+
"Value to set X-bin width to which overrides XWidthLog");
88+
declareProperty("Units", "Wavelength",
89+
std::make_shared<StringListValidator>(UnitFactory::Instance().getConvertibleUnits()),
90+
"The name of the units to convert to (must be one of those registered in the Unit Factory)");
91+
declareProperty(std::make_unique<WorkspaceProperty<Workspace2D>>("OutputWorkspace", "", Direction::Output),
92+
"An output workspace.");
93+
}
94+
95+
std::map<std::string, std::string> LoadEventAsWorkspace2D::validateInputs() {
96+
std::map<std::string, std::string> results;
97+
98+
const std::vector<std::string> allow_list = getProperty("LogAllowList");
99+
const std::vector<std::string> block_list = getProperty("LogBlockList");
100+
101+
if (!allow_list.empty() && !block_list.empty()) {
102+
results["LogAllowList"] =
103+
"LogBlockList and LogAllowList are mutually exclusive. Please only enter values for one of these fields.";
104+
results["LogBlockList"] =
105+
"LogBlockList and LogAllowList are mutually exclusive. Please only enter values for one of these fields.";
106+
}
107+
108+
const double tofMin = getProperty("FilterByTofMin");
109+
const double tofMax = getProperty("FilterByTofMax");
110+
111+
if (tofMin != EMPTY_DBL() && tofMax != EMPTY_DBL()) {
112+
if (tofMin == EMPTY_DBL() || tofMax == EMPTY_DBL()) {
113+
results["FilterByTofMin"] = "You must specify both min & max or neither TOF filters";
114+
results["FilterByTofMax"] = "You must specify both min & max or neither TOF filters";
115+
} else if (tofMin >= tofMax) {
116+
results["FilterByTofMin"] = "FilterByTofMin must be less than FilterByTofMax";
117+
results["FilterByTofMax"] = "FilterByTofMax must be greater than FilterByTofMin";
118+
}
119+
}
120+
121+
return results;
122+
}
123+
//----------------------------------------------------------------------------------------------
124+
/** Execute the algorithm.
125+
*/
126+
void LoadEventAsWorkspace2D::exec() {
127+
std::string filename = getPropertyValue("Filename");
128+
129+
auto prog = std::make_unique<Progress>(this, 0.0, 1.0, 6);
130+
131+
// temporary workspace to load instrument and metadata
132+
MatrixWorkspace_sptr WS = WorkspaceFactory::Instance().create("Workspace2D", 1, 1, 1);
133+
// Load the logs
134+
prog->doReport("Loading logs");
135+
int nPeriods = 1; // Unused
136+
auto periodLog = std::make_unique<const TimeSeriesProperty<int>>("period_log"); // Unused
137+
LoadEventNexus::runLoadNexusLogs<MatrixWorkspace_sptr>(filename, WS, *this, false, nPeriods, periodLog,
138+
getProperty("LogAllowList"), getProperty("LogBlockList"));
139+
140+
if (nPeriods != 1)
141+
g_log.warning("This algorithm does not correctly handle period data");
142+
143+
// set center and width parameters, do it before we try to load the data so if the log doesn't exist we fail fast
144+
double center = getProperty("XCenter");
145+
if (center == EMPTY_DBL())
146+
center = WS->run().getStatistics(getPropertyValue("XCenterLog")).mean;
147+
148+
double width = getProperty("XWidth");
149+
if (width == EMPTY_DBL())
150+
width = WS->run().getStatistics(getPropertyValue("XWidthLog")).mean;
151+
width *= center;
152+
153+
if (width == 0.) {
154+
std::string errmsg(
155+
"Width was calculated to be 0 (XCenter*XWidth). This will result in a invalid bin with zero width");
156+
g_log.error(errmsg);
157+
throw std::runtime_error(errmsg);
158+
}
159+
160+
const Kernel::NexusHDF5Descriptor descriptor(filename);
161+
162+
// Load the instrument
163+
prog->doReport("Loading instrument");
164+
LoadEventNexus::loadInstrument<MatrixWorkspace_sptr>(filename, WS, "entry", this, &descriptor);
165+
166+
// load run metadata
167+
prog->doReport("Loading metadata");
168+
try {
169+
LoadEventNexus::loadEntryMetadata(filename, WS, "entry", descriptor);
170+
} catch (std::exception &e) {
171+
g_log.warning() << "Error while loading meta data: " << e.what() << '\n';
172+
}
173+
174+
// create IndexInfo
175+
prog->doReport("Creating IndexInfo");
176+
const std::vector<int32_t> range;
177+
LoadEventNexusIndexSetup indexSetup(WS, EMPTY_INT(), EMPTY_INT(), range);
178+
auto indexInfo = indexSetup.makeIndexInfo();
179+
const size_t numHist = indexInfo.size();
180+
181+
// make output workspace with correct number of histograms
182+
MatrixWorkspace_sptr outWS = WorkspaceFactory::Instance().create(WS, numHist, 2, 1);
183+
// set spectrum index information
184+
outWS->setIndexInfo(indexInfo);
185+
186+
// now load the data
187+
const auto id_to_wi = outWS->getDetectorIDToWorkspaceIndexMap();
188+
detid_t min_detid = std::numeric_limits<detid_t>::max();
189+
detid_t max_detid = std::numeric_limits<detid_t>::min();
190+
191+
for (const auto &entry : id_to_wi) {
192+
min_detid = std::min(min_detid, entry.first);
193+
max_detid = std::max(max_detid, entry.first);
194+
}
195+
196+
const double tof_min = getProperty("FilterByTofMin");
197+
const double tof_max = getProperty("FilterByTofMax");
198+
const bool tof_filtering = (tof_min != EMPTY_DBL() && tof_max != EMPTY_DBL());
199+
200+
// vector to stored to integrated counts by detector ID
201+
std::vector<uint32_t> Y(max_detid - min_detid + 1, 0);
202+
203+
::NeXus::File h5file(filename);
204+
205+
h5file.openPath("/");
206+
h5file.openGroup("entry", "NXentry");
207+
208+
// Now we want to go through all the bankN_event entries
209+
const std::map<std::string, std::set<std::string>> &allEntries = descriptor.getAllEntries();
210+
211+
prog->doReport("Reading and integrating data");
212+
213+
auto itClassEntries = allEntries.find("NXevent_data");
214+
215+
if (itClassEntries != allEntries.end()) {
216+
217+
const std::set<std::string> &classEntries = itClassEntries->second;
218+
const std::regex classRegex("(/entry/)([^/]*)");
219+
std::smatch groups;
220+
221+
for (const std::string &classEntry : classEntries) {
222+
223+
if (std::regex_match(classEntry, groups, classRegex)) {
224+
const std::string entry_name(groups[2].str());
225+
226+
// skip entries with junk data
227+
if (entry_name == "bank_error_events" || entry_name == "bank_unmapped_events")
228+
continue;
229+
230+
g_log.debug() << "Loading bank " << entry_name << '\n';
231+
h5file.openGroup(entry_name, "NXevent_data");
232+
233+
std::vector<uint32_t> event_ids;
234+
235+
if (descriptor.isEntry("/entry/" + entry_name + "/event_id", "SDS"))
236+
event_ids = Mantid::NeXus::NeXusIOHelper::readNexusVector<uint32_t>(h5file, "event_id");
237+
else
238+
event_ids = Mantid::NeXus::NeXusIOHelper::readNexusVector<uint32_t>(h5file, "event_pixel_id");
239+
240+
std::vector<float> event_times;
241+
if (tof_filtering) {
242+
if (descriptor.isEntry("/entry/" + entry_name + "/event_time_offset", "SDS"))
243+
event_times = Mantid::NeXus::NeXusIOHelper::readNexusVector<float>(h5file, "event_time_offset");
244+
else
245+
event_times = Mantid::NeXus::NeXusIOHelper::readNexusVector<float>(h5file, "event_time_of_flight");
246+
}
247+
248+
for (size_t i = 0; i < event_ids.size(); i++) {
249+
if (tof_filtering) {
250+
const auto tof = event_times[i];
251+
if (tof < tof_min || tof > tof_max)
252+
continue;
253+
}
254+
255+
const detid_t det_id = event_ids[i];
256+
if (det_id < min_detid || det_id > max_detid)
257+
continue;
258+
259+
Y[det_id - min_detid]++;
260+
}
261+
262+
h5file.closeGroup();
263+
}
264+
}
265+
}
266+
267+
h5file.closeGroup();
268+
h5file.close();
269+
270+
// determine x values
271+
const auto xBins = {center - width / 2, center + width / 2};
272+
273+
prog->doReport("Setting data to workspace");
274+
// set the data on the workspace
275+
const auto histX = Mantid::Kernel::make_cow<HistogramX>(xBins);
276+
for (detid_t detid = min_detid; detid <= max_detid; detid++) {
277+
const auto id_wi = id_to_wi.find(detid);
278+
if (id_wi == id_to_wi.end())
279+
continue;
280+
281+
const auto wi = id_wi->second;
282+
const auto y = Y[detid - min_detid];
283+
284+
outWS->mutableY(wi) = y;
285+
outWS->mutableE(wi) = sqrt(y);
286+
outWS->setSharedX(wi, histX);
287+
}
288+
289+
// set units
290+
outWS->getAxis(0)->setUnit(getPropertyValue("Units"));
291+
outWS->setYUnit("Counts");
292+
293+
// add filename
294+
outWS->mutableRun().addProperty("Filename", filename);
295+
setProperty("OutputWorkspace", outWS);
296+
}
297+
298+
} // namespace DataHandling
299+
} // namespace Mantid

Framework/DataHandling/src/LoadEventNexus.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -969,7 +969,6 @@ void LoadEventNexus::loadEvents(API::Progress *const prog, const bool monitors)
969969
vector<string> bankNames;
970970
vector<std::size_t> bankNumEvents;
971971
std::string classType = monitors ? "NXmonitor" : "NXevent_data";
972-
::NeXus::Info info;
973972
bool oldNeXusFileNames(false);
974973
bool haveWeights = false;
975974
auto firstPulseT = DateAndTime::maximum();

0 commit comments

Comments
 (0)