diff --git a/docs/examples/_valid_examples.toml b/docs/examples/_valid_examples.toml index e5ecfb142..5e4e4f6be 100644 --- a/docs/examples/_valid_examples.toml +++ b/docs/examples/_valid_examples.toml @@ -59,6 +59,7 @@ files = [ "viz_emwave_animation.py", "viz_helical_motion.py", "viz_play_video.py", + "viz_play_cube.py", "viz_dt_ellipsoids.py" ] diff --git a/docs/examples/viz_play_cube.py b/docs/examples/viz_play_cube.py new file mode 100644 index 000000000..2d5ef6455 --- /dev/null +++ b/docs/examples/viz_play_cube.py @@ -0,0 +1,108 @@ +""" +======================================================= +Play video on a Cube +======================================================= + +The goal of this demo is to show how to visualize a video +on a cube by updating its textures. +""" + +from fury import actor, window +import numpy as np +import cv2 + +######################################################################### +# The 6 sources for the video, can be URL or directory paths on your machine. +# There'll be a significant delay if your internet connectivity is poor, +# use local directory paths for fast rendering. +sources = [ + 'https://commondatastorage.googleapis.com/gtv-videos-bucket/' + + 'sample/BigBuckBunny.mp4', + 'https://commondatastorage.googleapis.com/gtv-videos-bucket/' + + 'sample/BigBuckBunny.mp4', + 'https://commondatastorage.googleapis.com/gtv-videos-bucket/' + + 'sample/BigBuckBunny.mp4', + 'https://commondatastorage.googleapis.com/gtv-videos-bucket/' + + 'sample/BigBuckBunny.mp4', + 'https://commondatastorage.googleapis.com/gtv-videos-bucket/' + + 'sample/BigBuckBunny.mp4', + 'https://commondatastorage.googleapis.com/gtv-videos-bucket/' + + 'sample/BigBuckBunny.mp4' +] + +######################################################################### +# We are creating ``OpenCV videoCapture`` objects to capture frames from +# sources. +video_captures = [cv2.VideoCapture(source) for source in sources] + +# rgb_images will store the RGB values of the frames. +rgb_images = [] +for video_capture in video_captures: + _, bgr_image = video_capture.read() + + # OpenCV reads in BGR, we are converting it to RGB. + rgb_image = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2RGB) + rgb_images.append(rgb_image) + + +######################################################################## +# ``timer_callback`` gets called repeatedly to change texture. + +def timer_callback(_caller, _timer_event): + rgb_images = [] + for video_capture in video_captures: + # Taking the new frames + _, bgr_image = video_capture.read() + + # Condition used to stop rendering when the smallest video is over. + if isinstance(bgr_image, np.ndarray): + rgb_image = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2RGB) + rgb_images.append(rgb_image) + else: + show_manager.exit() + return + + for actor_, image in zip(cube, rgb_images): + # texture_update is a function to update the texture of an actor + actor.texture_update(actor_, image) + + # you've to re-render the pipeline again to display the results + show_manager.render() + +####################################################################### +# ``texture_on_cube`` is the function we use, the images are assigned in +# cubemap order. + + +""" + |----| + | +Y | +|----|----|----|----| +| -X | +Z | +X | -Z | +|----|----|----|----| + | -Y | + |----| +""" +###################################################################### + + +cube = actor.texture_on_cube(*rgb_images, centers=(0, 0, 0)) + +# adding the returned Actors to scene +scene = window.Scene() +scene.add(*cube) + +###################################################################### +# ``ShowManager`` controls the frequency of changing textures. +# The video is rendered by changing textures very frequently. +show_manager = window.ShowManager(scene, size=(1280, 720), reset_camera=False) +show_manager.add_timer_callback(True, int(1/60), timer_callback) + + +###################################################################### +# Flip it to ``True`` for video. +interactive = False +if interactive: + show_manager.start() + +window.record(scene, size=(1280, 720), out_path='viz_play_cube.png') diff --git a/fury/actor.py b/fury/actor.py index 9f80299e1..ec2c70bb5 100644 --- a/fury/actor.py +++ b/fury/actor.py @@ -25,6 +25,7 @@ ConeSource, ContourFilter, CylinderSource, + PlaneSource, DiskSource, FloatArray, Follower, @@ -74,6 +75,7 @@ get_actor_from_primitive, lines_to_vtk_polydata, numpy_to_vtk_colors, + numpy_to_vtk_image_data, repeat_sources, rgb_to_vtk, set_input, @@ -3954,3 +3956,97 @@ def uncertainty_cone( angles = main_dir_uncertainty(evals, evecs, signal, sigma, b_matrix) return double_cone(centers, evecs, angles, colors, scales, opacity) + + +def texture_on_cube(negx, negy, negz, posx, posy, posz, centers=(0, 0, 0)): + """Map RGB or RGBA textures on a cube + + Parameters + ---------- + negx : ndarray + Input 2D RGB or RGBA array. + negy : ndarray + Input 2D RGB or RGBA array. + negz : ndarray + Input 2D RGB or RGBA array. + posx : ndarray + Input 2D RGB or RGBA array. + posy : ndarray + Input 2D RGB or RGBA array. + posz : ndarray + Input 2D RGB or RGBA array. + centers : tuple (3,), optional + The X, Y and Z coordinate of the cube. + + |----| + | +Y | + |----|----|----|----| + | -X | +Z | +X | -Z | + |----|----|----|----| + | -Y | + |----| + + Returns + ------- + actors : list[Actor] + A list of 6 Actor objects, one for each face of the cube, in order. + + """ + plane_objects = [PlaneSource() for _ in range(6)] + + center_x, center_y, center_z = centers + plane_centers = [ + (-0.5 + center_x, 0 + center_y, 0 + center_z), + (0 + center_x, -0.5 + center_y, 0 + center_z), + (0 + center_x, 0 + center_y, -0.5 + center_z), + (0.5 + center_x, 0 + center_y, 0 + center_z), + (0 + center_x, 0.5 + center_y, 0 + center_z), + (0 + center_x, 0 + center_y, 0.5 + center_z) + ] + + plane_normals = [ + (-1, 0, 0), + (0, -1, 0), + (0, 0, -1), + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + + for plane, center, normal in zip( + plane_objects, + plane_centers, + plane_normals + ): + plane.SetCenter(*center) + plane.SetNormal(*normal) + + image_grids = [negx, negy, negz, posx, posy, posz] + image_data_objects = [ + numpy_to_vtk_image_data(grid) for grid in image_grids + ] + + texture_objects = [Texture() for _ in range(6)] + for image_data, texture in zip( + image_data_objects, + texture_objects + ): + texture.SetInputDataObject(image_data) + + polydatamapper_objects = [PolyDataMapper() for _ in range(6)] + for mapper, plane in zip( + polydatamapper_objects, + plane_objects + ): + mapper.SetInputConnection(plane.GetOutputPort()) + + actor_objects = [Actor() for _ in range(6)] + for actor, mapper, texture in zip( + actor_objects, + polydatamapper_objects, + texture_objects + ): + actor.SetMapper(mapper) + actor.SetTexture(texture) + + return actor_objects diff --git a/fury/lib.py b/fury/lib.py index dc0bf6a47..b9279f63c 100644 --- a/fury/lib.py +++ b/fury/lib.py @@ -228,6 +228,8 @@ TexturedSphereSource = fsvtk.vtkTexturedSphereSource #: class for RegularPolygonSource RegularPolygonSource = fsvtk.vtkRegularPolygonSource +#: class for PlaneSource +PlaneSource = fsvtk.vtkPlaneSource ############################################################## # vtkCommonDataModel Module diff --git a/fury/tests/test_actors.py b/fury/tests/test_actors.py index 318a2df4e..691b96bd3 100644 --- a/fury/tests/test_actors.py +++ b/fury/tests/test_actors.py @@ -11,6 +11,7 @@ from fury.actor import grid from fury.decorators import skip_linux, skip_osx, skip_win from fury.deprecator import ExpiredDeprecationError +from fury.lib import Actor # Allow import, but disable doctests if we don't have dipy from fury.optpkg import optional_package @@ -1879,3 +1880,37 @@ def test_actors_primitives_count(): primitives_count = test_case[2] act = act_func(**args) npt.assert_equal(primitives_count_from_actor(act), primitives_count) + + +def test_texture_on_cube(interactive=False): + arr_255 = np.full((720, 1280), 255, dtype=np.uint8) + arr_0 = np.full((720, 1280), 0, dtype=np.uint8) + + arr_white = np.full((720, 1280, 3), 255, dtype=np.uint8) + arr_red = np.dstack((arr_255, arr_0, arr_0)) + arr_green = np.dstack((arr_0, arr_255, arr_0)) + arr_blue = np.dstack((arr_0, arr_0, arr_255)) + arr_yellow = np.dstack((arr_255, arr_255, arr_0)) + arr_aqua = np.dstack((arr_0, arr_255, arr_255)) + + cube = actor.texture_on_cube(arr_white, + arr_red, + arr_green, + arr_blue, + arr_yellow, + arr_aqua, + (1, 2, 3)) + + # testing whether 6 VTK Planes are returned + assert len(cube) == 6 and isinstance(cube[0], Actor) + + # testing whether the colors render as required (testing only 1) + scene = window.Scene() + scene.add(cube[5]) + + pic1 = window.snapshot(scene) + res1 = window.analyze_snapshot(im=pic1, colors=[ + (0, 255, 255), (255, 255, 255) + ]) + + npt.assert_equal(res1.colors_found, [True, False])