diff options
author | Picca Frédéric-Emmanuel <picca@debian.org> | 2017-10-07 07:59:01 +0200 |
---|---|---|
committer | Picca Frédéric-Emmanuel <picca@debian.org> | 2017-10-07 07:59:01 +0200 |
commit | bfa4dba15485b4192f8bbe13345e9658c97ecf76 (patch) | |
tree | fb9c6e5860881fbde902f7cbdbd41dc4a3a9fb5d /examples | |
parent | f7bdc2acff3c13a6d632c28c4569690ab106eed7 (diff) |
New upstream version 0.6.0+dfsg
Diffstat (limited to 'examples')
-rw-r--r-- | examples/customHdf5TreeModel.py | 290 | ||||
-rwxr-xr-x | examples/fftPlotAction.py | 20 | ||||
-rw-r--r-- | examples/icons.py | 10 | ||||
-rw-r--r-- | examples/plotContextMenu.py | 100 | ||||
-rwxr-xr-x[-rw-r--r--] | examples/plotItemsSelector.py (renamed from examples/colorbar.py) | 50 | ||||
-rw-r--r-- | examples/plotLimits.py | 93 | ||||
-rw-r--r-- | examples/plotUpdateFromThread.py | 128 | ||||
-rw-r--r-- | examples/plotWidget.py | 121 | ||||
-rwxr-xr-x | examples/printPreview.py | 82 | ||||
-rwxr-xr-x | examples/shiftPlotAction.py | 2 | ||||
-rw-r--r-- | examples/syncaxis.py | 101 | ||||
-rw-r--r-- | examples/viewer3DVolume.py | 27 | ||||
-rw-r--r--[-rwxr-xr-x] | examples/writetoh5.py (renamed from examples/spectoh5.py) | 20 |
13 files changed, 983 insertions, 61 deletions
diff --git a/examples/customHdf5TreeModel.py b/examples/customHdf5TreeModel.py new file mode 100644 index 0000000..8bd444a --- /dev/null +++ b/examples/customHdf5TreeModel.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2016-2017 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. +# +# ###########################################################################*/ +"""Qt Hdf5 widget examples +""" + +import logging +import sys +import tempfile +import numpy +import h5py + +logging.basicConfig() +_logger = logging.getLogger("customHdf5TreeModel") +"""Module logger""" + +from silx.gui import qt +import silx.gui.hdf5 +from silx.gui.data.DataViewerFrame import DataViewerFrame +from silx.gui.widgets.ThreadPoolPushButton import ThreadPoolPushButton +from silx.gui.hdf5.Hdf5TreeModel import Hdf5TreeModel + + +class CustomTooltips(qt.QIdentityProxyModel): + """Custom the tooltip of the model by composition. + + It is a very stable way to custom it cause it uses the Qt API. Then it will + not change according to the version of Silx. + + But it is not well integrated if you only want to add custom fields to the + default tooltips. + """ + + def data(self, index, role=qt.Qt.DisplayRole): + if role == qt.Qt.ToolTipRole: + + # Reach information from the node + sourceIndex = self.mapToSource(index) + sourceModel = self.sourceModel() + originalTooltip = sourceModel.data(sourceIndex, qt.Qt.ToolTipRole) + originalH5pyObject = sourceModel.data(sourceIndex, Hdf5TreeModel.H5PY_OBJECT_ROLE) + + # We can filter according to the column + if sourceIndex.column() == Hdf5TreeModel.TYPE_COLUMN: + return super(CustomTooltips, self).data(index, role) + + # Let's create our own tooltips + template = u"""<html> + <dl> + <dt><b>Original</b></dt><dd>{original}</dd> + <dt><b>Parent name</b></dt><dd>{parent_name}</dd> + <dt><b>Name</b></dt><dd>{name}</dd> + <dt><b>Power of 2</b></dt><dd>{pow_of_2}</dd> + </dl> + </html> + """ + + try: + data = originalH5pyObject[()] + if data.size <= 10: + result = data ** 2 + else: + result = "..." + except Exception: + result = "NA" + + info = dict( + original=originalTooltip, + parent_name=originalH5pyObject.parent.name, + name=originalH5pyObject.name, + pow_of_2=str(result) + ) + return template.format(**info) + + return super(CustomTooltips, self).data(index, role) + + +_file_cache = {} + + +def get_hdf5_with_all_types(): + global _file_cache + ID = "alltypes" + if ID in _file_cache: + return _file_cache[ID].name + + tmp = tempfile.NamedTemporaryFile(prefix=ID + "_", suffix=".h5", delete=True) + tmp.file.close() + h5 = h5py.File(tmp.name, "w") + + g = h5.create_group("arrays") + g.create_dataset("scalar", data=10) + g.create_dataset("list", data=numpy.arange(10)) + base_image = numpy.arange(10**2).reshape(10, 10) + images = [base_image, + base_image.T, + base_image.size - 1 - base_image, + base_image.size - 1 - base_image.T] + dtype = images[0].dtype + data = numpy.empty((10 * 10, 10, 10), dtype=dtype) + for i in range(10 * 10): + data[i] = images[i % 4] + data.shape = 10, 10, 10, 10 + g.create_dataset("image", data=data[0, 0]) + g.create_dataset("cube", data=data[0]) + g.create_dataset("hypercube", data=data) + g = h5.create_group("dtypes") + g.create_dataset("int32", data=numpy.int32(10)) + g.create_dataset("int64", data=numpy.int64(10)) + g.create_dataset("float32", data=numpy.float32(10)) + g.create_dataset("float64", data=numpy.float64(10)) + g.create_dataset("string_", data=numpy.string_("Hi!")) + # g.create_dataset("string0",data=numpy.string0("Hi!\x00")) + + g.create_dataset("bool", data=True) + g.create_dataset("bool2", data=False) + h5.close() + + _file_cache[ID] = tmp + return tmp.name + + +class Hdf5TreeViewExample(qt.QMainWindow): + """ + This window show an example of use of a Hdf5TreeView. + + The tree is initialized with a list of filenames. A panel allow to play + with internal property configuration of the widget, and a text screen + allow to display events. + """ + + def __init__(self, filenames=None): + """ + :param files_: List of HDF5 or Spec files (pathes or + :class:`silx.io.spech5.SpecH5` or :class:`h5py.File` + instances) + """ + qt.QMainWindow.__init__(self) + self.setWindowTitle("Silx HDF5 widget example") + + self.__asyncload = False + self.__treeview = silx.gui.hdf5.Hdf5TreeView(self) + """Silx HDF5 TreeView""" + + self.__sourceModel = self.__treeview.model() + """Store the source model""" + + self.__text = qt.QTextEdit(self) + """Widget displaying information""" + + self.__dataViewer = DataViewerFrame(self) + vSpliter = qt.QSplitter(qt.Qt.Vertical) + vSpliter.addWidget(self.__dataViewer) + vSpliter.addWidget(self.__text) + vSpliter.setSizes([10, 0]) + + spliter = qt.QSplitter(self) + spliter.addWidget(self.__treeview) + spliter.addWidget(vSpliter) + spliter.setStretchFactor(1, 1) + + main_panel = qt.QWidget(self) + layout = qt.QVBoxLayout() + layout.addWidget(spliter) + layout.addWidget(self.createTreeViewConfigurationPanel(self, self.__treeview)) + layout.setStretchFactor(spliter, 1) + main_panel.setLayout(layout) + + self.setCentralWidget(main_panel) + + # append all files to the tree + for file_name in filenames: + self.__treeview.findHdf5TreeModel().appendFile(file_name) + + self.__treeview.activated.connect(self.displayData) + + def displayData(self): + """Called to update the dataviewer with the selected data. + """ + selected = list(self.__treeview.selectedH5Nodes()) + if len(selected) == 1: + # Update the viewer for a single selection + data = selected[0] + # data is a hdf5.H5Node object + # data.h5py_object is a Group/Dataset object (from h5py, spech5, fabioh5) + # The dataviewer can display both + self.__dataViewer.setData(data) + + def __fileCreated(self, filename): + if self.__asyncload: + self.__treeview.findHdf5TreeModel().insertFileAsync(filename) + else: + self.__treeview.findHdf5TreeModel().insertFile(filename) + + def __hdf5ComboChanged(self, index): + function = self.__hdf5Combo.itemData(index) + self.__createHdf5Button.setCallable(function) + + def __edfComboChanged(self, index): + function = self.__edfCombo.itemData(index) + self.__createEdfButton.setCallable(function) + + def __useCustomLabel(self): + customModel = CustomTooltips(self.__treeview) + customModel.setSourceModel(self.__sourceModel) + self.__treeview.setModel(customModel) + + def __useOriginalModel(self): + self.__treeview.setModel(self.__sourceModel) + + def createTreeViewConfigurationPanel(self, parent, treeview): + """Create a configuration panel to allow to play with widget states""" + panel = qt.QWidget(parent) + panel.setLayout(qt.QHBoxLayout()) + + content = qt.QGroupBox("Create HDF5", panel) + content.setLayout(qt.QVBoxLayout()) + panel.layout().addWidget(content) + + combo = qt.QComboBox() + combo.addItem("Containing all types", get_hdf5_with_all_types) + combo.activated.connect(self.__hdf5ComboChanged) + content.layout().addWidget(combo) + + button = ThreadPoolPushButton(content, text="Create") + button.setCallable(combo.itemData(combo.currentIndex())) + button.succeeded.connect(self.__fileCreated) + content.layout().addWidget(button) + + self.__hdf5Combo = combo + self.__createHdf5Button = button + + content.layout().addStretch(1) + + option = qt.QGroupBox("Custom model", panel) + option.setLayout(qt.QVBoxLayout()) + panel.layout().addWidget(option) + + button = qt.QPushButton("Original model") + button.clicked.connect(self.__useOriginalModel) + option.layout().addWidget(button) + + button = qt.QPushButton("Custom tooltips by composition") + button.clicked.connect(self.__useCustomLabel) + option.layout().addWidget(button) + + option.layout().addStretch(1) + + panel.layout().addStretch(1) + return panel + + +def main(filenames): + """ + :param filenames: list of file paths + """ + app = qt.QApplication([]) + sys.excepthook = qt.exceptionHandler + window = Hdf5TreeViewExample(filenames) + window.show() + result = app.exec_() + # remove ending warnings relative to QTimer + app.deleteLater() + sys.exit(result) + + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/examples/fftPlotAction.py b/examples/fftPlotAction.py index e4d4081..66ecfbd 100755 --- a/examples/fftPlotAction.py +++ b/examples/fftPlotAction.py @@ -40,7 +40,7 @@ See shiftPlotAction.py for a simpler example with more basic comments. """ __authors__ = ["P. Knobel"] __license__ = "MIT" -__date__ = "12/01/2017" +__date__ = "27/06/2017" import numpy import os @@ -48,7 +48,7 @@ import sys from silx.gui import qt from silx.gui.plot import PlotWindow -from silx.gui.plot.PlotActions import PlotAction +from silx.gui.plot.actions import PlotAction # Custom icon # make sure there is a "fft.png" file saved in the same folder as this script @@ -77,8 +77,8 @@ class FftAction(PlotAction): def _rememberGraphLabels(self): """Store labels and title as attributes""" self.original_title = self.plot.getGraphTitle() - self.original_xlabel = self.plot.getGraphXLabel() - self.original_ylabel = self.plot.getGraphYLabel() + self.original_xlabel = self.plot.getXAxis().getLabel() + self.original_ylabel = self.plot.getYAxis().getLabel() def fftAllCurves(self, checked=False): """Get all curves from our PlotWindow, compute the amplitude spectrum @@ -97,13 +97,13 @@ class FftAction(PlotAction): self._rememberGraphLabels() # change them self.plot.setGraphTitle("Amplitude spectrum") - self.plot.setGraphXLabel("Frequency") - self.plot.setGraphYLabel("Amplitude") + self.plot.getXAxis().setLabel("Frequency") + self.plot.getYAxis().setLabel("Amplitude") else: # restore original labels self.plot.setGraphTitle(self.original_title) - self.plot.setGraphXLabel(self.original_xlabel) - self.plot.setGraphYLabel(self.original_ylabel) + self.plot.getXAxis().setLabel(self.original_xlabel) + self.plot.getYAxis().setLabel(self.original_ylabel) self.plot.clearCurves() @@ -186,8 +186,8 @@ plotwin.addCurve(x, y2, legend="cos") plotwin.addCurve(x, y3, legend="square wave") plotwin.setGraphTitle("Original data") -plotwin.setGraphYLabel("amplitude") -plotwin.setGraphXLabel("time") +plotwin.getYAxis().setLabel("amplitude") +plotwin.getXAxis().setLabel("time") plotwin.show() app.exec_() diff --git a/examples/icons.py b/examples/icons.py index 0992cf4..a6f0ada 100644 --- a/examples/icons.py +++ b/examples/icons.py @@ -26,10 +26,10 @@ """ Display available project icons using Qt. """ +import functools + from silx.gui import qt import silx.gui.icons -import pkg_resources -import functools class IconPreview(qt.QMainWindow): @@ -74,9 +74,11 @@ class IconPreview(qt.QMainWindow): self.tools = [] - icons = pkg_resources.resource_listdir("silx.resources", "gui/icons") + import silx.resources + + icons = silx.resources.list_dir("gui/icons") # filter out sub-directories - icons = filter(lambda x: not pkg_resources.resource_isdir("silx.resources", "gui/icons/" + x), icons) + icons = filter(lambda x: not silx.resources.is_dir("gui/icons/" + x), icons) # remove extension icons = [i.split(".")[0] for i in icons] # remove duplicated names diff --git a/examples/plotContextMenu.py b/examples/plotContextMenu.py new file mode 100644 index 0000000..3e9af1e --- /dev/null +++ b/examples/plotContextMenu.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2017 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 addition of a context menu to a PlotWidget. + +This is done by adding a custom context menu to the plot area of PlotWidget: +- set the context menu policy of the plot area to Qt.CustomContextMenu. +- connect to the plot area customContextMenuRequested signal. + +The same method works with PlotWindow, Plot1D and Plot2D widgets as they +inherit from PlotWidget. + +For more information on context menus, see Qt documentation. +""" + +import numpy + +from silx.gui import qt +from silx.gui.plot import PlotWidget +from silx.gui.plot.actions.control import ZoomBackAction, CrosshairAction +from silx.gui.plot.actions.io import SaveAction, PrintAction + + +class PlotWidgetWithContextMenu(PlotWidget): + """This class adds a custom context menu to PlotWidget's plot area.""" + + def __init__(self, *args, **kwargs): + super(PlotWidgetWithContextMenu, self).__init__(*args, **kwargs) + self.setWindowTitle('PlotWidget with a context menu') + self.setGraphTitle('Right-click on the plot to access context menu') + + # Create QAction for the context menu once for all + self._zoomBackAction = ZoomBackAction(plot=self, parent=self) + self._crosshairAction = CrosshairAction(plot=self, parent=self) + self._saveAction = SaveAction(plot=self, parent=self) + self._printAction = PrintAction(plot=self, parent=self) + + # Retrieve PlotWidget's plot area widget + plotArea = self.getWidgetHandle() + + # Set plot area custom context menu + plotArea.setContextMenuPolicy(qt.Qt.CustomContextMenu) + plotArea.customContextMenuRequested.connect(self._contextMenu) + + def _contextMenu(self, pos): + """Handle plot area customContextMenuRequested signal. + + :param QPoint pos: Mouse position relative to plot area + """ + # Create the context menu + menu = qt.QMenu(self) + menu.addAction(self._zoomBackAction) + menu.addSeparator() + menu.addAction(self._crosshairAction) + menu.addSeparator() + menu.addAction(self._saveAction) + menu.addAction(self._printAction) + + # Displaying the context menu at the mouse position requires + # a global position. + # The position received as argument is relative to PlotWidget's + # plot area, and thus needs to be converted. + plotArea = self.getWidgetHandle() + globalPosition = plotArea.mapToGlobal(pos) + menu.exec_(globalPosition) + + +# Start the QApplication +app = qt.QApplication([]) # Start QApplication +plot = PlotWidgetWithContextMenu() # Create the widget + +# Add content to the plot +x = numpy.linspace(0, 2 * numpy.pi, 1000) +plot.addCurve(x, numpy.sin(x), legend='sin') + +# Show the widget and start the application +plot.show() +app.exec_() diff --git a/examples/colorbar.py b/examples/plotItemsSelector.py index a4dc2d8..8f29457 100644..100755 --- a/examples/colorbar.py +++ b/examples/plotItemsSelector.py @@ -2,7 +2,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2016-2017 European Synchrotron Radiation Facility +# Copyright (c) 2017 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 @@ -23,40 +23,34 @@ # THE SOFTWARE. # # ###########################################################################*/ +"""This example illustrates how to use a :class:`ItemsSelectionDialog` widget +associated with a :class:`PlotWidget`. """ -Example to show the use of `ColorBarWidget` widget. -It can be associated to a plot. -In this exqmple the `ColorBarWidget` widget will display the colormap of the -active image. - -To change the active image slick on the image you want to set active. -""" - -__authors__ = ["H. Payno"] +__authors__ = ["P. Knobel"] __license__ = "MIT" -__date__ = "03/05/2017" - +__date__ = "28/06/2017" from silx.gui import qt -import numpy -from silx.gui.plot import Plot2D -from silx.gui.plot.ColorBar import ColorBarWidget - -image = numpy.exp(numpy.random.rand(100, 100) * 10) +from silx.gui.plot.PlotWidget import PlotWidget +from silx.gui.plot.ItemsSelectionDialog import ItemsSelectionDialog app = qt.QApplication([]) -plot = Plot2D() -colorbar = ColorBarWidget(parent=None, plot=plot) -colorbar.setLegend('my colormap') -colorbar.show() -plot.show() - -clm = plot.getDefaultColormap() -clm['normalization'] = 'log' -clm['name'] = 'viridis' -plot.addImage(data=image, colormap=clm, legend='image') -plot.setActiveImage('image') +pw = PlotWidget() +pw.addCurve([0, 1, 2], [3, 2, 1], "A curve") +pw.addScatter([0, 1, 2.5], [3, 2.5, 0.9], [8, 9, 72], "A scatter") +pw.addHistogram([0, 1, 2.5], [0, 1, 2, 3], "A histogram") +pw.addImage([[0, 1, 2], [3, 2, 1]], "An image") +pw.show() + +isd = ItemsSelectionDialog(plot=pw) +isd.setItemsSelectionMode(qt.QTableWidget.ExtendedSelection) +result = isd.exec_() +if result: + for item in isd.getSelectedItems(): + print(item.getLegend(), type(item)) +else: + print("Selection cancelled") app.exec_() diff --git a/examples/plotLimits.py b/examples/plotLimits.py new file mode 100644 index 0000000..0a39bc6 --- /dev/null +++ b/examples/plotLimits.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2016-2017 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 is an example to illustrate how to use axis synchronization +tool. +""" + +from silx.gui import qt +from silx.gui import plot +import numpy +import silx.test.utils + + +class ConstrainedViewPlot(qt.QMainWindow): + + def __init__(self): + qt.QMainWindow.__init__(self) + self.setWindowTitle("Plot with synchronized axes") + widget = qt.QWidget(self) + self.setCentralWidget(widget) + + layout = qt.QGridLayout() + widget.setLayout(layout) + + backend = "mpl" + + data = numpy.arange(100 * 100) + data = (data % 100) / 5.0 + data = numpy.sin(data) + data = silx.test.utils.add_gaussian_noise(data, mean=0.01) + data.shape = 100, 100 + + data1d = numpy.mean(data, axis=0) + + self.plot2d = plot.Plot2D(parent=widget, backend=backend) + self.plot2d.setGraphTitle("A pixel can't be too big") + self.plot2d.setInteractiveMode('pan') + self.plot2d.addImage(data) + self.plot2d.getXAxis().setRangeConstraints(minRange=10) + self.plot2d.getYAxis().setRangeConstraints(minRange=10) + + self.plot2d2 = plot.Plot2D(parent=widget, backend=backend) + self.plot2d2.setGraphTitle("The image can't be too small") + self.plot2d2.setInteractiveMode('pan') + self.plot2d2.addImage(data) + self.plot2d2.getXAxis().setRangeConstraints(maxRange=200) + self.plot2d2.getYAxis().setRangeConstraints(maxRange=200) + + self.plot1d = plot.Plot1D(parent=widget, backend=backend) + self.plot1d.setGraphTitle("The curve is clamped into the view") + self.plot1d.addCurve(x=numpy.arange(100), y=data1d, legend="mean") + self.plot1d.getXAxis().setLimitsConstraints(minPos=0, maxPos=100) + self.plot1d.getYAxis().setLimitsConstraints(minPos=data1d.min(), maxPos=data1d.max()) + + self.plot1d2 = plot.Plot1D(parent=widget, backend=backend) + self.plot1d2.setGraphTitle("Only clamp y-axis") + self.plot1d2.setInteractiveMode('pan') + self.plot1d2.addCurve(x=numpy.arange(100), y=data1d, legend="mean") + self.plot1d2.getYAxis().setLimitsConstraints(minPos=data1d.min(), maxPos=data1d.max()) + + layout.addWidget(self.plot2d, 0, 0) + layout.addWidget(self.plot1d, 0, 1) + layout.addWidget(self.plot2d2, 1, 0) + layout.addWidget(self.plot1d2, 1, 1) + + +if __name__ == "__main__": + app = qt.QApplication([]) + window = ConstrainedViewPlot() + window.setVisible(True) + app.exec_() diff --git a/examples/plotUpdateFromThread.py b/examples/plotUpdateFromThread.py new file mode 100644 index 0000000..d36bc48 --- /dev/null +++ b/examples/plotUpdateFromThread.py @@ -0,0 +1,128 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2017 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 silx.gui.plot widget from a thread. + +The problem is that plot and GUI methods should be called from the main thread. +To safely update the plot from another thread, one need to make the update +asynchronously from the main thread. +In this example, this is achieved through a Qt signal. + +In this example we create a subclass of :class:`silx.gui.plot.Plot1D` +that adds a thread-safe method to add curves: +:meth:`ThreadSafePlot1D.addCurveThreadSafe`. +This thread-safe method is then called from a thread to update the plot. +""" + +__authors__ = ["T. Vincent"] +__license__ = "MIT" +__date__ = "05/09/2017" + + +import threading +import time + +import numpy + +from silx.gui import qt +from silx.gui.plot import Plot1D + + +class ThreadSafePlot1D(Plot1D): + """Add a thread-safe :meth:`addCurveThreadSafe` method to Plot1D. + """ + + _sigAddCurve = qt.Signal(tuple, dict) + """Signal used to perform addCurve in the main thread. + + It takes args and kwargs as arguments. + """ + + def __init__(self, parent=None): + super(ThreadSafePlot1D, self).__init__(parent) + # Connect the signal to the method actually calling addCurve + self._sigAddCurve.connect(self.__addCurve) + + def __addCurve(self, args, kwargs): + """Private method calling addCurve from _sigAddCurve""" + self.addCurve(*args, **kwargs) + + def addCurveThreadSafe(self, *args, **kwargs): + """Thread-safe version of :meth:`silx.gui.plot.Plot.addCurve` + + This method takes the same arguments as Plot.addCurve. + + WARNING: This method does not return a value as opposed to Plot.addCurve + """ + self._sigAddCurve.emit(args, kwargs) + + +class UpdateThread(threading.Thread): + """Thread updating the curve of a :class:`ThreadSafePlot1D` + + :param plot1d: The ThreadSafePlot1D 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) + self.plot1d.addCurveThreadSafe( + numpy.arange(1000), numpy.random.random(1000), resetzoom=False) + + def stop(self): + """Stop the update thread""" + self.running = False + self.join(2) + + +def main(): + global app + app = qt.QApplication([]) + + # Create a ThreadSafePlot1D, set its limits and display it + plot1d = ThreadSafePlot1D() + plot1d.setLimits(0., 1000., 0., 1.) + plot1d.show() + + # Create the thread that calls ThreadSafePlot1D.addCurveThreadSafe + updateThread = UpdateThread(plot1d) + updateThread.start() # Start updating the plot + + app.exec_() + + updateThread.stop() # Stop updating the plot + + +if __name__ == '__main__': + main() diff --git a/examples/plotWidget.py b/examples/plotWidget.py new file mode 100644 index 0000000..698fe56 --- /dev/null +++ b/examples/plotWidget.py @@ -0,0 +1,121 @@ +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2017 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 shows how to subclass :class:`PlotWidget` to tune its tools. + +It subclasses a :class:`silx.gui.plot.PlotWidget` and adds toolbars and +a colorbar by using pluggable widgets: + +- QAction from :mod:`silx.gui.plot.actions` +- QToolButton from :mod:`silx.gui.plot.PlotToolButtons` +- QToolBar from :mod:`silx.gui.plot.PlotTools` +- :class:`ColorBarWidget` from :mod:`silx.gui.plot.ColorBar`. +""" + +__authors__ = ["T. Vincent"] +__license__ = "MIT" +__date__ = "05/09/2017" + +import numpy + +from silx.gui import qt +import silx.gui.plot + +from silx.gui.plot import actions # QAction to use with PlotWidget +from silx.gui.plot import PlotToolButtons # QToolButton to use with PlotWidget +from silx.gui.plot.PlotTools import LimitsToolBar +from silx.gui.plot.ColorBar import ColorBarWidget + +class MyPlotWidget(silx.gui.plot.PlotWidget): + """PlotWidget with an ad hoc toolbar and a colorbar""" + + def __init__(self, parent=None): + super(MyPlotWidget, self).__init__(parent) + + # Add a tool bar to PlotWidget + toolBar = qt.QToolBar("Plot Tools", self) + self.addToolBar(toolBar) + + # Add actions from silx.gui.plot.action to the toolbar + resetZoomAction = actions.control.ResetZoomAction(self, self) + toolBar.addAction(resetZoomAction) + + # Add tool buttons from silx.gui.plot.PlotToolButtons + aspectRatioButton = PlotToolButtons.AspectToolButton( + parent=self, plot=self) + toolBar.addWidget(aspectRatioButton) + + # Add limits tool bar from silx.gui.plot.PlotTools + limitsToolBar = LimitsToolBar(parent=self, plot=self) + self.addToolBar(qt.Qt.BottomToolBarArea, limitsToolBar) + + self._initColorBar() + + def _initColorBar(self): + """Create the ColorBarWidget and add it to the PlotWidget""" + + # Add a colorbar on the right side + colorBar = ColorBarWidget(parent=self, plot=self) + + # Make ColorBarWidget background white by changing its palette + colorBar.setAutoFillBackground(True) + palette = colorBar.palette() + palette.setColor(qt.QPalette.Background, qt.Qt.white) + palette.setColor(qt.QPalette.Window, qt.Qt.white) + colorBar.setPalette(palette) + + # Add the ColorBarWidget by changing PlotWidget's central widget + gridLayout = qt.QGridLayout() + gridLayout.setSpacing(0) + gridLayout.setContentsMargins(0, 0, 0, 0) + plot = self.getWidgetHandle() # Get the widget rendering the plot + gridLayout.addWidget(plot, 0, 0) + gridLayout.addWidget(colorBar, 0, 1) + gridLayout.setRowStretch(0, 1) + gridLayout.setColumnStretch(0, 1) + centralWidget = qt.QWidget() + centralWidget.setLayout(gridLayout) + self.setCentralWidget(centralWidget) + + +def main(): + global app + app = qt.QApplication([]) + + # Create the ad hoc plot widget and change its default colormap + plot = MyPlotWidget() + plot.getDefaultColormap().setName('viridis') + plot.show() + + # 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) + + app.exec_() + + +if __name__ == '__main__': + main() diff --git a/examples/printPreview.py b/examples/printPreview.py new file mode 100755 index 0000000..187ad84 --- /dev/null +++ b/examples/printPreview.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2016-2017 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 how to add a print preview tool button to any plot +widget inheriting :class:`PlotWidget`. + +Three plot widgets are instantiated. One of them uses a standalone +:class:`PrintPreviewToolButton`, while the other two use a +:class:`SingletonPrintPreviewToolButton` which allows them to send their content +to the same print preview page. +""" +__authors__ = ["P. Knobel"] +__license__ = "MIT" +__date__ = "25/07/2017" + +import numpy + +from silx.gui import qt +from silx.gui.plot import PlotWidget +from silx.gui.plot import PrintPreviewToolButton + +app = qt.QApplication([]) + +x = numpy.arange(1000) + +# first widget has a standalone print preview action +pw1 = PlotWidget() +pw1.setWindowTitle("Widget 1 with standalone print preview") +toolbar1 = qt.QToolBar(pw1) +toolbutton1 = PrintPreviewToolButton.PrintPreviewToolButton(parent=toolbar1, + plot=pw1) +pw1.addToolBar(toolbar1) +toolbar1.addWidget(toolbutton1) +pw1.show() +pw1.addCurve(x, numpy.tan(x * 2 * numpy.pi / 1000)) + +# next two plots share a common print preview +pw2 = PlotWidget() +pw2.setWindowTitle("Widget 2 with shared print preview") +toolbar2 = qt.QToolBar(pw2) +toolbutton2 = PrintPreviewToolButton.SingletonPrintPreviewToolButton( + parent=toolbar2, plot=pw2) +pw2.addToolBar(toolbar2) +toolbar2.addWidget(toolbutton2) +pw2.show() +pw2.addCurve(x, numpy.sin(x * 2 * numpy.pi / 1000)) + + +pw3 = PlotWidget() +pw3.setWindowTitle("Widget 3 with shared print preview") +toolbar3 = qt.QToolBar(pw3) +toolbutton3 = PrintPreviewToolButton.SingletonPrintPreviewToolButton( + parent=toolbar3, plot=pw3) +pw3.addToolBar(toolbar3) +toolbar3.addWidget(toolbutton3) +pw3.show() +pw3.addCurve(x, numpy.cos(x * 2 * numpy.pi / 1000)) + + +app.exec_() diff --git a/examples/shiftPlotAction.py b/examples/shiftPlotAction.py index ca48300..7cac08c 100755 --- a/examples/shiftPlotAction.py +++ b/examples/shiftPlotAction.py @@ -37,7 +37,7 @@ __date__ = "12/01/2017" import sys from silx.gui import qt from silx.gui.plot import PlotWindow -from silx.gui.plot.PlotActions import PlotAction +from silx.gui.plot.actions import PlotAction class ShiftUpAction(PlotAction): diff --git a/examples/syncaxis.py b/examples/syncaxis.py new file mode 100644 index 0000000..1033738 --- /dev/null +++ b/examples/syncaxis.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python +# coding: utf-8 +# /*########################################################################## +# +# Copyright (c) 2016-2017 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 is an example to illustrate how to use axis synchronization +tool. +""" + +from silx.gui import qt +from silx.gui import plot +import numpy +import silx.test.utils +from silx.gui.plot.utils.axis import SyncAxes + + +class SyncPlot(qt.QMainWindow): + + def __init__(self): + qt.QMainWindow.__init__(self) + self.setWindowTitle("Plot with synchronized axes") + widget = qt.QWidget(self) + self.setCentralWidget(widget) + + layout = qt.QGridLayout() + widget.setLayout(layout) + + backend = "mpl" + self.plot2d = plot.Plot2D(parent=widget, backend=backend) + self.plot2d.setInteractiveMode('pan') + self.plot1d_x1 = plot.Plot1D(parent=widget, backend=backend) + self.plot1d_x2 = plot.PlotWidget(parent=widget, backend=backend) + self.plot1d_y1 = plot.Plot1D(parent=widget, backend=backend) + self.plot1d_y2 = plot.PlotWidget(parent=widget, backend=backend) + + data = numpy.arange(100 * 100) + data = (data % 100) / 5.0 + data = numpy.sin(data) + data = silx.test.utils.add_gaussian_noise(data, mean=0.01) + data.shape = 100, 100 + + self.plot2d.addImage(data) + self.plot1d_x1.addCurve(x=numpy.arange(100), y=numpy.mean(data, axis=0), legend="mean") + self.plot1d_x1.addCurve(x=numpy.arange(100), y=numpy.max(data, axis=0), legend="max") + self.plot1d_x1.addCurve(x=numpy.arange(100), y=numpy.min(data, axis=0), legend="min") + self.plot1d_x2.addCurve(x=numpy.arange(100), y=numpy.std(data, axis=0)) + + self.plot1d_y1.addCurve(y=numpy.arange(100), x=numpy.mean(data, axis=1), legend="mean") + self.plot1d_y1.addCurve(y=numpy.arange(100), x=numpy.max(data, axis=1), legend="max") + self.plot1d_y1.addCurve(y=numpy.arange(100), x=numpy.min(data, axis=1), legend="min") + self.plot1d_y2.addCurve(y=numpy.arange(100), x=numpy.std(data, axis=1)) + + self.constraint1 = SyncAxes([self.plot2d.getXAxis(), self.plot1d_x1.getXAxis(), self.plot1d_x2.getXAxis()]) + self.constraint2 = SyncAxes([self.plot2d.getYAxis(), self.plot1d_y1.getYAxis(), self.plot1d_y2.getYAxis()]) + self.constraint3 = SyncAxes([self.plot1d_x1.getYAxis(), self.plot1d_y1.getXAxis()]) + self.constraint4 = SyncAxes([self.plot1d_x2.getYAxis(), self.plot1d_y2.getXAxis()]) + + layout.addWidget(self.plot2d, 0, 0) + layout.addWidget(self.createCenteredLabel(u"↓↑"), 1, 0) + layout.addWidget(self.plot1d_x1, 2, 0) + layout.addWidget(self.createCenteredLabel(u"↓↑"), 3, 0) + layout.addWidget(self.plot1d_x2, 4, 0) + layout.addWidget(self.createCenteredLabel(u"→\n←"), 0, 1) + layout.addWidget(self.plot1d_y1, 0, 2) + layout.addWidget(self.createCenteredLabel(u"→\n←"), 0, 3) + layout.addWidget(self.plot1d_y2, 0, 4) + layout.addWidget(self.createCenteredLabel(u"↗↙"), 2, 2) + layout.addWidget(self.createCenteredLabel(u"↗↙"), 4, 4) + + def createCenteredLabel(self, text): + label = qt.QLabel(self) + label.setAlignment(qt.Qt.AlignCenter) + label.setText(text) + return label + + +if __name__ == "__main__": + app = qt.QApplication([]) + window = SyncPlot() + window.setVisible(True) + app.exec_() diff --git a/examples/viewer3DVolume.py b/examples/viewer3DVolume.py index 7a95d8a..82022f9 100644 --- a/examples/viewer3DVolume.py +++ b/examples/viewer3DVolume.py @@ -101,6 +101,19 @@ def load(filename): return data +def default_isolevel(data): + """Compute a default isosurface level: mean + 1 std + + :param numpy.ndarray data: The data to process + :rtype: float + """ + data = data[numpy.isfinite(data)] + if len(data) == 0: + return 0 + else: + return numpy.mean(data) + numpy.std(data) + + def main(argv=None): # Parse input arguments parser = argparse.ArgumentParser( @@ -171,11 +184,11 @@ def main(argv=None): else: # Create dummy data _logger.warning('Not data file provided, creating dummy data') - size = 128 - z, y, x = numpy.mgrid[0:size, 0:size, 0:size] - data = numpy.asarray( - size**2 - ((x-size/2)**2 + (y-size/2)**2 + (z-size/2)**2), - dtype='float32') + coords = numpy.linspace(-10, 10, 64) + z = coords.reshape(-1, 1, 1) + y = coords.reshape(1, -1, 1) + x = coords.reshape(1, 1, -1) + data = numpy.sin(x * y * z) / (x * y * z) # Set ScalarFieldView data window.setData(data) @@ -195,9 +208,7 @@ def main(argv=None): window.addIsosurface(args.level, '#FF0000FF') else: # Add an iso-surface from a function - window.addIsosurface( - lambda data: numpy.mean(data) + numpy.std(data), - '#FF0000FF') + window.addIsosurface(default_isolevel, '#FF0000FF') window.show() return app.exec_() diff --git a/examples/spectoh5.py b/examples/writetoh5.py index f2f796b..5e89e48 100755..100644 --- a/examples/spectoh5.py +++ b/examples/writetoh5.py @@ -2,7 +2,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2004-2016 European Synchrotron Radiation Facility +# Copyright (c) 2004-2017 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 @@ -23,7 +23,7 @@ # THE SOFTWARE. # # ###########################################################################*/ -"""This script converts SPEC data files to HDF5 files. +"""This script converts a supported data file (SPEC, EDF,...) to a HDF5 file. By default, it creates a new output file or fails if the output file given on the command line already exist, but the user can choose to overwrite @@ -39,14 +39,14 @@ possible to specify a different target path. __authors__ = ["P. Knobel"] __license__ = "MIT" -__date__ = "18/10/2016" +__date__ = "12/09/2016" import argparse -from silx.io.spectoh5 import write_spec_to_h5 +from silx.io.convert import write_to_h5 parser = argparse.ArgumentParser(description=__doc__) -parser.add_argument('spec_path', - help='Path to input SPEC data file') +parser.add_argument('input_path', + help='Path to input data file') parser.add_argument('h5_path', help='Path to output HDF5 file') parser.add_argument('-t', '--target-path', default="/", @@ -82,7 +82,7 @@ else: # by default, use "write" mode and fail if file already exists mode = "w-" -write_spec_to_h5(args.spec_path, args.h5_path, - h5path=args.target_path, - mode=mode, - overwrite_data=args.overwrite_data) +write_to_h5(args.input_path, args.h5_path, + h5path=args.target_path, + mode=mode, + overwrite_data=args.overwrite_data) |