Top-down Renderer

Click and Open In Colab

2D Top-down renderer is widely used in this documentation for rendering the results, as it is lightweight and can run on any platforms without GPU requirement. If your system has poor support for OpenGL like Apple M1/M2 chips, a good choice is to use top-down renderer. And the simulation results are exactly the same using either 3D renderer or top-down renderer.

Lifetime

You are free to launch this renderer at any timestep by calling env.render(mode="topdown"). The renderer will be created and work until the env.reset() is called. It will shutdown the top_down_renderer and destroy it. Thus the lifetime of a renderer is the period between calling env.render for the first time and executing next env.reset.

The following example running an environment for 100 steps. It launches the renderer when episode_step=50, and thus the generated gif only records the last 50 frames. Also, it demonstrate how to record screen and generate gif.

from metadrive.envs import MetaDriveEnv
from IPython.display import Image
from metadrive.utils import print_source, get_source
import cv2
env = MetaDriveEnv(dict(log_level=50))
env.reset()
for i in range(100):
    env.step([0,0])
    if i>=50:
        env.render(mode="topdown",
                   window=False,
                   screen_size=(400, 200),
                   screen_record=True,
                   text={"Step": i})
env.top_down_renderer.generate_gif()
print("Before reset the renderer is", env.top_down_renderer)
env.reset()
print("After reset the renderer is", env.top_down_renderer)

env.close()

Image(open("demo.gif", 'rb').read())
error: XDG_RUNTIME_DIR not set in the environment.
Before reset the renderer is <metadrive.engine.top_down_renderer.TopDownRenderer object at 0x7fde466a6e50>
After reset the renderer is None
_images/1283d2aeac00eb603a02d20c78bbbdbd198bbdcb87c249e451f9db452ac79156.png

Configs

The env.render() accepts parameters like screen_size, window and so on as input which defines the behavior of the top-down renderer. Note that these parameters only take effect when you call env.render for the first time in one episode.

All accepted arguments for creating the top-down renderer are as follows.

from metadrive.engine.top_down_renderer import TopDownRenderer
from metadrive.utils import CONFIG, FUNC_2
print_source(TopDownRenderer.__init__, ["def", "# doc-end"], colorscheme=FUNC_2)
def __init__(
        self,
        film_size=(2000, 2000),  # draw map in size = film_size/scaling. By default, it is set to 400m
        scaling=5,  # None for auto-scale
        screen_size=(800, 800),
        num_stack=15,
        history_smooth=0,
        show_agent_name=False,
        camera_position=None,
        target_agent_heading_up=False,
        target_vehicle_heading_up=None,
        draw_target_vehicle_trajectory=False,
        semantic_map=False,
        semantic_broken_line=True,
        draw_contour=True,
        window=True,
        screen_record=False,
    ):
        """
        Launch a top-down renderer for current episode. Usually, it is launched by env.render(mode="topdown") and will
        be closed when next env.reset() is called or next episode starts.
        Args:
            film_size: The size of the film used to draw the map. The unit is pixel. It should cover the whole map to
            ensure it is complete in the rendered result. It works with the argument scaling to select the region
            to draw. For example, (2000, 2000) film size with scaling=5 can draw any maps whose width and height
            less than 2000/5 = 400 meters.

            scaling: The scaling determines how many pixels are used to draw one meter.

            screen_size: The size of the window popped up. It shows a region with width and length = screen_size/scaling

            num_stack: How many history steps to keep. History trajectory will show in faded color. It should be > 1

            history_smooth: Smoothing the trajectory by drawing less history positions. This value determines the sample
            rate. By default, this value is 0, meaning positions in previous num_stack steps will be shown.

            show_agent_name: Draw the name of the agent.

            camera_position: Set the (x,y) position of the top_down camera. If it is not specified, the camera will move
            with the ego car.

            target_agent_heading_up: Whether to rotate the camera according to the ego agent's heading. When enabled,
            The ego car always faces upwards.

            target_vehicle_heading_up: Deprecated, use target_agent_heading_up instead!

            draw_target_vehicle_trajectory: Whether to draw the ego car's whole trajectory without faded color

            semantic_map: Whether to draw semantic color for each object. The color scheme is in TopDownSemanticColor.

            semantic_broken_line: Whether to draw broken line for semantic map

            draw_contour: Whether to draw a counter for objects

            window: Whether to pop up the window. Setting it to 'False' enables off-screen rendering

            screen_record: Whether to record the episode. The recorded result can be accessed by
            env.top_down_renderer.screen_frames or env.top_down_renderer.generate_gif(file_name, fps)
        """
        #

Region Size in Screen

If you wanna adjust the region size shown on the screen/window, change scaling to a reasonable value. The region size in meter is determined by screen_size[0]/scaling and screen_size[1]/scaling. For example, if your screen size is (1200, 800) and scaling is 5, then it draws a 240m x 160m region.

To demonstrate this, The following example draws exactly the same region with different screen_size and scaling.

env = MetaDriveEnv(dict(log_level=50, num_scenarios=1, map="X"))

