Esempio n. 1
0
/** Gets the distances between the source and detectors whose IDs you pass to it
*  @param WS :: the input workspace
*  @param mon0Spec :: Spectrum number of the output from the first monitor
*  @param mon1Spec :: Spectrum number of the output from the second monitor
*  @param monitor0Dist :: the calculated distance to the detector whose ID was
* passed to this function first
*  @param monitor1Dist :: calculated distance to the detector whose ID was
* passed to this function second
*  @throw NotFoundError if no detector is found for the detector ID given
*/
void GetEi::getGeometry(API::MatrixWorkspace_const_sptr WS, specid_t mon0Spec,
                        specid_t mon1Spec, double &monitor0Dist,
                        double &monitor1Dist) const {
    const IComponent_const_sptr source = WS->getInstrument()->getSource();

    // retrieve a pointer to the first detector and get its distance
    size_t monWI = 0;
    try {
        monWI = WS->getIndexFromSpectrumNumber(mon0Spec);
    } catch (std::runtime_error &) {
        g_log.error()
                << "Could not find the workspace index for the monitor at spectrum "
                << mon0Spec << "\n";
        g_log.error() << "Error retrieving data for the first monitor" << std::endl;
        throw std::bad_cast();
    }
    const std::set<detid_t> &dets = WS->getSpectrum(monWI)->getDetectorIDs();

    if (dets.size() != 1) {
        g_log.error() << "The detector for spectrum number " << mon0Spec
                      << " was either not found or is a group, grouped monitors "
                      "are not supported by this algorithm\n";
        g_log.error() << "Error retrieving data for the first monitor" << std::endl;
        throw std::bad_cast();
    }
    IDetector_const_sptr det = WS->getInstrument()->getDetector(*dets.begin());
    monitor0Dist = det->getDistance(*(source.get()));

    // repeat for the second detector
    try {
        monWI = WS->getIndexFromSpectrumNumber(mon0Spec);
    } catch (std::runtime_error &) {
        g_log.error()
                << "Could not find the workspace index for the monitor at spectrum "
                << mon0Spec << "\n";
        g_log.error() << "Error retrieving data for the second monitor\n";
        throw std::bad_cast();
    }
    const std::set<detid_t> &dets2 = WS->getSpectrum(monWI)->getDetectorIDs();
    if (dets2.size() != 1) {
        g_log.error() << "The detector for spectrum number " << mon1Spec
                      << " was either not found or is a group, grouped monitors "
                      "are not supported by this algorithm\n";
        g_log.error() << "Error retrieving data for the second monitor\n";
        throw std::bad_cast();
    }
    det = WS->getInstrument()->getDetector(*dets2.begin());
    monitor1Dist = det->getDistance(*(source.get()));
}
Esempio n. 2
0
/** sets up the object with workspace data and calculates cached values ready to
* calculate gravitional
*  effects across a spectrum
*  @param ws :: the workspace that contains the neutron counts
*  @param det :: the detector for which the calculations will be for
*/
GravitySANSHelper::GravitySANSHelper(API::MatrixWorkspace_const_sptr ws,
                                     Geometry::IDetector_const_sptr det)
    : m_beamLineNorm(-1), m_det(det), m_dropPerAngstrom2(-1), m_cachedDrop(0) {
  m_samplePos = ws->getInstrument()->getSample()->getPos();
  const V3D sourcePos = ws->getInstrument()->getSource()->getPos();
  m_beamLine = m_samplePos - sourcePos;

  m_beamLineNorm = 2.0 * (m_samplePos - sourcePos).norm();

  // this is the LineOfSight assuming no drop, the drop is added (and
  // subtracted) later in the code when required
  m_cachedLineOfSight = m_det->getPos() - m_samplePos;
  // the drop is proportional to the wave length squared and using this to do
  // the full calculation only once increases the speed a lot
  m_dropPerAngstrom2 = ws->gravitationalDrop(m_det, 1e-10);
}
Esempio n. 3
0
// read the monitors list from the workspace and try to do it once for any
// particular ws;
bool MonIDPropChanger::monitorIdReader(
    API::MatrixWorkspace_const_sptr inputWS) const {
  // no workspace
  if (!inputWS)
    return false;

  // no instrument
  Geometry::Instrument_const_sptr pInstr = inputWS->getInstrument();
  if (!pInstr)
    return false;

  std::vector<detid_t> mon = pInstr->getMonitors();
  if (mon.empty()) {
    if (iExistingAllowedValues.empty()) {
      return false;
    } else {
      iExistingAllowedValues.clear();
      return true;
    }
  }
  // are these monitors really there?
  // got the index of correspondent spectra.
  std::vector<size_t> indexList = inputWS->getIndicesFromDetectorIDs(mon);
  if (indexList.empty()) {
    if (iExistingAllowedValues.empty()) {
      return false;
    } else {
      iExistingAllowedValues.clear();
      return true;
    }
  }
  // index list can be less or equal to the mon list size (some monitors do not
  // have spectra)
  size_t mon_count =
      (mon.size() < indexList.size()) ? mon.size() : indexList.size();
  std::vector<int> allowed_values(mon_count);
  for (size_t i = 0; i < mon_count; i++) {
    allowed_values[i] = mon[i];
  }

  // are known values the same as the values we have just identified?
  if (iExistingAllowedValues.size() != mon_count) {
    iExistingAllowedValues.clear();
    iExistingAllowedValues.assign(allowed_values.begin(), allowed_values.end());
    return true;
  }
  // the monitor list has the same size as before. Is it equivalent to the
  // existing one?
  bool values_redefined = false;
  for (size_t i = 0; i < mon_count; i++) {
    if (iExistingAllowedValues[i] != allowed_values[i]) {
      values_redefined = true;
      iExistingAllowedValues[i] = allowed_values[i];
    }
  }
  return values_redefined;
}
Esempio n. 4
0
/** Checks that the two input workspace have common binning & size, the same instrument & unit.
 *  Also calls the checkForOverlap method.
 *  @param ws1 :: The first input workspace
 *  @param ws2 :: The second input workspace
 *  @throw std::invalid_argument If the workspaces are not compatible
 */
