Skip to content
Snippets Groups Projects
tube_spec.py 13.2 KiB
Newer Older
# Mantid Repository : https://github.com/mantidproject/mantid
#
# Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
#     NScD Oak Ridge National Laboratory, European Spallation Source
#     & Institut Laue - Langevin
# SPDX - License - Identifier: GPL - 3.0 +
#pylint: disable=invalid-name
Karl Palmen's avatar
Karl Palmen committed

# This class is to take a specification of a set of tubes for an instrument provided by a user
# and then provide a list of workspace index ranges corresponding to each of the specified tubes
# to be used the the tube calibration code

# Author: Karl Palmen ISIS

from __future__ import absolute_import, division, print_function

    The python class :class:`~tube_spec.TubeSpec` provides a way of specifying a set of tubes for
    calibration, so that the necessary information about detectors etc. is forethcoming. This class
    is provide by the python file tube_spec.py. The function :func:`~tube_calib.getCalibration` of
    :mod:`tube_calib` needs such an object.

     * :meth:`~tube_spec.TubeSpec.setTubeSpecByString`
     * :meth:`~tube_spec.TubeSpec.setTubeSpecByStringArray`

    There are some functions useful for getting information about a tube specification.
    It is not necessary to call them in a calibration script, but they may be useful for checking
    the specification. These methods are:

     * :meth:`~tube_spec.TubeSpec.getNumTubes`
     * :meth:`~tube_spec.TubeSpec.getDetectorInfoFromTube`
     * :meth:`~tube_spec.TubeSpec.getTubeLength`
     * :meth:`~tube_spec.TubeSpec.getTubeName`
     * :meth:`~tube_spec.TubeSpec.getTubeByString`

        Tubes are currently ordered in the specification in the same order as they appear in the IDF.
        This may differ from the order they appear in the workspace indices.

Karl Palmen's avatar
Karl Palmen committed
    def __init__(self,ws):
        """
        The constructor creates empty tube specification for specified instrument.
        :param ws: workspace containing the specified instrument with one pixel detector per spectrum.
        self.ws = ws
Karl Palmen's avatar
Karl Palmen committed
        self.inst = ws.getInstrument()
        self.numTubes = 0
        self.componentNameArray = []
        self.componentArray = []
        self.minNumDetsInTube = 200
Karl Palmen's avatar
Karl Palmen committed
        self.tubes = []
        self.delimiter = '/' # delimiter between parts of string in tree
    def setTubeSpecByString(self, tubeSpecString ):
        """
        Define the sets of tube from the workspace.

luz.paz's avatar
luz.paz committed
        Sets tube specification by string. The string specifies a component of the instrument
Karl Palmen's avatar
Karl Palmen committed
        as in the instrument tree of its IDF file. This component may contain one or more tubes
        and possibly all the tunes in the instrument.
        If the tube specification is not empty this component is added to those already
        in the specification. No checking is done for repeated or overlapping components.

        :param tubeSpecString: string specifying tubes of a component of the instrument

        The **tubeSpecString** may be the full path name for the component or
Karl Palmen's avatar
Karl Palmen committed
        steps may be missed out provided it remains unique.
        For example panel03 of WISH can be specified by just **panel03**,
        because panel03 is unique within the instrument. Also tube012 of this panel,
        which is unique within the panel but not within the instrument
        can be specified by **panel03/tube012** but not **tube012**.
Karl Palmen's avatar
Karl Palmen committed
        If the specification is not unique, the first found will be used and there will
        be no error message. So if in doubt don't skip a step.
        """
        self.componentNameArray.append(tubeSpecString)
Karl Palmen's avatar
Karl Palmen committed
        self.numTubes = -1  # Negative value forces tubes to be searched and counted
    def setTubeSpecByStringArray( self, tubeSpecArray ):
        """
        Define the sets of tube from the workspace with an array of strings.

        Set tube specification like setTubeSpecByString, but with an array of string
        to enable multiple components to be calibrated.
        This function allows you to calibrate a set of tubes that is not defined by a single component.
        For example a set of windows. It takes an array of strings as its argument.
        Each string specifies a component such as a window or a single tube in the same manner as for
        :meth:`~tube_spec.TubeSpec.setTubeSpecByString`. The components must be disjoint.

        :param tubeSpecArray: array of strings (ex. ['door1', 'door2'])

        """
        for i in range(len(tubeSpecArray)):
            self.setTubeSpecByString(tubeSpecArray[i])
Karl Palmen's avatar
Karl Palmen committed
    def getInstrumentName (self):
        return self.inst.getName()
Karl Palmen's avatar
Karl Palmen committed
    def isTube(self, comp):
Karl Palmen's avatar
Karl Palmen committed
        Determines whether the component is a tube.
        :param comp: the component
        :rtype: Value, true if component passes test as being a tube
Karl Palmen's avatar
Karl Palmen committed
        """
        # We simply assume it's a tube if it has a large number of children
        if  hasattr( comp, "nelements"):
            return comp.nelements() >= self.minNumDetsInTube
