diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..ec889b9 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Current File", + "type": "python", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": true, + "args": ["-m", "dijkstra", "-i", "small.png"] + } + ] +} diff --git a/FibonacciHeap.py b/FibonacciHeap.py index 0a01f99..d9971d3 100644 --- a/FibonacciHeap.py +++ b/FibonacciHeap.py @@ -11,6 +11,18 @@ def __init__(self, key, value): self.parent = self.child = None self.previous = self.next = self + def __lt__(self, other) -> bool: + if self.key < other.key: + return True + else: + return False + + def __le__(self, other) -> bool: + if self.key <= other.key: + return True + else: + return False + def issingle(self): return self == self.next @@ -23,7 +35,6 @@ def insert(self, node): self.next = node node.previous = self - def remove(self): self.previous.next = self.next self.next.previous = self.previous @@ -40,11 +51,13 @@ def addchild(self, node): def removechild(self, node): if node.parent != self: - raise AssertionError("Cannot remove child from a node that is not its parent") + raise AssertionError( + "Cannot remove child from a node that is not its parent") if node.issingle(): if self.child != node: - raise AssertionError("Cannot remove a node that is not a child") + raise AssertionError( + "Cannot remove a node that is not a child") self.child = None else: if self.child == node: @@ -56,7 +69,7 @@ def removechild(self, node): self.degree -= 1 #### End of Node Class #### - def __init__ (self): + def __init__(self): self.minnode = None self.count = 0 self.maxdegree = 0 @@ -112,7 +125,8 @@ def removeminimum(self): # 2.1: If we have removed the last key if self.minnode.next == self.minnode: if self.count != 0: - raise AssertionError("Heap error: Expected 0 keys, count is " + str(self.count)) + raise AssertionError( + "Heap error: Expected 0 keys, count is " + str(self.count)) self.minnode = None return removed_node @@ -146,7 +160,7 @@ def removeminimum(self): # 3: Remove current root and find new minnode self.minnode = None newmaxdegree = 0 - for d in range (0,logsize): + for d in range(0, logsize): if degreeroots[d] != None: degreeroots[d].next = degreeroots[d].previous = degreeroots[d] self._insertnode(degreeroots[d]) @@ -157,11 +171,10 @@ def removeminimum(self): return removed_node - def decreasekey(self, node, newkey): if newkey > node.key: #import code - #code.interact(local=locals()) + # code.interact(local=locals()) raise AssertionError("Cannot decrease a key to a greater value") elif newkey == node.key: return diff --git a/examples/small-copy.png b/examples/small-copy.png new file mode 100644 index 0000000..2ff0458 Binary files /dev/null and b/examples/small-copy.png differ diff --git a/mazes.py b/mazes.py index adf72fb..9a01ad3 100644 --- a/mazes.py +++ b/mazes.py @@ -5,6 +5,18 @@ def __init__(self, position): self.Neighbours = [None, None, None, None] #self.Weights = [0, 0, 0, 0] + def __lt__(self, other) -> bool: + if self.Position < other.Position: + return True + else: + return False + + def __le__(self, other) -> bool: + if self.Position <= other.Position: + return True + else: + return False + def __init__(self, im): width = im.size[0] @@ -19,15 +31,15 @@ def __init__(self, im): count = 0 # Start row - for x in range (1, width - 1): + for x in range(1, width - 1): if data[x] > 0: - self.start = Maze.Node((0,x)) + self.start = Maze.Node((0, x)) topnodes[x] = self.start count += 1 break - for y in range (1, height - 1): - #print ("row", str(y)) # Uncomment this line to keep a track of row progress + for y in range(1, height - 1): + # print ("row", str(y)) # Uncomment this line to keep a track of row progress rowoffset = y * width rowaboveoffset = rowoffset - width @@ -40,7 +52,7 @@ def __init__(self, im): leftnode = None - for x in range (1, width - 1): + for x in range(1, width - 1): # Move prev, current and next onwards. This way we read from the image once per pixel, marginal optimisation prv = cur cur = nxt @@ -57,14 +69,14 @@ def __init__(self, im): # PATH PATH PATH # Create node only if paths above or below if data[rowaboveoffset + x] > 0 or data[rowbelowoffset + x] > 0: - n = Maze.Node((y,x)) + n = Maze.Node((y, x)) leftnode.Neighbours[1] = n n.Neighbours[3] = leftnode leftnode = n else: # PATH PATH WALL # Create path at end of corridor - n = Maze.Node((y,x)) + n = Maze.Node((y, x)) leftnode.Neighbours[1] = n n.Neighbours[3] = leftnode leftnode = None @@ -72,14 +84,14 @@ def __init__(self, im): if nxt == True: # WALL PATH PATH # Create path at start of corridor - n = Maze.Node((y,x)) + n = Maze.Node((y, x)) leftnode = n else: # WALL PATH WALL # Create node only if in dead end if (data[rowaboveoffset + x] == 0) or (data[rowbelowoffset + x] == 0): #print ("Create Node in dead end") - n = Maze.Node((y,x)) + n = Maze.Node((y, x)) # If node isn't none, we can assume we can connect N-S somewhere if n != None: @@ -99,9 +111,9 @@ def __init__(self, im): # End row rowoffset = (height - 1) * width - for x in range (1, width - 1): + for x in range(1, width - 1): if data[rowoffset + x] > 0: - self.end = Maze.Node((height - 1,x)) + self.end = Maze.Node((height - 1, x)) t = topnodes[x] t.Neighbours[2] = self.end self.end.Neighbours[0] = t diff --git a/priority_queue.py b/priority_queue.py index 763edbb..18bb056 100644 --- a/priority_queue.py +++ b/priority_queue.py @@ -4,7 +4,8 @@ from FibonacciHeap import FibHeap import heapq -import Queue +from queue import Queue + class PriorityQueue(): __metaclass__ = ABCMeta @@ -24,6 +25,7 @@ def removeminimum(self): pass @abstractmethod def decreasekey(self, node, new_priority): pass + class FibPQ(PriorityQueue): def __init__(self): self.heap = FibHeap() @@ -43,6 +45,7 @@ def removeminimum(self): def decreasekey(self, node, new_priority): self.heap.decreasekey(node, new_priority) + class HeapPQ(PriorityQueue): def __init__(self): self.pq = [] @@ -127,4 +130,3 @@ def decreasekey(self, node, new_priority): self.remove(node) node.key = new_priority self.insert(node) - diff --git a/small-copy.png b/small-copy.png new file mode 100644 index 0000000..cc13ec4 Binary files /dev/null and b/small-copy.png differ diff --git a/small.png b/small.png new file mode 100644 index 0000000..2ff0458 Binary files /dev/null and b/small.png differ diff --git a/smallastarOP.png b/smallastarOP.png new file mode 100644 index 0000000..3f6ccb4 Binary files /dev/null and b/smallastarOP.png differ diff --git a/smalldepthfirstOP.png b/smalldepthfirstOP.png new file mode 100644 index 0000000..cc13ec4 Binary files /dev/null and b/smalldepthfirstOP.png differ diff --git a/smalldijkstraOP.png b/smalldijkstraOP.png new file mode 100644 index 0000000..3f6ccb4 Binary files /dev/null and b/smalldijkstraOP.png differ diff --git a/solve.py b/solve.py index 943ec59..a952570 100644 --- a/solve.py +++ b/solve.py @@ -1,29 +1,33 @@ +import argparse from PIL import Image import time from mazes import Maze from factory import SolverFactory Image.MAX_IMAGE_PIXELS = None +# usage: (method to be used) (ip image) (op image) +#! python3 solve.py -m depthfirst/breadthfirst -i small.png -o small-copy.png + # Read command line arguments - the python argparse class is convenient here. -import argparse -def solve(factory, method, input_file, output_file): + +def solve(factory, method: str = "breadthfirst", input_file: str = None, output_file: str = None): # Load Image - print ("Loading Image") + print("Loading Image") im = Image.open(input_file) # Create the maze (and time it) - for many mazes this is more time consuming than solving the maze - print ("Creating Maze") + print("Creating Maze") t0 = time.time() maze = Maze(im) t1 = time.time() - print ("Node Count:", maze.count) + print("Node Count:", maze.count) total = t1-t0 - print ("Time elapsed:", total, "\n") + print("Time elapsed:", total, "\n") # Create and run solver [title, solver] = factory.createsolver(method) - print ("Starting Solve:", title) + print("Starting Solve:", title) t0 = time.time() [result, stats] = solver(maze) @@ -32,12 +36,12 @@ def solve(factory, method, input_file, output_file): total = t1-t0 # Print solve stats - print ("Nodes explored: ", stats[0]) + print("Nodes explored: ", stats[0]) if (stats[2]): - print ("Path found, length", stats[1]) + print("Path found, length", stats[1]) else: - print ("No Path Found") - print ("Time elapsed: ", total, "\n") + print("No Path Found") + print("Time elapsed: ", total, "\n") """ Create and save the output image. @@ -46,7 +50,7 @@ def solve(factory, method, input_file, output_file): blue and red depending on how far down the path this section is. """ - print ("Saving Image") + print("Saving Image") im = im.convert('RGB') impixels = im.load() @@ -64,27 +68,35 @@ def solve(factory, method, input_file, output_file): if a[0] == b[0]: # Ys equal - horizontal line - for x in range(min(a[1],b[1]), max(a[1],b[1])): - impixels[x,a[0]] = px + for x in range(min(a[1], b[1]), max(a[1], b[1])): + impixels[x, a[0]] = px elif a[1] == b[1]: # Xs equal - vertical line - for y in range(min(a[0],b[0]), max(a[0],b[0]) + 1): - impixels[a[1],y] = px + for y in range(min(a[0], b[0]), max(a[0], b[0]) + 1): + impixels[a[1], y] = px + + if not output_file: + output_file = input_file.split(".") + output_file[0] = output_file[0] + method + "OP" + output_file = ".".join(output_file) im.save(output_file) def main(): sf = SolverFactory() - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser( + description="Maze Solving using Image Processing") parser.add_argument("-m", "--method", nargs='?', const=sf.Default, default=sf.Default, - choices=sf.Choices) - parser.add_argument("input_file") - parser.add_argument("output_file") + choices=sf.Choices, help="Method to be used on solving the maze") + parser.add_argument("-i", "--input", required=True, + help="Image of the maze input file") + parser.add_argument("-o", "--output", help="Solved output maze file") args = parser.parse_args() - solve(sf, args.method, args.input_file, args.output_file) + # NOTE: while debugging, use F5 key,cause the debug button won't pass the arguments to the program + solve(sf, args.method, args.input, args.output) + if __name__ == "__main__": main() -