Skip to content
Snippets Groups Projects
LoadInstrument.cpp 70.9 KiB
Newer Older
Nick Draper's avatar
Nick Draper committed
//----------------------------------------------------------------------
// Includes
//----------------------------------------------------------------------
#include "MantidDataHandling/LoadInstrument.h"
#include "MantidDataHandling/LoadInstCompsIntoOneShape.h"
#include "MantidDataHandling/LoadParameterFile.h"
Nick Draper's avatar
Nick Draper committed
#include "MantidGeometry/Objects/ShapeFactory.h"
#include "MantidGeometry/Instrument.h"
#include "MantidAPI/InstrumentDataService.h"
#include "MantidGeometry/Instrument/XMLlogfile.h"
Nick Draper's avatar
Nick Draper committed
#include "MantidGeometry/Instrument/Detector.h"
#include "MantidGeometry/Instrument/RectangularDetector.h"
Anders Markvardsen's avatar
Anders Markvardsen committed
#include "MantidGeometry/Instrument/Component.h"
#include "MantidGeometry/Instrument/ObjCompAssembly.h"
Nick Draper's avatar
Nick Draper committed
#include "MantidGeometry/Rendering/vtkGeometryCacheReader.h"
#include "MantidGeometry/Rendering/vtkGeometryCacheWriter.h"
#include "MantidKernel/PhysicalConstants.h"
#include "MantidKernel/DateAndTime.h"
#include "MantidDataHandling/LoadInstrumentHelper.h"
#include "MantidKernel/ArrayProperty.h"
Campbell, Stuart's avatar
Campbell, Stuart committed

#include <Poco/DOM/DOMParser.h>
#include <Poco/DOM/Document.h>
#include <Poco/DOM/Element.h>
#include <Poco/DOM/NodeList.h>
#include <Poco/DOM/NodeIterator.h>
#include <Poco/DOM/NodeFilter.h>
#include <Poco/File.h>
#include <Poco/Path.h>
#include <fstream>
#include <iostream>
using Poco::XML::DOMParser;
using Poco::XML::Document;
using Poco::XML::Element;
using Poco::XML::Node;
using Poco::XML::NodeList;
using Poco::XML::NodeIterator;
using Poco::XML::NodeFilter;
Nick Draper's avatar
Nick Draper committed
namespace Mantid
{
    
    /// Sets documentation strings for this algorithm
    void LoadInstrument::initDocs()
    {
      this->setWikiSummary(" Loads an Instrument Definition File ([[InstrumentDefinitionFile|IDF]]) into a [[workspace]]. After the IDF has been read this algorithm will attempt to run the sub-algorithm [[LoadParameterFile]]; where if IDF filename is of the form IDENTIFIER_Definition.xml then the instrument parameters in the file named IDENTIFIER_Parameters.xml would be loaded (in the directory specified by the parameterDefinition.directory [[Properties_File|Mantid property]]). ");
      this->setOptionalMessage("Loads an Instrument Definition File (IDF) into a workspace. After the IDF has been read this algorithm will attempt to run the sub-algorithm LoadParameterFile; where if IDF filename is of the form IDENTIFIER_Definition.xml then the instrument parameters in the file named IDENTIFIER_Parameters.xml would be loaded (in the directory specified by the parameterDefinition.directory Mantid property).");
    }
    
    using namespace Kernel;
    using namespace API;
    /// Empty default constructor
    LoadInstrument::LoadInstrument() : Algorithm(), hasParameterElement_beenSet(false),
      m_haveDefaultFacing(false), m_deltaOffsets(false), m_angleConvertConst(1.0),
      m_indirectPositions(false)

