diff options
Diffstat (limited to 'examples/blissPlot.py')
-rw-r--r-- | examples/blissPlot.py | 635 |
1 files changed, 0 insertions, 635 deletions
diff --git a/examples/blissPlot.py b/examples/blissPlot.py deleted file mode 100644 index 71c1fd3..0000000 --- a/examples/blissPlot.py +++ /dev/null @@ -1,635 +0,0 @@ -# coding: utf-8 - - -from __future__ import division, absolute_import, print_function, unicode_literals - - -import six - -from silx.gui import qt, icons -from silx.gui.plot.actions import PlotAction, mode -from silx.gui.plot import PlotWindow, PlotWidget -from silx.gui.plot.Colors import rgba - - -class DrawModeAction(PlotAction): - """Action that control drawing mode""" - - _MODES = { # shape: (icon, text, tooltip - 'rectangle': ('shape-rectangle', 'Rectangle selection', 'Select a rectangular region'), - 'line': ('shape-diagonal', 'Line selection', 'Select a line'), - 'hline': ('shape-horizontal', 'H. line selection', 'Select a horizontal line'), - 'vline': ('shape-vertical', 'V. line selection', 'Select a vertical line'), - 'polygon': ('shape-polygon', 'Polygon selection', 'Select a polygon'), - } - - def __init__(self, plot, parent=None): - self._shape = 'polygon' - self._label = None - self._color = 'black' - self._width = None - icon, text, tooltip = self._MODES[self._shape] - - super(DrawModeAction, self).__init__( - plot, icon=icon, text=text, - tooltip=tooltip, - triggered=self._actionTriggered, - checkable=True, parent=parent) - - # Listen to mode change - self.plot.sigInteractiveModeChanged.connect(self._modeChanged) - # Init the state - self._modeChanged(None) - - def _update(self): - if self.isChecked(): - self._actionTriggered() - - def setShape(self, shape): - self._shape = shape - icon, text, tooltip = self._MODES[self._shape] - self.setIcon(icons.getQIcon(icon)) - self.setText(text) - self.setToolTip(tooltip) - self._update() - - def getShape(self): - return self._shape - - def setColor(self, color): - self._color = rgba(color) - self._update() - - def getColor(self): - return qt.QColor.fromRgbF(*self._color) - - def setLabel(self, label): - self._label = label - self._update() - - def getLabel(self): - return self._label - - def setWidth(self, width): - self._width = width - self._update() - - def getWidth(self): - return self._width - - def _modeChanged(self, source): - modeDict = self.plot.getInteractiveMode() - old = self.blockSignals(True) - self.setChecked(modeDict['mode'] == 'draw' and - modeDict['shape'] == self._shape and - modeDict['label'] == self._label) - self.blockSignals(old) - - def _actionTriggered(self, checked=False): - self.plot.setInteractiveMode('draw', - source=self, - shape=self._shape, - color=self._color, - label=self._label, - width=self._width) - - -class ShapeSelector(qt.QObject): - """Handles the selection of a single shape in a PlotWidget - - :param parent: QObject's parent - """ - - selectionChanged = qt.Signal(tuple) - """Signal emitted whenever the selection has changed. - - It provides the selection. - """ - - selectionFinished = qt.Signal(tuple) - """Signal emitted when selection is terminated. - - It provides the selection. - """ - - def __init__(self, parent=None): - assert isinstance(parent, PlotWidget) - super(ShapeSelector, self).__init__(parent) - self._isSelectionRunning = False - self._selection = () - self._itemId = "%s-%s" % (self.__class__.__name__, id(self)) - - # Add a toolbar to plot - self._toolbar = qt.QToolBar('Selection') - self._modeAction = DrawModeAction(plot=parent) - self._modeAction.setLabel(self._itemId) - self._modeAction.setColor(rgba('red')) - toolButton = qt.QToolButton() - toolButton.setDefaultAction(self._modeAction) - toolButton.setToolButtonStyle(qt.Qt.ToolButtonTextBesideIcon) - self._toolbar.addWidget(toolButton) - - # Style - - def getColor(self): - """Returns the color used for the selection shape - - :rtype: QColor - """ - return self._modeAction.getColor() - - def setColor(self, color): - """Set the color used for the selection shape - - :param color: The color to use for selection shape as - either a color name, a QColor, a list of uint8 or float in [0, 1]. - """ - self._modeAction.setColor(color) - self._updateShape() - - # Control selection - - def getSelection(self): - """Returns selection control point coordinates - - Returns an empty tuple if there is no selection - - :return: Nx2 (x, y) coordinates or an empty tuple. - """ - return tuple(zip(*self._selection)) - - def _setSelection(self, x, y): - """Set the selection shape control points. - - Use :meth:`reset` to remove the selection. - - :param x: X coordinates of control points - :param y: Y coordinates of control points - """ - selection = x, y - if selection != self._selection: - self._selection = selection - self._updateShape() - self.selectionChanged.emit(self.getSelection()) - - def reset(self): - """Clear the rectangle selection""" - if self._selection: - self._selection = () - self._updateShape() - self.selectionChanged.emit(self.getSelection()) - - def start(self, shape): - """Start requiring user to select a rectangle - - :param str shape: The shape to select in: - 'rectangle', 'line', 'polygon', 'hline', 'vline' - """ - plot = self.parent() - if plot is None: - raise RuntimeError('No plot to perform selection') - - self.stop() - self.reset() - - assert shape in ('rectangle', 'line', 'polygon', 'hline', 'vline') - - self._modeAction.setShape(shape) - self._modeAction.trigger() # To set the interaction mode - - self._isSelectionRunning = True - - plot.sigPlotSignal.connect(self._handleDraw) - - self._toolbar.show() - plot.addToolBar(qt.Qt.BottomToolBarArea, self._toolbar) - - def stop(self): - """Stop shape selection""" - if not self._isSelectionRunning: - return - - plot = self.parent() - if plot is None: - return - - mode = plot.getInteractiveMode() - if mode['mode'] == 'draw' and mode['label'] == self._itemId: - plot.setInteractiveMode('zoom') # This disconnects draw handler - - plot.sigPlotSignal.disconnect(self._handleDraw) - - plot.removeToolBar(self._toolbar) - - self._isSelectionRunning = False - self.selectionFinished.emit(self.getSelection()) - - def _handleDraw(self, event): - """Handle shape drawing event""" - if (event['event'] == 'drawingFinished' and - event['parameters']['label'] == self._itemId): - self._setSelection(event['xdata'], event['ydata']) - self.stop() - - def _updateShape(self): - """Update shape on the plot""" - plot = self.parent() - if plot is not None: - if not self._selection: - plot.remove(legend=self._itemId, kind='item') - - else: - x, y = self._selection - shape = self._modeAction.getShape() - if shape == 'line': - shape = 'polylines' - - plot.addItem(x, y, - legend=self._itemId, - shape=shape, - color=rgba(self._modeAction.getColor()), - fill=False) - - - -class PointsSelector(qt.QObject): - """Handle selection of points in a PlotWidget""" - - selectionChanged = qt.Signal(tuple) - """Signal emitted whenever the selection has changed. - - It provides the selection. - """ - - selectionFinished = qt.Signal(tuple) - """Signal emitted when selection is terminated. - - It provides the selection. - """ - - - def __init__(self, parent): - assert isinstance(parent, PlotWidget) - super(PointsSelector, self).__init__(parent) - - self._isSelectionRunning = False - self._markersAndPos = [] - self._totalPoints = 0 - - def getSelection(self): - """Returns the selection""" - return tuple(pos for _, pos in self._markersAndPos) - - def eventFilter(self, obj, event): - """Event filter for plot hide and key event""" - if event.type() == qt.QEvent.Hide: - self.stop() - - elif event.type() == qt.QEvent.KeyPress: - if event.key() in (qt.Qt.Key_Delete, qt.Qt.Key_Backspace) or ( - event.key() == qt.Qt.Key_Z and event.modifiers() & qt.Qt.ControlModifier): - if len(self._markersAndPos) > 0: - plot = self.parent() - if plot is not None: - legend, _ = self._markersAndPos.pop() - plot.remove(legend=legend, kind='marker') - - self._updateStatusBar() - self.selectionChanged.emit(self.getSelection()) - return True # Stop further handling of those keys - - elif event.key() == qt.Qt.Key_Return: - self.stop() - return True # Stop further handling of those keys - - return super(PointsSelector, self).eventFilter(obj, event) - - def start(self, nbPoints=1): - """Start interactive selection of points - - :param int nbPoints: Number of points to select - """ - self.stop() - self.reset() - - plot = self.parent() - if plot is None: - raise RuntimeError('No plot to perform selection') - - self._totalPoints = nbPoints - self._isSelectionRunning = True - - plot.setInteractiveMode(mode='zoom') - self._handleInteractiveModeChanged(None) - plot.sigInteractiveModeChanged.connect( - self._handleInteractiveModeChanged) - - plot.installEventFilter(self) - - self._updateStatusBar() - - def stop(self): - """Stop interactive point selection""" - if not self._isSelectionRunning: - return - - plot = self.parent() - if plot is None: - return - - plot.removeEventFilter(self) - - plot.sigInteractiveModeChanged.disconnect( - self._handleInteractiveModeChanged) - - currentMode = plot.getInteractiveMode() - if currentMode['mode'] == 'zoom': # Stop handling mouse click - plot.sigPlotSignal.disconnect(self._handleSelect) - - plot.statusBar().clearMessage() - self._isSelectionRunning = False - self.selectionFinished.emit(self.getSelection()) - - def reset(self): - """Reset selected points""" - plot = self.parent() - if plot is None: - return - - for legend, _ in self._markersAndPos: - plot.remove(legend=legend, kind='marker') - self._markersAndPos = [] - self.selectionChanged.emit(self.getSelection()) - - def _updateStatusBar(self): - """Update status bar message""" - plot = self.parent() - if plot is None: - return - - msg = 'Select %d/%d input points' % (len(self._markersAndPos), - self._totalPoints) - - currentMode = plot.getInteractiveMode() - if currentMode['mode'] != 'zoom': - msg += ' (Use zoom mode to add/remove points)' - - plot.statusBar().showMessage(msg) - - def _handleSelect(self, event): - """Handle mouse events""" - if event['event'] == 'mouseClicked' and event['button'] == 'left': - plot = self.parent() - if plot is None: - return - - x, y = event['x'], event['y'] - - # Add marker - legend = "sx.ginput %d" % len(self._markersAndPos) - plot.addMarker( - x, y, - legend=legend, - text='%d' % len(self._markersAndPos), - color='red', - draggable=False) - - self._markersAndPos.append((legend, (x, y))) - self._updateStatusBar() - if len(self._markersAndPos) >= self._totalPoints: - self.stop() - - def _handleInteractiveModeChanged(self, source): - """Handle change of interactive mode in the plot - - :param source: Objects that triggered the mode change - """ - plot = self.parent() - if plot is None: - return - - mode = plot.getInteractiveMode() - if mode['mode'] == 'zoom': # Handle click events - plot.sigPlotSignal.connect(self._handleSelect) - else: # Do not handle click event - plot.sigPlotSignal.disconnect(self._handleSelect) - self._updateStatusBar() - - -# TODO refactor to make a selection by composition rather than inheritance... -class BlissPlot(PlotWindow): - """Plot with selection methods""" - - sigSelectionDone = qt.Signal(object) - """Signal emitted when the selection is done - - It provides the list of selected points - """ - - def __init__(self, parent=None, **kwargs): - super(BlissPlot, self).__init__(parent=parent, **kwargs) - self._selectionColor = rgba('red') - self._selectionMode = None - self._markers = [] - self._pointNames = () - - # Style - - def getColor(self): - """Returns the color used for selection markers - - :rtype: QColor - """ - return qt.QColor.fromRgbF(*self._selectionColor) - - def setColor(self, color): - """Set the markers used for selection - - :param color: The color to use for selection markers as - either a color name, a QColor, a list of uint8 or float in [0, 1]. - """ - self._selectionColor = rgba(color) - self._updateMarkers() # To apply color change - - # Marker helpers - - def _setSelectedPointMarker(self, x, y, index=None): - """Add/Update a marker for a point - - :param float x: X coord in plot - :param float y: Y coord in plot - :param int index: Index of point in points names to set - :return: corresponding marker legend - :rtype: str - """ - if index is None: - index = len(self._markers) - - name = self._pointNames[index] - legend = "BlissPlotSelection-%d" % index - - self.addMarker( - x, y, - legend=legend, - text=name, - color=self._selectionColor, - draggable=self._selectionMode is not None) - return legend - - def _updateMarkers(self): - """Update all markers to sync color/draggable""" - for index, (x, y) in enumerate(self.getSelectedPoints()): - self._setSelectedPointMarker(x, y, index) - - # Selection mode control - - def startPointSelection(self, points=1): - """Request the user to select a number of points - - :param points: - The number of points the user need to select (default: 1) - or a list of point names or a single name. - :type points: Union[int, List[str], str] - :return: A future to access the result - :rtype: concurrent.futures.Future - """ - self.stopSelection() - self.resetSelection() - - if isinstance(points, six.string_types): - points = [points] - elif isinstance(points, int): - points = [str(i) for i in range(points)] - - self._pointNames = points - - self._markers = [] - self._selectionMode = 'points' - - self.setInteractiveMode(mode='zoom') - self._handleInteractiveModeChanged(None) - self.sigInteractiveModeChanged.connect( - self._handleInteractiveModeChanged) - - def stopSelection(self): - """Stop current selection. - - Calling this method emits the selection through sigSelectionDone - and does not clear the selection. - """ - if self._selectionMode is not None: - currentMode = self.getInteractiveMode() - if currentMode['mode'] == 'zoom': # Stop handling mouse click - self.sigPlotSignal.disconnect(self._handleSelect) - - self.sigInteractiveModeChanged.disconnect( - self._handleInteractiveModeChanged) - - self._selectionMode = None - self.statusBar().showMessage('Selection done') - - self._updateMarkers() # To make them not draggable - - self.sigSelectionDone.emit(self.getSelectedPoints()) - - def getSelectedPoints(self): - """Returns list of currently selected points - - :rtype: tuple - """ - return tuple(self._getItem(kind='marker', legend=legend).getPosition() - for legend in self._markers) - - def resetSelection(self): - """Clear current selection""" - for legend in self._markers: - self.remove(legend, kind='marker') - self._markers = [] - - if self._selectionMode is not None: - self._updateStatusBar() - else: - self.statusBar().clearMessage() - - def _handleInteractiveModeChanged(self, source): - """Handle change of interactive mode in the plot - - :param source: Objects that triggered the mode change - """ - mode = self.getInteractiveMode() - if mode['mode'] == 'zoom': # Handle click events - self.sigPlotSignal.connect(self._handleSelect) - else: # Do not handle click event - self.sigPlotSignal.disconnect(self._handleSelect) - self._updateStatusBar() - - def _handleSelect(self, event): - """Handle mouse events""" - if event['event'] == 'mouseClicked' and event['button'] == 'left': - if len(self._markers) == len(self._pointNames): - return - - x, y = event['x'], event['y'] - legend = self._setSelectedPointMarker(x, y, len(self._markers)) - self._markers.append(legend) - self._updateStatusBar() - - def keyPressEvent(self, event): - """Handle keys for undo/done actions""" - if self._selectionMode is not None: - if event.key() in (qt.Qt.Key_Delete, qt.Qt.Key_Backspace) or ( - event.key() == qt.Qt.Key_Z and - event.modifiers() & qt.Qt.ControlModifier): - if len(self._markers) > 0: - legend = self._markers.pop() - self.remove(legend, kind='marker') - - self._updateStatusBar() - return # Stop processing the event - - elif event.key() == qt.Qt.Key_Return: - self.stopSelection() - return # Stop processing the event - - return super(BlissPlot, self).keyPressEvent(event) - - def _updateStatusBar(self): - """Update status bar message""" - if len(self._markers) < len(self._pointNames): - name = self._pointNames[len(self._markers)] - msg = 'Select point: %s (%d/%d)' % ( - name, len(self._markers), len(self._pointNames)) - else: - msg = 'Selection ready. Press Enter to validate' - - currentMode = self.getInteractiveMode() - if currentMode['mode'] != 'zoom': - msg += ' (Use zoom mode to add/edit points)' - - self.statusBar().showMessage(msg) - - -if __name__ == '__main__': - app = qt.QApplication([]) - - #plot = BlissPlot() - #plot.startPointSelection(('first', 'second', 'third')) - - def dumpChanged(selection): - print('selectionChanged', selection) - - def dumpFinished(selection): - print('selectionFinished', selection) - - plot = PlotWindow() - selector = ShapeSelector(plot) - #selector.start(shape='rectangle') - selector.selectionChanged.connect(dumpChanged) - selector.selectionFinished.connect(dumpFinished) - plot.show() - - points = PointsSelector(plot) - points.start(3) - points.selectionChanged.connect(dumpChanged) - points.selectionFinished.connect(dumpFinished) - #app.exec_() |