Theia is a hardware volume renderer that takes advantage of NVidias CUDA language to peform ray casting with GPUs instead of the CPU.
Only unigrid rendering is supported, but yt provides a grid mapping function to get unigrid data from amr or sph formats, see Extracting Fixed Resolution Data.
CUDA 5 or later and
The environment variable CUDA_SAMPLES must be set pointing to the common/inc samples shipped with CUDA. The following shows an example in bash with CUDA 5.5 installed in /usr/local :
export CUDA_SAMPLES=/usr/local/cuda-5.5/samples/common/inc
PyCUDA must also be installed to use Theia.
PyCUDA can be installed following these instructions :
git clone –recursive http://git.tiker.net/trees/pycuda.git
python configure.py python setup.py install
Currently rendering only works on uniform grids. Here is an example on a 1024 cube of float32 scalars.
from yt.visualization.volume_rendering.theia.scene import TheiaScene
from yt.visualization.volume_rendering.algorithms.front_to_back import FrontToBackRaycaster
import numpy as np
#load 3D numpy array of float32
volume = np.load("/home/bogert/log_densities_1024.npy")
scene = TheiaScene( volume = volume, raycaster = FrontToBackRaycaster() )
scene.camera.rotateX(1.0)
scene.update()
surface = scene.get_results()
#surface now contains an image array 2x2 int32 rbga values
A TheiaScene object has been created to provide a high level entry point for
controlling the raycaster’s view onto the data. The class
TheiaScene
encapsulates a
Camera object and a TheiaSource that intern encapsulates a volume. The
Camera
provides controls for
rotating, translating, and zooming into the volume. Using the
TheiaSource
automatically
transfers the volume to the graphic’s card texture memory.
OpenGL Example for interactive volume rendering:
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
from OpenGL.GL.ARB.vertex_buffer_object import *
import sys, time
import numpy as np
import pycuda.driver as cuda_driver
import pycuda.gl as cuda_gl
from yt.visualization.volume_rendering.theia.scene import TheiaScene
from yt.visualization.volume_rendering.theia.algorithms.front_to_back import FrontToBackRaycaster
from yt.visualization.volume_rendering.transfer_functions import ColorTransferFunction
from yt.visualization.color_maps import *
import numexpr as ne
window = None # Number of the glut window.
rot_enabled = True
#Theia Scene
ts = None
#RAY CASTING values
c_tbrightness = 1.0
c_tdensity = 0.05
output_texture = None # pointer to offscreen render target
leftButton = False
middleButton = False
rightButton = False
#Screen width and height
width = 1024
height = 1024
eyesep = 0.1
(pbo, pycuda_pbo) = [None]*2
def create_PBO(w, h):
global pbo, pycuda_pbo
num_texels = w*h
array = np.zeros((w,h,3),np.uint32)
pbo = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, pbo)
glBufferData(GL_ARRAY_BUFFER, array, GL_DYNAMIC_DRAW)
glBindBuffer(GL_ARRAY_BUFFER, 0)
pycuda_pbo = cuda_gl.RegisteredBuffer(long(pbo))
def destroy_PBO(self):
global pbo, pycuda_pbo
glBindBuffer(GL_ARRAY_BUFFER, long(pbo))
glDeleteBuffers(1, long(pbo));
glBindBuffer(GL_ARRAY_BUFFER, 0)
pbo,pycuda_pbo = [None]*2
#consistent with C initPixelBuffer()
def create_texture(w,h):
global output_texture
output_texture = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, output_texture)
# set basic parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
# buffer data
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
w, h, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, None)
#consistent with C initPixelBuffer()
def destroy_texture():
global output_texture
glDeleteTextures(output_texture);
output_texture = None
def init_gl(w = 512 , h = 512):
Width, Height = (w, h)
glClearColor(0.1, 0.1, 0.5, 1.0)
glDisable(GL_DEPTH_TEST)
#matrix functions
glViewport(0, 0, Width, Height)
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
#matrix functions
gluPerspective(60.0, Width/float(Height), 0.1, 10.0)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
def resize(Width, Height):
global width, height
(width, height) = Width, Height
glViewport(0, 0, Width, Height) # Reset The Current Viewport And Perspective Transformation
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(60.0, Width/float(Height), 0.1, 10.0)
def do_tick():
global time_of_last_titleupdate, frame_counter, frames_per_second
if ((time.clock () * 1000.0) - time_of_last_titleupdate >= 1000.):
frames_per_second = frame_counter # Save The FPS
frame_counter = 0 # Reset The FPS Counter
szTitle = "%d FPS" % (frames_per_second )
glutSetWindowTitle ( szTitle )
time_of_last_titleupdate = time.clock () * 1000.0
frame_counter += 1
oldMousePos = [ 0, 0 ]
def mouseButton( button, mode, x, y ):
"""Callback function (mouse button pressed or released).
The current and old mouse positions are stored in
a global renderParam and a global list respectively"""
global leftButton, middleButton, rightButton, oldMousePos
if button == GLUT_LEFT_BUTTON:
if mode == GLUT_DOWN:
leftButton = True
else:
leftButton = False
if button == GLUT_MIDDLE_BUTTON:
if mode == GLUT_DOWN:
middleButton = True
else:
middleButton = False
if button == GLUT_RIGHT_BUTTON:
if mode == GLUT_DOWN:
rightButton = True
else:
rightButton = False
oldMousePos[0], oldMousePos[1] = x, y
glutPostRedisplay( )
def mouseMotion( x, y ):
"""Callback function (mouse moved while button is pressed).
The current and old mouse positions are stored in
a global renderParam and a global list respectively.
The global translation vector is updated according to
the movement of the mouse pointer."""
global ts, leftButton, middleButton, rightButton, oldMousePos
deltaX = x - oldMousePos[ 0 ]
deltaY = y - oldMousePos[ 1 ]
factor = 0.001
if leftButton == True:
ts.camera.rotateX( - deltaY * factor)
ts.camera.rotateY( - deltaX * factor)
if middleButton == True:
ts.camera.translateX( deltaX* 2.0 * factor)
ts.camera.translateY( - deltaY* 2.0 * factor)
if rightButton == True:
ts.camera.scale += deltaY * factor
oldMousePos[0], oldMousePos[1] = x, y
glutPostRedisplay( )
def keyPressed(*args):
global c_tbrightness, c_tdensity
# If escape is pressed, kill everything.
if args[0] == '\033':
print('Closing..')
destroy_PBOs()
destroy_texture()
exit()
#change the brightness of the scene
elif args[0] == ']':
c_tbrightness += 0.025
elif args[0] == '[':
c_tbrightness -= 0.025
#change the density scale
elif args[0] == ';':
c_tdensity -= 0.001
elif args[0] == '\'':
c_tdensity += 0.001
def idle():
glutPostRedisplay()
def display():
try:
#process left eye
process_image()
display_image()
glutSwapBuffers()
except:
from traceback import print_exc
print_exc()
from os import _exit
_exit(0)
def process(eye = True):
global ts, pycuda_pbo, eyesep, c_tbrightness, c_tdensity
ts.get_raycaster().set_opacity(c_tdensity)
ts.get_raycaster().set_brightness(c_tbrightness)
dest_mapping = pycuda_pbo.map()
(dev_ptr, size) = dest_mapping.device_ptr_and_size()
ts.get_raycaster().surface.device_ptr = dev_ptr
ts.update()
# ts.get_raycaster().cast()
dest_mapping.unmap()
def process_image():
global output_texture, pbo, width, height
""" copy image and process using CUDA """
# run the Cuda kernel
process()
# download texture from PBO
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, np.uint64(pbo))
glBindTexture(GL_TEXTURE_2D, output_texture)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
width, height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, None)
def display_image(eye = True):
global width, height
""" render a screen sized quad """
glDisable(GL_DEPTH_TEST)
glDisable(GL_LIGHTING)
glEnable(GL_TEXTURE_2D)
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE)
#matix functions should be moved
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glLoadIdentity()
glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0)
glMatrixMode( GL_MODELVIEW)
glLoadIdentity()
glViewport(0, 0, width, height)
glBegin(GL_QUADS)
glTexCoord2f(0.0, 0.0)
glVertex3f(-1.0, -1.0, 0.5)
glTexCoord2f(1.0, 0.0)
glVertex3f(1.0, -1.0, 0.5)
glTexCoord2f(1.0, 1.0)
glVertex3f(1.0, 1.0, 0.5)
glTexCoord2f(0.0, 1.0)
glVertex3f(-1.0, 1.0, 0.5)
glEnd()
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glDisable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, 0)
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0)
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0)
#note we may need to init cuda_gl here and pass it to camera
def main():
global window, ts, width, height
(width, height) = (1024, 1024)
glutInit(sys.argv)
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH )
glutInitWindowSize(width, height)
glutInitWindowPosition(0, 0)
window = glutCreateWindow("Stereo Volume Rendering")
glutDisplayFunc(display)
glutIdleFunc(idle)
glutReshapeFunc(resize)
glutMouseFunc( mouseButton )
glutMotionFunc( mouseMotion )
glutKeyboardFunc(keyPressed)
init_gl(width, height)
# create texture for blitting to screen
create_texture(width, height)
import pycuda.gl.autoinit
import pycuda.gl
cuda_gl = pycuda.gl
create_PBO(width, height)
# ----- Load and Set Volume Data -----
density_grid = np.load("/home/bogert/dd150_log_densities.npy")
mi, ma= 21.5, 24.5
bins = 5000
tf = ColorTransferFunction( (mi, ma), bins)
tf.map_to_colormap(mi, ma, colormap="algae", scale_func = scale_func)
ts = TheiaScene(volume = density_grid, raycaster = FrontToBackRaycaster(size = (width, height), tf = tf))
ts.get_raycaster().set_sample_size(0.01)
ts.get_raycaster().set_max_samples(5000)
ts.update()
glutMainLoop()
def scale_func(v, mi, ma):
return np.minimum(1.0, np.abs((v)-ma)/np.abs(mi-ma) + 0.0)
# Print message to console, and kick off the main to get it rolling.
if __name__ == "__main__":
print("Hit ESC key to quit, 'a' to toggle animation, and 'e' to toggle cuda")
main()
Warning
Frame rate will suffer significantly from stereoscopic rendering. ~2x slower since the volume must be rendered twice.
OpenGL Stereoscopic Example:
from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
from OpenGL.GL.ARB.vertex_buffer_object import *
import sys, time
import numpy as np
import pycuda.driver as cuda_driver
import pycuda.gl as cuda_gl
from yt.visualization.volume_rendering.theia.scene import TheiaScene
from yt.visualization.volume_rendering.theia.algorithms.front_to_back import FrontToBackRaycaster
from yt.visualization.volume_rendering.transfer_functions import ColorTransferFunction
from yt.visualization.color_maps import *
import numexpr as ne
window = None # Number of the glut window.
rot_enabled = True
#Theia Scene
ts = None
#RAY CASTING values
c_tbrightness = 1.0
c_tdensity = 0.05
output_texture = None # pointer to offscreen render target
leftButton = False
middleButton = False
rightButton = False
#Screen width and height
width = 1920
height = 1080
eyesep = 0.1
(pbo, pycuda_pbo) = [None]*2
(rpbo, rpycuda_pbo) = [None]*2
#create 2 PBO for stereo scopic rendering
def create_PBO(w, h):
global pbo, pycuda_pbo, rpbo, rpycuda_pbo
num_texels = w*h
array = np.zeros((num_texels, 3),np.float32)
pbo = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, pbo)
glBufferData(GL_ARRAY_BUFFER, array, GL_DYNAMIC_DRAW)
glBindBuffer(GL_ARRAY_BUFFER, 0)
pycuda_pbo = cuda_gl.RegisteredBuffer(long(pbo))
rpbo = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, rpbo)
glBufferData(GL_ARRAY_BUFFER, array, GL_DYNAMIC_DRAW)
glBindBuffer(GL_ARRAY_BUFFER, 0)
rpycuda_pbo = cuda_gl.RegisteredBuffer(long(rpbo))
def destroy_PBO(self):
global pbo, pycuda_pbo, rpbo, rpycuda_pbo
glBindBuffer(GL_ARRAY_BUFFER, long(pbo))
glDeleteBuffers(1, long(pbo));
glBindBuffer(GL_ARRAY_BUFFER, 0)
pbo,pycuda_pbo = [None]*2
glBindBuffer(GL_ARRAY_BUFFER, long(rpbo))
glDeleteBuffers(1, long(rpbo));
glBindBuffer(GL_ARRAY_BUFFER, 0)
rpbo,rpycuda_pbo = [None]*2
#consistent with C initPixelBuffer()
def create_texture(w,h):
global output_texture
output_texture = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, output_texture)
# set basic parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
# buffer data
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
w, h, 0, GL_RGB, GL_FLOAT, None)
#consistent with C initPixelBuffer()
def destroy_texture():
global output_texture
glDeleteTextures(output_texture);
output_texture = None
def init_gl(w = 512 , h = 512):
Width, Height = (w, h)
glClearColor(0.1, 0.1, 0.5, 1.0)
glDisable(GL_DEPTH_TEST)
#matrix functions
glViewport(0, 0, Width, Height)
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
#matrix functions
gluPerspective(60.0, Width/float(Height), 0.1, 10.0)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
def resize(Width, Height):
global width, height
(width, height) = Width, Height
glViewport(0, 0, Width, Height) # Reset The Current Viewport And Perspective Transformation
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(60.0, Width/float(Height), 0.1, 10.0)
def do_tick():
global time_of_last_titleupdate, frame_counter, frames_per_second
if ((time.clock () * 1000.0) - time_of_last_titleupdate >= 1000.):
frames_per_second = frame_counter # Save The FPS
frame_counter = 0 # Reset The FPS Counter
szTitle = "%d FPS" % (frames_per_second )
glutSetWindowTitle ( szTitle )
time_of_last_titleupdate = time.clock () * 1000.0
frame_counter += 1
oldMousePos = [ 0, 0 ]
def mouseButton( button, mode, x, y ):
"""Callback function (mouse button pressed or released).
The current and old mouse positions are stored in
a global renderParam and a global list respectively"""
global leftButton, middleButton, rightButton, oldMousePos
if button == GLUT_LEFT_BUTTON:
if mode == GLUT_DOWN:
leftButton = True
else:
leftButton = False
if button == GLUT_MIDDLE_BUTTON:
if mode == GLUT_DOWN:
middleButton = True
else:
middleButton = False
if button == GLUT_RIGHT_BUTTON:
if mode == GLUT_DOWN:
rightButton = True
else:
rightButton = False
oldMousePos[0], oldMousePos[1] = x, y
glutPostRedisplay( )
def mouseMotion( x, y ):
"""Callback function (mouse moved while button is pressed).
The current and old mouse positions are stored in
a global renderParam and a global list respectively.
The global translation vector is updated according to
the movement of the mouse pointer."""
global ts, leftButton, middleButton, rightButton, oldMousePos
deltaX = x - oldMousePos[ 0 ]
deltaY = y - oldMousePos[ 1 ]
factor = 0.001
if leftButton == True:
ts.camera.rotateX( - deltaY * factor)
ts.camera.rotateY( - deltaX * factor)
if middleButton == True:
ts.camera.translateX( deltaX* 2.0 * factor)
ts.camera.translateY( - deltaY* 2.0 * factor)
if rightButton == True:
ts.camera.scale += deltaY * factor
oldMousePos[0], oldMousePos[1] = x, y
glutPostRedisplay( )
def keyPressed(*args):
global c_tbrightness, c_tdensity, eyesep
# If escape is pressed, kill everything.
if args[0] == '\033':
print('Closing..')
destroy_PBOs()
destroy_texture()
exit()
#change the brightness of the scene
elif args[0] == ']':
c_tbrightness += 0.025
elif args[0] == '[':
c_tbrightness -= 0.025
#change the density scale
elif args[0] == ';':
c_tdensity -= 0.001
elif args[0] == '\'':
c_tdensity += 0.001
#change the transfer scale
elif args[0] == '-':
eyesep -= 0.01
elif args[0] == '=':
eyesep += 0.01
def idle():
glutPostRedisplay()
def display():
try:
#process left eye
process_image()
display_image()
#process right eye
process_image(eye = False)
display_image(eye = False)
glutSwapBuffers()
except:
from traceback import print_exc
print_exc()
from os import _exit
_exit(0)
def process(eye = True):
global ts, pycuda_pbo, rpycuda_pbo, eyesep, c_tbrightness, c_tdensity
""" Use PyCuda """
ts.get_raycaster().set_opacity(c_tdensity)
ts.get_raycaster().set_brightness(c_tbrightness)
if (eye) :
ts.camera.translateX(-eyesep)
dest_mapping = pycuda_pbo.map()
(dev_ptr, size) = dest_mapping.device_ptr_and_size()
ts.get_raycaster().surface.device_ptr = dev_ptr
ts.update()
dest_mapping.unmap()
ts.camera.translateX(eyesep)
else :
ts.camera.translateX(eyesep)
dest_mapping = rpycuda_pbo.map()
(dev_ptr, size) = dest_mapping.device_ptr_and_size()
ts.get_raycaster().surface.device_ptr = dev_ptr
ts.update()
dest_mapping.unmap()
ts.camera.translateX(-eyesep)
def process_image(eye = True):
global output_texture, pbo, rpbo, width, height
""" copy image and process using CUDA """
# run the Cuda kernel
process(eye)
# download texture from PBO
if (eye) :
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, np.uint64(pbo))
glBindTexture(GL_TEXTURE_2D, output_texture)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
width, height, 0,
GL_RGB, GL_FLOAT, None)
else :
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, np.uint64(rpbo))
glBindTexture(GL_TEXTURE_2D, output_texture)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
width, height, 0,
GL_RGB, GL_FLOAT, None)
def display_image(eye = True):
global width, height
""" render a screen sized quad """
glDisable(GL_DEPTH_TEST)
glDisable(GL_LIGHTING)
glEnable(GL_TEXTURE_2D)
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE)
#matix functions should be moved
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glLoadIdentity()
glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0)
glMatrixMode( GL_MODELVIEW)
glLoadIdentity()
glViewport(0, 0, width, height)
if (eye) :
glDrawBuffer(GL_BACK_LEFT)
else :
glDrawBuffer(GL_BACK_RIGHT)
glBegin(GL_QUADS)
glTexCoord2f(0.0, 0.0)
glVertex3f(-1.0, -1.0, 0.5)
glTexCoord2f(1.0, 0.0)
glVertex3f(1.0, -1.0, 0.5)
glTexCoord2f(1.0, 1.0)
glVertex3f(1.0, 1.0, 0.5)
glTexCoord2f(0.0, 1.0)
glVertex3f(-1.0, 1.0, 0.5)
glEnd()
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glDisable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, 0)
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0)
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0)
#note we may need to init cuda_gl here and pass it to camera
def main():
global window, ts, width, height
(width, height) = (1920, 1080)
glutInit(sys.argv)
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_ALPHA | GLUT_DEPTH | GLUT_STEREO)
glutInitWindowSize(*initial_size)
glutInitWindowPosition(0, 0)
window = glutCreateWindow("Stereo Volume Rendering")
glutDisplayFunc(display)
glutIdleFunc(idle)
glutReshapeFunc(resize)
glutMouseFunc( mouseButton )
glutMotionFunc( mouseMotion )
glutKeyboardFunc(keyPressed)
init_gl(width, height)
# create texture for blitting to screen
create_texture(width, height)
import pycuda.gl.autoinit
import pycuda.gl
cuda_gl = pycuda.gl
create_PBO(width, height)
# ----- Load and Set Volume Data -----
density_grid = np.load("/home/bogert/dd150_log_densities.npy")
mi, ma= 21.5, 24.5
bins = 5000
tf = ColorTransferFunction( (mi, ma), bins)
tf.map_to_colormap(mi, ma, colormap="algae", scale_func = scale_func)
ts = TheiaScene(volume = density_grid, raycaster = FrontToBackRaycaster(size = (width, height), tf = tf))
ts.get_raycaster().set_sample_size(0.01)
ts.get_raycaster().set_max_samples(5000)
glutMainLoop()
def scale_func(v, mi, ma):
return np.minimum(1.0, np.abs((v)-ma)/np.abs(mi-ma) + 0.0)
# Print message to console, and kick off the main to get it rolling.
if __name__ == "__main__":
print("Hit ESC key to quit, 'a' to toggle animation, and 'e' to toggle cuda")
main()
Pseudo-Realtime video rendering with ffmpeg:
#This is an example of how to make videos of
#uniform grid data using Theia and ffmpeg
#The Scene object to hold the ray caster and view camera
from yt.visualization.volume_rendering.theia.scene import TheiaScene
#GPU based raycasting algorithm to use
from yt.visualization.volume_rendering.theia.algorithms.front_to_back import FrontToBackRaycaster
#These will be used to define how to color the data
from yt.visualization.volume_rendering.transfer_functions import ColorTransferFunction
from yt.visualization.color_maps import *
#This will be used to launch ffmpeg
import subprocess as sp
#Of course we need numpy for math magic
import numpy as np
#Opacity scaling function
def scale_func(v, mi, ma):
return np.minimum(1.0, (v-mi)/(ma-mi) + 0.0)
#load the uniform grid from a numpy array file
bolshoi = "/home/bogert/log_densities_1024.npy"
density_grid = np.load(bolshoi)
#Set the TheiaScene to use the density_grid and
#setup the raycaster for a resulting 1080p image
ts = TheiaScene(volume = density_grid, raycaster = FrontToBackRaycaster(size = (1920,1080) ))
#the min and max values in the data to color
mi, ma = 0.0, 3.6
#setup colortransferfunction
bins = 5000
tf = ColorTransferFunction( (mi, ma), bins)
tf.map_to_colormap(0.5, ma, colormap="spring", scale_func = scale_func)
#pass the transfer function to the ray caster
ts.source.raycaster.set_transfer(tf)
#Initial configuration for start of video
#set initial opacity and brightness values
#then zoom into the center of the data 30%
ts.source.raycaster.set_opacity(0.03)
ts.source.raycaster.set_brightness(2.3)
ts.camera.zoom(30.0)
#path to ffmpeg executable
FFMPEG_BIN = "/usr/local/bin/ffmpeg"
pipe = sp.Popen([ FFMPEG_BIN,
'-y', # (optional) overwrite the output file if it already exists
#This must be set to rawvideo because the image is an array
'-f', 'rawvideo',
#This must be set to rawvideo because the image is an array
'-vcodec','rawvideo',
#The size of the image array and resulting video
'-s', '1920x1080',
#This must be rgba to match array format (uint32)
'-pix_fmt', 'rgba',
#frame rate of video
'-r', '29.97',
#Indicate that the input to ffmpeg comes from a pipe
'-i', '-',
# Tells FFMPEG not to expect any audio
'-an',
#Setup video encoder
#Use any encoder you life available from ffmpeg
'-vcodec', 'libx264', '-preset', 'ultrafast', '-qp', '0',
'-pix_fmt', 'yuv420p',
#Name of the output
'bolshoiplanck2.mkv' ],
stdin=sp.PIPE,stdout=sp.PIPE)
#Now we loop and produce 500 frames
for k in range (0,500) :
#update the scene resulting in a new image
ts.update()
#get the image array from the ray caster
array = ts.source.get_results()
#send the image array to ffmpeg
array.tofile(pipe.stdin)
#rotate the scene by 0.01 rads in x,y & z
ts.camera.rotateX(0.01)
ts.camera.rotateZ(0.01)
ts.camera.rotateY(0.01)
#zoom in 0.01% for a total of a 5% zoom
ts.camera.zoom(0.01)
#Close the pipe to ffmpeg
pipe.terminate()