    //------------------------------------------------------------------------------------------------------------------------------
    /// Initialisation method.
    void LoadInstrument::init()
      // When used as a sub-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 to load the instrument definition into" );
      declareProperty(new FileProperty("Filename","", FileProperty::OptionalLoad, ".xml"),
        "The filename (including its full or relative path) of an instrument\n"
        "definition file");
      declareProperty(new ArrayProperty<detid_t>("MonitorList"),
        "List of detector ids of monitors loaded in to the workspace");
      declareProperty( "InstrumentName", "",
        "Name of instrument. Can be used instead of Filename to specify an IDF" );
      declareProperty("RewriteSpectraMap", true, "If true then the spectra-detector mapping "
                      "for the input workspace will be overwritten with a 1:1 map of spectrum "
                      "number to detector ID");
      declareProperty("XMLText", "",
          "Optional: Enter the full XML contents of the Instrument Definition File, instead of a"
          "loading it from the file. You still need to specify the instrument name.");
    /** Execute the algorithm with the parameters set (either manually or from inputs) */
    void LoadInstrument::execManually()
    {
      // Retrieve the filename from the properties
      {
        // look to see if an Instrument name provided in which case create
        // IDF filename on the fly
        {
          g_log.error("Either the InstrumentName or Filename property of LoadInstrument most be specified"); 
          throw Kernel::Exception::FileError("Either the InstrumentName or Filename property of LoadInstrument most be specified to load an IDF" , m_filename);
        }
        else
        {
          const std::string date = m_workspace->getWorkspaceStartDate();
          m_filename = ExperimentInfo::getInstrumentFilename(m_instName,date);
      if (!m_filename.empty())
      {
        // Remove the path from the filename for use with the InstrumentDataService
        const std::string::size_type stripPath = m_filename.find_last_of("\\/");
        std::string instrumentFile = m_filename.substr(stripPath+1,m_filename.size());
        // Strip off "_Definition.xml"
        m_instName = instrumentFile.substr(0,instrumentFile.find_first_of("_"));
      }

      // Load the XML text into a string
      {
        std::string str;
        std::ifstream in;
        in.open(m_filename.c_str());
        getline(in,str);
        while ( in ) {
      // Create our new instrument
      // We don't want the instrument name taken out of the XML file itself, it should come from the filename (or the property)
      m_instrument = Instrument_sptr(new Instrument(m_instName));
      // Save the XML file path and contents
      m_instrument->setFilename(m_filename);
      // Set up the DOM parser and parse xml file
      DOMParser pParser;
      Document* pDoc;
      try
      {
      catch(Poco::Exception& exc)
      {
        throw Kernel::Exception::FileError(exc.displayText() + ". Unable to parse File:", m_filename);
      }
      catch(...)
      {
        throw Kernel::Exception::FileError("Unable to parse File:" , m_filename);
      }
      // Get pointer to root element
      Element* pRootElem = pDoc->documentElement();
      if ( !pRootElem->hasChildNodes() )
      {
        g_log.error("XML file: " + m_filename + "contains no root element.");
        throw Kernel::Exception::InstrumentDefinitionError("No root element in XML instrument file", m_filename);
      }
      // Handle used in the singleton constructor for instrument file should append the value
      // of the last-modified tag inside the file to determine if it is already in memory so that
      // changes to the instrument file will cause file to be reloaded.
      std::string instrumentNameMangled = Poco::Path(m_filename).getFileName() + pRootElem->getAttribute("last-modified");
      // Check whether the instrument is already in the InstrumentDataService
      if ( InstrumentDataService::Instance().doesExist(instrumentNameMangled) )
      {
        // If it does, just use the one from the one stored there
        m_instrument = InstrumentDataService::Instance().retrieve(instrumentNameMangled);
        setValidityRange(pRootElem);

        readDefaults(pRootElem->getChildElement("defaults"));
        // create maps: isTypeAssembly and mapTypeNameToShape
        Geometry::ShapeFactory shapeCreator;
        NodeList* pNL_type = pRootElem->getElementsByTagName("type");
        if ( pNL_type->length() == 0 )
        {
          g_log.error("XML file: " + m_filename + "contains no type elements.");
          throw Kernel::Exception::InstrumentDefinitionError("No type elements in XML instrument file", m_filename);
        }
        // Collect some information about types for later use including:
        //  * populate directory getTypeElement
        //  * populate directory isTypeAssemply
        //  * create shapes for all none assemply components and store in mapTyepNameToShape
        //  * If 'Outline' attribute set for assemply add attribute object_created=no to tell
        //    create shape for such assemply also later   
        unsigned long numberTypes = pNL_type->length();
        for (unsigned long iType = 0; iType < numberTypes; iType++)
        {
          Element* pTypeElem = static_cast<Element*>(pNL_type->item(iType));
          std::string typeName = pTypeElem->getAttribute("name");
          // check if contain <combine-components-into-one-shape>. If this then such
          // types are adjusted after this loop has completed
          NodeList* pNL_type_combine_into_one_shape = pTypeElem->getElementsByTagName("combine-components-into-one-shape");
          if ( pNL_type_combine_into_one_shape->length() )
            continue;
          pNL_type_combine_into_one_shape->release();

          // Each type in the IDF must be uniquely named, hence return error if type
          // has already been defined
          if ( getTypeElement.find(typeName) != getTypeElement.end() )
          {
            g_log.error("XML file: " + m_filename + "contains more than one type element named " + typeName);
            throw Kernel::Exception::InstrumentDefinitionError("XML instrument file contains more than one type element named " + typeName, m_filename);
          }
          getTypeElement[typeName] = pTypeElem;
          // identify for now a type to be an assemble by it containing elements
          // with tag name 'component'
          NodeList* pNL_local = pTypeElem->getElementsByTagName("component");
          if (pNL_local->length() == 0)
          {
            isTypeAssembly[typeName] = false;
            // for now try to create a geometry shape associated with every type
            // that does not contain any component elements
            mapTypeNameToShape[typeName] = shapeCreator.createShape(pTypeElem);
Doucet, Mathieu's avatar
Doucet, Mathieu committed
            mapTypeNameToShape[typeName]->setName(static_cast<int>(iType));
            if (pTypeElem->hasAttribute("outline"))
            {
              pTypeElem->setAttribute("object_created","no");
            }
          }

        // Deal with adjusting types containing <combine-components-into-one-shape>
        for (unsigned long iType = 0; iType < numberTypes; iType++)
        {
          Element* pTypeElem = static_cast<Element*>(pNL_type->item(iType));
          std::string typeName = pTypeElem->getAttribute("name");

          // In this loop only interested in types containing <combine-components-into-one-shape>
          NodeList* pNL_type_combine_into_one_shape = pTypeElem->getElementsByTagName("combine-components-into-one-shape");
          if ( pNL_type_combine_into_one_shape->length() == 0 )
            continue;
          pNL_type_combine_into_one_shape->release();

          // Each type in the IDF must be uniquely named, hence return error if type
          // has already been defined
          if ( getTypeElement.find(typeName) != getTypeElement.end() )
          {
            g_log.error("XML file: " + m_filename + "contains more than one type element named " + typeName);
            throw Kernel::Exception::InstrumentDefinitionError("XML instrument file contains more than one type element named " + typeName, m_filename);
          }
          getTypeElement[typeName] = pTypeElem;

          LoadInstCompsIntoOneShape helper;
          helper.adjust(pTypeElem, isTypeAssembly, getTypeElement);

          isTypeAssembly[typeName] = false;

          mapTypeNameToShape[typeName] = shapeCreator.createShape(pTypeElem);
          mapTypeNameToShape[typeName]->setName(static_cast<int>(iType));
        }
        // create hasParameterElement
        NodeList* pNL_parameter = pRootElem->getElementsByTagName("parameter");
        unsigned long numParameter = pNL_parameter->length();
        hasParameterElement.reserve(numParameter);
        for (unsigned long i = 0; i < numParameter; i++)
        {
          Element* pParameterElem = static_cast<Element*>(pNL_parameter->item(i));
          hasParameterElement.push_back( static_cast<Element*>(pParameterElem->parentNode()) );
        }
        pNL_parameter->release();
        hasParameterElement_beenSet = true;

        // See if any parameters set at instrument level
        setLogfile(m_instrument.get(), pRootElem, m_instrument->getLogfileCache());

        // do analysis for each top level compoment element
        NodeList* pNL_comp = pRootElem->childNodes(); // here get all child nodes
        unsigned long pNL_comp_length = pNL_comp->length();
Doucet, Mathieu's avatar
Doucet, Mathieu committed
        API::Progress prog(this,0,1,static_cast<int>(pNL_comp_length));
        for (unsigned long i = 0; i < pNL_comp_length; i++)
          if (VERBOSE) std::cout << "exec(): Node = "<< pNL_comp->item(i)->nodeName() << "\n";
          prog.report();
          // we are only interest in the top level component elements hence
          // the reason for the if statement below
          if ( (pNL_comp->item(i))->nodeType() == Node::ELEMENT_NODE &&
            ((pNL_comp->item(i))->nodeName()).compare("component") == 0 )
          {
            Element* pElem = static_cast<Element*>(pNL_comp->item(i));

            IdList idList; // structure to possibly be populated with detector IDs

            // get all location elements contained in component element
            NodeList* pNL_location = pElem->getElementsByTagName("location");
            unsigned long pNL_location_length = pNL_location->length();
            if (pNL_location_length == 0)
            {
              g_log.error(std::string("A component element must contain at least one location element") +
                " even if it is just an empty location element of the form <location />");
              throw Kernel::Exception::InstrumentDefinitionError(
                std::string("A component element must contain at least one location element") +
                " even if it is just an empty location element of the form <location />", m_filename);
            }


            if ( isAssembly(pElem->getAttribute("type")) )
            {
              if (VERBOSE) std::cout << "exec(): This element has a type that is an assembly\n";

              for (unsigned long i_loc = 0; i_loc < pNL_location_length; i_loc++)
              {
                Element* pLocElem = static_cast<Element*>(pNL_location->item(i_loc));

                if (VERBOSE) std::cout << "exec(): AppendAssembly of " << pLocElem->nodeName() << "\n";

                appendAssembly(m_instrument.get(), pLocElem, idList);
              }

              // a check
              if (idList.counted != static_cast<int>(idList.vec.size()) )
              {
                std::stringstream ss1, ss2;
                ss1 << idList.vec.size(); ss2 << idList.counted;
                g_log.error("The number of detector IDs listed in idlist named "
                  + pElem->getAttribute("idlist") +
                  " is larger than the number of detectors listed in type = "
                  + pElem->getAttribute("type"));
                throw Kernel::Exception::InstrumentDefinitionError(
                  "Number of IDs listed in idlist (=" + ss1.str() + ") is larger than the number of detectors listed in type = "
                  + pElem->getAttribute("type") + " (=" + ss2.str() + ").", m_filename);
              }
            }
            else
              for (unsigned long i_loc = 0; i_loc < pNL_location_length; i_loc++)
                appendLeaf(m_instrument.get(), static_cast<Element*>(pNL_location->item(i_loc)), idList);
        pNL_comp->release();
        // Don't need this anymore (if it was even used) so empty it out to save memory
        LoadInstrumentHelper::m_tempPosHolder.clear();
        // Read in or create the geometry cache file
        setupGeometryCache();

        // Add/overwrite any instrument params with values specified in <component-link> XML elements
        setComponentLinks(m_instrument, pRootElem);
        if ( m_indirectPositions ) createNeutronicInstrument();
        // Add the instrument to the InstrumentDataService
        InstrumentDataService::Instance().add(instrumentNameMangled,m_instrument);

      // Add the instrument to the workspace
      m_workspace->setInstrument(m_instrument);

      // check if default parameter file is also present, unless loading from
      if (!m_filename.empty())
        runLoadParameterFile();
    }

    //------------------------------------------------------------------------------------------------------------------------------
    /** Executes the algorithm. Reading in the file and creating and populating
    *  the output workspace
    *
    *  @throw FileError Thrown if unable to parse XML file
    *  @throw InstrumentDefinitionError Thrown if issues with the content of XML instrument file
    */
    void LoadInstrument::exec()
    {
      // Get the input workspace
      MatrixWorkspace_sptr ws = getProperty("Workspace");
      m_workspace = boost::dynamic_pointer_cast<MatrixWorkspace>(ws);
      m_filename = getPropertyValue("Filename");
      m_instName = getPropertyValue("InstrumentName");
      m_xmlText = getPropertyValue("XMLText");

      m_xmlText = Strings::strip(m_xmlText);
      if (!m_xmlText.empty() && m_instName.empty())
        throw std::invalid_argument("LoadInstrument: If you specify XMLText, you also need to specify the InstrumentName parameter.");

      if (!m_xmlText.empty() && m_filename.empty())
        throw std::invalid_argument("LoadInstrument: If you specify XMLText, you still need to specify the Filename parameter (to find the geometry cache file).");


      execManually();

      // Set the monitors output property
      setProperty("MonitorList",m_instrument->getMonitors());

      // Rebuild the spectra map for this workspace so that it matches the instrument
      // if required
      const bool rewriteSpectraMap = getProperty("RewriteSpectraMap");
      if( rewriteSpectraMap && ws )
        ws->rebuildSpectraMapping();

    //------------------------------------------------------------------------------------------------------------------------------
    /** To manually set the parameters
     *
     * @param ei :: ExperimentInfo sptr, e.g. a workspace
     * @param filename :: IDF .xml path (full)
     * @param instName :: name of the instrument
     * @param xmlText :: XML contents of IDF
     */
    void LoadInstrument::setParametersManually(Mantid::API::ExperimentInfo_sptr ei, const std::string & filename, const std::string & instName, const std::string & xmlText)
    {
      m_workspace = ei;
      m_filename = filename;
      m_instName = instName;
      m_xmlText = xmlText;
    }

    //------------------------------------------------------------------------------------------------------------------------------
    /** Checks the validity range in the IDF and adds it to the instrument object
     *  @param pRootElem A pointer to the root element of the instrument definition
     */
    void LoadInstrument::setValidityRange(const Poco::XML::Element* pRootElem)
    {
      // check if IDF has valid-from and valid-to tags defined
      if ( !pRootElem->hasAttribute("valid-from") )
      {
        throw Kernel::Exception::InstrumentDefinitionError("<instrument> element must contain a valid-from tag", m_filename);
      }
      else
      {
        try
        {
          DateAndTime d(pRootElem->getAttribute("valid-from"));
          m_instrument->setValidFromDate(d);
        }
        catch(...)
        {
          throw Kernel::Exception::InstrumentDefinitionError("The valid-from <instrument> tag must be a ISO8601 string", m_filename);
        }
      }

      if ( !pRootElem->hasAttribute("valid-to") )
      {
        DateAndTime d = DateAndTime::get_current_time();
        m_instrument->setValidToDate(d);
        // Ticket #2335: no required valid-to date.
        //throw Kernel::Exception::InstrumentDefinitionError("<instrument> element must contain a valid-to tag", m_filename);
      }
      else
      {
        try
        {
          DateAndTime d(pRootElem->getAttribute("valid-to"));
          m_instrument->setValidToDate(d);
        }
        catch(...)
        {
          throw Kernel::Exception::InstrumentDefinitionError("The valid-to <instrument> tag must be a ISO8601 string", m_filename);
        }
      }
    }

    //-----------------------------------------------------------------------------------------------------------------------
    /** Reads the contents of the \<defaults\> element to set member variables,
    *  requires m_instrument to be already set
    *  @param defaults :: points to the data read from the \<defaults\> element
    */
    void LoadInstrument::readDefaults(Poco::XML::Element* defaults)
      // Check whether spherical coordinates should be treated as offsets to parents position
      std::string offsets;
      Element* offsetElement = defaults->getChildElement("offsets");
      if (offsetElement) offsets = offsetElement->getAttribute("spherical");
      if ( offsets == "delta" ) m_deltaOffsets = true;

      // Check whether default facing is set
      Element* defaultFacingElement = defaults->getChildElement("components-are-facing");
      if (defaultFacingElement)
        m_haveDefaultFacing = true;
        m_defaultFacing = parseFacingElementToV3D(defaultFacingElement);
      }

      // the default view is used by the instrument viewer to decide the angle to display the instrument from on start up
      Element* defaultView = defaults->getChildElement("default-view");
      if (defaultView)
      {
        m_instrument->setDefaultViewAxis(defaultView->getAttribute("axis-view"));

      // check if angle=radian has been set
      Element* angleUnit = defaults->getChildElement("angle");
      if (angleUnit)
      {
        if (angleUnit->getAttribute("unit") == "radian")
        {
          m_angleConvertConst = 180.0/M_PI;
          std::map<std::string, std::string>& units = m_instrument->getLogfileUnit();
          units["angle"] = "radian";
        }
      }

      // Check if the IDF specifies that this is an indirect geometry instrument that includes
      // both physical and 'neutronic' postions.
      // Any neutronic position tags will be ignored if this tag is missing
      if ( defaults->getChildElement("indirect-neutronic-positions") )
        m_indirectPositions = true;
    std::vector<std::string> LoadInstrument::buildExcludeList(const Poco::XML::Element* const location)
    {
      // check if <exclude> sub-elements for this location and create new exclude list to pass on
      NodeList* pNLexclude = location->getElementsByTagName("exclude");
      unsigned long numberExcludeEle = pNLexclude->length();
      std::vector<std::string> newExcludeList;
      for (unsigned long i = 0; i < numberExcludeEle; i++)
      {
        Element* pExElem = static_cast<Element*>(pNLexclude->item(i));
        if ( pExElem->hasAttribute("sub-part") )
          newExcludeList.push_back(pExElem->getAttribute("sub-part"));
      }
      pNLexclude->release();

      return newExcludeList;
    }
    //-----------------------------------------------------------------------------------------------------------------------
    /** Assumes second argument is a XML location element and its parent is a component element
    *  which is assigned to be an assembly. This method appends the parent component element of
    *  the location element to the CompAssembly passed as the 1st arg. Note this method may call
    *  itself, i.e. it may act recursively.
    *  @param parent :: CompAssembly to append new component to
    *  @param pLocElem ::  Poco::XML element that points to a location element in an instrument description XML file
    *  @param idList :: The current IDList
    void LoadInstrument::appendAssembly(Geometry::ICompAssembly* parent, Poco::XML::Element* pLocElem, IdList& idList)
      if (VERBOSE) std::cout << "appendAssembly() starting for parent " << parent->getName() << "\n";
Anders Markvardsen's avatar
Anders Markvardsen committed

      // The location element is required to be a child of a component element. Get this component element
      Element* pCompElem = LoadInstrumentHelper::getParentComponent(pLocElem);
      // Note idlist may be defined for any component
      // Note any new idlist found will take precedence.

      if ( pCompElem->hasAttribute("idlist") )
      {
        std::string idlist = pCompElem->getAttribute("idlist");

        if ( idlist.compare(idList.idname) )
        {
          Element* pFound = pCompElem->ownerDocument()->getElementById(idlist, "idname");

          if ( pFound == NULL )
          {
            throw Kernel::Exception::InstrumentDefinitionError(
              "No <idlist> with name idname=\"" + idlist + "\" present in instrument definition file.", m_filename);
          }
          idList.reset(); 
          populateIdList(pFound, idList);
        }
      }

      //Create the assembly that will be appended into the parent.
      Geometry::ICompAssembly *ass;
      // The newly added component is required to have a type. Find out what this
      // type is and find all the location elements of this type. Finally loop over these
      // location elements

      Element* pType = getTypeElement[pCompElem->getAttribute("type")];
      if (pType->hasAttribute("outline") && pType->getAttribute("outline") != "no")
      {
        ass = new Geometry::ObjCompAssembly(LoadInstrumentHelper::getNameOfLocationElement(pLocElem),parent);
        ass = new Geometry::CompAssembly(LoadInstrumentHelper::getNameOfLocationElement(pLocElem),parent);
      if (VERBOSE) std::cout << "appendAssembly() is creating an assembly called " << ass->getName() << "\n";
      // set location for this newly added comp and set facing if specified in instrument def. file. Also
      // check if any logfiles are referred to through the <parameter> element.
      LoadInstrumentHelper::setLocation(ass, pLocElem, m_angleConvertConst, m_deltaOffsets);
      setLogfile(ass, pCompElem, m_instrument->getLogfileCache());  // params specified within <component>
      setLogfile(ass, pLocElem, m_instrument->getLogfileCache());  // params specified within specific <location>
      // If enabled, check for a 'neutronic position' tag and add to cache if found
      if ( m_indirectPositions )
      {
        Element* neutronic = pLocElem->getChildElement("neutronic");
        if ( neutronic ) m_neutronicPos[ass] = neutronic;
      }

      // Check for <exclude> tags for this location
      const std::vector<std::string> excludeList = buildExcludeList(pLocElem);
      NodeIterator it(pType, NodeFilter::SHOW_ELEMENT);
        if ( pNode->nodeName().compare("location")==0 )
          Element* pElem = static_cast<Element*>(pNode);

          // check if this location is in the exclude list
          std::vector<std::string>::const_iterator it = find(excludeList.begin(), excludeList.end(), LoadInstrumentHelper::getNameOfLocationElement(pElem));
            std::string typeName = (LoadInstrumentHelper::getParentComponent(pElem))->getAttribute("type");
            if (VERBOSE) std::cout << "appendAssembly() has found that its parent's type = " << typeName << "\n";

      // create outline object for the assembly
      if (pType->hasAttribute("outline") && pType->getAttribute("outline") != "no")
      {
        Geometry::ObjCompAssembly* objAss = dynamic_cast<Geometry::ObjCompAssembly*>(ass);
        if (pType->getAttribute("object_created") == "no")
        {
          pType->setAttribute("object_created","yes");
          boost::shared_ptr<Geometry::Object> obj = objAss->createOutline();
          if (obj)
          {
            mapTypeNameToShape[pType->getAttribute("name")] = obj;
          }
          else
          {// object failed to be created
            pType->setAttribute("outline","no");
            g_log.warning()<<"Failed to create outline object for assembly "<<pType->getAttribute("name")<<'\n';
          }
        }
        else
        {
          objAss->setOutline(mapTypeNameToShape[pType->getAttribute("name")]);
        }
      }

    //-----------------------------------------------------------------------------------------------------------------------
    /** Assumes second argument is pointing to a leaf, which here means the location element (indirectly
    *  representing a component element) that contains no sub-components. This component is appended
    %  to the parent (1st argument).
    *
    *  @param parent :: CompAssembly to append component to
    *  @param pLocElem ::  Poco::XML element that points to the element in the XML doc we want to add
    *  @param idList :: The current IDList
    *
    *  @throw InstrumentDefinitionError Thrown if issues with the content of XML instrument file
    */
    void LoadInstrument::appendLeaf(Geometry::ICompAssembly* parent, Poco::XML::Element* pLocElem, IdList& idList)
      // The location element is required to be a child of a component element. Get this component element
      Element* pCompElem = LoadInstrumentHelper::getParentComponent(pLocElem);
      //--- Get the detector's X/Y pixel sizes (optional) ---
      if (VERBOSE) std::cout << "AppendLeaf: I am " << pLocElem->getAttribute("name") << " . " <<
          "Parent is=" << pCompElem->getAttribute("is") <<
          ". Parent type=" << pCompElem->getAttribute("type") <<
      // Note idlist may be defined for any component
      // Note any new idlist found will take precedence.

      if ( pCompElem->hasAttribute("idlist") )
      {
        std::string idlist = pCompElem->getAttribute("idlist");

        if ( idlist.compare(idList.idname) )
        {
          Element* pFound = pCompElem->ownerDocument()->getElementById(idlist, "idname");

          if ( pFound == NULL )
          {
            throw Kernel::Exception::InstrumentDefinitionError(
              "No <idlist> with name idname=\"" + idlist + "\" present in instrument definition file.", m_filename);
          }

          idList.reset(); 
          populateIdList(pFound, idList);
        }
      }


      // get the type element of the component element in order to determine if the type
      // belong to the category: "detector", "SamplePos or "Source".
      std::string typeName = pCompElem->getAttribute("type");
      Element* pType = getTypeElement[typeName];
      std::string category = "";
      if (pType->hasAttribute("is"))
        category = pType->getAttribute("is");
      // do stuff a bit differently depending on which category the type belong to
      if ( category.compare("rectangular_detector") == 0  || category.compare("rectangularDetector") == 0  || category.compare("rectangulardetector") == 0 || category.compare("RectangularDetector") == 0 )
      {
        //-------------- Create a RectangularDetector ------------------------------------------------
        std::string name = LoadInstrumentHelper::getNameOfLocationElement(pLocElem);

        if (VERBOSE) std::cout << "AppendLeaf: Creating RectangularDetector " << name << ".\n";

        //Create the bank with the given parent.
        Geometry::RectangularDetector * bank = new Geometry::RectangularDetector(name, parent);
        // set location for this newly added comp and set facing if specified in instrument def. file. Also
        // check if any logfiles are referred to through the <parameter> element.
        LoadInstrumentHelper::setLocation(bank, pLocElem, m_angleConvertConst, m_deltaOffsets);
        setFacing(bank, pLocElem);
        setLogfile(bank, pCompElem, m_instrument->getLogfileCache()); // params specified within <component>
        setLogfile(bank, pLocElem, m_instrument->getLogfileCache());  // params specified within specific <location>

        //Extract all the parameters from the XML attributes
        int xpixels=0; double xstart=0.; double xstep=0.;
        int ypixels=0; double ystart=0.; double ystep=0.;
        int idstart=0; bool idfillbyfirst_y=true; int idstepbyrow=0;
        // Given that this leaf component is actually an assembly, its constituent component detector shapes comes from its type attribute.
        const std::string shapeType = pType->getAttribute("type");
        boost::shared_ptr<Geometry::Object> shape = mapTypeNameToShape[shapeType];

        //These parameters are in the TYPE defining RectangularDetector
        if ( pType->hasAttribute("xpixels") ) xpixels = atoi((pType->getAttribute("xpixels")).c_str());
        if ( pType->hasAttribute("xstart") )  xstart  = atof((pType->getAttribute("xstart")).c_str());
        if ( pType->hasAttribute("xstep") )   xstep   = atof((pType->getAttribute("xstep")).c_str());
        if ( pType->hasAttribute("ypixels") ) ypixels = atoi((pType->getAttribute("ypixels")).c_str());
        if ( pType->hasAttribute("ystart") )  ystart  = atof((pType->getAttribute("ystart")).c_str());
        if ( pType->hasAttribute("ystep") )   ystep   = atof((pType->getAttribute("ystep")).c_str());

        //THESE parameters are in the INSTANCE of this type - since they will change.
        if ( pCompElem->hasAttribute("idstart") ) idstart = atoi((pCompElem->getAttribute("idstart")).c_str());
        if ( pCompElem->hasAttribute("idfillbyfirst") )
          idfillbyfirst_y = (pCompElem->getAttribute("idfillbyfirst") == "y");
        if (idfillbyfirst_y) idstepbyrow = ypixels;
          else idstepbyrow = xpixels;
Anders Markvardsen's avatar
Anders Markvardsen committed
        if ( pCompElem->hasAttribute("idstepbyrow") ) 
        {
          idstepbyrow = atoi((pCompElem->getAttribute("idstepbyrow")).c_str());
Anders Markvardsen's avatar
Anders Markvardsen committed
        }
        //Default ID row step size
        if ( pCompElem->hasAttribute("idstep") ) idstep = atoi((pCompElem->getAttribute("idstep")).c_str());

        if (VERBOSE) std::cout << "AppendLeaf: Initializing RectangularDetector with parameters : " <<
            "shape " << typeName << ", " << xpixels << ", " << xstart << ", " << xstep << ", " << ypixels << ", " << ystart << ", " << ystep << ", " << idstart << ", " << idfillbyfirst_y << ", " << idstepbyrow << ".\n";

        // Now, initialize all the pixels in the bank
        bank->initialize(shape, xpixels, xstart, xstep, ypixels, ystart, ystep, idstart, idfillbyfirst_y, idstepbyrow, idstep);

        //Loop through all detectors in the newly created bank and mark those in the instrument.
        try
        {
          for (int x=0; x < bank->nelements(); x++)
            boost::shared_ptr<Geometry::ICompAssembly> xColumn = boost::dynamic_pointer_cast<Geometry::ICompAssembly>((*bank)[x]);
            for (int y=0; y < xColumn->nelements(); y++)
              boost::shared_ptr<Geometry::Detector> detector = boost::dynamic_pointer_cast<Geometry::Detector>((*xColumn)[y]);
              if (detector)
              {
                //Make default facing for the pixel
                Geometry::IComponent* comp = (Geometry::IComponent*) detector.get();
                if (m_haveDefaultFacing)
                  makeXYplaneFaceComponent(comp, m_defaultFacing);
                //Mark it as a detector (add to the instrument cache)
                m_instrument->markAsDetector(detector.get());
              }
          }
        }
        catch(Kernel::Exception::ExistsError&)
        {
          throw Kernel::Exception::InstrumentDefinitionError(
              "Duplicate detector ID found when adding RectangularDetector " + name + " in XML instrument file" + m_filename);
        }
      }
      else if ( category.compare("detector") == 0 )
        //-------------- Create a Detector ------------------------------------------------
        std::string name = LoadInstrumentHelper::getNameOfLocationElement(pLocElem);
        // before setting detector ID check that the IDF satisfy the following 
        if (idList.counted >=  static_cast<int>(idList.vec.size()) )
        {
          std::stringstream ss1, ss2;
          ss1 << idList.vec.size(); ss2 << idList.counted;
          g_log.error("The number of detector IDs listed in idlist named "
            + idList.idname + " is less then the number of detectors");
          throw Kernel::Exception::InstrumentDefinitionError(
            "Number of IDs listed in idlist (=" + ss1.str() + ") is less than the number of detectors.", m_filename);
        }
        // Create detector and increment id. Finally add the detector to the parent
        Geometry::Detector* detector =
            new Geometry::Detector(name, idList.vec[idList.counted],mapTypeNameToShape[typeName], parent);
        idList.counted++;
        parent->add(detector);

        // set location for this newly added comp and set facing if specified in instrument def. file. Also
        // check if any logfiles are referred to through the <parameter> element.
        LoadInstrumentHelper::setLocation(detector, pLocElem, m_angleConvertConst, m_deltaOffsets);
        setFacing(detector, pLocElem);
        setLogfile(detector, pCompElem, m_instrument->getLogfileCache()); // params specified within <component>
        setLogfile(detector, pLocElem, m_instrument->getLogfileCache());  // params specified within specific <location>
        // If enabled, check for a 'neutronic position' tag and add to cache (null pointer added if not found)
        if ( m_indirectPositions )
        {
          m_neutronicPos[detector] = pLocElem->getChildElement("neutronic");
        }

        try
        {
          if ( pCompElem->hasAttribute("mark-as") || pLocElem->hasAttribute("mark-as") )
            m_instrument->markAsMonitor(detector);
          else
            m_instrument->markAsDetector(detector);
        }
        catch(Kernel::Exception::ExistsError&)
        {
          std::stringstream convert;
          convert << detector->getID();
          throw Kernel::Exception::InstrumentDefinitionError("Detector with ID = " + convert.str() +
            " present more then once in XML instrument file", m_filename);
        }
        // Add all monitors and detectors to 'facing component' container. This is only used if the
        // "facing" elements are defined in the instrument definition file
        m_facingComponent.push_back(detector);
        //-------------- Not a Detector nor a RectangularDetector ------------------------------
        std::string name = LoadInstrumentHelper::getNameOfLocationElement(pLocElem);

        Geometry::ObjComponent *comp = new Geometry::ObjComponent(name, mapTypeNameToShape[typeName], parent);
        parent->add(comp);
        // check if special Source or SamplePos Component
        if ( category.compare("Source") == 0 )
        {
          m_instrument->markAsSource(comp);
        }
        if ( category.compare("SamplePos") == 0 )
        {
          m_instrument->markAsSamplePos(comp);
        }
        // set location for this newly added comp and set facing if specified in instrument def. file. Also
        // check if any logfiles are referred to through the <parameter> element.
        LoadInstrumentHelper::setLocation(comp, pLocElem, m_angleConvertConst, m_deltaOffsets);
        setFacing(comp, pLocElem);
        setLogfile(comp, pCompElem, m_instrument->getLogfileCache()); // params specified within <component>
        setLogfile(comp, pLocElem, m_instrument->getLogfileCache());  // params specified within specific <location>
      }

    //-----------------------------------------------------------------------------------------------------------------------
    *  @param pE ::  Poco::XML element that points to an \<idlist\>
    *  @param idList :: The structure to populate with detector ID numbers
    *
    *  @throw logic_error Thrown if argument is not a child of component element
    *  @throw InstrumentDefinitionError Thrown if issues with the content of XML instrument file
    */
    void LoadInstrument::populateIdList(Poco::XML::Element* pE, IdList& idList)
    {
      if ( (pE->tagName()).compare("idlist") )
      {
        g_log.error("Argument to function createIdList must be a pointer to an XML element with tag name idlist.");
        throw std::logic_error( "Argument to function createIdList must be a pointer to an XML element with tag name idlist." );
      }
      idList.idname = pE->getAttribute("idname");
      // If idname element has start and end attributes then just use those to populate idlist.
      // Otherwise id sub-elements

      if ( pE->hasAttribute("start") )
      {
        int startID = atoi( (pE->getAttribute("start")).c_str() );

        int endID;
        if ( pE->hasAttribute("end") )
          endID = atoi( (pE->getAttribute("end")).c_str() );
        else
          endID = startID;

        int increment = 1;
        if ( pE->hasAttribute("step") ) increment = atoi( (pE->getAttribute("step")).c_str() );
        idList.vec.reserve(endID-startID/increment);
        for (int i = startID; i != endID+increment; i += increment)
          idList.vec.push_back(i);
      }
      else
        // test first if any <id> elements

        NodeList* pNL = pE->getElementsByTagName("id");
          throw Kernel::Exception::InstrumentDefinitionError("No id subelement of idlist element in XML instrument file", m_filename);
        // get id numbers

        NodeIterator it(pE, NodeFilter::SHOW_ELEMENT);

        Node* pNode = it.nextNode();
        while (pNode)
          if ( pNode->nodeName().compare("id")==0 )
          {
            Element* pIDElem = static_cast<Element*>(pNode);

            if ( pIDElem->hasAttribute("val") )
            {
              int valID = atoi( (pIDElem->getAttribute("val")).c_str() );
              idList.vec.push_back(valID);
            }
            else if ( pIDElem->hasAttribute("start") )
            {
              int startID = atoi( (pIDElem->getAttribute("start")).c_str() );

              int endID;
              if ( pIDElem->hasAttribute("end") )
                endID = atoi( (pIDElem->getAttribute("end")).c_str() );
              else
                endID = startID;