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

default_sensitivity = 10
default_color = (255,0,0)

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)

def unplot_file(imgfile, angles_coords, angles_values, \
                sensitivity=default_sensitivity, color=default_color):
    ((x1,y1),(x2,y2)) = angles_values
    ((x1p,y1p),(x2p,y2p)) = angles_coords
    xscale, yscale = (x2-x1)/(x2p-x1p), (y2-y1)/(y2p-y1p)
    pts = []
    im = Image.open(file(imgfile))
    w, h = im.size
    def check(var,maxval):
        if not (0 <= var < maxval):
            raise Exception("Coordinate %d is out of image dimension [0~%d]." % \
                            (var, maxval-1))
    for v,r in [(x1p,w), (x2p,w), (y1p,h), (y2p,h)]:
        check(v,r)

    for i in xrange(x1p,x2p+1):
        js = xrange(y1p,y2p-1,-1)
        pixels = imap(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)
        js = map(itemgetter(0), pixels)

        if js:
            j = sorted(js)[len(js)/2]  # median ordinate
            pts.append(((i-x1p)*xscale+x1, (j-y1p)*yscale+y1))
    return pts

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",default_sensitivity))
    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])

    if x2p < x1p:
        raise Exception("The right corner was given first (should be the second).")
    if y2p > y1p:
        raise Exception("The top corner was given first (should be the second).")

    pts = unplot_file(imgfile, ((x1p,y1p), (x2p,y2p)), ((x1,y1), (x2,y2)), sensitivity, color)
    for x,y in pts:
        print x,y
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.