Beispiel #1
0
/** Converts X axis to theta representation
* @param progress :: Progress indicator
* @param targetUnit :: Target conversion unit
* @param inputWS :: Input Workspace
* @param nHist :: Stores the number of histograms
*/
void ConvertSpectrumAxis2::createThetaMap(API::Progress &progress,
                                          const std::string &targetUnit,
                                          API::MatrixWorkspace_sptr &inputWS,
                                          size_t nHist) {
  // Set up binding to member funtion. Avoids condition as part of loop over
  // nHistograms.
  boost::function<double(const IDetector &)> thetaFunction;
  if (targetUnit.compare("signed_theta") == 0 ||
      targetUnit.compare("SignedTheta") == 0) {
    thetaFunction =
        boost::bind(&MatrixWorkspace::detectorSignedTwoTheta, inputWS, _1);
  } else if (targetUnit == "theta" || targetUnit == "Theta") {
    thetaFunction =
        boost::bind(&MatrixWorkspace::detectorTwoTheta, inputWS, _1);
  }

  bool warningGiven = false;

  for (size_t i = 0; i < nHist; ++i) {
    try {
      IDetector_const_sptr det = inputWS->getDetector(i);
      // Invoke relevant member function.
      m_indexMap.emplace(thetaFunction(*det) * rad2deg, i);
    } catch (Exception::NotFoundError &) {
      if (!warningGiven)
        g_log.warning("The instrument definition is incomplete - spectra "
                      "dropped from output");
      warningGiven = true;
    }

    progress.report("Converting to theta...");
  }
}
Beispiel #2
0
void CalculateDIFC::calculate(API::Progress &progress,
                              API::MatrixWorkspace_sptr &outputWs,
                              DataObjects::OffsetsWorkspace_sptr &offsetsWS,
                              double l1, double beamlineNorm,
                              Kernel::V3D &beamline, Kernel::V3D &samplePos,
                              detid2det_map &allDetectors) {
  SpecialWorkspace2D_sptr localWS =
      boost::dynamic_pointer_cast<SpecialWorkspace2D>(outputWs);

  // Now go through all
  detid2det_map::const_iterator it = allDetectors.begin();
  for (; it != allDetectors.end(); ++it) {
    Geometry::IDetector_const_sptr det = it->second;
    if ((!det->isMasked()) && (!det->isMonitor())) {
      const detid_t detID = it->first;
      double offset = 0.;
      if (offsetsWS)
        offset = offsetsWS->getValue(detID, 0.);

      double difc = Geometry::Instrument::calcConversion(
          l1, beamline, beamlineNorm, samplePos, det, offset);
      difc = 1. / difc; // calcConversion gives 1/DIFC
      localWS->setValue(detID, difc);
    }

    progress.report("Calculate DIFC");
  }
}
/**
* Main work portion of algorithm. Calculates mean of standard deviation,
* ignoring
* the detectors marked as "bad", then determines if any of the detectors are
* "bad".
* @param progress :: progress indicator
* @param valid :: eventual output workspace, holding 0 for bad and 1 for good
* @param values :: stddeviations of each spectra (I think)
*/
void IdentifyNoisyDetectors::getStdDev(API::Progress &progress,
                                       MatrixWorkspace_sptr valid,
                                       MatrixWorkspace_sptr values) {
  const int nhist = static_cast<int>(valid->getNumberHistograms());
  int count = 0;
  double mean = 0.0;
  double mean2 = 0.0;

  for (int i = 0; i < nhist; i++) {
    if (valid->readY(i)[0] > 0) {
      mean += values->readY(i)[0];
      mean2 += std::pow(values->readY(i)[0], 2);
      count++;
    }

    progress.report();
  }

  if (0 == count) {
    // all values are zero, no need to loop
    return;
  }

  mean = mean / count;
  double stddev = sqrt((mean2 / count) - std::pow(mean, 2));

  double upper = mean + 3 * stddev;
  double lower = mean - 3 * stddev;
  double min = mean * 0.0001;

  for (int i = 0; i < nhist; i++) {
    double value = values->readY(i)[0];
    if (value > upper) {
      valid->dataY(i)[0] = 0.0;
    } else if (value < lower) {
      valid->dataY(i)[0] = 0.0;
    } else if (value < min) {
      valid->dataY(i)[0] = 0.0;
    }

    progress.report("Calculating StdDev...");
  }
}
/// Executes the algorithm for events
void UnaryOperation::execEvent() {
  g_log.information("Processing event workspace");

  const MatrixWorkspace_const_sptr matrixInputWS = getProperty(inputPropName());

  // generate the output workspace pointer
  API::MatrixWorkspace_sptr matrixOutputWS = getProperty(outputPropName());
  if (matrixOutputWS != matrixInputWS) {
    matrixOutputWS = matrixInputWS->clone();
    setProperty(outputPropName(), matrixOutputWS);
  }
  auto outputWS = boost::dynamic_pointer_cast<EventWorkspace>(matrixOutputWS);

  // Now fetch any properties defined by concrete algorithm
  retrieveProperties();

  int64_t numHistograms = static_cast<int64_t>(outputWS->getNumberHistograms());
  API::Progress prog = API::Progress(this, 0.0, 1.0, numHistograms);
  PARALLEL_FOR_IF(Kernel::threadSafe(*outputWS))
  for (int64_t i = 0; i < numHistograms; ++i) {
    PARALLEL_START_INTERUPT_REGION
    // switch to weighted events if needed, and use the appropriate helper
    // function
    auto &evlist = outputWS->getSpectrum(i);
    switch (evlist.getEventType()) {
    case TOF:
      // Switch to weights if needed.
      evlist.switchTo(WEIGHTED);
    /* no break */
    // Fall through

    case WEIGHTED:
      unaryOperationEventHelper(evlist.getWeightedEvents());
      break;

    case WEIGHTED_NOTIME:
      unaryOperationEventHelper(evlist.getWeightedEventsNoTime());
      break;
    }

    prog.report();
    PARALLEL_END_INTERUPT_REGION
  }
  PARALLEL_CHECK_INTERUPT_REGION

  outputWS->clearMRU();
  auto inputWS = boost::dynamic_pointer_cast<EventWorkspace>(matrixOutputWS);
  if (inputWS->getNumberEvents() != outputWS->getNumberEvents()) {
    g_log.information() << "Number of events has changed!!!\n";
  }
}
Beispiel #5
0
/** Create the final output workspace after converting the X axis
* @returns the final output workspace
*
* @param progress :: Progress indicator
* @param targetUnit :: Target conversion unit
* @param inputWS :: Input workspace
* @param nHist :: Stores the number of histograms
*/
MatrixWorkspace_sptr ConvertSpectrumAxis2::createOutputWorkspace(
    API::Progress &progress, const std::string &targetUnit,
    API::MatrixWorkspace_sptr &inputWS, size_t nHist) {
  // Create the output workspace. Can not re-use the input one because the
  // spectra are re-ordered.
  MatrixWorkspace_sptr outputWorkspace = WorkspaceFactory::Instance().create(
      inputWS, m_indexMap.size(), inputWS->x(0).size(), inputWS->y(0).size());

  // Now set up a new numeric axis holding the theta values corresponding to
  // each spectrum.
  auto const newAxis = new NumericAxis(m_indexMap.size());
  outputWorkspace->replaceAxis(1, newAxis);

  progress.setNumSteps(nHist + m_indexMap.size());

  // Set the units of the axis.
  if (targetUnit == "theta" || targetUnit == "Theta" ||
      targetUnit == "signed_theta" || targetUnit == "SignedTheta") {
    newAxis->unit() = boost::make_shared<Units::Degrees>();
  } else if (targetUnit == "ElasticQ") {
    newAxis->unit() = UnitFactory::Instance().create("MomentumTransfer");
  } else if (targetUnit == "ElasticQSquared") {
    newAxis->unit() = UnitFactory::Instance().create("QSquared");
  }

  std::multimap<double, size_t>::const_iterator it;
  size_t currentIndex = 0;
  for (it = m_indexMap.begin(); it != m_indexMap.end(); ++it) {
    // Set the axis value.
    newAxis->setValue(currentIndex, it->first);
    // Copy over the data.
    outputWorkspace->setHistogram(currentIndex, inputWS->histogram(it->second));
    // We can keep the spectrum numbers etc.
    outputWorkspace->getSpectrum(currentIndex)
        .copyInfoFrom(inputWS->getSpectrum(it->second));
    ++currentIndex;

    progress.report("Creating output workspace...");
  }
  return outputWorkspace;
}
Beispiel #6
0
/** Convert X axis to Elastic Q representation
* @param progress :: Progress indicator
* @param targetUnit :: Target conversion unit
* @param inputWS :: Input workspace
* @param nHist :: Stores the number of histograms
*/
void ConvertSpectrumAxis2::createElasticQMap(API::Progress &progress,
                                             const std::string &targetUnit,
                                             API::MatrixWorkspace_sptr &inputWS,
                                             size_t nHist) {
  IComponent_const_sptr source = inputWS->getInstrument()->getSource();
  IComponent_const_sptr sample = inputWS->getInstrument()->getSample();

  const std::string emodeStr = getProperty("EMode");
  int emode = 0;
  if (emodeStr == "Direct")
    emode = 1;
  else if (emodeStr == "Indirect")
    emode = 2;

  for (size_t i = 0; i < nHist; i++) {
    IDetector_const_sptr detector = inputWS->getDetector(i);
    double twoTheta(0.0), efixed(0.0);
    if (!detector->isMonitor()) {
      twoTheta = 0.5 * inputWS->detectorTwoTheta(*detector);
      efixed = getEfixed(detector, inputWS, emode); // get efixed
    } else {
      twoTheta = 0.0;
      efixed = DBL_MIN;
    }

    // Convert to MomentumTransfer
    double elasticQInAngstroms = Kernel::UnitConversion::run(twoTheta, efixed);

    if (targetUnit == "ElasticQ") {
      m_indexMap.emplace(elasticQInAngstroms, i);
    } else if (targetUnit == "ElasticQSquared") {
      // The QSquared value.
      double elasticQSquaredInAngstroms =
          elasticQInAngstroms * elasticQInAngstroms;

      m_indexMap.emplace(elasticQSquaredInAngstroms, i);
    }

    progress.report("Converting to Elastic Q...");
  }
}
Beispiel #7
0
/** Execute the algorithm.
 */
