diff --git a/README.md b/README.md index 8318750..fdf2c00 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ There are two major steps to the algorithm: # Requirements * [Python 3](https://www.python.org/downloads/) - you should also know how to use the console/command prompt, and run/execute a Python script. Note that command line options might be different for those using Anaconda. + - the best is Python 3.6.3 * Optional: [Concorde TSP Solver](http://www.math.uwaterloo.ca/tsp/concorde/index.html) * Optional: [Git](https://git-scm.com/) * Optional: Image editing program. Free/open-source ones: [Krita](https://krita.org/en/), [GIMP](https://www.gimp.org/) @@ -31,6 +32,18 @@ And lastly, the image(s) that you want to convert! * imageio * scipy + +> attation: + +must install the right version : + + Pillow==3.4.2 + scipy==0.18.1 + numpy==1.11.2 + + + + ## What kind of images should I use for best results? **Format:** This will work for the common image formats (`.jpg`, `.png`). More obscure image formats might have some issues, so I'd recommend converting them to `.jpg` or `.png` first. diff --git a/draw_tsp_path.py b/draw_tsp_path.py new file mode 100644 index 0000000..2c1c5e2 --- /dev/null +++ b/draw_tsp_path.py @@ -0,0 +1,142 @@ +"""Modified code from https://developers.google.com/optimization/routing/tsp#or-tools """ +# Copyright Matthew Mack (c) 2020 under CC-BY 4.0: https://creativecommons.org/licenses/by/4.0/ + +from __future__ import print_function +import math +from ortools.constraint_solver import routing_enums_pb2 +from ortools.constraint_solver import pywrapcp +from PIL import Image, ImageDraw +import os + +# Change these file names to the relevant files. +ORIGINAL_IMAGE = "images/smileyface-inverted.png" +IMAGE_TSP = "images/smileyface-inverted-1024-stipple.tsp" + +def create_data_model(): + """Stores the data for the problem.""" + # Extracts coordinates from IMAGE_TSP and puts them into an array + list_of_nodes = [] + with open(IMAGE_TSP) as f: + for _ in range(6): + next(f) + for line in f: + i,x,y = line.split() + list_of_nodes.append((int(float(x)),int(float(y)))) + data = {} + # Locations in block units + data['locations'] = list_of_nodes # yapf: disable + data['num_vehicles'] = 1 + data['depot'] = 0 + return data + +def compute_euclidean_distance_matrix(locations): + """Creates callback to return distance between points.""" + distances = {} + for from_counter, from_node in enumerate(locations): + distances[from_counter] = {} + for to_counter, to_node in enumerate(locations): + if from_counter == to_counter: + distances[from_counter][to_counter] = 0 + else: + # Euclidean distance + distances[from_counter][to_counter] = (int( + math.hypot((from_node[0] - to_node[0]), + (from_node[1] - to_node[1])))) + return distances + +def print_solution(manager, routing, solution): + """Prints solution on console.""" + print('Objective: {}'.format(solution.ObjectiveValue())) + index = routing.Start(0) + plan_output = 'Route:\n' + route_distance = 0 + while not routing.IsEnd(index): + plan_output += ' {} ->'.format(manager.IndexToNode(index)) + previous_index = index + index = solution.Value(routing.NextVar(index)) + route_distance += routing.GetArcCostForVehicle(previous_index, index, 0) + plan_output += ' {}\n'.format(manager.IndexToNode(index)) + print(plan_output) + plan_output += 'Objective: {}m\n'.format(route_distance) + +def get_routes(solution, routing, manager): + """Get vehicle routes from a solution and store them in an array.""" + # Get vehicle routes and store them in a two dimensional array whose + # i,j entry is the jth location visited by vehicle i along its route. + routes = [] + for route_nbr in range(routing.vehicles()): + index = routing.Start(route_nbr) + route = [manager.IndexToNode(index)] + while not routing.IsEnd(index): + index = solution.Value(routing.NextVar(index)) + route.append(manager.IndexToNode(index)) + routes.append(route) + return routes[0] + +def draw_routes(nodes, path): + """Takes a set of nodes and a path, and outputs an image of the drawn TSP path""" + tsp_path = [] + for location in path: + tsp_path.append(nodes[int(location)]) + + original_image = Image.open(ORIGINAL_IMAGE) + width, height = original_image.size + + tsp_image = Image.new("RGBA",(width,height),color='white') + tsp_image_draw = ImageDraw.Draw(tsp_image) + #tsp_image_draw.point(tsp_path,fill='black') + tsp_image_draw.line(tsp_path,fill='black',width=1) + tsp_image = tsp_image.transpose(Image.FLIP_TOP_BOTTOM) + FINAL_IMAGE = IMAGE_TSP.replace("-stipple.tsp","-tsp.png") + tsp_image.save(FINAL_IMAGE) + print("TSP solution has been drawn and can be viewed at", FINAL_IMAGE) + +def main(): + """Entry point of the program.""" + # Instantiate the data problem. + print("Step 1/5: Initialising variables") + data = create_data_model() + + # Create the routing index manager. + manager = pywrapcp.RoutingIndexManager(len(data['locations']), + data['num_vehicles'], data['depot']) + + # Create Routing Model. + routing = pywrapcp.RoutingModel(manager) + print("Step 2/5: Computing distance matrix") + distance_matrix = compute_euclidean_distance_matrix(data['locations']) + + def distance_callback(from_index, to_index): + """Returns the distance between the two nodes.""" + # Convert from routing variable Index to distance matrix NodeIndex. + from_node = manager.IndexToNode(from_index) + to_node = manager.IndexToNode(to_index) + return distance_matrix[from_node][to_node] + + transit_callback_index = routing.RegisterTransitCallback(distance_callback) + + # Define cost of each arc. + routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index) + + # Setting first solution heuristic. + print("Step 3/5: Setting an initial solution") + search_parameters = pywrapcp.DefaultRoutingSearchParameters() + search_parameters.first_solution_strategy = ( + routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) + + # Solve the problem. + print("Step 4/5: Solving") + solution = routing.SolveWithParameters(search_parameters) + + # Print solution on console. + if solution: + #print_solution(manager, routing, solution) + print("Step 5/5: Drawing the solution") + routes = get_routes(solution, routing, manager) + draw_routes(data['locations'],routes) + else: + print("A solution couldn't be found :(") + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 69f4590..a2f4758 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,10 @@ -Pillow ortools tqdm imageio -scipy \ No newline at end of file +#Pillow +#scipy +matplotlib +Pillow==3.4.2 +scipy==0.18.1 +numpy==1.11.2 + diff --git a/stippling.py b/stippling.py index caf7c0b..b13e0c4 100644 --- a/stippling.py +++ b/stippling.py @@ -65,4 +65,5 @@ if(SAVE_AS_NPY): full_command += " --npy" -os.system(cmd + full_command) \ No newline at end of file +#os.system(cmd + full_command) +print(full_command) \ No newline at end of file diff --git a/tasks.py b/tasks.py new file mode 100644 index 0000000..1481938 --- /dev/null +++ b/tasks.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +'''PoL<-PoW +''' +__version__ = 'TSPart v.200716.1542' +__author__ = 'Zoom.Quiet' +__license__ = 'MIT@2020-07' + +import os +#import sys +import os.path as osp + +import time +import subprocess + +from pprint import pprint as pp +#from textwrap import dedent as dedentxt +import sys +import platform +os_name = platform.system() +del platform + + +from invoke import task + +import draw_tsp_path as drawtp + +#_IMG='images' + +@task +def V(c): + '''Version and how to usage + ''' + #print('\n\t powded by {}'.format(__version__)) + print( "\n$ inv -l show all support commands \n\tpowered by <-{}->\n".format(__version__) ) + +@task +def tsp(c, img, npoints): + '''got images/THE IMAGE and gen the TSP-art need picture + ''' + #print(img, npoints) + _cmd = 'python weighted-voronoi-stippler/stippler.py ' + _cmd += img + _cmd += ' --save ' + _cmd += '--n_point %s '%npoints + _cmd += '--n_iter 25 ' + _cmd += '--pointsize 1.0 1.0 ' + _cmd += '--figsize 8 ' + _cmd += '--threshold 255 ' + _cmd += '--force ' + _cmd += '--interactive ' + _cmd += '--png ' + #print(_cmd) + c.run(_cmd) + + dirname = os.path.dirname(img) + basename = (os.path.basename(img).split('.'))[0] + #print(img) + #print(dirname,basename) + _stippled = '{}/{}-{}-stipple'.format(dirname,basename,npoints) + #print(_stippled) + drawtp.ORIGINAL_IMAGE = '%s.png'%_stippled + drawtp.IMAGE_TSP = '%s.tsp'%_stippled + #print(drawtp.ORIGINAL_IMAGE) + #print(drawtp.IMAGE_TSP) + drawtp.main() + _tsp = '{}/{}-{}-tsp.png'.format(dirname,basename,npoints) + + + +