3.01 code from http://github.com/jimregan/tesseract-ocr with addaptions related to Linux and Windows (VC2008) compile process

git-svn-id: https://tesseract-ocr.googlecode.com/svn/trunk@526 d0cd1f9f-072b-0410-8dd7-cf729c803f20
This commit is contained in:
zdenop@gmail.com 2010-11-23 18:34:14 +00:00
parent 7511d76315
commit 4523ce9f7d
558 changed files with 51788 additions and 41709 deletions

View File

@ -1,3 +1,38 @@
2010-09-22 - V3.01
* Thread-safety! Moved all critical globals and statics to
members of the appropriate class. Tesseract is now
thread-safe (multiple instances can be used in parallel
in multiple threads.) with the minor exception that some
control parameters are still global and affect all threads.
* Added Cube, a new recognizer for Arabic. Cube can also be
used in combination with normal Tesseract for other languages
with an improvement in accuracy at the cost of (much) lower speed.
There is no training module for Cube yet.
* OcrEngineMode in Init replaces AccuracyVSpeed to control cube.
* Greatly improved segmentation search with consequent accuracy and
speed improvements, especially for Chinese.
* Added PageIterator and ResultIterator as cleaner ways to get the
full results out of Tesseract, that are not currently provided
by any of the TessBaseAPI::Get* methods.
All other methods, such as the ETEXT_STRUCT in particular are
deprecated and will be deleted in the future.
* ApplyBoxes totally rewritten to make training easier.
It can now cope with touching/overlapping training characters,
and a new boxfile format allows word boxes instead of character
boxes, BUT to use that you have to have already boostrapped the
language with character boxes. "Cyclic dependency" on traineddata.
* Auto orientation and script detection added to page layout analysis.
* Deleted *lots* of dead code.
* Fixxht module replaced with scalable data-driven module.
* Output font characteristics accuracy improved.
* Removed the double conversion at each classification.
* Upgraded oldest structs to be classes and deprecated PBLOB.
* Removed non-deterministic baseline fit.
* Added fixed length dawgs for Chinese.
* Handling of vertical text improved.
* Handling of leader dots improved.
* Table detection greatly improved.
2010-09-21 - V3.00
* Preparations for thread safety:
* Changed TessBaseAPI methods to be non-static

View File

@ -1,6 +1,6 @@
# TODO(luc) Add 'doc' to this list when ready
ACLOCAL_AMFLAGS = -I m4
SUBDIRS = ccstruct ccutil classify cutil dict image textord viewer wordrec ccmain training tessdata testing java api vs2008
SUBDIRS = ccstruct ccutil classify cube cutil dict image neural_networks/runtime textord viewer wordrec ccmain training tessdata testing java api
#if USING_GETTEXT
#SUBDIRS += po
#AM_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\"

View File

@ -234,7 +234,7 @@ top_srcdir = @top_srcdir@
# TODO(luc) Add 'doc' to this list when ready
ACLOCAL_AMFLAGS = -I m4
SUBDIRS = ccstruct ccutil classify cutil dict image textord viewer wordrec ccmain training tessdata testing java api vs2008
SUBDIRS = ccstruct ccutil classify cube cutil dict image neural_networks/runtime textord viewer wordrec ccmain training tessdata testing java api
#if USING_GETTEXT
#SUBDIRS += po
#AM_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\"

View File

@ -1,3 +1,38 @@
Tesseract release notes Oct 1 2010 - V3.01
* Thread-safety! Moved all critical globals and statics to
members of the appropriate class. Tesseract is now
thread-safe (multiple instances can be used in parallel
in multiple threads.) with the minor exception that some
control parameters are still global and affect all threads.
* Added Cube, a new recognizer for Arabic. Cube can also be
used in combination with normal Tesseract for other languages
with an improvement in accuracy at the cost of (much) lower speed.
There is no training module for Cube yet.
* OcrEngineMode in Init replaces AccuracyVSpeed to control cube.
* Greatly improved segmentation search with consequent accuracy and
speed improvements, especially for Chinese.
* Added PageIterator and ResultIterator as cleaner ways to get the
full results out of Tesseract, that are not currently provided
by any of the TessBaseAPI::Get* methods.
All other methods, such as the ETEXT_STRUCT in particular are
deprecated and will be deleted in the future.
* ApplyBoxes totally rewritten to make training easier.
It can now cope with touching/overlapping training characters,
and a new boxfile format allows word boxes instead of character
boxes, BUT to use that you have to have already boostrapped the
language with character boxes. "Cyclic dependency" on traineddata.
* Auto orientation and script detection added to page layout analysis.
* Deleted *lots* of dead code.
* Fixxht module replaced with scalable data-driven module.
* Output font characteristics accuracy improved.
* Removed the double conversion at each classification.
* Upgraded oldest structs to be classes and deprecated PBLOB.
* Removed non-deterministic baseline fit.
* Added fixed length dawgs for Chinese.
* Handling of vertical text improved.
* Handling of leader dots improved.
* Table detection greatly improved.
Tesseract release notes Sep 30 2010 - V3.00
* Preparations for thread safety:
* Changed TessBaseAPI methods to be non-static

View File

@ -8,13 +8,15 @@ AM_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\"\
-I$(top_srcdir)/textord
include_HEADERS = \
baseapi.h tesseractmain.h
apitypes.h baseapi.h pageiterator.h resultiterator.h tesseractmain.h
lib_LTLIBRARIES = libtesseract_api.la
libtesseract_api_la_SOURCES = baseapi.cpp
libtesseract_api_la_SOURCES = baseapi.cpp pageiterator.cpp resultiterator.cpp
libtesseract_api_la_LDFLAGS = -version-info $(GENERIC_LIBRARY_VERSION)
libtesseract_api_la_LIBADD = \
../ccmain/libtesseract_main.la \
../cube/libtesseract_cube.la \
../neural_networks/runtime/libtesseract_neural.la \
../textord/libtesseract_textord.la \
../wordrec/libtesseract_wordrec.la \
../classify/libtesseract_classify.la \

View File

@ -74,6 +74,8 @@ am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(bindir)" \
"$(DESTDIR)$(includedir)"
LTLIBRARIES = $(lib_LTLIBRARIES)
libtesseract_api_la_DEPENDENCIES = ../ccmain/libtesseract_main.la \
../cube/libtesseract_cube.la \
../neural_networks/runtime/libtesseract_neural.la \
../textord/libtesseract_textord.la \
../wordrec/libtesseract_wordrec.la \
../classify/libtesseract_classify.la \
@ -82,7 +84,8 @@ libtesseract_api_la_DEPENDENCIES = ../ccmain/libtesseract_main.la \
../image/libtesseract_image.la ../cutil/libtesseract_cutil.la \
../viewer/libtesseract_viewer.la \
../ccutil/libtesseract_ccutil.la
am_libtesseract_api_la_OBJECTS = baseapi.lo
am_libtesseract_api_la_OBJECTS = baseapi.lo pageiterator.lo \
resultiterator.lo
libtesseract_api_la_OBJECTS = $(am_libtesseract_api_la_OBJECTS)
libtesseract_api_la_LINK = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) \
$(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
@ -294,13 +297,15 @@ AM_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\"\
-I$(top_srcdir)/textord
include_HEADERS = \
baseapi.h tesseractmain.h
apitypes.h baseapi.h pageiterator.h resultiterator.h tesseractmain.h
lib_LTLIBRARIES = libtesseract_api.la
libtesseract_api_la_SOURCES = baseapi.cpp
libtesseract_api_la_SOURCES = baseapi.cpp pageiterator.cpp resultiterator.cpp
libtesseract_api_la_LDFLAGS = -version-info $(GENERIC_LIBRARY_VERSION)
libtesseract_api_la_LIBADD = \
../ccmain/libtesseract_main.la \
../cube/libtesseract_cube.la \
../neural_networks/runtime/libtesseract_neural.la \
../textord/libtesseract_textord.la \
../wordrec/libtesseract_wordrec.la \
../classify/libtesseract_classify.la \
@ -446,6 +451,8 @@ distclean-compile:
-rm -f *.tab.c
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/baseapi.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pageiterator.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/resultiterator.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tesseractmain.Po@am__quote@
.cpp.o:

31
api/apitypes.h Normal file
View File

@ -0,0 +1,31 @@
///////////////////////////////////////////////////////////////////////
// File: apitypes.h
// Description: Types used in both the API and internally
// Author: Ray Smith
// Created: Wed Mar 03 09:22:53 PST 2010
//
// (C) Copyright 2010, Google Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////
#ifndef TESSERACT_API_APITYPES_H__
#define TESSERACT_API_APITYPES_H__
#include "publictypes.h"
// The types used by the API and Page/ResultIterator can be found in
// ccstruct/publictypes.h.
// API interfaces and API users should be sure to include this file, rather
// than the lower-level one, and lower-level code should be sure to include
// only the lower-level file.
#endif // TESSERACT_API_APITYPES_H__

File diff suppressed because it is too large Load Diff

View File

@ -17,28 +17,38 @@
//
///////////////////////////////////////////////////////////////////////
#ifndef TESSERACT_CCMAIN_BASEAPI_H__
#define TESSERACT_CCMAIN_BASEAPI_H__
#ifndef TESSERACT_API_BASEAPI_H__
#define TESSERACT_API_BASEAPI_H__
// To avoid collision with other typenames include the ABSOLUTE MINIMUM
// complexity of includes here. Use forward declarations wherever possible
// and hide includes of complex types in baseapi.cpp.
#include "apitypes.h"
#include "thresholder.h"
#include "unichar.h"
class PAGE_RES;
class PAGE_RES_IT;
class BLOCK_LIST;
class DENORM;
class IMAGE;
class PBLOB;
class ROW;
class STRING;
class WERD;
struct Pix;
struct Box;
struct Pixa;
struct Boxa;
struct ETEXT_STRUCT;
class ETEXT_DESC;
struct OSResults;
struct TBOX;
class TBOX;
#define MAX_NUM_INT_FEATURES 512
struct INT_FEATURE_STRUCT;
typedef INT_FEATURE_STRUCT *INT_FEATURE;
typedef INT_FEATURE_STRUCT INT_FEATURE_ARRAY[MAX_NUM_INT_FEATURES];
struct TBLOB;
#ifdef TESSDLL_EXPORTS
#define TESSDLL_API __declspec(dllexport)
@ -51,37 +61,21 @@ typedef INT_FEATURE_STRUCT INT_FEATURE_ARRAY[MAX_NUM_INT_FEATURES];
namespace tesseract {
class CubeRecoContext;
class Dawg;
class Dict;
class PageIterator;
class ResultIterator;
class Tesseract;
class Trie;
class CubeRecoContext;
class TesseractCubeCombiner;
class CubeObject;
class CubeLineObject;
class Dawg;
typedef int (Dict::*DictFunc)(void* void_dawg_args, int char_index,
const void *word, bool word_end);
typedef int (Dict::*DictFunc)(void* void_dawg_args,
UNICHAR_ID unichar_id, bool word_end);
typedef double (Dict::*ProbabilityInContextFunc)(const char* context,
int context_bytes,
const char* character,
int character_bytes);
enum PageSegMode {
PSM_AUTO, ///< Fully automatic page segmentation.
PSM_SINGLE_COLUMN, ///< Assume a single column of text of variable sizes.
PSM_SINGLE_BLOCK, ///< Assume a single uniform block of text. (Default.)
PSM_SINGLE_LINE, ///< Treat the image as a single text line.
PSM_SINGLE_WORD, ///< Treat the image as a single word.
PSM_SINGLE_CHAR, ///< Treat the image as a single character.
PSM_COUNT ///< Number of enum entries.
};
/**
* The values in the AccuracyVSpeed enum provide hints for how the engine
* should trade speed for accuracy. There is no guarantee of any effect.
*/
enum AccuracyVSpeed {
AVS_FASTEST = 0, ///< Fastest speed, but lowest accuracy.
AVS_MOST_ACCURATE = 100 ///< Greatest accuracy, but slowest speed.
};
/**
* Base class for all tesseract APIs.
@ -106,47 +100,66 @@ class TESSDLL_API TessBaseAPI {
void SetOutputName(const char* name);
/**
* Set the value of an internal "variable" (of either old or new types).
* Supply the name of the variable and the value as a string, just as
* Set the value of an internal "parameter."
* Supply the name of the parameter and the value as a string, just as
* you would in a config file.
* Returns false if the name lookup failed.
* Eg SetVariable("tessedit_char_blacklist", "xyz"); to ignore x, y and z.
* Or SetVariable("bln_numericmode", "1"); to set numeric-only mode.
* SetVariable may be used before Init, but settings will revert to
* defaults on End().
* TODO(rays) Add a command-line option to dump the parameters to stdout
* and add a pointer to it in the FAQ
*/
bool SetVariable(const char* variable, const char* value);
bool SetVariable(const char* name, const char* value);
// Same as above, but the parameter is set only if it is one of the "init"
// parameters (defined with *_INIT_* macro).
bool SetVariableIfInit(const char *name, const char *value);
// Returns true if the parameter was found among Tesseract parameters.
// Fills in value with the value of the parameter.
bool GetIntVariable(const char *name, int *value) const;
bool GetBoolVariable(const char *name, bool *value) const;
bool GetDoubleVariable(const char *name, double *value) const;
// Returns the pointer to the string that represents the value of the
// parameter if it was found among Tesseract parameters.
const char *GetStringVariable(const char *name) const;
// Print Tesseract parameters to the given file.
void PrintVariables(FILE *fp) const;
/**
* Eventually instances will be thread-safe and totally independent,
* but for now, they all point to the same underlying engine,
* and are NOT RE-ENTRANT OR THREAD-SAFE. For now:
* it is safe to Init multiple TessBaseAPIs in the same language, use them
* sequentially, and End or delete them all, but once one is Ended, you can't
* do anything other than End the others. After End, it is safe to Init
* again on the same one.
* Instances are now mostly thread-safe and totally independent,
* but some global parameters remain. Basically it is safe to use multiple
* TessBaseAPIs in different threads in parallel, UNLESS:
* you use SetVariable on some of the Params in classify and textord.
* If you do, then the effect will be to change it for all your instances.
*
* Start tesseract. Returns zero on success and -1 on failure.
* NOTE that the only members that may be called before Init are those
* listed above here in the class definition.
*
* The datapath must be the name of the data directory (no ending /) or
* some other file in which the data directory resides (for instance argv[0].)
* The datapath must be the name of the parent directory of tessdata and
* must end in / . Any name after the last / will be stripped.
* The language is (usually) an ISO 639-3 string or NULL will default to eng.
* It is entirely safe (and eventually will be efficient too) to call
* Init multiple times on the same instance to change language, or just
* to reset the classifier.
* WARNING: On changing languages, all Variables are reset back to their
* default values. If you have a rare need to set a Variable that controls
* WARNING: On changing languages, all Tesseract parameters are reset
* back to their default values. (Which may vary between languages.)
* If you have a rare need to set a Variable that controls
* initialization for a second call to Init you should explicitly
* call End() and then use SetVariable before Init. This is only a very
* rare use case, since there are very few uses that require any variables
* rare use case, since there are very few uses that require any parameters
* to be set before Init.
*/
int Init(const char* datapath, const char* language,
char **configs, int configs_size, bool configs_global_only);
int Init(const char* datapath, const char* language, OcrEngineMode mode,
char **configs, int configs_size, bool configs_init_only);
int Init(const char* datapath, const char* language, OcrEngineMode oem) {
return Init(datapath, language, oem, NULL, 0, false);
}
int Init(const char* datapath, const char* language) {
return Init(datapath, language, 0, 0, false);
return Init(datapath, language, OEM_DEFAULT, NULL, 0, false);
}
/**
@ -157,22 +170,24 @@ class TESSDLL_API TessBaseAPI {
*/
int InitLangMod(const char* datapath, const char* language);
/**
* Init everything except the language model. Used to allow initialization for
* the specified language without any available dawg models.
*/
int InitWithoutLangModel(const char* datapath, const char* language);
// Init only for page layout analysis. Use only for calls to SetImage and
// AnalysePage. Calls that attempt recognition will generate an error.
void InitForAnalysePage();
/**
* Read a "config" file containing a set of variable, value pairs.
* Searches the standard places: tessdata/configs, tessdata/tessconfigs
* and also accepts a relative or absolute path name.
* If init_only is true, only sets the parameters marked with a special
* INIT flag, which are typically of functional/algorithmic effect
* rather than debug effect. Used to separate debug settings from
* working settings.
*/
void ReadConfigFile(const char* filename, bool global_only);
void ReadConfigFile(const char* filename, bool init_only);
/**
* Set the current page segmentation mode. Defaults to PSM_SINGLE_BLOCK.
* The mode is stored as an INT_VARIABLE so it can also be modified by
* The mode is stored as an IntParam so it can also be modified by
* ReadConfigFile or SetVariable("tessedit_pageseg_mode", mode as string).
*/
void SetPageSegMode(PageSegMode mode);
@ -180,19 +195,6 @@ class TESSDLL_API TessBaseAPI {
/** Return the current page segmentation mode. */
PageSegMode GetPageSegMode() const;
/**
* Set the hint for trading accuracy against speed.
* Default is AVS_FASTEST, which is the old behaviour.
* Note that this is only a hint. Depending on the language and/or
* build configuration, speed and accuracy may not be tradeable.
* Also note that despite being an enum, any value in the range
* AVS_FASTEST to AVS_MOST_ACCURATE can be provided, and may or may not
* have an effect, depending on the implementation.
* The mode is stored as an INT_VARIABLE so it can also be modified by
* ReadConfigFile or SetVariable("tessedit_accuracyvspeed", mode as string).
*/
void SetAccuracyVSpeed(AccuracyVSpeed mode);
/**
* Recognize a rectangle from an image and return the result as a string.
* May be called many times for a single Init.
@ -267,7 +269,7 @@ class TESSDLL_API TessBaseAPI {
* delete it when it it is replaced or the API is destructed.
*/
void SetThresholder(ImageThresholder* thresholder) {
if (thresholder_ != 0)
if (thresholder_ != NULL)
delete thresholder_;
thresholder_ = thresholder;
ClearResults();
@ -291,8 +293,8 @@ class TESSDLL_API TessBaseAPI {
* Get the textlines as a leptonica-style
* Boxa, Pixa pair, in reading order.
* Can be called before or after Recognize.
* If blockids is not NULL, the block-id of each line is also returned as an
* array of one element per line. delete [] after use.
* If blockids is not NULL, the block-id of each line is also returned
* as an array of one element per line. delete [] after use.
*/
Boxa* GetTextlines(Pixa** pixa, int** blockids);
@ -303,6 +305,22 @@ class TESSDLL_API TessBaseAPI {
*/
Boxa* GetWords(Pixa** pixa);
// Gets the individual connected (text) components (created
// after pages segmentation step, but before recognition)
// as a leptonica-style Boxa, Pixa pair, in reading order.
// Can be called before or after Recognize.
// Note: the caller is responsible for calling boxaDestroy()
// on the returned Boxa array and pixaDestroy() on cc array.
Boxa* GetConnectedComponents(Pixa** cc);
// Get the given level kind of components (block, textline, word etc.) as a
// leptonica-style Boxa, Pixa pair, in reading order.
// Can be called before or after Recognize.
// If blockids is not NULL, the block-id of each component is also returned
// as an array of one element per component. delete [] after use.
Boxa* GetComponentImages(PageIteratorLevel level,
Pixa** pixa, int** blockids);
/**
* Dump the internal binary image to a PGM file.
* @deprecated Use GetThresholdedImage and write the image using pixWrite
@ -310,13 +328,24 @@ class TESSDLL_API TessBaseAPI {
*/
void DumpPGM(const char* filename);
// Runs page layout analysis in the mode set by SetPageSegMode.
// May optionally be called prior to Recognize to get access to just
// the page layout results. Returns an iterator to the results.
// Returns NULL on error.
// The returned iterator must be deleted after use.
// WARNING! This class points to data held within the TessBaseAPI class, and
// therefore can only be used while the TessBaseAPI class still exists and
// has not been subjected to a call of Init, SetImage, Recognize, Clear, End
// DetectOS, or anything else that changes the internal PAGE_RES.
PageIterator* AnalyseLayout();
/**
* Recognize the image from SetAndThresholdImage, generating Tesseract
* internal structures. Returns 0 on success.
* Optional. The Get*Text functions below will call Recognize if needed.
* After Recognize, the output is kept internally until the next SetImage.
*/
int Recognize(ETEXT_STRUCT* monitor);
int Recognize(ETEXT_DESC* monitor);
/**
* Methods to retrieve information after SetAndThresholdImage(),
@ -324,7 +353,15 @@ class TESSDLL_API TessBaseAPI {
*/
/** Variant on Recognize used for testing chopper. */
int RecognizeForChopTest(struct ETEXT_STRUCT* monitor);
int RecognizeForChopTest(ETEXT_DESC* monitor);
// Get an iterator to the results of LayoutAnalysis and/or Recognize.
// The returned iterator must be deleted after use.
// WARNING! This class points to data held within the TessBaseAPI class, and
// therefore can only be used while the TessBaseAPI class still exists and
// has not been subjected to a call of Init, SetImage, Recognize, Clear, End
// DetectOS, or anything else that changes the internal PAGE_RES.
ResultIterator* GetIterator();
/**
* The recognized text is returned as a char* which is coded
@ -334,16 +371,15 @@ class TESSDLL_API TessBaseAPI {
/**
* Make a HTML-formatted string with hOCR markup from the internal
* data structures.
* STL removed from original patch submission and refactored by rays.
* page_id is 1-based and will appear in the output.
* page_number is 0-based but will appear in the output as 1-based.
*/
char* GetHOCRText(int page_id);
char* GetHOCRText(int page_number);
/**
* The recognized text is returned as a char* which is coded in the same
* format as a box file used in training. Returned string must be freed with
* the delete [] operator.
* Constructs coordinates in the original image - not just the rectangle.
* page_number is a 0-base page index that will appear in the box file.
* page_number is a 0-based page index that will appear in the box file.
*/
char* GetBoxText(int page_number);
/**
@ -388,9 +424,14 @@ class TESSDLL_API TessBaseAPI {
bool GetTextDirection(int* out_offset, float* out_slope);
/** Set the letter_is_okay function to point somewhere else. */
/** Sets Dict::letter_is_okay_ function to point to the given function. */
void SetDictFunc(DictFunc f);
/** Sets Dict::probability_in_context_ function to point to the given
* function.
*/
void SetProbabilityInContextFunc(ProbabilityInContextFunc f);
/**
* Estimates the Orientation And Script of the image.
* @return true if the image was processed successfully.
@ -398,8 +439,26 @@ class TESSDLL_API TessBaseAPI {
bool DetectOS(OSResults*);
/** This method returns the features associated with the input image. */
void GetFeatures(INT_FEATURE_ARRAY int_features,
int* num_features);
void GetFeaturesForBlob(TBLOB* blob, const DENORM& denorm,
INT_FEATURE_ARRAY int_features,
int* num_features, int* FeatureOutlineIndex);
// This method returns the row to which a box of specified dimensions would
// belong. If no good match is found, it returns NULL.
static ROW* FindRowForBox(BLOCK_LIST* blocks, int left, int top,
int right, int bottom);
// Method to run adaptive classifier on a blob.
// It returns at max num_max_matches results.
void RunAdaptiveClassifier(TBLOB* blob, const DENORM& denorm,
int num_max_matches,
int* unichar_ids,
char* configs,
float* ratings,
int* num_matches_returned);
// This method returns the string form of the specified unichar.
const char* GetUnichar(int unichar_id);
/** Return the pointer to the i-th dawg loaded into tesseract_ object. */
const Dawg *GetDawg(int i) const;
@ -410,6 +469,42 @@ class TESSDLL_API TessBaseAPI {
/** Return the language used in the last valid initialization. */
const char* GetLastInitLanguage() const;
// Returns a ROW object created from the input row specification.
static ROW *MakeTessOCRRow(float baseline, float xheight,
float descender, float ascender);
// Returns a TBLOB corresponding to the entire input image.
static TBLOB *MakeTBLOB(Pix *pix);
// This method baseline normalizes a TBLOB in-place. The input row is used
// for normalization. The denorm is an optional parameter in which the
// normalization-antidote is returned.
static void NormalizeTBLOB(TBLOB *tblob, ROW *row,
bool numeric_mode, DENORM *denorm);
Tesseract* const tesseract() const {
return tesseract_;
}
// Return a pointer to underlying CubeRecoContext object if present.
CubeRecoContext *GetCubeRecoContext() const;
void set_min_orientation_margin(double margin);
// Return text orientation of each block as determined by an earlier run
// of layout analysis.
void GetBlockTextOrientations(int** block_orientation,
bool** vertical_writing);
/** Find lines from the image making the BLOCK_LIST. */
BLOCK_LIST* FindLinesCreateBlockList();
/**
* Delete a block list.
* This is to keep BLOCK_LIST pointer opaque
* and let go of including the other headers.
*/
static void DeleteBlockList(BLOCK_LIST* block_list);
/* @} */
protected:
@ -441,17 +536,7 @@ class TESSDLL_API TessBaseAPI {
int TextLength(int* blob_count);
/** @defgroup ocropusAddOns ocropus add-ons */
/* @{ */
/** Find lines from the image making the BLOCK_LIST. */
BLOCK_LIST* FindLinesCreateBlockList();
/**
* Delete a block list.
* This is to keep BLOCK_LIST pointer opaque
* and let go of including the other headers.
*/
static void DeleteBlockList(BLOCK_LIST* block_list);
/**
* Adapt to recognize the current image as the given character.
@ -465,9 +550,8 @@ class TESSDLL_API TessBaseAPI {
float ascender);
/** Recognize text doing one pass only, using settings for a given pass. */
/*static*/ PAGE_RES* RecognitionPass1(BLOCK_LIST* block_list);
/*static*/ PAGE_RES* RecognitionPass2(BLOCK_LIST* block_list,
PAGE_RES* pass1_result);
PAGE_RES* RecognitionPass1(BLOCK_LIST* block_list);
PAGE_RES* RecognitionPass2(BLOCK_LIST* block_list, PAGE_RES* pass1_result);
/**
* Extract the OCR results, costs (penalty points for uncertainty),
@ -482,67 +566,25 @@ class TESSDLL_API TessBaseAPI {
int** y1,
PAGE_RES* page_res);
/**
* Call the Cube OCR engine. Takes the Region, line and word segmentation
* information from Tesseract as inputs. Makes changes or populates the
* output PAGE_RES object which contains the recogntion results.
* The behavior of this function depends on the
* current language and the value of the tessedit_accuracyvspeed:
* For English (and other Latin based scripts):
* If the accuracyvspeed flag is set to any value other than AVS_FASTEST,
* Cube uses the word information passed by Tesseract.
* Cube will run on a subset of the words segmented and recognized by
* Tesseract. The value of the accuracyvspeed and the Tesseract
* confidence of a word determines whether Cube runs on it or not and
* whether Cube's results override Tesseract's
* For Arabic & Hindi:
* Cube uses the Region information passed by Tesseract. It then performs
* its own line segmentation. This will change once Tesseract's line
* segmentation works for Arabic. Cube then segments each line into
* phrases. Each phrase is then recognized in phrase mode which allows
* spaces in the results.
* Note that at this point, the line segmentation algorithm might have
* some problems with ill spaced Arabic document.
*/
int Cube();
/** Run Cube on the lines extracted by Tesseract. */
int RunCubeOnLines();
/**
* Run Cube on a subset of the words already present in the page_res_ object
* The subset, and whether Cube overrides the results is determined by
* the SpeedVsAccuracy flag
*/
int CubePostProcessWords();
/** Create a Cube line object for each line */
CubeLineObject **CreateLineObjects(Pixa* pixa_lines);
/**
* Create a TBox array corresponding to the phrases in the array of
* line objects
*/
TBOX *CreatePhraseBoxes(Boxa* boxa_lines, CubeLineObject **line_objs,
int *phrase_cnt);
/** Recognize the phrases saving the results to the page_res_ object */
bool RecognizePhrases(int line_cnt, int phrase_cnt,
CubeLineObject **line_objs, TBOX *phrase_boxes);
/** Recognize a single phrase saving the results to the page_res_ object */
bool RecognizePhrase(CubeObject *phrase, PAGE_RES_IT *result);
/** Create the necessary Cube Objects */
bool CreateCubeObjects();
/* @} */
const PAGE_RES* GetPageRes() const {
return page_res_;
};
protected:
Tesseract* tesseract_; ///< The underlying data object.
ImageThresholder* thresholder_; ///< Image thresholding module.
bool threshold_done_; ///< Image has been passed to page_image.
BLOCK_LIST* block_list_; ///< The page layout.
PAGE_RES* page_res_; ///< The page-level data.
STRING* input_file_; ///< Name used by training code.
STRING* output_file_; ///< Name used by debug code.
STRING* datapath_; ///< Current location of tessdata.
STRING* language_; ///< Last initialized language.
Tesseract* tesseract_; ///< The underlying data object.
Tesseract* osd_tesseract_; ///< For orientation & script detection.
ImageThresholder* thresholder_; ///< Image thresholding module.
BLOCK_LIST* block_list_; ///< The page layout.
PAGE_RES* page_res_; ///< The page-level data.
STRING* input_file_; ///< Name used by training code.
STRING* output_file_; ///< Name used by debug code.
STRING* datapath_; ///< Current location of tessdata.
STRING* language_; ///< Last initialized language.
OcrEngineMode last_oem_requested_; ///< Last ocr language mode requested.
bool recognition_done_; ///< page_res_ contains recognition data.
/**
* @defgroup ThresholderParams
/**
* @defgroup ThresholderParams
* Parameters saved from the Thresholder. Needed to rebuild coordinates.
*/
/* @{ */
@ -555,6 +597,6 @@ class TESSDLL_API TessBaseAPI {
/* @} */
};
} // namespace tesseract.
} // namespace tesseract.
#endif // TESSERACT_CCMAIN_BASEAPI_H__
#endif // TESSERACT_API_BASEAPI_H__

388
api/pageiterator.cpp Normal file
View File

@ -0,0 +1,388 @@
///////////////////////////////////////////////////////////////////////
// File: pageiterator.cpp
// Description: Iterator for tesseract page structure that avoids using
// tesseract internal data structures.
// Author: Ray Smith
// Created: Fri Feb 26 14:32:09 PST 2010
//
// (C) Copyright 2010, Google Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////
#include "pageiterator.h"
#include "allheaders.h"
#include "helpers.h"
#include "pageres.h"
#include "tesseractclass.h"
namespace tesseract {
PageIterator::PageIterator(PAGE_RES* page_res, Tesseract* tesseract,
int scale, int scaled_yres,
int rect_left, int rect_top,
int rect_width, int rect_height)
: page_res_(page_res), tesseract_(tesseract),
word_(NULL), word_length_(0), blob_index_(0), cblob_it_(NULL),
scale_(scale), scaled_yres_(scaled_yres),
rect_left_(rect_left), rect_top_(rect_top),
rect_width_(rect_width), rect_height_(rect_height) {
it_ = new PAGE_RES_IT(page_res);
Begin();
}
PageIterator::~PageIterator() {
delete it_;
delete cblob_it_;
}
// PageIterators may be copied! This makes it possible to iterate over
// all the objects at a lower level, while maintaining an iterator to
// objects at a higher level.
PageIterator::PageIterator(const PageIterator& src)
: page_res_(src.page_res_), tesseract_(src.tesseract_),
word_(NULL), word_length_(src.word_length_),
blob_index_(src.blob_index_), cblob_it_(NULL),
scale_(src.scale_), scaled_yres_(src.scaled_yres_),
rect_left_(src.rect_left_), rect_top_(src.rect_top_),
rect_width_(src.rect_width_), rect_height_(src.rect_height_) {
it_ = new PAGE_RES_IT(*src.it_);
BeginWord(src.blob_index_);
}
const PageIterator& PageIterator::operator=(const PageIterator& src) {
page_res_ = src.page_res_;
tesseract_ = src.tesseract_;
scale_ = src.scale_;
scaled_yres_ = src.scaled_yres_;
rect_left_ = src.rect_left_;
rect_top_ = src.rect_top_;
rect_width_ = src.rect_width_;
rect_height_ = src.rect_height_;
if (it_ != NULL) delete it_;
it_ = new PAGE_RES_IT(*src.it_);
BeginWord(src.blob_index_);
return *this;
}
// ============= Moving around within the page ============.
// Resets the iterator to point to the start of the page.
void PageIterator::Begin() {
it_->restart_page_with_empties();
BeginWord(0);
}
// Moves to the start of the next object at the given level in the
// page hierarchy, and returns false if the end of the page was reached.
// NOTE that RIL_SYMBOL will skip non-text blocks, but all other
// PageIteratorLevel level values will visit each non-text block once.
// Think of non text blocks as containing a single para, with a single line,
// with a single imaginary word.
// Calls to Next with different levels may be freely intermixed.
// This function iterates words in right-to-left scripts correctly, if
// the appropriate language has been loaded into Tesseract.
bool PageIterator::Next(PageIteratorLevel level) {
if (it_->block() == NULL) return false; // Already at the end!
if (it_->word() == NULL)
level = RIL_BLOCK;
switch (level) {
case RIL_BLOCK:
case RIL_PARA:
it_->forward_block();
break;
case RIL_TEXTLINE:
for (it_->forward_with_empties(); it_->row() == it_->prev_row();
it_->forward_with_empties());
break;
case RIL_WORD:
it_->forward_with_empties();
break;
case RIL_SYMBOL:
if (cblob_it_ != NULL)
cblob_it_->forward();
++blob_index_;
if (blob_index_ >= word_length_)
it_->forward();
else
return true;
break;
}
BeginWord(0);
return it_->block() != NULL;
}
// Returns true if the iterator is at the start of an object at the given
// level. Possible uses include determining if a call to Next(RIL_WORD)
// moved to the start of a RIL_PARA.
bool PageIterator::IsAtBeginningOf(PageIteratorLevel level) const {
if (it_->block() == NULL) return false; // Already at the end!
if (it_->word() == NULL) return true; // In an image block.
switch (level) {
case RIL_BLOCK:
case RIL_PARA:
return it_->block() != it_->prev_block();
case RIL_TEXTLINE:
return it_->row() != it_->prev_row();
case RIL_WORD:
return blob_index_ == 0;
case RIL_SYMBOL:
return true;
}
return false;
}
// Returns whether the iterator is positioned at the last element in a
// given level. (e.g. the last word in a line, the last line in a block)
bool PageIterator::IsAtFinalElement(PageIteratorLevel level,
PageIteratorLevel element) const {
if (it_->word() == NULL) return true; // Already at the end!
// The result is true if we step forward by element and find we are
// at the the end of the page or at beginning of *all* levels in:
// [level, element).
// When there is more than one level difference between element and level,
// we could for instance move forward one symbol and still be at the first
// word on a line, so we also have to be at the first symbol in a word.
PageIterator next(*this);
next.Next(element);
if (next.it_->word() == NULL) return true; // Reached the end of the page.
while (element > level) {
element = static_cast<PageIteratorLevel>(element - 1);
if (!next.IsAtBeginningOf(element))
return false;
}
return true;
}
// ============= Accessing data ==============.
// Coordinate system:
// Integer coordinates are at the cracks between the pixels.
// The top-left corner of the top-left pixel in the image is at (0,0).
// The bottom-right corner of the bottom-right pixel in the image is at
// (width, height).
// Every bounding box goes from the top-left of the top-left contained
// pixel to the bottom-right of the bottom-right contained pixel, so
// the bounding box of the single top-left pixel in the image is:
// (0,0)->(1,1).
// If an image rectangle has been set in the API, then returned coordinates
// relate to the original (full) image, rather than the rectangle.
// Returns the bounding rectangle of the current object at the given level.
// See comment on coordinate system above.
// Returns false if there is no such object at the current position.
bool PageIterator::BoundingBox(PageIteratorLevel level,
int* left, int* top,
int* right, int* bottom) const {
if (it_->block() == NULL) return false; // Already at the end!
if (it_->word() == NULL && level != RIL_BLOCK) return false;
if (level == RIL_SYMBOL && blob_index_ >= word_length_)
return false; // Zero length word, or already at the end of it.
TBOX box;
switch (level) {
case RIL_BLOCK:
case RIL_PARA:
box = it_->block()->block->bounding_box();
break;
case RIL_TEXTLINE:
box = it_->row()->row->bounding_box();
break;
case RIL_WORD:
box = it_->word()->word->bounding_box();
break;
case RIL_SYMBOL:
if (cblob_it_ == NULL)
box = it_->word()->box_word->BlobBox(blob_index_);
else
box = cblob_it_->data()->bounding_box();
// Intersect with the word box.
const TBOX& word_box = it_->word()->word->bounding_box();
if (box.overlap(word_box))
box -= word_box;
else
box = word_box;
}
if (level != RIL_SYMBOL || cblob_it_ != NULL)
box.rotate(it_->block()->block->re_rotation());
// Now we have a box in tesseract coordinates relative to the image rectangle,
// we have to convert the coords to global page coords in a top-down system.
*left = ClipToRange(box.left() / scale_ + rect_left_,
rect_left_, rect_left_ + rect_width_);
*top = ClipToRange((rect_height_ - box.top()) / scale_ + rect_top_,
rect_top_, rect_top_ + rect_height_);
*right = ClipToRange((box.right() + scale_ - 1) / scale_ + rect_left_,
*left, rect_left_ + rect_width_);
*bottom = ClipToRange((rect_height_ - box.bottom() + scale_ - 1) / scale_
+ rect_top_,
*top, rect_top_ + rect_height_);
return true;
}
// Returns the type of the current block. See apitypes.h for PolyBlockType.
PolyBlockType PageIterator::BlockType() const {
if (it_->block() == NULL || it_->block()->block == NULL)
return PT_UNKNOWN; // Already at the end!
if (it_->block()->block->poly_block() == NULL)
return PT_FLOWING_TEXT; // No layout analysis used - assume text.
return it_->block()->block->poly_block()->isA();
}
// Returns a binary image of the current object at the given level.
// The position and size match the return from BoundingBox.
// Use pixDestroy to delete the image after use.
// The following methods are used to generate the images:
// RIL_BLOCK: mask the page image with the block polygon.
// RIL_TEXTLINE: Clip the rectangle of the line box from the page image.
// TODO(rays) fix this to generate and use a line polygon.
// RIL_WORD: Clip the rectangle of the word box from the page image.
// RIL_SYMBOL: Render the symbol outline to an image for cblobs (prior
// to recognition) or the bounding box otherwise.
// A reconstruction of the original image (using xor to check for double
// representation) should be reasonably accurate,
// apart from removed noise, at the block level. Below the block level, the
// reconstruction will be missing images and line separators.
// At the symbol level, kerned characters will be invade the bounding box
// if rendered after recognition, making an xor reconstruction inaccurate, but
// an or construction better. Before recognition, symbol-level reconstruction
// should be good, even with xor, since the images come from the connected
// components.
Pix* PageIterator::GetBinaryImage(PageIteratorLevel level) const {
int left, top, right, bottom;
if (!BoundingBox(level, &left, &top, &right, &bottom))
return NULL;
Pix* pix = NULL;
switch (level) {
case RIL_BLOCK:
case RIL_PARA:
pix = it_->block()->block->render_mask();
// AND the mask and the image.
pixRasterop(pix, 0, 0, pixGetWidth(pix), pixGetHeight(pix),
PIX_SRC & PIX_DST, tesseract_->pix_binary(),
left, top);
break;
case RIL_TEXTLINE:
case RIL_WORD:
case RIL_SYMBOL:
if (level == RIL_SYMBOL && cblob_it_ != NULL)
return cblob_it_->data()->render();
// Just clip from the bounding box.
Box* box = boxCreate(left, top, right - left, bottom - top);
pix = pixClipRectangle(tesseract_->pix_binary(), box, NULL);
boxDestroy(&box);
break;
}
return pix;
}
// Returns an image of the current object at the given level in greyscale
// if available in the input. To guarantee a binary image use BinaryImage.
// NOTE that in order to give the best possible image, the bounds are
// expanded slightly over the binary connected component, by the supplied
// padding, so the top-left position of the returned image is returned
// in (left,top). These will most likely not match the coordinates
// returned by BoundingBox.
// Use pixDestroy to delete the image after use.
Pix* PageIterator::GetImage(PageIteratorLevel level, int padding,
int* left, int* top) const {
int right, bottom;
if (!BoundingBox(level, left, top, &right, &bottom))
return NULL;
Pix* pix = tesseract_->pix_grey();
if (pix == NULL)
return GetBinaryImage(level);
// Expand the box.
*left = MAX(*left - padding, 0);
*top = MAX(*top - padding, 0);
right = MIN(right + padding, rect_width_);
bottom = MIN(bottom + padding, rect_height_);
Box* box = boxCreate(*left, *top, right - *left, bottom - *top);
Pix* grey_pix = pixClipRectangle(pix, box, NULL);
boxDestroy(&box);
if (level == RIL_BLOCK || level == RIL_PARA) {
Pix* mask = it_->block()->block->render_mask();
Pix* expanded_mask = pixCreate(right - *left, bottom - *top, 1);
pixRasterop(expanded_mask, padding, padding,
pixGetWidth(mask), pixGetHeight(mask),
PIX_SRC, mask, 0, 0);
pixDestroy(&mask);
pixDilateBrick(expanded_mask, expanded_mask, 2*padding + 1, 2*padding + 1);
pixInvert(expanded_mask, expanded_mask);
pixSetMasked(grey_pix, expanded_mask, 255);
pixDestroy(&expanded_mask);
}
return grey_pix;
}
// Returns the baseline of the current object at the given level.
// The baseline is the line that passes through (x1, y1) and (x2, y2).
// WARNING: with vertical text, baselines may be vertical!
bool PageIterator::Baseline(PageIteratorLevel level,
int* x1, int* y1, int* x2, int* y2) const {
if (it_->word() == NULL) return false; // Already at the end!
ROW* row = it_->row()->row;
WERD* word = it_->word()->word;
TBOX box = (level == RIL_WORD || level == RIL_SYMBOL)
? word->bounding_box()
: row->bounding_box();
int left = box.left();
ICOORD startpt(left, static_cast<inT16>(row->base_line(left) + 0.5));
int right = box.right();
ICOORD endpt(right, static_cast<inT16>(row->base_line(right) + 0.5));
// Rotate to image coordinates and convert to global image coords.
startpt.rotate(it_->block()->block->re_rotation());
endpt.rotate(it_->block()->block->re_rotation());
*x1 = startpt.x() / scale_ + rect_left_;
*y1 = (rect_height_ - startpt.y()) / scale_ + rect_top_;
*x2 = endpt.x() / scale_ + rect_left_;
*y2 = (rect_height_ - endpt.y()) / scale_ + rect_top_;
return true;
}
// Sets up the internal data for iterating the blobs of a new word, then
// moves the iterator to the given offset.
void PageIterator::BeginWord(int offset) {
WERD_RES* word_res = it_->word();
if (word_res == NULL) {
// This is a non-text block, so there is no word.
word_length_ = 0;
blob_index_ = 0;
word_ = NULL;
return;
}
if (word_res->best_choice != NULL) {
// Recognition has been done, so we are using the box_word, which
// is already baseline denormalized.
word_length_ = word_res->best_choice->length();
ASSERT_HOST(word_res->box_word != NULL);
ASSERT_HOST(word_res->box_word->length() == word_length_);
word_ = NULL;
// We will be iterating the box_word.
if (cblob_it_ != NULL) {
delete cblob_it_;
cblob_it_ = NULL;
}
} else {
// No recognition yet, so a "symbol" is a cblob.
word_ = word_res->word;
ASSERT_HOST(word_->cblob_list() != NULL);
word_length_ = word_->cblob_list()->length();
if (cblob_it_ == NULL) cblob_it_ = new C_BLOB_IT;
cblob_it_->set_to_list(word_->cblob_list());
}
for (blob_index_ = 0; blob_index_ < offset; ++blob_index_) {
if (cblob_it_ != NULL)
cblob_it_->forward();
}
}
} // namespace tesseract.

184
api/pageiterator.h Normal file
View File

@ -0,0 +1,184 @@
///////////////////////////////////////////////////////////////////////
// File: pageiterator.h
// Description: Iterator for tesseract page structure that avoids using
// tesseract internal data structures.
// Author: Ray Smith
// Created: Fri Feb 26 11:01:06 PST 2010
//
// (C) Copyright 2010, Google Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////
#ifndef TESSERACT_API_PAGEITERATOR_H__
#define TESSERACT_API_PAGEITERATOR_H__
#include "apitypes.h"
class C_BLOB_IT;
class PBLOB_IT;
class PAGE_RES;
class PAGE_RES_IT;
class WERD;
struct Pix;
namespace tesseract {
class Tesseract;
// Class to iterate over tesseract page structure, providing access to all
// levels of the page hierarchy, without including any tesseract headers or
// having to handle any tesseract structures.
// WARNING! This class points to data held within the TessBaseAPI class, and
// therefore can only be used while the TessBaseAPI class still exists and
// has not been subjected to a call of Init, SetImage, Recognize, Clear, End
// DetectOS, or anything else that changes the internal PAGE_RES.
// See apitypes.h for the definition of PageIteratorLevel.
// See also ResultIterator, derived from PageIterator, which adds in the
// ability to access OCR output with text-specific methods.
class PageIterator {
public:
// page_res and tesseract come directly from the BaseAPI.
// The rectangle parameters are copied indirectly from the Thresholder,
// via the BaseAPI. They represent the coordinates of some rectangle in an
// original image (in top-left-origin coordinates) and therefore the top-left
// needs to be added to any output boxes in order to specify coordinates
// in the original image. See TessBaseAPI::SetRectangle.
// The scale and scaled_yres are in case the Thresholder scaled the image
// rectangle prior to thresholding. Any coordinates in tesseract's image
// must be divided by scale before adding (rect_left, rect_top).
// The scaled_yres indicates the effective resolution of the binary image
// that tesseract has been given by the Thresholder.
// After the constructor, Begin has already been called.
PageIterator(PAGE_RES* page_res, Tesseract* tesseract,
int scale, int scaled_yres,
int rect_left, int rect_top,
int rect_width, int rect_height);
virtual ~PageIterator();
// Page/ResultIterators may be copied! This makes it possible to iterate over
// all the objects at a lower level, while maintaining an iterator to
// objects at a higher level. These constructors DO NOT CALL Begin, so
// iterations will continue from the location of src.
PageIterator(const PageIterator& src);
const PageIterator& operator=(const PageIterator& src);
// ============= Moving around within the page ============.
// Moves the iterator to point to the start of the page to begin an iteration.
void Begin();
// Moves to the start of the next object at the given level in the
// page hierarchy, and returns false if the end of the page was reached.
// NOTE that RIL_SYMBOL will skip non-text blocks, but all other
// PageIteratorLevel level values will visit each non-text block once.
// Think of non text blocks as containing a single para, with a single line,
// with a single imaginary word.
// Calls to Next with different levels may be freely intermixed.
// This function iterates words in right-to-left scripts correctly, if
// the appropriate language has been loaded into Tesseract.
bool Next(PageIteratorLevel level);
// Returns true if the iterator is at the start of an object at the given
// level. Possible uses include determining if a call to Next(RIL_WORD)
// moved to the start of a RIL_PARA.
bool IsAtBeginningOf(PageIteratorLevel level) const;
// Returns whether the iterator is positioned at the last element in a
// given level. (e.g. the last word in a line, the last line in a block)
bool IsAtFinalElement(PageIteratorLevel level,
PageIteratorLevel element) const;
// ============= Accessing data ==============.
// Coordinate system:
// Integer coordinates are at the cracks between the pixels.
// The top-left corner of the top-left pixel in the image is at (0,0).
// The bottom-right corner of the bottom-right pixel in the image is at
// (width, height).
// Every bounding box goes from the top-left of the top-left contained
// pixel to the bottom-right of the bottom-right contained pixel, so
// the bounding box of the single top-left pixel in the image is:
// (0,0)->(1,1).
// If an image rectangle has been set in the API, then returned coordinates
// relate to the original (full) image, rather than the rectangle.
// Returns the bounding rectangle of the current object at the given level.
// See comment on coordinate system above.
// Returns false if there is no such object at the current position.
// The returned bounding box is guaranteed to match the size and position
// of the image returned by GetBinaryImage, but may clip foreground pixels
// from a grey image. The padding argument to GetImage can be used to expand
// the image to include more foreground pixels. See GetImage below.
bool BoundingBox(PageIteratorLevel level,
int* left, int* top, int* right, int* bottom) const;
// Returns the type of the current block. See apitypes.h for PolyBlockType.
PolyBlockType BlockType() const;
// Returns a binary image of the current object at the given level.
// The position and size match the return from BoundingBox.
// Use pixDestroy to delete the image after use.
Pix* GetBinaryImage(PageIteratorLevel level) const;
// Returns an image of the current object at the given level in greyscale
// if available in the input. To guarantee a binary image use BinaryImage.
// NOTE that in order to give the best possible image, the bounds are
// expanded slightly over the binary connected component, by the supplied
// padding, so the top-left position of the returned image is returned
// in (left,top). These will most likely not match the coordinates
// returned by BoundingBox.
// Use pixDestroy to delete the image after use.
Pix* GetImage(PageIteratorLevel level, int padding,
int* left, int* top) const;
// Returns the baseline of the current object at the given level.
// The baseline is the line that passes through (x1, y1) and (x2, y2).
// WARNING: with vertical text, baselines may be vertical!
// Returns false if there is no baseline at the current position.
bool Baseline(PageIteratorLevel level,
int* x1, int* y1, int* x2, int* y2) const;
protected:
// Sets up the internal data for iterating the blobs of a new word, then
// moves the iterator to the given offset.
void BeginWord(int offset);
// Pointer to the page_res owned by the API.
PAGE_RES* page_res_;
// Pointer to the Tesseract object owned by the API.
Tesseract* tesseract_;
// The iterator to the page_res_. Owned by this ResultIterator.
// A pointer just to avoid dragging in Tesseract includes.
PAGE_RES_IT* it_;
// The current input WERD being iterated. If there is an output from OCR,
// then word_ is NULL. Owned by the API.
WERD* word_;
// The length of the current word_.
int word_length_;
// The current blob index within the word.
int blob_index_;
// Iterator to the blobs within the word. If NULL, then we are iterating
// OCR results in the box_word.
// Owned by this ResultIterator.
C_BLOB_IT* cblob_it_;
// Parameters saved from the Thresholder. Needed to rebuild coordinates.
int scale_;
int scaled_yres_;
int rect_left_;
int rect_top_;
int rect_width_;
int rect_height_;
};
} // namespace tesseract.
#endif // TESSERACT_API_PAGEITERATOR_H__

249
api/resultiterator.cpp Normal file
View File

@ -0,0 +1,249 @@
///////////////////////////////////////////////////////////////////////
// File: resultiterator.cpp
// Description: Iterator for tesseract results that avoids using tesseract
// internal data structures
// Author: Ray Smith
// Created: Fri Feb 26 14:32:09 PST 2010
//
// (C) Copyright 2010, Google Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////
#include "resultiterator.h"
#include "allheaders.h"
#include "pageres.h"
#include "tesseractclass.h"
namespace tesseract {
ResultIterator::ResultIterator(PAGE_RES* page_res, Tesseract* tesseract,
int scale, int scaled_yres,
int rect_left, int rect_top,
int rect_width, int rect_height)
: PageIterator(page_res, tesseract, scale, scaled_yres,
rect_left, rect_top, rect_width, rect_height) {
}
ResultIterator::~ResultIterator() {
}
// Returns the null terminated UTF-8 encoded text string for the current
// object at the given level. Use delete [] to free after use.
char* ResultIterator::GetUTF8Text(PageIteratorLevel level) const {
if (it_->word() == NULL) return NULL; // Already at the end!
STRING text;
PAGE_RES_IT res_it(*it_);
WERD_CHOICE* best_choice = res_it.word()->best_choice;
ASSERT_HOST(best_choice != NULL);
switch (level) {
case RIL_BLOCK:
case RIL_PARA:
do {
best_choice = res_it.word()->best_choice;
ASSERT_HOST(best_choice != NULL);
text += best_choice->unichar_string();
text += res_it.word()->word->flag(W_EOL) ? "\n" : " ";
res_it.forward();
} while (res_it.block() == res_it.prev_block());
break;
case RIL_TEXTLINE:
do {
best_choice = res_it.word()->best_choice;
ASSERT_HOST(best_choice != NULL);
text += best_choice->unichar_string();
text += res_it.word()->word->flag(W_EOL) ? "\n" : " ";
res_it.forward();
} while (res_it.row() == res_it.prev_row());
break;
case RIL_WORD:
text = best_choice->unichar_string();
break;
case RIL_SYMBOL:
text = tesseract_->unicharset.id_to_unichar(
best_choice->unichar_id(blob_index_));
}
int length = text.length() + 1;
char* result = new char[length];
strncpy(result, text.string(), length);
return result;
}
// Returns the mean confidence of the current object at the given level.
// The number should be interpreted as a percent probability. (0.0f-100.0f)
float ResultIterator::Confidence(PageIteratorLevel level) const {
if (it_->word() == NULL) return 0.0f; // Already at the end!
float mean_certainty = 0.0f;
int certainty_count = 0;
PAGE_RES_IT res_it(*it_);
WERD_CHOICE* best_choice = res_it.word()->best_choice;
ASSERT_HOST(best_choice != NULL);
switch (level) {
case RIL_BLOCK:
case RIL_PARA:
do {
best_choice = res_it.word()->best_choice;
ASSERT_HOST(best_choice != NULL);
mean_certainty += best_choice->certainty();
++certainty_count;
res_it.forward();
} while (res_it.block() == res_it.prev_block());
break;
case RIL_TEXTLINE:
do {
best_choice = res_it.word()->best_choice;
ASSERT_HOST(best_choice != NULL);
mean_certainty += best_choice->certainty();
++certainty_count;
res_it.forward();
} while (res_it.row() == res_it.prev_row());
break;
case RIL_WORD:
mean_certainty += best_choice->certainty();
++certainty_count;
break;
case RIL_SYMBOL:
BLOB_CHOICE_LIST_CLIST* choices = best_choice->blob_choices();
if (choices != NULL) {
BLOB_CHOICE_LIST_C_IT blob_choices_it(choices);
for (int blob = 0; blob < blob_index_; ++blob)
blob_choices_it.forward();
BLOB_CHOICE_IT choice_it(blob_choices_it.data());
for (choice_it.mark_cycle_pt();
!choice_it.cycled_list();
choice_it.forward()) {
if (choice_it.data()->unichar_id() ==
best_choice->unichar_id(blob_index_))
break;
}
mean_certainty += choice_it.data()->certainty();
} else {
mean_certainty += best_choice->certainty();
}
++certainty_count;
}
if (certainty_count > 0) {
mean_certainty /= certainty_count;
float confidence = 100 + 5 * mean_certainty;
if (confidence < 0.0f) confidence = 0.0f;
if (confidence > 100.0f) confidence = 100.0f;
return confidence;
}
return 0.0f;
}
// Returns the font attributes of the current word. If iterating at a higher
// level object than words, eg textlines, then this will return the
// attributes of the first word in that textline.
// The actual return value is a string representing a font name. It points
// to an internal table and SHOULD NOT BE DELETED. Lifespan is the same as
// the iterator itself, ie rendered invalid by various members of
// TessBaseAPI, including Init, SetImage, End or deleting the TessBaseAPI.
// Pointsize is returned in printers points (1/72 inch.)
const char* ResultIterator::WordFontAttributes(bool* is_bold,
bool* is_italic,
bool* is_underlined,
bool* is_monospace,
bool* is_serif,
int* pointsize,
int* font_id) const {
if (it_->word() == NULL) return NULL; // Already at the end!
*font_id = it_->word()->font1;
if (*font_id < 0) return NULL; // No font available.
const UnicityTable<FontInfo> &font_table = tesseract_->get_fontinfo_table();
FontInfo font_info = font_table.get(*font_id);
*is_bold = font_info.is_bold();
*is_italic = font_info.is_italic();
*is_underlined = false; // TODO(rays) fix this!
*is_monospace = font_info.is_fixed_pitch();
*is_serif = font_info.is_serif();
// The font size is calculated from a multiple of the x-height
// that came from the block.
float row_height = it_->row()->row->x_height() *
it_->block()->block->cell_over_xheight();
// Convert from pixels to printers points.
*pointsize = scaled_yres_ > 0
? static_cast<int>(row_height * kPointsPerInch / scaled_yres_ + 0.5)
: 0;
return font_info.name;
}
// Returns true if the current word was found in a dictionary.
bool ResultIterator::WordIsFromDictionary() const {
if (it_->word() == NULL) return false; // Already at the end!
int permuter = it_->word()->best_choice->permuter();
return permuter == SYSTEM_DAWG_PERM || permuter == FREQ_DAWG_PERM ||
permuter == USER_DAWG_PERM;
}
// Returns true if the current word is numeric.
bool ResultIterator::WordIsNumeric() const {
if (it_->word() == NULL) return false; // Already at the end!
int permuter = it_->word()->best_choice->permuter();
return permuter == NUMBER_PERM;
}
ChoiceIterator::ChoiceIterator(const ResultIterator& result_it) {
ASSERT_HOST(result_it.it_->word() != NULL);
tesseract_ = result_it.tesseract_;
PAGE_RES_IT res_it(*result_it.it_);
WERD_CHOICE* best_choice = res_it.word()->best_choice;
BLOB_CHOICE_LIST_CLIST* choices = best_choice->blob_choices();
if (choices != NULL) {
BLOB_CHOICE_LIST_C_IT blob_choices_it(choices);
for (int blob = 0; blob < result_it.blob_index_; ++blob)
blob_choices_it.forward();
choice_it_ = new BLOB_CHOICE_IT(blob_choices_it.data());
choice_it_->mark_cycle_pt();
} else {
choice_it_ = NULL;
}
}
ChoiceIterator::~ChoiceIterator() {
delete choice_it_;
}
// Moves to the next choice for the symbol and returns false if there
// are none left.
bool ChoiceIterator::Next() {
if (choice_it_ == NULL)
return false;
choice_it_->forward();
return !choice_it_->cycled_list();
}
// Returns the null terminated UTF-8 encoded text string for the current
// choice. Use delete [] to free after use.
const char* ChoiceIterator::GetUTF8Text() const {
if (choice_it_ == NULL)
return NULL;
UNICHAR_ID id = choice_it_->data()->unichar_id();
if (id < 0 || id >= tesseract_->unicharset.size() ||
id == INVALID_UNICHAR_ID)
return NULL;
return tesseract_->unicharset.id_to_unichar(id);
}
// Returns the confidence of the current choice.
// The number should be interpreted as a percent probability. (0.0f-100.0f)
float ChoiceIterator::Confidence() const {
if (choice_it_ == NULL)
return 0.0f;
float confidence = 100 + 5 * choice_it_->data()->certainty();
if (confidence < 0.0f) confidence = 0.0f;
if (confidence > 100.0f) confidence = 100.0f;
return confidence;
}
} // namespace tesseract.

144
api/resultiterator.h Normal file
View File

@ -0,0 +1,144 @@
///////////////////////////////////////////////////////////////////////
// File: resultiterator.h
// Description: Iterator for tesseract results that avoids using tesseract
// internal data structures.
// Author: Ray Smith
// Created: Fri Feb 26 11:01:06 PST 2010
//
// (C) Copyright 2010, Google Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////
#ifndef TESSERACT_API_RESULTITERATOR_H__
#define TESSERACT_API_RESULTITERATOR_H__
#include "pageiterator.h"
class BLOB_CHOICE_IT;
namespace tesseract {
class Tesseract;
// Class to iterate over tesseract results, providing access to all levels
// of the page hierarchy, without including any tesseract headers or having
// to handle any tesseract structures.
// WARNING! This class points to data held within the TessBaseAPI class, and
// therefore can only be used while the TessBaseAPI class still exists and
// has not been subjected to a call of Init, SetImage, Recognize, Clear, End
// DetectOS, or anything else that changes the internal PAGE_RES.
// See apitypes.h for the definition of PageIteratorLevel.
// See also base class PageIterator, which contains the bulk of the interface.
// ResultIterator adds text-specific methods for access to OCR output.
class ResultIterator : public PageIterator {
friend class ChoiceIterator;
public:
// page_res and tesseract come directly from the BaseAPI.
// The rectangle parameters are copied indirectly from the Thresholder,
// via the BaseAPI. They represent the coordinates of some rectangle in an
// original image (in top-left-origin coordinates) and therefore the top-left
// needs to be added to any output boxes in order to specify coordinates
// in the original image. See TessBaseAPI::SetRectangle.
// The scale and scaled_yres are in case the Thresholder scaled the image
// rectangle prior to thresholding. Any coordinates in tesseract's image
// must be divided by scale before adding (rect_left, rect_top).
// The scaled_yres indicates the effective resolution of the binary image
// that tesseract has been given by the Thresholder.
// After the constructor, Begin has already been called.
ResultIterator(PAGE_RES* page_res, Tesseract* tesseract,
int scale, int scaled_yres,
int rect_left, int rect_top,
int rect_width, int rect_height);
virtual ~ResultIterator();
// ResultIterators may be copied! This makes it possible to iterate over
// all the objects at a lower level, while maintaining an iterator to
// objects at a higher level. These constructors DO NOT CALL Begin, so
// iterations will continue from the location of src.
// TODO: For now the copy constructor and operator= only need the base class
// versions, but if new data members are added, don't forget to add them!
// ============= Moving around within the page ============.
// See PageIterator.
// ============= Accessing data ==============.
// Returns the null terminated UTF-8 encoded text string for the current
// object at the given level. Use delete [] to free after use.
char* GetUTF8Text(PageIteratorLevel level) const;
// Returns the mean confidence of the current object at the given level.
// The number should be interpreted as a percent probability. (0.0f-100.0f)
float Confidence(PageIteratorLevel level) const;
// ============= Functions that refer to words only ============.
// Returns the font attributes of the current word. If iterating at a higher
// level object than words, eg textlines, then this will return the
// attributes of the first word in that textline.
// The actual return value is a string representing a font name. It points
// to an internal table and SHOULD NOT BE DELETED. Lifespan is the same as
// the iterator itself, ie rendered invalid by various members of
// TessBaseAPI, including Init, SetImage, End or deleting the TessBaseAPI.
// Pointsize is returned in printers points (1/72 inch.)
const char* WordFontAttributes(bool* is_bold,
bool* is_italic,
bool* is_underlined,
bool* is_monospace,
bool* is_serif,
int* pointsize,
int* font_id) const;
// Returns true if the current word was found in a dictionary.
bool WordIsFromDictionary() const;
// Returns true if the current word is numeric.
bool WordIsNumeric() const;
};
// Class to iterate over the classifier choices for a single RIL_SYMBOL.
class ChoiceIterator {
public:
// Construction is from a ResultIterator that points to the symbol of
// interest. The ChoiceIterator allows a one-shot iteration over the
// choices for this symbol and after that is is useless.
explicit ChoiceIterator(const ResultIterator& result_it);
~ChoiceIterator();
// Moves to the next choice for the symbol and returns false if there
// are none left.
bool Next();
// ============= Accessing data ==============.
// Returns the null terminated UTF-8 encoded text string for the current
// choice.
// NOTE: Unlike ResultIterator::GetUTF8Text, the return points to an
// internal structure and should NOT be delete[]ed to free after use.
const char* GetUTF8Text() const;
// Returns the confidence of the current choice.
// The number should be interpreted as a percent probability. (0.0f-100.0f)
float Confidence() const;
private:
// Pointer to the Tesseract object owned by the API.
Tesseract* tesseract_;
// Iterator over the blob choices.
BLOB_CHOICE_IT* choice_it_;
};
} // namespace tesseract.
#endif // TESSERACT_API_RESULT_ITERATOR_H__

View File

@ -1,21 +1,21 @@
/**********************************************************************
* File: tessedit.cpp (Formerly tessedit.c)
* Description: Main program for merge of tess and editor.
* Author: Ray Smith
* Created: Tue Jan 07 15:21:46 GMT 1992
*
* (C) Copyright 1992, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
* File: tessedit.cpp (Formerly tessedit.c)
* Description: Main program for merge of tess and editor.
* Author: Ray Smith
* Created: Tue Jan 07 15:21:46 GMT 1992
*
* (C) Copyright 1992, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
#include "mfcpch.h"
//#define USE_VLD //Uncomment for Visual Leak Detector.
@ -23,7 +23,6 @@
#include <vld.h>
#endif
#include <ctype.h>
#include "applybox.h"
#include "control.h"
#include "tessvars.h"
#include "tessedit.h"
@ -31,18 +30,16 @@
#include "thresholder.h"
#include "pageres.h"
#include "imgs.h"
#include "varabled.h"
#include "params.h"
#include "paramsd.h"
#include "tprintf.h"
#include "tesseractmain.h"
#include "stderr.h"
#include "notdll.h"
#include "mainblk.h"
#include "output.h"
#include "globals.h"
#include "helpers.h"
#include "blread.h"
#include "tfacep.h"
#include "callnet.h"
// Include automatically generated configuration file if running autoconf
#ifdef HAVE_CONFIG_H
@ -55,33 +52,15 @@
#else
#define _(x) (x)
#endif
#ifdef HAVE_LIBTIFF
#include "tiffio.h"
#ifndef HAVE_LIBLEPT
#error "Sorry: Tesseract no longer compiles or runs without Leptonica!";
#endif
#ifdef HAVE_LIBLEPT
#include "allheaders.h"
#else
class Pix;
#endif
#ifdef _TIFFIO_
void read_tiff_image(TIFF* tif, IMAGE* image);
#endif
#define VARDIR "configs/" /*variables files */
//config under api
#define API_CONFIG "configs/api_config"
#define EXTERN
BOOL_VAR(tessedit_create_boxfile, FALSE, "Output text with boxes");
BOOL_VAR(tessedit_create_hocr, FALSE, "Output HTML with hOCR markup");
BOOL_VAR(tessedit_read_image, TRUE, "Ensure the image is read");
INT_VAR(tessedit_serial_unlv, 0,
"0->Whole page, 1->serial no adapt, 2->serial with adapt");
INT_VAR(tessedit_page_number, -1,
"-1 -> All pages, else specific page to process");
BOOL_VAR(tessedit_write_images, FALSE, "Capture the image from the IPE");
BOOL_VAR(tessedit_debug_to_screen, FALSE, "Dont use debug file");
const int kMaxIntSize = 22;
char szAppName[] = "Tessedit"; //app name
@ -112,444 +91,373 @@ char szAppName[] = "Tessedit"; //app name
// the value of input_file is ignored - ugly, but true - a consequence of
// the way that unlv zone file reading takes the place of a page layout
// analyzer.
void TesseractImage(const char* input_file, IMAGE* image, Pix* pix, int page_index,
void TesseractImage(const char* input_file, Pix* pix, int page_index,
tesseract::TessBaseAPI* api, STRING* text_out) {
api->SetInputName(input_file);
#ifdef HAVE_LIBLEPT
if (pix != NULL) {
api->SetImage(pix);
} else {
#endif
int bytes_per_line = check_legal_image_size(image->get_xsize(),
image->get_ysize(),
image->get_bpp());
api->SetImage(image->get_buffer(), image->get_xsize(), image->get_ysize(),
image->get_bpp() / 8, bytes_per_line);
#ifdef HAVE_LIBLEPT
}
#endif
if (tessedit_serial_unlv == 0) {
char* text;
if (tessedit_create_boxfile)
text = api->GetBoxText(page_index);
else if (tessedit_write_unlv)
text = api->GetUNLVText();
else if (tessedit_create_hocr)
text = api->GetHOCRText(page_index + 1);
else
text = api->GetUTF8Text();
*text_out += text;
delete [] text;
} else {
BLOCK_LIST blocks;
STRING filename = input_file;
const char* lastdot = strrchr(filename.string(), '.');
if (lastdot != NULL) {
filename[lastdot - filename.string()] = '\0';
}
if (!read_unlv_file(filename, image->get_xsize(), image->get_ysize(),
&blocks)) {
fprintf(stderr, _("Error: Must have a unlv zone file %s to read!\n"),
filename.string());
return;
}
BLOCK_IT b_it = &blocks;
for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
BLOCK* block = b_it.data();
TBOX box = block->bounding_box();
api->SetRectangle(box.left(), image->get_ysize() - box.top(),
box.width(), box.height());
char* text = api->GetUNLVText();
*text_out += text;
delete [] text;
if (tessedit_serial_unlv == 1)
api->ClearAdaptiveClassifier();
}
}
if (tessedit_write_images) {
page_image.write("tessinput.tif");
}
api->SetInputName(input_file);
api->SetImage(pix);
int serial_unlv;
ASSERT_HOST(api->GetIntVariable("tessedit_serial_unlv", &serial_unlv));
if (serial_unlv == 0) {
char* text;
bool bool_value;
if ((api->GetBoolVariable("tessedit_create_boxfile", &bool_value) &&
bool_value) ||
(api->GetBoolVariable("tessedit_make_boxes_from_boxes", &bool_value) &&
bool_value)) {
text = api->GetBoxText(page_index);
} else if (api->GetBoolVariable("tessedit_write_unlv", &bool_value) &&
bool_value) {
text = api->GetUNLVText();
} else if (api->GetBoolVariable("tessedit_create_hocr", &bool_value)
&& bool_value) {
text = api->GetHOCRText(page_index);
} else {
text = api->GetUTF8Text();
}
*text_out += text;
delete [] text;
} else {
BLOCK_LIST blocks;
STRING filename = input_file;
const char* lastdot = strrchr(filename.string(), '.');
if (lastdot != NULL) {
filename[lastdot - filename.string()] = '\0';
}
if (!read_unlv_file(filename, pixGetWidth(pix), pixGetHeight(pix),
&blocks)) {
fprintf(stderr, _("Error: Must have a unlv zone file %s to read!\n"),
filename.string());
return;
}
BLOCK_IT b_it = &blocks;
for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
BLOCK* block = b_it.data();
TBOX box = block->bounding_box();
api->SetRectangle(box.left(), pixGetHeight(pix) - box.top(),
box.width(), box.height());
char* text = api->GetUNLVText();
*text_out += text;
delete [] text;
if (serial_unlv == 1)
api->ClearAdaptiveClassifier();
}
}
bool bool_value;
if (api->GetBoolVariable("tessedit_write_images",
&bool_value) && bool_value) {
Pix* page_pix = api->GetThresholdedImage();
pixWrite("tessinput.tif", page_pix, IFF_TIFF_G4);
}
}
/**********************************************************************
* main()
*
**********************************************************************/
* main()
*
**********************************************************************/
int main(int argc, char **argv) {
STRING outfile; //output file
STRING outfile; //output file
#ifdef USING_GETTEXT
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
#endif
// Detect incorrectly placed -l option.
for (int arg = 0; arg < argc; ++arg) {
if (arg != 3 && strcmp(argv[arg], "-l") == 0) {
fprintf(stderr, _("Error: -l must be arg3, not %d\n"), arg);
argc = 0;
}
}
// Detect incorrectly placed -l option.
for (int arg = 0; arg < argc; ++arg) {
if (arg != 3 && strcmp(argv[arg], "-l") == 0) {
fprintf(stderr, _("Error: -l must be arg3, not %d\n"), arg);
argc = 0;
}
}
#ifdef HAVE_CONFIG_H /* Assume that only Unix users care about -v */
if (argc == 2 && strcmp(argv[1], "-v") == 0) {
fprintf(stderr, "tesseract %s\n", PACKAGE_VERSION);
exit(1);
}
if (argc == 2 && strcmp(argv[1], "-v") == 0) {
fprintf(stderr, "tesseract %s\n", PACKAGE_VERSION);
exit(1);
}
#endif
if (argc < 3) {
fprintf(stderr, "Usage:%s imagename outputbase [-l lang]"
" [configfile [[+|-]varfile]...]\n"
if (argc < 3) {
fprintf(stderr, "Usage:%s imagename outputbase [-l lang]"
" [configfile [[+|-]varfile]...]\n"
#if !defined(HAVE_LIBLEPT) && !defined(_TIFFIO_)
"Warning - no liblept or libtiff - cannot read compressed"
" tiff files.\n"
"Warning - no liblept or libtiff - cannot read compressed"
" tiff files.\n"
#endif
, argv[0]);
exit(1);
}
// Find the required language.
const char* lang = "eng";
int arg = 3;
if (argc >= 5 && strcmp(argv[3], "-l") == 0) {
lang = argv[4];
arg = 5;
}
, argv[0]);
exit(1);
}
// Find the required language.
const char* lang = "eng";
int arg = 3;
if (argc >= 5 && strcmp(argv[3], "-l") == 0) {
lang = argv[4];
arg = 5;
}
tesseract::TessBaseAPI api;
tesseract::TessBaseAPI api;
api.SetOutputName(argv[2]);
api.SetPageSegMode(tesseract::PSM_AUTO);
api.Init(argv[0], lang, &(argv[arg]), argc-arg, false);
api.SetOutputName(argv[2]);
api.Init(argv[0], lang, tesseract::OEM_DEFAULT, &(argv[arg]), argc-arg, false);
tprintf (_("Tesseract Open Source OCR Engine"));
#if defined(HAVE_LIBLEPT)
tprintf (_(" with Leptonica\n"));
#elif defined(_TIFFIO_)
tprintf (_(" with LibTiff\n"));
#else
tprintf ("\n");
#endif
tprintf (_("Tesseract Open Source OCR Engine with Leptonica\n"));
IMAGE image;
STRING text_out;
int page_number = tessedit_page_number;
if (page_number < 0)
page_number = 0;
FILE* fp = fopen(argv[1], "rb");
if (fp == NULL) {
tprintf(_("Image file %s cannot be opened!\n"), argv[1]);
fclose(fp);
exit(1);
}
#ifdef HAVE_LIBLEPT
int page = page_number;
int npages = 0;
bool is_tiff = fileFormatIsTiff(fp);
if (is_tiff)
{
int tiffstat = tiffGetCount(fp, &npages);
if (tiffstat == 1)
{
fprintf(stderr, _("Error reading file %s!\n"), argv[1]);
fclose(fp);
exit(1);
}
else
{
fprintf(stderr, _("Number of found pages: %d.\n"), npages);
}
}
fclose(fp);
fp = NULL;
STRING text_out;
int tessedit_page_number;
ASSERT_HOST(api.GetIntVariable("tessedit_page_number",
&tessedit_page_number));
int page_number = tessedit_page_number;
if (page_number < 0)
page_number = 0;
FILE* fp = fopen(argv[1], "rb");
if (fp == NULL) {
tprintf(_("Image file %s cannot be opened!\n"), argv[1]);
fclose(fp);
exit(1);
}
int page = page_number;
int npages = 0;
bool is_tiff = fileFormatIsTiff(fp);
if (is_tiff)
{
int tiffstat = tiffGetCount(fp, &npages);
if (tiffstat == 1)
{
fprintf (stderr, _("Error reading file %s!\n"), argv[1]);
fclose(fp);
exit(1);
}
else
fprintf(stderr, _("Number of found pages: %d.\n"), npages);
}
fclose(fp);
fp = NULL;
Pix *pix;
if (is_tiff)
{
for (; page < npages; ++page)
{
pix = pixReadTiff(argv[1], page);
if (!pix)
continue;
if (npages > 1)
{
tprintf(_("Page %d\n"), page);
}
char page_str[kMaxIntSize];
snprintf(page_str, kMaxIntSize - 1, "%d", page);
api.SetVariable("applybox_page", page_str);
// Run tesseract on the page!
TesseractImage(argv[1], NULL, pix, page, &api, &text_out);
pixDestroy(&pix);
if (tessedit_page_number >= 0 || npages == 1)
{
break;
}
}
}
else
{
// The file is not a tiff file, so use the general pixRead function.
// If the image fails to read, try it as a list of filenames.
PIX* pix = pixRead(argv[1]);
if (pix == NULL) {
FILE* fimg = fopen(argv[1], "r");
if (fimg == NULL) {
tprintf(_("File %s cannot be opened!\n"), argv[1]);
fclose(fimg);
exit(1);
}
char filename[MAX_PATH];
while (fgets(filename, sizeof(filename), fimg) != NULL) {
chomp_string(filename);
pix = pixRead(filename);
if (pix == NULL) {
tprintf(_("Image file %s cannot be read!\n"), filename);
fclose(fimg);
exit(1);
}
tprintf(_("Page %d : %s\n"), page, filename);
TesseractImage(filename, NULL, pix, page, &api, &text_out);
pixDestroy(&pix);
++page;
}
fclose(fimg);
} else {
TesseractImage(argv[1], NULL, pix, 0, &api, &text_out);
pixDestroy(&pix);
}
}
#else
#ifdef _TIFFIO_
int len = strlen(argv[1]);
char* ext = new char[5];
for (int i=4; i>=0; i--)
ext[4-i] = (char) tolower((int) argv[1][len - i]);
if (len > 3 && (strcmp("tif", ext + 1) == 0 || strcmp("tiff", ext) == 0)) {
// Use libtiff to read a tif file so multi-page can be handled.
// The page number so the tiff file can be closed and reopened.
TIFF* archive = NULL;
do {
// Since libtiff keeps all read images in memory we have to close the
// file and reopen it for every page, and seek to the appropriate page.
if (archive != NULL)
TIFFClose(archive);
archive = TIFFOpen(argv[1], "r");
if (archive == NULL) {
tprintf(_("Read of file %s failed.\n"), argv[1]);
exit(1);
}
if (page_number > 0)
tprintf(_("Page %d\n"), page_number);
Pix *pix;
if (is_tiff) {
for (; page < npages; ++page)
{
pix = pixReadTiff(argv[1], page);
if (!pix)
continue;
if (npages > 1)
tprintf(_("Page %d\n"), page);
char page_str[kMaxIntSize];
snprintf(page_str, kMaxIntSize - 1, "%d", page);
api.SetVariable("applybox_page", page_str);
// Seek to the appropriate page.
for (int i = 0; i < page_number; ++i) {
TIFFReadDirectory(archive);
}
char page_str[kMaxIntSize];
snprintf(page_str, kMaxIntSize - 1, "%d", page_number);
api.SetVariable("applybox_page", page_str);
// Read the current page into the Tesseract image.
IMAGE image;
read_tiff_image(archive, &image);
// Run tesseract on the page!
TesseractImage(argv[1], pix, page, &api, &text_out);
pixDestroy(&pix);
if (tessedit_page_number >= 0 || npages == 1)
{
break;
}
}
} else
{
// The file is not a tiff file, so use the general pixRead function.
// If the image fails to read, try it as a list of filenames.
pix = pixRead(argv[1]);
if (pix == NULL) {
FILE* fimg = fopen(argv[1], "r");
if (fimg == NULL) {
tprintf(_("File %s cannot be opened!\n"), argv[1]);
fclose(fimg);
exit(1);
}
char filename[MAX_PATH];
while (fgets(filename, sizeof(filename), fimg) != NULL) {
chomp_string(filename);
pix = pixRead(filename);
if (pix == NULL) {
tprintf(_("Image file %s cannot be read!\n"), filename);
fclose(fimg);
exit(1);
}
tprintf(_("Page %d : %s\n"), page, filename);
TesseractImage(filename, pix, page, &api, &text_out);
pixDestroy(&pix);
++page;
}
fclose(fimg);
} else {
TesseractImage(argv[1], pix, 0, &api, &text_out);
pixDestroy(&pix);
}
}
// Run tesseract on the page!
TesseractImage(argv[1], &image, NULL, page_number, &api, &text_out);
++page_number;
// Do this while there are more pages in the tiff file.
} while (TIFFReadDirectory(archive) &&
(page_number <= tessedit_page_number || tessedit_page_number < 0));
TIFFClose(archive);
} else {
#endif
// Using built-in image library to read bmp, or tiff without libtiff.
if (image.read_header(argv[1]) < 0) {
tprintf(_("Read of file %s failed.\n"), argv[1]);
exit(1);
}
if (image.read(image.get_ysize ()) < 0)
MEMORY_OUT.error(argv[0], EXIT, _("Read of image %s"), argv[1]);
invert_image(&image);
TesseractImage(argv[1], &image, NULL, 0, &api, &text_out);
#ifdef _TIFFIO_
}
delete[] ext;
#endif
#endif // HAVE_LIBLEPT
bool output_hocr = false;
api.GetBoolVariable("tessedit_create_hocr", &output_hocr);
bool output_box = false;
api.GetBoolVariable("tessedit_create_boxfile", &output_box);
outfile = argv[2];
outfile += output_hocr ? ".html" : output_box ? ".box" : ".txt";
FILE* fout = fopen(outfile.string(), "w");
if (fout == NULL) {
tprintf(_("Cannot create output file %s\n"), outfile.string());
fclose(fout);
exit(1);
}
if (output_hocr) {
const char html_header[] =
"<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\""
" \"http://www.w3.org/TR/html4/loose.dtd\">\n"
"<html>\n<head>\n<title></title>\n"
"<meta http-equiv=\"Content-Type\" content=\"text/html;"
"charset=utf-8\" >\n<meta name='ocr-system' content='tesseract'>\n"
"</head>\n<body>\n";
fprintf(fout, "%s", html_header);
}
fwrite(text_out.string(), 1, text_out.length(), fout);
if (output_hocr)
fprintf(fout, "</body>\n</html>\n");
fclose(fout);
//no longer using fp
if (fp != NULL) fclose(fp);
bool output_hocr = tessedit_create_hocr;
outfile = argv[2];
outfile += output_hocr ? ".html" : tessedit_create_boxfile ? ".box" : ".txt";
FILE* fout = fopen(outfile.string(), "w");
if (fout == NULL) {
tprintf(_("Cannot create output file %s\n"), outfile.string());
fclose(fout);
exit(1);
}
if (output_hocr) {
const char html_header[] =
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n"
" <title>OCR Output</title>\n"
" <meta http-equiv=\"Content-Type\" content=\"text/html;"
"charset=utf-8\" />\n <meta name='ocr-system' "
"content='tesseract-ocr 3.00' />\n <meta name='ocr-capabilities'"
" content='ocr_page' />\n</head>\n<body>\n";
fprintf(fout, "%s", html_header);
}
fwrite(text_out.string(), 1, text_out.length(), fout);
if (output_hocr)
fprintf(fout, "</body>\n</html>\n");
fclose(fout);
return 0; //Normal exit
return 0; //Normal exit
}
#ifdef __MSW32__
int initialized = 0;
/**********************************************************************
* WinMain
*
* Main function for a windows program.
**********************************************************************/
* WinMain
*
* Main function for a windows program.
**********************************************************************/
int WINAPI WinMain( //main for windows //command line
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,
int nCmdShow) {
WNDCLASS wc;
HWND hwnd;
MSG msg;
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,
int nCmdShow) {
WNDCLASS wc;
HWND hwnd;
MSG msg;
char **argv;
char *argsin[2];
int argc;
int exit_code;
char **argv;
char *argsin[2];
int argc;
int exit_code;
wc.style = CS_NOCLOSE | CS_OWNDC;
wc.lpfnWndProc = (WNDPROC) WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = NULL; //LoadIcon (NULL, IDI_APPLICATION);
wc.hCursor = NULL; //LoadCursor (NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = szAppName;
wc.style = CS_NOCLOSE | CS_OWNDC;
wc.lpfnWndProc = (WNDPROC) WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = NULL; //LoadIcon (NULL, IDI_APPLICATION);
wc.hCursor = NULL; //LoadCursor (NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = szAppName;
RegisterClass(&wc);
RegisterClass(&wc);
hwnd = CreateWindow (szAppName, szAppName,
WS_OVERLAPPEDWINDOW | WS_DISABLED,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, HWND_DESKTOP, NULL, hInstance, NULL);
hwnd = CreateWindow (szAppName, szAppName,
WS_OVERLAPPEDWINDOW | WS_DISABLED,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, HWND_DESKTOP, NULL, hInstance, NULL);
argsin[0] = strdup (szAppName);
argsin[1] = strdup (lpszCmdLine);
/*allocate memory for the args. There can never be more than half*/
/*the total number of characters in the arguments.*/
argv =
(char **) malloc (((strlen (argsin[0]) + strlen (argsin[1])) / 2 + 1) *
sizeof (char *));
argsin[0] = strdup (szAppName);
argsin[1] = strdup (lpszCmdLine);
/*allocate memory for the args. There can never be more than half*/
/*the total number of characters in the arguments.*/
argv =
(char **) malloc (((strlen (argsin[0]) + strlen (argsin[1])) / 2 + 1) *
sizeof (char *));
/*now construct argv as it should be for C.*/
argc = parse_args (2, argsin, argv);
/*now construct argv as it should be for C.*/
argc = parse_args (2, argsin, argv);
// ShowWindow (hwnd, nCmdShow);
// UpdateWindow (hwnd);
// ShowWindow (hwnd, nCmdShow);
// UpdateWindow (hwnd);
if (initialized) {
exit_code = main (argc, argv);
free (argsin[0]);
free (argsin[1]);
free(argv);
return exit_code;
}
while (GetMessage (&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
if (initialized) {
exit_code = main (argc, argv);
break;
}
else
exit_code = msg.wParam;
}
free (argsin[0]);
free (argsin[1]);
free(argv);
return exit_code;
if (initialized) {
exit_code = main (argc, argv);
free (argsin[0]);
free (argsin[1]);
free(argv);
return exit_code;
}
while (GetMessage (&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
if (initialized) {
exit_code = main (argc, argv);
break;
}
else
exit_code = msg.wParam;
}
free (argsin[0]);
free (argsin[1]);
free(argv);
return exit_code;
}
/**********************************************************************
* WndProc
*
* Function to respond to messages.
**********************************************************************/
* WndProc
*
* Function to respond to messages.
**********************************************************************/
LONG WINAPI WndProc( //message handler
HWND hwnd, //window with message
UINT msg, //message typ
WPARAM wParam,
LPARAM lParam) {
HDC hdc;
HWND hwnd, //window with message
UINT msg, //message typ
WPARAM wParam,
LPARAM lParam) {
HDC hdc;
if (msg == WM_CREATE) {
//
// Create a rendering context.
//
hdc = GetDC (hwnd);
ReleaseDC(hwnd, hdc);
initialized = 1;
return 0;
}
return DefWindowProc (hwnd, msg, wParam, lParam);
if (msg == WM_CREATE) {
//
// Create a rendering context.
//
hdc = GetDC (hwnd);
ReleaseDC(hwnd, hdc);
initialized = 1;
return 0;
}
return DefWindowProc (hwnd, msg, wParam, lParam);
}
/**********************************************************************
* parse_args
*
* Turn a list of args into a new list of args with each separate
* whitespace spaced string being an arg.
**********************************************************************/
* parse_args
*
* Turn a list of args into a new list of args with each separate
* whitespace spaced string being an arg.
**********************************************************************/
int
parse_args ( /*refine arg list */
int argc, /*no of input args */
char *argv[], /*input args */
char *arglist[] /*output args */
) {
int argcount; /*converted argc */
char *testchar; /*char in option string */
int arg; /*current argument */
int argc, /*no of input args */
char *argv[], /*input args */
char *arglist[] /*output args */
) {
int argcount; /*converted argc */
char *testchar; /*char in option string */
int arg; /*current argument */
argcount = 0; /*no of options */
for (arg = 0; arg < argc; arg++) {
testchar = argv[arg]; /*start of arg */
do {
while (*testchar
&& (*testchar == ' ' || *testchar == '\n'
|| *testchar == '\t'))
testchar++; /*skip white space */
if (*testchar) {
/*new arg */
arglist[argcount++] = testchar;
/*skip to white space */
for (testchar++; *testchar && *testchar != ' ' && *testchar != '\n' && *testchar != '\t'; testchar++);
if (*testchar)
*testchar++ = '\0'; /*turn to separate args */
}
}
while (*testchar);
}
return argcount; /*new number of args */
argcount = 0; /*no of options */
for (arg = 0; arg < argc; arg++) {
testchar = argv[arg]; /*start of arg */
do {
while (*testchar
&& (*testchar == ' ' || *testchar == '\n'
|| *testchar == '\t'))
testchar++; /*skip white space */
if (*testchar) {
/*new arg */
arglist[argcount++] = testchar;
/*skip to white space */
for (testchar++; *testchar && *testchar != ' ' && *testchar != '\n' && *testchar != '\t'; testchar++) ;
if (*testchar)
*testchar++ = '\0'; /*turn to separate args */
}
}
while (*testchar);
}
return argcount; /*new number of args */
}
#endif

View File

@ -20,41 +20,10 @@
#ifndef TESSERACTMAIN_H
#define TESSERACTMAIN_H
#include "varable.h"
#include "tessclas.h"
#include "params.h"
#include "blobs.h"
#include "notdll.h"
extern BOOL_VAR_H(tessedit_create_boxfile, FALSE, "Output text with boxes");
extern BOOL_VAR_H(tessedit_read_image, TRUE, "Ensure the image is read");
extern INT_VAR_H(tessedit_serial_unlv, 0,
"0->Whole page, 1->serial no adapt, 2->serial with adapt");
extern INT_VAR_H(tessedit_page_number, -1,
"-1 -> All pages, else specific page to process");
extern BOOL_VAR_H(tessedit_write_images, FALSE,
"Capture the image from the IPE");
extern BOOL_VAR_H(tessedit_debug_to_screen, FALSE, "Dont use debug file");
/**
* run from api
* @param arg0 program name
* @param lang language
*/
inT32 api_main(const char *arg0,
uinT16 lang);
/**
* setup dummy engine info
* @param lang user language
* @param name of engine
* @param version of engine
*/
inT16 setup_info(uinT16 lang,
const char *name,
const char *version);
/**
* read dummy image info
* @param im_out read dummy image info
*/
inT16 read_image(IMAGE *im_out);
#ifdef __MSW32__
/**
* main for windows command line

View File

@ -1,35 +1,37 @@
SUBDIRS =
AM_CPPFLAGS = \
-DUSE_STD_NAMESPACE \
-I$(top_srcdir)/ccutil -I$(top_srcdir)/ccstruct \
-I$(top_srcdir)/image -I$(top_srcdir)/viewer \
-I$(top_srcdir)/ccops -I$(top_srcdir)/dict \
-I$(top_srcdir)/classify \
-I$(top_srcdir)/wordrec -I$(top_srcdir)/cutil \
-I$(top_srcdir)/neural_networks/runtime -I$(top_srcdir)/cube \
-I$(top_srcdir)/textord
EXTRA_DIST = tessembedded.cpp ccmain.vcproj
EXTRA_DIST = tessembedded.cpp
include_HEADERS = \
adaptions.h applybox.h blobcmp.h \
callnet.h charcut.h charsample.h control.h \
docqual.h expandblob.h fixspace.h fixxht.h \
imgscale.h matmatch.h osdetect.h output.h \
pagewalk.h paircmp.h pgedit.h reject.h scaleimg.h \
charcut.h control.h cube_reco_context.h \
docqual.h fixspace.h \
imgscale.h osdetect.h output.h \
paramsd.h pgedit.h reject.h scaleimg.h \
tessbox.h tessedit.h tessembedded.h tesseractclass.h \
tessio.h tessvars.h tfacep.h tfacepp.h thresholder.h tstruct.h \
varabled.h werdit.h
tesseract_cube_combiner.h \
tessvars.h tfacep.h tfacepp.h thresholder.h tstruct.h \
werdit.h
lib_LTLIBRARIES = libtesseract_main.la
libtesseract_main_la_SOURCES = \
adaptions.cpp ambigsrecog.cpp applybox.cpp \
blobcmp.cpp \
callnet.cpp charcut.cpp charsample.cpp control.cpp \
docqual.cpp expandblob.cpp fixspace.cpp fixxht.cpp \
imgscale.cpp matmatch.cpp osdetect.cpp output.cpp \
pagewalk.cpp paircmp.cpp pgedit.cpp reject.cpp scaleimg.cpp \
adaptions.cpp applybox.cpp \
charcut.cpp control.cpp cube_control.cpp cube_reco_context.cpp \
docqual.cpp fixspace.cpp fixxht.cpp \
imgscale.cpp osdetect.cpp output.cpp pagesegmain.cpp \
pagewalk.cpp paramsd.cpp pgedit.cpp reject.cpp scaleimg.cpp \
recogtraining.cpp tesseract_cube_combiner.cpp \
tessbox.cpp tessedit.cpp tesseractclass.cpp tessvars.cpp \
tfacepp.cpp thresholder.cpp tstruct.cpp \
varabled.cpp werdit.cpp
werdit.cpp
libtesseract_main_la_LIBADD = \
../wordrec/libtesseract_wordrec.la
libtesseract_main_la_LDFLAGS = -version-info $(GENERIC_LIBRARY_VERSION)

View File

@ -72,13 +72,13 @@ am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(includedir)"
LTLIBRARIES = $(lib_LTLIBRARIES)
libtesseract_main_la_DEPENDENCIES = \
../wordrec/libtesseract_wordrec.la
am_libtesseract_main_la_OBJECTS = adaptions.lo ambigsrecog.lo \
applybox.lo blobcmp.lo callnet.lo charcut.lo charsample.lo \
control.lo docqual.lo expandblob.lo fixspace.lo fixxht.lo \
imgscale.lo matmatch.lo osdetect.lo output.lo pagewalk.lo \
paircmp.lo pgedit.lo reject.lo scaleimg.lo tessbox.lo \
tessedit.lo tesseractclass.lo tessvars.lo tfacepp.lo \
thresholder.lo tstruct.lo varabled.lo werdit.lo
am_libtesseract_main_la_OBJECTS = adaptions.lo applybox.lo charcut.lo \
control.lo cube_control.lo cube_reco_context.lo docqual.lo \
fixspace.lo fixxht.lo imgscale.lo osdetect.lo output.lo \
pagesegmain.lo pagewalk.lo paramsd.lo pgedit.lo reject.lo \
scaleimg.lo recogtraining.lo tesseract_cube_combiner.lo \
tessbox.lo tessedit.lo tesseractclass.lo tessvars.lo \
tfacepp.lo thresholder.lo tstruct.lo werdit.lo
libtesseract_main_la_OBJECTS = $(am_libtesseract_main_la_OBJECTS)
libtesseract_main_la_LINK = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) \
$(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
@ -251,7 +251,6 @@ libdir = @libdir@
libexecdir = @libexecdir@
localedir = @localedir@
localstatedir = @localstatedir@
lt_ECHO = @lt_ECHO@
mandir = @mandir@
mkdir_p = @mkdir_p@
oldincludedir = @oldincludedir@
@ -269,35 +268,37 @@ top_builddir = @top_builddir@
top_srcdir = @top_srcdir@
SUBDIRS =
AM_CPPFLAGS = \
-DUSE_STD_NAMESPACE \
-I$(top_srcdir)/ccutil -I$(top_srcdir)/ccstruct \
-I$(top_srcdir)/image -I$(top_srcdir)/viewer \
-I$(top_srcdir)/ccops -I$(top_srcdir)/dict \
-I$(top_srcdir)/classify \
-I$(top_srcdir)/wordrec -I$(top_srcdir)/cutil \
-I$(top_srcdir)/neural_networks/runtime -I$(top_srcdir)/cube \
-I$(top_srcdir)/textord
EXTRA_DIST = tessembedded.cpp ccmain.vcproj
EXTRA_DIST = tessembedded.cpp
include_HEADERS = \
adaptions.h applybox.h blobcmp.h \
callnet.h charcut.h charsample.h control.h \
docqual.h expandblob.h fixspace.h fixxht.h \
imgscale.h matmatch.h osdetect.h output.h \
pagewalk.h paircmp.h pgedit.h reject.h scaleimg.h \
charcut.h control.h cube_reco_context.h \
docqual.h fixspace.h \
imgscale.h osdetect.h output.h \
paramsd.h pgedit.h reject.h scaleimg.h \
tessbox.h tessedit.h tessembedded.h tesseractclass.h \
tessio.h tessvars.h tfacep.h tfacepp.h thresholder.h tstruct.h \
varabled.h werdit.h
tesseract_cube_combiner.h \
tessvars.h tfacep.h tfacepp.h thresholder.h tstruct.h \
werdit.h
lib_LTLIBRARIES = libtesseract_main.la
libtesseract_main_la_SOURCES = \
adaptions.cpp ambigsrecog.cpp applybox.cpp \
blobcmp.cpp \
callnet.cpp charcut.cpp charsample.cpp control.cpp \
docqual.cpp expandblob.cpp fixspace.cpp fixxht.cpp \
imgscale.cpp matmatch.cpp osdetect.cpp output.cpp \
pagewalk.cpp paircmp.cpp pgedit.cpp reject.cpp scaleimg.cpp \
adaptions.cpp applybox.cpp \
charcut.cpp control.cpp cube_control.cpp cube_reco_context.cpp \
docqual.cpp fixspace.cpp fixxht.cpp \
imgscale.cpp osdetect.cpp output.cpp pagesegmain.cpp \
pagewalk.cpp paramsd.cpp pgedit.cpp reject.cpp scaleimg.cpp \
recogtraining.cpp tesseract_cube_combiner.cpp \
tessbox.cpp tessedit.cpp tesseractclass.cpp tessvars.cpp \
tfacepp.cpp thresholder.cpp tstruct.cpp \
varabled.cpp werdit.cpp
werdit.cpp
libtesseract_main_la_LIBADD = \
../wordrec/libtesseract_wordrec.la
@ -378,34 +379,32 @@ distclean-compile:
-rm -f *.tab.c
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/adaptions.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ambigsrecog.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/applybox.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/blobcmp.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/callnet.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/charcut.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/charsample.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/control.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cube_control.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cube_reco_context.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/docqual.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/expandblob.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fixspace.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fixxht.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/imgscale.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/matmatch.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/osdetect.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/output.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pagesegmain.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pagewalk.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/paircmp.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/paramsd.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pgedit.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/recogtraining.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/reject.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/scaleimg.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tessbox.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tessedit.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tesseract_cube_combiner.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tesseractclass.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tessvars.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tfacepp.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/thresholder.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tstruct.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/varabled.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/werdit.Plo@am__quote@
.cpp.o:

File diff suppressed because it is too large Load Diff

View File

@ -1,89 +0,0 @@
/**********************************************************************
* File: adaptions.h (Formerly adaptions.h)
* Description: Functions used to adapt to blobs already confidently
* identified
* Author: Chris Newton
* Created: Thu Oct 7 10:17:28 BST 1993
*
* (C) Copyright 1992, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
#ifndef ADAPTIONS_H
#define ADAPTIONS_H
#include "charsample.h"
#include "charcut.h"
#include "notdll.h"
extern BOOL_VAR_H (tessedit_reject_ems, FALSE, "Reject all m's");
extern BOOL_VAR_H (tessedit_reject_suspect_ems, FALSE, "Reject suspect m's");
extern double_VAR_H (tessedit_cluster_t1, 0.20,
"t1 threshold for clustering samples");
extern double_VAR_H (tessedit_cluster_t2, 0.40,
"t2 threshold for clustering samples");
extern double_VAR_H (tessedit_cluster_t3, 0.12,
"Extra threshold for clustering samples, only keep a new sample if best score greater than this value");
extern double_VAR_H (tessedit_cluster_accept_fraction, 0.80,
"Largest fraction of characters in cluster for it to be used for adaption");
extern INT_VAR_H (tessedit_cluster_min_size, 3,
"Smallest number of samples in a cluster for it to be used for adaption");
extern BOOL_VAR_H (tessedit_cluster_debug, FALSE,
"Generate and print debug information for adaption by clustering");
extern BOOL_VAR_H (tessedit_use_best_sample, FALSE,
"Use best sample from cluster when adapting");
extern BOOL_VAR_H (tessedit_test_cluster_input, FALSE,
"Set reject map to enable cluster input to be measured");
extern BOOL_VAR_H (tessedit_matrix_match, TRUE, "Use matrix matcher");
extern BOOL_VAR_H (tessedit_old_matrix_match, FALSE, "Use matrix matcher");
extern BOOL_VAR_H (tessedit_mm_use_non_adaption_set, FALSE,
"Don't try to adapt to characters on this list");
extern STRING_VAR_H (tessedit_non_adaption_set, ",.;:'~@*",
"Characters to be avoided when adapting");
extern BOOL_VAR_H (tessedit_mm_adapt_using_prototypes, TRUE,
"Use prototypes when adapting");
extern BOOL_VAR_H (tessedit_mm_use_prototypes, TRUE,
"Use prototypes as clusters are built");
extern BOOL_VAR_H (tessedit_mm_use_rejmap, FALSE,
"Adapt to characters using reject map");
extern BOOL_VAR_H (tessedit_mm_all_rejects, FALSE,
"Adapt to all characters using, matrix matcher");
extern BOOL_VAR_H (tessedit_mm_only_match_same_char, FALSE,
"Only match samples against clusters for the same character");
extern BOOL_VAR_H (tessedit_process_rns, FALSE, "Handle m - rn ambigs");
extern BOOL_VAR_H (tessedit_demo_adaption, FALSE,
"Display cut images and matrix match for demo purposes");
extern INT_VAR_H (tessedit_demo_word1, 62,
"Word number of first word to display");
extern INT_VAR_H (tessedit_demo_word2, 64,
"Word number of second word to display");
extern STRING_VAR_H (tessedit_demo_file, "academe",
"Name of document containing demo words");
extern BOOL_VAR_H(tessedit_adapt_to_char_fragments, TRUE,
"Adapt to words that contain "
" a character composed form fragments");
void print_em_stats(CHAR_SAMPLES_LIST *char_clusters,
CHAR_SAMPLE_LIST *chars_waiting);
//lines of the image
CHAR_SAMPLE *clip_sample(PIXROW *pixrow,
IMAGELINE *imlines,
TBOX pix_box, //box of imlines extent
BOOL8 white_on_black,
char c);
void display_cluster_prototypes(CHAR_SAMPLES_LIST *char_clusters);
void reject_all_ems(WERD_RES *word);
void reject_all_fullstops(WERD_RES *word);
void reject_suspect_fullstops(WERD_RES *word);
BOOL8 suspect_em(WERD_RES *word, inT16 index);
BOOL8 suspect_fullstop(WERD_RES *word, inT16 i);
#endif

View File

@ -1,179 +0,0 @@
///////////////////////////////////////////////////////////////////////
// File: genericvector.h
// Description: Functions for producing classifications
// for the input to ambigstraining.
// Author: Daria Antonova
// Created: Mon Jun 23 11:26:43 PDT 2008
//
// (C) Copyright 2007, Google Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////
#include "ambigs.h"
#include "applybox.h"
#include "boxread.h"
#include "control.h"
#include "permute.h"
#include "ratngs.h"
#include "reject.h"
#include "stopper.h"
#include "tesseractclass.h"
namespace tesseract {
// Sets flags necessary for ambigs training mode.
// Opens and returns the pointer to the output file.
FILE *Tesseract::init_ambigs_training(const STRING &fname) {
permute_only_top = 1; // use only top choice permuter
tessedit_tess_adaption_mode.set_value(0); // turn off adaption
tessedit_ok_mode.set_value(0); // turn off context checking
tessedit_enable_doc_dict.set_value(0); // turn off document dictionary
save_best_choices.set_value(1); // save individual char choices
stopper_no_acceptable_choices.set_value(1); // explore all segmentations
save_raw_choices.set_value(1); // save raw choices
// Open ambigs output file.
STRING output_fname = fname;
const char *lastdot = strrchr(output_fname.string(), '.');
if (lastdot != NULL) {
output_fname[lastdot - output_fname.string()] = '\0';
}
output_fname += ".txt";
FILE *output_file;
if (!(output_file = fopen(output_fname.string(), "a+"))) {
CANTOPENFILE.error("ambigs_training", EXIT,
"Can't open box file %s\n", output_fname.string());
}
return output_file;
}
// This function takes tif/box pair of files and runs recognition on the image,
// while making sure that the word bounds that tesseract identified roughly
// match to those specified by the input box file. For each word (ngram in a
// single bounding box from the input box file) it outputs the ocred result,
// the correct label, rating and certainty.
void Tesseract::ambigs_training_segmented(const STRING &fname,
PAGE_RES *page_res,
volatile ETEXT_DESC *monitor,
FILE *output_file) {
STRING box_fname = fname;
const char *lastdot = strrchr(box_fname.string(), '.');
if (lastdot != NULL) {
box_fname[lastdot - box_fname.string()] = '\0';
}
box_fname += ".box";
FILE *box_file;
if (!(box_file = fopen(box_fname.string(), "r"))) {
CANTOPENFILE.error("ambigs_training", EXIT,
"Can't open box file %s\n", box_fname.string());
}
static PAGE_RES_IT page_res_it;
page_res_it.page_res = page_res;
page_res_it.restart_page();
int x_min, y_min, x_max, y_max;
char label[UNICHAR_LEN * 10];
// Process all the words on this page.
while (page_res_it.word() != NULL &&
read_next_box(applybox_page, box_file, label,
&x_min, &y_min, &x_max, &y_max)) {
// Init bounding box of the current word bounding box and from box file.
TBOX box = TBOX(ICOORD(x_min, y_min), ICOORD(x_max, y_max));
TBOX word_box(page_res_it.word()->word->bounding_box());
bool one_word = true;
// Check whether the bounding box of the next word overlaps with the
// current box from box file.
while (page_res_it.next_word() != NULL &&
box.x_overlap(page_res_it.next_word()->word->bounding_box())) {
word_box = word_box.bounding_union(
page_res_it.next_word()->word->bounding_box());
page_res_it.forward();
one_word = false;
}
if (!word_box.major_overlap(box)) {
if (!word_box.x_overlap(box)) {
// We must be looking at the word that belongs in the "next" bounding
// box from the box file. The ngram that was supposed to appear in
// the current box read from the box file must have been dropped by
// tesseract as noise.
tprintf("Word %s was dropped as noise.\n", label);
continue; // stay on this blob, but read next box from box file
} else {
tprintf("Error: Insufficient overlap for word box"
" and box from file for %s\n", label);
word_box.print();
box.print();
exit(1);
}
}
// Skip recognizing the ngram if tesseract is sure it's not
// one word, otherwise run one recognition pass on this word.
if (!one_word) {
tprintf("Tesseract segmented %s as multiple words\n", label);
} else {
ambigs_classify_and_output(&page_res_it, label, output_file);
}
page_res_it.forward();
}
fclose(box_file);
}
// Run classify_word_pass1() on the current word. Output tesseract's raw choice
// as a result of the classification. For words labeled with a single unichar
// also output all alternatives from blob_choices of the best choice.
void Tesseract::ambigs_classify_and_output(PAGE_RES_IT *page_res_it,
const char *label,
FILE *output_file) {
int offset;
// Classify word.
classify_word_pass1(page_res_it->word(), page_res_it->row()->row,
page_res_it->block()->block,
FALSE, NULL, NULL);
WERD_CHOICE *best_choice = page_res_it->word()->best_choice;
ASSERT_HOST(best_choice != NULL);
ASSERT_HOST(best_choice->blob_choices() != NULL);
// Compute the number of unichars in the label.
int label_num_unichars = 0;
int step = 1; // should be non-zero on the first iteration
for (offset = 0; label[offset] != '\0' && step > 0;
step = getDict().getUnicharset().step(label + offset),
offset += step, ++label_num_unichars);
if (step == 0) {
tprintf("Not outputting illegal unichar %s\n", label);
return;
}
// Output all classifier choices for the unigrams (1-1 classifications).
if (label_num_unichars == 1 && best_choice->blob_choices()->length() == 1) {
BLOB_CHOICE_LIST_C_IT outer_blob_choice_it;
outer_blob_choice_it.set_to_list(best_choice->blob_choices());
BLOB_CHOICE_IT blob_choice_it;
blob_choice_it.set_to_list(outer_blob_choice_it.data());
for (blob_choice_it.mark_cycle_pt();
!blob_choice_it.cycled_list();
blob_choice_it.forward()) {
BLOB_CHOICE *blob_choice = blob_choice_it.data();
if (blob_choice->unichar_id() != INVALID_UNICHAR_ID) {
fprintf(output_file, "%s\t%s\t%.4f\t%.4f\n",
unicharset.id_to_unichar(blob_choice->unichar_id()),
label, blob_choice->rating(), blob_choice->certainty());
}
}
}
// Output the raw choice for succesful non 1-1 classifications.
getDict().PrintAmbigAlternatives(output_file, label, label_num_unichars);
}
} // namespace tesseract

File diff suppressed because it is too large Load Diff

View File

@ -1,84 +0,0 @@
/**********************************************************************
* File: applybox.h (Formerly applybox.h)
* Description: Re segment rows according to box file data
* Author: Phil Cheatle
* Created: Wed Nov 24 09:11:23 GMT 1993
*
* (C) Copyright 1993, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
#ifndef APPLYBOX_H
#define APPLYBOX_H
#include "varable.h"
#include "ocrblock.h"
#include "ocrrow.h"
#include "notdll.h"
#include "unichar.h"
extern BOOL_VAR_H (applybox_rebalance, TRUE, "Drop dead");
extern INT_VAR_H (applybox_debug, 0, "Debug level");
extern INT_VAR_H (applybox_page, 0, "Page number to apply boxes from");
extern STRING_VAR_H (applybox_test_exclusions, "|",
"Chars ignored for testing");
extern double_VAR_H (applybox_error_band, 0.15, "Err band as fract of xht");
extern STRING_VAR_H(exposure_pattern, "exp",
"Exposure value follows this pattern in the image"
" filename. The name of the image files are expected"
" to be in the form [lang].[fontname].exp[num].tif");
static const int kMinFragmentOutlineArea = 10;
void apply_boxes(const STRING& filename,
BLOCK_LIST *block_list //real blocks
);
ROW *find_row_of_box(
BLOCK_LIST *block_list, //real blocks
const TBOX &box, //from boxfile
inT16 &block_id,
inT16 &row_id_to_process);
inT16 resegment_box(
ROW *row,
TBOX &box,
UNICHAR_ID uch_id,
inT16 block_id,
inT16 row_id,
inT16 boxfile_lineno,
inT16 boxfile_charno,
inT16 *tgt_char_counts,
bool learn_char_fragments,
bool learning);
void tidy_up(
BLOCK_LIST *block_list, //real blocks
inT16 &ok_char_count,
inT16 &ok_row_count,
inT16 &unlabelled_words,
inT16 *tgt_char_counts,
inT16 &rebalance_count,
UNICHAR_ID *min_uch_id,
inT16 &min_samples,
inT16 &final_labelled_blob_count,
bool learn_character_fragments,
bool learning);
void report_failed_box(inT16 boxfile_lineno,
inT16 boxfile_charno,
TBOX box,
const char *box_ch,
const char *err_msg);
void apply_box_training(const STRING& filename, BLOCK_LIST *block_list);
#endif

View File

@ -1,82 +0,0 @@
/**********************************************************************
* File: blobcmp.c (Formerly blobcmp.c)
* Description: Code to compare blobs using the adaptive matcher.
* Author: Ray Smith
* Created: Wed Apr 21 09:28:51 BST 1993
*
* (C) Copyright 1993, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
#include "mfcpch.h"
#include "fxdefs.h"
#include "ocrfeatures.h"
#include "intmatcher.h"
#include "intproto.h"
#include "adaptive.h"
#include "adaptmatch.h"
#include "const.h"
#include "tessvars.h"
#include "tesseractclass.h"
#define CMP_CLASS 0
/**********************************************************************
* compare_tess_blobs
*
* Match 2 blobs using the adaptive classifier.
**********************************************************************/
namespace tesseract {
float Tesseract::compare_tess_blobs(TBLOB *blob1,
TEXTROW *row1,
TBLOB *blob2,
TEXTROW *row2) {
int fcount; /*number of features */
ADAPT_CLASS adapted_class;
ADAPT_TEMPLATES ad_templates;
LINE_STATS line_stats1, line_stats2;
INT_FEATURE_ARRAY int_features;
FEATURE_SET float_features;
INT_RESULT_STRUCT int_result; /*output */
BIT_VECTOR AllProtosOn = NewBitVector (MAX_NUM_PROTOS);
BIT_VECTOR AllConfigsOn = NewBitVector (MAX_NUM_CONFIGS);
set_all_bits (AllProtosOn, WordsInVectorOfSize (MAX_NUM_PROTOS));
set_all_bits (AllConfigsOn, WordsInVectorOfSize (MAX_NUM_CONFIGS));
EnterClassifyMode;
ad_templates = NewAdaptedTemplates (false);
GetLineStatsFromRow(row1, &line_stats1);
/*copy baseline stuff */
GetLineStatsFromRow(row2, &line_stats2);
adapted_class = NewAdaptedClass ();
AddAdaptedClass (ad_templates, adapted_class, CMP_CLASS);
InitAdaptedClass(blob1, &line_stats1, CMP_CLASS, adapted_class, ad_templates);
fcount = GetAdaptiveFeatures (blob2, &line_stats2,
int_features, &float_features);
if (fcount > 0) {
SetBaseLineMatch();
IntegerMatcher (ClassForClassId (ad_templates->Templates, CMP_CLASS),
AllProtosOn, AllConfigsOn, fcount, fcount,
int_features, 0, &int_result, testedit_match_debug);
FreeFeatureSet(float_features);
if (int_result.Rating < 0)
int_result.Rating = MAX_FLOAT32;
}
free_adapted_templates(ad_templates);
FreeBitVector(AllConfigsOn);
FreeBitVector(AllProtosOn);
return fcount > 0 ? int_result.Rating * fcount : MAX_FLOAT32;
}
} // namespace tesseract

View File

@ -1,93 +0,0 @@
/**********************************************************************
* File: callnet.cpp (Formerly callnet.c)
* Description: Interface to Neural Net matcher
* Author: Phil Cheatle
* Created: Wed Nov 18 10:35:00 GMT 1992
*
* (C) Copyright 1992, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
#include "mfcpch.h"
#include "errcode.h"
//#include "nmatch.h"
#include "globals.h"
#define OUTPUT_NODES 94
const ERRCODE NETINIT = "NN init error";
//extern "C"
//{
//extern char* demodir; /* where program lives */
void init_net() { /* Initialise net */
#ifdef ASPIRIN_INCLUDED
char wts_filename[256];
if (nmatch_init_network () != 0) {
NETINIT.error ("Init_net", EXIT, "Errcode %s", nmatch_error_string ());
}
strcpy(wts_filename, demodir);
strcat (wts_filename, "tessdata/netwts");
if (nmatch_load_network (wts_filename) != 0) {
NETINIT.error ("Init_net", EXIT, "Weights failed, Errcode %s",
nmatch_error_string ());
}
#endif
}
void callnet( /* Apply image to net */
float *input_vector,
char *top,
float *top_score,
char *next,
float *next_score) {
#ifdef ASPIRIN_INCLUDED
float *output_vector;
int i;
int max_out_i = 0;
int next_max_out_i = 0;
float max_out = -9;
float next_max_out = -9;
nmatch_set_input(input_vector);
nmatch_propagate_forward();
output_vector = nmatch_get_output ();
/* Now find top two choices */
for (i = 0; i < OUTPUT_NODES; i++) {
if (output_vector[i] > max_out) {
next_max_out = max_out;
max_out = output_vector[i];
next_max_out_i = max_out_i;
max_out_i = i;
}
else {
if (output_vector[i] > next_max_out) {
next_max_out = output_vector[i];
next_max_out_i = i;
}
}
}
*top = max_out_i + '!';
*next = next_max_out_i + '!';
*top_score = max_out;
*next_score = next_max_out;
#endif
}
//};

View File

@ -18,12 +18,12 @@
**********************************************************************/
#include "mfcpch.h"
#include "charcut.h"
#include "imgs.h"
#include "svshowim.h"
//#include "evnts.h"
#include "notdll.h"
#include "scrollview.h"
#include "charcut.h"
#include "imgs.h"
#include "scrollview.h"
#include "svshowim.h"
#include "notdll.h"
#include "helpers.h"
// Include automatically generated configuration file if running autoconf.
#ifdef HAVE_CONFIG_H
@ -35,10 +35,6 @@
#define BUG_OFFSET 1
#define EXTERN
EXTERN INT_VAR (pix_word_margin, 3, "How far outside word BB to grow");
extern IMAGE page_image;
ELISTIZE (PIXROW)
/*************************************************************************
* PIXROW::PIXROW()
@ -58,8 +54,8 @@ PIXROW::PIXROW(inT16 pos, inT16 count, PBLOB *blob) {
row_offset = pos;
row_count = count;
min = (inT16 *) alloc_mem (count * sizeof (inT16));
max = (inT16 *) alloc_mem (count * sizeof (inT16));
min = (inT16 *) alloc_mem(count * sizeof(inT16));
max = (inT16 *) alloc_mem(count * sizeof(inT16));
outline_list = blob->out_list ();
outline_it.set_to_list (outline_list);
@ -67,27 +63,21 @@ PIXROW::PIXROW(inT16 pos, inT16 count, PBLOB *blob) {
min[i] = MAX_INT16 - 1;
max[i] = -MAX_INT16 + 1;
y_coord = row_offset + i + 0.5;
for (outline_it.mark_cycle_pt ();
!outline_it.cycled_list (); outline_it.forward ()) {
pts_list = outline_it.data ()->polypts ();
pts_it.set_to_list (pts_list);
for (pts_it.mark_cycle_pt ();
!pts_it.cycled_list (); pts_it.forward ()) {
pt = pts_it.data ()->pos;
vec = pts_it.data ()->vec;
if ((vec.y () != 0) &&
(((pt.y () <= y_coord) && (pt.y () + vec.y () >= y_coord))
|| ((pt.y () >= y_coord)
&& (pt.y () + vec.y () <= y_coord)))) {
for (outline_it.mark_cycle_pt();
!outline_it.cycled_list(); outline_it.forward()) {
pts_list = outline_it.data()->polypts();
pts_it.set_to_list(pts_list);
for (pts_it.mark_cycle_pt(); !pts_it.cycled_list(); pts_it.forward()) {
pt = pts_it.data()->pos;
vec = pts_it.data()->vec;
if ((vec.y() != 0) &&
(((pt.y() <= y_coord) && (pt.y() + vec.y() >= y_coord))
|| ((pt.y() >= y_coord) && (pt.y() + vec.y() <= y_coord)))) {
/* The segment crosses y_coord so find x-point and check for min/max. */
x_coord = (inT16) floor ((y_coord -
pt.y ()) * vec.x () / vec.y () +
pt.x () + 0.5);
if (x_coord < min[i])
min[i] = x_coord;
x_coord--; //to get pix to left of line
if (x_coord > max[i])
max[i] = x_coord;
x_coord = (inT16) floor((y_coord - pt.y()) * vec.x() / vec.y() +
pt.x() + 0.5);
// x_coord - 1 to get pix to left of line
UpdateRange<inT16>(x_coord, x_coord - 1, &min[i], &max[i]);
}
}
}
@ -154,20 +144,14 @@ TBOX PIXROW::bounding_box() const {
for (i = 0; i < row_count; i++) {
y_coord = row_offset + i;
if (min[i] <= max[i]) {
if (y_coord < min_y)
min_y = y_coord;
if (y_coord + 1 > max_y)
max_y = y_coord + 1;
if (min[i] < min_x)
min_x = min[i];
if (max[i] + 1 > max_x)
max_x = max[i] + 1;
UpdateRange<inT16>(y_coord, y_coord + 1, &min_y, &max_y);
UpdateRange<inT16>(min[i], max[i] + 1, &min_x, &max_x);
}
}
if (min_x > max_x || min_y > max_y)
return TBOX ();
return TBOX();
else
return TBOX (ICOORD (min_x, min_y), ICOORD (max_x, max_y));
return TBOX(ICOORD(min_x, min_y), ICOORD(max_x, max_y));
}
@ -479,10 +463,10 @@ void char_clip_word( //
/* Define region for max pixrow expansion */
pix_box = word_box;
pix_box.move_bottom_edge (-pix_word_margin);
pix_box.move_top_edge (pix_word_margin);
pix_box.move_left_edge (-pix_word_margin);
pix_box.move_right_edge (pix_word_margin);
pix_box.move_bottom_edge (-kPixWordMargin);
pix_box.move_top_edge (kPixWordMargin);
pix_box.move_left_edge (-kPixWordMargin);
pix_box.move_right_edge (kPixWordMargin);
pix_box -= TBOX (ICOORD (0, 0 + BUG_OFFSET),
ICOORD (bin_image.get_xsize (),
bin_image.get_ysize () - BUG_OFFSET));

View File

@ -16,14 +16,6 @@
** limitations under the License.
*
**********************************************************************/
/**
* @file charcut.h
* @note Formerly charclip.h
* @brief Code for character clipping
* @author Phil Cheatle
* @date Created Wed Nov 11 08:35:15 GMT 1992
*
*/
#ifndef CHARCUT_H
#define CHARCUT_H
@ -44,6 +36,8 @@ class ScrollView;
* the row defined by min[0] and max[0] is held in row_offset.
*/
const int kPixWordMargin = 3; // how far outside word BB to grow
class PIXROW:public ELIST_LINK
{
public:
@ -126,11 +120,6 @@ class PIXROW:public ELIST_LINK
};
ELISTIZEH (PIXROW)
extern INT_VAR_H (pix_word_margin, 3, "How far outside word BB to grow");
extern BOOL_VAR_H (show_char_clipping, TRUE, "Show clip image window?");
extern INT_VAR_H (net_image_width, 40, "NN input image width");
extern INT_VAR_H (net_image_height, 36, "NN input image height");
extern INT_VAR_H (net_image_x_height, 22, "NN input image x_height");
void char_clip_word(
WERD *word, ///< word to be processed
IMAGE &bin_image, ///< whole image

View File

@ -1,709 +0,0 @@
/**********************************************************************
* File: charsample.cpp (Formerly charsample.c)
* Description: Class to contain character samples and match scores
* to be used for adaption
* Author: Chris Newton
* Created: Thu Oct 7 13:40:37 BST 1993
*
* (C) Copyright 1993, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
#include "mfcpch.h"
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#ifdef __UNIX__
#include <assert.h>
#include <unistd.h>
#endif
#include "memry.h"
#include "tessvars.h"
#include "statistc.h"
#include "charsample.h"
#include "paircmp.h"
#include "matmatch.h"
#include "adaptions.h"
#include "secname.h"
#include "notdll.h"
#include "tesseractclass.h"
// Include automatically generated configuration file if running autoconf.
#ifdef HAVE_CONFIG_H
#include "config_auto.h"
#endif
extern inT32 demo_word; // Hack for demos
ELISTIZE (CHAR_SAMPLE) ELISTIZE (CHAR_SAMPLES) CHAR_SAMPLE::CHAR_SAMPLE () {
sample_blob = NULL;
sample_denorm = NULL;
sample_image = NULL;
ch = '\0';
n_samples_matched = 0;
total_match_scores = 0.0;
sumsq_match_scores = 0.0;
}
CHAR_SAMPLE::CHAR_SAMPLE(PBLOB *blob, DENORM *denorm, char c) {
sample_blob = blob;
sample_denorm = denorm;
sample_image = NULL;
ch = c;
n_samples_matched = 0;
total_match_scores = 0.0;
sumsq_match_scores = 0.0;
}
CHAR_SAMPLE::CHAR_SAMPLE(IMAGE *image, char c) {
sample_blob = NULL;
sample_denorm = NULL;
sample_image = image;
ch = c;
n_samples_matched = 0;
total_match_scores = 0.0;
sumsq_match_scores = 0.0;
}
float CHAR_SAMPLE::match_sample( // Update match scores
CHAR_SAMPLE *test_sample,
BOOL8 updating,
tesseract::Tesseract* tess) {
float score1;
float score2;
IMAGE *image = test_sample->image ();
if (sample_blob != NULL && test_sample->blob () != NULL) {
PBLOB *blob = test_sample->blob ();
DENORM *denorm = test_sample->denorm ();
score1 = tess->compare_bln_blobs (sample_blob, sample_denorm, blob, denorm);
score2 = tess->compare_bln_blobs (blob, denorm, sample_blob, sample_denorm);
score1 = (score1 > score2) ? score1 : score2;
}
else if (sample_image != NULL && image != NULL) {
CHAR_PROTO *sample = new CHAR_PROTO (this);
score1 = matrix_match (sample_image, image);
delete sample;
}
else
return BAD_SCORE;
if ((tessedit_use_best_sample || tessedit_cluster_debug) && updating) {
n_samples_matched++;
total_match_scores += score1;
sumsq_match_scores += score1 * score1;
}
return score1;
}
double CHAR_SAMPLE::mean_score() {
if (n_samples_matched > 0)
return (total_match_scores / n_samples_matched);
else
return BAD_SCORE;
}
double CHAR_SAMPLE::variance() {
double mean = mean_score ();
if (n_samples_matched > 0) {
return (sumsq_match_scores / n_samples_matched) - mean * mean;
}
else
return BAD_SCORE;
}
void CHAR_SAMPLE::print(FILE *f) {
if (!tessedit_cluster_debug)
return;
if (n_samples_matched > 0)
fprintf (f,
"%c - sample matched against " INT32FORMAT
" blobs, mean: %f, var: %f\n", ch, n_samples_matched,
mean_score (), variance ());
else
fprintf (f, "No matches for this sample (%c)\n", ch);
}
void CHAR_SAMPLE::reset_match_statistics() {
n_samples_matched = 0;
total_match_scores = 0.0;
sumsq_match_scores = 0.0;
}
CHAR_SAMPLES::CHAR_SAMPLES() {
type = UNKNOWN;
samples.clear ();
ch = '\0';
best_sample = NULL;
proto = NULL;
}
CHAR_SAMPLES::CHAR_SAMPLES(CHAR_SAMPLE *sample) {
CHAR_SAMPLE_IT sample_it = &samples;
ASSERT_HOST (sample->image () != NULL || sample->blob () != NULL);
if (sample->image () != NULL)
type = IMAGE_CLUSTER;
else if (sample->blob () != NULL)
type = BLOB_CLUSTER;
samples.clear ();
sample_it.add_to_end (sample);
if (tessedit_mm_only_match_same_char)
ch = sample->character ();
else
ch = '\0';
best_sample = NULL;
proto = NULL;
}
void CHAR_SAMPLES::add_sample(CHAR_SAMPLE *sample, tesseract::Tesseract* tess) {
CHAR_SAMPLE_IT sample_it = &samples;
if (tessedit_use_best_sample || tessedit_cluster_debug)
for (sample_it.mark_cycle_pt ();
!sample_it.cycled_list (); sample_it.forward ()) {
sample_it.data ()->match_sample (sample, TRUE, tess);
sample->match_sample (sample_it.data (), TRUE, tess);
}
sample_it.add_to_end (sample);
if (tessedit_mm_use_prototypes && type == IMAGE_CLUSTER) {
if (samples.length () == tessedit_mm_prototype_min_size)
this->build_prototype ();
else if (samples.length () > tessedit_mm_prototype_min_size)
this->add_sample_to_prototype (sample);
}
}
void CHAR_SAMPLES::add_sample_to_prototype(CHAR_SAMPLE *sample) {
BOOL8 rebuild = FALSE;
inT32 new_xsize = proto->x_size ();
inT32 new_ysize = proto->y_size ();
inT32 sample_xsize = sample->image ()->get_xsize ();
inT32 sample_ysize = sample->image ()->get_ysize ();
if (sample_xsize > new_xsize) {
new_xsize = sample_xsize;
rebuild = TRUE;
}
if (sample_ysize > new_ysize) {
new_ysize = sample_ysize;
rebuild = TRUE;
}
if (rebuild)
proto->enlarge_prototype (new_xsize, new_ysize);
proto->add_sample (sample);
}
void CHAR_SAMPLES::build_prototype() {
CHAR_SAMPLE_IT sample_it = &samples;
CHAR_SAMPLE *sample;
inT32 proto_xsize = 0;
inT32 proto_ysize = 0;
if (type != IMAGE_CLUSTER
|| samples.length () < tessedit_mm_prototype_min_size)
return;
for (sample_it.mark_cycle_pt ();
!sample_it.cycled_list (); sample_it.forward ()) {
sample = sample_it.data ();
if (sample->image ()->get_xsize () > proto_xsize)
proto_xsize = sample->image ()->get_xsize ();
if (sample->image ()->get_ysize () > proto_ysize)
proto_ysize = sample->image ()->get_ysize ();
}
proto = new CHAR_PROTO (proto_xsize, proto_ysize, 0, 0, '\0');
for (sample_it.mark_cycle_pt ();
!sample_it.cycled_list (); sample_it.forward ())
this->add_sample_to_prototype (sample_it.data ());
}
void CHAR_SAMPLES::find_best_sample() {
CHAR_SAMPLE_IT sample_it = &samples;
double score;
double best_score = MAX_INT32;
if (ch == '\0' || samples.length () < tessedit_mm_prototype_min_size)
return;
for (sample_it.mark_cycle_pt ();
!sample_it.cycled_list (); sample_it.forward ()) {
score = sample_it.data ()->mean_score ();
if (score < best_score) {
best_score = score;
best_sample = sample_it.data ();
}
}
#ifndef SECURE_NAMES
if (tessedit_cluster_debug) {
tprintf ("Best sample for this %c cluster:\n", ch);
best_sample->print (debug_fp);
}
#endif
}
float CHAR_SAMPLES::match_score(CHAR_SAMPLE *sample,
tesseract::Tesseract* tess) {
if (tessedit_mm_only_match_same_char && sample->character () != ch)
return BAD_SCORE;
if (tessedit_use_best_sample && best_sample != NULL)
return best_sample->match_sample (sample, FALSE, tess);
else if ((tessedit_mm_use_prototypes
|| tessedit_mm_adapt_using_prototypes) && proto != NULL)
return proto->match_sample (sample);
else
return this->nn_match_score (sample, tess);
}
float CHAR_SAMPLES::nn_match_score(CHAR_SAMPLE *sample,
tesseract::Tesseract* tess) {
CHAR_SAMPLE_IT sample_it = &samples;
float score;
float min_score = MAX_INT32;
for (sample_it.mark_cycle_pt ();
!sample_it.cycled_list (); sample_it.forward ()) {
score = sample_it.data ()->match_sample (sample, FALSE, tess);
if (score < min_score)
min_score = score;
}
return min_score;
}
void CHAR_SAMPLES::assign_to_char() {
STATS char_frequency(FIRST_CHAR, LAST_CHAR);
CHAR_SAMPLE_IT sample_it = &samples;
inT32 i;
inT32 max_index = 0;
inT32 max_freq = 0;
if (samples.length () == 0 || tessedit_mm_only_match_same_char)
return;
for (sample_it.mark_cycle_pt ();
!sample_it.cycled_list (); sample_it.forward ())
char_frequency.add ((inT32) sample_it.data ()->character (), 1);
for (i = FIRST_CHAR; i <= LAST_CHAR; i++)
if (char_frequency.pile_count (i) > max_freq) {
max_index = i;
max_freq = char_frequency.pile_count (i);
}
if (samples.length () >= tessedit_cluster_min_size
&& max_freq > samples.length () * tessedit_cluster_accept_fraction)
ch = (char) max_index;
}
void CHAR_SAMPLES::print(FILE *f) {
CHAR_SAMPLE_IT sample_it = &samples;
fprintf (f, "Collected " INT32FORMAT " samples\n", samples.length ());
#ifndef SECURE_NAMES
if (tessedit_cluster_debug)
for (sample_it.mark_cycle_pt ();
!sample_it.cycled_list (); sample_it.forward ())
sample_it.data ()->print (f);
if (ch == '\0')
fprintf (f, "\nCluster not used for adaption\n");
else
fprintf (f, "\nCluster used to adapt to '%c's\n", ch);
#endif
}
CHAR_PROTO::CHAR_PROTO() {
xsize = 0;
ysize = 0;
ch = '\0';
nsamples = 0;
proto_data = NULL;
proto = NULL;
}
CHAR_PROTO::CHAR_PROTO(inT32 x_size,
inT32 y_size,
inT32 n_samples,
float initial_value,
char c) {
inT32 x;
inT32 y;
xsize = x_size;
ysize = y_size;
ch = c;
nsamples = n_samples;
ALLOC_2D_ARRAY(xsize, ysize, proto_data, proto, float);
for (y = 0; y < ysize; y++)
for (x = 0; x < xsize; x++)
proto[x][y] = initial_value;
}
CHAR_PROTO::CHAR_PROTO(CHAR_SAMPLE *sample) {
inT32 x;
inT32 y;
IMAGELINE imline_s;
if (sample->image () == NULL) {
xsize = 0;
ysize = 0;
ch = '\0';
nsamples = 0;
proto_data = NULL;
proto = NULL;
}
else {
ch = sample->character ();
xsize = sample->image ()->get_xsize ();
ysize = sample->image ()->get_ysize ();
nsamples = 1;
ALLOC_2D_ARRAY(xsize, ysize, proto_data, proto, float);
for (y = 0; y < ysize; y++) {
sample->image ()->fast_get_line (0, y, xsize, &imline_s);
for (x = 0; x < xsize; x++)
if (imline_s.pixels[x] == BINIM_WHITE)
proto[x][y] = 1.0;
else
proto[x][y] = -1.0;
}
}
}
CHAR_PROTO::~CHAR_PROTO () {
if (proto_data != NULL)
FREE_2D_ARRAY(proto_data, proto);
}
float CHAR_PROTO::match_sample(CHAR_SAMPLE *test_sample) {
CHAR_PROTO *test_proto;
float score;
if (test_sample->image () != NULL) {
test_proto = new CHAR_PROTO (test_sample);
if (xsize > test_proto->x_size ())
score = this->match (test_proto);
else {
demo_word = -demo_word; // Flag different call
score = test_proto->match (this);
}
}
else
return BAD_SCORE;
delete test_proto;
return score;
}
float CHAR_PROTO::match(CHAR_PROTO *test_proto) {
inT32 xsize2 = test_proto->x_size ();
inT32 y_size;
inT32 y_size2;
inT32 x_offset;
inT32 y_offset;
inT32 x;
inT32 y;
CHAR_PROTO *match_proto;
float score;
float sum = 0.0;
ASSERT_HOST (xsize >= xsize2);
x_offset = (xsize - xsize2) / 2;
if (ysize < test_proto->y_size ()) {
y_size = test_proto->y_size ();
y_size2 = ysize;
y_offset = (y_size - y_size2) / 2;
match_proto = new CHAR_PROTO (xsize,
y_size,
nsamples * test_proto->n_samples (),
0, '\0');
for (y = 0; y < y_offset; y++) {
for (x = 0; x < xsize2; x++) {
match_proto->data ()[x + x_offset][y] =
test_proto->data ()[x][y] * nsamples;
sum += match_proto->data ()[x + x_offset][y];
}
}
for (y = y_offset + y_size2; y < y_size; y++) {
for (x = 0; x < xsize2; x++) {
match_proto->data ()[x + x_offset][y] =
test_proto->data ()[x][y] * nsamples;
sum += match_proto->data ()[x + x_offset][y];
}
}
for (y = y_offset; y < y_offset + y_size2; y++) {
for (x = 0; x < x_offset; x++) {
match_proto->data ()[x][y] = proto[x][y - y_offset] *
test_proto->n_samples ();
sum += match_proto->data ()[x][y];
}
for (x = x_offset + xsize2; x < xsize; x++) {
match_proto->data ()[x][y] = proto[x][y - y_offset] *
test_proto->n_samples ();
sum += match_proto->data ()[x][y];
}
for (x = x_offset; x < x_offset + xsize2; x++) {
match_proto->data ()[x][y] =
proto[x][y - y_offset] * test_proto->data ()[x - x_offset][y];
sum += match_proto->data ()[x][y];
}
}
}
else {
y_size = ysize;
y_size2 = test_proto->y_size ();
y_offset = (y_size - y_size2) / 2;
match_proto = new CHAR_PROTO (xsize,
y_size,
nsamples * test_proto->n_samples (),
0, '\0');
for (y = 0; y < y_offset; y++)
for (x = 0; x < xsize; x++) {
match_proto->data ()[x][y] =
proto[x][y] * test_proto->n_samples ();
sum += match_proto->data ()[x][y];
}
for (y = y_offset + y_size2; y < y_size; y++)
for (x = 0; x < xsize; x++) {
match_proto->data ()[x][y] =
proto[x][y] * test_proto->n_samples ();
sum += match_proto->data ()[x][y];
}
for (y = y_offset; y < y_offset + y_size2; y++) {
for (x = 0; x < x_offset; x++) {
match_proto->data ()[x][y] =
proto[x][y] * test_proto->n_samples ();
sum += match_proto->data ()[x][y];
}
for (x = x_offset + xsize2; x < xsize; x++) {
match_proto->data ()[x][y] =
proto[x][y] * test_proto->n_samples ();
sum += match_proto->data ()[x][y];
}
for (x = x_offset; x < x_offset + xsize2; x++) {
match_proto->data ()[x][y] = proto[x][y] *
test_proto->data ()[x - x_offset][y - y_offset];
sum += match_proto->data ()[x][y];
}
}
}
score = (1.0 - sum /
(xsize * y_size * nsamples * test_proto->n_samples ()));
if (tessedit_mm_debug) {
if (score < 0) {
tprintf ("Match score %f\n", score);
tprintf ("x: %d, y: %d, ns: %d, nt: %d, dx %d, dy: %d\n",
xsize, y_size, nsamples, test_proto->n_samples (),
x_offset, y_offset);
for (y = 0; y < y_size; y++) {
tprintf ("\n%d", y);
for (x = 0; x < xsize; x++)
tprintf ("\t%d", match_proto->data ()[x][y]);
}
tprintf ("\n");
fflush(debug_fp);
}
}
#ifndef GRAPHICS_DISABLED
if (tessedit_display_mm) {
tprintf ("Match score %f\n", score);
display_images (this->make_image (),
test_proto->make_image (), match_proto->make_image ());
}
else if (demo_word != 0) {
if (demo_word > 0)
display_image (test_proto->make_image (), "Test sample",
300, 400, FALSE);
else
display_image (this->make_image (), "Test sample", 300, 400, FALSE);
display_image (match_proto->make_image (), "Best match",
700, 400, TRUE);
}
#endif
delete match_proto;
return score;
}
void CHAR_PROTO::enlarge_prototype(inT32 new_xsize, inT32 new_ysize) {
float *old_proto_data = proto_data;
float **old_proto = proto;
inT32 old_xsize = xsize;
inT32 old_ysize = ysize;
inT32 x_offset;
inT32 y_offset;
inT32 x;
inT32 y;
ASSERT_HOST (new_xsize >= xsize && new_ysize >= ysize);
xsize = new_xsize;
ysize = new_ysize;
ALLOC_2D_ARRAY(xsize, ysize, proto_data, proto, float);
x_offset = (xsize - old_xsize) / 2;
y_offset = (ysize - old_ysize) / 2;
for (y = 0; y < y_offset; y++)
for (x = 0; x < xsize; x++)
proto[x][y] = nsamples;
for (y = y_offset + old_ysize; y < ysize; y++)
for (x = 0; x < xsize; x++)
proto[x][y] = nsamples;
for (y = y_offset; y < y_offset + old_ysize; y++) {
for (x = 0; x < x_offset; x++)
proto[x][y] = nsamples;
for (x = x_offset + old_xsize; x < xsize; x++)
proto[x][y] = nsamples;
for (x = x_offset; x < x_offset + old_xsize; x++)
proto[x][y] = old_proto[x - x_offset][y - y_offset];
}
FREE_2D_ARRAY(old_proto_data, old_proto);
}
void CHAR_PROTO::add_sample(CHAR_SAMPLE *sample) {
inT32 x_offset;
inT32 y_offset;
inT32 x;
inT32 y;
IMAGELINE imline_s;
inT32 sample_xsize = sample->image ()->get_xsize ();
inT32 sample_ysize = sample->image ()->get_ysize ();
x_offset = (xsize - sample_xsize) / 2;
y_offset = (ysize - sample_ysize) / 2;
ASSERT_HOST (x_offset >= 0 && y_offset >= 0);
for (y = 0; y < y_offset; y++)
for (x = 0; x < xsize; x++)
proto[x][y]++; // Treat pixels outside the
// range as white
for (y = y_offset + sample_ysize; y < ysize; y++)
for (x = 0; x < xsize; x++)
proto[x][y]++;
for (y = y_offset; y < y_offset + sample_ysize; y++) {
sample->image ()->fast_get_line (0,
y - y_offset, sample_xsize, &imline_s);
for (x = x_offset; x < x_offset + sample_xsize; x++) {
if (imline_s.pixels[x - x_offset] == BINIM_WHITE)
proto[x][y]++;
else
proto[x][y]--;
}
for (x = 0; x < x_offset; x++)
proto[x][y]++;
for (x = x_offset + sample_xsize; x < xsize; x++)
proto[x][y]++;
}
nsamples++;
}
IMAGE *CHAR_PROTO::make_image() {
IMAGE *image;
IMAGELINE imline_p;
inT32 x;
inT32 y;
ASSERT_HOST (nsamples != 0);
image = new (IMAGE);
image->create (xsize, ysize, 8);
for (y = 0; y < ysize; y++) {
image->fast_get_line (0, y, xsize, &imline_p);
for (x = 0; x < xsize; x++) {
imline_p.pixels[x] = 128 +
(uinT8) ((proto[x][y] * 128.0) / (0.00001 + nsamples));
}
image->fast_put_line (0, y, xsize, &imline_p);
}
return image;
}

View File

@ -1,214 +0,0 @@
/**********************************************************************
* File: charsample.h (Formerly charsample.h)
* Description: Class to contain character samples and match scores
* to be used for adaption
* Author: Chris Newton
* Created: Thu Oct 7 13:40:37 BST 1993
*
* (C) Copyright 1993, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
#ifndef CHARSAMPLE_H
#define CHARSAMPLE_H
#include "elst.h"
#include "pageres.h"
#include "memry.h"
#include "notdll.h"
#define BAD_SCORE MAX_INT32
#define FIRST_CHAR '!'
#define LAST_CHAR '~'
namespace tesseract {
class Tesseract; // Fwd decl.
}
enum ClusterType
{ UNKNOWN, BLOB_CLUSTER, IMAGE_CLUSTER };
class CHAR_SAMPLE; //forward decl
ELISTIZEH (CHAR_SAMPLE)
class CHAR_SAMPLES; //forward decl
ELISTIZEH (CHAR_SAMPLES)
class CHAR_PROTO; //forward decl
class CHAR_SAMPLE:public ELIST_LINK
{
public:
CHAR_SAMPLE(); // empty constructor
CHAR_SAMPLE( // simple constructor
PBLOB *blob,
DENORM *denorm,
char c
);
CHAR_SAMPLE( // simple constructor
IMAGE *image,
char c
);
~CHAR_SAMPLE () {
// We own the image, so it has to be deleted.
if (sample_image != NULL)
delete sample_image;
}
float match_sample(CHAR_SAMPLE *test_sample, BOOL8 updating,
tesseract::Tesseract* tess);
inT32 n_matches() {
return n_samples_matched;
}
IMAGE *image() {
return sample_image;
}
PBLOB *blob() {
return sample_blob;
}
DENORM *denorm() {
return sample_denorm;
}
double mean_score();
double variance();
char character() {
return ch;
}
void print(FILE *f);
void reset_match_statistics();
NEWDELETE2 (CHAR_SAMPLE) private:
IMAGE * sample_image;
PBLOB *sample_blob;
DENORM *sample_denorm;
inT32 n_samples_matched;
double total_match_scores;
double sumsq_match_scores;
char ch;
};
class CHAR_SAMPLES:public ELIST_LINK
{
public:
CHAR_SAMPLES(); //empty constructor
CHAR_SAMPLES(CHAR_SAMPLE *sample);
~CHAR_SAMPLES () { //destructor
}
inT32 n_samples() {
return samples.length ();
}
void add_sample(CHAR_SAMPLE *sample, tesseract::Tesseract*);
void build_prototype();
void rebuild_prototype(inT32 new_xsize, inT32 new_ysize);
void add_sample_to_prototype(CHAR_SAMPLE *sample);
CHAR_PROTO *prototype() {
return proto;
}
void find_best_sample();
float match_score(CHAR_SAMPLE *sample, tesseract::Tesseract* tess);
float nn_match_score(CHAR_SAMPLE *sample, tesseract::Tesseract* tess);
char character() {
return ch;
}
void assign_to_char();
void print(FILE *f);
NEWDELETE2 (CHAR_SAMPLES) private:
ClusterType type;
char ch;
CHAR_PROTO *proto;
CHAR_SAMPLE *best_sample;
CHAR_SAMPLE_LIST samples;
};
class CHAR_PROTO
{
public:
CHAR_PROTO(); // empty constructor
CHAR_PROTO(inT32 x_size,
inT32 y_size,
inT32 n_samples,
float initial_value,
char c);
CHAR_PROTO( // simple constructor
CHAR_SAMPLE *sample);
~CHAR_PROTO ();
float match_sample(CHAR_SAMPLE *test_sample);
float match(CHAR_PROTO *test_proto);
inT32 n_samples() {
return nsamples;
}
inT32 x_size() {
return xsize;
}
inT32 y_size() {
return ysize;
}
float **data() {
return proto;
}
char character() {
return ch;
}
void enlarge_prototype(inT32 new_xsize, inT32 new_ysize);
void add_sample(CHAR_SAMPLE *sample);
IMAGE *make_image();
void print(FILE *f);
NEWDELETE2 (CHAR_PROTO) private:
inT32 xsize;
inT32 ysize;
float *proto_data;
float **proto;
inT32 nsamples;
char ch;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@ -25,16 +25,11 @@
#ifndef CONTROL_H
#define CONTROL_H
#include "varable.h"
#include "params.h"
#include "ocrblock.h"
//#include "epapdest.h"
#include "ratngs.h"
#include "statistc.h"
//#include "epapconv.h"
#include "ocrshell.h"
#include "pageres.h"
//TODO (wanke) why does the app. path have to be so weird here?
#include "charsample.h"
#include "notdll.h"
enum ACCEPTABLE_WERD_TYPE
@ -49,129 +44,12 @@ enum ACCEPTABLE_WERD_TYPE
typedef BOOL8 (*BLOB_REJECTOR) (PBLOB *, BLOB_CHOICE_IT *, void *);
extern INT_VAR_H (tessedit_single_match, FALSE, "Top choice only from CP");
//extern BOOL_VAR_H(tessedit_small_match,FALSE,"Use small matrix matcher");
extern BOOL_VAR_H (tessedit_print_text, FALSE, "Write text to stdout");
extern BOOL_VAR_H (tessedit_draw_words, FALSE, "Draw source words");
extern BOOL_VAR_H (tessedit_draw_outwords, FALSE, "Draw output words");
extern BOOL_VAR_H (tessedit_training_wiseowl, FALSE,
"Call WO to learn blobs");
extern BOOL_VAR_H (tessedit_training_tess, FALSE, "Call Tess to learn blobs");
extern BOOL_VAR_H (tessedit_matcher_is_wiseowl, FALSE, "Call WO to classify");
extern BOOL_VAR_H (tessedit_dump_choices, FALSE, "Dump char choices");
extern BOOL_VAR_H (tessedit_fix_fuzzy_spaces, TRUE,
"Try to improve fuzzy spaces");
extern BOOL_VAR_H (tessedit_unrej_any_wd, FALSE,
"Dont bother with word plausibility");
extern BOOL_VAR_H (tessedit_fix_hyphens, TRUE, "Crunch double hyphens?");
extern BOOL_VAR_H (tessedit_reject_fullstops, FALSE, "Reject all fullstops");
extern BOOL_VAR_H (tessedit_reject_suspect_fullstops, FALSE,
"Reject suspect fullstops");
extern BOOL_VAR_H (tessedit_redo_xheight, TRUE, "Check/Correct x-height");
extern BOOL_VAR_H (tessedit_cluster_adaption_on, TRUE,
"Do our own adaption - ems only");
extern BOOL_VAR_H (tessedit_enable_doc_dict, TRUE,
"Add words to the document dictionary");
extern BOOL_VAR_H (word_occ_first, FALSE, "Do word occ before re-est xht");
extern BOOL_VAR_H (tessedit_xht_fiddles_on_done_wds, TRUE,
"Apply xht fix up even if done");
extern BOOL_VAR_H (tessedit_xht_fiddles_on_no_rej_wds, TRUE,
"Apply xht fix up even in no rejects");
extern INT_VAR_H (x_ht_check_word_occ, 2, "Check Char Block occupancy");
extern INT_VAR_H (x_ht_stringency, 1, "How many confirmed a/n to accept?");
extern BOOL_VAR_H (x_ht_quality_check, TRUE, "Dont allow worse quality");
extern BOOL_VAR_H (tessedit_debug_block_rejection, FALSE,
"Block and Row stats");
extern INT_VAR_H (debug_x_ht_level, 0, "Reestimate debug");
extern BOOL_VAR_H (rej_use_xht, TRUE, "Individual rejection control");
extern BOOL_VAR_H (debug_acceptable_wds, FALSE, "Dump word pass/fail chk");
extern STRING_VAR_H (chs_leading_punct, "('`\"", "Leading punctuation");
extern
STRING_VAR_H (chs_trailing_punct1, ").,;:?!", "1st Trailing punctuation");
extern STRING_VAR_H (chs_trailing_punct2, ")'`\"",
"2nd Trailing punctuation");
extern double_VAR_H (quality_rej_pc, 0.08,
"good_quality_doc lte rejection limit");
extern double_VAR_H (quality_blob_pc, 0.0,
"good_quality_doc gte good blobs limit");
extern double_VAR_H (quality_outline_pc, 1.0,
"good_quality_doc lte outline error limit");
extern double_VAR_H (quality_char_pc, 0.95,
"good_quality_doc gte good char limit");
extern INT_VAR_H (quality_min_initial_alphas_reqd, 2,
"alphas in a good word");
extern BOOL_VAR_H (tessedit_tess_adapt_to_rejmap, FALSE,
"Use reject map to control Tesseract adaption");
extern INT_VAR_H (tessedit_tess_adaption_mode, 3,
"Adaptation decision algorithm for tess");
extern INT_VAR_H (tessedit_em_adaption_mode, 62,
"Adaptation decision algorithm for ems matrix matcher");
extern BOOL_VAR_H (tessedit_cluster_adapt_after_pass1, FALSE,
"Adapt using clusterer after pass 1");
extern BOOL_VAR_H (tessedit_cluster_adapt_after_pass2, FALSE,
"Adapt using clusterer after pass 1");
extern BOOL_VAR_H (tessedit_cluster_adapt_after_pass3, FALSE,
"Adapt using clusterer after pass 1");
extern BOOL_VAR_H (tessedit_cluster_adapt_before_pass1, FALSE,
"Adapt using clusterer before Tess adaping during pass 1");
extern INT_VAR_H (tessedit_cluster_adaption_mode, 0,
"Adaptation decision algorithm for matrix matcher");
extern BOOL_VAR_H (tessedit_adaption_debug, FALSE,
"Generate and print debug information for adaption");
extern BOOL_VAR_H (tessedit_minimal_rej_pass1, FALSE,
"Do minimal rejection on pass 1 output");
extern BOOL_VAR_H (tessedit_test_adaption, FALSE,
"Test adaption criteria");
extern BOOL_VAR_H (tessedit_global_adaption, FALSE,
"Adapt to all docs over time");
extern BOOL_VAR_H (tessedit_matcher_log, FALSE, "Log matcher activity");
extern INT_VAR_H (tessedit_test_adaption_mode, 3,
"Adaptation decision algorithm for tess");
extern BOOL_VAR_H (test_pt, FALSE, "Test for point");
extern double_VAR_H (test_pt_x, 99999.99, "xcoord");
extern double_VAR_H (test_pt_y, 99999.99, "ycoord");
extern BOOL_VAR_H(save_best_choices, FALSE,
"Save the results of the recognition step"
" (blob_choices) within the corresponding WERD_CHOICE");
/*
void classify_word_pass1( //recog one word
WERD_RES *word, //word to do
ROW *row,
BOOL8 cluster_adapt,
CHAR_SAMPLES_LIST *char_clusters,
CHAR_SAMPLE_LIST *chars_waiting);
*/
//word to do
void classify_word_pass2(WERD_RES *word, ROW *row);
/**
* recognize one word
* @param word word to do
*/
void match_word_pass2(
WERD_RES *word,
ROW *row,
float x_height);
/**
* crunch double hyphens
* @param choice string to fix
* @param word word to do
* @param blob_choices char choices
*/
void fix_hyphens(
WERD_CHOICE *choice,
WERD *word,
BLOB_CHOICE_LIST_CLIST *blob_choices);
/**
* combine 2 blobs
* @param blob1 dest blob
* @param blob2 source blob
*/
void merge_blobs(
PBLOB *blob1,
PBLOB *blob2
);
void merge_blobs(PBLOB *blob1, PBLOB *blob2);
/** dump chars in word */
void choice_dump_tester(
PBLOB *, ///< blob
@ -181,20 +59,4 @@ void choice_dump_tester(
inT32 count, ///< chars in text
BLOB_CHOICE_LIST *ratings ///< list of results
);
WERD *make_bln_copy(WERD *src_word, ROW *row, BLOCK* block,
float x_height, DENORM *denorm);
BOOL8 check_debug_pt(WERD_RES *word, int location);
/** good chars in word */
void add_in_one_row(
ROW_RES *row, ///< current row
STATS *fonts, ///< font stats
inT8 *italic, ///< output count
inT8 *bold ///< output count
);
/** good chars in word */
void find_modal_font(
STATS *fonts, ///< font stats
inT8 *font_out, ///< output font
inT8 *font_count ///< output count
);
#endif

465
ccmain/cube_control.cpp Normal file
View File

@ -0,0 +1,465 @@
/******************************************************************
* File: cube_control.cpp
* Description: Tesseract class methods for invoking cube convolutional
* neural network word recognizer.
* Author: Raquel Romano
* Created: September 2009
*
**********************************************************************/
// Include automatically generated configuration file if running autoconf.
#ifdef HAVE_CONFIG_H
#include "config_auto.h"
#endif
#ifdef HAVE_LIBLEPT
// Include leptonica library only if autoconf (or makefile etc) tell us to.
#include "allheaders.h"
#endif
#include "cube_object.h"
#include "cube_reco_context.h"
#include "tesseractclass.h"
#include "tesseract_cube_combiner.h"
namespace tesseract {
/**********************************************************************
* convert_prob_to_tess_certainty
*
* Normalize a probability in the range [0.0, 1.0] to a tesseract
* certainty in the range [-20.0, 0.0]
**********************************************************************/
static float convert_prob_to_tess_certainty(float prob) {
return (prob - 1.0) * 20.0;
}
/**********************************************************************
* char_box_to_tbox
*
* Create a TBOX from a character bounding box. If nonzero, the
* x_offset accounts for any additional padding of the word box that
* should be taken into account.
*
**********************************************************************/
TBOX char_box_to_tbox(Box* char_box, TBOX word_box, int x_offset) {
l_int32 left;
l_int32 top;
l_int32 width;
l_int32 height;
l_int32 right;
l_int32 bottom;
boxGetGeometry(char_box, &left, &top, &width, &height);
left += word_box.left() - x_offset;
right = left + width;
top = word_box.bottom() + word_box.height() - top;
bottom = top - height;
return TBOX(left, bottom, right, top);
}
/**********************************************************************
* extract_cube_state
*
* Extract CharSamp objects and character bounding boxes from the
* CubeObject's state. The caller should free both structres.
*
**********************************************************************/
bool Tesseract::extract_cube_state(CubeObject* cube_obj,
int* num_chars,
Boxa** char_boxes,
CharSamp*** char_samples) {
if (!cube_obj) {
if (cube_debug_level > 0) {
tprintf("Cube WARNING (extract_cube_state): Invalid cube object "
"passed to extract_cube_state\n");
}
return false;
}
// Note that the CubeObject accessors return either the deslanted or
// regular objects search object or beam search object, whichever
// was used in the last call to Recognize()
CubeSearchObject* cube_search_obj = cube_obj->SrchObj();
if (!cube_search_obj) {
if (cube_debug_level > 0) {
tprintf("Cube WARNING (Extract_cube_state): Could not retrieve "
"cube's search object in extract_cube_state.\n");
}
return false;
}
BeamSearch *beam_search_obj = cube_obj->BeamObj();
if (!beam_search_obj) {
if (cube_debug_level > 0) {
tprintf("Cube WARNING (Extract_cube_state): Could not retrieve "
"cube's beam search object in extract_cube_state.\n");
}
return false;
}
// Get the character samples and bounding boxes by backtracking
// through the beam search path
int best_node_index = beam_search_obj->BestPresortedNodeIndex();
*char_samples = beam_search_obj->BackTrack(
cube_search_obj, best_node_index, num_chars, NULL, char_boxes);
if (!*char_samples)
return false;
return true;
}
/**********************************************************************
* create_cube_box_word
*
* Fill the given BoxWord with boxes from character bounding
* boxes. The char_boxes have local coordinates w.r.t. the
* word bounding box, i.e., the left-most character bbox of each word
* has (0,0) left-top coord, but the BoxWord must be defined in page
* coordinates.
**********************************************************************/
bool Tesseract::create_cube_box_word(Boxa *char_boxes,
int num_chars,
TBOX word_box,
BoxWord* box_word) {
if (!box_word) {
if (cube_debug_level > 0) {
tprintf("Cube WARNING (create_cube_box_word): Invalid box_word.\n");
}
return false;
}
// Find the x-coordinate of left-most char_box, which could be
// nonzero if the word image was padded before recognition took place.
int x_offset = -1;
for (int i = 0; i < num_chars; ++i) {
Box* char_box = boxaGetBox(char_boxes, i, L_CLONE);
if (x_offset < 0 || char_box->x < x_offset) {
x_offset = char_box->x;
}
boxDestroy(&char_box);
}
for (int i = 0; i < num_chars; ++i) {
Box* char_box = boxaGetBox(char_boxes, i, L_CLONE);
TBOX tbox = char_box_to_tbox(char_box, word_box, x_offset);
boxDestroy(&char_box);
box_word->InsertBox(i, tbox);
}
return true;
}
/**********************************************************************
* create_werd_choice
*
**********************************************************************/
static WERD_CHOICE *create_werd_choice(
CharSamp** char_samples,
int num_chars,
const char* str,
float certainty,
const UNICHARSET &unicharset,
CharSet* cube_char_set
) {
// Insert unichar ids into WERD_CHOICE
WERD_CHOICE *werd_choice = new WERD_CHOICE(num_chars);
ASSERT_HOST(werd_choice != NULL);
UNICHAR_ID uch_id;
for (int i = 0; i < num_chars; ++i) {
uch_id = cube_char_set->UnicharID(char_samples[i]->StrLabel());
if (uch_id != INVALID_UNICHAR_ID)
werd_choice->append_unichar_id_space_allocated(uch_id, 1, 0.0, certainty);
}
BLOB_CHOICE *blob_choice;
BLOB_CHOICE_LIST *choices_list;
BLOB_CHOICE_IT choices_list_it;
BLOB_CHOICE_LIST_CLIST *blob_choices = new BLOB_CHOICE_LIST_CLIST();
BLOB_CHOICE_LIST_C_IT blob_choices_it;
blob_choices_it.set_to_list(blob_choices);
for (int i = 0; i < werd_choice->length(); ++i) {
// Create new BLOB_CHOICE_LIST for this unichar
choices_list = new BLOB_CHOICE_LIST();
choices_list_it.set_to_list(choices_list);
// Add a single BLOB_CHOICE to the list
blob_choice = new BLOB_CHOICE(werd_choice->unichar_id(i),
0.0, certainty, -1, -1, 0);
choices_list_it.add_after_then_move(blob_choice);
// Add list to the clist
blob_choices_it.add_to_end(choices_list);
}
werd_choice->populate_unichars(unicharset);
werd_choice->set_certainty(certainty);
werd_choice->set_blob_choices(blob_choices);
return werd_choice;
}
/**********************************************************************
* init_cube_objects
*
* Instantitates Tesseract object's CubeRecoContext and TesseractCubeCombiner.
* Returns false if cube context could not be created or if load_combiner is
* true, but the combiner could not be loaded.
**********************************************************************/
bool Tesseract::init_cube_objects(bool load_combiner,
TessdataManager *tessdata_manager) {
ASSERT_HOST(cube_cntxt_ == NULL);
ASSERT_HOST(tess_cube_combiner_ == NULL);
// Create the cube context object
cube_cntxt_ = CubeRecoContext::Create(this, tessdata_manager, &unicharset);
if (cube_cntxt_ == NULL) {
if (cube_debug_level > 0) {
tprintf("Cube WARNING (Tesseract::init_cube_objects()): Failed to "
"instantiate CubeRecoContext\n");
}
return false;
}
// Create the combiner object and load the combiner net for target languages.
if (load_combiner) {
tess_cube_combiner_ = new tesseract::TesseractCubeCombiner(cube_cntxt_);
if (!tess_cube_combiner_ || !tess_cube_combiner_->LoadCombinerNet()) {
delete cube_cntxt_;
cube_cntxt_ = NULL;
if (tess_cube_combiner_ != NULL) {
delete tess_cube_combiner_;
tess_cube_combiner_ = NULL;
}
if (cube_debug_level > 0)
tprintf("Cube ERROR (Failed to instantiate TesseractCubeCombiner\n");
return false;
}
}
return true;
}
/**********************************************************************
* run_cube
*
* Iterate through tesseract's results and call cube on each word.
* If the combiner is present, optionally run the tesseract-cube
* combiner on each word.
**********************************************************************/
void Tesseract::run_cube(
PAGE_RES *page_res // page structure
) {
ASSERT_HOST(cube_cntxt_ != NULL);
if (!pix_binary_) {
if (cube_debug_level > 0)
tprintf("Tesseract::run_cube(): NULL binary image.\n");
return;
}
if (!page_res)
return;
PAGE_RES_IT page_res_it(page_res);
page_res_it.restart_page();
// Iterate through the word results and call cube on each word.
CubeObject *cube_obj;
for (page_res_it.restart_page(); page_res_it.word () != NULL;
page_res_it.forward()) {
WERD_RES* word = page_res_it.word();
TBOX word_box = word->word->bounding_box();
const BLOCK* block = word->denorm.block();
if (block != NULL && (block->re_rotation().x() != 1.0f ||
block->re_rotation().y() != 0.0f)) {
// TODO(rays) We have to rotate the bounding box to get the true coords.
// This will be achieved in the future via DENORM.
// In the mean time, cube can't process this word.
if (cube_debug_level > 0) {
tprintf("Cube can't process rotated word at:");
word_box.print();
}
if (word->best_choice == NULL)
page_res_it.DeleteCurrentWord(); // Nobody has an answer.
continue;
}
cube_obj = new tesseract::CubeObject(cube_cntxt_, pix_binary_,
word_box.left(),
pix_binary_->h - word_box.top(),
word_box.width(), word_box.height());
cube_recognize(cube_obj, &page_res_it);
delete cube_obj;
}
}
/**********************************************************************
* cube_recognize
*
* Call cube on the current word, optionally run the tess-cube combiner, and
* modify the tesseract result if cube wins. If cube fails to run, or
* if tesseract wins, leave the tesseract result unchanged. If the
* combiner is not instantiated, always use cube's result.
*
**********************************************************************/
void Tesseract::cube_recognize(
CubeObject *cube_obj,
PAGE_RES_IT *page_res_it
) {
// Retrieve tesseract's data structure for the current word.
WERD_RES *tess_werd_res = page_res_it->word();
if (!tess_werd_res->best_choice && tess_cube_combiner_ != NULL) {
if (cube_debug_level > 0)
tprintf("Cube WARNING (Tesseract::cube_recognize): Cannot run combiner "
"without a tess result.\n");
return;
}
// Skip cube entirely if combiner is present but tesseract's
// certainty is greater than threshold.
int combiner_run_thresh = convert_prob_to_tess_certainty(
cube_cntxt_->Params()->CombinerRunThresh());
if (tess_cube_combiner_ != NULL &&
(tess_werd_res->best_choice->certainty() >= combiner_run_thresh)) {
return;
}
// Run cube
WordAltList *cube_alt_list = cube_obj->RecognizeWord();
if (!cube_alt_list || cube_alt_list->AltCount() <= 0) {
if (cube_debug_level > 0) {
tprintf("Cube returned nothing for word at:");
tess_werd_res->word->bounding_box().print();
}
if (tess_werd_res->best_choice == NULL) {
// Nobody has recognized it, so pretend it doesn't exist.
if (cube_debug_level > 0) {
tprintf("Deleted word not recognized by cube and/or tesseract at:");
tess_werd_res->word->bounding_box().print();
}
page_res_it->DeleteCurrentWord();
}
return;
}
// At this point we *could* run the combiner and bail out if
// Tesseract wins, but that would require instantiating a new
// CubeObject to avoid losing the original recognition results
// (e.g., beam search lattice) stored with the CubeObject. Instead,
// we first extract the state we need from the current recognition
// and then reuse the CubeObject so that the combiner does not need
// to recompute the image's connected components, segmentation, etc.
// Get cube's best result and its probability, mapped to tesseract's
// certainty range
char_32 *cube_best_32 = cube_alt_list->Alt(0);
double cube_prob = CubeUtils::Cost2Prob(cube_alt_list->AltCost(0));
float cube_certainty = convert_prob_to_tess_certainty(cube_prob);
string cube_best_str;
CubeUtils::UTF32ToUTF8(cube_best_32, &cube_best_str);
// Retrieve Cube's character bounding boxes and CharSamples,
// corresponding to the most recent call to RecognizeWord().
Boxa *char_boxes = NULL;
CharSamp **char_samples = NULL;;
int num_chars;
if (!extract_cube_state(cube_obj, &num_chars, &char_boxes, &char_samples)
&& cube_debug_level > 0) {
tprintf("Cube WARNING (Tesseract::cube_recognize): Cannot extract "
"cube state.\n");
return;
}
// Convert cube's character bounding boxes to a BoxWord.
BoxWord cube_box_word;
TBOX tess_word_box = tess_werd_res->word->bounding_box();
if (tess_werd_res->denorm.block() != NULL)
tess_word_box.rotate(tess_werd_res->denorm.block()->re_rotation());
bool box_word_success = create_cube_box_word(char_boxes, num_chars,
tess_word_box,
&cube_box_word);
boxaDestroy(&char_boxes);
if (!box_word_success) {
if (cube_debug_level > 0) {
tprintf("Cube WARNING (Tesseract::cube_recognize): Could not "
"create cube BoxWord\n");
}
return;
}
// Create cube's best choice.
WERD_CHOICE* cube_werd_choice = create_werd_choice(
char_samples, num_chars, cube_best_str.c_str(), cube_certainty,
unicharset, cube_cntxt_->CharacterSet());
delete []char_samples;
if (!cube_werd_choice) {
if (cube_debug_level > 0) {
tprintf("Cube WARNING (Tesseract::cube_recognize): Could not "
"create cube WERD_CHOICE\n");
}
return;
}
// Run combiner if present, now that we're free to reuse the CubeObject.
if (tess_cube_combiner_ != NULL) {
float combiner_prob = tess_cube_combiner_->CombineResults(tess_werd_res,
cube_obj);
// If combiner probability is greater than tess/cube combiner
// classifier threshold, i.e. tesseract wins, then reset the WERD_RES
// certainty to the combiner certainty and return. Note that when
// tesseract and cube agree, the combiner probability is 1.0, so
// the final WERD_RES certainty will be maximized to 0.0.
if (combiner_prob >=
cube_cntxt_->Params()->CombinerClassifierThresh()) {
float combiner_certainty = convert_prob_to_tess_certainty(combiner_prob);
tess_werd_res->best_choice->set_certainty(combiner_certainty);
delete cube_werd_choice;
return;
}
if (cube_debug_level > 5) {
tprintf("Cube INFO: tesseract result replaced by cube: "
"%s -> %s\n",
tess_werd_res->best_choice->unichar_string().string(),
cube_best_str.c_str());
}
}
// Fill tesseract result's fields with cube results
fill_werd_res(cube_box_word, cube_werd_choice, cube_best_str.c_str(),
page_res_it);
}
/**********************************************************************
* fill_werd_res
*
* Fill Tesseract's word result fields with cube's.
*
**********************************************************************/
void Tesseract::fill_werd_res(const BoxWord& cube_box_word,
WERD_CHOICE* cube_werd_choice,
const char* cube_best_str,
PAGE_RES_IT *page_res_it) {
WERD_RES *tess_werd_res = page_res_it->word();
// Replace tesseract results's best choice with cube's
delete tess_werd_res->best_choice;
tess_werd_res->best_choice = cube_werd_choice;
delete tess_werd_res->box_word;
tess_werd_res->box_word = new BoxWord(cube_box_word);
tess_werd_res->box_word->ClipToOriginalWord(page_res_it->block()->block,
tess_werd_res->word);
// Fill text and remaining fields
tess_werd_res->word->set_text(cube_best_str);
tess_werd_res->tess_failed = FALSE;
tess_werd_res->tess_accepted =
tess_acceptable_word(tess_werd_res->best_choice,
tess_werd_res->raw_choice);
// There is no output word, so we can' call AdaptableWord, but then I don't
// think we need to. Fudge the result with accepted.
tess_werd_res->tess_would_adapt = tess_werd_res->tess_accepted;
// Initialize the reject_map and set it to done, i.e., ignore all of
// tesseract's tests for rejection
tess_werd_res->reject_map.initialise(cube_werd_choice->length());
tess_werd_res->done = tess_werd_res->tess_accepted;
// Some sanity checks
ASSERT_HOST(tess_werd_res->best_choice->length() ==
tess_werd_res->best_choice->blob_choices()->length());
ASSERT_HOST(tess_werd_res->best_choice->length() ==
tess_werd_res->reject_map.length());
}
} // namespace tesseract

View File

@ -0,0 +1,201 @@
/**********************************************************************
* File: cube_reco_context.cpp
* Description: Implementation of the Cube Recognition Context Class
* Author: Ahmad Abdulkader
* Created: 2007
*
* (C) Copyright 2008, Google Inc.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
#include <string>
#include <limits.h>
#include "cube_reco_context.h"
#include "classifier_factory.h"
#include "cube_tuning_params.h"
#include "dict.h"
#include "feature_bmp.h"
#include "tessdatamanager.h"
#include "tesseractclass.h"
#include "tess_lang_model.h"
namespace tesseract {
// Instantiate a CubeRecoContext object using a Tesseract object.
// CubeRecoContext will not take ownership of tess_obj, but will
// record the pointer to it and will make use of various Tesseract
// components (language model, flags, etc). Thus the caller should
// keep tess_obj alive so long as the instantiated CubeRecoContext is used.
CubeRecoContext::CubeRecoContext(Tesseract *tess_obj) {
tess_obj_ = tess_obj;
lang_ = "";
loaded_ = false;
lang_mod_ = NULL;
params_ = NULL;
char_classifier_ = NULL;
char_set_ = NULL;
word_size_model_ = NULL;
char_bigrams_ = NULL;
word_unigrams_ = NULL;
noisy_input_ = false;
size_normalization_ = false;
}
CubeRecoContext::~CubeRecoContext() {
if (char_classifier_ != NULL) {
delete char_classifier_;
char_classifier_ = NULL;
}
if (word_size_model_ != NULL) {
delete word_size_model_;
word_size_model_ = NULL;
}
if (char_set_ != NULL) {
delete char_set_;
char_set_ = NULL;
}
if (char_bigrams_ != NULL) {
delete char_bigrams_;
char_bigrams_ = NULL;
}
if (word_unigrams_ != NULL) {
delete word_unigrams_;
word_unigrams_ = NULL;
}
if (lang_mod_ != NULL) {
delete lang_mod_;
lang_mod_ = NULL;
}
if (params_ != NULL) {
delete params_;
params_ = NULL;
}
}
// Returns the path of the data files by looking up the TESSDATA_PREFIX
// environment variable and appending a "tessdata" directory to it
bool CubeRecoContext::GetDataFilePath(string *path) const {
*path = tess_obj_->datadir.string();
return true;
}
// The object initialization function that loads all the necessary
// components of a RecoContext. TessdataManager is used to load the
// data from [lang].traineddata file. If TESSDATA_CUBE_UNICHARSET
// component is present, Cube will be instantiated with the unicharset
// specified in this component and the corresponding dictionary
// (TESSDATA_CUBE_SYSTEM_DAWG), and will map Cube's unicharset to
// Tesseract's. Otherwise, TessdataManager will assume that Cube will
// be using Tesseract's unicharset and dawgs, and will load the
// unicharset from the TESSDATA_UNICHARSET component and will load the
// dawgs from TESSDATA_*_DAWG components.
bool CubeRecoContext::Load(TessdataManager *tessdata_manager,
UNICHARSET *tess_unicharset) {
ASSERT_HOST(tess_obj_ != NULL);
string data_file_path;
// Get the data file path.
if (GetDataFilePath(&data_file_path) == false) {
fprintf(stderr, "Unable to get data file path\n");
return false;
}
// Get the language from the Tesseract object.
lang_ = tess_obj_->lang.string();
// Create the char set.
if ((char_set_ =
CharSet::Create(tessdata_manager, tess_unicharset)) == NULL) {
fprintf(stderr, "Cube ERROR (CubeRecoContext::Load): unable to load "
"CharSet\n");
return false;
}
// Create the language model.
string lm_file_name = data_file_path + lang_ + ".cube.lm";
string lm_params;
if (!CubeUtils::ReadFileToString(lm_file_name, &lm_params)) {
fprintf(stderr, "Cube ERROR (CubeRecoContext::Load): unable to read cube "
"language model params from %s\n", lm_file_name.c_str());
return false;
}
lang_mod_ = new TessLangModel(lm_params, data_file_path,
tess_obj_->getDict().load_system_dawg,
tessdata_manager, this);
if (lang_mod_ == NULL) {
fprintf(stderr, "Cube ERROR (CubeRecoContext::Load): unable to create "
"TessLangModel\n");
return false;
}
// Create the optional char bigrams object.
char_bigrams_ = CharBigrams::Create(data_file_path, lang_);
// Create the optional word unigrams object.
word_unigrams_ = WordUnigrams::Create(data_file_path, lang_);
// Create the optional size model.
word_size_model_ = WordSizeModel::Create(data_file_path, lang_,
char_set_, Contextual());
// Load tuning params.
params_ = CubeTuningParams::Create(data_file_path, lang_);
if (params_ == NULL) {
fprintf(stderr, "Cube ERROR (CubeRecoContext::Load): unable to read "
"CubeTuningParams from %s\n", data_file_path.c_str());
return false;
}
// Create the char classifier.
char_classifier_ = CharClassifierFactory::Create(data_file_path, lang_,
lang_mod_, char_set_,
params_);
if (char_classifier_ == NULL) {
fprintf(stderr, "Cube ERROR (CubeRecoContext::Load): unable to load "
"CharClassifierFactory object from %s\n", data_file_path.c_str());
return false;
}
loaded_ = true;
return true;
}
// Creates a CubeRecoContext object using a tesseract object
CubeRecoContext * CubeRecoContext::Create(Tesseract *tess_obj,
TessdataManager *tessdata_manager,
UNICHARSET *tess_unicharset) {
// create the object
CubeRecoContext *cntxt = new CubeRecoContext(tess_obj);
if (cntxt == NULL) {
fprintf(stderr, "Cube ERROR (CubeRecoContext::Create): unable to create "
"CubeRecoContext object\n");
return NULL;
}
// load the necessary components
if (cntxt->Load(tessdata_manager, tess_unicharset) == false) {
fprintf(stderr, "Cube ERROR (CubeRecoContext::Create): unable to init "
"CubeRecoContext object\n");
delete cntxt;
return NULL;
}
// success
return cntxt;
}
} // tesseract}

155
ccmain/cube_reco_context.h Normal file
View File

@ -0,0 +1,155 @@
/**********************************************************************
* File: cube_reco_context.h
* Description: Declaration of the Cube Recognition Context Class
* Author: Ahmad Abdulkader
* Created: 2007
*
* (C) Copyright 2008, Google Inc.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
// The CubeRecoContext class abstracts the Cube OCR Engine. Typically a process
// (or a thread) would create one CubeRecoContext object per language.
// The CubeRecoContext object also provides methods to get and set the
// different attribues of the Cube OCR Engine.
#ifndef CUBE_RECO_CONTEXT_H
#define CUBE_RECO_CONTEXT_H
#include <string>
#include "neural_net.h"
#include "lang_model.h"
#include "classifier_base.h"
#include "feature_base.h"
#include "char_set.h"
#include "word_size_model.h"
#include "char_bigrams.h"
#include "word_unigrams.h"
namespace tesseract {
class Tesseract;
class TessdataManager;
class CubeRecoContext {
public:
// Reading order enum type
enum ReadOrder {
L2R,
R2L
};
// Instantiate using a Tesseract object
CubeRecoContext(Tesseract *tess_obj);
~CubeRecoContext();
// accessor functions
inline const string & Lang() const { return lang_; }
inline CharSet *CharacterSet() const { return char_set_; }
inline CharClassifier *Classifier() const { return char_classifier_; }
inline WordSizeModel *SizeModel() const { return word_size_model_; }
inline CharBigrams *Bigrams() const { return char_bigrams_; }
inline WordUnigrams *WordUnigramsObj() const { return word_unigrams_; }
inline TuningParams *Params() const { return params_; }
inline LangModel *LangMod() const { return lang_mod_; }
// the reading order of the language
inline ReadOrder ReadingOrder() const {
return ((lang_ == "ara") ? R2L : L2R);
}
// does the language support case
inline bool HasCase() const {
return (lang_ != "ara" && lang_ != "hin");
}
inline bool Cursive() const {
return (lang_ == "ara");
}
inline bool HasItalics() const {
return (lang_ != "ara" && lang_ != "hin" && lang_ != "uk");
}
inline bool Contextual() const {
return (lang_ == "ara");
}
// RecoContext runtime flags accessor functions
inline bool SizeNormalization() const { return size_normalization_; }
inline bool NoisyInput() const { return noisy_input_; }
inline bool OOD() const { return lang_mod_->OOD(); }
inline bool Numeric() const { return lang_mod_->Numeric(); }
inline bool WordList() const { return lang_mod_->WordList(); }
inline bool Punc() const { return lang_mod_->Punc(); }
inline bool CaseSensitive() const {
return char_classifier_->CaseSensitive();
}
inline void SetSizeNormalization(bool size_normalization) {
size_normalization_ = size_normalization;
}
inline void SetNoisyInput(bool noisy_input) {
noisy_input_ = noisy_input;
}
inline void SetOOD(bool ood_enabled) {
lang_mod_->SetOOD(ood_enabled);
}
inline void SetNumeric(bool numeric_enabled) {
lang_mod_->SetNumeric(numeric_enabled);
}
inline void SetWordList(bool word_list_enabled) {
lang_mod_->SetWordList(word_list_enabled);
}
inline void SetPunc(bool punc_enabled) {
lang_mod_->SetPunc(punc_enabled);
}
inline void SetCaseSensitive(bool case_sensitive) {
char_classifier_->SetCaseSensitive(case_sensitive);
}
inline tesseract::Tesseract *TesseractObject() const {
return tess_obj_;
}
// Returns the path of the data files
bool GetDataFilePath(string *path) const;
// Creates a CubeRecoContext object using a tesseract object. Data
// files are loaded via the tessdata_manager, and the tesseract
// unicharset is provided in order to map Cube's unicharset to
// Tesseract's in the case where the two unicharsets differ.
static CubeRecoContext *Create(Tesseract *tess_obj,
TessdataManager *tessdata_manager,
UNICHARSET *tess_unicharset);
private:
bool loaded_;
string lang_;
CharSet *char_set_;
WordSizeModel *word_size_model_;
CharClassifier *char_classifier_;
CharBigrams *char_bigrams_;
WordUnigrams *word_unigrams_;
TuningParams *params_;
LangModel *lang_mod_;
Tesseract *tess_obj_; // CubeRecoContext does not own this pointer
bool size_normalization_;
bool noisy_input_;
// Loads and initialized all the necessary components of a
// CubeRecoContext. See .cpp for more details.
bool Load(TessdataManager *tessdata_manager,
UNICHARSET *tess_unicharset);
};
}
#endif // CUBE_RECO_CONTEXT_H

View File

@ -27,397 +27,109 @@
#include "tstruct.h"
#include "tfacep.h"
#include "reject.h"
#include "tesscallback.h"
#include "tessvars.h"
#include "genblob.h"
#include "secname.h"
#include "globals.h"
#include "tesseractclass.h"
#define EXTERN
namespace tesseract{
EXTERN STRING_VAR (outlines_odd, "%| ", "Non standard number of outlines");
EXTERN STRING_VAR (outlines_2, "ij!?%\":;",
"Non standard number of outlines");
EXTERN BOOL_VAR (docqual_excuse_outline_errs, FALSE,
"Allow outline errs in unrejection?");
EXTERN BOOL_VAR (tessedit_good_quality_unrej, TRUE,
"Reduce rejection on good docs");
EXTERN BOOL_VAR (tessedit_use_reject_spaces, TRUE, "Reject spaces?");
EXTERN double_VAR (tessedit_reject_doc_percent, 65.00,
"%rej allowed before rej whole doc");
EXTERN double_VAR (tessedit_reject_block_percent, 45.00,
"%rej allowed before rej whole block");
EXTERN double_VAR (tessedit_reject_row_percent, 40.00,
"%rej allowed before rej whole row");
EXTERN double_VAR (tessedit_whole_wd_rej_row_percent, 70.00,
"%of row rejects in whole word rejects which prevents whole row rejection");
EXTERN BOOL_VAR (tessedit_preserve_blk_rej_perfect_wds, TRUE,
"Only rej partially rejected words in block rejection");
EXTERN BOOL_VAR (tessedit_preserve_row_rej_perfect_wds, TRUE,
"Only rej partially rejected words in row rejection");
EXTERN BOOL_VAR (tessedit_dont_blkrej_good_wds, FALSE,
"Use word segmentation quality metric");
EXTERN BOOL_VAR (tessedit_dont_rowrej_good_wds, FALSE,
"Use word segmentation quality metric");
EXTERN INT_VAR (tessedit_preserve_min_wd_len, 2,
"Only preserve wds longer than this");
EXTERN BOOL_VAR (tessedit_row_rej_good_docs, TRUE,
"Apply row rejection to good docs");
EXTERN double_VAR (tessedit_good_doc_still_rowrej_wd, 1.1,
"rej good doc wd if more than this fraction rejected");
EXTERN BOOL_VAR (tessedit_reject_bad_qual_wds, TRUE,
"Reject all bad quality wds");
EXTERN BOOL_VAR (tessedit_debug_doc_rejection, FALSE, "Page stats");
EXTERN BOOL_VAR (tessedit_debug_quality_metrics, FALSE,
"Output data to debug file");
EXTERN BOOL_VAR (bland_unrej, FALSE, "unrej potential with no checks");
EXTERN double_VAR (quality_rowrej_pc, 1.1,
"good_quality_doc gte good char limit");
// A little class to provide the callbacks as we have no pre-bound args.
struct DocQualCallbacks {
explicit DocQualCallbacks(WERD_RES* word0)
: word(word0), match_count(0), accepted_match_count(0) {}
EXTERN BOOL_VAR (unlv_tilde_crunching, TRUE,
"Mark v.bad words for tilde crunch");
EXTERN BOOL_VAR (crunch_early_merge_tess_fails, TRUE, "Before word crunch?");
EXTERN BOOL_EVAR (crunch_early_convert_bad_unlv_chs, FALSE,
"Take out ~^ early?");
void CountMatchingBlobs(int index) {
++match_count;
}
EXTERN double_VAR (crunch_terrible_rating, 80.0, "crunch rating lt this");
EXTERN BOOL_VAR (crunch_terrible_garbage, TRUE, "As it says");
EXTERN double_VAR (crunch_poor_garbage_cert, -9.0,
"crunch garbage cert lt this");
EXTERN double_VAR (crunch_poor_garbage_rate, 60,
"crunch garbage rating lt this");
void CountAcceptedBlobs(int index) {
if (word->reject_map[index].accepted())
++accepted_match_count;
++match_count;
}
EXTERN double_VAR (crunch_pot_poor_rate, 40,
"POTENTIAL crunch rating lt this");
EXTERN double_VAR (crunch_pot_poor_cert, -8.0,
"POTENTIAL crunch cert lt this");
EXTERN BOOL_VAR (crunch_pot_garbage, TRUE, "POTENTIAL crunch garbage");
void AcceptIfGoodQuality(int index) {
if (word->reject_map[index].accept_if_good_quality())
word->reject_map[index].setrej_quality_accept();
}
EXTERN double_VAR (crunch_del_rating, 60, "POTENTIAL crunch rating lt this");
EXTERN double_VAR (crunch_del_cert, -10.0, "POTENTIAL crunch cert lt this");
EXTERN double_VAR (crunch_del_min_ht, 0.7, "Del if word ht lt xht x this");
EXTERN double_VAR (crunch_del_max_ht, 3.0, "Del if word ht gt xht x this");
EXTERN double_VAR (crunch_del_min_width, 3.0,
"Del if word width lt xht x this");
EXTERN double_VAR (crunch_del_high_word, 1.5,
"Del if word gt xht x this above bl");
EXTERN double_VAR (crunch_del_low_word, 0.5,
"Del if word gt xht x this below bl");
EXTERN double_VAR (crunch_small_outlines_size, 0.6, "Small if lt xht x this");
EXTERN INT_VAR (crunch_rating_max, 10, "For adj length in rating per ch");
EXTERN INT_VAR (crunch_pot_indicators, 1,
"How many potential indicators needed");
EXTERN BOOL_VAR (crunch_leave_ok_strings, TRUE,
"Dont touch sensible strings");
EXTERN BOOL_VAR (crunch_accept_ok, TRUE, "Use acceptability in okstring");
EXTERN BOOL_VAR (crunch_leave_accept_strings, FALSE,
"Dont pot crunch sensible strings");
EXTERN BOOL_VAR (crunch_include_numerals, FALSE, "Fiddle alpha figures");
EXTERN INT_VAR (crunch_leave_lc_strings, 4,
"Dont crunch words with long lower case strings");
EXTERN INT_VAR (crunch_leave_uc_strings, 4,
"Dont crunch words with long lower case strings");
EXTERN INT_VAR (crunch_long_repetitions, 3,
"Crunch words with long repetitions");
EXTERN INT_VAR (crunch_debug, 0, "As it says");
static BOOL8 crude_match_blobs(PBLOB *blob1, PBLOB *blob2);
static void unrej_good_chs(WERD_RES *word, ROW *row);
WERD_RES* word;
inT16 match_count;
inT16 accepted_match_count;
};
/*************************************************************************
* word_blob_quality()
* How many blobs in the outword are identical to those of the inword?
* ASSUME blobs in both initial word and outword are in ascending order of
* How many blobs in the box_word are identical to those of the inword?
* ASSUME blobs in both initial word and box_word are in ascending order of
* left hand blob edge.
*************************************************************************/
inT16 word_blob_quality( //Blob seg changes
WERD_RES *word,
ROW *row) {
WERD *bln_word; //BL norm init word
TWERD *tessword; //tess format
WERD *init_word; //BL norm init word
PBLOB_IT outword_it;
PBLOB_IT initial_it;
inT16 i;
inT16 init_blobs_left;
inT16 match_count = 0;
BOOL8 matched;
TBOX out_box;
PBLOB *test_blob;
DENORM denorm;
float bln_xht;
if (word->word->gblob_list ()->empty ())
inT16 Tesseract::word_blob_quality(WERD_RES *word, ROW *row) {
if (word->bln_boxes == NULL ||
word->rebuild_word == NULL || word->rebuild_word->blobs == NULL)
return 0;
//xht used for blnorm
bln_xht = bln_x_height / word->denorm.scale ();
bln_word = make_bln_copy(word->word, row, NULL, bln_xht, &denorm);
/*
NOTE: Need to convert to tess format and back again to ensure that the
same float -> int rounding of coords is done to source wd as out wd before
comparison
*/
tessword = make_tess_word(bln_word, NULL); // Convert word.
init_word = make_ed_word(tessword, bln_word);
delete bln_word;
delete_word(tessword);
if (init_word == NULL) {
// Conversion failed.
return 0;
}
initial_it.set_to_list(init_word->blob_list());
init_blobs_left = initial_it.length();
outword_it.set_to_list(word->outword->blob_list());
for (outword_it.mark_cycle_pt();
!outword_it.cycled_list(); outword_it.forward()) {
out_box = outword_it.data()->bounding_box();
// Skip any initial blobs LEFT of current outword blob.
while (!initial_it.at_last() &&
(initial_it.data()->bounding_box().left() < out_box.left())) {
initial_it.forward();
init_blobs_left--;
}
/* See if current outword blob matches any initial blob with the same left
coord. (Normally only one but possibly more - in unknown order) */
i = 0;
matched = FALSE;
do {
test_blob = initial_it.data_relative (i++);
matched = crude_match_blobs (test_blob, outword_it.data ());
if (matched)
match_count++;
}
while (!matched &&
(init_blobs_left - i > 0) &&
(i < 129) &&
!initial_it.at_last() &&
test_blob->bounding_box().left() == out_box.left());
}
delete init_word;
return match_count;
DocQualCallbacks cb(word);
word->bln_boxes->ProcessMatchedBlobs(
*word->rebuild_word,
NewPermanentTessCallback(&cb, &DocQualCallbacks::CountMatchingBlobs));
return cb.match_count;
}
/*************************************************************************
* crude_match_blobs()
* Check bounding boxes are the same and the number of outlines are the same.
*************************************************************************/
static BOOL8 crude_match_blobs(PBLOB *blob1, PBLOB *blob2) {
TBOX box1 = blob1->bounding_box();
TBOX box2 = blob2->bounding_box();
if (box1.contains(box2) &&
box2.contains(box1) &&
(blob1->out_list()->length() == blob1->out_list()->length()))
return TRUE;
else
return FALSE;
}
inT16 word_outline_errs(WERD_RES *word) {
PBLOB_IT outword_it;
inT16 Tesseract::word_outline_errs(WERD_RES *word) {
inT16 i = 0;
inT16 err_count = 0;
outword_it.set_to_list(word->outword->blob_list());
TBLOB* blob = word->rebuild_word->blobs;
for (outword_it.mark_cycle_pt();
!outword_it.cycled_list(); outword_it.forward()) {
for (; blob != NULL; blob = blob->next) {
err_count += count_outline_errs(word->best_choice->unichar_string()[i],
outword_it.data()->out_list()->length());
blob->NumOutlines());
i++;
}
return err_count;
}
/*************************************************************************
* word_char_quality()
* Combination of blob quality and outline quality - how many good chars are
* there? - I.e chars which pass the blob AND outline tests.
*************************************************************************/
void word_char_quality(WERD_RES *word,
ROW *row,
inT16 *match_count,
inT16 *accepted_match_count) {
WERD *bln_word; // BL norm init word
TWERD *tessword; // tess format
WERD *init_word; // BL norm init word
PBLOB_IT outword_it;
PBLOB_IT initial_it;
inT16 i;
inT16 init_blobs_left;
BOOL8 matched;
TBOX out_box;
PBLOB *test_blob;
DENORM denorm;
float bln_xht;
inT16 j = 0;
*match_count = 0;
*accepted_match_count = 0;
if (word->word->gblob_list ()->empty ())
void Tesseract::word_char_quality(WERD_RES *word,
ROW *row,
inT16 *match_count,
inT16 *accepted_match_count) {
if (word->bln_boxes == NULL ||
word->rebuild_word == NULL || word->rebuild_word->blobs == NULL)
return;
// xht used for blnorm
bln_xht = bln_x_height / word->denorm.scale();
bln_word = make_bln_copy(word->word, row, NULL, bln_xht, &denorm);
/*
NOTE: Need to convert to tess format and back again to ensure that the
same float -> int rounding of coords is done to source wd as out wd before
comparison
*/
tessword = make_tess_word(bln_word, NULL); // Convert word.
init_word = make_ed_word(tessword, bln_word);
delete bln_word;
delete_word(tessword);
if (init_word == NULL)
return;
initial_it.set_to_list(init_word->blob_list());
init_blobs_left = initial_it.length();
outword_it.set_to_list(word->outword->blob_list());
for (outword_it.mark_cycle_pt();
!outword_it.cycled_list(); outword_it.forward()) {
out_box = outword_it.data()->bounding_box();
/* Skip any initial blobs LEFT of current outword blob */
while (!initial_it.at_last() &&
(initial_it.data()->bounding_box().left() < out_box.left())) {
initial_it.forward();
init_blobs_left--;
}
/* See if current outword blob matches any initial blob with the same left
coord. (Normally only one but possibly more - in unknown order) */
i = 0;
matched = FALSE;
do {
test_blob = initial_it.data_relative(i++);
matched = crude_match_blobs(test_blob, outword_it.data());
if (matched &&
(count_outline_errs (word->best_choice->unichar_string()[j],
outword_it.data ()->out_list ()->length ())
== 0)) {
(*match_count)++;
if (word->reject_map[j].accepted ())
(*accepted_match_count)++;
}
}
while (!matched &&
(init_blobs_left - i > 0) &&
(i < 129) &&
!initial_it.at_last() &&
test_blob->bounding_box().left() == out_box.left());
j++;
}
delete init_word;
DocQualCallbacks cb(word);
word->bln_boxes->ProcessMatchedBlobs(
*word->rebuild_word,
NewPermanentTessCallback(&cb, &DocQualCallbacks::CountAcceptedBlobs));
*match_count = cb.match_count;
*accepted_match_count = cb.accepted_match_count;
}
/*************************************************************************
* unrej_good_chs()
* Unreject POTENTIAL rejects if the blob passes the blob and outline checks
*************************************************************************/
static void unrej_good_chs(WERD_RES *word, ROW *row) {
WERD *bln_word; // BL norm init word
TWERD *tessword; // tess format
WERD *init_word; // BL norm init word
PBLOB_IT outword_it;
PBLOB_IT initial_it;
inT16 i;
inT16 init_blobs_left;
BOOL8 matched;
TBOX out_box;
PBLOB *test_blob;
DENORM denorm;
float bln_xht;
inT16 j = 0;
if (word->word->gblob_list ()->empty ())
void Tesseract::unrej_good_chs(WERD_RES *word, ROW *row) {
if (word->bln_boxes == NULL ||
word->rebuild_word == NULL || word->rebuild_word->blobs == NULL)
return;
// xht used for blnorm
bln_xht = bln_x_height / word->denorm.scale ();
bln_word = make_bln_copy(word->word, row, NULL, bln_xht, &denorm);
/*
NOTE: Need to convert to tess format and back again to ensure that the
same float -> int rounding of coords is done to source wd as out wd before
comparison
*/
tessword = make_tess_word(bln_word, NULL); // Convert word
init_word = make_ed_word(tessword, bln_word);
delete bln_word;
delete_word(tessword);
if (init_word == NULL)
return;
initial_it.set_to_list (init_word->blob_list ());
init_blobs_left = initial_it.length ();
outword_it.set_to_list (word->outword->blob_list ());
for (outword_it.mark_cycle_pt ();
!outword_it.cycled_list (); outword_it.forward ()) {
out_box = outword_it.data ()->bounding_box ();
/* Skip any initial blobs LEFT of current outword blob */
while (!initial_it.at_last () &&
(initial_it.data ()->bounding_box ().left () < out_box.left ())) {
initial_it.forward ();
init_blobs_left--;
}
/* See if current outword blob matches any initial blob with the same left
coord. (Normally only one but possibly more - in unknown order) */
i = 0;
matched = FALSE;
do {
test_blob = initial_it.data_relative (i++);
matched = crude_match_blobs (test_blob, outword_it.data ());
if (matched &&
(word->reject_map[j].accept_if_good_quality ()) &&
(docqual_excuse_outline_errs ||
(count_outline_errs (word->best_choice->unichar_string()[j],
outword_it.data ()->out_list ()->
length ()) == 0)))
word->reject_map[j].setrej_quality_accept ();
}
while (!matched &&
(init_blobs_left - i > 0) &&
(i < 129) &&
!initial_it.at_last () &&
test_blob->bounding_box ().left () == out_box.left ());
j++;
}
delete init_word;
DocQualCallbacks cb(word);
word->bln_boxes->ProcessMatchedBlobs(
*word->rebuild_word,
NewPermanentTessCallback(&cb, &DocQualCallbacks::AcceptIfGoodQuality));
}
void print_boxes(WERD *word) {
PBLOB_IT it;
TBOX box;
it.set_to_list (word->blob_list ());
for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ()) {
box = it.data ()->bounding_box ();
box.print ();
}
}
inT16 count_outline_errs(char c, inT16 outline_count) {
inT16 Tesseract::count_outline_errs(char c, inT16 outline_count) {
int expected_outline_count;
if (STRING (outlines_odd).contains (c))
@ -429,20 +141,11 @@ inT16 count_outline_errs(char c, inT16 outline_count) {
return abs (outline_count - expected_outline_count);
}
namespace tesseract {
void Tesseract::quality_based_rejection(PAGE_RES_IT &page_res_it,
BOOL8 good_quality_doc) {
if ((tessedit_good_quality_unrej && good_quality_doc))
unrej_good_quality_words(page_res_it);
doc_and_block_rejection(page_res_it, good_quality_doc);
page_res_it.restart_page ();
while (page_res_it.word () != NULL) {
insert_rej_cblobs(page_res_it.word());
page_res_it.forward();
}
if (unlv_tilde_crunching) {
tilde_crunch(page_res_it);
tilde_delete(page_res_it);
@ -542,7 +245,7 @@ void Tesseract::doc_and_block_rejection( //reject big chunks
BOOL8 rej_word;
BOOL8 prev_word_rejected;
inT16 char_quality;
inT16 char_quality = 0;
inT16 accepted_char_quality;
if ((page_res_it.page_res->rej_count * 100.0 /
@ -833,10 +536,10 @@ void Tesseract::tilde_crunch(PAGE_RES_IT &page_res_it) {
page_res_it.forward ();
}
}
} // namespace tesseract
BOOL8 terrible_word_crunch(WERD_RES *word, GARBAGE_LEVEL garbage_level) {
BOOL8 Tesseract::terrible_word_crunch(WERD_RES *word,
GARBAGE_LEVEL garbage_level) {
float rating_per_ch;
int adjusted_len;
int crunch_mode = 0;
@ -873,7 +576,6 @@ BOOL8 terrible_word_crunch(WERD_RES *word, GARBAGE_LEVEL garbage_level) {
return FALSE;
}
namespace tesseract {
BOOL8 Tesseract::potential_word_crunch(WERD_RES *word,
GARBAGE_LEVEL garbage_level,
BOOL8 ok_dict_word) {
@ -1022,36 +724,30 @@ void Tesseract::convert_bad_unlv_chs(WERD_RES *word_res) {
}
}
// Callback helper for merge_tess_fails returns a space if both
// arguments are space, otherwise INVALID_UNICHAR_ID.
UNICHAR_ID Tesseract::BothSpaces(UNICHAR_ID id1, UNICHAR_ID id2) {
if (id1 == id2 && id1 == unicharset.unichar_to_id(" "))
return id1;
else
return INVALID_UNICHAR_ID;
}
// Change pairs of tess failures to a single one
void Tesseract::merge_tess_fails(WERD_RES *word_res) {
PBLOB_IT blob_it; //blobs
int len = word_res->best_choice->length();
bool modified = false;
ASSERT_HOST (word_res->reject_map.length () == len);
ASSERT_HOST (word_res->outword->blob_list ()->length () == len);
UNICHAR_ID unichar_space = unicharset.unichar_to_id(" ");
blob_it = word_res->outword->blob_list ();
int i = 0;
while (i < word_res->best_choice->length()-1) {
if ((word_res->best_choice->unichar_id(i) == unichar_space) &&
(word_res->best_choice->unichar_id(i+1) == unichar_space)) {
modified = true;
word_res->best_choice->remove_unichar_id(i);
word_res->reject_map.remove_pos (i);
merge_blobs (blob_it.data_relative (1), blob_it.data ());
delete blob_it.extract (); //get rid of spare
} else {
i++;
}
blob_it.forward ();
}
len = word_res->best_choice->length();
ASSERT_HOST (word_res->reject_map.length () == len);
ASSERT_HOST (word_res->outword->blob_list ()->length () == len);
if (modified) {
word_res->best_choice->populate_unichars(unicharset);
if (word_res->ConditionalBlobMerge(
unicharset,
NewPermanentTessCallback(this, &Tesseract::BothSpaces), NULL,
word_res->best_choice->blob_choices())) {
tprintf("Post:bc len=%d, rejmap=%d, boxword=%d, chopword=%d, rebuild=%d\n",
word_res->best_choice->length(),
word_res->reject_map.length(),
word_res->box_word->length(),
word_res->chopped_word->NumBlobs(),
word_res->rebuild_word->NumBlobs());
int len = word_res->best_choice->length();
ASSERT_HOST(word_res->reject_map.length() == len);
ASSERT_HOST(word_res->box_word->length() == len);
}
}
@ -1252,7 +948,6 @@ GARBAGE_LEVEL Tesseract::garbage_word(WERD_RES *word, BOOL8 ok_dict_word) {
return G_OK;
}
}
} // namespace tesseract
/*************************************************************************
@ -1271,7 +966,7 @@ GARBAGE_LEVEL Tesseract::garbage_word(WERD_RES *word, BOOL8 ok_dict_word) {
* >75% of the outline BBs have longest dimension < 0.5xht
*************************************************************************/
CRUNCH_MODE word_deletable(WERD_RES *word, inT16 &delete_mode) {
CRUNCH_MODE Tesseract::word_deletable(WERD_RES *word, inT16 &delete_mode) {
int word_len = word->reject_map.length ();
float rating_per_ch;
TBOX box; //BB of word
@ -1286,13 +981,13 @@ CRUNCH_MODE word_deletable(WERD_RES *word, inT16 &delete_mode) {
return CR_DELETE;
}
box = word->outword->bounding_box ();
if (box.height () < crunch_del_min_ht * bln_x_height) {
box = word->rebuild_word->bounding_box();
if (box.height () < crunch_del_min_ht * kBlnXHeight) {
delete_mode = 4;
return CR_DELETE;
}
if (noise_outlines (word->outword)) {
if (noise_outlines(word->rebuild_word)) {
delete_mode = 5;
return CR_DELETE;
}
@ -1314,23 +1009,23 @@ CRUNCH_MODE word_deletable(WERD_RES *word, inT16 &delete_mode) {
return CR_LOOSE_SPACE;
}
if (box.top () < bln_baseline_offset - crunch_del_low_word * bln_x_height) {
if (box.top () < kBlnBaselineOffset - crunch_del_low_word * kBlnXHeight) {
delete_mode = 9;
return CR_LOOSE_SPACE;
}
if (box.bottom () >
bln_baseline_offset + crunch_del_high_word * bln_x_height) {
kBlnBaselineOffset + crunch_del_high_word * kBlnXHeight) {
delete_mode = 10;
return CR_LOOSE_SPACE;
}
if (box.height () > crunch_del_max_ht * bln_x_height) {
if (box.height () > crunch_del_max_ht * kBlnXHeight) {
delete_mode = 11;
return CR_LOOSE_SPACE;
}
if (box.width () < crunch_del_min_width * bln_x_height) {
if (box.width () < crunch_del_min_width * kBlnXHeight) {
delete_mode = 3;
return CR_LOOSE_SPACE;
}
@ -1339,7 +1034,7 @@ CRUNCH_MODE word_deletable(WERD_RES *word, inT16 &delete_mode) {
return CR_NONE;
}
inT16 failure_count(WERD_RES *word) {
inT16 Tesseract::failure_count(WERD_RES *word) {
const char *str = word->best_choice->unichar_string().string();
int tess_rejs = 0;
@ -1351,134 +1046,25 @@ inT16 failure_count(WERD_RES *word) {
}
BOOL8 noise_outlines(WERD *word) {
PBLOB_IT blob_it;
OUTLINE_IT outline_it;
TBOX box; //BB of outline
BOOL8 Tesseract::noise_outlines(TWERD *word) {
TBOX box; // BB of outline
inT16 outline_count = 0;
inT16 small_outline_count = 0;
inT16 max_dimension;
float small_limit = bln_x_height * crunch_small_outlines_size;
float small_limit = kBlnXHeight * crunch_small_outlines_size;
blob_it.set_to_list (word->blob_list ());
for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) {
outline_it.set_to_list (blob_it.data ()->out_list ());
for (outline_it.mark_cycle_pt ();
!outline_it.cycled_list (); outline_it.forward ()) {
for (TBLOB* blob = word->blobs; blob != NULL; blob = blob->next) {
for (TESSLINE* ol = blob->outlines; ol != NULL; ol = ol->next) {
outline_count++;
box = outline_it.data ()->bounding_box ();
if (box.height () > box.width ())
max_dimension = box.height ();
box = ol->bounding_box();
if (box.height() > box.width())
max_dimension = box.height();
else
max_dimension = box.width ();
max_dimension = box.width();
if (max_dimension < small_limit)
small_outline_count++;
}
}
return (small_outline_count >= outline_count);
}
/*************************************************************************
* insert_rej_cblobs()
* Put rejected word blobs back into the outword.
* NOTE!!! AFTER THIS THE CHOICES LIST WILL NOT HAVE THE CORRECT NUMBER
* OF ELEMENTS.
*************************************************************************/
namespace tesseract {
void Tesseract::insert_rej_cblobs(WERD_RES *word) {
PBLOB_IT blob_it; //blob iterator
PBLOB_IT rej_blob_it;
const STRING *word_str;
const STRING *word_lengths;
int old_len;
int rej_len;
char new_str[512 * UNICHAR_LEN];
char new_lengths[512];
REJMAP new_map;
int i = 0; //new_str index
int j = 0; //old_str index
int i_offset = 0; //new_str offset
int j_offset = 0; //old_str offset
int new_len;
gblob_sort_list (word->outword->rej_blob_list (), TRUE);
rej_blob_it.set_to_list (word->outword->rej_blob_list ());
if (rej_blob_it.empty ())
return;
rej_len = rej_blob_it.length ();
blob_it.set_to_list (word->outword->blob_list ());
word_str = &(word->best_choice->unichar_string());
word_lengths = &(word->best_choice->unichar_lengths());
old_len = word->best_choice->length();
ASSERT_HOST (word->reject_map.length () == old_len);
ASSERT_HOST (blob_it.length () == old_len);
if ((old_len + rej_len) > 511)
return; //Word is garbage anyway prevent abort
new_map.initialise (old_len + rej_len);
while (!rej_blob_it.empty ()) {
if ((j >= old_len) ||
(rej_blob_it.data ()->bounding_box ().left () <=
blob_it.data ()->bounding_box ().left ())) {
/* Insert reject blob */
if (j >= old_len)
blob_it.add_to_end (rej_blob_it.extract ());
else
blob_it.add_before_stay_put (rej_blob_it.extract ());
if (!rej_blob_it.empty ())
rej_blob_it.forward ();
new_str[i_offset] = ' ';
new_lengths[i] = 1;
new_map[i].setrej_rej_cblob ();
i_offset += new_lengths[i++];
}
else {
strncpy(new_str + i_offset, &(*word_str)[j_offset],
(*word_lengths)[j]);
new_lengths[i] = (*word_lengths)[j];
new_map[i] = word->reject_map[j];
i_offset += new_lengths[i++];
j_offset += (*word_lengths)[j++];
blob_it.forward ();
}
}
/* Add any extra normal blobs to strings */
while (j < word_lengths->length ()) {
strncpy(new_str + i_offset, &(*word_str)[j_offset],
(*word_lengths)[j]);
new_lengths[i] = (*word_lengths)[j];
new_map[i] = word->reject_map[j];
i_offset += new_lengths[i++];
j_offset += (*word_lengths)[j++];
}
new_str[i_offset] = '\0';
new_lengths[i] = 0;
/*
tprintf(
"\nOld len %d; New len %d; New str \"%s\"; New map \"%s\"\n",
old_len, i, new_str, new_map );
*/
ASSERT_HOST (i == blob_it.length ());
ASSERT_HOST (i == old_len + rej_len);
word->reject_map = new_map;
// Update word->best_choice if needed.
if (strcmp(new_str, word->best_choice->unichar_string().string()) != 0 ||
strcmp(new_lengths, word->best_choice->unichar_lengths().string()) != 0) {
WERD_CHOICE *new_choice =
new WERD_CHOICE(new_str, new_lengths,
word->best_choice->rating(),
word->best_choice->certainty(),
word->best_choice->permuter(),
getDict().getUnicharset());
new_choice->populate_unichars(getDict().getUnicharset());
delete word->best_choice;
word->best_choice = new_choice;
}
new_len = word->best_choice->length();
ASSERT_HOST (word->reject_map.length () == new_len);
ASSERT_HOST (word->outword->blob_list ()->length () == new_len);
}
} // namespace tesseract

View File

@ -31,108 +31,6 @@ enum GARBAGE_LEVEL
G_TERRIBLE
};
extern STRING_VAR_H (outlines_odd, "%| ", "Non standard number of outlines");
extern STRING_VAR_H (outlines_2, "ij!?%\":;",
"Non standard number of outlines");
extern BOOL_VAR_H (docqual_excuse_outline_errs, FALSE,
"Allow outline errs in unrejection?");
extern BOOL_VAR_H (tessedit_good_quality_unrej, TRUE,
"Reduce rejection on good docs");
extern BOOL_VAR_H (tessedit_use_reject_spaces, TRUE, "Reject spaces?");
extern double_VAR_H (tessedit_reject_doc_percent, 65.00,
"%rej allowed before rej whole doc");
extern double_VAR_H (tessedit_reject_block_percent, 45.00,
"%rej allowed before rej whole block");
extern double_VAR_H (tessedit_reject_row_percent, 40.00,
"%rej allowed before rej whole row");
extern double_VAR_H (tessedit_whole_wd_rej_row_percent, 70.00,
"%of row rejects in whole word rejects which prevents whole row rejection");
extern BOOL_VAR_H (tessedit_preserve_blk_rej_perfect_wds, TRUE,
"Only rej partially rejected words in block rejection");
extern BOOL_VAR_H (tessedit_preserve_row_rej_perfect_wds, TRUE,
"Only rej partially rejected words in row rejection");
extern BOOL_VAR_H (tessedit_dont_blkrej_good_wds, FALSE,
"Use word segmentation quality metric");
extern BOOL_VAR_H (tessedit_dont_rowrej_good_wds, FALSE,
"Use word segmentation quality metric");
extern INT_VAR_H (tessedit_preserve_min_wd_len, 2,
"Only preserve wds longer than this");
extern BOOL_VAR_H (tessedit_row_rej_good_docs, TRUE,
"Apply row rejection to good docs");
extern double_VAR_H (tessedit_good_doc_still_rowrej_wd, 1.1,
"rej good doc wd if more than this fraction rejected");
extern BOOL_VAR_H (tessedit_reject_bad_qual_wds, TRUE,
"Reject all bad quality wds");
extern BOOL_VAR_H (tessedit_debug_doc_rejection, FALSE, "Page stats");
extern BOOL_VAR_H (tessedit_debug_quality_metrics, FALSE,
"Output data to debug file");
extern BOOL_VAR_H (bland_unrej, FALSE, "unrej potential with no chekcs");
extern double_VAR_H (quality_rowrej_pc, 1.1,
"good_quality_doc gte good char limit");
extern BOOL_VAR_H (unlv_tilde_crunching, TRUE,
"Mark v.bad words for tilde crunch");
extern BOOL_VAR_H (crunch_early_merge_tess_fails, TRUE,
"Before word crunch?");
extern BOOL_VAR_H (crunch_early_convert_bad_unlv_chs, FALSE,
"Take out ~^ early?");
extern double_VAR_H (crunch_terrible_rating, 80.0, "crunch rating lt this");
extern BOOL_VAR_H (crunch_terrible_garbage, TRUE, "As it says");
extern double_VAR_H (crunch_poor_garbage_cert, -9.0,
"crunch garbage cert lt this");
extern double_VAR_H (crunch_poor_garbage_rate, 60,
"crunch garbage rating lt this");
extern double_VAR_H (crunch_pot_poor_rate, 40,
"POTENTIAL crunch rating lt this");
extern double_VAR_H (crunch_pot_poor_cert, -8.0,
"POTENTIAL crunch cert lt this");
extern BOOL_VAR_H (crunch_pot_garbage, TRUE, "POTENTIAL crunch garbage");
extern double_VAR_H (crunch_del_rating, 60,
"POTENTIAL crunch rating lt this");
extern double_VAR_H (crunch_del_cert, -10.0, "POTENTIAL crunch cert lt this");
extern double_VAR_H (crunch_del_min_ht, 0.7, "Del if word ht lt xht x this");
extern double_VAR_H (crunch_del_max_ht, 3.0, "Del if word ht gt xht x this");
extern double_VAR_H (crunch_del_min_width, 3.0,
"Del if word width lt xht x this");
extern double_VAR_H (crunch_del_high_word, 1.5,
"Del if word gt xht x this above bl");
extern double_VAR_H (crunch_del_low_word, 0.5,
"Del if word gt xht x this below bl");
extern double_VAR_H (crunch_small_outlines_size, 0.6,
"Small if lt xht x this");
extern INT_VAR_H (crunch_rating_max, 10, "For adj length in rating per ch");
extern INT_VAR_H (crunch_pot_indicators, 1,
"How many potential indicators needed");
extern BOOL_VAR_H (crunch_leave_ok_strings, TRUE,
"Dont touch sensible strings");
extern BOOL_VAR_H (crunch_accept_ok, TRUE, "Use acceptability in okstring");
extern BOOL_VAR_H (crunch_leave_accept_strings, FALSE,
"Dont pot crunch sensible strings");
extern BOOL_VAR_H (crunch_include_numerals, FALSE, "Fiddle alpha figures");
extern INT_VAR_H (crunch_leave_lc_strings, 4,
"Dont crunch words with long lower case strings");
extern INT_VAR_H (crunch_leave_uc_strings, 4,
"Dont crunch words with long lower case strings");
extern INT_VAR_H (crunch_long_repetitions, 3,
"Crunch words with long repetitions");
extern INT_VAR_H (crunch_debug, 0, "As it says");
inT16 word_blob_quality( //Blob seg changes
WERD_RES *word,
ROW *row);
//BOOL8 crude_match_blobs(PBLOB *blob1, PBLOB *blob2);
inT16 word_outline_errs( //Outline count errs
WERD_RES *word);
void word_char_quality( //Blob seg changes
WERD_RES *word,
ROW *row,
inT16 *match_count,
inT16 *accepted_match_count);
//void unrej_good_chs(WERD_RES *word, ROW *row);
void print_boxes(WERD *word);
inT16 count_outline_errs(char c, inT16 outline_count);
inT16 word_blob_quality(WERD_RES *word, ROW *row);
void reject_whole_page(PAGE_RES_IT &page_res_it);
BOOL8 terrible_word_crunch(WERD_RES *word, GARBAGE_LEVEL garbage_level);
//word to do
CRUNCH_MODE word_deletable(WERD_RES *word, inT16 &delete_mode);
inT16 failure_count(WERD_RES *word);
BOOL8 noise_outlines(WERD *word);
#endif

View File

@ -1,82 +0,0 @@
/**************************************************************************
* Revision 5.1 89/07/27 11:46:53 11:46:53 ray ()
* (C) Copyright 1989, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**************************************************************************/
#include "mfcpch.h"
#include "expandblob.h"
#include "tessclas.h"
#include "const.h"
#include "structures.h"
#include "freelist.h"
/***********************************************************************
free_blob(blob) frees the blob and everything it is connected to,
i.e. outlines, nodes, edgepts, bytevecs, ratings etc
*************************************************************************/
void free_blob( /*blob to free */
register TBLOB *blob) {
if (blob == NULL)
return; /*duff blob */
free_tree (blob->outlines); /*do the tree of outlines */
oldblob(blob); /*free the actual blob */
}
/***************************************************************************
free_tree(outline) frees the current outline
and then its sub-tree
*****************************************************************************/
void free_tree( /*outline to draw */
register TESSLINE *outline) {
if (outline == NULL)
return; /*duff outline */
if (outline->next != NULL)
free_tree (outline->next);
if (outline->child != NULL)
free_tree (outline->child); /*and sub-tree */
free_outline(outline); /*free the outline */
}
/*******************************************************************************
free_outline(outline) frees an outline and anything connected to it
*********************************************************************************/
void free_outline( /*outline to free */
register TESSLINE *outline) {
if (outline->compactloop != NULL)
/*no compact loop */
memfree (outline->compactloop);
if (outline->loop != NULL)
free_loop (outline->loop);
oldoutline(outline);
}
/*********************************************************************************
free_loop(startpt) frees all the elements of the closed loop
starting at startpt
***********************************************************************************/
void free_loop( /*outline to free */
register EDGEPT *startpt) {
register EDGEPT *edgept; /*current point */
if (startpt == NULL)
return;
edgept = startpt;
do {
edgept = oldedgept (edgept); /*free it and move on */
}
while (edgept != startpt);
}

View File

@ -1,13 +0,0 @@
#ifndef EXPANDBLOB_H
#define EXPANDBLOB_H
#include "tessclas.h"
void free_blob(register TBLOB *blob);
void free_tree(register TESSLINE *outline);
void free_outline(register TESSLINE *outline);
void free_loop(register EDGEPT *startpt);
#endif

File diff suppressed because it is too large Load Diff

View File

@ -23,37 +23,10 @@
#define FIXSPACE_H
#include "pageres.h"
#include "varable.h"
#include "ocrclass.h"
#include "params.h"
#include "notdll.h"
extern BOOL_VAR_H (fixsp_check_for_fp_noise_space, TRUE,
"Try turning noise to space in fixed pitch");
extern BOOL_VAR_H (fixsp_fp_eval, TRUE, "Use alternate evaluation for fp");
extern BOOL_VAR_H (fixsp_noise_score_fixing, TRUE, "More sophisticated?");
extern INT_VAR_H (fixsp_non_noise_limit, 1,
"How many non-noise blbs either side?");
extern double_VAR_H (fixsp_small_outlines_size, 0.28,
"Small if lt xht x this");
extern BOOL_VAR_H (fixsp_ignore_punct, TRUE, "In uniform spacing calc");
extern BOOL_VAR_H (fixsp_numeric_fix, TRUE, "Try to deal with numeric punct");
extern BOOL_VAR_H (fixsp_prefer_joined_1s, TRUE, "Arbitrary boost");
extern BOOL_VAR_H (tessedit_test_uniform_wd_spacing, FALSE,
"Limit context word spacing");
extern BOOL_VAR_H (tessedit_prefer_joined_punct, FALSE,
"Reward punctation joins");
extern INT_VAR_H (fixsp_done_mode, 1, "What constitutes done for spacing");
extern INT_VAR_H (debug_fix_space_level, 0, "Contextual fixspace debug");
extern STRING_VAR_H (numeric_punctuation, ".,",
"Punct. chs expected WITHIN numbers");
void initialise_search(WERD_RES_LIST &src_list, WERD_RES_LIST &new_list);
void transform_to_next_perm(WERD_RES_LIST &words);
void dump_words(WERD_RES_LIST &perm, inT16 score, inT16 mode, BOOL8 improved);
BOOL8 uniformly_spaced( //sensible word
WERD_RES *word);
BOOL8 fixspace_thinks_word_done(WERD_RES *word);
void break_noisiest_blob_word(WERD_RES_LIST &words);
inT16 worst_noise_blob(WERD_RES *word_res, float *worst_noise_score);
float blob_noise_score(PBLOB *blob);
void fixspace_dbg(WERD_RES *word);
#endif

View File

@ -17,816 +17,150 @@
*
**********************************************************************/
#ifdef _MSC_VER
#pragma warning(disable:4244) // Conversion warnings
#endif
#include "mfcpch.h"
#include <string.h>
#include <ctype.h>
#include "varable.h"
#include "tessvars.h"
#include "control.h"
#include "reject.h"
#include "fixxht.h"
#include "secname.h"
#include "params.h"
#include "float2int.h"
#include "tesseractclass.h"
#define EXTERN
EXTERN double_VAR (x_ht_fraction_of_caps_ht, 0.7,
"Fract of cps ht est of xht");
EXTERN double_VAR (x_ht_variation, 0.35,
"Err band as fract of caps/xht dist");
EXTERN double_VAR (x_ht_sub_variation, 0.5,
"Err band as fract of caps/xht dist");
EXTERN BOOL_VAR (rej_trial_ambigs, TRUE,
"reject x-ht ambigs when under trial");
EXTERN BOOL_VAR (x_ht_conservative_ambigs, FALSE,
"Dont rely on ambigs + maxht");
EXTERN BOOL_VAR (x_ht_check_est, TRUE, "Cross check estimates");
EXTERN BOOL_VAR (x_ht_case_flip, FALSE, "Flip or reject suspect case");
EXTERN BOOL_VAR (x_ht_include_dodgy_blobs, TRUE,
"Include blobs with possible noise?");
EXTERN BOOL_VAR (x_ht_limit_flip_trials, TRUE,
"Dont do trial flips when ambigs are close to xht?");
EXTERN BOOL_VAR (rej_use_check_block_occ, TRUE,
"Analyse rejection behaviour");
EXTERN STRING_VAR (chs_non_ambig_caps_ht,
"!#$%&()/12346789?ABDEFGHIKLNQRT[]\\bdfhkl",
"Reliable ascenders");
EXTERN STRING_VAR (chs_x_ht, "acegmnopqrsuvwxyz", "X height chars");
EXTERN STRING_VAR (chs_non_ambig_x_ht, "aenqr", "reliable X height chars");
EXTERN STRING_VAR (chs_ambig_caps_x, "cCmMoO05sSuUvVwWxXzZ",
"X ht or caps ht chars");
EXTERN STRING_VAR (chs_bl_ambig_caps_x, "pPyY", " Caps or descender ambigs");
/* The following arent used in this module but are used in applybox.c */
EXTERN STRING_VAR (chs_caps_ht,
"!#$%&()/0123456789?ABCDEFGHIJKLMNOPQRSTUVWXYZ[]\\bdfhkl{|}",
"Ascender chars");
EXTERN STRING_VAR (chs_desc, "gjpqy", "Descender chars");
EXTERN STRING_VAR (chs_non_ambig_bl,
"!#$%&01246789?ABCDEFGHIKLMNORSTUVWXYZabcdehiklmnorstuvwxz",
"Reliable baseline chars");
EXTERN STRING_VAR (chs_odd_top, "ijt", "Chars with funny ascender region");
EXTERN STRING_VAR (chs_odd_bot, "()35JQ[]\\/{}|", "Chars with funny base");
/* The following arent used but are defined for completeness */
EXTERN STRING_VAR (chs_bl,
"!#$%&()/01246789?ABCDEFGHIJKLMNOPRSTUVWXYZ[]\\abcdefhiklmnorstuvwxz{}",
"Baseline chars");
EXTERN STRING_VAR (chs_non_ambig_desc, "gq", "Reliable descender chars");
/**
* re_estimate_x_ht()
*
* Walk the blobs in the word together with the text string and reject map.
* NOTE: All evaluation is done on the baseline normalised word. This is so that
* the TBOX class can be used (integer). The reasons for this are:
* a) We must use the outword - ie the Tess result
* b) The outword is always converted to integer representation as that is how
* Tess works
* c) We would like to use the TBOX class, cos its there - this is integer
* precision.
* d) If we de-normed the outword we would get rounding errors and would find
* that integers are too imprecise (x-height around 15 pixels instead of a
* scale of 128 in bln form.
* CONVINCED?
*
* A) Try to re-estimatate x-ht and caps ht from confirmed pts in word.
*
* @verbatim
FOR each non reject blob
IF char is baseline posn ambiguous
Remove ambiguity by comparing its posn with respect to baseline.
IF char is a confirmed x-ht char
Add x-ht posn to confirmed_x_ht pts for word
IF char is a confirmed caps-ht char
Add blob_ht to caps ht pts for word
IF Std Dev of caps hts < 2 (AND # samples > 0)
Use mean as caps ht estimate (Dont use median as we can expect a
fair variation between the heights of the NON_AMBIG_CAPS_HT_CHS)
IF Std Dev of caps hts >= 2 (AND # samples > 0)
Suspect small caps font.
Look for 2 clusters, each with Std Dev < 2.
IF 2 clusters found
Pick the smaller median as the caps ht estimate of the smallcaps.
IF failed to estimate a caps ht
Use the median caps ht if there is one,
ELSE use the caps ht estimate of the previous word. NO!!!
IF there are confirmed x-height chars
Estimate confirmed x-height as the median value
ELSE IF there is a confirmed caps ht
Estimate confirmed x-height as a fraction of confirmed caps ht value
ELSE
Use the value for the previous word or the row value if this is the
first word in the block. NO!!!
@endverbatim
*
* B) Add in case ambiguous blobs based on confirmed x-ht/caps ht, changing case
* as necessary. Reestimate caps ht and x-ht as in A, using the extended
* clusters.
*
* C) If word contains rejects, and x-ht estimate significantly differs from
* original estimate, return TRUE so that the word can be rematched
*/
void re_estimate_x_ht( //improve for 1 word
WERD_RES *word_res, //word to do
float *trial_x_ht //new match value
) {
PBLOB_IT blob_it;
inT16 blob_ht_above_baseline;
const char *word_str;
inT16 i;
inT16 offset;
STATS all_blobs_ht (0, 300); //every blob in word
STATS x_ht (0, 300); //confirmed pts in wd
STATS caps_ht (0, 300); //confirmed pts in wd
STATS case_ambig (0, 300); //lower case ambigs
inT16 rej_blobs_count = 0;
inT16 rej_blobs_max_height = 0;
inT32 rej_blobs_max_area = 0;
float x_ht_ok_variation;
float max_blob_ht;
float marginally_above_x_ht;
TBOX blob_box; //blob bounding box
float est_x_ht = 0.0; //word estimate
float est_caps_ht = 0.0; //word estimate
//based on hard data?
BOOL8 est_caps_ht_certain = FALSE;
BOOL8 est_x_ht_certain = FALSE;//based on hard data?
BOOL8 trial = FALSE; //Sepeculative values?
BOOL8 no_comment = FALSE; //No change in xht
float ambig_lc_x_est;
float ambig_uc_caps_est;
inT16 x_ht_ambigs = 0;
inT16 caps_ht_ambigs = 0;
/* Calculate default variation of blob x_ht from bln x_ht for bln word */
x_ht_ok_variation =
(bln_x_height / x_ht_fraction_of_caps_ht - bln_x_height) * x_ht_variation;
word_str = word_res->best_choice->unichar_string().string();
/*
Cycle blobs, allocating to one of the stats sets when possible.
*/
blob_it.set_to_list (word_res->outword->blob_list ());
for (blob_it.mark_cycle_pt (), i = 0, offset = 0;
!blob_it.cycled_list (); blob_it.forward (),
offset += word_res->best_choice->unichar_lengths()[i++]) {
if (!dodgy_blob (blob_it.data ())) {
blob_box = blob_it.data ()->bounding_box ();
blob_ht_above_baseline = blob_box.top () - bln_baseline_offset;
all_blobs_ht.add (blob_ht_above_baseline, 1);
if (word_res->reject_map[i].rejected ()) {
rej_blobs_count++;
if (blob_box.height () > rej_blobs_max_height)
rej_blobs_max_height = blob_box.height ();
if (blob_box.area () > rej_blobs_max_area)
rej_blobs_max_area = blob_box.area ();
}
else {
if (STRING (chs_non_ambig_x_ht).contains (word_str[offset]))
x_ht.add (blob_ht_above_baseline, 1);
if (STRING (chs_non_ambig_caps_ht).contains (word_str[offset]))
caps_ht.add (blob_ht_above_baseline, 1);
if (STRING (chs_ambig_caps_x).contains (word_str[offset])) {
case_ambig.add (blob_ht_above_baseline, 1);
if (STRING (chs_x_ht).contains (word_str[offset]))
x_ht_ambigs++;
else
caps_ht_ambigs++;
}
if (STRING (chs_bl_ambig_caps_x).contains (word_str[offset])) {
if (STRING (chs_x_ht).contains (word_str[offset])) {
/* confirm x_height provided > 15% total height below baseline */
if ((bln_baseline_offset - blob_box.bottom ()) /
(float) blob_box.height () > 0.15)
x_ht.add (blob_ht_above_baseline, 1);
}
else {
/* confirm caps_height provided < 5% total height below baseline */
if ((bln_baseline_offset - blob_box.bottom ()) /
(float) blob_box.height () < 0.05)
caps_ht.add (blob_ht_above_baseline, 1);
}
}
}
}
}
est_caps_ht = estimate_from_stats (caps_ht);
est_x_ht = estimate_from_stats (x_ht);
est_ambigs(word_res, case_ambig, &ambig_lc_x_est, &ambig_uc_caps_est);
max_blob_ht = all_blobs_ht.ile (0.9999);
#ifndef SECURE_NAMES
if (debug_x_ht_level >= 20) {
tprintf ("Mode20:A: %s ", word_str);
word_res->reject_map.print (debug_fp);
tprintf (" XHT:%f CAP:%f MAX:%f AMBIG X:%f CAP:%f\n",
est_x_ht, est_caps_ht, max_blob_ht,
ambig_lc_x_est, ambig_uc_caps_est);
}
#endif
if (!x_ht_conservative_ambigs &&
(ambig_lc_x_est > 0) &&
(ambig_lc_x_est == ambig_uc_caps_est) &&
(max_blob_ht > ambig_lc_x_est + x_ht_ok_variation)) {
//may be zero but believe xht
ambig_uc_caps_est = est_caps_ht;
#ifndef SECURE_NAMES
if (debug_x_ht_level >= 20)
tprintf ("Mode20:B: Fiddle ambig_uc_caps_est to %f\n",
ambig_lc_x_est);
#endif
}
/* Now make some estimates */
if ((est_x_ht > 0) || (est_caps_ht > 0) ||
((ambig_lc_x_est > 0) && (ambig_lc_x_est != ambig_uc_caps_est))) {
/* There is some sensible data to go on so make the most of it. */
if (debug_x_ht_level >= 20)
tprintf ("Mode20:C: Sensible Data\n", ambig_lc_x_est);
if (est_x_ht > 0) {
est_x_ht_certain = TRUE;
if (est_caps_ht == 0) {
if ((ambig_uc_caps_est > ambig_lc_x_est) &&
(ambig_uc_caps_est > est_x_ht + x_ht_ok_variation))
est_caps_ht = ambig_uc_caps_est;
else
est_caps_ht = est_x_ht / x_ht_fraction_of_caps_ht;
}
if (case_ambig.get_total () > 0)
improve_estimate(word_res, est_x_ht, est_caps_ht, x_ht, caps_ht);
est_caps_ht_certain = caps_ht.get_total () > 0;
#ifndef SECURE_NAMES
if (debug_x_ht_level >= 20)
tprintf ("Mode20:D: Est from xht XHT:%f CAP:%f\n",
est_x_ht, est_caps_ht);
#endif
}
else if (est_caps_ht > 0) {
est_caps_ht_certain = TRUE;
if ((ambig_lc_x_est > 0) &&
(ambig_lc_x_est < est_caps_ht - x_ht_ok_variation))
est_x_ht = ambig_lc_x_est;
else
est_x_ht = est_caps_ht * x_ht_fraction_of_caps_ht;
if (ambig_lc_x_est + ambig_uc_caps_est > 0)
improve_estimate(word_res, est_x_ht, est_caps_ht, x_ht, caps_ht);
est_x_ht_certain = x_ht.get_total () > 0;
#ifndef SECURE_NAMES
if (debug_x_ht_level >= 20)
tprintf ("Mode20:E: Est from caps XHT:%f CAP:%f\n",
est_x_ht, est_caps_ht);
#endif
}
else {
/* Do something based on case ambig chars alone - we have guessed that the
ambigs are lower case. */
est_x_ht = ambig_lc_x_est;
est_x_ht_certain = TRUE;
if (ambig_uc_caps_est > ambig_lc_x_est) {
est_caps_ht = ambig_uc_caps_est;
est_caps_ht_certain = TRUE;
}
else
est_caps_ht = est_x_ht / x_ht_fraction_of_caps_ht;
#ifndef SECURE_NAMES
if (debug_x_ht_level >= 20)
tprintf ("Mode20:F: Est from ambigs XHT:%f CAP:%f\n",
est_x_ht, est_caps_ht);
#endif
}
/* Check for sane interpretation of evidence:
Try shifting caps ht if min certain caps ht is not significantly greater
than the estimated x ht or the max certain x ht is not significantly less
than the estimated caps ht. */
if (x_ht_check_est) {
if ((caps_ht.get_total () > 0) &&
(est_x_ht + x_ht_ok_variation >= caps_ht.ile (0.0001))) {
trial = TRUE;
est_caps_ht = est_x_ht;
est_x_ht = x_ht_fraction_of_caps_ht * est_caps_ht;
#ifndef SECURE_NAMES
if (debug_x_ht_level >= 20)
tprintf ("Mode20:G: Trial XHT:%f CAP:%f\n",
est_x_ht, est_caps_ht);
#endif
}
else if ((x_ht.get_total () > 0) &&
(est_caps_ht - x_ht_ok_variation <= x_ht.ile (0.9999))) {
trial = TRUE;
est_x_ht = est_caps_ht;
est_caps_ht = est_x_ht / x_ht_fraction_of_caps_ht;
#ifndef SECURE_NAMES
if (debug_x_ht_level >= 20)
tprintf ("Mode20:H: Trial XHT:%f CAP:%f\n",
est_x_ht, est_caps_ht);
#endif
}
}
}
else {
/* There is no sensible data so we're in the dark. */
marginally_above_x_ht = bln_x_height +
x_ht_ok_variation * x_ht_sub_variation;
/*
If there are no rejects, or the only rejects have a narrow height, or have
a small area compared to a normal char, then estimate the x-height as the
original one. (I.e dont fiddle about if the only rejects look like
punctuation) - we use max height as mean or median will be too low if
there are only two blobs - Eg "F."
*/
if (debug_x_ht_level >= 20)
tprintf ("Mode20:I: In the dark\n");
if ((rej_blobs_count == 0) ||
(rej_blobs_max_height < 0.3 * max_blob_ht) ||
(rej_blobs_max_area < 0.3 * max_blob_ht * max_blob_ht)) {
no_comment = TRUE;
if (debug_x_ht_level >= 20)
tprintf ("Mode20:J: No comment due to no rejects\n");
}
else if (x_ht_limit_flip_trials &&
((max_blob_ht < marginally_above_x_ht) ||
((ambig_lc_x_est > 0) &&
(ambig_lc_x_est == ambig_uc_caps_est) &&
(ambig_lc_x_est < marginally_above_x_ht)))) {
no_comment = TRUE;
if (debug_x_ht_level >= 20)
tprintf ("Mode20:K: No comment as close to xht %f < %f\n",
ambig_lc_x_est, marginally_above_x_ht);
}
else if (x_ht_conservative_ambigs && (ambig_uc_caps_est > 0)) {
trial = TRUE;
est_caps_ht = ambig_lc_x_est;
est_x_ht = x_ht_fraction_of_caps_ht * est_caps_ht;
#ifndef SECURE_NAMES
if (debug_x_ht_level >= 20)
tprintf ("Mode20:L: Trial XHT:%f CAP:%f\n",
est_x_ht, est_caps_ht);
#endif
}
/*
If the top of the word is nowhere near where we expect ascenders to be
(less than half the x_ht -> caps_ht distance) - suspect an all caps word
at the x-ht. Estimate x-ht accordingly - but only as a TRIAL!
NOTE we do NOT check location of baseline. Commas can descend as much as
real descenders so we would need to do something to make sure that any
disqualifying descenders were not at the end.
*/
else {
if (max_blob_ht <
(bln_x_height + bln_x_height / x_ht_fraction_of_caps_ht) / 2.0) {
trial = TRUE;
est_x_ht = x_ht_fraction_of_caps_ht * max_blob_ht;
est_caps_ht = max_blob_ht;
#ifndef SECURE_NAMES
if (debug_x_ht_level >= 20)
tprintf ("Mode20:M: Trial XHT:%f CAP:%f\n",
est_x_ht, est_caps_ht);
#endif
}
else {
no_comment = TRUE;
if (debug_x_ht_level >= 20)
tprintf ("Mode20:N: No comment as nothing else matched\n");
}
}
}
/* Sanity check - reject word if fails */
if (!no_comment &&
((est_x_ht > 2 * bln_x_height) ||
(est_x_ht / word_res->denorm.scale () <= min_sane_x_ht_pixels) ||
(est_caps_ht <= est_x_ht) || (est_caps_ht >= 2.5 * est_x_ht))) {
no_comment = TRUE;
if (!trial && rej_use_xht) {
if (debug_x_ht_level >= 2) {
tprintf ("Sanity check rejecting %s ", word_str);
word_res->reject_map.print (debug_fp);
tprintf ("\n");
}
word_res->reject_map.rej_word_xht_fixup ();
}
if (debug_x_ht_level >= 20)
tprintf ("Mode20:O: No comment as nothing else matched\n");
}
if (no_comment || trial) {
word_res->x_height = bln_x_height / word_res->denorm.scale ();
word_res->guessed_x_ht = TRUE;
word_res->caps_height = (bln_x_height / x_ht_fraction_of_caps_ht) /
word_res->denorm.scale ();
word_res->guessed_caps_ht = TRUE;
/*
Reject ambigs in the current word if we are uncertain and:
there are rejects OR
there is only one char which is an ambig OR
there is conflict between the case of the ambigs even though there is
no height separation Eg "Ms" recognised from "MS"
*/
if (rej_trial_ambigs &&
((word_res->reject_map.reject_count () > 0) ||
(word_res->reject_map.length () == 1) ||
((x_ht_ambigs > 0) && (caps_ht_ambigs > 0)))) {
#ifndef SECURE_NAMES
if (debug_x_ht_level >= 2) {
tprintf ("TRIAL Rej Ambigs %s ", word_str);
word_res->reject_map.print (debug_fp);
}
#endif
reject_ambigs(word_res);
if (debug_x_ht_level >= 2) {
tprintf (" ");
word_res->reject_map.print (debug_fp);
tprintf ("\n");
}
}
}
else {
word_res->x_height = est_x_ht / word_res->denorm.scale ();
word_res->guessed_x_ht = !est_x_ht_certain;
word_res->caps_height = est_caps_ht / word_res->denorm.scale ();
word_res->guessed_caps_ht = !est_caps_ht_certain;
}
if (!no_comment && (fabs (est_x_ht - bln_x_height) > x_ht_ok_variation))
*trial_x_ht = est_x_ht / word_res->denorm.scale ();
else
*trial_x_ht = 0.0;
#ifndef SECURE_NAMES
if (((*trial_x_ht > 0) && (debug_x_ht_level >= 3)) ||
(debug_x_ht_level >= 5)) {
tprintf ("%s ", word_str);
word_res->reject_map.print (debug_fp);
tprintf
(" X:%0.2f Cps:%0.2f Mxht:%0.2f RJ MxHt:%d MxAr:%d Rematch:%c\n",
est_x_ht, est_caps_ht, max_blob_ht, rej_blobs_max_height,
rej_blobs_max_area, *trial_x_ht > 0 ? '*' : ' ');
}
#endif
}
namespace tesseract {
/**
* check_block_occ()
* Checks word for coarse block occupancy, rejecting more chars and flipping
* case of case ambiguous chars as required.
*/
void Tesseract::check_block_occ(WERD_RES *word_res) {
PBLOB_IT blob_it;
STRING new_string;
STRING new_string_lengths(word_res->best_choice->unichar_lengths());
REJMAP new_map = word_res->reject_map;
WERD_CHOICE *new_choice;
const char *word_str = word_res->best_choice->unichar_string().string();
inT16 i;
inT16 offset;
inT16 reject_count = 0;
char confirmed_char[UNICHAR_LEN + 1];
char temp_char[UNICHAR_LEN + 1];
float x_ht;
float caps_ht;
// Fixxht overview.
// Premise: Initial estimate of x-height is adequate most of the time, but
// occasionally it is incorrect. Most notable causes of failure are:
// 1. Small caps, where the top of the caps is the same as the body text
// xheight. For small caps words the xheight needs to be reduced to correctly
// recognize the caps in the small caps word.
// 2. All xheight lines, such as summer. Here the initial estimate will have
// guessed that the blob tops are caps and will have placed the xheight too low.
// 3. Noise/logos beside words, or changes in font size on a line. Such
// things can blow the statistics and cause an incorrect estimate.
//
// Algorithm.
// Compare the vertical position (top only) of alphnumerics in a word with
// the range of positions in training data (in the unicharset).
// See CountMisfitTops. If any characters disagree sufficiently with the
// initial xheight estimate, then recalculate the xheight, re-run OCR on
// the word, and if the number of vertical misfits goes down, along with
// either the word rating or certainty, then keep the new xheight.
// The new xheight is calculated as follows:ComputeCompatibleXHeight
// For each alphanumeric character that has a vertically misplaced top
// (a misfit), yet its bottom is within the acceptable range (ie it is not
// likely a sub-or super-script) calculate the range of acceptable xheight
// positions from its range of tops, and give each value in the range a
// number of votes equal to the distance of its top from its acceptance range.
// The x-height position with the median of the votes becomes the new
// x-height. This assumes that most characters will be correctly recognized
// even if the x-height is incorrect. This is not a terrible assumption, but
// it is not great. An improvement would be to use a classifier that does
// not care about vertical position or scaling at all.
new_string_lengths[0] = 0;
// If the max-min top of a unicharset char is bigger than kMaxCharTopRange
// then the char top cannot be used to judge misfits or suggest a new top.
const int kMaxCharTopRange = 48;
if (word_res->x_height > 0)
x_ht = word_res->x_height * word_res->denorm.scale ();
else
x_ht = bln_x_height;
if (word_res->caps_height > 0)
caps_ht = word_res->caps_height * word_res->denorm.scale ();
else
caps_ht = x_ht / x_ht_fraction_of_caps_ht;
blob_it.set_to_list (word_res->outword->blob_list ());
for (blob_it.mark_cycle_pt (), i = 0, offset = 0;
!blob_it.cycled_list (); blob_it.forward (),
offset += word_res->best_choice->unichar_lengths()[i++]) {
strncpy(temp_char, word_str + offset,
word_res->best_choice->unichar_lengths()[i]); //default copy
temp_char[word_res->best_choice->unichar_lengths()[i]] = '\0';
if (word_res->reject_map[i].accepted ()) {
check_blob_occ (temp_char,
blob_it.data ()->bounding_box ().
top () - bln_baseline_offset, x_ht,
caps_ht, confirmed_char);
if (strcmp(confirmed_char, "") == 0) {
if (rej_use_check_block_occ) {
new_map[i].setrej_xht_fixup ();
reject_count++;
}
// Returns the number of misfit blob tops in this word.
int Tesseract::CountMisfitTops(WERD_RES *word_res) {
int bad_blobs = 0;
TBLOB* blob = word_res->rebuild_word->blobs;
int blob_id = 0;
for (; blob != NULL; blob = blob->next, ++blob_id) {
UNICHAR_ID class_id = word_res->best_choice->unichar_id(blob_id);
if (unicharset.get_isalpha(class_id) || unicharset.get_isdigit(class_id)) {
int top = blob->bounding_box().top();
if (top >= INT_FEAT_RANGE)
top = INT_FEAT_RANGE - 1;
int min_bottom, max_bottom, min_top, max_top;
unicharset.get_top_bottom(class_id, &min_bottom, &max_bottom,
&min_top, &max_top);
if (max_top - min_top > kMaxCharTopRange)
continue;
bool bad = top < min_top - x_ht_acceptance_tolerance ||
top > max_top + x_ht_acceptance_tolerance;
if (bad)
++bad_blobs;
if (debug_x_ht_level >= 1) {
tprintf("Class %s is %s with top %d vs limits of %d->%d, +/-%d\n",
unicharset.id_to_unichar(class_id),
bad ? "Misfit" : "OK", top, min_top, max_top,
static_cast<int>(x_ht_acceptance_tolerance));
}
else
strcpy(temp_char, confirmed_char);
}
new_string += temp_char;
new_string_lengths[i] = strlen(temp_char);
new_string_lengths[i + 1] = 0;
}
if ((reject_count > 0) || (new_string != word_str)) {
if (debug_x_ht_level >= 2) {
tprintf ("Shape Verification: %s ", word_str);
word_res->reject_map.print (debug_fp);
tprintf (" -> %s ", new_string.string ());
new_map.print (debug_fp);
tprintf ("\n");
}
new_choice = new WERD_CHOICE(new_string.string(),
new_string_lengths.string(),
word_res->best_choice->rating(),
word_res->best_choice->certainty(),
word_res->best_choice->permuter(),
unicharset);
new_choice->populate_unichars(unicharset);
delete word_res->best_choice;
word_res->best_choice = new_choice;
word_res->reject_map = new_map;
}
return bad_blobs;
}
// Returns a new x-height maximally compatible with the result in word_res.
// See comment above for overall algorithm.
float Tesseract::ComputeCompatibleXheight(WERD_RES *word_res) {
STATS top_stats(0, MAX_UINT8);
TBLOB* blob = word_res->rebuild_word->blobs;
int blob_id = 0;
for (; blob != NULL; blob = blob->next, ++blob_id) {
UNICHAR_ID class_id = word_res->best_choice->unichar_id(blob_id);
if (unicharset.get_isalpha(class_id) || unicharset.get_isdigit(class_id)) {
int top = blob->bounding_box().top();
// Clip the top to the limit of normalized feature space.
if (top >= INT_FEAT_RANGE)
top = INT_FEAT_RANGE - 1;
int bottom = blob->bounding_box().bottom();
int min_bottom, max_bottom, min_top, max_top;
unicharset.get_top_bottom(class_id, &min_bottom, &max_bottom,
&min_top, &max_top);
// Chars with a wild top range would mess up the result so ignore them.
if (max_top - min_top > kMaxCharTopRange)
continue;
int misfit_dist = MAX((min_top - x_ht_acceptance_tolerance) - top,
top - (max_top + x_ht_acceptance_tolerance));
int height = top - kBlnBaselineOffset;
if (debug_x_ht_level >= 20) {
tprintf("Class %s: height=%d, bottom=%d,%d top=%d,%d, actual=%d,%d : ",
unicharset.id_to_unichar(class_id),
height, min_bottom, max_bottom, min_top, max_top,
bottom, top);
}
// Use only chars that fit in the expected bottom range, and where
// the range of tops is sensibly near the xheight.
if (min_bottom <= bottom + x_ht_acceptance_tolerance &&
bottom - x_ht_acceptance_tolerance <= max_bottom &&
min_top > kBlnBaselineOffset &&
max_top - kBlnBaselineOffset >= kBlnXHeight &&
misfit_dist > 0) {
// Compute the x-height position using proportionality between the
// actual height and expected height.
int min_xht = DivRounded(height * kBlnXHeight,
max_top - kBlnBaselineOffset);
int max_xht = DivRounded(height * kBlnXHeight,
min_top - kBlnBaselineOffset);
if (debug_x_ht_level >= 20) {
tprintf(" xht range min=%d, max=%d\n",
min_xht, max_xht);
}
// The range of expected heights gets a vote equal to the distance
// of the actual top from the expected top.
for (int y = min_xht; y <= max_xht; ++y)
top_stats.add(y, misfit_dist);
} else if (debug_x_ht_level >= 20) {
tprintf(" already OK\n");
}
}
}
if (top_stats.get_total() == 0)
return 0.0f;
// The new xheight is just the median vote, which is then scaled out
// of BLN space back to pixel space to get the x-height in pixel space.
float new_xht = top_stats.median();
if (debug_x_ht_level >= 20) {
tprintf("Median xht=%f\n", new_xht);
tprintf("Mode20:A: New x-height = %f (norm), %f (orig)\n",
new_xht, new_xht / word_res->denorm.scale());
}
// The xheight must change by at least x_ht_min_change to be used.
if (fabs(new_xht - kBlnXHeight) >= x_ht_min_change)
return new_xht / word_res->denorm.scale();
else
return 0.0f;
}
} // namespace tesseract
/**
* check_blob_occ()
*
* Checks blob for position relative to position above baseline
* @return 0 for reject, or (possibly case shifted) confirmed char
*/
void check_blob_occ(char* proposed_char,
inT16 blob_ht_above_baseline,
float x_ht,
float caps_ht,
char* confirmed_char) {
BOOL8 blob_definite_x_ht;
BOOL8 blob_definite_caps_ht;
float acceptable_variation;
acceptable_variation = (caps_ht - x_ht) * x_ht_variation;
/* ??? REJECT if expected descender and nothing significantly below BL */
/* ??? REJECT if expected ascender and nothing significantly above x-ht */
/*
IF AMBIG_CAPS_X_CHS
IF blob is definitely an ascender ( > xht + xht err )AND
char is an x-ht char
THEN
flip case
IF blob is defintiely an x-ht ( <= xht + xht err ) AND
char is an ascender char
THEN
flip case
*/
blob_definite_x_ht = blob_ht_above_baseline <= x_ht + acceptable_variation;
blob_definite_caps_ht = blob_ht_above_baseline >=
caps_ht - acceptable_variation;
if (STRING (chs_ambig_caps_x).contains (*proposed_char)) {
if ((!blob_definite_x_ht && !blob_definite_caps_ht) ||
((strcmp(proposed_char, "0") == 0) && !blob_definite_caps_ht) ||
((strcmp(proposed_char, "o") == 0) && !blob_definite_x_ht)) {
strcpy(confirmed_char, "");
return;
}
else if (blob_definite_caps_ht &&
STRING (chs_x_ht).contains (*proposed_char)) {
if (x_ht_case_flip) {
//flip to upper case
proposed_char[0] = (char) toupper (*proposed_char);
return;
} else {
strcpy(confirmed_char, "");
return;
}
}
else if (blob_definite_x_ht &&
!STRING (chs_x_ht).contains (*proposed_char)) {
if (x_ht_case_flip) {
//flip to lower case
proposed_char[0] = (char) tolower (*proposed_char);
} else {
strcpy(confirmed_char, "");
return;
}
}
}
else
if ((STRING (chs_non_ambig_x_ht).contains (*proposed_char)
&& !blob_definite_x_ht)
|| (STRING (chs_non_ambig_caps_ht).contains (*proposed_char)
&& !blob_definite_caps_ht)) {
strcpy(confirmed_char, "");
return;
}
strcpy(confirmed_char, proposed_char);
return;
}
float estimate_from_stats(STATS &stats) {
if (stats.get_total () <= 0)
return 0.0;
else if (stats.get_total () >= 3)
return stats.ile (0.5); //median
else
return stats.mean ();
}
void improve_estimate(WERD_RES *word_res,
float &est_x_ht,
float &est_caps_ht,
STATS &x_ht,
STATS &caps_ht) {
PBLOB_IT blob_it;
inT16 blob_ht_above_baseline;
const char *word_str;
inT16 i;
inT16 offset;
TBOX blob_box; //blob bounding box
char confirmed_char[UNICHAR_LEN + 1];
char temp_char[UNICHAR_LEN + 1];
float new_val;
/* IMPROVE estimates here - if good estimates, and case ambig chars,
rescan blobs to fix case ambig blobs, re-estimate hts ??? maybe always do
it after deciding x-height
*/
blob_it.set_to_list (word_res->outword->blob_list ());
word_str = word_res->best_choice->unichar_string().string();
for (blob_it.mark_cycle_pt (), i = 0, offset = 0;
!blob_it.cycled_list (); blob_it.forward (),
offset += word_res->best_choice->unichar_lengths()[i++]) {
if ((STRING (chs_ambig_caps_x).contains (word_str[offset])) &&
(!dodgy_blob (blob_it.data ()))) {
blob_box = blob_it.data ()->bounding_box ();
blob_ht_above_baseline = blob_box.top () - bln_baseline_offset;
strncpy(temp_char, word_str + offset,
word_res->best_choice->unichar_lengths()[i]);
temp_char[word_res->best_choice->unichar_lengths()[i]] = '\0';
check_blob_occ (temp_char,
blob_ht_above_baseline,
est_x_ht, est_caps_ht, confirmed_char);
if (strcmp(confirmed_char, "") != 0) {
if (STRING (chs_x_ht).contains (*confirmed_char))
x_ht.add (blob_ht_above_baseline, 1);
else
caps_ht.add (blob_ht_above_baseline, 1);
}
}
}
new_val = estimate_from_stats (x_ht);
if (new_val > 0)
est_x_ht = new_val;
new_val = estimate_from_stats (caps_ht);
if (new_val > 0)
est_caps_ht = new_val;
}
void reject_ambigs( //rej any accepted xht ambig chars
WERD_RES *word) {
const char *word_str;
int i = 0;
word_str = word->best_choice->unichar_string().string();
while (*word_str != '\0') {
if (STRING (chs_ambig_caps_x).contains (*word_str))
word->reject_map[i].setrej_xht_fixup ();
word_str += word->best_choice->unichar_lengths()[i++];
}
}
void est_ambigs( //xht ambig ht stats
WERD_RES *word_res,
STATS &stats,
float *ambig_lc_x_est, //xht est
float *ambig_uc_caps_est //caps est
) {
float x_ht_ok_variation;
STATS short_ambigs (0, 300);
STATS tall_ambigs (0, 300);
PBLOB_IT blob_it;
TBOX blob_box; //blob bounding box
inT16 blob_ht_above_baseline;
const char *word_str;
inT16 i;
inT16 offset;
float min; //min ambig ch ht
float max; //max ambig ch ht
float short_limit; // for lower case
float tall_limit; // for upper case
x_ht_ok_variation =
(bln_x_height / x_ht_fraction_of_caps_ht - bln_x_height) * x_ht_variation;
if (stats.get_total () == 0) {
*ambig_lc_x_est = 0;
*ambig_uc_caps_est = 0;
}
else {
min = stats.ile (0.0);
max = stats.ile (0.99999);
if ((max - min) < x_ht_ok_variation) {
*ambig_lc_x_est = *ambig_uc_caps_est = stats.mean ();
//close enough
}
else {
/* Try reclustering into lower and upper case chars */
short_limit = min + (max - min) * x_ht_variation;
tall_limit = max - (max - min) * x_ht_variation;
word_str = word_res->best_choice->unichar_string().string();
blob_it.set_to_list (word_res->outword->blob_list ());
for (blob_it.mark_cycle_pt (), i = 0, offset = 0;
!blob_it.cycled_list (); blob_it.forward (),
offset += word_res->best_choice->unichar_lengths()[i++]) {
if (word_res->reject_map[i].accepted () &&
STRING (chs_ambig_caps_x).contains (word_str[offset]) &&
(!dodgy_blob (blob_it.data ()))) {
blob_box = blob_it.data ()->bounding_box ();
blob_ht_above_baseline =
blob_box.top () - bln_baseline_offset;
if (blob_ht_above_baseline <= short_limit)
short_ambigs.add (blob_ht_above_baseline, 1);
else if (blob_ht_above_baseline >= tall_limit)
tall_ambigs.add (blob_ht_above_baseline, 1);
}
}
*ambig_lc_x_est = short_ambigs.mean ();
*ambig_uc_caps_est = tall_ambigs.mean ();
/* Cop out if we havent got sensible clusters. */
if (*ambig_uc_caps_est - *ambig_lc_x_est <= x_ht_ok_variation)
*ambig_lc_x_est = *ambig_uc_caps_est = stats.mean ();
//close enough
}
}
}
/**
* dodgy_blob()
* Returns true if the blob has more than one outline, one above the other.
* These are dodgy as the top blob could be noise, causing the bounding box xht
* to be misleading
*/
BOOL8 dodgy_blob(PBLOB *blob) {
OUTLINE_IT outline_it = blob->out_list ();
inT16 highest_bottom = -MAX_INT16;
inT16 lowest_top = MAX_INT16;
TBOX outline_box;
if (x_ht_include_dodgy_blobs)
return FALSE; //no blob is ever dodgy
for (outline_it.mark_cycle_pt ();
!outline_it.cycled_list (); outline_it.forward ()) {
outline_box = outline_it.data ()->bounding_box ();
if (lowest_top > outline_box.top ())
lowest_top = outline_box.top ();
if (highest_bottom < outline_box.bottom ())
highest_bottom = outline_box.bottom ();
}
return highest_bottom >= lowest_top;
}

View File

@ -1,92 +0,0 @@
/**********************************************************************
* File: fixxht.h (Formerly fixxht.h)
* Description: Improve x_ht and look out for case inconsistencies
* Author: Phil Cheatle
* Created: Thu Aug 5 14:11:08 BST 1993
*
* (C) Copyright 1992, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
#ifndef FIXXHT_H
#define FIXXHT_H
#include "varable.h"
#include "statistc.h"
#include "pageres.h"
#include "notdll.h"
extern double_VAR_H (x_ht_fraction_of_caps_ht, 0.7,
"Fract of cps ht est of xht");
extern double_VAR_H (x_ht_variation, 0.35,
"Err band as fract of caps/xht dist");
extern double_VAR_H (x_ht_sub_variation, 0.5,
"Err band as fract of caps/xht dist");
extern BOOL_VAR_H (rej_trial_ambigs, TRUE,
"reject x-ht ambigs when under trial");
extern BOOL_VAR_H (x_ht_conservative_ambigs, FALSE,
"Dont rely on ambigs + maxht");
extern BOOL_VAR_H (x_ht_check_est, TRUE, "Cross check estimates");
extern BOOL_VAR_H (x_ht_case_flip, FALSE, "Flip or reject suspect case");
extern BOOL_VAR_H (x_ht_include_dodgy_blobs, TRUE,
"Include blobs with possible noise?");
extern BOOL_VAR_H (x_ht_limit_flip_trials, TRUE,
"Dont do trial flips when ambigs are close to xht?");
extern BOOL_VAR_H (rej_use_check_block_occ, TRUE,
"Analyse rejection behaviour");
extern STRING_VAR_H (chs_non_ambig_caps_ht,
"!#$%&()/12346789?ABDEFGHIKLNQRT[]\\bdfhkl",
"Reliable ascenders");
extern STRING_VAR_H (chs_x_ht, "acegmnopqrsuvwxyz", "X height chars");
extern STRING_VAR_H (chs_non_ambig_x_ht, "aenqr", "reliable X height chars");
extern STRING_VAR_H (chs_ambig_caps_x, "cCmMoO05sSuUvVwWxXzZ",
"X ht or caps ht chars");
extern STRING_VAR_H (chs_bl_ambig_caps_x, "pPyY",
" Caps or descender ambigs");
extern STRING_VAR_H (chs_caps_ht,
"!#$%&()/0123456789?ABCDEFGHIJKLMNOPQRSTUVWXYZ[]\\bdfhkl{|}",
"Ascender chars");
extern STRING_VAR_H (chs_desc, "gjpqy", "Descender chars");
extern STRING_VAR_H (chs_non_ambig_bl,
"!#$%&01246789?ABCDEFGHIKLMNORSTUVWXYZabcdehiklmnorstuvwxz",
"Reliable baseline chars");
extern STRING_VAR_H (chs_odd_top, "ijt", "Chars with funny ascender region");
extern STRING_VAR_H (chs_odd_bot, "()35JQ[]\\/{}|", "Chars with funny base");
extern STRING_VAR_H (chs_bl,
"!#$%&()/01246789?ABCDEFGHIJKLMNOPRSTUVWXYZ[]\\abcdefhiklmnorstuvwxz{}",
"Baseline chars");
extern STRING_VAR_H (chs_non_ambig_desc, "gq", "Reliable descender chars");
void re_estimate_x_ht( //improve for 1 word
WERD_RES *word_res, //word to do
float *trial_x_ht //new match value
);
void check_blob_occ(char *proposed_char,
inT16 blob_ht_above_baseline,
float x_ht,
float caps_ht,
char *confirmed_char);
float estimate_from_stats(STATS &stats);
void improve_estimate(WERD_RES *word_res,
float &est_x_ht,
float &est_caps_ht,
STATS &x_ht,
STATS &caps_ht);
void reject_ambigs( //rej any accepted xht ambig chars
WERD_RES *word);
//xht ambig ht stats
void est_ambigs(WERD_RES *word_res,
STATS &stats,
float *ambig_lc_x_est, //xht est
float *ambig_uc_caps_est //caps est
);
BOOL8 dodgy_blob(PBLOB *blob);
#endif

View File

@ -1,396 +0,0 @@
/**********************************************************************
* File: matmatch.cpp (Formerly matrix_match.c)
* Description: matrix matching routines for Tessedit
* Author: Chris Newton
* Created: Wed Nov 24 15:57:41 GMT 1993
*
* (C) Copyright 1993, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
#include "mfcpch.h"
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <ctype.h>
#ifdef __UNIX__
#include <assert.h>
#endif
#include "tessvars.h"
#include "stderr.h"
#include "img.h"
//#include "evnts.h"
//#include "showim.h"
#include "hosthplb.h"
#include "scrollview.h"
//#include "evnts.h"
#include "adaptions.h"
#include "matmatch.h"
#include "secname.h"
#include "svshowim.h"
// Include automatically generated configuration file if running autoconf.
#ifdef HAVE_CONFIG_H
#include "config_auto.h"
#endif
#define EXTERN
EXTERN BOOL_VAR (tessedit_display_mm, FALSE, "Display matrix matches");
EXTERN BOOL_VAR (tessedit_mm_debug, FALSE,
"Print debug information for matrix matcher");
EXTERN INT_VAR (tessedit_mm_prototype_min_size, 3,
"Smallest number of samples in a cluster for a prototype to be used");
// Colours for displaying the match
#define BB_COLOUR 0
#define BW_COLOUR 1
#define WB_COLOUR 3
#define UB_COLOUR 5
#define BU_COLOUR 7
#define UU_COLOUR 9
#define WU_COLOUR 11
#define UW_COLOUR 13
#define WW_COLOUR 15
#define BINIM_BLACK 0
#define BINIM_WHITE 1
float matrix_match( // returns match score
IMAGE *image1,
IMAGE *image2) {
ASSERT_HOST (image1->get_bpp () == 1 && image2->get_bpp () == 1);
if (image1->get_xsize () >= image2->get_xsize ())
return match1 (image1, image2);
else
return match1 (image2, image1);
}
float match1( /* returns match score */
IMAGE *image_w,
IMAGE *image_n) {
inT32 x_offset;
inT32 y_offset;
inT32 x_size = image_w->get_xsize ();
inT32 y_size;
inT32 x_size2 = image_n->get_xsize ();
inT32 y_size2;
IMAGE match_image;
IMAGELINE imline_w;
IMAGELINE imline_n;
IMAGELINE match_imline;
inT32 x;
inT32 y;
float sum = 0.0;
x_offset = (image_w->get_xsize () - image_n->get_xsize ()) / 2;
ASSERT_HOST (x_offset >= 0);
match_imline.init (x_size);
sum = 0;
if (image_w->get_ysize () < image_n->get_ysize ()) {
y_size = image_n->get_ysize ();
y_size2 = image_w->get_ysize ();
y_offset = (y_size - y_size2) / 2;
if (tessedit_display_mm && !tessedit_mm_use_prototypes)
tprintf ("I1 (%d, %d), I2 (%d, %d), MI (%d, %d)\n", x_size,
image_w->get_ysize (), x_size2, image_n->get_ysize (),
x_size, y_size);
match_image.create (x_size, y_size, 4);
for (y = 0; y < y_offset; y++) {
image_n->fast_get_line (0, y, x_size2, &imline_n);
for (x = 0; x < x_size2; x++) {
if (imline_n.pixels[x] == BINIM_BLACK) {
sum += -1;
match_imline.pixels[x] = UB_COLOUR;
}
else {
match_imline.pixels[x] = UW_COLOUR;
}
}
match_image.fast_put_line (x_offset, y, x_size2, &match_imline);
}
for (y = y_offset + y_size2; y < y_size; y++) {
image_n->fast_get_line (0, y, x_size2, &imline_n);
for (x = 0; x < x_size2; x++) {
if (imline_n.pixels[x] == BINIM_BLACK) {
sum += -1.0;
match_imline.pixels[x] = UB_COLOUR;
}
else {
match_imline.pixels[x] = UW_COLOUR;
}
}
match_image.fast_put_line (x_offset, y, x_size2, &match_imline);
}
for (y = y_offset; y < y_offset + y_size2; y++) {
image_w->fast_get_line (0, y - y_offset, x_size, &imline_w);
image_n->fast_get_line (0, y, x_size2, &imline_n);
for (x = 0; x < x_offset; x++) {
if (imline_w.pixels[x] == BINIM_BLACK) {
sum += -1.0;
match_imline.pixels[x] = BU_COLOUR;
}
else {
match_imline.pixels[x] = WU_COLOUR;
}
}
for (x = x_offset + x_size2; x < x_size; x++) {
if (imline_w.pixels[x] == BINIM_BLACK) {
sum += -1.0;
match_imline.pixels[x] = BU_COLOUR;
}
else {
match_imline.pixels[x] = WU_COLOUR;
}
}
for (x = x_offset; x < x_offset + x_size2; x++) {
if (imline_n.pixels[x - x_offset] == imline_w.pixels[x]) {
sum += 1.0;
if (imline_w.pixels[x] == BINIM_BLACK)
match_imline.pixels[x] = BB_COLOUR;
else
match_imline.pixels[x] = WW_COLOUR;
}
else {
sum += -1.0;
if (imline_w.pixels[x] == BINIM_BLACK)
match_imline.pixels[x] = BW_COLOUR;
else
match_imline.pixels[x] = WB_COLOUR;
}
}
match_image.fast_put_line (0, y, x_size, &match_imline);
}
}
else {
y_size = image_w->get_ysize ();
y_size2 = image_n->get_ysize ();
y_offset = (y_size - y_size2) / 2;
if (tessedit_display_mm && !tessedit_mm_use_prototypes)
tprintf ("I1 (%d, %d), I2 (%d, %d), MI (%d, %d)\n", x_size,
image_w->get_ysize (), x_size2, image_n->get_ysize (),
x_size, y_size);
match_image.create (x_size, y_size, 4);
for (y = 0; y < y_offset; y++) {
image_w->fast_get_line (0, y, x_size, &imline_w);
for (x = 0; x < x_size; x++) {
if (imline_w.pixels[x] == BINIM_BLACK) {
sum += -1;
match_imline.pixels[x] = BU_COLOUR;
}
else {
match_imline.pixels[x] = WU_COLOUR;
}
}
match_image.fast_put_line (0, y, x_size, &match_imline);
}
for (y = y_offset + y_size2; y < y_size; y++) {
image_w->fast_get_line (0, y, x_size, &imline_w);
for (x = 0; x < x_size; x++) {
if (imline_w.pixels[x] == BINIM_BLACK) {
sum += -1;
match_imline.pixels[x] = BU_COLOUR;
}
else {
match_imline.pixels[x] = WU_COLOUR;
}
}
match_image.fast_put_line (0, y, x_size, &match_imline);
}
for (y = y_offset; y < y_offset + y_size2; y++) {
image_w->fast_get_line (0, y, x_size, &imline_w);
image_n->fast_get_line (0, y - y_offset, x_size2, &imline_n);
for (x = 0; x < x_offset; x++) {
if (imline_w.pixels[x] == BINIM_BLACK) {
sum += -1.0;
match_imline.pixels[x] = BU_COLOUR;
}
else {
match_imline.pixels[x] = WU_COLOUR;
}
}
for (x = x_offset + x_size2; x < x_size; x++) {
if (imline_w.pixels[x] == BINIM_BLACK) {
sum += -1.0;
match_imline.pixels[x] = BU_COLOUR;
}
else {
match_imline.pixels[x] = WU_COLOUR;
}
}
for (x = x_offset; x < x_offset + x_size2; x++) {
if (imline_n.pixels[x - x_offset] == imline_w.pixels[x]) {
sum += 1.0;
if (imline_w.pixels[x] == BINIM_BLACK)
match_imline.pixels[x] = BB_COLOUR;
else
match_imline.pixels[x] = WW_COLOUR;
}
else {
sum += -1.0;
if (imline_w.pixels[x] == BINIM_BLACK)
match_imline.pixels[x] = BW_COLOUR;
else
match_imline.pixels[x] = WB_COLOUR;
}
}
match_image.fast_put_line (0, y, x_size, &match_imline);
}
}
#ifndef GRAPHICS_DISABLED
if (tessedit_display_mm && !tessedit_mm_use_prototypes) {
tprintf ("Match score %f\n", 1.0 - sum / (x_size * y_size));
display_images(image_w, image_n, &match_image);
}
#endif
if (tessedit_mm_debug)
tprintf ("Match score %f\n", 1.0 - sum / (x_size * y_size));
return (1.0 - sum / (x_size * y_size));
}
/*************************************************************************
* display_images()
*
* Show a pair of images, plus the match image
*
*************************************************************************/
#ifndef GRAPHICS_DISABLED
void display_images(IMAGE *image_w, IMAGE *image_n, IMAGE *match_image) {
ScrollView* w_im_window;
ScrollView* n_im_window;
ScrollView* match_window;
inT16 i;
w_im_window = new ScrollView("Image 1", 20, 100,
10 * image_w->get_xsize (), 10 * image_w->get_ysize (),
image_w->get_xsize (), image_w->get_ysize ());
sv_show_sub_image (image_w,
0, 0,
image_w->get_xsize (), image_w->get_ysize (),
w_im_window, 0, 0);
w_im_window->Pen(255,0,0);
for (i = 1; i < image_w->get_xsize (); i++) {
w_im_window->Line(i, 0, i, image_w->get_ysize ());
}
for (i = 1; i < image_w->get_ysize (); i++) {
w_im_window->Line(0, i, image_w->get_xsize (), i);
}
n_im_window = new ScrollView ("Image 2", 240, 100,
10 * image_n->get_xsize (), 10 * image_n->get_ysize (),
image_n->get_xsize (), image_n->get_ysize ());
sv_show_sub_image (image_n,
0, 0,
image_n->get_xsize (), image_n->get_ysize (),
n_im_window, 0, 0);
n_im_window->Pen(255,0,0);
for (i = 1; i < image_n->get_xsize (); i++) {
n_im_window->Line(i, 0, i, image_n->get_ysize ());
}
for (i = 1; i < image_n->get_ysize (); i++) {
n_im_window->Line(0, i, image_n->get_xsize (), i);
}
match_window = new ScrollView ("Match Result", 460, 100,
10 * match_image->get_xsize (), 10 * match_image->get_ysize (),
match_image->get_xsize (), match_image->get_ysize ());
match_window->Clear();
sv_show_sub_image (match_image,
0, 0,
match_image->get_xsize (), match_image->get_ysize (),
match_window, 0, 0);
match_window->Pen(255,0,0);
for (i = 1; i < match_image->get_xsize (); i++) {
match_window->Line(i, 0, i, match_image->get_ysize ());
}
for (i = 1; i < match_image->get_ysize (); i++) {
match_window->Line(0, i, match_image->get_xsize (), i);
}
SVEvent* sve = match_window->AwaitEvent(SVET_DESTROY);
delete sve;
delete w_im_window;
delete n_im_window;
delete match_window;
}
/*************************************************************************
* display_image()
*
* Show a single image
*
*************************************************************************/
ScrollView* display_image(IMAGE *image,
const char *title,
inT32 x,
inT32 y,
BOOL8 wait) {
ScrollView* im_window;
inT16 i;
im_window = new ScrollView (title, x, y,
10 * image->get_xsize (), 10 * image->get_ysize (),
image->get_xsize (), image->get_ysize ());
sv_show_sub_image (image,
0, 0,
image->get_xsize (), image->get_ysize (), im_window, 0, 0);
im_window->Pen(255,0,0);
for (i = 1; i < image->get_xsize (); i++) {
im_window->SetCursor(i, 0);
im_window->DrawTo(i, image->get_ysize());
}
for (i = 1; i < image->get_ysize (); i++) {
im_window->SetCursor(0, i);
im_window->DrawTo(image->get_xsize(),i);
}
if (wait) { delete im_window->AwaitEvent(SVET_CLICK); }
return im_window;
}
#endif

View File

@ -1,48 +0,0 @@
/**********************************************************************
* File: matmatch.h (Formerly matrix_match.h)
* Description: matrix matching routines for Tessedit
* Author: Chris Newton
* Created: Wed Nov 24 15:57:41 GMT 1993
*
* (C) Copyright 1993, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
#ifndef MATMATCH_H
#define MATMATCH_H
#include "img.h"
#include "hosthplb.h"
#include "notdll.h"
#define BINIM_BLACK 0
#define BINIM_WHITE 1
#define BAD_MATCH 9999.0
extern BOOL_VAR_H (tessedit_display_mm, FALSE, "Display matrix matches");
extern BOOL_VAR_H (tessedit_mm_debug, FALSE,
"Print debug information for matrix matcher");
extern INT_VAR_H (tessedit_mm_prototype_min_size, 3,
"Smallest number of samples in a cluster for a prototype to be used");
float matrix_match( // returns match score
IMAGE *image1,
IMAGE *image2);
float match1( /* returns match score */
IMAGE *image_w,
IMAGE *image_n);
void display_images(IMAGE *image_w, IMAGE *image_n, IMAGE *match_image);
ScrollView* display_image(IMAGE *image,
const char *title,
inT32 x,
inT32 y,
BOOL8 wait);
#endif

View File

@ -2,6 +2,7 @@
// File: osdetect.cpp
// Description: Orientation and script detection.
// Author: Samuel Charron
// Ranjith Unnikrishnan
//
// (C) Copyright 2008, Google Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
@ -18,24 +19,25 @@
#include "osdetect.h"
#include "strngs.h"
#include "blobbox.h"
#include "blread.h"
#include "tordmain.h"
#include "ratngs.h"
#include "colfind.h"
#include "imagefind.h"
#include "linefind.h"
#include "oldlist.h"
#include "adaptmatch.h"
#include "tstruct.h"
#include "expandblob.h"
#include "tesseractclass.h"
#include "qrsequence.h"
extern IMAGE page_image;
#include "ratngs.h"
#include "strngs.h"
#include "tabvector.h"
#include "tesseractclass.h"
#include "textord.h"
#include "tstruct.h"
const int kMinCharactersToTry = 50;
const int kMaxCharactersToTry = 5 * kMinCharactersToTry;
const float kSizeRatioToReject = 2.0;
const int kMinAcceptableBlobHeight = 10;
const float kOrientationAcceptRatio = 1.3;
const float kScriptAcceptRatio = 1.3;
@ -43,8 +45,6 @@ const float kScriptAcceptRatio = 1.3;
const float kHanRatioInKorean = 0.7;
const float kHanRatioInJapanese = 0.3;
const float kLatinRationInFraktur = 0.7;
const float kNonAmbiguousMargin = 1.0;
// General scripts
@ -59,45 +59,140 @@ const char* ScriptDetector::korean_script_ = "Korean";
const char* ScriptDetector::japanese_script_ = "Japanese";
const char* ScriptDetector::fraktur_script_ = "Fraktur";
CLISTIZEH(BLOBNBOX);
CLISTIZE(BLOBNBOX);
// Minimum believable resolution.
const int kMinCredibleResolution = 70;
// Default resolution used if input is not believable.
const int kDefaultResolution = 300;
void OSResults::update_best_orientation() {
float first = orientations[0];
float second = orientations[1];
best_result.orientation_id = 0;
if (orientations[0] < orientations[1]) {
first = orientations[1];
second = orientations[0];
best_result.orientation_id = 1;
}
for (int i = 2; i < 4; ++i) {
if (orientations[i] > first) {
second = first;
first = orientations[i];
best_result.orientation_id = i;
} else if (orientations[i] > second) {
second = orientations[i];
}
}
// Store difference of top two orientation scores.
best_result.oconfidence = first - second;
}
void OSResults::set_best_orientation(int orientation_id) {
best_result.orientation_id = orientation_id;
best_result.oconfidence = 0;
}
void OSResults::update_best_script(int orientation) {
// We skip index 0 to ignore the "Common" script.
float first = scripts_na[orientation][1];
float second = scripts_na[orientation][2];
best_result.script_id = 1;
if (scripts_na[orientation][1] < scripts_na[orientation][2]) {
first = scripts_na[orientation][2];
second = scripts_na[orientation][1];
best_result.script_id = 2;
}
for (int i = 3; i < kMaxNumberOfScripts; ++i) {
if (scripts_na[orientation][i] > first) {
best_result.script_id = i;
second = first;
first = scripts_na[orientation][i];
} else if (scripts_na[orientation][i] > second) {
second = scripts_na[orientation][i];
}
}
best_result.sconfidence =
(first / second - 1.0) / (kScriptAcceptRatio - 1.0);
}
// Detect and erase horizontal/vertical lines and picture regions from the
// image, so that non-text blobs are removed from consideration.
void remove_nontext_regions(tesseract::Tesseract *tess, BLOCK_LIST *blocks,
TO_BLOCK_LIST *to_blocks) {
Pix *pix = tess->pix_binary();
ASSERT_HOST(pix != NULL);
int vertical_x = 0;
int vertical_y = 1;
tesseract::TabVector_LIST v_lines;
tesseract::TabVector_LIST h_lines;
Boxa* boxa = NULL;
Pixa* pixa = NULL;
const int kMinCredibleResolution = 70;
int resolution = (kMinCredibleResolution > pixGetXRes(pix)) ?
kMinCredibleResolution : pixGetXRes(pix);
tesseract::LineFinder::FindVerticalLines(resolution, pix, &vertical_x,
&vertical_y, &v_lines);
tesseract::LineFinder::FindHorizontalLines(resolution, pix, &h_lines);
tesseract::ImageFinder::FindImages(pix, &boxa, &pixa);
pixaDestroy(&pixa);
boxaDestroy(&boxa);
tess->mutable_textord()->find_components(tess->pix_binary(),
blocks, to_blocks);
}
// Find connected components in the page and process a subset until finished or
// a stopping criterion is met.
// Returns true if the page was successfully processed.
bool orientation_and_script_detection(STRING& filename,
OSResults* osr,
tesseract::Tesseract* tess) {
// Returns the number of blobs used in making the estimate. 0 implies failure.
int orientation_and_script_detection(STRING& filename,
OSResults* osr,
tesseract::Tesseract* tess) {
STRING name = filename; //truncated name
const char *lastdot; //of name
TO_BLOCK_LIST land_blocks, port_blocks;
BLOCK_LIST blocks;
TBOX page_box;
lastdot = strrchr (name.string (), '.');
if (lastdot != NULL)
name[lastdot-name.string()] = '\0';
if (!read_unlv_file(name, page_image.get_xsize(), page_image.get_ysize(),
&blocks))
FullPageBlock(page_image.get_xsize(), page_image.get_ysize(), &blocks);
find_components(&blocks, &land_blocks, &port_blocks, &page_box);
ASSERT_HOST(tess->pix_binary() != NULL)
int width = pixGetWidth(tess->pix_binary());
int height = pixGetHeight(tess->pix_binary());
int resolution = pixGetXRes(tess->pix_binary());
// Zero resolution messes up the algorithms, so make sure it is credible.
if (resolution < kMinCredibleResolution)
resolution = kDefaultResolution;
BLOCK_LIST blocks;
if (!read_unlv_file(name, width, height, &blocks))
FullPageBlock(width, height, &blocks);
// Try to remove non-text regions from consideration.
TO_BLOCK_LIST land_blocks, port_blocks;
remove_nontext_regions(tess, &blocks, &port_blocks);
if (port_blocks.empty()) {
// page segmentation did not succeed, so we need to find_components first.
tess->mutable_textord()->find_components(tess->pix_binary(),
&blocks, &port_blocks);
} else {
page_box.set_left(0);
page_box.set_bottom(0);
page_box.set_right(width);
page_box.set_top(height);
// Filter_blobs sets up the TO_BLOCKs the same as find_components does.
tess->mutable_textord()->filter_blobs(page_box.topright(),
&port_blocks, true);
}
return os_detect(&port_blocks, osr, tess);
}
// Filter and sample the blobs.
// Returns true if the page was successfully processed, or false if the page had
// too few characters to be reliable
bool os_detect(TO_BLOCK_LIST* port_blocks, OSResults* osr,
tesseract::Tesseract* tess) {
// Returns a non-zero number of blobs if the page was successfully processed, or
// zero if the page had too few characters to be reliable
int os_detect(TO_BLOCK_LIST* port_blocks, OSResults* osr,
tesseract::Tesseract* tess) {
int blobs_total = 0;
OSResults osr_;
if (osr == NULL)
osr = &osr_;
osr->unicharset = &tess->unicharset;
OrientationDetector o(osr);
ScriptDetector s(osr, tess);
TO_BLOCK_IT block_it;
block_it.set_to_list(port_blocks);
@ -106,9 +201,11 @@ bool os_detect(TO_BLOCK_LIST* port_blocks, OSResults* osr,
for (block_it.mark_cycle_pt(); !block_it.cycled_list();
block_it.forward ()) {
TO_BLOCK* block = block_it.data();
TO_BLOCK* to_block = block_it.data();
if (to_block->block->poly_block() &&
!to_block->block->poly_block()->IsText()) continue;
BLOBNBOX_IT bbox_it;
bbox_it.set_to_list(&block->blobs);
bbox_it.set_to_list(&to_block->blobs);
for (bbox_it.mark_cycle_pt (); !bbox_it.cycled_list ();
bbox_it.forward ()) {
BLOBNBOX* bbox = bbox_it.data();
@ -122,22 +219,36 @@ bool os_detect(TO_BLOCK_LIST* port_blocks, OSResults* osr,
float ratio = x_y > y_x ? x_y : y_x;
// Blob is ambiguous
if (ratio > kSizeRatioToReject) continue;
if (box.height() < 10) continue;
if (box.height() < kMinAcceptableBlobHeight) continue;
filtered_it.add_to_end(bbox);
}
}
if (filtered_it.length() > 0)
filtered_it.move_to_first();
return os_detect_blobs(&filtered_list, osr, tess);
}
// Detect orientation and script from a list of blobs.
// Returns a non-zero number of blobs if the list was successfully processed, or
// zero if the list had too few characters to be reliable
int os_detect_blobs(BLOBNBOX_CLIST* blob_list, OSResults* osr,
tesseract::Tesseract* tess) {
OSResults osr_;
if (osr == NULL)
osr = &osr_;
osr->unicharset = &tess->unicharset;
OrientationDetector o(osr);
ScriptDetector s(osr, tess);
BLOBNBOX_C_IT filtered_it(blob_list);
int real_max = MIN(filtered_it.length(), kMaxCharactersToTry);
printf("Total blobs found = %d\n", blobs_total);
printf("Number of blobs post-filtering = %d\n", filtered_it.length());
printf("Number of blobs to try = %d\n", real_max);
// printf("Total blobs found = %d\n", blobs_total);
// printf("Number of blobs post-filtering = %d\n", filtered_it.length());
// printf("Number of blobs to try = %d\n", real_max);
// If there are too few characters, skip this page entirely.
if (real_max < kMinCharactersToTry / 2) {
printf("Too few characters. Skipping this page\n");
return false;
return 0;
}
BLOBNBOX** blobs = new BLOBNBOX*[filtered_it.length()];
@ -147,18 +258,20 @@ bool os_detect(TO_BLOCK_LIST* port_blocks, OSResults* osr,
blobs[number_of_blobs++] = (BLOBNBOX*)filtered_it.data();
}
QRSequenceGenerator sequence(number_of_blobs);
int num_blobs_evaluated = 0;
for (int i = 0; i < real_max; ++i) {
if (os_detect_blob(blobs[sequence.GetVal()], &o, &s, osr, tess)
&& i > kMinCharactersToTry) {
break;
}
++num_blobs_evaluated;
}
delete [] blobs;
// Make sure the best_result is up-to-date
int orientation = o.get_orientation();
s.update_best_script(orientation);
return true;
osr->update_best_script(orientation);
return num_blobs_evaluated;
}
// Processes a single blob to estimate script and orientation.
@ -173,39 +286,40 @@ bool os_detect_blob(BLOBNBOX* bbox, OrientationDetector* o,
int x_mid = (box.left() + box.right()) / 2.0f;
int y_mid = (box.bottom() + box.top()) / 2.0f;
PBLOB pblob(blob, box.height());
PBLOB pblob(blob);
BLOB_CHOICE_LIST ratings[4];
// Test the 4 orientations
for (int i = 0; i < 4; ++i) {
// normalize the blob
float scaling = static_cast<float>(kBlnXHeight) / box.height();
DENORM denorm(x_mid, scaling, 0.0, box.bottom(), 0, NULL, false, NULL);
pblob.move(FCOORD(-x_mid, -box.bottom()));
pblob.scale(static_cast<float>(bln_x_height) / box.height());
pblob.move(FCOORD(0.0f, bln_baseline_offset));
pblob.scale(scaling);
pblob.move(FCOORD(0.0f, kBlnBaselineOffset));
{
// List of choices given by the classifier
TBLOB *tessblob; //converted blob
TEXTROW tessrow; //dummy row
tess_cn_matching.set_value(true); // turn it on
tess_bn_matching.set_value(false);
//convert blob
tessblob = make_tess_blob (&pblob, TRUE);
//make dummy row
make_tess_row(NULL, &tessrow);
//classify
tess->AdaptiveClassifier (tessblob, NULL, &tessrow, ratings + i, NULL);
free_blob(tessblob);
tess->tess_cn_matching.set_value(true); // turn it on
tess->tess_bn_matching.set_value(false);
// Convert blob
TBLOB* tessblob = make_tess_blob(&pblob);
// Classify
tess->set_denorm(&denorm);
tess->AdaptiveClassifier(tessblob, ratings + i, NULL);
delete tessblob;
}
// undo normalize
pblob.move(FCOORD(0.0f, -bln_baseline_offset));
pblob.scale(1.0f / (static_cast<float>(bln_x_height) / box.height()));
pblob.move(FCOORD(0.0f, -kBlnBaselineOffset));
pblob.scale(1.0f / scaling);
pblob.move(FCOORD(x_mid, box.bottom()));
// center the blob
pblob.move(FCOORD(-x_mid, -y_mid));
// TODO(rays) Although we should now get the correct image coords with
// the DENORM, there is nothing to tell the classifier to rotate the
// image or to actually rotate the image for it.
// Rotate it
pblob.rotate();
@ -233,14 +347,24 @@ OrientationDetector::OrientationDetector(OSResults* osr) {
// Score the given blob and return true if it is now sure of the orientation
// after adding this block.
bool OrientationDetector::detect_blob(BLOB_CHOICE_LIST* scores) {
float blob_o_score[4] = {0.0, 0.0, 0.0, 0.0};
float total_blob_o_score = 0.0;
for (int i = 0; i < 4; ++i) {
BLOB_CHOICE_IT choice_it;
choice_it.set_to_list(scores + i);
if (!choice_it.empty()) {
osr_->orientations[i] += (100 + choice_it.data()->certainty());
// The certainty score ranges between [-20,0]. This is converted here to
// [0,1], with 1 indicating best match.
blob_o_score[i] = 1 + 0.05 * choice_it.data()->certainty();
total_blob_o_score += blob_o_score[i];
}
}
// Normalize the orientation scores for the blob and use them to
// update the aggregated orientation score.
for (int i = 0; total_blob_o_score != 0 && i < 4; ++i) {
osr_->orientations[i] += log(blob_o_score[i] / total_blob_o_score);
}
float first = -1;
float second = -1;
@ -259,35 +383,9 @@ bool OrientationDetector::detect_blob(BLOB_CHOICE_LIST* scores) {
return first / second > kOrientationAcceptRatio;
}
void OrientationDetector::update_best_orientation() {
float first = osr_->orientations[0];
float second = osr_->orientations[1];
if (first < second) {
second = first;
first = osr_->orientations[1];
}
osr_->best_result.orientation = 0;
osr_->best_result.oconfidence = 0;
for (int i = 0; i < 4; ++i) {
if (osr_->orientations[i] > first) {
second = first;
first = osr_->orientations[i];
osr_->best_result.orientation = i;
} else if (osr_->orientations[i] > second) {
second = osr_->orientations[i];
}
}
osr_->best_result.oconfidence =
(first / second - 1.0) / (kOrientationAcceptRatio - 1.0);
}
int OrientationDetector::get_orientation() {
update_best_orientation();
return osr_->best_result.orientation;
osr_->update_best_orientation();
return osr_->best_result.orientation_id;
}
@ -347,7 +445,7 @@ void ScriptDetector::detect_blob(BLOB_CHOICE_LIST* scores) {
prev_class_id = choice->unichar_id();
prev_config = choice->config();
} else if (-choice->certainty() < prev_score + kNonAmbiguousMargin) {
script_count++;
++script_count;
next_best_score = -choice->certainty();
next_best_script_id = choice->script_id();
next_best_unichar = tess_->unicharset.id_to_unichar(choice->unichar_id());
@ -365,7 +463,7 @@ void ScriptDetector::detect_blob(BLOB_CHOICE_LIST* scores) {
// Character is non ambiguous
if (script_count == 1) {
// Update the score of the winning script
osr_->scripts_na[i][prev_id] += 1;
osr_->scripts_na[i][prev_id] += 1.0;
// Workaround for Fraktur
if (prev_id == latin_id_) {
@ -379,19 +477,19 @@ void ScriptDetector::detect_blob(BLOB_CHOICE_LIST* scores) {
// fi.is_serif(), fi.is_fraktur(),
// prev_unichar);
if (fi.is_fraktur()) {
osr_->scripts_na[i][prev_id] -= 1;
osr_->scripts_na[i][fraktur_id_] += 1;
osr_->scripts_na[i][prev_id] -= 1.0;
osr_->scripts_na[i][fraktur_id_] += 1.0;
}
}
}
// Update Japanese / Korean pseudo-scripts
if (prev_id == katakana_id_)
osr_->scripts_na[i][japanese_id_] += 1;
osr_->scripts_na[i][japanese_id_] += 1.0;
if (prev_id == hiragana_id_)
osr_->scripts_na[i][japanese_id_] += 1;
osr_->scripts_na[i][japanese_id_] += 1.0;
if (prev_id == hangul_id_)
osr_->scripts_na[i][korean_id_] += 1;
osr_->scripts_na[i][korean_id_] += 1.0;
if (prev_id == han_id_)
osr_->scripts_na[i][korean_id_] += kHanRatioInKorean;
if (prev_id == han_id_)
@ -401,27 +499,24 @@ void ScriptDetector::detect_blob(BLOB_CHOICE_LIST* scores) {
}
bool ScriptDetector::must_stop(int orientation) {
update_best_script(orientation);
osr_->update_best_script(orientation);
return osr_->best_result.sconfidence > 1;
}
void ScriptDetector::update_best_script(int orientation) {
float first = -1;
float second = -1;
// i = 1 -> ignore Common scripts
for (int i = 1; i < kMaxNumberOfScripts; ++i) {
if (osr_->scripts_na[orientation][i] > first) {
osr_->best_result.script =
tess_->unicharset.get_script_from_script_id(i);
second = first;
first = osr_->scripts_na[orientation][i];
} else if (osr_->scripts_na[orientation][i] > second) {
second = osr_->scripts_na[orientation][i];
}
// Helper method to convert an orientation index to its value in degrees.
// The value represents the amount of clockwise rotation in degrees that must be
// applied for the text to be upright (readable).
const int OrientationIdToValue(const int& id) {
switch (id) {
case 0:
return 0;
case 1:
return 270;
case 2:
return 180;
case 3:
return 90;
default:
return -1;
}
osr_->best_result.sconfidence =
(first / second - 1.0) / (kOrientationAcceptRatio - 1.0);
}

View File

@ -2,6 +2,7 @@
// File: osdetect.h
// Description: Orientation and script detection.
// Author: Samuel Charron
// Ranjith Unnikrishnan
//
// (C) Copyright 2008, Google Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
@ -25,6 +26,7 @@
class TO_BLOCK_LIST;
class BLOBNBOX;
class BLOB_CHOICE_LIST;
class BLOBNBOX_CLIST;
namespace tesseract {
class Tesseract;
@ -34,8 +36,10 @@ class Tesseract;
const int kMaxNumberOfScripts = 116 + 1 + 2 + 1;
struct OSBestResult {
int orientation;
const char* script;
OSBestResult() : orientation_id(0), script_id(0), sconfidence(0.0),
oconfidence(0.0) {}
int orientation_id;
int script_id;
float sconfidence;
float oconfidence;
};
@ -48,7 +52,16 @@ struct OSResults {
orientations[i] = 0;
}
}
void update_best_orientation();
void set_best_orientation(int orientation_id);
void update_best_script(int orientation_id);
// Array holding scores for each orientation id [0,3].
// Orientation ids [0..3] map to [0, 270, 180, 90] degree orientations of the
// page respectively, where the values refer to the amount of clockwise
// rotation to be applied to the page for the text to be upright and readable.
float orientations[4];
// Script confidence scores for each of 4 possible orientations.
float scripts_na[4][kMaxNumberOfScripts];
UNICHARSET* unicharset;
@ -59,7 +72,6 @@ class OrientationDetector {
public:
OrientationDetector(OSResults*);
bool detect_blob(BLOB_CHOICE_LIST* scores);
void update_best_orientation();
int get_orientation();
private:
OSResults* osr_;
@ -69,7 +81,6 @@ class ScriptDetector {
public:
ScriptDetector(OSResults*, tesseract::Tesseract* tess);
void detect_blob(BLOB_CHOICE_LIST* scores);
void update_best_script(int);
void get_script() ;
bool must_stop(int orientation);
private:
@ -88,15 +99,25 @@ class ScriptDetector {
tesseract::Tesseract* tess_;
};
bool orientation_and_script_detection(STRING& filename,
OSResults*,
tesseract::Tesseract*);
int orientation_and_script_detection(STRING& filename,
OSResults*,
tesseract::Tesseract*);
bool os_detect(TO_BLOCK_LIST* port_blocks,
OSResults* osr,
tesseract::Tesseract* tess);
int os_detect(TO_BLOCK_LIST* port_blocks,
OSResults* osr,
tesseract::Tesseract* tess);
int os_detect_blobs(BLOBNBOX_CLIST* blob_list,
OSResults* osr,
tesseract::Tesseract* tess);
bool os_detect_blob(BLOBNBOX* bbox, OrientationDetector* o,
ScriptDetector* s, OSResults*,
tesseract::Tesseract* tess);
// Helper method to convert an orientation index to its value in degrees.
// The value represents the amount of clockwise rotation in degrees that must be
// applied for the text to be upright (readable).
const int OrientationIdToValue(const int& id);
#endif // TESSERACT_CCMAIN_OSDETECT_H__

View File

@ -22,28 +22,25 @@
#endif
#include "mfcpch.h"
#include "ocrshell.h"
#include <string.h>
#include <ctype.h>
#include <string.h>
#include <ctype.h>
#ifdef __UNIX__
#include <assert.h>
#include <unistd.h>
#include <errno.h>
#include <errno.h>
#endif
#include "mainblk.h"
#include "tfacep.h"
#include "tessvars.h"
#include "control.h"
#include "secname.h"
#include "reject.h"
#include "docqual.h"
#include "output.h"
#include "helpers.h"
#include "tfacep.h"
#include "tessvars.h"
#include "control.h"
#include "secname.h"
#include "reject.h"
#include "docqual.h"
#include "output.h"
#include "bestfirst.h"
#include "globals.h"
#include "tesseractclass.h"
#define EXTERN
#define EPAPER_EXT ".ep"
#define PAGE_YSIZE 3508
#define CTRL_INSET '\024' //dc4=text inset
@ -54,44 +51,6 @@
#define CTRL_NEWLINE '\012' //newline
#define CTRL_HARDLINE '\015' //cr
EXTERN BOOL_EVAR (tessedit_write_block_separators, FALSE,
"Write block separators in output");
EXTERN BOOL_VAR (tessedit_write_raw_output, FALSE,
"Write raw stuff to name.raw");
EXTERN BOOL_EVAR (tessedit_write_output, FALSE, "Write text to name.txt");
EXTERN BOOL_EVAR (tessedit_write_ratings, FALSE,
"Return ratings in IPEOCRAPI data");
EXTERN BOOL_EVAR (tessedit_write_txt_map, FALSE,
"Write .txt to .etx map file");
EXTERN BOOL_EVAR (tessedit_write_rep_codes, FALSE,
"Write repetition char code");
EXTERN BOOL_EVAR (tessedit_write_unlv, FALSE, "Write .unlv output file");
EXTERN STRING_EVAR (unrecognised_char, "|",
"Output char for unidentified blobs");
EXTERN INT_EVAR (suspect_level, 99, "Suspect marker level");
EXTERN INT_VAR (suspect_space_level, 100,
"Min suspect level for rejecting spaces");
EXTERN INT_VAR (suspect_short_words, 2,
"Dont Suspect dict wds longer than this");
EXTERN BOOL_VAR (suspect_constrain_1Il, FALSE,
"UNLV keep 1Il chars rejected");
EXTERN double_VAR (suspect_rating_per_ch, 999.9,
"Dont touch bad rating limit");
EXTERN double_VAR (suspect_accept_rating, -999.9, "Accept good rating limit");
EXTERN BOOL_EVAR (tessedit_minimal_rejection, FALSE,
"Only reject tess failures");
EXTERN BOOL_VAR (tessedit_zero_rejection, FALSE, "Dont reject ANYTHING");
EXTERN BOOL_VAR (tessedit_word_for_word, FALSE,
"Make output have exactly one word per WERD");
EXTERN BOOL_VAR (tessedit_zero_kelvin_rejection, FALSE,
"Dont reject ANYTHING AT ALL");
EXTERN BOOL_VAR (tessedit_consistent_reps, TRUE,
"Force all rep chars the same");
FILE *txt_mapfile = NULL; //reject map
FILE *unlv_file = NULL; //reject map
/**********************************************************************
* pixels_to_pts
*
@ -112,17 +71,13 @@ inT32 pixels_to_pts( //convert coords
namespace tesseract {
void Tesseract::output_pass( //Tess output pass //send to api
PAGE_RES_IT &page_res_it,
BOOL8 write_to_shm,
TBOX *target_word_box) {
const TBOX *target_word_box) {
BLOCK_RES *block_of_last_word;
inT16 block_id;
BOOL8 force_eol; //During output
BLOCK *nextblock; //block of next word
WERD *nextword; //next word
if (tessedit_write_txt_map)
txt_mapfile = open_outfile (".map");
page_res_it.restart_page ();
block_of_last_word = NULL;
while (page_res_it.word () != NULL) {
@ -144,9 +99,6 @@ void Tesseract::output_pass( //Tess output pass //send to api
block_of_last_word != page_res_it.block ()) {
block_of_last_word = page_res_it.block ();
block_id = block_of_last_word->block->index();
if (!wordrec_no_block)
fprintf (textfile, "|^~tr%d\n", block_id);
fprintf (txt_mapfile, "|^~tr%d\n", block_id);
}
force_eol = (tessedit_write_block_separators &&
@ -162,23 +114,11 @@ void Tesseract::output_pass( //Tess output pass //send to api
else
nextblock = NULL;
//regardless of tilde crunching
write_results (page_res_it, determine_newline_type (page_res_it.word ()->word, page_res_it.block ()->block, nextword, nextblock), force_eol,
write_to_shm);
page_res_it.forward ();
}
if (write_to_shm)
ocr_send_text(FALSE);
if (tessedit_write_block_separators) {
if (!wordrec_no_block)
fprintf (textfile, "|^~tr\n");
fprintf (txt_mapfile, "|^~tr\n");
}
if (tessedit_write_txt_map) {
fprintf (txt_mapfile, "\n"); //because txt gets one
#ifdef __UNIX__
fsync (fileno (txt_mapfile));
#endif
fclose(txt_mapfile);
write_results(page_res_it,
determine_newline_type(page_res_it.word()->word,
page_res_it.block()->block,
nextword, nextblock), force_eol);
page_res_it.forward();
}
}
@ -195,18 +135,10 @@ void Tesseract::output_pass( //Tess output pass //send to api
* inset list - a list of bounding boxes of reject insets - indexed by the
* reject strings in the epchoice text.
*************************************************************************/
void Tesseract::write_results( //output a word
//full info
PAGE_RES_IT &page_res_it,
char newline_type, //type of newline
//override tilde crunch?
BOOL8 force_eol,
BOOL8 write_to_shm //send to api
) {
//word to do
WERD_RES *word = page_res_it.word ();
// WERD_CHOICE *ep_choice; //ep format
void Tesseract::write_results(PAGE_RES_IT &page_res_it,
char newline_type, // type of newline
BOOL8 force_eol) { // override tilde crunch?
WERD_RES *word = page_res_it.word();
STRING repetition_code;
const STRING *wordstr;
STRING wordstr_lengths;
@ -217,49 +149,34 @@ void Tesseract::write_results( //output a word
char txt_chs[32]; //Only for unlv_tilde_crunch
char map_chs[32]; //Only for unlv_tilde_crunch
int txt_index = 0;
static BOOL8 tilde_crunch_written = FALSE;
static BOOL8 last_char_was_newline = TRUE;
static BOOL8 last_char_was_tilde = FALSE;
static BOOL8 empty_block = TRUE;
BOOL8 need_reject = FALSE;
PBLOB_IT blob_it; //blobs
UNICHAR_ID space = unicharset.unichar_to_id(" ");
/* if (word->best_choice->string().length() == 0)
{
tprintf("No output: to output\n");
}
else if (word->best_choice->string()[0]==' ')
{
tprintf("spaceword to output\n");
}
else if (word->best_choice->string()[0]=='\0')
{
tprintf("null to output\n");
}*/
if (word->unlv_crunch_mode != CR_NONE
&& !tessedit_zero_kelvin_rejection && !tessedit_word_for_word) {
if ((word->unlv_crunch_mode != CR_NONE ||
word->best_choice->length() == 0) &&
!tessedit_zero_kelvin_rejection && !tessedit_word_for_word) {
if ((word->unlv_crunch_mode != CR_DELETE) &&
(!tilde_crunch_written ||
((word->unlv_crunch_mode == CR_KEEP_SPACE) &&
(word->word->space () > 0) &&
!word->word->flag (W_FUZZY_NON) &&
!word->word->flag (W_FUZZY_SP)))) {
(!stats_.tilde_crunch_written ||
((word->unlv_crunch_mode == CR_KEEP_SPACE) &&
(word->word->space () > 0) &&
!word->word->flag (W_FUZZY_NON) &&
!word->word->flag (W_FUZZY_SP)))) {
if (!word->word->flag (W_BOL) &&
(word->word->space () > 0) &&
!word->word->flag (W_FUZZY_NON) &&
!word->word->flag (W_FUZZY_SP)) {
/* Write a space to separate from preceeding good text */
(word->word->space () > 0) &&
!word->word->flag (W_FUZZY_NON) &&
!word->word->flag (W_FUZZY_SP)) {
// Write a space to separate from preceeding good text.
txt_chs[txt_index] = ' ';
map_chs[txt_index++] = '1';
ep_chars[ep_chars_index++] = ' ';
last_char_was_tilde = FALSE;
stats_.last_char_was_tilde = false;
}
need_reject = TRUE;
}
if ((need_reject && !last_char_was_tilde) || (force_eol && empty_block)) {
if ((need_reject && !stats_.last_char_was_tilde) ||
(force_eol && stats_.write_results_empty_block)) {
/* Write a reject char - mark as rejected unless zero_rejection mode */
last_char_was_tilde = TRUE;
stats_.last_char_was_tilde = TRUE;
txt_chs[txt_index] = unrecognised;
if (tessedit_zero_rejection || (suspect_level == 0)) {
map_chs[txt_index++] = '1';
@ -271,8 +188,7 @@ void Tesseract::write_results( //output a word
The ep_choice string is a faked reject to allow newdiff to sync the
.etx with the .txt and .map files.
*/
ep_chars[ep_chars_index++] = CTRL_INSET;
//escape code
ep_chars[ep_chars_index++] = CTRL_INSET; // escape code
//dummy reject
ep_chars[ep_chars_index++] = 1;
//dummy reject
@ -284,12 +200,12 @@ void Tesseract::write_results( //output a word
//dummy reject
ep_chars[ep_chars_index++] = 1;
}
tilde_crunch_written = TRUE;
last_char_was_newline = FALSE;
empty_block = FALSE;
stats_.tilde_crunch_written = true;
stats_.last_char_was_newline = false;
stats_.write_results_empty_block = false;
}
if ((word->word->flag (W_EOL) && !last_char_was_newline) || force_eol) {
if ((word->word->flag (W_EOL) && !stats_.last_char_was_newline) || force_eol) {
/* Add a new line output */
txt_chs[txt_index] = '\n';
map_chs[txt_index++] = '\n';
@ -297,70 +213,63 @@ void Tesseract::write_results( //output a word
ep_chars[ep_chars_index++] = newline_type;
//Cos of the real newline
tilde_crunch_written = FALSE;
last_char_was_newline = TRUE;
last_char_was_tilde = FALSE;
stats_.tilde_crunch_written = false;
stats_.last_char_was_newline = true;
stats_.last_char_was_tilde = false;
}
txt_chs[txt_index] = '\0';
map_chs[txt_index] = '\0';
//xiaofan
if (tessedit_write_output && !wordrec_no_block)
fprintf (textfile, "%s", txt_chs);
if (tessedit_write_txt_map)
fprintf (txt_mapfile, "%s", map_chs);
//terminate string
ep_chars[ep_chars_index] = '\0';
ep_chars[ep_chars_index] = '\0'; // terminate string
word->ep_choice = new WERD_CHOICE(ep_chars, unicharset);
if (force_eol)
empty_block = TRUE;
stats_.write_results_empty_block = true;
return;
}
/* NORMAL PROCESSING of non tilde crunched words */
tilde_crunch_written = FALSE;
stats_.tilde_crunch_written = false;
if (newline_type)
last_char_was_newline = TRUE;
stats_.last_char_was_newline = true;
else
last_char_was_newline = FALSE;
empty_block = force_eol; //About to write a real word
stats_.last_char_was_newline = false;
stats_.write_results_empty_block = force_eol; // about to write a real word
if (unlv_tilde_crunching &&
last_char_was_tilde &&
stats_.last_char_was_tilde &&
(word->word->space() == 0) &&
!(word->word->flag(W_REP_CHAR) && tessedit_write_rep_codes) &&
(word->best_choice->unichar_id(0) == space)) {
/* Prevent adjacent tilde across words - we know that adjacent tildes within
words have been removed */
word->best_choice->remove_unichar_id(0);
if (word->best_choice->blob_choices() != NULL) {
BLOB_CHOICE_LIST_C_IT blob_choices_it(word->best_choice->blob_choices());
if (!blob_choices_it.empty()) delete blob_choices_it.extract();
}
word->best_choice->populate_unichars(getDict().getUnicharset());
word->reject_map.remove_pos (0);
blob_it = word->outword->blob_list ();
delete blob_it.extract (); //get rid of reject blob
delete word->box_word;
word->box_word = new BoxWord;
}
if (newline_type ||
(word->word->flag (W_REP_CHAR) && tessedit_write_rep_codes))
last_char_was_tilde = FALSE;
stats_.last_char_was_tilde = false;
else {
if (word->reject_map.length () > 0) {
if (word->best_choice->unichar_id(word->reject_map.length() - 1) == space)
last_char_was_tilde = TRUE;
stats_.last_char_was_tilde = true;
else
last_char_was_tilde = FALSE;
stats_.last_char_was_tilde = false;
}
else if (word->word->space () > 0)
last_char_was_tilde = FALSE;
stats_.last_char_was_tilde = false;
/* else it is unchanged as there are no output chars */
}
ASSERT_HOST (word->best_choice->length() == word->reject_map.length());
if (word->word->flag (W_REP_CHAR) && tessedit_consistent_reps)
ensure_rep_chars_are_consistent(word);
set_unlv_suspects(word);
check_debug_pt (word, 120);
if (tessedit_rejection_debug) {
@ -368,21 +277,13 @@ void Tesseract::write_results( //output a word
word->best_choice->debug_string(unicharset).string(),
dict_word(*(word->best_choice)));
}
#if 0
if (tessedit_write_unlv) {
write_unlv_text(word);
}
#endif
if (word->word->flag (W_REP_CHAR) && tessedit_write_rep_codes) {
repetition_code = "|^~R";
wordstr_lengths = "\001\001\001\001";
repetition_code += unicharset.id_to_unichar(get_rep_char (word));
wordstr_lengths += strlen(unicharset.id_to_unichar(get_rep_char (word)));
wordstr = &repetition_code;
}
else {
} else {
if (tessedit_zero_rejection) {
/* OVERRIDE ALL REJECTION MECHANISMS - ONLY REJECT TESS FAILURES */
for (i = 0; i < word->best_choice->length(); ++i) {
@ -399,209 +300,9 @@ void Tesseract::write_results( //output a word
}
}
}
if (write_to_shm)
write_shm_text (word, page_res_it.block ()->block,
page_res_it.row (), *wordstr, wordstr_lengths);
#if 0
if (tessedit_write_output)
write_cooked_text (word->word, *wordstr, TRUE, FALSE, textfile);
if (tessedit_write_raw_output)
write_cooked_text (word->word, word->raw_choice->string (),
TRUE, FALSE, rawfile);
if (tessedit_write_txt_map)
write_map(txt_mapfile, word);
ep_choice = make_epaper_choice (word, newline_type);
word->ep_choice = ep_choice;
#endif
character_count += word->best_choice->length();
word_count++;
}
} // namespace tesseract
/**********************************************************************
* make_epaper_choice
*
* Construct the epaper text string for a word, using the reject map to
* determine whether each blob should be rejected.
**********************************************************************/
#if 0
WERD_CHOICE *make_epaper_choice( //convert one word
WERD_RES *word, //word to do
char newline_type //type of newline
) {
inT16 index = 0; //to string
inT16 blobindex; //to word
inT16 prevright = 0; //right of previous blob
inT16 nextleft; //left of next blob
PBLOB *blob;
TBOX inset_box; //bounding box
PBLOB_IT blob_it; //blob iterator
char word_string[MAX_PATH]; //converted string
BOOL8 force_total_reject;
char unrecognised = STRING (unrecognised_char)[0];
blob_it.set_to_list (word->outword->blob_list ());
ASSERT_HOST (word->reject_map.length () ==
word->best_choice->string ().length ());
/*
tprintf( "\"%s\" -> length: %d; blobcount: %d (%d)\n",
word->best_choice->string().string(),
word->best_choice->string().length(),
blob_it.length(),
blob_count( word->outword ) );
*/
if (word->best_choice->string ().length () == 0)
force_total_reject = TRUE;
else {
force_total_reject = FALSE;
ASSERT_HOST (blob_it.length () ==
word->best_choice->string ().length ());
}
if (!blob_it.empty ()) {
for (index = 0; index < word->word->space (); index++)
word_string[index] = ' '; //leading blanks
}
/* Why does this generate leading blanks regardless of whether the
word_choice string is empty, when write_cooked_text ony generates leading
blanks when the string is NOT empty???. */
if (word->word->flag (W_REP_CHAR) && tessedit_write_rep_codes) {
strcpy (word_string + index, "|^~R");
index += 4;
strcpy(word_string + index, unicharset.id_to_unichar(get_rep_char (word)));
index += strlen(unicharset.id_to_unichar(get_rep_char (word)));
}
else {
if (!blob_it.empty ())
prevright = blob_it.data ()->bounding_box ().left ();
//actually first left
for (blobindex = 0, blob_it.mark_cycle_pt ();
!blob_it.cycled_list (); blobindex++, blob_it.forward ()) {
blob = blob_it.data ();
if (word->reject_map[blobindex].accepted ()) {
if (word->best_choice->string ()[blobindex] == ' ')
//but not rejected!!
word_string[index++] = unrecognised;
else
word_string[index++] =
word->best_choice->string ()[blobindex];
}
else { // start reject
inset_box = blob->bounding_box ();
/* Extend reject box to include rejected neighbours */
while (!blob_it.at_last () &&
(force_total_reject ||
(word->reject_map[blobindex + 1].rejected ()))) {
blobindex++;
blob = blob_it.forward ();
//get total box
inset_box += blob->bounding_box ();
}
if (blob_it.at_last ())
nextleft = inset_box.right ();
else
nextleft = blob_it.data_relative (1)->bounding_box ().left ();
// tprintf("Making reject from (%d,%d)->(%d,%d)\n",
// inset_box.left(),inset_box.bottom(),
// inset_box.right(),inset_box.top());
index += make_reject (&inset_box, prevright, nextleft,
&word->denorm, &word_string[index]);
}
prevright = blob->bounding_box ().right ();
}
}
if (newline_type)
//end line
word_string[index++] = newline_type;
word_string[index] = '\0'; //terminate string
if (strlen (word_string) != index) {
tprintf ("ASSERT ABOUT TO FAIL: %s, index %d len %d\n",
word_string, index, strlen (word_string));
}
//don't pass any zeros
ASSERT_HOST (strlen (word_string) == index);
return new WERD_CHOICE (word_string, 0, 0, NO_PERM);
}
#endif
/**********************************************************************
* make_reject
*
* Add the escape code to the string for the reject.
**********************************************************************/
inT16
make_reject ( //make reject code
TBOX * inset_box, //bounding box
inT16 prevright, //previous char
inT16 nextleft, //next char
DENORM * denorm, //de-normalizer
char word_string[] //output string
) {
inT16 index; //to string
inT16 xpos; //start of inset
inT16 ypos;
inT16 width; //size of inset
inT16 height;
inT16 left_offset; //shift form prev char
inT16 right_offset; //shift to next char
inT16 baseline_offset; //shift from baseline
inT16 inset_index = 0; //number of inset
inT16 min_chars; //min width estimate
inT16 max_chars; //max width estimate
float x_centre; //centre of box
index = 0;
x_centre = (inset_box->left () + inset_box->right ()) / 2.0;
left_offset =
(inT16) (denorm->x (inset_box->left ()) - denorm->x (prevright));
right_offset =
(inT16) (denorm->x (nextleft) - denorm->x (inset_box->right ()));
xpos = (inT16) floor (denorm->x (inset_box->left ()));
width = (inT16) ceil (denorm->x (inset_box->right ())) - xpos;
ypos = (inT16) floor (denorm->y (inset_box->bottom (), x_centre));
height = (inT16) ceil (denorm->y (inset_box->top (), x_centre)) - ypos;
baseline_offset = ypos - (inT16) denorm->y (bln_baseline_offset, x_centre);
//escape code
word_string[index++] = CTRL_INSET;
min_chars = (inT16) ceil (0.27 * width / denorm->row ()->x_height ());
max_chars = (inT16) floor (1.8 * width / denorm->row ()->x_height ());
/*
Ensure min_chars and max_chars are in the range 0..254. This ensures that
we can add 1 to them to avoid putting \0 in a string, and still not exceed
the max value in a byte.
*/
if (min_chars < 0)
min_chars = 0;
if (min_chars > 254)
min_chars = 254;
if (max_chars < min_chars)
max_chars = min_chars;
if (max_chars > 254)
max_chars = 254;
//min chars
word_string[index++] = min_chars + 1;
//max chars
word_string[index++] = max_chars + 1;
word_string[index++] = 2; //type?
//store index
word_string[index++] = inset_index / 255 + 1;
word_string[index++] = inset_index % 255 + 1;
return index; //size of string
}
/**********************************************************************
* determine_newline_type
*
@ -641,305 +342,6 @@ char determine_newline_type( //test line ends
return end_gap > width ? CTRL_HARDLINE : CTRL_NEWLINE;
}
/**********************************************************************
* write_shm_text
*
* Write the cooked text to the shared memory for the api.
**********************************************************************/
void write_shm_text( //write output
WERD_RES *word, //word to do
BLOCK *block, //block it is from
ROW_RES *row, //row it is from
const STRING &text, //text to write
const STRING &text_lengths
) {
inT32 index; //char counter
inT32 index2; //char counter
inT32 length; //chars in word
inT32 ptsize; //font size
inT8 blanks; //blanks in word
uinT8 enhancement; //bold etc
uinT8 font; //font index
char unrecognised = STRING (unrecognised_char)[0];
PBLOB *blob;
TBOX blob_box; //bounding box
PBLOB_IT blob_it; //blob iterator
WERD copy_outword; // copy to denorm
uinT32 rating; //of char
BOOL8 lineend; //end of line
int offset;
int offset2;
//point size
ptsize = pixels_to_pts ((inT32) (row->row->x_height () + row->row->ascenders () - row->row->descenders ()), 300);
if (word->word->flag (W_BOL) && ocr_char_space () < 128
&& ocr_send_text (TRUE) != OKAY)
return; //release failed
copy_outword = *(word->outword);
copy_outword.baseline_denormalise (&word->denorm);
blob_it.set_to_list (copy_outword.blob_list ());
length = text_lengths.length ();
if (length > 0) {
blanks = word->word->space ();
if (blanks == 0 && tessedit_word_for_word && !word->word->flag (W_BOL))
blanks = 1;
for (index = 0, offset = 0; index < length;
offset += text_lengths[index++], blob_it.forward ()) {
blob = blob_it.data ();
blob_box = blob->bounding_box ();
enhancement = 0;
if (word->italic > 0 || (word->italic == 0 && row->italic > 0))
enhancement |= EUC_ITALIC;
if (word->bold > 0 || (word->bold == 0 && row->bold > 0))
enhancement |= EUC_BOLD;
if (tessedit_write_ratings)
rating = (uinT32) (-word->best_choice->certainty () / 0.035);
else if (tessedit_zero_rejection)
rating = text[offset] == ' ' ? 100 : 0;
else
rating = word->reject_map[index].accepted ()? 0 : 100;
if (rating > 255)
rating = 255;
if (word->font1_count > 2)
font = word->font1;
else if (row->font1_count > 8)
font = row->font1;
else
//font index
font = word->word->flag (W_DONT_CHOP) ? 0 : 1;
lineend = word->word->flag (W_EOL) && index == length - 1;
if (word->word->flag (W_EOL) && tessedit_zero_rejection
&& index < length - 1 && text[index + text_lengths[index]] == ' ') {
for (index2 = index + 1, offset2 = offset + text_lengths[index];
index2 < length && text[offset2] == ' ';
offset2 += text_lengths[index2++]);
if (index2 == length)
lineend = TRUE;
}
if (!tessedit_zero_rejection || text[offset] != ' '
|| tessedit_word_for_word) {
//confidence
if (text[offset] == ' ') {
ocr_append_char (unrecognised,
blob_box.left (), blob_box.right (),
page_image.get_ysize () - 1 - blob_box.top (),
page_image.get_ysize () - 1 - blob_box.bottom (),
font, (uinT8) rating,
ptsize, //point size
blanks, enhancement, //enhancement
OCR_CDIR_LEFT_RIGHT,
OCR_LDIR_DOWN_RIGHT,
lineend ? OCR_NL_NEWLINE : OCR_NL_NONE);
} else {
for (int suboffset = 0; suboffset < text_lengths[index]; ++suboffset)
ocr_append_char (static_cast<unsigned char>(text[offset+suboffset]),
blob_box.left (), blob_box.right (),
page_image.get_ysize () - 1 - blob_box.top (),
page_image.get_ysize () - 1 - blob_box.bottom (),
font, (uinT8) rating,
ptsize, //point size
blanks, enhancement, //enhancement
OCR_CDIR_LEFT_RIGHT,
OCR_LDIR_DOWN_RIGHT,
lineend ? OCR_NL_NEWLINE : OCR_NL_NONE);
}
blanks = 0;
}
}
}
else if (tessedit_word_for_word) {
blanks = word->word->space ();
if (blanks == 0 && !word->word->flag (W_BOL))
blanks = 1;
blob_box = word->word->bounding_box ();
enhancement = 0;
if (word->italic > 0)
enhancement |= EUC_ITALIC;
if (word->bold > 0)
enhancement |= EUC_BOLD;
rating = 100;
if (word->font1_count > 2)
font = word->font1;
else if (row->font1_count > 8)
font = row->font1;
else
//font index
font = word->word->flag (W_DONT_CHOP) ? 0 : 1;
lineend = word->word->flag (W_EOL);
//font index
ocr_append_char (unrecognised,
blob_box.left (), blob_box.right (),
page_image.get_ysize () - 1 - blob_box.top (),
page_image.get_ysize () - 1 - blob_box.bottom (),
font,
rating, //confidence
ptsize, //point size
blanks, enhancement, //enhancement
OCR_CDIR_LEFT_RIGHT,
OCR_LDIR_DOWN_RIGHT,
lineend ? OCR_NL_NEWLINE : OCR_NL_NONE);
}
}
/**********************************************************************
* write_map
*
* Write a map file of 0's and 1'a which associates characters from the .txt
* file with those in the .etx file. 0 = .txt char was deleted. 1 = .txt char
* is kept. Note that there may be reject regions in the .etx file WITHOUT
* .txt chars being rejected. The map file should be the same length, and
* the same number of lines as the .txt file
*
* The paramaterised input is because I thought I might be able to generate
* multiple map files in a single run. However, it didn't work because
* newdiff needs etx files!
**********************************************************************/
#if 0
void write_map( //output a map file
FILE *mapfile, //mapfile to write to
WERD_RES *word) {
inT16 index;
int status;
STRING mapstr = "";
if (word->best_choice->string ().length () > 0) {
for (index = 0; index < word->word->space (); index++) {
if (word->reject_spaces &&
(suspect_level >= suspect_space_level) &&
!tessedit_minimal_rejection && !tessedit_zero_rejection)
/* Write rejected spaces to .map file ONLY. Newdiff converts these back to
accepted spaces AFTER generating basic space stats but BEFORE using .etx */
status = fprintf (mapfile, "0");
else
status = fprintf (mapfile, "1");
if (status < 0)
WRITEFAILED.error ("write_map", EXIT, "Space Errno: %d", errno);
}
if ((word->word->flag (W_REP_CHAR) && tessedit_write_rep_codes)) {
for (index = 0; index < 5; index++)
mapstr += '1';
}
else {
ASSERT_HOST (word->reject_map.length () ==
word->best_choice->string ().length ());
for (index = 0; index < word->reject_map.length (); index++) {
if (word->reject_map[index].accepted ())
mapstr += '1';
else
mapstr += '0';
}
}
status = fprintf (mapfile, "%s", mapstr.string ());
if (status < 0)
WRITEFAILED.error ("write_map", EXIT, "Map str Errno: %d", errno);
}
if (word->word->flag (W_EOL)) {
status = fprintf (mapfile, "\n");
if (status < 0)
WRITEFAILED.error ("write_map", EXIT, "Newline Errno: %d", errno);
}
status = fflush (mapfile);
if (status != 0)
WRITEFAILED.error ("write_map", EXIT, "fflush Errno: %d", errno);
}
#endif
/*************************************************************************
* open_file()
*************************************************************************/
namespace tesseract {
FILE *Tesseract::open_outfile( //open .map & .unlv file
const char *extension) {
STRING file_name;
FILE *outfile;
file_name = imagebasename + extension;
if (!(outfile = fopen (file_name.string (), "w"))) {
CANTOPENFILE.error ("open_outfile", EXIT, "%s %d",
file_name.string (), errno);
}
return outfile;
}
} // namespace tesseract
#if 0
void write_unlv_text(WERD_RES *word) {
const char *wordstr;
char buff[512]; //string to output
int i = 0;
int j = 0;
char unrecognised = STRING (unrecognised_char)[0];
int status;
char space_str[3];
wordstr = word->best_choice->string ().string ();
/* DONT need to do anything special for repeated char words - at this stage
the repetition char has been identified and any other chars have been
rejected.
*/
for (; wordstr[i] != '\0'; i++) {
if ((wordstr[i] == ' ') ||
(wordstr[i] == '~') || (wordstr[i] == '^') || (wordstr[i] == '|'))
buff[j++] = unrecognised;
else {
if (word->reject_map[i].rejected ())
buff[j++] = '^'; //Add suspect marker
buff[j++] = wordstr[i];
}
}
buff[j] = '\0';
if (strlen (wordstr) > 0) {
if (word->reject_spaces &&
(suspect_level >= suspect_space_level) &&
!tessedit_minimal_rejection && !tessedit_zero_rejection)
strcpy (space_str, "^ "); //Suspect space
else
strcpy (space_str, " "); //Certain space
for (i = 0; i < word->word->space (); i++) {
status = fprintf (unlv_file, "%s", space_str);
if (status < 0)
WRITEFAILED.error ("write_unlv_text", EXIT,
"Space Errno: %d", errno);
}
status = fprintf (unlv_file, "%s", buff);
if (status < 0)
WRITEFAILED.error ("write_unlv_text", EXIT, "Word Errno: %d", errno);
}
if (word->word->flag (W_EOL)) {
status = fprintf (unlv_file, "\n");
if (status < 0)
WRITEFAILED.error ("write_unlv_text", EXIT,
"Newline Errno: %d", errno);
}
status = fflush (unlv_file);
if (status != 0)
WRITEFAILED.error ("write_unlv_text", EXIT, "Fflush Errno: %d", errno);
}
#endif
/*************************************************************************
* get_rep_char()
* Return the first accepted character from the repetition string. This is the
@ -957,36 +359,6 @@ UNICHAR_ID Tesseract::get_rep_char(WERD_RES *word) { // what char is repeated?
return unicharset.unichar_to_id(unrecognised_char.string());
}
}
} // namespace tesseract
void ensure_rep_chars_are_consistent(WERD_RES *word) {
#if 0
char rep_char = get_rep_char (word);
char *ptr;
ptr = (char *) word->best_choice->string ().string ();
for (; *ptr != '\0'; ptr++) {
if (*ptr != rep_char)
*ptr = rep_char;
}
#endif
#if 0
UNICHAR_ID rep_char = get_rep_char (word); //TODO(tkielbus) Reactivate
int i;
char *ptr;
STRING consistent_string;
STRING consistent_string_lengths;
ptr = (char *) word->best_choice->string ().string ();
for (i = 0; *ptr != '\0'; ptr += word->best_choice->lengths()[i++]) {
consistent_string += unicharset.id_to_unichar(rep_char);
consistent_string_lengths += strlen(unicharset.id_to_unichar(rep_char));
}
word->best_choice->string() = consistent_string;
word->best_choice->lengths() = consistent_string_lengths;
#endif
}
/*************************************************************************
* SUSPECT LEVELS
@ -998,8 +370,6 @@ void ensure_rep_chars_are_consistent(WERD_RES *word) {
* NOTE: to reject JUST tess failures in the .map file set suspect_level 3 and
* tessedit_minimal_rejection.
*************************************************************************/
namespace tesseract {
void Tesseract::set_unlv_suspects(WERD_RES *word_res) {
int len = word_res->reject_map.length();
const WERD_CHOICE &word = *(word_res->best_choice);

View File

@ -20,91 +20,15 @@
#ifndef OUTPUT_H
#define OUTPUT_H
#include "varable.h"
#include "params.h"
//#include "epapconv.h"
#include "pageres.h"
#include "notdll.h"
extern BOOL_EVAR_H (tessedit_write_block_separators, TRUE,
"Write block separators in output");
extern BOOL_VAR_H (tessedit_write_raw_output, FALSE,
"Write raw stuff to name.raw");
extern BOOL_EVAR_H (tessedit_write_output, TRUE, "Write text to name.txt");
extern BOOL_EVAR_H (tessedit_write_txt_map, TRUE,
"Write .txt to .etx map file");
extern BOOL_EVAR_H (tessedit_write_rep_codes, TRUE,
"Write repetition char code");
extern BOOL_EVAR_H (tessedit_write_unlv, FALSE, "Write .unlv output file");
extern STRING_EVAR_H (unrecognised_char, "|",
"Output char for unidentified blobs");
extern INT_EVAR_H (suspect_level, 99, "Suspect marker level");
extern INT_VAR_H (suspect_space_level, 100,
"Min suspect level for rejecting spaces");
extern INT_VAR_H (suspect_short_words, 2,
"Dont Suspect dict wds longer than this");
extern BOOL_VAR_H (suspect_constrain_1Il, FALSE,
"UNLV keep 1Il chars rejected");
extern double_VAR_H (suspect_rating_per_ch, 999.9,
"Dont touch bad rating limit");
extern double_VAR_H (suspect_accept_rating, -999.9,
"Accept good rating limit");
extern BOOL_EVAR_H (tessedit_minimal_rejection, FALSE,
"Only reject tess failures");
extern BOOL_VAR_H (tessedit_zero_rejection, FALSE, "Dont reject ANYTHING");
extern BOOL_VAR_H (tessedit_word_for_word, FALSE,
"Make output have exactly one word per WERD");
extern BOOL_VAR_H (tessedit_consistent_reps, TRUE,
"Force all rep chars the same");
/** output a word */
void write_results(
PAGE_RES_IT &page_res_it, ///< full info
char newline_type, ///< type of newline
BOOL8 force_eol, ///< override tilde crunch?
BOOL8 write_to_shm ///< send to api
);
/** convert one word */
WERD_CHOICE *make_epaper_choice(
WERD_RES *word, ///< word to do
char newline_type ///< type of newline
);
/** make reject code */
inT16 make_reject (
TBOX * inset_box, ///< bounding box
inT16 prevright, ///< previous char
inT16 nextleft, ///< next char
DENORM * denorm, ///< de-normalizer
char word_string[] ///< output string
);
/** test line ends */
char determine_newline_type(WERD *word, ///< word to do
BLOCK *block, ///< current block
WERD *next_word, ///< next word
BLOCK *next_block ///< block of next word
);
/** write output */
void write_cooked_text(WERD *word, ///< word to do
const STRING &text, ///< text to write
BOOL8 acceptable, ///< good stuff
BOOL8 pass2, ///< done on pass2
FILE *fp ///< file to write
);
/** write output */
void write_shm_text(WERD_RES *word, ///< word to do
BLOCK *block, ///< block it is from
ROW_RES *row, ///< row it is from
const STRING &text, ///< text to write
const STRING &text_lengths
);
/** output a map file */
void write_map(
FILE *mapfile, ///< mapfile to write to
WERD_RES *word ///< word
);
/*FILE *open_outfile( //open .map & .unlv file
const char *extension);*/
void write_unlv_text(WERD_RES *word);
void ensure_rep_chars_are_consistent(WERD_RES *word);
#endif

View File

@ -46,7 +46,8 @@
#include "blread.h"
#include "wordseg.h"
#include "makerow.h"
#include "baseapi.h"
#include "osdetect.h"
#include "textord.h"
#include "tordmain.h"
#include "tessvars.h"
@ -56,6 +57,48 @@ namespace tesseract {
const int kMinCredibleResolution = 70;
/// Default resolution used if input in not believable.
const int kDefaultResolution = 300;
// Max erosions to perform in removing an enclosing circle.
const int kMaxCircleErosions = 8;
// Helper to remove an enclosing circle from an image.
// If there isn't one, then the image will most likely get badly mangled.
// The returned pix must be pixDestroyed after use. NULL may be returned
// if the image doesn't meet the trivial conditions that it uses to determine
// success.
static Pix* RemoveEnclosingCircle(Pix* pixs) {
Pix* pixsi = pixInvert(NULL, pixs);
Pix* pixc = pixCreateTemplate(pixs);
pixSetOrClearBorder(pixc, 1, 1, 1, 1, PIX_SET);
pixSeedfillBinary(pixc, pixc, pixsi, 4);
pixInvert(pixc, pixc);
pixDestroy(&pixsi);
Pix* pixt = pixAnd(NULL, pixs, pixc);
l_int32 max_count;
pixCountConnComp(pixt, 8, &max_count);
// The count has to go up before we start looking for the minimum.
l_int32 min_count = MAX_INT32;
Pix* pixout = NULL;
for (int i = 1; i < kMaxCircleErosions; i++) {
pixDestroy(&pixt);
pixErodeBrick(pixc, pixc, 3, 3);
pixt = pixAnd(NULL, pixs, pixc);
l_int32 count;
pixCountConnComp(pixt, 8, &count);
if (i == 1 || count > max_count) {
max_count = count;
min_count = count;
} else if (i > 1 && count < min_count) {
min_count = count;
pixDestroy(&pixout);
pixout = pixCopy(NULL, pixt); // Save the best.
} else if (count >= min_count) {
break; // We have passed by the best.
}
}
pixDestroy(&pixt);
pixDestroy(&pixc);
return pixout;
}
/**
* Segment the page according to the current value of tessedit_pageseg_mode.
@ -63,18 +106,12 @@ const int kDefaultResolution = 300;
* and copied to image, otherwise it just uses image as the input.
* On return the blocks list owns all the constructed page layout.
*/
int Tesseract::SegmentPage(const STRING* input_file,
IMAGE* image, BLOCK_LIST* blocks) {
int width = image->get_xsize();
int height = image->get_ysize();
int resolution = image->get_res();
#ifdef HAVE_LIBLEPT
if (pix_binary_ != NULL) {
width = pixGetWidth(pix_binary_);
height = pixGetHeight(pix_binary_);
resolution = pixGetXRes(pix_binary_);
}
#endif
int Tesseract::SegmentPage(const STRING* input_file, BLOCK_LIST* blocks,
Tesseract* osd_tess, OSResults* osr) {
ASSERT_HOST(pix_binary_ != NULL);
int width = pixGetWidth(pix_binary_);
int height = pixGetHeight(pix_binary_);
int resolution = pixGetXRes(pix_binary_);
// Zero resolution messes up the algorithms, so make sure it is credible.
if (resolution < kMinCredibleResolution)
resolution = kDefaultResolution;
@ -82,7 +119,7 @@ int Tesseract::SegmentPage(const STRING* input_file,
PageSegMode pageseg_mode = static_cast<PageSegMode>(
static_cast<int>(tessedit_pageseg_mode));
// If a UNLV zone file can be found, use that instead of segmentation.
if (pageseg_mode != tesseract::PSM_AUTO &&
if (!PSM_COL_FIND_ENABLED(pageseg_mode) &&
input_file != NULL && input_file->length() > 0) {
STRING name = *input_file;
const char* lastdot = strrchr(name.string(), '.');
@ -90,88 +127,85 @@ int Tesseract::SegmentPage(const STRING* input_file,
name[lastdot - name.string()] = '\0';
read_unlv_file(name, width, height, blocks);
}
bool single_column = pageseg_mode > PSM_AUTO;
if (blocks->empty()) {
// No UNLV file present. Work according to the PageSegMode.
// First make a single block covering the whole image.
BLOCK_IT block_it(blocks);
BLOCK* block = new BLOCK("", TRUE, 0, 0, 0, 0, width, height);
block->set_right_to_left(right_to_left());
block_it.add_to_end(block);
} else {
// UNLV file present. Use PSM_SINGLE_COLUMN.
pageseg_mode = PSM_SINGLE_COLUMN;
}
bool single_column = !PSM_COL_FIND_ENABLED(pageseg_mode);
bool osd_enabled = PSM_OSD_ENABLED(pageseg_mode);
bool osd_only = pageseg_mode == PSM_OSD_ONLY;
TO_BLOCK_LIST land_blocks, port_blocks;
TBOX page_box;
if (pageseg_mode <= PSM_SINGLE_COLUMN) {
if (AutoPageSeg(width, height, resolution, single_column,
image, blocks, &port_blocks) < 0) {
return -1;
}
int auto_page_seg_ret_val = 0;
TO_BLOCK_LIST to_blocks;
if (osd_enabled || PSM_BLOCK_FIND_ENABLED(pageseg_mode)) {
auto_page_seg_ret_val =
AutoPageSeg(resolution, single_column, osd_enabled, osd_only,
blocks, &to_blocks, osd_tess, osr);
if (osd_only)
return auto_page_seg_ret_val;
// To create blobs from the image region bounds uncomment this line:
// port_blocks.clear(); // Uncomment to go back to the old mode.
// to_blocks.clear(); // Uncomment to go back to the old mode.
} else {
#if HAVE_LIBLEPT
image->FromPix(pix_binary_);
#endif
deskew_ = FCOORD(1.0f, 0.0f);
reskew_ = FCOORD(1.0f, 0.0f);
if (pageseg_mode == PSM_CIRCLE_WORD) {
Pix* pixcleaned = RemoveEnclosingCircle(pix_binary_);
if (pixcleaned != NULL) {
pixDestroy(&pix_binary_);
pix_binary_ = pixcleaned;
}
}
}
if (auto_page_seg_ret_val < 0) {
return -1;
}
if (blocks->empty()) {
tprintf("Empty page\n");
return 0; // AutoPageSeg found an empty page.
}
if (port_blocks.empty()) {
// AutoPageSeg was not used, so we need to find_components first.
find_components(blocks, &land_blocks, &port_blocks, &page_box);
} else {
// AutoPageSeg does not need to find_components as it did that already.
page_box.set_left(0);
page_box.set_bottom(0);
page_box.set_right(width);
page_box.set_top(height);
// Filter_blobs sets up the TO_BLOCKs the same as find_components does.
filter_blobs(page_box.topright(), &port_blocks, true);
}
textord_.TextordPage(pageseg_mode, width, height, pix_binary_,
blocks, &to_blocks);
SetupWordScripts(blocks);
return auto_page_seg_ret_val;
}
TO_BLOCK_IT to_block_it(&port_blocks);
ASSERT_HOST(!port_blocks.empty());
TO_BLOCK* to_block = to_block_it.data();
if (pageseg_mode <= PSM_SINGLE_BLOCK ||
to_block->line_size < 2) {
// For now, AUTO, SINGLE_COLUMN and SINGLE_BLOCK all map to the old
// textord. The difference is the number of blocks and how the are made.
textord_page(page_box.topright(), blocks, &land_blocks, &port_blocks,
this);
} else {
// SINGLE_LINE, SINGLE_WORD and SINGLE_CHAR all need a single row.
float gradient = make_single_row(page_box.topright(),
to_block, &port_blocks, this);
if (pageseg_mode == PSM_SINGLE_LINE) {
// SINGLE_LINE uses the old word maker on the single line.
make_words(page_box.topright(), gradient, blocks,
&land_blocks, &port_blocks, this);
} else {
// SINGLE_WORD and SINGLE_CHAR cram all the blobs into a
// single word, and in SINGLE_CHAR mode, all the outlines
// go in a single blob.
make_single_word(pageseg_mode == PSM_SINGLE_CHAR,
to_block->get_rows(), to_block->block->row_list());
// TODO(rays) This is a hack to set all the words with a default script.
// In the future this will be set by a preliminary pass over the document.
void Tesseract::SetupWordScripts(BLOCK_LIST* blocks) {
int script = unicharset.default_sid();
bool has_x_height = unicharset.script_has_xheight();
bool is_latin = script == unicharset.latin_sid();
BLOCK_IT b_it(blocks);
for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
ROW_IT r_it(b_it.data()->row_list());
for (r_it.mark_cycle_pt(); !r_it.cycled_list(); r_it.forward()) {
WERD_IT w_it(r_it.data()->word_list());
for (w_it.mark_cycle_pt(); !w_it.cycled_list(); w_it.forward()) {
WERD* word = w_it.data();
word->set_script_id(script);
word->set_flag(W_SCRIPT_HAS_XHEIGHT, has_x_height);
word->set_flag(W_SCRIPT_IS_LATIN, is_latin);
}
}
}
return 0;
}
/**
* Auto page segmentation. Divide the page image into blocks of uniform
* text linespacing and images.
*
* Width, height and resolution are derived from the input image.
*
* If the pix is non-NULL, then it is assumed to be the input, and it is
* copied to the image, otherwise the image is used directly.
* Resolution (in ppi) is derived from the input image.
*
* The output goes in the blocks list with corresponding TO_BLOCKs in the
* to_blocks list.
@ -179,10 +213,17 @@ int Tesseract::SegmentPage(const STRING* input_file,
* If single_column is true, then no attempt is made to divide the image
* into columns, but multiple blocks are still made if the text is of
* non-uniform linespacing.
*
* If osd is true, then orientation and script detection is performed as well.
* If only_osd is true, then only orientation and script detection is
* performed. If osr is desired, the osr_tess must be another Tesseract
* that was initialized especially for osd, and the results will be output
* into osr.
*/
int Tesseract::AutoPageSeg(int width, int height, int resolution,
bool single_column, IMAGE* image,
BLOCK_LIST* blocks, TO_BLOCK_LIST* to_blocks) {
int Tesseract::AutoPageSeg(int resolution, bool single_column,
bool osd, bool only_osd,
BLOCK_LIST* blocks, TO_BLOCK_LIST* to_blocks,
Tesseract* osd_tess, OSResults* osr) {
int vertical_x = 0;
int vertical_y = 1;
TabVector_LIST v_lines;
@ -196,7 +237,8 @@ int Tesseract::AutoPageSeg(int width, int height, int resolution,
#ifdef HAVE_LIBLEPT
if (pix_binary_ != NULL) {
if (textord_debug_images) {
Pix* grey_pix = pixCreate(width, height, 8);
Pix* grey_pix = pixCreate(pixGetWidth(pix_binary_),
pixGetHeight(pix_binary_), 8);
// Printable images are light grey on white, but for screen display
// they are black on dark grey so the other colors show up well.
if (textord_debug_printable) {
@ -210,8 +252,9 @@ int Tesseract::AutoPageSeg(int width, int height, int resolution,
pixWrite(AlignedBlob::textord_debug_pix().string(), grey_pix, IFF_PNG);
pixDestroy(&grey_pix);
}
if (tessedit_dump_pageseg_images)
if (tessedit_dump_pageseg_images) {
pixWrite("tessinput.png", pix_binary_, IFF_PNG);
}
// Leptonica is used to find the lines and image regions in the input.
LineFinder::FindVerticalLines(resolution, pix_binary_,
&vertical_x, &vertical_y, &v_lines);
@ -221,16 +264,13 @@ int Tesseract::AutoPageSeg(int width, int height, int resolution,
ImageFinder::FindImages(pix_binary_, &boxa, &pixa);
if (tessedit_dump_pageseg_images)
pixWrite("tessnoimages.png", pix_binary_, IFF_PNG);
// Copy the Pix to the IMAGE.
image->FromPix(pix_binary_);
if (single_column)
v_lines.clear();
}
#endif
TO_BLOCK_LIST land_blocks, port_blocks;
TBOX page_box;
TO_BLOCK_LIST port_blocks;
// The rest of the algorithm uses the usual connected components.
find_components(blocks, &land_blocks, &port_blocks, &page_box);
textord_.find_components(pix_binary_, blocks, &port_blocks);
TO_BLOCK_IT to_block_it(&port_blocks);
ASSERT_HOST(!to_block_it.empty());
@ -244,20 +284,50 @@ int Tesseract::AutoPageSeg(int width, int height, int resolution,
// that there aren't any interesting line separators or images, since
// it means that we have a pre-defined unlv zone file.
ColumnFinder finder(static_cast<int>(to_block->line_size),
blkbox.botleft(), blkbox.topright(),
blkbox.botleft(), blkbox.topright(), resolution,
&v_lines, &h_lines, vertical_x, vertical_y);
if (finder.FindBlocks(height, resolution, single_column,
BLOBNBOX_CLIST osd_blobs;
int osd_orientation = 0;
bool vertical_text = finder.IsVerticallyAlignedText(to_block, &osd_blobs);
if (osd && osd_tess != NULL && osr != NULL) {
os_detect_blobs(&osd_blobs, osr, osd_tess);
if (only_osd) continue;
osd_orientation = osr->best_result.orientation_id;
double osd_score = osr->orientations[osd_orientation];
double osd_margin = min_orientation_margin * 2;
// tprintf("Orientation scores:");
for (int i = 0; i < 4; ++i) {
if (i != osd_orientation &&
osd_score - osr->orientations[i] < osd_margin) {
osd_margin = osd_score - osr->orientations[i];
}
// tprintf(" %d:%f", i, osr->orientations[i]);
}
// tprintf("\n");
if (osd_margin < min_orientation_margin) {
// Margin insufficient - dream up a suitable default.
if (vertical_text && (osd_orientation & 1))
osd_orientation = 3;
else
osd_orientation = 0;
tprintf("Score margin insufficient:%.2f, using %d as a default\n",
osd_margin, osd_orientation);
}
}
osd_blobs.shallow_clear();
finder.CorrectOrientation(to_block, vertical_text, osd_orientation);
if (finder.FindBlocks(single_column, pixGetHeight(pix_binary_),
to_block, boxa, pixa, &found_blocks, to_blocks) < 0)
return -1;
finder.ComputeDeskewVectors(&deskew_, &reskew_);
finder.GetDeskewVectors(&deskew_, &reskew_);
boxa = NULL;
pixa = NULL;
}
}
#ifdef HAVE_LIBLEPT
boxaDestroy(&boxa);
pixaDestroy(&pixa);
#endif
if (only_osd) return 0;
blocks->clear();
BLOCK_IT block_it(blocks);
// Move the found blocks to the input/output blocks.

View File

@ -17,602 +17,31 @@
*
**********************************************************************/
#ifdef _MSC_VER
#pragma warning(disable:4244) // Conversion warnings
#endif
#include "mfcpch.h"
#include "pagewalk.h"
#include "pageres.h"
#include "tesseractclass.h"
#define EXTERN
EXTERN BOOL_VAR (current_word_quit, FALSE, "Stop processing this word");
DLLSYM BOOL_VAR (selection_quit, FALSE, "Stop processing this selection");
/**
* block_list_bounding_box()
*
* Scan block list to find the bounding box of all blocks.
* @param block_list the block list to find the bounding box of
*/
TBOX block_list_bounding_box(BLOCK_LIST *block_list)
{
BLOCK_IT block_it(block_list);
TBOX enclosing_box;
for (block_it.mark_cycle_pt (); !block_it.cycled_list ();
block_it.forward ())
enclosing_box += block_it.data ()->bounding_box ();
return enclosing_box;
}
/**
* block_list_compress()
*
* Pack a block list to occupy a smaller space by compressing each block and
* moving the compressed blocks one above the other.
* The compressed block list has the same top left point as the uncompressed
* first. Blocks are reordered so that the source names are in alphabetic
* order. (This gathers together, but does not combine, blocks from the same
* file.)
*
* The enclosing box of the compressed block list is returned.
*/
const TBOX block_list_compress(BLOCK_LIST *block_list)
{
BLOCK_IT block_it(block_list);
BLOCK *block;
ICOORD initial_top_left;
ICOORD block_spacing (0, BLOCK_SPACING);
TBOX enclosing_box; //for full display
initial_top_left = block_it.data()->bounding_box().topleft();
//group srcfile blks
block_it.sort (block_name_order);
/* Compress the target block list into an area starting from the top left of
the first block on the list */
enclosing_box = TBOX (initial_top_left, initial_top_left);
enclosing_box.move_bottom_edge (BLOCK_SPACING);
for (block_it.mark_cycle_pt ();
!block_it.cycled_list (); block_it.forward ()) {
block = block_it.data ();
block->compress (enclosing_box.botleft () - block_spacing -
block->bounding_box ().topleft ());
enclosing_box += block->bounding_box ();
}
return enclosing_box;
}
/**
* block_list_move()
*
* Move all the blocks in the list by a vector
*
* @param block_list the block list to move
* @param vec the vector to move it by
*/
void block_list_move(BLOCK_LIST *block_list,
ICOORD vec)
{
BLOCK_IT block_it(block_list);
for (block_it.mark_cycle_pt (); !block_it.cycled_list ();
block_it.forward ())
block_it.data ()->move (vec);
}
/**
* block_name_order()
*
* Block comparator used to sort a block list so that blocks from the same
* filename are located together, and blocks from the same file are ordered
* by vertical position.
*/
int block_name_order(const void *block1p,
const void *block2p)
{
int result;
BLOCK *block1 = *(BLOCK **) block1p;
BLOCK *block2 = *(BLOCK **) block2p;
result = strcmp (block1->name (), block2->name ());
if (result == 0)
result = block2->bounding_box ().top () - block1->bounding_box ().top ();
return result;
}
/**
* process_all_blobs()
*
* Walk the current block list applying the specified blob processor function
* to all blobs
* @param block_list the blocks to check
* @param blob_processor function to call
* @param c_blob_processor function to call
*/
void
process_all_blobs (BLOCK_LIST * block_list,
BOOL8 blob_processor (BLOCK *, ROW *, WERD *, PBLOB *),
BOOL8 c_blob_processor (BLOCK *, ROW *, WERD *, C_BLOB *))
{
BLOCK_IT block_it(block_list);
BLOCK *block;
ROW_IT row_it;
ROW *row;
WERD_IT word_it;
WERD *word;
PBLOB_IT blob_it;
PBLOB *blob;
C_BLOB_IT c_blob_it;
C_BLOB *c_blob;
for (block_it.mark_cycle_pt (); !block_it.cycled_list (); block_it.forward ()) {
block = block_it.data ();
row_it.set_to_list (block->row_list ());
for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
row = row_it.data ();
word_it.set_to_list (row->word_list ());
for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
word = word_it.data ();
if (word->flag (W_POLYGON)) {
if (blob_processor != NULL) {
blob_it.set_to_list (word->blob_list ());
for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) {
blob = blob_it.data ();
if (!blob_processor (block, row, word, blob) || selection_quit)
return;
}
}
}
else {
if (c_blob_processor != NULL) {
c_blob_it.set_to_list (word->cblob_list ());
for (c_blob_it.mark_cycle_pt (); !c_blob_it.cycled_list (); c_blob_it.forward ()) {
c_blob = c_blob_it.data ();
if (!c_blob_processor (block, row, word, c_blob) || selection_quit)
return;
}
}
}
}
}
}
}
/**
* process_selected_blobs()
*
* Walk the current block list applying the specified blob processor function
* to each selected blob
* @param block_list the blocks to check
* @param selection_box within this box(?)
* @param blob_processor function to call
* @param c_blob_processor function to call
*/
void
process_selected_blobs (BLOCK_LIST * block_list,
TBOX & selection_box,
BOOL8 blob_processor (BLOCK *, ROW *, WERD *, PBLOB *),
BOOL8 c_blob_processor (BLOCK *, ROW *, WERD *, C_BLOB *))
{
BLOCK_IT block_it(block_list);
BLOCK *block;
ROW_IT row_it;
ROW *row;
WERD_IT word_it;
WERD *word;
PBLOB_IT blob_it;
PBLOB *blob;
C_BLOB_IT c_blob_it;
C_BLOB *c_blob;
for (block_it.mark_cycle_pt (); !block_it.cycled_list (); block_it.forward ()) {
block = block_it.data ();
if (block->bounding_box ().overlap (selection_box)) {
row_it.set_to_list (block->row_list ());
for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
row = row_it.data ();
if (row->bounding_box ().overlap (selection_box)) {
word_it.set_to_list (row->word_list ());
for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
word = word_it.data ();
if (word->bounding_box ().overlap (selection_box)) {
if (word->flag (W_POLYGON)) {
if (blob_processor != NULL) {
blob_it.set_to_list (word->blob_list ());
for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) {
blob = blob_it.data ();
if (blob->bounding_box().overlap (selection_box)) {
if (!blob_processor(block, row, word, blob) || selection_quit)
return;
}
}
}
}
else {
if (c_blob_processor != NULL) {
c_blob_it.set_to_list (word->cblob_list ());
for (c_blob_it.mark_cycle_pt (); !c_blob_it.cycled_list (); c_blob_it.forward ()) {
c_blob = c_blob_it.data ();
if (c_blob->bounding_box ().overlap (selection_box)) {
if (!c_blob_processor(block, row, word, c_blob) || selection_quit)
return;
}
}
}
}
}
}
}
}
}
}
}
/**
* process_all_words()
*
* Walk the current block list applying the specified word processor function
* to all words
*/
void
process_all_words (BLOCK_LIST * block_list,
BOOL8 word_processor (BLOCK *, ROW *, WERD *))
{
BLOCK_IT block_it(block_list);
BLOCK *block;
ROW_IT row_it;
ROW *row;
WERD_IT word_it;
WERD *word;
for (block_it.mark_cycle_pt (); !block_it.cycled_list (); block_it.forward ()) {
block = block_it.data ();
row_it.set_to_list (block->row_list ());
for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
row = row_it.data ();
word_it.set_to_list (row->word_list ());
for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
word = word_it.data ();
if (!word_processor (block, row, word) || selection_quit)
return;
}
}
}
}
/**
* process_selected_words()
*
* Walk the current block list applying the specified word processor function
* to each word selected.
* to each word that overlaps the selection_box.
*/
void
process_selected_words (BLOCK_LIST * block_list,
TBOX & selection_box,
BOOL8 word_processor (BLOCK *, ROW *, WERD *))
{
BLOCK_IT block_it(block_list);
BLOCK *block;
ROW_IT row_it;
ROW *row;
WERD_IT word_it;
WERD *word;
for (block_it.mark_cycle_pt (); !block_it.cycled_list (); block_it.forward ()) {
block = block_it.data ();
if (block->bounding_box ().overlap (selection_box)) {
row_it.set_to_list (block->row_list ());
for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
row = row_it.data ();
if (row->bounding_box ().overlap (selection_box)) {
word_it.set_to_list (row->word_list ());
for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
word = word_it.data ();
if (word->bounding_box ().overlap (selection_box)) {
if (!word_processor (block, row, word) || selection_quit)
return;
}
}
}
}
}
}
}
namespace tesseract {
void
Tesseract::process_selected_words (BLOCK_LIST * block_list,
TBOX & selection_box,
BOOL8 (tesseract::Tesseract::*word_processor) (BLOCK *, ROW *, WERD *))
{
BLOCK_IT block_it(block_list);
BLOCK *block;
ROW_IT row_it;
ROW *row;
WERD_IT word_it;
WERD *word;
for (block_it.mark_cycle_pt (); !block_it.cycled_list (); block_it.forward ()) {
block = block_it.data ();
if (block->bounding_box ().overlap (selection_box)) {
row_it.set_to_list (block->row_list ());
for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
row = row_it.data ();
if (row->bounding_box ().overlap (selection_box)) {
word_it.set_to_list (row->word_list ());
for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
word = word_it.data ();
if (word->bounding_box ().overlap (selection_box)) {
if (!((this->*word_processor) (block, row, word)) || selection_quit)
return;
}
}
}
}
void Tesseract::process_selected_words(
PAGE_RES* page_res, // blocks to check
TBOX & selection_box,
BOOL8(tesseract::Tesseract::*word_processor)( // function to call
BLOCK* block, ROW* row, WERD_RES* word_res)) {
for (PAGE_RES_IT page_res_it(page_res); page_res_it.word() != NULL;
page_res_it.forward()) {
WERD* word = page_res_it.word()->word;
if (word->bounding_box().overlap(selection_box)) {
if (!((this->*word_processor)(page_res_it.block()->block,
page_res_it.row()->row,
page_res_it.word())))
return;
}
}
}
} // namespace tesseract
/**
* process_all_words_it() PASS ITERATORS
*
* Walk the current block list applying the specified word processor function
* to all words
*/
void
process_all_words_it (BLOCK_LIST * block_list,
BOOL8 word_processor (BLOCK *, ROW *, WERD *, BLOCK_IT &, ROW_IT &, WERD_IT &))
{
BLOCK_IT block_it(block_list);
BLOCK *block;
ROW_IT row_it;
ROW *row;
WERD_IT word_it;
WERD *word;
for (block_it.mark_cycle_pt (); !block_it.cycled_list (); block_it.forward ()) {
block = block_it.data ();
row_it.set_to_list (block->row_list ());
for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
row = row_it.data ();
word_it.set_to_list (row->word_list ());
for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
word = word_it.data ();
if (!word_processor (block, row, word, block_it, row_it, word_it) || selection_quit)
return;
}
}
}
}
/**
* process_selected_words_it() PASS ITERATORS
*
* Walk the current block list applying the specified word processor function
* to each word selected.
*/
void
process_selected_words_it (BLOCK_LIST * block_list,
TBOX & selection_box,
BOOL8 word_processor (BLOCK *, ROW *, WERD *, BLOCK_IT &, ROW_IT &, WERD_IT &))
{
BLOCK_IT block_it(block_list);
BLOCK *block;
ROW_IT row_it;
ROW *row;
WERD_IT word_it;
WERD *word;
for (block_it.mark_cycle_pt (); !block_it.cycled_list (); block_it.forward ()) {
block = block_it.data ();
if (block->bounding_box ().overlap (selection_box)) {
row_it.set_to_list (block->row_list ());
for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
row = row_it.data ();
if (row->bounding_box ().overlap (selection_box)) {
word_it.set_to_list (row->word_list ());
for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
word = word_it.data ();
if (word->bounding_box ().overlap (selection_box)) {
if (!word_processor (block, row, word, block_it, row_it, word_it) || selection_quit)
return;
}
}
}
}
}
}
}
/**
* process_all_blocks()
*
* Walk the current block list applying the specified block processor function
* to each block.
*/
void
process_all_blocks (BLOCK_LIST * block_list,
BOOL8 block_processor (BLOCK *))
{
BLOCK_IT block_it(block_list);
BLOCK *block;
for (block_it.mark_cycle_pt (); !block_it.cycled_list (); block_it.forward ()) {
block = block_it.data ();
if (!block_processor (block) || selection_quit)
return;
}
}
/**
* process_selected_blocks()
*
* Walk the current block list applying the specified block processor function
* to each block selected.
*/
void
process_selected_blocks (BLOCK_LIST * block_list,
TBOX & selection_box,
BOOL8 block_processor (BLOCK *))
{
BLOCK_IT block_it(block_list);
BLOCK *block;
for (block_it.mark_cycle_pt (); !block_it.cycled_list (); block_it.forward ()) {
block = block_it.data ();
if (block->bounding_box ().overlap (selection_box)) {
if (!block_processor (block) || selection_quit)
return;
}
}
}
/**
* process_all_rows()
*
* Walk the current block list applying the specified row processor function
* to all rows
*/
void
process_all_rows (BLOCK_LIST * block_list,
BOOL8 row_processor (BLOCK *, ROW *))
{
BLOCK_IT block_it(block_list);
BLOCK *block;
ROW_IT row_it;
ROW *row;
for (block_it.mark_cycle_pt (); !block_it.cycled_list (); block_it.forward ()) {
block = block_it.data ();
row_it.set_to_list (block->row_list ());
for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
row = row_it.data ();
if (!row_processor (block, row) || selection_quit)
return;
}
}
}
/**
* process_selected_rows()
*
* Walk the current block list applying the specified row processor function
* to each row selected.
*/
void
process_selected_rows (BLOCK_LIST * block_list,
TBOX & selection_box,
BOOL8 row_processor (BLOCK *, ROW *))
{
BLOCK_IT block_it(block_list);
BLOCK *block;
ROW_IT row_it;
ROW *row;
for (block_it.mark_cycle_pt (); !block_it.cycled_list (); block_it.forward ()) {
block = block_it.data ();
if (block->bounding_box ().overlap (selection_box)) {
row_it.set_to_list (block->row_list ());
for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
row = row_it.data ();
if (row->bounding_box ().overlap (selection_box)) {
if (!row_processor (block, row) || selection_quit)
return;
}
}
}
}
}
/**
* process_all_rows_it() PASS ITERATORS
*
* Walk the current block list applying the specified row processor function
* to all rows
*/
void
process_all_rows_it (BLOCK_LIST * block_list,
BOOL8 row_processor (BLOCK *, ROW *, BLOCK_IT &, ROW_IT &))
{
BLOCK_IT block_it(block_list);
BLOCK *block;
ROW_IT row_it;
ROW *row;
for (block_it.mark_cycle_pt (); !block_it.cycled_list (); block_it.forward ()) {
block = block_it.data ();
row_it.set_to_list (block->row_list ());
for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
row = row_it.data ();
if (!row_processor (block, row, block_it, row_it) || selection_quit)
return;
}
}
}
/**
* process_selected_rows_it() PASS ITERATORS
*
* Walk the current block list applying the specified row processor function
* to each row selected.
*/
void
process_selected_rows_it (BLOCK_LIST * block_list,
TBOX & selection_box,
BOOL8 row_processor (BLOCK *, ROW *, BLOCK_IT &, ROW_IT &))
{
BLOCK_IT block_it(block_list);
BLOCK *block;
ROW_IT row_it;
ROW *row;
for (block_it.mark_cycle_pt (); !block_it.cycled_list (); block_it.forward ()) {
block = block_it.data ();
if (block->bounding_box ().overlap (selection_box)) {
row_it.set_to_list (block->row_list ());
for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
row = row_it.data ();
if (row->bounding_box ().overlap (selection_box)) {
if (!row_processor (block, row, block_it, row_it) || selection_quit)
return;
}
}
}
}
}

View File

@ -1,157 +0,0 @@
/**********************************************************************
* File: pagewalk.h (Formerly walkers.h)
* Description: Structure processors
* Author: Phil Cheatle
* Created: Thu Oct 10 16:25:24 BST 1991
*
* (C) Copyright 1991, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
#ifndef PAGEWALK_H
#define PAGEWALK_H
#include "ocrblock.h"
#include "ocrrow.h"
#include "werd.h"
#include "polyblob.h"
#include "stepblob.h"
#include "rect.h"
#include "varable.h"
#include "notdll.h"
#include "tesseractclass.h"
#define BLOCK_SPACING 20
extern BOOL_VAR_H (current_word_quit, FALSE, "Stop processing this word");
extern DLLSYM BOOL_VAR_H (selection_quit, FALSE,
"Stop processing this selection");
TBOX block_list_bounding_box( //find bounding box
BLOCK_LIST *block_list //of this block list
);
const TBOX block_list_compress( //shuffle up blocks
BLOCK_LIST *block_list);
void block_list_move( //move
BLOCK_LIST *block_list, //this list
ICOORD vec //by this vector
);
int block_name_order( //sort blocks
const void *block1p, //ptr to ptr to block1
const void *block2p //ptr to ptr to block2
);
void process_all_blobs ( //process blobs
BLOCK_LIST * block_list, //blocks to check
BOOL8 blob_processor ( //function to call
//function to call
BLOCK *, ROW *, WERD *, PBLOB *), BOOL8 c_blob_processor (
BLOCK
*,
ROW
*,
WERD
*,
C_BLOB
*));
void process_selected_blobs ( //process blobs
BLOCK_LIST * block_list, //blocks to check
//function to call
TBOX & selection_box, BOOL8 blob_processor (
//function to call
BLOCK *, ROW *, WERD *, PBLOB *), BOOL8 c_blob_processor (
BLOCK
*,
ROW
*,
WERD
*,
C_BLOB
*));
void process_all_words ( //process words
BLOCK_LIST * block_list, //blocks to check
BOOL8 word_processor ( //function to call
BLOCK *, ROW *, WERD *));
void process_selected_words ( //process words
BLOCK_LIST * block_list, //blocks to check
//function to call
TBOX & selection_box, BOOL8 word_processor (
BLOCK
*,
ROW
*,
WERD
*));
void process_all_words_it ( //process words
BLOCK_LIST * block_list, //blocks to check
BOOL8 word_processor ( //function to call
BLOCK *,
ROW *,
WERD *,
BLOCK_IT &,
ROW_IT &, WERD_IT &));
void process_selected_words_it ( //process words
BLOCK_LIST * block_list, //blocks to check
//function to call
TBOX & selection_box, BOOL8 word_processor (
BLOCK
*,
ROW
*,
WERD
*,
BLOCK_IT
&,
ROW_IT
&,
WERD_IT
&));
void process_all_blocks ( //process blocks
BLOCK_LIST * block_list, //blocks to check
BOOL8 block_processor ( //function to call
BLOCK *));
void process_selected_blocks ( //process blocks
BLOCK_LIST * block_list, //blocks to check
//function to call
TBOX & selection_box, BOOL8 block_processor (
BLOCK
*));
void process_all_rows ( //process words
BLOCK_LIST * block_list, //blocks to check
BOOL8 row_processor ( //function to call
BLOCK *, ROW *));
void process_selected_rows ( //process rows
BLOCK_LIST * block_list, //blocks to check
//function to call
TBOX & selection_box, BOOL8 row_processor (
BLOCK
*,
ROW
*));
void process_all_rows_it ( //process words
BLOCK_LIST * block_list, //blocks to check
BOOL8 row_processor ( //function to call
BLOCK *,
ROW *,
BLOCK_IT &, ROW_IT &));
void process_selected_rows_it ( //process rows
BLOCK_LIST * block_list, //blocks to check
//function to call
TBOX & selection_box, BOOL8 row_processor (
BLOCK
*,
ROW
*,
BLOCK_IT
&,
ROW_IT
&));
#endif

View File

@ -1,113 +0,0 @@
/**********************************************************************
* File: paircmp.cpp (Formerly paircmp.c)
* Description: Code to compare two blobs using the adaptive matcher
* Author: Ray Smith
* Created: Wed Apr 21 09:31:02 BST 1993
*
* (C) Copyright 1993, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
#ifdef _MSC_VER
#pragma warning(disable:4244) // Conversion warnings
#endif
#include "mfcpch.h"
#include "blobcmp.h"
#include "tfacep.h"
#include "paircmp.h"
#include "tesseractclass.h"
#define EXTERN
/**********************************************************************
* compare_blob_pairs
*
* A blob processor to compare pairs of selected blobs.
**********************************************************************/
namespace tesseract {
BOOL8 Tesseract::compare_blob_pairs( //blob processor
BLOCK *,
ROW *row, //row it came from
WERD *,
PBLOB *blob //blob to compare
) {
static ROW *prev_row = NULL; //other in pair
static PBLOB *prev_blob = NULL;
float rating; //from matcher
if (prev_row == NULL || prev_blob == NULL) {
prev_row = row;
prev_blob = blob;
}
else {
rating = compare_blobs (prev_blob, prev_row, blob, row);
tprintf ("Rating=%g\n", rating);
prev_row = NULL;
prev_blob = NULL;
}
return TRUE;
}
/**********************************************************************
* compare_blobs
*
* Compare 2 blobs and return the rating.
**********************************************************************/
float Tesseract::compare_blobs( //match 2 blobs
PBLOB *blob1, //first blob
ROW *row1, //row it came from
PBLOB *blob2, //other blob
ROW *row2) {
PBLOB *bn_blob1; //baseline norm
PBLOB *bn_blob2;
DENORM denorm1, denorm2;
float rating; //match result
bn_blob1 = blob1->baseline_normalise (row1, &denorm1);
bn_blob2 = blob2->baseline_normalise (row2, &denorm2);
rating = compare_bln_blobs (bn_blob1, &denorm1, bn_blob2, &denorm2);
delete bn_blob1;
delete bn_blob2;
return rating;
}
/**********************************************************************
* compare_bln_blobs
*
* Compare 2 baseline normalised blobs and return the rating.
**********************************************************************/
float Tesseract::compare_bln_blobs( //match 2 blobs
PBLOB *blob1, //first blob
DENORM *denorm1,
PBLOB *blob2, //other blob
DENORM *denorm2) {
TBLOB *tblob1; //tessblobs
TBLOB *tblob2;
TEXTROW tessrow1, tessrow2; //tess rows
float rating; //match result
tblob1 = make_tess_blob (blob1, TRUE);
make_tess_row(denorm1, &tessrow1);
tblob2 = make_tess_blob (blob2, TRUE);
make_tess_row(denorm2, &tessrow2);
rating = compare_tess_blobs (tblob1, &tessrow1, tblob2, &tessrow2);
free_blob(tblob1);
free_blob(tblob2);
return rating;
}
} // namespace tesseract

View File

@ -1,6 +1,6 @@
///////////////////////////////////////////////////////////////////////
// File: varabled.cpp
// Description: Variables Editor
// File: paramsd.cpp
// Description: Tesseract parameter Editor
// Author: Joern Wanke
// Created: Wed Jul 18 10:05:01 PDT 2007
//
@ -17,7 +17,7 @@
//
///////////////////////////////////////////////////////////////////////
//
// The variables editor is used to edit all the variables used within
// The parameters editor is used to edit all the parameters used within
// tesseract from the ui.
#ifdef WIN32
#else
@ -33,69 +33,68 @@
#endif
#ifndef GRAPHICS_DISABLED
#include "varabled.h"
#include "paramsd.h"
#include "params.h"
#include "scrollview.h"
#include "svmnode.h"
#include "varable.h"
#include "mainblk.h"
#define VARDIR "configs/" /*variables files */
#define VARDIR "configs/" /*parameters files */
#define MAX_ITEMS_IN_SUBMENU 30
const ERRCODE NO_VARIABLES_TO_EDIT = "No Variables defined to edit";
// The following variables should remain static globals, since they
// are used by debug editor, which uses a single Tesseract instance.
//
// Contains the mappings from unique VC ids to their actual pointers.
static std::map<int, VariableContent*> vcMap;
static int nrVariables = 0;
static std::map<int, ParamContent*> vcMap;
static int nrParams = 0;
static int writeCommands[2];
ELISTIZE(VariableContent)
ELISTIZE(ParamContent)
// Constructors for the various VarTypes.
VariableContent::VariableContent(STRING_VARIABLE* it) {
my_id_ = nrVariables;
nrVariables++;
var_type_ = VT_STRING;
// Constructors for the various ParamTypes.
ParamContent::ParamContent(tesseract::StringParam* it) {
my_id_ = nrParams;
nrParams++;
param_type_ = VT_STRING;
sIt = it;
vcMap[my_id_] = this;
}
// Constructors for the various VarTypes.
VariableContent::VariableContent(INT_VARIABLE* it) {
my_id_ = nrVariables;
nrVariables++;
var_type_ = VT_INTEGER;
// Constructors for the various ParamTypes.
ParamContent::ParamContent(tesseract::IntParam* it) {
my_id_ = nrParams;
nrParams++;
param_type_ = VT_INTEGER;
iIt = it;
vcMap[my_id_] = this;
}
// Constructors for the various VarTypes.
VariableContent::VariableContent(BOOL_VARIABLE* it) {
my_id_ = nrVariables;
nrVariables++;
var_type_ = VT_BOOLEAN;
// Constructors for the various ParamTypes.
ParamContent::ParamContent(tesseract::BoolParam* it) {
my_id_ = nrParams;
nrParams++;
param_type_ = VT_BOOLEAN;
bIt = it;
vcMap[my_id_] = this;
}
// Constructors for the various VarTypes.
VariableContent::VariableContent(double_VARIABLE* it) {
my_id_ = nrVariables;
nrVariables++;
var_type_ = VT_DOUBLE;
// Constructors for the various ParamTypes.
ParamContent::ParamContent(tesseract::DoubleParam* it) {
my_id_ = nrParams;
nrParams++;
param_type_ = VT_DOUBLE;
dIt = it;
vcMap[my_id_] = this;
}
// Gets a VC object identified by its ID.
VariableContent* VariableContent::GetVariableContentById(int id) {
ParamContent* ParamContent::GetParamContentById(int id) {
return vcMap[id];
}
// Copy the first N words from the source string to the target string.
// Words are delimited by "_".
void VariablesEditor::GetFirstWords(
void ParamsEditor::GetFirstWords(
const char *s, // source string
int n, // number of words
char *t // target string
@ -114,34 +113,34 @@ void VariablesEditor::GetFirstWords(
}
// Getter for the name.
const char* VariableContent::GetName() const {
if (var_type_ == VT_INTEGER) { return iIt->name_str(); }
else if (var_type_ == VT_BOOLEAN) { return bIt->name_str(); }
else if (var_type_ == VT_DOUBLE) { return dIt->name_str(); }
else if (var_type_ == VT_STRING) { return sIt->name_str(); }
const char* ParamContent::GetName() const {
if (param_type_ == VT_INTEGER) { return iIt->name_str(); }
else if (param_type_ == VT_BOOLEAN) { return bIt->name_str(); }
else if (param_type_ == VT_DOUBLE) { return dIt->name_str(); }
else if (param_type_ == VT_STRING) { return sIt->name_str(); }
else
return "ERROR: VariableContent::GetName()";
return "ERROR: ParamContent::GetName()";
}
// Getter for the description.
const char* VariableContent::GetDescription() const {
if (var_type_ == VT_INTEGER) { return iIt->info_str(); }
else if (var_type_ == VT_BOOLEAN) { return bIt->info_str(); }
else if (var_type_ == VT_DOUBLE) { return dIt->info_str(); }
else if (var_type_ == VT_STRING) { return sIt->info_str(); }
const char* ParamContent::GetDescription() const {
if (param_type_ == VT_INTEGER) { return iIt->info_str(); }
else if (param_type_ == VT_BOOLEAN) { return bIt->info_str(); }
else if (param_type_ == VT_DOUBLE) { return dIt->info_str(); }
else if (param_type_ == VT_STRING) { return sIt->info_str(); }
else return NULL;
}
// Getter for the value.
const char* VariableContent::GetValue() const {
const char* ParamContent::GetValue() const {
char* msg = new char[1024];
if (var_type_ == VT_INTEGER) {
if (param_type_ == VT_INTEGER) {
sprintf(msg, "%d", ((inT32) *(iIt)));
} else if (var_type_ == VT_BOOLEAN) {
} else if (param_type_ == VT_BOOLEAN) {
sprintf(msg, "%d", ((BOOL8) * (bIt)));
} else if (var_type_ == VT_DOUBLE) {
} else if (param_type_ == VT_DOUBLE) {
sprintf(msg, "%g", ((double) * (dIt)));
} else if (var_type_ == VT_STRING) {
} else if (param_type_ == VT_STRING) {
if (((STRING) * (sIt)).string() != NULL) {
sprintf(msg, "%s", ((STRING) * (sIt)).string());
} else {
@ -152,26 +151,26 @@ char* msg = new char[1024];
}
// Setter for the value.
void VariableContent::SetValue(const char* val) {
void ParamContent::SetValue(const char* val) {
// TODO (wanke) Test if the values actually are properly converted.
// (Quickly visible impacts?)
changed_ = TRUE;
if (var_type_ == VT_INTEGER) {
if (param_type_ == VT_INTEGER) {
iIt->set_value(atoi(val));
} else if (var_type_ == VT_BOOLEAN) {
} else if (param_type_ == VT_BOOLEAN) {
bIt->set_value(atoi(val));
} else if (var_type_ == VT_DOUBLE) {
} else if (param_type_ == VT_DOUBLE) {
dIt->set_value(strtod(val, NULL));
} else if (var_type_ == VT_STRING) {
} else if (param_type_ == VT_STRING) {
sIt->set_value(val);
}
}
// Gets the up to the first 3 prefixes from s (split by _).
// For example, tesseract_foo_bar will be split into tesseract,foo and bar.
void VariablesEditor::GetPrefixes(const char* s, STRING* level_one,
STRING* level_two,
STRING* level_three) {
void ParamsEditor::GetPrefixes(const char* s, STRING* level_one,
STRING* level_two,
STRING* level_three) {
char* p = new char[1024];
GetFirstWords(s, 1, p);
*level_one = p;
@ -183,50 +182,47 @@ void VariablesEditor::GetPrefixes(const char* s, STRING* level_one,
}
// Compare two VC objects by their name.
int VariableContent::Compare(const void* v1, const void* v2) {
const VariableContent* one =
*reinterpret_cast<const VariableContent* const *>(v1);
const VariableContent* two =
*reinterpret_cast<const VariableContent* const *>(v2);
int ParamContent::Compare(const void* v1, const void* v2) {
const ParamContent* one =
*reinterpret_cast<const ParamContent* const *>(v1);
const ParamContent* two =
*reinterpret_cast<const ParamContent* const *>(v2);
return strcmp(one->GetName(), two->GetName());
}
// Find all editable variables used within tesseract and create a
// Find all editable parameters used within tesseract and create a
// SVMenuNode tree from it.
// TODO (wanke): This is actually sort of hackish.
SVMenuNode* VariablesEditor::BuildListOfAllLeaves() { // find all variables.
SVMenuNode* ParamsEditor::BuildListOfAllLeaves(tesseract::Tesseract *tess) {
SVMenuNode* mr = new SVMenuNode();
VariableContent_LIST vclist;
VariableContent_IT vc_it(&vclist);
ParamContent_LIST vclist;
ParamContent_IT vc_it(&vclist);
// Amount counts the number of entries for a specific char*.
// TODO(rays) get rid of the use of std::map.
std::map<const char*, int> amount;
INT_VARIABLE_C_IT int_it(INT_VARIABLE::get_head());
BOOL_VARIABLE_C_IT bool_it(BOOL_VARIABLE::get_head());
STRING_VARIABLE_C_IT str_it(STRING_VARIABLE::get_head());
double_VARIABLE_C_IT dbl_it(double_VARIABLE::get_head());
// Add all variables to a list.
for (int_it.mark_cycle_pt(); !int_it.cycled_list(); int_it.forward()) {
vc_it.add_after_then_move(new VariableContent(int_it.data()));
}
for (bool_it.mark_cycle_pt(); !bool_it.cycled_list(); bool_it.forward()) {
vc_it.add_after_then_move(new VariableContent(bool_it.data()));
}
for (str_it.mark_cycle_pt(); !str_it.cycled_list(); str_it.forward()) {
vc_it.add_after_then_move(new VariableContent(str_it.data()));
}
for (dbl_it.mark_cycle_pt(); !dbl_it.cycled_list(); dbl_it.forward()) {
vc_it.add_after_then_move(new VariableContent(dbl_it.data()));
// Add all parameters to a list.
int v, i;
int num_iterations = (tess->params() == NULL) ? 1 : 2;
for (v = 0; v < num_iterations; ++v) {
tesseract::ParamsVectors *vec = (v == 0) ? GlobalParams() : tess->params();
for (i = 0; i < vec->int_params.size(); ++i) {
vc_it.add_after_then_move(new ParamContent(vec->int_params[i]));
}
for (i = 0; i < vec->bool_params.size(); ++i) {
vc_it.add_after_then_move(new ParamContent(vec->bool_params[i]));
}
for (i = 0; i < vec->string_params.size(); ++i) {
vc_it.add_after_then_move(new ParamContent(vec->string_params[i]));
}
for (i = 0; i < vec->double_params.size(); ++i) {
vc_it.add_after_then_move(new ParamContent(vec->double_params[i]));
}
}
// Count the # of entries starting with a specific prefix.
for (vc_it.mark_cycle_pt(); !vc_it.cycled_list(); vc_it.forward()) {
VariableContent* vc = vc_it.data();
ParamContent* vc = vc_it.data();
STRING tag;
STRING tag2;
STRING tag3;
@ -237,14 +233,14 @@ SVMenuNode* VariablesEditor::BuildListOfAllLeaves() { // find all variables.
amount[tag3.string()]++;
}
vclist.sort(VariableContent::Compare); // Sort the list alphabetically.
vclist.sort(ParamContent::Compare); // Sort the list alphabetically.
SVMenuNode* other = mr->AddChild("OTHER");
// go through the list again and this time create the menu structure.
vc_it.move_to_first();
for (vc_it.mark_cycle_pt(); !vc_it.cycled_list(); vc_it.forward()) {
VariableContent* vc = vc_it.data();
ParamContent* vc = vc_it.data();
STRING tag;
STRING tag2;
STRING tag3;
@ -270,15 +266,15 @@ SVMenuNode* VariablesEditor::BuildListOfAllLeaves() { // find all variables.
}
// Event listener. Waits for SVET_POPUP events and processes them.
void VariablesEditor::Notify(const SVEvent* sve) {
void ParamsEditor::Notify(const SVEvent* sve) {
if (sve->type == SVET_POPUP) { // only catch SVET_POPUP!
char* param = sve->parameter;
if (sve->command_id == writeCommands[0]) {
WriteVars(param, false);
WriteParams(param, false);
} else if (sve->command_id == writeCommands[1]) {
WriteVars(param, true);
WriteParams(param, true);
} else {
VariableContent* vc = VariableContent::GetVariableContentById(
ParamContent* vc = ParamContent::GetParamContentById(
sve->command_id);
vc->SetValue(param);
sv_window_->AddMessage("Setting %s to %s",
@ -287,13 +283,13 @@ void VariablesEditor::Notify(const SVEvent* sve) {
}
}
// Integrate the variables editor as popupmenu into the existing scrollview
// Integrate the parameters editor as popupmenu into the existing scrollview
// window (usually the pg editor). If sv == null, create a new empty
// empty window and attach the variables editor to that window (ugly).
VariablesEditor::VariablesEditor(const tesseract::Tesseract* tess,
// empty window and attach the parameters editor to that window (ugly).
ParamsEditor::ParamsEditor(tesseract::Tesseract* tess,
ScrollView* sv) {
if (sv == NULL) {
const char* name = "VarEditorMAIN";
const char* name = "ParamEditorMAIN";
sv = new ScrollView(name, 1, 1, 200, 200, 300, 200);
}
@ -302,31 +298,30 @@ VariablesEditor::VariablesEditor(const tesseract::Tesseract* tess,
//Only one event handler per window.
//sv->AddEventHandler((SVEventHandler*) this);
SVMenuNode* svMenuRoot = BuildListOfAllLeaves();
SVMenuNode* svMenuRoot = BuildListOfAllLeaves(tess);
STRING varfile;
varfile = tess->datadir;
varfile += VARDIR; // variables dir
varfile += "edited"; // actual name
STRING paramfile;
paramfile = tess->datadir;
paramfile += VARDIR; // parameters dir
paramfile += "edited"; // actual name
SVMenuNode* std_menu = svMenuRoot->AddChild ("Build Config File");
writeCommands[0] = nrVariables+1;
std_menu->AddChild("All Variables", writeCommands[0],
varfile.string(), "Config file name?");
writeCommands[0] = nrParams+1;
std_menu->AddChild("All Parameters", writeCommands[0],
paramfile.string(), "Config file name?");
writeCommands[1] = nrVariables+2;
std_menu->AddChild ("changed_ Variables Only", writeCommands[1],
varfile.string(), "Config file name?");
writeCommands[1] = nrParams+2;
std_menu->AddChild ("changed_ Parameters Only", writeCommands[1],
paramfile.string(), "Config file name?");
svMenuRoot->BuildMenu(sv, false);
}
// Write all (changed_) variables to a config file.
void VariablesEditor::WriteVars(char *filename, // in this file
bool changes_only // changed_ vars only?
) {
// Write all (changed_) parameters to a config file.
void ParamsEditor::WriteParams(char *filename,
bool changes_only) {
FILE *fp; // input file
char msg_str[255];
// if file exists
@ -344,10 +339,10 @@ void VariablesEditor::WriteVars(char *filename, // in this file
return;
}
for (std::map<int, VariableContent*>::iterator iter = vcMap.begin();
for (std::map<int, ParamContent*>::iterator iter = vcMap.begin();
iter != vcMap.end();
++iter) {
VariableContent* cur = iter->second;
ParamContent* cur = iter->second;
if (!changes_only || cur->HasChanged()) {
fprintf (fp, "%-25s %-12s # %s\n",
cur->GetName(), cur->GetValue(), cur->GetDescription());

124
ccmain/paramsd.h Normal file
View File

@ -0,0 +1,124 @@
///////////////////////////////////////////////////////////////////////
// File: paramsd.cpp
// Description: Tesseract parameter editor
// Author: Joern Wanke
// Created: Wed Jul 18 10:05:01 PDT 2007
//
// (C) Copyright 2007, Google Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////
//
// Tesseract parameter editor is used to edit all the parameters used
// within tesseract from the ui.
#ifndef GRAPHICS_DISABLED
#ifndef VARABLED_H
#define VARABLED_H
#include "elst.h"
#include "scrollview.h"
#include "params.h"
#include "tesseractclass.h"
class SVMenuNode;
// A list of all possible parameter types used.
enum ParamType {
VT_INTEGER,
VT_BOOLEAN,
VT_STRING,
VT_DOUBLE
};
// A rather hackish helper structure which can take any kind of parameter input
// (defined by ParamType) and do a couple of common operations on them, like
// comparisond or getting its value. It is used in the context of the
// ParamsEditor as a bridge from the internal tesseract parameters to the
// ones displayed by the ScrollView server.
class ParamContent : public ELIST_LINK {
public:
// Compare two VC objects by their name.
static int Compare(const void* v1, const void* v2);
// Gets a VC object identified by its ID.
static ParamContent* GetParamContentById(int id);
// Constructors for the various ParamTypes.
ParamContent() {
}
ParamContent(tesseract::StringParam* it);
ParamContent(tesseract::IntParam* it);
ParamContent(tesseract::BoolParam* it);
ParamContent(tesseract::DoubleParam* it);
// Getters and Setters.
void SetValue(const char* val);
const char* GetValue() const;
const char* GetName() const;
const char* GetDescription() const;
int GetId() { return my_id_; }
bool HasChanged() { return changed_; }
private:
// The unique ID of this VC object.
int my_id_;
// Whether the parameter was changed_ and thus needs to be rewritten.
bool changed_;
// The actual ParamType of this VC object.
ParamType param_type_;
tesseract::StringParam* sIt;
tesseract::IntParam* iIt;
tesseract::BoolParam* bIt;
tesseract::DoubleParam* dIt;
};
ELISTIZEH(ParamContent)
// The parameters editor enables the user to edit all the parameters used within
// tesseract. It can be invoked on its own, but is supposed to be invoked by
// the program editor.
class ParamsEditor : public SVEventHandler {
public:
// Integrate the parameters editor as popupmenu into the existing scrollview
// window (usually the pg editor). If sv == null, create a new empty
// empty window and attach the parameter editor to that window (ugly).
ParamsEditor(tesseract::Tesseract*, ScrollView* sv = NULL);
// Event listener. Waits for SVET_POPUP events and processes them.
void Notify(const SVEvent* sve);
private:
// Gets the up to the first 3 prefixes from s (split by _).
// For example, tesseract_foo_bar will be split into tesseract,foo and bar.
void GetPrefixes(const char* s, STRING* level_one,
STRING* level_two, STRING* level_three);
// Gets the first n words (split by _) and puts them in t.
// For example, tesseract_foo_bar with N=2 will yield tesseract_foo_.
void GetFirstWords(const char *s, // source string
int n, // number of words
char *t); // target string
// Find all editable parameters used within tesseract and create a
// SVMenuNode tree from it.
SVMenuNode *BuildListOfAllLeaves(tesseract::Tesseract *tess);
// Write all (changed_) parameters to a config file.
void WriteParams(char* filename, bool changes_only);
ScrollView* sv_window_;
};
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@ -24,8 +24,7 @@
#include "ocrrow.h"
#include "werd.h"
#include "rect.h"
#include "pagewalk.h"
#include "varable.h"
#include "params.h"
#include "notdll.h"
#include "tesseractclass.h"
@ -45,7 +44,6 @@ class PGEventHandler : public SVEventHandler {
};
extern BLOCK_LIST *current_block_list;
extern BOOL8 *current_image_changed;
extern STRING_VAR_H (editor_image_win_name, "EditorImage",
"Editor image window name");
extern INT_VAR_H (editor_image_xpos, 590, "Editor image X Pos");
@ -71,14 +69,8 @@ extern INT_VAR_H (editor_word_height, 240, "Word window height");
extern INT_VAR_H (editor_word_width, 655, "Word window width");
extern double_VAR_H (editor_smd_scale_factor, 1.0, "Scaling for smd image");
void add_word( //to block list
WERD *word, //word to be added
ROW *src_row, //source row
BLOCK *src_block, //source block
BLOCK_LIST *dest_block_list //add to this
);
ScrollView* bln_word_window_handle(); //return handle
void build_image_window(TBOX page_bounding_box);
void build_image_window(int width, int height);
void display_bln_lines(ScrollView window,
ScrollView::Color colour,
float scale_factor,
@ -86,86 +78,11 @@ void display_bln_lines(ScrollView window,
float minx,
float maxx);
//function to call
void do_re_display (BOOL8 word_painter (
BLOCK *, ROW *, WERD *));
const TBOX do_tidy_cmd(); //tidy
void do_view_cmd();
void do_write_file( //serialise
char *name //file name
);
void pgeditor_msg( //message display
const char *msg);
void pgeditor_show_point( //display coords
SVEvent *event);
void pgeditor_write_file( //serialise
char *name, //file name
BLOCK_LIST *blocks //block list to write
);
//put bln word in box
float re_scale_and_move_bln_word(WERD *norm_word, //BL normalised word
const TBOX &box //destination box
);
void re_segment_word( //break/join words
BLOCK_LIST *block_list, //blocks to check
TBOX &selection_box);
void block_space_stat( //show space stats
BLOCK_LIST *block_list, //blocks to check
TBOX &selection_box);
void row_space_stat( //show space stats
BLOCK_LIST *block_list, //blocks to check
TBOX &selection_box);
void show_point( //display posn of bloba word
BLOCK_LIST *block_list, //blocks to check
float x,
float y);
//display a word
BOOL8 word_blank_and_set_display(BLOCK *block, //block holding word
ROW *row, //row holding word
WERD *word //word to be processed
);
BOOL8 word_bln_display( //bln & display
BLOCK *, //block holding word
ROW *row, //row holding word
WERD *word //word to be processed
);
BOOL8 word_change_text( //change correct text
BLOCK *block, //block holding word
ROW *row, //row holding word
WERD *word //word to be processed
);
BOOL8 word_copy( //copy a word
BLOCK *block, //block holding word
ROW *row, //row holding word
WERD *word //word to be processed
);
BOOL8 word_delete( //delete a word
BLOCK *block, //block holding word
ROW *row, //row holding word
WERD *word, //word to be processed
BLOCK_IT &block_it, //block list iterator
ROW_IT &row_it, //row list iterator
WERD_IT &word_it //word list iterator
);
BOOL8 word_display( // display a word
BLOCK *, //block holding word
ROW *row, //row holding word
WERD *word //word to be processed
);
BOOL8 word_dumper( //dump word
BLOCK *block, //block holding word
ROW *row, //row holding word
WERD *word //word to be processed
);
BOOL8 word_set_display( //display a word
BLOCK *block, //block holding word
ROW *row, //row holding word
WERD *word //word to be processed
);
BOOL8 word_toggle_seg( //toggle seg flag
BLOCK *, //block holding word
ROW *, //row holding word
WERD *word //word to be processed
);
void do_check_mem( //do it
inT32 level);
void show_point(PAGE_RES* page_res, float x, float y);
#endif

182
ccmain/recogtraining.cpp Normal file
View File

@ -0,0 +1,182 @@
///////////////////////////////////////////////////////////////////////
// File: recogtraining.cpp
// Description: Functions for ambiguity and parameter training.
// Author: Daria Antonova
// Created: Mon Aug 13 11:26:43 PDT 2009
//
// (C) Copyright 2009, Google Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////
#include "tesseractclass.h"
#include "boxread.h"
#include "control.h"
#include "cutil.h"
#include "host.h"
#include "permute.h"
#include "ratngs.h"
#include "reject.h"
#include "stopper.h"
namespace tesseract {
const inT16 kMaxBoxEdgeDiff = 2;
// Sets flags necessary for recognition in the training mode.
// Opens and returns the pointer to the output file.
FILE *Tesseract::init_recog_training(const STRING &fname) {
if (tessedit_ambigs_training) {
tessedit_tess_adaption_mode.set_value(0); // turn off adaption
tessedit_enable_doc_dict.set_value(0); // turn off document dictionary
save_best_choices.set_value(1); // save individual char choices
getDict().save_raw_choices.set_value(1); // save raw choices
getDict().permute_only_top.set_value(true); // use only top choice permuter
tessedit_ok_mode.set_value(0); // turn off context checking
// Explore all segmentations.
getDict().stopper_no_acceptable_choices.set_value(1);
}
STRING output_fname = fname;
const char *lastdot = strrchr(output_fname.string(), '.');
if (lastdot != NULL) output_fname[lastdot - output_fname.string()] = '\0';
output_fname += ".txt";
FILE *output_file = open_file(output_fname.string(), "a+");
return output_file;
}
// Copies the bounding box from page_res_it->word() to the given TBOX.
bool read_t(PAGE_RES_IT *page_res_it, TBOX *tbox) {
if (page_res_it->word() != NULL) {
*tbox = page_res_it->word()->word->bounding_box();
page_res_it->forward();
return true;
} else {
return false;
}
}
// Reads the next box from the given box file into TBOX.
bool read_b(int applybox_page, int *line_number, FILE *box_file,
char *label, TBOX *bbox) {
int x_min, y_min, x_max, y_max;
if (read_next_box(applybox_page, line_number, box_file, label,
&x_min, &y_min, &x_max, &y_max)) {
bbox->set_to_given_coords(x_min, y_min, x_max, y_max);
return true;
} else {
return false;
}
}
// This function takes tif/box pair of files and runs recognition on the image,
// while making sure that the word bounds that tesseract identified roughly
// match to those specified by the input box file. For each word (ngram in a
// single bounding box from the input box file) it outputs the ocred result,
// the correct label, rating and certainty.
void Tesseract::recog_training_segmented(const STRING &fname,
PAGE_RES *page_res,
volatile ETEXT_DESC *monitor,
FILE *output_file) {
STRING box_fname = fname;
const char *lastdot = strrchr(box_fname.string(), '.');
if (lastdot != NULL) box_fname[lastdot - box_fname.string()] = '\0';
box_fname += ".box";
// read_next_box() will close box_file
FILE *box_file = open_file(box_fname.string(), "r");
PAGE_RES_IT page_res_it;
page_res_it.page_res = page_res;
page_res_it.restart_page();
char label[kBoxReadBufSize];
// Process all the words on this page.
TBOX tbox; // tesseract-identified box
TBOX bbox; // box from the box file
bool keep_going;
int line_number = 0;
do {
keep_going = read_t(&page_res_it, &tbox);
keep_going &= read_b(applybox_page, &line_number, box_file, label, &bbox);
// Align bottom left points of the TBOXes.
while (keep_going &&
!NearlyEqual<int>(tbox.bottom(), bbox.bottom(), kMaxBoxEdgeDiff)) {
keep_going = (bbox.bottom() < tbox.bottom()) ?
read_t(&page_res_it, &tbox) :
read_b(applybox_page, &line_number, box_file, label, &bbox);
}
while (keep_going &&
!NearlyEqual<int>(tbox.left(), bbox.left(), kMaxBoxEdgeDiff)) {
keep_going = (bbox.left() > tbox.left()) ? read_t(&page_res_it, &tbox) :
read_b(applybox_page, &line_number, box_file, label, &bbox);
}
// OCR the word if top right points of the TBOXes are similar.
if (keep_going &&
NearlyEqual<int>(tbox.right(), bbox.right(), kMaxBoxEdgeDiff) &&
NearlyEqual<int>(tbox.top(), bbox.top(), kMaxBoxEdgeDiff)) {
ambigs_classify_and_output(page_res_it.prev_word(),
page_res_it.prev_row(),
page_res_it.prev_block(),
label, output_file);
}
} while (keep_going);
}
// Runs classify_word_pass1() on the current word. Outputs Tesseract's
// raw choice as a result of the classification. For words labeled with a
// single unichar also outputs all alternatives from blob_choices of the
// best choice.
void Tesseract::ambigs_classify_and_output(WERD_RES *werd_res,
ROW_RES *row_res,
BLOCK_RES *block_res,
const char *label,
FILE *output_file) {
int offset;
// Classify word.
classify_word_pass1(werd_res, row_res->row, block_res->block);
WERD_CHOICE *best_choice = werd_res->best_choice;
ASSERT_HOST(best_choice != NULL);
ASSERT_HOST(best_choice->blob_choices() != NULL);
// Compute the number of unichars in the label.
int label_num_unichars = 0;
int step = 1; // should be non-zero on the first iteration
for (offset = 0; label[offset] != '\0' && step > 0;
step = getDict().getUnicharset().step(label + offset),
offset += step, ++label_num_unichars);
if (step == 0) {
tprintf("Not outputting illegal unichar %s\n", label);
return;
}
// Output all classifier choices for the unigrams (1->1 classifications).
if (label_num_unichars == 1 && best_choice->blob_choices()->length() == 1) {
BLOB_CHOICE_LIST_C_IT outer_blob_choice_it;
outer_blob_choice_it.set_to_list(best_choice->blob_choices());
BLOB_CHOICE_IT blob_choice_it;
blob_choice_it.set_to_list(outer_blob_choice_it.data());
for (blob_choice_it.mark_cycle_pt();
!blob_choice_it.cycled_list();
blob_choice_it.forward()) {
BLOB_CHOICE *blob_choice = blob_choice_it.data();
if (blob_choice->unichar_id() != INVALID_UNICHAR_ID) {
fprintf(output_file, "%s\t%s\t%.4f\t%.4f\n",
unicharset.id_to_unichar(blob_choice->unichar_id()),
label, blob_choice->rating(), blob_choice->certainty());
}
}
}
// Output raw choices for many->many and 1->many classifications.
getDict().PrintAmbigAlternatives(output_file, label, label_num_unichars);
}
} // namespace tesseract

File diff suppressed because it is too large Load Diff

View File

@ -20,121 +20,15 @@
#ifndef REJECT_H
#define REJECT_H
#include "varable.h"
#include "params.h"
#include "pageres.h"
#include "notdll.h"
extern INT_VAR_H (tessedit_reject_mode, 5, "Rejection algorithm");
extern INT_VAR_H (tessedit_ok_mode, 5, "Acceptance decision algorithm");
extern BOOL_VAR_H (tessedit_use_nn, TRUE, "");
extern BOOL_VAR_H (tessedit_rejection_debug, FALSE, "Adaption debug");
extern BOOL_VAR_H (tessedit_rejection_stats, FALSE, "Show NN stats");
extern BOOL_VAR_H (tessedit_flip_0O, TRUE, "Contextual 0O O0 flips");
extern double_VAR_H (tessedit_lower_flip_hyphen, 1.5,
"Aspect ratio dot/hyphen test");
extern double_VAR_H (tessedit_upper_flip_hyphen, 1.8,
"Aspect ratio dot/hyphen test");
extern BOOL_VAR_H (rej_trust_doc_dawg, FALSE,
"Use DOC dawg in 11l conf. detector");
extern BOOL_VAR_H (rej_1Il_use_dict_word, FALSE, "Use dictword test");
extern BOOL_VAR_H (rej_1Il_trust_permuter_type, TRUE, "Dont double check");
extern BOOL_VAR_H (one_ell_conflict_default, TRUE,
"one_ell_conflict default");
extern BOOL_VAR_H (show_char_clipping, FALSE, "Show clip image window?");
extern BOOL_VAR_H (nn_debug, FALSE, "NN DEBUGGING?");
extern BOOL_VAR_H (nn_reject_debug, FALSE, "NN DEBUG each char?");
extern BOOL_VAR_H (nn_lax, FALSE, "Use 2nd rate matches");
extern BOOL_VAR_H (nn_double_check_dict, FALSE, "Double check");
extern BOOL_VAR_H (nn_conf_double_check_dict, TRUE,
"Double check for confusions");
extern BOOL_VAR_H (nn_conf_1Il, TRUE, "NN use 1Il conflicts");
extern BOOL_VAR_H (nn_conf_Ss, TRUE, "NN use Ss conflicts");
extern BOOL_VAR_H (nn_conf_hyphen, TRUE, "NN hyphen conflicts");
extern BOOL_VAR_H (nn_conf_test_good_qual, FALSE, "NN dodgy 1Il cross check");
extern BOOL_VAR_H (nn_conf_test_dict, TRUE, "NN dodgy 1Il cross check");
extern BOOL_VAR_H (nn_conf_test_sensible, TRUE, "NN dodgy 1Il cross check");
extern BOOL_VAR_H (nn_conf_strict_on_dodgy_chs, TRUE,
"Require stronger NN match");
extern double_VAR_H (nn_dodgy_char_threshold, 0.99, "min accept score");
extern INT_VAR_H (nn_conf_accept_level, 4, "NN accept dodgy 1Il matches? ");
extern INT_VAR_H (nn_conf_initial_i_level, 3,
"NN accept initial Ii match level ");
extern BOOL_VAR_H (no_unrej_dubious_chars, TRUE,
"Dubious chars next to reject?");
extern BOOL_VAR_H (no_unrej_no_alphanum_wds, TRUE,
"Stop unrej of non A/N wds?");
extern BOOL_VAR_H (no_unrej_1Il, FALSE, "Stop unrej of 1Ilchars?");
extern BOOL_VAR_H (rej_use_tess_accepted, TRUE,
"Individual rejection control");
extern BOOL_VAR_H (rej_use_tess_blanks, TRUE, "Individual rejection control");
extern BOOL_VAR_H (rej_use_good_perm, TRUE, "Individual rejection control");
extern BOOL_VAR_H (rej_use_sensible_wd, FALSE, "Extend permuter check");
extern BOOL_VAR_H (rej_alphas_in_number_perm, FALSE, "Extend permuter check");
extern double_VAR_H (rej_whole_of_mostly_reject_word_fract, 0.85,
"if >this fract");
extern INT_VAR_H (rej_mostly_reject_mode, 1,
"0-never, 1-afterNN, 2-after new xht");
extern double_VAR_H (tessed_fullstop_aspect_ratio, 1.2,
"if >this fract then reject");
extern INT_VAR_H (net_image_width, 40, "NN input image width");
extern INT_VAR_H (net_image_height, 36, "NN input image height");
extern INT_VAR_H (net_image_x_height, 22, "NN input image x_height");
extern INT_VAR_H (tessedit_image_border, 2, "Rej blbs near image edge limit");
extern INT_VAR_H (net_bl_nodes, 20, "Number of baseline nodes");
extern double_VAR_H (nn_reject_threshold, 0.5, "NN min accept score");
extern double_VAR_H (nn_reject_head_and_shoulders, 0.6,
"top scores sep factor");
extern STRING_VAR_H (ok_single_ch_non_alphanum_wds, "-?\075",
"Allow NN to unrej");
extern STRING_VAR_H (ok_repeated_ch_non_alphanum_wds, "-?*\075",
"Allow NN to unrej");
extern STRING_VAR_H (conflict_set_I_l_1, "Il1[]", "Il1 conflict set");
extern STRING_VAR_H (conflict_set_S_s, "Ss$", "Ss conflict set");
extern STRING_VAR_H (conflict_set_hyphen, "-_~", "hyphen conflict set");
extern STRING_VAR_H (dubious_chars_left_of_reject, "!'+`()-./\\<>;:^_,~\"",
"Unreliable chars");
extern STRING_VAR_H (dubious_chars_right_of_reject, "!'+`()-./\\<>;:^_,~\"",
"Unreliable chars");
extern INT_VAR_H (min_sane_x_ht_pixels, 8,
"Reject any x-ht lt or eq than this");
void reject_blanks(WERD_RES *word);
void reject_I_1_L(WERD_RES *word);
//detailed results
void reject_poor_matches(WERD_RES *word, BLOB_CHOICE_LIST_CLIST *blob_choices);
float compute_reject_threshold( //compute threshold //detailed results
BLOB_CHOICE_LIST_CLIST *blob_choices);
int sort_floats( //qsort function
const void *arg1, //ptrs to floats
const void *arg2);
void reject_edge_blobs(WERD_RES *word);
BOOL8 word_contains_non_1_digit(const char *word,
const char *word_lengths);
//of character
inT16 nn_match_char(IMAGE &scaled_image,
float baseline_pos, //rel to scaled_image
BOOL8 dict_word, //part of dict wd?
BOOL8 checked_dict_word, //part of dict wd?
BOOL8 sensible_word, //part acceptable str?
BOOL8 centre, //not at word ends?
BOOL8 good_quality_word, //initial segmentation
char tess_ch //confirm this?
);
inT16 evaluate_net_match(char top,
float top_score,
char next,
float next_score,
char tess_ch,
BOOL8 dict_word,
BOOL8 checked_dict_word,
BOOL8 sensible_word,
BOOL8 centre,
BOOL8 good_quality_word);
void dont_allow_dubious_chars(WERD_RES *word);
float compute_reject_threshold(BLOB_CHOICE_LIST_CLIST *blob_choices);
BOOL8 word_contains_non_1_digit(const char *word, const char *word_lengths);
void dont_allow_1Il(WERD_RES *word);
void reject_mostly_rejects( //rej all if most rejectd
WERD_RES *word);
void flip_hyphens(WERD_RES *word);
void flip_0O(WERD_RES *word);
BOOL8 non_0_digit(const char* str, int length);

View File

@ -34,43 +34,31 @@
* @name tess_segment_pass1
*
* Segment a word using the pass1 conditions of the tess segmenter.
* @param word bln word to do
* @param denorm de-normaliser
* @param matcher matcher function
* @param raw_choice raw result
* @param word word to do
* @param blob_choices list of blob lists
* @param outword bln word output
*/
namespace tesseract {
WERD_CHOICE *Tesseract::tess_segment_pass1(WERD *word,
DENORM *denorm,
POLY_MATCHER matcher,
WERD_CHOICE *&raw_choice,
BLOB_CHOICE_LIST_CLIST *blob_choices,
WERD *&outword) {
WERD_CHOICE *result; //return value
void Tesseract::tess_segment_pass1(WERD_RES *word,
BLOB_CHOICE_LIST_CLIST *blob_choices) {
int saved_enable_assoc = 0;
int saved_chop_enable = 0;
if (word->flag (W_DONT_CHOP)) {
if (word->word->flag(W_DONT_CHOP)) {
saved_enable_assoc = wordrec_enable_assoc;
saved_chop_enable = chop_enable;
wordrec_enable_assoc.set_value(0);
chop_enable.set_value(0);
if (word->flag (W_REP_CHAR))
permute_only_top = 1;
if (word->word->flag(W_REP_CHAR))
getDict().permute_only_top.set_value(true);
}
set_pass1();
// tprintf("pass1 chop on=%d, seg=%d, onlytop=%d",chop_enable,enable_assoc,permute_only_top);
result = recog_word (word, denorm, matcher, NULL, NULL, FALSE,
raw_choice, blob_choices, outword);
if (word->flag (W_DONT_CHOP)) {
recog_word(word, blob_choices);
if (word->word->flag(W_DONT_CHOP)) {
wordrec_enable_assoc.set_value(saved_enable_assoc);
chop_enable.set_value(saved_chop_enable);
permute_only_top = 0;
getDict().permute_only_top.set_value(false);
}
return result;
}
@ -78,101 +66,32 @@ WERD_CHOICE *Tesseract::tess_segment_pass1(WERD *word,
* @name tess_segment_pass2
*
* Segment a word using the pass2 conditions of the tess segmenter.
* @param word bln word to do
* @param denorm de-normaliser
* @param matcher matcher function
* @param raw_choice raw result
* @param word word to do
* @param blob_choices list of blob lists
* @param outword bln word output
*/
WERD_CHOICE *Tesseract::tess_segment_pass2(WERD *word,
DENORM *denorm,
POLY_MATCHER matcher,
WERD_CHOICE *&raw_choice,
BLOB_CHOICE_LIST_CLIST *blob_choices,
WERD *&outword) {
WERD_CHOICE *result; //return value
void Tesseract::tess_segment_pass2(WERD_RES *word,
BLOB_CHOICE_LIST_CLIST *blob_choices) {
int saved_enable_assoc = 0;
int saved_chop_enable = 0;
if (word->flag (W_DONT_CHOP)) {
if (word->word->flag(W_DONT_CHOP)) {
saved_enable_assoc = wordrec_enable_assoc;
saved_chop_enable = chop_enable;
wordrec_enable_assoc.set_value(0);
chop_enable.set_value(0);
if (word->flag (W_REP_CHAR))
permute_only_top = 1;
if (word->word->flag(W_REP_CHAR))
getDict().permute_only_top.set_value(true);
}
set_pass2();
result = recog_word (word, denorm, matcher, NULL, NULL, FALSE,
raw_choice, blob_choices, outword);
if (word->flag (W_DONT_CHOP)) {
recog_word(word, blob_choices);
if (word->word->flag(W_DONT_CHOP)) {
wordrec_enable_assoc.set_value(saved_enable_assoc);
chop_enable.set_value(saved_chop_enable);
permute_only_top = 0;
getDict().permute_only_top.set_value(false);
}
return result;
}
/**
* @name correct_segment_pass2
*
* Segment a word correctly using the pass2 conditions of the tess segmenter.
* Then call the tester with all the correctly segmented blobs.
* If the correct segmentation cannot be found, the tester is called
* with the segmentation found by tess and all the correct flags set to
* false and all strings are NULL.
* @param word bln word to do
* @param denorm de-normaliser
* @param matcher matcher function
* @param tester tester function
* @param raw_choice raw result
* @param blob_choices list of blob lists
* @param outword bln word output
*/
WERD_CHOICE *Tesseract::correct_segment_pass2(WERD *word,
DENORM *denorm,
POLY_MATCHER matcher,
POLY_TESTER tester,
WERD_CHOICE *&raw_choice,
BLOB_CHOICE_LIST_CLIST *blob_choices,
WERD *&outword) {
set_pass2();
return recog_word (word, denorm, matcher, NULL, tester, TRUE,
raw_choice, blob_choices, outword);
}
/**
* @name test_segment_pass2
*
* Segment a word correctly using the pass2 conditions of the tess segmenter.
* Then call the tester on all words used by tess in its search.
* Do this only on words where the correct segmentation could be found.
* @param word bln word to do
* @param denorm de-normaliser
* @param matcher matcher function
* @param tester tester function
* @param raw_choice raw result
* @param blob_choices list of blob lists
* @param outword bln word output
*/
WERD_CHOICE *Tesseract::test_segment_pass2(WERD *word,
DENORM *denorm,
POLY_MATCHER matcher,
POLY_TESTER tester,
WERD_CHOICE *&raw_choice,
BLOB_CHOICE_LIST_CLIST *blob_choices,
WERD *&outword) {
set_pass2();
return recog_word (word, denorm, matcher, tester, NULL, TRUE,
raw_choice, blob_choices, outword);
}
/**
* @name tess_acceptable_word
*
@ -180,202 +99,10 @@ WERD_CHOICE *Tesseract::test_segment_pass2(WERD *word,
* @param word_choice after context
* @param raw_choice before context
*/
BOOL8 Tesseract::tess_acceptable_word(WERD_CHOICE *word_choice,
WERD_CHOICE *raw_choice) {
return getDict().AcceptableResult(*word_choice, *raw_choice);
}
/**
* @name tess_adaptable_word
*
* @return true if the word is regarded as "good enough".
* @param word word to test
* @param best_choice after context
* @param raw_choice before context
*/
BOOL8 Tesseract::tess_adaptable_word(WERD *word,
WERD_CHOICE *best_choice,
WERD_CHOICE *raw_choice) {
TWERD *tessword = make_tess_word(word, NULL);
int result = (tessword && best_choice && raw_choice &&
AdaptableWord(tessword, *best_choice, *raw_choice));
delete_word(tessword);
return result != 0;
}
/**
* @name tess_cn_matcher
*
* Match a blob using the Tess Char Normalized (non-adaptive) matcher
* only.
* @param pblob previous blob
* @param blob blob to match
* @param nblob next blob
* @param word word it came from
* @param denorm de-normaliser
* @param[out] ratings list of results
* @param[out] cpresults may be null
*/
void Tesseract::tess_cn_matcher(PBLOB *pblob,
PBLOB *blob,
PBLOB *nblob,
WERD *word,
DENORM *denorm,
BLOB_CHOICE_LIST *ratings,
CLASS_PRUNER_RESULTS cpresults) {
TBLOB *tessblob; //converted blob
TEXTROW tessrow; //dummy row
tess_cn_matching.set_value(true); //turn it on
tess_bn_matching.set_value(false);
//convert blob
tessblob = make_rotated_tess_blob(denorm, blob, true);
//make dummy row
make_tess_row(denorm, &tessrow);
//classify
AdaptiveClassifier(tessblob, NULL, &tessrow, ratings, cpresults);
free_blob(tessblob);
}
/**
* @name tess_bn_matcher
*
* Match a blob using the Tess Baseline Normalized (adaptive) matcher
* only.
* @param pblob previous blob
* @param blob blob to match
* @param nblob next blob
* @param word word it came from
* @param denorm de-normaliser
* @param[out] ratings list of results
*/
void Tesseract::tess_bn_matcher(PBLOB *pblob,
PBLOB *blob,
PBLOB *nblob,
WERD *word,
DENORM *denorm,
BLOB_CHOICE_LIST *ratings) {
TBLOB *tessblob; //converted blob
TEXTROW tessrow; //dummy row
tess_bn_matching.set_value(true); //turn it on
tess_cn_matching.set_value(false);
//convert blob
tessblob = make_rotated_tess_blob(denorm, blob, true);
//make dummy row
make_tess_row(denorm, &tessrow);
//classify
AdaptiveClassifier(tessblob, NULL, &tessrow, ratings, NULL);
free_blob(tessblob);
}
/**
* @name tess_default_matcher
*
* Match a blob using the default functionality of the Tess matcher.
* @param pblob previous blob
* @param blob blob to match
* @param nblob next blob
* @param word word it came from
* @param denorm de-normaliser
* @param[out] ratings list of results
* @param script (unused)
*/
void Tesseract::tess_default_matcher(PBLOB *pblob,
PBLOB *blob,
PBLOB *nblob,
WERD *word,
DENORM *denorm,
BLOB_CHOICE_LIST *ratings,
const char* script) {
assert(ratings != NULL);
TBLOB *tessblob; //converted blob
TEXTROW tessrow; //dummy row
tess_bn_matching.set_value(false); //turn it off
tess_cn_matching.set_value(false);
//convert blob
tessblob = make_rotated_tess_blob(denorm, blob, true);
//make dummy row
make_tess_row(denorm, &tessrow);
//classify
AdaptiveClassifier (tessblob, NULL, &tessrow, ratings, NULL);
free_blob(tessblob);
}
} // namespace tesseract
/**
* @name tess_training_tester
*
* Matcher tester function which actually trains tess.
* @param filename filename to output
* @param blob blob to match
* @param denorm de-normaliser
* @param correct ly segmented
* @param text correct text
* @param count chars in text
* @param[out] ratings list of results
*/
void tess_training_tester(const STRING& filename,
PBLOB *blob,
DENORM *denorm,
BOOL8 correct,
char *text,
inT32 count,
BLOB_CHOICE_LIST *ratings) {
TBLOB *tessblob; //converted blob
TEXTROW tessrow; //dummy row
if (correct) {
classify_norm_method.set_value(character); // force char norm spc 30/11/93
tess_bn_matching.set_value(false); //turn it off
tess_cn_matching.set_value(false);
//convert blob
tessblob = make_tess_blob (blob, TRUE);
//make dummy row
make_tess_row(denorm, &tessrow);
//learn it
LearnBlob(filename, tessblob, &tessrow, text);
free_blob(tessblob);
}
}
namespace tesseract {
/**
* @name tess_adapter
*
* Adapt to the word using the Tesseract mechanism.
* @param word bln word
* @param denorm de-normalise
* @param choice string for word
* @param raw_choice before context
* @param rejmap reject map
*/
void Tesseract::tess_adapter(WERD *word,
DENORM *denorm,
const WERD_CHOICE& choice,
const WERD_CHOICE& raw_choice,
const char *rejmap) {
TWERD *tessword; //converted word
static TEXTROW tessrow; //dummy row
//make dummy row
make_tess_row(denorm, &tessrow);
//make a word
tessword = make_tess_word (word, &tessrow);
AdaptToWord(tessword, &tessrow, choice, raw_choice, rejmap);
//adapt to it
delete_word(tessword); //free it
BOOL8 Tesseract::tess_acceptable_word(
WERD_CHOICE *word_choice, // after context
WERD_CHOICE *raw_choice) { // before context
return getDict().AcceptableResult(*word_choice);
}

View File

@ -24,13 +24,6 @@
#include "notdll.h"
#include "tesseractclass.h"
void tess_training_tester(
const STRING& filename,
PBLOB *blob,
DENORM *denorm,
BOOL8 correct,
char *text,
inT32 count,
BLOB_CHOICE_LIST *ratings
);
// TODO(ocr-team): Delete this along with other empty header files.
#endif

View File

@ -34,47 +34,28 @@
#include "reject.h"
#include "pageres.h"
//#include "gpapdest.h"
#include "mainblk.h"
#include "nwmain.h"
#include "pgedit.h"
#include "ocrshell.h"
#include "tprintf.h"
//#include "ipeerr.h"
//#include "restart.h"
#include "tessedit.h"
//#include "fontfind.h"
#include "permute.h"
#include "permdawg.h"
#include "stopper.h"
#include "adaptmatch.h"
#include "intmatcher.h"
#include "chop.h"
#include "efio.h"
#include "danerror.h"
#include "globals.h"
#include "tesseractclass.h"
#include "varable.h"
/*
** Include automatically generated configuration file if running autoconf
*/
#ifdef HAVE_CONFIG_H
#include "config_auto.h"
#endif
// Includes libtiff if HAVE_LIBTIFF is defined
#ifdef HAVE_LIBTIFF
#include "tiffio.h"
#endif
#include "params.h"
#include "notdll.h" //phils nn stuff
#define VARDIR "configs/" /*variables files */
//config under api
#define API_CONFIG "configs/api_config"
#define EXTERN
EXTERN BOOL_EVAR (tessedit_write_vars, FALSE, "Write all vars to file");
ETEXT_DESC *global_monitor = NULL; // progress monitor
@ -83,7 +64,7 @@ namespace tesseract {
// Read a "config" file containing a set of variable, value pairs.
// Searches the standard places: tessdata/configs, tessdata/tessconfigs
// and also accepts a relative or absolute path name.
void Tesseract::read_config_file(const char *filename, bool global_only) {
void Tesseract::read_config_file(const char *filename, bool init_only) {
STRING path = datadir;
path += "configs/";
path += filename;
@ -100,33 +81,25 @@ void Tesseract::read_config_file(const char *filename, bool global_only) {
path = filename;
}
}
read_variables_file(path.string(), global_only);
ParamUtils::ReadParamsFile(path.string(), init_only, this->params());
}
// Returns false if a unicharset file for the specified language was not found
// or was invalid.
// This function initializes TessdataManager. After TessdataManager is
// no longer needed, TessdataManager::End() should be called.
//
// This function sets tessedit_oem_mode to the given OcrEngineMode oem, unless
// it is OEM_DEFAULT, in which case the value of the variable will be obtained
// from the language-specific config file (stored in [lang].traineddata), from
// the config files specified on the command line or left as the default
// OEM_TESSERACT_ONLY if none of the configs specify this variable.
bool Tesseract::init_tesseract_lang_data(
const char *arg0, const char *textbase, const char *language,
char **configs, int configs_size, bool configs_global_only) {
FILE *var_file;
static char c_path[MAX_PATH]; //path for c code
OcrEngineMode oem, char **configs, int configs_size,
bool configs_init_only) {
// Set the basename, compute the data directory.
main_setup(arg0, textbase);
debug_window_on.set_value (FALSE);
if (tessedit_write_vars) {
var_file = fopen ("edited.cfg", "w");
if (var_file != NULL) {
print_variables(var_file);
fclose(var_file);
}
}
strcpy (c_path, datadir.string());
c_path[strlen (c_path) - strlen (m_data_sub_dir.string ())] = '\0';
demodir = c_path;
// Set the language data path prefix
lang = language != NULL ? language : "eng";
@ -134,25 +107,51 @@ bool Tesseract::init_tesseract_lang_data(
language_data_path_prefix += lang;
language_data_path_prefix += ".";
// Load tesseract variables from config files.
for (int i = 0; i < configs_size; ++i) {
read_config_file(configs[i], configs_global_only);
}
// Initialize TessdataManager.
STRING tessdata_path = language_data_path_prefix + kTrainedDataSuffix;
tessdata_manager.Init(tessdata_path.string());
tessdata_manager.Init(tessdata_path.string(),
tessdata_manager_debug_level);
// If a language specific config file (lang.config) exists, load it in.
if (tessdata_manager.SeekToStart(TESSDATA_LANG_CONFIG)) {
read_variables_from_fp(tessdata_manager.GetDataFilePtr(),
tessdata_manager.GetEndOffset(TESSDATA_LANG_CONFIG),
false);
if (global_tessdata_manager_debug_level) {
ParamUtils::ReadParamsFromFp(
tessdata_manager.GetDataFilePtr(),
tessdata_manager.GetEndOffset(TESSDATA_LANG_CONFIG),
false, this->params());
if (tessdata_manager_debug_level) {
tprintf("Loaded language config file\n");
}
}
// Load tesseract variables from config files. This is done after loading
// language-specific variables from [lang].traineddata file, so that custom
// config files can override values in [lang].traineddata file.
for (int i = 0; i < configs_size; ++i) {
read_config_file(configs[i], configs_init_only);
}
if (((STRING &)tessedit_write_params_to_file).length() > 0) {
FILE *params_file = fopen(tessedit_write_params_to_file.string(), "w");
if (params_file != NULL) {
ParamUtils::PrintParams(params_file, this->params());
fclose(params_file);
if (tessdata_manager_debug_level > 0) {
tprintf("Wrote parameters to %s\n",
tessedit_write_params_to_file.string());
}
} else {
tprintf("Failed to open %s for writing params.\n",
tessedit_write_params_to_file.string());
}
}
// Determine which ocr engine(s) should be loaded and used for recognition.
if (oem != OEM_DEFAULT) tessedit_ocr_engine_mode.set_value(oem);
if (tessdata_manager_debug_level) {
tprintf("Loading Tesseract/Cube with tessedit_ocr_engine_mode %d\n",
static_cast<int>(tessedit_ocr_engine_mode));
}
// Load the unicharset
if (!tessdata_manager.SeekToStart(TESSDATA_UNICHARSET) ||
!unicharset.load_from_file(tessdata_manager.GetDataFilePtr())) {
@ -162,51 +161,63 @@ bool Tesseract::init_tesseract_lang_data(
tprintf("Error: Size of unicharset is greater than MAX_NUM_CLASSES\n");
return false;
}
if (global_tessdata_manager_debug_level) tprintf("Loaded unicharset\n");
right_to_left_ = unicharset.any_right_to_left();
if (tessdata_manager_debug_level) tprintf("Loaded unicharset\n");
if (!global_tessedit_ambigs_training &&
if (!tessedit_ambigs_training &&
tessdata_manager.SeekToStart(TESSDATA_AMBIGS)) {
unichar_ambigs.LoadUnicharAmbigs(
tessdata_manager.GetDataFilePtr(),
tessdata_manager.GetEndOffset(TESSDATA_AMBIGS),
&unicharset);
if (global_tessdata_manager_debug_level) tprintf("Loaded ambigs\n");
ambigs_debug_level, use_ambigs_for_adaption, &unicharset);
if (tessdata_manager_debug_level) tprintf("Loaded ambigs\n");
}
// Load Cube objects if necessary.
if (tessedit_ocr_engine_mode == OEM_CUBE_ONLY) {
ASSERT_HOST(init_cube_objects(false, &tessdata_manager));
if (tessdata_manager_debug_level)
tprintf("Loaded Cube w/out combiner\n");
} else if (tessedit_ocr_engine_mode == OEM_TESSERACT_CUBE_COMBINED) {
ASSERT_HOST(init_cube_objects(true, &tessdata_manager));
if (tessdata_manager_debug_level)
tprintf("Loaded Cube with combiner\n");
}
return true;
}
int Tesseract::init_tesseract(
const char *arg0, const char *textbase, const char *language,
char **configs, int configs_size, bool configs_global_only) {
if (!init_tesseract_lang_data(arg0, textbase, language, configs,
configs_size, configs_global_only)) {
OcrEngineMode oem, char **configs, int configs_size,
bool configs_init_only) {
if (!init_tesseract_lang_data(arg0, textbase, language, oem, configs,
configs_size, configs_init_only)) {
return -1;
}
start_recog(textbase);
// If only Cube will be used, skip loading Tesseract classifier's
// pre-trained templates.
bool init_tesseract_classifier =
(tessedit_ocr_engine_mode == OEM_TESSERACT_ONLY ||
tessedit_ocr_engine_mode == OEM_TESSERACT_CUBE_COMBINED);
// If only Cube will be used and if it has its own Unicharset,
// skip initializing permuter and loading Tesseract Dawgs.
bool init_dict =
!(tessedit_ocr_engine_mode == OEM_CUBE_ONLY &&
tessdata_manager.SeekToStart(TESSDATA_CUBE_UNICHARSET));
program_editup(textbase, init_tesseract_classifier, init_dict);
tessdata_manager.End();
return 0; //Normal exit
}
// Init everything except the language model
int Tesseract::init_tesseract_classifier(
const char *arg0, const char *textbase, const char *language,
char **configs, int configs_size, bool configs_global_only) {
if (!init_tesseract_lang_data (arg0, textbase, language, configs,
configs_size, configs_global_only)) {
return -1;
}
// Dont initialize the permuter.
program_editup(textbase, false);
tessdata_manager.End();
return 0;
}
// init the LM component
int Tesseract::init_tesseract_lm(const char *arg0,
const char *textbase,
const char *language) {
init_tesseract_lang_data(arg0, textbase, language, NULL, 0, false);
getDict().init_permute();
if (!init_tesseract_lang_data(arg0, textbase, language,
OEM_TESSERACT_ONLY, NULL, 0, false))
return -1;
getDict().Load();
tessdata_manager.End();
return 0;
}
@ -226,42 +237,3 @@ enum CMD_EVENTS
};
} // namespace tesseract
#ifdef _TIFFIO_
void read_tiff_image(TIFF* tif, IMAGE* image) {
tdata_t buf;
uint32 image_width, image_height;
uint16 photometric;
inT16 bpp;
inT16 samples_per_pixel = 0;
TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &image_width);
TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &image_height);
if (!TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bpp))
bpp = 1; // Binary is default if no value provided.
TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel);
TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric);
if (samples_per_pixel > 1)
bpp *= samples_per_pixel;
// Tesseract's internal representation is 0-is-black,
// so if the photometric is 1 (min is black) then high-valued pixels
// are 1 (white), otherwise they are 0 (black).
uinT8 high_value = photometric == 1;
image->create(image_width, image_height, bpp);
IMAGELINE line;
line.init(image_width);
buf = _TIFFmalloc(TIFFScanlineSize(tif));
int bytes_per_line = (image_width*bpp + 7)/8;
uinT8* dest_buf = image->get_buffer();
// This will go badly wrong with one of the more exotic tiff formats,
// but the majority will work OK.
for (int y = 0; y < image_height; ++y) {
TIFFReadScanline(tif, buf, y);
memcpy(dest_buf, buf, bytes_per_line);
dest_buf += bytes_per_line;
}
if (high_value == 0)
invert_image(image);
_TIFFfree(buf);
}
#endif

View File

@ -20,9 +20,8 @@
#ifndef TESSEDIT_H
#define TESSEDIT_H
#include "tessclas.h"
#include "ocrclass.h"
#include "pgedit.h"
#include "blobs.h"
#include "pgedit.h"
#include "notdll.h"
//progress monitor

View File

@ -0,0 +1,308 @@
/**********************************************************************
* File: tesseract_cube_combiner.h
* Description: Declaration of the Tesseract & Cube results combiner Class
* Author: Ahmad Abdulkader
* Created: 2008
*
* (C) Copyright 2008, Google Inc.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
// The TesseractCubeCombiner class provides the functionality of combining
// the recognition results of Tesseract and Cube at the word level
#include <algorithm>
#include <string>
#include <vector>
#include <wctype.h>
#include "tesseract_cube_combiner.h"
#include "cube_object.h"
#include "cube_reco_context.h"
#include "cube_utils.h"
#include "neural_net.h"
#include "tesseractclass.h"
#include "word_altlist.h"
namespace tesseract {
TesseractCubeCombiner::TesseractCubeCombiner(CubeRecoContext *cube_cntxt) {
cube_cntxt_ = cube_cntxt;
combiner_net_ = NULL;
}
TesseractCubeCombiner::~TesseractCubeCombiner() {
if (combiner_net_ != NULL) {
delete combiner_net_;
combiner_net_ = NULL;
}
}
bool TesseractCubeCombiner::LoadCombinerNet() {
ASSERT_HOST(cube_cntxt_);
// Compute the path of the combiner net
string data_path;
cube_cntxt_->GetDataFilePath(&data_path);
string net_file_name = data_path + cube_cntxt_->Lang() +
".tesseract_cube.nn";
// Return false if file does not exist
FILE *fp = fopen(net_file_name.c_str(), "r");
if (fp == NULL)
return false;
else
fclose(fp);
// Load and validate net
combiner_net_ = NeuralNet::FromFile(net_file_name);
if (combiner_net_ == NULL) {
tprintf("Could not read combiner net file %s", net_file_name.c_str());
return false;
} else if (combiner_net_->out_cnt() != 2) {
tprintf("Invalid combiner net file %s! Output count != 2\n",
net_file_name.c_str());
delete combiner_net_;
combiner_net_ = NULL;
return false;
}
return true;
}
// Normalize a UTF-8 string. Converts the UTF-8 string to UTF32 and optionally
// strips punc and/or normalizes case and then converts back
string TesseractCubeCombiner::NormalizeString(const string &str,
bool remove_punc,
bool norm_case) {
// convert to UTF32
string_32 str32;
CubeUtils::UTF8ToUTF32(str.c_str(), &str32);
// strip punc and normalize
string_32 new_str32;
for (int idx = 0; idx < str32.length(); idx++) {
// if no punc removal is required or not a punctuation character
if (!remove_punc || iswpunct(str32[idx]) == 0) {
char_32 norm_char = str32[idx];
// normalize case if required
if (norm_case && iswalpha(norm_char)) {
norm_char = towlower(norm_char);
}
new_str32.push_back(norm_char);
}
}
// convert back to UTF8
string new_str;
CubeUtils::UTF32ToUTF8(new_str32.c_str(), &new_str);
return new_str;
}
// Compares 2 strings optionally ignoring punctuation
int TesseractCubeCombiner::CompareStrings(const string &str1,
const string &str2,
bool ignore_punc,
bool ignore_case) {
if (!ignore_punc && !ignore_case) {
return str1.compare(str2);
}
string norm_str1 = NormalizeString(str1, ignore_punc, ignore_case);
string norm_str2 = NormalizeString(str2, ignore_punc, ignore_case);
return norm_str1.compare(norm_str2);
}
// Check if a string is a valid Tess dict word or not
bool TesseractCubeCombiner::ValidWord(const string &str) {
return (cube_cntxt_->TesseractObject()->getDict().valid_word(str.c_str())
> 0);
}
// Public method for computing the combiner features. The agreement
// output parameter will be true if both answers are identical,
// and false otherwise.
bool TesseractCubeCombiner::ComputeCombinerFeatures(const string &tess_str,
int tess_confidence,
CubeObject *cube_obj,
WordAltList *cube_alt_list,
vector<double> *features,
bool *agreement) {
features->clear();
*agreement = false;
if (cube_alt_list == NULL || cube_alt_list->AltCount() <= 0)
return false;
// Get Cube's best string; return false if empty
char_32 *cube_best_str32 = cube_alt_list->Alt(0);
if (cube_best_str32 == NULL || CubeUtils::StrLen(cube_best_str32) < 1)
return false;
string cube_best_str;
int cube_best_cost = cube_alt_list->AltCost(0);
int cube_best_bigram_cost = 0;
bool cube_best_bigram_cost_valid = true;
if (cube_cntxt_->Bigrams())
cube_best_bigram_cost = cube_cntxt_->Bigrams()->
Cost(cube_best_str32, cube_cntxt_->CharacterSet(),
&cube_cntxt_->TesseractObject()->unicharset);
else
cube_best_bigram_cost_valid = false;
CubeUtils::UTF32ToUTF8(cube_best_str32, &cube_best_str);
// Get Tesseract's UTF32 string
string_32 tess_str32;
CubeUtils::UTF8ToUTF32(tess_str.c_str(), &tess_str32);
// Compute agreement flag
*agreement = (tess_str.compare(cube_best_str) == 0);
// Get Cube's second best string; if empty, return false
char_32 *cube_next_best_str32;
string cube_next_best_str;
int cube_next_best_cost = WORST_COST;
if (cube_alt_list->AltCount() > 1) {
cube_next_best_str32 = cube_alt_list->Alt(1);
if (cube_next_best_str32 == NULL ||
CubeUtils::StrLen(cube_next_best_str32) == 0) {
return false;
}
cube_next_best_cost = cube_alt_list->AltCost(1);
CubeUtils::UTF32ToUTF8(cube_next_best_str32, &cube_next_best_str);
}
// Rank of Tesseract's top result in Cube's alternate list
int tess_rank = 0;
for (tess_rank = 0; tess_rank < cube_alt_list->AltCount(); tess_rank++) {
string alt_str;
CubeUtils::UTF32ToUTF8(cube_alt_list->Alt(tess_rank), &alt_str);
if (alt_str == tess_str)
break;
}
// Cube's cost for tesseract's result. Note that this modifies the
// state of cube_obj, including its alternate list by calling RecognizeWord()
int tess_cost = cube_obj->WordCost(tess_str.c_str());
// Cube's bigram cost of Tesseract's string
int tess_bigram_cost = 0;
int tess_bigram_cost_valid = true;
if (cube_cntxt_->Bigrams())
tess_bigram_cost = cube_cntxt_->Bigrams()->
Cost(tess_str32.c_str(), cube_cntxt_->CharacterSet(),
&cube_cntxt_->TesseractObject()->unicharset);
else
tess_bigram_cost_valid = false;
// Tesseract confidence
features->push_back(tess_confidence);
// Cube cost of Tesseract string
features->push_back(tess_cost);
// Cube Rank of Tesseract string
features->push_back(tess_rank);
// length of Tesseract OCR string
features->push_back(tess_str.length());
// Tesseract OCR string in dictionary
features->push_back(ValidWord(tess_str));
if (tess_bigram_cost_valid) {
// bigram cost of Tesseract string
features->push_back(tess_bigram_cost);
}
// Cube tess_cost of Cube best string
features->push_back(cube_best_cost);
// Cube tess_cost of Cube next best string
features->push_back(cube_next_best_cost);
// length of Cube string
features->push_back(cube_best_str.length());
// Cube string in dictionary
features->push_back(ValidWord(cube_best_str));
if (cube_best_bigram_cost_valid) {
// bigram cost of Cube string
features->push_back(cube_best_bigram_cost);
}
// case-insensitive string comparison, including punctuation
int compare_nocase_punc = CompareStrings(cube_best_str.c_str(),
tess_str.c_str(), false, true);
features->push_back(compare_nocase_punc == 0);
// case-sensitive string comparison, ignoring punctuation
int compare_case_nopunc = CompareStrings(cube_best_str.c_str(),
tess_str.c_str(), true, false);
features->push_back(compare_case_nopunc == 0);
// case-insensitive string comparison, ignoring punctuation
int compare_nocase_nopunc = CompareStrings(cube_best_str.c_str(),
tess_str.c_str(), true, true);
features->push_back(compare_nocase_nopunc == 0);
return true;
}
// The CubeObject parameter is used for 2 purposes: 1) to retrieve
// cube's alt list, and 2) to compute cube's word cost for the
// tesseract result. The call to CubeObject::WordCost() modifies
// the object's alternate list, so previous state will be lost.
float TesseractCubeCombiner::CombineResults(WERD_RES *tess_res,
CubeObject *cube_obj) {
// If no combiner is loaded or the cube object is undefined,
// tesseract wins with probability 1.0
if (combiner_net_ == NULL || cube_obj == NULL) {
tprintf("Cube WARNING (TesseractCubeCombiner::CombineResults): "
"Cube objects not initialized; defaulting to Tesseract\n");
return 1.0;
}
// Retrieve the alternate list from the CubeObject's current state.
// If the alt list empty, tesseract wins with probability 1.0
WordAltList *cube_alt_list = cube_obj->AlternateList();
if (cube_alt_list == NULL)
cube_alt_list = cube_obj->RecognizeWord();
if (cube_alt_list == NULL || cube_alt_list->AltCount() <= 0) {
tprintf("Cube WARNING (TesseractCubeCombiner::CombineResults): "
"Cube returned no results; defaulting to Tesseract\n");
return 1.0;
}
return CombineResults(tess_res, cube_obj, cube_alt_list);
}
// The alt_list parameter is expected to have been extracted from the
// CubeObject that recognized the word to be combined. The cube_obj
// parameter passed may be either same instance or a separate instance to
// be used only by the combiner. In both cases, its alternate
// list will be modified by an internal call to RecognizeWord().
float TesseractCubeCombiner::CombineResults(WERD_RES *tess_res,
CubeObject *cube_obj,
WordAltList *cube_alt_list) {
// If no combiner is loaded or the cube object is undefined, or the
// alt list is empty, tesseract wins with probability 1.0
if (combiner_net_ == NULL || cube_obj == NULL ||
cube_alt_list == NULL || cube_alt_list->AltCount() <= 0) {
tprintf("Cube WARNING (TesseractCubeCombiner::CombineResults): "
"Cube result cannot be retrieved; defaulting to Tesseract\n");
return 1.0;
}
// Tesseract result string, tesseract confidence, and cost of
// tesseract result according to cube
string tess_str = tess_res->best_choice->unichar_string().string();
// Map certainty [-20.0, 0.0] to confidence [0, 100]
int tess_confidence = MIN(100, MAX(1, static_cast<int>(
100 + (5 * tess_res->best_choice->certainty()))));
// Compute the combiner features. If feature computation fails or
// answers are identical, tesseract wins with probability 1.0
vector<double> features;
bool agreement;
bool combiner_success = ComputeCombinerFeatures(tess_str, tess_confidence,
cube_obj, cube_alt_list,
&features, &agreement);
if (!combiner_success || agreement)
return 1.0;
// Classify combiner feature vector and return output (probability
// of tesseract class).
double net_out[2];
if (!combiner_net_->FeedForward(&features[0], net_out))
return 1.0;
return net_out[1];
}
}

View File

@ -0,0 +1,103 @@
/**********************************************************************
* File: tesseract_cube_combiner.h
* Description: Declaration of the Tesseract & Cube results combiner Class
* Author: Ahmad Abdulkader
* Created: 2008
*
* (C) Copyright 2008, Google Inc.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
// The TesseractCubeCombiner class provides the functionality of combining
// the recognition results of Tesseract and Cube at the word level
#ifndef TESSERACT_CCMAIN_TESSERACT_CUBE_COMBINER_H
#define TESSERACT_CCMAIN_TESSERACT_CUBE_COMBINER_H
#include <string>
#include <vector>
#include "pageres.h"
#ifdef __MSW32__
#include <windows.h>
using namespace std;
#endif
#ifdef USE_STD_NAMESPACE
using std::string;
using std::vector;
#endif
namespace tesseract {
class CubeObject;
class NeuralNet;
class CubeRecoContext;
class WordAltList;
class TesseractCubeCombiner {
public:
explicit TesseractCubeCombiner(CubeRecoContext *cube_cntxt);
virtual ~TesseractCubeCombiner();
// There are 2 public methods for combining the results of tesseract
// and cube. Both return the probability that the Tesseract result is
// correct. The difference between the two interfaces is in how the
// passed-in CubeObject is used.
// The CubeObject parameter is used for 2 purposes: 1) to retrieve
// cube's alt list, and 2) to compute cube's word cost for the
// tesseract result. Both uses may modify the state of the
// CubeObject (including the BeamSearch state) with a call to
// RecognizeWord().
float CombineResults(WERD_RES *tess_res, CubeObject *cube_obj);
// The alt_list parameter is expected to have been extracted from the
// CubeObject that recognized the word to be combined. The cube_obj
// parameter passed in is a separate instance to be used only by
// the combiner.
float CombineResults(WERD_RES *tess_res, CubeObject *cube_obj,
WordAltList *alt_list);
// Public method for computing the combiner features. The agreement
// output parameter will be true if both answers are identical,
// false otherwise. Modifies the cube_alt_list, so no assumptions
// should be made about its state upon return.
bool ComputeCombinerFeatures(const string &tess_res,
int tess_confidence,
CubeObject *cube_obj,
WordAltList *cube_alt_list,
vector<double> *features,
bool *agreement);
// Is the word valid according to Tesseract's language model
bool ValidWord(const string &str);
// Loads the combiner neural network from file, using cube_cntxt_
// to find path.
bool LoadCombinerNet();
private:
// Normalize a UTF-8 string. Converts the UTF-8 string to UTF32 and optionally
// strips punc and/or normalizes case and then converts back
string NormalizeString(const string &str, bool remove_punc, bool norm_case);
// Compares 2 strings after optionally normalizing them and or stripping
// punctuation
int CompareStrings(const string &str1, const string &str2, bool ignore_punc,
bool norm_case);
NeuralNet *combiner_net_; // pointer to the combiner NeuralNet object
CubeRecoContext *cube_cntxt_; // used for language ID and data paths
};
}
#endif // TESSERACT_CCMAIN_TESSERACT_CUBE_COMBINER_H

View File

@ -19,6 +19,8 @@
///////////////////////////////////////////////////////////////////////
#include "tesseractclass.h"
#include "cube_reco_context.h"
#include "tesseract_cube_combiner.h"
#include "globals.h"
// Include automatically generated configuration file if running autoconf.
@ -35,44 +37,373 @@ namespace tesseract {
Tesseract::Tesseract()
: BOOL_MEMBER(tessedit_resegment_from_boxes, false,
"Take segmentation and labeling from box file"),
"Take segmentation and labeling from box file",
this->params()),
BOOL_MEMBER(tessedit_resegment_from_line_boxes, false,
"Conversion of word/line box file to char box file",
this->params()),
BOOL_MEMBER(tessedit_train_from_boxes, false,
"Generate training data from boxed chars"),
"Generate training data from boxed chars", this->params()),
BOOL_MEMBER(tessedit_make_boxes_from_boxes, false,
"Generate more boxes from boxed chars", this->params()),
BOOL_MEMBER(tessedit_dump_pageseg_images, false,
"Dump itermediate images made during page segmentation"),
"Dump intermediate images made during page segmentation",
this->params()),
// The default for pageseg_mode is the old behaviour, so as not to
// upset anything that relies on that.
INT_MEMBER(tessedit_pageseg_mode, 2,
"Page seg mode: 0=auto, 1=col, 2=block, 3=line, 4=word, 6=char"
" (Values from PageSegMode enum in baseapi.h)"),
INT_MEMBER(tessedit_accuracyvspeed, 0,
"Accuracy V Speed tradeoff: 0 fastest, 100 most accurate"
" (Values from AccuracyVSpeed enum in baseapi.h)"),
BOOL_MEMBER(tessedit_train_from_boxes_word_level, false,
"Generate training data from boxed chars at word level."),
INT_MEMBER(tessedit_pageseg_mode, PSM_SINGLE_BLOCK,
"Page seg mode: 0=osd only, 1=auto+osd, 2=auto, 3=col, 4=block,"
" 5=line, 6=word, 7=char"
" (Values from PageSegMode enum in publictypes.h)",
this->params()),
INT_INIT_MEMBER(tessedit_ocr_engine_mode, tesseract::OEM_TESSERACT_ONLY,
"Which OCR engine(s) to run (Tesseract, Cube, both)."
" Defaults to loading and running only Tesseract"
" (no Cube,no combiner)."
" Values from OcrEngineMode enum in tesseractclass.h)",
this->params()),
STRING_MEMBER(tessedit_char_blacklist, "",
"Blacklist of chars not to recognize"),
"Blacklist of chars not to recognize", this->params()),
STRING_MEMBER(tessedit_char_whitelist, "",
"Whitelist of chars to recognize"),
BOOL_MEMBER(global_tessedit_ambigs_training, false,
"Perform training for ambiguities"),
"Whitelist of chars to recognize", this->params()),
BOOL_INIT_MEMBER(tessedit_ambigs_training, false,
"Perform training for ambiguities", this->params()),
STRING_MEMBER(tessedit_write_params_to_file, "",
"Write all parameters to the given file.", this->params()),
BOOL_MEMBER(tessedit_adapt_to_char_fragments, true,
"Adapt to words that contain "
" a character composed form fragments", this->params()),
BOOL_MEMBER(tessedit_adaption_debug, false, "Generate and print debug"
" information for adaption", this->params()),
BOOL_MEMBER(applybox_rebalance, TRUE, "Drop dead", this->params()),
INT_MEMBER(applybox_debug, 1, "Debug level", this->params()),
INT_MEMBER(applybox_page, 0,
"Page number to apply boxes from", this->params()),
STRING_MEMBER(applybox_test_exclusions, "",
"Chars ignored for testing", this->params()),
double_MEMBER(applybox_error_band, 0.15,
"Err band as fract of xht", this->params()),
STRING_MEMBER(applybox_exposure_pattern, ".exp", "Exposure value follows"
" this pattern in the image filename. The name of the image"
" files are expected to be in the form"
" [lang].[fontname].exp[num].tif", this->params()),
BOOL_MEMBER(applybox_learn_chars_and_char_frags_mode, false,
"Learn both character fragments (as is done in the"
" special low exposure mode) as well as unfragmented"
" characters.", this->params()),
BOOL_MEMBER(applybox_learn_ngrams_mode, false, "Each bounding box"
" is assumed to contain ngrams. Only learn the ngrams"
" whose outlines overlap horizontally.", this->params()),
BOOL_MEMBER(tessedit_print_text, false,
"Write text to stdout", this->params()),
BOOL_MEMBER(tessedit_draw_words, false,
"Draw source words", this->params()),
BOOL_MEMBER(tessedit_draw_outwords, false,
"Draw output words", this->params()),
BOOL_MEMBER(tessedit_training_tess, false,
"Call Tess to learn blobs", this->params()),
BOOL_MEMBER(tessedit_dump_choices, false,
"Dump char choices", this->params()),
BOOL_MEMBER(tessedit_fix_fuzzy_spaces, true,
"Try to improve fuzzy spaces", this->params()),
BOOL_MEMBER(tessedit_unrej_any_wd, false,
"Dont bother with word plausibility", this->params()),
BOOL_MEMBER(tessedit_fix_hyphens, true,
"Crunch double hyphens?", this->params()),
BOOL_MEMBER(tessedit_redo_xheight, true,
"Check/Correct x-height", this->params()),
BOOL_MEMBER(tessedit_enable_doc_dict, true,
"Add words to the document dictionary", this->params()),
BOOL_MEMBER(tessedit_debug_fonts, false,
"Output font info per char", this->params()),
BOOL_MEMBER(tessedit_debug_block_rejection, false,
"Block and Row stats", this->params()),
INT_MEMBER(debug_x_ht_level, 0, "Reestimate debug", this->params()),
BOOL_MEMBER(debug_acceptable_wds, false,
"Dump word pass/fail chk", this->params()),
STRING_MEMBER(chs_leading_punct, "('`\"",
"Leading punctuation", this->params()),
STRING_MEMBER(chs_trailing_punct1, ").,;:?!",
"1st Trailing punctuation", this->params()),
STRING_MEMBER(chs_trailing_punct2, ")'`\"",
"2nd Trailing punctuation", this->params()),
double_MEMBER(quality_rej_pc, 0.08,
"good_quality_doc lte rejection limit", this->params()),
double_MEMBER(quality_blob_pc, 0.0,
"good_quality_doc gte good blobs limit", this->params()),
double_MEMBER(quality_outline_pc, 1.0,
"good_quality_doc lte outline error limit", this->params()),
double_MEMBER(quality_char_pc, 0.95,
"good_quality_doc gte good char limit", this->params()),
INT_MEMBER(quality_min_initial_alphas_reqd, 2,
"alphas in a good word", this->params()),
BOOL_MEMBER(tessedit_tess_adapt_to_rejmap, false,
"Use reject map to control Tesseract adaption", this->params()),
INT_MEMBER(tessedit_tess_adaption_mode, 0x27,
"Adaptation decision algorithm for tess", this->params()),
BOOL_MEMBER(tessedit_minimal_rej_pass1, false,
"Do minimal rejection on pass 1 output", this->params()),
BOOL_MEMBER(tessedit_test_adaption, false,
"Test adaption criteria", this->params()),
BOOL_MEMBER(tessedit_matcher_log, false,
"Log matcher activity", this->params()),
INT_MEMBER(tessedit_test_adaption_mode, 3,
"Adaptation decision algorithm for tess", this->params()),
BOOL_MEMBER(save_best_choices, false,
"Save the results of the recognition step (blob_choices)"
" within the corresponding WERD_CHOICE", this->params()),
BOOL_MEMBER(test_pt, false, "Test for point", this->params()),
double_MEMBER(test_pt_x, 99999.99, "xcoord", this->params()),
double_MEMBER(test_pt_y, 99999.99, "ycoord", this->params()),
INT_MEMBER(cube_debug_level, 1, "Print cube debug info.", this->params()),
STRING_MEMBER(outlines_odd, "%| ", "Non standard number of outlines",
this->params()),
STRING_MEMBER(outlines_2, "ij!?%\":;",
"Non standard number of outlines", this->params()),
BOOL_MEMBER(docqual_excuse_outline_errs, false,
"Allow outline errs in unrejection?", this->params()),
BOOL_MEMBER(tessedit_good_quality_unrej, true,
"Reduce rejection on good docs", this->params()),
BOOL_MEMBER(tessedit_use_reject_spaces, true,
"Reject spaces?", this->params()),
double_MEMBER(tessedit_reject_doc_percent, 65.00,
"%rej allowed before rej whole doc", this->params()),
double_MEMBER(tessedit_reject_block_percent, 45.00,
"%rej allowed before rej whole block", this->params()),
double_MEMBER(tessedit_reject_row_percent, 40.00,
"%rej allowed before rej whole row", this->params()),
double_MEMBER(tessedit_whole_wd_rej_row_percent, 70.00,
"Number of row rejects in whole word rejects"
"which prevents whole row rejection", this->params()),
BOOL_MEMBER(tessedit_preserve_blk_rej_perfect_wds, true,
"Only rej partially rejected words in block rejection",
this->params()),
BOOL_MEMBER(tessedit_preserve_row_rej_perfect_wds, true,
"Only rej partially rejected words in row rejection",
this->params()),
BOOL_MEMBER(tessedit_dont_blkrej_good_wds, false,
"Use word segmentation quality metric", this->params()),
BOOL_MEMBER(tessedit_dont_rowrej_good_wds, false,
"Use word segmentation quality metric", this->params()),
INT_MEMBER(tessedit_preserve_min_wd_len, 2,
"Only preserve wds longer than this", this->params()),
BOOL_MEMBER(tessedit_row_rej_good_docs, true,
"Apply row rejection to good docs", this->params()),
double_MEMBER(tessedit_good_doc_still_rowrej_wd, 1.1,
"rej good doc wd if more than this fraction rejected",
this->params()),
BOOL_MEMBER(tessedit_reject_bad_qual_wds, true,
"Reject all bad quality wds", this->params()),
BOOL_MEMBER(tessedit_debug_doc_rejection, false,
"Page stats", this->params()),
BOOL_MEMBER(tessedit_debug_quality_metrics, false,
"Output data to debug file", this->params()),
BOOL_MEMBER(bland_unrej, false,
"unrej potential with no chekcs", this->params()),
double_MEMBER(quality_rowrej_pc, 1.1,
"good_quality_doc gte good char limit", this->params()),
BOOL_MEMBER(unlv_tilde_crunching, true,
"Mark v.bad words for tilde crunch", this->params()),
BOOL_MEMBER(crunch_early_merge_tess_fails, true,
"Before word crunch?", this->params()),
BOOL_MEMBER(crunch_early_convert_bad_unlv_chs, false,
"Take out ~^ early?", this->params()),
double_MEMBER(crunch_terrible_rating, 80.0,
"crunch rating lt this", this->params()),
BOOL_MEMBER(crunch_terrible_garbage, true, "As it says", this->params()),
double_MEMBER(crunch_poor_garbage_cert, -9.0,
"crunch garbage cert lt this", this->params()),
double_MEMBER(crunch_poor_garbage_rate, 60,
"crunch garbage rating lt this", this->params()),
double_MEMBER(crunch_pot_poor_rate, 40,
"POTENTIAL crunch rating lt this", this->params()),
double_MEMBER(crunch_pot_poor_cert, -8.0,
"POTENTIAL crunch cert lt this", this->params()),
BOOL_MEMBER(crunch_pot_garbage, true,
"POTENTIAL crunch garbage", this->params()),
double_MEMBER(crunch_del_rating, 60,
"POTENTIAL crunch rating lt this", this->params()),
double_MEMBER(crunch_del_cert, -10.0,
"POTENTIAL crunch cert lt this", this->params()),
double_MEMBER(crunch_del_min_ht, 0.7,
"Del if word ht lt xht x this", this->params()),
double_MEMBER(crunch_del_max_ht, 3.0,
"Del if word ht gt xht x this", this->params()),
double_MEMBER(crunch_del_min_width, 3.0,
"Del if word width lt xht x this", this->params()),
double_MEMBER(crunch_del_high_word, 1.5,
"Del if word gt xht x this above bl", this->params()),
double_MEMBER(crunch_del_low_word, 0.5,
"Del if word gt xht x this below bl", this->params()),
double_MEMBER(crunch_small_outlines_size, 0.6,
"Small if lt xht x this", this->params()),
INT_MEMBER(crunch_rating_max, 10,
"For adj length in rating per ch", this->params()),
INT_MEMBER(crunch_pot_indicators, 1,
"How many potential indicators needed", this->params()),
BOOL_MEMBER(crunch_leave_ok_strings, true,
"Dont touch sensible strings", this->params()),
BOOL_MEMBER(crunch_accept_ok, true,
"Use acceptability in okstring", this->params()),
BOOL_MEMBER(crunch_leave_accept_strings, false,
"Dont pot crunch sensible strings", this->params()),
BOOL_MEMBER(crunch_include_numerals, false,
"Fiddle alpha figures", this->params()),
INT_MEMBER(crunch_leave_lc_strings, 4,
"Dont crunch words with long lower case strings",
this->params()),
INT_MEMBER(crunch_leave_uc_strings, 4,
"Dont crunch words with long lower case strings",
this->params()),
INT_MEMBER(crunch_long_repetitions, 3,
"Crunch words with long repetitions", this->params()),
INT_MEMBER(crunch_debug, 0, "As it says", this->params()),
INT_MEMBER(fixsp_non_noise_limit, 1,
"How many non-noise blbs either side?", this->params()),
double_MEMBER(fixsp_small_outlines_size, 0.28,
"Small if lt xht x this", this->params()),
BOOL_MEMBER(tessedit_prefer_joined_punct, false,
"Reward punctation joins", this->params()),
INT_MEMBER(fixsp_done_mode, 1,
"What constitues done for spacing", this->params()),
INT_MEMBER(debug_fix_space_level, 0,
"Contextual fixspace debug", this->params()),
STRING_MEMBER(numeric_punctuation, ".,",
"Punct. chs expected WITHIN numbers", this->params()),
INT_MEMBER(x_ht_acceptance_tolerance, 8,
"Max allowed deviation of blob top outside of font data",
this->params()),
INT_MEMBER(x_ht_min_change, 8,
"Min change in xht before actually trying it", this->params()),
BOOL_MEMBER(tessedit_write_block_separators, false,
"Write block separators in output", this->params()),
BOOL_MEMBER(tessedit_write_raw_output, false,
"Write raw stuff to name.raw", this->params()),
BOOL_MEMBER(tessedit_write_output, false,
"Write text to name.txt", this->params()),
BOOL_MEMBER(tessedit_write_ratings, false,
"Return ratings in IPEOCRAPI data", this->params()),
BOOL_MEMBER(tessedit_write_rep_codes, false,
"Write repetition char code", this->params()),
BOOL_MEMBER(tessedit_write_unlv, false,
"Write .unlv output file", this->params()),
BOOL_MEMBER(tessedit_create_hocr, false,
"Write .html hOCR output file", this->params()),
STRING_MEMBER(unrecognised_char, "|",
"Output char for unidentified blobs", this->params()),
INT_MEMBER(suspect_level, 99, "Suspect marker level", this->params()),
INT_MEMBER(suspect_space_level, 100,
"Min suspect level for rejecting spaces", this->params()),
INT_MEMBER(suspect_short_words, 2,
"Dont Suspect dict wds longer than this", this->params()),
BOOL_MEMBER(suspect_constrain_1Il, false,
"UNLV keep 1Il chars rejected", this->params()),
double_MEMBER(suspect_rating_per_ch, 999.9,
"Dont touch bad rating limit", this->params()),
double_MEMBER(suspect_accept_rating, -999.9,
"Accept good rating limit", this->params()),
BOOL_MEMBER(tessedit_minimal_rejection, false,
"Only reject tess failures", this->params()),
BOOL_MEMBER(tessedit_zero_rejection, false,
"Dont reject ANYTHING", this->params()),
BOOL_MEMBER(tessedit_word_for_word, false,
"Make output have exactly one word per WERD", this->params()),
BOOL_MEMBER(tessedit_zero_kelvin_rejection, false,
"Dont reject ANYTHING AT ALL", this->params()),
BOOL_MEMBER(tessedit_consistent_reps, true,
"Force all rep chars the same", this->params()),
INT_MEMBER(tessedit_reject_mode, 0, "Rejection algorithm", this->params()),
INT_MEMBER(tessedit_ok_mode, 5,
"Acceptance decision algorithm", this->params()),
BOOL_MEMBER(tessedit_rejection_debug, false,
"Adaption debug", this->params()),
BOOL_MEMBER(tessedit_flip_0O, true,
"Contextual 0O O0 flips", this->params()),
double_MEMBER(tessedit_lower_flip_hyphen, 1.5,
"Aspect ratio dot/hyphen test", this->params()),
double_MEMBER(tessedit_upper_flip_hyphen, 1.8,
"Aspect ratio dot/hyphen test", this->params()),
BOOL_MEMBER(rej_trust_doc_dawg, false,
"Use DOC dawg in 11l conf. detector", this->params()),
BOOL_MEMBER(rej_1Il_use_dict_word, false,
"Use dictword test", this->params()),
BOOL_MEMBER(rej_1Il_trust_permuter_type, true,
"Dont double check", this->params()),
BOOL_MEMBER(rej_use_tess_accepted, true,
"Individual rejection control", this->params()),
BOOL_MEMBER(rej_use_tess_blanks, true,
"Individual rejection control", this->params()),
BOOL_MEMBER(rej_use_good_perm, true,
"Individual rejection control", this->params()),
BOOL_MEMBER(rej_use_sensible_wd, false,
"Extend permuter check", this->params()),
BOOL_MEMBER(rej_alphas_in_number_perm, false,
"Extend permuter check", this->params()),
double_MEMBER(rej_whole_of_mostly_reject_word_fract, 0.85,
"if >this fract", this->params()),
INT_MEMBER(tessedit_image_border, 2,
"Rej blbs near image edge limit", this->params()),
STRING_MEMBER(ok_repeated_ch_non_alphanum_wds, "-?*\075",
"Allow NN to unrej", this->params()),
STRING_MEMBER(conflict_set_I_l_1, "Il1[]",
"Il1 conflict set", this->params()),
INT_MEMBER(min_sane_x_ht_pixels, 8,
"Reject any x-ht lt or eq than this", this->params()),
BOOL_MEMBER(tessedit_create_boxfile, false,
"Output text with boxes", this->params()),
BOOL_MEMBER(tessedit_read_image, true,
"Ensure the image is read", this->params()),
INT_MEMBER(tessedit_serial_unlv, 0, "0->Whole page, 1->serial"
" no adapt, 2->serial with adapt", this->params()),
INT_MEMBER(tessedit_page_number, -1, "-1 -> All pages"
" , else specifc page to process", this->params()),
BOOL_MEMBER(tessedit_write_images, false,
"Capture the image from the IPE", this->params()),
BOOL_MEMBER(interactive_mode, false, "Run interactively?", this->params()),
STRING_MEMBER(file_type, ".tif", "Filename extension", this->params()),
INT_MEMBER(testedit_match_debug, 0,
"Integer match debug ctrl", this->params()),
BOOL_MEMBER(tessedit_override_permuter, true,
"According to dict_word", this->params()),
INT_INIT_MEMBER(tessdata_manager_debug_level, 0, "Debug level for"
" TessdataManager functions.", this->params()),
double_MEMBER(min_orientation_margin, 12.0,
"Min acceptable orientation margin", this->params()),
backup_config_file_(NULL),
pix_binary_(NULL),
pix_grey_(NULL),
orig_image_changed_(false),
textord_(this),
right_to_left_(false),
deskew_(1.0f, 0.0f),
reskew_(1.0f, 0.0f),
hindi_image_(false) {
cube_cntxt_(NULL),
tess_cube_combiner_(NULL) {
}
Tesseract::~Tesseract() {
Clear();
// Delete cube objects.
if (cube_cntxt_ != NULL) {
delete cube_cntxt_;
cube_cntxt_ = NULL;
}
if (tess_cube_combiner_ != NULL) {
delete tess_cube_combiner_;
tess_cube_combiner_ = NULL;
}
}
void Tesseract::Clear() {
#ifdef HAVE_LIBLEPT
if (pix_binary_ != NULL)
pixDestroy(&pix_binary_);
if (pix_grey_ != NULL)
pixDestroy(&pix_grey_);
#endif
deskew_ = FCOORD(1.0f, 0.0f);
reskew_ = FCOORD(1.0f, 0.0f);
deskew_ = FCOORD(1.0f, 0.0f);
reskew_ = FCOORD(1.0f, 0.0f);
orig_image_changed_ = false;
}
void Tesseract::SetBlackAndWhitelist() {

File diff suppressed because it is too large Load Diff

View File

@ -1,210 +0,0 @@
/**********************************************************************
* File: tessio.h (Formerly tessread.h)
* Description: Read/write Tesseract format row files.
* Author: Ray Smith
* Created: Wed Oct 09 15:02:46 BST 1991
*
* (C) Copyright 1991, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
#ifndef TESSIO_H
#define TESSIO_H
#include <stdio.h>
#include "tessclas.h"
#include "notdll.h"
/**
* open read & close
* @param name file name
* @param topright corner
*/
TEXTROW *get_tess_row_file(
const char *name,
TPOINT *topright
);
/**
* open read & close
* @param name file name
* @param topright corner
*/
TBLOB *get_tess_blob_file(
const char *name,
TPOINT *topright
);
/**
* read row file
* @param gphfd file to read
* @param count number expected
* @param imagesize size of image
*/
TEXTROW *readrows(
int gphfd,
int count,
TPOINT *imagesize
);
/**
* read some words
* @param gphfd file to read
* @param count number expected
* @param row row it comes from
* @param imagesize size of image
*/
TWERD *readwords(
int gphfd,
int count,
TEXTROW *row,
TPOINT *imagesize
);
/**
* read some blobs
* @param gphfd file to read
* @param count number expected
* @param imagesize size of image
*/
TBLOB *readblobs(
int gphfd,
int count,
TPOINT *imagesize
);
/**
* get a string
* @param gphfd file to read
* @param ratingspace size to read
*/
char *readratings(
int gphfd,
int ratingspace
);
/**
* read some outlines
* @param gphfd file to read
* @param outlines array of ptrs
* @param outlinecount no to read
*/
void readoutlines(
int gphfd,
TESSLINE **outlines,
int outlinecount
);
/**
* read with testing
* @param fd file to read
* @param start buffer to write
* @param size amount to write
* @param checkeof give error on eof?
*/
int readgph(
int fd,
void *start,
int size,
int checkeof
);
/**
* write a row
* @param name file name
* @param row row to write
*/
void write_row(
FILE *name,
TEXTROW *row
);
/**
* write special row
* @param name file name
* @param row row to write
* @param wordcount number of words to go
*/
void write_error_row(
FILE *name,
TEXTROW *row,
int wordcount
);
/**
* write special blob
* @param name file name
* @param blob blob to write
* @param charlist true chars
* @param charcount number of true chars
*/
void write_error_blob(
FILE *name,
TBLOB *blob,
char *charlist,
int charcount
);
/**
* write special word
* @param name file name
* @param word word to write
* @param charlist true chars
* @param charcount number of true chars
*/
void write_error_word(
FILE *name,
TWERD *word,
char *charlist,
int charcount
);
/**
* write a blob
* @param name file to write
* @param blob blob to write
*/
void writeblob(
FILE *name,
TBLOB *blob
);
/**
* serialize
* @param name file to write to
* @param blob current blob
* @param outline current outline
* @param outlineno current serial no
*/
void serial_outlines(
FILE *name,
TBLOB *blob,
register TESSLINE *outline,
int *outlineno
);
/**
* count loopsize
* @param vector vectors to count
*/
int countloop(
register BYTEVEC *vector
);
/**
* get serial no
* @param outline start of search
* @param target outline to find
* @param serial serial no so far
*/
int outlineserial(
register TESSLINE *outline,
register TESSLINE *target,
int serial
);
/**
* Interface to fwrite
* @param name file to write
* @param start buffer to write
* @param size amount to write
*/
void writegph(
FILE *name,
void *start,
int size
);
#endif

View File

@ -17,22 +17,9 @@
*
**********************************************************************/
#include <stdio.h>
#include "mfcpch.h"
#include "tessvars.h"
#include "tessvars.h"
#define EXTERN
EXTERN INT_VAR (tessedit_adapt_kludge, 0,
"Use acceptable result or dangambigs");
EXTERN BOOL_VAR (interactive_mode, FALSE, "Run interactively?");
EXTERN BOOL_VAR (edit_variables, FALSE, "Variables Editor Window?");
// xiaofan EXTERN STRING_VAR(file_type,".bl","Filename extension");
EXTERN STRING_VAR (file_type, ".tif", "Filename extension");
INT_VAR (testedit_match_debug, 0, "Integer match debug ctrl");
EXTERN INT_VAR (tessedit_dangambigs_chop, FALSE,
"Use UnicharAmbigs to direct chop");
EXTERN INT_VAR (tessedit_dangambigs_assoc, FALSE,
"Use UnicharAmbigs to direct assoc");
EXTERN IMAGE page_image; //image of page
EXTERN FILE *debug_fp = stderr; //write debug stuff here
FILE *debug_fp = stderr; // write debug stuff here

View File

@ -20,29 +20,10 @@
#ifndef TESSVARS_H
#define TESSVARS_H
#include "varable.h"
#include <stdio.h>
#include "img.h"
#include "tordmain.h"
#include "notdll.h"
extern INT_VAR_H (tessedit_adapt_kludge, 0,
"Use acceptable result or dangambigs");
extern BOOL_VAR_H (interactive_mode, FALSE, "Run interactively?");
extern BOOL_VAR_H (edit_variables, FALSE, "Variables Editor Window?");
//xiaofan extern STRING_VAR_H(file_type,".bl","Filename extension");
extern STRING_VAR_H (file_type, ".tif", "Filename extension");
extern INT_VAR_H (tessedit_truncate_wordchoice_log, 10,
"Max words to keep in list");
extern INT_VAR_H (testedit_match_debug, 0, "Integer match debug ctrl");
extern INT_VAR_H (tessedit_truncate_chopper, 1,
"Shorten chopper seam search");
extern INT_VAR_H (tessedit_fix_sideways_chops, 1,
"Fix sideways chop problem");
extern INT_VAR_H (tessedit_dangambigs_chop, FALSE,
"Use UnicharAmbigs to direct chop");
extern INT_VAR_H (tessedit_dangambigs_assoc, FALSE,
"Use UnicharAmbigs to direct assoc");
extern IMAGE page_image; //image of page
extern FILE *debug_fp; //write debug stuff here
extern FILE *debug_fp; // write debug stuff here
#endif

View File

@ -17,45 +17,23 @@
*
**********************************************************************/
#ifndef TFACEP_H
#define TFACEP_H
#ifndef TFACEP_H
#define TFACEP_H
#include "hosthplb.h"
#include "tessclas.h"
#include "tessarray.h"
#include "tstruct.h"
#include "notdll.h"
#include "choices.h"
#include "hosthplb.h"
#include "blobs.h"
#include "tessarray.h"
#include "tstruct.h"
#include "notdll.h"
#include "oldlist.h"
#include "tface.h"
#include "permute.h"
#include "adaptmatch.h"
#include "blobclass.h"
#include "stopper.h"
#include "associate.h"
#include "chop.h"
#include "expandblob.h"
#include "tordvars.h"
#include "metrics.h"
#include "tface.h"
#include "badwords.h"
#include "structures.h"
typedef void (*TESS_TESTER) (TBLOB *, BOOL8, char *, inT32, LIST);
typedef LIST (*TESS_MATCHER) (TBLOB *, TBLOB *, TBLOB *, void *, TEXTROW *);
typedef LIST (*TESS_MATCHER) (TBLOB *, TBLOB *, TBLOB *);
extern TEXTROW normalized_row;
extern int display_ratings;
#if 0
#define strsave(s) \
((s) ? \
((char*) strcpy ((char*)alloc_string (strlen(s)+1), s)) : \
(NULL))
#endif
#define BOLD_ON "&dB(s3B"
#define BOLD_OFF "&d@(s0B"
#define UNDERLINE_ON "&dD"
#define UNDERLINE_OFF "&d@"
#endif

View File

@ -39,11 +39,6 @@
#include "reject.h"
#include "tesseractclass.h"
#define EXTERN
EXTERN BOOL_VAR (tessedit_override_permuter, TRUE, "According to dict_word");
#define MAX_UNDIVIDED_LENGTH 24
@ -55,70 +50,52 @@ EXTERN BOOL_VAR (tessedit_override_permuter, TRUE, "According to dict_word");
* Convert the output back to editor form.
**********************************************************************/
namespace tesseract {
WERD_CHOICE *Tesseract::recog_word( //recog one owrd
WERD *word, //word to do
DENORM *denorm, //de-normaliser
//matcher function
POLY_MATCHER matcher,
POLY_TESTER tester, //tester function
POLY_TESTER trainer, //trainer function
BOOL8 testing, //true if answer driven
//raw result
WERD_CHOICE *&raw_choice,
//list of blob lists
BLOB_CHOICE_LIST_CLIST *blob_choices,
WERD *&outword //bln word output
) {
WERD_CHOICE *word_choice;
uinT8 perm_type;
uinT8 real_dict_perm_type;
if (word->blob_list ()->empty ()) {
word_choice = new WERD_CHOICE("", NULL, 10.0f, -1.0f,
TOP_CHOICE_PERM, unicharset);
raw_choice = new WERD_CHOICE("", NULL, 10.0f, -1.0f,
TOP_CHOICE_PERM, unicharset);
outword = word->poly_copy (denorm->row ()->x_height ());
void Tesseract::recog_word(WERD_RES *word,
BLOB_CHOICE_LIST_CLIST *blob_choices) {
ASSERT_HOST(word->chopped_word->blobs != NULL);
recog_word_recursive(word, blob_choices);
word->SetupBoxWord();
if ((word->best_choice->length() != word->box_word->length()) ||
(word->best_choice->length() != blob_choices->length())) {
tprintf("recog_word ASSERT FAIL String:\"%s\"; "
"Strlen=%d; #Blobs=%d; #Choices=%d\n",
word->best_choice->debug_string(unicharset).string(),
word->best_choice->length(), word->box_word->length(),
blob_choices->length());
}
else
word_choice = recog_word_recursive (word, denorm, matcher, tester,
trainer, testing, raw_choice,
blob_choices, outword);
if ((word_choice->length() != outword->blob_list()->length()) ||
(word_choice->length() != blob_choices->length())) {
tprintf
("recog_word ASSERT FAIL String:\"%s\"; Strlen=%d; #Blobs=%d; #Choices=%d\n",
word_choice->debug_string(unicharset).string(),
word_choice->length(), outword->blob_list()->length(),
blob_choices->length());
}
ASSERT_HOST(word_choice->length() == outword->blob_list()->length());
ASSERT_HOST(word_choice->length() == blob_choices->length());
/* Copy any reject blobs into the outword */
outword->rej_blob_list()->deep_copy(word->rej_blob_list(), &PBLOB::deep_copy);
ASSERT_HOST(word->best_choice->length() == word->box_word->length());
ASSERT_HOST(word->best_choice->length() == blob_choices->length());
if (tessedit_override_permuter) {
/* Override the permuter type if a straight dictionary check disagrees. */
perm_type = word_choice->permuter();
uinT8 perm_type = word->best_choice->permuter();
if ((perm_type != SYSTEM_DAWG_PERM) &&
(perm_type != FREQ_DAWG_PERM) && (perm_type != USER_DAWG_PERM)) {
real_dict_perm_type = dict_word(*word_choice);
uinT8 real_dict_perm_type = dict_word(*word->best_choice);
if (((real_dict_perm_type == SYSTEM_DAWG_PERM) ||
(real_dict_perm_type == FREQ_DAWG_PERM) ||
(real_dict_perm_type == USER_DAWG_PERM)) &&
(alpha_count(word_choice->unichar_string().string(),
word_choice->unichar_lengths().string()) > 0)) {
word_choice->set_permuter (real_dict_perm_type); // use dict perm
(alpha_count(word->best_choice->unichar_string().string(),
word->best_choice->unichar_lengths().string()) > 0)) {
word->best_choice->set_permuter(real_dict_perm_type); // use dict perm
}
}
if (tessedit_rejection_debug && perm_type != word_choice->permuter ()) {
tprintf ("Permuter Type Flipped from %d to %d\n",
perm_type, word_choice->permuter ());
if (tessedit_rejection_debug &&
perm_type != word->best_choice->permuter()) {
tprintf("Permuter Type Flipped from %d to %d\n",
perm_type, word->best_choice->permuter());
}
}
assert ((word_choice == NULL) == (raw_choice == NULL));
return word_choice;
// Factored out from control.cpp
ASSERT_HOST((word->best_choice == NULL) == (word->raw_choice == NULL));
if (word->best_choice == NULL || word->best_choice->length() == 0 ||
strspn(word->best_choice->unichar_string().string(), " ") ==
word->best_choice->length()) {
word->tess_failed = true;
word->reject_map.initialise(word->box_word->length());
word->reject_map.rej_word_tess_failure();
} else {
word->tess_failed = false;
}
}
@ -128,105 +105,65 @@ WERD_CHOICE *Tesseract::recog_word( //recog one owrd
* Convert the word to tess form and pass it to the tess segmenter.
* Convert the output back to editor form.
**********************************************************************/
WERD_CHOICE *
Tesseract::recog_word_recursive(
WERD *word, // word to do
DENORM *denorm, // de-normaliser
POLY_MATCHER matcher, // matcher function
POLY_TESTER tester, // tester function
POLY_TESTER trainer, // trainer function
BOOL8 testing, // true if answer driven
WERD_CHOICE *&raw_choice, // raw result
BLOB_CHOICE_LIST_CLIST *blob_choices, // list of blob lists
WERD *&outword // bln word output
) {
inT32 initial_blob_choice_len;
inT32 word_length; // no of blobs
STRING word_string; // converted from tess
STRING word_string_lengths;
BLOB_CHOICE_LIST_VECTOR *tess_ratings; // tess results
TWERD *tessword; // tess format
BLOB_CHOICE_LIST_C_IT blob_choices_it; // iterator
void Tesseract::recog_word_recursive(WERD_RES *word,
BLOB_CHOICE_LIST_CLIST *blob_choices) {
int word_length = word->chopped_word->NumBlobs(); // no of blobs
if (word_length > MAX_UNDIVIDED_LENGTH) {
return split_and_recog_word(word, blob_choices);
}
int initial_blob_choice_len = blob_choices->length();
BLOB_CHOICE_LIST_VECTOR* tess_ratings = cc_recog(word);
tess_matcher = matcher; // install matcher
tess_tester = testing ? tester : NULL;
tess_trainer = testing ? trainer : NULL;
tess_denorm = denorm;
tess_word = word;
// blob_matchers[1]=call_matcher;
if (word->blob_list ()->length () > MAX_UNDIVIDED_LENGTH) {
return split_and_recog_word (word, denorm, matcher, tester, trainer,
testing, raw_choice, blob_choices,
outword);
} else {
// Put BLOB_CHOICE_LISTs from tess_ratings into blob_choices.
BLOB_CHOICE_LIST_C_IT blob_choices_it(blob_choices);
for (int i = 0; i < tess_ratings->length(); ++i) {
blob_choices_it.add_to_end(tess_ratings->get(i));
}
delete tess_ratings;
word_length = word->rebuild_word->NumBlobs(); // No of blobs in output.
// Pad raw_choice with spaces if needed.
if (word->raw_choice->length() < word_length) {
UNICHAR_ID space_id = unicharset.unichar_to_id(" ");
WERD_CHOICE *best_choice = new WERD_CHOICE();
raw_choice = new WERD_CHOICE();
initial_blob_choice_len = blob_choices->length();
tessword = make_tess_word (word, NULL);
tess_ratings = cc_recog(tessword, best_choice, raw_choice,
testing && tester != NULL,
testing && trainer != NULL,
word->flag(W_EOL));
while (word->raw_choice->length() < word_length) {
word->raw_choice->append_unichar_id(space_id, 1, 0.0,
word->raw_choice->certainty());
}
word->raw_choice->populate_unichars(unicharset);
}
outword = make_ed_word (tessword, word); // convert word
if (outword == NULL) {
outword = word->poly_copy (denorm->row ()->x_height ());
// Do sanity checks and minor fixes on best_choice.
if (word->best_choice->length() > word_length) {
word->best_choice->make_bad(); // should never happen
tprintf("recog_word: Discarded long string \"%s\""
" (%d characters vs %d blobs)\n",
word->best_choice->unichar_string().string(),
word->best_choice->length(), word_length);
tprintf("Word is at:");
word->word->bounding_box().print();
}
if (blob_choices->length() - initial_blob_choice_len != word_length) {
word->best_choice->make_bad(); // force rejection
tprintf("recog_word: Choices list len:%d; blob lists len:%d\n",
blob_choices->length(), word_length);
blob_choices_it.set_to_list(blob_choices); // list of lists
while (blob_choices->length() - initial_blob_choice_len < word_length) {
blob_choices_it.add_to_end(new BLOB_CHOICE_LIST()); // add a fake one
tprintf("recog_word: Added dummy choice list\n");
}
delete_word(tessword); // get rid of it
word_length = outword->blob_list()->length(); // no of blobs
// Put BLOB_CHOICE_LISTs from tess_ratings into blob_choices.
blob_choices_it.set_to_list(blob_choices);
for (int i = 0; i < tess_ratings->length(); ++i) {
blob_choices_it.add_to_end(tess_ratings->get(i));
while (blob_choices->length() - initial_blob_choice_len > word_length) {
blob_choices_it.move_to_last(); // should never happen
delete blob_choices_it.extract();
tprintf("recog_word: Deleted choice list\n");
}
delete tess_ratings;
// Pad raw_choice with spaces if needed.
if (raw_choice->length() < word_length) {
while (raw_choice->length() < word_length) {
raw_choice->append_unichar_id(space_id, 1, 0.0,
raw_choice->certainty());
}
raw_choice->populate_unichars(unicharset);
}
if (word->best_choice->length() < word_length) {
UNICHAR_ID space_id = unicharset.unichar_to_id(" ");
while (word->best_choice->length() < word_length) {
word->best_choice->append_unichar_id(space_id, 1, 0.0,
word->best_choice->certainty());
}
// Do sanity checks and minor fixes on best_choice.
if (best_choice->length() > word_length) {
tprintf("recog_word: Discarded long string \"%s\""
" (%d characters vs %d blobs)\n",
best_choice->unichar_string().string (),
best_choice->length(), word_length);
best_choice->make_bad(); // should never happen
tprintf("Word is at (%g,%g)\n",
denorm->origin(),
denorm->y(word->bounding_box().bottom(), 0.0));
}
if (blob_choices->length() - initial_blob_choice_len != word_length) {
best_choice->make_bad(); // force rejection
tprintf ("recog_word: Choices list len:%d; blob lists len:%d\n",
blob_choices->length(), word_length);
blob_choices_it.set_to_list(blob_choices); // list of lists
while (blob_choices->length() - initial_blob_choice_len < word_length) {
blob_choices_it.add_to_end(new BLOB_CHOICE_LIST()); // add a fake one
tprintf("recog_word: Added dummy choice list\n");
}
while (blob_choices->length() - initial_blob_choice_len > word_length) {
blob_choices_it.move_to_last(); // should never happen
delete blob_choices_it.extract();
tprintf("recog_word: Deleted choice list\n");
}
}
if (best_choice->length() < word_length) {
while (best_choice->length() < word_length) {
best_choice->append_unichar_id(space_id, 1, 0.0,
best_choice->certainty());
}
best_choice->populate_unichars(unicharset);
}
return best_choice;
word->best_choice->populate_unichars(unicharset);
}
}
@ -234,143 +171,76 @@ Tesseract::recog_word_recursive(
/**********************************************************************
* split_and_recog_word
*
* Convert the word to tess form and pass it to the tess segmenter.
* Convert the output back to editor form.
* Split the word into 2 smaller pieces at the largest gap.
* Recognize the pieces and stick the results back together.
**********************************************************************/
WERD_CHOICE *
Tesseract::split_and_recog_word( //recog one owrd
WERD *word, //word to do
DENORM *denorm, //de-normaliser
POLY_MATCHER matcher, //matcher function
POLY_TESTER tester, //tester function
POLY_TESTER trainer, //trainer function
BOOL8 testing, //true if answer driven
//raw result
WERD_CHOICE *&raw_choice,
//list of blob lists
BLOB_CHOICE_LIST_CLIST *blob_choices,
WERD *&outword //bln word output
) {
// inT32 outword1_len;
// inT32 outword2_len;
WERD *first_word; //poly copy of word
WERD *second_word; //fabricated word
WERD *outword2; //2nd output word
PBLOB *blob;
WERD_CHOICE *result; //return value
WERD_CHOICE *result2; //output of 2nd word
WERD_CHOICE *raw_choice2; //raw version of 2nd
float gap; //blob gap
float bestgap; //biggest gap
PBLOB_LIST new_blobs; //list of gathered blobs
PBLOB_IT blob_it;
//iterator
PBLOB_IT new_blob_it = &new_blobs;
first_word = word->poly_copy (denorm->row ()->x_height ());
blob_it.set_to_list (first_word->blob_list ());
bestgap = (float) -MAX_INT32;
while (!blob_it.at_last ()) {
blob = blob_it.data ();
//gap to next
gap = (float) blob_it.data_relative(1)->bounding_box().left() -
blob->bounding_box().right();
blob_it.forward ();
if (gap > bestgap) {
bestgap = gap; //find biggest
new_blob_it = blob_it; //save position
void Tesseract::split_and_recog_word(WERD_RES *word,
BLOB_CHOICE_LIST_CLIST *blob_choices) {
// Find the biggest blob gap in the chopped_word.
int bestgap = -MAX_INT32;
TPOINT best_split_pt;
TBLOB* best_end = NULL;
TBLOB* prev_blob = NULL;
for (TBLOB* blob = word->chopped_word->blobs; blob != NULL;
blob = blob->next) {
if (prev_blob != NULL) {
TBOX prev_box = prev_blob->bounding_box();
TBOX blob_box = blob->bounding_box();
int gap = blob_box.left() - prev_box.right();
if (gap > bestgap) {
bestgap = gap;
best_end = prev_blob;
best_split_pt.x = (prev_box.right() + blob_box.left()) / 2;
best_split_pt.y = (prev_box.top() + prev_box.bottom() +
blob_box.top() + blob_box.bottom()) / 4;
}
}
prev_blob = blob;
}
//take 2nd half
new_blobs.assign_to_sublist (&new_blob_it, &blob_it);
//make it a word
second_word = new WERD (&new_blobs, 1, NULL);
ASSERT_HOST (word->blob_list ()->length () ==
first_word->blob_list ()->length () +
second_word->blob_list ()->length ());
ASSERT_HOST(best_end != NULL);
result = recog_word_recursive (first_word, denorm, matcher,
tester, trainer, testing, raw_choice,
blob_choices, outword);
delete first_word; //done that one
result2 = recog_word_recursive (second_word, denorm, matcher,
tester, trainer, testing, raw_choice2,
blob_choices, outword2);
delete second_word; //done that too
*result += *result2; //combine ratings
delete result2;
*raw_choice += *raw_choice2;
delete raw_choice2; //finished with it
// outword1_len= outword->blob_list()->length();
// outword2_len= outword2->blob_list()->length();
outword->join_on (outword2); //join words
delete outword2;
// if ( outword->blob_list()->length() != outword1_len + outword2_len )
// tprintf( "Split&Recog: part1len=%d; part2len=%d; combinedlen=%d\n",
// outword1_len, outword2_len, outword->blob_list()->length() );
// ASSERT_HOST( outword->blob_list()->length() == outword1_len + outword2_len );
return result;
// Make a copy of the word to put the 2nd half in.
WERD_RES* word2 = new WERD_RES(*word);
// Blow away the copied chopped_word, as we want to work with the blobs
// from the input chopped_word so the seam_arrays can be merged.
delete word2->chopped_word;
word2->chopped_word = new TWERD;
word2->chopped_word->blobs = best_end->next;
best_end->next = NULL;
// Make a new seamarray on both words.
free_seam_list(word->seam_array);
word->seam_array = start_seam_list(word->chopped_word->blobs);
word2->seam_array = start_seam_list(word2->chopped_word->blobs);
// Recognize the first part of the word.
recog_word_recursive(word, blob_choices);
// Recognize the second part of the word.
recog_word_recursive(word2, blob_choices);
// Tack the word2 outputs onto the end of the word outputs.
// New blobs might have appeared on the end of word1.
for (best_end = word->chopped_word->blobs; best_end->next != NULL;
best_end = best_end->next);
best_end->next = word2->chopped_word->blobs;
TBLOB* blob;
for (blob = word->rebuild_word->blobs; blob->next != NULL; blob = blob->next);
blob->next = word2->rebuild_word->blobs;
word2->chopped_word->blobs = NULL;
word2->rebuild_word->blobs = NULL;
// Copy the seams onto the end of the word1 seam_array.
// Since the seam list is one element short, an empty seam marking the
// end of the last blob in the first word is needed first.
word->seam_array = add_seam(word->seam_array,
new_seam(0.0, best_split_pt, NULL, NULL, NULL));
for (int i = 0; i < array_count(word2->seam_array); ++i) {
SEAM* seam = reinterpret_cast<SEAM*>(array_value(word2->seam_array, i));
array_value(word2->seam_array, i) = NULL;
word->seam_array = add_seam(word->seam_array, seam);
}
word->best_state += word2->best_state;
// Append the word choices.
*word->best_choice += *word2->best_choice;
*word->raw_choice += *word2->raw_choice;
delete word2;
}
} // namespace tesseract
/**********************************************************************
* call_tester
*
* Called from Tess with a blob in tess form.
* Convert the blob to editor form.
* Call the tester setup by the segmenter in tess_tester.
**********************************************************************/
#if 0 // dead code
void call_tester( //call a tester
const STRING& filename,
TBLOB *tessblob, //blob to test
BOOL8 correct_blob, //true if good
char *text, //source text
inT32 count, //chars in text
LIST result //output of matcher
) {
PBLOB *blob; //converted blob
BLOB_CHOICE_LIST ratings; //matcher result
blob = make_ed_blob (tessblob);//convert blob
if (blob == NULL)
return;
//make it right type
convert_choice_list(result, ratings);
if (tess_tester != NULL)
(*tess_tester) (filename, blob, tess_denorm, correct_blob, text, count, &ratings);
delete blob; //don't need that now
}
#endif
/**********************************************************************
* call_train_tester
*
* Called from Tess with a blob in tess form.
* Convert the blob to editor form.
* Call the trainer setup by the segmenter in tess_trainer.
**********************************************************************/
#if 0 // dead code
void call_train_tester( //call a tester
const STRING& filename,
TBLOB *tessblob, //blob to test
BOOL8 correct_blob, //true if good
char *text, //source text
inT32 count, //chars in text
LIST result //output of matcher
) {
PBLOB *blob; //converted blob
BLOB_CHOICE_LIST ratings; //matcher result
blob = make_ed_blob (tessblob);//convert blob
if (blob == NULL)
return;
//make it right type
convert_choice_list(result, ratings);
if (tess_trainer != NULL)
(*tess_trainer) (filename, blob, tess_denorm, correct_blob, text, count, &ratings);
delete blob; //don't need that now
}
#endif

View File

@ -20,15 +20,12 @@
#ifndef TFACEPP_H
#define TFACEPP_H
#include "varable.h"
#include "tstruct.h"
#include "ratngs.h"
#include "tessclas.h"
#include "blobs.h"
#include "notdll.h"
#include "tesseractclass.h"
extern BOOL_VAR_H (tessedit_override_permuter, TRUE,
"According to dict_word");
void call_tester( //call a tester
TBLOB *tessblob, //blob to test
BOOL8 correct_blob, //true if good

View File

@ -230,6 +230,11 @@ void ImageThresholder::ThresholdToPix(Pix** pix) {
}
}
// Common initialization shared between SetImage methods.
void ImageThresholder::Init() {
SetRectangle(0, 0, image_width_, image_height_);
}
// Get a clone/copy of the source image rectangle.
// The returned Pix must be pixDestroyed.
// This function will be used in the future by the page layout analysis, and
@ -253,12 +258,24 @@ Pix* ImageThresholder::GetPixRect() {
RawRectToPix(&raw_pix);
return raw_pix;
}
#endif
// Common initialization shared between SetImage methods.
void ImageThresholder::Init() {
SetRectangle(0, 0, image_width_, image_height_);
// Get a clone/copy of the source image rectangle, reduced to greyscale.
// The returned Pix must be pixDestroyed.
// This function will be used in the future by the page layout analysis, and
// the layout analysis that uses it will only be available with Leptonica,
// so there is no raw equivalent.
Pix* ImageThresholder::GetPixRectGrey() {
Pix* pix = GetPixRect(); // May have to be reduced to grey.
int depth = pixGetDepth(pix);
if (depth != 8) {
Pix* result = depth < 8 ? pixConvertTo8(pix, false)
: pixConvertRGBToLuminance(pix);
pixDestroy(&pix);
return result;
}
return pix;
}
#endif
// Otsu threshold the rectangle, taking everything except the image buffer
// pointer from the class, to the output IMAGE.

View File

@ -66,7 +66,7 @@ class ImageThresholder {
virtual void GetImageSizes(int* left, int* top, int* width, int* height,
int* imagewidth, int* imageheight);
/// Return true if HAVE_LIBLEPT and this thresholder implements the Pix
/// Return true if this thresholder implements the Pix
/// interface.
virtual bool HasThresholdToPix() const;
@ -75,11 +75,15 @@ class ImageThresholder {
return image_bytespp_ >= 3;
}
/// Returns true if the source image is binary.
bool IsBinary() const {
return image_bytespp_ == 0;
}
/// Threshold the source image as efficiently as possible to the output
/// tesseract IMAGE class.
virtual void ThresholdToIMAGE(IMAGE* image);
#ifdef HAVE_LIBLEPT
/// Pix vs raw, which to use?
/// Implementations should provide the ability to source and target Pix
/// where possible. A future version of Tesseract may choose to use Pix
@ -101,7 +105,13 @@ class ImageThresholder {
/// the layout analysis that uses it will only be available with Leptonica,
/// so there is no raw equivalent.
Pix* GetPixRect();
#endif
/// Get a clone/copy of the source image rectangle, reduced to greyscale.
/// The returned Pix must be pixDestroyed.
/// This function will be used in the future by the page layout analysis, and
/// the layout analysis that uses it will only be available with Leptonica,
/// so there is no raw equivalent.
Pix* GetPixRectGrey();
protected:
// ----------------------------------------------------------------------
@ -133,7 +143,6 @@ class ImageThresholder {
/// output IMAGE.
void CopyBinaryRectRawToIMAGE(IMAGE* image) const;
#ifdef HAVE_LIBLEPT
/// Otsu threshold the rectangle, taking everything except the image buffer
/// pointer from the class, to the output Pix.
void OtsuThresholdRectToPix(const unsigned char* imagedata,
@ -152,14 +161,11 @@ class ImageThresholder {
/// Cut out the requested rectangle of the binary image to the output IMAGE.
void CopyBinaryRectPixToIMAGE(IMAGE* image) const;
#endif
protected:
#ifdef HAVE_LIBLEPT
/// Clone or other copy of the source Pix.
/// The pix will always be PixDestroy()ed on destruction of the class.
Pix* pix_;
#endif
/// Exactly one of pix_ and image_data_ is not NULL.
const unsigned char* image_data_; //< Raw source image.
@ -178,4 +184,3 @@ class ImageThresholder {
#endif // TESSERACT_CCMAIN_THRESHOLDER_H__

View File

@ -18,370 +18,12 @@
**********************************************************************/
#include "mfcpch.h"
#ifdef _MSC_VER
#pragma warning(disable:4244) // Conversion warnings
#endif
#include "tfacep.h"
#include "tstruct.h"
#include "makerow.h"
#include "ocrblock.h"
//#include "structures.h"
static ERRCODE BADFRAGMENTS = "Couldn't find matching fragment ends";
ELISTIZE (FRAGMENT)
//extern /*"C"*/ oldoutline(TESSLINE*);
/**********************************************************************
* FRAGMENT::FRAGMENT
*
* Constructor for fragments.
**********************************************************************/
FRAGMENT::FRAGMENT ( //constructor
EDGEPT * head_pt, //start point
EDGEPT * tail_pt //end point
):head (head_pt->pos.x, head_pt->pos.y), tail (tail_pt->pos.x,
tail_pt->pos.y) {
headpt = head_pt; // save ptrs
tailpt = tail_pt;
}
// Helper function to make a fake PBLOB formed from the bounding box
// of the given old-format outline.
static PBLOB* MakeRectBlob(TESSLINE* ol) {
POLYPT_LIST poly_list;
POLYPT_IT poly_it = &poly_list;
FCOORD pos, vec;
POLYPT *polypt;
// Create points at each of the 4 corners of the rectangle in turn.
pos = FCOORD(ol->topleft.x, ol->topleft.y);
vec = FCOORD(0.0f, ol->botright.y - ol->topleft.y);
polypt = new POLYPT(pos, vec);
poly_it.add_after_then_move(polypt);
pos = FCOORD(ol->topleft.x, ol->botright.y);
vec = FCOORD(ol->botright.x - ol->topleft.x, 0.0f);
polypt = new POLYPT(pos, vec);
poly_it.add_after_then_move(polypt);
pos = FCOORD(ol->botright.x, ol->botright.y);
vec = FCOORD(0.0f, ol->topleft.y - ol->botright.y);
polypt = new POLYPT(pos, vec);
poly_it.add_after_then_move(polypt);
pos = FCOORD(ol->botright.x, ol->topleft.y);
vec = FCOORD(ol->topleft.x - ol->botright.x, 0.0f);
polypt = new POLYPT(pos, vec);
poly_it.add_after_then_move(polypt);
OUTLINE_LIST out_list;
OUTLINE_IT out_it = &out_list;
out_it.add_after_then_move(new OUTLINE(&poly_it));
return new PBLOB(&out_list);
}
/**********************************************************************
* make_ed_word
*
* Make an editor format word from the tess style word.
**********************************************************************/
WERD *make_ed_word( //construct word
TWERD *tessword, //word to convert
WERD *clone //clone this one
) {
WERD *word; //converted word
TBLOB *tblob; //current blob
PBLOB *blob; //new blob
PBLOB_LIST blobs; //list of blobs
PBLOB_IT blob_it = &blobs; //iterator
for (tblob = tessword->blobs; tblob != NULL; tblob = tblob->next) {
blob = make_ed_blob (tblob);
if (blob == NULL && tblob->outlines != NULL) {
// Make a fake blob using the bounding box rectangle of the 1st outline.
blob = MakeRectBlob(tblob->outlines);
}
if (blob != NULL) {
blob_it.add_after_then_move (blob);
}
}
if (!blobs.empty ())
word = new WERD (&blobs, clone);
else
word = NULL;
return word;
}
/**********************************************************************
* make_ed_blob
*
* Make an editor format blob from the tess style blob.
**********************************************************************/
PBLOB *make_ed_blob( //construct blob
TBLOB *tessblob //blob to convert
) {
TESSLINE *tessol; //tess outline
FRAGMENT_LIST fragments; //list of fragments
OUTLINE *outline; //current outline
OUTLINE_LIST out_list; //list of outlines
OUTLINE_IT out_it = &out_list; //iterator
for (tessol = tessblob->outlines; tessol != NULL; tessol = tessol->next) {
//stick in list
register_outline(tessol, &fragments);
}
while (!fragments.empty ()) {
outline = make_ed_outline (&fragments);
if (outline != NULL) {
out_it.add_after_then_move (outline);
}
}
if (out_it.empty())
return NULL; //couldn't do it
return new PBLOB (&out_list); //turn to blob
}
/**********************************************************************
* make_ed_outline
*
* Make an editor format outline from the list of fragments.
**********************************************************************/
OUTLINE *make_ed_outline( //constructoutline
FRAGMENT_LIST *list //list of fragments
) {
FRAGMENT *fragment; //current fragment
EDGEPT *edgept; //current point
ICOORD headpos; //coords of head
ICOORD tailpos; //coords of tail
FCOORD pos; //coords of edgept
FCOORD vec; //empty
POLYPT *polypt; //current point
POLYPT_LIST poly_list; //list of point
POLYPT_IT poly_it = &poly_list;//iterator
FRAGMENT_IT fragment_it = list;//fragment
headpos = fragment_it.data ()->head;
do {
fragment = fragment_it.data ();
edgept = fragment->headpt; //start of segment
do {
pos = FCOORD (edgept->pos.x, edgept->pos.y);
vec = FCOORD (edgept->vec.x, edgept->vec.y);
polypt = new POLYPT (pos, vec);
//add to list
poly_it.add_after_then_move (polypt);
edgept = edgept->next;
}
while (edgept != fragment->tailpt);
tailpos = ICOORD (edgept->pos.x, edgept->pos.y);
//get rid of it
delete fragment_it.extract ();
if (tailpos != headpos) {
if (fragment_it.empty ()) {
return NULL;
}
fragment_it.forward ();
//find next segment
for (fragment_it.mark_cycle_pt (); !fragment_it.cycled_list () &&
fragment_it.data ()->head != tailpos;
fragment_it.forward ());
if (fragment_it.data ()->head != tailpos) {
// It is legitimate for the heads to not all match to tails,
// since not all combinations of seams always make sense.
for (fragment_it.mark_cycle_pt ();
!fragment_it.cycled_list (); fragment_it.forward ()) {
fragment = fragment_it.extract ();
delete fragment;
}
return NULL; //can't do it
}
}
}
while (tailpos != headpos);
return new OUTLINE (&poly_it); //turn to outline
}
/**********************************************************************
* register_outline
*
* Add the fragments in the given outline to the list
**********************************************************************/
void register_outline( //add fragments
TESSLINE *outline, //tess format
FRAGMENT_LIST *list //list to add to
) {
EDGEPT *startpt; //start of outline
EDGEPT *headpt; //start of fragment
EDGEPT *tailpt; //end of fragment
FRAGMENT *fragment; //new fragment
FRAGMENT_IT it = list; //iterator
startpt = outline->loop;
do {
startpt = startpt->next;
if (startpt == NULL)
return; //illegal!
}
while (startpt->flags[0] == 0 && startpt != outline->loop);
headpt = startpt;
do
startpt = startpt->next;
while (startpt->flags[0] != 0 && startpt != headpt);
if (startpt->flags[0] != 0)
return; //all hidden!
headpt = startpt;
do {
tailpt = headpt;
do
tailpt = tailpt->next;
while (tailpt->flags[0] == 0 && tailpt != startpt);
fragment = new FRAGMENT (headpt, tailpt);
it.add_after_then_move (fragment);
while (tailpt->flags[0] != 0)
tailpt = tailpt->next;
headpt = tailpt;
}
while (tailpt != startpt);
}
/**********************************************************************
* make_tess_row
*
* Make a fake row structure to pass to the tesseract matchers.
**********************************************************************/
void make_tess_row( //make fake row
DENORM *denorm, //row info
TEXTROW *tessrow //output row
) {
tessrow->baseline.segments = 1;
tessrow->baseline.xstarts[0] = -32767;
tessrow->baseline.xstarts[1] = 32767;
tessrow->baseline.quads[0].a = 0;
tessrow->baseline.quads[0].b = 0;
tessrow->baseline.quads[0].c = bln_baseline_offset;
tessrow->xheight.segments = 1;
tessrow->xheight.xstarts[0] = -32767;
tessrow->xheight.xstarts[1] = 32767;
tessrow->xheight.quads[0].a = 0;
tessrow->xheight.quads[0].b = 0;
tessrow->xheight.quads[0].c = bln_x_height + bln_baseline_offset;
tessrow->lineheight = bln_x_height;
if (denorm != NULL) {
tessrow->ascrise = denorm->row ()->ascenders () * denorm->scale ();
tessrow->descdrop = denorm->row ()->descenders () * denorm->scale ();
} else {
tessrow->ascrise = bln_baseline_offset;
tessrow->descdrop = -bln_baseline_offset;
}
}
/**********************************************************************
* make_tess_word
*
* Convert the word to Tess format.
**********************************************************************/
TWERD *make_tess_word( //convert word
WERD *word, //word to do
TEXTROW *row //fake row
) {
TWERD *tessword; //tess format
tessword = newword (); //use old allocator
tessword->row = row; //give them something
//copy string
tessword->correct = strsave (word->text ());
tessword->guess = NULL;
tessword->blobs = make_tess_blobs (word->blob_list ());
tessword->blanks = 1;
tessword->blobcount = word->blob_list ()->length ();
tessword->next = NULL;
return tessword;
}
/**********************************************************************
* make_tess_blobs
*
* Make Tess style blobs from a list of BLOBs.
**********************************************************************/
TBLOB *make_tess_blobs( //make tess blobs
PBLOB_LIST *bloblist //list to convert
) {
PBLOB_IT it = bloblist; //iterator
PBLOB *blob; //current blob
TBLOB *head; //output list
TBLOB *tail; //end of list
TBLOB *tessblob;
head = NULL;
tail = NULL;
for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ()) {
blob = it.data ();
tessblob = make_tess_blob (blob, TRUE);
if (head)
tail->next = tessblob;
else
head = tessblob;
tail = tessblob;
}
return head;
}
/**********************************************************************
* make_rotated_tess_blob
*
* Make a single Tess style blob, applying the given rotation and
* renormalizing.
**********************************************************************/
TBLOB *make_rotated_tess_blob(const DENORM* denorm, PBLOB *blob,
BOOL8 flatten) {
if (denorm != NULL && denorm->block() != NULL &&
denorm->block()->classify_rotation().y() != 0.0) {
TBOX box = blob->bounding_box();
int src_width = box.width();
int src_height = box.height();
src_width = static_cast<int>(src_width / denorm->scale() + 0.5);
src_height = static_cast<int>(src_height / denorm->scale() + 0.5);
int x_middle = (box.left() + box.right()) / 2;
int y_middle = (box.top() + box.bottom()) / 2;
PBLOB* rotated_blob = PBLOB::deep_copy(blob);
rotated_blob->move(FCOORD(-x_middle, -y_middle));
rotated_blob->rotate(denorm->block()->classify_rotation());
ICOORD median_size = denorm->block()->median_size();
int tolerance = median_size.x() / 8;
// TODO(dsl/rays) find a better normalization solution. In the mean time
// make it work for CJK by normalizing for Cap height in the same way
// as is applied in compute_block_xheight when the row is presumed to
// be ALLCAPS, i.e. the x-height is the fixed fraction
// blob height * textord_merge_x / (textord_merge_x + textord_merge_asc)
if (NearlyEqual(src_width, static_cast<int>(median_size.x()), tolerance) &&
NearlyEqual(src_height, static_cast<int>(median_size.y()), tolerance)) {
float target_height = bln_x_height * (textord_merge_x + textord_merge_asc)
/ textord_merge_x;
rotated_blob->scale(target_height / box.width());
rotated_blob->move(FCOORD(0.0f,
bln_baseline_offset -
rotated_blob->bounding_box().bottom()));
}
TBLOB* result = make_tess_blob(rotated_blob, flatten);
delete rotated_blob;
return result;
} else {
return make_tess_blob(blob, flatten);
}
}
#include "ccstruct.h"
#include "helpers.h"
#include "tfacep.h"
#include "tstruct.h"
#include "makerow.h"
#include "ocrblock.h"
/**********************************************************************
* make_tess_blob
@ -389,24 +31,9 @@ TBLOB *make_rotated_tess_blob(const DENORM* denorm, PBLOB *blob,
* Make a single Tess style blob
**********************************************************************/
TBLOB *make_tess_blob( //make tess blob
PBLOB *blob, //blob to convert
BOOL8 flatten //flatten outline structure
) {
inT32 index;
TBLOB *tessblob;
tessblob = newblob ();
tessblob->outlines = (struct olinestruct *)
make_tess_outlines (blob->out_list (), flatten);
for (index = 0; index < TBLOBFLAGS; index++)
tessblob->flags[index] = 0; //!!
tessblob->correct = 0;
tessblob->guess = 0;
for (index = 0; index < MAX_WO_CLASSES; index++) {
tessblob->classes[index] = 0;
tessblob->values[index] = 0;
}
TBLOB *make_tess_blob(PBLOB *blob) {
TBLOB* tessblob = new TBLOB;
tessblob->outlines = make_tess_outlines(blob->out_list(), false);
tessblob->next = NULL;
return tessblob;
}
@ -418,10 +45,8 @@ TBLOB *make_tess_blob( //make tess blob
* Make Tess style outlines from a list of OUTLINEs.
**********************************************************************/
TESSLINE *make_tess_outlines( //make tess outlines
OUTLINE_LIST *outlinelist, //list to convert
BOOL8 flatten //flatten outline structure
) {
TESSLINE *make_tess_outlines(OUTLINE_LIST *outlinelist, // List to convert.
bool is_holes) { // These are hole outlines.
OUTLINE_IT it = outlinelist; //iterator
OUTLINE *outline; //current outline
TESSLINE *head; //output list
@ -430,31 +55,21 @@ TESSLINE *make_tess_outlines( //make tess outlines
head = NULL;
tail = NULL;
for (it.mark_cycle_pt (); !it.cycled_list (); it.forward ()) {
outline = it.data ();
tessoutline = newoutline ();
tessoutline->compactloop = NULL;
tessoutline->loop = make_tess_edgepts (outline->polypts (),
tessoutline->topleft,
tessoutline->botright);
for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
outline = it.data();
tessoutline = new TESSLINE;
tessoutline->loop = make_tess_edgepts(outline->polypts(),
tessoutline->topleft,
tessoutline->botright);
if (tessoutline->loop == NULL) {
oldoutline(tessoutline);
delete tessoutline;
continue;
}
tessoutline->start = tessoutline->loop->pos;
tessoutline->node = NULL;
tessoutline->next = NULL;
tessoutline->child = NULL;
if (!outline->child ()->empty ()) {
if (flatten)
tessoutline->next = (struct olinestruct *)
make_tess_outlines (outline->child (), flatten);
else {
tessoutline->next = NULL;
tessoutline->child = (struct olinestruct *)
make_tess_outlines (outline->child (), flatten);
}
}
tessoutline->is_hole = is_holes;
if (!outline->child()->empty())
tessoutline->next = make_tess_outlines(outline->child(), true);
else
tessoutline->next = NULL;
if (head)
@ -492,22 +107,17 @@ EDGEPT *make_tess_edgepts( //make tess edgepts
tl.y = -MAX_INT16;
br.x = -MAX_INT16;
br.y = MAX_INT16;
for (it.mark_cycle_pt (); !it.cycled_list ();) {
edgept = it.data ();
tessedgept = newedgept ();
tessedgept->pos.x = (inT16) edgept->pos.x ();
tessedgept->pos.y = (inT16) edgept->pos.y ();
if (tessedgept->pos.x < tl.x)
tl.x = tessedgept->pos.x;
if (tessedgept->pos.x > br.x)
br.x = tessedgept->pos.x;
if (tessedgept->pos.y > tl.y)
tl.y = tessedgept->pos.y;
if (tessedgept->pos.y < br.y)
br.y = tessedgept->pos.y;
if (head != NULL && tessedgept->pos.x == tail->pos.x
&& tessedgept->pos.y == tail->pos.y) {
oldedgept(tessedgept);
for (it.mark_cycle_pt(); !it.cycled_list ();) {
edgept = it.data();
tessedgept = new EDGEPT;
tessedgept->pos.x = (inT16) edgept->pos.x();
tessedgept->pos.y = (inT16) edgept->pos.y();
UpdateRange(tessedgept->pos.x, &tl.x, &br.x);
UpdateRange(tessedgept->pos.y, &br.y, &tl.y);
if (head != NULL &&
tessedgept->pos.x == tail->pos.x &&
tessedgept->pos.y == tail->pos.y) {
delete tessedgept;
}
else {
for (index = 0; index < EDGEPTFLAGS; index++)
@ -530,7 +140,7 @@ EDGEPT *make_tess_edgepts( //make tess edgepts
tail->vec.x = head->pos.x - tail->pos.x;
tail->vec.y = head->pos.y - tail->pos.y;
if (head == tail) {
oldedgept(head);
delete head;
return NULL; //empty
}
return head;

View File

@ -21,62 +21,13 @@
#define TSTRUCT_H
#include "werd.h"
#include "tessclas.h"
#include "blobs.h"
#include "ratngs.h"
#include "notdll.h"
class FRAGMENT:public ELIST_LINK
{
public:
FRAGMENT() { //constructor
}
FRAGMENT(EDGEPT *head_pt, //start
EDGEPT *tail_pt); //end
ICOORD head; //coords of start
ICOORD tail; //coords of end
EDGEPT *headpt; //start point
EDGEPT *tailpt; //end point
NEWDELETE2 (FRAGMENT)
};
ELISTIZEH (FRAGMENT)
WERD *make_ed_word( //construct word
TWERD *tessword, //word to convert
WERD *clone //clone this one
);
PBLOB *make_ed_blob( //construct blob
TBLOB *tessblob //blob to convert
);
OUTLINE *make_ed_outline( //constructoutline
FRAGMENT_LIST *list //list of fragments
);
void register_outline( //add fragments
TESSLINE *outline, //tess format
FRAGMENT_LIST *list //list to add to
);
void make_tess_row( //make fake row
DENORM *denorm, //row info
TEXTROW *tessrow //output row
);
TWERD *make_tess_word( //convert owrd
WERD *word, //word to do
TEXTROW *row //fake row
);
TBLOB *make_tess_blobs( //make tess blobs
PBLOB_LIST *bloblist //list to convert
);
TBLOB *make_rotated_tess_blob(const DENORM* denorm, PBLOB *blob,
BOOL8 flatten);
TBLOB *make_tess_blob( //make tess blob
PBLOB *blob, //blob to convert
BOOL8 flatten //flatten outline structure
);
TESSLINE *make_tess_outlines( //make tess outlines
OUTLINE_LIST *outlinelist, //list to convert
BOOL8 flatten //flatten outline structure
);
TBLOB *make_tess_blob(PBLOB *blob);
TESSLINE *make_tess_outlines(OUTLINE_LIST *outlinelist, // List to convert
bool is_holes); // These are hole outlines.
EDGEPT *make_tess_edgepts( //make tess edgepts
POLYPT_LIST *edgeptlist, //list to convert
TPOINT &tl, //bounding box

View File

@ -1,139 +0,0 @@
///////////////////////////////////////////////////////////////////////
// File: varabled.cpp
// Description: Variables Editor
// Author: Joern Wanke
// Created: Wed Jul 18 10:05:01 PDT 2007
//
// (C) Copyright 2007, Google Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////
/**
* @file varabled.h
* The variables editor is used to edit all the variables used within
* tesseract from the ui.
*/
#ifndef GRAPHICS_DISABLED
#ifndef VARABLED_H
#define VARABLED_H
#include "elst.h"
#include "scrollview.h"
#include "varable.h"
#include "tesseractclass.h"
class SVMenuNode;
/** A list of all possible variable types used. */
enum VarType {
VT_INTEGER,
VT_BOOLEAN,
VT_STRING,
VT_DOUBLE
};
/**
* A rather hackish helper structure which can take any kind of variable input
* (defined by VarType) and do a couple of common operations on them, like
* comparisond or getting its value. It is used in the context of the
* VariablesEditor as a bridge from the internal tesseract variables to the
* ones displayed by the ScrollView server.
*/
class VariableContent : public ELIST_LINK {
public:
/** Compare two VC objects by their name. */
static int Compare(const void* v1, const void* v2);
/** Gets a VC object identified by its ID. */
static VariableContent* GetVariableContentById(int id);
/** Constructors for the various VarTypes. */
VariableContent() {
}
VariableContent(STRING_VARIABLE* it);
VariableContent(INT_VARIABLE* it);
VariableContent(BOOL_VARIABLE* it);
VariableContent(double_VARIABLE* it);
/** Getters and Setters. */
void SetValue(const char* val);
const char* GetValue() const;
const char* GetName() const;
const char* GetDescription() const;
int GetId() { return my_id_; }
bool HasChanged() { return changed_; }
private:
/** The unique ID of this VC object. */
int my_id_;
/** Whether the variable was changed_ and thus needs to be rewritten. */
bool changed_;
/** The actual vartype of this VC object. */
VarType var_type_;
STRING_VARIABLE* sIt;
INT_VARIABLE* iIt;
BOOL_VARIABLE* bIt;
double_VARIABLE* dIt;
};
ELISTIZEH(VariableContent)
/**
* The variables editor enables the user to edit all the variables used within
* tesseract. It can be invoked on its own, but is supposed to be invoked by
* the program editor.
*/
class VariablesEditor : public SVEventHandler {
public:
/**
* Integrate the variables editor as popupmenu into the existing scrollview
* window (usually the pg editor). If sv == null, create a new empty
* empty window and attach the variables editor to that window (ugly).
*/
VariablesEditor(const tesseract::Tesseract*, ScrollView* sv = NULL);
/** Event listener. Waits for SVET_POPUP events and processes them. */
void Notify(const SVEvent* sve);
private:
/**
* Gets the up to the first 3 prefixes from s (split by _).
* For example, tesseract_foo_bar will be split into tesseract, foo, and bar.
*/
void GetPrefixes(const char* s, STRING* level_one,
STRING* level_two, STRING* level_three);
/**
* Gets the first n words (split by _) and puts them in t.
* For example, tesseract_foo_bar with N=2 will yield tesseract_foo_.
*/
void GetFirstWords(const char *s, // source string
int n, // number of words
char *t); // target string
/**
* Find all editable variables used within tesseract and create a
* SVMenuNode tree from it.
*/
SVMenuNode *BuildListOfAllLeaves();
/** Write all (changed_) variables to a config file. */
void WriteVars(char* filename, bool changes_only);
ScrollView* sv_window_;
};
#endif
#endif

View File

@ -1,3 +1,4 @@
/**********************************************************************
* File: werdit.cpp (Formerly wordit.c)
* Description: An iterator for passing over all the words in a document.
@ -18,99 +19,7 @@
**********************************************************************/
#include "mfcpch.h"
#include "werdit.h"
#define EXTERN
//EXTERN BOOL_VAR(wordit_linearc,FALSE,"Pass poly of linearc to Tess");
/**********************************************************************
* WERDIT::start_page
*
* Get ready to iterate over the page by setting the iterators.
**********************************************************************/
void WERDIT::start_page( //set iterators
BLOCK_LIST *block_list //blocks to check
) {
block_it.set_to_list (block_list);
block_it.mark_cycle_pt ();
do {
while (block_it.data ()->row_list ()->empty ()
&& !block_it.cycled_list ()) {
block_it.forward ();
}
if (!block_it.data ()->row_list ()->empty ()) {
row_it.set_to_list (block_it.data ()->row_list ());
row_it.mark_cycle_pt ();
while (row_it.data ()->word_list ()->empty ()
&& !row_it.cycled_list ()) {
row_it.forward ();
}
if (!row_it.data ()->word_list ()->empty ()) {
word_it.set_to_list (row_it.data ()->word_list ());
word_it.mark_cycle_pt ();
}
}
}
while (!block_it.cycled_list () && row_it.data ()->word_list ()->empty ());
}
/**********************************************************************
* WERDIT::forward
*
* Give the next word on the page, or NULL if none left.
* This code assumes all rows to be non-empty, but blocks are allowed
* to be empty as eventually we will have non-text blocks.
* The output is always a copy and needs to be deleted by somebody.
**********************************************************************/
WERD *WERDIT::forward() { //use iterators
WERD *word; //actual word
// WERD *larc_word; //linearc copy
WERD *result; //output word
ROW *row; //row of word
if (word_it.cycled_list ()) {
return NULL; //finished page
}
else {
word = word_it.data ();
row = row_it.data ();
word_it.forward ();
if (word_it.cycled_list ()) {
row_it.forward (); //finished row
if (row_it.cycled_list ()) {
do {
block_it.forward (); //finished block
if (!block_it.cycled_list ()) {
row_it.set_to_list (block_it.data ()->row_list ());
row_it.mark_cycle_pt ();
}
}
//find non-empty block
while (!block_it.cycled_list ()
&& row_it.cycled_list ());
}
if (!row_it.cycled_list ()) {
word_it.set_to_list (row_it.data ()->word_list ());
word_it.mark_cycle_pt ();
}
}
// if (wordit_linearc && !word->flag(W_POLYGON))
// {
// larc_word=word->larc_copy(row->x_height());
// result=larc_word->poly_copy(row->x_height());
// delete larc_word;
// }
// else
result = word->poly_copy (row->x_height ());
return result;
}
}
#include "werdit.h"
/**********************************************************************
* make_pseudo_word
@ -119,74 +28,33 @@ WERD *WERDIT::forward() { //use iterators
* The word is always a copy and needs to be deleted.
**********************************************************************/
WERD *make_pseudo_word( //make fake word
BLOCK_LIST *block_list, //blocks to check //block of selection
WERD *make_pseudo_word(PAGE_RES* page_res, // Blocks to check.
TBOX &selection_box,
BLOCK *&pseudo_block,
ROW *&pseudo_row //row of selection
) {
BLOCK_IT block_it(block_list);
BLOCK *block;
ROW_IT row_it;
ROW *row;
WERD_IT word_it;
WERD *word;
PBLOB_IT blob_it;
PBLOB *blob;
PBLOB_LIST new_blobs; //list of gathered blobs
//iterator
PBLOB_IT new_blob_it = &new_blobs;
WERD *pseudo_word; //fabricated word
WERD *poly_word; //poly copy of word
// WERD *larc_word; //linearc copy
ROW *&pseudo_row) { // Row of selection.
PAGE_RES_IT pr_it(page_res);
C_BLOB_LIST new_blobs; // list of gathered blobs
C_BLOB_IT new_blob_it = &new_blobs; // iterator
WERD *pseudo_word; // fabricated word
for (block_it.mark_cycle_pt ();
!block_it.cycled_list (); block_it.forward ()) {
block = block_it.data ();
if (block->bounding_box ().overlap (selection_box)) {
pseudo_block = block;
row_it.set_to_list (block->row_list ());
for (row_it.mark_cycle_pt ();
!row_it.cycled_list (); row_it.forward ()) {
row = row_it.data ();
if (row->bounding_box ().overlap (selection_box)) {
word_it.set_to_list (row->word_list ());
for (word_it.mark_cycle_pt ();
!word_it.cycled_list (); word_it.forward ()) {
word = word_it.data ();
if (word->bounding_box ().overlap (selection_box)) {
// if (wordit_linearc && !word->flag(W_POLYGON))
// {
// larc_word=word->larc_copy(row->x_height());
// poly_word=larc_word->poly_copy(row->x_height());
// delete larc_word;
// }
// else
poly_word = word->poly_copy (row->x_height ());
blob_it.set_to_list (poly_word->blob_list ());
for (blob_it.mark_cycle_pt ();
!blob_it.cycled_list (); blob_it.forward ()) {
blob = blob_it.data ();
if (blob->bounding_box ().
overlap (selection_box)) {
new_blob_it.add_after_then_move (blob_it.
extract
());
//steal off list
pseudo_row = row;
}
}
delete poly_word; //get rid of it
}
}
for (WERD_RES* word_res = pr_it.word(); word_res != NULL;
word_res = pr_it.forward()) {
WERD* word = word_res->word;
if (word->bounding_box().overlap(selection_box)) {
C_BLOB_IT blob_it(word->cblob_list());
for (blob_it.mark_cycle_pt();
!blob_it.cycled_list(); blob_it.forward()) {
C_BLOB* blob = blob_it.data();
if (blob->bounding_box().overlap(selection_box)) {
new_blob_it.add_after_then_move(C_BLOB::deep_copy(blob));
pseudo_row = pr_it.row()->row;
pseudo_block = pr_it.block()->block;
}
}
}
}
if (!new_blobs.empty ()) {
//make new word
pseudo_word = new WERD (&new_blobs, 1, NULL);
}
if (!new_blobs.empty())
pseudo_word = new WERD(&new_blobs, 1, NULL);
else
pseudo_word = NULL;
return pseudo_word;

View File

@ -20,48 +20,12 @@
#ifndef WERDIT_H
#define WERDIT_H
#include "varable.h"
#include "ocrblock.h"
#include "pageres.h"
#include "notdll.h"
class WERDIT
{
public:
WERDIT() {
} //empty contructor
WERDIT( //empty contructor
BLOCK_LIST *blocklist) { //blocks on page
start_page(blocklist); //ready to scan
}
void start_page( //get ready
BLOCK_LIST *blocklist); //blocks on page
WERD *forward(); //get next word
WERD *next_word() { //get next word
return word_it.data (); //already at next
}
ROW *row() { //get current row
return word_it.cycled_list ()? NULL : row_it.data ();
}
ROW *next_row() { //get next row
return row_it.data_relative (1);
}
BLOCK *block() { //get current block
return block_it.data ();
}
private:
BLOCK_IT block_it; //iterators
ROW_IT row_it;
WERD_IT word_it;
};
//extern BOOL_VAR_H(wordit_linearc,FALSE,"Pass poly of linearc to Tess");
WERD *make_pseudo_word( //make fake word
BLOCK_LIST *block_list, //blocks to check //block of selection
WERD *make_pseudo_word(PAGE_RES* page_res, // blocks to check
TBOX &selection_box,
BLOCK *&pseudo_block,
ROW *&pseudo_row //row of selection
);
ROW *&pseudo_row);
#endif

View File

@ -3,28 +3,26 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/ccutil -I$(top_srcdir)/cutil \
-I$(top_srcdir)/image -I$(top_srcdir)/viewer
EXTRA_DIST = ccstruct.vcproj
include_HEADERS = \
blckerr.h blobbox.h blobs.h blread.h ccstruct.h coutln.h crakedge.h \
detlinefit.h genblob.h hpddef.h hpdsizes.h ipoints.h \
labls.h linlsq.h lmedsq.h mod128.h normalis.h \
blckerr.h blobbox.h blobs.h blread.h boxword.h ccstruct.h coutln.h crakedge.h \
detlinefit.h dppoint.h genblob.h hpddef.h hpdsizes.h ipoints.h \
linlsq.h matrix.h mod128.h normalis.h \
ocrblock.h ocrrow.h otsuthr.h \
pageres.h pdblock.h points.h polyaprx.h polyblk.h \
polyblob.h polyvert.h poutline.h \
polyblob.h polyvert.h poutline.h publictypes.h \
quadlsq.h quadratc.h quspline.h ratngs.h rect.h rejctmap.h \
statistc.h stepblob.h vecfuncs.h werd.h
seam.h split.h statistc.h stepblob.h vecfuncs.h werd.h
lib_LTLIBRARIES = libtesseract_ccstruct.la
libtesseract_ccstruct_la_SOURCES = \
blobbox.cpp blobs.cpp blread.cpp callcpp.cpp ccstruct.cpp coutln.cpp \
detlinefit.cpp genblob.cpp \
labls.cpp linlsq.cpp lmedsq.cpp mod128.cpp normalis.cpp \
blobbox.cpp blobs.cpp blread.cpp boxword.cpp callcpp.cpp ccstruct.cpp coutln.cpp \
detlinefit.cpp dppoint.cpp genblob.cpp \
linlsq.cpp matrix.cpp mod128.cpp normalis.cpp \
ocrblock.cpp ocrrow.cpp otsuthr.cpp \
pageres.cpp pdblock.cpp points.cpp polyaprx.cpp polyblk.cpp \
polyblob.cpp polyvert.cpp poutline.cpp \
polyblob.cpp polyvert.cpp poutline.cpp publictypes.cpp \
quadlsq.cpp quadratc.cpp quspline.cpp ratngs.cpp rect.cpp rejctmap.cpp \
statistc.cpp stepblob.cpp \
seam.cpp split.cpp statistc.cpp stepblob.cpp \
vecfuncs.cpp werd.cpp
libtesseract_ccstruct_la_LDFLAGS = -version-info $(GENERIC_LIBRARY_VERSION)

View File

@ -72,12 +72,13 @@ am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(includedir)"
LTLIBRARIES = $(lib_LTLIBRARIES)
libtesseract_ccstruct_la_LIBADD =
am_libtesseract_ccstruct_la_OBJECTS = blobbox.lo blobs.lo blread.lo \
callcpp.lo ccstruct.lo coutln.lo detlinefit.lo genblob.lo \
labls.lo linlsq.lo lmedsq.lo mod128.lo normalis.lo ocrblock.lo \
ocrrow.lo otsuthr.lo pageres.lo pdblock.lo points.lo \
polyaprx.lo polyblk.lo polyblob.lo polyvert.lo poutline.lo \
quadlsq.lo quadratc.lo quspline.lo ratngs.lo rect.lo \
rejctmap.lo statistc.lo stepblob.lo vecfuncs.lo werd.lo
boxword.lo callcpp.lo ccstruct.lo coutln.lo detlinefit.lo \
dppoint.lo genblob.lo linlsq.lo matrix.lo mod128.lo \
normalis.lo ocrblock.lo ocrrow.lo otsuthr.lo pageres.lo \
pdblock.lo points.lo polyaprx.lo polyblk.lo polyblob.lo \
polyvert.lo poutline.lo publictypes.lo quadlsq.lo quadratc.lo \
quspline.lo ratngs.lo rect.lo rejctmap.lo seam.lo split.lo \
statistc.lo stepblob.lo vecfuncs.lo werd.lo
libtesseract_ccstruct_la_OBJECTS = \
$(am_libtesseract_ccstruct_la_OBJECTS)
libtesseract_ccstruct_la_LINK = $(LIBTOOL) --tag=CXX \
@ -252,7 +253,6 @@ libdir = @libdir@
libexecdir = @libexecdir@
localedir = @localedir@
localstatedir = @localstatedir@
lt_ECHO = @lt_ECHO@
mandir = @mandir@
mkdir_p = @mkdir_p@
oldincludedir = @oldincludedir@
@ -273,27 +273,26 @@ AM_CPPFLAGS = \
-I$(top_srcdir)/ccutil -I$(top_srcdir)/cutil \
-I$(top_srcdir)/image -I$(top_srcdir)/viewer
EXTRA_DIST = ccstruct.vcproj
include_HEADERS = \
blckerr.h blobbox.h blobs.h blread.h ccstruct.h coutln.h crakedge.h \
detlinefit.h genblob.h hpddef.h hpdsizes.h ipoints.h \
labls.h linlsq.h lmedsq.h mod128.h normalis.h \
blckerr.h blobbox.h blobs.h blread.h boxword.h ccstruct.h coutln.h crakedge.h \
detlinefit.h dppoint.h genblob.h hpddef.h hpdsizes.h ipoints.h \
linlsq.h matrix.h mod128.h normalis.h \
ocrblock.h ocrrow.h otsuthr.h \
pageres.h pdblock.h points.h polyaprx.h polyblk.h \
polyblob.h polyvert.h poutline.h \
polyblob.h polyvert.h poutline.h publictypes.h \
quadlsq.h quadratc.h quspline.h ratngs.h rect.h rejctmap.h \
statistc.h stepblob.h vecfuncs.h werd.h
seam.h split.h statistc.h stepblob.h vecfuncs.h werd.h
lib_LTLIBRARIES = libtesseract_ccstruct.la
libtesseract_ccstruct_la_SOURCES = \
blobbox.cpp blobs.cpp blread.cpp callcpp.cpp ccstruct.cpp coutln.cpp \
detlinefit.cpp genblob.cpp \
labls.cpp linlsq.cpp lmedsq.cpp mod128.cpp normalis.cpp \
blobbox.cpp blobs.cpp blread.cpp boxword.cpp callcpp.cpp ccstruct.cpp coutln.cpp \
detlinefit.cpp dppoint.cpp genblob.cpp \
linlsq.cpp matrix.cpp mod128.cpp normalis.cpp \
ocrblock.cpp ocrrow.cpp otsuthr.cpp \
pageres.cpp pdblock.cpp points.cpp polyaprx.cpp polyblk.cpp \
polyblob.cpp polyvert.cpp poutline.cpp \
polyblob.cpp polyvert.cpp poutline.cpp publictypes.cpp \
quadlsq.cpp quadratc.cpp quspline.cpp ratngs.cpp rect.cpp rejctmap.cpp \
statistc.cpp stepblob.cpp \
seam.cpp split.cpp statistc.cpp stepblob.cpp \
vecfuncs.cpp werd.cpp
libtesseract_ccstruct_la_LDFLAGS = -version-info $(GENERIC_LIBRARY_VERSION)
@ -374,14 +373,15 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/blobbox.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/blobs.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/blread.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/boxword.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/callcpp.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ccstruct.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/coutln.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/detlinefit.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dppoint.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/genblob.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/labls.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/linlsq.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmedsq.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/matrix.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mod128.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/normalis.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ocrblock.Plo@am__quote@
@ -395,12 +395,15 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/polyblob.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/polyvert.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/poutline.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/publictypes.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quadlsq.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quadratc.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/quspline.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ratngs.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rect.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rejctmap.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/seam.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/split.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/statistc.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stepblob.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/vecfuncs.Plo@am__quote@

View File

@ -18,17 +18,45 @@
**********************************************************************/
#include "mfcpch.h"
#include "blobbox.h"
#include "blobbox.h"
#include "helpers.h"
#define PROJECTION_MARGIN 10 //arbitrary
#define EXTERN
EXTERN double_VAR (textord_error_weight, 3,
"Weighting for error in believability");
EXTERN BOOL_VAR (pitsync_projection_fix, TRUE,
"Fix bug in projection profile");
ELISTIZE (BLOBNBOX) ELIST2IZE (TO_ROW) ELISTIZE (TO_BLOCK)
// Upto 30 degrees is allowed for rotations of diacritic blobs.
const double kCosSmallAngle = 0.866;
// Min aspect ratio for a joined word to indicate an obvious flow direction.
const double kDefiniteAspectRatio = 2.0;
// Multiple of short length in perimeter to make a joined word.
const double kComplexShapePerimeterRatio = 1.5;
void BLOBNBOX::rotate(FCOORD rotation) {
cblob_ptr->rotate(rotation);
rotate_box(rotation);
compute_bounding_box();
}
// Rotate the box by the angle given by rotation.
// If the blob is a diacritic, then only small rotations for skew
// correction can be applied.
void BLOBNBOX::rotate_box(FCOORD rotation) {
if (IsDiacritic()) {
ASSERT_HOST(rotation.x() >= kCosSmallAngle)
ICOORD top_pt((box.left() + box.right()) / 2, base_char_top_);
ICOORD bottom_pt(top_pt.x(), base_char_bottom_);
top_pt.rotate(rotation);
base_char_top_ = top_pt.y();
bottom_pt.rotate(rotation);
base_char_bottom_ = bottom_pt.y();
box.rotate(rotation);
} else {
box.rotate(rotation);
set_diacritic_box(box);
}
}
/**********************************************************************
* BLOBNBOX::merge
*
@ -38,10 +66,22 @@ void BLOBNBOX::merge( //merge blobs
BLOBNBOX *nextblob //blob to join with
) {
box += nextblob->box; //merge boxes
set_diacritic_box(box);
nextblob->joined = TRUE;
}
// Merge this with other, taking the outlines from other.
// Other is not deleted, but left for the caller to handle.
void BLOBNBOX::really_merge(BLOBNBOX* other) {
if (cblob_ptr != NULL && other->cblob_ptr != NULL) {
C_OUTLINE_IT ol_it(cblob_ptr->out_list());
ol_it.add_list_after(other->cblob_ptr->out_list());
}
compute_bounding_box();
}
/**********************************************************************
* BLOBNBOX::chop
*
@ -88,10 +128,7 @@ void BLOBNBOX::chop( //chop blobs
rightx,
/*rotation, */ test_ymin, test_ymax);
blob_it.forward ();
if (test_ymin < ymin)
ymin = test_ymin;
if (test_ymax > ymax)
ymax = test_ymax;
UpdateRange(test_ymin, test_ymax, &ymin, &ymax);
}
while (blob != end_it->data ());
if (ymin < ymax) {
@ -107,6 +144,8 @@ void BLOBNBOX::chop( //chop blobs
//box is all it has
newblob->box = TBOX (bl, tr);
//stay on current
newblob->base_char_top_ = tr.y();
newblob->base_char_bottom_ = bl.y();
end_it->add_after_stay_put (newblob);
}
}
@ -114,6 +153,201 @@ void BLOBNBOX::chop( //chop blobs
}
}
// Returns the box gaps between this and its neighbours_ in an array
// indexed by BlobNeighbourDir.
void BLOBNBOX::NeighbourGaps(int gaps[BND_COUNT]) const {
for (int dir = 0; dir < BND_COUNT; ++dir) {
gaps[dir] = MAX_INT16;
BLOBNBOX* neighbour = neighbours_[dir];
if (neighbour != NULL) {
TBOX n_box = neighbour->bounding_box();
if (dir == BND_LEFT || dir == BND_RIGHT) {
gaps[dir] = box.x_gap(n_box);
} else {
gaps[dir] = box.y_gap(n_box);
}
}
}
}
// Returns the min and max horizontal and vertical gaps (from NeighbourGaps)
// modified so that if the max exceeds the max dimension of the blob, and
// the min is less, the max is replaced with the min.
// The objective is to catch cases where there is only a single neighbour
// and avoid reporting the other gap as a ridiculously large number
void BLOBNBOX::MinMaxGapsClipped(int* h_min, int* h_max,
int* v_min, int* v_max) const {
int max_dimension = MAX(box.width(), box.height());
int gaps[BND_COUNT];
NeighbourGaps(gaps);
*h_min = MIN(gaps[BND_LEFT], gaps[BND_RIGHT]);
*h_max = MAX(gaps[BND_LEFT], gaps[BND_RIGHT]);
if (*h_max > max_dimension && *h_min < max_dimension) *h_max = *h_min;
*v_min = MIN(gaps[BND_ABOVE], gaps[BND_BELOW]);
*v_max = MAX(gaps[BND_ABOVE], gaps[BND_BELOW]);
if (*v_max > max_dimension && *v_min < max_dimension) *v_max = *v_min;
}
// Returns positive if there is at least one side neighbour that has a similar
// stroke width and is not on the other side of a rule line.
int BLOBNBOX::GoodTextBlob() const {
int score = 0;
for (int dir = 0; dir < BND_COUNT; ++dir) {
BlobNeighbourDir bnd = static_cast<BlobNeighbourDir>(dir);
if (good_stroke_neighbour(bnd))
++score;
}
return score;
}
// Returns true, and sets vert_possible/horz_possible if the blob has some
// feature that makes it individually appear to flow one way.
// eg if it has a high aspect ratio, yet has a complex shape, such as a
// joined word in Latin, Arabic, or Hindi, rather than being a -, I, l, 1 etc.
bool BLOBNBOX::DefiniteIndividualFlow() {
int box_perimeter = 2 * (box.height() + box.width());
if (box.width() > box.height() * kDefiniteAspectRatio) {
// Attempt to distinguish a wide joined word from a dash.
// If it is a dash, then its perimeter is approximately
// 2 * (box width + stroke width), but more if the outline is noisy,
// so perimeter - 2*(box width + stroke width) should be close to zero.
// A complex shape such as a joined word should have a much larger value.
int perimeter = cblob()->perimeter();
if (vert_stroke_width() > 0)
perimeter -= 2 * vert_stroke_width();
else
perimeter -= 4 * cblob()->area() / perimeter;
perimeter -= 2 * box.width();
// Use a multiple of the box perimeter as a threshold.
if (perimeter > kComplexShapePerimeterRatio * box_perimeter) {
set_vert_possible(false);
set_horz_possible(true);
return true;
}
}
if (box.height() > box.width() * kDefiniteAspectRatio) {
// As above, but for a putative vertical word vs a I/1/l.
int perimeter = cblob()->perimeter();
if (horz_stroke_width() > 0)
perimeter -= 2 * horz_stroke_width();
else
perimeter -= 4 * cblob()->area() / perimeter;
perimeter -= 2 * box.height();
if (perimeter > kComplexShapePerimeterRatio * box_perimeter) {
set_vert_possible(true);
set_horz_possible(false);
return true;
}
}
return false;
}
// Returns true if there is no tabstop violation in merging this and other.
bool BLOBNBOX::ConfirmNoTabViolation(const BLOBNBOX& other) const {
if (box.left() < other.box.left() && box.left() < other.left_rule_)
return false;
if (other.box.left() < box.left() && other.box.left() < left_rule_)
return false;
if (box.right() > other.box.right() && box.right() > other.right_rule_)
return false;
if (other.box.right() > box.right() && other.box.right() > right_rule_)
return false;
return true;
}
// Returns true if other has a similar stroke width to this.
bool BLOBNBOX::MatchingStrokeWidth(const BLOBNBOX& other,
double fractional_tolerance,
double constant_tolerance) const {
// The perimeter-based width is used as a backup in case there is
// no information in the blob.
double p_width = area_stroke_width();
double n_p_width = other.area_stroke_width();
float h_tolerance = horz_stroke_width_ * fractional_tolerance
+ constant_tolerance;
float v_tolerance = vert_stroke_width_ * fractional_tolerance
+ constant_tolerance;
double p_tolerance = p_width * fractional_tolerance
+ constant_tolerance;
bool h_zero = horz_stroke_width_ == 0.0f || other.horz_stroke_width_ == 0.0f;
bool v_zero = vert_stroke_width_ == 0.0f || other.vert_stroke_width_ == 0.0f;
bool h_ok = !h_zero && NearlyEqual(horz_stroke_width_,
other.horz_stroke_width_, h_tolerance);
bool v_ok = !v_zero && NearlyEqual(vert_stroke_width_,
other.vert_stroke_width_, v_tolerance);
bool p_ok = h_zero && v_zero && NearlyEqual(p_width, n_p_width, p_tolerance);
// For a match, at least one of the horizontal and vertical widths
// must match, and the other one must either match or be zero.
// Only if both are zero will we look at the perimeter metric.
return p_ok || ((v_ok || h_ok) && (h_ok || h_zero) && (v_ok || v_zero));
}
// Returns a bounding box of the outline contained within the
// given horizontal range.
TBOX BLOBNBOX::BoundsWithinLimits(int left, int right) {
FCOORD no_rotation(1.0f, 0.0f);
float top, bottom;
if (cblob_ptr != NULL) {
find_cblob_limits(cblob_ptr, static_cast<float>(left),
static_cast<float>(right), no_rotation,
bottom, top);
} else {
find_blob_limits(blob_ptr, static_cast<float>(left),
static_cast<float>(right), no_rotation,
bottom, top);
}
if (top < bottom) {
top = box.top();
bottom = box.bottom();
}
FCOORD bot_left(left, bottom);
FCOORD top_right(right, top);
TBOX shrunken_box(bot_left);
TBOX shrunken_box2(top_right);
shrunken_box += shrunken_box2;
return shrunken_box;
}
#ifndef GRAPHICS_DISABLED
ScrollView::Color BLOBNBOX::TextlineColor(BlobRegionType region_type,
BlobTextFlowType flow_type) {
switch (region_type) {
case BRT_HLINE:
return ScrollView::BROWN;
case BRT_VLINE:
return ScrollView::DARK_GREEN;
case BRT_RECTIMAGE:
return ScrollView::RED;
case BRT_POLYIMAGE:
return ScrollView::ORANGE;
case BRT_UNKNOWN:
return flow_type == BTFT_NONTEXT ? ScrollView::CYAN : ScrollView::WHITE;
case BRT_VERT_TEXT:
if (flow_type == BTFT_STRONG_CHAIN || flow_type == BTFT_TEXT_ON_IMAGE)
return ScrollView::GREEN;
if (flow_type == BTFT_CHAIN)
return ScrollView::LIME_GREEN;
return ScrollView::YELLOW;
case BRT_TEXT:
if (flow_type == BTFT_STRONG_CHAIN)
return ScrollView::BLUE;
if (flow_type == BTFT_TEXT_ON_IMAGE)
return ScrollView::LIGHT_BLUE;
if (flow_type == BTFT_CHAIN)
return ScrollView::MEDIUM_BLUE;
if (flow_type == BTFT_LEADER)
return ScrollView::WHEAT;
return ScrollView::MAGENTA;
default:
return ScrollView::GREY;
}
}
// Keep in sync with BlobRegionType.
ScrollView::Color BLOBNBOX::BoxColor() const {
return TextlineColor(region_type_, flow_);
}
#endif
/**********************************************************************
* find_blob_limits
@ -152,26 +386,15 @@ void find_blob_limits( //get y limits
if ((pos.x () < leftx && pos.x () + vec.x () > leftx)
|| (pos.x () > leftx && pos.x () + vec.x () < leftx)) {
testy = pos.y () + vec.y () * (leftx - pos.x ()) / vec.x ();
//intercept of boundary
if (testy < ymin)
ymin = testy;
if (testy > ymax)
ymax = testy;
UpdateRange(testy, &ymin, &ymax);
}
if (pos.x () >= leftx && pos.x () <= rightx) {
if (pos.y () > ymax)
ymax = pos.y ();
if (pos.y () < ymin)
ymin = pos.y ();
UpdateRange(pos.y(), &ymin, &ymax);
}
if ((pos.x () > rightx && pos.x () + vec.x () < rightx)
|| (pos.x () < rightx && pos.x () + vec.x () > rightx)) {
testy = pos.y () + vec.y () * (rightx - pos.x ()) / vec.x ();
//intercept of boundary
if (testy < ymin)
ymin = testy;
if (testy > ymax)
ymax = testy;
UpdateRange(testy, &ymin, &ymax);
}
}
}
@ -208,10 +431,7 @@ void find_cblob_limits( //get y limits
for (stepindex = 0; stepindex < outline->pathlength (); stepindex++) {
//inside
if (pos.x () >= leftx && pos.x () <= rightx) {
if (pos.y () > ymax)
ymax = pos.y ();
if (pos.y () < ymin)
ymin = pos.y ();
UpdateRange(pos.y(), &ymin, &ymax);
}
vec = outline->step (stepindex);
vec.rotate (rotation);
@ -249,10 +469,7 @@ void find_cblob_vlimits( //get y limits
for (stepindex = 0; stepindex < outline->pathlength (); stepindex++) {
//inside
if (pos.x () >= leftx && pos.x () <= rightx) {
if (pos.y () > ymax)
ymax = pos.y ();
if (pos.y () < ymin)
ymin = pos.y ();
UpdateRange(pos.y(), &ymin, &ymax);
}
vec = outline->step (stepindex);
pos += vec; //move to next
@ -289,10 +506,7 @@ void find_cblob_hlimits( //get x limits
for (stepindex = 0; stepindex < outline->pathlength (); stepindex++) {
//inside
if (pos.y () >= bottomy && pos.y () <= topy) {
if (pos.x () > xmax)
xmax = pos.x ();
if (pos.x () < xmin)
xmin = pos.x ();
UpdateRange(pos.x(), &xmin, &xmax);
}
vec = outline->step (stepindex);
pos += vec; //move to next
@ -351,7 +565,7 @@ PBLOB *rotate_cblob( //rotate it
OUTLINE_IT out_it;
POLYPT_IT poly_it; //outline pts
copy = new PBLOB (blob, xheight);
copy = new PBLOB (blob);
out_it.set_to_list (copy->out_list ());
for (out_it.mark_cycle_pt (); !out_it.cycled_list (); out_it.forward ()) {
//get points
@ -458,7 +672,12 @@ BLOBNBOX * blob, //first blob
float top, //corrected top
float bottom, //of row
float row_size //ideal
): y_min(bottom), y_max(top), initial_y_min(bottom), num_repeated_sets_(-1) {
) {
clear();
y_min = bottom;
y_max = top;
initial_y_min = bottom;
float diff; //in size
BLOBNBOX_IT it = &blobs; //list of blobs
@ -572,6 +791,46 @@ void TO_ROW::compute_vertical_projection() { //project whole row
}
/**********************************************************************
* TO_ROW::clear
*
* Zero out all scalar members.
**********************************************************************/
void TO_ROW::clear() {
all_caps = 0;
used_dm_model = 0;
projection_left = 0;
projection_right = 0;
pitch_decision = PITCH_DUNNO;
fixed_pitch = 0.0;
fp_space = 0.0;
fp_nonsp = 0.0;
pr_space = 0.0;
pr_nonsp = 0.0;
spacing = 0.0;
xheight = 0.0;
xheight_evidence = 0;
ascrise = 0.0;
descdrop = 0.0;
min_space = 0;
max_nonspace = 0;
space_threshold = 0;
kern_size = 0.0;
space_size = 0.0;
y_min = 0.0;
y_max = 0.0;
initial_y_min = 0.0;
m = 0.0;
c = 0.0;
error = 0.0;
para_c = 0.0;
para_error = 0.0;
y_origin = 0.0;
credibility = 0.0;
num_repeated_sets_ = -1;
}
/**********************************************************************
* vertical_blob_projection
*
@ -722,16 +981,9 @@ void vertical_coutline_projection( //project outlines
for (stepindex = 0; stepindex < length; stepindex++) {
step = outline->step (stepindex);
if (step.x () > 0) {
if (pitsync_projection_fix)
stats->add (pos.x (), -pos.y ());
else
stats->add (pos.x (), pos.y ());
}
else if (step.x () < 0) {
if (pitsync_projection_fix)
stats->add (pos.x () - 1, pos.y ());
else
stats->add (pos.x () - 1, -pos.y ());
stats->add (pos.x (), -pos.y ());
} else if (step.x () < 0) {
stats->add (pos.x () - 1, pos.y ());
}
pos += step;
}
@ -751,6 +1003,7 @@ void vertical_coutline_projection( //project outlines
TO_BLOCK::TO_BLOCK( //make a block
BLOCK *src_block //real block
) {
clear();
block = src_block;
}
@ -767,6 +1020,32 @@ static void clear_blobnboxes(BLOBNBOX_LIST* boxes) {
}
}
/**********************************************************************
* TO_BLOCK::clear
*
* Zero out all scalar members.
**********************************************************************/
void TO_BLOCK::clear() {
block = NULL;
pitch_decision = PITCH_DUNNO;
line_spacing = 0.0;
line_size = 0.0;
max_blob_size = 0.0;
baseline_offset = 0.0;
xheight = 0.0;
fixed_pitch = 0.0;
kern_size = 0.0;
space_size = 0.0;
min_space = 0;
max_nonspace = 0;
fp_space = 0.0;
fp_nonsp = 0.0;
pr_space = 0.0;
pr_nonsp = 0.0;
key_row = NULL;
}
TO_BLOCK::~TO_BLOCK() {
// Any residual BLOBNBOXes at this stage own their blobs, so delete them.
clear_blobnboxes(&blobs);
@ -802,6 +1081,4 @@ void plot_blob_list(ScrollView* win, // window to draw in
it.data()->plot(win, body_colour, child_colour);
}
}
#endif //GRAPHICS_DISABLED

View File

@ -20,16 +20,12 @@
#ifndef BLOBBOX_H
#define BLOBBOX_H
#include "varable.h"
#include "clst.h"
#include "elst2.h"
#include "werd.h"
#include "ocrblock.h"
#include "statistc.h"
extern double_VAR_H (textord_error_weight, 3,
"Weighting for error in believability");
enum PITCH_TYPE
{
PITCH_DUNNO, //insufficient data
@ -53,10 +49,12 @@ enum TabType {
// The possible region types of a BLOBNBOX.
// Note: keep all the text types > BRT_UNKNOWN and all the image types less.
// Keep in sync with kBlobTypes in colpartition.cpp and BoxColor below.
// Keep in sync with kBlobTypes in colpartition.cpp and BoxColor, and the
// *Type static functions below.
enum BlobRegionType {
BRT_NOISE, // Neither text nor image.
BRT_HLINE, // Horizontal separator line.
BRT_VLINE, // Vertical separator line.
BRT_RECTIMAGE, // Rectangular image.
BRT_POLYIMAGE, // Non-rectangular image.
BRT_UNKNOWN, // Not determined yet.
@ -66,6 +64,46 @@ enum BlobRegionType {
BRT_COUNT // Number of possibilities.
};
// enum for elements of arrays that refer to neighbours.
enum BlobNeighbourDir {
BND_LEFT,
BND_BELOW,
BND_RIGHT,
BND_ABOVE,
BND_COUNT
};
// BlobTextFlowType indicates the quality of neighbouring information
// related to a chain of connected components, either horizontally or
// vertically. Also used by ColPartition for the collection of blobs
// within, which should all have the same value in most cases.
enum BlobTextFlowType {
BTFT_NONE, // No text flow set yet.
BTFT_NONTEXT, // Flow too poor to be likely text.
BTFT_NEIGHBOURS, // Neighbours support flow in this direction.
BTFT_CHAIN, // There is a weak chain of text in this direction.
BTFT_STRONG_CHAIN, // There is a strong chain of text in this direction.
BTFT_TEXT_ON_IMAGE, // There is a strong chain of text on an image.
BTFT_LEADER, // Leader dots/dashes etc.
BTFT_COUNT
};
// Returns true if type1 dominates type2 in a merge. Mostly determined by the
// ordering of the enum, but NONTEXT dominates everything else, and LEADER
// dominates nothing.
// The function is anti-symmetric (t1 > t2) === !(t2 > t1), except that
// this cannot be true if t1 == t2, so the result is undefined.
inline bool DominatesInMerge(BlobTextFlowType type1, BlobTextFlowType type2) {
// NONTEXT dominates everything.
if (type1 == BTFT_NONTEXT) return true;
if (type2 == BTFT_NONTEXT) return false;
// LEADER always loses.
if (type1 == BTFT_LEADER) return false;
if (type2 == BTFT_LEADER) return true;
// With those out of the way, the ordering of the enum determines the result.
return type1 >= type2;
}
namespace tesseract {
class ColPartition;
}
@ -76,46 +114,84 @@ class BLOBNBOX:public ELIST_LINK
{
public:
BLOBNBOX() {
blob_ptr = NULL;
cblob_ptr = NULL;
area = 0;
Init();
ConstructionInit();
}
explicit BLOBNBOX(PBLOB *srcblob) {
box = srcblob->bounding_box();
ConstructionInit();
blob_ptr = srcblob;
cblob_ptr = NULL;
box = srcblob->bounding_box ();
area = (int) srcblob->area ();
Init();
area = static_cast<int>(srcblob->area());
}
explicit BLOBNBOX(C_BLOB *srcblob) {
blob_ptr = NULL;
box = srcblob->bounding_box();
ConstructionInit();
cblob_ptr = srcblob;
box = srcblob->bounding_box ();
area = (int) srcblob->area ();
Init();
area = static_cast<int>(srcblob->area());
}
static BLOBNBOX* RealBlob(C_OUTLINE* outline) {
C_BLOB* blob = new C_BLOB(outline);
return new BLOBNBOX(blob);
}
void rotate_box(FCOORD vec) {
box.rotate(vec);
}
void rotate_box(FCOORD rotation);
void rotate(FCOORD rotation);
void translate_box(ICOORD v) {
box.move(v);
if (IsDiacritic()) {
box.move(v);
base_char_top_ += v.y();
base_char_bottom_ += v.y();
} else {
box.move(v);
set_diacritic_box(box);
}
}
void merge(BLOBNBOX *nextblob);
void really_merge(BLOBNBOX* other);
void chop( // fake chop blob
BLOBNBOX_IT *start_it, // location of this
BLOBNBOX_IT *blob_it, // iterator
FCOORD rotation, // for landscape
float xheight); // line height
void NeighbourGaps(int gaps[BND_COUNT]) const;
void MinMaxGapsClipped(int* h_min, int* h_max,
int* v_min, int* v_max) const;
int GoodTextBlob() const;
// Returns true, and sets vert_possible/horz_possible if the blob has some
// feature that makes it individually appear to flow one way.
// eg if it has a high aspect ratio, yet has a complex shape, such as a
// joined word in Latin, Arabic, or Hindi, rather than being a -, I, l, 1.
bool DefiniteIndividualFlow();
// Returns true if there is no tabstop violation in merging this and other.
bool ConfirmNoTabViolation(const BLOBNBOX& other) const;
// Returns true if other has a similar stroke width to this.
bool MatchingStrokeWidth(const BLOBNBOX& other,
double fractional_tolerance,
double constant_tolerance) const;
// Returns a bounding box of the outline contained within the
// given horizontal range.
TBOX BoundsWithinLimits(int left, int right);
// Simple accessors.
const TBOX& bounding_box() const {
return box;
}
// Set the bounding box. Use with caution.
// Normally use compute_bounding_box instead.
void set_bounding_box(const TBOX& new_box) {
box = new_box;
base_char_top_ = box.top();
base_char_bottom_ = box.bottom();
}
void compute_bounding_box() {
box = cblob_ptr != NULL ? cblob_ptr->bounding_box()
: blob_ptr->bounding_box();
base_char_top_ = box.top();
base_char_bottom_ = box.bottom();
}
const TBOX& reduced_box() const {
return red_box;
@ -163,6 +239,24 @@ class BLOBNBOX:public ELIST_LINK
void set_region_type(BlobRegionType new_type) {
region_type_ = new_type;
}
BlobTextFlowType flow() const {
return flow_;
}
void set_flow(BlobTextFlowType value) {
flow_ = value;
}
bool vert_possible() const {
return vert_possible_;
}
void set_vert_possible(bool value) {
vert_possible_ = value;
}
bool horz_possible() const {
return horz_possible_;
}
void set_horz_possible(bool value) {
horz_possible_ = value;
}
int left_rule() const {
return left_rule_;
}
@ -199,40 +293,80 @@ class BLOBNBOX:public ELIST_LINK
void set_vert_stroke_width(float width) {
vert_stroke_width_ = width;
}
float area_stroke_width() const {
return area_stroke_width_;
}
tesseract::ColPartition* owner() const {
return owner_;
}
void set_owner(tesseract::ColPartition* new_owner) {
owner_ = new_owner;
}
void set_noise_flag(bool flag) {
noise_flag_ = flag;
bool leader_on_left() const {
return leader_on_left_;
}
bool noise_flag() const {
return noise_flag_;
void set_leader_on_left(bool flag) {
leader_on_left_ = flag;
}
bool leader_on_right() const {
return leader_on_right_;
}
void set_leader_on_right(bool flag) {
leader_on_right_ = flag;
}
BLOBNBOX* neighbour(BlobNeighbourDir n) const {
return neighbours_[n];
}
bool good_stroke_neighbour(BlobNeighbourDir n) const {
return good_stroke_neighbours_[n];
}
void set_neighbour(BlobNeighbourDir n, BLOBNBOX* neighbour, bool good) {
neighbours_[n] = neighbour;
good_stroke_neighbours_[n] = good;
}
bool IsDiacritic() const {
return base_char_top_ != box.top() || base_char_bottom_ != box.bottom();
}
int base_char_top() const {
return base_char_top_;
}
int base_char_bottom() const {
return base_char_bottom_;
}
void set_diacritic_box(const TBOX& diacritic_box) {
base_char_top_ = diacritic_box.top();
base_char_bottom_ = diacritic_box.bottom();
}
bool UniquelyVertical() const {
return vert_possible_ && !horz_possible_;
}
bool UniquelyHorizontal() const {
return horz_possible_ && !vert_possible_;
}
// Returns true if the region type is text.
static bool IsTextType(BlobRegionType type) {
return type == BRT_TEXT || type == BRT_VERT_TEXT;
}
// Returns true if the region type is image.
static bool IsImageType(BlobRegionType type) {
return type == BRT_RECTIMAGE || type == BRT_POLYIMAGE;
}
// Returns true if the region type is line.
static bool IsLineType(BlobRegionType type) {
return type == BRT_HLINE || type == BRT_VLINE;
}
// Returns true if the region type cannot be merged.
static bool UnMergeableType(BlobRegionType type) {
return IsLineType(type) || IsImageType(type);
}
static ScrollView::Color TextlineColor(BlobRegionType region_type,
BlobTextFlowType flow_type);
#ifndef GRAPHICS_DISABLED
// Keep in sync with BlobRegionType.
ScrollView::Color BoxColor() const {
switch (region_type_) {
case BRT_HLINE:
return ScrollView::YELLOW;
case BRT_RECTIMAGE:
return ScrollView::RED;
case BRT_POLYIMAGE:
return ScrollView::ORANGE;
case BRT_UNKNOWN:
return ScrollView::CYAN;
case BRT_VERT_TEXT:
return ScrollView::GREEN;
case BRT_TEXT:
return ScrollView::BLUE;
case BRT_NOISE:
default:
return ScrollView::GREY;
}
}
ScrollView::Color BoxColor() const;
void plot(ScrollView* window, // window to draw in
ScrollView::Color blob_colour, // for outer bits
@ -244,27 +378,53 @@ class BLOBNBOX:public ELIST_LINK
}
#endif
NEWDELETE2(BLOBNBOX)
NEWDELETE2(BLOBNBOX)
private:
// Initializes the bulk of the members to default values.
void Init() {
// Initializes the bulk of the members to default values for use at
// construction time.
void ConstructionInit() {
blob_ptr = NULL;
cblob_ptr = NULL;
area = 0;
area_stroke_width_ = 0.0f;
horz_stroke_width_ = 0.0f;
vert_stroke_width_ = 0.0f;
ReInit();
}
// Initializes members set by StrokeWidth and beyond, without discarding
// stored area and strokewidth values, which are expensive to calculate.
void ReInit() {
joined = false;
reduced = false;
repeated_set_ = 0;
left_tab_type_ = TT_NONE;
right_tab_type_ = TT_NONE;
region_type_ = BRT_UNKNOWN;
flow_ = BTFT_NONE;
left_rule_ = 0;
right_rule_ = 0;
left_crossing_rule_ = 0;
right_crossing_rule_ = 0;
horz_stroke_width_ = 0.0f;
vert_stroke_width_ = 0.0f;
if (area_stroke_width_ == 0.0f && area > 0 && cblob() != NULL)
area_stroke_width_ = 2.0f * area / cblob()->perimeter();
owner_ = NULL;
noise_flag_ = false;
base_char_top_ = box.top();
base_char_bottom_ = box.bottom();
horz_possible_ = false;
vert_possible_ = false;
leader_on_left_ = false;
leader_on_right_ = false;
ClearNeighbours();
}
void ClearNeighbours() {
for (int n = 0; n < BND_COUNT; ++n) {
neighbours_[n] = NULL;
good_stroke_neighbours_[n] = false;
}
}
private:
PBLOB *blob_ptr; // poly blob
C_BLOB *cblob_ptr; // edgestep blob
TBOX box; // bounding box
@ -276,22 +436,32 @@ class BLOBNBOX:public ELIST_LINK
TabType left_tab_type_; // Indicates tab-stop assessment
TabType right_tab_type_; // Indicates tab-stop assessment
BlobRegionType region_type_; // Type of region this blob belongs to
BlobTextFlowType flow_; // Quality of text flow.
inT16 left_rule_; // x-coord of nearest but not crossing rule line
inT16 right_rule_; // x-coord of nearest but not crossing rule line
inT16 left_crossing_rule_; // x-coord of nearest or crossing rule line
inT16 right_crossing_rule_; // x-coord of nearest or crossing rule line
inT16 base_char_top_; // y-coord of top/bottom of diacritic base,
inT16 base_char_bottom_; // if it exists else top/bottom of this blob.
float horz_stroke_width_; // Median horizontal stroke width
float vert_stroke_width_; // Median vertical stroke width
float area_stroke_width_; // Stroke width from area/perimeter ratio.
tesseract::ColPartition* owner_; // Who will delete me when I am not needed
// Was the blob flagged as noise in the initial filtering step
bool noise_flag_;
BLOBNBOX* neighbours_[BND_COUNT];
bool good_stroke_neighbours_[BND_COUNT];
bool horz_possible_; // Could be part of horizontal flow.
bool vert_possible_; // Could be part of vertical flow.
bool leader_on_left_; // There is a leader to the left.
bool leader_on_right_; // There is a leader to the right.
};
class TO_ROW:public ELIST2_LINK
class TO_ROW: public ELIST2_LINK
{
public:
static const int kErrorWeight = 3;
TO_ROW() {
num_repeated_sets_ = -1;
clear();
} //empty
TO_ROW( //constructor
BLOBNBOX *blob, //from first blob
@ -359,7 +529,7 @@ class TO_ROW:public ELIST2_LINK
para_c = new_c;
para_error = new_error;
credibility =
(float) (blobs.length () - textord_error_weight * new_error);
(float) (blobs.length () - kErrorWeight * new_error);
y_origin = (float) (new_c / sqrt (1 + gradient * gradient));
//real intercept
}
@ -413,6 +583,8 @@ class TO_ROW:public ELIST2_LINK
STATS projection; // vertical projection
private:
void clear(); // clear all values to reasonable defaults
BLOBNBOX_LIST blobs; //blobs in row
float y_min; //coords
float y_max;
@ -432,16 +604,45 @@ ELIST2IZEH (TO_ROW)
class TO_BLOCK:public ELIST_LINK
{
public:
TO_BLOCK() {
TO_BLOCK() : pitch_decision(PITCH_DUNNO) {
clear();
} //empty
TO_BLOCK( //constructor
BLOCK *src_block); //real block
~TO_BLOCK();
void clear(); // clear all scalar members.
TO_ROW_LIST *get_rows() { //access function
return &row_list;
}
// Rotate all the blobnbox lists and the underlying block. Then update the
// median size statistic from the blobs list.
void rotate(const FCOORD& rotation) {
BLOBNBOX_LIST* blobnbox_list[] = {&blobs, &underlines, &noise_blobs,
&small_blobs, &large_blobs, NULL};
for (BLOBNBOX_LIST** list = blobnbox_list; *list != NULL; ++list) {
BLOBNBOX_IT it(*list);
for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
it.data()->rotate(rotation);
}
}
// Rotate the block
ASSERT_HOST(block->poly_block() != NULL);
block->rotate(rotation);
// Update the median size statistic from the blobs list.
STATS widths(0, block->bounding_box().width());
STATS heights(0, block->bounding_box().height());
BLOBNBOX_IT blob_it(&blobs);
for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
widths.add(blob_it.data()->bounding_box().width(), 1);
heights.add(blob_it.data()->bounding_box().height(), 1);
}
block->set_median_size(static_cast<int>(widths.median() + 0.5),
static_cast<int>(heights.median() + 0.5));
}
void print_rows() { //debug info
TO_ROW_IT row_it = &row_list;
TO_ROW *row;
@ -468,6 +669,11 @@ class TO_BLOCK:public ELIST_LINK
BLOCK *block; //real block
PITCH_TYPE pitch_decision; //how strong is decision
float line_spacing; //estimate
// line_size is a lower-bound estimate of the font size in pixels of
// the text in the block (with ascenders and descenders), being a small
// (1.25) multiple of the median height of filtered blobs.
// In most cases the font size will be bigger, but it will be closer
// if the text is allcaps, or in a no-x-height script.
float line_size; //estimate
float max_blob_size; //line assignment limit
float baseline_offset; //phase shift

View File

@ -30,11 +30,434 @@
#include "blobs.h"
#include "cutil.h"
#include "emalloc.h"
#include "helpers.h"
#include "ndminx.h"
#include "normalis.h"
#include "ocrrow.h"
#include "points.h"
#include "polyaprx.h"
#include "structures.h"
#include "werd.h"
// A Vector representing the "vertical" direction when measuring the
// divisiblity of blobs into multiple blobs just by separating outlines.
// See divisible_blob below for the use.
const TPOINT kDivisibleVerticalUpright = {0, 1};
// A vector representing the "vertical" direction for italic text for use
// when separating outlines. Using it actually deteriorates final accuracy,
// so it is only used for ApplyBoxes chopping to get a better segmentation.
const TPOINT kDivisibleVerticalItalic = {1, 5};
/*----------------------------------------------------------------------
F u n c t i o n s
----------------------------------------------------------------------*/
// Consume the circular list of EDGEPTs to make a TESSLINE.
TESSLINE* TESSLINE::BuildFromOutlineList(EDGEPT* outline) {
TESSLINE* result = new TESSLINE;
result->loop = outline;
result->SetupFromPos();
return result;
}
// Copies the data and the outline, but leaves next untouched.
void TESSLINE::CopyFrom(const TESSLINE& src) {
Clear();
topleft = src.topleft;
botright = src.botright;
start = src.start;
is_hole = src.is_hole;
if (src.loop != NULL) {
EDGEPT* prevpt = NULL;
EDGEPT* newpt = NULL;
EDGEPT* srcpt = src.loop;
do {
newpt = new EDGEPT(*srcpt);
if (prevpt == NULL) {
loop = newpt;
} else {
newpt->prev = prevpt;
prevpt->next = newpt;
}
prevpt = newpt;
srcpt = srcpt->next;
} while (srcpt != src.loop);
loop->prev = newpt;
newpt->next = loop;
}
}
// Deletes owned data.
void TESSLINE::Clear() {
if (loop == NULL)
return;
EDGEPT* this_edge = loop;
do {
EDGEPT* next_edge = this_edge->next;
delete this_edge;
this_edge = next_edge;
} while (this_edge != loop);
loop = NULL;
}
// Rotates by the given rotation in place.
void TESSLINE::Rotate(const FCOORD rot) {
EDGEPT* pt = loop;
do {
int tmp = static_cast<int>(floor(pt->pos.x * rot.x() -
pt->pos.y * rot.y() + 0.5));
pt->pos.y = static_cast<int>(floor(pt->pos.y * rot.x() +
pt->pos.x * rot.y() + 0.5));
pt->pos.x = tmp;
pt = pt->next;
} while (pt != loop);
SetupFromPos();
}
// Moves by the given vec in place.
void TESSLINE::Move(const ICOORD vec) {
EDGEPT* pt = loop;
do {
pt->pos.x += vec.x();
pt->pos.y += vec.y();
pt = pt->next;
} while (pt != loop);
SetupFromPos();
}
// Scales by the given factor in place.
void TESSLINE::Scale(float factor) {
EDGEPT* pt = loop;
do {
pt->pos.x = static_cast<int>(floor(pt->pos.x * factor + 0.5));
pt->pos.y = static_cast<int>(floor(pt->pos.y * factor + 0.5));
pt = pt->next;
} while (pt != loop);
SetupFromPos();
}
// Sets up the start and vec members of the loop from the pos members.
void TESSLINE::SetupFromPos() {
EDGEPT* pt = loop;
do {
pt->vec.x = pt->next->pos.x - pt->pos.x;
pt->vec.y = pt->next->pos.y - pt->pos.y;
pt = pt->next;
} while (pt != loop);
start = pt->pos;
ComputeBoundingBox();
}
// Recomputes the bounding box from the points in the loop.
void TESSLINE::ComputeBoundingBox() {
int minx = MAX_INT32;
int miny = MAX_INT32;
int maxx = -MAX_INT32;
int maxy = -MAX_INT32;
// Find boundaries.
start = loop->pos;
EDGEPT* this_edge = loop;
do {
if (!this_edge->IsHidden() || !this_edge->prev->IsHidden()) {
if (this_edge->pos.x < minx)
minx = this_edge->pos.x;
if (this_edge->pos.y < miny)
miny = this_edge->pos.y;
if (this_edge->pos.x > maxx)
maxx = this_edge->pos.x;
if (this_edge->pos.y > maxy)
maxy = this_edge->pos.y;
}
this_edge = this_edge->next;
} while (this_edge != loop);
// Reset bounds.
topleft.x = minx;
topleft.y = maxy;
botright.x = maxx;
botright.y = miny;
}
// Computes the min and max cross product of the outline points with the
// given vec and returns the results in min_xp and max_xp. Geometrically
// this is the left and right edge of the outline perpendicular to the
// given direction, but to get the distance units correct, you would
// have to divide by the modulus of vec.
void TESSLINE::MinMaxCrossProduct(const TPOINT vec,
int* min_xp, int* max_xp) const {
*min_xp = MAX_INT32;
*max_xp = MIN_INT32;
EDGEPT* this_edge = loop;
do {
if (!this_edge->IsHidden() || !this_edge->prev->IsHidden()) {
int product = CROSS(this_edge->pos, vec);
UpdateRange(product, min_xp, max_xp);
}
this_edge = this_edge->next;
} while (this_edge != loop);
}
TBOX TESSLINE::bounding_box() const {
return TBOX(topleft.x, botright.y, botright.x, topleft.y);
}
void TESSLINE::plot(ScrollView* window, ScrollView::Color color,
ScrollView::Color child_color) {
if (is_hole)
window->Pen(child_color);
else
window->Pen(color);
window->SetCursor(start.x, start.y);
EDGEPT* pt = loop;
do {
bool prev_hidden = pt->IsHidden();
pt = pt->next;
if (prev_hidden)
window->SetCursor(pt->pos.x, pt->pos.y);
else
window->DrawTo(pt->pos.x, pt->pos.y);
} while (pt != loop);
}
// Iterate the given list of outlines, converting to TESSLINE by polygonal
// approximation and recursively any children, returning the current tail
// of the resulting list of TESSLINEs.
static TESSLINE** ApproximateOutlineList(C_OUTLINE_LIST* outlines,
bool children,
TESSLINE** tail) {
C_OUTLINE_IT ol_it(outlines);
for (ol_it.mark_cycle_pt(); !ol_it.cycled_list(); ol_it.forward()) {
C_OUTLINE* outline = ol_it.data();
TESSLINE* tessline = ApproximateOutline(outline);
tessline->is_hole = children;
*tail = tessline;
tail = &tessline->next;
if (!outline->child()->empty()) {
tail = ApproximateOutlineList(outline->child(), true, tail);
}
}
return tail;
}
// Factory to build a TBLOB from a C_BLOB with polygonal
// approximation along the way.
TBLOB* TBLOB::PolygonalCopy(C_BLOB* src) {
C_OUTLINE_IT ol_it = src->out_list();
TBLOB* tblob = new TBLOB;
ApproximateOutlineList(src->out_list(), false, &tblob->outlines);
return tblob;
}
// Copies the data and the outline, but leaves next untouched.
void TBLOB::CopyFrom(const TBLOB& src) {
Clear();
TESSLINE* prev_outline = NULL;
for (TESSLINE* srcline = src.outlines; srcline != NULL;
srcline = srcline->next) {
TESSLINE* new_outline = new TESSLINE(*srcline);
if (outlines == NULL)
outlines = new_outline;
else
prev_outline->next = new_outline;
prev_outline = new_outline;
}
}
// Deletes owned data.
void TBLOB::Clear() {
for (TESSLINE* next_outline = NULL; outlines != NULL;
outlines = next_outline) {
next_outline = outlines->next;
delete outlines;
}
}
// Rotates by the given rotation in place.
void TBLOB::Rotate(const FCOORD rotation) {
for (TESSLINE* outline = outlines; outline != NULL; outline = outline->next) {
outline->Rotate(rotation);
}
}
// Moves by the given vec in place.
void TBLOB::Move(const ICOORD vec) {
for (TESSLINE* outline = outlines; outline != NULL; outline = outline->next) {
outline->Move(vec);
}
}
// Scales by the given factor in place.
void TBLOB::Scale(float factor) {
for (TESSLINE* outline = outlines; outline != NULL; outline = outline->next) {
outline->Scale(factor);
}
}
// Recomputes the bounding boxes of the outlines.
void TBLOB::ComputeBoundingBoxes() {
for (TESSLINE* outline = outlines; outline != NULL; outline = outline->next) {
outline->ComputeBoundingBox();
}
}
// Returns the number of outlines.
int TBLOB::NumOutlines() const {
int result = 0;
for (TESSLINE* outline = outlines; outline != NULL; outline = outline->next)
++result;
return result;
}
TBOX TBLOB::bounding_box() const {
TPOINT topleft;
TPOINT botright;
blob_bounding_box(this, &topleft, &botright);
TBOX box(topleft.x, botright.y, botright.x, topleft.y);
return box;
}
void TBLOB::plot(ScrollView* window, ScrollView::Color color,
ScrollView::Color child_color) {
for (TESSLINE* outline = outlines; outline != NULL; outline = outline->next)
outline->plot(window, color, child_color);
}
// Factory to build a TWERD from a (C_BLOB) WERD, with polygonal
// approximation along the way.
TWERD* TWERD::PolygonalCopy(WERD* src) {
TWERD* tessword = new TWERD;
tessword->latin_script = src->flag(W_SCRIPT_IS_LATIN);
C_BLOB_IT b_it(src->cblob_list());
TBLOB *tail = NULL;
for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
C_BLOB* blob = b_it.data();
TBLOB* tblob = TBLOB::PolygonalCopy(blob);
if (tail == NULL) {
tessword->blobs = tblob;
} else {
tail->next = tblob;
}
tail = tblob;
}
return tessword;
}
// Normalize in-place and record the normalization in the DENORM.
void TWERD::Normalize(ROW* row, float x_height, bool numeric_mode,
DENORM* denorm) {
TBOX word_box = bounding_box();
DENORM antidote((word_box.left() + word_box.right()) / 2.0,
kBlnXHeight / x_height, row);
if (row == NULL) {
antidote = DENORM(antidote.origin(), antidote.scale(), 0.0,
word_box.bottom(), 0, NULL, false, NULL);
}
int num_segments = 0;
DENORM_SEG *segs = new DENORM_SEG[NumBlobs()];
for (TBLOB* blob = blobs; blob != NULL; blob = blob->next) {
TBOX blob_box = blob->bounding_box();
ICOORD translation(-static_cast<int>(floor(antidote.origin() + 0.5)),
-blob_box.bottom());
float factor = antidote.scale();
if (numeric_mode) {
factor = ClipToRange(kBlnXHeight * 4.0f / (3 * blob_box.height()),
factor, factor * 1.5f);
segs[num_segments].xstart = blob->bounding_box().left();
segs[num_segments].ycoord = blob_box.bottom();
segs[num_segments++].scale_factor = factor;
} else {
float blob_x_center = (blob_box.left() + blob_box.right()) / 2.0;
float y_shift = antidote.yshift_at_orig_x(blob_x_center);
translation.set_y(-static_cast<int>(floor(y_shift + 0.5)));
}
blob->Move(translation);
blob->Scale(factor);
blob->Move(ICOORD(0, kBlnBaselineOffset));
}
if (num_segments > 0) {
antidote.set_segments(segs, num_segments);
}
delete [] segs;
if (denorm != NULL)
*denorm = antidote;
}
// Copies the data and the blobs, but leaves next untouched.
void TWERD::CopyFrom(const TWERD& src) {
Clear();
latin_script = src.latin_script;
TBLOB* prev_blob = NULL;
for (TBLOB* srcblob = src.blobs; srcblob != NULL; srcblob = srcblob->next) {
TBLOB* new_blob = new TBLOB(*srcblob);
if (blobs == NULL)
blobs = new_blob;
else
prev_blob->next = new_blob;
prev_blob = new_blob;
}
}
// Deletes owned data.
void TWERD::Clear() {
for (TBLOB* next_blob = NULL; blobs != NULL; blobs = next_blob) {
next_blob = blobs->next;
delete blobs;
}
}
// Recomputes the bounding boxes of the blobs.
void TWERD::ComputeBoundingBoxes() {
for (TBLOB* blob = blobs; blob != NULL; blob = blob->next) {
blob->ComputeBoundingBoxes();
}
}
TBOX TWERD::bounding_box() const {
TBOX result;
for (TBLOB* blob = blobs; blob != NULL; blob = blob->next) {
TBOX box = blob->bounding_box();
result += box;
}
return result;
}
// Merges the blobs from start to end, not including end, and deletes
// the blobs between start and end.
void TWERD::MergeBlobs(int start, int end) {
TBLOB* blob = blobs;
for (int i = 0; i < start && blob != NULL; ++i)
blob = blob->next;
if (blob == NULL || blob->next == NULL)
return;
TBLOB* next_blob = blob->next;
TESSLINE* outline = blob->outlines;
for (int i = start + 1; i < end && next_blob != NULL; ++i) {
// Take the outlines from the next blob.
if (outline == NULL) {
blob->outlines = next_blob->outlines;
outline = blob->outlines;
} else {
while (outline->next != NULL)
outline = outline->next;
outline->next = next_blob->outlines;
next_blob->outlines = NULL;
}
// Delete the next blob and move on.
TBLOB* dead_blob = next_blob;
next_blob = next_blob->next;
blob->next = next_blob;
delete dead_blob;
}
}
void TWERD::plot(ScrollView* window) {
ScrollView::Color color = WERD::NextColor(ScrollView::BLACK);
for (TBLOB* blob = blobs; blob != NULL; blob = blob->next) {
blob->plot(window, color, ScrollView::BROWN);
color = WERD::NextColor(color);
}
}
/**********************************************************************
* blob_origin
*
@ -61,32 +484,23 @@ void blob_origin(TBLOB *blob, /*blob to compute on */
* max coordinate value of the bounding boxes of all the top-level
* outlines in the box.
**********************************************************************/
void blob_bounding_box(TBLOB *blob, /*blob to compute on */
register TPOINT *topleft, /*bounding box */
register TPOINT *botright) {
register TESSLINE *outline; /*current outline */
void blob_bounding_box(const TBLOB *blob, // blob to compute on.
TPOINT *topleft, // bounding box.
TPOINT *botright) {
register TESSLINE *outline; // Current outline.
if (blob == NULL || blob->outlines == NULL) {
topleft->x = topleft->y = 0;
*botright = *topleft; /*default value */
}
else {
*botright = *topleft; // Default value.
} else {
outline = blob->outlines;
*topleft = outline->topleft;
*botright = outline->botright;
for (outline = outline->next; outline != NULL; outline = outline->next) {
if (outline->topleft.x < topleft->x)
/*find extremes */
topleft->x = outline->topleft.x;
if (outline->botright.x > botright->x)
/*find extremes */
botright->x = outline->botright.x;
if (outline->topleft.y > topleft->y)
/*find extremes */
topleft->y = outline->topleft.y;
if (outline->botright.y < botright->y)
/*find extremes */
botright->y = outline->botright.y;
UpdateRange(outline->topleft.x, outline->botright.x,
&topleft->x, &botright->x);
UpdateRange(outline->botright.y, outline->topleft.y,
&botright->y, &topleft->y);
}
}
}
@ -100,11 +514,10 @@ void blob_bounding_box(TBLOB *blob, /*blob to compute on */
void blobs_bounding_box(TBLOB *blobs, TPOINT *topleft, TPOINT *botright) {
TPOINT tl;
TPOINT br;
TBLOB *blob;
/* Start with first blob */
blob_bounding_box(blobs, topleft, botright);
iterate_blobs(blob, blobs) {
for (TBLOB* blob = blobs; blob != NULL; blob = blob->next) {
blob_bounding_box(blob, &tl, &br);
if (tl.x < topleft->x)
@ -148,7 +561,6 @@ WIDTH_RECORD *blobs_widths(TBLOB *blobs) { /*blob to compute on */
WIDTH_RECORD *width_record;
TPOINT topleft; /*bounding box */
TPOINT botright;
TBLOB *blob; /*blob to compute on */
int i = 0;
int blob_end;
int num_blobs = count_blobs (blobs);
@ -162,7 +574,7 @@ WIDTH_RECORD *blobs_widths(TBLOB *blobs) { /*blob to compute on */
/* First width */
blob_end = botright.x;
iterate_blobs (blob, blobs->next) {
for (TBLOB* blob = blobs->next; blob != NULL; blob = blob->next) {
blob_bounding_box(blob, &topleft, &botright);
width_record->widths[i++] = topleft.x - blob_end;
width_record->widths[i++] = botright.x - topleft.x;
@ -178,70 +590,102 @@ WIDTH_RECORD *blobs_widths(TBLOB *blobs) { /*blob to compute on */
* Return a count of the number of blobs attached to this one.
**********************************************************************/
int count_blobs(TBLOB *blobs) {
TBLOB *b;
int x = 0;
iterate_blobs (b, blobs) x++;
return (x);
for (TBLOB* b = blobs; b != NULL; b = b->next)
x++;
return x;
}
/**********************************************************************
* delete_word
* divisible_blob
*
* Reclaim the memory taken by this word structure and all of its
* lower level structures.
* Returns true if the blob contains multiple outlines than can be
* separated using divide_blobs. Sets the location to be used in the
* call to divide_blobs.
**********************************************************************/
void delete_word(TWERD *word) {
TBLOB *blob;
TBLOB *nextblob;
TESSLINE *outline;
TESSLINE *nextoutline;
TESSLINE *child;
TESSLINE *nextchild;
for (blob = word->blobs; blob; blob = nextblob) {
nextblob = blob->next;
for (outline = blob->outlines; outline; outline = nextoutline) {
nextoutline = outline->next;
delete_edgepts (outline->loop);
for (child = outline->child; child; child = nextchild) {
nextchild = child->next;
delete_edgepts (child->loop);
oldoutline(child);
bool divisible_blob(TBLOB *blob, bool italic_blob, TPOINT* location) {
if (blob->outlines == NULL || blob->outlines->next == NULL)
return false; // Need at least 2 outlines for it to be possible.
int max_gap = 0;
TPOINT vertical = italic_blob ? kDivisibleVerticalItalic
: kDivisibleVerticalUpright;
for (TESSLINE* outline1 = blob->outlines; outline1 != NULL;
outline1 = outline1->next) {
if (outline1->is_hole)
continue; // Holes do not count as separable.
TPOINT mid_pt1 = {(outline1->topleft.x + outline1->botright.x) / 2,
(outline1->topleft.y + outline1->botright.y) / 2};
int mid_prod1 = CROSS(mid_pt1, vertical);
int min_prod1, max_prod1;
outline1->MinMaxCrossProduct(vertical, &min_prod1, &max_prod1);
for (TESSLINE* outline2 = outline1->next; outline2 != NULL;
outline2 = outline2->next) {
if (outline2->is_hole)
continue; // Holes do not count as separable.
TPOINT mid_pt2 = { (outline2->topleft.x + outline2->botright.x) / 2,
(outline2->topleft.y + outline2->botright.y) / 2};
int mid_prod2 = CROSS(mid_pt2, vertical);
int min_prod2, max_prod2;
outline2->MinMaxCrossProduct(vertical, &min_prod2, &max_prod2);
int mid_gap = abs(mid_prod2 - mid_prod1);
int overlap = MIN(max_prod1, max_prod2) - MAX(min_prod1, min_prod2);
if (mid_gap - overlap / 2 > max_gap) {
max_gap = mid_gap - overlap / 2;
*location = mid_pt1;
*location += mid_pt2;
*location /= 2;
}
oldoutline(outline);
}
oldblob(blob);
}
if (word->correct != NULL)
strfree (word->correct); /* Reclaim memory */
oldword(word);
// Use the y component of the vertical vector as an approximation to its
// length.
return max_gap > vertical.y;
}
/**********************************************************************
* delete_edgepts
* divide_blobs
*
* Delete a list of EDGEPT structures.
* Create two blobs by grouping the outlines in the appropriate blob.
* The outlines that are beyond the location point are moved to the
* other blob. The ones whose x location is less than that point are
* retained in the original blob.
**********************************************************************/
void delete_edgepts(register EDGEPT *edgepts) {
register EDGEPT *this_edge;
register EDGEPT *next_edge;
void divide_blobs(TBLOB *blob, TBLOB *other_blob, bool italic_blob,
const TPOINT& location) {
TPOINT vertical = italic_blob ? kDivisibleVerticalItalic
: kDivisibleVerticalUpright;
TESSLINE *outline1 = NULL;
TESSLINE *outline2 = NULL;
if (edgepts == NULL)
return;
TESSLINE *outline = blob->outlines;
blob->outlines = NULL;
int location_prod = CROSS(location, vertical);
this_edge = edgepts;
do {
next_edge = this_edge->next;
oldedgept(this_edge);
this_edge = next_edge;
while (outline != NULL) {
TPOINT mid_pt = {(outline->topleft.x + outline->botright.x) / 2,
(outline->topleft.y + outline->botright.y) / 2};
int mid_prod = CROSS(mid_pt, vertical);
if (mid_prod < location_prod) {
// Outline is in left blob.
if (outline1)
outline1->next = outline;
else
blob->outlines = outline;
outline1 = outline;
} else {
// Outline is in right blob.
if (outline2)
outline2->next = outline;
else
other_blob->outlines = outline;
outline2 = outline;
}
outline = outline->next;
}
while (this_edge != edgepts);
if (outline1)
outline1->next = NULL;
if (outline2)
outline2->next = NULL;
}

View File

@ -29,18 +29,225 @@
/*----------------------------------------------------------------------
I n c l u d e s
----------------------------------------------------------------------*/
#include "vecfuncs.h"
#include "tessclas.h"
#include "rect.h"
#include "vecfuncs.h"
class C_BLOB;
class DENORM;
class ROW;
class WERD;
/*----------------------------------------------------------------------
T y p e s
----------------------------------------------------------------------*/
#define EDGEPTFLAGS 4 /*concavity,length etc. */
typedef struct
{ /* Widths of pieces */
int num_chars;
int widths[1];
} WIDTH_RECORD;
struct TPOINT {
void operator+=(const TPOINT& other) {
x += other.x;
y += other.y;
}
void operator/=(int divisor) {
x /= divisor;
y /= divisor;
}
inT16 x; // absolute x coord.
inT16 y; // absolute y coord.
};
typedef TPOINT VECTOR; // structure for coordinates.
struct EDGEPT {
EDGEPT() : next(NULL), prev(NULL) {
memset(flags, 0, EDGEPTFLAGS * sizeof(flags[0]));
}
EDGEPT(const EDGEPT& src) : next(NULL), prev(NULL) {
CopyFrom(src);
}
EDGEPT& operator=(const EDGEPT& src) {
CopyFrom(src);
return *this;
}
// Copies the data elements, but leaves the pointers untouched.
void CopyFrom(const EDGEPT& src) {
pos = src.pos;
vec = src.vec;
memcpy(flags, src.flags, EDGEPTFLAGS * sizeof(flags[0]));
}
// Accessors to hide or reveal a cut edge from feature extractors.
void Hide() {
flags[0] = true;
}
void Reveal() {
flags[0] = false;
}
bool IsHidden() const {
return flags[0] != 0;
}
TPOINT pos; // position
VECTOR vec; // vector to next point
// TODO(rays) Remove flags and replace with
// is_hidden, runlength, dir, and fixed. The only use
// of the flags other than is_hidden is in polyaprx.cpp.
char flags[EDGEPTFLAGS]; // concavity, length etc
EDGEPT* next; // anticlockwise element
EDGEPT* prev; // clockwise element
};
struct TESSLINE {
TESSLINE() : is_hole(false), loop(NULL), next(NULL) {}
TESSLINE(const TESSLINE& src) : loop(NULL), next(NULL) {
CopyFrom(src);
}
~TESSLINE() {
Clear();
}
TESSLINE& operator=(const TESSLINE& src) {
CopyFrom(src);
return *this;
}
// Consume the circular list of EDGEPTs to make a TESSLINE.
static TESSLINE* BuildFromOutlineList(EDGEPT* outline);
// Copies the data and the outline, but leaves next untouched.
void CopyFrom(const TESSLINE& src);
// Deletes owned data.
void Clear();
// Rotates by the given rotation in place.
void Rotate(const FCOORD rotation);
// Moves by the given vec in place.
void Move(const ICOORD vec);
// Scales by the given factor in place.
void Scale(float factor);
// Sets up the start and vec members of the loop from the pos members.
void SetupFromPos();
// Recomputes the bounding box from the points in the loop.
void ComputeBoundingBox();
// Computes the min and max cross product of the outline points with the
// given vec and returns the results in min_xp and max_xp. Geometrically
// this is the left and right edge of the outline perpendicular to the
// given direction, but to get the distance units correct, you would
// have to divide by the modulus of vec.
void MinMaxCrossProduct(const TPOINT vec, int* min_xp, int* max_xp) const;
TBOX bounding_box() const;
// Returns true if the point is contained within the outline box.
bool Contains(const TPOINT& pt) {
return topleft.x <= pt.x && pt.x <= botright.x &&
botright.y <= pt.y && pt.y <= topleft.y;
}
void plot(ScrollView* window, ScrollView::Color color,
ScrollView::Color child_color);
int BBArea() const {
return (botright.x - topleft.x) * (topleft.y - botright.y);
}
TPOINT topleft; // Top left of loop.
TPOINT botright; // Bottom right of loop.
TPOINT start; // Start of loop.
bool is_hole; // True if this is a hole/child outline.
EDGEPT *loop; // Edgeloop.
TESSLINE *next; // Next outline in blob.
}; // Outline structure.
struct TBLOB {
TBLOB() : outlines(NULL), next(NULL) {}
TBLOB(const TBLOB& src) : outlines(NULL), next(NULL) {
CopyFrom(src);
}
~TBLOB() {
Clear();
}
TBLOB& operator=(const TBLOB& src) {
CopyFrom(src);
return *this;
}
// Factory to build a TBLOB from a C_BLOB with polygonal
// approximation along the way.
static TBLOB* PolygonalCopy(C_BLOB* src);
// Copies the data and the outlines, but leaves next untouched.
void CopyFrom(const TBLOB& src);
// Deletes owned data.
void Clear();
// Rotates by the given rotation in place.
void Rotate(const FCOORD rotation);
// Moves by the given vec in place.
void Move(const ICOORD vec);
// Scales by the given factor in place.
void Scale(float factor);
// Recomputes the bounding boxes of the outlines.
void ComputeBoundingBoxes();
// Returns the number of outlines.
int NumOutlines() const;
TBOX bounding_box() const;
void plot(ScrollView* window, ScrollView::Color color,
ScrollView::Color child_color);
int BBArea() const {
int total_area = 0;
for (TESSLINE* outline = outlines; outline != NULL; outline = outline->next)
total_area += outline->BBArea();
return total_area;
}
TESSLINE *outlines; // List of outlines in blob.
TBLOB *next; // Next blob in block.
}; // Blob structure.
int count_blobs(TBLOB *blobs);
struct TWERD {
TWERD() : blobs(NULL), latin_script(false), next(NULL) {}
TWERD(const TWERD& src) : blobs(NULL), next(NULL) {
CopyFrom(src);
}
~TWERD() {
Clear();
}
TWERD& operator=(const TWERD& src) {
CopyFrom(src);
return *this;
}
// Factory to build a TWERD from a (C_BLOB) WERD, with polygonal
// approximation along the way.
static TWERD* PolygonalCopy(WERD* src);
// Normalize in-place and record the normalization in the DENORM.
void Normalize(ROW* row, float x_height, bool numeric_mode, DENORM* denorm);
// Copies the data and the blobs, but leaves next untouched.
void CopyFrom(const TWERD& src);
// Deletes owned data.
void Clear();
// Recomputes the bounding boxes of the blobs.
void ComputeBoundingBoxes();
// Returns the number of blobs in the word.
int NumBlobs() const {
return count_blobs(blobs);
}
TBOX bounding_box() const;
// Merges the blobs from start to end, not including end, and deletes
// the blobs between start and end.
void MergeBlobs(int start, int end);
void plot(ScrollView* window);
TBLOB* blobs; // blobs in word.
bool latin_script; // This word is in a latin-based script.
TWERD* next; // next word.
};
/*----------------------------------------------------------------------
M a c r o s
----------------------------------------------------------------------*/
@ -55,13 +262,17 @@ if (w) memfree (w)
/*----------------------------------------------------------------------
F u n c t i o n s
----------------------------------------------------------------------*/
// TODO(rays) This will become a member of TBLOB when TBLOB's definition
// moves to blobs.h
TBOX TBLOB_bounding_box(const TBLOB* blob);
void blob_origin(TBLOB *blob, /*blob to compute on */
TPOINT *origin); /*return value */
/*blob to compute on */
void blob_bounding_box(TBLOB *blob,
register TPOINT *topleft, /*bounding box */
register TPOINT *botright);
void blob_bounding_box(const TBLOB *blob,
TPOINT *topleft, // Bounding box.
TPOINT *botright);
void blobs_bounding_box(TBLOB *blobs, TPOINT *topleft, TPOINT *botright);
@ -71,49 +282,9 @@ void blobs_origin(TBLOB *blobs, /*blob to compute on */
/*blob to compute on */
WIDTH_RECORD *blobs_widths(TBLOB *blobs);
int count_blobs(TBLOB *blobs);
bool divisible_blob(TBLOB *blob, bool italic_blob, TPOINT* location);
void delete_word(TWERD *word);
void divide_blobs(TBLOB *blob, TBLOB *other_blob, bool italic_blob,
const TPOINT& location);
void delete_edgepts(register EDGEPT *edgepts);
/*
#if defined(__STDC__) || defined(__cplusplus)
# define _ARGS(s) s
#else
# define _ARGS(s) ()
#endif*/
/* blobs.c
void blob_origin
_ARGS((BLOB *blob,
TPOINT *origin));
void blob_bounding_box
_ARGS((BLOB *blob,
TPOINT *topleft,
TPOINT *botright));
void blobs_bounding_box
_ARGS((BLOB *blobs,
TPOINT *topleft,
TPOINT *botright));
void blobs_origin
_ARGS((BLOB *blobs,
TPOINT *origin));
WIDTH_RECORD *blobs_widths
_ARGS((BLOB *blobs));
int count_blobs
_ARGS((BLOB *blobs));
void delete_word
_ARGS((TWERD *word));
void delete_edgepts
_ARGS((EDGEPT *edgepts));
#undef _ARGS
*/
#endif

View File

@ -20,7 +20,7 @@
#ifndef BLREAD_H
#define BLREAD_H
#include "varable.h"
#include "params.h"
#include "ocrblock.h"
bool read_unlv_file( //print list of sides

214
ccstruct/boxword.cpp Normal file
View File

@ -0,0 +1,214 @@
///////////////////////////////////////////////////////////////////////
// File: boxword.h
// Description: Class to represent the bounding boxes of the output.
// Author: Ray Smith
// Created: Tue May 25 14:18:14 PDT 2010
//
// (C) Copyright 2010, Google Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////
#include "blobs.h"
#include "boxword.h"
#include "normalis.h"
#include "ocrblock.h"
#include "pageres.h"
namespace tesseract {
// Clip output boxes to input blob boxes for bounds that are within this
// tolerance. Otherwise, the blob may be chopped and we have to just use
// the word bounding box.
const int kBoxClipTolerance = 2;
BoxWord::BoxWord() : length_(0) {
}
BoxWord::BoxWord(const BoxWord& src) {
CopyFrom(src);
}
BoxWord::~BoxWord() {
}
BoxWord& BoxWord::operator=(const BoxWord& src) {
CopyFrom(src);
return *this;
}
void BoxWord::CopyFrom(const BoxWord& src) {
bbox_ = src.bbox_;
length_ = src.length_;
boxes_.clear();
boxes_.reserve(length_);
for (int i = 0; i < length_; ++i)
boxes_.push_back(src.boxes_[i]);
}
// Factory to build a BoxWord from a TWERD and the DENORM to switch
// back to original image coordinates.
// If the denorm is not NULL, then the output is denormalized and rotated
// back to the original image coordinates.
BoxWord* BoxWord::CopyFromNormalized(const DENORM* denorm,
TWERD* tessword) {
const BLOCK* block = denorm != NULL ? denorm->block() : NULL;
BoxWord* boxword = new BoxWord();
// Count the blobs.
boxword->length_ = 0;
for (TBLOB* tblob = tessword->blobs; tblob != NULL; tblob = tblob->next)
++boxword->length_;
// Allocate memory.
boxword->boxes_.reserve(boxword->length_);
for (TBLOB* tblob = tessword->blobs; tblob != NULL; tblob = tblob->next) {
TBOX blob_box;
for (TESSLINE* outline = tblob->outlines; outline != NULL;
outline = outline->next) {
EDGEPT* edgept = outline->loop;
// Iterate over the edges.
do {
if (!edgept->IsHidden() || !edgept->prev->IsHidden()) {
ICOORD pos(edgept->pos.x, edgept->pos.y);
if (denorm != NULL) {
FCOORD denormed(denorm->x(edgept->pos.x),
denorm->y(edgept->pos.y, edgept->pos.x));
if (block != NULL)
denormed.rotate(block->re_rotation());
pos.set_x(static_cast<inT16>(floor(denormed.x() + 0.5)));
pos.set_y(static_cast<inT16>(floor(denormed.y() + 0.5)));
}
TBOX pt_box(pos, pos);
blob_box += pt_box;
}
edgept = edgept->next;
} while (edgept != outline->loop);
}
boxword->boxes_.push_back(blob_box);
}
boxword->ComputeBoundingBox();
return boxword;
}
BoxWord* BoxWord::CopyFromPBLOBs(PBLOB_LIST* blobs) {
BoxWord* boxword = new BoxWord();
// Count the blobs.
boxword->length_ = blobs->length();
// Allocate memory.
boxword->boxes_.reserve(boxword->length_);
// Copy the boxes.
PBLOB_IT pb_it(blobs);
int i = 0;
for (pb_it.mark_cycle_pt(); !pb_it.cycled_list(); pb_it.forward(), ++i) {
boxword->boxes_.push_back(pb_it.data()->bounding_box());
}
boxword->ComputeBoundingBox();
return boxword;
}
// Clean up the bounding boxes from the polygonal approximation by
// expanding slightly, then clipping to the blobs from the original_word
// that overlap. If not null, the block provides the inverse rotation.
void BoxWord::ClipToOriginalWord(const BLOCK* block, WERD* original_word) {
for (int i = 0; i < length_; ++i) {
TBOX box = boxes_[i];
// Expand by a single pixel, as the poly approximation error is 1 pixel.
box = TBOX(box.left() - 1, box.bottom() - 1,
box.right() + 1, box.top() + 1);
// Now find the original box that matches.
TBOX original_box;
C_BLOB_IT b_it(original_word->cblob_list());
for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
TBOX blob_box = b_it.data()->bounding_box();
if (block != NULL)
blob_box.rotate(block->re_rotation());
if (blob_box.major_overlap(box)) {
original_box += blob_box;
}
}
if (!original_box.null_box()) {
if (NearlyEqual<int>(original_box.left(), box.left(), kBoxClipTolerance))
box.set_left(original_box.left());
if (NearlyEqual<int>(original_box.right(), box.right(),
kBoxClipTolerance))
box.set_right(original_box.right());
if (NearlyEqual<int>(original_box.top(), box.top(), kBoxClipTolerance))
box.set_top(original_box.top());
if (NearlyEqual<int>(original_box.bottom(), box.bottom(),
kBoxClipTolerance))
box.set_bottom(original_box.bottom());
}
boxes_[i] = box.intersection(original_word->bounding_box());
}
ComputeBoundingBox();
}
// Merges the boxes from start to end, not including end, and deletes
// the boxes between start and end.
void BoxWord::MergeBoxes(int start, int end) {
start = ClipToRange(start, 0, length_);
end = ClipToRange(end, 0, length_);
if (end <= start + 1)
return;
for (int i = start + 1; i < end; ++i) {
boxes_[start] += boxes_[i];
}
int shrinkage = end - 1 - start;
length_ -= shrinkage;
for (int i = start + 1; i < length_; ++i)
boxes_[i] = boxes_[i + shrinkage];
boxes_.truncate(length_);
}
// Inserts a new box before the given index.
// Recomputes the bounding box.
void BoxWord::InsertBox(int index, const TBOX& box) {
if (index < length_)
boxes_.insert(box, index);
else
boxes_.push_back(box);
length_ = boxes_.size();
ComputeBoundingBox();
}
// Deletes the box with the given index, and shuffles up the rest.
// Recomputes the bounding box.
void BoxWord::DeleteBox(int index) {
ASSERT_HOST(0 <= index && index < length_);
boxes_.remove(index);
--length_;
ComputeBoundingBox();
}
// Computes the bounding box of the word.
void BoxWord::ComputeBoundingBox() {
bbox_ = TBOX();
for (int i = 0; i < length_; ++i)
bbox_ += boxes_[i];
}
// This and other putatively are the same, so call the (permanent) callback
// for each blob index where the bounding boxes match.
// The callback is deleted on completion.
void BoxWord::ProcessMatchedBlobs(const TWERD& other,
TessCallback1<int>* cb) const {
TBLOB* blob = other.blobs;
for (int i = 0; i < length_ && blob != NULL; ++i, blob = blob->next) {
TBOX blob_box = blob->bounding_box();
if (blob_box == boxes_[i])
cb->Run(i);
}
delete cb;
}
} // namespace tesseract.

98
ccstruct/boxword.h Normal file
View File

@ -0,0 +1,98 @@
///////////////////////////////////////////////////////////////////////
// File: boxword.h
// Description: Class to represent the bounding boxes of the output.
// Author: Ray Smith
// Created: Tue May 25 14:18:14 PDT 2010
//
// (C) Copyright 2010, Google Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////
#ifndef TESSERACT_CSTRUCT_BOXWORD_H__
#define TESSERACT_CSTRUCT_BOXWORD_H__
#include "genericvector.h"
#include "rect.h"
class BLOCK;
class DENORM;
class PBLOB_LIST;
struct TWERD;
class WERD;
class WERD_RES;
namespace tesseract {
// Class to hold an array of bounding boxes for an output word and
// the bounding box of the whole word.
class BoxWord {
public:
BoxWord();
explicit BoxWord(const BoxWord& src);
~BoxWord();
BoxWord& operator=(const BoxWord& src);
void CopyFrom(const BoxWord& src);
// Factory to build a BoxWord from a TWERD and the DENORM to switch
// back to original image coordinates.
// If the denorm is not NULL, then the output is denormalized and rotated
// back to the original image coordinates.
static BoxWord* CopyFromNormalized(const DENORM* denorm,
TWERD* tessword);
static BoxWord* CopyFromPBLOBs(PBLOB_LIST* blobs);
// Clean up the bounding boxes from the polygonal approximation by
// expanding slightly, then clipping to the blobs from the original_word
// that overlap. If not null, the block provides the inverse rotation.
void ClipToOriginalWord(const BLOCK* block, WERD* original_word);
// Merges the boxes from start to end, not including end, and deletes
// the boxes between start and end.
void MergeBoxes(int start, int end);
// Inserts a new box before the given index.
// Recomputes the bounding box.
void InsertBox(int index, const TBOX& box);
// Deletes the box with the given index, and shuffles up the rest.
// Recomputes the bounding box.
void DeleteBox(int index);
// This and other putatively are the same, so call the (permanent) callback
// for each blob index where the bounding boxes match.
// The callback is deleted on completion.
void ProcessMatchedBlobs(const TWERD& other, TessCallback1<int>* cb) const;
const TBOX& bounding_box() const {
return bbox_;
}
const int length() const {
return length_;
}
const TBOX& BlobBox(int index) const {
return boxes_[index];
}
private:
void ComputeBoundingBox();
TBOX bbox_;
int length_;
GenericVector<TBOX> boxes_;
};
} // namespace tesseract.
#endif // TESSERACT_CSTRUCT_BOXWORD_H__

View File

@ -18,20 +18,18 @@
**********************************************************************/
#include "mfcpch.h"
#include "errcode.h"
#include "errcode.h"
#ifdef __UNIX__
#include <assert.h>
#include <assert.h>
#include <stdarg.h>
#endif
#include <time.h>
#include "memry.h"
#include "scrollview.h"
//#include "evnts.h"
#include "varable.h"
#include "callcpp.h"
#include "tprintf.h"
//#include "strace.h"
#include "host.h"
#include <time.h>
#include "memry.h"
#include "scrollview.h"
#include "params.h"
#include "callcpp.h"
#include "tprintf.h"
#include "host.h"
#include "unichar.h"
// Include automatically generated configuration file if running autoconf.
@ -39,30 +37,6 @@
#include "config_auto.h"
#endif
//extern "C" {
INT_VAR (tess_cp_mapping0, 0, "Mappings for class pruner distance");
INT_VAR (tess_cp_mapping1, 1, "Mappings for class pruner distance");
INT_VAR (tess_cp_mapping2, 2, "Mappings for class pruner distance");
INT_VAR (tess_cp_mapping3, 3, "Mappings for class pruner distance");
INT_VAR (record_matcher_output, 0, "Record detailed matcher info");
INT_VAR (il1_adaption_test, 0, "Dont adapt to i/I at beginning of word");
double_VAR (permuter_pending_threshold, 0.0,
"Worst conf for using pending dictionary");
//Global matcher info from the class pruner.
inT32 cp_maps[4];
//Global info to control writes of matcher info
char blob_answer[UNICHAR_LEN + 1]; //correct char
char *word_answer; //correct word
inT32 bits_in_states; //no of bits in states
void setup_cp_maps() {
cp_maps[0] = tess_cp_mapping0;
cp_maps[1] = tess_cp_mapping1;
cp_maps[2] = tess_cp_mapping2;
cp_maps[3] = tess_cp_mapping3;
}
void
cprintf ( //Trace printf
const char *format, ... //special message
@ -172,6 +146,3 @@ void reverse16(void *ptr) {
*cptr = *(cptr + 1);
*(cptr + 1) = tmp;
}
//};

View File

@ -19,6 +19,15 @@
#include "ccstruct.h"
namespace tesseract {
// APPROXIMATIONS of the fractions of the character cell taken by
// the descenders, ascenders, and x-height.
const double CCStruct::kDescenderFraction = 0.25;
const double CCStruct::kXHeightFraction = 0.5;
const double CCStruct::kAscenderFraction = 0.25;
const double CCStruct::kXHeightCapRatio = CCStruct::kXHeightFraction /
(CCStruct::kXHeightFraction + CCStruct::kAscenderFraction);
CCStruct::CCStruct()
: image_(this) {
}

View File

@ -22,17 +22,21 @@
#include "cutil.h"
#include "image.h"
class PBLOB;
class DENORM;
class WERD;
class BLOB_CHOICE_LIST;
namespace tesseract {
class CCStruct : public CUtil {
public:
CCStruct();
~CCStruct();
// Globally accessible constants.
// APPROXIMATIONS of the fractions of the character cell taken by
// the descenders, ascenders, and x-height.
static const double kDescenderFraction; // = 0.25;
static const double kXHeightFraction; // = 0.5;
static const double kAscenderFraction; // = 0.25;
// Derived value giving the x-height as a fraction of cap-height.
static const double kXHeightCapRatio; // = XHeight/(XHeight + Ascender).
protected:
Image image_;
};
@ -40,13 +44,5 @@ class CCStruct : public CUtil {
class Tesseract;
} // namespace tesseract
typedef void (tesseract::Tesseract::*POLY_MATCHER)
(PBLOB *, PBLOB *, PBLOB *, WERD *,
DENORM *, BLOB_CHOICE_LIST *, const char*);
/*
typedef void (tesseract::Tesseract::*POLY_TESTER)
(const STRING&, PBLOB *, DENORM *, BOOL8, char *,
inT32, BLOB_CHOICE_LIST *);
*/
#endif // TESSERACT_CCSTRUCT_CCSTRUCT_H__

View File

@ -18,11 +18,12 @@
**********************************************************************/
#include "mfcpch.h"
#include <string.h>
#include <string.h>
#ifdef __UNIX__
#include <assert.h>
#include <assert.h>
#endif
#include "coutln.h"
#include "coutln.h"
#include "allheaders.h"
// Include automatically generated configuration file if running autoconf.
#ifdef HAVE_CONFIG_H
@ -620,6 +621,23 @@ void C_OUTLINE::RemoveSmallRecursive(int min_size, C_OUTLINE_IT* it) {
}
}
// Renders the outline to the given pix, with left and top being
// the coords of the upper-left corner of the pix.
void C_OUTLINE::render(int left, int top, Pix* pix) {
ICOORD pos = start;
for (int stepindex = 0; stepindex < stepcount; ++stepindex) {
ICOORD next_step = step(stepindex);
if (next_step.y() < 0) {
pixRasterop(pix, 0, top - pos.y(), pos.x() - left, 1,
PIX_NOT(PIX_DST), NULL, 0, 0);
} else if (next_step.y() > 0) {
pixRasterop(pix, 0, top - pos.y() - 1, pos.x() - left, 1,
PIX_NOT(PIX_DST), NULL, 0, 0);
}
pos += next_step;
}
}
/**********************************************************************
* C_OUTLINE::plot
*
@ -628,15 +646,14 @@ void C_OUTLINE::RemoveSmallRecursive(int min_size, C_OUTLINE_IT* it) {
#ifndef GRAPHICS_DISABLED
void C_OUTLINE::plot( //draw it
ScrollView* window, //window to draw in
ScrollView::Color colour //colour to draw in
ScrollView* window, // window to draw in
ScrollView::Color colour // colour to draw in
) const {
inT16 stepindex; //index to cstep
ICOORD pos; //current position
DIR128 stepdir; //direction of step
DIR128 oldstepdir; //previous stepdir
inT16 stepindex; // index to cstep
ICOORD pos; // current position
DIR128 stepdir; // direction of step
pos = start; //current position
pos = start; // current position
window->Pen(colour);
if (stepcount == 0) {
window->Rectangle(box.left(), box.top(), box.right(), box.bottom());
@ -645,19 +662,17 @@ void C_OUTLINE::plot( //draw it
window->SetCursor(pos.x(), pos.y());
stepindex = 0;
stepdir = step_dir (0); //get direction
while (stepindex < stepcount) {
do {
pos += step (stepindex); //step to next
stepindex++; //count steps
oldstepdir = stepdir;
//new direction
stepdir = step_dir (stepindex);
pos += step(stepindex); // step to next
stepdir = step_dir(stepindex);
stepindex++; // count steps
// merge straight lines
while (stepindex < stepcount &&
stepdir.get_dir() == step_dir(stepindex).get_dir()) {
pos += step(stepindex);
stepindex++;
}
while (stepindex < stepcount
&& oldstepdir.get_dir () == stepdir.get_dir ());
//merge straight lines
window->DrawTo(pos.x(), pos.y());
window->DrawTo(pos.x(), pos.y());
}
}
#endif

View File

@ -38,6 +38,7 @@ enum C_OUTLINE_FLAGS
};
class DLLSYM C_OUTLINE; //forward declaration
struct Pix;
ELISTIZEH_S (C_OUTLINE)
class DLLSYM C_OUTLINE:public ELIST_LINK
@ -149,6 +150,10 @@ class DLLSYM C_OUTLINE:public ELIST_LINK
// then this is extracted from *it, so an iteration can continue.
void RemoveSmallRecursive(int min_size, C_OUTLINE_IT* it);
// Renders the outline to the given pix, with left and top being
// the coords of the upper-left corner of the pix.
void render(int left, int top, Pix* pix);
void plot( //draw one
ScrollView* window, //window to draw in
ScrollView::Color colour) const; //colour to draw it

View File

@ -103,6 +103,104 @@ double DetLineFit::Fit(ICOORD* pt1, ICOORD* pt2) {
return best_uq > 0.0 ? sqrt(best_uq) : best_uq;
}
// Backwards compatible fit returning a gradient and constant.
// Deprecated. Prefer Fit(ICOORD*, ICOORD*) where possible, but use this
// function in preference to the LMS class.
double DetLineFit::Fit(float* m, float* c) {
ICOORD start, end;
double error = Fit(&start, &end);
if (end.x() != start.x()) {
*m = static_cast<float>(end.y() - start.y()) / (end.x() - start.x());
*c = start.y() - *m * start.x();
} else {
*m = 0.0f;
*c = 0.0f;
}
return error;
}
// Helper function to compute a fictitious end point that is on a line
// of a given gradient through the given start.
ICOORD ComputeEndFromGradient(const ICOORD& start, double m) {
if (m > 1.0 || m < -1.0) {
// dy dominates. Force it to have the opposite sign of start.y() and
// compute dx based on dy being as large as possible
int dx = static_cast<int>(floor(MAX_INT16 / m));
if (dx < 0) ++dx; // Truncate towards 0.
if (start.y() > 0) dx = - dx; // Force dy to be opposite to start.y().
// Constrain dx so the result fits in an inT16.
while (start.x() + dx > MAX_INT16 || start.x() + dx < -MAX_INT16)
dx /= 2;
if (-1 <= dx && dx <= 1) {
return ICOORD(start.x(), start.y() + 1); // Too steep for anything else.
}
int y = start.y() + static_cast<int>(floor(dx * m + 0.5));
ASSERT_HOST(-MAX_INT16 <= y && y <= MAX_INT16);
return ICOORD(start.x() + dx, y);
} else {
// dx dominates. Force it to have the opposite sign of start.x() and
// compute dy based on dx being as large as possible.
int dy = static_cast<int>(floor(MAX_INT16 * m));
if (dy < 0) ++dy; // Truncate towards 0.
if (start.x() > 0) dy = - dy; // Force dx to be opposite to start.x().
// Constrain dy so the result fits in an inT16.
while (start.y() + dy > MAX_INT16 || start.y() + dy < -MAX_INT16)
dy /= 2;
if (-1 <= dy && dy <= 1) {
return ICOORD(start.x() + 1, start.y()); // Too flat for anything else.
}
int x = start.x() + static_cast<int>(floor(dy / m + 0.5));
ASSERT_HOST(-MAX_INT16 <= x && x <= MAX_INT16);
return ICOORD(x, start.y() + dy);
}
}
// Backwards compatible constrained fit with a supplied gradient.
double DetLineFit::ConstrainedFit(double m, float* c) {
ICOORDELT_IT it(&pt_list_);
// Do something sensible with no points.
if (pt_list_.empty()) {
*c = 0.0f;
return 0.0;
}
// Count the points and find the first and last kNumEndPoints.
// Put the ends in a single array to make their use easier later.
ICOORD* pts[kNumEndPoints * 2];
int pt_count = 0;
for (it.mark_cycle_pt(); !it.cycled_list(); it.forward()) {
if (pt_count < kNumEndPoints) {
pts[pt_count] = it.data();
pts[kNumEndPoints + pt_count] = pts[pt_count];
} else {
for (int i = 1; i < kNumEndPoints; ++i)
pts[kNumEndPoints + i - 1] = pts[kNumEndPoints + i];
pts[kNumEndPoints * 2 - 1] = it.data();
}
++pt_count;
}
while (pt_count < kNumEndPoints) {
pts[pt_count] = NULL;
pts[kNumEndPoints + pt_count++] = NULL;
}
int* distances = new int[pt_count];
double best_uq = -1.0;
// Iterate each pair of points and find the best fitting line.
for (int i = 0; i < kNumEndPoints * 2; ++i) {
ICOORD* start = pts[i];
if (start == NULL) continue;
ICOORD end = ComputeEndFromGradient(*start, m);
// Compute the upper quartile error from the line.
double dist = ComputeErrors(*start, end, distances);
if (dist < best_uq || best_uq < 0.0) {
best_uq = dist;
*c = start->y() - start->x() * m;
}
}
delete [] distances;
// Finally compute the square root to return the true distance.
return best_uq > 0.0 ? sqrt(best_uq) : best_uq;
}
// Comparator function used by the nth_item funtion.
static int CompareInts(const void *p1, const void *p2) {
const int* i1 = reinterpret_cast<const int*>(p1);

View File

@ -67,6 +67,14 @@ class DetLineFit {
// points, and the upper quartile error.
double Fit(ICOORD* pt1, ICOORD* pt2);
// Backwards compatible fit returning a gradient and constant.
// Deprecated. Prefer Fit(ICOORD*, ICOORD*) where possible, but use this
// function in preference to the LMS class.
double Fit(float* m, float* c);
// Backwards compatible constrained fit with a supplied gradient.
double ConstrainedFit(double m, float* c);
private:
double ComputeErrors(const ICOORD start, const ICOORD end, int* distances);

98
ccstruct/dppoint.cpp Normal file
View File

@ -0,0 +1,98 @@
/**********************************************************************
* File: dppoint.cpp
* Description: Simple generic dynamic programming class.
* Author: Ray Smith
* Created: Wed Mar 25 19:08:01 PDT 2009
*
* (C) Copyright 2009, Google Inc.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
#include "dppoint.h"
#include "tprintf.h"
namespace tesseract {
// Solve the dynamic programming problem for the given array of points, with
// the given size and cost function.
// Steps backwards are limited to being between min_step and max_step
// inclusive.
// The return value is the tail of the best path.
DPPoint* DPPoint::Solve(int min_step, int max_step, bool debug,
CostFunc cost_func, int size, DPPoint* points) {
if (size <= 0 || max_step < min_step || min_step >= size)
return NULL; // Degenerate, but not necessarily an error.
ASSERT_HOST(min_step > 0); // Infinite loop possible if this is not true.
if (debug)
tprintf("min = %d, max=%d\n",
min_step, max_step);
// Evaluate the total cost at each point.
for (int i = 0; i < size; ++i) {
for (int offset = min_step; offset <= max_step; ++offset) {
DPPoint* prev = offset <= i ? points + i - offset : NULL;
inT64 new_cost = (points[i].*cost_func)(prev);
if (points[i].best_prev_ != NULL && offset > min_step * 2 &&
new_cost > points[i].total_cost_)
break; // Find only the first minimum if going over twice the min.
}
points[i].total_cost_ += points[i].local_cost_;
if (debug) {
tprintf("At point %d, local cost=%d, total_cost=%d, steps=%d\n",
i, points[i].local_cost_, points[i].total_cost_,
points[i].total_steps_);
}
}
// Now find the end of the best path and return it.
int best_cost = points[size - 1].total_cost_;
int best_end = size - 1;
for (int end = best_end - 1; end >= size - min_step; --end) {
int cost = points[end].total_cost_;
if (cost < best_cost) {
best_cost = cost;
best_end = end;
}
}
return points + best_end;
}
// A CostFunc that takes the variance of step into account in the cost.
inT64 DPPoint::CostWithVariance(const DPPoint* prev) {
if (prev == NULL || prev == this) {
UpdateIfBetter(0, 1, NULL, 0, 0, 0);
return 0;
}
int delta = this - prev;
inT32 n = prev->n_ + 1;
inT32 sig_x = prev->sig_x_ + delta;
inT64 sig_xsq = prev->sig_xsq_ + delta * delta;
inT64 cost = (sig_xsq - sig_x * sig_x / n) / n;
cost += prev->total_cost_;
UpdateIfBetter(cost, prev->total_steps_ + 1, prev, n, sig_x, sig_xsq);
return cost;
}
// Update the other members if the cost is lower.
void DPPoint::UpdateIfBetter(inT64 cost, inT32 steps, const DPPoint* prev,
inT32 n, inT32 sig_x, inT64 sig_xsq) {
if (cost < total_cost_) {
total_cost_ = cost;
total_steps_ = steps;
best_prev_ = prev;
n_ = n;
sig_x_ = sig_x;
sig_xsq_ = sig_xsq;
}
}
} // namespace tesseract.

102
ccstruct/dppoint.h Normal file
View File

@ -0,0 +1,102 @@
/**********************************************************************
* File: dppoint.h
* Description: Simple generic dynamic programming class.
* Author: Ray Smith
* Created: Wed Mar 25 18:57:01 PDT 2009
*
* (C) Copyright 2009, Google Inc.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
#ifndef TESSERACT_CCSTRUCT_DPPOINT_H__
#define TESSERACT_CCSTRUCT_DPPOINT_H__
#include "host.h"
namespace tesseract {
// A simple class to provide a dynamic programming solution to a class of
// 1st-order problems in which the cost is dependent only on the current
// step and the best cost to that step, with a possible special case
// of using the variance of the steps, and only the top choice is required.
// Useful for problems such as finding the optimal cut points in a fixed-pitch
// (vertical or horizontal) situation.
// Skeletal Example:
// DPPoint* array = new DPPoint[width];
// for (int i = 0; i < width; i++) {
// array[i].AddLocalCost(cost_at_i)
// }
// DPPoint* best_end = DPPoint::Solve(..., array);
// while (best_end != NULL) {
// int cut_index = best_end - array;
// best_end = best_end->best_prev();
// }
// delete [] array;
class DPPoint {
public:
// The cost function evaluates the total cost at this (excluding this's
// local_cost) and if it beats this's total_cost, then
// replace the appropriate values in this.
typedef inT64 (DPPoint::*CostFunc)(const DPPoint* prev);
DPPoint()
: local_cost_(0), total_cost_(MAX_INT32), total_steps_(1), best_prev_(NULL),
n_(0), sig_x_(0), sig_xsq_(0) {
}
// Solve the dynamic programming problem for the given array of points, with
// the given size and cost function.
// Steps backwards are limited to being between min_step and max_step
// inclusive.
// The return value is the tail of the best path.
static DPPoint* Solve(int min_step, int max_step, bool debug,
CostFunc cost_func, int size, DPPoint* points);
// A CostFunc that takes the variance of step into account in the cost.
inT64 CostWithVariance(const DPPoint* prev);
// Accessors.
int total_cost() const {
return total_cost_;
}
int Pathlength() const {
return total_steps_;
}
const DPPoint* best_prev() const {
return best_prev_;
}
void AddLocalCost(int new_cost) {
local_cost_ += new_cost;
}
private:
// Code common to different cost functions.
// Update the other members if the cost is lower.
void UpdateIfBetter(inT64 cost, inT32 steps, const DPPoint* prev,
inT32 n, inT32 sig_x, inT64 sig_xsq);
inT32 local_cost_; // Cost of this point on its own.
inT32 total_cost_; // Sum of all costs in best path to here.
// During cost calculations local_cost is excluded.
inT32 total_steps_; // Number of steps in best path to here.
const DPPoint* best_prev_; // Pointer to prev point in best path from here.
// Information for computing the variance part of the cost.
inT32 n_; // Number of steps in best path to here for variance.
inT32 sig_x_; // Sum of step sizes for computing variance.
inT64 sig_xsq_; // Sum of squares of steps for computing variance.
};
} // namespace tesseract.
#endif // TESSERACT_CCSTRUCT_DPPOINT_H__

View File

@ -1,193 +0,0 @@
/**********************************************************************
* File: labls.c (Formerly labels.c)
* Description: Attribute definition tables
* Author: Sheelagh Lloyd?
* Created:
*
* (C) Copyright 1993, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
// Include automatically generated configuration file if running autoconf.
#ifdef HAVE_CONFIG_H
#include "config_auto.h"
#endif
#include "mfcpch.h"
#include "hpdsizes.h"
#include "labls.h"
/******************************************************************************
* TEXT REGIONS
*****************************************************************************/
DLLSYM inT32 tn[NUM_TEXT_ATTR] = {
3, //T_HORIZONTAL
4, //T_TEXT
2, //T_SERIF
2, //T_PROPORTIONAL
2, //T_NORMAL
2, //T_UPRIGHT
2, //T_SOLID
3, //T_BLACK
2, //T_NOTUNDER
2, //T_NOTDROP
};
DLLSYM char tlabel[NUM_TEXT_ATTR][4][MAXLENGTH] = { {
//T_HORIZONTAL
"Horizontal",
"Vertical",
"Skew",
""
},
{ //T_TEXT
"Text",
"Table",
"Form",
"Mixed"
},
{ //T_SERIF
"Serif",
"Sans-serif",
"",
""
},
{ //T_PROPORTIONAL
"Proportional",
"Fixed pitch",
"",
""
},
{ //T_NORMAL
"Normal",
"Bold",
"",
""
},
{ //T_UPRIGHT
"Upright",
"Italic",
"",
""
},
{ //T_SOLID
"Solid",
"Outline",
"",
""
},
{ //T_BLACK
"Black",
"White",
"Coloured",
""
},
{ //T_NOTUNDER
"Not underlined",
"Underlined",
"",
""
},
{ //T_NOTDROP
"Not drop caps",
"Drop Caps",
"",
""
}
};
DLLSYM inT32 bn[NUM_BLOCK_ATTR] = {
4, //G_MONOCHROME
2, //I_MONOCHROME
2, //I_SMOOTH
3, //R_SINGLE
3, //R_BLACK
3, //S_BLACK
2 //W_TEXT
};
DLLSYM inT32 tvar[NUM_TEXT_ATTR];
DLLSYM inT32 bvar[NUM_BLOCK_ATTR];
DLLSYM char blabel[NUM_BLOCK_ATTR][4][MAXLENGTH] = { {
//G_MONOCHROME
/****************************************************************************
* GRAPHICS
***************************************************************************/
"Monochrome ",
"Two colour ",
"Spot colour",
"Multicolour"
},
/****************************************************************************
* IMAGE
***************************************************************************/
{ //I_MONOCHROME
"Monochrome ",
"Colour ",
"",
""
},
{ //I_SMOOTH
"Smooth ",
"Grainy ",
"",
""
},
/****************************************************************************
* RULES
***************************************************************************/
{ //R_SINGLE
"Single ",
"Double ",
"Multiple",
""
},
{ //R_BLACK
"Black ",
"White ",
"Coloured",
""
},
/****************************************************************************
* SCRIBBLE
***************************************************************************/
{ //S_BLACK
"Black ",
"White ",
"Coloured",
""
},
/****************************************************************************
* WEIRD
***************************************************************************/
{ //W_TEXT
"No text ",
"Contains text",
"",
""
}
};
DLLSYM char backlabel[NUM_BACKGROUNDS][MAXLENGTH] = {
"White", //B_WHITE
"Black", //B_BLACK
"Coloured", //B_COLOURED
"Textured", //B_TEXTURED
"Patterned", //B_PATTERNED
"Gradient fill", //B_GRADIENTFILL
"Image", //B_IMAGE
"Text" //B_TEXT
};

View File

@ -1,38 +0,0 @@
/**********************************************************************
* File: labls.h (Formerly labels.h)
* Description: Attribute definition tables
* Author: Sheelagh Lloyd?
* Created:
*
* (C) Copyright 1993, Hewlett-Packard Ltd.
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
** http://www.apache.org/licenses/LICENSE-2.0
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*
**********************************************************************/
#ifndef LABLS_H
#define LABLS_H
#include "host.h"
#include "hpdsizes.h"
#include "hpddef.h" //must be last (handpd.dll)
extern DLLSYM inT32 tn[NUM_TEXT_ATTR];
extern DLLSYM char tlabel[NUM_TEXT_ATTR][4][MAXLENGTH];
extern DLLSYM inT32 bn[NUM_BLOCK_ATTR];
extern DLLSYM inT32 tvar[NUM_TEXT_ATTR];
extern DLLSYM inT32 bvar[NUM_BLOCK_ATTR];
extern DLLSYM char blabel[NUM_BLOCK_ATTR][4][MAXLENGTH];
extern DLLSYM char backlabel[NUM_BACKGROUNDS][MAXLENGTH];
#endif

View File

@ -31,9 +31,8 @@ const ERRCODE EMPTY_LLSQ = "Can't delete from an empty LLSQ";
#define EXTERN
EXTERN double_VAR (pdlsq_posdir_ratio, 4e-6, "Mult of dir to cf pos");
EXTERN double_VAR (pdlsq_threshold_angleavg, 0.1666666,
"Frac of pi for simple fit");
const double kPdlsqPosdirRatio = 4e-6f; // Mult of dir to cf pos
const double kPdlsqThresholdAngleAvg = 0.166666f; // Frac of pi for simple fit
/**********************************************************************
* LLSQ::clear
@ -192,11 +191,11 @@ float PDLSQ::fit( //get fit
if (pos.n > 0) {
a = pos.sigxy - pos.sigx * pos.sigy / pos.n
+ pdlsq_posdir_ratio * dir.sigxy;
+ kPdlsqPosdirRatio * dir.sigxy;
b =
pos.sigxx - pos.sigyy + (pos.sigy * pos.sigy -
pos.sigx * pos.sigx) / pos.n +
pdlsq_posdir_ratio * (dir.sigxx - dir.sigyy);
kPdlsqPosdirRatio * (dir.sigxx - dir.sigyy);
if (dir.sigy != 0 || dir.sigx != 0)
avg_angle = atan2 (dir.sigy, dir.sigx);
else
@ -214,8 +213,8 @@ float PDLSQ::fit( //get fit
error += M_PI;
angle -= M_PI;
}
if (error > M_PI * pdlsq_threshold_angleavg
|| error < -M_PI * pdlsq_threshold_angleavg)
if (error > M_PI * kPdlsqThresholdAngleAvg ||
error < -M_PI * kPdlsqThresholdAngleAvg)
angle = avg_angle; //go simple
//convert direction
ang = (inT16) (angle * MODULUS / (2 * M_PI));
@ -227,7 +226,7 @@ float PDLSQ::fit( //get fit
// a,b,angle,r);
error = dir.sigxx * sinx * sinx + dir.sigyy * cosx * cosx
- 2 * dir.sigxy * sinx * cosx;
error *= pdlsq_posdir_ratio;
error *= kPdlsqPosdirRatio;
error += sinx * sinx * pos.sigxx + cosx * cosx * pos.sigyy
- 2 * sinx * cosx * pos.sigxy
- 2 * r * (sinx * pos.sigx - cosx * pos.sigy) + r * r * pos.n;

Some files were not shown because too many files have changed in this diff Show More