void LoadNXSPE::exec() {
  std::string filename = getProperty("Filename");
  // quicly check if it's really nxspe
  try {
    ::NeXus::File file(filename);
    std::string mainEntry = (*(file.getEntries().begin())).first;
    file.openGroup(mainEntry, "NXentry");
    file.openData("definition");
    if (identiferConfidence(file.getStrData()) < 1) {
      throw std::invalid_argument("Not NXSPE");
    }
    file.close();
  } catch (...) {
    throw std::invalid_argument("Not NeXus or not NXSPE");
  }

  // Load the data
  ::NeXus::File file(filename);

  std::string mainEntry = (*(file.getEntries().begin())).first;
  file.openGroup(mainEntry, "NXentry");

  file.openGroup("NXSPE_info", "NXcollection");
  std::map<std::string, std::string> entries = file.getEntries();
  std::vector<double> temporary;
  double fixed_energy, psi = 0.;

  if (!entries.count("fixed_energy")) {
    throw std::invalid_argument("fixed_energy field was not found");
  }
  file.openData("fixed_energy");
  file.getData(temporary);
  fixed_energy = temporary.at(0);
  file.closeData();

  if (entries.count("psi")) {
    file.openData("psi");
    file.getData(temporary);
    psi = temporary.at(0);
    file.closeData();
  }

  int kikfscaling = 0;
  if (entries.count("ki_over_kf_scaling")) {
    file.openData("ki_over_kf_scaling");
    std::vector<int> temporaryint;
    file.getData(temporaryint);
    kikfscaling = temporaryint.at(0);
    file.closeData();
  }

  file.closeGroup(); // NXSPE_Info

  file.openGroup("data", "NXdata");
  entries = file.getEntries();

  if (!entries.count("data")) {
    throw std::invalid_argument("data field was not found");
  }
  file.openData("data");
  ::NeXus::Info info = file.getInfo();
  std::size_t numSpectra = static_cast<std::size_t>(info.dims.at(0));
  std::size_t numBins = static_cast<std::size_t>(info.dims.at(1));
  std::vector<double> data;
  file.getData(data);
  file.closeData();

  if (!entries.count("error")) {
    throw std::invalid_argument("error field was not found");
  }
  file.openData("error");
  std::vector<double> error;
  file.getData(error);
  file.closeData();

  if (!entries.count("energy")) {
    throw std::invalid_argument("energy field was not found");
  }
  file.openData("energy");
  std::vector<double> energies;
  file.getData(energies);
  file.closeData();

  if (!entries.count("azimuthal")) {
    throw std::invalid_argument("azimuthal field was not found");
  }
  file.openData("azimuthal");
  std::vector<double> azimuthal;
  file.getData(azimuthal);
  file.closeData();

  if (!entries.count("azimuthal_width")) {
    throw std::invalid_argument("azimuthal_width field was not found");
  }
  file.openData("azimuthal_width");
  std::vector<double> azimuthal_width;
  file.getData(azimuthal_width);
  file.closeData();

  if (!entries.count("polar")) {
    throw std::invalid_argument("polar field was not found");
  }
  file.openData("polar");
  std::vector<double> polar;
  file.getData(polar);
  file.closeData();

  if (!entries.count("polar_width")) {
    throw std::invalid_argument("polar_width field was not found");
  }
  file.openData("polar_width");
  std::vector<double> polar_width;
  file.getData(polar_width);
  file.closeData();

  // distance might not have been saved in all NXSPE files
  std::vector<double> distance;
  if (entries.count("distance")) {
    file.openData("distance");
    file.getData(distance);
    file.closeData();
  }

  file.closeGroup(); // data group
  file.closeGroup(); // Main entry
  file.close();

  // check if dimensions of the vectors are correct
  if ((error.size() != data.size()) || (azimuthal.size() != numSpectra) ||
      (azimuthal_width.size() != numSpectra) || (polar.size() != numSpectra) ||
      (polar_width.size() != numSpectra) ||
      ((energies.size() != numBins) && (energies.size() != numBins + 1))) {
    throw std::invalid_argument(
        "incompatible sizes of fields in the NXSPE file");
  }

  MatrixWorkspace_sptr outputWS = boost::dynamic_pointer_cast<MatrixWorkspace>(
      WorkspaceFactory::Instance().create("Workspace2D", numSpectra,
                                          energies.size(), numBins));
  // Need to get hold of the parameter map
  outputWS->getAxis(0)->unit() = UnitFactory::Instance().create("DeltaE");
  outputWS->setYUnit("SpectraNumber");

  // add logs
  outputWS->mutableRun().addLogData(
      new PropertyWithValue<double>("Ei", fixed_energy));
  outputWS->mutableRun().addLogData(new PropertyWithValue<double>("psi", psi));
  outputWS->mutableRun().addLogData(new PropertyWithValue<std::string>(
      "ki_over_kf_scaling", kikfscaling == 1 ? "true" : "false"));

  // Set Goniometer
  Geometry::Goniometer gm;
  gm.pushAxis("psi", 0, 1, 0, psi);
  outputWS->mutableRun().setGoniometer(gm, true);

  // generate instrument
  Geometry::Instrument_sptr instrument(new Geometry::Instrument("NXSPE"));
  outputWS->setInstrument(instrument);

  Geometry::ObjComponent *source = new Geometry::ObjComponent("source");
  source->setPos(0.0, 0.0, -10.0);
  instrument->add(source);
  instrument->markAsSource(source);
  Geometry::ObjComponent *sample = new Geometry::ObjComponent("sample");
  instrument->add(sample);
  instrument->markAsSamplePos(sample);

  Geometry::Object_const_sptr cuboid(
      createCuboid(0.1, 0.1, 0.1)); // FIXME: memory hog on rendering. Also,
                                    // make each detector separate size
  for (std::size_t i = 0; i < numSpectra; ++i) {
    double r = 1.0;
    if (!distance.empty()) {
      r = distance.at(i);
    }

    Kernel::V3D pos;
    pos.spherical(r, polar.at(i), azimuthal.at(i));

    Geometry::Detector *det =
        new Geometry::Detector("pixel", static_cast<int>(i + 1), sample);
    det->setPos(pos);
    det->setShape(cuboid);
    instrument->add(det);
    instrument->markAsDetector(det);
  }

  Geometry::ParameterMap &pmap = outputWS->instrumentParameters();
  std::vector<double>::iterator itdata = data.begin(), iterror = error.begin(),
                                itdataend, iterrorend;
  API::Progress prog = API::Progress(this, 0.0, 0.9, numSpectra);
  for (std::size_t i = 0; i < numSpectra; ++i) {
    itdataend = itdata + numBins;
    iterrorend = iterror + numBins;
    outputWS->dataX(i) = energies;
    if ((!boost::math::isfinite(*itdata)) || (*itdata <= -1e10)) // masked bin
    {
      outputWS->dataY(i) = std::vector<double>(numBins, 0);
      outputWS->dataE(i) = std::vector<double>(numBins, 0);
      pmap.addBool(outputWS->getDetector(i)->getComponentID(), "masked", true);
    } else {
      outputWS->dataY(i) = std::vector<double>(itdata, itdataend);
      outputWS->dataE(i) = std::vector<double>(iterror, iterrorend);
    }
    itdata = (itdataend);
    iterror = (iterrorend);
    prog.report();
  }

  setProperty("OutputWorkspace", outputWS);
}
/// Executes the algorithm for events
void UnaryOperation::execEvent() {
  g_log.information("Processing event workspace");

  const MatrixWorkspace_const_sptr matrixInputWS =
      this->getProperty(inputPropName());
  EventWorkspace_const_sptr inputWS =
      boost::dynamic_pointer_cast<const EventWorkspace>(matrixInputWS);

  // generate the output workspace pointer
  API::MatrixWorkspace_sptr matrixOutputWS =
      this->getProperty(outputPropName());
  EventWorkspace_sptr outputWS;
  if (matrixOutputWS == matrixInputWS) {
    outputWS = boost::dynamic_pointer_cast<EventWorkspace>(matrixOutputWS);
  } else {
    // Make a brand new EventWorkspace
    outputWS = boost::dynamic_pointer_cast<EventWorkspace>(
        API::WorkspaceFactory::Instance().create(
            "EventWorkspace", inputWS->getNumberHistograms(), 2, 1));
    // Copy geometry over.
    API::WorkspaceFactory::Instance().initializeFromParent(inputWS, outputWS,
                                                           false);
    // You need to copy over the data as well.
    outputWS->copyDataFrom((*inputWS));

    // Cast to the matrixOutputWS and save it
    matrixOutputWS = boost::dynamic_pointer_cast<MatrixWorkspace>(outputWS);
    this->setProperty("OutputWorkspace", matrixOutputWS);
  }

  // Now fetch any properties defined by concrete algorithm
  retrieveProperties();

  int64_t numHistograms = static_cast<int64_t>(inputWS->getNumberHistograms());
  API::Progress prog = API::Progress(this, 0.0, 1.0, numHistograms);
  PARALLEL_FOR1(outputWS)
  for (int64_t i = 0; i < numHistograms; ++i) {
    PARALLEL_START_INTERUPT_REGION
    // switch to weighted events if needed, and use the appropriate helper
    // function
    EventList *evlist = outputWS->getEventListPtr(i);
    switch (evlist->getEventType()) {
    case TOF:
      // Switch to weights if needed.
      evlist->switchTo(WEIGHTED);
    /* no break */
    // Fall through

    case WEIGHTED:
      unaryOperationEventHelper(evlist->getWeightedEvents());
      break;

    case WEIGHTED_NOTIME:
      unaryOperationEventHelper(evlist->getWeightedEventsNoTime());
      break;
    }

    prog.report();
    PARALLEL_END_INTERUPT_REGION
  }
  PARALLEL_CHECK_INTERUPT_REGION

  outputWS->clearMRU();
  if (inputWS->getNumberEvents() != outputWS->getNumberEvents()) {
    g_log.information() << "Number of events has changed!!!" << std::endl;
  }
}
Beispiel #9
0
void CorrectKiKf::execEvent()
{
  g_log.information("Processing event workspace");

  const MatrixWorkspace_const_sptr matrixInputWS = this->getProperty("InputWorkspace");
  EventWorkspace_const_sptr inputWS= boost::dynamic_pointer_cast<const EventWorkspace>(matrixInputWS);

  // generate the output workspace pointer
  API::MatrixWorkspace_sptr matrixOutputWS = this->getProperty("OutputWorkspace");
  EventWorkspace_sptr outputWS;
  if (matrixOutputWS == matrixInputWS)
    outputWS = boost::dynamic_pointer_cast<EventWorkspace>(matrixOutputWS);
  else
  {
    //Make a brand new EventWorkspace
    outputWS = boost::dynamic_pointer_cast<EventWorkspace>(
            API::WorkspaceFactory::Instance().create("EventWorkspace", inputWS->getNumberHistograms(), 2, 1));
    //Copy geometry over.
    API::WorkspaceFactory::Instance().initializeFromParent(inputWS, outputWS, false);
    //You need to copy over the data as well.
    outputWS->copyDataFrom( (*inputWS) );

    //Cast to the matrixOutputWS and save it
    matrixOutputWS = boost::dynamic_pointer_cast<MatrixWorkspace>(outputWS);
    this->setProperty("OutputWorkspace", matrixOutputWS);
  }

  const std::string emodeStr = getProperty("EMode");
  double efixedProp = getProperty("EFixed"),efixed;

  if( efixedProp == EMPTY_DBL() )
  {
    if (emodeStr == "Direct")
    {
      // Check if it has been store on the run object for this workspace
      if( this->inputWS->run().hasProperty("Ei"))
      {
        Kernel::Property* eiprop = this->inputWS->run().getProperty("Ei");
        efixedProp = boost::lexical_cast<double>(eiprop->value());
        g_log.debug() << "Using stored Ei value " << efixedProp << "\n";
      }
      else
      {
        throw std::invalid_argument("No Ei value has been set or stored within the run information.");
      }
    }
    else
    {
      // If not specified, will try to get Ef from the parameter file for indirect geometry, 
      // but it will be done for each spectrum separately, in case of different analyzer crystals
    }
  }

  // Get the parameter map
  const ParameterMap& pmap = outputWS->constInstrumentParameters();

  int64_t numHistograms = static_cast<int64_t>(inputWS->getNumberHistograms());
  API::Progress prog = API::Progress(this, 0.0, 1.0, numHistograms);
  PARALLEL_FOR1(outputWS)
  for (int64_t i=0; i < numHistograms; ++i)
  {
    PARALLEL_START_INTERUPT_REGION
    
    double Efi = 0;
    // Now get the detector object for this histogram to check if monitor
    // or to get Ef for indirect geometry
    if (emodeStr == "Indirect") 
    {
      if ( efixedProp != EMPTY_DBL()) Efi = efixedProp;
      else try 
      {
        IDetector_const_sptr det = inputWS->getDetector(i);
        if (!det->isMonitor())
        {
            try
            {
              Parameter_sptr par = pmap.getRecursive(det.get(),"Efixed");
              if (par)
              {
                Efi = par->value<double>();
                g_log.debug() << "Detector: " << det->getID() << " EFixed: " << Efi << "\n";
              }
            }
            catch (std::runtime_error&) { /* Throws if a DetectorGroup, use single provided value */ }
        }

      }
      catch(std::runtime_error&) { g_log.information() << "Workspace Index " << i << ": cannot find detector" << "\n"; }
    }

    if (emodeStr == "Indirect") efixed=Efi;
    else efixed=efixedProp;

    //Do the correction
		EventList *evlist=outputWS->getEventListPtr(i);
    switch (evlist->getEventType())
    {
      case TOF:
        //Switch to weights if needed.
        evlist->switchTo(WEIGHTED);
        /* no break */
        // Fall through
  
      case WEIGHTED:
        correctKiKfEventHelper(evlist->getWeightedEvents(), efixed,emodeStr);
        break;
  
      case WEIGHTED_NOTIME:
        correctKiKfEventHelper(evlist->getWeightedEventsNoTime(), efixed,emodeStr);
        break;
    }


    prog.report();
    PARALLEL_END_INTERUPT_REGION
  }
  PARALLEL_CHECK_INTERUPT_REGION

  outputWS->clearMRU();
  if (inputWS->getNumberEvents( ) != outputWS->getNumberEvents( ))
  {
    g_log.information() <<"Ef <= 0 or Ei <= 0 for "<<inputWS->getNumberEvents( )-outputWS->getNumberEvents( )<<" events, out of "<<inputWS->getNumberEvents( )<<std::endl;
    if ( efixedProp == EMPTY_DBL()) g_log.information()<<"Try to set fixed energy"<<std::endl ;
  }
}
/**
 * Create an test instrument with n panels of rectangular detectors,
 *pixels*pixels in size,
 * a source and spherical sample shape.
 *
 * Banks' lower-left corner is at position (0,0,5*banknum) and they go up to
 *(pixels*0.008, pixels*0.008, Z)
 * Pixels are 4 mm wide.
 *
 * @param progress :: progress indicator
 * @param num_banks :: number of rectangular banks to create
 * @param pixels :: number of pixels in each direction.
 * @param pixelSpacing :: padding between pixels
 * @param bankDistanceFromSample :: Distance of first bank from sample (defaults
 *to 5.0m)
 * @param sourceSampleDistance :: The distance from the source to the sample
 * @returns A shared pointer to the generated instrument
 */
