Faux Volume Rendering Along a Spacecraft Trajectory

Faux Volume Rendering Along a Spacecraft Trajectory#

This example demonstrates a technique for simulating volumetric rendering of coronal density by deconstructing a 3-D SphericalMesh into stacked semi-transparent radial shells — a faux volume that avoids the GPU memory cost of true volumetric rendering while still conveying the large-scale 3-D structure of the corona.

The scene is animated along a real Parker Solar Probe (PSP) trajectory obtained from the JPL Horizons ephemeris service via spacecraft_trajectory(), placing the virtual observer at each spacecraft position and framing the field of view in helioprojective angular coordinates using observer_los_view.

See also

Camera Orbit Animation

Simpler orbit animation driven by a synthetic observer path.

Combining Slices, Contours, and Fieldlines

Multi-layer coronal scene that combines slices, contours, and fieldlines.

import os

import numpy as np
from pyvisual import Plot3d
from pyvisual.core.mesh3d import SphericalMesh
from pyvisual.utils.data import fetch_datasets
from pyvisual.utils.geometry import spacecraft_trajectory

Load Data#

spacecraft_trajectory() queries the JPL Horizons ephemeris for Parker Solar Probe over a four-day window and returns a numpy.ndarray of shape (3, N) whose rows are \((r,\,\theta,\,\phi)\) in the Carrington frame, sampled at the default 1-hour cadence.

fetch_datasets() downloads (or loads from cache) the CR 2282 coronal density field on the \(1\text{–}30\,R_\odot\) grid and returns the path to the HDF5 file as the named attribute cor_rho.

trajectory = spacecraft_trajectory('psp', '2024-03-27', '2024-03-31')
rho_file = fetch_datasets("cor", "rho").cor_rho
INFO: Obtained JPL HORIZONS location for Parker Solar Probe (spacecraft) (-96) [sunpy.coordinates.ephemeris]

Build the Faux Volume Representation#

The full density grid is loaded into a SphericalMesh. Indexing with [0, ...] selects the innermost radial shell at \(r \approx 1\,R_\odot\) as a 2-D reference surface for the photospheric density.

The remaining shells [1:, ...] form the 3-D coronal volume. Applying numpy.log via the mesh arithmetic suite compresses the large dynamic range of coronal density.

deconstruct() with method='slices' converts the 3-D pyvista.RectilinearGrid into a collection of 2-D radial shell surfaces (via build_slice_polydata()). Rendered with PyVista’s 'sigmoid_7' opacity transfer function, the stacked shells map low log-density regions to near-transparent and high log-density regions to near-opaque, mimicking the appearance of a volumetric render without requiring volume rendering hardware support.

mesh = SphericalMesh(rho_file)
radial_slice_at_photosphere = mesh[0, ...]
deconstructed_mesh_volume = np.log(mesh[1:, ...]).deconstruct(method='slices')

Animate Along the Spacecraft Trajectory#

The scene is assembled once and then animated by stepping the observer through each position in the PSP trajectory. Iterating over trajectory.T yields one \((r,\,\theta,\,\phi)\) column per time step, which is assigned directly to observer_position.

observer_los_view frames the field of view as helioprojective angular extents \((x_0,\, x_1,\, y_0,\, y_1)\) in degrees. Re-applying the FOV at every frame ensures consistent framing as the spacecraft distance changes over the trajectory.

if not os.environ.get('SPHINX_GALLERY_BUILD'):
    plotter = Plot3d()
    plotter.add_axes()
    plotter.add_mesh(radial_slice_at_photosphere, show_scalar_bar=False)
    plotter.add_mesh(deconstructed_mesh_volume, opacity='sigmoid_7', show_scalar_bar=False)
    plotter.open_movie("parker_trajectory.mp4", framerate=10)
    for position in trajectory.T:
        plotter.observer_position = position
        plotter.observer_los_view = -50, 50, -45, 45
        plotter.write_frame()
    plotter.close()

Total running time of the script: (0 minutes 1.163 seconds)

Gallery generated by Sphinx-Gallery