示例#1
0
class ThermoStatedCellHolder(Device):
    """
    The device holds fundamental parameters for the sample environment
    `Thermo Stated Cell Holder`. This holder has two rows. Each row can have
    four cartridges (thus in total of eight cartridges).

    Each cartridge can be loaded with three different kind of cell holder,
    namely, `Narrow Cell` with 10 positions, `Wide Cell` with four positions and
    `Rotating Cell` with three positions.
    """
    parameters = {
        'cell_type_indices':
        Param(
            'Cell type indices',
            type=listof(int),
            default=[0] * 8,  # Number of cartridges
            settable=False),
        'cell_type_names':
        Param('Cell types',
              type=listof(str),
              default=['Narrow Cell'] * 8,
              settable=False),
        'first_positions':
        Param('Scanned first positions',
              type=listof(tupleof(float, float)),
              default=[(0.0, 0.0)] * 8,
              settable=False),
        'positions':
        Param('Calculated positions',
              type=listof(listof(tupleof(float, float))),
              settable=False)
    }
示例#2
0
class SelectorLambda(Moveable):
    """
    Control selector wavelength directly, converting between speed and
    wavelength.

    This uses not the default conversion from the Astrium selector classes,
    since the selector is tilted against the beam, and it is easier to use
    the constant determined by wavelength calibration.

    This class allows two calibration settings, determined by the current
    value of a (manually moved) "tilt" device.
    """

    parameters = {
        'constants':
        Param(
            'Conversion constants: '
            'lam[Ang] = constant/speed[Hz] + offset',
            type=tupleof(float, float),
            mandatory=True,
            unit='Ang Hz'),
        'offsets':
        Param('Conversion offsets: '
              'lam[Ang] = constant/speed[Hz] + offset',
              type=tupleof(float, float),
              mandatory=True,
              unit='Ang'),
    }

    attached_devices = {
        'seldev': Attach('The selector speed device', Moveable),
        'tiltdev': Attach('The tilt device', Readable),
    }

    hardware_access = False

    def doRead(self, maxage=0):
        tilted = bool(self._attached_tiltdev.read(maxage))
        speed = self._attached_seldev.read(maxage)
        return (60 * self.constants[tilted] / speed) + self.offsets[tilted] \
            if speed else -1

    def doIsAllowed(self, value):
        if value == 0:
            return False, 'zero wavelength not allowed'
        tilted = bool(self._attached_tiltdev.read(0))
        speed = int(
            round(60 * self.constants[tilted] /
                  (value - self.offsets[tilted])))
        return self._attached_seldev.isAllowed(speed)

    def doStart(self, value):
        tilted = bool(self._attached_tiltdev.read(0))
        speed = int(
            round(60 * self.constants[tilted] /
                  (value - self.offsets[tilted])))
        self.log.debug('moving selector to %f rpm', speed)
        self._attached_seldev.start(speed)
示例#3
0
class Detector(Moveable):
    """Combination device for the detector axes."""

    valuetype = tupleof(float, float, float)

    attached_devices = {
        'x': Attach('X motor', Moveable),
        'y': Attach('Y motor', Moveable),
        'z': Attach('Z motor', Moveable),
    }

    parameter_overrides = {
        'unit':   Override(mandatory=False, default='mm'),
        'fmtstr': Override(default='%.1f, %.2f, %.0f'),
    }

    def doRead(self, maxage=0):
        return (self._attached_x.read(maxage),
                self._attached_y.read(maxage),
                self._attached_z.read(maxage))

    def doIsAllowed(self, pos):
        for (i, name, dev) in [(0, 'x', self._attached_x),
                               (1, 'y', self._attached_y),
                               (2, 'z', self._attached_z)]:
            ok, why = dev.isAllowed(pos[i])
            if not ok:
                return False, name + ': ' + why
        return True, ''

    def doStart(self, pos):
        self._attached_x.start(pos[0])
        self._attached_y.start(pos[1])
        self._attached_z.start(pos[2])
示例#4
0
class LinearROIChannel(PostprocessPassiveChannel):
    """Calculates counts for a region of interest in a 1D spectrum."""

    parameters = {
        'roi':
        Param('Region of interest (start, end) including end',
              tupleof(int, int),
              settable=True,
              category='general'),
    }

    parameter_overrides = {
        'unit': Override(default='cts'),
        'fmtstr': Override(default='%d'),
    }

    def getReadResult(self, arrays, _results, _quality):
        if any(self.roi):
            return [arr[self.roi[0]:self.roi[1] + 1].sum() for arr in arrays]
        return [arr.sum() for arr in arrays]

    def valueInfo(self):
        if len(self.readresult) > 1:
            return tuple(
                Value(name=self.name + '[%d]' % i, type='counter', fmtstr='%d')
                for i in range(1,
                               len(self.readresult) + 1))
        return Value(name=self.name, type='counter', fmtstr='%d'),
示例#5
0
class Spectrum(VirtualImage):

    parameters = {
        'preselection': Param('Preset value for this channel', type=float,
                              settable=True),
    }

    parameter_overrides = {
        'sizes': Override(type=tupleof(intrange(1, 1), intrange(1, 16384)),
                          default=(1, 16384)),
        'ismaster': Override(settable=True),
    }

    # set to True to get a simplified doEstimateTime
    is_timer = False

    def doEstimateTime(self, elapsed):
        if not self.ismaster or self.doStatus()[0] != status.BUSY:
            return None
        if self.is_timer:
            return self.preselection - elapsed
        else:
            counted = float(self.doRead()[0])
            # only estimated if we have more than 3% or at least 100 counts
            if counted > 100 or counted > 0.03 * self.preselection:
                if 0 <= counted <= self.preselection:
                    return (self.preselection - counted) * elapsed / counted

    def doReadArray(self, _quality):
        return self._buf[0]
示例#6
0
class RectROIChannel(PostprocessPassiveChannel):
    """Calculates counts for a rectangular region of interest."""

    parameters = {
        'roi':
        Param('Rectangular region of interest (x, y, width, height)',
              tupleof(int, int, int, int),
              settable=True,
              category='general'),
    }

    parameter_overrides = {
        'unit': Override(default='cts'),
        'fmtstr': Override(default='%d'),
    }

    def getReadResult(self, arrays, _results, _quality):
        if any(self.roi):
            x, y, w, h = self.roi
            return [arr[y:y + h, x:x + w].sum() for arr in arrays]
        return [arr.sum() for arr in arrays]

    def valueInfo(self):
        if len(self.readresult) > 1:
            return tuple(
                Value(name=self.name + '[%d]' % i, type='counter', fmtstr='%d')
                for i in range(1,
                               len(self.readresult) + 1))
        return Value(name=self.name, type='counter', fmtstr='%d'),