Instrument_sptr CreateSampleWorkspace::createTestInstrumentRectangular(
    API::Progress &progress, int num_banks, int pixels, double pixelSpacing,
    const double bankDistanceFromSample, const double sourceSampleDistance) {
  boost::shared_ptr<Instrument> testInst(new Instrument("basic_rect"));
  // The instrument is going to be set up with z as the beam axis and y as the
  // vertical axis.
  testInst->setReferenceFrame(
      boost::shared_ptr<ReferenceFrame>(new ReferenceFrame(Y, Z, Left, "")));

  const double cylRadius(pixelSpacing / 2);
  const double cylHeight(0.0002);
  // One object
  Object_sptr pixelShape = createCappedCylinder(
      cylRadius, cylHeight, V3D(0.0, -cylHeight / 2.0, 0.0), V3D(0., 1.0, 0.),
      "pixel-shape");

  for (int banknum = 1; banknum <= num_banks; banknum++) {
    // Make a new bank
    std::ostringstream bankname;
    bankname << "bank" << banknum;

    RectangularDetector *bank = new RectangularDetector(bankname.str());
    bank->initialize(pixelShape, pixels, 0.0, pixelSpacing, pixels, 0.0,
                     pixelSpacing, banknum * pixels * pixels, true, pixels);

    // Mark them all as detectors
    for (int x = 0; x < pixels; x++)
      for (int y = 0; y < pixels; y++) {
        boost::shared_ptr<Detector> detector = bank->getAtXY(x, y);
        if (detector)
          // Mark it as a detector (add to the instrument cache)
          testInst->markAsDetector(detector.get());
      }

    testInst->add(bank);
    // Set the bank along the z-axis of the instrument. (beam direction).
    bank->setPos(V3D(0.0, 0.0, bankDistanceFromSample * banknum));

    progress.report();
  }

  // Define a source component
  ObjComponent *source =
      new ObjComponent("moderator", Object_sptr(new Object), testInst.get());
  source->setPos(V3D(0.0, 0.0, -sourceSampleDistance));
  testInst->add(source);
  testInst->markAsSource(source);

  // Add chopper
  ObjComponent *chopper = new ObjComponent(
      "chopper-position", Object_sptr(new Object), testInst.get());
  chopper->setPos(V3D(0.0, 0.0, -0.25 * sourceSampleDistance));
  testInst->add(chopper);

  // Define a sample as a simple sphere
  Object_sptr sampleSphere =
      createSphere(0.001, V3D(0.0, 0.0, 0.0), "sample-shape");
  ObjComponent *sample =
      new ObjComponent("sample", sampleSphere, testInst.get());
  testInst->setPos(0.0, 0.0, 0.0);
  testInst->add(sample);
  testInst->markAsSamplePos(sample);

  return testInst;
}