summaryrefslogtreecommitdiff
path: root/silx/gui/plot3d/items/core.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/gui/plot3d/items/core.py')
-rw-r--r--silx/gui/plot3d/items/core.py779
1 files changed, 0 insertions, 779 deletions
diff --git a/silx/gui/plot3d/items/core.py b/silx/gui/plot3d/items/core.py
deleted file mode 100644
index ab2ceb6..0000000
--- a/silx/gui/plot3d/items/core.py
+++ /dev/null
@@ -1,779 +0,0 @@
-# coding: utf-8
-# /*##########################################################################
-#
-# Copyright (c) 2017-2020 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 the base class for items of the :class:`.SceneWidget`.
-"""
-
-from __future__ import absolute_import
-
-__authors__ = ["T. Vincent"]
-__license__ = "MIT"
-__date__ = "15/11/2017"
-
-from collections import defaultdict
-import enum
-
-import numpy
-import six
-
-from ... import qt
-from ...plot.items import ItemChangedType
-from .. import scene
-from ..scene import axes, primitives, transform
-from ._pick import PickContext
-
-
-@enum.unique
-class Item3DChangedType(enum.Enum):
- """Type of modification provided by :attr:`Item3D.sigItemChanged` signal."""
-
- INTERPOLATION = 'interpolationChanged'
- """Item3D image interpolation changed flag."""
-
- TRANSFORM = 'transformChanged'
- """Item3D transform changed flag."""
-
- HEIGHT_MAP = 'heightMapChanged'
- """Item3D height map changed flag."""
-
- ISO_LEVEL = 'isoLevelChanged'
- """Isosurface level changed flag."""
-
- LABEL = 'labelChanged'
- """Item's label changed flag."""
-
- BOUNDING_BOX_VISIBLE = 'boundingBoxVisibleChanged'
- """Item's bounding box visibility changed"""
-
- ROOT_ITEM = 'rootItemChanged'
- """Item's root changed flag."""
-
-
-class Item3D(qt.QObject):
- """Base class representing an item in the scene.
-
- :param parent: The View widget this item belongs to.
- :param primitive: An optional primitive to use as scene primitive
- """
-
- _LABEL_INDICES = defaultdict(int)
- """Store per class label indices"""
-
- sigItemChanged = qt.Signal(object)
- """Signal emitted when an item's property has changed.
-
- It provides a flag describing which property of the item has changed.
- See :class:`ItemChangedType` and :class:`Item3DChangedType`
- for flags description.
- """
-
- def __init__(self, parent, primitive=None):
- qt.QObject.__init__(self, parent)
-
- if primitive is None:
- primitive = scene.Group()
-
- self._primitive = primitive
-
- self.__syncForegroundColor()
-
- labelIndex = self._LABEL_INDICES[self.__class__]
- self._label = six.text_type(self.__class__.__name__)
- if labelIndex != 0:
- self._label += u' %d' % labelIndex
- self._LABEL_INDICES[self.__class__] += 1
-
- if isinstance(parent, Item3D):
- parent.sigItemChanged.connect(self.__parentItemChanged)
-
- def setParent(self, parent):
- """Override set parent to handle root item change"""
- previousParent = self.parent()
- if isinstance(previousParent, Item3D):
- previousParent.sigItemChanged.disconnect(self.__parentItemChanged)
-
- super(Item3D, self).setParent(parent)
-
- if isinstance(parent, Item3D):
- parent.sigItemChanged.connect(self.__parentItemChanged)
-
- self._updated(Item3DChangedType.ROOT_ITEM)
-
- def __parentItemChanged(self, event):
- """Handle updates of the parent if it is an Item3D
-
- :param Item3DChangedType event:
- """
- if event == Item3DChangedType.ROOT_ITEM:
- self._updated(Item3DChangedType.ROOT_ITEM)
-
- def root(self):
- """Returns the root of the scene this item belongs to.
-
- The root is the up-most Item3D in the scene tree hierarchy.
-
- :rtype: Union[Item3D, None]
- """
- root = None
- ancestor = self.parent()
- while isinstance(ancestor, Item3D):
- root = ancestor
- ancestor = ancestor.parent()
-
- return root
-
- def _getScenePrimitive(self):
- """Return the group containing the item rendering"""
- return self._primitive
-
- def _updated(self, event=None):
- """Handle MixIn class updates.
-
- :param event: The event to send to :attr:`sigItemChanged` signal.
- """
- if event == Item3DChangedType.ROOT_ITEM:
- self.__syncForegroundColor()
-
- if event is not None:
- self.sigItemChanged.emit(event)
-
- # Label
-
- def getLabel(self):
- """Returns the label associated to this item.
-
- :rtype: str
- """
- return self._label
-
- def setLabel(self, label):
- """Set the label associated to this item.
-
- :param str label:
- """
- label = six.text_type(label)
- if label != self._label:
- self._label = label
- self._updated(Item3DChangedType.LABEL)
-
- # Visibility
-
- def isVisible(self):
- """Returns True if item is visible, else False
-
- :rtype: bool
- """
- return self._getScenePrimitive().visible
-
- def setVisible(self, visible=True):
- """Set the visibility of the item in the scene.
-
- :param bool visible: True (default) to show the item, False to hide
- """
- visible = bool(visible)
- primitive = self._getScenePrimitive()
- if visible != primitive.visible:
- primitive.visible = visible
- self._updated(ItemChangedType.VISIBLE)
-
- # Foreground color
-
- def _setForegroundColor(self, color):
- """Set the foreground color of the item.
-
- The default implementation does nothing, override it in subclass.
-
- :param color: RGBA color
- :type color: tuple of 4 float in [0., 1.]
- """
- if hasattr(super(Item3D, self), '_setForegroundColor'):
- super(Item3D, self)._setForegroundColor(color)
-
- def __syncForegroundColor(self):
- """Retrieve foreground color from parent and update this item"""
- # Look-up for SceneWidget to get its foreground color
- root = self.root()
- if root is not None:
- widget = root.parent()
- if isinstance(widget, qt.QWidget):
- self._setForegroundColor(
- widget.getForegroundColor().getRgbF())
-
- # picking
-
- def _pick(self, context):
- """Implement picking on this item.
-
- :param PickContext context: Current picking context
- :return: Data indices at picked position or None
- :rtype: Union[None,PickingResult]
- """
- if (self.isVisible() and
- context.isEnabled() and
- context.isItemPickable(self) and
- self._pickFastCheck(context)):
- return self._pickFull(context)
- return None
-
- def _pickFastCheck(self, context):
- """Approximate item pick test (e.g., bounding box-based picking).
-
- :param PickContext context: Current picking context
- :return: True if item might be picked
- :rtype: bool
- """
- primitive = self._getScenePrimitive()
-
- positionNdc = context.getNDCPosition()
- if positionNdc is None: # No picking outside viewport
- return False
-
- bounds = primitive.bounds(transformed=False, dataBounds=False)
- if bounds is None: # primitive has no bounds
- return False
-
- bounds = primitive.objectToNDCTransform.transformBounds(bounds)
-
- return (bounds[0, 0] <= positionNdc[0] <= bounds[1, 0] and
- bounds[0, 1] <= positionNdc[1] <= bounds[1, 1])
-
- def _pickFull(self, context):
- """Perform precise picking in this item at given widget position.
-
- :param PickContext context: Current picking context
- :return: Object holding the results or None
- :rtype: Union[None,PickingResult]
- """
- return None
-
-
-class DataItem3D(Item3D):
- """Base class representing a data item with transform in the scene.
-
- :param parent: The View widget this item belongs to.
- :param Union[GroupBBox, None] group:
- The scene group to use for rendering
- """
-
- def __init__(self, parent, group=None):
- if group is None:
- group = primitives.GroupBBox()
-
- # Set-up bounding box
- group.boxVisible = False
- group.axesVisible = False
- else:
- assert isinstance(group, primitives.GroupBBox)
-
- Item3D.__init__(self, parent=parent, primitive=group)
-
- # Transformations
- self._translate = transform.Translate()
- self._rotateForwardTranslation = transform.Translate()
- self._rotate = transform.Rotate()
- self._rotateBackwardTranslation = transform.Translate()
- self._translateFromRotationCenter = transform.Translate()
- self._matrix = transform.Matrix()
- self._scale = transform.Scale()
- # Group transforms to do to data before rotation
- # This is useful to handle rotation center relative to bbox
- self._transformObjectToRotate = transform.TransformList(
- [self._matrix, self._scale])
- self._transformObjectToRotate.addListener(self._updateRotationCenter)
-
- self._rotationCenter = 0., 0., 0.
-
- self.__transforms = transform.TransformList([
- self._translate,
- self._rotateForwardTranslation,
- self._rotate,
- self._rotateBackwardTranslation,
- self._transformObjectToRotate])
-
- self._getScenePrimitive().transforms = self.__transforms
-
- def _updated(self, event=None):
- """Handle MixIn class updates.
-
- :param event: The event to send to :attr:`sigItemChanged` signal.
- """
- if event == ItemChangedType.DATA:
- self._updateRotationCenter()
- super(DataItem3D, self)._updated(event)
-
- # Transformations
-
- def _getSceneTransforms(self):
- """Return TransformList corresponding to current transforms
-
- :rtype: TransformList
- """
- return self.__transforms
-
- def setScale(self, sx=1., sy=1., sz=1.):
- """Set the scale of the item in the scene.
-
- :param float sx: Scale factor along the X axis
- :param float sy: Scale factor along the Y axis
- :param float sz: Scale factor along the Z axis
- """
- scale = numpy.array((sx, sy, sz), dtype=numpy.float32)
- if not numpy.all(numpy.equal(scale, self.getScale())):
- self._scale.scale = scale
- self._updated(Item3DChangedType.TRANSFORM)
-
- def getScale(self):
- """Returns the scales provided by :meth:`setScale`.
-
- :rtype: numpy.ndarray
- """
- return self._scale.scale
-
- def setTranslation(self, x=0., y=0., z=0.):
- """Set the translation of the origin of the item in the scene.
-
- :param float x: Offset of the data origin on the X axis
- :param float y: Offset of the data origin on the Y axis
- :param float z: Offset of the data origin on the Z axis
- """
- translation = numpy.array((x, y, z), dtype=numpy.float32)
- if not numpy.all(numpy.equal(translation, self.getTranslation())):
- self._translate.translation = translation
- self._updated(Item3DChangedType.TRANSFORM)
-
- def getTranslation(self):
- """Returns the offset set by :meth:`setTranslation`.
-
- :rtype: numpy.ndarray
- """
- return self._translate.translation
-
- _ROTATION_CENTER_TAGS = 'lower', 'center', 'upper'
-
- def _updateRotationCenter(self, *args, **kwargs):
- """Update rotation center relative to bounding box"""
- center = []
- for index, position in enumerate(self.getRotationCenter()):
- # Patch position relative to bounding box
- if position in self._ROTATION_CENTER_TAGS:
- bounds = self._getScenePrimitive().bounds(
- transformed=False, dataBounds=True)
- bounds = self._transformObjectToRotate.transformBounds(bounds)
-
- if bounds is None:
- position = 0.
- elif position == 'lower':
- position = bounds[0, index]
- elif position == 'center':
- position = 0.5 * (bounds[0, index] + bounds[1, index])
- elif position == 'upper':
- position = bounds[1, index]
-
- center.append(position)
-
- if not numpy.all(numpy.equal(
- center, self._rotateForwardTranslation.translation)):
- self._rotateForwardTranslation.translation = center
- self._rotateBackwardTranslation.translation = \
- - self._rotateForwardTranslation.translation
- self._updated(Item3DChangedType.TRANSFORM)
-
- def setRotationCenter(self, x=0., y=0., z=0.):
- """Set the center of rotation of the item.
-
- Position of the rotation center is either a float
- for an absolute position or one of the following
- string to define a position relative to the item's bounding box:
- 'lower', 'center', 'upper'
-
- :param x: rotation center position on the X axis
- :rtype: float or str
- :param y: rotation center position on the Y axis
- :rtype: float or str
- :param z: rotation center position on the Z axis
- :rtype: float or str
- """
- center = []
- for position in (x, y, z):
- if isinstance(position, six.string_types):
- assert position in self._ROTATION_CENTER_TAGS
- else:
- position = float(position)
- center.append(position)
- center = tuple(center)
-
- if center != self._rotationCenter:
- self._rotationCenter = center
- self._updateRotationCenter()
-
- def getRotationCenter(self):
- """Returns the rotation center set by :meth:`setRotationCenter`.
-
- :rtype: 3-tuple of float or str
- """
- return self._rotationCenter
-
- def setRotation(self, angle=0., axis=(0., 0., 1.)):
- """Set the rotation of the item in the scene
-
- :param float angle: The rotation angle in degrees.
- :param axis: The (x, y, z) coordinates of the rotation axis.
- """
- axis = numpy.array(axis, dtype=numpy.float32)
- assert axis.ndim == 1
- assert axis.size == 3
- if (self._rotate.angle != angle or
- not numpy.all(numpy.equal(axis, self._rotate.axis))):
- self._rotate.setAngleAxis(angle, axis)
- self._updated(Item3DChangedType.TRANSFORM)
-
- def getRotation(self):
- """Returns the rotation set by :meth:`setRotation`.
-
- :return: (angle, axis)
- :rtype: 2-tuple (float, numpy.ndarray)
- """
- return self._rotate.angle, self._rotate.axis
-
- def setMatrix(self, matrix=None):
- """Set the transform matrix
-
- :param numpy.ndarray matrix: 3x3 transform matrix
- """
- matrix4x4 = numpy.identity(4, dtype=numpy.float32)
-
- if matrix is not None:
- matrix = numpy.array(matrix, dtype=numpy.float32)
- assert matrix.shape == (3, 3)
- matrix4x4[:3, :3] = matrix
-
- if not numpy.all(numpy.equal(matrix4x4, self._matrix.getMatrix())):
- self._matrix.setMatrix(matrix4x4)
- self._updated(Item3DChangedType.TRANSFORM)
-
- def getMatrix(self):
- """Returns the matrix set by :meth:`setMatrix`
-
- :return: 3x3 matrix
- :rtype: numpy.ndarray"""
- return self._matrix.getMatrix(copy=True)[:3, :3]
-
- # Bounding box
-
- def _setForegroundColor(self, color):
- """Set the color of the bounding box
-
- :param color: RGBA color as 4 floats in [0, 1]
- """
- self._getScenePrimitive().color = color
- super(DataItem3D, self)._setForegroundColor(color)
-
- def isBoundingBoxVisible(self):
- """Returns item's bounding box visibility.
-
- :rtype: bool
- """
- return self._getScenePrimitive().boxVisible
-
- def setBoundingBoxVisible(self, visible):
- """Set item's bounding box visibility.
-
- :param bool visible:
- True to show the bounding box, False (default) to hide it
- """
- visible = bool(visible)
- primitive = self._getScenePrimitive()
- if visible != primitive.boxVisible:
- primitive.boxVisible = visible
- self._updated(Item3DChangedType.BOUNDING_BOX_VISIBLE)
-
-
-class BaseNodeItem(DataItem3D):
- """Base class for data item having children (e.g., group, 3d volume)."""
-
- def __init__(self, parent=None, group=None):
- """Base class representing a group of items in the scene.
-
- :param parent: The View widget this item belongs to.
- :param Union[GroupBBox, None] group:
- The scene group to use for rendering
- """
- DataItem3D.__init__(self, parent=parent, group=group)
-
- def getItems(self):
- """Returns the list of items currently present in the group.
-
- :rtype: tuple
- """
- raise NotImplementedError('getItems must be implemented in subclass')
-
- def visit(self, included=True):
- """Generator visiting the group content.
-
- It traverses the group sub-tree in a top-down left-to-right way.
-
- :param bool included: True (default) to include self in visit
- """
- if included:
- yield self
- for child in self.getItems():
- yield child
- if hasattr(child, 'visit'):
- for item in child.visit(included=False):
- yield item
-
- def pickItems(self, x, y, condition=None):
- """Iterator over picked items in the group at given position.
-
- Each picked item yield a :class:`PickingResult` object
- holding the picking information.
-
- It traverses the group sub-tree in a left-to-right top-down way.
-
- :param int x: X widget device pixel coordinate
- :param int y: Y widget device pixel coordinate
- :param callable condition: Optional test called for each item
- checking whether to process it or not.
- """
- viewport = self._getScenePrimitive().viewport
- if viewport is None:
- raise RuntimeError(
- 'Cannot perform picking: Item not attached to a widget')
-
- context = PickContext(x, y, viewport, condition)
- for result in self._pickItems(context):
- yield result
-
- def _pickItems(self, context):
- """Implement :meth:`pickItems`
-
- :param PickContext context: Current picking context
- """
- if not self.isVisible() or not context.isEnabled():
- return # empty iterator
-
- # Use a copy to discard context changes once this returns
- context = context.copy()
-
- if not self._pickFastCheck(context):
- return # empty iterator
-
- result = self._pick(context)
- if result is not None:
- yield result
-
- for child in self.getItems():
- if isinstance(child, BaseNodeItem):
- for result in child._pickItems(context):
- yield result # Flatten result
-
- else:
- result = child._pick(context)
- if result is not None:
- yield result
-
-
-class _BaseGroupItem(BaseNodeItem):
- """Base class for group of items sharing a common transform."""
-
- sigItemAdded = qt.Signal(object)
- """Signal emitted when a new item is added to the group.
-
- The newly added item is provided by this signal
- """
-
- sigItemRemoved = qt.Signal(object)
- """Signal emitted when an item is removed from the group.
-
- The removed item is provided by this signal.
- """
-
- def __init__(self, parent=None, group=None):
- """Base class representing a group of items in the scene.
-
- :param parent: The View widget this item belongs to.
- :param Union[GroupBBox, None] group:
- The scene group to use for rendering
- """
- BaseNodeItem.__init__(self, parent=parent, group=group)
- self._items = []
-
- def _getGroupPrimitive(self):
- """Returns the group for which to handle children.
-
- This allows this group to be different from the primitive.
- """
- return self._getScenePrimitive()
-
- def addItem(self, item, index=None):
- """Add an item to the group
-
- :param Item3D item: The item to add
- :param int index: The index at which to place the item.
- By default it is appended to the end of the list.
- :raise ValueError: If the item is already in the group.
- """
- assert isinstance(item, Item3D)
- assert item.parent() in (None, self)
-
- if item in self.getItems():
- raise ValueError("Item3D already in group: %s" % item)
-
- item.setParent(self)
- if index is None:
- self._getGroupPrimitive().children.append(
- item._getScenePrimitive())
- self._items.append(item)
- else:
- self._getGroupPrimitive().children.insert(
- index, item._getScenePrimitive())
- self._items.insert(index, item)
- self.sigItemAdded.emit(item)
-
- def getItems(self):
- """Returns the list of items currently present in the group.
-
- :rtype: tuple
- """
- return tuple(self._items)
-
- def removeItem(self, item):
- """Remove an item from the scene.
-
- :param Item3D item: The item to remove from the scene
- :raises ValueError: If the item does not belong to the group
- """
- if item not in self.getItems():
- raise ValueError("Item3D not in group: %s" % str(item))
-
- self._getGroupPrimitive().children.remove(item._getScenePrimitive())
- self._items.remove(item)
- item.setParent(None)
- self.sigItemRemoved.emit(item)
-
- def clearItems(self):
- """Remove all item from the group."""
- for item in self.getItems():
- self.removeItem(item)
-
-
-class GroupItem(_BaseGroupItem):
- """Group of items sharing a common transform."""
-
- def __init__(self, parent=None):
- super(GroupItem, self).__init__(parent=parent)
-
-
-class GroupWithAxesItem(_BaseGroupItem):
- """
- Group of items sharing a common transform surrounded with labelled axes.
- """
-
- def __init__(self, parent=None):
- """Class representing a group of items in the scene with labelled axes.
-
- :param parent: The View widget this item belongs to.
- """
- super(GroupWithAxesItem, self).__init__(parent=parent,
- group=axes.LabelledAxes())
-
- # Axes labels
-
- def setAxesLabels(self, xlabel=None, ylabel=None, zlabel=None):
- """Set the text labels of the axes.
-
- :param str xlabel: Label of the X axis, None to leave unchanged.
- :param str ylabel: Label of the Y axis, None to leave unchanged.
- :param str zlabel: Label of the Z axis, None to leave unchanged.
- """
- labelledAxes = self._getScenePrimitive()
- if xlabel is not None:
- labelledAxes.xlabel = xlabel
-
- if ylabel is not None:
- labelledAxes.ylabel = ylabel
-
- if zlabel is not None:
- labelledAxes.zlabel = zlabel
-
- class _Labels(tuple):
- """Return type of :meth:`getAxesLabels`"""
-
- def getXLabel(self):
- """Label of the X axis (str)"""
- return self[0]
-
- def getYLabel(self):
- """Label of the Y axis (str)"""
- return self[1]
-
- def getZLabel(self):
- """Label of the Z axis (str)"""
- return self[2]
-
- def getAxesLabels(self):
- """Returns the text labels of the axes
-
- >>> group = GroupWithAxesItem()
- >>> group.setAxesLabels(xlabel='X')
-
- You can get the labels either as a 3-tuple:
-
- >>> xlabel, ylabel, zlabel = group.getAxesLabels()
-
- Or as an object with methods getXLabel, getYLabel and getZLabel:
-
- >>> labels = group.getAxesLabels()
- >>> labels.getXLabel()
- ... 'X'
-
- :return: object describing the labels
- """
- labelledAxes = self._getScenePrimitive()
- return self._Labels((labelledAxes.xlabel,
- labelledAxes.ylabel,
- labelledAxes.zlabel))
-
-
-class RootGroupWithAxesItem(GroupWithAxesItem):
- """Special group with axes item for root of the scene.
-
- Uses 2 groups so that axes take transforms into account.
- """
-
- def __init__(self, parent=None):
- super(RootGroupWithAxesItem, self).__init__(parent)
- self.__group = scene.Group()
- self.__group.transforms = self._getSceneTransforms()
-
- groupWithAxes = self._getScenePrimitive()
- groupWithAxes.transforms = [] # Do not apply transforms here
- groupWithAxes.children.append(self.__group)
-
- def _getGroupPrimitive(self):
- """Returns the group for which to handle children.
-
- This allows this group to be different from the primitive.
- """
- return self.__group