174 lines
5.2 KiB
Python
174 lines
5.2 KiB
Python
import shelve
|
|
from pathlib import Path
|
|
from collections import UserDict
|
|
|
|
|
|
import copy
|
|
from math import sqrt
|
|
from random import choice
|
|
|
|
import numpy as np
|
|
|
|
from PIL import Image, ImageDraw
|
|
import networkx as nx
|
|
from matplotlib import pyplot as plt
|
|
|
|
from lib.objects.trajectory import Trajectory
|
|
|
|
|
|
class Map(object):
|
|
|
|
# This setting is for Img mode "L" aka GreyScale Image; values: 0-255
|
|
white = 255
|
|
black = 0
|
|
|
|
def __copy__(self):
|
|
return copy.deepcopy(self)
|
|
|
|
@property
|
|
def shape(self):
|
|
return self.map_array.shape
|
|
|
|
@property
|
|
def width(self):
|
|
return self.shape[0]
|
|
|
|
@property
|
|
def height(self):
|
|
return self.shape[1]
|
|
|
|
@property
|
|
def as_graph(self):
|
|
return self._G
|
|
|
|
@property
|
|
def as_array(self):
|
|
return self.map_array
|
|
|
|
def __init__(self, name='', array_like_map_representation=None):
|
|
self.map_array: np.ndarray = array_like_map_representation
|
|
self.name = name
|
|
pass
|
|
|
|
def __setattr__(self, key, value):
|
|
super(Map, self).__setattr__(key, value)
|
|
if key == 'map_array' and self.map_array is not None:
|
|
self._G = self._build_graph()
|
|
|
|
def _build_graph(self, full_neighbors=True):
|
|
graph = nx.Graph()
|
|
# Do checks in order: up - left - upperLeft - lowerLeft
|
|
neighbors = [(0, -1, 1), (-1, 0, 1), (-1, -1, sqrt(2)), (-1, 1, sqrt(2))]
|
|
|
|
# Check pixels for their color (determine if walkable)
|
|
for idx, value in np.ndenumerate(self.map_array):
|
|
if value == self.white:
|
|
try:
|
|
y, x = idx
|
|
except ValueError:
|
|
y, x, channels = idx
|
|
idx = (y, x)
|
|
# IF walkable, add node
|
|
graph.add_node((y, x), count=0)
|
|
# Fully connect to all surrounding neighbors
|
|
for n, (xdif, ydif, weight) in enumerate(neighbors):
|
|
# Differentiate between 8 and 4 neighbors
|
|
if not full_neighbors and n >= 2:
|
|
break
|
|
|
|
query_node = (y + ydif, x + xdif)
|
|
if graph.has_node(query_node):
|
|
graph.add_edge(idx, query_node, weight=weight)
|
|
return graph
|
|
|
|
@classmethod
|
|
def from_image(cls, imagepath: Path):
|
|
with Image.open(imagepath) as image:
|
|
# Turn the image to single Channel Greyscale
|
|
if image.mode != 'L':
|
|
image = image.convert('L')
|
|
map_array = np.array(image)
|
|
return cls(name=imagepath.name, array_like_map_representation=map_array)
|
|
|
|
def simple_trajectory_between(self, start, dest):
|
|
vertices = list(nx.shortest_path(self._G, start, dest))
|
|
trajectory = Trajectory(vertices)
|
|
return trajectory
|
|
|
|
def get_valid_position(self):
|
|
valid_position = choice(list(self._G.nodes))
|
|
return valid_position
|
|
|
|
def get_trajectory_from_vertices(self, *args):
|
|
coords = list()
|
|
for start, dest in zip(args[:-1], args[1:]):
|
|
coords.extend(nx.shortest_path(self._G, start, dest))
|
|
return Trajectory(coords)
|
|
|
|
def get_random_trajectory(self):
|
|
start = self.get_valid_position()
|
|
dest = self.get_valid_position()
|
|
return self.simple_trajectory_between(start, dest)
|
|
|
|
def generate_alternative(self, trajectory, mode='one_patching'):
|
|
start, dest = trajectory.endpoints
|
|
if mode == 'one_patching':
|
|
patch = self.get_valid_position()
|
|
alternative = self.get_trajectory_from_vertices(start, patch, dest)
|
|
else:
|
|
raise RuntimeError(f'mode checking went wrong...')
|
|
|
|
return alternative
|
|
|
|
def are_homotopic(self, trajectory, other_trajectory):
|
|
if not all(isinstance(x, Trajectory) for x in [trajectory, other_trajectory]):
|
|
raise TypeError
|
|
polyline = trajectory.vertices.copy()
|
|
polyline.extend(reversed(other_trajectory.vertices))
|
|
|
|
img = Image.new('L', (self.height, self.width), 0)
|
|
draw = ImageDraw.Draw(img)
|
|
draw.polygon(polyline, outline=255, fill=255)
|
|
|
|
a = (np.array(img) * np.where(self.map_array == self.white, 0, 1)).sum()
|
|
|
|
if a >= 1:
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
def draw(self):
|
|
fig, ax = plt.gcf(), plt.gca()
|
|
# The standard colormaps also all have reversed versions.
|
|
# They have the same names with _r tacked on to the end.
|
|
# https: // matplotlib.org / api / pyplot_summary.html?highlight = colormaps
|
|
img = ax.imshow(self.as_array, cmap='Greys_r')
|
|
return dict(img=img, fig=fig, ax=ax)
|
|
|
|
|
|
class MapStorage(object):
|
|
|
|
def __init__(self, map_root, load_all=False):
|
|
self.data = dict()
|
|
self.map_root = Path(map_root)
|
|
if load_all:
|
|
for map_file in self.map_root.glob('*.bmp'):
|
|
_ = self[map_file.name]
|
|
|
|
def __getitem__(self, item):
|
|
if item in hasattr(self, item):
|
|
return self.__getattribute__(item)
|
|
else:
|
|
with shelve.open(self.map_root / f'{item}.pik', flag='r') as d:
|
|
self.__setattr__(item, d['map']['map'])
|
|
return self[item]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|