summaryrefslogtreecommitdiff
path: root/silx/gui/data/DataViewer.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/data/DataViewer.py')
-rw-r--r--silx/gui/data/DataViewer.py593
1 files changed, 0 insertions, 593 deletions
diff --git a/silx/gui/data/DataViewer.py b/silx/gui/data/DataViewer.py
deleted file mode 100644
index 2e51439..0000000
--- a/silx/gui/data/DataViewer.py
+++ /dev/null
@@ -1,593 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2016-2019 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 widget designed to display data using the most adapted
-view from the ones provided by silx.
-"""
-from __future__ import division
-
-import logging
-import os.path
-import collections
-from silx.gui import qt
-from silx.gui.data import DataViews
-from silx.gui.data.DataViews import _normalizeData
-from silx.gui.utils import blockSignals
-from silx.gui.data.NumpyAxesSelector import NumpyAxesSelector
-
-
-__authors__ = ["V. Valls"]
-__license__ = "MIT"
-__date__ = "12/02/2019"
-
-
-_logger = logging.getLogger(__name__)
-
-
-DataSelection = collections.namedtuple("DataSelection",
- ["filename", "datapath",
- "slice", "permutation"])
-
-
-class DataViewer(qt.QFrame):
- """Widget to display any kind of data
-
- .. image:: img/DataViewer.png
-
- The method :meth:`setData` allows to set any data to the widget. Mostly
- `numpy.array` and `h5py.Dataset` are supported with adapted views. Other
- data types are displayed using a text viewer.
-
- A default view is automatically selected when a data is set. The method
- :meth:`setDisplayMode` allows to change the view. To have a graphical tool
- to select the view, prefer using the widget :class:`DataViewerFrame`.
-
- The dimension of the input data and the expected dimension of the selected
- view can differ. For example you can display an image (2D) from 4D
- data. In this case a :class:`NumpyAxesSelector` is displayed to allow the
- user to select the axis mapping and the slicing of other axes.
-
- .. code-block:: python
-
- import numpy
- data = numpy.random.rand(500,500)
- viewer = DataViewer()
- viewer.setData(data)
- viewer.setVisible(True)
- """
-
- displayedViewChanged = qt.Signal(object)
- """Emitted when the displayed view changes"""
-
- dataChanged = qt.Signal()
- """Emitted when the data changes"""
-
- currentAvailableViewsChanged = qt.Signal()
- """Emitted when the current available views (which support the current
- data) change"""
-
- def __init__(self, parent=None):
- """Constructor
-
- :param QWidget parent: The parent of the widget
- """
- super(DataViewer, self).__init__(parent)
-
- self.__stack = qt.QStackedWidget(self)
- self.__numpySelection = NumpyAxesSelector(self)
- self.__numpySelection.selectedAxisChanged.connect(self.__numpyAxisChanged)
- self.__numpySelection.selectionChanged.connect(self.__numpySelectionChanged)
- self.__numpySelection.customAxisChanged.connect(self.__numpyCustomAxisChanged)
-
- self.setLayout(qt.QVBoxLayout(self))
- self.layout().addWidget(self.__stack, 1)
-
- group = qt.QGroupBox(self)
- group.setLayout(qt.QVBoxLayout())
- group.layout().addWidget(self.__numpySelection)
- group.setTitle("Axis selection")
- self.__axisSelection = group
-
- self.layout().addWidget(self.__axisSelection)
-
- self.__currentAvailableViews = []
- self.__currentView = None
- self.__data = None
- self.__info = None
- self.__useAxisSelection = False
- self.__userSelectedView = None
- self.__hooks = None
-
- self.__views = []
- self.__index = {}
- """store stack index for each views"""
-
- self._initializeViews()
-
- def _initializeViews(self):
- """Inisialize the available views"""
- views = self.createDefaultViews(self.__stack)
- 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
- overwriten to provide a different set of viewers.
-
- :param QWidget parent: QWidget parent of the views
- :rtype: List[silx.gui.data.DataViews.DataView]
- """
- viewClasses = [
- DataViews._EmptyView,
- DataViews._Hdf5View,
- DataViews._NXdataView,
- DataViews._Plot1dView,
- DataViews._ImageView,
- DataViews._Plot3dView,
- DataViews._RawView,
- DataViews._StackView,
- DataViews._Plot2dRecordView,
- ]
- views = []
- for viewClass in viewClasses:
- try:
- view = viewClass(parent)
- views.append(view)
- except Exception:
- _logger.warning("%s instantiation failed. View is ignored" % viewClass.__name__)
- _logger.debug("Backtrace", exc_info=True)
-
- return views
-
- def clear(self):
- """Clear the widget"""
- self.setData(None)
-
- def normalizeData(self, data):
- """Returns a normalized data if the embed a numpy or a dataset.
- Else returns the data."""
- return _normalizeData(data)
-
- def __getStackIndex(self, view):
- """Get the stack index containing the view.
-
- :param silx.gui.data.DataViews.DataView view: The view
- """
- if view not in self.__index:
- widget = view.getWidget()
- index = self.__stack.addWidget(widget)
- self.__index[view] = index
- else:
- index = self.__index[view]
- return index
-
- def __clearCurrentView(self):
- """Clear the current selected view"""
- view = self.__currentView
- if view is not None:
- view.clear()
-
- def __numpyCustomAxisChanged(self, name, value):
- view = self.__currentView
- if view is not None:
- view.setCustomAxisValue(name, value)
-
- def __updateNumpySelectionAxis(self):
- """
- Update the numpy-selector according to the needed axis names
- """
- with blockSignals(self.__numpySelection):
- previousPermutation = self.__numpySelection.permutation()
- previousSelection = self.__numpySelection.selection()
-
- self.__numpySelection.clear()
-
- 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
- self.__numpySelection.setAxisNames(axisNames)
- self.__numpySelection.setCustomAxis(
- self.__currentView.customAxisNames())
- data = self.normalizeData(self.__data)
- self.__numpySelection.setData(data)
-
- # Try to restore previous permutation and selection
- try:
- self.__numpySelection.setSelection(
- previousSelection, previousPermutation)
- except ValueError as e:
- _logger.info("Not restoring selection because: %s", e)
-
- if hasattr(data, "shape"):
- isVisible = not (len(axisNames) == 1 and len(data.shape) == 1)
- else:
- isVisible = True
- self.__axisSelection.setVisible(isVisible)
- else:
- self.__useAxisSelection = False
- self.__axisSelection.setVisible(False)
-
- def __updateDataInView(self):
- """
- Update the views using the current data
- """
- if self.__useAxisSelection:
- self.__displayedData = self.__numpySelection.selectedData()
-
- permutation = self.__numpySelection.permutation()
- normal = tuple(range(len(permutation)))
- if permutation == normal:
- permutation = None
- slicing = self.__numpySelection.selection()
- normal = tuple([slice(None)] * len(slicing))
- if slicing == normal:
- slicing = None
- else:
- self.__displayedData = self.__data
- permutation = None
- slicing = None
-
- try:
- filename = os.path.abspath(self.__data.file.filename)
- except:
- filename = None
-
- try:
- datapath = self.__data.name
- except:
- datapath = None
-
- # FIXME: maybe use DataUrl, with added support of permutation
- self.__displayedSelection = DataSelection(filename, datapath, slicing, permutation)
-
- # TODO: would be good to avoid that, it should be synchonous
- qt.QTimer.singleShot(10, self.__setDataInView)
-
- def __setDataInView(self):
- self.__currentView.setData(self.__displayedData)
- self.__currentView.setDataSelection(self.__displayedSelection)
-
- def setDisplayedView(self, view):
- """Set the displayed view.
-
- Change the displayed view according to the view itself.
-
- :param silx.gui.data.DataViews.DataView view: The DataView to use to display the data
- """
- self.__userSelectedView = view
- self._setDisplayedView(view)
-
- def _setDisplayedView(self, view):
- """Internal set of the displayed view.
-
- Change the displayed view according to the view itself.
-
- :param silx.gui.data.DataViews.DataView view: The DataView to use to display the data
- """
- if self.__currentView is view:
- return
- self.__clearCurrentView()
- self.__currentView = view
- self.__updateNumpySelectionAxis()
- self.__updateDataInView()
- stackIndex = self.__getStackIndex(self.__currentView)
- if self.__currentView is not None:
- self.__currentView.select()
- self.__stack.setCurrentIndex(stackIndex)
- self.displayedViewChanged.emit(view)
-
- def getViewFromModeId(self, modeId):
- """Returns the first available view which have the requested modeId.
- Return None if modeId does not correspond to an existing view.
-
- :param int modeId: Requested mode id
- :rtype: silx.gui.data.DataViews.DataView
- """
- for view in self.__views:
- if view.modeId() == modeId:
- return view
- return None
-
- def setDisplayMode(self, modeId):
- """Set the displayed view using display mode.
-
- Change the displayed view according to the requested mode.
-
- :param int modeId: Display mode, one of
-
- - `DataViews.EMPTY_MODE`: display nothing
- - `DataViews.PLOT1D_MODE`: display the data as a curve
- - `DataViews.IMAGE_MODE`: display the data as an image
- - `DataViews.PLOT3D_MODE`: display the data as an isosurface
- - `DataViews.RAW_MODE`: display the data as a table
- - `DataViews.STACK_MODE`: display the data as a stack of images
- - `DataViews.HDF5_MODE`: display the data as a table of HDF5 info
- - `DataViews.NXDATA_MODE`: display the data as NXdata
- """
- try:
- view = self.getViewFromModeId(modeId)
- except KeyError:
- raise ValueError("Display mode %s is unknown" % modeId)
- self._setDisplayedView(view)
-
- def displayedView(self):
- """Returns the current displayed view.
-
- :rtype: silx.gui.data.DataViews.DataView
- """
- return self.__currentView
-
- def addView(self, view):
- """Allow to add a view to the dataview.
-
- If the current data support this view, it will be displayed.
-
- :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()
-
- def removeView(self, view):
- """Allow to remove a view which was available from the dataview.
-
- If the view was displayed, the widget will be updated.
-
- :param DataView view: A dataview
- """
- self.__views.remove(view)
- self.__stack.removeWidget(view.getWidget())
- # invalidate the full index. It will be updated as expected
- self.__index = {}
-
- if self.__userSelectedView is view:
- self.__userSelectedView = None
-
- if view is self.__currentView:
- self.__updateView()
- else:
- # TODO It can be skipped if the view is not part of the
- # available views
- self.__updateAvailableViews()
-
- def __updateAvailableViews(self):
- """
- Update available views from the current data.
- """
- data = self.__data
- info = self._getInfo()
- # sort available views according to priority
- views = []
- for v in self.__views:
- views.extend(v.getMatchingViews(data, info))
- views = [(v.getCachedDataPriority(data, info), v) for v in views]
- views = filter(lambda t: t[0] > DataViews.DataView.UNSUPPORTED, views)
- views = sorted(views, reverse=True)
- views = [v[1] for v in views]
-
- # store available views
- self.__setCurrentAvailableViews(views)
-
- def __updateView(self):
- """Display the data using the widget which fit the best"""
- data = self.__data
-
- # update available views for this data
- self.__updateAvailableViews()
- available = self.__currentAvailableViews
-
- # display the view with the most priority (the default view)
- view = self.getDefaultViewFromAvailableViews(data, available)
- self.__clearCurrentView()
- try:
- self._setDisplayedView(view)
- except Exception as e:
- # in case there is a problem to read the data, try to use a safe
- # view
- view = self.getSafeViewFromAvailableViews(data, available)
- self._setDisplayedView(view)
- raise e
-
- def getSafeViewFromAvailableViews(self, data, available):
- """Returns a view which is sure to display something without failing
- on rendering.
-
- :param object data: data which will be displayed
- :param List[view] available: List of available views, from highest
- priority to lowest.
- :rtype: DataView
- """
- hdf5View = self.getViewFromModeId(DataViews.HDF5_MODE)
- if hdf5View in available:
- return hdf5View
- return self.getViewFromModeId(DataViews.EMPTY_MODE)
-
- def getDefaultViewFromAvailableViews(self, data, available):
- """Returns the default view which will be used according to available
- views.
-
- :param object data: data which will be displayed
- :param List[view] available: List of available views, from highest
- priority to lowest.
- :rtype: DataView
- """
- if len(available) > 0:
- # returns the view with the highest priority
- if self.__userSelectedView in available:
- return self.__userSelectedView
- self.__userSelectedView = None
- view = available[0]
- else:
- # else returns the empty view
- view = self.getViewFromModeId(DataViews.EMPTY_MODE)
- return view
-
- def __setCurrentAvailableViews(self, availableViews):
- """Set the current available viewa
-
- :param List[DataView] availableViews: Current available viewa
- """
- self.__currentAvailableViews = availableViews
- self.currentAvailableViewsChanged.emit()
-
- def currentAvailableViews(self):
- """Returns the list of available views for the current data
-
- :rtype: List[DataView]
- """
- return self.__currentAvailableViews
-
- def getReachableViews(self):
- """Returns the list of reachable views from the registred available
- views.
-
- :rtype: List[DataView]
- """
- views = []
- for v in self.availableViews():
- views.extend(v.getReachableViews())
- return views
-
- def availableViews(self):
- """Returns the list of registered views
-
- :rtype: List[DataView]
- """
- return self.__views
-
- def setData(self, data):
- """Set the data to view.
-
- It mostly can be a h5py.Dataset or a numpy.ndarray. Other kind of
- objects will be displayed as text rendering.
-
- :param numpy.ndarray data: The data.
- """
- self.__data = data
- self._invalidateInfo()
- self.__displayedData = None
- self.__displayedSelection = None
- self.__updateView()
- self.__updateNumpySelectionAxis()
- self.__updateDataInView()
- self.dataChanged.emit()
-
- def __numpyAxisChanged(self):
- """
- Called when axis selection of the numpy-selector changed
- """
- self.__clearCurrentView()
-
- def __numpySelectionChanged(self):
- """
- Called when data selection of the numpy-selector changed
- """
- self.__updateDataInView()
-
- def data(self):
- """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()
-
- def replaceView(self, modeId, newView):
- """Replace one of the builtin data views with a custom view.
- Return True in case of success, False in case of failure.
-
- .. note::
-
- This method must be called just after instantiation, before
- the viewer is used.
-
- :param int modeId: Unique mode ID identifying the DataView to
- be replaced. One of:
-
- - `DataViews.EMPTY_MODE`
- - `DataViews.PLOT1D_MODE`
- - `DataViews.IMAGE_MODE`
- - `DataViews.PLOT2D_MODE`
- - `DataViews.COMPLEX_IMAGE_MODE`
- - `DataViews.PLOT3D_MODE`
- - `DataViews.RAW_MODE`
- - `DataViews.STACK_MODE`
- - `DataViews.HDF5_MODE`
- - `DataViews.NXDATA_MODE`
- - `DataViews.NXDATA_INVALID_MODE`
- - `DataViews.NXDATA_SCALAR_MODE`
- - `DataViews.NXDATA_CURVE_MODE`
- - `DataViews.NXDATA_XYVSCATTER_MODE`
- - `DataViews.NXDATA_IMAGE_MODE`
- - `DataViews.NXDATA_STACK_MODE`
-
- :param DataViews.DataView newView: New data view
- :return: True if replacement was successful, else False
- """
- assert isinstance(newView, DataViews.DataView)
- 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
- elif isinstance(view, DataViews.CompositeDataView):
- isReplaced = view.replaceView(modeId, newView)
- if isReplaced:
- break
-
- if isReplaced:
- self.__updateAvailableViews()
- return isReplaced