Code owners
Assign users and groups as approvers for specific file changes. Learn more.
LoadISISNexus2.cpp 46.42 KiB
// 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 & CSNS, Institute of High Energy Physics, CAS
// SPDX - License - Identifier: GPL - 3.0 +
//----------------------------------------------------------------------
// Includes
//----------------------------------------------------------------------
#include "MantidDataHandling/LoadISISNexus2.h"
#include "MantidDataHandling/DataBlockGenerator.h"
#include "MantidDataHandling/LoadEventNexus.h"
#include "MantidDataHandling/LoadISISNexusHelper.h"
#include "MantidDataHandling/LoadRawHelper.h"
#include "MantidAPI/Axis.h"
#include "MantidAPI/FileProperty.h"
#include "MantidAPI/RegisterFileLoader.h"
#include "MantidAPI/WorkspaceFactory.h"
#include "MantidGeometry/Instrument.h"
#include "MantidGeometry/Instrument/Detector.h"
#include "MantidKernel/ArrayProperty.h"
#include "MantidKernel/BoundedValidator.h"
#include "MantidKernel/ConfigService.h"
#include "MantidKernel/ListValidator.h"
//#include "MantidKernel/LogParser.h"
#include "MantidKernel/LogFilter.h"
#include "MantidKernel/TimeSeriesProperty.h"
#include "MantidKernel/UnitFactory.h"
// clang-format off
#include <nexus/NeXusFile.hpp>
#include <nexus/NeXusException.hpp>
// clang-format on
#include <algorithm>
#include <cmath>
#include <cctype>
#include <climits>
#include <functional>
#include <sstream>
#include <vector>
namespace {
Mantid::DataHandling::DataBlockComposite
getMonitorsFromComposite(Mantid::DataHandling::DataBlockComposite &composite,
Mantid::DataHandling::DataBlockComposite &monitors) {
auto dataBlocks = composite.getDataBlocks();
auto monitorBlocks = monitors.getDataBlocks();
auto matchesMonitorBlock =
[&monitorBlocks](Mantid::DataHandling::DataBlock &dataBlock) {
return std::find(std::begin(monitorBlocks), std::end(monitorBlocks),
dataBlock) != std::end(monitorBlocks);
};
Mantid::DataHandling::DataBlockComposite newComposite;
for (auto &dataBlock : dataBlocks) {
if (matchesMonitorBlock(dataBlock)) {
newComposite.addDataBlock(dataBlock);
}
}
return newComposite;
}
} // namespace
namespace Mantid {
namespace DataHandling {
DECLARE_NEXUS_FILELOADER_ALGORITHM(LoadISISNexus2)
using namespace Kernel;
using namespace API;
using namespace NeXus;
using namespace HistogramData;
using std::size_t;
/// Empty default constructor
LoadISISNexus2::LoadISISNexus2()
: m_filename(), m_instrument_name(), m_samplename(), m_detBlockInfo(),
m_monBlockInfo(), m_loadBlockInfo(), m_have_detector(false),
m_hasVMSBlock(false), m_load_selected_spectra(false),
m_wsInd2specNum_map(), m_spec2det_map(), m_entrynumber(0), m_tof_data(),
m_spec(), m_spec_end(nullptr), m_monitors(), m_logCreator(), m_progress(),
m_nexusFile() {}
/**
* Return the confidence criteria for 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
*/
int LoadISISNexus2::confidence(Kernel::NexusDescriptor &descriptor) const {
if (descriptor.pathOfTypeExists("/raw_data_1", "NXentry")) {
// It also could be an Event Nexus file or a TOFRaw file,
// so confidence is set to less than 80.
return 75;
}
return 0;
}
/// Initialization method.
void LoadISISNexus2::init() {
const std::vector<std::string> exts{".nxs", ".n*"};
declareProperty(
std::make_unique<FileProperty>("Filename", "", FileProperty::Load, exts),
"The name of the Nexus file to load");
declareProperty(std::make_unique<WorkspaceProperty<Workspace>>(
"OutputWorkspace", "", Direction::Output));
auto mustBePositive = std::make_shared<BoundedValidator<int64_t>>();
mustBePositive->setLower(0);
declareProperty("SpectrumMin", static_cast<int64_t>(0), mustBePositive);
declareProperty("SpectrumMax", static_cast<int64_t>(EMPTY_INT()),
mustBePositive);
declareProperty(std::make_unique<ArrayProperty<int64_t>>("SpectrumList"));
declareProperty("EntryNumber", static_cast<int64_t>(0), mustBePositive,
"0 indicates that every entry is loaded, into a separate "
"workspace within a group. "
"A positive number identifies one entry to be loaded, into "
"one worskspace");
std::vector<std::string> monitorOptions{"Include", "Exclude", "Separate"};
std::map<std::string, std::string> monitorOptionsAliases;
monitorOptionsAliases["1"] = "Separate";
monitorOptionsAliases["0"] = "Exclude";
declareProperty(
"LoadMonitors", "Include",
std::make_shared<Kernel::StringListValidator>(monitorOptions,
monitorOptionsAliases),
"Option to control the loading of monitors.\n"
"Allowed options are Include,Exclude, Separate.\n"
"Include:The default is Include option would load monitors with the "
"workspace if monitors spectra are within the range of loaded "
"detectors.\n"
"If the time binning for the monitors is different from the\n"
"binning of the detectors this option is equivalent to the Separate "
"option\n"
"Exclude:Exclude option excludes monitors from the output workspace.\n"
"Separate:Separate option loads monitors into a separate workspace "
"called: OutputWorkspace_monitors.\n"
"Defined aliases:\n"
"1: Equivalent to Separate.\n"
"0: Equivalent to Exclude.\n");
}
/** Executes the algorithm. Reading in the file and creating and populating
* the output workspace
*
* @throw Exception::FileError If the Nexus file cannot be found/opened
* @throw std::invalid_argument If the optional properties are set to invalid
*values
*/
void LoadISISNexus2::exec() {
//**********************************************************************
// process load monitor options request
bool bincludeMonitors, bseparateMonitors, bexcludeMonitors;
LoadRawHelper::ProcessLoadMonitorOptions(bincludeMonitors, bseparateMonitors,
bexcludeMonitors, this);
//**********************************************************************
m_filename = getPropertyValue("Filename");
// Create the root Nexus class
NXRoot root(m_filename);
// "Open" the same file but with the C++ interface
m_nexusFile.reset(new ::NeXus::File(root.m_fileID));
// Open the raw data group 'raw_data_1'
NXEntry entry = root.openEntry("raw_data_1");
// Read in the instrument name from the Nexus file
m_instrument_name = entry.getString("name");
// Test if we have a vms block
if (entry.containsGroup("isis_vms_compat")) {
m_hasVMSBlock = true;
}
// Get number of detectors and spectrum list
size_t ndets{0};
try {
NXClass det_class = entry.openNXGroup("detector_1");
NXInt spectrum_index = det_class.openNXInt("spectrum_index");
spectrum_index.load();
ndets = spectrum_index.dim0();
// We assume that this spectrum list increases monotonically
m_spec = spectrum_index.vecBuffer();
m_spec_end = m_spec.data() + ndets;
m_have_detector = true;
} catch (std::runtime_error &) {
ndets = 0;
}
// Load detector and spectra ids, and number of monitors + detectors?
auto [udet, spec] = LoadISISNexusHelper::findDetectorIDsAndSpectrumNumber(
entry, m_hasVMSBlock);
int64_t nsp1 = LoadISISNexusHelper::findNumberOfSpectra(entry, m_hasVMSBlock);
// Pull out the monitor blocks, if any exist
size_t nmons{0};
for (auto it = entry.groups().cbegin(); it != entry.groups().cend(); ++it) {
if (it->nxclass == "NXmonitor") // Count monitors
{
NXInt index =
entry.openNXInt(std::string(it->nxname) + "/spectrum_index");
index.load();
int64_t ind = *index();
// Spectrum index of 0 means no spectrum associated with that monitor,
// so only count those with index > 0
if (ind > 0) {
m_monitors[ind] = it->nxname;
++nmons;
}
}
}
if (ndets == 0 && nmons == 0) {
if (bexcludeMonitors) {
g_log.warning() << "Nothing to do. No detectors found and no monitor "
"loading requested";
return;
} else {
g_log.error()
<< "Invalid NeXus structure, cannot find detector or monitor blocks.";
throw std::runtime_error("Inconsistent NeXus file structure.");
}
}
// Determine the the data block for the detectors and monitors
bseparateMonitors =
findSpectraDetRangeInFile(entry, m_spec, ndets, nsp1, m_monitors,
bexcludeMonitors, bseparateMonitors);
size_t x_length = m_loadBlockInfo.getNumberOfChannels() + 1;
// Check input is consistent with the file, throwing if not
bseparateMonitors =
checkOptionalProperties(bseparateMonitors, bexcludeMonitors);
// Fill up m_spectraBlocks
size_t total_specs = prepareSpectraBlocks(m_monitors, m_loadBlockInfo);
m_progress = std::make_shared<API::Progress>(
this, 0.0, 1.0, total_specs * m_detBlockInfo.getNumberOfPeriods());
DataObjects::Workspace2D_sptr local_workspace =
std::dynamic_pointer_cast<DataObjects::Workspace2D>(
WorkspaceFactory::Instance().create(
"Workspace2D", total_specs, x_length,
m_loadBlockInfo.getNumberOfChannels()));
// Set the units on the workspace to TOF & Counts
local_workspace->getAxis(0)->unit() = UnitFactory::Instance().create("TOF");
local_workspace->setYUnit("Counts");
// Load instrument and other data once then copy it later
m_progress->report("Loading instrument and run details");
// load run details
loadRunDetails(local_workspace, entry);
// Test if IDF exists in Nexus otherwise load default instrument
bool foundInstrument = LoadEventNexus::runLoadIDFFromNexus(
m_filename, local_workspace, "raw_data_1", this);
if (m_load_selected_spectra)
m_spec2det_map = SpectrumDetectorMapping(spec(), udet(), udet.dim0());
else if (bseparateMonitors) {
m_spec2det_map = SpectrumDetectorMapping(spec(), udet(), udet.dim0());
local_workspace->updateSpectraUsing(m_spec2det_map);
} else {
local_workspace->updateSpectraUsing(
SpectrumDetectorMapping(spec(), udet(), udet.dim0()));
}
if (!foundInstrument) {
runLoadInstrument(local_workspace);
}
// Load logs and sample information
m_nexusFile->openPath(entry.path());
local_workspace->loadSampleAndLogInfoNexus(m_nexusFile.get());
// Load logs and sample information further information... See maintenance
// ticket #8697
loadSampleData(local_workspace, entry);
m_progress->report("Loading logs");
loadLogs(local_workspace);
// Load first period outside loop
m_progress->report("Loading data");
if (ndets > 0) {
// Get the X data
NXFloat timeBins = entry.openNXFloat("detector_1/time_of_flight");
timeBins.load();
m_tof_data =
std::make_shared<HistogramX>(timeBins(), timeBins() + x_length);
}
int64_t firstentry = (m_entrynumber > 0) ? m_entrynumber : 1;
loadPeriodData(firstentry, entry, local_workspace, m_load_selected_spectra);
// Clone the workspace at this point to provide a base object for future
// workspace generation.
DataObjects::Workspace2D_sptr period_free_workspace =
std::dynamic_pointer_cast<DataObjects::Workspace2D>(
WorkspaceFactory::Instance().create(local_workspace));
createPeriodLogs(firstentry, local_workspace);
WorkspaceGroup_sptr wksp_group(new WorkspaceGroup);
if (m_loadBlockInfo.getNumberOfPeriods() > 1 && m_entrynumber == 0) {
wksp_group->setTitle(local_workspace->getTitle());
// This forms the name of the group
const std::string base_name = getPropertyValue("OutputWorkspace") + "_";
const std::string prop_name = "OutputWorkspace_";
for (int p = 1; p <= m_loadBlockInfo.getNumberOfPeriods(); ++p) {
std::ostringstream os;
os << p;
m_progress->report("Loading period " + os.str());
if (p > 1) {
local_workspace = std::dynamic_pointer_cast<DataObjects::Workspace2D>(
WorkspaceFactory::Instance().create(period_free_workspace));
loadPeriodData(p, entry, local_workspace, m_load_selected_spectra);
createPeriodLogs(p, local_workspace);
// Check consistency of logs data for multi-period workspaces and raise
// warnings where necessary.
validateMultiPeriodLogs(local_workspace);
}
declareProperty(std::make_unique<WorkspaceProperty<Workspace>>(
prop_name + os.str(), base_name + os.str(), Direction::Output));
wksp_group->addWorkspace(local_workspace);
setProperty(prop_name + os.str(),
std::static_pointer_cast<Workspace>(local_workspace));
}
// The group is the root property value
setProperty("OutputWorkspace",
std::dynamic_pointer_cast<Workspace>(wksp_group));
} else {
setProperty("OutputWorkspace",
std::dynamic_pointer_cast<Workspace>(local_workspace));
}
//***************************************************************************************************
// Workspace or group of workspaces without monitors is loaded. Now we are
// loading monitors separately.
if (bseparateMonitors) {
std::string wsName = getPropertyValue("OutputWorkspace");
if (m_monBlockInfo.getNumberOfSpectra() > 0) {
x_length = m_monBlockInfo.getNumberOfChannels() + 1;
// reset the size of the period free workspace to the monitor size
period_free_workspace =
std::dynamic_pointer_cast<DataObjects::Workspace2D>(
WorkspaceFactory::Instance().create(
period_free_workspace, m_monBlockInfo.getNumberOfSpectra(),
x_length, m_monBlockInfo.getNumberOfChannels()));
auto monitor_workspace =
std::dynamic_pointer_cast<DataObjects::Workspace2D>(
WorkspaceFactory::Instance().create(period_free_workspace));
m_spectraBlocks.clear();
m_wsInd2specNum_map.clear();
// at the moment here we clear this map to enable possibility to load
// monitors from the spectra block (wiring table bug).
// if monitor's spectra present in the detectors block due to this bug
// should be read from monitors, this map should be dealt with properly.
buildSpectraInd2SpectraNumMap(true /*hasRange*/, false /*hasSpectraList*/,
m_monBlockInfo);
// lo
prepareSpectraBlocks(m_monitors, m_monBlockInfo);
firstentry = (m_entrynumber > 0) ? m_entrynumber : 1;
loadPeriodData(firstentry, entry, monitor_workspace, true);
local_workspace->setMonitorWorkspace(monitor_workspace);
ISISRunLogs monLogCreator(monitor_workspace->run());
monLogCreator.addPeriodLogs(1, monitor_workspace->mutableRun());
const std::string monitorPropBase = "MonitorWorkspace";
const std::string monitorWsNameBase = wsName + "_monitors";
if (m_detBlockInfo.getNumberOfPeriods() > 1 && m_entrynumber == 0) {
WorkspaceGroup_sptr monitor_group(new WorkspaceGroup);
monitor_group->setTitle(monitor_workspace->getTitle());
for (int p = 1; p <= m_detBlockInfo.getNumberOfPeriods(); ++p) {
std::ostringstream os;
os << "_" << p;
m_progress->report("Loading period " + os.str());
if (p > 1) {
monitor_workspace =
std::dynamic_pointer_cast<DataObjects::Workspace2D>(
WorkspaceFactory::Instance().create(period_free_workspace));
loadPeriodData(p, entry, monitor_workspace,
m_load_selected_spectra);
monLogCreator.addPeriodLogs(p, monitor_workspace->mutableRun());
// Check consistency of logs data for multi-period workspaces and
// raise
// warnings where necessary.
validateMultiPeriodLogs(monitor_workspace);
auto data_ws = std::static_pointer_cast<API::MatrixWorkspace>(
wksp_group->getItem(p - 1));
data_ws->setMonitorWorkspace(monitor_workspace);
}
declareProperty(std::make_unique<WorkspaceProperty<Workspace>>(
monitorPropBase + os.str(), monitorWsNameBase + os.str(),
Direction::Output));
monitor_group->addWorkspace(monitor_workspace);
setProperty(monitorPropBase + os.str(),
std::static_pointer_cast<Workspace>(monitor_workspace));
}
// The group is the root property value
declareProperty(std::make_unique<WorkspaceProperty<Workspace>>(
monitorPropBase, monitorWsNameBase, Direction::Output));
setProperty(monitorPropBase,
std::dynamic_pointer_cast<Workspace>(monitor_group));
} else {
declareProperty(std::make_unique<WorkspaceProperty<Workspace>>(
monitorPropBase, monitorWsNameBase, Direction::Output));
setProperty(monitorPropBase,
std::static_pointer_cast<Workspace>(monitor_workspace));
}
} else {
g_log.information() << " no monitors to load for workspace: " << wsName
<< '\n';
}
}
// Clear off the member variable containers
m_tof_data.reset();
m_spec.clear();
m_monitors.clear();
m_wsInd2specNum_map.clear();
}
// Function object for remove_if STL algorithm
namespace {
// Check the numbers supplied are not in the range and erase the ones that are
struct range_check {
range_check(int64_t min, int64_t max) : m_min(min), m_max(max) {}
bool operator()(int64_t x) { return (x >= m_min && x <= m_max); }
private:
int64_t m_min;
int64_t m_max;
};
} // namespace
/**
Check for a set of synthetic logs associated with multi-period log data. Raise
warnings where necessary.
*/
void LoadISISNexus2::validateMultiPeriodLogs(
const Mantid::API::MatrixWorkspace_sptr &ws) {
const Run &run = ws->run();
if (!run.hasProperty("current_period")) {
g_log.warning("Workspace has no current_period log.");
}
if (!run.hasProperty("nperiods")) {
g_log.warning("Workspace has no nperiods log");
}
if (!run.hasProperty("proton_charge_by_period")) {
g_log.warning("Workspace has not proton_charge_by_period log");
}
}
/**
* Check the validity of the optional properties of the algorithm and identify
* if partial data should be loaded.
* @param bseparateMonitors: flag indicating if the monitors are to be loaded
* separately
* @param bexcludeMonitor: flag indicating if the monitors are to be excluded
*/
bool LoadISISNexus2::checkOptionalProperties(bool bseparateMonitors,
bool bexcludeMonitor) {
// optional properties specify that only some spectra have to be loaded
bool range_supplied(false);
// Get the spectrum selection which were specfied by the user
int64_t spec_min = getProperty("SpectrumMin");
int64_t spec_max = getProperty("SpectrumMax");
// If spearate monitors or excluded monitors is selected then we
// need to build up a wsIndex to spectrum number map as well,
// since we cannot rely on contiguous blocks of detectors
if (bexcludeMonitor || bseparateMonitors) {
m_load_selected_spectra = true;
}
if (spec_min == 0)
spec_min = m_loadBlockInfo.getMinSpectrumID();
else {
range_supplied = true;
m_load_selected_spectra = true;
}
if (spec_max == EMPTY_INT())
spec_max = m_loadBlockInfo.getMaxSpectrumID();
else {
range_supplied = true;
m_load_selected_spectra = true;
}
// Sanity check for min/max
if (spec_min > spec_max) {
throw std::invalid_argument("Inconsistent range properties. SpectrumMin is "
"larger than SpectrumMax.");
}
if (spec_max > m_loadBlockInfo.getMaxSpectrumID()) {
std::string err =
"Inconsistent range property. SpectrumMax is larger than number of "
"spectra: " +
std::to_string(m_loadBlockInfo.getMaxSpectrumID());
throw std::invalid_argument(err);
}
// Check the entry number
m_entrynumber = getProperty("EntryNumber");
if (static_cast<int>(m_entrynumber) > m_loadBlockInfo.getNumberOfPeriods() ||
m_entrynumber < 0) {
std::string err = "Invalid entry number entered. File contains " +
std::to_string(m_loadBlockInfo.getNumberOfPeriods()) +
" period. ";
throw std::invalid_argument(err);
}
if (m_loadBlockInfo.getNumberOfPeriods() == 1) {
m_entrynumber = 1;
}
// Did the user provide a spectrum list
std::vector<int64_t> spec_list = getProperty("SpectrumList");
auto hasSpecList = false;
if (!spec_list.empty()) {
m_load_selected_spectra = true;
// Sort the list so that we can check it's range
std::sort(spec_list.begin(), spec_list.end());
// Check if the spectra list entries are outside of the bounds
// If we load the monitors separately, then we need to make sure that we
// take them into account
bool isSpectraListTooLarge;
bool isSpectraListTooSmall;
auto maxLoadBlock = m_loadBlockInfo.getMaxSpectrumID();
auto minLoadBlock = m_loadBlockInfo.getMinSpectrumID();
if (bseparateMonitors) {
auto maxMonBlock = m_monBlockInfo.getMaxSpectrumID();
auto minMonBlock = m_monBlockInfo.getMinSpectrumID();
isSpectraListTooLarge =
spec_list.back() > std::max(maxMonBlock, maxLoadBlock);
isSpectraListTooSmall =
spec_list.front() < std::min(minMonBlock, minLoadBlock);
} else {
isSpectraListTooLarge = spec_list.back() > maxLoadBlock;
isSpectraListTooSmall = spec_list.front() < minLoadBlock;
}
if (isSpectraListTooLarge) {
std::string err =
"The specified spectrum list contains a spectrum number which is "
"larger "
"than the largest loadable spectrum number for your selection of "
"excluded/included/separate monitors. The largest loadable "
"spectrum number is " +
std::to_string(m_loadBlockInfo.getMinSpectrumID());
throw std::invalid_argument(err);
}
if (isSpectraListTooSmall) {
std::string err =
"The specified spectrum list contains a spectrum number which is "
"smaller "
"than the smallest loadable spectrum number for your selection of "
"excluded/included/separate monitors. The smallest loadable "
"spectrum number is " +
std::to_string(m_loadBlockInfo.getMinSpectrumID());
throw std::invalid_argument(err);
}
// The users can provide a spectrum list and and a spectrum range. Handle
// this here.
if (range_supplied) {
// First remove all entries which are inside of the min and max spectrum,
// to avoid duplicates
auto isInRange = [&spec_min, &spec_max](int64_t x) {
return (spec_min <= x) && (x <= spec_max);
};
spec_list.erase(remove_if(spec_list.begin(), spec_list.end(), isInRange),
spec_list.end());
// The spec_min - spec_max range needs to be added to the spec list
for (int64_t i = spec_min; i < spec_max + 1; ++i) {
auto spec_num = static_cast<specnum_t>(i);
spec_list.emplace_back(spec_num);
std::sort(spec_list.begin(), spec_list.end());
// supplied range converted into the list, so no more supplied range
}
}
auto monitorSpectra = m_monBlockInfo.getAllSpectrumNumbers();
// Create DataBlocks from the spectrum list
DataBlockComposite composite;
populateDataBlockCompositeWithContainer(
composite, spec_list, spec_list.size(),
m_loadBlockInfo.getNumberOfPeriods(),
m_loadBlockInfo.getNumberOfChannels(), monitorSpectra);
// If the monitors are to be loaded separately, then we have
// to remove them at this point, but we also have to check if the
if (bexcludeMonitor || bseparateMonitors) {
auto newMonitors = getMonitorsFromComposite(composite, m_monBlockInfo);
composite.removeSpectra(m_monBlockInfo);
// This is important. If there are no monitors which were specifically
// selected,
// then we load the full monitor range, else respect the selection.
if (!newMonitors.isEmpty()) {
m_monBlockInfo = newMonitors;
}
// Handle case where the composite is empty since it only contained
// monitors, but we want to load the monitors sepearately. In this case we
// should set the loadBlock to the selected monitors.
if (bseparateMonitors && composite.isEmpty()) {
composite = m_monBlockInfo;
bseparateMonitors = false;
}
}
m_loadBlockInfo = composite;
hasSpecList = true;
} else {
// At this point we don't have a spectrum list but there might have been a
// spectrum range which we need to take into account, by truncating
// the current range. If we load the monitors separately, we need to
// truncate them as well (provided they are affected)
if (range_supplied) {
m_loadBlockInfo.truncate(spec_min, spec_max);
auto new_monitors = m_monBlockInfo;
new_monitors.truncate(spec_min, spec_max);
m_monBlockInfo = new_monitors;
}
}
if (m_load_selected_spectra) {
buildSpectraInd2SpectraNumMap(range_supplied, hasSpecList, m_loadBlockInfo);
}
// Check that the load blocks contain anything at all.
if (m_loadBlockInfo.isEmpty()) {
throw std::invalid_argument(
"Your spectrum number selection was not valid. "
"Make sure that you select spectrum numbers "
"and ranges which are compatible with your "
"selection of excluded/included/separate monitors. ");
}
return bseparateMonitors;
}
/**
Build the list of spectra to load and include into spectra-detectors map.
The map should be built if the user either specified a range or if the user
provided a list of spectrum numbers.
@param range_supplied: if true specifies that the range of values provided
below have to be processed rather then spectra list
@param hasSpectraList: did the user specify a spectrum list
@param dataBlockComposite: a data block composite specfiying the spectra
intervals
**/
void LoadISISNexus2::buildSpectraInd2SpectraNumMap(
bool range_supplied, bool hasSpectraList,
DataBlockComposite &dataBlockComposite) {
if (range_supplied || hasSpectraList || true) {
auto generator = dataBlockComposite.getGenerator();
int64_t hist = 0;
for (; !generator->isDone(); generator->next()) {
auto spec_num = static_cast<specnum_t>(generator->getValue());
m_wsInd2specNum_map.emplace(hist, spec_num);
++hist;
}
}
}
/**
* Analyze the spectra ranges and prepare a list contiguous blocks. Each monitor
* must be
* in a separate block.
* @return :: Number of spectra to load.
*/
size_t
LoadISISNexus2::prepareSpectraBlocks(std::map<int64_t, std::string> &monitors,
DataBlockComposite &LoadBlock) {
std::vector<int64_t> includedMonitors;
// Setup the SpectraBlocks based on the DataBlocks
auto dataBlocks = LoadBlock.getDataBlocks();
auto isMonitor = [&monitors](int64_t spectrumNumber) {
return monitors.find(spectrumNumber) != monitors.end();
};
for (const auto &dataBlock : dataBlocks) {
auto min = dataBlock.getMinSpectrumID();
if (isMonitor(min)) {
m_spectraBlocks.emplace_back(
SpectraBlock(min, min, true, monitors.find(min)->second));
includedMonitors.emplace_back(min);
} else {
auto max = dataBlock.getMaxSpectrumID();
m_spectraBlocks.emplace_back(SpectraBlock(min, max, false, ""));
}
}
// sort and check for overlapping
if (m_spectraBlocks.size() > 1) {
std::sort(m_spectraBlocks.begin(), m_spectraBlocks.end(),
[](const LoadISISNexus2::SpectraBlock &block1,
const LoadISISNexus2::SpectraBlock &block2) {
return block1.last < block2.first;
});
checkOverlappingSpectraRange();
}
// Remove monitors that have been used.
auto allMonitorsIncluded = monitors.size() == includedMonitors.size();
if (!includedMonitors.empty() && !allMonitorsIncluded) {
for (auto it = monitors.begin(); it != monitors.end();) {
if (std::find(includedMonitors.begin(), includedMonitors.end(),
it->first) != includedMonitors.end()) {
auto it1 = it;
++it;
monitors.erase(it1);
} else {
++it;
}
}
}
// Count the number of spectra.
const auto nSpec = std::accumulate(
m_spectraBlocks.cbegin(), m_spectraBlocks.cend(), static_cast<size_t>(0),
[](size_t sum, const auto &spectraBlock) {
return sum + spectraBlock.last - spectraBlock.first + 1;
});
return nSpec;
}
/**
* Check if any spectra block ranges overlap.
*
* Iterate over the sorted list of spectra blocks and check
* if the last element of the preceeding block is less than
* the first element of the next block.
*/
void LoadISISNexus2::checkOverlappingSpectraRange() {
for (size_t i = 1; i < m_spectraBlocks.size(); ++i) {
const auto &block1 = m_spectraBlocks[i - 1];
const auto &block2 = m_spectraBlocks[i];
if (block1.first > block1.last && block2.first > block2.last)
throw std::runtime_error("LoadISISNexus2: inconsistent spectra ranges");
if (block1.last >= block2.first) {
throw std::runtime_error(
"LoadISISNexus2: the range of SpectraBlocks must not overlap");
}
}
}
/**
* Load a given period into the workspace
* @param period :: The period number to load (starting from 1)
* @param entry :: The opened root entry node for accessing the monitor and data
* nodes
* @param local_workspace :: The workspace to place the data in
* @param update_spectra2det_mapping :: reset spectra-detector map to the one
* calculated earlier. (Warning! -- this map has to be calculated correctly!)
*/
void LoadISISNexus2::loadPeriodData(
int64_t period, NXEntry &entry,
DataObjects::Workspace2D_sptr &local_workspace,
bool update_spectra2det_mapping) {
int64_t hist_index = 0;
int64_t period_index(period - 1);
// int64_t first_monitor_spectrum = 0;
for (const auto &spectraBlock : m_spectraBlocks) {
if (spectraBlock.isMonitor) {
NXData monitor = entry.openNXData(spectraBlock.monName);
NXInt mondata = monitor.openIntData();
m_progress->report("Loading monitor");
mondata.load(1, static_cast<int>(period - 1)); // TODO this is just wrong
NXFloat timeBins = monitor.openNXFloat("time_of_flight");
timeBins.load();
local_workspace->setHistogram(
hist_index, BinEdges(timeBins(), timeBins() + timeBins.dim0()),
Counts(mondata(), mondata() + m_monBlockInfo.getNumberOfChannels()));
if (update_spectra2det_mapping) {
// local_workspace->getAxis(1)->setValue(hist_index,
// static_cast<specnum_t>(it->first));
auto &spec = local_workspace->getSpectrum(hist_index);
specnum_t specNum = m_wsInd2specNum_map.at(hist_index);
spec.setDetectorIDs(
m_spec2det_map.getDetectorIDsForSpectrumNo(specNum));
spec.setSpectrumNo(specNum);
}
hist_index++;
} else if (m_have_detector) {
NXData nxdata = entry.openNXData("detector_1");
NXDataSetTyped<int> data = nxdata.openIntData();
data.open();
// Start with the list members that are lower than the required spectrum
const int *const spec_begin = m_spec.data();
// When reading in blocks we need to be careful that the range is exactly
// divisible by the block-size
// and if not have an extra read of the left overs
const int64_t blocksize = 8;
const int64_t rangesize = spectraBlock.last - spectraBlock.first + 1;
const int64_t fullblocks = rangesize / blocksize;
int64_t spectra_no = spectraBlock.first;
// For this to work correctly, we assume that the spectrum list increases
// monotonically
int64_t filestart =
std::lower_bound(spec_begin, m_spec_end, spectra_no) - spec_begin;
if (fullblocks > 0) {
for (int64_t i = 0; i < fullblocks; ++i) {
loadBlock(data, blocksize, period_index, filestart, hist_index,
spectra_no, local_workspace);
filestart += blocksize;
}
}
int64_t finalblock = rangesize - (fullblocks * blocksize);
if (finalblock > 0) {
loadBlock(data, finalblock, period_index, filestart, hist_index,
spectra_no, local_workspace);
}
}
}
try {
const std::string title = entry.getString("title");
local_workspace->setTitle(title);
// write the title into the log file (run object)
local_workspace->mutableRun().addProperty("run_title", title, true);
} catch (std::runtime_error &) {
g_log.debug() << "No title was found in the input file, "
<< getPropertyValue("Filename") << '\n';
}
}
/**
* Creates period log data in the workspace
* @param period :: period number
* @param local_workspace :: workspace to add period log data to.
*/
void LoadISISNexus2::createPeriodLogs(
int64_t period, DataObjects::Workspace2D_sptr &local_workspace) {
m_logCreator->addPeriodLogs(static_cast<int>(period),
local_workspace->mutableRun());
}
/**
* Perform a call to nxgetslab, via the NexusClasses wrapped methods for a given
* block-size
* @param data :: The NXDataSet object
* @param blocksize :: The block-size to use
* @param period :: The period number
* @param start :: The index within the file to start reading from (zero based)
* @param hist :: The workspace index to start reading into
* @param spec_num :: The spectrum number that matches the hist variable
* @param local_workspace :: The workspace to fill the data with
*/
void LoadISISNexus2::loadBlock(NXDataSetTyped<int> &data, int64_t blocksize,
int64_t period, int64_t start, int64_t &hist,
int64_t &spec_num,
DataObjects::Workspace2D_sptr &local_workspace) {
data.load(static_cast<int>(blocksize), static_cast<int>(period),
static_cast<int>(start)); // TODO this is just wrong
int *data_start = data();
int *data_end = data_start + m_loadBlockInfo.getNumberOfChannels();
int64_t final(hist + blocksize);
while (hist < final) {
m_progress->report("Loading data");
local_workspace->setHistogram(hist, BinEdges(m_tof_data),
Counts(data_start, data_end));
data_start += m_detBlockInfo.getNumberOfChannels();
data_end += m_detBlockInfo.getNumberOfChannels();
if (m_load_selected_spectra) {
// local_workspace->getAxis(1)->setValue(hist,
// static_cast<specnum_t>(spec_num));
auto &spec = local_workspace->getSpectrum(hist);
specnum_t specNum = m_wsInd2specNum_map.at(hist);
// set detectors corresponding to spectra Number
spec.setDetectorIDs(m_spec2det_map.getDetectorIDsForSpectrumNo(specNum));
// set correct spectra Number
spec.setSpectrumNo(specNum);
}
++hist;
++spec_num;
}
}
/// Run the Child Algorithm LoadInstrument (or LoadInstrumentFromNexus)
void LoadISISNexus2::runLoadInstrument(
DataObjects::Workspace2D_sptr &localWorkspace) {
IAlgorithm_sptr loadInst = createChildAlgorithm("LoadInstrument");
// Now execute the Child Algorithm. Catch and log any error, but don't stop.
bool executionSuccessful(true);
try {
loadInst->setPropertyValue("InstrumentName", m_instrument_name);
loadInst->setProperty<MatrixWorkspace_sptr>("Workspace", localWorkspace);
loadInst->setProperty("RewriteSpectraMap",
Mantid::Kernel::OptionalBool(false));
loadInst->execute();
} catch (std::invalid_argument &) {
g_log.information("Invalid argument to LoadInstrument Child Algorithm");
executionSuccessful = false;
} catch (std::runtime_error &) {
g_log.information(
"Unable to successfully run LoadInstrument Child Algorithm");
executionSuccessful = false;
}
if (executionSuccessful) {
// If requested update the instrument to positions in the data file
const auto &pmap = localWorkspace->constInstrumentParameters();
if (pmap.contains(localWorkspace->getInstrument()->getComponentID(),
"det-pos-source")) {
std::shared_ptr<Geometry::Parameter> updateDets = pmap.get(
localWorkspace->getInstrument()->getComponentID(), "det-pos-source");
std::string value = updateDets->value<std::string>();
if (value.substr(0, 8) == "datafile") {
IAlgorithm_sptr updateInst =
createChildAlgorithm("UpdateInstrumentFromFile");
updateInst->setProperty<MatrixWorkspace_sptr>("Workspace",
localWorkspace);
updateInst->setPropertyValue("Filename", m_filename);
if (value == "datafile-ignore-phi") {
updateInst->setProperty("IgnorePhi", true);
g_log.information("Detector positions in IDF updated with positions "
"in the data file except for the phi values");
} else {
g_log.information("Detector positions in IDF updated with positions "
"in the data file");
}
// We want this to throw if it fails to warn the user that the
// information is not correct.
updateInst->execute();
}
}
}
}
/**
* Load data about the run
* @param local_workspace :: The workspace to load the run information in to
* @param entry :: The Nexus entry
*/
void LoadISISNexus2::loadRunDetails(
DataObjects::Workspace2D_sptr &local_workspace, NXEntry &entry) {
API::Run &runDetails = local_workspace->mutableRun();
// Data details on run not the workspace
runDetails.addProperty(
"nspectra", static_cast<int>(m_loadBlockInfo.getNumberOfSpectra()));
runDetails.addProperty(
"nchannels", static_cast<int>(m_loadBlockInfo.getNumberOfChannels()));
runDetails.addProperty(
"nperiods", static_cast<int>(m_loadBlockInfo.getNumberOfPeriods()));
LoadISISNexusHelper::loadRunDetails(runDetails, entry, m_hasVMSBlock);
}
/**
* Load data about the sample
* @param local_workspace :: The workspace to load the logs to.
* @param entry :: The Nexus entry
*/
void LoadISISNexus2::loadSampleData(
DataObjects::Workspace2D_sptr &local_workspace, NXEntry &entry) {
// load sample geometry - Id and dimensions
LoadISISNexusHelper::loadSampleGeometry(local_workspace->mutableSample(),
entry, m_hasVMSBlock);
g_log.debug() << "Sample geometry - ID: "
<< local_workspace->mutableSample().getGeometryFlag()
<< ", thickness: "
<< local_workspace->mutableSample().getThickness()
<< ", height: " << local_workspace->mutableSample().getHeight()
<< ", width: " << local_workspace->mutableSample().getWidth()
<< "\n";
}
/** Load logs from Nexus file. Logs are expected to be in
* /raw_data_1/runlog group of the file. Call to this method must be done
* within /raw_data_1 group.
* @param ws :: The workspace to load the logs to.
*/
void LoadISISNexus2::loadLogs(DataObjects::Workspace2D_sptr &ws) {
IAlgorithm_sptr alg = createChildAlgorithm("LoadNexusLogs", 0.0, 0.5);
alg->setPropertyValue("Filename", this->getProperty("Filename"));
alg->setProperty<MatrixWorkspace_sptr>("Workspace", ws);
try {
alg->executeAsChildAlg();
} catch (std::runtime_error &) {
g_log.warning() << "Unable to load run logs. There will be no log "
<< "data associated with this workspace\n";
return;
}
// Populate the instrument parameters.
ws->populateInstrumentParameters();
// Make log creator object and add the run status log
m_logCreator.reset(new ISISRunLogs(ws->run()));
m_logCreator->addStatusLog(ws->mutableRun());
}
double LoadISISNexus2::dblSqrt(double in) { return sqrt(in); }
/**Method takes input parameters which describe monitor loading and analyze
*them against spectra/monitor block information in the file.
* The result is the option if monitors can be loaded together with spectra or
*mast be treated separately
* and additional information on how to treat monitor spectra.
*
*@param entry :: entry to the NeXus file, opened at root folder
*@param spectrum_index :: array of spectra indexes of the data present in
*the file
*@param ndets :: size of the spectrum index array
*@param n_vms_compat_spectra :: number of data entries containing common time
*bins (e.g. all spectra, or all spectra and monitors or some spectra (this is
*not fully supported)
*@param monitors :: map connecting monitor spectra ID against
*monitor group name in the file.
*@param excludeMonitors :: input property indicating if it is requested to
*exclude monitors from the target workspace
*@param separateMonitors :: input property indicating if it is requested to
*load monitors separately (and exclude them from target data workspace this
*way)
*@return excludeMonitors :: indicator if monitors should or must be
*excluded from the main data workspace if they can not be loaded with the data
* (contain different number of time channels)
*
*/
bool LoadISISNexus2::findSpectraDetRangeInFile(
NXEntry &entry, std::vector<int> &spectrum_index, int64_t ndets,
int64_t n_vms_compat_spectra, std::map<int64_t, std::string> &monitors,
bool excludeMonitors, bool separateMonitors) {
size_t nmons = monitors.size();
if (nmons > 0) {
NXInt chans = entry.openNXInt(m_monitors.begin()->second + "/data");
// Iterate over each monitor and create a data block for each monitor
for (const auto &monitor : monitors) {
auto monID = static_cast<int64_t>(monitor.first);
auto monTemp = DataBlock(chans);
monTemp.setMinSpectrumID(monID);
monTemp.setMaxSpectrumID(monID);
m_monBlockInfo.addDataBlock(monTemp);
}
// at this stage we assume that the only going to load monitors
m_loadBlockInfo = m_monBlockInfo;
}
// Check if the there are only monitors in the workspace, in which
// case the monitors are loaded as the main workspace, ie not as
// a separate workspace.
if (ndets == 0) {
separateMonitors = false;
return separateMonitors;
}
// There are detectors present. The spectrum_index array contains
// all available spectra of detectors, but these indices might
// not be contiguous.
NXData nxData = entry.openNXData("detector_1");
NXInt data = nxData.openIntData();
auto monitorSpectra = m_monBlockInfo.getAllSpectrumNumbers();
populateDataBlockCompositeWithContainer(
m_detBlockInfo, spectrum_index, ndets, data.dim0() /*Number of Periods*/,
data.dim2() /*Number of channels*/, monitorSpectra);
// We should handle legacy files which include the spectrum number of the
// monitors
// in the detector group ("raw_data_1/detector_1/spectrum_index")
// Simple try to remove the monitors. If they are not included nothing should
// happen
m_detBlockInfo.removeSpectra(m_monBlockInfo);
m_loadBlockInfo = m_detBlockInfo;
// Check what is actually going or can be loaded
bool removeMonitors = excludeMonitors || separateMonitors;
// If the monitors are to be pulled into the same workspace as the detector
// information,
// then the number of periods and the number of channels has to conincide
if (nmons > 0 && ((m_detBlockInfo.getNumberOfPeriods() !=
m_monBlockInfo.getNumberOfPeriods()) ||
(m_detBlockInfo.getNumberOfChannels() !=
m_monBlockInfo.getNumberOfChannels()))) {
if (!removeMonitors) {
g_log.warning() << " Performing separate loading as can not load spectra "
"and monitors in the single workspace:\n";
g_log.warning() << " Monitors data contain :"
<< m_monBlockInfo.getNumberOfChannels()
<< " time channels and: "
<< m_monBlockInfo.getNumberOfPeriods() << " period(s)\n";
g_log.warning() << " Spectra data contain :"
<< m_detBlockInfo.getNumberOfChannels()
<< " time channels and: "
<< m_detBlockInfo.getNumberOfPeriods() << " period(s)\n";
}
// Force the monitors to be removed and separate if the periods and channels
// don't conincide
// between monitors and detectors.
separateMonitors = true;
removeMonitors = true;
}
int64_t spectraID_min = std::min(m_monBlockInfo.getMinSpectrumID(),
m_detBlockInfo.getMinSpectrumID());
int64_t spectraID_max = std::max(m_monBlockInfo.getMaxSpectrumID(),
m_detBlockInfo.getMaxSpectrumID());
size_t totNumOfSpectra =
m_monBlockInfo.getNumberOfSpectra() + m_detBlockInfo.getNumberOfSpectra();
// In case we want to load everything into a one workspace, we should combine
// the
// the data blocks of the monitor and the detector
if (!removeMonitors) {
m_loadBlockInfo = m_detBlockInfo + m_monBlockInfo;
}
// If the monitors are to be loaded separately, then we set the loadblocks to
// the detblocks,
// since we want to deal with the detectors (the main workspace) first.
if (separateMonitors)
m_loadBlockInfo = m_detBlockInfo;
// Perform a sanity check of the spectrum numbers
if ((totNumOfSpectra != static_cast<size_t>(n_vms_compat_spectra)) ||
(spectraID_max - spectraID_min + 1 !=
static_cast<int64_t>(n_vms_compat_spectra))) {
// At this point we normally throw since there is a mismatch between the
// number
// spectra of the detectors+monitors and the entry in NSP1, but in the
// case of multiple time regimes this comparison is not any longer valid.
// Hence we only throw if the file does not correspond to a multiple time
// regime file.
if (!isMultipleTimeRegimeFile(entry)) {
throw std::runtime_error("LoadISISNexus: There seems to be an "
"inconsistency in the spectrum numbers.");
}
}
return separateMonitors;
}
/**
* Determine if a file is a multiple time regime file. Note that for a true
* multi-time regime file we need at least three time regime entries, since
* two time regimes are handled by vms_compat.
* @param entry a handle to the Nexus file
* @return if the file has multiple time regimes or not
*/
bool LoadISISNexus2::isMultipleTimeRegimeFile(NeXus::NXEntry &entry) const {
auto hasMultipleTimeRegimes(false);
try {
NXClass instrument = entry.openNXGroup("instrument");
NXClass dae = instrument.openNXGroup("dae");
hasMultipleTimeRegimes = dae.containsGroup("time_channels_3");
} catch (...) {
}
return hasMultipleTimeRegimes;
}
} // namespace DataHandling
} // namespace Mantid