summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/animatedicons.py155
-rw-r--r--examples/colorbar.py62
-rw-r--r--examples/fft.pngbin0 -> 1432 bytes
-rwxr-xr-xexamples/fftPlotAction.py194
-rwxr-xr-xexamples/hdf5widget.py753
-rw-r--r--examples/icons.py108
-rwxr-xr-xexamples/imageview.py153
-rw-r--r--examples/periodicTable.py81
-rw-r--r--examples/scatterMask.py153
-rwxr-xr-xexamples/shiftPlotAction.py113
-rwxr-xr-xexamples/simplewidget.py108
-rwxr-xr-xexamples/spectoh5.py88
-rw-r--r--examples/stackView.py58
-rw-r--r--examples/viewer3DVolume.py209
14 files changed, 2235 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/colorbar.py b/examples/colorbar.py
new file mode 100644
index 0000000..a4dc2d8
--- /dev/null
+++ b/examples/colorbar.py
@@ -0,0 +1,62 @@
+#!/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 `ColorBarWidget` widget.
+It can be associated to a plot.
+
+In this exqmple the `ColorBarWidget` widget will display the colormap of the
+active image.
+
+To change the active image slick on the image you want to set active.
+"""
+
+__authors__ = ["H. Payno"]
+__license__ = "MIT"
+__date__ = "03/05/2017"
+
+
+from silx.gui import qt
+import numpy
+from silx.gui.plot import Plot2D
+from silx.gui.plot.ColorBar import ColorBarWidget
+
+image = numpy.exp(numpy.random.rand(100, 100) * 10)
+
+app = qt.QApplication([])
+
+plot = Plot2D()
+colorbar = ColorBarWidget(parent=None, plot=plot)
+colorbar.setLegend('my colormap')
+colorbar.show()
+plot.show()
+
+clm = plot.getDefaultColormap()
+clm['normalization'] = 'log'
+clm['name'] = 'viridis'
+plot.addImage(data=image, colormap=clm, legend='image')
+plot.setActiveImage('image')
+
+app.exec_()
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..e4d4081
--- /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__ = "12/01/2017"
+
+import numpy
+import os
+import sys
+
+from silx.gui import qt
+from silx.gui.plot import PlotWindow
+from silx.gui.plot.PlotActions 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.getGraphXLabel()
+ self.original_ylabel = self.plot.getGraphYLabel()
+
+ 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.setGraphXLabel("Frequency")
+ self.plot.setGraphYLabel("Amplitude")
+ else:
+ # restore original labels
+ self.plot.setGraphTitle(self.original_title)
+ self.plot.setGraphXLabel(self.original_xlabel)
+ self.plot.setGraphYLabel(self.original_ylabel)
+
+ self.plot.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.setGraphYLabel("amplitude")
+plotwin.setGraphXLabel("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..0992cf4
--- /dev/null
+++ b/examples/icons.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.
+#
+# ###########################################################################*/
+"""
+Display available project icons using Qt.
+"""
+from silx.gui import qt
+import silx.gui.icons
+import pkg_resources
+import functools
+
+
+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 = []
+
+ icons = pkg_resources.resource_listdir("silx.resources", "gui/icons")
+ # filter out sub-directories
+ icons = filter(lambda x: not pkg_resources.resource_isdir("silx.resources", "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/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..ca48300
--- /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.PlotActions 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/spectoh5.py b/examples/spectoh5.py
new file mode 100755
index 0000000..f2f796b
--- /dev/null
+++ b/examples/spectoh5.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2004-2016 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 SPEC data files to HDF5 files.
+
+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__ = "18/10/2016"
+
+import argparse
+from silx.io.spectoh5 import write_spec_to_h5
+
+parser = argparse.ArgumentParser(description=__doc__)
+parser.add_argument('spec_path',
+ help='Path to input SPEC 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_spec_to_h5(args.spec_path, args.h5_path,
+ h5path=args.target_path,
+ mode=mode,
+ overwrite_data=args.overwrite_data)
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/viewer3DVolume.py b/examples/viewer3DVolume.py
new file mode 100644
index 0000000..7a95d8a
--- /dev/null
+++ b/examples/viewer3DVolume.py
@@ -0,0 +1,209 @@
+# 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 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 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 main(argv=None):
+ # 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=argv)
+
+ # Start GUI
+ global app # QApplication must be global to avoid seg fault on quit
+ app = qt.QApplication([])
+
+ # Create the viewer main window
+ window = ScalarFieldView()
+ window.setAttribute(qt.Qt.WA_DeleteOnClose)
+
+ # 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')
+ size = 128
+ z, y, x = numpy.mgrid[0:size, 0:size, 0:size]
+ data = numpy.asarray(
+ size**2 - ((x-size/2)**2 + (y-size/2)**2 + (z-size/2)**2),
+ dtype='float32')
+
+ # 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(
+ lambda data: numpy.mean(data) + numpy.std(data),
+ '#FF0000FF')
+
+ window.show()
+ return app.exec_()
+
+
+if __name__ == '__main__':
+ import sys
+
+ sys.exit(main(argv=sys.argv[1:]))