void ConjoinWorkspaces::validateInputs(API::MatrixWorkspace_const_sptr ws1, API::MatrixWorkspace_const_sptr ws2) const
{
  // This is the full check for common binning
  if ( !WorkspaceHelpers::commonBoundaries(ws1) || !WorkspaceHelpers::commonBoundaries(ws2) )
  {
    g_log.error("Both input workspaces must have common binning for all their spectra");
    throw std::invalid_argument("Both input workspaces must have common binning for all their spectra");
  }

  if ( ws1->getInstrument()->getName() != ws2->getInstrument()->getName() )
  {
    const std::string message("The input workspaces are not compatible because they come from different instruments");
    g_log.error(message);
    throw std::invalid_argument(message);
  }

  Unit_const_sptr ws1_unit = ws1->getAxis(0)->unit();
  Unit_const_sptr ws2_unit = ws2->getAxis(0)->unit();
  const std::string ws1_unitID = ( ws1_unit ? ws1_unit->unitID() : "" );
  const std::string ws2_unitID = ( ws2_unit ? ws2_unit->unitID() : "" );

  if ( ws1_unitID != ws2_unitID )
  {
    const std::string message("The input workspaces are not compatible because they have different units on the X axis");
    g_log.error(message);
    throw std::invalid_argument(message);
  }

  if ( ws1->isDistribution()   != ws2->isDistribution() )
  {
    const std::string message("The input workspaces have inconsistent distribution flags");
    g_log.error(message);
    throw std::invalid_argument(message);
  }

  if ( !WorkspaceHelpers::matchingBins(ws1,ws2,true) )
  {
    const std::string message("The input workspaces are not compatible because they have different binning");
    g_log.error(message);
    throw std::invalid_argument(message);
  }

  this->checkForOverlap(ws1,ws2, true);
}
Esempio n. 5
0
double ConvertEmptyToTof::getL2(API::MatrixWorkspace_const_sptr workspace,
                                int detId) {
  // Get a pointer to the instrument contained in the workspace
  Geometry::Instrument_const_sptr instrument = workspace->getInstrument();
  // Get the distance between the source and the sample (assume in metres)
  Geometry::IComponent_const_sptr sample = instrument->getSample();
  // Get the sample-detector distance for this detector (in metres)
  double l2 =
      workspace->getDetector(detId)->getPos().distance(sample->getPos());
  return l2;
}
/** Checks that the axes of the input workspaces match and creates the output
 * workspace if necessary
 *  @param w1 ::  The first input workspace
 *  @param w2 ::  The second input workspace
 *  @param out :: Pointer to the output workspace
 */
