diff --git a/Framework/DataHandling/CMakeLists.txt b/Framework/DataHandling/CMakeLists.txt index efc18bf54c669823107ad883bf17a3fec42cdc58..b187d2df60b2b3c23f661e523743126df10ab4e4 100644 --- a/Framework/DataHandling/CMakeLists.txt +++ b/Framework/DataHandling/CMakeLists.txt @@ -92,6 +92,7 @@ set(SRC_FILES src/LoadNexusProcessed.cpp src/LoadNexusProcessed2.cpp src/LoadOff.cpp + src/LoadNGEM.cpp src/LoadPDFgetNFile.cpp src/LoadPLN.cpp src/LoadPSIMuonBin.cpp @@ -294,6 +295,7 @@ set(INC_FILES inc/MantidDataHandling/LoadNexusProcessed.h inc/MantidDataHandling/LoadNexusProcessed2.h inc/MantidDataHandling/LoadOff.h + inc/MantidDataHandling/LoadNGEM.h inc/MantidDataHandling/LoadPDFgetNFile.h inc/MantidDataHandling/LoadPLN.h inc/MantidDataHandling/LoadPSIMuonBin.h @@ -484,6 +486,7 @@ set(TEST_FILES LoadNexusProcessed2Test.h LoadNexusProcessedTest.h LoadNexusTest.h + LoadNGEMTest.h LoadPDFgetNFileTest.h LoadPLNTest.h LoadPSIMuonBinTest.h @@ -628,6 +631,8 @@ target_link_libraries(DataHandling ${TCMALLOC_LIBRARIES_LINKTIME} ${MANTIDLIBS} Nexus + HistogramData + DataObjects ${NEXUS_LIBRARIES} ${HDF5_LIBRARIES} ${HDF5_HL_LIBRARIES} diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadNGEM.h b/Framework/DataHandling/inc/MantidDataHandling/LoadNGEM.h new file mode 100644 index 0000000000000000000000000000000000000000..33d17354812f619ce047a0f60ebe2644a6cb0ff5 --- /dev/null +++ b/Framework/DataHandling/inc/MantidDataHandling/LoadNGEM.h @@ -0,0 +1,142 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2019 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + + +#ifndef MANTID_DATAHANDLING_LOADNGEM_H_ +#define MANTID_DATAHANDLING_LOADNGEM_H_ + +#include "MantidAPI/IFileLoader.h" +#include "MantidDataObjects/EventWorkspace.h" + +namespace Mantid { +namespace DataHandling { + +static constexpr uint64_t CONTIN_ID_VALUE = 0x4F; + +/// Generic event to separate bits. +struct GenericEvent { + uint64_t t0id : 24; // T0 ID + uint64_t reserved2 : 32; // Reserved for non-generics + uint64_t contin : 8; // 0x4F Continuation Code + uint64_t reserved1 : 56; // Reserved for non-generics + uint64_t id : 8; // 0x4E Event ID +}; + +/// Indicate time 0, the start of a new frame. +struct T0FrameEvent { + uint64_t t0id : 24; // T0 ID + uint64_t eventCount : 32; // Event Count + uint64_t contin : 8; // 0x4F Continuation Code + uint64_t totalLoss : 24; // Total loss count + uint64_t eventLoss : 20; // Event loss count + uint64_t frameLoss : 12; // Frame loss count + uint64_t id : 8; // 0x4E Event ID + static constexpr int T0_IDENTIFIER = 0x4E; + bool check() const { + return id == T0_IDENTIFIER && contin == CONTIN_ID_VALUE; + } +}; + +/// A detected neutron. +struct CoincidenceEvent { + uint64_t t0id : 24; // T0 ID + uint64_t clusterTimeY : 10; // Integrated time of the cluster on the Y side + // (5ns pixel) + uint64_t timeDiffY : 6; // Time lag from first to last detection on Y (5ns) + uint64_t clusterTimeX : 10; // Integrated time of the cluster on the X side + // (5ns pixel) + uint64_t timeDiffX : 6; // Time lag from first to last detection on X (5ns) + uint64_t contin : 8; // 0x4F Continuation Code + uint64_t lastY : 7; // Y position of pixel detected last + uint64_t firstY : 7; // Y position of pixel detected first + uint64_t lastX : 7; // X position of pixel detected last + uint64_t firstX : 7; // X position of pixel detected first + uint64_t timeOfFlight : 28; // Difference between T0 and detection (1ns) + uint64_t id : 8; // 0x47 Event ID. + + uint64_t avgX() const { return (firstX + lastX) / 2; } + uint64_t avgY() const { return (firstY + lastY) / 2; } + static constexpr int COINCIDENCE_IDENTIFIER = 0x47; + bool check() { + return id == COINCIDENCE_IDENTIFIER && contin == CONTIN_ID_VALUE; + } + uint64_t getPixel() const { + return avgX() + (avgY() << 7); // Increase Y significance by 7 bits to + // account for 128x128 grid. + } +}; + +/// Holds the 128 bit words from the detector. +struct DetectorWord { + uint64_t words[2]; // Array holding the word from the detector split in two. +}; + +/// Is able to hold all versions of the data words in the same memory location. +union EventUnion { + GenericEvent generic; + T0FrameEvent tZero; + CoincidenceEvent coincidence; + DetectorWord splitWord; +}; + +class DLLExport LoadNGEM : public API::IFileLoader<Kernel::FileDescriptor> { +public: + /// Algorithm's name for identification. + const std::string name() const override { return "LoadNGEM"; } + /// The purpose of the algorithm. + const std::string summary() const override { + return "Load a file or range of files created by the nGEM detector into a " + "workspace."; + }; + /// Algorithm's Version for identification. + int version() const override { return 1; } + /// Algorithm's category for identification. + const std::string category() const override { return "DataHandling\\NGEM"; }; + /// Should the loader load multiple files into one workspace. + bool loadMutipleAsOne() override { return true; } + + /// The confidence that an algorithm is able to load the file. + int confidence(Kernel::FileDescriptor &descriptor) const override; + +private: + /// Initialise the algorithm. + void init() override; + /// Execute the algorithm. + void exec() override; + /// Load a file into the event lists. + void loadSingleFile(const std::vector<std::string> &filePath, + int &eventCountInFrame, int &maxToF, int &minToF, + int &rawFrames, int &goodFrames, const int &minEventsReq, + const int &maxEventsReq, MantidVec &frameEventCounts, + std::vector<DataObjects::EventList> &events, + std::vector<DataObjects::EventList> &eventsInFrame, + const size_t &totalFilePaths, int &fileCount); + /// Add some text information to the sample logs. + void addToSampleLog(const std::string &logName, const std::string &logText, + DataObjects::EventWorkspace_sptr &ws); + /// Add some number information to the sample logs. + void addToSampleLog(const std::string &logName, const int &logNumber, + DataObjects::EventWorkspace_sptr &ws); + /// Check that a file to be loaded is in 128 bit words. + size_t verifyFileSize(std::ifstream &file); + /// Reports progress and checks cancel flag. + bool reportProgressAndCheckCancel(size_t &numProcessedEvents, + int &eventCountInFrame, + const size_t &totalNumEvents, + const size_t &totalFilePaths, + const int &fileCount); + /// Create a workspace to store the number of counts per frame. + void createCountWorkspace(const std::vector<double> &frameEventCounts); + /// Load the instrument and attach to the data workspace. + void loadInstrument(DataObjects::EventWorkspace_sptr &dataWorkspace); + /// Validate the imputs into the algorithm, overrides. + std::map<std::string, std::string> validateInputs() override; +}; + +} // namespace DataHandling +} // namespace Mantid + +#endif // MANTID_DATAHANDLING_LOADNGEM_H_ diff --git a/Framework/DataHandling/src/Load.cpp b/Framework/DataHandling/src/Load.cpp index af7c416efdcd36b1c3b9e7c6695bf62e0285b6a8..6441366335df378a1597bd9146c7894f937963da 100644 --- a/Framework/DataHandling/src/Load.cpp +++ b/Framework/DataHandling/src/Load.cpp @@ -291,6 +291,7 @@ void Load::init() { exts.emplace_back(".sqw"); exts.emplace_back(".fits"); exts.emplace_back(".bin"); + exts.emplace_back(".edb"); declareProperty( std::make_unique<MultipleFileProperty>("Filename", exts), diff --git a/Framework/DataHandling/src/LoadNGEM.cpp b/Framework/DataHandling/src/LoadNGEM.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cb44c0de2fdb549adab2ea1fd92213e104313a9b --- /dev/null +++ b/Framework/DataHandling/src/LoadNGEM.cpp @@ -0,0 +1,464 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2019 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + + +#include "MantidDataHandling/LoadNGEM.h" +#include "MantidAPI/Axis.h" +#include "MantidAPI/MultipleFileProperty.h" +#include "MantidAPI/RegisterFileLoader.h" +#include "MantidDataObjects/Workspace2D.h" +#include "MantidDataObjects/WorkspaceCreation.h" +#include "MantidKernel/BoundedValidator.h" +#include "MantidKernel/MultiThreaded.h" +#include "MantidKernel/OptionalBool.h" +#include "MantidKernel/Unit.h" +#include "MantidKernel/UnitFactory.h" + +#include <boost/algorithm/string.hpp> +#include <fstream> + +namespace Mantid { +namespace DataHandling { + +DECLARE_FILELOADER_ALGORITHM(LoadNGEM) + +// Constants and helper functions. +namespace { +constexpr int NUM_OF_SPECTRA = 16384; + +/** + * @brief Byte swap a 64 bit word as the nGEM detector outputs in big endian + * format. So must be swapped to have correct values on x86 and x64 + * architectures. + * + * @param word The 64 bit word to swap. + * @return uint64_t The swapped word. + */ +uint64_t swapUint64(uint64_t word) { + word = ((word << 8) & 0xFF00FF00FF00FF00ULL) | + ((word >> 8) & 0x00FF00FF00FF00FFULL); + word = ((word << 16) & 0xFFFF0000FFFF0000ULL) | + ((word >> 16) & 0x0000FFFF0000FFFFULL); + return (word << 32) | (word >> 32); +} + +/** + * @brief Correct a big endian event to be compatible with x64 and x86 + * architecture. + * + * @param bigEndian The big endian formatted event. + * @param smallEndian The resulting small endian formatted event. + */ +void correctForBigEndian(EventUnion *&bigEndian, EventUnion &smallEndian) { + smallEndian.splitWord.words[0] = swapUint64(bigEndian->splitWord.words[1]); + smallEndian.splitWord.words[1] = swapUint64(bigEndian->splitWord.words[0]); +} + +/** + * @brief Add a frame to the main set of events. + * + * @param rawFrames The number of T0 Events detected so far. + * @param goodFrames The number of good frames detected so far. + * @param eventCountInFrame The number of events in the current frame. + * @param minEventsReq The number of events required to be a good frame. + * @param maxEventsReq The max events allowed to be a good frame. + * @param frameEventCounts A vector of the number of events in each good frame. + * @param events The main set of events for the data so far. + * @param eventsInFrame The set of events for the current frame. + */ +void addFrameToOutputWorkspace( + int &rawFrames, int &goodFrames, const int &eventCountInFrame, + const int &minEventsReq, const int &maxEventsReq, + MantidVec &frameEventCounts, std::vector<DataObjects::EventList> &events, + std::vector<DataObjects::EventList> &eventsInFrame) { + ++rawFrames; + if (eventCountInFrame >= minEventsReq && eventCountInFrame <= maxEventsReq) { + // Add number of event counts to workspace. + frameEventCounts.emplace_back(eventCountInFrame); + ++goodFrames; + + PARALLEL_FOR_NO_WSP_CHECK() + // Add events that match parameters to workspace + for (auto i = 0; i < NUM_OF_SPECTRA; ++i) { + if (eventsInFrame[i].getNumberEvents() > 0) { + events[i] += eventsInFrame[i]; + eventsInFrame[i].clear(); + } + } + } +} + +/** + * @brief Creates an event workspace and fills it with the data. + * + * @param maxToF The largest ToF seen so far. + * @param binWidth The width of each bin. + * @param events The main events data. + * @param dataWorkspace The workspace to add the data to. + */ +void createEventWorkspace(const int &maxToF, const double &binWidth, + std::vector<DataObjects::EventList> &events, + DataObjects::EventWorkspace_sptr &dataWorkspace) { + std::vector<double> xAxis; + // Round up number of bins needed and reserve the space in the vector. + xAxis.reserve(int(std::ceil(maxToF / binWidth))); + for (auto i = 0; i < (maxToF / binWidth); i++) { + xAxis.push_back(i * binWidth); + } + + dataWorkspace = DataObjects::create<DataObjects::EventWorkspace>( + NUM_OF_SPECTRA, HistogramData::Histogram(HistogramData::BinEdges(xAxis))); + PARALLEL_FOR_NO_WSP_CHECK() + for (auto i = 0u; i < events.size(); ++i) { + dataWorkspace->getSpectrum(i) = events[i]; + dataWorkspace->getSpectrum(i).setSpectrumNo(i + 1); + dataWorkspace->getSpectrum(i).setDetectorID(i + 1); + } + dataWorkspace->setAllX(HistogramData::BinEdges{xAxis}); + dataWorkspace->getAxis(0)->unit() = + Kernel::UnitFactory::Instance().create("TOF"); + dataWorkspace->setYUnit("Counts"); +} +} // namespace + +/** + * @brief The confidence that a file can be loaded. + * + * @param descriptor A description of the file. + * @return int The level of certainty that the file can be loaded with this + * loader. + */ +int LoadNGEM::confidence(Kernel::FileDescriptor &descriptor) const { + if (descriptor.extension() == ".edb") { + return 95; + } else { + return 0; + } +} + +/** + * @brief Initialisation of the algorithm, setting up the properties. + */ +void LoadNGEM::init() { + // Filename property. + const std::vector<std::string> extentions{".edb"}; + declareProperty( + std::make_unique<API::MultipleFileProperty>("Filename", extentions), + "The name of the nGEM file to load. Selecting multiple files will " + "combine them into one workspace."); + // Output workspace + declareProperty(std::make_unique<API::WorkspaceProperty<API::Workspace>>( + "OutputWorkspace", "", Kernel::Direction::Output), + "The output workspace"); + + auto mustBePositive = boost::make_shared<Kernel::BoundedValidator<int>>(); + mustBePositive->setLower(0); + + auto mustBePositiveDbl = + boost::make_shared<Kernel::BoundedValidator<double>>(); + mustBePositiveDbl->setLower(0.0); + + // Bin Width + declareProperty("BinWidth", 10.0, mustBePositiveDbl, + "The width of the time bins in the output."); + + declareProperty("MinEventsPerFrame", 0, mustBePositive, + "The minimum number of events required in a frame before a " + "it is considered 'good'."); + declareProperty("MaxEventsPerFrame", EMPTY_INT(), mustBePositive, + "The maximum number of events allowed in a frame to be " + "considered 'good'."); + declareProperty( + std::make_unique<Kernel::PropertyWithValue<bool>>( + "GenerateEventsPerFrame", false, Kernel::Direction::Input), + "Generate a workspace to show the number of events captured by each " + "frame. (optional, default False)."); +} + +/** + * @brief Execute the algorithm. + */ +void LoadNGEM::exec() { + progress(0); + + std::vector<std::vector<std::string>> filePaths = getProperty("Filename"); + + const int minEventsReq(getProperty("MinEventsPerFrame")); + const int maxEventsReq(getProperty("MaxEventsPerFrame")); + + int maxToF = -1; + int minToF = 2147483647; + const double binWidth(getProperty("BinWidth")); + + int rawFrames = 0; + int goodFrames = 0; + std::vector<double> frameEventCounts; + int eventCountInFrame = 0; + + std::vector<DataObjects::EventList> events, eventsInFrame; + events.resize(NUM_OF_SPECTRA); + eventsInFrame.resize(NUM_OF_SPECTRA); + progress(0.04); + + size_t totalFilePaths(filePaths.size()); + int counter(1); + for (const auto &filePath : filePaths) { + loadSingleFile(filePath, eventCountInFrame, maxToF, minToF, rawFrames, + goodFrames, minEventsReq, maxEventsReq, frameEventCounts, + events, eventsInFrame, totalFilePaths, counter); + } + // Add the final frame of events (as they are not followed by a T0 event) + addFrameToOutputWorkspace(rawFrames, goodFrames, eventCountInFrame, + minEventsReq, maxEventsReq, frameEventCounts, + events, eventsInFrame); + progress(0.90); + + DataObjects::EventWorkspace_sptr dataWorkspace; + createEventWorkspace(maxToF, binWidth, events, dataWorkspace); + + addToSampleLog("raw_frames", rawFrames, dataWorkspace); + addToSampleLog("good_frames", goodFrames, dataWorkspace); + addToSampleLog("max_ToF", maxToF, dataWorkspace); + addToSampleLog("min_ToF", minToF, dataWorkspace); + + loadInstrument(dataWorkspace); + + setProperty("OutputWorkspace", dataWorkspace); + if (this->getProperty("GenerateEventsPerFrame")) { + createCountWorkspace(frameEventCounts); + } + progress(1.00); +} + +/** + * @brief Load a single file into the event lists. + * + * @param filePath The path to the file. + * @param eventCountInFrame The number of events in the current frame. + * @param maxToF The highest detected ToF + * @param minToF The lowest detected ToF + * @param rawFrames The number of T0 Events detected so far. + * @param goodFrames The number of good frames detected so far. + * @param minEventsReq The number of events required to be a good frame. + * @param maxEventsReq The max events allowed to be a good frame. + * @param frameEventCounts A vector of the number of events in each good frame. + * @param events The main set of events for the data so far. + * @param eventsInFrame The set of events for the current frame. + * @param totalFilePaths The total number of file paths. + * @param fileCount The number of file paths processed. + */ +void LoadNGEM::loadSingleFile( + const std::vector<std::string> &filePath, int &eventCountInFrame, + int &maxToF, int &minToF, int &rawFrames, int &goodFrames, + const int &minEventsReq, const int &maxEventsReq, + MantidVec &frameEventCounts, std::vector<DataObjects::EventList> &events, + std::vector<DataObjects::EventList> &eventsInFrame, + const size_t &totalFilePaths, int &fileCount) { + // Create file reader + if (filePath.size() > 1) { + throw std::runtime_error("Invalid filename parameter."); + } + std::ifstream file(filePath[0].c_str(), std::ifstream::binary); + if (!file.is_open()) { + throw std::runtime_error("File could not be found."); + } + std::array<char, 16> buffer; + + const size_t totalNumEvents = verifyFileSize(file) / 16; + size_t numProcessedEvents = 0; + + while (!file.eof()) { + // Load an event into the variable. + file.read(buffer.data(), 16); + auto eventBigEndian = reinterpret_cast<EventUnion *>(buffer.data()); + + // Correct for the big endian format. + EventUnion event; + correctForBigEndian(eventBigEndian, event); + + if (event.coincidence.check()) { // Check for coincidence event. + ++eventCountInFrame; + uint64_t pixel = event.coincidence.getPixel(); + int tof = + event.coincidence.timeOfFlight / 1000; // Convert to microseconds (us) + + if (tof > maxToF) { + maxToF = tof; + } else if (tof < minToF) { + minToF = tof; + } + eventsInFrame[pixel].addEventQuickly(Types::Event::TofEvent(tof)); + + } else if (event.tZero.check()) { // Check for T0 event. + addFrameToOutputWorkspace(rawFrames, goodFrames, eventCountInFrame, + minEventsReq, maxEventsReq, frameEventCounts, + events, eventsInFrame); + + if (reportProgressAndCheckCancel(numProcessedEvents, eventCountInFrame, + totalNumEvents, totalFilePaths, + fileCount)) { + return; + } + } else { // Catch all other events and notify. + g_log.warning() << "Unexpected event type loaded.\n"; + } + } + g_log.information() << "Finished loading a file.\n"; + ++fileCount; +} + +/** + * @brief Add a string value to the sample logs. + * + * @param logName The name of the log. + * @param logText The content of the log. + * @param ws The workspace to add the log to. + */ +void LoadNGEM::addToSampleLog(const std::string &logName, + const std::string &logText, + DataObjects::EventWorkspace_sptr &ws) { + API::Algorithm_sptr sampLogAlg = createChildAlgorithm("AddSampleLog"); + sampLogAlg->setProperty("Workspace", ws); + sampLogAlg->setProperty("LogType", "String"); + sampLogAlg->setProperty("LogName", logName); + sampLogAlg->setProperty("LogText", logText); + sampLogAlg->executeAsChildAlg(); +} + +/** + * @brief Add a number value to the sample logs. + * + * @param logName Name of the log. + * @param logNumber The value of the log. + * @param ws The workspace to add the log to. + */ +void LoadNGEM::addToSampleLog(const std::string &logName, const int &logNumber, + DataObjects::EventWorkspace_sptr &ws) { + API::Algorithm_sptr sampLogAlg = createChildAlgorithm("AddSampleLog"); + sampLogAlg->setProperty("Workspace", ws); + sampLogAlg->setProperty("LogType", "Number"); + sampLogAlg->setProperty("LogName", logName); + sampLogAlg->setProperty("LogText", std::to_string(logNumber)); + sampLogAlg->executeAsChildAlg(); +} + +/** + * @brief Ensure that the file fits into 16, as the detector spits out 128 bit + * words (16 bytes) + * + * @param file The file to check. + * @return size_t The size of the file. + */ +size_t LoadNGEM::verifyFileSize(std::ifstream &file) { + // Check that the file fits into 16 byte sections. + file.seekg(0, file.end); + size_t size = file.tellg(); + if (size % 16 != 0) { + g_log.warning() + << "Invalid file size. File is size is " << size + << " bytes which is not a multiple of 16. There may be some bytes " + "missing from the data. \n"; + } + file.seekg(0); + return size; +} + +/** + * @brief Report the progress of the loader through the current file. + * + * @param numProcessedEvents The number of events processed so far. + * @param eventCountInFrame The number of events in the current frame. + * @param totalNumEvents The total number of events in the file. + * @param totalFilePaths The total number of file paths to process. + * @param fileCount The number of files processed. + * @return true If user has cancelled the load. + * @return false If the user has not cancelled. + */ +bool LoadNGEM::reportProgressAndCheckCancel(size_t &numProcessedEvents, + int &eventCountInFrame, + const size_t &totalNumEvents, + const size_t &totalFilePaths, + const int &fileCount) { + numProcessedEvents += eventCountInFrame; + std::string message(std::to_string(fileCount) + "/" + + std::to_string(totalFilePaths)); + progress(double(numProcessedEvents) / double(totalNumEvents) / 1.11111, + message); + eventCountInFrame = 0; + // Check for cancel flag. + return this->getCancel(); +} + +/** + * @brief Create a count workspace to allow for the selection of "good" + * frames. + * + * @param frameEventCounts A Vector of the number of events per frame. + */ +void LoadNGEM::createCountWorkspace( + const std::vector<double> &frameEventCounts) { + std::vector<double> xAxisCounts(frameEventCounts.size() + 1); + std::generate(xAxisCounts.begin(), + xAxisCounts.end(), [n = 0.0]() mutable { return ++n; }); + + DataObjects::Workspace2D_sptr countsWorkspace = + DataObjects::create<DataObjects::Workspace2D>( + 1, HistogramData::Histogram(HistogramData::BinEdges(xAxisCounts))); + + countsWorkspace->mutableY(0) = frameEventCounts; + std::string countsWorkspaceName(this->getProperty("OutputWorkspace")); + countsWorkspaceName.append("_event_counts"); + countsWorkspace->setYUnit("Counts"); + boost::shared_ptr<Kernel::Units::Label> XLabel = + boost::dynamic_pointer_cast<Kernel::Units::Label>( + Kernel::UnitFactory::Instance().create("Label")); + XLabel->setLabel("Frame"); + countsWorkspace->getAxis(0)->unit() = XLabel; + + this->declareProperty( + std::make_unique<API::WorkspaceProperty<API::Workspace>>( + "CountsWorkspace", countsWorkspaceName, Kernel::Direction::Output), + "Counts of events per frame."); + progress(1.00); + this->setProperty("CountsWorkspace", countsWorkspace); +} + +/** + * @brief Load the nGEM instrument into a workspace. + * + * @param dataWorkspace The workspace to load into. + */ +void LoadNGEM::loadInstrument(DataObjects::EventWorkspace_sptr &dataWorkspace) { + auto loadInstrument = this->createChildAlgorithm("LoadInstrument"); + loadInstrument->setPropertyValue("InstrumentName", "NGEM"); + loadInstrument->setProperty<API::MatrixWorkspace_sptr>("Workspace", + dataWorkspace); + loadInstrument->setProperty("RewriteSpectraMap", Kernel::OptionalBool(false)); + loadInstrument->execute(); +} + +/** + * @brief Validate inputs into the loader GUI. + * + * @return std::map<std::string, std::string> A map that is empty if there are + * no issues, and contains a key of the input field and a value of the error + * message otherwise. + */ +std::map<std::string, std::string> LoadNGEM::validateInputs() { + std::map<std::string, std::string> results; + + int MinEventsPerFrame = getProperty("MinEventsPerFrame"); + int MaxEventsPerFrame = getProperty("MaxEventsPerFrame"); + + if (MaxEventsPerFrame < MinEventsPerFrame) { + results["MaxEventsPerFrame"] = + "MaxEventsPerFrame is less than MinEvents per frame"; + } + return results; +} + +} // namespace DataHandling +} // namespace Mantid \ No newline at end of file diff --git a/Framework/DataHandling/test/LoadNGEMTest.h b/Framework/DataHandling/test/LoadNGEMTest.h new file mode 100644 index 0000000000000000000000000000000000000000..1bffa9539f7349675a60ded55792ec455ac0ecdb --- /dev/null +++ b/Framework/DataHandling/test/LoadNGEMTest.h @@ -0,0 +1,184 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef LOADNGEMTEST_H_ +#define LOADNGEMTEST_H_ + +#include <MantidAPI/AnalysisDataService.h> +#include <MantidAPI/FileFinder.h> +#include <MantidDataHandling/LoadNGEM.h> +#include <MantidDataObjects/EventWorkspace.h> +#include <cxxtest/TestSuite.h> + +using namespace Mantid; +using namespace Mantid::DataHandling; +using namespace Mantid::API; + +class LoadNGEMTest : public CxxTest::TestSuite { +public: + void test_init() { + LoadNGEM alg; + TS_ASSERT_THROWS_NOTHING(alg.initialize()); + + TS_ASSERT(alg.isInitialized()); + + TS_ASSERT_THROWS_NOTHING(alg.setProperty( + "Filename", getTestFilePath("GEM000005_00_000_short.edb"))); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("OutputWorkspace", "ws")); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("BinWidth", 10.0)); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("MinEventsPerFrame", 10)); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("MaxEventsPerFrame", 20)); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("GenerateEventsPerFrame", false)); + } + + void test_exec_loads_data_to_ads() { + LoadNGEM alg; + alg.initialize(); + TS_ASSERT(alg.isInitialized()); + + TS_ASSERT_THROWS_NOTHING(alg.setProperty( + "Filename", getTestFilePath("GEM000005_00_000_short.edb"))); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("OutputWorkspace", "ws")); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("GenerateEventsPerFrame", false)); + TS_ASSERT_THROWS_NOTHING(alg.execute()); + + TS_ASSERT_THROWS_NOTHING(AnalysisDataService::Instance().remove("ws")); + } + + void test_exec_loads_event_counts_workspace() { + LoadNGEM alg; + alg.initialize(); + TS_ASSERT(alg.isInitialized()); + + TS_ASSERT_THROWS_NOTHING(alg.setProperty( + "Filename", getTestFilePath("GEM000005_00_000_short.edb"))); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("OutputWorkspace", "ws")); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("GenerateEventsPerFrame", true)); + TS_ASSERT_THROWS_NOTHING(alg.execute()); + + TS_ASSERT_THROWS_NOTHING(AnalysisDataService::Instance().remove("ws")); + TS_ASSERT_THROWS_NOTHING( + AnalysisDataService::Instance().remove("ws_event_counts")); + } + + void test_exec_not_load_event_counts_workspace() { + LoadNGEM alg; + alg.initialize(); + TS_ASSERT(alg.isInitialized()); + + TS_ASSERT_THROWS_NOTHING(alg.setProperty( + "Filename", getTestFilePath("GEM000005_00_000_short.edb"))); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("OutputWorkspace", "ws")); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("GenerateEventsPerFrame", false)); + TS_ASSERT_THROWS_NOTHING(alg.execute()); + + TS_ASSERT_THROWS_NOTHING(AnalysisDataService::Instance().remove("ws")); + TS_ASSERT_THROWS_ANYTHING( + AnalysisDataService::Instance().retrieve("ws_event_counts")); + } + + void test_init_fails_on_bad_binWidth() { + LoadNGEM alg; + alg.initialize(); + TS_ASSERT(alg.isInitialized()); + + TS_ASSERT_THROWS_ANYTHING(alg.setProperty("BinWidth", -10.0)); + } + + void test_init_fails_on_bad_MaxEventsPerFrame() { + LoadNGEM alg; + alg.initialize(); + TS_ASSERT(alg.isInitialized()); + + TS_ASSERT_THROWS_ANYTHING(alg.setProperty("MaxEventsPerFrame", -10)); + } + + void test_init_fails_on_bad_MinEventsPerFrame() { + LoadNGEM alg; + alg.initialize(); + TS_ASSERT(alg.isInitialized()); + + TS_ASSERT_THROWS_ANYTHING(alg.setProperty("MinEventsPerFrame", -10)); + } + + void test_init_fails_on_MaxEvents_is_less_than_MinEvents() { + LoadNGEM alg; + alg.initialize(); + TS_ASSERT(alg.isInitialized()); + + TS_ASSERT_THROWS_NOTHING(alg.setProperty( + "Filename", getTestFilePath("GEM000005_00_000_short.edb"))); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("OutputWorkspace", "ws")); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("MinEventsPerFrame", 20)); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("MaxEventsPerFrame", 10)); + + TS_ASSERT_THROWS(alg.execute(), std::runtime_error); + } + + void test_MinEventsPerFrame_removes_low_values() { + LoadNGEM alg; + alg.initialize(); + TS_ASSERT(alg.isInitialized()); + + TS_ASSERT_THROWS_NOTHING(alg.setProperty( + "Filename", getTestFilePath("GEM000005_00_000_short.edb"))); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("OutputWorkspace", "ws")); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("MinEventsPerFrame", 0)); + TS_ASSERT_THROWS_NOTHING(alg.execute()); + + DataObjects::EventWorkspace_sptr ws; + + ws = + AnalysisDataService::Instance().retrieveWS<DataObjects::EventWorkspace>( + "ws"); + TS_ASSERT(ws); + const size_t rawNumEvents = ws->getNumberEvents(); + + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("MinEventsPerFrame", Mantid::EMPTY_INT())); + TS_ASSERT_THROWS_NOTHING(alg.execute()); + ws = + AnalysisDataService::Instance().retrieveWS<DataObjects::EventWorkspace>( + "ws"); + TS_ASSERT(rawNumEvents > ws->getNumberEvents()); + } + + void test_MaxEventsPerFrame_removes_high_values() { + LoadNGEM alg; + alg.initialize(); + TS_ASSERT(alg.isInitialized()); + + TS_ASSERT_THROWS_NOTHING(alg.setProperty( + "Filename", getTestFilePath("GEM000005_00_000_short.edb"))); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("OutputWorkspace", "ws")); + TS_ASSERT_THROWS_NOTHING(alg.execute()); + + DataObjects::EventWorkspace_sptr ws; + ws = + AnalysisDataService::Instance().retrieveWS<DataObjects::EventWorkspace>( + "ws"); + TS_ASSERT(ws); + const size_t rawNumEvents = ws->getNumberEvents(); + + TS_ASSERT_THROWS_NOTHING(alg.setProperty("MaxEventsPerFrame", 0)); + TS_ASSERT_THROWS_NOTHING(alg.execute()); + ws = + AnalysisDataService::Instance().retrieveWS<DataObjects::EventWorkspace>( + "ws"); + TS_ASSERT(rawNumEvents > ws->getNumberEvents()); + } + +private: + // Helper function to get the path of a specified file. + std::string getTestFilePath(const std::string &filename) { + const std::string filepath = + Mantid::API::FileFinder::Instance().getFullPath(filename); + TS_ASSERT_DIFFERS(filepath, ""); + return filepath; + } +}; + +#endif // LOADNGEMTEST_H_ \ No newline at end of file diff --git a/Testing/Data/UnitTest/GEM000005_00_000_short.edb.md5 b/Testing/Data/UnitTest/GEM000005_00_000_short.edb.md5 new file mode 100644 index 0000000000000000000000000000000000000000..7953cfbf0e1bec3b83dc69259b9010fd70be242f --- /dev/null +++ b/Testing/Data/UnitTest/GEM000005_00_000_short.edb.md5 @@ -0,0 +1 @@ +1010588fa5f3933b43ace958c201f93f diff --git a/docs/source/algorithms/LoadNGEM-v1.rst b/docs/source/algorithms/LoadNGEM-v1.rst new file mode 100644 index 0000000000000000000000000000000000000000..e825dfe732b2c312548ffdb0e765db5d057e1144 --- /dev/null +++ b/docs/source/algorithms/LoadNGEM-v1.rst @@ -0,0 +1,34 @@ +.. algorithm:: + +.. summary:: + +.. relatedalgorithms:: + +.. properties:: + +Description +----------- +The LoadNGEM algorithm will read .edb files generated by the nGEM +detector. + +The detector produces files that are up to 1GB in size, so if the +collection produces more than 1GB of data, it will create a new file +to write the rest of the data to. When all of the files are selected +they are loaded into a single workspace containing all of the events. + +The loading process allows the option to include a count of the number +of events in each frame (begun by a T0 event), which is loaded as a +plottable workspace. This can then be used to determine what is considered +a "good frame" so that only good frames are included in the final data. + +Child Algorithms used +##################### + +The ChildAlgorithms used by LoadNGEM are: + +* :ref:`algm-AddSampleLog-v1` - Adds data to the Sample Log of the workspace +* :ref:`algm-LoadInstrument-v1` - Loads the instrument associated with the data + +.. categories:: + +.. sourcelink:: diff --git a/docs/source/release/v4.2.0/framework.rst b/docs/source/release/v4.2.0/framework.rst index dd0a72879211e45749e032e908a00c6beb442a70..650606854d9afcba40be75012ac37d3067807ed7 100644 --- a/docs/source/release/v4.2.0/framework.rst +++ b/docs/source/release/v4.2.0/framework.rst @@ -14,6 +14,7 @@ Concepts Algorithms ---------- +* :ref:`LoadNGEM <algm-LoadNGEM>` added as a loader for the .edb files generated by the nGEM detector used for diagnostics. Generates an event workspace. * :ref:`MaskAngle <algm-MaskAngle>` has an additional option of ``Angle='InPlane'`` * :ref:`FitIncidentSpectrum <algm-FitIncidentSpectrum>` will fit a curve to an incident spectrum returning the curve and it's first derivative. * Whitespace is now ignored anywhere in the string when setting the Filename parameter in :ref:`Load <algm-Load>`. diff --git a/instrument/Facilities.xml b/instrument/Facilities.xml index 7ba5baee6fe35b8ccdf5f0400cbfda798f7f7d9e..61a2438d497af2e360984ba911ec75ff346a4d17 100644 --- a/instrument/Facilities.xml +++ b/instrument/Facilities.xml @@ -157,6 +157,14 @@ </livedata> </instrument> + <instrument name="NGEM"> + <technique>Neutron Diffraction</technique> + <zeropadding size="8"/> + <livedata> + <connection name="histo" address="NDXGEM:6789" listener="ISISHistoDataListener" /> + </livedata> + </instrument> + <instrument name="OSIRIS" shortname="OSI" beamline="N6"> <zeropadding size="8" startRunNumber="99780" prefix="OSIRIS"/> <technique>Neutron Diffraction</technique> diff --git a/instrument/NGEM_Definition.xml b/instrument/NGEM_Definition.xml new file mode 100644 index 0000000000000000000000000000000000000000..17a9172e710f91969bdd4a4512a2ebb199a56e0b --- /dev/null +++ b/instrument/NGEM_Definition.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- For help on the notation used to specify an Instrument Definition File + see http://www.mantidproject.org/IDF --> +<instrument xmlns="http://www.mantidproject.org/IDF/1.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.mantidproject.org/IDF/1.0 http://schema.mantidproject.org/IDF/1.0/IDFSchema.xsd" + name="NGEM" valid-from ="2019-05-30 00:00:00" + valid-to ="2100-10-17 09:29:59" + last-modified="2019-05-30 11:40:00"> + + <defaults> + <length unit="meter"/> + <angle unit="degree"/> + <reference-frame> + <!-- The z-axis is set parallel to and in the direction of the beam. the + y-axis points up and the coordinate system is right handed. --> + <along-beam axis="z"/> + <pointing-up axis="y"/> + <handedness val="right"/> + </reference-frame> + <default-view axis-view="z-"/> + </defaults> + + <!-- LIST OF PHYSICAL COMPONENTS (which the instrument consists of) --> + + <!-- source and sample-position components --> + + <component type="source"> + <location z="-40.0"> <facing val="none"/> </location> + </component> + <type name="source" is="Source" /> + + <component type="some-sample-holder"> + <location><facing val="none"/> </location> + </component> + +<type name="some-sample-holder" is="SamplePos"> + <properties /> + <sphere id="some-shape"> + <centre x="0.0" y="0.0" z="0.0" /> + <radius val="0.03" /> + </sphere> + <algebra val="some-shape" /> +</type> + + + <component type="main-detector-bank" idstart="1" idfillbyfirst="x" idstepbyrow="128"> + <location z="1.5" name="main-detector-bank"/> + </component> + + <type name="main-detector-bank" is="rectangular_detector" type="main-detector-pixel" + xpixels="128" xstart="-0.05" xstep="+0.000787402" + ypixels="128" ystart="-0.05" ystep="+0.000787402" > + </type> + + <type name="main-detector-pixel" is="detector"> + <cuboid id="shape"> + <left-front-bottom-point x="0.000393701" y="-0.000393701" z="0.0" /> + <left-front-top-point x="0.000393701" y="-0.000393701" z="0.00000005" /> + <left-back-bottom-point x="-0.000393701" y="-0.000393701" z="0.0" /> + <right-front-bottom-point x="0.000393701" y="0.000393701" z="0.0" /> + </cuboid> + <algebra val="shape" /> + </type> + + +</instrument>