diff options
Diffstat (limited to 'silx/gui/plot3d/items/volume.py')
-rw-r--r-- | silx/gui/plot3d/items/volume.py | 151 |
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) |