diff options
Diffstat (limited to 'silx/gui/data/DataViews.py')
-rw-r--r-- | silx/gui/data/DataViews.py | 209 |
1 files changed, 177 insertions, 32 deletions
diff --git a/silx/gui/data/DataViews.py b/silx/gui/data/DataViews.py index d8d605a..1ad997b 100644 --- a/silx/gui/data/DataViews.py +++ b/silx/gui/data/DataViews.py @@ -25,6 +25,7 @@ """This module defines a views used by :class:`silx.gui.data.DataViewer`. """ +from collections import OrderedDict import logging import numbers import numpy @@ -34,11 +35,11 @@ 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 NXdata +from silx.io.nxdata import NXdata, get_attr_as_string __authors__ = ["V. Valls", "P. Knobel"] __license__ = "MIT" -__date__ = "07/04/2017" +__date__ = "03/10/2017" _logger = logging.getLogger(__name__) @@ -52,6 +53,7 @@ RAW_MODE = 40 RAW_ARRAY_MODE = 41 RAW_RECORD_MODE = 42 RAW_SCALAR_MODE = 43 +RAW_HEXA_MODE = 44 STACK_MODE = 50 HDF5_MODE = 60 @@ -62,6 +64,8 @@ def _normalizeData(data): If the data embed a numpy data or a dataset it is returned. Else returns the input data.""" if isinstance(data, H5Node): + if data.is_broken: + return None return data.h5py_object return data @@ -89,11 +93,14 @@ class DataInfo(object): self.isArray = False self.interpretation = None self.isNumeric = False + self.isVoid = False self.isComplex = False + self.isBoolean = False self.isRecord = False self.isNXdata = False self.shape = tuple() self.dim = 0 + self.size = 0 if data is None: return @@ -110,23 +117,32 @@ class DataInfo(object): self.isArray = False if silx.io.is_dataset(data): - self.interpretation = data.attrs.get("interpretation", None) + if "interpretation" in data.attrs: + self.interpretation = get_attr_as_string(data, "interpretation") + else: + self.interpretation = None elif self.isNXdata: self.interpretation = nxd.interpretation else: self.interpretation = None if hasattr(data, "dtype"): + if numpy.issubdtype(data.dtype, numpy.void): + # That's a real opaque type, else it is a structured type + self.isVoid = data.dtype.fields is None self.isNumeric = numpy.issubdtype(data.dtype, numpy.number) self.isRecord = data.dtype.fields is not None self.isComplex = numpy.issubdtype(data.dtype, numpy.complex) + self.isBoolean = numpy.issubdtype(data.dtype, numpy.bool_) elif self.isNXdata: self.isNumeric = numpy.issubdtype(nxd.signal.dtype, numpy.number) self.isComplex = numpy.issubdtype(nxd.signal.dtype, numpy.complex) + self.isBoolean = numpy.issubdtype(nxd.signal.dtype, numpy.bool_) else: self.isNumeric = isinstance(data, numbers.Number) self.isComplex = isinstance(data, numbers.Complex) + self.isBoolean = isinstance(data, bool) self.isRecord = False if hasattr(data, "shape"): @@ -135,7 +151,13 @@ class DataInfo(object): self.shape = nxd.signal.shape else: self.shape = tuple() - self.dim = len(self.shape) + if self.shape is not None: + self.dim = len(self.shape) + + if hasattr(data, "size"): + self.size = int(data.size) + else: + self.size = 1 def normalizeData(self, data): """Returns a normalized data if the embed a numpy or a dataset. @@ -237,12 +259,12 @@ class DataView(object): def axesNames(self, data, info): """Returns names of the expected axes of the view, according to the - input data. + input data. A none value will disable the default axes selectior. :param data: Data to display :type data: numpy.ndarray or h5py.Dataset :param DataInfo info: Pre-computed information on the data - :rtype: list[str] + :rtype: list[str] or None """ return [] @@ -276,7 +298,7 @@ class CompositeDataView(DataView): :param qt.QWidget parent: Parent of the hold widget """ super(CompositeDataView, self).__init__(parent, modeId, icon, label) - self.__views = {} + self.__views = OrderedDict() self.__currentView = None def addView(self, dataView): @@ -285,10 +307,9 @@ class CompositeDataView(DataView): def getBestView(self, data, info): """Returns the best view according to priorities.""" - info = DataInfo(data) views = [(v.getDataPriority(data, info), v) for v in self.__views.keys()] views = filter(lambda t: t[0] > DataView.UNSUPPORTED, views) - views = sorted(views, reverse=True) + views = sorted(views, key=lambda t: t[0], reverse=True) if len(views) == 0: return None @@ -361,7 +382,7 @@ class _EmptyView(DataView): DataView.__init__(self, parent, modeId=EMPTY_MODE) def axesNames(self, data, info): - return [] + return None def createWidget(self, parent): return qt.QLabel(parent) @@ -406,6 +427,8 @@ class _Plot1dView(DataView): return ["y"] def getDataPriority(self, data, info): + if info.size <= 0: + return DataView.UNSUPPORTED if data is None or not info.isArray or not info.isNumeric: return DataView.UNSUPPORTED if info.dim < 1: @@ -434,9 +457,10 @@ class _Plot2dView(DataView): def createWidget(self, parent): from silx.gui import plot widget = plot.Plot2D(parent=parent) + widget.getIntensityHistogramAction().setVisible(True) widget.setKeepDataAspectRatio(True) - widget.setGraphXLabel('X') - widget.setGraphYLabel('Y') + widget.getXAxis().setLabel('X') + widget.getYAxis().setLabel('Y') return widget def clear(self): @@ -459,7 +483,11 @@ class _Plot2dView(DataView): return ["y", "x"] def getDataPriority(self, data, info): - if data is None or not info.isArray or not info.isNumeric: + if info.size <= 0: + return DataView.UNSUPPORTED + if (data is None or + not info.isArray or + not (info.isNumeric or info.isBoolean)): return DataView.UNSUPPORTED if info.dim < 2: return DataView.UNSUPPORTED @@ -494,8 +522,15 @@ class _Plot3dView(DataView): plot = ScalarFieldView.ScalarFieldView(parent) plot.setAxesLabels(*reversed(self.axesNames(None, None))) - plot.addIsosurface( - lambda data: numpy.mean(data) + numpy.std(data), '#FF0000FF') + + def computeIsolevel(data): + data = data[numpy.isfinite(data)] + if len(data) == 0: + return 0 + else: + return numpy.mean(data) + numpy.std(data) + + plot.addIsosurface(computeIsolevel, '#FF0000FF') # Create a parameter tree for the scalar field view options = SFViewParamTree.TreeView(plot) @@ -527,6 +562,8 @@ class _Plot3dView(DataView): return ["z", "y", "x"] def getDataPriority(self, data, info): + if info.size <= 0: + return DataView.UNSUPPORTED if data is None or not info.isArray or not info.isNumeric: return DataView.UNSUPPORTED if info.dim < 3: @@ -539,6 +576,54 @@ class _Plot3dView(DataView): return 10 +class _ComplexImageView(DataView): + """View displaying data using a ComplexImageView""" + + def __init__(self, parent): + super(_ComplexImageView, self).__init__( + parent=parent, + modeId=PLOT2D_MODE, + label="Complex Image", + icon=icons.getQIcon("view-2d")) + + def createWidget(self, parent): + from silx.gui.plot.ComplexImageView import ComplexImageView + widget = ComplexImageView(parent=parent) + widget.getPlot().getIntensityHistogramAction().setVisible(True) + widget.getPlot().setKeepDataAspectRatio(True) + widget.getXAxis().setLabel('X') + widget.getYAxis().setLabel('Y') + return widget + + def clear(self): + self.getWidget().setData(None) + + def normalizeData(self, data): + data = DataView.normalizeData(self, data) + return data + + def setData(self, data): + data = self.normalizeData(data) + self.getWidget().setData(data) + + def axesNames(self, data, info): + return ["y", "x"] + + def getDataPriority(self, data, info): + if info.size <= 0: + return DataView.UNSUPPORTED + if data is None or not info.isArray or not info.isComplex: + return DataView.UNSUPPORTED + if info.dim < 2: + return DataView.UNSUPPORTED + if info.interpretation == "image": + return 1000 + if info.dim == 2: + return 200 + else: + return 190 + + class _ArrayView(DataView): """View displaying data using a 2d table""" @@ -562,6 +647,8 @@ class _ArrayView(DataView): return ["col", "row"] def getDataPriority(self, data, info): + if info.size <= 0: + return DataView.UNSUPPORTED if data is None or not info.isArray or info.isRecord: return DataView.UNSUPPORTED if info.dim < 2: @@ -618,6 +705,8 @@ class _StackView(DataView): return ["depth", "y", "x"] def getDataPriority(self, data, info): + if info.size <= 0: + return DataView.UNSUPPORTED if data is None or not info.isArray or not info.isNumeric: return DataView.UNSUPPORTED if info.dim < 3: @@ -644,17 +733,21 @@ class _ScalarView(DataView): self.getWidget().setText("") def setData(self, data): - data = self.normalizeData(data) - if silx.io.is_dataset(data): - data = data[()] - text = self.__formatter.toString(data) + d = self.normalizeData(data) + if silx.io.is_dataset(d): + d = d[()] + text = self.__formatter.toString(d, data.dtype) self.getWidget().setText(text) def axesNames(self, data, info): return [] def getDataPriority(self, data, info): + if info.size <= 0: + return DataView.UNSUPPORTED data = self.normalizeData(data) + if info.shape is None: + return DataView.UNSUPPORTED if data is None: return DataView.UNSUPPORTED if silx.io.is_group(data): @@ -681,13 +774,16 @@ class _RecordView(DataView): data = self.normalizeData(data) widget = self.getWidget() widget.setArrayData(data) - widget.resizeRowsToContents() - widget.resizeColumnsToContents() + if len(data) < 100: + widget.resizeRowsToContents() + widget.resizeColumnsToContents() def axesNames(self, data, info): return ["data"] def getDataPriority(self, data, info): + if info.size <= 0: + return DataView.UNSUPPORTED if info.isRecord: return 40 if data is None or not info.isArray: @@ -703,6 +799,36 @@ class _RecordView(DataView): return DataView.UNSUPPORTED +class _HexaView(DataView): + """View displaying data using text""" + + def __init__(self, parent): + DataView.__init__(self, parent, modeId=RAW_HEXA_MODE) + + def createWidget(self, parent): + from .HexaTableView import HexaTableView + widget = HexaTableView(parent) + return widget + + def clear(self): + self.getWidget().setArrayData(None) + + def setData(self, data): + data = self.normalizeData(data) + widget = self.getWidget() + widget.setArrayData(data) + + def axesNames(self, data, info): + return [] + + def getDataPriority(self, data, info): + if info.size <= 0: + return DataView.UNSUPPORTED + if info.isVoid: + return 2000 + return DataView.UNSUPPORTED + + class _Hdf5View(DataView): """View displaying data using text""" @@ -727,7 +853,7 @@ class _Hdf5View(DataView): widget.setData(data) def axesNames(self, data, info): - return [] + return None def getDataPriority(self, data, info): widget = self.getWidget() @@ -750,11 +876,28 @@ class _RawView(CompositeDataView): modeId=RAW_MODE, label="Raw", icon=icons.getQIcon("view-raw")) + self.addView(_HexaView(parent)) self.addView(_ScalarView(parent)) self.addView(_ArrayView(parent)) self.addView(_RecordView(parent)) +class _ImageView(CompositeDataView): + """View displaying data as 2D image + + It choose between Plot2D and ComplexImageView widgets + """ + + def __init__(self, parent): + super(_ImageView, self).__init__( + parent=parent, + modeId=PLOT2D_MODE, + label="Image", + icon=icons.getQIcon("view-2d")) + self.addView(_ComplexImageView(parent)) + self.addView(_Plot2dView(parent)) + + class _NXdataScalarView(DataView): """DataView using a table view for displaying NXdata scalars: 0-D signal or n-D signal with *@interpretation=scalar*""" @@ -806,7 +949,7 @@ class _NXdataCurveView(DataView): def axesNames(self, data, info): # disabled (used by default axis selector widget in Hdf5Viewer) - return [] + return None def clear(self): self.getWidget().clear() @@ -814,10 +957,10 @@ class _NXdataCurveView(DataView): def setData(self, data): data = self.normalizeData(data) nxd = NXdata(data) - signal_name = data.attrs["signal"] + signal_name = get_attr_as_string(data, "signal") group_name = data.name - if nxd.axes_names[-1] is not None: - x_errors = nxd.get_axis_errors(nxd.axes_names[-1]) + if nxd.axes_dataset_names[-1] is not None: + x_errors = nxd.get_axis_errors(nxd.axes_dataset_names[-1]) else: x_errors = None @@ -853,7 +996,7 @@ class _NXdataXYVScatterView(DataView): def axesNames(self, data, info): # disabled (used by default axis selector widget in Hdf5Viewer) - return [] + return None def clear(self): self.getWidget().clear() @@ -861,7 +1004,7 @@ class _NXdataXYVScatterView(DataView): def setData(self, data): data = self.normalizeData(data) nxd = NXdata(data) - signal_name = data.attrs["signal"] + signal_name = get_attr_as_string(data, "signal") # signal_errors = nx.errors # not supported group_name = data.name x_axis, y_axis = nxd.axes[-2:] @@ -902,7 +1045,8 @@ class _NXdataImageView(DataView): return widget def axesNames(self, data, info): - return [] + # disabled (used by default axis selector widget in Hdf5Viewer) + return None def clear(self): self.getWidget().clear() @@ -910,7 +1054,7 @@ class _NXdataImageView(DataView): def setData(self, data): data = self.normalizeData(data) nxd = NXdata(data) - signal_name = data.attrs["signal"] + signal_name = get_attr_as_string(data, "signal") group_name = data.name y_axis, x_axis = nxd.axes[-2:] y_label, x_label = nxd.axes_names[-2:] @@ -942,7 +1086,8 @@ class _NXdataStackView(DataView): return widget def axesNames(self, data, info): - return [] + # disabled (used by default axis selector widget in Hdf5Viewer) + return None def clear(self): self.getWidget().clear() @@ -950,7 +1095,7 @@ class _NXdataStackView(DataView): def setData(self, data): data = self.normalizeData(data) nxd = NXdata(data) - signal_name = data.attrs["signal"] + signal_name = get_attr_as_string(data, "signal") group_name = data.name z_axis, y_axis, x_axis = nxd.axes[-3:] z_label, y_label, x_label = nxd.axes_names[-3:] |