From 4fd3de6e4e551a36c8c088952fece559a101149e Mon Sep 17 00:00:00 2001 From: Geish Miladinovic <geish.miladinovic@ansto.gov.au> Date: Thu, 6 Dec 2018 14:53:39 +1100 Subject: [PATCH] Merged emu workflow support (no ticket) --- Framework/API/inc/MantidAPI/IEventList.h | 2 + .../MantidDataHandling/LoadANSTOEventFile.h | 6 +- .../inc/MantidDataHandling/LoadEMU.h | 43 +- Framework/DataHandling/src/LoadEMU.cpp | 837 ++++++++++++++---- Framework/DataHandling/test/LoadEMUauTest.h | 7 +- .../inc/MantidDataObjects/EventList.h | 5 + Framework/DataObjects/src/EventList.cpp | 86 ++ Framework/DataObjects/test/EventListTest.h | 35 + .../mantid/api/src/Exports/IEventList.cpp | 14 +- .../api/src/Exports/IEventWorkspace.cpp | 6 + 10 files changed, 842 insertions(+), 199 deletions(-) diff --git a/Framework/API/inc/MantidAPI/IEventList.h b/Framework/API/inc/MantidAPI/IEventList.h index 5e699f6b510..24d9473b5f6 100644 --- a/Framework/API/inc/MantidAPI/IEventList.h +++ b/Framework/API/inc/MantidAPI/IEventList.h @@ -77,6 +77,8 @@ public: virtual void addPulsetime(const double seconds) = 0; /// Mask a given TOF range virtual void maskTof(const double tofMin, const double tofMax) = 0; + /// Mask the events by the condition vector + virtual void maskCondition(const std::vector<bool> & mask) = 0; /// Return the list of TOF values virtual std::vector<double> getTofs() const = 0; /// Return the list of TOF values diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadANSTOEventFile.h b/Framework/DataHandling/inc/MantidDataHandling/LoadANSTOEventFile.h index 549a68d77d8..006e80d7f75 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/LoadANSTOEventFile.h +++ b/Framework/DataHandling/inc/MantidDataHandling/LoadANSTOEventFile.h @@ -91,11 +91,11 @@ void ReadEventFile(IReader &loader, IEventHandler &handler, IProgress &progress, int32_t def_clock_scale, bool use_tx_chopper) { // read file headers (base header then packed-format header) EventFileHeader_Base hdr_base; - if (!loader.read(reinterpret_cast<int8_t *>(&hdr_base), sizeof(hdr_base))) + if (!loader.read(reinterpret_cast<char *>(&hdr_base), sizeof(hdr_base))) throw std::runtime_error("unable to load EventFileHeader-Base"); EventFileHeader_Packed hdr_packed; - if (!loader.read(reinterpret_cast<int8_t *>(&hdr_packed), sizeof(hdr_packed))) + if (!loader.read(reinterpret_cast<char *>(&hdr_packed), sizeof(hdr_packed))) throw std::runtime_error("unable to load EventFileHeader-Packed"); if (hdr_base.magic_number != EVENTFILEHEADER_BASE_MAGIC_NUMBER) @@ -185,7 +185,7 @@ void ReadEventFile(IReader &loader, IEventHandler &handler, IProgress &progress, // read next byte uint8_t ch; - if (!loader.read(reinterpret_cast<int8_t *>(&ch), 1)) + if (!loader.read(reinterpret_cast<char *>(&ch), 1)) break; int32_t nbits_ch_used = 0; // no bits used initially, 8 to go diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadEMU.h b/Framework/DataHandling/inc/MantidDataHandling/LoadEMU.h index 829ac8d3fd8..89b311cbc96 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/LoadEMU.h +++ b/Framework/DataHandling/inc/MantidDataHandling/LoadEMU.h @@ -31,12 +31,15 @@ Required Properties: Optional Properties: <UL> +<LI> PathToBinaryEventFile - Rel or abs path to event file linked to hdf</LI> <LI> Mask - The input filename of the mask data</LI> <LI> SelectDetectorTubes - Range of detector tubes to be loaded</LI> +<LI> OverrideDopplerFrequency - Override the Doppler frequency (Hz)</LI> <LI> OverrideDopplerPhase - Override the Doppler phase (degrees)</LI> +<LI> CalibrateDopplerPhase - Calibrate the Doppler phase prior to TOF</LI> +<LI> LoadAsRawDopplerTime - Save event time relative the Doppler</LI> <LI> FilterByTimeStart - Only include events after the start time</LI> <LI> FilterByTimeStop - Only include events before the stop time</LI> -<LI> LoadAsRawDopplerTime - Save event time relative the Doppler</LI> </UL> @author Geish Miladinovic (ANSTO) @@ -63,7 +66,8 @@ File change history is stored at: <https://github.com/mantidproject/mantid>. Code Documentation is available at: <http://doxygen.mantidproject.org> */ -class DLLExport LoadEMU : public API::IFileLoader<Kernel::FileDescriptor> { +template <typename FD> +class DLLExport LoadEMU : public API::IFileLoader<FD> { public: // description @@ -71,34 +75,36 @@ public: const std::vector<std::string> seeAlso() const override { return {"Load", "LoadQKK"}; } - const std::string name() const override { return "LoadEMU"; } + const std::string name() const override; const std::string category() const override { return "DataHandling\\ANSTO"; } - const std::string summary() const override { - return "Loads an EMU data file into a workspace."; - } + const std::string summary() const override; // returns a confidence value that this algorithm can load a specified file - int confidence(Kernel::FileDescriptor &descriptor) const override; + int confidence(FD &descriptor) const override; private: // initialisation void init() override; + void init(bool hdfLoader); // execution void exec() override; + void exec(const std::string &hdfFile, const std::string &eventFile); // region of intreset static std::vector<bool> createRoiVector(const std::string &seltubes, const std::string &maskfile); // load parameters from input file - void loadParameters(ANSTO::Tar::File &tarFile, API::LogManager &logm); + void loadParameters(const std::string &hdfFile, API::LogManager &logm); + void loadEnvironParameters(const std::string &hdfFile, + API::LogManager &logm); // load the instrument definition and instrument parameters void loadInstrument(); // create workspace - void createWorkspace(ANSTO::Tar::File &tarFile); + void createWorkspace(const std::string &title); // dynamically update the neutronic position void loadDetectorL2Values(); @@ -107,6 +113,12 @@ private: // load and log the Doppler parameters void loadDopplerParameters(API::LogManager &logm); + // calibrate doppler phase + void calibrateDopplerPhase(std::vector<size_t> &eventCounts, + std::vector<EventVector_pt> &eventVectors); + void dopplerTimeToTOF(std::vector<EventVector_pt> &eventVectors, + double& minTOF, double& maxTOF); + // prepare event storage void prepareEventStorage(ANSTO::ProgressTracker &prog, std::vector<size_t> &eventCounts, @@ -116,21 +128,28 @@ private: void setupDetectorMasks(std::vector<bool> &roi); // binary file access - template <class EventProcessor> + template <class Processor> static void loadEvents(API::Progress &prog, const char *progMsg, - ANSTO::Tar::File &tarFile, - EventProcessor &eventProcessor); + const std::string &eventFile, + Processor &eventProcessor); // shared member variables DataObjects::EventWorkspace_sptr m_localWorkspace; std::vector<double> m_detectorL2; + int32_t m_datasetIndex; + std::string m_startRun; // Doppler characteristics double m_dopplerAmpl; double m_dopplerFreq; double m_dopplerPhase; + int32_t m_dopplerRun; + bool m_calibrateDoppler; }; +using LoadEMUHdf = LoadEMU<Kernel::NexusDescriptor>; +using LoadEMUTar = LoadEMU<Kernel::FileDescriptor>; + } // namespace DataHandling } // namespace Mantid diff --git a/Framework/DataHandling/src/LoadEMU.cpp b/Framework/DataHandling/src/LoadEMU.cpp index 3fe20129738..8086397332b 100644 --- a/Framework/DataHandling/src/LoadEMU.cpp +++ b/Framework/DataHandling/src/LoadEMU.cpp @@ -19,12 +19,18 @@ #include "MantidNexus/NexusClasses.h" #include <boost/math/special_functions/round.hpp> +#include <boost/math/tools/minima.hpp> +#include <boost/filesystem.hpp> #include <Poco/AutoPtr.h> #include <Poco/TemporaryFile.h> #include <Poco/Util/PropertyFileConfiguration.h> -#include <math.h> +#include <cstdio> +#include <cmath> +#include <iostream> +#include <fstream> +#include <algorithm> namespace Mantid { namespace DataHandling { @@ -36,9 +42,9 @@ namespace { // number of physical detectors constexpr size_t HORIZONTAL_TUBES = 16; constexpr size_t VERTICAL_TUBES = 35; -constexpr size_t DETECTORS = HORIZONTAL_TUBES + VERTICAL_TUBES; +constexpr size_t DETECTOR_TUBES = HORIZONTAL_TUBES + VERTICAL_TUBES; // analysed and direct detectors -constexpr size_t HISTO_BINS_X = DETECTORS * 2; +constexpr size_t HISTO_BINS_X = DETECTOR_TUBES * 2; constexpr size_t HISTO_BINS_Y = 1024; constexpr size_t HISTO_BINS_Y_DENUMERATOR = 16; constexpr size_t PIXELS_PER_TUBE = HISTO_BINS_Y / HISTO_BINS_Y_DENUMERATOR; @@ -55,24 +61,52 @@ constexpr size_t Progress_Total = constexpr char FilenameStr[] = "Filename"; constexpr char MaskStr[] = "Mask"; constexpr char SelectDetectorTubesStr[] = "SelectDetectorTubes"; +constexpr char SelectDatasetStr[] = "SelectDataset"; +constexpr char OverrideDopplerFreqStr[] = "OverrideDopplerFrequency"; constexpr char OverrideDopplerPhaseStr[] = "OverrideDopplerPhase"; constexpr char FilterByTimeStartStr[] = "FilterByTimeStart"; constexpr char FilterByTimeStopStr[] = "FilterByTimeStop"; constexpr char RawDopplerTimeStr[] = "LoadAsRawDopplerTime"; +constexpr char CalibrateDopplerPhaseStr[] = "CalibrateDopplerPhase"; +constexpr char PathToBinaryStr[] = "BinaryEventPath"; // Common pairing of limits using TimeLimits = std::pair<double, double>; +template <typename Type> +void AddSinglePointTimeSeriesProperty(API::LogManager &logManager, + const std::string &time, + const std::string &name, + const Type value) { + // create time series property and add single value + auto p = new Kernel::TimeSeriesProperty<Type>(name); + p->addValue(time, value); + + // add to log manager + logManager.addProperty(p); +} + +template <typename T> +void MapNeXusToSeries(NeXus::NXEntry &entry, const std::string &path, + const T &defval, API::LogManager &logManager, + const std::string &time, const std::string &name, + const T &factor, int32_t index) { + + T value = GetNeXusValue<T>(entry, path, defval, index); + AddSinglePointTimeSeriesProperty<T>(logManager, time, + name, value * factor); +} + // Utility functions for loading values with defaults // Single value properties only support int, double, string and bool template <typename Type> Type GetNeXusValue(NeXus::NXEntry &entry, const std::string &path, - const Type &defval) { + const Type &defval, int32_t index) { try { NeXus::NXDataSetTyped<Type> dataSet = entry.openNXDataSet<Type>(path); dataSet.load(); - return *dataSet(); + return dataSet()[index]; } catch (std::runtime_error &) { return defval; } @@ -80,12 +114,12 @@ Type GetNeXusValue(NeXus::NXEntry &entry, const std::string &path, // string and double are special cases template <> double GetNeXusValue<double>(NeXus::NXEntry &entry, const std::string &path, - const double &defval) { + const double &defval, int32_t index) { try { NeXus::NXDataSetTyped<float> dataSet = entry.openNXDataSet<float>(path); dataSet.load(); - return *dataSet(); + return dataSet()[index]; } catch (std::runtime_error &) { return defval; } @@ -93,7 +127,7 @@ double GetNeXusValue<double>(NeXus::NXEntry &entry, const std::string &path, template <> std::string GetNeXusValue<std::string>(NeXus::NXEntry &entry, const std::string &path, - const std::string &defval) { + const std::string &defval, int32_t) { try { NeXus::NXChar dataSet = entry.openNXChar(path); @@ -108,9 +142,9 @@ std::string GetNeXusValue<std::string>(NeXus::NXEntry &entry, template <typename T> void MapNeXusToProperty(NeXus::NXEntry &entry, const std::string &path, const T &defval, API::LogManager &logManager, - const std::string &name, const T &factor) { + const std::string &name, const T &factor, int32_t index) { - T value = GetNeXusValue<T>(entry, path, defval); + T value = GetNeXusValue<T>(entry, path, defval, index); logManager.addProperty<T>(name, value * factor); } @@ -118,9 +152,10 @@ void MapNeXusToProperty(NeXus::NXEntry &entry, const std::string &path, template <> void MapNeXusToProperty<std::string>( NeXus::NXEntry &entry, const std::string &path, const std::string &defval, - API::LogManager &logManager, const std::string &name, const std::string &) { + API::LogManager &logManager, const std::string &name, const std::string &, + int32_t index) { - std::string value = GetNeXusValue<std::string>(entry, path, defval); + std::string value = GetNeXusValue<std::string>(entry, path, defval, index); logManager.addProperty<std::string>(name, value); } @@ -189,13 +224,15 @@ double invert(double y, const F &f, double x0 = 0.0, const double eps = 1e-16) { // the detectors is not constant so it needs to store the distance for each // Note that observation time and TOF are in usec // +using TofData = std::tuple<double, double>; + class ConvertTOF { const double m_w; const double m_phi; const double m_L0; const double m_v2; const double m_A; - std::vector<double> m_L2; + const std::vector<double>& m_L2; inline double L1(double t) const { return m_L0 + m_A * sin(m_w * t + m_phi); } @@ -209,7 +246,7 @@ public: : m_w(2 * M_PI * freq), m_phi(M_PI * phase / 180.0), m_L0(L1), m_v2(v2), m_A(Amp), m_L2(L2) {} - double directTOF(size_t detID, double tobs) const { + TofData directTOF(size_t detID, double tobs) const { // observation time and tof are in usec auto tn = [=](double t) { return t + (L1(t) + m_L2[detID]) / v1(t); }; @@ -217,12 +254,12 @@ public: double tsec = tobs * 1.0e-6; double t0 = tsec - (m_L0 + m_L2[detID]) / m_v2; double tinv = invert(tsec, tn, t0); - double tof = m_L0 / v1(tinv) + m_L2[detID] / m_v2; + double tof = (m_L0 + m_L2[detID]) / v1(tinv); - return tof * 1.0e6; + return TofData(tinv * 1.0e6, tof * 1.0e6); } - double analysedTOF(size_t detID, double tobs) const { + TofData analysedTOF(size_t detID, double tobs) const { // observation time and tof are in usec auto tn = [=](double t) { return t + L1(t) / v1(t) + m_L2[detID] / m_v2; }; @@ -231,10 +268,81 @@ public: double t = invert(tsec, tn, t0); double tof = m_L0 / v1(t) + m_L2[detID] / m_v2; - return tof * 1.0e6; + return TofData(t * 1.0e6, tof * 1.0e6); } }; +// calculate mean of a subset of the vector +double maskedMean(std::vector<double>& vec, std::vector<bool>& mask) { + if (vec.size() == 0 || vec.size() != mask.size()) + throw std::runtime_error("masked mean of empty or mismatched vectors"); + double sum = 0.0; + size_t count = 0; + for (size_t i = 0; i != vec.size(); i++) { + if (!mask[i]) + continue; + sum += vec[i]; + count++; + } + if (count == 0) + throw std::runtime_error("mean of empty vector"); + return sum / count; +} + +// calculate stdev fro a subset of the vector +double maskedStdev(std::vector<double>& vec, std::vector<bool>& mask) { + + auto avg = maskedMean(vec, mask); + size_t count = 0; + double sum = 0.0; + for (size_t i = 0; i != vec.size(); i++) { + if (!mask[i]) + continue; + sum += (vec[i] - avg) * (vec[i] - avg); + count++; + } + return std::sqrt(sum / count); +} + +// Simple reader that is compatible with the ASNTO event file loader +class FileLoader +{ + std::ifstream _ifs; + size_t _size; + +public: + FileLoader(const char *filename) + : _ifs(filename, std::ios::binary | std::ios::in) + { + if (!_ifs.is_open() || _ifs.fail()) + throw std::runtime_error("unable to open file"); + + _ifs.seekg(0, _ifs.end); + _size = _ifs.tellg(); + _ifs.seekg(0, _ifs.beg); + } + + bool read(char *s, std::streamsize n) + { + return static_cast<bool>(_ifs.read(s, n)); + } + + size_t size() + { + return _size; + } + + size_t position() + { + return _ifs.tellg(); + } + + size_t selected_position() + { + return _ifs.tellg(); + } +}; + } // anonymous namespace namespace EMU { @@ -298,6 +406,13 @@ public: // length test in seconds return m_framePeriod * static_cast<double>(m_frames) * 1.0e-6; } + + inline int64_t frameStart() const { + // returns time in nanoseconds from start of test + auto start = m_framePeriod * static_cast<double>(m_frames); + return static_cast<int64_t>(start * 1.0e3); + } + void addEvent(size_t x, size_t p, double tdop, double taux) { // check if in time boundaries @@ -308,7 +423,7 @@ public: auto y = static_cast<size_t>(p / HISTO_BINS_Y_DENUMERATOR); // determine detector id and check limits - if (x >= DETECTORS || y >= m_stride) + if (x >= DETECTOR_TUBES || y >= m_stride) return; // map the raw detector index to the physical model @@ -318,7 +433,7 @@ public: // longer background chopper rate double ptaux = fmod(taux, m_gatePeriod); if (ptaux >= m_directTaux.first && ptaux <= m_directTaux.second) - xid = xid + DETECTORS; + xid = xid + DETECTOR_TUBES; else if (!(ptaux >= m_analysedTaux.first && ptaux <= m_analysedTaux.second)) return; @@ -366,22 +481,33 @@ protected: const ConvertTOF &m_convertTOF; double m_tofMin; double m_tofMax; + int64_t m_startTime; bool m_saveAsTOF; void addEventImpl(size_t id, size_t x, size_t, double tobs) override { - // convert observation time to tof + // get the absolute time for the start of the frame + auto offset = m_startTime + frameStart(); + + // convert observation time to tof and set the pulse time + // relative to the start of the doppler cycle double tof = tobs; - if (m_saveAsTOF) - tof = x < DETECTORS ? m_convertTOF.analysedTOF(id, tobs) - : m_convertTOF.directTOF(id, tobs); + double pulse; + if (m_saveAsTOF) { + if (x < DETECTOR_TUBES) + std::tie(pulse, tof) = m_convertTOF.analysedTOF(id, tobs); + else + std::tie(pulse, tof) = m_convertTOF.directTOF(id, tobs); + offset += static_cast<int64_t>(pulse * 1e3); + } if (m_tofMin > tof) m_tofMin = tof; if (m_tofMax < tof) m_tofMax = tof; - m_eventVectors[id]->push_back(tof); + auto ev = Types::Event::TofEvent(tof, Types::Core::DateAndTime(offset)); + m_eventVectors[id]->push_back(ev); } public: @@ -390,12 +516,14 @@ public: const double framePeriod, const double gatePeriod, const TimeLimits &timeBoundary, const TimeLimits &directLimits, const TimeLimits &analysedLimits, ConvertTOF &convert, - std::vector<EventVector_pt> &eventVectors, bool saveAsTOF) + std::vector<EventVector_pt> &eventVectors, int64_t startTime, + bool saveAsTOF) : EventProcessor(roi, mapIndex, stride, framePeriod, gatePeriod, timeBoundary, directLimits, analysedLimits), m_eventVectors(eventVectors), m_convertTOF(convert), m_tofMin(std::numeric_limits<double>::max()), - m_tofMax(std::numeric_limits<double>::min()), m_saveAsTOF(saveAsTOF) {} + m_tofMax(std::numeric_limits<double>::min()), + m_startTime(startTime), m_saveAsTOF(saveAsTOF) {} double tofMin() const { return m_tofMin <= m_tofMax ? m_tofMin : 0.0; } double tofMax() const { return m_tofMin <= m_tofMax ? m_tofMax : 0.0; } @@ -403,7 +531,24 @@ public: } // namespace EMU // register the algorithm into the AlgorithmFactory -DECLARE_FILELOADER_ALGORITHM(LoadEMU) +DECLARE_FILELOADER_ALGORITHM(LoadEMUTar) +DECLARE_NEXUS_FILELOADER_ALGORITHM(LoadEMUHdf) + +const std::string LoadEMU<Kernel::FileDescriptor>::name() const { + return "LoadEMUTar"; +} + +const std::string LoadEMU<Kernel::FileDescriptor>::summary() const { + return "Loads a merged EMU Hdf and event file into a workspace."; +} + +const std::string LoadEMU<Kernel::NexusDescriptor>::name() const { + return "LoadEMUHdf"; +} + +const std::string LoadEMU<Kernel::NexusDescriptor>::summary() const { + return "Loads an EMU Hdf and linked event file into a workspace."; +} /** * Return the confidence value that this algorithm can load the file @@ -411,7 +556,8 @@ DECLARE_FILELOADER_ALGORITHM(LoadEMU) * @returns An integer specifying the confidence level. 0 indicates it will not * be used */ -int LoadEMU::confidence(Kernel::FileDescriptor &descriptor) const { +template <> +int LoadEMU<Kernel::FileDescriptor>::confidence(Kernel::FileDescriptor &descriptor) const { if (descriptor.extension() != ".tar") return 0; @@ -437,23 +583,77 @@ int LoadEMU::confidence(Kernel::FileDescriptor &descriptor) const { return (hdfFiles == 1) && (binFiles == 1) ? 50 : 0; } +/** +* Return the confidence value that this algorithm can load the file +* @param descriptor A descriptor for the file +* @returns An integer specifying the confidence level. 0 indicates it will not +* be used +*/ +template <> +int LoadEMU<Kernel::NexusDescriptor>::confidence(Kernel::NexusDescriptor &descriptor) const { + if (descriptor.extension() != ".hdf") + return 0; + + if (descriptor.pathExists("/entry1/site_name") && + descriptor.pathExists("/entry1/instrument/doppler/ctrl/velocity") && + descriptor.pathExists("/entry1/instrument/doppler/ctrl/amplitude") && + descriptor.pathExists("/entry1/instrument/detector/daq_dirname") && + descriptor.pathExists("/entry1/instrument/detector/dataset_number") && + descriptor.pathExists("/entry1/data/hmm_total_t_ds0") && + descriptor.pathExists("/entry1/data/hmm_total_t_ds1") && + descriptor.pathExists("/entry1/data/hmm_total_xt_ds0") && + descriptor.pathExists("/entry1/data/hmm_total_xt_ds1")) { + return 80; + } + else { + return 0; + } +} + /** * Initialise the algorithm. Declare properties which can be set before - * execution (input) and - * read from after the execution (output). + * execution (input) and read from after the execution (output). + */ +template <> +void LoadEMU<Kernel::FileDescriptor>::init() { + init(false); +} + +/** +* Initialise the algorithm. Declare properties which can be set before +* execution (input) and read from after the execution (output). +*/ +template <> +void LoadEMU<Kernel::NexusDescriptor>::init() { + init(true); +} + +/** + * Declares the properties for the two loader variants. Adds the path option + * path to the binary file if it is the hdf loader. */ -void LoadEMU::init() { +template <typename FD> +void LoadEMU<FD>::init(bool hdfLoader) { // Specify file extensions which can be associated with a specific file. std::vector<std::string> exts; // Declare the Filename algorithm property. Mandatory. Sets the path to the // file to load. exts.clear(); - exts.emplace_back(".tar"); + if (hdfLoader) + exts.emplace_back(".hdf"); + else + exts.emplace_back(".tar"); declareProperty(Kernel::make_unique<API::FileProperty>( FilenameStr, "", API::FileProperty::Load, exts), "The input filename of the stored data"); + if (hdfLoader) { + declareProperty(PathToBinaryStr, "", + "Relative or absolute path to the compressed binary\n" + "event file linked to the HDF file, eg /storage/data/"); + } + // mask exts.clear(); exts.emplace_back(".xml"); @@ -463,17 +663,29 @@ void LoadEMU::init() { declareProperty(SelectDetectorTubesStr, "", "Comma separated range of detectors tubes to be loaded,\n" - " e.g. 16,19-45,47"); + " eg 16,19-45,47"); declareProperty( Kernel::make_unique<API::WorkspaceProperty<API::IEventWorkspace>>( "OutputWorkspace", "", Kernel::Direction::Output)); + if (hdfLoader) { + declareProperty(SelectDatasetStr, 0, + "Select the index for the dataset to be loaded."); + } + + declareProperty(OverrideDopplerFreqStr, EMPTY_DBL(), + "Override the Doppler frequency, in Hertz."); + declareProperty(OverrideDopplerPhaseStr, EMPTY_DBL(), "Override the Doppler phase, in degrees."); + declareProperty(CalibrateDopplerPhaseStr, false, + "Calibrate the Doppler phase prior to TOF conversion,\n" + "ignored if imported as Doppler time or phase entered"); + declareProperty(RawDopplerTimeStr, false, - "Import file as observed time relative the Doppler " + "Import file as observed time relative the Doppler\n" "drive, in microsecs."); declareProperty(FilterByTimeStartStr, 0.0, @@ -492,7 +704,8 @@ void LoadEMU::init() { /** * Creates an event workspace and sets the title. */ -void LoadEMU::createWorkspace(ANSTO::Tar::File &tarFile) { +template <typename FD> +void LoadEMU<FD>::createWorkspace(const std::string &title) { // Create the workspace m_localWorkspace = boost::make_shared<DataObjects::EventWorkspace>(); @@ -504,53 +717,162 @@ void LoadEMU::createWorkspace(ANSTO::Tar::File &tarFile) { m_localWorkspace->setYUnit("Counts"); // set title - const std::vector<std::string> &subFiles = tarFile.files(); - for (const auto &subFile : subFiles) - if (subFile.compare(0, 3, "EMU") == 0) { - std::string title = subFile; + m_localWorkspace->setTitle(title); +} - if (title.rfind(".hdf") == title.length() - 4) - title.resize(title.length() - 4); +/** + * Execute the algorithm. Extracts the hdf and event file from the tar + * and invokes the invokes the common exec() function that works with + * the two files. + */ +template <> +void LoadEMU<Kernel::FileDescriptor>::exec() { + /** + * Opens the tar and extracts the hdf and event data to temporary files + */ + std::string filename = getPropertyValue(FilenameStr); + ANSTO::Tar::File tarFile(filename); + if (!tarFile.good()) + throw std::invalid_argument("invalid EMU tar file"); - if (title.rfind(".nx") == title.length() - 3) - title.resize(title.length() - 3); + // dataset selection not supported in tar version - order is not guaranteed + m_datasetIndex = 0; - m_localWorkspace->setTitle(title); - break; - } + // lambda functions to find the first file of extension and to extract + // the file + const std::vector<std::string> &files = tarFile.files(); + auto selectFile = [&](const std::string& ext) { + auto itf = + std::find_if(files.cbegin(), files.cend(), [&ext](const std::string &file) { + return file.rfind(ext) == file.length() - 4; + }); + if (itf == files.end()) + throw std::runtime_error("missing tar file data"); + else + tarFile.select(itf->c_str()); + }; + auto extractFile = [&](Poco::TemporaryFile& tfile) { + + boost::shared_ptr<FILE> handle(fopen(tfile.path().c_str(), "wb"), fclose); + if (handle) { + // copy content + char buffer[4096]; + size_t bytesRead; + while (0 != (bytesRead = tarFile.read(buffer, sizeof(buffer)))) + fwrite(buffer, bytesRead, 1, handle.get()); + handle.reset(); + } + }; + + // extract hdf file into tmp file + selectFile(".hdf"); + Poco::TemporaryFile hdfFile; + extractFile(hdfFile); + + // extract the event file + selectFile(".bin"); + Poco::TemporaryFile eventFile; + extractFile(eventFile); + + // call the common loader + exec(hdfFile.path(), eventFile.path()); } /** - * Execute the algorithm. The steps involved are: - * Get the instrument properties and load options - * Create the workspace - * Load the instrument from the IDF - * Reposition the relevant neutronic values for model based on the parameters - * Load the data values and convert to TOF - * Setting up the masks - */ -void LoadEMU::exec() { +* Execute the algorithm. Establishes the filepath to the event file +* from the HDF link and the path provided and invokes the common +* exec() function that works with the two files. +*/ +template <> +void LoadEMU<Kernel::NexusDescriptor>::exec() { + + namespace fs = boost::filesystem; + + // Open the hdf file and find the dirname and dataset number + std::string hdfFile = getPropertyValue(FilenameStr); + std::string evtPath = getPropertyValue(PathToBinaryStr); + if (evtPath.empty()) + evtPath = "./"; + + // if relative ./ or ../ then append to the directory for the hdf file + if (evtPath.rfind("./") == 0 || evtPath.rfind("../") == 0) { + fs::path hp = hdfFile; + evtPath = fs::canonical(evtPath, hp.parent_path()).generic_string(); + } + + // dataset index to be loaded + m_datasetIndex = getProperty(SelectDatasetStr); + + // if path provided build the file path + if (fs::is_directory(evtPath)) + { + NeXus::NXRoot root(hdfFile); + NeXus::NXEntry entry = root.openFirstEntry(); + auto eventDir = GetNeXusValue<std::string>(entry, + "instrument/detector/daq_dirname", "./", 0); + auto dataset = GetNeXusValue<int32_t>(entry, + "instrument/detector/dataset_number", 0, + m_datasetIndex); + + // build the path to the event file as if a relative or absolute + // path is passed: + // 'relpath/[daq_dirname]/DATASET_[n]/EOS.bin' or the + char buffer[255] = {}; + snprintf(buffer, sizeof(buffer), "%s/DATASET_%d/EOS.bin", + eventDir.c_str(), dataset); + fs::path path = evtPath; + path /= buffer; + path = fs::absolute(path); + evtPath = path.generic_string(); + } + + // finally check that the event file exists + if (!fs::is_regular_file(evtPath)) { + std::string msg = "Check path, cannot open binary event file: " + evtPath; + throw std::runtime_error(msg); + } + + exec(hdfFile, evtPath); +} + +/** +* Execute the algorithm. The steps involved are: +* Create the workspace +* Get the instrument properties and load options +* Load the instrument from the IDF +* Reposition the relevant neutronic values for model based on the parameters +* Load the data values and convert to TOF +* Setting up the masks +*/ +template <typename FD> +void LoadEMU<FD>::exec(const std::string& hdfFile, const std::string& eventFile) { + + namespace fs = boost::filesystem; // Create workspace // ---------------- - std::string filename = getPropertyValue(FilenameStr); - ANSTO::Tar::File tarFile(filename); - if (!tarFile.good()) - throw std::invalid_argument("invalid EMU file"); - createWorkspace(tarFile); + fs::path p = hdfFile; + for (; !p.extension().empty();) + p = p.stem(); + std::string title = p.generic_string(); + createWorkspace(title); API::LogManager &logManager = m_localWorkspace->mutableRun(); API::Progress prog(this, 0.0, 1.0, Progress_Total); // Load instrument and workspace properties // ---------------------------------------- - loadParameters(tarFile, logManager); + logManager.addProperty(SelectDatasetStr, m_datasetIndex); + loadParameters(hdfFile, logManager); prog.doReport("creating instrument"); loadInstrument(); - // Get the region of interest and filters + // Get the region of interest and filters and save to log // std::string maskfile = getPropertyValue(MaskStr); std::string seltubes = getPropertyValue(SelectDetectorTubesStr); + logManager.addProperty(SelectDetectorTubesStr, seltubes); + logManager.addProperty(MaskStr, maskfile); + std::vector<bool> roi = createRoiVector(seltubes, maskfile); double timeMaxBoundary = getProperty(FilterByTimeStopStr); if (isEmpty(timeMaxBoundary)) @@ -568,14 +890,14 @@ void LoadEMU::exec() { // sequence starting from 0 // double sampleAnalyser = iparam("SampleAnalyser"); - auto endID = static_cast<detid_t>(DETECTORS * PIXELS_PER_TUBE); + auto endID = static_cast<detid_t>(DETECTOR_TUBES * PIXELS_PER_TUBE); for (detid_t detID = 0; detID < endID; detID++) updateNeutronicPostions(detID, sampleAnalyser); // get the detector map from raw input to a physical detector // std::string dmapStr = instr->getParameterAsString("DetectorMap"); - std::vector<size_t> detMapIndex = std::vector<size_t>(DETECTORS, 0); + std::vector<size_t> detMapIndex = std::vector<size_t>(DETECTOR_TUBES, 0); mapRangeToIndex(dmapStr, detMapIndex, [](size_t n) { return n; }); // Collect the L2 distances, Doppler characteristics and @@ -587,12 +909,8 @@ void LoadEMU::exec() { double framePeriod = 1.0e6 / m_dopplerFreq; // period and max direct as microsec double sourceSample = iparam("SourceSample"); - ConvertTOF convertTOF(m_dopplerAmpl, m_dopplerFreq, m_dopplerPhase, - sourceSample, v2, m_detectorL2); - - Types::Core::DateAndTime start_time( - logManager.getPropertyValueAsType<std::string>("StartTime")); - std::string time_str = start_time.toISO8601String(); + ConvertTOF convertTOF(m_dopplerAmpl * m_dopplerRun, m_dopplerFreq, + m_dopplerPhase, sourceSample, v2, m_detectorL2); // Load the events file // -------------------- @@ -614,48 +932,73 @@ void LoadEMU::exec() { 1000.0 * iparam("AnalysedTauxMax")); // fabs because the value can be negative - double gatePeriod = 1.0e6 / fabs(logManager.getPropertyValueAsType<double>( - "GraphiteChopperFrequency")); + double gatePeriod = 1.0e6 / fabs(logManager.getTimeSeriesProperty<double>( + "GraphiteChopperFrequency")->firstValue()); // count total events per pixel and reserve necessary memory EMU::EventCounter eventCounter(roi, detMapIndex, PIXELS_PER_TUBE, framePeriod, gatePeriod, timeBoundary, directLimits, analysedLimits, eventCounts); - loadEvents(prog, "loading neutron counts", tarFile, eventCounter); + loadEvents(prog, "loading neutron counts", eventFile, eventCounter); ANSTO::ProgressTracker progTracker(prog, "creating neutron event lists", numberHistograms, Progress_ReserveMemory); prepareEventStorage(progTracker, eventCounts, eventVectors); - // now perfrom the actual event collection and TOF convert if necessary - bool saveAsTOF = !getProperty(RawDopplerTimeStr); + // now perform the actual event collection and TOF convert if necessary + // if a phase calibration is required then load it as raw doppler time + // perform the calibration and then convert to TOF + Types::Core::DateAndTime startTime(m_startRun); + auto start_nanosec = startTime.totalNanoseconds(); + bool saveAsTOF = !getProperty(RawDopplerTimeStr); + bool loadAsTOF = !m_calibrateDoppler && saveAsTOF; EMU::EventAssigner eventAssigner( roi, detMapIndex, PIXELS_PER_TUBE, framePeriod, gatePeriod, timeBoundary, - directLimits, analysedLimits, convertTOF, eventVectors, saveAsTOF); - loadEvents(prog, "loading neutron events (TOF)", tarFile, eventAssigner); + directLimits, analysedLimits, convertTOF, eventVectors, + start_nanosec, loadAsTOF); + loadEvents(prog, "loading neutron events (TOF)", eventFile, eventAssigner); + + // perform a calibration and then TOF conversion if necessary + // and update the tof limits + auto minTOF = eventAssigner.tofMin(); + auto maxTOF = eventAssigner.tofMax(); + if (m_calibrateDoppler) { + calibrateDopplerPhase(eventCounts, eventVectors); + if (saveAsTOF) { + dopplerTimeToTOF(eventVectors, minTOF, maxTOF); + } + } + AddSinglePointTimeSeriesProperty<double>(logManager, m_startRun, + "DopplerPhase", m_dopplerPhase); // just to make sure the bins hold it all and setup the detector masks m_localWorkspace->setAllX( - HistogramData::BinEdges{std::max(0.0, floor(eventAssigner.tofMin())), - eventAssigner.tofMax() + 1}); + HistogramData::BinEdges{std::max(0.0, floor(minTOF)), maxTOF + 1}); setupDetectorMasks(roi); // set log values auto frame_count = static_cast<int>(eventCounter.numFrames()); + AddSinglePointTimeSeriesProperty<int>(logManager, m_startRun, + "frame_count", frame_count); + std::string filename = getPropertyValue(FilenameStr); logManager.addProperty("filename", filename); - logManager.addProperty("frame_count", frame_count); Types::Core::time_duration duration = boost::posix_time::microseconds( static_cast<boost::int64_t>(eventCounter.duration() * 1.0e6)); - Types::Core::DateAndTime end_time(start_time + duration); - logManager.addProperty("start_time", start_time.toISO8601String()); - logManager.addProperty("end_time", end_time.toISO8601String()); + Types::Core::DateAndTime endTime(startTime + duration); + logManager.addProperty("start_time", startTime.toISO8601String()); + logManager.addProperty("end_time", endTime.toISO8601String()); + logManager.addProperty<double>("dur", eventCounter.duration()); + + // Finally add the time-series parameter explicitly + loadEnvironParameters(hdfFile, logManager); setProperty("OutputWorkspace", m_localWorkspace); } // set up the detector masks -void LoadEMU::setupDetectorMasks(std::vector<bool> &roi) { +template <typename FD> +void LoadEMU<FD>::setupDetectorMasks(std::vector<bool> &roi) { // count total number of masked bins size_t maskedBins = 0; @@ -680,7 +1023,8 @@ void LoadEMU::setupDetectorMasks(std::vector<bool> &roi) { } // prepare the event storage -void LoadEMU::prepareEventStorage(ANSTO::ProgressTracker &progTracker, +template <typename FD> +void LoadEMU<FD>::prepareEventStorage(ANSTO::ProgressTracker &progTracker, std::vector<size_t> &eventCounts, std::vector<EventVector_pt> &eventVectors) { @@ -702,12 +1046,23 @@ void LoadEMU::prepareEventStorage(ANSTO::ProgressTracker &progTracker, } // get and log the Doppler parameters -void LoadEMU::loadDopplerParameters(API::LogManager &logm) { +template <typename FD> +void LoadEMU<FD>::loadDopplerParameters(API::LogManager &logm) { auto instr = m_localWorkspace->getInstrument(); - m_dopplerFreq = logm.getPropertyValueAsType<double>("DopplerFrequency"); - m_dopplerAmpl = logm.getPropertyValueAsType<double>("DopplerAmplitude"); + + // use nominal frequency based on amp and velocity unless overridden + m_dopplerAmpl = logm.getTimeSeriesProperty<double>("DopplerAmplitude")->firstValue(); + m_dopplerRun = logm.getTimeSeriesProperty<int32_t>("DopplerRun")->firstValue(); + m_dopplerFreq = getProperty(OverrideDopplerFreqStr); + if (isEmpty(m_dopplerFreq)) { + auto doppVel = logm.getTimeSeriesProperty<double>("DopplerVelocity")->firstValue(); + m_dopplerFreq = 0.5 * doppVel / (M_PI * m_dopplerAmpl); + } + AddSinglePointTimeSeriesProperty<double>(logm, m_startRun, "DopplerFrequency", m_dopplerFreq); + m_dopplerPhase = getProperty(OverrideDopplerPhaseStr); + m_calibrateDoppler = getProperty(CalibrateDopplerPhaseStr) && isEmpty(m_dopplerPhase); if (isEmpty(m_dopplerPhase)) { // sinusoidal motion crossing a threshold with a delay double doppThreshold = @@ -717,11 +1072,135 @@ void LoadEMU::loadDopplerParameters(API::LogManager &logm) { 180.0 - asin(0.001 * doppThreshold / m_dopplerAmpl) * 180.0 / M_PI + doppDelay * m_dopplerFreq; } - logm.addProperty<double>("DopplerPhase", m_dopplerPhase); + + // problems adding 'bool' to log + int32_t calPhase = 1 ? m_calibrateDoppler : 0; + logm.addProperty("CalibratePhase", calPhase); +} + +// calibrate doppler phase based on teh analysed events +template <typename FD> +void LoadEMU<FD>::calibrateDopplerPhase(std::vector<size_t> &eventCounts, + std::vector<EventVector_pt> &eventVectors) { + + // get the doppler parameters + auto instr = m_localWorkspace->getInstrument(); + double v2 = instr->getNumberParameter("AnalysedV2")[0]; + double l1 = instr->getNumberParameter("SourceSample")[0]; + + // get the number of analysed events and initial doppler time + auto startID = static_cast<size_t>(HORIZONTAL_TUBES * PIXELS_PER_TUBE); + auto endID = static_cast<size_t>(DETECTOR_TUBES * PIXELS_PER_TUBE); + size_t numEvents = 0; + for (size_t i = startID; i < endID; i++) + numEvents += eventCounts[i]; + if (numEvents == 0) + throw std::runtime_error("no analysed events for phase calibration"); + std::vector<double> nVel(numEvents); + std::vector<size_t> nMap(numEvents); + std::vector<bool> nCnd(numEvents); + constexpr size_t NHIST = 100; + std::vector<int> histogram(NHIST+1, 0); + + // define the cost function to optimize phase + auto costFn = [&, this](double phase) { + + ConvertTOF convTOF(m_dopplerAmpl * m_dopplerRun, m_dopplerFreq, phase, + l1, v2, m_detectorL2); + + // convert each analysed event to source velocity + size_t ix = 0; + double tof, pulse; + for (size_t i = startID; i < endID; i++) { + for (auto const& x : *eventVectors[i]) { + std::tie(pulse, tof) = convTOF.analysedTOF(i, x.tof()); + auto tof1 = 1e-6 * tof - m_detectorL2[i] / v2; + nVel[ix++] = l1 / tof1; + } + } + + // now histogram the data and create the map from velocity to hist + auto ixlim = std::minmax_element(nVel.begin(), nVel.end()); + auto vmin = nVel[ixlim.first - nVel.begin()]; + auto vmax = nVel[ixlim.second - nVel.begin()]; + int maxHist = 0; + std::fill(histogram.begin(), histogram.end(), 0); + auto delta = (vmax - vmin) / NHIST; + for (int i = 0; i < numEvents; i++) { + auto v = nVel[i]; + auto ix = static_cast<size_t>(std::floor((v - vmin) / delta)); + histogram[ix]++; + if (histogram[ix] > maxHist) + maxHist = histogram[ix]; + nMap[i] = ix; + } + + // determine the points above the 25% threshold + int minLevel = static_cast<int>(maxHist / 4); + for (size_t i = 0; i < numEvents; i++) { + nCnd[i] = (histogram[nMap[i]] >= minLevel ? true : false); + } + + // calculate the standard deviation for the points above the threshold + auto cost = maskedStdev(nVel, nCnd); + return cost; + }; + + // call the optimizer and update the doppler phase value + // limit the optimization to 30 iterations + int bits = std::numeric_limits<double>::digits; + boost::uintmax_t itn = 30; + using boost::math::tools::brent_find_minima; + auto minPhase = m_dopplerPhase - 5.0; + auto maxPhase = m_dopplerPhase + 5.0; + auto r = brent_find_minima(costFn, minPhase, maxPhase, bits, itn); + m_dopplerPhase = r.first; +} + +// convert the doppler time to TOF for all the events in the workspace +template <typename FD> +void LoadEMU<FD>::dopplerTimeToTOF(std::vector<EventVector_pt> &eventVectors, + double& minTOF, double& maxTOF) { + + // get the doppler parameters and initialise TOD converter + auto instr = m_localWorkspace->getInstrument(); + double v2 = instr->getNumberParameter("AnalysedV2")[0]; + double l1 = instr->getNumberParameter("SourceSample")[0]; + ConvertTOF convTOF(m_dopplerAmpl * m_dopplerRun, m_dopplerFreq, + m_dopplerPhase, l1, v2, m_detectorL2); + + // run through all the events noting that analysed event are in + // the bottom half of the detector ids + auto start = true; + auto directID = static_cast<size_t>(DETECTOR_TUBES * PIXELS_PER_TUBE); + for (size_t id = 0; id < eventVectors.size(); id++) { + for (auto &x : *eventVectors[id]) { + double tof, pulse; + if (id < directID) + std::tie(pulse, tof) = convTOF.analysedTOF(id, x.tof()); + else + std::tie(pulse, tof) = convTOF.directTOF(id, x.tof()); + + // update the pulse time and tof + int64_t pulseTime = x.pulseTime().totalNanoseconds(); + pulseTime += static_cast<int64_t>(pulse * 1000); + x = Types::Event::TofEvent(tof, Types::Core::DateAndTime(pulseTime)); + + if (start) { + minTOF = maxTOF = x.tof(); + start = false; + } + else { + minTOF = std::min(minTOF, x.tof()); + maxTOF = std::max(maxTOF, x.tof()); + } + } + } } // Recovers the L2 neutronic distance for each detector. -void LoadEMU::loadDetectorL2Values() { +template <typename FD> +void LoadEMU<FD>::loadDetectorL2Values() { m_detectorL2 = std::vector<double>(HISTOGRAMS); const auto &detectorInfo = m_localWorkspace->detectorInfo(); @@ -734,14 +1213,8 @@ void LoadEMU::loadDetectorL2Values() { } // update the neutronic positins -void LoadEMU::updateNeutronicPostions(detid_t detID, double sampleAnalyser) { - - // get the instrument - - // get the list of indirect horizontal detectors - - // for each detector get the current position and scale - // the detectors +template <typename FD> +void LoadEMU<FD>::updateNeutronicPostions(detid_t detID, double sampleAnalyser) { Geometry::Instrument_const_sptr instrument = m_localWorkspace->getInstrument(); @@ -765,7 +1238,8 @@ void LoadEMU::updateNeutronicPostions(detid_t detID, double sampleAnalyser) { // region of interest is defined by the selected detectors and the mask // -std::vector<bool> LoadEMU::createRoiVector(const std::string &selected, +template <typename FD> +std::vector<bool> LoadEMU<FD>::createRoiVector(const std::string &selected, const std::string &maskfile) { std::vector<bool> result(HISTOGRAMS, true); @@ -804,83 +1278,88 @@ std::vector<bool> LoadEMU::createRoiVector(const std::string &selected, return result; } -// load parameters from input file -void LoadEMU::loadParameters(ANSTO::Tar::File &tarFile, API::LogManager &logm) { +// load parameters from input hdf file +template <typename FD> +void LoadEMU<FD>::loadParameters(const std::string& hdfFile, API::LogManager &logm) { - // extract log and hdf file - const std::vector<std::string> &files = tarFile.files(); - auto file_it = - std::find_if(files.cbegin(), files.cend(), [](const std::string &file) { - return file.rfind(".hdf") == file.length() - 4; - }); - if (file_it != files.end()) { - tarFile.select(file_it->c_str()); - // extract hdf file into tmp file - Poco::TemporaryFile hdfFile; - boost::shared_ptr<FILE> handle(fopen(hdfFile.path().c_str(), "wb"), fclose); - if (handle) { - // copy content - char buffer[4096]; - size_t bytesRead; - while (0 != (bytesRead = tarFile.read(buffer, sizeof(buffer)))) - fwrite(buffer, bytesRead, 1, handle.get()); - handle.reset(); - - NeXus::NXRoot root(hdfFile.path()); + NeXus::NXRoot root(hdfFile); NeXus::NXEntry entry = root.openFirstEntry(); MapNeXusToProperty<std::string>(entry, "sample/name", "unknown", logm, - "SampleName", ""); + "SampleName", "", 0); MapNeXusToProperty<std::string>(entry, "sample/description", "unknown", - logm, "SampleDescription", ""); - MapNeXusToProperty<std::string>( - entry, "start_time", "2000-01-01T00:00:00", logm, "StartTime", ""); - - MapNeXusToProperty<double>(entry, "instrument/doppler/ctrl/amplitude", - 75.0, logm, "DopplerAmplitude", 0.001); - double speedToFreq = - 0.5 / (M_PI * m_localWorkspace->run().getPropertyValueAsType<double>( - "DopplerAmplitude")); - MapNeXusToProperty<double>(entry, "instrument/doppler/ctrl/velocity", 4.7, - logm, "DopplerFrequency", speedToFreq); - - MapNeXusToProperty<double>(entry, "instrument/chpr/background/actspeed", - 1272.8, logm, "BackgroundChopperFrequency", - 1.0 / 60); - MapNeXusToProperty<double>(entry, "instrument/chpr/graphite/actspeed", - 2545.6, logm, "GraphiteChopperFrequency", - 1.0 / 60); - MapNeXusToProperty<double>(entry, "instrument/hztubegap", 0.02, logm, - "HorizontalTubesGap", 1.0); - MapNeXusToProperty<int32_t>(entry, "monitor/bm1_counts", 0, logm, - "MonitorCounts", 1); - - // temp fix for source position when loading IDF + logm, "SampleDescription", "", 0); + + // if dataset index > 0 need to add an offset to the start time + Types::Core::DateAndTime startTime(GetNeXusValue<std::string>( + entry, "start_time", + "2000-01-01T00:00:00", 0)); + if (m_datasetIndex > 0) { + auto baseTime = GetNeXusValue<int32_t>(entry, + "instrument/detector/start_time", + 0, 0); + auto nthTime = GetNeXusValue<int32_t>(entry, + "instrument/detector/start_time", + 0, m_datasetIndex); + + Types::Core::time_duration duration = boost::posix_time::microseconds( + static_cast<boost::int64_t>((nthTime - baseTime) * 1.0e6)); + Types::Core::DateAndTime startDataset(startTime + duration); + m_startRun = startDataset.toISO8601String(); + } + else { + m_startRun = startTime.toISO8601String(); + } + + MapNeXusToSeries<double>(entry, "instrument/doppler/ctrl/amplitude", + 75.0, logm, m_startRun, "DopplerAmplitude", + 0.001, m_datasetIndex); + MapNeXusToSeries<double>(entry, "instrument/doppler/ctrl/velocity", 4.7, + logm, m_startRun, "DopplerVelocity", 1, m_datasetIndex); + MapNeXusToSeries<int>(entry, "instrument/doppler/ctrl/run_cmd", + 1, logm, m_startRun, "DopplerRun", 1, m_datasetIndex); + + MapNeXusToSeries<double>(entry, "instrument/chpr/background/actspeed", + 1272.8, logm, m_startRun, "BackgroundChopperFrequency", + 1.0 / 60, 0); + MapNeXusToSeries<double>(entry, "instrument/chpr/graphite/actspeed", + 2545.6, logm, m_startRun, "GraphiteChopperFrequency", + 1.0 / 60, 0); + // hz tube gap or equivalent to be added later - reverts to default + MapNeXusToSeries<double>(entry, "instrument/hztubegap", 0.02, logm, m_startRun, + "horizontal_tubes_gap", 1.0, 0); + MapNeXusToSeries<int32_t>(entry, "monitor/bm1_counts", 0, logm, m_startRun, + "MonitorCounts", 1, m_datasetIndex); + + // fix for source position when loading IDF MapNeXusToProperty<double>(entry, "instrument/doppler/tosource", 2.035, - logm, "SourceSample", 1.0); - } - } + logm, "SourceSample", 1.0, 0); +} - // patching - file_it = std::find(files.cbegin(), files.cend(), "History.log"); - if (file_it != files.cend()) { - tarFile.select(file_it->c_str()); - std::string logContent; - logContent.resize(tarFile.selected_size()); - tarFile.read(&logContent[0], logContent.size()); - std::istringstream data(logContent); - Poco::AutoPtr<Poco::Util::PropertyFileConfiguration> conf( - new Poco::Util::PropertyFileConfiguration(data)); - - if (conf->hasProperty("SampleName")) { - std::string name = conf->getString("SampleName"); - logm.addProperty("SampleName", name); - } - } +// the environment variable needs to be loaded at the end +// as the +template <typename FD> +void LoadEMU<FD>::loadEnvironParameters(const std::string &hdfFile, API::LogManager &logm) { + + NeXus::NXRoot root(hdfFile); + NeXus::NXEntry entry = root.openFirstEntry(); + auto time_str = logm.getPropertyValueAsType<std::string>("end_time"); + + // load the environment variables for teh dataset loaded + std::vector<std::string> tags = { + "P01PS03", "P01PSP03", "T01S00", "T01S05", "T01S06", "T01SP00", + "T01SP06", "T02S00", "T02S04", "T02S05", "T02S06", + "T02SP00", "T02SP06" }; + + for (const auto &tag : tags) { + MapNeXusToSeries<double>(entry, "data/" + tag, 0.0, logm, time_str, + "env_" + tag, 1.0, m_datasetIndex); + } } // load the instrument definition and instrument parameters -void LoadEMU::loadInstrument() { +template <typename FD> +void LoadEMU<FD>::loadInstrument() { // loads the IDF and parameter file API::IAlgorithm_sptr loadInstrumentAlg = @@ -893,30 +1372,28 @@ void LoadEMU::loadInstrument() { } // read counts/events from binary file +template <typename FD> template <class EventProcessor> -void LoadEMU::loadEvents(API::Progress &prog, const char *progMsg, - ANSTO::Tar::File &tarFile, +void LoadEMU<FD>::loadEvents(API::Progress &prog, const char *progMsg, + const std::string& eventFile, EventProcessor &eventProcessor) { using namespace ANSTO; prog.doReport(progMsg); - // select bin file - int64_t fileSize = 0; - const std::vector<std::string> &files = tarFile.files(); - for (const auto &file : files) - if (file.rfind(".bin") == file.length() - 4) { - tarFile.select(file.c_str()); - fileSize = tarFile.selected_size(); - break; - } + FileLoader loader(eventFile.c_str()); // for progress notifications - ANSTO::ProgressTracker progTracker(prog, progMsg, fileSize, + ANSTO::ProgressTracker progTracker(prog, progMsg, loader.size(), Progress_LoadBinFile); - ReadEventFile(tarFile, eventProcessor, progTracker, 100, false); + ReadEventFile(loader, eventProcessor, progTracker, 100, false); } + +// finally instantiate the two loaders +template class LoadEMU<Kernel::FileDescriptor>; +template class LoadEMU<Kernel::NexusDescriptor>; + } // namespace DataHandling } // namespace Mantid \ No newline at end of file diff --git a/Framework/DataHandling/test/LoadEMUauTest.h b/Framework/DataHandling/test/LoadEMUauTest.h index 4c9dffc9726..ceb47bd38b0 100644 --- a/Framework/DataHandling/test/LoadEMUauTest.h +++ b/Framework/DataHandling/test/LoadEMUauTest.h @@ -23,14 +23,14 @@ using namespace Mantid::DataObjects; class LoadEMUauTest : public CxxTest::TestSuite { public: void test_load_emu_algorithm_init() { - LoadEMU algToBeTested; + LoadEMUTar algToBeTested; TS_ASSERT_THROWS_NOTHING(algToBeTested.initialize()); TS_ASSERT(algToBeTested.isInitialized()); } void test_load_emu_algorithm() { - LoadEMU algToBeTested; + LoadEMUTar algToBeTested; if (!algToBeTested.isInitialized()) algToBeTested.initialize(); @@ -72,7 +72,8 @@ public: // test some data properties auto logpm = [&run](std::string tag) { - return run.getPropertyValueAsType<double>(tag); + return dynamic_cast<TimeSeriesProperty<double> *>( + run.getProperty(tag))->firstValue(); }; TS_ASSERT_DELTA(logpm("DopplerFrequency"), 9.974, 1.0e-3); TS_ASSERT_DELTA(logpm("DopplerAmplitude"), 0.075, 1.0e-4); diff --git a/Framework/DataObjects/inc/MantidDataObjects/EventList.h b/Framework/DataObjects/inc/MantidDataObjects/EventList.h index 22e967eaa25..e6efdb71d92 100644 --- a/Framework/DataObjects/inc/MantidDataObjects/EventList.h +++ b/Framework/DataObjects/inc/MantidDataObjects/EventList.h @@ -244,6 +244,7 @@ public: void addPulsetime(const double seconds) override; void maskTof(const double tofMin, const double tofMax) override; + void maskCondition(const std::vector<bool> & mask) override; void getTofs(std::vector<double> &tofs) const override; double getTofMin() const override; @@ -456,6 +457,10 @@ private: static std::size_t maskTofHelper(std::vector<T> &events, const double tofMin, const double tofMax); template <class T> + static std::size_t maskConditionHelper(std::vector<T> &events, + const std::vector<bool> &mask); + + template <class T> static void getTofsHelper(const std::vector<T> &events, std::vector<double> &tofs); template <class T> diff --git a/Framework/DataObjects/src/EventList.cpp b/Framework/DataObjects/src/EventList.cpp index 4cef5659ab2..cb1ad8aff2b 100644 --- a/Framework/DataObjects/src/EventList.cpp +++ b/Framework/DataObjects/src/EventList.cpp @@ -2695,6 +2695,92 @@ void EventList::maskTof(const double tofMin, const double tofMax) { this->clear(false); } +// -------------------------------------------------------------------------- +/** Mask out events by the condition vector. +* Events are removed from the list. +* @param events :: reference to a vector of events to change. +* @param mask :: condition vector +* @returns The number of events deleted. +*/ +template <class T> +std::size_t EventList::maskConditionHelper(std::vector<T> &events, + const std::vector<bool> &mask) { + + // runs through the two synchronized vectors and delete elements + // for condition false + auto itm = std::find(mask.begin(), mask.end(), false); + auto first = events.begin() + (itm - mask.begin()); + + if (itm != mask.end()) { + for (auto ite = first; ++ite != events.end() && ++itm != mask.end();) { + if (*itm != false) { + *first++ = std::move(*ite); + } + } + } + + auto n = events.end() - first; + if (n != 0) + events.erase(first, events.end()); + + return n; + + /* + size_t count = 0; + auto it_e = events.begin(); + auto it_m = mask.begin(); + while (it_e != events.end() && it_m != mask.end()) { + if (*it_m) { + ++it_e; + } + else { + it_e = events.erase(it_e); + count++; + } + ++it_m; + } + return count; + */ +} + +// -------------------------------------------------------------------------- +/** +* Mask out events by the condition vector. +* Events are removed from the list. +* @param mask :: condition vector +*/ +void EventList::maskCondition(const std::vector<bool> & mask) { + + // mask size must match the number of events + if (this->getNumberEvents() != mask.size()) + throw std::runtime_error("EventList::maskTof: tofMax must be > tofMin"); + + // don't do anything with an emply list + if (this->getNumberEvents() == 0) + return; + + // Convert the list + size_t numOrig = 0; + size_t numDel = 0; + switch (eventType) { + case TOF: + numOrig = this->events.size(); + numDel = this->maskConditionHelper(this->events, mask); + break; + case WEIGHTED: + numOrig = this->weightedEvents.size(); + numDel = this->maskConditionHelper(this->weightedEvents, mask); + break; + case WEIGHTED_NOTIME: + numOrig = this->weightedEventsNoTime.size(); + numDel = this->maskConditionHelper(this->weightedEventsNoTime, mask); + break; + } + + if (numDel >= numOrig) + this->clear(false); +} + // -------------------------------------------------------------------------- /** Get the m_tof member of all events in a list * diff --git a/Framework/DataObjects/test/EventListTest.h b/Framework/DataObjects/test/EventListTest.h index d29c5ac56fd..ffc6ac593c1 100644 --- a/Framework/DataObjects/test/EventListTest.h +++ b/Framework/DataObjects/test/EventListTest.h @@ -1314,6 +1314,41 @@ public: } } + //----------------------------------------------------------------------------------------------- + void test_maskCondition_allTypes() { + // Go through each possible EventType as the input + for (int this_type = 0; this_type < 3; this_type++) { + this->fake_uniform_data(); + el.switchTo(static_cast<EventType>(this_type)); + + // tof steps of 5 microseconds, starting at 100 ns, up to 20 msec + // How many events did we make? + TS_ASSERT_EQUALS(el.getNumberEvents(), 2 * MAX_TOF / BIN_DELTA); + + // Mask out 5-10 milliseconds + auto nlen = el.getNumberEvents(); + std::vector<bool> mask(nlen, true); + + // first check no removal + el.maskCondition(mask); + TS_ASSERT_EQUALS(el.getNumberEvents(), 2 * MAX_TOF / BIN_DELTA); + + double min = MAX_TOF * 0.25; + double max = MAX_TOF * 0.5; + for (size_t i = 0; i < nlen; i++) { + if ((el.getEvent(i).tof() >= min) && (el.getEvent(i).tof() <= max)) { + mask[i] = false; + } + } + el.maskCondition(mask); + for (std::size_t i = 0; i < el.getNumberEvents(); i++) { + // No tofs in that range + TS_ASSERT((el.getEvent(i).tof() < min) || (el.getEvent(i).tof() > max)); + } + TS_ASSERT_EQUALS(el.getNumberEvents(), 0.75 * 2 * MAX_TOF / BIN_DELTA); + } + } + //----------------------------------------------------------------------------------------------- void test_getTofs_and_setTofs() { // Go through each possible EventType as the input diff --git a/Framework/PythonInterface/mantid/api/src/Exports/IEventList.cpp b/Framework/PythonInterface/mantid/api/src/Exports/IEventList.cpp index 006e403de80..92581c82e27 100644 --- a/Framework/PythonInterface/mantid/api/src/Exports/IEventList.cpp +++ b/Framework/PythonInterface/mantid/api/src/Exports/IEventList.cpp @@ -7,6 +7,7 @@ #include "MantidAPI/IEventList.h" #include "MantidPythonInterface/kernel/GetPointer.h" #include "MantidPythonInterface/kernel/Policies/VectorToNumpy.h" +#include "MantidPythonInterface/kernel/Converters/NDArrayToVector.h" #include <boost/python/class.hpp> #include <boost/python/enum.hpp> #include <boost/python/register_ptr_to_python.hpp> @@ -18,11 +19,16 @@ using Mantid::API::TOF; using Mantid::API::WEIGHTED; using Mantid::API::WEIGHTED_NOTIME; -namespace Policies = Mantid::PythonInterface::Policies; +using namespace Mantid::PythonInterface; using namespace boost::python; GET_POINTER_SPECIALIZATION(IEventList) +namespace { +void maskCondition(IEventList &self, const NDArray &data) { + self.maskCondition(Converters::NDArrayToVector<bool>(data)()); +} +} /// return_value_policy for copied numpy array using return_clone_numpy = return_value_policy<Policies::VectorToNumpy>; @@ -67,6 +73,8 @@ void export_IEventList() { .def("maskTof", &IEventList::maskTof, args("self", "tofMin", "tofMax"), "Mask out events that have a tof between tofMin and tofMax " "(inclusively)") + .def("maskCondition", &maskCondition, args("self", "mask"), + "Mask out events by the condition vector") .def("getTofs", (std::vector<double>(IEventList::*)(void) const) & IEventList::getTofs, @@ -84,6 +92,10 @@ void export_IEventList() { "Get a vector of the weights of the events") .def("getPulseTimes", &IEventList::getPulseTimes, args("self"), "Get a vector of the pulse times of the events") + .def("getPulseTimeMax", &IEventList::getPulseTimeMax, args("self"), + "The maximum pulse time for the list of the events.") + .def("getPulseTimeMin", &IEventList::getPulseTimeMin, args("self"), + "The minimum pulse time for the list of the events.") .def("getTofMin", &IEventList::getTofMin, args("self"), "The minimum tof value for the list of the events.") .def("getTofMax", &IEventList::getTofMax, args("self"), diff --git a/Framework/PythonInterface/mantid/api/src/Exports/IEventWorkspace.cpp b/Framework/PythonInterface/mantid/api/src/Exports/IEventWorkspace.cpp index 291268be925..94a7ded037a 100644 --- a/Framework/PythonInterface/mantid/api/src/Exports/IEventWorkspace.cpp +++ b/Framework/PythonInterface/mantid/api/src/Exports/IEventWorkspace.cpp @@ -45,6 +45,12 @@ void export_IEventWorkspace() { .def("getTofMax", &IEventWorkspace::getTofMax, args("self"), "Returns the maximum TOF value (in microseconds) held by the " ":class:`~mantid.api.Workspace`") + .def("getPulseTimeMin", &IEventWorkspace::getPulseTimeMin, args("self"), + "Returns the minimum pulse time held by the " + ":class:`~mantid.api.Workspace`") + .def("getPulseTimeMax", &IEventWorkspace::getPulseTimeMax, args("self"), + "Returns the maximum pulse time held by the " + ":class:`~mantid.api.Workspace`") .def("getEventList", &deprecatedGetEventList, return_internal_reference<>(), args("self", "workspace_index"), "Return the :class:`~mantid.api.IEventList` managing the events at " -- GitLab