Skip to main content
`Ctrl`+`K`

# hdf5widget.py

Sample code demonstrating `silx.gui.hdf5` widgets:

    #!/usr/bin/env python
    # /*##########################################################################
    #
    # Copyright (c) 2016-2021 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 html
    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
    from silx.gui import qt
    from silx.gui.data.DataViewerFrame import DataViewerFrame
    from silx.gui.widgets.ThreadPoolPushButton import ThreadPoolPushButton

    import fabio

    _file_cache = {}

    def str_attrs(str_list):
        """Return a numpy array of unicode strings"""
        text_dtype = h5py.special_dtype(vlen=str)
        return numpy.array(str_list, dtype=text_dtype)

    def get_hdf5_with_all_types():
        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.bytes_("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():
        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():
        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():
        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():
        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():
        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():
        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():
        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["auxiliary_signals"] = str_attrs(["count.5", "count2"])
        g1d0.attrs["axes"] = "energy_calib"
        g1d0.attrs["uncertainties"] = str_attrs(["energy_errors"])
        g1d0.create_dataset("count", data=numpy.arange(10))
        g1d0.create_dataset("count.5", data=0.5 * numpy.arange(10))
        d2 = g1d0.create_dataset("count2", data=2 * numpy.arange(10))
        d2.attrs["long_name"] = "count multiplied by 2"
        g1d0.create_dataset("energy_calib", data=(10, 5))  # 10 * idx + 5
        g1d0.create_dataset("energy_errors", data=3.14 * numpy.random.rand(10))
        g1d0.create_dataset("title", data="Title example provided as dataset")

        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"] = str_attrs(["energy"])
        ds = g1d2.create_dataset(
            "counts", data=numpy.arange(2 * 2 * 3 * 10).reshape((2, 2, 3, 10))
        )
        ds.attrs["interpretation"] = "spectrum"
        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["auxiliary_signals"] = str_attrs(["image2", "image3"])
        g2d0.attrs["axes"] = str_attrs(["rows_calib", "columns_coordinates"])
        g2d0.attrs["title"] = "Title example provided as group attr"
        g2d0.create_dataset("image", data=numpy.arange(4 * 6).reshape((4, 6)))
        g2d0.create_dataset("image2", data=1 / (1.0 + numpy.arange(4 * 6).reshape((4, 6))))
        ds = g2d0.create_dataset("image3", data=-numpy.arange(4 * 6).reshape((4, 6)))
        ds.attrs["long_name"] = "3rd image (2nd auxiliary)"
        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))

        g2d4 = g2d.create_group("RGBA_image")
        g2d4.attrs["NX_class"] = "NXdata"
        g2d4.attrs["signal"] = "image"
        g2d4.attrs["auxiliary_signals"] = "squared image"
        g2d4.attrs["axes"] = str_attrs(["rows_calib", "columns_coordinates"])
        rgba_image = numpy.linspace(0, 1, num=7 * 8 * 3).reshape((7, 8, 3))
        rgba_image[:, :, 1] = 1 - rgba_image[:, :, 1]  # invert G channel to add some color
        ds = g2d4.create_dataset("image", data=rgba_image)
        ds.attrs["interpretation"] = "rgba-image"
        ds = g2d4.create_dataset("squared image", data=rgba_image**2)
        ds.attrs["interpretation"] = "rgba-image"
        ds = g2d4.create_dataset("rows_calib", data=(10, 5))
        ds.attrs["long_name"] = "Calibrated Y"
        g2d4.create_dataset("columns_coordinates", data=0.5 + 0.02 * numpy.arange(8))

        g2d1 = g2d.create_group("2D_irregular_data")
        g2d1.attrs["NX_class"] = "NXdata"
        g2d1.attrs["signal"] = "data"
        g2d1.attrs["axes"] = str_attrs(["rows_coordinates", "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"] = str_attrs(["rows_coordinates", "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"] = str_attrs(["x"])
        gd0.attrs["title"] = "simple y = f(x) scatters cannot be distinguished from curves"
        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["auxiliary_signals"] = str_attrs(["values.5", "values2"])
        gd1.attrs["axes"] = str_attrs(["x", "y"])
        gd1.attrs["title"] = "x, y, values scatter with asymmetric y_errors"
        gd1.create_dataset("values", data=3.14 * numpy.random.rand(128))
        gd1.create_dataset("values.5", data=0.5 * 3.14 * numpy.random.rand(128))
        gd1.create_dataset("values2", data=2.0 * 3.14 * numpy.random.rand(128))
        gd1.create_dataset("y", data=numpy.random.rand(128))
        y_errors = [0.03 * numpy.random.rand(128), 0.04 * numpy.random.rand(128)]
        gd1.create_dataset("y_errors", data=y_errors)
        ds = gd1.create_dataset("x", data=2 * numpy.random.rand(128))
        ds.attrs["long_name"] = "horizontal axis"
        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"] = str_attrs(["img_idx", "rows_coordinates", "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))
        )

        gd2 = g.create_group("3D_nonlinear_scaling")
        gd2.attrs["NX_class"] = "NXdata"
        gd2.attrs["signal"] = "cube"
        gd2.attrs["axes"] = str_attrs(["img_idx", "rows_coordinates", "cols_coordinates"])
        gd2.create_dataset("cube", data=numpy.arange(4 * 5 * 6).reshape((4, 5, 6)))
        gd2.create_dataset("img_idx", data=numpy.array([2.0, -0.1, 8, 3.14]))
        gd2.create_dataset("rows_coordinates", data=0.1 * numpy.arange(5))
        gd2.create_dataset("cols_coordinates", data=[0.1, 0.6, 0.7, 8.0, 8.1, 8.2])

        # invalid NXdata
        g = h5.create_group("invalid")
        g0 = g.create_group("invalid NXdata")
        g0.attrs["NX_class"] = "NXdata"

        g1 = g.create_group("invalid NXentry")
        g1.attrs["NX_class"] = "NXentry"
        g1.attrs["default"] = "missing NXdata group"

        g2 = g.create_group("invalid NXroot")
        g2.attrs["NX_class"] = "NXroot"
        g2.attrs["default"] = "invalid NXentry in NXroot"
        g20 = g2.create_group("invalid NXentry in NXroot")
        g20.attrs["NX_class"] = "NXentry"
        g20.attrs["default"] = "missing NXdata group"

        h5.close()

        _file_cache[ID] = tmp
        return tmp.name

    def get_edf_with_all_types():
        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():
        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.append_frame(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 = html.escape(str(name)), 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 not menu.isEmpty():
                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 not menu.isEmpty():
                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)

            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:])