Graphics#
The opynsim.graphics module provides utilities for rasterizing
OPynSim’s datastructures, images, lines, and shapes into a grid of pixels
(a bitmap/raster image). The utilities are available at different abstraction
levels, depending on how much control the caller needs over the rendering
process:
High-Level Rendering Functions: Functions such as
opynsim.graphics.render_model_in_state()are high-level functions that internally handle graphics context initialization, scene graph generation, rendering, packing the raster image into a Python-readable datastructure, and cleanup.Low-Level Rendering Utilities: TODO.
Examples#
Render to a PNG#
This example uses Pillow to encode
the pixel data returned by opynsim.graphics.Texture2D.pixels_rgba32() into a PNG file.
import opynsim
import opynsim.graphics
from PIL import Image # from `Pillow` package
# Create/import a `Model` + `ModelState`.
model_specification = opynsim.example_specification_double_pendulum()
model = opynsim.compile_specification(model_specification)
model_state = model.initial_state()
model.realize(model_state, opynsim.ModelStateStage.REPORT) # usually required for rendering
# Render the `Model` + `ModelState` to an `opynsim.graphics.Texture2D`.
texture_2d = opynsim.graphics.render_model_in_state(model, model_state)
# Read the pixels into a `PIL.Image` object.
image = Image.fromarray(texture_2d.pixels_rgba32(), mode="RGBA")
# Write the `PIL.Image` to disk as a PNG file.
image.save("render_output.png")
Render to a Plot#
This example uses Matplotlib to composite the pixel data
returned by opynsim.graphics.Texture2D.pixels_rgba32() into a line plot.
import opynsim
import opynsim.graphics
import numpy as np
import matplotlib.pyplot as plt # from `matplotlib` package
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
# Create/import a `Model` + `ModelState`.
model_specification = opynsim.example_specification_double_pendulum()
model = opynsim.compile_specification(model_specification)
model_state = model.initial_state()
model.realize(model_state, opynsim.ModelStateStage.REPORT) # usually required for rendering
# Render the `Model` + `ModelState` to an `opynsim.graphics.Texture2D`.
texture_2d = opynsim.graphics.render_model_in_state(model, model_state)
# Create a Matplotlib `OffsetImage` using the rendered pixel data
offset_image = OffsetImage(texture_2d.pixels_rgba32(), zoom=0.2, alpha=1.0)
# Wrap the `OffsetImage` into an `AnnotationBox` to anchor the image somewhere
# in the plot data (here, on the first peak of sin(x)).
ab = AnnotationBbox(
offset_image, # The rendered scene, wrapped in `OffsetImage`
(np.pi/2, 1), # First peak of sin(x)
xybox=(-25, 50), # Offset the image from the peak
xycoords='data', # Use plot coordinates for the anchor
boxcoords="offset points",
frameon=False,
pad=0.0,
arrowprops=dict(arrowstyle="->", connectionstyle="angle,angleA=0,angleB=90,rad=3")
)
# Create plot
fig, ax = plt.subplots(figsize=(10, 6))
x = np.linspace(0, 10, 100)
ax.plot(x, np.sin(x), label='Sine Wave', color='blue')
ax.set_title("Model Render Overlay at Peak")
ax.set_ylim(-1.5, 2.5) # Make some room for the image
ax.add_artist(ab) # Add image
plt.legend()
# Show the figure in a window.
#
# This might require installing `python3-tk` on Linux.
plt.show()
# Alternatively, Save plot to PNG file
# plt.savefig("plot.png", dpi=300)
API Reference#
- opynsim.graphics.render_model_in_state(model: opynsim._core.Model, model_state: opynsim._core.ModelState, *, dimensions: tuple[int, int] = (640, 480), zoom_to_fit: bool = True, draw_floor: bool = False) opynsim._core.graphics.Texture2D#
Renders the given
opynsim.Model+opynsim.ModelStateto aopynsim.graphics.Texture2D.- Parameters:
model (opynsim.Model) – The model to render.
model_state (opynsim.ModelState) – The state of the model to render. Should be realized to at least
opynsim.ModelStateStage.REPORT.dimensions (tuple[int, int]) – The desired output resolution (width, height) of the rendered image in pixels.
zoom_to_fit (bool) – Tells the renderer to automatically set up the camera to focus on the center of the bounds of the scene at a distance that can see the entire scene.
draw_floor (bool) – Draws a floor, matching the default behavior of Simbody and OpenSim GUI.
- Returns:
The rendered image, which will have the specified
dimensions.- Return type:
- class opynsim.graphics.Texture2D#
Bases:
objectRepresents a two-dimensional image.
- pixels_rgba32(self) numpy.ndarray[dtype=uint8, shape=(*, *, 4), writable=False]#
Returns an ndarray with the shape
(height, width, 4), where each element of the 3rd dimension is a raw uint8 (0-255) pixel value for the red (R), green (G), blue (B), and alpha (A) components of the texture respectively.The grid layout and component encoding of the returned array tries to match the conventions used by other Python libraries (e.g. matplotlib, pyvista) and image tools so that the render is easy to compose into other systems.
The layout of the pixels is such that the first pixel in the first row (
rv[0, 0]) starts in the top-left of the image. Subsequent pixels within that row are then encoded left-to-right. The following row (rv[1]) is then below that row, and so on, until the final pixel, which is in the bottom-right of the image.The encoding the the components of a pixel is such that the lowest value for each component is zero, representing zero intensity of that component. The highest value for each component is 255, representing the maximum intensity of that component. The color components (R, G, and B) are in an sRGB color space, while the alpha component (A) is linear.