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.ModelState to a opynsim.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:

opynsim.graphics.Texture2D

class opynsim.graphics.Texture2D#

Bases: object

Represents 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.