File indexing completed on 2024-05-12 15:55:06
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) 2000-2016 Klaas Freitag <freitag@suse.de> * 0007 * Jonathan Marten <jjm@keelhaul.me.uk> * 0008 * * 0009 * Kooka is free software; you can redistribute it and/or modify it * 0010 * under the terms of the GNU Library General Public License as * 0011 * published by the Free Software Foundation and appearing in the * 0012 * file COPYING included in the packaging of this file; either * 0013 * version 2 of the License, or (at your option) any later version. * 0014 * * 0015 * As a special exception, permission is given to link this program * 0016 * with any version of the KADMOS OCR/ICR engine (a product of * 0017 * reRecognition GmbH, Kreuzlingen), and distribute the resulting * 0018 * executable without including the source code for KADMOS in the * 0019 * source distribution. * 0020 * * 0021 * This program is distributed in the hope that it will be useful, * 0022 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0023 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0024 * GNU General Public License for more details. * 0025 * * 0026 * You should have received a copy of the GNU General Public * 0027 * License along with this program; see the file COPYING. If * 0028 * not, see <http://www.gnu.org/licenses/>. * 0029 * * 0030 ************************************************************************/ 0031 0032 #include "ocrgocrengine.h" 0033 0034 #ifdef HAVE_ERRNO_H 0035 #include <errno.h> 0036 #endif 0037 #ifdef HAVE_STRERROR 0038 #include <string.h> 0039 #endif 0040 0041 #include <qregexp.h> 0042 #include <qfile.h> 0043 #include <qdir.h> 0044 #include <qtemporaryfile.h> 0045 #include <qtemporarydir.h> 0046 #include <qprocess.h> 0047 0048 #include <klocalizedstring.h> 0049 #include <kpluginfactory.h> 0050 #include <kconfigskeleton.h> 0051 0052 #include "imgsaver.h" 0053 #include "scanimage.h" 0054 #include "imageformat.h" 0055 #include "ocrgocrdialog.h" 0056 #include "executablepathdialogue.h" 0057 #include "kookasettings.h" 0058 #include "ocr_logging.h" 0059 0060 0061 K_PLUGIN_FACTORY_WITH_JSON(OcrGocrEngineFactory, "kookaocr-gocr.json", registerPlugin<OcrGocrEngine>();) 0062 #include "ocrgocrengine.moc" 0063 0064 0065 static const char *possibleResultFiles[] = { "out30.png", "out20.png", "out30.bmp", "out20.bmp", nullptr }; 0066 0067 0068 OcrGocrEngine::OcrGocrEngine(QObject *pnt, const QVariantList &args) 0069 : AbstractOcrEngine(pnt, "OcrGocrEngine") 0070 { 0071 m_tempDir = nullptr; 0072 m_inputFile = QString(); // input image file 0073 m_resultFile = QString(); // OCR result text file 0074 } 0075 0076 0077 AbstractOcrDialogue *OcrGocrEngine::createOcrDialogue(AbstractOcrEngine *plugin, QWidget *pnt) 0078 { 0079 return (new OcrGocrDialog(plugin, pnt)); 0080 } 0081 0082 0083 bool OcrGocrEngine::createOcrProcess(AbstractOcrDialogue *dia, ScanImage::Ptr img) 0084 { 0085 OcrGocrDialog *gocrDia = static_cast<OcrGocrDialog *>(dia); 0086 const QString cmd = gocrDia->getOCRCmd(); 0087 0088 const char *format; 0089 if (img->depth() == 1) { 0090 format = "PBM"; // B&W bitmap 0091 } else if (img->isGrayscale()) { 0092 format = "PGM"; // greyscale 0093 } else { 0094 format = "PPM"; // colour 0095 } 0096 0097 // TODO: if the input file is local and is readable by GOCR, 0098 // can use it directly (but don't delete it afterwards!) 0099 m_inputFile = tempSaveImage(img, ImageFormat(format)); 0100 // save image to a temp file 0101 QProcess *proc = initOcrProcess(); // start process for OCR 0102 QStringList args; // arguments for process 0103 // new unique temporary directory 0104 m_tempDir = new QTemporaryDir(QDir::tempPath()+"/ocrgocrdir_XXXXXX"); 0105 proc->setWorkingDirectory(m_tempDir->path()); // run process in there 0106 0107 if (!isBW()) // Not a B&W image 0108 { 0109 args << "-l" << QString::number(gocrDia->getGraylevel()); 0110 } 0111 args << "-s" << QString::number(gocrDia->getSpaceWidth()); 0112 args << "-d" << QString::number(gocrDia->getDustsize()); 0113 args << "-a" << QString::number(gocrDia->getCertainty()); 0114 0115 // The verbosity value is a bitfield. 1 means to print more information, 0116 // 32 means output a PNG result image (which is always requested). 0117 args << "-v" << (gocrDia->verboseDebug() ? "33" : "32"); 0118 0119 // TODO: use '-f' to output XML (with position and accuracy data) 0120 0121 // Specify this explicitly, because "-" does not mean the same 0122 // as "/dev/stdout". The former interleaves the progress output 0123 // with the OCR result text. See GOCR's process_arguments() 0124 // in gocr.c and ini_progress() in progress.c. 0125 // 0126 // This is still not very useful because GOCR only outputs progress 0127 // information every 10 seconds with no option for a shorter interval. 0128 // Set by 'time_t printinterval = 10' in GOCR's progress.c. 0129 // 0130 // Even for OCR processing which takes longer than that, it is still 0131 // not very useful because Kooka doesn't use the progress result - nothing 0132 // connects to the ocrProgress() signal :-( 0133 args << "-x" << "/dev/stdout"; // progress to stdout 0134 0135 m_resultFile = tempFileName("gocrout.txt"); // OCR result text file 0136 args << "-o" << QFile::encodeName(m_resultFile); 0137 0138 args << "-i" << QFile::encodeName(m_inputFile); // input image file 0139 0140 proc->setProgram(cmd); 0141 proc->setArguments(args); 0142 0143 proc->setReadChannel(QProcess::StandardOutput); // collect stdout for progress 0144 connect(proc, &QProcess::readyReadStandardOutput, this, &OcrGocrEngine::slotGOcrStdout); 0145 0146 return (runOcrProcess()); 0147 } 0148 0149 0150 bool OcrGocrEngine::finishedOcrProcess(QProcess *proc) 0151 { 0152 QFile rf(m_resultFile); 0153 if (!rf.open(QIODevice::ReadOnly)) { 0154 #ifdef HAVE_STRERROR 0155 const char *reason = strerror(errno); 0156 #else 0157 const char *reason = ""; 0158 #endif 0159 setErrorText(xi18nc("@info", "Cannot read GOCR result file <filename>%1</filename><nl/>%2", m_resultFile, reason)); 0160 return (false); 0161 } 0162 0163 qCDebug(OCR_LOG) << "reading" << m_resultFile; 0164 const QString ocrResultText = rf.readAll(); // read all the result text 0165 rf.close(); // finished with result file 0166 0167 // Now all the text output by GOCR is in m_ocrResultText. Split this up 0168 // first into lines and then into words, and save this as the OCR results. 0169 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) 0170 QStringList lines = ocrResultText.split('\n', Qt::SkipEmptyParts); 0171 #else 0172 QStringList lines = ocrResultText.split('\n', QString::SkipEmptyParts); 0173 #endif 0174 //qCDebug(OCR_LOG) << "RESULT" << ocrResultText; 0175 qCDebug(OCR_LOG) << "split to" << lines.count() << "lines"; 0176 0177 startResultDocument(); 0178 0179 for (QStringList::const_iterator itLine = lines.constBegin(); itLine != lines.constEnd(); ++itLine) 0180 { 0181 startLine(); 0182 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) 0183 QStringList words = (*itLine).split(QRegExp("\\s+"), Qt::SkipEmptyParts); 0184 #else 0185 QStringList words = (*itLine).split(QRegExp("\\s+"), QString::SkipEmptyParts); 0186 #endif 0187 for (QStringList::const_iterator itWord = words.constBegin(); itWord != words.constEnd(); ++itWord) { 0188 OcrWordData wd; 0189 addWord((*itWord), wd); 0190 } 0191 finishLine(); 0192 } 0193 0194 finishResultDocument(); 0195 qCDebug(OCR_LOG) << "finished reading results"; 0196 0197 // Find the GOCR result image 0198 QDir dir(m_tempDir->path()); 0199 QString foundResult; 0200 const char **prf = possibleResultFiles; 0201 while (*prf != nullptr) { // search for result files 0202 QString ri = dir.absoluteFilePath(*prf); 0203 if (QFile::exists(ri)) { // take first one that matches 0204 qCDebug(OCR_LOG) << "found result image" << ri; 0205 foundResult = ri; 0206 break; 0207 } 0208 ++prf; 0209 } 0210 0211 // This used to replace the introduced image with the result file, having 0212 // been cleared above: 0213 // 0214 // if (m_introducedImage!=nullptr) delete m_introducedImage; 0215 // m_introducedImage = new ScanImage(); 0216 // ... 0217 // if (!m_ocrResultFile.isNull()) m_introducedImage->load(m_ocrResultFile); 0218 // else //qCDebug(OCR_LOG) << "cannot find result image in" << dir.absolutePath(); 0219 // 0220 // But that seems pointless - the replaced m_introducedImage was not 0221 // subsequently used anywhere (although would it be reused if "Start OCR" 0222 // were done again without closing the dialogue?). Not doing this means 0223 // that m_introducedImage can be const, which in turn means that it can 0224 // refer to the viewed image directly - i.e. there is no need to take a 0225 // copy in OcrEngine::setImage(). The result image is still displayed in 0226 // the image viewer in OcrEngine::finishedOCRVisible(). 0227 if (!foundResult.isEmpty()) setResultImage(foundResult); 0228 else qCDebug(OCR_LOG) << "cannot find result image in" << dir.absolutePath(); 0229 0230 return (true); 0231 } 0232 0233 0234 QStringList OcrGocrEngine::tempFiles(bool retain) 0235 { 0236 QStringList result; 0237 0238 result << m_inputFile << m_resultFile; 0239 0240 if (m_tempDir != nullptr) { 0241 result << m_tempDir->path(); 0242 m_tempDir->setAutoRemove(!retain); 0243 delete m_tempDir; // autoRemove will do the rest 0244 m_tempDir = nullptr; 0245 } 0246 0247 return (result); 0248 } 0249 0250 void OcrGocrEngine::slotGOcrStdout() 0251 { 0252 // This never seems to match! Format from an earlier GOCR version? 0253 QRegExp rx1("^\\s*(\\d+)\\s+(\\d+)"); 0254 0255 // GOCR 0.49 20100924 prints progress as: 0256 // 0257 // progress pgm2asc_main 100 / 100 time[s] 7 / 7 (skip=63) 0258 // 0259 // Split up because we don't know what the 2nd field (counter name) 0260 // may contain. 0261 QRegExp rx2a("^\\s*progress "); 0262 QRegExp rx2b("\\s(\\d+)\\s+/\\s+(\\d+)\\s+time"); 0263 0264 int progress = -1; 0265 int subProgress; 0266 0267 QByteArray line; 0268 while (!(line = ocrProcess()->readLine()).isEmpty()) { 0269 //qCDebug(OCR_LOG) << "GOCR stdout:" << line; 0270 // Calculate OCR progress 0271 if (rx1.indexIn(line) > -1) { 0272 progress = rx1.capturedTexts()[1].toInt(); 0273 subProgress = rx1.capturedTexts()[2].toInt(); 0274 } else if (rx2a.indexIn(line) > -1 && rx2b.indexIn(line) > -1) { 0275 progress = rx2b.capturedTexts()[1].toInt(); 0276 subProgress = rx2b.capturedTexts()[2].toInt(); 0277 } 0278 0279 if (progress > 0) emit ocrProgress(progress, subProgress); 0280 } 0281 } 0282 0283 0284 void OcrGocrEngine::openAdvancedSettings() 0285 { 0286 ExecutablePathDialogue d(nullptr); 0287 0288 QString exec = KookaSettings::ocrGocrBinary(); 0289 if (exec.isEmpty()) 0290 { 0291 KConfigSkeletonItem *ski = KookaSettings::self()->ocrGocrBinaryItem(); 0292 ski->setDefault(); 0293 exec = KookaSettings::ocrGocrBinary(); 0294 } 0295 0296 d.setPath(exec); 0297 d.setLabel(i18n("Name or path of the GOCR executable:")); 0298 if (!d.exec()) return; 0299 0300 KookaSettings::setOcrGocrBinary(d.path()); 0301 }