Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/python-package-min.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
python-version: ["3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v2
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
python-version: ["3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v2
Expand Down
1 change: 1 addition & 0 deletions doc/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ kaleido
ipywidgets<8,>=7.6.0
sphinxcontrib-video
anywidget # for plotly v6+
scikit-image # for 3D implicit plotting
7 changes: 7 additions & 0 deletions doc/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
Changelog
==========

v4.2.0
======

Implemented 3D volumetric implicit plotting with K3D-Jupyter, Plotly
and Matplotlib.


v4.1.2
======

Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ psutil
av
tqdm
kaleido
scikit-image
# mayavi>=4.8.0
# PyQt5>=5.15.7
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def readme():
"vtk", # needed for streamlines in k3d
"control>=0.10.0",
"imageio", "imageio-ffmpeg", "psutil", "av", "tqdm", # to save animations
"scikit-image", # for 3D implicit plotting
# mayavi-related
# "mayavi>=4.8.0",
# "PyQt5>=5.15.7",
Expand Down
2 changes: 1 addition & 1 deletion spb/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "4.1.2"
__version__ = "4.2.0"
4 changes: 3 additions & 1 deletion spb/backends/k3d/k3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
from spb.backends.k3d.renderers import (
Line3DRenderer, Vector3DRenderer,
ComplexRenderer, SurfaceRenderer, Implicit3DRenderer,
Implicit3DVoxelRenderer,
GeometryRenderer, Arrow3DRenderer
)
from spb.series import (
Parametric3DLineSeries, ComplexParametric3DLineSeries,
List3DSeries, Vector3DSeries, SliceVector3DSeries,
RiemannSphereSeries, Implicit3DSeries,
RiemannSphereSeries, Implicit3DSeries, Implicit3DVoxelSeries,
ComplexDomainColoringSeries, ComplexSurfaceSeries,
SurfaceOver2DRangeSeries, ParametricSurfaceSeries,
PlaneSeries, Geometry3DSeries, Arrow3DSeries
Expand Down Expand Up @@ -65,6 +66,7 @@ class K3DBackend(Plot):
Vector3DSeries: Vector3DRenderer,
SliceVector3DSeries: Vector3DRenderer,
Implicit3DSeries: Implicit3DRenderer,
Implicit3DVoxelSeries: Implicit3DVoxelRenderer,
ComplexDomainColoringSeries: ComplexRenderer,
ComplexSurfaceSeries: ComplexRenderer,
RiemannSphereSeries: ComplexRenderer,
Expand Down
3 changes: 2 additions & 1 deletion spb/backends/k3d/renderers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from spb.backends.k3d.renderers.line3d import Line3DRenderer
from spb.backends.k3d.renderers.surface import SurfaceRenderer
from spb.backends.k3d.renderers.implicit3d import Implicit3DRenderer
from spb.backends.k3d.renderers.implicit3d import Implicit3DRenderer, Implicit3DVoxelRenderer
from spb.backends.k3d.renderers.vector3d import Vector3DRenderer
from spb.backends.k3d.renderers.complex import ComplexRenderer
from spb.backends.k3d.renderers.geometry import GeometryRenderer
from spb.backends.k3d.renderers.arrow3d import Arrow3DRenderer

__all__ = [
"Line3DRenderer", "SurfaceRenderer", "Implicit3DRenderer",
"Implicit3DVoxelRenderer",
"Vector3DRenderer", "ComplexRenderer", "GeometryRenderer",
"Arrow3DRenderer"
]
40 changes: 40 additions & 0 deletions spb/backends/k3d/renderers/implicit3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,43 @@ class Implicit3DRenderer(Renderer):
draw_update_map = {
_draw_implicit3d_helper: _update_implicit3d_helper
}


def _draw_implicit3dvoxel_helper(renderer, data):
p, s = renderer.plot, renderer.series
np = p.np

# NOTE: here I'm going to use voxels. K3D also exposes the volume function:
# https://k3d-jupyter.org/reference/factory.volume.html
# however, at the time of writing this, it doesn't scale the data to the
# appropriate boundaries, hence I'll only use voxels.

mesh_x, mesh_y, mesh_z, f = data
xmin, xmax = s.start_x, s.end_x
ymin, ymax = s.start_y, s.end_y
zmin, zmax = s.start_z, s.end_z
color = p._convert_to_int(next(p._cl))

a = dict(
bounds=[xmin, xmax, ymin, ymax, zmin, zmax],
color_map=[color],
outlines=False
)
kw = p.merge({}, a, s.rendering_kw)
plt_vox = p.k3d.voxels(
f.transpose(2,1,0).astype(np.uint8),
**kw
)
p._fig += plt_vox

return plt_vox


def _update_implicit3dvoxel_helper(renderer, data, handle):
raise NotImplementedError


class Implicit3DVoxelRenderer(Renderer):
draw_update_map = {
_draw_implicit3dvoxel_helper: _update_implicit3dvoxel_helper
}
8 changes: 6 additions & 2 deletions spb/backends/matplotlib/matplotlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
NyquistRenderer, Arrow2DRendererFancyArrowPatch,
Arrow3DRendererFancyArrowPatch, RootLocusRenderer, SGridLineRenderer,
ZGridLineRenderer, NGridLineRenderer, MCirclesRenderer, PoleZeroRenderer,
NicholsLineRenderer
NicholsLineRenderer, Implicit3DRenderer, Implicit3DVoxelRenderer
)
from spb.series import (
LineOver1DRangeSeries, List2DSeries, Parametric2DLineSeries,
Expand All @@ -25,7 +25,8 @@
Arrow2DSeries, Arrow3DSeries, RootLocusSeries, SGridLineSeries,
ZGridLineSeries, SystemResponseSeries, ColoredSystemResponseSeries,
PoleZeroSeries, NGridLineSeries, PoleZeroWithSympySeries,
MCirclesSeries, HLineSeries, VLineSeries
MCirclesSeries, HLineSeries, VLineSeries, Implicit3DSeries,
Implicit3DVoxelSeries
)
from sympy.external import import_module
from packaging import version
Expand Down Expand Up @@ -97,6 +98,8 @@ class MatplotlibBackend(Plot):
MCirclesSeries: MCirclesRenderer,
PoleZeroSeries: PoleZeroRenderer,
PoleZeroWithSympySeries: PoleZeroRenderer,
Implicit3DSeries: Implicit3DRenderer,
Implicit3DVoxelSeries: Implicit3DVoxelRenderer
}

def __init__(self, *args, **kwargs):
Expand Down Expand Up @@ -125,6 +128,7 @@ def __init__(self, *args, **kwargs):
self.Line3DCollection = self.mpl_toolkits.mplot3d.art3d.Line3DCollection
self.Path3DCollection = self.mpl_toolkits.mplot3d.art3d.Path3DCollection
self.Axes3D = self.mpl_toolkits.mplot3d.Axes3D
self.Poly3DCollection = self.mpl_toolkits.mplot3d.art3d.Poly3DCollection

kwargs["_library"] = "matplotlib"
kwargs.setdefault("colormaps", [
Expand Down
4 changes: 3 additions & 1 deletion spb/backends/matplotlib/renderers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from spb.backends.matplotlib.renderers.mcircles import MCirclesRenderer
from spb.backends.matplotlib.renderers.polezero import PoleZeroRenderer
from spb.backends.matplotlib.renderers.nichols import NicholsLineRenderer
from spb.backends.matplotlib.renderers.implicit3d import Implicit3DRenderer, Implicit3DVoxelRenderer

__all__ = [
"Line2DRenderer", "Line3DRenderer", "ContourRenderer", "SurfaceRenderer",
Expand All @@ -30,5 +31,6 @@
"Arrow2DRendererQuivers", "Arrow2DRendererFancyArrowPatch",
"Arrow3DRendererFancyArrowPatch", "RootLocusRenderer", "SGridLineRenderer",
"ZGridLineRenderer", "NGridLineRenderer", "MCirclesRenderer",
"PoleZeroRenderer", "NicholsLineRenderer"
"PoleZeroRenderer", "NicholsLineRenderer",
"Implicit3DRenderer", "Implicit3DVoxelRenderer"
]
87 changes: 87 additions & 0 deletions spb/backends/matplotlib/renderers/implicit3d.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from spb.backends.matplotlib.renderers.renderer import MatplotlibRenderer
from spb.backends.utils import compute_streamtubes
from sympy.external import import_module


def _draw_implicit3d_isosurface_helper(renderer, data):
skimage = import_module("skimage", import_kwargs={'fromlist': ['measure']})
if skimage is None:
raise ModuleNotFoundError(
"`skimage` is a mandatory dependency for 3D implicit plots,"
" which is currently missing.")

p, s = renderer.plot, renderer.series
mesh_x, mesh_y, mesh_z, f = data
verts, faces, normals, values = skimage.measure.marching_cubes(f, 0)

x = mesh_x[:, 0, 0]
y = mesh_y[0, :, 0]
z = mesh_z[0, 0, :]
dx = abs(x[0] - x[1])
dy = abs(y[0] - y[1])
dz = abs(z[0] - z[1])
verts[:, 0] = x[0] + verts[:, 0] * dx
verts[:, 1] = y[0] + verts[:, 1] * dy
verts[:, 2] = z[0] + verts[:, 2] * dz

mkw = dict(linewidths=0.25, edgecolor="k", facecolors=next(p._cl))
kw = p.merge({}, mkw, s.rendering_kw)

# Fancy indexing: `verts[faces]` to generate a collection of triangles
mesh = p.Poly3DCollection(verts[faces], **kw)
p.ax.add_collection3d(mesh)

return [mesh]


def _update_implicit3d_isosurface_helper(renderer, data, handle):
raise NotImplementedError


class Implicit3DRenderer(MatplotlibRenderer):
_sal = True
draw_update_map = {
_draw_implicit3d_isosurface_helper: _update_implicit3d_isosurface_helper
}


def _draw_implicit3d_volume_helper(renderer, data):
skimage = import_module("skimage", import_kwargs={'fromlist': ['measure']})
if skimage is None:
raise ModuleNotFoundError(
"`skimage` is a mandatory dependency for 3D implicit plots,"
" which is currently missing.")

p, s = renderer.plot, renderer.series
mesh_x, mesh_y, mesh_z, f = data
x_edges = mesh_x[:, 0, 0]
y_edges = mesh_y[0, :, 0]
z_edges = mesh_z[0, 0, :]
dx = abs(x_edges[1] - x_edges[0])
dy = abs(y_edges[1] - y_edges[0])
dz = abs(z_edges[1] - z_edges[0])

x_min, x_max = float(s.range_x[1]), float(s.range_x[2])
y_min, y_max = float(s.range_y[1]), float(s.range_y[2])
z_min, z_max = float(s.range_z[1]), float(s.range_z[2])
x_edges = p.np.linspace(x_min - dx / 2, x_max + dx / 2, s.n1)
y_edges = p.np.linspace(y_min - dy / 2, y_max + dy / 2, s.n2)
z_edges = p.np.linspace(z_min - dz / 2, z_max + dz / 2, s.n3)
x, y, z = p.np.meshgrid(x_edges, y_edges, z_edges, indexing="ij")

mkw = dict(linewidths=0.25, edgecolor="k", facecolors=next(p._cl))
kw = p.merge({}, mkw, s.rendering_kw)
mesh = p.ax.voxels(x, y, z, f, **kw)

return [mesh]


def _update_implicit3d_volume_helper(renderer, data, handle):
raise NotImplementedError


class Implicit3DVoxelRenderer(MatplotlibRenderer):
_sal = True
draw_update_map = {
_draw_implicit3d_volume_helper: _update_implicit3d_volume_helper
}
6 changes: 4 additions & 2 deletions spb/backends/plotly/plotly.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
from spb.backends.plotly.renderers import (
Line2DRenderer, Line3DRenderer, Vector2DRenderer, Vector3DRenderer,
ComplexRenderer, ContourRenderer, SurfaceRenderer, Implicit3DRenderer,
GeometryRenderer, GenericRenderer, HVLineRenderer, Arrow2DRenderer
GeometryRenderer, GenericRenderer, HVLineRenderer, Arrow2DRenderer,
Implicit3DVoxelRenderer
)
from spb.series import (
LineOver1DRangeSeries, List2DSeries, Parametric2DLineSeries,
ColoredLineOver1DRangeSeries, AbsArgLineSeries, ComplexPointSeries,
Parametric3DLineSeries, ComplexParametric3DLineSeries,
List3DSeries, Vector2DSeries, Vector3DSeries, SliceVector3DSeries,
RiemannSphereSeries, Implicit3DSeries,
RiemannSphereSeries, Implicit3DSeries, Implicit3DVoxelSeries,
ComplexDomainColoringSeries, ComplexSurfaceSeries,
ContourSeries, SurfaceOver2DRangeSeries, ParametricSurfaceSeries,
PlaneSeries, Geometry2DSeries, Geometry3DSeries, GenericDataSeries,
Expand Down Expand Up @@ -70,6 +71,7 @@ class PlotlyBackend(Plot):
Vector3DSeries: Vector3DRenderer,
SliceVector3DSeries: Vector3DRenderer,
Implicit3DSeries: Implicit3DRenderer,
Implicit3DVoxelSeries: Implicit3DVoxelRenderer,
ComplexDomainColoringSeries: ComplexRenderer,
ComplexSurfaceSeries: ComplexRenderer,
RiemannSphereSeries: ComplexRenderer,
Expand Down
8 changes: 4 additions & 4 deletions spb/backends/plotly/renderers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from spb.backends.plotly.renderers.contour import ContourRenderer
from spb.backends.plotly.renderers.surface import SurfaceRenderer
from spb.backends.plotly.renderers.geometry import GeometryRenderer
from spb.backends.plotly.renderers.implicit3d import Implicit3DRenderer
from spb.backends.plotly.renderers.implicit3d import Implicit3DRenderer, Implicit3DVoxelRenderer
from spb.backends.plotly.renderers.vector2d import Vector2DRenderer
from spb.backends.plotly.renderers.vector3d import Vector3DRenderer
from spb.backends.plotly.renderers.complex import ComplexRenderer
Expand All @@ -13,7 +13,7 @@

__all__ = [
"Line2DRenderer", "Line3DRenderer", "ContourRenderer", "SurfaceRenderer",
"GeometryRenderer", "Implicit3DRenderer", "Vector2DRenderer",
"Vector3DRenderer", "ComplexRenderer", "GenericRenderer", "HVLineRenderer",
"Arrow2DRenderer"
"GeometryRenderer", "Implicit3DRenderer", "Implicit3DVoxelRenderer",
"Vector2DRenderer", "Vector3DRenderer", "ComplexRenderer",
"GenericRenderer", "HVLineRenderer", "Arrow2DRenderer"
]
Loading
Loading