summaryrefslogtreecommitdiff
path: root/silx/gui/hdf5
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/hdf5')
-rw-r--r--silx/gui/hdf5/Hdf5Formatter.py17
-rw-r--r--silx/gui/hdf5/Hdf5Item.py38
-rw-r--r--silx/gui/hdf5/Hdf5TreeModel.py41
-rw-r--r--silx/gui/hdf5/NexusSortFilterProxyModel.py4
-rw-r--r--silx/gui/hdf5/_utils.py74
-rw-r--r--silx/gui/hdf5/test/test_hdf5.py59
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()