diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | 47d455dd55be855e4cc691c32f687f723d9247ee (patch) | |
tree | 52e236aaa2576bdb3840ebede26619692fed6d7d /kooka/ksaneocr.cpp | |
download | tdegraphics-47d455dd55be855e4cc691c32f687f723d9247ee.tar.gz tdegraphics-47d455dd55be855e4cc691c32f687f723d9247ee.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdegraphics@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kooka/ksaneocr.cpp')
-rw-r--r-- | kooka/ksaneocr.cpp | 1493 |
1 files changed, 1493 insertions, 0 deletions
diff --git a/kooka/ksaneocr.cpp b/kooka/ksaneocr.cpp new file mode 100644 index 00000000..cf10d682 --- /dev/null +++ b/kooka/ksaneocr.cpp @@ -0,0 +1,1493 @@ +/*************************************************************************** + ksaneocr.cpp - generic ocr + ------------------- + begin : Fri Jun 30 2000 + copyright : (C) 2000 by Klaas Freitag + email : freitag@suse.de + ***************************************************************************/ + +/*************************************************************************** + * * + * This file may be distributed and/or modified under the terms of the * + * GNU General Public License version 2 as published by the Free Software * + * Foundation and appearing in the file COPYING included in the * + * packaging of this file. * + * + * As a special exception, permission is given to link this program * + * with any version of the KADMOS ocr/icr engine of reRecognition GmbH, * + * Kreuzlingen and distribute the resulting executable without * + * including the source code for KADMOS in the source distribution. * + * + * As a special exception, permission is given to link this program * + * with any edition of Qt, and distribute the resulting executable, * + * without including the source code for Qt in the source distribution. * + * * + ***************************************************************************/ + +/* $Id$ */ + +#include <kdebug.h> +#include <kmessagebox.h> +#include <kconfig.h> +#include <kapplication.h> +#include <ktempfile.h> +#include <kprocess.h> +#include <stdlib.h> +#include <kspell.h> +#include <kspelldlg.h> +#include <qfile.h> +#include <qcolor.h> +#include <stdio.h> +#include <unistd.h> + +#include <img_canvas.h> + +#include "img_saver.h" +#include "kadmosocr.h" +#include "kocrbase.h" +#include "kocrkadmos.h" +#include "kocrocrad.h" +#include "config.h" +#include "ksaneocr.h" +#include "kocrgocr.h" +#include "kookaimage.h" +#include "kookapref.h" +#include "ocrword.h" + +#include <qtimer.h> +#include <qregexp.h> +#include <klocale.h> +#include <qpaintdevice.h> +#include <qpainter.h> +#include <qpen.h> +#include <qbrush.h> +#include <qfileinfo.h> + +/* + * Thread support is disabled here because the kadmos lib seems not to be + * thread save unfortunately. See slotKadmosResult-comments for more information + */ + +KSANEOCR::KSANEOCR( QWidget*, KConfig *cfg ): + m_ocrProcessDia(0L), + daemon(0L), + visibleOCRRunning(false), + m_resultImage(0), + m_imgCanvas(0L), + m_spell(0L), + m_wantKSpell(true), + m_kspellVisible(true), + m_hideDiaWhileSpellcheck(true), + m_spellInitialConfig(0L), + m_parent(0L), + m_ocrCurrLine(0), + m_currHighlight(-1), + m_applyFilter(false), + m_unlinkORF(true) +{ + KConfig *konf = KGlobal::config (); + m_ocrEngine = OCRAD; + m_img = 0L; + m_tmpFile = 0L; + + if( cfg ) + m_hideDiaWhileSpellcheck = cfg->readBoolEntry( HIDE_BASE_DIALOG, true ); + /* + * a initial config is needed as a starting point for the config dialog + * but also for ocr without visible dialog. + */ + m_spellInitialConfig = new KSpellConfig( 0L, 0L ,0L, false ); + + if( konf ) + { + /* -- ocr dialog information -- */ + konf->setGroup( CFG_GROUP_OCR_DIA ); + QString eng = konf->readEntry(CFG_OCR_ENGINE, "ocrad"); + + if( eng == "ocrad" ) + { + m_ocrEngine = OCRAD; + } + else if( eng == "gocr" ) + { + m_ocrEngine = GOCR; + } +#ifdef HAVE_KADMOS + else if( eng == QString("kadmos") ) m_ocrEngine = KADMOS; +#endif + kdDebug(28000) << "OCR engine is " << eng << endl; + + m_unlinkORF = konf->readBoolEntry( CFG_OCR_CLEANUP, true ); + } + + /* resize m_blocks to size 1 since there is at least one block */ + m_blocks.resize(1); + +} + + +KSANEOCR::~KSANEOCR() +{ + if( daemon ) { + delete( daemon ); + daemon = 0; + } + if ( m_tmpFile ) + { + m_tmpFile->setAutoDelete( true ); + delete m_tmpFile; + } + + if( m_resultImage ) + { + delete m_resultImage; + m_resultImage = 0; + } + + if( m_img ) delete m_img; + if( m_spellInitialConfig ) delete m_spellInitialConfig; +} + +/* + * This slot is called to introduce a new image, usually if the user clicks on a + * new image either in the gallery or on the thumbnailview. + */ +void KSANEOCR::slSetImage(KookaImage *img ) +{ + if( ! img ) return ; + + if( m_img ) + delete m_img; + + // FIXME: copy all the image is bad. + m_img = new KookaImage(*img); + + if( m_ocrProcessDia ) + { + m_ocrProcessDia->introduceImage( m_img ); + } + + m_applyFilter = false; +} + +/* + * Request to visualise a line-box in the source image, KADMOS Engine + */ +void KSANEOCR::slLineBox( const QRect& ) +{ + if( ! m_img ) return; +} + + +/* + * starts visual ocr process. Depending on the ocr engine, this function creates + * a new dialog, and shows it. + */ +bool KSANEOCR::startOCRVisible( QWidget *parent ) +{ + if( visibleOCRRunning ) return( false ); + bool res = true; + + m_parent = parent; + + if( m_ocrEngine == GOCR ) + { + m_ocrProcessDia = new KGOCRDialog ( parent, m_spellInitialConfig ); + } + else if( m_ocrEngine == OCRAD ) + { + m_ocrProcessDia = new ocradDialog( parent, m_spellInitialConfig ); + } + else if( m_ocrEngine == KADMOS ) + { +#ifdef HAVE_KADMOS +/*** Kadmos Engine OCR ***/ + m_ocrProcessDia = new KadmosDialog( parent, m_spellInitialConfig ); +#else + KMessageBox::sorry(0, i18n("This version of Kooka was not compiled with KADMOS support.\n" + "Please select another OCR engine in Kooka's options dialog.")); + kdDebug(28000) << "Sorry, this version of Kooka has no KADMOS support" << endl; +#endif /* HAVE_KADMOS */ + } + else + { + kdDebug(28000) << "ERR Unknown OCR engine requested!" << endl; + } + + /* + * this part is independant from the engine again + */ + if( m_ocrProcessDia ) + { + m_ocrProcessDia->setupGui(); + + m_ocrProcessDia->introduceImage( m_img ); + visibleOCRRunning = true; + + connect( m_ocrProcessDia, SIGNAL( user1Clicked()), this, SLOT( startOCRProcess() )); + connect( m_ocrProcessDia, SIGNAL( closeClicked()), this, SLOT( slotClose() )); + connect( m_ocrProcessDia, SIGNAL( user2Clicked()), this, SLOT( slotStopOCR() )); + m_ocrProcessDia->show(); + + } + return( res ); +} + +/** + * This method should be called by the engine specific finish slots. + * It does the not engine dependant cleanups like re-enabling buttons etc. + */ + +void KSANEOCR::finishedOCRVisible( bool success ) +{ + bool doSpellcheck = m_wantKSpell; + + if( m_ocrProcessDia ) + { + m_ocrProcessDia->stopOCR(); + doSpellcheck = m_ocrProcessDia->wantSpellCheck(); + } + + if( success ) + { + QString goof = ocrResultText(); + + emit newOCRResultText(goof); + + if( m_imgCanvas ) + { + if( m_resultImage != 0 ) delete m_resultImage; + kdDebug(28000) << "Result image name: " << m_ocrResultImage << endl; + m_resultImage = new QImage( m_ocrResultImage, "BMP" ); + kdDebug(28000) << "New result image has dimensions: " << m_resultImage->width() << "x" << m_resultImage->height()<< endl; + /* The image canvas is non-zero. Set it to our image */ + m_imgCanvas->newImageHoldZoom( m_resultImage ); + m_imgCanvas->setReadOnly(true); + + /* now handle double clicks to jump to the word */ + m_applyFilter=true; + } + + /** now it is time to invoke the dictionary if required **/ + emit readOnlyEditor( false ); + + if( doSpellcheck ) + { + m_ocrCurrLine = 0; + /* + * create a new kspell object, based on the config of the base dialog + */ + + connect( new KSpell( m_parent, i18n("Kooka OCR Dictionary Check"), + this, SLOT( slSpellReady(KSpell*)), + m_ocrProcessDia->spellConfig() ), + SIGNAL( death()), this, SLOT(slSpellDead())); + } + + delete m_ocrProcessDia; + m_ocrProcessDia = 0L; + + } + + visibleOCRRunning = false; + cleanUpFiles(); + + + kdDebug(28000) << "# ocr finished #" << endl; +} + +/* + * starting the spell check on line m_ocrCurrLine if the line exists. + * If not, the function returns. + */ +void KSANEOCR::startLineSpellCheck() +{ + if( m_ocrCurrLine < m_ocrPage.size() ) + { + m_checkStrings = (m_ocrPage[m_ocrCurrLine]).stringList(); + + /* In case the checklist is empty, call the result slot immediately */ + if( m_checkStrings.count() == 0 ) + { + slCheckListDone(false); + return; + } + + kdDebug(28000)<< "Wordlist (size " << m_ocrPage[m_ocrCurrLine].count() << ", line " << m_ocrCurrLine << "):" << m_checkStrings.join(", ") << endl; + + // if( list.count() > 0 ) + + m_spell->checkList( &m_checkStrings, m_kspellVisible ); + kdDebug(28000)<< "Started!" << endl; + /** + * This call ends in three slots: + * 1. slMisspelling: Hit _before_ the dialog (if any) appears. Time to + * mark the wrong word. + * 2. slSpellCorrected: Hit if the user decided which word to use. + * 3. slCheckListDone: The line is finished. The global counter needs to be + * increased and this function needs to be called again. + **/ + + } + else + { + kdDebug(28000) << k_funcinfo <<" -- no more lines !" << endl; + m_spell->cleanUp(); + } +} + + + +/* User Cancel is called when the user does not really start the + * ocr but uses the cancel-Button to come out of the Dialog */ +void KSANEOCR::slotClose() +{ + kdDebug(28000) << "closing ocr Dialog" << endl; + if( daemon && daemon->isRunning() ) + { + kdDebug(28000) << "Still running - Killing daemon with Sig. 9" << endl; + daemon->kill(9); + } + finishedOCRVisible(false); +} + +void KSANEOCR::slotStopOCR() +{ + kdDebug(28000) << "closing ocr Dialog" << endl; + if( daemon && daemon->isRunning() ) + { + kdDebug(28000) << "Killing daemon with Sig. 9" << endl; + daemon->kill(9); + // that leads to the process being destroyed. + KMessageBox::error(0, i18n("The OCR-process was stopped.") ); + } + +} + +void KSANEOCR::startOCRAD( ) +{ + ocradDialog *ocrDia = static_cast<ocradDialog*>(m_ocrProcessDia); + + m_ocrResultImage = ocrDia->orfUrl(); + const QString cmd = ocrDia->getOCRCmd(); + + // if( m_ocrResultImage.isEmpty() ) + { + /* The url is empty. Start the program to fill up a temp file */ + m_ocrResultImage = ImgSaver::tempSaveImage( m_img, "BMP", 8 ); // m_tmpFile->name(); + kdDebug(28000) << "The new image name is <" << m_ocrResultImage << ">" << endl; + } + + m_ocrImagePBM = ImgSaver::tempSaveImage( m_img, "PBM", 1 ); + + /* temporar file for orf result */ + KTempFile *tmpOrf = new KTempFile( QString(), ".orf" ); + tmpOrf->setAutoDelete( false ); + tmpOrf->close(); + m_tmpOrfName = QFile::encodeName(tmpOrf->name()); + + + if( daemon ) + { + delete( daemon ); + daemon = 0; + } + + daemon = new KProcess; + Q_CHECK_PTR(daemon); + + *daemon << cmd; + *daemon << QString("-x"); + *daemon << m_tmpOrfName; // the orf result file + *daemon << QFile::encodeName( m_ocrImagePBM ); // The name of the image + *daemon << QString("-l"); + *daemon << QString::number( ocrDia->layoutDetectionMode()); + + KConfig *konf = KGlobal::config (); + KConfigGroupSaver( konf, CFG_GROUP_OCRAD ); + + QString format = konf->readEntry( CFG_OCRAD_FORMAT, "utf8"); + *daemon << QString("-F"); + *daemon << format; + + QString charset = konf->readEntry( CFG_OCRAD_CHARSET, "iso-8859-15"); + *daemon << QString("-c"); + *daemon << charset; + + + QString addArgs = konf->readEntry( CFG_OCRAD_EXTRA_ARGUMENTS, QString() ); + + if( !addArgs.isEmpty() ) + { + kdDebug(28000) << "Setting additional args from config for ocrad: " << addArgs << endl; + *daemon << addArgs; + } + + m_ocrResultText = ""; + + connect(daemon, SIGNAL(processExited(KProcess *)), + this, SLOT( ocradExited(KProcess*))); + connect(daemon, SIGNAL(receivedStdout(KProcess *, char*, int)), + this, SLOT( ocradStdIn(KProcess*, char*, int))); + connect(daemon, SIGNAL(receivedStderr(KProcess *, char*, int)), + this, SLOT( ocradStdErr(KProcess*, char*, int))); + + if (!daemon->start(KProcess::NotifyOnExit, KProcess::All)) + { + kdDebug(28000) << "Error starting ocrad-daemon!" << endl; + } + else + { + kdDebug(28000) << "Start OK" << endl; + + } + delete tmpOrf; +} + + +void KSANEOCR::ocradExited(KProcess* ) +{ + kdDebug(28000) << "ocrad exit " << endl; + QString err; + bool parseRes = true; + + if( ! readORF(m_tmpOrfName, err) ) + { + KMessageBox::error( m_parent, + i18n("Parsing of the OCR Result File failed:") + err, + i18n("Parse Problem")); + parseRes = false; + } + finishedOCRVisible( parseRes ); + +} + +void KSANEOCR::ocradStdErr(KProcess*, char* buffer, int buflen) +{ + QString errorBuffer = QString::fromLocal8Bit(buffer, buflen); + kdDebug(28000) << "ocrad says on stderr: " << errorBuffer << endl; + +} + +void KSANEOCR::ocradStdIn(KProcess*, char* buffer, int buflen) +{ + QString errorBuffer = QString::fromLocal8Bit(buffer, buflen); + kdDebug(28000) << "ocrad says on stdin: " << errorBuffer << endl; +} + + + + +/* + * This slot is fired if the user clicks on the 'Start' button of the GUI, no + * difference which engine is active. + */ +void KSANEOCR::startOCRProcess( void ) +{ + if( ! m_ocrProcessDia ) return; + + /* starting the animation, setting fields disabled */ + m_ocrProcessDia->startOCR(); + + kapp->processEvents(); + if( m_ocrEngine == OCRAD ) + { + startOCRAD(); + } + + if( m_ocrEngine == GOCR ) + { + /* + * Starting a gocr process + */ + + KGOCRDialog *gocrDia = static_cast<KGOCRDialog*>(m_ocrProcessDia); + + const QString cmd = gocrDia->getOCRCmd(); + + /* Save the image to a temp file */ + + /** + * Save images formats: + * Black&White: PBM + * Gray: PGM + * Bunt: PPM + */ + QString format; + if( m_img->depth() == 1 ) + format = "PBM"; + else if( m_img->isGrayscale() ) + format = "PGM"; + else + format = "PPM"; + + QString tmpFile = ImgSaver::tempSaveImage( m_img, format ); // m_tmpFile->name(); + + kdDebug(28000) << "Starting GOCR-Command: " << cmd << " on file " << tmpFile + << ", format " << format << endl; + + if( daemon ) { + delete( daemon ); + daemon = 0; + } + + daemon = new KProcess; + Q_CHECK_PTR(daemon); + m_ocrResultText = ""; + + connect(daemon, SIGNAL(processExited(KProcess *)), + this, SLOT( gocrExited(KProcess*))); + connect(daemon, SIGNAL(receivedStdout(KProcess *, char*, int)), + this, SLOT( gocrStdIn(KProcess*, char*, int))); + connect(daemon, SIGNAL(receivedStderr(KProcess *, char*, int)), + this, SLOT( gocrStdErr(KProcess*, char*, int))); + + QString opt; + *daemon << QFile::encodeName(cmd); + *daemon << "-x"; + *daemon << "-"; + if( !( m_img->numColors() > 0 && m_img->numColors() <3 )) /* not a bw-image */ + { + *daemon << "-l"; + opt.setNum(gocrDia->getGraylevel()); + *daemon << opt; + } + *daemon << "-s"; + opt.setNum(gocrDia->getSpaceWidth()); + *daemon << opt; + *daemon << "-d"; + opt.setNum(gocrDia->getDustsize()); + *daemon << opt; + + // Write an result image + *daemon << "-v"; + *daemon << "32"; + + // Unfortunately this is fixed by gocr. + m_ocrResultImage = "out30.bmp"; + + *daemon << QFile::encodeName(tmpFile); + + m_ocrCurrLine = 0; // Important in gocrStdIn to store the results + + if (!daemon->start(KProcess::NotifyOnExit, KProcess::All)) + { + kdDebug(28000) << "Error starting daemon!" << endl; + } + else + { + kdDebug(28000) << "Start OK" << endl; + + } + } +#ifdef HAVE_KADMOS + if( m_ocrEngine == KADMOS ) + { + KadmosDialog *kadDia = static_cast<KadmosDialog*>(m_ocrProcessDia); + + kdDebug(28000) << "Starting Kadmos OCR Engine" << endl; + + QString clasPath; /* target where the clasPath is written in */ + if( ! kadDia->getSelClassifier( clasPath ) ) + { + KMessageBox::error( m_parent, + i18n("The classifier file necessary for OCR cannot be loaded: %1;\n" + "OCR with the KADMOS engine is not possible." ). + arg(clasPath), i18n("KADMOS Installation Problem")); + finishedOCRVisible(false); + return; + } + QCString c = clasPath.latin1(); + + kdDebug(28000) << "Using classifier " << c << endl; + m_rep.Init( c ); + if( m_rep.kadmosError() ) /* check if kadmos initialised OK */ + { + KMessageBox::error( m_parent, + i18n("The KADMOS OCR system could not be started:\n") + + m_rep.getErrorText()+ + i18n("\nPlease check the configuration." ), + i18n("KADMOS Failure") ); + } + else + { + /** Since initialising succeeded, we start the ocr here **/ + m_rep.SetNoiseReduction( kadDia->getNoiseReduction() ); + m_rep.SetScaling( kadDia->getAutoScale() ); + kdDebug(28000) << "Image size " << m_img->width() << " x " << m_img->height() << endl; + kdDebug(28000) << "Image depth " << m_img->depth() << ", colors: " << m_img->numColors() << endl; +#define USE_KADMOS_FILEOP /* use a save-file for OCR instead of filling the reImage struct manually */ +#ifdef USE_KADMOS_FILEOP + m_tmpFile = new KTempFile( QString(), QString("bmp")); + m_tmpFile->setAutoDelete( false ); + m_tmpFile->close(); + QString tmpFile = m_tmpFile->name(); + kdDebug() << "Saving to file " << tmpFile << endl; + m_img->save( tmpFile, "BMP" ); + m_rep.SetImage(tmpFile); +#else + m_rep.SetImage(m_img); +#endif + // rep.Recognize(); + m_rep.run(); + + /* Dealing with threads or no threads (using QT_THREAD_SUPPORT to distinguish) + * If threads are here, the recognition task is started in its own thread. The gui thread + * needs to wait until the recognition thread is finished. Therefore, a timer is fired once + * that calls slotKadmosResult and checks if the recognition task is finished. If it is not, + * a new one-shot-timer is fired in slotKadmosResult. If it is, the OCR result can be + * processed. + * In case the system has no threads, the method start of the recognition engine does not + * return until it is ready, the user has to live with a non responsive gui while + * recognition is performed. The start()-method is implemented as a wrapper to the run() + * method of CRep, which does the recognition job. Instead of pulling up a timer, simply + * the result slot is called if start()=run() has finished. In the result slot, finished() + * is only a dummy always returning true to avoid more preprocessor tags here. + * Hope that works ... + * It does not :( That is why it is not used here. Maybe some day... + */ + } +#ifdef QT_THREAD_SUPPORT + /* start a timer and wait until it fires. */ + QTimer::singleShot( 500, this, SLOT( slotKadmosResult() )); +#else + slotKadmosResult(); +#endif + + } +#endif /* HAVE_KADMOS */ +} + +/* + * This method is called to check if the kadmos process was already finished, if + * thread support is enabled (check for preprocessor variable QT_THREAD_SUPPORT) + * The problem is that the kadmos library seems not to be thread stable so thread + * support should not be enabled by default. In case threads are enabled, this slot + * checks if the KADMOS engine is finished already and if not it fires a timer. + */ + +void KSANEOCR::slotKadmosResult() +{ +#ifdef HAVE_KADMOS + kdDebug(28000) << "check for Recognition finished" << endl; + + + if( m_rep.finished() ) + { + /* The recognition thread is finished. */ + kdDebug(28000) << "kadmos is finished." << endl; + + m_ocrResultText = ""; + if( ! m_rep.kadmosError() ) + { + int lines = m_rep.GetMaxLine(); + kdDebug(28000) << "Count lines: " << lines << endl; + m_ocrPage.clear(); + m_ocrPage.resize( lines ); + + for( int line = 0; line < m_rep.GetMaxLine(); line++ ) + { + // ocrWordList wordList = m_rep.getLineWords(line); + /* call an ocr engine independent method to use the spellbook */ + ocrWordList words = m_rep.getLineWords(line); + kdDebug(28000) << "Have " << words.count() << " entries in list" << endl; + m_ocrPage[line]=words; + } + + /* show results of ocr */ + m_rep.End(); + } + finishedOCRVisible( !m_rep.kadmosError() ); + + } + else + { + /* recognition thread is not yet finished. Wait another half a second. */ + QTimer::singleShot( 500, this, SLOT( slotKadmosResult() )); + /* Never comes here if no threads exist on the system */ + } +#endif /* HAVE_KADMOS */ +} + + + + +/* + * + */ +void KSANEOCR::gocrExited(KProcess* d) +{ + kdDebug(28000) << "daemonExited start !" << endl; + + /* Now all the text of gocr is in the member m_ocrResultText. This one must + * be split up now to m_ocrPage. First break up the lines, resize m_ocrPage + * accordingly and than go through every line and create ocrwords for every + * word. + */ + QStringList lines = QStringList::split( '\n', m_ocrResultText, true ); + + m_ocrPage.clear(); + m_ocrPage.resize( lines.count() ); + + kdDebug(28000) << "RESULT " << m_ocrResultText << " was splitted to lines " << lines.count() << endl; + + unsigned int lineCnt = 0; + + for ( QStringList::Iterator it = lines.begin(); it != lines.end(); ++it ) + { + kdDebug(28000) << "Splitting up line " << *it << endl; + ocrWordList ocrLine; + + QStringList words = QStringList::split( QRegExp( "\\s+" ), *it, false ); + for ( QStringList::Iterator itWord = words.begin(); itWord != words.end(); ++itWord ) + { + kdDebug(28000) << "Appending to results: " << *itWord << endl; + ocrLine.append( ocrWord( *itWord )); + } + m_ocrPage[lineCnt] = ocrLine; + lineCnt++; + } + kdDebug(28000) << "Finished to split!" << endl; + /* set the result pixmap to the result pix of gocr */ + if( ! m_resPixmap.load( m_ocrResultImage ) ) + { + kdDebug(28000) << "Can not load result image!" << endl; + } + + /* load the gocr result image */ + if( m_img ) delete m_img; + m_img = new KookaImage(); + m_img->load( "out30.bmp" ); + + finishedOCRVisible( d->normalExit() ); +} + +/* + * A sample orf snippet: + * + * # Ocr Results File. Created by GNU ocrad version 0.3pre1 + * total blocks 2 + * block 1 0 0 560 344 + * lines 5 + * line 1 chars 10 height 26 + * 71 109 17 26;2,'0'1,'o'0 + * 93 109 15 26;2,'1'1,'l'0 + * 110 109 18 26;1,'2'0 + * 131 109 18 26;1,'3'0 + * 151 109 19 26;1,'4'0 + * 172 109 17 26;1,'5'0 + * 193 109 17 26;1,'6'0 + * 213 108 17 27;1,'7'0 + * 232 109 18 26;1,'8'0 + * 253 109 17 26;1,'9'0 + * line 2 chars 14 height 27 + * + */ + +bool KSANEOCR::readORF( const QString& fileName, QString& errStr ) +{ + QFile file( fileName ); + QRegExp rx; + bool error = false; + + /* use a global line number counter here, not the one from the orf. The orf one + * starts at 0 for every block, but we want line-no counting page global here. + */ + unsigned int lineNo = 0; + int blockCnt = 0; + int currBlock = -1; + + + /* Fetch the numeric version of ocrad */ + ocradDialog *ocrDia = static_cast<ocradDialog*>(m_ocrProcessDia); + int ocradVersion = 0; + if( ocrDia ) + { + ocradVersion = ocrDia->getNumVersion(); + } + + /* clear the ocr result page */ + m_ocrPage.clear(); + kdDebug(28000) << "***** starting to analyse orf at " << fileName << " *****" << endl; + + /* some checks on the orf */ + QFileInfo fi( fileName ); + if( ! fi.exists() ) { + error = true; + errStr = i18n("The orf %1 does not exist.").arg(fileName); + } + if( ! error && ! fi.isReadable() ) { + error = true; + errStr = i18n("Permission denied on file %1.").arg(fileName); + } + + + if ( !error && file.open( IO_ReadOnly ) ) + { + QTextStream stream( &file ); + QString line; + QString recLine; // recognised line + + while ( !stream.atEnd() ) + { + line = stream.readLine().stripWhiteSpace(); // line of text excluding '\n' + int len = line.length(); + + if( ! line.startsWith( "#" )) // Comments + { + kdDebug(28000) << "# Line check |" << line << "|" << endl; + if( line.startsWith( "total blocks " ) ) // total count fo blocks, must be first line + { + blockCnt = line.right( len - 13 /* QString("total blocks ").length() */ ).toInt(); + kdDebug(28000) << "Amount of blocks: " << blockCnt << endl; + m_blocks.resize(blockCnt); + } + else if( line.startsWith( "total text blocks " )) + { + blockCnt = line.right( len - 18 /* QString("total text blocks ").length() */ ).toInt(); + kdDebug(28000) << "Amount of blocks (V. 10): " << blockCnt << endl; + m_blocks.resize(blockCnt); + } + else if( line.startsWith( "block ") || line.startsWith( "text block ") ) + { + rx.setPattern("^.*block\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)"); + if( rx.search( line ) > -1) + { + currBlock = (rx.cap(1).toInt())-1; + kdDebug(28000) << "Setting current block " << currBlock << endl; + QRect r( rx.cap(2).toInt(), rx.cap(3).toInt(), rx.cap(4).toInt(), rx.cap(5).toInt()); + m_blocks[currBlock] = r; + } + else + { + kdDebug(28000) << "WRN: Unknown block line: " << line << endl; + // Not a killing bug + } + } + else if( line.startsWith( "lines " )) + { + int lineCnt = line.right( len - 6 /* QString("lines ").length() */).toInt(); + m_ocrPage.resize(m_ocrPage.size()+lineCnt); + kdDebug(28000) << "Resized ocrPage to linecount " << lineCnt << endl; + } + else if( line.startsWith( "line" )) + { + // line 5 chars 13 height 20 + rx.setPattern("^line\\s+(\\d+)\\s+chars\\s+(\\d+)\\s+height\\s+\\d+" ); + if( rx.search( line )>-1 ) + { + kdDebug(28000) << "RegExp-Result: " << rx.cap(1) << " : " << rx.cap(2) << endl; + int charCount = rx.cap(2).toInt(); + ocrWord word; + QRect brect; + ocrWordList ocrLine; + ocrLine.setBlock(currBlock); + /* Loop over all characters in the line. Every char has it's own line + * defined in the orf file */ + kdDebug(28000) << "Found " << charCount << " chars for line " << lineNo << endl; + + for( int c=0; c < charCount && !stream.atEnd(); c++ ) + { + /* Read one line per character */ + QString charLine = stream.readLine(); + int semiPos = charLine.find(';'); + if( semiPos == -1 ) + { + kdDebug(28000) << "invalid line: " << charLine << endl; + } + else + { + QString rectStr = charLine.left( semiPos ); + QString results = charLine.remove(0, semiPos+1 ); + bool lineErr = false; + + // rectStr contains the rectangle info of for the character + // results contains the real result caracter + + // find the amount of alternatives. + int altCount = 0; + int h = results.find(','); // search the first comma + if( h > -1 ) { + // kdDebug(28000) << "Results of count search: " << results.left(h) << endl; + altCount = results.left(h).toInt(); + results = results.remove( 0, h+1 ).stripWhiteSpace(); + } else { + lineErr = true; + } + // kdDebug(28000) << "Results-line after cutting the alter: " << results << endl; + QChar detectedChar = UndetectedChar; + if( !lineErr ) + { + /* take the first alternative only FIXME */ + if( altCount > 0 ) + detectedChar = results[1]; + // kdDebug(28000) << "Found " << altCount << " alternatives for " + // << QString(detectedChar) << endl; + } + + /* Analyse the rectangle */ + if( ! lineErr && detectedChar != ' ' ) + { + // kdDebug(28000) << "STRING: " << rectStr << "<" << endl; + rx.setPattern( "(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)"); + if( rx.search( rectStr ) != -1 ) + { + /* unite the rectangles */ + QRect privRect( rx.cap(1).toInt(), rx.cap(2).toInt(), + rx.cap(3).toInt(), rx.cap(4).toInt() ); + word.setRect( word.rect() | privRect ); + } + else + { + kdDebug(28000) << "ERR: Unable to read rect info for char!" << endl; + lineErr = true; + } + } + + if( ! lineErr ) + { + /* store word if finished by a space */ + if( detectedChar == ' ' ) + { + /* add the block offset to the rect of the word */ + QRect r = word.rect(); + if( ocradVersion < 10 ) + { + QRect blockRect = m_blocks[currBlock]; + r.moveBy( blockRect.x(), blockRect.y()); + } + + word.setRect( r ); + ocrLine.append( word ); + word = ocrWord(); + } + else + { + word.append( detectedChar ); + } + } + } + } + if( !word.isEmpty() ) + { + /* add the block offset to the rect of the word */ + QRect r = word.rect(); + if( ocradVersion < 10 ) + { + QRect blockRect = m_blocks[currBlock]; + r.moveBy( blockRect.x(), blockRect.y()); + } + word.setRect( r ); + + ocrLine.append( word ); + } + if( lineNo < m_ocrPage.size() ) + { + kdDebug(29000) << "Store result line no " << lineNo << "=\"" << + ocrLine.first() << "..." << endl; + m_ocrPage[lineNo] = ocrLine; + lineNo++; + } + else + { + kdDebug(28000) << "ERR: line index overflow: " << lineNo << endl; + } + } + else + { + kdDebug(28000) << "ERR: Unknown line found: " << line << endl; + } + } + else + { + kdDebug(29000) << "Unknown line: " << line << endl; + } + } /* is a comment? */ + + } + file.close(); + } + return !error; +} + + +void KSANEOCR::cleanUpFiles( void ) +{ + if( m_tmpFile ) + { + delete m_tmpFile; + m_tmpFile = 0L; + } + + if( ! m_ocrResultImage.isEmpty()) + { + kdDebug(28000) << "Unlinking OCR Result image file!" << endl; + unlink(QFile::encodeName(m_ocrResultImage)); + m_ocrResultImage = QString(); + } + + if( ! m_ocrImagePBM.isEmpty()) + { + kdDebug(28000) << "Unlinking OCR PBM file!" << endl; + unlink( QFile::encodeName(m_ocrImagePBM)); + m_ocrImagePBM = QString(); + } + + if( ! m_tmpOrfName.isEmpty() ) + { + if( m_unlinkORF ) + { + unlink(QFile::encodeName(m_tmpOrfName)); + m_tmpOrfName = QString(); + } + else + { + kdDebug(28000) << "Do NOT unlink temp orf file " << m_tmpOrfName << endl; + } + } + + /* Delete the debug images of gocr ;) */ + unlink( "out20.bmp" ); +} + + +void KSANEOCR::gocrStdErr(KProcess*, char* buffer, int buflen) +{ + QString errorBuffer = QString::fromLocal8Bit(buffer, buflen); + kdDebug(28000) << "gocr says: " << errorBuffer << endl; + +} + + +void KSANEOCR::gocrStdIn(KProcess*, char* buffer, int buflen) +{ + QString aux = QString::fromLocal8Bit(buffer, buflen); + + QRegExp rx( "^\\s*\\d+\\s+\\d+"); + if( rx.search( aux ) > -1 ) + { + /* calculate ocr progress for gocr */ + int progress = rx.capturedTexts()[0].toInt(); + int subProgress = rx.capturedTexts()[1].toInt(); + // kdDebug(28000) << "Emitting progress: " << progress << endl; + emit ocrProgress( progress, subProgress ); + } + else + { + m_ocrResultText += aux; + } + + // kdDebug(28000) << aux << endl; + +} + +/* + * Assemble the result text + */ +QString KSANEOCR::ocrResultText() +{ + QString res; + const QString space(" "); + + /* start from the back and search the original word to replace it */ + QValueVector<ocrWordList>::iterator pageIt; + + for( pageIt = m_ocrPage.begin(); pageIt != m_ocrPage.end(); ++pageIt ) + { + /* thats goes over all lines */ + QValueList<ocrWord>::iterator lineIt; + for( lineIt = (*pageIt).begin(); lineIt != (*pageIt).end(); ++lineIt ) + { + res += space + *lineIt; + } + res += "\n"; + } + kdDebug(28000) << "Returning result String " << res << endl; + return res; +} + + +/* -------------------------------------------------------------------------------- + * event filter to filter the mouse events to the image viewer + */ + +void KSANEOCR::setImageCanvas( ImageCanvas *canvas ) +{ + m_imgCanvas = canvas; + + m_imgCanvas->installEventFilter( this ); +} + + +bool KSANEOCR::eventFilter( QObject *object, QEvent *event ) +{ + QWidget *w = (QWidget*) object; + + if( m_applyFilter && m_imgCanvas && w == m_imgCanvas ) + { + if( event->type() == QEvent::MouseButtonDblClick ) + { + QMouseEvent *mev = static_cast<QMouseEvent*>(event); + + int x = mev->x(); + int y = mev->y(); + int scale = m_imgCanvas->getScaleFactor(); + + m_imgCanvas->viewportToContents( mev->x(), mev->y(), + x, y ); + + kdDebug(28000) << "Clicked to " << x << "/" << y << ", scale " << scale << endl; + if( scale != 100 ) + { + // Scale is e.g. 50 that means tha the image is only half of size. + // thus the clicked coords must be multiplied with 2 + y = int(double(y)*100/scale); + x = int(double(x)*100/scale); + } + /* now search the word that was clicked on */ + QValueVector<ocrWordList>::iterator pageIt; + + int line = 0; + bool valid = false; + ocrWord wordToFind; + + for( pageIt = m_ocrPage.begin(); pageIt != m_ocrPage.end(); ++pageIt ) + { + QRect r = (*pageIt).wordListRect(); + + if( y > r.top() && y < r.bottom() ) + { + kdDebug(28000)<< "It is in between " << r.top() << "/" << r.bottom() + << ", line " << line << endl; + valid = true; + break; + } + line++; + } + + /* + * If valid, we have the line into which the user clicked. Now we + * have to find out the actual word + */ + if( valid ) + { + valid = false; + /* find the word in the line and mark it */ + ocrWordList words = *pageIt; + ocrWordList::iterator wordIt; + + for( wordIt = words.begin(); wordIt != words.end() && !valid; ++wordIt ) + { + QRect r = (*wordIt).rect(); + if( x > r.left() && x < r.right() ) + { + wordToFind = *wordIt; + valid = true; + } + } + + } + + /* + * if valid, the wordToFind contains the correct word now. + */ + if( valid ) + { + kdDebug(28000) << "Found the clicked word " << wordToFind << endl; + emit selectWord( line, wordToFind ); + } + + return true; + } + } + return false; +} + + + +/* -------------------------------------------------------------------------------- + * Spellbook support + */ + + +/* + * This slot is hit when the checkWord method of KSpell thinks a word is wrong. + * KSpell detects the correction by itself and delivers it in newword here. + * To see all alternatives KSpell proposes, slMissspelling must be used. + */ +void KSANEOCR::slSpellCorrected( const QString& originalword, + const QString& newword, + unsigned int pos ) +{ + kdDebug(28000) << "Corrected: Original Word " << originalword << " was corrected to " + << newword << ", pos ist " << pos << endl; + + kdDebug(28000) << "Dialog state is " << m_spell->dlgResult() << endl; + + if( slUpdateWord( m_ocrCurrLine, pos, originalword, newword ) ) + { + if( m_imgCanvas && m_currHighlight > -1 ) + { + if( m_applyFilter ) + m_imgCanvas->removeHighlight( m_currHighlight ); + } + else + { + kdDebug(28000) << "No highlighting to remove!" << endl; + } + } + +} + + +void KSANEOCR::slSpellIgnoreWord( const QString& word ) +{ + ocrWord ignoreOCRWord; + + ignoreOCRWord = ocrWordFromKSpellWord( m_ocrCurrLine, word ); + if( ! ignoreOCRWord.isEmpty() ) + { + emit ignoreWord( m_ocrCurrLine, ignoreOCRWord ); + + if( m_imgCanvas && m_currHighlight > -1 ) + { + m_imgCanvas->removeHighlight( m_currHighlight ); + + /* create a new highlight. That will never be removed */ + QBrush brush; + QPen pen( gray, 1 ); + QRect r = ignoreOCRWord.rect(); + r.moveBy(0,2); // a bit offset to the top + + if( m_applyFilter ) + m_imgCanvas->highlight( r, pen, brush ); + } + } +} + +ocrWord KSANEOCR::ocrWordFromKSpellWord( int line, const QString& word ) +{ + ocrWord resWord; + if( lineValid(line) ) + { + ocrWordList words = m_ocrPage[line]; + + words.findFuzzyIndex( word, resWord ); + } + + return resWord; +} + + +bool KSANEOCR::lineValid( int line ) +{ + bool ret = false; + + if( line >= 0 && (uint)line < m_ocrPage.count() ) + ret = true; + + return ret; +} + +void KSANEOCR::slMisspelling( const QString& originalword, const QStringList& suggestions, + unsigned int pos ) +{ + /* for the first try, use the first suggestion */ + ocrWord s( suggestions.first()); + kdDebug(28000) << "Misspelled: " << originalword << " at position " << pos << endl; + + int line = m_ocrCurrLine; + m_currHighlight = -1; + + // ocrWord resWord = ocrWordFromKSpellWord( line, originalword ); + ocrWordList words = m_ocrPage[line]; + ocrWord resWord; + kdDebug(28000) << "Size of wordlist (line " << line << "): " << words.count() << endl; + + if( pos < words.count() ) + { + resWord = words[pos]; + } + + if( ! resWord.isEmpty() ) + { + QBrush brush; + brush.setColor( QColor(red)); // , "Dense4Pattern" ); + brush.setStyle( Qt::Dense4Pattern ); + QPen pen( red, 2 ); + QRect r = resWord.rect(); + + r.moveBy(0,2); // a bit offset to the top + + if( m_applyFilter ) + m_currHighlight = m_imgCanvas->highlight( r, pen, brush, true ); + + kdDebug(28000) << "Position ist " << r.x() << ", " << r.y() << ", width: " + << r.width() << ", height: " << r.height() << endl; + + /* draw a line under the word to check */ + + /* copy the source */ + emit repaintOCRResImage(); + } + else + { + kdDebug(28000) << "Could not find the ocrword for " << originalword << endl; + } + + emit markWordWrong( line, resWord ); +} + +/* + * This is the global starting point for spell checking of the ocr result. + * After the KSpell object was created in method finishedOCRVisible, this + * slot is called if the KSpell-object feels itself ready for operation. + * Coming into this slot, the spelling starts in a line by line manner + */ +void KSANEOCR::slSpellReady( KSpell *spell ) +{ + m_spell = spell; + connect ( m_spell, SIGNAL( misspelling( const QString&, const QStringList&, + unsigned int )), + this, SLOT( slMisspelling(const QString& , + const QStringList& , + unsigned int ))); + connect( m_spell, SIGNAL( corrected ( const QString&, const QString&, unsigned int )), + this, SLOT( slSpellCorrected( const QString&, const QString&, unsigned int ))); + + connect( m_spell, SIGNAL( ignoreword( const QString& )), + this, SLOT( slSpellIgnoreWord( const QString& ))); + + connect( m_spell, SIGNAL( done(bool)), this, SLOT(slCheckListDone(bool))); + + kdDebug(28000) << "Spellcheck available" << endl; + + if( m_ocrProcessDia && m_hideDiaWhileSpellcheck ) + m_ocrProcessDia->hide(); + emit readOnlyEditor( true ); + startLineSpellCheck(); +} + +/** + * slot called after either the spellcheck finished or the KSpell object found + * out that it does not want to run because of whatever problems came up. + * If it is an KSpell-init problem, the m_spell variable is still zero and + * Kooka pops up a warning. + */ +void KSANEOCR::slSpellDead() +{ + if( ! m_spell ) + { + kdDebug(28000) << "Spellcheck NOT available" << endl; + /* Spellchecking has not yet been existing, thus there is a base problem with + * spellcheck on this system. + */ + KMessageBox::error( m_parent, + i18n("Spell-checking cannot be started on this system.\n" + "Please check the configuration" ), + i18n("Spell-Check") ); + + } + else + { + if( m_spell->status() == KSpell::Cleaning ) + { + kdDebug(28000) << "KSpell cleans up" << endl; + } + else if( m_spell->status() == KSpell::Finished ) + { + kdDebug(28000) << "KSpell finished" << endl; + } + else if( m_spell->status() == KSpell::Error ) + { + kdDebug(28000) << "KSpell finished with Errors" << endl; + } + else if( m_spell->status() == KSpell::Crashed ) + { + kdDebug(28000) << "KSpell Chrashed" << endl; + } + else + { + kdDebug(28000) << "KSpell finished with unknown state!" << endl; + } + + /* save the current config */ + delete m_spell; + m_spell = 0L; + + /* reset values */ + m_checkStrings.clear(); + m_ocrCurrLine = 0; + if( m_imgCanvas && m_currHighlight > -1 ) + m_imgCanvas->removeHighlight( m_currHighlight ); + + } + if( m_ocrProcessDia ) + m_ocrProcessDia->show(); + emit readOnlyEditor( false ); +} + + +/** + * This slot reads the current line from the member m_ocrCurrLine and + * writes the corrected wordlist to the member page word lists + */ +void KSANEOCR::slCheckListDone(bool) +{ + + /* + * nothing needs to be updated here in the texts, because it is already done + * in the slSpellCorrected slot + */ + + /* Check the dialog state here */ + if( m_spell->dlgResult() == KS_CANCEL || + m_spell->dlgResult() == KS_STOP ) + { + /* stop processing */ + m_spell->cleanUp(); + } + else + { + m_ocrCurrLine++; + kdDebug(28000) << "Starting spellcheck from CheckListDone" << endl; + startLineSpellCheck(); + } +} + +/** + * updates the word at position spellWordIndx in line line to the new word newWord. + * The original word was origWord. This slot is called from slSpellCorrected + * + */ +bool KSANEOCR::slUpdateWord( int line, int spellWordIndx, const QString& origWord, + const QString& newWord ) +{ + bool result = false; + + if( lineValid( line )) + { + ocrWordList words = m_ocrPage[line]; + kdDebug(28000) << "Updating word " << origWord << " to " << newWord << endl; + + if( words.updateOCRWord( words[spellWordIndx] /* origWord */, newWord ) ) // searches for the word and updates + { + result = true; + emit updateWord( line, origWord, newWord ); + } + else + kdDebug(28000) << "WRN: Update from " << origWord << " to " << newWord << " failed" << endl; + + } + else + { + kdDebug(28000) << "WRN: Line " << line << " no not valid!" << endl; + } + return result; +} + + +char KSANEOCR::UndetectedChar = '_'; + +/* -- */ +#include "ksaneocr.moc" |