Newer
Older
#include "MantidQtSliceViewer/LineViewer.h"
#include <qwt_plot_curve.h>
#include "MantidKernel/VMD.h"
#include "MantidGeometry/MDGeometry/MDTypes.h"
#include "MantidAPI/IAlgorithm.h"
#include "MantidAPI/FrameworkManager.h"
#include "MantidAPI/AnalysisDataService.h"
#include <QIntValidator>
using namespace Mantid::API;
namespace MantidQt
{
namespace SliceViewer
{
LineViewer::LineViewer(QWidget *parent)
: QWidget(parent),
m_numBins(100)
// --------- Create the plot -----------------
m_plotLayout = new QHBoxLayout(ui.frmPlot);
m_plot = new QwtPlot();
m_plot->autoRefresh();
m_plot->setBackgroundColor(QColor(255,255,255)); // White background
m_plotLayout->addWidget(m_plot, 1);
// Make the 2 curves
m_previewCurve = new QwtPlotCurve("Preview");
m_fullCurve = new QwtPlotCurve("Integrated");
m_previewCurve->attach(m_plot);
m_fullCurve->attach(m_plot);
m_previewCurve->setVisible(false);
m_fullCurve->setVisible(false);
// Make the splitter use the minimum size for the controls and not stretch out
ui.splitter->setStretchFactor(0, 0);
ui.splitter->setStretchFactor(1, 1);
//----------- Connect signals -------------
QObject::connect(ui.btnApply, SIGNAL(clicked()), this, SLOT(apply()));
QObject::connect(ui.chkAdaptiveBins, SIGNAL( stateChanged(int)), this, SLOT(adaptiveBinsChanged()));
QObject::connect(ui.spinNumBins, SIGNAL(valueChanged(int)), this, SLOT(numBinsChanged()));
}
LineViewer::~LineViewer()
{
}
//-----------------------------------------------------------------------------------------------
/** With the workspace set, create the dimension text boxes */
void LineViewer::createDimensionWidgets()
{
// Create all necessary widgets
if (m_startText.size() < int(m_ws->getNumDims()))
{
for (size_t d=m_startText.size(); d<m_ws->getNumDims(); d++)
{
QLabel * dimLabel = new QLabel(this);
dimLabel->setAlignment(Qt::AlignHCenter);
ui.gridLayout->addWidget(dimLabel, 0, int(d)+1);
m_dimensionLabel.push_back(dimLabel);
QLineEdit * startText = new QLineEdit(this);
QLineEdit * endText = new QLineEdit(this);
QLineEdit * widthText = new QLineEdit(this);
startText->setMaximumWidth(120);
endText->setMaximumWidth(120);
widthText->setMaximumWidth(120);
startText->setToolTip("Start point of the line in this dimension");
endText->setToolTip("End point of the line in this dimension");
widthText->setToolTip("Width of the line in this dimension");
startText->setValidator(new QDoubleValidator(startText));
endText->setValidator(new QDoubleValidator(endText));
widthText->setValidator(new QDoubleValidator(widthText));
ui.gridLayout->addWidget(startText, 1, int(d)+1);
ui.gridLayout->addWidget(endText, 2, int(d)+1);
ui.gridLayout->addWidget(widthText, 3, int(d)+1);
m_startText.push_back(startText);
m_endText.push_back(endText);
m_widthText.push_back(widthText);
// Signals that don't change
QObject::connect(widthText, SIGNAL(textEdited(QString)), this, SLOT(widthTextEdited()));
}
}
// ------ Update the widgets -------------------------
for (int d=0; d<int(m_ws->getNumDims()); d++)
{
m_dimensionLabel[d]->setText( QString::fromStdString(m_ws->getDimension( size_t(d))->getName() ) );
}
}
//-----------------------------------------------------------------------------------------------
/** Disable any controls relating to dimensions that are not "free"
* e.g. if you are in the X-Y plane, the Z position cannot be changed.
*/
for (int d=0; d<int(m_ws->getNumDims()); d++)
{
// Can always change the start value
m_startText[d]->setEnabled(true);
// This dimension is free to move if b == true
bool b = (m_allDimsFree || d == m_freeDimX || d == m_freeDimY);
m_endText[d]->setEnabled(b);
// If all dims are free, width makes little sense. Only allow one (circular) width
if (m_allDimsFree)
m_widthText[d]->setVisible(d != 0);
m_widthText[d]->setVisible(!b);
m_widthText[d]->setToolTip("Integration width in this dimension.");
// --- Adjust the signals ---
m_startText[d]->disconnect();
m_endText[d]->disconnect();
if (d == m_freeDimX || d == m_freeDimY)
{
// Free dimension - update the preview
QObject::connect(m_startText[d], SIGNAL(textEdited(QString)), this, SLOT(startEndTextEdited()));
QObject::connect(m_endText[d], SIGNAL(textEdited(QString)), this, SLOT(startEndTextEdited()));
}
else
{
// Non-Free dimension - link start to end
QObject::connect(m_startText[d], SIGNAL(textEdited(QString)), this, SLOT(startLinkedToEndText()));
}
}
// But enable the width setting on the free X dimension
if (!m_allDimsFree)
{
m_widthText[m_freeDimX]->setVisible(true);
m_widthText[m_freeDimX]->setToolTip("Integration width perpendicular to the line, along the chosen plane.");
}
//-----------------------------------------------------------------------------------------------
/** Show the start/end/width points in the GUI */
void LineViewer::updateStartEnd()
{
for (int d=0; d<int(m_ws->getNumDims()); d++)
{
m_startText[d]->setText(QString::number(m_start[d]));
m_endText[d]->setText(QString::number(m_end[d]));
m_widthText[d]->setText(QString::number(m_width[d]));
}
}
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
//-----------------------------------------------------------------------------------------------
/** Read all the text boxes and interpret their values.
* Does not refresh.
*/
void LineViewer::readTextboxes()
{
VMD start = m_start;
VMD end = m_start;
VMD width = m_width;
bool allOk = true;
for (int d=0; d<int(m_ws->getNumDims()); d++)
{
bool ok;
start[d] = m_startText[d]->text().toDouble(&ok);
allOk = allOk && ok;
end[d] = m_endText[d]->text().toDouble(&ok);
allOk = allOk && ok;
width[d] = m_widthText[d]->text().toDouble(&ok);
allOk = allOk && ok;
}
// Only continue if all values typed were valid numbers.
if (!allOk) return;
m_start = start;
m_end = end;
m_width = width;
}
//-----------------------------------------------------------------------------------------------
/** Perform the 1D integration using the current parameters */
void LineViewer::apply()
{
if (m_allDimsFree)
throw std::runtime_error("Not currently supported with all dimensions free!");
std::string outWsName = m_ws->getName() + "_line" ;
bool adaptive = ui.chkAdaptiveBins->isChecked();
// (half-width in the plane)
double planeWidth = this->getPlanarWidth();
// Length of the line
double length = (m_end - m_start).norm();
double dx = m_end[m_freeDimX] - m_start[m_freeDimX];
double dy = m_end[m_freeDimY] - m_start[m_freeDimY];
// Angle of the line
double angle = atan2(dy, dx);
double perpAngle = angle + M_PI / 2.0;
// Build the basis vectors using the angles
VMD basisX = m_start * 0;
basisX[m_freeDimX] = cos(angle);
basisX[m_freeDimY] = sin(angle);
VMD basisY = m_start * 0;
basisY[m_freeDimX] = cos(perpAngle);
basisY[m_freeDimY] = sin(perpAngle);
// Offset the origin in the plane by the width
VMD origin = m_start - basisY * planeWidth;
// And now offset by the width in each direction
for (int d=0; d<int(m_ws->getNumDims()); d++)
{
if ((d != m_freeDimX) && (d != m_freeDimY))
origin[d] -= m_width[d];
}
IAlgorithm * alg = NULL;
size_t numBins = m_numBins;
if (adaptive)
{
alg = FrameworkManager::Instance().createAlgorithm("SliceMD");
// "SplitInto" parameter
numBins = 2;
}
else
alg = FrameworkManager::Instance().createAlgorithm("BinMD");
alg->setProperty("InputWorkspace", m_ws);
alg->setPropertyValue("OutputWorkspace", outWsName);
alg->setProperty("AxisAligned", false);
// The X basis vector
alg->setPropertyValue("BasisVectorX", "X,units," + basisX.toString(",")
+ "," + Strings::toString(length) + "," + Strings::toString(numBins) );
// The Y basis vector, with one bin
alg->setPropertyValue("BasisVectorY", "Y,units," + basisY.toString(",")
+ "," + Strings::toString(planeWidth*2.0) + ",1" );
// Now each remaining dimension
std::string dimChars = "XYZT"; // SlicingAlgorithm::getDimensionChars();
size_t propNum = 2;
for (int d=0; d<int(m_ws->getNumDims()); d++)
{
if ((d != m_freeDimX) && (d != m_freeDimY))
{
// Letter of the dimension
std::string dim(" "); dim[0] = dimChars[propNum];
// Simple basis vector going only in this direction
VMD basis = m_start * 0;
basis[d] = 1.0;
// Set the basis vector with the width *2 and 1 bin
alg->setPropertyValue("BasisVector" + dim, dim +",units," + basis.toString(",")
+ "," + Strings::toString(m_width[d]*2.0) + ",1" );
propNum++;
if (propNum >= dimChars.size())
throw std::runtime_error("LineViewer::apply(): too many dimensions!");
}
}
alg->setPropertyValue("Origin", origin.toString(",") );
if (!adaptive)
{
alg->setProperty("IterateEvents", true);
}
alg->execute();
if (alg->isExecuted())
{
//m_sliceWS = alg->getProperty("OutputWorkspace");
m_sliceWS = boost::dynamic_pointer_cast<IMDWorkspace>(AnalysisDataService::Instance().retrieve(outWsName));
this->showFull();
}
}
// ==============================================================================================
// ================================== SLOTS =====================================================
// ==============================================================================================
//-------------------------------------------------------------------------------------------------
/** Slot called when the start text of a non-free dimensions is changed.
* Changes the end text correspondingly
void LineViewer::startLinkedToEndText()
{
for (int d=0; d<int(m_ws->getNumDims()); d++)
{
if (d != m_freeDimX && d != m_freeDimY)
{
// Copy the start text to the end text
m_endText[d]->setText( m_startText[d]->text() );
}
}
// Call the slot to update the preview
startEndTextEdited();
}
//-------------------------------------------------------------------------------------------------
/** Slot called when any of the start/end text boxes are edited
* in GUI. Only changes the values if they are all valid.
*/
void LineViewer::startEndTextEdited()
{
this->readTextboxes();
// Send the signal that the positions changed
emit changedStartOrEnd(m_start, m_end);
/** Slot called when the width text box is edited */
void LineViewer::widthTextEdited()
{
this->readTextboxes();
//TODO: Don't always auto-apply
this->apply();
// Send the signal that the width changed
emit changedPlanarWidth(this->getPlanarWidth());
/** Slot called when the number of bins changes */
void LineViewer::numBinsChanged()
{
m_numBins = ui.spinNumBins->value();
//TODO: Don't always auto-apply
this->apply();
}
/** Slot called when checking the adaptive box */
void LineViewer::adaptiveBinsChanged()
{
//TODO: Don't always auto-apply
this->apply();
}
// ==============================================================================================
// ================================== External Getters ==========================================
// ==============================================================================================
/** @return the width in the plane, or the width in dimension 0 if not restricted to a plane */
double LineViewer::getPlanarWidth() const
{
if (m_allDimsFree)
return m_width[0];
else
return m_width[m_freeDimX];
}
/// @return the full width vector in each dimensions
Mantid::Kernel::VMD LineViewer::getWidth() const
{
return m_width;
}
// ==============================================================================================
// ================================== External Setters ==========================================
// ==============================================================================================
//-----------------------------------------------------------------------------------------------
/** Set the workspace being sliced
*
* @param ws :: IMDWorkspace */
void LineViewer::setWorkspace(Mantid::API::IMDWorkspace_sptr ws)
{
m_ws = ws;
m_width = VMD(ws->getNumDims());
createDimensionWidgets();
}
/** Set the start point of the line to integrate
* @param start :: vector for the start point */
void LineViewer::setStart(Mantid::Kernel::VMD start)
{
if (m_ws && start.getNumDims() != m_ws->getNumDims())
throw std::runtime_error("LineViewer::setStart(): Invalid number of dimensions in the start vector.");
}
/** Set the end point of the line to integrate
* @param end :: vector for the end point */
void LineViewer::setEnd(Mantid::Kernel::VMD end)
{
if (m_ws && end.getNumDims() != m_ws->getNumDims())
throw std::runtime_error("LineViewer::setEnd(): Invalid number of dimensions in the end vector.");
updateStartEnd();
}
/** Set the width of the line in each dimensions
* @param width :: vector for the width in each dimension. X dimension stands in for the XY plane width */
void LineViewer::setWidth(Mantid::Kernel::VMD width)
{
if (m_ws && width.getNumDims() != m_ws->getNumDims())
throw std::runtime_error("LineViewer::setwidth(): Invalid number of dimensions in the width vector.");
m_width = width;
updateStartEnd();
/** Set the width of the line in the planar dimension only.
* Other dimensions' widths will follow unless they were manually changed
* @param width :: width in the plane. */
void LineViewer::setPlanarWidth(double width)
{
if (m_allDimsFree)
{
for (size_t d=0; d<m_width.getNumDims(); d++)
m_width[d] = width;
}
else
{
double oldPlanarWidth = this->getPlanarWidth();
for (size_t d=0; d<m_width.getNumDims(); d++)
{
// Only modify the locked onese
if (m_width[d] == oldPlanarWidth)
m_width[d] = width;
}
// And always set the one
m_width[m_freeDimX] = width;
}
updateStartEnd();
}
/** Set the number of bins in the line
* @param nbins :: # of bins */
void LineViewer::setNumBins(size_t numBins)
{
m_numBins = numBins;
ui.spinNumBins->blockSignals(true);
ui.spinNumBins->setValue( int(numBins) );
ui.spinNumBins->blockSignals(false);
/** Set the free dimensions - dimensions that are allowed to change
*
* @param all :: Flag that is true when all dimensions are allowed to change
* @param dimX :: Index of the X dimension in the 2D slice
* @param dimY :: Index of the Y dimension in the 2D slice
*/
void LineViewer::setFreeDimensions(bool all, int dimX, int dimY)
{
int nd = int(m_ws->getNumDims());
if (dimX < 0 || dimX >= nd)
throw std::runtime_error("LineViewer::setFreeDimensions(): Free X dimension index is out of range.");
if (dimY < 0 || dimY >= nd)
throw std::runtime_error("LineViewer::setFreeDimensions(): Free Y dimension index is out of range.");
m_allDimsFree = all;
m_freeDimX = dimX;
m_freeDimY = dimY;
this->updateFreeDimensions();
/** Slot called to set the free dimensions (called from the SliceViewer widget)
*
* @param dimX :: index of the X-dimension of the plane
* @param dimY :: index of the Y-dimension of the plane
*/
void LineViewer::setFreeDimensions(size_t dimX, size_t dimY)
{
m_allDimsFree = false;
m_freeDimX = int(dimX);
m_freeDimY = int(dimY);
this->updateFreeDimensions();
}
// ==============================================================================================
// ================================== Rendering =================================================
// ==============================================================================================
/** Calculate a curve between two points given a linear start/end point
*
* @param ws :: MDWorkspace to plot
* @param start :: start point in ND
* @param end :: end point in ND
* @param minNumPoints :: minimum number of points to plot
* @param curve :: curve to set
*/
void LineViewer::calculateCurve(IMDWorkspace_sptr ws, VMD start, VMD end,
size_t minNumPoints, QwtPlotCurve * curve)
// Use the width of the plot (in pixels) to choose the fineness)
// That way, there is ~1 point per pixel = as fine as it needs to be
size_t numPoints = size_t(m_plot->width());
if (numPoints < minNumPoints) numPoints = minNumPoints;
VMD step = (end-start) / double(numPoints);
double stepLength = step.norm();
// These will be the curve as plotted
double * x = new double[numPoints];
double * y = new double[numPoints];
for (size_t i=0; i<numPoints; i++)
{
// Coordinate along the line
VMD coord = start + step * double(i);
// Signal in the WS at that coordinate
signal_t signal = ws->getSignalAtCoord(coord);
// Make into array
x[i] = stepLength * double(i);
y[i] = signal;
}
// Make the curve
curve->setData(x,y, int(numPoints));
}
/** Calculate and show the preview (non-integrated) line */
void LineViewer::showPreview()
{
calculateCurve(m_ws, m_start, m_end, 100, m_previewCurve);
if (m_fullCurve->isVisible())
{
m_fullCurve->setVisible(false);
m_fullCurve->detach();
m_previewCurve->attach(m_plot);
}
m_previewCurve->setVisible(true);
m_plot->replot();
m_plot->setTitle("Preview Plot");
}
/** Calculate and show the full (integrated) line */
void LineViewer::showFull()
{
if (!m_sliceWS) return;
VMD start(m_sliceWS->getNumDims());
start *= 0;
VMD end = start;
end[0] = m_sliceWS->getDimension(0)->getMaximum();
calculateCurve(m_sliceWS, start, end, m_numBins, m_fullCurve);
if (m_previewCurve->isVisible())
{
m_previewCurve->setVisible(false);
m_previewCurve->detach();
m_fullCurve->attach(m_plot);
}
m_fullCurve->setVisible(true);
m_plot->replot();
m_plot->setTitle("Integrated Line Plot");