-
Martyn Gigg authored
Manually merged to release branch.
Martyn Gigg authoredManually merged to release branch.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
ReflDataProcessorPresenter.cpp 33.21 KiB
#include "ReflDataProcessorPresenter.h"
#include "MantidAPI/AlgorithmManager.h"
#include "MantidAPI/IEventWorkspace.h"
#include "MantidAPI/MatrixWorkspace.h"
#include "MantidAPI/Run.h"
#include "MantidKernel/Tolerance.h"
#include "MantidQtWidgets/Common/DataProcessorUI/DataProcessorView.h"
#include "MantidQtWidgets/Common/DataProcessorUI/OptionsMap.h"
#include "MantidQtWidgets/Common/DataProcessorUI/TreeData.h"
#include "MantidQtWidgets/Common/DataProcessorUI/TreeManager.h"
#include "MantidQtWidgets/Common/DataProcessorUI/WorkspaceNameUtils.h"
#include "MantidQtWidgets/Common/ParseKeyValueString.h"
#include "MantidQtWidgets/Common/ParseNumerics.h"
#include "MantidQtWidgets/Common/ProgressPresenter.h"
#include "ReflFromStdStringMap.h"
using namespace MantidQt::MantidWidgets::DataProcessor;
using namespace MantidQt::MantidWidgets;
using namespace Mantid::API;
namespace MantidQt {
namespace CustomInterfaces {
// unnamed namespace
namespace {
/** Return the minimum number of slices for all rows in the group
* or return 0 if there are no rows
*/
size_t getMinimumSlicesForGroup(const GroupData &group) {
if (group.size() < 1)
return 0;
size_t minNumberOfSlices = std::numeric_limits<size_t>::max();
for (const auto &row : group) {
minNumberOfSlices =
std::min(minNumberOfSlices, row.second->numberOfSlices());
}
return minNumberOfSlices;
}
/** Check whether the given row data contains a value for an angle
*/
bool hasAngle(RowData_sptr data) {
// The angle is the second value in the row
return (data->size() > 1 && !data->value(1).isEmpty());
}
/** Get the angle from the given row as a double. Throws if not specified
*/
double angle(RowData_sptr data) {
if (!hasAngle(data))
throw std::runtime_error(
std::string("Error parsing angle: angle was not set"));
bool ok = false;
double angle = data->value(1).toDouble(&ok);
if (!ok)
throw std::runtime_error("Error parsing angle: " +
data->value(1).toStdString());
return angle;
}
}
TimeSlicingInfo::TimeSlicingInfo(QString type, QString values)
: m_type(std::move(type)), m_values(std::move(values)),
m_enableSlicing(true), m_constNumberOfSlices(0),
m_constSliceDuration(0.0) {
// If the input is empty, do not perform time slicing
if (m_values.isEmpty()) {
m_enableSlicing = false;
return;
}
try {
if (isUniform())
parseUniform();
else if (isUniformEven())
parseUniformEven();
else if (isCustom())
parseCustom();
else if (isLogValue())
parseLogValue();
} catch (const std::runtime_error &ex) {
throw std::runtime_error(
std::string("Error parsing time slicing values: ") + ex.what());
}
}
/** Return the number of slices
* @throws : runtime_error if the number of slices has not been set
*/
size_t TimeSlicingInfo::numberOfSlices() const {
// most types of slicing have a constant number of slices set
auto numSlices = m_constNumberOfSlices;
// for uniform slicing, use the number of slices actually created
if (isUniform())
numSlices = m_startTimes.size();
// if this function is called before the above are set it is an error
if (numSlices < 1)
throw std::runtime_error("Number of slices has not been set");
return numSlices;
}
/** Return the slice duration. This is only applicable where the duration
* is constant for all slices.
* @throws : runtime_error if the number of slices is not constant
*/
double TimeSlicingInfo::sliceDuration() const {
if (!isUniform())
throw std::runtime_error("Slice duration is not constant");
return m_constSliceDuration;
}
/** Add a slice with the given time range
* @param startTime : the start of the slice range
* @param stopTime : the end of the slice range
* @throws : runtime_error if the input is invalid
*/
void TimeSlicingInfo::addSlice(const double startTime, const double stopTime) {
if (startTime < 0 || stopTime < 0)
throw std::runtime_error("The slice start/stop times cannot be negative");
if (startTime >= stopTime)
throw std::runtime_error(
"The slice stop time should be larger than the start time");
// Add a slice if it doesn't already exist
if (std::find(m_startTimes.begin(), m_startTimes.end(), startTime) ==
m_startTimes.end() &&
std::find(m_stopTimes.begin(), m_stopTimes.end(), stopTime) ==
m_stopTimes.end()) {
m_startTimes.push_back(startTime);
m_stopTimes.push_back(stopTime);
}
}
/** Clear the list of time slices
*/
void TimeSlicingInfo::clearSlices() {
m_startTimes.clear();
m_stopTimes.clear();
}
/** Parses the values string for uniform slicing with a constant slice duration.
* Note that this means that the number of slices may not be constant (even) as
* it will depend on the length of the individual runs.
* @throws : runtime_error if input is invalid
*/
void TimeSlicingInfo::parseUniform() {
// set the constant slice duration
m_constSliceDuration = parseDouble(values());
if (m_constSliceDuration <= Mantid::Kernel::Tolerance)
throw std::runtime_error("Slice duration must be greater than zero");
}
/** Parses the values string for uniform slicing with a constant (even)
* number of slices
* @throws : runtime_error if input is invalid
*/
void TimeSlicingInfo::parseUniformEven() {
auto const numberOfSlices = parseDenaryInteger(values());
if (numberOfSlices < 1)
throw std::runtime_error("The number of slices must be greater than zero");
// Set the constant number of slices
m_constNumberOfSlices = numberOfSlices;
}
/** Parses the values string to extract custom time slicing
* @throws : runtime_error if input is invalid
*/
void TimeSlicingInfo::parseCustom() {
// Split the string into a list of doubles
auto timeStr = values().split(",");
std::vector<double> times;
std::transform(timeStr.begin(), timeStr.end(), std::back_inserter(times),
[](const QString &astr) { return parseDouble(astr); });
if (times.size() == 0) {
throw std::runtime_error("The number of slices must be greater than zero");
}
if (times.size() == 1) {
// Only one value was provided: assume a range from 0 to the given value
m_constNumberOfSlices = 1;
addSlice(0, times[0]);
return;
}
// More than one value; create ranges for each pair of adjacent values in the
// list
m_constNumberOfSlices = times.size() - 1;
for (size_t i = 0; i < m_constNumberOfSlices; i++) {
addSlice(times[i], times[i + 1]);
}
}
/** Parses the vlaues string to extract log value filter and time slicing
* @throws : runtime_error if input is invalid
*/
void TimeSlicingInfo::parseLogValue() {
// Extract the slicing and log values from the input which will be of the
// format e.g. "Slicing=0,10,20,30,LogFilter=proton_charge"
auto strMap = parseKeyValueQString(values());
auto const hasSlicingValues = strMap.count("Slicing");
auto const hasLogValue = strMap.count("LogFilter");
if (hasSlicingValues && hasLogValue) {
// We need both inputs in order to do slicing
QString timeSlicing = strMap.at("Slicing");
m_values = timeSlicing;
m_logFilter = strMap.at("LogFilter");
parseCustom();
} else if (hasSlicingValues) {
throw std::runtime_error("You have entered a Log name for time slicing "
"but not a python list: please enter both, or "
"neither");
} else if (hasLogValue) {
throw std::runtime_error("You have entered a python list for time slicing "
"but not a Log name: please enter both, or "
"neither");
} else {
// Empty input should already have been dealt with so we should not get
// here
throw std::runtime_error("Invalid input for slicing by Log value");
}
}
/**
* Constructor
* @param whitelist : The set of properties we want to show as columns
* @param preprocessMap : A map containing instructions for pre-processing
* @param processor : A ProcessingAlgorithm
* @param postprocessor : A PostprocessingAlgorithm
* workspaces
* @param group : The zero-based index of this presenter within the tab.
* @param postprocessMap : A map containing instructions for post-processing.
* This map links column name to properties of the post-processing algorithm
* @param loader : The algorithm responsible for loading data
*/
ReflDataProcessorPresenter::ReflDataProcessorPresenter(
const WhiteList &whitelist,
const std::map<QString, PreprocessingAlgorithm> &preprocessMap,
const ProcessingAlgorithm &processor,
const PostprocessingAlgorithm &postprocessor, int group,
const std::map<QString, QString> &postprocessMap, const QString &loader)
: GenericDataProcessorPresenter(whitelist, preprocessMap, processor,
postprocessor, group, postprocessMap,
loader) {}
/**
* Destructor
*/
ReflDataProcessorPresenter::~ReflDataProcessorPresenter() {}
/**
Process selected data
*/
void ReflDataProcessorPresenter::process() {
// Get selected runs
const auto newSelected = m_manager->selectedData(true);
// Don't continue if there are no items to process
if (newSelected.empty())
return;
// If slicing is not specified, process normally, delegating to
// GenericDataProcessorPresenter
std::unique_ptr<TimeSlicingInfo> slicing;
try {
slicing = Mantid::Kernel::make_unique<TimeSlicingInfo>(
m_mainPresenter->getTimeSlicingType(),
m_mainPresenter->getTimeSlicingValues());
} catch (const std::runtime_error &ex) {
m_view->giveUserWarning(ex.what(), "Error");
return;
}
if (!slicing->hasSlicing()) {
// Check if any input event workspaces still exist in ADS
if (proceedIfWSTypeInADS(newSelected, true)) {
setPromptUser(false); // Prevent prompting user twice
GenericDataProcessorPresenter::process();
}
return;
}
m_selectedData = newSelected;
// Check if any input non-event workspaces exist in ADS
if (!proceedIfWSTypeInADS(m_selectedData, false))
return;
// Progress report
int progress = 0;
int maxProgress = static_cast<int>(m_selectedData.size());
ProgressPresenter progressReporter(progress, maxProgress, maxProgress,
m_progressView);
// True if all groups were processed as event workspaces
bool allGroupsWereEvent = true;
// True if errors where encountered when reducing table
bool errors = false;
// Loop in groups
for (const auto &item : m_selectedData) {
// Group of runs
GroupData group = item.second;
try {
// First load the runs.
bool allEventWS = loadGroup(group);
if (allEventWS) {
// Process the group
if (processGroupAsEventWS(item.first, group, *slicing.get()))
errors = true;
// Notebook not implemented yet
if (m_view->getEnableNotebook()) {
/// @todo Implement save notebook for event-sliced workspaces.
// The per-slice input properties are stored in the RowData but
// at the moment GenerateNotebook just uses the parent row
// saveNotebook(m_selectedData);
GenericDataProcessorPresenter::giveUserWarning(
"Notebook not implemented for sliced data yet",
"Notebook will not be generated");
}
} else {
// Process the group
if (processGroupAsNonEventWS(item.first, group))
errors = true;
// Notebook
if (m_view->getEnableNotebook())
saveNotebook(m_selectedData);
}
if (!allEventWS)
allGroupsWereEvent = false;
} catch (...) {
errors = true;
}
progressReporter.report();
}
if (!allGroupsWereEvent)
m_view->giveUserWarning(
"Some groups could not be processed as event workspaces", "Warning");
if (errors)
m_view->giveUserWarning("Some errors were encountered when "
"reducing table. Some groups may not have "
"been fully processed.",
"Warning");
progressReporter.clear();
}
/** Loads a group of runs. Tries loading runs as event workspaces. If any of the
* workspaces in the group is not an event workspace, stops loading and re-loads
* all of them as non-event workspaces. We need the workspaces to be of the same
* type to process them together.
*
* @param group :: the group of runs
* @return :: true if all runs were loaded as event workspaces. False otherwise
*/
bool ReflDataProcessorPresenter::loadGroup(const GroupData &group) {
// Set of runs loaded successfully
std::set<QString> loadedRuns;
for (const auto &row : group) {
// The run number
auto runNo = row.second->value(0);
// Try loading as event workspace
bool eventWS = loadEventRun(runNo);
if (!eventWS) {
// This run could not be loaded as event workspace. We need to load and
// process the whole group as non-event data.
for (const auto &rowNew : group) {
// The run number
auto runNo = rowNew.second->value(0);
// Load as non-event workspace
loadNonEventRun(runNo);
}
// Remove monitors which were loaded as separate workspaces
for (const auto &run : loadedRuns) {
AnalysisDataService::Instance().remove(
("TOF_" + run + "_monitors").toStdString());
}
return false;
}
loadedRuns.insert(runNo);
}
return true;
}
/** Get a list of workspace property names for the workspaces
* that will be affected by slicing, i.e. the input run and
* all of the output workspaces will be sliced.
*
*/
std::vector<QString>
ReflDataProcessorPresenter::getSlicedWorkspacePropertyNames() const {
// For the input properties, the InputWorkspace is the only one that is
// sliced. Transmission workspaces are not sliced.
auto workspaceProperties = std::vector<QString>{"InputWorkspace"};
auto outputProperties = m_processor.outputProperties();
workspaceProperties.insert(workspaceProperties.end(),
outputProperties.begin(), outputProperties.end());
return workspaceProperties;
}
/** Process a row as event-sliced data
* @param rowData : the row to process
* @param slicing : the time-slicing info
* @return : true if processed successfully
*/
bool ReflDataProcessorPresenter::reduceRowAsEventWS(RowData_sptr rowData,
TimeSlicingInfo &slicing) {
// Preprocess the row. Note that this only needs to be done once and
// not for each slice because the slice data can be inferred from the
// row data
preprocessOptionValues(rowData);
// Get the (preprocessed) input workspace name for the reduction. The
// input runs are from the first column in the whitelist and we look up
// the associated algorithm property value in the options.
auto const &runName =
rowData->preprocessedOptionValue(m_processor.defaultInputPropertyName());
// Do time slicing now if using uniform slicing because this is dependant on
// the start/stop times of the current input workspace
if (slicing.isUniform() || slicing.isUniformEven()) {
slicing.clearSlices();
parseUniform(slicing, runName);
}
const auto slicedWorkspaceProperties = getSlicedWorkspacePropertyNames();
// Clear slices from any previous reduction because they will be
// recreated
rowData->clearSlices();
for (size_t i = 0; i < slicing.numberOfSlices(); i++) {
try {
// Create the slice
QString sliceSuffix = takeSlice(runName, slicing, i);
auto slice = rowData->addSlice(sliceSuffix, slicedWorkspaceProperties);
// Run the algorithm
const auto alg = createAndRunAlgorithm(slice->preprocessedOptions());
// Populate any empty values in the row with output from the algorithm.
// Note that this overwrites the data each time with the results
// from the latest slice. It would be good to do some validation
// that the results are the same for each slice e.g. the resolution
// should always be the same.
updateModelFromResults(alg, rowData);
} catch (...) {
return false;
}
}
return true;
}
/** Processes a group of runs
*
* @param groupID :: An integer number indicating the id of this group
* @param group :: the group of event workspaces
* @param slicing :: Info about how time slicing should be performed
* @return :: true if errors were encountered
*/
bool ReflDataProcessorPresenter::processGroupAsEventWS(
int groupID, const GroupData &group, TimeSlicingInfo &slicing) {
bool errors = false;
bool multiRow = group.size() > 1;
for (const auto &row : group) {
const auto rowID = row.first; // Integer ID of this row
const auto rowData = row.second; // data values for this row
auto runNo = row.second->value(0); // The run number
// Set up all data required for processing the row
if (!initRowForProcessing(rowData))
return true;
if (!reduceRowAsEventWS(rowData, slicing))
return true;
// Update the model with the results
m_manager->update(groupID, rowID, rowData->data());
}
// Post-process (if needed)
if (multiRow) {
// Get the number of slices common to all groups
auto numGroupSlices = getMinimumSlicesForGroup(group);
addNumGroupSlicesEntry(groupID, numGroupSlices);
// Loop through each slice index
for (size_t i = 0; i < numGroupSlices; i++) {
// Create a group containing the relevant slice from each row
GroupData sliceGroup;
for (const auto rowKvp : group) {
auto rowIndex = rowKvp.first;
auto rowData = rowKvp.second;
sliceGroup[rowIndex] = rowData->getSlice(i);
}
// Post process the group of slices
try {
postProcessGroup(sliceGroup);
} catch (...) {
errors = true;
}
}
}
return errors;
}
/** Processes a group of non-event workspaces
*
* @param groupID :: An integer number indicating the id of this group
* @param group :: the group of event workspaces
* @return :: true if errors were encountered
*/
bool ReflDataProcessorPresenter::processGroupAsNonEventWS(int groupID,
GroupData &group) {
bool errors = false;
for (auto &row : group) {
auto rowData = row.second;
// Set up all data required for processing the row
if (!initRowForProcessing(rowData))
return true;
// Do the reduction
reduceRow(rowData);
// Update the tree
m_manager->update(groupID, row.first, rowData->data());
}
// Post-process (if needed)
if (group.size() > 1) {
try {
postProcessGroup(group);
} catch (...) {
errors = true;
}
}
return errors;
}
Mantid::API::IEventWorkspace_sptr
ReflDataProcessorPresenter::retrieveWorkspace(QString const &name) const {
return AnalysisDataService::Instance().retrieveWS<IEventWorkspace>(
name.toStdString());
}
/** Retrieves a workspace from the AnalysisDataService based on it's name.
*
* @param name :: The name of the workspace to retrieve.
* @return A pointer to the retrieved workspace or null if the workspace does
*not exist or
* is not an event workspace.
*/
Mantid::API::IEventWorkspace_sptr
ReflDataProcessorPresenter::retrieveWorkspaceOrCritical(
QString const &name) const {
IEventWorkspace_sptr mws;
if (workspaceExists(name)) {
auto mws = retrieveWorkspace(name);
if (mws == nullptr) {
m_view->giveUserCritical("Workspace to slice " + name +
" is not an event workspace!",
"Time slicing error");
return nullptr;
} else {
return mws;
}
} else {
m_view->giveUserCritical("Workspace to slice not found: " + name,
"Time slicing error");
return nullptr;
}
}
/** Parses a string to extract uniform time slicing
*
* @param slicing :: Info about how time slicing should be performed
* @param wsName :: The name of the workspace to be sliced
*/
void ReflDataProcessorPresenter::parseUniform(TimeSlicingInfo &slicing,
const QString &wsName) {
IEventWorkspace_sptr mws = retrieveWorkspaceOrCritical(wsName);
if (mws != nullptr) {
const auto run = mws->run();
const auto totalDuration = run.endTime() - run.startTime();
double totalDurationSec = totalDuration.total_seconds();
double sliceDuration = .0;
size_t numSlices = 0;
if (slicing.isUniformEven()) {
numSlices = slicing.numberOfSlices();
sliceDuration = totalDurationSec / static_cast<double>(numSlices);
} else if (slicing.isUniform()) {
sliceDuration = slicing.sliceDuration();
numSlices = static_cast<int>(ceil(totalDurationSec / sliceDuration));
}
// Add the start/stop times
for (size_t i = 0; i < numSlices; i++) {
auto const indexAsDouble = static_cast<double>(i);
slicing.addSlice(sliceDuration * indexAsDouble,
sliceDuration * (indexAsDouble + 1));
}
}
}
bool ReflDataProcessorPresenter::workspaceExists(
QString const &workspaceName) const {
return AnalysisDataService::Instance().doesExist(workspaceName.toStdString());
}
/** Loads an event workspace and puts it into the ADS
*
* @param runNo :: The run number as a string
* @return :: True if algorithm was executed. False otherwise
*/
bool ReflDataProcessorPresenter::loadEventRun(const QString &runNo) {
bool runFound;
QString outName;
QString prefix = "TOF_";
QString instrument = m_view->getProcessInstrument();
outName = findRunInADS(runNo, prefix, runFound);
if (!runFound || !workspaceExists(outName + "_monitors") ||
retrieveWorkspace(outName) == nullptr) {
// Monitors must be loaded first and workspace must be an event workspace
loadRun(runNo, instrument, prefix, "LoadEventNexus", runFound);
}
return runFound;
}
/** Loads a non-event workspace and puts it into the ADS
*
* @param runNo :: The run number as a string
*/
void ReflDataProcessorPresenter::loadNonEventRun(const QString &runNo) {
bool runFound; // unused but required
auto prefix = QString("TOF_");
auto instrument = m_view->getProcessInstrument();
findRunInADS(runNo, prefix, runFound);
if (!runFound)
loadRun(runNo, instrument, prefix, m_loader, runFound);
}
/** Tries loading a run from disk
*
* @param run : The name of the run
* @param instrument : The instrument the run belongs to
* @param prefix : The prefix to be prepended to the run number
* @param loader : The algorithm used for loading runs
* @param runFound : Whether or not the run was actually found
* @returns string name of the run
*/
QString ReflDataProcessorPresenter::loadRun(const QString &run,
const QString &instrument,
const QString &prefix,
const QString &loader,
bool &runFound) {
runFound = true;
auto const fileName = instrument + run;
auto const outputName = prefix + run;
IAlgorithm_sptr algLoadRun =
AlgorithmManager::Instance().create(loader.toStdString());
algLoadRun->initialize();
algLoadRun->setProperty("Filename", fileName.toStdString());
algLoadRun->setProperty("OutputWorkspace", outputName.toStdString());
if (loader == "LoadEventNexus")
algLoadRun->setProperty("LoadMonitors", true);
algLoadRun->execute();
if (!algLoadRun->isExecuted()) {
// Run not loaded from disk
runFound = false;
return "";
}
return outputName;
}
/** Takes a slice from a run and puts the 'sliced' workspace into the ADS
*
* @param runName :: The input workspace name as a string
* @param slicing :: Info about how time slicing should be performed
* @param sliceIndex :: The index of the slice being taken
* @return :: the suffix used for the slice name
*/
QString ReflDataProcessorPresenter::takeSlice(const QString &runName,
TimeSlicingInfo &slicing,
size_t sliceIndex) {
QString sliceSuffix = "_slice_" +
QString::number(slicing.startTime(sliceIndex)) +
"_to_" + QString::number(slicing.stopTime(sliceIndex));
QString sliceName = runName + sliceSuffix;
QString monName = runName + "_monitors";
QString filterAlg =
slicing.logFilter().isEmpty() ? "FilterByTime" : "FilterByLogValue";
auto startTime = slicing.startTime(sliceIndex);
auto stopTime = slicing.stopTime(sliceIndex);
// Filter the run using the appropriate filter algorithm
IAlgorithm_sptr filter =
AlgorithmManager::Instance().create(filterAlg.toStdString());
filter->initialize();
filter->setProperty("InputWorkspace", runName.toStdString());
filter->setProperty("OutputWorkspace", sliceName.toStdString());
if (filterAlg == "FilterByTime") {
filter->setProperty("StartTime", startTime);
filter->setProperty("StopTime", stopTime);
} else { // FilterByLogValue
filter->setProperty("MinimumValue", startTime);
filter->setProperty("MaximumValue", stopTime);
filter->setProperty("TimeTolerance", 1.0);
filter->setProperty("LogName", slicing.logFilter().toStdString());
}
filter->execute();
// Obtain the normalization constant for this slice
IEventWorkspace_sptr mws = retrieveWorkspace(runName);
double total = mws->run().getProtonCharge();
mws = retrieveWorkspace(sliceName);
double slice = mws->run().getProtonCharge();
double scaleFactor = slice / total;
IAlgorithm_sptr scale = AlgorithmManager::Instance().create("Scale");
scale->initialize();
scale->setProperty("InputWorkspace", monName.toStdString());
scale->setProperty("Factor", scaleFactor);
scale->setProperty("OutputWorkspace", "__" + monName.toStdString() + "_temp");
scale->execute();
IAlgorithm_sptr rebinDet =
AlgorithmManager::Instance().create("RebinToWorkspace");
rebinDet->initialize();
rebinDet->setProperty("WorkspaceToRebin", sliceName.toStdString());
rebinDet->setProperty("WorkspaceToMatch",
"__" + monName.toStdString() + "_temp");
rebinDet->setProperty("OutputWorkspace", sliceName.toStdString());
rebinDet->setProperty("PreserveEvents", false);
rebinDet->execute();
IAlgorithm_sptr append = AlgorithmManager::Instance().create("AppendSpectra");
append->initialize();
append->setProperty("InputWorkspace1",
"__" + monName.toStdString() + "_temp");
append->setProperty("InputWorkspace2", sliceName.toStdString());
append->setProperty("OutputWorkspace", sliceName.toStdString());
append->setProperty("MergeLogs", true);
append->execute();
// Remove temporary monitor ws
AnalysisDataService::Instance().remove("__" + monName.toStdString() +
"_temp");
return sliceSuffix;
}
/** Plots any currently selected rows */
void ReflDataProcessorPresenter::plotRow() {
const auto selectedData = m_manager->selectedData();
if (selectedData.size() == 0)
return;
// If slicing values are empty plot normally
auto timeSlicingValues =
m_mainPresenter->getTimeSlicingValues().toStdString();
if (timeSlicingValues.empty()) {
GenericDataProcessorPresenter::plotRow();
return;
}
// Set of workspaces to plot
QOrderedSet<QString> workspaces;
// Set of workspaces not found in the ADS
QSet<QString> notFound;
// Get the property name for the default output workspace so we
// can find the reduced workspace name for each slice
const auto outputPropertyName = m_processor.defaultOutputPropertyName();
for (const auto &selectedItem : selectedData) {
auto groupData = selectedItem.second;
for (const auto &rowItem : groupData) {
auto rowData = rowItem.second;
const size_t numSlices = rowData->numberOfSlices();
for (size_t slice = 0; slice < numSlices; slice++) {
auto sliceData = rowData->getSlice(slice);
const auto sliceName =
sliceData->preprocessedOptionValue(outputPropertyName);
if (workspaceExists(sliceName))
workspaces.insert(sliceName, nullptr);
else
notFound.insert(sliceName);
}
}
}
if (!notFound.isEmpty())
issueNotFoundWarning("rows", notFound);
plotWorkspaces(workspaces);
}
/** Plots any currently selected groups */
void ReflDataProcessorPresenter::plotGroup() {
const auto selectedData = m_manager->selectedData();
if (selectedData.size() == 0)
return;
// If slicing values are empty plot normally
auto timeSlicingValues = m_mainPresenter->getTimeSlicingValues();
if (timeSlicingValues.isEmpty()) {
GenericDataProcessorPresenter::plotGroup();
return;
}
// Set of workspaces to plot
QOrderedSet<QString> workspaces;
// Set of workspaces not found in the ADS
QSet<QString> notFound;
for (const auto &selectedItem : selectedData) {
auto groupIndex = selectedItem.first;
auto groupData = selectedItem.second;
// Only consider multi-row groups
if (groupData.size() < 2)
continue;
// We should always have a record of the number of slices for this group
if (m_numGroupSlicesMap.count(groupIndex) < 1)
throw std::runtime_error("Invalid group data for group " +
std::to_string(groupIndex));
size_t numSlices = m_numGroupSlicesMap.at(groupIndex);
for (size_t slice = 0; slice < numSlices; slice++) {
const auto wsName = getPostprocessedWorkspaceName(groupData, slice);
if (workspaceExists(wsName))
workspaces.insert(wsName, nullptr);
else
notFound.insert(wsName);
}
}
if (!notFound.isEmpty())
issueNotFoundWarning("groups", notFound);
plotWorkspaces(workspaces);
}
/** Asks user if they wish to proceed if the AnalysisDataService contains input
* workspaces of a specific type
*
* @param data :: The data selected in the table
* @param findEventWS :: Whether or not we are searching for event workspaces
* @return :: Boolean - true if user wishes to proceed, false if not
*/
bool ReflDataProcessorPresenter::proceedIfWSTypeInADS(const TreeData &data,
const bool findEventWS) {
QStringList foundInputWorkspaces;
for (const auto &item : data) {
const auto group = item.second;
for (const auto &row : group) {
bool runFound = false;
auto runNo = row.second->value(0);
auto outName = findRunInADS(runNo, "TOF_", runFound);
if (runFound) {
bool isEventWS = retrieveWorkspace(outName) != nullptr;
if (findEventWS == isEventWS) {
foundInputWorkspaces.append(outName);
} else if (isEventWS) { // monitors must be loaded
auto monName = outName + "_monitors";
if (!workspaceExists(monName))
foundInputWorkspaces.append(outName);
}
}
}
}
if (foundInputWorkspaces.size() > 0) {
// Input workspaces of type found, ask user if they wish to process
auto foundStr = foundInputWorkspaces.join("\n");
bool process = m_view->askUserYesNo(
"Processing selected rows will replace the following workspaces:\n\n" +
foundStr + "\n\nDo you wish to continue?",
"Process selected rows?");
if (process) {
// Remove all found workspaces
for (auto &wsName : foundInputWorkspaces) {
AnalysisDataService::Instance().remove(wsName.toStdString());
}
}
return process;
}
// No input workspaces of type found, proceed with reduction automatically
return true;
}
/** Add entry for the number of slices for all rows in a group
*
* @param groupID :: The ID of the group
* @param numSlices :: Number of slices
*/
void ReflDataProcessorPresenter::addNumGroupSlicesEntry(int groupID,
size_t numSlices) {
m_numGroupSlicesMap[groupID] = numSlices;
}
/** Get the processing options for a given row
*
* @param data : the row data
* @throws :: if the settings the user entered are invalid
* */
OptionsMap ReflDataProcessorPresenter::getProcessingOptions(RowData_sptr data) {
// Return the global settings but also include the transmission runs,
// which vary depending on which row is being processed
auto options = m_processingOptions;
// Get the angle for the current row. The angle is the second data item
if (!hasAngle(data)) {
if (m_mainPresenter->hasPerAngleOptions()) {
// The user has specified per-angle transmission runs on the settings
// tab. In theory this is fine, but it could cause confusion when the
// angle is not available in the data processor table because the
// per-angle transmission runs will NOT be used. However, the angle will
// be updated in the table AFTER reduction is run, so it might look like
// it should have been used (and it WILL be used next time if reduction
// is re-run).
throw std::runtime_error(
"An angle must be specified for all rows because "
"per-angle transmission runs are specified in the "
"Settings tab. Please enter angles for all runs, "
"or remove the per-angle settings.");
} else {
// If per-angle transmission runs are not set then it's fine to just use
// any default transmission runs, which will already be in the options.
return options;
}
}
// Get the options for this angle
auto optionsForAngle =
convertOptionsFromQMap(m_mainPresenter->getOptionsForAngle(angle(data)));
// Add the default options (only added if per-angle options don't exist)
optionsForAngle.insert(options.begin(), options.end());
return optionsForAngle;
}
}
}