Can I have a card to scan?
You could use a screen grab from my
Javascript Virtual Cardpunch. Or you use the following python punchcardgen.py script that generates card images from text read from stdin (how about a t-shirt with a message punched into it):
#!/usr/bin/env python
#
# punchcardgen.py
#
# Copyright (C) 2011: Michael Hamilton
# The code is GPL 3.0(GNU General Public License) ( http://www.gnu.org/copyleft/gpl.html )
#
import Image
import sys
CARD_COLUMNS = 80
CARD_ROWS = 12
# found measurements at http://www.quadibloc.com/comp/cardint.htm
CARD_WIDTH = 7.0 + 3.0/8.0 # Inches
CARD_HEIGHT = 3.25 # Inches
CARD_COL_WIDTH = 0.087 # Inches
CARD_HOLE_WIDTH = 0.055 # Inches IBM, 0.056 Control Data
CARD_ROW_HEIGHT = 0.25 # Inches
CARD_HOLE_HEIGHT = 0.125 # Inches
CARD_TOPBOT_MARGIN = 3.0/16.0 # Inches at top and bottom
CARD_SIDE_MARGIN = 0.2235 # Inches on each side
DARK = (0,0,0)
BRIGHT = (255,255,255) # pixel brightness value (i.e. (R+G+B)/3)
REDUCE_IN_SIZE=8
IBM_MODEL_029_KEYPUNCH = """
/&-0123456789ABCDEFGHIJKLMNOPQR/STUVWXYZ:#@'="`.<(+|!$*);^~,%_>? |
12 / O OOOOOOOOO OOOOOO |
11| O OOOOOOOOO OOOOOO |
0| O OOOOOOOOO OOOOOO |
1| O O O O |
2| O O O O O O O O |
3| O O O O O O O O |
4| O O O O O O O O |
5| O O O O O O O O |
6| O O O O O O O O |
7| O O O O O O O O |
8| O O O O OOOOOOOOOOOOOOOOOOOOOOOO |
9| O O O O |
|__________________________________________________________________|"""
translate = None
if translate == None:
translate = {}
# Turn the ASCII art sideways and build a hash look up for
# column values, for example:
# A:(O, , ,O, , , , , , , , )
# B:(O, , , ,O, , , , , , , )
# C:(O, , , , ,O, , , , , , )
rows = IBM_MODEL_029_KEYPUNCH[1:].split('\n');
rotated = [[ r[i] for r in rows[0:13]] for i in range(5, len(rows[0]) - 1)]
for v in rotated:
translate[v[0]] = tuple(v[1:])
if __name__ == '__main__':
scale = 1000
margin = 200
card_x_pixels = int(CARD_WIDTH * scale)
card_y_pixels = int(CARD_HEIGHT * scale)
img_size = (2 * margin + card_x_pixels, 2 * margin + card_y_pixels)
side_margin_pixels = int(CARD_SIDE_MARGIN * scale)
col_width_pixels = int(CARD_COL_WIDTH * scale)
top_bot_margin = int(CARD_TOPBOT_MARGIN * scale)
row_height_pixels = int(CARD_ROW_HEIGHT * scale)
hole_width = int(CARD_HOLE_WIDTH * scale)
hole_height = int(CARD_HOLE_HEIGHT * scale)
card_area = (margin, margin, margin + card_x_pixels, margin + card_y_pixels)
proto_img = Image.new('RGB', img_size, BRIGHT)
proto_pix = proto_img.load()
proto_img.paste(DARK, card_area)
# Remove the top left corner (don't know the standard for this - guess)
i = 0
for x in xrange(margin, margin + side_margin_pixels):
for y in xrange(margin, margin + top_bot_margin + hole_height - i):
proto_pix[x,y] = BRIGHT
i += 2
card_number = 1
for line in sys.stdin:
img = proto_img.copy()
x = margin + side_margin_pixels
for char in line:
if char in translate:
values = translate[char]
y = margin + top_bot_margin
for row in xrange(0, CARD_ROWS):
if values[row] == 'O':
img.paste(BRIGHT, (x, y, x + hole_width, y + hole_height))
y += row_height_pixels
x += col_width_pixels
if x > margin + card_x_pixels:
break
img = img.resize((img_size[0]/REDUCE_IN_SIZE, img_size[1]/REDUCE_IN_SIZE))
filename = "%010.10d.jpg" % ( card_number )
print filename, line
img.save(filename)
card_number += 1
The script has no command line options, just feed it uppercase text, for example:
% python punchcardgen.py
PROGRAM FORTRAN; WRITE(*,*)'HELLO WORLD'; END PROGRAM
In this case the script produces a single image:
The full-sized image can be rescanned to text by using my original punchcard script, for example:
% python punchcard.py 0000000001.jpg > prog.f90
% gfortran prog.f90
% ./a.out
HELLO WORLD
If you have your own cards, you can just hold them up to an even light and take their picture, for example you might use a monitor displaying white or a cloudy sky - just make sure the resulting image background is smooth and the picture is straight and square - and hold it by the bottom corner, for example:
To get the best scan, try the python script with the -d or -i options for debug info, use -b N to change the threshold light levels. Use a full sized image - if the image is too small the calculations introduce errors.
Why
not use an auto-feed scanner with a straight through paper path?
That would be the way to go if
I wanted to spend money on it – and
didn't want to learn a little electronics.
Why not add a motor and automate the
feed?
I was worried about jams on older decks of cards. I think I
could build a better feed by copying my photo printer's paper feed in Lego. (possible patent violation?)
I did consider using my
photo-printer as a feeder, that would probably have worked quite well. I would have to figure out how to collect cards as they exit the printer.
Some way along I figured I could get the job done with stuff at hand without out buying anything. Once I set that constraint, options narrowed considerably and decisions were easier to make.
There was also the case of the minicomputer
with a crank fitted over the instruction single-step toggle-switch –
variable speed debugging – consider my approach a homage to that
earlier clever hardware hack.
Why not use an array of detectors
connected to the Arduino and eliminate the camera?
That would be cool but - this is my
first attempt at electronics. Advancing each column past a single
column scan would seem hard to calibrate correctly. I imagine that
could be solved with a grid/wheel of calibration holes moving with
the card or moving with the scanner.
If
the card moved at a constant speed it might be possible to detect start and end of card, and from the timing figure
out what went past and what row it belonged to.
Why not just read the text printed
at the top of the card?
I didn't think the text would be good
enough for OCR. Some cards were quite worn. I did not want to
manually read and enter each card. OCR seems a tough problem compared
to reading the holes.
What is the Arduino for exactly?
The Arduino stops the card, detects that a card has stopped, signals the camera to focus and shoot, opens the servo to let the card go. It plays a key role in keeping the cards in order, both the order of the images in the camera, and the order of the physical cards in the output bin. It could do more, such as run a feed motor. But really, the Card Reader is a integration with an Arduino in the mix - it's not a pure Arduino project.
Why not use a webcam, Android camera?
The Canon S2 IS employed here is old, but produces reasonable distortion free images - with the CHDK firmware hack it seemed a shame not to use it. It would be nice to feed directly to the PC - Android or a webcam would accomplish this. Perhaps a wireless capable SD-card might also work.
Why would anyone want to go to this much time/effort?
For me, learning by doing works best. This was a well bounded problem that look solvable. It wasn't all that much effort, I just kept the problem in the back of my mind over the last year. There was only the occasional burst of activity when ideas solidified – I was not working to a deadline.
Why not tweak-it/finish-it/enhance-it in some respect?
I've scanned the cards I wanted to - some MIX, some FORTRAN. In the process achieved my goal of learning a little about Arduino,
electronics, CHDK, fritzing, and PIL. I hope to apply some of
what I've learnt to some of my other
interests, for example, nature photography: