From d33779f301d4c116b5d179a70407cd8699f63335 Mon Sep 17 00:00:00 2001 From: Jim Barlow Date: Thu, 9 Jan 2014 01:37:28 -0800 Subject: [PATCH 1/2] Detect monochrome images and extract them as PBM (1 bpp) --- src/ocrPage.sh | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/ocrPage.sh b/src/ocrPage.sh index 1a023e05..2cb70880 100644 --- a/src/ocrPage.sh +++ b/src/ocrPage.sh @@ -43,7 +43,7 @@ FORCE_OCR="${14}" # Force to OCR, even if the page already contains fonts # - 2: in case the page contains more than one image ################################## getImgInfo() { - local page widthPDF heightPDF curImgInfo nbImg curImg propCurImg widthCurImg heightCurImg colorspaceCurImg dpi + local page widthPDF heightPDF curImgInfo nbImg curImg propCurImg widthCurImg heightCurImg colorspaceCurImg depthCurImg dpi # page number page="$1" @@ -73,10 +73,11 @@ getImgInfo() { fi # Get characteristics of the extracted image curImg=`ls -1 "$curOrigImg"*` - propCurImg=`identify -format "%w %h %[colorspace]" "$curImg"` + propCurImg=`identify -format "%w %h %[colorspace] %[depth]" "$curImg"` widthCurImg=`echo "$propCurImg" | cut -f1 -d" "` heightCurImg=`echo "$propCurImg" | cut -f2 -d" "` colorspaceCurImg=`echo "$propCurImg" | cut -f3 -d" "` + depthCurImg=`echo "$propCurImg" | cut -f4 -d" "` [ $VERBOSITY -ge $LOG_DEBUG ] && echo "Page $page: Size ${heightCurImg}x${widthCurImg} (in pixel)" # compute the resolution of the image (making the assumption that x & y resolution are equal) @@ -87,6 +88,7 @@ getImgInfo() { # save the image characteristics echo "DPI=$dpi" > "$curImgInfo" echo "COLOR_SPACE=$colorspaceCurImg" >> "$curImgInfo" + echo "DEPTH=$depthCurImg" >> "$curImgInfo" return 0 } @@ -109,6 +111,7 @@ curImgInfo="$TMP_FLD/${page}.orig-img-info.txt" # Detected characteristics of # auto-detect the characteristics of the embedded image +depthCurImg="8" getImgInfo "$page" "$widthPDF" "$heightPDF" "$curImgInfo" ret_code="$?" # in case the page contains text do not OCR, unless the FORCE_OCR flag is set @@ -127,6 +130,7 @@ else # read the image characteristics from the file dpi=`cat "$curImgInfo" | grep "^DPI=" | cut -f2 -d"="` colorspaceCurImg=`cat "$curImgInfo" | grep "^COLOR_SPACE=" | cut -f2 -d"="` + depthCurImg=`cat "$curImgInfo" | grep "^DEPTH=" | cut -f2 -d"="` fi # perform oversampling if the resolution is not sufficient to get good OCR results @@ -140,7 +144,10 @@ fi # Identify if page image should be saved as ppm (color) or pgm (gray) ext="ppm" opt="" -if [ "$colorspaceCurImg" = "Gray" ]; then +if [ "$colorspaceCurImg" = "Gray" ] && [ "$depthCurImg" = "1" ]; then + ext="pbm" + opt="-mono" +elif [ "$colorspaceCurImg" = "Gray" ]; then ext="pgm" opt="-gray" fi @@ -209,4 +216,4 @@ if [ $KEEP_TMP -eq 0 ]; then rm -f "$curImgInfo" fi -exit 0 \ No newline at end of file +exit 0 From 9aa21710522896f6aaacc47e383b95f3ebe75181 Mon Sep 17 00:00:00 2001 From: Jim Barlow Date: Thu, 9 Jan 2014 02:59:17 -0800 Subject: [PATCH 2/2] Monkeypatch reportlab to output grayscale and monochrome colorspaces --- src/hocrTransform.py | 124 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 104 insertions(+), 20 deletions(-) diff --git a/src/hocrTransform.py b/src/hocrTransform.py index ce4f253d..006e2938 100644 --- a/src/hocrTransform.py +++ b/src/hocrTransform.py @@ -6,12 +6,95 @@ # Initial version by Jonathan Brinley, jonathanbrinley@gmail.com ############################################################################## from reportlab.pdfgen.canvas import Canvas +from reportlab.pdfgen.pdfimages import PDFImage from reportlab.lib.units import inch from lxml import etree as ElementTree from PIL import Image import re, sys import argparse + +def monkeypatch_method(cls): + ''' + Override a class method at runtime. + + Rationale: + https://mail.python.org/pipermail/python-dev/2008-January/076194.html + ''' + def decorator(func): + setattr(cls, func.__name__, func) + return func + return decorator + + +@monkeypatch_method(PDFImage) +def PIL_imagedata(self): + ''' + Add ability to output greyscale and 1-bit PIL images without conversion to RGB. + + The upstream Python 2.7 version of reportlab converts 1-bit PIL images to RGB + instead of saving them in a lower BPP format. They have since added the following + fix to their Python 3.3 branch, but it has not been back-ported. + + https://bitbucket.org/rptlab/reportlab/commits/177ddcbe4df6f9b461dac62612df9b8da3966a5d + ''' + image = self.image + if image.format == 'JPEG': + fp = image.fp + fp.seek(0) + return self._jpg_imagedata(fp) + + from reportlab.lib.utils import import_zlib + from reportlab import rl_config + from reportlab.pdfbase.pdfutils import _AsciiBase85Encode, _chunker + + self.source = 'PIL' + zlib = import_zlib() + if not zlib: + return + + bpc = 8 + # Use the colorSpace in the image + if image.mode == 'CMYK': + myimage = image + colorSpace = 'DeviceCMYK' + bpp = 4 + elif image.mode == '1': + myimage = image + colorSpace = 'DeviceGray' + bpp = 1 + bpc = 1 + elif image.mode == 'L': + myimage = image + colorSpace = 'DeviceGray' + bpp = 1 + else: + myimage = image.convert('RGB') + colorSpace = 'RGB' + bpp = 3 + imgwidth, imgheight = myimage.size + + # this describes what is in the image itself + # *NB* according to the spec you can only use the short form in inline images + + imagedata = ['BI /W %d /H %d /BPC %d /CS /%s /F [%s/Fl] ID' % + (imgwidth, imgheight, bpc, colorSpace, rl_config.useA85 and '/A85 ' or '')] + + # use a flate filter and, optionally, Ascii Base 85 to compress + raw = myimage.tostring() + rowstride = (imgwidth * bpc * bpp + 7) / 8 + assert len(raw) == rowstride * imgheight, "Wrong amount of data for image" + data = zlib.compress(raw) # this bit is very fast... + + if rl_config.useA85: + # ...sadly this may not be + data = _AsciiBase85Encode(data) + # append in blocks of 60 characters + _chunker(data, imagedata) + imagedata.append('EI') + return (imagedata, imgwidth, imgheight) + + class hocrTransform(): """ A class for converting documents from the hOCR format. @@ -24,20 +107,20 @@ class hocrTransform(): 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) self.xmlns = '' if matches: self.xmlns = matches.group(1) - + # get dimension in pt (not pixel!!!!) of the OCRed image for div in self.hocr.findall(".//%sdiv[@class='ocr_page']"%(self.xmlns)): coords = self.element_coordinates(div) self.width = self.px2pt(coords[2]-coords[0]) self.height = self.px2pt(coords[3]-coords[1]) break # there shouldn't be more than one, and if there is, we don't want it - + # no width and heigh definition in the ocr_image element of the hocr file if self.width is None: print("No page dimension found in the hocr file") @@ -54,7 +137,7 @@ class hocrTransform(): 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 @@ -67,7 +150,7 @@ class hocrTransform(): 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 @@ -80,13 +163,13 @@ class hocrTransform(): coords = matches.group(1).split() out = (int(coords[0]),int(coords[1]),int(coords[2]),int(coords[3])) return out - + def px2pt(self, pxl): """ Returns the length in pt given length in pxl """ return float(pxl)/self.dpi*inch - + def to_pdf(self, outFileName, imageFileName, showBoundingboxes, fontname="Helvetica"): """ Creates a PDF file with an image superimposed on top of the text. @@ -97,13 +180,13 @@ class hocrTransform(): """ # create the PDF file pdf = Canvas(outFileName, pagesize=(self.width, self.height), pageCompression=1) # page size in points (1/72 in.) - + # draw bounding box for each paragraph pdf.setStrokeColorRGB(0,1,1) # light blue for bounding box of paragraph pdf.setFillColorRGB(0,1,1) # light blue for bounding box of paragraph pdf.setLineWidth(0) # no line for bounding box for elem in self.hocr.findall(".//%sp[@class='%s']" % (self.xmlns, "ocr_par")): - + elemtxt=self._get_element_text(elem).rstrip() if len(elemtxt) == 0: continue @@ -113,12 +196,12 @@ class hocrTransform(): y1=self.px2pt(coords[1]) x2=self.px2pt(coords[2]) y2=self.px2pt(coords[3]) - + # draw the bbox border if showBoundingboxes == True: - pdf.rect(x1, self.height-y2, x2-x1, y2-y1, fill=1) - - + pdf.rect(x1, self.height-y2, x2-x1, y2-y1, fill=1) + + # check if element with class 'ocrx_word' are available # otherwise use 'ocr_line' as fallback elemclass="ocr_line" @@ -141,7 +224,7 @@ class hocrTransform(): y1=self.px2pt(coords[1]) x2=self.px2pt(coords[2]) y2=self.px2pt(coords[3]) - + # draw the bbox border if showBoundingboxes == True: pdf.rect(x1, self.height-y2, x2-x1, y2-y1, fill=0) @@ -152,7 +235,7 @@ class hocrTransform(): # set cursor to bottom left corner of bbox (adjust for dpi) text.setTextOrigin(x1, self.height-y2) - + # scale the width of the text to fill the width of the bbox text.setHorizScale(100*(x2-x1)/pdf.stringWidth(elemtxt, fontname, fontsize)) @@ -162,13 +245,14 @@ class hocrTransform(): # put the image on the page, scaled to fill the page if imageFileName != None: - im = Image.open(imageFileName) + im = Image.open(imageFileName) pdf.drawInlineImage(im, 0, 0, width=self.width, height=self.height) - + # finish up the page and save it pdf.showPage() pdf.save() - + + if __name__ == "__main__": parser = argparse.ArgumentParser(description='Convert hocr file to PDF') parser.add_argument('-b', '--boundingboxes', action="store_true", default=False, help='Show bounding boxes borders') @@ -181,5 +265,5 @@ if __name__ == "__main__": hocr = hocrTransform(args.hocrfile, args.resolution) hocr.to_pdf(args.outputfile, args.image, args.boundingboxes) - - + +