|
| 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 | + |
| 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 |
0 commit comments