summaryrefslogtreecommitdiff
path: root/silx/gui/data
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/data')
-rw-r--r--silx/gui/data/DataViewer.py37
-rw-r--r--silx/gui/data/DataViewerFrame.py9
-rw-r--r--silx/gui/data/DataViews.py260
-rw-r--r--silx/gui/data/Hdf5TableView.py62
-rw-r--r--silx/gui/data/HexaTableView.py10
-rw-r--r--silx/gui/data/NXdataWidgets.py32
-rw-r--r--silx/gui/data/TextFormatter.py18
-rw-r--r--silx/gui/data/test/test_dataviewer.py6
8 files changed, 283 insertions, 151 deletions
diff --git a/silx/gui/data/DataViewer.py b/silx/gui/data/DataViewer.py
index 5e0b25e..4db2863 100644
--- a/silx/gui/data/DataViewer.py
+++ b/silx/gui/data/DataViewer.py
@@ -37,7 +37,7 @@ from silx.utils.property import classproperty
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "26/02/2018"
+__date__ = "24/04/2018"
_logger = logging.getLogger(__name__)
@@ -167,8 +167,10 @@ class DataViewer(qt.QFrame):
self.__currentAvailableViews = []
self.__currentView = None
self.__data = None
+ self.__info = None
self.__useAxisSelection = False
self.__userSelectedView = None
+ self.__hooks = None
self.__views = []
self.__index = {}
@@ -182,6 +184,15 @@ class DataViewer(qt.QFrame):
self.__views = list(views)
self.setDisplayMode(DataViews.EMPTY_MODE)
+ def setGlobalHooks(self, hooks):
+ """Set a data view hooks for all the views
+
+ :param DataViewHooks context: The hooks to use
+ """
+ self.__hooks = hooks
+ for v in self.__views:
+ v.setHooks(hooks)
+
def createDefaultViews(self, parent=None):
"""Create and returns available views which can be displayed by default
by the data viewer. It is called internally by the widget. It can be
@@ -250,7 +261,7 @@ class DataViewer(qt.QFrame):
"""
previous = self.__numpySelection.blockSignals(True)
self.__numpySelection.clear()
- info = DataViews.DataInfo(self.__data)
+ info = self._getInfo()
axisNames = self.__currentView.axesNames(self.__data, info)
if info.isArray and info.size != 0 and self.__data is not None and axisNames is not None:
self.__useAxisSelection = True
@@ -359,6 +370,8 @@ class DataViewer(qt.QFrame):
:param DataView view: A dataview
"""
+ if self.__hooks is not None:
+ view.setHooks(self.__hooks)
self.__views.append(view)
# TODO It can be skipped if the view do not support the data
self.__updateAvailableViews()
@@ -390,8 +403,8 @@ class DataViewer(qt.QFrame):
Update available views from the current data.
"""
data = self.__data
+ info = self._getInfo()
# sort available views according to priority
- info = DataViews.DataInfo(data)
priorities = [v.getDataPriority(data, info) for v in self.__views]
views = zip(priorities, self.__views)
views = filter(lambda t: t[0] > DataViews.DataView.UNSUPPORTED, views)
@@ -490,6 +503,7 @@ class DataViewer(qt.QFrame):
:param numpy.ndarray data: The data.
"""
self.__data = data
+ self._invalidateInfo()
self.__displayedData = None
self.__updateView()
self.__updateNumpySelectionAxis()
@@ -512,6 +526,21 @@ class DataViewer(qt.QFrame):
"""Returns the data"""
return self.__data
+ def _invalidateInfo(self):
+ """Invalidate DataInfo cache."""
+ self.__info = None
+
+ def _getInfo(self):
+ """Returns the DataInfo of the current selected data.
+
+ This value is cached.
+
+ :rtype: DataInfo
+ """
+ if self.__info is None:
+ self.__info = DataViews.DataInfo(self.__data)
+ return self.__info
+
def displayMode(self):
"""Returns the current display mode"""
return self.__currentView.modeId()
@@ -552,6 +581,8 @@ class DataViewer(qt.QFrame):
isReplaced = False
for idx, view in enumerate(self.__views):
if view.modeId() == modeId:
+ if self.__hooks is not None:
+ newView.setHooks(self.__hooks)
self.__views[idx] = newView
isReplaced = True
break
diff --git a/silx/gui/data/DataViewerFrame.py b/silx/gui/data/DataViewerFrame.py
index 89a9992..4e6d2e8 100644
--- a/silx/gui/data/DataViewerFrame.py
+++ b/silx/gui/data/DataViewerFrame.py
@@ -27,7 +27,7 @@
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "21/09/2017"
+__date__ = "24/04/2018"
from silx.gui import qt
from .DataViewer import DataViewer
@@ -113,6 +113,13 @@ class DataViewerFrame(qt.QWidget):
"""Called when the displayed view changes"""
self.displayedViewChanged.emit(view)
+ def setGlobalHooks(self, hooks):
+ """Set a data view hooks for all the views
+
+ :param DataViewHooks context: The hooks to use
+ """
+ self.__dataViewer.setGlobalHooks(hooks)
+
def availableViews(self):
"""Returns the list of registered views
diff --git a/silx/gui/data/DataViews.py b/silx/gui/data/DataViews.py
index ef69441..2291e87 100644
--- a/silx/gui/data/DataViews.py
+++ b/silx/gui/data/DataViews.py
@@ -35,13 +35,13 @@ from silx.gui import qt, icons
from silx.gui.data.TextFormatter import TextFormatter
from silx.io import nxdata
from silx.gui.hdf5 import H5Node
-from silx.io.nxdata import get_attr_as_string
-from silx.gui.plot.Colormap import Colormap
-from silx.gui.plot.actions.control import ColormapAction
+from silx.io.nxdata import get_attr_as_unicode
+from silx.gui.colors import Colormap
+from silx.gui.dialog.ColormapDialog import ColormapDialog
__authors__ = ["V. Valls", "P. Knobel"]
__license__ = "MIT"
-__date__ = "23/01/2018"
+__date__ = "23/05/2018"
_logger = logging.getLogger(__name__)
@@ -109,6 +109,7 @@ class DataInfo(object):
self.isBoolean = False
self.isRecord = False
self.hasNXdata = False
+ self.isInvalidNXdata = False
self.shape = tuple()
self.dim = 0
self.size = 0
@@ -118,8 +119,28 @@ class DataInfo(object):
if silx.io.is_group(data):
nxd = nxdata.get_default(data)
+ nx_class = get_attr_as_unicode(data, "NX_class")
if nxd is not None:
self.hasNXdata = True
+ # can we plot it?
+ is_scalar = nxd.signal_is_0d or nxd.interpretation in ["scalar", "scaler"]
+ if not (is_scalar or nxd.is_curve or nxd.is_x_y_value_scatter or
+ nxd.is_image or nxd.is_stack):
+ # invalid: cannot be plotted by any widget
+ self.isInvalidNXdata = True
+ elif nx_class == "NXdata":
+ # group claiming to be NXdata could not be parsed
+ self.isInvalidNXdata = True
+ elif nx_class == "NXentry" and "default" in data.attrs:
+ # entry claiming to have a default NXdata could not be parsed
+ self.isInvalidNXdata = True
+ elif nx_class == "NXroot" or silx.io.is_file(data):
+ # root claiming to have a default entry
+ if "default" in data.attrs:
+ def_entry = data.attrs["default"]
+ if def_entry in data and "default" in data[def_entry].attrs:
+ # and entry claims to have default NXdata
+ self.isInvalidNXdata = True
if isinstance(data, numpy.ndarray):
self.isArray = True
@@ -130,7 +151,7 @@ class DataInfo(object):
if silx.io.is_dataset(data):
if "interpretation" in data.attrs:
- self.interpretation = get_attr_as_string(data, "interpretation")
+ self.interpretation = get_attr_as_unicode(data, "interpretation")
else:
self.interpretation = None
elif self.hasNXdata:
@@ -166,7 +187,11 @@ class DataInfo(object):
if self.shape is not None:
self.dim = len(self.shape)
- if hasattr(data, "size"):
+ if hasattr(data, "shape") and data.shape is None:
+ # This test is expected to avoid to fall done on the h5py issue
+ # https://github.com/h5py/h5py/issues/1044
+ self.size = 0
+ elif hasattr(data, "size"):
self.size = int(data.size)
else:
self.size = 1
@@ -177,6 +202,18 @@ class DataInfo(object):
return _normalizeData(data)
+class DataViewHooks(object):
+ """A set of hooks defined to custom the behaviour of the data views."""
+
+ def getColormap(self, view):
+ """Returns a colormap for this view."""
+ return None
+
+ def getColormapDialog(self, view):
+ """Returns a color dialog for this view."""
+ return None
+
+
class DataView(object):
"""Holder for the data view."""
@@ -184,12 +221,6 @@ class DataView(object):
"""Priority returned when the requested data can't be displayed by the
view."""
- _defaultColormap = None
- """Store a default colormap shared with all the views"""
-
- _defaultColorDialog = None
- """Store a default color dialog shared with all the views"""
-
def __init__(self, parent, modeId=None, icon=None, label=None):
"""Constructor
@@ -204,32 +235,46 @@ class DataView(object):
if icon is None:
icon = qt.QIcon()
self.__icon = icon
+ self.__hooks = None
- @staticmethod
- def defaultColormap():
- """Returns a shared colormap as default for all the views.
+ def getHooks(self):
+ """Returns the data viewer hooks used by this view.
- :rtype: Colormap
+ :rtype: DataViewHooks
"""
- if DataView._defaultColormap is None:
- DataView._defaultColormap = Colormap(name="viridis")
- return DataView._defaultColormap
+ return self.__hooks
- @staticmethod
- def defaultColorDialog():
- """Returns a shared color dialog as default for all the views.
+ def setHooks(self, hooks):
+ """Set the data view hooks to use with this view.
- :rtype: ColorDialog
+ :param DataViewHooks hooks: The data view hooks to use
"""
- if DataView._defaultColorDialog is None:
- DataView._defaultColorDialog = ColormapAction._createDialog(qt.QApplication.instance().activeWindow())
- return DataView._defaultColorDialog
+ self.__hooks = hooks
- @staticmethod
- def _cleanUpCache():
- """Clean up the cache. Needed for tests"""
- DataView._defaultColormap = None
- DataView._defaultColorDialog = None
+ def defaultColormap(self):
+ """Returns a default colormap.
+
+ :rtype: Colormap
+ """
+ colormap = None
+ if self.__hooks is not None:
+ colormap = self.__hooks.getColormap(self)
+ if colormap is None:
+ colormap = Colormap(name="viridis")
+ return colormap
+
+ def defaultColorDialog(self):
+ """Returns a default color dialog.
+
+ :rtype: ColormapDialog
+ """
+ dialog = None
+ if self.__hooks is not None:
+ dialog = self.__hooks.getColormapDialog(self)
+ if dialog is None:
+ dialog = ColormapDialog()
+ dialog.setModal(False)
+ return dialog
def icon(self):
"""Returns the default icon"""
@@ -345,8 +390,21 @@ class CompositeDataView(DataView):
self.__views = OrderedDict()
self.__currentView = None
+ def setHooks(self, hooks):
+ """Set the data context to use with this view.
+
+ :param DataViewHooks hooks: The data view hooks to use
+ """
+ super(CompositeDataView, self).setHooks(hooks)
+ if hooks is not None:
+ for v in self.__views:
+ v.setHooks(hooks)
+
def addView(self, dataView):
"""Add a new dataview to the available list."""
+ hooks = self.getHooks()
+ if hooks is not None:
+ dataView.setHooks(hooks)
self.__views[dataView] = None
def availableViews(self):
@@ -446,6 +504,9 @@ class CompositeDataView(DataView):
break
elif isinstance(view, CompositeDataView):
# recurse
+ hooks = self.getHooks()
+ if hooks is not None:
+ newView.setHooks(hooks)
if view.replaceView(modeId, newView):
return True
if oldView is None:
@@ -1022,70 +1083,46 @@ class _InvalidNXdataView(DataView):
def getDataPriority(self, data, info):
data = self.normalizeData(data)
- if silx.io.is_group(data):
- nxd = nxdata.get_default(data)
- nx_class = get_attr_as_string(data, "NX_class")
-
- if nxd is None:
- if nx_class == "NXdata":
- # invalid: could not even be parsed by NXdata
- self._msg = "Group has @NX_class = NXdata, but could not be interpreted"
- self._msg += " as valid NXdata."
- return 100
- elif nx_class == "NXentry":
- if "default" not in data.attrs:
- # no link to NXdata, no problem
- return DataView.UNSUPPORTED
- self._msg = "NXentry group provides a @default attribute,"
- default_nxdata_name = data.attrs["default"]
- if default_nxdata_name not in data:
- self._msg += " but no corresponding NXdata group exists."
- elif get_attr_as_string(data[default_nxdata_name], "NX_class") != "NXdata":
- self._msg += " but the corresponding item is not a "
- self._msg += "NXdata group."
- else:
- self._msg += " but the corresponding NXdata seems to be"
- self._msg += " malformed."
- return 100
- elif nx_class == "NXroot" or silx.io.is_file(data):
- if "default" not in data.attrs:
- # no link to NXentry, no problem
- return DataView.UNSUPPORTED
- default_entry_name = data.attrs["default"]
- if default_entry_name not in data:
- # this is a problem, but not NXdata related
- return DataView.UNSUPPORTED
- default_entry = data[default_entry_name]
- if "default" not in default_entry.attrs:
- # no NXdata specified, no problemo
- return DataView.UNSUPPORTED
- default_nxdata_name = default_entry.attrs["default"]
- self._msg = "NXroot group provides a @default attribute "
- self._msg += "pointing to a NXentry which defines its own "
- self._msg += "@default attribute, "
- if default_nxdata_name not in default_entry:
- self._msg += " but no corresponding NXdata group exists."
- elif get_attr_as_string(default_entry[default_nxdata_name],
- "NX_class") != "NXdata":
- self._msg += " but the corresponding item is not a "
- self._msg += "NXdata group."
- else:
- self._msg += " but the corresponding NXdata seems to be"
- self._msg += " malformed."
- return 100
- else:
- # Not pretending to be NXdata, no problem
- return DataView.UNSUPPORTED
-
- is_scalar = nxd.signal_is_0d or nxd.interpretation in ["scalar", "scaler"]
- if not (is_scalar or nxd.is_curve or nxd.is_x_y_value_scatter or
- nxd.is_image or nxd.is_stack):
- # invalid: cannot be plotted by any widget (I cannot imagine a case)
- self._msg = "NXdata seems valid, but cannot be displayed "
- self._msg += "by any existing plot widget."
- return 100
- return DataView.UNSUPPORTED
+ if not info.isInvalidNXdata:
+ return DataView.UNSUPPORTED
+
+ if info.hasNXdata:
+ self._msg = "NXdata seems valid, but cannot be displayed "
+ self._msg += "by any existing plot widget."
+ else:
+ nx_class = get_attr_as_unicode(data, "NX_class")
+ if nx_class == "NXdata":
+ # invalid: could not even be parsed by NXdata
+ self._msg = "Group has @NX_class = NXdata, but could not be interpreted"
+ self._msg += " as valid NXdata."
+ elif nx_class == "NXentry":
+ self._msg = "NXentry group provides a @default attribute,"
+ default_nxdata_name = data.attrs["default"]
+ if default_nxdata_name not in data:
+ self._msg += " but no corresponding NXdata group exists."
+ elif get_attr_as_unicode(data[default_nxdata_name], "NX_class") != "NXdata":
+ self._msg += " but the corresponding item is not a "
+ self._msg += "NXdata group."
+ else:
+ self._msg += " but the corresponding NXdata seems to be"
+ self._msg += " malformed."
+ elif nx_class == "NXroot" or silx.io.is_file(data):
+ default_entry = data[data.attrs["default"]]
+ default_nxdata_name = default_entry.attrs["default"]
+ self._msg = "NXroot group provides a @default attribute "
+ self._msg += "pointing to a NXentry which defines its own "
+ self._msg += "@default attribute, "
+ if default_nxdata_name not in default_entry:
+ self._msg += " but no corresponding NXdata group exists."
+ elif get_attr_as_unicode(default_entry[default_nxdata_name],
+ "NX_class") != "NXdata":
+ self._msg += " but the corresponding item is not a "
+ self._msg += "NXdata group."
+ else:
+ self._msg += " but the corresponding NXdata seems to be"
+ self._msg += " malformed."
+ return 100
class _NXdataScalarView(DataView):
@@ -1111,7 +1148,7 @@ class _NXdataScalarView(DataView):
def setData(self, data):
data = self.normalizeData(data)
# data could be a NXdata or an NXentry
- nxd = nxdata.get_default(data)
+ nxd = nxdata.get_default(data, validate=False)
signal = nxd.signal
self.getWidget().setArrayData(signal,
labels=True)
@@ -1119,8 +1156,8 @@ class _NXdataScalarView(DataView):
def getDataPriority(self, data, info):
data = self.normalizeData(data)
- if info.hasNXdata:
- nxd = nxdata.get_default(data)
+ if info.hasNXdata and not info.isInvalidNXdata:
+ nxd = nxdata.get_default(data, validate=False)
if nxd.signal_is_0d or nxd.interpretation in ["scalar", "scaler"]:
return 100
return DataView.UNSUPPORTED
@@ -1151,7 +1188,7 @@ class _NXdataCurveView(DataView):
def setData(self, data):
data = self.normalizeData(data)
- nxd = nxdata.get_default(data)
+ nxd = nxdata.get_default(data, validate=False)
signals_names = [nxd.signal_name] + nxd.auxiliary_signals_names
if nxd.axes_dataset_names[-1] is not None:
x_errors = nxd.get_axis_errors(nxd.axes_dataset_names[-1])
@@ -1177,8 +1214,8 @@ class _NXdataCurveView(DataView):
def getDataPriority(self, data, info):
data = self.normalizeData(data)
- if info.hasNXdata:
- if nxdata.get_default(data).is_curve:
+ if info.hasNXdata and not info.isInvalidNXdata:
+ if nxdata.get_default(data, validate=False).is_curve:
return 100
return DataView.UNSUPPORTED
@@ -1204,8 +1241,13 @@ class _NXdataXYVScatterView(DataView):
def setData(self, data):
data = self.normalizeData(data)
- nxd = nxdata.get_default(data)
+ nxd = nxdata.get_default(data, validate=False)
+
x_axis, y_axis = nxd.axes[-2:]
+ if x_axis is None:
+ x_axis = numpy.arange(nxd.signal.size)
+ if y_axis is None:
+ y_axis = numpy.arange(nxd.signal.size)
x_label, y_label = nxd.axes_names[-2:]
if x_label is not None:
@@ -1226,8 +1268,8 @@ class _NXdataXYVScatterView(DataView):
def getDataPriority(self, data, info):
data = self.normalizeData(data)
- if info.hasNXdata:
- if nxdata.get_default(data).is_x_y_value_scatter:
+ if info.hasNXdata and not info.isInvalidNXdata:
+ if nxdata.get_default(data, validate=False).is_x_y_value_scatter:
return 100
return DataView.UNSUPPORTED
@@ -1256,7 +1298,7 @@ class _NXdataImageView(DataView):
def setData(self, data):
data = self.normalizeData(data)
- nxd = nxdata.get_default(data)
+ nxd = nxdata.get_default(data, validate=False)
isRgba = nxd.interpretation == "rgba-image"
# last two axes are Y & X
@@ -1274,8 +1316,8 @@ class _NXdataImageView(DataView):
def getDataPriority(self, data, info):
data = self.normalizeData(data)
- if info.hasNXdata:
- if nxdata.get_default(data).is_image:
+ if info.hasNXdata and not info.isInvalidNXdata:
+ if nxdata.get_default(data, validate=False).is_image:
return 100
return DataView.UNSUPPORTED
@@ -1302,7 +1344,7 @@ class _NXdataStackView(DataView):
def setData(self, data):
data = self.normalizeData(data)
- nxd = nxdata.get_default(data)
+ nxd = nxdata.get_default(data, validate=False)
signal_name = nxd.signal_name
z_axis, y_axis, x_axis = nxd.axes[-3:]
z_label, y_label, x_label = nxd.axes_names[-3:]
@@ -1319,8 +1361,8 @@ class _NXdataStackView(DataView):
def getDataPriority(self, data, info):
data = self.normalizeData(data)
- if info.hasNXdata:
- if nxdata.get_default(data).is_stack:
+ if info.hasNXdata and not info.isInvalidNXdata:
+ if nxdata.get_default(data, validate=False).is_stack:
return 100
return DataView.UNSUPPORTED
diff --git a/silx/gui/data/Hdf5TableView.py b/silx/gui/data/Hdf5TableView.py
index e4a0747..04199b2 100644
--- a/silx/gui/data/Hdf5TableView.py
+++ b/silx/gui/data/Hdf5TableView.py
@@ -30,8 +30,9 @@ from __future__ import division
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "10/10/2017"
+__date__ = "23/05/2018"
+import collections
import functools
import os.path
import logging
@@ -41,6 +42,7 @@ from .TextFormatter import TextFormatter
import silx.gui.hdf5
from silx.gui.widgets import HierarchicalTableView
from ..hdf5.Hdf5Formatter import Hdf5Formatter
+from ..hdf5._utils import htmlFromDict
try:
import h5py
@@ -54,7 +56,7 @@ _logger = logging.getLogger(__name__)
class _CellData(object):
"""Store a table item
"""
- def __init__(self, value=None, isHeader=False, span=None):
+ def __init__(self, value=None, isHeader=False, span=None, tooltip=None):
"""
Constructor
@@ -65,6 +67,7 @@ class _CellData(object):
self.__value = value
self.__isHeader = isHeader
self.__span = span
+ self.__tooltip = tooltip
def isHeader(self):
"""Returns true if the property is a sub-header title.
@@ -85,6 +88,19 @@ class _CellData(object):
"""
return self.__span
+ def tooltip(self):
+ """Returns the tooltip of the item.
+
+ :rtype: tuple
+ """
+ return self.__tooltip
+
+ def invalidateValue(self):
+ self.__value = None
+
+ def invalidateToolTip(self):
+ self.__tooltip = None
+
class _TableData(object):
"""Modelize a table with header, row and column span.
@@ -143,7 +159,7 @@ class _TableData(object):
item = _CellData(value=headerLabel, isHeader=True, span=(1, self.__colCount))
self.__data.append([item])
- def addHeaderValueRow(self, headerLabel, value):
+ def addHeaderValueRow(self, headerLabel, value, tooltip=None):
"""Append the table with a row using the first column as an header and
other cells as a single cell for the value.
@@ -151,7 +167,7 @@ class _TableData(object):
:param object value: value to store.
"""
header = _CellData(value=headerLabel, isHeader=True)
- value = _CellData(value=value, span=(1, self.__colCount))
+ value = _CellData(value=value, span=(1, self.__colCount), tooltip=tooltip)
self.__data.append([header, value])
def addRow(self, *args):
@@ -214,7 +230,20 @@ class Hdf5TableModel(HierarchicalTableView.HierarchicalTableModel):
elif role == qt.Qt.DisplayRole:
value = cell.value()
if callable(value):
- value = value(self.__obj)
+ try:
+ value = value(self.__obj)
+ except Exception:
+ cell.invalidateValue()
+ raise
+ return value
+ elif role == qt.Qt.ToolTipRole:
+ value = cell.tooltip()
+ if callable(value):
+ try:
+ value = value(self.__obj)
+ except Exception:
+ cell.invalidateToolTip()
+ raise
return value
return None
@@ -260,6 +289,14 @@ class Hdf5TableModel(HierarchicalTableView.HierarchicalTableModel):
"""Format the HDF5 type"""
return self.__hdf5Formatter.humanReadableHdf5Type(dataset)
+ def __attributeTooltip(self, attribute):
+ attributeDict = collections.OrderedDict()
+ if hasattr(attribute, "shape"):
+ attributeDict["Shape"] = self.__hdf5Formatter.humanReadableShape(attribute)
+ attributeDict["Data type"] = self.__hdf5Formatter.humanReadableType(attribute, full=True)
+ html = htmlFromDict(attributeDict, title="HDF5 Attribute")
+ return html
+
def __formatDType(self, dataset):
"""Format the numpy dtype"""
return self.__hdf5Formatter.humanReadableType(dataset, full=True)
@@ -310,7 +347,8 @@ class Hdf5TableModel(HierarchicalTableView.HierarchicalTableModel):
# it's a real H5py object
self.__data.addHeaderValueRow("Basename", lambda x: os.path.basename(x.name))
self.__data.addHeaderValueRow("Name", lambda x: x.name)
- self.__data.addHeaderValueRow("File", lambda x: x.file.filename)
+ if obj.file is not None:
+ self.__data.addHeaderValueRow("File", lambda x: x.file.filename)
if hasattr(obj, "path"):
# That's a link
@@ -322,8 +360,11 @@ class Hdf5TableModel(HierarchicalTableView.HierarchicalTableModel):
else:
if silx.io.is_file(obj):
physical = lambda x: x.filename + SEPARATOR + x.name
+ elif obj.file is not None:
+ physical = lambda x: x.file.filename + SEPARATOR + x.name
else:
- physical = lambda x: x.file.filename + SEPARATOR + x.name
+ # Guess it is a virtual node
+ physical = "No physical location"
self.__data.addHeaderValueRow("Physical", physical)
if hasattr(obj, "dtype"):
@@ -367,7 +408,10 @@ class Hdf5TableModel(HierarchicalTableView.HierarchicalTableModel):
self.__data.addHeaderRow(headerLabel="Attributes")
for key in sorted(obj.attrs.keys()):
callback = lambda key, x: self.__formatter.toString(x.attrs[key])
- self.__data.addHeaderValueRow(headerLabel=key, value=functools.partial(callback, key))
+ callbackTooltip = lambda key, x: self.__attributeTooltip(x.attrs[key])
+ self.__data.addHeaderValueRow(headerLabel=key,
+ value=functools.partial(callback, key),
+ tooltip=functools.partial(callbackTooltip, key))
def __get_filter_info(self, dataset, filterIndex):
"""Get a tuple of readable info from dataset filters
@@ -447,7 +491,7 @@ class Hdf5TableView(HierarchicalTableView.HierarchicalTableView):
def setData(self, data):
"""Set the h5py-like object exposed by the model
- :param h5pyObject: A h5py-like object. It can be a `h5py.Dataset`,
+ :param data: A h5py-like object. It can be a `h5py.Dataset`,
a `h5py.File`, a `h5py.Group`. It also can be a,
`silx.gui.hdf5.H5Node` which is needed to display some local path
information.
diff --git a/silx/gui/data/HexaTableView.py b/silx/gui/data/HexaTableView.py
index 1b2a7e9..c86c0af 100644
--- a/silx/gui/data/HexaTableView.py
+++ b/silx/gui/data/HexaTableView.py
@@ -37,7 +37,7 @@ from silx.gui.widgets.TableWidget import CopySelectedCellsAction
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "27/09/2017"
+__date__ = "23/05/2018"
class _VoidConnector(object):
@@ -54,7 +54,13 @@ class _VoidConnector(object):
def __getBuffer(self, bufferId):
if bufferId not in self.__cache:
pos = bufferId << 10
- data = self.__data.tobytes()[pos:pos + 1024]
+ data = self.__data
+ if hasattr(data, "tobytes"):
+ data = data.tobytes()[pos:pos + 1024]
+ else:
+ # Old fashion
+ data = data.data[pos:pos + 1024]
+
self.__cache[bufferId] = data
if len(self.__cache) > 32:
self.__cache.popitem()
diff --git a/silx/gui/data/NXdataWidgets.py b/silx/gui/data/NXdataWidgets.py
index ae2911d..1bf5425 100644
--- a/silx/gui/data/NXdataWidgets.py
+++ b/silx/gui/data/NXdataWidgets.py
@@ -26,14 +26,14 @@
"""
__authors__ = ["P. Knobel"]
__license__ = "MIT"
-__date__ = "20/12/2017"
+__date__ = "24/04/2018"
import numpy
from silx.gui import qt
from silx.gui.data.NumpyAxesSelector import NumpyAxesSelector
-from silx.gui.plot import Plot1D, Plot2D, StackView
-from silx.gui.plot.Colormap import Colormap
+from silx.gui.plot import Plot1D, Plot2D, StackView, ScatterView
+from silx.gui.colors import Colormap
from silx.gui.widgets.FrameBrowser import HorizontalSliderWithBrowser
from silx.math.calibration import ArrayCalibration, NoCalibration, LinearCalibration
@@ -211,10 +211,10 @@ class XYVScatterPlot(qt.QWidget):
self.__y_axis_name = None
self.__y_axis_errors = None
- self._plot = Plot1D(self)
- self._plot.setDefaultColormap(Colormap(name="viridis",
- vmin=None, vmax=None,
- normalization=Colormap.LINEAR))
+ self._plot = ScatterView(self)
+ self._plot.setColormap(Colormap(name="viridis",
+ vmin=None, vmax=None,
+ normalization=Colormap.LINEAR))
self._slider = HorizontalSliderWithBrowser(parent=self)
self._slider.setMinimum(0)
@@ -235,9 +235,9 @@ class XYVScatterPlot(qt.QWidget):
def getPlot(self):
"""Returns the plot used for the display
- :rtype: Plot1D
+ :rtype: PlotWidget
"""
- return self._plot
+ return self._plot.getPlotWidget()
def setScattersData(self, y, x, values,
yerror=None, xerror=None,
@@ -284,8 +284,6 @@ class XYVScatterPlot(qt.QWidget):
x = self.__x_axis
y = self.__y_axis
- self._plot.remove(kind=("scatter", ))
-
idx = self._slider.value()
title = ""
@@ -294,16 +292,15 @@ class XYVScatterPlot(qt.QWidget):
title += self.__scatter_titles[idx] # scatter dataset name
self._plot.setGraphTitle(title)
- self._plot.addScatter(x, y, self.__values[idx],
- legend="scatter%d" % idx,
- xerror=self.__x_axis_errors,
- yerror=self.__y_axis_errors)
+ self._plot.setData(x, y, self.__values[idx],
+ xerror=self.__x_axis_errors,
+ yerror=self.__y_axis_errors)
self._plot.resetZoom()
self._plot.getXAxis().setLabel(self.__x_axis_name)
self._plot.getYAxis().setLabel(self.__y_axis_name)
def clear(self):
- self._plot.clear()
+ self._plot.getPlotWidget().clear()
class ArrayImagePlot(qt.QWidget):
@@ -476,7 +473,8 @@ class ArrayImagePlot(qt.QWidget):
scale = (xscale, yscale)
self._plot.addImage(image, legend=legend,
- origin=origin, scale=scale)
+ origin=origin, scale=scale,
+ replace=True)
else:
scatterx, scattery = numpy.meshgrid(x_axis, y_axis)
# fixme: i don't think this can handle "irregular" RGBA images
diff --git a/silx/gui/data/TextFormatter.py b/silx/gui/data/TextFormatter.py
index 332625c..8440509 100644
--- a/silx/gui/data/TextFormatter.py
+++ b/silx/gui/data/TextFormatter.py
@@ -27,7 +27,7 @@ data module to format data as text in the same way."""
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "13/12/2017"
+__date__ = "25/06/2018"
import numpy
import numbers
@@ -204,7 +204,7 @@ class TextFormatter(qt.QObject):
def __formatBinary(self, data):
if isinstance(data, numpy.void):
if six.PY2:
- data = [ord(d) for d in data.item()]
+ data = [ord(d) for d in data.data]
else:
data = data.item().astype(numpy.uint8)
elif six.PY2:
@@ -266,6 +266,8 @@ class TextFormatter(qt.QObject):
elif vlen == six.binary_type:
# HDF5 ASCII
return self.__formatCharString(data)
+ elif isinstance(vlen, numpy.dtype):
+ return self.toString(data, vlen)
return None
def toString(self, data, dtype=None):
@@ -291,11 +293,17 @@ class TextFormatter(qt.QObject):
else:
text = [self.toString(d, dtype) for d in data]
return "[" + " ".join(text) + "]"
+ if dtype is not None and dtype.kind == 'O':
+ text = self.__formatH5pyObject(data, dtype)
+ if text is not None:
+ return text
elif isinstance(data, numpy.void):
if dtype is None:
dtype = data.dtype
- if data.dtype.fields is not None:
- text = [self.toString(data[f], dtype) for f in dtype.fields]
+ if dtype.fields is not None:
+ text = []
+ for index, field in enumerate(dtype.fields.items()):
+ text.append(field[0] + ":" + self.toString(data[index], field[1][0]))
return "(" + " ".join(text) + ")"
return self.__formatBinary(data)
elif isinstance(data, (numpy.unicode_, six.text_type)):
@@ -340,7 +348,7 @@ class TextFormatter(qt.QObject):
elif isinstance(data, (numbers.Real, numpy.floating)):
# It have to be done before complex checking
return self.__floatFormat % data
- elif isinstance(data, (numpy.complex_, numbers.Complex)):
+ elif isinstance(data, (numpy.complexfloating, numbers.Complex)):
text = ""
if data.real != 0:
text += self.__floatFormat % data.real
diff --git a/silx/gui/data/test/test_dataviewer.py b/silx/gui/data/test/test_dataviewer.py
index 274df92..f3c2808 100644
--- a/silx/gui/data/test/test_dataviewer.py
+++ b/silx/gui/data/test/test_dataviewer.py
@@ -24,7 +24,7 @@
# ###########################################################################*/
__authors__ = ["V. Valls"]
__license__ = "MIT"
-__date__ = "22/02/2018"
+__date__ = "23/04/2018"
import os
import tempfile
@@ -208,7 +208,6 @@ class AbstractDataViewerTests(TestCaseQt):
self.assertEquals(widget.displayedView().modeId(), DataViews.RAW_MODE)
widget.setDisplayMode(DataViews.EMPTY_MODE)
self.assertEquals(widget.displayedView().modeId(), DataViews.EMPTY_MODE)
- DataView._cleanUpCache()
def test_create_default_views(self):
widget = self.create_widget()
@@ -287,7 +286,6 @@ class TestDataView(TestCaseQt):
dataViewClass = DataViews._Plot2dView
widget = self.createDataViewWithData(dataViewClass, data[0])
self.qWaitForWindowExposed(widget)
- DataView._cleanUpCache()
def testCubeWithComplex(self):
self.skipTest("OpenGL widget not yet tested")
@@ -299,14 +297,12 @@ class TestDataView(TestCaseQt):
dataViewClass = DataViews._Plot3dView
widget = self.createDataViewWithData(dataViewClass, data)
self.qWaitForWindowExposed(widget)
- DataView._cleanUpCache()
def testImageStackWithComplex(self):
data = self.createComplexData()
dataViewClass = DataViews._StackView
widget = self.createDataViewWithData(dataViewClass, data)
self.qWaitForWindowExposed(widget)
- DataView._cleanUpCache()
def suite():