示例#7
0
class LaserDetector(Measurable):
    parameters = {
        'pv_name':
        Param('Store the current identifier',
              internal=False,
              type=str,
              default="SES-SCAN:LSR-001:AnalogInput",
              settable=True),
        'curstatus':
        Param('Store the current device status',
              internal=True,
              type=tupleof(int, str),
              default=(status.OK, ""),
              settable=True),
        'answer':
        Param('Store the current device status',
              internal=True,
              type=float,
              default=0,
              settable=True),
    }

    def doPrepare(self):
        self.curstatus = status.BUSY, "Preparing"
        self.curstatus = status.OK, ""

    def doStart(self):
        max_pow = 0
        results = []
        for _ in range(5):
            session.delay(0.1)
            val = pvget(self.pv_name)
            max_pow = max(val, max_pow)
            results.append(val)
        self.answer = sum(results) / len(results)

    def doRead(self, maxage=0):
        return [self.answer]

    def doFinish(self):
        self._stop_processing()

    def _stop_processing(self):
        self.curstatus = status.OK, ""

    def doSetPreset(self, t, **preset):
        self.curstatus = status.BUSY, "Preparing"

    def doStop(self):
        # Treat like a finish
        self._stop_processing()

    def doStatus(self, maxage=0):
        return self.curstatus

    def duringMeasureHook(self, elapsed):
        return LIVE

    def valueInfo(self):
        return Value(self.name, unit=self.unit),
示例#8
0
文件: kafka.py 项目: umithardal/nicos
class ImageKafkaDataSink(ProducesKafkaMessages, DataSink):
    """ Data sink which writes images to Kafka after serializing
    them. The parameter *channeltostream* provides a dict of all
    the image channels from which the data is to be be forwarded
    mapped to a tuple of (kafka topic, message source name)
    """

    parameters = {
        'maximagesize': Param('Expected max array size of the image',
                              type=int, default=5e7),
        'channeltostream': Param(
            'Dict of image channel name(to be forwarded) -> (topic, source)',
            type=dictof(str, tupleof(str, str)), mandatory=True),
    }

    parameter_overrides = {
        'settypes': Override(default=[POINT]),
    }

    handlerclass = ImageKafkaDataSinkHandler
    serializer = HistogramFlatbuffersSerializer()

    def doInit(self, mode):
        # Increase the maximum message size that the producer can send
        self._setProducerConfig(max_request_size=self.maximagesize)
