File indexing completed on 2024-05-12 15:55:08
0001 /************************************************************************ 0002 * * 0003 * This file is part of Kooka, a scanning/OCR application using * 0004 * Qt <http://www.qt.io> and KDE Frameworks <http://www.kde.org>. * 0005 * * 0006 * Copyright (C) 2020 Jonathan Marten <jjm@keelhaul.me.uk> * 0007 * * 0008 * Kooka is free software; you can redistribute it and/or modify it * 0009 * under the terms of the GNU Library General Public License as * 0010 * published by the Free Software Foundation and appearing in the * 0011 * file COPYING included in the packaging of this file; either * 0012 * version 2 of the License, or (at your option) any later version. * 0013 * * 0014 * As a special exception, permission is given to link this program * 0015 * with any version of the KADMOS OCR/ICR engine (a product of * 0016 * reRecognition GmbH, Kreuzlingen), and distribute the resulting * 0017 * executable without including the source code for KADMOS in the * 0018 * source distribution. * 0019 * * 0020 * This program is distributed in the hope that it will be useful, * 0021 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0023 * GNU General Public License for more details. * 0024 * * 0025 * You should have received a copy of the GNU General Public * 0026 * License along with this program; see the file COPYING. If * 0027 * not, see <http://www.gnu.org/licenses/>. * 0028 * * 0029 ************************************************************************/ 0030 0031 #include "ocrtesseractdialog.h" 0032 0033 #include <qlabel.h> 0034 #include <qregexp.h> 0035 #include <qcombobox.h> 0036 #include <qlayout.h> 0037 #include <qprogressbar.h> 0038 0039 #include <klocalizedstring.h> 0040 #include <kurlrequester.h> 0041 #include <kprocess.h> 0042 0043 #include "kookapref.h" 0044 #include "kookasettings.h" 0045 #include "dialogbase.h" 0046 0047 #include "ocrtesseractengine.h" 0048 #include "ocr_logging.h" 0049 0050 0051 OcrTesseractDialog::OcrTesseractDialog(AbstractOcrEngine *plugin, QWidget *pnt) 0052 : AbstractOcrDialogue(plugin, pnt), 0053 m_setupWidget(nullptr), 0054 m_ocrCmd(QString()), 0055 m_versionNum(0), 0056 m_versionStr(QString()) 0057 { 0058 } 0059 0060 0061 bool OcrTesseractDialog::setupGui() 0062 { 0063 AbstractOcrDialogue::setupGui(); // build the standard GUI 0064 0065 // Options available vary with the Tesseract version. So we need to find 0066 // the Tesseract binary and get its version before creating the GUI. 0067 m_ocrCmd = engine()->findExecutable(&KookaSettings::ocrTesseractBinary, KookaSettings::self()->ocrTesseractBinaryItem()); 0068 0069 if (!m_ocrCmd.isEmpty()) getVersion(m_ocrCmd); // found, get its version 0070 else // not found or invalid 0071 { 0072 engine()->setErrorText(i18n("The Tesseract executable is not configured or is not available.")); 0073 } 0074 0075 QWidget *w = addExtraSetupWidget(); 0076 QGridLayout *gl = new QGridLayout(w); 0077 int row = 0; 0078 0079 // Language, auto detected values 0080 QMap<QString,QString> vals = getValidValues("list-langs"); 0081 KConfigSkeletonItem *ski = KookaSettings::self()->ocrTesseractLanguageItem(); 0082 Q_ASSERT(ski!=nullptr); 0083 QLabel *l = new QLabel(ski->label(), w); 0084 gl->addWidget(l, row, 0); 0085 m_language = new QComboBox(w); 0086 m_language->setToolTip(ski->toolTip()); 0087 m_language->addItem(i18n("(default)"), QString()); 0088 for (QMap<QString,QString>::const_iterator it = vals.constBegin(); it!=vals.constEnd(); ++it) 0089 { 0090 m_language->addItem((!it.value().isEmpty() ? it.value() : it.key()), it.key()); 0091 } 0092 0093 if (vals.isEmpty()) m_language->setEnabled(false); 0094 else 0095 { 0096 int ix = m_language->findData(KookaSettings::ocrTesseractLanguage()); 0097 if (ix!=-1) m_language->setCurrentIndex(ix); 0098 } 0099 0100 gl->addWidget(m_language, row, 1); 0101 l->setBuddy(m_language); 0102 ++row; 0103 0104 // User words, from file 0105 ski = KookaSettings::self()->ocrTesseractUserWordsItem(); 0106 Q_ASSERT(ski!=nullptr); 0107 l = new QLabel(ski->label(), w); 0108 gl->addWidget(l, row, 0); 0109 m_userWords = new KUrlRequester(w); 0110 m_userWords->setAcceptMode(QFileDialog::AcceptOpen); 0111 m_userWords->setMode(KFile::File|KFile::ExistingOnly|KFile::LocalOnly); 0112 m_userWords->setPlaceholderText(i18n("Select a file if required...")); 0113 0114 QUrl u = KookaSettings::ocrTesseractUserWords(); 0115 if (u.isValid()) m_userWords->setUrl(u); 0116 0117 gl->addWidget(m_userWords, row, 1); 0118 l->setBuddy(m_userWords); 0119 ++row; 0120 0121 // User patterns, from file 0122 ski = KookaSettings::self()->ocrTesseractUserPatternsItem(); 0123 Q_ASSERT(ski!=nullptr); 0124 l = new QLabel(ski->label(), w); 0125 gl->addWidget(l, row, 0); 0126 m_userPatterns = new KUrlRequester(w); 0127 m_userPatterns->setAcceptMode(QFileDialog::AcceptOpen); 0128 m_userPatterns->setMode(KFile::File|KFile::ExistingOnly|KFile::LocalOnly); 0129 m_userPatterns->setPlaceholderText(i18n("Select a file if required...")); 0130 0131 u = KookaSettings::ocrTesseractUserPatterns(); 0132 if (u.isValid()) m_userPatterns->setUrl(u); 0133 0134 gl->addWidget(m_userPatterns, row, 1); 0135 l->setBuddy(m_userPatterns); 0136 ++row; 0137 0138 gl->setRowMinimumHeight(row, DialogBase::verticalSpacing()); 0139 ++row; 0140 0141 // Page segmentation mode, auto detected values 0142 vals = getValidValues("help-psm"); 0143 ski = KookaSettings::self()->ocrTesseractSegmentationModeItem(); 0144 Q_ASSERT(ski!=nullptr); 0145 l = new QLabel(ski->label(), w); 0146 gl->addWidget(l, row, 0); 0147 m_segmentationMode = new QComboBox(w); 0148 m_segmentationMode->setToolTip(ski->toolTip()); 0149 m_segmentationMode->addItem(i18n("(default)"), QString()); 0150 for (QMap<QString,QString>::const_iterator it = vals.constBegin(); it != vals.constEnd(); ++it) 0151 { 0152 m_segmentationMode->addItem(it.value(), it.key()); 0153 } 0154 0155 if (vals.isEmpty()) m_segmentationMode->setEnabled(false); 0156 else 0157 { 0158 int ix = m_segmentationMode->findData(KookaSettings::ocrTesseractSegmentationMode()); 0159 if (ix!=-1) m_segmentationMode->setCurrentIndex(ix); 0160 } 0161 0162 gl->addWidget(m_segmentationMode, row, 1); 0163 l->setBuddy(m_segmentationMode); 0164 ++row; 0165 0166 // OCR engine mode, auto detected values 0167 vals = getValidValues("help-oem"); 0168 ski = KookaSettings::self()->ocrTesseractEngineModeItem(); 0169 Q_ASSERT(ski!=nullptr); 0170 l = new QLabel(ski->label(), w); 0171 gl->addWidget(l, row, 0); 0172 m_engineMode = new QComboBox(w); 0173 m_engineMode->setToolTip(ski->toolTip()); 0174 m_engineMode->addItem(i18n("(default)"), QString()); 0175 for (QMap<QString,QString>::const_iterator it = vals.constBegin(); it != vals.constEnd(); ++it) 0176 { 0177 m_engineMode->addItem(it.value(), it.key()); 0178 } 0179 0180 if (vals.isEmpty()) m_engineMode->setEnabled(false); 0181 else 0182 { 0183 int ix = m_engineMode->findData(KookaSettings::ocrTesseractEngineMode()); 0184 if (ix!=-1) m_engineMode->setCurrentIndex(ix); 0185 } 0186 0187 gl->addWidget(m_engineMode, row, 1); 0188 l->setBuddy(m_engineMode); 0189 ++row; 0190 0191 gl->setRowMinimumHeight(row, DialogBase::verticalSpacing()); 0192 ++row; 0193 0194 gl->setRowStretch(row-1, 1); // for top alignment 0195 gl->setColumnStretch(1, 1); 0196 0197 ocrShowInfo(m_ocrCmd, m_versionStr); // show the binary and version 0198 progressBar()->setRange(0, 0); // progress animation only 0199 0200 m_setupWidget = w; 0201 return (!m_ocrCmd.isEmpty()); 0202 } 0203 0204 0205 void OcrTesseractDialog::slotWriteConfig() 0206 { 0207 AbstractOcrDialogue::slotWriteConfig(); 0208 0209 KookaSettings::setOcrTesseractBinary(getOCRCmd()); 0210 0211 KookaSettings::setOcrTesseractLanguage(m_language->currentData().toString()); 0212 KookaSettings::setOcrTesseractUserWords(m_userWords->url()); 0213 KookaSettings::setOcrTesseractUserPatterns(m_userPatterns->url()); 0214 KookaSettings::setOcrTesseractSegmentationMode(m_segmentationMode->currentData().toString()); 0215 KookaSettings::setOcrTesseractEngineMode(m_engineMode->currentData().toString()); 0216 } 0217 0218 0219 void OcrTesseractDialog::enableFields(bool enable) 0220 { 0221 m_setupWidget->setEnabled(enable); 0222 } 0223 0224 0225 void OcrTesseractDialog::getVersion(const QString &bin) 0226 { 0227 qCDebug(OCR_LOG) << "of" << bin; 0228 if (bin.isEmpty()) return; 0229 0230 KProcess proc; 0231 proc.setOutputChannelMode(KProcess::MergedChannels); 0232 proc << bin << "-v"; 0233 0234 int status = proc.execute(5000); 0235 if (status==0) 0236 { 0237 QByteArray output = proc.readAllStandardOutput(); 0238 QRegExp rx("tesseract ([\\d\\.]+)"); 0239 if (rx.indexIn(output)>-1) 0240 { 0241 m_ocrCmd = bin; 0242 m_versionStr = rx.cap(1); 0243 m_versionNum = m_versionStr.left(1).toInt(); 0244 qCDebug(OCR_LOG) << "version" << m_versionStr << "=" << m_versionNum; 0245 } 0246 } 0247 else 0248 { 0249 qCDebug(OCR_LOG) << "failed with status" << status; 0250 m_versionStr = i18n("Error"); 0251 } 0252 } 0253 0254 0255 QMap<QString,QString> OcrTesseractDialog::getValidValues(const QString &opt) 0256 { 0257 // The values displayed by and passed to Tesseract for OEM and PSM are integers, 0258 // but the values for the language are strings. For simplicity we treat the 0259 // OEM/PSM settings as strings also. 0260 QMap<QString,QString> result; 0261 0262 KConfigSkeletonItem *ski = KookaSettings::self()->ocrTesseractValidValuesItem(); 0263 Q_ASSERT(ski!=nullptr); 0264 QString groupName = QString("%1_v%2").arg(ski->group()).arg(m_versionStr); 0265 KConfigGroup grp = KookaSettings::self()->config()->group(groupName); 0266 0267 if (grp.hasKey(opt+"_keys")) // values in config already 0268 { 0269 qCDebug(OCR_LOG) << "option" << opt << "already in config"; 0270 0271 const QStringList keys = grp.readEntry(opt+"_keys", QStringList()); 0272 const QStringList descs = grp.readEntry(opt+"_descs", QStringList()); 0273 for (int i = 0; i<keys.count(); ++i) result.insert(keys.at(i), descs.at(i)); 0274 } 0275 else // not in config, need to extract 0276 { 0277 if (m_ocrCmd.isEmpty()) 0278 { 0279 qCWarning(OCR_LOG) << "cannot get values, no binary"; 0280 return (result); 0281 } 0282 0283 KProcess proc; 0284 proc.setOutputChannelMode(KProcess::MergedChannels); 0285 proc << m_ocrCmd << QString("--%1").arg(opt); 0286 0287 proc.execute(5000); 0288 const QByteArray output = proc.readAllStandardOutput(); 0289 const QList<QByteArray> lines = output.split('\n'); 0290 for (const QByteArray &line : lines) 0291 { 0292 const QString lineStr = QString::fromLocal8Bit(line); 0293 qCDebug(OCR_LOG) << "line:" << lineStr; 0294 0295 QRegExp rx; 0296 if (opt=="list-langs") rx.setPattern("^\\s*(\\w+)()$"); 0297 else rx.setPattern("^\\s*(\\d+)\\s+(\\w.+)?$"); 0298 if (rx.indexIn(lineStr)>-1) 0299 { 0300 const QString value = rx.cap(1); 0301 QString desc = rx.cap(2).simplified(); 0302 if (desc.endsWith(QLatin1Char('.')) || desc.endsWith(QLatin1Char(','))) desc.chop(1); 0303 result.insert(value, desc); 0304 } 0305 } 0306 0307 qCDebug(OCR_LOG) << "parsed result count" << result.count(); 0308 if (!result.isEmpty()) 0309 { // save result for next time 0310 grp.writeEntry(opt+"_keys", result.keys()); // ordered list of keys 0311 grp.writeEntry(opt+"_descs", result.values()); // same-ordered list of values 0312 grp.sync(); 0313 } 0314 } 0315 0316 qCDebug(OCR_LOG) << "values for" << opt << "=" << result.keys(); 0317 return (result); 0318 }