def test_listof(): assert listof(int)([0., 1, '2']) == [0, 1, 2] assert listof(int)() == [] # should also accept tuples assert listof(int)((1, 2, 3)) == [1, 2, 3] assert raises(ValueError, listof(int), 10) # assert that the list is read-only assert raises(TypeError, listof(int)([0, 1, 2]).__setitem__, 0, 1)
class CacheKeyFilter(DeviceMixinBase): """Mixin for filtering cache keys that might be forwarded. This is used on the source side (Collector device), as well as on individual sinks (ForwarderBase devices) for maximum flexibility. """ parameters = { 'keyfilters': Param( 'Filter keys to send (regexps); if empty, all ' 'keys are accepted', type=listof(str)), } def _initFilters(self): self._prefixfilters = () self._regexfilters = [] for regex in self.keyfilters: # special case: prefixes if PREFIX_RE.match(regex): self._prefixfilters += (regex[:-2], ) else: self._regexfilters.append(re.compile(regex)) def _checkKey(self, key): if not self._prefixfilters and not self._regexfilters: return True if key.startswith(self._prefixfilters): return True for keyfilter in self._regexfilters: if keyfilter.match(key): return True return False
class DetectorDistance(Readable): """Calculate detector distance based on the detector tubes position""" attached_devices = { 'detectubes': Attach('Pilatus detector tubes', Readable, multiple=4) } parameters = { 'offset': Param('Minimum distance between Pilatus and sample', type=int, settable=True), 'tubelength': Param('List of tube length', type=listof(int), settable=False, default=[450, 450, 900, 900]), } hardware_access = False def doInit(self, mode): self.log.debug('Detector distance init') self.read() def doRead(self, maxage=0): distance = 0 for tube, l in zip(self._attached_detectubes, self.tubelength): # tubes can only be set in correct sequence if tube.read(maxage) != 'up': break distance += l return self.offset + distance
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 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 FileSink(DataSink): """Base class for sinks that save data into files.""" parameters = { 'subdir': Param('Filetype specific subdirectory name', type=subdir, mandatory=False, default=''), 'filenametemplate': Param('List of templates for data file names ' '(will be hardlinked), can contain ' 'subdirectories', ext_desc=TEMPLATE_DESC, type=listof(str), default=['%(pointcounter)08d.dat'], settable=False, prefercache=False), 'filemode': Param('File access rights after closing the file, ' "if set to 'none' (default) the OS defaults " 'will be used', type=none_or(intrange(0o000, 0o777),)), }
class FileSink(DataSink): """Base class for sinks that save data into files.""" parameters = { 'subdir': Param('Filetype specific subdirectory name', type=subdir, mandatory=False, default=''), 'filenametemplate': Param( 'List of templates for data file names ' '(will be hardlinked), can contain ' 'subdirectories', ext_desc=TEMPLATE_DESC, type=listof(str), default=['%(pointcounter)08d.dat'], settable=False, prefercache=False), }
def _array(members, minlen=0, **kwds): # ignore maxlen and minlen > 1 members = get_validator(**members) return nonemptylistof(members) if minlen else listof(members)
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 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 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 ImageChannel(QMesyDAQImage, BaseImageChannel): parameters = { 'readout': Param('Readout mode of the Detector', settable=True, type=oneof('raw', 'mapped', 'amplitude'), default='mapped', mandatory=False, chatty=True), 'flipaxes': Param('Flip data along these axes after reading from det', type=listof(int), default=[], unit=''), 'transpose': Param('Whether to transpose the image', type=bool, default=False), } # Use the configuration from QMesyDAQ parameter_overrides = { 'listmode': Override(volatile=True), 'histogram': Override(volatile=True), } def doWriteListmode(self, value): self._dev.SetProperties(['writelistmode', ('%s' % value).lower()]) return self.doReadListmode() def doReadListmode(self): return { 'false': False, 'true': True }[self._getProperty('writelistmode')] def doWriteHistogram(self, value): self._dev.SetProperties(['writehistogram', ('%s' % value).lower()]) return self.doReadHistogram() def doReadHistogram(self): return { 'false': False, 'true': True }[self._getProperty('writehistogram')] def doWriteReadout(self, value): self._dev.SetProperties(['histogram', value]) return self._getProperty('histogram') def doWriteListmodefile(self, value): self._dev.SetProperties(['lastlistfile', value]) return self._getProperty('lastlistfile') # def doReadListmodefile(self): # return self._getProperty('lastlistfile') def doWriteHistogramfile(self, value): self._dev.SetProperties(['lasthistfile', 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') def doReadArray(self, quality): narray = BaseImageChannel.doReadArray(self, quality) if self.transpose: narray = np.transpose(narray) for axis in self.flipaxes: narray = np.flip(narray, axis) return narray def doFinish(self): self.doStatus(0) return BaseImageChannel.doFinish(self) def doStop(self): self.doStatus(0) return BaseImageChannel.doStop(self)
class SISChannel(BaseImageChannel): """ Spheres SIS ImageChannel """ parameters = { 'analyzers': Param('Analyzer Crystal', type=oneof('Si111', 'Si311'), default='Si111'), 'monochromator': Param('Monochromator Crystal', type=oneof('Si111', 'Si311'), default='Si111'), 'incremental': Param('Incremental Mode', type=bool, settable=True), 'inelasticinterval': Param('Interval for the inelastic scan', type=int, settable=True, default=1200), 'regulardets': Param('relevant detectors for the monitor', type=listof(int), volatile=True), 'elasticparams': Param('Interval and amount for one elastic scan ' 'datafile', type=listof(int), settable=True, volatile=True), 'detamount': Param('Amount of detectors for the reshaping ' 'of the read data.', type=int, default=16), 'backgroundmode': Param('Mode of the background chopper', type=float, volatile=True), 'backgroundoffset': Param( 'Count offset in relation to the first PST ' 'zero after each background zero.', type=float, settable=True, volatile=True), 'chopperopen': Param( 'Chopper is open in this range. If the ' 'first value is bigger then the second the ' 'area is wrapped around 360 deg', type=listof(float), settable=True, volatile=True), 'chopperreflecting': Param( 'Chopper is reflecting in this range. If ' 'the first value is bigger then the second ' 'the area is wrapped around 360 deg', type=listof(float), settable=True, volatile=True), 'chopstatisticlen': Param('Revolutions for Background chopper ' 'statistics', type=int, settable=True, volatile=True), 'backzerorange': Param( 'Range of the pst zero passes for the last' 'chopstatisticlen pst revolutions', type=listof(float), volatile=True), 'measuremode': Param('Mode in which the detector is measuring', type=oneof(ELASTIC, INELASTIC, SIMULATION), volatile=True), } def __init__(self, name, **config): BaseImageChannel.__init__(self, name, **config) self._block = [] self._reason = '' self.clearAccumulated() def clearAccumulated(self): self._last_edata = None self._last_cdata = None def doReadElasticparams(self): return [self._dev.tscan_interval, self._dev.tscan_amount] def doWriteElasticparams(self, val): self._dev.tscan_interval = val[0] self._dev.tscan_amount = val[1] def doReadBackgroundmode(self): return self._dev.backgr_mode def doReadBackgroundoffset(self): return self._dev.backgr_offset def doWriteBackgroundoffset(self, value): self._dev.backgr_offset = value def doReadChopperopen(self): return [self._dev.chop_open_f, self._dev.chop_open_t] def doWriteChopperopen(self, value): if len(value) != 2: raise UsageError('Chopperopen needs exactly 2 values: ' '"from" and "to"') self._dev.chop_open_f = value[0] self._dev.chop_open_t = value[1] def doReadChopperreflecting(self): return [self._dev.chop_refl_f, self._dev.chop_refl_t] def doWriteChopperreflecting(self, value): if len(value) != 2: raise UsageError('Chopperreflecting needs exactly 2 values: ' '"from" and "to"') self._dev.chop_refl_f = value[0] self._dev.chop_refl_t = value[1] def doReadChopstatisticlen(self): return self._dev.chop_statisticlen def doWriteChopstatisticlen(self, value): self._dev.chop_statisticlen = value def doReadBackzerorange(self): return [self._dev.backgr_zero_min, self._dev.backgr_zero_max] def doReadMeasuremode(self): if session.sessiontype == SIMULATION: return SIMULATION return self._dev.GetMeasureMode() def setTscanAmount(self, amount): if session.sessiontype == SIMULATION: return if self.status()[0] == status.OK: self._dev.setProperties(['tscan_amount', str(amount)]) def doPrepare(self): self._checkShutter() self._dev.Prepare() self._hw_wait() def doReadArray(self, quality): mode = self.measuremode if mode == SIMULATION: return [] if quality == LIVE: return [self._readLiveData()] else: self._reason = quality if mode == ELASTIC: return self._readElastic() elif mode == INELASTIC: return self._readInelastic() def doReadRegulardets(self): if session.sessiontype == SIMULATION: return [] return list(self._dev.GetRegularDetectors()) def valueInfo(self): return Value(name=TOTAL, type="counter", fmtstr="%d", unit="cts"), def _readLiveData(self): if self.measuremode == INELASTIC: if self._last_edata is not None: if self.incremental: live = self._readData(ENERGY) self._mergeCounts(live, self._last_edata) else: live = self._last_edata else: live = self._readData(ENERGY) else: live = [] return live def _readElastic(self): live = self._readLiveData() params = self._dev.GetParams() + \ ['type', 'elastic'] + \ self.getAdditionalParams() data = self._readData(TIME) return live, params, data def _readInelastic(self): live = self._readLiveData() params = self._dev.GetParams() + \ ['type', 'inelastic'] + \ self.getAdditionalParams() edata = self._readData(ENERGY) cdata = self._readData(CHOPPER) if self.incremental: if self._reason == FINAL: self._processCounts(edata, cdata) edata = self._last_edata cdata = self._last_cdata else: self._mergeCounts(edata, self._last_edata) self._mergeCounts(cdata, self._last_cdata) return live, params, edata, cdata def _readData(self, target): '''Read the requested data from the hardware and generate the according histogram tuples to make further processing easier. ''' if target == ENERGY: targettuple = EnergyHisto elif target == TIME: targettuple = TimeHisto elif target == CHOPPER: targettuple = ChopperHisto else: raise NicosError('Can not read "%s"-data. Target not supported' % target) xvals = self._dev.GetTickData(target) xvalsize = len(xvals) rawdata = self._dev.GetData(target) rawdatasize = len(rawdata) data = [] amount = rawdatasize // (xvalsize * self.detamount * 2) if target in [ENERGY, TIME]: self.readresult = [sum(rawdata[:rawdatasize // 2])] counts = rawdata[:rawdatasize // 2].reshape(amount, xvalsize, self.detamount) times = rawdata[rawdatasize // 2:].reshape(amount, xvalsize, self.detamount) # to filter out the superfluous zero arrays in the elastic scan # we only iterate over the amount of unique xvals. The arrays not yet # 'triggered' all have the corresponding xval of 0, same as the first # array. Thus every array corresponding to the 2nd+ 0 xval will not be # added. for i in range(len(set(xvals))): # first insert the xvalue block = [float(xvals[i])] # then add the i-th count array from each of the count blocks for h in range(amount): block.append(counts[h, i, :]) # then add the corresponding timesteps the same way for h in range(amount): block.append(times[h, i, :]) data.append(targettuple._make(block)) return data def getAdditionalParams(self): return [ 'monochromator', self.monochromator, 'analyzers', self.analyzers, 'reason', self._reason, 'incremental', self.incremental, 'dets4mon', [self.regulardets[0], self.regulardets[-1]] ] def _mergeCounts(self, total, increment): """ Increments the first array, entry by entry with the corresponding entries from the second array. """ if not increment: return for i, entry in enumerate(total): for j, arr in enumerate(entry): if j == 0: continue arr.__iadd__(increment[i][j]) def _processCounts(self, edata, cdata): """ Set data arrays to the right values for further processing. """ if self._last_edata is None: self._last_edata = edata self._last_cdata = cdata return try: self._mergeCounts(self._last_edata, edata) self._mergeCounts(self._last_cdata, cdata) except IndexError: self.resetIncremental( 'Error while merging arrays. ' 'Lenght of accumulated(%d, %d) differs ' 'from provided(%d, %d) array. ' 'Switching to non incremental mode.' % (len(self._last_edata), len( self._last_cdata), len(edata), len(cdata))) return def resetIncremental(self, message): self.log.warning(message + ' Switching to non incremental mode.') self.incremental = False self._last_edata = None self._last_cdata = None def setDummyDoppler(self, speed): self._dev.dummy_doppvel = speed