Extra: Viz

There are a few utility functions used throughout this jupyter notebook to visualize our grid. We will discuss them briefly here.

In [3]:
%reload_ext autoreload
%autoreload 2

import sys
import numpy as np
from pathfinding import grid, utils, finder
from pathfinding.grid import core, viz

# we do this so np print won't truncate
np.set_printoptions(threshold=sys.maxsize)

# let's alias core.find_walkable_neighbors to fwn for brevity
fwn = core.find_walkable_neighbors

Text

The first one we see is generate_text, which pretty prints our grid.

In [4]:
polygons = [[(14, 5), (14, 7), (16, 7), (16, 5)]]
maze, start, end = grid.generate(20,10, polygons), (2, 2), (20 - 3, 10 - 3)
path, expansion = finder.bfs(maze, fwn, start, end, with_expansion=True)

print(viz.generate_text(maze, start, end, path))
[[= = = = = = = = = = = = = = = = = = = =]
 [=                                     =]
 [=   S                                 =]
 [=   *                                 =]
 [=   * * * * * * * * * * * * * * * *   =]
 [=                           = = = *   =]
 [=                           =   = *   =]
 [=                           = = = E   =]
 [=                                     =]
 [= = = = = = = = = = = = = = = = = = = =]]

For generate_text, we only copy the grid/list and replae the values with our own visual text representation (i.e. space for 1)

In [5]:
def generate_text_map(x):
    if x == 0:
        return "="
    elif x == 1:
        return " "
    if x == 4:
        return "*"
    if x == 5:
        return "@"
    elif x == 2:
        return "S"
    elif x == 3:
        return "E"
    else:
        return str(x)

def generate_text(grid, start, end, path=[], expansion=[]):
    maze = np.copy(grid)

    for coord in expansion:
        maze[coord[1]][coord[0]] = 5

    for coord in path:
        maze[coord[1]][coord[0]] = 4

    maze[start[1]][start[0]] = 2
    maze[end[1]][end[0]] = 3

    return np.array2string(maze, formatter={'int': generate_text_map})

Image

Image is a bit more involved, but not much harder. Here, we simply create a RGB map from our grid/maze data, and create an image from our rgb array.

In [6]:
def generate_image(grid, start, end, path=[], expansion=[]):
    r = g = b = (grid * 255).astype(np.uint8)
    img_array = np.empty((grid.shape[0], grid.shape[1], 3), dtype=np.uint8)
    img_array[..., 0] = r
    img_array[..., 1] = g
    img_array[..., 2] = b
    for coord in expansion:
        img_array[coord[1], coord[0]] = [200, 200, 200]
    for coord in path:
        img_array[coord[1], coord[0]] = [254, 254, 59]
    # display start
    img_array[start[1], start[0]] = [0, 255, 0]
    # display end
    img_array[end[1], end[0]] = [255, 0, 0]
    return img_array

def render_image(img_array, display_size=(1000, 500)):
    return display(Image.fromarray(img_array, 'RGB').resize(display_size))
In [8]:
polygons = [[(14, 5), (14, 7), (16, 7), (16, 5)]]
maze, start, end = grid.generate(20,10, polygons), (2, 2), (20 - 3, 10 - 3)
path, expansion = finder.bfs(maze, fwn, start, end, with_expansion=True)

utils.render_image(viz.generate_image(maze, start, end, path))

Animation

Animation involves the most 'hacks' but not by much. Here, we simply use matplotlib's FuncAnimation and loop through drawing patches.Rectangle

Note that since most of our algorithms have really large number of nodes expanded, we have to be smart and bundle some nodes together in frames, otherwise it would take a really long time to generate an animation (hence the frames argument)

In [9]:
def generate_anim(grid, start, end, path=[], expansion=[], frames=15):
    fig, ax = plt.subplots()
    fig.subplots_adjust(left=0, right=1, bottom=0, top=1)

    img = generate_image(grid, start, end, path, expansion[:1])
    ax.imshow(img)

    squares = [Rectangle((coord[0] - 0.5, coord[1] - 0.5), 1, 1, color='black') for coord in expansion]
    for sq in squares:
        sq.set_alpha(0)
        ax.add_patch(sq)

    multiple = int(len(expansion) / frames)

    def init():
        return squares

    def update(i):
        for j in range(i * multiple, (i + 1) * multiple):
            squares[j].set_alpha(0.3)
        return squares

    return animation.FuncAnimation(fig, update, frames, blit=True, interval=100, init_func=init)
In [12]:
polygons = [[(14, 5), (14, 7), (16, 7), (16, 5)]]
maze, start, end = grid.generate(20,10, polygons), (2, 2), (20 - 3, 10 - 3)
path, expansion = finder.bfs(maze, fwn, start, end, with_expansion=True)

utils.render_anim(viz.generate_anim(maze, start, end, path, expansion, frames=len(expansion)))
Out[12]:


Once Loop Reflect

That's it, thanks for reading!

Back to Index


This is a post in the CS 180 Pathfinding series.
Other posts in this series:

Arian Valdez @Secretmapper

React.JS and Node.JS Software engineering consultant. Developer/Designer Hybrid. Author of Alt Tracker, Combustion, Riyu, etc.