From cd02a27637cc31b20f4b0da3e4e1da3fd748dd3e Mon Sep 17 00:00:00 2001 From: Henry Cooke Date: Mon, 10 Aug 2020 20:43:07 +0100 Subject: [PATCH 01/10] added .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 0d20b64..1d17dae 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -*.pyc +.venv From c5ebb5950d978ea7909258ecaf0143a876983be8 Mon Sep 17 00:00:00 2001 From: Henry Cooke Date: Mon, 10 Aug 2020 20:43:33 +0100 Subject: [PATCH 02/10] move ifmain to bottm --- punchcards/punchcard.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/punchcards/punchcard.py b/punchcards/punchcard.py index 4661395..770d8ee 100755 --- a/punchcards/punchcard.py +++ b/punchcards/punchcard.py @@ -312,10 +312,6 @@ def dump(self, id, raw_data=False): print('') -if __name__ == '__main__': - main() - - def main(): usage = """usage: %prog [options] image [image...] decode punch card image into ASCII.""" @@ -340,3 +336,7 @@ def main(): card.dump(arg) if (options.dumpraw): card.dump(arg, raw_data=True) + + +if __name__ == '__main__': + main() From 108903d514a7005f5bfb2162153bd549cabb0ef0 Mon Sep 17 00:00:00 2001 From: Henry Cooke Date: Mon, 10 Aug 2020 21:42:20 +0100 Subject: [PATCH 03/10] add some int() casting --- punchcards/punchcard.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/punchcards/punchcard.py b/punchcards/punchcard.py index 770d8ee..4688222 100755 --- a/punchcards/punchcard.py +++ b/punchcards/punchcard.py @@ -10,6 +10,8 @@ from optparse import OptionParser import logging +import pdb + SPEC_IBM_MODEL_029 = "IBM Model 029 Punch Card" # only one for now CARD_COLUMNS = 80 @@ -116,8 +118,8 @@ def _crop(self): self.xmax = self.xsize if self.ymax == 0: self.ymax = self.ysize - self.midx = self.xmin + (self.xmax - self.xmin) / 2 + self.xadjust - self.midy = self.ymin + (self.ymax - self.ymin) / 2 + self.midx = int(self.xmin + (self.xmax - self.xmin) / 2) + self.xadjust + self.midy = int( self.ymin + (self.ymax - self.ymin) / 2) # heuristic for finding a reasonable cutoff brightness def _find_threshold_brightness(self): @@ -192,6 +194,7 @@ def _find_data_horiz_dimensions(self, probe_y): # column and hole horizontal dimensions def _find_data_vert_dimensions(self): top_border, bottom_border = self.ymin, self.ymax + breakpoint() for y in range(self.ymin, self.midy): if self._brightness(self.pix[self.midx, y]) < self.threshold: top_border = y @@ -213,7 +216,7 @@ def _find_data_vert_dimensions(self): self.debug_pix[x,bottom_border] = 255 if self.logger.isEnabledFor(logging.DEBUG): # mark search parameters - for x in range(self.midx - self.xsize/20, self.midx + self.xsize/20): + for x in range(self.midx - int(self.xsize/20), int(self.midx + self.xsize/20)): self.debug_pix[x,self.ymin] = 255 self.debug_pix[x,self.ymax - 1] = 255 for y in range(0, self.ymin): From 119b2c1143de85374f191dc38d64cbb7ef9f2e55 Mon Sep 17 00:00:00 2001 From: Henry Cooke Date: Mon, 10 Aug 2020 21:48:00 +0100 Subject: [PATCH 04/10] replace raw_input with wrapper for python 2/3 input --- punchcards/punchcard.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/punchcards/punchcard.py b/punchcards/punchcard.py index 4688222..f6a974c 100755 --- a/punchcards/punchcard.py +++ b/punchcards/punchcard.py @@ -276,7 +276,7 @@ def _scan(self, bright=-1): if self.logger.isEnabledFor(logging.DEBUG): self.debug_image.show() # prevent run-a-way debug shows causing my desktop to run out of memory - raw_input("Press Enter to continue...") + self._input("Press Enter to continue...") self.decoded = [] # Could fold this loop into the previous one - but would it be faster? for col in range(0, CARD_COLUMNS): @@ -313,6 +313,14 @@ def dump(self, id, raw_data=False): print('`' + '-' * CARD_COLUMNS + "'") print(' ' + '123456789-' * (CARD_COLUMNS/10)) print('') + + + # simple shim for python 2/3 compatibility + def _input( self, prompt ): + try: + return raw_input( prompt ) + except NameError: + return input( prompt) def main(): From 20bc8192c2d370033ffa6ef53c8e3f450dc01a77 Mon Sep 17 00:00:00 2001 From: Henry Cooke Date: Mon, 10 Aug 2020 21:48:57 +0100 Subject: [PATCH 05/10] remove debug breakpoint --- punchcards/punchcard.py | 1 - 1 file changed, 1 deletion(-) diff --git a/punchcards/punchcard.py b/punchcards/punchcard.py index f6a974c..d37892d 100755 --- a/punchcards/punchcard.py +++ b/punchcards/punchcard.py @@ -194,7 +194,6 @@ def _find_data_horiz_dimensions(self, probe_y): # column and hole horizontal dimensions def _find_data_vert_dimensions(self): top_border, bottom_border = self.ymin, self.ymax - breakpoint() for y in range(self.ymin, self.midy): if self._brightness(self.pix[self.midx, y]) < self.threshold: top_border = y From 5d34517ff938e210f640a48f6bceb25fd21fd5a5 Mon Sep 17 00:00:00 2001 From: Henry Cooke Date: Mon, 10 Aug 2020 21:52:02 +0100 Subject: [PATCH 06/10] another couple of int casts --- punchcards/punchcard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/punchcards/punchcard.py b/punchcards/punchcard.py index d37892d..989b887 100755 --- a/punchcards/punchcard.py +++ b/punchcards/punchcard.py @@ -297,7 +297,7 @@ def _scan(self, bright=-1): # ASCII art image of card def dump(self, id, raw_data=False): print(' Card Dump of Image file:', id, 'Format', 'Raw' if raw_data else 'Dump', 'threshold=', self.threshold) - print(' ' + '123456789-' * (CARD_COLUMNS/10)) + print(' ' + '123456789-' * int(CARD_COLUMNS/10)) print(' ' + '_' * CARD_COLUMNS + ' ') print('/' + self.text + '_' * (CARD_COLUMNS - len(self.text)) + '|') for rnum in range(len(self.decoded[0])): @@ -310,7 +310,7 @@ def dump(self, id, raw_data=False): sys.stdout.write(col[rnum] if col[rnum] == 'O' else '.') print('|') print('`' + '-' * CARD_COLUMNS + "'") - print(' ' + '123456789-' * (CARD_COLUMNS/10)) + print(' ' + '123456789-' * int(CARD_COLUMNS/10)) print('') From eb671fc0af77535a9ed7b9dd14296ebea1c5d9a7 Mon Sep 17 00:00:00 2001 From: Henry Cooke Date: Mon, 10 Aug 2020 21:54:17 +0100 Subject: [PATCH 07/10] fix .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1d17dae..db1a302 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.venv +*.pyc +.venv \ No newline at end of file From 3b1de9d161194af46c649b4f94f4a60a2cee94a1 Mon Sep 17 00:00:00 2001 From: Henry Cooke Date: Mon, 10 Aug 2020 21:54:46 +0100 Subject: [PATCH 08/10] remove pdb import --- punchcards/punchcard.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/punchcards/punchcard.py b/punchcards/punchcard.py index 989b887..98c73b9 100755 --- a/punchcards/punchcard.py +++ b/punchcards/punchcard.py @@ -10,8 +10,6 @@ from optparse import OptionParser import logging -import pdb - SPEC_IBM_MODEL_029 = "IBM Model 029 Punch Card" # only one for now CARD_COLUMNS = 80 From 88a97d8b1357dedb71f71dbdae625d9442c89514 Mon Sep 17 00:00:00 2001 From: Henry Cooke Date: Mon, 10 Aug 2020 23:33:11 +0100 Subject: [PATCH 09/10] separated card metrics into a data class --- punchcards/punchcard.py | 124 +++++++++++++++++++++++++++------------- 1 file changed, 84 insertions(+), 40 deletions(-) diff --git a/punchcards/punchcard.py b/punchcards/punchcard.py index 98c73b9..3a8d5e4 100755 --- a/punchcards/punchcard.py +++ b/punchcards/punchcard.py @@ -10,31 +10,65 @@ from optparse import OptionParser import logging -SPEC_IBM_MODEL_029 = "IBM Model 029 Punch Card" # only one for now - -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 - -CARD_SIDE_MARGIN_RATIO = CARD_SIDE_MARGIN/CARD_WIDTH # as proportion of card width (margin/width) -CARD_TOP_MARGIN_RATIO = CARD_TOPBOT_MARGIN/CARD_HEIGHT # as proportion of card height (margin/height) -CARD_ROW_HEIGHT_RATIO = CARD_ROW_HEIGHT/CARD_HEIGHT # as proportion of card height - works -CARD_COL_WIDTH_RATIO = CARD_COL_WIDTH/CARD_WIDTH # as proportion of card height - works -CARD_HOLE_HEIGHT_RATIO = CARD_HOLE_HEIGHT/CARD_HEIGHT # as proportion of card height - works -CARD_HOLE_WIDTH_RATIO = CARD_HOLE_WIDTH/CARD_WIDTH # as a proportion of card width + +# # 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 + BRIGHTNESS_THRESHOLD = 200 # pixel brightness value (i.e. (R+G+B)/3) +# Holds measurements for a givenpunchcard type +class CardMetrics(object): + def __init__( self, description, columns, rows, width, height, col_width, row_height, hole_width, hole_height, vertical_margin, horizontal_margin ): + self.description = description + self.columns = columns + self.rows = rows + self.width = width + self.height = height + self.col_width = col_width # Inches + self.row_height = row_height # Inches + self.hole_width = hole_width # Inches + self.hole_height = hole_height # Inches + self.vertical_margin = vertical_margin # Inches at top and bottom + self.horizontal_margin = horizontal_margin # Inches on each side + self.calculate() + + def calculate( self ): + self.side_margin_ratio = self.horizontal_margin/self.width # as proportion of card width (margin/width) + self.top_margin_ratio = self.vertical_margin/self.height # as proportion of card height (margin/height) + self.row_height_ratio = self.row_height/self.height # as proportion of card height - works + self.col_width_ratio = self.col_width/self.width # as proportion of card height - works + self.hole_height_ratio = self.hole_height/self.height # as proportion of card height - works + self.hole_width_ratio = self.hole_width/self.width # as a proportion of card width + + +CARD_METRICS = { + # only one for now + "IBM_MODEL_029" : CardMetrics( + # found measurements at http://www.quadibloc.com/comp/cardint.htm + description = "IBM Model 029 Punch Card", + columns = 80, + rows = 12, + width = 7.0 + 3.0/8.0, # Inches, + height = 3.25, # Inches, + col_width = 0.087, # Inches, + hole_width = 0.055, # Inches IBM, 0.056 Control Data, + row_height = 0.25, # Inches, + hole_height = 0.125, # Inches, + vertical_margin = 3.0/16.0, # Inches at top and bottom, + horizontal_margin = .2235 # Inches on each side + ) +} + + # Represents a punchcard image plus scanned data class PunchCard(object): @@ -78,7 +112,7 @@ def drange(self, start, stop, step=1.0): yield r r += step - def __init__(self, image, bright=-1, debug=False, xstart=0, xstop=0, ystart=0, ystop=0, xadjust=0): + def __init__(self, image, bright=-1, debug=False, xstart=0, xstop=0, ystart=0, ystop=0, xadjust=0, card_metrics=None): self.create_card_map() pass self.text = '' @@ -92,6 +126,7 @@ def __init__(self, image, bright=-1, debug=False, xstart=0, xstop=0, ystart=0, y self.xadjust = xadjust self.image = image self.pix = image.load() + self.card_metrics = card_metrics self._crop() if debug: self.logger.setLevel(logging.DEBUG) @@ -171,12 +206,12 @@ def _find_data_horiz_dimensions(self, probe_y): right_border = x break width = right_border - left_border - card_side_margin_width = int(width * CARD_SIDE_MARGIN_RATIO) + card_side_margin_width = int(width * self.card_metrics.side_margin_ratio) data_left_x = left_border + card_side_margin_width #data_right_x = right_border - card_side_margin_width - data_right_x = data_left_x + int((CARD_COLUMNS * width) * CARD_COL_WIDTH/CARD_WIDTH) - col_width = width * CARD_COL_WIDTH_RATIO - hole_width = width * CARD_HOLE_WIDTH_RATIO + data_right_x = data_left_x + int((self.card_metrics.columns * width) * self.card_metrics.col_width/self.card_metrics.width) + col_width = width * self.card_metrics.col_width_ratio + hole_width = width * self.card_metrics.hole_width_ratio if self.logger.isEnabledFor(logging.DEBUG): # mark left and right edges on the copy for y in range(int(probe_y - self.ysize/100), int(probe_y + self.ysize/100)): @@ -201,11 +236,11 @@ def _find_data_vert_dimensions(self): bottom_border = y break card_height = bottom_border - top_border - card_top_margin = int(card_height * CARD_TOP_MARGIN_RATIO) + card_top_margin = int(card_height * self.card_metrics.top_margin_ratio) data_begins = top_border + card_top_margin - hole_height = int(card_height * CARD_HOLE_HEIGHT_RATIO) + hole_height = int(card_height * self.card_metrics.hole_height_ratio) data_top_y = data_begins + hole_height / 2 - col_height = int(card_height * CARD_ROW_HEIGHT_RATIO) + col_height = int(card_height * self.card_metrics.row_height_ratio) if self.logger.isEnabledFor(logging.DEBUG): # mark up the copy with the edges for x in range(self.xmin, self.xmax-1): @@ -236,8 +271,8 @@ def _scan(self, bright=-1): # Chads are narrow so find then heuristically by accumulating pixel brightness # along the row. Should be forgiving if the image is slightly wonky. y = y_data_pos #- col_height/8 - for row_num in range(CARD_ROWS): - probe_y = y + col_height if row_num == 0 else ( y - col_height if row_num == CARD_ROWS -1 else y ) # Line 0 has a corner missing + for row_num in range(self.card_metrics.rows): + probe_y = y + col_height if row_num == 0 else ( y - col_height if row_num == self.card_metrics.rows -1 else y ) # line 0 has a corner missing x_data_left, x_data_right, col_width, hole_width = self._find_data_horiz_dimensions(probe_y) left_edge = -1 # of a punch-hole for x in range(x_data_left, x_data_right): @@ -276,10 +311,10 @@ def _scan(self, bright=-1): self._input("Press Enter to continue...") self.decoded = [] # Could fold this loop into the previous one - but would it be faster? - for col in range(0, CARD_COLUMNS): + for col in range(0, self.card_metrics.columns): col_pattern = [] col_surface = [] - for row in range(CARD_ROWS): + for row in range(self.card_metrics.rows): key = (col, row) # avergage for 1/3 of a column is greater than the threshold col_pattern.append('O' if key in data else ' ') @@ -295,9 +330,9 @@ def _scan(self, bright=-1): # ASCII art image of card def dump(self, id, raw_data=False): print(' Card Dump of Image file:', id, 'Format', 'Raw' if raw_data else 'Dump', 'threshold=', self.threshold) - print(' ' + '123456789-' * int(CARD_COLUMNS/10)) - print(' ' + '_' * CARD_COLUMNS + ' ') - print('/' + self.text + '_' * (CARD_COLUMNS - len(self.text)) + '|') + print(' ' + '123456789-' * int(self.card_metrics.columns/10)) + print(' ' + '_' * self.card_metrics.columns + ' ') + print('/' + self.text + '_' * (self.card_metrics.columns - len(self.text)) + '|') for rnum in range(len(self.decoded[0])): sys.stdout.write('|') if raw_data: @@ -307,8 +342,8 @@ def dump(self, id, raw_data=False): for col in self.decoded: sys.stdout.write(col[rnum] if col[rnum] == 'O' else '.') print('|') - print('`' + '-' * CARD_COLUMNS + "'") - print(' ' + '123456789-' * int(CARD_COLUMNS/10)) + print('`' + '-' * self.card_metrics.columns + "'") + print(' ' + '123456789-' * int(self.card_metrics.columns/10)) print('') @@ -325,7 +360,6 @@ def main(): decode punch card image into ASCII.""" parser = OptionParser(usage) parser.add_option('-b', '--bright-threshold', type='int', dest='bright', default=-1, help='Brightness (R+G+B)/3, e.g. 127.') - parser.add_option('-s', '--side-margin-ratio', type='float', dest='side_margin_ratio', default=CARD_SIDE_MARGIN_RATIO, help='Manually set side margin ratio (sideMargin/cardWidth).') parser.add_option('-d', '--dump', action='store_true', dest='dump', help='Output an ASCII-art version of the card.') parser.add_option('-i', '--display-image', action='store_true', dest='display', help='Display an anotated version of the image.') parser.add_option('-r', '--dump-raw', action='store_true', dest='dumpraw', help='Output ASCII-art with raw row/column accumulator values.') @@ -334,11 +368,21 @@ def main(): parser.add_option('-y', '--y-start', type='int', dest='ystart', default=0, help='Start looking for a card edge at y position') parser.add_option('-Y', '--y-stop', type='int', dest='ystop', default=0, help='Stop looking for a card edge at y position') parser.add_option('-a', '--adjust-x', type='int', dest='xadjust', default=0, help='Adjust middle edge detect location (pixels)') + parser.add_option('-s', '--side-margin-ratio', type='float', dest='side_margin_ratio', default=0, help='Manually set side margin ratio (sideMargin/cardWidth).') + parser.add_option('-w', '--row-height', type='float', dest='row_height', default=0, help='Manually set row height (inches)') (options, args) = parser.parse_args() for arg in args: image = Image.open(arg) - card = PunchCard(image, bright=options.bright, debug=options.display, xstart=options.xstart, xstop=options.xstop, ystart=options.ystart, ystop=options.ystop, xadjust=options.xadjust) + + metrics = CARD_METRICS[ "IBM_MODEL_029" ] + if options.row_height != 0: + metrics.row_height = options.row_height + if options.side_margin_ratio != 0: + metrics.side_margin_ratio = options.side_margin_ratio + metrics.calculate() + + card = PunchCard(image, bright=options.bright, debug=options.display, xstart=options.xstart, xstop=options.xstop, ystart=options.ystart, ystop=options.ystop, xadjust=options.xadjust, card_metrics=metrics ) print(card.text) if (options.dump): card.dump(arg) From ae0f436d664db071b39886b982d821ca44e61960 Mon Sep 17 00:00:00 2001 From: Henry Cooke Date: Mon, 10 Aug 2020 23:40:22 +0100 Subject: [PATCH 10/10] wqRevert "separated card metrics into a data class" This reverts commit 88a97d8b1357dedb71f71dbdae625d9442c89514. --- punchcards/punchcard.py | 124 +++++++++++++--------------------------- 1 file changed, 40 insertions(+), 84 deletions(-) diff --git a/punchcards/punchcard.py b/punchcards/punchcard.py index 3a8d5e4..98c73b9 100755 --- a/punchcards/punchcard.py +++ b/punchcards/punchcard.py @@ -10,65 +10,31 @@ from optparse import OptionParser import logging - -# # 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 - +SPEC_IBM_MODEL_029 = "IBM Model 029 Punch Card" # only one for now + +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 + +CARD_SIDE_MARGIN_RATIO = CARD_SIDE_MARGIN/CARD_WIDTH # as proportion of card width (margin/width) +CARD_TOP_MARGIN_RATIO = CARD_TOPBOT_MARGIN/CARD_HEIGHT # as proportion of card height (margin/height) +CARD_ROW_HEIGHT_RATIO = CARD_ROW_HEIGHT/CARD_HEIGHT # as proportion of card height - works +CARD_COL_WIDTH_RATIO = CARD_COL_WIDTH/CARD_WIDTH # as proportion of card height - works +CARD_HOLE_HEIGHT_RATIO = CARD_HOLE_HEIGHT/CARD_HEIGHT # as proportion of card height - works +CARD_HOLE_WIDTH_RATIO = CARD_HOLE_WIDTH/CARD_WIDTH # as a proportion of card width BRIGHTNESS_THRESHOLD = 200 # pixel brightness value (i.e. (R+G+B)/3) -# Holds measurements for a givenpunchcard type -class CardMetrics(object): - def __init__( self, description, columns, rows, width, height, col_width, row_height, hole_width, hole_height, vertical_margin, horizontal_margin ): - self.description = description - self.columns = columns - self.rows = rows - self.width = width - self.height = height - self.col_width = col_width # Inches - self.row_height = row_height # Inches - self.hole_width = hole_width # Inches - self.hole_height = hole_height # Inches - self.vertical_margin = vertical_margin # Inches at top and bottom - self.horizontal_margin = horizontal_margin # Inches on each side - self.calculate() - - def calculate( self ): - self.side_margin_ratio = self.horizontal_margin/self.width # as proportion of card width (margin/width) - self.top_margin_ratio = self.vertical_margin/self.height # as proportion of card height (margin/height) - self.row_height_ratio = self.row_height/self.height # as proportion of card height - works - self.col_width_ratio = self.col_width/self.width # as proportion of card height - works - self.hole_height_ratio = self.hole_height/self.height # as proportion of card height - works - self.hole_width_ratio = self.hole_width/self.width # as a proportion of card width - - -CARD_METRICS = { - # only one for now - "IBM_MODEL_029" : CardMetrics( - # found measurements at http://www.quadibloc.com/comp/cardint.htm - description = "IBM Model 029 Punch Card", - columns = 80, - rows = 12, - width = 7.0 + 3.0/8.0, # Inches, - height = 3.25, # Inches, - col_width = 0.087, # Inches, - hole_width = 0.055, # Inches IBM, 0.056 Control Data, - row_height = 0.25, # Inches, - hole_height = 0.125, # Inches, - vertical_margin = 3.0/16.0, # Inches at top and bottom, - horizontal_margin = .2235 # Inches on each side - ) -} - - # Represents a punchcard image plus scanned data class PunchCard(object): @@ -112,7 +78,7 @@ def drange(self, start, stop, step=1.0): yield r r += step - def __init__(self, image, bright=-1, debug=False, xstart=0, xstop=0, ystart=0, ystop=0, xadjust=0, card_metrics=None): + def __init__(self, image, bright=-1, debug=False, xstart=0, xstop=0, ystart=0, ystop=0, xadjust=0): self.create_card_map() pass self.text = '' @@ -126,7 +92,6 @@ def __init__(self, image, bright=-1, debug=False, xstart=0, xstop=0, ystart=0, y self.xadjust = xadjust self.image = image self.pix = image.load() - self.card_metrics = card_metrics self._crop() if debug: self.logger.setLevel(logging.DEBUG) @@ -206,12 +171,12 @@ def _find_data_horiz_dimensions(self, probe_y): right_border = x break width = right_border - left_border - card_side_margin_width = int(width * self.card_metrics.side_margin_ratio) + card_side_margin_width = int(width * CARD_SIDE_MARGIN_RATIO) data_left_x = left_border + card_side_margin_width #data_right_x = right_border - card_side_margin_width - data_right_x = data_left_x + int((self.card_metrics.columns * width) * self.card_metrics.col_width/self.card_metrics.width) - col_width = width * self.card_metrics.col_width_ratio - hole_width = width * self.card_metrics.hole_width_ratio + data_right_x = data_left_x + int((CARD_COLUMNS * width) * CARD_COL_WIDTH/CARD_WIDTH) + col_width = width * CARD_COL_WIDTH_RATIO + hole_width = width * CARD_HOLE_WIDTH_RATIO if self.logger.isEnabledFor(logging.DEBUG): # mark left and right edges on the copy for y in range(int(probe_y - self.ysize/100), int(probe_y + self.ysize/100)): @@ -236,11 +201,11 @@ def _find_data_vert_dimensions(self): bottom_border = y break card_height = bottom_border - top_border - card_top_margin = int(card_height * self.card_metrics.top_margin_ratio) + card_top_margin = int(card_height * CARD_TOP_MARGIN_RATIO) data_begins = top_border + card_top_margin - hole_height = int(card_height * self.card_metrics.hole_height_ratio) + hole_height = int(card_height * CARD_HOLE_HEIGHT_RATIO) data_top_y = data_begins + hole_height / 2 - col_height = int(card_height * self.card_metrics.row_height_ratio) + col_height = int(card_height * CARD_ROW_HEIGHT_RATIO) if self.logger.isEnabledFor(logging.DEBUG): # mark up the copy with the edges for x in range(self.xmin, self.xmax-1): @@ -271,8 +236,8 @@ def _scan(self, bright=-1): # Chads are narrow so find then heuristically by accumulating pixel brightness # along the row. Should be forgiving if the image is slightly wonky. y = y_data_pos #- col_height/8 - for row_num in range(self.card_metrics.rows): - probe_y = y + col_height if row_num == 0 else ( y - col_height if row_num == self.card_metrics.rows -1 else y ) # line 0 has a corner missing + for row_num in range(CARD_ROWS): + probe_y = y + col_height if row_num == 0 else ( y - col_height if row_num == CARD_ROWS -1 else y ) # Line 0 has a corner missing x_data_left, x_data_right, col_width, hole_width = self._find_data_horiz_dimensions(probe_y) left_edge = -1 # of a punch-hole for x in range(x_data_left, x_data_right): @@ -311,10 +276,10 @@ def _scan(self, bright=-1): self._input("Press Enter to continue...") self.decoded = [] # Could fold this loop into the previous one - but would it be faster? - for col in range(0, self.card_metrics.columns): + for col in range(0, CARD_COLUMNS): col_pattern = [] col_surface = [] - for row in range(self.card_metrics.rows): + for row in range(CARD_ROWS): key = (col, row) # avergage for 1/3 of a column is greater than the threshold col_pattern.append('O' if key in data else ' ') @@ -330,9 +295,9 @@ def _scan(self, bright=-1): # ASCII art image of card def dump(self, id, raw_data=False): print(' Card Dump of Image file:', id, 'Format', 'Raw' if raw_data else 'Dump', 'threshold=', self.threshold) - print(' ' + '123456789-' * int(self.card_metrics.columns/10)) - print(' ' + '_' * self.card_metrics.columns + ' ') - print('/' + self.text + '_' * (self.card_metrics.columns - len(self.text)) + '|') + print(' ' + '123456789-' * int(CARD_COLUMNS/10)) + print(' ' + '_' * CARD_COLUMNS + ' ') + print('/' + self.text + '_' * (CARD_COLUMNS - len(self.text)) + '|') for rnum in range(len(self.decoded[0])): sys.stdout.write('|') if raw_data: @@ -342,8 +307,8 @@ def dump(self, id, raw_data=False): for col in self.decoded: sys.stdout.write(col[rnum] if col[rnum] == 'O' else '.') print('|') - print('`' + '-' * self.card_metrics.columns + "'") - print(' ' + '123456789-' * int(self.card_metrics.columns/10)) + print('`' + '-' * CARD_COLUMNS + "'") + print(' ' + '123456789-' * int(CARD_COLUMNS/10)) print('') @@ -360,6 +325,7 @@ def main(): decode punch card image into ASCII.""" parser = OptionParser(usage) parser.add_option('-b', '--bright-threshold', type='int', dest='bright', default=-1, help='Brightness (R+G+B)/3, e.g. 127.') + parser.add_option('-s', '--side-margin-ratio', type='float', dest='side_margin_ratio', default=CARD_SIDE_MARGIN_RATIO, help='Manually set side margin ratio (sideMargin/cardWidth).') parser.add_option('-d', '--dump', action='store_true', dest='dump', help='Output an ASCII-art version of the card.') parser.add_option('-i', '--display-image', action='store_true', dest='display', help='Display an anotated version of the image.') parser.add_option('-r', '--dump-raw', action='store_true', dest='dumpraw', help='Output ASCII-art with raw row/column accumulator values.') @@ -368,21 +334,11 @@ def main(): parser.add_option('-y', '--y-start', type='int', dest='ystart', default=0, help='Start looking for a card edge at y position') parser.add_option('-Y', '--y-stop', type='int', dest='ystop', default=0, help='Stop looking for a card edge at y position') parser.add_option('-a', '--adjust-x', type='int', dest='xadjust', default=0, help='Adjust middle edge detect location (pixels)') - parser.add_option('-s', '--side-margin-ratio', type='float', dest='side_margin_ratio', default=0, help='Manually set side margin ratio (sideMargin/cardWidth).') - parser.add_option('-w', '--row-height', type='float', dest='row_height', default=0, help='Manually set row height (inches)') (options, args) = parser.parse_args() for arg in args: image = Image.open(arg) - - metrics = CARD_METRICS[ "IBM_MODEL_029" ] - if options.row_height != 0: - metrics.row_height = options.row_height - if options.side_margin_ratio != 0: - metrics.side_margin_ratio = options.side_margin_ratio - metrics.calculate() - - card = PunchCard(image, bright=options.bright, debug=options.display, xstart=options.xstart, xstop=options.xstop, ystart=options.ystart, ystop=options.ystop, xadjust=options.xadjust, card_metrics=metrics ) + card = PunchCard(image, bright=options.bright, debug=options.display, xstart=options.xstart, xstop=options.xstop, ystart=options.ystart, ystop=options.ystop, xadjust=options.xadjust) print(card.text) if (options.dump): card.dump(arg)