Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
class ReshapeHistogramImageChannel(HistogramImageChannel):
    """
    An image channel which can reshape the data. This is sometimes necessary.
    """

    parameters = {
        'dimensions': Param(
            'Desired shape of the data',
            type=dictof(str, int),
        ),
    }

    _shape = None
    _HM_dim_desc = None

    def doInit(self, mode):
        self._shape = tuple(self.dimensions.values())
        res = []
        for name, dim in self.dimensions.items():
            res.append(HistogramDimDesc(dim, name, ''))
        self._HM_dim_desc = res

    def doReadArray(self, quality):
        data = HistogramImageChannel.doReadArray(self, quality)
        if len(data) >= numpy.prod(self.shape):
            return data.reshape(self.shape, order='C')
        return data

    @property
    def shape(self):
        return self._shape

    def _dimDesc(self):
        return self._HM_dim_desc
Ejemplo n.º 3
0
class CaressScanfileSink(FileSink):
    """A data sink that writes the CARESS file format."""

    handlerclass = CaressScanfileSinkHandler

    parameters = {
        # Since some devices gives strings back as read values, but the CARESS
        # format does not accept strings they must be mapped to numbers
        'mapping':
        Param('Mapping of (string) device values to float values',
              unit='',
              settable=False,
              type=dictof(str, float),
              default={
                  'on': 1,
                  'off': 0,
                  'open': 1,
                  'closed': 0
              }),
    }

    parameter_overrides = {
        'settypes':
        Override(default=[SCAN, SUBSCAN]),
        # 'settypes': Override(default=[SCAN, SUBSCAN, POINT]),
        'filenametemplate':
        Override(
            default=['m2%(pointcounter)08d.dat', 'm2%(scancounter)08d.dat']),
    }
