Rename Letter to Glyph.

This commit is contained in:
Andrey Golovizin 2014-08-18 15:44:36 +02:00
parent c5cb68f063
commit ada771b5c9
7 changed files with 72 additions and 72 deletions

View file

@ -44,8 +44,8 @@ def main():
ocr = OCREngine(sys.argv[1:]) ocr = OCREngine(sys.argv[1:])
ocr.pageChanged.connect(win.pageScene.setPage) ocr.pageChanged.connect(win.pageScene.setPage)
ocr.unknownLetter.connect(win.unknownLetter) ocr.unknownGlyph.connect(win.unknownGlyph)
win.letterEntered.connect(ocr.give_help) win.glyphEntered.connect(ocr.give_help)
ocr.start() ocr.start()
signal.signal(signal.SIGINT, signal.SIG_DFL) signal.signal(signal.SIGINT, signal.SIG_DFL)

View file

@ -28,7 +28,7 @@ from PyQt4.QtGui import (
class GlyphEdit(QWidget): class GlyphEdit(QWidget):
letterEntered = signal([str]) glyphEntered = signal([str])
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -47,11 +47,11 @@ class GlyphEdit(QWidget):
self.italic.setShortcut('Ctrl+i') self.italic.setShortcut('Ctrl+i')
self.text = QLineEdit(self) self.text = QLineEdit(self)
self.text.returnPressed.connect(self.sendLetter) self.text.returnPressed.connect(self.sendGlyph)
self.layout.addWidget(self.bold) self.layout.addWidget(self.bold)
self.layout.addWidget(self.italic) self.layout.addWidget(self.italic)
self.layout.addWidget(self.text) self.layout.addWidget(self.text)
def sendLetter(self): def sendGlyph(self):
self.letterEntered.emit(self.text.text()) self.glyphEntered.emit(self.text.text())

View file

@ -23,11 +23,11 @@ from PyQt4.QtCore import (
) )
from ..image import Image from ..image import Image
from ..page import Page, Letter, Space from ..page import Page, Glyph, Space
class OCREngine(QThread): class OCREngine(QThread):
unknownLetter = signal([Letter]) unknownGlyph = signal([Glyph])
pageChanged = signal([Page]) pageChanged = signal([Page])
def __init__(self, filenames): def __init__(self, filenames):
@ -54,21 +54,21 @@ class OCREngine(QThread):
yield ''.join(self.recognize_line(line)) yield ''.join(self.recognize_line(line))
def recognize_line(self, line): def recognize_line(self, line):
for letter in line.letters: for glyph in line.glyphs:
yield self.recognize_letter(letter) yield self.recognize_glyph(glyph)
def recognize_letter(self, letter): def recognize_glyph(self, glyph):
if isinstance(letter, Space): if isinstance(glyph, Space):
return ' ' return ' '
try: try:
return self.chardb[letter.key] return self.chardb[glyph.key]
except KeyError: except KeyError:
text = self.ask_for_help(letter) text = self.ask_for_help(glyph)
self.chardb[letter.key] = text self.chardb[glyph.key] = text
return text return text
def ask_for_help(self, unknown_letter): def ask_for_help(self, unknown_glyph):
self.unknownLetter.emit(unknown_letter) self.unknownGlyph.emit(unknown_glyph)
return self.receive_help() return self.receive_help()
def give_help(self, text): def give_help(self, text):

View file