void PointByPointVCorrection::check_validity(
    API::MatrixWorkspace_const_sptr &w1, API::MatrixWorkspace_const_sptr &w2,
    API::MatrixWorkspace_sptr &out) {
  // First check that the instrument matches for both input workspaces
  if (w1->getInstrument()->getName() != w2->getInstrument()->getName()) {
    g_log.error("The input workspaces have different instrument definitions");
    throw std::runtime_error(
        "The input workspaces have different instrument definitions");
  }
  // Check that the two workspaces are the same size
  if (w1->size() != w2->size()) {
    g_log.error("The input workspaces are not the same size");
    throw std::runtime_error("The input workspaces are not the same size");
  }
  // Now check that the bins match
  if (!WorkspaceHelpers::matchingBins(*w1, *w2)) {
    g_log.error("The input workspaces have different binning");
    throw std::runtime_error("The input workspaces have different binning");
  }
  const Mantid::API::Axis *const axis1 = w1->getAxis(1);
  const Mantid::API::Axis *const axis2 = w2->getAxis(1);
  if (!((*axis1) == (*axis2))) // Spectra axis are different, so division does
                               // not make any sense
  {
    g_log.error(
        "The two workspaces InputW1 and InputW2 have different spectra list");
    throw std::runtime_error(
        "The two workspaces InputW1 and InputW2 have different spectra list");
  }

  if (out != w1 && out != w2) // Create a new workspace only if it is different
                              // from of the input ones.
  {
    out = API::WorkspaceFactory::Instance().create(w1);
    setProperty("OutputWorkspace", out);
  } else if (out == w2) {
    g_log.warning("Any masking in the output workspaces will be taken from the "
                  "vanadium workspace (InputW2)");
  }
}
Esempio n. 7
0
/**Calculates the distance a neutron coming from the sample will have deviated
* from a
*  straight tragetory before hitting a detector. If calling this function many
* times
*  for the same detector you can call this function once, with waveLength=1, and
* use
*  the fact drop is proportional to wave length squared .This function has no
* knowledge
*  of which axis is vertical for a given instrument
*  @param ws :: workspace
*  @param det :: the detector that the neutron entered
*  @param waveLength :: the neutrons wave length in meters
*  @param extraLength :: additional length
*  @return the deviation in meters
*/
double GravitySANSHelper::gravitationalDrop(API::MatrixWorkspace_const_sptr ws,
                                            Geometry::IDetector_const_sptr det,
                                            const double waveLength,
                                            const double extraLength) const {
  using namespace PhysicalConstants;
  /// Pre-factor in gravity calculation: gm^2/2h^2
  static const double gm2_OVER_2h2 =
      g * NeutronMass * NeutronMass / (2.0 * h * h);

  const V3D samplePos = ws->getInstrument()->getSample()->getPos();
  const double pathLength = det->getPos().distance(samplePos) + extraLength;
  // Want L2 (sample-pixel distance) squared, times the prefactor g^2/h^2
  const double L2 = gm2_OVER_2h2 * std::pow(pathLength, 2);

  return waveLength * waveLength * L2;
}
Esempio n. 8
0
/** Method for updating m_waveLength.
 *  If size of m_waveLength is equal to number of data (for a new instance of
 *this
 *  class this vector is empty initially) then don't recalculate it.
 *
 *  @param xValues :: x values
 *  @param nData :: length of xValues
 */
