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
from .layerlist import LayerList
from ._base import SceneCanvas, HasViewBox, MultiPlot

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._viewbox.camera = scene.ArcballCamera(fov=0) @property def layers(self): """Return the layer list.""" return self._layerlist @property def camera(self): """Return the native camera.""" return self._viewbox.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, ) self._layerlist.append(image) self._viewbox.camera.scale_factor = max(data.shape) self._viewbox.camera.center = [s / 2 - 0.5 for s in data.shape] return 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, ) self._layerlist.append(surface) self._viewbox.camera.scale_factor = max(data.shape) self._viewbox.camera.center = [s / 2 - 0.5 for s in data.shape] return 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, ) self._layerlist.append(surface) mins = np.min(data[0], axis=0) maxs = np.max(data[0], axis=0) self._viewbox.camera.scale_factor = max(maxs - mins) self._viewbox.camera.center = [(s1 + s0) / 2 for s0, s1 in zip(mins, maxs)] return surface
[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?