Karl Palmen's avatar
Karl Palmen committed
        else:
Karl Palmen's avatar
Karl Palmen committed
    def searchForTubes(self, comp):
         Searches the component for tubes and saves them in array, appending if array is not empty.
         :param comp: the component
Karl Palmen's avatar
Karl Palmen committed
         """
         # Go through all descendents that are not a descendent of a tube and if it's a tube, store and count it.
        if self.isTube( comp ):
            self.tubes.append( comp )
Karl Palmen's avatar
Karl Palmen committed
         # If not tube, Search children, if any
            if  hasattr( comp, "nelements"):
                for i in range(comp.nelements()):
                    self.searchForTubes(comp[i])
Karl Palmen's avatar
Karl Palmen committed
    def getNumTubes(self):
Karl Palmen's avatar
Karl Palmen committed
        Returns number of tubes specified. May also save info about these tubes
        :rtype: Value, number of tubes (-1 for erroneous specification)
Karl Palmen's avatar
Karl Palmen committed
            return self.numTubes
        # We have a negative number set in self.numTubes, so we search for tubes
        comps = self.getComponents()
Karl Palmen's avatar
Karl Palmen committed
            return self.numTubes
        for i in range( len(comps)):
            self.searchForTubes(comps[i])
Karl Palmen's avatar
Karl Palmen committed
        self.numTubes = len(self.tubes)
        return self.numTubes
Karl Palmen's avatar
Karl Palmen committed
    def getComponent ( self ):
Karl Palmen's avatar
Karl Palmen committed
        Returns instrument component corresponding to specification
        :rtype: instrument component
        if  self.componentArray != []:
            return self.componentArray[0]
        print("Looking for", self.componentNameArray[0], end="")
        comp = self.inst.getComponentByName(self.componentNameArray[0])
            self.componentArray.append(comp)
        return self.componentArray[0]
    def getComponents ( self ):
        Returns instrument components corresponding to specification
        :rtype: array of instrument components
        if  self.componentArray != []:
            return self.componentArray
        # We look for the components
        for i in range( len(self.componentNameArray)):
            print("Looking for", self.componentNameArray[i])
            comp = self.inst.getComponentByName(self.componentNameArray[i])
            self.componentArray.append(comp)
            print("Did not find", self.componentNameArray[i])
            print("Tube specification not valid")
        return self.componentArray
Karl Palmen's avatar
Karl Palmen committed
    def getDetectorInfoFromTube( self, tubeIx ):
        Returns detector info for one tube.
        Returns information about detectors in the ( **tubeIx** +1)st tube in the specification,
        where **tubeIx** is the argument. Three integers are returned:

          the ID for the first detector,
          the number of detectors in the tube and
          the increment step of detector IDs in the tube (usually 1, but may be -1).
Karl Palmen's avatar
Karl Palmen committed
        It assumes that all the pixels along the tube have consecutive detector IDs.

        :param tubeIx:  index of Tube in specified set

        :rtype: ID of first detector, number of detectors and step between detectors +1 or -1
        nTubes = self.getNumTubes()
            print("Error in listing tubes")
            return 0, 0, 1
        if tubeIx < 0 or tubeIx >= nTubes:
            print("Tube index",tubeIx,"out of range 0 to",nTubes)
            return 0, 0, 1
        comp = self.tubes[tubeIx]
Karl Palmen's avatar
Karl Palmen committed
            firstDet = comp[0].getID()
            numDet = comp.nelements()
            # Allow for reverse numbering of Detectors
            lastDet = comp[numDet-1].getID()
                if  firstDet - lastDet + 1 != numDet:
                    print("Detector number range",firstDet-lastDet+1," not equal to number of detectors",numDet)
                    print("Detectors not numbered continuously in this tube. Calibration will fail for this tube.")
                if  lastDet - firstDet + 1 != numDet:
                    print("Detector number range",lastDet-firstDet+1," not equal to number of detectors",numDet)
                    print("Detectors not numbered continuously in this tube. Calibration will fail for this tube.")
Karl Palmen's avatar
Karl Palmen committed
        else:
            print(self.componentNameArray[0], tubeIx, "not found")
Karl Palmen's avatar
Karl Palmen committed
    def getTubeLength( self, tubeIx ):
        """
        Returns length of the ( **tubeIx** +1)st tube.

        :param tubeIx:  index of Tube in specified set

        :rtype: Length of tube (first pixel to last pixel) in metres. 0.0 if tube not found.
        nTubes = self.getNumTubes()
            print("Error in listing tubes")
        if tubeIx < 0 or tubeIx >= nTubes:
            print("Tube index",tubeIx,"out of range 0 to",nTubes)
        comp = self.tubes[tubeIx]
            numDet = comp.nelements()
