class MultiCounter(BaseImageChannel): """Channel for QMesyDAQ that allows to access selected channels in a multi-channel setup. """ parameters = { 'channels': Param('Tuple of active channels (1 based)', settable=True, type=listof(int)), } def doRead(self, maxage=0): if self._mode == SIMULATION: res = [0] * (max(self.channels) + 3) else: # read data via Tango and transform it val = self._dev.value res = val.tolist() if isinstance(val, numpy.ndarray) else val expected = 3 + max(self.channels or [0]) # first 3 values are sizes of dimensions if len(res) >= expected: data = res[3:] # ch is 1 based, data is 0 based total = sum([data[ch - 1] for ch in self.channels]) else: self.log.warning('not enough data returned, check config! ' '(got %d elements, expected >=%d)', len(res), expected) data = None total = 0 resultlist = [total] if data is not None: for ch in self.channels: # ch is 1 based, data is 0 based resultlist.append(data[ch - 1]) return resultlist def valueInfo(self): resultlist = [Value('ch.sum', unit='cts', errors='sqrt', type='counter', fmtstr='%d')] for ch in self.channels: resultlist.append(Value('ch%d' % ch, unit='cts', errors='sqrt', type='counter', fmtstr='%d')) return tuple(resultlist) def doReadIsmaster(self): return False def doReadFmtstr(self): return ', '.join(['sum %d'] + ['ch%d %%d' % ch for ch in self.channels])
class NexusSink(FileSink): """This is a sink for writing NeXus HDF5 files from a template. The template is a dictionary representing the structure of the NeXus file. Special elements in this dictionary are responsible for writing the various NeXus elements. The actual writing work is done in the NexusSinkHandler. This class just initializes the handler properly. """ parameters = { 'templateclass': Param('Python class implementing ' 'NexusTemplateProvider', type=str, mandatory=True), } parameter_overrides = { 'settypes': Override(type=setof(*(POINT,))), } handlerclass = NexusSinkHandler _handlerObj = None def doInit(self, mode): if mode == SLAVE: return # The default implementation creates gazillions of SinkHandlers. # For NeXus we do not want this, we want one keeping track of the whole # process. However, we want a handler object per file. def createHandlers(self, dataset): if self._handlerObj is None: if len(dataset.detectors) == 1: self._handlerObj = self.handlerclass(self, dataset, dataset.detectors[0]) else: self._handlerObj = self.handlerclass(self, dataset, None) else: self._handlerObj.dataset = dataset return [self._handlerObj] def end(self): self._handlerObj = None def loadTemplate(self): mod, cls = self.templateclass.rsplit('.', 1) mod = importlib.import_module(mod) class_ = getattr(mod, cls) return class_().getTemplate()
class FocusPoint(HasLimits, Moveable): attached_devices = { 'table': Attach('table', Moveable), 'pivot': Attach('pivot', Readable), } parameters = { 'gisansdistance': Param('GISANS distance', type=floatrange(0, None), default=10700), } parameter_overrides = { 'abslimits': Override(mandatory=True, volatile=False), } def moveToFocus(self): self._attached_table.move(self._calculation()) def doRead(self, maxage=0): return self._attached_table.read(maxage) def doStart(self, pos): # self.moveToFocus() or move table self._attached_table.move(pos) def _calculation(self, pivot=None): if pivot is None: pivot = self._attached_pivot.read(0) focus = self.gisansdistance - pivot * self._attached_pivot.grid self.log.debug('FocusPoint _calculation focus %f pivot %d', focus, pivot) return focus def doStatus(self, maxage=0): state = self._attached_table.status(maxage) if state[0] != status.OK: return state table = self._attached_table.read() focus = self._calculation() precision = 0 if hasattr(self._attached_table, '_attached_device'): precision = self._attached_table._attached_device.precision elif hasattr(self._attached_table, 'precision'): precision = self._attached_table.precision else: precision = 0 text = 'focus' if abs(table - focus) <= precision else state[1] return status.OK, text
class Collimator(Axis): """Polarization collimator device.""" parameters = { 'divergency': Param('Divergency of the collimator', type=floatrange(0, None), settable=False, userparam=False, default=0.5, unit='deg'), } parameter_overrides = { 'unit': Override(mandatory=False, default='deg'), }
class SkewMotor(Motor): """Device moving by using two axes and having a fixed inclination. Both axis have a fixed inclination given by the ``skew`` parameter. The position of the devices is given for the middle between both axis. The ``motor_1`` device has always the smaller position value than the ``motor_2`` device. pos(motor_1) + skew / 2 == pos == pos(motor_2) - skew / 2. """ attached_devices = { 'motor_1': Attach('moving motor, 1', Moveable), 'motor_2': Attach('moving motor, 2', Moveable), } parameters = { 'skew': Param('Skewness of hardware, difference between both motors', type=floatrange(0.), default=0., settable=True, unit='main'), } def _read_motors(self, maxage=0): return self._attached_motor_1.read(maxage), \ self._attached_motor_2.read(maxage) def doRead(self, maxage=0): return sum(self._read_motors(maxage)) / 2. def doIsAtTarget(self, pos): if self.target is None: return True if not self._attached_motor_1.isAtTarget(pos - self.skew / 2.) or \ not self._attached_motor_2.isAtTarget(pos + self.skew / 2.): return False m1, m2 = self._read_motors() self.log.debug('%.3f, %.3f, %.3f, %.3f', m1, m2, (m1 + self.skew / 2.), (m2 - self.skew / 2.)) return abs(m1 - m2 + self.skew) <= self.precision def doStart(self, target): self._attached_motor_1.move(target - self.skew / 2.) self._attached_motor_2.move(target + self.skew / 2.)
class HexapodSpecial(PyTangoDevice, Device): """Ohe Hexapod Device for Workspace Configuration.""" parameters = { "workspaces": Param("Hexapod workspaces list containing tuples of " "(id, [xn, xp, yn, yp, zn, zp, rzn, rzp, ryn, ryp, " "rxn, rxp, tx, ty, tz, rz, ry, rx])", type=listof(tupleof(int, listof(float))), mandatory=True, settable=False) } def doInit(self, mode): if any(idx < 10 or idx > 19 for idx, _ in self.workspaces): raise ConfigurationError("Workspace ids muste be in 10..19 " "(Jülich workspace range)") if mode != SIMULATION: workspaces = self._dev.workspaces # Tango get workspaces for wsid, ws in self.workspaces: workspaces[wsid] = ws self._dev.workspaces = workspaces # Tango set workspaces
class SkewMotor(HasOffset, SkewRead, Motor): """Device moving by using two axes and having a fixed inclination. Both axis have a fixed inclination given by the ``skew`` parameter. The position of the devices is given for the middle between both axis. The ``one`` device has always the smaller position value than the ``two`` device. pos(one) + skew / 2 == pos == pos(two) - skew / 2. """ parameters = { 'skew': Param('Skewness of hardware, difference between "one" and ' '"two"', type=float, default=0., settable=True, unit='main'), } def _read_devices(self, maxage=0): return self._attached_one.read(maxage), self._attached_two.read(maxage) def doRead(self, maxage=0): return sum(self._read_devices(maxage)) / 2. def doIsAtTarget(self, pos, target): if target is None: return True m1, m2 = self._read_devices() self.log.debug('%.3f, %.3f, %.3f, %.3f', m1, m2, (m1 + self.skew / 2.), (m2 - self.skew / 2.)) if (not self._attached_one.isAtTarget(m1, pos - self.skew / 2.) or not self._attached_two.isAtTarget(m2, pos + self.skew / 2.)): return False return abs(m1 - m2 + self.skew) <= self.precision def doStart(self, target): self._attached_one.move(target - self.skew / 2.) self._attached_two.move(target + self.skew / 2.)
class SelectorLambda(_SelectorLambda): parameters = { 'delta': Param('Maximum deviation between requested rpm and limits', unit='rpm', default=10) } def _adjustValue(self, value): speed = int(self._constant() / value) amin, amax = self._attached_seldev.abslimits if speed > amax and (speed - amax) <= self.delta: value = self._constant() / amax elif speed < amin and (amin - speed) <= self.delta: value = self._constant() / amin return value def doStart(self, value): return _SelectorLambda.doStart(self, self._adjustValue(value)) def doIsAllowed(self, value): return _SelectorLambda.doIsAllowed(self, self._adjustValue(value))
class Shutter(NamedDigitalOutput): """Shutter implements a NamedDigitalOutput which moves to `stoptarget` position when the device is stopped. This can be used to close the shutter in case of (emergency) stops. If the shutter should not move on (emergency) stops please use `NamedDigitalOutput`.""" parameters = { 'stoptarget': Param('Target position on Stop', type=str, default='closed', userparam=False) } def doInit(self, mode): NamedDigitalOutput.doInit(self, mode) # TODO: remove this code after successful migration if self.stoptarget == 'close': self._setROParam('stoptarget', CLOSED) self.log.warning('stoptarget parameter has been changed to %r', CLOSED) def doStop(self): self.maw(self.stoptarget)
class MotorEncoderDifference(Readable): attached_devices = { 'motor': Attach('moving motor', Moveable), 'analog': Attach('analog encoder maybe poti', Readable), } parameters = { 'absolute': Param('Value is absolute or signed.', type=bool, settable=True, default=True), } def doRead(self, maxage=0): dif = self._attached_analog.read(maxage) - \ self._attached_motor.read(maxage) return abs(dif) if self.absolute else dif def doStatus(self, maxage=0): return status.OK, ''
class ImageChannel(QMesyDAQImage, BaseImageChannel): parameters = { 'readout': Param('Readout mode of the Detector', settable=True, type=oneof('raw', 'mapped', 'amplitude'), default='mapped', mandatory=False, chatty=True), } def doWriteListmode(self, value): self._dev.SetProperties(['writelistmode', '%s' % value]) return self._getProperty('writelistmode') def doWriteHistogram(self, value): self._dev.SetProperties('writehistogram', '%s' % value) return self._getProperty('writehistogram') def doWriteReadout(self, value): self._dev.SetProperties(['histogram', '%s' % value]) return self._getProperty('histogram') def doWriteListmodefile(self, value): self._dev.SetProperties(['lastlistfile', '%s' % value]) return self._getProperty('lastlistfile') # def doReadListmodefile(self): # return self._getProperty('lastlistfile') def doWriteHistogramfile(self, value): self._taco_update_resource('lasthistfile', '%s' % value) return self._getProperty('lasthistfile') # def doReadHistogramfile(self): # return self._getProperty('lasthistfile') def doReadConfigfile(self): return self._getProperty('configfile') def doReadCalibrationfile(self): return self._getProperty('calibrationfile')
class BasePos(Readable): valuetype = int attached_devices = { 'comm': Attach('Communication device', StringIO), } parameters = { 'index': Param('index to get one side', type=intrange(1, 2), settable=False, userparam=True), } def doStatus(self, maxage=0): return status.OK, '' def doRead(self, maxage=0): return int( self._attached_comm.communicate('$0%d?*\r\n' % self.index)[1:-1])
class ArmController(IsController, Device): parameters = { 'minangle': Param('Minimum angle between two arms', type=floatrange(0, None), settable=False, userparam=False, default=50.), } attached_devices = { 'arm1': Attach('Arm 1 device', Moveable), 'arm2': Attach('Arm 2 device', Moveable), } parameter_overrides = { 'lowlevel': Override(default=True), } def isAdevTargetAllowed(self, adev, adevtarget): self.log.debug('%s: %s', adev, adevtarget) if adev == self._attached_arm1: target = self._attached_arm2.target if target is None: target = self._attached_arm2.read(0) absdiff = target - adevtarget else: target = self._attached_arm1.target if target is None: target = self._attached_arm1.read(0) absdiff = adevtarget - target if absdiff < 0: return False, 'Arms will cross.' dist = abs(absdiff) if dist >= self.minangle: return True, '' return False, 'Arms become too close to each other: %.3f deg, min. ' \ 'dist is %.3f' % (dist, self.minangle)
class AnalogCalc(Coder): attached_devices = { 'device1': Attach('first value for calculation', Readable), 'device2': Attach('second value for calculation', Readable), } parameters = { 'calc': Param('choose the calculation', type=oneof('mul', 'div', 'add', 'dif'), settable=False, mandatory=True, default='div'), } def doRead(self, maxage=0): """Return a read analogue signal corrected by a polynom. A correcting polynom of at least first order is used. Result is then offset + mul * <previously calculated value> """ value1 = self._attached_device1.read(maxage) value2 = self._attached_device2.read(maxage) self.log.info('value 1: %f 2: %f', value1, value2) if self.calc == 'mul': result = value1 * value2 elif self.calc == 'div': result = value1 / value2 elif self.calc == 'add': result = value1 + value2 elif self.calc == 'dif': result = value1 - value2 self.log.debug('final result: %f', result) return result
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 PolynomFit(DeviceMixinBase): """Fit values to a polynom.""" parameters = { 'poly': Param( 'Polynomial coefficients in ascending order for ' 'potentiometer calibration', type=nonemptylistof(float), settable=False, mandatory=False, default=[0., 1.]), } _fitter = np.polynomial.Polynomial([0., 1.]) # identity conversion def doUpdatePoly(self, poly): self._fitter = np.polynomial.Polynomial(poly) def _fit(self, value): self.log.debug('uncorrected value: %f', value) result = self._fitter(value) self.log.debug('fitted result: %f', result) return result
class DataSink(Device): """The base class for data sinks. Each sink represents one way of processing incoming data. This is a device to be instantiated once per setup so that it can be configured easily through NICOS setups. Actual processing is done by a `DataSinkHandler` class, of which one or more instances are created for each dataset that is processed with this sink. Usually, sinks are specific to a certain type of dataset (e.g. points or scans) and therefore override the `settypes` parameter with a default value that reflects this. .. attribute:: handlerclass This class attribute must be set by subclasses. It selects the subclass of `.DataSinkHandler` that is to be used for handling datasets with this sink. .. attribute:: activeInSimulation This is a class attribute that selects whether this sink can be used in simulation mode. This should only be true for sinks that write no data, such as a "write scan data to the console" sink. .. automethod:: isActive """ parameters = { 'detectors': Param('List of detector names to activate this sink ' '(default is always activated)', type=listof(str)), 'settypes': Param('List of dataset types to activate this sink ' '(default is for all settypes the sink supports)', type=setof(*SETTYPES)), } parameter_overrides = { 'lowlevel': Override(default=True, mandatory=False), } # Set to true in subclasses that are safe for simulation. activeInSimulation = False # Set this to the corresponding Handler class. handlerclass = None def isActive(self, dataset): """Should return True if the sink can and should process this dataset. The default implementation, which should always be called in overrides, checks for simulation mode and for a match with the settypes and the detectors selected by the respective parameters. Derived classes can add additional checks, such as the dataset producing an array that can be written to an image file. """ if session.mode == SIMULATION and not self.activeInSimulation: return False if self.settypes and dataset.settype not in self.settypes: return False if self.detectors and \ not ({d.name for d in dataset.detectors} & set(self.detectors)): return False return True def createHandlers(self, dataset): """Start processing the given dataset (a BaseDataset). Creates the corresponding DataSinkHandler instances to use for this dataset determined via `handlerclass` and returns them. """ if self.handlerclass is None: raise NotImplementedError('Must set an "handlerclass" attribute ' 'on %s' % self.__class__) # pylint: disable=not-callable if dataset.settype == POINT: dets = {d.name for d in dataset.detectors} if self.detectors: dets &= set(self.detectors) return [self.handlerclass(self, dataset, session.getDevice(det)) for det in dets] else: return [self.handlerclass(self, dataset, None)]
class CascadeDetector(VirtualImage): _perfoil = 16 parameters = { 'mode': Param('Data acquisition mode (tof or image)', type=oneof('tof', 'image'), settable=True, default='image', category='presets'), 'roi': Param('Region of interest, given as (x1, y1, x2, y2)', type=tupleof(int, int, int, int), default=(-1, -1, -1, -1), settable=True), 'tofchannels': Param('Total number of TOF channels to use', type=intrange(1, 1024), default=128, settable=True, category='presets'), 'foilsorder': Param('Usable foils, ordered by number.', type=listof(intrange(0, 31)), settable=False, default=[0, 1, 2, 3, 4, 5, 6, 7], category='instrument'), 'fitfoil': Param('Foil for contrast fitting (number BEFORE ' 'resorting)', type=int, default=0, settable=True), } def doInit(self, mode): self._buf = self._generate(0).astype('<u4') def doUpdateMode(self, value): self._dataprefix = (value == 'image') and 'IMAG' or 'DATA' self._datashape = (value == 'image') and self.sizes or \ (self._perfoil * len(self.foilsorder), ) + self.sizes # (self.tofchannels,) self._tres = (value == 'image') and 1 or self.tofchannels def doStart(self): self.readresult = [0, 0] VirtualImage.doStart(self) def valueInfo(self): if self.mode == 'tof': return (Value(self.name + '.roi', unit='cts', type='counter', errors='sqrt', fmtstr='%d'), Value(self.name + '.total', unit='cts', type='counter', errors='sqrt', fmtstr='%d'), Value('fit.contrast', unit='', type='other', errors='next', fmtstr='%.3f'), Value('fit.contrastErr', unit='', type='error', errors='none', fmtstr='%.3f'), Value('fit.avg', unit='', type='other', errors='next', fmtstr='%.1f'), Value('fit.avgErr', unit='', type='error', errors='none', fmtstr='%.1f'), Value('roi.contrast', unit='', type='other', errors='next', fmtstr='%.3f'), Value('roi.contrastErr', unit='', type='error', errors='none', fmtstr='%.3f'), Value('roi.avg', unit='', type='other', errors='next', fmtstr='%.1f'), Value('roi.avgErr', unit='', type='error', errors='none', fmtstr='%.1f')) return (Value(self.name + '.roi', unit='cts', type='counter', errors='sqrt', fmtstr='%d'), Value(self.name + '.total', unit='cts', type='counter', errors='sqrt', fmtstr='%d')) @property def arraydesc(self): if self.mode == 'image': return ArrayDesc('data', self._datashape, '<u4', ['X', 'Y']) return ArrayDesc('data', self._datashape, '<u4', ['T', 'X', 'Y']) def doReadArray(self, quality): # get current data array from detector, reshape properly data = VirtualImage.doReadArray(self, quality) # determine total and roi counts total = data.sum() if self.roi != (-1, -1, -1, -1): x1, y1, x2, y2 = self.roi roi = data[..., y1:y2, x1:x2].sum() else: x1, y1, x2, y2 = 0, 0, data.shape[-1], data.shape[-2] roi = total if self.mode == 'image': self.readresult = [roi, total] return data data = np.array([data] * self._datashape[0]) # demux timing into foil + timing nperfoil = self._datashape[0] // len(self.foilsorder) shaped = data.reshape((len(self.foilsorder), nperfoil) + self._datashape[1:]) # nperfoil = self.tofchannels // self.foils # shaped = data.reshape((self.foils, nperfoil) + self._datashape[1:]) x = np.arange(nperfoil) ty = shaped[self.fitfoil].sum((1, 2)) ry = shaped[self.fitfoil, :, y1:y2, x1:x2].sum((1, 2)) self.log.debug('fitting %r and %r' % (ty, ry)) self.readresult = [ roi, total, ] # also fit per foil data and pack everything together to be send # via cache for display payload = [] for foil in self.foilsorder: foil_tot = shaped[foil].sum((1, 2)) foil_roi = shaped[foil, :, y1:y2, x1:x2].sum((1, 2)) tpopt, tperr, _ = fit_a_sin_fixed_freq(x, foil_tot) rpopt, rperr, _ = fit_a_sin_fixed_freq(x, foil_roi) payload.append([ tpopt, tperr, foil_tot.tolist(), rpopt, rperr, foil_roi.tolist() ]) self._cache.put(self.name, '_foildata', payload, flag=FLAG_NO_STORE) return data
class BiodiffDetector(Detector): attached_devices = { "gammashutter": Attach("Gamma shutter", Moveable), "photoshutter": Attach("Photo shutter", Moveable), } parameters = { "ctrl_gammashutter": Param("Control gamma shutter?", type=bool, settable=True, mandatory=False, default=True), "ctrl_photoshutter": Param("Control photo shutter?", type=bool, settable=True, mandatory=False, default=True), } # guard against multiple prepare calls here: # * `RScan.preparePoint` has been extended to call `prepare` in oder to # prepare detectors for exposure before moving any devices. # * `nicos.core.acquire.acquire` internally calls `prepare` again which # must be ignored. _prepared = False def doPrepare(self): if not self._prepared: # close shutter if self.ctrl_photoshutter: self._attached_photoshutter.maw(CLOSED) if self.ctrl_gammashutter: self._attached_gammashutter.maw(CLOSED) Detector.doPrepare(self) self._prepared = True def doStart(self): # open shutter if self.ctrl_gammashutter: self._attached_gammashutter.maw(OPEN) if self.ctrl_photoshutter: self._attached_photoshutter.maw(OPEN) Detector.doStart(self) self._prepared = False def _check_shutter(self): if (self.ctrl_photoshutter and self._attached_photoshutter.read() == CLOSED): raise InvalidValueError(self, 'photo shutter not open after ' 'exposure, check safety system') if (self.ctrl_gammashutter and self._attached_gammashutter.read() == CLOSED): raise InvalidValueError(self, 'gamma shutter not open after ' 'exposure, check safety system') def _getWaiters(self): adevs = dict(self._adevs) if not self.ctrl_photoshutter: adevs.pop(self._attached_photoshutter.name) if not self.ctrl_gammashutter: adevs.pop(self._attached_gammashutter.name) return adevs def doFinish(self): Detector.doFinish(self) self._check_shutter() # close shutter if self.ctrl_photoshutter: self._attached_photoshutter.maw(CLOSED) if self.ctrl_gammashutter: self._attached_gammashutter.maw(CLOSED) self._prepared = False def doStop(self): Detector.doStop(self) # close shutter if self.ctrl_photoshutter: self._attached_photoshutter.maw(CLOSED) if self.ctrl_gammashutter: self._attached_gammashutter.maw(CLOSED) self._prepared = False
class SingleDetectors(Measurable): attached_devices = { 'pintimer': Attach('Time for pin measurement', Moveable), 'pinstate': Attach('Status for pin measurement', NamedDigitalInput), 'pincontrol': Attach('Status control for pin measurement', DigitalInput), 'pindiodes': Attach('Integrated pindiodes', AnalogInput, multiple=5), } parameters = { 'detector': Param('Detector used for timing', type=str, default='pilatus'), } def doInit(self, mode): self.log.debug('Integral init') self._preset = 0 self._timeout = 3 def presetInfo(self): return ['t'] def doSetPreset(self, **presets): self.log.debug('Integral set preset') if 't' in presets: self._preset = presets['t'] def valueInfo(self): return tuple( Value('%16s' % diode.name, unit='%23s' % 'counts', errors='none', type='counter', fmtstr='%18.10g') for diode in self._attached_pindiodes) def doRead(self, maxage=0): self.log.debug('Integral read') values = [dev.read(0) for dev in self._attached_pindiodes] return values def doPrepare(self): self.log.debug('Pindiode prepare') timeval = time.time() + self._timeout if self.detector not in session.experiment.detlist: self._attached_pintimer.start(0) while self._attached_pincontrol.read(0) != 1: if time.time() > timeval: self.log.warning('Pinstate timeout in prepare') return session.delay(0.02) def doStart(self): self.log.debug('Integral start') timeval = time.time() + self._timeout if self.detector not in session.experiment.detlist: self._attached_pintimer.start(self._preset) while self.status(0)[0] != status.BUSY: if time.time() > timeval: self.log.warning('Pinstate timeout in start') return session.delay(0.02) def doStop(self): self.log.debug('Pintimer stop') self._attached_pintimer.start(0) def doFinish(self): pass def doReset(self): self.log.debug('Pintimer reset') self._attached_pintimer.start(0) def doStatus(self, maxage=0): self.log.debug('Integral status') timeval = time.time() + self._timeout state = self._attached_pinstate.read(0) self._attached_pintimer.poll() if state == 'counting': if time.time() > (timeval + self._preset): return status.ERROR, 'Timeout in PIN diode device.' return status.BUSY, 'The device is in MOVING state.' return status.OK, 'The device is in ON state.'
class SEController(tango.TemperatureController): """Controller to set Temperature """ attached_devices = { 'samplecontroller': Attach('Controller of the sampletemperature', TemperatureController), 'tubecontroller': Attach('Controller of the tubetemperature', TemperatureController) } parameters = { 'tubeoffset': Param('Keep tube this many degrees below the setpoint.', volatile=True, settable=True), 'samplestick': Param('Sample stick currently in use', type=oneof('lt', 'ht'), volatile=True, settable=True), 'devtarget': Param('Target of the underlying tango device', volatile=True) } def rushTemperature(self, temperature): """Move to temperature as fast as possible Due to potential delay when going over 310K this will be handled in the underlying Tango server.""" if session.mode == SIMULATION: return self._dev.RushTemperature(temperature) def _combinedStatus(self, maxage=0): state = tango.TemperatureController.doStatus(self, maxage) # if there is a warning from the controller, display it. if state[0] == WARN: return state else: return tango.TemperatureController._combinedStatus(self, maxage) def stopPressure(self): self._dev.StopPressureRegulation() def doReadTubeoffset(self): return self._dev.tube_offset def doWriteTubeoffset(self, value): self._dev.tube_offset = value def doReadSamplestick(self): return self._dev.sample_stick def doWriteSamplestick(self, value): self._dev.sample_stick = value def doReadDevtarget(self): if not self._dev: return None return self._dev.target def isAtTarget(self, val): ct = currenttime() self._cacheCB('value', val, ct) if self.devtarget is None: return True # check subset of _history which is in window # also check if there is at least one value before window # to know we have enough datapoints hist = self._history[:] window_start = ct - self.window hist_in_window = [v for (t, v) in hist if t >= window_start] stable = all( abs(v - self.devtarget) <= self.precision for v in hist_in_window) if 0 < len(hist_in_window) < len(hist) and stable: # pylint: disable=len-as-condition if hasattr(self, 'doIsAtTarget'): return self.doIsAtTarget(val) return True return False def doIsAtTarget(self, pos): target = self.devtarget if target is None: return True return abs(target - pos) <= self.precision
def test_param_class(): assert str(Param('my parameter')) == '<Param info>' text = Param('my parameter', prefercache=True).formatDoc() assert text == 'Parameter: my parameter\n\n * Type: float\n * ' \ 'Default value: ``0.0``\n * Not settable at runtime\n' \ ' * Prefer value from cache: 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 Detector(GenericDetector): """Generic detector that provides access to the image directory and filename as well as the diskspace attributes of a DECTRIS Pilatus detector. This detector requires exactly one 'other' channel that must be an instance of :class:`Configuration`. """ parameters = { 'diskspace': Param( 'Available space on the Pilatus computer in gibibytes.', type=float, unit='GiB', fmtstr='%.3f', volatile=True, ), 'filename': Param( 'Name of currently being created (during exposures) or last ' 'created image file.', type=str, unit='', volatile=True, ), 'imagedir': Param( 'Current target directory on the Pilatus computer where the next ' 'detector image will be stored at.', type=str, settable=True, unit='', volatile=True, internal=True, ), 'nextfilename': Param( 'Name of the next created image file.', type=str, settable=True, unit='', internal=True, ), } def doPreinit(self, mode): if len(self._attached_others) != 1 or not isinstance( self._attached_others[0], Configuration): raise ConfigurationError( 'pilatus detectors require exactly one "other" channel that ' 'is an instance of {}'.format(Configuration) ) self._cfg_channel = self._attached_others[0]._dev GenericDetector.doPreinit(self, mode) def doPoll(self, _n, _maxage): self._pollParam('filename') self._pollParam('imagedir') def doPrepare(self): GenericDetector.doPrepare(self) self.readArrays(FINAL) # update readresult of image channels def doReadDiskspace(self): return self._cfg_channel.diskSpace def doReadFilename(self): return path.basename(self._cfg_channel.absImagePath) def doReadImagedir(self): return self._cfg_channel.imageDir def doWriteImagedir(self, value): self._cfg_channel.imageDir = value def doReadNextfilename(self): return self._cfg_channel.nextFilename def doWriteNextfilename(self, value): self._cfg_channel.nextFilename = value
class SEController(tango.TemperatureController): """Controller to set Temperature """ attached_devices = { 'samplecontroller': Attach('Controller of the sampletemperature', TemperatureController), 'tubecontroller': Attach('Controller of the tubetemperature', TemperatureController) } parameters = { 'tubeoffset': Param('Keep tube this many degrees below the setpoint.', volatile=True, settable=True), 'samplestick': Param('Sample stick currently in use', type=oneof('lt', 'ht'), volatile=True, settable=True), 'devtarget': Param('Target of the underlying tango device', volatile=True) } def rushTemperature(self, temperature): """Move to temperature as fast as possible Due to potential delay when going over 310K this will be handled in the underlying Tango server.""" if session.mode == SIMULATION: return self._dev.RushTemperature(temperature) def _combinedStatus(self, maxage=0): state = tango.TemperatureController.doStatus(self, maxage) # if there is a warning from the controller, display it. if state[0] == WARN: return state else: return tango.TemperatureController._combinedStatus(self, maxage) def stopPressure(self): self._dev.StopPressureRegulation() def doReadTubeoffset(self): return self._dev.tube_offset def doWriteTubeoffset(self, value): self._dev.tube_offset = value def doReadSamplestick(self): return self._dev.sample_stick def doWriteSamplestick(self, value): self._dev.sample_stick = value def doReadDevtarget(self): if not self._dev: return None return self._dev.target def isAtTarget(self, pos=None, target=None): if target is None: if self.devtarget is None: return True target = self.devtarget return TemperatureController.isAtTarget(self, target=target)
class Flux(VectorInput): """Device which stores the flux averages over the relevant detectors. """ parameters = { 'fluxvalues': Param('Raw flux values', internal=True, type=listof(listof(int))) } def init(self): VectorInput.init(self) self._fluxvalues = [[], [], []] def doPoll(self, i, maxage): val = self.doRead() self._pollParam('fluxvalues') return self.status(), val def doReadFluxvalues(self): if not hasattr(self, '_fluxvalues'): self.doRead() return self._fluxvalues def doRead(self, maxage=0): flux = self._dev.GetFlux() if len(flux) != 16 * 6: self.log.warning('SIS returned %d flux values, expected %d', len(flux), 16 * 6) return [0, 0, 0] # pylint: disable=unbalanced-tuple-unpacking cElast, cInelast, cDir, tElast, tInelast, tDir = self.split(flux, 16) rDets = self._dev.GetRegularDetectors() self._fluxvalues = [cElast, cInelast, cDir] elast = [ sum([x for i, x in enumerate(cElast) if i in rDets]), sum([x for i, x in enumerate(tElast) if i in rDets]) ] inelast = [ sum([x for i, x in enumerate(cInelast) if i in rDets]), sum([x for i, x in enumerate(tInelast) if i in rDets]) ] direct = [ sum([x for i, x in enumerate(cDir) if i in rDets]), sum([x for i, x in enumerate(tDir) if i in rDets]) ] if not elast[1]: elastic = 0 else: elastic = int(elast[0] / 2e-5 / elast[1]) if not inelast[1]: inelastic = 0 else: inelastic = int(inelast[0] / 2e-5 / inelast[1]) if not direct[1]: direct = 0 else: direct = int(direct[0] / 2e-5 / direct[1]) return [elastic, inelastic, direct] def split(self, inp, chunksize): output = [] index = 0 listsize = len(inp) while index < listsize: output.append([int(x) for x in inp[index:index + chunksize]]) index += chunksize return output
class ImagePlateImage(ImageChannelMixin, PassiveChannel): """Represents the client to a MAATEL Image Plate Detector.""" MAP_SHAPE = { 125: (900, 10000), 250: (900, 5000), 500: (900, 2500), } attached_devices = { "imgdrum": Attach("Image Plate Detector Drum " "control device.", ImagePlateDrum), } parameters = { "erase": Param("Erase image plate on next start?", type=bool, settable=True, mandatory=False, default=True), "roi": Param("Region of interest", type=tupleof(int, int, int, int), default=(0, 0, 0, 0), settable=True, volatile=True, category="general"), "pixelsize": Param("Pixel size in microns", type=oneof(125, 250, 500), default=500, settable=True, volatile=True, category="general"), "file": Param("Image file location on maatel computer", type=str, settable=True, volatile=True, category="general"), "readout_millis": Param("Timeout in ms for the readout", type=int, settable=True, default=60000), } def doInit(self, mode): self.arraydesc = ArrayDesc('data', self.MAP_SHAPE[self.pixelsize], numpy.uint16) def doPrepare(self): # erase and expo position if self.erase: self._attached_imgdrum.maw(ImagePlateDrum.POS_ERASE) self._attached_imgdrum.maw(ImagePlateDrum.POS_EXPO) def valueInfo(self): # no readresult -> no values return () def doReadArray(self, quality): if quality == FINAL: # start readout self._attached_imgdrum.maw(ImagePlateDrum.POS_READ) narray = None timeout = self._attached_imgdrum._dev.get_timeout_millis() self._attached_imgdrum._dev.set_timeout_millis(self.readout_millis) try: narray = self._attached_imgdrum._dev.Bitmap16Bit finally: self._attached_imgdrum._dev.set_timeout_millis(timeout) return narray return None def doReadRoi(self): return (0, self._attached_imgdrum._dev.InterestZoneY, 1250, self._attached_imgdrum._dev.InterestZoneH) def doReadPixelsize(self): return self._attached_imgdrum._dev.PixelSize def doReadFile(self): return self._attached_imgdrum._dev.ImageFile def doWriteRoi(self, value): self.log.warning("setting x offset and width are not supported " "- ignored.") self._attached_imgdrum._dev.InterestZoneY = value[1] self._attached_imgdrum._dev.InterestZoneH = value[3] def doWritePixelsize(self, value): self._attached_imgdrum._dev.PixelSize = value self.arraydesc = ArrayDesc('data', self.MAP_SHAPE[value], numpy.uint16) def doWriteFile(self, value): self._attached_imgdrum._dev.ImageFile = value
class CalibratedMagnet(HasLimits, Moveable): """Base clase for magnet supplies having an bipolar current source. Use this for magnets which can be calibrated, i.e. where:: B(I) = Ic0 + c1*erf(c2*I) + c3*atan(c4*I) works reasonably well. Coefficients c0..c4 are given as 'calibration' parameter. """ attached_devices = { 'currentsource': Attach('bipolar Powersupply', Moveable), } parameters = { 'ramp': Param('Target rate of field change per minute', unit='main/min', mandatory=False, settable=True, volatile=True), 'calibration': Param( 'Coefficients for calibration ' 'function: [c0, c1, c2, c3, c4] calculates ' 'B(I) = c0*I + c1*erf(c2*I) + c3*atan(c4*I)' ' in T', type=tupleof(float, float, float, float, float), default=(1.0, 0.0, 0.0, 0.0, 0.0), settable=True, chatty=True), } parameter_overrides = { 'unit': Override(mandatory=False, default='T'), 'abslimits': Override(mandatory=False, volatile=True), } def _current2field(self, current, *coefficients): """Return field in T for given current in A. Should be monotonic and asymetric or _field2current will fail! Note: This may be overridden in derived classes. """ v = coefficients or self.calibration if len(v) != 5: self.log.warning('Wrong number of coefficients in calibration ' 'data! Need exactly 5 coefficients!') return v[0]*current + v[1]*math.erf(v[2]*current) + \ v[3]*math.atan(v[4]*current) def _field2current(self, field): """Return required current in A for requested field in T. Note: This may be overridden in derived classes. """ # binary search/bisection maxcurr = self._attached_currentsource.abslimits[1] mincurr = -maxcurr maxfield = self._current2field(maxcurr) minfield = self._current2field(mincurr) if not minfield <= field <= maxfield: raise LimitError( self, 'requested field %g %s out of range %g..%g %s' % (field, self.unit, minfield, maxfield, self.unit)) res = fsolve(lambda cur: self._current2field(cur) - field, 0)[0] self.log.debug('current for %g %s is %g', field, self.unit, res) return res def doRead(self, maxage=0): return self._current2field(self._attached_currentsource.read(maxage)) def doStart(self, target): self._attached_currentsource.start(self._field2current(target)) def doStop(self): self._attached_currentsource.stop() def doReset(self): return self._attached_currentsource.reset() def doReadRamp(self): # This is an approximation! return self.calibration[0] * abs(self._attached_currentsource.ramp) def doWriteRamp(self, newramp): # This is an approximation! self._attached_currentsource.ramp = newramp / self.calibration[0] def doReadAbslimits(self): minfield, maxfield = [ self._current2field(I) for I in self._attached_currentsource.abslimits ] # include 0 in allowed range if minfield > 0: minfield = 0 if maxfield < 0: maxfield = 0 # get configured limits if any, or take max from source limits = self._config.get('abslimits', (minfield, maxfield)) # in any way, clamp limits to what the source can handle limits = [clamp(i, minfield, maxfield) for i in limits] return min(limits), max(limits) def doWriteUserlimits(self, limits): HasLimits.doWriteUserlimits(self, limits) # all Ok, set source to max of pos/neg field current maxcurr = max(self._field2current(i) for i in limits) mincurr = min(self._field2current(i) for i in limits) self._attached_currentsource.userlimits = (mincurr, maxcurr) def doTime(self, startval, target): # get difference in current delta = abs( self._field2current(target) - self._field2current(startval)) # ramp is per minute, doTime should return seconds return 60 * delta / self._attached_currentsource.ramp @usermethod def calibrate(self, fieldcolumn, *scannumbers): """Calibrate the B to I conversion, argument is one or more scan numbers. The first argument specifies the name of the device which should be used as a measuring device for the field. Example: >>> B_mira.calibrate(Bf, 351) """ scans = session.experiment.data.getLastScans() self.log.info('determining calibration from scans, please wait...') Is = [] Bs = [] currentcolumn = self._attached_currentsource.name # XXX(dataapi): adapt to new Dataset class for scan in scans: if scan.counter not in scannumbers: continue if fieldcolumn not in scan.ynames or \ currentcolumn not in scan.xnames: self.log.info('%s is not a calibration scan', scan.counter) continue xindex = scan.xnames.index(currentcolumn) yindex = scan.ynames.index(fieldcolumn) yunit = scan.yunits[yindex] if yunit == 'T': factor = 1.0 elif yunit == 'mT': factor = 1e-3 elif yunit == 'uT': factor = 1e-6 elif yunit == 'G': factor = 1e-4 elif yunit == 'kG': factor = 1e-1 else: raise NicosError( self, 'unknown unit for B field ' 'readout: %r' % yunit) for xr, yr in zip(scan.xresults, scan.yresults): Is.append(xr[xindex]) Bs.append(yr[yindex] * factor) if not Is: self.log.error('no calibration data found') return fit = Fit('calibration', self._current2field, ['c%d' % i for i in range(len(self.calibration))], [1] * len(self.calibration)) res = fit.run(Is, Bs, [1] * len(Bs)) if res._failed: self.log.warning('fit failed') return self.calibration = res._pars[1]
class ImagePlateDrum(PyTangoDevice, Moveable): """ImagePlateDrum implements erasing, moving to expo position and readout for MAATEL Image Plate Detectors. """ DEFAULT_URL_FMT = "tango://%s/EMBL/Microdiff/General#dbase=no" tango_status_mapping = dict(PyTangoDevice.tango_status_mapping) tango_status_mapping[DevState.STANDBY] = status.OK tango_status_mapping[DevState.ALARM] = status.ERROR POS_ERASE = "erase" POS_EXPO = "expo" POS_READ = "read" valuetype = oneof(POS_ERASE, POS_EXPO, POS_READ) parameters = { "drumpos": Param("Drum position in degree", type=float, settable=True, volatile=True, category="general"), "readheadpos": Param("Read head motor position in mm", type=float, settable=True, volatile=True, category="general"), "drumexpo": Param("Drum expo position in degree", type=float, settable=True, volatile=True, category="general"), "readspeed": Param("Readout velocity for the detector drum " "in rpm", type=float, settable=True, volatile=True, category="general"), "erasespeed": Param("Erase velocity for the detector drum " "in rpm", type=float, settable=True, volatile=True, category="general"), "freqlaser": Param("Frequency for the laser diode in Hz", type=float, settable=True, volatile=True, category="general"), "timeerase": Param("Erasure time in seconds", type=float, settable=True, volatile=True, category="general"), } parameter_overrides = { "unit": Override(default="", mandatory=False, settable=False) } def doInit(self, mode): self._lastStatus = None self._moveTo = None if mode == SIMULATION: self._mapStart = {} self._mapStop = {} return self._mapStart = { ImagePlateDrum.POS_ERASE: self._dev.StartErasureProcess, ImagePlateDrum.POS_EXPO: self._dev.MoveExpoPosition, ImagePlateDrum.POS_READ: self._dev.StartReadProcess, } self._mapStop = { ImagePlateDrum.POS_ERASE: self._dev.AbortErasureProcess, ImagePlateDrum.POS_EXPO: self._dev.AbortExposureProcess, ImagePlateDrum.POS_READ: self._dev.AbortReadProcess, } def doStart(self, pos): self.log.debug("doStart: pos: %s", pos) myStatus = self.status(0) if myStatus[0] == status.OK: self._moveTo = pos self._mapStart[pos]() else: raise MoveError(self, "Movement not allowed during device status " "'%s'" % (status.statuses[myStatus[0]])) def doStop(self): self.log.debug("doStop") if self._moveTo in self._mapStop: self._mapStop[self._moveTo]() else: myStatus = self.status(0) if myStatus[0] == status.OK: self.log.warning("Device already stopped.") else: raise NicosError(self, "Internal moveTo state unknown. " "Check device status.") def doRead(self, maxage=0): return self.target def doStatus(self, maxage=0): # Workaround for status changes from busy to another state although the # operation has _not_ been completed. st, msg = PyTangoDevice.doStatus(self, maxage) if self._lastStatus == status.BUSY and st != status.BUSY: self.log.debug("doStatus: leaving busy state (%d)? %d. " "Check again after a short delay.", status.BUSY, st) session.delay(5) st, msg = PyTangoDevice.doStatus(self, 0) self.log.debug("doStatus: recheck result: %d", st) self._lastStatus = st return st, msg def doFinish(self): self._moveTo = None def doReadDrumpos(self): return self._dev.DrumPosition def doReadReadheadpos(self): return self._dev.ReadHeadPosition def doReadDrumexpo(self): return self._dev.DrumExpoPosition def doReadReadspeed(self): return self._dev.ReadingDrumJogSpeed def doReadErasespeed(self): return self._dev.ErasureDrumJogSpeed def doReadFreqlaser(self): return self._dev.LaserDiodeLevel def doReadTimeerase(self): return self._dev.ErasureDuration def doWriteDrumpos(self, value): self._dev.DrumPosition = value def doWriteReadheadpos(self, value): self._dev.ReadHeadPosition = value def doWriteDrumexpo(self, value): self._dev.DrumExpoPosition = value def doWriteReadspeed(self, value): self._dev.ReadingDrumJogSpeed = value def doWriteErasespeed(self, value): self._dev.ErasureDrumJogSpeed = value def doWriteFreqlaser(self, value): self._dev.LaserDiodeLevel = value def doWriteTimeerase(self, value): self._dev.ErasureDuration = value
def test_nonemptystring(): p = Param('nonemptystring', type=nonemptystring) assert p.default is None assert raises(ValueError, nonemptystring, '') assert nonemptystring('text') == 'text'