summaryrefslogtreecommitdiff
path: root/silx/gui/data/DataViews.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/data/DataViews.py')
-rw-r--r--silx/gui/data/DataViews.py260
1 files changed, 151 insertions, 109 deletions
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