示例#9
0
文件: dls.py 项目: umithardal/nicos
class DLSCard(BaseImageChannel):
    attached_devices = {
        'wheels': Attach('The filter wheel positions', Readable,
                         multiple=True, optional=True),
    }

    parameters = {
        'angles': Param('Scattering angles of the detector',
                        type=tupleof(float, float), mandatory=True,
                        settable=True),
        'mode':   Param('Measure mode', type=oneof(*MODES),
                        default='cross_cross', settable=True),
    }

    def _get_filters(self):
        return ' '.join('%d' % wh.read() for wh in self._attached_wheels)

    def setMode(self):
        self._dev.readoutMode = self.mode

    def readAbscissa(self):
        return self._dev.abscissa

    def readIntensity(self):
        data = self._dev.intensity
        return data.reshape((len(data) // 3, 3))

    abscissa_arraydesc = ArrayDesc('data', shape=(264,), dtype='<f8')
    intensity_arraydesc = ArrayDesc('data', shape=(100, 3), dtype='<f8')
示例#10
0
class HoveringAxis(Axis):
    """An axis that also controls air for airpads."""

    attached_devices = {
        'switch': Attach('The device used for switching air on and off',
                         Moveable),
    }

    parameters = {
        'startdelay':
        Param('Delay after switching on air',
              type=float,
              mandatory=True,
              unit='s'),
        'stopdelay':
        Param('Delay before switching off air',
              type=float,
              mandatory=True,
              unit='s'),
        'switchvalues':
        Param('(off, on) values to write to switch device',
              type=tupleof(anytype, anytype),
              default=(0, 1)),
    }

    def doTime(self, start, end):
        return Axis.doTime(self, start, end) + self.startdelay + self.stopdelay

    def _preMoveAction(self):
        self._adevs['switch'].maw(self.switchvalues[1])
        session.delay(self.startdelay)

    def _postMoveAction(self):
        session.delay(self.stopdelay)
        self._adevs['switch'].maw(self.switchvalues[0])
示例#11
0
class SwordAxis(Axis):

    attached_devices = {
        'switch': Attach('The device used for switching the brake', Moveable),
    }

    parameters = {
        'startdelay':   Param('Delay after switching on brake', type=float,
                              mandatory=True, unit='s'),
        'stopdelay':    Param('Delay before switching off brake', type=float,
                              mandatory=True, unit='s'),
        'switchvalues': Param('(on, off) values to write to brake switch',
                              type=tupleof(anytype, anytype), default=(2, 1)),
    }

    def doStatus(self, maxage=0):
        stval, ststr = Axis.doStatus(self, maxage)
        # special case: the Phytron server correctly returns an error if the
        # enable bit is not set, but since this is always the case we want to
        # present it as just a WARN state
        if stval == status.ERROR and ststr == 'motor halted, ENABLE not set':
            return status.WARN, ststr
        return stval, ststr

    def doTime(self, start, end):
        return Axis.doTime(self, start, end) + self.startdelay + self.stopdelay

    def _preMoveAction(self):
        self._adevs['switch'].maw(self.switchvalues[1])
        session.delay(self.startdelay)

    def _postMoveAction(self):
        session.delay(self.stopdelay)
        self._adevs['switch'].maw(self.switchvalues[0])
示例#12
0
class PolSwitcher(SequencerMixin, MultiSwitcher):
    """The turntable that contains the polarizer or neutron guide.

    Changing the table positions has to be done in a certain order, so that
    the final positions of the polarizer or guide are always reproducible.
    """

    parameters = {
        'movepos':  Param('Position (xv, yv, xh, yh) while rotating',
                          type=tupleof(float, float, float, float),
                          default=(5., 5., 5., 5.)),
    }

    parameter_overrides = {
        'fmtstr':     Override(default='%s'),
        'unit':       Override(mandatory=False, default=''),
    }

    def doInit(self, mode):
        MultiSwitcher.doInit(self, mode)
        if len(self._attached_moveables) != 5:
            raise ConfigurationError(self, 'must have exactly 5 moveables')
        self._mot_rot, self._mot_xv, self._mot_yv, self._mot_xh, \
            self._mot_yh = self._attached_moveables
        self.valuetype = oneof(*sorted(self.mapping, key=num_sort))

    def _generateSequence(self, target):
        seq = []
        targets = self.mapping[target]
        rot_target, xv_target, xh_target, yv_target, yh_target = targets
        # move translation units to move pos (in parallel)
        seq.append(tuple(SeqDev(m, p) for (m, p)
                         in zip(self._attached_moveables[1:], self.movepos)))
        # move rotation stage
        seq.append(SeqDev(self._mot_rot, rot_target))
        # move Y axes to final position with backlash
        seq.append((SeqDev(self._mot_yv, yv_target + 0.1),
                    SeqDev(self._mot_yh, yh_target + 0.1)))
        seq.append(SeqDev(self._mot_yv, yv_target))
        seq.append(SeqDev(self._mot_yh, yh_target))
        # move X axes to 0.1 and then to final position
        seq.append((SeqDev(self._mot_xv, 0.1),
                    SeqDev(self._mot_xh, 0.1)))
        seq.append(SeqDev(self._mot_xv, xv_target))
        seq.append(SeqDev(self._mot_xh, xh_target))
        # move rotation stage again, if it changed
        seq.append(SeqDev(self._mot_rot, rot_target))
        return seq

    def doStart(self, target):
        if self._seq_is_running():
            if self._mode == SIMULATION:
                self._seq_thread.join()
                self._seq_thread = None
            else:
                raise MoveError(self, 'Cannot start device, sequence is still '
                                      'running (at %s)!' % self._seq_status[1])
        self._startSequence(self._generateSequence(target))
示例#13
0
class MagnetSampleTheta(Moveable):
    """Class for controlling the sample rotation inside a magnet that is built
    with significant dark angles that must be avoided for incoming and
    outgoing beam, by rotating the magnet itself on the sample table.
    """

    attached_devices = {
        'sample_theta': Attach('Sample-only theta motor', Moveable),
        'magnet_theta': Attach('Magnet-plus-sample motor', Moveable),
        'two_theta':    Attach('Scattering angle', Moveable),
    }

    parameters = {
        'blocked':     Param('Blocked angle range in the magnet. 0 is the '
                             'incoming beam direction', unit='deg',
                             type=listof(tupleof(float, float))),
        'windowstep':  Param('Steps in which to move the magnet when looking '
                             'for free windows', unit='deg', type=int,
                             default=5),
    }

    def _find_window(self, gamma, magnet):
        # find a free window for incoming and outgoing beam, which is closest
        # to the current position of the magnet
        result = []
        for pos in range(0, 360, self.windowstep):
            for (a1, a2) in self.blocked:
                # check for blocked incoming beam
                if in_range(pos, -a2, -a1):
                    break
                # check for blocked outgoing beam
                if in_range(pos, -a2 + 180 + gamma, -a1 + 180 + gamma):
                    break
            else:  # no "break"
                result.append(pos)
        self.log.debug('gamma: %.3f, magnet: %.3f', gamma, magnet)
        self.log.debug('new possible positions: %s', result)
        if not result:
            raise ComputationError(self, 'no position found for magnet with '
                                   'incoming and outgoing beam free')
        return min(result, key=lambda pos: abs(pos - 0.1))

    def doStart(self, pos):
        # get target for scattering angle
        gamma = self._attached_two_theta.target
        magnet = self._attached_magnet_theta.read(0)
        # determine nearest free window
        new_magnet = self._find_window(gamma, magnet)
        self._attached_magnet_theta.start(to_range(new_magnet))
        self._attached_sample_theta.start(to_range(pos - new_magnet))

    def _getWaiters(self):
        return [self._attached_sample_theta, self._attached_magnet_theta]

    def doRead(self, maxage=0):
        angle = self._attached_magnet_theta.read(maxage) + \
            self._attached_sample_theta.read(maxage)
        return to_range(angle)
示例#14
0
文件: axis.py 项目: umithardal/nicos
class HoveringAxis(SequencerMixin, Axis):
    """An axis that also controls air for airpads."""

    attached_devices = {
        'switch': Attach('The device used for switching air on and off',
                         Moveable),
    }

    parameters = {
        'startdelay':
        Param('Delay after switching on air',
              type=float,
              mandatory=True,
              unit='s'),
        'stopdelay':
        Param('Delay before switching off air',
              type=float,
              mandatory=True,
              unit='s'),
        'switchvalues':
        Param('(off, on) values to write to switch device',
              type=tupleof(anytype, anytype),
              default=(0, 1)),
    }

    hardware_access = True

    def _generateSequence(self, target):
        return [
            SeqDev(self._attached_switch, self.switchvalues[1]),
            SeqSleep(self.startdelay),
            SeqCall(Axis.doStart, self, target),
            SeqCall(self._hw_wait),
            SeqSleep(self.stopdelay),
            SeqDev(self._attached_switch, self.switchvalues[0]),
        ]

    def _hw_wait(self):
        # overridden: query Axis status, not HoveringAxis status
        while Axis.doStatus(self, 0)[0] == status.BUSY:
            session.delay(self._base_loop_delay)

    def doStart(self, target):
        if self._seq_is_running():
            self.stop()
            self.log.info('waiting for axis to stop...')
            self.wait()
        if abs(target - self.read()) < self.precision:
            return
        self._startSequence(self._generateSequence(target))

    def doStop(self):
        # stop only the axis, but the sequence has to run through
        Axis.doStop(self)

    def doTime(self, start, end):
        return Axis.doTime(self, start, end) + self.startdelay + self.stopdelay
示例#15
0
class Polarizer(Moveable):
    """Controls both the position of the polarizer and the spin flipper.
    """

    valuetype = oneof(*POL_SETTINGS)

    hardware_access = False

    attached_devices = {
        'switcher': Attach('polarizer in/out switch', Moveable),
        'flipper': Attach('flipper', Moveable),
    }

    parameters = {
        'values':
        Param('Possible values (for GUI)',
              internal=True,
              type=listof(str),
              default=POL_SETTINGS),
        'switchervalues':
        Param('Possible values for the switcher (out, in)',
              type=tupleof(str, str),
              default=('ng', 'pol')),
    }

    parameter_overrides = {
        'fmtstr': Override(default='%s'),
        'unit': Override(mandatory=False, default=''),
    }

    def doRead(self, maxage=0):
        switcher_pos = self._attached_switcher.read(maxage)
        flipper_pos = self._attached_flipper.read(maxage)
        if switcher_pos == 'unknown' or flipper_pos == 'unknown':
            return 'unknown'
        if switcher_pos == self.switchervalues[0]:
            return 'out'
        # Polarizer is a transmission supermirror => without flipper we get
        # the "down" polarization.
        if flipper_pos == 'on':
            return 'up'
        return 'down'

    def doStart(self, target):
        switch_pos = self._attached_switcher.read(0)
        if target == 'out':
            if switch_pos != self.switchervalues[0]:
                self._attached_switcher.start(self.switchervalues[0])
            self._attached_flipper.start('off')
        else:
            if switch_pos != self.switchervalues[1]:
                self._attached_switcher.start(self.switchervalues[1])
            if target == 'up':
                self._attached_flipper.start('on')
            elif target == 'down':
                self._attached_flipper.start('off')
示例#16
0
 def doInit(self, mode):
     SXTalBase.doInit(self, mode)
     if self.inelastic:
         self.__dict__['en'] = SXTalIndex('en',
                                          unit='meV',
                                          fmtstr='%.3f',
                                          index=3,
                                          lowlevel=True,
                                          sxtal=self)
         self.valuetype = tupleof(float, float, float, float)
示例#17
0
class NPGZFileSink(BaseNPGZFileSink):
    handlerclass = NPGZImageSinkHandler

    parameters = {
        'linknametemplate': Param('Template for the data file name hardlinked'
                                  'to the i-th file configured using'
                                  '(i, [nametemplates]).',
                                  type=tupleof(int, list),
                                  mandatory=True,settable = False,
                                  prefercache = False, ext_desc=TEMPLATE_DESC),
    }
示例#18
0
class CollimationSlit(TwoAxisSlit):
    """Two-axis slit with an additional parameter for the "open" position."""

    parameters = {
        'openpos':   Param('Position to move slit completely open',
                           type=tupleof(float, float), default=(50.0, 50.0)),
    }

    parameter_overrides = {
        'fmtstr':    Override(default='%.1f x %.1f'),
    }
示例#19
0
class VirtualChannel(ActiveChannel):
    """A virtual detector channel."""
    parameters = {
        'curvalue':  Param('Current value', settable=True, unit='main'),
        'curstatus': Param('Current status', type=tupleof(int, str),
                           settable=True, default=(status.OK, 'idle'),
                           no_sim_restore=True),
    }

    _delay = 0.1
    _thread = None

    def doInit(self, mode):
        self._stopflag = False
        if mode == MASTER:
            self.curvalue = 0

    def doStart(self):
        if self._thread and self._thread.is_alive():
            return
        self.curvalue = 0
        self.doResume()

    def doPause(self):
        self.doFinish()
        return True

    def doResume(self):
        self._stopflag = False
        self.curstatus = (status.BUSY, 'counting')
        self._thread = createThread('%s %s' % (self.__class__.__name__, self),
                                    self._counting)

    def doFinish(self):
        if self._thread and self._thread.is_alive():
            self._stopflag = True
            self._thread.join()
        else:
            self.curstatus = (status.OK, 'idle')

    def doStop(self):
        self.doFinish()

    def doStatus(self, maxage=0):
        return self.curstatus

    def doRead(self, maxage=0):
        return self.curvalue

    def doShutdown(self):
        if self._thread:
            self.doStop()
示例#20
0
文件: slits.py 项目: umithardal/nicos
 def doInit(self, mode):
     # Even if the slit could not be become closer then 0 and not more
     # opened the maxheight the instrument scientist want to scan over
     # the limits to find out the 'open' and 'closed' point for the neutrons
     self.valuetype = tupleof(floatrange(-1, self.maxheight + 1), float)
     # generate auto devices
     for name, idx, opmode in [('height', 0, CENTERED),
                               ('center', 1, CENTERED)]:
         self.__dict__[name] = SingleSlitAxis('%s.%s' % (self.name, name),
                                              slit=self,
                                              unit=self.unit,
                                              lowlevel=True,
                                              index=idx,
                                              opmode=opmode)
     self._motors = [self._attached_slit_r, self._attached_slit_s]
示例#21
0
class HePolSink(DataSink):
    """For every scan, records the mon2/mon1 ratio in the transmission device.
    """

    attached_devices = {
        'transmission': Attach('Transmission device', Moveable),
    }

    parameters = {
        'monitors': Param('Names of the two monitor devices to calculate '
                          'the transmission ratio', type=tupleof(str, str),
                          mandatory=True),
    }

    parameter_overrides = {
        'settypes': Override(default={SCAN, SUBSCAN})
    }

    handlerclass = HePolSinkHandler
示例#22
0
class Mailer(Notifier):
    """Sends notifications via e-mail.

    If a Mailer is configured as a notifier (the Mailer device is in the list
    of `notifiers` in the `sysconfig` entry), the receiver addresses (not
    copies) can be set by `.SetMailReceivers`.
    """

    parameters = {
        'mailserver':
        Param('Mail server', type=str, default='localhost', settable=True),
        'sender':
        Param('Mail sender address', type=mailaddress, mandatory=True),
    }

    parameter_overrides = {
        'receivers':
        Override(description='Mail receiver addresses',
                 type=listof(mailaddress)),
        'copies':
        Override(type=listof(tupleof(mailaddress, oneof('all', 'important')))),
    }

    def reset(self):
        self.log.info('mail receivers cleared')
        self.receivers = []

    def send(self, subject, body, what=None, short=None, important=True):
        def send():
            receivers = self._getAllRecipients(important)
            if not receivers:
                return
            ret = sendMail(self.mailserver, receivers, self.sender,
                           self.subject + ' -- ' + subject, body)
            if not ret:  # on error, ret is a list of errors
                self.log.info('%smail sent to %s', what and what + ' ' or '',
                              ', '.join(receivers))
            else:
                self.log.warning('sending mail failed: %s', ', '.join(ret))

        if not self._checkRateLimit():
            return
        createThread('mail sender', send)
示例#23
0
class ROIChannel(PostprocessPassiveChannel):
    """Calculates counts for a rectangular or ellipsoid region of interest."""

    parameters = {
        'roi':
        Param('Rectangular region of interest (x1, y1, x2, y2)',
              type=tupleof(int, int, int, int),
              settable=True,
              category='general'),
        'shape':
        Param('Select the shape of the ROI',
              type=oneof('rectangle', 'ellipse'),
              settable=True,
              category='general'),
    }

    parameter_overrides = {
        'unit': Override(default='cts'),
        'fmtstr': Override(default='%d'),
    }

    def getReadResult(self, arrays, _results, _quality):
        arr = arrays[0]
        if arr is None:
            return [0, 0]
        if any(self.roi):
            x1, y1, x2, y2 = self.roi
            if self.shape == 'rectangle':
                inner = arr[y1:y2, x1:x2].sum()
            else:
                cx = (x1 + x2) / 2.
                cy = (y1 + y2) / 2.
                y, x = numpy.indices(arr.shape)
                ix = ((y - cy) / (y2 - cy))**2 - ((x - cx) / (x2 - cx))**2 <= 1
                inner = arr[ix].sum()
            outer = arr.sum() - inner
            return [inner, outer]
        return [arr.sum(), 0]

    def valueInfo(self):
        return (Value(name=self.name + '.in', type='counter', fmtstr='%d'),
                Value(name=self.name + '.out', type='counter', fmtstr='%d'))
示例#24
0
class SINQAsciiSink(FileSink):
    """
    This is a filesink which writes scan data files in the SINQ ASCII format.
    The implementation strives to be as compatible as possible to the old
    format as written by SICS. SINQ ASCII files are constructed from a
    template file. This template file contains ASCII text which is copied
    verbatim to the output intermixed with place holder strings. These are
    replaced with data from NICOS. In addition, the actual data from the
    scan is collected and written too. The special placeholders recognized
    are:
    - !!VAR(dev,par)!! is replaced with the value of the parameter par of
                        device dev. Par can be missing and is then value.
    - !!DRIV(dev)!! replaced by the value of dev
    - !!ZERO(dev)!! replaced by the offset of dev. Dev must be a motor
    - !!SCRIPT(txt)!! replaced by the output of running script txt
    - !!DATE!!  replaced by the current date and time
    - !!FILE!! replaced by the original file path of the scan file
    - !!SCANZERO!! replaced by a list of zero points of the scanned devices
    There is some redundancy here but as the goal is to reuse the SICS template
    files as far as possible, this has to be accepted.

    One file per scan is written. This format is designed with single
    counters in mind, this is not for area detetcor data. Use the
    NexusFileSink for such data.

    """
    parameters = {
        'templatefile':
        Param('Path to SICS style template file', type=str, mandatory=True),
        'scaninfo':
        Param('Header text and nicos device for each scan point',
              type=listof(tupleof(str, nicosdev)),
              mandatory=True),
    }

    parameter_overrides = {
        'settypes': Override(default=[SCAN, SUBSCAN]),
    }

    handlerclass = SINQAsciiSinkHandler
示例#25
0
文件: flipper.py 项目: ess-dmsc/nicos
class MezeiFlipper(BaseFlipper):
    """
    Class for a Mezei type flipper consisting of flipper and correction current.

    For the state "on" the two power supplies are moved to the values given by
    the `currents` parameter, for "off" they are moved to zero.
    """

    parameters = {
        'currents':
        Param('Flipper and correction current',
              settable=True,
              type=tupleof(float, float)),
    }

    def doStart(self, value):
        if value == ON:
            self._attached_flip.start(self.currents[0])
            self._attached_corr.start(self.currents[1])
        else:
            self._attached_flip.start(0)
            self._attached_corr.start(0)
示例#26
0
class VSForbiddenMoveable(WindowMoveable):
    """
    Velocity selectors have forbidden regions in which they are
    not supposed to run for reason of excessive vibration. This class
    checks for this
    """

    parameters = {
        'forbidden_regions':
        Param('List of forbidden regions',
              type=listof(tupleof(float, float)),
              mandatory=True)
    }

    valuetype = float

    def doIsAllowed(self, value):
        for region in self.forbidden_regions:
            if region[0] < value < region[1]:
                return False, \
                       'Desired value value is within ' \
                       'forbidden region %f to %f' \
                       % (region[0], region[1])
        return True, ''
示例#27
0
class McStasImage(ImageChannelMixin, PassiveChannel):
    """Image channel based on McStas simulation."""

    _mythread = None

    _process = None

    parameters = {
        'size':
        Param(
            'Detector size in pixels (x, y)',
            settable=False,
            type=tupleof(intrange(1, 8192), intrange(1, 8192)),
            default=(1, 1),
        ),
        'mcstasprog':
        Param('Name of the McStas simulation executable',
              type=str,
              settable=False),
        'mcstasdir':
        Param('Directory where McStas stores results',
              type=str,
              default='singlecount',
              settable=False),
        'mcstasfile':
        Param('Name of the McStas data file', type=str, settable=False),
        'mcsiminfo':
        Param('Name for the McStas Siminfo file',
              settable=False,
              type=str,
              default='mccode.sim'),
        'ci':
        Param('Constant ci applied to simulated intensity I',
              settable=False,
              type=floatrange(0.),
              default=1e3)
    }

    def doInit(self, mode):
        self.arraydesc = ArrayDesc(self.name, self.size, '<u4')
        self._workdir = os.getcwd()

    def doReadArray(self, quality):
        self.log.debug('quality: %s', quality)
        if quality == LIVE:
            self._send_signal(SIGUSR2)
        elif quality == FINAL:
            if self._mythread and self._mythread.is_alive():
                self._mythread.join(1.)
                if self._mythread.is_alive():
                    self.log.exception("Couldn't join readout thread.")
                else:
                    self._mythread = None
        self._readpsd(quality == LIVE)
        return self._buf

    def _prepare_params(self):
        """Return a list of key=value strings.

        Each entry defines a parameter setting for the mcstas simulation call.

        examples:
            param=10
        """
        raise NotImplementedError('Please implement _prepare_params method')

    def doPrepare(self):
        self._mcstas_params = ' '.join(self._prepare_params())
        self.log.debug('McStas parameters: %s', self._mcstas_params)
        self._buf = np.zeros(self.size[::-1])
        self.readresult = [0]

    def valueInfo(self):
        return (Value(self.name + '.sum',
                      unit='cts',
                      type='counter',
                      errors='sqrt',
                      fmtstr='%d'), )

    def doStart(self):
        self._mythread = createThread('detector %s' % self, self._run)

    def doStatus(self, maxage=0):
        if self._mythread and self._mythread.is_alive():
            return status.BUSY, 'busy'
        return status.OK, 'idle'

    def doFinish(self):
        self.log.debug('finish')
        self._send_signal(SIGTERM)

    def _send_signal(self, sig):
        if self._process and self._process.is_running():
            self._process.send_signal(sig)
            # wait for mcstas releasing fds
            datafile = path.join(self._workdir, self.mcstasdir,
                                 self.mcstasfile)
            siminfo = path.join(self._workdir, self.mcstasdir, self.mcsiminfo)
            try:
                while self._process and self._process.is_running():
                    fnames = [f.path for f in self._process.open_files()]
                    if siminfo not in fnames and datafile not in fnames:
                        break
                    session.delay(.01)
            except (AccessDenied, NoSuchProcess):
                self.log.debug(
                    'McStas process already terminated in _send_signal(%r)',
                    sig)
            self.log.debug('McStas process has written file on signal (%r)',
                           sig)

    def _run(self):
        """Run McStas simulation executable.

        The current settings of the instrument parameters will be transferred
        to it.
        """
        try:
            shutil.rmtree(self.mcstasdir)
        except (IOError, OSError):
            self.log.info('could not remove old data')
        command = '%s -n 1e8 -d %s %s' % (self.mcstasprog, self.mcstasdir,
                                          self._mcstas_params)
        self.log.debug('run %s', command)
        try:
            self._process = Popen(command.split(),
                                  stdout=PIPE,
                                  stderr=PIPE,
                                  cwd=self._workdir)
            out, err = self._process.communicate()
            if out:
                self.log.debug('McStas output:')
                for line in out.splitlines():
                    self.log.debug('[McStas] %s', line)
            if err:
                self.log.warning('McStas found some problems:')
                for line in err.splitlines():
                    self.log.warning('[McStas] %s', line)
        except OSError as e:
            self.log.error('Execution failed: %s', e)
        self._process.wait()
        self._process = None

    def _readpsd(self, ignore_error=False):
        try:
            with open(
                    path.join(self._workdir, self.mcstasdir, self.mcstasfile),
                    'r') as f:
                lines = f.readlines()[-3 * (self.size[0] + 1):]
            if lines[0].startswith('# Data') and self.mcstasfile in lines[0]:
                self._buf = (
                    np.loadtxt(lines[1:self.size[0] + 1], dtype=np.float32) *
                    self.ci).astype(np.uint32)
                self.readresult = [self._buf.sum()]
            elif not ignore_error:
                raise IOError('Did not find start line: %s' % lines[0])
        except IOError:
            if not ignore_error:
                self.log.exception('Could not read result file')
示例#28
0
class TwoAxisSlit(CanReference, Moveable):
    """A rectangular slit consisting of 2 orthogonal slits.

    All instances have attributes controlling single dimensions that can be used
    as devices, for example in scans.  These attributes are:

    * `width`, `height` -- aliases for the horizontal and vertical slits

    Example usage::

        >>> scan(slit.width, 0, 1, 6)  # scan over slit width from 0 to 5 mm
    """

    attached_devices = {
        'horizontal': Attach('Horizontal slit', HasPrecision),
        'vertical':   Attach('Vertical slit', HasPrecision),
    }

    parameters = {
        'parallel_ref': Param('Set to True if the blades\' reference drive '
                              'can be done in parallel.', type=bool,
                              default=False),
    }

    parameter_overrides = {
        'fmtstr': Override(default='%.2f %.2f'),
        'unit': Override(mandatory=False),
    }

    valuetype = tupleof(float, float)

    hardware_access = False

    def doInit(self, mode):
        self._slits = [self._attached_horizontal, self._attached_vertical]
        self._slitnames = ['horizontal', 'vertical']

        for name in self._slitnames:
            self.__dict__[name] = self._adevs[name]
        self.__dict__['width'] = self.horizontal
        self.__dict__['height'] = self.vertical

    def doIsAllowed(self, target):
        if len(target) != 2:
            raise InvalidValueError(self, 'arguments required for centered '
                                    'mode: [width, height]')
        for slit, slitname, pos in zip(self._slits, self._slitnames, target):
            ok, why = slit.isAllowed(pos)
            if not ok:
                return ok, '[%s slit] %s' % (slitname, why)
        return True, ''

    def doStart(self, target):
        th, tv = target
        self._attached_horizontal.move(th)
        self._attached_vertical.move(tv)

    def doReset(self):
        for ax in self._slits:
            ax.reset()
        for ax in self._slits:
            ax.wait()

    def doReference(self):
        multiReference(self, self._slits, self.parallel_ref)

    def doRead(self, maxage=0):
        return [d.read(maxage) for d in self._slits]

    def valueInfo(self):
        return Value('%s.width' % self, unit=self.unit, fmtstr='%.2f'), \
            Value('%s.height' % self, unit=self.unit, fmtstr='%.2f')

    def doStatus(self, maxage=0):
        return multiStatus(list(zip(self._slitnames, self._slits)))

    def doReadUnit(self):
        return self._attached_horizontal.unit
示例#29
0
class HttpConnector(HasCommunication, Readable):
    """ Device to connect to the HTTP Server using HTTP Basic Authentication.
    *baseurl* provided in the parameters is prepended while connecting to the
    server using GET or POST. Parameter *base64auth* provides a way to
    authenticate the connection.
    """
    parameters = {
        'baseurl':
        Param('Base request URL to be used', type=str, mandatory=True),
        'base64auth':
        Param('HTTP authentication encoded in base64',
              type=str,
              mandatory=True,
              userparam=False),
        'byteorder':
        Param('Endianness of the raw data on server(big/little)',
              type=oneof('big', 'little'),
              default='little'),
        'curstatus':
        Param('Current status of the connection (readonly)',
              type=tupleof(int, str),
              settable=True,
              internal=True)
    }

    parameter_overrides = {
        'unit': Override(mandatory=False, userparam=False, settable=False)
    }

    status_code_msg = {
        400: 'Bad request',
        403: 'Authentication did not work..',
        404: 'Somehow, address was not found!',
        500: 'Internal server error',
    }

    def doInit(self, mode):
        # Check if the base url is available
        self._com_retry(None,
                        requests.get,
                        self.baseurl,
                        headers=self._get_auth())

    def _get_auth(self):
        return {"Authorization": "Basic %s" % self.base64auth}

    def _com_return(self, result, info):
        # Check if the communication was successful
        response = result.status_code
        if response in self.status_code_msg:
            raise CommunicationError(self.status_code_msg.get(response))
        elif response != 200:
            raise CommunicationError('Error while connecting to server!')
        self._setROParam('curstatus', (status.OK, ''))
        return result

    def _com_raise(self, err, info):
        self._setROParam('curstatus', (status.ERROR, 'Communication Error!'))
        HasCommunication._com_raise(self, err, info)

    def _com_warn(self, retries, name, err, info):
        self._com_raise(err, info)

    def doRead(self, maxage=0):
        return ''

    def doStatus(self, maxage=0):
        return self.curstatus

    def get(self, name='', params=()):
        """Connect to *baseurl/name* using the GET protocol
        :param name: String to be appended to the *baseurl*
        :param params: GET parameters to be passed
        :return: (requests.Response) response
        """
        return self._com_retry(None,
                               requests.get,
                               self.baseurl + '/' + name,
                               headers=self._get_auth(),
                               params=params)

    def post(self, name='', data=()):
        """Connect to *baseurl/name* using the POST protocol
        :param name: String to be appended to the *baseurl*
        :param data: POST parameters to be passed
        :return: (requests.Response) response
        """
        return self._com_retry(None,
                               requests.post,
                               self.baseurl + '/' + name,
                               headers=self._get_auth(),
                               data=data)
示例#30
0
class TAS(Instrument, Moveable):
    """An instrument class that can move in (q,w) space.

    When setting up a triple-axis configuration, use this as your instrument
    device (or derive an individual subclass).
    """

    attached_devices = {
        'cell':  Attach('Unit cell object to calculate angles', Cell),
        'mono':  Attach('Monochromator device', Monochromator),
        'ana':   Attach('Analysator device', Monochromator),
        'phi':   Attach('Sample scattering angle', Moveable),
        'psi':   Attach('Sample rocking angle', Moveable),
        'alpha': Attach('Device moved to "alpha" angle between ki and Q',
                        Moveable, optional=True),
    }

    parameters = {
        'scanmode':     Param('Operation mode: one of ' + ', '.join(SCANMODES),
                              type=oneof(*SCANMODES), default='CKI',
                              settable=True, category='instrument'),
        'scanconstant': Param('Constant of the operation mode', type=float,
                              default=0, settable=True, category='instrument'),
        'axiscoupling': Param('Whether the sample th/tt axes are coupled',
                              type=bool, default=True, settable=True,
                              category='instrument'),
        'psi360':       Param('Whether the range of psi is 0-360 deg '
                              '(otherwise -180-180 deg is assumed)',
                              type=bool, default=True, settable=True,
                              category='instrument'),
        'scatteringsense': Param('Scattering sense', default=(1, -1, 1),
                                 type=tupleof(oneof(1, -1),
                                              oneof(1, -1),
                                              oneof(1, -1)), settable=True,
                                 chatty=True, category='instrument'),
        'energytransferunit': Param('Energy transfer unit', type=str,
                                    default='THz', settable=True),
        'collimation':  Param('Collimation settings', type=str,
                              settable=True, category='instrument'),
        'spurioncheck': Param('Whether to check for spurions during simulation',
                              settable=True, type=bool, default=True),
    }

    parameter_overrides = {
        'fmtstr': Override(default='[%6.4f, %6.4f, %6.4f, %6.4f]'),
        'unit':   Override(default='rlu rlu rlu THz', mandatory=False,
                           settable=True)
    }

    valuetype = tupleof(float, float, float, float)

    hardware_access = False

    def doInit(self, mode):
        self.__dict__['h'] = TASIndex('h', unit='rlu', fmtstr='%.3f',
                                      index=0, lowlevel=True, tas=self)
        self.__dict__['k'] = TASIndex('k', unit='rlu', fmtstr='%.3f',
                                      index=1, lowlevel=True, tas=self)
        self.__dict__['l'] = TASIndex('l', unit='rlu', fmtstr='%.3f',
                                      index=2, lowlevel=True, tas=self)
        self.__dict__['E'] = TASIndex('E', unit=self.energytransferunit,
                                      fmtstr='%.3f', index=3, lowlevel=True,
                                      tas=self)
        self._last_calpos = None

        if self.scatteringsense[0] != self._attached_mono.scatteringsense:
            self.log.warning('%s.scatteringsense is not the same as '
                             '%s.scatteringsense[0], please reset %s',
                             self._attached_mono, self, self)
        if self.scatteringsense[2] != self._attached_ana.scatteringsense:
            self.log.warning('%s.scatteringsense is not the same as '
                             '%s.scatteringsense[2], please reset %s',
                             self._attached_ana, self, self)

    def doShutdown(self):
        for name in ['h', 'k', 'l', 'E']:
            if name in self.__dict__:
                self.__dict__[name].shutdown()

    def _getWaiters(self):
        if self.scanmode == 'DIFF':
            res = [self._attached_mono, self._attached_phi, self._attached_psi]
        else:
            res = [self._attached_mono, self._attached_ana,
                   self._attached_phi, self._attached_psi]
        if self._attached_alpha is not None:
            res.append(self._attached_alpha)
        return res

    def _thz(self, ny):
        if self.energytransferunit == 'meV':
            return ny / THZ2MEV
        return ny

    def doIsAllowed(self, pos):
        qh, qk, ql, ny = pos
        ny = self._thz(ny)
        try:
            angles = self._attached_cell.cal_angles(
                [qh, qk, ql], ny, self.scanmode, self.scanconstant,
                self.scatteringsense[1], self.axiscoupling, self.psi360)
        except ComputationError as err:
            return False, str(err)
        # check limits for the individual axes
        for devname, value in zip(['mono', 'ana', 'phi', 'psi', 'alpha'], angles):
            dev = self._adevs[devname]
            if dev is None:
                continue
            if isinstance(dev, Monochromator):
                ok, why = dev.isAllowed(from_k(value, dev.unit))
            else:
                ok, why = dev.isAllowed(value)
            if not ok:
                return ok, 'target position %s outside limits for %s: %s' % \
                    (dev.format(value, unit=True), dev, why)
        return True, ''

    def _sim_getMinMax(self):
        ret = []
        if self._sim_min is not None:
            for i, name in enumerate(['h', 'k', 'l', 'E']):
                ret.append((name, '%.4f' % self._sim_value[i],
                            '%.4f' % self._sim_min[i], '%.4f' % self._sim_max[i]))
        return ret

    def _sim_setValue(self, pos):
        self._sim_old_value = self._sim_value
        self._sim_value = pos
        self._sim_min = tuple(map(min, pos, self._sim_min or pos))
        self._sim_max = tuple(map(max, pos, self._sim_max or pos))

    def doReset(self):
        self.doWriteScatteringsense(self.scatteringsense)

    def doStart(self, pos):
        self.doWriteScatteringsense(self.scatteringsense)
        qh, qk, ql, ny = pos
        ny = self._thz(ny)
        angles = self._attached_cell.cal_angles(
            [qh, qk, ql], ny, self.scanmode, self.scanconstant,
            self.scatteringsense[1], self.axiscoupling, self.psi360)
        mono, ana, phi, psi, alpha = self._attached_mono, self._attached_ana, \
            self._attached_phi, self._attached_psi, self._attached_alpha
        self.log.debug('moving phi/stt to %s', angles[2])
        phi.start(angles[2])
        self.log.debug('moving psi/sth to %s', angles[3])
        psi.start(angles[3])
        if alpha is not None:
            self.log.debug('moving alpha to %s', angles[4])
            alpha.start(angles[4])
        self.log.debug('moving mono to %s', angles[0])
        mono.start(from_k(angles[0], mono.unit))
        if self.scanmode != 'DIFF':
            self.log.debug('moving ana to %s', angles[1])
            ana.start(from_k(angles[1], ana.unit))
        # spurion check
        if self.spurioncheck and self._mode == SIMULATION:
            self._spurionCheck(pos)
        # store the min and max values of h,k,l, and E for simulation
        self._sim_setValue(pos)

    def doFinish(self):
        # make sure index members read the latest value
        for index in (self.h, self.k, self.l, self.E):
            if index._cache:
                index._cache.invalidate(index, 'value')

    def doStatus(self, maxage=0):
        if self.scanmode == 'DIFF':
            return multiStatus(((name, self._adevs[name]) for name in
                                ['mono', 'phi', 'psi', 'alpha']), maxage)
        else:
            return multiStatus(((name, self._adevs[name]) for name in
                                ['mono', 'ana', 'phi', 'psi', 'alpha']), maxage)

    def doWriteScanmode(self, val):
        if val == 'DIFF':
            self.log.warning('Switching to two-axis mode; you are responsible '
                             'for moving the analyzer axes to the desired '
                             'position')

    def doWriteScatteringsense(self, val):
        self._attached_mono.scatteringsense = val[0]
        self._attached_ana.scatteringsense = val[2]

    def doReadUnit(self):
        return 'rlu rlu rlu %s' % self.energytransferunit

    def doWriteEnergytransferunit(self, val):
        if val not in ENERGYTRANSFERUNITS:
            raise InvalidValueError(self,
                                    'invalid energy transfer unit: %r' % val)
        if self._cache:
            self._cache.invalidate(self, 'value')
        self.unit = 'rlu rlu rlu %s' % val
        self.E.unit = val

    def valueInfo(self):
        return Value('h', unit='rlu', fmtstr='%.4f'), \
            Value('k', unit='rlu', fmtstr='%.4f'), \
            Value('l', unit='rlu', fmtstr='%.4f'), \
            Value('E', unit=self.energytransferunit, fmtstr='%.4f')

    def doRead(self, maxage=0):
        mono, ana, phi, psi = self._attached_mono, self._attached_ana, \
                              self._attached_phi, self._attached_psi
        # read out position
        monovalue = to_k(mono.read(maxage), mono.unit)
        if self.scanmode == 'DIFF':
            hkl = self._attached_cell.angle2hkl(
                [monovalue, monovalue, phi.read(maxage), psi.read(maxage)],
                self.axiscoupling)
            ny = 0
        else:
            anavalue = to_k(ana.read(maxage), ana.unit)
            hkl = self._attached_cell.angle2hkl(
                [monovalue, anavalue, phi.read(maxage), psi.read(maxage)],
                self.axiscoupling)
            ny = self._attached_cell.cal_ny(monovalue, anavalue)
            if self.energytransferunit == 'meV':
                ny *= THZ2MEV
        pos = [hkl[0], hkl[1], hkl[2], ny]
        return pos

    def _calpos(self, pos, printout=True, checkonly=True):
        qh, qk, ql, ny, sc, sm = pos
        ny = self._thz(ny)
        if sm is None:
            sm = self.scanmode
        if sc is None:
            sc = self.scanconstant
        try:
            angles = self._attached_cell.cal_angles(
                [qh, qk, ql], ny, sm, sc,
                self.scatteringsense[1], self.axiscoupling, self.psi360)
        except ComputationError as err:
            if checkonly:
                self.log.error('cannot calculate position: %s', err)
                return
            else:
                raise
        if not printout:
            return angles
        ok, why = True, ''
        for devname, value in zip(['mono', 'ana', 'phi', 'psi', 'alpha'], angles):
            dev = self._adevs[devname]
            if dev is None:
                continue
            if isinstance(dev, Monochromator):
                devok, devwhy = dev.isAllowed(from_k(value, dev.unit))
            else:
                devok, devwhy = dev.isAllowed(value)
            if not devok:
                ok = False
                why += 'target position %s outside limits for %s: %s -- ' % \
                    (dev.format(value, unit=True), dev, devwhy)
        self.log.info('ki:            %8.3f A-1', angles[0])
        if self.scanmode != 'DIFF':
            self.log.info('kf:            %8.3f A-1', angles[1])
        self.log.info('2theta sample: %8.3f deg', angles[2])
        self.log.info('theta sample:  %8.3f deg', angles[3])
        if self._attached_alpha is not None:
            self.log.info('alpha:         %8.3f deg', angles[4])
        if ok:
            self._last_calpos = pos
            if checkonly:
                self.log.info('position allowed')
        else:
            if checkonly:
                self.log.warning('position not allowed: %s', why[:-4])
            else:
                raise LimitError(self, 'position not allowed: ' + why[:-4])

    def _reverse_calpos(self, phi, psi, **kwds):
        if 'E' in kwds:
            ny = self._thz(kwds['E'])
            if self.scanmode == 'CKI':
                ki = self.scanconstant
                kf = self._attached_cell.cal_kf(ny, ki)
            elif self.scanmode == 'CKF':
                kf = self.scanconstant
                ki = self._attached_cell.cal_ki1(ny, kf)
            else:
                self.log.error('cannot calculate position with scanmode %s',
                               self.scanmode)
        elif 'ki' in kwds or 'kf' in kwds:
            ki = kwds.get('ki')
            kf = kwds.get('kf')
            if not ki or not kf:
                self.log.error('must give both ki and kf arguments')
        else:
            ki = self._attached_mono.read()
            kf = self._attached_ana.read()
        ny = self._attached_cell.cal_ny(ki, kf)
        if self.energytransferunit == 'meV':
            ny *= THZ2MEV
        hkl = self._calhkl([ki, kf, phi, psi])
        self.log.info('ki: %8.3f A-1', ki)
        self.log.info('kf: %8.3f A-1', kf)
        self.log.info('pos: [%.4f, %.4f, %.4f, %.4f] rlu rlu rlu %s',
                      *(tuple(hkl) + (ny, self.energytransferunit)))

    def _calhkl(self, angles):
        return self._attached_cell.angle2hkl(angles, self.axiscoupling)

    def _getCollimation(self):
        """Return current Soller collimator acceptance angles in minutes of arc.
        Order of the returned list must be alpha1-alpha4 then beta1-beta4.  If
        not installed, use '6000'.

        Must be overridden for instruments with collimation support.
        """
        def to_coll(v):
            if v == 'open':
                return 6000
            return int(v)
        try:
            a1, a2, a3, a4, b1, b2, b3, b4 = map(to_coll, self.collimation.split())
        except Exception:
            try:
                a1, a2, a3, a4 = map(to_coll, self.collimation.split())
            except Exception:
                self.log.warning('collimation parameter should be set to '
                                 '"a1 a2 a3 a4 b1 b2 b3 b4", assuming open')
                return [6000, 6000, 6000, 6000, 6000, 6000, 6000, 6000]
            else:
                return [a1, a2, a3, a4, 6000, 6000, 6000, 6000]
        else:
            return [a1, a2, a3, a4, b1, b2, b3, b4]

    def _getResolutionParameters(self):
        """Return a list of 30 parameters used for resolution calculation."""
        return [
            0,   # circular (0) or rectangular (1) source
            5,   # width of source / diameter (cm)
            5,   # height of source / diameter (cm)
            0,   # no guide (0) or guide (1)
            1,   # horizontal guide divergence (min/AA)
            1,   # vertical guide divergence (min/AA)

            1,   # cylindrical (0) or cuboid (1) sample
            1,   # sample width / diameter perp. to Q (cm)
            1,   # sample width / diameter along Q (cm)
            1,   # sample height (cm)

            1,   # circular (0) or rectangular (1) detector
            2.5, # width / diameter of the detector (cm)
            10,  # height / diameter of the detector (cm)

            0.2, # thickness of monochromator (cm)
            20,  # width of monochromator (cm)
            20,  # height of monochromator (cm)

            0.2, # thickness of analyzer (cm)
            15,  # width of analyzer (cm)
            15,  # height of analyzer (cm)

            200, # distance source - monochromator (cm)
            200, # distance monochromator - sample (cm)
            100, # distance sample - analyzer (cm)
            100, # distance analyzer - detector (cm)

            0,   # horizontal curvature of monochromator (1/cm)
            0,   # vertical curvature of monochromator (1/cm)
            0,   # horizontal curvature of analyzer (1/cm)
            0,   # vertical curvature of analyzer (1/cm)

            100, # distance monochromator - monitor (cm)
            4,   # width of monitor (cm)
            10,  # height of monitor (cm)
        ]

    def _spurionCheck(self, pos):
        for line in spurions.check_acc_bragg(self, *pos):
            self.log.info(line)
        for line in spurions.check_ho_spurions(
                to_k(self._attached_ana.read(), self._attached_ana.unit),
                pos[3] - 0.25, pos[3] + 0.25):
            self.log.info(line)
        kival = to_k(self._attached_mono.read(), self._attached_mono.unit)
        phival = self._attached_phi.read()
        for line in spurions.check_powderrays(kival, spurions.alu_hkl, phival):
            self.log.info(line)
        for line in spurions.check_powderrays(kival, spurions.copper_hkl, phival):
            self.log.info(line)