Skip to content
Snippets Groups Projects
UpdateInstrumentFromFile.cpp 15.1 KiB
Newer Older
//----------------------------------------------------------------------
// Includes
//----------------------------------------------------------------------
#include "MantidDataHandling/UpdateInstrumentFromFile.h"
#include "MantidDataHandling/LoadAscii.h"
#include "MantidDataHandling/LoadEventNexus.h"
#include "MantidDataHandling/LoadISISNexus2.h"
#include "MantidDataHandling/LoadRawHelper.h"
#include "MantidGeometry/Instrument.h"
#include "MantidGeometry/Instrument/ComponentHelper.h"
#include "MantidKernel/NexusDescriptor.h"
#include <boost/algorithm/string/predicate.hpp>
#include <nexus/NeXusException.hpp>
#include <Poco/StringTokenizer.h>

#include <fstream>

namespace Mantid
{
    DECLARE_ALGORITHM(UpdateInstrumentFromFile)
    
    using namespace Kernel;
    using namespace API;
    using Geometry::Instrument_sptr;
    using Geometry::IDetector_sptr;
    /// Empty default constructor
    UpdateInstrumentFromFile::UpdateInstrumentFromFile()
      : m_workspace(), m_ignorePhi(false), m_ignoreMonitors(true)
    /// Initialisation method.
    void UpdateInstrumentFromFile::init()
    {
      // When used as a Child Algorithm the workspace name is not used - hence the "Anonymous" to satisfy the validator
      declareProperty(
        new WorkspaceProperty<MatrixWorkspace>("Workspace","Anonymous",Direction::InOut),
        "The name of the workspace in which to store the imported instrument");
      std::vector<std::string> exts;
      exts.push_back(".raw");
      exts.push_back(".s*");
      declareProperty(new FileProperty("Filename", "", FileProperty::Load, exts),
        "The filename of the input file.\n"
        "Currently supports RAW, ISIS NeXus, DAT & multi-column (at least 2) ascii files"
      declareProperty("MoveMonitors", (!m_ignoreMonitors),
                      "If true the positions of any detectors marked as monitors "
                      "in the IDF will be moved also");
      declareProperty("IgnorePhi", m_ignorePhi,
                      "If true the phi values form the file will be ignored ");
      declareProperty("AsciiHeader", "",
                      "If the file is a simple text file, then this property is used to"
                      "define the values in each column of the file. For example: spectrum,theta,t0,-,R"
                      "Keywords=spectrum,ID,R,theta,phi. A dash means skip column. Keywords are recognised"
                      "as identifying components to move to new positions. Any other names in the list"
                      "are added as instrument parameters.");
      declareProperty("SkipFirstNLines", 0, "If the file is ASCII, then skip this number of lines at the start of the file");
    /** Executes the algorithm. Reading in the file and creating and populating
    *  the output workspace
    *
    *  @throw FileError Thrown if unable to parse XML file
    */
    void UpdateInstrumentFromFile::exec()
    {
      // Retrieve the filename from the properties
      const std::string filename = getPropertyValue("Filename");
      m_workspace = getProperty("Workspace");
      if(!m_workspace->getInstrument())
      {
        throw std::runtime_error("Input workspace has no defined instrument");
      }
      m_ignorePhi = getProperty("IgnorePhi");
      const bool moveMonitors = getProperty("MoveMonitors");
      m_ignoreMonitors = (!moveMonitors);

      if(NexusDescriptor::isHDF(filename))
        LoadISISNexus2 isisNexus;
        LoadEventNexus eventNexus;
        boost::scoped_ptr<Kernel::NexusDescriptor> descriptor(new Kernel::NexusDescriptor(filename));
        if(isisNexus.confidence(*descriptor) > 0 || eventNexus.confidence(*descriptor) > 0)
          auto & nxFile = descriptor->data();
          const auto & rootEntry = descriptor->firstEntryNameType();
          nxFile.openGroup(rootEntry.first, rootEntry.second);
          updateFromNeXus(nxFile);

      if(FileDescriptor::isAscii(filename))
        // If no header specified & the extension is .dat or .sca, then assume ISIS
        // DAT file structure
        if(getPropertyValue("AsciiHeader").empty() &&
           (boost::iends_with(filename,".dat") || boost::iends_with(filename,".sca")))
        {
          this->setPropertyValue("AsciiHeader", "ID,-,R,-,theta,phi,-,-,-,-,-,-,-,-,-,-,-,-,-");
          this->setProperty("SkipFirstNLines",2);
        }

      LoadRawHelper isisRAW;
      auto *descriptor = new Kernel::FileDescriptor(filename);
      if( isisRAW.confidence(*descriptor) > 0 )
        delete descriptor;
        updateFromRaw(filename);
        throw std::invalid_argument("File \"" + filename + "\" is not a valid input file.");
    /**
    * Update the detector information from a raw file
    * @param filename :: The input filename
    */
    void UpdateInstrumentFromFile::updateFromRaw(const std::string & filename)
      ISISRAW2 iraw;
      if (iraw.readFromFile(filename.c_str(),false) != 0)
      {
        g_log.error("Unable to open file " + filename);
        throw Exception::FileError("Unable to open File:" , filename);
      }
      const int32_t numDetector = iraw.i_det;
      std::vector<int32_t> detID(iraw.udet, iraw.udet + numDetector);
      std::vector<float> l2(iraw.len2, iraw.len2 + numDetector);
      std::vector<float> theta(iraw.tthe, iraw.tthe + numDetector);
      // Is ut01 (=phi) present? Sometimes an array is present but has wrong values e.g.all 1.0 or all 2.0
      bool phiPresent = iraw.i_use>0 && iraw.ut[0]!= 1.0 && iraw.ut[0] !=2.0; 
      std::vector<float> phi(0);
      if( phiPresent )
      {
        phi = std::vector<float>(iraw.ut, iraw.ut + numDetector);
      }
      else
      {
        phi = std::vector<float>(numDetector, 0.0);
      }
      g_log.information() << "Setting detector postions from RAW file.\n";
      setDetectorPositions(detID, l2, theta, phi);

    /**
    * Update the detector information from a NeXus file
    * @param nxFile :: Handle to a NeXus file where the root group has been opened
    void UpdateInstrumentFromFile::updateFromNeXus(::NeXus::File & nxFile)
        nxFile.openGroup("isis_vms_compat","IXvms");
        throw std::runtime_error("Unknown NeXus flavour. Cannot update instrument positions using this type of file");
      // Det ID
      std::vector<int32_t> detID;
      nxFile.openData("UDET");
      nxFile.getData(detID);
      nxFile.closeData();
      // Position information
      std::vector<float> l2, theta,phi;
      nxFile.openData("LEN2");
      nxFile.getData(l2);
      nxFile.closeData();
      nxFile.openData("TTHE");
      nxFile.getData(theta);
      nxFile.closeData();
      nxFile.openData("UT01");
      nxFile.getData(phi);
      nxFile.closeData();

      g_log.information() << "Setting detector postions from NeXus file.\n";
      setDetectorPositions(detID, l2, theta, phi);
    /**
     * Updates from a more generic ascii file
     * @param filename :: The input filename
     */
    void UpdateInstrumentFromFile::updateFromAscii(const std::string & filename)
    {
      AsciiFileHeader header;
      const bool isSpectrum = parseAsciiHeader(header);

      Geometry::Instrument_const_sptr inst = m_workspace->getInstrument();
      // Throws for multiple detectors
      const spec2index_map specToIndex(m_workspace->getSpectrumToWorkspaceIndexMap());

      std::ifstream datfile(filename.c_str(), std::ios_base::in);
      const int skipNLines = getProperty("SkipFirstNLines");
      int lineCount(0);
      while(lineCount < skipNLines)
      {
        std::getline(datfile,line);
        ++lineCount;
      }

      std::vector<double> colValues(header.colCount - 1, 0.0);
      while(std::getline(datfile,line))
      {
        boost::trim(line);
        std::istringstream is(line);
        // Column 0 should be ID/spectrum number
        // If first thing read is not a number then skip the line
        if(is.fail())
        {
          g_log.debug() << "Skipping \"" << line << "\". Cannot interpret as list of numbers.\n";
          continue;
        }

        Geometry::IDetector_const_sptr det;
            auto it = specToIndex.find(detOrSpec);
            if(it != specToIndex.end())
              const size_t wsIndex = it->second;
              det = m_workspace->getDetector(wsIndex);
              g_log.debug() << "Skipping \"" << line << "\". Spectrum is not in workspace.\n";
            det = inst->getDetector(detOrSpec);
        catch(Kernel::Exception::NotFoundError&)
          g_log.debug() << "Skipping \"" << line << "\". Spectrum in workspace but cannot find associated detector.\n";
        }

        // Special cases for detector r,t,p. Everything else is
        // attached as an detector parameter
        double R(0.0),theta(0.0), phi(0.0);
        for(size_t i = 1; i < header.colCount; ++i)
        {
          double value(0.0);
          is >> value;
          if(i < header.colCount - 1 && is.eof())
          {
            //If stringstream is at EOF & we are not at the last column then
            // there aren't enought columns in the file
            throw std::runtime_error("UpdateInstrumentFromFile::updateFromAscii - "
                          "File contains fewer than expected number of columns, check AsciiHeader property.");
          }

          if(i == header.rColIdx) R = value;
          else if(i == header.thetaColIdx) theta = value;
          else if(i == header.phiColIdx) phi = value;
          else if(header.detParCols.count(i) == 1)
          {
            Geometry::ParameterMap & pmap = m_workspace->instrumentParameters();
            pmap.addDouble(det->getComponentID(), header.colToName[i],value);
          }
        }
        // Check stream state. stringstream::EOF should have been reached, if not then there is still more to
        // read and the file has more columns than the header indicated
        if(!is.eof())
        {
          throw std::runtime_error("UpdateInstrumentFromFile::updateFromAscii - "
              "File contains more than expected number of columns, check AsciiHeader property.");
        }

        // If not supplied use current values
        double r,t,p;
        det->getPos().getSpherical(r,t,p);
        if(header.rColIdx == 0) R = r;
        if(header.thetaColIdx == 0) theta = t;
        if(header.phiColIdx == 0) phi = p;

        setDetectorPosition(det, static_cast<float>(R), static_cast<float>(theta), static_cast<float>(phi));
      }
    }

    /**
     * Parse the header and fill the headerInfo struct and returns a boolean
     * indicating if the table is spectrum or detector ID based
     * @param headerInfo :: [Out] Fills the given struct with details about the header
     * @returns True if the header is spectrum based, false otherwise
     */
    bool UpdateInstrumentFromFile::parseAsciiHeader(UpdateInstrumentFromFile::AsciiFileHeader & headerInfo)
    {
      const std::string header = getProperty("AsciiHeader");
      if(header.empty())
      {
        throw std::invalid_argument("Ascii file provided but the AsciiHeader property is empty, cannot interpret columns");
      }

      Poco::StringTokenizer splitter(header, ",",Poco::StringTokenizer::TOK_TRIM);
      headerInfo.colCount = splitter.count();
      auto it = splitter.begin(); // First column must be spectrum number or detector ID
      const std::string & col0 = *it;
      bool isSpectrum(false);
      if(boost::iequals("spectrum",col0)) isSpectrum = true;
      if(!isSpectrum && !boost::iequals("id",col0))
      {
        throw std::invalid_argument("Invalid AsciiHeader, first column name must be either 'spectrum' or 'id'");
      }

      ++it;
      size_t counter(1);
      for(; it != splitter.end(); ++it)
      {
        const std::string & colName = *it;
        if(boost::iequals("R",colName)) headerInfo.rColIdx = counter;
        else if(boost::iequals("theta",colName)) headerInfo.thetaColIdx = counter;
        else if(boost::iequals("phi",colName)) headerInfo.phiColIdx = counter;
        else if(boost::iequals("-",colName)) // Skip dashed
        {
          ++counter;
          continue;
        }
        else
        {
          headerInfo.detParCols.insert(counter);
          headerInfo.colToName.insert(std::make_pair(counter, colName));
        }
        ++counter;
      }

      return isSpectrum;
    }

    /**
     * Set the detector positions given the r,theta and phi.
     * @param detID :: A vector of detector IDs
     * @param l2 :: A vector of l2 distances
     * @param theta :: A vector of theta distances
     * @param phi :: A vector of phi values
     */
    void UpdateInstrumentFromFile::setDetectorPositions(const std::vector<int32_t> & detID, const std::vector<float> & l2,
                                                        const std::vector<float> & theta, const std::vector<float> & phi)
        Geometry::Instrument_const_sptr inst = m_workspace->getInstrument();
        const int numDetector = static_cast<int>(detID.size());
        g_log.information() << "Setting new positions for " << numDetector << " detectors\n";
          try
          {
            Geometry::IDetector_const_sptr det = inst->getDetector(detID[i]);
            setDetectorPosition(det, l2[i], theta[i], phi[i]);
          }
          catch (Kernel::Exception::NotFoundError&)
          {
            continue;
          }
          progress(static_cast<double>(i)/numDetector,"Updating Detector Positions from File");
    /**
     * Set the new detector position given the r,theta and phi.
     * @param det :: A pointer to the detector
     * @param l2 :: A single l2
     * @param theta :: A single theta
     * @param phi :: A single phi
     */
    void UpdateInstrumentFromFile::setDetectorPosition(const Geometry::IDetector_const_sptr & det, const float l2,
                                                       const float theta, const float phi)
    {
      if( m_ignoreMonitors && det->isMonitor() ) return;

      Geometry::ParameterMap & pmap = m_workspace->instrumentParameters();
        double r,t,p;
        det->getPos().getSpherical(r,t,p);
        pos.spherical(l2, theta, p);
      Geometry::ComponentHelper::moveComponent(*det, pmap, pos, Geometry::ComponentHelper::Absolute);
} // namespace Mantid