diff options
Diffstat (limited to 'examples')
-rw-r--r-- | examples/animatedicons.py | 155 | ||||
-rw-r--r-- | examples/customHdf5TreeModel.py | 290 | ||||
-rw-r--r-- | examples/fft.png | bin | 0 -> 1432 bytes | |||
-rwxr-xr-x | examples/fftPlotAction.py | 194 | ||||
-rwxr-xr-x | examples/hdf5widget.py | 753 | ||||
-rw-r--r-- | examples/icons.py | 110 | ||||
-rwxr-xr-x | examples/imageview.py | 153 | ||||
-rw-r--r-- | examples/periodicTable.py | 81 | ||||
-rw-r--r-- | examples/plot3dContextMenu.py | 112 | ||||
-rw-r--r-- | examples/plotContextMenu.py | 100 | ||||
-rwxr-xr-x | examples/plotItemsSelector.py | 56 | ||||
-rw-r--r-- | examples/plotLimits.py | 93 | ||||
-rw-r--r-- | examples/plotUpdateFromThread.py | 128 | ||||
-rw-r--r-- | examples/plotWidget.py | 121 | ||||
-rwxr-xr-x | examples/printPreview.py | 82 | ||||
-rw-r--r-- | examples/scatterMask.py | 153 | ||||
-rwxr-xr-x | examples/shiftPlotAction.py | 113 | ||||
-rwxr-xr-x | examples/simplewidget.py | 108 | ||||
-rw-r--r-- | examples/stackView.py | 58 | ||||
-rw-r--r-- | examples/syncaxis.py | 101 | ||||
-rw-r--r-- | examples/viewer3DVolume.py | 212 | ||||
-rw-r--r-- | examples/writetoh5.py | 88 |
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 Binary files differnew file mode 100644 index 0000000..2464580 --- /dev/null +++ b/examples/fft.png 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) |