Skip to content
2 changes: 2 additions & 0 deletions examples/mesh/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Mesh Examples
=============
5 changes: 2 additions & 3 deletions examples/mesh/image_surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,18 @@

import imageio.v3 as iio
import fastplotlib as fpl
import numpy as np
import scipy.ndimage

im = iio.imread("imageio:astronaut.png")

figure = fpl.Figure(size=(700, 560), cameras='3d', controller_types='orbit')
figure = fpl.Figure(size=(700, 560), cameras="3d", controller_types="orbit")


# Create the height map from the image
z = im.mean(axis=2)
z = scipy.ndimage.gaussian_filter(z, 5) # 2nd arg is sigma

mesh = figure[0, 0].add_surface(z, colors="magenta", cmap=im)
mesh = figure[0, 0].add_surface(z, cmap=im)
mesh.world_object.local.scale_y = -1


Expand Down
7 changes: 3 additions & 4 deletions examples/mesh/mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,20 @@
# sphinx_gallery_pygfx_docs = 'screenshot'

import fastplotlib as fpl
import numpy as np
import pygfx as gfx


figure = fpl.Figure(size=(700, 560), cameras='3d', controller_types='orbit')
figure = fpl.Figure(size=(700, 560), cameras="3d", controller_types="orbit")


# Load geometry using Pygfx's geometry util
geo = gfx.geometries.torus_knot_geometry()
positions = geo.positions.data
indices = geo.indices.data

mesh = figure[0, 0].add_mesh(positions, indices, colors="magenta")

mesh = fpl.MeshGraphic(positions, indices, colors="magenta")

figure[0, 0].add_graphic(mesh)
figure[0, 0].axes.grids.xy.visible = True
figure[0, 0].camera.show_object(mesh.world_object, (1, 1, -1), up=(0, 0, 1))

Expand Down
76 changes: 76 additions & 0 deletions examples/mesh/polygon_animation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""
Polygon animation
=================

Polygon animation example that changes the polygon data. Random points are generated by sampling from a
2D gaussian and a polygon is updated to visualize a convex hull for the sampled points.

"""

# test_example = false
# sphinx_gallery_pygfx_docs = 'animate 8s'

import numpy as np
from scipy.spatial import ConvexHull
import fastplotlib as fpl


def points_to_hull(points) -> np.ndarray:
hull = ConvexHull(points, qhull_options="Qs")
return points[hull.vertices]


figure = fpl.Figure(size=(700, 560))


cov = np.array([[1, 0], [0, 1]])

# sample points from a 2d gaussian
samples1 = np.random.multivariate_normal((0, 0), cov, size=20)
samples2 = np.random.multivariate_normal((5, 0), cov, size=50)

# add the convex hull as a polygon
polygon1 = figure[0, 0].add_polygon(
points_to_hull(samples1), colors="cyan", alpha=0.7, alpha_mode="blend"
)
# add the sampled points
scatter1 = figure[0, 0].add_scatter(
samples1, sizes=8, colors="blue", alpha=0.7, alpha_mode="blend"
)

# add the second gaussian and convex hull polygon
polygon2 = figure[0, 0].add_polygon(
points_to_hull(samples2), colors="magenta", alpha=0.7, alpha_mode="blend"
)
scatter2 = figure[0, 0].add_scatter(
samples2, sizes=8, colors="r", alpha=0.7, alpha_mode="blend"
)


def animate():
# set new scatter data
scatter1.data[:, :-1] += np.random.normal(0, 0.05, size=samples1.size).reshape(
samples1.shape
)
# set convex hull with new polygon vertices
polygon1.data = points_to_hull(scatter1.data[:, :-1])

# set the other scatter and polygon
scatter2.data[:, :-1] += np.random.normal(0, 0.05, size=samples2.size).reshape(
samples2.shape
)
polygon2.data = points_to_hull(scatter2.data[:, :-1])


figure.show()
figure[0, 0].camera.width = 10
figure[0, 0].camera.height = 10

figure.add_animations(animate)


# NOTE: fpl.loop.run() should not be used for interactive sessions
# See the "JupyterLab and IPython" section in the user guide
if __name__ == "__main__":
print(__doc__)
fpl.loop.run()
61 changes: 61 additions & 0 deletions examples/mesh/polygons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""
Polygons
========

An example with polygons.

"""

