From 064b1b66a5371eecf907f94f672ba995e1de4451 Mon Sep 17 00:00:00 2001 From: Philipp Schrader Date: Sun, 19 May 2013 10:54:54 -0700 Subject: [PATCH 1/3] Broke the scantron module into several pieces. --- scantron/__init__.py | 4 ++ scantron/data.py | 8 ++++ scantron.py => scantron/generator.py | 68 +--------------------------- scantron/parser.py | 60 ++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 67 deletions(-) create mode 100644 scantron/__init__.py create mode 100755 scantron/data.py rename scantron.py => scantron/generator.py (69%) create mode 100755 scantron/parser.py diff --git a/scantron/__init__.py b/scantron/__init__.py new file mode 100644 index 0000000..dce0486 --- /dev/null +++ b/scantron/__init__.py @@ -0,0 +1,4 @@ +from generator import ScantronGenerator +from parser import ScantronParser +from data import ScantronField +from reportlab.lib.units import inch diff --git a/scantron/data.py b/scantron/data.py new file mode 100755 index 0000000..2b30f89 --- /dev/null +++ b/scantron/data.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python2 + + +class ScantronField: + def __init__(self, name, label, fieldType): + self.name = name + self.label = label + self.fieldType = fieldType diff --git a/scantron.py b/scantron/generator.py similarity index 69% rename from scantron.py rename to scantron/generator.py index b8ebb5a..df3be18 100755 --- a/scantron.py +++ b/scantron/generator.py @@ -5,67 +5,8 @@ from reportlab.pdfgen import canvas from qrcode import * -import numpy as np -import scipy as sp -from scipy import ndimage -from PIL import Image, ImageDraw -import math - -class ScantronParser: - def __init__(self): - pass - - - def scan(self, data, path): - img = Image.open(path).convert('RGB') - im = sp.misc.fromimage(img, flatten=True) - im = np.where(im > 128, 0, 1) - label_im, num = ndimage.label(im, structure=np.ones((3, 3)).tolist()) - centroids = ndimage.measurements.center_of_mass(im, label_im, xrange(1, - num+1)) - slices = ndimage.find_objects(label_im) - - squares = [] - - for i in range(len(slices)): - sub_img = np.where(label_im[slices[i]] == i + 1, 1, 0) - num_ones = np.sum(sub_img) - num_all = sub_img.size - shape = sub_img.shape - - ratio = float(shape[0]) / float(shape[1]) - darkness = float(num_ones)/float(num_all) - - if darkness > 0.95 and abs(ratio - 1.0) < 0.1 and shape[0] > 14: - x1, x2 = slices[i][1].start, slices[i][1].stop - y1, y2 = slices[i][0].start, slices[i][0].stop - - draw = ImageDraw.Draw(img) - draw.rectangle([x1, y1, x2, y2], outline='blue') - del draw - - squares.append(i) - - if len(squares) != 3: - print('Could not uniquely identify the three page markers.') - raise Exception - - squares = zip(squares, map(lambda s: sum(centroids[s]), squares)) - squares = sorted(squares, key=lambda x: x[1]) - - for s in squares: - print('square ' + str(s)) - - tl = centroids[squares[0][0]] - bl = centroids[squares[1][0]] - br = centroids[squares[2][0]] - - rotation = math.atan2(bl[1] - tl[1], bl[0] - tl[0]) - print('rotation: ' + str(rotation)) - - -class Scantron: +class ScantronGenerator: def __init__(self, filename, spacing=0.3*inch): self._fontSize = 0.15*inch @@ -217,10 +158,3 @@ def populate(self, data, matches=1, collate='on'): def save(self): self._canvas.save() - - -class Field: - def __init__(self, name, label, fieldType): - self.name = name - self.label = label - self.fieldType = fieldType diff --git a/scantron/parser.py b/scantron/parser.py new file mode 100755 index 0000000..f120092 --- /dev/null +++ b/scantron/parser.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python2 + +import numpy as np +import scipy as sp +from scipy import ndimage +from PIL import Image, ImageDraw +import math + + +class ScantronParser: + def __init__(self): + pass + + + def scan(self, data, path): + img = Image.open(path).convert('RGB') + im = sp.misc.fromimage(img, flatten=True) + im = np.where(im > 128, 0, 1) + label_im, num = ndimage.label(im, structure=np.ones((3, 3)).tolist()) + centroids = ndimage.measurements.center_of_mass(im, label_im, xrange(1, + num+1)) + slices = ndimage.find_objects(label_im) + + squares = [] + + for i in range(len(slices)): + sub_img = np.where(label_im[slices[i]] == i + 1, 1, 0) + num_ones = np.sum(sub_img) + num_all = sub_img.size + shape = sub_img.shape + + ratio = float(shape[0]) / float(shape[1]) + darkness = float(num_ones)/float(num_all) + + if darkness > 0.95 and abs(ratio - 1.0) < 0.1 and shape[0] > 14: + x1, x2 = slices[i][1].start, slices[i][1].stop + y1, y2 = slices[i][0].start, slices[i][0].stop + + draw = ImageDraw.Draw(img) + draw.rectangle([x1, y1, x2, y2], outline='blue') + del draw + + squares.append(i) + + if len(squares) != 3: + print('Could not uniquely identify the three page markers.') + raise Exception + + squares = zip(squares, map(lambda s: sum(centroids[s]), squares)) + squares = sorted(squares, key=lambda x: x[1]) + + for s in squares: + print('square ' + str(s)) + + tl = centroids[squares[0][0]] + bl = centroids[squares[1][0]] + br = centroids[squares[2][0]] + + rotation = math.atan2(bl[1] - tl[1], bl[0] - tl[0]) + print('rotation: ' + str(rotation)) From 5ca781cb7b310579fa9a0ebfdec3dbd45e32f3d7 Mon Sep 17 00:00:00 2001 From: Philipp Schrader Date: Sun, 19 May 2013 10:58:25 -0700 Subject: [PATCH 2/3] Fixed import statements based on new structure. --- data_2013.py | 32 ++++++++++++++++---------------- generate_pdf.py | 4 ++-- testcase.py | 26 +++++++++++++------------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/data_2013.py b/data_2013.py index 3098b24..b005ae7 100755 --- a/data_2013.py +++ b/data_2013.py @@ -1,21 +1,21 @@ #!/usr/bin/env python2 -from scantron import Field +from scantron import ScantronField data = [ - Field('auton_high', 'Auton High', int), - Field('auton_mid', 'Auton Mid', int), - Field('auton_low', 'Auton Low', int), - Field('high', 'High', int), - Field('mid', 'Mid', int), - Field('low', 'Low', int), - Field('pyramid', 'Pyramid', int), - Field('missed', 'Missed', int), - Field('fouls', 'Fouls', int), - Field('tech_fouls', 'Tech fouls', int), - Field('defense', 'Defense', bool), - Field('pickup', 'Pickup', bool), - Field('noshow', 'Noshow', bool), - Field('brokedown', 'Brokedown', bool), - Field('dq', 'DQ', bool), + ScantronField('auton_high', 'Auton High', int), + ScantronField('auton_mid', 'Auton Mid', int), + ScantronField('auton_low', 'Auton Low', int), + ScantronField('high', 'High', int), + ScantronField('mid', 'Mid', int), + ScantronField('low', 'Low', int), + ScantronField('pyramid', 'Pyramid', int), + ScantronField('missed', 'Missed', int), + ScantronField('fouls', 'Fouls', int), + ScantronField('tech_fouls', 'Tech fouls', int), + ScantronField('defense', 'Defense', bool), + ScantronField('pickup', 'Pickup', bool), + ScantronField('noshow', 'Noshow', bool), + ScantronField('brokedown', 'Brokedown', bool), + ScantronField('dq', 'DQ', bool), ] diff --git a/generate_pdf.py b/generate_pdf.py index cd4288f..a6c8df6 100755 --- a/generate_pdf.py +++ b/generate_pdf.py @@ -3,7 +3,7 @@ import os import sys from argparse import ArgumentParser -from scantron import * +from scantron import ScantronGenerator, inch # Make this file easier to use by adding nice arguments parser = ArgumentParser(description='Generate scantron PDFs.') @@ -47,7 +47,7 @@ quit(1) # If everything went well, proceed to generate the PDF -st = Scantron(args.output, spacing=0.3*inch) +st = ScantronGenerator(args.output, spacing=0.3*inch) st.set_box_sizes(box_size=0.2*inch, box_spacing=0.3*inch) st.populate(data, matches=args.num_matches, collate=args.collate) st.save() diff --git a/testcase.py b/testcase.py index ea0fa9d..bff61c1 100755 --- a/testcase.py +++ b/testcase.py @@ -1,30 +1,30 @@ #!/usr/bin/env python2 -from scantron import * +from scantron import ScantronGenerator, ScantronField, inch import PythonMagick from pyPdf import PdfFileReader from PIL import Image -data = [ - Field('foo', 'Foo foo foo', int), - Field('bar', 'Bar bar bar', int), - Field('baz', 'Baz baz baz', int), - Field('laber', 'Laber laber', bool), +test_data = [ + ScantronField('foo', 'Foo foo foo', int), + ScantronField('bar', 'Bar bar bar', int), + ScantronField('baz', 'Baz baz baz', int), + ScantronField('laber', 'Laber laber', bool), ] -# Generate PDF -st = Scantron('test.pdf') -st.set_box_sizes(box_size=0.2*inch, box_spacing=0.3*inch) -st.populate(data, matches=1, collate='no') -st.save() +# Generate scantron PDF +pdf = ScantronGenerator('test.pdf') +pdf.set_box_sizes(box_size=0.2*inch, box_spacing=0.3*inch) +pdf.populate(test_data, matches=1, collate='no') +pdf.save() # Convert PDF to a series of pictures pages = [] pdf = PdfFileReader(file('test.pdf', 'rb')) +num_pages = pdf.getNumPages() -for page in range(pdf.getNumPages()): - #page += 1 +for page in range(num_pages): name = 'test_image_%d.png' % page im = PythonMagick.Image() From f8fc621d5344f2594638ca4f320f606f0d7138af Mon Sep 17 00:00:00 2001 From: Philipp Schrader Date: Sun, 19 May 2013 11:35:45 -0700 Subject: [PATCH 3/3] Removed black background from rotated images. --- testcase.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/testcase.py b/testcase.py index bff61c1..0dc7471 100755 --- a/testcase.py +++ b/testcase.py @@ -3,7 +3,7 @@ from scantron import ScantronGenerator, ScantronField, inch import PythonMagick from pyPdf import PdfFileReader -from PIL import Image +from PIL import Image, ImageOps test_data = [ ScantronField('foo', 'Foo foo foo', int), @@ -35,9 +35,16 @@ pages.append(name) # Create a series of transformations to apply +def rotate(im, deg): + blank = Image.new('L', im.size, 255) + mask = blank.rotate(deg, expand=False) + mask = ImageOps.invert(mask) + rot = im.rotate(deg, expand=False) + return Image.composite(blank, rot, mask) + transformations = [ - lambda x: x.rotate(10, expand=False), - lambda x: x.rotate(-10, expand=False), + lambda x: rotate(x, 10), + lambda x: rotate(x, -10), ] tf = 0