void IkedaCarpenterPV::calWavelengthAtEachDataPoint(const double *xValues,
                                                    const size_t &nData) const {
  // if wavelength vector already have the right size no need for resizing it
  // further we make the assumption that no need to recalculate this vector if
  // it already has the right size

  if (m_waveLength.size() != nData) {
    m_waveLength.resize(nData);

    Mantid::Kernel::Unit_sptr wavelength =
        Mantid::Kernel::UnitFactory::Instance().create("Wavelength");
    for (size_t i = 0; i < nData; i++) {
      m_waveLength[i] = xValues[i];
    }

    // note if a version of convertValue was added which allows a double* as
    // first argument
    // then could avoid copying above plus only have to resize m_wavelength when
    // its size smaller than nData
    API::MatrixWorkspace_const_sptr mws = getMatrixWorkspace();
    if (mws) {
      API::MatrixWorkspace_const_sptr mws = getMatrixWorkspace();
      Instrument_const_sptr instrument = mws->getInstrument();
      Geometry::IComponent_const_sptr sample = instrument->getSample();
      if (sample != nullptr) {
        convertValue(m_waveLength, wavelength, mws, m_workspaceIndex);
      } else {
        g_log.warning()
            << "No sample set for instrument in workspace.\n"
            << "Can't calculate wavelength in IkedaCarpenter.\n"
            << "Default all wavelengths to one.\n"
            << "Solution is to load appropriate instrument into workspace.\n";
        for (size_t i = 0; i < nData; i++)
          m_waveLength[i] = 1.0;
      }
    } else {
      g_log.warning() << "Workspace not set.\n"
                      << "Can't calculate wavelength in IkedaCarpenter.\n"
                      << "Default all wavelengths to one.\n"
                      << "Solution call setMatrixWorkspace() for function.\n";
      for (size_t i = 0; i < nData; i++)
        m_waveLength[i] = 1.0;
    }
  }
}
Esempio n. 9
0
    /**
     * Init variables caches
     * @param :: Workspace pointer
     */
    void SofQW2::initCachedValues(API::MatrixWorkspace_const_sptr workspace)
    {
      m_progress->report("Initializing caches");

      // Retrieve the emode & efixed properties
      const std::string emode = getProperty("EMode");
      // Convert back to an integer representation
      m_emode = 0;
      if (emode == "Direct") m_emode=1;
      else if (emode == "Indirect") m_emode = 2;
      m_efixed = getProperty("EFixed");

      // Conversion constant for E->k. k(A^-1) = sqrt(energyToK*E(meV))
      m_EtoK = 8.0*M_PI*M_PI*PhysicalConstants::NeutronMass*PhysicalConstants::meV*1e-20 / 
        (PhysicalConstants::h*PhysicalConstants::h);

      // Get a pointer to the instrument contained in the workspace
      Geometry::Instrument_const_sptr instrument = workspace->getInstrument();
      // Get the distance between the source and the sample (assume in metres)
      Geometry::IObjComponent_const_sptr source = instrument->getSource();
      Geometry::IObjComponent_const_sptr sample = instrument->getSample();
      m_samplePos = sample->getPos();
      m_beamDir = m_samplePos - source->getPos();
      m_beamDir.normalize();
      // Is the instrument set up correctly
      double l1(0.0);
      try
      {
        l1 = source->getDistance(*sample);
        g_log.debug() << "Source-sample distance: " << l1 << std::endl;
      }
      catch (Exception::NotFoundError &)
      {
        throw Exception::InstrumentDefinitionError("Unable to calculate source-sample distance", 
                                                   workspace->getTitle());
      }
      // Index Q cache
      initQCache(workspace);
    }
Esempio n. 10
0
/**
 * If the InstrumentParameter property is set then it attempts to retrieve the parameter
 * from the component, else it returns the value of the Factor property
 * @param inputWS A pointer to the input workspace
 * @param index The current index to inspect
 * @return Value for the scale factor
 */