Karl Palmen's avatar
Karl Palmen committed
            return comp[0].getDistance( comp[numDet-1] )
        else:
            print(self.componentNameArray[0], tubeIx, "not found")
Karl Palmen's avatar
Karl Palmen committed
            return 0.0
Karl Palmen's avatar
Karl Palmen committed
    def getTubeName ( self, tubeIx ):
Karl Palmen's avatar
Karl Palmen committed
        This function is not used in tube calibration, but may be useful as a diagnostic.
        It is used in creating the peakfile, which lists the peaks found for each tube.

        :param tubeIx:  index of Tube in specified set

        :rtype: Name of tube as in IDF or 'unknown' if not found.
        nTubes = self.getNumTubes()
            print("Error in listing tubes")
            return 'Unknown'
        if tubeIx < 0 or tubeIx >= nTubes:
            print("Tube index",tubeIx,"out of range 0 to",nTubes)
            return 'Unknown'

        comp = self.tubes[tubeIx]

            return comp.getFullName()
Karl Palmen's avatar
Karl Palmen committed
        else:
            print(self.componentNameArray[0], tubeIx, "not found")
Karl Palmen's avatar
Karl Palmen committed
            return "Unknown"

    def getTubeByString(self, tubeIx):
        Returns list of workspace indices of a tube set that has been specified by string
        It assumes that all the pixels along the tube have consecutive detector IDs

        :param tubeIx:  index of Tube in specified set
        firstDet, numDet, step = self.getDetectorInfoFromTube( tubeIx )
Karl Palmen's avatar
Karl Palmen committed
        wkIds = []
luz.paz's avatar
luz.paz committed
        # print " First detector", firstDet," Last detector", firstDet+numDet-1, "Number of detectors", numDet
        # print "Histograms", self.ws.getNumberHistograms()
Karl Palmen's avatar
Karl Palmen committed
        # First check we have one detector per histogram/workpsaceID/spectrum
        sampleIndex = 10
        sp = self.ws.getSpectrum(sampleIndex)
Karl Palmen's avatar
Karl Palmen committed
        detids = sp.getDetectorIDs()
        numDetsPerWkID = len(detids)
            print("We have",numDetsPerWkID,"detectors per workspace index. 1 is required.")
            print("cannot obtain range of workspace indices for this tube in this workspace")
Karl Palmen's avatar
Karl Palmen committed
        # Go and get workspace Indices
            startDet = firstDet - numDet + 1
        else:
            startDet = firstDet
            for i in range (0, self.ws.getNumberHistograms(), numDet):
                try:
                    deti = self.ws.getDetector(i)
                except:
                if detID  >= startDet and detID < startDet+numDet:
                    iPixel = detID - firstDet
                    wkIds = range( i - iPixel, i - iPixel + step*numDet, step)
Karl Palmen's avatar
Karl Palmen committed
        else:
            print("specified tube has no detectors.")
Karl Palmen's avatar
Karl Palmen committed
            self.numTubes = 0
Karl Palmen's avatar
Karl Palmen committed

    def getTube(self, tubeIx):
Karl Palmen's avatar
Karl Palmen committed
        Returns list of workspace indices of a tube
        :param tubeIx:  index of Tube in specified set
        :rtype: list of indices
Karl Palmen's avatar
Karl Palmen committed
        """
        nTubes = self.getNumTubes()
        if  (0 <= tubeIx) & (tubeIx < nTubes) :
            return self.getTubeByString(tubeIx)
        else:
            print("Tube", tubeIx, "out of range 0 to",self.numTubes,".")