hdf5widget.pyΒΆ

Sample code demonstrating silx.gui.hdf5 widgets:

# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2004-2016 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ###########################################################################*/
"""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 os
import sys
import numpy
import logging
from silx.gui import qt
import silx.gui.hdf5
import silx.utils.html
from silx.gui.widgets.ThreadPoolPushButton import ThreadPoolPushButton
import h5py
import tempfile

try:
    import fabio
except ImportError:
    fabio = None


_logger = logging.getLogger(__name__)
"""Module logger"""


_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=[10])
    g.create_dataset("image", data=[[10]])
    g.create_dataset("cube", data=[[[10]]])
    g.create_dataset("hypercube", data=[[[[10]]]])

    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_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()
        """Silx HDF5 TreeView"""
        self.__text = qt.QTextEdit(self)
        """Widget displaying information"""

        spliter = qt.QSplitter()
        spliter.addWidget(self.__treeview)
        spliter.addWidget(self.__text)
        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(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)
        # lamba 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 displayEvent(self, eventName, index):

        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.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([])
    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:])

Previous topic

Getting started with HDF5 widgets

Next topic

Hdf5TreeView class

This Page