# test_example = True
# sphinx_gallery_pygfx_docs = 'screenshot'

import fastplotlib as fpl
import numpy as np
from cmap import Colormap

figure = fpl.Figure(size=(700, 560))


def make_circle(center, radius: float, n_points: int = 75) -> np.ndarray:
theta = np.linspace(0, 2 * np.pi, n_points, endpoint=False)
xs = radius * np.sin(theta)
ys = radius * np.cos(theta)

return np.column_stack([xs, ys]) + np.asarray(center)[None]


# define vertices for some polygons
circle_data = make_circle(center=(0, 0), radius=5)
octogon_data = make_circle(center=(15, 0), radius=7, n_points=8)
rectangle_data = np.array([[10, 10], [20, 10], [20, 15], [10, 15]])
triangle_data = np.array(
[
[-5, 8],
[5, 8],
[0, 15],
[-5, 8],
]
)

# add polygons
figure[0, 0].add_polygon(circle_data, name="circle")
figure[0, 0].add_polygon(
octogon_data,
colors=Colormap("jet").lut(8), # set vertex colors from jet cmap
name="octogon"
)
figure[0, 0].add_polygon(
rectangle_data,
colors=["r", "r", "cyan", "y"], # manually specify vertex colors
name="rectangle"
)
figure[0, 0].add_polygon(triangle_data, colors="m")

figure.show()


# NOTE: fpl.loop.run() should not be used for interactive sessions
# See the "JupyterLab and IPython" section in the user guide
if __name__ == "__main__":
print(__doc__)
fpl.loop.run()
95 changes: 95 additions & 0 deletions examples/mesh/surface_earth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""
Earth sphere animation
======================

Example showing how to create a sphere with an image of the Earth and rotate it around its 23.44° axis of rotation
with respect to the ecliptic (the xz plane in the visualization).

"""

# test_example = false
# sphinx_gallery_pygfx_docs = 'animate 8s'

import fastplotlib as fpl
import numpy as np
import imageio.v3 as iio
import pylinalg as la


figure = fpl.Figure(size=(700, 560), cameras="3d", controller_types="orbit")

# create a sphere from spherical coordinates
# see this for reference: https://mathworld.wolfram.com/SphericalCoordinates.html
# phi and theta are swapped in this example w.r.t. the wolfram alpha description
radius = 10
nx = 101
phi = np.linspace(0, np.pi * 2, num=nx, dtype=np.float32)
ny = 51
theta = np.linspace(0, np.pi, num=ny, dtype=np.float32)

phi_grid, theta_grid = np.meshgrid(phi, theta)

# convert to cartesian coordinates
theta_grid_sin = np.sin(theta_grid)
x = radius * np.cos(phi_grid) * theta_grid_sin * -1
y = radius * np.cos(theta_grid)
z = radius * np.sin(phi_grid) * theta_grid_sin

# get texture coords to map the image onto the mesh positions
u = phi_grid / (np.pi * 2)
v = 1 - (theta_grid / np.pi)
texcoords = np.dstack([u, v]).reshape(-1, 2)

# get an image of the earth from nasa
image = iio.imread(
"https://svs.gsfc.nasa.gov/vis/a000000/a003600/a003615/flat_earth_Largest_still.0330.jpg"
)
# images coordinate systems are typically inverted in y, so flip the image
image = np.ascontiguousarray(np.flipud(image))

# create a sphere
sphere = figure[0, 0].add_surface(
np.dstack([x, y, z]),
mode="phong",
colors="magenta",
cmap=image,
mapcoords=texcoords,
)

# display xz plane as a grid
figure[0, 0].axes.grids.xz.visible = True
figure.show()

# view from top right angle
figure[0, 0].camera.show_object(sphere.world_object, (-0.5, -0.25, -1), up=(0, 1, 0))
figure[0, 0].camera.zoom = 1.25

# create quaternion for 23.44 degrees axial tilt
axial_tilt = la.quat_from_euler((np.radians(23.44), 0), order="XY")

# a line to indicate the axial tilt
figure[0, 0].add_line(
np.array([[0, -20, 0], [0, 20, 0]]), rotation=axial_tilt, colors="magenta"
)

rot = 1


def rotate():
# rotate by 1 degree
global rot
rot += 1
rot_quat = la.quat_from_euler((0, np.radians(rot)), order="XY")

# apply rotation w.r.t. axial tilt
sphere.rotation = la.quat_mul(axial_tilt, rot_quat)


figure[0, 0].add_animations(rotate)


# NOTE: fpl.loop.run() should not be used for interactive sessions
# See the "JupyterLab and IPython" section in the user guide
if __name__ == "__main__":
print(__doc__)
fpl.loop.run()
55 changes: 55 additions & 0 deletions examples/mesh/surface_ellipsoid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""
Ellipsoid surface
=================

Simple example of a sphere surface mesh with a colormap indicating z values.

"""

