summaryrefslogtreecommitdiff
path: root/silx/gui/plot3d/items/volume.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot3d/items/volume.py')
-rw-r--r--silx/gui/plot3d/items/volume.py151
1 files changed, 126 insertions, 25 deletions
diff --git a/silx/gui/plot3d/items/volume.py b/silx/gui/plot3d/items/volume.py
index ae91e82..e0a2a1f 100644
--- a/silx/gui/plot3d/items/volume.py
+++ b/silx/gui/plot3d/items/volume.py
@@ -37,13 +37,14 @@ import numpy
from silx.math.combo import min_max
from silx.math.marchingcubes import MarchingCubes
+from silx.math.interpolate import interp3d
from ....utils.proxy import docstring
from ... import _glutils as glu
from ... import qt
from ...colors import rgba
-from ..scene import cutplane, primitives, transform, utils
+from ..scene import cutplane, function, primitives, transform, utils
from .core import BaseNodeItem, Item3D, ItemChangedType, Item3DChangedType
from .mixins import ColormapMixIn, ComplexMixIn, InterpolationMixIn, PlaneMixIn
@@ -301,6 +302,15 @@ class Isosurface(Item3D):
"""Return the color of this iso-surface (QColor)"""
return qt.QColor.fromRgbF(*self._color)
+ def _updateColor(self, color):
+ """Handle update of color
+
+ :param List[float] color: RGBA channels in [0, 1]
+ """
+ primitive = self._getScenePrimitive()
+ if len(primitive.children) != 0:
+ primitive.children[0].setAttribute('color', color)
+
def setColor(self, color):
"""Set the color of the iso-surface
@@ -310,15 +320,15 @@ class Isosurface(Item3D):
color = rgba(color)
if color != self._color:
self._color = color
- primitive = self._getScenePrimitive()
- if len(primitive.children) != 0:
- primitive.children[0].setAttribute('color', self._color)
+ self._updateColor(self._color)
self._updated(ItemChangedType.COLOR)
- def _updateScenePrimitive(self):
- """Update underlying mesh"""
- self._getScenePrimitive().children = []
+ def _computeIsosurface(self):
+ """Compute isosurface for current state.
+ :return: (vertices, normals, indices) arrays
+ :rtype: List[Union[None,numpy.ndarray]]
+ """
data = self.getData(copy=False)
if data is None:
@@ -349,24 +359,31 @@ class Isosurface(Item3D):
self._level = level
self._updated(Item3DChangedType.ISO_LEVEL)
- if not numpy.isfinite(self._level):
- return
+ if numpy.isfinite(self._level):
+ st = time.time()
+ vertices, normals, indices = MarchingCubes(
+ data,
+ isolevel=self._level)
+ _logger.info('Computed iso-surface in %f s.', time.time() - st)
- st = time.time()
- vertices, normals, indices = MarchingCubes(
- data,
- isolevel=self._level)
- _logger.info('Computed iso-surface in %f s.', time.time() - st)
+ if len(vertices) != 0:
+ return vertices, normals, indices
- if len(vertices) == 0:
- return
- else:
- mesh = primitives.Mesh3D(vertices,
- colors=self._color,
- normals=normals,
- mode='triangles',
- indices=indices)
- self._getScenePrimitive().children = [mesh]
+ return None, None, None
+
+ def _updateScenePrimitive(self):
+ """Update underlying mesh"""
+ self._getScenePrimitive().children = []
+
+ vertices, normals, indices = self._computeIsosurface()
+ if vertices is not None:
+ mesh = primitives.Mesh3D(vertices,
+ colors=self._color,
+ normals=normals,
+ mode='triangles',
+ indices=indices,
+ copy=False)
+ self._getScenePrimitive().children = [mesh]
def _pickFull(self, context):
"""Perform picking in this item at given widget position.
@@ -677,17 +694,39 @@ class ComplexCutPlane(CutPlane, ComplexMixIn):
super(ComplexCutPlane, self)._updated(event)
-class ComplexIsosurface(Isosurface):
+class ComplexIsosurface(Isosurface, ComplexMixIn, ColormapMixIn):
"""Class representing an iso-surface in a :class:`ComplexField3D` item.
:param parent: The DataItem3D this iso-surface belongs to
"""
+ _SUPPORTED_COMPLEX_MODES = \
+ (ComplexMixIn.ComplexMode.NONE,) + ComplexMixIn._SUPPORTED_COMPLEX_MODES
+ """Overrides supported ComplexMode"""
+
def __init__(self, parent):
- super(ComplexIsosurface, self).__init__(parent)
+ ComplexMixIn.__init__(self)
+ ColormapMixIn.__init__(self, function.Colormap())
+ Isosurface.__init__(self, parent=parent)
+ self.setComplexMode(self.ComplexMode.NONE)
+
+ def _updateColor(self, color):
+ """Handle update of color
+
+ :param List[float] color: RGBA channels in [0, 1]
+ """
+ primitive = self._getScenePrimitive()
+ if (len(primitive.children) != 0 and
+ isinstance(primitive.children[0], primitives.ColormapMesh3D)):
+ primitive.children[0].alpha = self._color[3]
+ else:
+ super(ComplexIsosurface, self)._updateColor(color)
def _syncDataWithParent(self):
"""Synchronize this instance data with that of its parent"""
+ if self.getComplexMode() != self.ComplexMode.NONE:
+ self._setRangeFromData(self.getColormappedData(copy=False))
+
parent = self.parent()
if parent is None:
self._data = None
@@ -702,6 +741,67 @@ class ComplexIsosurface(Isosurface):
self._syncDataWithParent()
super(ComplexIsosurface, self)._parentChanged(event)
+ def getColormappedData(self, copy=True):
+ """Return 3D dataset used to apply the colormap on the isosurface.
+
+ This depends on :meth:`getComplexMode`.
+
+ :param bool copy:
+ True (default) to get a copy,
+ False to get the internal data (DO NOT modify!)
+ :return: The data set (or None if not set)
+ :rtype: Union[numpy.ndarray,None]
+ """
+ if self.getComplexMode() == self.ComplexMode.NONE:
+ return None
+ else:
+ parent = self.parent()
+ if parent is None:
+ return None
+ else:
+ return parent.getData(mode=self.getComplexMode(), copy=copy)
+
+ def _updated(self, event=None):
+ """Handle update of the isosurface (and take care of mode change)
+
+ :param ItemChangedType event: The kind of update
+ """
+ if (event == ItemChangedType.COMPLEX_MODE and
+ self.getComplexMode() != self.ComplexMode.NONE):
+ self._setRangeFromData(self.getColormappedData(copy=False))
+
+ if event in (ItemChangedType.COMPLEX_MODE,
+ ItemChangedType.COLORMAP,
+ Item3DChangedType.INTERPOLATION):
+ self._updateScenePrimitive()
+ super(ComplexIsosurface, self)._updated(event)
+
+ def _updateScenePrimitive(self):
+ """Update underlying mesh"""
+ if self.getComplexMode() == self.ComplexMode.NONE:
+ super(ComplexIsosurface, self)._updateScenePrimitive()
+
+ else: # Specific display for colormapped isosurface
+ self._getScenePrimitive().children = []
+
+ values = self.getColormappedData(copy=False)
+ if values is not None:
+ vertices, normals, indices = self._computeIsosurface()
+ if vertices is not None:
+ values = interp3d(values, vertices, method='linear_omp')
+ # TODO reuse isosurface when only color changes...
+
+ mesh = primitives.ColormapMesh3D(
+ vertices,
+ value=values.reshape(-1, 1),
+ colormap=self._getSceneColormap(),
+ normal=normals,
+ mode='triangles',
+ indices=indices,
+ copy=False)
+ mesh.alpha = self._color[3]
+ self._getScenePrimitive().children = [mesh]
+
class ComplexField3D(ScalarField3D, ComplexMixIn):
"""3D complex field on a regular grid.
@@ -720,6 +820,7 @@ class ComplexField3D(ScalarField3D, ComplexMixIn):
@docstring(ComplexMixIn)
def setComplexMode(self, mode):
+ mode = ComplexMixIn.ComplexMode.from_value(mode)
if mode != self.getComplexMode():
self.clearIsosurfaces() # Reset isosurfaces
ComplexMixIn.setComplexMode(self, mode)