|
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2013, Marten de Vries
#
# This file is part of OpenTeacher.
#
# OpenTeacher 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 3 of the License, or
# (at your option) any later version.
#
# OpenTeacher 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 OpenTeacher. If not, see <http://www.gnu.org/licenses/>.
import os
import tempfile
import contextlib
def installQtClasses():
global OcrWizard
class ImageView(QtWidgets.QGraphicsView):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setScene(QtWidgets.QGraphicsScene())
self._rect = self.scene().addRect(0.0, 0.0, 0.0, 0.0)
self._rect.setZValue(1)
self._imageItem = None
def wheelEvent(self, event):
factor = 1.1
if event.angleDelta().y() < 0:
factor = 1.0 / factor
self.scale(factor, factor)
def updateImage(self, path):
if self._imageItem:
self.scene().removeItem(self._imageItem)
self._imageItem = self.scene().addPixmap(QtGui.QPixmap(path))
def getExcision(self):
size = self._rect.rect().size().toSize()
if size.isEmpty():
raise ValueError("Empty selection")
img = QtGui.QImage(size, QtGui.QImage.Format_ARGB32_Premultiplied)
painter = QtGui.QPainter(img)
self.scene().render(painter, QtCore.QRectF(), self._rect.rect())
return img
def updateRotation(self, value):
if self._imageItem:
self._imageItem.setRotation(value)
def mousePressEvent(self, event):
self._startPos = self.mapToScene(event.pos())
def mouseReleaseEvent(self, event):
newRect = QtCore.QRectF(
self._startPos,
self.mapToScene(QtCore.QPoint(event.pos()))
).normalized()
self._rect.setRect(newRect)
mouseMoveEvent = mouseReleaseEvent
class StartPage(QtWidgets.QWizardPage):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._label = QtWidgets.QLabel()
self._label.setWordWrap(True)
vbox = QtWidgets.QVBoxLayout()
vbox.addWidget(self._label)
self.setLayout(vbox)
def retranslate(self):
self.setTitle(_("Introduction"))
self._label.setText(_("This wizard will guide you through the process of creating a word list from a scan or picture of a physical word list. Click 'Next' to start."))
class SignalLabel(QtWidgets.QLabel):
textChanged = QtCore.pyqtSignal()
def setText(self, *args, **kwargs):
result = super().setText(*args, **kwargs)
self.textChanged.emit()
return result
class ImageChoicePage(QtWidgets.QWizardPage):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._label = QtWidgets.QLabel()
self._label.setWordWrap(True)
self._chooseLocationButton = QtWidgets.QPushButton()
self._chooseLocationButton.clicked.connect(self._chooseLocationClicked)
self._locationLabelLabel = QtWidgets.QLabel()
self._locationLabel = SignalLabel()
self.registerField("path", self._locationLabel, "text", self._locationLabel.textChanged)
vbox = QtWidgets.QVBoxLayout()
vbox.addWidget(self._label)
vbox.addWidget(self._chooseLocationButton)
vbox.addWidget(self._locationLabelLabel)
vbox.addWidget(self._locationLabel)
self.setLayout(vbox)
def retranslate(self):
self.setTitle(_("Choose an image"))
self._label.setText(_("Please choose the location of the scan or picture of the physical word list on your system below."))
self._chooseLocationButton.setText(_("Click here to choose a location"))
self._locationLabelLabel.setText(_("Location:"))
def _chooseLocationClicked(self):
extsList = map(str, QtGui.QImageReader.supportedImageFormats())
exts = "*." + " *.".join(extsList)
filter = _("Images ({extensions})".format(extensions=exts))
path = QtWidgets.QFileDialog.getOpenFileName(self, filter=filter)[0]
if path:
self._locationLabel.setText(path)
self.completeChanged.emit()
def isComplete(self):
path = self._locationLabel.text()
return os.path.exists(path)
class ImageEditPage(QtWidgets.QWizardPage):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._label1 = QtWidgets.QLabel()
self._label2 = QtWidgets.QLabel()
self._label1.setWordWrap(True)
self._label2.setWordWrap(True)
self._view = ImageView()
self._rotationSlider = QtWidgets.QSlider()
self._rotationSlider.setOrientation(QtCore.Qt.Horizontal)
self._rotationSlider.setMinimum(-180)
self._rotationSlider.setMaximum(180)
self._rotationSlider.valueChanged.connect(self._view.updateRotation)
vbox = QtWidgets.QVBoxLayout()
vbox.addWidget(self._label1)
vbox.addWidget(self._rotationSlider)
vbox.addWidget(self._label2)
vbox.addWidget(self._view)
self.setLayout(vbox)
def initializePage(self):
path = self.field("path")
self._view.updateImage(path)
def retranslate(self):
self.setTitle(_("Make image ready"))
self._label1.setText(_("Please make sure the image is rotated completely upright with the rotation slider under this text:"))
self._label2.setText(_("When the image is completely upright, make a selection on the image with the mouse with in the left top the first question to include and in the right bottom the last answer to include. Click 'Next' when done."))
def getExcision(self):
return self._view.getExcision()
class OcrThread(QtCore.QThread):
def __init__(self, loadWordList, img, *args, **kwargs):
super().__init__(*args, **kwargs)
self._loadWordList = loadWordList
self._img = img
def run(self):
fd, imgPath = tempfile.mkstemp(".png")
os.close(fd)
self._img.save(imgPath)
self.lesson = self._loadWordList(imgPath)
os.remove(imgPath)
class PreviewPage(QtWidgets.QWizardPage):
def __init__(self, loadWordList, composeList, *args, **kwargs):
super().__init__(*args, **kwargs)
self._loadWordList = loadWordList
self._composeList = composeList
self._label = QtWidgets.QLabel()
self._label.setWordWrap(True)
self._previewTextEdit = QtWidgets.QTextEdit()
self._previewTextEdit.setReadOnly(True)
vbox = QtWidgets.QVBoxLayout()
vbox.addWidget(self._label)
vbox.addWidget(self._previewTextEdit)
self.setLayout(vbox)
def initializePage(self):
try:
image = self.wizard().getExcision()
except ValueError:
QtWidgets.QMessageBox.critical(self, _("No selection was made"), _("No selection was made. Please try again."))
#next event loop iteration, otherwise Qt messes up
#completely (combining multiple pages).
QtCore.QTimer.singleShot(0, self.wizard().back)
return
self._thread = OcrThread(self._loadWordList, image)
self._thread.finished.connect(self._showPreview)
self._thread.start()
self._dialog = QtWidgets.QProgressDialog(self, QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint)
self._dialog.setWindowTitle(_("Recognizing word list..."))
self._dialog.setLabelText(_("Recognizing word list..."))
self._dialog.setCancelButton(None)
self._dialog.setRange(0, 0)
self._dialog.setModal(True)
self._dialog.show()
def _showPreview(self):
self._dialog.hide()
self.lesson = self._thread.lesson
text = self._composeList(self.lesson)
self._previewTextEdit.setText(text)
def retranslate(self):
self._label.setText(_("Down here, you can see a preview of the result. There will probably be some wrongly recognized words in there, but the structure of the word list should be recognizable. If not, click 'Back' and try again. Otherwise, click 'Finish'."))
self.setTitle(_("Result preview"))
class OcrWizard(QtWidgets.QWizard):
def __init__(self, loadWordList, composeList, *args, **kwargs):
super().__init__(*args, **kwargs)
self._imageEditPage = ImageEditPage()
self._previewPage = PreviewPage(loadWordList, composeList)
self._pages = [
StartPage(),
ImageChoicePage(),
self._imageEditPage,
self._previewPage,
]
for page in self._pages:
self.addPage(page)
self.setWizardStyle(QtWidgets.QWizard.ClassicStyle)
def getExcision(self):
return self._imageEditPage.getExcision()
def getLesson(self):
return self._previewPage.lesson
def retranslate(self):
for page in self._pages:
page.retranslate()
class OcrGuiModule:
"""Provides a wizard that assists the user in loading a word list
from a picture or scan of a real-world word list. (E.g. out of a
book). The actual logic is in the ocrWordListLoader module,
although some work around that (making the image ready) is done
by this module.
"""
def __init__(self, moduleManager, *args, **kwargs):
super().__init__(*args, **kwargs)
self._mm = moduleManager
self.type = "ocrGui"
self.requires = (
self._mm.mods(type="ui"),
self._mm.mods(type="ocrWordListLoader"),
self._mm.mods(type="buttonRegister"),
self._mm.mods(type="wordListStringComposer"),
self._mm.mods(type="loaderGui"),
)
self.uses = (
self._mm.mods(type="translator"),
)
self.priorities = {
"all": 975,
}
self.filesWithTranslations = ("ocrGui.py",)
def enable(self):
global QtCore, QtGui, QtWidgets
try:
from PyQt5 import QtCore, QtGui, QtWidgets
except ImportError:
return
installQtClasses()
self._modules = next(iter(self._mm.mods(type="modules")))
self._button = self._modules.default("active", type="buttonRegister").registerButton("create")
self._button.clicked.handle(self._startWizard)
self._button.changePriority.send(self.priorities["all"])
self._button.changeSize.send("small")
try:
translator = self._modules.default("active", type="translator")
except IndexError:
pass
else:
translator.languageChanged.handle(self._retranslate)
self._retranslate()
self.active = True
_uiModule = property(lambda self: self._modules.default("active", type="ui"))
_loadWordList = property(lambda self: self._modules.default("active", type="ocrWordListLoader").loadWordList)
_composeList = property(lambda self: self._modules.default("active", type="wordListStringComposer").composeList)
exit _loadFromLesson = property(lambda self: self._modules.default("active", type="loaderGui").loadFromLesson)
def _startWizard(self):
self._wizard = OcrWizard(self._loadWordList, self._composeList)
tab = self._uiModule.addCustomTab(self._wizard)
tab.closeRequested.handle(self._wizard.reject)
self._wizard.tab = tab
self._retranslate()
self._wizard.finished.connect(tab.close)
self._wizard.accepted.connect(self._loadResultiveLesson)
def _loadResultiveLesson(self):
lesson = self._wizard.getLesson()
with contextlib.suppress(NotImplementedError):
self._loadFromLesson("words", lesson)
def _retranslate(self):
global _, ngettext
try:
translator = self._modules.default("active", type="translator")
except IndexError:
_, ngettext = str, lambda a, b, n: a if n == 1 else b
else:
_, ngettext = translator.gettextFunctions(
self._mm.resourcePath("translations")
)
self._button.changeText.send(_("Create words lesson from a scan or picture"))
if hasattr(self, "_wizard"):
self._wizard.retranslate()
self._wizard.setWindowTitle(_("Words lesson from picture"))
self._wizard.tab.title = self._wizard.windowTitle()
def disable(self):
self.active = False
del self._modules
del self._button
if hasattr(self, "_wizard"):
del self._pages
def init(moduleManager):
return OcrGuiModule(moduleManager)
|