Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,5 @@ target/
# python virtual env
venv/

examples/out.png
examples/*.perf
examples/*/out.png
examples/*/*.perf
30 changes: 25 additions & 5 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
# Example implementations using python-pathfinding

## *image_pathfinding.py*
Create a map from and image and run a path finding algorithm on it (requires PIL/Pillow).
## Image Simple

You can all it with an input and output file like this:
Create a map from an image and run a path finding algorithm on it. Requires: `pip install pillow`.

You can run it with an input and output file like this:
```
cd examples/
cd examples/image-simple/
python3 image_pathfinding.py -i map.png -o foo.png
```
```

----

## Image Weighted

Create a map from and image and run a path finding algorithm on it. Requires: `pip install pillow`.

It maps specific colors to their weights. Make sure to update the value-mapping for your custom input-maps!

You can run it with an input and output file like this:

```
cd examples/image-weighted/
python3 image_pathfinding.py -i map.png -o foo.png
```

To add some randomization to the weights - you can use the `-r` flag: `python3 image_pathfinding.py -i map.png -o foo.png -r 0.5`

Note: The terrain-map was generated with OpenSimplex-noise via [this Python script](https://github.com/O-X-L/opensimplex).
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ def pixel_walkable(pixel, x, y):
return any([p > 50 for p in pixel]) # darker pixel are not walkable


def main(filename_map: str = MAP_FILE, filename_out: str = OUT_FILE):
def main(filename_map: str = MAP_FILE, filename_out: str = OUT_FILE, diagonal_movement: bool = False):
nodes = []
if not Path(filename_map).exists():
print(f'File {filename_map} does not exist.')
return

print('Parsing map..')
with Image.open(filename_map) as im:
width, height = im.size
for y in range(height):
Expand All @@ -56,11 +58,14 @@ def main(filename_map: str = MAP_FILE, filename_out: str = OUT_FILE):
end = grid.node(*_goal)
start = grid.node(*_start)

finder = AStarFinder(diagonal_movement=DiagonalMovement.never)
print('Finding optimal path..')
finder = AStarFinder(diagonal_movement=DiagonalMovement.always if diagonal_movement else DiagonalMovement.never)
path, runs = finder.find_path(start, end, grid)

# print(grid.grid_str(path=path, end=end, start=start))
print('iterations:', runs, 'path length:', len(path))
print(f'iterations: {runs:_} path length: {len(path):_}')

print('Saving image..')
out = im.copy()
for p in path[1:-1]:
out.putpixel((p.x, p.y), (255, 165, 0))
Expand All @@ -79,5 +84,9 @@ def main(filename_map: str = MAP_FILE, filename_out: str = OUT_FILE):
'-o', '--filename_out',
help='output file',
default=OUT_FILE)
parser.add_argument(
'-d', '--diagonal-movement',
help='allow for diagonal movement',
action='store_true')

main(**vars(parser.parse_args()))
File renamed without changes
108 changes: 108 additions & 0 deletions examples/image-weighted/image_pathfinding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Create a Map from an image
import os
from pathlib import Path
import argparse
from random import uniform as random_float

# Pillow
from PIL import Image

# pathfinding
from pathfinding.core.diagonal_movement import DiagonalMovement
from pathfinding.core.grid import Grid
from pathfinding.finder.a_star import AStarFinder


# image file with the map
BASE_PATH = Path(os.path.dirname(__file__))
MAP_FILE = BASE_PATH / "map.png"
OUT_FILE = BASE_PATH / "out.png"

# this script searches for specific colors in RGB-format
COLOR_START = (255, 255, 0) # yellow
COLOR_END = (255, 0, 0) # red
COLOR_PATH = (255, 165, 0) # orange
COLOR_WEIGHT_MAPPING = {
(0, 62, 178): 10, # deep water
(9, 82, 198): 3, # water
(254, 224, 179): 1, # sand
(9, 120, 93): 2, # grass
(10, 107, 72): 3, # bushes
(11, 94, 51): 4, # forest
(140, 142, 123): 5, # hills
(160, 162, 143): 10, # alpine
(53, 54, 68): 15, # steep cliff
(255, 255, 255): 10, # snow
}


def main(
filename_map: str = MAP_FILE, filename_out: str = OUT_FILE,
weight_randomization: float = 0, diagonal_movement: bool = False,
):
nodes = []
if not Path(filename_map).exists():
print(f'File {filename_map} does not exist.')
return

print('Parsing map..')
with Image.open(filename_map) as im:
width, height = im.size
for y in range(height):
nodes.append([])
for x in range(width):
pixel = im.getpixel((x, y))
weight = COLOR_WEIGHT_MAPPING.get(pixel, 1)

if weight_randomization != 0:
weight += random_float(0, weight_randomization)

nodes[y].append(weight)

if pixel == COLOR_END:
_goal = (x, y)
elif pixel == COLOR_START:
_start = (x, y)

grid = Grid(matrix=nodes)
end = grid.node(*_goal)
start = grid.node(*_start)

print('Finding optimal path..')
finder = AStarFinder(diagonal_movement=DiagonalMovement.always if diagonal_movement else DiagonalMovement.never)
path, runs = finder.find_path(start, end, grid)

# print(grid.grid_str(path=path, end=end, start=start))
print(f'iterations: {runs:_} path length: {len(path):_}')

print('Saving image..')
out = im.copy()
for p in path[1:-1]:
out.putpixel((p.x, p.y), COLOR_PATH)
out.save(filename_out)


if __name__ == '__main__':
parser = argparse.ArgumentParser(
prog='image_pathfinding',
description='find a path in an image from a yellow pixel (rgb: 255,255,0) to a red one (rgb: 255,0,0) '
'with weighted-tiles')
parser.add_argument(
'-i', '--filename_map',
help='input file',
default=MAP_FILE)
parser.add_argument(
'-o', '--filename_out',
help='output file',
default=OUT_FILE)
parser.add_argument(
'-r', '--weight-randomization',
help='how much randomization should be added to the tile-weights (disabled by default)',
type=float,
default=0)
parser.add_argument(
'-d', '--diagonal-movement',
help='allow for diagonal movement',
action='store_true')

main(**vars(parser.parse_args()))
Binary file added examples/image-weighted/map.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.