Newer
Older
#include "MantidDataObjects/PeaksWorkspace.h"
#include "MantidKernel/System.h"
#include "MantidDataObjects/MDEventFactory.h"
#include "MantidDataObjects/MDHistoWorkspace.h"
#include "MantidKernel/VMD.h"
#include <boost/math/special_functions/fpclassify.hpp>
#include <boost/type_traits/integral_constant.hpp>
#include <vector>
using namespace Mantid::Kernel;
using namespace Mantid::API;
using namespace Mantid::DataObjects;
using namespace Mantid::DataObjects;
namespace Mantid {
namespace MDAlgorithms {
namespace {
// ---------- Template deduction of the event type
// --------------------------------
// See boost::type_traits documentation
/// Type trait to indicate that a general type is not a full MDEvent
template <typename MDE, size_t nd> struct IsFullEvent : boost::false_type {};
/// Specialization of type trait to indicate that a MDEvent is a full event
template <size_t nd> struct IsFullEvent<MDEvent<nd>, nd> : boost::true_type {};
/**
* Specialization if isFullEvent for DataObjects
* to return true
*/
template <typename MDE, size_t nd>
bool isFullMDEvent(const boost::true_type &) {
return true;
}
/**
* Specialization if isFullEvent for DataObjects
* to return false
*/
template <typename MDE, size_t nd>
bool isFullMDEvent(const boost::false_type &) {
return false;
}
/**
* Returns true if the templated type is a full MDEvent
*/
template <typename MDE, size_t nd> bool isFullMDEvent() {
return isFullMDEvent<MDE, nd>(IsFullEvent<MDE, nd>());
}
/**
* Add the detectors from the given box as contributing detectors to the peak
* @param peak :: The peak that relates to the box
* @param box :: A reference to the box containing the peak
*/
template <typename MDE, size_t nd>
void addDetectors(DataObjects::Peak &peak, MDBoxBase<MDE, nd> &box,
const boost::true_type &) {
if (box.getNumChildren() > 0) {
std::cerr << "Box has children\n";
addDetectors(peak, box, boost::true_type());
}
MDBox<MDE, nd> *mdBox = dynamic_cast<MDBox<MDE, nd> *>(&box);
if (!mdBox) {
throw std::invalid_argument("FindPeaksMD::addDetectors - Unexpected Box "
"type, cannot retrieve events");
const auto &events = mdBox->getConstEvents();
auto itend = events.end();
for (auto it = events.begin(); it != itend; ++it) {
peak.addContributingDetID(it->getDetectorID());
}
/// Add detectors based on lean events. Always throws as they do not know their
/// IDs
template <typename MDE, size_t nd>
void addDetectors(DataObjects::Peak &, MDBoxBase<MDE, nd> &,
const boost::false_type &) {
throw std::runtime_error("FindPeaksMD - Workspace contains lean events, "
"cannot include detector information");
}
/**
* Add the detectors from the given box as contributing detectors to the peak
* @param peak :: The peak that relates to the box
* @param box :: A reference to the box containing the peak
*/
template <typename MDE, size_t nd>
void addDetectors(DataObjects::Peak &peak, MDBoxBase<MDE, nd> &box) {
// Compile time deduction of the correct function call
addDetectors(peak, box, IsFullEvent<MDE, nd>());
}
}
// Register the algorithm into the AlgorithmFactory
DECLARE_ALGORITHM(FindPeaksMD)
//----------------------------------------------------------------------------------------------
/** Constructor
*/
FindPeaksMD::FindPeaksMD()
: peakWS(), peakRadiusSquared(), DensityThresholdFactor(0.0), m_maxPeaks(0),
m_addDetectors(true), m_densityScaleFactor(1e-6), prog(NULL), inst(),
m_runNumber(-1), dimType(), m_goniometer() {}
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//----------------------------------------------------------------------------------------------
/** Destructor
*/
FindPeaksMD::~FindPeaksMD() {}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
/** Initialize the algorithm's properties.
*/
void FindPeaksMD::init() {
declareProperty(new WorkspaceProperty<IMDWorkspace>("InputWorkspace", "",
Direction::Input),
"An input MDEventWorkspace or MDHistoWorkspace with at least "
"3 dimensions.");
declareProperty(
new PropertyWithValue<double>("PeakDistanceThreshold", 0.1,
Direction::Input),
"Threshold distance for rejecting peaks that are found to be too close "
"from each other.\n"
"This should be some multiple of the radius of a peak. Default: 0.1.");
declareProperty(
new PropertyWithValue<int64_t>("MaxPeaks", 500, Direction::Input),
"Maximum number of peaks to find. Default: 500.");
declareProperty(new PropertyWithValue<double>("DensityThresholdFactor", 10.0,
Direction::Input),
"The overall signal density of the workspace will be "
"multiplied by this factor \n"
"to get a threshold signal density below which boxes are NOT "
"considered to be peaks. See the help.\n"
"Default: 10.0");
declareProperty(new WorkspaceProperty<PeaksWorkspace>("OutputWorkspace", "",
Direction::Output),
"An output PeaksWorkspace with the peaks' found positions.");
declareProperty("AppendPeaks", false,
"If checked, then append the peaks in the output workspace "
"if it exists. \n"
"If unchecked, the output workspace is replaced (Default).");
}
//----------------------------------------------------------------------------------------------
/** Extract needed data from the workspace's experiment info */
void FindPeaksMD::readExperimentInfo(const ExperimentInfo_sptr &ei,
const IMDWorkspace_sptr &ws) {
// Instrument associated with workspace
inst = ei->getInstrument();
// Find the run number
m_runNumber = ei->getRunNumber();
// Check that the workspace dimensions are in Q-sample-frame or Q-lab-frame.
std::string dim0 = ws->getDimension(0)->getName();
if (dim0 == "H") {
dimType = HKL;
throw std::runtime_error(
"Cannot find peaks in a workspace that is already in HKL space.");
} else if (dim0 == "Q_lab_x") {
dimType = QLAB;
} else if (dim0 == "Q_sample_x")
dimType = QSAMPLE;
else
throw std::runtime_error(
"Unexpected dimensions: need either Q_lab_x or Q_sample_x.");
// Find the goniometer rotation matrix
m_goniometer =
Mantid::Kernel::Matrix<double>(3, 3, true); // Default IDENTITY matrix
try {
m_goniometer = ei->mutableRun().getGoniometerMatrix();
} catch (std::exception &e) {
g_log.warning() << "Error finding goniometer matrix. It will not be set in "
"the peaks found." << std::endl;
g_log.warning() << e.what() << std::endl;
}
//----------------------------------------------------------------------------------------------
/** Create and add a Peak to the output workspace
*
* @param Q :: Q_lab or Q_sample, depending on workspace
* @param binCount :: bin count to give to the peak.
*/
void FindPeaksMD::addPeak(const V3D &Q, const double binCount) {
try {
auto p = this->createPeak(Q, binCount);
if (p->getDetectorID() != -1)
peakWS->addPeak(*p);
} catch (std::exception &e) {
g_log.notice() << "Error creating peak at " << Q << " because of '"
<< e.what() << "'. Peak will be skipped." << std::endl;
}
/**
* Creates a Peak object from Q & bin count
* */
boost::shared_ptr<DataObjects::Peak>
FindPeaksMD::createPeak(const Mantid::Kernel::V3D &Q, const double binCount) {
boost::shared_ptr<DataObjects::Peak> p;
if (dimType == QLAB) {
// Build using the Q-lab-frame constructor
p = boost::shared_ptr<DataObjects::Peak>(new Peak(inst, Q));
// Save gonio matrix for later
p->setGoniometerMatrix(m_goniometer);
} else if (dimType == QSAMPLE) {
// Build using the Q-sample-frame constructor
p = boost::shared_ptr<DataObjects::Peak>(new Peak(inst, Q, m_goniometer));
}
try { // Look for a detector
p->findDetector();
} catch (...) { /* Ignore errors in ray-tracer */
p->setBinCount(binCount);
// Save the run number found before.
p->setRunNumber(m_runNumber);
return p;
}
//----------------------------------------------------------------------------------------------
/** Integrate the peaks of the workspace using parameters saved in the algorithm
* class
* @param ws :: MDEventWorkspace to integrate
*/
template <typename MDE, size_t nd>
void FindPeaksMD::findPeaks(typename MDEventWorkspace<MDE, nd>::sptr ws) {
if (nd < 3)
throw std::invalid_argument("Workspace must have at least 3 dimensions.");
if (isFullMDEvent<MDE, nd>()) {
m_addDetectors = true;
} else {
m_addDetectors = false;
g_log.warning("Workspace contains only lean events. Resultant "
"PeaksWorkspaces will not contain full detector "
"information.");
}
progress(0.01, "Refreshing Centroids");
// TODO: This might be slow, progress report?
// Make sure all centroids are fresh
// ws->getBox()->refreshCentroid();
if (ws->getNumExperimentInfo() == 0)
throw std::runtime_error(
"No instrument was found in the MDEventWorkspace. Cannot find peaks.");
for (uint16_t iexp = 0; iexp < ws->getNumExperimentInfo(); iexp++) {
ExperimentInfo_sptr ei = ws->getExperimentInfo(iexp);
this->readExperimentInfo(ei, boost::dynamic_pointer_cast<IMDWorkspace>(ws));
// Copy the instrument, sample, run to the peaks workspace.
peakWS->copyExperimentInfoFrom(ei.get());
// Calculate a threshold below which a box is too diffuse to be considered a
// peak.
signal_t thresholdDensity = ws->getBox()->getSignalNormalized() *
DensityThresholdFactor * m_densityScaleFactor;
if (boost::math::isnan(thresholdDensity) ||
(thresholdDensity == std::numeric_limits<double>::infinity()) ||
(thresholdDensity == -std::numeric_limits<double>::infinity())) {
g_log.warning()
<< "Infinite or NaN overall density found. Your input data "
"may be invalid. Using a 0 threshold instead." << std::endl;
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
thresholdDensity = 0;
}
g_log.notice() << "Threshold signal density: " << thresholdDensity
<< std::endl;
typedef API::IMDNode *boxPtr;
// We will fill this vector with pointers to all the boxes (up to a given
// depth)
typename std::vector<API::IMDNode *> boxes;
// Get all the MDboxes
progress(0.10, "Getting Boxes");
ws->getBox()->getBoxes(boxes, 1000, true);
// This pair is the <density, ptr to the box>
typedef std::pair<double, API::IMDNode *> dens_box;
// Map that will sort the boxes by increasing density. The key = density;
// value = box *.
typename std::multimap<double, API::IMDNode *> sortedBoxes;
// --------------- Sort and Filter by Density -----------------------------
progress(0.20, "Sorting Boxes by Density");
auto it1 = boxes.begin();
auto it1_end = boxes.end();
for (; it1 != it1_end; it1++) {
auto box = *it1;
double density = box->getSignalNormalized() * m_densityScaleFactor;
// Skip any boxes with too small a signal density.
if (density > thresholdDensity)
sortedBoxes.insert(dens_box(density, box));
}
Janik Zikovsky
committed
// --------------- Find Peak Boxes -----------------------------
// List of chosen possible peak boxes.
std::vector<API::IMDNode *> peakBoxes;
prog = new Progress(this, 0.30, 0.95, m_maxPeaks);
// used for selecting method for calculating BinCount
bool isMDEvent(ws->id().find("MDEventWorkspace") != std::string::npos);
int64_t numBoxesFound = 0;
// Now we go (backwards) through the map
// e.g. from highest density down to lowest density.
typename std::multimap<double, boxPtr>::reverse_iterator it2;
typename std::multimap<double, boxPtr>::reverse_iterator it2_end =
sortedBoxes.rend();
for (it2 = sortedBoxes.rbegin(); it2 != it2_end; it2++) {
signal_t density = it2->first;
boxPtr box = it2->second;
coord_t boxCenter[nd];
box->calculateCentroid(boxCenter);
const coord_t *boxCenter = box->getCentroid();
// Compare to all boxes already picked.
bool badBox = false;
for (typename std::vector<boxPtr>::iterator it3 = peakBoxes.begin();
it3 != peakBoxes.end(); it3++) {
coord_t otherCenter[nd];
(*it3)->calculateCentroid(otherCenter);
const coord_t *otherCenter = (*it3)->getCentroid();
// Distance between this box and a box we already put in.
coord_t distSquared = 0.0;
for (size_t d = 0; d < nd; d++) {
coord_t dist = otherCenter[d] - boxCenter[d];
distSquared += (dist * dist);
}
// Reject this box if it is too close to another previously found box.
if (distSquared < peakRadiusSquared) {
badBox = true;
break;
}
Janik Zikovsky
committed
// The box was not rejected for another reason.
if (!badBox) {
if (numBoxesFound++ >= m_maxPeaks) {
g_log.notice() << "Number of peaks found exceeded the limit of "
<< m_maxPeaks << ". Stopping peak finding."
<< std::endl;
break;
}
peakBoxes.push_back(box);
for (size_t d = 0; d < nd; d++)
g_log.debug() << (d > 0 ? "," : "") << boxCenter[d];
g_log.debug() << "; Density = " << density << std::endl;
// Report progres for each box found.
prog->report("Finding Peaks");
Janik Zikovsky
committed
}
}
prog->resetNumSteps(numBoxesFound, 0.95, 1.0);
// --- Convert the "boxes" to peaks ----
for (typename std::vector<boxPtr>::iterator it3 = peakBoxes.begin();
it3 != peakBoxes.end(); it3++) {
// The center of the box = Q in the lab frame
boxPtr box = *it3;
coord_t boxCenter[nd];
box->calculateCentroid(boxCenter);
const coord_t *boxCenter = box->getCentroid();
// Q of the centroid of the box
V3D Q(boxCenter[0], boxCenter[1], boxCenter[2]);
// The "bin count" used will be the box density or the number of events in
// the box
double binCount = box->getSignalNormalized() * m_densityScaleFactor;
if (isMDEvent)
binCount = static_cast<double>(box->getNPoints());
try {
auto p = this->createPeak(Q, binCount);
if (m_addDetectors) {
auto mdBox = dynamic_cast<MDBoxBase<MDE, nd> *>(box);
if (!mdBox) {
throw std::runtime_error("Failed to cast box to MDBoxBase");
}
addDetectors(*p, *mdBox);
}
if (p->getDetectorID() != -1) {
peakWS->addPeak(*p);
g_log.information() << "Add new peak with Q-center = " << Q[0] << ", "
<< Q[1] << ", " << Q[2] << "\n";
}
} catch (std::exception &e) {
g_log.notice() << "Error creating peak at " << Q << " because of '"
<< e.what() << "'. Peak will be skipped." << std::endl;
// Report progress for each box found.
prog->report("Adding Peaks");
} // for each box found
g_log.notice() << "Number of peaks found: " << peakWS->getNumberPeaks()
<< std::endl;
}
//----------------------------------------------------------------------------------------------
/** Find peaks in the given MDHistoWorkspace
*
* @param ws :: MDHistoWorkspace
*/
void FindPeaksMD::findPeaksHisto(
Mantid::DataObjects::MDHistoWorkspace_sptr ws) {
size_t nd = ws->getNumDims();
if (nd < 3)
throw std::invalid_argument("Workspace must have at least 3 dimensions.");
g_log.warning("Workspace is an MDHistoWorkspace. Resultant PeaksWorkspaces "
"will not contain full detector information.");
if (ws->getNumExperimentInfo() == 0)
throw std::runtime_error(
"No instrument was found in the workspace. Cannot find peaks.");
for (uint16_t iexp = 0; iexp < ws->getNumExperimentInfo(); iexp++) {
ExperimentInfo_sptr ei = ws->getExperimentInfo(iexp);
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
this->readExperimentInfo(ei, boost::dynamic_pointer_cast<IMDWorkspace>(ws));
// Copy the instrument, sample, run to the peaks workspace.
peakWS->copyExperimentInfoFrom(ei.get());
// This pair is the <density, box index>
typedef std::pair<double, size_t> dens_box;
// Map that will sort the boxes by increasing density. The key = density;
// value = box index.
std::multimap<double, size_t> sortedBoxes;
size_t numBoxes = ws->getNPoints();
// --------- Count the overall signal density -----------------------------
progress(0.10, "Counting Total Signal");
double totalSignal = 0;
for (size_t i = 0; i < numBoxes; i++)
totalSignal += ws->getSignalAt(i);
// Calculate the threshold density
double thresholdDensity =
(totalSignal * ws->getInverseVolume() / double(numBoxes)) *
DensityThresholdFactor * m_densityScaleFactor;
if ((thresholdDensity != thresholdDensity) ||
(thresholdDensity == std::numeric_limits<double>::infinity()) ||
(thresholdDensity == -std::numeric_limits<double>::infinity())) {
g_log.warning()
<< "Infinite or NaN overall density found. Your input data "
"may be invalid. Using a 0 threshold instead." << std::endl;
thresholdDensity = 0;
}
g_log.notice() << "Threshold signal density: " << thresholdDensity
<< std::endl;
// -------------- Sort and Filter by Density -----------------------------
progress(0.20, "Sorting Boxes by Density");
for (size_t i = 0; i < numBoxes; i++) {
double density = ws->getSignalNormalizedAt(i) * m_densityScaleFactor;
// Skip any boxes with too small a signal density.
if (density > thresholdDensity)
sortedBoxes.insert(dens_box(density, i));
// --------------- Find Peak Boxes -----------------------------
// List of chosen possible peak boxes.
std::vector<size_t> peakBoxes;
prog = new Progress(this, 0.30, 0.95, m_maxPeaks);
int64_t numBoxesFound = 0;
// Now we go (backwards) through the map
// e.g. from highest density down to lowest density.
std::multimap<double, size_t>::reverse_iterator it2;
std::multimap<double, size_t>::reverse_iterator it2_end =
sortedBoxes.rend();
for (it2 = sortedBoxes.rbegin(); it2 != it2_end; ++it2) {
signal_t density = it2->first;
size_t index = it2->second;
// Get the center of the box
VMD boxCenter = ws->getCenter(index);
// Compare to all boxes already picked.
bool badBox = false;
for (std::vector<size_t>::iterator it3 = peakBoxes.begin();
it3 != peakBoxes.end(); ++it3) {
VMD otherCenter = ws->getCenter(*it3);
// Distance between this box and a box we already put in.
coord_t distSquared = 0.0;
for (size_t d = 0; d < nd; d++) {
coord_t dist = otherCenter[d] - boxCenter[d];
distSquared += (dist * dist);
}
// Reject this box if it is too close to another previously found box.
if (distSquared < peakRadiusSquared) {
badBox = true;
break;
}
// The box was not rejected for another reason.
if (!badBox) {
if (numBoxesFound++ >= m_maxPeaks) {
g_log.notice() << "Number of peaks found exceeded the limit of "
<< m_maxPeaks << ". Stopping peak finding."
<< std::endl;
break;
}
peakBoxes.push_back(index);
g_log.debug() << "Found box at index " << index;
g_log.debug() << "; Density = " << density << std::endl;
// Report progres for each box found.
prog->report("Finding Peaks");
}
// --- Convert the "boxes" to peaks ----
for (std::vector<size_t>::iterator it3 = peakBoxes.begin();
it3 != peakBoxes.end(); ++it3) {
size_t index = *it3;
// The center of the box = Q in the lab frame
VMD boxCenter = ws->getCenter(index);
// Q of the centroid of the box
V3D Q(boxCenter[0], boxCenter[1], boxCenter[2]);
// The "bin count" used will be the box density.
double binCount = ws->getSignalNormalizedAt(index) * m_densityScaleFactor;
// Create the peak
addPeak(Q, binCount);
// Report progres for each box found.
prog->report("Adding Peaks");
} // for each box found
}
g_log.notice() << "Number of peaks found: " << peakWS->getNumberPeaks()
<< std::endl;
//----------------------------------------------------------------------------------------------
/** Execute the algorithm.
*/
void FindPeaksMD::exec() {
bool AppendPeaks = getProperty("AppendPeaks");
// Output peaks workspace, create if needed
peakWS = getProperty("OutputWorkspace");
if (!peakWS || !AppendPeaks)
peakWS = PeaksWorkspace_sptr(new PeaksWorkspace());
// The MDEventWorkspace as input
IMDWorkspace_sptr inWS = getProperty("InputWorkspace");
MDHistoWorkspace_sptr inMDHW =
boost::dynamic_pointer_cast<MDHistoWorkspace>(inWS);
IMDEventWorkspace_sptr inMDEW =
boost::dynamic_pointer_cast<IMDEventWorkspace>(inWS);
// Other parameters
double PeakDistanceThreshold = getProperty("PeakDistanceThreshold");
peakRadiusSquared =
static_cast<coord_t>(PeakDistanceThreshold * PeakDistanceThreshold);
DensityThresholdFactor = getProperty("DensityThresholdFactor");
m_maxPeaks = getProperty("MaxPeaks");
// Execute the proper algo based on the type of workspace
if (inMDHW) {
this->findPeaksHisto(inMDHW);
} else if (inMDEW) {
CALL_MDEVENT_FUNCTION3(this->findPeaks, inMDEW);
} else {
throw std::runtime_error("This algorithm can only find peaks on a "
"MDHistoWorkspace or a MDEventWorkspace; it does "
"not work on a regular MatrixWorkspace.");
}
delete prog;
// Do a sort by bank name and then descending bin count (intensity)
std::vector<std::pair<std::string, bool>> criteria;
criteria.push_back(std::pair<std::string, bool>("RunNumber", true));
criteria.push_back(std::pair<std::string, bool>("BankName", true));
criteria.push_back(std::pair<std::string, bool>("bincount", false));
peakWS->sort(criteria);
// Save the output
setProperty("OutputWorkspace", peakWS);
}
} // namespace DataObjects