diff options
Diffstat (limited to 'silx/gui/hdf5')
-rw-r--r-- | silx/gui/hdf5/Hdf5Formatter.py | 17 | ||||
-rw-r--r-- | silx/gui/hdf5/Hdf5Item.py | 38 | ||||
-rw-r--r-- | silx/gui/hdf5/Hdf5TreeModel.py | 41 | ||||
-rw-r--r-- | silx/gui/hdf5/NexusSortFilterProxyModel.py | 4 | ||||
-rw-r--r-- | silx/gui/hdf5/_utils.py | 74 | ||||
-rw-r--r-- | silx/gui/hdf5/test/test_hdf5.py | 59 |
6 files changed, 153 insertions, 80 deletions
diff --git a/silx/gui/hdf5/Hdf5Formatter.py b/silx/gui/hdf5/Hdf5Formatter.py index 6802142..5754fe8 100644 --- a/silx/gui/hdf5/Hdf5Formatter.py +++ b/silx/gui/hdf5/Hdf5Formatter.py @@ -1,7 +1,7 @@ # coding: utf-8 # /*########################################################################## # -# Copyright (c) 2017 European Synchrotron Radiation Facility +# Copyright (c) 2017-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 @@ -30,14 +30,12 @@ __license__ = "MIT" __date__ = "06/06/2018" import numpy -from silx.third_party import six +import six + from silx.gui import qt from silx.gui.data.TextFormatter import TextFormatter -try: - import h5py -except ImportError: - h5py = None +import h5py class Hdf5Formatter(qt.QObject): @@ -162,10 +160,9 @@ class Hdf5Formatter(qt.QObject): 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" + enumType = h5py.check_dtype(enum=dtype) + if enumType is not None: + return "enum" text = str(dtype.newbyteorder('N')) if numpy.issubdtype(dtype, numpy.floating): diff --git a/silx/gui/hdf5/Hdf5Item.py b/silx/gui/hdf5/Hdf5Item.py index b3c313e..6ea870f 100644 --- a/silx/gui/hdf5/Hdf5Item.py +++ b/silx/gui/hdf5/Hdf5Item.py @@ -25,11 +25,12 @@ __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "03/09/2018" +__date__ = "17/01/2019" import logging import collections + from .. import qt from .. import icons from . import _utils @@ -37,7 +38,6 @@ 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) @@ -217,14 +217,32 @@ class Hdf5Item(Hdf5Node): def _populateChild(self, populateAll=False): if self.isGroupObj(): - for name in self.obj: + 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.warning("Internal %s error", lib_name, exc_info=True) + _logger.error("Internal %s error", lib_name) _logger.debug("Backtrace", exc_info=True) class_ = None try: @@ -344,14 +362,12 @@ class Hdf5Item(Hdf5Node): 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 = "" + obj = self.obj.attrs.get("NX_class", None) + if obj is None: + text = "" 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") + text = self._getFormatter().textFormatter().toString(obj) + self.__nx_class = text.strip('"') return self.__nx_class def dataName(self, role): diff --git a/silx/gui/hdf5/Hdf5TreeModel.py b/silx/gui/hdf5/Hdf5TreeModel.py index 438200b..152f3e5 100644 --- a/silx/gui/hdf5/Hdf5TreeModel.py +++ b/silx/gui/hdf5/Hdf5TreeModel.py @@ -25,7 +25,7 @@ __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "08/10/2018" +__date__ = "12/03/2019" import os @@ -360,9 +360,7 @@ class Hdf5TreeModel(qt.QAbstractItemModel): def mimeTypes(self): types = [] - if self.__fileMoveEnabled: - types.append(_utils.Hdf5NodeMimeData.MIME_TYPE) - if self.__datasetDragEnabled: + if self.__fileMoveEnabled or self.__datasetDragEnabled: types.append(_utils.Hdf5DatasetMimeData.MIME_TYPE) return types @@ -386,7 +384,7 @@ class Hdf5TreeModel(qt.QAbstractItemModel): node = self.nodeFromIndex(indexes[0]) if self.__fileMoveEnabled and node.parent is self.__root: - mimeData = _utils.Hdf5NodeMimeData(node=node) + mimeData = _utils.Hdf5DatasetMimeData(node=node, isRoot=True) elif self.__datasetDragEnabled: mimeData = _utils.Hdf5DatasetMimeData(node=node) else: @@ -413,23 +411,24 @@ class Hdf5TreeModel(qt.QAbstractItemModel): 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 self.__fileMoveEnabled and mimedata.hasFormat(_utils.Hdf5DatasetMimeData.MIME_TYPE): + if mimedata.isRoot(): + 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 + 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 + dragNodeParent = dragNode.parent + sourceRow = dragNodeParent.indexOfChild(dragNode) + self.moveRow(parentIndex, sourceRow, parentIndex, row) + return True if self.__fileDropEnabled and mimedata.hasFormat("text/uri-list"): @@ -571,7 +570,7 @@ class Hdf5TreeModel(qt.QAbstractItemModel): drag-and-drop""" obj = node.obj for f in self.__openedFiles: - if f in obj: + if f is obj: _logger.debug("Close file %s", obj.filename) obj.close() self.__openedFiles.remove(obj) diff --git a/silx/gui/hdf5/NexusSortFilterProxyModel.py b/silx/gui/hdf5/NexusSortFilterProxyModel.py index 216e992..9c3533f 100644 --- a/silx/gui/hdf5/NexusSortFilterProxyModel.py +++ b/silx/gui/hdf5/NexusSortFilterProxyModel.py @@ -25,7 +25,7 @@ __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "24/07/2018" +__date__ = "29/11/2018" import logging @@ -108,6 +108,8 @@ class NexusSortFilterProxyModel(qt.QSortFilterProxyModel): def __isNXnode(self, node): """Returns true if the node is an NX concept""" + if not hasattr(node, "h5Class"): + return False class_ = node.h5Class if class_ is None or class_ != silx.io.utils.H5Type.GROUP: return False diff --git a/silx/gui/hdf5/_utils.py b/silx/gui/hdf5/_utils.py index 6a34933..aaab228 100644 --- a/silx/gui/hdf5/_utils.py +++ b/silx/gui/hdf5/_utils.py @@ -28,12 +28,15 @@ package `silx.gui.hdf5` package. __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "04/05/2018" +__date__ = "17/01/2019" import logging -from .. import qt +import os.path + import silx.io.utils +import silx.io.url +from .. import qt from silx.utils.html import escape _logger = logging.getLogger(__name__) @@ -107,11 +110,22 @@ class Hdf5DatasetMimeData(qt.QMimeData): MIME_TYPE = "application/x-internal-h5py-dataset" - def __init__(self, node=None, dataset=None): + SILX_URI_TYPE = "application/x-silx-uri" + + def __init__(self, node=None, dataset=None, isRoot=False): qt.QMimeData.__init__(self) self.__dataset = dataset self.__node = node + self.__isRoot = isRoot self.setData(self.MIME_TYPE, "".encode(encoding='utf-8')) + if node is not None: + h5Node = H5Node(node) + silxUrl = h5Node.url + self.setText(silxUrl) + self.setData(self.SILX_URI_TYPE, silxUrl.encode(encoding='utf-8')) + + def isRoot(self): + return self.__isRoot def node(self): return self.__node @@ -122,20 +136,6 @@ class Hdf5DatasetMimeData(qt.QMimeData): 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 @@ -419,3 +419,43 @@ class H5Node(object): :rtype: str """ return self.physical_name.split("/")[-1] + + @property + def data_url(self): + """Returns a :class:`silx.io.url.DataUrl` object identify this node in the file + system. + + :rtype: ~silx.io.url.DataUrl + """ + absolute_filename = os.path.abspath(self.local_filename) + return silx.io.url.DataUrl(scheme="silx", + file_path=absolute_filename, + data_path=self.local_name) + + @property + def url(self): + """Returns an URL object identifying this node in the file + system. + + This URL can be used in different ways. + + .. code-block:: python + + # Parsing the URL + import silx.io.url + dataurl = silx.io.url.DataUrl(item.url) + # dataurl provides access to URL fields + + # Open a numpy array + import silx.io + dataset = silx.io.get_data(item.url) + + # Open an hdf5 object (URL targetting a file or a group) + import silx.io + with silx.io.open(item.url) as h5: + ...your stuff... + + :rtype: str + """ + data_url = self.data_url + return data_url.path() diff --git a/silx/gui/hdf5/test/test_hdf5.py b/silx/gui/hdf5/test/test_hdf5.py index 1751a21..f22d4ae 100644 --- a/silx/gui/hdf5/test/test_hdf5.py +++ b/silx/gui/hdf5/test/test_hdf5.py @@ -26,7 +26,7 @@ __authors__ = ["V. Valls"] __license__ = "MIT" -__date__ = "03/05/2018" +__date__ = "12/03/2019" import time @@ -43,10 +43,7 @@ from silx.gui.utils.testutils import SignalListener from silx.io import commonh5 import weakref -try: - import h5py -except ImportError: - h5py = None +import h5py _tmpDirectory = None @@ -56,14 +53,13 @@ def setUpModule(): global _tmpDirectory _tmpDirectory = tempfile.mkdtemp(prefix=__name__) - if h5py is not None: - filename = _tmpDirectory + "/data.h5" + 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() + # create h5 data + f = h5py.File(filename, "w") + g = f.create_group("arrays") + g.create_dataset("scalar", data=10) + f.close() def tearDownModule(): @@ -91,8 +87,6 @@ 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): @@ -127,8 +121,6 @@ class TestHdf5TreeModel(TestCaseQt): 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) @@ -246,6 +238,37 @@ class TestHdf5TreeModel(TestCaseQt): model.setFileDropEnabled(False) self.assertNotEquals(model.supportedDropActions(), 0) + def testCloseFile(self): + """A file inserted as a filename is open and closed internally.""" + filename = _tmpDirectory + "/data.h5" + model = hdf5.Hdf5TreeModel() + self.assertEqual(model.rowCount(qt.QModelIndex()), 0) + model.insertFile(filename) + self.assertEqual(model.rowCount(qt.QModelIndex()), 1) + index = model.index(0, 0) + h5File = model.data(index, role=hdf5.Hdf5TreeModel.H5PY_OBJECT_ROLE) + model.removeIndex(index) + self.assertEqual(model.rowCount(qt.QModelIndex()), 0) + self.assertFalse(bool(h5File.id.valid), "The HDF5 file was not closed") + + def testNotCloseFile(self): + """A file inserted as an h5py object is not open (then not closed) + internally.""" + filename = _tmpDirectory + "/data.h5" + try: + h5File = h5py.File(filename) + model = hdf5.Hdf5TreeModel() + self.assertEqual(model.rowCount(qt.QModelIndex()), 0) + model.insertH5pyObject(h5File) + self.assertEqual(model.rowCount(qt.QModelIndex()), 1) + index = model.index(0, 0) + h5File = model.data(index, role=hdf5.Hdf5TreeModel.H5PY_OBJECT_ROLE) + model.removeIndex(index) + self.assertEqual(model.rowCount(qt.QModelIndex()), 0) + self.assertTrue(bool(h5File.id.valid), "The HDF5 file was unexpetedly closed") + finally: + h5File.close() + def testDropExternalFile(self): filename = _tmpDirectory + "/data.h5" model = hdf5.Hdf5TreeModel() @@ -571,8 +594,6 @@ 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) @@ -809,8 +830,6 @@ class TestHdf5TreeView(TestCaseQt): def setUp(self): super(TestHdf5TreeView, self).setUp() - if h5py is None: - self.skipTest("h5py is not available") def testCreate(self): view = hdf5.Hdf5TreeView() |