double ScaleX::getScaleFactor(const API::MatrixWorkspace_const_sptr & inputWS, const size_t index)
{
  if(m_parname.empty()) return m_algFactor;

  // Try and get factor from component. If we see a DetectorGroup use this will use the first component
  Geometry::IDetector_const_sptr det;
  auto inst = inputWS->getInstrument();

  auto *spec = inputWS->getSpectrum(index);
  const auto & ids = spec->getDetectorIDs();
  const size_t ndets(ids.size());
  if(ndets > 0)
  {
    try
    {
      det = inst->getDetector(*ids.begin());
    }
    catch(Exception::NotFoundError&)
    {
      return 0.0;
    }
  }
  else return 0.0;

  const auto & pmap = inputWS->constInstrumentParameters();
  auto par = pmap.getRecursive(det->getComponentID(), m_parname);
  if(par)
  {
    if(!m_combine) return par->value<double>();
    else return m_binOp(m_algFactor,par->value<double>());
  }
  else
  {
    std::ostringstream os;
    os << "Spectrum at index '" << index << "' has no parameter named '" << m_parname << "'\n";
    throw std::runtime_error(os.str());
  }
}
/**Calculates the distance a neutron coming from the sample will have deviated
* from a
*  straight tragetory before hitting a detector. If calling this function many
* times
*  for the same detector you can call this function once, with waveLength=1, and
* use
*  the fact drop is proportional to wave length squared .This function has no
* knowledge
*  of which axis is vertical for a given instrument
*  @param ws :: workspace
*  @param det :: the detector that the neutron entered
*  @param waveLength :: the neutrons wave length in meters
*  @param extraLength :: additional length
*  @return the deviation in meters
*/
double GravitySANSHelper::gravitationalDrop(API::MatrixWorkspace_const_sptr ws,
        Geometry::IDetector_const_sptr det,
        const double waveLength,
        const double extraLength) const {
    using namespace PhysicalConstants;
    /// Pre-factor in gravity calculation: gm^2/2h^2
    static const double gm2_OVER_2h2 =
        g * NeutronMass * NeutronMass / (2.0 * h * h);

    const V3D samplePos = ws->getInstrument()->getSample()->getPos();

    // Perform a path length correction if an Lextra is specified.
    // The correction is Lcorr^2 = (L + Lextra)^2 -(LExtra)^2
    const auto pathLengthWithExtraLength =
        det->getPos().distance(samplePos) + extraLength;
    const auto pathLengthSquared =
        std::pow(pathLengthWithExtraLength, 2) - std::pow(extraLength, 2);

    // Want L2 (sample-pixel distance) squared, times the prefactor g^2/h^2
    const double L2 = gm2_OVER_2h2 * pathLengthSquared;

    return waveLength * waveLength * L2;
}
Esempio n. 12
0
double ConvertEmptyToTof::getL1(API::MatrixWorkspace_const_sptr workspace) {
  Geometry::Instrument_const_sptr instrument = workspace->getInstrument();
  Geometry::IComponent_const_sptr sample = instrument->getSample();
  double l1 = instrument->getSource()->getDistance(*sample);
  return l1;
}
/** method does preliminary calculations of the detectors positions to convert
results into k-dE space ;
and places the results into static cash to be used in subsequent calls to this
algorithm */
void PreprocessDetectorsToMD::processDetectorsPositions(
    const API::MatrixWorkspace_const_sptr &inputWS,
    DataObjects::TableWorkspace_sptr &targWS) {
  g_log.information()
      << "Preprocessing detector locations in a target reciprocal space\n";
  //
  Geometry::Instrument_const_sptr instrument = inputWS->getInstrument();
  // this->pBaseInstr                = instrument->baseInstrument();
  //
  Geometry::IComponent_const_sptr source = instrument->getSource();
  Geometry::IComponent_const_sptr sample = instrument->getSample();
  if ((!source) || (!sample)) {
    g_log.error() << " Instrument is not fully defined. Can not identify "
                     "source or sample\n";
    throw Kernel::Exception::InstrumentDefinitionError(
        "Instrument not sufficiently defined: failed to get source and/or "
        "sample");
  }

  // L1
  try {
    double L1 = source->getDistance(*sample);
    targWS->logs()->addProperty<double>("L1", L1, true);
    g_log.debug() << "Source-sample distance: " << L1 << '\n';
  } catch (Kernel::Exception::NotFoundError &) {
    throw Kernel::Exception::InstrumentDefinitionError(
        "Unable to calculate source-sample distance for workspace",
        inputWS->getTitle());
  }
  // Instrument name
  std::string InstrName = instrument->getName();
  targWS->logs()->addProperty<std::string>(
      "InstrumentName", InstrName,
      true); // "The name which should unique identify current instrument");
  targWS->logs()->addProperty<bool>("FakeDetectors", false, true);

  // get access to the workspace memory
  auto &sp2detMap = targWS->getColVector<size_t>("spec2detMap");
  auto &detId = targWS->getColVector<int32_t>("DetectorID");
  auto &detIDMap = targWS->getColVector<size_t>("detIDMap");
  auto &L2 = targWS->getColVector<double>("L2");
  auto &TwoTheta = targWS->getColVector<double>("TwoTheta");
  auto &Azimuthal = targWS->getColVector<double>("Azimuthal");
  auto &detDir = targWS->getColVector<Kernel::V3D>("DetDirections");

  // Efixed; do we need one and does one exist?
  double Efi = targWS->getLogs()->getPropertyValueAsType<double>("Ei");
  float *pEfixedArray(nullptr);
  const Geometry::ParameterMap &pmap = inputWS->constInstrumentParameters();
  if (m_getEFixed)
    pEfixedArray = targWS->getColDataArray<float>("eFixed");

  // check if one needs to generate masked detectors column.
  int *pMasksArray(nullptr);
  if (m_getIsMasked)
    pMasksArray = targWS->getColDataArray<int>("detMask");

  //// progress message appearance
  size_t div = 100;
  size_t nHist = targWS->rowCount();
  Mantid::API::Progress theProgress(this, 0, 1, nHist);
  //// Loop over the spectra
  uint32_t liveDetectorsCount(0);
  const auto &spectrumInfo = inputWS->spectrumInfo();
  for (size_t i = 0; i < nHist; i++) {
    sp2detMap[i] = std::numeric_limits<uint64_t>::quiet_NaN();
    detId[i] = std::numeric_limits<int32_t>::quiet_NaN();
    detIDMap[i] = std::numeric_limits<uint64_t>::quiet_NaN();
    L2[i] = std::numeric_limits<double>::quiet_NaN();
    TwoTheta[i] = std::numeric_limits<double>::quiet_NaN();
    Azimuthal[i] = std::numeric_limits<double>::quiet_NaN();
    //     detMask[i]  = true;

    if (!spectrumInfo.hasDetectors(i) || spectrumInfo.isMonitor(i))
      continue;

    // if masked detectors state is not used, masked detectors just ignored;
    bool maskDetector = spectrumInfo.isMasked(i);
    if (m_getIsMasked)
      *(pMasksArray + liveDetectorsCount) = maskDetector ? 1 : 0;
    else if (maskDetector)
      continue;

    const auto &spDet = spectrumInfo.detector(i);

    // calculate the requested values;
    sp2detMap[i] = liveDetectorsCount;
    detId[liveDetectorsCount] = int32_t(spDet.getID());
    detIDMap[liveDetectorsCount] = i;
    L2[liveDetectorsCount] = spectrumInfo.l2(i);

    double polar = spectrumInfo.twoTheta(i);
    double azim = spDet.getPhi();
    TwoTheta[liveDetectorsCount] = polar;
    Azimuthal[liveDetectorsCount] = azim;

    double sPhi = sin(polar);
    double ez = cos(polar);
    double ex = sPhi * cos(azim);
    double ey = sPhi * sin(azim);

    detDir[liveDetectorsCount].setX(ex);
    detDir[liveDetectorsCount].setY(ey);
    detDir[liveDetectorsCount].setZ(ez);

    // double sinTheta=sin(0.5*polar);
    // this->SinThetaSq[liveDetectorsCount]  = sinTheta*sinTheta;

    // specific code which should work and makes sense
    // for indirect instrument but may be deployed on any code with Ei property
    // defined;
    if (pEfixedArray) {
      try {
        Geometry::Parameter_sptr par = pmap.getRecursive(&spDet, "eFixed");
        if (par)
          Efi = par->value<double>();
      } catch (std::runtime_error &) {
      }
      // set efixed for each existing detector
      *(pEfixedArray + liveDetectorsCount) = static_cast<float>(Efi);
    }

    liveDetectorsCount++;
    if (i % div == 0)
      theProgress.report(i, "Preprocessing detectors");
  }
  targWS->logs()->addProperty<uint32_t>("ActualDetectorsNum",
                                        liveDetectorsCount, true);

  theProgress.report();
  g_log.information() << "Finished preprocessing detector locations. Found: "
                      << liveDetectorsCount << " detectors out of: " << nHist
                      << " histograms\n";
}
Esempio n. 14
0
/**
 * Apply the detector test criterion
 * @param counts1 :: A workspace containing the integrated counts of the first
 * white beam run
 * @param counts2 :: A workspace containing the integrated counts of the first
 * white beam run
 * @param average :: The computed median
 * @param variation :: The allowed variation in terms of number of medians, i.e
 * those spectra where
 * the ratio of the counts outside this range will fail the tests and will be
 * masked on counts1
 * @return number of detectors for which tests failed
 */
