Visualising superpositions of isotropic 3D Gaussian fields as isosurfaces (metaballs), animated over time.
- Overview
- Per-frame pipeline
- Install
- Usage
- The two scenes
- Performance
- Project structure
- MATLAB originals
- Licence
This package builds, sums, and renders isotropic 3D Gaussian "metaballs" as animated isosurfaces. The central idea is superposition: several Gaussian centres are summed into one composite scalar field, and the field is contoured at a fixed level (isovalue 0.5). At that threshold neighbouring centres fuse into smooth, metaball-ish shapes โ metaballs โ so the rendered surface behaves like soft spheres melting into one another as the centres move.
Two animations ship as named scenes:
cinematicโ five coupled metaballs orbiting on hand-coded paths over a ~60 s timeline, with a keyframe-driven style / camera / resolution schedule (wireframe, shaded, oblique, red) and optional MP4 recording.swarmโ eightParameterRotate-driven particles on a fixed-resolution grid, slowly precessing in an orthographic box; the heavier scene, designed for headless recording.
This is a Python (PyVista / VTK) port of two MATLAB originals (Gaussian3DAnimation.m and Gaussian3DTest01.m). The original MATLAB sources are kept under src/matlab/ for reference; the kernel, motion, and schedules are reproduced to match them (including the deliberate "base-a" Gaussian form and a couple of load-bearing MATLAB quirks).
Each animation frame walks the same data pipeline. The sample grid is rebuilt only when the resolution step changes (so the constant-resolution swarm builds its grid once and reuses it), and time advances after the field is built, so the increment shows up only on the next frame.
flowchart LR
P["Frame parameters<br/>centres, variance v, step, style"]
G["Build or reuse<br/>cube sample grid"]
F["Evaluate + sum<br/>Gaussian bumps -> scalar volume"]
C["Contour @ isovalue 0.5<br/>(VTK or scikit-image)"]
R["Render<br/>style, camera, lights"]
P --> G --> F --> C --> R
R --> W["Interactive window"]
R --> M["MP4 frame (when recording)"]
R --> T["Advance time"]
T -. "next frame" .-> P
The package targets Python >= 3.10 and is validated on CPython 3.14 (Windows). From the package directory src/python/, create and activate a virtual environment, then install the package into that venv:
cd src/python
python -m venv .venv
# Activate the venv:
# Windows (PowerShell): .venv\Scripts\Activate.ps1
# Windows (cmd): .venv\Scripts\activate.bat
# Linux / macOS: source .venv/bin/activate
# Install (with the test / dev extra, recommended):
pip install -e .[dev] # or: pip install -e . (runtime only)Installing into the venv puts the gaussian-isosurfaces console script inside that venv, so the commands below work whenever it is active. If the shell reports gaussian-isosurfaces is not recognized, you are in a different shell/venv than the one you installed into โ re-activate the venv above (or use the equivalent python -m gaussian_isosurfaces.cli ...).
Runtime dependencies: numpy, pyvista, vtk, imageio-ffmpeg. The [dev] extra adds scikit-image (the alternate contour backend, required by the backend-agreement tests), pytest, matplotlib, and ruff.
With the virtual environment active (see Install), the package provides a console script, gaussian-isosurfaces (equivalently python -m gaussian_isosurfaces.cli). A scene is always required; every other flag falls back to the authoritative per-scene default when omitted.
| Flag | Argument | Default | Description |
|---|---|---|---|
--scene |
{cinematic, swarm} |
(required) | Which animation to run. |
--frames |
N |
scene default (cinematic 1200, swarm 600) | Frame budget / stop condition. |
--fps |
N |
scene default (cinematic 20, swarm 60) | Animation and recording rate. |
--resolution |
STEP |
scene default | Override the mesh step (one constant step for every frame). |
--isovalue |
V |
0.5 |
Isosurface level. |
--backend |
{vtk, skimage} |
vtk |
Contour engine. |
--window-size |
W H |
1048 800 |
Interactive / render image size in pixels. |
--record |
[PATH] |
(off) | Record an MP4. Bare --record uses the scene default filename (cinematic.mp4 / particle_swarm.mp4). |
--output |
PATH |
(off) | Record an MP4 to PATH (takes precedence over --record). |
--offscreen |
(flag) | off | Headless render; the frame count is the authoritative stop. |
--wireframe |
(flag) | off | Swarm: white wireframe instead of blue Phong. |
--oblique |
(flag) | off | Swarm: view (45, 22.5) instead of (0, 0). |
--hidden-lines |
(flag) | off | Hidden-line removal (MATLAB hidden on/off): hide wireframe edges occluded by the surface; --no-hidden-lines forces it off. Mainly affects wireframe. |
# Interactive cinematic scene (live window; press ESC or close the window to stop).
gaussian-isosurfaces --scene cinematic
# Record the swarm scene to MP4 headlessly (the supported path for the 49^3 grid).
gaussian-isosurfaces --scene swarm --offscreen --record
# Swarm as a white wireframe seen from the oblique view, recorded to a named file.
gaussian-isosurfaces --scene swarm --offscreen --wireframe --oblique --output swarm_wire.mp4
# A short cinematic clip using the scikit-image marching-cubes backend.
gaussian-isosurfaces --scene cinematic --backend skimage --frames 200 --offscreen --record| Parameter | cinematic (Scene A) |
swarm (Scene B) |
|---|---|---|
| Source | Gaussian3DAnimation.m |
Gaussian3DTest01.m |
| metaballss / particles | 5 coupled metaballs | 8 ParameterRotate particles |
| Domain (cube half-extent) | [-8, 8]^3 |
[-6, 6]^3 |
Variance v |
1.25 | 1.0 |
| Isovalue | 0.5 | 0.5 |
| FPS / frames | 20 fps, 1200 frames | 60 fps, 600 frames |
| Resolution | keyframe-scheduled (steps 2.0 / 1.0 / 0.75) | constant step 0.25 -> 49^3 |
| Projection | perspective | orthographic |
| Grid colour | green (0, 0.75, 0) |
green (0, 0.5, 0) |
Cinematic resolution + style schedule. The cinematic scene steps through keyframe bands at fps * {10, 20, 30, 40, 50, 60} (frames 200 / 400 / 600 / 800 / 1000 / 1200 at the default 20 fps). The mesh resolution coarsens and refines across these bands โ step 2.0 (9^3) before the first band, then 1.0 (17^3), 0.75 (22^3), back to 1.0, then 0.75 โ while the render style cycles through wireframe, solid blue, oblique, wireframe, and finally solid red. (A deliberate off-by-one between the style and resolution schedules at the kf4 boundary is reproduced from the original on purpose.)
Swarm constant grid + slow advance. The swarm uses a single fixed step of 0.25 โ a 49^3 grid built once and reused โ with the eight particles advancing by only pi / 8192 per frame, so the formation precesses slowly.
Interactive vs offscreen. Without --offscreen a live window opens and you can quit with ESC or by closing the window; the lighter cinematic grids play comfortably this way. The swarm's 49^3 grid is heavier, so offscreen recording is the supported path for it.
Representative per-frame timings measured on this machine, offscreen, over the full pipeline (grid + field sum + contour + render) at the standard 1048x800 window size. Numbers are mean ms/frame after warm-up; treat them as order-of-magnitude.
| Scene | Step | Grid | Backend | Mean ms/frame |
|---|---|---|---|---|
| cinematic | 2.0 | 9^3 | vtk | ~10 |
| cinematic | 1.0 | 17^3 | vtk | ~9 |
| cinematic | 0.75 | 22^3 | vtk | ~9 |
| swarm | 0.25 | 49^3 | vtk | ~30 |
At the cinematic grid sizes the per-frame cost is dominated by render/setup overhead, so the three steps land close together. The swarm's 49^3 volume is markedly heavier per frame; interactive playback adds window-event and present overhead on top of the figures above, so for the swarm prefer --offscreen --record rather than the live window. Times scale with grid size, window size, and your GPU/driver, so your own numbers will vary.
src/python/
โโโ pyproject.toml deps + console script
โโโ gaussian_isosurfaces/
โ โโโ __init__.py public API re-exports
โ โโโ cli.py argparse front end + main ()
โ โโโ config.py AppConfig (single source of truth)
โ โโโ field.py Gaussian kernel + field superposition
โ โโโ isosurface.py VTK / scikit-image contour backends
โ โโโ motion.py per-frame centre motion
โ โโโ style.py keyframe style / resolution schedules
โ โโโ renderer.py IsosurfaceRenderer (PyVista / VTK)
โ โโโ scenes/
โ โโโ base.py shared per-frame loop
โ โโโ cinematic.py CinematicScene (5 metaballs)
โ โโโ swarm.py SwarmScene (8 particles)
โโโ tests/ pytest suite (math + offscreen render)
The curated public API is re-exported from the package root:
from gaussian_isosurfaces import (
GaussianField, contour_field, marching_cubes_field,
IsosurfaceRenderer, AppConfig, CinematicScene, SwarmScene, main,
__version__,
)The two animations began as MATLAB scripts; this package is a faithful Python / PyVista port. The originals โ Gaussian3DAnimation.m (cinematic) and Gaussian3DTest01.m (swarm), along with the lower-dimensional demos and the ParameterRotate / Gaussian3D helpers โ are kept under src/matlab/ for reference and comparison. The port reproduces their mathematics (notably the non-canonical base-a Gaussian kernel) and their per-frame ordering so the metaballs match at isovalue 0.5.
Released under the MIT License โ see LICENSE. Copyright ยฉ 2019 Rohin Gosling.
