summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/animatedicons.py155
-rw-r--r--examples/customHdf5TreeModel.py290
-rw-r--r--examples/fft.pngbin0 -> 1432 bytes
-rwxr-xr-xexamples/fftPlotAction.py194
-rwxr-xr-xexamples/hdf5widget.py753
-rw-r--r--examples/icons.py110
-rwxr-xr-xexamples/imageview.py153
-rw-r--r--examples/periodicTable.py81
-rw-r--r--examples/plot3dContextMenu.py112
-rw-r--r--examples/plotContextMenu.py100
-rwxr-xr-xexamples/plotItemsSelector.py56
-rw-r--r--examples/plotLimits.py93
-rw-r--r--examples/plotUpdateFromThread.py128
-rw-r--r--examples/plotWidget.py121
-rwxr-xr-xexamples/printPreview.py82
-rw-r--r--examples/scatterMask.py153
-rwxr-xr-xexamples/shiftPlotAction.py113
-rwxr-xr-xexamples/simplewidget.py108
-rw-r--r--examples/stackView.py58
-rw-r--r--examples/syncaxis.py101
-rw-r--r--examples/viewer3DVolume.py212
-rw-r--r--examples/writetoh5.py88
22 files changed, 3261 insertions, 0 deletions
diff --git a/examples/animatedicons.py b/examples/animatedicons.py
new file mode 100644
index 0000000..5a5cad6
--- /dev/null
+++ b/examples/animatedicons.py
@@ -0,0 +1,155 @@
+#!/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.
+#
+# ###########################################################################*/
+"""
+Display available project icons using Qt.
+"""
+from silx.gui import qt
+import silx.gui.icons
+import functools
+
+
+class AnimatedToolButton(qt.QToolButton):
+ """ToolButton which support animated icons"""
+
+ def __init__(self, parent=None):
+ super(AnimatedToolButton, self).__init__(parent)
+ self.__animatedIcon = None
+
+ def setIcon(self, icon):
+ if isinstance(icon, silx.gui.icons.AbstractAnimatedIcon):
+ self._setAnimatedIcon(icon)
+ else:
+ self._setAnimatedIcon(None)
+ super(AnimatedToolButton, self).setIcon(icon)
+
+ def _setAnimatedIcon(self, icon):
+ if self.__animatedIcon is not None:
+ self.__animatedIcon.unregister(self)
+ self.__animatedIcon.iconChanged.disconnect(self.__updateIcon)
+ self.__animatedIcon = icon
+ if self.__animatedIcon is not None:
+ self.__animatedIcon.register(self)
+ self.__animatedIcon.iconChanged.connect(self.__updateIcon)
+ i = self.__animatedIcon.currentIcon()
+ else:
+ i = qt.QIcon()
+ super(AnimatedToolButton, self).setIcon(i)
+
+ def __updateIcon(self, icon):
+ super(AnimatedToolButton, self).setIcon(icon)
+
+ def icon(self):
+ if self.__animatedIcon is not None:
+ return self.__animatedIcon
+ else:
+ return super(AnimatedToolButton, self).icon()
+
+
+class AnimatedIconPreview(qt.QMainWindow):
+
+ def __init__(self, *args, **kwargs):
+ qt.QMainWindow.__init__(self, *args, **kwargs)
+
+ widget = qt.QWidget(self)
+ self.iconPanel = self.createIconPanel(widget)
+ self.sizePanel = self.createSizePanel(widget)
+
+ layout = qt.QVBoxLayout(widget)
+ layout.addWidget(self.sizePanel)
+ layout.addWidget(self.iconPanel)
+ layout.addStretch()
+ self.setCentralWidget(widget)
+
+ def createSizePanel(self, parent):
+ group = qt.QButtonGroup()
+ group.setExclusive(True)
+ panel = qt.QWidget(parent)
+ panel.setLayout(qt.QHBoxLayout())
+
+ buttons = {}
+ for size in [16, 24, 32]:
+ button = qt.QPushButton("%spx" % size, panel)
+ button.clicked.connect(functools.partial(self.setIconSize, size))
+ button.setCheckable(True)
+ panel.layout().addWidget(button)
+ group.addButton(button)
+ buttons[size] = button
+
+ self.__sizeGroup = group
+ buttons[24].setChecked(True)
+ return panel
+
+ def createIconPanel(self, parent):
+ panel = qt.QWidget(parent)
+ layout = qt.QVBoxLayout()
+ panel.setLayout(layout)
+
+ self.tools = []
+
+ # wait icon
+ icon = silx.gui.icons.getWaitIcon()
+ tool = AnimatedToolButton(panel)
+ tool.setIcon(icon)
+ tool.setText("getWaitIcon")
+ tool.setToolButtonStyle(qt.Qt.ToolButtonTextBesideIcon)
+ self.tools.append(tool)
+
+ icon = silx.gui.icons.getAnimatedIcon("process-working")
+ tool = AnimatedToolButton(panel)
+ tool.setIcon(icon)
+ tool.setText("getAnimatedIcon")
+ tool.setToolButtonStyle(qt.Qt.ToolButtonTextBesideIcon)
+ self.tools.append(tool)
+
+ icon = silx.gui.icons.MovieAnimatedIcon("process-working", self)
+ tool = AnimatedToolButton(panel)
+ tool.setIcon(icon)
+ tool.setText("MovieAnimatedIcon")
+ tool.setToolButtonStyle(qt.Qt.ToolButtonTextBesideIcon)
+ self.tools.append(tool)
+
+ icon = silx.gui.icons.MultiImageAnimatedIcon("process-working", self)
+ tool = AnimatedToolButton(panel)
+ tool.setIcon(icon)
+ tool.setText("MultiImageAnimatedIcon")
+ tool.setToolButtonStyle(qt.Qt.ToolButtonTextBesideIcon)
+ self.tools.append(tool)
+
+ for t in self.tools:
+ layout.addWidget(t)
+
+ return panel
+
+ def setIconSize(self, size):
+ for tool in self.tools:
+ tool.setIconSize(qt.QSize(size, size))
+
+
+if __name__ == "__main__":
+ app = qt.QApplication([])
+ window = AnimatedIconPreview()
+ window.setVisible(True)
+ app.exec_()
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/fft.png b/examples/fft.png
new file mode 100644
index 0000000..2464580
--- /dev/null
+++ b/examples/fft.png
Binary files differ
diff --git a/examples/fftPlotAction.py b/examples/fftPlotAction.py
new file mode 100755
index 0000000..66ecfbd
--- /dev/null
+++ b/examples/fftPlotAction.py
@@ -0,0 +1,194 @@
+#!/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 a simple example of how to create a PlotWindow with a custom
+PlotAction added to the toolbar.
+
+The action computes the FFT of all curves and plots their amplitude spectrum.
+It also performs the reverse transform.
+
+This example illustrates:
+ - how to create a checkable action
+ - how to store user info with a curve in a PlotWindow
+ - how to modify the graph title and axes labels
+ - how to add your own icon as a PNG file
+
+See shiftPlotAction.py for a simpler example with more basic comments.
+
+"""
+__authors__ = ["P. Knobel"]
+__license__ = "MIT"
+__date__ = "27/06/2017"
+
+import numpy
+import os
+import sys
+
+from silx.gui import qt
+from silx.gui.plot import PlotWindow
+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
+scriptdir = os.path.dirname(os.path.realpath(__file__))
+my_icon = os.path.join(scriptdir, "fft.png")
+
+
+class FftAction(PlotAction):
+ """QAction performing a Fourier transform on all curves when checked,
+ and reverse transform when unchecked.
+
+ :param plot: PlotWindow on which to operate
+ :param parent: See documentation of :class:`QAction`
+ """
+ def __init__(self, plot, parent=None):
+ PlotAction.__init__(
+ self,
+ plot,
+ icon=qt.QIcon(my_icon),
+ text='FFT',
+ tooltip='Perform Fast Fourier Transform on all curves',
+ triggered=self.fftAllCurves,
+ checkable=True,
+ parent=parent)
+
+ def _rememberGraphLabels(self):
+ """Store labels and title as attributes"""
+ self.original_title = self.plot.getGraphTitle()
+ 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
+ using a Fast Fourier Transform, replace all curves with their
+ amplitude spectra.
+
+ When un-checking the button, do the reverse transform.
+
+ :param checked: Boolean parameter signaling whether the action
+ has been checked or unchecked.
+ """
+ allCurves = self.plot.getAllCurves(withhidden=True)
+
+ if checked:
+ # remember original labels
+ self._rememberGraphLabels()
+ # change them
+ self.plot.setGraphTitle("Amplitude spectrum")
+ self.plot.getXAxis().setLabel("Frequency")
+ self.plot.getYAxis().setLabel("Amplitude")
+ else:
+ # restore original labels
+ self.plot.setGraphTitle(self.original_title)
+ self.plot.getXAxis().setLabel(self.original_xlabel)
+ self.plot.getYAxis().setLabel(self.original_ylabel)
+
+ self.plot.clearCurves()
+
+ for curve in allCurves:
+ x = curve.getXData()
+ y = curve.getYData()
+ legend = curve.getLegend()
+ info = curve.getInfo()
+ if info is None:
+ info = {}
+
+ if checked:
+ # FAST FOURIER TRANSFORM
+ fft_y = numpy.fft.fft(y)
+ # amplitude spectrum
+ A = numpy.abs(fft_y)
+
+ # sampling frequency (samples per X unit)
+ Fs = len(x) / (max(x) - min(x))
+ # frequency array (abscissa of new curve)
+ F = [k * Fs / len(x) for k in range(len(A))]
+
+ # we need to store the complete transform (complex data) to be
+ # able to perform the reverse transform.
+ info["complex fft"] = fft_y
+ info["original x"] = x
+
+ # plot the amplitude spectrum
+ self.plot.addCurve(F, A, legend="FFT of " + legend,
+ info=info)
+
+ else:
+ # INVERSE FFT
+ fft_y = info["complex fft"]
+ # we keep only the real part because we know the imaginary
+ # part is 0 (our original data was real numbers)
+ y1 = numpy.real(numpy.fft.ifft(fft_y))
+
+ # recover original info
+ x1 = info["original x"]
+ legend1 = legend[7:] # remove "FFT of "
+
+ # remove restored data from info dict
+ for key in ["complex fft", "original x"]:
+ del info[key]
+
+ # plot the original data
+ self.plot.addCurve(x1, y1, legend=legend1,
+ info=info)
+
+ self.plot.resetZoom()
+
+
+app = qt.QApplication([])
+
+sys.excepthook = qt.exceptionHandler
+
+plotwin = PlotWindow(control=True)
+toolbar = qt.QToolBar("My toolbar")
+plotwin.addToolBar(toolbar)
+
+myaction = FftAction(plotwin)
+toolbar.addAction(myaction)
+
+# x range: 0 -- 10 (1000 points)
+x = numpy.arange(1000) * 0.01
+
+twopi = 2 * numpy.pi
+# Sum of sine functions with frequencies 3, 20 and 42 Hz
+y1 = numpy.sin(twopi * 3 * x) + 1.5 * numpy.sin(twopi * 20 * x) + 2 * numpy.sin(twopi * 42 * x)
+# Cosine with frequency 7 Hz and phase pi / 3
+y2 = numpy.cos(twopi * 7 * (x - numpy.pi / 3))
+# 5 periods of square wave, amplitude 2
+y3 = numpy.zeros_like(x)
+for i in [0, 2, 4, 6, 8]:
+ y3[i * len(x) / 10:(i + 1) * len(x) / 10] = 2
+
+plotwin.addCurve(x, y1, legend="sin")
+plotwin.addCurve(x, y2, legend="cos")
+plotwin.addCurve(x, y3, legend="square wave")
+
+plotwin.setGraphTitle("Original data")
+plotwin.getYAxis().setLabel("amplitude")
+plotwin.getXAxis().setLabel("time")
+
+plotwin.show()
+app.exec_()
+sys.excepthook = sys.__excepthook__
diff --git a/examples/hdf5widget.py b/examples/hdf5widget.py
new file mode 100755
index 0000000..95607ff
--- /dev/null
+++ b/examples/hdf5widget.py
@@ -0,0 +1,753 @@
+#!/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
+
+.. note:: This module has a dependency on the `h5py <http://www.h5py.org/>`_
+ library, which is not a mandatory dependency for `silx`. You might need
+ to install it if you don't already have it.
+"""
+
+import logging
+import sys
+import tempfile
+import numpy
+
+logging.basicConfig()
+_logger = logging.getLogger("hdf5widget")
+"""Module logger"""
+
+try:
+ # it should be loaded before h5py
+ import hdf5plugin # noqa
+except ImportError:
+ message = "Module 'hdf5plugin' is not installed. It supports some hdf5"\
+ + " compressions. You can install it using \"pip install hdf5plugin\"."
+ _logger.warning(message)
+import h5py
+
+import silx.gui.hdf5
+import silx.utils.html
+from silx.gui import qt
+from silx.gui.data.DataViewerFrame import DataViewerFrame
+from silx.gui.widgets.ThreadPoolPushButton import ThreadPoolPushButton
+
+try:
+ import fabio
+except ImportError:
+ fabio = None
+
+_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
+
+
+def get_hdf5_with_all_links():
+ global _file_cache
+ ID = "alllinks"
+ 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("group")
+ g.create_dataset("dataset", data=numpy.int64(10))
+ h5.create_dataset("dataset", data=numpy.int64(10))
+
+ h5["hard_link_to_group"] = h5["/group"]
+ h5["hard_link_to_dataset"] = h5["/dataset"]
+
+ h5["soft_link_to_group"] = h5py.SoftLink("/group")
+ h5["soft_link_to_dataset"] = h5py.SoftLink("/dataset")
+ h5["soft_link_to_nothing"] = h5py.SoftLink("/foo/bar/2000")
+
+ alltypes_filename = get_hdf5_with_all_types()
+
+ h5["external_link_to_group"] = h5py.ExternalLink(alltypes_filename, "/arrays")
+ h5["external_link_to_dataset"] = h5py.ExternalLink(alltypes_filename, "/arrays/cube")
+ h5["external_link_to_nothing"] = h5py.ExternalLink(alltypes_filename, "/foo/bar/2000")
+ h5["external_link_to_missing_file"] = h5py.ExternalLink("missing_file.h5", "/")
+ h5.close()
+
+ _file_cache[ID] = tmp
+ return tmp.name
+
+
+def get_hdf5_with_1000_datasets():
+ global _file_cache
+ ID = "dataset1000"
+ 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("group")
+ for i in range(1000):
+ g.create_dataset("dataset%i" % i, data=numpy.int64(10))
+ h5.close()
+
+ _file_cache[ID] = tmp
+ return tmp.name
+
+
+def get_hdf5_with_10000_datasets():
+ global _file_cache
+ ID = "dataset10000"
+ 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("group")
+ for i in range(10000):
+ g.create_dataset("dataset%i" % i, data=numpy.int64(10))
+ h5.close()
+
+ _file_cache[ID] = tmp
+ return tmp.name
+
+
+def get_hdf5_with_100000_datasets():
+ global _file_cache
+ ID = "dataset100000"
+ 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("group")
+ for i in range(100000):
+ g.create_dataset("dataset%i" % i, data=numpy.int64(10))
+ h5.close()
+
+ _file_cache[ID] = tmp
+ return tmp.name
+
+
+def get_hdf5_with_recursive_links():
+ global _file_cache
+ ID = "recursive_links"
+ 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("group")
+ g.create_dataset("dataset", data=numpy.int64(10))
+ h5.create_dataset("dataset", data=numpy.int64(10))
+
+ h5["hard_recursive_link"] = h5["/group"]
+ g["recursive"] = h5["hard_recursive_link"]
+ h5["hard_link_to_dataset"] = h5["/dataset"]
+
+ h5["soft_link_to_group"] = h5py.SoftLink("/group")
+ h5["soft_link_to_link"] = h5py.SoftLink("/soft_link_to_group")
+ h5["soft_link_to_itself"] = h5py.SoftLink("/soft_link_to_itself")
+ h5.close()
+
+ _file_cache[ID] = tmp
+ return tmp.name
+
+
+def get_hdf5_with_external_recursive_links():
+ global _file_cache
+ ID = "external_recursive_links"
+ if ID in _file_cache:
+ return _file_cache[ID][0].name
+
+ tmp1 = tempfile.NamedTemporaryFile(prefix=ID + "_", suffix=".h5", delete=True)
+ tmp1.file.close()
+ h5_1 = h5py.File(tmp1.name, "w")
+
+ tmp2 = tempfile.NamedTemporaryFile(prefix=ID + "_", suffix=".h5", delete=True)
+ tmp2.file.close()
+ h5_2 = h5py.File(tmp2.name, "w")
+
+ g = h5_1.create_group("group")
+ g.create_dataset("dataset", data=numpy.int64(10))
+ h5_1["soft_link_to_group"] = h5py.SoftLink("/group")
+ h5_1["external_link_to_link"] = h5py.ExternalLink(tmp2.name, "/soft_link_to_group")
+ h5_1["external_link_to_recursive_link"] = h5py.ExternalLink(tmp2.name, "/external_link_to_recursive_link")
+ h5_1.close()
+
+ g = h5_2.create_group("group")
+ g.create_dataset("dataset", data=numpy.int64(10))
+ h5_2["soft_link_to_group"] = h5py.SoftLink("/group")
+ h5_2["external_link_to_link"] = h5py.ExternalLink(tmp1.name, "/soft_link_to_group")
+ h5_2["external_link_to_recursive_link"] = h5py.ExternalLink(tmp1.name, "/external_link_to_recursive_link")
+ h5_2.close()
+
+ _file_cache[ID] = (tmp1, tmp2)
+ return tmp1.name
+
+
+def get_hdf5_with_nxdata():
+ global _file_cache
+ ID = "nxdata"
+ 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")
+
+ # SCALARS
+ g0d = h5.create_group("scalars")
+
+ g0d0 = g0d.create_group("0D_scalar")
+ g0d0.attrs["NX_class"] = "NXdata"
+ g0d0.attrs["signal"] = "scalar"
+ g0d0.create_dataset("scalar", data=10)
+
+ g0d1 = g0d.create_group("2D_scalars")
+ g0d1.attrs["NX_class"] = "NXdata"
+ g0d1.attrs["signal"] = "scalars"
+ ds = g0d1.create_dataset("scalars", data=numpy.arange(3*10).reshape((3, 10)))
+ ds.attrs["interpretation"] = "scalar"
+
+ g0d1 = g0d.create_group("4D_scalars")
+ g0d1.attrs["NX_class"] = "NXdata"
+ g0d1.attrs["signal"] = "scalars"
+ ds = g0d1.create_dataset("scalars", data=numpy.arange(2*2*3*10).reshape((2, 2, 3, 10)))
+ ds.attrs["interpretation"] = "scalar"
+
+ # SPECTRA
+ g1d = h5.create_group("spectra")
+
+ g1d0 = g1d.create_group("1D_spectrum")
+ g1d0.attrs["NX_class"] = "NXdata"
+ g1d0.attrs["signal"] = "count"
+ g1d0.attrs["axes"] = "energy_calib"
+ g1d0.attrs["uncertainties"] = b"energy_errors",
+ g1d0.create_dataset("count", data=numpy.arange(10))
+ g1d0.create_dataset("energy_calib", data=(10, 5)) # 10 * idx + 5
+ g1d0.create_dataset("energy_errors", data=3.14*numpy.random.rand(10))
+
+ g1d1 = g1d.create_group("2D_spectra")
+ g1d1.attrs["NX_class"] = "NXdata"
+ g1d1.attrs["signal"] = "counts"
+ ds = g1d1.create_dataset("counts", data=numpy.arange(3*10).reshape((3, 10)))
+ ds.attrs["interpretation"] = "spectrum"
+
+ g1d2 = g1d.create_group("4D_spectra")
+ g1d2.attrs["NX_class"] = "NXdata"
+ g1d2.attrs["signal"] = "counts"
+ g1d2.attrs["axes"] = b"energy",
+ ds = g1d2.create_dataset("counts", data=numpy.arange(2*2*3*10).reshape((2, 2, 3, 10)))
+ ds.attrs["interpretation"] = "spectrum"
+ ds = g1d2.create_dataset("errors", data=4.5*numpy.random.rand(2, 2, 3, 10))
+ ds = g1d2.create_dataset("energy", data=5+10*numpy.arange(15),
+ shuffle=True, compression="gzip")
+ ds.attrs["long_name"] = "Calibrated energy"
+ ds.attrs["first_good"] = 3
+ ds.attrs["last_good"] = 12
+ g1d2.create_dataset("energy_errors", data=10*numpy.random.rand(15))
+
+ # IMAGES
+ g2d = h5.create_group("images")
+
+ g2d0 = g2d.create_group("2D_regular_image")
+ g2d0.attrs["NX_class"] = "NXdata"
+ g2d0.attrs["signal"] = "image"
+ g2d0.attrs["axes"] = b"rows_calib", b"columns_coordinates"
+ g2d0.create_dataset("image", data=numpy.arange(4*6).reshape((4, 6)))
+ ds = g2d0.create_dataset("rows_calib", data=(10, 5))
+ ds.attrs["long_name"] = "Calibrated Y"
+ g2d0.create_dataset("columns_coordinates", data=0.5+0.02*numpy.arange(6))
+
+ g2d1 = g2d.create_group("2D_irregular_data")
+ g2d1.attrs["NX_class"] = "NXdata"
+ g2d1.attrs["signal"] = "data"
+ g2d1.attrs["axes"] = b"rows_coordinates", b"columns_coordinates"
+ g2d1.create_dataset("data", data=numpy.arange(64*128).reshape((64, 128)))
+ g2d1.create_dataset("rows_coordinates", data=numpy.arange(64) + numpy.random.rand(64))
+ g2d1.create_dataset("columns_coordinates", data=numpy.arange(128) + 2.5 * numpy.random.rand(128))
+
+ g2d2 = g2d.create_group("3D_images")
+ g2d2.attrs["NX_class"] = "NXdata"
+ g2d2.attrs["signal"] = "images"
+ ds = g2d2.create_dataset("images", data=numpy.arange(2*4*6).reshape((2, 4, 6)))
+ ds.attrs["interpretation"] = "image"
+
+ g2d3 = g2d.create_group("5D_images")
+ g2d3.attrs["NX_class"] = "NXdata"
+ g2d3.attrs["signal"] = "images"
+ g2d3.attrs["axes"] = b"rows_coordinates", b"columns_coordinates"
+ ds = g2d3.create_dataset("images", data=numpy.arange(2*2*2*4*6).reshape((2, 2, 2, 4, 6)))
+ ds.attrs["interpretation"] = "image"
+ g2d3.create_dataset("rows_coordinates", data=5+10*numpy.arange(4))
+ g2d3.create_dataset("columns_coordinates", data=0.5+0.02*numpy.arange(6))
+
+ # SCATTER
+ g = h5.create_group("scatters")
+
+ gd0 = g.create_group("x_y_scatter")
+ gd0.attrs["NX_class"] = "NXdata"
+ gd0.attrs["signal"] = "y"
+ gd0.attrs["axes"] = b"x",
+ gd0.create_dataset("y", data=numpy.random.rand(128) - 0.5)
+ gd0.create_dataset("x", data=2*numpy.random.rand(128))
+ gd0.create_dataset("x_errors", data=0.05*numpy.random.rand(128))
+ gd0.create_dataset("errors", data=0.05*numpy.random.rand(128))
+
+ gd1 = g.create_group("x_y_value_scatter")
+ gd1.attrs["NX_class"] = "NXdata"
+ gd1.attrs["signal"] = "values"
+ gd1.attrs["axes"] = b"x", b"y"
+ gd1.create_dataset("values", data=3.14*numpy.random.rand(128))
+ gd1.create_dataset("y", data=numpy.random.rand(128))
+ gd1.create_dataset("y_errors", data=0.02*numpy.random.rand(128))
+ gd1.create_dataset("x", data=numpy.random.rand(128))
+ gd1.create_dataset("x_errors", data=0.02*numpy.random.rand(128))
+
+ # NDIM > 3
+ g = h5.create_group("cubes")
+
+ gd0 = g.create_group("3D_cube")
+ gd0.attrs["NX_class"] = "NXdata"
+ gd0.attrs["signal"] = "cube"
+ gd0.attrs["axes"] = b"img_idx", b"rows_coordinates", b"cols_coordinates"
+ gd0.create_dataset("cube", data=numpy.arange(4*5*6).reshape((4, 5, 6)))
+ gd0.create_dataset("img_idx", data=numpy.arange(4))
+ gd0.create_dataset("rows_coordinates", data=0.1*numpy.arange(5))
+ gd0.create_dataset("cols_coordinates", data=[0.2, 0.3]) # linear calibration
+
+ gd1 = g.create_group("5D")
+ gd1.attrs["NX_class"] = "NXdata"
+ gd1.attrs["signal"] = "hypercube"
+ gd1.create_dataset("hypercube",
+ data=numpy.arange(2*3*4*5*6).reshape((2, 3, 4, 5, 6)))
+
+ h5.close()
+
+ _file_cache[ID] = tmp
+ return tmp.name
+
+
+def get_edf_with_all_types():
+ global _file_cache
+ ID = "alltypesedf"
+ if ID in _file_cache:
+ return _file_cache[ID].name
+
+ tmp = tempfile.NamedTemporaryFile(prefix=ID + "_", suffix=".edf", delete=True)
+
+ header = fabio.fabioimage.OrderedDict()
+ header["integer"] = "10"
+ header["float"] = "10.5"
+ header["string"] = "Hi!"
+ header["integer_list"] = "10 20 50"
+ header["float_list"] = "1.1 3.14 500.12"
+ header["motor_pos"] = "10 2.5 a1"
+ header["motor_mne"] = "integer_position float_position named_position"
+
+ data = numpy.array([[10, 50], [50, 10]])
+ fabiofile = fabio.edfimage.EdfImage(data, header)
+ fabiofile.write(tmp.name)
+
+ _file_cache[ID] = tmp
+ return tmp.name
+
+
+def get_edf_with_100000_frames():
+ global _file_cache
+ ID = "frame100000"
+ if ID in _file_cache:
+ return _file_cache[ID].name
+
+ tmp = tempfile.NamedTemporaryFile(prefix=ID + "_", suffix=".edf", delete=True)
+
+ fabiofile = None
+ for framre_id in range(100000):
+ data = numpy.array([[framre_id, 50], [50, 10]])
+ if fabiofile is None:
+ header = fabio.fabioimage.OrderedDict()
+ header["nb_frames"] = "100000"
+ fabiofile = fabio.edfimage.EdfImage(data, header)
+ else:
+ header = fabio.fabioimage.OrderedDict()
+ header["frame_nb"] = framre_id
+ fabiofile.appendFrame(fabio.edfimage.Frame(data, header, framre_id))
+ fabiofile.write(tmp.name)
+
+ _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.__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)
+ self.__treeview.activated.connect(lambda index: self.displayEvent("activated", index))
+ self.__treeview.clicked.connect(lambda index: self.displayEvent("clicked", index))
+ self.__treeview.doubleClicked.connect(lambda index: self.displayEvent("doubleClicked", index))
+ self.__treeview.entered.connect(lambda index: self.displayEvent("entered", index))
+ self.__treeview.pressed.connect(lambda index: self.displayEvent("pressed", index))
+
+ self.__treeview.addContextMenuCallback(self.customContextMenu)
+ # lambda function will never be called cause we store it as weakref
+ self.__treeview.addContextMenuCallback(lambda event: None)
+ # you have to store it first
+ self.__store_lambda = lambda event: self.closeAndSyncCustomContextMenu(event)
+ self.__treeview.addContextMenuCallback(self.__store_lambda)
+
+ 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 displayEvent(self, eventName, index):
+ """Called to log event in widget
+ """
+ def formatKey(name, value):
+ name, value = silx.utils.html.escape(str(name)), silx.utils.html.escape(str(value))
+ return "<li><b>%s</b>: %s</li>" % (name, value)
+
+ text = "<html>"
+ text += "<h1>Event</h1>"
+ text += "<ul>"
+ text += formatKey("name", eventName)
+ text += formatKey("index", type(index))
+ text += "</ul>"
+
+ text += "<h1>Selected HDF5 objects</h1>"
+
+ for h5_obj in self.__treeview.selectedH5Nodes():
+ text += "<h2>HDF5 object</h2>"
+ text += "<ul>"
+ text += formatKey("local_filename", h5_obj.local_file.filename)
+ text += formatKey("local_basename", h5_obj.local_basename)
+ text += formatKey("local_name", h5_obj.local_name)
+ text += formatKey("real_filename", h5_obj.file.filename)
+ text += formatKey("real_basename", h5_obj.basename)
+ text += formatKey("real_name", h5_obj.name)
+
+ text += formatKey("obj", h5_obj.ntype)
+ text += formatKey("dtype", getattr(h5_obj, "dtype", None))
+ text += formatKey("shape", getattr(h5_obj, "shape", None))
+ text += formatKey("attrs", getattr(h5_obj, "attrs", None))
+ if hasattr(h5_obj, "attrs"):
+ text += "<ul>"
+ if len(h5_obj.attrs) == 0:
+ text += "<li>empty</li>"
+ for key, value in h5_obj.attrs.items():
+ text += formatKey(key, value)
+ text += "</ul>"
+ text += "</ul>"
+
+ text += "</html>"
+ self.__text.setHtml(text)
+
+ def useAsyncLoad(self, useAsync):
+ self.__asyncload = useAsync
+
+ def __fileCreated(self, filename):
+ if self.__asyncload:
+ self.__treeview.findHdf5TreeModel().insertFileAsync(filename)
+ else:
+ self.__treeview.findHdf5TreeModel().insertFile(filename)
+
+ def customContextMenu(self, event):
+ """Called to populate the context menu
+
+ :param silx.gui.hdf5.Hdf5ContextMenuEvent event: Event
+ containing expected information to populate the context menu
+ """
+ selectedObjects = event.source().selectedH5Nodes()
+ menu = event.menu()
+
+ hasDataset = False
+ for obj in selectedObjects:
+ if obj.ntype is h5py.Dataset:
+ hasDataset = True
+ break
+
+ if len(menu.children()):
+ menu.addSeparator()
+
+ if hasDataset:
+ action = qt.QAction("Do something on the datasets", event.source())
+ menu.addAction(action)
+
+ def closeAndSyncCustomContextMenu(self, event):
+ """Called to populate the context menu
+
+ :param silx.gui.hdf5.Hdf5ContextMenuEvent event: Event
+ containing expected information to populate the context menu
+ """
+ selectedObjects = event.source().selectedH5Nodes()
+ menu = event.menu()
+
+ if len(menu.children()):
+ menu.addSeparator()
+
+ for obj in selectedObjects:
+ if obj.ntype is h5py.File:
+ action = qt.QAction("Remove %s" % obj.local_filename, event.source())
+ action.triggered.connect(lambda: self.__treeview.findHdf5TreeModel().removeH5pyObject(obj.h5py_object))
+ menu.addAction(action)
+ action = qt.QAction("Synchronize %s" % obj.local_filename, event.source())
+ action.triggered.connect(lambda: self.__treeview.findHdf5TreeModel().synchronizeH5pyObject(obj.h5py_object))
+ menu.addAction(action)
+
+ 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 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.addItem("Containing all links", get_hdf5_with_all_links)
+ combo.addItem("Containing 1000 datasets", get_hdf5_with_1000_datasets)
+ combo.addItem("Containing 10000 datasets", get_hdf5_with_10000_datasets)
+ combo.addItem("Containing 100000 datasets", get_hdf5_with_100000_datasets)
+ combo.addItem("Containing recursive links", get_hdf5_with_recursive_links)
+ combo.addItem("Containing external recursive links", get_hdf5_with_external_recursive_links)
+ combo.addItem("Containing NXdata groups", get_hdf5_with_nxdata)
+ 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
+
+ asyncload = qt.QCheckBox("Async load", content)
+ asyncload.setChecked(self.__asyncload)
+ asyncload.toggled.connect(lambda: self.useAsyncLoad(asyncload.isChecked()))
+ content.layout().addWidget(asyncload)
+
+ content.layout().addStretch(1)
+
+ if fabio is not None:
+ content = qt.QGroupBox("Create EDF", panel)
+ content.setLayout(qt.QVBoxLayout())
+ panel.layout().addWidget(content)
+
+ combo = qt.QComboBox()
+ combo.addItem("Containing all types", get_edf_with_all_types)
+ combo.addItem("Containing 100000 datasets", get_edf_with_100000_frames)
+ combo.activated.connect(self.__edfComboChanged)
+ 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.__edfCombo = combo
+ self.__createEdfButton = button
+
+ content.layout().addStretch(1)
+
+ option = qt.QGroupBox("Tree options", panel)
+ option.setLayout(qt.QVBoxLayout())
+ panel.layout().addWidget(option)
+
+ sorting = qt.QCheckBox("Enable sorting", option)
+ sorting.setChecked(treeview.selectionMode() == qt.QAbstractItemView.MultiSelection)
+ sorting.toggled.connect(lambda: treeview.setSortingEnabled(sorting.isChecked()))
+ option.layout().addWidget(sorting)
+
+ multiselection = qt.QCheckBox("Multi-selection", option)
+ multiselection.setChecked(treeview.selectionMode() == qt.QAbstractItemView.MultiSelection)
+ switch_selection = lambda: treeview.setSelectionMode(
+ qt.QAbstractItemView.MultiSelection if multiselection.isChecked()
+ else qt.QAbstractItemView.SingleSelection)
+ multiselection.toggled.connect(switch_selection)
+ option.layout().addWidget(multiselection)
+
+ filedrop = qt.QCheckBox("Drop external file", option)
+ filedrop.setChecked(treeview.findHdf5TreeModel().isFileDropEnabled())
+ filedrop.toggled.connect(lambda: treeview.findHdf5TreeModel().setFileDropEnabled(filedrop.isChecked()))
+ option.layout().addWidget(filedrop)
+
+ filemove = qt.QCheckBox("Reorder files", option)
+ filemove.setChecked(treeview.findHdf5TreeModel().isFileMoveEnabled())
+ filemove.toggled.connect(lambda: treeview.findHdf5TreeModel().setFileMoveEnabled(filedrop.isChecked()))
+ option.layout().addWidget(filemove)
+
+ option.layout().addStretch(1)
+
+ option = qt.QGroupBox("Header options", panel)
+ option.setLayout(qt.QVBoxLayout())
+ panel.layout().addWidget(option)
+
+ autosize = qt.QCheckBox("Auto-size headers", option)
+ autosize.setChecked(treeview.header().hasAutoResizeColumns())
+ autosize.toggled.connect(lambda: treeview.header().setAutoResizeColumns(autosize.isChecked()))
+ option.layout().addWidget(autosize)
+
+ columnpopup = qt.QCheckBox("Popup to hide/show columns", option)
+ columnpopup.setChecked(treeview.header().hasHideColumnsPopup())
+ columnpopup.toggled.connect(lambda: treeview.header().setEnableHideColumnsPopup(columnpopup.isChecked()))
+ option.layout().addWidget(columnpopup)
+
+ define_columns = qt.QComboBox()
+ define_columns.addItem("Default columns", treeview.findHdf5TreeModel().COLUMN_IDS)
+ define_columns.addItem("Only name and Value", [treeview.findHdf5TreeModel().NAME_COLUMN, treeview.findHdf5TreeModel().VALUE_COLUMN])
+ define_columns.activated.connect(lambda index: treeview.header().setSections(define_columns.itemData(index)))
+ option.layout().addWidget(define_columns)
+
+ 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/icons.py b/examples/icons.py
new file mode 100644
index 0000000..a6f0ada
--- /dev/null
+++ b/examples/icons.py
@@ -0,0 +1,110 @@
+#!/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.
+#
+# ###########################################################################*/
+"""
+Display available project icons using Qt.
+"""
+import functools
+
+from silx.gui import qt
+import silx.gui.icons
+
+
+class IconPreview(qt.QMainWindow):
+
+ def __init__(self, *args, **kwargs):
+ qt.QMainWindow.__init__(self, *args, **kwargs)
+
+ widget = qt.QWidget(self)
+ self.iconPanel = self.createIconPanel(widget)
+ self.sizePanel = self.createSizePanel(widget)
+
+ layout = qt.QVBoxLayout()
+ widget.setLayout(layout)
+ # layout.setSizeConstraint(qt.QLayout.SetMinAndMaxSize)
+ layout.addWidget(self.sizePanel)
+ layout.addWidget(self.iconPanel)
+ layout.addStretch()
+ self.setCentralWidget(widget)
+
+ def createSizePanel(self, parent):
+ group = qt.QButtonGroup()
+ group.setExclusive(True)
+ panel = qt.QWidget(parent)
+ panel.setLayout(qt.QHBoxLayout())
+
+ for size in [16, 24, 32]:
+ button = qt.QPushButton("%spx" % size, panel)
+ button.clicked.connect(functools.partial(self.setIconSize, size))
+ button.setCheckable(True)
+ panel.layout().addWidget(button)
+ group.addButton(button)
+
+ self.__sizeGroup = group
+ button.setChecked(True)
+ return panel
+
+ def createIconPanel(self, parent):
+ panel = qt.QWidget(parent)
+ layout = qt.QGridLayout()
+ # layout.setSizeConstraint(qt.QLayout.SetMinAndMaxSize)
+ panel.setLayout(layout)
+
+ self.tools = []
+
+ import silx.resources
+
+ icons = silx.resources.list_dir("gui/icons")
+ # filter out sub-directories
+ 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
+ icons = set(icons)
+ # sort by names
+ icons = sorted(icons)
+
+ for i, icon_name in enumerate(icons):
+ col, line = i / 10, i % 10
+ icon = silx.gui.icons.getQIcon(icon_name)
+ tool = qt.QToolButton(panel)
+ tool.setIcon(icon)
+ tool.setIconSize(qt.QSize(32, 32))
+ tool.setToolTip(icon_name)
+ layout.addWidget(tool, col, line)
+ self.tools.append(tool)
+
+ return panel
+
+ def setIconSize(self, size):
+ for tool in self.tools:
+ tool.setIconSize(qt.QSize(size, size))
+
+
+if __name__ == "__main__":
+ app = qt.QApplication([])
+ window = IconPreview()
+ window.setVisible(True)
+ app.exec_()
diff --git a/examples/imageview.py b/examples/imageview.py
new file mode 100755
index 0000000..34595e2
--- /dev/null
+++ b/examples/imageview.py
@@ -0,0 +1,153 @@
+#!/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.
+#
+# ###########################################################################*/
+"""
+Example to show the use of `ImageView` widget. It can be used to open an EDF
+or TIFF file from the shell command line.
+
+To view an image file with the current installed silx library:
+``python examples/imageview.py <file to open>``
+To get help:
+``python examples/imageview.py -h``
+
+For developers with a git clone you can use it with the bootstrap
+To view an image file with the current installed silx library:
+
+``./bootstrap.py python examples/imageview.py <file to open>``
+"""
+from __future__ import division
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "18/10/2016"
+
+import logging
+from silx.gui.plot.ImageView import ImageViewMainWindow
+from silx.gui import qt
+import numpy
+
+logger = logging.getLogger(__name__)
+
+
+def main(argv=None):
+ """Display an image from a file in an :class:`ImageView` widget.
+
+ :param argv: list of command line arguments or None (the default)
+ to use sys.argv.
+ :type argv: list of str
+ :return: Exit status code
+ :rtype: int
+ :raises IOError: if no image can be loaded from the file
+ """
+ import argparse
+ import os.path
+
+ from silx.third_party.EdfFile import EdfFile
+
+ # Command-line arguments
+ parser = argparse.ArgumentParser(
+ description='Browse the images of an EDF file.')
+ parser.add_argument(
+ '-o', '--origin', nargs=2,
+ type=float, default=(0., 0.),
+ help="""Coordinates of the origin of the image: (x, y).
+ Default: 0., 0.""")
+ parser.add_argument(
+ '-s', '--scale', nargs=2,
+ type=float, default=(1., 1.),
+ help="""Scale factors applied to the image: (sx, sy).
+ Default: 1., 1.""")
+ parser.add_argument(
+ '-l', '--log', action="store_true",
+ help="Use logarithm normalization for colormap, default: Linear.")
+ parser.add_argument(
+ 'filename', nargs='?',
+ help='EDF filename of the image to open')
+ args = parser.parse_args(args=argv)
+
+ # Open the input file
+ if not args.filename:
+ logger.warning('No image file provided, displaying dummy data')
+ edfFile = None
+ data = numpy.arange(1024 * 1024.).reshape(1024, 1024)
+ nbFrames = 1
+
+ else:
+ if not os.path.isfile(args.filename):
+ raise IOError('No input file: %s' % args.filename)
+
+ else:
+ edfFile = EdfFile(args.filename)
+ data = edfFile.GetData(0)
+
+ nbFrames = edfFile.GetNumImages()
+ if nbFrames == 0:
+ raise IOError(
+ 'Cannot read image(s) from file: %s' % args.filename)
+
+ global app # QApplication must be global to avoid seg fault on quit
+ app = qt.QApplication([])
+ sys.excepthook = qt.exceptionHandler
+
+ mainWindow = ImageViewMainWindow()
+ mainWindow.setAttribute(qt.Qt.WA_DeleteOnClose)
+
+ if args.log: # Use log normalization by default
+ colormap = mainWindow.getDefaultColormap()
+ colormap['normalization'] = 'log'
+ mainWindow.setColormap(colormap)
+
+ mainWindow.setImage(data,
+ origin=args.origin,
+ scale=args.scale)
+
+ if edfFile is not None and nbFrames > 1:
+ # Add a toolbar for multi-frame EDF support
+ multiFrameToolbar = qt.QToolBar('Multi-frame')
+ multiFrameToolbar.addWidget(qt.QLabel(
+ 'Frame [0-%d]:' % (nbFrames - 1)))
+
+ spinBox = qt.QSpinBox()
+ spinBox.setRange(0, nbFrames - 1)
+
+ def updateImage(index):
+ mainWindow.setImage(edfFile.GetData(index),
+ origin=args.origin,
+ scale=args.scale,
+ reset=False)
+ spinBox.valueChanged[int].connect(updateImage)
+ multiFrameToolbar.addWidget(spinBox)
+
+ mainWindow.addToolBar(multiFrameToolbar)
+
+ mainWindow.show()
+ mainWindow.setFocus(qt.Qt.OtherFocusReason)
+
+ return app.exec_()
+
+
+if __name__ == "__main__":
+ import sys
+ sys.exit(main(argv=sys.argv[1:]))
diff --git a/examples/periodicTable.py b/examples/periodicTable.py
new file mode 100644
index 0000000..e329ef7
--- /dev/null
+++ b/examples/periodicTable.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+# coding: utf-8
+# /*##########################################################################
+#
+# 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
+# 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 a simple example of how to use the periodic table widgets,
+select elements and connect signals.
+"""
+import sys
+from silx.gui import qt
+from silx.gui.widgets import PeriodicTable
+
+a = qt.QApplication(sys.argv)
+sys.excepthook = qt.exceptionHandler
+a.lastWindowClosed.connect(a.quit)
+
+w = qt.QTabWidget()
+
+pt = PeriodicTable.PeriodicTable(w, selectable=True)
+pc = PeriodicTable.PeriodicCombo(w)
+pl = PeriodicTable.PeriodicList(w)
+
+pt.setSelection(['Fe', 'Si', 'Mt'])
+pl.setSelectedElements(['H', 'Be', 'F'])
+pc.setSelection("Li")
+
+
+def change_list(items):
+ print("New list selection:", [item.symbol for item in items])
+
+
+def change_combo(item):
+ print("New combo selection:", item.symbol)
+
+
+def click_table(item):
+ print("New table click: %s (%s)" % (item.name, item.subcategory))
+
+
+def change_table(items):
+ print("New table selection:", [item.symbol for item in items])
+
+
+pt.sigElementClicked.connect(click_table)
+pt.sigSelectionChanged.connect(change_table)
+pl.sigSelectionChanged.connect(change_list)
+pc.sigSelectionChanged.connect(change_combo)
+
+# move combo into container widget to preventing it from filling
+# the tab inside TabWidget
+comboContainer = qt.QWidget(w)
+comboContainer.setLayout(qt.QVBoxLayout())
+comboContainer.layout().addWidget(pc)
+
+w.addTab(pt, "PeriodicTable")
+w.addTab(pl, "PeriodicList")
+w.addTab(comboContainer, "PeriodicCombo")
+w.show()
+
+a.exec_()
+
diff --git a/examples/plot3dContextMenu.py b/examples/plot3dContextMenu.py
new file mode 100644
index 0000000..d33bb8f
--- /dev/null
+++ b/examples/plot3dContextMenu.py
@@ -0,0 +1,112 @@
+# 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 adds a context menu to a :class:`silx.gui.plot3d.ScalarFieldView`.
+
+This is done by adding a custom context menu to the :class:`Plot3DWidget`:
+
+- set the context menu policy to Qt.CustomContextMenu.
+- connect to the customContextMenuRequested signal.
+
+For more information on context menus, see Qt documentation.
+"""
+
+from __future__ import absolute_import, division, unicode_literals
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "03/10/2017"
+
+
+import logging
+
+import numpy
+
+from silx.gui import qt
+
+from silx.gui.plot3d.ScalarFieldView import ScalarFieldView
+from silx.gui.plot3d import actions
+
+logging.basicConfig()
+
+_logger = logging.getLogger(__name__)
+
+
+class ScalarFieldViewWithContextMenu(ScalarFieldView):
+ """Subclass ScalarFieldView to add a custom context menu to its 3D area."""
+
+ def __init__(self, parent=None):
+ super(ScalarFieldViewWithContextMenu, self).__init__(parent)
+ self.setWindowTitle("Right-click to open the context menu")
+
+ # Set Plot3DWidget custom context menu
+ self.getPlot3DWidget().setContextMenuPolicy(qt.Qt.CustomContextMenu)
+ self.getPlot3DWidget().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(actions.mode.PanAction(
+ parent=menu, plot3d=self.getPlot3DWidget()))
+ menu.addAction(actions.mode.RotateArcballAction(
+ parent=menu, plot3d=self.getPlot3DWidget()))
+ menu.addSeparator()
+ menu.addAction(actions.io.CopyAction(
+ parent=menu, plot3d=self.getPlot3DWidget()))
+
+ # Displaying the context menu at the mouse position requires
+ # a global position.
+ # The position received as argument is relative to Plot3DWidget
+ # and needs to be converted.
+ globalPosition = self.getPlot3DWidget().mapToGlobal(pos)
+ menu.exec_(globalPosition)
+
+
+# Start Qt QApplication
+app = qt.QApplication([])
+
+# Create the viewer main window
+window = ScalarFieldViewWithContextMenu()
+
+# Create dummy data
+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)
+
+# Add an iso-surface
+window.addIsosurface(0.2, '#FF0000FF')
+
+window.show()
+app.exec_()
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/plotItemsSelector.py b/examples/plotItemsSelector.py
new file mode 100755
index 0000000..8f29457
--- /dev/null
+++ b/examples/plotItemsSelector.py
@@ -0,0 +1,56 @@
+#!/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 example illustrates how to use a :class:`ItemsSelectionDialog` widget
+associated with a :class:`PlotWidget`.
+"""
+
+__authors__ = ["P. Knobel"]
+__license__ = "MIT"
+__date__ = "28/06/2017"
+
+from silx.gui import qt
+from silx.gui.plot.PlotWidget import PlotWidget
+from silx.gui.plot.ItemsSelectionDialog import ItemsSelectionDialog
+
+app = qt.QApplication([])
+
+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/scatterMask.py b/examples/scatterMask.py
new file mode 100644
index 0000000..45b1bac
--- /dev/null
+++ b/examples/scatterMask.py
@@ -0,0 +1,153 @@
+# 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 example demonstrates how to use ScatterMaskToolsWidget
+and NamedScatterAlphaSlider with a PlotWidget.
+"""
+
+import numpy
+
+from silx.gui import qt
+from silx.gui.plot import PlotWidget
+
+from silx.gui.plot.AlphaSlider import NamedScatterAlphaSlider
+
+from silx.gui.plot import ScatterMaskToolsWidget
+
+
+class MaskScatterWidget(qt.QMainWindow):
+ """Simple plot widget designed to display a scatter plot on top
+ of a background image.
+
+ A transparency slider is provided to adjust the transparency of the
+ scatter points.
+
+ A mask tools widget is provided to select/mask points of the scatter
+ plot.
+ """
+ def __init__(self, parent=None):
+ super(MaskScatterWidget, self).__init__(parent=parent)
+ self._activeScatterLegend = "active scatter"
+ self._bgImageLegend = "background image"
+
+ # widgets
+ centralWidget = qt.QWidget(self)
+
+ self._plot = PlotWidget(parent=centralWidget)
+
+ self._maskToolsWidget = ScatterMaskToolsWidget.ScatterMaskToolsWidget(
+ plot=self._plot, parent=centralWidget)
+
+ self._alphaSlider = NamedScatterAlphaSlider(parent=self, plot=self._plot)
+ self._alphaSlider.setOrientation(qt.Qt.Horizontal)
+ self._alphaSlider.setToolTip("Adjust scatter opacity")
+
+ # layout
+ layout = qt.QVBoxLayout(centralWidget)
+ layout.addWidget(self._plot)
+ layout.addWidget(self._alphaSlider)
+ layout.addWidget(self._maskToolsWidget)
+ centralWidget.setLayout(layout)
+
+ self.setCentralWidget(centralWidget)
+
+ def setSelectionMask(self, mask, copy=True):
+ """Set the mask to a new array.
+
+ :param numpy.ndarray mask: The array to use for the mask.
+ Mask type: array of uint8 of dimension 1,
+ Array of other types are converted.
+ :param bool copy: True (the default) to copy the array,
+ False to use it as is if possible.
+ :return: None if failed, shape of mask as 1-tuple if successful.
+ """
+ return self._maskToolsWidget.setSelectionMask(mask,
+ copy=copy)
+
+ def getSelectionMask(self, copy=True):
+ """Get the current mask as a 1D array.
+
+ :param bool copy: True (default) to get a copy of the mask.
+ If False, the returned array MUST not be modified.
+ :return: The array of the mask with dimension of the scatter data.
+ If there is no scatter data, an empty array is returned.
+ :rtype: 1D numpy.ndarray of uint8
+ """
+ return self._maskToolsWidget.getSelectionMask(copy=copy)
+
+ def setBackgroundImage(self, image, xscale=(0, 1.), yscale=(0, 1.),
+ colormap=None):
+ """Set a background image
+
+ :param image: 2D image, array of shape (nrows, ncolumns)
+ or (nrows, ncolumns, 3) or (nrows, ncolumns, 4) RGB(A) pixmap
+ :param xscale: Factors for polynomial scaling for x-axis,
+ *(a, b)* such as :math:`x \mapsto a + bx`
+ :param yscale: Factors for polynomial scaling for y-axis
+ """
+ self._plot.addImage(image, legend=self._bgImageLegend,
+ origin=(xscale[0], yscale[0]),
+ scale=(xscale[1], yscale[1]),
+ z=0, replace=False,
+ colormap=colormap)
+
+ def setScatter(self, x, y, v=None, info=None, colormap=None):
+ """Set the scatter data, by providing its data as a 1D
+ array or as a pixmap.
+
+ The scatter plot set through this method is associated
+ with the transparency slider.
+
+ :param x: 1D array of x coordinates
+ :param y: 1D array of y coordinates
+ :param v: Array of values for each point, represented as the color
+ of the point on the plot.
+ """
+ self._plot.addScatter(x, y, v, legend=self._activeScatterLegend,
+ info=info, colormap=colormap)
+ # the mask is associated with the active scatter
+ self._plot._setActiveItem(kind="scatter",
+ legend=self._activeScatterLegend)
+
+ self._alphaSlider.setLegend(self._activeScatterLegend)
+
+
+if __name__ == "__main__":
+ app = qt.QApplication([])
+ msw = MaskScatterWidget()
+
+ # create a synthetic bg image
+ bg_img = numpy.arange(200*150).reshape((200, 150))
+ bg_img[75:125, 80:120] = 1000
+
+ # create synthetic data for a scatter plot
+ twopi = numpy.pi * 2
+ x = 50 + 80 * numpy.linspace(0, twopi, num=100) / twopi * numpy.cos(numpy.linspace(0, twopi, num=100))
+ y = 150 + 150 * numpy.linspace(0, twopi, num=100) / twopi * numpy.sin(numpy.linspace(0, twopi, num=100))
+ v = numpy.arange(100) / 3.14
+
+ msw.setScatter(x, y, v=v)
+ msw.setBackgroundImage(bg_img)
+ msw.show()
+ app.exec_()
diff --git a/examples/shiftPlotAction.py b/examples/shiftPlotAction.py
new file mode 100755
index 0000000..7cac08c
--- /dev/null
+++ b/examples/shiftPlotAction.py
@@ -0,0 +1,113 @@
+#!/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 a simple (trivial) example of how to create a PlotWindow,
+create a custom :class:`PlotAction` and add it to the toolbar.
+
+The action simply shifts the selected curve up by 1 unit by adding 1 to each
+value of y.
+"""
+
+__authors__ = ["P. Knobel"]
+__license__ = "MIT"
+__date__ = "12/01/2017"
+
+import sys
+from silx.gui import qt
+from silx.gui.plot import PlotWindow
+from silx.gui.plot.actions import PlotAction
+
+
+class ShiftUpAction(PlotAction):
+ """QAction shifting up a curve by one unit
+
+ :param plot: :class:`.PlotWidget` instance on which to operate
+ :param parent: See :class:`QAction`
+ """
+ def __init__(self, plot, parent=None):
+ PlotAction.__init__(self,
+ plot,
+ icon='shape-circle',
+ text='Shift up',
+ tooltip='Shift active curve up by one unit',
+ triggered=self.shiftActiveCurveUp,
+ parent=parent)
+
+ def shiftActiveCurveUp(self):
+ """Get the active curve, add 1 to all y values, use this new y
+ array to replace the original curve"""
+ # By inheriting from PlotAction, we get access to attribute self.plot
+ # which is a reference to the PlotWindow
+ activeCurve = self.plot.getActiveCurve()
+
+ if activeCurve is None:
+ qt.QMessageBox.information(self.plot,
+ 'Shift Curve',
+ 'Please select a curve.')
+ else:
+ # Unpack curve data.
+ # Each curve is represented by an object with methods to access:
+ # the curve data, its legend, associated information and curve style
+ # Here we retrieve the x and y data of the curve
+ x0 = activeCurve.getXData()
+ y0 = activeCurve.getYData()
+
+ # Add 1 to all values in the y array
+ # and assign the result to a new array y1
+ y1 = y0 + 1.0
+
+ # Set the active curve data with the shifted y values
+ activeCurve.setData(x0, y1)
+
+
+# creating QApplication is mandatory in order to use qt widget
+app = qt.QApplication([])
+
+sys.excepthook = qt.exceptionHandler
+
+# create a PlotWindow
+plotwin = PlotWindow()
+# Add a new toolbar
+toolbar = qt.QToolBar("My toolbar")
+plotwin.addToolBar(toolbar)
+# Get a reference to the PlotWindow's menu bar, add a menu
+menubar = plotwin.menuBar()
+actions_menu = menubar.addMenu("Custom actions")
+
+# Initialize our action, give it plotwin as a parameter
+myaction = ShiftUpAction(plotwin)
+# Add action to the menubar and toolbar
+toolbar.addAction(myaction)
+actions_menu.addAction(myaction)
+
+# Plot a couple of curves with synthetic data
+x = [0, 1, 2, 3, 4, 5, 6]
+y1 = [0, 1, 0, 1, 0, 1, 0]
+y2 = [0, 1, 2, 3, 4, 5, 6]
+plotwin.addCurve(x, y1, legend="triangle shaped curve")
+plotwin.addCurve(x, y2, legend="oblique line")
+
+plotwin.show()
+app.exec_()
diff --git a/examples/simplewidget.py b/examples/simplewidget.py
new file mode 100755
index 0000000..605beb3
--- /dev/null
+++ b/examples/simplewidget.py
@@ -0,0 +1,108 @@
+#!/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 shows a gallery of simple widgets provided by silx.
+
+It shows the following widgets:
+
+- :class:WaitingPushButton: A button with a progress-like waiting animated icon
+"""
+
+__authors__ = ["V. Valls"]
+__license__ = "MIT"
+__date__ = "12/01/2017"
+
+import sys
+from silx.gui import qt
+from silx.gui.widgets.WaitingPushButton import WaitingPushButton
+from silx.gui.widgets.ThreadPoolPushButton import ThreadPoolPushButton
+
+
+class SimpleWidgetExample(qt.QMainWindow):
+ """This windows shows simple widget provided by silx."""
+
+ def __init__(self):
+ """Constructor"""
+ qt.QMainWindow.__init__(self)
+ self.setWindowTitle("Silx simple widget example")
+
+ main_panel = qt.QWidget(self)
+ main_panel.setLayout(qt.QVBoxLayout())
+
+ main_panel.layout().addWidget(qt.QLabel("WaitingPushButton"))
+ main_panel.layout().addWidget(self.createWaitingPushButton())
+ main_panel.layout().addWidget(self.createWaitingPushButton2())
+
+ main_panel.layout().addWidget(qt.QLabel("ThreadPoolPushButton"))
+ main_panel.layout().addWidget(self.createThreadPoolPushButton())
+
+ self.setCentralWidget(main_panel)
+
+ def createWaitingPushButton(self):
+ widget = WaitingPushButton(text="Push me and wait for ever")
+ widget.clicked.connect(widget.swapWaiting)
+ return widget
+
+ def createWaitingPushButton2(self):
+ widget = WaitingPushButton(text="Push me")
+ widget.setDisabledWhenWaiting(False)
+ widget.clicked.connect(widget.swapWaiting)
+ return widget
+
+ def printResult(self, result):
+ print(result)
+
+ def printError(self, result):
+ print("Error")
+ print(result)
+
+ def takesTimeToComputePow(self, a, b):
+ qt.QThread.sleep(2)
+ return a ** b
+
+ def createThreadPoolPushButton(self):
+ widget = ThreadPoolPushButton(text="Compute 2^16")
+ widget.setCallable(self.takesTimeToComputePow, 2, 16)
+ widget.succeeded.connect(self.printResult)
+ widget.failed.connect(self.printError)
+ return widget
+
+
+def main():
+ """
+ Main function
+ """
+ app = qt.QApplication([])
+ sys.excepthook = qt.exceptionHandler
+ window = SimpleWidgetExample()
+ window.show()
+ result = app.exec_()
+ # remove ending warnings relative to QTimer
+ app.deleteLater()
+ sys.excepthook = sys.__excepthook__
+ sys.exit(result)
+
+
+main()
diff --git a/examples/stackView.py b/examples/stackView.py
new file mode 100644
index 0000000..0447cea
--- /dev/null
+++ b/examples/stackView.py
@@ -0,0 +1,58 @@
+#!/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 a simple example to illustrate how to use the StackView
+widget.
+"""
+import numpy
+import sys
+from silx.gui import qt
+from silx.gui.plot.StackView import StackViewMainWindow
+
+app = qt.QApplication(sys.argv[1:])
+
+a, b, c = numpy.meshgrid(numpy.linspace(-10, 10, 200),
+ numpy.linspace(-10, 5, 150),
+ numpy.linspace(-5, 10, 120),
+ indexing="ij")
+mystack = numpy.asarray(numpy.sin(a * b * c) / (a * b * c),
+ dtype='float32')
+
+# linear calibrations (a, b), x -> a + bx
+dim0_calib = (-10., 20. / 200.)
+dim1_calib = (-10., 15. / 150.)
+dim2_calib = (-5., 15. / 120.)
+
+# sv = StackView()
+sv = StackViewMainWindow()
+sv.setColormap("jet", autoscale=True)
+sv.setStack(mystack,
+ calibrations=[dim0_calib, dim1_calib, dim2_calib])
+sv.setLabels(["dim0: -10 to 10 (200 samples)",
+ "dim1: -10 to 5 (150 samples)",
+ "dim2: -5 to 10 (120 samples)"])
+sv.show()
+
+app.exec_()
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
new file mode 100644
index 0000000..d030fba
--- /dev/null
+++ b/examples/viewer3DVolume.py
@@ -0,0 +1,212 @@
+# 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 the use of :class:`silx.gui.plot3d.ScalarFieldView`.
+
+It loads a 3D scalar data set from a file and displays iso-surfaces and
+an interactive cutting plane.
+It can also be started without providing a file.
+"""
+
+from __future__ import absolute_import, division, unicode_literals
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "05/01/2017"
+
+
+import argparse
+import logging
+import os.path
+import sys
+
+import numpy
+
+from silx.gui import qt
+
+from silx.gui.plot3d.ScalarFieldView import ScalarFieldView
+from silx.gui.plot3d import SFViewParamTree
+
+logging.basicConfig()
+
+_logger = logging.getLogger(__name__)
+
+
+try:
+ import h5py
+except ImportError:
+ _logger.warning('h5py is not installed: HDF5 not supported')
+ h5py = None
+
+
+def load(filename):
+ """Load 3D scalar field from file.
+
+ It supports 3D stack HDF5 files and numpy files.
+
+ :param str filename: Name of the file to open
+ and path in file for hdf5 file
+ :return: numpy.ndarray with 3 dimensions.
+ """
+ if not os.path.isfile(filename.split('::')[0]):
+ raise IOError('No input file: %s' % filename)
+
+ if h5py is not None and h5py.is_hdf5(filename.split('::')[0]):
+ if '::' not in filename:
+ raise ValueError(
+ 'HDF5 path not provided: Use <filename>::<path> format')
+
+ filename, path = filename.split('::')
+ path, indices = path.split('#')[0], path.split('#')[1:]
+
+ with h5py.File(filename) as f:
+ data = f[path]
+
+ # Loop through indices along first dimensions
+ for index in indices:
+ data = data[int(index)]
+
+ data = numpy.array(data, order='C', dtype='float32')
+
+ else: # Try with numpy
+ try:
+ data = numpy.load(filename)
+ except IOError:
+ raise IOError('Unsupported file format: %s' % filename)
+
+ if data.ndim != 3:
+ raise RuntimeError(
+ 'Unsupported data set dimensions, only supports 3D datasets')
+
+ 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)
+
+
+# Parse input arguments
+parser = argparse.ArgumentParser(
+ description=__doc__)
+parser.add_argument(
+ '-l', '--level', nargs='?', type=float, default=float('nan'),
+ help="The value at which to generate the iso-surface")
+parser.add_argument(
+ '-sx', '--xscale', nargs='?', type=float, default=1.,
+ help="The scale of the data on the X axis")
+parser.add_argument(
+ '-sy', '--yscale', nargs='?', type=float, default=1.,
+ help="The scale of the data on the Y axis")
+parser.add_argument(
+ '-sz', '--zscale', nargs='?', type=float, default=1.,
+ help="The scale of the data on the Z axis")
+parser.add_argument(
+ '-ox', '--xoffset', nargs='?', type=float, default=0.,
+ help="The offset of the data on the X axis")
+parser.add_argument(
+ '-oy', '--yoffset', nargs='?', type=float, default=0.,
+ help="The offset of the data on the Y axis")
+parser.add_argument(
+ '-oz', '--zoffset', nargs='?', type=float, default=0.,
+ help="The offset of the data on the Z axis")
+parser.add_argument(
+ 'filename',
+ nargs='?',
+ default=None,
+ help="""Filename to open.
+
+ It supports 3D volume saved as .npy or in .h5 files.
+
+ It also support nD data set (n>=3) stored in a HDF5 file.
+ For HDF5, provide the filename and path as: <filename>::<path_in_file>.
+ If the data set has more than 3 dimensions, it is possible to choose a
+ 3D data set as a subset by providing the indices along the first n-3
+ dimensions with '#':
+ <filename>::<path_in_file>#<1st_dim_index>...#<n-3th_dim_index>
+
+ E.g.: data.h5::/data_5D#1#1
+ """)
+args = parser.parse_args(args=sys.argv[1:])
+
+# Start GUI
+app = qt.QApplication([])
+
+# Create the viewer main window
+window = ScalarFieldView()
+
+# Create a parameter tree for the scalar field view
+treeView = SFViewParamTree.TreeView(window)
+treeView.setSfView(window) # Attach the parameter tree to the view
+
+# Add the parameter tree to the main window in a dock widget
+dock = qt.QDockWidget()
+dock.setWindowTitle('Parameters')
+dock.setWidget(treeView)
+window.addDockWidget(qt.Qt.RightDockWidgetArea, dock)
+
+# Load data from file
+if args.filename is not None:
+ data = load(args.filename)
+ _logger.info('Data:\n\tShape: %s\n\tRange: [%f, %f]',
+ str(data.shape), data.min(), data.max())
+else:
+ # Create dummy data
+ _logger.warning('Not data file provided, creating dummy data')
+ 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)
+
+# Set scale of the data
+window.setScale(args.xscale, args.yscale, args.zscale)
+
+# Set offset of the data
+window.setTranslation(args.xoffset, args.yoffset, args.zoffset)
+
+# Set axes labels
+window.setAxesLabels('X', 'Y', 'Z')
+
+# Add an iso-surface
+if not numpy.isnan(args.level):
+ # Add an iso-surface at the given iso-level
+ window.addIsosurface(args.level, '#FF0000FF')
+else:
+ # Add an iso-surface from a function
+ window.addIsosurface(default_isolevel, '#FF0000FF')
+
+window.show()
+app.exec_()
diff --git a/examples/writetoh5.py b/examples/writetoh5.py
new file mode 100644
index 0000000..5e89e48
--- /dev/null
+++ b/examples/writetoh5.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+# coding: utf-8
+# /*##########################################################################
+#
+# 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
+# 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 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
+existing files, or append SPEC data to existing HDF5 files.
+
+In case of appending data to HDF5 files, the user can choose between ignoring
+input data if a corresponding dataset already exists in the output file, or
+overwriting existing datasets.
+
+By default, new scans are written to the root (/) of the HDF5 file, but it is
+possible to specify a different target path.
+"""
+
+__authors__ = ["P. Knobel"]
+__license__ = "MIT"
+__date__ = "12/09/2016"
+
+import argparse
+from silx.io.convert import write_to_h5
+
+parser = argparse.ArgumentParser(description=__doc__)
+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="/",
+ help='Name of the group in which to save the scans ' +
+ 'in the output file')
+
+mode_group = parser.add_mutually_exclusive_group()
+mode_group.add_argument('-o', '--overwrite', action="store_true",
+ help='Overwrite output file if it exists, ' +
+ 'else create new file.')
+mode_group.add_argument('-a', '--append', action="store_true",
+ help='Append data to existing file if it exists, ' +
+ 'else create new file.')
+
+parser.add_argument('--overwrite-data', action="store_true",
+ help='In append mode, overwrite existing groups and ' +
+ 'datasets in the output file, if they exist with ' +
+ 'the same name as input data. By default, existing' +
+ ' data is not touched, corresponding input data is' +
+ ' ignored.')
+
+args = parser.parse_args()
+
+if args.overwrite_data and not args.append:
+ print("Option --overwrite-data ignored " +
+ "(only relevant combined with option -a)")
+
+if args.overwrite:
+ mode = "w"
+elif args.append:
+ mode = "a"
+else:
+ # by default, use "write" mode and fail if file already exists
+ mode = "w-"
+
+write_to_h5(args.input_path, args.h5_path,
+ h5path=args.target_path,
+ mode=mode,
+ overwrite_data=args.overwrite_data)