int DetectorEfficiencyVariation::doDetectorTests(
    API::MatrixWorkspace_const_sptr counts1,
    API::MatrixWorkspace_const_sptr counts2, const double average,
    double variation) {
  // DIAG in libISIS did this.  A variation of less than 1 doesn't make sense in
  // this algorithm
  if (variation < 1) {
    variation = 1.0 / variation;
  }
  // criterion for if the the first spectrum is larger than expected
  double largest = average * variation;
  // criterion for if the the first spectrum is lower than expected
  double lowest = average / variation;

  const int numSpec = static_cast<int>(counts1->getNumberHistograms());
  const int progStep = static_cast<int>(std::ceil(numSpec / 30.0));

  // Create a workspace for the output
  MaskWorkspace_sptr maskWS = this->generateEmptyMask(counts1);

  bool checkForMask = false;
  Geometry::Instrument_const_sptr instrument = counts1->getInstrument();
  if (instrument != nullptr) {
    checkForMask = ((instrument->getSource() != nullptr) &&
                    (instrument->getSample() != nullptr));
  }

  const double deadValue(1.0);
  int numFailed(0);
  PARALLEL_FOR3(counts1, counts2, maskWS)
  for (int i = 0; i < numSpec; ++i) {
    PARALLEL_START_INTERUPT_REGION
    // move progress bar
    if (i % progStep == 0) {
      advanceProgress(progStep * static_cast<double>(RTMarkDetects) / numSpec);
      progress(m_fracDone);
      interruption_point();
    }

    if (checkForMask) {
      const std::set<detid_t> &detids =
          counts1->getSpectrum(i)->getDetectorIDs();
      if (instrument->isMonitor(detids))
        continue;
      if (instrument->isDetectorMasked(detids)) {
        // Ensure it is masked on the output
        maskWS->dataY(i)[0] = deadValue;
        continue;
      }
    }

    const double signal1 = counts1->readY(i)[0];
    const double signal2 = counts2->readY(i)[0];

    // Mask out NaN and infinite
    if (boost::math::isinf(signal1) || boost::math::isnan(signal1) ||
        boost::math::isinf(signal2) || boost::math::isnan(signal2)) {
      maskWS->dataY(i)[0] = deadValue;
      PARALLEL_ATOMIC
      ++numFailed;
      continue;
    }

    // Check the ratio is within the given range
    const double ratio = signal1 / signal2;
    if (ratio < lowest || ratio > largest) {
      maskWS->dataY(i)[0] = deadValue;
      PARALLEL_ATOMIC
      ++numFailed;
    }

    PARALLEL_END_INTERUPT_REGION
  }
  PARALLEL_CHECK_INTERUPT_REGION

  // Register the results with the ADS
  setProperty("OutputWorkspace", maskWS);

  return numFailed;
}