Skip to content
Snippets Groups Projects
ScanningWorkspaceBuilder.cpp 11.5 KiB
Newer Older
#include "MantidDataObjects/ScanningWorkspaceBuilder.h"

#include "MantidAPI/WorkspaceFactory.h"
#include "MantidDataObjects/Workspace2D.h"
#include "MantidDataObjects/WorkspaceCreation.h"
#include "MantidGeometry/Instrument.h"
#include "MantidGeometry/Instrument/DetectorInfo.h"
#include "MantidHistogramData/BinEdges.h"
#include "MantidHistogramData/Histogram.h"
#include "MantidHistogramData/LinearGenerator.h"
#include "MantidHistogramData/Points.h"
#include "MantidTypes/SpectrumDefinition.h"

using namespace Mantid::API;
using namespace Mantid::HistogramData;
using namespace Mantid::Indexing;
namespace DataObjects {
/**
 * Create the scanning workspace builder. Time ranges must still be set before
 *this can be used.
 *
 * @param instrument A pointer to the base instrument for the workspace
 * @param nTimeIndexes The number of time indexes to create
 * @param nBins The number of bins (or points) for each spectrum
 * @param isPointData If true will use points for the x-axis instead of bins
 */
ScanningWorkspaceBuilder::ScanningWorkspaceBuilder(
    const boost::shared_ptr<const Geometry::Instrument> &instrument,
    const size_t nTimeIndexes, const size_t nBins, const bool isPointData)
    : m_nDetectors(instrument->getNumberDetectors()),
      m_nTimeIndexes(nTimeIndexes), m_nBins(nBins), m_instrument(instrument),
      m_histogram(BinEdges(nBins + 1, LinearGenerator(1.0, 1.0)),
Ian Bush's avatar
Ian Bush committed
                  Counts(nBins, 0.0)),
      m_indexingType(IndexingType::Default) {
  if (isPointData)
    m_histogram = HistogramData::Histogram(Points(nBins), Counts(nBins, 0.0));
}
/**
 * Set a histogram to be used for all the workspace spectra. This can be used to
 *set the correct bin edges, but only if the binning is identical for every
 *spectra.
 *
 * @param histogram A histogram with bin edges defined
 */
void ScanningWorkspaceBuilder::setHistogram(
    HistogramData::Histogram histogram) {
Ian Bush's avatar
Ian Bush committed
  if (histogram.size() != m_nBins)
    throw std::logic_error(
        "Histogram supplied does not have the correct size.");

  m_histogram = std::move(histogram);
/**
 * Set time ranges from a vector of start time, end time pairs.
 *
 * @param timeRanges A vector of DateAndTime pairs, corresponding to the start
 *and end times
 */
void ScanningWorkspaceBuilder::setTimeRanges(std::vector<
    std::pair<Types::Core::DateAndTime, Types::Core::DateAndTime>> timeRanges) {
  verifyTimeIndexSize(timeRanges.size(), "start time, end time pairs");
  m_timeRanges = std::move(timeRanges);
/**
 * Set time ranges from a start time and a vector of durations
 *
 * @param startTime A DateAndTime object corresponding to the start of the first
 *scan
 * @param durations A vector of doubles containing the duration in seconds
 */
void ScanningWorkspaceBuilder::setTimeRanges(
    const Types::Core::DateAndTime &startTime,
    const std::vector<double> &durations) {
  verifyTimeIndexSize(durations.size(), "time durations");

LamarMoore's avatar
LamarMoore committed
  std::vector<std::pair<Types::Core::DateAndTime, Types::Core::DateAndTime>>
      timeRanges = {
          std::pair<Types::Core::DateAndTime, Types::Core::DateAndTime>(
              startTime, startTime + durations[0])};

  for (size_t i = 1; i < m_nTimeIndexes; ++i) {
    const auto newStartTime = timeRanges[i - 1].second;
    const auto endTime = newStartTime + durations[i];
LamarMoore's avatar
LamarMoore committed
    timeRanges.push_back(
        std::pair<Types::Core::DateAndTime, Types::Core::DateAndTime>(
            newStartTime, endTime));
  setTimeRanges(std::move(timeRanges));
/**
 * Supply a vector of vectors which contain positions. The inner vectors should
 *contain the position for each time index, the outer vector the vector for each
 *detector.
 *
 * @param positions A vector of vectors containing positions
 */
void ScanningWorkspaceBuilder::setPositions(
    std::vector<std::vector<Kernel::V3D>> positions) {

  if (!m_positions.empty() || !m_instrumentAngles.empty())
    throw std::logic_error("Can not set positions, as positions or instrument "
                           "angles have already been set.");

  for (const auto &vector : positions) {
    verifyTimeIndexSize(vector.size(), "positions");
  }
  verifyDetectorSize(positions.size(), "positions");

  m_positions = std::move(positions);
/**
 * Supply a vector of vectors which contain rotations. The inner vectors should
 *contain the rotation for each time index, the outer vector the vector for each
 *detector.
 *
 * @param rotations A vector of vectors containing rotations
 */
void ScanningWorkspaceBuilder::setRotations(
    std::vector<std::vector<Kernel::Quat>> rotations) {

  if (!m_rotations.empty() || !m_instrumentAngles.empty())
    throw std::logic_error("Can not set rotations, as rotations or instrument "
                           "angles have already been set.");

  for (const auto &vector : rotations) {
    verifyTimeIndexSize(vector.size(), "rotations");
  }
  verifyDetectorSize(rotations.size(), "rotations");

  m_rotations = std::move(rotations);
/**
 * Set a vector of rotations corresponding to each time index. These angles
 *rotate the detector banks around the source, setting the corresponding
 *positions and rotations of the detectors.
 *
 * Here explicit assumptions are made - that the source is at (0, 0, 0), and the
 *rotation is in the X-Z plane. This corresponds to the common case of moving
 *detectors to increase angular coverage.
 *
 * @param relativeRotations a vector of angles, the size matching the number of
 * @param rotationPosition the position to rotate around, e.g. the sample
 *position
 * @param rotationAxis the axis to rotate around. e.g. the vertical axis to
 *rotate the instrument in the horizontal plane
void ScanningWorkspaceBuilder::setRelativeRotationsForScans(
    std::vector<double> relativeRotations, const Kernel::V3D &rotationPosition,
    const Kernel::V3D &rotationAxis) {

  if (!m_positions.empty() || !m_rotations.empty())
    throw std::logic_error("Can not set instrument angles, as positions and/or "
                           "rotations have already been set.");

  verifyTimeIndexSize(relativeRotations.size(), "instrument angles");
  m_instrumentAngles = std::move(relativeRotations);
  m_rotationPosition = rotationPosition;
  m_rotationAxis = rotationAxis;
/**
 * Set the indexing type, either to time or detector oriented indexing.
 *
 * @param indexingType An index type enum
 */
void ScanningWorkspaceBuilder::setIndexingType(
    const IndexingType indexingType) {
  if (m_indexingType != IndexingType::Default)
    throw std::logic_error("Indexing type has been set already.");

  m_indexingType = indexingType;
}

/**
 * Verify everything has been set that is required and return the workspace.
 *
 * @return Workspace2D with the scanning information set
 */
MatrixWorkspace_sptr ScanningWorkspaceBuilder::buildWorkspace() const {
  auto outputWorkspace = create<Workspace2D>(
      m_instrument, m_nDetectors * m_nTimeIndexes, m_histogram);

  auto &outputDetectorInfo = outputWorkspace->mutableDetectorInfo();
  outputDetectorInfo.setScanInterval(m_timeRanges[0]);
  buildOutputDetectorInfo(outputDetectorInfo);
  if (!m_positions.empty())
    buildPositions(outputDetectorInfo);

  if (!m_rotations.empty())
    buildRotations(outputDetectorInfo);

  if (!m_instrumentAngles.empty())
    buildRelativeRotationsForScans(outputDetectorInfo);

  switch (m_indexingType) {
  case IndexingType::Default:
    outputWorkspace->setIndexInfo(
        Indexing::IndexInfo(m_nDetectors * m_nTimeIndexes));
    break;
  case IndexingType::TimeOriented:
    createTimeOrientedIndexInfo(*outputWorkspace);
  case IndexingType::DetectorOriented:
    createDetectorOrientedIndexInfo(*outputWorkspace);
  return boost::shared_ptr<MatrixWorkspace>(std::move(outputWorkspace));
void ScanningWorkspaceBuilder::buildOutputDetectorInfo(
    Geometry::DetectorInfo &outputDetectorInfo) const {
  auto mergeWorkspace =
      create<Workspace2D>(m_instrument, m_nDetectors, m_histogram.binEdges());
  for (size_t i = 1; i < m_nTimeIndexes; ++i) {
    auto &mergeDetectorInfo = mergeWorkspace->mutableDetectorInfo();
    mergeDetectorInfo.setScanInterval(m_timeRanges[i]);
    outputDetectorInfo.merge(mergeDetectorInfo);
  }
}

void ScanningWorkspaceBuilder::buildRotations(
    Geometry::DetectorInfo &outputDetectorInfo) const {
  for (size_t i = 0; i < m_nDetectors; ++i) {
    for (size_t j = 0; j < m_nTimeIndexes; ++j) {
      outputDetectorInfo.setRotation({i, j}, m_rotations[i][j]);
    }
  }
}

void ScanningWorkspaceBuilder::buildPositions(
    Geometry::DetectorInfo &outputDetectorInfo) const {
  for (size_t i = 0; i < m_nDetectors; ++i) {
    for (size_t j = 0; j < m_nTimeIndexes; ++j) {
      outputDetectorInfo.setPosition({i, j}, m_positions[i][j]);
    }
  }
}

void ScanningWorkspaceBuilder::buildRelativeRotationsForScans(
    Geometry::DetectorInfo &outputDetectorInfo) const {
  for (size_t i = 0; i < outputDetectorInfo.size(); ++i) {
    for (size_t j = 0; j < outputDetectorInfo.scanCount(i); ++j) {
      if (outputDetectorInfo.isMonitor({i, j}))
        continue;
      auto position = outputDetectorInfo.position({i, j});
      const auto rotation = Kernel::Quat(m_instrumentAngles[j], m_rotationAxis);
      position -= m_rotationPosition;
      rotation.rotate(position);
      position += m_rotationPosition;
      outputDetectorInfo.setPosition({i, j}, position);
      const auto &oldRotation = outputDetectorInfo.rotation({i, j});
      outputDetectorInfo.setRotation({i, j}, rotation * oldRotation);
void ScanningWorkspaceBuilder::createTimeOrientedIndexInfo(
    MatrixWorkspace &ws) const {
  auto indexInfo = ws.indexInfo();
  auto spectrumDefinitions =
      std::vector<SpectrumDefinition>(m_nDetectors * m_nTimeIndexes);
  for (size_t detIndex = 0; detIndex < m_nDetectors; ++detIndex) {
    for (size_t timeIndex = 0; timeIndex < m_nTimeIndexes; ++timeIndex) {
      spectrumDefinitions[detIndex * m_nTimeIndexes + timeIndex].add(detIndex,
                                                                     timeIndex);
  indexInfo.setSpectrumDefinitions(spectrumDefinitions);
  ws.setIndexInfo(indexInfo);
void ScanningWorkspaceBuilder::createDetectorOrientedIndexInfo(
    MatrixWorkspace &ws) const {
  auto indexInfo = ws.indexInfo();
  auto spectrumDefinitions =
      std::vector<SpectrumDefinition>(m_nDetectors * m_nTimeIndexes);
  for (size_t timeIndex = 0; timeIndex < m_nTimeIndexes; ++timeIndex) {
    for (size_t detIndex = 0; detIndex < m_nDetectors; ++detIndex) {
      spectrumDefinitions[timeIndex * m_nDetectors + detIndex].add(detIndex,
                                                                   timeIndex);
  indexInfo.setSpectrumDefinitions(spectrumDefinitions);
  ws.setIndexInfo(indexInfo);
void ScanningWorkspaceBuilder::verifyTimeIndexSize(
    const size_t timeIndexSize, const std::string &description) const {
  if (timeIndexSize != m_nTimeIndexes) {
    throw std::logic_error(
        "Number of " + description +
        " supplied does not match the number of time indexes.");
void ScanningWorkspaceBuilder::verifyDetectorSize(
    const size_t detectorSize, const std::string &description) const {
  if (detectorSize != m_nDetectors) {
    throw std::logic_error("Number of " + description +
                           " supplied does not match the number of detectors.");
  }
}

void ScanningWorkspaceBuilder::validateInputs() const {
  if (m_timeRanges.empty())
    throw std::logic_error("Can not build workspace - time ranges have not "
                           "been set. Please call setTimeRanges() before "
                           "building.");
}

} // namespace DataObjects