DEV Community

Dave Jacoby
Dave Jacoby

Posted on • Originally published at jacoby.github.io on

4 2

Travelling Salesman Portrait - A Variation

#r

This starts when I saw that Randy Olson found an interesting thing and built upon it.

Traveling salesman portrait in Python is where I came in. It runs through a series of steps:

  • Take an image, in this case of Boris Karloff as Frankenstein’s Monster
  • Turn it into grayscale
  • Dither it to get it to more completely black and white
  • Pull a number of randomly-chosen black pixels
  • Run a Traveling Salesman Algorithm on it to connect all the pixels
  • Draw the line and write the image

Problem is, Dave Jacoby and Python is like Charlie Brown and a kite; I can never get it to work and I end up defeated and tied to a tree. In this case, believe it’s because matplotlib doesn’t like how I am handling fonts, which is immaterial because no fonts are involved in this process.

( #VirtualEnv All The Things )

But Randy Olson didn’t dream this up himself, he adapted it from R to Python, using Fronkonstin’s Work as a base. And I have few problems with R. My main issue is that I find it hard to think in terms of R’s data arrays and such. And my ggplot2 knowledge is so cargo-cult. But I can work with the code.

#!/usr/bin/env Rscript
# https://github.com/aschinchon/travelling-salesman-portrait/blob/master/frankenstein_TSP.R

# to be done:
# replace the library() calls with something quieter
# make usage -i file -o file
# remove the urlfile part (?)
# use Cairo so I can batch it

# Quietly load modules
suppressPackageStartupMessages(require("methods", quietly=TRUE))
suppressPackageStartupMessages(require("imager", quietly=TRUE))
suppressPackageStartupMessages(require("dplyr", quietly=TRUE))
suppressPackageStartupMessages(require("ggplot2", quietly=TRUE))
suppressPackageStartupMessages(require("scales", quietly=TRUE))
suppressPackageStartupMessages(require("TSP", quietly=TRUE))

# Handle Command Line Arguments
# cmd -> error ('need arguments')
# cmd file -> error if ! -f file, input = file, output = 'output.jpg'
# cmd file1 file2 -> error if ! -f file1, input = file1, output = file2
args = commandArgs(trailingOnly=TRUE)
if (length(args)==0) {
  stop("At least one argument must be supplied (input file).", call.=FALSE)
} else if (length(args)==1) {
  # default output file
  args[2] = "./output.jpg"
}
input=args[1]
output=args[2]

file=input
if (!file.exists(file)) { stop ("No valid input file", call.=FALSE) }

# Load, convert to grayscale, filter image (to convert it to bw) and sample
load.image(file) %>% 
  grayscale() %>%
  threshold("45%") %>% 
  as.cimg() %>% 
  as.data.frame() %>% 
  sample_n(8000, weight=(1-value)) %>% 
  select(x,y) -> data

# Compute distances and solve TSP (it may take a minute)
as.TSP(dist(data)) %>% 
  solve_TSP(method = "arbitrary_insertion") %>% 
  as.integer() -> solution

# Create a dataframe with the output of TSP
data.frame(id=solution) %>% 
  mutate(order=row_number()) -> order

# Rearrange the original points according the TSP output
data %>% 
  mutate(id=row_number()) %>% 
  inner_join(order, by="id") %>% arrange(order) %>% 
  select(x,y) -> data_to_plot

# A little bit of ggplot to plot results
ggplot(data_to_plot, aes(x,y)) +
    geom_path() +
    scale_y_continuous(trans=reverse_trans())+
    coord_fixed()+
    theme_void()

# Do you like the result? Save it! (Change the filename if you want)
ggsave(output, dpi=600, width = 4, height = 4)
Enter fullscreen mode Exit fullscreen mode

This allows me to do a thing like:

./tsp.R Frankenstein.jpg TSP-Frankenstein.png

and get this output.

My TSP Frankenstein.

And do the same with

./tsp.R headshot.jpg TSP-headshot.png

and get this

My TSP Headshot.

But I don’t like this, as you can see in the comics. I think this is because “real R users” use it as a data shell and I like to think of things as programs. I come up with similarly-formatted data (like from a database), run it through my R in batch, and come up with the output while I’m doing other things.

As that kind of user, I really want this to be

./tsp.R --input headshot.jpg --output TSP-headshot.png

with optional flags for threshold and line width. I do not know of an R equivalent of Perl’s Getopt::Long. I might have to write one, if only for my own edification.

If you have any questions or comments, I would be glad to hear it. Ask me on Twitter or make an issue on my blog repo.

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay