Show mouse position & findMatchingTemplateByText:

- Show current mouse position in template designer.
- New 'findMatchingTemplateByText()' still not working properly
but most code is already there including the action to test it
from template designer.
This commit is contained in:
Albert Cervera i Areny 2008-09-14 17:32:40 +02:00
parent c0e881db5d
commit 9cc4af299d
3 changed files with 333 additions and 31 deletions

View File

@ -24,6 +24,8 @@ from ocr import *
from template import *
from document import *
from trigram import *
from hamming import *
from translator import *
import tempfile
@ -42,6 +44,7 @@ class Recognizer(QObject):
QObject.__init__(self, parent)
self.barcode = Barcode()
self.ocr = Ocr()
self.image = None
## @brief Returns the text of a given region of the image.
def textInRegion(self, region, type=None):
@ -145,7 +148,7 @@ class Recognizer(QObject):
#
# TODO: Using offsets to find the best template is easy but highly inefficient.
# a smarter solution should be implemented.
def findMatchingTemplate( self, templates, offset = 5 ):
def findMatchingTemplateByOffset( self, templates, offset = 5 ):
max = 0
best = {
'template': None,
@ -165,7 +168,7 @@ class Recognizer(QObject):
for documentBox in currentDocument.boxes:
templateBox = documentBox.templateBox
if documentBox.templateBox.type != 'matcher':
print "Jumping %s due to type %s " % ( templateBox.name, templateBox.type )
print "Jumping %s due to type %s" % ( templateBox.name, templateBox.type )
continue
matcherBoxes += 1
similarity = Trigram.trigram( documentBox.text, templateBox.text )
@ -182,6 +185,57 @@ class Recognizer(QObject):
print "Template %s has score %s with offset (%s,%s)" % (template.name, score, xOffset, yOffset)
return best
## @brief Tries to find out the best template in 'templates' for the current
# image.
# This algorithm starts by looking for template boxes of type 'matching' in the
# text and then looks if the relative positions of the new document and template
# boxes are similar. This is intended to be faster than exhaustive algorithm used
# in findMatchingTemplateByOffset().
#
# Note that the image must have been scanned (using scan() or startScan())
# before using this function.
#
# TODO: Using offsets to find the best template is easy but highly inefficient.
# a smarter solution should be implemented.
def findMatchingTemplateByText( self, templates ):
max = 0
best = {
'template': None,
'document': Document(),
'xOffset' : 0,
'yOffset' : 0
}
for template in templates:
if not template.boxes:
continue
# Find out template's offset
offset = self.findTemplateOffset( template )
if not offset:
continue
score = 0
matcherBoxes = 0
# Apply template with offset found
currentDocument = self.extractWithTemplate( template, offset.x(), offset.y() )
for documentBox in currentDocument.boxes:
templateBox = documentBox.templateBox
if documentBox.templateBox.type != 'matcher':
continue
matcherBoxes += 1
similarity = Trigram.trigram( documentBox.text, templateBox.text )
score += similarity
score = score / matcherBoxes
if score > max:
max = score
best = {
'template': template,
'document': currentDocument,
'xOffset' : offset.x(),
'yOffset' : offset.y()
}
return best
## @brief Returns a QPoint with the offset that needs to be applied to the given
# template to best fit the current image.
def findTemplateOffset( self, template ):
@ -190,12 +244,161 @@ class Recognizer(QObject):
lines = self.ocr.textLinesWithSpaces()
# Create a default translator only once
translator = Translator()
# This list will keep a pointer to each template box of type 'matcher'
matchers = []
for templateBox in template.boxes:
if templateBox.type != 'matcher':
continue
for line in lines:
templateBox.text
templateBox.ranges = Range.extractAllRangesFromDocument(lines, len(templateBox.text))
for ran in templateBox.ranges:
text = ran.text()
value = Hamming.hamming( text, templateBox.text, translator )
ran.distance = value
templateBox.ranges.sort( rangeDistanceComparison )
if templateBox.ranges:
bestRange = templateBox.ranges[0]
print "The best match for template box '%s' is '%s'" % (templateBox.text, bestRange.text() )
matchers.append( templateBox )
# Once we have all ranges sorted for each template box we search which
# range combination matches the template.
iterator = TemplateBoxRangeIterator( matchers )
i = 0
for ranges in iterator:
documentBoxCenter = ranges[0].rect().center()
templateBoxCenter = matchers[0].featureRect.center()
diff = templateBoxCenter - documentBoxCenter
print "Difference: ", diff
found = True
for pos in range(1,len(ranges)):
documentBoxCenter = ranges[pos].rect().center()
templateBoxCenter = matchers[pos].featureRect.center()
d = templateBoxCenter - documentBoxCenter
# If difference of relative positions of boxes between
# template and document are bigger than 5mm we discard
# the ranges
print "Difference in loop: ", d
if ( abs(d.x()) + 5.0 > abs(diff.x()) ):
found = False
break
if ( abs(d.y()) + 5.0 > abs(diff.y()) ):
found = False
break
if found:
break
i += 1
if i > 10:
break
if found:
return diff
else:
return None
class TemplateBoxRangeIterator:
def __init__(self, boxes):
self.boxes = boxes
self.pos = [0] * len(self.boxes)
self.loopPos = [0] * len(self.boxes)
self.added = None
def __iter__(self):
return self
def next(self):
result = []
for x in range(len(self.boxes)):
result.append( self.boxes[x].ranges[ self.pos[x] ] )
print '----'
print (u', '.join( [x.text() for x in result] )).encode('ascii', 'ignore')
print self.pos
print self.loopPos
if self.loopPos == self.pos:
# Search next value to add
value = float('infinity')
pos = 0
for x in range(len(self.pos)):
if x >= len(self.boxes[x].ranges) - 1:
continue
if self.pos[x] < value:
value = self.pos[x]
self.added = x
# If value is Infinity it means that we reached the end
# of all possible iterations
if value == float('infinity'):
raise StopIteration
self.pos[self.added] += 1
self.loopPos = [0] * len(self.boxes)
self.loopPos[self.added] = self.pos[self.added]
else:
for x in range(len(self.loopPos)):
if x == self.added:
continue
if self.loopPos[x] < self.pos[x]:
self.loopPos[x] += 1
break
return result
def rangeDistanceComparison(x, y):
if x.distance > y.distance:
return 1
elif x.distance < y.distance:
return -1
else:
return 0
## @brief This class represents a group of characters in a document.
class Range:
def __init__(self):
self.line = 0
self.pos = 0
self.length = 0
self.document = None
## @brief Returns a unicode string with the text of the current range
def text(self):
line = self.document[self.line]
chars = line[self.pos:self.pos + self.length]
return u''.join( [x.character for x in chars] )
## @brief Returns the bounding rectangle of the text in the range
def rect(self):
line = self.document[self.line]
chars = line[self.pos:self.pos + self.length]
rect = QRectF()
for c in chars:
rect = rect.united( c.box )
return rect
## @brief Returns a list with all possible ranges of size length of the
# given document
@staticmethod
def extractAllRangesFromDocument(lines, length):
if length <= 0:
return []
ranges = []
for line in range(len(lines)):
if length > len(lines[line]):
ran = Range()
ran.line = line
ran.pos = 0
ran.length = len(lines[line])
ran.document = lines
ranges.append( ran )
continue
for pos in range(len(lines[line]) - length):
ran = Range()
ran.line = line
ran.pos = pos
ran.length = length
ran.document = lines
ranges.append( ran )
return ranges

View File

@ -119,6 +119,7 @@ class TemplateBoxItem(QGraphicsRectItem):
self.templateBox = None
if featureRect:
self.feature = QGraphicsRectItem(featureRect, self)
self.feature.setZValue( -1 )
class DocumentScene(QGraphicsScene):
@ -192,6 +193,9 @@ class DocumentScene(QGraphicsScene):
self.setTemplateBoxesVisible( self._templateBoxesVisible )
def mapRectFromRecognizer(self, rect):
# When there's no image loaded yet, return the same rect
if not self.dotsPerMillimeterX:
return rect
box = QRectF()
box.setX( rect.x() * self.dotsPerMillimeterX )
box.setY( rect.y() * self.dotsPerMillimeterY )
@ -200,6 +204,9 @@ class DocumentScene(QGraphicsScene):
return box
def mapRectToRecognizer(self, rect):
# When there's no image loaded yet, return the same rect
if not self.dotsPerMillimeterX:
return rect
box = QRectF()
box.setX( rect.x() / self.dotsPerMillimeterX )
box.setY( rect.y() / self.dotsPerMillimeterY )
@ -208,12 +215,18 @@ class DocumentScene(QGraphicsScene):
return box
def mapPointFromRecognizer(self, point):
# When there's no image loaded yet, return the same point
if not self.dotsPerMillimeterX:
return point
d = QPointF()
d.setX( point.x() * self.dotsPerMillimeterX )
d.setY( point.y() * self.dotsPerMillimeterY )
return d
def mapPointToRecognizer(self, point):
# When there's no image loaded yet, return the same point
if not self.dotsPerMillimeterX:
return point
d = QPointF()
d.setX( point.x() / self.dotsPerMillimeterX )
d.setY( point.y() / self.dotsPerMillimeterY )
@ -343,11 +356,14 @@ class DocumentScene(QGraphicsScene):
if not self.isEnabled():
return
if self._mode == self.CreationMode:
item = self.itemAt( event.scenePos() )
if item and unicode(item.data( 0 ).toString()) == 'TemplateBox':
self.setActiveItem( item )
return
# Search if there's a TemplateBox where the user clicked
# If so, make the item active.
for item in self.items( event.scenePos() ):
if unicode(item.data(0).toString()) == 'TemplateBox':
self.setActiveItem( item )
return
# Otherwise, create a new template box
rect = QRectF(event.scenePos().x(), event.scenePos().y(), 1, 1)
self._selection = self.createTemplateBox( rect )
elif self._mode == self.SelectionMode:
@ -371,6 +387,7 @@ class DocumentScene(QGraphicsScene):
self.emit( SIGNAL('newTemplateBox(QRectF)'), self.mapRectToRecognizer( rect ) )
def mouseMoveEvent(self, event):
self.emit( SIGNAL('mouseMoved'), event.scenePos() )
if not self.isEnabled():
return
if self._mode == self.CreationMode and self._selection:
@ -414,6 +431,7 @@ class MainWindow(QMainWindow):
self.connect( self.scene, SIGNAL('newTemplateBox(QRectF)'), self.newTemplateBox )
self.connect( self.scene, SIGNAL('currentTemplateBoxChanged(PyQt_PyObject,PyQt_PyObject)'), self.currentTemplateBoxChanged)
self.connect( self.scene, SIGNAL('mouseMoved'), self.sceneMouseMoved )
self.connect( self.actionExit, SIGNAL('triggered()'), self.close )
self.connect( self.actionOpenImage, SIGNAL('triggered()'), self.openImage )
self.connect( self.actionOpenTemplate, SIGNAL('triggered()'), self.openTemplate )
@ -428,7 +446,8 @@ class MainWindow(QMainWindow):
self.connect( self.actionDelete, SIGNAL('triggered()'), self.removeTemplateBox )
self.connect( self.actionZoom, SIGNAL('triggered()'), self.zoom )
self.connect( self.actionUnzoom, SIGNAL('triggered()'), self.unzoom )
self.connect( self.actionFindMatchingTemplate, SIGNAL('triggered()'), self.findMatchingTemplate )
self.connect( self.actionFindMatchingTemplateByOffset, SIGNAL('triggered()'), self.findMatchingTemplateByOffset )
self.connect( self.actionFindMatchingTemplateByText, SIGNAL('triggered()'), self.findMatchingTemplateByText )
self.toggleImageBoxes()
QTimer.singleShot( 1000, self.setup )
self.updateTitle()
@ -446,17 +465,44 @@ class MainWindow(QMainWindow):
self.uiToolDock.setWidget( self.uiTool )
#rpc.session.login( 'http://admin:admin@127.0.0.1:8069', 'g1' )
def sceneMouseMoved(self, pos):
self.updatePosition( pos )
def findMatchingTemplateByOffset(self):
self.findMatchingTemplate( 'offset' )
def findMatchingTemplateByText(self):
self.findMatchingTemplate( 'text' )
def findMatchingTemplate(self, type):
if type == 'offset':
title = _('Template search by offset')
else:
title = _('Template search by text')
if not self.recognizer.image:
QMessageBox.information( self, title, _('No image opened. Please open an image to find a matching template.') )
return
def findMatchingTemplate(self):
if not rpc.session.logged():
if not self.login():
return
templates = TemplateStorageManager.loadAll()
result = self.recognizer.findMatchingTemplate( templates )
time = QTime()
time.start()
if type == 'offset':
result = self.recognizer.findMatchingTemplateByOffset( templates )
else:
result = self.recognizer.findMatchingTemplateByText( templates )
elapsed = time.elapsed()
if not result['template']:
QMessageBox.information( self, title, _('No template found for the current image. Took %d milliseconds') % elapsed )
return
self._template = result['template']
self.scene.setTemplate(self._template)
self.updateTitle()
QMessageBox.information( self, _('Template parameters'), _('Template found with offset (%d, %d)') % (result['xOffset'], result['yOffset']) )
QMessageBox.information( self, title, _('Template found with offset (%d, %d) in %d milliseconds') % (result['xOffset'], result['yOffset'], elapsed) )
def recognizerChanged(self, recognizer):
rect = self.uiTool.box.rect
@ -631,7 +677,13 @@ class MainWindow(QMainWindow):
server = _('Press %s to login') % shortcut
else:
server = 'not logged in'
self.statusBar().showMessage( server )
self.uiServer.setText( server )
def updatePosition(self, pos):
pos = self.uiView.mapToScene( self.uiView.mapFromGlobal( QCursor.pos() ) )
pos = self.scene.mapPointToRecognizer( pos )
position = _('(%.2f, %.2f)') % (pos.x(), pos.y())
self.uiPosition.setText( position )
def updateActions(self):
# Allow deleting if there's a TemplateBox selected

View File

@ -18,10 +18,10 @@
<x>0</x>
<y>48</y>
<width>709</width>
<height>417</height>
<height>439</height>
</rect>
</property>
<layout class="QHBoxLayout" >
<layout class="QHBoxLayout" name="horizontalLayout_2" >
<item>
<layout class="QVBoxLayout" >
<item>
@ -41,7 +41,7 @@
<x>0</x>
<y>20</y>
<width>70</width>
<height>377</height>
<height>399</height>
</rect>
</property>
</widget>
@ -50,7 +50,58 @@
</layout>
</item>
<item>
<widget class="QGraphicsView" name="uiView" />
<layout class="QVBoxLayout" name="verticalLayout" >
<item>
<widget class="QGraphicsView" name="uiView" />
</item>
<item>
<widget class="QFrame" name="frame" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Expanding" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape" >
<enum>QFrame::VLine</enum>
</property>
<property name="frameShadow" >
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout" >
<property name="margin" >
<number>0</number>
</property>
<item>
<widget class="QLabel" name="uiServer" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Preferred" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text" >
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="uiPosition" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Preferred" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text" >
<string>(0, 0)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
@ -96,23 +147,14 @@
<property name="title" >
<string>&amp;Actions</string>
</property>
<addaction name="actionFindMatchingTemplate" />
<addaction name="actionFindMatchingTemplateByOffset" />
<addaction name="actionFindMatchingTemplateByText" />
</widget>
<addaction name="menuFile" />
<addaction name="menuEdit" />
<addaction name="menu_Actions" />
<addaction name="menuView" />
</widget>
<widget class="QStatusBar" name="statusbar" >
<property name="geometry" >
<rect>
<x>0</x>
<y>465</y>
<width>709</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QToolBar" name="toolBar" >
<property name="geometry" >
<rect>
@ -239,9 +281,14 @@
<string>Show Feature Boxes</string>
</property>
</action>
<action name="actionFindMatchingTemplate" >
<action name="actionFindMatchingTemplateByOffset" >
<property name="text" >
<string>Find Matching Template</string>
<string>Find Matching Template by &amp;Offset</string>
</property>
</action>
<action name="actionFindMatchingTemplateByText" >
<property name="text" >
<string>Find Matching Template by &amp;Text</string>
</property>
</action>
</widget>