unplot.py / unplot.py

#!/usr/bin/env python

"""Convert plot image to tabular data. Inverse operation of plotting.

Usage: unplot.py [options] COLOR x1 x1_px y1 y1_px x2 x2_px y2 y2_px image.png

where
  COLOR is HTML color of the line to match, e.g. #ff0000 for red
  (x1_px, y1_px) are coordinates of the lower left corner of the scan area
  (x2_px, y2_px) are coordinates of the upper right corner of the scan area
  (x1, y1) and (y1, y2) are real-world quantities corresponding to lower left
                        and to upper right corners of scan area

options:
      -h                this message
      -s NUMBER         acceptable color difference in %  [default: 10%]
"""

import sys

from getopt import getopt
from operator import itemgetter
from itertools import imap, ifilter

from PIL import Image

def html_to_rgb(colorstring):
    """Convert #RRGGBB to an (R, G, B) tuple.
    Source: http://code.activestate.com/recipes/266466/"""
    colorstring = colorstring.strip()
    if colorstring[0] == '#': colorstring = colorstring[1:]
    if len(colorstring) != 6:
        raise ValueError, "input #%s is not in #RRGGBB format" % colorstring
    r, g, b = colorstring[:2], colorstring[2:4], colorstring[4:]
    r, g, b = [int(n, 16) for n in (r, g, b)]
    return (r, g, b)

def color_diff(color1, color2):
    """Relative difference between two colors."""
    paired = zip(color1, color2)
    diff = map(lambda (x,y): abs(x-y)/255.0, paired)
    return sum(diff)/len(paired)

def usage(where=sys.stdout,msg=None):
    if msg:
        print >>where, msg
    print >>where, __doc__
    if where == sys.stdout:
        sys.exit(0)
    else:
        sys.exit(1)

if __name__ == '__main__':
    opts, args = getopt(sys.argv[1:], "hs:")
    opts = dict(opts)

    if "-h" in opts: usage()

    try:
        color, x1, x1p, y1, y1p, x2, x2p, y2, y2p, imgfile = args
    except ValueError:
        usage(sys.stderr, "Wrong number of arguments: %d" % len(args))

    sensitivity = int(opts.get("-s",10))
    color = html_to_rgb(color)
    x1, y1, x2, y2 = map(float, [x1, y1, x2, y2])
    x1p, y1p, x2p, y2p = map(int, [x1p, y1p, x2p, y2p])
    xscale, yscale = (x2-x1)/(x2p-x1p), (y2-y1)/(y2p-y1p)

    im = Image.open(file(imgfile))
    w, h = im.size
    for i in xrange(x1p,x2p+1):
        js = xrange(y1p,y2p-1,-1)
        pixels = map(lambda j: (j, im.getpixel((i,j))), js)

        def similar(px):
            j, c = px
            return color_diff(c,color)*100 < sensitivity

        pixels = ifilter(similar, pixels)
        pixels = sorted(pixels, key=lambda (j,c): -color_diff(c,color))
        if pixels:
            j, c = pixels[0]
            print (i-x1p)*xscale+x1, (j-y1p)*yscale+y1
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.