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.py988
1 files changed, 988 insertions, 0 deletions
diff --git a/silx/gui/data/DataViews.py b/silx/gui/data/DataViews.py
new file mode 100644
index 0000000..d8d605a
--- /dev/null
+++ b/silx/gui/data/DataViews.py
@@ -0,0 +1,988 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2016-2017 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
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""This module defines a views used by :class:`silx.gui.data.DataViewer`.
+"""
+
+import logging
+import numbers
+import numpy
+
+import silx.io
+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
+
+__authors__ = ["V. Valls", "P. Knobel"]
+__license__ = "MIT"
+__date__ = "07/04/2017"
+
+_logger = logging.getLogger(__name__)
+
+
+# DataViewer modes
+EMPTY_MODE = 0
+PLOT1D_MODE = 10
+PLOT2D_MODE = 20
+PLOT3D_MODE = 30
+RAW_MODE = 40
+RAW_ARRAY_MODE = 41
+RAW_RECORD_MODE = 42
+RAW_SCALAR_MODE = 43
+STACK_MODE = 50
+HDF5_MODE = 60
+
+
+def _normalizeData(data):
+ """Returns a normalized data.
+
+ If the data embed a numpy data or a dataset it is returned.
+ Else returns the input data."""
+ if isinstance(data, H5Node):
+ return data.h5py_object
+ return data
+
+
+def _normalizeComplex(data):
+ """Returns a normalized complex data.
+
+ If the data is a numpy data with complex, returns the
+ absolute value.
+ Else returns the input data."""
+ if hasattr(data, "dtype"):
+ isComplex = numpy.issubdtype(data.dtype, numpy.complex)
+ else:
+ isComplex = isinstance(data, numbers.Complex)
+ if isComplex:
+ data = numpy.absolute(data)
+ return data
+
+
+class DataInfo(object):
+ """Store extracted information from a data"""
+
+ def __init__(self, data):
+ data = self.normalizeData(data)
+ self.isArray = False
+ self.interpretation = None
+ self.isNumeric = False
+ self.isComplex = False
+ self.isRecord = False
+ self.isNXdata = False
+ self.shape = tuple()
+ self.dim = 0
+
+ if data is None:
+ return
+
+ if silx.io.is_group(data) and nxdata.is_valid_nxdata(data):
+ self.isNXdata = True
+ nxd = nxdata.NXdata(data)
+
+ if isinstance(data, numpy.ndarray):
+ self.isArray = True
+ elif silx.io.is_dataset(data) and data.shape != tuple():
+ self.isArray = True
+ else:
+ self.isArray = False
+
+ if silx.io.is_dataset(data):
+ self.interpretation = data.attrs.get("interpretation", None)
+ elif self.isNXdata:
+ self.interpretation = nxd.interpretation
+ else:
+ self.interpretation = None
+
+ if hasattr(data, "dtype"):
+ self.isNumeric = numpy.issubdtype(data.dtype, numpy.number)
+ self.isRecord = data.dtype.fields is not None
+ self.isComplex = numpy.issubdtype(data.dtype, numpy.complex)
+ elif self.isNXdata:
+ self.isNumeric = numpy.issubdtype(nxd.signal.dtype,
+ numpy.number)
+ self.isComplex = numpy.issubdtype(nxd.signal.dtype, numpy.complex)
+ else:
+ self.isNumeric = isinstance(data, numbers.Number)
+ self.isComplex = isinstance(data, numbers.Complex)
+ self.isRecord = False
+
+ if hasattr(data, "shape"):
+ self.shape = data.shape
+ elif self.isNXdata:
+ self.shape = nxd.signal.shape
+ else:
+ self.shape = tuple()
+ self.dim = len(self.shape)
+
+ def normalizeData(self, data):
+ """Returns a normalized data if the embed a numpy or a dataset.
+ Else returns the data."""
+ return _normalizeData(data)
+
+
+class DataView(object):
+ """Holder for the data view."""
+
+ UNSUPPORTED = -1
+ """Priority returned when the requested data can't be displayed by the
+ view."""
+
+ def __init__(self, parent, modeId=None, icon=None, label=None):
+ """Constructor
+
+ :param qt.QWidget parent: Parent of the hold widget
+ """
+ self.__parent = parent
+ self.__widget = None
+ self.__modeId = modeId
+ if label is None:
+ label = self.__class__.__name__
+ self.__label = label
+ if icon is None:
+ icon = qt.QIcon()
+ self.__icon = icon
+
+ def icon(self):
+ """Returns the default icon"""
+ return self.__icon
+
+ def label(self):
+ """Returns the default label"""
+ return self.__label
+
+ def modeId(self):
+ """Returns the mode id"""
+ return self.__modeId
+
+ def normalizeData(self, data):
+ """Returns a normalized data if the embed a numpy or a dataset.
+ Else returns the data."""
+ return _normalizeData(data)
+
+ def customAxisNames(self):
+ """Returns names of axes which can be custom by the user and provided
+ to the view."""
+ return []
+
+ def setCustomAxisValue(self, name, value):
+ """
+ Set the value of a custom axis
+
+ :param str name: Name of the custom axis
+ :param int value: Value of the custom axis
+ """
+ pass
+
+ def isWidgetInitialized(self):
+ """Returns true if the widget is already initialized.
+ """
+ return self.__widget is not None
+
+ def select(self):
+ """Called when the view is selected to display the data.
+ """
+ return
+
+ def getWidget(self):
+ """Returns the widget hold in the view and displaying the data.
+
+ :returns: qt.QWidget
+ """
+ if self.__widget is None:
+ self.__widget = self.createWidget(self.__parent)
+ return self.__widget
+
+ def createWidget(self, parent):
+ """Create the the widget displaying the data
+
+ :param qt.QWidget parent: Parent of the widget
+ :returns: qt.QWidget
+ """
+ raise NotImplementedError()
+
+ def clear(self):
+ """Clear the data from the view"""
+ return None
+
+ def setData(self, data):
+ """Set the data displayed by the view
+
+ :param data: Data to display
+ :type data: numpy.ndarray or h5py.Dataset
+ """
+ return None
+
+ def axesNames(self, data, info):
+ """Returns names of the expected axes of the view, according to the
+ input data.
+
+ :param data: Data to display
+ :type data: numpy.ndarray or h5py.Dataset
+ :param DataInfo info: Pre-computed information on the data
+ :rtype: list[str]
+ """
+ return []
+
+ def getDataPriority(self, data, info):
+ """
+ Returns the priority of using this view according to a data.
+
+ - `UNSUPPORTED` means this view can't display this data
+ - `1` means this view can display the data
+ - `100` means this view should be used for this data
+ - `1000` max value used by the views provided by silx
+ - ...
+
+ :param object data: The data to check
+ :param DataInfo info: Pre-computed information on the data
+ :rtype: int
+ """
+ return DataView.UNSUPPORTED
+
+ def __lt__(self, other):
+ return str(self) < str(other)
+
+
+class CompositeDataView(DataView):
+ """Data view which can display a data using different view according to
+ the kind of the data."""
+
+ def __init__(self, parent, modeId=None, icon=None, label=None):
+ """Constructor
+
+ :param qt.QWidget parent: Parent of the hold widget
+ """
+ super(CompositeDataView, self).__init__(parent, modeId, icon, label)
+ self.__views = {}
+ self.__currentView = None
+
+ def addView(self, dataView):
+ """Add a new dataview to the available list."""
+ self.__views[dataView] = None
+
+ 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)
+
+ if len(views) == 0:
+ return None
+ elif views[0][0] == DataView.UNSUPPORTED:
+ return None
+ else:
+ return views[0][1]
+
+ def customAxisNames(self):
+ if self.__currentView is None:
+ return
+ return self.__currentView.customAxisNames()
+
+ def setCustomAxisValue(self, name, value):
+ if self.__currentView is None:
+ return
+ self.__currentView.setCustomAxisValue(name, value)
+
+ def __updateDisplayedView(self):
+ widget = self.getWidget()
+ if self.__currentView is None:
+ return
+
+ # load the widget if it is not yet done
+ index = self.__views[self.__currentView]
+ if index is None:
+ w = self.__currentView.getWidget()
+ index = widget.addWidget(w)
+ self.__views[self.__currentView] = index
+ if widget.currentIndex() != index:
+ widget.setCurrentIndex(index)
+ self.__currentView.select()
+
+ def select(self):
+ self.__updateDisplayedView()
+ if self.__currentView is not None:
+ self.__currentView.select()
+
+ def createWidget(self, parent):
+ return qt.QStackedWidget()
+
+ def clear(self):
+ for v in self.__views.keys():
+ v.clear()
+
+ def setData(self, data):
+ if self.__currentView is None:
+ return
+ self.__updateDisplayedView()
+ self.__currentView.setData(data)
+
+ def axesNames(self, data, info):
+ view = self.getBestView(data, info)
+ self.__currentView = view
+ return view.axesNames(data, info)
+
+ def getDataPriority(self, data, info):
+ view = self.getBestView(data, info)
+ self.__currentView = view
+ if view is None:
+ return DataView.UNSUPPORTED
+ else:
+ return view.getDataPriority(data, info)
+
+
+class _EmptyView(DataView):
+ """Dummy view to display nothing"""
+
+ def __init__(self, parent):
+ DataView.__init__(self, parent, modeId=EMPTY_MODE)
+
+ def axesNames(self, data, info):
+ return []
+
+ def createWidget(self, parent):
+ return qt.QLabel(parent)
+
+ def getDataPriority(self, data, info):
+ return DataView.UNSUPPORTED
+
+
+class _Plot1dView(DataView):
+ """View displaying data using a 1d plot"""
+
+ def __init__(self, parent):
+ super(_Plot1dView, self).__init__(
+ parent=parent,
+ modeId=PLOT1D_MODE,
+ label="Curve",
+ icon=icons.getQIcon("view-1d"))
+ self.__resetZoomNextTime = True
+
+ def createWidget(self, parent):
+ from silx.gui import plot
+ return plot.Plot1D(parent=parent)
+
+ def clear(self):
+ self.getWidget().clear()
+ self.__resetZoomNextTime = True
+
+ def normalizeData(self, data):
+ data = DataView.normalizeData(self, data)
+ data = _normalizeComplex(data)
+ return data
+
+ def setData(self, data):
+ data = self.normalizeData(data)
+ self.getWidget().addCurve(legend="data",
+ x=range(len(data)),
+ y=data,
+ resetzoom=self.__resetZoomNextTime)
+ self.__resetZoomNextTime = True
+
+ def axesNames(self, data, info):
+ return ["y"]
+
+ def getDataPriority(self, data, info):
+ if data is None or not info.isArray or not info.isNumeric:
+ return DataView.UNSUPPORTED
+ if info.dim < 1:
+ return DataView.UNSUPPORTED
+ if info.interpretation == "spectrum":
+ return 1000
+ if info.dim == 2 and info.shape[0] == 1:
+ return 210
+ if info.dim == 1:
+ return 100
+ else:
+ return 10
+
+
+class _Plot2dView(DataView):
+ """View displaying data using a 2d plot"""
+
+ def __init__(self, parent):
+ super(_Plot2dView, self).__init__(
+ parent=parent,
+ modeId=PLOT2D_MODE,
+ label="Image",
+ icon=icons.getQIcon("view-2d"))
+ self.__resetZoomNextTime = True
+
+ def createWidget(self, parent):
+ from silx.gui import plot
+ widget = plot.Plot2D(parent=parent)
+ widget.setKeepDataAspectRatio(True)
+ widget.setGraphXLabel('X')
+ widget.setGraphYLabel('Y')
+ return widget
+
+ def clear(self):
+ self.getWidget().clear()
+ self.__resetZoomNextTime = True
+
+ def normalizeData(self, data):
+ data = DataView.normalizeData(self, data)
+ data = _normalizeComplex(data)
+ return data
+
+ def setData(self, data):
+ data = self.normalizeData(data)
+ self.getWidget().addImage(legend="data",
+ data=data,
+ resetzoom=self.__resetZoomNextTime)
+ self.__resetZoomNextTime = False
+
+ def axesNames(self, data, info):
+ return ["y", "x"]
+
+ def getDataPriority(self, data, info):
+ if data is None or not info.isArray or not info.isNumeric:
+ 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 _Plot3dView(DataView):
+ """View displaying data using a 3d plot"""
+
+ def __init__(self, parent):
+ super(_Plot3dView, self).__init__(
+ parent=parent,
+ modeId=PLOT3D_MODE,
+ label="Cube",
+ icon=icons.getQIcon("view-3d"))
+ try:
+ import silx.gui.plot3d #noqa
+ except ImportError:
+ _logger.warning("Plot3dView is not available")
+ _logger.debug("Backtrace", exc_info=True)
+ raise
+ self.__resetZoomNextTime = True
+
+ def createWidget(self, parent):
+ from silx.gui.plot3d import ScalarFieldView
+ from silx.gui.plot3d import SFViewParamTree
+
+ plot = ScalarFieldView.ScalarFieldView(parent)
+ plot.setAxesLabels(*reversed(self.axesNames(None, None)))
+ plot.addIsosurface(
+ lambda data: numpy.mean(data) + numpy.std(data), '#FF0000FF')
+
+ # Create a parameter tree for the scalar field view
+ options = SFViewParamTree.TreeView(plot)
+ options.setSfView(plot)
+
+ # Add the parameter tree to the main window in a dock widget
+ dock = qt.QDockWidget()
+ dock.setWidget(options)
+ plot.addDockWidget(qt.Qt.RightDockWidgetArea, dock)
+
+ return plot
+
+ def clear(self):
+ self.getWidget().setData(None)
+ self.__resetZoomNextTime = True
+
+ def normalizeData(self, data):
+ data = DataView.normalizeData(self, data)
+ data = _normalizeComplex(data)
+ return data
+
+ def setData(self, data):
+ data = self.normalizeData(data)
+ plot = self.getWidget()
+ plot.setData(data)
+ self.__resetZoomNextTime = False
+
+ def axesNames(self, data, info):
+ return ["z", "y", "x"]
+
+ def getDataPriority(self, data, info):
+ if data is None or not info.isArray or not info.isNumeric:
+ return DataView.UNSUPPORTED
+ if info.dim < 3:
+ return DataView.UNSUPPORTED
+ if min(data.shape) < 2:
+ return DataView.UNSUPPORTED
+ if info.dim == 3:
+ return 100
+ else:
+ return 10
+
+
+class _ArrayView(DataView):
+ """View displaying data using a 2d table"""
+
+ def __init__(self, parent):
+ DataView.__init__(self, parent, modeId=RAW_ARRAY_MODE)
+
+ def createWidget(self, parent):
+ from silx.gui.data.ArrayTableWidget import ArrayTableWidget
+ widget = ArrayTableWidget(parent)
+ widget.displayAxesSelector(False)
+ return widget
+
+ def clear(self):
+ self.getWidget().setArrayData(numpy.array([[]]))
+
+ def setData(self, data):
+ data = self.normalizeData(data)
+ self.getWidget().setArrayData(data)
+
+ def axesNames(self, data, info):
+ return ["col", "row"]
+
+ def getDataPriority(self, data, info):
+ if data is None or not info.isArray or info.isRecord:
+ return DataView.UNSUPPORTED
+ if info.dim < 2:
+ return DataView.UNSUPPORTED
+ if info.interpretation in ["scalar", "scaler"]:
+ return 1000
+ return 500
+
+
+class _StackView(DataView):
+ """View displaying data using a stack of images"""
+
+ def __init__(self, parent):
+ super(_StackView, self).__init__(
+ parent=parent,
+ modeId=STACK_MODE,
+ label="Image stack",
+ icon=icons.getQIcon("view-2d-stack"))
+ self.__resetZoomNextTime = True
+
+ def customAxisNames(self):
+ return ["depth"]
+
+ def setCustomAxisValue(self, name, value):
+ if name == "depth":
+ self.getWidget().setFrameNumber(value)
+ else:
+ raise Exception("Unsupported axis")
+
+ def createWidget(self, parent):
+ from silx.gui import plot
+ widget = plot.StackView(parent=parent)
+ widget.setKeepDataAspectRatio(True)
+ widget.setLabels(self.axesNames(None, None))
+ # hide default option panel
+ widget.setOptionVisible(False)
+ return widget
+
+ def clear(self):
+ self.getWidget().clear()
+ self.__resetZoomNextTime = True
+
+ def normalizeData(self, data):
+ data = DataView.normalizeData(self, data)
+ data = _normalizeComplex(data)
+ return data
+
+ def setData(self, data):
+ data = self.normalizeData(data)
+ self.getWidget().setStack(stack=data, reset=self.__resetZoomNextTime)
+ self.__resetZoomNextTime = False
+
+ def axesNames(self, data, info):
+ return ["depth", "y", "x"]
+
+ def getDataPriority(self, data, info):
+ if data is None or not info.isArray or not info.isNumeric:
+ return DataView.UNSUPPORTED
+ if info.dim < 3:
+ return DataView.UNSUPPORTED
+ if info.interpretation == "image":
+ return 500
+ return 90
+
+
+class _ScalarView(DataView):
+ """View displaying data using text"""
+
+ def __init__(self, parent):
+ DataView.__init__(self, parent, modeId=RAW_SCALAR_MODE)
+
+ def createWidget(self, parent):
+ widget = qt.QTextEdit(parent)
+ widget.setTextInteractionFlags(qt.Qt.TextSelectableByMouse)
+ widget.setAlignment(qt.Qt.AlignLeft | qt.Qt.AlignTop)
+ self.__formatter = TextFormatter(parent)
+ return widget
+
+ def clear(self):
+ self.getWidget().setText("")
+
+ def setData(self, data):
+ data = self.normalizeData(data)
+ if silx.io.is_dataset(data):
+ data = data[()]
+ text = self.__formatter.toString(data)
+ self.getWidget().setText(text)
+
+ def axesNames(self, data, info):
+ return []
+
+ def getDataPriority(self, data, info):
+ data = self.normalizeData(data)
+ if data is None:
+ return DataView.UNSUPPORTED
+ if silx.io.is_group(data):
+ return DataView.UNSUPPORTED
+ return 2
+
+
+class _RecordView(DataView):
+ """View displaying data using text"""
+
+ def __init__(self, parent):
+ DataView.__init__(self, parent, modeId=RAW_RECORD_MODE)
+
+ def createWidget(self, parent):
+ from .RecordTableView import RecordTableView
+ widget = RecordTableView(parent)
+ widget.setWordWrap(False)
+ return widget
+
+ def clear(self):
+ self.getWidget().setArrayData(None)
+
+ def setData(self, data):
+ data = self.normalizeData(data)
+ widget = self.getWidget()
+ widget.setArrayData(data)
+ widget.resizeRowsToContents()
+ widget.resizeColumnsToContents()
+
+ def axesNames(self, data, info):
+ return ["data"]
+
+ def getDataPriority(self, data, info):
+ if info.isRecord:
+ return 40
+ if data is None or not info.isArray:
+ return DataView.UNSUPPORTED
+ if info.dim == 1:
+ if info.interpretation in ["scalar", "scaler"]:
+ return 1000
+ if info.shape[0] == 1:
+ return 510
+ return 500
+ elif info.isRecord:
+ return 40
+ return DataView.UNSUPPORTED
+
+
+class _Hdf5View(DataView):
+ """View displaying data using text"""
+
+ def __init__(self, parent):
+ super(_Hdf5View, self).__init__(
+ parent=parent,
+ modeId=HDF5_MODE,
+ label="HDF5",
+ icon=icons.getQIcon("view-hdf5"))
+
+ def createWidget(self, parent):
+ from .Hdf5TableView import Hdf5TableView
+ widget = Hdf5TableView(parent)
+ return widget
+
+ def clear(self):
+ widget = self.getWidget()
+ widget.setData(None)
+
+ def setData(self, data):
+ widget = self.getWidget()
+ widget.setData(data)
+
+ def axesNames(self, data, info):
+ return []
+
+ def getDataPriority(self, data, info):
+ widget = self.getWidget()
+ if widget.isSupportedData(data):
+ return 1
+ else:
+ return DataView.UNSUPPORTED
+
+
+class _RawView(CompositeDataView):
+ """View displaying data as raw data.
+
+ This implementation use a 2d-array view, or a record array view, or a
+ raw text output.
+ """
+
+ def __init__(self, parent):
+ super(_RawView, self).__init__(
+ parent=parent,
+ modeId=RAW_MODE,
+ label="Raw",
+ icon=icons.getQIcon("view-raw"))
+ self.addView(_ScalarView(parent))
+ self.addView(_ArrayView(parent))
+ self.addView(_RecordView(parent))
+
+
+class _NXdataScalarView(DataView):
+ """DataView using a table view for displaying NXdata scalars:
+ 0-D signal or n-D signal with *@interpretation=scalar*"""
+ def __init__(self, parent):
+ DataView.__init__(self, parent)
+
+ def createWidget(self, parent):
+ from silx.gui.data.ArrayTableWidget import ArrayTableWidget
+ widget = ArrayTableWidget(parent)
+ # widget.displayAxesSelector(False)
+ return widget
+
+ def axesNames(self, data, info):
+ return ["col", "row"]
+
+ def clear(self):
+ self.getWidget().setArrayData(numpy.array([[]]),
+ labels=True)
+
+ def setData(self, data):
+ data = self.normalizeData(data)
+ signal = NXdata(data).signal
+ self.getWidget().setArrayData(signal,
+ labels=True)
+
+ def getDataPriority(self, data, info):
+ data = self.normalizeData(data)
+ if info.isNXdata:
+ nxd = NXdata(data)
+ if nxd.signal_is_0d or nxd.interpretation in ["scalar", "scaler"]:
+ return 100
+ return DataView.UNSUPPORTED
+
+
+class _NXdataCurveView(DataView):
+ """DataView using a Plot1D for displaying NXdata curves:
+ 1-D signal or n-D signal with *@interpretation=spectrum*.
+
+ It also handles basic scatter plots:
+ a 1-D signal with one axis whose values are not monotonically increasing.
+ """
+ def __init__(self, parent):
+ DataView.__init__(self, parent)
+
+ def createWidget(self, parent):
+ from silx.gui.data.NXdataWidgets import ArrayCurvePlot
+ widget = ArrayCurvePlot(parent)
+ return widget
+
+ def axesNames(self, data, info):
+ # disabled (used by default axis selector widget in Hdf5Viewer)
+ return []
+
+ def clear(self):
+ self.getWidget().clear()
+
+ def setData(self, data):
+ data = self.normalizeData(data)
+ nxd = NXdata(data)
+ signal_name = data.attrs["signal"]
+ group_name = data.name
+ if nxd.axes_names[-1] is not None:
+ x_errors = nxd.get_axis_errors(nxd.axes_names[-1])
+ else:
+ x_errors = None
+
+ self.getWidget().setCurveData(nxd.signal, nxd.axes[-1],
+ yerror=nxd.errors, xerror=x_errors,
+ ylabel=signal_name, xlabel=nxd.axes_names[-1],
+ title="NXdata group " + group_name)
+
+ def getDataPriority(self, data, info):
+ data = self.normalizeData(data)
+ if info.isNXdata:
+ nxd = NXdata(data)
+ if nxd.is_x_y_value_scatter or nxd.is_unsupported_scatter:
+ return DataView.UNSUPPORTED
+ if nxd.signal_is_1d and \
+ not nxd.interpretation in ["scalar", "scaler"]:
+ return 100
+ if nxd.interpretation == "spectrum":
+ return 100
+ return DataView.UNSUPPORTED
+
+
+class _NXdataXYVScatterView(DataView):
+ """DataView using a Plot1D for displaying NXdata 3D scatters as
+ a scatter of coloured points (1-D signal with 2 axes)"""
+ def __init__(self, parent):
+ DataView.__init__(self, parent)
+
+ def createWidget(self, parent):
+ from silx.gui.data.NXdataWidgets import ArrayCurvePlot
+ widget = ArrayCurvePlot(parent)
+ return widget
+
+ def axesNames(self, data, info):
+ # disabled (used by default axis selector widget in Hdf5Viewer)
+ return []
+
+ def clear(self):
+ self.getWidget().clear()
+
+ def setData(self, data):
+ data = self.normalizeData(data)
+ nxd = NXdata(data)
+ signal_name = data.attrs["signal"]
+ # signal_errors = nx.errors # not supported
+ group_name = data.name
+ x_axis, y_axis = nxd.axes[-2:]
+
+ x_label, y_label = nxd.axes_names[-2:]
+ if x_label is not None:
+ x_errors = nxd.get_axis_errors(x_label)
+ else:
+ x_errors = None
+
+ if y_label is not None:
+ y_errors = nxd.get_axis_errors(y_label)
+ else:
+ y_errors = None
+
+ self.getWidget().setCurveData(y_axis, x_axis, values=nxd.signal,
+ yerror=y_errors, xerror=x_errors,
+ ylabel=signal_name, xlabel=x_label,
+ title="NXdata group " + group_name)
+
+ def getDataPriority(self, data, info):
+ data = self.normalizeData(data)
+ if info.isNXdata:
+ if NXdata(data).is_x_y_value_scatter:
+ return 100
+ return DataView.UNSUPPORTED
+
+
+class _NXdataImageView(DataView):
+ """DataView using a Plot2D for displaying NXdata images:
+ 2-D signal or n-D signals with *@interpretation=spectrum*."""
+ def __init__(self, parent):
+ DataView.__init__(self, parent)
+
+ def createWidget(self, parent):
+ from silx.gui.data.NXdataWidgets import ArrayImagePlot
+ widget = ArrayImagePlot(parent)
+ return widget
+
+ def axesNames(self, data, info):
+ return []
+
+ def clear(self):
+ self.getWidget().clear()
+
+ def setData(self, data):
+ data = self.normalizeData(data)
+ nxd = NXdata(data)
+ signal_name = data.attrs["signal"]
+ group_name = data.name
+ y_axis, x_axis = nxd.axes[-2:]
+ y_label, x_label = nxd.axes_names[-2:]
+
+ self.getWidget().setImageData(
+ nxd.signal, x_axis=x_axis, y_axis=y_axis,
+ signal_name=signal_name, xlabel=x_label, ylabel=y_label,
+ title="NXdata group %s: %s" % (group_name, signal_name))
+
+ def getDataPriority(self, data, info):
+ data = self.normalizeData(data)
+ if info.isNXdata:
+ nxd = NXdata(data)
+ if nxd.signal_is_2d:
+ if nxd.interpretation not in ["scalar", "spectrum", "scaler"]:
+ return 100
+ if nxd.interpretation == "image":
+ return 100
+ return DataView.UNSUPPORTED
+
+
+class _NXdataStackView(DataView):
+ def __init__(self, parent):
+ DataView.__init__(self, parent)
+
+ def createWidget(self, parent):
+ from silx.gui.data.NXdataWidgets import ArrayStackPlot
+ widget = ArrayStackPlot(parent)
+ return widget
+
+ def axesNames(self, data, info):
+ return []
+
+ def clear(self):
+ self.getWidget().clear()
+
+ def setData(self, data):
+ data = self.normalizeData(data)
+ nxd = NXdata(data)
+ signal_name = data.attrs["signal"]
+ group_name = data.name
+ z_axis, y_axis, x_axis = nxd.axes[-3:]
+ z_label, y_label, x_label = nxd.axes_names[-3:]
+
+ self.getWidget().setStackData(
+ nxd.signal, x_axis=x_axis, y_axis=y_axis, z_axis=z_axis,
+ signal_name=signal_name,
+ xlabel=x_label, ylabel=y_label, zlabel=z_label,
+ title="NXdata group %s: %s" % (group_name, signal_name))
+
+ def getDataPriority(self, data, info):
+ data = self.normalizeData(data)
+ if info.isNXdata:
+ nxd = NXdata(data)
+ if nxd.signal_ndim >= 3:
+ if nxd.interpretation not in ["scalar", "scaler",
+ "spectrum", "image"]:
+ return 100
+ return DataView.UNSUPPORTED
+
+
+class _NXdataView(CompositeDataView):
+ """Composite view displaying NXdata groups using the most adequate
+ widget depending on the dimensionality."""
+ def __init__(self, parent):
+ super(_NXdataView, self).__init__(
+ parent=parent,
+ label="NXdata",
+ icon=icons.getQIcon("view-nexus"))
+
+ self.addView(_NXdataScalarView(parent))
+ self.addView(_NXdataCurveView(parent))
+ self.addView(_NXdataXYVScatterView(parent))
+ self.addView(_NXdataImageView(parent))
+ self.addView(_NXdataStackView(parent))