Rename Letter to Glyph.
This commit is contained in:
parent
c5cb68f063
commit
ada771b5c9
7 changed files with 72 additions and 72 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue