def test_dictof(): assert dictof(int, str)({1: 0, 2: 1}) == {1: '0', 2: '1'} assert dictof(int, str)() == {} assert raises(ValueError, dictof(int, str), ('a', 'b')) assert raises(ValueError, dictof(int, str), {'x': 'y'}) # test that the dict is read-only assert raises(TypeError, dictof(int, str)({1: 'x'}).pop, 1)
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) if DigitalOutput.doRead(self, 0) != value: DigitalOutput.doStart(self, value) def doRead(self, maxage=0): value = DigitalOutput.doRead(self, maxage) return self._reverse.get(value, value)
class FocusRing(Axis): parameters = { 'lenses': Param('Defintion of userlimits for each of the lenses.', type=dictof(str, limits), settable=False, mandatory=True, userparam=False), 'lens': Param('Currently used lens', type=str, userparam=True, settable=True, default=''), } def doPreInit(self): pass def doReference(self): """Do a reference drive. Since the motor has no limit switches the motor has to move to the lowest position. It will stop due to the mechanical movement limitation and this position will be set to 'absmin'. """ if self._hascoder: self.log.error('This is an encoded axis, no need to reference') return motor = self._attached_motor _userlimits = motor.userlimits # store user limits # The use of _setROParam suppresses output to inform users about # changing of the user limits motor._setROParam('userlimits', motor.abslimits) # open limits try: motor.setPosition(motor.absmax) motor.maw(motor.absmin) finally: motor._setROParam('userlimits', _userlimits) # restore user limits def doWriteLens(self, value): if value not in self.lenses: raise ValueError('Lens is not defined. Possible lenses are: %s' % ', '.join(["'%s'" % x for x in list(self.lenses.keys())])) self.userlimits = self.lenses[value]
class MappingCacheForwarder(CacheForwarder): """Forwards cache updates to another cache, while remapping the device part of the key. """ parameters = { 'map': Param('Mapping for devices', type=dictof(str, str), mandatory=True), } def _putChange(self, time, ttl, key, value): if not self._checkKey(key): return dev, slash, sub = key.partition('/') dev = self.map.get(dev, dev) if value is None: msg = '%s@%s%s%s\n' % (time, self._prefix, dev + slash + sub, OP_TELLOLD) else: msg = '%s%s@%s%s%s%s\n' % (time, ttl, self._prefix, dev + slash + sub, OP_TELL, value) self._queue.put(msg)
class HasMapping(DeviceMixinBase): """ Mixin class for devices that use a finite mapping between user supplied input and internal representation. This is mainly useful for devices which can only yield certain values or go to positions from a predefined set, like switching devices. Abstract classes that use this mixin are implemented in `nicos.devices.abstract.MappedReadable` and `.MappedMoveable`. """ parameters = { 'mapping': Param('Mapping of device values to raw (internal) values', unit='', settable=False, mandatory=True, type=dictof(str, anytype)), 'fallback': Param( 'Readback value if the raw device value is not in ' 'the mapping or None to disable', default=None, unit='', type=anytype, settable=False), } # mapped values usually are string constants and have no unit parameter_overrides = { 'unit': Override(mandatory=False), } def doIsAllowed(self, target): if target not in self.mapping: return False, 'unknown value: %r, must be one of %s' % \ (target, ', '.join(map(repr, sorted(self.mapping)))) return True, ''
class Configuration(PyTangoDevice, PassiveChannel): """Channel that allows to configure various parameters of the DECTRIS Pilatus detector. Without this channel you cannot access any parameter of the Pilatus detector except for the exposure time (TimerChannel) out of NICOS. You can attach devices to this channel in order to read out their values and store them in the Pilatus image header via the ``mxsettings`` parameter. """ # TODO: add proper descriptions attached_devices = { 'detector_distance': Attach( 'Current detector distance to sample.', Readable, optional=True, ), 'detector_voffset': Attach( 'Current vertical detector translation.', Readable, optional=True, ), 'flux': Attach( 'Current photon flux in cps.', Readable, optional=True, ), 'filter_transmission': Attach( 'Current transmission filter factor.', Readable, optional=True, ), 'start_angle': Attach( '', Readable, optional=True, ), 'detector_2theta': Attach( 'Current two-theta position.', Readable, optional=True, ), 'polarization': Attach( '', Readable, optional=True, ), 'alpha': Attach( 'Current alpha position.', Readable, optional=True, ), 'kappa': Attach( 'Current kappa position.', Readable, optional=True, ), 'phi': Attach( 'Current phi position.', Readable, optional=True, ), 'chi': Attach( 'Current chi position.', Readable, optional=True, ), 'omega': Attach( 'Current omega position.', Readable, optional=True, ), 'start_position': Attach( '', Readable, optional=True, ), 'shutter_time': Attach( '', Readable, optional=True, ), } parameters = { 'energy': Param( 'X-ray and threshold energy in kilo electron volt. Set to "None" ' 'if the energy is either not set yet not configurable for this ' 'detector.', type=none_or( dictwith(**dict((p, float) for p in ENERGY_PARAMETERS))), settable=True, volatile=True, unit='keV', prefercache=False, chatty=True, ), 'exposures': Param( 'Number of exposures to accumulate per frame/readout.', type=intrange(1, 2**32 - 1), settable=True, volatile=True, userparam=False, unit='', ), 'imageheader': Param( 'String to be included in the image header.', type=str, settable=True, volatile=True, unit='', chatty=True, ), 'images': Param( 'Number of images for an automatic sequence.', type=intrange(1, 2**16 - 1), settable=True, volatile=True, userparam=False, unit='', ), 'mxsettings': Param( 'Crystallographic parameters reported in the image header.', type=dictof(oneof(*MX_PARAMETERS), anytype), settable=True, volatile=True, unit='', prefercache=False, ), 'period': Param( 'Exposure period in seconds (must be longer than exposure time + ' '2.28 ms readout time).', type=floatrange(1.0015, 60 * 24 * 60 * 60), # maximum: 60 days settable=True, volatile=True, userparam=False, unit='s', ), 'sensorvalues': Param( 'Relative humidity and temperature sensor values on all channels.', type=dictof(str, str), unit='', volatile=True, ), } parameter_overrides = { 'lowlevel': Override(default=True), } def _poll_all_channels(self): # update the status of all pilatus detector channels for detector in session.experiment.detectors: if isinstance(detector, Detector): detector.poll() def doRead(self, maxage=0): return [] def valueInfo(self): return () def doStatus(self, maxage=0): return PyTangoDevice.doStatus(self, maxage)[0], '' def doPrepare(self): self.doUpdateMxsettings({}) def doReadEnergy(self): values = self._dev.energy return dict(zip(ENERGY_PARAMETERS, values)) if all(values) else None def _write_energy(self, value): # only send the energy parameters to the hardware if they have changed if self.energy: for param in ENERGY_PARAMETERS: if abs(value[param] - self.energy[param]) > 0.001: self._dev.energy = [value['xray'], value['threshold']] return def doWriteEnergy(self, value): self._write_energy(value) self._poll_all_channels() def doUpdateEnergy(self, value): # only necessary for transmitting setup values to the hardware if self.doStatus()[0] == status.OK: self._write_energy(value) def doReadExposures(self): return self._dev.exposures def doReadImages(self): return self._dev.images def doWriteImages(self, value): self._dev.images = value def doReadImageheader(self): return self._dev.imageHeader def doWriteImageHeader(self, value): self._dev.imageHeader = value def doReadMxsettings(self): mx_settings = self._dev.mxSettings if not mx_settings: return {} # create dict {k1: v1, k2: v2, ...} from list [k1, v1, k2, v2, ...] mx_settings = {mx_settings[2 * i]: mx_settings[2 * i + 1] for i in range(len(mx_settings) // 2)} # convert values to tuple, float or int return {k: MX_PARAMETERS[k](v) for k, v in mx_settings.items()} def doWriteMxsettings(self, value): start_time = time() # energy update must be completed after maximum 15 seconds while time() < start_time + 15: if self.doStatus()[0] == status.OK: break self.log.info('waiting until the detector is ready') session.delay(1.5) else: self.log.error('mxsettings update could not be performed: ' 'pilatus detector is still busy') return # flatten dict {k1: v1, k2: v2, ...} to [k1, v1, k2, v2, ...] self._dev.mxSettings = [str(v) for v in list(sum(value.items(), ()))] def doUpdateMxsettings(self, value): value = dict(value) # convert to writable dict for name, adev in ((k, v) for k, v in self._adevs.items() if v): adev_value = adev.read() if name == 'filter_transmission': adev_value = 1 / adev_value value[name] = str(adev_value) if value: self.doWriteMxsettings(value) def doReadPeriod(self): return self._dev.period def doWritePeriod(self, value): self._dev.period = value def doReadSensorvalues(self): sensor_values = self._dev.sensorValues # create dict {k1: v1, k2: v2, ...} from list [k1, v1, k2, v2, ...] return {sensor_values[2 * i]: sensor_values[2 * i + 1] for i in range(len(sensor_values) // 2)} @usermethod @requires(level=USER) def setEnergy(self, radiation=None, **value): """Either load the predefined settings that are suitable for usage with silver, chromium, copper, iron oder molybdenum radiation or set the x-ray and threshold energy to any other appropriate values. :param str radiation: 'Ag', 'Cr', 'Cu', 'Fe' or 'Mo' (case insensitive) :param dict[str, float] value: {'xray': x, 'threshold': y} """ if not (radiation or value) or radiation and value: raise InvalidValueError('call either dev.SetEnergy("<radiation>") ' 'or dev.SetEnergy(xray=x, threshold=y)') if radiation: self._dev.LoadEnergySettings(radiation) self._poll_all_channels() else: self.energy = value
class HVSwitch(SequencerMixin, MappedMoveable): """High voltage convenience switching device for the CHARM detector.""" valuetype = oneof('on', 'off', 'safe') attached_devices = { 'anodes': Attach('HV channels for the anodes', Moveable, multiple=[2, 9]), 'banodes': Attach('HV channels for the boundary anodes', Moveable, multiple=[1, 8]), 'cathodes': Attach('HV channels for the boundary cathodes', Moveable, multiple=2), 'window': Attach('HV channel for the window', Moveable, multiple=1), 'trip': Attach('Devices signaling a trip on the hardware', Readable), } parameters = { '_tripped': Param('Indicator for hardware trip', type=bool, internal=True, default=False), } parameter_overrides = { 'unit': Override(default='', mandatory=False), 'fallback': Override(default='unknown'), 'mapping': Override(type=dictof(str, dictof(str, float))), } def doInit(self, mode): if self.fallback in self.mapping: raise ConfigurationError( self, 'Value of fallback parameter is ' 'not allowed to be in the mapping!') self._devices = { dev.name: dev for dev in (self._attached_anodes + self._attached_banodes + self._attached_cathodes + self._attached_window) } if len(self._attached_anodes) != len(self._attached_banodes) + 1: raise ConfigurationError( self, 'Number of anode devices must be ' 'the number of boundary anodes + 1: %d, ' '%d' % (len(self.anodes), len(self.banodes))) # if not self.relax_mapping: # self.valuetype = oneof(*sorted(self.mapping, key=num_sort)) for value in sorted(self.mapping, key=num_sort): try: self.valuetype(value) except ValueError as err: raise ConfigurationError( self, '%r not allowed as key in mapping. %s' % (value, err)) from err def doIsAllowed(self, target): if target == 'off': ok = True else: ok = not self._tripped and not self._attached_trip.read(0) return ok, '' if ok else 'hardware is tripped' def doStop(self): if not self._tripped: SequencerMixin.doStop(self) else: raise ModeError(self, "can't be stopped, device is tripped.") def doReset(self): if self._tripped: if self.doStatus(0)[0] == status.BUSY or self._hardware_tripped(): raise ModeError(self, "can't reset device. Hardware is tripped") SequencerMixin.doReset(self) self._setROParam('_tripped', False) def doPoll(self, n, maxage): if not self._tripped and session.sessiontype == POLLER and \ self._hardware_tripped(): self._setROParam('_tripped', True) def _hardware_tripped(self): return self._attached_trip.read(0) == 'Trip' def _generateSequence(self, target): anodes = self._attached_anodes + self._attached_banodes seq = [ [ SeqDev(dev, self.mapping[target][dev.name]) for dev in self._attached_window ], [SeqDev(dev, self.mapping[target][dev.name]) for dev in anodes], [ SeqDev(dev, self.mapping[target][dev.name]) for dev in self._attached_cathodes ], ] return seq def _is_at_target(self, pos, target): # if values are exact the same if pos == target: return True for dev in pos: if not self._devices[dev].isAtTarget(pos[dev], target[dev]): return False return True def _mapReadValue(self, value): for val in self.mapping: if self._is_at_target(value, self.mapping[val]): return val if self.fallback is not None: return self.fallback else: raise PositionError(self, 'unknown unmapped position %r' % value) def _readRaw(self, maxage=0): return {dev.name: dev.read(maxage) for dev in self._devices.values()} def _startRaw(self, target): ramp = 60 * self.mapping[self.target]['ramp'] seq = self._generateSequence(self.target) if self.target in ['off', 'safe']: seq.reverse() self._startSequence([ SeqParam(dev, 'ramp', ramp) for dev in self._attached_anodes + self._attached_banodes + self._attached_cathodes + self._attached_window ] + seq)
class TofChannel(PyTangoDevice, ImageChannelMixin, PassiveChannel): """Basic Tango Device for TofDetector.""" STRSHAPE = ['x', 'y', 'z', 't'] parameters = { 'detshape': Param('Shape of tof detector', type=dictof(str, int)), 'timechannels': Param('Number of time channels - if set to 1 TOF mode ' 'is disabled', type=intrange(1, 1024), settable=True), 'divisor': Param('Width of a time channel', type=int, unit='0.1us', settable=True), 'delay': Param('Offset delay in measure begin', type=int, unit='0.1us', settable=True), 'readchannels': Param('Tuple of (start, end) channel numbers will be ' 'returned by a read', type=tupleof(int, int), default=(0, 0), settable=True, mandatory=True), 'readtimechan': Param('Tuple of (start, end) integrated time channels ' 'will be returned by a read', type=tupleof(int, int), default=(0, 0), settable=True, mandatory=True), } def doInit(self, mode): self.arraydesc = ArrayDesc('data', (self.detshape.get('x', 1), self.detshape.get('t', 1)), numpy.uint32) if mode != SIMULATION: self._dev.set_timeout_millis(10000) def doPrepare(self): self._dev.Clear() PyTangoDevice._hw_wait(self) self.log.debug("Detector cleared") self._dev.Prepare() def doStart(self): start, end = self.readchannels self.readresult = [0] * (end - start + 1) self._dev.Start() self.log.debug("Detector started") def doFinish(self): self._dev.Stop() session.delay(0.2) PyTangoDevice._hw_wait(self) def doStop(self): self._dev.Stop() def doPause(self): self.doFinish() return True def doResume(self): self.doStart() def doReadTimechannels(self): return self._dev.timeChannels def doWriteTimechannels(self, value): self._dev.timeChannels = value self._pollParam('detshape') def doReadDivisor(self): return self._dev.timeInterval def doWriteDivisor(self, value): self._dev.timeInterval = value def doReadDelay(self): return self._dev.delay def doWriteDelay(self, value): self._dev.delay = value def doReadDetshape(self): shvalue = self._dev.detectorSize return {'x': shvalue[0], 't': shvalue[3]} def valueInfo(self): start, end = self.readchannels return tuple(Value("chan-%d" % i, unit="cts", errors="sqrt", type="counter", fmtstr="%d") for i in range(start, end + 1)) def doReadArray(self, quality): self.log.debug("Tof Detector read image") start, end = self.readchannels # get current data array from detector array = numpy.asarray(self._dev.value, numpy.uint32) array = array.reshape(self.detshape['t'], self.detshape['x']) if self.timechannels > 1: startT, endT = self.readtimechan res = array[startT:endT+1].sum(axis=0)[start:end+1] else: res = array[0, start:end+1] self.readresult = res.tolist() return array