diff options
author | Alexandre Marie <alexandre.marie@synchrotron-soleil.fr> | 2019-07-09 10:20:20 +0200 |
---|---|---|
committer | Alexandre Marie <alexandre.marie@synchrotron-soleil.fr> | 2019-07-09 10:20:20 +0200 |
commit | 654a6ac93513c3cc1ef97cacd782ff674c6f4559 (patch) | |
tree | 3b986e4972de7c57fa465820367602fc34bcb0d3 /examples | |
parent | a763e5d1b3921b3194f3d4e94ab9de3fbe08bbdd (diff) |
New upstream version 0.11.0+dfsg
Diffstat (limited to 'examples')
-rw-r--r-- | examples/plot3dSceneWindow.py | 12 | ||||
-rw-r--r-- | examples/plot3dUpdateScatterFromThread.py | 176 | ||||
-rw-r--r-- | examples/plotLimits.py | 6 | ||||
-rw-r--r-- | examples/plotStats.py | 63 | ||||
-rw-r--r-- | examples/plotWidget.py | 72 |
5 files changed, 292 insertions, 37 deletions
diff --git a/examples/plot3dSceneWindow.py b/examples/plot3dSceneWindow.py index 5e78aa6..1b2f808 100644 --- a/examples/plot3dSceneWindow.py +++ b/examples/plot3dSceneWindow.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2017-2018 European Synchrotron Radiation Facility +# Copyright (c) 2017-2019 European Synchrotron Radiation Facility # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -51,8 +51,6 @@ import numpy from silx.gui import qt from silx.gui.plot3d.SceneWindow import SceneWindow, items -from silx.gui.plot3d.tools.PositionInfoWidget import PositionInfoWidget -from silx.gui.widgets.BoxLayoutDockWidget import BoxLayoutDockWidget SIZE = 1024 @@ -69,14 +67,6 @@ sceneWidget.setForegroundColor((1., 1., 1., 1.)) sceneWidget.setTextColor((0.1, 0.1, 0.1, 1.)) -# Add PositionInfoWidget to display picking info -positionInfo = PositionInfoWidget() -positionInfo.setSceneWidget(sceneWidget) -dock = BoxLayoutDockWidget() -dock.setWindowTitle("Selection Info") -dock.setWidget(positionInfo) -window.addDockWidget(qt.Qt.BottomDockWidgetArea, dock) - # 2D Image ### # Add a dummy RGBA image diff --git a/examples/plot3dUpdateScatterFromThread.py b/examples/plot3dUpdateScatterFromThread.py new file mode 100644 index 0000000..9c2213f --- /dev/null +++ b/examples/plot3dUpdateScatterFromThread.py @@ -0,0 +1,176 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2019 European Synchrotron Radiation Facility +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ###########################################################################*/ +"""This script illustrates the update of a +:class:`~silx.gui.plot3d.SceneWindow.SceneWindow` widget from a thread. + +The problem is that GUI methods should be called from the main thread. +To safely update the scene from another thread, one need to execute the update +asynchronously in the main thread. +In this example, this is achieved with +:func:`~silx.gui.utils.concurrent.submitToQtMainThread`. + +In this example a thread calls submitToQtMainThread to append data to a 3D scatter. +""" + +__authors__ = ["T. Vincent"] +__license__ = "MIT" +__date__ = "08/03/2019" + + +import threading +import time + +import numpy + +from silx.gui import qt +from silx.gui.utils import concurrent +from silx.gui.plot3d.SceneWindow import SceneWindow +from silx.gui.plot3d import items + + +MAX_NUMBER_OF_POINTS = 10**6 + + +class UpdateScatterThread(threading.Thread): + """Thread updating the scatter 3D item data + + :param ~silx.gui.plot3d.items.Scatter3D scatter3d: 3D scatter to update. + """ + + def __init__(self, scatter3d): + self.scatter3d = scatter3d + self.running = False + self.future_result = None + super(UpdateScatterThread, self).__init__() + + def start(self): + """Start the update thread""" + self.running = True + super(UpdateScatterThread, self).start() + + def _appendScatterData(self, x, y, z, value): + """Add some data points to the Scatter3D item. + + This method MUST be called in the Qt main thread. + + :param numpy.ndarray x: + :param numpy.ndarray y: + :param numpy.ndarray z: + :param numpy.ndarray value: + """ + # use copy=False to avoid useless copy of numpy arrays + curX, curY, curZ, curValue = self.scatter3d.getData(copy=False) + + x = numpy.append(curX, x) + y = numpy.append(curY, y) + z = numpy.append(curZ, z) + value = numpy.append(curValue, value) + + # Update data + self.scatter3d.setData(x, y, z, value, copy=False) + + def run(self): + """Method implementing thread loop that updates the scatter data + + It produces adds scatter points every 10 ms or so, up to 1 million. + """ + count = 0 # Number of data points currently rendered + + # Init arrays that accumulate scatter points + x = numpy.array((), dtype=numpy.float32) + y = numpy.array((), dtype=numpy.float32) + z = numpy.array((), dtype=numpy.float32) + value = numpy.array((), dtype=numpy.float32) + + while self.running: + time.sleep(0.01) + + # Generate new data points + inclination = numpy.random.random(1000).astype(numpy.float32) * numpy.pi + azimuth = numpy.random.random(1000).astype(numpy.float32) * 2. * numpy.pi + radius = numpy.random.normal(loc=10., scale=.5, size=1000) + newX = radius * numpy.sin(inclination) * numpy.cos(azimuth) + newY = radius * numpy.sin(inclination) * numpy.sin(azimuth) + newZ = radius * numpy.cos(inclination) + newValue = numpy.random.random(1000).astype(numpy.float32) + + # Accumulate data points + x = numpy.append(x, newX) + y = numpy.append(y, newY) + z = numpy.append(z, newZ) + value = numpy.append(value, newValue) + + # Only append data if the previous one has been added + if self.future_result is None or self.future_result.done(): + if count > MAX_NUMBER_OF_POINTS: + # Restart a new scatter plot asyn + self.future_result = concurrent.submitToQtMainThread( + self.scatter3d.setData, x, y, z, value) + + count = len(x) + else: + # Append data asynchronously + self.future_result = concurrent.submitToQtMainThread( + self._appendScatterData, x, y, z, value) + + count += len(x) + + # Reset accumulators + x = numpy.array((), dtype=numpy.float32) + y = numpy.array((), dtype=numpy.float32) + z = numpy.array((), dtype=numpy.float32) + value = numpy.array((), dtype=numpy.float32) + + def stop(self): + """Stop the update thread""" + self.running = False + self.join(2) + + +def main(): + global app + app = qt.QApplication([]) + + # Create a SceneWindow + window = SceneWindow() + window.show() + + sceneWidget = window.getSceneWidget() + scatter = items.Scatter3D() + scatter.setSymbol(',') + scatter.getColormap().setName('magma') + sceneWidget.addItem(scatter) + + # Create the thread that calls submitToQtMainThread + updateThread = UpdateScatterThread(scatter) + updateThread.start() # Start updating the plot + + app.exec_() + + updateThread.stop() # Stop updating the plot + + +if __name__ == '__main__': + main() diff --git a/examples/plotLimits.py b/examples/plotLimits.py index 0a39bc6..c7cc7f5 100644 --- a/examples/plotLimits.py +++ b/examples/plotLimits.py @@ -23,8 +23,8 @@ # THE SOFTWARE. # # ###########################################################################*/ -"""This script is an example to illustrate how to use axis synchronization -tool. +"""This script is an example to illustrate how to set range constraints on +plot axes. """ from silx.gui import qt @@ -37,7 +37,7 @@ class ConstrainedViewPlot(qt.QMainWindow): def __init__(self): qt.QMainWindow.__init__(self) - self.setWindowTitle("Plot with synchronized axes") + self.setWindowTitle("Plot with constrained axes") widget = qt.QWidget(self) self.setCentralWidget(widget) diff --git a/examples/plotStats.py b/examples/plotStats.py index f7474ec..3929697 100644 --- a/examples/plotStats.py +++ b/examples/plotStats.py @@ -46,7 +46,46 @@ from silx.gui import qt from silx.gui.colors import Colormap from silx.gui.plot import Plot1D from silx.gui.plot.stats.stats import StatBase +from silx.gui.utils import concurrent +import random +import threading +import argparse import numpy +import time + + +class UpdateThread(threading.Thread): + """Thread updating the curve of a :class:`~silx.gui.plot.Plot1D` + + :param plot1d: The Plot1D to update.""" + + def __init__(self, plot1d): + self.plot1d = plot1d + self.running = False + super(UpdateThread, self).__init__() + + def start(self): + """Start the update thread""" + self.running = True + super(UpdateThread, self).start() + + def run(self): + """Method implementing thread loop that updates the plot""" + while self.running: + time.sleep(1) + # Run plot update asynchronously + concurrent.submitToQtMainThread( + self.plot1d.addCurve, + numpy.arange(1000), + numpy.random.random(1000), + resetzoom=False, + legend=random.choice(('mycurve0', 'mycurve1')) + ) + + def stop(self): + """Stop the update thread""" + self.running = False + self.join(2) class Integral(StatBase): @@ -88,17 +127,22 @@ class COM(StatBase): return comX, comY -def main(): +def main(argv): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + '--update-mode', + default='manual', + help='update mode to display (manual or auto)') + + options = parser.parse_args(argv[1:]) + app = qt.QApplication([]) plot = Plot1D() - x = numpy.arange(21) - y = numpy.arange(21) - plot.addCurve(x=x, y=y, legend='myCurve') - plot.addCurve(x=x, y=(y + 5), legend='myCurve2') - - plot.setActiveCurve('myCurve') + # Create the thread that calls submitToQtMainThread + updateThread = UpdateThread(plot) + updateThread.start() # Start updating the plot plot.addScatter(x=[0, 2, 5, 5, 12, 20], y=[2, 3, 4, 20, 15, 6], @@ -113,11 +157,14 @@ def main(): ] plot.getStatsWidget().setStats(stats) + plot.getStatsWidget().setUpdateMode(options.update_mode) + plot.getStatsWidget().setDisplayOnlyActiveItem(False) plot.getStatsWidget().parent().setVisible(True) plot.show() app.exec_() + updateThread.stop() # Stop updating the plot if __name__ == '__main__': - main() + main(sys.argv) diff --git a/examples/plotWidget.py b/examples/plotWidget.py index c8a90a5..af64afb 100644 --- a/examples/plotWidget.py +++ b/examples/plotWidget.py @@ -36,7 +36,7 @@ as its central widget and adds toolbars and a colorbar by using pluggable widget __authors__ = ["T. Vincent"] __license__ = "MIT" -__date__ = "05/09/2017" +__date__ = "06/05/2019" import numpy @@ -68,15 +68,29 @@ class MyPlotWindow(qt.QMainWindow): palette.setColor(qt.QPalette.Window, qt.Qt.white) colorBar.setPalette(palette) + options = qt.QWidget(self) + layout = qt.QVBoxLayout(options) + button = qt.QPushButton("Show image", self) + button.clicked.connect(self.showImage) + layout.addWidget(button) + button = qt.QPushButton("Show scatter", self) + button.clicked.connect(self.showScatter) + layout.addWidget(button) + button = qt.QPushButton("Show delaunay", self) + button.clicked.connect(self.showDelaunay) + layout.addWidget(button) + layout.addStretch() + # Combine the ColorBarWidget and the PlotWidget as # this QMainWindow's central widget gridLayout = qt.QGridLayout() gridLayout.setSpacing(0) gridLayout.setContentsMargins(0, 0, 0, 0) - gridLayout.addWidget(self._plot, 0, 0) - gridLayout.addWidget(colorBar, 0, 1) + gridLayout.addWidget(options, 0, 0) + gridLayout.addWidget(self._plot, 0, 1) + gridLayout.addWidget(colorBar, 0, 2) gridLayout.setRowStretch(0, 1) - gridLayout.setColumnStretch(0, 1) + gridLayout.setColumnStretch(1, 1) centralWidget = qt.QWidget(self) centralWidget.setLayout(gridLayout) self.setCentralWidget(centralWidget) @@ -116,6 +130,44 @@ class MyPlotWindow(qt.QMainWindow): """Returns the PlotWidget contains in this window""" return self._plot + def showImage(self): + plot = self.getPlotWidget() + plot.clear() + plot.getDefaultColormap().setName('viridis') + + # Add an image to the plot + x = numpy.outer( + numpy.linspace(-10, 10, 200), numpy.linspace(-10, 5, 150)) + image = numpy.sin(x) / x + plot.addImage(image) + plot.resetZoom() + + def showScatter(self): + plot = self.getPlotWidget() + plot.clear() + plot.getDefaultColormap().setName('viridis') + + nbPoints = 50 + x = numpy.random.rand(nbPoints) + y = numpy.random.rand(nbPoints) + value = numpy.random.rand(nbPoints) + plot.addScatter(x=x, y=y, value=value) + plot.resetZoom() + + def showDelaunay(self): + plot = self.getPlotWidget() + plot.clear() + plot.getDefaultColormap().setName('viridis') + + nbPoints = 50 + x = numpy.random.rand(nbPoints) + y = numpy.random.rand(nbPoints) + value = numpy.random.rand(nbPoints) + legend = plot.addScatter(x=x, y=y, value=value) + scatter = plot.getScatter(legend) + scatter.setVisualization("solid") + plot.resetZoom() + def main(): global app @@ -125,17 +177,7 @@ def main(): window = MyPlotWindow() window.setAttribute(qt.Qt.WA_DeleteOnClose) window.show() - - # Change the default colormap - plot = window.getPlotWidget() - plot.getDefaultColormap().setName('viridis') - - # Add an image to the plot - x = numpy.outer( - numpy.linspace(-10, 10, 200), numpy.linspace(-10, 5, 150)) - image = numpy.sin(x) / x - plot.addImage(image) - + window.showImage() app.exec_() |