Instrument Access via SpectrumInfo, DetectorInfo, ComponentInfo

Introduction

There are three layers to access instrument information, SpectrumInfo, DetectorInfo, and ComponentInfo, which are introduced to Mantid as part of Instrument 2.0. These classes store all commonly accessed information about spectra and detectors, components, and the relationships between them. Masking, monitor flags, L1, L2, 2-theta and position are stored as part of DetectorInfo. In addition, ComponentInfo provides the API to tree and shape related operations historically performed by Instrument type.

A spectrum corresponds to (a group of) one or more detectors. Most algorithms work with spectra and thus SpectrumInfo would be used. Some algorithms work on a lower level (with individual detectors) and thus DetectorInfo would be used.

The legacy Instrument largely consists of Detectors and Components - all detectors are also components. DetectorInfo and ComponentInfo are the respective replacements for these. ComponentInfo introduces a component index for access, and geometry:DetectorInfo introduces a detector index, these will be discussed further below. DetectorInfo and ComponentInfo share in-memory data. The difference between the two is best thought about in terms of their interfaces. The interface for DetectorInfo is designed for working with detectors, and the interface for ComponentInfo is designed for working with generic components.

In many cases direct access to legacy Instrument can be removed by using these layers. This will also help in moving to using indexes for enumeration, and only working with IDs for user-facing input.

Current Status

SpectrumInfo, DetectorInfo and ComponentInfo are largely complete, with a diminishing number of cases where any legacy direct Instrument access is still necessary. However, using the new interfaces everywhere now will help with the eventual complete rollout of Instrument 2.0.

SpectrumInfo

SpectrumInfo can be obtained from a call to mantid.api.MatrixWorkspace.spectrumInfo(). The wrapper class holds a reference to a DetectorInfo object and calls through to this for access to information on masking, monitor flags etc.

DetectorInfo

DetectorInfo can be obtained from a call to mantid.api.ExperimentInfo.detectorInfo() (usually this method would be called on MatrixWorkspace). The wrapper class holds a reference to the parametrised instrument for retrieving the relevant information.

There is also a near-complete implementation of the “real” DetectorInfo class, in the Beamline namespace. The wrapper DetectorInfo class (which you get from detectorInfo()) holds a reference to the real class. This does not affect the rollout, where the wrapper class should still be used in all cases.

ExperimentInfo now also provides a method mutableDetectorInfo() so that non-const access to the DetectorInfo is possible for purposes of writing detector related information such as positions or rotations.

The python interface to DetectorInfo has matured, and includes widespread immutable access via iterators. The iterators can also be used to set masking flags.

See DetectorInfo for more information.

ComponentInfo

ComponentInfo can be obtatined from a call to ExperimentInfo::componentInfo() (usually this method would be called on MatrixWorkspace). Much like DetectorInfo, the ComponentInfo yielded from this method call is a wrapper, which contains shape and index information, that cannot yet be moved in to the real Beamline::ComponentInfo. However, replacing existing usage of IComponent and IObjComponent wherever possible with ComponentInfo across the framework will represent a major step forwards.

For writing to the component tree. You can extract a non-const ComponentInfo via ExperimentInfo::mutableComponentInfo.

The python interface to ComponentInfo has matured, and now provides equal, if not better support than the Instrument API for navigating the high-level instrument. Iterator support has also been provided for ComponentInfo.

See ComponentInfo for more information.

Changes for Rollout

Performance Tests

Before starting the refactoring work please take a look at the state of any performance tests that exist for the algorithms. If they exist they should be run to get the “before” timings. If they do not exist please add performance test for any algorithms that are widely used, or might be expected to have a performance increase. See this performance test added for the previous SpectrumInfo rollout phase for an example of adding such a test.

Each PR should include the runtime metrics for the algorithms changed, so that improvements can be captured for the release notes.

ComponentInfo

Basics

The conversion is similar to that for DetectorInfo, which is already largely complete in the framework. For ComponentInfo all instances of Instrument::getComponentByID(const ComponentID id) should be replaced using calls to the ComponentInfo object obtained from MatrixWorkspace::componentInfo(). The methods below can then be called on ComponentInfo instead of on the component.

  • isDetector(componentIndex)
  • detectorsInSubtree(componentIndex)
  • componentsInSubtree(componentIndex)
  • position(componentIndex)
  • rotation(componentIndex)
  • hasParent(componentIndex)
  • parent(componentIndex)
  • position(componentIndex)
  • solidAngle(componentIndex)
  • scaleFactor(componentIndex)
  • sourcePosition()
  • samplePosition()
  • l1()

The following methods are useful helpers on ComponentInfo that allow the extraction of the component index for key components

  • root()
  • source()
  • sample()

Indexing

