diff options
Diffstat (limited to 'silx/gui/hdf5')
-rw-r--r-- | silx/gui/hdf5/Hdf5Formatter.py | 244 | ||||
-rw-r--r-- | silx/gui/hdf5/Hdf5HeaderView.py | 195 | ||||
-rw-r--r-- | silx/gui/hdf5/Hdf5Item.py | 488 | ||||
-rw-r--r-- | silx/gui/hdf5/Hdf5LoadingItem.py | 77 | ||||
-rw-r--r-- | silx/gui/hdf5/Hdf5Node.py | 238 | ||||
-rw-r--r-- | silx/gui/hdf5/Hdf5TreeModel.py | 779 | ||||
-rw-r--r-- | silx/gui/hdf5/Hdf5TreeView.py | 271 | ||||
-rw-r--r-- | silx/gui/hdf5/NexusSortFilterProxyModel.py | 222 | ||||
-rw-r--r-- | silx/gui/hdf5/__init__.py | 44 | ||||
-rw-r--r-- | silx/gui/hdf5/_utils.py | 421 | ||||
-rw-r--r-- | silx/gui/hdf5/setup.py | 41 | ||||
-rw-r--r-- | silx/gui/hdf5/test/__init__.py | 39 | ||||
-rw-r--r-- | silx/gui/hdf5/test/test_hdf5.py | 981 |
13 files changed, 0 insertions, 4040 deletions
diff --git a/silx/gui/hdf5/Hdf5Formatter.py b/silx/gui/hdf5/Hdf5Formatter.py deleted file mode 100644 index 6802142..0000000 --- a/silx/gui/hdf5/Hdf5Formatter.py +++ /dev/null @@ -1,244 +0,0 @@ -# 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 package provides a class sharred by widgets to format HDF5 data as -text.""" - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "06/06/2018" - -import numpy -from silx.third_party import six -from silx.gui import qt -from silx.gui.data.TextFormatter import TextFormatter - -try: - import h5py -except ImportError: - h5py = None - - -class Hdf5Formatter(qt.QObject): - """Formatter to convert HDF5 data to string. - """ - - formatChanged = qt.Signal() - """Emitted when properties of the formatter change.""" - - def __init__(self, parent=None, textFormatter=None): - """ - Constructor - - :param qt.QObject parent: Owner of the object - :param TextFormatter formatter: Text formatter - """ - qt.QObject.__init__(self, parent) - if textFormatter is not None: - self.__formatter = textFormatter - else: - self.__formatter = TextFormatter(self) - self.__formatter.formatChanged.connect(self.__formatChanged) - - def textFormatter(self): - """Returns the used text formatter - - :rtype: TextFormatter - """ - return self.__formatter - - def setTextFormatter(self, textFormatter): - """Set the text formatter to be used - - :param TextFormatter textFormatter: The text formatter to use - """ - if textFormatter is None: - raise ValueError("Formatter expected but None found") - if self.__formatter is textFormatter: - return - self.__formatter.formatChanged.disconnect(self.__formatChanged) - self.__formatter = textFormatter - self.__formatter.formatChanged.connect(self.__formatChanged) - self.__formatChanged() - - def __formatChanged(self): - self.formatChanged.emit() - - def humanReadableShape(self, dataset): - if dataset.shape is None: - return "none" - if dataset.shape == tuple(): - return "scalar" - shape = [str(i) for i in dataset.shape] - text = u" \u00D7 ".join(shape) - return text - - def humanReadableValue(self, dataset): - if dataset.shape is None: - return "No data" - - dtype = dataset.dtype - if dataset.dtype.type == numpy.void: - if dtype.fields is None: - return "Raw data" - - if dataset.shape == tuple(): - numpy_object = dataset[()] - text = self.__formatter.toString(numpy_object, dtype=dataset.dtype) - else: - if dataset.size < 5 and dataset.compression is None: - numpy_object = dataset[0:5] - text = self.__formatter.toString(numpy_object, dtype=dataset.dtype) - else: - dimension = len(dataset.shape) - if dataset.compression is not None: - text = "Compressed %dD data" % dimension - else: - text = "%dD data" % dimension - return text - - def humanReadableType(self, dataset, full=False): - if hasattr(dataset, "dtype"): - dtype = dataset.dtype - else: - # Fallback... - dtype = type(dataset) - return self.humanReadableDType(dtype, full) - - def humanReadableDType(self, dtype, full=False): - if dtype == six.binary_type or numpy.issubdtype(dtype, numpy.string_): - text = "string" - if full: - text = "ASCII " + text - return text - elif dtype == six.text_type or numpy.issubdtype(dtype, numpy.unicode_): - text = "string" - if full: - text = "UTF-8 " + text - return text - elif dtype.type == numpy.object_: - ref = h5py.check_dtype(ref=dtype) - if ref is not None: - return "reference" - vlen = h5py.check_dtype(vlen=dtype) - if vlen is not None: - text = self.humanReadableDType(vlen, full=full) - if full: - text = "variable-length " + text - return text - return "object" - elif dtype.type == numpy.bool_: - return "bool" - elif dtype.type == numpy.void: - if dtype.fields is None: - return "opaque" - else: - if not full: - return "compound" - else: - fields = sorted(dtype.fields.items(), key=lambda e: e[1][1]) - compound = [d[1][0] for d in fields] - compound = [self.humanReadableDType(d) for d in compound] - return "compound(%s)" % ", ".join(compound) - elif numpy.issubdtype(dtype, numpy.integer): - if h5py is not None: - enumType = h5py.check_dtype(enum=dtype) - if enumType is not None: - return "enum" - - text = str(dtype.newbyteorder('N')) - if numpy.issubdtype(dtype, numpy.floating): - if hasattr(numpy, "float128") and dtype == numpy.float128: - text = "float80" - if full: - text += " (padding 128bits)" - elif hasattr(numpy, "float96") and dtype == numpy.float96: - text = "float80" - if full: - text += " (padding 96bits)" - - if full: - if dtype.byteorder == "<": - text = "Little-endian " + text - elif dtype.byteorder == ">": - text = "Big-endian " + text - elif dtype.byteorder == "=": - text = "Native " + text - - dtype = dtype.newbyteorder('N') - return text - - def humanReadableHdf5Type(self, dataset): - """Format the internal HDF5 type as a string""" - t = dataset.id.get_type() - class_ = t.get_class() - if class_ == h5py.h5t.NO_CLASS: - return "NO_CLASS" - elif class_ == h5py.h5t.INTEGER: - return "INTEGER" - elif class_ == h5py.h5t.FLOAT: - return "FLOAT" - elif class_ == h5py.h5t.TIME: - return "TIME" - elif class_ == h5py.h5t.STRING: - charset = t.get_cset() - strpad = t.get_strpad() - text = "" - - if strpad == h5py.h5t.STR_NULLTERM: - text += "NULLTERM" - elif strpad == h5py.h5t.STR_NULLPAD: - text += "NULLPAD" - elif strpad == h5py.h5t.STR_SPACEPAD: - text += "SPACEPAD" - else: - text += "UNKNOWN_STRPAD" - - if t.is_variable_str(): - text += " VARIABLE" - - if charset == h5py.h5t.CSET_ASCII: - text += " ASCII" - elif charset == h5py.h5t.CSET_UTF8: - text += " UTF8" - else: - text += " UNKNOWN_CSET" - - return text + " STRING" - elif class_ == h5py.h5t.BITFIELD: - return "BITFIELD" - elif class_ == h5py.h5t.OPAQUE: - return "OPAQUE" - elif class_ == h5py.h5t.COMPOUND: - return "COMPOUND" - elif class_ == h5py.h5t.REFERENCE: - return "REFERENCE" - elif class_ == h5py.h5t.ENUM: - return "ENUM" - elif class_ == h5py.h5t.VLEN: - return "VLEN" - elif class_ == h5py.h5t.ARRAY: - return "ARRAY" - else: - return "UNKNOWN_CLASS" diff --git a/silx/gui/hdf5/Hdf5HeaderView.py b/silx/gui/hdf5/Hdf5HeaderView.py deleted file mode 100644 index 7baa6e0..0000000 --- a/silx/gui/hdf5/Hdf5HeaderView.py +++ /dev/null @@ -1,195 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 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. -# -# ###########################################################################*/ - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "16/06/2017" - - -from .. import qt -from .Hdf5TreeModel import Hdf5TreeModel - -QTVERSION = qt.qVersion() - - -class Hdf5HeaderView(qt.QHeaderView): - """ - Default HDF5 header - - Manage auto-resize and context menu to display/hide columns - """ - - def __init__(self, orientation, parent=None): - """ - Constructor - - :param orientation qt.Qt.Orientation: Orientation of the header - :param parent qt.QWidget: Parent of the widget - """ - super(Hdf5HeaderView, self).__init__(orientation, parent) - self.setContextMenuPolicy(qt.Qt.CustomContextMenu) - self.customContextMenuRequested.connect(self.__createContextMenu) - - # default initialization done by QTreeView for it's own header - if QTVERSION < "5.0": - self.setClickable(True) - self.setMovable(True) - else: - self.setSectionsClickable(True) - self.setSectionsMovable(True) - self.setDefaultAlignment(qt.Qt.AlignLeft | qt.Qt.AlignVCenter) - self.setStretchLastSection(True) - - self.__auto_resize = True - self.__hide_columns_popup = True - - def setModel(self, model): - """Override model to configure view when a model is expected - - `qt.QHeaderView.setResizeMode` expect already existing columns - to work. - - :param model qt.QAbstractItemModel: A model - """ - super(Hdf5HeaderView, self).setModel(model) - self.__updateAutoResize() - - def __updateAutoResize(self): - """Update the view according to the state of the auto-resize""" - if QTVERSION < "5.0": - setResizeMode = self.setResizeMode - else: - setResizeMode = self.setSectionResizeMode - - if self.__auto_resize: - setResizeMode(Hdf5TreeModel.NAME_COLUMN, qt.QHeaderView.ResizeToContents) - setResizeMode(Hdf5TreeModel.TYPE_COLUMN, qt.QHeaderView.ResizeToContents) - setResizeMode(Hdf5TreeModel.SHAPE_COLUMN, qt.QHeaderView.ResizeToContents) - setResizeMode(Hdf5TreeModel.VALUE_COLUMN, qt.QHeaderView.Interactive) - setResizeMode(Hdf5TreeModel.DESCRIPTION_COLUMN, qt.QHeaderView.Interactive) - setResizeMode(Hdf5TreeModel.NODE_COLUMN, qt.QHeaderView.ResizeToContents) - setResizeMode(Hdf5TreeModel.LINK_COLUMN, qt.QHeaderView.ResizeToContents) - else: - setResizeMode(Hdf5TreeModel.NAME_COLUMN, qt.QHeaderView.Interactive) - setResizeMode(Hdf5TreeModel.TYPE_COLUMN, qt.QHeaderView.Interactive) - setResizeMode(Hdf5TreeModel.SHAPE_COLUMN, qt.QHeaderView.Interactive) - setResizeMode(Hdf5TreeModel.VALUE_COLUMN, qt.QHeaderView.Interactive) - setResizeMode(Hdf5TreeModel.DESCRIPTION_COLUMN, qt.QHeaderView.Interactive) - setResizeMode(Hdf5TreeModel.NODE_COLUMN, qt.QHeaderView.Interactive) - setResizeMode(Hdf5TreeModel.LINK_COLUMN, qt.QHeaderView.Interactive) - - def setAutoResizeColumns(self, autoResize): - """Enable/disable auto-resize. When auto-resized, the header take care - of the content of the column to set fixed size of some of them, or to - auto fix the size according to the content. - - :param autoResize bool: Enable/disable auto-resize - """ - if self.__auto_resize == autoResize: - return - self.__auto_resize = autoResize - self.__updateAutoResize() - - def hasAutoResizeColumns(self): - """Is auto-resize enabled. - - :rtype: bool - """ - return self.__auto_resize - - autoResizeColumns = qt.Property(bool, hasAutoResizeColumns, setAutoResizeColumns) - """Property to enable/disable auto-resize.""" - - def setEnableHideColumnsPopup(self, enablePopup): - """Enable/disable a popup to allow to hide/show each column of the - model. - - :param bool enablePopup: Enable/disable popup to hide/show columns - """ - self.__hide_columns_popup = enablePopup - - def hasHideColumnsPopup(self): - """Is popup to hide/show columns is enabled. - - :rtype: bool - """ - return self.__hide_columns_popup - - enableHideColumnsPopup = qt.Property(bool, hasHideColumnsPopup, setAutoResizeColumns) - """Property to enable/disable popup allowing to hide/show columns.""" - - def __genHideSectionEvent(self, column): - """Generate a callback which change the column visibility according to - the event parameter - - :param int column: logical id of the column - :rtype: callable - """ - return lambda checked: self.setSectionHidden(column, not checked) - - def __createContextMenu(self, pos): - """Callback to create and display a context menu - - :param pos qt.QPoint: Requested position for the context menu - """ - if not self.__hide_columns_popup: - return - - model = self.model() - if model.columnCount() > 1: - menu = qt.QMenu(self) - menu.setTitle("Display/hide columns") - - action = qt.QAction("Display/hide column", self) - action.setEnabled(False) - menu.addAction(action) - - for column in range(model.columnCount()): - if column == 0: - # skip the main column - continue - text = model.headerData(column, qt.Qt.Horizontal, qt.Qt.DisplayRole) - action = qt.QAction("%s displayed" % text, self) - action.setCheckable(True) - action.setChecked(not self.isSectionHidden(column)) - action.toggled.connect(self.__genHideSectionEvent(column)) - menu.addAction(action) - - menu.popup(self.viewport().mapToGlobal(pos)) - - def setSections(self, logicalIndexes): - """ - Defines order of visible sections by logical indexes. - - Use `Hdf5TreeModel.NAME_COLUMN` to set the list. - - :param list logicalIndexes: List of logical indexes to display - """ - for pos, column_id in enumerate(logicalIndexes): - current_pos = self.visualIndex(column_id) - self.moveSection(current_pos, pos) - self.setSectionHidden(column_id, False) - for column_id in set(range(self.model().columnCount())) - set(logicalIndexes): - self.setSectionHidden(column_id, True) diff --git a/silx/gui/hdf5/Hdf5Item.py b/silx/gui/hdf5/Hdf5Item.py deleted file mode 100644 index b3c313e..0000000 --- a/silx/gui/hdf5/Hdf5Item.py +++ /dev/null @@ -1,488 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2016-2018 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. -# -# ###########################################################################*/ - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "03/09/2018" - - -import logging -import collections -from .. import qt -from .. import icons -from . import _utils -from .Hdf5Node import Hdf5Node -import silx.io.utils -from silx.gui.data.TextFormatter import TextFormatter -from ..hdf5.Hdf5Formatter import Hdf5Formatter -from ...third_party import six -_logger = logging.getLogger(__name__) -_formatter = TextFormatter() -_hdf5Formatter = Hdf5Formatter(textFormatter=_formatter) -# FIXME: The formatter should be an attribute of the Hdf5Model - - -class Hdf5Item(Hdf5Node): - """Subclass of :class:`qt.QStandardItem` to represent an HDF5-like - item (dataset, file, group or link) as an element of a HDF5-like - tree structure. - """ - - def __init__(self, text, obj, parent, key=None, h5Class=None, linkClass=None, populateAll=False): - """ - :param str text: text displayed - :param object obj: Pointer to a h5py-link object. See the `obj` attribute. - """ - self.__obj = obj - self.__key = key - self.__h5Class = h5Class - self.__isBroken = obj is None and h5Class is None - self.__error = None - self.__text = text - self.__linkClass = linkClass - self.__nx_class = None - Hdf5Node.__init__(self, parent, populateAll=populateAll) - - def _getCanonicalName(self): - parent = self.parent - if parent is None: - return self.__text - else: - return "%s/%s" % (parent._getCanonicalName(), self.__text) - - @property - def obj(self): - if self.__key: - self.__initH5Object() - return self.__obj - - @property - def basename(self): - return self.__text - - @property - def h5Class(self): - """Returns the class of the stored object. - - When the object is in lazy loading, this method should be able to - return the type of the futrue loaded object. It allows to delay the - real load of the object. - - :rtype: silx.io.utils.H5Type - """ - if self.__h5Class is None and self.obj is not None: - self.__h5Class = silx.io.utils.get_h5_class(self.obj) - return self.__h5Class - - @property - def h5pyClass(self): - """Returns the class of the stored object. - - When the object is in lazy loading, this method should be able to - return the type of the futrue loaded object. It allows to delay the - real load of the object. - - :rtype: h5py.File or h5py.Dataset or h5py.Group - """ - type_ = self.h5Class - return silx.io.utils.h5type_to_h5py_class(type_) - - @property - def linkClass(self): - """Returns the link class object of this node - - :rtype: H5Type - """ - return self.__linkClass - - def isGroupObj(self): - """Returns true if the stored HDF5 object is a group (contains sub - groups or datasets). - - :rtype: bool - """ - if self.h5Class is None: - return False - return self.h5Class in [silx.io.utils.H5Type.GROUP, silx.io.utils.H5Type.FILE] - - def isBrokenObj(self): - """Returns true if the stored HDF5 object is broken. - - The stored object is then an h5py-like link (external or not) which - point to nowhere (tbhe external file is not here, the expected - dataset is still not on the file...) - - :rtype: bool - """ - return self.__isBroken - - def _getFormatter(self): - """ - Returns an Hdf5Formatter - - :rtype: Hdf5Formatter - """ - return _hdf5Formatter - - def _expectedChildCount(self): - if self.isGroupObj(): - return len(self.obj) - return 0 - - def __initH5Object(self): - """Lazy load of the HDF5 node. It is reached from the parent node - with the key of the node.""" - parent_obj = self.parent.obj - - try: - obj = parent_obj.get(self.__key) - except Exception as e: - _logger.error("Internal error while reaching HDF5 object: %s", str(e)) - _logger.debug("Backtrace", exc_info=True) - try: - self.__obj = parent_obj.get(self.__key, getlink=True) - except Exception: - self.__obj = None - self.__error = e.args[0] - self.__isBroken = True - else: - if obj is None: - # that's a broken link - self.__obj = parent_obj.get(self.__key, getlink=True) - - # TODO monkey-patch file (ask that in h5py for consistency) - if not hasattr(self.__obj, "name"): - parent_name = parent_obj.name - if parent_name == "/": - self.__obj.name = "/" + self.__key - else: - self.__obj.name = parent_name + "/" + self.__key - # TODO monkey-patch file (ask that in h5py for consistency) - if not hasattr(self.__obj, "file"): - self.__obj.file = parent_obj.file - - class_ = silx.io.utils.get_h5_class(self.__obj) - - if class_ == silx.io.utils.H5Type.EXTERNAL_LINK: - message = "External link broken. Path %s::%s does not exist" % (self.__obj.filename, self.__obj.path) - elif class_ == silx.io.utils.H5Type.SOFT_LINK: - message = "Soft link broken. Path %s does not exist" % (self.__obj.path) - else: - name = self.__obj.__class__.__name__.split(".")[-1].capitalize() - message = "%s broken" % (name) - self.__error = message - self.__isBroken = True - else: - self.__obj = obj - if not self.isGroupObj(): - try: - # pre-fetch of the data - if obj.shape is None: - pass - elif obj.shape == tuple(): - obj[()] - else: - if obj.compression is None and obj.size > 0: - key = tuple([0] * len(obj.shape)) - obj[key] - except Exception as e: - _logger.debug(e, exc_info=True) - message = "%s broken. %s" % (self.__obj.name, e.args[0]) - self.__error = message - self.__isBroken = True - - self.__key = None - - def _populateChild(self, populateAll=False): - if self.isGroupObj(): - for name in self.obj: - try: - class_ = self.obj.get(name, getclass=True) - link = self.obj.get(name, getclass=True, getlink=True) - link = silx.io.utils.get_h5_class(class_=link) - except Exception: - lib_name = self.obj.__class__.__module__.split(".")[0] - _logger.warning("Internal %s error", lib_name, exc_info=True) - _logger.debug("Backtrace", exc_info=True) - class_ = None - try: - link = self.obj.get(name, getclass=True, getlink=True) - link = silx.io.utils.get_h5_class(class_=link) - except Exception: - _logger.debug("Backtrace", exc_info=True) - link = silx.io.utils.H5Type.HARD_LINK - - h5class = None - if class_ is not None: - h5class = silx.io.utils.get_h5_class(class_=class_) - if h5class is None: - _logger.error("Class %s unsupported", class_) - item = Hdf5Item(text=name, obj=None, parent=self, key=name, h5Class=h5class, linkClass=link) - self.appendChild(item) - - def hasChildren(self): - """Retuens true of this node have chrild. - - :rtype: bool - """ - if not self.isGroupObj(): - return False - return Hdf5Node.hasChildren(self) - - def _getDefaultIcon(self): - """Returns the icon displayed by the main column. - - :rtype: qt.QIcon - """ - # Pre-fetch the object, in case it is broken - obj = self.obj - style = qt.QApplication.style() - if self.__isBroken: - icon = style.standardIcon(qt.QStyle.SP_MessageBoxCritical) - return icon - class_ = self.h5Class - if class_ == silx.io.utils.H5Type.FILE: - return style.standardIcon(qt.QStyle.SP_FileIcon) - elif class_ == silx.io.utils.H5Type.GROUP: - return style.standardIcon(qt.QStyle.SP_DirIcon) - elif class_ == silx.io.utils.H5Type.SOFT_LINK: - return style.standardIcon(qt.QStyle.SP_DirLinkIcon) - elif class_ == silx.io.utils.H5Type.EXTERNAL_LINK: - return style.standardIcon(qt.QStyle.SP_FileLinkIcon) - elif class_ == silx.io.utils.H5Type.DATASET: - if obj.shape is None: - name = "item-none" - elif len(obj.shape) < 4: - name = "item-%ddim" % len(obj.shape) - else: - name = "item-ndim" - icon = icons.getQIcon(name) - return icon - return None - - def _createTooltipAttributes(self): - """ - Add key/value attributes that will be displayed in the item tooltip - - :param Dict[str,str] attributeDict: Key/value attributes - """ - attributeDict = collections.OrderedDict() - - if self.h5Class == silx.io.utils.H5Type.DATASET: - attributeDict["#Title"] = "HDF5 Dataset" - attributeDict["Name"] = self.basename - attributeDict["Path"] = self.obj.name - attributeDict["Shape"] = self._getFormatter().humanReadableShape(self.obj) - attributeDict["Value"] = self._getFormatter().humanReadableValue(self.obj) - attributeDict["Data type"] = self._getFormatter().humanReadableType(self.obj, full=True) - elif self.h5Class == silx.io.utils.H5Type.GROUP: - attributeDict["#Title"] = "HDF5 Group" - if self.nexusClassName: - attributeDict["NX_class"] = self.nexusClassName - attributeDict["Name"] = self.basename - attributeDict["Path"] = self.obj.name - elif self.h5Class == silx.io.utils.H5Type.FILE: - attributeDict["#Title"] = "HDF5 File" - attributeDict["Name"] = self.basename - attributeDict["Path"] = "/" - elif self.h5Class == silx.io.utils.H5Type.EXTERNAL_LINK: - attributeDict["#Title"] = "HDF5 External Link" - attributeDict["Name"] = self.basename - attributeDict["Path"] = self.obj.name - attributeDict["Linked path"] = self.obj.path - attributeDict["Linked file"] = self.obj.filename - elif self.h5Class == silx.io.utils.H5Type.SOFT_LINK: - attributeDict["#Title"] = "HDF5 Soft Link" - attributeDict["Name"] = self.basename - attributeDict["Path"] = self.obj.name - attributeDict["Linked path"] = self.obj.path - else: - pass - return attributeDict - - def _getDefaultTooltip(self): - """Returns the default tooltip - - :rtype: str - """ - if self.__error is not None: - self.obj # lazy loading of the object - return self.__error - - attrs = self._createTooltipAttributes() - title = attrs.pop("#Title", None) - if len(attrs) > 0: - tooltip = _utils.htmlFromDict(attrs, title=title) - else: - tooltip = "" - - return tooltip - - @property - def nexusClassName(self): - """Returns the Nexus class name""" - if self.__nx_class is None: - self.__nx_class = self.obj.attrs.get("NX_class", None) - if self.__nx_class is None: - self.__nx_class = "" - else: - if six.PY2: - self.__nx_class = self.__nx_class.decode() - elif not isinstance(self.__nx_class, str): - self.__nx_class = str(self.__nx_class, "UTF-8") - return self.__nx_class - - def dataName(self, role): - """Data for the name column""" - if role == qt.Qt.TextAlignmentRole: - return qt.Qt.AlignTop | qt.Qt.AlignLeft - if role == qt.Qt.DisplayRole: - return self.__text - if role == qt.Qt.DecorationRole: - return self._getDefaultIcon() - if role == qt.Qt.ToolTipRole: - return self._getDefaultTooltip() - return None - - def dataType(self, role): - """Data for the type column""" - if role == qt.Qt.DecorationRole: - return None - if role == qt.Qt.TextAlignmentRole: - return qt.Qt.AlignTop | qt.Qt.AlignLeft - if role == qt.Qt.DisplayRole: - if self.__error is not None: - return "" - class_ = self.h5Class - if self.isGroupObj(): - text = self.nexusClassName - elif class_ == silx.io.utils.H5Type.DATASET: - text = self._getFormatter().humanReadableType(self.obj) - else: - text = "" - return text - return None - - def dataShape(self, role): - """Data for the shape column""" - if role == qt.Qt.DecorationRole: - return None - if role == qt.Qt.TextAlignmentRole: - return qt.Qt.AlignTop | qt.Qt.AlignLeft - if role == qt.Qt.DisplayRole: - if self.__error is not None: - return "" - class_ = self.h5Class - if class_ != silx.io.utils.H5Type.DATASET: - return "" - return self._getFormatter().humanReadableShape(self.obj) - return None - - def dataValue(self, role): - """Data for the value column""" - if role == qt.Qt.DecorationRole: - return None - if role == qt.Qt.TextAlignmentRole: - return qt.Qt.AlignTop | qt.Qt.AlignLeft - if role == qt.Qt.DisplayRole: - if self.__error is not None: - return "" - if self.h5Class != silx.io.utils.H5Type.DATASET: - return "" - return self._getFormatter().humanReadableValue(self.obj) - return None - - def dataDescription(self, role): - """Data for the description column""" - if role == qt.Qt.DecorationRole: - return None - if role == qt.Qt.TextAlignmentRole: - return qt.Qt.AlignTop | qt.Qt.AlignLeft - if role == qt.Qt.DisplayRole: - if self.__isBroken: - self.obj # lazy loading of the object - return self.__error - if "desc" in self.obj.attrs: - text = self.obj.attrs["desc"] - else: - return "" - return text - if role == qt.Qt.ToolTipRole: - if self.__error is not None: - self.obj # lazy loading of the object - self.__initH5Object() - return self.__error - if "desc" in self.obj.attrs: - text = self.obj.attrs["desc"] - else: - return "" - return "Description: %s" % text - return None - - def dataNode(self, role): - """Data for the node column""" - if role == qt.Qt.DecorationRole: - return None - if role == qt.Qt.TextAlignmentRole: - return qt.Qt.AlignTop | qt.Qt.AlignLeft - if role == qt.Qt.DisplayRole: - if self.isBrokenObj(): - return "" - class_ = self.obj.__class__ - text = class_.__name__.split(".")[-1] - return text - if role == qt.Qt.ToolTipRole: - class_ = self.obj.__class__ - if class_ is None: - return "" - return "Class name: %s" % self.__class__ - return None - - def dataLink(self, role): - """Data for the link column - - Overwrite it to implement the content of the 'link' column. - - :rtype: qt.QVariant - """ - if role == qt.Qt.DecorationRole: - return None - if role == qt.Qt.TextAlignmentRole: - return qt.Qt.AlignTop | qt.Qt.AlignLeft - if role == qt.Qt.DisplayRole: - link = self.linkClass - if link is None: - return "" - elif link == silx.io.utils.H5Type.EXTERNAL_LINK: - return "External" - elif link == silx.io.utils.H5Type.SOFT_LINK: - return "Soft" - elif link == silx.io.utils.H5Type.HARD_LINK: - return "" - else: - return link.__name__ - if role == qt.Qt.ToolTipRole: - return None - return None diff --git a/silx/gui/hdf5/Hdf5LoadingItem.py b/silx/gui/hdf5/Hdf5LoadingItem.py deleted file mode 100644 index f11d252..0000000 --- a/silx/gui/hdf5/Hdf5LoadingItem.py +++ /dev/null @@ -1,77 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 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. -# -# ###########################################################################*/ - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "06/07/2018" - - -from .. import qt -from .Hdf5Node import Hdf5Node -import silx.io.utils - - -class Hdf5LoadingItem(Hdf5Node): - """Item displayed when an Hdf5Node is loading. - - At the end of the loading this item is replaced by the loaded one. - """ - - def __init__(self, text, parent, animatedIcon): - """Constructor""" - Hdf5Node.__init__(self, parent) - self.__text = text - self.__animatedIcon = animatedIcon - self.__animatedIcon.register(self) - - @property - def obj(self): - return None - - @property - def h5Class(self): - """Returns the class of the stored object. - - :rtype: silx.io.utils.H5Type - """ - return silx.io.utils.H5Type.FILE - - def dataName(self, role): - if role == qt.Qt.DecorationRole: - return self.__animatedIcon.currentIcon() - if role == qt.Qt.TextAlignmentRole: - return qt.Qt.AlignTop | qt.Qt.AlignLeft - if role == qt.Qt.DisplayRole: - return self.__text - return None - - def dataDescription(self, role): - if role == qt.Qt.DecorationRole: - return None - if role == qt.Qt.TextAlignmentRole: - return qt.Qt.AlignTop | qt.Qt.AlignLeft - if role == qt.Qt.DisplayRole: - return "Loading..." - return None diff --git a/silx/gui/hdf5/Hdf5Node.py b/silx/gui/hdf5/Hdf5Node.py deleted file mode 100644 index be16535..0000000 --- a/silx/gui/hdf5/Hdf5Node.py +++ /dev/null @@ -1,238 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 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. -# -# ###########################################################################*/ - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "24/07/2018" - -import weakref - - -class Hdf5Node(object): - """Abstract tree node - - It provides link to the childs and to the parents, and a link to an - external object. - """ - def __init__(self, parent=None, populateAll=False): - """ - Constructor - - :param Hdf5Node parent: Parent of the node, if exists, else None - :param bool populateAll: If true, populate all the tree node. Else - everything is lazy loaded. - """ - self.__child = None - self.__parent = None - if parent is not None: - self.__parent = weakref.ref(parent) - if populateAll: - self.__child = [] - self._populateChild(populateAll=True) - - def _getCanonicalName(self): - parent = self.parent - if parent is None: - return "root" - else: - return "%s/?" % (parent._getCanonicalName()) - - @property - def parent(self): - """Parent of the node, or None if the node is a root - - :rtype: Hdf5Node - """ - if self.__parent is None: - return None - parent = self.__parent() - if parent is None: - self.__parent = parent - return parent - - def setParent(self, parent): - """Redefine the parent of the node. - - It does not set the node as the children of the new parent. - - :param Hdf5Node parent: The new parent - """ - if parent is None: - self.__parent = None - else: - self.__parent = weakref.ref(parent) - - def appendChild(self, child): - """Append a child to the node. - - It does not update the parent of the child. - - :param Hdf5Node child: Child to append to the node. - """ - self.__initChild() - self.__child.append(child) - - def removeChildAtIndex(self, index): - """Remove a child at an index of the children list. - - The child is removed and returned. - - :param int index: Index in the child list. - :rtype: Hdf5Node - :raises: IndexError if list is empty or index is out of range. - """ - self.__initChild() - return self.__child.pop(index) - - def insertChild(self, index, child): - """ - Insert a child at a specific index of the child list. - - It does not update the parent of the child. - - :param int index: Index in the child list. - :param Hdf5Node child: Child to insert in the child list. - """ - self.__initChild() - self.__child.insert(index, child) - - def indexOfChild(self, child): - """ - Returns the index of the child in the child list of this node. - - :param Hdf5Node child: Child to find - :raises: ValueError if the value is not present. - """ - self.__initChild() - return self.__child.index(child) - - def hasChildren(self): - """Returns true if the node contains children. - - :rtype: bool - """ - return self.childCount() > 0 - - def childCount(self): - """Returns the number of child in this node. - - :rtype: int - """ - if self.__child is not None: - return len(self.__child) - return self._expectedChildCount() - - def child(self, index): - """Return the child at an expected index. - - :param int index: Index of the child in the child list of the node - :rtype: Hdf5Node - """ - self.__initChild() - return self.__child[index] - - def __initChild(self): - """Init the child of the node in case the list was lazy loaded.""" - if self.__child is None: - self.__child = [] - self._populateChild() - - def _expectedChildCount(self): - """Returns the expected count of children - - :rtype: int - """ - return 0 - - def _populateChild(self, populateAll=False): - """Recurse through an HDF5 structure to append groups an datasets - into the tree model. - - Overwrite it to implement the initialisation of child of the node. - """ - pass - - def dataName(self, role): - """Data for the name column - - Overwrite it to implement the content of the 'name' column. - - :rtype: qt.QVariant - """ - return None - - def dataType(self, role): - """Data for the type column - - Overwrite it to implement the content of the 'type' column. - - :rtype: qt.QVariant - """ - return None - - def dataShape(self, role): - """Data for the shape column - - Overwrite it to implement the content of the 'shape' column. - - :rtype: qt.QVariant - """ - return None - - def dataValue(self, role): - """Data for the value column - - Overwrite it to implement the content of the 'value' column. - - :rtype: qt.QVariant - """ - return None - - def dataDescription(self, role): - """Data for the description column - - Overwrite it to implement the content of the 'description' column. - - :rtype: qt.QVariant - """ - return None - - def dataNode(self, role): - """Data for the node column - - Overwrite it to implement the content of the 'node' column. - - :rtype: qt.QVariant - """ - return None - - def dataLink(self, role): - """Data for the link column - - Overwrite it to implement the content of the 'link' column. - - :rtype: qt.QVariant - """ - return None diff --git a/silx/gui/hdf5/Hdf5TreeModel.py b/silx/gui/hdf5/Hdf5TreeModel.py deleted file mode 100644 index 438200b..0000000 --- a/silx/gui/hdf5/Hdf5TreeModel.py +++ /dev/null @@ -1,779 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2016-2018 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. -# -# ###########################################################################*/ - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "08/10/2018" - - -import os -import logging -import functools -from .. import qt -from .. import icons -from .Hdf5Node import Hdf5Node -from .Hdf5Item import Hdf5Item -from .Hdf5LoadingItem import Hdf5LoadingItem -from . import _utils -from ... import io as silx_io - -_logger = logging.getLogger(__name__) - -"""Helpers to take care of None objects as signal parameters. -PySide crash if a signal with a None parameter is emitted between threads. -""" -if qt.BINDING == 'PySide': - class _NoneWraper(object): - pass - _NoneWraperInstance = _NoneWraper() - - def _wrapNone(x): - """Wrap x if it is a None value, else returns x""" - if x is None: - return _NoneWraperInstance - else: - return x - - def _unwrapNone(x): - """Unwrap x as a None if a None was stored by `wrapNone`, else returns - x""" - if x is _NoneWraperInstance: - return None - else: - return x -else: - # Allow to fix None event params to avoid PySide crashes - def _wrapNone(x): - return x - - def _unwrapNone(x): - return x - - -def _createRootLabel(h5obj): - """ - Create label for the very first npde of the tree. - - :param h5obj: The h5py object to display in the GUI - :type h5obj: h5py-like object - :rtpye: str - """ - if silx_io.is_file(h5obj): - label = os.path.basename(h5obj.filename) - else: - filename = os.path.basename(h5obj.file.filename) - path = h5obj.name - if path.startswith("/"): - path = path[1:] - label = "%s::%s" % (filename, path) - return label - - -class LoadingItemRunnable(qt.QRunnable): - """Runner to process item loading from a file""" - - class __Signals(qt.QObject): - """Signal holder""" - itemReady = qt.Signal(object, object, object) - runnerFinished = qt.Signal(object) - - def __init__(self, filename, item): - """Constructor - - :param LoadingItemWorker worker: Object holding data and signals - """ - super(LoadingItemRunnable, self).__init__() - self.filename = filename - self.oldItem = item - self.signals = self.__Signals() - - def setFile(self, filename, item): - self.filenames.append((filename, item)) - - @property - def itemReady(self): - return self.signals.itemReady - - @property - def runnerFinished(self): - return self.signals.runnerFinished - - def __loadItemTree(self, oldItem, h5obj): - """Create an item tree used by the GUI from an h5py object. - - :param Hdf5Node oldItem: The current item displayed the GUI - :param h5py.File h5obj: The h5py object to display in the GUI - :rtpye: Hdf5Node - """ - text = _createRootLabel(h5obj) - item = Hdf5Item(text=text, obj=h5obj, parent=oldItem.parent, populateAll=True) - return item - - def run(self): - """Process the file loading. The worker is used as holder - of the data and the signal. The result is sent as a signal. - """ - h5file = None - try: - h5file = silx_io.open(self.filename) - newItem = self.__loadItemTree(self.oldItem, h5file) - error = None - except IOError as e: - # Should be logged - error = e - newItem = None - if h5file is not None: - h5file.close() - - # Take care of None value in case of PySide - newItem = _wrapNone(newItem) - error = _wrapNone(error) - self.itemReady.emit(self.oldItem, newItem, error) - self.runnerFinished.emit(self) - - def autoDelete(self): - return True - - -class Hdf5TreeModel(qt.QAbstractItemModel): - """Tree model storing a list of :class:`h5py.File` like objects. - - The main column display the :class:`h5py.File` list and there hierarchy. - Other columns display information on node hierarchy. - """ - - H5PY_ITEM_ROLE = qt.Qt.UserRole - """Role to reach h5py item from an item index""" - - H5PY_OBJECT_ROLE = qt.Qt.UserRole + 1 - """Role to reach h5py object from an item index""" - - USER_ROLE = qt.Qt.UserRole + 2 - """Start of range of available user role for derivative models""" - - NAME_COLUMN = 0 - """Column id containing HDF5 node names""" - - TYPE_COLUMN = 1 - """Column id containing HDF5 dataset types""" - - SHAPE_COLUMN = 2 - """Column id containing HDF5 dataset shapes""" - - VALUE_COLUMN = 3 - """Column id containing HDF5 dataset values""" - - DESCRIPTION_COLUMN = 4 - """Column id containing HDF5 node description/title/message""" - - NODE_COLUMN = 5 - """Column id containing HDF5 node type""" - - LINK_COLUMN = 6 - """Column id containing HDF5 link type""" - - COLUMN_IDS = [ - NAME_COLUMN, - TYPE_COLUMN, - SHAPE_COLUMN, - VALUE_COLUMN, - DESCRIPTION_COLUMN, - NODE_COLUMN, - LINK_COLUMN, - ] - """List of logical columns available""" - - sigH5pyObjectLoaded = qt.Signal(object) - """Emitted when a new root item was loaded and inserted to the model.""" - - sigH5pyObjectRemoved = qt.Signal(object) - """Emitted when a root item is removed from the model.""" - - sigH5pyObjectSynchronized = qt.Signal(object, object) - """Emitted when an item was synchronized.""" - - def __init__(self, parent=None, ownFiles=True): - """ - Constructor - - :param qt.QWidget parent: Parent widget - :param bool ownFiles: If true (default) the model will manage the files - life cycle when they was added using path (like DnD). - """ - super(Hdf5TreeModel, self).__init__(parent) - - self.header_labels = [None] * len(self.COLUMN_IDS) - self.header_labels[self.NAME_COLUMN] = 'Name' - self.header_labels[self.TYPE_COLUMN] = 'Type' - self.header_labels[self.SHAPE_COLUMN] = 'Shape' - self.header_labels[self.VALUE_COLUMN] = 'Value' - self.header_labels[self.DESCRIPTION_COLUMN] = 'Description' - self.header_labels[self.NODE_COLUMN] = 'Node' - self.header_labels[self.LINK_COLUMN] = 'Link' - - # Create items - self.__root = Hdf5Node() - self.__fileDropEnabled = True - self.__fileMoveEnabled = True - self.__datasetDragEnabled = False - - self.__animatedIcon = icons.getWaitIcon() - self.__animatedIcon.iconChanged.connect(self.__updateLoadingItems) - self.__runnerSet = set([]) - - # store used icons to avoid the cache to release it - self.__icons = [] - self.__icons.append(icons.getQIcon("item-none")) - self.__icons.append(icons.getQIcon("item-0dim")) - self.__icons.append(icons.getQIcon("item-1dim")) - self.__icons.append(icons.getQIcon("item-2dim")) - self.__icons.append(icons.getQIcon("item-3dim")) - self.__icons.append(icons.getQIcon("item-ndim")) - - self.__ownFiles = ownFiles - self.__openedFiles = [] - """Store the list of files opened by the model itself.""" - # FIXME: It should be managed one by one by Hdf5Item itself - - # It is not possible to override the QObject destructor nor - # to access to the content of the Python object with the `destroyed` - # signal cause the Python method was already removed with the QWidget, - # while the QObject still exists. - # We use a static method plus explicit references to objects to - # release. The callback do not use any ref to self. - onDestroy = functools.partial(self._closeFileList, self.__openedFiles) - self.destroyed.connect(onDestroy) - - @staticmethod - def _closeFileList(fileList): - """Static method to close explicit references to internal objects.""" - _logger.debug("Clear Hdf5TreeModel") - for obj in fileList: - _logger.debug("Close file %s", obj.filename) - obj.close() - fileList[:] = [] - - def _closeOpened(self): - """Close files which was opened by this model. - - File are opened by the model when it was inserted using - `insertFileAsync`, `insertFile`, `appendFile`.""" - self._closeFileList(self.__openedFiles) - - def __updateLoadingItems(self, icon): - for i in range(self.__root.childCount()): - item = self.__root.child(i) - if isinstance(item, Hdf5LoadingItem): - index1 = self.index(i, 0, qt.QModelIndex()) - index2 = self.index(i, self.columnCount() - 1, qt.QModelIndex()) - self.dataChanged.emit(index1, index2) - - def __itemReady(self, oldItem, newItem, error): - """Called at the end of a concurent file loading, when the loading - item is ready. AN error is defined if an exception occured when - loading the newItem . - - :param Hdf5Node oldItem: current displayed item - :param Hdf5Node newItem: item loaded, or None if error is defined - :param Exception error: An exception, or None if newItem is defined - """ - # Take care of None value in case of PySide - newItem = _unwrapNone(newItem) - error = _unwrapNone(error) - row = self.__root.indexOfChild(oldItem) - - rootIndex = qt.QModelIndex() - self.beginRemoveRows(rootIndex, row, row) - self.__root.removeChildAtIndex(row) - self.endRemoveRows() - - if newItem is not None: - rootIndex = qt.QModelIndex() - if self.__ownFiles: - self.__openedFiles.append(newItem.obj) - self.beginInsertRows(rootIndex, row, row) - self.__root.insertChild(row, newItem) - self.endInsertRows() - - if isinstance(oldItem, Hdf5LoadingItem): - self.sigH5pyObjectLoaded.emit(newItem.obj) - else: - self.sigH5pyObjectSynchronized.emit(oldItem.obj, newItem.obj) - - # FIXME the error must be displayed - - def isFileDropEnabled(self): - return self.__fileDropEnabled - - def setFileDropEnabled(self, enabled): - self.__fileDropEnabled = enabled - - fileDropEnabled = qt.Property(bool, isFileDropEnabled, setFileDropEnabled) - """Property to enable/disable file dropping in the model.""" - - def isDatasetDragEnabled(self): - return self.__datasetDragEnabled - - def setDatasetDragEnabled(self, enabled): - self.__datasetDragEnabled = enabled - - datasetDragEnabled = qt.Property(bool, isDatasetDragEnabled, setDatasetDragEnabled) - """Property to enable/disable drag of datasets.""" - - def isFileMoveEnabled(self): - return self.__fileMoveEnabled - - def setFileMoveEnabled(self, enabled): - self.__fileMoveEnabled = enabled - - fileMoveEnabled = qt.Property(bool, isFileMoveEnabled, setFileMoveEnabled) - """Property to enable/disable drag-and-drop of files to - change the ordering in the model.""" - - def supportedDropActions(self): - if self.__fileMoveEnabled or self.__fileDropEnabled: - return qt.Qt.CopyAction | qt.Qt.MoveAction - else: - return 0 - - def mimeTypes(self): - types = [] - if self.__fileMoveEnabled: - types.append(_utils.Hdf5NodeMimeData.MIME_TYPE) - if self.__datasetDragEnabled: - types.append(_utils.Hdf5DatasetMimeData.MIME_TYPE) - return types - - def mimeData(self, indexes): - """ - Returns an object that contains serialized items of data corresponding - to the list of indexes specified. - - :param List[qt.QModelIndex] indexes: List of indexes - :rtype: qt.QMimeData - """ - if len(indexes) == 0: - return None - - indexes = [i for i in indexes if i.column() == 0] - if len(indexes) > 1: - raise NotImplementedError("Drag of multi rows is not implemented") - if len(indexes) == 0: - raise NotImplementedError("Drag of cell is not implemented") - - node = self.nodeFromIndex(indexes[0]) - - if self.__fileMoveEnabled and node.parent is self.__root: - mimeData = _utils.Hdf5NodeMimeData(node=node) - elif self.__datasetDragEnabled: - mimeData = _utils.Hdf5DatasetMimeData(node=node) - else: - mimeData = None - return mimeData - - def flags(self, index): - defaultFlags = qt.QAbstractItemModel.flags(self, index) - - if index.isValid(): - node = self.nodeFromIndex(index) - if self.__fileMoveEnabled and node.parent is self.__root: - # that's a root - return qt.Qt.ItemIsDragEnabled | defaultFlags - elif self.__datasetDragEnabled: - return qt.Qt.ItemIsDragEnabled | defaultFlags - return defaultFlags - elif self.__fileDropEnabled or self.__fileMoveEnabled: - return qt.Qt.ItemIsDropEnabled | defaultFlags - else: - return defaultFlags - - def dropMimeData(self, mimedata, action, row, column, parentIndex): - if action == qt.Qt.IgnoreAction: - return True - - if self.__fileMoveEnabled and mimedata.hasFormat(_utils.Hdf5NodeMimeData.MIME_TYPE): - dragNode = mimedata.node() - parentNode = self.nodeFromIndex(parentIndex) - if parentNode is not dragNode.parent: - return False - - if row == -1: - # append to the parent - row = parentNode.childCount() - else: - # insert at row - pass - - dragNodeParent = dragNode.parent - sourceRow = dragNodeParent.indexOfChild(dragNode) - self.moveRow(parentIndex, sourceRow, parentIndex, row) - return True - - if self.__fileDropEnabled and mimedata.hasFormat("text/uri-list"): - - parentNode = self.nodeFromIndex(parentIndex) - if parentNode is not self.__root: - while(parentNode is not self.__root): - node = parentNode - parentNode = node.parent - row = parentNode.indexOfChild(node) - else: - if row == -1: - row = self.__root.childCount() - - messages = [] - for url in mimedata.urls(): - try: - self.insertFileAsync(url.toLocalFile(), row) - row += 1 - except IOError as e: - messages.append(e.args[0]) - if len(messages) > 0: - title = "Error occurred when loading files" - message = "<html>%s:<ul><li>%s</li><ul></html>" % (title, "</li><li>".join(messages)) - qt.QMessageBox.critical(None, title, message) - return True - - return False - - def headerData(self, section, orientation, role=qt.Qt.DisplayRole): - if orientation == qt.Qt.Horizontal: - if role in [qt.Qt.DisplayRole, qt.Qt.EditRole]: - return self.header_labels[section] - return None - - def insertNode(self, row, node): - if row == -1: - row = self.__root.childCount() - self.beginInsertRows(qt.QModelIndex(), row, row) - self.__root.insertChild(row, node) - self.endInsertRows() - - def moveRow(self, sourceParentIndex, sourceRow, destinationParentIndex, destinationRow): - if sourceRow == destinationRow or sourceRow == destinationRow - 1: - # abort move, same place - return - return self.moveRows(sourceParentIndex, sourceRow, 1, destinationParentIndex, destinationRow) - - def moveRows(self, sourceParentIndex, sourceRow, count, destinationParentIndex, destinationRow): - self.beginMoveRows(sourceParentIndex, sourceRow, sourceRow, destinationParentIndex, destinationRow) - sourceNode = self.nodeFromIndex(sourceParentIndex) - destinationNode = self.nodeFromIndex(destinationParentIndex) - - if sourceNode is destinationNode and sourceRow < destinationRow: - item = sourceNode.child(sourceRow) - destinationNode.insertChild(destinationRow, item) - sourceNode.removeChildAtIndex(sourceRow) - else: - item = sourceNode.removeChildAtIndex(sourceRow) - destinationNode.insertChild(destinationRow, item) - - self.endMoveRows() - return True - - def index(self, row, column, parent=qt.QModelIndex()): - try: - node = self.nodeFromIndex(parent) - return self.createIndex(row, column, node.child(row)) - except IndexError: - return qt.QModelIndex() - - def data(self, index, role=qt.Qt.DisplayRole): - node = self.nodeFromIndex(index) - - if role == self.H5PY_ITEM_ROLE: - return node - - if role == self.H5PY_OBJECT_ROLE: - return node.obj - - if index.column() == self.NAME_COLUMN: - return node.dataName(role) - elif index.column() == self.TYPE_COLUMN: - return node.dataType(role) - elif index.column() == self.SHAPE_COLUMN: - return node.dataShape(role) - elif index.column() == self.VALUE_COLUMN: - return node.dataValue(role) - elif index.column() == self.DESCRIPTION_COLUMN: - return node.dataDescription(role) - elif index.column() == self.NODE_COLUMN: - return node.dataNode(role) - elif index.column() == self.LINK_COLUMN: - return node.dataLink(role) - else: - return None - - def columnCount(self, parent=qt.QModelIndex()): - return len(self.COLUMN_IDS) - - def hasChildren(self, parent=qt.QModelIndex()): - node = self.nodeFromIndex(parent) - if node is None: - return 0 - return node.hasChildren() - - def rowCount(self, parent=qt.QModelIndex()): - node = self.nodeFromIndex(parent) - if node is None: - return 0 - return node.childCount() - - def parent(self, child): - if not child.isValid(): - return qt.QModelIndex() - - node = self.nodeFromIndex(child) - - if node is None: - return qt.QModelIndex() - - parent = node.parent - - if parent is None: - return qt.QModelIndex() - - grandparent = parent.parent - if grandparent is None: - return qt.QModelIndex() - row = grandparent.indexOfChild(parent) - - assert row != - 1 - return self.createIndex(row, 0, parent) - - def nodeFromIndex(self, index): - return index.internalPointer() if index.isValid() else self.__root - - def _closeFileIfOwned(self, node): - """"Close the file if it was loaded from a filename or a - drag-and-drop""" - obj = node.obj - for f in self.__openedFiles: - if f in obj: - _logger.debug("Close file %s", obj.filename) - obj.close() - self.__openedFiles.remove(obj) - - def synchronizeIndex(self, index): - """ - Synchronize a file a given its index. - - Basically close it and load it again. - - :param qt.QModelIndex index: Index of the item to update - """ - node = self.nodeFromIndex(index) - if node.parent is not self.__root: - return - - filename = node.obj.filename - self.insertFileAsync(filename, index.row(), synchronizingNode=node) - - def h5pyObjectRow(self, h5pyObject): - for row in range(self.__root.childCount()): - item = self.__root.child(row) - if item.obj == h5pyObject: - return row - return -1 - - def synchronizeH5pyObject(self, h5pyObject): - """ - Synchronize a h5py object in all the tree. - - Basically close it and load it again. - - :param h5py.File h5pyObject: A :class:`h5py.File` object. - """ - index = 0 - while index < self.__root.childCount(): - item = self.__root.child(index) - if item.obj == h5pyObject: - qindex = self.index(index, 0, qt.QModelIndex()) - self.synchronizeIndex(qindex) - index += 1 - - def removeIndex(self, index): - """ - Remove an item from the model using its index. - - :param qt.QModelIndex index: Index of the item to remove - """ - node = self.nodeFromIndex(index) - if node.parent != self.__root: - return - self._closeFileIfOwned(node) - self.beginRemoveRows(qt.QModelIndex(), index.row(), index.row()) - self.__root.removeChildAtIndex(index.row()) - self.endRemoveRows() - self.sigH5pyObjectRemoved.emit(node.obj) - - def removeH5pyObject(self, h5pyObject): - """ - Remove an item from the model using the holding h5py object. - It can remove more than one item. - - :param h5py.File h5pyObject: A :class:`h5py.File` object. - """ - index = 0 - while index < self.__root.childCount(): - item = self.__root.child(index) - if item.obj == h5pyObject: - qindex = self.index(index, 0, qt.QModelIndex()) - self.removeIndex(qindex) - else: - index += 1 - - def insertH5pyObject(self, h5pyObject, text=None, row=-1): - """Append an HDF5 object from h5py to the tree. - - :param h5pyObject: File handle/descriptor for a :class:`h5py.File` - or any other class of h5py file structure. - """ - if text is None: - text = _createRootLabel(h5pyObject) - if row == -1: - row = self.__root.childCount() - self.insertNode(row, Hdf5Item(text=text, obj=h5pyObject, parent=self.__root)) - - def hasPendingOperations(self): - return len(self.__runnerSet) > 0 - - def insertFileAsync(self, filename, row=-1, synchronizingNode=None): - if not os.path.isfile(filename): - raise IOError("Filename '%s' must be a file path" % filename) - - # create temporary item - if synchronizingNode is None: - text = os.path.basename(filename) - item = Hdf5LoadingItem(text=text, parent=self.__root, animatedIcon=self.__animatedIcon) - self.insertNode(row, item) - else: - item = synchronizingNode - - # start loading the real one - runnable = LoadingItemRunnable(filename, item) - runnable.itemReady.connect(self.__itemReady) - runnable.runnerFinished.connect(self.__releaseRunner) - self.__runnerSet.add(runnable) - qt.silxGlobalThreadPool().start(runnable) - - def __releaseRunner(self, runner): - self.__runnerSet.remove(runner) - - def insertFile(self, filename, row=-1): - """Load a HDF5 file into the data model. - - :param filename: file path. - """ - try: - h5file = silx_io.open(filename) - if self.__ownFiles: - self.__openedFiles.append(h5file) - self.sigH5pyObjectLoaded.emit(h5file) - self.insertH5pyObject(h5file, row=row) - except IOError: - _logger.debug("File '%s' can't be read.", filename, exc_info=True) - raise - - def clear(self): - """Remove all the content of the model""" - for _ in range(self.rowCount()): - qindex = self.index(0, 0, qt.QModelIndex()) - self.removeIndex(qindex) - - def appendFile(self, filename): - self.insertFile(filename, -1) - - def indexFromH5Object(self, h5Object): - """Returns a model index from an h5py-like object. - - :param object h5Object: An h5py-like object - :rtype: qt.QModelIndex - """ - if h5Object is None: - return qt.QModelIndex() - - filename = h5Object.file.filename - - # Seach for the right roots - rootIndices = [] - for index in range(self.rowCount(qt.QModelIndex())): - index = self.index(index, 0, qt.QModelIndex()) - obj = self.data(index, Hdf5TreeModel.H5PY_OBJECT_ROLE) - if obj.file.filename == filename: - # We can have many roots with different subtree of the same - # root - rootIndices.append(index) - - if len(rootIndices) == 0: - # No root found - return qt.QModelIndex() - - path = h5Object.name + "/" - path = path.replace("//", "/") - - # Search for the right node - found = False - foundIndices = [] - for _ in range(1000 * len(rootIndices)): - # Avoid too much iterations, in case of recurssive links - if len(foundIndices) == 0: - if len(rootIndices) == 0: - # Nothing found - break - # Start fron a new root - foundIndices.append(rootIndices.pop(0)) - - obj = self.data(index, Hdf5TreeModel.H5PY_OBJECT_ROLE) - p = obj.name + "/" - p = p.replace("//", "/") - if path == p: - found = True - break - - parentIndex = foundIndices[-1] - for index in range(self.rowCount(parentIndex)): - index = self.index(index, 0, parentIndex) - obj = self.data(index, Hdf5TreeModel.H5PY_OBJECT_ROLE) - - p = obj.name + "/" - p = p.replace("//", "/") - if path == p: - foundIndices.append(index) - found = True - break - elif path.startswith(p): - foundIndices.append(index) - break - else: - # Nothing found, start again with another root - foundIndices = [] - - if found: - break - - if found: - return foundIndices[-1] - return qt.QModelIndex() diff --git a/silx/gui/hdf5/Hdf5TreeView.py b/silx/gui/hdf5/Hdf5TreeView.py deleted file mode 100644 index a86140a..0000000 --- a/silx/gui/hdf5/Hdf5TreeView.py +++ /dev/null @@ -1,271 +0,0 @@ -# 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. -# -# ###########################################################################*/ - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "30/04/2018" - - -import logging -from .. import qt -from ...utils import weakref as silxweakref -from .Hdf5TreeModel import Hdf5TreeModel -from .Hdf5HeaderView import Hdf5HeaderView -from .NexusSortFilterProxyModel import NexusSortFilterProxyModel -from .Hdf5Item import Hdf5Item -from . import _utils - -_logger = logging.getLogger(__name__) - - -class Hdf5TreeView(qt.QTreeView): - """TreeView which allow to browse HDF5 file structure. - - .. image:: img/Hdf5TreeView.png - - It provides columns width auto-resizing and additional - signals. - - The default model is a :class:`NexusSortFilterProxyModel` sourcing - a :class:`Hdf5TreeModel`. The :class:`Hdf5TreeModel` is reachable using - :meth:`findHdf5TreeModel`. The default header is :class:`Hdf5HeaderView`. - - Context menu is managed by the :meth:`setContextMenuPolicy` with the value - Qt.CustomContextMenu. This policy must not be changed, otherwise context - menus will not work anymore. You can use :meth:`addContextMenuCallback` and - :meth:`removeContextMenuCallback` to add your custum actions according - to the selected objects. - """ - def __init__(self, parent=None): - """ - Constructor - - :param parent qt.QWidget: The parent widget - """ - qt.QTreeView.__init__(self, parent) - - model = self.createDefaultModel() - self.setModel(model) - - self.setHeader(Hdf5HeaderView(qt.Qt.Horizontal, self)) - self.setSelectionBehavior(qt.QAbstractItemView.SelectRows) - self.sortByColumn(0, qt.Qt.AscendingOrder) - # optimise the rendering - self.setUniformRowHeights(True) - - self.setIconSize(qt.QSize(16, 16)) - self.setAcceptDrops(True) - self.setDragEnabled(True) - self.setDragDropMode(qt.QAbstractItemView.DragDrop) - self.showDropIndicator() - - self.__context_menu_callbacks = silxweakref.WeakList() - self.setContextMenuPolicy(qt.Qt.CustomContextMenu) - self.customContextMenuRequested.connect(self._createContextMenu) - - def createDefaultModel(self): - """Creates and returns the default model. - - Inherite to custom the default model""" - model = Hdf5TreeModel(self) - proxy_model = NexusSortFilterProxyModel(self) - proxy_model.setSourceModel(model) - return proxy_model - - def __removeContextMenuProxies(self, ref): - """Callback to remove dead proxy from the list""" - self.__context_menu_callbacks.remove(ref) - - def _createContextMenu(self, pos): - """ - Create context menu. - - :param pos qt.QPoint: Position of the context menu - """ - actions = [] - - menu = qt.QMenu(self) - - hovered_index = self.indexAt(pos) - hovered_node = self.model().data(hovered_index, Hdf5TreeModel.H5PY_ITEM_ROLE) - if hovered_node is None or not isinstance(hovered_node, Hdf5Item): - return - - hovered_object = _utils.H5Node(hovered_node) - event = _utils.Hdf5ContextMenuEvent(self, menu, hovered_object) - - for callback in self.__context_menu_callbacks: - try: - callback(event) - except KeyboardInterrupt: - raise - except Exception: - # make sure no user callback crash the application - _logger.error("Error while calling callback", exc_info=True) - pass - - if not menu.isEmpty(): - for action in actions: - menu.addAction(action) - menu.popup(self.viewport().mapToGlobal(pos)) - - def addContextMenuCallback(self, callback): - """Register a context menu callback. - - The callback will be called when a context menu is requested with the - treeview and the list of selected h5py objects in parameters. The - callback must return a list of :class:`qt.QAction` object. - - Callbacks are stored as saferef. The object must store a reference by - itself. - """ - self.__context_menu_callbacks.append(callback) - - def removeContextMenuCallback(self, callback): - """Unregister a context menu callback""" - self.__context_menu_callbacks.remove(callback) - - def findHdf5TreeModel(self): - """Find the Hdf5TreeModel from the stack of model filters. - - :returns: A Hdf5TreeModel, else None - :rtype: Hdf5TreeModel - """ - model = self.model() - while model is not None: - if isinstance(model, qt.QAbstractProxyModel): - model = model.sourceModel() - else: - break - if model is None: - return None - if isinstance(model, Hdf5TreeModel): - return model - else: - return None - - def dragEnterEvent(self, event): - model = self.findHdf5TreeModel() - if model is not None and model.isFileDropEnabled() and event.mimeData().hasFormat("text/uri-list"): - self.setState(qt.QAbstractItemView.DraggingState) - event.accept() - else: - qt.QTreeView.dragEnterEvent(self, event) - - def dragMoveEvent(self, event): - model = self.findHdf5TreeModel() - if model is not None and model.isFileDropEnabled() and event.mimeData().hasFormat("text/uri-list"): - event.setDropAction(qt.Qt.CopyAction) - event.accept() - else: - qt.QTreeView.dragMoveEvent(self, event) - - def selectedH5Nodes(self, ignoreBrokenLinks=True): - """Returns selected h5py objects like :class:`h5py.File`, - :class:`h5py.Group`, :class:`h5py.Dataset` or mimicked objects. - - :param ignoreBrokenLinks bool: Returns objects which are not not - broken links. - :rtype: iterator(:class:`_utils.H5Node`) - """ - for index in self.selectedIndexes(): - if index.column() != 0: - continue - item = self.model().data(index, Hdf5TreeModel.H5PY_ITEM_ROLE) - if item is None: - continue - if isinstance(item, Hdf5Item): - if ignoreBrokenLinks and item.isBrokenObj(): - continue - yield _utils.H5Node(item) - - def __intermediateModels(self, index): - """Returns intermediate models from the view model to the - model of the index.""" - models = [] - targetModel = index.model() - model = self.model() - while model is not None: - if model is targetModel: - # found - return models - models.append(model) - if isinstance(model, qt.QAbstractProxyModel): - model = model.sourceModel() - else: - break - raise RuntimeError("Model from the requested index is not reachable from this view") - - def mapToModel(self, index): - """Map an index from any model reachable by the view to an index from - the very first model connected to the view. - - :param qt.QModelIndex index: Index from the Hdf5Tree model - :rtype: qt.QModelIndex - :return: Index from the model connected to the view - """ - if not index.isValid(): - return index - models = self.__intermediateModels(index) - for model in reversed(models): - index = model.mapFromSource(index) - return index - - def setSelectedH5Node(self, h5Object): - """ - Select the specified node of the tree using an h5py node. - - - If the item is found, parent items are expended, and then the item - is selected. - - If the item is not found, the selection do not change. - - A none argument allow to deselect everything - - :param h5py.Node h5Object: The node to select - """ - if h5Object is None: - self.setCurrentIndex(qt.QModelIndex()) - return - - model = self.findHdf5TreeModel() - index = model.indexFromH5Object(h5Object) - index = self.mapToModel(index) - if index.isValid(): - # Update the GUI - i = index - while i.isValid(): - self.expand(i) - i = i.parent() - self.setCurrentIndex(index) - - def mousePressEvent(self, event): - """Override mousePressEvent to provide a consistante compatible API - between Qt4 and Qt5 - """ - super(Hdf5TreeView, self).mousePressEvent(event) - if event.button() != qt.Qt.LeftButton: - # Qt5 only sends itemClicked on left button mouse click - if qt.qVersion() > "5": - qindex = self.indexAt(event.pos()) - self.clicked.emit(qindex) diff --git a/silx/gui/hdf5/NexusSortFilterProxyModel.py b/silx/gui/hdf5/NexusSortFilterProxyModel.py deleted file mode 100644 index 216e992..0000000 --- a/silx/gui/hdf5/NexusSortFilterProxyModel.py +++ /dev/null @@ -1,222 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2016-2018 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. -# -# ###########################################################################*/ - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "24/07/2018" - - -import logging -import re -import numpy -from .. import qt -from .Hdf5TreeModel import Hdf5TreeModel -import silx.io.utils -from silx.gui import icons - - -_logger = logging.getLogger(__name__) - - -class NexusSortFilterProxyModel(qt.QSortFilterProxyModel): - """Try to sort items according to Nexus structure. Else sort by name.""" - - def __init__(self, parent=None): - qt.QSortFilterProxyModel.__init__(self, parent) - self.__split = re.compile("(\\d+|\\D+)") - self.__iconCache = {} - - def hasChildren(self, parent): - """Returns true if parent has any children; otherwise returns false. - - :param qt.QModelIndex parent: Index of the item to check - :rtype: bool - """ - parent = self.mapToSource(parent) - return self.sourceModel().hasChildren(parent) - - def rowCount(self, parent): - """Returns the number of rows under the given parent. - - :param qt.QModelIndex parent: Index of the item to check - :rtype: int - """ - parent = self.mapToSource(parent) - return self.sourceModel().rowCount(parent) - - def lessThan(self, sourceLeft, sourceRight): - """Returns True if the value of the item referred to by the given - index `sourceLeft` is less than the value of the item referred to by - the given index `sourceRight`, otherwise returns false. - - :param qt.QModelIndex sourceLeft: - :param qt.QModelIndex sourceRight: - :rtype: bool - """ - if sourceLeft.column() != Hdf5TreeModel.NAME_COLUMN: - return super(NexusSortFilterProxyModel, self).lessThan( - sourceLeft, sourceRight) - - # Do not sort child of root (files) - if sourceLeft.parent() == qt.QModelIndex(): - return sourceLeft.row() < sourceRight.row() - - left = self.sourceModel().data(sourceLeft, Hdf5TreeModel.H5PY_ITEM_ROLE) - right = self.sourceModel().data(sourceRight, Hdf5TreeModel.H5PY_ITEM_ROLE) - - if self.__isNXentry(left) and self.__isNXentry(right): - less = self.childDatasetLessThan(left, right, "start_time") - if less is not None: - return less - less = self.childDatasetLessThan(left, right, "end_time") - if less is not None: - return less - - left = self.sourceModel().data(sourceLeft, qt.Qt.DisplayRole) - right = self.sourceModel().data(sourceRight, qt.Qt.DisplayRole) - return self.nameLessThan(left, right) - - def __isNXentry(self, node): - """Returns true if the node is an NXentry""" - class_ = node.h5Class - if class_ is None or class_ != silx.io.utils.H5Type.GROUP: - return False - nxClass = node.obj.attrs.get("NX_class", None) - return nxClass == "NXentry" - - def __isNXnode(self, node): - """Returns true if the node is an NX concept""" - class_ = node.h5Class - if class_ is None or class_ != silx.io.utils.H5Type.GROUP: - return False - nxClass = node.obj.attrs.get("NX_class", None) - return nxClass is not None - - def getWordsAndNumbers(self, name): - """ - Returns a list of words and integers composing the name. - - An input `"aaa10bbb50.30"` will return - `["aaa", 10, "bbb", 50, ".", 30]`. - - :param str name: A name - :rtype: List - """ - nonSensitive = self.sortCaseSensitivity() == qt.Qt.CaseInsensitive - words = self.__split.findall(name) - result = [] - for i in words: - if i[0].isdigit(): - i = int(i) - elif nonSensitive: - i = i.lower() - result.append(i) - return result - - def nameLessThan(self, left, right): - """Returns True if the left string is less than the right string. - - Number composing the names are compared as integers, as result "name2" - is smaller than "name10". - - :param str left: A string - :param str right: A string - :rtype: bool - """ - leftList = self.getWordsAndNumbers(left) - rightList = self.getWordsAndNumbers(right) - try: - return leftList < rightList - except TypeError: - # Back to string comparison if list are not type consistent - return left < right - - def childDatasetLessThan(self, left, right, childName): - """ - Reach the same children name of two items and compare their values. - - Returns True if the left one is smaller than the right one. - - :param Hdf5Item left: An item - :param Hdf5Item right: An item - :param str childName: Name of the children to search. Returns None if - the children is not found. - :rtype: bool - """ - try: - left_time = left.obj[childName][()] - right_time = right.obj[childName][()] - if isinstance(left_time, numpy.ndarray): - return left_time[0] < right_time[0] - return left_time < right_time - except KeyboardInterrupt: - raise - except Exception: - _logger.debug("Exception occurred", exc_info=True) - return None - - def __createCompoundIcon(self, backgroundIcon, foregroundIcon): - icon = qt.QIcon() - - sizes = backgroundIcon.availableSizes() - sizes = sorted(sizes, key=lambda s: s.height()) - sizes = filter(lambda s: s.height() < 100, sizes) - sizes = list(sizes) - if len(sizes) > 0: - baseSize = sizes[-1] - else: - baseSize = qt.QSize(32, 32) - - modes = [qt.QIcon.Normal, qt.QIcon.Disabled] - for mode in modes: - pixmap = qt.QPixmap(baseSize) - pixmap.fill(qt.Qt.transparent) - painter = qt.QPainter(pixmap) - painter.drawPixmap(0, 0, backgroundIcon.pixmap(baseSize, mode=mode)) - painter.drawPixmap(0, 0, foregroundIcon.pixmap(baseSize, mode=mode)) - painter.end() - icon.addPixmap(pixmap, mode=mode) - - return icon - - def __getNxIcon(self, baseIcon): - iconHash = baseIcon.cacheKey() - icon = self.__iconCache.get(iconHash, None) - if icon is None: - nxIcon = icons.getQIcon("layer-nx") - icon = self.__createCompoundIcon(baseIcon, nxIcon) - self.__iconCache[iconHash] = icon - return icon - - def data(self, index, role=qt.Qt.DisplayRole): - result = super(NexusSortFilterProxyModel, self).data(index, role) - - if index.column() == Hdf5TreeModel.NAME_COLUMN: - if role == qt.Qt.DecorationRole: - sourceIndex = self.mapToSource(index) - item = self.sourceModel().data(sourceIndex, Hdf5TreeModel.H5PY_ITEM_ROLE) - if self.__isNXnode(item): - result = self.__getNxIcon(result) - return result diff --git a/silx/gui/hdf5/__init__.py b/silx/gui/hdf5/__init__.py deleted file mode 100644 index 1b5a602..0000000 --- a/silx/gui/hdf5/__init__.py +++ /dev/null @@ -1,44 +0,0 @@ -# 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 package provides a set of Qt widgets for displaying content relative to -HDF5 format. - -.. note:: - - This package depends on *h5py*. -""" - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "23/09/2016" - - -from .Hdf5TreeView import Hdf5TreeView # noqa -from ._utils import H5Node -from ._utils import Hdf5ContextMenuEvent # noqa -from .NexusSortFilterProxyModel import NexusSortFilterProxyModel # noqa -from .Hdf5TreeModel import Hdf5TreeModel # noqa - -__all__ = ['Hdf5TreeView', 'H5Node', 'Hdf5ContextMenuEvent', 'NexusSortFilterProxyModel', 'Hdf5TreeModel'] diff --git a/silx/gui/hdf5/_utils.py b/silx/gui/hdf5/_utils.py deleted file mode 100644 index 6a34933..0000000 --- a/silx/gui/hdf5/_utils.py +++ /dev/null @@ -1,421 +0,0 @@ -# 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 package provides a set of helper class and function used by the -package `silx.gui.hdf5` package. -""" - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "04/05/2018" - - -import logging -from .. import qt -import silx.io.utils -from silx.utils.html import escape - -_logger = logging.getLogger(__name__) - - -class Hdf5ContextMenuEvent(object): - """Hold information provided to context menu callbacks.""" - - def __init__(self, source, menu, hoveredObject): - """ - Constructor - - :param QWidget source: Widget source - :param QMenu menu: Context menu which will be displayed - :param H5Node hoveredObject: Hovered H5 node - """ - self.__source = source - self.__menu = menu - self.__hoveredObject = hoveredObject - - def source(self): - """Source of the event - - :rtype: Hdf5TreeView - """ - return self.__source - - def menu(self): - """Menu which will be displayed - - :rtype: qt.QMenu - """ - return self.__menu - - def hoveredObject(self): - """Item content hovered by the mouse when the context menu was - requested - - :rtype: H5Node - """ - return self.__hoveredObject - - -def htmlFromDict(dictionary, title=None): - """Generate a readable HTML from a dictionary - - :param dict dictionary: A Dictionary - :rtype: str - """ - result = """<html> - <head> - <style type="text/css"> - ul { -qt-list-indent: 0; list-style: none; } - li > b {display: inline-block; min-width: 4em; font-weight: bold; } - </style> - </head> - <body> - """ - if title is not None: - result += "<b>%s</b>" % escape(title) - result += "<ul>" - for key, value in dictionary.items(): - result += "<li><b>%s</b>: %s</li>" % (escape(key), escape(value)) - result += "</ul>" - result += "</body></html>" - return result - - -class Hdf5DatasetMimeData(qt.QMimeData): - """Mimedata class to identify an internal drag and drop of a Hdf5Node.""" - - MIME_TYPE = "application/x-internal-h5py-dataset" - - def __init__(self, node=None, dataset=None): - qt.QMimeData.__init__(self) - self.__dataset = dataset - self.__node = node - self.setData(self.MIME_TYPE, "".encode(encoding='utf-8')) - - def node(self): - return self.__node - - def dataset(self): - if self.__node is not None: - return self.__node.obj - return self.__dataset - - -class Hdf5NodeMimeData(qt.QMimeData): - """Mimedata class to identify an internal drag and drop of a Hdf5Node.""" - - MIME_TYPE = "application/x-internal-h5py-node" - - def __init__(self, node=None): - qt.QMimeData.__init__(self) - self.__node = node - self.setData(self.MIME_TYPE, "".encode(encoding='utf-8')) - - def node(self): - return self.__node - - -class H5Node(object): - """Adapter over an h5py object to provide missing informations from h5py - nodes, like internal node path and filename (which are not provided by - :mod:`h5py` for soft and external links). - - It also provides an abstraction to reach node type for mimicked h5py - objects. - """ - - def __init__(self, h5py_item=None): - """Constructor - - :param Hdf5Item h5py_item: An Hdf5Item - """ - self.__h5py_object = h5py_item.obj - self.__h5py_target = None - self.__h5py_item = h5py_item - - def __getattr__(self, name): - if hasattr(self.__h5py_object, name): - attr = getattr(self.__h5py_object, name) - return attr - raise AttributeError("H5Node has no attribute %s" % name) - - def __get_target(self, obj): - """ - Return the actual physical target of the provided object. - - Objects can contains links in the middle of the path, this function - check each groups and remove this prefix in case of the link by the - link of the path. - - :param obj: A valid h5py object (File, group or dataset) - :type obj: h5py.Dataset or h5py.Group or h5py.File - :rtype: h5py.Dataset or h5py.Group or h5py.File - """ - elements = obj.name.split("/") - if obj.name == "/": - return obj - elif obj.name.startswith("/"): - elements.pop(0) - path = "" - subpath = "" - while len(elements) > 0: - e = elements.pop(0) - subpath = path + "/" + e - link = obj.parent.get(subpath, getlink=True) - classlink = silx.io.utils.get_h5_class(link) - - if classlink == silx.io.utils.H5Type.EXTERNAL_LINK: - subpath = "/".join(elements) - external_obj = obj.parent.get(self.basename + "/" + subpath) - return self.__get_target(external_obj) - elif classlink == silx.io.utils.H5Type.SOFT_LINK: - # Restart from this stat - root_elements = link.path.split("/") - if link.path == "/": - path = "" - root_elements = [] - elif link.path.startswith("/"): - path = "" - root_elements.pop(0) - - for name in reversed(root_elements): - elements.insert(0, name) - else: - path = subpath - - return obj.file[path] - - @property - def h5py_target(self): - if self.__h5py_target is not None: - return self.__h5py_target - self.__h5py_target = self.__get_target(self.__h5py_object) - return self.__h5py_target - - @property - def h5py_object(self): - """Returns the internal h5py node. - - :rtype: h5py.File or h5py.Group or h5py.Dataset - """ - return self.__h5py_object - - @property - def h5type(self): - """Returns the node type, as an H5Type. - - :rtype: H5Node - """ - return silx.io.utils.get_h5_class(self.__h5py_object) - - @property - def ntype(self): - """Returns the node type, as an h5py class. - - :rtype: - :class:`h5py.File`, :class:`h5py.Group` or :class:`h5py.Dataset` - """ - type_ = self.h5type - return silx.io.utils.h5type_to_h5py_class(type_) - - @property - def basename(self): - """Returns the basename of this h5py node. It is the last identifier of - the path. - - :rtype: str - """ - return self.__h5py_object.name.split("/")[-1] - - @property - def is_broken(self): - """Returns true if the node is a broken link. - - :rtype: bool - """ - if self.__h5py_item is None: - raise RuntimeError("h5py_item is not defined") - return self.__h5py_item.isBrokenObj() - - @property - def local_name(self): - """Returns the path from the master file root to this node. - - For links, this path is not equal to the h5py one. - - :rtype: str - """ - if self.__h5py_item is None: - raise RuntimeError("h5py_item is not defined") - - result = [] - item = self.__h5py_item - while item is not None: - # stop before the root item (item without parent) - if item.parent.parent is None: - name = item.obj.name - if name != "/": - result.append(item.obj.name) - break - else: - result.append(item.basename) - item = item.parent - if item is None: - raise RuntimeError("The item does not have parent holding h5py.File") - if result == []: - return "/" - if not result[-1].startswith("/"): - result.append("") - result.reverse() - name = "/".join(result) - return name - - def __get_local_file(self): - """Returns the file of the root of this tree - - :rtype: h5py.File - """ - item = self.__h5py_item - while item.parent.parent is not None: - class_ = silx.io.utils.get_h5_class(class_=item.h5pyClass) - if class_ == silx.io.utils.H5Type.FILE: - break - item = item.parent - - class_ = silx.io.utils.get_h5_class(class_=item.h5pyClass) - if class_ == silx.io.utils.H5Type.FILE: - return item.obj - else: - return item.obj.file - - @property - def local_file(self): - """Returns the master file in which is this node. - - For path containing external links, this file is not equal to the h5py - one. - - :rtype: h5py.File - :raises RuntimeException: If no file are found - """ - return self.__get_local_file() - - @property - def local_filename(self): - """Returns the filename from the master file of this node. - - For path containing external links, this path is not equal to the - filename provided by h5py. - - :rtype: str - :raises RuntimeException: If no file are found - """ - return self.local_file.filename - - @property - def local_basename(self): - """Returns the basename from the master file root to this node. - - For path containing links, this basename can be different than the - basename provided by h5py. - - :rtype: str - """ - class_ = self.__h5py_item.h5Class - if class_ is not None and class_ == silx.io.utils.H5Type.FILE: - return "" - return self.__h5py_item.basename - - @property - def physical_file(self): - """Returns the physical file in which is this node. - - .. versionadded:: 0.6 - - :rtype: h5py.File - :raises RuntimeError: If no file are found - """ - class_ = silx.io.utils.get_h5_class(self.__h5py_object) - if class_ == silx.io.utils.H5Type.EXTERNAL_LINK: - # It means the link is broken - raise RuntimeError("No file node found") - if class_ == silx.io.utils.H5Type.SOFT_LINK: - # It means the link is broken - return self.local_file - - physical_obj = self.h5py_target - return physical_obj.file - - @property - def physical_name(self): - """Returns the path from the location this h5py node is physically - stored. - - For broken links, this filename can be different from the - filename provided by h5py. - - :rtype: str - """ - class_ = silx.io.utils.get_h5_class(self.__h5py_object) - if class_ == silx.io.utils.H5Type.EXTERNAL_LINK: - # It means the link is broken - return self.__h5py_object.path - if class_ == silx.io.utils.H5Type.SOFT_LINK: - # It means the link is broken - return self.__h5py_object.path - - physical_obj = self.h5py_target - return physical_obj.name - - @property - def physical_filename(self): - """Returns the filename from the location this h5py node is physically - stored. - - For broken links, this filename can be different from the - filename provided by h5py. - - :rtype: str - """ - class_ = silx.io.utils.get_h5_class(self.__h5py_object) - if class_ == silx.io.utils.H5Type.EXTERNAL_LINK: - # It means the link is broken - return self.__h5py_object.filename - if class_ == silx.io.utils.H5Type.SOFT_LINK: - # It means the link is broken - return self.local_file.filename - - return self.physical_file.filename - - @property - def physical_basename(self): - """Returns the basename from the location this h5py node is physically - stored. - - For broken links, this basename can be different from the - basename provided by h5py. - - :rtype: str - """ - return self.physical_name.split("/")[-1] diff --git a/silx/gui/hdf5/setup.py b/silx/gui/hdf5/setup.py deleted file mode 100644 index 786a851..0000000 --- a/silx/gui/hdf5/setup.py +++ /dev/null @@ -1,41 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 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. -# -# ###########################################################################*/ -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "28/09/2016" - - -from numpy.distutils.misc_util import Configuration - - -def configuration(parent_package='', top_path=None): - config = Configuration('hdf5', parent_package, top_path) - config.add_subpackage('test') - return config - - -if __name__ == "__main__": - from numpy.distutils.core import setup - setup(configuration=configuration) diff --git a/silx/gui/hdf5/test/__init__.py b/silx/gui/hdf5/test/__init__.py deleted file mode 100644 index 3000d96..0000000 --- a/silx/gui/hdf5/test/__init__.py +++ /dev/null @@ -1,39 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 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. -# -# ###########################################################################*/ -import unittest - -from . import test_hdf5 - - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "28/09/2016" - - -def suite(): - test_suite = unittest.TestSuite() - test_suite.addTests( - [test_hdf5.suite()]) - return test_suite diff --git a/silx/gui/hdf5/test/test_hdf5.py b/silx/gui/hdf5/test/test_hdf5.py deleted file mode 100644 index 1751a21..0000000 --- a/silx/gui/hdf5/test/test_hdf5.py +++ /dev/null @@ -1,981 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 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. -# -# ###########################################################################*/ -"""Test for silx.gui.hdf5 module""" - -__authors__ = ["V. Valls"] -__license__ = "MIT" -__date__ = "03/05/2018" - - -import time -import os -import unittest -import tempfile -import numpy -import shutil -from contextlib import contextmanager -from silx.gui import qt -from silx.gui.utils.testutils import TestCaseQt -from silx.gui import hdf5 -from silx.gui.utils.testutils import SignalListener -from silx.io import commonh5 -import weakref - -try: - import h5py -except ImportError: - h5py = None - - -_tmpDirectory = None - - -def setUpModule(): - global _tmpDirectory - _tmpDirectory = tempfile.mkdtemp(prefix=__name__) - - if h5py is not None: - filename = _tmpDirectory + "/data.h5" - - # create h5 data - f = h5py.File(filename, "w") - g = f.create_group("arrays") - g.create_dataset("scalar", data=10) - f.close() - - -def tearDownModule(): - global _tmpDirectory - shutil.rmtree(_tmpDirectory) - _tmpDirectory = None - - -_called = 0 - - -class _Holder(object): - def callback(self, *args, **kvargs): - _called += 1 - - -def create_NXentry(group, name): - attrs = {"NX_class": "NXentry"} - node = commonh5.Group(name, parent=group, attrs=attrs) - group.add_node(node) - return node - - -class TestHdf5TreeModel(TestCaseQt): - - def setUp(self): - super(TestHdf5TreeModel, self).setUp() - if h5py is None: - self.skipTest("h5py is not available") - - def waitForPendingOperations(self, model): - for _ in range(10): - if not model.hasPendingOperations(): - break - self.qWait(10) - else: - raise RuntimeError("Still waiting for a pending operation") - - @contextmanager - def h5TempFile(self): - # create tmp file - fd, tmp_name = tempfile.mkstemp(suffix=".h5") - os.close(fd) - # create h5 data - h5file = h5py.File(tmp_name, "w") - g = h5file.create_group("arrays") - g.create_dataset("scalar", data=10) - h5file.close() - yield tmp_name - # clean up - os.unlink(tmp_name) - - def testCreate(self): - model = hdf5.Hdf5TreeModel() - self.assertIsNotNone(model) - - def testAppendFilename(self): - filename = _tmpDirectory + "/data.h5" - model = hdf5.Hdf5TreeModel() - self.assertEqual(model.rowCount(qt.QModelIndex()), 0) - model.appendFile(filename) - self.assertEqual(model.rowCount(qt.QModelIndex()), 1) - # clean up - index = model.index(0, 0, qt.QModelIndex()) - h5File = model.data(index, hdf5.Hdf5TreeModel.H5PY_OBJECT_ROLE) - ref = weakref.ref(model) - model = None - self.qWaitForDestroy(ref) - - def testAppendBadFilename(self): - model = hdf5.Hdf5TreeModel() - self.assertRaises(IOError, model.appendFile, "#%$") - - def testInsertFilename(self): - filename = _tmpDirectory + "/data.h5" - try: - model = hdf5.Hdf5TreeModel() - self.assertEqual(model.rowCount(qt.QModelIndex()), 0) - model.insertFile(filename) - self.assertEqual(model.rowCount(qt.QModelIndex()), 1) - # clean up - index = model.index(0, 0, qt.QModelIndex()) - h5File = model.data(index, hdf5.Hdf5TreeModel.H5PY_OBJECT_ROLE) - self.assertIsNotNone(h5File) - finally: - ref = weakref.ref(model) - model = None - self.qWaitForDestroy(ref) - - def testInsertFilenameAsync(self): - filename = _tmpDirectory + "/data.h5" - try: - model = hdf5.Hdf5TreeModel() - self.assertEqual(model.rowCount(qt.QModelIndex()), 0) - model.insertFileAsync(filename) - index = model.index(0, 0, qt.QModelIndex()) - self.assertIsInstance(model.nodeFromIndex(index), hdf5.Hdf5LoadingItem.Hdf5LoadingItem) - self.waitForPendingOperations(model) - index = model.index(0, 0, qt.QModelIndex()) - self.assertIsInstance(model.nodeFromIndex(index), hdf5.Hdf5Item.Hdf5Item) - finally: - ref = weakref.ref(model) - model = None - self.qWaitForDestroy(ref) - - def testInsertObject(self): - h5 = commonh5.File("/foo/bar/1.mock", "w") - model = hdf5.Hdf5TreeModel() - self.assertEqual(model.rowCount(qt.QModelIndex()), 0) - model.insertH5pyObject(h5) - self.assertEqual(model.rowCount(qt.QModelIndex()), 1) - - def testRemoveObject(self): - h5 = commonh5.File("/foo/bar/1.mock", "w") - model = hdf5.Hdf5TreeModel() - self.assertEqual(model.rowCount(qt.QModelIndex()), 0) - model.insertH5pyObject(h5) - self.assertEqual(model.rowCount(qt.QModelIndex()), 1) - model.removeH5pyObject(h5) - self.assertEqual(model.rowCount(qt.QModelIndex()), 0) - - def testSynchronizeObject(self): - filename = _tmpDirectory + "/data.h5" - h5 = h5py.File(filename) - model = hdf5.Hdf5TreeModel() - model.insertH5pyObject(h5) - self.assertEqual(model.rowCount(qt.QModelIndex()), 1) - index = model.index(0, 0, qt.QModelIndex()) - node1 = model.nodeFromIndex(index) - model.synchronizeH5pyObject(h5) - self.waitForPendingOperations(model) - # Now h5 was loaded from it's filename - # Another ref is owned by the model - h5.close() - - index = model.index(0, 0, qt.QModelIndex()) - node2 = model.nodeFromIndex(index) - self.assertIsNot(node1, node2) - # after sync - time.sleep(0.1) - self.qapp.processEvents() - time.sleep(0.1) - index = model.index(0, 0, qt.QModelIndex()) - self.assertIsInstance(model.nodeFromIndex(index), hdf5.Hdf5Item.Hdf5Item) - # clean up - index = model.index(0, 0, qt.QModelIndex()) - h5File = model.data(index, hdf5.Hdf5TreeModel.H5PY_OBJECT_ROLE) - self.assertIsNotNone(h5File) - h5File = None - # delete the model - ref = weakref.ref(model) - model = None - self.qWaitForDestroy(ref) - - def testFileMoveState(self): - model = hdf5.Hdf5TreeModel() - self.assertEqual(model.isFileMoveEnabled(), True) - model.setFileMoveEnabled(False) - self.assertEqual(model.isFileMoveEnabled(), False) - - def testFileDropState(self): - model = hdf5.Hdf5TreeModel() - self.assertEqual(model.isFileDropEnabled(), True) - model.setFileDropEnabled(False) - self.assertEqual(model.isFileDropEnabled(), False) - - def testSupportedDrop(self): - model = hdf5.Hdf5TreeModel() - self.assertNotEquals(model.supportedDropActions(), 0) - - model.setFileMoveEnabled(False) - model.setFileDropEnabled(False) - self.assertEqual(model.supportedDropActions(), 0) - - model.setFileMoveEnabled(False) - model.setFileDropEnabled(True) - self.assertNotEquals(model.supportedDropActions(), 0) - - model.setFileMoveEnabled(True) - model.setFileDropEnabled(False) - self.assertNotEquals(model.supportedDropActions(), 0) - - def testDropExternalFile(self): - filename = _tmpDirectory + "/data.h5" - model = hdf5.Hdf5TreeModel() - mimeData = qt.QMimeData() - mimeData.setUrls([qt.QUrl.fromLocalFile(filename)]) - model.dropMimeData(mimeData, qt.Qt.CopyAction, 0, 0, qt.QModelIndex()) - self.assertEqual(model.rowCount(qt.QModelIndex()), 1) - # after sync - self.waitForPendingOperations(model) - index = model.index(0, 0, qt.QModelIndex()) - self.assertIsInstance(model.nodeFromIndex(index), hdf5.Hdf5Item.Hdf5Item) - # clean up - index = model.index(0, 0, qt.QModelIndex()) - h5File = model.data(index, role=hdf5.Hdf5TreeModel.H5PY_OBJECT_ROLE) - self.assertIsNotNone(h5File) - h5File = None - ref = weakref.ref(model) - model = None - self.qWaitForDestroy(ref) - - def getRowDataAsDict(self, model, row): - displayed = {} - roles = [qt.Qt.DisplayRole, qt.Qt.DecorationRole, qt.Qt.ToolTipRole, qt.Qt.TextAlignmentRole] - for column in range(0, model.columnCount(qt.QModelIndex())): - index = model.index(0, column, qt.QModelIndex()) - for role in roles: - datum = model.data(index, role) - displayed[column, role] = datum - return displayed - - def getItemName(self, model, row): - index = model.index(row, hdf5.Hdf5TreeModel.NAME_COLUMN, qt.QModelIndex()) - return model.data(index, qt.Qt.DisplayRole) - - def testFileData(self): - h5 = commonh5.File("/foo/bar/1.mock", "w") - model = hdf5.Hdf5TreeModel() - model.insertH5pyObject(h5) - displayed = self.getRowDataAsDict(model, row=0) - self.assertEqual(displayed[hdf5.Hdf5TreeModel.NAME_COLUMN, qt.Qt.DisplayRole], "1.mock") - self.assertIsInstance(displayed[hdf5.Hdf5TreeModel.NAME_COLUMN, qt.Qt.DecorationRole], qt.QIcon) - self.assertEqual(displayed[hdf5.Hdf5TreeModel.TYPE_COLUMN, qt.Qt.DisplayRole], "") - self.assertEqual(displayed[hdf5.Hdf5TreeModel.SHAPE_COLUMN, qt.Qt.DisplayRole], "") - self.assertEqual(displayed[hdf5.Hdf5TreeModel.VALUE_COLUMN, qt.Qt.DisplayRole], "") - self.assertEqual(displayed[hdf5.Hdf5TreeModel.DESCRIPTION_COLUMN, qt.Qt.DisplayRole], "") - self.assertEqual(displayed[hdf5.Hdf5TreeModel.NODE_COLUMN, qt.Qt.DisplayRole], "File") - - def testGroupData(self): - h5 = commonh5.File("/foo/bar/1.mock", "w") - d = h5.create_group("foo") - d.attrs["desc"] = "fooo" - - model = hdf5.Hdf5TreeModel() - model.insertH5pyObject(d) - displayed = self.getRowDataAsDict(model, row=0) - self.assertEqual(displayed[hdf5.Hdf5TreeModel.NAME_COLUMN, qt.Qt.DisplayRole], "1.mock::foo") - self.assertIsInstance(displayed[hdf5.Hdf5TreeModel.NAME_COLUMN, qt.Qt.DecorationRole], qt.QIcon) - self.assertEqual(displayed[hdf5.Hdf5TreeModel.TYPE_COLUMN, qt.Qt.DisplayRole], "") - self.assertEqual(displayed[hdf5.Hdf5TreeModel.SHAPE_COLUMN, qt.Qt.DisplayRole], "") - self.assertEqual(displayed[hdf5.Hdf5TreeModel.VALUE_COLUMN, qt.Qt.DisplayRole], "") - self.assertEqual(displayed[hdf5.Hdf5TreeModel.DESCRIPTION_COLUMN, qt.Qt.DisplayRole], "fooo") - self.assertEqual(displayed[hdf5.Hdf5TreeModel.NODE_COLUMN, qt.Qt.DisplayRole], "Group") - - def testDatasetData(self): - h5 = commonh5.File("/foo/bar/1.mock", "w") - value = numpy.array([1, 2, 3]) - d = h5.create_dataset("foo", data=value) - - model = hdf5.Hdf5TreeModel() - model.insertH5pyObject(d) - displayed = self.getRowDataAsDict(model, row=0) - self.assertEqual(displayed[hdf5.Hdf5TreeModel.NAME_COLUMN, qt.Qt.DisplayRole], "1.mock::foo") - self.assertIsInstance(displayed[hdf5.Hdf5TreeModel.NAME_COLUMN, qt.Qt.DecorationRole], qt.QIcon) - self.assertEqual(displayed[hdf5.Hdf5TreeModel.TYPE_COLUMN, qt.Qt.DisplayRole], value.dtype.name) - self.assertEqual(displayed[hdf5.Hdf5TreeModel.SHAPE_COLUMN, qt.Qt.DisplayRole], "3") - self.assertEqual(displayed[hdf5.Hdf5TreeModel.VALUE_COLUMN, qt.Qt.DisplayRole], "[1 2 3]") - self.assertEqual(displayed[hdf5.Hdf5TreeModel.DESCRIPTION_COLUMN, qt.Qt.DisplayRole], "") - self.assertEqual(displayed[hdf5.Hdf5TreeModel.NODE_COLUMN, qt.Qt.DisplayRole], "Dataset") - - def testDropLastAsFirst(self): - model = hdf5.Hdf5TreeModel() - h5_1 = commonh5.File("/foo/bar/1.mock", "w") - h5_2 = commonh5.File("/foo/bar/2.mock", "w") - model.insertH5pyObject(h5_1) - model.insertH5pyObject(h5_2) - self.assertEqual(self.getItemName(model, 0), "1.mock") - self.assertEqual(self.getItemName(model, 1), "2.mock") - index = model.index(1, 0, qt.QModelIndex()) - mimeData = model.mimeData([index]) - model.dropMimeData(mimeData, qt.Qt.MoveAction, 0, 0, qt.QModelIndex()) - self.assertEqual(self.getItemName(model, 0), "2.mock") - self.assertEqual(self.getItemName(model, 1), "1.mock") - - def testDropFirstAsLast(self): - model = hdf5.Hdf5TreeModel() - h5_1 = commonh5.File("/foo/bar/1.mock", "w") - h5_2 = commonh5.File("/foo/bar/2.mock", "w") - model.insertH5pyObject(h5_1) - model.insertH5pyObject(h5_2) - self.assertEqual(self.getItemName(model, 0), "1.mock") - self.assertEqual(self.getItemName(model, 1), "2.mock") - index = model.index(0, 0, qt.QModelIndex()) - mimeData = model.mimeData([index]) - model.dropMimeData(mimeData, qt.Qt.MoveAction, 2, 0, qt.QModelIndex()) - self.assertEqual(self.getItemName(model, 0), "2.mock") - self.assertEqual(self.getItemName(model, 1), "1.mock") - - def testRootParent(self): - model = hdf5.Hdf5TreeModel() - h5_1 = commonh5.File("/foo/bar/1.mock", "w") - model.insertH5pyObject(h5_1) - index = model.index(0, 0, qt.QModelIndex()) - index = model.parent(index) - self.assertEqual(index, qt.QModelIndex()) - - -class TestHdf5TreeModelSignals(TestCaseQt): - - def setUp(self): - TestCaseQt.setUp(self) - self.model = hdf5.Hdf5TreeModel() - filename = _tmpDirectory + "/data.h5" - self.h5 = h5py.File(filename) - self.model.insertH5pyObject(self.h5) - - self.listener = SignalListener() - self.model.sigH5pyObjectLoaded.connect(self.listener.partial(signal="loaded")) - self.model.sigH5pyObjectRemoved.connect(self.listener.partial(signal="removed")) - self.model.sigH5pyObjectSynchronized.connect(self.listener.partial(signal="synchronized")) - - def tearDown(self): - self.signals = None - ref = weakref.ref(self.model) - self.model = None - self.qWaitForDestroy(ref) - self.h5.close() - self.h5 = None - TestCaseQt.tearDown(self) - - def waitForPendingOperations(self, model): - for _ in range(10): - if not model.hasPendingOperations(): - break - self.qWait(10) - else: - raise RuntimeError("Still waiting for a pending operation") - - def testInsert(self): - filename = _tmpDirectory + "/data.h5" - h5 = h5py.File(filename) - self.model.insertH5pyObject(h5) - self.assertEqual(self.listener.callCount(), 0) - - def testLoaded(self): - filename = _tmpDirectory + "/data.h5" - self.model.insertFile(filename) - self.assertEqual(self.listener.callCount(), 1) - self.assertEqual(self.listener.karguments(argumentName="signal")[0], "loaded") - self.assertIsNot(self.listener.arguments(callIndex=0)[0], self.h5) - self.assertEqual(self.listener.arguments(callIndex=0)[0].filename, filename) - - def testRemoved(self): - self.model.removeH5pyObject(self.h5) - self.assertEqual(self.listener.callCount(), 1) - self.assertEqual(self.listener.karguments(argumentName="signal")[0], "removed") - self.assertIs(self.listener.arguments(callIndex=0)[0], self.h5) - - def testSynchonized(self): - self.model.synchronizeH5pyObject(self.h5) - self.waitForPendingOperations(self.model) - self.assertEqual(self.listener.callCount(), 1) - self.assertEqual(self.listener.karguments(argumentName="signal")[0], "synchronized") - self.assertIs(self.listener.arguments(callIndex=0)[0], self.h5) - self.assertIsNot(self.listener.arguments(callIndex=0)[1], self.h5) - - -class TestNexusSortFilterProxyModel(TestCaseQt): - - def getChildNames(self, model, index): - count = model.rowCount(index) - result = [] - for row in range(0, count): - itemIndex = model.index(row, hdf5.Hdf5TreeModel.NAME_COLUMN, index) - name = model.data(itemIndex, qt.Qt.DisplayRole) - result.append(name) - return result - - def testNXentryStartTime(self): - """Test NXentry with start_time""" - model = hdf5.Hdf5TreeModel() - h5 = commonh5.File("/foo/bar/1.mock", "w") - create_NXentry(h5, "a").create_dataset("start_time", data=numpy.string_("2015")) - create_NXentry(h5, "b").create_dataset("start_time", data=numpy.string_("2013")) - create_NXentry(h5, "c").create_dataset("start_time", data=numpy.string_("2014")) - model.insertH5pyObject(h5) - - proxy = hdf5.NexusSortFilterProxyModel() - proxy.setSourceModel(model) - proxy.sort(0, qt.Qt.DescendingOrder) - names = self.getChildNames(proxy, proxy.index(0, 0, qt.QModelIndex())) - self.assertListEqual(names, ["a", "c", "b"]) - - def testNXentryStartTimeInArray(self): - """Test NXentry with start_time""" - model = hdf5.Hdf5TreeModel() - h5 = commonh5.File("/foo/bar/1.mock", "w") - create_NXentry(h5, "a").create_dataset("start_time", data=numpy.array([numpy.string_("2015")])) - create_NXentry(h5, "b").create_dataset("start_time", data=numpy.array([numpy.string_("2013")])) - create_NXentry(h5, "c").create_dataset("start_time", data=numpy.array([numpy.string_("2014")])) - model.insertH5pyObject(h5) - - proxy = hdf5.NexusSortFilterProxyModel() - proxy.setSourceModel(model) - proxy.sort(0, qt.Qt.DescendingOrder) - names = self.getChildNames(proxy, proxy.index(0, 0, qt.QModelIndex())) - self.assertListEqual(names, ["a", "c", "b"]) - - def testNXentryEndTimeInArray(self): - """Test NXentry with end_time""" - model = hdf5.Hdf5TreeModel() - h5 = commonh5.File("/foo/bar/1.mock", "w") - create_NXentry(h5, "a").create_dataset("end_time", data=numpy.array([numpy.string_("2015")])) - create_NXentry(h5, "b").create_dataset("end_time", data=numpy.array([numpy.string_("2013")])) - create_NXentry(h5, "c").create_dataset("end_time", data=numpy.array([numpy.string_("2014")])) - model.insertH5pyObject(h5) - - proxy = hdf5.NexusSortFilterProxyModel() - proxy.setSourceModel(model) - proxy.sort(0, qt.Qt.DescendingOrder) - names = self.getChildNames(proxy, proxy.index(0, 0, qt.QModelIndex())) - self.assertListEqual(names, ["a", "c", "b"]) - - def testNXentryName(self): - """Test NXentry without start_time or end_time""" - model = hdf5.Hdf5TreeModel() - h5 = commonh5.File("/foo/bar/1.mock", "w") - create_NXentry(h5, "a") - create_NXentry(h5, "c") - create_NXentry(h5, "b") - model.insertH5pyObject(h5) - - proxy = hdf5.NexusSortFilterProxyModel() - proxy.setSourceModel(model) - proxy.sort(0, qt.Qt.AscendingOrder) - names = self.getChildNames(proxy, proxy.index(0, 0, qt.QModelIndex())) - self.assertListEqual(names, ["a", "b", "c"]) - - def testStartTime(self): - """If it is not NXentry, start_time is not used""" - model = hdf5.Hdf5TreeModel() - h5 = commonh5.File("/foo/bar/1.mock", "w") - h5.create_group("a").create_dataset("start_time", data=numpy.string_("2015")) - h5.create_group("b").create_dataset("start_time", data=numpy.string_("2013")) - h5.create_group("c").create_dataset("start_time", data=numpy.string_("2014")) - model.insertH5pyObject(h5) - - proxy = hdf5.NexusSortFilterProxyModel() - proxy.setSourceModel(model) - proxy.sort(0, qt.Qt.AscendingOrder) - names = self.getChildNames(proxy, proxy.index(0, 0, qt.QModelIndex())) - self.assertListEqual(names, ["a", "b", "c"]) - - def testName(self): - model = hdf5.Hdf5TreeModel() - h5 = commonh5.File("/foo/bar/1.mock", "w") - h5.create_group("a") - h5.create_group("c") - h5.create_group("b") - model.insertH5pyObject(h5) - - proxy = hdf5.NexusSortFilterProxyModel() - proxy.setSourceModel(model) - proxy.sort(0, qt.Qt.AscendingOrder) - names = self.getChildNames(proxy, proxy.index(0, 0, qt.QModelIndex())) - self.assertListEqual(names, ["a", "b", "c"]) - - def testNumber(self): - model = hdf5.Hdf5TreeModel() - h5 = commonh5.File("/foo/bar/1.mock", "w") - h5.create_group("a1") - h5.create_group("a20") - h5.create_group("a3") - model.insertH5pyObject(h5) - - proxy = hdf5.NexusSortFilterProxyModel() - proxy.setSourceModel(model) - proxy.sort(0, qt.Qt.AscendingOrder) - names = self.getChildNames(proxy, proxy.index(0, 0, qt.QModelIndex())) - self.assertListEqual(names, ["a1", "a3", "a20"]) - - def testMultiNumber(self): - model = hdf5.Hdf5TreeModel() - h5 = commonh5.File("/foo/bar/1.mock", "w") - h5.create_group("a1-1") - h5.create_group("a20-1") - h5.create_group("a3-1") - h5.create_group("a3-20") - h5.create_group("a3-3") - model.insertH5pyObject(h5) - - proxy = hdf5.NexusSortFilterProxyModel() - proxy.setSourceModel(model) - proxy.sort(0, qt.Qt.AscendingOrder) - names = self.getChildNames(proxy, proxy.index(0, 0, qt.QModelIndex())) - self.assertListEqual(names, ["a1-1", "a3-1", "a3-3", "a3-20", "a20-1"]) - - def testUnconsistantTypes(self): - model = hdf5.Hdf5TreeModel() - h5 = commonh5.File("/foo/bar/1.mock", "w") - h5.create_group("aaa100") - h5.create_group("100aaa") - model.insertH5pyObject(h5) - - proxy = hdf5.NexusSortFilterProxyModel() - proxy.setSourceModel(model) - proxy.sort(0, qt.Qt.AscendingOrder) - names = self.getChildNames(proxy, proxy.index(0, 0, qt.QModelIndex())) - self.assertListEqual(names, ["100aaa", "aaa100"]) - - -class TestH5Node(TestCaseQt): - - @classmethod - def setUpClass(cls): - super(TestH5Node, cls).setUpClass() - if h5py is None: - raise unittest.SkipTest("h5py is not available") - - cls.tmpDirectory = tempfile.mkdtemp() - cls.h5Filename = cls.createResource(cls.tmpDirectory) - cls.h5File = h5py.File(cls.h5Filename, mode="r") - cls.model = cls.createModel(cls.h5File) - - @classmethod - def createResource(cls, directory): - filename = os.path.join(directory, "base.h5") - externalFilename = os.path.join(directory, "base__external.h5") - - externalh5 = h5py.File(externalFilename, mode="w") - externalh5["target/dataset"] = 50 - externalh5["target/link"] = h5py.SoftLink("/target/dataset") - externalh5.close() - - h5 = h5py.File(filename, mode="w") - h5["group/dataset"] = 50 - h5["link/soft_link"] = h5py.SoftLink("/group/dataset") - h5["link/soft_link_to_group"] = h5py.SoftLink("/group") - h5["link/soft_link_to_link"] = h5py.SoftLink("/link/soft_link") - h5["link/soft_link_to_file"] = h5py.SoftLink("/") - h5["group/soft_link_relative"] = h5py.SoftLink("dataset") - h5["link/external_link"] = h5py.ExternalLink(externalFilename, "/target/dataset") - h5["link/external_link_to_link"] = h5py.ExternalLink(externalFilename, "/target/link") - h5["broken_link/external_broken_file"] = h5py.ExternalLink(externalFilename + "_not_exists", "/target/link") - h5["broken_link/external_broken_link"] = h5py.ExternalLink(externalFilename, "/target/not_exists") - h5["broken_link/soft_broken_link"] = h5py.SoftLink("/group/not_exists") - h5["broken_link/soft_link_to_broken_link"] = h5py.SoftLink("/group/not_exists") - h5.close() - - return filename - - @classmethod - def createModel(cls, h5pyFile): - model = hdf5.Hdf5TreeModel() - model.insertH5pyObject(h5pyFile) - return model - - @classmethod - def tearDownClass(cls): - ref = weakref.ref(cls.model) - cls.model = None - cls.qWaitForDestroy(ref) - cls.h5File.close() - shutil.rmtree(cls.tmpDirectory) - super(TestH5Node, cls).tearDownClass() - - def getIndexFromPath(self, model, path): - """ - :param qt.QAbstractItemModel: model - """ - index = qt.QModelIndex() - for name in path: - for row in range(model.rowCount(index)): - i = model.index(row, 0, index) - label = model.data(i) - if label == name: - index = i - break - else: - raise RuntimeError("Path not found") - return index - - def getH5NodeFromPath(self, model, path): - index = self.getIndexFromPath(model, path) - item = model.data(index, hdf5.Hdf5TreeModel.H5PY_ITEM_ROLE) - h5node = hdf5.H5Node(item) - return h5node - - def testFile(self): - path = ["base.h5"] - h5node = self.getH5NodeFromPath(self.model, path) - - self.assertEqual(h5node.physical_filename, h5node.local_filename) - self.assertIn("base.h5", h5node.physical_filename) - self.assertEqual(h5node.physical_basename, "") - self.assertEqual(h5node.physical_name, "/") - self.assertEqual(h5node.local_basename, "") - self.assertEqual(h5node.local_name, "/") - - def testGroup(self): - path = ["base.h5", "group"] - h5node = self.getH5NodeFromPath(self.model, path) - - self.assertEqual(h5node.physical_filename, h5node.local_filename) - self.assertIn("base.h5", h5node.physical_filename) - self.assertEqual(h5node.physical_basename, "group") - self.assertEqual(h5node.physical_name, "/group") - self.assertEqual(h5node.local_basename, "group") - self.assertEqual(h5node.local_name, "/group") - - def testDataset(self): - path = ["base.h5", "group", "dataset"] - h5node = self.getH5NodeFromPath(self.model, path) - - self.assertEqual(h5node.physical_filename, h5node.local_filename) - self.assertIn("base.h5", h5node.physical_filename) - self.assertEqual(h5node.physical_basename, "dataset") - self.assertEqual(h5node.physical_name, "/group/dataset") - self.assertEqual(h5node.local_basename, "dataset") - self.assertEqual(h5node.local_name, "/group/dataset") - - def testSoftLink(self): - path = ["base.h5", "link", "soft_link"] - h5node = self.getH5NodeFromPath(self.model, path) - - self.assertEqual(h5node.physical_filename, h5node.local_filename) - self.assertIn("base.h5", h5node.physical_filename) - self.assertEqual(h5node.physical_basename, "dataset") - self.assertEqual(h5node.physical_name, "/group/dataset") - self.assertEqual(h5node.local_basename, "soft_link") - self.assertEqual(h5node.local_name, "/link/soft_link") - - def testSoftLinkToLink(self): - path = ["base.h5", "link", "soft_link_to_link"] - h5node = self.getH5NodeFromPath(self.model, path) - - self.assertEqual(h5node.physical_filename, h5node.local_filename) - self.assertIn("base.h5", h5node.physical_filename) - self.assertEqual(h5node.physical_basename, "dataset") - self.assertEqual(h5node.physical_name, "/group/dataset") - self.assertEqual(h5node.local_basename, "soft_link_to_link") - self.assertEqual(h5node.local_name, "/link/soft_link_to_link") - - def testSoftLinkRelative(self): - path = ["base.h5", "group", "soft_link_relative"] - h5node = self.getH5NodeFromPath(self.model, path) - - self.assertEqual(h5node.physical_filename, h5node.local_filename) - self.assertIn("base.h5", h5node.physical_filename) - self.assertEqual(h5node.physical_basename, "dataset") - self.assertEqual(h5node.physical_name, "/group/dataset") - self.assertEqual(h5node.local_basename, "soft_link_relative") - self.assertEqual(h5node.local_name, "/group/soft_link_relative") - - def testExternalLink(self): - path = ["base.h5", "link", "external_link"] - h5node = self.getH5NodeFromPath(self.model, path) - - self.assertNotEqual(h5node.physical_filename, h5node.local_filename) - self.assertIn("base.h5", h5node.local_filename) - self.assertIn("base__external.h5", h5node.physical_filename) - self.assertEqual(h5node.physical_basename, "dataset") - self.assertEqual(h5node.physical_name, "/target/dataset") - self.assertEqual(h5node.local_basename, "external_link") - self.assertEqual(h5node.local_name, "/link/external_link") - - def testExternalLinkToLink(self): - path = ["base.h5", "link", "external_link_to_link"] - h5node = self.getH5NodeFromPath(self.model, path) - - self.assertNotEqual(h5node.physical_filename, h5node.local_filename) - self.assertIn("base.h5", h5node.local_filename) - self.assertIn("base__external.h5", h5node.physical_filename) - - self.assertNotEqual(h5node.physical_filename, h5node.local_filename) - self.assertEqual(h5node.physical_basename, "dataset") - self.assertEqual(h5node.physical_name, "/target/dataset") - self.assertEqual(h5node.local_basename, "external_link_to_link") - self.assertEqual(h5node.local_name, "/link/external_link_to_link") - - def testExternalBrokenFile(self): - path = ["base.h5", "broken_link", "external_broken_file"] - h5node = self.getH5NodeFromPath(self.model, path) - - self.assertNotEqual(h5node.physical_filename, h5node.local_filename) - self.assertIn("base.h5", h5node.local_filename) - self.assertIn("not_exists", h5node.physical_filename) - self.assertEqual(h5node.physical_basename, "link") - self.assertEqual(h5node.physical_name, "/target/link") - self.assertEqual(h5node.local_basename, "external_broken_file") - self.assertEqual(h5node.local_name, "/broken_link/external_broken_file") - - def testExternalBrokenLink(self): - path = ["base.h5", "broken_link", "external_broken_link"] - h5node = self.getH5NodeFromPath(self.model, path) - - self.assertNotEqual(h5node.physical_filename, h5node.local_filename) - self.assertIn("base.h5", h5node.local_filename) - self.assertIn("__external", h5node.physical_filename) - self.assertEqual(h5node.physical_basename, "not_exists") - self.assertEqual(h5node.physical_name, "/target/not_exists") - self.assertEqual(h5node.local_basename, "external_broken_link") - self.assertEqual(h5node.local_name, "/broken_link/external_broken_link") - - def testSoftBrokenLink(self): - path = ["base.h5", "broken_link", "soft_broken_link"] - h5node = self.getH5NodeFromPath(self.model, path) - - self.assertEqual(h5node.physical_filename, h5node.local_filename) - self.assertIn("base.h5", h5node.physical_filename) - self.assertEqual(h5node.physical_basename, "not_exists") - self.assertEqual(h5node.physical_name, "/group/not_exists") - self.assertEqual(h5node.local_basename, "soft_broken_link") - self.assertEqual(h5node.local_name, "/broken_link/soft_broken_link") - - def testSoftLinkToBrokenLink(self): - path = ["base.h5", "broken_link", "soft_link_to_broken_link"] - h5node = self.getH5NodeFromPath(self.model, path) - - self.assertEqual(h5node.physical_filename, h5node.local_filename) - self.assertIn("base.h5", h5node.physical_filename) - self.assertEqual(h5node.physical_basename, "not_exists") - self.assertEqual(h5node.physical_name, "/group/not_exists") - self.assertEqual(h5node.local_basename, "soft_link_to_broken_link") - self.assertEqual(h5node.local_name, "/broken_link/soft_link_to_broken_link") - - def testDatasetFromSoftLinkToGroup(self): - path = ["base.h5", "link", "soft_link_to_group", "dataset"] - h5node = self.getH5NodeFromPath(self.model, path) - - self.assertEqual(h5node.physical_filename, h5node.local_filename) - self.assertIn("base.h5", h5node.physical_filename) - self.assertEqual(h5node.physical_basename, "dataset") - self.assertEqual(h5node.physical_name, "/group/dataset") - self.assertEqual(h5node.local_basename, "dataset") - self.assertEqual(h5node.local_name, "/link/soft_link_to_group/dataset") - - def testDatasetFromSoftLinkToFile(self): - path = ["base.h5", "link", "soft_link_to_file", "link", "soft_link_to_group", "dataset"] - h5node = self.getH5NodeFromPath(self.model, path) - - self.assertEqual(h5node.physical_filename, h5node.local_filename) - self.assertIn("base.h5", h5node.physical_filename) - self.assertEqual(h5node.physical_basename, "dataset") - self.assertEqual(h5node.physical_name, "/group/dataset") - self.assertEqual(h5node.local_basename, "dataset") - self.assertEqual(h5node.local_name, "/link/soft_link_to_file/link/soft_link_to_group/dataset") - - -class TestHdf5TreeView(TestCaseQt): - """Test to check that icons module.""" - - def setUp(self): - super(TestHdf5TreeView, self).setUp() - if h5py is None: - self.skipTest("h5py is not available") - - def testCreate(self): - view = hdf5.Hdf5TreeView() - self.assertIsNotNone(view) - - def testContextMenu(self): - view = hdf5.Hdf5TreeView() - view._createContextMenu(qt.QPoint(0, 0)) - - def testSelection_OriginalModel(self): - tree = commonh5.File("/foo/bar/1.mock", "w") - item = tree.create_group("a/b/c/d") - item.create_group("e").create_group("f") - - view = hdf5.Hdf5TreeView() - view.findHdf5TreeModel().insertH5pyObject(tree) - view.setSelectedH5Node(item) - - selected = list(view.selectedH5Nodes())[0] - self.assertIs(item, selected.h5py_object) - - def testSelection_Simple(self): - tree = commonh5.File("/foo/bar/1.mock", "w") - item = tree.create_group("a/b/c/d") - item.create_group("e").create_group("f") - - model = hdf5.Hdf5TreeModel() - model.insertH5pyObject(tree) - view = hdf5.Hdf5TreeView() - view.setModel(model) - view.setSelectedH5Node(item) - - selected = list(view.selectedH5Nodes())[0] - self.assertIs(item, selected.h5py_object) - - def testSelection_NotFound(self): - tree2 = commonh5.File("/foo/bar/2.mock", "w") - tree = commonh5.File("/foo/bar/1.mock", "w") - item = tree.create_group("a/b/c/d") - item.create_group("e").create_group("f") - - model = hdf5.Hdf5TreeModel() - model.insertH5pyObject(tree) - view = hdf5.Hdf5TreeView() - view.setModel(model) - view.setSelectedH5Node(tree2) - - selection = list(view.selectedH5Nodes()) - self.assertEqual(len(selection), 0) - - def testSelection_ManyGroupFromSameFile(self): - tree = commonh5.File("/foo/bar/1.mock", "w") - group1 = tree.create_group("a1") - group2 = tree.create_group("a2") - group3 = tree.create_group("a3") - group1.create_group("b/c/d") - item = group2.create_group("b/c/d") - group3.create_group("b/c/d") - - model = hdf5.Hdf5TreeModel() - model.insertH5pyObject(group1) - model.insertH5pyObject(group2) - model.insertH5pyObject(group3) - view = hdf5.Hdf5TreeView() - view.setModel(model) - view.setSelectedH5Node(item) - - selected = list(view.selectedH5Nodes())[0] - self.assertIs(item, selected.h5py_object) - - def testSelection_RootFromSubTree(self): - tree = commonh5.File("/foo/bar/1.mock", "w") - group = tree.create_group("a1") - group.create_group("b/c/d") - - model = hdf5.Hdf5TreeModel() - model.insertH5pyObject(group) - view = hdf5.Hdf5TreeView() - view.setModel(model) - view.setSelectedH5Node(group) - - selected = list(view.selectedH5Nodes())[0] - self.assertIs(group, selected.h5py_object) - - def testSelection_FileFromSubTree(self): - tree = commonh5.File("/foo/bar/1.mock", "w") - group = tree.create_group("a1") - group.create_group("b").create_group("b").create_group("d") - - model = hdf5.Hdf5TreeModel() - model.insertH5pyObject(group) - view = hdf5.Hdf5TreeView() - view.setModel(model) - view.setSelectedH5Node(tree) - - selection = list(view.selectedH5Nodes()) - self.assertEqual(len(selection), 0) - - def testSelection_Tree(self): - tree1 = commonh5.File("/foo/bar/1.mock", "w") - tree2 = commonh5.File("/foo/bar/2.mock", "w") - tree3 = commonh5.File("/foo/bar/3.mock", "w") - tree1.create_group("a/b/c") - tree2.create_group("a/b/c") - tree3.create_group("a/b/c") - item = tree2 - - model = hdf5.Hdf5TreeModel() - model.insertH5pyObject(tree1) - model.insertH5pyObject(tree2) - model.insertH5pyObject(tree3) - view = hdf5.Hdf5TreeView() - view.setModel(model) - view.setSelectedH5Node(item) - - selected = list(view.selectedH5Nodes())[0] - self.assertIs(item, selected.h5py_object) - - def testSelection_RecurssiveLink(self): - """ - Recurssive link selection - - This example is not really working as expected cause commonh5 do not - support recurssive links. - But item.name == "/a/b" and the result is found. - """ - tree = commonh5.File("/foo/bar/1.mock", "w") - group = tree.create_group("a") - group.add_node(commonh5.SoftLink("b", "/")) - - item = tree["/a/b/a/b/a/b/a/b/a/b/a/b/a/b/a/b"] - - model = hdf5.Hdf5TreeModel() - model.insertH5pyObject(tree) - view = hdf5.Hdf5TreeView() - view.setModel(model) - view.setSelectedH5Node(item) - - selected = list(view.selectedH5Nodes())[0] - self.assertEqual(item.name, selected.h5py_object.name) - - def testSelection_SelectNone(self): - tree = commonh5.File("/foo/bar/1.mock", "w") - - model = hdf5.Hdf5TreeModel() - model.insertH5pyObject(tree) - view = hdf5.Hdf5TreeView() - view.setModel(model) - view.setSelectedH5Node(tree) - view.setSelectedH5Node(None) - - selection = list(view.selectedH5Nodes()) - self.assertEqual(len(selection), 0) - - -def suite(): - test_suite = unittest.TestSuite() - loadTests = unittest.defaultTestLoader.loadTestsFromTestCase - test_suite.addTest(loadTests(TestHdf5TreeModel)) - test_suite.addTest(loadTests(TestHdf5TreeModelSignals)) - test_suite.addTest(loadTests(TestNexusSortFilterProxyModel)) - test_suite.addTest(loadTests(TestHdf5TreeView)) - test_suite.addTest(loadTests(TestH5Node)) - return test_suite - - -if __name__ == '__main__': - unittest.main(defaultTest='suite') |