Source code for magicclass.ext.vispy.widgets3d

from __future__ import annotations
import numpy as np
from numpy.typing import ArrayLike
from vispy import scene
from . import layer3d
from .layerlist import LayerList
from ._base import SceneCanvas, HasViewBox, MultiPlot, LayerItem
from .camera import Camera

from ...widgets import FreeWidget
from ...types import Color


[docs]class Has3DViewBox(HasViewBox): """ A Vispy canvas for 3-D object visualization. Very similar to napari. This widget can be used independent of napari, or as a mini-viewer of napari. """ def __init__(self, viewbox: scene.ViewBox): super().__init__(viewbox) self._camera = Camera(viewbox) @property def layers(self): """Return the layer list.""" return self._layerlist @property def camera(self) -> Camera: """Return the native camera.""" return self._camera
[docs] def add_image( self, data: ArrayLike, *, contrast_limits: tuple[float, float] = None, rendering: str = "mip", iso_threshold: float | None = None, attenuation: float = 1.0, cmap: str = "grays", gamma: float = 1.0, interpolation: str = "linear", ) -> layer3d.Image: """ Add a 3D array as a volumic image. Parameters ---------- data : ArrayLike Image data. contrast_limits : tuple[float, float], optional Contrast limits of the image. rendering : str, optional Rendering method. iso_threshold : float, optional Threshold of iso-surface rendering. attenuation : float, optional Attenuation of attenuated rendering method. cmap : str, optional Colormap of image. gamma : float, optional Gamma value of contrast. interpolation : str, optional Interpolation method. Returns ------- Image A new Image layer. """ data = np.asarray(data) if data.dtype.kind == "f": data = data.astype(np.float32) image = layer3d.Image( data, self._viewbox, contrast_limits=contrast_limits, rendering=rendering, iso_threshold=iso_threshold, attenuation=attenuation, cmap=cmap, gamma=gamma, interpolation=interpolation, ) return self.add_layer(image)
[docs] def add_isosurface( self, data: ArrayLike, *, contrast_limits: tuple[float, float] | None = None, iso_threshold: float | None = None, face_color: Color | None = None, edge_color: Color | None = None, shading: str = "smooth", ) -> layer3d.IsoSurface: """ Add a 3D array as a iso-surface. The difference between this method and the iso-surface rendering of ``add_image`` is that the layer created by this method can be a mesh. Parameters ---------- data : ArrayLike Image data. contrast_limits : tuple[float, float], optional Contrast limits of the image. iso_threshold : float, optional Threshold of iso-surface. face_color : Color, optional Face color of the surface. edge_color : Color, optional Edge color of the surface. shading : str, optional Shading mode of the surface. Returns ------- Isosurface A new Isosurface layer. """ surface = layer3d.IsoSurface( data, self._viewbox, contrast_limits=contrast_limits, iso_threshold=iso_threshold, edge_color=edge_color, face_color=face_color, shading=shading, ) return self.add_layer(surface)
[docs] def add_surface( self, data: tuple[ArrayLike, ArrayLike] | tuple[ArrayLike, ArrayLike, ArrayLike], *, face_color: Color | None = None, edge_color: Color | None = None, shading: str = "smooth", ) -> layer3d.Surface: """ Add vertices, faces and optional values as a surface. Parameters ---------- data : two or three arrays Data that defines a surface. face_color : Color | None, optional Face color of the surface. edge_color : Color | None, optional Edge color of the surface. shading : str, optional Shading mode of the surface. Returns ------- Surface A new Surface layer. """ surface = layer3d.Surface( data, self._viewbox, face_color=face_color, edge_color=edge_color, shading=shading, ) return self.add_layer(surface)
[docs] def add_curve( self, data: ArrayLike, color: Color = "white", width: float = 1, blending: str = "translucent", ) -> layer3d.Curve3D: """ Add a (N, 3) array as a curve. Parameters ---------- data : ArrayLike Coordinates of the curve. color : Color, default is "white" Color of the curve. width : float, default is 1. Width of the curve line. blending : str, default is "translucent" Blending mode of the layer. Returns ------- _type_ _description_ """ curve = layer3d.Curve3D( data=np.asarray(data, dtype=np.float32), viewbox=self._viewbox, color=color, width=width, blending=blending, ) return self.add_layer(curve)
[docs] def add_points( self, data: ArrayLike, face_color: Color = "white", edge_color: Color = "white", edge_width: float = 0.0, size: float = 5.0, blending: str = "translucent", spherical: bool = True, ) -> layer3d.Points3D: """ Add a (N, 3) array as a point cloud. Parameters ---------- data : ArrayLike Z, Y, X coordinates of the points. face_color : Color, optional Face color of the points. edge_color : Color, optional Edge color of the points. edge_width : float, default is 0.0 Edge width of the points. size : float, default is 1.0 Size of the points. blending : str, default is "translucent" Blending mode of the layer. spherical : bool, default is True Whether the points are rendered as spherical objects. Returns ------- Points3D A new Points3D layer. """ points = layer3d.Points3D( data=np.asarray(data, dtype=np.float32), viewbox=self._viewbox, face_color=face_color, edge_color=edge_color, edge_width=edge_width, size=size, blending=blending, spherical=spherical, ) return self.add_layer(points)
[docs] def add_arrows( self, data: ArrayLike, arrow_type: str = "stealth", arrow_size: float = 5.0, color: Color ="white", width: float = 1.0, blending: str = "translucent", ) -> layer3d.Arrows3D: """ Add a (N, P, 3) array as a set of arrows. ``P`` is the number of points in each arrow. If you want to draw simple arrows with lines, the shape of the input array will be (N, 2, 3) and ``data[:, 0]`` is the start points and ``data[:, 1]`` is the end points. Parameters ---------- data : ArrayLike Arrow coordinates. arrow_type : str, default is "stealth" Shape of the arrow. arrow_size : float, default is 5.0 Size of the arrows. color : str, default is "white" Color of the arrow and the bodies. width : float, default is 1.0 Width of the arrow bodies. blending : str, default is "translucent" Blending mode of the layer. Returns ------- Arrow3D A new Arrow3D layer. """ arrows = layer3d.Arrows3D( data=np.asarray(data, dtype=np.float32), viewbox=self._viewbox, arrow_type=arrow_type, arrow_size=arrow_size, color=color, width=width, blending=blending, ) return self.add_layer(arrows)
[docs] def add_layer(self, layer: LayerItem): """Add a layer item to the canvas.""" self.layers.append(layer) if len(self.layers) == 1: low, high = layer._get_bbox() self.camera.scale = max(high - low) self.camera.center = (high + low) / 2 self.camera.angles = (0.0, 0.0, 90.0) self._viewbox.update() return layer
[docs]class Vispy3DCanvas(FreeWidget, Has3DViewBox): """A Vispy based 3-D canvas.""" def __init__(self): super().__init__() self._scene = SceneCanvas() grid = self._scene.central_widget.add_grid() _viewbox = grid.add_view() Has3DViewBox.__init__(self, _viewbox) self._layerlist = LayerList() self._scene.create_native() self.set_widget(self._scene.native)
[docs]class VispyMulti3DCanvas(MultiPlot): """A multiple Vispy based 3-D canvas.""" _base_class = Has3DViewBox
# BUG: the second canvas has wrong offset. Need updates in event object?