The ComponentInfo object is accessed by an index going from 0 to the number of components (including the instrument iteself). The component index for a detector is EQUAL to the detector index, this is an important point to understand. In other words, a detector with a Detector Index of 5, for the purposes of working with a DetectorInfo and will have a Component Index of 5, when working with a ComponentInfo. Explained in yet another way: The first 0 - n components referenced in the ComponentInfo are detectors, where n is the total number of detectors. This guarantee can be leveraged to provide speedups, as some of the examples will show.

A ComponentID for compatibility with older code, and be extracted from ComponentInfo::componentID(componentIndex), but such calls should be avoided where possible.

It is also possible to use the method componentInfo.indexOf(componentID) to get the index for a particular component ID. However, this is a call to a lookup in an unordered map, so is an expensive calculation which should be avoided where possible.

One should NEVER expose a Component Index or Detector Index through a user facing interface, such an algorithm or fit function.. Detector Index and Component Indexes are internal concepts for fast enumeration. It is however desirable to translate from a ComponentIndex via ComponentInfo::indexOf to as early as possible and in such a way to avoid repeated calls to this method, as stated above. Likewise, conversion back to a ComponentIndex, if so required, should be done as infrequently and, as late as possible.

Below is an example refactoring.

Before refactoring

auto instrument = ws->getInstrument();
std::vector<IComponent_const_sptr> children;
instrument->getChildren(children, true /*Get all sub-children too*/);
std::vector<IComponent_const_sptr>::const_iterator it;
for (it = children.begin(); it != children.end(); ++it) {
  if (const ObjComponent* obj = dynamic_cast<const ObjComponent*>(it->get())) {
    // Do something with the obj component
    obj.solidAngle(observer);
  }
}

After - looping over index

#include "MantidGeometry/Instrument/ComponentInfo.h"

...

const auto &componentInfo = ws->componentInfo();
for (size_t i = 0; i < componentInfo.size(); ++i) {
  componentInfo.solidAngle(i, observer);
}

Access to Components and working with Detectors

Detector Indices are the same as the corresponding Component Indices. Note that there are no dynamic casts. The following examples are for illustration purposes only.

Combining DetectorInfo and ComponentInfo

#include "MantidGeometry/Instrument/ComponentInfo.h"
#include "MantidGeometry/Instrument/DetectorInfo.h"

...

const auto &componentInfo = ws->componentInfo();
const auto &detectorInfo = ws->componentInfo();

std::vector<double> solidAnglesForDetectors(detectorInfo.size(), -1.0);
for (size_t i = 0; i < componentInfo.size(); ++i) {
  if(componentInfo.isDetector(i) && !detectorInfo.isMasked(i))
   solidAnglesForDetectors[i] = componentInfo.solidAngle(i, observer);
  }
}

ComponentInfo can give quick access to parent and sub-tree component and detector indices.

#include "MantidGeometry/Instrument/ComponentInfo.h"
#include "MantidGeometry/Instrument/DetectorInfo.h"

size_t bank0Index; // Component index for bank 0
...

const auto &componentInfo = ws->componentInfo();
auto bankComponents = componentInfo.componentsInSubtree(bank0Index);
auto bankDetectors = componentInfo.detectorsInSubtree(bank0Index);

Mutable ComponentInfo

The method ExperimentInfo::mutableComponentInfo() returns a non-const ComponentInfo object. This allows the methods below to be used.

  • setPosition(const size_t index, const Kernel::V3D &position);
  • setRotation(const size_t index, const Kernel::Quat &rotation);
  • setScaleFactor(const size_t index, const Kernel::V3D &scaleFactor);

Useful Tips

  • Creation of ComponentInfo is not cheap enough to perform uncessarily inside loops. For const access, ws.componentInfo() should be called outside of loops that enumerate over all components.
  • If a ComponentInfo object is required for more than one workspace, include the workspace name in the variable name to avoid confusion.
  • Get the ComponentInfo object as a const-ref and use const auto &componentInfo = ws->componentInfo();, do not get a non-const reference unless you really do need to modify the object, and ensure that the & is always included to prevent accidental copies.
  • ComponentInfo is widely forward declared. Ensure that you import - #include "MantidGeometry/Instrument/ComponentInfo.h"
  • As explained above, a detector index is the same thing as a component index. No translation necessary. The fact that the first 0-n component indexes are for detectors is a feature that can be leveraged.
  • A bank always has a higher component index than any of its nested components. The root is the highest component index of all. This feature can be leveraged. Consider reverse iterating through component indexes when performing operations that involve higher-level components.

Dealing with problems

Join #instrument-2_0 on Slack if you need help or have questions. Please also feel free to get in touch with Owen Arnold, Simon Heybrock or Lamar Moore directly for any questions about the ComponentInfo rollout.

Category: Concepts