Implemented DataMatrix analyzer.

Other architecture enhancements involving generalization
of analyzers to create new once more easily, improved
spawn() function to allow it to be run in several simultaneous
threads.
This commit is contained in:
Albert Cervera i Areny 2008-12-23 20:29:28 +01:00
parent 327542e74d
commit ec3b13fac2
9 changed files with 274 additions and 51 deletions

57
NaNScaN/analyzer.py Normal file
View File

@ -0,0 +1,57 @@
# coding=iso-8859-1
# Copyright (C) 2008 by Albert Cervera i Areny
# albert@nan-tic.com
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the
# Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
import subprocess
import tempfile
import codecs
import os
class Analyzer:
analyzers = {}
def __init__(self):
self.boxes = []
@staticmethod
def registerAnalyzer(name, analyzer):
Analyzer.analyzers[name] = analyzer
@staticmethod
def unregisterAnalyzer(name):
del Analyzer.analyzers[name]
@staticmethod
def create(name):
return Analyzer.analyzers[name]()
def scan(self, image):
pass
def textInRegion(self, region):
pass
def featureRectInRegion(self, region):
pass
# Spawn process and return STDOUT
def spawn(self, command, *args):
command = [ command ] + list( args )
process = subprocess.Popen( command , stdout=subprocess.PIPE )
content = process.communicate()[0]
return content

View File

@ -23,6 +23,7 @@ from string import lower
import os
import tempfile
from temporaryfile import *
from analyzer import *
class Box:
def __init__(self):
@ -30,25 +31,10 @@ class Box:
self.type = None
self.position = None
class Barcode:
class Barcode(Analyzer):
def __init__(self):
self.boxes = []
# Spawn process and return STDOUT
def spawn(self, command, *args):
(fd,filename) = tempfile.mkstemp()
previousFd = os.dup(1)
os.dup2(fd, 1)
p = os.spawnlp(os.P_WAIT, command, command, *args)
os.dup2(previousFd, 1)
os.close(fd)
os.close(previousFd)
f = open( filename )
content = f.read()
f.close()
os.unlink( filename )
return content
def parseBardecodeOutput(self, content):
# Sample output "818043376500 [type: ean13 at: (1798,936)]"
for line in content.splitlines():
@ -101,3 +87,4 @@ class Barcode:
self.parseBardecodeOutput( content )
self.printBoxes()
Analyzer.registerAnalyzer( 'barcode', Barcode )

146
NaNScaN/datamatrix.py Normal file
View File

@ -0,0 +1,146 @@
# Copyright (C) 2008 by Albert Cervera i Areny
# albert@nan-tic.com
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the
# Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
# Do not import everything as Template is defined in string too
from string import lower
import os
import tempfile
from temporaryfile import *
from analyzer import *
class Box:
def __init__(self):
self.text = None
self.position = None
self.size = None
self.dataCodewords = None
self.errorCodewordsd = None
self.dataRegions = None
self.interleavedBlocks = None
self.rotationAngle = None
self.box = None
class DataMatrix(Analyzer):
def __init__(self):
self.boxes = []
# Spawn process and return STDOUT
def outputTextToPoint(self, text):
pos = text.strip('(').strip(')').split(',')
x = float(pos[0]) / self.dotsPerMillimeterX
y = float(pos[1]) / self.dotsPerMillimeterY
return QPointF( x, y )
def parseOutput(self, content):
# Each datamatrix is a line of the output
nextText = False
box = None
lines = content.splitlines()
for x in xrange(len(lines)):
line = lines[x]
if not box and line == ('-' * 50):
continue
if not box:
box = Box()
self.boxes.append( box )
if nextText:
box.text = line
nextText = False
box = None
continue
if line == ('-' * 50):
nextText = True
continue
key, value = line.split(':')
value = value.strip()
if 'Matrix Size' in key:
box.size = value
elif 'Data Codewords' in key:
box.dataCodewords = value
elif 'Error Codewords' in key:
box.errorCodewords = value
elif 'Data Regions' in key:
box.dataRegions = value
elif 'Interleaved Blocks' in key:
box.interleavedBlocks = value
elif 'Rotation Angle' in key:
box.rotationAngle = value
elif 'Corner 0' in key:
box.corner0 = self.outputTextToPoint( value )
elif 'Corner 1' in key:
box.corner1 = self.outputTextToPoint( value )
elif 'Corner 2' in key:
box.corner2 = self.outputTextToPoint( value )
elif 'Corner 3' in key:
box.corner3 = self.outputTextToPoint( value )
r1 = QRectF( box.corner0, box.corner1 )
r2 = QRectF( box.corner2, box.corner3 )
box.box = r1.united( r2 )
def printBoxes(self):
for x in self.boxes:
print "Text: '%s'; Position: %f, %f; Size: %f, %f;" % (x.text, x.box.x(), x.box.y(), x.box.width(), x.box.height() )
## @brief Returns all data matrix values concatenated for a given region of the image.
def textInRegion(self, region):
texts = []
for x in self.boxes:
if region.intersects(x.box):
texts.append( unicode(x.text) )
# Always return unicode strings
return u''.join( texts )
## @brief Returns the bounding rectangle of the text returned by textInRegion for
# the given region.
def featureRectInRegion(self, region):
rect = QRectF()
for x in self.boxes:
if region.intersects(x.box):
rect = rect.united( x.box )
return rect
## @brief Scans the given image (QImage) looking for barcodes.
def scan(self, image):
# Clean boxes so scan() can be called more than once
self.boxes = []
# Obtain image resolution
image = QImage( image )
self.dotsPerMillimeterX = float( image.dotsPerMeterX() ) / 1000.0
self.dotsPerMillimeterY = float( image.dotsPerMeterY() ) / 1000.0
file = TemporaryFile.create()
image.save( file, 'PNG' )
command = 'dmtxread'
content = self.spawn( command, '-n', '-v', file )
self.parseOutput( content )
self.printBoxes()
Analyzer.registerAnalyzer( 'dataMatrix', DataMatrix )
if __name__ == '__main__':
d = DataMatrix()
d.scan( '/tmp/ex3.png' )

