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 .layer3d import Image, IsoSurface, Surface, Curve3D
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", ): image = 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", ): surface = 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", ): surface = 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="white", width=1, ): curve = Curve3D( data=np.asarray(data, dtype=np.float32), viewbox=self._viewbox, color=color, width=width, ) return self.add_layer(curve)
[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?