class TaurusBaseImageItem(TaurusBaseComponent): '''A ImageItem that gets its data from a taurus attribute''' dataChanged = baseSignal('dataChanged') def setModel(self, model): # do the standard stuff TaurusBaseComponent.setModel(self, model) #... and fire a fake event for initialization try: value = self.getModelObj().read() self.fireEvent(self, taurus.core.taurusbasetypes.TaurusEventType.Change, value) except: pass def handleEvent(self, evt_src, evt_type, evt_value): if evt_value is None or getattr(evt_value, 'rvalue', None) is None: self.debug('Ignoring event from %s' % repr(evt_src)) return v = evt_value.rvalue if isinstance(v, Quantity): v = v.magnitude # TODO: units should be used for setting some title in the colorbar try: v = self.filterData(v) except Exception, e: self.info('Ignoring event. Reason: %s', e.message) return # this is the range of the z axis (color scale) lut_range = self.get_lut_range() # if the range was not set, make it None (autoscale z axis) if lut_range[0] == lut_range[1]: lut_range = None self.set_data(v, lut_range=lut_range) self.dataChanged.emit() p = self.plot() if p is not None: p.update_colormap_axis(self) p.replot()
class TaurusCurveItem(CurveItem, TaurusBaseComponent): '''A CurveItem that autoupdates its values & params when x or y components change''' dataChanged = baseSignal('dataChanged') def __init__(self, curveparam=None, taurusparam=None): CurveItem.__init__(self, curveparam=curveparam) TaurusBaseComponent.__init__(self, self.__class__.__name__) # I need to do this because I am not using the standard model attach # mechanism self.taurusEvent.connect(self.filterEvent) self._xcomp = None self._ycomp = None if taurusparam is None: taurusparam = TaurusCurveParam() self.taurusparam = taurusparam def setModels(self, x, y): # create/get new components if x is None: newX = None else: newX = taurus.Attribute(x) newY = taurus.Attribute(y) # stop listening to previous components (if they are not the same as # the new) if self._xcomp is not None and self._xcomp is not newX: self._xcomp.removeListener(self) self._xcomp = newX if self._ycomp is not None and self._ycomp is not newY: self._ycomp.removeListener(self) self._ycomp = newY # start listening to new components if self._xcomp is not None: self._xcomp.addListener(self) self._ycomp.addListener(self) self.onCurveDataChanged() self.taurusparam.xModel = x self.taurusparam.yModel = y def getModels(self): return self.taurusparam.xModel, self.taurusparam.yModel def handleEvent(self, evt_src, ect_type, evt_value): if evt_value is None or getattr(evt_value, 'rvalue', None) is None: self.debug('Ignoring event from %s' % repr(evt_src)) return if evt_src is self._xcomp or evt_src is self._ycomp: self.onCurveDataChanged() self.dataChanged.emit() def onCurveDataChanged(self): # TODO: Take units into account for displaying curves, axis, etc. try: yvalue = self._ycomp.read().rvalue.magnitude except: yvalue = None if yvalue is None: return # TODO: Take units into account for displaying curves, axis, etc. try: xvalue = self._xcomp.read().rvalue.magnitude except: xvalue = None if xvalue is None: xvalue = numpy.arange(len(yvalue)) self.set_data(xvalue, yvalue) p = self.plot() if p is not None: p.replot() def get_item_parameters(self, itemparams): CurveItem.get_item_parameters(self, itemparams) itemparams.add("TaurusParam", self, self.taurusparam) def updateTaurusParams(self): self.taurusparam.update_curve(self) def set_item_parameters(self, itemparams): CurveItem.set_item_parameters(self, itemparams) self.updateTaurusParams()
class TaurusTrendItem(CurveItem, TaurusBaseComponent): '''A CurveItem that listens to events from a Taurus scalar attribute and appends new values to it''' dataChanged = baseSignal('dataChanged') scrollRequested = baseSignal('scrollRequested', object, object, object) def __init__(self, curveparam=None, taurusparam=None): CurveItem.__init__(self, curveparam=curveparam) TaurusBaseComponent.__init__(self, self.__class__.__name__) self.__xBuffer = None self.__yBuffer = None self.__timeOffset = None if taurusparam is None: taurusparam = TaurusTrendParam() self.taurusparam = taurusparam self.updateTaurusParams() def setBufferSize(self, buffersize): '''sets the size of the stack. :param buffersize: (int) size of the stack ''' self.taurusparam.maxBufferSize = buffersize try: if self.__xBuffer is not None: self.__xBuffer.setMaxSize(buffersize) if self.__yBuffer is not None: self.__yBuffer.setMaxSize(buffersize) except ValueError: self.info( 'buffer downsizing requested. Current contents will be discarded' ) self.__xBuffer = None self.__yBuffer = None def setModel(self, model): # do the standard stuff TaurusBaseComponent.setModel(self, model) # update the taurusparam self.taurusparam.model = self.getModelName() #... and fire a fake event for initialization try: value = self.getModelObj().read() self.fireEvent(self, taurus.core.taurusbasetypes.TaurusEventType.Change, value) except: pass def handleEvent(self, evt_src, evt_type, evt_value): if evt_value is None or getattr(evt_value, 'rvalue', None) is None: self.debug('Ignoring event from %s' % repr(evt_src)) return plot = self.plot() # initialization\ if self.__xBuffer is None: self.__xBuffer = ArrayBuffer( numpy.zeros(min(128, self.taurusparam.maxBufferSize), dtype='d'), maxSize=self.taurusparam.maxBufferSize) if self.__yBuffer is None: self.__yBuffer = ArrayBuffer( numpy.zeros(min(128, self.taurusparam.maxBufferSize), dtype='d'), maxSize=self.taurusparam.maxBufferSize) # update x values if self.taurusparam.stackMode == 'datetime': if self.__timeOffset is None: self.__timeOffset = evt_value.time.totime() if plot is not None: plot.set_axis_title('bottom', 'Time') plot.set_axis_unit('bottom', '') self.__xBuffer.append(evt_value.time.totime()) elif self.taurusparam.stackMode == 'deltatime': try: self.__xBuffer.append(evt_value.time.totime() - self.__timeOffset) except TypeError: # this will happen if self.__timeOffset has not been initialized self.__timeOffset = evt_value.time.totime() self.__xBuffer.append(0) if plot is not None: plot.set_axis_title( 'bottom', 'Time since %s' % evt_value.time.isoformat()) plot.set_axis_unit('bottom', '') else: try: # +numpy.random.randint(0,4) #for debugging we can put a variable step step = 1 self.__xBuffer.append(self.__xBuffer[-1] + step) except IndexError: # this will happen when the x buffer is empty self.__xBuffer.append(0) if plot is not None: plot.set_axis_title('bottom', 'Event #') plot.set_axis_unit('bottom', '') # update y # TODO: Take units into account for displaying curves, axis, etc. self.__yBuffer.append(evt_value.rvalue.magnitude) # update the plot data x, y = self.__xBuffer.contents(), self.__yBuffer.contents() self.set_data(x, y) # signal data changed and replot self.dataChanged.emit() if plot is not None: value = x[-1] axis = self.xAxis() xmin, xmax = plot.get_axis_limits(axis) if value > xmax or value < xmin: self.scrollRequested.emit(plot, axis, value) plot.replot() def get_item_parameters(self, itemparams): CurveItem.get_item_parameters(self, itemparams) itemparams.add("TaurusParam", self, self.taurusparam) def updateTaurusParams(self): self.taurusparam.update_curve(self) def set_item_parameters(self, itemparams): CurveItem.set_item_parameters(self, itemparams) self.updateTaurusParams()
class TaurusTrend2DItem(XYImageItem, TaurusBaseComponent): '''A XYImageItem that is constructed by stacking 1D arrays from events from a Taurus 1D attribute''' scrollRequested = baseSignal('scrollRequested', object, object, object) dataChanged = baseSignal('dataChanged') def __init__(self, param=None, buffersize=512, stackMode='datetime'): XYImageItem.__init__(self, numpy.arange(2), numpy.arange( 2), numpy.zeros((2, 2)), param=param) TaurusBaseComponent.__init__(self, self.__class__.__name__) self.maxBufferSize = buffersize self._yValues = None self._xBuffer = None self._zBuffer = None self.stackMode = stackMode self.set_interpolation(INTERP_NEAREST) self.__timeOffset = None def setBufferSize(self, buffersize): '''sets the size of the stack :param buffersize: (int) size of the stack ''' self.maxBufferSize = buffersize try: if self._xBuffer is not None: self._xBuffer.setMaxSize(buffersize) if self._zBuffer is not None: self._zBuffer.setMaxSize(buffersize) except ValueError: self.info( 'buffer downsizing requested. Current contents will be discarded') self._xBuffer = None self._zBuffer = None def setModel(self, model): # do the standard stuff TaurusBaseComponent.setModel(self, model) #... and fire a fake event for initialization try: value = self.getModelObj().read() self.fireEvent( self, taurus.core.taurusbasetypes.TaurusEventType.Change, value) except: pass def handleEvent(self, evt_src, evt_type, evt_value): if evt_value is None or getattr(evt_value, 'rvalue', None) is None: self.debug('Ignoring event from %s' % repr(evt_src)) return plot = self.plot() if plot is None: return # initialization ySize = len(evt_value.rvalue) if self._yValues is None: self._yValues = numpy.arange(ySize, dtype='d') if self._xBuffer is None: self._xBuffer = ArrayBuffer(numpy.zeros( min(128, self.maxBufferSize), dtype='d'), maxSize=self.maxBufferSize) if self._zBuffer is None: self._zBuffer = ArrayBuffer(numpy.zeros( (min(128, self.maxBufferSize), ySize), dtype='d'), maxSize=self.maxBufferSize) return # check that new data is compatible with previous data if ySize != self._yValues.size: self.info('Incompatible shape in data from event (orig=%i, current=%i). Ignoring' % ( self._yValues.size, ySize)) return # update x values if self.stackMode == 'datetime': x = evt_value.time.totime() if self.__timeOffset is None: self.__timeOffset = x plot.set_axis_title('bottom', 'Time') plot.set_axis_unit('bottom', '') elif self.stackMode == 'deltatime': try: x = evt_value.time.totime() - self.__timeOffset except TypeError: # this will happen if self.__timeOffset has not been initialized self.__timeOffset = evt_value.time.totime() x = 0 plot.set_axis_title('bottom', 'Time since %s' % evt_value.time.isoformat()) plot.set_axis_unit('bottom', '') else: try: # +numpy.random.randint(0,4) #for debugging we can put a variable step step = 1 x = self._xBuffer[-1] + step except IndexError: # this will happen when the x buffer is empty x = 0 plot.set_axis_title('bottom', 'Event #') plot.set_axis_unit('bottom', '') if len(self._xBuffer) and x <= self._xBuffer[-1]: self.info('Ignoring event (non-increasing x value)') return self._xBuffer.append(x) # update z rvalue = evt_value.rvalue if isinstance(evt_value.rvalue, Quantity): rvalue = evt_value.rvalue.magnitude # TODO: units should be checked for coherence with previous values self._zBuffer.append(rvalue) # check if there is enough data to start plotting if len(self._xBuffer) < 2: self.info('waiting for at least 2 values to start plotting') return x = self._xBuffer.contents() y = self._yValues z = self._zBuffer.contents().transpose() if x.size == 2: plot.set_axis_limits('left', y.min(), y.max()) # guess the max of the scale allowed by the buffer xmax = x[0] + (x[1] - x[0]) * self.maxBufferSize plot.set_axis_limits('bottom', x.min(), xmax) # update the plot data lut_range = self.get_lut_range() # this is the range of the z axis (color scale) if lut_range[0] == lut_range[1]: # if the range was not set, make it None (autoscale z axis) lut_range = None self.set_data(z, lut_range=lut_range) self.set_xy(x, y) # signal data changed and replot self.dataChanged.emit() if plot is not None: value = x[-1] axis = self.xAxis() xmin, xmax = plot.get_axis_limits(axis) if value > xmax or value < xmin: self.scrollRequested.emit(plot, axis, value) plot.update_colormap_axis(self) plot.replot()
class QTaurusBaseListener(TaurusListener): """Base class for QObjects listening to taurus events. .. note:: :meth:`getSignaller` is now unused and deprecated. This is because `taurusEvent` is implemented using :func:`baseSignal`, that doesn't require the class to inherit from QObject. """ taurusEvent = baseSignal('taurusEvent', object, object, object) def __init__(self, name=None, parent=None): if name is None: name = self.__class__.__name__ super(QTaurusBaseListener, self).__init__(name, parent=parent) self._eventFilters = [] self.taurusEvent.connect(self.filterEvent) #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- # Event handling chain #-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- def eventReceived(self, evt_src, evt_type, evt_value): """The basic implementation of the event handling chain is as follows: - eventReceived just calls :meth:`fireEvent` which emits a "taurusEvent" PyQt signal that is connected (by :meth:`preAttach`) to the :meth:`filterEvent` method. - After filtering, :meth:`handleEvent` is invoked with the resulting filtered event .. note:: in the earlier steps of the chain (i.e., in :meth:`eventReceived`/:meth:`fireEvent`), the code is executed in a Python thread, while from eventFilter ahead, the code is executed in a Qt thread. When writing widgets, one should normally work on the Qt thread (i.e. reimplementing :meth:`handleEvent`) :param evt_src: (object) object that triggered the event :param evt_type: (taurus.core.taurusbasetypes.TaurusEventType) type of event :param evt_value: (object) event value """ self.fireEvent(evt_src, evt_type, evt_value) def fireEvent(self, evt_src=None, evt_type=None, evt_value=None): """Emits a "taurusEvent" signal. It is unlikely that you may need to reimplement this method in subclasses. Consider reimplementing :meth:`eventReceived` or :meth:`handleEvent` instead depending on whether you need to execute code in the python or Qt threads, respectively :param evt_src: (object or None) object that triggered the event :param evt_type: (taurus.core.taurusbasetypes.TaurusEventType or None) type of event :param evt_value: (object or None) event value """ try: self.taurusEvent.emit(evt_src, evt_type, evt_value) except: pass def filterEvent(self, evt_src=-1, evt_type=-1, evt_value=-1): """The event is processed by each and all filters in strict order unless one of them returns None (in which case the event is discarded) :param evt_src: (object) object that triggered the event :param evt_type: (taurus.core.taurusbasetypes.TaurusEventType) type of event :param evt_value: (object) event value """ r = evt_src, evt_type, evt_value if r == (-1, -1, -1): # @todo In an ideal world the signature of this method should be # (evt_src, evt_type, evt_value). However there's a bug in PyQt: # If a signal is disconnected between the moment it is emitted and # the moment the slot is called, then the slot is called without # parameters (!?). We added this default values to detect if # this is the case without printing an error message each time. # If this gets fixed, we should remove this line. return for f in self._eventFilters: r = f(*r) if r is None: return self.handleEvent(*r) def handleEvent(self, evt_src, evt_type, evt_value): """Event handling. Default implementation does nothing. Reimplement as necessary :param evt_src: (object or None) object that triggered the event :param evt_type: (taurus.core.taurusbasetypes.TaurusEventType or None) type of event :param evt_value: (object or None) event value """ pass def setEventFilters(self, filters=None): """sets the taurus event filters list. The filters are run in order, using each output to feed the next filter. A filter must be a function that accepts 3 arguments ``(evt_src, evt_type, evt_value)`` If the event is to be ignored, the filter must return None. If the event is not to be ignored, filter must return a ``(evt_src, evt_type, evt_value)`` tuple which may (or not) differ from the input. For a library of common filters, see taurus/core/util/eventfilters.py :param filters: (sequence) a sequence of filters See also: insertEventFilter """ if filters is None: filters = [] self._eventFilters = list(filters) def getEventFilters(self): """Returns the list of event filters for this widget :return: (sequence<callable>) the event filters """ return self._eventFilters def insertEventFilter(self, filter, index=-1): """insert a filter in a given position :param filter: (callable(evt_src, evt_type, evt_value)) a filter :param index: (int) index to place the filter (default = -1 meaning place at the end) See also: setEventFilters """ self._eventFilters.insert(index, filter) @deprecation_decorator(rel='4.0') def getSignaller(self): return self
class TaurusBaseImageItem(TaurusBaseComponent): '''A ImageItem that gets its data from a taurus attribute''' dataChanged = baseSignal('dataChanged') def setModel(self, model): # do the standard stuff TaurusBaseComponent.setModel(self, model) #... and fire a fake event for initialization try: value = self.getModelObj().read() self.fireEvent(self, taurus.core.taurusbasetypes.TaurusEventType.Change, value) except: pass def handleEvent(self, evt_src, evt_type, evt_value): if evt_value is None or getattr(evt_value, 'rvalue', None) is None: self.debug('Ignoring event from %s' % repr(evt_src)) return v = evt_value.rvalue if isinstance(v, Quantity): v = v.magnitude # TODO: units should be used for setting some title in the colorbar try: v = self.filterData(v) except Exception as e: self.info('Ignoring event. Reason: %s', e.message) return # this is the range of the z axis (color scale) lut_range = self.get_lut_range() # if the range was not set, make it None (autoscale z axis) if lut_range[0] == lut_range[1]: lut_range = None self.set_data(v, lut_range=lut_range) self.dataChanged.emit() p = self.plot() if p is not None: p.update_colormap_axis(self) p.replot() def filterData(self, data): '''Reimplement this method if you want to pre-process the data that will be passed to set_data. It should return something acceptable by :meth:`setData` and raise an exception if the data cannot be processed. This default implementation casts array types not supported by guiqwt to numpy.int32 See: - http://code.google.com/p/guiqwt/issues/detail?id=44 and - https://sourceforge.net/tracker/?func=detail&atid=484769&aid=3603991&group_id=57612 - https://sourceforge.net/p/tauruslib/tickets/33/ ''' try: dtype = data.dtype v = data except: v = numpy.array(data) # note that this is potentially expensive dtype = v.dtype if dtype not in (float, numpy.double, numpy.int32, numpy.uint16, numpy.int16, numpy.uint8, numpy.int8, bool): # note: numpy.uint32 was not included because of # https://sourceforge.net/p/tauruslib/tickets/33/ try: self.debug('casting to numpy.int32') v = numpy.int32(v) except OverflowError: raise OverflowError( "type %s not supported by guiqwt and cannot be casted to int32" % repr(v.dtype)) return v