Ejemplo n.º 4
0
class DigitalValue(Readable):
    attached_devices = {
        'iodev': Attach('IO Device', VSDIO),
    }
    parameters = {
        'channel':
        Param('Channel for readout',
              type=oneof(*VSDIO._HW_DigitalChannels),
              settable=True,
              preinit=True),
        'mapping':
        Param('Mapping of 0/1 to sensible strings',
              type=dictof(str, oneof(0, 1)),
              mandatory=True),
    }

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

    def doInit(self, mode):
        self._revmapping = {v: k for k, v in self.mapping.items()}

    def doRead(self, maxage=0):
        ofs, bit = self._attached_iodev._HW_DigitalChannels[self.channel]
        # ofs is in Bytes, we need it in words! => /2
        if self._attached_iodev._readU16(ofs // 2) & (1 << bit):
            return self._revmapping[1]
        return self._revmapping[0]

    def doStatus(self, maxage=0):
        return status.OK, ''
Ejemplo n.º 5
0
class NamedDigitalOutput(DigitalOutput):
    """
    A DigitalOutput with numeric values mapped to names.
    """

    parameters = {
        'mapping':
        Param('A dictionary mapping state names to integer values',
              type=dictof(str, int),
              mandatory=True),
    }

    def doInit(self, mode):
        self._reverse = {v: k for (k, v) in self.mapping.items()}
        # oneofdict: allows both types of values (string/int), but normalizes
        # them into the string form
        self.valuetype = oneofdict(self._reverse)

    def doStart(self, target):
        value = self.mapping.get(target, target)
        self._dev.value = value

    def doRead(self, maxage=0):
        value = self._dev.value
        return self._reverse.get(value, value)
Ejemplo n.º 6
0
class MantidDevice(Readable):
    parameters = {
        'args':
        Param('Additional arguments to MoveInstrumentComponent.',
              type=dictof(str, anytype)),
        'algorithm':
        Param('Mantid algorithm name.',
              type=str,
              settable=False,
              userparam=False,
              mandatory=True),
    }

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

    valuetype = dictof(str, anytype)
Ejemplo n.º 7
0
class MirrorSample(BaseMirrorSample):

    _misalignments = {}

    parameters = {
        'alignerrors':
        Param(
            'Errors to simulate the sample misalignment',
            type=dictof(str, floatrange(0)),
            settable=False,
            userparam=False,
            default={
                'sample_y': 2,
                'omega': 0.1,
                'detarm': 0.2,
                'chi': 1,
            },
        ),
    }

    def doInit(self, mode):
        if mode == MASTER:
            self._misalign()

    def _applyParams(self, number, parameters):
        BaseMirrorSample._applyParams(self, number, parameters)
        self._misalign()

    def _misalign(self):
        for n, err in self.alignerrors.items():
            self._misalignments[n] = random.uniform(-err, err)
Ejemplo n.º 8
0
class NamedDigitalInput(DigitalInput):
    """
    A DigitalInput with numeric values mapped to names.
    """

    parameters = {
        'mapping':
        Param(
            'A dictionary mapping state names to integers - '
            'if not given, read the mapping from the Tango '
            'device if possible',
            type=dictof(str, int),
            mandatory=False),
    }

    def doInit(self, mode):
        if self.mapping:
            self._reverse = {v: k for (k, v) in self.mapping.items()}
            return
        try:
            self._reverse = parse_mapping(self._getProperty('mapping'))[0]
        except Exception:
            self.log.warning('could not parse value mapping from Tango', exc=1)
            self._reverse = {}

    def doRead(self, maxage=0):
        value = self._dev.value
        return self._reverse.get(value, value)
Ejemplo n.º 9
0
class SelectorSwitcher(KWS1SelectorSwitcher):
    """Switcher whose mapping is determined by a list of presets."""

    parameter_overrides = {
        'presets':
        Override(type=dictof(
            str, dictwith(lam=float, speed=float, spread=float, tilted=bool))),
    }
Ejemplo n.º 10
0
class Authenticator(BaseAuthenticator):
    """Authenticates against the GhOST REST API.

    The resulting user object carries around the GhOST wrapper object as
    metadata, so that further queries can be made with it while the user
    is logged in.
    """

    parameters = {
        'ghosthost':
        Param('Host of the GhOST system to authenticate against',
              type=nonemptystring,
              mandatory=True),
        'instrument':
        Param('Name of the instrument in GhOST', type=str),
        'checkexp':
        Param(
            'If true, check that users are either affiliated '
            'with the current experiment, or registered '
            'local contacts for the instrument',
            type=bool,
            default=True),
        'aliases':
        Param(
            'Map of short user names to GhOST email addresses '
            'and their desired user level',
            type=dictof(nonemptystring, UserLevelAuthEntry)),
    }

    def authenticate(self, username, password):
        if username in self.aliases:
            email, level = self.aliases[username]
        else:
            email, level = username, USER
        ghost = GhostWrapper('https://' + self.ghosthost, self.log)
        try:
            realname = ghost.login(self.instrument,
                                   email,
                                   password,
                                   strict=self.checkexp)
        except ghostapi.errors.GhostAccessError:
            self.log.exception('during authentication')
            raise AuthenticationError('unknown user or wrong password') \
                from None
        except AuthenticationError:
            self.log.exception('during authentication')
            raise
        except Exception as err:
            self.log.exception('during authentication')
            raise AuthenticationError('exception during authenticate(): %s' %
                                      err) from None
        if email == username:
            # try to extract the user's real name from the user data
            username = realname
        return User(username, level, {
            'ghost': ghost,
            'keepalive': ghost.keepalive
        })
Ejemplo n.º 11
0
class DetectorPosSwitcherMixin(DeviceMixinBase):

    parameters = {
        'offsets':  Param('Offsets to correct TOF chopper-detector length '
                          'for the errors in the det_z axis value',
                          type=dictof(float, float),
                          mandatory=True),
        'mapkey':   Param('Last selector position for mapping',
                          type=str, settable=True, internal=True),
    }
Ejemplo n.º 12
0
class AmorNexusUpdater(NexusFileWriterStatus):
    """Updates the nexus files once they are written."""

    parameters = {
        'fileupdates':
        Param('Dict of hdf path mapped to tupleof: '
              '(dev, dtype, unit)',
              type=dictof(str, tuple)),
        'binpaths':
        Param('Paths in file where bin information is stored',
              type=listof(str))
    }

    def _on_close(self, jobid, dataset):
        NexusFileWriterStatus._on_close(self, jobid, dataset)
        filename = dataset.filepaths[0]
        try:
            nxfile = h5py.File(filename, 'r+')
        except (ValueError, OSError):
            self.log.error('Unable to edit file for dataset #%d!',
                           dataset.counter)
            return

        # Remove the last bins
        for path in self.binpaths:
            if path in nxfile:
                arr = numpy.delete(nxfile[path].value, -1)
                axis = nxfile[path].attrs['axis']
                unit = nxfile[path].attrs['units']
                del nxfile[path]
                ds = nxfile.create_dataset(path,
                                           arr.shape,
                                           dtype=str(arr.dtype),
                                           data=arr)
                ds.attrs['axis'] = axis
                ds.attrs['units'] = unit

        for path in self.fileupdates:
            dev, dtype, unit = self.fileupdates[path]
            if dev in dataset.values:
                value = dataset.values[dev]
                shape = numpy.atleast_1d(value).shape
                if path in nxfile:
                    del nxfile[path]
                nxdset = nxfile.create_dataset(path,
                                               shape,
                                               dtype=dtype,
                                               data=value)
                nxdset.attrs['units'] = unit
                nxdset.attrs['nicos_name'] = dev
        nxfile.close()
Ejemplo n.º 13
0
class NamedDigitalInput(DigitalInput):
    """A DigitalInput with numeric values mapped to names."""

    parameters = {
        'mapping':
        Param('A dictionary mapping state names to integers',
              type=dictof(str, int)),
    }

    def doInit(self, mode):
        self._reverse = {v: k for (k, v) in self.mapping.items()}

    def doRead(self, maxage=0):
        value = self._taco_guard(self._dev.read)
        return self._reverse.get(value, value)
Ejemplo n.º 14
0
class Trigger(PyTangoDevice, Moveable):
    """sends a preconfigured string upon move to the configured StringIO

    This should only be used with SCPI devices and the string to be send
    should *not* provoke an answer.
    """
    parameters = {
        'strings':
        Param('mapping of nicos-value to pre-configured string',
              type=dictof(str, str),
              settable=True,
              unit=''),
        'safesetting':
        Param('selection of a \'safe\' setting',
              type=str,
              settable=True,
              unit=''),
    }
    parameter_overrides = {
        'unit': Override(default='', mandatory=False),
    }

    def doInit(self, mode):
        self.valuetype = oneof(*self.strings.keys())
        if self.target not in self.strings:
            self._setROParam('target', self.safesetting)

    def doStart(self, value):
        # !ALWAYS! send selected string
        self._dev.WriteLine(self.strings[value])
        # wait until our buffer is empty
        self._dev.Flush()
        # send a query and wait for the response,
        # which will only come after the above string was fully processed
        # note: this relies on the fact that the selected string above will NOT
        # provoke an answer, but just set parameters (base it on the *LRN? output)
        self._dev.Communicate('SYST:ERROR?')

    def doStatus(self, maxage=0):
        return status.OK, 'indeterminate'

    def read(self, maxage=0):
        # fix bad overwrite from StringIO
        return Moveable.read(self, maxage)

    def doRead(self, maxage=0):
        # no way to read back!
        return self.target
Ejemplo n.º 15
0
class HVSwitcher(Switcher):
    """Override precision to work with lists.

    Also sets all relevant PV values on the eight-packs when starting the
    detector.
    """

    hardware_access = True

    parameters = {
        'pv_values':
        Param('PV values to set when starting the detector',
              type=dictof(str, list),
              mandatory=True),
    }

    def _mapReadValue(self, pos):
        prec = self.precision
        for name, value in iteritems(self.mapping):
            if prec:
                if all(abs(p - v) <= prec for (p, v) in zip(pos, value)):
                    return name
            elif pos == value:
                return name
        return self.fallback

    def doTime(self, old, new):
        if old != new:
            session.clock.tick(90)
        return 0

    def doStart(self, target):
        # on start, also set all configured parameters
        if target == 'on' and self.read() != 'on':
            self.transferSettings()
        Switcher.doStart(self, target)

    @usermethod
    def transferSettings(self):
        import epics
        for epicsid, pvs in iteritems(self.pv_values):
            for pvname, pvvalue in pvs:
                fullpvname = '%s:%s_W' % (epicsid, pvname)
                self.log.debug('setting %s = %s' % (fullpvname, pvvalue))
                epics.caput(fullpvname, pvvalue)
        session.delay(2)
Ejemplo n.º 16
0
class NamedDigitalOutput(DigitalOutput):
    """
    A DigitalOutput with numeric values mapped to names.
    """

    parameters = {
        'mapping':
        Param(
            'A dictionary mapping state names to integers - '
            'if not given, read the mapping from the Tango '
            'device if possible',
            type=dictof(str, int),
            mandatory=False),
    }

    def doInit(self, mode):
        if self.mapping:
            self._reverse = {v: k for (k, v) in self.mapping.items()}
            # oneofdict: allows both types of values (string/int), but
            # normalizes them into the string form
            self.valuetype = oneofdict(self._reverse)
            self._forward = self.mapping
            return
        try:
            self._reverse, self._forward = \
                parse_mapping(self._getProperty('mapping'))
            # we don't build the valuetype from self._reverse since it should
            # only contain the write-able values
            self.valuetype = oneofdict(
                {v: k
                 for (k, v) in self._forward.items()})
        except Exception:
            self.log.warning('could not parse value mapping from Tango', exc=1)
            self._reverse = self._forward = {}

    def doStart(self, target):
        value = self._forward.get(target, target)
        self._dev.value = value

    def doRead(self, maxage=0):
        value = self._dev.value
        return self._reverse.get(value, value)
Ejemplo n.º 17
0
class LokiSample(Sample):
    """Device that collects the various sample properties specific to
    samples at LoKI.
    """

    parameters = {
        'position':
        Param(
            'Mapping of devices to positions for driving to this'
            ' sample\'s position',
            type=dictof(str, anytype),
            settable=True),
        'thickness':
        Param('Sample thickness (info only)',
              type=float,
              settable=True,
              unit='mm',
              category='sample'),
        'comment':
        Param('Sample comment', type=str, settable=True, category='sample'),
    }
Ejemplo n.º 18
0
class FlexRegulator(TemperatureController):
    """Temperature controller with varying setup for software and hardware
    regulation."""

    parameters = {
        'dummy':
        Param('Dummy input device', type=tangodev, mandatory=True),
        'configs':
        Param('Possible setups', type=dictof(str, entry), mandatory=True),
        'config':
        Param('Current configuration', type=str, volatile=True, settable=True),
    }

    def doReadConfig(self):
        props = self._dev.GetProperties()
        indev = props[props.index('indev') + 1]
        outdev = props[props.index('outdev') + 1]
        for (cfgname, config) in iteritems(self.configs):
            if config[0] == outdev:
                if config[1] is None:
                    if indev == self.dummy:
                        return cfgname
                elif indev == config[1][0]:
                    return cfgname
        return '<unknown>'

    def doWriteConfig(self, value):
        cfg = self.configs[value]
        props = ['outdev', cfg[0]]
        if cfg[1]:
            indev, outmin, outmax, initp, initi, initd = cfg[1]
            props += [
                'indev', indev, 'software', 'True', 'outmin',
                str(outmin), 'outmax',
                str(outmax), 'initpid',
                '[%s, %s, %s]' % (initp, initi, initd)
            ]
        else:
            props += ['indev', self.dummy, 'software', 'False']
        self._dev.SetProperties(props)
Ejemplo n.º 19
0
class SelectorSwitcher(MultiSwitcher):
    """Switcher whose mapping is determined by a list of presets."""

    parameters = {
        'presets':
        Param('Presets that determine the mapping',
              type=dictof(str, dictwith(lam=float, speed=float, spread=float)),
              mandatory=True),
    }

    attached_devices = {
        'det_pos': Attach('Detector preset device', DetectorPosSwitcherMixin),
    }

    def doUpdateValue(self, position):
        self._attached_det_pos._updateMapping(position)

    def _getWaiters(self):
        return self._attached_moveables

    def _start_unchecked(self, position):
        MultiSwitcher._start_unchecked(self, position)
        self._attached_det_pos._updateMapping(position)
Ejemplo n.º 20
0
class SingleSlit(PseudoNOK, HasOffset, Moveable):
    """Slit using one axis."""

    hardware_access = False

    attached_devices = {
        'motor': Attach('moving motor', Moveable),
    }

    parameters = {
        'mode':
        Param('Beam mode',
              type=oneof(*MODES),
              settable=True,
              userparam=True,
              default='slit',
              category='general'),
        '_offsets':
        Param('List of offsets per mode position',
              settable=False,
              internal=True,
              type=dictof(str, float),
              default={}),
        'opmode':
        Param('Mode of operation for the slit',
              type=oneof(CENTERED),
              userparam=True,
              settable=True,
              default=CENTERED,
              category='experiment'),
    }

    parameter_overrides = {
        'masks':
        Override(type=dictwith(**{name: float
                                  for name in MODES}),
                 unit='',
                 mandatory=True),
    }

    valuetype = float

    def doWriteOffset(self, value):
        HasOffset.doWriteOffset(self, value)
        # deep copy is need to be able to change the values
        d = self._offsets.copy()
        d[self.mode] = value
        self._setROParam('_offsets', d)

    def doRead(self, maxage=0):
        return self._attached_motor.read(maxage) - self.masks[self.mode] - \
            self.offset

    def doIsAllowed(self, target):
        return self._attached_motor.isAllowed(target + self.masks[self.mode])

    def doStop(self):
        self._attached_motor.stop()

    def doStart(self, target):
        self._attached_motor.start(target + self.masks[self.mode] +
                                   self.offset)

    def doWriteMode(self, mode):
        self._attached_motor.start(
            self._attached_motor.read(0) + self.masks[mode] -
            self.masks[self.mode])
        # update the offset parameter from offset mapping
        self._setROParam('offset', self._offsets.get(mode, 0.))
        self.log.debug('New offset is now: %f', self.offset)
Ejemplo n.º 21
0
class DistancesHandler(BaseSequencer):
    """
    AMOR component handling module. These distances along the optical bench
    are measured with the dimetix laser distance measurement device.
    To this purpose each component has a little mirror attached to it,
    at a different height for each component. The dimetix is sitting
    on a translation that can be moved up and down and thus can measure
    the distance of a given component by selecting the appropriate height.

    The calculation of distance for each component involves following
    properties:

     - Known offset of the attached mirror to the actual component
     - Offset in the calculation of the distance (occurs when laser is not at 0)
     - Value read from the laser

    The actual value is then given with the following equation:

     S = d - S' - ls

    where d is scale offset, S' is the value read by laser and ls is the
    known offset.
    """

    valuetype = list

    parameters = {
        'components':
        Param(
            'Components mapped to tuple of their offsets '
            '(mark_offset, scale_offset)',
            type=dictof(str, tuple),
            userparam=False),
        'fixedcomponents':
        Param('Fixed components mapped to their distances',
              type=dictof(str, float)),
        'rawdistances':
        Param('Calculated distances of components',
              type=dictof(str, float),
              userparam=False,
              settable=True),
        'order':
        Param('Order of componenets for display/mesaurment',
              type=listof(str),
              userparam=False)
    }

    attached_devices = {
        'switch':
        Attach('Switch to turn laser on/off', SpsSwitch),
        'positioner':
        Attach('Positions laser to measure various components', Moveable),
        'dimetix':
        Attach('Measures and returns the distance', Readable)
    }

    def __call__(self, components=None):
        # Print the distances of specified components from
        # Laser, Sample and Chopper
        if not components:
            components = self._components()
        sample = getattr(self, 'sample', None)
        chopper = getattr(self, 'chopper', None)
        table = []
        self.log.info('Horizontal distances of components:')
        for component in components:
            distance = getattr(self, component, None)
            row = [component]
            if isinstance(distance, number_types):
                if isinstance(sample, number_types):
                    row.append(str(sample - distance))
                else:
                    row.append('-')
                if isinstance(chopper, number_types):
                    row.append(str(chopper - distance))
                else:
                    row.append('-')
                table.append(row)
        printTable(['Component', 'From SAMPLE', 'From CHOPPER'],
                   table,
                   self.log.info,
                   rjust=True)

    def doInit(self, mode):
        self._update()

    def _update(self):
        unknown = []
        inactive_loaded = []
        for component in self._components():
            self._update_component(component)
            val = getattr(self, component)
            if val == 'UNKNOWN':
                unknown.append(component)
            elif val == 'NOT ACTIVE' and component in session.loaded_setups:
                inactive_loaded.append(component)

        if unknown:
            self.log.warning('Distances for following components unknown:')
            self.log.warning('** ' + ', '.join(unknown))
            self.log.warning(' ')

        if inactive_loaded:
            self.log.warning('Following components are inactive but loaded in '
                             'setups:')
            self.log.warning('** ' + ', '.join(inactive_loaded))
            self.log.warning('Do one of the following:')
            self.log.warning('Unload these setups OR Run: %s.mesaure() to '
                             'redo distances!' % self.name)

    def doInfo(self):
        ret = []
        for component in self._components():
            ret.append((component, getattr(self, component),
                        str(getattr(self, component)), 'mm', 'general'))
        return ret

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

    def doStatus(self, maxage=0):
        if self._seq_is_running():
            return status.BUSY, 'Measuring'
        return status.OK, ''

    def doIsAtTarget(self, pos):
        return True

    def _update_component(self, component, printValues=False):
        if component in self.fixedcomponents:
            self.__dict__[component] = self.fixedcomponents.get(component)
            if printValues:
                self._logvalues(
                    [component, "",
                     getattr(self, component), "FIXED"])
            return

        if component in self.components:
            raw = self.rawdistances.get(component)
            if raw is None:
                self.__dict__[component] = 'UNKNOWN'
                if printValues:
                    self._logvalues([component, "", "", "UNKNOWN"])
                return

            if raw > 8000:
                self.__dict__[component] = 'NOT ACTIVE'
                if printValues:
                    self._logvalues([component, raw, "", "NOT ACTIVE"])
                return

            offsets = self.components[component]
            if not isinstance(offsets, tuple):
                offsets = tuple(offsets)
            scaleoffset = offsets[1] if len(offsets) > 1 else 0.0
            markoffset = offsets[0]
            self.__dict__[component] = abs(scaleoffset - raw - markoffset)
            if printValues:
                self._logvalues([component, raw, getattr(self, component), ""])

    def _logvalues(self, values, isheader=False):
        if isheader:
            values = ['{0: <13}'.format(val) for val in values]
            printTable(values, [], self.log.info)
        else:
            values = ['{0: >13}'.format(val) for val in values]
            printTable([], [values], self.log.info)

    def _components(self):
        # Return the ordered list of components
        components = self.components.keys() + self.fixedcomponents.keys()

        # Add the missing components in the order dict
        ordered = self.order
        for comp in components:
            if comp not in ordered:
                ordered.append(comp)

        return sorted(components, key=ordered.index)

    def _update_raw_distance(self, component):
        distances = {}
        if self.rawdistances:
            distances.update(self.rawdistances)

        # Get the value from cox
        try:
            cox = session.getDevice('cox')
        except ConfigurationError:
            coxval = 0.0
        else:
            coxval = cox.read(0)

        distances[component] = self._attached_dimetix.read(0) + coxval
        self.rawdistances = distances

    def _generateSequence(self, target):
        if not target:
            target = self._components()

        # Add everything to be done in the seq list
        seq = []

        # If the laser is now on, turn it on
        if self._attached_switch.read(0) != 'ON':
            seq.append(SeqDev(self._attached_switch, 'ON'))
            seq.append(SeqSleep(5))

        seq.append(
            SeqCall(self._logvalues,
                    ['Component', 'Read', 'Final', 'Comments'], True))

        for component in target:
            if component not in self._components():
                comments = 'Skipping! Component not valid..'
                seq.append(
                    SeqCall(self._logvalues, [component, '', '', comments]))
                continue

            if component in self.fixedcomponents:
                comments = 'Skipping! Component fixed..'
                seq.append(
                    SeqCall(self._logvalues, [component, '', '', comments]))
                continue

            if component not in self._attached_positioner.mapping:
                comments = 'Skipping! Height not configured..'
                seq.append(
                    SeqCall(self._logvalues, [component, '', '', comments]))
                continue

            # Move the laser to the component height
            seq.append(SeqDev(self._attached_positioner, component))

            # Sleep for few seconds before reading the value
            seq.append(SeqSleep(3))

            # Read in and change the distance measured by laser
            seq.append(SeqCall(self._update_raw_distance, component))
            seq.append(SeqCall(self._update_component, component, True))

        seq.append(SeqCall(self.log.info, 'Parking and turning off laser..'))
        seq.append(SeqDev(self._attached_positioner, 'park'))
        seq.append(SeqDev(self._attached_switch, 'OFF'))
        seq.append(SeqCall(self.log.info, 'Done! Summary below:'))
        seq.append(SeqCall(self.__call__, target))
        seq.append(SeqCall(self._update))

        return seq

    # User Methods
    @usermethod
    def measure(self, components=None):
        """Routine to calculate the distances of various components in AMOR.
        The laser is moved to the height of a component and the distance is
        then measured.

        NOTE: The following components are allowed:
        analyzer, detector, polarizer, filter, sample, slit1, slit2, slit3,
        slit4, selene

        Example:

        Following command calculates distances for all the components
        >>> CalculateComponentDistances()

        Following command calculates the distances for specified components
        >>> CalculateComponentDistances(['analyzer', 'sample'])
        """
        if components is None:
            components = []
        self.maw(components)
Ejemplo n.º 22
0
class Slit(CanReference, Moveable):
    """A rectangular slit consisting of four blades.

    The slit can operate in four "opmodes", controlled by the `opmode`
    parameter:

    * '4blades' -- all four blades are controlled separately.  Values read from
      the slit are lists in the order ``[left, right, bottom, top]``; for
      ``move()`` the same list of coordinates has to be supplied.
    * '4blades_opposite' -- like '4blades', but left/right and bottom/top have
      opposite coordinate systems, i.e. [5, 5, 5, 5] is an opening of 10x10.
    * 'centered' -- only width and height are controlled; the slit is centered
      at the zero value of the left-right and bottom-top coordinates.  Values
      read and written are in the form ``[width, height]``.
    * 'offcentered' -- the center and width/height are controlled.  Values read
      and written are in the form ``[centerx, centery, width, height]``.

    Normally, the lower level ``right`` and ``left`` as well as the ``bottom``
    and ``top`` devices need to share a common coordinate system, i.e. when
    ``right.read() == left.read()`` the slit is closed.  A different convention
    can be selected when setting `coordinates` to ``"opposite"``: in this case,
    the blades meet at coordinate 0, and both move in positive direction when
    they open.

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

    * `left`, `right`, `bottom`, `top` -- controlling the blades individually,
      independent of the opmode
    * `centerx`, `centery`, `width`, `height` -- controlling "logical"
      coordinates of the slit, independent of the opmode

    Example usage::

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

    attached_devices = {
        'left':   Attach('Left blade', HasPrecision),
        'right':  Attach('Right blade', HasPrecision),
        'bottom': Attach('Bottom blade', HasPrecision),
        'top':    Attach('Top blade', HasPrecision),
    }

    parameters = {
        'opmode': Param('Mode of operation for the slit',
                        type=oneof('4blades', '4blades_opposite',
                                   'centered', 'offcentered'),
                        settable=True),
        'coordinates': Param('Coordinate convention for left/right and '
                             'top/bottom blades', default='equal',
                             type=oneof('equal', 'opposite')),
        'fmtstr_map': Param('A dictionary mapping operation modes to format '
                            'strings (used for internal management).',
                            type=dictof(str, str), settable=False,
                            mandatory=False, userparam=False,
                            default={
                                '4blades': '%.2f %.2f %.2f %.2f',
                                '4blades_opposite': '%.2f %.2f %.2f %.2f',
                                'centered': '%.2f x %.2f',
                                'offcentered': '(%.2f, %.2f) %.2f x %.2f',
                            }),
        'parallel_ref': Param('Set to True if the blades\' reference drive '
                              'can be done in parallel.', type=bool,
                              default=False),
    }

    parameter_overrides = {
        'fmtstr': Override(volatile=True),
        'unit': Override(mandatory=False),
    }

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

    hardware_access = False

    _delay = 0.25  # delay between starting to move opposite blades

    def doInit(self, mode):
        self._axes = [self._attached_left, self._attached_right,
                      self._attached_bottom, self._attached_top]
        self._axnames = ['left', 'right', 'bottom', 'top']

        for name in self._axnames:
            self.__dict__[name] = self._adevs[name]

        for name, cls in [
            ('centerx', CenterXSlitAxis), ('centery', CenterYSlitAxis),
            ('width', WidthSlitAxis), ('height', HeightSlitAxis)
        ]:
            self.__dict__[name] = cls(self.name+'.'+name, slit=self,
                                      unit=self.unit, lowlevel=True)

    def doShutdown(self):
        for name in ['centerx', 'centery', 'width', 'height']:
            if name in self.__dict__:
                self.__dict__[name].shutdown()

    def _getPositions(self, target):
        if self.opmode == '4blades':
            if len(target) != 4:
                raise InvalidValueError(self, 'arguments required for 4-blades '
                                        'mode: [left, right, bottom, top]')
            positions = list(target)
        elif self.opmode == '4blades_opposite':
            if len(target) != 4:
                raise InvalidValueError(self, 'arguments required for 4-blades '
                                        'mode: [left, right, bottom, top]')
            positions = [-target[0], target[1], -target[2], target[3]]
        elif self.opmode == 'centered':
            if len(target) != 2:
                raise InvalidValueError(self, 'arguments required for centered '
                                        'mode: [width, height]')
            positions = [-target[0]/2., target[0]/2.,
                         -target[1]/2., target[1]/2.]
        else:
            if len(target) != 4:
                raise InvalidValueError(self, 'arguments required for '
                                        'offcentered mode: [xcenter, ycenter, '
                                        'width, height]')
            positions = [target[0] - target[2]/2., target[0] + target[2]/2.,
                         target[1] - target[3]/2., target[1] + target[3]/2.]
        return positions

    def doIsAllowed(self, target):
        return self._doIsAllowedPositions(self._getPositions(target))

    def _isAllowedSlitOpening(self, positions):
        ok, why = True, ''
        if positions[1] < positions[0]:
            ok, why = False, 'horizontal slit opening is negative'
        elif positions[3] < positions[2]:
            ok, why = False, 'vertical slit opening is negative'
        return ok, why

    def _doIsAllowedPositions(self, positions):
        f = self.coordinates == 'opposite' and -1 or +1
        for ax, axname, pos in zip(self._axes, self._axnames, positions):
            if axname in ('left', 'bottom'):
                pos *= f
            ok, why = ax.isAllowed(pos)
            if not ok:
                return ok, '[%s blade] %s' % (axname, why)
        return self._isAllowedSlitOpening(positions)

    def doStart(self, target):
        self._doStartPositions(self._getPositions(target))

    def _doStartPositions(self, positions):
        f = self.coordinates == 'opposite' and -1 or +1
        tl, tr, tb, tt = positions
        # determine which axes to move first, so that the blades can
        # not touch when one moves first
        cl, cr, cb, ct = [d.read(0) for d in self._axes]
        cl *= f
        cb *= f
        al, ar, ab, at = self._axes
        if tr < cr and tl < cl:
            # both move to smaller values, need to start right blade first
            al.move(tl * f)
            session.delay(self._delay)
            ar.move(tr)
        elif tr > cr and tl > cl:
            # both move to larger values, need to start left blade first
            ar.move(tr)
            session.delay(self._delay)
            al.move(tl * f)
        else:
            # don't care
            ar.move(tr)
            al.move(tl * f)
        if tb < cb and tt < ct:
            ab.move(tb * f)
            session.delay(self._delay)
            at.move(tt)
        elif tb > cb and tt > ct:
            at.move(tt)
            session.delay(self._delay)
            ab.move(tb * f)
        else:
            at.move(tt)
            ab.move(tb * f)

    def doReset(self):
        multiReset(self._axes)
        multiWait(self._axes)

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

    def _doReadPositions(self, maxage):
        cl, cr, cb, ct = [d.read(maxage) for d in self._axes]
        if self.coordinates == 'opposite':
            cl *= -1
            cb *= -1
        return [cl, cr, cb, ct]

    def doRead(self, maxage=0):
        positions = self._doReadPositions(maxage)
        l, r, b, t = positions
        if self.opmode == 'centered':
            if abs((l+r)/2.) > self._attached_left.precision or \
               abs((t+b)/2.) > self._attached_top.precision:
                self.log.warning('slit seems to be offcentered, but is '
                                 'set to "centered" mode')
            return [r-l, t-b]
        elif self.opmode == 'offcentered':
            return [(l+r)/2, (t+b)/2, r-l, t-b]
        elif self.opmode == '4blades_opposite':
            return [-l, r, -b, t]
        else:
            return positions

    def doPoll(self, n, maxage):
        # also poll sub-AutoDevices we created
        for dev in devIter(self.__dict__, baseclass=AutoDevice):
            dev.poll(n, maxage)

    def valueInfo(self):
        if self.opmode == 'centered':
            return Value('%s.width' % self, unit=self.unit, fmtstr='%.2f'), \
                Value('%s.height' % self, unit=self.unit, fmtstr='%.2f')
        elif self.opmode == 'offcentered':
            return Value('%s.centerx' % self, unit=self.unit, fmtstr='%.2f'), \
                Value('%s.centery' % self, unit=self.unit, fmtstr='%.2f'), \
                Value('%s.width' % self, unit=self.unit, fmtstr='%.2f'), \
                Value('%s.height' % self, unit=self.unit, fmtstr='%.2f')
        else:
            return Value('%s.left' % self, unit=self.unit, fmtstr='%.2f'), \
                Value('%s.right' % self, unit=self.unit, fmtstr='%.2f'), \
                Value('%s.bottom' % self, unit=self.unit, fmtstr='%.2f'), \
                Value('%s.top' % self, unit=self.unit, fmtstr='%.2f')

    def doStatus(self, maxage=0):
        return multiStatus(list(zip(self._axnames, self._axes)))

    def doReadUnit(self):
        return self._attached_left.unit

    def doWriteFmtstr(self, value):
        # since self.fmtstr_map is a readonly dict a temp. copy is created
        # to update the dict and then put to cache back
        tmp = dict(self.fmtstr_map)
        tmp[self.opmode] = value
        self._setROParam('fmtstr_map', tmp)

    def doReadFmtstr(self):
        return self.fmtstr_map[self.opmode]

    def doWriteOpmode(self, value):
        if self._cache:
            self._cache.invalidate(self, 'value')

    def doUpdateOpmode(self, value):
        if value == 'centered':
            self.valuetype = tupleof(float, float)
        else:
            self.valuetype = tupleof(float, float, float, float)
Ejemplo n.º 23
0
class KWSSample(Sample):
    """Device that collects the various sample properties specific to
    samples at KWS.
    """

    parameters = {
        'aperture':
        Param('Aperture (x, y, w, h) for position',
              type=tupleof(float, float, float, float),
              unit='mm',
              settable=True),
        'position':
        Param(
            'Mapping of devices to positions for driving '
            'to this sample\'s position',
            type=dictof(str, anytype),
            settable=True),
        'timefactor':
        Param('Measurement time factor for this sample',
              type=float,
              settable=True,
              category='sample'),
        'thickness':
        Param('Sample thickness (info only)',
              type=float,
              settable=True,
              unit='mm',
              category='sample'),
        'detoffset':
        Param('Detector offset (info only)',
              type=float,
              settable=True,
              unit='mm',
              category='sample'),
        'comment':
        Param('Sample comment', type=str, settable=True, category='sample'),
        'apname':
        Param('Name of sample aperture',
              type=str,
              mandatory=False,
              default='ap_sam'),
    }

    def _applyKwsParams(self, parameters):
        # these keys must be present when dealing with KWS1 samples, but are
        # *not* present in the dummy sample created on experiment start
        self.aperture = parameters.get('aperture', (0, 0, 0, 0))
        self.position = parameters.get('position', {})
        self.timefactor = parameters.get('timefactor', 1.0)
        self.thickness = parameters.get('thickness', 0.0)
        self.detoffset = parameters.get('detoffset', 0.0)
        self.comment = parameters.get('comment', '')

    def clear(self):
        Sample.clear(self)
        self._applyKwsParams({})

    def _applyParams(self, number, parameters):
        Sample._applyParams(self, number, parameters)
        self._applyKwsParams(parameters)

        self.log.info('moving to position of sample %s (%s)...', number,
                      parameters['name'] or 'unnamed')
        waitdevs = []
        if self.aperture != (0, 0, 0, 0):
            ap = session.getDevice(self.apname)
            ap.opmode = 'offcentered'  # to be sure
            ap.move(self.aperture)
            waitdevs.append(ap)
        for devname, devpos in iteritems(self.position):
            dev = session.getDevice(devname)
            dev.move(devpos)
            waitdevs.append(dev)
        multiWait(waitdevs)

    def set(self, number, parameters):
        for key in ('name', 'aperture', 'position', 'timefactor', 'thickness',
                    'detoffset', 'comment'):
            if key not in parameters:
                raise ConfigurationError(
                    self, 'missing key %r in sample entry' % key)
        Sample.set(self, number, parameters)
Ejemplo n.º 24
0
class MiezeMaster(Moveable):

    parameters = {
        'setting':    Param('Current setting', type=int, settable=True),
        'tuning':     Param('Current tuning', type=str, settable=True,
                            category='instrument', default=''),
        'curtable':   Param('Current tuning table', settable=True,
                            type=listof(dictof(str, anytype))),
        'tunetables': Param('Tuning tables for MIEZE settings',
                            type=dictof(str, listof(dictof(str, anytype))),
                            settable=True),
    }

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

    def doRead(self, maxage=0):
        if not self.tuning:
            return ['<no tuning selected>', 0.0]
        return [self.curtable[self.setting]['_name_'],
                self.curtable[self.setting]['_tau_']]

    def valueInfo(self):
        return Value('mieze', fmtstr='%s'), \
            Value('tau', fmtstr='%.2f', unit='ps')

    def _findsetting(self, target):
        if not self.tuning:
            raise NicosError(self, 'no tuning selected, use %s.usetuning(name)'
                             ' to select a tuning table' % self)
        if not isinstance(target, int):
            for idx, setting in enumerate(self.curtable):
                if setting['_name_'] == target:
                    target = idx
                    break
            else:
                raise NicosError(self, 'no such MIEZE setting: %r' % (target,))
        if not 0 <= target < len(self.curtable):
            raise NicosError(self, 'no such MIEZE setting: %r' % target)
        return target

    def doStart(self, target):
        index = self._findsetting(target)
        setting = self.curtable[index]
        devs = sorted(setting.items())
        for devname, devvalue in devs:
            if devname.startswith('amp'):
                self.log.debug('moving %r to %r', devname, 0)
                dev = session.getDevice(devname)
                dev.move(0)
        for devname, devvalue in devs:
            if devname.startswith('_') or devname.startswith('amp'):
                continue
            self.log.debug('moving %r to %r', devname, devvalue)
            dev = session.getDevice(devname)
            dev.move(devvalue)
        for devname, devvalue in devs:
            if not devname.startswith('amp'):
                continue
            self.log.debug('moving %r to %r', devname, devvalue)
            dev = session.getDevice(devname)
            dev.move(devvalue)
        self.setting = index

    def doWriteTuning(self, value):
        if value not in self.tunetables:
            raise NicosError(self, 'no such tuning: %r' % value)
        self.curtable = self.tunetables[value]

    @usermethod
    def usetuning(self, name):
        """Use the given tuning table."""
        self.tuning = name

    @usermethod
    def listtunings(self):
        """List all existing tuning tables."""
        data = []
        for name, table in self.tunetables.items():
            data.append((name,
                         ', '.join(setting['_name_'] for setting in table)))
        self.log.info('all tuning tables:')
        printTable(('name', 'settings'), data, session.log.info)

    @usermethod
    def printtuning(self):
        """Print out the current tuning table."""
        data = []
        valueidx = {}
        all_values = set()
        for setting in self.curtable:
            all_values.update(key for key in setting if not key.startswith('_'))
        all_values = sorted(all_values)
        valueidx = {val: idx for idx, val in enumerate(all_values)}
        for idx, setting in enumerate(self.curtable):
            values = ['---'] * len(all_values)
            for devname, devvalue in setting.items():
                if not devname.startswith('_'):
                    values[valueidx[devname]] = str(devvalue)[:15]
            data.append((str(idx), setting['_name_'], '%.3f' %
                         setting['_tau_']) + tuple(values))
        self.log.info('current MIEZE settings (%s):', self.tuning)
        printTable(('#', 'name', 'tau (ps)') + tuple(all_values), data,
                   session.log.info)

    @usermethod
    def savetuning(self, name):
        """Save the current tuning table."""
        if name in self.tunetables:
            raise NicosError(self, 'tuning with this name exists already, '
                             'please use removetuning() first')
        tables = self.tunetables.copy()
        tables[name] = copy.deepcopy(self.curtable)
        self.tunetables = tables
        self.log.info('current tuning saved as %r', name)

    @usermethod
    def removetuning(self, name):
        """Delete a tuning table."""
        if name not in self.tunetables:
            raise NicosError(self, 'tuning %r not found in tables' % name)
        tables = self.tunetables.copy()
        del tables[name]
        self.tunetables = tables
        self.log.info('tuning %r removed', name)

    @usermethod
    def newsetting(self, name, **values):
        """Add a new setting to the current tuning table."""
        table = self.curtable[:]
        try:
            index = self._findsetting(name)
            setting = table[index].copy()
        except NicosError:
            if not self.tuning:
                raise
            index = len(table)
            table.append({'_name_': name, '_tau_': 0})
            setting = table[index]
        for devname, devvalue in values.items():
            setting[devname] = devvalue
        table[index] = setting
        self.curtable = table
        self.log.info('created new MIEZE setting %r with index %s',
                      name, index)

    @usermethod
    def updatesetting(self, name, **values):
        """Update a setting in the current tuning table."""
        index = self._findsetting(name)
        table = self.curtable[:]
        setting = table[index].copy()
        for devname, devvalue in values.items():
            setting[devname] = devvalue
        table[index] = setting
        self.curtable = table
        self.log.info('tuning for MIEZE setting %r updated',
                      table[index]['_name_'])

    @usermethod
    def removesetting(self, name):
        """Remove a setting from the current tuning table."""
        index = self._findsetting(name)
        table = self.curtable[:]
        setting = table.pop(index)
        self.curtable = table
        self.log.info('removed MIEZE setting %r', setting['_name_'])

    @usermethod
    def ordersettings(self, *names):
        """Reorder the settings in the current tuning table.

        Usage example::

            mieze.ordersettings('46_69', '72_108', '200_300')
        """
        indices = [self._findsetting(n) for n in names]
        other_indices = set(range(len(self.curtable))) - set(indices)
        new_table = [self.curtable[i] for i in chain(indices, other_indices)]
        self.curtable = new_table
Ejemplo n.º 25
0
class Authenticator(BaseAuthenticator):
    """Authenticates against the configured LDAP server.

    Basically it tries to bind on the server with the given userdn.
    Per default, all ldap users are rejected when there is no user level
    definition inside the 'roles' dictionary.
    """

    BIND_METHODS = {
        'none': ldap3.AUTO_BIND_NONE,
        'no_tls': ldap3.AUTO_BIND_NO_TLS,
        'tls_before_bind': ldap3.AUTO_BIND_TLS_BEFORE_BIND,
        'tls_after_bind': ldap3.AUTO_BIND_TLS_AFTER_BIND,
    }

    parameters = {
        'uri':
        Param('LDAP connection URI', type=str, mandatory=True),
        'bindmethod':
        Param('LDAP port', type=oneof(*BIND_METHODS), default='no_tls'),
        'userbasedn':
        Param('Base dn to query users.', type=str, mandatory=True),
        'userfilter':
        Param('Filter for querying users. Must contain '
              '"%(username)s"',
              type=str,
              default='(&(uid=%(username)s)'
              '(objectClass=posixAccount))'),
        'groupbasedn':
        Param('Base dn to query groups', type=str, mandatory=True),
        'groupfilter':
        Param(
            'Filter for querying groups. '
            'Must contain "%(gidnumber)s"',
            type=str,
            default='(&(gidNumber=%(gidnumber)s)'
            '(objectClass=posixGroup))'),
        'usergroupfilter':
        Param(
            'Filter groups of a specific user. '
            'Must contain "%(username)s"',
            type=str,
            default='(&(memberUid=%(username)s)'
            '(objectClass=posixGroup))'),
        'userroles':
        Param('Dictionary of allowed users with their '
              'associated role',
              type=dictof(str, oneof(*ACCESS_LEVELS.values()))),
        'grouproles':
        Param('Dictionary of allowed groups with their '
              'associated role',
              type=dictof(str, oneof(*ACCESS_LEVELS.values()))),
    }

    def doInit(self, mode):
        self._access_levels = {
            value: key
            for key, value in iteritems(ACCESS_LEVELS)
        }

    def authenticate(self, username, password):
        userdn = self._get_user_dn(username)

        # first of all: try a bind to check user existence and password
        try:
            connection = ldap3.Connection(
                self.uri,
                user=userdn,
                password=password,
                auto_bind=self.BIND_METHODS[self.bindmethod])
        except ldap3.core.exceptions.LDAPException as e:
            raise AuthenticationError('LDAP connection failed (%s)' % e)

        userlevel = -1

        # check if the user has explicit rights
        if username in self.userroles:
            userlevel = self._access_levels[self.userroles[username]]
            return User(username, userlevel)

        # if no explicit user right was given, check group rights
        groups = self._get_user_groups(connection, username)

        for group in groups:
            if group in self.grouproles:
                userlevel = max(userlevel,
                                self._access_levels[self.grouproles[group]])

        if userlevel >= 0:
            return User(username, userlevel)

        raise AuthenticationError('Login not permitted for the given user')

    def _get_user_groups(self, connection, username):
        # start with the users default group
        groups = [self._get_default_group(connection, username)]

        # find additional groups of the user
        filter_str = self.usergroupfilter % {'username': username}
        connection.search(self.groupbasedn,
                          filter_str,
                          ldap3.LEVEL,
                          attributes=ldap3.ALL_ATTRIBUTES)

        for group in connection.response:
            groups.append(group['attributes']['cn'][0])

        return groups

    def _get_default_group(self, connection, username):
        filter_str = self.userfilter % {'username': username}

        if not connection.search(self.userbasedn,
                                 filter_str,
                                 ldap3.LEVEL,
                                 attributes=ldap3.ALL_ATTRIBUTES):
            raise AuthenticationError('User not found in LDAP directory')

        return self._get_group_name(
            connection, connection.response[0]['attributes']['gidNumber'])

    def _get_group_name(self, connection, gid):
        filter_str = self.groupfilter % {'gidnumber': gid}
        if not connection.search(self.groupbasedn,
                                 filter_str,
                                 ldap3.LEVEL,
                                 attributes=ldap3.ALL_ATTRIBUTES):
            raise AuthenticationError('Group %s not found' % gid)
        return connection.response[0]['attributes']['cn'][0]

    def _get_user_dn(self, username):
        return 'uid=%s,%s' % (username, self.userbasedn)
Ejemplo n.º 26
0
class DLSDetector(Measurable):
    attached_devices = {
        'cards':    Attach('DLS cards', DLSCard, multiple=True),
        'shutter':  Attach('Shutter to open before measuring', Moveable),
        'limiters': Attach('The filter wheel adjusters', Moveable, multiple=True),
        'lasersel': Attach('The laser wavelength selector', Readable, optional=True),
    }

    parameters = {
        'duration':  Param('Duration of a single DLS measurement.', default=10,
                           unit='s', settable=True),
        'intensity': Param('Intensity to aim for when adjusting filter wheels.',
                           default=100, unit='kHz', settable=True),
        'wavelengthmap': Param('Laser wavelength depending on selector',
                               unit='nm', type=dictof(str, float)),
        # these should be sample properties instead...
        'viscosity': Param('Sample viscosity', unit='cp', settable=True),
        'refrindex': Param('Sample refractive index', settable=True),
    }

    def doInit(self, mode):
        self._adjusting = -1
        self._measuring = False
        self._nfinished = 1
        self._nstarted = 1
        self._ntotal = 1

    def _get_wavelength(self):
        if self._attached_lasersel:
            return self.wavelengthmap.get(
                self._attached_lasersel.read(), -1)
        else:
            return self.wavelengthmap.get('', -1)

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

    def valueInfo(self):
        return ()

    def doReadArrays(self, quality):
        result = []
        for card in self._attached_cards:
            result.append(card.readArray(quality))
            result.append(card.readAbscissa())
            result.append(card.readIntensity())
        return result

    def arrayInfo(self):
        result = []
        for card in self._attached_cards:
            result.append(card.arraydesc)
            result.append(card.abscissa_arraydesc)
            result.append(card.intensity_arraydesc)
        return result

    def doStatus(self, maxage=0):
        return BUSY if self._measuring else OK, \
            '%d of %d done' % (self._nfinished, self._ntotal)

    def doSetPreset(self, **preset):
        if 't' in preset:
            self._ntotal = int(preset['t'] / self.duration) or 1

    def presetInfo(self):
        return ('t',)

    def doStart(self):
        self._attached_shutter.start(1)
        self._adjusting = 0
        self._nfinished = 0
        self._nstarted = 0
        self._measuring = True
        self._attached_limiters[0].start(self.intensity)

    def doFinish(self):
        self.doStop()

    def doStop(self):
        self._measuring = False
        for card in self._attached_cards:
            try:
                card.stop()
            except Exception:
                pass
        self._attached_shutter.start(0)
        for limiter in self._attached_limiters:
            limiter.maw(0)   # close wheels

    def duringMeasureHook(self, elapsed):
        retval = None
        if self._adjusting > -1:
            adj_lim = self._attached_limiters[self._adjusting]
            if adj_lim.status(0)[0] == BUSY:
                return
            self.log.info('reached %s adjustment: %.1f kHz', adj_lim,
                          adj_lim.read(0))
            if self._adjusting == len(self._attached_limiters) - 1:
                self._adjusting = -1
            else:
                self._adjusting += 1
                self._attached_limiters[self._adjusting].start(self.intensity)
                return
        # start new measurements when needed
        if all(c.status(0)[0] != BUSY for c in self._attached_cards):
            if self._nstarted > self._nfinished:
                # read out now, start new measurement next time
                retval = INTERMEDIATE
                self._nfinished += 1
            elif self._nfinished < self._ntotal:
                self.log.info('starting new DLS measurement')
                for card in self._attached_cards:
                    card.setMode()
                    card.preselection = self.duration
                    card.start()
                self._nstarted += 1
            else:
                self._measuring = False
        return retval
Ejemplo n.º 27
0
class NexusFileWriterSink(ProducesKafkaMessages, FileSink):
    """ Sink for writing NeXus files using the FileWriter. The file
    writer accepts commands on a Kafka topic. This command is in the
    form of a JSON object which contains properties such as start
    time, brokers and the nexus structure. The details are provided
    in the github repository of the FileWriter:
    https://github.com/ess-dmsc/kafka-to-nexus

    Topic on which the FileWriter accepts the commands is given by the
    parameter *cmdtopic*. The module containing various nexus templates
    is provided using the parameter *templatesmodule*. A default template
    can be chosen from these nexus templates using the parameter *templatename*

    Rules for the template:
    * Groups should be represented as: <group_name>:<NX_class> in the keys
    * NX_class should be one of the standard NeXus groups
    * Datasets should be represented as: <dataset_name> in the keys
    * Dataset value should be a instance of class NXDataset.
    * Attribute value can either be numerical or instance of class NXattribute
    * Detector event streams are marked using the class EventStream
    * Device values that are to be streamed are marked with DeviceStream

    Example template:
    template = {
        "entry1:NXentry": {
            "INST:NXinstrument": {
                "name": NXDataset("Instrument"),
                "detector:NXdetector": {
                    "data": EventStream(topic="EventTopic", source="SrcName")
                },
            },
            "sample:NXsample": {
                "height": DeviceDataset('dev'),
                "property": DeviceDataset('dev', 'param', unit='K'),
            },
        }
    }
    """
    parameters = {
        'cmdtopic':
        Param('Kafka topic where status commands are written',
              type=str,
              settable=False,
              preinit=True,
              mandatory=True),
        'templatesmodule':
        Param('Python module containing NeXus nexus_templates',
              type=str,
              mandatory=True),
        'templatename':
        Param('Template name from the nexus_templates module',
              type=str,
              mandatory=True),
        'lastsinked':
        Param('Saves the counter, start and end time of sinks',
              type=tupleof(int, float, float, dictof(tuple, tuple)),
              settable=True,
              internal=True),
        'useswmr':
        Param('Use SWMR feature when writing HDF files',
              type=bool,
              settable=False,
              userparam=False,
              default=True),
        'start_fw_file':
        Param(
            'JSON file containing the command for starting '
            'the filewriter (for testing)',
            type=str,
            default=None),
        'title':
        Param('Title to set in NeXus file',
              type=str,
              settable=True,
              userparam=True,
              default=""),
        'cachetopic':
        Param('Kafka topic for cache messages', type=str),
    }

    parameter_overrides = {
        'settypes':
        Override(default=[POINT]),
        'filenametemplate':
        Override(default=['%(proposal)s_%(pointcounter)08d.hdf']),
    }

    attached_devices = {
        'status_provider':
        Attach('Device that provides file writing status',
               NexusFileWriterStatus,
               optional=True),
    }

    handlerclass = NexusFileWriterSinkHandler
    template = None

    def doInit(self, mode):
        self.log.info(self.templatesmodule)
        self._templates = __import__(self.templatesmodule,
                                     fromlist=[self.templatename])
        self.log.info('Finished importing nexus_templates')
        self.set_template(self.templatename)

    def set_template(self, val):
        """
        Sets the template from the given template modules.
        Parses the template using *parserclass* method parse. The parsed
        root, event kafka streams and device placeholders are then set.
        :param val: template name
        """
        if not hasattr(self._templates, val):
            raise NicosError('Template %s not found in module %s' %
                             (val, self.templatesmodule))

        self.template = getattr(self._templates, val)

        if self.templatename != val:
            self._setROParam('templatename', val)

    @property
    def status_provider(self):
        """ Returns the class that provides status of this sink
        :return: Attached NexusFileWriterStatus
        """
        return self._attached_status_provider

    def dataset_started(self, dataset):
        """ Capture that a dataset has started and change the status
        of the dataset
        :param dataset: the dataset that has been started
        """
        if self.status_provider:
            jobid = str(dataset.uid)
            self.status_provider._on_issue(jobid, dataset)

    def dataset_ended(self, dataset, rewriting):
        """ Capture the end of the dataset. Record the counter number,
        start and end time of this dataset in cache using the *lastsinked*
        parameter.
        :param dataset: the dataset that was ended
        :param rewriting: Is this dataset being rewritten
        """
        if self.status_provider:
            jobid = str(dataset.uid)
            self.status_provider._on_stop(jobid, dataset)

        # If rewriting, the dataset was already written in the lastsinked
        if not rewriting:
            self.lastsinked = (dataset.counter, dataset.started,
                               dataset.finished, dataset.metainfo)
Ejemplo n.º 28
0
class SXTalSample(Sample):
    parameters = {
        'cell':
        Param('Unit cell with matrix',
              type=SXTalCellType,
              settable=True,
              mandatory=False,
              default=SXTalCell.fromabc(5)),
        'a':
        Param('a', type=float, category='sample'),
        'b':
        Param('b', type=float, category='sample'),
        'c':
        Param('c', type=float, category='sample'),
        'alpha':
        Param('alpha', type=floatrange(1., 179.), category='sample'),
        'beta':
        Param('beta', type=floatrange(1., 179.), category='sample'),
        'gamma':
        Param('gamma', type=floatrange(1., 179.), category='sample'),
        'rmat':
        Param('rmat',
              type=listof(listof(float)),
              default=None,
              userparam=False),
        'ubmatrix':
        Param('UB matrix (rmat^T)',
              type=listof(listof(float)),
              category='sample'),
        'bravais':
        Param('Bravais lattice',
              type=oneof(*symmetry.Bravais.conditions),
              settable=True,
              default='P',
              category='sample'),
        'laue':
        Param('Laue group',
              type=oneof(*symmetry.symbols),
              settable=True,
              default='1',
              category='sample'),
        'peaklists':
        Param('Lists of peaks for scanning',
              internal=True,
              type=dictof(str, list),
              settable=True),
        'poslists':
        Param('Lists of positions for indexing',
              internal=True,
              type=dictof(str, list),
              settable=True),
    }

    def clear(self):
        """Clear experiment-specific information."""
        Sample.clear(self)
        self.cell = SXTalCell.fromabc(5)
        self.peaklists = {}
        self.poslists = {}

    def new(self, parameters):
        """Accepts several ways to spell new cell params."""
        lattice = parameters.pop('lattice', None)
        if lattice is not None:
            try:
                parameters['a'], parameters['b'], parameters['c'] = lattice
            except Exception:
                self.log.warning('invalid lattice spec ignored, should be '
                                 '[a, b, c]')
        angles = parameters.pop('angles', None)
        if angles is not None:
            try:
                parameters['alpha'], parameters['beta'], \
                    parameters['gamma'] = angles
            except Exception:
                self.log.warning('invalid angles spec ignored, should be '
                                 '[alpha, beta, gamma]')
        a = parameters.pop('a', None)
        if a is None:
            if 'cell' not in parameters:
                self.log.warning('using dummy lattice constant of 5 A')
            a = 5.0
        b = parameters.pop('b', a)
        c = parameters.pop('c', a)
        alpha = parameters.pop('alpha', 90.0)
        beta = parameters.pop('beta', 90.0)
        gamma = parameters.pop('gamma', 90.0)
        # TODO: map spacegroup/bravais/laue with triple-axis
        bravais = parameters.pop('bravais', 'P')
        laue = parameters.pop('laue', '1')
        if 'cell' not in parameters:
            parameters['cell'] = [a, b, c, alpha, beta, gamma, bravais, laue]
        Sample.new(self, parameters)

    def _applyParams(self, number, parameters):
        Sample._applyParams(self, number, parameters)
        if 'cell' in parameters:
            self.cell = parameters['cell']

    def doReadBravais(self):
        return self.cell.bravais.bravais

    def doWriteBravais(self, value):
        self.cell.bravais = symmetry.Bravais(value)

    def doReadLaue(self):
        return self.cell.laue.laue

    def doWriteLaue(self, value):
        self.cell.laue = symmetry.Laue(value)

    def doWriteCell(self, cell):
        params = cell.cellparams()
        self._setROParam('a', params.a)
        self._setROParam('b', params.b)
        self._setROParam('c', params.c)
        self._setROParam('alpha', params.alpha)
        self._setROParam('beta', params.beta)
        self._setROParam('gamma', params.gamma)
        self._setROParam('rmat', cell.rmat.tolist())
        self._setROParam('ubmatrix', cell.rmat.T.tolist())
        self._setROParam('bravais', cell.bravais.bravais)
        self._setROParam('laue', cell.laue.laue)

        self.log.info('New sample cell set. Parameters:')
        self.log.info('a = %8.3f  b = %8.3f  c = %8.3f', params.a, params.b,
                      params.c)
        self.log.info('alpha = %8.3f  beta = %8.3f  gamma = %8.3f',
                      params.alpha, params.beta, params.gamma)
        self.log.info('Bravais: %s  Laue: %s', cell.bravais.bravais,
                      cell.laue.laue)
        self.log.info('UB matrix:')
        for row in cell.rmat.T:
            self.log.info('%8.4f %8.4f %8.4f', *row)
Ejemplo n.º 29
0
class SamplePos(Moveable):
    """Control selector speed via SPS I/O devices."""

    attached_devices = {
        'active_ap': Attach('Alias for active aperture', DeviceAlias),
        'active_x': Attach('Alias for active x translation', DeviceAlias),
        'active_y': Attach('Alias for active y translation', DeviceAlias),
    }

    parameters = {
        'alloweddevs':
        Param('List of allowed devices for presets', type=listof(str)),
        'presets':
        Param('Presets for sample position switching',
              type=dictof(str, dictof(str, anytype))),
    }

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

    def doInit(self, mode):
        self.valuetype = oneof(*sorted(self.presets, key=num_sort))
        self._waitdevs = []
        self._aliases = {}
        self._devpos = {}
        for setting, values in self.presets.items():
            values = dict(values)
            try:
                self._aliases[setting] = (values.pop('active_ap'),
                                          values.pop('active_x'),
                                          values.pop('active_y'))
            except KeyError:
                raise ConfigurationError(
                    self, 'setting %r needs active_ap, active_x and active_y '
                    'settings' % setting) from None
            try:
                for name in self._aliases[setting]:
                    session.getDevice(name)
            except NicosError as exc:
                raise ConfigurationError(
                    self,
                    'could not create/find alias targets for setting %r' %
                    setting) from exc
            for key in values:
                if key not in self.alloweddevs:
                    raise ConfigurationError(
                        self, 'device %s is not allowed '
                        'to be moved by sample_pos' % key)
            self._devpos[setting] = values

    def doRead(self, maxage=0):
        current_targets = (
            self._attached_active_ap.alias,
            self._attached_active_x.alias,
            self._attached_active_y.alias,
        )
        setting = None
        for setting, targets in self._aliases.items():
            if targets == current_targets:
                break
        else:
            return 'unknown'
        ok = True
        for devname, devpos in self._devpos[setting].items():
            dev = session.getDevice(devname)
            devval = dev.read(maxage)
            if isinstance(dev, HasPrecision):
                ok &= abs(devval - devpos) <= dev.precision
            elif isinstance(dev, Slit):
                ok &= all(abs(v - w) <= 0.1 for (v, w) in zip(devval, devpos))
            else:
                ok &= devval == devpos
        if ok:
            return setting
        return 'unknown'

    def doStart(self, target):
        aliases = self._aliases[target]
        self._attached_active_ap.alias = aliases[0]
        self._attached_active_x.alias = aliases[1]
        self._attached_active_y.alias = aliases[2]
        self._waitdevs = []
        for dev, devpos in self._devpos[target].items():
            dev = session.getDevice(dev)
            dev.move(devpos)
            self._waitdevs.append(dev)

    def _getWaiters(self):
        if self._waitdevs:
            return self._waitdevs
        return self._adevs
Ejemplo n.º 30
0
class Resolution(MultiSwitcher):
    """Switcher for the resolution."""

    parameters = {
        'presets': Param('Available presets', type=dictof(anytype, anytype)),
    }