summaryrefslogtreecommitdiff
path: root/silx/gui/data
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/data')
-rw-r--r--silx/gui/data/ArrayTableModel.py5
-rw-r--r--silx/gui/data/DataViews.py46
-rw-r--r--silx/gui/data/NXdataWidgets.py68
-rw-r--r--silx/gui/data/_VolumeWindow.py148
-rw-r--r--silx/gui/data/test/test_dataviewer.py6
-rw-r--r--silx/gui/data/test/test_textformatter.py4
6 files changed, 180 insertions, 97 deletions
diff --git a/silx/gui/data/ArrayTableModel.py b/silx/gui/data/ArrayTableModel.py
index ad4d33a..8805241 100644
--- a/silx/gui/data/ArrayTableModel.py
+++ b/silx/gui/data/ArrayTableModel.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2016-2017 European Synchrotron Radiation Facility
+# 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
@@ -245,8 +245,7 @@ class ArrayTableModel(qt.QAbstractTableModel):
if index.isValid() and role == qt.Qt.EditRole:
try:
# cast value to same type as array
- v = numpy.asscalar(
- numpy.array(value, dtype=self._array.dtype))
+ v = numpy.array(value, dtype=self._array.dtype).item()
except ValueError:
return False
diff --git a/silx/gui/data/DataViews.py b/silx/gui/data/DataViews.py
index 6575d0d..664090d 100644
--- a/silx/gui/data/DataViews.py
+++ b/silx/gui/data/DataViews.py
@@ -893,53 +893,27 @@ class _Plot3dView(DataView):
label="Cube",
icon=icons.getQIcon("view-3d"))
try:
- import silx.gui.plot3d #noqa
+ from ._VolumeWindow import VolumeWindow # noqa
except ImportError:
- _logger.warning("Plot3dView is not available")
+ _logger.warning("3D visualization 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
+ from ._VolumeWindow import VolumeWindow
- plot = ScalarFieldView.ScalarFieldView(parent)
+ plot = VolumeWindow(parent)
plot.setAxesLabels(*reversed(self.axesNames(None, None)))
-
- 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)
- 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.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)
- plot = self.getWidget()
- plot.setData(data)
+ self.getWidget().setData(data)
self.__resetZoomNextTime = False
def axesNames(self, data, info):
@@ -973,10 +947,10 @@ class _ComplexImageView(DataView):
def createWidget(self, parent):
from silx.gui.plot.ComplexImageView import ComplexImageView
widget = ComplexImageView(parent=parent)
- widget.setColormap(self.defaultColormap(), mode=ComplexImageView.Mode.ABSOLUTE)
- widget.setColormap(self.defaultColormap(), mode=ComplexImageView.Mode.SQUARE_AMPLITUDE)
- widget.setColormap(self.defaultColormap(), mode=ComplexImageView.Mode.REAL)
- widget.setColormap(self.defaultColormap(), mode=ComplexImageView.Mode.IMAGINARY)
+ widget.setColormap(self.defaultColormap(), mode=ComplexImageView.ComplexMode.ABSOLUTE)
+ widget.setColormap(self.defaultColormap(), mode=ComplexImageView.ComplexMode.SQUARE_AMPLITUDE)
+ widget.setColormap(self.defaultColormap(), mode=ComplexImageView.ComplexMode.REAL)
+ widget.setColormap(self.defaultColormap(), mode=ComplexImageView.ComplexMode.IMAGINARY)
widget.getPlot().getColormapAction().setColorDialog(self.defaultColorDialog())
widget.getPlot().getIntensityHistogramAction().setVisible(True)
widget.getPlot().setKeepDataAspectRatio(True)
diff --git a/silx/gui/data/NXdataWidgets.py b/silx/gui/data/NXdataWidgets.py
index e5a2550..c3aefd3 100644
--- a/silx/gui/data/NXdataWidgets.py
+++ b/silx/gui/data/NXdataWidgets.py
@@ -29,7 +29,6 @@ __license__ = "MIT"
__date__ = "12/11/2018"
import logging
-import numbers
import numpy
from silx.gui import qt
@@ -533,10 +532,10 @@ class ArrayComplexImagePlot(qt.QWidget):
self._plot = ComplexImageView(self)
if colormap is not None:
- for mode in (ComplexImageView.Mode.ABSOLUTE,
- ComplexImageView.Mode.SQUARE_AMPLITUDE,
- ComplexImageView.Mode.REAL,
- ComplexImageView.Mode.IMAGINARY):
+ for mode in (ComplexImageView.ComplexMode.ABSOLUTE,
+ ComplexImageView.ComplexMode.SQUARE_AMPLITUDE,
+ ComplexImageView.ComplexMode.REAL,
+ ComplexImageView.ComplexMode.IMAGINARY):
self._plot.setColormap(colormap, mode)
self._plot.getPlot().getIntensityHistogramAction().setVisible(True)
@@ -893,28 +892,9 @@ class ArrayVolumePlot(qt.QWidget):
self.__x_axis = None
self.__x_axis_name = None
- from silx.gui.plot3d.ScalarFieldView import ScalarFieldView
- from silx.gui.plot3d import SFViewParamTree
+ from ._VolumeWindow import VolumeWindow
- self._view = ScalarFieldView(self)
-
- def computeIsolevel(data):
- data = data[numpy.isfinite(data)]
- if len(data) == 0:
- return 0
- else:
- return numpy.mean(data) + numpy.std(data)
-
- self._view.addIsosurface(computeIsolevel, '#FF0000FF')
-
- # Create a parameter tree for the scalar field view
- options = SFViewParamTree.TreeView(self._view)
- options.setSfView(self._view)
-
- # Add the parameter tree to the main window in a dock widget
- dock = qt.QDockWidget()
- dock.setWidget(options)
- self._view.addDockWidget(qt.Qt.RightDockWidgetArea, dock)
+ self._view = VolumeWindow(self)
self._hline = qt.QFrame(self)
self._hline.setFrameStyle(qt.QFrame.HLine)
@@ -935,24 +915,10 @@ class ArrayVolumePlot(qt.QWidget):
def getVolumeView(self):
"""Returns the plot used for the display
- :rtype: ScalarFieldView
+ :rtype: SceneWindow
"""
return self._view
- def normalizeComplexData(self, data):
- """
- Converts a complex data array to its amplitude, if necessary.
- :param data: the data to normalize
- :return:
- """
- if hasattr(data, "dtype"):
- isComplex = numpy.issubdtype(data.dtype, numpy.complexfloating)
- else:
- isComplex = isinstance(data, numbers.Complex)
- if isComplex:
- data = numpy.absolute(data)
- return data
-
def setData(self, signal,
x_axis=None, y_axis=None, z_axis=None,
signal_name=None,
@@ -977,7 +943,6 @@ class ArrayVolumePlot(qt.QWidget):
:param zlabel: Label for Z axis
:param title: Graph title
"""
- signal = self.normalizeComplexData(signal)
if self.__selector_is_connected:
self._selector.selectionChanged.disconnect(self._updateVolume)
self.__selector_is_connected = False
@@ -994,9 +959,6 @@ class ArrayVolumePlot(qt.QWidget):
self._selector.setData(signal)
self._selector.setAxisNames(["Y", "X", "Z"])
- self._view.setAxesLabels(self.__x_axis_name or 'X',
- self.__y_axis_name or 'Y',
- self.__z_axis_name or 'Z')
self._updateVolume()
# the legend label shows the selection slice producing the volume
@@ -1017,7 +979,6 @@ class ArrayVolumePlot(qt.QWidget):
def _updateVolume(self):
"""Update displayed stack according to the current axes selector
data."""
- data = self._selector.selectedData()
x_axis = self.__x_axis
y_axis = self.__y_axis
z_axis = self.__z_axis
@@ -1049,15 +1010,16 @@ class ArrayVolumePlot(qt.QWidget):
legend = legend[:-2] + "]"
self._legend.setText("Displayed data: " + legend)
- self._view.setData(data, copy=False)
- self._view.setScale(*scale)
- self._view.setTranslation(*offset)
- self._view.setAxesLabels(self.__x_axis_name,
- self.__y_axis_name,
- self.__z_axis_name)
+ # Update SceneWidget
+ data = self._selector.selectedData()
+
+ volumeView = self.getVolumeView()
+ volumeView.setData(data, offset=offset, scale=scale)
+ volumeView.setAxesLabels(
+ self.__x_axis_name, self.__y_axis_name, self.__z_axis_name)
def clear(self):
old = self._selector.blockSignals(True)
self._selector.clear()
self._selector.blockSignals(old)
- self._view.setData(None)
+ self.getVolumeView().clear()
diff --git a/silx/gui/data/_VolumeWindow.py b/silx/gui/data/_VolumeWindow.py
new file mode 100644
index 0000000..03b6876
--- /dev/null
+++ b/silx/gui/data/_VolumeWindow.py
@@ -0,0 +1,148 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 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 provides a widget to visualize 3D arrays"""
+
+__authors__ = ["T. Vincent"]
+__license__ = "MIT"
+__date__ = "22/03/2019"
+
+
+import numpy
+
+from .. import qt
+from ..plot3d.SceneWindow import SceneWindow
+from ..plot3d.items import ScalarField3D, ComplexField3D, ItemChangedType
+
+
+class VolumeWindow(SceneWindow):
+ """Extends SceneWindow with a convenient API for 3D array
+
+ :param QWidget: parent
+ """
+
+ def __init__(self, parent):
+ super(VolumeWindow, self).__init__(parent)
+ self.__firstData = True
+ # Hide global parameter dock
+ self.getGroupResetWidget().parent().setVisible(False)
+
+ def setAxesLabels(self, xlabel=None, ylabel=None, zlabel=None):
+ """Set the text labels of the axes.
+
+ :param Union[str,None] xlabel: Label of the X axis
+ :param Union[str,None] ylabel: Label of the Y axis
+ :param Union[str,None] zlabel: Label of the Z axis
+ """
+ sceneWidget = self.getSceneWidget()
+ sceneWidget.getSceneGroup().setAxesLabels(
+ 'X' if xlabel is None else xlabel,
+ 'Y' if ylabel is None else ylabel,
+ 'Z' if zlabel is None else zlabel)
+
+ def clear(self):
+ """Clear any currently displayed data"""
+ sceneWidget = self.getSceneWidget()
+ items = sceneWidget.getItems()
+ if (len(items) == 1 and
+ isinstance(items[0], (ScalarField3D, ComplexField3D))):
+ items[0].setData(None)
+ else: # Safety net
+ sceneWidget.clearItems()
+
+ @staticmethod
+ def __computeIsolevel(data):
+ """Returns a suitable isolevel value for data
+
+ :param numpy.ndarray data:
+ :rtype: float
+ """
+ data = data[numpy.isfinite(data)]
+ if len(data) == 0:
+ return 0
+ else:
+ return numpy.mean(data) + numpy.std(data)
+
+ def setData(self, data, offset=(0., 0., 0.), scale=(1., 1., 1.)):
+ """Set the 3D array data to display.
+
+ :param numpy.ndarray data: 3D array of float or complex
+ :param List[float] offset: (tx, ty, tz) coordinates of the origin
+ :param List[float] scale: (sx, sy, sz) scale for each dimension
+ """
+ sceneWidget = self.getSceneWidget()
+ dataMaxCoords = numpy.array(list(reversed(data.shape))) - 1
+
+ previousItems = sceneWidget.getItems()
+ if (len(previousItems) == 1 and
+ isinstance(previousItems[0], (ScalarField3D, ComplexField3D)) and
+ numpy.iscomplexobj(data) == isinstance(previousItems[0], ComplexField3D)):
+ # Reuse existing volume item
+ volume = sceneWidget.getItems()[0]
+ volume.setData(data, copy=False)
+ # Make sure the plane goes through the dataset
+ for plane in volume.getCutPlanes():
+ point = numpy.array(plane.getPoint())
+ if numpy.any(point < (0, 0, 0)) or numpy.any(point > dataMaxCoords):
+ plane.setPoint(dataMaxCoords // 2)
+ else:
+ # Add a new volume
+ sceneWidget.clearItems()
+ volume = sceneWidget.addVolume(data, copy=False)
+ volume.setLabel('Volume')
+ for plane in volume.getCutPlanes():
+ # Make plane going through the center of the data
+ plane.setPoint(dataMaxCoords // 2)
+ plane.setVisible(False)
+ plane.sigItemChanged.connect(self.__cutPlaneUpdated)
+ volume.addIsosurface(self.__computeIsolevel, '#FF0000FF')
+
+ # Expand the parameter tree
+ model = self.getParamTreeView().model()
+ index = qt.QModelIndex() # Invalid index for top level
+ while 1:
+ rowCount = model.rowCount(parent=index)
+ if rowCount == 0:
+ break
+ index = model.index(rowCount - 1, 0, parent=index)
+ self.getParamTreeView().setExpanded(index, True)
+ if not index.isValid():
+ break
+
+ volume.setTranslation(*offset)
+ volume.setScale(*scale)
+
+ if self.__firstData: # Only center for first dataset
+ self.__firstData = False
+ sceneWidget.centerScene()
+
+ def __cutPlaneUpdated(self, event):
+ """Handle the change of visibility of the cut plane
+
+ :param event: Kind of update
+ """
+ if event == ItemChangedType.VISIBLE:
+ plane = self.sender()
+ if plane.isVisible():
+ self.getSceneWidget().selection().setCurrentItem(plane)
diff --git a/silx/gui/data/test/test_dataviewer.py b/silx/gui/data/test/test_dataviewer.py
index dc6fee8..12a640e 100644
--- a/silx/gui/data/test/test_dataviewer.py
+++ b/silx/gui/data/test/test_dataviewer.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2016-2018 European Synchrotron Radiation Facility
+# 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
@@ -242,8 +242,8 @@ class AbstractDataViewerTests(TestCaseQt):
self.assertTrue(replaced)
nxdata_view = widget.getViewFromModeId(DataViews.NXDATA_MODE)
self.assertNotIn(DataViews.NXDATA_INVALID_MODE,
- [v.modeId() for v in nxdata_view.availableViews()])
- self.assertTrue(view in nxdata_view.availableViews())
+ [v.modeId() for v in nxdata_view.getViews()])
+ self.assertTrue(view in nxdata_view.getViews())
class TestDataViewer(AbstractDataViewerTests):
diff --git a/silx/gui/data/test/test_textformatter.py b/silx/gui/data/test/test_textformatter.py
index 935344a..1a63074 100644
--- a/silx/gui/data/test/test_textformatter.py
+++ b/silx/gui/data/test/test_textformatter.py
@@ -1,7 +1,7 @@
# coding: utf-8
# /*##########################################################################
#
-# Copyright (c) 2016-2018 European Synchrotron Radiation Facility
+# 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
@@ -48,7 +48,7 @@ class TestTextFormatter(TestCaseQt):
self.assertIsNot(formatter, copy)
copy.setFloatFormat("%.3f")
self.assertEqual(formatter.integerFormat(), copy.integerFormat())
- self.assertNotEquals(formatter.floatFormat(), copy.floatFormat())
+ self.assertNotEqual(formatter.floatFormat(), copy.floatFormat())
self.assertEqual(formatter.useQuoteForText(), copy.useQuoteForText())
self.assertEqual(formatter.imaginaryUnit(), copy.imaginaryUnit())