# test_example = false
# sphinx_gallery_pygfx_docs = 'screenshot'

import fastplotlib as fpl
import numpy as np

figure = fpl.Figure(size=(700, 560), cameras="3d", controller_types="orbit")

# create an ellipsoid from spherical coordinates
# see this for reference: https://mathworld.wolfram.com/SphericalCoordinates.html
# phi and theta are swapped in this example w.r.t. the wolfram alpha description
radius = 10

nx = 101
phi = np.linspace(0, np.pi * 2, num=nx, dtype=np.float32)
ny = 51
theta = np.linspace(0, np.pi, num=ny, dtype=np.float32)

phi_grid, theta_grid = np.meshgrid(phi, theta)

# convert to cartesian coordinates
theta_grid_sin = np.sin(theta_grid)
x = radius * np.cos(phi_grid) * theta_grid_sin * -1
y = radius * np.cos(theta_grid)

# elongate along z axis
z = radius * 2 * np.sin(phi_grid) * theta_grid_sin

sphere = figure[0, 0].add_surface(
np.dstack([x, y, z]),
mode="phong",
cmap="bwr", # by default, providing a colormap name will map the colors to z values
)

# display xz plane as a grid
figure[0, 0].axes.grids.xy.visible = True
figure.show()

# view from top right angle
figure[0, 0].camera.show_object(sphere.world_object, (1, 1, -1), up=(0, 0, 1))


# NOTE: fpl.loop.run() should not be used for interactive sessions
# See the "JupyterLab and IPython" section in the user guide
if __name__ == "__main__":
print(__doc__)
fpl.loop.run()
45 changes: 45 additions & 0 deletions examples/mesh/surface_gaussian.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""
Gaussian kernel as a surface
============================

Example showing a gaussian kernel as a surface mesh
"""

# test_example = true
# sphinx_gallery_pygfx_docs = 'screenshot'

import fastplotlib as fpl
import numpy as np


figure = fpl.Figure(size=(700, 560), cameras="3d", controller_types="orbit")


def gaus2d(x=0, y=0, mx=0, my=0, sx=1, sy=1):
return (
1.0
/ (2.0 * np.pi * sx * sy)
* np.exp(
-((x - mx) ** 2.0 / (2.0 * sx**2.0) + (y - my) ** 2.0 / (2.0 * sy**2.0))
)
)


r = np.linspace(0, 10, num=200)
x, y = np.meshgrid(r, r)
z = gaus2d(x, y, mx=5, my=5, sx=1, sy=1) * 50

mesh = figure[0, 0].add_surface(
np.dstack([x, y, z]), mode="phong", cmap="jet"
)

# figure[0, 0].axes.grids.xy.visible = True
figure[0, 0].camera.show_object(mesh.world_object, (-2, 2, -2), up=(0, 0, 1))
figure.show()


# NOTE: fpl.loop.run() should not be used for interactive sessions
# See the "JupyterLab and IPython" section in the user guide
if __name__ == "__main__":
print(__doc__)
fpl.loop.run()
Loading