AGU 2023 Tutorial: Visualizing 2D and 3D Geoscience Data in Python
Set “homeDir” to example data directory
Download example data here
#homeDir = "/Users/pearse/Examples/AGU2023"
homeDir = "/Users/pearse/Downloads/AGU2023"
Create a new Session
from vapor import session
ses = session.Session()
Warning: sysroot "/Applications/Xcode_12.4.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" not found (ignoring for now).
Vapor 3.9.0
Python 3.9.18 (/Users/pearse/miniconda3/envs/3.9.1)
OpenGL 4.1 Metal - 83.1
Find documentation
help(session.Session)
# Renderer - help(renderer.Renderer)
# Camera - help(camera.Camera)
# Animation - help(animation.Amimation)
# https://ncar.github.io/VaporDocumentationWebsite/pythonAPIReference/classReference.html
Help on class Session in module vapor.session:
class Session(cppyy.gbl.Session)
| Method resolution order:
| Session
| cppyy.gbl.Session
| cppyy.gbl.CPPInstance
| builtins.object
|
| Methods defined here:
|
| CreatePythonDataset(self)
| Creates a python dataset or returns one if it already exists for the current session.
|
| DeleteRenderer(self, renderer: vapor.renderer.Renderer)
| void Session::DeleteRenderer(String name)
|
| GetAxisAnnotations(self) -> vapor.annotations.AxisAnnotation
|
| GetCamera(self)
|
| GetDataset(self, name) -> vapor.dataset.Dataset
|
| GetDatasets(self)
|
| GetRenderer(self, name)
|
| GetRenderers(self) -> list[vapor.renderer.Renderer]
|
| GetSceneAnnotations(self) -> vapor.annotations.SceneAnnotation
|
| NewRenderer(self, Class: vapor.renderer.Renderer, datasetName: str) -> vapor.renderer.Renderer
| std::string Session::NewRenderer(String type, String dataset = "")
|
| OpenDataset(self, datasetType: str, files: list[str])
| Open a dataset of type datasetType from a list of files.
| A list of supported dataset types can be retrieved from Dataset.GetDatasetTypes()
|
| RenderToImage(self, fast=False) -> <module 'PIL.Image' from '/Users/pearse/miniconda3/envs/3.9.1/lib/python3.9/site-packages/PIL/Image.py'>
|
| SetResolution(self, width, height)
|
| Show(self)
|
| __init__(self)
| Session::Session()
| Session::Session(const Session&)
|
| getAnimationParams(...)
| AnimationParams* __cppyy_internal::Dispatcher1::getAnimationParams()
|
| getGUIStateParams(...)
| GUIStateParams* __cppyy_internal::Dispatcher1::getGUIStateParams()
|
| getParamsDatasetInfo(...)
| void __cppyy_internal::Dispatcher1::getParamsDatasetInfo(std::string arg0, std::string* arg1, std::vector<std::string>* arg2)
|
| getSettingsParams(...)
| SettingsParams* __cppyy_internal::Dispatcher1::getSettingsParams()
|
| getWinName(...)
| std::string __cppyy_internal::Dispatcher1::getWinName()
|
| loadAllParamsDatasets(...)
| void __cppyy_internal::Dispatcher1::loadAllParamsDatasets()
|
| ----------------------------------------------------------------------
| Data and other attributes defined here:
|
| __cpp_cross__ = 'Session'
|
| ----------------------------------------------------------------------
| Methods inherited from cppyy.gbl.Session:
|
| CloseAllDatasets(...)
| void Session::CloseAllDatasets()
|
| CloseDataset(...)
| void Session::CloseDataset(String name)
|
| GetDatasetNames(...)
| std::vector<std::string> Session::GetDatasetNames()
|
| GetPythonWinName(...)
| std::string Session::GetPythonWinName()
|
| GetRendererNames(...)
| std::vector<std::string> Session::GetRendererNames()
|
| GetTimesteps(...)
| int Session::GetTimesteps()
|
| Load(...)
| int Session::Load(String path)
|
| Render(...)
| int Session::Render(String imagePath, bool fast = false)
|
| Reset(...)
| void Session::Reset()
|
| Save(...)
| int Session::Save(String path)
|
| SetTimestep(...)
| void Session::SetTimestep(int ts)
|
| SetWaspMyBaseErrMsgFilePtrToSTDERR(...)
| static void Session::SetWaspMyBaseErrMsgFilePtrToSTDERR()
|
| __assign__(...)
| Session& Session::operator=(const Session&)
|
| ----------------------------------------------------------------------
| Data descriptors inherited from cppyy.gbl.Session:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
|
| ----------------------------------------------------------------------
| Methods inherited from cppyy.gbl.CPPInstance:
|
| __add__(self, value, /)
| Return self+value.
|
| __bool__(self, /)
| True if self else False
|
| __destruct__(...)
| call the C++ destructor
|
| __dispatch__(...)
| dispatch to selected overload
|
| __eq__(self, value, /)
| Return self==value.
|
| __ge__(self, value, /)
| Return self>=value.
|
| __getitem__(...)
| pointer dereferencing
|
| __gt__(self, value, /)
| Return self>value.
|
| __hash__(self, /)
| Return hash(self).
|
| __invert__(self, /)
| ~self
|
| __le__(self, value, /)
| Return self<=value.
|
| __lt__(self, value, /)
| Return self<value.
|
| __mul__(self, value, /)
| Return self*value.
|
| __ne__(self, value, /)
| Return self!=value.
|
| __neg__(self, /)
| -self
|
| __pos__(self, /)
| +self
|
| __radd__(self, value, /)
| Return value+self.
|
| __repr__(self, /)
| Return repr(self).
|
| __rmul__(self, value, /)
| Return value*self.
|
| __rsub__(self, value, /)
| Return value-self.
|
| __rtruediv__(self, value, /)
| Return value/self.
|
| __smartptr__(...)
| get associated smart pointer, if any
|
| __str__(self, /)
| Return str(self).
|
| __sub__(self, value, /)
| Return self-value.
|
| __truediv__(self, value, /)
| Return self/value.
|
| ----------------------------------------------------------------------
| Static methods inherited from cppyy.gbl.CPPInstance:
|
| __new__(*args, **kwargs) from cppyy.CPPScope
| Create and return a new object. See help(type) for accurate signature.
|
| ----------------------------------------------------------------------
| Data descriptors inherited from cppyy.gbl.CPPInstance:
|
| __python_owns__
| If true, python manages the life time of this object
Load data
from pathlib import Path
myPath = homeDir + "/data"
myFiles = [str(file) for file in Path(myPath).rglob('*')]
for file_path in myFiles:
print(file_path)
from vapor import dataset
data = ses.OpenDataset(dataset.WRF, myFiles)
/Users/pearse/Downloads/AGU2023/data/wrfout_met_d02_2020-10-22_12:00:00.slab
/Users/pearse/Downloads/AGU2023/data/wrfout_met_d02_2020-10-22_17:00:00.slab
/Users/pearse/Downloads/AGU2023/data/wrfout_met_d02_2020-10-22_10:00:00.slab
/Users/pearse/Downloads/AGU2023/data/wrfout_met_d02_2020-10-22_18:00:00.slab
/Users/pearse/Downloads/AGU2023/data/wrfout_met_d02_2020-10-22_15:00:00.slab
/Users/pearse/Downloads/AGU2023/data/wrfout_met_d02_2020-10-22_16:00:00.slab
/Users/pearse/Downloads/AGU2023/data/wrfout_met_d02_2020-10-22_13:00:00.slab
/Users/pearse/Downloads/AGU2023/data/wrfout_met_d02_2020-10-22_14:00:00.slab
/Users/pearse/Downloads/AGU2023/data/wrfout_met_d02_2020-10-22_11:00:00.slab
Show dataset metadata
Coordinate variables
print("Time Coordinate Variable Name:", data.GetTimeCoordVarName())
print("Coordinate Variable Names:", data.GetCoordVarNames())
Time Coordinate Variable Name: Time
Coordinate Variable Names: ['Time', 'XLAT', 'XLAT_U', 'XLAT_V', 'XLONG', 'XLONG_U', 'XLONG_V', 'bottom_top', 'bottom_top_stag', 'Elevation', 'ElevationU', 'ElevationV', 'ElevationW', 'XLATY', 'XLAT_UY', 'XLAT_VY', 'XLONGX', 'XLONG_UX', 'XLONG_VX']
Variables
print("Data Variables:")
for var in data.GetDataVarNames():
print(f" {var}")
print(f" Time Varying:", bool(data.IsTimeVarying(var)))
print(f" Dimensionality:", data.GetVarGeometryDim(var))
print(f" Coordinates:", data.GetVarCoordVars(var, True))
print(" Data Range:", data.GetDataRange(var))
Data Variables:
FIRE_AREA
Time Varying: True
Dimensionality: 2
Coordinates: ['XLONGX', 'XLATY']
Data Range: [0.0, 1.0]
HGT
Time Varying: True
Dimensionality: 2
Coordinates: ['XLONGX', 'XLATY']
Data Range: [2244.004638671875, 4026.545654296875]
PH
Time Varying: True
Dimensionality: 3
Coordinates: ['XLONGX', 'XLATY', 'ElevationW']
Data Range: [0.0, 765.3257446289062]
PHB
Time Varying: True
Dimensionality: 3
Coordinates: ['XLONGX', 'XLATY', 'ElevationW']
Data Range: [22013.685546875, 52509.6484375]
U
Time Varying: True
Dimensionality: 3
Coordinates: ['XLONG_UX', 'XLAT_UY', 'ElevationU']
Data Range: [-13.070408821105957, 38.378875732421875]
V
Time Varying: True
Dimensionality: 3
Coordinates: ['XLONG_VX', 'XLAT_VY', 'ElevationV']
Data Range: [-14.471896171569824, 20.526811599731445]
W
Time Varying: True
Dimensionality: 3
Coordinates: ['XLONGX', 'XLATY', 'ElevationW']
Data Range: [-11.834917068481445, 11.622491836547852]
fire_smoke
Time Varying: True
Dimensionality: 3
Coordinates: ['XLONGX', 'XLATY', 'Elevation']
Data Range: [-4.964304935128894e-06, 0.0457780659198761]
Create a 2D Data renderer
from vapor import renderer
ren = data.NewRenderer(renderer.TwoDDataRenderer)
ren.SetVariableName("FIRE_AREA") # Set to first 2D data variable
Show the renderer
ses.Show()
Apply a height variable
ren.SetHeightVariableName("HGT")
ses.Show()
Create an interactive Visualizer with VaporVisualizerWidget()
from jupyter_vapor_widget import *
viz = VaporVisualizerWidget(ses)
viz.Render()
viz
Show a Transfer Function
from vapor import utils
utils.ShowMatPlotLibHistogram(ses, ren)
Apply opacity to a Transfer Function
plt = utils.GetMatPlotLibHistogram(ses, ren)
opacities = [(0.2, 0), (0.8, 1)]
plt.plot(*zip(*opacities))
plt.show()
Render the current Session in an interactive Visualizer
tf = ren.GetPrimaryTransferFunction()
tf.SetOpacityControlPoints(opacities)
viz.Render()
viz
Load a builtin Transfer Function
tf.LoadBuiltinColormap("Sequential/BlackBody")
viz.Render()
viz
Load a Transfer Function that was configured in VAPOR
tf.LoadColormapFromFile( homeDir + "/fireArea.tf3")
viz.Render()
viz
Interactive Sliders - Open visualizer_widget_example.ipynb
*** Back to VAPOR for DVR and Python Engine ***
Timeseries Animation
%%time
from vapor.animation import Animation
ses.Load( homeDir + "/et_1.vs3")
anim = Animation(ses)
timesteps = ses.GetTimesteps()
for ts in range(0, timesteps):
ses.SetTimestep(ts)
anim.CaptureFrame()
anim.ShowInteractive()
Keyframing
# Import necessary modules
from vapor import session, camera
# Acquire the camera settings from an initial session file
ses1 = session.Session()
#ses1.Load("/Users/pearse/Examples/eastTroublesome/et_1.vs3")
ses1.Load(homeDir + "/et_1.vs3")
#ses1.Load("~/Examples/eastTroublesome/et_1.vs3")
cam1 = ses1.GetCamera()
dir1 = cam1.GetDirection()
pos1 = cam1.GetPosition()
up1 = cam1.GetUp()
# Acquire the camera settings from a secondary session file that we will transition into
ses2 = session.Session()
ses2.Load( homeDir + "/et_2.vs3")
cam2 = ses2.GetCamera()
dir2 = cam2.GetDirection()
pos2 = cam2.GetPosition()
up2 = cam2.GetUp()
# Difference between camera positions on each axis
dPositionX = (pos2[0] - pos1[0])
dPositionY = (pos2[1] - pos1[1])
dPositionZ = (pos2[2] - pos1[2])
# Difference between camera direction vectors on each axis
dDirectionX = (dir2[0] - dir1[0])
dDirectionY = (dir2[1] - dir1[1])
dDirectionZ = (dir2[2] - dir1[2])
# Difference between camera up vectors on each axis
dUpX = (up2[0] - up1[0])
dUpY = (up2[1] - up1[1])
dUpZ = (up2[2] - up1[2])
# Perform a linear interoplation between the Camera's start position, direction, and up vector
steps=50
for i in range(0,steps):
position = [
pos1[0]+dPositionX*i/steps,
pos1[1]+dPositionY*i/steps,
pos1[2]+dPositionZ*i/steps
]
cam1.SetPosition( position )
direction = [
dir1[0]+dDirectionX*i/steps,
dir1[1]+dDirectionY*i/steps,
dir1[2]+dDirectionZ*i/steps
]
cam1.SetDirection( direction )
up = [
up1[0]+dUpX*i/steps,
up1[1]+dUpY*i/steps,
up1[2]+dUpZ*i/steps
]
cam1.SetUp( up )
ses1.Render( homeDir + "captures/" + str(i) + ".png")
Camera Orbit
# Import necessary modules
import cv2, os
from vapor import session, animation
from numpy import cross, eye, dot, radians, asarray, array
from scipy.linalg import expm, norm
UseValueFromSessionFile = None
# Configure various parameters for rotating the camera around the scene
session_path = homeDir + "/et_2.vs3"
output = "rotation.mp4"
video_framerate = 30
video_resolution = (640, 480)
data_timestep_framerate = 0 # set to zero to disable
duration = 4 # seconds
rotate_speed = 45 # deg/s
rotation_axis = [0,0,1] # Z (up)
rotation_center = UseValueFromSessionFile # Can be replaced with [x,y,z] coordinates here
save_individual_frames = False
# Configure our session according to the parameters that were defined above
session_path, output = [os.path.expanduser(p) for p in (session_path, output)]
n_frames = video_framerate * duration
ses = session.Session()
ses.Load(session_path)
ses.SetResolution(*video_resolution)
cam = ses.GetCamera()
pos, dir, up, tgt = [asarray(x) for x in [cam.GetPosition(), cam.GetDirection(), cam.GetUp(), cam.GetTarget()]]
if rotation_center:
tgt = asarray(rotation_center)
# Define a function that returns a matrix exponential for rotating the camera
def rotation_matrix(axis, theta):
return expm(cross(eye(3), axis / norm(axis) * theta))
anim = animation.Animation(ses)
for i in range(0, n_frames):
print(f"Rendering... [{'#'*round(40*i/(n_frames-1))}{' '*round(40*(1-i/(n_frames-1)))}] {100*(i+1)/n_frames:.0f}%", end="\r" if i < n_frames-1 else "\n")
ses.SetTimestep(int(data_timestep_framerate * i / video_framerate))
M = rotation_matrix(rotation_axis, radians(rotate_speed) * i / video_framerate)
cam.SetPosition(dot(M, pos - tgt) + tgt)
cam.SetDirection(dot(M, dir))
cam.SetUp(dot(M, up))
anim.CaptureFrame()
if save_individual_frames:
ses.Render(f"{output}_{i:04}.png")
anim.SaveMP4(output, video_framerate)