@ -50,8 +50,8 @@ class PageScene(QGraphicsScene):
page = None page = None
pageItem = None pageItem = None
letterPen = QColor(0, 0, 0, 80) glyphPen = QColor(0, 0, 0, 80)
letterBrush = QColor(255, 255, 0, 80) glyphBrush = QColor(255, 255, 0, 80)
spacePen = QColor(0, 0, 0, 80) spacePen = QColor(0, 0, 0, 80)
spaceBrush = QColor(0, 0, 255, 20) spaceBrush = QColor(0, 0, 255, 20)
linePen = QColor(255, 50, 50, 100) linePen = QColor(255, 50, 50, 100)
@ -60,11 +60,11 @@ class PageScene(QGraphicsScene):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.highlightedLetters = {} self.highlightedGlyphs = {}
self.setBackgroundBrush(self.palette().window()) self.setBackgroundBrush(self.palette().window())
def setPage(self, page): def setPage(self, page):
self.highlightedLetters.clear() self.highlightedGlyphs.clear()
for item in self.items(): for item in self.items():
self.remoteItem(item) self.remoteItem(item)
self.page = page self.page = page
@ -78,11 +78,11 @@ class PageScene(QGraphicsScene):
def highlightAll(self): def highlightAll(self):
for line in page: for line in page:
for letter in line: for glyph in line:
if not letter.image.isspace: if not glyph.image.isspace:
self.addRect(letter.x - 1, letter.y - 1, letter.width + 1, letter.height + 1, self.letterPen, self.letterBrush) self.addRect(glyph.x - 1, glyph.y - 1, glyph.width + 1, glyph.height + 1, self.glyphPen, self.glyphBrush)
else: else:
self.addRect(letter.x - 1, letter.y - 1, letter.width + 1, letter.height + 1, self.spacePen, self.spaceBrush) self.addRect(glyph.x - 1, glyph.y - 1, glyph.width + 1, glyph.height + 1, self.spacePen, self.spaceBrush)
self.addLine(line.left, line.baseline, line.right, line.baseline, self.linePen) self.addLine(line.left, line.baseline, line.right, line.baseline, self.linePen)
def addPage(self, page): def addPage(self, page):
@ -90,14 +90,14 @@ class PageScene(QGraphicsScene):
graphicsitem = self.addPixmap(QPixmap.fromImage(qimage)) graphicsitem = self.addPixmap(QPixmap.fromImage(qimage))
return graphicsitem return graphicsitem
def highlightLetter(self, letter): def highlightGlyph(self, glyph):
rect = self.addRect(letter.x - 1, letter.y - 1, letter.width + 1, letter.height + 1, self.letterPen, self.letterBrush) rect = self.addRect(glyph.x - 1, glyph.y - 1, glyph.width + 1, glyph.height + 1, self.glyphPen, self.glyphBrush)
self.highlightedLetters[letter] = rect self.highlightedGlyphs[glyph] = rect
def clearHighlight(self): def clearHighlight(self):
for item in self.highlightedLetters.values(): for item in self.highlightedGlyphs.values():
self.removeItem(item) self.removeItem(item)
self.highlightedLetters.clear() self.highlightedGlyphs.clear()
class PageView(QGraphicsView): class PageView(QGraphicsView):

View file

@ -34,7 +34,7 @@ from .glyphedit import GlyphEdit
class MainWindow(QMainWindow): class MainWindow(QMainWindow):
letterEntered = signal([str]) glyphEntered = signal([str])
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -46,8 +46,8 @@ class MainWindow(QMainWindow):
self.page = PageView(self.pageScene, centralWidget) self.page = PageView(self.pageScene, centralWidget)
self.pageScene.pageItemChanged.connect(self.page.centerOnPage) self.pageScene.pageItemChanged.connect(self.page.centerOnPage)
self.glyphEdit = GlyphEdit(centralWidget) self.glyphEdit = GlyphEdit(centralWidget)
self.glyphEdit.letterEntered.connect(self.letterEntered) self.glyphEdit.glyphEntered.connect(self.glyphEntered)
self.glyphEdit.letterEntered.connect(self.pageScene.clearHighlight) self.glyphEdit.glyphEntered.connect(self.pageScene.clearHighlight)
layout = QVBoxLayout(centralWidget) layout = QVBoxLayout(centralWidget)
layout.setSpacing(0) layout.setSpacing(0)
@ -74,8 +74,8 @@ class MainWindow(QMainWindow):
self.restoreGeometry(settings.value("windowGeometry") or b'') self.restoreGeometry(settings.value("windowGeometry") or b'')
self.restoreState(settings.value("windowState") or b'') self.restoreState(settings.value("windowState") or b'')
def unknownLetter(self, letter): def unknownGlyph(self, glyph):
self.pageScene.highlightLetter(letter) self.pageScene.highlightGlyph(glyph)
self.glyphEdit.setEnabled(True) self.glyphEdit.setEnabled(True)
self.glyphEdit.text.clear() self.glyphEdit.text.clear()
self.glyphEdit.text.setFocus() self.glyphEdit.text.setFocus()

View file

@ -117,7 +117,7 @@ class Image(object):
"""Return a two-color version of the image. """Return a two-color version of the image.
0 = white (blank) pixel 0 = white (blank) pixel
1 = black (letter) pixel 1 = black (glyph) pixel
""" """
grayscale = rgb2gray(self.data) grayscale = rgb2gray(self.data)

View file