env.reset()
frame_1 = env.render(mode="topdown", window=False, camera_position=(50, 7.5),
       screen_size=(400, 200), scaling=4)

env.reset()
frame_2 = env.render(mode="topdown", window=False, camera_position=(50, 7.5),
       screen_size=(200, 100), scaling=2)

env.reset()
frame_3 = env.render(mode="topdown", window=False, camera_position=(50, 7.5),
       screen_size=(100, 50), scaling=1)

env.reset()
frame_4 = env.render(mode="topdown", window=False, camera_position=(50, 7.5),
   screen_size=(200, 100), scaling=1)

env.close()

import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, figsize=(10, 5)) # You can adjust the figsize as needed
axes[0][0].imshow(frame_1)
axes[0][0].axis('off')  # Turn off axis
axes[0][0].set_title("screen_size=(400, 200), scaling=4")
axes[0][1].imshow(frame_2)
axes[0][1].axis('off')  # Turn off axis
axes[0][1].set_title("screen_size=(200, 100), scaling=2")
axes[1][0].imshow(frame_3)
axes[1][0].axis('off')  # Turn off axis
axes[1][0].set_title("screen_size=(100, 50), scaling=1")
axes[1][1].imshow(frame_4)
axes[1][1].axis('off')  # Turn off axis
axes[1][1].set_title("screen_size=(200, 100), scaling=1")
plt.subplots_adjust(wspace=0.05)
plt.show()
_images/e4c9a8d98da5245afb71598891ba2068b228fdb232a051f529b4b5988fe7fc2a.png

Map Region Size

The Map region size is determined by film_size and scaling like how to determine the region shown in window. Users have to make sure the map region size exceeds the actual map size to make sure the map is shown complete. Usually, maps in MetaDrive are smaller than 400m x 400m. Thus the default film_size=(2000, 2000) and scaling=5 are able to handle most cases.

If you find the map in top-down rendering is incomplete, consider increase the film_size or decrease the scaling. The following example shows what will happen if the film_size is too small.

env = MetaDriveEnv(dict(log_level=50, num_scenarios=1, map="X"))

env.reset()
frame_1 = env.render(mode="topdown", window=False, camera_position=(50, 7.5),
       screen_size=(800, 400), scaling=4, film_size=(200, 200))
map_1 = env.top_down_renderer.get_map()

env.reset()
frame_2 = env.render(mode="topdown", window=False, camera_position=(50, 7.5),
       screen_size=(800, 400), scaling=4, film_size=(400, 400))
map_2 = env.top_down_renderer.get_map()

env.close()

import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, figsize=(10, 5))
axes[0][0].imshow(map_1)
axes[0][0].axis('off')  # Turn off axis
axes[0][0].set_title("Map region 50m x 50m")
axes[0][1].imshow(map_2)
axes[0][1].axis('off')  # Turn off axis
axes[0][1].set_title("Map region 100m x 100m")

axes[1][0].imshow(frame_1)
axes[1][0].axis('off')  # Turn off axis
axes[1][0].set_title("Rendering result")
axes[1][1].imshow(frame_2)
axes[1][1].axis('off')  # Turn off axis
axes[1][1].set_title("Rendering result")

plt.subplots_adjust(wspace=0.05)
plt.show()
_images/a9c3bafdc18e383214fcb12a2704c5de473d060e18b9adb79835d3ea609cbbe3.png

Semantic Top-down View

The top-down view can be changed to semantic view by adding semantic_map=True when creating the renderer.

from metadrive.envs import ScenarioEnv

env = ScenarioEnv(dict(log_level=50, 
                        num_scenarios=2))

env.reset(seed=0)
frame_1 = env.render(mode="topdown", window=False,
                     screen_size=(800, 800), scaling=5)

env.reset(seed=0)
frame_2 = env.render(mode="topdown", window=False,
                     screen_size=(800, 800), scaling=5, semantic_map=True)

env.reset(seed=1)
frame_3 = env.render(mode="topdown", window=False,
                     screen_size=(800, 800), scaling=5)

env.reset(seed=1)
frame_4 = env.render(mode="topdown", window=False,
                     screen_size=(800, 800), scaling=5, semantic_map=True)

env.close()

import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, figsize=(10, 10)) # You can adjust the figsize as needed
axes[0][0].imshow(frame_1)
axes[0][0].axis('off')  # Turn off axis
axes[0][0].set_title("Seed: 0, Normal")
axes[0][1].imshow(frame_2)
axes[0][1].axis('off')  # Turn off axis
axes[0][1].set_title("Seed: 0, Semantic View")
axes[1][0].imshow(frame_3)
axes[1][0].axis('off')  # Turn off axis
axes[1][0].set_title("Seed: 1, Normal")
axes[1][1].imshow(frame_4)
axes[1][1].axis('off')  # Turn off axis
axes[1][1].set_title("Seed: 1, Semantic View")
plt.subplots_adjust(wspace=0.)
plt.show()
_images/1bc23c63b9e0e52c758897d4e16adb10c0e8b5da26064e60c5e2036ac408c03b.png