From 4e4b5ddc58067a91d019e2f9e3fad3e00cbd1f80 Mon Sep 17 00:00:00 2001 From: fritz-hh Date: Tue, 9 Apr 2013 19:00:26 +0200 Subject: [PATCH] initial version --- OCRmyPDF.sh | 53 +++++++++++++++ hocrTransform.py | 171 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 OCRmyPDF.sh create mode 100644 hocrTransform.py diff --git a/OCRmyPDF.sh b/OCRmyPDF.sh new file mode 100644 index 00000000..9d82673d --- /dev/null +++ b/OCRmyPDF.sh @@ -0,0 +1,53 @@ +#!/bin/sh +echo "usage: ./scan-archive.sh filename.pdf" + +infile="$1" +lan="deu" +dpi=300 + +tmp="./tmp" + +# delete tmp files +rm -r -f "${tmp}" +mkdir -p "${tmp}" + +# detect the number of page of the pdf file +numpages=`pdftk $infile "dump_data" | grep "NumberOfPages" | cut -f2 -d" "` +echo "PDF file has $numpages pages" + +# Itterate the pages of the pdf file +page="1" +while [ $page -le $numpages ]; do + + # extract image from current pdf page + echo "Page $page: Extracting image from PDF" + pdftk "$infile" cat $page output "${tmp}/${page}.pdf" > /dev/null + pdftoppm -r $dpi "${tmp}/${page}.pdf" > "${tmp}/${page}.ppm" + + # improve quality of the image to get better ocr results + echo "Page $page: Preprocessing image with unpaper" + unpaper --dpi $dpi --mask-scan-size 100 \ + --no-grayfilter --no-blackfilter --no-mask-center --no-border-align \ + --overwrite "$tmp/$page.ppm" "$tmp/$page.forocr.ppm" > /dev/null + + # perform OCR + echo "Page $page: Performing OCR" + tesseract -l ${lan} "${tmp}/${page}.forocr.ppm" "${tmp}/${page}.hocr" hocr 1> /dev/null 2> /dev/null + cp "${tmp}/${page}.hocr.html" "${tmp}/${page}.hocr" + + # embed text and image to new pdf file + echo "Page $page: Embedding text in PDF" + python hocrTransform.py "${tmp}/${page}.hocr" "${tmp}/${page}.forocr.ppm" "${tmp}/${page}-ocred.pdf" + + # go to next page of the pdf + page=$(($page+1)) +done + + +# concatenate all pages +pdftk ${tmp}/*-ocred.pdf cat output "${tmp}/ocred.pdf" + +# insert metadata + +# validate generated pdf file (compliance to PDF/A) +#java -jar jhove/bin/JhoveApp.jar -m PDF-hul "$1" |egrep "Status|Message" diff --git a/hocrTransform.py b/hocrTransform.py new file mode 100644 index 00000000..046cf4e5 --- /dev/null +++ b/hocrTransform.py @@ -0,0 +1,171 @@ +from reportlab.pdfgen.canvas import Canvas +from reportlab.lib.units import inch +#from xml.etree.ElementTree import ElementTree +from lxml import etree as ElementTree +import Image, re, sys + +class HocrConverter(): + """ + A class for converting documents to/from the hOCR format. + For details of the hOCR format, see: + http://docs.google.com/View?docid=dfxcv4vc_67g844kf + See also: + http://code.google.com/p/hocr-tools/ + Basic usage: + Create a PDF from an hOCR file and an image: + hocr = HocrConverter("path/to/hOCR/file") + hocr.to_pdf("path/to/image/file", "path/to/output/file") + """ + def __init__(self, hocrFileName = None): + self.hocr = None + self.xmlns = '' + self.boxPattern = re.compile('bbox((\s+\d+){4})') + if hocrFileName is not None: + self.parse_hocr(hocrFileName) + + def __str__(self): + """ + Return the textual content of the HTML body + """ + if self.hocr is None: + return '' + body = self.hocr.find(".//%sbody"%(self.xmlns)) + if body: + return self._get_element_text(body).encode('utf-8') # XML gives unicode + else: + return '' + + def _get_element_text(self, element): + """ + Return the textual content of the element and its children + """ + text = '' + if element.text is not None: + text = text + element.text + for child in element.getchildren(): + text = text + self._get_element_text(child) + if element.tail is not None: + text = text + element.tail + return text + + def element_coordinates(self, element): + """ + Returns a tuple containing the coordinates of the bounding box around + an element + """ + out = (0,0,0,0) + if 'title' in element.attrib: + matches = self.boxPattern.search(element.attrib['title']) + if matches: + coords = matches.group(1).split() + out = (int(coords[0]),int(coords[1]),int(coords[2]),int(coords[3])) + return out + + def parse_hocr(self, hocrFileName): + """ + Reads an XML/XHTML file into an ElementTree object + """ + self.hocr = ElementTree.ElementTree() + self.hocr.parse(hocrFileName) + + # if the hOCR file has a namespace, ElementTree requires its use to find elements + matches = re.match('({.*})html', self.hocr.getroot().tag) + if matches: + self.xmlns = matches.group(1) + else: + self.xmlns = '' + + def to_pdf(self, imageFileName, outFileName, fontname="Courier"): + """ + Creates a PDF file with an image superimposed on top of the text. + Text is positioned according to the bounding box of the lines in + the hOCR file. + The image need not be identical to the image used to create the hOCR file. + It can be scaled, have a lower resolution, different color mode, etc. + """ + if self.hocr is None: + print "No hocr file provided. exiting" + exit + + im = Image.open(imageFileName) + if 'dpi' in im.info: + width = float(im.size[0])/im.info['dpi'][0] + height = float(im.size[1])/im.info['dpi'][1] + else: + # we have to make a reasonable guess + # set to None for now and try again using info from hOCR file + width = height = None + + # get dimensions of the OCR, which may not match the image + ocr_dpi = (300, 300) # a default, in case we can't find it + for div in self.hocr.findall(".//%sdiv[@class='ocr_page']"%(self.xmlns)): + coords = self.element_coordinates(div) + ocrwidth = coords[2]-coords[0] + ocrheight = coords[3]-coords[1] + if width is None: + # no dpi info with the image + # assume OCR was done at 300 dpi + width = ocrwidth/300 + height = ocrheight/300 + ocr_dpi = (ocrwidth/width, ocrheight/height) + break # there shouldn't be more than one, and if there is, we don't want it + + if width is None: + # no dpi info within the image, and no help from the hOCR file either + # this will probably end up looking awful, so issue a warning + print "Warning: DPI unavailable for image %s. Assuming 300 DPI."%(imageFileName) + width = float(im.size[0])/300 + height = float(im.size[1])/300 + + # create the PDF file + pdf = Canvas(outFileName, pagesize=(width*inch, height*inch), pageCompression=1) # page size in points (1/72 in.) + + # put the image on the page, scaled to fill the page + #pdf.drawInlineImage(im, 0, 0, width=width*inch, height=height*inch) + + # check if element with class 'ocrx_word' are available + # otherwise use 'ocr_line' as fallback + elemclass="ocr_line" + if self.hocr.find(".//%sspan[@class='ocrx_word']" %(self.xmlns)) is not None: + elemclass="ocrx_word" + + + for elem in self.hocr.findall(".//%sspan[@class='%s']" % (self.xmlns, elemclass)): + + elemtxt=self._get_element_text(elem).rstrip() + if len(elemtxt) == 0: + continue + + coords = self.element_coordinates(elem) + + # compute font size according to bbox coordinates + fontsize=(coords[3]-coords[1])/(ocr_dpi[1]*(0.35146/25.4)) + + # draw the bbox border + #pdf.rect((float(coords[0])/ocr_dpi[0])*inch, (height*inch)-(float(coords[3])/ocr_dpi[1])*inch,(float(coords[2]-coords[0])/ocr_dpi[0])*inch, float(coords[3]-coords[1])/ocr_dpi[1]*inch, fill=0) + + text = pdf.beginText() + text.setFont(fontname, fontsize) + + #text.setTextRenderMode(3) # invisible + + # set cursor to bottom left corner of bbox (adjust for dpi) + text.setTextOrigin((float(coords[0])/ocr_dpi[0])*inch, (height*inch)-(float(coords[3])/ocr_dpi[1])*inch) + + # scale the width of the text to fill the width of the bbox + text.setHorizScale((((float(coords[2])/ocr_dpi[0]*inch)-(float(coords[0])/ocr_dpi[0]*inch))/pdf.stringWidth(elemtxt, fontname, fontsize))*100) + + # write the text to the page + text.textLine(elemtxt) + pdf.drawText(text) + + # finish up the page and save it + pdf.showPage() + pdf.save() + +if __name__ == "__main__": + if len(sys.argv) < 4: + print 'Usage: python HocrConverter.py inputHocrFile inputImageFile outputPdfFile' + sys.exit(1) + hocr = HocrConverter(sys.argv[1]) + hocr.to_pdf(sys.argv[2], sys.argv[3])