View File

@ -21,6 +21,7 @@ class Document:
self.name = ''
self.template = None
self.boxes = []
self.formatedText = None
def addBox(self, box):
self.boxes.append( box )

View File

@ -16,6 +16,8 @@
# Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
import levenshtein
class Levenshtein:
@staticmethod

View File

@ -25,6 +25,7 @@ import shutil
import math
from temporaryfile import *
from analyzer import *
from gamera.core import *
from PyQt4.QtCore import *
@ -45,14 +46,25 @@ def boxComparison(x, y):
## @breif This class allows using an OCR and provides several convenient functions
# regarding text and image processing such as deskewing or obtaining formated text.
class Ocr:
class Ocr(Analyzer):
file = ""
## @brief Uses tesseract to recognize text of the current image.
def ocr(self):
def tesseract(self):
directory = tempfile.mkdtemp()
path = os.path.join( directory, 'tesseract' )
os.spawnlp(os.P_WAIT, 'tesseract', 'tesseract', self.file, path, '-l', 'spa', 'batch.nochop', 'makebox' )
self.spawn( 'tesseract', self.file, path, '-l', 'spa', 'batch.nochop', 'makebox' )
f=codecs.open(path + '.txt', 'r', 'utf-8')
content = f.read()
f.close()
shutil.rmtree(directory, True)
return content
## @brief Uses cuneiform to recognize text of the current image.
def cuneiform(self):
directory = tempfile.mkdtemp()
path = os.path.join( directory, 'cuneiform' )
os.spawnlpe(os.P_WAIT, '/home/albert/d/git/cuneiform/bin/cuneiform', '/home/albert/d/git/cuneiform/bin/cuneiform', self.file, path, '-l', 'spa', 'batch.nochop', {'LD_LIBRARY_PATH': '/home/albert/d/git/cuneiform/lib'} )
f=codecs.open(path + '.txt', 'r', 'utf-8')
content = f.read()
f.close()
@ -130,7 +142,7 @@ class Ocr:
self.file = TemporaryFile.create('.tif')
self.convertToGrayScale(image, self.file)
txt = lower( self.ocr() )
txt = lower( self.tesseract() )
self.boxes = self.parseTesseractOutput(txt)
@ -316,6 +328,8 @@ class Ocr:
if slope > 0.001:
self.deskewOnce( self, region )
Analyzer.registerAnalyzer( 'text', Ocr )
## @brief Initializes OCR functions that need to be executed once before the library
# can work. Currently only initiates Gamera which is not being used by now.
def initOcrSystem():

View File