@ -84,7 +84,7 @@ class Page(PageObject):
class Line(PageObject): class Line(PageObject):
def __iter__(self): def __iter__(self):
return iter(self.letters) return iter(self.glyphs)
@cached_property @cached_property
def baseline(self): def baseline(self):
@ -97,62 +97,62 @@ class Line(PageObject):
return self.y + bottom return self.y + bottom
@property @property
def letters(self): def glyphs(self):
labels, max_label = ndimage.label(self.image.bitmap, CONNECTIVITY8) labels, max_label = ndimage.label(self.image.bitmap, CONNECTIVITY8)
blob_slices = enumerate(ndimage.find_objects(labels, max_label), 1) blob_slices = enumerate(ndimage.find_objects(labels, max_label), 1)
letter_images = ( glyph_images = (
self._extract_blob(blob_slice, label, labels) self._extract_blob(blob_slice, label, labels)
for (label, blob_slice) in blob_slices for (label, blob_slice) in blob_slices
) )
letters = ( glyphs = (
Letter(image, self.baseline - image.bottom) Glyph(image, self.baseline - image.bottom)
for image in letter_images for image in glyph_images
) )
letters = sorted(letters, key=lambda letter: (letter.left, -letter.bottom)) glyphs = sorted(glyphs, key=lambda glyph: (glyph.left, -glyph.bottom))
letters = self._combine_diacritics(letters) glyphs = self._combine_diacritics(glyphs)
return self._insert_spaces(letters) return self._insert_spaces(glyphs)
def _optical_correction(self, letter1, letter2): def _optical_correction(self, glyph1, glyph2):
base = min(letter1.top, letter2.top) base = min(glyph1.top, glyph2.top)
height = max(letter1.bottom, letter2.bottom) - base height = max(glyph1.bottom, glyph2.bottom) - base
bitmap1 = np.hstack([np.ones((letter1.height, 1)), letter1.image.bitmap]) bitmap1 = np.hstack([np.ones((glyph1.height, 1)), glyph1.image.bitmap])
bitmap2 = np.hstack([letter2.image.bitmap, np.ones((letter2.height, 1))]) bitmap2 = np.hstack([glyph2.image.bitmap, np.ones((glyph2.height, 1))])
margin1 = np.zeros(height, np.int) margin1 = np.zeros(height, np.int)
margin1.fill(letter1.width) margin1.fill(glyph1.width)
margin2 = np.zeros(height, np.int) margin2 = np.zeros(height, np.int)
margin2.fill(letter2.width) margin2.fill(glyph2.width)
margin1[letter1.top - base: letter1.bottom - base] = np.fliplr(bitmap1).argmax(axis=1) margin1[glyph1.top - base: glyph1.bottom - base] = np.fliplr(bitmap1).argmax(axis=1)
margin2[letter2.top - base: letter2.bottom - base] = bitmap2.argmax(axis=1) margin2[glyph2.top - base: glyph2.bottom - base] = bitmap2.argmax(axis=1)
margins = margin1 + margin2 margins = margin1 + margin2
return margins.min() return margins.min()
def _combine_diacritics(self, letters): def _combine_diacritics(self, glyphs):
def is_diacritic(glyph): def is_diacritic(glyph):
# XXX # XXX
return ( return (
letter is not None glyph is not None
and glyph is not None and glyph is not None
and glyph.elevation > 5 and glyph.elevation > 5
) )
while letters: while glyphs:
letter, *letters = letters glyph, *glyphs = glyphs
diacritics = list(itertools.takewhile(is_diacritic, iter(letters))) diacritics = list(itertools.takewhile(is_diacritic, iter(glyphs)))
for diacritic in diacritics: for diacritic in diacritics:
letter = Letter(letter.image.combine(diacritic.image), elevation=letter.elevation) glyph = Glyph(glyph.image.combine(diacritic.image), elevation=glyph.elevation)
letters = letters[len(diacritics):] glyphs = glyphs[len(diacritics):]
yield letter yield glyph
def _insert_spaces(self, letters): def _insert_spaces(self, glyphs):
for letter, next_letter in pairwise(letters): for glyph, next_glyph in pairwise(glyphs):
yield letter yield glyph
if next_letter is not None: if next_glyph is not None:
correction = self._optical_correction(letter, next_letter) correction = self._optical_correction(glyph, next_glyph)
distance = next_letter.left - letter.right + correction distance = next_glyph.left - glyph.right + correction
if distance > 5: if distance > 5:
yield Space(self.image.space(letter.right, self.top, distance, self.height), self.baseline - self.top) yield Space(self.image.space(glyph.right, self.top, distance, self.height), self.baseline - self.top)
def _extract_blob(self, blob_slice, label, labels): def _extract_blob(self, blob_slice, label, labels):
image = self.image[blob_slice] image = self.image[blob_slice]
@ -160,17 +160,17 @@ class Line(PageObject):
return image.mask(mask) return image.mask(mask)
class Letter(PageObject): class Glyph(PageObject):
def __init__(self, image, elevation): def __init__(self, image, elevation):
super().__init__(image) super().__init__(image)
self.elevation = elevation self.elevation = elevation
@property @property
def key(self): def key(self):
"""Return a dictionary key uniquely representing this letter.""" """Return a dictionary key uniquely representing this glyph."""
return self.elevation, self.image.tostring() return self.elevation, self.image.tostring()
class Space(Letter): class Space(Glyph):
pass pass