diff options
Diffstat (limited to 'silx/gui/hdf5/Hdf5Item.py')
-rwxr-xr-x | silx/gui/hdf5/Hdf5Item.py | 642 |
1 files changed, 0 insertions, 642 deletions
diff --git a/silx/gui/hdf5/Hdf5Item.py b/silx/gui/hdf5/Hdf5Item.py deleted file mode 100755 index e07f835..0000000 --- a/silx/gui/hdf5/Hdf5Item.py +++ /dev/null @@ -1,642 +0,0 @@ -# coding: utf-8 -# /*########################################################################## -# -# Copyright (c) 2016-2019 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__ = "17/01/2019" - - -import logging -import collections -import enum - -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 -_logger = logging.getLogger(__name__) -_formatter = TextFormatter() -_hdf5Formatter = Hdf5Formatter(textFormatter=_formatter) -# FIXME: The formatter should be an attribute of the Hdf5Model - - -class DescriptionType(enum.Enum): - """List of available kind of description. - """ - ERROR = "error" - DESCRIPTION = "description" - TITLE = "title" - PROGRAM = "program" - NAME = "name" - VALUE = "value" - - -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.__description = None - 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 future 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 future 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(): - keys = [] - try: - for name in self.obj: - keys.append(name) - except Exception: - lib_name = self.obj.__class__.__module__.split(".")[0] - _logger.error("Internal %s error. The file is corrupted.", lib_name) - _logger.debug("Backtrace", exc_info=True) - if keys == []: - # If the file was open in READ_ONLY we still can reach something - # https://github.com/silx-kit/silx/issues/2262 - try: - for name in self.obj: - keys.append(name) - except Exception: - lib_name = self.obj.__class__.__module__.split(".")[0] - _logger.error("Internal %s error (second time). The file is corrupted.", lib_name) - _logger.debug("Backtrace", exc_info=True) - for name in keys: - 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.error("Internal %s error", lib_name) - _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: - obj = self.obj.attrs.get("NX_class", None) - if obj is None: - text = "" - else: - text = self._getFormatter().textFormatter().toString(obj) - text = text.strip('"') - # Check NX_class formatting - lower = text.lower() - formatedNX_class = "" - if lower.startswith('nx'): - formatedNX_class = 'NX' + lower[2:] - if lower == 'nxcansas': - formatedNX_class = 'NXcanSAS' # That's the only class with capital letters... - if text != formatedNX_class: - _logger.error("NX_class: '%s' is malformed (should be '%s')", - text, - formatedNX_class) - text = formatedNX_class - - self.__nx_class = text - 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 - - _NEXUS_CLASS_TO_VALUE_CHILDREN = { - 'NXaperture': ( - (DescriptionType.DESCRIPTION, 'description'), - ), - 'NXbeam_stop': ( - (DescriptionType.DESCRIPTION, 'description'), - ), - 'NXdetector': ( - (DescriptionType.NAME, 'local_name'), - (DescriptionType.DESCRIPTION, 'description') - ), - 'NXentry': ( - (DescriptionType.TITLE, 'title'), - ), - 'NXenvironment': ( - (DescriptionType.NAME, 'short_name'), - (DescriptionType.NAME, 'name'), - (DescriptionType.DESCRIPTION, 'description') - ), - 'NXinstrument': ( - (DescriptionType.NAME, 'name'), - ), - 'NXlog': ( - (DescriptionType.DESCRIPTION, 'description'), - ), - 'NXmirror': ( - (DescriptionType.DESCRIPTION, 'description'), - ), - 'NXpositioner': ( - (DescriptionType.NAME, 'name'), - ), - 'NXprocess': ( - (DescriptionType.PROGRAM, 'program'), - ), - 'NXsample': ( - (DescriptionType.TITLE, 'short_title'), - (DescriptionType.NAME, 'name'), - (DescriptionType.DESCRIPTION, 'description') - ), - 'NXsample_component': ( - (DescriptionType.NAME, 'name'), - (DescriptionType.DESCRIPTION, 'description') - ), - 'NXsensor': ( - (DescriptionType.NAME, 'short_name'), - (DescriptionType.NAME, 'name') - ), - 'NXsource': ( - (DescriptionType.NAME, 'name'), - ), # or its 'short_name' attribute... This is not supported - 'NXsubentry': ( - (DescriptionType.DESCRIPTION, 'definition'), - (DescriptionType.PROGRAM, 'program_name'), - (DescriptionType.TITLE, 'title'), - ), - } - """Mapping from NeXus class to child names containing data to use as value""" - - def __computeDataDescription(self): - """Compute the data description of this item - - :rtype: Tuple[kind, str] - """ - if self.__isBroken or self.__error is not None: - self.obj # lazy loading of the object - return DescriptionType.ERROR, self.__error - - if self.h5Class == silx.io.utils.H5Type.DATASET: - return DescriptionType.VALUE, self._getFormatter().humanReadableValue(self.obj) - - elif self.isGroupObj() and self.nexusClassName: - # For NeXus groups, try to find a title or name - # By default, look for a title (most application definitions should have one) - defaultSequence = ((DescriptionType.TITLE, 'title'),) - sequence = self._NEXUS_CLASS_TO_VALUE_CHILDREN.get(self.nexusClassName, defaultSequence) - for kind, child_name in sequence: - for index in range(self.childCount()): - child = self.child(index) - if (isinstance(child, Hdf5Item) and - child.h5Class == silx.io.utils.H5Type.DATASET and - child.basename == child_name): - return kind, self._getFormatter().humanReadableValue(child.obj) - - description = self.obj.attrs.get("desc", None) - if description is not None: - return DescriptionType.DESCRIPTION, description - else: - return None, None - - def __getDataDescription(self): - """Returns a cached version of the data description - - As the data description have to reach inside the HDF5 tree, the result - is cached. A better implementation could be to use a MRU cache, to avoid - to allocate too much data. - - :rtype: Tuple[kind, str] - """ - if self.__description is None: - self.__description = self.__computeDataDescription() - return self.__description - - def dataDescription(self, role): - """Data for the description column""" - if role == qt.Qt.DecorationRole: - kind, _label = self.__getDataDescription() - if kind is not None: - icon = icons.getQIcon("description-%s" % kind.value) - return icon - return None - if role == qt.Qt.TextAlignmentRole: - return qt.Qt.AlignTop | qt.Qt.AlignLeft - if role == qt.Qt.DisplayRole: - _kind, label = self.__getDataDescription() - return label - if role == qt.Qt.ToolTipRole: - if self.__error is not None: - self.obj # lazy loading of the object - self.__initH5Object() - return self.__error - kind, label = self.__getDataDescription() - if label is not None: - return "<b>%s</b><br/>%s" % (kind.value.capitalize(), label) - else: - return "" - 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: - # Mark as link - link = self.linkClass - if link is None: - pass - elif link == silx.io.utils.H5Type.HARD_LINK: - pass - elif link == silx.io.utils.H5Type.EXTERNAL_LINK: - return "External" - elif link == silx.io.utils.H5Type.SOFT_LINK: - return "Soft" - else: - return link.__name__ - # Mark as external data - if self.h5Class == silx.io.utils.H5Type.DATASET: - obj = self.obj - if hasattr(obj, "is_virtual"): - if obj.is_virtual: - return "Virtual" - if hasattr(obj, "external"): - if obj.external: - return "ExtRaw" - return "" - if role == qt.Qt.ToolTipRole: - return None - return None |