@ -20,7 +20,9 @@
from PyQt4.QtCore import *
from barcode import *
from ocr import *
from datamatrix import *
from analyzer import *
from template import *
from document import *
from trigram import *
@ -39,63 +41,64 @@ class Analyze(QThread):
def run(self):
self.analyzer.scan( self.image )
class Recognizer(QObject):
def __init__(self, parent=None):
QObject.__init__(self, parent)
self.barcode = Barcode()
self.ocr = Ocr()
self.analyzers = {}
for x in Analyzer.analyzers:
self.analyzers[x] = Analyzer.create(x)
self.ocr = self.analyzers['text']
self.image = None
self.threads = []
## @brief Returns the text of a given region of the image.
def textInRegion(self, region, type=None):
if type == 'barcode':
return self.barcode.textInRegion( region )
elif type == 'text':
return self.ocr.textInRegion( region )
if type in self.analyzers.keys():
return self.analyzers[type].textInRegion( region )
else:
return None
## @brief Returns the bounding rectangle of the text returned by textInRegion for
# the given region.
def featureRectInRegion(self, region, type=None):
if type == 'barcode':
return self.barcode.featureRectInRegion( region )
elif type == 'text':
return self.ocr.featureRectInRegion( region )
if type in self.analyzers:
return self.analyzers[type].featureRectInRegion( region )
else:
return None
def boxes(self, type):
if type == 'barcode':
return self.barcode.boxes
elif type == 'text':
return self.ocr.boxes
if type in self.analyzers:
return self.analyzers[type].boxes
else:
return None
def analyzersAvailable(self):
return ['barcode', 'text']
return self.analyzers.keys()
# Synchronous
def recognize(self, image):
self.image = image
self.barcode.scan( image )
self.ocr.scan( image )
for analyzer in self.analyzers.values():
analyzer.scan( image )
#self.barcode.scan( image )
#self.ocr.scan( image )
## @brief Asynchronous: Starts analyzers in background threads. Emits finished() at the end
def startRecognition(self, image):
self.image = image
self.ocrThread = Analyze( self.ocr, image, self )
self.barcodeThread = Analyze( self.barcode, image, self )
self.connect( self.ocrThread, SIGNAL('finished()'), self.recognitionFinished )
self.connect( self.barcodeThread, SIGNAL('finished()'), self.recognitionFinished )
self.ocrThread.start()
self.barcodeThread.start()
self.threads = []
for analyzer in self.analyzers.values():
thread = Analyze( analyzer, image, self )
self.connect( thread, SIGNAL('finished()'), self.recognitionFinished )
self.threads.append( thread )
thread.start()
def recognitionFinished(self):
if self.ocrThread.isFinished() and self.barcodeThread.isFinished():
self.emit( SIGNAL('finished()') )
for thread in self.threads:
if thread.isRunning():
return
self.emit( SIGNAL('finished()') )
self.threads = []
def filter(self, value, filterType):
numeric = '0123456789'
@ -246,6 +249,7 @@ class Recognizer(QObject):
return QPoint( 0, 0 )
lines = self.ocr.textLinesWithSpaces()
print "FORMATED: ", self.ocr.formatedText().encode( 'ascii', 'replace' )
# Create a default translator only once
translator = Translator()
@ -257,7 +261,7 @@ class Recognizer(QObject):
continue
templateBoxText = templateBox.text.strip()
templateBox.ranges = Range.extractAllRangesFromDocument(lines, len(templateBoxText))
templateBox.ranges = Range.extractAllRangesFromDocument( lines, len(templateBoxText), templateBox.featureRect.width() + 2 )
for ran in templateBox.ranges:
text = ran.text()
#value = Hamming.hamming( text, templateBoxText, translator )
@ -402,7 +406,7 @@ class Range:
## @brief Returns a list with all possible ranges of size length of the
# given document
@staticmethod
def extractAllRangesFromDocument(lines, length):
def extractAllRangesFromDocument(lines, length, width=0):
if length <= 0:
return []
ranges = []
@ -413,6 +417,9 @@ class Range:
ran.pos = 0
ran.length = len(lines[line])
ran.document = lines
#if width:
# while ran.rect().width() > width:
# ran.length -= 1
ranges.append( ran )
continue
for pos in range(len(lines[line]) - length + 1):
@ -421,6 +428,9 @@ class Range:
ran.pos = pos
ran.length = length
ran.document = lines
#if width:
# while ran.rect().width() > width:
# ran.length -= 1
ranges.append( ran )
return ranges

View File

@ -35,7 +35,7 @@ class Template(QObject):
self.emit(SIGNAL('boxRemoved(PyQt_PyObject)'), box)
class TemplateBox:
recognizers = ['text', 'barcode']
recognizers = ['text', 'barcode', 'dataMatrix']
types = ['matcher','input']
filters = ['none','numeric','alphabetic','alphanumeric']

View File

@ -183,6 +183,13 @@ class DocumentScene(QGraphicsScene):
circle.setBrush( self._circleItemBrush )
self._imageBoxes.addToGroup( circle )
for i in self.recognizer.boxes('dataMatrix'):
#rect = QGraphicsRectItem( i.box, self._imageBoxes )
rect = QGraphicsRectItem( self.mapRectFromRecognizer( i.box ), self._imageBoxes )
rect.setPen( self._boxItemPen )
rect.setBrush( self._boxItemBrush )
self._imageBoxes.addToGroup( rect )
self.setImageBoxesVisible( self._imageBoxesVisible )
self._imageBoxes.setZValue( 2 )
@ -512,7 +519,7 @@ class MainWindow(QMainWindow):
def recognizerChanged(self, recognizer):
rect = self.uiTool.box.rect
self.uiTool.setText( self.scene.recognizer.textInRegion( rect, recognizer ) )
self.uiTool.setText( self.scene.recognizer.textInRegion( rect, unicode(recognizer) ) )
def newTemplateBox(self, rect):
# Creating and adding the box to the template
@ -644,7 +651,6 @@ class MainWindow(QMainWindow):
if not self.login():
return
print "DDDDD: ", Rpc.session
dialog = OpenTemplateDialog(self)
if dialog.exec_() == QDialog.Rejected:
return