-
Peterson, Peter authoredPeterson, Peter authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
CreateChunkingFromInstrument.cpp 16.60 KiB
/*WIKI*
Workflow algorithm to determine chunking strategy
for event nexus file. This will break up the instrument
into logical components according to the instrument hierarchy.
This algorithm does assume that there are components in the
geometry named "bank#" and returns a table workspace of those
names. Trying this on an instrument without components named
that will generate an exception. Also, requesting "ChunkBy=All"
will return an empty table workspace.
*WIKI*/
#include "MantidAPI/FileProperty.h"
#include "MantidAPI/MatrixWorkspace.h"
#include "MantidAPI/TableRow.h"
#include "MantidDataHandling/CreateChunkingFromInstrument.h"
#include "MantidDataObjects/Workspace2D.h"
#include "MantidGeometry/IDetector.h"
#include "MantidKernel/ListValidator.h"
#include <boost/tokenizer.hpp>
#include <nexus/NeXusFile.hpp>
#include <nexus/NeXusException.hpp>
namespace Mantid
{
namespace DataHandling
{
using namespace Mantid::API;
using namespace Mantid::DataObjects;
using namespace Mantid::Geometry;
using namespace Mantid::Kernel;
using namespace std;
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
// Register the algorithm into the AlgorithmFactory
DECLARE_ALGORITHM(CreateChunkingFromInstrument)
namespace { // anonymous namespace to hide things
/// Input file name
const string PARAM_IN_FILE("Filename");
/// Input workspace parameter name
const string PARAM_IN_WKSP("InputWorkspace");
/// Instrument name parameter name
const string PARAM_INST_NAME("InstrumentName");
/// Instrument file parameter name
const string PARAM_INST_FILE("InstrumentFilename");
/// Explicitly name instrument components
const string PARAM_CHUNK_NAMES("ChunkNames");
/// Canned instrument components names
const string PARAM_CHUNK_BY("ChunkBy");
/// Recursion depth parameter name
const string PARAM_MAX_RECURSE("MaxRecursionDepth");
/// Output workspace parameter name
const string PARAM_OUT_WKSP("OutputWorkspace");
/// Maximum number of banks to look for
const string PARAM_MAX_BANK_NUM("MaxBankNumber");
}
//----------------------------------------------------------------------------------------------
/** Constructor
*/
CreateChunkingFromInstrument::CreateChunkingFromInstrument()
{
}
//----------------------------------------------------------------------------------------------
/** Destructor
*/
CreateChunkingFromInstrument::~CreateChunkingFromInstrument()
{
}
//----------------------------------------------------------------------------------------------
/// Algorithm's name for identification. @see Algorithm::name
const string CreateChunkingFromInstrument::name() const { return "CreateChunkingFromInstrument";};
/// Algorithm's version for identification. @see Algorithm::version
int CreateChunkingFromInstrument::version() const { return 1;};
/// Algorithm's category for identification. @see Algorithm::category
const string CreateChunkingFromInstrument::category() const { return "Workflow\\DataHandling";}
//----------------------------------------------------------------------------------------------
/// Sets documentation strings for this algorithm
void CreateChunkingFromInstrument::initDocs()
{
string msg("Creates chunking at a level of the instrument or instrument components.");
this->setWikiSummary(msg);
this->setOptionalMessage(msg);
}
//----------------------------------------------------------------------------------------------
/** Initialize the algorithm's properties.
*/
void CreateChunkingFromInstrument::init()
{
// instrument selection
string grp1Name("Specify the Instrument");
std::vector<std::string> exts;
exts.push_back("_event.nxs");
exts.push_back(".nxs.h5");
exts.push_back(".nxs");
this->declareProperty(new FileProperty(PARAM_IN_FILE, "", FileProperty::OptionalLoad, exts),
"The name of the event nexus file to read, including its full or relative path." );
this->declareProperty(new WorkspaceProperty<>(PARAM_IN_WKSP,"",Direction::Input, PropertyMode::Optional),
"Optional: An input workspace with the instrument we want to use.");
this->declareProperty(new PropertyWithValue<string>(PARAM_INST_NAME,"",Direction::Input),
"Optional: Name of the instrument to base the ChunkingWorkpace on which to base the GroupingWorkspace.");
this->declareProperty(new FileProperty(PARAM_INST_FILE, "", FileProperty::OptionalLoad, ".xml"),
"Optional: Path to the instrument definition file on which to base the ChunkingWorkpace.");
this->setPropertyGroup(PARAM_IN_FILE, grp1Name);
this->setPropertyGroup(PARAM_IN_WKSP, grp1Name);
this->setPropertyGroup(PARAM_INST_NAME, grp1Name);
this->setPropertyGroup(PARAM_INST_FILE, grp1Name);
// chunking
string grp2Name("Specify Instrument Components");
declareProperty(PARAM_CHUNK_NAMES,"",
"Optional: A string of the instrument component names to use as separate groups. "
"Use / or , to separate multiple groups. "
"If empty, then an empty GroupingWorkspace will be created.");
vector<string> grouping;
grouping.push_back("");
grouping.push_back("All");
grouping.push_back("Group");
grouping.push_back("Column");
grouping.push_back("bank");
declareProperty(PARAM_CHUNK_BY, "", boost::make_shared<StringListValidator>(grouping),
"Only used if GroupNames is empty: All detectors as one group, Groups (East,West for SNAP), Columns for SNAP, detector banks");
this->setPropertyGroup(PARAM_CHUNK_NAMES, grp2Name);
this->setPropertyGroup(PARAM_CHUNK_BY, grp2Name);
// everything else
declareProperty(PARAM_MAX_RECURSE, 5,
"Number of levels to search into the instrument (default=5)");
declareProperty(PARAM_MAX_BANK_NUM, 300,
"Maximum bank number to search for in the instrument");
declareProperty(new WorkspaceProperty<API::ITableWorkspace>(PARAM_OUT_WKSP,"",Direction::Output),
"An output workspace describing the cunking.");
}
/// @copydoc Mantid::API::Algorithm::validateInputs
map<string, string> CreateChunkingFromInstrument::validateInputs()
{
map<string, string> result;
// get the input paramters
string filename = getPropertyValue(PARAM_IN_FILE);
MatrixWorkspace_sptr inWS = getProperty(PARAM_IN_WKSP);
string instName = getPropertyValue(PARAM_INST_NAME);
string instFilename = getPropertyValue(PARAM_INST_FILE);
// count how many ways the input instrument was specified
int numInst = 0;
if (!filename.empty()) numInst++;
if (inWS) numInst++;
if (!instName.empty()) numInst++;
if (!instFilename.empty()) numInst++;
// set the error bits
string msg;
if (numInst == 0)
{
msg = "Must specify instrument one way";
}
else if (numInst > 1)
{
msg = "Can only specify instrument one way";
}
if (!msg.empty())
{
result[PARAM_IN_FILE] = msg;
result[PARAM_IN_WKSP] = msg;
result[PARAM_INST_NAME] = msg;
result[PARAM_INST_FILE] = msg;
}
// get the chunking technology to use
string chunkNames = getPropertyValue(PARAM_CHUNK_NAMES);
string chunkGroups = getPropertyValue(PARAM_CHUNK_BY);
msg = "";
if (chunkNames.empty() && chunkGroups.empty())
{
msg = "Must specify either " + PARAM_CHUNK_NAMES + " or "
+ PARAM_CHUNK_BY;
}
else if ((!chunkNames.empty()) && (!chunkGroups.empty()))
{
msg = "Must specify either " + PARAM_CHUNK_NAMES + " or "
+ PARAM_CHUNK_BY + " not both";
}
if (!msg.empty())
{
result[PARAM_CHUNK_NAMES] = msg;
result[PARAM_CHUNK_BY] = msg;
}
return result;
}
/**
* Returns true if str starts with prefix.
*
* @param str The string to check.
* @param prefix The prefix to look for.
* @return true if str starts with prefix.
*/
bool startsWith(const string & str, const string & prefix)
{
// can't start with if it is shorter than the prefix
if (str.length() < prefix.length())
return false;
return (str.substr(0, prefix.length()).compare(prefix) == 0);
}
/**
* Find the name of the parent of the component that starts with the
* supplied prefix.
*
* @param comp The component to find the parent of.
* @param prefix Prefix of parent names to look for.
* @return The correct parent name. This is an empty string if the name
* isn't found.
*/
string parentName(IComponent_const_sptr comp, const string & prefix)
{
// handle the special case of the component has the name
if (startsWith(comp->getName(), prefix))
return comp->getName();
// find the parent with the correct name
IComponent_const_sptr parent = comp->getParent();
if (parent)
{
if (startsWith(parent->getName(), prefix))
return parent->getName();
else
return parentName(parent, prefix);
}
else
{
return "";
}
}
/**
* Find the name of the parent of the component that is in the list of
* parents that are being searched for.
*
* @param comp The component to find the parent of.
* @param names List of parent names to look for.
* @return The correct parent name. This is an empty string if the name
* isn't found.
*/
string parentName(IComponent_const_sptr comp, const vector<string> & names)
{
// handle the special case of the component has the name
for (auto name = names.begin(); name != names.end(); ++name)
if (name->compare(comp->getName()) == 0)
return (*name);
// find the parent with the correct name
IComponent_const_sptr parent = comp->getParent();
if (parent)
{
// see if this is the parent
for (auto name = names.begin(); name != names.end(); ++name)
if (name->compare(parent->getName()) == 0)
return (*name);
// or recurse
return parentName(parent, names);
}
else
{
return "";
}
}
/**
* Split a list of instrument components into a vector of strings.
*
* @param names Comma separated list of instrument components
* @return The vector of instrument component names.
*/
vector<string> getGroupNames(const string & names)
{
vector<string> groups;
// check that there is something
if (names.empty())
return groups;
// do the actual splitting
const boost::char_separator<char> SEPERATOR(",");
tokenizer tokens(names, SEPERATOR);
for (auto item = tokens.begin(); item != tokens.end(); ++item)
{
groups.push_back(*item);
}
return groups;
}
/**
* Determine the instrument from the various input parameters.
*
* @return The correct instrument.
*/
Instrument_const_sptr CreateChunkingFromInstrument::getInstrument()
{
// try the input workspace
MatrixWorkspace_sptr inWS = getProperty(PARAM_IN_WKSP);
if (inWS)
{
return inWS->getInstrument();
}
// temporary workspace to hang everything else off of
MatrixWorkspace_sptr tempWS(new Workspace2D());
// name of the instrument
string instName = getPropertyValue(PARAM_INST_NAME);
// see if there is an input file
string filename = getPropertyValue(PARAM_IN_FILE);
if (!filename.empty())
{
string top_entry_name("entry"); // TODO make more flexible
// get the instrument name from the filename
size_t n = filename.rfind('/');
if (n != std::string::npos)
{
std::string temp = filename.substr(n+1, filename.size()-n-1);
n = temp.find('_');
if (n != std::string::npos && n > 0)
{
instName = temp.substr(0, n);
}
}
// read information from the nexus file itself
try {
NeXus::File nxsfile(filename);
// get the run start time
string start_time;
nxsfile.openGroup(top_entry_name, "NXentry");
nxsfile.readData("start_time", start_time);
tempWS->mutableRun().addProperty("run_start", DateAndTime(start_time).toISO8601String(), true );
// get the instrument name
nxsfile.openGroup("instrument", "NXinstrument");
nxsfile.readData("name", instName);
nxsfile.closeGroup();
// Test if IDF exists in file, move on quickly if not
nxsfile.openPath("instrument/instrument_xml");
nxsfile.close();
IAlgorithm_sptr loadInst= createChildAlgorithm("LoadIDFFromNexus",0.0,0.2);
// Now execute the Child Algorithm. Catch and log any error, but don't stop.
try
{
loadInst->setPropertyValue("Filename", filename);
loadInst->setProperty<MatrixWorkspace_sptr> ("Workspace", tempWS);
loadInst->setPropertyValue("InstrumentParentPath",top_entry_name);
loadInst->execute();
}
catch( std::invalid_argument&)
{
g_log.error("Invalid argument to LoadIDFFromNexus Child Algorithm ");
}
catch (std::runtime_error&)
{
g_log.debug("No instrument definition found in "+filename+" at "+top_entry_name+"/instrument");
}
if ( loadInst->isExecuted() )
return tempWS->getInstrument();
else
g_log.information("No IDF loaded from Nexus file.");
} catch (::NeXus::Exception&) {
g_log.information("No instrument definition found in "+filename+" at "+top_entry_name+"/instrument");
}
}
// run LoadInstrument if other methods have not run
string instFilename = getPropertyValue(PARAM_INST_FILE);
Algorithm_sptr childAlg = createChildAlgorithm("LoadInstrument",0.0,0.2);
childAlg->setProperty<MatrixWorkspace_sptr>("Workspace", tempWS);
childAlg->setPropertyValue("Filename", instFilename);
childAlg->setPropertyValue("InstrumentName", instName);
childAlg->executeAsChildAlg();
return tempWS->getInstrument();
}
//----------------------------------------------------------------------------------------------
/** Execute the algorithm.
*/
void CreateChunkingFromInstrument::exec()
{
// get the instrument
Instrument_const_sptr inst = this->getInstrument();
// setup the output workspace
ITableWorkspace_sptr strategy = WorkspaceFactory::Instance().createTable("TableWorkspace");
strategy->addColumn("str", "BankName");
this->setProperty("OutputWorkspace", strategy);
// get the correct level of grouping
string groupLevel = this->getPropertyValue(PARAM_CHUNK_BY);
vector<string> groupNames = getGroupNames(this->getPropertyValue(PARAM_CHUNK_NAMES));
if (groupLevel.compare("All") == 0)
{
groupNames.clear();
groupNames.push_back(inst->getName());
}
else if (inst->getName().compare("SNAP") == 0 && groupLevel.compare("Group") == 0)
{
groupNames.clear();
groupNames.push_back("East");
groupNames.push_back("West");
}
// set up a progress bar with the "correct" number of steps
int maxBankNum = this->getProperty(PARAM_MAX_BANK_NUM);
Progress progress(this, .2, 1., maxBankNum);
// search the instrument for the bank names
int maxRecurseDepth = this->getProperty(PARAM_MAX_RECURSE);
map<string, vector<string> > grouping;
// cppcheck-suppress syntaxError
PRAGMA_OMP(parallel for schedule(dynamic, 1) )
for (int num = 0; num < maxBankNum; ++num)
{
PARALLEL_START_INTERUPT_REGION
ostringstream mess;
mess<< "bank"<<num;
IComponent_const_sptr comp = inst->getComponentByName(mess.str(), maxRecurseDepth);
PARALLEL_CRITICAL(grouping)
if(comp)
{
// get the name of the correct parent
string parent;
if (groupNames.empty())
{
parent = parentName(comp, groupLevel);
}
else
{
parent = parentName(comp, groupNames);
}
// add it to the correct chunk
if (!parent.empty())
{
if (grouping.count(parent) == 0)
grouping[parent] = vector<string>();
grouping[parent].push_back(comp->getName());
}
}
progress.report();
PARALLEL_END_INTERUPT_REGION
}
PARALLEL_CHECK_INTERUPT_REGION
// fill in the table workspace
for (auto group = grouping.begin(); group != grouping.end(); ++group)
{
stringstream banks;
for (auto bank = group->second.begin(); bank != group->second.end(); ++bank)
banks << (*bank) << ",";
// remove the trailing comma
string banksStr = banks.str();
banksStr = banksStr.substr(0, banksStr.size()-1);
// add it to the table
TableRow row = strategy->appendRow();
row << banksStr;
}
}
} // namespace DataHandling
} // namespace Mantid